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.
- package/CHANGELOG.md +119 -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/capture/CHANGELOG.md +4 -0
- package/framework/.claude/skills/capture/SKILL.md +2 -1
- package/framework/.claude/skills/design-sync/CHANGELOG.md +4 -0
- package/framework/.claude/skills/design-sync/SKILL.md +2 -1
- package/framework/.claude/skills/ds-edit/CHANGELOG.md +5 -0
- package/framework/.claude/skills/ds-edit/SKILL.md +9 -10
- package/framework/.claude/skills/ds-handoff/CHANGELOG.md +5 -0
- package/framework/.claude/skills/ds-handoff/SKILL.md +3 -2
- package/framework/.claude/skills/ds-new/CHANGELOG.md +4 -0
- package/framework/.claude/skills/ds-new/SKILL.md +2 -1
- package/framework/.claude/skills/ds-render/CHANGELOG.md +4 -0
- package/framework/.claude/skills/ds-render/SKILL.md +2 -1
- package/framework/.claude/skills/e2e-review/CHANGELOG.md +4 -0
- package/framework/.claude/skills/e2e-review/SKILL.md +2 -1
- package/framework/.claude/skills/gamification-design/CHANGELOG.md +4 -0
- package/framework/.claude/skills/gamification-design/SKILL.md +2 -1
- package/framework/.claude/skills/graph-align/CHANGELOG.md +4 -0
- package/framework/.claude/skills/graph-align/SKILL.md +2 -1
- package/framework/.claude/skills/i18n/CHANGELOG.md +4 -0
- package/framework/.claude/skills/i18n/SKILL.md +2 -1
- package/framework/.claude/skills/motion-design/CHANGELOG.md +4 -0
- package/framework/.claude/skills/motion-design/SKILL.md +2 -1
- package/framework/.claude/skills/new/CHANGELOG.md +27 -0
- package/framework/.claude/skills/new/SKILL.md +2 -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 +40 -0
- package/framework/.claude/skills/new2/SKILL.md +53 -12
- package/framework/.claude/skills/prd/CHANGELOG.md +40 -0
- package/framework/.claude/skills/prd/SKILL.md +18 -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/prd-add/CHANGELOG.md +4 -0
- package/framework/.claude/skills/prd-add/SKILL.md +2 -1
- package/framework/.claude/skills/skill-creator/references/skill-structure.md +31 -2
- package/framework/.claude/skills/skill-creator/scripts/quick_validate.py +25 -3
- package/framework/.claude/skills/ui-design/CHANGELOG.md +4 -0
- package/framework/.claude/skills/ui-design/SKILL.md +2 -1
- package/framework/.claude/skills/ui-implement/CHANGELOG.md +4 -0
- package/framework/.claude/skills/ui-implement/SKILL.md +2 -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 +158 -18
- 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
- package/src/commands/configure.js +9 -2
- package/src/commands/doctor.js +65 -0
- package/src/utils/skill-gate.js +139 -0
- 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,...>" "
|
|
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.
|
|
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` → `
|
|
36
|
-
tolerated only on imported third-party skills; do not
|
|
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
|
-
#
|
|
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.
|
|
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.
|
|
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.
|
|
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 || ''
|