baldart 4.53.0 → 4.53.1
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 +12 -0
- package/VERSION +1 -1
- package/framework/.claude/agents/api-perf-cost-auditor.md +4 -0
- package/framework/.claude/agents/code-reviewer.md +11 -0
- package/framework/.claude/agents/doc-reviewer.md +1 -0
- package/framework/.claude/agents/security-reviewer.md +7 -0
- package/framework/.claude/skills/new/references/final-review.md +12 -3
- package/framework/.claude/skills/new/references/review-cycle.md +14 -1
- package/framework/.claude/workflows/new-card-review.js +35 -10
- package/framework/.claude/workflows/new-final-review.js +29 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,18 @@ All notable changes to BALDART will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [4.53.1] - 2026-06-18
|
|
9
|
+
|
|
10
|
+
**Review workflows no longer spawn fixers for verified-but-no-action findings, and a `finding_id` collision that turned applied fixes into false residuals is closed.** Diagnosed from a real `/new FEAT-0035` team-mode run where the per-wave review cluster spawned `security-reviewer` (Sonnet) + `coder` (Opus) for ~110k tokens to apply ~3 LOW fixes and **no-op ~8 non-actionable findings** — finders (simplify / security / codex / api-perf / doc) emit *verified observations that need NO change* (`minimal_fix_direction` = "No fix required" / "acceptable as-is" / "verified non-issue") as findings; these survive the false-positive check (they are TRUE, not false positives), are `preValidated` → skip Verify → classified `VERIFIED` → flood the fix partitions. The system conflated **VERIFIED with actionable**. Fix (two fail-safe layers): the `FINDING` schema gains an optional `requires_action` flag, every finder prompt is told to set it `false` (or simply not emit) a no-change observation, and a deterministic `isActionable()` gate — honoured for MED/LOW only, a **BLOCKER/HIGH is always actionable** so a cross-wave merge-ordering HIGH still surfaces as residual — segregates no-action findings into a recorded/counted bucket (`summary.noAction`, `decision=skipped reason=no-action`) that **never reaches a writer and is never silently dropped**. A regex backstop on the fix direction covers a finder that omits the flag. **Second defect (root cause of an observed empty-coder re-spawn):** finders number `finding_id` (`F###`) independently, so a security `F003` and a migration `F003` collide; the Fix-phase bookkeeping (`appliedIds`/`unresolvedIds`/`codeResidual`/`bucket`) keys on `finding_id` with flat Sets, so one finder's `unresolved` id dragged another finder's *applied* same-id fix into residual → the skill then re-spawned a coder that found nothing to do. Fixed by making `finding_id` globally unique at the fan-in (prefix `source` + index). Verified with a deterministic test that extracts the real gate from source and runs it over the actual FEAT-0035 findings (4 no-action LOW suppressed incl. a mid-prose "no fix required", 3 real LOW + the cross-wave HIGH kept) plus a collision regression (bug reproduces pre-fix, gone post-fix). Applied to **both** review workflows + their prose-SSOT (so the inline fallback benefits) + (opt-in) the finder agents' system prompts. **PATCH** (economy/quality refinement of existing workflows; no new agent/skill/command, no install-layout change, no `baldart.config.yml` key).
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **`framework/.claude/workflows/new-card-review.js`** — `FINDING` schema gains `requires_action`; finder prompts (codex / simplify / security / code-reviewer fallback) instructed to flag no-action observations; new `NO_ACTION_RE` + `isNoAction()` gate (MED/LOW only) segregates a `noAction` partition excluded from `securityFix`/`actionable`/`docResidual` and logged; `finding_id` made globally unique at the fan-in (fixes the cross-finder `appliedIds`/`unresolvedIds`/`codeResidual` contamination); coder fix-pass prompt clarifies `applied` = edit made even if the build stays red for out-of-scope reasons, `unresolved` = the edit itself could not be made; `summary.noAction` added.
|
|
15
|
+
- **`framework/.claude/workflows/new-final-review.js`** — same `requires_action` schema field + finder-prompt instruction (codex / doc / api-perf / code-reviewer fallback) + `finding_id` uniqueness at the fan-in; no-action VERIFIED MED/LOW findings segregated out of the returned `findings` into a new `noActionFindings` field + `summary.noAction`, so the skill's F.5 writer partition never routes them.
|
|
16
|
+
- **`framework/.claude/skills/new/references/review-cycle.md`** / **`final-review.md`** — drift-note + Phase-2.55 step-4 / F.4 classification + F.5 partition updated to document the actionability gate and the `finding_id` uniqueness (the inline fallback path mirrors the workflow). `codex-gate.md` unchanged (its per-card gate only auto-fixes BLOCKER/HIGH, which are never no-action).
|
|
17
|
+
- **`framework/.claude/agents/code-reviewer.md`**, **`api-perf-cost-auditor.md`**, **`security-reviewer.md`**, **`doc-reviewer.md`** — an Actionability clause after the FP challenge: a verified-safe / already-accurate observation is a cleared concern, not an actionable finding (set `requires_action:false` or omit; MED/LOW only) — defense-in-depth that also benefits standalone `/codexreview`, `/prd`, `/bug`.
|
|
18
|
+
- **`framework/.claude/workflows/new2.js`** / **`new2-resolve.js`** — unchanged (justified): they route only BLOCKER/HIGH findings, which the severity invariant always keeps actionable, so no-action MED/LOW never reach them; they group by file/area, not `finding_id`.
|
|
19
|
+
|
|
8
20
|
## [4.53.0] - 2026-06-18
|
|
9
21
|
|
|
10
22
|
**Worktree setup for `/new` / `/nw` / `new2` is now a deterministic SSOT script — no model in the loop for pure plumbing.** Diagnosed from a real `/new FEAT-0036 -full` run where the Phase-0 worktree-setup subagent **created the worktree but went idle before `npm install`** (node_modules missing, no registry entry), forcing the orchestrator to burn turns probing → messaging the agent → waiting → cleaning the half-built orphan → falling back to inline `/nw` (which reloads the ~1200-line skill body into the orchestrator — the exact cost the delegation was meant to avoid). Same root cause as the earlier haiku *fabrication* (a well-formed `baseline: pass` with nothing on disk, 2/2): a **model in the loop for mechanical work** — it either fabricates the expected return or stalls mid-execution. The fix removes the model: the entire build sequence (worktree add → sync untracked cards → install → copy `stack.env_files` → allocate port → write registry → baseline) now lives **once** in `framework/.claude/skills/worktree-manager/scripts/setup-worktree.sh` and is invoked identically by all three callers as a background `Bash`. A deterministic script cannot fabricate or stall: it does the work or fails honestly with a log + a structured manifest. The orchestrator's disk-verification gate (setup.md step 6a) and git-authoritative idempotency pre-check (step 4a2) are unchanged — defence-in-depth survives the redesign. Non-interactive HARD (stdin closed, `CI=1`, build under `timeout 600`); port allocation is atomic under a `.worktrees/.wt-setup.lock` mutex (mirrors `allocate-id.sh`) so concurrent `/nw` runs never collide; the registry is written via `node` (atomic tmp+rename, no jq dependency); the script never self-repairs a failing baseline (role boundary — the coder does, downstream). Verified end-to-end on a throwaway repo: happy path (worktree + env+PORT + card-sync + `buildVerified:true` + manifest), collision (loud fail, exit 3), build-fail (`baseline:fail`, port held, `buildVerified:false`), and second-worktree port allocation (3001+3002 reserved → 3003). Opt-in-with-fallback: a pre-this-release subtree without the script falls back to inline `/nw`. **MINOR** (new shipped SSOT asset + behaviour change to existing skills; backward-compatible, no install-layout or command change, no `baldart.config.yml` key — `stack.env_files` / `toolchain.commands.*` are reused).
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.53.
|
|
1
|
+
4.53.1
|
|
@@ -202,6 +202,10 @@ Consider:
|
|
|
202
202
|
|
|
203
203
|
**Exception**: never-demote items above are never false positives in this project — do not suppress them.
|
|
204
204
|
|
|
205
|
+
### Actionability Pass (after the FP challenge)
|
|
206
|
+
|
|
207
|
+
A finding that survives the FP challenge can still need NO change — the pattern is acceptable as-is (e.g. an admin path where the optimization is genuinely not worth it). That is a cleared concern, **not an actionable finding**: reporting it spawns a downstream fixer to no-op it. Either omit it, or — when a consumer schema exposes a `requires_action` field — set `requires_action:false` (recorded, never routed to a writer). Record cleared concerns under a separate "Verified, no action" list. Guard: MEDIUM/LOW only (a BLOCKER/HIGH is never no-action); a change needed but out of your scope is NOT no-action — report it (it surfaces as residual).
|
|
208
|
+
|
|
205
209
|
## Load Simulation Pass (MANDATORY — execute mentally before findings)
|
|
206
210
|
|
|
207
211
|
Walk the changed handler/cron/query as if it were running under realistic production load. For each entry point in scope:
|
|
@@ -288,6 +288,17 @@ Consider:
|
|
|
288
288
|
|
|
289
289
|
**Never-demote items above are never false positives — do not suppress them.**
|
|
290
290
|
|
|
291
|
+
### Actionability Pass (after the FP challenge)
|
|
292
|
+
|
|
293
|
+
A finding that survives the FP challenge can still need NO change — the code is fine as-is, and the only honest "fix" is "no fix required" / "acceptable as-is" / "verified safe". That is **not a finding**: it is a cleared concern. Do NOT report it as an actionable finding (a downstream fixer would be spawned to no-op it). Either omit it, or — when a consumer schema exposes a `requires_action` field — set `requires_action:false` so it is recorded but never routed to a writer. Record cleared concerns separately:
|
|
294
|
+
|
|
295
|
+
<details>
|
|
296
|
+
<summary>Verified, no action (N items)</summary>
|
|
297
|
+
- **Title** — why it is safe as-is
|
|
298
|
+
</details>
|
|
299
|
+
|
|
300
|
+
Guard: this applies to MEDIUM/LOW only — a BLOCKER/HIGH is never "no action". And if a change IS needed but is out of your scope, that is NOT no-action: report it as a real finding (it will surface as residual).
|
|
301
|
+
|
|
291
302
|
## Diff Simulation Pass (MANDATORY — execute mentally before findings)
|
|
292
303
|
|
|
293
304
|
Walk the diff as if you were the runtime. For each non-trivial changed function/handler:
|
|
@@ -233,6 +233,7 @@ Key points:
|
|
|
233
233
|
- Fast Mode: verdict + one-liner only.
|
|
234
234
|
|
|
235
235
|
## Constraints
|
|
236
|
+
- **Actionability (when pooled as a finder, e.g. in the final-review fan-out):** a doc you VERIFIED is already accurate / up-to-date is a cleared concern, **not a finding** — do not emit it as a `LOW`/`MEDIUM` finding (a downstream fixer would be spawned to no-op it). Either omit it, or — when the pooled schema exposes a `requires_action` field — set `requires_action: false` (recorded, never routed to a writer). In your own write mode this is moot (you simply make no edit). A `BLOCKER`/`HIGH` is never no-action.
|
|
236
237
|
- For **feature documentation**: WRITE missing docs directly. You are fully responsible -- do not defer to other agents.
|
|
237
238
|
- For **general audits**: PROPOSE structural changes, but WRITE fixes for factual gaps, stale content, and missing entries.
|
|
238
239
|
- **ALWAYS identify the macro feature** -- even for small bug fixes or one-off cards.
|
|
@@ -144,6 +144,13 @@ Map your severity labels to the pooled enum: Critical → `BLOCKER`, High → `H
|
|
|
144
144
|
`# Security Review Summary` (scope / overall risk / main attack surfaces / most critical concern) so
|
|
145
145
|
the orchestrator has a header. Do NOT emit the long markdown finding blocks in this mode.
|
|
146
146
|
|
|
147
|
+
**Actionability**: a control you have VERIFIED as safe-as-is (the honest recommendation is "no fix
|
|
148
|
+
required" / "verified safe") is a cleared concern, **not a finding** — emitting it as a `LOW`/`MEDIUM`
|
|
149
|
+
finding makes the orchestrator spawn a fixer to no-op it. Either omit it, or — when the pooled schema
|
|
150
|
+
exposes a `requires_action` field — set `requires_action: false` (recorded for the audit trail, never
|
|
151
|
+
routed to a writer). A `BLOCKER`/`HIGH` is never no-action; a fix needed but out of your ownership IS a
|
|
152
|
+
real finding (report it as residual, not as no-action).
|
|
153
|
+
|
|
147
154
|
### Mode B — Standalone review (when invoked directly by a human)
|
|
148
155
|
|
|
149
156
|
When invoked directly (no pooling orchestrator), use this human-readable structure:
|
|
@@ -124,7 +124,12 @@ that is a **gate violation**: log it as
|
|
|
124
124
|
> *orchestration shape* of F.2–F.4; its agent briefs cite THIS module for the
|
|
125
125
|
> review *semantics*, so the SSOT for what each reviewer checks stays here. When
|
|
126
126
|
> you change the F.2–F.4 reviewer set, gates, or classification rules, update the
|
|
127
|
-
> workflow's phase/agent wiring to match.
|
|
127
|
+
> workflow's phase/agent wiring to match. Since v4.53.1 the workflow also (a) makes every
|
|
128
|
+
> `finding_id` globally unique at the fan-in (finders number `F###` independently → a collision
|
|
129
|
+
> corrupts downstream finding_id-keyed routing/telemetry), and (b) runs an **actionability gate** that
|
|
130
|
+
> segregates VERIFIED-but-no-change findings (`requires_action:false` / regex backstop, MED/LOW only)
|
|
131
|
+
> into `noActionFindings` + `summary.noAction`, keeping them out of the returned `findings` the skill
|
|
132
|
+
> routes to writers in F.5. Keep both mirrored with the inline F.4 classification above.
|
|
128
133
|
|
|
129
134
|
### Step F.2 — Architecture baseline
|
|
130
135
|
|
|
@@ -189,6 +194,10 @@ that is a **gate violation**: log it as
|
|
|
189
194
|
- minimal_fix_direction: what to change
|
|
190
195
|
5. Run mandatory false-positive check: for each finding, ask 'What is the strongest
|
|
191
196
|
argument this is a false positive?' Suppress if the FP argument is convincing.
|
|
197
|
+
5.5 Actionability: an observation you have VERIFIED needs NO change anywhere (the honest direction
|
|
198
|
+
would be 'no fix required' / 'acceptable as-is' / 'verified non-issue') is not work — set
|
|
199
|
+
requires_action:false on it (recorded, never routed to a fixer), or simply do not report it.
|
|
200
|
+
This is for MEDIUM/LOW only; a BLOCKER/HIGH is never no-action.
|
|
192
201
|
6. Classify surviving findings as VERIFIED, FALSE_POSITIVE, or NEEDS_MANUAL_CONFIRMATION.
|
|
193
202
|
|
|
194
203
|
Return ONLY verified findings. If zero verified bugs: state 'No verified bugs found.'
|
|
@@ -248,12 +257,12 @@ that is a **gate violation**: log it as
|
|
|
248
257
|
- **Each domain specialist OWNS its lane end-to-end** — it runs its OWN false-positive check in the finding pass (doc-reviewer for doc, api-perf-cost-auditor for api/perf/data, and the Codex/`code-reviewer`-fallback engine for code), so its surviving findings are **already validated**. Do **NOT** cross-validate a doc or api finding by spawning `code-reviewer` (that is the WRONG specialist judging another domain), and do **NOT** re-run the same specialist over its own findings (self-judge — no model diversity). Codex findings are likewise already FP-validated (Step F.3 protocol includes it).
|
|
249
258
|
- **Residual only:** a finding the originating specialist explicitly leaves UNRESOLVED (`confidence < 80`) is routed to the finding's **domain specialist** (by `domain`: doc→doc-reviewer, api/perf→api-perf-cost-auditor, security/migration→security-reviewer, test→qa-sentinel, else code-reviewer) over the cited file:line. If that domain specialist **is** the originating finder (it already had its pass), classify `NEEDS_MANUAL_CONFIRMATION` instead of re-spawning it — never self-judge, never silently drop.
|
|
250
259
|
- Classify: `VERIFIED` | `FALSE_POSITIVE` | `NEEDS_MANUAL_CONFIRMATION`.
|
|
251
|
-
- `VERIFIED` findings proceed to fixes. **`NEEDS_MANUAL_CONFIRMATION` findings are NOT discarded** — list them in `## Issues & Flags` and surface them to the user via `AskUserQuestion` (treat as VERIFIED, treat as FALSE_POSITIVE, or hand off) before merge. Only `FALSE_POSITIVE` are dropped.
|
|
260
|
+
- `VERIFIED` findings proceed to fixes — **EXCEPT no-action ones** (`requires_action:false`, or a "no fix required" / "acceptable as-is" / "verified non-issue" direction; MEDIUM/LOW only). A no-action finding is TRUE but needs no change: record it (`decision=skipped | reason=no-action`) and do NOT route it to a writer. (The delegated `new-final-review` workflow returns these separately as `noActionFindings` + `summary.noAction`; the inline path classifies them here.) **`NEEDS_MANUAL_CONFIRMATION` findings are NOT discarded** — list them in `## Issues & Flags` and surface them to the user via `AskUserQuestion` (treat as VERIFIED, treat as FALSE_POSITIVE, or hand off) before merge. Only `FALSE_POSITIVE` are dropped.
|
|
252
261
|
|
|
253
262
|
### Step F.5 — Apply fixes and final build
|
|
254
263
|
|
|
255
264
|
10. **Persist verified findings** to `/tmp/batch-final-review-<FIRST-CARD-ID>.md`.
|
|
256
|
-
11. **Merge-blocking gate (mirrors the per-card Phase 3.7 gate this final pass backstops):** if any VERIFIED **BLOCKER or HIGH** finding exists, it MUST be resolved before Phase 6 merge. Apply fixes by **domain owner** (since v3.40.0 — same Domain-Override routing as the per-card phases), then re-verify; if a BLOCKER/HIGH cannot be resolved in a single apply + one retry, log it in `## Issues & Flags` and invoke `AskUserQuestion` (override with reason / escalate to a follow-up card / halt) — do NOT proceed to Phase 6 with an unresolved BLOCKER or HIGH. VERIFIED findings of severity MEDIUM are also applied (advisory below that). Partition the verified findings by the **Domain-Override match rules** ("Domain-Override Domains"):
|
|
265
|
+
11. **Merge-blocking gate (mirrors the per-card Phase 3.7 gate this final pass backstops):** if any VERIFIED **BLOCKER or HIGH** finding exists, it MUST be resolved before Phase 6 merge. Apply fixes by **domain owner** (since v3.40.0 — same Domain-Override routing as the per-card phases), then re-verify; if a BLOCKER/HIGH cannot be resolved in a single apply + one retry, log it in `## Issues & Flags` and invoke `AskUserQuestion` (override with reason / escalate to a follow-up card / halt) — do NOT proceed to Phase 6 with an unresolved BLOCKER or HIGH. VERIFIED findings of severity MEDIUM are also applied (advisory below that). Partition the verified findings — **excluding the no-action ones segregated in F.4** (`requires_action:false` / no-change direction, MED/LOW; they are recorded, never fixed) — by the **Domain-Override match rules** ("Domain-Override Domains"):
|
|
257
266
|
- **`doc`-domain findings** (file path matching the `doc` match rule — `*.md` under `${paths.references_dir}`/`${paths.prd_dir}`, `CHANGELOG.md`, `ssot-registry.md`) → invoke the **doc-reviewer** agent once in write mode to apply them. NEVER route doc fixes to coder.
|
|
258
267
|
- **`security`-domain findings** (path in `paths.high_risk_modules`, or RLS-policy SQL) → route to **security-reviewer** in write mode (canonical writer map v4.26.1 — it owns the security-invariant contract a coder lacks; NEVER route security fixes to coder). **`migration`-domain findings** (SQL under the migrations dir) → route to **coder**. For both, apply the Sub-agent failure protocol's STOP-on-crash rule (never inline-fallback on a security/migration fix). These are NOT collapsed into a generic "everything else" bucket.
|
|
259
268
|
- **All remaining findings** (other code, perf, test) → invoke the **coder** agent once to apply them in a single pass.
|
|
@@ -74,6 +74,13 @@ so it surfaces in telemetry.
|
|
|
74
74
|
`gateTable` is all PASS/SKIP and `checksFailed` is false** — a delegated QA FAIL must block exactly
|
|
75
75
|
as the inline path does. (`gateTable` is **wave-scoped**, not per-card — qa-sentinel runs once over
|
|
76
76
|
the worktree; a FAIL applies to the whole wave's commit.)
|
|
77
|
+
- **No-action findings (since v4.53.1)**: the workflow already segregated VERIFIED findings that
|
|
78
|
+
need NO change (finder `requires_action:false`, or a regex backstop on a "no fix required" /
|
|
79
|
+
"acceptable as-is" / "verified non-issue" direction — honoured for MED/LOW only; BLOCKER/HIGH are
|
|
80
|
+
always actionable). They are **counted in `summary.noAction`** and **excluded from both the writer
|
|
81
|
+
passes and `residual`** — so they never spawn a fixer and never surface as user-facing residual.
|
|
82
|
+
Append one `## Fix Application Log` row each as `decision=skipped | reason=no-action` (audit trail,
|
|
83
|
+
no silent drop). Do NOT re-route them to a writer.
|
|
77
84
|
- **Telemetry**: append the workflow's `fixesApplied` + `residual` to `## Fix Application Log`
|
|
78
85
|
(`decision=workflow`). Derive the Phase-8 producers from the return: `qa_first_attempt` ←
|
|
79
86
|
`summary.qaRan ? (summary.failingGates.length === 0 && !summary.checksFailed ? "pass" : "fail") : "n/a"`
|
|
@@ -90,7 +97,12 @@ so it surfaces in telemetry.
|
|
|
90
97
|
> review cluster; its agent briefs cite THIS module for the review *semantics*, so the SSOT for what each
|
|
91
98
|
> finder checks stays here. When you change the Simplify lenses, the qa contract, the Codex/Step-A
|
|
92
99
|
> profile rule, or the FP-check/routing, update the workflow's phase/agent wiring to match. The
|
|
93
|
-
>
|
|
100
|
+
> workflow also (since v4.53.1) (a) makes every `finding_id` globally unique at the fan-in — finders
|
|
101
|
+
> number `F###` independently and the Fix-phase bookkeeping keys on `finding_id`, so a collision used
|
|
102
|
+
> to drag one finder's applied fix into another's residual; and (b) runs an **actionability gate** that
|
|
103
|
+
> keeps VERIFIED-but-no-change findings (finder `requires_action:false` / regex backstop, MED/LOW only)
|
|
104
|
+
> out of the writer passes. Keep both in sync with the inline path below (step 4 skip-classification).
|
|
105
|
+
> The delegated path runs the doc-review (Phase 3) on the FINAL post-fix/post-E2E code; the inline path runs
|
|
94
106
|
> it at its current position — both are doc-relevance-gated and both are backstopped by the Final FULL
|
|
95
107
|
> doc-reviewer.
|
|
96
108
|
|
|
@@ -114,6 +126,7 @@ After completeness is verified, clean up the implementation before it reaches re
|
|
|
114
126
|
- **Valid AND in a Domain-Override domain** (the finding's target file matches the `doc`, `security`, or `migration` match rule in "Domain-Override Domains") → do NOT apply inline. Delegate to the domain **writer** (canonical writer map v4.26.1): `doc` → `doc-reviewer` (write mode), `security` → `security-reviewer` (write mode — it owns the security-invariant contract a coder lacks), `migration` → `coder`. Even a one-line efficiency fix in `paths.high_risk_modules` (security) or a migration file goes to the owning agent — the orchestrator lacks that domain's invariant contract.
|
|
115
127
|
- **Valid AND not in a Domain-Override domain** → fix directly (apply edits inline).
|
|
116
128
|
- **False positive / not worth addressing** → skip, BUT record it (see telemetry). If the skip rests on a "covered by X" / "redundant" / "not needed" rationalization (the same family the AC-Closure Gate guards against), do NOT discard silently — verify the rationale by reading `X`, and if it does not hold, treat the finding as valid.
|
|
129
|
+
- **Verified but no action needed** (the finding is TRUE but its only honest direction is "no fix required" / "acceptable as-is" / "verified safe" — a cleared concern, not work) → do NOT apply and do NOT delegate; record it with `decision=skipped | reason=no-action`. This is distinct from a false positive (the observation holds) and from "not worth addressing" (a judgement call). Guard: this is for `MEDIUM`/`LOW` only — a `BLOCKER`/`HIGH` is never "no action". If a change IS needed but not here (out of this card's scope), it is NOT no-action — keep it valid and let it surface as residual. (This mirrors the delegated workflow's `requires_action` gate.)
|
|
117
130
|
|
|
118
131
|
**Telemetry (Fix Application Log)** — for EVERY finding (valid OR skipped) append one row to the tracker's `## Fix Application Log` section per the schema above. Use `domain=simplify-{reuse|quality|efficiency}` matching the originating agent. Include the `severity` trailing key. Inline: `decision=inline | applied_by=orchestrator | est_lines=<n> | severity=<HIGH|MEDIUM> | finding=<1-line>`. Delegated (domain-override): `decision=<coder|doc-reviewer> | applied_by=<coder|doc-reviewer> | est_lines=<n> | severity=<...> | finding=<1-line>`. Skipped: `decision=skipped | applied_by=- | est_lines=0 | reason=<false-positive|not-worth-addressing>`.
|
|
119
132
|
|
|
@@ -80,6 +80,7 @@ const FINDING = {
|
|
|
80
80
|
evidence: { type: 'string', description: 'exact file:line + code quote' },
|
|
81
81
|
minimal_fix_direction: { type: 'string' },
|
|
82
82
|
domain: { enum: ['doc', 'security', 'migration', 'code', 'perf', 'test'], description: 'Domain-Override routing bucket' },
|
|
83
|
+
requires_action: { type: 'boolean', description: 'false = a VERIFIED observation that needs NO change anywhere (concern cleared / safe-as-is / advisory) — recorded for audit, NEVER routed to a writer. Omit (⇒ actionable) when a concrete edit is needed. If a change is needed but not by you (out of scope), keep true and say so in minimal_fix_direction (it surfaces as residual).' },
|
|
83
84
|
},
|
|
84
85
|
}
|
|
85
86
|
const FINDINGS_SCHEMA = {
|
|
@@ -200,7 +201,8 @@ const codexPrompt =
|
|
|
200
201
|
`Return codexAvailable:false ONLY if $REVIEW_FILE ends up containing "CODEX_NOT_FOUND" or stays empty after the FULL 10-minute window — NEVER because a single Bash call returned slowly.\n\n` +
|
|
201
202
|
`${waveBrief}\n\n${baselineBrief}\n\n` +
|
|
202
203
|
`For each finding return: finding_id, title, severity (BLOCKER|HIGH|MEDIUM|LOW), confidence (0-100), evidence (exact file:line + code quote), minimal_fix_direction, and domain (doc|security|migration|code|perf|test). ` +
|
|
203
|
-
`Run the mandatory false-positive check on every finding and suppress the unconvincing ones (your findings are treated as already FP-validated).
|
|
204
|
+
`Run the mandatory false-positive check on every finding and suppress the unconvincing ones (your findings are treated as already FP-validated). ` +
|
|
205
|
+
`Then the actionability check: an observation you have VERIFIED needs NO change anywhere (the fix direction would be "no fix required" / "acceptable as-is" / "verified non-issue") is not work — set requires_action:false on it (it is recorded but never sent to a fixer), or simply do not emit it. Emit requires_action true/omitted ONLY when a concrete edit is needed. Set codexAvailable:true when the review ran.`
|
|
204
206
|
|
|
205
207
|
const tcGateLines = [
|
|
206
208
|
['lint', tcCmds.lint], ['typecheck', tcCmds.typecheck], ['test', tcCmds.test],
|
|
@@ -218,12 +220,14 @@ function simplifyPrompt(c) {
|
|
|
218
220
|
` • Quality — redundant state, parameter sprawl, copy-paste with slight variation, leaky abstractions, stringly-typed code where enums exist, unnecessary JSX nesting, WHAT-comments / narration.\n` +
|
|
219
221
|
` • Efficiency — redundant computation, duplicate API calls, N+1, missed concurrency, hot-path bloat, missing change-detection guards, unbounded structures.\n\n` +
|
|
220
222
|
`${cardScopeBrief(c)}\n\n${baselineBrief}\n\n` +
|
|
221
|
-
`Run a false-positive check on every finding and SUPPRESS the unconvincing ones (your surviving findings are treated as validated).
|
|
223
|
+
`Run a false-positive check on every finding and SUPPRESS the unconvincing ones (your surviving findings are treated as validated). ` +
|
|
224
|
+
`Then the actionability check: an observation you have VERIFIED needs NO change (the fix direction would be "no fix required" / "acceptable as-is" / "optional") is not work — set requires_action:false (recorded, never sent to a fixer), or do not emit it. Return findings with domain in {code, perf}. Flag only a finding you genuinely cannot resolve as confidence < 80.`
|
|
222
225
|
}
|
|
223
226
|
|
|
224
227
|
function securityPrompt(c) {
|
|
225
228
|
return `AppSec review (read-only) over ONE card's committed diff — auth, permissions, secrets, webhooks, file upload, infra, multi-tenant isolation, injection. Security-sensitive paths: ${highRisk.join(', ') || '(none configured)'}.\n\n${cardScopeBrief(c)}\n\n${baselineBrief}\n\n` +
|
|
226
|
-
`You OWN the security domain end-to-end: run the mandatory false-positive check on every finding yourself and SUPPRESS the unconvincing ones — your surviving findings are treated as validated and are NOT re-judged by another agent.
|
|
229
|
+
`You OWN the security domain end-to-end: run the mandatory false-positive check on every finding yourself and SUPPRESS the unconvincing ones — your surviving findings are treated as validated and are NOT re-judged by another agent. ` +
|
|
230
|
+
`Then the actionability check: a control you have VERIFIED as safe-as-is needs NO change (the fix direction would be "no fix required" / "verified safe") — set requires_action:false on it (recorded for the security audit trail, never sent to a fixer), or do not emit it. Flag only a finding you genuinely cannot resolve as confidence < 80. Return findings with domain in {security, migration}.`
|
|
227
231
|
}
|
|
228
232
|
|
|
229
233
|
// Build the finder fan-out. Per-card: simplify (if runSimplify), security (if hasSecurityFiles).
|
|
@@ -273,12 +277,19 @@ for (const item of findResults) {
|
|
|
273
277
|
if (!codexRan) {
|
|
274
278
|
codexEngine = 'code-reviewer (fallback)'
|
|
275
279
|
const fb = await agent(
|
|
276
|
-
`Codex was unavailable for this wave's code review. Run the FULL code review yourself over the wave diff, per ${protocolRef} (Phase 3.7).\n\n${waveBrief}\n\n${baselineBrief}\n\nReturn findings using the schema fields, with a self false-positive check applied (your findings are treated as validated).`,
|
|
280
|
+
`Codex was unavailable for this wave's code review. Run the FULL code review yourself over the wave diff, per ${protocolRef} (Phase 3.7).\n\n${waveBrief}\n\n${baselineBrief}\n\nReturn findings using the schema fields, with a self false-positive check applied (your findings are treated as validated). An observation you VERIFIED needs NO change (fix direction "no fix required" / "acceptable as-is") is not work — set requires_action:false (recorded, never sent to a fixer), or do not emit it.`,
|
|
277
281
|
{ label: 'code-reviewer (fallback)', phase: 'Discovery', agentType: 'code-reviewer', schema: FINDINGS_SCHEMA }
|
|
278
282
|
)
|
|
279
283
|
if (fb && Array.isArray(fb.findings)) raw.push(...fb.findings.map((f) => ({ ...f, source: 'code-reviewer', preValidated: true })))
|
|
280
284
|
}
|
|
281
285
|
|
|
286
|
+
// Globally-unique finding_id at the fan-in. Finders number F### INDEPENDENTLY, so a Codex F003 and
|
|
287
|
+
// a security F003 collide. The Fix-phase bookkeeping (appliedIds / unresolvedIds / codeResidual /
|
|
288
|
+
// bucket) keys on finding_id with FLAT Sets, so a collision cross-contaminates: one finder's
|
|
289
|
+
// `unresolved` id drags another finder's APPLIED same-id finding into residual (the empty-coder
|
|
290
|
+
// re-spawn bug). Prefix source + running index → uniqueness without losing the human-readable id.
|
|
291
|
+
raw.forEach((f, i) => { f.finding_id = `${f.source || 'src'}#${i}:${f.finding_id || ('F' + i)}` })
|
|
292
|
+
|
|
282
293
|
// ───────────────────────────────────────────────────────────────────────────
|
|
283
294
|
// Phase Verify — specialist-owned validation (parity with new-final-review.js F.4)
|
|
284
295
|
// ───────────────────────────────────────────────────────────────────────────
|
|
@@ -330,16 +341,29 @@ const isDoc = (f) => /doc|wiki|ssot|readme/.test(String(f.domain).toLowerCase())
|
|
|
330
341
|
// migration/test → coder), so match the exact 'security' domain, not the broader verifier regex.
|
|
331
342
|
const isSecurity = (f) => String(f.domain).toLowerCase() === 'security'
|
|
332
343
|
const isManual = (f) => f.classification === 'NEEDS_MANUAL_CONFIRMATION'
|
|
344
|
+
// Actionability gate: a VERIFIED finding that needs NO change must NOT reach a writer (it floods the
|
|
345
|
+
// fix pass with no-ops). Honoured ONLY for MED/LOW — a BLOCKER/HIGH is always actionable (the flag
|
|
346
|
+
// can never suppress a blocker; a cross-wave HIGH like "don't merge before the consumer cards" stays
|
|
347
|
+
// actionable → surfaces as residual). Signal: finder flag requires_action:false, OR a conservative
|
|
348
|
+
// regex backstop on the fix direction for when the model omitted the flag. Never a silent drop —
|
|
349
|
+
// no-action findings are logged + counted, just excluded from the writer partitions.
|
|
350
|
+
const NO_ACTION_RE = /(^|[.\s(])(no\s+(fix|change|action)\s+(is\s+)?(required|needed)|none\s+(needed|required)|not\s+a\s+(defect|bug|correctness\s+bug)|acceptable\s+as[-\s]is|verified\s+(safe|non[-\s]issue)|no\s+action\s+(needed|required))([.\s,;)]|$)/i
|
|
351
|
+
const isNoAction = (f) => (f.severity !== 'BLOCKER' && f.severity !== 'HIGH') &&
|
|
352
|
+
(f.requires_action === false || NO_ACTION_RE.test(String(f.minimal_fix_direction || '')))
|
|
333
353
|
// Partition `surviving` (= VERIFIED + NEEDS_MANUAL; FALSE_POSITIVE already dropped) with NO overlap:
|
|
334
354
|
// securityFix = VERIFIED security → security-reviewer applies (it owns the security invariants).
|
|
335
355
|
// actionable = VERIFIED non-doc non-security → the coder fixes these.
|
|
336
356
|
// docResidual = VERIFIED doc → the skill runs doc-reviewer post-E2E on final code.
|
|
337
357
|
// manualResidual= NEEDS_MANUAL any → human gate, owned by the skill (a doc-manual must NOT be
|
|
338
358
|
// silently auto-re-reviewed: it carries its needs-manual classification out).
|
|
339
|
-
|
|
340
|
-
const
|
|
341
|
-
const
|
|
359
|
+
// No-action VERIFIED findings: recorded + counted, excluded from every writer partition.
|
|
360
|
+
const noAction = surviving.filter((f) => f.classification === 'VERIFIED' && isNoAction(f))
|
|
361
|
+
const noActionSet = new Set(noAction)
|
|
362
|
+
const securityFix = surviving.filter((f) => f.classification === 'VERIFIED' && !isDoc(f) && isSecurity(f) && !noActionSet.has(f))
|
|
363
|
+
const actionable = surviving.filter((f) => f.classification === 'VERIFIED' && !isDoc(f) && !isSecurity(f) && !noActionSet.has(f))
|
|
364
|
+
const docResidual = surviving.filter((f) => f.classification === 'VERIFIED' && isDoc(f) && !noActionSet.has(f))
|
|
342
365
|
const manualResidual = surviving.filter(isManual)
|
|
366
|
+
for (const f of noAction) log(`Review: no-action finding recorded (not routed to a writer): [${f.finding_id}] ${f.severity} ${f.domain} — ${f.title} (${f.requires_action === false ? 'requires_action:false' : 'no-action-inferred'})`)
|
|
343
367
|
|
|
344
368
|
const SKIP_CHECKS = { lint: 'SKIP', tsc: 'SKIP', build: 'SKIP' }
|
|
345
369
|
|
|
@@ -360,7 +384,7 @@ async function applyFixPass(findings, writer, label, role) {
|
|
|
360
384
|
findings.map((f) => `- [${f.finding_id}] (${f.card || '?'} / ${f.domain} / ${f.severity}) ${f.title}\n evidence: ${f.evidence}\n direction: ${f.minimal_fix_direction}`).join('\n') +
|
|
361
385
|
`\n\nAfter applying: run \`${tc('lint', 'npm run lint')}\` and (when the project uses typescript) \`${tc('typecheck', 'npx tsc --noEmit')}\` and \`${tc('build', 'npm run build')}\` in the worktree. If a check fails because of an edit you made, fix the regression — at most 2 retries — staying within the allowed files. ` +
|
|
362
386
|
`Do NOT commit. Do NOT git stash (refs/stash is shared across worktrees). ` +
|
|
363
|
-
`Return: applied (finding_ids you fixed), unresolved (finding_ids you could NOT fix within the allowed files / 2 retries), and checks (PASS/FAIL/SKIP for lint, tsc, build).`
|
|
387
|
+
`Return: applied (finding_ids you fixed — list a finding here if you MADE the edit, even when the build stays red for reasons OUTSIDE this finding / your ownership, e.g. a known cross-wave cascade; the build status is reported separately in checks), unresolved (finding_ids you could NOT fix within the allowed files / 2 retries — i.e. the EDIT itself could not be made), and checks (PASS/FAIL/SKIP for lint, tsc, build).`
|
|
364
388
|
const r = await agent(fixBrief, { label, phase: 'Fix', agentType: writer, schema: FIX_SCHEMA })
|
|
365
389
|
// Normalize: the agent may die (null) or return a truthy object missing fields.
|
|
366
390
|
const res = (r && typeof r === 'object') ? r : { applied: [], unresolved: findings.map((f) => f.finding_id), checks: { ...SKIP_CHECKS } }
|
|
@@ -407,6 +431,7 @@ const summary = makeSummary({
|
|
|
407
431
|
cards: cards.length,
|
|
408
432
|
totalFindings: raw.length,
|
|
409
433
|
verified: surviving.filter((f) => f.classification === 'VERIFIED').length,
|
|
434
|
+
noAction: noAction.length, // VERIFIED-but-no-change findings: recorded, never sent to a writer
|
|
410
435
|
falsePositive: classified.filter((f) => f.classification === 'FALSE_POSITIVE').length,
|
|
411
436
|
needsManual: manualResidual.length,
|
|
412
437
|
fixesApplied: appliedIds.size,
|
|
@@ -418,7 +443,7 @@ const summary = makeSummary({
|
|
|
418
443
|
blockers: surviving.filter((f) => f.classification === 'VERIFIED' && f.severity === 'BLOCKER').length,
|
|
419
444
|
highs: surviving.filter((f) => f.classification === 'VERIFIED' && f.severity === 'HIGH').length,
|
|
420
445
|
})
|
|
421
|
-
log(`Wave review done: ${summary.fixesApplied} fixed, ${summary.codeResidual} code-residual, ${summary.docResidual} doc-residual, ${summary.needsManual} needs-manual, ${summary.failingGates.length} failing gate(s)${checksFailed ? ', post-fix checks FAILED' : ''}. Engine: ${codexEngine}.`)
|
|
446
|
+
log(`Wave review done: ${summary.fixesApplied} fixed, ${summary.codeResidual} code-residual, ${summary.docResidual} doc-residual, ${summary.noAction} no-action (recorded), ${summary.needsManual} needs-manual, ${summary.failingGates.length} failing gate(s)${checksFailed ? ', post-fix checks FAILED' : ''}. Engine: ${codexEngine}.`)
|
|
422
447
|
|
|
423
448
|
return { codexEngine, perCard, gateTable, summary }
|
|
424
449
|
|
|
@@ -426,7 +451,7 @@ return { codexEngine, perCard, gateTable, summary }
|
|
|
426
451
|
function asArr(x) { return Array.isArray(x) ? x.filter(Boolean) : [] }
|
|
427
452
|
function dedupe(xs) { return Array.from(new Set(asArr(xs))) }
|
|
428
453
|
function makeSummary(o) {
|
|
429
|
-
return Object.assign({ cards: 0, totalFindings: 0, verified: 0, falsePositive: 0, needsManual: 0, fixesApplied: 0, docResidual: 0, codeResidual: 0, qaRan: false, checksFailed: false, failingGates: [], blockers: 0, highs: 0 }, o || {})
|
|
454
|
+
return Object.assign({ cards: 0, totalFindings: 0, verified: 0, noAction: 0, falsePositive: 0, needsManual: 0, fixesApplied: 0, docResidual: 0, codeResidual: 0, qaRan: false, checksFailed: false, failingGates: [], blockers: 0, highs: 0 }, o || {})
|
|
430
455
|
}
|
|
431
456
|
function slimFinding(f) {
|
|
432
457
|
return { finding_id: f.finding_id, title: f.title, severity: f.severity, domain: f.domain, evidence: f.evidence, minimal_fix_direction: f.minimal_fix_direction, classification: f.classification, card: f.card }
|
|
@@ -54,6 +54,7 @@ const FINDING = {
|
|
|
54
54
|
evidence: { type: 'string', description: 'exact file:line + code quote' },
|
|
55
55
|
minimal_fix_direction: { type: 'string' },
|
|
56
56
|
domain: { enum: ['doc', 'security', 'migration', 'code', 'perf', 'test'], description: 'Domain-Override routing bucket' },
|
|
57
|
+
requires_action: { type: 'boolean', description: 'false = a VERIFIED observation that needs NO change anywhere (concern cleared / safe-as-is / advisory) — recorded for audit, NEVER routed to a writer. Omit (⇒ actionable) when a concrete edit is needed. If a change is needed but not by you (out of scope), keep true and say so in minimal_fix_direction (it surfaces as residual).' },
|
|
57
58
|
},
|
|
58
59
|
}
|
|
59
60
|
const FINDINGS_SCHEMA = {
|
|
@@ -157,16 +158,19 @@ const codexPrompt =
|
|
|
157
158
|
`Return codexAvailable:false ONLY if $REVIEW_FILE ends up containing "CODEX_NOT_FOUND" or stays empty after the FULL 10-minute window — NEVER because a single Bash call returned slowly. The script's existence is already verified, so a premature false here is a bug, not a capability gap.\n\n` +
|
|
158
159
|
`${scopeBrief}\n\n${baselineBrief}\n\n` +
|
|
159
160
|
`For each finding return: finding_id, title, severity (BLOCKER|HIGH|MEDIUM|LOW), confidence (0-100), evidence (exact file:line + code quote), minimal_fix_direction, and domain (doc|security|migration|code|perf|test). ` +
|
|
160
|
-
`Run the mandatory false-positive check on every finding and suppress the unconvincing ones (your findings are treated as already FP-validated).
|
|
161
|
+
`Run the mandatory false-positive check on every finding and suppress the unconvincing ones (your findings are treated as already FP-validated). ` +
|
|
162
|
+
`Then the actionability check: an observation you have VERIFIED needs NO change anywhere (the fix direction would be "no fix required" / "acceptable as-is" / "verified non-issue") is not work — set requires_action:false on it (it is recorded but never sent to a fixer), or simply do not emit it. Emit requires_action true/omitted ONLY when a concrete edit is needed. Set codexAvailable:true when the review ran.`
|
|
161
163
|
|
|
162
164
|
const docPrompt =
|
|
163
165
|
`Cross-card documentation + SSOT-registry review over the batch diff, per ${protocolRef} Step F.3 (doc-reviewer row). Check doc consistency, ssot-registry completeness, and invariants across the changed files.\n\n${scopeBrief}\n\n${baselineBrief}\n\n` +
|
|
164
|
-
`You OWN the doc domain end-to-end: run the mandatory false-positive check on every finding yourself and SUPPRESS the unconvincing ones — your surviving findings are treated as already validated and are NOT re-judged by another agent (a generic code-reviewer judging prose would be cross-domain).
|
|
166
|
+
`You OWN the doc domain end-to-end: run the mandatory false-positive check on every finding yourself and SUPPRESS the unconvincing ones — your surviving findings are treated as already validated and are NOT re-judged by another agent (a generic code-reviewer judging prose would be cross-domain). ` +
|
|
167
|
+
`Then the actionability check: an observation you VERIFIED needs NO change (the fix direction would be "no fix required" / "already accurate") is not work — set requires_action:false (recorded, never sent to a fixer), or do not emit it. Flag only a finding you genuinely cannot resolve as confidence < 80.\n\n` +
|
|
165
168
|
`Return findings (domain almost always "doc"). Use the finding schema fields.`
|
|
166
169
|
|
|
167
170
|
const apiPrompt =
|
|
168
171
|
`API / data-model / performance / cost defect review over the batch diff, per ${protocolRef} Step F.3 (api-perf-cost-auditor row). Look for unbounded reads, N+1, missing pagination, contract drift, and cost regressions.\n\n${scopeBrief}\n\n${baselineBrief}\n\n` +
|
|
169
|
-
`You OWN the api/perf/data domain end-to-end: run the mandatory false-positive check on every finding yourself and SUPPRESS the unconvincing ones — your surviving findings are treated as already validated and are NOT re-judged by another agent.
|
|
172
|
+
`You OWN the api/perf/data domain end-to-end: run the mandatory false-positive check on every finding yourself and SUPPRESS the unconvincing ones — your surviving findings are treated as already validated and are NOT re-judged by another agent. ` +
|
|
173
|
+
`Then the actionability check: an observation you VERIFIED needs NO change (the fix direction would be "no fix required" / "acceptable as-is" / "optional") is not work — set requires_action:false (recorded, never sent to a fixer), or do not emit it. Flag only a finding you genuinely cannot resolve as confidence < 80.\n\n` +
|
|
170
174
|
`Return findings with domain in {code, perf, migration, security}. Use the finding schema fields.`
|
|
171
175
|
|
|
172
176
|
const qaPrompt =
|
|
@@ -238,7 +242,7 @@ for (const item of reviewResults) {
|
|
|
238
242
|
if (!codexRan) {
|
|
239
243
|
codexEngine = 'code-reviewer (fallback)'
|
|
240
244
|
const fb = await agent(
|
|
241
|
-
`Codex was unavailable for the batch final review. Run the FULL code review yourself over the batch diff, per ${protocolRef} Step F.3.\n\n${scopeBrief}\n\n${baselineBrief}\n\nReturn findings using the schema fields, with a self false-positive check applied.`,
|
|
245
|
+
`Codex was unavailable for the batch final review. Run the FULL code review yourself over the batch diff, per ${protocolRef} Step F.3.\n\n${scopeBrief}\n\n${baselineBrief}\n\nReturn findings using the schema fields, with a self false-positive check applied. An observation you VERIFIED needs NO change (fix direction "no fix required" / "acceptable as-is") is not work — set requires_action:false (recorded, never sent to a fixer), or do not emit it.`,
|
|
242
246
|
{ label: 'code-reviewer (fallback)', phase: 'Review', agentType: 'code-reviewer', schema: FINDINGS_SCHEMA }
|
|
243
247
|
)
|
|
244
248
|
// the fallback code-reviewer IS the primary code-review engine here and applied its own FP
|
|
@@ -247,6 +251,12 @@ if (!codexRan) {
|
|
|
247
251
|
if (fb && Array.isArray(fb.findings)) raw.push(...fb.findings.map((f) => ({ ...f, source: 'code-reviewer', preValidated: true })))
|
|
248
252
|
}
|
|
249
253
|
|
|
254
|
+
// Globally-unique finding_id at the fan-in (parity with new-card-review.js). Finders number F###
|
|
255
|
+
// INDEPENDENTLY → a Codex F003 and a doc F003 collide; downstream telemetry/routing (new2 ledger,
|
|
256
|
+
// the skill's F.5 partition) keys on finding_id, so a collision conflates them. Prefix source +
|
|
257
|
+
// running index → uniqueness without losing the human-readable id.
|
|
258
|
+
raw.forEach((f, i) => { f.finding_id = `${f.source || 'src'}#${i}:${f.finding_id || ('F' + i)}` })
|
|
259
|
+
|
|
250
260
|
// ───────────────────────────────────────────────────────────────────────────
|
|
251
261
|
// Phase Verify (F.4) — specialist-owned validation (NOT a generic code-reviewer re-judge).
|
|
252
262
|
// Each domain specialist owns its lane end-to-end: it FP-checks its OWN findings in the
|
|
@@ -292,20 +302,31 @@ async function verifyFinding(f) {
|
|
|
292
302
|
return { ...f, classification: (v && v.classification) || 'NEEDS_MANUAL_CONFIRMATION' }
|
|
293
303
|
}
|
|
294
304
|
|
|
295
|
-
// ---- Result: drop FALSE_POSITIVE, keep VERIFIED + NEEDS_MANUAL
|
|
296
|
-
|
|
305
|
+
// ---- Result: drop FALSE_POSITIVE, segregate no-action, keep actionable VERIFIED + NEEDS_MANUAL --
|
|
306
|
+
// Actionability gate (parity with new-card-review.js): a VERIFIED finding needing NO change must NOT
|
|
307
|
+
// reach the skill's F.5 writer partition. Honoured ONLY for MED/LOW — BLOCKER/HIGH always actionable.
|
|
308
|
+
// No silent drop: no-action findings are returned separately (noActionFindings) + counted, so the
|
|
309
|
+
// skill can log them; only the actionable set flows into `findings` (what F.5 routes to writers).
|
|
310
|
+
const NO_ACTION_RE = /(^|[.\s(])(no\s+(fix|change|action)\s+(is\s+)?(required|needed)|none\s+(needed|required)|not\s+a\s+(defect|bug|correctness\s+bug)|acceptable\s+as[-\s]is|verified\s+(safe|non[-\s]issue)|no\s+action\s+(needed|required))([.\s,;)]|$)/i
|
|
311
|
+
const isNoAction = (f) => f.classification === 'VERIFIED' && (f.severity !== 'BLOCKER' && f.severity !== 'HIGH') &&
|
|
312
|
+
(f.requires_action === false || NO_ACTION_RE.test(String(f.minimal_fix_direction || '')))
|
|
313
|
+
const noActionFindings = classified.filter(isNoAction)
|
|
314
|
+
const noActionSet = new Set(noActionFindings)
|
|
315
|
+
const findings = classified.filter((f) => f.classification !== 'FALSE_POSITIVE' && !noActionSet.has(f))
|
|
316
|
+
for (const f of noActionFindings) log(`Final review: no-action finding recorded (not routed to a writer): [${f.finding_id}] ${f.severity} ${f.domain} — ${f.title} (${f.requires_action === false ? 'requires_action:false' : 'no-action-inferred'})`)
|
|
297
317
|
const summary = {
|
|
298
318
|
total: raw.length,
|
|
299
319
|
verified: classified.filter((f) => f.classification === 'VERIFIED').length,
|
|
320
|
+
noAction: noActionFindings.length,
|
|
300
321
|
falsePositive: classified.filter((f) => f.classification === 'FALSE_POSITIVE').length,
|
|
301
322
|
needsManual: classified.filter((f) => f.classification === 'NEEDS_MANUAL_CONFIRMATION').length,
|
|
302
323
|
blockers: classified.filter((f) => f.classification === 'VERIFIED' && f.severity === 'BLOCKER').length,
|
|
303
324
|
highs: classified.filter((f) => f.classification === 'VERIFIED' && f.severity === 'HIGH').length,
|
|
304
325
|
failingGates: gateTable.filter((g) => g.status === 'FAIL').map((g) => g.gate),
|
|
305
326
|
}
|
|
306
|
-
log(`Final review done: ${summary.verified} verified (${summary.blockers} BLOCKER / ${summary.highs} HIGH), ${summary.needsManual} needs-manual, ${summary.failingGates.length} failing gate(s). Engine: ${codexEngine}.`)
|
|
327
|
+
log(`Final review done: ${summary.verified} verified (${summary.blockers} BLOCKER / ${summary.highs} HIGH), ${summary.noAction} no-action (recorded), ${summary.needsManual} needs-manual, ${summary.failingGates.length} failing gate(s). Engine: ${codexEngine}.`)
|
|
307
328
|
|
|
308
|
-
return { codexEngine, findings, gateTable, summary }
|
|
329
|
+
return { codexEngine, findings, noActionFindings, gateTable, summary }
|
|
309
330
|
|
|
310
331
|
function emptySummary() {
|
|
311
332
|
return { total: 0, verified: 0, falsePositive: 0, needsManual: 0, blockers: 0, highs: 0, failingGates: [] }
|