maestro-flow 0.4.21 → 0.4.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/.agents/skills/maestro-analyze/SKILL.md +78 -34
  2. package/.agents/skills/maestro-blueprint/SKILL.md +57 -24
  3. package/.agents/skills/maestro-brainstorm/SKILL.md +69 -34
  4. package/.agents/skills/maestro-execute/SKILL.md +45 -15
  5. package/.agents/skills/maestro-grill/SKILL.md +63 -38
  6. package/.agents/skills/maestro-init/SKILL.md +59 -17
  7. package/.agents/skills/maestro-milestone-audit/SKILL.md +48 -5
  8. package/.agents/skills/maestro-milestone-complete/SKILL.md +48 -6
  9. package/.agents/skills/maestro-milestone-release/SKILL.md +42 -11
  10. package/.agents/skills/maestro-plan/SKILL.md +19 -13
  11. package/.agents/skills/maestro-roadmap/SKILL.md +59 -29
  12. package/.agents/skills/maestro-verify/SKILL.md +46 -11
  13. package/.agents/skills/team-adversarial-swarm/scripts/__pycache__/pheromone.cpython-313.pyc +0 -0
  14. package/.agents/skills/team-adversarial-swarm/scripts/__pycache__/scoring.cpython-313.pyc +0 -0
  15. package/.agents/skills/team-adversarial-swarm/scripts/aco.py +15 -15
  16. package/.agents/skills/team-adversarial-swarm/scripts/pheromone.py +2 -2
  17. package/.agents/skills/team-adversarial-swarm/scripts/scoring.py +1 -1
  18. package/.agents/skills/team-swarm/scripts/aco.py +15 -15
  19. package/.agents/skills/team-swarm/scripts/pheromone.py +2 -2
  20. package/.agents/skills/team-swarm/scripts/scoring.py +1 -1
  21. package/.agy/skills/maestro-analyze/SKILL.md +78 -34
  22. package/.agy/skills/maestro-blueprint/SKILL.md +57 -24
  23. package/.agy/skills/maestro-brainstorm/SKILL.md +69 -34
  24. package/.agy/skills/maestro-execute/SKILL.md +45 -15
  25. package/.agy/skills/maestro-grill/SKILL.md +63 -38
  26. package/.agy/skills/maestro-init/SKILL.md +59 -17
  27. package/.agy/skills/maestro-milestone-audit/SKILL.md +48 -5
  28. package/.agy/skills/maestro-milestone-complete/SKILL.md +48 -6
  29. package/.agy/skills/maestro-milestone-release/SKILL.md +42 -11
  30. package/.agy/skills/maestro-plan/SKILL.md +19 -13
  31. package/.agy/skills/maestro-roadmap/SKILL.md +59 -29
  32. package/.agy/skills/maestro-verify/SKILL.md +46 -11
  33. package/.agy/skills/team-adversarial-swarm/scripts/__pycache__/pheromone.cpython-313.pyc +0 -0
  34. package/.agy/skills/team-adversarial-swarm/scripts/__pycache__/scoring.cpython-313.pyc +0 -0
  35. package/.agy/skills/team-adversarial-swarm/scripts/aco.py +15 -15
  36. package/.agy/skills/team-adversarial-swarm/scripts/pheromone.py +2 -2
  37. package/.agy/skills/team-adversarial-swarm/scripts/scoring.py +1 -1
  38. package/.agy/skills/team-swarm/scripts/aco.py +15 -15
  39. package/.agy/skills/team-swarm/scripts/pheromone.py +2 -2
  40. package/.agy/skills/team-swarm/scripts/scoring.py +1 -1
  41. package/.claude/commands/maestro-analyze.md +78 -34
  42. package/.claude/commands/maestro-blueprint.md +57 -24
  43. package/.claude/commands/maestro-brainstorm.md +69 -34
  44. package/.claude/commands/maestro-execute.md +45 -15
  45. package/.claude/commands/maestro-grill.md +63 -38
  46. package/.claude/commands/maestro-init.md +59 -17
  47. package/.claude/commands/maestro-milestone-audit.md +48 -5
  48. package/.claude/commands/maestro-milestone-complete.md +48 -6
  49. package/.claude/commands/maestro-milestone-release.md +42 -11
  50. package/.claude/commands/maestro-plan.md +19 -13
  51. package/.claude/commands/maestro-roadmap.md +59 -29
  52. package/.claude/commands/maestro-verify.md +46 -11
  53. package/.claude/skills/team-adversarial-swarm/scripts/__pycache__/pheromone.cpython-313.pyc +0 -0
  54. package/.claude/skills/team-adversarial-swarm/scripts/__pycache__/scoring.cpython-313.pyc +0 -0
  55. package/.claude/skills/team-adversarial-swarm/scripts/aco.py +15 -15
  56. package/.claude/skills/team-adversarial-swarm/scripts/pheromone.py +2 -2
  57. package/.claude/skills/team-adversarial-swarm/scripts/scoring.py +1 -1
  58. package/.claude/skills/team-swarm/scripts/aco.py +15 -15
  59. package/.claude/skills/team-swarm/scripts/pheromone.py +2 -2
  60. package/.claude/skills/team-swarm/scripts/scoring.py +1 -1
  61. package/package.json +1 -1
  62. package/workflows/command-authoring.md +823 -0
  63. package/workflows/interview-mechanics.md +7 -0
@@ -17,6 +17,8 @@ allowed-tools:
17
17
 
18
18
  <purpose>
19
19
  Package a completed milestone into a releasable version. Bumps the project version (e.g. `package.json`, `pyproject.toml`, or language-specific manifest), generates or appends a changelog entry from phase/milestone summaries and git log, creates an annotated git tag, and optionally pushes to the remote. Runs after `/maestro-milestone-complete` has archived the milestone; serves as the final delivery step in the SDLC loop.
20
+
21
+ Pipeline position: downstream of `/maestro-milestone-complete` (consumes archived milestone + audit verdict). Terminal command — no downstream consumer.
20
22
  </purpose>
21
23
 
22
24
  <required_reading>
@@ -27,11 +29,14 @@ Package a completed milestone into a releasable version. Bumps the project versi
27
29
  $ARGUMENTS -- optional explicit version string and flags.
28
30
 
29
31
  **Flags:**
30
- - `<version>` -- explicit version (e.g. `1.2.0`). If omitted, version is derived from `--bump` or prompted.
31
- - `--bump patch|minor|major` -- semver bump relative to the current version (default: `minor`)
32
- - `--dry-run` -- compute the next version, changelog diff, and tag name without writing files or creating tags
33
- - `--no-tag` -- skip git tag creation (version bump + changelog only)
34
- - `--no-push` -- skip `git push --follow-tags` after tagging
32
+
33
+ | Flag | Effect | Default |
34
+ |------|--------|---------|
35
+ | `<version>` | Explicit version (e.g. `1.2.0`). If omitted, version is derived from `--bump` or prompted | — |
36
+ | `--bump patch\|minor\|major` | Semver bump relative to the current version | `minor` |
37
+ | `--dry-run` | Compute the next version, changelog diff, and tag name without writing files or creating tags | `false` |
38
+ | `--no-tag` | Skip git tag creation (version bump + changelog only) | `false` |
39
+ | `--no-push` | Skip `git push --follow-tags` after tagging | `false` |
35
40
 
36
41
  **State files:**
37
42
  - `.workflow/state.json` -- current_milestone, previous release version
@@ -45,6 +50,13 @@ $ARGUMENTS -- optional explicit version string and flags.
45
50
  - Working tree must be clean (no uncommitted changes) unless `--dry-run`
46
51
  </context>
47
52
 
53
+ <interview_protocol>
54
+ Follows @~/.maestro/workflows/interview-mechanics.md standard.
55
+
56
+ **Decision points**: version bump type (major / minor / patch / custom), changelog review and confirmation
57
+ **Scope guard**: only release decisions; do not prejudge next milestone scope
58
+ </interview_protocol>
59
+
48
60
  <execution>
49
61
  Follow '~/.maestro/workflows/milestone-release.md' completely.
50
62
 
@@ -57,7 +69,12 @@ Follow '~/.maestro/workflows/milestone-release.md' completely.
57
69
  6. Create annotated git tag `v{version}` with release notes body (unless `--no-tag`)
58
70
  7. Push commit + tag to remote (unless `--no-push`)
59
71
 
60
- **Report format on completion:**
72
+ For `--dry-run`, print the computed version, changelog diff, and tag name without side effects.
73
+ </execution>
74
+
75
+ <completion>
76
+ ### Standalone report
77
+
61
78
  ```
62
79
  === RELEASE COMPLETE ===
63
80
  Version: v{previous} → v{new}
@@ -65,14 +82,28 @@ Milestone: {milestone_name}
65
82
  Tag: v{new} {pushed|local-only}
66
83
  Changelog: {N} entries written to CHANGELOG.md
67
84
  Manifest: {file_path} updated
85
+ ```
86
+
87
+ ### Ralph-invoked completion
68
88
 
69
- Next steps:
70
- /maestro-plan {next_phase} -- Start next milestone's first phase
71
- /manage-status -- View project dashboard
89
+ End the step by calling the CLI (no text block output):
90
+ ```
91
+ maestro ralph complete <idx> --status {STATUS} [--evidence {path}]
72
92
  ```
73
93
 
74
- For `--dry-run`, print the computed version, changelog diff, and tag name without side effects.
75
- </execution>
94
+ Status verdicts:
95
+ - **DONE** — Normal completion
96
+ - **DONE_WITH_CONCERNS** — Completed with caveats; pass `--concerns`
97
+ - **NEEDS_RETRY** — Tooling error / transient issue; ralph will retry
98
+ - **BLOCKED** — External hard blocker; pass `--reason`
99
+
100
+ ### Next-step routing
101
+
102
+ | Condition | Suggestion |
103
+ |-----------|-----------|
104
+ | Release successful, starting next milestone | `/maestro-plan {next_phase}` |
105
+ | Want to view project dashboard | `/manage-status` |
106
+ </completion>
76
107
 
77
108
  <error_codes>
78
109
  | Code | Severity | Condition | Recovery |
@@ -122,7 +122,13 @@ For each created TASK-{NNN}.json that has issue_id:
122
122
 
123
123
  This ensures issue → TASK traceability. The `task_refs[]` and `task_plan_dir` fields on the issue allow the dashboard to resolve and display associated TASK details.
124
124
 
125
- **Report format on completion:**
125
+ ### Mode: Revise / Check
126
+
127
+ Follow workflow plan.md § "Revise Mode" and § "Check Mode" respectively. These modes bypass the standard P1-P5 create pipeline.
128
+ </execution>
129
+
130
+ <completion>
131
+ ### Standalone report
126
132
 
127
133
  ```
128
134
  === PLAN READY ===
@@ -133,20 +139,16 @@ Collision: {collision_status}
133
139
 
134
140
  Plan: scratch/{YYYYMMDD}-plan-P{N}-{slug}/plan.json
135
141
  Tasks: scratch/{YYYYMMDD}-plan-P{N}-{slug}/.task/TASK-*.json
136
-
137
- Next steps:
138
- /maestro-execute -- Execute the plan
139
- /maestro-execute --dir {dir} -- Execute specific plan
140
- /maestro-plan {phase} -- Re-plan with modifications
141
142
  ```
142
143
 
143
- **Completion (when invoked from ralph):**
144
- End the step by calling the CLI (no `--- COMPLETION STATUS ---` text block):
144
+ ### Ralph-invoked completion
145
+
146
+ End the step by calling the CLI (no text block output):
145
147
  ```
146
- maestro ralph complete <idx> --status DONE [--evidence scratch/{YYYYMMDD}-plan-P{N}-{slug}/plan.json]
148
+ maestro ralph complete <idx> --status {STATUS} [--evidence scratch/{YYYYMMDD}-plan-P{N}-{slug}/plan.json]
147
149
  ```
148
150
 
149
- STATUS verdicts (CLI-enforced enum):
151
+ Status verdicts:
150
152
  - **DONE** — Plan created/revised and confirmed → next step picks up automatically
151
153
  - **DONE_WITH_CONCERNS** — Plan produced but with explicit caveats; pass `--concerns "..."`
152
154
  - **NEEDS_RETRY** — Plan failed (tooling error, transient issue); ralph will retry
@@ -154,10 +156,14 @@ STATUS verdicts (CLI-enforced enum):
154
156
 
155
157
  > Ambiguous requirements are NOT a completion status — resolve them in-place via `ask_question` during planning (≤3 rounds), then proceed to DONE. `NEEDS_CONTEXT` has been removed; context shortage is handled by the harness's automatic compaction.
156
158
 
157
- ### Mode: Revise / Check
159
+ ### Next-step routing
158
160
 
159
- Follow workflow plan.md § "Revise Mode" and § "Check Mode" respectively. These modes bypass the standard P1-P5 create pipeline.
160
- </execution>
161
+ | Condition | Suggestion |
162
+ |-----------|-----------|
163
+ | Plan confirmed for execution | `/maestro-execute` |
164
+ | Plan confirmed, specific directory | `/maestro-execute --dir {dir}` |
165
+ | Re-plan with modifications | `/maestro-plan {phase}` |
166
+ </completion>
161
167
 
162
168
  <error_codes>
163
169
  | Code | Severity | Condition | Recovery |
@@ -38,13 +38,16 @@ For formal specification documents (Product Brief, PRD, Architecture, Epics), us
38
38
  $ARGUMENTS -- requirement text, @file reference, or upstream context source.
39
39
 
40
40
  **Flags:**
41
- - `-y` / `--yes`: Auto mode — skip interactive questions, use recommended defaults
42
- - `-c` / `--continue`: Resume from last checkpoint
43
- - `-m progressive|direct|auto`: Decomposition strategy (default: auto)
44
- - `--from <source>`: Load upstream context package (brainstorm:ID, blueprint:BLP-xxx, analyze:ANL-xxx, @file, or path). Consumes context-package.json
45
- - `--from-brainstorm SESSION-ID`: (backward compat alias for `--from brainstorm:ID`)
46
- - `--revise [instructions]`: Revise existing roadmap. If instructions provided, apply directly. If omitted, ask user. Preserves completed phase progress.
47
- - `--review`: Roadmap health assessment (read-only)
41
+
42
+ | Flag | Effect | Default |
43
+ |------|--------|---------|
44
+ | `-y` / `--yes` | Auto mode skip interactive questions, use recommended defaults | false |
45
+ | `-c` / `--continue` | Resume from last checkpoint | false |
46
+ | `-m progressive\|direct\|auto` | Decomposition strategy | auto |
47
+ | `--from <source>` | Load upstream context package (brainstorm:ID, blueprint:BLP-xxx, analyze:ANL-xxx, @file, or path). Consumes context-package.json | — |
48
+ | `--from-brainstorm SESSION-ID` | Backward compat alias for `--from brainstorm:ID` | — |
49
+ | `--revise [instructions]` | Revise existing roadmap. If instructions provided, apply directly. If omitted, ask user. Preserves completed phase progress. | — |
50
+ | `--review` | Roadmap health assessment (read-only) | — |
48
51
 
49
52
  **Input types:**
50
53
  - Direct text: `"Implement user authentication system with OAuth and 2FA"`
@@ -63,24 +66,22 @@ maestro-roadmap → .workflow/roadmap.md (Milestone > Phase hierarchy)
63
66
  maestro-analyze {phase} → maestro-plan → maestro-execute → maestro-verify
64
67
  ```
65
68
 
66
- ### Pre-load specs
67
- 1. **Architecture specs**: Run `maestro spec load --category arch` to load architecture constraints. Use as context for phase decomposition — ensures roadmap respects documented decisions and boundaries.
68
- 2. Optionalproceed without if unavailable.
69
+ ### Pre-load
70
+
71
+ 1. **Specs**: `maestro spec load --category arch` load architecture constraints for phase decomposition
72
+ 2. **Wiki search**: `maestro wiki search "{requirement keywords}" --json` → prior knowledge
73
+ 3. All optional — proceed without if unavailable
69
74
  </context>
70
75
 
71
76
  <interview_protocol>
72
- Interview the user relentlessly until shared understanding is reached. Active only in interactive mode; skip when `-y/--yes`, `--revise`, `--review`, `-c/--continue`, or input is already specific (clear requirement + mode).
73
-
74
- - One decision per turn via ask_question with 2–4 options + a (Recommended) default. The user controls termination — keep interviewing until convergence; they can interrupt naturally or via `Other` at any time.
75
- - Search-first when uncertain: before asking, resolve via `state.json`, existing `roadmap.md`, `project.md`, `maestro spec load`, `maestro wiki search`, Glob/Grep/Read, or for open-ended multi-file scans — spawn `invoke_subagent([{ TypeName: "<TypeName>", Role: "<Role>", Prompt: "<Prompt>", Workspace: "inherit" }])` / `maestro delegate ... --role explore`. Never ask what code or memory can verify; never bounce your own ambiguity back to the user — search first, then ask only what truly needs human judgment.
76
- - Writeback cadence: each settled decision is immediately appended/updated in the `Roadmap Decisions` section at the top of `.workflow/roadmap.md` (create the section if absent). Do NOT batch writeback to the end — partial decisions must already be on disk before the next question.
77
- - Walk the decision dependency tree strictly: mode → requirement scope → decomposition strategy → phase dependencies/order. Do not open the next branch until the current one is settled.
78
- - Scope guard: only decide the shape of the roadmap. Do not pre-resolve intra-phase task breakdown — that belongs to `plan`.
79
-
80
- Decision points: scope (MVP / complete / phased) → strategy (progressive / direct / auto) → milestone boundaries → phase dependencies and order.
81
-
82
- Exit: on consensus or explicit user signal to proceed, finalize the `Roadmap Decisions` section (rows already populated incrementally). Schema:
83
- `| # | Decision | Choice | Source (user / code / default) |`
77
+ Follows @~/.maestro/workflows/interview-mechanics.md standard.
78
+
79
+ **Interaction mode**: convergent menu-driven
80
+ **Decision tree** (strict order): mode (create / revise / review) requirement scope (MVP / complete / phased) decomposition strategy (progressive / direct / auto) milestone boundaries phase dependencies and order
81
+ **Scope guard**: only roadmap shape; do not pre-resolve intra-phase task breakdown (belongs to plan)
82
+ **Writeback target**: .workflow/roadmap.md "Roadmap Decisions" section (create if absent)
83
+ **Additional skip conditions**: --revise, --review (skip to respective mode)
84
+ **Exit condition**: on consensus or explicit user signal → finalize Roadmap Decisions section
84
85
  </interview_protocol>
85
86
 
86
87
  <execution>
@@ -93,16 +94,45 @@ Sub-modes:
93
94
  - **Revise** (`--revise`): Follow workflow roadmap.md "Mode: Revise" section
94
95
  - **Review** (`--review`): Follow workflow roadmap.md "Mode: Review" section
95
96
 
96
- ### Next-step routing on completion
97
+ </execution>
98
+
99
+ <completion>
100
+ ### Standalone report
101
+
102
+ ```
103
+ === ROADMAP READY ===
104
+ Milestones: {count}
105
+ Phases: {total_phases}
106
+ Strategy: {progressive|direct|auto}
107
+ Output: .workflow/roadmap.md
108
+ --- COMPLETION STATUS ---
109
+ Status: {DONE|DONE_WITH_CONCERNS}
110
+ Concerns: {if any}
111
+ ```
112
+
113
+ ### Ralph-invoked completion
114
+
115
+ End the step by calling the CLI (no text block output):
116
+ ```
117
+ maestro ralph complete <idx> --status {STATUS} [--evidence {path}]
118
+ ```
119
+
120
+ Status verdicts:
121
+ - **DONE** — Normal completion
122
+ - **DONE_WITH_CONCERNS** — Completed with caveats; pass `--concerns`
123
+ - **NEEDS_RETRY** — Tooling error / transient issue; ralph will retry
124
+ - **BLOCKED** — External hard blocker; pass `--reason`
125
+
126
+ ### Next-step routing
97
127
 
98
128
  | Condition | Suggestion |
99
129
  |-----------|-----------|
100
- | Roadmap approved, need analysis | /maestro-analyze 1 |
101
- | Simple project, ready to plan | /maestro-plan 1 |
102
- | Need UI design first | /maestro-impeccable build |
103
- | View project dashboard | /manage-status |
104
- | Need formal spec documents | /maestro-blueprint |
105
- </execution>
130
+ | Roadmap approved, need analysis | `/maestro-analyze 1` |
131
+ | Simple project, ready to plan | `/maestro-plan 1` |
132
+ | Need UI design first | `/maestro-impeccable build` |
133
+ | View project dashboard | `/manage-status` |
134
+ | Need formal spec documents | `/maestro-blueprint` |
135
+ </completion>
106
136
 
107
137
  <error_codes>
108
138
  | Code | Severity | Condition | Recovery |
@@ -39,7 +39,22 @@ Registers VRF artifact in state.json on completion.
39
39
  <context>
40
40
  $ARGUMENTS — phase number or no args for milestone-wide, with optional flags.
41
41
 
42
- Flags (`--skip-tests`, `--skip-antipattern`, `--dir`), scope routing, output paths, and VRF artifact registration schema are defined in workflow `verify.md`.
42
+ ### Flags
43
+
44
+ | Flag | Effect | Default |
45
+ |------|--------|---------|
46
+ | `--skip-tests` | Skip Nyquist test coverage validation (V2), only run Goal-Backward verification | false |
47
+ | `--skip-antipattern` | Skip anti-pattern scan step | false |
48
+ | `--dir <path>` | Verify a single plan directory instead of milestone-wide | — (milestone mode) |
49
+
50
+ **Scope routing:**
51
+ | Input | Scope | Resolution |
52
+ |-------|-------|------------|
53
+ | `--dir scratch/{dir}` | single plan | Verify one plan, write verification.json into plan dir |
54
+ | numeric arg | phase | Verify all execute artifacts for that phase |
55
+ | no args | milestone | Aggregate all execute artifacts for current milestone |
56
+
57
+ Output paths and VRF artifact registration schema are defined in workflow `verify.md`.
43
58
 
44
59
  ### Pre-load context (before verification)
45
60
 
@@ -65,28 +80,48 @@ Follow '~/.maestro/workflows/verify.md' completely.
65
80
 
66
81
  On confirm → `Skill("spec-add", "<category> <content>")`.
67
82
 
68
- **Next-step routing on completion:**
69
- - All checks pass, no gaps → /quality-review
70
- - Gaps found (must-have failures or anti-pattern blockers) → /maestro-plan --gaps
71
- - Low test coverage (Nyquist gaps) → /quality-auto-test
83
+ </execution>
72
84
 
73
- **Gap-fix closure loop:**
74
- Gaps found → maestro-plan --gaps → maestro-execute → maestro-verify (re-run)
85
+ <completion>
86
+ ### Standalone report
75
87
 
76
- **Completion status:**
77
88
  ```
78
- --- COMPLETION STATUS ---
89
+ === VERIFY COMPLETE ===
79
90
  STATUS: DONE|DONE_WITH_CONCERNS|NEEDS_RETRY
80
91
  CONCERNS: {description if applicable}
81
92
  NEXT: /quality-review
82
- --- END STATUS ---
93
+ === END VERIFY ===
83
94
  ```
84
95
 
85
96
  Status mapping:
86
97
  - **DONE** — All checks pass, no gaps → NEXT: /quality-review
87
98
  - **DONE_WITH_CONCERNS** — Gaps found (must-have failures or anti-pattern blockers) → NEXT: /maestro-execute (after /maestro-plan --gaps)
88
99
  - **NEEDS_RETRY** — Verification could not complete (missing artifacts, corrupt data)
89
- </execution>
100
+
101
+ ### Ralph-invoked completion
102
+
103
+ End the step by calling the CLI (no text block output):
104
+ ```
105
+ maestro ralph complete <idx> --status {STATUS} [--evidence {path}]
106
+ ```
107
+
108
+ Status verdicts:
109
+ - **DONE** — Normal completion
110
+ - **DONE_WITH_CONCERNS** — Completed with caveats; pass `--concerns`
111
+ - **NEEDS_RETRY** — Tooling error / transient issue; ralph will retry
112
+ - **BLOCKED** — External hard blocker; pass `--reason`
113
+
114
+ ### Next-step routing
115
+
116
+ | Condition | Suggestion |
117
+ |-----------|-----------|
118
+ | All checks pass, no gaps | `/quality-review` |
119
+ | Gaps found (must-have failures or anti-pattern blockers) | `/maestro-plan --gaps` |
120
+ | Low test coverage (Nyquist gaps) | `/quality-auto-test` |
121
+
122
+ **Gap-fix closure loop:**
123
+ Gaps found → maestro-plan --gaps → maestro-execute → maestro-verify (re-run)
124
+ </completion>
90
125
 
91
126
  <error_codes>
92
127
  | Code | Severity | Condition | Recovery |
@@ -89,7 +89,7 @@ def cmd_init(args: argparse.Namespace) -> None:
89
89
  paths = SessionPaths(Path(args.session))
90
90
  if not paths.config.exists():
91
91
  _fail(2, f"config not found: {paths.config}")
92
- config = json.loads(paths.config.read_text())
92
+ config = json.loads(paths.config.read_text(encoding="utf-8"))
93
93
 
94
94
  paths.ensure_dirs()
95
95
 
@@ -107,7 +107,7 @@ def cmd_init(args: argparse.Namespace) -> None:
107
107
  "start_nodes": config.get("task_space", {}).get("start_nodes", "any"),
108
108
  "edges": config.get("task_space", {}).get("edges", "complete"),
109
109
  }
110
- paths.task_space.write_text(json.dumps(task_space, indent=2, ensure_ascii=False))
110
+ paths.task_space.write_text(json.dumps(task_space, indent=2, ensure_ascii=False), encoding="utf-8")
111
111
 
112
112
  # Initialize pheromone
113
113
  aco_cfg = config.get("aco", {})
@@ -148,9 +148,9 @@ def _pick_start_node(nodes: List[str], state: PheromoneState, mode: str) -> str:
148
148
 
149
149
  def cmd_select(args: argparse.Namespace) -> None:
150
150
  paths = SessionPaths(Path(args.session))
151
- config = json.loads(paths.config.read_text())
151
+ config = json.loads(paths.config.read_text(encoding="utf-8"))
152
152
  state = PheromoneState.load(paths.pheromone_current)
153
- task_space = json.loads(paths.task_space.read_text())
153
+ task_space = json.loads(paths.task_space.read_text(encoding="utf-8"))
154
154
 
155
155
  n_ants = config.get("swarm", {}).get("n_ants", 5)
156
156
  nodes = task_space["nodes"]
@@ -190,7 +190,7 @@ def _load_iteration_artifacts(paths: SessionPaths, iteration: int) -> List[dict]
190
190
  artifacts = []
191
191
  for f in files:
192
192
  try:
193
- artifacts.append(json.loads(Path(f).read_text()))
193
+ artifacts.append(json.loads(Path(f).read_text(encoding="utf-8")))
194
194
  except json.JSONDecodeError as e:
195
195
  print(f"warning: skipped malformed artifact {f}: {e}", file=sys.stderr)
196
196
  return artifacts
@@ -213,9 +213,9 @@ def _validate_artifact(art: dict, valid_nodes: set) -> Optional[str]:
213
213
 
214
214
  def cmd_update(args: argparse.Namespace) -> None:
215
215
  paths = SessionPaths(Path(args.session))
216
- config = json.loads(paths.config.read_text())
216
+ config = json.loads(paths.config.read_text(encoding="utf-8"))
217
217
  state = PheromoneState.load(paths.pheromone_current)
218
- task_space = json.loads(paths.task_space.read_text())
218
+ task_space = json.loads(paths.task_space.read_text(encoding="utf-8"))
219
219
  valid_nodes = set(task_space["nodes"])
220
220
 
221
221
  artifacts = _load_iteration_artifacts(paths, args.iter)
@@ -266,7 +266,7 @@ def cmd_update(args: argparse.Namespace) -> None:
266
266
  # Elitist: re-load best history, deposit extra on best path
267
267
  best_data = None
268
268
  if paths.best.exists():
269
- best_data = json.loads(paths.best.read_text())
269
+ best_data = json.loads(paths.best.read_text(encoding="utf-8"))
270
270
  current_best = max(scored, key=lambda x: x["score"]) if scored else None
271
271
  if current_best:
272
272
  best_art = next(a for a in artifacts if a["ant_id"] == current_best["ant_id"])
@@ -281,7 +281,7 @@ def cmd_update(args: argparse.Namespace) -> None:
281
281
  "evidence": best_art.get("evidence", []),
282
282
  "updated_at": time.time(),
283
283
  }
284
- paths.best.write_text(json.dumps(best_data, indent=2, ensure_ascii=False))
284
+ paths.best.write_text(json.dumps(best_data, indent=2, ensure_ascii=False), encoding="utf-8")
285
285
  # Elite deposit
286
286
  state.deposit(best_data["path"], best_data["score"])
287
287
 
@@ -292,14 +292,14 @@ def cmd_update(args: argparse.Namespace) -> None:
292
292
 
293
293
  # Persist trails
294
294
  trails_file = paths.trails / f"{args.iter}.jsonl"
295
- trails_file.write_text("\n".join(json.dumps(t, ensure_ascii=False) for t in trail_log))
295
+ trails_file.write_text("\n".join(json.dumps(t, ensure_ascii=False) for t in trail_log), encoding="utf-8")
296
296
 
297
297
  mean_score = sum(s["score"] for s in scored) / len(scored) if scored else 0.0
298
298
  best_score = best_data["score"] if best_data else 0.0
299
299
  prev_best = 0.0
300
300
  history_files = sorted(paths.pheromone_history.glob("*.json"))
301
301
  if len(history_files) >= 2:
302
- prev = json.loads(history_files[-2].read_text())
302
+ prev = json.loads(history_files[-2].read_text(encoding="utf-8"))
303
303
  prev_best = prev.get("stats", {}).get("best_known", best_score)
304
304
  delta = best_score - prev_best
305
305
 
@@ -323,7 +323,7 @@ def cmd_update(args: argparse.Namespace) -> None:
323
323
 
324
324
  def cmd_converged(args: argparse.Namespace) -> None:
325
325
  paths = SessionPaths(Path(args.session))
326
- config = json.loads(paths.config.read_text())
326
+ config = json.loads(paths.config.read_text(encoding="utf-8"))
327
327
  cv = config.get("convergence", {})
328
328
 
329
329
  state = PheromoneState.load(paths.pheromone_current)
@@ -339,7 +339,7 @@ def cmd_converged(args: argparse.Namespace) -> None:
339
339
  }
340
340
 
341
341
  if paths.best.exists():
342
- metrics["best_score"] = json.loads(paths.best.read_text()).get("score", 0.0)
342
+ metrics["best_score"] = json.loads(paths.best.read_text(encoding="utf-8")).get("score", 0.0)
343
343
 
344
344
  # max_iterations
345
345
  max_iter = cv.get("max_iterations", 5)
@@ -397,7 +397,7 @@ def cmd_report(args: argparse.Namespace) -> None:
397
397
 
398
398
  best = None
399
399
  if paths.best.exists():
400
- best = json.loads(paths.best.read_text())
400
+ best = json.loads(paths.best.read_text(encoding="utf-8"))
401
401
 
402
402
  # Top-K trails across all iterations
403
403
  all_trails = []
@@ -410,7 +410,7 @@ def cmd_report(args: argparse.Namespace) -> None:
410
410
  # Convergence curve
411
411
  curve = []
412
412
  for hf in sorted(paths.pheromone_history.glob("*.json"), key=lambda p: int(p.stem)):
413
- snap = json.loads(hf.read_text())
413
+ snap = json.loads(hf.read_text(encoding="utf-8"))
414
414
  curve.append({
415
415
  "iteration": snap["iteration"],
416
416
  "entropy": snap["stats"]["entropy"],
@@ -114,11 +114,11 @@ class PheromoneState:
114
114
 
115
115
  def save(self, path: Path) -> None:
116
116
  path.parent.mkdir(parents=True, exist_ok=True)
117
- path.write_text(json.dumps(self.to_dict(), indent=2, ensure_ascii=False))
117
+ path.write_text(json.dumps(self.to_dict(), indent=2, ensure_ascii=False), encoding="utf-8")
118
118
 
119
119
  @classmethod
120
120
  def load(cls, path: Path) -> "PheromoneState":
121
- return cls.from_dict(json.loads(path.read_text()))
121
+ return cls.from_dict(json.loads(path.read_text(encoding="utf-8")))
122
122
 
123
123
  def select_neighbors(
124
124
  self,
@@ -62,7 +62,7 @@ def load_verified_scores(scores_file: Path) -> Dict[str, float]:
62
62
  """Load pre-computed verified_scores from scorer role output (if exists)."""
63
63
  if not scores_file.exists():
64
64
  return {}
65
- data = json.loads(scores_file.read_text())
65
+ data = json.loads(scores_file.read_text(encoding="utf-8"))
66
66
  return {
67
67
  ant_id: entry["verified_score"]
68
68
  for ant_id, entry in data.get("scores", {}).items()
@@ -89,7 +89,7 @@ def cmd_init(args: argparse.Namespace) -> None:
89
89
  paths = SessionPaths(Path(args.session))
90
90
  if not paths.config.exists():
91
91
  _fail(2, f"config not found: {paths.config}")
92
- config = json.loads(paths.config.read_text())
92
+ config = json.loads(paths.config.read_text(encoding="utf-8"))
93
93
 
94
94
  paths.ensure_dirs()
95
95
 
@@ -107,7 +107,7 @@ def cmd_init(args: argparse.Namespace) -> None:
107
107
  "start_nodes": config.get("task_space", {}).get("start_nodes", "any"),
108
108
  "edges": config.get("task_space", {}).get("edges", "complete"),
109
109
  }
110
- paths.task_space.write_text(json.dumps(task_space, indent=2, ensure_ascii=False))
110
+ paths.task_space.write_text(json.dumps(task_space, indent=2, ensure_ascii=False), encoding="utf-8")
111
111
 
112
112
  # Initialize pheromone
113
113
  aco_cfg = config.get("aco", {})
@@ -148,9 +148,9 @@ def _pick_start_node(nodes: List[str], state: PheromoneState, mode: str) -> str:
148
148
 
149
149
  def cmd_select(args: argparse.Namespace) -> None:
150
150
  paths = SessionPaths(Path(args.session))
151
- config = json.loads(paths.config.read_text())
151
+ config = json.loads(paths.config.read_text(encoding="utf-8"))
152
152
  state = PheromoneState.load(paths.pheromone_current)
153
- task_space = json.loads(paths.task_space.read_text())
153
+ task_space = json.loads(paths.task_space.read_text(encoding="utf-8"))
154
154
 
155
155
  n_ants = config.get("swarm", {}).get("n_ants", 5)
156
156
  nodes = task_space["nodes"]
@@ -190,7 +190,7 @@ def _load_iteration_artifacts(paths: SessionPaths, iteration: int) -> List[dict]
190
190
  artifacts = []
191
191
  for f in files:
192
192
  try:
193
- artifacts.append(json.loads(Path(f).read_text()))
193
+ artifacts.append(json.loads(Path(f).read_text(encoding="utf-8")))
194
194
  except json.JSONDecodeError as e:
195
195
  print(f"warning: skipped malformed artifact {f}: {e}", file=sys.stderr)
196
196
  return artifacts
@@ -213,9 +213,9 @@ def _validate_artifact(art: dict, valid_nodes: set) -> Optional[str]:
213
213
 
214
214
  def cmd_update(args: argparse.Namespace) -> None:
215
215
  paths = SessionPaths(Path(args.session))
216
- config = json.loads(paths.config.read_text())
216
+ config = json.loads(paths.config.read_text(encoding="utf-8"))
217
217
  state = PheromoneState.load(paths.pheromone_current)
218
- task_space = json.loads(paths.task_space.read_text())
218
+ task_space = json.loads(paths.task_space.read_text(encoding="utf-8"))
219
219
  valid_nodes = set(task_space["nodes"])
220
220
 
221
221
  artifacts = _load_iteration_artifacts(paths, args.iter)
@@ -266,7 +266,7 @@ def cmd_update(args: argparse.Namespace) -> None:
266
266
  # Elitist: re-load best history, deposit extra on best path
267
267
  best_data = None
268
268
  if paths.best.exists():
269
- best_data = json.loads(paths.best.read_text())
269
+ best_data = json.loads(paths.best.read_text(encoding="utf-8"))
270
270
  current_best = max(scored, key=lambda x: x["score"]) if scored else None
271
271
  if current_best:
272
272
  best_art = next(a for a in artifacts if a["ant_id"] == current_best["ant_id"])
@@ -281,7 +281,7 @@ def cmd_update(args: argparse.Namespace) -> None:
281
281
  "evidence": best_art.get("evidence", []),
282
282
  "updated_at": time.time(),
283
283
  }
284
- paths.best.write_text(json.dumps(best_data, indent=2, ensure_ascii=False))
284
+ paths.best.write_text(json.dumps(best_data, indent=2, ensure_ascii=False), encoding="utf-8")
285
285
  # Elite deposit
286
286
  state.deposit(best_data["path"], best_data["score"])
287
287
 
@@ -292,14 +292,14 @@ def cmd_update(args: argparse.Namespace) -> None:
292
292
 
293
293
  # Persist trails
294
294
  trails_file = paths.trails / f"{args.iter}.jsonl"
295
- trails_file.write_text("\n".join(json.dumps(t, ensure_ascii=False) for t in trail_log))
295
+ trails_file.write_text("\n".join(json.dumps(t, ensure_ascii=False) for t in trail_log), encoding="utf-8")
296
296
 
297
297
  mean_score = sum(s["score"] for s in scored) / len(scored) if scored else 0.0
298
298
  best_score = best_data["score"] if best_data else 0.0
299
299
  prev_best = 0.0
300
300
  history_files = sorted(paths.pheromone_history.glob("*.json"))
301
301
  if len(history_files) >= 2:
302
- prev = json.loads(history_files[-2].read_text())
302
+ prev = json.loads(history_files[-2].read_text(encoding="utf-8"))
303
303
  prev_best = prev.get("stats", {}).get("best_known", best_score)
304
304
  delta = best_score - prev_best
305
305
 
@@ -323,7 +323,7 @@ def cmd_update(args: argparse.Namespace) -> None:
323
323
 
324
324
  def cmd_converged(args: argparse.Namespace) -> None:
325
325
  paths = SessionPaths(Path(args.session))
326
- config = json.loads(paths.config.read_text())
326
+ config = json.loads(paths.config.read_text(encoding="utf-8"))
327
327
  cv = config.get("convergence", {})
328
328
 
329
329
  state = PheromoneState.load(paths.pheromone_current)
@@ -339,7 +339,7 @@ def cmd_converged(args: argparse.Namespace) -> None:
339
339
  }
340
340
 
341
341
  if paths.best.exists():
342
- metrics["best_score"] = json.loads(paths.best.read_text()).get("score", 0.0)
342
+ metrics["best_score"] = json.loads(paths.best.read_text(encoding="utf-8")).get("score", 0.0)
343
343
 
344
344
  # max_iterations
345
345
  max_iter = cv.get("max_iterations", 5)
@@ -397,7 +397,7 @@ def cmd_report(args: argparse.Namespace) -> None:
397
397
 
398
398
  best = None
399
399
  if paths.best.exists():
400
- best = json.loads(paths.best.read_text())
400
+ best = json.loads(paths.best.read_text(encoding="utf-8"))
401
401
 
402
402
  # Top-K trails across all iterations
403
403
  all_trails = []
@@ -410,7 +410,7 @@ def cmd_report(args: argparse.Namespace) -> None:
410
410
  # Convergence curve
411
411
  curve = []
412
412
  for hf in sorted(paths.pheromone_history.glob("*.json"), key=lambda p: int(p.stem)):
413
- snap = json.loads(hf.read_text())
413
+ snap = json.loads(hf.read_text(encoding="utf-8"))
414
414
  curve.append({
415
415
  "iteration": snap["iteration"],
416
416
  "entropy": snap["stats"]["entropy"],
@@ -114,11 +114,11 @@ class PheromoneState:
114
114
 
115
115
  def save(self, path: Path) -> None:
116
116
  path.parent.mkdir(parents=True, exist_ok=True)
117
- path.write_text(json.dumps(self.to_dict(), indent=2, ensure_ascii=False))
117
+ path.write_text(json.dumps(self.to_dict(), indent=2, ensure_ascii=False), encoding="utf-8")
118
118
 
119
119
  @classmethod
120
120
  def load(cls, path: Path) -> "PheromoneState":
121
- return cls.from_dict(json.loads(path.read_text()))
121
+ return cls.from_dict(json.loads(path.read_text(encoding="utf-8")))
122
122
 
123
123
  def select_neighbors(
124
124
  self,
@@ -62,7 +62,7 @@ def load_verified_scores(scores_file: Path) -> Dict[str, float]:
62
62
  """Load pre-computed verified_scores from scorer role output (if exists)."""
63
63
  if not scores_file.exists():
64
64
  return {}
65
- data = json.loads(scores_file.read_text())
65
+ data = json.loads(scores_file.read_text(encoding="utf-8"))
66
66
  return {
67
67
  ant_id: entry["verified_score"]
68
68
  for ant_id, entry in data.get("scores", {}).items()