devflow-kit 2.2.0 → 2.4.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 +45 -0
- package/dist/cli/commands/attribution-prompts.js +144 -0
- package/dist/cli/commands/compliance-prompts.js +7 -11
- package/dist/cli/commands/init-seed.js +45 -4
- package/dist/cli/commands/init.js +49 -6
- package/dist/cli/commands/prompt-io.js +38 -0
- package/dist/cli/commands/proxy.js +91 -31
- package/dist/cli/commands/uninstall.js +22 -12
- package/dist/commands/resolve.md +29 -16
- package/dist/core/flags.js +145 -4
- package/dist/core/proxy-log.js +4 -2
- package/dist/core/proxy-state.js +91 -37
- package/dist/targets/claude-code/post-install.js +106 -29
- package/package.json +2 -2
- package/src/assets/agents/learning.md +63 -13
- package/src/assets/agents/triage.md +19 -1
- package/src/assets/commands/resolve.mds +29 -16
- package/src/assets/scripts/hooks/background-memory-update +180 -37
- package/src/assets/scripts/hooks/ensure-proxy +64 -5
- package/src/assets/scripts/hooks/is-hex-sha +14 -0
- package/src/assets/scripts/hooks/json-helper.cjs +223 -38
- package/src/assets/scripts/hooks/lib/decisions-format.cjs +264 -43
- package/src/assets/scripts/hooks/lib/mkdir-lock.cjs +1 -1
- package/src/assets/scripts/hooks/lib/render-decisions.cjs +59 -28
- package/src/assets/scripts/hooks/pre-compact-memory +40 -22
- package/src/assets/scripts/hooks/session-start-memory +19 -20
- package/src/assets/skills/git/SKILL.md +1 -5
- package/src/assets/skills/git/references/patterns.md +0 -6
- package/src/targets/claude-code/templates/settings.json +0 -4
|
@@ -712,10 +712,95 @@ export async function stripUserSecurityDenyList(settingsPath) {
|
|
|
712
712
|
await writeFileAtomicExclusive(settingsPath, stripped);
|
|
713
713
|
return { removed };
|
|
714
714
|
}
|
|
715
|
+
/** True for a non-null, non-array object literal. */
|
|
716
|
+
function isPlainObject(value) {
|
|
717
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Every `command` string carried by a hook matcher, tolerating foreign shapes.
|
|
721
|
+
* Returns an empty array for anything that is not `{ hooks: [{ command: string }] }`.
|
|
722
|
+
*/
|
|
723
|
+
function hookCommandsOf(matcher) {
|
|
724
|
+
if (!isPlainObject(matcher) || !Array.isArray(matcher.hooks))
|
|
725
|
+
return [];
|
|
726
|
+
return matcher.hooks
|
|
727
|
+
.filter(isPlainObject)
|
|
728
|
+
.map((h) => h.command)
|
|
729
|
+
.filter((c) => typeof c === 'string');
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Merge Devflow's template hook entries and top-level fields into an existing
|
|
733
|
+
* parsed settings object. Idempotent by exact command string — a hook that is
|
|
734
|
+
* already present is skipped. Sets `statusLine` only when the user has no existing
|
|
735
|
+
* value. Attribution is NOT injected here — managed by the flags pipeline (D27).
|
|
736
|
+
* Mutates `existing` in place.
|
|
737
|
+
*
|
|
738
|
+
* D-SETTINGS-1: merge strategy — never replace; only add devflow entries that are absent.
|
|
739
|
+
* Returns { changed: true } when any field was added.
|
|
740
|
+
*
|
|
741
|
+
* `existing` comes from a hand-editable file, so every branch is shape-guarded:
|
|
742
|
+
* a `hooks` value (or per-event value) that is not the expected object/array shape
|
|
743
|
+
* is left untouched rather than overwritten or thrown on (applies PF-023 — validate
|
|
744
|
+
* at the sink that mutates).
|
|
745
|
+
*
|
|
746
|
+
* Exported for testing.
|
|
747
|
+
*/
|
|
748
|
+
export function mergeDevflowSettingsTemplate(existing, template) {
|
|
749
|
+
let changed = false;
|
|
750
|
+
// Merge hook entries — idempotent by exact command string
|
|
751
|
+
const tmplHooks = isPlainObject(template.hooks) ? template.hooks : {};
|
|
752
|
+
const existingHooksRaw = existing.hooks;
|
|
753
|
+
if (existingHooksRaw === undefined || isPlainObject(existingHooksRaw)) {
|
|
754
|
+
const existingHooks = existingHooksRaw ?? {};
|
|
755
|
+
let hooksChanged = false;
|
|
756
|
+
for (const [event, matchers] of Object.entries(tmplHooks)) {
|
|
757
|
+
if (!Array.isArray(matchers))
|
|
758
|
+
continue;
|
|
759
|
+
for (const matcher of matchers) {
|
|
760
|
+
const cmd = hookCommandsOf(matcher)[0];
|
|
761
|
+
if (!cmd)
|
|
762
|
+
continue;
|
|
763
|
+
const current = existingHooks[event];
|
|
764
|
+
if (current !== undefined && !Array.isArray(current))
|
|
765
|
+
break; // foreign shape — leave the event alone
|
|
766
|
+
const eventArr = current ?? [];
|
|
767
|
+
if (eventArr.some((m) => hookCommandsOf(m).includes(cmd)))
|
|
768
|
+
continue;
|
|
769
|
+
eventArr.push(matcher);
|
|
770
|
+
existingHooks[event] = eventArr;
|
|
771
|
+
hooksChanged = true;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
// Attach only when something was actually added — never introduce an empty
|
|
775
|
+
// `hooks` key into a settings.json that had none.
|
|
776
|
+
if (hooksChanged) {
|
|
777
|
+
existing.hooks = existingHooks;
|
|
778
|
+
changed = true;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
// Set statusLine only if the user has none
|
|
782
|
+
if (existing.statusLine === undefined && template.statusLine !== undefined) {
|
|
783
|
+
existing.statusLine = template.statusLine;
|
|
784
|
+
changed = true;
|
|
785
|
+
}
|
|
786
|
+
// attribution: owned exclusively by the flags pipeline (applyFlags/stripFlags, D27) — a second writer here would race it.
|
|
787
|
+
return { changed };
|
|
788
|
+
}
|
|
715
789
|
/**
|
|
716
790
|
* Install or update settings.json with Devflow configuration.
|
|
717
|
-
*
|
|
718
|
-
*
|
|
791
|
+
*
|
|
792
|
+
* Strategy (D-SETTINGS-1: merge, never overwrite wholesale):
|
|
793
|
+
* - Fresh file: write the template directly.
|
|
794
|
+
* - Existing file: MERGE devflow hook entries and fields into the parsed object.
|
|
795
|
+
* Idempotent by exact command string — existing hooks are never duplicated or removed.
|
|
796
|
+
* Preserves every user key (env, permissions, apiKeyHelper, model, etc.) untouched.
|
|
797
|
+
* - Parse failure: warn and skip; file left byte-identical (never clobber a broken file).
|
|
798
|
+
*
|
|
799
|
+
* The merge is additive only, so it runs without a confirmation prompt: init is called
|
|
800
|
+
* from inside an active spinner (a prompt would render on top of it), and declining
|
|
801
|
+
* could not protect the file anyway — init's own settings pass rewrites the hook set
|
|
802
|
+
* unconditionally right afterwards. A prompt whose answer changes nothing is worse
|
|
803
|
+
* than no prompt.
|
|
719
804
|
*
|
|
720
805
|
* The deny list is handled by init's dedicated security step
|
|
721
806
|
* (applyUserSecurityDenyList / installManagedSettings) after installSettings completes.
|
|
@@ -741,38 +826,30 @@ export async function installSettings(claudeDir, rootDir, devflowDir, verbose) {
|
|
|
741
826
|
}
|
|
742
827
|
return;
|
|
743
828
|
}
|
|
744
|
-
// Settings exist —
|
|
745
|
-
let
|
|
829
|
+
// Settings exist — parse and merge (never overwrite wholesale)
|
|
830
|
+
let existingParsed;
|
|
746
831
|
try {
|
|
747
|
-
|
|
748
|
-
hasHooks = !!existing.hooks;
|
|
832
|
+
existingParsed = JSON.parse(await fs.readFile(settingsPath, 'utf-8'));
|
|
749
833
|
}
|
|
750
|
-
catch {
|
|
751
|
-
|
|
752
|
-
|
|
834
|
+
catch {
|
|
835
|
+
// Parse failure — warn and skip; file left byte-identical (D-SETTINGS-1)
|
|
836
|
+
if (verbose) {
|
|
837
|
+
p.log.warn('settings.json could not be parsed — Devflow hooks not added. ' +
|
|
838
|
+
'Fix the JSON manually and re-run devflow init.');
|
|
839
|
+
}
|
|
753
840
|
return;
|
|
754
841
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
p.cancel('Installation cancelled.');
|
|
763
|
-
process.exit(0);
|
|
764
|
-
}
|
|
765
|
-
if (confirmed) {
|
|
766
|
-
await fs.writeFile(settingsPath, settingsContent, 'utf-8');
|
|
767
|
-
p.log.success('Settings overridden');
|
|
768
|
-
}
|
|
769
|
-
else {
|
|
770
|
-
p.log.info('Keeping existing settings');
|
|
771
|
-
}
|
|
842
|
+
const templateParsed = JSON.parse(settingsContent);
|
|
843
|
+
// Merge template into existing (mutates existingParsed in place).
|
|
844
|
+
// If nothing needs to be added the write is skipped entirely.
|
|
845
|
+
const { changed } = mergeDevflowSettingsTemplate(existingParsed, templateParsed);
|
|
846
|
+
if (!changed) {
|
|
847
|
+
// Already fully configured — nothing to do
|
|
848
|
+
return;
|
|
772
849
|
}
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
p.log.
|
|
850
|
+
await writeFileAtomicExclusive(settingsPath, JSON.stringify(existingParsed, null, 2) + '\n');
|
|
851
|
+
if (verbose) {
|
|
852
|
+
p.log.success('Settings updated with Devflow hooks and HUD');
|
|
776
853
|
}
|
|
777
854
|
}
|
|
778
855
|
catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devflow-kit",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "A meta-harness for Claude Code — turns a single coding agent into an engineering team: orchestration, parallel review, persistent memory, self-learning, and graph workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"@clack/prompts": "^0.9.1",
|
|
64
64
|
"commander": "^12.0.0",
|
|
65
65
|
"picocolors": "^1.1.1",
|
|
66
|
-
"subswitch": "0.
|
|
66
|
+
"subswitch": "0.4.0"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@mdscript/mds": "0.2.0",
|
|
@@ -18,7 +18,7 @@ skills:
|
|
|
18
18
|
You process the pending decisions queue for one project: claim it atomically, detect
|
|
19
19
|
decision/pitfall patterns worth keeping, curate the existing ledger, and delete the claimed
|
|
20
20
|
queue as your final act. You read and edit the data files directly — no script reads,
|
|
21
|
-
validates, or applies anything on your behalf. The only executables you call are the
|
|
21
|
+
validates, or applies anything on your behalf. The only executables you call are the four
|
|
22
22
|
ledger ops below.
|
|
23
23
|
|
|
24
24
|
## Iron Law
|
|
@@ -26,11 +26,12 @@ ledger ops below.
|
|
|
26
26
|
> **assign-anchor OWNS NUMBERING; render OWNS THE .md; NEVER HAND-EDIT decisions.md, pitfalls.md, or index.md**
|
|
27
27
|
>
|
|
28
28
|
> ADR and PF numbers are assigned exclusively by `assign-anchor`. The `.md` files are written
|
|
29
|
-
> exclusively by `render-decisions.cjs` (invoked internally by `assign-anchor`/`retire-anchor`).
|
|
30
|
-
> One `assign-anchor` invocation claims one number and re-renders all three files
|
|
31
|
-
> (decisions.md, pitfalls.md, index.md
|
|
32
|
-
>
|
|
33
|
-
>
|
|
29
|
+
> exclusively by `render-decisions.cjs` (invoked internally by `assign-anchor`/`retire-anchor`/`refresh-anchor`).
|
|
30
|
+
> One `assign-anchor` invocation claims one number and re-renders all three files
|
|
31
|
+
> (decisions.md, pitfalls.md, index.md — each write atomic; the sequence is not transactional:
|
|
32
|
+
> a crash between writes self-heals on the next op). To deprecate, supersede, or retire an entry, call
|
|
33
|
+
> `retire-anchor <anchor_id> <status>` — never edit the `.md` files directly. Every ledger op
|
|
34
|
+
> re-renders all three files internally; there is no separate render step for you to run.
|
|
34
35
|
|
|
35
36
|
## Environment
|
|
36
37
|
|
|
@@ -39,6 +40,7 @@ are relative to it. The ledger ops live at `$HOME/.devflow/scripts/hooks/json-he
|
|
|
39
40
|
|
|
40
41
|
- `assign-anchor <type> <obs_id>` — claims the next ADR/PF number and re-renders all three `.md` files (decisions.md, pitfalls.md, index.md)
|
|
41
42
|
- `retire-anchor <anchor_id> <status>` — flips a ledger row's rendered status and re-renders
|
|
43
|
+
- `refresh-anchor <anchor_id> [<anchor_id>...]` — variadic: re-projects one or more anchored log rows through the same projector as `assign-anchor` in a single lock/parse/render pass; use after reinforcing already-anchored observations (ADR-022)
|
|
42
44
|
- `rotate-observations` — archives `observing` log rows older than 30 days
|
|
43
45
|
|
|
44
46
|
Each op self-locks internally. Call them plainly — never wrap them in a lock of your own,
|
|
@@ -119,6 +121,23 @@ rewrite the whole file:
|
|
|
119
121
|
timestamps are UTC ISO (`date -u +%Y-%m-%dT%H:%M:%SZ`). Estimate `confidence` honestly —
|
|
120
122
|
it is curation metadata only, NOT a gate; do not inflate it.
|
|
121
123
|
|
|
124
|
+
**`details` grammar**: use `Key: value` segments separated by `;`. Recognised keys are per
|
|
125
|
+
type and disjoint — decisions: `context:`, `decision:`, `rationale:`; pitfalls: `area:`,
|
|
126
|
+
`issue:`, `impact:`, `resolution:`. A segment that begins with a key recognised FOR THAT TYPE
|
|
127
|
+
starts a new field; any other segment (including a key from the opposite type) is appended to
|
|
128
|
+
the previous field's value, so semicolons inside a value are preserved. Keep prose out of key
|
|
129
|
+
positions — do not start a value with text that looks like a recognised key for that type. The
|
|
130
|
+
parser has a recovery pass for legacy mid-segment keys.
|
|
131
|
+
|
|
132
|
+
**`amendments` field**: when reinforcing an already-anchored observation with a dated
|
|
133
|
+
correction or ratification that should remain visible as history (rather than silently
|
|
134
|
+
rewriting `details`), APPEND `{ "date": "YYYY-MM-DD", "note": "..." }` to the log row's
|
|
135
|
+
`amendments` array (create the array if absent). The shape is exactly `{date, note}` — the
|
|
136
|
+
schema guard rejects bare strings. Amendments render at the end of the entry body in
|
|
137
|
+
`decisions.md`/`pitfalls.md`; they never appear in `index.md` lines. A follow-up
|
|
138
|
+
`refresh-anchor <anchor_id>` is required to propagate the addition to the rendered files
|
|
139
|
+
(ADR-022).
|
|
140
|
+
|
|
122
141
|
- **Reinforce an existing row** — use the Edit tool to replace that row's single line:
|
|
123
142
|
increment `observations`, union `evidence` (dedupe, cap 10), update `last_seen`, and
|
|
124
143
|
refresh `pattern`/`details`/`confidence` only where the new evidence sharpens them.
|
|
@@ -134,12 +153,32 @@ node "$HOME/.devflow/scripts/hooks/json-helper.cjs" assign-anchor "pitfall" "obs
|
|
|
134
153
|
NEVER hand-edit `decisions.md` or `pitfalls.md`. NEVER invent an ADR-NNN/PF-NNN number
|
|
135
154
|
yourself — `assign-anchor` is the only source of numbering.
|
|
136
155
|
|
|
156
|
+
**After reinforcing already-anchored observations**: once you have updated all target log rows
|
|
157
|
+
(incrementing `observations`, refreshing `pattern`/`details`, updating `last_seen`), collect
|
|
158
|
+
all anchor ids and make ONE variadic call:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
node "$HOME/.devflow/scripts/hooks/json-helper.cjs" refresh-anchor <anchor_id1> [<anchor_id2> ...]
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
This re-projects all sharpened log rows in a single lock/parse/render pass, propagating
|
|
165
|
+
improvements to `decisions.md`/`pitfalls.md`/`index.md`. BATCH: do not call once per row — N
|
|
166
|
+
calls pay N full-corpus renders; one variadic call pays one. Refresh calls do not consume
|
|
167
|
+
curation slots; however, at most 10 anchors may be refreshed per run — stop if the cap is
|
|
168
|
+
reached.
|
|
169
|
+
|
|
137
170
|
## Part 2 — Curation
|
|
138
171
|
|
|
139
172
|
Periodic housekeeping of the ledger and rendered `.md` files. Bounds: **≤5 curation changes
|
|
140
173
|
per run**. **7-day protection window** — never touch any entry whose `date` field in the
|
|
141
174
|
ledger (`.devflow/learning/decisions-ledger.jsonl`) is within the past 7 days. The window key
|
|
142
|
-
is the ledger row's `date` field (YYYY-MM-DD), not anything in the `.md` file.
|
|
175
|
+
is the ledger row's `date` field (YYYY-MM-DD), not anything in the `.md` file. If the ledger
|
|
176
|
+
row lacks a `date` field (pitfall rows promoted before date-stamping was added), use the
|
|
177
|
+
observation log row's `last_seen` date for the window. If `last_seen` is also unavailable, the
|
|
178
|
+
entry predates date-stamping and is outside the protection window (no backfill: a fabricated date would be worse than an unprotected entry — ADR-022).
|
|
179
|
+
Example: a pitfall row with no ledger `date` whose log row has `last_seen: "2026-08-27"` → window
|
|
180
|
+
key 2026-08-27 (protected if within 7 days of today); no ledger `date` AND no log `last_seen`
|
|
181
|
+
→ outside the window, eligible for curation.
|
|
143
182
|
|
|
144
183
|
Ground yourself first, all by direct reads:
|
|
145
184
|
- Active entries and counts: `decisions.md` / `pitfalls.md` — what is rendered is what is active.
|
|
@@ -148,6 +187,13 @@ Ground yourself first, all by direct reads:
|
|
|
148
187
|
those files still exist (Glob). An entry whose referenced files are gone is a preferred
|
|
149
188
|
retirement candidate — a signal to prefer, not an automatic retirement.
|
|
150
189
|
|
|
190
|
+
**PF-040 pointer-vs-citation gate**: before acting on a missing-path signal (a file cited in
|
|
191
|
+
`details`/`evidence` no longer exists), determine whether the reference is a live POINTER (a
|
|
192
|
+
file a reader should follow today) or a HISTORICAL CITATION (the file the entry recorded
|
|
193
|
+
deleting, replacing, or retiring). A missing live pointer is drift — repair the reference. A
|
|
194
|
+
missing historical citation is confirmation that the decision was implemented — leave the entry
|
|
195
|
+
intact.
|
|
196
|
+
|
|
151
197
|
**Rotate stale observations first** (before selecting curation candidates):
|
|
152
198
|
|
|
153
199
|
```bash
|
|
@@ -180,10 +226,13 @@ node "$HOME/.devflow/scripts/hooks/json-helper.cjs" retire-anchor <anchor_id> <s
|
|
|
180
226
|
|
|
181
227
|
`retire-anchor` is atomic and idempotent. Call it once per entry.
|
|
182
228
|
|
|
183
|
-
**Citation preservation
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
`
|
|
229
|
+
**Citation preservation** (ADR-022 — log is content authority): if an entry being retired
|
|
230
|
+
has inbound `applies ADR-NNN` citations in other entries' `pattern`/`details`, update those
|
|
231
|
+
other entries to reference the surviving entry — do this by editing their **log rows** in
|
|
232
|
+
`decisions-log.jsonl` (one line at a time), then collecting all updated anchor ids and calling
|
|
233
|
+
ONCE: `node "$HOME/.devflow/scripts/hooks/json-helper.cjs" refresh-anchor <anchor_id1> [<anchor_id2> ...]`
|
|
234
|
+
Batch all ids into the single variadic call — one lock/parse/render pass for the whole set.
|
|
235
|
+
Never edit the ledger directly for content changes; the log is the authority.
|
|
187
236
|
|
|
188
237
|
**Cap enforcement**: stop after 5 changes regardless of remaining candidates.
|
|
189
238
|
|
|
@@ -191,8 +240,9 @@ instead — edit those ledger rows directly (one line at a time), then re-render
|
|
|
191
240
|
|
|
192
241
|
1. Run `rotate-observations` if you have not already this run (Part 2 covers it — never run
|
|
193
242
|
it twice).
|
|
194
|
-
2. Delete the claim file as your FINAL act, strictly after every other write (
|
|
195
|
-
|
|
243
|
+
2. Delete the claim file as your FINAL act, strictly after every other write (`rm -f` is
|
|
244
|
+
denied by devflow's recommended deny-list; `unlink` and a flagless `rm` both pass — use
|
|
245
|
+
`unlink` (PF-003)):
|
|
196
246
|
`unlink .devflow/learning/.pending-turns.processing`
|
|
197
247
|
If deletion is denied, finish normally and note the leftover claim file in your summary —
|
|
198
248
|
the next run's stale-merge recovery folds it in.
|
|
@@ -28,10 +28,22 @@ You receive from orchestrator:
|
|
|
28
28
|
|
|
29
29
|
1. **Read context per issue**: For each issue, Read 30 lines around the reported file:line to understand the actual code.
|
|
30
30
|
2. **Apply Decisions**: Scan the DECISIONS_CONTEXT index to identify relevant ADR and PF entries. Read full bodies on demand. Cite `applies ADR-NNN` / `avoids PF-NNN` in your Reasoning column. Skip when DECISIONS_CONTEXT is empty or `(none)`. Use only verbatim IDs from the index — do not fabricate.
|
|
31
|
-
3. **Assign disposition**:
|
|
31
|
+
3. **Assign disposition**: Run the duplicate grouping pre-pass, then apply the blast-radius matrix to each group's primary. Every issue gets exactly one verdict (DUPLICATE included) — none may vanish.
|
|
32
32
|
4. **Document evidence**: FALSE_POSITIVE requires cited grep/file:line. BY_DESIGN requires an ADR or inline comment/doc citation.
|
|
33
33
|
5. **Assign risk tier**: For every FIX_NOW issue, annotate Standard or Careful.
|
|
34
34
|
|
|
35
|
+
## Duplicate Grouping Pre-Pass
|
|
36
|
+
|
|
37
|
+
Run this pre-pass **before** the disposition matrix. It is a relation between issues, not a matrix row.
|
|
38
|
+
|
|
39
|
+
1. **Group by same defect**: cluster issues that share the same root cause — typically the same or adjacent file:line reported by different review foci, or the same logical error in different phrasings.
|
|
40
|
+
2. **Select primary**: from each group, designate as primary the most specific and complete report — but when a group mixes security and non-security findings (a 'security member' is one that would trigger the Security Gate), the security member is always the primary. All other members are non-primary duplicates.
|
|
41
|
+
3. **Security gate applies to the whole group**: if ANY member is a security finding, the group's primary passes through the Security Gate (→ FIX_NOW or ESCALATED only). Never downgrade a group because non-security members outnumber the security finding.
|
|
42
|
+
4. **Non-primary members**: assign verdict **DUPLICATE** with `duplicate_of: <primary-id>`. Never chain — `duplicate_of` must reference a non-DUPLICATE issue. A DUPLICATE inherits its primary's outcome.
|
|
43
|
+
5. **Single-member groups**: if an issue has no duplicates it is its own primary — apply the matrix directly.
|
|
44
|
+
|
|
45
|
+
Apply the disposition matrix to each group's **primary only**.
|
|
46
|
+
|
|
35
47
|
## Blast-Radius Disposition Matrix
|
|
36
48
|
|
|
37
49
|
**First match wins. Apply in the order listed.**
|
|
@@ -117,6 +129,11 @@ Return the verdict ledger grouped by disposition:
|
|
|
117
129
|
|----------|-----------|----------------------|
|
|
118
130
|
| {id} | {file}:{line} | {why requires complete redesign} |
|
|
119
131
|
|
|
132
|
+
### DUPLICATE
|
|
133
|
+
| Issue ID | Duplicate Of | File:Line | Reason |
|
|
134
|
+
|----------|-------------|-----------|--------|
|
|
135
|
+
| {id} | {primary-id} | {file}:{line} | {same defect as {primary-id}, reported by {focus}} |
|
|
136
|
+
|
|
120
137
|
### Summary
|
|
121
138
|
- Total Issues: {n}
|
|
122
139
|
- ESCALATED: {n}
|
|
@@ -125,6 +142,7 @@ Return the verdict ledger grouped by disposition:
|
|
|
125
142
|
- BY_DESIGN: {n}
|
|
126
143
|
- FIX_SEPARATE: {n}
|
|
127
144
|
- TECH_DEBT: {n}
|
|
145
|
+
- DUPLICATE: {n}
|
|
128
146
|
```
|
|
129
147
|
|
|
130
148
|
## Boundaries
|
|
@@ -56,7 +56,7 @@ In multi-worktree mode, spawn all pre-flight agents **in a single message** (par
|
|
|
56
56
|
|
|
57
57
|
**If BLOCKED:** In single-worktree mode, stop and report the blocker to user. If no reviews found, suggest `/code-review` or `/bug-analysis` first. In multi-worktree mode, report the failure but continue with other worktrees.
|
|
58
58
|
|
|
59
|
-
**Extract from response:** `branch`, `branch_slug`, `pr_number`, `review_count`, `diff_files` per worktree.
|
|
59
|
+
**Extract from response:** `branch`, `base_branch`, `branch_slug`, `pr_number`, `review_count`, `diff_files` per worktree.
|
|
60
60
|
|
|
61
61
|
**Fetch PR body** (after extracting `pr_number`):
|
|
62
62
|
```bash
|
|
@@ -138,7 +138,7 @@ Issues are extracted from `\{TARGET_DIR\}` only — never cross-reference review
|
|
|
138
138
|
### Phase 1b: Fetch External Review Threads (Compliance-gated)
|
|
139
139
|
|
|
140
140
|
**Produces:** THREAD_MAP
|
|
141
|
-
**Requires:**
|
|
141
|
+
**Requires:** BRANCH_INFO, COMPLIANCE_SKILL_INSTALLED
|
|
142
142
|
|
|
143
143
|
Skip this phase if `COMPLIANCE_SKILL_INSTALLED` is false.
|
|
144
144
|
|
|
@@ -157,7 +157,7 @@ Parse `THREAD_MAP` from Git agent output. If Git agent returns `TRACEABILITY: DE
|
|
|
157
157
|
### Phase 2: Global Triage
|
|
158
158
|
|
|
159
159
|
**Produces:** TRIAGE_RESULTS
|
|
160
|
-
**Requires:** ISSUES, DIFF_FILES, DECISIONS_CONTEXT, FEATURE_KNOWLEDGE, PR_DESCRIPTION
|
|
160
|
+
**Requires:** ISSUES, DIFF_FILES, DECISIONS_CONTEXT, FEATURE_KNOWLEDGE, PR_DESCRIPTION
|
|
161
161
|
|
|
162
162
|
Spawn a single global Triage agent for ALL issues:
|
|
163
163
|
|
|
@@ -169,7 +169,7 @@ WORKTREE_PATH: {worktree_path} (omit if cwd)
|
|
|
169
169
|
DECISIONS_CONTEXT: {decisions_context}
|
|
170
170
|
FEATURE_KNOWLEDGE: {feature_knowledge}
|
|
171
171
|
PR_DESCRIPTION: <pr-description>{pr_description}</pr-description>
|
|
172
|
-
Triage every issue
|
|
172
|
+
Triage every issue: collapse duplicates first, then apply the blast-radius disposition matrix to each group's primary. Assign exactly one verdict per issue.
|
|
173
173
|
Follow devflow:apply-decisions to Read full ADR/PF bodies on demand.
|
|
174
174
|
Follow devflow:apply-feature-knowledge for FEATURE_KNOWLEDGE."
|
|
175
175
|
```
|
|
@@ -181,11 +181,12 @@ Wait for Triage agent to complete before proceeding. Parse verdict ledger from T
|
|
|
181
181
|
- **BY_DESIGN**: Intentional code (with ADR or code doc citation)
|
|
182
182
|
- **FIX_SEPARATE**: Valid but out of blast-radius scope (must become manage-debt ticket)
|
|
183
183
|
- **TECH_DEBT**: Architectural overhaul only — LAST RESORT
|
|
184
|
+
- **DUPLICATE**: Collapsed duplicate issue — carries `duplicate_of: <primary-id>` referencing the non-DUPLICATE primary; inherits the primary's outcome
|
|
184
185
|
|
|
185
186
|
Collect all decisions citations (ADR-NNN / PF-NNN) from Triage agent Reasoning columns.
|
|
186
187
|
|
|
187
188
|
**Triage agent completeness assertion (avoids PF-002):** Verify the parsed ledger against ISSUES before proceeding:
|
|
188
|
-
1. Every issue `id` from ISSUES must appear in exactly one verdict bucket — none may vanish, none may appear in multiple buckets.
|
|
189
|
+
1. Every issue `id` from ISSUES must appear in exactly one verdict bucket — none may vanish, none may appear in multiple buckets. DUPLICATE is a valid bucket; a valid DUPLICATE entry must name its `duplicate_of` primary (the `Duplicate Of` column of the ledger's DUPLICATE table) and that primary must be a non-DUPLICATE issue id. A missing `duplicate_of` or one that chains to another DUPLICATE is a **Triage agent failure** (retry-then-abort as below).
|
|
189
190
|
2. If the Triage agent output is empty, contains a skill re-entrancy guard string (e.g., contains `already running`), or is missing any issue IDs from ISSUES: treat as a **Triage agent failure**:
|
|
190
191
|
- Retry the Triage agent once with the same inputs.
|
|
191
192
|
- If the retry also fails the completeness check: abort with a clear error message listing the missing issue IDs and failure reason — never proceed with dropped issues.
|
|
@@ -199,7 +200,7 @@ Collect all decisions citations (ADR-NNN / PF-NNN) from Triage agent Reasoning c
|
|
|
199
200
|
|
|
200
201
|
If FIX_NOW list is empty: skip to Phase 5 — write full summary (Phase 5), run manage-debt (Phase 9) if FIX_SEPARATE/TECH_DEBT exist, run thread resolution + resolution comment (Phase 9b), run merge readiness (Phase 9c), display results (Phase 10).
|
|
201
202
|
|
|
202
|
-
Otherwise, batch FIX_NOW issues for Code agent execution:
|
|
203
|
+
Otherwise, batch FIX_NOW issues for Code agent execution. **DUPLICATE issues are never dispatched** — they inherit the primary's outcome:
|
|
203
204
|
- **Same-file issues** → one batch (one Code agent per file, sequential for same-file pairs)
|
|
204
205
|
- **Distinct-file issues** → parallel Code agents
|
|
205
206
|
- **Max 5 issues per batch** — chunk large sets
|
|
@@ -207,7 +208,7 @@ Otherwise, batch FIX_NOW issues for Code agent execution:
|
|
|
207
208
|
### Phase 4: Fix (Code agent × N)
|
|
208
209
|
|
|
209
210
|
**Produces:** CODE_AGENT_RESULTS
|
|
210
|
-
**Requires:** BATCHES, DECISIONS_CONTEXT, FEATURE_KNOWLEDGE
|
|
211
|
+
**Requires:** BATCHES, DECISIONS_CONTEXT, FEATURE_KNOWLEDGE
|
|
211
212
|
|
|
212
213
|
For each batch, spawn Code agent with `OPERATION: issue-fix` and `PUSH: false`:
|
|
213
214
|
|
|
@@ -236,12 +237,14 @@ Collect from each Code agent:
|
|
|
236
237
|
### Phase 5: Write resolution-summary.md
|
|
237
238
|
|
|
238
239
|
**Produces:** RESOLUTION_FILE (early write for compaction safety)
|
|
239
|
-
**Requires:** TRIAGE_RESULTS, CODE_AGENT_RESULTS
|
|
240
|
+
**Requires:** TRIAGE_RESULTS, CODE_AGENT_RESULTS, TARGET_DIR, BRANCH_INFO
|
|
240
241
|
|
|
241
242
|
**Immediately write `resolution-summary.md`** to `\{TARGET_DIR\}` using the Write tool. Do this now — not in Phase 9 — while results are fresh in context. This ensures the record is persisted even if later phases (Simplify, Verification Gate, CI gate, Tech Debt) trigger context compaction.
|
|
242
243
|
|
|
243
244
|
Set `Tracked` for FIX_SEPARATE and TECH_DEBT items to `(pending)` — to be backfilled after Phase 9 manage-debt.
|
|
244
245
|
|
|
246
|
+
DUPLICATE issues are listed **only** in `## Duplicates` — never in `## Fixed Issues`, `## False Positives`, `## By Design`, `## Fix Separately`, `## Deferred to Tech Debt`, `## Escalations`, or `## Blocked`. A duplicate of a FALSE_POSITIVE primary therefore leaves only the primary in the `False Positive` row and the `## False Positives` section; the same holds for every other outcome the duplicate inherits.
|
|
247
|
+
|
|
245
248
|
Use the template from the Output Artifact section below.
|
|
246
249
|
|
|
247
250
|
### Phase 6: Simplify
|
|
@@ -334,7 +337,7 @@ Otherwise, for each worktree with fixes:
|
|
|
334
337
|
|
|
335
338
|
**IMPORTANT**: Run sequentially across all worktrees (not in parallel) to avoid GitHub API conflicts.
|
|
336
339
|
|
|
337
|
-
If any issues are FIX_SEPARATE or TECH_DEBT, spawn Git agent:
|
|
340
|
+
If any issues are FIX_SEPARATE or TECH_DEBT, spawn Git agent. **DUPLICATE issues never create their own debt tickets** — a duplicate of a deferred primary is covered by the primary's ticket:
|
|
338
341
|
|
|
339
342
|
```
|
|
340
343
|
Agent(subagent_type="Git"):
|
|
@@ -359,6 +362,7 @@ Skip this step if `COMPLIANCE_SKILL_INSTALLED` is false or THREAD_MAP is empty.
|
|
|
359
362
|
|
|
360
363
|
Prepare THREAD_MAP with verdicts from triage/code agent results:
|
|
361
364
|
- For each `ext-\{N\}`: match to an issue verdict (FIXED, FALSE_POSITIVE, BY_DESIGN, ESCALATED) by `file:line` correlation
|
|
365
|
+
- If the matched issue has verdict DUPLICATE, use the **primary's** verdict and verification status for the reply — do not expose DUPLICATE to the thread author (avoids PF-024; caller-side mapping — git.md contracts unchanged)
|
|
362
366
|
- Include `commit_sha` from Code agent results for FIXED verdicts
|
|
363
367
|
- Unmatched threads: ESCALATED (human review)
|
|
364
368
|
|
|
@@ -399,7 +403,7 @@ Update `## Third-Party Threads` section in resolution-summary.md with thread res
|
|
|
399
403
|
### Phase 9c: Merge Readiness (Compliance-gated, Report-only)
|
|
400
404
|
|
|
401
405
|
**Produces:** MERGE_READINESS_REPORT
|
|
402
|
-
**Requires:**
|
|
406
|
+
**Requires:** BRANCH_INFO, COMPLIANCE_SKILL_INSTALLED
|
|
403
407
|
|
|
404
408
|
Skip this phase if `COMPLIANCE_SKILL_INSTALLED` is false.
|
|
405
409
|
|
|
@@ -418,7 +422,7 @@ If Git agent returns `TRACEABILITY: DEGRADED`: warn, proceed to Phase 10.
|
|
|
418
422
|
|
|
419
423
|
### Phase 10: Report
|
|
420
424
|
|
|
421
|
-
**Requires:** TARGET_DIR
|
|
425
|
+
**Requires:** BRANCH_INFO, TARGET_DIR
|
|
422
426
|
|
|
423
427
|
The resolution summary was already written to `\{TARGET_DIR\}/resolution-summary.md` in Phase 5 (updated by Phase 7 and Phase 9). Display results to the user:
|
|
424
428
|
|
|
@@ -438,6 +442,7 @@ The resolution summary was already written to `\{TARGET_DIR\}/resolution-summary
|
|
|
438
442
|
| Deferred | {n} |
|
|
439
443
|
| Blocked | {n} |
|
|
440
444
|
| Escalated | {n} |
|
|
445
|
+
| Duplicates Collapsed | {n} |
|
|
441
446
|
|
|
442
447
|
### Verification
|
|
443
448
|
Final gate: {PASS | FAILED after N attempts}
|
|
@@ -483,9 +488,9 @@ In multi-worktree mode, report results per worktree with aggregate summary.
|
|
|
483
488
|
│ └─ Git agent (fetch-review-threads) → THREAD_MAP
|
|
484
489
|
│
|
|
485
490
|
├─ Phase 2: Global Triage [Triage agent, opus, single agent]
|
|
486
|
-
│ └─ ALL issues → verdict ledger by disposition
|
|
491
|
+
│ └─ ALL issues → verdict ledger by disposition (incl. DUPLICATE with duplicate_of)
|
|
487
492
|
│
|
|
488
|
-
├─ Phase 3: Batch FIX_NOW issues (skip if empty)
|
|
493
|
+
├─ Phase 3: Batch FIX_NOW issues (skip if empty; DUPLICATE issues never dispatched)
|
|
489
494
|
│ └─ same-file sequential, distinct-file parallel, max 5/batch
|
|
490
495
|
│
|
|
491
496
|
├─ Phase 4: Fix [Code agent × N, OPERATION: issue-fix, PUSH: false]
|
|
@@ -528,6 +533,8 @@ In multi-worktree mode, report results per worktree with aggregate summary.
|
|
|
528
533
|
| Worktree pre-flight fails | Report failure, continue with other worktrees |
|
|
529
534
|
| Empty FIX_NOW list | Skip Phases 3-4/6-8; still write full summary + run manage-debt if FIX_SEPARATE/TECH_DEBT exist |
|
|
530
535
|
| ESCALATED security issues | Surfaced in ## Escalations + display callout; never routed to manage-debt |
|
|
536
|
+
| DUPLICATE verdict without duplicate_of, or chained to another DUPLICATE | Treated as Triage failure — same retry-then-abort as a vanished id |
|
|
537
|
+
| DUPLICATE issues in THREAD_MAP | Map ext-\{N\} to primary's verdict/verification status for thread reply |
|
|
531
538
|
| Verification Gate FAILED after 2 attempts | Recorded as FAILED in ## Verification + blocking callout; CI gate skipped; proceed to Phase 9 (manage-debt) then Phase 10 (display) |
|
|
532
539
|
| gh/GitHub absent | manage-debt fails gracefully; Tracked stays "(pending)" + noted — recorded, not dropped |
|
|
533
540
|
| COMPLIANCE_SKILL_INSTALLED false | Phases 1b, 9b-step-1, and 9c are skipped; post-resolution-summary (Phase 9b step 2) still runs if a PR is known |
|
|
@@ -556,7 +563,7 @@ Written in Phase 5 (Collect Results) to `\{TARGET_DIR\}/resolution-summary.md`:
|
|
|
556
563
|
```markdown
|
|
557
564
|
# Resolution Summary
|
|
558
565
|
|
|
559
|
-
**Branch**: {branch} -> {
|
|
566
|
+
**Branch**: {branch} -> {base_branch}
|
|
560
567
|
**Date**: {timestamp}
|
|
561
568
|
**Review**: {TARGET_DIR}
|
|
562
569
|
**Command**: /resolve
|
|
@@ -578,8 +585,9 @@ Written in Phase 5 (Collect Results) to `\{TARGET_DIR\}/resolution-summary.md`:
|
|
|
578
585
|
| Deferred | {n} |
|
|
579
586
|
| Blocked | {n} |
|
|
580
587
|
| Escalated | {n} |
|
|
588
|
+
| Duplicates Collapsed | {n} |
|
|
581
589
|
|
|
582
|
-
_(Note: `Deferred` = `## Fix Separately` count + `## Deferred to Tech Debt` count combined — the two sections are distinct by scope, but the Statistics row aggregates both for the convergence parser.)_
|
|
590
|
+
_(Note: `Deferred` = `## Fix Separately` count + `## Deferred to Tech Debt` count combined — the two sections are distinct by scope, but the Statistics row aggregates both for the convergence parser. `Total Issues` counts every triaged issue including collapsed duplicates; every row **between** `Total Issues` and `Duplicates Collapsed` counts UNIQUE (non-DUPLICATE) issues only, so `Total Issues` equals the sum of the rows below it. Excluding duplicates from `Fixed`, `False Positive`, and `Deferred` de-skews the fp\_ratio convergence formula in code-review without any parser change.)_
|
|
583
591
|
|
|
584
592
|
## Verification
|
|
585
593
|
| Command | Result |
|
|
@@ -625,6 +633,11 @@ Final gate: PASS | FAILED after {n} attempts
|
|
|
625
633
|
|-------|-----------|---------|
|
|
626
634
|
| {description} | {file}:{line} | {why} |
|
|
627
635
|
|
|
636
|
+
## Duplicates
|
|
637
|
+
| Issue | Duplicate Of | File:Line |
|
|
638
|
+
|-------|-------------|-----------|
|
|
639
|
+
| {description} | {primary-id} | {file}:{line} |
|
|
640
|
+
|
|
628
641
|
## Third-Party Threads
|
|
629
642
|
| Thread | File:Line | Verdict | Status |
|
|
630
643
|
|--------|-----------|---------|--------|
|
|
@@ -633,4 +646,4 @@ Final gate: PASS | FAILED after {n} attempts
|
|
|
633
646
|
|
|
634
647
|
_(Omit `## Third-Party Threads` if `COMPLIANCE_SKILL_INSTALLED` is false or no external threads were found.)_
|
|
635
648
|
|
|
636
|
-
**Statistics mapping (parser contract)**: the `Deferred` row = FIX_SEPARATE + TECH_DEBT (both deferral dispositions combined); By Design and Escalated are counted separately and excluded from `Deferred`. The `/code-review` convergence parser reads
|
|
649
|
+
**Statistics mapping (parser contract)**: the `Deferred` row = FIX_SEPARATE + TECH_DEBT (both deferral dispositions combined); By Design and Escalated are counted separately and excluded from `Deferred`. The `Duplicates Collapsed` row is additive — the `/code-review` convergence parser reads only `Deferred`, `Fixed`, and `False Positive` rows plus `## Fixed Issues` / `## False Positives` headings — keep those labels byte-stable. All rows that the parser reads count UNIQUE (non-DUPLICATE) issues only, so collapsed duplicates do not inflate fp\_ratio.
|