baldart 4.92.0 → 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 +14 -0
- package/VERSION +1 -1
- package/framework/.claude/skills/e2e-review/CHANGELOG.md +10 -0
- package/framework/.claude/skills/e2e-review/SKILL.md +11 -4
- package/framework/.claude/skills/new2/CHANGELOG.md +16 -0
- package/framework/.claude/skills/new2/SKILL.md +32 -11
- package/framework/.claude/skills/ui-implement/CHANGELOG.md +5 -0
- package/framework/.claude/skills/ui-implement/SKILL.md +7 -2
- package/framework/.claude/workflows/new2.js +49 -16
- package/framework/scripts/structural-compare.mjs +7 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ 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.92.1] - 2026-07-02
|
|
9
|
+
|
|
10
|
+
**Review avversariale post-release del motore new2 (coerenza interna).** Tre reviewer indipendenti a contesto fresco (dataflow / edge-case / contratti) sull'onda v4.90–4.92; ogni finding ri-verificato contro il codice prima di agire: **11 confermati e fixati, 9 refutati con evidenza empirica** (tra i refutati: la "race" del worker-pool adattivo — simulata: 0 buchi; `verify-item.sh` su bash 3.2 reale — funziona; `parseInt` con CRLF; un CAP attribuito a `new2.js` che non esiste).
|
|
11
|
+
|
|
12
|
+
**PATCH** — hardening senza cambi di superficie. **Nessuna nuova chiave config**. **Codex parity: portable/N/A** (prosa + workflow JS Claude-only).
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **`new2.js`**: verifier strutturale che si auto-dichiara `status:"skipped"` = UNDERRUN (mai PASS — era l'ultimo canale di under-run mascherato); **re-verify deterministica** una-tantum dopo un fix strutturale risolto (il self-report del fixer non è la conferma del verifier); **`bindingCheck` consumato** (violazioni sopravvissute al pass dell'agent → resolve ui; report assente → ledger fail-open, mai silente); `${MAIN}` quotato dentro `$(git -C …)` (path con spazi).
|
|
17
|
+
- **`/e2e-review` 1.2.1**: stringa lane canonica `skipped:render_unavailable_structural_covered` scritta esplicitamente da Phase 3.8 (l'assertion di Phase 5 valida l'enum chiuso); chiave di convergenza del self-heal estesa con `viewport_role`.
|
|
18
|
+
- **`new2` 1.2.1**: Step 3.5 ramo AUTONOMOUS (CI/`BALDART_AUTONOMOUS`/origine delegata → mai una domanda, mai un apply command autonomo); **origine delegata = autonoma ovunque** (escape hatch 3b SKIPPED anche con TTY — chiuso il rischio di ciclo `/new`→`new2`→`/new` a costo pieno); contatore resume **durabile nel batch tracker** (una compaction non resetta il bound) + teardown broker Codex su ogni uscita per stallo.
|
|
19
|
+
- **`structural-compare.mjs`**: arbitrary value Tailwind con funzioni (`grid-cols-[repeat(2,_minmax(0,1fr))]`) contati con la `countTracks` parens-aware (era `split('_')` → 3 invece di 2, falso segnale di divergenza).
|
|
20
|
+
- **`ui-implement` 1.1.1**: Return Contract documenta i campi `bindings`/`lane_coverage`/`lanes` (schema SSOT in integration.md).
|
|
21
|
+
|
|
8
22
|
## [4.92.0] - 2026-07-02
|
|
9
23
|
|
|
10
24
|
**Tier 3 del programma "deep analysis /new+/ui-implement": parity tripwire, bound dei loop, verifica payload new2.** Chiude il programma a 3 tier (4.90.0 → 4.92.0).
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.92.
|
|
1
|
+
4.92.1
|
|
@@ -2,6 +2,16 @@
|
|
|
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
|
+
- Phase 3.8: la lane visual del route non renderizzabile con mockup code-form viene
|
|
8
|
+
marcata esplicitamente `skipped:render_unavailable_structural_covered` nel Lane
|
|
9
|
+
Matrix (l'enum canonico che l'assertion di Phase 5 step 0 valida); `gaps[]` tiene
|
|
10
|
+
il diagnostico `render_unavailable` — due stringhe deliberatamente distinte.
|
|
11
|
+
- Phase 5b step 3: chiave di convergenza estesa con `viewport_role`
|
|
12
|
+
(`route, source, category, viewport_role, region`) — una regressione
|
|
13
|
+
viewport-scoped non può più matchare falsamente il finding pre-fix.
|
|
14
|
+
|
|
5
15
|
## 1.2.0 — 2026-07-02
|
|
6
16
|
|
|
7
17
|
- **Self-heal convergence detection (Phase 5b step 3)**: se il fix produce finding
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: e2e-review
|
|
3
3
|
effort: medium
|
|
4
|
-
version: 1.2.
|
|
4
|
+
version: 1.2.1
|
|
5
5
|
description: >
|
|
6
6
|
Deterministic, BLOCKING end-to-end review run after a feature is implemented.
|
|
7
7
|
Combines functional E2E (Playwright spec written by `coder`, executed via
|
|
@@ -520,8 +520,12 @@ For each route with a mockup (`mockup_source.level != "skip"`):
|
|
|
520
520
|
- A comparable render was produced → it is the implementation screenshot for Phase 4.
|
|
521
521
|
- Still degenerate after the loop **and** the mockup is **code-form** (a Phase 2.7
|
|
522
522
|
`structural_source` exists) → the structural pass already covered fidelity; record
|
|
523
|
-
`render_unavailable` in `gaps[]`
|
|
524
|
-
|
|
523
|
+
`render_unavailable` in `gaps[]` (diagnostic), set the route's visual lane in the
|
|
524
|
+
Lane Matrix to **`skipped:render_unavailable_structural_covered`** (the canonical
|
|
525
|
+
enum the Phase 5 step 0 assertion validates — the two strings are deliberately
|
|
526
|
+
distinct: `gaps[]` carries the diagnostic, `lanes` carries the closed-set reason),
|
|
527
|
+
and let Phase 4's visual step skip this route's desktop diff (no false
|
|
528
|
+
coverage-gap — structure was checked route-free).
|
|
525
529
|
- Still degenerate after the loop **and** the mockup is **image-only**
|
|
526
530
|
(`level ∈ {figma, local}` with no `structural_source`) → mark the route
|
|
527
531
|
`coverage_obligation_unmet = true` for Phase 4 step 1c (it will synthesize a
|
|
@@ -894,7 +898,10 @@ For each iteration:
|
|
|
894
898
|
the Phase 4b quality pre-filter already make a clean re-run cheap.
|
|
895
899
|
3. **Re-aggregate and re-decide** (back to Phase 5 step 1), with **convergence
|
|
896
900
|
detection (v4.92.0)**: compare the new gating-finding set with the pre-fix one by
|
|
897
|
-
`(route, source, category, region)
|
|
901
|
+
`(route, source, category, viewport_role, region)` — the Phase 4 step 4
|
|
902
|
+
de-duplication key extended with `source`, so a viewport-scoped regression (a fix
|
|
903
|
+
that repairs desktop while breaking the mobile pass on the same route/category) is
|
|
904
|
+
a NEW finding, never a false match. If the fix produced **NEW gating findings that
|
|
898
905
|
were not present before** (the fixer is regressing while fixing — the documented
|
|
899
906
|
non-convergent failure mode), do NOT burn another iteration: STOP the loop now and
|
|
900
907
|
transition to the override path with both sets in the report (`convergence:
|
|
@@ -2,6 +2,22 @@
|
|
|
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
|
+
|
|
5
21
|
## 1.2.0 — 2026-07-02
|
|
6
22
|
|
|
7
23
|
- **Resume loop HARD-BOUNDED (Step 5.3)**: `resumeMaxAttempts = 3` con contatore
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: new2
|
|
3
3
|
effort: high
|
|
4
|
-
version: 1.2.
|
|
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
|
|
@@ -79,7 +79,12 @@ pre-v4.16 install).
|
|
|
79
79
|
`/new`'s AUTONOMOUS OFF-CONTEXT DELEGATION GATE invokes this skill with the same
|
|
80
80
|
residual grammar (card IDs + `-full` + `-stats` + `effort=`) — never
|
|
81
81
|
`-auto`/`-auto-ship`/`-inline` (this skill IS autonomous by contract; the
|
|
82
|
-
`AUTO_SHIP` deploy boundary stays in `/new`'s classic path).
|
|
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
|
|
83
88
|
delegates **migration-free** batches (it probes `migration_plan.required`
|
|
84
89
|
itself), so Step 3.5's one interactive question cannot fire on a delegated run —
|
|
85
90
|
if you nonetheless find a declared, unapplied migration in a delegated batch,
|
|
@@ -139,6 +144,14 @@ untouched), so the schema is live **before** the workflow starts. It mirrors `/n
|
|
|
139
144
|
modalities (always include "Già applicata — prosegui" and "Abort"). This is the SAME class as the
|
|
140
145
|
Step-2 "ONE pre-launch question" — pre-launch, not a mid-run gate; the zero-ask contract is about
|
|
141
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).
|
|
142
155
|
6. **Execute the choice in `$MAIN`**:
|
|
143
156
|
- **Schema-deploy-from-trunk-only guard** (when `stack.schema_deploy_from_trunk_only: true`): a
|
|
144
157
|
**command** modality is a remote schema deploy → run it only from trunk. Check
|
|
@@ -260,14 +273,21 @@ returns when the batch is done. It returns:
|
|
|
260
273
|
new `ts`). The per-card **skip-completed** guard makes the resume idempotent —
|
|
261
274
|
already-committed cards are skipped, only the incomplete/blocked ones run.
|
|
262
275
|
Bounds (BOTH enforced, count explicitly — do not rely on remembering):
|
|
263
|
-
- **`resumeMaxAttempts = 3`** total re-invocations per batch.
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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).
|
|
268
287
|
- **Stall detection**: if a resume completes with the SAME incomplete card set
|
|
269
|
-
as the previous attempt (zero progress
|
|
270
|
-
|
|
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.
|
|
271
291
|
An unbounded resume loop re-pays the whole batch-loop cost per cycle (~0–200k+
|
|
272
292
|
tokens each) with no new information; a persistent `degraded` is an outage or a
|
|
273
293
|
structural blocker, not something iteration fixes.
|
|
@@ -283,8 +303,9 @@ returns when the batch is done. It returns:
|
|
|
283
303
|
the offer is purely additive over an already-safe ledger — declining (or a closed
|
|
284
304
|
terminal) never drops a residual.
|
|
285
305
|
- **Skip this step entirely in AUTONOMOUS mode** (env `BALDART_AUTONOMOUS` / `CI` /
|
|
286
|
-
`GITHUB_ACTIONS` set,
|
|
287
|
-
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.
|
|
288
309
|
- **Eligible set** = the follow-ups whose residual `deferralClass` is **code-actionable**:
|
|
289
310
|
`unresolved`, `out-of-ownership`, `scope-expansion`. EXCLUDE `owner-gated` /
|
|
290
311
|
`not-a-code-defect` / `policy-deferred-ac` (external infra steps — `/new` cannot perform
|
|
@@ -2,6 +2,11 @@
|
|
|
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
|
+
|
|
5
10
|
## 1.1.0 — 2026-07-02
|
|
6
11
|
|
|
7
12
|
- **Binding-compliance gate (Step 5b)**: nuovo script deterministico
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ui-implement
|
|
3
3
|
effort: medium
|
|
4
|
-
version: 1.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
|
|
@@ -233,7 +233,12 @@ older install), `/new` falls back to an inline **coarse** build brief + a direct
|
|
|
233
233
|
|
|
234
234
|
**Mode:** COMPACT (default). Your final message is bounded — verdict
|
|
235
235
|
(`passed | blocked | overridden | skipped | error`) + the chosen build workflow +
|
|
236
|
-
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
|
|
237
242
|
`/e2e-review` report + tracker), return the short form. FULL narrative only when the
|
|
238
243
|
user invoked you directly. Schema + persist-then-summarize rule:
|
|
239
244
|
`framework/agents/return-contract-protocol.md`.
|
|
@@ -283,7 +283,7 @@ try {
|
|
|
283
283
|
preflight = await agentSafe(
|
|
284
284
|
`You are the deterministic PRE-FLIGHT for an autonomous /new batch (variant new2). Follow ${REF}/setup.md (Phase 0 + Pre-flight) and ${REF}/implement.md (Phase 1 depends-on gate) for the SEMANTICS, but replace EVERY AskUserQuestion with the deterministic policy below. You run all git/bash yourself (the workflow cannot).\n\n` +
|
|
285
285
|
`${projectBrief}\n\nCards in batch (Read each YAML):\n${cardPaths.join('\n')}\nCard IDs: ${cardIds.join(' ')}\n\n` +
|
|
286
|
-
`Create/maintain the recovery tracker at "$(git -C ${MAIN} rev-parse --git-common-dir)/baldart/run/batch-tracker-${firstCard}.md" (mkdir -p its dir first; per setup.md § Context Tracking — DURABLE location, v4.91.0: /tmp is volatile and a paused/overnight run lost it — B4).\n\n` +
|
|
286
|
+
`Create/maintain the recovery tracker at "$(git -C "${MAIN}" rev-parse --git-common-dir)/baldart/run/batch-tracker-${firstCard}.md" (mkdir -p its dir first; per setup.md § Context Tracking — DURABLE location, v4.91.0: /tmp is volatile and a paused/overnight run lost it — B4).\n\n` +
|
|
287
287
|
`ROLE BOUNDARY (specialization integrity): you are the OPS/GIT agent. You NEVER edit source or doc files — any needed content change belongs to the coder specialist; report it instead.\n\n` +
|
|
288
288
|
`DETERMINISTIC GATE POLICIES (NO user prompts):\n` +
|
|
289
289
|
`• G1 dirty-tree (main repo ${MAIN}): partition framework-managed noise exactly as setup.md step 3 ($METRICS=${METRICS}, .baldart/generated|state.json|skill-conflicts.json — NOT overlays/). Genuine user work → auto-stash 'baldart-new2-${firstCard}' (main checkout) and record the label. Never commit/abort/prompt.\n` +
|
|
@@ -614,7 +614,7 @@ async function runCard(cardId, cardPath) {
|
|
|
614
614
|
`Return: { epic, buildBlocked, blockedGate, unmetACs:[{n,text}], scopeFiles, mayEditPaths, revertedOutOfOwnership:[paths], fileDiffViolation, note }`,
|
|
615
615
|
{ label: `implement:${cardId}`, phase: 'Implement', agentType: ownerAgent,
|
|
616
616
|
schema: { type: 'object', required: ['epic', 'buildBlocked', 'unmetACs', 'scopeFiles'], additionalProperties: true,
|
|
617
|
-
properties: { epic: { type: 'boolean' }, buildBlocked: { type: 'boolean' }, blockedGate: { type: 'string' }, unmetACs: { type: 'array', items: { type: 'object', additionalProperties: true } }, scopeFiles: { type: 'array', items: { type: 'string' } }, mayEditPaths: { type: 'array', items: { type: 'string' } }, revertedOutOfOwnership: { type: 'array', items: { type: 'string' } }, fileDiffViolation: { type: 'boolean' }, note: { type: 'string' } } } }
|
|
617
|
+
properties: { epic: { type: 'boolean' }, buildBlocked: { type: 'boolean' }, blockedGate: { type: 'string' }, unmetACs: { type: 'array', items: { type: 'object', additionalProperties: true } }, scopeFiles: { type: 'array', items: { type: 'string' } }, mayEditPaths: { type: 'array', items: { type: 'string' } }, revertedOutOfOwnership: { type: 'array', items: { type: 'string' } }, fileDiffViolation: { type: 'boolean' }, note: { type: 'string' }, bindingCheck: { type: 'object', additionalProperties: true, description: 'check-bindings.mjs one-line JSON (mockup+bindings cards only)' } } } }
|
|
618
618
|
)
|
|
619
619
|
} catch (e) {
|
|
620
620
|
if (e && e.transientExhausted) { noteDegraded('outage'); return { card: cardId, status: 'pending', gates } }
|
|
@@ -653,6 +653,23 @@ async function runCard(cardId, cardPath) {
|
|
|
653
653
|
if (r.status !== 'resolved') { deferredOpen = true; deferredClasses.add(r.deferralClass || 'unresolved') }
|
|
654
654
|
}
|
|
655
655
|
|
|
656
|
+
// --- R1-F2 (v4.92.1): the binding gate result is CONSUMED, not write-only. The agent
|
|
657
|
+
// was already told to run check-bindings.mjs and fix before returning; here the workflow
|
|
658
|
+
// verifies the report: violations survive the agent's own pass → resolve (ui domain);
|
|
659
|
+
// report absent on a bindings card → fail-open but LEDGERED (audit trail, never silent).
|
|
660
|
+
if (node.hasMockup && node.hasBindings) {
|
|
661
|
+
const bc = impl && impl.bindingCheck
|
|
662
|
+
const bcViol = (bc && Array.isArray(bc.violations)) ? bc.violations : []
|
|
663
|
+
if (!bc) g('binding-gate', 'NOT-REPORTED', 'agent returned no bindingCheck — fail-open (script may be absent on older installs), recorded for audit')
|
|
664
|
+
else if (bcViol.length) {
|
|
665
|
+
g('binding-gate', 'VIOLATIONS', bcViol.map((v) => v.component).join(', '))
|
|
666
|
+
for (const v of bcViol) {
|
|
667
|
+
const r = await resolve('blocker', cardId, `binding-not-honored: region "${v.region || ''}" is bound action:${v.action || 'reuse'} → ${v.component}, but the component appears in NO touched implementation file — the binding map is authoritative (card-schema.md § component_bindings)`, { mayEditPaths: mayEdit, scopeFiles, domain: 'ui' })
|
|
668
|
+
if (r.status !== 'resolved') { deferredOpen = true; deferredClasses.add(r.deferralClass || 'unresolved') }
|
|
669
|
+
}
|
|
670
|
+
} else g('binding-gate', 'OK', `${(bc && bc.honored) || 0}/${(bc && bc.checked) || 0} bindings honored`)
|
|
671
|
+
}
|
|
672
|
+
|
|
656
673
|
// --- T1.A (v4.90.0): route-free structural fidelity lane — the new2 parallel of
|
|
657
674
|
// /e2e-review Phase 2.7. new2 has NO browser lanes (visual/quality verify runs only on
|
|
658
675
|
// the /new path), so the code-form structural check is its ONE per-card fidelity lane.
|
|
@@ -664,26 +681,42 @@ async function runCard(cardId, cardPath) {
|
|
|
664
681
|
const structSrc = node.designSrcDir
|
|
665
682
|
? { level: 'design_src', design_src_dir: node.designSrcDir }
|
|
666
683
|
: { level: 'html', mockup_path: node.designHtml }
|
|
684
|
+
const spawnMfv = () => agentSafe(
|
|
685
|
+
`Compare the implementation's code structure against the mockup's code structure and return JSON per your output schema (route-free — no browser, no dev server; cd into ${sharedCtx.worktreePath}).\n\nInput payload:\n${JSON.stringify({ card_id: cardId, route: '(card-level)', mockup_source: structSrc, implementation_files: scopeFiles, tolerance: 'strict' })}\n\nReturn STRICT JSON only: { status, findings:[{category,severity,description,expected,actual,fix_hint}] }`,
|
|
686
|
+
{ label: `fidelity:${cardId}:structural`, phase: 'Implement', agentType: 'markup-fidelity-verifier',
|
|
687
|
+
schema: { type: 'object', required: ['findings'], additionalProperties: true, properties: { status: { type: 'string' }, findings: { type: 'array', items: { type: 'object', additionalProperties: true } } } } }
|
|
688
|
+
)
|
|
667
689
|
let mfv = null
|
|
668
|
-
try {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
if (e && e.transientExhausted) noteDegraded('outage')
|
|
676
|
-
}
|
|
677
|
-
if (!mfv) {
|
|
678
|
-
g('fidelity-structural', 'UNDERRUN', 'planned lane never executed (verifier unavailable) — verification-coverage-gap, card cannot auto-DONE')
|
|
690
|
+
try { mfv = await spawnMfv() } catch (e) { if (e && e.transientExhausted) noteDegraded('outage') }
|
|
691
|
+
// R1-F1 (v4.92.1): a verifier that RETURNS but self-reports `status:"skipped"`
|
|
692
|
+
// (no_structural_ground_truth — unreadable mockup / no layout-bearing impl) is an
|
|
693
|
+
// under-run exactly like an unavailable verifier, NEVER a PASS.
|
|
694
|
+
const mfvSkipped = mfv && /skip/i.test(String(mfv.status || ''))
|
|
695
|
+
if (!mfv || mfvSkipped) {
|
|
696
|
+
g('fidelity-structural', 'UNDERRUN', `planned lane did not verify (${!mfv ? 'verifier unavailable' : 'verifier skipped: ' + String((mfv && mfv.reason) || 'no_structural_ground_truth')}) — verification-coverage-gap, card cannot auto-DONE`)
|
|
679
697
|
deferredOpen = true; deferredClasses.add('unresolved')
|
|
680
|
-
residuals.push({ card: cardId, kind: 'verification-coverage-gap', evidence:
|
|
698
|
+
residuals.push({ card: cardId, kind: 'verification-coverage-gap', evidence: `structural fidelity lane planned but did not verify (${!mfv ? 'markup-fidelity-verifier unavailable' : 'verifier skipped'})`, materialized: false })
|
|
681
699
|
} else {
|
|
682
|
-
const
|
|
700
|
+
const gatingOf = (x) => ((x && x.findings) || []).filter((f) => /critical|major/i.test(String(f.severity || '')))
|
|
701
|
+
const gating = gatingOf(mfv)
|
|
683
702
|
g('fidelity-structural', gating.length ? 'BLOCKED' : 'PASS', gating.length ? `${gating.length} gating structural finding(s)` : `ran — ${(mfv.findings || []).length} finding(s), none gating`)
|
|
703
|
+
let anyResolved = false
|
|
684
704
|
for (const f of gating) {
|
|
685
705
|
const r = await resolve('blocker', cardId, `structural fidelity (${f.category || 'layout'}): ${f.description || ''} — expected: ${f.expected || '(see mockup)'}`, { mayEditPaths: mayEdit, scopeFiles, domain: 'ui' })
|
|
686
706
|
if (r.status !== 'resolved') { deferredOpen = true; deferredClasses.add(r.deferralClass || 'unresolved') }
|
|
707
|
+
else anyResolved = true
|
|
708
|
+
}
|
|
709
|
+
// R1-F3 (v4.92.1): a resolved structural fix is RE-VERIFIED by the deterministic
|
|
710
|
+
// verifier once (mirror of /e2e-review Phase 5b step 2 — a fixer's self-report is
|
|
711
|
+
// not the verifier's confirmation). Single re-check, no loop: still-gating after
|
|
712
|
+
// the re-run → deferral (the follow-up carries it), never a second fix pass here.
|
|
713
|
+
if (anyResolved) {
|
|
714
|
+
let mfvRe = null
|
|
715
|
+
try { mfvRe = await spawnMfv() } catch (e) { if (e && e.transientExhausted) noteDegraded('outage') }
|
|
716
|
+
const reGating = mfvRe && !/skip/i.test(String(mfvRe.status || '')) ? gatingOf(mfvRe) : null
|
|
717
|
+
if (reGating === null) { g('fidelity-structural-reverify', 'UNDERRUN', 're-verify did not run — treated as unresolved'); deferredOpen = true; deferredClasses.add('unresolved') }
|
|
718
|
+
else if (reGating.length) { g('fidelity-structural-reverify', 'STILL-BLOCKED', `${reGating.length} gating finding(s) survive the fix — deferred`); deferredOpen = true; deferredClasses.add('unresolved'); residuals.push({ card: cardId, kind: 'blocker', domain: 'ui', evidence: `structural fidelity still failing after fix: ${reGating.map((f) => f.category).join(', ')}`, materialized: false }) }
|
|
719
|
+
else g('fidelity-structural-reverify', 'PASS', 'fix confirmed by the verifier')
|
|
687
720
|
}
|
|
688
721
|
}
|
|
689
722
|
}
|
|
@@ -1152,7 +1185,7 @@ if (!committed.length) {
|
|
|
1152
1185
|
} else {
|
|
1153
1186
|
try {
|
|
1154
1187
|
mergeResult = await agentSafe(
|
|
1155
|
-
`Auto-merge the batch worktree to ${TRUNK} per ${REF}/merge-cleanup.md: Phase 6 via the DETERMINISTIC script merge-worktree.sh, then Phase 6b status reconciliation + Phase 6c hygiene. Run git yourself.\n\n${projectBrief}\nWorktree: ${sharedCtx.worktreePath}\nBranch: ${sharedCtx.branch}\nmerge_strategy: ${mergeStrategy}\nCommitted cards: ${committed.map((r) => r.card).join(' ')}\nPhase-0 stash to restore (if any): see "$(git -C ${MAIN} rev-parse --git-common-dir)/baldart/run/batch-tracker-${firstCard}.md".\n\n` +
|
|
1188
|
+
`Auto-merge the batch worktree to ${TRUNK} per ${REF}/merge-cleanup.md: Phase 6 via the DETERMINISTIC script merge-worktree.sh, then Phase 6b status reconciliation + Phase 6c hygiene. Run git yourself.\n\n${projectBrief}\nWorktree: ${sharedCtx.worktreePath}\nBranch: ${sharedCtx.branch}\nmerge_strategy: ${mergeStrategy}\nCommitted cards: ${committed.map((r) => r.card).join(' ')}\nPhase-0 stash to restore (if any): see "$(git -C "${MAIN}" rev-parse --git-common-dir)/baldart/run/batch-tracker-${firstCard}.md".\n\n` +
|
|
1156
1189
|
`PHASE 6 — run the SSOT script (the merge analogue of setup-worktree.sh; NOT a hand-rolled merge, NOT /mw inline):\n` +
|
|
1157
1190
|
` MERGE_SH="$(ls .framework/framework/.claude/skills/worktree-manager/scripts/merge-worktree.sh .claude/skills/worktree-manager/scripts/merge-worktree.sh 2>/dev/null | head -1)"\n` +
|
|
1158
1191
|
` bash "$MERGE_SH" --worktree ${sharedCtx.worktreePath} --manifest /tmp/new2-merge-status-${firstCard}.txt --skip-checks\n` +
|
|
@@ -111,9 +111,14 @@ function signature(texts) {
|
|
|
111
111
|
sig.grid_cols_responsive_max = Math.max(sig.grid_cols_responsive_max, countTracks(m[1]));
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
|
-
// Tailwind: grid-cols-N (+ responsive variants), incl. arbitrary grid-cols-[a_b]
|
|
114
|
+
// Tailwind: grid-cols-N (+ responsive variants), incl. arbitrary grid-cols-[a_b].
|
|
115
|
+
// Arbitrary values use `_` as the space stand-in and may contain functions —
|
|
116
|
+
// `grid-cols-[repeat(2,_minmax(0,1fr))]` is 2 tracks, not split('_').length=3:
|
|
117
|
+
// normalize `_`→space and reuse the parens-aware countTracks().
|
|
115
118
|
for (const m of t.matchAll(/(?:^|[\s"'`])(?:(sm|md|lg|xl|2xl):)?grid-cols-(\d+|\[[^\]]+\])/g)) {
|
|
116
|
-
const cols = m[2].startsWith('[')
|
|
119
|
+
const cols = m[2].startsWith('[')
|
|
120
|
+
? countTracks(m[2].slice(1, -1).replace(/_/g, ' '))
|
|
121
|
+
: parseInt(m[2], 10);
|
|
117
122
|
sig.grid_cols_max = Math.max(sig.grid_cols_max, cols);
|
|
118
123
|
if (m[1]) {
|
|
119
124
|
sig.has_responsive_prefixes = true;
|