diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index a7ed83f..a02f86d 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -4090,4 +4090,13 @@ target_link_libraries(step578_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step579_test tests/step579_test.cpp) +target_include_directories(step579_test PRIVATE src) +target_link_libraries(step579_test PRIVATE + nlohmann_json::nlohmann_json + unofficial::tree-sitter::tree-sitter + tree_sitter_python tree_sitter_cpp tree_sitter_elisp + tree_sitter_javascript tree_sitter_typescript + tree_sitter_java tree_sitter_rust tree_sitter_go) + # Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies) diff --git a/editor/src/TaskitemGeneratorV2.h b/editor/src/TaskitemGeneratorV2.h new file mode 100644 index 0000000..7d4a490 --- /dev/null +++ b/editor/src/TaskitemGeneratorV2.h @@ -0,0 +1,65 @@ +#pragma once +// Step 579: Taskitem Generator v2 + +#include "ScopeMilestoneDecomposer.h" + +#include +#include +#include + +struct GeneratedTaskitem { + std::string taskId; + std::string title; + std::string milestoneId; + std::vector dependencyTaskIds; + std::vector prerequisiteOps; + bool queueReady = false; +}; + +class TaskitemGeneratorV2 { +public: + static bool generate(const DecomposedScopePlan& plan, + std::vector* outTasks, + std::string* error) { + if (!outTasks || !error) return false; + error->clear(); + outTasks->clear(); + if (plan.milestones.empty()) { + *error = "plan_empty"; + return false; + } + + std::map workstreamToTask; + int nextId = 1; + for (std::size_t mi = 0; mi < plan.milestones.size(); ++mi) { + const auto& milestone = plan.milestones[mi]; + for (const auto& workstream : milestone.workstreams) { + GeneratedTaskitem task; + task.taskId = "task-" + std::to_string(nextId++); + task.title = workstream.title; + task.milestoneId = milestone.milestoneId; + task.prerequisiteOps = inferPrerequisiteOps(workstream); + task.queueReady = !task.prerequisiteOps.empty() && !workstream.requirementIds.empty(); + if (mi > 0 && !outTasks->empty()) task.dependencyTaskIds.push_back(outTasks->back().taskId); + workstreamToTask[workstream.workstreamId] = task.taskId; + outTasks->push_back(task); + } + } + + if (outTasks->empty()) { + *error = "tasks_empty"; + return false; + } + return true; + } + +private: + static std::vector inferPrerequisiteOps(const DecomposedWorkstream& workstream) { + std::vector ops; + ops.push_back("validate-intake"); + if (workstream.uncertaintyScore >= 35) ops.push_back("architect-review"); + if (workstream.title.find("Execution") != std::string::npos) ops.push_back("resolve-dependencies"); + if (workstream.title.find("Review") != std::string::npos) ops.push_back("manual-approval"); + return ops; + } +}; diff --git a/editor/tests/step579_test.cpp b/editor/tests/step579_test.cpp new file mode 100644 index 0000000..2bf042b --- /dev/null +++ b/editor/tests/step579_test.cpp @@ -0,0 +1,225 @@ +// Step 579: Taskitem Generator v2 (12 tests) + +#include "TaskitemGeneratorV2.h" + +#include + +static int passed = 0, failed = 0; +#define TEST(name) { std::cout << " " << #name << "... "; } +#define PASS() { std::cout << "PASS\n"; ++passed; } +#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; } +#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {} + +static DecomposedWorkstream ws(const std::string& id, + const std::string& title, + int uncertainty, + const std::vector& reqIds) { + DecomposedWorkstream w; + w.workstreamId = id; + w.title = title; + w.uncertaintyScore = uncertainty; + w.requirementIds = reqIds; + return w; +} + +void test_generate_success() { + TEST(generate_success); + DecomposedScopePlan plan; + DecomposedMilestone m; + m.milestoneId = "m1"; + m.title = "M1"; + m.workstreams.push_back(ws("ws1", "Primary", 10, {"r1"})); + plan.milestones.push_back(m); + + std::vector tasks; + std::string error; + CHECK(TaskitemGeneratorV2::generate(plan, &tasks, &error), "generate should succeed"); + CHECK(tasks.size() == 1, "task count mismatch"); + PASS(); +} + +void test_generate_fails_for_empty_plan() { + TEST(generate_fails_for_empty_plan); + DecomposedScopePlan plan; + std::vector tasks; + std::string error; + CHECK(!TaskitemGeneratorV2::generate(plan, &tasks, &error), "generate should fail"); + CHECK(error == "plan_empty", "wrong error"); + PASS(); +} + +void test_task_ids_increment() { + TEST(task_ids_increment); + DecomposedScopePlan plan; + DecomposedMilestone m; + m.milestoneId = "m1"; + m.workstreams.push_back(ws("ws1", "A", 10, {"r1"})); + m.workstreams.push_back(ws("ws2", "B", 10, {"r2"})); + plan.milestones.push_back(m); + std::vector tasks; + std::string error; + CHECK(TaskitemGeneratorV2::generate(plan, &tasks, &error), "generate should succeed"); + CHECK(tasks[0].taskId == "task-1", "first task id mismatch"); + CHECK(tasks[1].taskId == "task-2", "second task id mismatch"); + PASS(); +} + +void test_task_title_and_milestone_mapped() { + TEST(task_title_and_milestone_mapped); + DecomposedScopePlan plan; + DecomposedMilestone m; + m.milestoneId = "milestone-77"; + m.workstreams.push_back(ws("ws1", "Execution Readiness Primary", 10, {"r1"})); + plan.milestones.push_back(m); + std::vector tasks; + std::string error; + CHECK(TaskitemGeneratorV2::generate(plan, &tasks, &error), "generate should succeed"); + CHECK(tasks[0].title == "Execution Readiness Primary", "title mismatch"); + CHECK(tasks[0].milestoneId == "milestone-77", "milestone mismatch"); + PASS(); +} + +void test_queue_ready_requires_prereqs_and_requirements() { + TEST(queue_ready_requires_prereqs_and_requirements); + DecomposedScopePlan plan; + DecomposedMilestone m; + m.milestoneId = "m1"; + m.workstreams.push_back(ws("ws1", "Primary", 10, {})); + plan.milestones.push_back(m); + std::vector tasks; + std::string error; + CHECK(TaskitemGeneratorV2::generate(plan, &tasks, &error), "generate should succeed"); + CHECK(!tasks[0].queueReady, "queueReady should be false with no requirements"); + PASS(); +} + +void test_uncertainty_adds_architect_review_prereq() { + TEST(uncertainty_adds_architect_review_prereq); + DecomposedScopePlan plan; + DecomposedMilestone m; + m.milestoneId = "m1"; + m.workstreams.push_back(ws("ws1", "Primary", 50, {"r1"})); + plan.milestones.push_back(m); + std::vector tasks; + std::string error; + CHECK(TaskitemGeneratorV2::generate(plan, &tasks, &error), "generate should succeed"); + bool found = false; + for (const auto& op : tasks[0].prerequisiteOps) if (op == "architect-review") found = true; + CHECK(found, "architect-review prerequisite expected"); + PASS(); +} + +void test_execution_title_adds_dependency_resolution_prereq() { + TEST(execution_title_adds_dependency_resolution_prereq); + DecomposedScopePlan plan; + DecomposedMilestone m; + m.milestoneId = "m1"; + m.workstreams.push_back(ws("ws1", "Execution Ready Stream", 10, {"r1"})); + plan.milestones.push_back(m); + std::vector tasks; + std::string error; + CHECK(TaskitemGeneratorV2::generate(plan, &tasks, &error), "generate should succeed"); + bool found = false; + for (const auto& op : tasks[0].prerequisiteOps) if (op == "resolve-dependencies") found = true; + CHECK(found, "resolve-dependencies prerequisite expected"); + PASS(); +} + +void test_review_title_adds_manual_approval_prereq() { + TEST(review_title_adds_manual_approval_prereq); + DecomposedScopePlan plan; + DecomposedMilestone m; + m.milestoneId = "m1"; + m.workstreams.push_back(ws("ws1", "Architect Review", 10, {"r1"})); + plan.milestones.push_back(m); + std::vector tasks; + std::string error; + CHECK(TaskitemGeneratorV2::generate(plan, &tasks, &error), "generate should succeed"); + bool found = false; + for (const auto& op : tasks[0].prerequisiteOps) if (op == "manual-approval") found = true; + CHECK(found, "manual-approval prerequisite expected"); + PASS(); +} + +void test_second_milestone_task_depends_on_prior_task() { + TEST(second_milestone_task_depends_on_prior_task); + DecomposedScopePlan plan; + DecomposedMilestone a; + a.milestoneId = "m1"; + a.workstreams.push_back(ws("ws1", "A", 10, {"r1"})); + DecomposedMilestone b; + b.milestoneId = "m2"; + b.workstreams.push_back(ws("ws2", "B", 10, {"r2"})); + plan.milestones.push_back(a); + plan.milestones.push_back(b); + std::vector tasks; + std::string error; + CHECK(TaskitemGeneratorV2::generate(plan, &tasks, &error), "generate should succeed"); + CHECK(tasks[1].dependencyTaskIds.size() == 1, "dependency expected"); + CHECK(tasks[1].dependencyTaskIds[0] == tasks[0].taskId, "dependency id mismatch"); + PASS(); +} + +void test_tasks_empty_error_when_milestones_without_workstreams() { + TEST(tasks_empty_error_when_milestones_without_workstreams); + DecomposedScopePlan plan; + DecomposedMilestone m; + m.milestoneId = "m1"; + plan.milestones.push_back(m); + std::vector tasks; + std::string error; + CHECK(!TaskitemGeneratorV2::generate(plan, &tasks, &error), "generate should fail"); + CHECK(error == "tasks_empty", "wrong error"); + PASS(); +} + +void test_validate_intake_prerequisite_always_present() { + TEST(validate_intake_prerequisite_always_present); + DecomposedScopePlan plan; + DecomposedMilestone m; + m.milestoneId = "m1"; + m.workstreams.push_back(ws("ws1", "Any", 0, {"r1"})); + plan.milestones.push_back(m); + std::vector tasks; + std::string error; + CHECK(TaskitemGeneratorV2::generate(plan, &tasks, &error), "generate should succeed"); + CHECK(!tasks[0].prerequisiteOps.empty(), "prerequisite ops expected"); + CHECK(tasks[0].prerequisiteOps[0] == "validate-intake", "validate-intake should be first"); + PASS(); +} + +void test_multiple_workstreams_generate_multiple_tasks() { + TEST(multiple_workstreams_generate_multiple_tasks); + DecomposedScopePlan plan; + DecomposedMilestone m; + m.milestoneId = "m1"; + m.workstreams.push_back(ws("ws1", "A", 10, {"r1"})); + m.workstreams.push_back(ws("ws2", "B", 10, {"r2"})); + m.workstreams.push_back(ws("ws3", "C", 10, {"r3"})); + plan.milestones.push_back(m); + std::vector tasks; + std::string error; + CHECK(TaskitemGeneratorV2::generate(plan, &tasks, &error), "generate should succeed"); + CHECK(tasks.size() == 3, "three tasks expected"); + PASS(); +} + +int main() { + std::cout << "Step 579: Taskitem Generator v2\n"; + + test_generate_success(); // 1 + test_generate_fails_for_empty_plan(); // 2 + test_task_ids_increment(); // 3 + test_task_title_and_milestone_mapped(); // 4 + test_queue_ready_requires_prereqs_and_requirements(); // 5 + test_uncertainty_adds_architect_review_prereq(); // 6 + test_execution_title_adds_dependency_resolution_prereq();// 7 + test_review_title_adds_manual_approval_prereq(); // 8 + test_second_milestone_task_depends_on_prior_task(); // 9 + test_tasks_empty_error_when_milestones_without_workstreams();// 10 + test_validate_intake_prerequisite_always_present(); // 11 + test_multiple_workstreams_generate_multiple_tasks(); // 12 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index f774d69..4285438 100644 --- a/progress.md +++ b/progress.md @@ -10733,3 +10733,38 @@ normalization/conflict detection, decomposition, and architect review workflow. **Phase 32a totals (574-578):** - **Steps completed:** 5 - **New tests in this phase plan:** 56/56 passing + +### Step 579: Taskitem Generator v2 +**Status:** PASS (12/12 tests) + +Implements v2 taskitem generation from decomposed scope plans, including +queue-readiness metadata, prerequisite operations, and dependency hints. + +**Files added:** +- `editor/src/TaskitemGeneratorV2.h` - task generator module: + - taskitem generation from milestone/workstream decomposition + - stable task id generation + - prerequisite operation inference for constrained execution + - queue-readiness gating based on requirement/prereq presence + - milestone-order dependency hint emission +- `editor/tests/step579_test.cpp` - 12 tests covering: + - generation success/failure behavior + - id/title/milestone mapping behavior + - queue-readiness behavior + - prerequisite inference behavior + - dependency-hint behavior + - empty-workstream handling behavior + +**Files modified:** +- `editor/CMakeLists.txt` - `step579_test` target + +**Verification run:** +- `cmake -S editor -B editor/build-native` - PASS +- `cmake --build editor/build-native --target step579_test step578_test` - PASS +- `./editor/build-native/step579_test` - PASS (12/12) +- `./editor/build-native/step578_test` - PASS (8/8) regression coverage + +**Architecture gate check:** +- `editor/src/TaskitemGeneratorV2.h` within header-size limit (`65` <= `600`) +- `editor/tests/step579_test.cpp` within test-file size guidance (`225` lines) +- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`