diff --git a/agents/runner.py b/agents/runner.py index 5a7c295..0a24101 100644 --- a/agents/runner.py +++ b/agents/runner.py @@ -1090,15 +1090,6 @@ def _execute_department_head_step( role = step["role"] dept_name = role.replace("_head", "") - # Create child pipeline in DB - child_pipeline = models.create_pipeline( - conn, task_id, project_id, - route_type="dept_sub", - steps=sub_pipeline, - parent_pipeline_id=parent_pipeline_id, - department=dept_name, - ) - # Build initial context for workers: dept head's plan + artifacts dept_plan_context = json.dumps({ "department_head_plan": { @@ -1109,12 +1100,16 @@ def _execute_department_head_step( }, ensure_ascii=False) # Run the sub-pipeline (noninteractive=True — Opus already reviewed the plan) + # pass parent_pipeline_id and department so run_pipeline creates the child + # pipeline with correct attributes (route_type='dept_sub') — no double create sub_result = run_pipeline( conn, task_id, sub_pipeline, dry_run=False, allow_write=allow_write, noninteractive=True, initial_previous_output=dept_plan_context, + parent_pipeline_id=parent_pipeline_id, + department=dept_name, ) # Extract decisions from sub-pipeline results for handoff diff --git a/core/db.py b/core/db.py index 2272be7..cebb6f0 100644 --- a/core/db.py +++ b/core/db.py @@ -612,16 +612,18 @@ def _migrate(conn: sqlite3.Connection): conn.commit() # Migrate pipelines: add parent_pipeline_id and department columns (KIN-098) - pipeline_cols = {r[1] for r in conn.execute("PRAGMA table_info(pipelines)").fetchall()} - if "parent_pipeline_id" not in pipeline_cols: - conn.execute("ALTER TABLE pipelines ADD COLUMN parent_pipeline_id INTEGER REFERENCES pipelines(id)") - conn.commit() - if "department" not in pipeline_cols: - conn.execute("ALTER TABLE pipelines ADD COLUMN department TEXT") - conn.commit() - if "pid" not in pipeline_cols: - conn.execute("ALTER TABLE pipelines ADD COLUMN pid INTEGER") - conn.commit() + # Guard: table may not exist in legacy schemas without pipelines (old test fixtures) + if "pipelines" in existing_tables: + pipeline_cols = {r[1] for r in conn.execute("PRAGMA table_info(pipelines)").fetchall()} + if "parent_pipeline_id" not in pipeline_cols: + conn.execute("ALTER TABLE pipelines ADD COLUMN parent_pipeline_id INTEGER REFERENCES pipelines(id)") + conn.commit() + if "department" not in pipeline_cols: + conn.execute("ALTER TABLE pipelines ADD COLUMN department TEXT") + conn.commit() + if "pid" not in pipeline_cols: + conn.execute("ALTER TABLE pipelines ADD COLUMN pid INTEGER") + conn.commit() # Create department_handoffs table (KIN-098) if "department_handoffs" not in existing_tables: diff --git a/tests/test_qa_gaps.py b/tests/test_qa_gaps.py index 586e1c6..bfce568 100644 --- a/tests/test_qa_gaps.py +++ b/tests/test_qa_gaps.py @@ -394,22 +394,19 @@ class TestPmPromptStatusFieldConsistency: "Issue 3 has been fixed — update or remove this test." ) - def test_pm_prompt_mentions_status_values_after_example(self): - """Line after main example mentions 'Valid values for status: done, blocked'. + def test_pm_prompt_does_not_mention_status_values_after_example(self): + """Orphaned 'Valid values for status' line was removed from pm.md. - Documents that this note appears AFTER the main example, causing ambiguity. + Issue 3 fixed: the ambiguous note that appeared after the main JSON example + and referenced a 'status' field absent from that example has been deleted. """ with open("agents/prompts/pm.md") as f: content = f.read() - assert 'Valid values for `status`' in content, ( - "Expected 'Valid values for `status`' in pm.md — did the prompt change?" + assert 'Valid values for `status`' not in content, ( + "Orphaned 'Valid values for `status`' line was re-introduced in pm.md" ) - # Confirm it references done and blocked - assert '"done"' in content - assert '"blocked"' in content - def test_pm_blocked_protocol_example_uses_reason_not_blocked_reason(self): """The Blocked Protocol JSON example uses 'reason' (not 'blocked_reason'). @@ -521,10 +518,11 @@ class TestContextBuilderDuplicateAssignment: assert "PROJ-001" in result assert "My test task" in result - def test_source_code_has_duplicate_task_assignment(self): - """Documents that the duplicate assignment still exists in the source. + def test_source_code_has_single_task_assignment(self): + """Duplicate assignment was removed from context_builder.py. - This test will FAIL once the duplicate is removed (cleanup completed). + Issue 4 fixed: task = context.get('task') now appears exactly once + inside format_prompt(). """ import ast import pathlib @@ -536,14 +534,12 @@ class TestContextBuilderDuplicateAssignment: task_assign_count = 0 for node in ast.walk(tree): if isinstance(node, ast.Assign): - # Check target is 'task' targets_are_task = any( isinstance(t, ast.Name) and t.id == "task" for t in node.targets ) if not targets_are_task: continue - # Check value is context.get('task') or context.get("task") val = node.value if ( isinstance(val, ast.Call) @@ -557,8 +553,7 @@ class TestContextBuilderDuplicateAssignment: ): task_assign_count += 1 - assert task_assign_count == 2, ( - f"Expected exactly 2 duplicate 'task = context.get(\"task\")' assignments " - f"(documenting Issue 4), found {task_assign_count}. " - "If count is 1, the duplicate was removed — this test can be deleted." + assert task_assign_count == 1, ( + f"Expected exactly 1 'task = context.get(\"task\")' assignment, " + f"found {task_assign_count}. Duplicate may have been re-introduced." ) diff --git a/web/frontend/src/api.ts b/web/frontend/src/api.ts index 537a466..f4836fa 100644 --- a/web/frontend/src/api.ts +++ b/web/frontend/src/api.ts @@ -68,6 +68,7 @@ export interface Project { tech_stack: string[] | null execution_mode: string | null autocommit_enabled: number | null + auto_test_enabled: number | null obsidian_vault_path: string | null deploy_command: string | null test_command: string | null diff --git a/web/frontend/src/views/ProjectView.vue b/web/frontend/src/views/ProjectView.vue index 803c3da..1ae6da9 100644 --- a/web/frontend/src/views/ProjectView.vue +++ b/web/frontend/src/views/ProjectView.vue @@ -696,6 +696,12 @@ async function addDecision() { + +
⚠ {{ uploadWarning }}
+ +