baldart 4.89.2 → 4.92.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +87 -0
- package/VERSION +1 -1
- package/framework/.claude/agents/api-perf-cost-auditor.md +9 -0
- package/framework/.claude/agents/code-reviewer.md +9 -0
- package/framework/.claude/agents/codebase-architect.md +6 -0
- package/framework/.claude/agents/coder.md +1 -1
- package/framework/.claude/agents/markup-fidelity-verifier.md +24 -1
- package/framework/.claude/agents/qa-sentinel.md +9 -0
- package/framework/.claude/agents/security-reviewer.md +9 -0
- package/framework/.claude/agents/ui-expert.md +49 -0
- package/framework/.claude/agents/visual-fidelity-verifier.md +16 -12
- package/framework/.claude/skills/e2e-review/CHANGELOG.md +59 -0
- package/framework/.claude/skills/e2e-review/SKILL.md +187 -26
- package/framework/.claude/skills/new/CHANGELOG.md +29 -0
- package/framework/.claude/skills/new/SKILL.md +44 -2
- package/framework/.claude/skills/new/references/completeness.md +1 -0
- package/framework/.claude/skills/new/references/implement.md +19 -3
- package/framework/.claude/skills/new/references/review-cycle.md +23 -1
- package/framework/.claude/skills/new/references/team-mode.md +4 -0
- package/framework/.claude/skills/new/scripts/verify-item.sh +54 -0
- package/framework/.claude/skills/new2/CHANGELOG.md +46 -0
- package/framework/.claude/skills/new2/SKILL.md +58 -10
- package/framework/.claude/skills/ui-implement/CHANGELOG.md +20 -0
- package/framework/.claude/skills/ui-implement/SKILL.md +37 -7
- package/framework/.claude/skills/ui-implement/references/integration.md +12 -3
- package/framework/.claude/skills/ui-implement/scripts/check-bindings.mjs +103 -0
- package/framework/.claude/workflows/new-card-review.js +19 -5
- package/framework/.claude/workflows/new-final-review.js +24 -9
- package/framework/.claude/workflows/new2.js +84 -5
- package/framework/agents/code-search-protocol.md +24 -0
- package/framework/agents/design-system-protocol.md +27 -0
- package/framework/docs/WORKFLOWS.md +9 -4
- package/framework/scripts/structural-compare.mjs +183 -0
- package/package.json +1 -1
|
@@ -2,6 +2,52 @@
|
|
|
2
2
|
|
|
3
3
|
Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
|
|
4
4
|
|
|
5
|
+
## 1.2.1 — 2026-07-02
|
|
6
|
+
|
|
7
|
+
Review avversariale post-release (3 lenti indipendenti, finding verificati):
|
|
8
|
+
- **Step 3.5 ramo AUTONOMOUS**: env-autonomous / origine delegata → mai una domanda;
|
|
9
|
+
verify green ⇒ prosegui, altrimenti `degraded` (deferral owner-gated di fine batch);
|
|
10
|
+
mai un apply command autonomo.
|
|
11
|
+
- **Origine delegata = autonoma ovunque** (Step 1.5): l'escape hatch 3b è SKIPPED anche
|
|
12
|
+
con TTY (niente ciclo /new→new2→/new a costo pieno inline).
|
|
13
|
+
- **Contatore resume DURABILE** (Step 5.3): `resume_attempt: N/3` appeso al batch
|
|
14
|
+
tracker prima di ogni re-invocazione (una compaction non resetta il bound) +
|
|
15
|
+
teardown del broker Codex prima di ogni uscita per stallo.
|
|
16
|
+
- `new2.js`: verifier strutturale `status:"skipped"` = UNDERRUN (mai PASS);
|
|
17
|
+
re-verify deterministica una-tantum dopo un fix strutturale risolto;
|
|
18
|
+
`bindingCheck` CONSUMATO (violazioni → resolve ui; assente → ledger, fail-open);
|
|
19
|
+
`${MAIN}` quotato dentro `$(git -C …)` (path con spazi).
|
|
20
|
+
|
|
21
|
+
## 1.2.0 — 2026-07-02
|
|
22
|
+
|
|
23
|
+
- **Resume loop HARD-BOUNDED (Step 5.3)**: `resumeMaxAttempts = 3` con contatore
|
|
24
|
+
esplicito + stall detection (stesso set di card incomplete = stop immediato).
|
|
25
|
+
Un `degraded` persistente è un outage/blocker strutturale, non converge iterando
|
|
26
|
+
(~0–200k+ token a ciclo).
|
|
27
|
+
|
|
28
|
+
## 1.1.1 — 2026-07-02
|
|
29
|
+
|
|
30
|
+
- Fix B4 (location parallela in `new2.js`): il preflight creava il tracker in
|
|
31
|
+
`/tmp/batch-tracker-…` (volatile — la causa dei 2/3 run con "tracker LOST");
|
|
32
|
+
ora la location DURABILE `$(git -C $MAIN rev-parse --git-common-dir)/baldart/
|
|
33
|
+
run/…` allineata a new/SKILL.md § Context Tracking. Anche il riferimento del
|
|
34
|
+
merge allo stash Phase-0 aggiornato.
|
|
35
|
+
- Cap adattivo del fan-out nei workflow review (`new-card-review.js` /
|
|
36
|
+
`new-final-review.js`): 5 → 2 al primo transient rate-limit (era fisso 3).
|
|
37
|
+
|
|
38
|
+
## 1.1.0 — 2026-07-02
|
|
39
|
+
|
|
40
|
+
- **Promossa a motore di default di `/new -auto`** (delegation gate v4.90.0):
|
|
41
|
+
non più solo variante sperimentale A/B. Nuovo Step 1.5 (contratto della
|
|
42
|
+
delega: grammar residua, mai `-auto`/`-auto-ship`/`-inline`; batch delegati
|
|
43
|
+
sempre migration-free — trovare una migrazione dichiarata su run delegata =
|
|
44
|
+
HALT + handback a `/new -inline`, mai una domanda).
|
|
45
|
+
- `new2.js`: lane di fedeltà strutturale route-free per card con mockup
|
|
46
|
+
(markup-fidelity-verifier + obbligo Lane-Ledger: verifier assente →
|
|
47
|
+
`verification-coverage-gap`, card mai auto-DONE) + binding gate deterministico
|
|
48
|
+
nel brief di implement + campi cardGraph `designSrcDir`/`designHtml`/
|
|
49
|
+
`hasBindings` (T1.A).
|
|
50
|
+
|
|
5
51
|
## 1.0.0 — 2026-07-01
|
|
6
52
|
|
|
7
53
|
- Baseline: versioning per-skill introdotto al framework v4.82.0.
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: new2
|
|
3
3
|
effort: high
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.1
|
|
5
5
|
description: >
|
|
6
|
-
|
|
6
|
+
Workflow-hosted batch engine for /new. Implements one or
|
|
7
7
|
more backlog cards end-to-end by delegating the WHOLE batch to a background
|
|
8
8
|
dynamic workflow — so subagent output never enters the main orchestrator
|
|
9
9
|
context. The batch runs autonomously (zero AskUserQuestion during the run): every
|
|
10
10
|
/new gate is replaced by a deterministic policy + a self-healing resolution pass;
|
|
11
11
|
in interactive mode an optional post-batch escape hatch can hand the hard-case
|
|
12
|
-
follow-ups to /new for the real human gate.
|
|
12
|
+
follow-ups to /new for the real human gate. Since v4.90.0 this is ALSO the
|
|
13
|
+
default engine of `/new -auto` (its delegation gate invokes this skill when the
|
|
14
|
+
Workflow tool + new2.js are present — measured driver: 53–59% orchestrator
|
|
15
|
+
turn-tax on inline -auto runs). Claude-only (needs the Workflow tool).
|
|
13
16
|
Usage: /new2 CARD-IDS (same arg grammar as /new). Triggers on:
|
|
14
17
|
/new2, "implementa le card con workflow", "new2".
|
|
15
18
|
---
|
|
@@ -71,6 +74,24 @@ Also verify the workflow script is linked: if `.claude/workflows/new2.js` is abs
|
|
|
71
74
|
print the same guidance and HALT (the framework did not link it — Codex tree, or a
|
|
72
75
|
pre-v4.16 install).
|
|
73
76
|
|
|
77
|
+
### Step 1.5 — Called by `/new -auto` (delegation note, v4.90.0)
|
|
78
|
+
|
|
79
|
+
`/new`'s AUTONOMOUS OFF-CONTEXT DELEGATION GATE invokes this skill with the same
|
|
80
|
+
residual grammar (card IDs + `-full` + `-stats` + `effort=`) — never
|
|
81
|
+
`-auto`/`-auto-ship`/`-inline` (this skill IS autonomous by contract; the
|
|
82
|
+
`AUTO_SHIP` deploy boundary stays in `/new`'s classic path). **A delegated run is
|
|
83
|
+
AUTONOMOUS-ORIGIN**: treat it exactly like the env-signalled autonomous state
|
|
84
|
+
everywhere this skill branches on it — Step 3.5 resolves deterministically (its
|
|
85
|
+
AUTONOMOUS branch) and **Step 3b's escape hatch is SKIPPED** (the user asked for
|
|
86
|
+
`-auto`; a post-batch question — and a possible `/new` re-invocation at full
|
|
87
|
+
inline cost — would break that promise even with a TTY present). `/new` only
|
|
88
|
+
delegates **migration-free** batches (it probes `migration_plan.required`
|
|
89
|
+
itself), so Step 3.5's one interactive question cannot fire on a delegated run —
|
|
90
|
+
if you nonetheless find a declared, unapplied migration in a delegated batch,
|
|
91
|
+
HALT and hand back to `/new` classic (`"batch con migrazione dichiarata — riesegui
|
|
92
|
+
/new <cards> -auto -inline"`) instead of asking. Everything else is identical to a
|
|
93
|
+
direct `/new2` invocation.
|
|
94
|
+
|
|
74
95
|
### Step 2 — Parse the invocation (same grammar as `/new`)
|
|
75
96
|
|
|
76
97
|
Parse `CARD-IDS` exactly as `/new` does — space-separated, hyphen-range
|
|
@@ -123,6 +144,14 @@ untouched), so the schema is live **before** the workflow starts. It mirrors `/n
|
|
|
123
144
|
modalities (always include "Già applicata — prosegui" and "Abort"). This is the SAME class as the
|
|
124
145
|
Step-2 "ONE pre-launch question" — pre-launch, not a mid-run gate; the zero-ask contract is about
|
|
125
146
|
the *workflow*, which is untouched.
|
|
147
|
+
**AUTONOMOUS branch (v4.92.1 — env `BALDART_AUTONOMOUS` / `CI` / `GITHUB_ACTIONS`, or a
|
|
148
|
+
delegated `/new -auto` origin per Step 1.5): NEVER ask.** Resolve deterministically:
|
|
149
|
+
(a) `migration_plan.verify` exists → run it; green ⇒ treat as "Già applicata — prosegui",
|
|
150
|
+
red/absent ⇒ (b) set `migration = { status: 'degraded', reason: 'autonomous —
|
|
151
|
+
owner-gated apply deferred' }` and continue (the workflow's end-of-batch owner-gated
|
|
152
|
+
deferral handles it — a `db-migration-deploy` residual, exactly the `/new` Phase 0 1b
|
|
153
|
+
autonomous disposition). Never run an apply **command** modality autonomously (it is an
|
|
154
|
+
outward/irreversible action).
|
|
126
155
|
6. **Execute the choice in `$MAIN`**:
|
|
127
156
|
- **Schema-deploy-from-trunk-only guard** (when `stack.schema_deploy_from_trunk_only: true`): a
|
|
128
157
|
**command** modality is a remote schema deploy → run it only from trunk. Check
|
|
@@ -239,11 +268,29 @@ returns when the batch is done. It returns:
|
|
|
239
268
|
prints nothing, set the epic card `status: DONE` + `completed_date` + note
|
|
240
269
|
`"epic-closure gate — all children DONE (post-run, new2 skill)"`, folded into the SAME
|
|
241
270
|
reconciliation commit. If any child is still open, leave the epic untouched.
|
|
242
|
-
3. **Resume if degraded.** If `degraded` is true, re-invoke
|
|
243
|
-
`Workflow({ scriptPath, resumeFromRunId })` (same `args` + the
|
|
244
|
-
per-card **skip-completed** guard makes the resume idempotent —
|
|
245
|
-
cards are skipped, only the incomplete/blocked ones run.
|
|
246
|
-
|
|
271
|
+
3. **Resume if degraded — HARD-BOUNDED (v4.92.0).** If `degraded` is true, re-invoke
|
|
272
|
+
the workflow with `Workflow({ scriptPath, resumeFromRunId })` (same `args` + the
|
|
273
|
+
new `ts`). The per-card **skip-completed** guard makes the resume idempotent —
|
|
274
|
+
already-committed cards are skipped, only the incomplete/blocked ones run.
|
|
275
|
+
Bounds (BOTH enforced, count explicitly — do not rely on remembering):
|
|
276
|
+
- **`resumeMaxAttempts = 3`** total re-invocations per batch. The counter is
|
|
277
|
+
DURABLE, not a working note: append `resume_attempt: N/3 | <ts> | incomplete:
|
|
278
|
+
<card list>` to the batch tracker (`$(git -C "$MAIN" rev-parse
|
|
279
|
+
--git-common-dir)/baldart/run/batch-tracker-<FIRST-CARD-ID>.md`) BEFORE each
|
|
280
|
+
re-invocation — a compaction/crash between resumes must not reset the count
|
|
281
|
+
(re-read the tracker's `resume_attempt` lines to recover it). At 3 still
|
|
282
|
+
`degraded` → STOP resuming: run the **Codex broker teardown first** (the same
|
|
283
|
+
idempotent two-line block as step 6 below — a stall exit must not leak the
|
|
284
|
+
broker), then surface `"batch stalled after 3 resumes — manual intervention
|
|
285
|
+
needed"` with the incomplete card list (their follow-ups are already on disk
|
|
286
|
+
from step 1 — nothing is lost by stopping).
|
|
287
|
+
- **Stall detection**: if a resume completes with the SAME incomplete card set
|
|
288
|
+
as the previous attempt (zero progress — compare against the tracker's last
|
|
289
|
+
`resume_attempt` line), stop immediately even before the cap — a second
|
|
290
|
+
identical pass will not converge either. Same teardown-then-surface exit.
|
|
291
|
+
An unbounded resume loop re-pays the whole batch-loop cost per cycle (~0–200k+
|
|
292
|
+
tokens each) with no new information; a persistent `degraded` is an outage or a
|
|
293
|
+
structural blocker, not something iteration fixes.
|
|
247
294
|
3b. **Escape-hatch escalation for the hard cases (INTERACTIVE mode only — the `new2`
|
|
248
295
|
"relaxation").** `new2` is autonomous *during the batch* — but a genuinely-blocked
|
|
249
296
|
card (the workflow rolled it back / left it `IN_PROGRESS`, DoD not met) is exactly the
|
|
@@ -256,8 +303,9 @@ returns when the batch is done. It returns:
|
|
|
256
303
|
the offer is purely additive over an already-safe ledger — declining (or a closed
|
|
257
304
|
terminal) never drops a residual.
|
|
258
305
|
- **Skip this step entirely in AUTONOMOUS mode** (env `BALDART_AUTONOMOUS` / `CI` /
|
|
259
|
-
`GITHUB_ACTIONS` set,
|
|
260
|
-
exactly as before. The escape
|
|
306
|
+
`GITHUB_ACTIONS` set, no TTY, **or a delegated `/new -auto` origin — Step 1.5**) —
|
|
307
|
+
leave the cards `IN_PROGRESS` + their follow-ups, exactly as before. The escape
|
|
308
|
+
hatch is interactive-only, and a delegated run is autonomous even with a TTY.
|
|
261
309
|
- **Eligible set** = the follow-ups whose residual `deferralClass` is **code-actionable**:
|
|
262
310
|
`unresolved`, `out-of-ownership`, `scope-expansion`. EXCLUDE `owner-gated` /
|
|
263
311
|
`not-a-code-defect` / `policy-deferred-ac` (external infra steps — `/new` cannot perform
|
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
|
|
4
4
|
|
|
5
|
+
## 1.1.1 — 2026-07-02
|
|
6
|
+
|
|
7
|
+
- Return Contract (SKILL.md): documentati i campi `bindings` + `lane_coverage`/`lanes`
|
|
8
|
+
del JSON programmatico (lo schema resta SSOT in references/integration.md).
|
|
9
|
+
|
|
10
|
+
## 1.1.0 — 2026-07-02
|
|
11
|
+
|
|
12
|
+
- **Binding-compliance gate (Step 5b)**: nuovo script deterministico
|
|
13
|
+
`scripts/check-bindings.mjs` — ogni `component_bindings` entry `reuse`/
|
|
14
|
+
`reuse-variant` deve comparire (import o uso JSX) nei file toccati dalla build;
|
|
15
|
+
violazione = `binding-not-honored` (Critical, 1 retry a `ui-expert`, poi
|
|
16
|
+
`blocked`). Chiude il buco "la mappa è autoritativa ma nessuno verifica che la
|
|
17
|
+
build l'abbia onorata". Fail-open sugli errori interni dello script.
|
|
18
|
+
- **Build-with-eyes nel briefing (Step 4)**: il brief di `ui-expert` ora porta
|
|
19
|
+
l'obbligo del self-check in-build (`ui-expert.md` § "Build-with-eyes": check
|
|
20
|
+
strutturale deterministico via `structural-compare.mjs` + check visivo con
|
|
21
|
+
screenshot Playwright quando catturabile, max 2 iterazioni, degrado onesto su
|
|
22
|
+
runtime non-multimodale) — costruisci-bene a monte invece di
|
|
23
|
+
cattura-e-ripara a valle.
|
|
24
|
+
|
|
5
25
|
## 1.0.0 — 2026-07-01
|
|
6
26
|
|
|
7
27
|
- Skill introdotta (framework v4.82.0). Estrae da `/new` il playbook mockup→code
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ui-implement
|
|
3
3
|
effort: medium
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.1
|
|
5
5
|
description: >
|
|
6
6
|
Implement an ALREADY-APPROVED mockup into the codebase end-to-end and
|
|
7
7
|
self-verify its fidelity. Takes a mockup in any channel — a Claude Design
|
|
@@ -123,11 +123,15 @@ Each step is a delegation. Read the cited SSOT; do not paraphrase it.
|
|
|
123
123
|
4. **Implement — spawn `ui-expert`.** Build via the `ui-expert` agent with a
|
|
124
124
|
briefing that carries: the chosen build workflow (Step 1), the mockup path(s),
|
|
125
125
|
the registry-first cascade declaration requirement, the design-source
|
|
126
|
-
declaration requirement (when code-src exists), the `component_bindings`,
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
126
|
+
declaration requirement (when code-src exists), the `component_bindings`, the
|
|
127
|
+
**build-with-eyes self-check obligation** (`ui-expert.md` § "Build-with-eyes"
|
|
128
|
+
— bounded structural + visual self-check BEFORE returning; the completion
|
|
129
|
+
report must carry its one-line outcome), and the i18n / states-taxonomy /
|
|
130
|
+
token-cascade constraints. All of these are `ui-expert.md`'s own contracts —
|
|
131
|
+
the briefing points at them; it does not re-teach them. (Codex /
|
|
132
|
+
no-subagents → the skill performs the build inline following the same
|
|
133
|
+
`ui-expert.md` contracts; a non-multimodal runtime degrades the visual
|
|
134
|
+
self-check to a logged skip.)
|
|
131
135
|
|
|
132
136
|
5. **Ground the design-source declaration** *(only when the card has
|
|
133
137
|
`links.design_src` / `mockups/_src/` exists)*. After the agent's first
|
|
@@ -137,6 +141,27 @@ Each step is a delegation. Read the cited SSOT; do not paraphrase it.
|
|
|
137
141
|
re-spawn. Log `design-source: grounded` | `rejected (<reason>)`. (This is the
|
|
138
142
|
Step 7b grounding lifted out of `/new`.)
|
|
139
143
|
|
|
144
|
+
5b. **Binding-compliance gate (deterministic — only when the card carries
|
|
145
|
+
`component_bindings`).** The bindings are authoritative (Step 3), but until
|
|
146
|
+
now nothing verified the built code actually HONORED them. Run:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
node .claude/skills/ui-implement/scripts/check-bindings.mjs \
|
|
150
|
+
--bindings-json '<the card's component_bindings serialized to JSON>' \
|
|
151
|
+
--files <the build's touched implementation files>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
One JSON line on stdout; exit `3` = violations. Every `reuse`/`reuse-variant`
|
|
155
|
+
binding whose component appears in NO touched file (import or JSX usage) is a
|
|
156
|
+
**`binding-not-honored`** finding (Critical, UI domain): re-spawn `ui-expert`
|
|
157
|
+
ONCE with the violation list ("the binding map is authoritative — use
|
|
158
|
+
`<Component>` for region `<region>`, do not re-invent it"); still violated →
|
|
159
|
+
the card is `blocked` (this is a fidelity-contract breach, not a taste issue).
|
|
160
|
+
`action: new` entries are informational here — their governance belongs to the
|
|
161
|
+
Post-Intervention Coherence Check (Step 6). Script errors fail OPEN (`skipped`,
|
|
162
|
+
exit 0) — the gate is a safety net, never a tribunal. Log
|
|
163
|
+
`bindings: ok (N honored)` | `violations (<components>)` | `skipped`.
|
|
164
|
+
|
|
140
165
|
6. **Post-Intervention Coherence Check (BLOCKING).** Reconcile the design-system
|
|
141
166
|
sources in the SAME change — CITE `design-system-protocol.md`
|
|
142
167
|
§ "Post-Intervention Coherence Check": INDEX (`DS_INDEX_DRIFT`), component spec
|
|
@@ -208,7 +233,12 @@ older install), `/new` falls back to an inline **coarse** build brief + a direct
|
|
|
208
233
|
|
|
209
234
|
**Mode:** COMPACT (default). Your final message is bounded — verdict
|
|
210
235
|
(`passed | blocked | overridden | skipped | error`) + the chosen build workflow +
|
|
211
|
-
key findings as `path:line` + `Report: <path>`.
|
|
236
|
+
key findings as `path:line` + `Report: <path>`. The programmatic JSON ALSO carries
|
|
237
|
+
(since 1.1.1): `bindings` (the Step 5b `check-bindings.mjs` one-line result, when
|
|
238
|
+
the card has `component_bindings`) and `lane_coverage` + `lanes` (forwarded from
|
|
239
|
+
the internal `/e2e-review` report) — full schema:
|
|
240
|
+
[references/integration.md](references/integration.md) § Return contract (the
|
|
241
|
+
SSOT; do not restate it here). Persist the long form (the
|
|
212
242
|
`/e2e-review` report + tracker), return the short form. FULL narrative only when the
|
|
213
243
|
user invoked you directly. Schema + persist-then-summarize rule:
|
|
214
244
|
`framework/agents/return-contract-protocol.md`.
|
|
@@ -34,6 +34,9 @@ only the input source and the return shape differ.
|
|
|
34
34
|
"findings": [ /* gating findings, canonical severity taxonomy */ ],
|
|
35
35
|
"ds_drift_codes": [ "DS_INDEX_DRIFT" | "DS_COMPONENT_STALE" | "DS_TOKENS_DRIFT" ],
|
|
36
36
|
"design_system_coherence": { /* INDEX / components / tokens / mirror reconciled */ },
|
|
37
|
+
"bindings": { /* Step 5b check-bindings.mjs one-line JSON (skill ≥1.1.0); absent when the card has no component_bindings */ },
|
|
38
|
+
"lane_coverage": "complete" | "incomplete",
|
|
39
|
+
"lanes": { /* forwarded from the internal /e2e-review report (skill ≥1.1.0, e2e-review ≥1.1.0) */ },
|
|
37
40
|
"report_path": ".baldart/e2e-review/<id>/report.json"
|
|
38
41
|
}
|
|
39
42
|
```
|
|
@@ -85,8 +88,14 @@ same opt-in contract as `/prd` → `/ds-handoff`.
|
|
|
85
88
|
## `new2` (workflow) note
|
|
86
89
|
|
|
87
90
|
`new2.js` hosts the batch in a background workflow and reads `implement.md` /
|
|
88
|
-
`review-cycle.md` for the per-card semantics.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
+
`review-cycle.md` for the per-card semantics. `new2` keeps its **inline**
|
|
92
|
+
implement path (the fallback prose stays valid), with two T1.A parallels wired in
|
|
93
|
+
directly (v4.90.0): the mockup card's implement brief carries the
|
|
94
|
+
**binding-compliance gate** (same `check-bindings.mjs` script as Step 5b), and a
|
|
95
|
+
**route-free structural fidelity lane** (`markup-fidelity-verifier`) runs after
|
|
96
|
+
implement for every card with a code-form mockup — its Lane-Ledger obligation
|
|
97
|
+
(`design-system-protocol.md` § 6): verifier unavailable → `verification-coverage-gap`
|
|
98
|
+
residual, the card cannot auto-DONE. Browser lanes (visual/quality) remain
|
|
99
|
+
`/new`-path only. Full `new2` parity — delegating the UI card's implement+verify
|
|
91
100
|
off-context via `workflow()` — is a follow-up, to be taken after the context-economy
|
|
92
101
|
measurement (`SKILL.md` scope note + the plan's measure-first gate).
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// check-bindings.mjs — deterministic `binding-not-honored` gate (T1.A, framework v4.90.0).
|
|
3
|
+
//
|
|
4
|
+
// A card's `component_bindings` (card-schema.md § component_bindings) is AUTHORITATIVE
|
|
5
|
+
// over the mockup: each `action: reuse | reuse-variant` entry promises that the bound
|
|
6
|
+
// registry component is actually USED in the implementation. Until now nothing verified
|
|
7
|
+
// that promise after the build — a ui-expert that ignored a binding and re-invented the
|
|
8
|
+
// primitive sailed through (the verifier trio checks fidelity-to-mockup, not
|
|
9
|
+
// binding-compliance). This script closes that gap without a model call.
|
|
10
|
+
//
|
|
11
|
+
// Contract:
|
|
12
|
+
// node check-bindings.mjs --bindings-json '<json array>' --files <f1> <f2> ...
|
|
13
|
+
// node check-bindings.mjs --bindings-file <path.json> --files <f1> <f2> ...
|
|
14
|
+
//
|
|
15
|
+
// - `--bindings-json` is the card's `component_bindings` serialized to JSON by the
|
|
16
|
+
// caller (the orchestrator/agent already has the card in context; YAML parsing is
|
|
17
|
+
// deliberately NOT re-implemented here — the deterministic part is the code check).
|
|
18
|
+
// - `--files` are the implementation files the card touched (the build's scopeFiles).
|
|
19
|
+
// - Prints ONE JSON line to stdout:
|
|
20
|
+
// { status: "ok" | "violations" | "skipped",
|
|
21
|
+
// checked, honored, violations:[{component, region, action}],
|
|
22
|
+
// results:[{component, region, action, status, matched_in:[...]}] }
|
|
23
|
+
// - Exit codes: 0 = ok/skipped (nothing to gate), 3 = violations (gate fires).
|
|
24
|
+
// Internal errors NEVER crash the caller: fail-open → {status:"skipped"} exit 0
|
|
25
|
+
// (same philosophy as the framework-edit-gate: a safety net, not a tribunal).
|
|
26
|
+
//
|
|
27
|
+
// Matching rule (deterministic, conservative — prefer false-negative over false-block):
|
|
28
|
+
// an entry with action reuse/reuse-variant is HONORED when the component identifier
|
|
29
|
+
// appears in ≥1 provided file as an import specifier OR a JSX opening tag
|
|
30
|
+
// (`<Component` / `<NS.Component`). `action: new` entries are reported as
|
|
31
|
+
// informational (`new-component`) and never count as violations here — the
|
|
32
|
+
// Post-Intervention Coherence Check owns their governance.
|
|
33
|
+
|
|
34
|
+
import fs from 'node:fs';
|
|
35
|
+
|
|
36
|
+
function emit(obj, code) {
|
|
37
|
+
process.stdout.write(JSON.stringify(obj) + '\n');
|
|
38
|
+
process.exit(code);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function parseArgs(argv) {
|
|
42
|
+
const a = { bindingsJson: null, bindingsFile: null, files: [] };
|
|
43
|
+
for (let i = 2; i < argv.length; i++) {
|
|
44
|
+
if (argv[i] === '--bindings-json') a.bindingsJson = argv[++i];
|
|
45
|
+
else if (argv[i] === '--bindings-file') a.bindingsFile = argv[++i];
|
|
46
|
+
else if (argv[i] === '--files') {
|
|
47
|
+
while (i + 1 < argv.length && !argv[i + 1].startsWith('--')) a.files.push(argv[++i]);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return a;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function main() {
|
|
54
|
+
const args = parseArgs(process.argv);
|
|
55
|
+
let raw = args.bindingsJson;
|
|
56
|
+
if (!raw && args.bindingsFile) {
|
|
57
|
+
if (!fs.existsSync(args.bindingsFile)) emit({ status: 'skipped', reason: 'bindings file not found: ' + args.bindingsFile }, 0);
|
|
58
|
+
raw = fs.readFileSync(args.bindingsFile, 'utf8');
|
|
59
|
+
}
|
|
60
|
+
if (!raw) emit({ status: 'skipped', reason: 'no --bindings-json / --bindings-file' }, 0);
|
|
61
|
+
|
|
62
|
+
let bindings;
|
|
63
|
+
try { bindings = JSON.parse(raw); } catch (e) { emit({ status: 'skipped', reason: 'bindings JSON unparsable: ' + e.message }, 0); }
|
|
64
|
+
if (!Array.isArray(bindings) || bindings.length === 0) emit({ status: 'ok', checked: 0, honored: 0, violations: [], results: [], note: 'no bindings to check' }, 0);
|
|
65
|
+
|
|
66
|
+
const sources = [];
|
|
67
|
+
for (const f of args.files) {
|
|
68
|
+
try {
|
|
69
|
+
if (fs.existsSync(f) && fs.statSync(f).isFile()) sources.push({ path: f, text: fs.readFileSync(f, 'utf8') });
|
|
70
|
+
} catch { /* unreadable file → treated as absent, fail-open per-file */ }
|
|
71
|
+
}
|
|
72
|
+
if (sources.length === 0) emit({ status: 'skipped', reason: 'no readable implementation files passed via --files' }, 0);
|
|
73
|
+
|
|
74
|
+
const results = [];
|
|
75
|
+
for (const b of bindings) {
|
|
76
|
+
const component = String(b.component || b.name || '').trim();
|
|
77
|
+
const action = String(b.action || 'reuse').trim();
|
|
78
|
+
const region = String(b.region || '').trim();
|
|
79
|
+
if (!component) { results.push({ component: '(missing)', region, action, status: 'skipped-malformed', matched_in: [] }); continue; }
|
|
80
|
+
if (action === 'new') { results.push({ component, region, action, status: 'new-component', matched_in: [] }); continue; }
|
|
81
|
+
|
|
82
|
+
// Import specifier or JSX opening tag, word-bounded. Conservative: any hit counts.
|
|
83
|
+
const esc = component.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
84
|
+
const importRe = new RegExp(`import[^;]*\\b${esc}\\b[^;]*from`, 'm');
|
|
85
|
+
const jsxRe = new RegExp(`<(?:[A-Za-z_$][\\w$]*\\.)?${esc}[\\s/>]`);
|
|
86
|
+
const bareRe = new RegExp(`\\b${esc}\\b`);
|
|
87
|
+
const matched = sources.filter((s) => importRe.test(s.text) || jsxRe.test(s.text) || (/\.(css|scss)$/.test(s.path) && bareRe.test(s.text)));
|
|
88
|
+
results.push({
|
|
89
|
+
component, region, action,
|
|
90
|
+
status: matched.length ? 'honored' : 'not-found',
|
|
91
|
+
matched_in: matched.map((s) => s.path),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const violations = results.filter((r) => r.status === 'not-found').map(({ component, region, action }) => ({ component, region, action }));
|
|
96
|
+
const honored = results.filter((r) => r.status === 'honored').length;
|
|
97
|
+
emit(
|
|
98
|
+
{ status: violations.length ? 'violations' : 'ok', checked: results.length, honored, violations, results },
|
|
99
|
+
violations.length ? 3 : 0
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try { main(); } catch (e) { emit({ status: 'skipped', reason: 'internal error: ' + (e && e.message) }, 0); }
|
|
@@ -46,18 +46,28 @@ const protocolRef = '.claude/skills/new/references/review-cycle.md'
|
|
|
46
46
|
// (kills agents mid-work). Cap concurrent agents at 3 — a rolling worker pool (NOT chunked barriers,
|
|
47
47
|
// which would block fast finders behind the slow background Codex poll). Mirrors the team-mode coder
|
|
48
48
|
// cap so review + implementation obey the same ceiling.
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
// ADAPTIVE since v4.91.0 (mirrors new-final-review.js): start at 5; on the FIRST rate-limit
|
|
50
|
+
// transient anywhere in the run drop to 2 (below the old fixed 3 — when the shared org limit
|
|
51
|
+
// is hot, narrower is what keeps reviewers alive; when it is not, the fixed cap was pure
|
|
52
|
+
// queue-wait tax). Surplus workers retire at their next pickup.
|
|
53
|
+
const CAP = { value: 5 }
|
|
54
|
+
const MAX_PARALLEL = CAP.value // kept for arg-compat at call sites; capNow() is authoritative
|
|
55
|
+
const capNow = () => CAP.value
|
|
56
|
+
function noteRateLimitPressure(src) {
|
|
57
|
+
if (CAP.value > 2) { CAP.value = 2; log(`parallelCapped: rate-limit transient (${src}) → cap 5→2 for the rest of the run`) }
|
|
58
|
+
}
|
|
59
|
+
async function parallelCapped(thunks, _cap) {
|
|
51
60
|
if (!Array.isArray(thunks) || !thunks.length) return []
|
|
52
61
|
const results = new Array(thunks.length)
|
|
53
62
|
let next = 0
|
|
54
|
-
const worker = async () => {
|
|
63
|
+
const worker = async (idx) => {
|
|
55
64
|
while (next < thunks.length) {
|
|
65
|
+
if (idx >= capNow()) return // adaptive shrink
|
|
56
66
|
const i = next++
|
|
57
67
|
try { results[i] = await thunks[i]() } catch (_) { results[i] = null }
|
|
58
68
|
}
|
|
59
69
|
}
|
|
60
|
-
await Promise.all(Array.from({ length: Math.min(
|
|
70
|
+
await Promise.all(Array.from({ length: Math.min(capNow(), thunks.length) }, (_, idx) => worker(idx)))
|
|
61
71
|
return results
|
|
62
72
|
}
|
|
63
73
|
|
|
@@ -79,7 +89,11 @@ async function agentSafe(prompt, opts, maxAttempts) {
|
|
|
79
89
|
try {
|
|
80
90
|
const o = i === 0 ? opts : Object.assign({}, opts, { label: `${(opts && opts.label) || 'agent'}~retry${i}` })
|
|
81
91
|
return await agent(prompt, o)
|
|
82
|
-
} catch (e) {
|
|
92
|
+
} catch (e) {
|
|
93
|
+
lastErr = e
|
|
94
|
+
if (!isTransient(e)) throw e // permanent error → surface
|
|
95
|
+
noteRateLimitPressure((opts && opts.label) || 'agent') // v4.91.0 — shrink the pool for the rest of the run
|
|
96
|
+
}
|
|
83
97
|
}
|
|
84
98
|
const err = new Error('transient-exhausted: ' + String((lastErr && lastErr.message) || lastErr))
|
|
85
99
|
err.transientExhausted = true
|
|
@@ -53,22 +53,33 @@ const editableFiles = Array.isArray(a.editableFiles) ? a.editableFiles.filter(Bo
|
|
|
53
53
|
const tcCmds = (cfg.toolchain && cfg.toolchain.commands) || {}
|
|
54
54
|
const tc = (key, fallback) => (tcCmds[key] && String(tcCmds[key]).trim()) || fallback
|
|
55
55
|
|
|
56
|
-
// Concurrency cap (v4.53.6
|
|
57
|
-
// coder cap). The org-level rate limit is SHARED across every terminal/session; a wide
|
|
58
|
-
// saturates it and the server throttles
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
// Concurrency cap (v4.53.6 guardrail, ADAPTIVE since v4.91.0 — mirrors new-card-review.js + the
|
|
57
|
+
// team-mode coder cap). The org-level rate limit is SHARED across every terminal/session; a wide
|
|
58
|
+
// agent fan-out saturates it and the server throttles (the v4.66.0 transient-kill class). The old
|
|
59
|
+
// fixed cap of 3 protected against that but taxed EVERY run's wall-clock, including the majority
|
|
60
|
+
// with no rate pressure (measured: review fan-out ≈ 50 min on a small batch, mostly queue wait).
|
|
61
|
+
// Adaptive policy: start at 5; on the FIRST rate-limit transient seen anywhere in the run, drop to
|
|
62
|
+
// 2 for the remainder (BELOW the old cap — when the org limit is hot, narrower is what saves the
|
|
63
|
+
// reviewers). Rolling worker pool (NOT chunked barriers — those would block fast finders behind
|
|
64
|
+
// the slow background Codex poll); workers above the current cap retire at their next pickup.
|
|
65
|
+
const CAP = { value: 5 }
|
|
66
|
+
const MAX_PARALLEL = CAP.value // kept for arg-compat at call sites; capNow() is authoritative
|
|
67
|
+
const capNow = () => CAP.value
|
|
68
|
+
function noteRateLimitPressure(src) {
|
|
69
|
+
if (CAP.value > 2) { CAP.value = 2; log(`parallelCapped: rate-limit transient (${src}) → cap 5→2 for the rest of the run`) }
|
|
70
|
+
}
|
|
71
|
+
async function parallelCapped(thunks, _cap) {
|
|
62
72
|
if (!Array.isArray(thunks) || !thunks.length) return []
|
|
63
73
|
const results = new Array(thunks.length)
|
|
64
74
|
let next = 0
|
|
65
|
-
const worker = async () => {
|
|
75
|
+
const worker = async (idx) => {
|
|
66
76
|
while (next < thunks.length) {
|
|
77
|
+
if (idx >= capNow()) return // adaptive shrink: retire surplus workers at pickup time
|
|
67
78
|
const i = next++
|
|
68
79
|
try { results[i] = await thunks[i]() } catch (_) { results[i] = null }
|
|
69
80
|
}
|
|
70
81
|
}
|
|
71
|
-
await Promise.all(Array.from({ length: Math.min(
|
|
82
|
+
await Promise.all(Array.from({ length: Math.min(capNow(), thunks.length) }, (_, idx) => worker(idx)))
|
|
72
83
|
return results
|
|
73
84
|
}
|
|
74
85
|
|
|
@@ -91,7 +102,11 @@ async function agentSafe(prompt, opts, maxAttempts) {
|
|
|
91
102
|
try {
|
|
92
103
|
const o = i === 0 ? opts : Object.assign({}, opts, { label: `${(opts && opts.label) || 'agent'}~retry${i}` })
|
|
93
104
|
return await agent(prompt, o)
|
|
94
|
-
} catch (e) {
|
|
105
|
+
} catch (e) {
|
|
106
|
+
lastErr = e
|
|
107
|
+
if (!isTransient(e)) throw e // permanent error → surface
|
|
108
|
+
noteRateLimitPressure((opts && opts.label) || 'agent') // v4.91.0 — shrink the pool for the rest of the run
|
|
109
|
+
}
|
|
95
110
|
}
|
|
96
111
|
const err = new Error('transient-exhausted: ' + String((lastErr && lastErr.message) || lastErr))
|
|
97
112
|
err.transientExhausted = true
|