@adia-ai/adia-ui-forge 0.8.51 → 0.8.52
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +16 -0
- package/README.md +3 -1
- package/__init__.py +20 -9
- package/agents/a2ui-maintenance-agent.md +6 -4
- package/agents/a2ui-planner-agent.md +5 -5
- package/agents/demo-audit-agent.md +4 -4
- package/agents/framework-checker.md +5 -5
- package/agents/framework-planner-agent.md +7 -6
- package/agents/gen-ui-review-agent.md +4 -4
- package/agents/package-release-agent.md +4 -2
- package/agents/primitive-authoring-agent.md +1 -0
- package/package.json +1 -1
- package/plugin.yaml +1 -1
- package/skills/cross-harness-authoring-standards/SKILL.md +120 -0
- package/skills/cross-harness-authoring-standards/agents/openai.yaml +3 -0
- package/skills/cross-harness-compatibility-standards/SKILL.md +219 -0
- package/skills/cross-harness-compatibility-standards/agents/openai.yaml +3 -0
- package/skills/package-release/references/cut-procedure.md +2 -0
- package/skills/package-release/scripts/release-pack.mjs +244 -42
- package/skills/primitive-authoring/references/api-contract.md +35 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adia-ui-kit-forge",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.52",
|
|
4
4
|
"description": "Maintain the adia-ui (@adia-ai) framework itself \u2014 author primitives and shells, run the A2UI generation pipeline and its corpus, review gen-UI quality, sweep QA, cut releases, deploy. The maintainer counterpart to adia-ui-kit-factory (the consumer/app-author plugin).",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Kim",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adia-ui-kit-forge",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.52",
|
|
4
4
|
"description": "Maintain the adia-ui (@adia-ai) framework itself — author primitives and shells, run the A2UI generation pipeline and its corpus, review gen-UI quality, sweep QA, cut releases, deploy. The maintainer counterpart to adia-ui-kit-factory (the consumer/app-author plugin).",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Kim",
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog — adia-ui-kit-forge
|
|
2
2
|
|
|
3
|
+
## [0.8.52] — 2026-08-25
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **`cross-harness-authoring-standards` (renamed from `writing-harness-install-instructions`, which had only ever existed as uncommitted worktree content) + new sibling skill `cross-harness-compatibility-standards` (gh#1975, PR #2003).** The rename is byte-faithful aside from the three self-referential mentions (frontmatter name, H1, one in-body reference). The new skill is the upstream engineering standard for actually building a capability that works across Claude Code, Codex, Pi, and Hermes — distinct from the renamed sibling's downstream consumer-facing install-docs concern — extracted from ADR-0091 and the four PRs (#1997-#2000) that built and shipped it: the skill-vs-thin-adapter classification rule, verify-before-coding per harness, one authored skill body with every runtime as a thin consumer, one contract grammar (record-lint family) instead of a schema per runtime, and naming distribution channels distinctly. Codex and Hermes derived manifests regenerated for both skills; README/`AGENTS.md` skill-roster and count updated.
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
- **`package-release`'s pre-cut roster runs in three phases instead of one serial for-loop (gh#2006, follow-up from gh#1987's audit)** — `release-pack.mjs`'s `step3PreFlight()` now runs gate 4 (`test:unit:serial`) solo, the eval-health chain (gates 16 → 27 → 28 — a real CSS-before-JS-shaped file-write ordering dependency: gate 28 reads whichever `evals/mcp/runs/` directory sorts lexically last, so gate 27's free-form run must land uninterrupted) strictly in order, and every other gate in a concurrency-capped pool (`PREFLIGHT_CONCURRENCY`, default 4) — all concurrent with the chain. Cuts real wall-clock with zero coverage loss: no gate dropped, none renumbered, none silently skipped. `--dry` previews are unchanged (still the original flat serial walk, byte-for-byte, so `selftest`'s exact-output assertions keep passing).
|
|
10
|
+
- **All agent seats pinned to `sonnet` + `xhigh` reasoning effort, replacing the prior fable/opus ceiling-ladder pins (PR #1999).** Operator's direct standing instruction, applied across `a2ui-maintenance-agent`, `a2ui-planner-agent`, `demo-audit-agent`, `framework-checker`, `framework-planner-agent`, `gen-ui-review-agent`, `package-release-agent`, `primitive-authoring-agent`. See root `CHANGELOG.md` for the cross-plugin note.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **`__init__.py`'s Hermes manifest generator's `register_skill` call passed a plain string where the runtime requires a `pathlib.Path` (calls `path.exists()`) — wrapped, discovered incidentally while building `adia-ui-factory`'s Hermes `delegate_task` command (PR #2000).**
|
|
14
|
+
|
|
15
|
+
### Maintenance
|
|
16
|
+
- **`.claude-plugin/plugin.json` version bump** — moves in lockstep with package.json (the `/plugin update` cache key).
|
|
17
|
+
- **`.codex-plugin/` touched in this release window** (1 file(s), e.g. `.codex-plugin/plugin.json`) — carried by the entries above.
|
|
18
|
+
|
|
3
19
|
## [0.8.51] — 2026-08-24
|
|
4
20
|
|
|
5
21
|
### Added
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Re-engineered 2026-07 for the Fable 5 harness (design:
|
|
|
8
8
|
`.claude/docs/specs/plugin-estate-v2.md`): one skill per job, explicit
|
|
9
9
|
invocation dials, references for depth, mechanical rules in hooks, thin agents.
|
|
10
10
|
|
|
11
|
-
## Skills (
|
|
11
|
+
## Skills (11)
|
|
12
12
|
|
|
13
13
|
| Skill | Species | Job |
|
|
14
14
|
|---|---|---|
|
|
@@ -21,6 +21,8 @@ invocation dials, references for depth, mechanical rules in hooks, thin agents.
|
|
|
21
21
|
| `site-deployment` | procedural | exe.dev service ops — site deploys, VM diagnosis, secret rotation |
|
|
22
22
|
| `site-docs-authoring` | procedural | author/review `site/pages/` docs pages against the shared skeleton |
|
|
23
23
|
| `ssr-compatibility` | knowledge | answers SSR failure-shape questions (linkedom/Astro consumers) |
|
|
24
|
+
| `cross-harness-authoring-standards` | procedural | author consumer-facing multi-harness install instructions |
|
|
25
|
+
| `cross-harness-compatibility-standards` | procedural | engineering standard for building a capability that genuinely works across Claude/Codex/Pi/Hermes (ADR-0091) |
|
|
24
26
|
|
|
25
27
|
## Agents (8, thin shells)
|
|
26
28
|
|
package/__init__.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""adia-ui-kit-forge — generated by scripts/build/harness-manifests.mjs. Do not edit."""
|
|
2
2
|
import os
|
|
3
|
+
from pathlib import Path
|
|
3
4
|
|
|
4
5
|
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
5
6
|
|
|
@@ -7,46 +8,56 @@ _HERE = os.path.dirname(os.path.abspath(__file__))
|
|
|
7
8
|
def register(ctx):
|
|
8
9
|
ctx.register_skill(
|
|
9
10
|
name="a2ui-maintenance",
|
|
10
|
-
path=os.path.join(_HERE, "skills", "a2ui-maintenance", "SKILL.md"),
|
|
11
|
+
path=Path(os.path.join(_HERE, "skills", "a2ui-maintenance", "SKILL.md")),
|
|
11
12
|
description="Maintains the A2UI pipeline (packages/gen-ui/a2ui/): the chunk corpus, compose strategies (zettel, chunk-zettel, free-form, monolithic), retrieval, validator, calibration, evals, the a2ui MCP server. Use to author/harvest/fix chunks, tune STRONG_MATCH or zettel thresholds, validate an A2UI document, diagnose an eval gap/regression or lift a semantic fail, change MCP tools (generate_ui, compose_from_chunks, check_anti_patterns, refine_composition), scan anti-patterns, run pipeline ops, or when a contract can't express a shape. NOT for app screens (screen-composition), runtime gen-UI features (gen-ui-wiring), primitive authoring (primitive-authoring), or gallery scoring (gen-ui-review).",
|
|
12
13
|
)
|
|
14
|
+
ctx.register_skill(
|
|
15
|
+
name="cross-harness-authoring-standards",
|
|
16
|
+
path=Path(os.path.join(_HERE, "skills", "cross-harness-authoring-standards", "SKILL.md")),
|
|
17
|
+
description="Authors copy-paste install instructions for an adia-ui plugin (or any Claude Code plugin) across multiple agent harnesses — Claude Code, Codex, Pi, Hermes. Use when asked to \"write a Slack post for installing our plugins\", \"how do devs on Codex/Pi/Hermes install this\", \"update the multi-harness install docs\", or after a harness-distribution change (a new .codex-plugin/, plugin.yaml, or package.json \"pi\" field) needs its install instructions to catch up. NOT for the manifest/build mechanics themselves (package-release's Codex-manifest step, gh#1888/gh#1954) — this skill only writes the consumer-facing instructions once those exist.",
|
|
18
|
+
)
|
|
19
|
+
ctx.register_skill(
|
|
20
|
+
name="cross-harness-compatibility-standards",
|
|
21
|
+
path=Path(os.path.join(_HERE, "skills", "cross-harness-compatibility-standards", "SKILL.md")),
|
|
22
|
+
description="The engineering/architecture standard for actually BUILDING a Claude agent-family (or any multi-runtime capability) so it genuinely works across Claude Code, Codex, Pi, and Hermes — not the consumer-facing install docs once it already works (that's cross-harness-authoring-standards). Use when deciding whether a capability earns a new user-facing skill or should stay a thin runtime-specific agent/seat/extension, before writing a Pi extension or Hermes command and unsure whether the target API actually exists, defining a structured handoff record two or more runtimes must exchange, or naming a new plugin distribution/marketplace channel alongside an existing one. NOT for writing the resulting per-harness install commands (cross-harness-authoring-standards owns that, once the capability already works).",
|
|
23
|
+
)
|
|
13
24
|
ctx.register_skill(
|
|
14
25
|
name="demo-audit",
|
|
15
|
-
path=os.path.join(_HERE, "skills", "demo-audit", "SKILL.md"),
|
|
26
|
+
path=Path(os.path.join(_HERE, "skills", "demo-audit", "SKILL.md")),
|
|
16
27
|
description="Seven-mode QA sweep of the monorepo's demo/app surfaces: visual probe, app-shell QA, attr-quote typos, native-primitive leak, admin-shell composition, card anatomy, plus an aggregated token/contrast/lifecycle drift battery (`npm run dogfood:status`). Use for \"run a dogfood sweep\", \"find broken demos\", \"audit native primitive leaks\". NOT for gen-UI gallery scoring (gen-ui-review) or authoring primitives (primitive-authoring).",
|
|
17
28
|
)
|
|
18
29
|
ctx.register_skill(
|
|
19
30
|
name="gen-ui-review",
|
|
20
|
-
path=os.path.join(_HERE, "skills", "gen-ui-review", "SKILL.md"),
|
|
31
|
+
path=Path(os.path.join(_HERE, "skills", "gen-ui-review", "SKILL.md")),
|
|
21
32
|
description="Closed-loop Gen UI gallery quality review — per cycle: derive ideal specs, decompose the rendered canvas, score the gap, root-cause, emit fix plans until every prompt clears the exit gate. Use for /gen-review, \"score the gallery\", \"review gen-ui outputs\". NOT for corpus fixes (a2ui-maintenance), primitive authoring (primitive-authoring), or a hand-authored demo/app-shell QA sweep (demo-audit).",
|
|
22
33
|
)
|
|
23
34
|
ctx.register_skill(
|
|
24
35
|
name="llm-client-maintenance",
|
|
25
|
-
path=os.path.join(_HERE, "skills", "llm-client-maintenance", "SKILL.md"),
|
|
36
|
+
path=Path(os.path.join(_HERE, "skills", "llm-client-maintenance", "SKILL.md")),
|
|
26
37
|
description="Maintains @adia-ai/llm (packages/llm/core/): provider adapters (anthropic/openai/ gemini), the shared SSE parser, model registry, chat()/streamChat() facade, createAdapter() bridge. Use when adding or fixing a provider adapter, debugging streaming bugs (StreamChunk, no terminal `done` chunk), raw `stopReason`/usage mapping, buildRequest() or passthrough proxy dispatch (browser 401s, API key in browser), detectProvider/MODELS registry changes, or the stub adapter. NOT for wiring the client into an app (llm-wiring, adia-ui-factory plugin).",
|
|
27
38
|
)
|
|
28
39
|
ctx.register_skill(
|
|
29
40
|
name="package-release",
|
|
30
|
-
path=os.path.join(_HERE, "skills", "package-release", "SKILL.md"),
|
|
41
|
+
path=Path(os.path.join(_HERE, "skills", "package-release", "SKILL.md")),
|
|
31
42
|
description="Release engineering for the @adia-ai lockstep monorepo. Use to cut and ship a release, promote [Unreleased] CHANGELOG entries, tag and push lockstep packages to origin, publish a 10-package release (roster: scripts/package-paths.mjs), publish a single plugin independently of the lockstep set (Class B), batch-push piled-up release commits, recover a cut that landed wrong or whose publish workflows didn't fire, fix a check:lockstep bump failure or an F-N1 CHANGELOG warning, verify release gates without cutting anything, or author release notes/a MIGRATION GUIDE section. NOT for consumer-app migration sweeps (app-migration) or exe.dev VM ops (site-deployment).",
|
|
32
43
|
)
|
|
33
44
|
ctx.register_skill(
|
|
34
45
|
name="primitive-authoring",
|
|
35
|
-
path=os.path.join(_HERE, "skills", "primitive-authoring", "SKILL.md"),
|
|
46
|
+
path=Path(os.path.join(_HERE, "skills", "primitive-authoring", "SKILL.md")),
|
|
36
47
|
description="Author or modify AdiaUI framework source inside the monorepo — primitives (packages/web-components), shells/composites (packages/web-modules), yaml SoTs, demos. Use to add a new primitive, fix a prop/slot/attribute/CSS variant, update a yaml, build or fix a shell (chat-shell, admin-shell, editor-shell — sidebar/pane/bespoke-tier composition), promote repeated inline content into a shared module, audit a component's four-axis contract/token usage/lifecycle for drift, or author a demo or examples.html. NOT for app screens (screen-composition), A2UI internals (a2ui-maintenance), @adia-ai/llm internals (llm-client-maintenance), or site/pages docs (site-docs-authoring).",
|
|
37
48
|
)
|
|
38
49
|
ctx.register_skill(
|
|
39
50
|
name="site-deployment",
|
|
40
|
-
path=os.path.join(_HERE, "skills", "site-deployment", "SKILL.md"),
|
|
51
|
+
path=Path(os.path.join(_HERE, "skills", "site-deployment", "SKILL.md")),
|
|
41
52
|
description="Deploys and operates the AdiaUI site + services on exe.dev VMs — pushing a `site-v*` tag through the hardened rsync --delete deploy to ui-kit.exe.xyz (dry-run delete summary reviewed before the real deploy job runs), diagnosis (\"Port 8000 unbound\", a 502, a stale/404ing build behind npm after the last lockstep cut), rolling back a broken deploy, VM provisioning, secret rotation. Use for \"deploy to exe.dev\", \"push a site-v* tag\", \"the site is 502ing / looks stale\", \"roll back the last deploy\", \"restart/diagnose the exe service\", \"rotate keys on the VM\". NOT for cutting the release itself (package-release).",
|
|
42
53
|
)
|
|
43
54
|
ctx.register_skill(
|
|
44
55
|
name="site-docs-authoring",
|
|
45
|
-
path=os.path.join(_HERE, "skills", "site-docs-authoring", "SKILL.md"),
|
|
56
|
+
path=Path(os.path.join(_HERE, "skills", "site-docs-authoring", "SKILL.md")),
|
|
46
57
|
description="Review or author pages under site/pages/{architecture,getting-started, guides,patterns,reference}/ — the docs site (count the pages on disk; it grows). Use when asked to add or edit a getting-started/architecture/guides/patterns/reference page, review a site docs page for consistency, fix a callout that reads as plain text, or explain why an inline-code chip or a demo gallery looks broken. NOT for a component's own .examples.html demo (primitive-authoring) or any pure-primitive-composition training-harvest page (governed by composition-and-examples.md's no-style-block rule; owner: a2ui-maintenance).",
|
|
47
58
|
)
|
|
48
59
|
ctx.register_skill(
|
|
49
60
|
name="ssr-compatibility",
|
|
50
|
-
path=os.path.join(_HERE, "skills", "ssr-compatibility", "SKILL.md"),
|
|
61
|
+
path=Path(os.path.join(_HERE, "skills", "ssr-compatibility", "SKILL.md")),
|
|
51
62
|
description="Answers why an AdiaUI component crashes, drops content, renders wrong, or mutates a byte-identical subtree under SSR (linkedom/Astro) — the known failure shapes, what's fixed vs open, how to prove a fix under the linkedom shim gate. Use for \"does this work under SSR\", why a component crashes on attachInternals/ResizeObserver/adoptedStyleSheets/matchMedia/ `instanceof Node` under a DOM shim, why table-ui/chart-ui/select-ui or a container CE renders empty or drops nested children server-rendered, whether getBoundingClientRect() is safe in connectedCallback, whether a custom render()/connected() path adopts-in-place or rebuilds a server-rendered subtree that already matches (zero-subtree-mutation / AC-004a-shaped asks), whether a querySelector-guard-before-innerHTML component is SSR-safe, or whether a shim can be deleted after a fix ships. ANSWERS only. NOT for a fix (primitive-authoring) or host/hydration wiring (host-wiring, adia-ui-factory).",
|
|
52
63
|
)
|
|
@@ -12,10 +12,12 @@ description: |
|
|
|
12
12
|
tools: Read, Grep, Glob, Edit, Write, Bash
|
|
13
13
|
skills:
|
|
14
14
|
- a2ui-maintenance
|
|
15
|
-
# Explicit pin (gh#618): never `inherit` — a
|
|
16
|
-
# silently run this seat's pipeline-tuning and eval-diagnosis work on
|
|
17
|
-
#
|
|
18
|
-
|
|
15
|
+
# Explicit pin (gh#618): never `inherit` — a caller on a cheaper tier would
|
|
16
|
+
# silently run this seat's pipeline-tuning and eval-diagnosis work on its
|
|
17
|
+
# own tier instead. Operator's explicit standing instruction for this seat
|
|
18
|
+
# family: sonnet + xhigh.
|
|
19
|
+
model: sonnet
|
|
20
|
+
effort: xhigh
|
|
19
21
|
---
|
|
20
22
|
|
|
21
23
|
The a2ui-maintenance-agent works the pipeline to the preloaded `a2ui-maintenance` procedure:
|
|
@@ -15,11 +15,11 @@ skills:
|
|
|
15
15
|
- break-down-problem
|
|
16
16
|
- doc-writing-rules
|
|
17
17
|
- a2ui-maintenance
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
model:
|
|
22
|
-
effort:
|
|
18
|
+
# Explicit pin: never `inherit` — a catalog-cohesion decision sets the
|
|
19
|
+
# ceiling on what a2ui-maintenance-agent builds against it. Operator's
|
|
20
|
+
# explicit standing instruction for this seat family: sonnet + xhigh.
|
|
21
|
+
model: sonnet
|
|
22
|
+
effort: xhigh
|
|
23
23
|
---
|
|
24
24
|
|
|
25
25
|
The a2ui-planner-agent decomposes one named A2UI catalog-system change via the preloaded
|
|
@@ -16,10 +16,10 @@ tools: Read, Grep, Glob, Bash
|
|
|
16
16
|
skills:
|
|
17
17
|
- demo-audit
|
|
18
18
|
# Explicit pin (gh#618, tier corrected gh#1045): a review/critic seat's
|
|
19
|
-
# verdict must not depend on the caller's model tier — never `inherit
|
|
20
|
-
#
|
|
21
|
-
model:
|
|
22
|
-
effort:
|
|
19
|
+
# verdict must not depend on the caller's model tier — never `inherit`.
|
|
20
|
+
# Operator's explicit standing instruction for this seat family: sonnet + xhigh.
|
|
21
|
+
model: sonnet
|
|
22
|
+
effort: xhigh
|
|
23
23
|
---
|
|
24
24
|
|
|
25
25
|
The demo-audit-agent runs the preloaded dogfood sweep procedure and returns
|
|
@@ -13,11 +13,11 @@ description: |
|
|
|
13
13
|
tools: Read, Grep, Glob, Bash
|
|
14
14
|
skills:
|
|
15
15
|
- primitive-authoring
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
model:
|
|
20
|
-
effort:
|
|
16
|
+
# Explicit pin (gh#618) — never `inherit`, same reasoning as demo-audit-agent's
|
|
17
|
+
# own explicit pin: a critic's verdict must not depend on the caller's tier.
|
|
18
|
+
# Operator's explicit standing instruction for this seat family: sonnet + xhigh.
|
|
19
|
+
model: sonnet
|
|
20
|
+
effort: xhigh
|
|
21
21
|
---
|
|
22
22
|
|
|
23
23
|
The framework-checker sweeps the named scope (the whole primitive corpus, or a
|
|
@@ -14,12 +14,13 @@ tools: Read, Grep, Glob, Write, Edit
|
|
|
14
14
|
skills:
|
|
15
15
|
- break-down-problem
|
|
16
16
|
- doc-writing-rules
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
# Explicit pin: never `inherit` — a decomposition seat's output sets the
|
|
18
|
+
# ceiling on everything primitive-authoring-agent builds downstream from it;
|
|
19
|
+
# a cheap tier would make load-bearing architectural calls with no
|
|
20
|
+
# independent check. Operator's explicit standing instruction for this seat
|
|
21
|
+
# family: sonnet + xhigh.
|
|
22
|
+
model: sonnet
|
|
23
|
+
effort: xhigh
|
|
23
24
|
---
|
|
24
25
|
|
|
25
26
|
The framework-planner-agent decomposes one named framework change via the preloaded
|
|
@@ -17,10 +17,10 @@ tools: Read, Grep, Glob, Bash
|
|
|
17
17
|
skills:
|
|
18
18
|
- gen-ui-review
|
|
19
19
|
# Explicit pin (gh#618, tier corrected gh#1045): a review/critic seat's
|
|
20
|
-
# verdict must not depend on the caller's model tier — never `inherit
|
|
21
|
-
#
|
|
22
|
-
model:
|
|
23
|
-
effort:
|
|
20
|
+
# verdict must not depend on the caller's model tier — never `inherit`.
|
|
21
|
+
# Operator's explicit standing instruction for this seat family: sonnet + xhigh.
|
|
22
|
+
model: sonnet
|
|
23
|
+
effort: xhigh
|
|
24
24
|
---
|
|
25
25
|
|
|
26
26
|
The gen-ui-review-agent runs the preloaded gen-ui-review cycle and returns
|
|
@@ -14,8 +14,10 @@ skills:
|
|
|
14
14
|
- package-release
|
|
15
15
|
# Explicit pin (gh#618): never `inherit` — this seat runs UNATTENDED cuts,
|
|
16
16
|
# where a cheap caller tier would make live gate-failure judgment calls
|
|
17
|
-
# with no operator watching.
|
|
18
|
-
|
|
17
|
+
# with no operator watching. Operator's explicit standing instruction for
|
|
18
|
+
# this seat family: sonnet + xhigh.
|
|
19
|
+
model: sonnet
|
|
20
|
+
effort: xhigh
|
|
19
21
|
---
|
|
20
22
|
|
|
21
23
|
The package-release-agent runs the preloaded `package-release` procedure under its
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adia-ai/adia-ui-forge",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.52",
|
|
4
4
|
"description": "Maintain the adia-ui (@adia-ai) framework itself — author primitives and shells, run the A2UI generation pipeline and its corpus, review gen-UI quality, sweep QA, cut releases, deploy. The maintainer counterpart to adia-factory (the consumer/app-author plugin).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"adia-ui",
|
package/plugin.yaml
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
name: "adia-ui-kit-forge"
|
|
2
|
-
version: "0.8.
|
|
2
|
+
version: "0.8.52"
|
|
3
3
|
description: "Maintain the adia-ui (@adia-ai) framework itself — author primitives and shells, run the A2UI generation pipeline and its corpus, review gen-UI quality, sweep QA, cut releases, deploy. The maintainer counterpart to adia-ui-kit-factory (the consumer/app-author plugin)."
|
|
4
4
|
manifest_version: 1
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cross-harness-authoring-standards
|
|
3
|
+
description: >-
|
|
4
|
+
Authors copy-paste install instructions for an adia-ui plugin (or any
|
|
5
|
+
Claude Code plugin) across multiple agent harnesses — Claude Code, Codex,
|
|
6
|
+
Pi, Hermes. Use when asked to "write a Slack post for installing our
|
|
7
|
+
plugins", "how do devs on Codex/Pi/Hermes install this", "update the
|
|
8
|
+
multi-harness install docs", or after a harness-distribution change
|
|
9
|
+
(a new .codex-plugin/, plugin.yaml, or package.json "pi" field) needs its
|
|
10
|
+
install instructions to catch up. NOT for the manifest/build mechanics
|
|
11
|
+
themselves (package-release's Codex-manifest step, gh#1888/gh#1954) — this
|
|
12
|
+
skill only writes the consumer-facing instructions once those exist.
|
|
13
|
+
disable-model-invocation: false
|
|
14
|
+
user-invocable: true
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# cross-harness-authoring-standards — one command per harness, zero invented syntax
|
|
18
|
+
|
|
19
|
+
Every harness's install path is a claim someone will paste verbatim into a terminal. A
|
|
20
|
+
plausible-looking command that was never verified against this repo's actual shipped
|
|
21
|
+
manifests is worse than no instructions — it fails silently or errors somewhere the
|
|
22
|
+
author never sees. The whole discipline is: **read the real generated file before
|
|
23
|
+
describing what it does.**
|
|
24
|
+
|
|
25
|
+
## The verification-before-writing gate `[gate]`
|
|
26
|
+
|
|
27
|
+
Before writing one line of install instructions for a harness, verify against the strongest
|
|
28
|
+
source actually reachable — never infer a harness's install surface from another harness's
|
|
29
|
+
shape, and never invent a CLI subcommand you have not seen documented or confirmed:
|
|
30
|
+
|
|
31
|
+
- **Strongest: the target harness's own agent, live.** If a Codex/Pi/Hermes agent is
|
|
32
|
+
reachable (a peer session, a cross-harness message), have it check or correct the draft
|
|
33
|
+
against its own real CLI/config surface directly — it knows its own tool's commands with
|
|
34
|
+
more authority than any manifest file a human read once and wrote down. A harness section
|
|
35
|
+
corrected this way outranks a manifest-file read on the same point; update the draft to
|
|
36
|
+
match, don't average the two.
|
|
37
|
+
- **Fallback: the repo's own shipped manifest for that harness** (below), when no live
|
|
38
|
+
agent is reachable.
|
|
39
|
+
|
|
40
|
+
| Harness | What to read before writing anything |
|
|
41
|
+
| --- | --- |
|
|
42
|
+
| Claude Code | The plugin's own `README.md` "Install" section + `.claude-plugin/marketplace.json` (or the published npm marketplace manifest) |
|
|
43
|
+
| Codex | `.codex-plugin/plugin.json` + `codex.interface.json` + the repo's own `AGENTS.md` "Non-Claude harnesses" section (gh#1888 — the canonical degradation-table citation) |
|
|
44
|
+
| Hermes | `plugin.yaml` + `__init__.py` at the package root, and whether an `hermes-mcp.yaml` (or equivalent) fragment exists — Hermes MCP is user-level config, not a plugin field, in every verified case so far |
|
|
45
|
+
| Pi | `package.json`'s `"pi"` field + `pi-package` keyword, and `prompts/*.md` if present |
|
|
46
|
+
|
|
47
|
+
If a harness's manifest file doesn't exist yet in the repo, that harness has **no verified
|
|
48
|
+
install path** — say so plainly (`cross-harness-authoring-standards` never fabricates a
|
|
49
|
+
"probably works like X" command) and route the gap to the maintainer skill/issue that owns
|
|
50
|
+
harness-manifest generation, rather than guessing syntax.
|
|
51
|
+
|
|
52
|
+
## The four-axis degradation table
|
|
53
|
+
|
|
54
|
+
Every harness other than Claude Code lacks at least one of: commands, agent seats, hooks,
|
|
55
|
+
native MCP. State the gap plainly per harness, in the instructions themselves — a dev who
|
|
56
|
+
installs expecting the full surface and silently gets less is worse served than one told
|
|
57
|
+
up front:
|
|
58
|
+
|
|
59
|
+
- **Commands** → Codex/Pi/Hermes have no slash-command layer in the verified cases; a
|
|
60
|
+
command degrades to invoking its underlying skill directly (Codex, Hermes) or, where the
|
|
61
|
+
harness's own prompt-template layer supports genuine commands (Pi's `prompts/*.md`),
|
|
62
|
+
upgrades instead of degrading — check per harness, don't assume degradation is universal.
|
|
63
|
+
- **Agent seats** → do not exist outside Claude Code. Never write "then invoke
|
|
64
|
+
`<agent-name>`" for Codex/Pi/Hermes; give the entry-point *skill* that agent dispatches
|
|
65
|
+
internally instead (see "The common starting prompt" below).
|
|
66
|
+
- **Hooks** → Codex/Pi/Hermes have no hook runtime in the verified cases; state "no
|
|
67
|
+
hooks surface" rather than omitting the topic (an omission reads as "works the same").
|
|
68
|
+
- **MCP** → the highest-risk gap. Verify per harness: does it read `.mcp.json` unmodified
|
|
69
|
+
(Codex — verified), read it via a third-party adapter the dev must separately install
|
|
70
|
+
(Pi's `pi-mcp-adapter` — verified 2026-08-24), or require hand-merging a generated
|
|
71
|
+
fragment into the harness's own user-level config (Hermes — verified, no plugin-level MCP
|
|
72
|
+
field exists)? If the plugin's whole value proposition is its MCP server, say that
|
|
73
|
+
explicitly before promising support — a silent no-op MCP install is the worst failure
|
|
74
|
+
mode this skill exists to prevent.
|
|
75
|
+
|
|
76
|
+
## The common starting prompt
|
|
77
|
+
|
|
78
|
+
A "getting started" prompt that opens with an agent name breaks on every harness but
|
|
79
|
+
Claude Code. Give two forms:
|
|
80
|
+
|
|
81
|
+
- **Claude Code**: name the coordinating agent directly (`Use <agent-name> to build
|
|
82
|
+
[...]`).
|
|
83
|
+
- **Everywhere else**: name the entry-point *skill* that agent dispatches internally —
|
|
84
|
+
same routing, no agent-seat dependency. Confirm the skill is genuinely the entry point
|
|
85
|
+
(its own description says "run FIRST" / "cold-start router") rather than picking an
|
|
86
|
+
arbitrary skill from the roster.
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
Bad (breaks on Codex/Pi/Hermes — no agent seat exists):
|
|
90
|
+
Use ui-architect to build a settings page.
|
|
91
|
+
|
|
92
|
+
Good (Claude Code):
|
|
93
|
+
Use ui-architect to build a settings page. Check pattern-catalog for an
|
|
94
|
+
existing pattern before composing from scratch.
|
|
95
|
+
|
|
96
|
+
Good (Codex/Pi/Hermes — same routing, no agent dependency):
|
|
97
|
+
Use the app-planning skill to orient a new settings page. Check
|
|
98
|
+
pattern-catalog for an existing pattern before composing from scratch,
|
|
99
|
+
then use screen-composition to build it.
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Structure
|
|
103
|
+
|
|
104
|
+
One block per harness, each self-contained (a reader only cares about their own harness):
|
|
105
|
+
install command(s) → any manual wiring step (config file edit, separate adapter install) →
|
|
106
|
+
the degradation summary (what's full-strength, what's reduced, what's absent) for that
|
|
107
|
+
harness specifically. Never a single combined command block trying to cover two harnesses —
|
|
108
|
+
the reader copy-pastes the whole block for their tool and nothing else.
|
|
109
|
+
|
|
110
|
+
## Failure branches
|
|
111
|
+
|
|
112
|
+
A harness with no verified manifest in the repo → report the gap, do not draft syntax for
|
|
113
|
+
it. A degradation claim that contradicts what a just-read manifest file actually shows
|
|
114
|
+
(e.g. writing "no MCP" for a harness whose `.mcp.json` passthrough is confirmed working) →
|
|
115
|
+
re-read the source file; the manifest is authoritative over any prior draft, including this
|
|
116
|
+
skill's own past output for an earlier plugin version.
|
|
117
|
+
|
|
118
|
+
Done when every harness section cites the specific file it was verified against (in prose,
|
|
119
|
+
not a footnote) and no command exists in the output that wasn't read from a real shipped
|
|
120
|
+
artifact.
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cross-harness-compatibility-standards
|
|
3
|
+
description: >-
|
|
4
|
+
The engineering/architecture standard for actually BUILDING a Claude
|
|
5
|
+
agent-family (or any multi-runtime capability) so it genuinely works
|
|
6
|
+
across Claude Code, Codex, Pi, and Hermes — not the consumer-facing
|
|
7
|
+
install docs once it already works (that's
|
|
8
|
+
cross-harness-authoring-standards). Use when deciding whether a
|
|
9
|
+
capability earns a new user-facing skill or should stay a thin
|
|
10
|
+
runtime-specific agent/seat/extension, before writing a Pi extension or
|
|
11
|
+
Hermes command and unsure whether the target API actually exists,
|
|
12
|
+
defining a structured handoff record two or more runtimes must exchange,
|
|
13
|
+
or naming a new plugin distribution/marketplace channel alongside an
|
|
14
|
+
existing one. NOT for writing the resulting per-harness install commands
|
|
15
|
+
(cross-harness-authoring-standards owns that, once the capability
|
|
16
|
+
already works).
|
|
17
|
+
disable-model-invocation: false
|
|
18
|
+
user-invocable: true
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# cross-harness-compatibility-standards — build it portable, don't just document it
|
|
22
|
+
|
|
23
|
+
`cross-harness-authoring-standards` covers the downstream concern: writing the
|
|
24
|
+
consumer-facing install instructions once a capability already works across
|
|
25
|
+
harnesses. This skill covers the upstream concern — the engineering standard
|
|
26
|
+
for actually **building** that capability so it works on Claude Code, Codex,
|
|
27
|
+
Pi, and Hermes in the first place. If you're drafting a "how to install"
|
|
28
|
+
block, you want the other skill; if you're deciding whether something needs
|
|
29
|
+
a new skill, writing a Pi/Hermes adapter, or designing a structured record
|
|
30
|
+
two runtimes must exchange, you're in the right place.
|
|
31
|
+
|
|
32
|
+
This standard is not invented doctrine — it is extracted from
|
|
33
|
+
[ADR-0091](../../../../../docs/ops/adr/adr-0091-ui-architect-cross-harness-portability.md)
|
|
34
|
+
and its companion
|
|
35
|
+
[PRD](../../../../../docs/ops/prd/prd-ui-architect-cross-harness-portability.md)/
|
|
36
|
+
[SPEC](../../../../../docs/ops/spec/spec-ui-architect-cross-harness-portability.md)/
|
|
37
|
+
[LLD](../../../../../docs/ops/lld/lld-ui-architect-cross-harness-portability.md),
|
|
38
|
+
ratified and built across four merged PRs (#1997 docs, #1998 skill + contracts
|
|
39
|
+
+ Codex marketplace, #1999 agent pinning, #2000 Pi extension + Hermes
|
|
40
|
+
command) that made `ui-architect` portable. Every rule below cites the
|
|
41
|
+
decision or requirement it comes from — re-read the source before extending
|
|
42
|
+
this standard, don't paraphrase from this skill alone.
|
|
43
|
+
|
|
44
|
+
## Rule 1 — skill vs. thin adapter: earn the promotion `[gate]`
|
|
45
|
+
|
|
46
|
+
Substantive reusable behavior belongs in a portable skill body; a
|
|
47
|
+
runtime-specific agent/seat/extension stays **only if** it provides one of:
|
|
48
|
+
restricted tools, isolated context, parallelism, multi-skill preload, or a
|
|
49
|
+
distinct model/effort configuration (ADR-0091 D1). Delete an agent, or fold
|
|
50
|
+
it into its skill, if it provides none of those structural properties. Never
|
|
51
|
+
convert every internal seat into a user-facing skill merely because
|
|
52
|
+
portability is required — a capability is user-invocable only when a user
|
|
53
|
+
has a sensible reason to request it directly, not because promoting it was
|
|
54
|
+
convenient.
|
|
55
|
+
|
|
56
|
+
Apply the rule by checking each existing seat against it, on the record, not
|
|
57
|
+
by assertion:
|
|
58
|
+
|
|
59
|
+
- `ui-architect` was the one seat of four in `adia-ui-factory` missing its
|
|
60
|
+
skill twin — it earned `skills/ui-architect/SKILL.md` because the
|
|
61
|
+
Gear-selection/wave/generator-≠-reviewer/four-lap-cap/failure-routing
|
|
62
|
+
procedure is exactly the "substantive reusable behavior" class the rule
|
|
63
|
+
routes to a skill, and Codex/Pi/Hermes users are the concrete "sensible
|
|
64
|
+
reason to request it directly." The agent seat was **retained, not
|
|
65
|
+
replaced**: no Write/Edit (restricted tools), dispatches into isolated
|
|
66
|
+
per-screen contexts (isolated context + parallelism), pinned `fable`/
|
|
67
|
+
`high` never-`inherit` (distinct model/effort) — independently sufficient
|
|
68
|
+
to keep it.
|
|
69
|
+
- `app-planning-agent`, `screen-composition-agent`, `surface-qa-agent`
|
|
70
|
+
already each preloaded a same-named user-invocable skill before this
|
|
71
|
+
change touched anything — the rule was already satisfied; nothing about
|
|
72
|
+
their shape changed beyond the same Claude-adapter slimming (Rule 3)
|
|
73
|
+
applied to all four seats equally.
|
|
74
|
+
- `wave-coordination` stayed `user-invocable: false`, Claude-only,
|
|
75
|
+
unpromoted — team-pattern *mechanics* for one runtime's dispatch model,
|
|
76
|
+
not a capability a Codex/Pi/Hermes user has a sensible reason to request
|
|
77
|
+
directly. Failing the rule's own admission test on purpose is a legitimate
|
|
78
|
+
outcome, not an oversight to fix later.
|
|
79
|
+
|
|
80
|
+
## Rule 2 — verify before coding, per harness, every time `[gate]`
|
|
81
|
+
|
|
82
|
+
Never assume a target harness's API, extension mechanism, or delegation
|
|
83
|
+
surface from documentation alone. Check the actually-installed version
|
|
84
|
+
live — read its real source, run its real CLI — before writing one line of
|
|
85
|
+
adapter code against it. This is the single most load-bearing lesson from
|
|
86
|
+
how `ui-architect`'s Pi and Hermes adapters were actually built (PR #2000):
|
|
87
|
+
|
|
88
|
+
- **Pi** was verified present by reading the installed
|
|
89
|
+
`@earendil-works/pi-coding-agent` package directly: its own shipped
|
|
90
|
+
`examples/extensions/subagent/` proved a real subprocess-based
|
|
91
|
+
subagent-dispatch pattern (`registerCommand` + `child_process` spawn of a
|
|
92
|
+
nested `pi --mode json -p --no-session`), and `package.json`'s
|
|
93
|
+
`pi.extensions` field was confirmed real and documented — not inferred by
|
|
94
|
+
analogy to Claude's Agent tool. Once verified, the extension was
|
|
95
|
+
**live-tested end to end**: a real `pi` session ran `/ui-architect`
|
|
96
|
+
against a live model, dispatched all three isolated passes, wrote a real
|
|
97
|
+
file, took a real screenshot, and returned a clean `VerifyProof`.
|
|
98
|
+
- **Hermes** was verified sufficient by reading the installed Hermes
|
|
99
|
+
Agent's own source — `PluginContext.register_command` + `dispatch_tool`,
|
|
100
|
+
and `tools/delegate_tool.py`'s `delegate_task(goal, context, role, ...)` —
|
|
101
|
+
confirmed as real, current APIs before the generator surgery (Rule 3)
|
|
102
|
+
was written against them. The generated command was built behind its own
|
|
103
|
+
`--selftest` fixture (a real `py_compile` check plus a fake `delegate_task`
|
|
104
|
+
double driving the actual generated logic against the real linters)
|
|
105
|
+
*before* wiring it into the real plugin tree, then verified live via
|
|
106
|
+
`hermes plugins doctor --ci`. A full interactive dispatch was blocked by
|
|
107
|
+
the build sandbox refusing to mutate Hermes's global plugin-enable
|
|
108
|
+
state — that limitation was **noted honestly in the README rather than
|
|
109
|
+
worked around or silently dropped**.
|
|
110
|
+
|
|
111
|
+
If a required capability is genuinely absent, or materially different from
|
|
112
|
+
what the docs claimed (SPEC REQ-011/012's capability-boundary discipline):
|
|
113
|
+
stop that adapter at an explicit, clearly-labeled capability boundary and
|
|
114
|
+
record four things — the API checked, the observed mismatch, the smallest
|
|
115
|
+
viable fallback, and the remaining work. Never simulate success with an
|
|
116
|
+
unverified manifest field or a dead adapter file that looks like it works
|
|
117
|
+
but was never actually exercised. One of the two outcomes (working adapter
|
|
118
|
+
+ passing native smoke test, or a capability-boundary record with all four
|
|
119
|
+
fields) must exist — never neither, never both.
|
|
120
|
+
|
|
121
|
+
## Rule 3 — one authored copy, many thin consumers
|
|
122
|
+
|
|
123
|
+
The portable behavior lives in exactly **one** skill body. Every runtime's
|
|
124
|
+
own adapter mechanism *consumes* that one body — it never gets a second,
|
|
125
|
+
hand-copied prose duplicate:
|
|
126
|
+
|
|
127
|
+
- **Claude** preloads it (`skills:` frontmatter on the owning agent file).
|
|
128
|
+
- **Codex** auto-derives its manifest sibling
|
|
129
|
+
(`skills/<name>/agents/openai.yaml`) from the same skill frontmatter via
|
|
130
|
+
this repo's own generator (`scripts/build/codex-manifests.mjs`) — no
|
|
131
|
+
manual authoring, ever.
|
|
132
|
+
- **Pi**'s extension reads the skill file *at runtime* rather than copying
|
|
133
|
+
its prose into the extension source (`ui-architect`'s
|
|
134
|
+
`pi/ui-architect.extension.ts` loads agent descriptions from the
|
|
135
|
+
plugin-local skill directly).
|
|
136
|
+
- **Hermes**'s command derives from the same source
|
|
137
|
+
(`scripts/build/harness-manifests.mjs`'s generated `__init__.py`
|
|
138
|
+
registers the skill; the generated `/ui-architect` command runs the same
|
|
139
|
+
planning/composition/QA loop through `delegate_task`, gated by the same
|
|
140
|
+
contracts).
|
|
141
|
+
|
|
142
|
+
A change to the workflow means editing one file, not four. If you find
|
|
143
|
+
yourself pasting a skill's prose into an agent file, an extension, or a
|
|
144
|
+
generator template instead of citing or reading it, stop — that's the
|
|
145
|
+
duplication this rule exists to prevent.
|
|
146
|
+
|
|
147
|
+
## Rule 4 — one contract grammar, not a schema per runtime
|
|
148
|
+
|
|
149
|
+
When two or more runtimes need to exchange a structured handoff record,
|
|
150
|
+
define it once as a plain strict-prose-record shape validated by a small
|
|
151
|
+
stdlib CLI linter — never introduce JSON Schema or another
|
|
152
|
+
schema-validation library into every runtime's own toolchain to do the same
|
|
153
|
+
job (ADR-0091 D3). This repo's own `record-lint` family is the reusable
|
|
154
|
+
grammar: `<script> <file>|-|selftest`, exit 0 clean / 1 on findings, with
|
|
155
|
+
`UNMEASURED — <reason>` as a legal per-slot value (silent omission is not
|
|
156
|
+
legal). `OrientationRecord` (`scripts/record-lint`), `VerifyProof`
|
|
157
|
+
(`scripts/verify-proof-lint`), and `BuildResult`
|
|
158
|
+
(`scripts/build-result-lint`) — all three in `adia-ui-factory`'s
|
|
159
|
+
`scripts/` — are structurally identical siblings, not three different
|
|
160
|
+
formats doing the same job. Every runtime adapter that produces or consumes
|
|
161
|
+
one of these contracts calls the same linter script; a second,
|
|
162
|
+
runtime-specific parsing or validation implementation for the same contract
|
|
163
|
+
is a defect, not a stylistic choice.
|
|
164
|
+
|
|
165
|
+
Before minting a new contract shape, check whether an existing one already
|
|
166
|
+
covers it under a different name — reuse beats a fourth near-duplicate
|
|
167
|
+
grammar.
|
|
168
|
+
|
|
169
|
+
## Rule 5 — name distribution channels distinctly
|
|
170
|
+
|
|
171
|
+
When the same plugin ships **both** an in-repo/access-gated channel and a
|
|
172
|
+
public/npm-sourced channel for the same runtime, name them distinctly. A
|
|
173
|
+
collision — both channels sharing one marketplace name — makes
|
|
174
|
+
`<plugin>@<marketplace-name>` resolution ambiguous for a consumer trying to
|
|
175
|
+
install from a specific channel.
|
|
176
|
+
|
|
177
|
+
The confirmed, working precedent is Claude's own two-channel split: the
|
|
178
|
+
repo-root, access-gated `.claude-plugin/marketplace.json` is named
|
|
179
|
+
`adia-ui-kit`; the public, npm-sourced
|
|
180
|
+
`packages/plugins/adia-plugins/marketplace.json` is named `adia-plugins`
|
|
181
|
+
(ADR-0091 D2's own table) — two distinct names, two distinct reachability
|
|
182
|
+
tiers, no ambiguity for a consumer running `/plugin install
|
|
183
|
+
<plugin>@<marketplace-name>`.
|
|
184
|
+
|
|
185
|
+
ADR-0091 D2 designed the Codex side to mirror that same split (a
|
|
186
|
+
repo-root/local-source channel alongside the existing
|
|
187
|
+
`packages/plugins/adia-plugins/.agents/plugins/marketplace.json` npm
|
|
188
|
+
sibling) — but the LLD (§C7) flagged the new manifest's `name` field as an
|
|
189
|
+
explicit, unverified, build-time question: does Codex key off `name` for
|
|
190
|
+
`<plugin>@<marketplace-name>` resolution the way Claude does? **Verify this
|
|
191
|
+
against the actually-shipped manifests before citing the Codex side as a
|
|
192
|
+
settled precedent** — Rule 2's verify-before-coding discipline applies to
|
|
193
|
+
this rule's own worked example, not only to Pi/Hermes adapters. Diff the
|
|
194
|
+
`name` field of both Codex marketplace manifests against each other before
|
|
195
|
+
assuming they follow the Claude-side pattern.
|
|
196
|
+
|
|
197
|
+
## Failure branches
|
|
198
|
+
|
|
199
|
+
A capability promoted to a user-facing skill with none of Rule 1's five
|
|
200
|
+
qualifying properties on its remaining agent seat → fold the seat into the
|
|
201
|
+
skill or delete it; don't keep a Write/Edit-free, non-isolated, no-model-pin
|
|
202
|
+
shell around "for consistency." An adapter written against a harness's
|
|
203
|
+
documented API with no live verification → stop, verify against the
|
|
204
|
+
installed runtime first (Rule 2); a passing test against a mocked or
|
|
205
|
+
assumed API is not verification. A second hand-copied prose block for a
|
|
206
|
+
skill's workflow anywhere outside its one `SKILL.md` → delete the copy,
|
|
207
|
+
point the consumer at the source (Rule 3). A new structured record
|
|
208
|
+
introduced as JSON Schema, or as a shape that already has a linter under a
|
|
209
|
+
different name → stop, extend `record-lint`'s family instead or reuse the
|
|
210
|
+
existing contract (Rule 4). A new distribution manifest reusing an existing
|
|
211
|
+
sibling's marketplace name → rename before shipping (Rule 5).
|
|
212
|
+
|
|
213
|
+
Done when: every new or promoted seat cites the specific Rule 1 property
|
|
214
|
+
that keeps it (or is deleted/folded); every harness adapter names the API
|
|
215
|
+
it verified live and how (Rule 2); the skill body has exactly one authored
|
|
216
|
+
copy with every consumer citing or reading it, never duplicating it (Rule
|
|
217
|
+
3); every cross-runtime contract has exactly one shape and one stdlib
|
|
218
|
+
linter (Rule 4); and every distribution channel's marketplace name is
|
|
219
|
+
diffed against its siblings and confirmed distinct (Rule 5).
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
interface:
|
|
2
|
+
display_name: "Cross Harness Compatibility Standards"
|
|
3
|
+
short_description: "The engineering/architecture standard for actually BUILDING a Claude agent-family (or any multi-runtime capability) so it genuinely works across Claude Code, Codex, Pi, and Hermes — not the consumer-facing install docs once it already works (that's cross-harness-authoring-standards)."
|
|
@@ -89,6 +89,8 @@ When only source *hashes* move and chunk content does not, `check:embeddings-fre
|
|
|
89
89
|
|
|
90
90
|
### 3.1 The full roster — every gate runs; a subset = pre-flight failure
|
|
91
91
|
|
|
92
|
+
**Execution model (gh#2006): three phases, not one serial walk.** `step3PreFlight()` runs gate 4 solo first (see its own note below), then gates 16 → 27 → 28 strictly in order (the eval-health write-then-read dependency — gate 28 reads whichever `evals/mcp/runs/` directory sorts lexically LAST, so nothing else may write there between 27 and 28), concurrently with a bounded pool running every other gate at once (`PREFLIGHT_CONCURRENCY`, default 4 — override for a dedicated/idle host). Every gate still resolves the same command, still fails the whole pre-flight on a red result, and still reports its own number — only the WALL-CLOCK schedule changed, never the roster below or its numbering. `--dry` previews stay the original flat serial walk unchanged.
|
|
93
|
+
|
|
92
94
|
```bash
|
|
93
95
|
node scripts/build/components.mjs --verify # 1 yaml ↔ sidecar ↔ .d.ts
|
|
94
96
|
npm run verify:traits # 2 trait coverage
|
|
@@ -60,7 +60,8 @@
|
|
|
60
60
|
// block BEFORE the prompt (H5) — never a bare y/N — whether it waits for input
|
|
61
61
|
// (manual flags) or auto-confirms (--go).
|
|
62
62
|
|
|
63
|
-
import { execSync } from 'node:child_process';
|
|
63
|
+
import { execSync, exec as execCb } from 'node:child_process';
|
|
64
|
+
import { promisify } from 'node:util';
|
|
64
65
|
import { createInterface } from 'node:readline/promises';
|
|
65
66
|
import { stdin, stdout } from 'node:process';
|
|
66
67
|
import fs from 'node:fs';
|
|
@@ -347,6 +348,15 @@ function shCapture(cmd, args) {
|
|
|
347
348
|
}
|
|
348
349
|
}
|
|
349
350
|
|
|
351
|
+
// Async, CAPTURING exec — the concurrent pre-flight phases (gh#2006) can't
|
|
352
|
+
// use sh()'s stdio:'inherit' (N processes' live output interleaved is
|
|
353
|
+
// unreadable), so every gate's stdout+stderr is captured and printed as one
|
|
354
|
+
// block the moment that gate finishes instead. maxBuffer generous (64 MB) —
|
|
355
|
+
// a truncated capture on a real failure (tsc, vitest) would read as a false
|
|
356
|
+
// pass/silent-gap the same class of defect shFailure's own doc-comment warns
|
|
357
|
+
// against.
|
|
358
|
+
const execAsync = promisify(execCb);
|
|
359
|
+
|
|
350
360
|
// ── Half-cut detection (gh#765) ──────────────────────────────────
|
|
351
361
|
// An aborted Step-4 run (the v0.8.29 cut aborted three times at Step 4e)
|
|
352
362
|
// leaves a half-cut tree: package versions bumped to the cut version +
|
|
@@ -428,53 +438,245 @@ function step1ReBaseline(args) {
|
|
|
428
438
|
console.log(' (empty — origin not ahead)');
|
|
429
439
|
}
|
|
430
440
|
|
|
431
|
-
// ── Step 3 — Pre-flight gates
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
441
|
+
// ── Step 3 — Pre-flight gates (gh#2006) ───────────────────────────
|
|
442
|
+
//
|
|
443
|
+
// gh#1987's audit found the real "feels slow" lever isn't roster size (all
|
|
444
|
+
// 32 gates are justified defense-in-depth — `main`'s branch protection only
|
|
445
|
+
// hard-requires the browser-scheme probe, so the `npm run check` name-
|
|
446
|
+
// duplicates in this roster are real re-checks, not dead weight) — it's
|
|
447
|
+
// that step3PreFlight ran every gate one at a time. Most of the 32 are
|
|
448
|
+
// independent, read-only checks with no shared mutable state; this splits
|
|
449
|
+
// them into three phases instead of one flat serial for-loop. Two real
|
|
450
|
+
// sequencing requirements survived the audit — everything else runs
|
|
451
|
+
// concurrently:
|
|
452
|
+
//
|
|
453
|
+
// Phase 1 (solo) — gate 4 (test:unit:serial) alone, no sibling gate
|
|
454
|
+
// process running at the same time. Not a proven requirement — the
|
|
455
|
+
// roster's own "serial on purpose" note is about vitest's OWN
|
|
456
|
+
// --no-file-parallelism, not about co-running with sibling PROCESSES —
|
|
457
|
+
// but a deliberately conservative one: gate 4 is the roster's single
|
|
458
|
+
// heaviest gate, and this repo's own host-audit F3 remedy
|
|
459
|
+
// (scripts/dev/worker-cap.mjs, gh#1792) exists precisely because
|
|
460
|
+
// unbounded concurrent lanes on a shared multi-session host turn a gate
|
|
461
|
+
// that's clean in isolation red under contention. Revisit with real
|
|
462
|
+
// measurement before loosening this.
|
|
463
|
+
// Phase 2 (concurrent) — two workstreams launched together and awaited
|
|
464
|
+
// together:
|
|
465
|
+
// (a) a bounded-concurrency pool of every remaining gate EXCEPT the
|
|
466
|
+
// eval-health chain below — each independently read-only
|
|
467
|
+
// (--verify/--fix-gated/dry-diff, checked directly against every
|
|
468
|
+
// script in the roster) or, for gate 17 (dogfood:status), writing
|
|
469
|
+
// only to a gate-private path (qa/findings/dogfood-tracker.md)
|
|
470
|
+
// no other gate reads or writes;
|
|
471
|
+
// (b) the eval-health chain — gates 16 → 27 → 28 — run strictly
|
|
472
|
+
// serially relative to EACH OTHER (still concurrent with (a),
|
|
473
|
+
// since neither touches (a)'s files). This is the roster's one
|
|
474
|
+
// CSS-before-JS-shaped file-write ordering dependency: gate 27
|
|
475
|
+
// (`eval:diff --engine free-form`) writes a fresh
|
|
476
|
+
// evals/mcp/runs/<ISO>/ directory; gate 28
|
|
477
|
+
// (write-eval-health.mjs) reads whichever directory under
|
|
478
|
+
// evals/mcp/runs/ SORTS LEXICALLY LAST — not specifically the
|
|
479
|
+
// latest free-form run — and expects free-form.json there. Gate
|
|
480
|
+
// 16 (`eval:diff --engine zettel`) writes its OWN fresh directory
|
|
481
|
+
// under that same root; if 16 and 27 ran concurrently and 16
|
|
482
|
+
// happened to finish after 27, gate 28 would silently pick 16's
|
|
483
|
+
// directory (no free-form.json) and commit
|
|
484
|
+
// evals/health/<version>.json with `engines.free-form: null` — a
|
|
485
|
+
// real, silent coverage loss the roster's fixed serial order
|
|
486
|
+
// prevents today only by construction. Keeping 16 before 27
|
|
487
|
+
// before 28, uninterrupted by any other evals/mcp/runs/ writer,
|
|
488
|
+
// preserves that guarantee.
|
|
489
|
+
//
|
|
490
|
+
// Fail-fast BETWEEN phases (a Phase 1 failure skips Phase 2 outright — no
|
|
491
|
+
// point spending Phase 2's wall-clock once the roster's known-flakiest gate
|
|
492
|
+
// is already red); fail-TOGETHER within Phase 2 (every Phase 2 gate runs to
|
|
493
|
+
// completion regardless of a sibling's failure, so one red pre-flight run
|
|
494
|
+
// reports every failing gate at once instead of the old one-at-a-time
|
|
495
|
+
// discovery) — the explicit fail-fast-vs-fail-together decision gh#2006's
|
|
496
|
+
// acceptance criteria calls for. Within the eval-health chain specifically,
|
|
497
|
+
// a failure DOES stop the chain (16 failing skips 27 and 28; 27 failing
|
|
498
|
+
// skips 28) — that trio has a real dependency relationship the rest of
|
|
499
|
+
// Phase 2 doesn't, so it keeps today's strict fail-fast semantics.
|
|
500
|
+
//
|
|
501
|
+
// versionArg/targetVersionArg/resume-mode branching is unchanged — every
|
|
502
|
+
// gate resolves its actual command through the same resolveGateCommand()
|
|
503
|
+
// logic step3PreFlight always applied, now factored out so all three
|
|
504
|
+
// phases share it verbatim.
|
|
505
|
+
|
|
506
|
+
const SOLO_GATE_NUMBER = 4; // test:unit:serial — see the phase-model note above.
|
|
507
|
+
const EVAL_HEALTH_CHAIN_NUMBERS = [16, 27, 28]; // eval:diff zettel → eval:diff free-form → write-eval-health.mjs
|
|
508
|
+
|
|
509
|
+
// Concurrency cap for Phase 2's pool — a fixed, deliberately conservative
|
|
510
|
+
// constant rather than the host's full core count. Most Phase-2 gates are
|
|
511
|
+
// lightweight verify/diff scripts, but a few (typecheck, the bundle
|
|
512
|
+
// verifiers, corpus verify) are real tsc/esbuild/Node processes, and
|
|
513
|
+
// worker-cap.mjs's own host-audit F3 finding is that unbounded concurrent
|
|
514
|
+
// lanes on a shared multi-session host reintroduce exactly the contention
|
|
515
|
+
// class this ticket must not reintroduce. Override via PREFLIGHT_CONCURRENCY
|
|
516
|
+
// for a dedicated/idle host.
|
|
517
|
+
const PREFLIGHT_CONCURRENCY = Number(process.env.PREFLIGHT_CONCURRENCY) || 4;
|
|
518
|
+
|
|
519
|
+
// Resolves one gate's actual command string — the same versionArg/
|
|
520
|
+
// targetVersionArg/resume branching step3PreFlight has always applied.
|
|
521
|
+
// Returns null for a versionArg gate deferred by an in-flight resume (Step
|
|
522
|
+
// 4g re-runs it once the notes it needs actually exist).
|
|
523
|
+
function resolveGateCommand(g, args) {
|
|
524
|
+
// versionArg gates take a MODE-DEPENDENT version (both directions bit the
|
|
525
|
+
// first 0.8.8 run): cut modes run pre-flight PRE-BUMP, where the README
|
|
526
|
+
// "Current version" claim still equals the PREVIOUS version (the bump
|
|
527
|
+
// moves it in Step 4; Step 4g then re-proves at the cut version) — while
|
|
528
|
+
// handoff runs POST-MERGE, where the claim already equals the CUT version
|
|
529
|
+
// and validating at previous fails by construction. A RESUMED half-cut run
|
|
530
|
+
// (gh#765) is post-bump too: the earlier aborted run already moved every
|
|
531
|
+
// reference to the cut version, so validating at previous false-fails
|
|
532
|
+
// gate 24 exactly like handoff would.
|
|
533
|
+
const hygieneVersion = (args.mode === 'handoff' || args.resume) ? args.version : args.previous;
|
|
534
|
+
// A RESUMED run's versionArg gates (cut-hygiene) demand artifacts Step 4
|
|
535
|
+
// has not recreated yet — docs/ops/releases/vX.Y.Z.md exists only after 4e,
|
|
536
|
+
// which runs AFTER this pre-flight. Step 4g re-runs these same gates at
|
|
537
|
+
// the cut version once the notes exist, so skipping them here loses no
|
|
538
|
+
// coverage — running them here false-fails by construction (the v0.8.32
|
|
539
|
+
// cut hit exactly this: 23/24 green, gate 24 dead on the missing notes).
|
|
540
|
+
if (g.versionArg && args.resume) return null;
|
|
541
|
+
if (g.versionArg) return `${g.cmd} --version ${hygieneVersion}`;
|
|
542
|
+
// targetVersionArg (gh#1135, REQ-06) is a distinct flag from versionArg:
|
|
543
|
+
// write-eval-health.mjs NAMES a new file after the cut's target version
|
|
544
|
+
// from the very start — there is no pre/post-bump claim to reconcile the
|
|
545
|
+
// way gate 24's README-currency check has, so it always takes
|
|
546
|
+
// `args.version` verbatim regardless of mode or resume.
|
|
547
|
+
if (g.targetVersionArg) return `${g.cmd} --version ${args.version}`;
|
|
548
|
+
return g.cmd;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Runs ONE gate to completion, capturing rather than streaming its output —
|
|
552
|
+
// see the file-header note above for why. Never throws: a failed gate is a
|
|
553
|
+
// normal, expected outcome the caller decides how to act on, exactly like
|
|
554
|
+
// shOk()'s own contract.
|
|
555
|
+
async function runGateCaptured(g, args) {
|
|
556
|
+
const cmd = resolveGateCommand(g, args);
|
|
557
|
+
if (cmd === null) {
|
|
558
|
+
return { n: g.n, cmd: g.cmd, what: g.what, ok: true, skipped: true,
|
|
559
|
+
note: '[resume] deferred to Step 4g (notes not regenerated yet)' };
|
|
560
|
+
}
|
|
561
|
+
const start = Date.now();
|
|
562
|
+
try {
|
|
563
|
+
const { stdout, stderr } = await execAsync(cmd, { cwd: REPO, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
|
|
564
|
+
return { n: g.n, cmd, what: g.what, ok: true, stdout, stderr, ms: Date.now() - start };
|
|
565
|
+
} catch (e) {
|
|
566
|
+
return {
|
|
567
|
+
n: g.n, cmd, what: g.what, ok: false, ms: Date.now() - start,
|
|
568
|
+
status: e.code ?? (e.signal ? `signal ${e.signal}` : '?'),
|
|
569
|
+
stdout: e.stdout, stderr: e.stderr,
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Prints one captured gate's result in the same shape a serial gate would
|
|
575
|
+
// have streamed live, so a scan-back-through-the-log habit built on the old
|
|
576
|
+
// stdio:'inherit' output still works.
|
|
577
|
+
function printGateResult(r) {
|
|
578
|
+
if (r.skipped) {
|
|
579
|
+
console.log(`\n ${r.n}/${GATE_ROSTER.length}. ${r.cmd} # ${r.what} — ${r.note}`);
|
|
435
580
|
return;
|
|
436
581
|
}
|
|
437
|
-
console.log(`\n
|
|
582
|
+
console.log(`\n ${r.n}/${GATE_ROSTER.length}. ${r.cmd} # ${r.what} (${(r.ms / 1000).toFixed(1)}s)`);
|
|
583
|
+
const out = [r.stdout, r.stderr].map((s) => (s || '').toString().trim()).filter(Boolean).join('\n');
|
|
584
|
+
if (out) console.log(out.split('\n').map((l) => ` ${l}`).join('\n'));
|
|
585
|
+
if (!r.ok) {
|
|
586
|
+
console.error(` ✗ FAILED (${r.n}/${GATE_ROSTER.length}): ${r.cmd} (exit ${r.status})`);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Runs a fixed array of gates strictly one after another, in array order —
|
|
591
|
+
// the eval-health chain (16 → 27 → 28), where the ordering IS the
|
|
592
|
+
// correctness requirement (see the phase-model note above), never a
|
|
593
|
+
// scheduling nicety. A failure stops the chain — the gates after it have a
|
|
594
|
+
// real dependency on what it would have produced, unlike the pool below.
|
|
595
|
+
async function runGateChain(gates, args) {
|
|
596
|
+
const results = [];
|
|
597
|
+
for (const g of gates) {
|
|
598
|
+
const r = await runGateCaptured(g, args);
|
|
599
|
+
printGateResult(r);
|
|
600
|
+
results.push(r);
|
|
601
|
+
if (!r.ok) break;
|
|
602
|
+
}
|
|
603
|
+
return results;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Runs a list of gates concurrently, capped at `limit` in flight at once —
|
|
607
|
+
// a minimal pool, not a dependency graph: every gate here is fully
|
|
608
|
+
// independent, so order WITHIN the pool carries no meaning, only the cap
|
|
609
|
+
// does. Every gate runs to completion regardless of a sibling's failure
|
|
610
|
+
// (fail-together within the pool — see the file-header note above).
|
|
611
|
+
async function runGatePool(gates, args, limit) {
|
|
612
|
+
const queue = [...gates];
|
|
613
|
+
const results = [];
|
|
614
|
+
async function worker() {
|
|
615
|
+
while (queue.length) {
|
|
616
|
+
const g = queue.shift();
|
|
617
|
+
const r = await runGateCaptured(g, args);
|
|
618
|
+
printGateResult(r);
|
|
619
|
+
results.push(r);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
await Promise.all(Array.from({ length: Math.min(limit, gates.length) }, worker));
|
|
623
|
+
return results;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// DRY preview stays the original flat serial walk, byte-for-byte — no real
|
|
627
|
+
// process ever spawns in dry mode, so there is no concurrency win to chase,
|
|
628
|
+
// and selftest()'s exact-output assertions (below) only ever run `--dry`.
|
|
629
|
+
function step3PreFlightDry(args) {
|
|
438
630
|
for (const g of GATE_ROSTER) {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
// README "Current version" claim still equals the PREVIOUS version (the
|
|
442
|
-
// bump moves it in Step 4; Step 4g then re-proves at the cut version) —
|
|
443
|
-
// while handoff runs POST-MERGE, where the claim already equals the CUT
|
|
444
|
-
// version and validating at previous fails by construction. A RESUMED
|
|
445
|
-
// half-cut run (gh#765) is post-bump too: the earlier aborted run already
|
|
446
|
-
// moved every reference to the cut version, so validating at previous
|
|
447
|
-
// false-fails gate 24 exactly like handoff would.
|
|
448
|
-
const hygieneVersion = (args.mode === 'handoff' || args.resume) ? args.version : args.previous;
|
|
449
|
-
// A RESUMED run's versionArg gates (cut-hygiene) demand artifacts Step 4
|
|
450
|
-
// has not recreated yet — docs/ops/releases/vX.Y.Z.md exists only after 4e,
|
|
451
|
-
// which runs AFTER this pre-flight. Step 4g re-runs these same gates at
|
|
452
|
-
// the cut version once the notes exist, so skipping them here loses no
|
|
453
|
-
// coverage — running them here false-fails by construction (the v0.8.32
|
|
454
|
-
// cut hit exactly this: 23/24 green, gate 24 dead on the missing notes).
|
|
455
|
-
if (g.versionArg && args.resume) {
|
|
631
|
+
const cmd = resolveGateCommand(g, args);
|
|
632
|
+
if (cmd === null) {
|
|
456
633
|
console.log(`\n ${g.n}/${GATE_ROSTER.length}. ${g.cmd} # ${g.what} — [resume] deferred to Step 4g (notes not regenerated yet)`);
|
|
457
634
|
continue;
|
|
458
635
|
}
|
|
459
|
-
// targetVersionArg (gh#1135, REQ-06) is a distinct flag from versionArg:
|
|
460
|
-
// write-eval-health.mjs NAMES a new file after the cut's target version
|
|
461
|
-
// from the very start — there is no pre/post-bump claim to reconcile
|
|
462
|
-
// the way gate 24's README-currency check has, so it always takes
|
|
463
|
-
// `args.version` verbatim regardless of mode or resume.
|
|
464
|
-
const cmd = g.versionArg
|
|
465
|
-
? `${g.cmd} --version ${hygieneVersion}`
|
|
466
|
-
: g.targetVersionArg
|
|
467
|
-
? `${g.cmd} --version ${args.version}`
|
|
468
|
-
: g.cmd;
|
|
469
636
|
console.log(`\n ${g.n}/${GATE_ROSTER.length}. ${cmd} # ${g.what}`);
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
637
|
+
sh(cmd, args, { stdio: 'inherit' }); // dry: logs `[dry] cmd`, never executes
|
|
638
|
+
}
|
|
639
|
+
console.log(`\n ✓ all ${GATE_ROSTER.length}/${GATE_ROSTER.length} gates passed`);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
async function step3PreFlight(args) {
|
|
643
|
+
if (args.skipGates) {
|
|
644
|
+
console.log('\n=== Step 3 — Pre-flight (SKIPPED via --skip-gates; danger!) ===');
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
console.log(`\n=== Step 3 — Pre-flight gates (${GATE_ROSTER.length}-gate roster — gate-roster.mjs) ===`);
|
|
648
|
+
if (args.dry) {
|
|
649
|
+
step3PreFlightDry(args);
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const soloGate = GATE_ROSTER.find((g) => g.n === SOLO_GATE_NUMBER);
|
|
654
|
+
const chainGates = EVAL_HEALTH_CHAIN_NUMBERS.map((n) => GATE_ROSTER.find((g) => g.n === n));
|
|
655
|
+
const chainNumbers = new Set(EVAL_HEALTH_CHAIN_NUMBERS);
|
|
656
|
+
const poolGates = GATE_ROSTER.filter((g) => g.n !== SOLO_GATE_NUMBER && !chainNumbers.has(g.n));
|
|
657
|
+
|
|
658
|
+
console.log(`\n-- Phase 1/2: gate ${SOLO_GATE_NUMBER} solo (test:unit:serial) --`);
|
|
659
|
+
const soloResult = await runGateCaptured(soloGate, args);
|
|
660
|
+
printGateResult(soloResult);
|
|
661
|
+
if (!soloResult.ok) {
|
|
662
|
+
console.error('\n Pre-flight aborted at Phase 1 — fix the gate failure before proceeding.');
|
|
663
|
+
console.error(' See references/gates-catalog.md for failure-mode → recovery.');
|
|
664
|
+
process.exit(1);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
console.log(`\n-- Phase 2/2: ${poolGates.length} gates (concurrency ${PREFLIGHT_CONCURRENCY}) + eval-health chain (gates ${EVAL_HEALTH_CHAIN_NUMBERS.join(' → ')}) --`);
|
|
668
|
+
const [poolResults, chainResults] = await Promise.all([
|
|
669
|
+
runGatePool(poolGates, args, PREFLIGHT_CONCURRENCY),
|
|
670
|
+
runGateChain(chainGates, args),
|
|
671
|
+
]);
|
|
672
|
+
|
|
673
|
+
const allResults = [soloResult, ...poolResults, ...chainResults].sort((a, b) => a.n - b.n);
|
|
674
|
+
const failed = allResults.filter((r) => !r.ok);
|
|
675
|
+
if (failed.length > 0) {
|
|
676
|
+
console.error(`\n ✗ ${failed.length}/${GATE_ROSTER.length} gate(s) FAILED: ${failed.map((r) => r.n).join(', ')}`);
|
|
677
|
+
console.error(' Pre-flight aborted. Fix the gate failure(s) before proceeding.');
|
|
678
|
+
console.error(' See references/gates-catalog.md for failure-mode → recovery.');
|
|
679
|
+
process.exit(1);
|
|
478
680
|
}
|
|
479
681
|
console.log(`\n ✓ all ${GATE_ROSTER.length}/${GATE_ROSTER.length} gates passed`);
|
|
480
682
|
}
|
|
@@ -1129,7 +1331,7 @@ async function main() {
|
|
|
1129
1331
|
process.exit(2);
|
|
1130
1332
|
}
|
|
1131
1333
|
step1ReBaseline(args);
|
|
1132
|
-
step3PreFlight(args);
|
|
1334
|
+
await step3PreFlight(args);
|
|
1133
1335
|
step4PromoteAndBump(args);
|
|
1134
1336
|
step5Commit(args);
|
|
1135
1337
|
// Invariant 3 (operator ruling 2026-07-12): release commits land via PR,
|
|
@@ -143,6 +143,41 @@ pixel-diff/visual-eval gate, not unit tests or review alone. Source:
|
|
|
143
143
|
[ADR-0081](../../../../../../docs/ops/adr/adr-0081-chart-2-0-foundations-attribute-grammar.md)
|
|
144
144
|
Amendment (2026-08-21).
|
|
145
145
|
|
|
146
|
+
## Canonical breakpoint scale (adr-0089)
|
|
147
|
+
|
|
148
|
+
[verified 2026-08-25, gh#1984] `core/responsive.js`'s `BREAKPOINTS` export is
|
|
149
|
+
the single canonical reference for any device-class threshold, whether the
|
|
150
|
+
query mechanism is `@media` (the five sanctioned viewport primitives —
|
|
151
|
+
`grid-ui`, `col-ui`, `row-ui`, `block-ui`, `text-ui` — plus top-layer/popover
|
|
152
|
+
positioning with no ancestor box to query) or `@container` (everything else,
|
|
153
|
+
per `adr-0088` — `spec-breakpoint-convention` REQ-004 forbids importing
|
|
154
|
+
`core/responsive.js` itself outside those cases). This table MUST match
|
|
155
|
+
`core/responsive.js`'s `BREAKPOINTS` export byte-for-byte — a gap here was
|
|
156
|
+
exactly the drift `spec-breakpoint-convention` REQ-007 named and closed:
|
|
157
|
+
|
|
158
|
+
| Name | Min-width |
|
|
159
|
+
| --- | --- |
|
|
160
|
+
| `xs` | `0` |
|
|
161
|
+
| `sm` | `480px` |
|
|
162
|
+
| `md` | `768px` |
|
|
163
|
+
| `lg` | `1024px` |
|
|
164
|
+
| `xl` | `1280px` |
|
|
165
|
+
|
|
166
|
+
A `@container`/`@media` rule can't read a CSS custom property at parse time
|
|
167
|
+
(forbidden-pattern #2 in [token-contract.md](token-contract.md)), so these
|
|
168
|
+
values are cited as raw literals in component CSS, never tokenized — but
|
|
169
|
+
every such literal MUST carry a preceding comment naming the rung it
|
|
170
|
+
implements (device-class threshold) or the ergonomic reason for the number
|
|
171
|
+
(a component-intrinsic threshold, exempt from this table entirely — `nav-ui`'s
|
|
172
|
+
96px icon-rail floor, `table-toolbar-ui`'s compaction stages, `chart-ui`'s
|
|
173
|
+
200px legend-hide are correctly exempt and are NOT migration candidates).
|
|
174
|
+
`packages/web-components/core/breakpoint-observer.js` is the shared
|
|
175
|
+
`ResizeObserver`-to-attribute helper for a component that needs
|
|
176
|
+
JS-observable state (relocating a node, switching a positioning strategy)
|
|
177
|
+
rather than a pure CSS layout switch — see its own module doc for the
|
|
178
|
+
boolean-vs-named-value reflection modes. Full requirements:
|
|
179
|
+
[spec-breakpoint-convention](../../../../../../docs/ops/spec/spec-breakpoint-convention.md).
|
|
180
|
+
|
|
146
181
|
## Numeric props — `null` over sentinels
|
|
147
182
|
|
|
148
183
|
Indeterminate, unknown, or "not yet set" numeric state uses `null`, not `-1` or `Infinity`:
|