@adia-ai/adia-ui-forge 0.8.3 → 0.8.5
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/CHANGELOG.md +20 -0
- package/agents/framework-verifier.md +13 -5
- package/bin/release-pretag-docs-gate +226 -0
- package/hooks/hooks.json +10 -0
- package/package.json +1 -1
- package/skills/adia-a2ui/SKILL.md +10 -2
- package/skills/adia-a2ui/references/pipeline-overview.md +1 -1
- package/skills/adia-author/SKILL.md +1 -0
- package/skills/adia-author/references/anti-patterns.md +7 -7
- package/skills/adia-author/references/api-contract.md +9 -9
- package/skills/adia-author/references/authoring-cycle.md +6 -6
- package/skills/adia-author/references/code-style.md +4 -4
- package/skills/adia-author/references/lifecycle-patterns.md +8 -8
- package/skills/adia-author/references/token-contract.md +5 -5
- package/skills/adia-author/references/worked-example.md +16 -16
- package/skills/adia-author/references/yaml-contract.md +1 -1
- package/skills/adia-deploy/SKILL.md +44 -0
- package/skills/adia-deploy/evals/routing-corpus.json +146 -0
- package/skills/adia-deploy/references/deploy-playbooks.md +1 -1
- package/skills/adia-dogfood/SKILL.md +64 -11
- package/skills/adia-dogfood/evals/routing-corpus.json +146 -0
- package/skills/adia-gen-review/SKILL.md +55 -0
- package/skills/adia-gen-review/evals/routing-corpus.json +146 -0
- package/skills/adia-llm-internals/evals/routing-corpus.json +146 -0
- package/skills/adia-llm-internals/references/adapter-contract.md +1 -1
- package/skills/adia-llm-internals/references/add-a-provider.md +1 -0
- package/skills/adia-llm-internals/references/bridge-facade.md +1 -1
- package/skills/adia-llm-internals/references/browser-proxy-boundary.md +8 -6
- package/skills/adia-llm-internals/references/model-registry.md +1 -1
- package/skills/adia-release/SKILL.md +12 -5
- package/skills/adia-release/evals/evals.json +24 -0
- package/skills/adia-release/references/changelog-discipline.md +4 -4
- package/skills/adia-release/references/cut-procedure.md +45 -11
- package/skills/adia-release/references/gates-catalog.md +3 -3
- package/skills/adia-release/references/recovery-paths.md +4 -2
- package/skills/adia-release/scripts/bump.mjs +32 -1
- package/skills/adia-release/scripts/dispatch-publish.mjs +49 -11
- package/skills/adia-release/scripts/gate-roster.mjs +71 -0
- package/skills/adia-release/scripts/insert-stub.mjs +35 -1
- package/skills/adia-release/scripts/promote-unreleased.mjs +35 -1
- package/skills/adia-release/scripts/release-pack.mjs +284 -53
- package/skills/adia-release/scripts/tag-lockstep.mjs +29 -7
- package/skills/adia-ssr/SKILL.md +110 -0
- package/skills/adia-ssr/evals/audit-report.md +63 -0
- package/skills/adia-ssr/evals/routing-corpus.json +138 -0
- package/skills/adia-ssr/references/consumer-workarounds.md +63 -0
- package/skills/adia-ssr/references/failure-shapes.md +122 -0
- package/skills/adia-ssr/references/guard-patterns.md +142 -0
- package/skills/adia-ssr/references/status-ledger.md +35 -0
- package/skills/adia-ssr/references/test-without-linkedom.md +113 -0
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
|
|
10
10
|
Two entry variants, converging at Step 5:
|
|
11
11
|
|
|
12
|
-
- **Variant A — deploy handoff:** a peer pre-cut the release commit + CHANGELOG + bump + lockfile. Re-baseline, verify, then resume at Step 6 (tag).
|
|
12
|
+
- **Variant A — deploy handoff:** a peer pre-cut the release commit + CHANGELOG + bump + lockfile. Re-baseline, verify, run **Step 4e** (release docs + team notes — the peer's cut may not have generated them, and the pretag gate blocks Step 6 without them), then resume at Step 6 (tag).
|
|
13
13
|
- **Variant B — author from scratch:** source landed under `## [Unreleased]` with no bump. Do Step 4 (promotion + bump + lockfile), then the full tail.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
`` `${CLAUDE_PLUGIN_ROOT}/skills/adia-release/scripts/release-pack.mjs` `` (bundled) mechanizes the sequence in two phases per invariant 3 — the cut modes stop at the release commit (PR → CI → merge happens between), `--mode handoff` runs tag→publish→deploy from post-merge main — with the three operator checkpoints preserved; the steps below are the manual/diagnostic form.
|
|
16
16
|
|
|
17
17
|
| Step | Action | Mutates? |
|
|
18
18
|
| --- | --- | --- |
|
|
@@ -27,7 +27,7 @@ Two entry variants, converging at Step 5:
|
|
|
27
27
|
| 7 | F-N1 release trip-wire; enrich CHANGELOGs on warns | If warns |
|
|
28
28
|
| 8 | Push tags (one per push; `main` already merged) — **checkpoint 2 first** | Push |
|
|
29
29
|
| 9 | Dispatch publish workflows; wait; verify registry — **checkpoint 3 first** | Publish |
|
|
30
|
-
| 10 | GH releases + site deploy | Deploy |
|
|
30
|
+
| 10 | GH releases + site deploy — **checkpoint 4 first** | Deploy |
|
|
31
31
|
| 11 | Author release notes (default, not optional) | No |
|
|
32
32
|
|
|
33
33
|
---
|
|
@@ -96,9 +96,11 @@ npm run dogfood:status # 17 P0/P1 dogfood floor
|
|
|
96
96
|
|
|
97
97
|
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.
|
|
98
98
|
|
|
99
|
-
## §Step 4 — (Variant B) Promote, bump, lockfile
|
|
99
|
+
## §Step 4 — (Variant B, or ANY variant with uncommitted `[Unreleased]` content) Promote, bump, lockfile
|
|
100
100
|
|
|
101
|
-
**4a
|
|
101
|
+
**Run 4a whenever a hand-authored `## [Unreleased]` section is still sitting uncommitted, not only on a strict Variant B.** `release-pack.mjs --mode cut` (a peer's pre-staged content, not yet promoted) needs it exactly as much as `--mode from-scratch` does — the v0.8.4 near-miss was `--mode cut` skipping this step entirely because the doc (and the script) only associated promotion with "from scratch". Both modes now run it and both hard-fail before the bump if any of the 11 packages still carries non-empty `[Unreleased]` content afterward.
|
|
102
|
+
|
|
103
|
+
**4a. Promote** `## [Unreleased]` → `## [vX.Y.Z] — YYYY-MM-DD` per package (`` `${CLAUDE_PLUGIN_ROOT}/skills/adia-release/scripts/promote-unreleased.mjs` ``); author fresh blocks for changed-but-unlogged packages; stub the pure ride-alongs (`` `${CLAUDE_PLUGIN_ROOT}/skills/adia-release/scripts/insert-stub.mjs` ``). Classification recipe + shapes: [`changelog-discipline.md`](changelog-discipline.md).
|
|
102
104
|
|
|
103
105
|
**4b. Bump.** PATCH vs MINOR: **MINOR is reserved for API-surface breaks only** (removed/renamed prop, attribute, slot, event, token, or tag). Visible behavior changes, re-scalings, and opt-in features stay PATCH; a CHANGELOG bullet saying "(MINOR behavior change)" is prose, not a semver directive. Unqualified "bump version" = PATCH; don't round-trip to ask. `node "${CLAUDE_PLUGIN_ROOT}/skills/adia-release/scripts/bump.mjs" --from X.Y.Z-1 --to X.Y.Z`. On a MINOR cut, also bump the internal `@adia-ai/*` `^ranges` separately (bump.mjs touches `"version"` fields only) — and a MINOR cut owes a MIGRATION GUIDE section ([`migration-guide-authoring.md`](migration-guide-authoring.md)).
|
|
104
106
|
|
|
@@ -106,6 +108,23 @@ Any red → route via [`gates-catalog.md`](gates-catalog.md); fix at the source,
|
|
|
106
108
|
|
|
107
109
|
**4d.** `npm run check:lockstep` → `OK — all packages at X.Y.Z, all internal ranges at ^X.Y.0`.
|
|
108
110
|
|
|
111
|
+
**4e. Release docs + team notes** (gated, not optional): review the entry
|
|
112
|
+
files the release touches (root README/CHANGELOG, per-package READMEs —
|
|
113
|
+
content currency is YOUR judgment; the gate only proves presence), then
|
|
114
|
+
generate the team notes and land them IN the release commit:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
node scripts/release/generate-release-notes.mjs --version X.Y.Z --write # → docs/releases/vX.Y.Z.md
|
|
118
|
+
node scripts/release/check-release-docs.mjs --version X.Y.Z # must print OK
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`check-release-docs` is enforced twice downstream — the
|
|
122
|
+
`release-pretag-docs-gate` Claude Code hook (adia-forge plugin) denies
|
|
123
|
+
tag-creation/tag-push/publish-dispatch commands until it passes, and
|
|
124
|
+
`.githooks/pre-push` blocks release-tag pushes the same way (v0.8.4
|
|
125
|
+
shipped tarballs with unpromoted `[Unreleased]` CHANGELOG headers because
|
|
126
|
+
this class of check ran only after tagging).
|
|
127
|
+
|
|
109
128
|
## §Step 5 — Stage and commit (on a release branch)
|
|
110
129
|
|
|
111
130
|
The release commit lands via PR, never a direct push to `main` (repo
|
|
@@ -118,7 +137,8 @@ allowlist (never `git add -A`; if peers may have pre-staged files,
|
|
|
118
137
|
git -C "$REPO" checkout -b "release/vX.Y.Z"
|
|
119
138
|
git -C "$REPO" reset HEAD >/dev/null 2>&1
|
|
120
139
|
git -C "$REPO" add package-lock.json packages/*/package.json packages/*/CHANGELOG.md \
|
|
121
|
-
packages/a2ui/*/package.json packages/a2ui/*/CHANGELOG.md
|
|
140
|
+
packages/a2ui/*/package.json packages/a2ui/*/CHANGELOG.md \
|
|
141
|
+
docs/releases/vX.Y.Z.md # + in-scope source + Step-3.0 regen outputs
|
|
122
142
|
git -C "$REPO" diff --cached --stat | tail -3 # count must match the allowlist
|
|
123
143
|
```
|
|
124
144
|
|
|
@@ -152,9 +172,22 @@ post-merge HEAD, so anything merged before tagging ships in the tarball.
|
|
|
152
172
|
If unrelated PRs merged between yours and the tag step, that is fine: tag
|
|
153
173
|
at HEAD is the invariant, and the window closes at the tag.
|
|
154
174
|
|
|
175
|
+
## §Operator checkpoints — evidence-first, not a bare y/N
|
|
176
|
+
|
|
177
|
+
Four checkpoints total (corrected on PR #289 review — this section previously said "three", missing the Step-10 deploy-dispatch confirm that already existed in the script). Each prints its evidence block BEFORE the prompt, never a bare confirm (H5, forge-campaign gh#268 audit — the prior shape asked "proceed? [y/N]" with nothing to judge it against). The prompt itself is a plain two-way gate, honestly: **yes proceeds** (the mutating step for that checkpoint runs); **anything else aborts** — the script exits cleanly having done nothing past that point. There is no in-script "enrich" path (a CodeRabbit finding on PR #289 caught this doc previously over-promising a three-way go/enrich/abort choice the code never implemented) — to enrich, abort, fix the thing the evidence surfaced (a CHANGELOG entry, a version mismatch), then re-run from that checkpoint; the evidence re-prints fresh against the fixed state.
|
|
178
|
+
|
|
179
|
+
| Checkpoint | Evidence shown BEFORE the prompt | Why this evidence (not F-N1 at cp1) |
|
|
180
|
+
| --- | --- | --- |
|
|
181
|
+
| 1 — before tagging | The planned tag list (umbrella + 11 per-package) | F-N1 inspects tag commits — it has nothing to check until Step 6 creates them |
|
|
182
|
+
| 2 — before pushing | The Step 7 F-N1 output (now real, against the just-created tags) + the tag list again | This is the FIRST point F-N1 evidence exists — showing it here, not at cp1, is why the step order (tag → F-N1 → cp2) is fixed |
|
|
183
|
+
| 3 — before publishing | The current registry snapshot (per-package versions + `dist-tags.latest`) | Publish ordering is judged against what's LIVE right now, not an assumption |
|
|
184
|
+
| 4 — before deploying the site | The `deploy-site.yml` dispatch itself as evidence (never a raw rsync) | Deploy is its own irreversible, outward-facing action distinct from publish |
|
|
185
|
+
|
|
186
|
+
`release-pack.mjs` implements this: `checkpoint(label, evidence, autoConfirmed)` always prints `evidence` first, then `[CHECKPOINT] <label> — proceed? [y/N]`; `--yes` auto-confirms checkpoint 1 ONLY, `--push` gates checkpoint 2, `--publish` gates checkpoints 3 AND 4 (a single `--yes` used to flatten all of them — the defect this replaced). **F-N1 errors (not warns) hard-stop before checkpoint 2 even appears — no flag, including `--push`, skips this.** A cosmetic F-N1 warn does not hard-stop, but it also blocks `--push` from auto-confirming checkpoint 2 — only the interactive prompt may proceed past a warn (never past an error, which already exited).
|
|
187
|
+
|
|
155
188
|
## §Step 6 — Tag ← operator checkpoint 1 (before tagging)
|
|
156
189
|
|
|
157
|
-
Show the operator the planned tag list
|
|
190
|
+
Show the operator the planned tag list; proceed only on their go (F-N1 can't run yet — see the table above). Then tag **at `main`'s post-merge HEAD** (post-bump fixes belong in the tarball; the window's last merge is the tag point — exception: batch push tags each version at its own release-merge SHA):
|
|
158
191
|
|
|
159
192
|
```bash
|
|
160
193
|
node "${CLAUDE_PLUGIN_ROOT}/skills/adia-release/scripts/tag-lockstep.mjs" \
|
|
@@ -172,7 +205,8 @@ Per-package tags must be `✓ clean`; the umbrella-tag error is expected noise.
|
|
|
172
205
|
## §Step 8 — Push tags ← operator checkpoint 2 (before pushing tags)
|
|
173
206
|
|
|
174
207
|
`main` is already on the remote (the release PR merged in Step 5.7) —
|
|
175
|
-
only the tags push here. Show the tag list
|
|
208
|
+
only the tags push here. Show the Step 7 F-N1 results + the tag list (the
|
|
209
|
+
evidence block for this checkpoint — see the table above) + confirm
|
|
176
210
|
`git rev-list --count origin/main..HEAD` is 0 (a non-zero count means
|
|
177
211
|
local commits bypassed the PR flow — stop and route them through a PR
|
|
178
212
|
first); on go:
|
|
@@ -189,7 +223,7 @@ git -C "$REPO" push origin vX.Y.Z # umbrella last; triggers nothing
|
|
|
189
223
|
|
|
190
224
|
## §Step 9 — Publish ← operator checkpoint 3 (before publishing)
|
|
191
225
|
|
|
192
|
-
For batch pushes, confirm ordering with the operator: **oldest version publishes and settles first** — `npm dist-tag latest` is set by publish order. Then:
|
|
226
|
+
Show the current registry snapshot (per-package versions + `dist-tags.latest`) as this checkpoint's evidence. For batch pushes, confirm ordering with the operator against that snapshot: **oldest version publishes and settles first** — `npm dist-tag latest` is set by publish order. Then:
|
|
193
227
|
|
|
194
228
|
```bash
|
|
195
229
|
node "${CLAUDE_PLUGIN_ROOT}/skills/adia-release/scripts/dispatch-publish.mjs" \
|
|
@@ -208,7 +242,7 @@ npm view @adia-ai/web-components dist-tags.latest # must equal X.Y.Z
|
|
|
208
242
|
|
|
209
243
|
Zero workflows fired after a tag push → [`recovery-paths.md`](recovery-paths.md) §Scenario 7.
|
|
210
244
|
|
|
211
|
-
## §Step 10 — GH releases + site deploy
|
|
245
|
+
## §Step 10 — GH releases + site deploy ← operator checkpoint 4 (before deploying)
|
|
212
246
|
|
|
213
247
|
```bash
|
|
214
248
|
for pkg in <the 9>; do
|
|
@@ -220,7 +254,7 @@ gh workflow run "Deploy site (ui-kit.exe.xyz)" --repo adiahealth/gen-ui-kit --re
|
|
|
220
254
|
|
|
221
255
|
Deploy discipline (the release tenant of the demo-site host; VM/service ops belong to the deploy skill, `adia-deploy`):
|
|
222
256
|
|
|
223
|
-
- `deploy-site.yml` owns build → pre-flight verify → snapshot → rsync → post-deploy verify → auto-rollback; it builds from `main`, so the release commit must already be merged (§Step 5.7 guarantees this). A raw local rsync bypasses every one of those gates.
|
|
257
|
+
- `deploy-site.yml` owns build → pre-flight verify → snapshot → rsync → post-deploy verify → auto-rollback; it builds from `main`, so the release commit must already be merged (§Step 5.7 guarantees this). A raw local rsync bypasses every one of those gates — `release-pack.mjs` dispatches the workflow (never rsyncs directly) behind its own confirm (H1, forge-campaign gh#268 audit: the script had drifted from this already-documented procedure).
|
|
224
258
|
- **Verify deployed FILES, never SPA routes.** The docs site returns HTTP 200 + the same ~5 KB shell for *every* path; an unmatched route renders blank with no error. Curl a content file and grep for real bytes:
|
|
225
259
|
|
|
226
260
|
```bash
|
|
@@ -16,7 +16,7 @@ Row layout per gate: **What** · **Typical failure** · **Recovery**.
|
|
|
16
16
|
|
|
17
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).
|
|
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
|
-
- **Recovery:** version drift →
|
|
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.
|
|
21
21
|
|
|
22
22
|
### `node scripts/release/check-release.mjs --all-pending` (F-N1, the release trip-wire)
|
|
@@ -24,7 +24,7 @@ Row layout per gate: **What** · **Typical failure** · **Recovery**.
|
|
|
24
24
|
- **What:** for every unpushed tag, verifies CHANGELOG coverage of the diff between this tag and the package's previous tag.
|
|
25
25
|
- **Cosmetic failure:** "diff `packages/<pkg>/components/` touched but CHANGELOG `[X.Y.Z]` doesn't mention 'components'" — the entry names the component (`table-ui`) but not the literal path keyword. Regex miss, change IS documented.
|
|
26
26
|
- **Real failure:** a touched directory has NO matching CHANGELOG entry at all — the cycle missed documenting a change.
|
|
27
|
-
- **Recovery:** cosmetic → add the path keyword to a relevant entry (`table.yaml` → `components/table/table.yaml`); real → author the missing entry. Either way:
|
|
27
|
+
- **Recovery:** cosmetic → add the path keyword to a relevant entry (`table.yaml` → `components/table/table.yaml`); real → author the missing entry. Either way: land it as a new commit through the PR flow (the release commit is already merged — `--amend` is not possible post-invariant-3), delete + re-create the tags at the new post-merge SHA, re-run. Full discipline: [`changelog-discipline.md`](changelog-discipline.md) §F-N1 enrichment.
|
|
28
28
|
- The umbrella tag's "doesn't match `<pkg>-v<version>`" **error is expected — ignore it**; only per-package warns need action.
|
|
29
29
|
|
|
30
30
|
### `npm run check:changelog-coverage`
|
|
@@ -207,7 +207,7 @@ npm run check:demo-shells
|
|
|
207
207
|
|
|
208
208
|
Add `verify:corpus` + `check:embeddings-fresh` if chunks were touched; `check:lightningcss-build` if CSS was touched; F-N1 if unpushed release tags exist.
|
|
209
209
|
|
|
210
|
-
**Full pre-cut sweep** — the 17-gate roster in [`cut-procedure.md`](cut-procedure.md) §Step 3. ~90s wall time.
|
|
210
|
+
**Full pre-cut sweep** — the 17-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.
|
|
211
211
|
|
|
212
212
|
**Omnibus** — `npm run check` invokes everything. Heavy; use when re-baselining a stale checkout.
|
|
213
213
|
|
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
|
|
25
25
|
**Shape:** multiple unpushed `release(*): vX.Y.Z` commits since the last published tag; operator asks to "publish what's accumulated."
|
|
26
26
|
|
|
27
|
+
`release-pack.mjs --mode batch` hard-rejects and points here rather than attempting this — each version tags at its OWN release-commit SHA (not `main`'s single post-merge HEAD) and publish order must be enforced across versions, which the single-version orchestrator has no model for. This IS the manual procedure; there is no mechanized alternative.
|
|
28
|
+
|
|
27
29
|
**Resolution:**
|
|
28
30
|
|
|
29
31
|
1. Order the release commits oldest → newest.
|
|
@@ -36,7 +38,7 @@
|
|
|
36
38
|
|
|
37
39
|
**Shape:** source + CHANGELOG entries landed under `## [Unreleased]`, no bump, no release commit.
|
|
38
40
|
|
|
39
|
-
**Resolution:** [`cut-procedure.md`](cut-procedure.md) Variant B;
|
|
41
|
+
**Resolution:** [`cut-procedure.md`](cut-procedure.md) Variant B; `` `${CLAUDE_PLUGIN_ROOT}/skills/adia-release/scripts/promote-unreleased.mjs` `` mechanizes the heading swap; fresh blocks per [`changelog-discipline.md`](changelog-discipline.md) §Authoring.
|
|
40
42
|
|
|
41
43
|
## §Scenario 4 — `[Unreleased]` extension (early cut + entangled fix)
|
|
42
44
|
|
|
@@ -72,7 +74,7 @@ node "${CLAUDE_PLUGIN_ROOT}/skills/adia-release/scripts/dispatch-publish.mjs" \
|
|
|
72
74
|
--version X.Y.Z --verify-triggered # re-dispatches ONLY packages with no run; idempotent
|
|
73
75
|
```
|
|
74
76
|
|
|
75
|
-
For a batch, preserve npm-latest ordering (`--after <prev>`). Verify against the registry, not the workflows. **Prevention:** push tags one-at-a-time ([`cut-procedure.md`](cut-procedure.md) §Step 8);
|
|
77
|
+
For a batch, preserve npm-latest ordering (`--after <prev>`). Verify against the registry, not the workflows. **Prevention:** push tags one-at-a-time ([`cut-procedure.md`](cut-procedure.md) §Step 8); `` `${CLAUDE_PLUGIN_ROOT}/skills/adia-release/scripts/release-pack.mjs` `` does this automatically and follows with `--verify-triggered`.
|
|
76
78
|
|
|
77
79
|
## §Scenario 8 — Cut on the wrong branch
|
|
78
80
|
|
|
@@ -140,4 +140,35 @@ function main() {
|
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
|
|
143
|
+
// Fixture-locked proof of the core transform, entirely in-memory (no real
|
|
144
|
+
// package.json files touched) — the `dry: true` path below asserts the
|
|
145
|
+
// script never calls fs.writeFileSync by pointing at a path whose directory
|
|
146
|
+
// doesn't exist; a latent write bug would throw ENOENT instead of passing.
|
|
147
|
+
function selftest() {
|
|
148
|
+
const fake = [{
|
|
149
|
+
pkg: 'packages/web-components',
|
|
150
|
+
path: '/nonexistent-fixture-dir/package.json',
|
|
151
|
+
txt: '{\n "name": "@adia-ai/web-components",\n "version": "0.8.4"\n}\n',
|
|
152
|
+
version: '0.8.4',
|
|
153
|
+
}];
|
|
154
|
+
if (!validateAllAtFrom(fake, '0.8.4')) {
|
|
155
|
+
console.error('selftest FAIL: validateAllAtFrom rejected a matching version'); process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
if (validateAllAtFrom(fake, '0.8.3')) {
|
|
158
|
+
console.error('selftest FAIL: validateAllAtFrom accepted a mismatched version'); process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
const bumped = bumpAll(fake, '0.8.4', '0.8.5', true /* dry */);
|
|
161
|
+
if (bumped !== fake.length) {
|
|
162
|
+
console.error('selftest FAIL: bumpAll(dry) did not report all packages bumped'); process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
// Corrupted package.json (regex has nothing to match) must be caught, not silently skipped.
|
|
165
|
+
const corrupted = [{ pkg: 'packages/x', path: '/nonexistent-fixture-dir/x/package.json', txt: '{}', version: '0.8.4' }];
|
|
166
|
+
if (bumpAll(corrupted, '0.8.4', '0.8.5', true) !== -1) {
|
|
167
|
+
console.error('selftest FAIL: corrupted package.json (no version match) not caught'); process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
console.log('selftest OK');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const topArgv = process.argv.slice(2);
|
|
173
|
+
if (topArgv[0] === 'selftest') selftest();
|
|
174
|
+
else main();
|
|
@@ -199,19 +199,57 @@ async function main() {
|
|
|
199
199
|
}
|
|
200
200
|
console.log(`\n[dispatch] ${succeeded}/${PACKAGES.length} dispatched ${args.dry ? '(dry)' : ''}`);
|
|
201
201
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
202
|
+
// defect-2 (forge-campaign gh#268 audit): this used to print a "[next] wait
|
|
203
|
+
// for / verify" block phrased as a to-do EVEN THOUGH the dispatch above
|
|
204
|
+
// already fired — an operator's agent read it as "publish hasn't happened
|
|
205
|
+
// yet" and went back to refine CHANGELOGs post-publish. State what WAS
|
|
206
|
+
// done; offer monitoring as optional, not required.
|
|
207
|
+
if (!args.dry && succeeded > 0) {
|
|
208
|
+
// CodeRabbit (PR #289): "RUNNING" overclaimed — `gh workflow run` only
|
|
209
|
+
// confirms the dispatch was SUBMITTED; the run may still queue, or fail
|
|
210
|
+
// to materialize (bad ref, disabled workflow). "dispatched" is what we
|
|
211
|
+
// actually know happened; no further action is still true either way —
|
|
212
|
+
// the point stands, the verb doesn't overreach.
|
|
213
|
+
console.log(`\n✓ ${succeeded} publish workflow(s) dispatched above — no further action required.`);
|
|
214
|
+
console.log(' Optional — monitor until they settle:');
|
|
215
|
+
console.log(` until [ "$(gh run list --workflow=publish-a2ui-validator.yml --limit 1 --json status -q '.[0].status')" = "completed" ]; do sleep 5; done`);
|
|
216
|
+
console.log(' Optional — check each conclusion once settled:');
|
|
217
|
+
console.log(` for pkg in ${PACKAGES.join(' ')}; do`);
|
|
218
|
+
console.log(` gh run list --workflow=publish-$pkg.yml --limit 1 --json conclusion -q '.[0].conclusion'`);
|
|
219
|
+
console.log(` done`);
|
|
209
220
|
}
|
|
210
221
|
|
|
211
222
|
if (succeeded !== PACKAGES.length) process.exit(1);
|
|
212
223
|
}
|
|
213
224
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
225
|
+
// dry mode never shells out (run()/dispatch() short-circuit before
|
|
226
|
+
// execSync), so this exercises the real functions without touching gh/npm.
|
|
227
|
+
function selftest() {
|
|
228
|
+
let allOk = true;
|
|
229
|
+
for (const pkg of PACKAGES) {
|
|
230
|
+
if (!dispatch(pkg, '9.9.9', true /* dry */)) allOk = false;
|
|
231
|
+
}
|
|
232
|
+
if (!allOk) { console.error('selftest FAIL: dry dispatch() reported a failure for at least one package'); process.exit(1); }
|
|
233
|
+
|
|
234
|
+
// triggeredForTag() in dry mode must conservatively report "not yet
|
|
235
|
+
// triggered" — verifyTriggeredAndRedispatch's whole point is to re-dispatch
|
|
236
|
+
// misses; silently assuming "triggered" would let a real miss through.
|
|
237
|
+
if (triggeredForTag('web-components', '9.9.9', true) !== false) {
|
|
238
|
+
console.error('selftest FAIL: dry triggeredForTag() must report false (conservative default)'); process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const rc = verifyTriggeredAndRedispatch('9.9.9', true);
|
|
242
|
+
if (rc !== 0) { console.error(`selftest FAIL: dry verifyTriggeredAndRedispatch() should report rc=0, got ${rc}`); process.exit(1); }
|
|
243
|
+
|
|
244
|
+
console.log('selftest OK');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const topArgv = process.argv.slice(2);
|
|
248
|
+
if (topArgv[0] === 'selftest') {
|
|
249
|
+
selftest();
|
|
250
|
+
} else {
|
|
251
|
+
main().catch((e) => {
|
|
252
|
+
console.error(`error: ${e.message}`);
|
|
253
|
+
process.exit(1);
|
|
254
|
+
});
|
|
255
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// gate-roster.mjs — the ONE ordered list of the pre-cut gate roster.
|
|
3
|
+
//
|
|
4
|
+
// H3 (forge-campaign gh#268 audit): release-pack.mjs's Step 3 pre-flight ran
|
|
5
|
+
// only 12 of the 17 gates cut-procedure.md §3.1 declares as "every gate runs;
|
|
6
|
+
// a subset = pre-flight failure" — the roster had drifted into two places
|
|
7
|
+
// (the script's own hardcoded array + the doc's table) and only one of them
|
|
8
|
+
// got the memo when gates were added. This module is the single source: the
|
|
9
|
+
// doc's §3.1 table transcribes it, release-pack.mjs's step3PreFlight()
|
|
10
|
+
// imports and runs it in full, and nothing else declares its own subset — a
|
|
11
|
+
// gate can only be dropped by editing this file, which is the change the
|
|
12
|
+
// doc and the script both need anyway.
|
|
13
|
+
//
|
|
14
|
+
// Usage:
|
|
15
|
+
// import { GATE_ROSTER } from './gate-roster.mjs';
|
|
16
|
+
// node gate-roster.mjs --list # human-readable numbered roster (docs/CI use)
|
|
17
|
+
// node gate-roster.mjs --count # just the count (drift check)
|
|
18
|
+
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
import { realpathSync } from 'node:fs';
|
|
21
|
+
|
|
22
|
+
export const GATE_ROSTER = [
|
|
23
|
+
{ n: 1, cmd: 'node scripts/build/components.mjs --verify', what: 'yaml ↔ sidecar ↔ .d.ts' },
|
|
24
|
+
{ n: 2, cmd: 'npm run verify:traits', what: 'trait coverage' },
|
|
25
|
+
{ n: 3, cmd: 'npm run check:lockstep', what: 'version coherence' },
|
|
26
|
+
{ n: 4, cmd: 'npm run test:unit', what: 'vitest' },
|
|
27
|
+
{ n: 5, cmd: 'npm run typecheck', what: 'tsc --noEmit' },
|
|
28
|
+
{ n: 6, cmd: 'npm run check:demo-shells', what: 'demo imports cover composes:' },
|
|
29
|
+
{ n: 7, cmd: 'npm run check:lightningcss-build', what: 'CSS minifies' },
|
|
30
|
+
{ n: 8, cmd: 'npm run check:css-bundles-fresh', what: 'dist CSS matches source' },
|
|
31
|
+
{ n: 9, cmd: 'npm run check:js-bundles-fresh', what: 'dist JS matches source' },
|
|
32
|
+
{ n: 10, cmd: 'npm run smoke:engines', what: 'gen-UI engines' },
|
|
33
|
+
{ n: 11, cmd: 'npm run smoke:register-engine', what: 'register-engine 11/11' },
|
|
34
|
+
{ n: 12, cmd: 'npm run verify:corpus', what: 'corpus 0 errors' },
|
|
35
|
+
{ n: 13, cmd: 'npm run check:chunks-fresh', what: 'chunk index vs sources' },
|
|
36
|
+
{ n: 14, cmd: 'npm run check:embeddings-fresh', what: 'embeddings vs chunk index' },
|
|
37
|
+
{ n: 15, cmd: 'npm run check:links', what: 'intra-repo links' },
|
|
38
|
+
{ n: 16, cmd: 'npm run eval:diff -- --engine zettel', what: 'eval floors' },
|
|
39
|
+
{ n: 17, cmd: 'npm run dogfood:status', what: 'P0/P1 dogfood floor' },
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
// -- CLI ------------------------------------------------------------------
|
|
43
|
+
// Only runs when invoked directly (not when imported by release-pack.mjs).
|
|
44
|
+
// realpathSync BOTH sides (CodeRabbit, PR #289): a naive string/URL compare
|
|
45
|
+
// breaks under a symlinked invocation — this skill is normally reached
|
|
46
|
+
// through `.claude/skills/adia-release` -> `packages/plugins/adia-ui-forge/
|
|
47
|
+
// skills/adia-release`, and Node resolves `import.meta.url` through that
|
|
48
|
+
// symlink to the real file while `process.argv[1]` keeps the as-invoked
|
|
49
|
+
// path, so an unresolved compare never matches even when this IS the entry
|
|
50
|
+
// point. Verified directly (see embeddings-chunks.mjs's identical guard).
|
|
51
|
+
const isMain = (() => {
|
|
52
|
+
if (!process.argv[1]) return false;
|
|
53
|
+
try {
|
|
54
|
+
return realpathSync(fileURLToPath(import.meta.url)) === realpathSync(process.argv[1]);
|
|
55
|
+
} catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
})();
|
|
59
|
+
if (isMain) {
|
|
60
|
+
const args = new Set(process.argv.slice(2));
|
|
61
|
+
if (args.has('--count')) {
|
|
62
|
+
console.log(GATE_ROSTER.length);
|
|
63
|
+
} else if (args.has('--list') || args.size === 0) {
|
|
64
|
+
console.log(`${GATE_ROSTER.length}-gate pre-cut roster (source of truth — cut-procedure.md §3.1 transcribes this):\n`);
|
|
65
|
+
for (const g of GATE_ROSTER) {
|
|
66
|
+
console.log(` ${String(g.n).padStart(2)}. ${g.cmd.padEnd(46)} # ${g.what}`);
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
console.log('Usage: node gate-roster.mjs [--list|--count]');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
// cycles.
|
|
17
17
|
|
|
18
18
|
import fs from 'node:fs';
|
|
19
|
+
import os from 'node:os';
|
|
20
|
+
import path from 'node:path';
|
|
19
21
|
import process from 'node:process';
|
|
20
22
|
import { resolvePackageChangelog } from './package-paths.mjs';
|
|
21
23
|
|
|
@@ -126,4 +128,36 @@ function main() {
|
|
|
126
128
|
}
|
|
127
129
|
}
|
|
128
130
|
|
|
129
|
-
|
|
131
|
+
// Fixture-locked proof against a real (throwaway) temp directory.
|
|
132
|
+
function selftest() {
|
|
133
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'insert-stub-selftest-'));
|
|
134
|
+
try {
|
|
135
|
+
const dir = path.join(tmp, 'packages', 'llm');
|
|
136
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
137
|
+
const clPath = path.join(dir, 'CHANGELOG.md');
|
|
138
|
+
const anchor = '## [0.1.0] — 2026-01-01';
|
|
139
|
+
fs.writeFileSync(clPath, `# C\n\n${anchor}\n\n- prior\n`);
|
|
140
|
+
|
|
141
|
+
const stub = buildStub('0.2.0', '2026-02-02', '@adia-ai/web-components', 'packages/web-components/CHANGELOG.md#020--2026-02-02');
|
|
142
|
+
const r1 = insertStub(tmp, 'llm', anchor, stub, '0.2.0', false);
|
|
143
|
+
if (r1 !== 'inserted') { console.error(`selftest FAIL: expected 'inserted', got '${r1}'`); process.exit(1); }
|
|
144
|
+
const out = fs.readFileSync(clPath, 'utf8');
|
|
145
|
+
if (!out.includes('## [0.2.0]')) { console.error('selftest FAIL: stub heading not written'); process.exit(1); }
|
|
146
|
+
if (out.indexOf('## [0.2.0]') > out.indexOf(anchor)) {
|
|
147
|
+
console.error('selftest FAIL: stub must land ABOVE the previous-version anchor, not below'); process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Idempotency: re-inserting against an already-stubbed target must fail
|
|
151
|
+
// loudly, not create a duplicate [0.2.0] block.
|
|
152
|
+
const r2 = insertStub(tmp, 'llm', anchor, stub, '0.2.0', false);
|
|
153
|
+
if (r2 !== 'already-present') { console.error(`selftest FAIL: expected 'already-present' on re-run, got '${r2}'`); process.exit(1); }
|
|
154
|
+
|
|
155
|
+
console.log('selftest OK');
|
|
156
|
+
} finally {
|
|
157
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const topArgv = process.argv.slice(2);
|
|
162
|
+
if (topArgv[0] === 'selftest') selftest();
|
|
163
|
+
else main();
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
// 2 — bad args / file I/O error
|
|
21
21
|
|
|
22
22
|
import fs from 'node:fs';
|
|
23
|
+
import os from 'node:os';
|
|
24
|
+
import path from 'node:path';
|
|
23
25
|
import process from 'node:process';
|
|
24
26
|
import { resolvePackageChangelog } from './package-paths.mjs';
|
|
25
27
|
|
|
@@ -86,4 +88,36 @@ function main() {
|
|
|
86
88
|
}
|
|
87
89
|
}
|
|
88
90
|
|
|
89
|
-
|
|
91
|
+
// Fixture-locked proof against a real (throwaway) temp directory — promote()
|
|
92
|
+
// does real fs.readFileSync/writeFileSync via resolvePackageChangelog, so a
|
|
93
|
+
// pure in-memory test can't exercise it; a temp dir keeps this <1s and
|
|
94
|
+
// touches nothing in the real repo.
|
|
95
|
+
function selftest() {
|
|
96
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'promote-unreleased-selftest-'));
|
|
97
|
+
try {
|
|
98
|
+
const dir = path.join(tmp, 'packages', 'web-components');
|
|
99
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
100
|
+
const clPath = path.join(dir, 'CHANGELOG.md');
|
|
101
|
+
fs.writeFileSync(clPath, '# C\n\n## [Unreleased]\n\n### Fixed\n- x\n\n## [0.1.0] — 2026-01-01\n\n- y\n');
|
|
102
|
+
|
|
103
|
+
const r1 = promote(tmp, 'web-components', '0.2.0', '2026-02-02', false);
|
|
104
|
+
if (r1 !== 'promoted') { console.error(`selftest FAIL: expected 'promoted', got '${r1}'`); process.exit(1); }
|
|
105
|
+
const out = fs.readFileSync(clPath, 'utf8');
|
|
106
|
+
if (!out.includes('## [0.2.0] — 2026-02-02')) { console.error('selftest FAIL: new heading not written'); process.exit(1); }
|
|
107
|
+
if (out.includes('## [Unreleased]')) { console.error('selftest FAIL: [Unreleased] heading should have been renamed, not left behind'); process.exit(1); }
|
|
108
|
+
if (!out.includes('### Fixed\n- x')) { console.error('selftest FAIL: content under the heading must survive the rename'); process.exit(1); }
|
|
109
|
+
|
|
110
|
+
// Idempotency: re-running against an already-promoted target must fail
|
|
111
|
+
// loudly (a silent no-op or a duplicate block would both be wrong).
|
|
112
|
+
const r2 = promote(tmp, 'web-components', '0.2.0', '2026-02-02', false);
|
|
113
|
+
if (r2 !== 'already-promoted') { console.error(`selftest FAIL: expected 'already-promoted' on re-run, got '${r2}'`); process.exit(1); }
|
|
114
|
+
|
|
115
|
+
console.log('selftest OK');
|
|
116
|
+
} finally {
|
|
117
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const topArgv = process.argv.slice(2);
|
|
122
|
+
if (topArgv[0] === 'selftest') selftest();
|
|
123
|
+
else main();
|