Step 579: add taskitem generator v2
This commit is contained in:
@@ -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)
|
||||
|
||||
65
editor/src/TaskitemGeneratorV2.h
Normal file
65
editor/src/TaskitemGeneratorV2.h
Normal file
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
// Step 579: Taskitem Generator v2
|
||||
|
||||
#include "ScopeMilestoneDecomposer.h"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct GeneratedTaskitem {
|
||||
std::string taskId;
|
||||
std::string title;
|
||||
std::string milestoneId;
|
||||
std::vector<std::string> dependencyTaskIds;
|
||||
std::vector<std::string> prerequisiteOps;
|
||||
bool queueReady = false;
|
||||
};
|
||||
|
||||
class TaskitemGeneratorV2 {
|
||||
public:
|
||||
static bool generate(const DecomposedScopePlan& plan,
|
||||
std::vector<GeneratedTaskitem>* outTasks,
|
||||
std::string* error) {
|
||||
if (!outTasks || !error) return false;
|
||||
error->clear();
|
||||
outTasks->clear();
|
||||
if (plan.milestones.empty()) {
|
||||
*error = "plan_empty";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> 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<std::string> inferPrerequisiteOps(const DecomposedWorkstream& workstream) {
|
||||
std::vector<std::string> 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;
|
||||
}
|
||||
};
|
||||
225
editor/tests/step579_test.cpp
Normal file
225
editor/tests/step579_test.cpp
Normal file
@@ -0,0 +1,225 @@
|
||||
// Step 579: Taskitem Generator v2 (12 tests)
|
||||
|
||||
#include "TaskitemGeneratorV2.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
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<std::string>& 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<GeneratedTaskitem> 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<GeneratedTaskitem> 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<GeneratedTaskitem> 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<GeneratedTaskitem> 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<GeneratedTaskitem> 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<GeneratedTaskitem> 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<GeneratedTaskitem> 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<GeneratedTaskitem> 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<GeneratedTaskitem> 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<GeneratedTaskitem> 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<GeneratedTaskitem> 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<GeneratedTaskitem> 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;
|
||||
}
|
||||
35
progress.md
35
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`
|
||||
|
||||
Reference in New Issue
Block a user