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.
- package/CHANGELOG.md +98 -0
- package/README.md +4 -4
- package/dist/agents/{diagnose.md → debug.md} +4 -4
- package/dist/agents/{code-fix.md → fix.md} +2 -2
- package/dist/agents/{scrutinize.md → harden.md} +6 -6
- package/dist/agents/{code-edit.md → refactor.md} +3 -3
- package/dist/cli/agents-view/render.js +1 -1
- package/dist/cli/commands/init.js +3 -3
- package/dist/cli/commands/learning.js +12 -12
- package/dist/cli/commands/legacy-hooks.js +1 -1
- package/dist/cli/commands/uninstall.js +111 -5
- package/dist/commands/bug-analysis.md +19 -15
- package/dist/commands/code-review.md +13 -22
- package/dist/commands/debug.md +15 -15
- package/dist/commands/dynamic-build.md +51 -55
- package/dist/commands/dynamic-plan.md +11 -9
- package/dist/commands/dynamic-profile.md +7 -5
- package/dist/commands/dynamic-tickets.md +11 -9
- package/dist/commands/explore.md +13 -13
- package/dist/commands/implement.md +58 -127
- package/dist/commands/plan.md +19 -41
- package/dist/commands/release.md +9 -5
- package/dist/commands/research.md +12 -8
- package/dist/commands/resolve.md +42 -86
- package/dist/commands/self-review.md +42 -42
- package/dist/core/agent-models.js +47 -11
- package/dist/core/{learning-tuning-config.js → curate-tuning-config.js} +14 -14
- package/dist/core/learning-queue-cleanup.js +1 -1
- package/dist/core/migrations.js +63 -44
- package/dist/core/plugins.js +14 -12
- package/dist/core/project-paths.js +3 -3
- package/dist/hud/codex-usage.js +247 -0
- package/dist/hud/components/codex-quota.js +37 -0
- package/dist/hud/components/usage-quota.js +1 -1
- package/dist/hud/config.js +1 -0
- package/dist/hud/index.js +16 -3
- package/dist/hud/render.js +3 -1
- package/dist/learning-off/agents/{diagnose.md → debug.md} +4 -4
- package/dist/learning-off/agents/{code-fix.md → fix.md} +2 -2
- package/dist/learning-off/agents/{scrutinize.md → harden.md} +6 -6
- package/dist/learning-off/agents/{code-edit.md → refactor.md} +3 -3
- package/dist/learning-off/commands/bug-analysis.md +9 -9
- package/dist/learning-off/commands/code-review.md +2 -15
- package/dist/learning-off/commands/debug.md +4 -8
- package/dist/learning-off/commands/dynamic-build.md +45 -51
- package/dist/learning-off/commands/dynamic-plan.md +5 -5
- package/dist/learning-off/commands/dynamic-profile.md +1 -1
- package/dist/learning-off/commands/dynamic-tickets.md +5 -5
- package/dist/learning-off/commands/explore.md +2 -6
- package/dist/learning-off/commands/implement.md +46 -119
- package/dist/learning-off/commands/plan.md +8 -33
- package/dist/learning-off/commands/release.md +4 -2
- package/dist/learning-off/commands/research.md +2 -2
- package/dist/learning-off/commands/resolve.md +31 -79
- package/dist/learning-off/commands/self-review.md +30 -34
- package/dist/skills/git/references/pr/check-merge-readiness.md +2 -0
- package/dist/skills/git/references/pr/update-pr-evidence.md +4 -6
- package/dist/targets/claude-code/installer.js +117 -84
- package/dist/targets/claude-code/tracker-servers.js +1 -1
- package/package.json +1 -1
- package/src/assets/agents/_partials/_code.mds +2 -2
- package/src/assets/agents/code.mds +2 -2
- package/src/assets/agents/{learning.md → curate.md} +4 -4
- package/src/assets/agents/{diagnose.mds → debug.mds} +4 -4
- package/src/assets/agents/evaluate.md +4 -4
- package/src/assets/agents/{code-fix.mds → fix.mds} +5 -5
- package/src/assets/agents/{scrutinize.mds → harden.mds} +6 -6
- package/src/assets/agents/{simplify.md → polish.md} +4 -4
- package/src/assets/agents/{code-edit.mds → refactor.mds} +6 -6
- package/src/assets/agents/test.md +2 -2
- package/src/assets/commands/_partials/_compliance.mds +1 -1
- package/src/assets/commands/_partials/_decisions.mds +26 -6
- package/src/assets/commands/_partials/_engine.mds +15 -15
- package/src/assets/commands/_partials/_evidence_policy.mds +0 -17
- package/src/assets/commands/_partials/_knowledge.mds +14 -9
- package/src/assets/commands/_partials/_preamble.mds +4 -2
- package/src/assets/commands/_partials/_roster.mds +4 -4
- package/src/assets/commands/bug-analysis.mds +7 -7
- package/src/assets/commands/code-review.mds +1 -14
- package/src/assets/commands/debug.mds +2 -2
- package/src/assets/commands/dynamic-build.mds +26 -32
- package/src/assets/commands/implement.mds +45 -102
- package/src/assets/commands/plan.mds +7 -37
- package/src/assets/commands/release.mds +5 -1
- package/src/assets/commands/resolve.mds +28 -76
- package/src/assets/commands/self-review.mds +27 -27
- package/src/assets/mds/git/_pr.mds +6 -6
- package/src/assets/scripts/hooks/assets/orchestrator-charter.md +4 -3
- package/src/assets/scripts/hooks/capture-prompt +1 -1
- package/src/assets/scripts/hooks/capture-question +1 -1
- package/src/assets/scripts/hooks/capture-turn +1 -1
- package/src/assets/scripts/hooks/json-helper.cjs +2 -2
- package/src/assets/scripts/hooks/lib/learning-store.cjs +1 -1
- package/src/assets/scripts/hooks/lib/project-paths.cjs +4 -4
- package/src/assets/scripts/hooks/preamble +1 -1
- package/src/assets/scripts/hooks/session-start-context +58 -45
- package/src/assets/scripts/verify-evidence.cjs +726 -9
- package/src/assets/skills/agent-references/SKILL.md +9 -5
- package/src/assets/skills/agent-references/references/commands/implement-evidence-asks.md +35 -0
- package/src/assets/skills/agent-references/references/commands/implement-revalidate.md +15 -0
- package/src/assets/skills/agent-references/references/synthesize/bug-analysis.md +1 -1
- package/src/assets/skills/apply-decisions/SKILL.md +6 -4
- package/src/assets/skills/docs-framework/SKILL.md +3 -3
- package/src/assets/skills/qa/SKILL.md +1 -1
- package/src/assets/skills/quality-gates/SKILL.md +7 -7
- package/src/assets/skills/quality-gates/references/report-template.md +7 -7
- /package/src/assets/skills/agent-references/references/{learning → curate}/maintain.md +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Self-review workflow -
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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`
|
|
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 —
|
|
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.
|
|
66
|
+
When `DECISIONS_CONTEXT` is not `(none)`, hand it only to the agents whose contract declares it.
|
|
67
67
|
|
|
68
|
-
|
|
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).
|
|
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.**
|
|
127
|
+
**One git call, then direct reads — no `.cjs` script.**
|
|
124
128
|
|
|
125
|
-
Pass `FEATURE_KNOWLEDGE_RULES` to
|
|
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:
|
|
133
|
+
### Phase 1: Polish agent (Code Refinement)
|
|
130
134
|
|
|
131
|
-
**Produces:**
|
|
135
|
+
**Produces:** POLISH_COMMITS
|
|
132
136
|
**Requires:** FILES_CHANGED, TASK_DESCRIPTION
|
|
133
137
|
|
|
134
|
-
Spawn
|
|
138
|
+
Spawn Polish agent to refine code for clarity and consistency:
|
|
135
139
|
|
|
136
|
-
Agent(subagent_type="
|
|
140
|
+
Agent(subagent_type="Polish", run_in_background=false):
|
|
137
141
|
"TASK_DESCRIPTION: {task_description}
|
|
138
142
|
FILES_CHANGED: {files_changed}
|
|
139
|
-
|
|
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.**
|
|
146
|
+
**Wait for completion.** Polish agent commits changes directly.
|
|
143
147
|
|
|
144
|
-
### Phase 2:
|
|
148
|
+
### Phase 2: Harden agent (9-Pillar Quality Gate)
|
|
145
149
|
|
|
146
|
-
**Produces:**
|
|
150
|
+
**Produces:** HARDEN_STATUS
|
|
147
151
|
**Requires:** FILES_CHANGED, TASK_DESCRIPTION, DECISIONS_CONTEXT
|
|
148
152
|
|
|
149
|
-
Spawn
|
|
153
|
+
Spawn Harden agent for quality evaluation and fixing:
|
|
150
154
|
|
|
151
|
-
Agent(subagent_type="
|
|
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,
|
|
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
|
|
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:**
|
|
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
|
-
###
|
|
194
|
+
### Polish agent
|
|
191
195
|
- {n} files refined for clarity
|
|
192
196
|
|
|
193
|
-
###
|
|
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: {
|
|
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
|
|
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
|
-
|
|
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:
|
|
275
|
+
├─ Phase 1: Polish agent
|
|
276
276
|
│ └─ Code refinement (commits directly)
|
|
277
277
|
│
|
|
278
|
-
├─ Phase 2:
|
|
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 (
|
|
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
|
-
|
|
|
293
|
-
|
|
|
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** -
|
|
301
|
-
3. **Validation gate** - If
|
|
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
|
|
145
|
+
* Maps every retired agent key to the live slug that replaces it. Two renames feed it:
|
|
146
146
|
*
|
|
147
|
-
*
|
|
148
|
-
* (
|
|
149
|
-
*
|
|
150
|
-
*
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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
|
|
11
|
-
* with, the `model:` line of src/assets/agents/
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(`[
|
|
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
|
|
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
|
|
74
|
+
export function loadCurateTuningConfig(ledgerRoot) {
|
|
75
75
|
const globalConfigPath = path.join(getDevFlowDirectory(), 'learning.json');
|
|
76
|
-
const projectConfigPath =
|
|
76
|
+
const projectConfigPath = getCurateTuningConfigPath(ledgerRoot);
|
|
77
77
|
let config = { ...DEFAULTS };
|
|
78
78
|
const globalJson = readConfigFile(globalConfigPath);
|
|
79
79
|
if (globalJson !== null) {
|
|
80
|
-
config =
|
|
80
|
+
config = applyCurateTuningConfigLayer(config, globalJson);
|
|
81
81
|
}
|
|
82
82
|
const projectJson = readConfigFile(projectConfigPath);
|
|
83
83
|
if (projectJson !== null) {
|
|
84
|
-
config =
|
|
84
|
+
config = applyCurateTuningConfigLayer(config, projectJson);
|
|
85
85
|
}
|
|
86
86
|
return config;
|
|
87
87
|
}
|
|
88
|
-
//# sourceMappingURL=
|
|
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
|
|
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.
|
package/dist/core/migrations.js
CHANGED
|
@@ -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-
|
|
37
|
-
* marks a migration applied for ANY non-throwing return, and
|
|
38
|
-
*
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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',
|