From 2eb4963e869d2e4de8831e5621c370fb54bf3aa2 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 7 Apr 2026 14:42:08 +0200 Subject: [PATCH 01/24] feat: add DAG extractor that introspects Luigi task graph Walks FullPlanPipeline.requires()/output() recursively to extract stage names, output files, primary outputs, and upstream dependencies as JSON. Replaces the need to hand-maintain the pipeline DAG mapping. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../worker_plan_internal/extract_dag.py | 176 ++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 worker_plan/worker_plan_internal/extract_dag.py diff --git a/worker_plan/worker_plan_internal/extract_dag.py b/worker_plan/worker_plan_internal/extract_dag.py new file mode 100644 index 00000000..722e2dc6 --- /dev/null +++ b/worker_plan/worker_plan_internal/extract_dag.py @@ -0,0 +1,176 @@ +"""Extract the pipeline DAG from Luigi task introspection. + +Walks the FullPlanPipeline task graph via requires()/output() and produces +a JSON description of every stage: name, output files, primary output, and +upstream stages. This replaces the hand-maintained registry with a generated +artifact that stays in sync with the actual pipeline code. + +Usage: + cd worker_plan + python -m worker_plan_internal.flaw_tracer.extract_dag + python -m worker_plan_internal.flaw_tracer.extract_dag --output pipeline_dag.json +""" +import json +import re +import sys +from pathlib import Path +from typing import Any + +import luigi + + +def _class_name_to_stage_name(class_name: str) -> str: + """Convert CamelCase task class name to snake_case stage name. + + Removes the 'Task' suffix, then converts CamelCase → snake_case. + + Examples: + PotentialLeversTask → potential_levers + SWOTAnalysisTask → swot_analysis + WBSProjectLevel1AndLevel2Task → wbs_project_level1_and_level2 + GovernancePhase1AuditTask → governance_phase1_audit + """ + name = class_name.removesuffix("Task") + # Insert underscore between lowercase/digit and uppercase + name = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", name) + # Insert underscore between consecutive uppercase run and uppercase+lowercase + name = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1_\2", name) + return name.lower() + + +def _pick_primary_output(filenames: list[str]) -> str: + """Pick the best primary output from a list of filenames. + + Preference: .md > .html > non-raw file > first file. + """ + for ext in (".md", ".html"): + for f in filenames: + if f.endswith(ext): + return f + # Prefer non-raw files + non_raw = [f for f in filenames if "_raw" not in f] + if non_raw: + return non_raw[0] + return filenames[0] if filenames else "" + + +def _extract_output_filenames(task: luigi.Task) -> list[str]: + """Extract output filenames (basenames) from a task's output() method.""" + try: + outputs = task.output() + except Exception: + return [] + + targets: list[Any] = [] + if isinstance(outputs, dict): + targets = list(outputs.values()) + elif isinstance(outputs, (list, tuple)): + targets = list(outputs) + else: + targets = [outputs] + + filenames: list[str] = [] + for target in targets: + if hasattr(target, "path"): + filenames.append(Path(target.path).name) + return filenames + + +def _extract_upstream_tasks(task: luigi.Task) -> list[luigi.Task]: + """Extract upstream task instances from a task's requires() method.""" + try: + deps = task.requires() + except Exception: + return [] + + if deps is None: + return [] + if isinstance(deps, dict): + return list(deps.values()) + if isinstance(deps, (list, tuple)): + return list(deps) + if isinstance(deps, luigi.Task): + return [deps] + return [] + + +def _output_sort_key(stage: dict[str, Any]) -> tuple[int, int, str]: + """Sort key: numeric prefix from the primary output filename, then name.""" + filename = stage.get("primary_output", "") or "" + if not filename and stage.get("output_files"): + filename = stage["output_files"][0] + match = re.match(r"(\d+)-?(\d+)?", filename) + if match: + major = int(match.group(1)) + minor = int(match.group(2)) if match.group(2) else 0 + return (major, minor, stage["name"]) + return (9999, 0, stage["name"]) + + +def extract_dag() -> list[dict[str, Any]]: + """Walk the FullPlanPipeline task graph and extract DAG info. + + Returns a list of stage dicts sorted by output file prefix (pipeline order). + """ + from worker_plan_internal.plan.stages.full_plan_pipeline import FullPlanPipeline + + root = FullPlanPipeline(run_id_dir=Path("/tmp/_dag_extract_dummy")) + + stages: list[dict[str, Any]] = [] + visited: set[str] = set() + + def _walk(task: luigi.Task) -> None: + class_name = task.__class__.__name__ + if class_name in visited: + return + visited.add(class_name) + + upstream_tasks = _extract_upstream_tasks(task) + + # Recurse into dependencies first (depth-first) + for dep in upstream_tasks: + _walk(dep) + + # Skip the orchestrator itself + if class_name == "FullPlanPipeline": + return + + stage_name = _class_name_to_stage_name(class_name) + output_files = _extract_output_filenames(task) + primary_output = _pick_primary_output(output_files) + upstream_stage_names = sorted(set( + _class_name_to_stage_name(dep.__class__.__name__) + for dep in upstream_tasks + )) + + stages.append({ + "name": stage_name, + "output_files": output_files, + "primary_output": primary_output, + "upstream_stages": upstream_stage_names, + }) + + _walk(root) + + stages.sort(key=_output_sort_key) + return stages + + +def main() -> None: + output_path = None + args = sys.argv[1:] + if len(args) >= 2 and args[0] == "--output": + output_path = args[1] + + stages = extract_dag() + dag_json = json.dumps(stages, indent=2, ensure_ascii=False) + + if output_path: + Path(output_path).write_text(dag_json + "\n", encoding="utf-8") + print(f"Wrote {len(stages)} stages to {output_path}", file=sys.stderr) + else: + print(dag_json) + + +if __name__ == "__main__": + main() From 3be325ab0e13ec8ec8318ebdac56e5ef2178b5bf Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 7 Apr 2026 15:07:00 +0200 Subject: [PATCH 02/24] feat: add source_files to DAG extractor via PlanTask.source_files() + auto-detection - Add source_files() classmethod to PlanTask (returns task's own file by default, subclasses can override to declare additional implementation files) - Auto-detect implementation imports in extract_dag.py by inspecting module namespace for classes/functions from worker_plan_internal.* that aren't infrastructure (stages, llm_util, etc.) - Remove primary_output field (was flaw-tracer-specific, not a Luigi concept) Co-Authored-By: Claude Opus 4.6 (1M context) --- .../worker_plan_internal/extract_dag.py | 110 +++++++++++++----- .../plan/run_plan_pipeline.py | 24 ++++ 2 files changed, 107 insertions(+), 27 deletions(-) diff --git a/worker_plan/worker_plan_internal/extract_dag.py b/worker_plan/worker_plan_internal/extract_dag.py index 722e2dc6..93f35d37 100644 --- a/worker_plan/worker_plan_internal/extract_dag.py +++ b/worker_plan/worker_plan_internal/extract_dag.py @@ -1,15 +1,16 @@ """Extract the pipeline DAG from Luigi task introspection. Walks the FullPlanPipeline task graph via requires()/output() and produces -a JSON description of every stage: name, output files, primary output, and -upstream stages. This replaces the hand-maintained registry with a generated -artifact that stays in sync with the actual pipeline code. +a JSON description of every stage: name, output files, upstream stages, +and source code files. This replaces the hand-maintained registry with a +generated artifact that stays in sync with the actual pipeline code. Usage: cd worker_plan - python -m worker_plan_internal.flaw_tracer.extract_dag - python -m worker_plan_internal.flaw_tracer.extract_dag --output pipeline_dag.json + python -m worker_plan_internal.extract_dag + python -m worker_plan_internal.extract_dag --output pipeline_dag.json """ +import inspect import json import re import sys @@ -18,6 +19,20 @@ import luigi +_WORKER_PLAN_DIR = Path(__file__).resolve().parent.parent # worker_plan/ + +# Module prefixes that are infrastructure/utilities, not implementation logic. +# Imports from these are excluded from source_files auto-detection. +_INFRASTRUCTURE_PREFIXES = ( + "worker_plan_internal.plan.", + "worker_plan_internal.llm_util.", + "worker_plan_internal.llm_factory", + "worker_plan_internal.luigi_util.", + "worker_plan_internal.utils.", + "worker_plan_internal.format_", + "worker_plan_api.", +) + def _class_name_to_stage_name(class_name: str) -> str: """Convert CamelCase task class name to snake_case stage name. @@ -38,22 +53,6 @@ def _class_name_to_stage_name(class_name: str) -> str: return name.lower() -def _pick_primary_output(filenames: list[str]) -> str: - """Pick the best primary output from a list of filenames. - - Preference: .md > .html > non-raw file > first file. - """ - for ext in (".md", ".html"): - for f in filenames: - if f.endswith(ext): - return f - # Prefer non-raw files - non_raw = [f for f in filenames if "_raw" not in f] - if non_raw: - return non_raw[0] - return filenames[0] if filenames else "" - - def _extract_output_filenames(task: luigi.Task) -> list[str]: """Extract output filenames (basenames) from a task's output() method.""" try: @@ -94,11 +93,68 @@ def _extract_upstream_tasks(task: luigi.Task) -> list[luigi.Task]: return [] +def _detect_implementation_files(cls: type) -> list[str]: + """Auto-detect implementation source files from module-level imports. + + Scans the module that defines *cls* for classes and functions imported + from ``worker_plan_internal.*`` that are NOT infrastructure (stages, + LLM utilities, API types, etc.). Returns paths relative to worker_plan/. + """ + module = inspect.getmodule(cls) + if module is None: + return [] + + files: list[str] = [] + seen_modules: set[str] = set() + + for attr_name in dir(module): + obj = getattr(module, attr_name, None) + if obj is None or not (inspect.isclass(obj) or inspect.isfunction(obj)): + continue + + obj_module_name = getattr(obj, "__module__", "") or "" + if not obj_module_name.startswith("worker_plan_internal."): + continue + if any(obj_module_name.startswith(p) for p in _INFRASTRUCTURE_PREFIXES): + continue + if obj_module_name in seen_modules: + continue + seen_modules.add(obj_module_name) + + try: + obj_file = Path(inspect.getfile(obj)).resolve() + rel = str(obj_file.relative_to(_WORKER_PLAN_DIR)) + if rel not in files: + files.append(rel) + except (TypeError, ValueError, OSError): + continue + + return files + + +def _extract_source_files(task: luigi.Task) -> list[str]: + """Get source files: task's own file + auto-detected implementation files. + + If the task class overrides ``source_files()``, those are used as the + base. Auto-detected implementation imports are appended if not already + present. + """ + cls = type(task) + + # Start with what the task class declares + declared = list(cls.source_files()) + + # Supplement with auto-detected implementation files + for f in _detect_implementation_files(cls): + if f not in declared: + declared.append(f) + + return declared + + def _output_sort_key(stage: dict[str, Any]) -> tuple[int, int, str]: - """Sort key: numeric prefix from the primary output filename, then name.""" - filename = stage.get("primary_output", "") or "" - if not filename and stage.get("output_files"): - filename = stage["output_files"][0] + """Sort key: numeric prefix from the first output filename, then name.""" + filename = stage["output_files"][0] if stage.get("output_files") else "" match = re.match(r"(\d+)-?(\d+)?", filename) if match: major = int(match.group(1)) @@ -137,7 +193,7 @@ def _walk(task: luigi.Task) -> None: stage_name = _class_name_to_stage_name(class_name) output_files = _extract_output_filenames(task) - primary_output = _pick_primary_output(output_files) + source_files = _extract_source_files(task) upstream_stage_names = sorted(set( _class_name_to_stage_name(dep.__class__.__name__) for dep in upstream_tasks @@ -146,8 +202,8 @@ def _walk(task: luigi.Task) -> None: stages.append({ "name": stage_name, "output_files": output_files, - "primary_output": primary_output, "upstream_stages": upstream_stage_names, + "source_files": source_files, }) _walk(root) diff --git a/worker_plan/worker_plan_internal/plan/run_plan_pipeline.py b/worker_plan/worker_plan_internal/plan/run_plan_pipeline.py index 93861faf..a9f22dd8 100644 --- a/worker_plan/worker_plan_internal/plan/run_plan_pipeline.py +++ b/worker_plan/worker_plan_internal/plan/run_plan_pipeline.py @@ -78,6 +78,30 @@ class PlanTask(luigi.Task): # If the callback is not provided, the pipeline will run until completion. _pipeline_executor_callback = luigi.Parameter(default=None, significant=False, visibility=luigi.parameter.ParameterVisibility.PRIVATE) + @classmethod + def source_files(cls) -> list[str]: + """Return source code file paths for this task, relative to worker_plan/. + + Default implementation returns just the file containing this class. + Override in subclasses to include significant implementation files. + + Example override:: + + @classmethod + def source_files(cls) -> list[str]: + return super().source_files() + [ + "worker_plan_internal/swot/swot_analysis.py", + "worker_plan_internal/swot/swot_phase2_conduct_analysis.py", + ] + """ + import inspect + worker_plan_dir = Path(__file__).resolve().parent.parent.parent + task_file = Path(inspect.getfile(cls)).resolve() + try: + return [str(task_file.relative_to(worker_plan_dir))] + except ValueError: + return [str(task_file)] + def file_path(self, filename: FilenameEnum) -> Path: return self.run_id_dir / filename.value From f8ccc229ed29b2bb93ed7534f712b505622b5d6b Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 7 Apr 2026 15:11:26 +0200 Subject: [PATCH 03/24] refactor: move source_files logic from PlanTask into extract_dag.py No subclasses override source_files(), so keep it as a local function in extract_dag.py rather than a method on PlanTask. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../worker_plan_internal/extract_dag.py | 22 ++++++++--------- .../plan/run_plan_pipeline.py | 24 ------------------- 2 files changed, 11 insertions(+), 35 deletions(-) diff --git a/worker_plan/worker_plan_internal/extract_dag.py b/worker_plan/worker_plan_internal/extract_dag.py index 93f35d37..d8ec0864 100644 --- a/worker_plan/worker_plan_internal/extract_dag.py +++ b/worker_plan/worker_plan_internal/extract_dag.py @@ -133,23 +133,23 @@ def _detect_implementation_files(cls: type) -> list[str]: def _extract_source_files(task: luigi.Task) -> list[str]: - """Get source files: task's own file + auto-detected implementation files. - - If the task class overrides ``source_files()``, those are used as the - base. Auto-detected implementation imports are appended if not already - present. - """ + """Get source files: task's own file + auto-detected implementation files.""" cls = type(task) - # Start with what the task class declares - declared = list(cls.source_files()) + # The task's own file + result: list[str] = [] + try: + task_file = Path(inspect.getfile(cls)).resolve() + result.append(str(task_file.relative_to(_WORKER_PLAN_DIR))) + except (TypeError, ValueError, OSError): + pass # Supplement with auto-detected implementation files for f in _detect_implementation_files(cls): - if f not in declared: - declared.append(f) + if f not in result: + result.append(f) - return declared + return result def _output_sort_key(stage: dict[str, Any]) -> tuple[int, int, str]: diff --git a/worker_plan/worker_plan_internal/plan/run_plan_pipeline.py b/worker_plan/worker_plan_internal/plan/run_plan_pipeline.py index a9f22dd8..93861faf 100644 --- a/worker_plan/worker_plan_internal/plan/run_plan_pipeline.py +++ b/worker_plan/worker_plan_internal/plan/run_plan_pipeline.py @@ -78,30 +78,6 @@ class PlanTask(luigi.Task): # If the callback is not provided, the pipeline will run until completion. _pipeline_executor_callback = luigi.Parameter(default=None, significant=False, visibility=luigi.parameter.ParameterVisibility.PRIVATE) - @classmethod - def source_files(cls) -> list[str]: - """Return source code file paths for this task, relative to worker_plan/. - - Default implementation returns just the file containing this class. - Override in subclasses to include significant implementation files. - - Example override:: - - @classmethod - def source_files(cls) -> list[str]: - return super().source_files() + [ - "worker_plan_internal/swot/swot_analysis.py", - "worker_plan_internal/swot/swot_phase2_conduct_analysis.py", - ] - """ - import inspect - worker_plan_dir = Path(__file__).resolve().parent.parent.parent - task_file = Path(inspect.getfile(cls)).resolve() - try: - return [str(task_file.relative_to(worker_plan_dir))] - except ValueError: - return [str(task_file)] - def file_path(self, filename: FilenameEnum) -> Path: return self.run_id_dir / filename.value From 5e04c6321ae51c7731a3276ebd276a2ad799375d Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 7 Apr 2026 15:19:46 +0200 Subject: [PATCH 04/24] fix: narrow infrastructure prefix filter so plan/ implementation files are detected The broad "worker_plan_internal.plan." prefix was excluding implementation files like plan/data_collection.py. Narrowed to only skip plan/stages/, plan/run_plan_pipeline, plan/pipeline_environment, and plan/ping_llm. Co-Authored-By: Claude Opus 4.6 (1M context) --- worker_plan/worker_plan_internal/extract_dag.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/worker_plan/worker_plan_internal/extract_dag.py b/worker_plan/worker_plan_internal/extract_dag.py index d8ec0864..fbfa9264 100644 --- a/worker_plan/worker_plan_internal/extract_dag.py +++ b/worker_plan/worker_plan_internal/extract_dag.py @@ -24,7 +24,10 @@ # Module prefixes that are infrastructure/utilities, not implementation logic. # Imports from these are excluded from source_files auto-detection. _INFRASTRUCTURE_PREFIXES = ( - "worker_plan_internal.plan.", + "worker_plan_internal.plan.stages.", + "worker_plan_internal.plan.run_plan_pipeline", + "worker_plan_internal.plan.pipeline_environment", + "worker_plan_internal.plan.ping_llm", "worker_plan_internal.llm_util.", "worker_plan_internal.llm_factory", "worker_plan_internal.luigi_util.", From c8816d79197fbd1d4eb6780972ca79dcdd933022 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 7 Apr 2026 15:35:53 +0200 Subject: [PATCH 05/24] feat: add description field to DAG extractor - Add description() classmethod to PlanTask, returns first line of docstring - Add class docstrings to 23 task classes that were missing them - Include description in extract_dag.py JSON output Co-Authored-By: Claude Opus 4.6 (1M context) --- worker_plan/worker_plan_internal/extract_dag.py | 3 +++ .../worker_plan_internal/plan/run_plan_pipeline.py | 12 ++++++++++++ .../plan/stages/consolidate_governance.py | 2 ++ .../plan/stages/create_schedule.py | 2 ++ .../plan/stages/enrich_team_background_story.py | 2 ++ .../plan/stages/enrich_team_contract_type.py | 2 ++ .../plan/stages/enrich_team_environment_info.py | 2 ++ .../plan/stages/find_team_members.py | 2 ++ .../plan/stages/governance_phase1_audit.py | 2 ++ .../plan/stages/governance_phase2_bodies.py | 2 ++ .../plan/stages/governance_phase3_impl_plan.py | 2 ++ .../governance_phase4_decision_escalation_matrix.py | 2 ++ .../stages/governance_phase5_monitoring_progress.py | 2 ++ .../plan/stages/governance_phase6_extra.py | 2 ++ .../plan/stages/pre_project_assessment.py | 1 + .../plan/stages/premise_attack.py | 2 ++ .../worker_plan_internal/plan/stages/premortem.py | 2 ++ .../worker_plan_internal/plan/stages/project_plan.py | 3 ++- .../plan/stages/questions_and_answers.py | 2 ++ .../worker_plan_internal/plan/stages/redline_gate.py | 2 ++ .../plan/stages/related_resources.py | 2 ++ .../worker_plan_internal/plan/stages/review_team.py | 2 ++ .../worker_plan_internal/plan/stages/self_audit.py | 2 ++ .../plan/stages/swot_analysis.py | 2 ++ .../plan/stages/team_markdown.py | 2 ++ 25 files changed, 60 insertions(+), 1 deletion(-) diff --git a/worker_plan/worker_plan_internal/extract_dag.py b/worker_plan/worker_plan_internal/extract_dag.py index fbfa9264..c816aac8 100644 --- a/worker_plan/worker_plan_internal/extract_dag.py +++ b/worker_plan/worker_plan_internal/extract_dag.py @@ -194,7 +194,9 @@ def _walk(task: luigi.Task) -> None: if class_name == "FullPlanPipeline": return + cls = type(task) stage_name = _class_name_to_stage_name(class_name) + description = cls.description() if hasattr(cls, "description") else "" output_files = _extract_output_filenames(task) source_files = _extract_source_files(task) upstream_stage_names = sorted(set( @@ -204,6 +206,7 @@ def _walk(task: luigi.Task) -> None: stages.append({ "name": stage_name, + "description": description, "output_files": output_files, "upstream_stages": upstream_stage_names, "source_files": source_files, diff --git a/worker_plan/worker_plan_internal/plan/run_plan_pipeline.py b/worker_plan/worker_plan_internal/plan/run_plan_pipeline.py index 93861faf..56b5f1a7 100644 --- a/worker_plan/worker_plan_internal/plan/run_plan_pipeline.py +++ b/worker_plan/worker_plan_internal/plan/run_plan_pipeline.py @@ -78,6 +78,18 @@ class PlanTask(luigi.Task): # If the callback is not provided, the pipeline will run until completion. _pipeline_executor_callback = luigi.Parameter(default=None, significant=False, visibility=luigi.parameter.ParameterVisibility.PRIVATE) + @classmethod + def description(cls) -> str: + """Brief description of what this task does. + + Default returns the first line of the class docstring. + Override in subclasses for a custom description. + """ + doc = cls.__doc__ + if doc: + return doc.strip().split("\n")[0].strip() + return "" + def file_path(self, filename: FilenameEnum) -> Path: return self.run_id_dir / filename.value diff --git a/worker_plan/worker_plan_internal/plan/stages/consolidate_governance.py b/worker_plan/worker_plan_internal/plan/stages/consolidate_governance.py index 424ae8c4..7971ccc0 100644 --- a/worker_plan/worker_plan_internal/plan/stages/consolidate_governance.py +++ b/worker_plan/worker_plan_internal/plan/stages/consolidate_governance.py @@ -10,6 +10,8 @@ class ConsolidateGovernanceTask(PlanTask): + """Consolidate all governance phases into a single markdown document.""" + def requires(self): return { 'governance_phase1_audit': self.clone(GovernancePhase1AuditTask), diff --git a/worker_plan/worker_plan_internal/plan/stages/create_schedule.py b/worker_plan/worker_plan_internal/plan/stages/create_schedule.py index 82077822..e920ae2a 100644 --- a/worker_plan/worker_plan_internal/plan/stages/create_schedule.py +++ b/worker_plan/worker_plan_internal/plan/stages/create_schedule.py @@ -19,6 +19,8 @@ class CreateScheduleTask(PlanTask): + """Build the project schedule and generate Gantt charts.""" + def output(self): return { 'dhtmlx_html': self.local_target(FilenameEnum.SCHEDULE_GANTT_DHTMLX_HTML), diff --git a/worker_plan/worker_plan_internal/plan/stages/enrich_team_background_story.py b/worker_plan/worker_plan_internal/plan/stages/enrich_team_background_story.py index 7ebcb848..4512d2ff 100644 --- a/worker_plan/worker_plan_internal/plan/stages/enrich_team_background_story.py +++ b/worker_plan/worker_plan_internal/plan/stages/enrich_team_background_story.py @@ -20,6 +20,8 @@ class EnrichTeamMembersWithBackgroundStoryTask(PlanTask): + """Develop background story for each team member.""" + def requires(self): return { 'setup': self.clone(SetupTask), diff --git a/worker_plan/worker_plan_internal/plan/stages/enrich_team_contract_type.py b/worker_plan/worker_plan_internal/plan/stages/enrich_team_contract_type.py index a6b52292..c5faf3c4 100644 --- a/worker_plan/worker_plan_internal/plan/stages/enrich_team_contract_type.py +++ b/worker_plan/worker_plan_internal/plan/stages/enrich_team_contract_type.py @@ -19,6 +19,8 @@ class EnrichTeamMembersWithContractTypeTask(PlanTask): + """Determine contract type for each team member.""" + def requires(self): return { 'setup': self.clone(SetupTask), diff --git a/worker_plan/worker_plan_internal/plan/stages/enrich_team_environment_info.py b/worker_plan/worker_plan_internal/plan/stages/enrich_team_environment_info.py index 6791fe51..baba514c 100644 --- a/worker_plan/worker_plan_internal/plan/stages/enrich_team_environment_info.py +++ b/worker_plan/worker_plan_internal/plan/stages/enrich_team_environment_info.py @@ -19,6 +19,8 @@ class EnrichTeamMembersWithEnvironmentInfoTask(PlanTask): + """Enrich team members with environmental and contextual information.""" + def requires(self): return { 'setup': self.clone(SetupTask), diff --git a/worker_plan/worker_plan_internal/plan/stages/find_team_members.py b/worker_plan/worker_plan_internal/plan/stages/find_team_members.py index c5cefb60..1c0f32f1 100644 --- a/worker_plan/worker_plan_internal/plan/stages/find_team_members.py +++ b/worker_plan/worker_plan_internal/plan/stages/find_team_members.py @@ -18,6 +18,8 @@ class FindTeamMembersTask(PlanTask): + """Identify team members required for project execution.""" + def requires(self): return { 'setup': self.clone(SetupTask), diff --git a/worker_plan/worker_plan_internal/plan/stages/governance_phase1_audit.py b/worker_plan/worker_plan_internal/plan/stages/governance_phase1_audit.py index 46d239c5..9797cbf5 100644 --- a/worker_plan/worker_plan_internal/plan/stages/governance_phase1_audit.py +++ b/worker_plan/worker_plan_internal/plan/stages/governance_phase1_audit.py @@ -14,6 +14,8 @@ class GovernancePhase1AuditTask(PlanTask): + """Audit organizational governance structure and compliance requirements.""" + def requires(self): return { 'setup': self.clone(SetupTask), diff --git a/worker_plan/worker_plan_internal/plan/stages/governance_phase2_bodies.py b/worker_plan/worker_plan_internal/plan/stages/governance_phase2_bodies.py index 708c2a24..2633a712 100644 --- a/worker_plan/worker_plan_internal/plan/stages/governance_phase2_bodies.py +++ b/worker_plan/worker_plan_internal/plan/stages/governance_phase2_bodies.py @@ -15,6 +15,8 @@ class GovernancePhase2BodiesTask(PlanTask): + """Define governance bodies and organizational hierarchy.""" + def requires(self): return { 'setup': self.clone(SetupTask), diff --git a/worker_plan/worker_plan_internal/plan/stages/governance_phase3_impl_plan.py b/worker_plan/worker_plan_internal/plan/stages/governance_phase3_impl_plan.py index 55bcc12f..c46e3c6b 100644 --- a/worker_plan/worker_plan_internal/plan/stages/governance_phase3_impl_plan.py +++ b/worker_plan/worker_plan_internal/plan/stages/governance_phase3_impl_plan.py @@ -17,6 +17,8 @@ class GovernancePhase3ImplPlanTask(PlanTask): + """Create implementation plan for the governance structure.""" + def requires(self): return { 'setup': self.clone(SetupTask), diff --git a/worker_plan/worker_plan_internal/plan/stages/governance_phase4_decision_escalation_matrix.py b/worker_plan/worker_plan_internal/plan/stages/governance_phase4_decision_escalation_matrix.py index cd54ba3c..1e1f1b85 100644 --- a/worker_plan/worker_plan_internal/plan/stages/governance_phase4_decision_escalation_matrix.py +++ b/worker_plan/worker_plan_internal/plan/stages/governance_phase4_decision_escalation_matrix.py @@ -18,6 +18,8 @@ class GovernancePhase4DecisionEscalationMatrixTask(PlanTask): + """Establish decision-making authority and escalation pathways.""" + def requires(self): return { 'setup': self.clone(SetupTask), diff --git a/worker_plan/worker_plan_internal/plan/stages/governance_phase5_monitoring_progress.py b/worker_plan/worker_plan_internal/plan/stages/governance_phase5_monitoring_progress.py index 16b29515..52458ad3 100644 --- a/worker_plan/worker_plan_internal/plan/stages/governance_phase5_monitoring_progress.py +++ b/worker_plan/worker_plan_internal/plan/stages/governance_phase5_monitoring_progress.py @@ -19,6 +19,8 @@ class GovernancePhase5MonitoringProgressTask(PlanTask): + """Define monitoring mechanisms and progress tracking.""" + def requires(self): return { 'setup': self.clone(SetupTask), diff --git a/worker_plan/worker_plan_internal/plan/stages/governance_phase6_extra.py b/worker_plan/worker_plan_internal/plan/stages/governance_phase6_extra.py index e772622d..a6dc24c4 100644 --- a/worker_plan/worker_plan_internal/plan/stages/governance_phase6_extra.py +++ b/worker_plan/worker_plan_internal/plan/stages/governance_phase6_extra.py @@ -21,6 +21,8 @@ class GovernancePhase6ExtraTask(PlanTask): + """Address additional governance considerations.""" + def requires(self): return { 'setup': self.clone(SetupTask), diff --git a/worker_plan/worker_plan_internal/plan/stages/pre_project_assessment.py b/worker_plan/worker_plan_internal/plan/stages/pre_project_assessment.py index a026861c..79c300bd 100644 --- a/worker_plan/worker_plan_internal/plan/stages/pre_project_assessment.py +++ b/worker_plan/worker_plan_internal/plan/stages/pre_project_assessment.py @@ -13,6 +13,7 @@ class PreProjectAssessmentTask(PlanTask): + """Evaluate project viability and readiness before detailed planning.""" def requires(self): return { 'setup': self.clone(SetupTask), diff --git a/worker_plan/worker_plan_internal/plan/stages/premise_attack.py b/worker_plan/worker_plan_internal/plan/stages/premise_attack.py index 7f42939b..bd3e0bfd 100644 --- a/worker_plan/worker_plan_internal/plan/stages/premise_attack.py +++ b/worker_plan/worker_plan_internal/plan/stages/premise_attack.py @@ -7,6 +7,8 @@ class PremiseAttackTask(PlanTask): + """Challenge the fundamental assumptions underlying the plan.""" + def requires(self): return self.clone(SetupTask) diff --git a/worker_plan/worker_plan_internal/plan/stages/premortem.py b/worker_plan/worker_plan_internal/plan/stages/premortem.py index cc9d9bfe..1058d6f4 100644 --- a/worker_plan/worker_plan_internal/plan/stages/premortem.py +++ b/worker_plan/worker_plan_internal/plan/stages/premortem.py @@ -21,6 +21,8 @@ class PremortemTask(PlanTask): + """Conduct premortem analysis to identify potential failure modes.""" + def output(self): return { 'raw': self.local_target(FilenameEnum.PREMORTEM_RAW), diff --git a/worker_plan/worker_plan_internal/plan/stages/project_plan.py b/worker_plan/worker_plan_internal/plan/stages/project_plan.py index f63fa3c8..8c91e15d 100644 --- a/worker_plan/worker_plan_internal/plan/stages/project_plan.py +++ b/worker_plan/worker_plan_internal/plan/stages/project_plan.py @@ -14,8 +14,9 @@ logger = logging.getLogger(__name__) - class ProjectPlanTask(PlanTask): + """Generate the comprehensive project plan.""" + def requires(self): return { 'setup': self.clone(SetupTask), diff --git a/worker_plan/worker_plan_internal/plan/stages/questions_and_answers.py b/worker_plan/worker_plan_internal/plan/stages/questions_and_answers.py index 0c7d6dfa..aa2485bc 100644 --- a/worker_plan/worker_plan_internal/plan/stages/questions_and_answers.py +++ b/worker_plan/worker_plan_internal/plan/stages/questions_and_answers.py @@ -20,6 +20,8 @@ class QuestionsAndAnswersTask(PlanTask): + """Generate Q&A documentation addressing plan details.""" + def output(self): return { 'raw': self.local_target(FilenameEnum.QUESTIONS_AND_ANSWERS_RAW), diff --git a/worker_plan/worker_plan_internal/plan/stages/redline_gate.py b/worker_plan/worker_plan_internal/plan/stages/redline_gate.py index 40108651..b8ffce7b 100644 --- a/worker_plan/worker_plan_internal/plan/stages/redline_gate.py +++ b/worker_plan/worker_plan_internal/plan/stages/redline_gate.py @@ -7,6 +7,8 @@ class RedlineGateTask(PlanTask): + """Check the plan prompt against redline criteria.""" + def requires(self): return self.clone(SetupTask) diff --git a/worker_plan/worker_plan_internal/plan/stages/related_resources.py b/worker_plan/worker_plan_internal/plan/stages/related_resources.py index 66a6cad8..bbb37f48 100644 --- a/worker_plan/worker_plan_internal/plan/stages/related_resources.py +++ b/worker_plan/worker_plan_internal/plan/stages/related_resources.py @@ -16,6 +16,8 @@ class RelatedResourcesTask(PlanTask): + """Identify external resources and tools needed for the project.""" + def requires(self): return { 'setup': self.clone(SetupTask), diff --git a/worker_plan/worker_plan_internal/plan/stages/review_team.py b/worker_plan/worker_plan_internal/plan/stages/review_team.py index 2567d315..5f2c44a2 100644 --- a/worker_plan/worker_plan_internal/plan/stages/review_team.py +++ b/worker_plan/worker_plan_internal/plan/stages/review_team.py @@ -20,6 +20,8 @@ class ReviewTeamTask(PlanTask): + """Review and validate the assembled team composition.""" + def requires(self): return { 'setup': self.clone(SetupTask), diff --git a/worker_plan/worker_plan_internal/plan/stages/self_audit.py b/worker_plan/worker_plan_internal/plan/stages/self_audit.py index 9e2d3252..e2f6f369 100644 --- a/worker_plan/worker_plan_internal/plan/stages/self_audit.py +++ b/worker_plan/worker_plan_internal/plan/stages/self_audit.py @@ -27,6 +27,8 @@ class SelfAuditTask(PlanTask): + """Perform self-audit to validate plan completeness and consistency.""" + def output(self): return { 'raw': self.local_target(FilenameEnum.SELF_AUDIT_RAW), diff --git a/worker_plan/worker_plan_internal/plan/stages/swot_analysis.py b/worker_plan/worker_plan_internal/plan/stages/swot_analysis.py index 34bb2bb5..e44ad61a 100644 --- a/worker_plan/worker_plan_internal/plan/stages/swot_analysis.py +++ b/worker_plan/worker_plan_internal/plan/stages/swot_analysis.py @@ -19,6 +19,8 @@ class SWOTAnalysisTask(PlanTask): + """Perform SWOT analysis on the project plan.""" + def requires(self): return { 'setup': self.clone(SetupTask), diff --git a/worker_plan/worker_plan_internal/plan/stages/team_markdown.py b/worker_plan/worker_plan_internal/plan/stages/team_markdown.py index ffd7150a..2b6ee47b 100644 --- a/worker_plan/worker_plan_internal/plan/stages/team_markdown.py +++ b/worker_plan/worker_plan_internal/plan/stages/team_markdown.py @@ -11,6 +11,8 @@ class TeamMarkdownTask(PlanTask): + """Generate the team information document in markdown.""" + def requires(self): return { 'enrich_team_members_with_environment_info': self.clone(EnrichTeamMembersWithEnvironmentInfoTask), From 195a9933d523e00ac3967df584837004b79384d3 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 7 Apr 2026 15:38:50 +0200 Subject: [PATCH 06/24] fix: improve extract_constraints description Co-Authored-By: Claude Opus 4.6 (1M context) --- .../worker_plan_internal/plan/stages/extract_constraints.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/worker_plan/worker_plan_internal/plan/stages/extract_constraints.py b/worker_plan/worker_plan_internal/plan/stages/extract_constraints.py index e7a37fa5..63db8da6 100644 --- a/worker_plan/worker_plan_internal/plan/stages/extract_constraints.py +++ b/worker_plan/worker_plan_internal/plan/stages/extract_constraints.py @@ -7,10 +7,7 @@ class ExtractConstraintsTask(PlanTask): - """ - Extract and classify constraints from the user's prompt. - Produces a list of positive/negative constraint items. - """ + """Extract positive/negative constraints from the user's prompt.""" def requires(self): return self.clone(SetupTask) From f64a256849eac08d20b7629bd00d78c71abded37 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 7 Apr 2026 15:43:11 +0200 Subject: [PATCH 07/24] fix: improve screen_planning_prompt description Co-Authored-By: Claude Opus 4.6 (1M context) --- .../plan/stages/screen_planning_prompt.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/worker_plan/worker_plan_internal/plan/stages/screen_planning_prompt.py b/worker_plan/worker_plan_internal/plan/stages/screen_planning_prompt.py index 5a72c723..33b4d730 100644 --- a/worker_plan/worker_plan_internal/plan/stages/screen_planning_prompt.py +++ b/worker_plan/worker_plan_internal/plan/stages/screen_planning_prompt.py @@ -7,10 +7,7 @@ class ScreenPlanningPromptTask(PlanTask): - """ - Screen the user's prompt for quality before plan generation. - Classifies the prompt as USABLE or UNUSABLE. - """ + """Flag prompts as UNUSABLE when there is high confidence the prompt is garbage.""" def requires(self): return self.clone(SetupTask) From 4d10a716bae2a0cbda0811d4673f84a68ab484d8 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 7 Apr 2026 15:44:28 +0200 Subject: [PATCH 08/24] fix: improve redline_gate description Co-Authored-By: Claude Opus 4.6 (1M context) --- worker_plan/worker_plan_internal/plan/stages/redline_gate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worker_plan/worker_plan_internal/plan/stages/redline_gate.py b/worker_plan/worker_plan_internal/plan/stages/redline_gate.py index b8ffce7b..b74aa046 100644 --- a/worker_plan/worker_plan_internal/plan/stages/redline_gate.py +++ b/worker_plan/worker_plan_internal/plan/stages/redline_gate.py @@ -7,7 +7,7 @@ class RedlineGateTask(PlanTask): - """Check the plan prompt against redline criteria.""" + """Block prompts that cross policy, legal, or ethical red lines.""" def requires(self): return self.clone(SetupTask) From fb5f0ee917af4f962d12843a076ca1cded46707f Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 7 Apr 2026 15:48:31 +0200 Subject: [PATCH 09/24] fix: improve premise_attack description Co-Authored-By: Claude Opus 4.6 (1M context) --- worker_plan/worker_plan_internal/plan/stages/premise_attack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worker_plan/worker_plan_internal/plan/stages/premise_attack.py b/worker_plan/worker_plan_internal/plan/stages/premise_attack.py index bd3e0bfd..396aee67 100644 --- a/worker_plan/worker_plan_internal/plan/stages/premise_attack.py +++ b/worker_plan/worker_plan_internal/plan/stages/premise_attack.py @@ -7,7 +7,7 @@ class PremiseAttackTask(PlanTask): - """Challenge the fundamental assumptions underlying the plan.""" + """Stress-test the plan's premise through five independent lenses to kill bad ideas early.""" def requires(self): return self.clone(SetupTask) From c409adbde015870489cee0f8d238c97c1ef12ecb Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 7 Apr 2026 15:51:28 +0200 Subject: [PATCH 10/24] fix: improve potential_levers description Co-Authored-By: Claude Opus 4.6 (1M context) --- .../worker_plan_internal/plan/stages/potential_levers.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/worker_plan/worker_plan_internal/plan/stages/potential_levers.py b/worker_plan/worker_plan_internal/plan/stages/potential_levers.py index c43fdfc7..b065f2c8 100644 --- a/worker_plan/worker_plan_internal/plan/stages/potential_levers.py +++ b/worker_plan/worker_plan_internal/plan/stages/potential_levers.py @@ -10,9 +10,7 @@ class PotentialLeversTask(PlanTask): - """ - Identify potential levers that can be adjusted. - """ + """Brainstorm actionable levers — knobs the plan can turn to change outcomes.""" def requires(self): return { 'setup': self.clone(SetupTask), From 8225d7c1c61c9ae9ae09d69ca12def1c378b8f93 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 7 Apr 2026 15:52:39 +0200 Subject: [PATCH 11/24] fix: improve deduplicate_levers description Co-Authored-By: Claude Opus 4.6 (1M context) --- .../worker_plan_internal/plan/stages/deduplicate_levers.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/worker_plan/worker_plan_internal/plan/stages/deduplicate_levers.py b/worker_plan/worker_plan_internal/plan/stages/deduplicate_levers.py index 82f6b645..1142d058 100644 --- a/worker_plan/worker_plan_internal/plan/stages/deduplicate_levers.py +++ b/worker_plan/worker_plan_internal/plan/stages/deduplicate_levers.py @@ -11,9 +11,7 @@ class DeduplicateLeversTask(PlanTask): - """ - The potential levers usually have some redundant levers. - """ + """Triage levers into primary, secondary, or remove.""" def requires(self): return { 'setup': self.clone(SetupTask), From aa1156a2aa01532dcefd8027259999ba8d1146dd Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 7 Apr 2026 15:54:06 +0200 Subject: [PATCH 12/24] fix: improve enrich_levers description Co-Authored-By: Claude Opus 4.6 (1M context) --- worker_plan/worker_plan_internal/plan/stages/enrich_levers.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/worker_plan/worker_plan_internal/plan/stages/enrich_levers.py b/worker_plan/worker_plan_internal/plan/stages/enrich_levers.py index 179b66a8..059ee1b9 100644 --- a/worker_plan/worker_plan_internal/plan/stages/enrich_levers.py +++ b/worker_plan/worker_plan_internal/plan/stages/enrich_levers.py @@ -11,9 +11,7 @@ class EnrichLeversTask(PlanTask): - """ - Enrich potential levers with more information. - """ + """Add description, synergy, and conflict text to each lever.""" def requires(self): return { 'setup': self.clone(SetupTask), From 8753952f0826d6c5a910bba225bb09c96dde99b0 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 7 Apr 2026 15:58:51 +0200 Subject: [PATCH 13/24] fix: improve descriptions for 5 unclear task docstrings - strategic_decisions_markdown: clarify it summarizes the lever pipeline - candidate_scenarios: specify aggressive/moderate/conservative scenarios - filter_documents_to_find: describe what it does, not the problem - distill_assumptions: explain condensing verbose into concise - make_assumptions: specify filling info gaps with grounded assumptions Co-Authored-By: Claude Opus 4.6 (1M context) --- .../worker_plan_internal/plan/stages/candidate_scenarios.py | 4 +--- .../worker_plan_internal/plan/stages/distill_assumptions.py | 4 +--- .../plan/stages/filter_documents_to_find.py | 5 +---- .../worker_plan_internal/plan/stages/make_assumptions.py | 4 +--- .../plan/stages/strategic_decisions_markdown.py | 4 +--- 5 files changed, 5 insertions(+), 16 deletions(-) diff --git a/worker_plan/worker_plan_internal/plan/stages/candidate_scenarios.py b/worker_plan/worker_plan_internal/plan/stages/candidate_scenarios.py index 958f7a48..d48b70f8 100644 --- a/worker_plan/worker_plan_internal/plan/stages/candidate_scenarios.py +++ b/worker_plan/worker_plan_internal/plan/stages/candidate_scenarios.py @@ -11,9 +11,7 @@ class CandidateScenariosTask(PlanTask): - """ - Combinations of the vital few levers. - """ + """Generate aggressive, moderate, and conservative scenarios from the vital few levers.""" def requires(self): return { 'setup': self.clone(SetupTask), diff --git a/worker_plan/worker_plan_internal/plan/stages/distill_assumptions.py b/worker_plan/worker_plan_internal/plan/stages/distill_assumptions.py index c645d54f..eee50a78 100644 --- a/worker_plan/worker_plan_internal/plan/stages/distill_assumptions.py +++ b/worker_plan/worker_plan_internal/plan/stages/distill_assumptions.py @@ -13,9 +13,7 @@ class DistillAssumptionsTask(PlanTask): - """ - Distill raw assumption data. - """ + """Condense verbose assumptions into concise, strategically important ones.""" def requires(self): return { 'setup': self.clone(SetupTask), diff --git a/worker_plan/worker_plan_internal/plan/stages/filter_documents_to_find.py b/worker_plan/worker_plan_internal/plan/stages/filter_documents_to_find.py index 6b7f81ab..90fec0fe 100644 --- a/worker_plan/worker_plan_internal/plan/stages/filter_documents_to_find.py +++ b/worker_plan/worker_plan_internal/plan/stages/filter_documents_to_find.py @@ -13,10 +13,7 @@ class FilterDocumentsToFindTask(PlanTask): - """ - The "documents to find" may be a long list of documents, some duplicates, irrelevant, not needed at an early stage of the project. - This task narrows down to a handful of relevant documents. - """ + """Narrow the documents-to-find list to the most relevant ones for the current plan.""" def output(self): return { "raw": self.local_target(FilenameEnum.FILTER_DOCUMENTS_TO_FIND_RAW), diff --git a/worker_plan/worker_plan_internal/plan/stages/make_assumptions.py b/worker_plan/worker_plan_internal/plan/stages/make_assumptions.py index 282f2764..3b7e1e15 100644 --- a/worker_plan/worker_plan_internal/plan/stages/make_assumptions.py +++ b/worker_plan/worker_plan_internal/plan/stages/make_assumptions.py @@ -14,9 +14,7 @@ class MakeAssumptionsTask(PlanTask): - """ - Make assumptions about the plan. - """ + """Fill information gaps with grounded assumptions about costs, timelines, and resources.""" def requires(self): return { 'setup': self.clone(SetupTask), diff --git a/worker_plan/worker_plan_internal/plan/stages/strategic_decisions_markdown.py b/worker_plan/worker_plan_internal/plan/stages/strategic_decisions_markdown.py index 123de49a..f7bd8097 100644 --- a/worker_plan/worker_plan_internal/plan/stages/strategic_decisions_markdown.py +++ b/worker_plan/worker_plan_internal/plan/stages/strategic_decisions_markdown.py @@ -8,9 +8,7 @@ class StrategicDecisionsMarkdownTask(PlanTask): - """ - Human readable markdown with the levers. - """ + """Summarize the lever exploration pipeline into a readable strategic-decisions document.""" def requires(self): return { 'enriched_levers': self.clone(EnrichLeversTask), From c62921375878e30e2ec9c6964f1452b1cd43bb3a Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 7 Apr 2026 16:00:50 +0200 Subject: [PATCH 14/24] fix: improve descriptions for 5 more unclear task docstrings - filter_documents_to_create: describe the action, not the problem - draft_documents_to_find: specify content specs with essential info, risks, scenarios - draft_documents_to_create: same as above - focus_on_vital_few_levers: specify ~5 levers rated critical/high/medium - review_assumptions: specify flagging unreasonable/missing/contradictory Co-Authored-By: Claude Opus 4.6 (1M context) --- .../plan/stages/draft_documents_to_create.py | 4 +--- .../plan/stages/draft_documents_to_find.py | 4 +--- .../plan/stages/filter_documents_to_create.py | 5 +---- .../plan/stages/focus_on_vital_few_levers.py | 4 +--- .../worker_plan_internal/plan/stages/review_assumptions.py | 4 +--- 5 files changed, 5 insertions(+), 16 deletions(-) diff --git a/worker_plan/worker_plan_internal/plan/stages/draft_documents_to_create.py b/worker_plan/worker_plan_internal/plan/stages/draft_documents_to_create.py index 47a6e7d2..a5a64cfe 100644 --- a/worker_plan/worker_plan_internal/plan/stages/draft_documents_to_create.py +++ b/worker_plan/worker_plan_internal/plan/stages/draft_documents_to_create.py @@ -18,9 +18,7 @@ class DraftDocumentsToCreateTask(PlanTask): - """ - The "documents to create". Write bullet points to what each document roughly should contain. - """ + """Draft content specs for each document to create: essential info, risks, and scenarios.""" def output(self): return self.local_target(FilenameEnum.DRAFT_DOCUMENTS_TO_CREATE_CONSOLIDATED) diff --git a/worker_plan/worker_plan_internal/plan/stages/draft_documents_to_find.py b/worker_plan/worker_plan_internal/plan/stages/draft_documents_to_find.py index 65842998..e4e0bd8e 100644 --- a/worker_plan/worker_plan_internal/plan/stages/draft_documents_to_find.py +++ b/worker_plan/worker_plan_internal/plan/stages/draft_documents_to_find.py @@ -18,9 +18,7 @@ class DraftDocumentsToFindTask(PlanTask): - """ - The "documents to find". Write bullet points to what each document roughly should contain. - """ + """Draft content specs for each document to find: essential info, risks, and scenarios.""" def output(self): return self.local_target(FilenameEnum.DRAFT_DOCUMENTS_TO_FIND_CONSOLIDATED) diff --git a/worker_plan/worker_plan_internal/plan/stages/filter_documents_to_create.py b/worker_plan/worker_plan_internal/plan/stages/filter_documents_to_create.py index 179b4c25..3af2f826 100644 --- a/worker_plan/worker_plan_internal/plan/stages/filter_documents_to_create.py +++ b/worker_plan/worker_plan_internal/plan/stages/filter_documents_to_create.py @@ -13,10 +13,7 @@ class FilterDocumentsToCreateTask(PlanTask): - """ - The "documents to create" may be a long list of documents, some duplicates, irrelevant, not needed at an early stage of the project. - This task narrows down to a handful of relevant documents. - """ + """Narrow the documents-to-create list to the most relevant ones for the current plan.""" def output(self): return { "raw": self.local_target(FilenameEnum.FILTER_DOCUMENTS_TO_CREATE_RAW), diff --git a/worker_plan/worker_plan_internal/plan/stages/focus_on_vital_few_levers.py b/worker_plan/worker_plan_internal/plan/stages/focus_on_vital_few_levers.py index 83ed59cf..dd8dff05 100644 --- a/worker_plan/worker_plan_internal/plan/stages/focus_on_vital_few_levers.py +++ b/worker_plan/worker_plan_internal/plan/stages/focus_on_vital_few_levers.py @@ -11,9 +11,7 @@ class FocusOnVitalFewLeversTask(PlanTask): - """ - Apply the 80/20 principle to the levers. - """ + """Select the ~5 highest-impact levers by rating each as critical, high, or medium.""" def requires(self): return { 'setup': self.clone(SetupTask), diff --git a/worker_plan/worker_plan_internal/plan/stages/review_assumptions.py b/worker_plan/worker_plan_internal/plan/stages/review_assumptions.py index 17db2648..600b3125 100644 --- a/worker_plan/worker_plan_internal/plan/stages/review_assumptions.py +++ b/worker_plan/worker_plan_internal/plan/stages/review_assumptions.py @@ -18,9 +18,7 @@ class ReviewAssumptionsTask(PlanTask): - """ - Find issues with the assumptions. - """ + """Flag unreasonable, missing, or contradictory assumptions with recommendations.""" def requires(self): return { 'identify_purpose': self.clone(IdentifyPurposeTask), From 328981b290afb814e8ee71fb31f8c9118da7de80 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 7 Apr 2026 16:05:44 +0200 Subject: [PATCH 15/24] fix: improve descriptions for 5 more task docstrings - identify_risks: broad risk register, not just location-dependent - create_pitch: fix typo, specify pitch structure - consolidate_assumptions_markdown: list what it actually merges - identify_purpose: fix typo, explain why classification matters - data_collection: specify concrete data-gathering areas Co-Authored-By: Claude Opus 4.6 (1M context) --- .../plan/stages/consolidate_assumptions_markdown.py | 4 +--- .../worker_plan_internal/plan/stages/create_pitch.py | 10 +--------- .../plan/stages/data_collection.py | 4 +--- .../plan/stages/identify_purpose.py | 4 +--- .../worker_plan_internal/plan/stages/identify_risks.py | 4 +--- 5 files changed, 5 insertions(+), 21 deletions(-) diff --git a/worker_plan/worker_plan_internal/plan/stages/consolidate_assumptions_markdown.py b/worker_plan/worker_plan_internal/plan/stages/consolidate_assumptions_markdown.py index f150618b..e7b873b5 100644 --- a/worker_plan/worker_plan_internal/plan/stages/consolidate_assumptions_markdown.py +++ b/worker_plan/worker_plan_internal/plan/stages/consolidate_assumptions_markdown.py @@ -18,9 +18,7 @@ class ConsolidateAssumptionsMarkdownTask(PlanTask): - """ - Combines multiple small markdown documents into a single big document. - """ + """Merge locations, currency, risks, and assumption stages into one reference document.""" def requires(self): return { 'identify_purpose': self.clone(IdentifyPurposeTask), diff --git a/worker_plan/worker_plan_internal/plan/stages/create_pitch.py b/worker_plan/worker_plan_internal/plan/stages/create_pitch.py index 6a759430..397dc67b 100644 --- a/worker_plan/worker_plan_internal/plan/stages/create_pitch.py +++ b/worker_plan/worker_plan_internal/plan/stages/create_pitch.py @@ -17,15 +17,7 @@ class CreatePitchTask(PlanTask): - """ - Create a the pitch that explains the project plan, from multiple perspectives. - - This task depends on: - - ProjectPlanTask: provides the project plan JSON. - - WBSProjectLevel1AndLevel2Task: containing the top level of the project plan. - - The resulting pitch JSON is written to the file specified by FilenameEnum.PITCH. - """ + """Create a compelling project pitch with target audience, call to action, and risk mitigation.""" def output(self): return self.local_target(FilenameEnum.PITCH_RAW) diff --git a/worker_plan/worker_plan_internal/plan/stages/data_collection.py b/worker_plan/worker_plan_internal/plan/stages/data_collection.py index 86de18c4..bab62fe0 100644 --- a/worker_plan/worker_plan_internal/plan/stages/data_collection.py +++ b/worker_plan/worker_plan_internal/plan/stages/data_collection.py @@ -14,9 +14,7 @@ class DataCollectionTask(PlanTask): - """ - Determine what kind of data is to be collected. - """ + """Specify data-gathering actions needed to validate the plan: market, financial, regulatory, etc.""" def output(self): return { 'raw': self.local_target(FilenameEnum.DATA_COLLECTION_RAW), diff --git a/worker_plan/worker_plan_internal/plan/stages/identify_purpose.py b/worker_plan/worker_plan_internal/plan/stages/identify_purpose.py index 6f7bdd67..40fd1b02 100644 --- a/worker_plan/worker_plan_internal/plan/stages/identify_purpose.py +++ b/worker_plan/worker_plan_internal/plan/stages/identify_purpose.py @@ -7,9 +7,7 @@ class IdentifyPurposeTask(PlanTask): - """ - Determine if this is this going to be a business/personal/other plan. - """ + """Classify the plan as business, personal, or other to tailor downstream prompts.""" def requires(self): return self.clone(SetupTask) diff --git a/worker_plan/worker_plan_internal/plan/stages/identify_risks.py b/worker_plan/worker_plan_internal/plan/stages/identify_risks.py index df662f0e..9977dc9b 100644 --- a/worker_plan/worker_plan_internal/plan/stages/identify_risks.py +++ b/worker_plan/worker_plan_internal/plan/stages/identify_risks.py @@ -13,9 +13,7 @@ class IdentifyRisksTask(PlanTask): - """ - Identify risks for the plan, depending on the physical locations. - """ + """Build a risk register covering strategic, operational, financial, and location-specific risks.""" def requires(self): return { 'setup': self.clone(SetupTask), From 2252052f791167db5f687b076b689f0f88f00df7 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 7 Apr 2026 16:15:13 +0200 Subject: [PATCH 16/24] fix: improve descriptions for 10 more task docstrings - scenarios_markdown: mention selected + rejected alternatives - physical_locations: "extract or suggest" instead of "identify/suggest" - currency_strategy: concise, mention cross-border - markdown_with_documents: describe structured output - convert_pitch_to_markdown: specify JSON-to-markdown conversion - identify_task_dependencies: clarify prerequisites for scheduling - estimate_task_durations: specify bottom-up with min/max/realistic - governance_phase6_extra: validation with tough questions - review_plan: critical review with SMART recommendations - report: assembles all outputs into final HTML Co-Authored-By: Claude Opus 4.6 (1M context) --- .../plan/stages/convert_pitch_to_markdown.py | 7 +------ .../plan/stages/currency_strategy.py | 4 +--- .../plan/stages/estimate_task_durations.py | 15 +-------------- .../plan/stages/governance_phase6_extra.py | 2 +- .../plan/stages/identify_task_dependencies.py | 4 +--- .../plan/stages/markdown_documents.py | 4 +--- .../plan/stages/physical_locations.py | 4 +--- .../worker_plan_internal/plan/stages/report.py | 4 +--- .../plan/stages/review_plan.py | 4 +--- .../plan/stages/scenarios_markdown.py | 4 +--- 10 files changed, 10 insertions(+), 42 deletions(-) diff --git a/worker_plan/worker_plan_internal/plan/stages/convert_pitch_to_markdown.py b/worker_plan/worker_plan_internal/plan/stages/convert_pitch_to_markdown.py index 43856006..2928b5bb 100644 --- a/worker_plan/worker_plan_internal/plan/stages/convert_pitch_to_markdown.py +++ b/worker_plan/worker_plan_internal/plan/stages/convert_pitch_to_markdown.py @@ -9,12 +9,7 @@ class ConvertPitchToMarkdownTask(PlanTask): - """ - Human readable version of the pitch. - - This task depends on: - - CreatePitchTask: Creates the pitch JSON. - """ + """Convert the raw pitch JSON into a polished, scannable markdown document.""" def output(self): return { 'raw': self.local_target(FilenameEnum.PITCH_CONVERT_TO_MARKDOWN_RAW), diff --git a/worker_plan/worker_plan_internal/plan/stages/currency_strategy.py b/worker_plan/worker_plan_internal/plan/stages/currency_strategy.py index 87992af0..22f735c9 100644 --- a/worker_plan/worker_plan_internal/plan/stages/currency_strategy.py +++ b/worker_plan/worker_plan_internal/plan/stages/currency_strategy.py @@ -12,9 +12,7 @@ class CurrencyStrategyTask(PlanTask): - """ - Identify/suggest what currency to use for the plan, depending on the physical locations. - """ + """Choose the project currency based on physical locations and cross-border needs.""" def requires(self): return { 'setup': self.clone(SetupTask), diff --git a/worker_plan/worker_plan_internal/plan/stages/estimate_task_durations.py b/worker_plan/worker_plan_internal/plan/stages/estimate_task_durations.py index c6cd991f..55d4e3ea 100644 --- a/worker_plan/worker_plan_internal/plan/stages/estimate_task_durations.py +++ b/worker_plan/worker_plan_internal/plan/stages/estimate_task_durations.py @@ -15,20 +15,7 @@ class EstimateTaskDurationsTask(PlanTask): - """ - This task estimates durations for WBS tasks in chunks. - - It depends on: - - ProjectPlanTask: providing the project plan JSON. - - WBSProjectLevel1AndLevel2Task: providing the major phases with subtasks and the task UUIDs. - - For each chunk of 3 task IDs, a raw JSON file (e.g. "011-1-task_durations_raw.json") is written, - and an aggregated JSON file (defined by FilenameEnum.TASK_DURATIONS) is produced. - - IDEA: 1st estimate the Tasks that have zero children. - 2nd estimate tasks that have children where all children have been estimated. - repeat until all tasks have been estimated. - """ + """Estimate realistic, minimum, and maximum durations for each WBS task bottom-up.""" def output(self): return self.local_target(FilenameEnum.TASK_DURATIONS) diff --git a/worker_plan/worker_plan_internal/plan/stages/governance_phase6_extra.py b/worker_plan/worker_plan_internal/plan/stages/governance_phase6_extra.py index a6dc24c4..83ece1a5 100644 --- a/worker_plan/worker_plan_internal/plan/stages/governance_phase6_extra.py +++ b/worker_plan/worker_plan_internal/plan/stages/governance_phase6_extra.py @@ -21,7 +21,7 @@ class GovernancePhase6ExtraTask(PlanTask): - """Address additional governance considerations.""" + """Validate prior governance phases with tough questions and a high-level summary.""" def requires(self): return { diff --git a/worker_plan/worker_plan_internal/plan/stages/identify_task_dependencies.py b/worker_plan/worker_plan_internal/plan/stages/identify_task_dependencies.py index a2268f1b..948371cb 100644 --- a/worker_plan/worker_plan_internal/plan/stages/identify_task_dependencies.py +++ b/worker_plan/worker_plan_internal/plan/stages/identify_task_dependencies.py @@ -16,9 +16,7 @@ class IdentifyTaskDependenciesTask(PlanTask): - """ - This task identifies the dependencies between WBS tasks. - """ + """Identify prerequisite relationships between WBS tasks for scheduling.""" def output(self): return self.local_target(FilenameEnum.TASK_DEPENDENCIES_RAW) diff --git a/worker_plan/worker_plan_internal/plan/stages/markdown_documents.py b/worker_plan/worker_plan_internal/plan/stages/markdown_documents.py index 066c84ac..22f67876 100644 --- a/worker_plan/worker_plan_internal/plan/stages/markdown_documents.py +++ b/worker_plan/worker_plan_internal/plan/stages/markdown_documents.py @@ -8,9 +8,7 @@ class MarkdownWithDocumentsToCreateAndFindTask(PlanTask): - """ - Create markdown with the "documents to create and find" - """ + """Format drafted documents into a structured markdown with roles, templates, and approval steps.""" def output(self): return self.local_target(FilenameEnum.DOCUMENTS_TO_CREATE_AND_FIND_MARKDOWN) diff --git a/worker_plan/worker_plan_internal/plan/stages/physical_locations.py b/worker_plan/worker_plan_internal/plan/stages/physical_locations.py index 85f74a68..4d8b9c06 100644 --- a/worker_plan/worker_plan_internal/plan/stages/physical_locations.py +++ b/worker_plan/worker_plan_internal/plan/stages/physical_locations.py @@ -15,9 +15,7 @@ class PhysicalLocationsTask(PlanTask): - """ - Identify/suggest physical locations for the plan. - """ + """Determine where the project operates — extract or suggest physical locations.""" def requires(self): return { 'setup': self.clone(SetupTask), diff --git a/worker_plan/worker_plan_internal/plan/stages/report.py b/worker_plan/worker_plan_internal/plan/stages/report.py index da7e4034..5f41bdbd 100644 --- a/worker_plan/worker_plan_internal/plan/stages/report.py +++ b/worker_plan/worker_plan_internal/plan/stages/report.py @@ -29,9 +29,7 @@ class ReportTask(PlanTask): - """ - Generate a report html document. - """ + """Assemble all pipeline outputs into the final HTML report.""" def output(self): return self.local_target(FilenameEnum.REPORT) diff --git a/worker_plan/worker_plan_internal/plan/stages/review_plan.py b/worker_plan/worker_plan_internal/plan/stages/review_plan.py index 66bd44b1..f64309f3 100644 --- a/worker_plan/worker_plan_internal/plan/stages/review_plan.py +++ b/worker_plan/worker_plan_internal/plan/stages/review_plan.py @@ -17,9 +17,7 @@ class ReviewPlanTask(PlanTask): - """ - Ask questions about the almost finished plan. - """ + """Critically review the near-final plan with targeted questions and SMART recommendations.""" def output(self): return { 'raw': self.local_target(FilenameEnum.REVIEW_PLAN_RAW), diff --git a/worker_plan/worker_plan_internal/plan/stages/scenarios_markdown.py b/worker_plan/worker_plan_internal/plan/stages/scenarios_markdown.py index fa707435..6051b09a 100644 --- a/worker_plan/worker_plan_internal/plan/stages/scenarios_markdown.py +++ b/worker_plan/worker_plan_internal/plan/stages/scenarios_markdown.py @@ -8,9 +8,7 @@ class ScenariosMarkdownTask(PlanTask): - """ - Present the scenarios in a human readable format. - """ + """Format the selected scenario and rejected alternatives into a readable document.""" def requires(self): return { 'candidate_scenarios': self.clone(CandidateScenariosTask), From e3bbfc9c821653cc9834395065f903d5547f606d Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 7 Apr 2026 16:18:50 +0200 Subject: [PATCH 17/24] fix: polish final 10 task descriptions - create_wbs_level1: extract project title and top-level phases - create_wbs_level2: decompose phases into major tasks - create_wbs_level3: break tasks into detailed subtasks - wbs_project_level1_and_level2: merge into unified tree - wbs_project_level1_and_level2_and_level3: complete hierarchy + CSV - executive_summary: one-pager for decision-makers - select_scenario: evaluate trade-offs with rationale - questions_and_answers: anticipate stakeholder questions - premortem: imagine failure, identify how and why - self_audit: checklist for gaps, contradictions, unsupported claims Co-Authored-By: Claude Opus 4.6 (1M context) --- .../plan/stages/create_wbs_level1.py | 9 +-------- .../plan/stages/create_wbs_level2.py | 10 +--------- .../plan/stages/create_wbs_level3.py | 13 +------------ .../plan/stages/executive_summary.py | 4 +--- .../worker_plan_internal/plan/stages/premortem.py | 2 +- .../plan/stages/questions_and_answers.py | 2 +- .../plan/stages/select_scenario.py | 4 +--- .../worker_plan_internal/plan/stages/self_audit.py | 2 +- .../plan/stages/wbs_project_level1_and_level2.py | 8 +------- .../plan/stages/wbs_project_level1_level2_level3.py | 8 +------- 10 files changed, 10 insertions(+), 52 deletions(-) diff --git a/worker_plan/worker_plan_internal/plan/stages/create_wbs_level1.py b/worker_plan/worker_plan_internal/plan/stages/create_wbs_level1.py index 5755d8ed..e8f08fa3 100644 --- a/worker_plan/worker_plan_internal/plan/stages/create_wbs_level1.py +++ b/worker_plan/worker_plan_internal/plan/stages/create_wbs_level1.py @@ -12,14 +12,7 @@ class CreateWBSLevel1Task(PlanTask): - """ - Creates the Work Breakdown Structure (WBS) Level 1. - Depends on: - - ProjectPlanTask: provides the project plan as JSON. - Produces: - - Raw WBS Level 1 output file (xxx-wbs_level1_raw.json) - - Cleaned up WBS Level 1 file (xxx-wbs_level1.json) - """ + """Extract the project title and top-level phases (WBS Level 1) from the project plan.""" def requires(self): return { 'project_plan': self.clone(ProjectPlanTask) diff --git a/worker_plan/worker_plan_internal/plan/stages/create_wbs_level2.py b/worker_plan/worker_plan_internal/plan/stages/create_wbs_level2.py index 6ae31ff5..74e900cc 100644 --- a/worker_plan/worker_plan_internal/plan/stages/create_wbs_level2.py +++ b/worker_plan/worker_plan_internal/plan/stages/create_wbs_level2.py @@ -16,15 +16,7 @@ class CreateWBSLevel2Task(PlanTask): - """ - Creates the Work Breakdown Structure (WBS) Level 2. - Depends on: - - ProjectPlanTask: provides the project plan as JSON. - - CreateWBSLevel1Task: provides the cleaned WBS Level 1 result. - Produces: - - Raw WBS Level 2 output (007-wbs_level2_raw.json) - - Cleaned WBS Level 2 output (008-wbs_level2.json) - """ + """Decompose top-level phases into major tasks (WBS Level 2).""" def requires(self): return { 'strategic_decisions_markdown': self.clone(StrategicDecisionsMarkdownTask), diff --git a/worker_plan/worker_plan_internal/plan/stages/create_wbs_level3.py b/worker_plan/worker_plan_internal/plan/stages/create_wbs_level3.py index 282e94cc..7e854827 100644 --- a/worker_plan/worker_plan_internal/plan/stages/create_wbs_level3.py +++ b/worker_plan/worker_plan_internal/plan/stages/create_wbs_level3.py @@ -19,18 +19,7 @@ class CreateWBSLevel3Task(PlanTask): - """ - This task creates the Work Breakdown Structure (WBS) Level 3, by decomposing tasks from Level 2 into subtasks. - - It depends on: - - ProjectPlanTask: provides the project plan JSON. - - WBSProjectLevel1AndLevel2Task: provides the major phases with subtasks and the task UUIDs. - - EstimateTaskDurationsTask: provides the aggregated task durations (task_duration_list). - - For each task without any subtasks, a query is built and executed using the LLM. - The raw JSON result for each task is written to a file using the template from FilenameEnum. - Finally, all individual results are accumulated and written as an aggregated JSON file. - """ + """Break Level 2 tasks into detailed subtasks (WBS Level 3).""" def output(self): return self.local_target(FilenameEnum.WBS_LEVEL3) diff --git a/worker_plan/worker_plan_internal/plan/stages/executive_summary.py b/worker_plan/worker_plan_internal/plan/stages/executive_summary.py index 0c7ba83b..33b14a35 100644 --- a/worker_plan/worker_plan_internal/plan/stages/executive_summary.py +++ b/worker_plan/worker_plan_internal/plan/stages/executive_summary.py @@ -18,9 +18,7 @@ class ExecutiveSummaryTask(PlanTask): - """ - Create an executive summary of the plan. - """ + """Produce a concise one-pager for decision-makers with key findings and recommendations.""" def output(self): return { 'raw': self.local_target(FilenameEnum.EXECUTIVE_SUMMARY_RAW), diff --git a/worker_plan/worker_plan_internal/plan/stages/premortem.py b/worker_plan/worker_plan_internal/plan/stages/premortem.py index 1058d6f4..49d42c00 100644 --- a/worker_plan/worker_plan_internal/plan/stages/premortem.py +++ b/worker_plan/worker_plan_internal/plan/stages/premortem.py @@ -21,7 +21,7 @@ class PremortemTask(PlanTask): - """Conduct premortem analysis to identify potential failure modes.""" + """Imagine the project has already failed — identify how and why it would happen.""" def output(self): return { diff --git a/worker_plan/worker_plan_internal/plan/stages/questions_and_answers.py b/worker_plan/worker_plan_internal/plan/stages/questions_and_answers.py index aa2485bc..66a0a261 100644 --- a/worker_plan/worker_plan_internal/plan/stages/questions_and_answers.py +++ b/worker_plan/worker_plan_internal/plan/stages/questions_and_answers.py @@ -20,7 +20,7 @@ class QuestionsAndAnswersTask(PlanTask): - """Generate Q&A documentation addressing plan details.""" + """Anticipate stakeholder questions and provide clear answers from the plan.""" def output(self): return { diff --git a/worker_plan/worker_plan_internal/plan/stages/select_scenario.py b/worker_plan/worker_plan_internal/plan/stages/select_scenario.py index ef5a9bac..faabde29 100644 --- a/worker_plan/worker_plan_internal/plan/stages/select_scenario.py +++ b/worker_plan/worker_plan_internal/plan/stages/select_scenario.py @@ -13,9 +13,7 @@ class SelectScenarioTask(PlanTask): - """ - Pick the best fitting scenario to make a plan for. - """ + """Evaluate trade-offs and select the best scenario with a rationale.""" def requires(self): return { 'setup': self.clone(SetupTask), diff --git a/worker_plan/worker_plan_internal/plan/stages/self_audit.py b/worker_plan/worker_plan_internal/plan/stages/self_audit.py index e2f6f369..cbe70036 100644 --- a/worker_plan/worker_plan_internal/plan/stages/self_audit.py +++ b/worker_plan/worker_plan_internal/plan/stages/self_audit.py @@ -27,7 +27,7 @@ class SelfAuditTask(PlanTask): - """Perform self-audit to validate plan completeness and consistency.""" + """Checklist-based diagnostic: find gaps, contradictions, and unsupported claims across all stages.""" def output(self): return { diff --git a/worker_plan/worker_plan_internal/plan/stages/wbs_project_level1_and_level2.py b/worker_plan/worker_plan_internal/plan/stages/wbs_project_level1_and_level2.py index e9a60463..03b4c37b 100644 --- a/worker_plan/worker_plan_internal/plan/stages/wbs_project_level1_and_level2.py +++ b/worker_plan/worker_plan_internal/plan/stages/wbs_project_level1_and_level2.py @@ -8,13 +8,7 @@ class WBSProjectLevel1AndLevel2Task(PlanTask): - """ - Create a WBS project from the WBS Level 1 and Level 2 JSON files. - - It depends on: - - CreateWBSLevel1Task: providing the cleaned WBS Level 1 JSON. - - CreateWBSLevel2Task: providing the major phases with subtasks and the task UUIDs. - """ + """Merge Level 1 and Level 2 into a unified WBS project tree.""" def output(self): return self.local_target(FilenameEnum.WBS_PROJECT_LEVEL1_AND_LEVEL2) diff --git a/worker_plan/worker_plan_internal/plan/stages/wbs_project_level1_level2_level3.py b/worker_plan/worker_plan_internal/plan/stages/wbs_project_level1_level2_level3.py index 033075ed..901a197c 100644 --- a/worker_plan/worker_plan_internal/plan/stages/wbs_project_level1_level2_level3.py +++ b/worker_plan/worker_plan_internal/plan/stages/wbs_project_level1_level2_level3.py @@ -9,13 +9,7 @@ class WBSProjectLevel1AndLevel2AndLevel3Task(PlanTask): - """ - Create a WBS project from the WBS Level 1 and Level 2 and Level 3 JSON files. - - It depends on: - - WBSProjectLevel1AndLevel2Task: providing the major phases with subtasks and the task UUIDs. - - CreateWBSLevel3Task: providing the decomposed tasks. - """ + """Merge all three WBS levels into the complete project hierarchy (JSON + CSV).""" def output(self): return { 'full': self.local_target(FilenameEnum.WBS_PROJECT_LEVEL1_AND_LEVEL2_AND_LEVEL3_FULL), From f7b80b22c728556fd4b41abef2235788045a729a Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 7 Apr 2026 16:22:11 +0200 Subject: [PATCH 18/24] fix: final polish on 10 task descriptions - start_time: active voice - setup: describe as loading input, not just data - expert_review: fix tense, reframe as assembling a panel - related_resources: list concrete examples - enrich_team_environment_info: clarify as equipment/facility needs - project_plan: specify goals, milestones, deliverables, criteria - governance_phase1_audit: designing governance, not auditing existing - swot_analysis: mention tailoring by plan purpose - team_markdown: list what it compiles - identify_documents: give concrete examples Co-Authored-By: Claude Opus 4.6 (1M context) --- .../plan/stages/enrich_team_environment_info.py | 2 +- worker_plan/worker_plan_internal/plan/stages/expert_review.py | 4 +--- .../plan/stages/governance_phase1_audit.py | 2 +- .../worker_plan_internal/plan/stages/identify_documents.py | 4 +--- worker_plan/worker_plan_internal/plan/stages/project_plan.py | 2 +- .../worker_plan_internal/plan/stages/related_resources.py | 2 +- worker_plan/worker_plan_internal/plan/stages/setup.py | 2 +- worker_plan/worker_plan_internal/plan/stages/start_time.py | 2 +- worker_plan/worker_plan_internal/plan/stages/swot_analysis.py | 2 +- worker_plan/worker_plan_internal/plan/stages/team_markdown.py | 2 +- 10 files changed, 10 insertions(+), 14 deletions(-) diff --git a/worker_plan/worker_plan_internal/plan/stages/enrich_team_environment_info.py b/worker_plan/worker_plan_internal/plan/stages/enrich_team_environment_info.py index baba514c..e3217c6c 100644 --- a/worker_plan/worker_plan_internal/plan/stages/enrich_team_environment_info.py +++ b/worker_plan/worker_plan_internal/plan/stages/enrich_team_environment_info.py @@ -19,7 +19,7 @@ class EnrichTeamMembersWithEnvironmentInfoTask(PlanTask): - """Enrich team members with environmental and contextual information.""" + """Add equipment needs and facility requirements for each team member's role.""" def requires(self): return { diff --git a/worker_plan/worker_plan_internal/plan/stages/expert_review.py b/worker_plan/worker_plan_internal/plan/stages/expert_review.py index 05b8635f..bb3fd4c5 100644 --- a/worker_plan/worker_plan_internal/plan/stages/expert_review.py +++ b/worker_plan/worker_plan_internal/plan/stages/expert_review.py @@ -19,9 +19,7 @@ class ExpertReviewTask(PlanTask): - """ - Finds experts to review the SWOT analysis and have them provide criticism. - """ + """Assemble a panel of domain experts and have them critique the plan.""" def requires(self): return { 'setup': self.clone(SetupTask), diff --git a/worker_plan/worker_plan_internal/plan/stages/governance_phase1_audit.py b/worker_plan/worker_plan_internal/plan/stages/governance_phase1_audit.py index 9797cbf5..7939372f 100644 --- a/worker_plan/worker_plan_internal/plan/stages/governance_phase1_audit.py +++ b/worker_plan/worker_plan_internal/plan/stages/governance_phase1_audit.py @@ -14,7 +14,7 @@ class GovernancePhase1AuditTask(PlanTask): - """Audit organizational governance structure and compliance requirements.""" + """Design the governance structure and compliance requirements for the plan.""" def requires(self): return { diff --git a/worker_plan/worker_plan_internal/plan/stages/identify_documents.py b/worker_plan/worker_plan_internal/plan/stages/identify_documents.py index b0970696..51d765c3 100644 --- a/worker_plan/worker_plan_internal/plan/stages/identify_documents.py +++ b/worker_plan/worker_plan_internal/plan/stages/identify_documents.py @@ -16,9 +16,7 @@ class IdentifyDocumentsTask(PlanTask): - """ - Identify documents that need to be created or found for the project. - """ + """List documents the project needs — permits, contracts, specs, research, etc.""" def output(self): return { "raw": self.local_target(FilenameEnum.IDENTIFIED_DOCUMENTS_RAW), diff --git a/worker_plan/worker_plan_internal/plan/stages/project_plan.py b/worker_plan/worker_plan_internal/plan/stages/project_plan.py index 8c91e15d..f91ac204 100644 --- a/worker_plan/worker_plan_internal/plan/stages/project_plan.py +++ b/worker_plan/worker_plan_internal/plan/stages/project_plan.py @@ -15,7 +15,7 @@ logger = logging.getLogger(__name__) class ProjectPlanTask(PlanTask): - """Generate the comprehensive project plan.""" + """Generate the project plan with goals, milestones, deliverables, and success criteria.""" def requires(self): return { diff --git a/worker_plan/worker_plan_internal/plan/stages/related_resources.py b/worker_plan/worker_plan_internal/plan/stages/related_resources.py index bbb37f48..ae0b2584 100644 --- a/worker_plan/worker_plan_internal/plan/stages/related_resources.py +++ b/worker_plan/worker_plan_internal/plan/stages/related_resources.py @@ -16,7 +16,7 @@ class RelatedResourcesTask(PlanTask): - """Identify external resources and tools needed for the project.""" + """Identify external resources needed: software, APIs, datasets, services, etc.""" def requires(self): return { diff --git a/worker_plan/worker_plan_internal/plan/stages/setup.py b/worker_plan/worker_plan_internal/plan/stages/setup.py index bd0ba06d..af60ff95 100644 --- a/worker_plan/worker_plan_internal/plan/stages/setup.py +++ b/worker_plan/worker_plan_internal/plan/stages/setup.py @@ -4,7 +4,7 @@ class SetupTask(PlanTask): - """The plan prompt text provided by the user.""" + """Load the user's plan prompt as the pipeline input.""" def output(self): return self.local_target(FilenameEnum.INITIAL_PLAN) diff --git a/worker_plan/worker_plan_internal/plan/stages/start_time.py b/worker_plan/worker_plan_internal/plan/stages/start_time.py index c655d279..c7e2a32b 100644 --- a/worker_plan/worker_plan_internal/plan/stages/start_time.py +++ b/worker_plan/worker_plan_internal/plan/stages/start_time.py @@ -4,7 +4,7 @@ class StartTimeTask(PlanTask): - """The timestamp when the pipeline was started.""" + """Record the pipeline start time.""" def output(self): return self.local_target(FilenameEnum.START_TIME) diff --git a/worker_plan/worker_plan_internal/plan/stages/swot_analysis.py b/worker_plan/worker_plan_internal/plan/stages/swot_analysis.py index e44ad61a..b488c69b 100644 --- a/worker_plan/worker_plan_internal/plan/stages/swot_analysis.py +++ b/worker_plan/worker_plan_internal/plan/stages/swot_analysis.py @@ -19,7 +19,7 @@ class SWOTAnalysisTask(PlanTask): - """Perform SWOT analysis on the project plan.""" + """Identify strengths, weaknesses, opportunities, and threats tailored to the plan's purpose.""" def requires(self): return { diff --git a/worker_plan/worker_plan_internal/plan/stages/team_markdown.py b/worker_plan/worker_plan_internal/plan/stages/team_markdown.py index 2b6ee47b..c7ff9313 100644 --- a/worker_plan/worker_plan_internal/plan/stages/team_markdown.py +++ b/worker_plan/worker_plan_internal/plan/stages/team_markdown.py @@ -11,7 +11,7 @@ class TeamMarkdownTask(PlanTask): - """Generate the team information document in markdown.""" + """Compile team roles, contracts, backgrounds, and equipment into one team document.""" def requires(self): return { From 41c8d48ae717b3834ec783256823e25af2d2aa6f Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 7 Apr 2026 16:25:41 +0200 Subject: [PATCH 19/24] fix: differentiate the 6 constraint checker descriptions Each now reflects its position in the pipeline (brainstormed, triaged, enriched, vital, scenarios, chosen) instead of repeating "Check X output for constraint violations." Co-Authored-By: Claude Opus 4.6 (1M context) --- .../plan/stages/constraint_checker_stages.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/worker_plan/worker_plan_internal/plan/stages/constraint_checker_stages.py b/worker_plan/worker_plan_internal/plan/stages/constraint_checker_stages.py index 74c08c28..b0ab8468 100644 --- a/worker_plan/worker_plan_internal/plan/stages/constraint_checker_stages.py +++ b/worker_plan/worker_plan_internal/plan/stages/constraint_checker_stages.py @@ -30,7 +30,7 @@ def _read_constraints_json(task: PlanTask) -> str: class PotentialLeversConstraintTask(PlanTask): - """Check potential levers output for constraint violations.""" + """Guardrail: verify brainstormed levers respect the user's constraints.""" def requires(self): return { 'extract_constraints': self.clone(ExtractConstraintsTask), @@ -49,7 +49,7 @@ def run_with_llm(self, llm: LLM) -> None: class DeduplicatedLeversConstraintTask(PlanTask): - """Check deduplicated levers output for constraint violations.""" + """Guardrail: verify triaged levers still respect the user's constraints.""" def requires(self): return { 'extract_constraints': self.clone(ExtractConstraintsTask), @@ -68,7 +68,7 @@ def run_with_llm(self, llm: LLM) -> None: class EnrichedLeversConstraintTask(PlanTask): - """Check enriched levers output for constraint violations.""" + """Guardrail: verify enriched levers still respect the user's constraints.""" def requires(self): return { 'extract_constraints': self.clone(ExtractConstraintsTask), @@ -87,7 +87,7 @@ def run_with_llm(self, llm: LLM) -> None: class VitalFewLeversConstraintTask(PlanTask): - """Check vital few levers output for constraint violations.""" + """Guardrail: verify the selected vital levers respect the user's constraints.""" def requires(self): return { 'extract_constraints': self.clone(ExtractConstraintsTask), @@ -106,7 +106,7 @@ def run_with_llm(self, llm: LLM) -> None: class CandidateScenariosConstraintTask(PlanTask): - """Check candidate scenarios output for constraint violations.""" + """Guardrail: verify generated scenarios respect the user's constraints.""" def requires(self): return { 'extract_constraints': self.clone(ExtractConstraintsTask), @@ -125,7 +125,7 @@ def run_with_llm(self, llm: LLM) -> None: class SelectedScenarioConstraintTask(PlanTask): - """Check selected scenario output for constraint violations.""" + """Guardrail: verify the chosen scenario respects the user's constraints before planning begins.""" def requires(self): return { 'extract_constraints': self.clone(ExtractConstraintsTask), From 72874bd75e6ae935deef9c4d1a92c030b52fc3e1 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 7 Apr 2026 16:34:14 +0200 Subject: [PATCH 20/24] refactor: rename upstream_stages to depends_on in DAG JSON output Co-Authored-By: Claude Opus 4.6 (1M context) --- worker_plan/worker_plan_internal/extract_dag.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worker_plan/worker_plan_internal/extract_dag.py b/worker_plan/worker_plan_internal/extract_dag.py index c816aac8..43f98acf 100644 --- a/worker_plan/worker_plan_internal/extract_dag.py +++ b/worker_plan/worker_plan_internal/extract_dag.py @@ -199,7 +199,7 @@ def _walk(task: luigi.Task) -> None: description = cls.description() if hasattr(cls, "description") else "" output_files = _extract_output_filenames(task) source_files = _extract_source_files(task) - upstream_stage_names = sorted(set( + depends_on_names = sorted(set( _class_name_to_stage_name(dep.__class__.__name__) for dep in upstream_tasks )) @@ -208,7 +208,7 @@ def _walk(task: luigi.Task) -> None: "name": stage_name, "description": description, "output_files": output_files, - "upstream_stages": upstream_stage_names, + "depends_on": depends_on_names, "source_files": source_files, }) From 1101fd46bb738f9686a36be2c9b4f755381a94e6 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 7 Apr 2026 16:38:59 +0200 Subject: [PATCH 21/24] feat: wrap DAG output in a top-level schema object Adds schema_version, pipeline_name, and description fields around the stages array. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../worker_plan_internal/extract_dag.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/worker_plan/worker_plan_internal/extract_dag.py b/worker_plan/worker_plan_internal/extract_dag.py index 43f98acf..322b98cf 100644 --- a/worker_plan/worker_plan_internal/extract_dag.py +++ b/worker_plan/worker_plan_internal/extract_dag.py @@ -166,10 +166,10 @@ def _output_sort_key(stage: dict[str, Any]) -> tuple[int, int, str]: return (9999, 0, stage["name"]) -def extract_dag() -> list[dict[str, Any]]: +def extract_dag() -> dict[str, Any]: """Walk the FullPlanPipeline task graph and extract DAG info. - Returns a list of stage dicts sorted by output file prefix (pipeline order). + Returns a top-level schema object with stages sorted by pipeline order. """ from worker_plan_internal.plan.stages.full_plan_pipeline import FullPlanPipeline @@ -215,7 +215,13 @@ def _walk(task: luigi.Task) -> None: _walk(root) stages.sort(key=_output_sort_key) - return stages + + return { + "schema_version": "1.0", + "pipeline_name": "planning_pipeline", + "description": "LLM-driven planning pipeline", + "stages": stages, + } def main() -> None: @@ -224,12 +230,12 @@ def main() -> None: if len(args) >= 2 and args[0] == "--output": output_path = args[1] - stages = extract_dag() - dag_json = json.dumps(stages, indent=2, ensure_ascii=False) + dag = extract_dag() + dag_json = json.dumps(dag, indent=2, ensure_ascii=False) if output_path: Path(output_path).write_text(dag_json + "\n", encoding="utf-8") - print(f"Wrote {len(stages)} stages to {output_path}", file=sys.stderr) + print(f"Wrote {len(dag['stages'])} stages to {output_path}", file=sys.stderr) else: print(dag_json) From ef9213e33e0debeb97ed86a34ca507728a2299a8 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 7 Apr 2026 16:51:57 +0200 Subject: [PATCH 22/24] refactor: rename "name" to "id" in DAG JSON stage objects Co-Authored-By: Claude Opus 4.6 (1M context) --- worker_plan/worker_plan_internal/extract_dag.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/worker_plan/worker_plan_internal/extract_dag.py b/worker_plan/worker_plan_internal/extract_dag.py index 322b98cf..e954d622 100644 --- a/worker_plan/worker_plan_internal/extract_dag.py +++ b/worker_plan/worker_plan_internal/extract_dag.py @@ -162,8 +162,8 @@ def _output_sort_key(stage: dict[str, Any]) -> tuple[int, int, str]: if match: major = int(match.group(1)) minor = int(match.group(2)) if match.group(2) else 0 - return (major, minor, stage["name"]) - return (9999, 0, stage["name"]) + return (major, minor, stage["id"]) + return (9999, 0, stage["id"]) def extract_dag() -> dict[str, Any]: @@ -205,7 +205,7 @@ def _walk(task: luigi.Task) -> None: )) stages.append({ - "name": stage_name, + "id": stage_name, "description": description, "output_files": output_files, "depends_on": depends_on_names, From 26f8b3b049be8b9864acd6a6e10851e5f09feee5 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 7 Apr 2026 16:55:41 +0200 Subject: [PATCH 23/24] fix: mention PlanExe in pipeline description Co-Authored-By: Claude Opus 4.6 (1M context) --- worker_plan/worker_plan_internal/extract_dag.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worker_plan/worker_plan_internal/extract_dag.py b/worker_plan/worker_plan_internal/extract_dag.py index e954d622..e88cc399 100644 --- a/worker_plan/worker_plan_internal/extract_dag.py +++ b/worker_plan/worker_plan_internal/extract_dag.py @@ -219,7 +219,7 @@ def _walk(task: luigi.Task) -> None: return { "schema_version": "1.0", "pipeline_name": "planning_pipeline", - "description": "LLM-driven planning pipeline", + "description": "PlanExe's LLM-driven planning pipeline", "stages": stages, } From 9b6044b38737d3aad6cd1ce68f7ed86956c83b7e Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 7 Apr 2026 16:58:29 +0200 Subject: [PATCH 24/24] fix: update pipeline description to mention DAG and AI-driven planning Co-Authored-By: Claude Opus 4.6 (1M context) --- worker_plan/worker_plan_internal/extract_dag.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worker_plan/worker_plan_internal/extract_dag.py b/worker_plan/worker_plan_internal/extract_dag.py index e88cc399..62ac55a6 100644 --- a/worker_plan/worker_plan_internal/extract_dag.py +++ b/worker_plan/worker_plan_internal/extract_dag.py @@ -219,7 +219,7 @@ def _walk(task: luigi.Task) -> None: return { "schema_version": "1.0", "pipeline_name": "planning_pipeline", - "description": "PlanExe's LLM-driven planning pipeline", + "description": "DAG for PlanExe, an AI-driven project planning system.", "stages": stages, }