devflow-kit 3.5.0 → 3.6.0

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 (107) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/README.md +4 -4
  3. package/dist/agents/{diagnose.md → debug.md} +4 -4
  4. package/dist/agents/{code-fix.md → fix.md} +2 -2
  5. package/dist/agents/{scrutinize.md → harden.md} +6 -6
  6. package/dist/agents/{code-edit.md → refactor.md} +3 -3
  7. package/dist/cli/agents-view/render.js +1 -1
  8. package/dist/cli/commands/init.js +3 -3
  9. package/dist/cli/commands/learning.js +12 -12
  10. package/dist/cli/commands/legacy-hooks.js +1 -1
  11. package/dist/cli/commands/uninstall.js +111 -5
  12. package/dist/commands/bug-analysis.md +19 -15
  13. package/dist/commands/code-review.md +13 -22
  14. package/dist/commands/debug.md +15 -15
  15. package/dist/commands/dynamic-build.md +51 -55
  16. package/dist/commands/dynamic-plan.md +11 -9
  17. package/dist/commands/dynamic-profile.md +7 -5
  18. package/dist/commands/dynamic-tickets.md +11 -9
  19. package/dist/commands/explore.md +13 -13
  20. package/dist/commands/implement.md +58 -127
  21. package/dist/commands/plan.md +19 -41
  22. package/dist/commands/release.md +9 -5
  23. package/dist/commands/research.md +12 -8
  24. package/dist/commands/resolve.md +42 -86
  25. package/dist/commands/self-review.md +42 -42
  26. package/dist/core/agent-models.js +47 -11
  27. package/dist/core/{learning-tuning-config.js → curate-tuning-config.js} +14 -14
  28. package/dist/core/learning-queue-cleanup.js +1 -1
  29. package/dist/core/migrations.js +63 -44
  30. package/dist/core/plugins.js +14 -12
  31. package/dist/core/project-paths.js +3 -3
  32. package/dist/hud/codex-usage.js +247 -0
  33. package/dist/hud/components/codex-quota.js +37 -0
  34. package/dist/hud/components/usage-quota.js +1 -1
  35. package/dist/hud/config.js +1 -0
  36. package/dist/hud/index.js +16 -3
  37. package/dist/hud/render.js +3 -1
  38. package/dist/learning-off/agents/{diagnose.md → debug.md} +4 -4
  39. package/dist/learning-off/agents/{code-fix.md → fix.md} +2 -2
  40. package/dist/learning-off/agents/{scrutinize.md → harden.md} +6 -6
  41. package/dist/learning-off/agents/{code-edit.md → refactor.md} +3 -3
  42. package/dist/learning-off/commands/bug-analysis.md +9 -9
  43. package/dist/learning-off/commands/code-review.md +2 -15
  44. package/dist/learning-off/commands/debug.md +4 -8
  45. package/dist/learning-off/commands/dynamic-build.md +45 -51
  46. package/dist/learning-off/commands/dynamic-plan.md +5 -5
  47. package/dist/learning-off/commands/dynamic-profile.md +1 -1
  48. package/dist/learning-off/commands/dynamic-tickets.md +5 -5
  49. package/dist/learning-off/commands/explore.md +2 -6
  50. package/dist/learning-off/commands/implement.md +46 -119
  51. package/dist/learning-off/commands/plan.md +8 -33
  52. package/dist/learning-off/commands/release.md +4 -2
  53. package/dist/learning-off/commands/research.md +2 -2
  54. package/dist/learning-off/commands/resolve.md +31 -79
  55. package/dist/learning-off/commands/self-review.md +30 -34
  56. package/dist/skills/git/references/pr/check-merge-readiness.md +2 -0
  57. package/dist/skills/git/references/pr/update-pr-evidence.md +4 -6
  58. package/dist/targets/claude-code/installer.js +117 -84
  59. package/dist/targets/claude-code/tracker-servers.js +1 -1
  60. package/package.json +1 -1
  61. package/src/assets/agents/_partials/_code.mds +2 -2
  62. package/src/assets/agents/code.mds +2 -2
  63. package/src/assets/agents/{learning.md → curate.md} +4 -4
  64. package/src/assets/agents/{diagnose.mds → debug.mds} +4 -4
  65. package/src/assets/agents/evaluate.md +4 -4
  66. package/src/assets/agents/{code-fix.mds → fix.mds} +5 -5
  67. package/src/assets/agents/{scrutinize.mds → harden.mds} +6 -6
  68. package/src/assets/agents/{simplify.md → polish.md} +4 -4
  69. package/src/assets/agents/{code-edit.mds → refactor.mds} +6 -6
  70. package/src/assets/agents/test.md +2 -2
  71. package/src/assets/commands/_partials/_compliance.mds +1 -1
  72. package/src/assets/commands/_partials/_decisions.mds +26 -6
  73. package/src/assets/commands/_partials/_engine.mds +15 -15
  74. package/src/assets/commands/_partials/_evidence_policy.mds +0 -17
  75. package/src/assets/commands/_partials/_knowledge.mds +14 -9
  76. package/src/assets/commands/_partials/_preamble.mds +4 -2
  77. package/src/assets/commands/_partials/_roster.mds +4 -4
  78. package/src/assets/commands/bug-analysis.mds +7 -7
  79. package/src/assets/commands/code-review.mds +1 -14
  80. package/src/assets/commands/debug.mds +2 -2
  81. package/src/assets/commands/dynamic-build.mds +26 -32
  82. package/src/assets/commands/implement.mds +45 -102
  83. package/src/assets/commands/plan.mds +7 -37
  84. package/src/assets/commands/release.mds +5 -1
  85. package/src/assets/commands/resolve.mds +28 -76
  86. package/src/assets/commands/self-review.mds +27 -27
  87. package/src/assets/mds/git/_pr.mds +6 -6
  88. package/src/assets/scripts/hooks/assets/orchestrator-charter.md +4 -3
  89. package/src/assets/scripts/hooks/capture-prompt +1 -1
  90. package/src/assets/scripts/hooks/capture-question +1 -1
  91. package/src/assets/scripts/hooks/capture-turn +1 -1
  92. package/src/assets/scripts/hooks/json-helper.cjs +2 -2
  93. package/src/assets/scripts/hooks/lib/learning-store.cjs +1 -1
  94. package/src/assets/scripts/hooks/lib/project-paths.cjs +4 -4
  95. package/src/assets/scripts/hooks/preamble +1 -1
  96. package/src/assets/scripts/hooks/session-start-context +58 -45
  97. package/src/assets/scripts/verify-evidence.cjs +726 -9
  98. package/src/assets/skills/agent-references/SKILL.md +9 -5
  99. package/src/assets/skills/agent-references/references/commands/implement-evidence-asks.md +35 -0
  100. package/src/assets/skills/agent-references/references/commands/implement-revalidate.md +15 -0
  101. package/src/assets/skills/agent-references/references/synthesize/bug-analysis.md +1 -1
  102. package/src/assets/skills/apply-decisions/SKILL.md +6 -4
  103. package/src/assets/skills/docs-framework/SKILL.md +3 -3
  104. package/src/assets/skills/qa/SKILL.md +1 -1
  105. package/src/assets/skills/quality-gates/SKILL.md +7 -7
  106. package/src/assets/skills/quality-gates/references/report-template.md +7 -7
  107. /package/src/assets/skills/agent-references/references/{learning → curate}/maintain.md +0 -0
@@ -1,9 +1,9 @@
1
1
  ---
2
- description: Self-review workflow - Simplify agent (code clarity) then Scrutinize agent (9-pillar quality gate)
2
+ description: Self-review workflow - Polish agent (code clarity) then Harden agent (9-pillar quality gate)
3
3
  ---
4
4
  # Self-Review Command
5
5
 
6
- Run Simplify agent and Scrutinize agent sequentially on changed files for post-implementation quality refinement.
6
+ Run Polish agent and Harden agent sequentially on changed files for post-implementation quality refinement.
7
7
 
8
8
  ## Usage
9
9
 
@@ -22,7 +22,7 @@ Detect changed files and build context:
22
22
  2. Else run `git diff --name-only HEAD` + `git diff --name-only --cached` to get staged + unstaged
23
23
  3. If no changes found, report "No changes to review" and exit
24
24
  4. Build TASK_DESCRIPTION from recent commit messages or branch name
25
- 5. Record `HEAD_BEFORE` (`git rev-parse HEAD`) now, before Simplify runs — Phase 3 compares against it
25
+ 5. Record `HEAD_BEFORE` (`git rev-parse HEAD`) now, before Polish runs — Phase 3 compares against it
26
26
 
27
27
  **Resolve the settings line** once per worktree root, reusing a line this run already resolved for the same root. `{root}` is the worktree the values are for — the repository root when the run has one worktree:
28
28
 
@@ -38,19 +38,19 @@ The accepted line is the only source of these values: the script alone folds the
38
38
 
39
39
  When the settings line says `LEARNING=off`, set `DECISIONS_CONTEXT` to `(none)` and skip this step, locating no ledger and reading no index.
40
40
 
41
- The decisions ledger belongs to the repository, not to one checkout: in a linked worktree it lives in the main worktree, and a session started in a subdirectory reads the copy at the repository root. Locate it with ONE git call, run from the start directory — `WORKTREE_PATH` if provided, otherwise cwd (`devflow:worktree-support`):
41
+ The decisions ledger belongs to the repository, not to one checkout: a linked worktree reads the main worktree's copy, a subdirectory session the repository root's. Locate it with ONE git call, run from the start directory — `WORKTREE_PATH` if provided, otherwise cwd (`devflow:worktree-support`):
42
42
 
43
43
  ```bash
44
44
  git -C "{start}" rev-parse --path-format=absolute --show-toplevel --git-common-dir
45
45
  ```
46
46
 
47
- Line 1 is the checkout's toplevel, line 2 the repository's common git directory. A git older than 2.31 echoes `--path-format=absolute` back as a line of its own first. `{ledger}` is the first of these that applies:
47
+ Line 1 is the checkout's toplevel, line 2 the common git directory. A git older than 2.31 echoes `--path-format=absolute` back as a line of its own first. `{ledger}` is the first that applies:
48
48
 
49
49
  1. **The main worktree** — line 2 without its trailing `/.git`, when the output is exactly two lines each beginning with `/`, line 2 ends in `/.git`, and the directory left once it is removed is not your home directory and contains a `.devflow/` directory.
50
50
  2. **The toplevel** — line 1, or on an older git the line after the echoed flag.
51
51
  3. **The start directory itself** — when the command failed or printed no absolute toplevel (outside a git repository).
52
52
 
53
- This is the rule the learning hooks apply (D-LEDGER-MAIN-WORKTREE, D-PROMPT-ROOT), so you read the index the Learning agent writes.
53
+ This is the rule the learning hooks apply, so you read the index the Curate agent writes.
54
54
 
55
55
  **Step 1 — Read the pre-rendered index:**
56
56
 
@@ -59,13 +59,17 @@ Attempt to read `{ledger}/.devflow/learning/index.md`.
59
59
  - If the file exists and contains non-empty content: use that content as `DECISIONS_CONTEXT`.
60
60
  - If the file is absent or empty: set `DECISIONS_CONTEXT` to `(none)`.
61
61
 
62
- The index is one direct file read, written at render time by `render-decisions.cjs` alongside `decisions.md`/`pitfalls.md` — no `.cjs` script runs here, and the index's own footer names the files that hold each entry's full body.
62
+ The index is one direct file read, written at render time by `render-decisions.cjs` — no `.cjs` script runs here.
63
63
 
64
- **Step 2 — Agents apply the decisions:**
64
+ **Step 2 — Slice the index, then hand it on:**
65
65
 
66
- When `DECISIONS_CONTEXT` is not `(none)`, hand it only to the agents whose contract declares it. They follow `devflow:apply-decisions`: scan the index, identify plausibly-relevant entries, Read full entry bodies on demand, and cite verbatim IDs in their reasoning. A host that uses the index in its own thread says so in its own step.
66
+ When `DECISIONS_CONTEXT` is not `(none)`, hand it only to the agents whose contract declares it.
67
67
 
68
- Pass `DECISIONS_CONTEXT` to Scrutinize agent — the compact index lists active ADR/PF entries; Scrutinize agent uses `devflow:apply-decisions` to Read full entry bodies on demand. Known pitfalls help identify reintroduced issues, prior decisions help validate architectural consistency. (Simplify agent does not consume decisions — it operates at code-shape level and Scrutinize agent runs after to catch any architectural drift.)
68
+ Hand each agent that takes `DECISIONS_CONTEXT` a slice, not the whole index: the index lines whose title, scope or area touch the files and task it works on, verbatim, led by `(slice: N of M entries — full index: <absolute index path>)` and closed by the index footer. When unsure, include the line. Siblings over one diff share ONE slice.
69
+
70
+ Those agents follow `devflow:apply-decisions`. A host that uses the index in its own thread says so in its own step.
71
+
72
+ Pass `DECISIONS_CONTEXT` to Harden agent — the compact index lists active ADR/PF entries; Harden agent uses `devflow:apply-decisions` to Read full entry bodies on demand. Known pitfalls help identify reintroduced issues, prior decisions help validate architectural consistency. (Polish agent does not consume decisions — it operates at code-shape level and Harden agent runs after to catch any architectural drift.)
69
73
 
70
74
  ### Load Feature Knowledge
71
75
 
@@ -89,7 +93,7 @@ If `index.md` exists and contains at least one entry line, use it for relevance
89
93
 
90
94
  **Step 2 — Fallback: glob frontmatter (if `index.md` is absent or empty):**
91
95
 
92
- Glob `{worktree}/.devflow/features/*/KNOWLEDGE.md`. For each file found, read only its YAML frontmatter block (between the opening and closing `---` delimiters). The frontmatter fields `name`, `description`, and `directories` are the authoritative relevance surface — `index.md` is only a cache.
96
+ Glob `{worktree}/.devflow/features/*/KNOWLEDGE.md`. For each file found, read only its YAML frontmatter block (between the opening and closing `---` delimiters).
93
97
 
94
98
  **Step 3 — Pick relevant KBs:**
95
99
 
@@ -120,35 +124,35 @@ Headings: L5 Rules · L40 Overview · L62 Anti-Patterns · L118 Key Files
120
124
 
121
125
  A KB with no Rules section labels its entries `Rules ({section name}):` and gives them no ID. `FEATURE_KNOWLEDGE` is these blocks; `FEATURE_KNOWLEDGE_RULES` is the same blocks without the `Headings:` line. Both come from this one selection, and each spawn names the variable its recipient takes. If no KBs exist, no KBs are relevant, or `.devflow/features/` is absent, set both to `(none)`.
122
126
 
123
- **One git call, then direct reads — no `.cjs` script.** After resolving `{worktree}`, this step is 1 index read (or N frontmatter reads on fallback), plus one heading listing and one Rules read per selected KB, bounded by KB count.
127
+ **One git call, then direct reads — no `.cjs` script.**
124
128
 
125
- Pass `FEATURE_KNOWLEDGE_RULES` to Scrutinize agent.
129
+ Pass `FEATURE_KNOWLEDGE_RULES` to Harden agent.
126
130
 
127
131
  **Extract:** FILES_CHANGED (list), TASK_DESCRIPTION (string), DECISIONS_CONTEXT (string, optional), FEATURE_KNOWLEDGE (string, optional), FEATURE_KNOWLEDGE_RULES (string, optional), HEAD_BEFORE (40-hex SHA)
128
132
 
129
- ### Phase 1: Simplify agent (Code Refinement)
133
+ ### Phase 1: Polish agent (Code Refinement)
130
134
 
131
- **Produces:** SIMPLIFY_COMMITS
135
+ **Produces:** POLISH_COMMITS
132
136
  **Requires:** FILES_CHANGED, TASK_DESCRIPTION
133
137
 
134
- Spawn Simplify agent to refine code for clarity and consistency:
138
+ Spawn Polish agent to refine code for clarity and consistency:
135
139
 
136
- Agent(subagent_type="Simplify", run_in_background=false):
140
+ Agent(subagent_type="Polish", run_in_background=false):
137
141
  "TASK_DESCRIPTION: {task_description}
138
142
  FILES_CHANGED: {files_changed}
139
- Simplify and refine the code for clarity and consistency while preserving functionality.
143
+ Polish and refine the code for clarity and consistency while preserving functionality.
140
144
  Commit any improvements with a conventional-commit message."
141
145
 
142
- **Wait for completion.** Simplify agent commits changes directly.
146
+ **Wait for completion.** Polish agent commits changes directly.
143
147
 
144
- ### Phase 2: Scrutinize agent (9-Pillar Quality Gate)
148
+ ### Phase 2: Harden agent (9-Pillar Quality Gate)
145
149
 
146
- **Produces:** SCRUTINIZE_STATUS
150
+ **Produces:** HARDEN_STATUS
147
151
  **Requires:** FILES_CHANGED, TASK_DESCRIPTION, DECISIONS_CONTEXT
148
152
 
149
- Spawn Scrutinize agent for quality evaluation and fixing:
153
+ Spawn Harden agent for quality evaluation and fixing:
150
154
 
151
- Agent(subagent_type="Scrutinize", run_in_background=false):
155
+ Agent(subagent_type="Harden", run_in_background=false):
152
156
  "TASK_DESCRIPTION: {task_description}
153
157
  FILES_CHANGED: {files_changed}
154
158
  DECISIONS_CONTEXT: {decisions_context}
@@ -162,11 +166,11 @@ Follow devflow:apply-feature-knowledge for FEATURE_KNOWLEDGE. Skip if (none)."
162
166
  ### Phase 3: Conditional Validation
163
167
 
164
168
  **Produces:** VALIDATION_RESULT
165
- **Requires:** HEAD_BEFORE, SCRUTINIZE_STATUS
169
+ **Requires:** HEAD_BEFORE, HARDEN_STATUS
166
170
 
167
171
  If STATUS is BLOCKED, skip this phase: the report (Phase 4) names the blocker.
168
172
 
169
- Run this phase only if HEAD moved — `git rev-parse HEAD` differs from `HEAD_BEFORE`, meaning Simplify or Scrutinize committed (Simplify alone counts, whatever Scrutinize's STATUS is). Otherwise skip it. Its scope is changed-only over what the two agents changed:
173
+ Run this phase only if HEAD moved — `git rev-parse HEAD` differs from `HEAD_BEFORE`, meaning Polish or Harden committed (Polish alone counts, whatever Harden's STATUS is). Otherwise skip it. Its scope is changed-only over what the two agents changed:
170
174
 
171
175
  Agent(subagent_type="Validate", run_in_background=false):
172
176
  "FILES_CHANGED: {output of `git diff --name-only HEAD_BEFORE..HEAD`}
@@ -178,7 +182,7 @@ Run build, typecheck, lint, test on modified files"
178
182
 
179
183
  ### Phase 4: Report
180
184
 
181
- **Requires:** SCRUTINIZE_STATUS, VALIDATION_RESULT
185
+ **Requires:** HARDEN_STATUS, VALIDATION_RESULT
182
186
 
183
187
  Display summary:
184
188
 
@@ -187,10 +191,10 @@ Display summary:
187
191
  **Files Reviewed**: {n}
188
192
  **Status**: {PASS|FIXED|BLOCKED}
189
193
 
190
- ### Simplify agent
194
+ ### Polish agent
191
195
  - {n} files refined for clarity
192
196
 
193
- ### Scrutinize agent (9-Pillar Evaluation)
197
+ ### Harden agent (9-Pillar Evaluation)
194
198
  | Pillar | Status |
195
199
  |--------|--------|
196
200
  | Design | {status} |
@@ -240,21 +244,17 @@ FEATURE_SLUG: {slug derived from primary changed directory, kebab-case}
240
244
  FEATURE_NAME: {human-readable name}
241
245
  DIRECTORIES: {list of primary directories touched by this workflow}
242
246
  FILES_CHANGED: {list of files changed}
243
- DECISIONS_CONTEXT: {DECISIONS_CONTEXT if available, else (none)}
247
+ DECISIONS_CONTEXT: {the slice for these directories, else (none)}
244
248
 
245
249
  Write the knowledge base to:
246
250
  {worktree}/.devflow/features/{slug}/KNOWLEDGE.md
247
251
 
248
- Then update the index cache by performing a read-modify-write on:
252
+ Then update the index cache:
249
253
  {worktree}/.devflow/features/index.md
250
254
 
251
255
  Index line format: `- **{slug}** — {areas} — {Use-when description}` — at most 300 characters, the description at most 220; reword a longer one, never cut it.
252
256
 
253
- If the line for this slug already exists in index.md, replace it. If it does not exist, append it. If index.md does not exist, create it with just this line.
254
-
255
- The frontmatter in KNOWLEDGE.md is the source of truth — index.md is only a cache. Write the two files directly — no intermediate result JSON files, no external scripts.
256
-
257
- After writing, commit the two files to the current worktree branch yourself by running git via your Bash tool (do not use a script). Stage ONLY .devflow/features/index.md and .devflow/features/{slug}/KNOWLEDGE.md, then commit just those paths with a docs(knowledge): message. Do NOT push, do NOT force, do NOT stage anything else. Follow your Commit Protocol — it is non-blocking, so if any git step fails, report KB_COMMIT and finish normally."
257
+ Then commit the two files as your Commit Protocol directs."
258
258
  ```
259
259
 
260
260
  **Step 4 — Surface an uncommitted knowledge base:**
@@ -272,14 +272,14 @@ When the Knowledge agent reports `KB_COMMIT: skipped (detached HEAD)`, the files
272
272
  │ ├─ Record HEAD_BEFORE
273
273
  │ └─ Read index.md → DECISIONS_CONTEXT
274
274
  │
275
- ├─ Phase 1: Simplify agent
275
+ ├─ Phase 1: Polish agent
276
276
  │ └─ Code refinement (commits directly)
277
277
  │
278
- ├─ Phase 2: Scrutinize agent
278
+ ├─ Phase 2: Harden agent
279
279
  │ └─ 9-pillar quality gate (may fix and commit)
280
280
  │
281
281
  ├─ Phase 3: Validate agent (conditional)
282
- │ └─ If HEAD moved since HEAD_BEFORE (Simplify or Scrutinize committed), changed-only Validate over HEAD_BEFORE..HEAD
282
+ │ └─ If HEAD moved since HEAD_BEFORE (Polish or Harden committed), changed-only Validate over HEAD_BEFORE..HEAD
283
283
  │
284
284
  └─ Phase 4: Report
285
285
  └─ Display summary with pillar status
@@ -289,15 +289,15 @@ When the Knowledge agent reports `KB_COMMIT: skipped (detached HEAD)`, the files
289
289
  | Case | Handling |
290
290
  |------|----------|
291
291
  | No changes | Report "No changes to review" and exit |
292
- | Simplify agent finds nothing | Normal, continue to Scrutinize agent |
293
- | Scrutinize agent BLOCKED | Report blocking issue, halt workflow |
292
+ | Polish agent finds nothing | Normal, continue to Harden agent |
293
+ | Harden agent BLOCKED | Report blocking issue, halt workflow |
294
294
  | Validation fails | Report failures, halt (don't create broken state) |
295
295
  | Not in git repo | Report error, suggest running in git repo |
296
296
 
297
297
  ## Principles
298
298
 
299
299
  1. **Orchestration only** - Command spawns agents, doesn't do the work
300
- 2. **Sequential execution** - Simplify agent must complete before Scrutinize agent
301
- 3. **Validation gate** - If Simplify agent or Scrutinize agent committed, the changes must pass validation
300
+ 2. **Sequential execution** - Polish agent must complete before Harden agent
301
+ 3. **Validation gate** - If Polish agent or Harden agent committed, the changes must pass validation
302
302
  4. **Honest reporting** - Display actual agent outputs
303
303
  5. **Fail fast** - Stop on BLOCKED or validation failure
@@ -142,38 +142,56 @@ export function validateWorkerValue(field, value) {
142
142
  // Key migration
143
143
  // ---------------------------------------------------------------------------
144
144
  /**
145
- * Maps old agent keys (form A slugs) to their canonical replacement keys.
145
+ * Maps every retired agent key to the live slug that replaces it. Two renames feed it:
146
146
  *
147
- * 13 agents renamed from noun form (coder, reviewer, …) to action-verb form
148
- * (code, review, …). The canonicalise-agent-keys-v1 migration in migrations.ts
149
- * calls canonicaliseAgentKeys() to rewrite ~/.devflow/agent-models.json on the
150
- * user's first init after upgrading.
147
+ * - The 2.0.0 action-verb rename (coder, reviewer, …) and the 1.x names that rename
148
+ * retired (scrutinizer, simplifier, bug-analyzer).
149
+ * - The rename of six agents to the names that state their job (code-fix, code-edit,
150
+ * scrutinize, simplify, diagnose, learning).
151
+ *
152
+ * SINGLE HOP: every value is a live slug and no value is itself a key. A 1.x name
153
+ * therefore points straight at its final slug (scrutinizer → harden, never
154
+ * scrutinizer → scrutinize → harden), so one pass of canonicaliseAgentKeys() lands
155
+ * every key. The two canonicalise-agent-keys migrations in migrations.ts call it to
156
+ * rewrite ~/.devflow/agent-models.json; readAgentMapping() calls it on every read.
151
157
  *
152
158
  * PROTOTYPE SAFETY: Object.create(null) prevents prototype-pollution.
153
159
  * Every lookup MUST use Object.hasOwn — never key in LEGACY_AGENT_KEYS or
154
160
  * direct bracket access. `--set constructor` would otherwise return a function
155
161
  * that flows into path.join (the object-literal lookup on argv trap).
156
162
  *
163
+ * ORDER: entries are declared oldest first. canonicaliseAgentKeys() reads that order
164
+ * when two keys meet at one slug (scrutinizer and scrutinize both mean harden): the
165
+ * entry declared later is the more recent name and wins.
166
+ *
157
167
  * DORMANCY: entries here are stored whether or not the key rename has been
158
168
  * applied to the file yet. canonicaliseAgentKeys() is idempotent, so
159
169
  * double-execution from concurrent sessions is a harmless no-op — the second
160
170
  * run finds the file already migrated and makes zero writes.
161
171
  */
162
172
  export const LEGACY_AGENT_KEYS = Object.assign(Object.create(null), {
163
- // Phase 4 — agent-action-verbs rename (old slug → new slug)
173
+ // 1.x names (noun form), retargeted to the live slug of their final rename.
174
+ 'scrutinizer': 'harden',
175
+ 'simplifier': 'polish',
176
+ 'bug-analyzer': 'debug',
177
+ // 2.0.0 action-verb rename (old slug → new slug).
164
178
  'coder': 'code',
165
179
  'designer': 'design',
166
180
  'evaluator': 'evaluate',
167
181
  'researcher': 'research',
168
182
  'reviewer': 'review',
169
- 'scrutinizer': 'scrutinize',
170
- 'simplifier': 'simplify',
171
183
  'skimmer': 'skim',
172
184
  'synthesizer': 'synthesize',
173
185
  'tester': 'test',
174
186
  'triager': 'triage',
175
187
  'validator': 'validate',
176
- 'bug-analyzer': 'diagnose',
188
+ // Rename of six agents to the names that state their job.
189
+ 'code-fix': 'fix',
190
+ 'code-edit': 'refactor',
191
+ 'scrutinize': 'harden',
192
+ 'simplify': 'polish',
193
+ 'diagnose': 'debug',
194
+ 'learning': 'curate',
177
195
  });
178
196
  /**
179
197
  * Canonicalise agent keys in a raw agents map by applying LEGACY_AGENT_KEYS.
@@ -184,6 +202,10 @@ export const LEGACY_AGENT_KEYS = Object.assign(Object.create(null), {
184
202
  *
185
203
  * Collision detection is against the ORIGINAL map so results are order-independent.
186
204
  *
205
+ * Two legacy keys that name one agent (scrutinizer and scrutinize both mean harden) meet
206
+ * at one slug. The key declared later in LEGACY_AGENT_KEYS, the more recent name, wins;
207
+ * the other is dropped. The pick reads the declaration order, never the file order.
208
+ *
187
209
  * The function is pure and idempotent: calling it twice on the same input
188
210
  * produces the same result as calling it once (idempotent under concurrent execution).
189
211
  *
@@ -208,6 +230,19 @@ export function canonicaliseAgentKeys(rawAgents, onWarning) {
208
230
  }
209
231
  // Snapshot the original keys for collision detection (order-independent result).
210
232
  const originalKeys = new Set(Object.keys(rawAgents));
233
+ // When several legacy keys in the file name one agent, the one declared latest in
234
+ // LEGACY_AGENT_KEYS is the one that is renamed; the others are dropped below.
235
+ const declared = Object.keys(LEGACY_AGENT_KEYS);
236
+ const winnerByTarget = new Map();
237
+ for (const oldKey of originalKeys) {
238
+ if (oldKey === '__proto__' || !Object.hasOwn(LEGACY_AGENT_KEYS, oldKey))
239
+ continue;
240
+ const target = LEGACY_AGENT_KEYS[oldKey];
241
+ const held = winnerByTarget.get(target);
242
+ if (held === undefined || declared.indexOf(oldKey) > declared.indexOf(held)) {
243
+ winnerByTarget.set(target, oldKey);
244
+ }
245
+ }
211
246
  const result = { ...rawAgents };
212
247
  let didMutate = false;
213
248
  for (const oldKey of originalKeys) {
@@ -237,8 +272,9 @@ export function canonicaliseAgentKeys(rawAgents, onWarning) {
237
272
  didMutate = true;
238
273
  continue;
239
274
  }
240
- if (originalKeys.has(newKey)) {
241
- // Collision: new (canonical) key already present → new wins, old dropped.
275
+ if (originalKeys.has(newKey) || winnerByTarget.get(newKey) !== oldKey) {
276
+ // Collision: new (canonical) key already present, or a more recent legacy key
277
+ // names the same agent → that value wins, old dropped.
242
278
  // Warning is surfaced by the caller's structured dropped-block (e.g. migrations.ts)
243
279
  // to avoid printing the same collision twice in two different vocabularies (F8).
244
280
  dropped.push(oldKey);
@@ -1,17 +1,17 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { getDevFlowDirectory } from '../targets/claude-code/claude-paths.js';
4
- import { getLearningTuningConfigPath } from './project-paths.js';
4
+ import { getCurateTuningConfigPath } from './project-paths.js';
5
5
  const VALID_MODELS = new Set(['opus', 'sonnet', 'haiku']);
6
6
  function isValidModel(value) {
7
7
  return typeof value === 'string' && VALID_MODELS.has(value);
8
8
  }
9
9
  /**
10
- * D-LEARNING-SHIPPED-ROW: the default model is the model the Learning agent ships
11
- * with, the `model:` line of src/assets/agents/learning.md. With no layer
10
+ * D-LEARNING-SHIPPED-ROW: the default model is the model the Curate agent ships
11
+ * with, the `model:` line of src/assets/agents/curate.md. With no layer
12
12
  * configuring a model, the spawn directive names none and that line decides, so a
13
13
  * different default here would describe a run that never happens.
14
- * tests/decisions/config.test.ts holds this value equal to the Learning row of the
14
+ * tests/decisions/config.test.ts holds this value equal to the Curate row of the
15
15
  * agent-config table, which a change of the shipped tier edits with the frontmatter.
16
16
  */
17
17
  const DEFAULTS = {
@@ -19,12 +19,12 @@ const DEFAULTS = {
19
19
  debug: false,
20
20
  };
21
21
  /**
22
- * Apply a single JSON config layer onto a LearningTuningConfig, returning a new object.
22
+ * Apply a single JSON config layer onto a CurateTuningConfig, returning a new object.
23
23
  * Skips fields with wrong types. Swallows parse errors — callers see defaults.
24
24
  * Unknown fields (e.g. an old config still on disk with extra knobs) are
25
25
  * silently ignored, not an error.
26
26
  */
27
- export function applyLearningTuningConfigLayer(config, json) {
27
+ export function applyCurateTuningConfigLayer(config, json) {
28
28
  try {
29
29
  const parsed = JSON.parse(json);
30
30
  // Object-shape guard mirrors coerceConfig in feature-config.ts: a JSON
@@ -34,7 +34,7 @@ export function applyLearningTuningConfigLayer(config, json) {
34
34
  }
35
35
  const raw = parsed;
36
36
  return {
37
- // Only accept a model value that belongs to the closed LearningModelAlias domain.
37
+ // Only accept a model value that belongs to the closed CurateModelAlias domain.
38
38
  model: isValidModel(raw.model) ? raw.model : config.model,
39
39
  debug: typeof raw.debug === 'boolean' ? raw.debug : config.debug,
40
40
  };
@@ -54,13 +54,13 @@ function readConfigFile(filePath) {
54
54
  catch (err) {
55
55
  if (err.code !== 'ENOENT') {
56
56
  // Warn but don't crash — callers fall back to defaults.
57
- console.warn(`[learning-tuning-config] warning: could not read ${filePath}: ${err.message}`);
57
+ console.warn(`[curate-tuning-config] warning: could not read ${filePath}: ${err.message}`);
58
58
  }
59
59
  return null;
60
60
  }
61
61
  }
62
62
  /**
63
- * Load and merge learning agent tuning configuration.
63
+ * Load and merge curate agent tuning configuration.
64
64
  *
65
65
  * Priority (highest wins): project config → global config → defaults.
66
66
  *
@@ -71,18 +71,18 @@ function readConfigFile(filePath) {
71
71
  *
72
72
  * Invalid JSON in either file is silently ignored and treated as absent.
73
73
  */
74
- export function loadLearningTuningConfig(ledgerRoot) {
74
+ export function loadCurateTuningConfig(ledgerRoot) {
75
75
  const globalConfigPath = path.join(getDevFlowDirectory(), 'learning.json');
76
- const projectConfigPath = getLearningTuningConfigPath(ledgerRoot);
76
+ const projectConfigPath = getCurateTuningConfigPath(ledgerRoot);
77
77
  let config = { ...DEFAULTS };
78
78
  const globalJson = readConfigFile(globalConfigPath);
79
79
  if (globalJson !== null) {
80
- config = applyLearningTuningConfigLayer(config, globalJson);
80
+ config = applyCurateTuningConfigLayer(config, globalJson);
81
81
  }
82
82
  const projectJson = readConfigFile(projectConfigPath);
83
83
  if (projectJson !== null) {
84
- config = applyLearningTuningConfigLayer(config, projectJson);
84
+ config = applyCurateTuningConfigLayer(config, projectJson);
85
85
  }
86
86
  return config;
87
87
  }
88
- //# sourceMappingURL=learning-tuning-config.js.map
88
+ //# sourceMappingURL=curate-tuning-config.js.map
@@ -11,7 +11,7 @@ import { drainQueueFiles } from './queue-drain.js';
11
11
  /**
12
12
  * Drain the learning (decisions-detection) pending-turns queue, its claimed
13
13
  * batch and the claim's owner file so stale turns don't process later — used by
14
- * both `--clear` and `--disable`. A mid-run Learning agent whose claimed batch
14
+ * both `--clear` and `--disable`. A mid-run Curate agent whose claimed batch
15
15
  * vanishes aborts without changes, which is the desired outcome in both cases.
16
16
  * Refused, deleting nothing, when `.devflow` or `.devflow/learning` under `gitRoot`
17
17
  * is a symbolic link (D-CLI-NO-SYMLINK). ENOENT-tolerant; other errors propagate.
@@ -14,6 +14,55 @@ import { writeFileAtomicExclusive } from './fs-atomic.js';
14
14
  import { getMemoryDir } from './project-paths.js';
15
15
  import { LEGACY_AGENT_KEYS, canonicaliseAgentKeys, parseAgentMappingEnvelope } from './agent-models.js';
16
16
  import { migrateLegacyTrackerConventions } from './tracker.js';
17
+ /**
18
+ * The body of every canonicalise-agent-keys-vN migration: read agent-models.json raw,
19
+ * apply LEGACY_AGENT_KEYS once, and write the result back atomically when a key changed.
20
+ * Each migration id passes its own id so that its warnings name it. Idempotent, and a
21
+ * file with no legacy key is left untouched.
22
+ */
23
+ function canonicaliseAgentKeysRun(migrationId) {
24
+ return async (ctx) => {
25
+ const infos = [];
26
+ const warnings = [];
27
+ // Fast path: no legacy keys defined — skip without touching the file.
28
+ if (Object.keys(LEGACY_AGENT_KEYS).length === 0)
29
+ return { infos, warnings };
30
+ const filePath = path.join(ctx.devflowDir, 'agent-models.json');
31
+ // parseAgentMappingEnvelope handles IO, BOM-strip, JSON parse, and shape validation.
32
+ // Raw read intentionally avoids readAgentMapping (which would drop unknown fields).
33
+ const envelope = await parseAgentMappingEnvelope(filePath);
34
+ if (envelope.kind === 'skip')
35
+ return { infos, warnings };
36
+ if (envelope.kind === 'warn') {
37
+ warnings.push(`${migrationId}: ${envelope.message}`);
38
+ return { infos, warnings };
39
+ }
40
+ const onWarning = (msg) => warnings.push(msg);
41
+ const { agents: migrated, didMutate, renamed, dropped, guardDropped } = canonicaliseAgentKeys(envelope.rawAgents, onWarning);
42
+ if (!didMutate)
43
+ return { infos, warnings };
44
+ // Write back with the canonical keys, preserving the rest of the envelope.
45
+ // Idempotent under concurrent execution (no lock needed; see D31 comment above).
46
+ const updated = { ...envelope.envelope, agents: migrated };
47
+ try {
48
+ await writeFileAtomicExclusive(filePath, JSON.stringify(updated, null, 2) + '\n');
49
+ }
50
+ catch (err) {
51
+ warnings.push(`${migrationId}: failed to write ${filePath}: ${err.message}`);
52
+ return { infos, warnings };
53
+ }
54
+ if (renamed.length > 0) {
55
+ infos.push(`Migrated agent-models.json: renamed ${renamed.map(k => `'${k}' → '${LEGACY_AGENT_KEYS[k]}'`).join(', ')}`);
56
+ }
57
+ if (dropped.length > 0) {
58
+ warnings.push(`${migrationId}: dropped legacy key(s) ${dropped.map(k => `'${k}'`).join(', ')} — the replacement key already holds a value, existing value kept`);
59
+ }
60
+ if (guardDropped.length > 0) {
61
+ warnings.push(`${migrationId}: dropped legacy key(s) ${guardDropped.map(k => `'${k}'`).join(', ')} — skipped due to prototype-pollution guard`);
62
+ }
63
+ return { infos, warnings };
64
+ };
65
+ }
17
66
  /**
18
67
  * D31: Registry pattern over scattered `if (!applied.includes(...))` conditionals.
19
68
  *
@@ -33,9 +82,9 @@ import { migrateLegacyTrackerConventions } from './tracker.js';
33
82
  * This was acceptable with an empty MIGRATIONS registry (D37: the vacuous-truth
34
83
  * case); this registry's first real entry is added below. Fix deferred.
35
84
  *
36
- * Note how that interacts with `canonicalise-agent-keys-v1`: runGlobalMigration
37
- * marks a migration applied for ANY non-throwing return, and that entry never
38
- * throws — it catches every I/O and parse failure and returns it as a *warning*.
85
+ * Note how that interacts with the `canonicalise-agent-keys-vN` entries: runGlobalMigration
86
+ * marks a migration applied for ANY non-throwing return, and those entries never
87
+ * throw — it catches every I/O and parse failure and returns it as a *warning*.
39
88
  * So it does NOT hit the unbounded-retry path. It hits the opposite one: a
40
89
  * transient EACCES or a failed write records the migration as applied and it
41
90
  * never runs again, leaving legacy keys on disk permanently.
@@ -52,47 +101,17 @@ export const MIGRATIONS = [
52
101
  id: 'canonicalise-agent-keys-v1',
53
102
  description: 'Rename legacy agent keys in ~/.devflow/agent-models.json to their canonical names',
54
103
  scope: 'global',
55
- async run(ctx) {
56
- const infos = [];
57
- const warnings = [];
58
- // Fast path: no legacy keys defined — skip without touching the file.
59
- if (Object.keys(LEGACY_AGENT_KEYS).length === 0)
60
- return { infos, warnings };
61
- const filePath = path.join(ctx.devflowDir, 'agent-models.json');
62
- // parseAgentMappingEnvelope handles IO, BOM-strip, JSON parse, and shape validation.
63
- // Raw read intentionally avoids readAgentMapping (which would drop unknown fields).
64
- const envelope = await parseAgentMappingEnvelope(filePath);
65
- if (envelope.kind === 'skip')
66
- return { infos, warnings };
67
- if (envelope.kind === 'warn') {
68
- warnings.push(`canonicalise-agent-keys-v1: ${envelope.message}`);
69
- return { infos, warnings };
70
- }
71
- const onWarning = (msg) => warnings.push(msg);
72
- const { agents: migrated, didMutate, renamed, dropped, guardDropped } = canonicaliseAgentKeys(envelope.rawAgents, onWarning);
73
- if (!didMutate)
74
- return { infos, warnings };
75
- // Write back with the canonical keys, preserving the rest of the envelope.
76
- // Idempotent under concurrent execution (no lock needed; see D31 comment above).
77
- const updated = { ...envelope.envelope, agents: migrated };
78
- try {
79
- await writeFileAtomicExclusive(filePath, JSON.stringify(updated, null, 2) + '\n');
80
- }
81
- catch (err) {
82
- warnings.push(`canonicalise-agent-keys-v1: failed to write ${filePath}: ${err.message}`);
83
- return { infos, warnings };
84
- }
85
- if (renamed.length > 0) {
86
- infos.push(`Migrated agent-models.json: renamed ${renamed.map(k => `'${k}' → '${LEGACY_AGENT_KEYS[k]}'`).join(', ')}`);
87
- }
88
- if (dropped.length > 0) {
89
- warnings.push(`canonicalise-agent-keys-v1: dropped legacy key(s) ${dropped.map(k => `'${k}'`).join(', ')} — canonical key already present, existing value kept`);
90
- }
91
- if (guardDropped.length > 0) {
92
- warnings.push(`canonicalise-agent-keys-v1: dropped legacy key(s) ${guardDropped.map(k => `'${k}'`).join(', ')} — skipped due to prototype-pollution guard`);
93
- }
94
- return { infos, warnings };
95
- },
104
+ run: canonicaliseAgentKeysRun('canonicalise-agent-keys-v1'),
105
+ },
106
+ {
107
+ // v1 is already recorded as applied on every machine that upgraded through 2.x, so the
108
+ // six-agent rename needs an id of its own. Both ids run the same body. On a 1.x machine
109
+ // v1 runs first and, with the single-hop LEGACY_AGENT_KEYS, already lands every key on its
110
+ // final slug, so v2 finds nothing to rewrite and makes no write.
111
+ id: 'canonicalise-agent-keys-v2',
112
+ description: 'Rename the keys of the six renamed agents in ~/.devflow/agent-models.json to their new names',
113
+ scope: 'global',
114
+ run: canonicaliseAgentKeysRun('canonicalise-agent-keys-v2'),
96
115
  },
97
116
  {
98
117
  id: 'tracker-conventions-per-provider-v1',