baldart 4.22.1 → 4.24.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 +34 -0
- package/VERSION +1 -1
- package/framework/.claude/agents/code-reviewer.md +22 -0
- package/framework/.claude/agents/prd-card-writer.md +36 -16
- package/framework/.claude/agents/prd.md +14 -1
- package/framework/.claude/agents/ui-expert.md +27 -0
- package/framework/.claude/agents/visual-fidelity-verifier.md +14 -2
- package/framework/.claude/commands/design-review.md +1 -0
- package/framework/.claude/skills/e2e-review/SKILL.md +8 -1
- package/framework/.claude/skills/prd/assets/claude-design-handoff-prompt.template.md +17 -1
- package/framework/.claude/skills/prd/references/discovery-phase.md +26 -0
- package/framework/.claude/skills/prd/references/ui-design-phase.md +7 -2
- package/framework/.claude/skills/ui-design/references/inventory.md +14 -4
- package/framework/.claude/skills/worktree-manager/SKILL.md +67 -2
- package/framework/.claude/skills/worktree-manager/scripts/allocate-id.sh +207 -0
- package/framework/agents/design-system-protocol.md +98 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,40 @@ All notable changes to BALDART will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [4.24.0] - 2026-06-10
|
|
9
|
+
|
|
10
|
+
**Atomic backlog-ID allocator — no FEAT/BUG collisions across parallel worktrees.** When several `/prd` (or `/new`/`new2` follow-up) sessions run in parallel on sibling worktrees, each branched from the same trunk, the old `max(^id: FEAT-) + 1` scan made them all land on the **same next integer**: the other session's card was in flight on an unmerged sibling branch, invisible to both the local backlog and the trunk merge-base — so two epics both became `FEAT-0024` and conflicted at rebase/merge. The `git fetch` + merge-base scan only ever covered *already-merged* IDs, never in-flight ones. A new allocator anchors a lock + per-prefix high-water mark in `$MAIN/.worktrees/` (the shared coordination point every worktree already reaches via `git rev-parse --git-common-dir`, gitignored like `registry.json`), so a reservation is atomic across every worktree **on the same machine**. The high-water mark bumped under the lock is the correctness anchor; `max()` against the real backlog + sibling-worktree backlogs + reservations log + trunk merge-base makes it **self-healing**. **MINOR** (additive capability on the `worktree-manager` skill; opt-in — callers fall back to the inline merge-base scan + `[ID-RACE-RISK]` note when the script is absent, so older installs and cross-machine cloud agents are unaffected. **No `baldart.config.yml` key** — the allocator reuses `paths.backlog_dir` + the gitignored `.worktrees/` convention, so the schema-change propagation rule does not apply).
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`framework/.claude/skills/worktree-manager/scripts/allocate-id.sh`** — prefix-parametric (`FEAT`/`BUG`/`UI`/`DOC`/`PERF`/…) atomic ID allocator. `reserve <PREFIX> <slug>` prints the next free integer zero-padded to 4 digits; `release <worktree-path>` prunes a finished worktree's reservations. Cross-process mutex via atomic `mkdir` (stale-stolen after 30s via the lock dir's own mtime — a directory, not a git ref, so it never touches the shared `refs/stash` that `git stash` in worktrees is forbidden over). The slow `git fetch` runs **before** the lock, keeping the critical section local-FS-only (sub-second). Reads `paths.backlog_dir` + `git.trunk_branch` from `baldart.config.yml` and resolves `$MAIN` at runtime — no hardcoded project facts (passes the framework-edit-gate). Same-machine scope; the trunk merge-base scan is the best-effort cross-machine guard.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- **`framework/.claude/skills/worktree-manager/SKILL.md`** — new section **"ID Allocation"** documenting the allocator, the shared `.id-alloc.lock/` / `.id-hwm-<PREFIX>` / `id-reservations.jsonl` files, the same-machine scope + opt-in fallback contract, and the gap-tolerant monotonic-counter design. `release` is wired into the cleanup paths of `/mw` (step 7), `mw-docs` (step 6), and `/cw` (step 4).
|
|
19
|
+
- **`framework/.claude/agents/prd-card-writer.md`** — § "FEAT-XXXX numbering" + Pre-Generation Checklist item 1 now reserve the integer via the allocator (primary path), with the trunk fetch + merge-base scan + `[ID-RACE-RISK]` note as the documented fallback when the script is absent. Applies to any prefix the writer mints.
|
|
20
|
+
- **`framework/.claude/agents/prd.md`** — § 4.1 NAMING CONVENTIONS prefers the allocator (any prefix) over the plain backlog scan, with the same documented fallback.
|
|
21
|
+
|
|
22
|
+
## [4.23.0] - 2026-06-09
|
|
23
|
+
|
|
24
|
+
**Functional Traceability Gate — no orphan UI affordances from mockups.** Handed-off mockups (Claude Design / Figma / the internal generators) routinely add interactive-looking chrome — buttons, icons, menu entries — that no requirement backed. Implementing a mockup **1:1 for fidelity** materialised that chrome into real markup + dead handlers + unused icon imports, and the cruft propagated forever. A new **unconditional** gate (independent of `features.has_design_system`) now requires every interactive or iconographic artifact to trace to a function before it becomes code: each artifact is classified **backed** (implement) / **decorative** (static-render, `aria-hidden`) / **orphan** (drop or escalate — never silent). The oracle is the PRD **UI Element Inventory `function_ref`** allowlist → card AC → orphan. Enforced at three boundaries: PRD mockup-intake (orphans become BLOCKING Discovery items), `ui-expert` implementation-time (BLOCKING pre-work), and `code-reviewer` / `/design-review` per-merge (`UI_ORPHAN_AFFORDANCE` HIGH finding). The `visual-fidelity-verifier` gains a `resolved_orphans` carve-out so a deliberately-dropped orphan is *expected-absent*, not a `component-missing` false positive. **MINOR** (additive discipline across existing UI surfaces; no new agent/skill/command and **no `baldart.config.yml` key** — the gate is registry-independent and reads its allowlist from the PRD inventory, so the schema-change propagation rule does not apply).
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **`framework/agents/design-system-protocol.md`** — new SSOT section **"Functional Traceability Gate (BLOCKING — unconditional)"**: the rule, the oracle hierarchy (UI Element Inventory `function_ref` → card AC → orphan), the backed/decorative/orphan classification + outcome matrix, the drop-or-escalate contract for orphans, where it runs (design / implementation / review), and the canonical `UI_ORPHAN_AFFORDANCE` finding code. The Gating section gains an explicit carve-out: this is the one part of the module **not** gated on `features.has_design_system`.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- **`framework/.claude/agents/ui-expert.md`** — BLOCKING step `0b` (Functional Traceability Gate) added to "When Designing New Interfaces"; check `3b` added to "When Reviewing UI Code"; new red-flag category **Functional traceability (`UI_ORPHAN_AFFORDANCE`)**.
|
|
33
|
+
- **`framework/.claude/agents/code-reviewer.md`** — new section **"Functional Traceability (MANDATORY for UI work — unconditional)"**: live affordances tracing to nothing are `UI_ORPHAN_AFFORDANCE` HIGH findings that block merge; positioned as the per-merge net behind `ui-expert`'s implementation-time gate.
|
|
34
|
+
- **`framework/.claude/agents/visual-fidelity-verifier.md`** — input contract gains optional `resolved_orphans[]`; `component-missing` / `interactive-state-missing` carve-out so a dropped/decorative orphan is expected-absent rather than a fidelity finding.
|
|
35
|
+
- **`framework/.claude/skills/e2e-review/SKILL.md`** — populates `resolved_orphans` in the verifier payload from the card's UI Element Inventory (`function_ref: decorative` or gate-resolved drops).
|
|
36
|
+
- **`framework/.claude/skills/prd/references/discovery-phase.md`** — mockup-intake `mockup_analysis.screens[]` schema gains an `affordances[]` block (`label` / `traces_to` / `classification`); new **Affordance discipline (Functional Traceability)** making every `orphan` a BLOCKING Discovery item resolved via Scope Expansion / `/prd-add` / downgrade / drop.
|
|
37
|
+
- **`framework/.claude/skills/prd/references/ui-design-phase.md`** — Step 3d gates (Full + Hybrid) now require every interactive/iconographic inventory row to carry a `function_ref` with no unresolved `orphan`.
|
|
38
|
+
- **`framework/.claude/skills/prd/assets/claude-design-handoff-prompt.template.md`** — § 3 gains a functional-traceability constraint (only ship controls tied to a listed function; mark decorative explicitly); § 7 gains a per-element traceability map + orphan-to-decide list requirement.
|
|
39
|
+
- **`framework/.claude/skills/ui-design/references/inventory.md`** — UI Element Inventory table gains a **Function ref** column + the rule binding it to the gate.
|
|
40
|
+
- **`framework/.claude/commands/design-review.md`** — new step 6 (Functional Traceability check) emitting `UI_ORPHAN_AFFORDANCE` HIGH→major findings.
|
|
41
|
+
|
|
8
42
|
## [4.22.1] - 2026-06-09
|
|
9
43
|
|
|
10
44
|
**Epic-parent closure at end of `/new` + `/new2`.** When the last child of an epic is implemented, the epic/parent card now gets marked `DONE` automatically instead of being left stuck at `TODO`. The Phase 6b post-merge reconciliation gate only ever touched the **batch cards** (the children); the epic/parent card (`group.is_epic: true`) is excluded from the batch by the epic guard, so it never got reconciled — final-review only *reported* "all children done" without writing the epic's own status. **PATCH** (strengthens the existing Phase 6b reconciliation gate; no new agent/skill/command/config key — `/new2` inherits it because its merge agent follows `merge-cleanup.md` as SSOT).
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.
|
|
1
|
+
4.24.0
|
|
@@ -90,6 +90,28 @@ When `features.has_design_system: false`, the registry reads are skipped but
|
|
|
90
90
|
the review still flags hardcoded values and inconsistencies against
|
|
91
91
|
`${paths.ui_guidelines}`, and recommends `/design-system-init` in the report.
|
|
92
92
|
|
|
93
|
+
## Functional Traceability (MANDATORY for UI work — unconditional)
|
|
94
|
+
|
|
95
|
+
Independent of `features.has_design_system`. For any diff that introduces UI
|
|
96
|
+
affordances, enforce the gate defined in
|
|
97
|
+
[`design-system-protocol.md`](../../agents/design-system-protocol.md)
|
|
98
|
+
§ "Functional Traceability Gate":
|
|
99
|
+
|
|
100
|
+
1. For every interactive or iconographic artifact added by the diff (button,
|
|
101
|
+
link, menu item, tab, toggle, input, icon, badge, status dot), verify it
|
|
102
|
+
traces to a function — a PRD UI Element Inventory `function_ref`, a card AC,
|
|
103
|
+
or a real handler/endpoint reached by the code.
|
|
104
|
+
2. A live affordance that traces to nothing is a **`UI_ORPHAN_AFFORDANCE`** HIGH
|
|
105
|
+
finding: dead/stub `onClick`, button with no effect, unused icon import kept
|
|
106
|
+
only to mirror a mockup, menu entry routing nowhere. It ships dead code and
|
|
107
|
+
breaks the minimalism contract — block merge.
|
|
108
|
+
3. The fix is always one of: wire the real behaviour (requires an AC — flag for
|
|
109
|
+
`/prd-add` or a follow-up card), render it static/`aria-hidden` decorative,
|
|
110
|
+
or drop it. "Leave it, it matches the mockup" is never acceptable.
|
|
111
|
+
|
|
112
|
+
This is the per-merge net behind `ui-expert`'s own implementation-time gate; if
|
|
113
|
+
it trips here, the upstream gate was skipped — flag that alongside the finding.
|
|
114
|
+
|
|
93
115
|
## Scope Boundary (MUST — read first)
|
|
94
116
|
|
|
95
117
|
Your review scope is STRICTLY limited to **changed files only**.
|
|
@@ -300,18 +300,36 @@ applies even when N=1.
|
|
|
300
300
|
|
|
301
301
|
### FEAT-XXXX numbering
|
|
302
302
|
|
|
303
|
-
- Pick the next free `FEAT-XXXX` integer
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
- **
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
303
|
+
- Pick the next free `FEAT-XXXX` integer. **Reserve the entire integer for this
|
|
304
|
+
epic** — do NOT reuse the integer for unrelated cards even if it has fewer than
|
|
305
|
+
~99 children.
|
|
306
|
+
- **Use the atomic allocator when present (primary path).** Plain `max + 1` is
|
|
307
|
+
unsafe under parallelism: concurrent PRD sessions on sibling worktrees, all
|
|
308
|
+
branched from the same trunk, see the same max (the other session's card is in
|
|
309
|
+
flight on an unmerged branch) and both pick the same integer → merge conflict.
|
|
310
|
+
The `worktree-manager` allocator reserves the integer atomically across every
|
|
311
|
+
worktree on this machine (lock + shared high-water mark under `.worktrees/`):
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
# $MAIN is the main repo root: dirname of `git rev-parse --git-common-dir`.
|
|
315
|
+
ALLOC="$MAIN/.claude/skills/worktree-manager/scripts/allocate-id.sh"
|
|
316
|
+
if [ -x "$ALLOC" ]; then
|
|
317
|
+
N="$("$ALLOC" reserve FEAT "<slug>")" # e.g. prints 0024
|
|
318
|
+
# → the reserved epic id is FEAT-$N. The integer is now claimed; a parallel
|
|
319
|
+
# session calling reserve will get FEAT-$((N+1)).
|
|
320
|
+
fi
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
State the reserved integer in your response.
|
|
324
|
+
- **Fallback when the allocator is absent** (older install, no script): degrade to
|
|
325
|
+
the canonical-backlog scan — `git fetch <git.trunk_branch>`, then
|
|
326
|
+
`max` over `git grep '^id: FEAT-' $(git merge-base HEAD <git.trunk_branch>)`
|
|
327
|
+
plus the local worktree backlog. This still misses in-flight sibling-worktree
|
|
328
|
+
IDs, so surface a `[ID-RACE-RISK]` note so the caller can re-verify before
|
|
329
|
+
commit. If `git fetch` is impossible (offline), say the reservation is
|
|
330
|
+
best-effort against the local view.
|
|
331
|
+
- The same procedure applies to any other prefix you mint (`BUG`, `UI`, `DOC`,
|
|
332
|
+
`PERF`): `reserve <PREFIX> <slug>` — the allocator is prefix-parametric.
|
|
315
333
|
|
|
316
334
|
### FORBIDDEN PATTERNS — agent MUST refuse to generate
|
|
317
335
|
|
|
@@ -350,10 +368,12 @@ Mirror their structure when generating new epic+child sets.
|
|
|
350
368
|
|
|
351
369
|
Before writing the first YAML file, confirm and report to the caller:
|
|
352
370
|
|
|
353
|
-
1. **Reserved FEAT-XXXX integer**:
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
371
|
+
1. **Reserved FEAT-XXXX integer**: reserve it via the atomic allocator
|
|
372
|
+
(`allocate-id.sh reserve FEAT <slug>`), per § "FEAT-XXXX numbering" — this is
|
|
373
|
+
the concurrency-safe path that prevents duplicate IDs across parallel sessions
|
|
374
|
+
on sibling worktrees. If the allocator is absent, fall back to the trunk
|
|
375
|
+
fetch + merge-base scan with an `[ID-RACE-RISK]` note. State the chosen
|
|
376
|
+
integer in your response.
|
|
357
377
|
2. **Drafted epic + N children list**: list the planned filenames
|
|
358
378
|
(`FEAT-XXXX-00-<slug>-epic.yml`, `FEAT-XXXX-01-<sub-slug>.yml`, ...) BEFORE
|
|
359
379
|
writing them. This is a contract — the actual files MUST match the list.
|
|
@@ -498,7 +498,20 @@ Create protocol-compliant backlog cards for every actionable step. Cards MUST fo
|
|
|
498
498
|
|
|
499
499
|
#### 4.1 — NAMING CONVENTIONS
|
|
500
500
|
|
|
501
|
-
Before creating cards,
|
|
501
|
+
Before creating cards, reserve the next number for the chosen prefix. **Prefer the
|
|
502
|
+
atomic allocator** — a plain `${paths.backlog_dir}/*.yml` scan collides under
|
|
503
|
+
parallelism (concurrent sessions on sibling worktrees both pick the same "next"
|
|
504
|
+
integer and conflict at merge):
|
|
505
|
+
|
|
506
|
+
```bash
|
|
507
|
+
# $MAIN = dirname of `git rev-parse --git-common-dir` (shared across worktrees).
|
|
508
|
+
ALLOC="$MAIN/.claude/skills/worktree-manager/scripts/allocate-id.sh"
|
|
509
|
+
[ -x "$ALLOC" ] && N="$("$ALLOC" reserve FEAT <slug>") # also BUG | UI | DOC | PERF
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
If the allocator is absent (older install), fall back to scanning
|
|
513
|
+
`${paths.backlog_dir}/*.yml` for the max and add 1, noting `[ID-RACE-RISK]`. See
|
|
514
|
+
`worktree-manager` § "ID Allocation" for the mechanism. File naming rules:
|
|
502
515
|
|
|
503
516
|
| Prefix | When to use | Example |
|
|
504
517
|
|--------|-------------|---------|
|
|
@@ -255,6 +255,13 @@ project context layer at invocation time:
|
|
|
255
255
|
AND tokens-reference (no hardcoded hex/shadow/radius when
|
|
256
256
|
`has_design_system: true`)
|
|
257
257
|
3. Evaluate minimalism: unnecessary controls, button placement, cognitive load
|
|
258
|
+
3b. **Functional Traceability check** (unconditional) — for every interactive /
|
|
259
|
+
iconographic artifact in the diff, verify it traces to a function (inventory
|
|
260
|
+
`function_ref`, an AC, or a real handler/endpoint). A live affordance that
|
|
261
|
+
does nothing (dead `onClick`, unused icon import, button with no effect) is a
|
|
262
|
+
`UI_ORPHAN_AFFORDANCE` HIGH finding per
|
|
263
|
+
[`design-system-protocol.md`](../../agents/design-system-protocol.md)
|
|
264
|
+
§ "Functional Traceability Gate".
|
|
258
265
|
4. **Small screen audit**: verify touch targets (44x44px min), layout at 320px, text scaling to 200%
|
|
259
266
|
5. Check brand theming: color application, CSS variable usage
|
|
260
267
|
6. Verify separation of concerns: presentation vs. logic boundaries
|
|
@@ -275,6 +282,16 @@ project context layer at invocation time:
|
|
|
275
282
|
### When Designing New Interfaces
|
|
276
283
|
0. **BLOCKING pre-work** — execute the registry-first cascade above
|
|
277
284
|
(`design-system-protocol.md`).
|
|
285
|
+
0b. **BLOCKING — Functional Traceability Gate** (runs regardless of
|
|
286
|
+
`has_design_system`). When implementing from a mockup (your own or one handed
|
|
287
|
+
off from `/prd` / Claude Design / Figma), build the list of interactive +
|
|
288
|
+
iconographic artifacts and classify each against the oracle (PRD UI Element
|
|
289
|
+
Inventory `function_ref` → card AC → orphan) per
|
|
290
|
+
[`design-system-protocol.md`](../../agents/design-system-protocol.md)
|
|
291
|
+
§ "Functional Traceability Gate". Resolve every orphan **before** writing
|
|
292
|
+
code — drop it, render it static-only (no stub handler, no dead icon import),
|
|
293
|
+
or escalate via `AskUserQuestion` (one per orphan). Never implement an
|
|
294
|
+
unrequested affordance "to stay 1:1 with the mockup".
|
|
278
295
|
1. **Component Discovery cascade** — for every visual element the task
|
|
279
296
|
introduces or touches, walk: `${paths.design_system}/INDEX.md` →
|
|
280
297
|
`${paths.components_primitives}/*` → external catalog (shadcn / 21st.dev /
|
|
@@ -377,6 +394,16 @@ Use the Task tool to launch the `motion-expert` agent with a clear brief:
|
|
|
377
394
|
- Controls that serve edge cases but clutter the main flow
|
|
378
395
|
- Inconsistent terminology in UI labels
|
|
379
396
|
|
|
397
|
+
### Functional traceability (`UI_ORPHAN_AFFORDANCE`)
|
|
398
|
+
- An interactive artifact (button, link, menu item, toggle, tab) wired to a
|
|
399
|
+
dead/empty/stub handler — it looks actionable but does nothing
|
|
400
|
+
- An icon import kept solely to mirror the mockup, with no semantic or
|
|
401
|
+
functional role (should be dropped or `aria-hidden` decorative)
|
|
402
|
+
- A mockup affordance implemented 1:1 with no backing AC / inventory
|
|
403
|
+
`function_ref` — implement only what traces to a function; drop or
|
|
404
|
+
static-render the rest (see `design-system-protocol.md`
|
|
405
|
+
§ "Functional Traceability Gate")
|
|
406
|
+
|
|
380
407
|
### Architecture & separation of concerns
|
|
381
408
|
- Business logic embedded in component render functions
|
|
382
409
|
- Hardcoded colors instead of theme variables in branded pages
|
|
@@ -64,13 +64,25 @@ The orchestrator (`/e2e-review`) invokes you with this JSON payload:
|
|
|
64
64
|
"components_in_scope": ["Button","Card"] // present for compliance-only
|
|
65
65
|
},
|
|
66
66
|
"tolerance": "strict" | "balanced" | "lenient",
|
|
67
|
-
"fix_hints_enabled": true
|
|
67
|
+
"fix_hints_enabled": true,
|
|
68
|
+
"resolved_orphans": [ // optional — Functional Traceability Gate
|
|
69
|
+
"Export CSV button", "bell icon" // mockup affordances deliberately dropped or
|
|
70
|
+
] // downgraded to decorative; absence is EXPECTED
|
|
68
71
|
}
|
|
69
72
|
```
|
|
70
73
|
|
|
71
74
|
If `mockup_source.level == "skip"`, return immediately with
|
|
72
75
|
`{ "status": "skipped", "reason": "no_mockup_available", "findings": [] }`.
|
|
73
76
|
|
|
77
|
+
**`resolved_orphans` carve-out (Functional Traceability Gate).** The mockup is a
|
|
78
|
+
fidelity target, not a requirements oracle: it may contain interactive chrome that
|
|
79
|
+
no requirement backed, which the implementer correctly **dropped or rendered
|
|
80
|
+
static**. Any element matching an entry in `resolved_orphans` that is absent (or
|
|
81
|
+
inert) in the rendered output is **EXPECTED — never a `component-missing` or
|
|
82
|
+
`interactive-state-missing` finding**. Treat the list as authoritative over the
|
|
83
|
+
raw mockup. See `framework/agents/design-system-protocol.md` § "Functional
|
|
84
|
+
Traceability Gate".
|
|
85
|
+
|
|
74
86
|
## Severity Taxonomy (CANONICAL — cite verbatim)
|
|
75
87
|
|
|
76
88
|
This taxonomy is the SSOT consumed by `/e2e-review` and `/design-review`. Do
|
|
@@ -83,7 +95,7 @@ not invent new severity levels. Do not move categories across tiers.
|
|
|
83
95
|
| `layout-break` | Flexbox/grid container collapsed, elements stacked when designed side-by-side, vertical rhythm broken |
|
|
84
96
|
| `element-order` | DOM/visual order differs from mockup (Button placed before Title when designed after) |
|
|
85
97
|
| `responsiveness-break` | Breakpoint missing — desktop layout served on 320–375px viewport, horizontal scroll on mobile |
|
|
86
|
-
| `component-missing` | Designed component absent from rendered output (e.g. mockup has a CTA card, screen does not) |
|
|
98
|
+
| `component-missing` | Designed component absent from rendered output (e.g. mockup has a CTA card, screen does not). **Exception:** an element listed in `resolved_orphans` is expected-absent — do NOT flag it. |
|
|
87
99
|
| `component-duplicated` | Same primitive rendered twice when designed once (e.g. accidental React key collision) |
|
|
88
100
|
| `unreachable-action` | Primary CTA off-screen / behind fixed overlay / unreachable by thumb on mobile |
|
|
89
101
|
|
|
@@ -117,6 +117,7 @@ When the user invokes `/design-review`, do the following (in both modes — only
|
|
|
117
117
|
5. **Design-System Conformance section** — primitives reused, primitives reinvented (HIGH→major), tokens bypassed (HIGH→major), spec drift (MEDIUM→minor), new primitives proposed.
|
|
118
118
|
- **Mode A**: include as a top-level section in the Markdown report per `agents/design-review.md` § Report Template.
|
|
119
119
|
- **Mode B**: emit as `findings[]` entries with the appropriate categories from the taxonomy above, and populate `ds_drift_codes[]` with the deduplicated set.
|
|
120
|
+
6. **Functional Traceability check** (unconditional — independent of `has_design_system`) — flag every interactive or iconographic affordance on the route that traces to no function (dead/stub handler, button with no effect, unused icon, menu entry routing nowhere) as a `UI_ORPHAN_AFFORDANCE` HIGH→major finding, per `framework/agents/design-system-protocol.md` § "Functional Traceability Gate". The fix is wire-it / static-render / drop — never "matches the mockup".
|
|
120
121
|
|
|
121
122
|
## Mode-detection contract (avoid silent misdetection)
|
|
122
123
|
|
|
@@ -351,9 +351,16 @@ For each `route` in `plan.routes[]`:
|
|
|
351
351
|
"implementation_screenshot_path": ".baldart/e2e-review/FEAT-XXXX/screenshots/merchant-dashboard.png",
|
|
352
352
|
"mockup_source": { ... },
|
|
353
353
|
"tolerance": "<from config>",
|
|
354
|
-
"fix_hints_enabled": true
|
|
354
|
+
"fix_hints_enabled": true,
|
|
355
|
+
"resolved_orphans": [ "<mockup affordances dropped / downgraded to decorative>" ]
|
|
355
356
|
}
|
|
356
357
|
```
|
|
358
|
+
Populate `resolved_orphans` from the card's UI Element Inventory: any
|
|
359
|
+
interactive/iconographic row whose `function_ref` is `decorative` or that was
|
|
360
|
+
resolved as dropped by the Functional Traceability Gate. This prevents the
|
|
361
|
+
verifier from flagging a deliberately-removed orphan affordance as
|
|
362
|
+
`component-missing`. Omit the field (or pass `[]`) when the inventory has no
|
|
363
|
+
resolved orphans.
|
|
357
364
|
3. **Parse the agent's JSON response** strictly. If the response is malformed
|
|
358
365
|
or contains prose outside the JSON, log
|
|
359
366
|
`verifier_protocol_violation` in `gaps[]` and treat the route's findings
|
|
@@ -34,6 +34,15 @@
|
|
|
34
34
|
|
|
35
35
|
## 3. Schermate in scope
|
|
36
36
|
|
|
37
|
+
> **Vincolo di tracciabilità funzionale.** Inserisci SOLO controlli (bottoni,
|
|
38
|
+
> icone-azione, voci di menu, tab, toggle) legati a una funzione elencata qui
|
|
39
|
+
> sotto. Non aggiungere chrome interattivo "per completezza visiva": ogni
|
|
40
|
+
> affordance senza funzione diventa codice morto in implementazione. Se proponi
|
|
41
|
+
> un elemento interattivo non presente nelle azioni primarie, segnalalo in
|
|
42
|
+
> consegna come *proposta da validare*, non come requisito. Gli elementi
|
|
43
|
+
> puramente decorativi (icone di sezione, marchi) marcali esplicitamente come
|
|
44
|
+
> **decorativi**.
|
|
45
|
+
|
|
37
46
|
{{#each screens_in_scope}}
|
|
38
47
|
### {{this.name}}
|
|
39
48
|
|
|
@@ -108,7 +117,14 @@ Per ogni schermata elencata in § 3:
|
|
|
108
117
|
|
|
109
118
|
1. **PNG export** ad alta risoluzione (≥ 2x) di OGNI stato richiesto (es. `lista-ordini__empty.png`, `lista-ordini__populated.png`).
|
|
110
119
|
2. {{output_format_preference_block}} <!-- "Codice HTML/CSS in un file unico" | "Codice React (.tsx) con import Tailwind" | "Link Figma alla frame" -->
|
|
111
|
-
3. **
|
|
120
|
+
3. **Mappa di tracciabilità** — per OGNI elemento interattivo o iconografico
|
|
121
|
+
della schermata, una riga: `elemento → funzione che lo motiva` (l'azione
|
|
122
|
+
primaria / il dato di § 3 che serve), oppure marcalo **decorativo** se non ha
|
|
123
|
+
comportamento. Qualunque elemento che non riesci a tracciare a una funzione,
|
|
124
|
+
elencalo separatamente come *orphan da decidere* — così posso scegliere se
|
|
125
|
+
dargli una funzione, renderlo decorativo o rimuoverlo, invece di
|
|
126
|
+
implementarlo a vuoto.
|
|
127
|
+
4. **Note inline** su:
|
|
112
128
|
- Componenti nuovi (non presenti nel registry sopra) che hai introdotto e perché.
|
|
113
129
|
- Token deviation: qualunque colore/spacing/radius fuori dalla palette sopra, con motivazione.
|
|
114
130
|
- Decisioni di layout non ovvie (es. priority sort, hierarchy visiva, density).
|
|
@@ -1095,6 +1095,10 @@ mockup_analysis:
|
|
|
1095
1095
|
purpose: <one line — what this screen lets the user do>
|
|
1096
1096
|
components:
|
|
1097
1097
|
- <component name from ${paths.design_system}/INDEX.md if matched, else free-form>
|
|
1098
|
+
affordances: # interactive + iconographic artifacts visible in the mockup
|
|
1099
|
+
- label: <e.g. "Export CSV button", "bell icon", "Filters tab">
|
|
1100
|
+
traces_to: <US-id / AC-id / discovery dimension this artifact serves — or "" if none yet>
|
|
1101
|
+
classification: backed | decorative | orphan
|
|
1098
1102
|
states_visible: [empty, loading, error, success, default]
|
|
1099
1103
|
copy_excerpts:
|
|
1100
1104
|
- <verbatim text visible in the mockup, max ~10 strings per screen>
|
|
@@ -1122,3 +1126,25 @@ mockup_analysis:
|
|
|
1122
1126
|
or an explicit non-question (recorded in Discovery Log as "skipped — outside
|
|
1123
1127
|
mockup scope"). Gaps that remain unresolved by the end of Discovery block PRD
|
|
1124
1128
|
writing in Step 4.
|
|
1129
|
+
|
|
1130
|
+
**Affordance discipline (Functional Traceability):** classify every interactive
|
|
1131
|
+
or iconographic artifact the mockup shows. The mockup is a fidelity target, not
|
|
1132
|
+
a requirements oracle — handed-off designs (Claude Design / Figma) routinely add
|
|
1133
|
+
buttons, icons, and menu entries no requirement asked for, and implementing them
|
|
1134
|
+
1:1 ships dead code. So:
|
|
1135
|
+
- `backed` — the artifact serves a user story / AC already in scope. Record
|
|
1136
|
+
`traces_to`.
|
|
1137
|
+
- `decorative` — legitimately present but carries no behaviour (section icon,
|
|
1138
|
+
brand mark). It will render static/`aria-hidden` at implementation, never as a
|
|
1139
|
+
live control.
|
|
1140
|
+
- `orphan` — interactive-looking but traces to nothing in scope. Each orphan is
|
|
1141
|
+
a **BLOCKING Discovery item**: ask the user (one per turn, like a gap) whether
|
|
1142
|
+
it should (a) gain a real function → routes through the Scope Expansion Check /
|
|
1143
|
+
`/prd-add`, (b) be downgraded to decorative, or (c) be dropped from the design.
|
|
1144
|
+
Never carry an unresolved `orphan` past Discovery — it would silently become
|
|
1145
|
+
dead UI. Record the resolution in the Discovery Log.
|
|
1146
|
+
|
|
1147
|
+
This classification is the seed of the UI Element Inventory `function_ref`
|
|
1148
|
+
column (Step 3d) — the allowlist `ui-expert` and `code-reviewer` enforce at
|
|
1149
|
+
implementation/review via the Functional Traceability Gate
|
|
1150
|
+
(`framework/agents/design-system-protocol.md`).
|
|
@@ -157,7 +157,10 @@ This prevents designing new components when 450+ reusable ones already exist.
|
|
|
157
157
|
**STOP.** Wait for user to choose.
|
|
158
158
|
- **After Step 3c:** explicit user approval ("confermo", "va bene", "ok").
|
|
159
159
|
- **After Step 3d:** `${paths.prd_dir}/<slug>/design.html` exists (inside `$WORKTREE_PATH`) AND `## UI Element Inventory`
|
|
160
|
-
is populated in the state file with at least 3 elements
|
|
160
|
+
is populated in the state file with at least 3 elements, AND every interactive
|
|
161
|
+
or iconographic row carries a `function_ref` (an US/AC/endpoint or `decorative`) —
|
|
162
|
+
no unresolved `orphan` (Functional Traceability Gate, see
|
|
163
|
+
`framework/agents/design-system-protocol.md`).
|
|
161
164
|
- **After Step 3e (BLOCKING when `features.has_design_system: true`):** ui-design Step H
|
|
162
165
|
(Post-Intervention Coherence Check) must complete with no unresolved `DS_INDEX_DRIFT` /
|
|
163
166
|
`DS_COMPONENT_STALE` / `DS_TOKENS_DRIFT` findings (or every finding explicitly waived
|
|
@@ -252,7 +255,9 @@ to the design system, approve, and inventory. Generate ONLY the screens not cove
|
|
|
252
255
|
`design_system_alignment.violations[]` is either resolved (token/component
|
|
253
256
|
swapped in the inventory) or explicitly waived by the user (logged as such).
|
|
254
257
|
- **After 3d:** UI Element Inventory populated with at least one row per screen in
|
|
255
|
-
`screens_in_scope[]
|
|
258
|
+
`screens_in_scope[]`, every interactive/iconographic row carrying a `function_ref`
|
|
259
|
+
(no unresolved `orphan` — Functional Traceability Gate).
|
|
260
|
+
`design.html_path` set OR `mockup-only` flag recorded.
|
|
256
261
|
- **After 3e — Registry Coherence (BLOCKING when `features.has_design_system: true`):**
|
|
257
262
|
ui-design Step H (Post-Intervention Coherence Check) must complete with no
|
|
258
263
|
unresolved `DS_INDEX_DRIFT` / `DS_COMPONENT_STALE` / `DS_TOKENS_DRIFT` findings
|
|
@@ -24,10 +24,10 @@ Aggiungi al state file del PRD sotto `## UI Element Inventory`:
|
|
|
24
24
|
|
|
25
25
|
Source: ${paths.prd_dir}/<slug>/design.html (option <X>, approved <date>)
|
|
26
26
|
|
|
27
|
-
| # | Element | Type | Purpose | Inputs/Outputs | Behavior |
|
|
28
|
-
|
|
29
|
-
| 1 | <name> | page / section / component / modal / form / button / endpoint | <what it does> | <fields, props, API params> | <interactions, states, validation> |
|
|
30
|
-
| 2 | ... | ... | ... | ... | ... |
|
|
27
|
+
| # | Element | Type | Purpose | Inputs/Outputs | Behavior | Function ref |
|
|
28
|
+
|---|---------|------|---------|----------------|----------|--------------|
|
|
29
|
+
| 1 | <name> | page / section / component / modal / form / button / endpoint | <what it does> | <fields, props, API params> | <interactions, states, validation> | <US-id / AC-id / endpoint — or `decorative`> |
|
|
30
|
+
| 2 | ... | ... | ... | ... | ... | ... |
|
|
31
31
|
|
|
32
32
|
### New components to create
|
|
33
33
|
- <ComponentName> — <purpose> (NOT existing in codebase)
|
|
@@ -45,6 +45,16 @@ Source: ${paths.prd_dir}/<slug>/design.html (option <X>, approved <date>)
|
|
|
45
45
|
- Sii specifico: "Save button that calls POST /api/v1/foo/bar" non "a button" (cita l'endpoint reale).
|
|
46
46
|
- Includi error states, loading states, e empty states visibili nel design.
|
|
47
47
|
- Distingui chiaramente tra componenti da creare e componenti da riusare.
|
|
48
|
+
- **Function ref (Functional Traceability):** ogni riga interattiva o iconografica
|
|
49
|
+
DEVE citare la funzione che la giustifica — uno US-id, un AC-id, o l'endpoint
|
|
50
|
+
reale che invoca. Un elemento puramente visivo si marca `decorative` (verrà reso
|
|
51
|
+
statico/`aria-hidden`, mai come controllo vivo). Se un artefatto del design non
|
|
52
|
+
traccia a nulla ed è interattivo (un **orphan**), NON inventargli uno scopo:
|
|
53
|
+
era un orphan già emerso nell'intake del mockup (Discovery) e va risolto lì
|
|
54
|
+
(funzione reale via `/prd-add`, downgrade a `decorative`, o rimozione dal design)
|
|
55
|
+
prima di scrivere l'inventario. Questa colonna è l'allowlist che `ui-expert` e
|
|
56
|
+
`code-reviewer` fanno rispettare con il Functional Traceability Gate
|
|
57
|
+
(`framework/agents/design-system-protocol.md`).
|
|
48
58
|
|
|
49
59
|
## Cosa NON includere
|
|
50
60
|
|
|
@@ -292,7 +292,9 @@ git -C "$WORKTREE_PATH" rebase "origin/$TRUNK"
|
|
|
292
292
|
# 5. Sync local trunk ref — reuse /mw "Common" block: ff-only pull when main
|
|
293
293
|
# repo HEAD is already $TRUNK; otherwise just fetch.
|
|
294
294
|
|
|
295
|
-
# 6. Remove the registry entry for this worktree
|
|
295
|
+
# 6. Remove the registry entry for this worktree, then release its backlog-ID
|
|
296
|
+
# reservations (best-effort, high-water mark untouched):
|
|
297
|
+
# .claude/skills/worktree-manager/scripts/allocate-id.sh release "$WORKTREE_PATH" 2>/dev/null || true
|
|
296
298
|
```
|
|
297
299
|
|
|
298
300
|
**Forbidden in docs merge**: pre-merge `npm run build`, `npx eslint`, `npx tsc`, `npm run test`, post-merge `npm run build`. The whole point of docs mode is that these gates are inapplicable.
|
|
@@ -356,6 +358,57 @@ Create the file if it doesn't exist. Update it on every `/nw`, `/mw`, `/cw` oper
|
|
|
356
358
|
|
|
357
359
|
---
|
|
358
360
|
|
|
361
|
+
## ID Allocation — atomic backlog-ID reservation across parallel worktrees
|
|
362
|
+
|
|
363
|
+
When several PRD / card sessions run in parallel on sibling worktrees, each
|
|
364
|
+
branched from the same trunk, a plain `max(^id: PREFIX-) + 1` scan makes them all
|
|
365
|
+
land on the **same next integer**: the other session's card is in flight on an
|
|
366
|
+
unmerged sibling branch, invisible to both the local backlog and the trunk
|
|
367
|
+
merge-base. They collide at rebase/merge time.
|
|
368
|
+
|
|
369
|
+
`scripts/allocate-id.sh` closes that race **on the same machine**. Every worktree
|
|
370
|
+
shares one main repo root (resolved from `git rev-parse --git-common-dir`), and
|
|
371
|
+
`.worktrees/` lives there and is gitignored — so it is the natural shared
|
|
372
|
+
coordination point, exactly like `registry.json`. The allocator anchors a lock
|
|
373
|
+
and a per-prefix high-water-mark there:
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
# Reserve the next free integer for a prefix (FEAT, BUG, UI, DOC, PERF, …).
|
|
377
|
+
# Prints the integer zero-padded to 4 digits on stdout; diagnostics on stderr.
|
|
378
|
+
.claude/skills/worktree-manager/scripts/allocate-id.sh reserve FEAT <slug>
|
|
379
|
+
|
|
380
|
+
# Release a finished worktree's reservations (best-effort prune; never blocks).
|
|
381
|
+
.claude/skills/worktree-manager/scripts/allocate-id.sh release <worktree-path>
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
Shared files, all under `$MAIN/.worktrees/` (already gitignored):
|
|
385
|
+
- `.id-alloc.lock/` — cross-process mutex (atomic `mkdir`, stale-stolen after 30s
|
|
386
|
+
via the dir's own mtime; it is a directory, NOT a git ref, so it does not touch
|
|
387
|
+
the shared `refs/stash` that `git stash` in worktrees is forbidden over).
|
|
388
|
+
- `.id-hwm-<PREFIX>` — monotonic high-water mark per prefix. **This is the
|
|
389
|
+
correctness anchor**: bumped under the lock, never decremented.
|
|
390
|
+
- `id-reservations.jsonl` — append-only `{prefix,id,worktree,slug,ts}` log for
|
|
391
|
+
observability + `release` pruning. NOT load-bearing for correctness.
|
|
392
|
+
|
|
393
|
+
Inside the lock, `reserve` takes `NEXT = max(high-water file, local backlog, every
|
|
394
|
+
sibling worktree's backlog from registry.json, reservations log, trunk
|
|
395
|
+
merge-base) + 1`. The `max()` against the real backlog makes it **self-healing**:
|
|
396
|
+
if `.id-hwm-*` is ever lost or a card was created bypassing the allocator, the
|
|
397
|
+
next reservation still skips occupied numbers. Abandoned reservations leave
|
|
398
|
+
**gaps**, which is fine — backlog IDs need not be contiguous, and "reserve the
|
|
399
|
+
whole integer, never reuse" is already the rule.
|
|
400
|
+
|
|
401
|
+
**Scope: same machine only.** The shared file cannot reach separate cloud agents;
|
|
402
|
+
the trunk merge-base scan inside `reserve` is the best-effort cross-machine guard.
|
|
403
|
+
Callers (`prd-card-writer`, the `prd` agent) invoke `reserve` when the script is
|
|
404
|
+
present and **fall back to their inline merge-base scan + `[ID-RACE-RISK]` note**
|
|
405
|
+
when it is absent (older installs) — the allocator is additive, never required.
|
|
406
|
+
|
|
407
|
+
`release` is wired into the cleanup path of `/mw`, `mw-docs`, and `/cw` (see those
|
|
408
|
+
steps) so a merged/cleaned worktree's reservations are pruned.
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
359
412
|
## /nw — New Worktree
|
|
360
413
|
|
|
361
414
|
Supports three modes:
|
|
@@ -1077,6 +1130,13 @@ git worktree prune
|
|
|
1077
1130
|
|
|
1078
1131
|
Remove the entry from `.worktrees/registry.json`.
|
|
1079
1132
|
|
|
1133
|
+
Also release this worktree's backlog-ID reservations (best-effort; the high-water
|
|
1134
|
+
mark stays untouched so numbering remains monotonic):
|
|
1135
|
+
|
|
1136
|
+
```bash
|
|
1137
|
+
.claude/skills/worktree-manager/scripts/allocate-id.sh release "$WORKTREE_PATH" 2>/dev/null || true
|
|
1138
|
+
```
|
|
1139
|
+
|
|
1080
1140
|
### 8. Report
|
|
1081
1141
|
|
|
1082
1142
|
```
|
|
@@ -1184,7 +1244,12 @@ git worktree prune
|
|
|
1184
1244
|
|
|
1185
1245
|
### 4. Update registry
|
|
1186
1246
|
|
|
1187
|
-
Remove cleaned entries from `.worktrees/registry.json`.
|
|
1247
|
+
Remove cleaned entries from `.worktrees/registry.json`. For each removed
|
|
1248
|
+
worktree, also release its backlog-ID reservations (best-effort):
|
|
1249
|
+
|
|
1250
|
+
```bash
|
|
1251
|
+
.claude/skills/worktree-manager/scripts/allocate-id.sh release "<removed-worktree-path>" 2>/dev/null || true
|
|
1252
|
+
```
|
|
1188
1253
|
|
|
1189
1254
|
### 5. Report
|
|
1190
1255
|
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# allocate-id.sh — atomic, cross-worktree backlog-ID allocator (same machine).
|
|
4
|
+
#
|
|
5
|
+
# Problem it solves: several PRD/card sessions running in parallel on sibling
|
|
6
|
+
# worktrees all branch from the same trunk, so each computes the same
|
|
7
|
+
# "max(^id: PREFIX-) + 1" and they collide at rebase/merge time. A plain
|
|
8
|
+
# backlog scan cannot see IDs that are still in flight on an unmerged sibling
|
|
9
|
+
# worktree branch.
|
|
10
|
+
#
|
|
11
|
+
# Mechanism: every worktree shares the same main repo root (resolved from
|
|
12
|
+
# `git rev-parse --git-common-dir`), and `.worktrees/` lives there and is
|
|
13
|
+
# gitignored. We anchor a lock + a per-prefix high-water-mark file there, so a
|
|
14
|
+
# reservation is atomic across every worktree on this machine. The high-water
|
|
15
|
+
# bumped under the lock is the correctness mechanism; the max() against the real
|
|
16
|
+
# backlog makes it self-healing if the counter file is ever lost or bypassed.
|
|
17
|
+
#
|
|
18
|
+
# Scope: SAME MACHINE only (a shared local file). Cross-machine (separate cloud
|
|
19
|
+
# agents) is not covered here — the caller keeps the merge-base scan as a
|
|
20
|
+
# best-effort cross-machine guard and surfaces an [ID-RACE-RISK] note.
|
|
21
|
+
#
|
|
22
|
+
# Usage:
|
|
23
|
+
# allocate-id.sh reserve <PREFIX> <slug> # prints the reserved integer, zero-padded to 4
|
|
24
|
+
# allocate-id.sh release <worktree-path> # prunes that worktree's reservations (best-effort)
|
|
25
|
+
#
|
|
26
|
+
# Exit non-zero on hard failure (not a git repo, lock unobtainable) so the
|
|
27
|
+
# caller can fall back to its inline scan. All diagnostics go to stderr; stdout
|
|
28
|
+
# carries ONLY the allocated number on success.
|
|
29
|
+
|
|
30
|
+
set -u
|
|
31
|
+
|
|
32
|
+
LOCK_STALE_SECONDS=30
|
|
33
|
+
LOCK_MAX_TRIES=150 # ~30s at 0.2s/try
|
|
34
|
+
|
|
35
|
+
err() { printf '%s\n' "$*" >&2; }
|
|
36
|
+
|
|
37
|
+
# --- Resolve the shared main repo root from any worktree -------------------
|
|
38
|
+
resolve_main() {
|
|
39
|
+
local common
|
|
40
|
+
common="$(git rev-parse --git-common-dir 2>/dev/null)" || return 1
|
|
41
|
+
case "$common" in
|
|
42
|
+
/*) ;; # already absolute
|
|
43
|
+
*) common="$(pwd)/$common" ;; # relative (we're in the main repo) → absolutise
|
|
44
|
+
esac
|
|
45
|
+
(cd "$common/.." 2>/dev/null && pwd) || return 1
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# --- Read a paths.* / git.* scalar from baldart.config.yml -----------------
|
|
49
|
+
config_scalar() {
|
|
50
|
+
# $1 = top-level block (paths|git), $2 = key
|
|
51
|
+
local block="$1" key="$2" cfg="$MAIN/baldart.config.yml"
|
|
52
|
+
[ -f "$cfg" ] || return 0
|
|
53
|
+
grep -A60 "^${block}:" "$cfg" 2>/dev/null \
|
|
54
|
+
| grep -m1 "[[:space:]]*${key}:" \
|
|
55
|
+
| sed -E "s/.*${key}:[[:space:]]*\"?([^\"#]*)\"?.*/\1/" \
|
|
56
|
+
| sed -E 's/[[:space:]]+$//'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# --- Highest integer for PREFIX across one or more directories -------------
|
|
60
|
+
scan_dirs_max() {
|
|
61
|
+
local prefix="$1"; shift
|
|
62
|
+
local max="$1"; shift # starting floor
|
|
63
|
+
local d line n
|
|
64
|
+
for d in "$@"; do
|
|
65
|
+
[ -d "$d" ] || continue
|
|
66
|
+
while IFS= read -r line; do
|
|
67
|
+
[ -n "$line" ] || continue
|
|
68
|
+
n="$(printf '%s' "$line" | sed -E "s/^id:[[:space:]]*${prefix}-0*([0-9]+).*/\1/")"
|
|
69
|
+
case "$n" in ''|*[!0-9]*) continue ;; esac
|
|
70
|
+
[ "$n" -gt "$max" ] && max="$n"
|
|
71
|
+
done <<EOF
|
|
72
|
+
$(grep -rhoE "^id:[[:space:]]*${prefix}-[0-9]+" "$d"/*.yml 2>/dev/null)
|
|
73
|
+
EOF
|
|
74
|
+
done
|
|
75
|
+
printf '%s' "$max"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# --- Lock helpers (mkdir is atomic on POSIX; portable, no flock) -----------
|
|
79
|
+
# Staleness uses the lock directory's own mtime (set atomically by mkdir, and
|
|
80
|
+
# refreshed when the holder writes pid) — NOT a ts file that may not be written
|
|
81
|
+
# yet, which would race a just-created lock into an instant false steal. The
|
|
82
|
+
# critical section is local-FS only (the slow `git fetch` runs before the lock),
|
|
83
|
+
# so the stale threshold is never tripped by a legitimate holder.
|
|
84
|
+
file_mtime() { stat -f %m "$1" 2>/dev/null || stat -c %Y "$1" 2>/dev/null || echo 0; }
|
|
85
|
+
acquire_lock() {
|
|
86
|
+
local tries=0 now m age
|
|
87
|
+
mkdir -p "$WT_DIR" 2>/dev/null || true
|
|
88
|
+
while ! mkdir "$LOCKDIR" 2>/dev/null; do
|
|
89
|
+
if [ -d "$LOCKDIR" ]; then
|
|
90
|
+
now="$(date +%s)"; m="$(file_mtime "$LOCKDIR")"
|
|
91
|
+
case "$m" in ''|*[!0-9]*) m=0 ;; esac
|
|
92
|
+
age=$(( now - m ))
|
|
93
|
+
if [ "$m" -gt 0 ] && [ "$age" -gt "$LOCK_STALE_SECONDS" ]; then
|
|
94
|
+
err "WARN: stealing stale id-alloc lock (age ${age}s)"; rm -rf "$LOCKDIR" 2>/dev/null; continue
|
|
95
|
+
fi
|
|
96
|
+
fi
|
|
97
|
+
tries=$(( tries + 1 ))
|
|
98
|
+
[ "$tries" -gt "$LOCK_MAX_TRIES" ] && { err "ERROR: could not acquire id-alloc lock"; return 1; }
|
|
99
|
+
sleep 0.2
|
|
100
|
+
done
|
|
101
|
+
printf '%s\n' "$$" > "$LOCKDIR/pid" 2>/dev/null || true
|
|
102
|
+
}
|
|
103
|
+
release_lock() { rm -rf "$LOCKDIR" 2>/dev/null || true; }
|
|
104
|
+
|
|
105
|
+
# ===========================================================================
|
|
106
|
+
CMD="${1:-}"
|
|
107
|
+
MAIN="$(resolve_main)" || { err "ERROR: not inside a git repository"; exit 1; }
|
|
108
|
+
WT_DIR="$MAIN/.worktrees"
|
|
109
|
+
LOCKDIR="$WT_DIR/.id-alloc.lock"
|
|
110
|
+
RESV="$WT_DIR/id-reservations.jsonl"
|
|
111
|
+
REG="$WT_DIR/registry.json"
|
|
112
|
+
|
|
113
|
+
case "$CMD" in
|
|
114
|
+
reserve)
|
|
115
|
+
PREFIX="${2:-}"; SLUG="${3:-}"
|
|
116
|
+
[ -n "$PREFIX" ] || { err "usage: allocate-id.sh reserve <PREFIX> <slug>"; exit 2; }
|
|
117
|
+
case "$PREFIX" in *[!A-Z]*|'') err "ERROR: PREFIX must be uppercase letters (e.g. FEAT, BUG)"; exit 2 ;; esac
|
|
118
|
+
|
|
119
|
+
BACKLOG_DIR="$(config_scalar paths backlog_dir)"; [ -n "$BACKLOG_DIR" ] || BACKLOG_DIR="backlog"
|
|
120
|
+
TRUNK="$(config_scalar git trunk_branch)"
|
|
121
|
+
WT="$(git rev-parse --show-toplevel 2>/dev/null || echo "$MAIN")"
|
|
122
|
+
HWM="$WT_DIR/.id-hwm-$PREFIX"
|
|
123
|
+
|
|
124
|
+
# Refresh the remote trunk ref BEFORE taking the lock — it's the only slow
|
|
125
|
+
# (network) step; keeping it out of the critical section means a holder can
|
|
126
|
+
# never be stale-stolen mid-allocation.
|
|
127
|
+
[ -n "$TRUNK" ] && git fetch origin "$TRUNK" --quiet 2>/dev/null || true
|
|
128
|
+
|
|
129
|
+
acquire_lock || exit 1
|
|
130
|
+
trap release_lock EXIT
|
|
131
|
+
|
|
132
|
+
# 1) Floor = stored high-water mark (correctness anchor).
|
|
133
|
+
max=0
|
|
134
|
+
if [ -f "$HWM" ]; then
|
|
135
|
+
max="$(tr -cd '0-9' < "$HWM" 2>/dev/null)"; case "$max" in ''|*[!0-9]*) max=0 ;; esac
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
# 2) Local worktree backlog + every sibling worktree's backlog (in flight).
|
|
139
|
+
dirs="$MAIN/$BACKLOG_DIR"
|
|
140
|
+
[ "$WT" != "$MAIN" ] && dirs="$dirs $WT/$BACKLOG_DIR"
|
|
141
|
+
if [ -f "$REG" ]; then
|
|
142
|
+
while IFS= read -r p; do
|
|
143
|
+
[ -n "$p" ] && dirs="$dirs $p/$BACKLOG_DIR"
|
|
144
|
+
done <<EOF
|
|
145
|
+
$(grep -oE '"path"[[:space:]]*:[[:space:]]*"[^"]*"' "$REG" 2>/dev/null | sed -E 's/.*"([^"]*)"$/\1/')
|
|
146
|
+
EOF
|
|
147
|
+
fi
|
|
148
|
+
# shellcheck disable=SC2086
|
|
149
|
+
max="$(scan_dirs_max "$PREFIX" "$max" $dirs)"
|
|
150
|
+
|
|
151
|
+
# 3) Reservations log (covers a wiped HWM with reservations still in flight).
|
|
152
|
+
if [ -f "$RESV" ]; then
|
|
153
|
+
while IFS= read -r n; do
|
|
154
|
+
n="$(printf '%s' "$n" | sed -E "s/.*\"${PREFIX}-0*([0-9]+).*/\1/")"
|
|
155
|
+
case "$n" in ''|*[!0-9]*) continue ;; esac
|
|
156
|
+
[ "$n" -gt "$max" ] && max="$n"
|
|
157
|
+
done <<EOF
|
|
158
|
+
$(grep -oE "\"id\":\"${PREFIX}-[0-9]+" "$RESV" 2>/dev/null)
|
|
159
|
+
EOF
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
# 4) Best-effort cross-machine guard: merge-base of trunk (already-merged IDs).
|
|
163
|
+
# The fetch already ran before the lock; this is a local object-store read.
|
|
164
|
+
if [ -n "$TRUNK" ]; then
|
|
165
|
+
MB="$(git merge-base HEAD "origin/$TRUNK" 2>/dev/null || git merge-base HEAD "$TRUNK" 2>/dev/null || true)"
|
|
166
|
+
if [ -n "$MB" ]; then
|
|
167
|
+
while IFS= read -r n; do
|
|
168
|
+
n="$(printf '%s' "$n" | sed -E "s/^id:[[:space:]]*${PREFIX}-0*([0-9]+).*/\1/")"
|
|
169
|
+
case "$n" in ''|*[!0-9]*) continue ;; esac
|
|
170
|
+
[ "$n" -gt "$max" ] && max="$n"
|
|
171
|
+
done <<EOF
|
|
172
|
+
$(git grep -hoE "^id:[[:space:]]*${PREFIX}-[0-9]+" "$MB" -- "$BACKLOG_DIR/*.yml" 2>/dev/null)
|
|
173
|
+
EOF
|
|
174
|
+
fi
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
NEXT=$(( max + 1 ))
|
|
178
|
+
printf '%s\n' "$NEXT" > "$HWM" 2>/dev/null || true
|
|
179
|
+
printf '{"prefix":"%s","id":"%s-%04d","worktree":"%s","slug":"%s","ts":"%s"}\n' \
|
|
180
|
+
"$PREFIX" "$PREFIX" "$NEXT" "$WT" "$SLUG" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$RESV" 2>/dev/null || true
|
|
181
|
+
|
|
182
|
+
release_lock; trap - EXIT
|
|
183
|
+
printf '%04d\n' "$NEXT"
|
|
184
|
+
;;
|
|
185
|
+
|
|
186
|
+
release)
|
|
187
|
+
WT="${2:-}"
|
|
188
|
+
[ -n "$WT" ] || { err "usage: allocate-id.sh release <worktree-path>"; exit 2; }
|
|
189
|
+
[ -f "$RESV" ] || exit 0
|
|
190
|
+
acquire_lock || exit 0 # best-effort prune; never block cleanup
|
|
191
|
+
trap release_lock EXIT
|
|
192
|
+
# grep -v exit code: 0 = some lines kept, 1 = none kept (all pruned → empty
|
|
193
|
+
# file is the correct result), >=2 = real error (keep the original).
|
|
194
|
+
grep -vF "\"worktree\":\"$WT\"" "$RESV" > "$RESV.tmp" 2>/dev/null
|
|
195
|
+
if [ "$?" -le 1 ]; then
|
|
196
|
+
mv "$RESV.tmp" "$RESV" 2>/dev/null || rm -f "$RESV.tmp" 2>/dev/null
|
|
197
|
+
else
|
|
198
|
+
rm -f "$RESV.tmp" 2>/dev/null
|
|
199
|
+
fi
|
|
200
|
+
release_lock; trap - EXIT
|
|
201
|
+
;;
|
|
202
|
+
|
|
203
|
+
*)
|
|
204
|
+
err "usage: allocate-id.sh {reserve <PREFIX> <slug> | release <worktree-path>}"
|
|
205
|
+
exit 2
|
|
206
|
+
;;
|
|
207
|
+
esac
|
|
@@ -36,6 +36,12 @@ in step 1 below are SKIPPED — agents/skills still respect
|
|
|
36
36
|
When the flag is `true`, the registry reads are **BLOCKING**: skipping them is
|
|
37
37
|
a protocol violation and the agent must refuse to proceed.
|
|
38
38
|
|
|
39
|
+
**One exception — the Functional Traceability Gate is unconditional.** The
|
|
40
|
+
gate defined in § "Functional Traceability Gate" below is the only part of this
|
|
41
|
+
module that is **not** gated on `features.has_design_system`. It concerns
|
|
42
|
+
*function provenance* (does each artifact in a mockup do something?), not the
|
|
43
|
+
visual registry, so it applies to every UI surface — design-system or not.
|
|
44
|
+
|
|
39
45
|
To bootstrap a registry on a project that does not yet have one, invoke the
|
|
40
46
|
`design-system-init` skill (`/design-system-init`).
|
|
41
47
|
|
|
@@ -259,6 +265,86 @@ agent has two choices:
|
|
|
259
265
|
Never silently introduce a new component that overlaps with an existing
|
|
260
266
|
registry primitive — that is the exact failure mode this protocol prevents.
|
|
261
267
|
|
|
268
|
+
## Functional Traceability Gate (BLOCKING — unconditional)
|
|
269
|
+
|
|
270
|
+
A mockup is a fidelity target, **not** a requirements oracle. Design tools
|
|
271
|
+
(Claude Design, Figma, the `ui-design`/`frontend-design` generators) routinely
|
|
272
|
+
add interactive-looking chrome — buttons, icons, menu entries, toggles, badges —
|
|
273
|
+
that no requirement asked for. Implementing a mockup **1:1 for fidelity**
|
|
274
|
+
materialises that chrome into real markup + dead handlers + unused icon imports,
|
|
275
|
+
and the cruft then propagates forever. This gate stops it at the boundary where
|
|
276
|
+
the leak happens: **before** any artifact becomes code.
|
|
277
|
+
|
|
278
|
+
**Gating: unconditional.** Unlike the registry cascade above, this gate runs on
|
|
279
|
+
every UI surface regardless of `features.has_design_system`. It is about whether
|
|
280
|
+
each artifact *does something*, not about tokens or the registry.
|
|
281
|
+
|
|
282
|
+
### The rule
|
|
283
|
+
|
|
284
|
+
> No interactive or iconographic artifact is implemented unless it traces to a
|
|
285
|
+
> function. Orphans are resolved — never implemented "to stay faithful to the
|
|
286
|
+
> mockup".
|
|
287
|
+
|
|
288
|
+
"Interactive or iconographic artifact" = any element a user could plausibly
|
|
289
|
+
expect to act on or read as a signal: buttons, links, menu items, tabs,
|
|
290
|
+
toggles, inputs, icons, badges, status dots, action affordances. Pure layout
|
|
291
|
+
and decorative-by-construction visuals (spacers, dividers, background art,
|
|
292
|
+
hero illustrations) are out of scope — they carry no implied behaviour.
|
|
293
|
+
|
|
294
|
+
### The oracle (where "a function" comes from), in order
|
|
295
|
+
|
|
296
|
+
1. **PRD UI Element Inventory** — the `function_ref` column (added by `/prd`
|
|
297
|
+
Step 3d / the `ui-design` inventory). When present, this is the authoritative
|
|
298
|
+
allowlist: each row names the AC / user story / handler / endpoint it serves,
|
|
299
|
+
or is explicitly marked `decorative`.
|
|
300
|
+
2. **Backlog card spec / acceptance criteria** — when no inventory row exists,
|
|
301
|
+
the artifact must map to an AC or an explicit behaviour in the card.
|
|
302
|
+
3. **Neither** — the artifact is an **orphan**. Do not guess a purpose for it.
|
|
303
|
+
|
|
304
|
+
### Classification + outcome (per artifact)
|
|
305
|
+
|
|
306
|
+
| Classification | Definition | Outcome |
|
|
307
|
+
|---|---|---|
|
|
308
|
+
| **Backed** | Traces to an inventory `function_ref`, an AC, or a real handler/endpoint | **Implement** — wire the real behaviour |
|
|
309
|
+
| **Decorative** | Legitimately present but carries no behaviour (e.g. a section icon, a brand mark) | **Static-only** — render it inert: no `onClick`, no stub handler, no route, no disabled-button placeholder. If it imports an icon purely for decoration, keep it minimal and labelled `aria-hidden` |
|
|
310
|
+
| **Orphan** | Interactive-looking, traces to nothing, not deliberately decorative | **Drop or escalate** — see below |
|
|
311
|
+
|
|
312
|
+
For every **orphan**, never decide silently (consistent with the framework's
|
|
313
|
+
`always-ask, never-assume` and "no silent AC deferral" contracts):
|
|
314
|
+
|
|
315
|
+
- **Drop** when the artifact is clearly unrequested chrome and removing it does
|
|
316
|
+
not change any in-scope flow — the default for obvious leftover decoration
|
|
317
|
+
rendered as a button.
|
|
318
|
+
- **Escalate** (one `AskUserQuestion` per orphan, or a single grouped report
|
|
319
|
+
when run inside an autonomous workflow) when it is plausible the artifact
|
|
320
|
+
*should* have a function that was simply never specified — i.e. dropping it
|
|
321
|
+
might silently discard intended scope. The three offered resolutions are:
|
|
322
|
+
*implement (needs an AC → routes to `/prd-add` or a follow-up card)*,
|
|
323
|
+
*static-only*, or *drop*.
|
|
324
|
+
|
|
325
|
+
### Where it runs
|
|
326
|
+
|
|
327
|
+
1. **Design / generation time** (`ui-design`, `frontend-design`, `ui-expert`
|
|
328
|
+
designing) — when producing a mockup, do not invent interactive chrome with
|
|
329
|
+
no backing requirement; mark genuinely decorative elements as such in the
|
|
330
|
+
inventory so the downstream gate is deterministic, not a guess.
|
|
331
|
+
2. **Implementation time** (`ui-expert` implementing) — **BLOCKING pre-work**:
|
|
332
|
+
build the artifact list from the mockup, classify each row against the oracle,
|
|
333
|
+
and resolve every orphan (drop / static-only / escalate) **before** writing
|
|
334
|
+
code. Implementing an orphan as a live affordance is the exact failure mode
|
|
335
|
+
this gate prevents.
|
|
336
|
+
3. **Review time** (`code-reviewer`, `/design-review`) — verify the shipped diff
|
|
337
|
+
introduces no live affordance that traces to nothing. Each one is a
|
|
338
|
+
`UI_ORPHAN_AFFORDANCE` HIGH finding (dead handler / unused icon import / button
|
|
339
|
+
with no effect).
|
|
340
|
+
|
|
341
|
+
### Finding code
|
|
342
|
+
|
|
343
|
+
`UI_ORPHAN_AFFORDANCE` — an interactive or iconographic artifact that was
|
|
344
|
+
implemented (or is about to be) without a traceable function. HIGH severity:
|
|
345
|
+
it ships dead code and erodes the minimalism contract. The fix is always one of
|
|
346
|
+
the three outcomes above, never "leave it, it matches the mockup".
|
|
347
|
+
|
|
262
348
|
## Post-Intervention Coherence Check (BLOCKING completion gate)
|
|
263
349
|
|
|
264
350
|
The BLOCKING pre-work above enforces *reads* before any UI change. This
|
|
@@ -351,6 +437,18 @@ The following files reference this module instead of duplicating its rules:
|
|
|
351
437
|
- `framework/.claude/commands/design-review.md`
|
|
352
438
|
- `framework/agents/design-review.md`
|
|
353
439
|
|
|
440
|
+
Additional consumers of the **Functional Traceability Gate** specifically
|
|
441
|
+
(unconditional, registry-independent):
|
|
442
|
+
|
|
443
|
+
- `framework/.claude/agents/visual-fidelity-verifier.md` (`resolved_orphans`
|
|
444
|
+
carve-out — a dropped orphan is expected-absent, not `component-missing`)
|
|
445
|
+
- `framework/.claude/skills/e2e-review/SKILL.md` (passes `resolved_orphans`)
|
|
446
|
+
- `framework/.claude/skills/prd/references/discovery-phase.md` (mockup-intake
|
|
447
|
+
affordance classification — the oracle's origin)
|
|
448
|
+
- `framework/.claude/skills/prd/references/ui-design-phase.md` +
|
|
449
|
+
`framework/.claude/skills/ui-design/references/inventory.md` (the
|
|
450
|
+
`function_ref` allowlist the gate enforces)
|
|
451
|
+
|
|
354
452
|
When changing the BLOCKING cascade, change it here — not in the consumers.
|
|
355
453
|
|
|
356
454
|
## See also
|