@adia-ai/adia-ui-forge 0.8.34 → 0.8.36

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.
Files changed (50) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +27 -0
  3. package/README.md +12 -4
  4. package/agents/a2ui-builder.md +12 -8
  5. package/agents/component-author.md +17 -12
  6. package/agents/framework-reviewer.md +18 -12
  7. package/agents/release-builder.md +15 -10
  8. package/commands/deploy.md +2 -0
  9. package/commands/dogfood.md +2 -0
  10. package/commands/gen-review.md +2 -0
  11. package/commands/release.md +3 -1
  12. package/package.json +4 -1
  13. package/scripts/forge-lint +38 -1
  14. package/scripts/release-pretag-docs-gate +45 -3
  15. package/skills/adia-a2ui/SKILL.md +2 -2
  16. package/skills/adia-author/SKILL.md +1 -1
  17. package/skills/adia-author/references/anti-patterns.md +1 -0
  18. package/skills/adia-author/references/code-style.md +3 -3
  19. package/skills/adia-author/references/worked-example.md +3 -3
  20. package/skills/adia-author/references/yaml-contract.md +42 -0
  21. package/skills/adia-deploy/SKILL.md +10 -11
  22. package/skills/adia-dogfood/SKILL.md +5 -4
  23. package/skills/adia-dogfood/references/admin-shell-anatomy.md +5 -2
  24. package/skills/adia-dogfood/references/app-shell-pitfalls.md +10 -3
  25. package/skills/adia-dogfood/scripts/analyze.mjs +2 -0
  26. package/skills/adia-gen-review/SKILL.md +10 -3
  27. package/skills/adia-gen-review/references/loop-protocol.md +15 -8
  28. package/skills/adia-gen-review/references/rubric-score.md +5 -2
  29. package/skills/adia-gen-review/scripts/gen-review-status.mjs +8 -6
  30. package/skills/adia-llm-internals/SKILL.md +1 -1
  31. package/skills/adia-release/SKILL.md +9 -8
  32. package/skills/adia-release/references/cut-procedure.md +16 -8
  33. package/skills/adia-release/references/gates-catalog.md +4 -2
  34. package/skills/adia-release/references/independent-package-release.md +13 -9
  35. package/skills/adia-release/references/recovery-paths.md +2 -2
  36. package/skills/adia-release/scripts/bump.mjs +7 -3
  37. package/skills/adia-release/scripts/dispatch-publish.mjs +5 -2
  38. package/skills/adia-release/scripts/gate-roster.mjs +29 -0
  39. package/skills/adia-release/scripts/package-paths.mjs +41 -13
  40. package/skills/adia-release/scripts/pr-bridge.mjs +36 -2
  41. package/skills/adia-release/scripts/promote-unreleased.mjs +27 -7
  42. package/skills/adia-release/scripts/release-pack.mjs +24 -11
  43. package/skills/adia-release/scripts/tag-lockstep.mjs +10 -8
  44. package/skills/adia-site-docs/SKILL.md +8 -6
  45. package/skills/adia-site-docs/intent.md +1 -1
  46. package/skills/adia-ssr/SKILL.md +1 -1
  47. package/skills/adia-ssr/references/failure-shapes.md +8 -5
  48. package/skills/adia-ssr/references/guard-patterns.md +1 -1
  49. package/skills/adia-site-docs/evals/audit-report.md +0 -30
  50. package/skills/adia-ssr/evals/audit-report.md +0 -63
@@ -92,8 +92,7 @@ reports "matches this allowlist row" as part of its finding and stops.
92
92
  Never auto-fixed: `zero-area` (always human-eyeballed), `empty-control` on an
93
93
  unrecognized component, native-primitive replacement (attribute-shape
94
94
  decisions), card-header wrapper hoists (slot intent), any warning without an
95
- allowlist row. When sweeping `avatar-ui[name]`, never touch the deprecation-
96
- handler string inside `components/avatar/avatar.js` itself.
95
+ allowlist row.
97
96
  **NEVER exceed 5 mechanical fixes per PR or apply a fix outside this
98
97
  allowlist** — blast radius stays human-reviewable.
99
98
 
@@ -111,8 +110,10 @@ same session — harvested training chunks silently hold stale values otherwise.
111
110
 
112
111
  ## Findings home + PR shape
113
112
 
114
- Reports and probe artifacts land in `qa/findings/` (visual baselines in
115
- `qa/baselines/`). Commit the report even on a zero-finding day — the paper
113
+ Reports and probe artifacts land in `qa/findings/` (the geometry-probe
114
+ baseline lives at `scripts/qa/empty-instantiation-baseline.json`; there is
115
+ no visual-baseline directory — probes are geometry-only, gh#988). Commit
116
+ the report even on a zero-finding day — the paper
116
117
  trail is how "used to be fine, now broken" is detected. PR title:
117
118
  `dogfood: <YYYY-MM-DD> — <N> critical, <M> warning`; body = report excerpt +
118
119
  auto-applied-fix checklist + left-for-human list.
@@ -3,8 +3,11 @@
3
3
  Script: `scripts/dev/audit-shell-composition.mjs` (repo-local), npm gates
4
4
  `audit:shell-composition{,:strict,:all}`. Static AST walk over
5
5
  `apps/**/*.html`, `playgrounds/**/*.html`, `catalog/page-shells/**/*.html` —
6
- no browser needed; pre-commit fast. The script mechanically checks a subset
7
- (the findings table below); this 13-part list is the full review standard.
6
+ no browser needed; pre-commit fast. The script's own `PARTS` array is the
7
+ mechanical census it has grown past this list (count it in
8
+ `scripts/dev/audit-shell-composition.mjs`, never from a hand-typed number
9
+ here); this numbered list is the human review standard, and the findings
10
+ table below samples common symptoms rather than the script's full roster.
8
11
  Canonical source: the monorepo's `site/index.html` — the markup that renders
9
12
  the live admin-dashboard example.
10
13
 
@@ -20,10 +20,17 @@ may stall — wait 30s, re-run.
20
20
  | 4 | Top-level `await` without async setup wrap | `[setup-failed]` console error | `export default async function setup(host) { … }` |
21
21
  | 5 | Vite import-analysis 500 on dynamic import | `[network-4xx] 500` for `./<name>.contents.js` | add `/* @vite-ignore */` to the dynamic import |
22
22
  | 6 | icon-ui not imported despite `<icon-ui>` / `[icon=…]` / icon-rendering composites | `[icon-ui-missing]` | `import "/packages/web-components/components/icon/icon.js"` |
23
+ | 7 | `<admin-page-body>` emitted without its `<admin-page>` ancestor (gh#981) | *(no audit-app-shells.mjs tag — apps/-only script, doesn't sweep this surface)* | wrap in `<admin-page>` — `admin-page > admin-page-body { flex:1; … }` (`admin-shell.bespoke.css:144`) is a direct-child selector; without that literal parent, `admin-page-body` falls back to UA `display:inline` |
23
24
 
24
- Secondary signals: `[collapsed-element]` (registered but 0px tall),
25
- `[network-4xx]` (typoed stylesheet href, missing contents.html, stale
26
- `import.meta.url`).
25
+ Secondary signals: `[collapsed-element]` (registered but 0px tall
26
+ `audit-app-shells.mjs`'s own threshold is <4px, so a shallower-but-still-broken
27
+ collapse won't trip it), `[network-4xx]` (typoed stylesheet href, missing
28
+ contents.html, stale `import.meta.url`). Row 7's specific case (gh#981, in
29
+ `site/site.js`'s router, not swept by this script at all): the 150px isn't
30
+ `admin-page-body`'s own height — it's a *replaced child* (an `<iframe>`)
31
+ whose `height:100%` can't resolve, so it falls back to the browser's
32
+ intrinsic default (300×150). The observable is a body rendering ~150px tall
33
+ with no console error, regardless of real content height.
27
34
 
28
35
  ## Composite → internal primitives (undiscoverable from the markup)
29
36
 
@@ -571,6 +571,8 @@ function renderReport(results, base) {
571
571
  lines.push('4. **Synonym-attr / synonym-slot drift** — markers documented in `.claude/docs/conventions/attribute-api-migration.md`.');
572
572
  lines.push('5. **Alert flex-row** — `alert-ui` with multiple bare `<text-ui>` children (need `<col-ui slot="content">` wrap).');
573
573
  lines.push('6. **Console** — every `console.error` and `console.warn` during page load + 800ms settling.');
574
+ lines.push('7. **Missing component CSS** — a rendered `*-ui` element with no resolved `--{prefix}-*` custom property, meaning its per-component stylesheet never loaded (controls render unstyled).');
575
+ lines.push('8. **Unstyled popover** — an OPEN `[popover]` with transparent background AND zero padding (transparent-host components like `feed-ui` allowlisted).');
574
576
  lines.push('');
575
577
 
576
578
  return lines.join('\n');
@@ -35,8 +35,15 @@ directives are findings.
35
35
  3. Zero P1 cosmetic findings (overflow entries auto-count as P1) — checked
36
36
  mechanically.
37
37
  4. Zero RENDER_FAILURE prompts; cycle `status: COMPLETE` — checked
38
- mechanically (plus the human-QA block, ≥4/5).
39
- 5. Human QA: 5 sampled prompts, `passCount 4`.
38
+ mechanically.
39
+ 5. **Human QA is per-sweep, not per-cycle** (spec-factory-dx-ws6-measurement.md
40
+ REQ-11, gh#1137 — retired from this exit gate 2026-08-12; the per-cycle
41
+ `humanQA` ledger field never held: null in 12 of 13 rows, and the one
42
+ populated row still said "gate pending"). The 5-sampled-prompts,
43
+ `passCount ≥ 4` bar is unchanged, but it now feeds `qa/dx/`'s R1 sweep
44
+ record (D5's induced-failure sample + D8's gallery term) instead of
45
+ blocking a single cycle's own exit condition. `gen-review-status.mjs
46
+ --check-exit` no longer reads `humanQA` off the ledger row.
40
47
 
41
48
  ## The Gen-Review Scorecard — the per-prompt output contract
42
49
 
@@ -182,7 +189,7 @@ map + FAILS/WORKS transpilation pairs:
182
189
  | New primitive / native tag in canvas | `TAG_TO_COMPONENT` in the decompose script (`Native*` prefix for natives); coverage-audit must go green |
183
190
  | New root-cause code | rubric-score.md table + `scores.schema.json` enum + `schemaVersion` bump |
184
191
  | New safe attribute | `ATTR_ALLOWLIST` — static enums only, never freeform-text attrs |
185
- | Threshold recalibration | rubric-score.md §Thresholds (pass-flip sweep) + the exit gate above |
192
+ | Threshold recalibration | rubric-score.md §Thresholds (pass-flip sweep) + the exit gate above + `EXCELLENCE_THRESHOLD` at `scripts/gen-review-status.mjs:31` — the only mechanical enforcer; skip it and `--check-exit` silently keeps the old floor |
186
193
 
187
194
  ## Posture
188
195
 
@@ -161,12 +161,16 @@ never get fix plans. Reads ONLY the decomposed file.
161
161
  skill does not perform the edits.
162
162
  3. **Regenerate**: `npm run gallery:generate`; confirm 0 console
163
163
  errors/warnings in the canvas output.
164
- 4. **Human QA gaterequired for COMPLETE.** Operator reviews 5 random
165
- PASSING prompts against: (a) serves the user's task? (b) right primary
166
- primitive? (c) would ship unchanged? Record
167
- `humanQA.{sampledPrompts,passCount,failCount}`. `failCount 2` cycle
168
- stays `OPEN`: the thresholds are miscalibrated recalibrate
169
- rubric-score.md §Thresholds against the human judgments.
164
+ 4. **Human QA sampleper-sweep, not per-cycle** (retired from this
165
+ cycle's own COMPLETE gate 2026-08-12, spec-factory-dx-ws6-measurement.md
166
+ REQ-11, gh#1137). Operator reviews 5 random PASSING prompts against:
167
+ (a) serves the user's task? (b) right primary primitive? (c) would ship
168
+ unchanged? Record the result in the next `qa/dx/` sweep record's
169
+ `humanQA.{sampledPrompts,pass,fail}` (`node scripts/qa/dx-status.mjs`),
170
+ not on this cycle's ledger row. `failCount ≥ 2` still means the
171
+ thresholds are miscalibrated — recalibrate rubric-score.md §Thresholds
172
+ against the human judgments — it just no longer blocks THIS cycle's own
173
+ `status: COMPLETE`.
170
174
  5. **Schema gate** (must exit 0 before touching the ledger):
171
175
 
172
176
  ```bash
@@ -175,8 +179,11 @@ never get fix plans. Reads ONLY the decomposed file.
175
179
 
176
180
  6. **Update ledger** (`review/cycle-ledger.json`): `cycleNumber`,
177
181
  `completedAt`, `engine`, `status`, `aggregate`
178
- (passingCount/failingCount/renderFailureCount/meanScore/Δ), `humanQA`.
179
- Remove the `cycle-{N}.lock` sentinel.
182
+ (passingCount/failingCount/renderFailureCount/meanScore/Δ). `humanQA` is
183
+ RETIRED from this per-cycle row (step 4) — do not populate it here; a
184
+ stray value is harmless (ignored) but the field's home is now the
185
+ per-sweep `qa/dx/sweeps/*.json` record. Remove the `cycle-{N}.lock`
186
+ sentinel.
180
187
  7. **Exit condition**:
181
188
 
182
189
  ```bash
@@ -22,8 +22,11 @@ Max score 105 (D1–D5 at 20 each = 100; D6 mechanical = 0 or +5).
22
22
  **Recalibrating** (when human QA contradicts mechanical scores across 2+
23
23
  cycles): sweep candidate threshold values and read the pass-flips at each step
24
24
  against the human verdicts — never pick a round number by feel. A threshold
25
- change must also update the exit gate in SKILL.md and the schema comment, and
26
- new cause codes / dimensions bump `schemaVersion` in
25
+ change must also update the exit gate in SKILL.md AND the
26
+ `EXCELLENCE_THRESHOLD` constant at `scripts/gen-review-status.mjs:31` that
27
+ constant is the only mechanical enforcer (`--check-exit` reads nothing else;
28
+ the schema carries no threshold), so missing it leaves the old floor silently
29
+ enforced. New cause codes / dimensions bump `schemaVersion` in
27
30
  [scores.schema.json](scores.schema.json).
28
31
 
29
32
  ---
@@ -17,9 +17,14 @@
17
17
  *
18
18
  * Exit-condition (from scores.schema.json + SKILL.md §ExitCondition):
19
19
  * - Every prompt PASSING (p1Count = 0 AND rubricScore.score ≥ 92)
20
- * - humanQA.passCount ≥ 4 (of 5 sampled)
21
20
  * - No RENDER_FAILURE prompts
22
21
  * - Cycle status = COMPLETE (not OPEN or INTERRUPTED)
22
+ *
23
+ * humanQA is RETIRED from this per-cycle exit condition (spec-factory-dx-
24
+ * ws6-measurement.md REQ-11, gh#1137, 2026-08-12): the per-cycle ledger
25
+ * field never held (null in 12/13 rows), so it moved to a per-sweep
26
+ * obligation — `qa/dx/sweeps/*.json`'s `humanQA` field (dx-status.mjs)
27
+ * reads a 5-prompt sample per sweep instead of per cycle.
23
28
  */
24
29
 
25
30
  import { readFileSync, existsSync } from 'node:fs';
@@ -85,11 +90,8 @@ function checkExitCondition() {
85
90
  reasons.push(`Cycle ${latest.cycleNumber} status is "${latest.status}" (need COMPLETE)`);
86
91
  }
87
92
 
88
- if (!latest.humanQA) {
89
- reasons.push('Human QA gate not completed (humanQA block missing)');
90
- } else if (latest.humanQA.passCount < 4) {
91
- reasons.push(`Human QA pass count ${latest.humanQA.passCount}/5 (need ≥ 4)`);
92
- }
93
+ // humanQA is RETIRED from this per-cycle check (REQ-11, gh#1137) — it's a
94
+ // per-sweep obligation now (qa/dx/sweeps/*.json), not a per-cycle gate.
93
95
 
94
96
  const agg = latest.aggregate;
95
97
  if (agg?.failingCount > 0) {
@@ -25,7 +25,7 @@ Consumers depend on: the `StreamChunk` union, `ChatResult` (`text` / `usage` / `
25
25
 
26
26
  Three invariants override any cleanup instinct:
27
27
 
28
- 1. **NEVER normalize `stopReason`.** Providers emit `end` / `stop` / `max_tokens` / `length` / `MAX_TOKENS` / `tool_use`; the downstream truncation detector reads the raw value. Collapsing to `end` hides truncation — a defect, not a cleanup.
28
+ 1. **Never collapse `stopReason` truncation values — and never invent a NEW normalization.** The one sanctioned mapping is OpenAI's own `finish_reason === 'stop'` → `end` (`openai.ts` `parseResponse`; adapter-contract.md §stopReason documents it as correct); everything else propagates raw. Providers emit `end` / `stop` / `max_tokens` / `length` / `MAX_TOKENS` / `tool_use`; the downstream truncation detector reads the raw value, so collapsing `max_tokens`/`length`/`MAX_TOKENS` to `end` hides truncation — a defect, not a cleanup.
29
29
  2. **`buildRequest()` is the single source of upstream shape** for direct AND passthrough-proxy mode; the dispatcher swaps only the URL. Never fork it per proxy flavor.
30
30
  3. **No real API key reaches the browser on a production host.** The same-origin passthrough proxy injects the key server-side; the sentinel-key + one-shot-warning path in `createAdapter()` must survive any refactor.
31
31
 
@@ -3,8 +3,9 @@ name: adia-release
3
3
  description: >-
4
4
  Release engineering for the @adia-ai lockstep monorepo. Use when asked to
5
5
  cut and ship a release, promote [Unreleased] CHANGELOG entries, tag and push the
6
- lockstep packages to origin, publish a 13-package release (11 npm
7
- libraries + 2 Claude Code plugins), batch-push piled-up release commits,
6
+ lockstep packages to origin, publish a 13-package release (11
7
+ npm libraries + 2 Claude Code plugins, as of gh#607 — see
8
+ scripts/package-paths.mjs), batch-push piled-up release commits,
8
9
  recover a cut that landed wrong or whose publish workflows didn't fire,
9
10
  fix a check:lockstep bump failure or an F-N1 CHANGELOG-coverage warning,
10
11
  verify the release gates without cutting anything, or author release
@@ -17,7 +18,7 @@ user-invocable: true
17
18
 
18
19
  # adia-release
19
20
 
20
- Release engineering for an @adia-ai-style lockstep monorepo: the `@adia-ai/*` packages on the roster (`scripts/package-paths.mjs`, the single source — 13 as of gh#607: 11 npm libraries plus the 2 Claude Code plugins under `packages/plugins/*`) version and publish together (class A; the plugins joined the lockstep 2026-07-15 after class-B independence let npm drift a full harness-reset behind the repo). Class B (independent versioning) currently has no members; the procedure is retained for future genuinely-independent packages. The substrate — `scripts/release/*`, the `check:*` gates, the tag-triggered publish workflows — encodes the invariants; this skill routes, sequences, and stops at the judgment calls.
21
+ Release engineering for an @adia-ai-style lockstep monorepo: the `@adia-ai/*` packages on the roster (`scripts/package-paths.mjs`, the single source — 13 lockstep as of gh#607: 11 npm libraries plus the 2 Claude Code plugins under `packages/plugins/*`) version and publish together (class A; the plugins joined the lockstep 2026-07-15 after class-B independence let npm drift a full harness-reset behind the repo). Class B (independent versioning) has one member — `@adia-ai/adia-plugins` (gh#1133/gh#1160, ADR-0045), the npm-sourced marketplace manifest package, marked `lockstep: false` on the roster; it cuts on its own version line and its own tag, never the umbrella ([`references/independent-package-release.md`](references/independent-package-release.md)). The substrate — `scripts/release/*`, the `check:*` gates, the tag-triggered publish workflows — encodes the invariants; this skill routes, sequences, and stops at the judgment calls.
21
22
 
22
23
  ## Authorization — one go, gates do the rest (operator ruling 2026-07-17)
23
24
 
@@ -34,7 +35,7 @@ Mechanics: `release-pack.mjs --go` auto-confirms all checkpoints (it equals `--y
34
35
  1. **Lockstep coherence** — every roster package bumps together (`check:lockstep`; the roster lives in `scripts/package-paths.mjs`, currently `web-components`, `web-modules`, `llm`, `agent`, `persona`, `a2ui-{runtime,compose,corpus,mcp,retrieval,validator}`, and the plugins `adia-ui-{factory,forge}`) (each plugin's `.claude-plugin/plugin.json` version moves with its package.json — `bump.mjs` handles both; `verify:plugins` gates the sync).
35
36
  2. **PATCH-cut asymmetry** — internal `@adia-ai/*` ranges hold at `^X.Y.0` during PATCH cuts; only MINOR bumps the floor. `^0.0.x` is forbidden (pre-1.0 caret locks exact — shipped a real stale-sibling bug).
36
37
  3. **Release commits land via PR, never a direct push to `main`** (repo policy, operator ruling 2026-07-12): commit on `release/vX.Y.Z` → PR → CI → merge — THEN tag at `main`'s post-merge HEAD, not at the bump commit; post-bump fixes merge before tagging and belong in the tarball. Exception: batch push tags each version at its own release-merge SHA.
37
- 4. **One umbrella + one per-package tag per cut** (`vX.Y.Z` + 11 × `<pkg>-vX.Y.Z`); publish workflows key off the per-package tags. Push tags **one per `git push`** — a batched multi-tag push drops the trigger event.
38
+ 4. **One umbrella + one per-package tag per cut** (`vX.Y.Z` + 13 × `<pkg>-vX.Y.Z`, one per lockstep-roster package — `scripts/package-paths.mjs`); publish workflows key off the per-package tags. Push tags **one per `git push`** — a batched multi-tag push drops the trigger event.
38
39
  5. **F-N1 (`check:release --all-pending`) per-package clean** — the umbrella-tag mismatch error is expected noise. Coverage is mechanized at cut time: Step 4f (`check-release.mjs --pending-version X.Y.Z --fix`) runs the SAME matcher pre-PR and auto-appends verified Maintenance bullets, so a post-tag warn means something novel — stop and investigate, don't loop enrichment PRs.
39
40
  6. **`npm dist-tag latest` is set by publish order** — batch pushes publish oldest first and WAIT for settle.
40
41
  7. **A breaking (MINOR) cut MUST ship its MIGRATION GUIDE section in the same cycle** — MINOR is reserved for removed/renamed API symbols; everything else, including visible behavior changes, stays PATCH.
@@ -58,7 +59,7 @@ The release is done only when reality confirms it: **the npm registry, the GH re
58
59
 
59
60
  | Task shape | Done when |
60
61
  | --- | --- |
61
- | Lockstep cut / handoff | `npm view @adia-ai/<pkg> version` = X.Y.Z for all 11 AND `dist-tags.latest` = X.Y.Z AND a deployed content file (not an SPA route) serves real bytes |
62
+ | Lockstep cut / handoff | `npm view @adia-ai/<pkg> version` = X.Y.Z for all 13 lockstep packages AND `dist-tags.latest` = X.Y.Z AND a deployed content file (not an SPA route) serves real bytes |
62
63
  | Batch push | every batched tag on `git ls-remote --tags origin` + every version on the registry, `latest` on the newest |
63
64
  | Verify-only | the failing gate re-runs green |
64
65
  | Recovery | the trip-wire that surfaced the issue passes |
@@ -74,7 +75,7 @@ Every cut reports, whether it ran inline or via `release-builder`:
74
75
  | --- | --- |
75
76
  | Version | X.Y.Z — all roster packages at this version (`check:lockstep`) |
76
77
  | Commit / PR | release commit SHA, PR # (merged) |
77
- | Tags | umbrella `vX.Y.Z` + 11 per-package tags pushed (or: which are still pending, and why) |
78
+ | Tags | umbrella `vX.Y.Z` + 13 per-package tags pushed (or: which are still pending, and why) |
78
79
  | Registry | `npm view @adia-ai/<pkg> version` per package, cited (not "publish workflow green") |
79
80
  | `dist-tags.latest` | confirmed = X.Y.Z |
80
81
  | Deploy | dispatched (`deploy-site.yml` run URL) / N/A this cut |
@@ -93,9 +94,9 @@ assumed pass, or "published" without the actual `npm view` output.
93
94
 
94
95
  ## Mechanization (bundled `scripts/`, stdlib Node, all support `--dry`)
95
96
 
96
- `release-pack.mjs` walks the cycle in two phases per invariant 3 — `--mode cut` / `--mode from-scratch` promote `[Unreleased]` (BOTH modes now — a peer's hand-authored section must be promoted whichever mode cut it, and a loud guard blocks the bump if any roster package still carries non-empty `[Unreleased]` content after promotion), run Step 4f (pre-tag coverage `--fix` with the authoritative F-N1 matcher), and stop at the release commit (PR → merge); `--mode handoff` tags/publishes/deploys from post-merge main. **`--go` is the standard invocation for an operator-initiated release** — the single authorization covers the cycle; evidence blocks print as a log; granular `--yes`/`--push`/`--publish` remain for cautious manual runs (`--push` still refuses to auto-fire past any F-N1 finding). `--mode batch` is not implemented — it hard-rejects with a pointer to [`recovery-paths.md`](references/recovery-paths.md) §Scenario 2 rather than silently misbehaving; a real batch tags each version at its own release-merge SHA, which this single-version orchestrator has no model for. Pre-flight (Step 3) runs the full 24-gate roster from the single shared list in `scripts/gate-roster.mjs` — the same list [`cut-procedure.md`](references/cut-procedure.md) §3.1 transcribes, so a subset run requires editing that one file, not two.
97
+ `release-pack.mjs` walks the cycle in two phases per invariant 3 — `--mode cut` / `--mode from-scratch` promote `[Unreleased]` (BOTH modes now — a peer's hand-authored section must be promoted whichever mode cut it, and a loud guard blocks the bump if any roster package still carries non-empty `[Unreleased]` content after promotion), run Step 4f (pre-tag coverage `--fix` with the authoritative F-N1 matcher), and stop at the release commit (PR → merge); `--mode handoff` tags/publishes/deploys from post-merge main. **`--go` is the standard invocation for an operator-initiated release** — the single authorization covers the cycle; evidence blocks print as a log; granular `--yes`/`--push`/`--publish` remain for cautious manual runs (`--push` still refuses to auto-fire past any F-N1 finding). `--mode batch` is not implemented — it hard-rejects with a pointer to [`recovery-paths.md`](references/recovery-paths.md) §Scenario 2 rather than silently misbehaving; a real batch tags each version at its own release-merge SHA, which this single-version orchestrator has no model for. Pre-flight (Step 3) runs the full 28-gate roster from the single shared list in `scripts/gate-roster.mjs` — the same list [`cut-procedure.md`](references/cut-procedure.md) §3.1 transcribes, so a subset run requires editing that one file, not two.
97
98
 
98
- The pieces are callable alone: `bump.mjs` (version fields + the factory `.mcp.json` a2ui-mcp pin, invariant 8), `promote-unreleased.mjs` / `insert-stub.mjs` (CHANGELOG blocks), `tag-lockstep.mjs` (12 tags, `--delete` to move), `dispatch-publish.mjs` (publish workflows; `--verify-triggered` re-dispatches missing AND dead — cancelled/failed/timed-out — runs, each gated behind an npm-registry check so an already-published version is never re-dispatched (gh#763); `--after` enforces batch ordering — dispatch output states what WAS done, never a to-do phrased as if it hadn't fired), and `pr-bridge.mjs` (the two-phase bridge: pushes the release branch, opens the PR, waits out CI, and merges ONLY on all-non-fail-soft-checks-green + zero unresolved threads + no changes-requested; any other state stops with evidence — §5.7 mechanized, `--dry` to preview the decision). All fail fast outside a monorepo root (`assert-monorepo-root.mjs`); fork-configurable via `--host` / `--scope`. All 6 CLI scripts (plus `scripts/release/check-release.mjs`) have a `selftest` mode wired into CI's Plugin bin selftests step; the 3 helper modules (`assert-monorepo-root`, `gate-roster`, `package-paths`) are exercised through the CLIs' selftests, not their own.
99
+ The pieces are callable alone: `bump.mjs` (version fields + the factory `.mcp.json` a2ui-mcp pin, invariant 8), `promote-unreleased.mjs` / `insert-stub.mjs` (CHANGELOG blocks), `tag-lockstep.mjs` (umbrella + one tag per roster package — 14 today, `--delete` to move), `dispatch-publish.mjs` (publish workflows; `--verify-triggered` re-dispatches missing AND dead — cancelled/failed/timed-out — runs, each gated behind an npm-registry check so an already-published version is never re-dispatched (gh#763); `--after` enforces batch ordering — dispatch output states what WAS done, never a to-do phrased as if it hadn't fired), and `pr-bridge.mjs` (the two-phase bridge: pushes the release branch, opens the PR, waits out CI, and merges ONLY on all-non-fail-soft-checks-green + zero unresolved threads + no changes-requested; any other state stops with evidence — §5.7 mechanized, `--dry` to preview the decision). All fail fast outside a monorepo root (`assert-monorepo-root.mjs`); fork-configurable via `--host` / `--scope`. All 6 CLI scripts (plus `scripts/release/check-release.mjs`) have a `selftest` mode wired into CI's Plugin bin selftests step; the 3 helper modules (`assert-monorepo-root`, `gate-roster`, `package-paths`) are exercised through the CLIs' selftests, not their own.
99
100
 
100
101
  Site deploy (Step 10 of the handoff) dispatches `deploy-site.yml` (its own pre-flight verify → snapshot → hardened rsync → post-deploy verify → auto-rollback), never a raw local `rsync` — H1 (forge-campaign gh#268 audit): the script had drifted into bypassing that pipeline even though this doc already taught the workflow-dispatch path. The dispatch rides the cycle's single authorization (`--go`, or `--publish` on a manual run); the workflow's own GitHub environment gate is the human stop for the deploy itself.
101
102
 
@@ -4,8 +4,9 @@
4
4
  > handoff). Companions: [`gates-catalog.md`](gates-catalog.md) (gate roster +
5
5
  > failure routing), [`changelog-discipline.md`](changelog-discipline.md) (promotion
6
6
  > + F-N1 enrichment), [`recovery-paths.md`](recovery-paths.md) (when it goes wrong).
7
- > The concrete gate names, 9 package paths, and `ui-kit.exe.xyz` deploy are the
8
- > @adia-ai monorepo's worked example of the portable discipline. `$REPO` = repo root.
7
+ > The concrete gate names, the roster's package paths (`scripts/package-paths.mjs`,
8
+ > 13 lockstep as of gh#607), and `ui-kit.exe.xyz` deploy are the @adia-ai
9
+ > monorepo's worked example of the portable discipline. `$REPO` = repo root.
9
10
 
10
11
  Two entry variants, converging at Step 5:
11
12
 
@@ -113,6 +114,10 @@ npm run check:token-semantics-sync # 21 adia-tokens generated refs v
113
114
  npm run check:demo-routes # 22 demo surfaces routed + patterns indexed
114
115
  npm run check:brand-assets # 23 brand mark token-driven, not baked raster
115
116
  node scripts/release/check-cut-hygiene.mjs --version <mode-dependent> # 24 README CDN-pin + version currency — CUT modes pre-flight at the PREVIOUS version (the README claim is exact-match and only moves at the Step-4 bump; Step 4g re-proves at the cut version), HANDOFF pre-flights at the CUT version (post-merge, the claim already moved)
117
+ python3 packages/plugins/adia-ui-factory/scripts/adia-scaffold selftest # 25 scaffold specifiers resolve in packed @adia-ai/web-components + @adia-ai/web-modules tarballs (gh#1132)
118
+ node scripts/release/check-dx-sweep-freshness.mjs # 26 DX sweep fresh + not regression-RED (gh#1137 REQ-05 — FLAGGED, needs adia-release confirmation; see the script's own header)
119
+ npm run eval:diff -- --engine free-form # 27 fresh free-form eval run (evals/health input, gh#1135)
120
+ node scripts/release/write-eval-health.mjs --version <cut> # 28 evals/health/<version>.json committed, AC-01/AC-02 run for real (gh#1135, WS-4 SPEC REQ-06)
116
121
  ```
117
122
 
118
123
  Any red → route via [`gates-catalog.md`](gates-catalog.md); fix at the source, re-run the narrowest gate, then re-run the full sequence. The canonical miss: a cut that ran 7 of the gates shipped a stale-embeddings defect that surfaced a day later and cost a tag-move recovery.
@@ -266,7 +271,7 @@ The operator's initiating instruction covers the whole cycle — no per-step re-
266
271
 
267
272
  | Evidence logged before | Content | Why it's still printed |
268
273
  | --- | --- | --- |
269
- | Tagging (Step 6) | The planned tag list (umbrella + 11 per-package) | The log line a recovery diagnoses from |
274
+ | Tagging (Step 6) | The planned tag list (umbrella + 13 per-package) | The log line a recovery diagnoses from |
270
275
  | Pushing (Step 8) | The Step 7 F-N1 output + tag list + `origin/main..HEAD` count | F-N1's first real evidence — and its ERROR path still hard-stops unconditionally |
271
276
  | Publishing (Step 9) | The current registry snapshot (versions + `dist-tags.latest`) | Ordering is verified against what's LIVE, mechanically |
272
277
  | Deploying (Step 10) | The `deploy-site.yml` dispatch (never a raw rsync) | The workflow carries its own GitHub-environment human gate |
@@ -281,7 +286,7 @@ After the bump + lockfile, run the AUTHORITATIVE F-N1 matcher against the workin
281
286
  node scripts/release/check-release.mjs --pending-version X.Y.Z --fix
282
287
  ```
283
288
 
284
- Stage its CHANGELOG edits into the release commit (the Step-5 allowlist already covers `CHANGELOG.md`). It re-verifies its own output with the same matcher before writing — a `--fix` that doesn't satisfy the checker is a hard error, not a silent pass. Do NOT use `check-changelog-coverage.mjs` as the pre-tag gate: it carries a different matcher and a 9-package roster missing the 2 plugins — the drift that cost the v0.8.5 cut 3 enrichment PRs and 3 tag rewrites after "coverage clean" at cut time.
289
+ Stage its CHANGELOG edits into the release commit (the Step-5 allowlist already covers `CHANGELOG.md`). It re-verifies its own output with the same matcher before writing — a `--fix` that doesn't satisfy the checker is a hard error, not a silent pass. (The former `check:changelog-coverage` gate a different matcher and a 9-package roster missing the 2 plugins — cost the v0.8.5 cut 3 enrichment PRs and 3 tag rewrites after "coverage clean" at cut time; it was deleted 2026-07-19, so `check-release.mjs --pending-version` is now the only pre-tag coverage check.)
285
290
 
286
291
  ## §Step 6 — Tag
287
292
 
@@ -289,7 +294,7 @@ Log the planned tag list (evidence table above), then tag **at `main`'s post-mer
289
294
 
290
295
  ```bash
291
296
  node "${CLAUDE_PLUGIN_ROOT}/skills/adia-release/scripts/tag-lockstep.mjs" \
292
- --version X.Y.Z # umbrella vX.Y.Z + 11 <pkg>-vX.Y.Z (incl. the 2 plugins, 2026-07-15 policy)
297
+ --version X.Y.Z # umbrella vX.Y.Z + 13 <pkg>-vX.Y.Z (11 npm + 2 plugins, 2026-07-15 policy)
293
298
  ```
294
299
 
295
300
  ## §Step 7 — F-N1 release trip-wire
@@ -330,8 +335,9 @@ node "${CLAUDE_PLUGIN_ROOT}/skills/adia-release/scripts/dispatch-publish.mjs" \
330
335
  Wait for the workflows to settle, then verify against the **registry**, never the workflow's green check:
331
336
 
332
337
  ```bash
333
- for pkg in web-components web-modules llm a2ui-runtime a2ui-compose a2ui-corpus \
334
- a2ui-mcp a2ui-retrieval a2ui-validator adia-ui-factory adia-ui-forge; do
338
+ for pkg in web-components web-modules llm agent persona a2ui-runtime a2ui-compose \
339
+ a2ui-corpus a2ui-mcp a2ui-retrieval a2ui-validator adia-ui-factory \
340
+ adia-ui-forge; do
335
341
  echo -n "$pkg: "; npm view "@adia-ai/$pkg" version
336
342
  done
337
343
  npm view @adia-ai/web-components dist-tags.latest # must equal X.Y.Z
@@ -342,7 +348,9 @@ Zero workflows fired after a tag push → [`recovery-paths.md`](recovery-paths.m
342
348
  ## §Step 10 — GH releases + site deploy dispatch
343
349
 
344
350
  ```bash
345
- for pkg in <the 9>; do
351
+ for pkg in web-components web-modules llm agent persona a2ui-runtime a2ui-compose \
352
+ a2ui-corpus a2ui-mcp a2ui-retrieval a2ui-validator adia-ui-factory \
353
+ adia-ui-forge; do
346
354
  gh release create "$pkg-vX.Y.Z" --title "@adia-ai/$pkg vX.Y.Z" --notes-file <body>.md
347
355
  done
348
356
  # Site deploy goes through the pipeline, never a raw rsync. The dispatch is
@@ -14,7 +14,7 @@ Row layout per gate: **What** · **Typical failure** · **Recovery**.
14
14
 
15
15
  ### `npm run check:lockstep`
16
16
 
17
- - **What:** all 11 `@adia-ai/*` packages declare the same `version`; internal `@adia-ai/*` dep ranges match policy (`^X.Y.0` during PATCH cycles, bumped at MINOR).
17
+ - **What:** all 13 lockstep `@adia-ai/*` packages declare the same `version` (the class-B `adia-plugins` package is excluded — `lockstep: false`, `scripts/package-paths.mjs`); internal `@adia-ai/*` dep ranges match policy (`^X.Y.0` during PATCH cycles, bumped at MINOR).
18
18
  - **Typical failure:** one package forgot to bump; a peer edited an internal range mid-PATCH; a `^0.0.x` range slipped in.
19
19
  - **Recovery:** version drift → `` `${CLAUDE_PLUGIN_ROOT}/skills/adia-release/scripts/bump.mjs` ``; range drift → `npm run check:lockstep:fix` auto-aligns, then re-run.
20
20
  - **Why `^0.0.x` is forbidden:** npm pre-1.0 semver only widens the caret when major+minor aren't both zero — `^0.0.6` resolves to `>=0.0.6 <0.0.7`, locked to exactly 0.0.6. An internal dep pinned that way silently installs a *stale* sibling on every fresh `npm i` (this shipped a real ~4-day-latent bug before the lockstep policy). The `^X.Y.0` floor (Y≥1) widens correctly across patches; trust the gate, don't reason about caret semantics by hand. Moot at 1.0.0.
@@ -153,6 +153,8 @@ The two `audit:*` probes are soft-fail pre-cut (exit-1 only on criticals); findi
153
153
  - **What:** the vitest suite (~1000+ tests).
154
154
  - **Stale-test failure:** the test asserts a behavior a peer deliberately changed (CHANGELOG-documented) without updating the assertion. Tell it apart by reading assertion vs CHANGELOG vs source: if the code matches the CHANGELOG's described behavior, the test is stale — update the assertion (see [`recovery-paths.md`](recovery-paths.md) §Scenario 5). Real regression → fix the regression.
155
155
  - **Parallel-contention flake:** heavy web-modules composite suites can fail under full parallelism on a loaded machine (`signals: drain loop exceeded 100 iterations`). Load-dependent, not a code defect. Sequential is the source of truth, and **the roster gate now runs it directly** (`npm run test:unit:serial` = `vitest run --no-file-parallelism`, gate 4 since 2026-07-19) — a parallel `test:unit` flake outside the roster still isn't a blocker, and don't raise the drain guard.
156
+ - **Exit 143 (SIGTERM):** the vitest process was KILLED (resource pressure / a stray terminator), not a test failure — re-run the gate directly (`npx vitest run --no-file-parallelism`) before diagnosing anything; a clean re-run means transient, proceed (v0.8.34 handoff hit this).
157
+ - **First-time-at-cut failures are REAL (v0.8.34):** this serial suite runs tests PR CI never does — `exit-gate.corpus` (dialect-catalog conformance over real harvested chunks) surfaces yaml-schema drift (e.g. an array prop missing `items.type`, the command/combobox `DynamicStringList` mis-map) only HERE, potentially weeks after the yaml edit merged green. Treat such a failure as a genuine latent defect to root-cause at the yaml SoT, never as release-blocking noise. (Structural fix — promoting the corpus batch into PR CI — tracked as a follow-up.)
156
158
 
157
159
  ### `npm run eval:diff -- --engine zettel`
158
160
 
@@ -202,7 +204,7 @@ npm run check:demo-shells
202
204
 
203
205
  Add `verify:corpus` + `check:embeddings-fresh` if chunks were touched; `check:lightningcss-build` if CSS was touched; F-N1 if unpushed release tags exist.
204
206
 
205
- **Full pre-cut sweep** — the 24-gate roster in [`cut-procedure.md`](cut-procedure.md) §Step 3, sourced from `` `${CLAUDE_PLUGIN_ROOT}/skills/adia-release/scripts/gate-roster.mjs` `` (the ONE list; `release-pack.mjs` imports and runs it in full — a subset run is impossible without editing that file). ~90s wall time.
207
+ **Full pre-cut sweep** — the 28-gate roster in [`cut-procedure.md`](cut-procedure.md) §Step 3, sourced from `` `${CLAUDE_PLUGIN_ROOT}/skills/adia-release/scripts/gate-roster.mjs` `` (the ONE list; `release-pack.mjs` imports and runs it in full — a subset run is impossible without editing that file). ~90s wall time.
206
208
 
207
209
  **Omnibus** — `npm run check` invokes everything. Heavy; use when re-baselining a stale checkout.
208
210
 
@@ -2,12 +2,16 @@
2
2
 
3
3
  > Load when releasing a package versioned **independently** of the lockstep set —
4
4
  > excluded from the lockstep gate, no umbrella tag, no demo-site deploy.
5
- > **Class B currently has NO members** (2026-07-15 policy change, operator
6
- > ruling): the Claude Code plugins under `packages/plugins/*` joined the
7
- > lockstep after class-B independence let npm drift a full harness-reset
8
- > behind the repo (npm served forge 0.1.3/factory 0.2.4 pre-reset content
9
- > while the rewritten estate sat unpublished at a regressed 0.1.0). This
10
- > procedure is retained for future genuinely-independent packages. The
5
+ > **Class B's current member is `@adia-ai/adia-plugins`** (gh#1133/gh#1160,
6
+ > ADR-0045) the npm-sourced marketplace manifest package, marked
7
+ > `lockstep: false` on the roster (`scripts/package-paths.mjs`). It's the
8
+ > first member since the 2026-07-15 policy change (operator ruling) that
9
+ > emptied the class: the Claude Code plugins under `packages/plugins/*`
10
+ > joined the lockstep after class-B independence let npm drift a full
11
+ > harness-reset behind the repo (npm served forge 0.1.3/factory 0.2.4
12
+ > pre-reset content while the rewritten estate sat unpublished at a
13
+ > regressed 0.1.0). This procedure covers both onboarding a NEW class-B
14
+ > package and a routine cut of an existing one (adia-plugins). The
11
15
  > lockstep cut is [`cut-procedure.md`](cut-procedure.md).
12
16
 
13
17
  ## §Two release classes in one monorepo
@@ -15,7 +19,7 @@
15
19
  | | Class A — lockstep cut | Class B — independent package |
16
20
  | --- | --- | --- |
17
21
  | Versioning | whole set bumps together | own version line per package |
18
- | Lockstep gate | enforced (`check:lockstep` 11/11) | **excluded** — a class-B package is not listed in `check-lockstep.mjs`'s `PACKAGES` (none currently exist; `packages/plugins/*` joined class A 2026-07-15) |
22
+ | Lockstep gate | enforced (`check:lockstep` 13/13) | **excluded** — a class-B package is not listed in `check-lockstep.mjs`'s `PACKAGES` (`adia-plugins`, the current member, is filtered out via `lockstep: false` `scripts/package-paths.mjs`) |
19
23
  | Umbrella tag | `vX.Y.Z` + per-package | **none** — per-package tags only (versions differ) |
20
24
  | `dist-tag` ordering | cross-package publish order matters | N/A |
21
25
  | Demo-site deploy | yes | **none** |
@@ -39,11 +43,11 @@ Substrate fixes (release scripts / lockfile / CHANGELOG), landed once as a norma
39
43
  4. **Tag at the published commit** — `git tag <pkg>-vX.Y.Z` per package; versions differ; no umbrella. Dry-run the trip-wire over the tags (`node scripts/release/check-release.mjs <tags>`) before pushing. If the shared working tree is on a peer's branch, push tags from a throwaway worktree on `origin/main`.
40
44
  5. **Push the tags** → the per-package workflows run `npm ci → verify:plugins → npm publish --access public`.
41
45
  6. **Verify against npm, not the workflow:** `npm view @adia-ai/<pkg> version` returns the new independent version for each package. Watch runs (`gh run watch <id> --exit-status`), but the registry is the source of truth.
42
- 7. **Marketplace cut-over** (if a plugin marketplace repo exists): point the manifest's per-plugin `source` at npm (`{ "source": "npm", "package": "@adia-ai/<pkg>" }`), `git rm -r` vendored copies, validate the JSON, commit, push. Consumers then `/plugin marketplace add <org>/<repo>` `/plugin install <pkg>@<repo>`.
46
+ 7. **Marketplace cut-over** per ADR-0045 the distribution channel is the npm-sourced manifest package `@adia-ai/adia-plugins` (no public git marketplace repo that shape was considered and rejected). Bump/publish the manifest package so its `marketplace.json` entries reference the new plugin versions; consumers add the marketplace from npm, never from a git repo.
43
47
 
44
48
  ## §Verify target
45
49
 
46
- The published package(s) on the **npm registry** at their independent versions, plus (if a marketplace exists) its manifest referencing npm. NOT "all 11 + the demo site" — that's class A.
50
+ The published package(s) on the **npm registry** at their independent versions, plus the npm-published `@adia-ai/adia-plugins` manifest referencing the new versions (ADR-0045). NOT "all 13 lockstep packages + the demo site" — that's class A.
47
51
 
48
52
  ## §Gotchas (from the first independent cut, factory v0.2.2 + forge v0.1.2)
49
53
 
@@ -17,7 +17,7 @@
17
17
 
18
18
  1. Verify the skip: `npm view <pkg> versions --json | tail` (no X+1 on npm) + `git tag --list 'vX.Y.*'` (no tags). Run pre-flight at the peer's commit to confirm it's shippable.
19
19
  2. Correct the version via a **new commit on top** (not amend — the peer's commit stays for history).
20
- 3. Sweep every occurrence of the wrong version: 9 × package.json, 9 × CHANGELOG (headers + body refs), any doc/CSS-comment refs, then regenerate the lockfile. Preserve filename references that intentionally encode the original label.
20
+ 3. Sweep every occurrence of the wrong version: 13 × package.json, 13 × CHANGELOG (headers + body refs — the lockstep roster, `scripts/package-paths.mjs`), any doc/CSS-comment refs, then regenerate the lockfile. Preserve filename references that intentionally encode the original label.
21
21
  4. Commit as `fix(release): correct vX.Y.Z+1 version-skip → vX.Y.Z` documenting the discovery, then resume [`cut-procedure.md`](cut-procedure.md) at Step 5.
22
22
 
23
23
  ## §Scenario 2 — Batch push
@@ -63,7 +63,7 @@
63
63
 
64
64
  ## §Scenario 7 — Tags pushed, ZERO publish workflows fired
65
65
 
66
- **Shape:** all 12 tags exist on origin, but no `publish-<pkg>.yml` run exists for them; npm latest unchanged; nothing errored.
66
+ **Shape:** all 14 tags exist on origin (umbrella + one per lockstep-roster package — `scripts/package-paths.mjs`), but no `publish-<pkg>.yml` run exists for them; npm latest unchanged; nothing errored.
67
67
 
68
68
  **Root cause:** pushing many tags in **one** `git push` fires a single batched create event that GitHub Actions routinely drops. Re-pushing is a no-op (the tags already exist remotely).
69
69
 
@@ -20,17 +20,21 @@
20
20
  import fs from 'node:fs';
21
21
  import path from 'node:path';
22
22
  import process from 'node:process';
23
- import { PACKAGE_DIRS, PACKAGE_ROSTER } from './package-paths.mjs';
23
+ import { PACKAGE_ROSTER } from './package-paths.mjs';
24
24
 
25
25
  // Single-sourced roster (H3, package-paths.mjs). Each plugin also carries a
26
26
  // .claude-plugin/plugin.json whose version MUST move with package.json —
27
27
  // plugin.json is the /plugin-update cache key (verify:plugins gates the sync).
28
- const PACKAGES = PACKAGE_DIRS;
28
+ // Filtered to `lockstep !== false`: a class-B package (gh#1133's
29
+ // adia-plugins) bumps on its own independent version line, never via this
30
+ // lockstep --from/--to sweep. See package-paths.mjs's `lockstep` field note.
31
+ const LOCKSTEP_ROSTER = PACKAGE_ROSTER.filter((p) => p.lockstep !== false);
32
+ const PACKAGES = LOCKSTEP_ROSTER.map((p) => p.dir);
29
33
 
30
34
  // Sibling manifests bumped in the same pass as package.json (path relative to
31
35
  // the package dir) — derived from the roster's plugin flag.
32
36
  const SIBLING_MANIFESTS = Object.fromEntries(
33
- PACKAGE_ROSTER.filter((p) => p.plugin).map((p) => [p.dir, '.claude-plugin/plugin.json']),
37
+ LOCKSTEP_ROSTER.filter((p) => p.plugin).map((p) => [p.dir, '.claude-plugin/plugin.json']),
34
38
  );
35
39
 
36
40
  // Cross-package EXACT pins bumped in the same pass — a different shape from a
@@ -20,14 +20,17 @@
20
20
  import { execFileSync } from 'node:child_process';
21
21
  import process from 'node:process';
22
22
  import { assertMonorepoRoot } from './assert-monorepo-root.mjs';
23
- import { PACKAGE_NAMES } from './package-paths.mjs';
23
+ import { PACKAGE_ROSTER } from './package-paths.mjs';
24
24
 
25
25
  // Instance data — fork-configurable. The npm scope the lockstep roster publishes under.
26
26
  // Default preserves @adia-ai behavior; override via --scope or $ADIA_NPM_SCOPE.
27
27
  const DEFAULT_SCOPE = '@adia-ai';
28
28
 
29
29
  // Single-sourced roster (H3, package-paths.mjs); order = dispatch order.
30
- const PACKAGES = PACKAGE_NAMES;
30
+ // Filtered to `lockstep !== false`: a class-B package (gh#1133's
31
+ // adia-plugins) publishes via its own manual cut (independent-package-
32
+ // release.md), never this lockstep dispatch loop.
33
+ const PACKAGES = PACKAGE_ROSTER.filter((p) => p.lockstep !== false).map((p) => p.name);
31
34
 
32
35
  function parseArgs(argv) {
33
36
  const args = {
@@ -64,6 +64,35 @@ export const GATE_ROSTER = [
64
64
  // v0.7.13-retro hygiene checker — README CDN pins / "Current version"
65
65
  // claims vs the cut version; branch/worktree limbo prints as warns (H2).
66
66
  { n: 24, cmd: 'node scripts/release/check-cut-hygiene.mjs', what: 'README CDN-pin + version currency', versionArg: true },
67
+ // gh#1132 (factory-dx-ws1-boot SPEC, REQ-04): npm-packs BOTH
68
+ // @adia-ai/web-components and @adia-ai/web-modules and resolves every
69
+ // specifier the scaffold templates emit against the extracted tarballs'
70
+ // real exports maps — the same blast radius gh#296's
71
+ // exports-wildcard-resolution.mjs gate covers, applied to the factory
72
+ // plugin's own templates instead of a consumer's hand-written import.
73
+ { n: 25, cmd: 'python3 packages/plugins/adia-ui-factory/scripts/adia-scaffold selftest', what: 'scaffold specifiers resolve in packed tarballs' },
74
+ // gh#1137 (factory-dx-ws6-measurement SPEC, REQ-05) — FLAGGED FOR
75
+ // CONFIRMATION: this entry was authored by the WS-6 build without a live
76
+ // adia-release sign-off (no operator was available mid-build to confirm
77
+ // the exact freshness window / regression semantics). Asserts a
78
+ // qa/dx/sweeps/ record exists dated within this cut's window AND its
79
+ // verdict is not a regression-RED (a plain floors<=1 baseline RED does
80
+ // NOT block a cut — only a floor that DROPPED since the prior sweep
81
+ // does). CONFIRMED by adia-release on the first live cut (v0.8.35,
82
+ // 2026-08-13): semantics hold, with one correction — the previous-cut
83
+ // comparison excludes the in-flight target version (self-reference fired
84
+ // on the handoff re-run, since the promoted heading was already on main).
85
+ { n: 26, cmd: 'node scripts/release/check-dx-sweep-freshness.mjs', targetVersionArg: true, what: 'DX sweep fresh + not regression-RED' },
86
+ // gh#1135 (WS-4 SPEC REQ-06): a fresh free-form eval run feeds the health
87
+ // record gate 28 writes — without it, "latest" under evals/mcp/runs/ could
88
+ // be a stale leftover from an earlier ad-hoc run, not this cut's own state.
89
+ { n: 27, cmd: 'npm run eval:diff -- --engine free-form', what: 'fresh free-form eval run (evals/health input)' },
90
+ // REQ-06: commits evals/health/<version>.json — the version this record
91
+ // names is the CUT's target version from the start, never the
92
+ // pre/post-bump `hygieneVersion` gate 24 needs (a claim-validation
93
+ // concern this is not) — hence `targetVersionArg` rather than
94
+ // `versionArg`, a distinct flag release-pack.mjs's step3PreFlight reads.
95
+ { n: 28, cmd: 'node scripts/release/write-eval-health.mjs', what: 'evals/health/<version>.json committed, AC-01/AC-02 run for real', targetVersionArg: true },
67
96
  ];
68
97
 
69
98
  // -- CLI ------------------------------------------------------------------
@@ -42,24 +42,52 @@ import path from 'node:path';
42
42
  // Order is the canonical publish/tag order (release-pack's historical order);
43
43
  // consumers needing a different ordering (check-lockstep sorts by path for
44
44
  // stable output) re-sort locally.
45
+ //
46
+ // `lockstep` (added gh#1133, onboarding the first class-B member since the
47
+ // 2026-07-15 policy emptied that class — independent-package-release.md):
48
+ // `true` (or omitted — every pre-2026-08 entry defaults true below) means the
49
+ // package rides the coordinated cut — version-match/caret gate
50
+ // (check-lockstep.mjs), the umbrella tag, the --pending-version sweep, bump.mjs's
51
+ // --from/--to pass, the dispatch-publish loop, and generate-release-notes.mjs's
52
+ // notes. `false` means class B: its own version line, its own manual tag +
53
+ // publish (independent-package-release.md), NEVER the umbrella tag or a
54
+ // lockstep bump/dispatch/notes pass. The ONE exception: the F-N1 release
55
+ // trip-wire (check-release.mjs's per-tag `checkTag` lookup) reads the FULL
56
+ // roster unfiltered — every pushed tag, lockstep or not, still needs its
57
+ // package.json/CHANGELOG validated, so it must recognize a class-B tag too.
58
+ // Every lockstep-scoped consumer must filter `p.lockstep !== false` itself
59
+ // (this module intentionally does NOT pre-filter PACKAGE_NAMES/PACKAGE_DIRS —
60
+ // they stay full-roster projections so a consumer's own filter is visible at
61
+ // its point of use, not hidden upstream).
45
62
  export const PACKAGE_ROSTER = [
46
- { name: 'web-components', dir: 'packages/web-components' },
47
- { name: 'web-modules', dir: 'packages/web-modules' },
48
- { name: 'llm', dir: 'packages/llm' },
49
- { name: 'agent', dir: 'packages/agent' },
50
- { name: 'persona', dir: 'packages/persona' },
51
- { name: 'a2ui-runtime', dir: 'packages/a2ui/runtime' },
52
- { name: 'a2ui-compose', dir: 'packages/a2ui/compose' },
53
- { name: 'a2ui-corpus', dir: 'packages/a2ui/corpus' },
54
- { name: 'a2ui-mcp', dir: 'packages/a2ui/mcp' },
55
- { name: 'a2ui-retrieval', dir: 'packages/a2ui/retrieval' },
56
- { name: 'a2ui-validator', dir: 'packages/a2ui/validator' },
63
+ { name: 'web-components', dir: 'packages/web-components', lockstep: true },
64
+ { name: 'web-modules', dir: 'packages/web-modules', lockstep: true },
65
+ { name: 'llm', dir: 'packages/llm', lockstep: true },
66
+ { name: 'agent', dir: 'packages/agent', lockstep: true },
67
+ { name: 'persona', dir: 'packages/persona', lockstep: true },
68
+ { name: 'a2ui-runtime', dir: 'packages/a2ui/runtime', lockstep: true },
69
+ { name: 'a2ui-compose', dir: 'packages/a2ui/compose', lockstep: true },
70
+ { name: 'a2ui-corpus', dir: 'packages/a2ui/corpus', lockstep: true },
71
+ { name: 'a2ui-mcp', dir: 'packages/a2ui/mcp', lockstep: true },
72
+ { name: 'a2ui-retrieval', dir: 'packages/a2ui/retrieval', lockstep: true },
73
+ { name: 'a2ui-validator', dir: 'packages/a2ui/validator', lockstep: true },
57
74
  // Plugins joined the lockstep 2026-07-15 (check-lockstep.mjs POLICY note);
58
75
  // each carries a .claude-plugin/plugin.json that bumps with package.json.
59
- { name: 'adia-ui-factory', dir: 'packages/plugins/adia-ui-factory', plugin: true },
60
- { name: 'adia-ui-forge', dir: 'packages/plugins/adia-ui-forge', plugin: true },
76
+ { name: 'adia-ui-factory', dir: 'packages/plugins/adia-ui-factory', lockstep: true, plugin: true },
77
+ { name: 'adia-ui-forge', dir: 'packages/plugins/adia-ui-forge', lockstep: true, plugin: true },
78
+ // adia-plugins is class B (gh#1133, ADR-0045/spec-factory-dx-ws2): the npm-
79
+ // sourced marketplace manifest package. No lockstep content (a roster of
80
+ // npm sources, no code) and REQ-03's unpinned sources mean it republishes
81
+ // only on its own roster change — cuts independently, own version line,
82
+ // never the umbrella tag. Not a Claude Code plugin itself (no `plugin.json`
83
+ // sidecar), so no `plugin: true`.
84
+ { name: 'adia-plugins', dir: 'packages/plugins/adia-plugins', lockstep: false },
61
85
  ];
62
86
 
87
+ // Full-roster projections — every registered package, lockstep or not. A
88
+ // lockstep-scoped consumer (bump, tag-lockstep, dispatch-publish, the
89
+ // version-match gate, the notes generator, the docs gate) must filter
90
+ // `.lockstep !== false` itself; see the `lockstep` field note above.
63
91
  export const PACKAGE_NAMES = PACKAGE_ROSTER.map((p) => p.name);
64
92
  export const PACKAGE_DIRS = PACKAGE_ROSTER.map((p) => p.dir);
65
93