From 6bf77dd87782becb8259618b8e89913ceeccd330 Mon Sep 17 00:00:00 2001 From: Dean Sharon Date: Fri, 28 Aug 2026 01:41:53 +0300 Subject: [PATCH 1/7] feat(resolve): collapse duplicate cross-reviewer issues via DUPLICATE triage verdict MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a DUPLICATE verdict to the /resolve pipeline so the same underlying defect flagged by multiple Review agents counts as one issue in resolution-summary.md, de-skewing fp_ratio convergence math in code-review without any parser change. Changes: - triage.md: duplicate grouping pre-pass before the disposition matrix; DUPLICATE verdict section in output; duplicate_of attribute (non-chainable); security gate applies to the whole group (applies ADR-006, avoids PF-026) - resolve.mds: DUPLICATE added to every verdict-enumeration site — Phase 2 parse list, completeness assertion, Phase 3 batch exclusion, Phase 9 manage-debt note, Phase 9b-1 THREAD_MAP mapping, Phase 10 Results table, Output artifact Statistics (Duplicates Collapsed row + ## Duplicates section), Architecture block, Edge Cases (avoids PF-024 spawn↔op seam; additive-only per ADR-006) - tests/build-mds.test.ts: §16b guards pin DUPLICATE, duplicate_of, "| Duplicates Collapsed | ", and "## Duplicates" in compiled resolve.md (PF-018) Co-Authored-By: Claude --- src/assets/agents/triage.md | 18 ++++++++++ src/assets/commands/resolve.mds | 25 ++++++++++---- tests/build-mds.test.ts | 58 +++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 7 deletions(-) diff --git a/src/assets/agents/triage.md b/src/assets/agents/triage.md index 41da659d..ade5381e 100644 --- a/src/assets/agents/triage.md +++ b/src/assets/agents/triage.md @@ -32,6 +32,18 @@ You receive from orchestrator: 4. **Document evidence**: FALSE_POSITIVE requires cited grep/file:line. BY_DESIGN requires an ADR or inline comment/doc citation. 5. **Assign risk tier**: For every FIX_NOW issue, annotate Standard or Careful. +## Duplicate Grouping Pre-Pass + +Run this pre-pass **before** the disposition matrix. It is a relation between issues, not a matrix row. + +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. +2. **Select primary**: from each group, designate as primary the most specific and complete report. All other members are non-primary duplicates. +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. +4. **Non-primary members**: assign verdict **DUPLICATE** immediately with `duplicate_of: `. Never chain — `duplicate_of` must reference a non-DUPLICATE issue. A DUPLICATE inherits its primary's outcome. +5. **Single-member groups**: if an issue has no duplicates it is its own primary — apply the matrix directly. + +Apply the disposition matrix to each group's **primary only**. + ## Blast-Radius Disposition Matrix **First match wins. Apply in the order listed.** @@ -117,6 +129,11 @@ Return the verdict ledger grouped by disposition: |----------|-----------|----------------------| | {id} | {file}:{line} | {why requires complete redesign} | +### DUPLICATE +| Issue ID | Duplicate Of | File:Line | Reason | +|----------|-------------|-----------|--------| +| {id} | {primary-id} | {file}:{line} | {same defect as {primary-id}, reported by {focus}} | + ### Summary - Total Issues: {n} - ESCALATED: {n} @@ -125,6 +142,7 @@ Return the verdict ledger grouped by disposition: - BY_DESIGN: {n} - FIX_SEPARATE: {n} - TECH_DEBT: {n} +- DUPLICATE: {n} ``` ## Boundaries diff --git a/src/assets/commands/resolve.mds b/src/assets/commands/resolve.mds index 1b24d80c..8d8786c6 100644 --- a/src/assets/commands/resolve.mds +++ b/src/assets/commands/resolve.mds @@ -181,11 +181,12 @@ Wait for Triage agent to complete before proceeding. Parse verdict ledger from T - **BY_DESIGN**: Intentional code (with ADR or code doc citation) - **FIX_SEPARATE**: Valid but out of blast-radius scope (must become manage-debt ticket) - **TECH_DEBT**: Architectural overhaul only — LAST RESORT +- **DUPLICATE**: Collapsed duplicate issue — carries `duplicate_of: ` referencing the non-DUPLICATE primary; inherits the primary's outcome Collect all decisions citations (ADR-NNN / PF-NNN) from Triage agent Reasoning columns. **Triage agent completeness assertion (avoids PF-002):** Verify the parsed ledger against ISSUES before proceeding: -1. Every issue `id` from ISSUES must appear in exactly one verdict bucket — none may vanish, none may appear in multiple buckets. +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 DUPLICATE entry satisfies this invariant when it carries a valid `duplicate_of` referencing a non-DUPLICATE issue id. If any DUPLICATE entry is missing `duplicate_of`, or if `duplicate_of` points to another DUPLICATE, treat it as a **Triage agent failure** (same retry-then-abort handling below). 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**: - Retry the Triage agent once with the same inputs. - 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 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). -Otherwise, batch FIX_NOW issues for Code agent execution: +Otherwise, batch FIX_NOW issues for Code agent execution. **DUPLICATE issues are never dispatched** — they inherit the primary's outcome and are excluded from all batches: - **Same-file issues** → one batch (one Code agent per file, sequential for same-file pairs) - **Distinct-file issues** → parallel Code agents - **Max 5 issues per batch** — chunk large sets @@ -334,7 +335,7 @@ Otherwise, for each worktree with fixes: **IMPORTANT**: Run sequentially across all worktrees (not in parallel) to avoid GitHub API conflicts. -If any issues are FIX_SEPARATE or TECH_DEBT, spawn Git agent: +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: ``` Agent(subagent_type="Git"): @@ -359,6 +360,7 @@ Skip this step if `COMPLIANCE_SKILL_INSTALLED` is false or THREAD_MAP is empty. Prepare THREAD_MAP with verdicts from triage/code agent results: - For each `ext-\{N\}`: match to an issue verdict (FIXED, FALSE_POSITIVE, BY_DESIGN, ESCALATED) by `file:line` correlation +- 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 only, no change to git.md operation contracts) - Include `commit_sha` from Code agent results for FIXED verdicts - Unmatched threads: ESCALATED (human review) @@ -438,6 +440,7 @@ The resolution summary was already written to `\{TARGET_DIR\}/resolution-summary | Deferred | {n} | | Blocked | {n} | | Escalated | {n} | +| Duplicates Collapsed | {n} | ### Verification Final gate: {PASS | FAILED after N attempts} @@ -483,9 +486,9 @@ In multi-worktree mode, report results per worktree with aggregate summary. │ └─ Git agent (fetch-review-threads) → THREAD_MAP │ ├─ Phase 2: Global Triage [Triage agent, opus, single agent] -│ └─ ALL issues → verdict ledger by disposition +│ └─ ALL issues → verdict ledger by disposition (incl. DUPLICATE with duplicate_of) │ -├─ Phase 3: Batch FIX_NOW issues (skip if empty) +├─ Phase 3: Batch FIX_NOW issues (skip if empty; DUPLICATE issues never dispatched) │ └─ same-file sequential, distinct-file parallel, max 5/batch │ ├─ Phase 4: Fix [Code agent × N, OPERATION: issue-fix, PUSH: false] @@ -528,6 +531,8 @@ In multi-worktree mode, report results per worktree with aggregate summary. | Worktree pre-flight fails | Report failure, continue with other worktrees | | Empty FIX_NOW list | Skip Phases 3-4/6-8; still write full summary + run manage-debt if FIX_SEPARATE/TECH_DEBT exist | | ESCALATED security issues | Surfaced in ## Escalations + display callout; never routed to manage-debt | +| DUPLICATE verdict without duplicate_of | Treated as Triage failure — same retry-then-abort as a vanished id | +| DUPLICATE issues in THREAD_MAP | Map ext-\{N\} to primary's verdict/verification status for thread reply | | 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) | | gh/GitHub absent | manage-debt fails gracefully; Tracked stays "(pending)" + noted — recorded, not dropped | | 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 | @@ -578,8 +583,9 @@ Written in Phase 5 (Collect Results) to `\{TARGET_DIR\}/resolution-summary.md`: | Deferred | {n} | | Blocked | {n} | | Escalated | {n} | +| Duplicates Collapsed | {n} | -_(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.)_ +_(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. All rows above `Duplicates Collapsed` count UNIQUE (non-DUPLICATE) issues only — this excludes collapsed duplicates from `Fixed`, `False Positive`, and `Deferred`, de-skewing the fp\_ratio convergence formula in code-review without any parser change.)_ ## Verification | Command | Result | @@ -625,6 +631,11 @@ Final gate: PASS | FAILED after {n} attempts |-------|-----------|---------| | {description} | {file}:{line} | {why} | +## Duplicates +| Issue | Duplicate Of | File:Line | +|-------|-------------|-----------| +| {description} | {primary-id} | {file}:{line} | + ## Third-Party Threads | Thread | File:Line | Verdict | Status | |--------|-----------|---------|--------| @@ -633,4 +644,4 @@ Final gate: PASS | FAILED after {n} attempts _(Omit `## Third-Party Threads` if `COMPLIANCE_SKILL_INSTALLED` is false or no external threads were found.)_ -**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 the `Deferred`, `Fixed`, and `False Positive` Statistics rows plus the `## Fixed Issues` / `## False Positives` headings — keep those labels byte-stable. +**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. diff --git a/tests/build-mds.test.ts b/tests/build-mds.test.ts index b57c12d4..554bd4b0 100644 --- a/tests/build-mds.test.ts +++ b/tests/build-mds.test.ts @@ -1157,6 +1157,64 @@ describe('Phase D traceability ops — resolve.md (Part 2, Step 2.4)', () => { }); }); +// --------------------------------------------------------------------------- +// §16b DUPLICATE verdict guards — resolve.md +// Pins the DUPLICATE triage bucket, duplicate_of reference attribute, +// Duplicates Collapsed Statistics row, and ## Duplicates section. +// All counts in existing Statistics rows now apply to UNIQUE (non-DUPLICATE) +// issues only — this is the byte-stable contract verified here. +// --------------------------------------------------------------------------- + +describe('DUPLICATE verdict guards — resolve.md (§16b)', () => { + let compiled: string; + + beforeAll(async () => { + const result = spawnSync('npx', ['tsx', path.join(ROOT, 'scripts', 'build-mds.ts')], { + cwd: ROOT, + encoding: 'utf-8', + timeout: 60_000, + }); + if (result.error) throw result.error; + expect( + result.status, + `build-mds.ts should exit 0 but exited ${result.status}.\nstdout: ${result.stdout}\nstderr: ${result.stderr}`, + ).toBe(0); + compiled = await fs.readFile(path.join(ROOT, DIST_COMMANDS, 'resolve.md'), 'utf-8'); + expect(compiled.length, 'resolve.md must be non-empty').toBeGreaterThan(0); + }); + + it('resolve.md contains DUPLICATE as a named verdict bucket', () => { + expect( + compiled, + 'resolve.md must name DUPLICATE as a verdict bucket (avoids PF-024 spawn↔op seam)', + ).toContain('DUPLICATE'); + }); + + it('resolve.md contains duplicate_of reference attribute for DUPLICATE entries', () => { + expect( + compiled, + 'resolve.md must contain duplicate_of — the per-entry attribute Triage must supply for every DUPLICATE verdict', + ).toContain('duplicate_of'); + }); + + it('resolve.md contains the Duplicates Collapsed Statistics row label (parser-contract additive extension)', () => { + // This row is additive — existing labels (Fixed, False Positive, Deferred) are unchanged. + // The convergence parser in code-review.md reads only those existing labels, so this + // new row does not break it while ensuring DUPLICATE counts surface in the artifact. + expect( + compiled, + 'resolve.md must contain "| Duplicates Collapsed | " Statistics row label', + ).toContain('| Duplicates Collapsed | '); + }); + + it('resolve.md contains ## Duplicates section for per-entry traceability', () => { + expect( + compiled, + 'resolve.md must contain ## Duplicates section (additive, safe per ADR-006)', + ).toContain('## Duplicates'); + }); +}); + // --------------------------------------------------------------------------- // §17 Phase E traceability — implement.mds (2.5) + plan.mds (2.6) guards // implement.md: ISSUE_NUMBER in Code-agent spawns, COMPLIANCE in Git spawn From c381db36dcc78894d2be4388b1433eaf6e19bba7 Mon Sep 17 00:00:00 2001 From: Dean Sharon Date: Fri, 28 Aug 2026 01:46:20 +0300 Subject: [PATCH 2/7] refactor(resolve): tighten DUPLICATE verdict prose MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove filler word ("immediately") from triage.md pre-pass rule. In resolve.mds: simplify the completeness-invariant sentence for DUPLICATE entries, drop the redundant "excluded from all batches" clause (implied by "never dispatched"), and condense the PF-024 parenthetical in Phase 9b-1. No behaviour or contract change — all §16b parser-contract literals preserved. --- src/assets/agents/triage.md | 2 +- src/assets/commands/resolve.mds | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/assets/agents/triage.md b/src/assets/agents/triage.md index ade5381e..6df9ff3d 100644 --- a/src/assets/agents/triage.md +++ b/src/assets/agents/triage.md @@ -39,7 +39,7 @@ Run this pre-pass **before** the disposition matrix. It is a relation between is 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. 2. **Select primary**: from each group, designate as primary the most specific and complete report. All other members are non-primary duplicates. 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. -4. **Non-primary members**: assign verdict **DUPLICATE** immediately with `duplicate_of: `. Never chain — `duplicate_of` must reference a non-DUPLICATE issue. A DUPLICATE inherits its primary's outcome. +4. **Non-primary members**: assign verdict **DUPLICATE** with `duplicate_of: `. Never chain — `duplicate_of` must reference a non-DUPLICATE issue. A DUPLICATE inherits its primary's outcome. 5. **Single-member groups**: if an issue has no duplicates it is its own primary — apply the matrix directly. Apply the disposition matrix to each group's **primary only**. diff --git a/src/assets/commands/resolve.mds b/src/assets/commands/resolve.mds index 8d8786c6..752afcc7 100644 --- a/src/assets/commands/resolve.mds +++ b/src/assets/commands/resolve.mds @@ -186,7 +186,7 @@ Wait for Triage agent to complete before proceeding. Parse verdict ledger from T Collect all decisions citations (ADR-NNN / PF-NNN) from Triage agent Reasoning columns. **Triage agent completeness assertion (avoids PF-002):** Verify the parsed ledger against ISSUES before proceeding: -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 DUPLICATE entry satisfies this invariant when it carries a valid `duplicate_of` referencing a non-DUPLICATE issue id. If any DUPLICATE entry is missing `duplicate_of`, or if `duplicate_of` points to another DUPLICATE, treat it as a **Triage agent failure** (same retry-then-abort handling below). +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 carry `duplicate_of` pointing to 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). 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**: - Retry the Triage agent once with the same inputs. - 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. @@ -200,7 +200,7 @@ Collect all decisions citations (ADR-NNN / PF-NNN) from Triage agent Reasoning c 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). -Otherwise, batch FIX_NOW issues for Code agent execution. **DUPLICATE issues are never dispatched** — they inherit the primary's outcome and are excluded from all batches: +Otherwise, batch FIX_NOW issues for Code agent execution. **DUPLICATE issues are never dispatched** — they inherit the primary's outcome: - **Same-file issues** → one batch (one Code agent per file, sequential for same-file pairs) - **Distinct-file issues** → parallel Code agents - **Max 5 issues per batch** — chunk large sets @@ -360,7 +360,7 @@ Skip this step if `COMPLIANCE_SKILL_INSTALLED` is false or THREAD_MAP is empty. Prepare THREAD_MAP with verdicts from triage/code agent results: - For each `ext-\{N\}`: match to an issue verdict (FIXED, FALSE_POSITIVE, BY_DESIGN, ESCALATED) by `file:line` correlation -- 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 only, no change to git.md operation contracts) +- 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) - Include `commit_sha` from Code agent results for FIXED verdicts - Unmatched threads: ESCALATED (human review) From 68193b664db6514a00096bdb69ad380ab94de0d0 Mon Sep 17 00:00:00 2001 From: Dean Sharon Date: Fri, 28 Aug 2026 01:52:36 +0300 Subject: [PATCH 3/7] fix(resolve): close DUPLICATE verdict seams and section-exclusivity gaps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Self-review fixes on the DUPLICATE triage verdict: - triage.md: responsibility 3 said "apply the matrix" to every issue, contradicting the pre-pass ("matrix applies to primaries only") and omitting DUPLICATE from ledger completeness. Reworded in place. - triage.md: a group mixing security and non-security findings could elect the non-security report as primary (it may be the "most specific and complete"), handing the Code agent a style description for a security defect. Security member is now always the primary; the whole-group Security Gate remains the backstop. - resolve.mds: Phase 5 never stated that DUPLICATE rows are exclusive to ## Duplicates. Without it, a duplicate of a FALSE_POSITIVE primary could be listed in ## False Positives while the unique-only Statistics row counted just the primary -- section and row disagreeing, and git.md manage-debt reading duplicates out of ## Fix Separately. - resolve.mds: the Statistics note swept `Total Issues` into the unique-only rule, so Total would have excluded collapsed duplicates and stopped equalling the sum of its rows. Total now counts every triaged issue; all other rows stay unique-only. - resolve.mds: completeness assertion demanded a `duplicate_of` field while the ledger emits a `Duplicate Of` column -- an enum/field-name mismatch across the spawn/op seam whose failure mode is a hard abort (avoids PF-024). Assertion now names the ledger surface. - resolve.mds: Triage spawn directive named only the matrix; now names the collapse-then-matrix order it parses a DUPLICATE bucket from. - tests: new tests/resolve/duplicate-verdict.test.ts pins the producer side of the seam (build-mds §16b pinned only the caller side, so the agent could stop emitting the bucket with every guard green). Byte-stable parser contract untouched: Fixed / False Positive / Deferred row labels and ## Fixed Issues / ## False Positives column layouts are unchanged (applies ADR-006, additive-only). --- src/assets/agents/triage.md | 4 +- src/assets/commands/resolve.mds | 8 +- tests/resolve/duplicate-verdict.test.ts | 97 +++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 5 deletions(-) create mode 100644 tests/resolve/duplicate-verdict.test.ts diff --git a/src/assets/agents/triage.md b/src/assets/agents/triage.md index 6df9ff3d..b9384181 100644 --- a/src/assets/agents/triage.md +++ b/src/assets/agents/triage.md @@ -28,7 +28,7 @@ You receive from orchestrator: 1. **Read context per issue**: For each issue, Read 30 lines around the reported file:line to understand the actual code. 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. -3. **Assign disposition**: Apply the blast-radius matrix below. Every issue gets exactly one verdict — none may vanish. +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. 4. **Document evidence**: FALSE_POSITIVE requires cited grep/file:line. BY_DESIGN requires an ADR or inline comment/doc citation. 5. **Assign risk tier**: For every FIX_NOW issue, annotate Standard or Careful. @@ -37,7 +37,7 @@ You receive from orchestrator: Run this pre-pass **before** the disposition matrix. It is a relation between issues, not a matrix row. 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. -2. **Select primary**: from each group, designate as primary the most specific and complete report. All other members are non-primary duplicates. +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, the security member is always the primary. All other members are non-primary duplicates. 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. 4. **Non-primary members**: assign verdict **DUPLICATE** with `duplicate_of: `. Never chain — `duplicate_of` must reference a non-DUPLICATE issue. A DUPLICATE inherits its primary's outcome. 5. **Single-member groups**: if an issue has no duplicates it is its own primary — apply the matrix directly. diff --git a/src/assets/commands/resolve.mds b/src/assets/commands/resolve.mds index 752afcc7..9ec44533 100644 --- a/src/assets/commands/resolve.mds +++ b/src/assets/commands/resolve.mds @@ -169,7 +169,7 @@ WORKTREE_PATH: {worktree_path} (omit if cwd) DECISIONS_CONTEXT: {decisions_context} FEATURE_KNOWLEDGE: {feature_knowledge} PR_DESCRIPTION: {pr_description} -Triage every issue using the blast-radius disposition matrix. Assign exactly one verdict per issue. +Triage every issue: collapse duplicates first, then apply the blast-radius disposition matrix to each group's primary. Assign exactly one verdict per issue. Follow devflow:apply-decisions to Read full ADR/PF bodies on demand. Follow devflow:apply-feature-knowledge for FEATURE_KNOWLEDGE." ``` @@ -186,7 +186,7 @@ Wait for Triage agent to complete before proceeding. Parse verdict ledger from T Collect all decisions citations (ADR-NNN / PF-NNN) from Triage agent Reasoning columns. **Triage agent completeness assertion (avoids PF-002):** Verify the parsed ledger against ISSUES before proceeding: -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 carry `duplicate_of` pointing to 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). +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). 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**: - Retry the Triage agent once with the same inputs. - 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. @@ -243,6 +243,8 @@ Collect from each Code agent: Set `Tracked` for FIX_SEPARATE and TECH_DEBT items to `(pending)` — to be backfilled after Phase 9 manage-debt. +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. + Use the template from the Output Artifact section below. ### Phase 6: Simplify @@ -585,7 +587,7 @@ Written in Phase 5 (Collect Results) to `\{TARGET_DIR\}/resolution-summary.md`: | Escalated | {n} | | Duplicates Collapsed | {n} | -_(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. All rows above `Duplicates Collapsed` count UNIQUE (non-DUPLICATE) issues only — this excludes collapsed duplicates from `Fixed`, `False Positive`, and `Deferred`, de-skewing the fp\_ratio convergence formula in code-review without any parser change.)_ +_(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 other row above `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.)_ ## Verification | Command | Result | diff --git a/tests/resolve/duplicate-verdict.test.ts b/tests/resolve/duplicate-verdict.test.ts new file mode 100644 index 00000000..71962ea4 --- /dev/null +++ b/tests/resolve/duplicate-verdict.test.ts @@ -0,0 +1,97 @@ +import { describe, it, expect } from 'vitest' +import { loadFile, extractSection } from '../helpers' + +// ------------------------------------------------------------------------- +// triage.md — DUPLICATE verdict contract (agent side of the PF-024 seam) +// +// build-mds.test.ts §16b pins the DUPLICATE literals on the *caller* side +// (compiled dist/commands/resolve.md). Those guards stay green even if the +// Triage agent stops emitting the bucket the caller parses — the pipeline +// would then abort on every run with a completeness failure and no test +// would say why. This file pins the producer side of the same seam. +// +// Read target: src/assets/agents/triage.md (source of truth; agents install +// directly with no build step, so there is no compiled artifact to read). +// ------------------------------------------------------------------------- + +const TRIAGE = loadFile('src/assets/agents/triage.md') + +describe('triage.md — duplicate grouping pre-pass', () => { + it('is non-vacuous', () => { + expect(TRIAGE.length).toBeGreaterThan(1000) + }) + + it('declares the pre-pass ahead of the disposition matrix', () => { + const prePass = TRIAGE.indexOf('## Duplicate Grouping Pre-Pass') + const matrix = TRIAGE.indexOf('## Blast-Radius Disposition Matrix') + expect(prePass, 'pre-pass section heading must exist').toBeGreaterThan(-1) + expect(matrix, 'matrix section heading must exist').toBeGreaterThan(-1) + expect( + prePass, + 'the pre-pass must precede the matrix — it selects which issues the matrix runs on', + ).toBeLessThan(matrix) + }) + + it('forbids duplicate_of chaining', () => { + const section = extractSection(TRIAGE, '## Duplicate Grouping Pre-Pass', '## Blast-Radius') + expect( + section, + 'duplicate_of must be pinned to a non-DUPLICATE primary — a chain makes outcome inheritance unresolvable', + ).toContain('must reference a non-DUPLICATE issue') + }) + + it('applies the security gate to the whole group, not the primary alone', () => { + const section = extractSection(TRIAGE, '## Duplicate Grouping Pre-Pass', '## Blast-Radius') + expect( + section, + 'a security member must not be collapsed into a non-security primary without the gate applying', + ).toContain('Security Gate') + expect( + section, + 'a mixed security/non-security group must promote the security member to primary', + ).toContain('the security member is always the primary') + }) + + it('restricts the matrix to group primaries', () => { + const section = extractSection(TRIAGE, '## Duplicate Grouping Pre-Pass', '## Blast-Radius') + expect(section).toContain("primary only") + }) +}) + +describe('triage.md — DUPLICATE verdict ledger', () => { + const output = extractSection(TRIAGE, '## Output', '## Boundaries') + + it('emits a DUPLICATE bucket with a Duplicate Of column', () => { + expect(output, 'ledger must carry a ### DUPLICATE bucket').toContain('### DUPLICATE') + expect( + output, + 'the DUPLICATE table must name its primary — the caller resolves inherited outcomes through it', + ).toContain('Duplicate Of') + }) + + it('counts DUPLICATE in the summary tally', () => { + expect( + output, + 'the summary tally is the caller completeness check — DUPLICATE must be counted there', + ).toContain('- DUPLICATE: {n}') + }) +}) + +describe('DUPLICATE seam — triage.md producer ↔ resolve.md consumer', () => { + const RESOLVE = loadFile('src/assets/commands/resolve.mds') + + it('both sides name the same verdict and reference attribute', () => { + for (const literal of ['DUPLICATE', 'duplicate_of']) { + expect(TRIAGE, `triage.md must produce ${literal}`).toContain(literal) + expect(RESOLVE, `resolve.mds must consume ${literal}`).toContain(literal) + } + }) + + it('resolve.mds keeps DUPLICATE out of every non-Duplicates summary section', () => { + expect( + RESOLVE, + 'without this rule a duplicate of a FALSE_POSITIVE primary would be listed in ## False Positives ' + + 'while the Statistics row counted only the primary — section and row would disagree', + ).toContain('DUPLICATE issues are listed **only** in `## Duplicates`') + }) +}) From 5f7cff370712babd84d420676aa2d4798f3272eb Mon Sep 17 00:00:00 2001 From: Dean Sharon Date: Fri, 28 Aug 2026 02:01:17 +0300 Subject: [PATCH 4/7] docs(resolve): propagate DUPLICATE verdict to docs and changelog; disambiguate statistics note MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - docs/commands.md: append DUPLICATE to the disposition enumeration (step 1) and note in step 7 that duplicate cross-reviewer reports are collapsed so Statistics counts reflect unique issues - CHANGELOG.md: add ### Changed entry under [Unreleased] describing the new DUPLICATE verdict and its effect on resolution-summary counts - resolve.mds: reword the ambiguous Statistics note — "every other row above Duplicates Collapsed" → "every row between Total Issues and Duplicates Collapsed" to make clear that Total Issues itself is not a unique-only count Co-Authored-By: Claude --- CHANGELOG.md | 3 +++ docs/commands.md | 4 ++-- src/assets/commands/resolve.mds | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5252e4c..2e58c424 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed +- **`/resolve` DUPLICATE verdict**: `/resolve` now collapses duplicate cross-reviewer findings via a new `DUPLICATE` triage verdict — resolution-summary counts unique issues, with a `Duplicates Collapsed` statistics row and a `## Duplicates` section for traceability. + --- ## [2.2.0] - 2026-08-25 diff --git a/docs/commands.md b/docs/commands.md index 50aafff2..7353852b 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -73,13 +73,13 @@ Applies to `/code-review` and `/resolve`. On public repositories, Devflow posts Processes all issues from `/code-review` reports through a validation/fix split: -1. **Triage** — A single Triage agent (opus) applies the blast-radius disposition matrix to every issue: FIX_NOW / FALSE_POSITIVE / BY_DESIGN / FIX_SEPARATE / TECH_DEBT / ESCALATED +1. **Triage** — A single Triage agent (opus) applies the blast-radius disposition matrix to every issue: FIX_NOW / FALSE_POSITIVE / BY_DESIGN / FIX_SEPARATE / TECH_DEBT / ESCALATED / DUPLICATE 2. **Fix** — Parallel Code agents (sonnet) fix only FIX_NOW issues using Standard or Careful protocols 3. **Verify** — A Validate agent (haiku) gate runs build/typecheck/lint/test; up to 2 fix-retry cycles; single push fires after this gate (pass or fail) 4. **CI Gate** — Check PR CI status (conditional — skipped if no fixes or Verification Gate failed) 5. **Manage Debt** — FIX_SEPARATE and TECH_DEBT items become tracked manage-debt tickets 6. **Resolution Comment** — Post the resolution summary as a single consolidated PR comment (marker-deduped — skipped if already posted; always runs when a PR is known) -7. **Report** — Write resolution summary with Verification, By Design, Fix Separately, and Escalations sections +7. **Report** — Write resolution summary with Verification, By Design, Fix Separately, and Escalations sections; duplicate cross-reviewer reports are collapsed via the DUPLICATE verdict so Statistics counts reflect unique issues ``` /resolve # Resolve latest review (or all worktrees) diff --git a/src/assets/commands/resolve.mds b/src/assets/commands/resolve.mds index 9ec44533..17eb98e6 100644 --- a/src/assets/commands/resolve.mds +++ b/src/assets/commands/resolve.mds @@ -587,7 +587,7 @@ Written in Phase 5 (Collect Results) to `\{TARGET_DIR\}/resolution-summary.md`: | Escalated | {n} | | Duplicates Collapsed | {n} | -_(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 other row above `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.)_ +_(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.)_ ## Verification | Command | Result | From ca575f3ade57977d5f80524df8ad5ec9d8165b75 Mon Sep 17 00:00:00 2001 From: Dean Sharon Date: Fri, 28 Aug 2026 02:09:03 +0300 Subject: [PATCH 5/7] docs(resolve): clarify security-member definition and chained-DUPLICATE edge case In triage.md Duplicate Grouping Pre-Pass step 2, add a parenthetical tying 'security member' to the Security Gate so the term is grounded on first use in the pre-pass. In resolve.mds Edge Cases table, extend the DUPLICATE-without-duplicate_of row label to also name the chained-to-another-DUPLICATE case, matching the Phase 2 completeness-assertion prose at ~line 189. Co-Authored-By: Claude --- src/assets/agents/triage.md | 2 +- src/assets/commands/resolve.mds | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/agents/triage.md b/src/assets/agents/triage.md index b9384181..8417b8e2 100644 --- a/src/assets/agents/triage.md +++ b/src/assets/agents/triage.md @@ -37,7 +37,7 @@ You receive from orchestrator: Run this pre-pass **before** the disposition matrix. It is a relation between issues, not a matrix row. 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. -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, the security member is always the primary. All other members are non-primary duplicates. +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. 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. 4. **Non-primary members**: assign verdict **DUPLICATE** with `duplicate_of: `. Never chain — `duplicate_of` must reference a non-DUPLICATE issue. A DUPLICATE inherits its primary's outcome. 5. **Single-member groups**: if an issue has no duplicates it is its own primary — apply the matrix directly. diff --git a/src/assets/commands/resolve.mds b/src/assets/commands/resolve.mds index 17eb98e6..cb2d1ef1 100644 --- a/src/assets/commands/resolve.mds +++ b/src/assets/commands/resolve.mds @@ -533,7 +533,7 @@ In multi-worktree mode, report results per worktree with aggregate summary. | Worktree pre-flight fails | Report failure, continue with other worktrees | | Empty FIX_NOW list | Skip Phases 3-4/6-8; still write full summary + run manage-debt if FIX_SEPARATE/TECH_DEBT exist | | ESCALATED security issues | Surfaced in ## Escalations + display callout; never routed to manage-debt | -| DUPLICATE verdict without duplicate_of | Treated as Triage failure — same retry-then-abort as a vanished id | +| DUPLICATE verdict without duplicate_of, or chained to another DUPLICATE | Treated as Triage failure — same retry-then-abort as a vanished id | | DUPLICATE issues in THREAD_MAP | Map ext-\{N\} to primary's verdict/verification status for thread reply | | 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) | | gh/GitHub absent | manage-debt fails gracefully; Tracked stays "(pending)" + noted — recorded, not dropped | From 4c8c59be8834c4d3f54d0fbef9d49a3844b13724 Mon Sep 17 00:00:00 2001 From: Dean Sharon Date: Fri, 28 Aug 2026 02:12:51 +0300 Subject: [PATCH 6/7] docs(resolve): reword duplicate-collapse note to satisfy retired-agent-name guard 'cross-reviewer' contains 'reviewer' which trips the case-insensitive retired-name scan for the pre-PR-#253 'Reviewer' agent name. Reword to 'multiple Review agents' which carries the same meaning and passes the guard. --- docs/commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/commands.md b/docs/commands.md index 7353852b..8136d18f 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -79,7 +79,7 @@ Processes all issues from `/code-review` reports through a validation/fix split: 4. **CI Gate** — Check PR CI status (conditional — skipped if no fixes or Verification Gate failed) 5. **Manage Debt** — FIX_SEPARATE and TECH_DEBT items become tracked manage-debt tickets 6. **Resolution Comment** — Post the resolution summary as a single consolidated PR comment (marker-deduped — skipped if already posted; always runs when a PR is known) -7. **Report** — Write resolution summary with Verification, By Design, Fix Separately, and Escalations sections; duplicate cross-reviewer reports are collapsed via the DUPLICATE verdict so Statistics counts reflect unique issues +7. **Report** — Write resolution summary with Verification, By Design, Fix Separately, and Escalations sections; duplicate findings reported by multiple Review agents are collapsed via the DUPLICATE verdict so Statistics counts reflect unique issues ``` /resolve # Resolve latest review (or all worktrees) From 1c124404df8aebacbc4e975389950d5c9c104dd2 Mon Sep 17 00:00:00 2001 From: Dean Sharon Date: Fri, 28 Aug 2026 02:19:31 +0300 Subject: [PATCH 7/7] docs(knowledge): update resolve-pipeline feature knowledge base MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds DUPLICATE verdict (7th bucket), duplicate grouping pre-pass, section exclusivity rules, fp_ratio de-skewing, and §16b/duplicate-verdict.test.ts test guard coverage for PR #307 (feat/resolve-duplicate-verdicts). --- .devflow/features/index.md | 2 +- .../features/resolve-pipeline/KNOWLEDGE.md | 98 ++++++++++++++++--- 2 files changed, 87 insertions(+), 13 deletions(-) diff --git a/.devflow/features/index.md b/.devflow/features/index.md index efcd29a9..43088fd2 100644 --- a/.devflow/features/index.md +++ b/.devflow/features/index.md @@ -1,7 +1,7 @@ - **feature-knowledge-system** — src/assets/commands/_partials, src/cli/commands/knowledge, src/assets/skills/feature-knowledge, src/assets/skills/apply-feature-knowledge, src/assets/agents/knowledge.md, scripts/build-mds.ts — Use when adding a new knowledge base entry, modifying how knowledge is loaded into agents, changing the write-through save model, extending the CLI knowledge commands, or understanding the MDS knowledge module. - **ambient-orchestrator** — src/assets/scripts/hooks, src/cli/commands/ambient.ts, src/core/plugins.ts — Use when modifying the ambient mode hooks (preamble, session-start-orchestrator), the orchestrator charter file (including the feature-knowledge operating rule), the git-marker helper, the ambient CLI toggle, or the plan-handoff fast-path. Keywords: ambient, preamble, orchestrator, charter, plan-handoff, session-start-orchestrator, git-marker, DEVFLOW_BG_UPDATER, devflow ambient, UserPromptSubmit, SessionStart, feature-knowledge. - **dynamic-workflow-engine** — src/assets/commands/dynamic-build.mds, src/assets/commands/dynamic-plan.mds, src/assets/commands/dynamic-tickets.mds, src/assets/commands/dynamic-profile.mds, src/assets/commands/_partials/_engine.mds, src/assets/commands/_partials/_wave.mds, dist/commands, tests/build-mds.test.ts — Use when authoring or modifying the dynamic-* commands (dynamic-build, dynamic-plan, dynamic-tickets, dynamic-profile), the shared engine/wave/preamble/factory MDS partials, or the build-mds test suite that pins doctrine literals. Keywords: dynamic-build, dynamic-plan, dynamic-tickets, dynamic-profile, Workflow tool, agentType, Gate 1, Gate 2, review pass, wave, tickets→plan→build, MDS, _engine.mds, _wave.mds. -- **resolve-pipeline** — src/assets/commands/resolve.mds, src/assets/agents/triage.md, src/assets/agents/code.md, src/core/plugins.ts, src/assets/commands/code-review.mds — Use when modifying /resolve or /code-review convergence logic, adding or changing Triage disposition rules, adjusting Code-agent operating modes (issue-fix/validation-fix), touching the resolution-summary.md parser contract, changing the Verification Gate retry loop, understanding how DIFF_FILES flows from git validate-branch into blast-radius triage, or working on traceability operations (fetch-review-threads, resolve-review-threads, post-resolution-summary, check-merge-readiness, THREAD_MAP). Keywords: resolve, triage, disposition matrix, blast-radius, FIX_NOW, FIX_SEPARATE, TECH_DEBT, FALSE_POSITIVE, BY_DESIGN, ESCALATED, resolution-summary, convergence parser, DIFF_FILES, issue-fix, validation-fix, Verification Gate, manage-debt, COMPLIANCE_SKILL_INSTALLED, TRACEABILITY DEGRADED, fetch-review-threads, THREAD_MAP, post-resolution-summary, Third-Party Threads, check-merge-readiness, ext-N, D7, D9, PF-024. +- **resolve-pipeline** — src/assets/commands/resolve.mds, src/assets/agents/triage.md, src/assets/agents/code.md, src/core/plugins.ts, src/assets/commands/code-review.mds — Use when modifying /resolve or /code-review convergence logic, adding or changing Triage disposition rules (including DUPLICATE collapsing), adjusting Code-agent operating modes (issue-fix/validation-fix), touching the resolution-summary.md parser contract, changing the Verification Gate retry loop, understanding how DIFF_FILES flows from git validate-branch into blast-radius triage, or working on traceability operations (fetch-review-threads, resolve-review-threads, post-resolution-summary, check-merge-readiness, THREAD_MAP). Keywords: resolve, triage, disposition matrix, blast-radius, FIX_NOW, FIX_SEPARATE, TECH_DEBT, FALSE_POSITIVE, BY_DESIGN, ESCALATED, DUPLICATE, duplicate-grouping, duplicates-collapse, duplicate_of, resolution-summary, convergence parser, DIFF_FILES, issue-fix, validation-fix, Verification Gate, manage-debt, COMPLIANCE_SKILL_INSTALLED, TRACEABILITY DEGRADED, fetch-review-threads, THREAD_MAP, post-resolution-summary, Third-Party Threads, check-merge-readiness, ext-N, D7, D9, PF-024. - **installer-shadowing** — src/targets/claude-code/installer.ts, src/targets/claude-code/legacy.ts, src/cli/commands/init.ts, src/cli/commands/init-seed.ts, src/cli/commands/uninstall.ts, src/cli/commands/rules.ts, src/cli/commands/skills.ts, src/cli/commands/flags.ts, src/cli/flags-view, src/cli/tui, src/core/plugins.ts, src/core/assets.ts, src/core/paths.ts, src/core/manifest.ts, src/core/flags.ts, src/core/feature-config.ts, src/core/orphan-sweep.ts, src/core/migrations.ts, src/cli/commands/compliance-prompts.ts — Use when modifying the install pipeline (installViaFileCopy, installAllRules, composeScripts, InstallReport), adding or changing skill/rule shadow override logic, touching uninstall scope (enumerateUserDevFlowContent, removeDevFlowInstallArtifacts, resolveDevflowDirCleanup, installArtifactPaths, sweepDevflowNamespaces, resolveProjectDataCleanup) or install-artifact cleanup, extending the CLI skills/rules/flags management commands, working with asset directory accessors (rulesDir, skillsDir, commandsDir) and package-root resolution, modifying the init seeding layer (resolveInitSeed, resolveSeedFeatures, resolveSeedFlags, resolveSeedPlugins, --reset, FlagsRecord, knownPlugins, readConfigIfPresent, resolveExistingViewMode, getAllCommandNames, proxy), working on the flags TUI (FlagsViewState, FlagRow, buildFlagRows, collectFlagRecord, effectiveDisplay, blurb, inline mode, RunTuiSpec screen) or the flags CLI (createFlagsCommand, lookupFlag, persistFlagConfig, formatFlagValue), or working on the compliance wizard step (shouldRunComplianceStep, runComplianceStep, modePromptShown, CompliancePromptIO). Keywords: installViaFileCopy, installAllRules, composeScripts, InstallReport, RuleInstallOutcome, SkillShadowState, RuleShadowState, shadow, unshadow, validateSkillShadow, validateRuleShadow, seedRuleShadow, prefixSkillName, unprefixSkillName, devflow:, skills, rules, uninstall, EISDIR, enumerateUserDevFlowContent, removeDevFlowInstallArtifacts, resolveDevflowDirCleanup, installArtifactPaths, enumerateDryRunExtras, sweepDevflowNamespaces, resolveProjectDataCleanup, runDryRunPhase, runSelectivePhaseForScope, runFullPhaseForScope, runCleanupPhase, getPackageRoot, isContainedIn, rulesDir, skillsDir, agentsDir, commandsDir, scriptsDir, LEGACY_SKILL_NAMES, sweepOrphanedAssets, SweepResult, sweepOrphans, sweepFailures, SweepFailure, mdFileName, mdEntryName, orphan sweep, getAllSkillNames, getAllCommandNames, getAllAgentNames, DELETED_PLUGIN_NAMES, EXCLUDED, resolveInitSeed, resolveSeedFeatures, resolveSeedFlags, resolveSeedPlugins, resolveResetGatedInputs, applyCliToggles, FlagsRecord, FlagsRecordValue, getDefaultFlagsRecord, parseManifestFlags, migrateLegacyFlagsToRecord, sanitizeFlagsRecord, coerceFlagValue, parseFlagValueInput, neutralValueOf, isNeutral, countActiveFlags, readViewMode, knownPlugins, readConfigIfPresent, resolveExistingViewMode, resolveFinalViewMode, reset, init-seed, proxy, reapplyAgentMapping, revertExternalAgents, agent-models.json, proxy.json, proxy-routing.json, proxy.pid, applyDisableToSettings, buildRealPreflightDeps, canonicalise-agent-keys-v1, AnyMigration, migrations.json, compliance-prompts, shouldRunComplianceStep, CompliancePromptIO, runComplianceStep, modePromptShown, createFlagsCommand, lookupFlag, persistFlagConfig, FlagsViewState, FlagRow, buildFlagRows, collectFlagRecord, buildStops, cycleForward, cycleBackward, sanitizeCell, padToVisible, truncateVisible, effectiveDisplay, EffectiveDisplay, formatFlagValue, blurb, FlagDefCommon, INLINE_MARGIN, cursorUp, RunTuiSpec, screen, inline. - **learning-capture-system** — src/assets/scripts/hooks, src/assets/agents/learning.md, src/cli/commands/learning.ts, src/core/feature-config.ts, src/core/learning-tuning-config.ts, src/hud/components/learning-counts.ts, src/assets/commands/_partials — Use when modifying capture hooks (capture-prompt/capture-turn/capture-question), the learning or memory pending-turns queues, the Learning agent (src/assets/agents/learning.md), the session-start-context learning directive, the feature-config toggles, the learning tuning config, the decisions content files (decisions.md/pitfalls.md/index.md) or their ledger ops, or the devflow learning CLI. Keywords: capture-prompt, capture-turn, capture-question, queue-append, pending-turns, memory-worker, Learning agent, learning directive, LEARNING MAINTENANCE, DEVFLOW_BG_UPDATER, learning-lock, queue_read_gates, decisions_load, DECISIONS_CONTEXT, feature-config, config.json, learning.json, decisions-ledger, assign-anchor, retire-anchor, render-decisions. - **external-model-routing** — src/core/proxy-state.ts, src/core/external-models.ts, src/core/agent-models.ts, src/core/agent-state.ts, src/core/agent-frontmatter.ts, src/core/codex-auth-inspect.ts, src/core/model-discovery.ts, src/core/cache.ts, src/core/proxy-log.ts, src/cli/commands/proxy.ts, src/cli/commands/agents.ts, src/cli/agents-view, src/cli/tui — Use when working on the proxy lifecycle (enable/disable/status/preflight), the ensure-proxy hook, per-agent model mapping, agent frontmatter rewriting, or the agents TUI. Keywords: proxy, external-model-routing, GPT, agent-models, ensure-proxy, frontmatter, devflow proxy, devflow agents, subswitch, ANTHROPIC_BASE_URL, dormancy, reapplyAgentMapping. diff --git a/.devflow/features/resolve-pipeline/KNOWLEDGE.md b/.devflow/features/resolve-pipeline/KNOWLEDGE.md index 99eb60a9..1838896a 100644 --- a/.devflow/features/resolve-pipeline/KNOWLEDGE.md +++ b/.devflow/features/resolve-pipeline/KNOWLEDGE.md @@ -1,11 +1,11 @@ --- feature: resolve-pipeline name: Resolve Pipeline (Triage → Fix → Verify) -description: "Use when modifying /resolve or /code-review convergence logic, adding or changing Triage disposition rules, adjusting Code-agent operating modes (issue-fix/validation-fix), touching the resolution-summary.md parser contract, changing the Verification Gate retry loop, understanding how DIFF_FILES flows from git validate-branch into blast-radius triage, or working on traceability operations (fetch-review-threads, resolve-review-threads, post-resolution-summary, check-merge-readiness, THREAD_MAP). Keywords: resolve, triage, disposition matrix, blast-radius, FIX_NOW, FIX_SEPARATE, TECH_DEBT, FALSE_POSITIVE, BY_DESIGN, ESCALATED, resolution-summary, convergence parser, DIFF_FILES, issue-fix, validation-fix, Verification Gate, manage-debt, COMPLIANCE_SKILL_INSTALLED, TRACEABILITY DEGRADED, fetch-review-threads, THREAD_MAP, post-resolution-summary, Third-Party Threads, check-merge-readiness, ext-N, D7, D9, PF-024." +description: "Use when modifying /resolve or /code-review convergence logic, adding or changing Triage disposition rules (including DUPLICATE collapsing), adjusting Code-agent operating modes (issue-fix/validation-fix), touching the resolution-summary.md parser contract, changing the Verification Gate retry loop, understanding how DIFF_FILES flows from git validate-branch into blast-radius triage, or working on traceability operations (fetch-review-threads, resolve-review-threads, post-resolution-summary, check-merge-readiness, THREAD_MAP). Keywords: resolve, triage, disposition matrix, blast-radius, FIX_NOW, FIX_SEPARATE, TECH_DEBT, FALSE_POSITIVE, BY_DESIGN, ESCALATED, DUPLICATE, duplicate-grouping, duplicates-collapse, duplicate_of, resolution-summary, convergence parser, DIFF_FILES, issue-fix, validation-fix, Verification Gate, manage-debt, COMPLIANCE_SKILL_INSTALLED, TRACEABILITY DEGRADED, fetch-review-threads, THREAD_MAP, post-resolution-summary, Third-Party Threads, check-merge-readiness, ext-N, D7, D9, PF-024." category: architecture directories: [src/assets/commands/resolve.mds, src/assets/agents/triage.md, src/assets/agents/code.md, src/core/plugins.ts, src/assets/commands/code-review.mds] created: 2026-07-08 -updated: 2026-08-21 +updated: 2026-08-28 --- # Resolve Pipeline (Triage → Fix → Verify) @@ -16,6 +16,8 @@ updated: 2026-08-21 PR #288 added a traceability layer: compliance-gated phases (1b, 9b-1, 9c) fetch external review threads, resolve them post-push, and check merge readiness. Phase 9b-2 posts a resolution comment to the PR unconditionally when a PR is known — regardless of compliance installation. +PR #307 added a seventh verdict bucket — **DUPLICATE** — and a pre-pass that collapses same-defect issues before the blast-radius matrix runs. This de-skews the `fp_ratio` convergence formula and eliminates duplicate debt tickets without any change to the code-review.mds parser. + The pipeline was restructured in PR #253 to replace the former single `Resolver` agent (which both judged and fixed) with the Triage + Code-agent-as-fixer split. The retired `resolver` agent file is removed from installs by the registry-diff orphan sweep on `devflow init`. ## System Context @@ -49,15 +51,22 @@ Phase 0 Worktree Discovery & Pre-Flight Phase 1 Orchestrator parses issues → ISSUES (with reviewer_confidence %) Phase 1b Git agent (fetch-review-threads) → THREAD_MAP [compliance-gated] Phase 2 Single global Triage agent → verdict ledger (one verdict per issue, none vanish) + Duplicate pre-pass fires FIRST; matrix runs on group primaries only. + DUPLICATE is a valid bucket; missing duplicate_of or a chained DUPLICATE = Triage failure. Phase 3 Batch FIX_NOW issues → BATCHES (same-file sequential, distinct-file parallel, max 5/batch) + DUPLICATE issues are NEVER dispatched — they inherit the primary's outcome. Phase 4 Code × N (OPERATION: issue-fix, PUSH: false) → CODE_AGENT_RESULTS Phase 5 Write resolution-summary.md ← compaction safety; Tracked = "(pending)" + Includes new additive Statistics row "| Duplicates Collapsed | {n} |" + and new additive section "## Duplicates". DUPLICATE issues appear ONLY in ## Duplicates. Phase 6 Simplify (only if fixes were made) Phase 7 Validate gate (haiku) + Code validation-fix loop ≤ 2 + SINGLE push Phase 8 CI Status Gate (conditional — skipped if no fixes or Phase 7 FAILED) Phase 9 manage-debt (FIX_SEPARATE + TECH_DEBT → backfill Tracked = #N) [SEQUENTIAL] + DUPLICATE issues NEVER create their own debt tickets — covered by the primary's ticket. Phase 9b Thread Resolution + Resolution Comment Step 9b-1 Git agent (resolve-review-threads) [compliance-gated; D9 gate applies] + ext-{N} matching a DUPLICATE → use primary's verdict/verification status (caller-side mapping) Step 9b-2 Git agent (post-resolution-summary) [ALWAYS-ON when PR known] Phase 9c Git agent (check-merge-readiness) [compliance-gated, report-only] Phase 10 Display results @@ -83,6 +92,8 @@ Git agent's `validate-branch` operation emits a `### Diff Scope` block containin **Phase 9b-1 — resolve-review-threads** (compliance-gated, runs after Phase 9 backfill): Prepares THREAD_MAP with verdicts from Triage/Code results by matching `ext-{N}` to issues by file:line correlation. Unmatched threads default to ESCALATED (human review required). Then spawns Git agent with `OPERATION: resolve-review-threads`. +**DUPLICATE in THREAD_MAP**: If a matched issue has verdict DUPLICATE, the orchestrator uses the **primary's** verdict and verification status for the thread reply — the DUPLICATE verdict is never exposed to the thread author. This is a caller-side mapping; git.md contracts are unchanged (applies PF-024). + **D9 gate (single authority in git.md `## Operation: resolve-review-threads`):** - `resolveReviewThread` mutation is called **ONLY when `VERIFICATION_STATUS == PASS` AND verdict `FIXED` AND `commit_sha` non-empty** - `FALSE_POSITIVE` and `BY_DESIGN`: **reply-only** — devflow supplies cited evidence but does not call `resolveReviewThread`; the thread author closes their own thread @@ -96,9 +107,32 @@ When VERIFICATION_STATUS is FAILED or SKIPPED, the agent replies to every thread **TRACEABILITY: DEGRADED contract**: Any of no-PR, no-gh-auth, no-remote causes the Git agent to return `TRACEABILITY: DEGRADED ({reason})`. All traceability operations skip-and-continue on DEGRADED — they never fail the pipeline. +## Duplicate Grouping Pre-Pass + +The pre-pass is a relation between issues that runs **before** the blast-radius matrix and selects which issues the matrix runs on. It is not a matrix row — it is a pre-filter. + +**Algorithm** (from `triage.md`): + +1. **Group by same defect**: cluster issues sharing the same root cause — typically the same or adjacent file:line reported by different review foci, or the same logical error in different phrasings. +2. **Select primary**: designate the most specific and complete report as primary. In a mixed security/non-security group, **the security member is always primary** — security findings are never collapsed into non-security primaries. +3. **Security gate propagates 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), regardless of how many non-security members are in the group. +4. **Non-primary members**: receive verdict **DUPLICATE** with `duplicate_of: `. The `duplicate_of` reference must point to a **non-DUPLICATE** issue — chaining is prohibited (makes outcome inheritance unresolvable). +5. **Single-member groups**: each issue is its own primary; the matrix runs directly on it. +6. **Inheritance**: a DUPLICATE inherits its primary's final outcome (FIX_NOW → fixed by the Code agents that fix the primary; FALSE_POSITIVE → excluded from False Positives section/count; etc.). + +**Ledger output** (triage.md `### DUPLICATE` bucket): +``` +### DUPLICATE +| Issue ID | Duplicate Of | File:Line | Reason | +|----------|-------------|-----------|--------| +| {id} | {primary-id} | {file}:{line} | {same defect as {primary-id}, reported by {focus}} | +``` + +Summary tally gains `- DUPLICATE: {n}`. + ## Triage Blast-Radius Disposition Matrix -**First-match-wins. Apply in exact order.** +**First-match-wins. Apply in exact order. Matrix runs on group primaries only (after the pre-pass).** | Priority | Verdict | Condition | Evidence Required | |----------|---------|-----------|-------------------| @@ -111,11 +145,13 @@ When VERIFICATION_STATUS is FAILED or SKIPPED, the agent replies to every thread **ESCALATED**: Security issues that cannot be dismissed or deferred — surfaced in `## Escalations`, never routed to manage-debt. This is not a matrix position; it is the second branch of the Security Gate. +**DUPLICATE**: Assigned by the pre-pass to non-primary group members. Never a matrix output — the matrix never produces this verdict. + **Empty DIFF_FILES** (bug-analysis edge case): clause 3 degrades conservatively — Standard/isolated → FIX_NOW still applies, but the "file in DIFF_FILES" path is unavailable. Security gate is unaffected. **Risk tiers for FIX_NOW:** - **Standard**: null checks, validation, error handling, docs, type annotations, isolated security fixes — Code agent fixes directly -- **Careful**: public API, shared state, >3 files, core logic, multi-service interface, auth flow, multi-service interface, auth flow — Code agent uses understand → plan → test → implement → verify → commit protocol +- **Careful**: public API, shared state, >3 files, core logic, multi-service interface, auth flow — Code agent uses understand → plan → test → implement → verify → commit protocol ## Code Agent Operating Modes @@ -131,6 +167,7 @@ The Code agent has five modes selected by the `OPERATION` input: **issue-fix mode rules:** - Receives pre-classified FIX_NOW issues — never re-litigates Triage dispositions +- DUPLICATE issues are never dispatched; they inherit the primary's outcome - Same-file issues → one commit (never two Code agents editing the same file concurrently) - Regression fix without a failing-then-passing regression test = INCOMPLETE → report BLOCKED, do not commit - Returns: `{status, commitShas, unresolved}` + `## Verification` block @@ -139,7 +176,7 @@ The Code agent has five modes selected by the `OPERATION` input: ## Parser Coupling: resolution-summary.md ↔ /code-review -**This contract is UNCHANGED.** The byte-stable format for the convergence parser has not been modified by the PR #288 / review-fix wave. Do not alter labels, column order, or Statistics row names without updating the convergence parser in code-review.mds. +**This contract is UNCHANGED for the convergence parser.** The byte-stable format for the convergence parser has not been modified by the DUPLICATE addition. Do not alter labels, column order, or Statistics row names without updating the convergence parser in code-review.mds. The `/code-review` convergence detection reads `resolution-summary.md` to compute `fp_ratio` for multi-cycle reviews. @@ -164,12 +201,23 @@ The section headings and their column layouts: **fp_ratio formula**: `fp_count / (fp_count + fixed_count + deferred_count)` - `Deferred` row = FIX_SEPARATE + TECH_DEBT combined - By Design and Escalated are **excluded from the denominator** +- DUPLICATE issues are **excluded from all three terms** — all Statistics rows that the parser reads count UNIQUE (non-DUPLICATE) issues only, so collapsed duplicates do not inflate fp_ratio - fp_ratio > 0.7 AND CYCLE_NUMBER >= 3 → convergence warning emitted -**Safe additions**: New sections (`## Escalations`, `## Blocked`, `## By Design`, `## Third-Party Threads`) are strictly additive — the parser reads specific rows and headings by label, and new material does not break it. +**Counting semantics** (resolution-summary.md note): +- `Total Issues` counts every triaged issue **including** collapsed duplicates +- Every row **between** `Total Issues` and `Duplicates Collapsed` counts UNIQUE (non-DUPLICATE) issues only +- `Total Issues` therefore equals the sum of the rows below it + +**Safe additions** (new elements that do not break the convergence parser): +- `## Escalations`, `## Blocked`, `## By Design`, `## Third-Party Threads` sections +- `| Duplicates Collapsed | {n} |` Statistics row (additive; code-review.mds parser unchanged) +- `## Duplicates` section with `| Issue | Duplicate Of | File:Line |` columns (additive) **Unsafe changes**: Renaming `Fixed` → `Resolved`, splitting `Deferred` into two rows, changing `False Positive` to `False Positives`, restructuring the Statistics table format. +**Section exclusivity for DUPLICATE**: DUPLICATE issues appear **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 leaves only the primary in the `False Positive` Statistics row and `## False Positives` section; the duplicate appears in `## Duplicates` only. This is what keeps manage-debt from creating tickets for duplicates. + ## Code-Review Phase 3: Sequential Synthesis + Comment `/code-review` Phase 3 runs two steps **sequentially per worktree** (step 3b cannot start until step 3a completes): @@ -201,10 +249,12 @@ The Triage agent (opus) is the sole judgment agent. Key constraints in `triage.m - Skills preloaded in frontmatter: `devflow:security`, `devflow:worktree-support`, `devflow:apply-decisions`, `devflow:apply-feature-knowledge` - **Never instructed to invoke skills via body text** (avoids PF-002 re-entrancy issue) - Reads 30-line context around each reported file:line to verify issues +- **Runs the Duplicate Grouping Pre-Pass first** — groups same-defect issues, elects primaries (security member is always primary in mixed groups), then runs the matrix on primaries only - For FALSE_POSITIVE: must provide grep output or file:line citation — opinion is not evidence - For BY_DESIGN: must cite an ADR or inline comment/doc — gut feeling is not a citation - For ESCALATED: security findings with ambiguous context go here rather than FALSE_POSITIVE -- Output is a verdict ledger grouped by disposition with a Summary section +- For DUPLICATE: `duplicate_of` must reference a non-DUPLICATE issue — never chained +- Output is a verdict ledger grouped by disposition (7 buckets including DUPLICATE) with a Summary section **Triage output is consumed by the orchestrator, not by Code agents.** The Triage agent never spawns sub-agents. @@ -226,7 +276,7 @@ The single `git push` runs after the Verification Gate regardless of PASS or FAI ## Test Guards -Three test files provide static content guards that fail loudly when load-bearing literals are silently changed (avoids PF-018): +Four test files provide static content guards that fail loudly when load-bearing literals are silently changed (avoids PF-018): **`tests/git-agent.test.ts`** (source-file guards, no build required): - Guard 0: file non-vacuousness @@ -248,6 +298,22 @@ Three test files provide static content guards that fail loudly when load-bearin - Every `beforeAll` block in §15 asserts the build exits 0 before the file-content checks run - Every scan loop asserts `scanned > 0` to prevent vacuous passes +**`tests/build-mds.test.ts §16b`** (build-gated, DUPLICATE verdict guards — consumer side): +- Pins `DUPLICATE` as a named verdict bucket in compiled `resolve.md` (avoids PF-024 spawn↔op seam) +- Pins `duplicate_of` reference attribute in compiled `resolve.md` — the per-entry attribute Triage must supply for every DUPLICATE verdict +- Pins `| Duplicates Collapsed | ` Statistics row label — additive extension; existing parser labels unchanged +- Pins `## Duplicates` section heading in compiled `resolve.md` — additive, safe per ADR-006 + +**`tests/resolve/duplicate-verdict.test.ts`** (source-file guards — producer side): +- Guards the duplicate grouping pre-pass ordering: `## Duplicate Grouping Pre-Pass` must appear before `## Blast-Radius Disposition Matrix` in `triage.md` +- Guards chaining prohibition: pre-pass section must contain "must reference a non-DUPLICATE issue" +- Guards security-primary election: pre-pass section must contain "the security member is always the primary" and "Security Gate" +- Guards matrix scope: pre-pass section must contain "primary only" +- Guards DUPLICATE ledger bucket: `### DUPLICATE` heading and `Duplicate Of` column must appear in triage.md Output section +- Guards summary tally: `- DUPLICATE: {n}` must appear in triage.md Output section +- Guards two-sided PF-024 seam: both `triage.md` and `resolve.mds` must contain `DUPLICATE` and `duplicate_of` +- Guards section exclusivity: `resolve.mds` must contain "DUPLICATE issues are listed **only** in `## Duplicates`" + ## Anti-Patterns - **Routing ESCALATED issues to manage-debt**: Security escalations that require human review must appear in `## Escalations` with a display callout. manage-debt would bury them in a ticket backlog with no visibility. @@ -259,12 +325,16 @@ Three test files provide static content guards that fail loudly when load-bearin - **Blocking on traceability operations**: Phases 1b, 9b-1, 9b-2, and 9c are all skip-and-continue on `TRACEABILITY: DEGRADED`. Never treat DEGRADED as a pipeline failure. - **Caller spawn blocks restating marker literals**: Callers (resolve.mds, code-review.mds) pass operation inputs only — they do not restate the marker string that the operation writes internally. The operation owns what it writes (avoids PF-024). - **Calling resolveReviewThread for FALSE_POSITIVE or BY_DESIGN**: D9 gate is FIXED-only. Thread authors close their own threads after seeing devflow's evidence reply. +- **Chaining duplicate_of references**: `duplicate_of` must reference a non-DUPLICATE issue. Chaining (DUPLICATE A → DUPLICATE B → primary C) makes outcome inheritance unresolvable and is treated as a Triage failure. +- **Listing DUPLICATE issues in outcome sections**: DUPLICATE issues belong exclusively in `## Duplicates`. Placing them in `## Fixed Issues`, `## False Positives`, or any other outcome section causes the Statistics rows and section bodies to disagree, and manage-debt would create spurious debt tickets. +- **Dispatching DUPLICATE issues to Code agents**: DUPLICATE issues inherit their primary's outcome. Only non-DUPLICATE FIX_NOW issues are batched and dispatched in Phase 3. +- **Exposing DUPLICATE verdict to thread authors in Phase 9b-1**: The DUPLICATE verdict must be mapped to the primary's verdict and verification status before the git.md operation is called. git.md contracts are unchanged — the mapping is caller-side. ## Gotchas - **DIFF_FILES is an empty string, not absent**: When the `### Diff Scope` block is missing from Git agent output (bug-analysis edge case), `DIFF_FILES` is set to `""`, not omitted. The Triage matrix degrades accordingly — do not treat empty string as "all files in scope." -- **Verdict ledger completeness**: Every issue from Phase 1 must appear in the Triage agent output. The pipeline validates that no issue vanishes. If the Triage agent output is missing an issue ID, it is a Triage agent failure, not an acceptable outcome. +- **Verdict ledger completeness**: Every issue from Phase 1 must appear in the Triage agent output. The pipeline validates that no issue vanishes. DUPLICATE is a valid bucket — a DUPLICATE entry with a missing `duplicate_of`, or one whose `duplicate_of` references another DUPLICATE, is treated as a Triage failure (retry-then-abort, same as a vanished id). - **resolution-summary.md is written multiple times**: Phase 5 writes the initial version with `Tracked = (pending)`. Phase 7 rewrites `## Verification`. Phase 9 backfills `Tracked` cells. Phase 9b updates `## Third-Party Threads`. Any phase that overwrites the file wholesale destroys Phase 5's compaction safety — always patch specific sections. @@ -278,6 +348,8 @@ Three test files provide static content guards that fail loudly when load-bearin - **Unmatched ext-{N} records default to ESCALATED**: External review threads from Phase 1b that cannot be matched to a Triage verdict by file:line correlation are classified as ESCALATED in Phase 9b-1, not silently dropped. +- **DUPLICATE ext-{N} thread matching is caller-side**: When a thread matches a DUPLICATE issue, the orchestrator maps to the primary's verdict and verification status before calling the git.md operation. The mapping is transparent to git.md — its contracts are unchanged. + - **`--review {timestamp}` not supported in multi-worktree mode**: The `--review` flag only works in single-worktree flow. - **Legacy flat layout**: If no timestamped subdirectories exist but flat `*.md` files are present in the branch review directory, the command reads them directly (backwards-compatible). @@ -287,7 +359,7 @@ Three test files provide static content guards that fail loudly when load-bearin ## Key Files - `src/assets/commands/resolve.mds` — MDS source for /resolve orchestration command (phases 0-10 + 1b, 9b, 9c); compiled to `dist/commands/` -- `src/assets/agents/triage.md` — Triage agent (opus): blast-radius disposition matrix, evidence rules, verdict ledger format +- `src/assets/agents/triage.md` — Triage agent (opus): duplicate grouping pre-pass, blast-radius disposition matrix, evidence rules, verdict ledger format (7 buckets including DUPLICATE) - `src/assets/agents/code.md` — Code agent: `issue-fix`, `validation-fix`, `alignment-fix`, `qa-fix` modes documented in Mode sections - `src/assets/agents/git.md` — Git agent: all traceability operations (validate-branch, fetch-review-threads, resolve-review-threads, post-review-summary, post-resolution-summary, check-merge-readiness, manage-debt, check-ci-status); D7/D8/D9 decision markers defined here - `src/assets/commands/_partials/_compliance.mds` — `compliance_gate()` partial: sets `COMPLIANCE_SKILL_INSTALLED` as plain boolean @@ -295,14 +367,16 @@ Three test files provide static content guards that fail loudly when load-bearin - `src/assets/commands/code-review.mds` — Contains convergence parser (fp_ratio), Phase 3 sequential synthesis+comment pattern, Step 0b COMPLIANCE_SKILL_INSTALLED resolution, REVIEW_TIMESTAMP spawn input - `tests/git-agent.test.ts` — Static content guards for git.md: ops, bounds, D9 gate, D4 rate-limit, dedup markers (PF-018) - `tests/registry-integrity.test.ts` — Guard 6: forward+reverse OPERATION: ↔ ## Operation: contract with INTERNAL_OPS allowlist (build-gated) -- `tests/build-mds.test.ts` — §15: REVIEW_TIMESTAMP input assertion; §16: resolve.md traceability ops; all beforeAll blocks assert exit-0 + non-empty corpus +- `tests/build-mds.test.ts` — §15: REVIEW_TIMESTAMP input assertion; §16: resolve.md traceability ops; §16b: DUPLICATE verdict guards (consumer side — DUPLICATE bucket, duplicate_of, Duplicates Collapsed row, ## Duplicates section); all beforeAll blocks assert exit-0 + non-empty corpus +- `tests/resolve/duplicate-verdict.test.ts` — DUPLICATE producer-side guards: pre-pass ordering, chaining prohibition, security-primary election, ledger bucket/column, two-sided PF-024 enum seam, section exclusivity ## Related +- ADR-006 (Triage judges / Code fixes split; resolution-summary schema strictly additive over the convergence parser) — applies to the new ## Duplicates section and Duplicates Collapsed row (additive, parser-safe) +- PF-024 (spawn↔op seams: enum domains must match both sides) — DUPLICATE verdict seam is pinned by duplicate-verdict.test.ts (producer) and build-mds.test.ts §16b (consumer); DUPLICATE→primary mapping in Phase 9b-1 is caller-side so git.md contracts are unchanged - PF-018 (real-path tests): git-agent.test.ts guard suite reads the source file directly for bounds and literal contracts - PF-019 (verdict-not-evidence): Triage agent must provide cited evidence (grep/file:line/ADR) not just verdicts; D9 propagates evidence through resolve-review-threads reply composition - PF-020 (parallel Code-agent staging): same-file Code agent batches must be sequential; distinct-file batches parallel -- PF-024 (spawn↔op seams): caller spawn blocks pass inputs only — they do not restate what the operation writes (marker literals, internal formats); the operation owns its output - ADR-003 (leave-the-end-state): Resolver retired with zero tombstones; its installed file is pruned by the registry-diff orphan sweep - PF-002 (skill re-entrancy): Triage agent skills are loaded via frontmatter — never body-instructed via `Skill()` calls - PF-003 (no bare rm in agent instructions): Agent shell operations must use safe-delete patterns