@adia-ai/adia-ui-forge 0.8.59 → 0.8.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +68 -0
- package/package.json +1 -1
- package/plugin.yaml +1 -1
- package/references/contracts/a2ui-mcp-surface.md +1 -1
- package/scripts/lint-rules.generated.mjs +2418 -359
- package/skills/a2ui-maintenance/references/eval-diagnostics.md +2 -2
- package/skills/a2ui-maintenance/references/pipeline-overview.md +26 -0
- package/skills/a2ui-maintenance/references/strategy-engines.md +12 -0
- package/skills/demo-audit/references/admin-shell-anatomy.md +18 -12
- package/skills/gen-ui-review/scripts/gen-review-decompose.mjs +27 -34
- package/skills/gen-ui-review/scripts/overflow-detect.generated.mjs +101 -0
- package/skills/package-release/references/cut-procedure.md +49 -5
- package/skills/package-release/references/recovery-paths.md +20 -0
- package/skills/package-release/scripts/bump.mjs +36 -6
- package/skills/package-release/scripts/dispatch-publish.mjs +8 -0
- package/skills/package-release/scripts/gate-roster.mjs +4 -4
- package/skills/package-release/scripts/release-pack.mjs +485 -68
- package/skills/package-release/scripts/stale-copy-warning.mjs +215 -0
- package/skills/package-release/scripts/tag-lockstep.mjs +8 -0
- package/skills/primitive-authoring/references/INDEX.md +1 -0
- package/skills/primitive-authoring/references/examples-structure.md +52 -0
- package/skills/primitive-authoring/references/shell-patterns.md +12 -11
- package/skills/primitive-authoring/references/token-contract.md +2 -2
- package/skills/primitive-authoring/references/yaml-contract.md +106 -9
|
@@ -71,6 +71,12 @@ import process from 'node:process';
|
|
|
71
71
|
import { assertMonorepoRoot } from './assert-monorepo-root.mjs';
|
|
72
72
|
import { resolvePackageChangelog, PACKAGE_ROSTER } from './package-paths.mjs';
|
|
73
73
|
import { GATE_ROSTER } from './gate-roster.mjs';
|
|
74
|
+
// gh#3361: Step 5's releaseFiles derives its PINNED_REFS-covered entries
|
|
75
|
+
// from bump.mjs's own tables (below, pinnedRefFiles()) instead of
|
|
76
|
+
// hand-duplicating them — bump.mjs guards its own main()/selftest() behind
|
|
77
|
+
// an entry-point check specifically so this import is side-effect-free.
|
|
78
|
+
import { PINNED_REFS, REPO_PINNED_REFS } from './bump.mjs';
|
|
79
|
+
import { warnIfStaleCopy } from './stale-copy-warning.mjs';
|
|
74
80
|
|
|
75
81
|
const SCRIPT_DIR = path.dirname(new URL(import.meta.url).pathname);
|
|
76
82
|
const REPO = process.cwd();
|
|
@@ -118,6 +124,7 @@ function parseArgs(argv) {
|
|
|
118
124
|
host: process.env.ADIA_DEPLOY_HOST || DEFAULT_DEPLOY_HOST,
|
|
119
125
|
scope: process.env.ADIA_NPM_SCOPE || DEFAULT_NPM_SCOPE,
|
|
120
126
|
dry: false, yes: false, push: false, publish: false, skipGates: false, skipSite: false,
|
|
127
|
+
fromStep10: false,
|
|
121
128
|
};
|
|
122
129
|
for (let i = 0; i < argv.length; i++) {
|
|
123
130
|
const k = argv[i];
|
|
@@ -148,6 +155,15 @@ function parseArgs(argv) {
|
|
|
148
155
|
else if (k === '--go') { args.yes = true; args.push = true; args.publish = true; }
|
|
149
156
|
else if (k === '--skip-gates') args.skipGates = true;
|
|
150
157
|
else if (k === '--skip-site') args.skipSite = true;
|
|
158
|
+
// gh#3342: resume a handoff that already tagged/pushed/published — a
|
|
159
|
+
// straight re-run of `--mode handoff` re-gates (Step 3 never runs in
|
|
160
|
+
// handoff mode, but Step 6 would re-tag at HEAD, which may no longer be
|
|
161
|
+
// the tagged commit if anything merged since) and Step 9 would
|
|
162
|
+
// re-dispatch publishes that already succeeded (idempotent, but a
|
|
163
|
+
// needless wait). `--from-step10` skips straight to Step 10 (GH
|
|
164
|
+
// releases + site deploy) — the exact "resume from Step 10 only" shape
|
|
165
|
+
// the v0.8.59 cut needed by hand (a one-off script, never checked in).
|
|
166
|
+
else if (k === '--from-step10') args.fromStep10 = true;
|
|
151
167
|
else if (k === '-h' || k === '--help') { help(); process.exit(0); }
|
|
152
168
|
}
|
|
153
169
|
if (!args.version || !args.date || !args.previous || !args.mode) {
|
|
@@ -235,6 +251,10 @@ function parseArgs(argv) {
|
|
|
235
251
|
console.error(`error: GH notes file not found or not a regular file: ${args.ghNotesFile}`);
|
|
236
252
|
process.exit(2);
|
|
237
253
|
}
|
|
254
|
+
} else if (args.fromStep10) {
|
|
255
|
+
console.error('error: --from-step10 only applies to --mode handoff (it resumes handoff\'s own');
|
|
256
|
+
console.error(' tag→publish→deploy sequence at its last step) — got --mode ' + args.mode);
|
|
257
|
+
process.exit(2);
|
|
238
258
|
}
|
|
239
259
|
return args;
|
|
240
260
|
}
|
|
@@ -259,8 +279,13 @@ Flags: --go (single-authorization: the operator's cut-start go covers the
|
|
|
259
279
|
--push (auto-confirm checkpoint 2 — push tags)
|
|
260
280
|
--publish (auto-confirm checkpoint 3 — dispatch publishes)
|
|
261
281
|
--skip-site (skip EXE deploy)
|
|
282
|
+
--from-step10 (--mode handoff only: resume at Step 10 — GH releases +
|
|
283
|
+
site deploy — skipping the pre-flight gates and re-tagging;
|
|
284
|
+
use once Steps 1-9 are independently confirmed done. See
|
|
285
|
+
recovery-paths.md §Scenario 9.)
|
|
262
286
|
Instance: --host <deploy-host> (default ${DEFAULT_DEPLOY_HOST}; or $ADIA_DEPLOY_HOST)
|
|
263
|
-
--scope <@org> (npm scope; default ${DEFAULT_NPM_SCOPE}; or $ADIA_NPM_SCOPE)
|
|
287
|
+
--scope <@org> (npm scope; default ${DEFAULT_NPM_SCOPE}; or $ADIA_NPM_SCOPE)
|
|
288
|
+
$REGISTRY_POLL_MINUTES (Step 9 registry-poll window; default 30)`);
|
|
264
289
|
}
|
|
265
290
|
|
|
266
291
|
const rl = createInterface({ input: stdin, output: stdout });
|
|
@@ -499,18 +524,18 @@ function step1ReBaseline(args) {
|
|
|
499
524
|
// since neither touches (a)'s files). This is the roster's one
|
|
500
525
|
// CSS-before-JS-shaped file-write ordering dependency: gate 27
|
|
501
526
|
// (`eval:diff --engine free-form`) writes a fresh
|
|
502
|
-
// evals/mcp/runs/<ISO>/ directory; gate 28
|
|
527
|
+
// packages/gen-ui/engine/evals/mcp/runs/<ISO>/ directory; gate 28
|
|
503
528
|
// (write-eval-health.mjs) reads whichever directory under
|
|
504
|
-
// evals/mcp/runs/ SORTS LEXICALLY LAST
|
|
529
|
+
// packages/gen-ui/engine/evals/mcp/runs/ SORTS LEXICALLY LAST, not specifically the
|
|
505
530
|
// latest free-form run — and expects free-form.json there. Gate
|
|
506
531
|
// 16 (`eval:diff --engine zettel`) writes its OWN fresh directory
|
|
507
532
|
// under that same root; if 16 and 27 ran concurrently and 16
|
|
508
533
|
// happened to finish after 27, gate 28 would silently pick 16's
|
|
509
534
|
// directory (no free-form.json) and commit
|
|
510
|
-
// evals/health/<version>.json with `engines.free-form: null
|
|
535
|
+
// packages/gen-ui/engine/evals/health/<version>.json with `engines.free-form: null`, a
|
|
511
536
|
// real, silent coverage loss the roster's fixed serial order
|
|
512
537
|
// prevents today only by construction. Keeping 16 before 27
|
|
513
|
-
// before 28, uninterrupted by any other evals/mcp/runs/ writer,
|
|
538
|
+
// before 28, uninterrupted by any other packages/gen-ui/engine/evals/mcp/runs/ writer,
|
|
514
539
|
// preserves that guarantee.
|
|
515
540
|
//
|
|
516
541
|
// Fail-fast BETWEEN phases (a Phase 1 failure skips Phase 2 outright — no
|
|
@@ -740,6 +765,27 @@ async function step3PreFlight(args) {
|
|
|
740
765
|
return;
|
|
741
766
|
}
|
|
742
767
|
console.log(`\n=== Step 3 — Pre-flight gates (${GATE_ROSTER.length}-gate roster — gate-roster.mjs) ===`);
|
|
768
|
+
|
|
769
|
+
// gh#3342: gate 4 (test:unit:serial) runs package-root `*.test.js` files
|
|
770
|
+
// for llm/agent/persona against their BUILT tsc output — agent.test.js's
|
|
771
|
+
// own docstring: "run against the BUILT output ... npm run build
|
|
772
|
+
// -w @adia-ai/agent first"; persona.test.js imports './index.js' directly;
|
|
773
|
+
// llm/core carries a dedicated dist-check.test.js that asserts the emitted
|
|
774
|
+
// artifacts exist and explicitly does NOT build them itself ("callers
|
|
775
|
+
// should run npm run build first"). All three packages' emitted .js/.d.ts
|
|
776
|
+
// are gitignored (packages/llm/agent/.gitignore: "Generated by npm run
|
|
777
|
+
// build ... not tracked in git"), so a fresh cut clone has none of them.
|
|
778
|
+
// cut-procedure.md §3.0's harvest preamble names `npm run build
|
|
779
|
+
// -w @adia-ai/llm` but that whole block is CONDITIONAL on source content
|
|
780
|
+
// having changed in the window, and never named agent/persona at all — a
|
|
781
|
+
// cut with no §3.0 trigger skipped the build outright and gate 4 failed
|
|
782
|
+
// on missing dist (the v0.8.59 cut). Unlike §3.0's regen outputs, these
|
|
783
|
+
// are plain build artifacts, not content-conditional — build them
|
|
784
|
+
// unconditionally, every cut, before gate 4 ever runs. Runs before the
|
|
785
|
+
// `--dry` early-return below so a dry preview shows it too.
|
|
786
|
+
console.log('\n-- Precondition: tsc build (llm, agent, persona — gate 4 needs their built output) --');
|
|
787
|
+
sh('npm run build -w @adia-ai/llm -w @adia-ai/agent -w @adia-ai/persona', args);
|
|
788
|
+
|
|
743
789
|
if (args.dry) {
|
|
744
790
|
step3PreFlightDry(args);
|
|
745
791
|
return;
|
|
@@ -973,6 +1019,31 @@ function step4PromoteAndBump(args) {
|
|
|
973
1019
|
console.log(' ✓ Hermes/Pi plugin manifests fresh after regeneration');
|
|
974
1020
|
}
|
|
975
1021
|
|
|
1022
|
+
// Step 4d.8 — rebuild the dist CSS+JS bundles (gh#3342): bump.mjs's
|
|
1023
|
+
// web-components PINNED_REFS entry moves icons-cdn.js's hand-typed
|
|
1024
|
+
// PACKAGE_VERSION literal every cut (gh#3240/invariant 9), and that
|
|
1025
|
+
// literal is embedded verbatim in three committed dist bundles
|
|
1026
|
+
// (web-components.min.js, everything.min.js, chat-shell.min.js) —
|
|
1027
|
+
// same "derived artifact embeds the lockstep version" class as 4d.5-4d.7
|
|
1028
|
+
// above. Gate 9 (check:js-bundles-fresh) runs pre-bump in Step 3 and only
|
|
1029
|
+
// proves freshness against the PREVIOUS version; relying on the
|
|
1030
|
+
// derived-resync bot to rebuild post-merge left gate 9 red in the
|
|
1031
|
+
// handoff pre-flight (AGENTS.md: committed dist/ bundles are PR-blocking,
|
|
1032
|
+
// Class C — never a derived-resync-only artifact). Rebuild here, under
|
|
1033
|
+
// the same npm-ci layout Step 1 already asserted for gate 9, and verify:
|
|
1034
|
+
console.log('\n Step 4d.8 — rebuild dist CSS+JS bundles (icons-cdn.js pin embeds the lockstep bump):');
|
|
1035
|
+
sh('npm run build:bundles', args);
|
|
1036
|
+
if (args.dry) {
|
|
1037
|
+
console.log(' [dry] npm run check:css-bundles-fresh && npm run check:js-bundles-fresh (post-rebuild proof)');
|
|
1038
|
+
} else if (!shOk('npm run check:css-bundles-fresh') || !shOk('npm run check:js-bundles-fresh')) {
|
|
1039
|
+
console.error('\nERROR: dist bundles still stale after rebuild — build:bundles did not produce');
|
|
1040
|
+
console.error(' output matching check:css-bundles-fresh/check:js-bundles-fresh\'s expectations.');
|
|
1041
|
+
console.error(' Run both by hand to diagnose before re-running the cut.');
|
|
1042
|
+
process.exit(1);
|
|
1043
|
+
} else {
|
|
1044
|
+
console.log(' ✓ dist CSS+JS bundles fresh after rebuild');
|
|
1045
|
+
}
|
|
1046
|
+
|
|
976
1047
|
// Step 4e — generate docs/ops/releases/vX.Y.Z.md (gh#399: neither this step nor
|
|
977
1048
|
// its staging into the release commit existed before — check-cut-hygiene's
|
|
978
1049
|
// "notes present" claim (Step 4g, below) went unverified until
|
|
@@ -1002,6 +1073,27 @@ function step4PromoteAndBump(args) {
|
|
|
1002
1073
|
}
|
|
1003
1074
|
}
|
|
1004
1075
|
|
|
1076
|
+
// gh#3361: derive Step 5's PINNED_REFS-covered allowlist entries directly
|
|
1077
|
+
// from bump.mjs's own tables, instead of hand-listing the same paths a
|
|
1078
|
+
// second time — the drift class this closes structurally (gh#1198, gh#1899,
|
|
1079
|
+
// gh#1954, gh#2473, gh#3342): every one of those was bump.mjs gaining a new
|
|
1080
|
+
// pinned file that release-pack.mjs's Step 5 didn't independently know
|
|
1081
|
+
// about. A package-keyed entry becomes `<pkgDir>/<file>`; a repo-level entry
|
|
1082
|
+
// (REPO_PINNED_REFS, e.g. the root README's "Current version" claim) is
|
|
1083
|
+
// already repo-root-relative. Pure — the selftest below asserts the
|
|
1084
|
+
// derivation against a fixture table, and separately that it agrees with
|
|
1085
|
+
// the REAL imported PINNED_REFS/REPO_PINNED_REFS for every entry currently
|
|
1086
|
+
// known (so an entry added to bump.mjs without a corresponding
|
|
1087
|
+
// release-pack.mjs edit still shows up here for free).
|
|
1088
|
+
function pinnedRefFiles(pinnedRefs, repoPinnedRefs) {
|
|
1089
|
+
const files = [];
|
|
1090
|
+
for (const [pkgDir, cfgs] of Object.entries(pinnedRefs)) {
|
|
1091
|
+
for (const cfg of cfgs) files.push(`${pkgDir}/${cfg.file}`);
|
|
1092
|
+
}
|
|
1093
|
+
for (const cfg of repoPinnedRefs) files.push(cfg.file);
|
|
1094
|
+
return files;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1005
1097
|
// Pure so the selftest can lock the parse without a real git tree (gh#2473,
|
|
1006
1098
|
// same "pure so selftest can lock the transform" shape as bump.mjs's
|
|
1007
1099
|
// bumpPinnedRef). Takes `git status --porcelain` output, returns the tracked
|
|
@@ -1069,10 +1161,10 @@ function step5Commit(args) {
|
|
|
1069
1161
|
sh('git reset HEAD >/dev/null 2>&1 || true', args);
|
|
1070
1162
|
const releaseFiles = [
|
|
1071
1163
|
'package-lock.json',
|
|
1072
|
-
// Root README's "Current version" claim
|
|
1073
|
-
//
|
|
1074
|
-
//
|
|
1075
|
-
|
|
1164
|
+
// Root README's "Current version" claim (gate 18, check-cut-hygiene
|
|
1165
|
+
// doc-currency, exact-matches it against the cut version) comes from
|
|
1166
|
+
// pinnedRefFiles(REPO_PINNED_REFS) below — bump.mjs's own REPO_PINNED_REFS
|
|
1167
|
+
// table names this exact file (gh#3361).
|
|
1076
1168
|
// Root CHANGELOG — the gh#453 promotion machinery renames its
|
|
1077
1169
|
// [Unreleased] heading at Step 4, but this roster predated it, so the
|
|
1078
1170
|
// v0.8.20 cut promoted the heading and then shipped WITHOUT it (the
|
|
@@ -1097,6 +1189,17 @@ function step5Commit(args) {
|
|
|
1097
1189
|
'packages/genui/adia-catalog/adia.agent.json',
|
|
1098
1190
|
'packages/genui/adia-catalog/adia.shells.json',
|
|
1099
1191
|
'packages/genui/adia-catalog/catalog-data.js',
|
|
1192
|
+
// gh#3342/gh#3361: packages/web-components/core/icons-cdn.js (bump.mjs's
|
|
1193
|
+
// PACKAGE_VERSION pin, fifth instance of this drift class — gh#1198,
|
|
1194
|
+
// gh#1899, gh#1954, gh#2473) comes from pinnedRefFiles(PINNED_REFS)
|
|
1195
|
+
// below, not a hand-typed entry — bump.mjs's own PINNED_REFS table names
|
|
1196
|
+
// it, so it can't drift out of this list again.
|
|
1197
|
+
// gh#3342: Step 4d.8 rebuilds the dist CSS+JS bundles post-bump (the
|
|
1198
|
+
// icons-cdn.js PACKAGE_VERSION pin above is embedded in three of them) —
|
|
1199
|
+
// directory pathspecs, `git add` stages whatever the rebuild actually
|
|
1200
|
+
// changed, same shape as the chunk-corpus/skills-glob entries below.
|
|
1201
|
+
'packages/web-components/dist',
|
|
1202
|
+
'packages/web-modules/dist',
|
|
1100
1203
|
// gh#1361: Step 4d.5b regenerates the catalog tier index post-bump (it
|
|
1101
1204
|
// derives from the catalog derive-genui-catalog.mjs just refreshed) and,
|
|
1102
1205
|
// when that regen goes stale against the chunk harvester's recorded
|
|
@@ -1122,7 +1225,7 @@ function step5Commit(args) {
|
|
|
1122
1225
|
// targetVersionArg comment above). Under --skip-gates the record was
|
|
1123
1226
|
// never written (Step 3 returned early), so staging it would make the
|
|
1124
1227
|
// whole `git add` fail on a missing pathspec (PR #1204 review).
|
|
1125
|
-
...(args.skipGates ? [] : [`evals/health/${args.version}.json`]),
|
|
1228
|
+
...(args.skipGates ? [] : [`packages/gen-ui/engine/evals/health/${args.version}.json`]),
|
|
1126
1229
|
...LOCKSTEP_ROSTER.flatMap(({ name, dir, plugin }) => {
|
|
1127
1230
|
// Roster-driven (H3): each package stages its manifest + CHANGELOG;
|
|
1128
1231
|
// plugins also stage .claude-plugin/plugin.json (the /plugin-update
|
|
@@ -1147,19 +1250,53 @@ function step5Commit(args) {
|
|
|
1147
1250
|
// Codex `agents/openai.yaml` glob above is.
|
|
1148
1251
|
files.push(`${dir}/plugin.yaml`, `${dir}/__init__.py`, `${dir}/prompts/*.md`);
|
|
1149
1252
|
}
|
|
1150
|
-
// gh#
|
|
1151
|
-
//
|
|
1152
|
-
//
|
|
1153
|
-
//
|
|
1154
|
-
//
|
|
1155
|
-
//
|
|
1156
|
-
|
|
1157
|
-
// (below) started catching it.
|
|
1158
|
-
if (name === 'adia-ui-factory') files.push(`${dir}/.mcp.json`, `${dir}/hermes-mcp.yaml`, `${dir}/README.md`);
|
|
1253
|
+
// gh#1954: Step 4d.7 also regenerates the factory's Hermes/Pi
|
|
1254
|
+
// hermes-mcp.yaml fragment post-bump — not PINNED_REFS-covered (it's
|
|
1255
|
+
// a manifest-generator output, not a version-pin literal), so it
|
|
1256
|
+
// stays hand-listed here. factory's `.mcp.json` + `README.md`
|
|
1257
|
+
// generation-MCP pins (gh#1198, gh#2473 — same invariant-8 scope) come
|
|
1258
|
+
// from pinnedRefFiles(PINNED_REFS) below instead (gh#3361).
|
|
1259
|
+
if (name === 'adia-ui-factory') files.push(`${dir}/hermes-mcp.yaml`);
|
|
1159
1260
|
return files;
|
|
1160
1261
|
}),
|
|
1262
|
+
// gh#3361: every PINNED_REFS/REPO_PINNED_REFS-covered file, derived
|
|
1263
|
+
// straight from bump.mjs's own tables — see pinnedRefFiles()'s header
|
|
1264
|
+
// comment for what this closes. Deduped below against entries this list
|
|
1265
|
+
// already carries for OTHER reasons (e.g. every roster package's own
|
|
1266
|
+
// package.json is already staged above; web-modules' peer-pin entry
|
|
1267
|
+
// targets that same package.json).
|
|
1268
|
+
...pinnedRefFiles(PINNED_REFS, REPO_PINNED_REFS),
|
|
1269
|
+
// gh#3946 (v0.8.60 cut hygiene, item 1), root-caused for real at the
|
|
1270
|
+
// v0.8.61 cut: the changelog-fragment assembly step
|
|
1271
|
+
// (assemble-changelog-fragments.mjs) DELETES each consumed
|
|
1272
|
+
// `changes/<pr>.md`/`changes/gh-<issue>.md` fragment (and its
|
|
1273
|
+
// package-suffixed siblings) after folding it into the target
|
|
1274
|
+
// CHANGELOG's own `[Unreleased]` section, a deletion of an
|
|
1275
|
+
// already-tracked file is exactly the 'D' shape Step 5.6's
|
|
1276
|
+
// unstagedTrackedLeftovers() guard blocks on below, same as any other
|
|
1277
|
+
// tracked modification this list must stage first. The v0.8.60 fix
|
|
1278
|
+
// used a shell glob (`changes/*.md`) on the WRONG theory that an
|
|
1279
|
+
// unmatched glob falls through to git's own pathspec wildcard matching:
|
|
1280
|
+
// that only holds when NOTHING in the directory survives. `changes/`
|
|
1281
|
+
// also holds `README.md`, never a fragment, never deleted, so the shell
|
|
1282
|
+
// glob reliably expands to exactly that one survivor and `git add`
|
|
1283
|
+
// never sees a pathspec covering the deleted fragments at all, they
|
|
1284
|
+
// stay unstaged with no error until Step 5.6's guard catches them
|
|
1285
|
+
// downstream (confirmed live at the v0.8.61 cut: 194 fragments deleted,
|
|
1286
|
+
// one survivor, exactly this shape). The correct form names the
|
|
1287
|
+
// DIRECTORY, not a glob inside it: git enumerates the pathspec itself
|
|
1288
|
+
// against its own index rather than asking the shell to enumerate the
|
|
1289
|
+
// working tree first, so it stages every tracked change under
|
|
1290
|
+
// `changes/` including a deletion, regardless of how many (if any)
|
|
1291
|
+
// sibling files still exist on disk. bump.mjs's own PACKAGE_VERSION-
|
|
1292
|
+
// literal pin writes into `packages/web-modules/index.css` too (the
|
|
1293
|
+
// same drift class as the icons-cdn.js entry pinnedRefFiles() already
|
|
1294
|
+
// covers above), so it needs the same explicit, hand-listed entry.
|
|
1295
|
+
'changes/',
|
|
1296
|
+
'packages/web-modules/index.css',
|
|
1161
1297
|
];
|
|
1162
|
-
|
|
1298
|
+
const dedupedReleaseFiles = [...new Set(releaseFiles)];
|
|
1299
|
+
sh(`git add ${dedupedReleaseFiles.join(' ')}`, args);
|
|
1163
1300
|
sh('git diff --cached --stat | tail -3', args);
|
|
1164
1301
|
|
|
1165
1302
|
// Step 5.6 — unstaged-tracked-files guard (gh#2473). Numbered 5.6, not
|
|
@@ -1317,6 +1454,77 @@ function registrySnapshot(args) {
|
|
|
1317
1454
|
return `Registry state BEFORE publish (target = ${args.version}):\n${lines.join('\n')}`;
|
|
1318
1455
|
}
|
|
1319
1456
|
|
|
1457
|
+
async function sleepMs(ms) {
|
|
1458
|
+
return new Promise((res) => setTimeout(res, ms));
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
// Registry truth for the Step 9 poll — which packages (plus, as the sentinel
|
|
1462
|
+
// entry 'dist-tags.latest', the tag itself) have NOT yet converged on the
|
|
1463
|
+
// target version. [] means fully converged.
|
|
1464
|
+
function registryStalePackages(args, scope, version) {
|
|
1465
|
+
const stale = [];
|
|
1466
|
+
for (const pkg of PACKAGES) {
|
|
1467
|
+
let v;
|
|
1468
|
+
try { v = execSync(`npm view "${scope}/${pkg}" version`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim(); }
|
|
1469
|
+
catch { v = null; }
|
|
1470
|
+
if (v !== version) stale.push(pkg);
|
|
1471
|
+
}
|
|
1472
|
+
let latest;
|
|
1473
|
+
try { latest = execSync(`npm view ${scope}/web-components dist-tags.latest`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim(); }
|
|
1474
|
+
catch { latest = null; }
|
|
1475
|
+
if (latest !== version) stale.push('dist-tags.latest');
|
|
1476
|
+
return stale;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
// gh#3342: npm now stages a large publish ASYNCHRONOUSLY — the tarball can
|
|
1480
|
+
// take up to ~25 minutes to actually become visible on `npm view`, well past
|
|
1481
|
+
// this poll's former 10-minute bound (the v0.8.59 cut hit exactly this). A
|
|
1482
|
+
// re-dispatch of the publish workflow while a package is in that state fails
|
|
1483
|
+
// its own `npm publish` step with npm error E409 ("Cannot publish over
|
|
1484
|
+
// previously staged version") — that error IS the staged-not-lost signal
|
|
1485
|
+
// (npm rejected the duplicate BECAUSE the real publish already landed
|
|
1486
|
+
// server-side), never a real failure. Check the package's latest
|
|
1487
|
+
// publish-<pkg>.yml run: a FAILED conclusion whose log matches the E409
|
|
1488
|
+
// signature confirms staged-not-lost; anything else (no failed run, or a
|
|
1489
|
+
// failure that isn't E409) is a genuine miss. Never throws — a gh query
|
|
1490
|
+
// failure just means "not confirmed E409", the conservative default so a
|
|
1491
|
+
// genuine miss is never masked.
|
|
1492
|
+
function publishRunFailedWithE409(pkg) {
|
|
1493
|
+
try {
|
|
1494
|
+
const runId = execSync(
|
|
1495
|
+
`gh run list --workflow=publish-${pkg}.yml --limit 1 --json databaseId,conclusion -q '.[0] | select(.conclusion=="failure") | .databaseId'`,
|
|
1496
|
+
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] },
|
|
1497
|
+
).trim();
|
|
1498
|
+
if (!runId) return false;
|
|
1499
|
+
const log = execSync(`gh run view ${runId} --log-failed`, { encoding: 'utf8', maxBuffer: 16 * 1024 * 1024, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
1500
|
+
return /E409|previously staged/i.test(log);
|
|
1501
|
+
} catch {
|
|
1502
|
+
return false;
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
// Poll the registry every 15s up to `minutes`, logging progress the same way
|
|
1507
|
+
// the original bash loop did. Returns the still-stale list (empty = fully
|
|
1508
|
+
// converged) — never throws; the caller decides what a non-empty result means.
|
|
1509
|
+
async function pollRegistry(args, scope, version, minutes, label) {
|
|
1510
|
+
if (args.dry) {
|
|
1511
|
+
console.log(` [dry] ${label} poll skipped (up to ${minutes}min, every 15s)`);
|
|
1512
|
+
return [];
|
|
1513
|
+
}
|
|
1514
|
+
const checks = Math.max(1, Math.round((minutes * 60) / 15));
|
|
1515
|
+
let stale = PACKAGES.length ? [...PACKAGES, 'dist-tags.latest'] : [];
|
|
1516
|
+
for (let i = 1; i <= checks; i++) {
|
|
1517
|
+
stale = registryStalePackages(args, scope, version);
|
|
1518
|
+
if (stale.length === 0) {
|
|
1519
|
+
console.log(` ✓ ${label} confirms all ${PACKAGES.length} + latest, after ${i} check(s)`);
|
|
1520
|
+
return [];
|
|
1521
|
+
}
|
|
1522
|
+
if (i < checks) await sleepMs(15000);
|
|
1523
|
+
}
|
|
1524
|
+
console.log(` ${label} did not converge after ${minutes}min — still stale: ${stale.join(', ')}`);
|
|
1525
|
+
return stale;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1320
1528
|
async function step9Publish(args) {
|
|
1321
1529
|
console.log('\n=== Step 9 — Verify-triggered + re-dispatch misses ===');
|
|
1322
1530
|
const evidence = registrySnapshot(args);
|
|
@@ -1334,39 +1542,105 @@ async function step9Publish(args) {
|
|
|
1334
1542
|
// Poll the REGISTRY itself instead — the skill's own doctrine (SKILL.md:
|
|
1335
1543
|
// "the registry is the verify target, never a workflow's green check")
|
|
1336
1544
|
// applies here as much as it does to the final check.
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1545
|
+
//
|
|
1546
|
+
// gh#3342: the poll window moved from a flat 10 minutes to
|
|
1547
|
+
// REGISTRY_POLL_MINUTES (default 30) — npm stages a large publish
|
|
1548
|
+
// ASYNCHRONOUSLY and the tarball can take up to ~25 minutes to become
|
|
1549
|
+
// visible on `npm view`, past the old bound (the v0.8.59 cut hit this and
|
|
1550
|
+
// exited 1 with every publish run actually green).
|
|
1551
|
+
const pollMinutes = Number(process.env.REGISTRY_POLL_MINUTES) || 30;
|
|
1552
|
+
console.log(`\n Waiting for the registry to confirm all ${PACKAGES.length} packages at ${args.version} (polling every 15s, up to ${pollMinutes}min)...`);
|
|
1553
|
+
let stale = await pollRegistry(args, args.scope, args.version, pollMinutes, 'registry');
|
|
1554
|
+
|
|
1555
|
+
// gh#3342: a still-stale package here may be genuinely missing, OR it may
|
|
1556
|
+
// be staged-but-not-yet-visible — distinguish via its latest publish run.
|
|
1557
|
+
// A run that failed specifically on npm's E409 ("Cannot publish over
|
|
1558
|
+
// previously staged version") proves the package IS staged: npm rejected
|
|
1559
|
+
// the (redundant) publish attempt BECAUSE the real one already landed
|
|
1560
|
+
// server-side. That is the staged-not-lost signal this ticket exists to
|
|
1561
|
+
// handle — never a hard failure, and never itself grounds to re-dispatch
|
|
1562
|
+
// again (a THIRD publish attempt would only 409 again). Give
|
|
1563
|
+
// E409-confirmed packages one more extended, isolated poll; a package
|
|
1564
|
+
// with no E409 signal is a genuine miss and skips straight to the
|
|
1565
|
+
// fail-closed report below.
|
|
1566
|
+
if (stale.length > 0 && !args.dry) {
|
|
1567
|
+
const staleePkgs = stale.filter((p) => p !== 'dist-tags.latest');
|
|
1568
|
+
const e409 = staleePkgs.filter((pkg) => publishRunFailedWithE409(pkg));
|
|
1569
|
+
if (e409.length > 0) {
|
|
1570
|
+
console.log(`\n ${e409.length} stale package(s) confirmed E409 (staged, not lost) — extending the poll another ${pollMinutes}min: ${e409.join(', ')}`);
|
|
1571
|
+
console.log(' (a genuinely missing package would show no failed run, or a failure that is not E409 — those are NOT extended)');
|
|
1572
|
+
// The fail-closed block below re-queries the registry fresh regardless
|
|
1573
|
+
// of this poll's own result — this extended wait exists to let a
|
|
1574
|
+
// genuinely staged package converge before that final read, not to
|
|
1575
|
+
// compute the verdict itself.
|
|
1576
|
+
await pollRegistry(args, args.scope, args.version, pollMinutes, 'extended registry poll');
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1353
1580
|
// Fail-closed: the registry is the verify target (SKILL.md) — a missing or
|
|
1354
1581
|
// stale package aborts the cut here, before GH releases + site deploy. This
|
|
1355
|
-
// is now a final confirmation of the poll above, not the first real read.
|
|
1582
|
+
// is now a final confirmation of the poll(s) above, not the first real read.
|
|
1356
1583
|
console.log(`\n Post-publish registry verification, all ${PACKAGES.length} at ${args.version} + npm latest (fail-closed):`);
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
`
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
`
|
|
1368
|
-
|
|
1369
|
-
|
|
1584
|
+
if (args.dry) {
|
|
1585
|
+
console.log(' [dry] registry re-verify skipped');
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
const final = registryStalePackages(args, args.scope, args.version);
|
|
1589
|
+
for (const pkg of PACKAGES) {
|
|
1590
|
+
console.log(` ${pkg}: ${final.includes(pkg) ? 'MISSING' : args.version}`);
|
|
1591
|
+
}
|
|
1592
|
+
console.log(` latest: ${final.includes('dist-tags.latest') ? 'MISSING' : args.version}`);
|
|
1593
|
+
if (final.length > 0) {
|
|
1594
|
+
console.error(`ERROR: registry does not confirm ${args.version} across all ${PACKAGES.length} packages — fix before GH releases/deploy`);
|
|
1595
|
+
console.error(` still stale: ${final.join(', ')}`);
|
|
1596
|
+
process.exit(1);
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
// Precondition for `--from-step10` (critic finding on PR #3364): jumping
|
|
1601
|
+
// straight to Step 10 without checking the release tags exist is a silent
|
|
1602
|
+
// trap — `gh release create <tag>` on a tag that doesn't exist yet mints a
|
|
1603
|
+
// NEW lightweight tag at whatever HEAD happens to be, not the real tagged
|
|
1604
|
+
// release commit. Refuse the resume outright unless every tag Step 6 would
|
|
1605
|
+
// have created (umbrella + all per-package) already exists on origin and
|
|
1606
|
+
// points at HEAD.
|
|
1607
|
+
function assertReleaseTagsAtHead(args) {
|
|
1608
|
+
console.log('\n Precondition — verifying release tags already exist and point at HEAD before resuming at Step 10:');
|
|
1609
|
+
const headSha = shQuiet('git rev-parse HEAD', args).trim();
|
|
1610
|
+
const tags = [`v${args.version}`, ...PACKAGES.map((p) => `${p}-v${args.version}`)];
|
|
1611
|
+
if (args.dry) {
|
|
1612
|
+
console.log(` [dry] git fetch origin ${tags.map((t) => `tag ${t}`).join(' ')}`);
|
|
1613
|
+
console.log(` [dry] verify each of ${tags.length} tags resolves to HEAD (${headSha || '<dry HEAD>'})`);
|
|
1614
|
+
return;
|
|
1615
|
+
}
|
|
1616
|
+
sh(`git fetch origin ${tags.map((t) => `tag ${t}`).join(' ')}`, args);
|
|
1617
|
+
const missing = [];
|
|
1618
|
+
const mismatched = [];
|
|
1619
|
+
for (const tag of tags) {
|
|
1620
|
+
let tagSha;
|
|
1621
|
+
try {
|
|
1622
|
+
tagSha = execSync(`git rev-parse "${tag}^{commit}"`, { cwd: REPO, encoding: 'utf8' }).trim();
|
|
1623
|
+
} catch {
|
|
1624
|
+
missing.push(tag);
|
|
1625
|
+
continue;
|
|
1626
|
+
}
|
|
1627
|
+
if (tagSha !== headSha) mismatched.push(`${tag} -> ${tagSha}`);
|
|
1628
|
+
}
|
|
1629
|
+
if (missing.length > 0 || mismatched.length > 0) {
|
|
1630
|
+
console.error('\nERROR: --from-step10 refuses to resume — the release tags this handoff needs are not all present at HEAD.');
|
|
1631
|
+
if (missing.length > 0) {
|
|
1632
|
+
console.error(` Missing entirely (Step 6 creates these, Step 8 pushes them to origin): ${missing.join(', ')}`);
|
|
1633
|
+
}
|
|
1634
|
+
if (mismatched.length > 0) {
|
|
1635
|
+
console.error(` Point somewhere other than HEAD (${headSha}): ${mismatched.join(', ')}`);
|
|
1636
|
+
}
|
|
1637
|
+
console.error(' Resuming Step 10 here would let `gh release create` mint NEW lightweight tags at the');
|
|
1638
|
+
console.error(' current HEAD instead of using the real tagged release commit — silently wrong.');
|
|
1639
|
+
console.error(' Fix: confirm the original handoff run actually reached Step 8 (tags pushed to origin)');
|
|
1640
|
+
console.error(' and that HEAD has not moved since, or re-run the full handoff instead of --from-step10.');
|
|
1641
|
+
process.exit(1);
|
|
1642
|
+
}
|
|
1643
|
+
console.log(` ✓ all ${tags.length} release tags exist on origin and point at HEAD (${headSha})`);
|
|
1370
1644
|
}
|
|
1371
1645
|
|
|
1372
1646
|
// ── Step 10 — GH releases + site deploy ──────────────────────────
|
|
@@ -1501,23 +1775,36 @@ async function main() {
|
|
|
1501
1775
|
// Fail-fast guard: refuse to git/npm/gh/curl against a non-monorepo directory.
|
|
1502
1776
|
assertMonorepoRoot(REPO);
|
|
1503
1777
|
console.log(`package-release release-pack — v${args.version} (${args.mode})${args.dry ? ' [DRY]' : ''}`);
|
|
1504
|
-
// Stale-plugin-cache self-check (v0.8.32 cut, first failure
|
|
1505
|
-
// plugin cache only refreshes on a
|
|
1506
|
-
//
|
|
1507
|
-
//
|
|
1508
|
-
//
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1778
|
+
// Stale-plugin-cache self-check (v0.8.32 cut, first failure; generalized
|
|
1779
|
+
// ticket #3943 item 2): the INSTALLED plugin cache only refreshes on a
|
|
1780
|
+
// version bump, so running this script from the cache mid-cycle can
|
|
1781
|
+
// execute a version that predates its own guards (SKILL.md §Plugin
|
|
1782
|
+
// cache). If the repo carries its own copy and the bytes differ, warn
|
|
1783
|
+
// loudly: the repo copy is almost always the newer one. The comparison
|
|
1784
|
+
// itself now lives in this skill's own stale-copy-warning.mjs (package-
|
|
1785
|
+
// local by design, see that file's own header), a small primitive its
|
|
1786
|
+
// sibling scripts share, rather than this inline block being the only
|
|
1787
|
+
// copy of the pattern.
|
|
1788
|
+
warnIfStaleCopy(
|
|
1789
|
+
import.meta.url,
|
|
1790
|
+
REPO,
|
|
1791
|
+
'packages/plugins/adia-ui-forge/skills/package-release/scripts/release-pack.mjs',
|
|
1792
|
+
);
|
|
1793
|
+
// gh#3342: --from-step10 resumes a handoff that already tagged, pushed,
|
|
1794
|
+
// and published — skip straight to Step 10 (GH releases + site deploy)
|
|
1795
|
+
// rather than a plain re-run, which would re-run the full pre-flight gate
|
|
1796
|
+
// roster (Step 3, ~15min) AND re-tag at HEAD (Step 6, wrong if anything
|
|
1797
|
+
// merged since the original tag). Every step this skips is either
|
|
1798
|
+
// idempotent-but-wasteful (3, 9) or actively wrong to repeat (6, if HEAD
|
|
1799
|
+
// moved) — a real operational need the v0.8.59 cut hit and worked around
|
|
1800
|
+
// with a one-off, never-checked-in script.
|
|
1801
|
+
if (args.mode === 'handoff' && args.fromStep10) {
|
|
1802
|
+
console.log('\n[resume --from-step10] skipping Steps 1/3/4/5/6/7/8/9 (already tagged/pushed/published) — jumping straight to Step 10.');
|
|
1803
|
+
assertReleaseTagsAtHead(args);
|
|
1804
|
+
await step10GhAndSite(args);
|
|
1805
|
+
rl.close();
|
|
1806
|
+
console.log('\n✓ release-pack complete (resumed at Step 10). Verify against the npm registry + GH releases, then author the release notes.');
|
|
1807
|
+
return;
|
|
1521
1808
|
}
|
|
1522
1809
|
// gh#765: an aborted Step-4 run leaves bumped versions + promoted
|
|
1523
1810
|
// CHANGELOGs; detect BEFORE pre-flight so gate 24 validates at the right
|
|
@@ -1652,6 +1939,50 @@ async function selftest() {
|
|
|
1652
1939
|
}
|
|
1653
1940
|
}
|
|
1654
1941
|
|
|
1942
|
+
// gh#3361 — pinnedRefFiles() must derive `<pkgDir>/<file>` for a
|
|
1943
|
+
// package-keyed entry and the bare `file` for a repo-level entry, from a
|
|
1944
|
+
// FIXTURE table (independent of bump.mjs's real, evolving content).
|
|
1945
|
+
{
|
|
1946
|
+
const fixturePinned = {
|
|
1947
|
+
'packages/plugins/fake-plugin': [{ file: '.mcp.json' }, { file: 'README.md' }],
|
|
1948
|
+
'packages/fake-pkg': [{ file: 'core/pinned.js' }],
|
|
1949
|
+
};
|
|
1950
|
+
const fixtureRepoPinned = [{ file: 'README.md' }];
|
|
1951
|
+
const got = pinnedRefFiles(fixturePinned, fixtureRepoPinned);
|
|
1952
|
+
const want = [
|
|
1953
|
+
'packages/plugins/fake-plugin/.mcp.json',
|
|
1954
|
+
'packages/plugins/fake-plugin/README.md',
|
|
1955
|
+
'packages/fake-pkg/core/pinned.js',
|
|
1956
|
+
'README.md',
|
|
1957
|
+
];
|
|
1958
|
+
if (JSON.stringify(got) !== JSON.stringify(want)) {
|
|
1959
|
+
console.error(`selftest FAIL: pinnedRefFiles() expected ${JSON.stringify(want)}, got ${JSON.stringify(got)}`);
|
|
1960
|
+
process.exit(1);
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
// gh#3361 — the structural guarantee this ticket exists for: every path
|
|
1964
|
+
// bump.mjs's REAL, imported PINNED_REFS/REPO_PINNED_REFS tables name is
|
|
1965
|
+
// present in Step 5's assembled releaseFiles (the `--mode cut --dry`
|
|
1966
|
+
// subprocess run below), proven by literally re-deriving the expected
|
|
1967
|
+
// set from the SAME imported tables the subprocess itself reads — a
|
|
1968
|
+
// future PINNED_REFS entry with no matching releaseFiles line (the
|
|
1969
|
+
// gh#1198/1899/1954/2473/3342 drift shape) fails HERE, not three cuts
|
|
1970
|
+
// later at Step 5.6.
|
|
1971
|
+
{
|
|
1972
|
+
const expectedPinned = pinnedRefFiles(PINNED_REFS, REPO_PINNED_REFS);
|
|
1973
|
+
if (expectedPinned.length === 0) {
|
|
1974
|
+
console.error('selftest FAIL: bump.mjs\'s real PINNED_REFS/REPO_PINNED_REFS produced zero derived paths — the import is broken');
|
|
1975
|
+
process.exit(1);
|
|
1976
|
+
}
|
|
1977
|
+
// The fixture set itself must never silently reduce to nothing new —
|
|
1978
|
+
// proves gh#3342's icons-cdn.js entry specifically survived the
|
|
1979
|
+
// gh#3361 refactor (it's the exact file that motivated this ticket).
|
|
1980
|
+
if (!expectedPinned.includes('packages/web-components/core/icons-cdn.js')) {
|
|
1981
|
+
console.error(`selftest FAIL: pinnedRefFiles(PINNED_REFS, REPO_PINNED_REFS) is missing packages/web-components/core/icons-cdn.js — got ${JSON.stringify(expectedPinned)}`);
|
|
1982
|
+
process.exit(1);
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1655
1986
|
// gh#2473 — Step 5.6's guard: unstagedTrackedLeftovers() must find a
|
|
1656
1987
|
// plain modified/deleted tracked file, unquote a C-quoted path
|
|
1657
1988
|
// (spaces), and report the NEW side of a rename/copy — all from a
|
|
@@ -1715,12 +2046,17 @@ async function selftest() {
|
|
|
1715
2046
|
`${GATE_ROSTER.length}-gate roster`,
|
|
1716
2047
|
`✓ all ${GATE_ROSTER.length}/${GATE_ROSTER.length} gates passed`,
|
|
1717
2048
|
'STOPPING per invariant 3',
|
|
2049
|
+
// gh#3342 — gate 4 needs llm/agent/persona's BUILT (tsc) output, not
|
|
2050
|
+
// just conditionally regenerated content; the precondition must run
|
|
2051
|
+
// even in a --dry preview, before the gate roster itself.
|
|
2052
|
+
'Precondition: tsc build (llm, agent, persona',
|
|
2053
|
+
'[dry] npm run build -w @adia-ai/llm -w @adia-ai/agent -w @adia-ai/persona',
|
|
1718
2054
|
// gh#358 — the release branch must actually be created, not just documented.
|
|
1719
2055
|
'git checkout -b release/v9.9.9',
|
|
1720
2056
|
// gh#1197 — Step 5's staging pathspec must carry gate 28's committed
|
|
1721
2057
|
// health record, or it rides untracked past the release commit
|
|
1722
2058
|
// (v0.8.35/v0.8.36 both did, PR #1196 swept them by hand).
|
|
1723
|
-
'evals/health/9.9.9.json',
|
|
2059
|
+
'packages/gen-ui/engine/evals/health/9.9.9.json',
|
|
1724
2060
|
// gh#1899 — Step 4 must regenerate the Codex plugin manifests post-bump
|
|
1725
2061
|
// (the v0.8.48 release PR #1897 shipped stale ones without this step).
|
|
1726
2062
|
'Step 4d.6 — regenerate Codex plugin manifests',
|
|
@@ -1752,6 +2088,31 @@ async function selftest() {
|
|
|
1752
2088
|
// README target), or it rides unstaged/uncommitted past the release
|
|
1753
2089
|
// commit the same way .mcp.json itself did before gh#1198.
|
|
1754
2090
|
'packages/plugins/adia-ui-factory/README.md',
|
|
2091
|
+
// gh#3342 — fifth instance of the same allowlist-drift class
|
|
2092
|
+
// (gh#1198, gh#1899, gh#1954, gh#2473): bump.mjs's web-components
|
|
2093
|
+
// PINNED_REFS entry moves icons-cdn.js's PACKAGE_VERSION literal
|
|
2094
|
+
// every cut; the v0.8.59 cut hit Step 5.6's guard on this exact file.
|
|
2095
|
+
'packages/web-components/core/icons-cdn.js',
|
|
2096
|
+
// gh#3342 — Step 4d.8 rebuilds the dist bundles post-bump (the
|
|
2097
|
+
// icons-cdn.js pin above is embedded in three of them) and Step 5
|
|
2098
|
+
// must stage the result, or gate 9 (check:js-bundles-fresh) fails in
|
|
2099
|
+
// the handoff pre-flight the way the v0.8.59 cut's did.
|
|
2100
|
+
'Step 4d.8 — rebuild dist CSS+JS bundles',
|
|
2101
|
+
'[dry] npm run build:bundles',
|
|
2102
|
+
'packages/web-components/dist',
|
|
2103
|
+
'packages/web-modules/dist',
|
|
2104
|
+
// gh#3946 (v0.8.60 cut hygiene, item 1) - bump.mjs and the changelog
|
|
2105
|
+
// assembly step both write to these two paths but neither was in
|
|
2106
|
+
// Step 5's allowlist, so the v0.8.60 cut's real Step 5.6 guard refused
|
|
2107
|
+
// with "244 tracked file(s) modified by this cut are still unstaged"
|
|
2108
|
+
// after all 36 gates had already passed. These markers prove the
|
|
2109
|
+
// `git add` pathspec this --dry run prints actually names both
|
|
2110
|
+
// entries. `changes/` is the directory form (root-caused at the
|
|
2111
|
+
// v0.8.61 cut, see the matching entry's own comment above), never a
|
|
2112
|
+
// glob: a glob that survives one non-fragment file (changes/README.md
|
|
2113
|
+
// always does) hides every deleted fragment from `git add` silently.
|
|
2114
|
+
'changes/',
|
|
2115
|
+
'packages/web-modules/index.css',
|
|
1755
2116
|
// gh#2473 — Step 5.6's unstaged-tracked-files guard must actually run
|
|
1756
2117
|
// (as a no-op) on a clean --dry plan. Its real leftover-detection
|
|
1757
2118
|
// logic is unit-tested directly above via unstagedTrackedLeftovers();
|
|
@@ -1908,6 +2269,62 @@ async function selftest() {
|
|
|
1908
2269
|
process.exit(1);
|
|
1909
2270
|
}
|
|
1910
2271
|
|
|
2272
|
+
// gh#3364 critic finding — `--from-step10` must (a) only apply to
|
|
2273
|
+
// --mode handoff, at parse time, and (b) refuse to resume without first
|
|
2274
|
+
// verifying the release tags already exist and point at HEAD (otherwise
|
|
2275
|
+
// `gh release create` on a tag that was never made mints a NEW
|
|
2276
|
+
// lightweight tag at whatever HEAD happens to be — silently wrong).
|
|
2277
|
+
let fromStep10ModeFailed = false;
|
|
2278
|
+
let fromStep10ModeOut = '';
|
|
2279
|
+
try {
|
|
2280
|
+
execSync(
|
|
2281
|
+
`node "${scriptPath}" --mode cut --version 9.9.9 --date 2026-01-01 --previous-version 9.9.8 --from-step10 --dry`,
|
|
2282
|
+
{ cwd: REPO, encoding: 'utf8' },
|
|
2283
|
+
);
|
|
2284
|
+
} catch (e) {
|
|
2285
|
+
fromStep10ModeFailed = true;
|
|
2286
|
+
fromStep10ModeOut = (e.stdout || '') + (e.stderr || '');
|
|
2287
|
+
}
|
|
2288
|
+
if (!fromStep10ModeFailed || !fromStep10ModeOut.includes('--from-step10 only applies to --mode handoff')) {
|
|
2289
|
+
console.error('selftest FAIL: --from-step10 with a non-handoff mode must hard-reject at parse time');
|
|
2290
|
+
process.exit(1);
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
let resumeOut;
|
|
2294
|
+
try {
|
|
2295
|
+
resumeOut = execSync(
|
|
2296
|
+
`node "${scriptPath}" --mode handoff --version 9.9.9 --date 2026-01-01 --previous-version 9.9.8 ` +
|
|
2297
|
+
`--gh-notes-file "${notesFile}" --from-step10 --skip-site --dry`,
|
|
2298
|
+
{ cwd: REPO, encoding: 'utf8' },
|
|
2299
|
+
);
|
|
2300
|
+
} catch (e) {
|
|
2301
|
+
console.error('selftest FAIL: --mode handoff --from-step10 --dry plan assembly exited non-zero');
|
|
2302
|
+
console.error((e.stdout || '') + (e.stderr || ''));
|
|
2303
|
+
process.exit(1);
|
|
2304
|
+
}
|
|
2305
|
+
const resumeNeed = [
|
|
2306
|
+
'[resume --from-step10] skipping Steps 1/3/4/5/6/7/8/9',
|
|
2307
|
+
'Precondition — verifying release tags already exist and point at HEAD',
|
|
2308
|
+
'[dry] git fetch origin',
|
|
2309
|
+
'=== Step 10 — GH releases + Site deploy',
|
|
2310
|
+
];
|
|
2311
|
+
for (const marker of resumeNeed) {
|
|
2312
|
+
if (!resumeOut.includes(marker)) {
|
|
2313
|
+
console.error(`selftest FAIL: expected marker not found in --from-step10 dry plan: "${marker}"`);
|
|
2314
|
+
process.exit(1);
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
// "=== Step 1" is a prefix of "=== Step 10" — every marker below must
|
|
2318
|
+
// carry enough of its own header to avoid a false positive against the
|
|
2319
|
+
// Step 10 banner this resume path is SUPPOSED to reach.
|
|
2320
|
+
const resumeMustSkip = ['=== Step 1 — Re-baseline', '=== Step 3 — Pre-flight', '=== Step 6 — Tag at HEAD', '=== Step 7 — F-N1', '=== Step 8 — Push', '=== Step 9 — Verify-triggered'];
|
|
2321
|
+
for (const marker of resumeMustSkip) {
|
|
2322
|
+
if (resumeOut.includes(marker)) {
|
|
2323
|
+
console.error(`selftest FAIL: --from-step10 must skip Steps 1-9 — found "${marker}" in the dry plan`);
|
|
2324
|
+
process.exit(1);
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
|
|
1911
2328
|
console.log('selftest OK');
|
|
1912
2329
|
} finally {
|
|
1913
2330
|
fs.rmSync(tmp, { recursive: true, force: true });
|