@adia-ai/adia-ui-forge 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/.claude-plugin/plugin.json +23 -0
  2. package/CHANGELOG.md +26 -0
  3. package/README.md +54 -0
  4. package/bin/forge-lint +263 -0
  5. package/bin/lib/audit-axes.mjs +555 -0
  6. package/bin/lib/dry-run-irreversible.mjs +236 -0
  7. package/bin/lib/run-skill-evals.mjs +487 -0
  8. package/bin/lib/teach-router.mjs +250 -0
  9. package/commands/adia-forge-a2ui.md +10 -0
  10. package/commands/adia-forge-author.md +10 -0
  11. package/commands/adia-forge-dogfood.md +8 -0
  12. package/commands/adia-forge-llm.md +8 -0
  13. package/commands/adia-forge-orient.md +10 -0
  14. package/commands/adia-forge-release.md +8 -0
  15. package/commands/adia-forge-review.md +10 -0
  16. package/hooks/hooks.json +15 -0
  17. package/package.json +41 -0
  18. package/references/shared/content-trust.md +76 -0
  19. package/references/shared/pev-rationale.md +64 -0
  20. package/references/shared/skill-conventions.md +133 -0
  21. package/skills/adia-ui-a2ui/CHANGELOG.md +32 -0
  22. package/skills/adia-ui-a2ui/SKILL.md +243 -0
  23. package/skills/adia-ui-a2ui/evals/adversarial-corpus.json +75 -0
  24. package/skills/adia-ui-a2ui/evals/routing-corpus.json +31 -0
  25. package/skills/adia-ui-a2ui/evals/teach-routing-cases.json +100 -0
  26. package/skills/adia-ui-a2ui/references/anti-patterns.md +24 -0
  27. package/skills/adia-ui-a2ui/references/chunk-authoring.md +88 -0
  28. package/skills/adia-ui-a2ui/references/corpus-discipline.md +56 -0
  29. package/skills/adia-ui-a2ui/references/eval-diagnostics.md +127 -0
  30. package/skills/adia-ui-a2ui/references/fragment-graph.md +91 -0
  31. package/skills/adia-ui-a2ui/references/mcp-pipeline-ops.md +106 -0
  32. package/skills/adia-ui-a2ui/references/mcp-tool-reference.md +398 -0
  33. package/skills/adia-ui-a2ui/references/pipeline-overview.md +175 -0
  34. package/skills/adia-ui-a2ui/references/semantic-fail-lifting.md +120 -0
  35. package/skills/adia-ui-a2ui/references/strategy-engines.md +111 -0
  36. package/skills/adia-ui-a2ui/references/teach-protocol.md +220 -0
  37. package/skills/adia-ui-a2ui/references/zettel-calibration.md +93 -0
  38. package/skills/adia-ui-a2ui/scripts/audit-a2ui-roster.mjs +96 -0
  39. package/skills/adia-ui-a2ui/scripts/teach-route.mjs +157 -0
  40. package/skills/adia-ui-a2ui/skill.json +38 -0
  41. package/skills/adia-ui-authoring/CHANGELOG.md +32 -0
  42. package/skills/adia-ui-authoring/SKILL.md +256 -0
  43. package/skills/adia-ui-authoring/assets/case-studies/admin-shell-decomposition.md +101 -0
  44. package/skills/adia-ui-authoring/assets/case-studies/maxtokens-32768-discovery.md +109 -0
  45. package/skills/adia-ui-authoring/assets/case-studies/theme-panel-promotion.md +113 -0
  46. package/skills/adia-ui-authoring/evals/adversarial-design-plan-gates.json +138 -0
  47. package/skills/adia-ui-authoring/evals/routing-corpus.json +245 -0
  48. package/skills/adia-ui-authoring/references/anti-patterns.md +606 -0
  49. package/skills/adia-ui-authoring/references/api-contract.md +205 -0
  50. package/skills/adia-ui-authoring/references/authoring-cycle.md +211 -0
  51. package/skills/adia-ui-authoring/references/canonical-pattern-index.md +179 -0
  52. package/skills/adia-ui-authoring/references/code-style.md +329 -0
  53. package/skills/adia-ui-authoring/references/common-gotchas.md +93 -0
  54. package/skills/adia-ui-authoring/references/composite-demo-protocol.md +1084 -0
  55. package/skills/adia-ui-authoring/references/css-patterns.md +364 -0
  56. package/skills/adia-ui-authoring/references/lifecycle-patterns.md +302 -0
  57. package/skills/adia-ui-authoring/references/llm-bridge.md +254 -0
  58. package/skills/adia-ui-authoring/references/module-promotion.md +289 -0
  59. package/skills/adia-ui-authoring/references/primitive-audit.md +123 -0
  60. package/skills/adia-ui-authoring/references/shell-patterns.md +561 -0
  61. package/skills/adia-ui-authoring/references/teach-protocol.md +428 -0
  62. package/skills/adia-ui-authoring/references/token-contract.md +120 -0
  63. package/skills/adia-ui-authoring/references/worked-example.md +351 -0
  64. package/skills/adia-ui-authoring/references/yaml-contract.md +224 -0
  65. package/skills/adia-ui-authoring/scripts/audit-authoring-roster.mjs +148 -0
  66. package/skills/adia-ui-authoring/scripts/build-canonical-pattern-index.mjs +199 -0
  67. package/skills/adia-ui-authoring/skill.json +45 -0
  68. package/skills/adia-ui-dogfood/CHANGELOG.md +17 -0
  69. package/skills/adia-ui-dogfood/README.md +62 -0
  70. package/skills/adia-ui-dogfood/SKILL.md +866 -0
  71. package/skills/adia-ui-dogfood/scripts/analyze.mjs +603 -0
  72. package/skills/adia-ui-dogfood/skill.json +40 -0
  73. package/skills/adia-ui-forge/SKILL.md +88 -0
  74. package/skills/adia-ui-forge/evals/routing-corpus.json +30 -0
  75. package/skills/adia-ui-gen-review/CHANGELOG.md +108 -0
  76. package/skills/adia-ui-gen-review/SKILL.md +266 -0
  77. package/skills/adia-ui-gen-review/references/loop-protocol.md +712 -0
  78. package/skills/adia-ui-gen-review/references/rubric-cosmetic.md +144 -0
  79. package/skills/adia-ui-gen-review/references/rubric-decompose.md +117 -0
  80. package/skills/adia-ui-gen-review/references/rubric-score.md +249 -0
  81. package/skills/adia-ui-gen-review/references/scores.schema.json +125 -0
  82. package/skills/adia-ui-gen-review/references/teach-protocol.md +214 -0
  83. package/skills/adia-ui-gen-review/scripts/gen-review-coverage-audit.mjs +127 -0
  84. package/skills/adia-ui-gen-review/scripts/gen-review-decompose.mjs +569 -0
  85. package/skills/adia-ui-gen-review/scripts/gen-review-status.mjs +176 -0
  86. package/skills/adia-ui-gen-review/scripts/validate-cycle-scores.mjs +198 -0
  87. package/skills/adia-ui-gen-review/skill.json +22 -0
  88. package/skills/adia-ui-llm/CHANGELOG.md +25 -0
  89. package/skills/adia-ui-llm/SKILL.md +165 -0
  90. package/skills/adia-ui-llm/evals/adversarial-corpus.json +75 -0
  91. package/skills/adia-ui-llm/evals/routing-corpus.json +30 -0
  92. package/skills/adia-ui-llm/evals/teach-routing-cases.json +73 -0
  93. package/skills/adia-ui-llm/references/adapter-contract.md +99 -0
  94. package/skills/adia-ui-llm/references/add-a-provider.md +90 -0
  95. package/skills/adia-ui-llm/references/bridge-facade.md +94 -0
  96. package/skills/adia-ui-llm/references/browser-proxy-boundary.md +89 -0
  97. package/skills/adia-ui-llm/references/model-registry.md +75 -0
  98. package/skills/adia-ui-llm/references/streaming-sse.md +95 -0
  99. package/skills/adia-ui-llm/references/teach-protocol.md +78 -0
  100. package/skills/adia-ui-llm/scripts/audit-llm-roster.mjs +93 -0
  101. package/skills/adia-ui-llm/scripts/teach-route.mjs +119 -0
  102. package/skills/adia-ui-llm/skill.json +33 -0
  103. package/skills/adia-ui-release/CHANGELOG.md +23 -0
  104. package/skills/adia-ui-release/SKILL.md +295 -0
  105. package/skills/adia-ui-release/assets/case-studies/2026-05-20-batch-push-v0.6.14-v0.6.15.md +144 -0
  106. package/skills/adia-ui-release/assets/case-studies/2026-05-20-corpus-drift-remediation-v0.6.15.md +155 -0
  107. package/skills/adia-ui-release/assets/case-studies/2026-05-20-version-skip-correction-v0.6.12.md +114 -0
  108. package/skills/adia-ui-release/assets/case-studies/2026-05-21-author-from-scratch-v0.6.18.md +139 -0
  109. package/skills/adia-ui-release/assets/case-studies/2026-05-21-feedback37-retraction-v0.6.21.md +124 -0
  110. package/skills/adia-ui-release/assets/case-studies/2026-05-21-fn1-enrichment-pass-v0.6.19.md +125 -0
  111. package/skills/adia-ui-release/assets/case-studies/2026-05-21-stale-test-detection-v0.6.20.md +142 -0
  112. package/skills/adia-ui-release/assets/case-studies/2026-05-23-freshness-gate-recovery-v0.6.32.md +97 -0
  113. package/skills/adia-ui-release/assets/case-studies/2026-05-26-catalog-drift-recurring-v0.6.40.md +147 -0
  114. package/skills/adia-ui-release/assets/templates/stub-changelog.template.md +22 -0
  115. package/skills/adia-ui-release/evals/evals.json +164 -0
  116. package/skills/adia-ui-release/references/changelog-discipline.md +250 -0
  117. package/skills/adia-ui-release/references/cycle-happy-path.md +520 -0
  118. package/skills/adia-ui-release/references/exe-deploy.md +149 -0
  119. package/skills/adia-ui-release/references/gates-catalog.md +778 -0
  120. package/skills/adia-ui-release/references/ledger-discipline.md +232 -0
  121. package/skills/adia-ui-release/references/migration-guide-authoring.md +174 -0
  122. package/skills/adia-ui-release/references/multi-agent-baseline.md +207 -0
  123. package/skills/adia-ui-release/references/notes-authoring.md +212 -0
  124. package/skills/adia-ui-release/references/recovery-paths.md +215 -0
  125. package/skills/adia-ui-release/references/rollup-notes.md +208 -0
  126. package/skills/adia-ui-release/references/teach-protocol.md +468 -0
  127. package/skills/adia-ui-release/scripts/assert-monorepo-root.mjs +49 -0
  128. package/skills/adia-ui-release/scripts/audit-gate-roster.mjs +196 -0
  129. package/skills/adia-ui-release/scripts/bump.mjs +118 -0
  130. package/skills/adia-ui-release/scripts/dispatch-publish.mjs +151 -0
  131. package/skills/adia-ui-release/scripts/insert-stub.mjs +127 -0
  132. package/skills/adia-ui-release/scripts/make-ledger.mjs +179 -0
  133. package/skills/adia-ui-release/scripts/promote-unreleased.mjs +88 -0
  134. package/skills/adia-ui-release/scripts/release-pack.mjs +350 -0
  135. package/skills/adia-ui-release/scripts/tag-lockstep.mjs +110 -0
  136. package/skills/adia-ui-release/skill.json +75 -0
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env node
2
+ // audit-gate-roster.mjs — §SelfAudit Axis 9 enforcement.
3
+ //
4
+ // Compares the `check:*` / `verify:*` / `smoke:*` / `test:*` scripts
5
+ // declared in the target repo's `package.json` against the gate roster
6
+ // documented in this skill's `references/gates-catalog.md`. Flags drift
7
+ // in either direction:
8
+ // - Gates that exist in package.json but aren't documented (new
9
+ // gate added without catalog update).
10
+ // - Gates documented in the catalog but no longer in package.json
11
+ // (gate removed without catalog cleanup).
12
+ //
13
+ // Usage (run from the @adia-ai-style monorepo root):
14
+ // node audit-gate-roster.mjs
15
+ // node audit-gate-roster.mjs --json
16
+ // node audit-gate-roster.mjs --strict # exit 1 on any drift
17
+ // node audit-gate-roster.mjs --repo /path/to/monorepo
18
+ //
19
+ // Path resolution: the gate catalog ships with the skill. It is resolved
20
+ // via ${CLAUDE_PLUGIN_ROOT}/skills/adia-ui-release/references/gates-catalog.md,
21
+ // with a fallback relative to this script (../references/gates-catalog.md)
22
+ // for when the plugin-root env var is not set.
23
+
24
+ import fs from 'node:fs';
25
+ import path from 'node:path';
26
+ import process from 'node:process';
27
+
28
+ function parseArgs(argv) {
29
+ const args = { json: false, strict: false, repo: process.cwd() };
30
+ for (let i = 0; i < argv.length; i++) {
31
+ if (argv[i] === '--json') args.json = true;
32
+ else if (argv[i] === '--strict') args.strict = true;
33
+ else if (argv[i] === '--repo') args.repo = argv[++i];
34
+ else if (argv[i] === '-h' || argv[i] === '--help') {
35
+ console.log('Usage: node audit-gate-roster.mjs [--json] [--strict] [--repo <monorepo-root>]');
36
+ process.exit(0);
37
+ }
38
+ }
39
+ return args;
40
+ }
41
+
42
+ // The gate catalog is skill-owned (ships in references/). Resolve it via the
43
+ // plugin root, falling back to a path relative to this script.
44
+ function resolveCatalog() {
45
+ const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT;
46
+ if (pluginRoot) {
47
+ const p = path.join(pluginRoot, 'skills/adia-ui-release/references/gates-catalog.md');
48
+ if (fs.existsSync(p)) return p;
49
+ }
50
+ const here = path.dirname(new URL(import.meta.url).pathname);
51
+ return path.join(here, '..', 'references', 'gates-catalog.md');
52
+ }
53
+
54
+ // The catalog documents the gates the RELEASE FLOW runs — a curated set, NOT
55
+ // every quality script in package.json (which has many check:/verify:/smoke:/
56
+ // audit:/test: scripts, most of them component-authoring audits). Two universes:
57
+ //
58
+ // - releaseGates (drives the UNDOCUMENTED check): the gates composed into the
59
+ // `npm run check` pre-flight aggregate PLUS the roster gates run directly in
60
+ // a cut (ROSTER_EXTRAS). A release-flow gate missing from the catalog is real
61
+ // drift worth flagging.
62
+ // - allScripts (drives the OBSOLETE check): every package.json script name. A
63
+ // documented gate that no longer exists ANYWHERE has been removed/renamed —
64
+ // real drift.
65
+ //
66
+ // This intentionally does NOT flag component-authoring `audit:*` scripts or
67
+ // `test:*` runner scripts as "undocumented" — they are not release-flow gates
68
+ // (audit:* are an authoring-side §SelfAudit domain, invoked indirectly via
69
+ // `check:dogfood-audits`).
70
+ const PREFLIGHT_AGGREGATE = 'check';
71
+ const ROSTER_EXTRAS = [
72
+ 'check:lockstep', 'verify:traits', 'smoke:engines', 'smoke:register-engine',
73
+ 'test:a2ui', 'test:unit', 'dogfood:status',
74
+ ];
75
+
76
+ function loadScripts(repo) {
77
+ const pkgJson = path.join(repo, 'package.json');
78
+ if (!fs.existsSync(pkgJson)) {
79
+ console.error(`error: ${pkgJson} not found (run from the monorepo root, or pass --repo)`);
80
+ process.exit(2);
81
+ }
82
+ return JSON.parse(fs.readFileSync(pkgJson, 'utf8')).scripts || {};
83
+ }
84
+
85
+ // Gates the release flow runs: every `npm run <gate>` composed into the
86
+ // `npm run check` pre-flight aggregate, plus the roster gates run directly.
87
+ function loadReleaseGates(scripts) {
88
+ const gates = new Set();
89
+ const agg = scripts[PREFLIGHT_AGGREGATE] || '';
90
+ const re = /\brun ([a-z][\w-]*(?::[\w-]+)+)/g;
91
+ let m;
92
+ while ((m = re.exec(agg)) !== null) gates.add(m[1]);
93
+ for (const g of ROSTER_EXTRAS) if (scripts[g]) gates.add(g);
94
+ return [...gates].sort();
95
+ }
96
+
97
+ function loadDocumentedGates(catalog) {
98
+ if (!fs.existsSync(catalog)) {
99
+ console.error(`error: ${catalog} not found`);
100
+ process.exit(2);
101
+ }
102
+ const txt = fs.readFileSync(catalog, 'utf8');
103
+ // Match ONLY heading-form gate documentation:
104
+ // ### `npm run <gate-name>`
105
+ // NOT inline references to `npm run X` (which are typically
106
+ // recovery commands cited in another gate's body).
107
+ const matches = new Set();
108
+ // Match `### `npm run X`` (Category 1-9 row form) OR
109
+ // `#### `npm run X`` (Category 10 stub form).
110
+ const re = /^#{3,4} `npm run ([a-z][\w-]*(?::[\w-]+)+)`(.*)$/gm;
111
+ let m;
112
+ while ((m = re.exec(txt)) !== null) {
113
+ // Skip explicitly forward-looking entries — a documented gate marked
114
+ // "(NOT YET SHIPPED — recommended)" is an intentional recommendation, not
115
+ // obsolete drift; it lives in the catalog before its script exists.
116
+ if (/NOT YET SHIPPED|recommended/i.test(m[2])) continue;
117
+ matches.add(m[1]);
118
+ }
119
+ return [...matches].sort();
120
+ }
121
+
122
+ // Suffix variants that count as documented if the base gate is documented.
123
+ // Per the §Suffix variant convention in references/gates-catalog.md.
124
+ const VARIANT_SUFFIXES = ['strict', 'fix', 'json', 'quiet', 'baseline', 'thinking', 'pro'];
125
+
126
+ function isCovered(gate, documentedSet) {
127
+ if (documentedSet.has(gate)) return true;
128
+ // Strip a single trailing variant suffix and check the base.
129
+ const lastColon = gate.lastIndexOf(':');
130
+ if (lastColon > 0) {
131
+ const suffix = gate.slice(lastColon + 1);
132
+ const base = gate.slice(0, lastColon);
133
+ if (VARIANT_SUFFIXES.includes(suffix) && documentedSet.has(base)) {
134
+ return true;
135
+ }
136
+ }
137
+ return false;
138
+ }
139
+
140
+ function diff(releaseGates, allScripts, documented) {
141
+ const allSet = new Set(allScripts);
142
+ const documentedSet = new Set(documented);
143
+ return {
144
+ // Release-flow gate not in the catalog → document it.
145
+ undocumented: releaseGates.filter((g) => !isCovered(g, documentedSet)),
146
+ // Documented gate whose script no longer exists anywhere → remove the row.
147
+ obsolete: documented.filter((g) => !allSet.has(g)),
148
+ both: releaseGates.filter((g) => isCovered(g, documentedSet)),
149
+ };
150
+ }
151
+
152
+ function main() {
153
+ const args = parseArgs(process.argv.slice(2));
154
+ const catalog = resolveCatalog();
155
+ const scripts = loadScripts(args.repo);
156
+ const releaseGates = loadReleaseGates(scripts);
157
+ const allScripts = Object.keys(scripts);
158
+ const documented = loadDocumentedGates(catalog);
159
+ const d = diff(releaseGates, allScripts, documented);
160
+
161
+ if (args.json) {
162
+ console.log(JSON.stringify({
163
+ releaseGates: releaseGates.length,
164
+ documented: documented.length,
165
+ both: d.both.length,
166
+ undocumented: d.undocumented,
167
+ obsolete: d.obsolete,
168
+ }, null, 2));
169
+ } else {
170
+ console.log(`[audit-gate-roster] adia-ui-release §SelfAudit Axis 9`);
171
+ console.log(` release-flow gates (check + roster): ${releaseGates.length}`);
172
+ console.log(` documented in gates-catalog.md: ${documented.length}`);
173
+ console.log(` both: ${d.both.length}`);
174
+ console.log();
175
+ if (d.undocumented.length === 0 && d.obsolete.length === 0) {
176
+ console.log(' Clean — gate roster in sync.');
177
+ } else {
178
+ if (d.undocumented.length > 0) {
179
+ console.log(` Undocumented gates (${d.undocumented.length}) — exist in package.json but NOT in catalog:`);
180
+ for (const g of d.undocumented) console.log(` ${g}`);
181
+ console.log(` -> add a row in references/gates-catalog.md describing this gate.`);
182
+ }
183
+ if (d.obsolete.length > 0) {
184
+ console.log(` Obsolete catalog entries (${d.obsolete.length}) — in catalog but NO LONGER in package.json:`);
185
+ for (const g of d.obsolete) console.log(` ${g}`);
186
+ console.log(` -> remove the rows from references/gates-catalog.md.`);
187
+ }
188
+ }
189
+ }
190
+
191
+ if (args.strict && (d.undocumented.length > 0 || d.obsolete.length > 0)) {
192
+ process.exit(1);
193
+ }
194
+ }
195
+
196
+ main();
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env node
2
+ // bump.mjs — bump 9 @adia-ai/* package.json from --from to --to.
3
+ //
4
+ // Idempotent. Stops on the first mismatch (a package not at --from) so
5
+ // you can investigate before any mutation lands.
6
+ //
7
+ // Usage:
8
+ // node bump.mjs --from 0.6.20 --to 0.6.21
9
+ // node bump.mjs --from 0.6.20 --to 0.6.21 --dry
10
+ //
11
+ // Phase 1 of the adia-ui-release skill. Replaces the /tmp/vXXX-prep.mjs
12
+ // improvisation pattern from the v0.6.13 → v0.6.21 cycles.
13
+ //
14
+ // Exits:
15
+ // 0 — all 9 bumped successfully (or --dry succeeded)
16
+ // 1 — at least one package not at --from (no mutation)
17
+ // 2 — bad args / file I/O error
18
+ // 3 — partial mutation (shouldn't happen if --from validation passes)
19
+
20
+ import fs from 'node:fs';
21
+ import path from 'node:path';
22
+ import process from 'node:process';
23
+
24
+ const PACKAGES = [
25
+ 'packages/web-components',
26
+ 'packages/web-modules',
27
+ 'packages/llm',
28
+ 'packages/a2ui/compose',
29
+ 'packages/a2ui/corpus',
30
+ 'packages/a2ui/mcp',
31
+ 'packages/a2ui/retrieval',
32
+ 'packages/a2ui/runtime',
33
+ 'packages/a2ui/validator',
34
+ ];
35
+
36
+ function parseArgs(argv) {
37
+ const args = { from: null, to: null, dry: false, repo: process.cwd() };
38
+ for (let i = 0; i < argv.length; i++) {
39
+ if (argv[i] === '--from') args.from = argv[++i];
40
+ else if (argv[i] === '--to') args.to = argv[++i];
41
+ else if (argv[i] === '--dry') args.dry = true;
42
+ else if (argv[i] === '--repo') args.repo = argv[++i];
43
+ else if (argv[i] === '-h' || argv[i] === '--help') {
44
+ console.log('Usage: node bump.mjs --from X.Y.Z --to X.Y.Z+1 [--dry] [--repo <path>]');
45
+ process.exit(0);
46
+ }
47
+ }
48
+ if (!args.from || !args.to) {
49
+ console.error('error: both --from and --to are required (e.g. --from 0.6.20 --to 0.6.21)');
50
+ process.exit(2);
51
+ }
52
+ if (!/^\d+\.\d+\.\d+(-[\w.]+)?$/.test(args.from) || !/^\d+\.\d+\.\d+(-[\w.]+)?$/.test(args.to)) {
53
+ console.error('error: --from and --to must be semver (e.g. 0.6.21 or 0.6.22-rc.1)');
54
+ process.exit(2);
55
+ }
56
+ return args;
57
+ }
58
+
59
+ function loadPackageJsons(repo) {
60
+ const result = [];
61
+ for (const pkg of PACKAGES) {
62
+ const p = path.join(repo, pkg, 'package.json');
63
+ if (!fs.existsSync(p)) {
64
+ console.error(`error: missing ${p}`);
65
+ process.exit(2);
66
+ }
67
+ const txt = fs.readFileSync(p, 'utf8');
68
+ const json = JSON.parse(txt);
69
+ result.push({ pkg, path: p, txt, version: json.version });
70
+ }
71
+ return result;
72
+ }
73
+
74
+ function validateAllAtFrom(packages, from) {
75
+ const wrong = packages.filter((p) => p.version !== from);
76
+ if (wrong.length > 0) {
77
+ console.error(`error: ${wrong.length}/${packages.length} package(s) NOT at ${from}:`);
78
+ for (const w of wrong) {
79
+ console.error(` ${w.pkg}: ${w.version}`);
80
+ }
81
+ return false;
82
+ }
83
+ return true;
84
+ }
85
+
86
+ function bumpAll(packages, from, to, dry) {
87
+ const FROM_PATTERN = new RegExp(`"version":\\s*"${from.replace(/\./g, '\\.')}"`);
88
+ const TO_LITERAL = `"version": "${to}"`;
89
+ let bumped = 0;
90
+ for (const p of packages) {
91
+ const out = p.txt.replace(FROM_PATTERN, TO_LITERAL);
92
+ if (out === p.txt) {
93
+ console.error(`error: ${p.pkg} — regex did not match (corrupted package.json?)`);
94
+ return -1;
95
+ }
96
+ if (!dry) fs.writeFileSync(p.path, out);
97
+ bumped++;
98
+ console.log(` ${dry ? '[dry] ' : ''}bumped ${p.pkg}: ${from} → ${to}`);
99
+ }
100
+ return bumped;
101
+ }
102
+
103
+ function main() {
104
+ const args = parseArgs(process.argv.slice(2));
105
+ const packages = loadPackageJsons(args.repo);
106
+ if (!validateAllAtFrom(packages, args.from)) process.exit(1);
107
+ const bumped = bumpAll(packages, args.from, args.to, args.dry);
108
+ if (bumped < 0) process.exit(3);
109
+ console.log(`\n[bump] ${bumped}/9 ${args.dry ? '(dry — no files written)' : 'bumped'}`);
110
+ if (!args.dry) {
111
+ console.log(`\n[next] regenerate the lockfile:`);
112
+ console.log(` npm install --package-lock-only --no-audit --no-fund`);
113
+ console.log(`\n[next] verify lockstep:`);
114
+ console.log(` npm run check:lockstep`);
115
+ }
116
+ }
117
+
118
+ main();
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env node
2
+ // dispatch-publish.mjs — dispatch the 9 publish workflows for a
3
+ // version, optionally waiting for an earlier batch to settle first.
4
+ //
5
+ // Usage:
6
+ // # Single-version dispatch:
7
+ // node dispatch-publish.mjs --version 0.6.22
8
+ //
9
+ // # Batch push: dispatch v0.6.21 first, wait, then v0.6.22:
10
+ // node dispatch-publish.mjs --version 0.6.21
11
+ // # ... wait for completion ...
12
+ // node dispatch-publish.mjs --version 0.6.22 --after 0.6.21
13
+ //
14
+ // # Dry mode:
15
+ // node dispatch-publish.mjs --version 0.6.22 --dry
16
+ //
17
+ // Phase 2 of the adia-ui-release skill. Mechanizes the dispatch loop
18
+ // + handles the npm-latest ordering rule for batch pushes.
19
+
20
+ import { execSync } from 'node:child_process';
21
+ import process from 'node:process';
22
+ import { assertMonorepoRoot } from './assert-monorepo-root.mjs';
23
+
24
+ // Instance data — fork-configurable. The npm scope the 9 packages publish under.
25
+ // Default preserves @adia-ai behavior; override via --scope or $ADIA_NPM_SCOPE.
26
+ const DEFAULT_SCOPE = '@adia-ai';
27
+
28
+ const PACKAGES = [
29
+ 'web-components',
30
+ 'web-modules',
31
+ 'llm',
32
+ 'a2ui-runtime',
33
+ 'a2ui-compose',
34
+ 'a2ui-corpus',
35
+ 'a2ui-mcp',
36
+ 'a2ui-retrieval',
37
+ 'a2ui-validator',
38
+ ];
39
+
40
+ function parseArgs(argv) {
41
+ const args = {
42
+ version: null, after: null, dry: false, sleepBefore: 4,
43
+ scope: process.env.ADIA_NPM_SCOPE || DEFAULT_SCOPE,
44
+ };
45
+ for (let i = 0; i < argv.length; i++) {
46
+ const k = argv[i];
47
+ if (k === '--version') args.version = argv[++i];
48
+ else if (k === '--after') args.after = argv[++i];
49
+ else if (k === '--dry') args.dry = true;
50
+ else if (k === '--sleep-before') args.sleepBefore = parseInt(argv[++i], 10);
51
+ else if (k === '--scope') args.scope = argv[++i];
52
+ else if (k === '-h' || k === '--help') {
53
+ console.log('Usage: node dispatch-publish.mjs --version X.Y.Z [--after X.Y.Z-1] [--dry] [--sleep-before <seconds>] [--scope @org]');
54
+ console.log('');
55
+ console.log('--after Verify npm latest is at the given version before dispatching this one.');
56
+ console.log(' Used in batch push to ensure publish ordering.');
57
+ console.log(`--scope npm scope the packages publish under (default: ${DEFAULT_SCOPE};`);
58
+ console.log(' or set $ADIA_NPM_SCOPE). The --after npm-latest check uses this scope.');
59
+ process.exit(0);
60
+ }
61
+ }
62
+ if (!args.version) {
63
+ console.error('error: --version is required');
64
+ process.exit(2);
65
+ }
66
+ return args;
67
+ }
68
+
69
+ function run(cmd, dry) {
70
+ if (dry) {
71
+ console.log(` [dry] ${cmd}`);
72
+ return '';
73
+ }
74
+ return execSync(cmd, { encoding: 'utf8' });
75
+ }
76
+
77
+ function checkAfter(afterVersion, scope) {
78
+ console.log(`Checking npm latest is at ${afterVersion} before dispatching...`);
79
+ try {
80
+ const latest = execSync(`npm view ${scope}/web-components dist-tags.latest`, { encoding: 'utf8' }).trim();
81
+ if (latest !== afterVersion) {
82
+ console.error(`error: npm ${scope}/web-components latest is '${latest}', expected '${afterVersion}'`);
83
+ console.error(' This means the previous batch hasn\'t completed publishing.');
84
+ console.error(' Wait for the previous --version cycle to settle, then re-run.');
85
+ process.exit(1);
86
+ }
87
+ console.log(` ✓ npm latest = ${latest}`);
88
+ } catch (e) {
89
+ console.error(`error: failed to query npm latest: ${e.message}`);
90
+ process.exit(2);
91
+ }
92
+ }
93
+
94
+ function dispatch(pkg, version, dry) {
95
+ const ref = `${pkg}-v${version}`;
96
+ const cmd = `gh workflow run "publish-${pkg}.yml" --ref "${ref}"`;
97
+ try {
98
+ const out = run(cmd, dry);
99
+ if (!dry && out) {
100
+ const url = out.split('\n').find((l) => l.startsWith('http')) || '';
101
+ console.log(` ✓ dispatched ${pkg} (${url.trim()})`);
102
+ } else if (dry) {
103
+ // already printed by run()
104
+ } else {
105
+ console.log(` ✓ dispatched ${pkg}`);
106
+ }
107
+ return true;
108
+ } catch (e) {
109
+ console.error(` ✗ ERROR ${pkg}: ${e.message}`);
110
+ return false;
111
+ }
112
+ }
113
+
114
+ async function sleep(ms) {
115
+ return new Promise((res) => setTimeout(res, ms));
116
+ }
117
+
118
+ async function main() {
119
+ const args = parseArgs(process.argv.slice(2));
120
+ // Fail-fast guard: refuse to run npm/gh against a non-monorepo directory.
121
+ assertMonorepoRoot(process.cwd());
122
+ if (args.after) checkAfter(args.after, args.scope);
123
+
124
+ if (args.sleepBefore > 0 && !args.dry) {
125
+ console.log(`Sleeping ${args.sleepBefore}s to let GH index the new tags...`);
126
+ await sleep(args.sleepBefore * 1000);
127
+ }
128
+
129
+ console.log(`Dispatching 9 publish workflows for v${args.version}:`);
130
+ let succeeded = 0;
131
+ for (const pkg of PACKAGES) {
132
+ if (dispatch(pkg, args.version, args.dry)) succeeded++;
133
+ }
134
+ console.log(`\n[dispatch] ${succeeded}/${PACKAGES.length} dispatched ${args.dry ? '(dry)' : ''}`);
135
+
136
+ if (!args.dry) {
137
+ console.log(`\n[next] wait for workflows to settle:`);
138
+ console.log(` until [ "$(gh run list --workflow=publish-a2ui-validator.yml --limit 1 --json status -q '.[0].status')" = "completed" ]; do sleep 5; done`);
139
+ console.log(`\n[next] verify all 9 succeeded:`);
140
+ console.log(` for pkg in ${PACKAGES.join(' ')}; do`);
141
+ console.log(` gh run list --workflow=publish-$pkg.yml --limit 1 --json conclusion -q '.[0].conclusion'`);
142
+ console.log(` done`);
143
+ }
144
+
145
+ if (succeeded !== PACKAGES.length) process.exit(1);
146
+ }
147
+
148
+ main().catch((e) => {
149
+ console.error(`error: ${e.message}`);
150
+ process.exit(1);
151
+ });
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env node
2
+ // insert-stub.mjs — insert the ride-along lockstep stub block into N
3
+ // package CHANGELOGs above the previous version's heading.
4
+ //
5
+ // Idempotent. Stops if any target already has the [vX.Y.Z] heading.
6
+ //
7
+ // Usage:
8
+ // node insert-stub.mjs --version 0.6.22 --date 2026-05-22 \
9
+ // --substantive "<one-line summary>" \
10
+ // --xref "packages/web-modules/CHANGELOG.md#0622--2026-05-22" \
11
+ // --previous-version 0.6.21 \
12
+ // --packages llm,a2ui/compose,a2ui/mcp,a2ui/retrieval,a2ui/runtime,a2ui/validator
13
+ //
14
+ // Phase 2 of the adia-ui-release skill. Replaces the inline stub-
15
+ // insertion pattern used across v0.6.18 / v0.6.19 / v0.6.20 / v0.6.21
16
+ // cycles.
17
+
18
+ import fs from 'node:fs';
19
+ import path from 'node:path';
20
+ import process from 'node:process';
21
+
22
+ function parseArgs(argv) {
23
+ const args = {
24
+ version: null,
25
+ date: null,
26
+ substantive: null,
27
+ xref: null,
28
+ previous: null,
29
+ packages: null,
30
+ dry: false,
31
+ repo: process.cwd(),
32
+ };
33
+ for (let i = 0; i < argv.length; i++) {
34
+ const k = argv[i];
35
+ if (k === '--version') args.version = argv[++i];
36
+ else if (k === '--date') args.date = argv[++i];
37
+ else if (k === '--substantive') args.substantive = argv[++i];
38
+ else if (k === '--xref') args.xref = argv[++i];
39
+ else if (k === '--previous-version') args.previous = argv[++i];
40
+ else if (k === '--packages') args.packages = argv[++i].split(',').map((s) => s.trim());
41
+ else if (k === '--dry') args.dry = true;
42
+ else if (k === '--repo') args.repo = argv[++i];
43
+ else if (k === '-h' || k === '--help') {
44
+ console.log('Usage: node insert-stub.mjs --version X.Y.Z --date YYYY-MM-DD --substantive "..." --xref "..." --previous-version X.Y.Z-1 --packages comma,list [--dry] [--repo <path>]');
45
+ process.exit(0);
46
+ }
47
+ }
48
+ for (const [k, v] of Object.entries(args)) {
49
+ if (v === null && k !== 'dry') {
50
+ console.error(`error: --${k.replace(/([A-Z])/g, '-$1').toLowerCase()} is required`);
51
+ process.exit(2);
52
+ }
53
+ }
54
+ if (!/^\d+\.\d+\.\d+(-[\w.]+)?$/.test(args.version)) {
55
+ console.error('error: --version must be semver');
56
+ process.exit(2);
57
+ }
58
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(args.date)) {
59
+ console.error('error: --date must be YYYY-MM-DD');
60
+ process.exit(2);
61
+ }
62
+ return args;
63
+ }
64
+
65
+ function buildStub(version, date, substantive, xref) {
66
+ return `## [${version}] — ${date}
67
+
68
+ ### Maintenance
69
+ - **Lockstep version bump only.** No source changes in this package; bumped to maintain the 9-package version coherence enforced by \`scripts/release/check-lockstep.mjs\`. Substantive v${version} work shipped in ${substantive}. See \`${xref}\` for details.
70
+
71
+ `;
72
+ }
73
+
74
+ function insertStub(repo, pkg, anchor, stubBlock, version, dry) {
75
+ const p = path.join(repo, 'packages', pkg, 'CHANGELOG.md');
76
+ if (!fs.existsSync(p)) {
77
+ console.error(` ERROR ${pkg} — CHANGELOG.md not found at ${p}`);
78
+ return 'missing';
79
+ }
80
+ const txt = fs.readFileSync(p, 'utf8');
81
+ if (txt.includes(`## [${version}]`)) {
82
+ console.error(` ERROR ${pkg} — already has [${version}] heading`);
83
+ return 'already-present';
84
+ }
85
+ if (!txt.includes(anchor)) {
86
+ console.error(` ERROR ${pkg} — anchor '${anchor}' not found`);
87
+ return 'no-anchor';
88
+ }
89
+ const out = txt.replace(anchor, stubBlock + anchor);
90
+ if (!dry) fs.writeFileSync(p, out);
91
+ console.log(` ${dry ? '[dry] ' : ''}stub inserted: ${pkg}`);
92
+ return 'inserted';
93
+ }
94
+
95
+ function main() {
96
+ const args = parseArgs(process.argv.slice(2));
97
+ // Anchor — find this in each CHANGELOG, insert ABOVE it.
98
+ // Read previous date from the actual anchor's line (auto-detect) — we
99
+ // only know the previous version, not the date. So we look for any
100
+ // line matching `## [<previous>] — `.
101
+ const PREV_HEADING_RE = new RegExp(`^## \\[${args.previous.replace(/\./g, '\\.')}\\] — \\d{4}-\\d{2}-\\d{2}$`, 'm');
102
+ const stubBlock = buildStub(args.version, args.date, args.substantive, args.xref);
103
+ const results = args.packages.map((pkg) => {
104
+ const p = path.join(args.repo, 'packages', pkg, 'CHANGELOG.md');
105
+ if (!fs.existsSync(p)) {
106
+ console.error(` ERROR ${pkg} — not found`);
107
+ return 'missing';
108
+ }
109
+ const txt = fs.readFileSync(p, 'utf8');
110
+ const m = txt.match(PREV_HEADING_RE);
111
+ if (!m) {
112
+ console.error(` ERROR ${pkg} — no '## [${args.previous}] — YYYY-MM-DD' heading found`);
113
+ return 'no-anchor';
114
+ }
115
+ const anchor = m[0];
116
+ return insertStub(args.repo, pkg, anchor, stubBlock, args.version, args.dry);
117
+ });
118
+ const inserted = results.filter((r) => r === 'inserted').length;
119
+ const failed = results.filter((r) => r !== 'inserted').length;
120
+ console.log(`\n[insert-stub] ${inserted}/${args.packages.length} ${args.dry ? '(dry)' : 'inserted'}`);
121
+ if (failed > 0) {
122
+ console.error(`[insert-stub] ${failed} target(s) failed — see ERRORs above`);
123
+ process.exit(1);
124
+ }
125
+ }
126
+
127
+ main();