@adia-ai/adia-ui-forge 0.8.31 → 0.8.33
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 +22 -0
- package/package.json +1 -1
- package/skills/adia-author/SKILL.md +6 -8
- package/skills/adia-author/references/INDEX.md +41 -0
- package/skills/adia-author/references/anti-patterns.md +24 -0
- package/skills/adia-author/references/api-contract.md +15 -0
- package/skills/adia-author/references/css-patterns.md +19 -0
- package/skills/adia-author/references/lifecycle-patterns.md +20 -0
- package/skills/adia-author/references/token-contract.md +15 -1
- package/skills/adia-author/references/trait-pages.md +38 -0
- package/skills/adia-gen-review/references/corpus-html-patterns.md +1 -1
- package/skills/adia-release/SKILL.md +1 -1
- package/skills/adia-release/references/cut-procedure.md +41 -17
- package/skills/adia-release/references/recovery-paths.md +1 -1
- package/skills/adia-release/scripts/dispatch-publish.mjs +217 -54
- package/skills/adia-release/scripts/release-pack.mjs +365 -20
|
@@ -147,6 +147,52 @@ function parseArgs(argv) {
|
|
|
147
147
|
// there: on the v0.8.10 cut the missing flag surfaced only after tags and
|
|
148
148
|
// npm publish were already irreversible (Step 10 is the LAST step). An arg
|
|
149
149
|
// error must never cost half a shipped release.
|
|
150
|
+
// From-scratch needs FULL roster CHANGELOG coverage at Step 4 — every
|
|
151
|
+
// package gets its [VERSION] section from either promote-unreleased
|
|
152
|
+
// (--substantive-packages) or insert-stub (--stub-packages). Validate at
|
|
153
|
+
// PARSE time, not there: on the v0.8.29 cut the missing --stub-packages
|
|
154
|
+
// surfaced only at Step 4e's generate-release-notes ("no [X.Y.Z] CHANGELOG
|
|
155
|
+
// section in: <8 ride-alongs>") — AFTER the ~15-min pre-flight, promote,
|
|
156
|
+
// and bump had all run, and each abort left a half-cut tree (gh#765).
|
|
157
|
+
// Mirrors the handoff --gh-notes-file guard below. Mode cut is exempt: a
|
|
158
|
+
// peer may have pre-staged the [VERSION] sections by hand.
|
|
159
|
+
// EITHER package flag arms the guard (CodeRabbit, PR #772): keying on
|
|
160
|
+
// --substantive-packages alone let a stub-only invocation bypass both the
|
|
161
|
+
// coverage check and the --substantive/--xref pairing check below — and a
|
|
162
|
+
// stub list without that pairing makes Step 4 silently skip insert-stub,
|
|
163
|
+
// the exact Step-4e death this guard exists to prevent. (A ZERO-flag
|
|
164
|
+
// from-scratch can't be judged here — it may be a half-cut resume whose
|
|
165
|
+
// sections already exist; main() rules on it after detectHalfCut.)
|
|
166
|
+
if (args.mode === 'from-scratch' && (args.substantivePackages || args.stubPackages)) {
|
|
167
|
+
// Flags accept both name form (a2ui-corpus) and path form (a2ui/corpus);
|
|
168
|
+
// normalize to name form for the roster set-difference.
|
|
169
|
+
const toName = (p) => (PACKAGE_NAMES.includes(p) ? p : p.replaceAll('/', '-'));
|
|
170
|
+
const covered = new Set([
|
|
171
|
+
...(args.substantivePackages ?? []).map(toName),
|
|
172
|
+
...(args.stubPackages ?? []).map(toName),
|
|
173
|
+
]);
|
|
174
|
+
const missing = PACKAGE_NAMES.filter((p) => !covered.has(p));
|
|
175
|
+
if (missing.length > 0) {
|
|
176
|
+
console.error(`error: --mode from-scratch covers only ${covered.size} of the ${PACKAGE_NAMES.length} lockstep`);
|
|
177
|
+
console.error(' packages — Step 4e (generate-release-notes) would die on a missing');
|
|
178
|
+
console.error(` [${args.version}] CHANGELOG section in the ${missing.length} ride-along(s):`);
|
|
179
|
+
console.error(` ${missing.join(', ')}`);
|
|
180
|
+
console.error(' Add them to --stub-packages (pure ride-alongs get the lockstep stub;');
|
|
181
|
+
console.error(' insert-stub also needs --substantive and --xref) or to');
|
|
182
|
+
console.error(' --substantive-packages if they carry real [Unreleased] content.');
|
|
183
|
+
console.error(' Failing now, BEFORE the ~15-min pre-flight (v0.8.29 burned three');
|
|
184
|
+
console.error(' full re-runs discovering this at Step 4e — gh#765).');
|
|
185
|
+
process.exit(2);
|
|
186
|
+
}
|
|
187
|
+
// insert-stub silently no-ops without --substantive/--xref (Step 4's
|
|
188
|
+
// guard condition) — same Step 4e death, so same parse-time fail.
|
|
189
|
+
if (args.stubPackages && (!args.substantive || !args.xref)) {
|
|
190
|
+
console.error('error: --stub-packages requires --substantive and --xref (insert-stub.mjs');
|
|
191
|
+
console.error(' needs both for the stub block; without them Step 4 silently skips');
|
|
192
|
+
console.error(' stub insertion and Step 4e dies on the missing CHANGELOG sections).');
|
|
193
|
+
process.exit(2);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
150
196
|
if (args.mode === 'handoff') {
|
|
151
197
|
if (!args.ghNotesFile) {
|
|
152
198
|
console.error('error: --mode handoff requires --gh-notes-file (Step 10 creates the 11 GH');
|
|
@@ -210,12 +256,39 @@ async function checkpoint(label, evidence, autoConfirmed) {
|
|
|
210
256
|
}
|
|
211
257
|
}
|
|
212
258
|
|
|
259
|
+
// A failed inner command must surface WHAT it said, not a bare execSync
|
|
260
|
+
// stack: the v0.8.29 cut died at Step 4e with only `Error: Command failed`
|
|
261
|
+
// — the generator's real message ("no [X.Y.Z] CHANGELOG section in: …") was
|
|
262
|
+
// invisible until the command was re-run by hand (gh#765). Print the failing
|
|
263
|
+
// command + exit status + any CAPTURED stdout/stderr (piped calls; inherit
|
|
264
|
+
// calls already streamed it live), then rethrow a marked, stack-free error
|
|
265
|
+
// so main()'s catch doesn't re-dump a useless execSync trace over it.
|
|
266
|
+
function shFailure(cmd, e) {
|
|
267
|
+
const status = e.status ?? (e.signal ? `signal ${e.signal}` : '?');
|
|
268
|
+
console.error(`\n✗ command failed (exit ${status}): ${cmd}`);
|
|
269
|
+
const captured = [e.stdout, e.stderr]
|
|
270
|
+
.map((s) => (s || '').toString().trim())
|
|
271
|
+
.filter(Boolean);
|
|
272
|
+
if (captured.length > 0) {
|
|
273
|
+
console.error(' ── the command\'s own output ──');
|
|
274
|
+
console.error(captured.join('\n').split('\n').map((l) => ` ${l}`).join('\n'));
|
|
275
|
+
console.error(' ──');
|
|
276
|
+
}
|
|
277
|
+
const err = new Error(`command failed (exit ${status}): ${cmd}`);
|
|
278
|
+
err.reported = true; // main()'s catch prints e.message only, no stack
|
|
279
|
+
return err;
|
|
280
|
+
}
|
|
281
|
+
|
|
213
282
|
function sh(cmd, args, opts = {}) {
|
|
214
283
|
if (args.dry) {
|
|
215
284
|
console.log(` [dry] ${cmd}`);
|
|
216
285
|
return '';
|
|
217
286
|
}
|
|
218
|
-
|
|
287
|
+
try {
|
|
288
|
+
return execSync(cmd, { cwd: REPO, encoding: 'utf8', stdio: opts.stdio || 'inherit' });
|
|
289
|
+
} catch (e) {
|
|
290
|
+
throw shFailure(cmd, e);
|
|
291
|
+
}
|
|
219
292
|
}
|
|
220
293
|
|
|
221
294
|
function shQuiet(cmd, args) {
|
|
@@ -223,7 +296,11 @@ function shQuiet(cmd, args) {
|
|
|
223
296
|
console.log(` [dry] ${cmd}`);
|
|
224
297
|
return '';
|
|
225
298
|
}
|
|
226
|
-
|
|
299
|
+
try {
|
|
300
|
+
return execSync(cmd, { cwd: REPO, encoding: 'utf8' });
|
|
301
|
+
} catch (e) {
|
|
302
|
+
throw shFailure(cmd, e);
|
|
303
|
+
}
|
|
227
304
|
}
|
|
228
305
|
|
|
229
306
|
/** Run a command for its EXIT STATUS only — true on 0, false on anything
|
|
@@ -266,6 +343,56 @@ function shCapture(cmd, args) {
|
|
|
266
343
|
}
|
|
267
344
|
}
|
|
268
345
|
|
|
346
|
+
// ── Half-cut detection (gh#765) ──────────────────────────────────
|
|
347
|
+
// An aborted Step-4 run (the v0.8.29 cut aborted three times at Step 4e)
|
|
348
|
+
// leaves a half-cut tree: package versions bumped to the cut version +
|
|
349
|
+
// CHANGELOGs promoted. A plain re-run then FALSE-FAILS the pre-flight —
|
|
350
|
+
// gate 24 (cut-hygiene) validates version currency at args.previous, which
|
|
351
|
+
// the half-done bump has already moved past.
|
|
352
|
+
//
|
|
353
|
+
// DETECT-AND-RESUME, not rollback — the smaller and safer of the two:
|
|
354
|
+
// resume is pure detection at the next invocation's start, so it works no
|
|
355
|
+
// matter HOW the previous run died (crash, SIGINT, kill -9 — a rollback
|
|
356
|
+
// handler never runs for those), and it never touches the working tree,
|
|
357
|
+
// so it cannot discard operator hand-edits the way a `git checkout --`
|
|
358
|
+
// rollback could. The already-correct Step-4 work is kept, not redone.
|
|
359
|
+
//
|
|
360
|
+
// Marker: the lockstep bump is the last tree-wide mutation before the
|
|
361
|
+
// abort point, so ALL roster package.json versions at args.version ⇒ the
|
|
362
|
+
// promote+bump half completed — resume. A MIX of cut/previous versions is
|
|
363
|
+
// a mid-bump abort this orchestrator cannot reason about: hard-stop with
|
|
364
|
+
// the manual recovery pointer. CHANGELOG promotion is then re-proved by
|
|
365
|
+
// Step 4's existing unpromoted-[Unreleased] guard, and the remaining Step-4
|
|
366
|
+
// substeps (lockfile, catalog, 4e notes, 4f fix, 4g hygiene) are idempotent
|
|
367
|
+
// regenerations that simply run again.
|
|
368
|
+
function detectHalfCut(args) {
|
|
369
|
+
if (args.mode === 'handoff' || args.dry) return false;
|
|
370
|
+
const versions = PACKAGE_ROSTER.map(({ name, dir }) => {
|
|
371
|
+
const p = path.join(REPO, dir, 'package.json');
|
|
372
|
+
return { name, version: JSON.parse(fs.readFileSync(p, 'utf8')).version };
|
|
373
|
+
});
|
|
374
|
+
const bumped = versions.filter((v) => v.version === args.version);
|
|
375
|
+
if (bumped.length === 0) return false; // fresh cut — nothing bumped yet
|
|
376
|
+
if (bumped.length < versions.length) {
|
|
377
|
+
console.error(`ERROR: mixed lockstep versions — ${bumped.length}/${versions.length} packages are already`);
|
|
378
|
+
console.error(` at ${args.version}, the rest are not (a mid-bump abort?):`);
|
|
379
|
+
for (const v of versions.filter((x) => x.version !== args.version)) {
|
|
380
|
+
console.error(` ✗ ${v.name}: ${v.version}`);
|
|
381
|
+
}
|
|
382
|
+
console.error(' This orchestrator can neither resume nor safely roll back a partial');
|
|
383
|
+
console.error(' bump. Restore the tree first (git status / git checkout -- <files>,');
|
|
384
|
+
console.error(' or re-run bump.mjs by hand), then re-run.');
|
|
385
|
+
process.exit(1);
|
|
386
|
+
}
|
|
387
|
+
console.log(`
|
|
388
|
+
[resume] half-cut tree detected: all ${versions.length} lockstep packages already at ${args.version}
|
|
389
|
+
(an earlier run's Step 4 promote+bump completed before it aborted — gh#765).
|
|
390
|
+
Resuming: pre-flight hygiene validates at ${args.version} instead of
|
|
391
|
+
${args.previous}, and Step 4 skips promote/stub/bump, re-running only its
|
|
392
|
+
idempotent substeps (lockfile, catalog, notes, coverage, hygiene).`);
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
|
|
269
396
|
// ── Step 1 — Re-baseline ──────────────────────────────────────────
|
|
270
397
|
function step1ReBaseline(args) {
|
|
271
398
|
console.log('\n=== Step 1 — Re-baseline ===');
|
|
@@ -310,8 +437,21 @@ function step3PreFlight(args) {
|
|
|
310
437
|
// README "Current version" claim still equals the PREVIOUS version (the
|
|
311
438
|
// bump moves it in Step 4; Step 4g then re-proves at the cut version) —
|
|
312
439
|
// while handoff runs POST-MERGE, where the claim already equals the CUT
|
|
313
|
-
// version and validating at previous fails by construction.
|
|
314
|
-
|
|
440
|
+
// version and validating at previous fails by construction. A RESUMED
|
|
441
|
+
// half-cut run (gh#765) is post-bump too: the earlier aborted run already
|
|
442
|
+
// moved every reference to the cut version, so validating at previous
|
|
443
|
+
// false-fails gate 24 exactly like handoff would.
|
|
444
|
+
const hygieneVersion = (args.mode === 'handoff' || args.resume) ? args.version : args.previous;
|
|
445
|
+
// A RESUMED run's versionArg gates (cut-hygiene) demand artifacts Step 4
|
|
446
|
+
// has not recreated yet — docs/releases/vX.Y.Z.md exists only after 4e,
|
|
447
|
+
// which runs AFTER this pre-flight. Step 4g re-runs these same gates at
|
|
448
|
+
// the cut version once the notes exist, so skipping them here loses no
|
|
449
|
+
// coverage — running them here false-fails by construction (the v0.8.32
|
|
450
|
+
// cut hit exactly this: 23/24 green, gate 24 dead on the missing notes).
|
|
451
|
+
if (g.versionArg && args.resume) {
|
|
452
|
+
console.log(`\n ${g.n}/${GATE_ROSTER.length}. ${g.cmd} # ${g.what} — [resume] deferred to Step 4g (notes not regenerated yet)`);
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
315
455
|
const cmd = g.versionArg ? `${g.cmd} --version ${hygieneVersion}` : g.cmd;
|
|
316
456
|
console.log(`\n ${g.n}/${GATE_ROSTER.length}. ${cmd} # ${g.what}`);
|
|
317
457
|
try {
|
|
@@ -371,16 +511,48 @@ function step4PromoteAndBump(args) {
|
|
|
371
511
|
// optional per window, so `root` joins the promote list exactly when its
|
|
372
512
|
// [Unreleased] carries real content. A repo-level-only window (0.8.7 was
|
|
373
513
|
// one) promotes root with zero substantive packages.
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
514
|
+
// A resumed half-cut run (gh#765, detected in main) already promoted,
|
|
515
|
+
// stubbed, and bumped — re-running promote/insert-stub would double-insert
|
|
516
|
+
// sections and bump.mjs --from would no longer match. Skip those three;
|
|
517
|
+
// the unpromoted-[Unreleased] guard below still re-proves the promotion
|
|
518
|
+
// actually completed, and every later substep is an idempotent regenerate.
|
|
519
|
+
if (!args.resume) {
|
|
520
|
+
const promoteTargets = [...(args.substantivePackages ?? [])];
|
|
521
|
+
if (unpromotedUnreleased(REPO, 'root')) promoteTargets.push('root');
|
|
522
|
+
if (promoteTargets.length > 0) {
|
|
523
|
+
const cmd = `node ${SCRIPT_DIR}/promote-unreleased.mjs --version ${args.version} --date ${args.date} --packages ${promoteTargets.join(',')}`;
|
|
524
|
+
sh(cmd, args);
|
|
525
|
+
}
|
|
380
526
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
527
|
+
if (args.stubPackages && args.substantive && args.xref) {
|
|
528
|
+
const cmd = `node ${SCRIPT_DIR}/insert-stub.mjs --version ${args.version} --date ${args.date} --previous-version ${args.previous} --substantive "${args.substantive}" --xref "${args.xref}" --packages ${args.stubPackages.join(',')}`;
|
|
529
|
+
sh(cmd, args);
|
|
530
|
+
}
|
|
531
|
+
} else {
|
|
532
|
+
// The bump-complete marker proves promote+bump ran — it CANNOT see
|
|
533
|
+
// whether insert-stub ran: a stub package's [Unreleased] is empty, so
|
|
534
|
+
// the unpromoted guard below passes with the [X.Y.Z] section entirely
|
|
535
|
+
// absent, and the death arrives later at 4e's generate-release-notes
|
|
536
|
+
// ("no [X.Y.Z] CHANGELOG section in: …"). The v0.8.32 cut hit exactly
|
|
537
|
+
// this (the aborted run predated stub insertion). Complete the missing
|
|
538
|
+
// stubs here instead of skipping blindly — insert-stub hard-errors on
|
|
539
|
+
// existing sections, so filter to the genuinely missing ones.
|
|
540
|
+
if (args.stubPackages && args.substantive && args.xref) {
|
|
541
|
+
const missingStubs = args.stubPackages.filter((pkg) => {
|
|
542
|
+
const p = resolvePackageChangelog(REPO, pkg);
|
|
543
|
+
return fs.existsSync(p) && !fs.readFileSync(p, 'utf8').includes(`## [${args.version}]`);
|
|
544
|
+
});
|
|
545
|
+
if (missingStubs.length > 0) {
|
|
546
|
+
console.log(` [resume] ${missingStubs.length} stub section(s) missing from the aborted run — completing: ${missingStubs.join(', ')}`);
|
|
547
|
+
sh(`node ${SCRIPT_DIR}/insert-stub.mjs --version ${args.version} --date ${args.date} --previous-version ${args.previous} --substantive "${args.substantive}" --xref "${args.xref}" --packages ${missingStubs.join(',')}`, args);
|
|
548
|
+
} else {
|
|
549
|
+
console.log(' [resume] promote/stub already applied by the aborted run — skipping');
|
|
550
|
+
}
|
|
551
|
+
} else {
|
|
552
|
+
console.log(' [resume] promote/stub already applied by the aborted run — skipping');
|
|
553
|
+
console.log(' (no --stub-packages/--substantive/--xref given; if 4e fails on');
|
|
554
|
+
console.log(' missing [X.Y.Z] sections, re-run with the stub flags to complete them)');
|
|
555
|
+
}
|
|
384
556
|
}
|
|
385
557
|
|
|
386
558
|
// Loud guard: after promotion, NO package in the lockstep set (including
|
|
@@ -404,7 +576,11 @@ function step4PromoteAndBump(args) {
|
|
|
404
576
|
console.log(` ✓ no unpromoted [Unreleased] content across all ${PACKAGES.length} packages + root`);
|
|
405
577
|
}
|
|
406
578
|
|
|
407
|
-
|
|
579
|
+
if (!args.resume) {
|
|
580
|
+
sh(`node ${SCRIPT_DIR}/bump.mjs --from ${args.previous} --to ${args.version}`, args);
|
|
581
|
+
} else {
|
|
582
|
+
console.log(` [resume] versions already at ${args.version} — skipping bump`);
|
|
583
|
+
}
|
|
408
584
|
sh('npm install --package-lock-only --no-audit --no-fund', args);
|
|
409
585
|
sh('npm run check:lockstep', args);
|
|
410
586
|
|
|
@@ -493,8 +669,15 @@ function step5Commit(args) {
|
|
|
493
669
|
`docs/releases/v${args.version}.md`,
|
|
494
670
|
// gh#617: Step 4d.5 regenerates these post-bump (catalogId embeds the
|
|
495
671
|
// lockstep version); unstaged, check:genui-catalog fails in CI (v0.8.26).
|
|
672
|
+
// catalog-data.js is the same derivation's third output (the browser-safe
|
|
673
|
+
// ES-module projection of base.json/adia-pack.json) — check-genui-catalog.mjs
|
|
674
|
+
// verifies its freshness too, but this list never carried it (found live
|
|
675
|
+
// during the v0.8.33 cut: base.json/adia-pack.json staged and committed
|
|
676
|
+
// fine, catalog-data.js sat modified-uncommitted, CI caught the drift a
|
|
677
|
+
// fresh checkout exposes that a local working tree doesn't).
|
|
496
678
|
'packages/genui/adia-catalog/base.json',
|
|
497
679
|
'packages/genui/adia-catalog/adia-pack.json',
|
|
680
|
+
'packages/genui/adia-catalog/catalog-data.js',
|
|
498
681
|
...PACKAGE_ROSTER.flatMap(({ name, dir, plugin }) => {
|
|
499
682
|
// Roster-driven (H3): each package stages its manifest + CHANGELOG;
|
|
500
683
|
// plugins also stage .claude-plugin/plugin.json (the /plugin-update
|
|
@@ -751,11 +934,58 @@ async function step10GhAndSite(args) {
|
|
|
751
934
|
`builds from main, runs pre-flight verify → snapshot → hardened rsync →\n` +
|
|
752
935
|
`post-deploy verify → auto-rollback. NOT a raw rsync (the bypass this replaced).`;
|
|
753
936
|
await checkpoint('Deploy site — before dispatching deploy-site.yml', evidence, args.publish);
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
937
|
+
// gh#762 + PR #775 review: dispatch and settle-wait are ONE shell block so
|
|
938
|
+
// the tracked run id survives between steps. Three fixes over the old
|
|
939
|
+
// "poll the newest run for status=completed" shape:
|
|
940
|
+
// 1. It could settle on the PREVIOUS completed run before the dispatch
|
|
941
|
+
// registered — so capture the newest id BEFORE dispatching and wait
|
|
942
|
+
// for a NEW id to appear.
|
|
943
|
+
// 2. It accepted failure/cancelled as settled — so poll the tracked run
|
|
944
|
+
// by id and require conclusion=success, else fail the handoff.
|
|
945
|
+
// 3. It waited in silence while a forgotten run at the production-site
|
|
946
|
+
// gate held the deploy-site concurrency group for 56h — so each poll
|
|
947
|
+
// names the gate-waiting blocker (id, since, URL).
|
|
948
|
+
// reap-stale-deploy-gate.yml auto-cancels gate-waiters older than 24h, but
|
|
949
|
+
// a live handoff should not wait hours to find out.
|
|
950
|
+
console.log(' Dispatching and waiting for the deploy workflow to settle...');
|
|
951
|
+
sh(String.raw`
|
|
952
|
+
prev_id=$(gh run list --workflow=deploy-site.yml --repo adiahealth/gen-ui-kit --limit 1 --json databaseId -q '.[0].databaseId')
|
|
953
|
+
gh workflow run "Deploy site (${args.host})" --repo adiahealth/gen-ui-kit --ref main
|
|
954
|
+
run_id=""
|
|
955
|
+
tries=0
|
|
956
|
+
while [ "$tries" -lt 60 ]; do
|
|
957
|
+
sleep 5
|
|
958
|
+
run_id=$(gh run list --workflow=deploy-site.yml --repo adiahealth/gen-ui-kit --limit 1 --json databaseId -q '.[0].databaseId')
|
|
959
|
+
if [ -n "$run_id" ] && [ "$run_id" != "$prev_id" ]; then break; fi
|
|
960
|
+
run_id=""
|
|
961
|
+
tries=$((tries + 1))
|
|
962
|
+
done
|
|
963
|
+
if [ -z "$run_id" ]; then
|
|
964
|
+
echo "ERROR: the dispatched deploy-site run never appeared within 5 min (newest is still $prev_id) — inspect the Actions tab before retrying" >&2
|
|
965
|
+
exit 1
|
|
966
|
+
fi
|
|
967
|
+
echo " Tracking dispatched run $run_id..."
|
|
968
|
+
while :; do
|
|
969
|
+
status=$(gh run view "$run_id" --repo adiahealth/gen-ui-kit --json status -q .status)
|
|
970
|
+
[ "$status" = "completed" ] && break
|
|
971
|
+
blocker=$(gh run list --workflow=deploy-site.yml --repo adiahealth/gen-ui-kit --limit 10 \
|
|
972
|
+
--json databaseId,status,createdAt,url \
|
|
973
|
+
-q '[.[] | select(.status == "waiting")] | last | if . == null then "" else "run \(.databaseId) — waiting at the production-site gate since \(.createdAt): \(.url)" end')
|
|
974
|
+
if [ "$status" = "waiting" ]; then
|
|
975
|
+
echo " … run $run_id is at the production-site approval gate: ${'$'}{blocker:-approve it in the Actions UI}"
|
|
976
|
+
elif [ -n "$blocker" ]; then
|
|
977
|
+
echo " … run $run_id is '$status' — BLOCKED BEHIND $blocker (gh#762: approve or cancel that run to release the deploy-site concurrency group)"
|
|
978
|
+
else
|
|
979
|
+
echo " … run $run_id: $status"
|
|
980
|
+
fi
|
|
981
|
+
sleep 15
|
|
982
|
+
done
|
|
983
|
+
conclusion=$(gh run view "$run_id" --repo adiahealth/gen-ui-kit --json conclusion -q .conclusion)
|
|
984
|
+
if [ "$conclusion" != "success" ]; then
|
|
985
|
+
echo "ERROR: deploy-site run $run_id completed with conclusion '$conclusion' — the site deploy did NOT land; investigate the run before re-dispatching" >&2
|
|
986
|
+
exit 1
|
|
987
|
+
fi
|
|
988
|
+
echo " ✓ deploy-site.yml run $run_id settled: success"`, args);
|
|
759
989
|
// Verify a deployed CONTENT FILE, never an SPA route — the docs site
|
|
760
990
|
// returns HTTP 200 + a blank shell for ANY unmatched route (v0.7.4 trap).
|
|
761
991
|
sh(`curl -s -o /dev/null -w "EXE host.css: HTTP %{http_code}\\n" https://${args.host}/packages/web-components/styles/host.css`, args);
|
|
@@ -767,6 +997,41 @@ async function main() {
|
|
|
767
997
|
// Fail-fast guard: refuse to git/npm/gh/curl against a non-monorepo directory.
|
|
768
998
|
assertMonorepoRoot(REPO);
|
|
769
999
|
console.log(`adia-release release-pack — v${args.version} (${args.mode})${args.dry ? ' [DRY]' : ''}`);
|
|
1000
|
+
// Stale-plugin-cache self-check (v0.8.32 cut, first failure): the INSTALLED
|
|
1001
|
+
// plugin cache only refreshes on a version bump, so running this script from
|
|
1002
|
+
// the cache mid-cycle can execute a version that predates its own guards
|
|
1003
|
+
// (SKILL.md §Plugin cache). If the repo carries its own copy and the bytes
|
|
1004
|
+
// differ, warn loudly — the repo copy is almost always the newer one.
|
|
1005
|
+
const repoCopy = path.join(REPO, 'packages/plugins/adia-ui-forge/skills/adia-release/scripts/release-pack.mjs');
|
|
1006
|
+
const selfPath = new URL(import.meta.url).pathname;
|
|
1007
|
+
if (fs.existsSync(repoCopy) && path.resolve(selfPath) !== path.resolve(repoCopy)) {
|
|
1008
|
+
const self = fs.readFileSync(selfPath, 'utf8');
|
|
1009
|
+
const repo = fs.readFileSync(repoCopy, 'utf8');
|
|
1010
|
+
if (self !== repo) {
|
|
1011
|
+
console.error('\n⚠ STALE-COPY WARNING: this release-pack.mjs differs from the repo\'s own copy at');
|
|
1012
|
+
console.error(` ${repoCopy}`);
|
|
1013
|
+
console.error(' The installed plugin cache lags the repo between cuts (SKILL.md §Plugin cache);');
|
|
1014
|
+
console.error(' the repo copy is almost certainly newer — prefer running it:');
|
|
1015
|
+
console.error(` node ${repoCopy} <same args>\n`);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
// gh#765: an aborted Step-4 run leaves bumped versions + promoted
|
|
1019
|
+
// CHANGELOGs; detect BEFORE pre-flight so gate 24 validates at the right
|
|
1020
|
+
// version and Step 4 skips the already-done promote/stub/bump.
|
|
1021
|
+
args.resume = detectHalfCut(args);
|
|
1022
|
+
// The zero-flag from-scratch shape parseArgs deferred (CodeRabbit, PR #772):
|
|
1023
|
+
// with NEITHER package flag, no CHANGELOG gets a [VERSION] section and
|
|
1024
|
+
// Step 4e is a guaranteed death — unless this is a half-cut RESUME, where
|
|
1025
|
+
// the aborted run already wrote the sections. Only detectHalfCut can tell
|
|
1026
|
+
// the two apart, so the ruling lives here — still BEFORE the pre-flight.
|
|
1027
|
+
if (args.mode === 'from-scratch' && !args.resume && !args.substantivePackages && !args.stubPackages) {
|
|
1028
|
+
console.error('error: --mode from-scratch needs --substantive-packages and/or --stub-packages');
|
|
1029
|
+
console.error(` covering all ${PACKAGE_NAMES.length} lockstep packages — with neither, no CHANGELOG gets`);
|
|
1030
|
+
console.error(` a [${args.version}] section and Step 4e (generate-release-notes) dies after`);
|
|
1031
|
+
console.error(' the ~15-min pre-flight (gh#765). A resumed half-cut tree is exempt,');
|
|
1032
|
+
console.error(' but this tree is not half-cut (versions are not at the cut version).');
|
|
1033
|
+
process.exit(2);
|
|
1034
|
+
}
|
|
770
1035
|
step1ReBaseline(args);
|
|
771
1036
|
step3PreFlight(args);
|
|
772
1037
|
step4PromoteAndBump(args);
|
|
@@ -859,6 +1124,83 @@ function selftest() {
|
|
|
859
1124
|
process.exit(1);
|
|
860
1125
|
}
|
|
861
1126
|
|
|
1127
|
+
// gh#765 — from-scratch with substantive-but-no-stub packages must die at
|
|
1128
|
+
// PARSE time, naming the ride-alongs, BEFORE any pre-flight gate runs
|
|
1129
|
+
// (v0.8.29 burned three ~15-min pre-flight re-runs discovering it at
|
|
1130
|
+
// Step 4e). Mirror of the handoff --gh-notes-file guard below.
|
|
1131
|
+
let guardFailed = false;
|
|
1132
|
+
let guardOut = '';
|
|
1133
|
+
try {
|
|
1134
|
+
execSync(
|
|
1135
|
+
`node "${scriptPath}" --mode from-scratch --version 9.9.9 --date 2026-01-01 --previous-version 9.9.8 ` +
|
|
1136
|
+
`--commit-message-file "${commitFile}" --substantive-packages web-components --dry`,
|
|
1137
|
+
{ cwd: REPO, encoding: 'utf8' },
|
|
1138
|
+
);
|
|
1139
|
+
} catch (e) {
|
|
1140
|
+
guardFailed = true;
|
|
1141
|
+
guardOut = (e.stdout || '') + (e.stderr || '');
|
|
1142
|
+
}
|
|
1143
|
+
if (!guardFailed || !guardOut.includes('ride-along') || !guardOut.includes('a2ui-validator')) {
|
|
1144
|
+
console.error('selftest FAIL: from-scratch without --stub-packages must hard-reject at parse time, naming the ride-alongs');
|
|
1145
|
+
process.exit(1);
|
|
1146
|
+
}
|
|
1147
|
+
if (guardOut.includes('Step 3') || guardOut.includes('gate roster')) {
|
|
1148
|
+
console.error('selftest FAIL: the ride-along guard must fire BEFORE the pre-flight, not after');
|
|
1149
|
+
process.exit(1);
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
// CodeRabbit (PR #772): the guard must arm on EITHER flag — a stub-only
|
|
1153
|
+
// invocation used to bypass it entirely. Three shapes: stub-only partial
|
|
1154
|
+
// coverage rejects naming ride-alongs; stub-only full coverage without
|
|
1155
|
+
// the --substantive/--xref pairing rejects (insert-stub would silently
|
|
1156
|
+
// no-op); zero-flag from-scratch on a NON-half-cut tree rejects in main
|
|
1157
|
+
// after detectHalfCut, still before the pre-flight.
|
|
1158
|
+
const rejectShapes = [
|
|
1159
|
+
{ flags: '--substantive x --xref y --stub-packages llm', marker: 'ride-along' },
|
|
1160
|
+
{ flags: `--stub-packages ${PACKAGE_NAMES.join(',')}`, marker: '--substantive and --xref' },
|
|
1161
|
+
{ flags: '', marker: 'needs --substantive-packages and/or --stub-packages' },
|
|
1162
|
+
];
|
|
1163
|
+
for (const { flags, marker } of rejectShapes) {
|
|
1164
|
+
let failed = false;
|
|
1165
|
+
let out = '';
|
|
1166
|
+
try {
|
|
1167
|
+
execSync(
|
|
1168
|
+
`node "${scriptPath}" --mode from-scratch --version 9.9.9 --date 2026-01-01 --previous-version 9.9.8 ` +
|
|
1169
|
+
`--commit-message-file "${commitFile}" ${flags} --dry`,
|
|
1170
|
+
{ cwd: REPO, encoding: 'utf8' },
|
|
1171
|
+
);
|
|
1172
|
+
} catch (e) {
|
|
1173
|
+
failed = true;
|
|
1174
|
+
out = (e.stdout || '') + (e.stderr || '');
|
|
1175
|
+
}
|
|
1176
|
+
if (!failed || !out.includes(marker) || out.includes('gate roster')) {
|
|
1177
|
+
console.error(`selftest FAIL: from-scratch shape "${flags || '(no package flags)'}" must reject pre-flight with "${marker}"`);
|
|
1178
|
+
process.exit(1);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// …and a fully covered from-scratch roster (substantive ∪ stub = all
|
|
1183
|
+
// packages) must pass the guard and assemble its dry plan normally.
|
|
1184
|
+
const stubs = PACKAGE_NAMES.filter((p) => p !== 'web-components').join(',');
|
|
1185
|
+
let coveredOut;
|
|
1186
|
+
try {
|
|
1187
|
+
coveredOut = execSync(
|
|
1188
|
+
`node "${scriptPath}" --mode from-scratch --version 9.9.9 --date 2026-01-01 --previous-version 9.9.8 ` +
|
|
1189
|
+
`--commit-message-file "${commitFile}" --gh-notes-file "${notesFile}" ` +
|
|
1190
|
+
`--substantive "selftest" --xref "CHANGELOG.md#999" ` +
|
|
1191
|
+
`--substantive-packages web-components --stub-packages ${stubs} --dry --yes`,
|
|
1192
|
+
{ cwd: REPO, encoding: 'utf8' },
|
|
1193
|
+
);
|
|
1194
|
+
} catch (e) {
|
|
1195
|
+
console.error('selftest FAIL: fully covered from-scratch --dry must pass the ride-along guard');
|
|
1196
|
+
console.error((e.stdout || '') + (e.stderr || ''));
|
|
1197
|
+
process.exit(1);
|
|
1198
|
+
}
|
|
1199
|
+
if (!coveredOut.includes('STOPPING per invariant 3')) {
|
|
1200
|
+
console.error('selftest FAIL: covered from-scratch dry plan missing the invariant-3 stop');
|
|
1201
|
+
process.exit(1);
|
|
1202
|
+
}
|
|
1203
|
+
|
|
862
1204
|
// Handoff without --gh-notes-file must die at PARSE time (v0.8.10: the old
|
|
863
1205
|
// Step-10-only check fired after tags+publish were irreversible).
|
|
864
1206
|
let handoffFailed = false;
|
|
@@ -892,7 +1234,10 @@ if (topArgv[0] === 'selftest') {
|
|
|
892
1234
|
selftest();
|
|
893
1235
|
} else {
|
|
894
1236
|
main().catch((e) => {
|
|
895
|
-
|
|
1237
|
+
// sh()/shQuiet() failures already printed the failing command + its
|
|
1238
|
+
// captured output (gh#765) — a full stack re-dump would bury it again.
|
|
1239
|
+
if (e && e.reported) console.error(e.message);
|
|
1240
|
+
else console.error(e);
|
|
896
1241
|
rl.close();
|
|
897
1242
|
process.exit(1);
|
|
898
1243
|
});
|