baldart 5.5.0 → 5.7.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.
Files changed (65) hide show
  1. package/CHANGELOG.md +119 -0
  2. package/VERSION +1 -1
  3. package/framework/.claude/agents/CHANGELOG.md +16 -0
  4. package/framework/.claude/agents/prd-card-writer.md +37 -3
  5. package/framework/.claude/skills/capture/CHANGELOG.md +4 -0
  6. package/framework/.claude/skills/capture/SKILL.md +2 -1
  7. package/framework/.claude/skills/design-sync/CHANGELOG.md +4 -0
  8. package/framework/.claude/skills/design-sync/SKILL.md +2 -1
  9. package/framework/.claude/skills/ds-edit/CHANGELOG.md +5 -0
  10. package/framework/.claude/skills/ds-edit/SKILL.md +9 -10
  11. package/framework/.claude/skills/ds-handoff/CHANGELOG.md +5 -0
  12. package/framework/.claude/skills/ds-handoff/SKILL.md +3 -2
  13. package/framework/.claude/skills/ds-new/CHANGELOG.md +4 -0
  14. package/framework/.claude/skills/ds-new/SKILL.md +2 -1
  15. package/framework/.claude/skills/ds-render/CHANGELOG.md +4 -0
  16. package/framework/.claude/skills/ds-render/SKILL.md +2 -1
  17. package/framework/.claude/skills/e2e-review/CHANGELOG.md +4 -0
  18. package/framework/.claude/skills/e2e-review/SKILL.md +2 -1
  19. package/framework/.claude/skills/gamification-design/CHANGELOG.md +4 -0
  20. package/framework/.claude/skills/gamification-design/SKILL.md +2 -1
  21. package/framework/.claude/skills/graph-align/CHANGELOG.md +4 -0
  22. package/framework/.claude/skills/graph-align/SKILL.md +2 -1
  23. package/framework/.claude/skills/i18n/CHANGELOG.md +4 -0
  24. package/framework/.claude/skills/i18n/SKILL.md +2 -1
  25. package/framework/.claude/skills/motion-design/CHANGELOG.md +4 -0
  26. package/framework/.claude/skills/motion-design/SKILL.md +2 -1
  27. package/framework/.claude/skills/new/CHANGELOG.md +27 -0
  28. package/framework/.claude/skills/new/SKILL.md +2 -1
  29. package/framework/.claude/skills/new/references/commit.md +12 -8
  30. package/framework/.claude/skills/new/references/completeness.md +10 -0
  31. package/framework/.claude/skills/new/references/final-review.md +23 -0
  32. package/framework/.claude/skills/new/references/implement.md +14 -4
  33. package/framework/.claude/skills/new/references/merge-cleanup.md +35 -14
  34. package/framework/.claude/skills/new2/CHANGELOG.md +40 -0
  35. package/framework/.claude/skills/new2/SKILL.md +53 -12
  36. package/framework/.claude/skills/prd/CHANGELOG.md +40 -0
  37. package/framework/.claude/skills/prd/SKILL.md +18 -5
  38. package/framework/.claude/skills/prd/assets/state-template.md +8 -1
  39. package/framework/.claude/skills/prd/references/audit-phase.md +13 -1
  40. package/framework/.claude/skills/prd/references/backlog-phase.md +20 -0
  41. package/framework/.claude/skills/prd/references/discovery-phase.md +35 -0
  42. package/framework/.claude/skills/prd/references/ui-design-phase.md +28 -0
  43. package/framework/.claude/skills/prd/references/validation-phase.md +45 -1
  44. package/framework/.claude/skills/prd-add/CHANGELOG.md +4 -0
  45. package/framework/.claude/skills/prd-add/SKILL.md +2 -1
  46. package/framework/.claude/skills/skill-creator/references/skill-structure.md +31 -2
  47. package/framework/.claude/skills/skill-creator/scripts/quick_validate.py +25 -3
  48. package/framework/.claude/skills/ui-design/CHANGELOG.md +4 -0
  49. package/framework/.claude/skills/ui-design/SKILL.md +2 -1
  50. package/framework/.claude/skills/ui-implement/CHANGELOG.md +4 -0
  51. package/framework/.claude/skills/ui-implement/SKILL.md +2 -1
  52. package/framework/.claude/skills/worktree-manager/CHANGELOG.md +13 -0
  53. package/framework/.claude/skills/worktree-manager/SKILL.md +11 -1
  54. package/framework/.claude/skills/worktree-manager/scripts/merge-worktree.sh +82 -6
  55. package/framework/.claude/workflows/new-final-review.js +9 -7
  56. package/framework/.claude/workflows/new2-resolve.js +29 -1
  57. package/framework/.claude/workflows/new2.js +158 -18
  58. package/framework/agents/card-schema.md +18 -1
  59. package/framework/agents/research-protocol.md +6 -1
  60. package/framework/scripts/stamp-holistic-audit.js +53 -10
  61. package/package.json +1 -1
  62. package/src/commands/configure.js +9 -2
  63. package/src/commands/doctor.js +65 -0
  64. package/src/utils/skill-gate.js +139 -0
  65. package/src/utils/symlinks.js +72 -7
@@ -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,...>" "$WORKTREE_PATH/${paths.backlog_dir}"/FEAT-XXXX-*.yml
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,10 @@
2
2
 
3
3
  Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
4
4
 
5
+ ## 1.1.1 — 2026-07-06
6
+
7
+ - **Feature-gate**: added `requires_feature: has_prd_workflow` frontmatter — this skill is now linked into a consumer only when `features.has_prd_workflow` is enabled (it already refused at runtime otherwise). Trims Codex's ~2% skill-description budget on projects without the feature. Install-time gate, portable across Claude + Codex.
8
+
5
9
  ## 1.1.0 — 2026-07-02
6
10
 
7
11
  - **Analysis-profile contract (framework v4.94.0)**: PATCH-mode re-discovery passes `PROFILE=impact` to `codebase-architect` (blast-radius scoping per `framework/agents/analysis-profiles.md`).
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  name: prd-add
3
3
  effort: medium
4
- version: 1.1.0
4
+ version: 1.1.1
5
+ requires_feature: has_prd_workflow
5
6
  description: "Change Request skill for active PRD sessions. Use when the user says /prd-add, 'aggiungi requisito', 'serve anche', 'manca un endpoint', or describes a new requirement that impacts an existing PRD. Also auto-triggered by /prd during discovery when the user's answer reveals a new sub-feature not covered by the original scope. Runs ICIAS impact analysis (semantic scan + structural propagation + scoring) to determine which PRD phases need SKIP/PATCH/REDO, then executes only affected phases."
6
7
  ---
7
8
 
@@ -27,13 +27,42 @@ when the skill needs them** — no empty placeholder dirs (that was upstream
27
27
  name: <kebab-case> # matches the directory name
28
28
  effort: medium # low | medium | high | xhigh | max (effort-protocol.md)
29
29
  version: 1.0.0 # the skill's OWN SemVer
30
+ requires_feature: has_x # OPTIONAL — install-time feature-gate (see below)
30
31
  description: > # triggers + what/when; NO angle brackets
31
32
  ...
32
33
  ---
33
34
  ```
34
35
 
35
- Order: `name` → `effort` → `version` → `description`. `license` / `metadata` are
36
- tolerated only on imported third-party skills; do not add other keys.
36
+ Order: `name` → `effort` → `version` → (`requires_feature`) `description`.
37
+ `license` / `metadata` are tolerated only on imported third-party skills; do not
38
+ add other keys.
39
+
40
+ ### `requires_feature` — install-time feature-gate (OPTIONAL)
41
+
42
+ A skill that is only meaningful when a `baldart.config.yml` `features.*` flag is
43
+ on (and already REFUSES at runtime otherwise) declares that dependency here, so
44
+ the installer links it into a consumer ONLY when the feature is enabled. This
45
+ trims Codex's ~2% skill-description budget on projects that don't use the feature.
46
+
47
+ ```yaml
48
+ requires_feature: has_design_system # string → link iff true
49
+ requires_feature: [has_design_system, has_e2e_review] # list = AND (ALL true)
50
+ ```
51
+
52
+ - **Omit it** for any skill that should always be linked (the default). The five
53
+ bootstrap skills that ENABLE a feature (`design-system-init`, `i18n-adopt`,
54
+ `graphify-bootstrap`, `lsp-bootstrap`, `toolchain-bootstrap`) MUST stay ungated
55
+ — otherwise the feature could never be turned on.
56
+ - The gate is **install-time only** and **portable**: both Claude (`.claude/skills/`)
57
+ and Codex (`.agents/skills/`) simply see fewer directories. Neither runtime reads
58
+ the field — it is ignored exactly like `version`/`effort`. Resolution lives in
59
+ `src/utils/skill-gate.js`; it reads `features.*` directly and **fails open** (a
60
+ missing config, or a malformed frontmatter, links the skill rather than hiding it).
61
+ - **NOT a `baldart.config.yml` key** — it consumes existing `features.*`, so the
62
+ schema-change propagation rule does NOT apply. It is skill-frontmatter metadata.
63
+ - **Invariant** (latent, for future Codex `agents/openai.yaml` metadata): a gated
64
+ skill MUST NOT be declared as a `dependencies:` target of an always-linked skill's
65
+ `openai.yaml` — pruning the gated skill would leave a dangling dependency.
37
66
 
38
67
  ## Body headers (in order, when applicable)
39
68
 
@@ -39,10 +39,22 @@ def validate_skill(skill_path):
39
39
  return False, f"Invalid YAML in frontmatter: {e}"
40
40
 
41
41
  # Define allowed properties
42
- # BALDART additions: `effort` (per-skill reasoning baseline — effort-protocol.md)
43
- # and `version` (per-skill SemVer — canonical structure, see references/skill-structure.md).
42
+ # BALDART additions: `effort` (per-skill reasoning baseline — effort-protocol.md),
43
+ # `version` (per-skill SemVer — canonical structure, see references/skill-structure.md)
44
+ # and `requires_feature` (install-time feature-gate — a skill is linked into a
45
+ # consumer only when this `features.*` flag is on; see references/skill-structure.md
46
+ # and src/utils/skill-gate.js). All three are OPTIONAL frontmatter metadata,
47
+ # ignored by the Claude/Codex skill loaders (they only read name+description).
44
48
  ALLOWED_PROPERTIES = {'name', 'description', 'license', 'allowed-tools', 'metadata',
45
- 'effort', 'version'}
49
+ 'effort', 'version', 'requires_feature'}
50
+
51
+ # Known feature flags a `requires_feature` value may reference (mirrors the
52
+ # features.* block of baldart.config.template.yml). Used for a SOFT warning
53
+ # only — an unknown value is not fatal (fail-open: the gate links the skill).
54
+ KNOWN_FEATURES = {'has_design_system', 'multi_tenant_theming', 'has_api_docs',
55
+ 'has_backlog', 'has_adrs', 'has_prd_workflow', 'has_wiki_overlay',
56
+ 'has_lsp_layer', 'has_code_graph', 'has_e2e_review',
57
+ 'has_toolchain', 'has_i18n'}
46
58
 
47
59
  # Check for unexpected properties (excluding nested keys under metadata)
48
60
  unexpected_keys = set(frontmatter.keys()) - ALLOWED_PROPERTIES
@@ -90,6 +102,16 @@ def validate_skill(skill_path):
90
102
  # `version` + a per-skill CHANGELOG.md are REQUIRED for new skills but WARNED (not
91
103
  # hard-failed) here for backwards-compat with skills authored before the convention.
92
104
  warnings = []
105
+ # Soft-validate requires_feature values against the known features.* set.
106
+ if 'requires_feature' in frontmatter:
107
+ req = frontmatter['requires_feature']
108
+ req_list = req if isinstance(req, list) else [req]
109
+ unknown = [str(f) for f in req_list if str(f) not in KNOWN_FEATURES]
110
+ if unknown:
111
+ warnings.append(
112
+ f"`requires_feature` references unknown flag(s): {', '.join(sorted(unknown))}. "
113
+ f"The gate fails open (skill still linked). Known: {', '.join(sorted(KNOWN_FEATURES))}"
114
+ )
93
115
  if 'version' not in frontmatter:
94
116
  warnings.append("missing `version:` in frontmatter (add `version: 1.0.0`)")
95
117
  if 'effort' not in frontmatter:
@@ -2,6 +2,10 @@
2
2
 
3
3
  Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
4
4
 
5
+ ## 2.0.1 — 2026-07-06
6
+
7
+ - **Feature-gate**: added `requires_feature: has_design_system` frontmatter — this skill is now linked into a consumer only when `features.has_design_system` is enabled (it already refused at runtime otherwise). Trims Codex's ~2% skill-description budget on projects without the feature. Install-time gate, portable across Claude + Codex.
8
+
5
9
  ## 2.0.0 — 2026-07-03
6
10
 
7
11
  **The local design studio.** Root-level refactor (framework v5.2.0): the
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  name: ui-design
3
3
  effort: medium
4
- version: 2.0.0
4
+ version: 2.0.1
5
+ requires_feature: has_design_system
5
6
  description: "The local design studio — THE framework skill for designing, prototyping and restyling UI directly in the repo (the internal twin of the Claude Design handoff). Produces context-aware, token-compliant HTML mockups in 3 distinct directions, with a locked Design Read, a deterministic anti-slop craft gate and separated generator/evaluator verification. Use when: (1) designing new pages, screens or components, (2) the /prd skill reaches its UI design phase (Step 3), (3) redesigning/restyling existing pages ('rifai la UI', 'migliora il design', 'restyle'), (4) exploring design options ('3 opzioni', 'opzioni di design', 'direzioni estetiche'), (5) prototyping a page/landing/dashboard locally ('prototipo', 'mockup', 'progetta interfaccia', 'design UI'). SUPERSEDES frontend-design (now a router) and ui-ux-pro-max for ALL design/prototyping work. Not for: implementing an already-approved mockup (→ /ui-implement), in-code UI fixes with no design decision (→ ui-expert agent), motion specs (→ /motion-design)."
6
7
  ---
7
8
 
@@ -2,6 +2,10 @@
2
2
 
3
3
  Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
4
4
 
5
+ ## 1.1.2 — 2026-07-06
6
+
7
+ - **Feature-gate**: added `requires_feature: has_design_system` frontmatter — this skill is now linked into a consumer only when `features.has_design_system` is enabled (it already refused at runtime otherwise). Trims Codex's ~2% skill-description budget on projects without the feature. Install-time gate, portable across Claude + Codex.
8
+
5
9
  ## 1.1.1 — 2026-07-02
6
10
 
7
11
  - Return Contract (SKILL.md): documentati i campi `bindings` + `lane_coverage`/`lanes`
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  name: ui-implement
3
3
  effort: medium
4
- version: 1.1.1
4
+ version: 1.1.2
5
+ requires_feature: has_design_system
5
6
  description: >
6
7
  Implement an ALREADY-APPROVED mockup into the codebase end-to-end and
7
8
  self-verify its fidelity. Takes a mockup in any channel — a Claude Design
@@ -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.1.0
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 (ALWAYS never skippable; files lost after rebase are unrecoverable)
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
- for f in $CHANGED $UNTRACKED; do git -C "$WORKTREE" add "$f" 2>>"$LOG"; done
371
- git -C "$WORKTREE" commit -m "[safety] Auto-commit uncommitted files before merge
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 Opus 4.8 <noreply@anthropic.com>" --quiet >>"$LOG" 2>&1 \
377
- || err "WARN: safety commit produced no commit (nothing staged?)"
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. Launch Codex in the BACKGROUND (run_in_background:true a sync run hits the Bash timeout):\n` +
344
- ` node "${codexScriptPath}" task "$(cat ${codexTaskFile})" --cwd "${a.worktreePath || '.'}" > ${codexReviewFile} 2>&1\n` +
345
- ` 3. POLL ${codexReviewFile} (BashOutput / repeated reads) until ONE of these appears whichever is FIRST ends the poll (do NOT keep waiting once one is present). Set \`marker\` accordingly:\n` +
346
- ` • "<<<END_FINDINGS_JSON>>>" marker = "END_JSON"\n` +
347
- ` • "Turn completed." → marker = "TURN_COMPLETED" (Codex finished; it may have written prose instead of the JSON block)\n` +
348
- ` • "CODEX_NOT_FOUND" → marker = "CODEX_NOT_FOUND" (companion missing)\n` +
349
- ` • the full 10-minute window elapses with none of the above → marker = "TIMEOUT" (NEVER pick this just because a single poll returned slowly)\n` +
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 finishesthat 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 || ''