baldart 5.5.0 → 5.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +64 -0
- package/VERSION +1 -1
- package/framework/.claude/agents/CHANGELOG.md +16 -0
- package/framework/.claude/agents/prd-card-writer.md +37 -3
- package/framework/.claude/skills/new/CHANGELOG.md +23 -0
- package/framework/.claude/skills/new/SKILL.md +1 -1
- package/framework/.claude/skills/new/references/commit.md +12 -8
- package/framework/.claude/skills/new/references/completeness.md +10 -0
- package/framework/.claude/skills/new/references/final-review.md +23 -0
- package/framework/.claude/skills/new/references/implement.md +14 -4
- package/framework/.claude/skills/new/references/merge-cleanup.md +35 -14
- package/framework/.claude/skills/new2/CHANGELOG.md +36 -0
- package/framework/.claude/skills/new2/SKILL.md +52 -12
- package/framework/.claude/skills/prd/CHANGELOG.md +36 -0
- package/framework/.claude/skills/prd/SKILL.md +17 -5
- package/framework/.claude/skills/prd/assets/state-template.md +8 -1
- package/framework/.claude/skills/prd/references/audit-phase.md +13 -1
- package/framework/.claude/skills/prd/references/backlog-phase.md +20 -0
- package/framework/.claude/skills/prd/references/discovery-phase.md +35 -0
- package/framework/.claude/skills/prd/references/ui-design-phase.md +28 -0
- package/framework/.claude/skills/prd/references/validation-phase.md +45 -1
- package/framework/.claude/skills/worktree-manager/CHANGELOG.md +13 -0
- package/framework/.claude/skills/worktree-manager/SKILL.md +11 -1
- package/framework/.claude/skills/worktree-manager/scripts/merge-worktree.sh +82 -6
- package/framework/.claude/workflows/new-final-review.js +9 -7
- package/framework/.claude/workflows/new2-resolve.js +29 -1
- package/framework/.claude/workflows/new2.js +153 -17
- package/framework/agents/card-schema.md +18 -1
- package/framework/agents/research-protocol.md +6 -1
- package/framework/scripts/stamp-holistic-audit.js +53 -10
- package/package.json +1 -1
|
@@ -2,6 +2,42 @@
|
|
|
2
2
|
|
|
3
3
|
Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
|
|
4
4
|
|
|
5
|
+
## 1.6.0 — 2026-07-04 (FEAT-0068 post-mortem: il root cause era qui)
|
|
6
|
+
|
|
7
|
+
- **ISA "UI ROLE FAMILY (registry-first)"** (`discovery-phase.md` Dimension 9, gated su
|
|
8
|
+
`features.has_design_system`): per feature chrome/primitive enumera i PROPRIETARI della
|
|
9
|
+
superficie del goal dal registry (role families / Selection Policy), non solo i consumer
|
|
10
|
+
esistenti del meccanismo; **flag BLOCK su `@deprecated`** nel fix-path (i successori
|
|
11
|
+
documentati diventano il target). Era: DrilldownHeader/SectionHeader (~90% degli screen)
|
|
12
|
+
in NESSUNA card, ricetta ancorata su un Header deprecato da 5 pagine.
|
|
13
|
+
- **§0 LOAD CONTRACT** in testa a `discovery-phase.md`: il modulo supera il cap del Read —
|
|
14
|
+
vista PARTIAL ⇒ offset-Read obbligatori fino alla fine (la coda persa conteneva Exit
|
|
15
|
+
condition + Codex completeness cross-check + Research Trigger, mai eseguiti sul run reale).
|
|
16
|
+
- **`module_read` stamps** (SKILL.md): la lettura dei moduli di fase è ESEGUITA non recitata
|
|
17
|
+
— stamp nello state file, verificato dal validator (3 moduli mai letti sul run reale).
|
|
18
|
+
- **`step_3_mode` enum CHIUSO + contratto `skipped`** (`ui-design-phase.md`): `skipped` =
|
|
19
|
+
NESSUN artefatto di design, `links.design` vuoto, ogni explainer passa da `/ui-design`
|
|
20
|
+
(era: modalità fabbricata `design-spec` + design.html hand-authored su 5/6 card);
|
|
21
|
+
**Reconciliation-lite** (no mockup ≠ no reconciliation: role family dal registry ⇒
|
|
22
|
+
`component_bindings`); **trigger di ri-enumerazione ISA** quando la ricetta cambia target.
|
|
23
|
+
- **Gate quantificatore-universale sugli AC epic** (`validation-phase.md` item e):
|
|
24
|
+
"ogni schermata" ⇒ owner della superficie (census fresco) ⊆ unione scope figli, else
|
|
25
|
+
BLOCKER; `invariant_owners:` persistiti nell'epic.
|
|
26
|
+
- **Item 0-ii Phase-evidence audit** (`validation-phase.md`): stamp module_read + gate con
|
|
27
|
+
evidenza (API Perf Gate stampato `N/A` senza spawn sul run reale) + enum step_3_mode.
|
|
28
|
+
- **Stamp holistic evidence-gated** (`stamp-holistic-audit.js --audit-report` →
|
|
29
|
+
`agents_run`/`findings_count`; senza artefatto → `status: skipped` che `/new` tratta come
|
|
30
|
+
ASSENTE). Era: stamp su 6/6 card con zero spawn di audit (phantom stamp).
|
|
31
|
+
- **Card-writer self-validation** (`backlog-phase.md` + agente): il validator gira
|
|
32
|
+
nell'output del writer PRIMA del return (era: round-trip completo su EARS in italiano);
|
|
33
|
+
**Rule A ramo deterministico** (files docs/test-only ⇒ owner coder, mai `links.design`);
|
|
34
|
+
epic con `ac_verification` obbligatoria + mai count hardcodato; invariante lifecycle
|
|
35
|
+
baseline per suite snapshot.
|
|
36
|
+
- **Step 7.9 HARD RULE mw-docs** (`validation-phase.md`): mai `/mw` generico su worktree
|
|
37
|
+
`kind: docs` (+ guardia lato worktree-manager); era ~4M di BUILD_FAIL falso + unblock umano.
|
|
38
|
+
- **EDIT CONTRACT dello state file** (template + HARD RULE 2): anchor sul bare `key:` con
|
|
39
|
+
literal appena letto (5 retry ciechi da ~250k sul run reale).
|
|
40
|
+
|
|
5
41
|
## 1.5.0 — 2026-07-03 (framework v5.4.0)
|
|
6
42
|
|
|
7
43
|
- **Step 2.1 Tier Gate (elastic pipeline).** At Discovery exit the feature is classified
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: prd
|
|
3
3
|
effort: high
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.6.0
|
|
5
5
|
description: "Structured PRD creation skill. Use when the user says /prd, 'crea un prd', 'nuova funzionalita', 'pianifica feature', or wants to plan a new feature end-to-end. Also handles /prd-add, 'aggiungi requisito', 'serve anche', 'manca un endpoint' for change requests on active PRD sessions — runs ICIAS impact analysis to determine which phases need SKIP/PATCH/REDO. Produces PRD + UI design + backlog cards, all validated and committed. Multi-turn conversation: asks questions, waits for answers, iterates through discovery, design, spec writing, card creation, and validation phases."
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -54,9 +54,17 @@ message. You ask questions, wait for answers, and iterate.
|
|
|
54
54
|
| 7 | Resolution, Commit & Merge | [validation-phase.md](references/validation-phase.md) |
|
|
55
55
|
| 7.6 | Obsidian back-reference — FINAL (upgrades the 1.2 provisional block; only if a spec note was given) | [validation-phase.md](references/validation-phase.md) + [obsidian-backref.md](references/obsidian-backref.md) |
|
|
56
56
|
|
|
57
|
-
**Before starting each phase, read the corresponding reference file
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
**Before starting each phase, read the corresponding reference file — EXECUTED, not
|
|
58
|
+
recited (v5.6.0).** After reading a phase module, record `module_read: <file> (full)` in the
|
|
59
|
+
state file's section for that phase; if the Read reported a PARTIAL view (the module exceeds
|
|
60
|
+
the token cap — `discovery-phase.md` does), issue offset Reads until the END of the file
|
|
61
|
+
before proceeding and stamp `(full, N reads)`. The Step 6 validator (`validation-phase.md`
|
|
62
|
+
item 0-ii) checks these stamps deterministically. This exists because a real session ran
|
|
63
|
+
Steps 2.5/4/4.5/5 with `research-phase.md`, `prd-writing-phase.md` and `backlog-phase.md`
|
|
64
|
+
NEVER read (6 Reads total in 73 turns): the improvised Step 5 contract produced a full
|
|
65
|
+
validator-rework round, and the truncated discovery tail silently dropped the mandatory Codex
|
|
66
|
+
completeness cross-check. ONE exception class: **agent-facing specs are passed BY PATH to the
|
|
67
|
+
agent that consumes them and MUST NOT be loaded into the orchestrator context.** Today these are
|
|
60
68
|
`references/api-perf-gate.md` (read by `api-perf-cost-auditor` at Step 4.5 and at the
|
|
61
69
|
Step 6 audit) and `references/audit-teammate-prompt.md` (read by each Step 6.6 audit
|
|
62
70
|
teammate). Rationale: a spec loaded by a subagent costs one cheap Read in a disposable
|
|
@@ -70,7 +78,11 @@ for the rest of the run.
|
|
|
70
78
|
> **Runtime portability.** On **Codex**, bind this skill's mechanics per `framework/agents/runtime-portability-protocol.md` (named-agent spawn, decision gates, file-backed state; never probe `Workflow`/`AskUserQuestion`/the task spine). Rules below are Claude-first with explicit **Codex:** branches (RULES 4, 11) — additive, Claude imperatives unchanged.
|
|
71
79
|
|
|
72
80
|
1. Follow steps in order. NEVER skip a step or jump ahead.
|
|
73
|
-
2. Every step MUST update the state file before proceeding.
|
|
81
|
+
2. Every step MUST update the state file before proceeding. **State-file Edit hygiene
|
|
82
|
+
(v5.6.0):** anchor every Edit on the bare `key:` prefix with a literal you just Read —
|
|
83
|
+
never a recalled literal with trailing comments/alignment/blank lines (the template's
|
|
84
|
+
EDIT CONTRACT header; 5 blind retries at ~250k each on a real session). A failed match →
|
|
85
|
+
Read the exact lines, retry ONCE with the fresh literal.
|
|
74
86
|
3. Every gate MUST be satisfied before moving to the next step.
|
|
75
87
|
4. **Progress tracking.** Claude Code: use TaskCreate/TaskUpdate for ALL progress tracking. **Codex** (no task spine): use a file-backed queue at `$WORKTREE_PATH/${paths.prd_dir}/sessions/<slug>-tasks.jsonl` — one record per task (`status` / `claimed_by` / `findings_path`) — and the session **state file** stays the recovery SSOT either way. (Binding: `framework/agents/runtime-portability-protocol.md` § state-spine.)
|
|
76
88
|
5. When context is compressed or session resumes: run Step 0 first.
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
# PRD Session: {{slug}}
|
|
2
|
+
|
|
3
|
+
<!-- EDIT CONTRACT (v5.6.0 — N8, 5 failed Edits at ~250k context each on a real session):
|
|
4
|
+
when updating a `key: value` line in this file, ANCHOR ON THE BARE KEY PREFIX ONLY
|
|
5
|
+
(`old_string` = from start-of-line through the current value you just Read — never a
|
|
6
|
+
recalled-from-memory literal that includes trailing `# comments`, column alignment, or
|
|
7
|
+
surrounding blank lines; those drift). If an Edit fails to match, Read the exact lines
|
|
8
|
+
FIRST and retry once with the fresh literal — never loop blind retries. -->
|
|
2
9
|
Created: {{YYYY-MM-DD HH:MM}}
|
|
3
10
|
Status: discovery # discovery | mockups-pending-input | research-in-progress | pending-external-handoff | ui-design | writing | specs-confirmed | backlog | audit | validation | committing | completed | done
|
|
4
11
|
stats_enabled: {{true if invoked with -stats/--stats, else false}}
|
|
@@ -121,7 +128,7 @@ mockup_analysis.design_system_alignment:
|
|
|
121
128
|
status: pending # aligned | violations | not-applicable
|
|
122
129
|
violations: []
|
|
123
130
|
|
|
124
|
-
step_3_mode: pending # full | hybrid | skipped | mockup-only
|
|
131
|
+
step_3_mode: pending # CLOSED ENUM: full | hybrid | skipped | mockup-only | pending_external_handoff — any other value is a Step 6 BLOCKER (v5.6.0)
|
|
125
132
|
design.html_path: pending # path | n/a (mockup-only)
|
|
126
133
|
|
|
127
134
|
screens_in_scope: [] # [{ name, slot_id, screen_id, canvas_ref, covered_by_mockups: bool, generated_in_step_3b: bool }]
|
|
@@ -398,8 +398,19 @@ metadata:
|
|
|
398
398
|
audited_at: "<HOLISTIC_AUDITED_AT>"
|
|
399
399
|
audited_commit: "<HOLISTIC_AUDITED_COMMIT>" # "" if git unavailable → /new treats as drift (runs Step 3d)
|
|
400
400
|
audited_set: ["FEAT-XXXX-01", "FEAT-XXXX-02", ...] # sorted; the joint cross-card scope
|
|
401
|
+
agents_run: ["plan-auditor", "codebase-architect", ...] # v5.6.0 EVIDENCE — written by the
|
|
402
|
+
# stamper from the audit report artifact (--audit-report). A stamp WITHOUT a non-empty
|
|
403
|
+
# agents_run is treated as ABSENT by /new P1 (the plan-auditor runs): on FEAT-0068 the
|
|
404
|
+
# stamp landed on all 6 cards while the audit team never spawned (phantom stamp).
|
|
405
|
+
findings_count: 3 # optional, from the report
|
|
401
406
|
```
|
|
402
407
|
|
|
408
|
+
**Write the audit report artifact FIRST (v5.6.0 — the stamp's evidence).** Before running the
|
|
409
|
+
stamper, persist the Step 6.6 audit outcome to `/tmp/prd-audit-<slug>.md` with at minimum a
|
|
410
|
+
literal `agents_run: <comma-separated agent types actually spawned>` line and a
|
|
411
|
+
`findings_count: <N>` line. No spawns happened → do NOT fabricate the file: run the stamper
|
|
412
|
+
without `--audit-report` and it stamps `status: "skipped"` honestly.
|
|
413
|
+
|
|
403
414
|
> **Fail-safe contract**: the block is an OPTIMIZATION hint for `/new`, never a *correctness* gate — if `audited_commit` is empty or the block is absent, `/new` Step 3d **and** the `implement.md` Phase-1 plan-auditor grounding (step 4, P1) simply RUN as before (redundant, never wrong). **But writing the block is MANDATORY whenever the audit ran.** It is the single most-omitted step under context pressure, and its absence is exactly what makes `/new` / `new2` spawn the plan-auditor on **every** freshly-authored card even when the batch is implemented immediately after this audit (nothing changed since). Do NOT skip it "to save a write". If git is genuinely unavailable, write `audited_commit: ""` **explicitly** — never omit the block. `validation-phase.md` Step 7 item 5b is the **deterministic backstop** that guarantees this lands before the PRD commit even if this per-card write is missed — but write it here too; the backstop is a safety net, not a license to skip.
|
|
404
415
|
|
|
405
416
|
**Deterministic writer — EXECUTE, don't hand-edit.** `framework/scripts/stamp-holistic-audit.js` mechanizes this write (idempotent, additive, epic-skipping, self-verifying). Run it ONCE for the whole card set after the per-card field edits:
|
|
@@ -407,7 +418,8 @@ metadata:
|
|
|
407
418
|
```bash
|
|
408
419
|
node .framework/framework/scripts/stamp-holistic-audit.js \
|
|
409
420
|
--at "$HOLISTIC_AUDITED_AT" --commit "$HOLISTIC_AUDITED_COMMIT" \
|
|
410
|
-
--set "<sorted child IDs, comma-separated>"
|
|
421
|
+
--set "<sorted child IDs, comma-separated>" \
|
|
422
|
+
--audit-report "/tmp/prd-audit-<slug>.md" ${CARD_PATHS}
|
|
411
423
|
```
|
|
412
424
|
|
|
413
425
|
Hand-edit the YAML only when the script is genuinely unreachable in this install — and say so in the audit log.
|
|
@@ -22,6 +22,19 @@ and BLOCK the commit. The `/new` orchestrator dispatches implementation
|
|
|
22
22
|
agent by this field (see `framework/.claude/skills/new/SKILL.md` § Agent
|
|
23
23
|
Routing), so a wrong value silently routes the card to the wrong specialist.
|
|
24
24
|
|
|
25
|
+
**Rule A deterministic branch (v5.6.0 — FEAT-0068):** when a card's
|
|
26
|
+
`files_likely_touched` are ALL docs/test/config/meta paths (zero `src/` UI or
|
|
27
|
+
component/style files), `owner_agent` MUST be `coder` regardless of the
|
|
28
|
+
feature's topic — a docs+ADR+test card for a UI epic is still a coder card
|
|
29
|
+
(`owner_agent: ui-expert` with zero UI paths is a Step 6 BLOCKER). Also do NOT
|
|
30
|
+
stamp `links.design` on such a card: `links.design` goes ONLY on cards whose
|
|
31
|
+
scope implements mocked regions (`component_bindings` present or
|
|
32
|
+
`files_likely_touched` intersecting the mocked components); on anything else it
|
|
33
|
+
is a stray pointer that clamps the build to ui-expert downstream (`/new`
|
|
34
|
+
mockup-first router) — the FEAT-0068 docs card cost 6.55M for exactly this.
|
|
35
|
+
This separates HETEROGENEOUS domains; it does not contradict the v5.4.0
|
|
36
|
+
proportionality floor (which discourages splitting a small HOMOGENEOUS change).
|
|
37
|
+
|
|
25
38
|
### Rule B — UI cards have UI-only scope (no mixed scope)
|
|
26
39
|
|
|
27
40
|
A card with `owner_agent: ui-expert` MUST be scoped EXCLUSIVELY to graphical
|
|
@@ -263,6 +276,13 @@ The `prd-card-writer` agent owns the entire card writing pipeline:
|
|
|
263
276
|
- State file update with card list and matrices
|
|
264
277
|
- `env_vars` field: for every card that introduces/modifies/removes env vars (detectable from PRD Section 6 or requirements that mention `process.env`, secrets, API keys, feature flags), populate the field with `action: new|modified|removed`, `scope`, `required`, `note`. If the card does not touch env vars, write `env_vars: []`.
|
|
265
278
|
- `review_profile` field: deterministic per-card review depth (`skip|light|balanced|deep`). The decision criteria (how each profile is chosen) live ONLY in `prd-card-writer.md § Rule C` — see that file for the criteria. The `/new` orchestrator reads this value from the card and runs an escalation-only detector; it does NOT re-derive from a duplicate criteria table. Epics always get `review_profile: skip`. Honor any explicit author override already present in the YAML.
|
|
279
|
+
- **Self-validation BEFORE returning (v5.6.0 — reviewer-owns-its-domain, FEAT-0068 N6):** the
|
|
280
|
+
card-writer brief MUST require the agent to RUN the deterministic validator on its own output
|
|
281
|
+
(`node .framework/framework/scripts/validate-card-baseline.js --prd <card files>`) and return
|
|
282
|
+
only on exit 0, fixing its own deterministic defects (enums, EARS token language, forbidden
|
|
283
|
+
patterns) in-context. Step 6 re-runs the same validator as the independent gate — but a
|
|
284
|
+
deterministic failure discovered only at Step 6 costs a full orchestrator→agent round-trip
|
|
285
|
+
(~1.5M + a resume on the real session, where all 6 children bounced on Italian EARS tokens).
|
|
266
286
|
|
|
267
287
|
### What stays in the main context
|
|
268
288
|
|
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Discovery Phase (Steps 0-2)
|
|
2
2
|
|
|
3
|
+
> **§0 — LOAD CONTRACT (v5.6.0 — read this first; it exists because of a real truncation).**
|
|
4
|
+
> This module is LARGER than the Read tool's default token cap. A plain `Read` returns a
|
|
5
|
+
> **PARTIAL view** (the banner says so) and silently drops the TAIL — which contains the
|
|
6
|
+
> **Exit condition**, the **mandatory Codex Discovery Completeness Cross-Check**, the
|
|
7
|
+
> **Research Trigger**, and the `mockup_analysis` schema. On the FEAT-0068 session exactly
|
|
8
|
+
> that happened: lines 953+ were never loaded, the cross-check never ran, and the coverage
|
|
9
|
+
> blind spot it exists to catch (integration owners no dimension modelled) shipped into the
|
|
10
|
+
> cards. HARD RULE: **if your Read of this file reports a partial view, you MUST issue
|
|
11
|
+
> follow-up Read calls with `offset` until you have seen the end of the file — BEFORE
|
|
12
|
+
> closing any discovery dimension.** Record `module_read: discovery-phase.md (full, N reads)`
|
|
13
|
+
> in the state file's phase section (the Step 6 validator checks the stamp). The
|
|
14
|
+
> tail-critical gates, so you know what you are looking for: (1) Exit condition — all
|
|
15
|
+
> dimensions covered + user validation; (2) Codex Discovery Completeness Cross-Check —
|
|
16
|
+
> MANDATORY sweep for the questioner's own blind spots before closing; (3) Research
|
|
17
|
+
> Trigger — background launch at 6+ dimensions; (4) `mockup_analysis` schema.
|
|
18
|
+
|
|
3
19
|
## Step 0 — Context Recovery
|
|
4
20
|
|
|
5
21
|
Run BEFORE any other step.
|
|
@@ -815,6 +831,25 @@ Instead, run the ISA scan and present findings for validation.
|
|
|
815
831
|
the picker-mode call, while the live catalog-mode consumer page was never
|
|
816
832
|
enumerated → it surfaced as a HIGH only at the final review.) Grep the function
|
|
817
833
|
name across the codebase and list each distinct caller file:line.
|
|
834
|
+
- UI ROLE FAMILY — registry-first surface OWNERSHIP (v5.6.0; only when
|
|
835
|
+
features.has_design_system is true AND the feature's recipe targets a chrome/
|
|
836
|
+
primitive ROLE: "the header/top edge of every screen", bottom bars, anchored
|
|
837
|
+
overlays/toasts, page shells). Every other category above enumerates EXISTING
|
|
838
|
+
code that references the feature's mechanism — but a component that LACKS the
|
|
839
|
+
mechanism is structurally invisible to that grep. This category enumerates the
|
|
840
|
+
OWNERS of the goal surface instead: read the design-system registry
|
|
841
|
+
(${paths.design_system}/INDEX.md role families / Selection Policy +
|
|
842
|
+
components/<Name>.md) for the family that renders that role, list EVERY member
|
|
843
|
+
as its OWN touchpoint (with its consumer count: `grep -rl <Component> | wc -l`),
|
|
844
|
+
and mark each HIGH (the goal is invisible wherever an unlisted member renders).
|
|
845
|
+
**@deprecated flag (BLOCKING):** if a family member the recipe anchors on carries
|
|
846
|
+
a `@deprecated` docblock, the touchpoint MUST name the documented successors and
|
|
847
|
+
the recipe MUST target them — anchoring a surface-wide goal on a deprecated
|
|
848
|
+
member is a scope error, not a style choice. (FEAT-0068: the ISA enumerated the
|
|
849
|
+
44 existing consumers of env(safe-area-*) and anchored "the header" on a
|
|
850
|
+
@deprecated Header.tsx serving 5 pages, while the successors DrilldownHeader/
|
|
851
|
+
SectionHeader — 63 files, ~90% of screens — appeared in NO card; the coverage
|
|
852
|
+
gate then passed 44/44 vacuously against the wrong inventory.)
|
|
818
853
|
|
|
819
854
|
For each touchpoint found, report:
|
|
820
855
|
- Category (from above)
|
|
@@ -114,6 +114,34 @@ dimension from the discovery checklist. Pick exactly one branch:
|
|
|
114
114
|
| `mockups.status: none` AND `mockups.source: internal` | **Full** | Run the standard pipeline 3a → 3b → 3c → 3d → 3e as below. `step_3_mode: full`. |
|
|
115
115
|
| `mockups.status` ∈ {`provided`, `partial`} AND UI impact ≠ N/A | **Hybrid** | Per-screen routing (see § Hybrid mode below). `step_3_mode: hybrid` (or `mockup-only` if every screen is covered AND no `mockup_analysis.design_system_alignment.violations` — distinct from `skipped`, which means no UI at all). |
|
|
116
116
|
|
|
117
|
+
**`step_3_mode` is a CLOSED ENUM (v5.6.0): `full | hybrid | mockup-only | skipped |
|
|
118
|
+
pending_external_handoff`.** Writing any other value is a protocol violation the Step 6
|
|
119
|
+
validator now rejects deterministically. The FEAT-0068 session invented `design-spec` when the
|
|
120
|
+
user declined mockups on a UI feature — an unscoped gap the model filled by fabrication — and
|
|
121
|
+
the hand-authored inline "visual explainer" it produced became `links.design` on 5/6 cards,
|
|
122
|
+
routing a docs card to ui-expert downstream. The rules that close that gap:
|
|
123
|
+
- **`skipped` means NO design artifact.** No inline design.html, no "visual spec" Write, and
|
|
124
|
+
`links.design` stays EMPTY on every card. Any visual explainer the user asks for goes through
|
|
125
|
+
`Skill(ui-design)` (which runs its craft gates) — the orchestrator NEVER hand-authors design
|
|
126
|
+
HTML.
|
|
127
|
+
- **UI feature + user declines mockups is still `skipped`** (or Full if the user accepts
|
|
128
|
+
internal generation) — there is no third "spec-only" lane. What the cards need instead is
|
|
129
|
+
§ Reconciliation-lite below.
|
|
130
|
+
- **Reconciliation-lite (v5.6.0 — "no mockup" must NOT mean "no reconciliation").** When
|
|
131
|
+
`step_3_mode: skipped` (or `mockup-only`) AND the feature touches design-system primitives
|
|
132
|
+
(`features.has_design_system: true`), STILL run the registry half of Component
|
|
133
|
+
Reconciliation: resolve the affected ROLE FAMILIES from `${paths.design_system}/INDEX.md`
|
|
134
|
+
(Selection Policy + role families — same lens as the ISA "UI ROLE FAMILY" category, which by
|
|
135
|
+
now has enumerated the owners), and emit `component_bindings` for the cards from the registry
|
|
136
|
+
alone. The FEAT-0068 chain — mockups skipped → reconciliation never ran → the registry that
|
|
137
|
+
named DrilldownHeader/SectionHeader as the canonical headers was never consulted → the recipe
|
|
138
|
+
anchored on a @deprecated component — is exactly what this closes.
|
|
139
|
+
- **Re-enumeration trigger (v5.6.0).** If a design/recipe decision taken IN THIS PHASE changes
|
|
140
|
+
WHAT the feature applies to (a different component family, a broader/narrower surface than
|
|
141
|
+
discovery assumed), the ISA surface inventory is STALE: re-run the affected ISA category
|
|
142
|
+
(Dimension 9 — at minimum UI ROLE FAMILY) before PRD writing. Locking a recipe after the scan
|
|
143
|
+
without re-enumerating is how a correct inventory goes wrong silently.
|
|
144
|
+
|
|
117
145
|
Mark task 2 as `in_progress` for the Full and Hybrid branches.
|
|
118
146
|
|
|
119
147
|
## Delegation to `ui-design` Skill
|
|
@@ -52,6 +52,19 @@ Mark task 5 as `in_progress`.
|
|
|
52
52
|
surface / ISA file owned by NO card is a BLOCKER (this is the gap that otherwise surfaces only
|
|
53
53
|
at `/new`'s expensive final review). Message: `"ISA touchpoint <path> (<category>) is owned by
|
|
54
54
|
no card — assign it to a card's files_likely_touched."`
|
|
55
|
+
- **Universal-quantifier epic AC gate (v5.6.0 — FEAT-0068):** for every EPIC acceptance
|
|
56
|
+
criterion carrying a universal quantifier over a UI/route surface ("ogni schermata",
|
|
57
|
+
"tutte le route", "every screen", "all pages", "nessun <difetto> su <superficie>"),
|
|
58
|
+
derive the surface's OWNER components from the **fresh registry/ISA census** (the
|
|
59
|
+
UI ROLE FAMILY touchpoints of discovery Dimension 9 — NOT the raw mechanism-consumer
|
|
60
|
+
touchpoints, or you inherit an incomplete inventory) and verify each owner appears in
|
|
61
|
+
the union of the child cards' `files_likely_touched`/scope. An owner covered by NO
|
|
62
|
+
child is a BLOCKER: `"Epic AC-<n> quantifies over <surface> but owner <component>
|
|
63
|
+
(<N> files) belongs to no card."` Persist the resolved owner list as
|
|
64
|
+
`invariant_owners:` in the epic YAML so the `/new` epic-closure gate can re-verify
|
|
65
|
+
it. (FEAT-0068: the epic promised "no seam on every screen" while DrilldownHeader/
|
|
66
|
+
SectionHeader — ~90% of screens — belonged to no card; the ISA→card gate passed
|
|
67
|
+
44/44 vacuously because the inventory itself was wrong.)
|
|
55
68
|
Both are SEMANTIC (path extraction from prose / ISA needs interpretation) — bias toward flagging.
|
|
56
69
|
|
|
57
70
|
**Gate**: the 0-i validator exits 0 on every card (enums, conditional `requirements`, zero
|
|
@@ -59,6 +72,22 @@ Mark task 5 as `in_progress`.
|
|
|
59
72
|
checks (b)/(c) are noted in the audit log; user may accept them after review. The 0-i and (e)
|
|
60
73
|
BLOCKERs must be fixed before commit.
|
|
61
74
|
|
|
75
|
+
0-ii. **Phase-evidence audit (v5.6.0 — executed, not recited; FEAT-0068 shipped TWO phantom
|
|
76
|
+
gates).** Deterministic checks on the STATE FILE before the audit protocol runs:
|
|
77
|
+
- **`module_read` stamps**: each executed phase section must carry its `module_read:
|
|
78
|
+
<reference file> (full)` stamp (SKILL.md § phase-module loading). A phase that ran with
|
|
79
|
+
its module unread is a protocol violation to surface — on FEAT-0068, `research-phase.md`,
|
|
80
|
+
`prd-writing-phase.md` and `backlog-phase.md` were never read and the improvised Step 5
|
|
81
|
+
contract produced the EARS rework round.
|
|
82
|
+
- **Gate stamps carry evidence**: any gate recorded as run/passed/`N/A` (API Performance
|
|
83
|
+
Gate, holistic audit, Codex Discovery Cross-Check) must name its evidence — an agent
|
|
84
|
+
spawn/report path for agent gates, the concrete reason for an `N/A`. A gate stamp with no
|
|
85
|
+
evidence is treated as NOT RUN (the FEAT-0068 session stamped `pass # N/A` on the API
|
|
86
|
+
gate without ever spawning the auditor, and the holistic stamp landed with zero audit
|
|
87
|
+
spawns).
|
|
88
|
+
- **`step_3_mode` ∈ {full, hybrid, mockup-only, skipped, pending_external_handoff}**: any
|
|
89
|
+
other value (the session fabricated `design-spec`) is a BLOCKER — fix the state file and
|
|
90
|
+
re-run the branch it should have taken.
|
|
62
91
|
1. **Field Name Audit (runs after item 0 — only when item 0 passes for a given card; skipped for cards blocked in item 0)** — for each generated card:
|
|
63
92
|
|
|
64
93
|
a. Verify every `data_fields` entry has `ts_verified: true` OR `status: new` with `schema_ref`.
|
|
@@ -142,9 +171,15 @@ created by Step 1 (HARD RULE 17). `$WORKTREE_PATH` is set in the state file.
|
|
|
142
171
|
HOLISTIC_AUDITED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
143
172
|
HOLISTIC_AUDITED_COMMIT="$(git -C "$WORKTREE_PATH" merge-base HEAD "${git.trunk_branch}" 2>/dev/null || git -C "$WORKTREE_PATH" rev-parse "${git.trunk_branch}" 2>/dev/null || echo "")"
|
|
144
173
|
# --set = the sorted Step-5 child card IDs (the joint cross-card scope), comma-separated.
|
|
174
|
+
# --audit-report (v5.6.0, REQUIRED for a full stamp) = the audit-phase report artifact
|
|
175
|
+
# (e.g. /tmp/prd-audit-<slug>.md, written by audit-phase Step 6.9 with its `agents_run:`
|
|
176
|
+
# line). NO artifact on disk = the audit team never ran (the FEAT-0068 phantom stamp) →
|
|
177
|
+
# run WITHOUT the flag and the script writes `status: skipped` + NO agents_run, which
|
|
178
|
+
# /new's P1 treats as ABSENT (plan-auditor runs — fail-safe, exactly right).
|
|
145
179
|
node .framework/framework/scripts/stamp-holistic-audit.js \
|
|
146
180
|
--at "$HOLISTIC_AUDITED_AT" --commit "$HOLISTIC_AUDITED_COMMIT" \
|
|
147
|
-
--set "<ID1,ID2,...>" "
|
|
181
|
+
--set "<ID1,ID2,...>" --audit-report "/tmp/prd-audit-<slug>.md" \
|
|
182
|
+
"$WORKTREE_PATH/${paths.backlog_dir}"/FEAT-XXXX-*.yml
|
|
148
183
|
```
|
|
149
184
|
The script is **idempotent** (a card already stamped by Step 6.9.4 is untouched),
|
|
150
185
|
**additive** (never overwrites other `metadata` keys), skips EPIC cards (they carry
|
|
@@ -223,6 +258,15 @@ created by Step 1 (HARD RULE 17). `$WORKTREE_PATH` is set in the state file.
|
|
|
223
258
|
Input: { "worktreePath": "$WORKTREE_PATH" }
|
|
224
259
|
```
|
|
225
260
|
|
|
261
|
+
**HARD RULE (v5.6.0 — FEAT-0068 N1): `mw-docs`, NEVER generic `/mw`, for a docs worktree.**
|
|
262
|
+
The registry entry for this worktree says `kind: "docs"` — a generic `/mw` on it runs the
|
|
263
|
+
forbidden unbounded `npm run build` (a docs worktree has no node_modules → guaranteed false
|
|
264
|
+
BUILD_FAIL, manual recovery, classifier-blocked push; ~4M wasted on the real session). If
|
|
265
|
+
you find yourself about to call `/mw`, or to invent invocation args (`checksAlreadyPassed`
|
|
266
|
+
is NOT an input of this contract), STOP — the invocation above is the whole contract.
|
|
267
|
+
`/mw` itself refuses a `kind: "docs"` worktree since v5.6.0 (worktree-manager pre-flight),
|
|
268
|
+
but do not rely on the belt: use the right mode.
|
|
269
|
+
|
|
226
270
|
The skill executes:
|
|
227
271
|
- Safety commit (idempotent — likely no-op after step 8; if any file was
|
|
228
272
|
missed by item 6's explicit list, the safety commit picks it up, but the
|
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
|
|
4
4
|
|
|
5
|
+
## 1.2.0 — 2026-07-04 (FEAT-0068 post-mortem)
|
|
6
|
+
|
|
7
|
+
- **`merge-worktree.sh --safety-sweep <all|allowlist:globs>`**: in modalità allowlist il
|
|
8
|
+
safety commit pre-rebase stagia SOLO i file che matchano (bookkeeping del batch), con
|
|
9
|
+
messaggio onesto che li enumera; le modifiche TRACKED fuori allowlist vengono messe in
|
|
10
|
+
quarantena byte-per-byte (`quarantined:` nel manifest) e ripristinate a HEAD, gli
|
|
11
|
+
untracked restano su disco (`uncommitted_left:`). Default `all` invariato per `/mw`
|
|
12
|
+
interattivo. Chiude lo split-brain col F-030 del chiamante ("[safety] Auto-commit" da
|
|
13
|
+
751 righe opaco su develop, ricorrente FEAT-0067/FEAT-0068).
|
|
14
|
+
- **Guardia docs-worktree in `/mw`** (BLOCKING, step 1): registry `kind: "docs"` ⇒ rifiuto
|
|
15
|
+
con puntatore a `mw-docs` (un `/mw` generico su un docs worktree = build unbounded senza
|
|
16
|
+
node_modules ⇒ BUILD_FAIL falso garantito, ~4M su un run `/prd` reale).
|
|
17
|
+
|
|
5
18
|
## 1.1.0 — 2026-07-03
|
|
6
19
|
|
|
7
20
|
### Fixed — local-trunk sync robustness (step 4c "Common", both strategies)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: worktree-manager
|
|
3
3
|
effort: medium
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
description: >
|
|
6
6
|
Manage fully independent git worktrees for parallel coding agents.
|
|
7
7
|
Commands: /nw (new worktree) creates an isolated workspace with its own
|
|
@@ -685,6 +685,16 @@ Supports two modes:
|
|
|
685
685
|
|
|
686
686
|
Read `.worktrees/registry.json`. If multiple entries exist or user didn't specify, list them and ask which to merge.
|
|
687
687
|
|
|
688
|
+
**Docs-worktree guard (v5.6.0 — BLOCKING, before anything else).** If the resolved registry
|
|
689
|
+
entry carries `kind: "docs"`, this is the WRONG mode: REFUSE and route to `mw-docs`
|
|
690
|
+
(programmatic: `Mode: programmatic, mw-docs`). A generic `/mw` on a docs worktree runs the
|
|
691
|
+
build/check pipeline against a tree with no node_modules → a guaranteed false BUILD_FAIL,
|
|
692
|
+
manual recovery, and a blocked push (observed on a real `/prd` session: ~4M tokens + a human
|
|
693
|
+
unblock). Message: `"WRONG MODE: worktree '<slug>' is kind:docs — use mw-docs (docs merge:
|
|
694
|
+
no build, additive-registry resolution). Refusing generic /mw."` Also ignore any invented
|
|
695
|
+
invocation args (`checksAlreadyPassed` is a `/new`-orchestrator flag, not a user contract) —
|
|
696
|
+
the registry entry is the SSOT for what kind of merge this is.
|
|
697
|
+
|
|
688
698
|
Fallback: `git worktree list` if registry is missing.
|
|
689
699
|
|
|
690
700
|
Determine, reading them from the worktree's registry entry (R6):
|
|
@@ -48,6 +48,21 @@
|
|
|
48
48
|
# --tc-build <cmd> toolchain build cmd (default: config / npm run build)
|
|
49
49
|
# --log <path> build/merge log sink (default: /tmp/wt-merge-<slug>.log)
|
|
50
50
|
# --build-timeout <s> hard build timeout sec (default: 600)
|
|
51
|
+
# --safety-sweep <mode> what the pre-rebase safety commit may stage (v5.6.0):
|
|
52
|
+
# all — every changed+untracked file (default;
|
|
53
|
+
# the historical /mw interactive behavior)
|
|
54
|
+
# allowlist:<globs> — comma-separated shell globs (e.g.
|
|
55
|
+
# "backlog/*.yml,docs/metrics/*,.baldart/*").
|
|
56
|
+
# Matching files → committed with an honest message that
|
|
57
|
+
# ENUMERATES them. Non-matching TRACKED changes → preserved
|
|
58
|
+
# byte-for-byte into a labeled quarantine dir, then restored
|
|
59
|
+
# to HEAD (so the rebase can start). Non-matching UNTRACKED
|
|
60
|
+
# files → left in place. Both reported in the manifest.
|
|
61
|
+
# Rationale (FEAT-0068 5f618347d / FEAT-0067 5a5f1c135): an
|
|
62
|
+
# unconditional sweep contradicts the caller's F-030 "never
|
|
63
|
+
# commit ungated code" by construction — dirty code at merge
|
|
64
|
+
# time did NOT pass the per-card gates and must never ride an
|
|
65
|
+
# opaque "[safety]" commit onto the trunk.
|
|
51
66
|
#
|
|
52
67
|
# Status file written (parsed by callers — STABLE contract):
|
|
53
68
|
# status: success | code_conflict | build_fail | sync_needs_decision | error
|
|
@@ -63,6 +78,8 @@
|
|
|
63
78
|
# sync_marker: <none | SYNC-DEFERRED | SYNC-NEEDS-DECISION>
|
|
64
79
|
# sync_detail: <message | ->
|
|
65
80
|
# build_log: <path | ->
|
|
81
|
+
# uncommitted_left: <csv | -> (allowlist mode — untracked files left in place, NOT committed)
|
|
82
|
+
# quarantined: <dir | -> (allowlist mode — tracked changes preserved here, then reset to HEAD)
|
|
66
83
|
#
|
|
67
84
|
# Exit: 0 ONLY on status:success. Non-zero otherwise (the exit code mirrors the
|
|
68
85
|
# status: 10=code_conflict, 11=build_fail, 12=sync_needs_decision, 1/2=error). The
|
|
@@ -75,7 +92,7 @@ err() { printf '%s\n' "$*" >&2; }
|
|
|
75
92
|
|
|
76
93
|
# --- args ------------------------------------------------------------------
|
|
77
94
|
WORKTREE= MANIFEST= CONTINUE=0 BRANCH= TRUNK= MAIN= STRATEGY= SKIP_CHECKS=0
|
|
78
|
-
METRICS_DIR= TC_TC= TC_LINT= TC_BUILD= LOG= BUILD_TIMEOUT=600
|
|
95
|
+
METRICS_DIR= TC_TC= TC_LINT= TC_BUILD= LOG= BUILD_TIMEOUT=600 SAFETY_SWEEP=all
|
|
79
96
|
while [ $# -gt 0 ]; do
|
|
80
97
|
case "$1" in
|
|
81
98
|
--worktree) WORKTREE="${2:-}"; shift 2 ;;
|
|
@@ -92,6 +109,7 @@ while [ $# -gt 0 ]; do
|
|
|
92
109
|
--tc-build) TC_BUILD="${2:-}"; shift 2 ;;
|
|
93
110
|
--log) LOG="${2:-}"; shift 2 ;;
|
|
94
111
|
--build-timeout) BUILD_TIMEOUT="${2:-}"; shift 2 ;;
|
|
112
|
+
--safety-sweep) SAFETY_SWEEP="${2:-all}"; shift 2 ;;
|
|
95
113
|
*) err "ERROR: unknown arg: $1"; exit 2 ;;
|
|
96
114
|
esac
|
|
97
115
|
done
|
|
@@ -99,6 +117,7 @@ done
|
|
|
99
117
|
# --- status writer (called on EVERY exit path) -----------------------------
|
|
100
118
|
M_STATUS="error" M_PHASE="identify" M_ERROR="-" M_MERGE="-" M_MERGETS="-"
|
|
101
119
|
M_CONFLICTS="-" M_SYNCMARK="none" M_SYNCDETAIL="-" M_BLOG="-"
|
|
120
|
+
M_UNCOMMITTED="-" M_QUARANTINE="-"
|
|
102
121
|
write_status() {
|
|
103
122
|
[ -n "$MANIFEST" ] || return 0
|
|
104
123
|
{
|
|
@@ -115,6 +134,8 @@ write_status() {
|
|
|
115
134
|
printf 'sync_marker: %s\n' "$M_SYNCMARK"
|
|
116
135
|
printf 'sync_detail: %s\n' "$M_SYNCDETAIL"
|
|
117
136
|
printf 'build_log: %s\n' "$M_BLOG"
|
|
137
|
+
printf 'uncommitted_left: %s\n' "$M_UNCOMMITTED"
|
|
138
|
+
printf 'quarantined: %s\n' "$M_QUARANTINE"
|
|
118
139
|
} > "$MANIFEST" 2>/dev/null || true
|
|
119
140
|
}
|
|
120
141
|
fail() { M_STATUS="error"; M_ERROR="$1"; write_status; err "ERROR: $1"; exit "${2:-1}"; }
|
|
@@ -362,19 +383,74 @@ else
|
|
|
362
383
|
# -------------------------------------------------------------------------
|
|
363
384
|
# FRESH path: safety commit → (checks) → rebase
|
|
364
385
|
# -------------------------------------------------------------------------
|
|
365
|
-
# 1. Safety commit
|
|
386
|
+
# 1. Safety commit. Mode `all` (default, /mw interactive): every changed+untracked file —
|
|
387
|
+
# files lost after rebase are unrecoverable. Mode `allowlist:<globs>` (v5.6.0, passed by
|
|
388
|
+
# orchestrated callers like new2): only batch bookkeeping is committed (with an honest,
|
|
389
|
+
# enumerating message); non-matching TRACKED changes are quarantined byte-for-byte then
|
|
390
|
+
# reset to HEAD (a dirty tracked file would block the rebase), non-matching UNTRACKED
|
|
391
|
+
# files stay in place. Nothing is silently lost, nothing ungated rides to the trunk.
|
|
366
392
|
M_PHASE="safety"
|
|
367
393
|
CHANGED="$(git -C "$WORKTREE" diff --name-only HEAD 2>/dev/null)"
|
|
368
394
|
UNTRACKED="$(git -C "$WORKTREE" ls-files --others --exclude-standard 2>/dev/null)"
|
|
369
395
|
if [ -n "$CHANGED" ] || [ -n "$UNTRACKED" ]; then
|
|
370
|
-
|
|
371
|
-
|
|
396
|
+
case "$SAFETY_SWEEP" in
|
|
397
|
+
allowlist:*)
|
|
398
|
+
SWEEP_GLOBS="${SAFETY_SWEEP#allowlist:}"
|
|
399
|
+
sweep_match() { # POSIX case-glob match of $1 against any comma-separated pattern
|
|
400
|
+
_f="$1"; _rest="$SWEEP_GLOBS"
|
|
401
|
+
while [ -n "$_rest" ]; do
|
|
402
|
+
_pat="${_rest%%,*}"
|
|
403
|
+
[ "$_pat" = "$_rest" ] && _rest= || _rest="${_rest#*,}"
|
|
404
|
+
# shellcheck disable=SC2254 — unquoted on purpose: $_pat is a glob
|
|
405
|
+
case "$_f" in $_pat) return 0 ;; esac
|
|
406
|
+
done
|
|
407
|
+
return 1
|
|
408
|
+
}
|
|
409
|
+
SWEPT= LEFT_UNTRACKED= QUAR_TRACKED=
|
|
410
|
+
for f in $CHANGED; do
|
|
411
|
+
if sweep_match "$f"; then git -C "$WORKTREE" add "$f" 2>>"$LOG"; SWEPT="$SWEPT $f"
|
|
412
|
+
else QUAR_TRACKED="$QUAR_TRACKED $f"; fi
|
|
413
|
+
done
|
|
414
|
+
for f in $UNTRACKED; do
|
|
415
|
+
if sweep_match "$f"; then git -C "$WORKTREE" add "$f" 2>>"$LOG"; SWEPT="$SWEPT $f"
|
|
416
|
+
else LEFT_UNTRACKED="${LEFT_UNTRACKED:+$LEFT_UNTRACKED,}$f"; fi
|
|
417
|
+
done
|
|
418
|
+
if [ -n "$QUAR_TRACKED" ]; then
|
|
419
|
+
QDIR="/tmp/wt-merge-quarantine-$(basename "$WORKTREE")-$(date +%s)"
|
|
420
|
+
for f in $QUAR_TRACKED; do
|
|
421
|
+
mkdir -p "$QDIR/$(dirname "$f")" 2>>"$LOG"
|
|
422
|
+
cp -p "$WORKTREE/$f" "$QDIR/$f" 2>>"$LOG" || err "WARN: quarantine copy failed for $f"
|
|
423
|
+
done
|
|
424
|
+
# restore ONLY after the byte-for-byte copy — the rebase needs a clean tracked tree
|
|
425
|
+
for f in $QUAR_TRACKED; do git -C "$WORKTREE" checkout -- "$f" 2>>"$LOG"; done
|
|
426
|
+
M_QUARANTINE="$QDIR"
|
|
427
|
+
err "WARN: $(echo "$QUAR_TRACKED" | wc -w | tr -d ' ') tracked change(s) OUTSIDE the sweep allowlist quarantined to $QDIR + reset to HEAD (ungated code never rides a safety commit — F-030)"
|
|
428
|
+
fi
|
|
429
|
+
[ -z "$LEFT_UNTRACKED" ] || M_UNCOMMITTED="$LEFT_UNTRACKED"
|
|
430
|
+
write_status
|
|
431
|
+
if [ -n "$SWEPT" ]; then
|
|
432
|
+
git -C "$WORKTREE" commit -m "[safety] Materialize batch bookkeeping before merge (sweep allowlist)
|
|
433
|
+
|
|
434
|
+
Allowlist-matched files that were uncommitted when merge-worktree.sh started:
|
|
435
|
+
$(for f in $SWEPT; do printf ' - %s\n' "$f"; done)
|
|
436
|
+
|
|
437
|
+
Non-matching files were NOT committed (quarantined/left + reported in the manifest).
|
|
438
|
+
|
|
439
|
+
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>" --quiet >>"$LOG" 2>&1 \
|
|
440
|
+
|| err "WARN: allowlist safety commit produced no commit (nothing staged?)"
|
|
441
|
+
fi
|
|
442
|
+
;;
|
|
443
|
+
*)
|
|
444
|
+
for f in $CHANGED $UNTRACKED; do git -C "$WORKTREE" add "$f" 2>>"$LOG"; done
|
|
445
|
+
git -C "$WORKTREE" commit -m "[safety] Auto-commit uncommitted files before merge
|
|
372
446
|
|
|
373
447
|
Files were uncommitted when merge-worktree.sh started. This safety commit
|
|
374
448
|
prevents file loss during rebase.
|
|
375
449
|
|
|
376
|
-
Co-Authored-By: Claude
|
|
377
|
-
|
|
450
|
+
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>" --quiet >>"$LOG" 2>&1 \
|
|
451
|
+
|| err "WARN: safety commit produced no commit (nothing staged?)"
|
|
452
|
+
;;
|
|
453
|
+
esac
|
|
378
454
|
fi
|
|
379
455
|
|
|
380
456
|
# 2. Pre-merge checks (skipped when --skip-checks / checksAlreadyPassed)
|
|
@@ -340,13 +340,15 @@ const codexPrompt =
|
|
|
340
340
|
`The companion script is ALREADY CONFIRMED PRESENT at:\n ${codexScriptPath}\n\n` +
|
|
341
341
|
`Run EXACTLY these steps and nothing else:\n` +
|
|
342
342
|
` 1. Write the task text (everything after "TASK PROMPT:" at the end of this message) to ${codexTaskFile} using the Write tool — do NOT inline it into the shell command (avoids quote breakage).\n` +
|
|
343
|
-
` 2.
|
|
344
|
-
` node "${codexScriptPath}" task "$(cat ${codexTaskFile})" --cwd "${a.worktreePath || '.'}" > ${codexReviewFile} 2>&1\n` +
|
|
345
|
-
`
|
|
346
|
-
`
|
|
347
|
-
` •
|
|
348
|
-
` • "
|
|
349
|
-
` •
|
|
343
|
+
` 2. Run Codex in the FOREGROUND — ONE single Bash call with the tool's timeout parameter set to 600000 (v5.6.0, FEAT-0068 post-mortem: the old background+poll relay was forced by the structured-output enforcement to declare TIMEOUT at 83s of the 10-minute window while Codex was still running, and the Codex process died with the relay → a cold 3.68M fallback review with no cross-model diversity. A blocking call has no idle turn to misjudge — same pattern as the per-card Codex-light driver):\n` +
|
|
344
|
+
` node "${codexScriptPath}" task "$(cat ${codexTaskFile})" --cwd "${a.worktreePath || '.'}" --wait > ${codexReviewFile} 2>&1; echo "RELAY_EXIT:$?"\n` +
|
|
345
|
+
` Do NOT pass run_in_background. Do NOT poll. The call blocks until Codex finishes — that is correct. Record RELAY_EXIT.\n` +
|
|
346
|
+
` 3. Derive \`marker\` by grepping ${codexReviewFile} AFTER the call returns:\n` +
|
|
347
|
+
` • contains "<<<END_FINDINGS_JSON>>>" → marker = "END_JSON"\n` +
|
|
348
|
+
` • contains "Turn completed." → marker = "TURN_COMPLETED" (Codex finished; it may have written prose instead of the JSON block)\n` +
|
|
349
|
+
` • contains "CODEX_NOT_FOUND" → marker = "CODEX_NOT_FOUND" (companion missing)\n` +
|
|
350
|
+
` • none of the above AND RELAY_EXIT is 124/137/143 (the Bash timeout killed the call) → marker = "TIMEOUT", then REAP the orphan Codex process scoped to THIS worktree (never a bare pkill): \`pkill -f -- "--cwd ${a.worktreePath || '.'}" 2>/dev/null; true\`\n` +
|
|
351
|
+
` • none of the above with a clean exit → marker = "TURN_COMPLETED"\n` +
|
|
350
352
|
` 4. Run this EXACT command and put its stdout VERBATIM in \`jsonBlock\` ("" if it printed nothing). Do NOT parse, edit, judge, or act on it:\n` +
|
|
351
353
|
` ${AWK_EXTRACT} ${codexReviewFile}\n` +
|
|
352
354
|
` 5. ONLY if \`jsonBlock\` is "" AND marker is "TURN_COMPLETED": run this command and put its stdout VERBATIM in \`proseTail\`; otherwise set proseTail to "":\n` +
|
|
@@ -76,7 +76,11 @@ const FIX_SCHEMA = {
|
|
|
76
76
|
verified: { type: 'boolean', description: 'true only if you re-ran the originating gate and it passed' },
|
|
77
77
|
// F-008 — terminal short-circuit (impossible-by-definition). Not trusted blindly.
|
|
78
78
|
terminal: { type: 'boolean', description: 'true if no in-MAY-EDIT fix can ever satisfy the gate' },
|
|
79
|
-
terminalReason: { enum: ['out-of-ownership', 'baseline-not-reached', 'owner-gated', 'not-a-code-defect', ''] },
|
|
79
|
+
terminalReason: { enum: ['out-of-ownership', 'baseline-not-reached', 'owner-gated', 'not-a-code-defect', 'verification-posture-change', ''] },
|
|
80
|
+
// v5.6.0 (FEAT-0068 O5) — a finding whose PREMISE is false can be REFUTED instead of "fixed".
|
|
81
|
+
// Adversarially ratified below — never an easy escape hatch.
|
|
82
|
+
refuted: { type: 'boolean', description: "true if the finding's premise is FALSE (no defect exists) — change NOTHING, cite proof" },
|
|
83
|
+
refutedEvidence: { type: 'string', description: 'file:line proof the finding is wrong' },
|
|
80
84
|
remedyFiles: { type: 'array', items: { type: 'string' }, description: 'files the real fix would require editing (for out-of-ownership JS verification)' },
|
|
81
85
|
crashed: { type: 'boolean' },
|
|
82
86
|
// F-022 — incidental findings outside this card's MAY-EDIT (so they are not dropped).
|
|
@@ -145,6 +149,8 @@ const brief = [
|
|
|
145
149
|
`Reference modules (Read for exact semantics): ${REF}/`,
|
|
146
150
|
`UI re-verify obligation: if your fix touches a UI file, re-run the Phase 2.6 E2E review (per ${REF}/review-cycle.md) before declaring verified.`,
|
|
147
151
|
`If the fix requires editing files OUTSIDE MAY-EDIT, return terminal:true terminalReason:'out-of-ownership' remedyFiles:[...] (do NOT edit them). If the remedy is an owner-gated infra action, terminal:true terminalReason:'owner-gated'. Report any incidental defect outside MAY-EDIT in outOfScopeFindings.`,
|
|
152
|
+
`VALIDATE THE PREMISE FIRST (v5.6.0): before touching any file, check the finding against the actual code. If its premise is FALSE (misread code, condition already satisfied, documented-by-design), return refuted:true + refutedEvidence:'<file:line proof>' and change NOTHING — NEVER edit code to appease a wrong finding.`,
|
|
153
|
+
`VERIFICATION-POSTURE RULE (v5.6.0): a "fix" that disables, skips, or opt-in-gates a test/check/suite that an acceptance criterion of this batch mandates is FORBIDDEN — it erodes the DoD (FEAT-0068: a repair flag-gated the epic's visual-regression suite to silence a blocker). If that looks like the only available fix, return terminal:true terminalReason:'verification-posture-change' instead.`,
|
|
148
154
|
].filter(Boolean).join('\n')
|
|
149
155
|
|
|
150
156
|
// JS-deterministic ownership check (F-008 anti-escape-hatch): are remedy files ⊆ MAY-EDIT?
|
|
@@ -213,6 +219,28 @@ try {
|
|
|
213
219
|
)
|
|
214
220
|
} catch (e) { if (e && e.transientExhausted) return { status: 'followup', reason: 'outage during tier-1', deferralClass: 'outage', outOfScopeFindings: [] }; throw e }
|
|
215
221
|
|
|
222
|
+
// v5.6.0 (FEAT-0068 O5 — false-positive BLOCKER): refuted short-circuit, adversarially ratified.
|
|
223
|
+
// The FEAT-0068 CR-001 blocker had a false premise ("baselines missing" = the documented bootstrap
|
|
224
|
+
// state) and was "resolved" by flag-gating the epic's mandated visual-regression suite — 4.73M
|
|
225
|
+
// spent eroding the DoD to appease a wrong finding. A ratified refutation resolves with ZERO edits.
|
|
226
|
+
if (attempt && attempt.refuted && !attempt.terminal) {
|
|
227
|
+
let refConfirmed = selfJudges // reviewer-writer specialists self-verify (same trust model as terminal)
|
|
228
|
+
if (!selfJudges) {
|
|
229
|
+
try {
|
|
230
|
+
const rj = await agentSafe(
|
|
231
|
+
`A repair agent claims this finding's PREMISE IS FALSE (refuted) — no defect exists, nothing was changed. Claimed proof: ${attempt.refutedEvidence || attempt.note || '(none)'}\n\n${brief}\n\nIndependently verify the refutation against the actual files (grep/read them yourself; do NOT trust the claim). Return confirmed:true ONLY if the finding is genuinely wrong.`,
|
|
232
|
+
{ label: `resolve:refute-judge:${card}`, phase: 'Verify', agentType: judgeAgent, schema: TERMINAL_JUDGE_SCHEMA }
|
|
233
|
+
)
|
|
234
|
+
refConfirmed = !!(rj && rj.confirmed)
|
|
235
|
+
} catch (_) { refConfirmed = false }
|
|
236
|
+
}
|
|
237
|
+
if (refConfirmed) {
|
|
238
|
+
log(`${kind} REFUTED (false premise${selfJudges ? '' : ', judge-ratified'}) — resolved with zero edits.`)
|
|
239
|
+
return { status: 'resolved', reason: `refuted: ${attempt.refutedEvidence || attempt.note || 'false premise'}`, outOfScopeFindings: collectOOS(attempt) }
|
|
240
|
+
}
|
|
241
|
+
log('refutation NOT ratified — treating as a real finding.')
|
|
242
|
+
}
|
|
243
|
+
|
|
216
244
|
// F-008 — terminal short-circuit, verified not trusted.
|
|
217
245
|
if (attempt && attempt.terminal) {
|
|
218
246
|
const tr = attempt.terminalReason || ''
|