@adia-ai/adia-ui-forge 0.8.8 → 0.8.10

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.
@@ -1,37 +1,134 @@
1
1
  {
2
- "positives": [
3
- "the gemini adapter is dropping usage tokens on streamed responses",
4
- "stopReason from openai never reaches the client as MAX_TOKENS",
5
- "the SSE parser chokes when a chunk splits mid-line",
6
- "the passthrough proxy is returning 401 for anthropic requests in production",
7
- "add a new model to the MODELS registry and set it as DEFAULT_MODEL",
8
- "createAdapter() is leaking a real API key to the browser on a production host",
9
- "add support for a new OpenAI-compatible gateway provider adapter",
10
- "streamChat() never emits a terminal done chunk",
11
- "detectProvider isn't resolving the new model id to the right adapter",
12
- "the anthropic adapter's buildRequest() diverges from the passthrough proxy path",
13
- "the StreamChunk union is missing a variant for tool_use deltas",
14
- "the stub LLM adapter isn't returning parseable A2UI JSON in dev mode"
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "name": "adia-llm-internals routing accuracy corpus",
4
+ "version": "2.1.0",
5
+ "purpose": "Routing-eval corpus for adia-llm-internals. Each phrase declares the skill (expected), a forbidden skill (expected_not, for phrases the source data only ever asserted as \"not this skill\"), or neither. Scored by scripts/skills/run-skill-evals.mjs (TF-IDF token overlap over per-skill description+triggers).",
6
+ "scoring_notes": "Heuristic signal, not ground truth. Treat misroutes as a prompt to tighten the skill description, never as a reason to keyword-stuff it. Real harness routing is LLM-driven.",
7
+ "scope": "adia-llm-internals routing \u2014 does this phrase activate adia-llm-internals?",
8
+ "phrases": [
9
+ {
10
+ "id": "adia-llm-internals-pos-01",
11
+ "phrase": "the gemini adapter is dropping usage tokens on streamed responses",
12
+ "expected": "adia-llm-internals"
13
+ },
14
+ {
15
+ "id": "adia-llm-internals-pos-02",
16
+ "phrase": "stopReason from openai never reaches the client as MAX_TOKENS",
17
+ "expected": "adia-llm-internals"
18
+ },
19
+ {
20
+ "id": "adia-llm-internals-pos-03",
21
+ "phrase": "the SSE parser chokes when a chunk splits mid-line",
22
+ "expected": "adia-llm-internals"
23
+ },
24
+ {
25
+ "id": "adia-llm-internals-pos-04",
26
+ "phrase": "the passthrough proxy is returning 401 for anthropic requests in production",
27
+ "expected": "adia-llm-internals"
28
+ },
29
+ {
30
+ "id": "adia-llm-internals-pos-05",
31
+ "phrase": "add a new model to the MODELS registry and set it as DEFAULT_MODEL",
32
+ "expected": "adia-llm-internals"
33
+ },
34
+ {
35
+ "id": "adia-llm-internals-pos-06",
36
+ "phrase": "createAdapter() is leaking a real API key to the browser on a production host",
37
+ "expected": "adia-llm-internals"
38
+ },
39
+ {
40
+ "id": "adia-llm-internals-pos-07",
41
+ "phrase": "add support for a new OpenAI-compatible gateway provider adapter",
42
+ "expected": "adia-llm-internals"
43
+ },
44
+ {
45
+ "id": "adia-llm-internals-pos-08",
46
+ "phrase": "streamChat() never emits a terminal done chunk",
47
+ "expected": "adia-llm-internals"
48
+ },
49
+ {
50
+ "id": "adia-llm-internals-pos-09",
51
+ "phrase": "detectProvider isn't resolving the new model id to the right adapter",
52
+ "expected": "adia-llm-internals"
53
+ },
54
+ {
55
+ "id": "adia-llm-internals-pos-10",
56
+ "phrase": "the anthropic adapter's buildRequest() diverges from the passthrough proxy path",
57
+ "expected": "adia-llm-internals"
58
+ },
59
+ {
60
+ "id": "adia-llm-internals-pos-11",
61
+ "phrase": "the StreamChunk union is missing a variant for tool_use deltas",
62
+ "expected": "adia-llm-internals"
63
+ },
64
+ {
65
+ "id": "adia-llm-internals-pos-12",
66
+ "phrase": "the stub LLM adapter isn't returning parseable A2UI JSON in dev mode",
67
+ "expected": "adia-llm-internals"
68
+ },
69
+ {
70
+ "id": "adia-llm-internals-neg-01",
71
+ "phrase": "wire the chat client into our admin app's chat-shell",
72
+ "expected_not": "adia-llm-internals"
73
+ },
74
+ {
75
+ "id": "adia-llm-internals-neg-02",
76
+ "phrase": "why did the composer emit composition-synthesized instead of composition-match",
77
+ "expected_not": "adia-llm-internals"
78
+ },
79
+ {
80
+ "id": "adia-llm-internals-neg-03",
81
+ "phrase": "cut a release and bump @adia-ai/llm's version with the rest of the lockstep packages",
82
+ "expected_not": "adia-llm-internals"
83
+ },
84
+ {
85
+ "id": "adia-llm-internals-neg-04",
86
+ "phrase": "add a new prop to the chat-input-ui primitive",
87
+ "expected_not": "adia-llm-internals"
88
+ },
89
+ {
90
+ "id": "adia-llm-internals-neg-05",
91
+ "phrase": "explain how Server-Sent Events work in general",
92
+ "expected_not": "adia-llm-internals"
93
+ },
94
+ {
95
+ "id": "adia-llm-internals-neg-06",
96
+ "phrase": "add a chat box to the admin app",
97
+ "expected_not": "adia-llm-internals"
98
+ },
99
+ {
100
+ "id": "adia-llm-internals-neg-07",
101
+ "phrase": "the chat-shell message list isn't rendering streamed replies",
102
+ "expected_not": "adia-llm-internals"
103
+ },
104
+ {
105
+ "id": "adia-llm-internals-neg-08",
106
+ "phrase": "tune STRONG_MATCH and lift the eval fails in the compose strategies",
107
+ "expected_not": "adia-llm-internals"
108
+ },
109
+ {
110
+ "id": "adia-llm-internals-neg-09",
111
+ "phrase": "deploy the ui-kit dist to exe.dev and restart the service",
112
+ "expected_not": "adia-llm-internals"
113
+ },
114
+ {
115
+ "id": "adia-llm-internals-neg-10",
116
+ "phrase": "run a dogfood sweep to find broken demos",
117
+ "expected_not": "adia-llm-internals"
118
+ },
119
+ {
120
+ "id": "adia-llm-internals-neg-11",
121
+ "phrase": "score the gen-ui gallery outputs against the exit gate",
122
+ "expected_not": "adia-llm-internals"
123
+ }
15
124
  ],
16
- "negatives": [
17
- "wire the chat client into our admin app's chat-shell",
18
- "why did the composer emit composition-synthesized instead of composition-match",
19
- "cut a release and bump @adia-ai/llm's version with the rest of the lockstep packages",
20
- "add a new prop to the chat-input-ui primitive",
21
- "explain how Server-Sent Events work in general",
22
- "add a chat box to the admin app",
23
- "the chat-shell message list isn't rendering streamed replies",
24
- "tune STRONG_MATCH and lift the eval fails in the compose strategies",
25
- "deploy the ui-kit dist to exe.dev and restart the service",
26
- "run a dogfood sweep to find broken demos",
27
- "score the gen-ui gallery outputs against the exit gate"
28
- ],
29
- "_measured": {
125
+ "_measured_historical": {
30
126
  "as_of": "2026-07-18",
31
127
  "scorer": "routing_eval.py (nonoun-plugins/forge)",
32
128
  "f1": 0.857,
33
129
  "precision": 1.0,
34
130
  "recall": 0.75,
35
- "exit_code": 0
131
+ "exit_code": 0,
132
+ "note": "measured by the external routing_eval.py (nonoun-plugins/forge) against the pre-conversion positives/negatives shape; historical record only, not regenerated by run-skill-evals.mjs."
36
133
  }
37
134
  }
@@ -1,35 +1,124 @@
1
1
  {
2
- "positives": [
3
- "Cut and ship v0.8.5 of the AdiaUI packages",
4
- "The [Unreleased] CHANGELOG entries are piling up, let's cut a release",
5
- "Tag the lockstep packages and push them to origin",
6
- "check:lockstep is failing \u2014 one package didn't bump",
7
- "Dispatch the publish workflows for v0.8.5 and verify npm",
8
- "We have three unpushed release commits piled up, batch-push them",
9
- "F-N1 is warning about a CHANGELOG entry not covering a touched directory",
10
- "The release cut landed on the wrong branch, help me recover",
11
- "Author the release notes and Slack post for the last two versions",
12
- "This cut removes the variant prop from button-ui, write the MIGRATION GUIDE section",
13
- "Publish the adia-ui-forge plugin independently of the lockstep set",
14
- "Just verify the release gates are green without cutting anything",
15
- "Tags are pushed but zero publish workflows fired, what happened"
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "name": "adia-release routing accuracy corpus",
4
+ "version": "2.1.0",
5
+ "purpose": "Routing-eval corpus for adia-release. Each phrase declares the skill (expected), a forbidden skill (expected_not, for phrases the source data only ever asserted as \"not this skill\"), or neither. Scored by scripts/skills/run-skill-evals.mjs (TF-IDF token overlap over per-skill description+triggers).",
6
+ "scoring_notes": "Heuristic signal, not ground truth. Treat misroutes as a prompt to tighten the skill description, never as a reason to keyword-stuff it. Real harness routing is LLM-driven.",
7
+ "scope": "adia-release routing \u2014 does this phrase activate adia-release?",
8
+ "phrases": [
9
+ {
10
+ "id": "adia-release-pos-01",
11
+ "phrase": "Cut and ship v0.8.5 of the AdiaUI packages",
12
+ "expected": "adia-release"
13
+ },
14
+ {
15
+ "id": "adia-release-pos-02",
16
+ "phrase": "The [Unreleased] CHANGELOG entries are piling up, let's cut a release",
17
+ "expected": "adia-release"
18
+ },
19
+ {
20
+ "id": "adia-release-pos-03",
21
+ "phrase": "Tag the lockstep packages and push them to origin",
22
+ "expected": "adia-release"
23
+ },
24
+ {
25
+ "id": "adia-release-pos-04",
26
+ "phrase": "check:lockstep is failing \u2014 one package didn't bump",
27
+ "expected": "adia-release"
28
+ },
29
+ {
30
+ "id": "adia-release-pos-05",
31
+ "phrase": "Dispatch the publish workflows for v0.8.5 and verify npm",
32
+ "expected": "adia-release"
33
+ },
34
+ {
35
+ "id": "adia-release-pos-06",
36
+ "phrase": "We have three unpushed release commits piled up, batch-push them",
37
+ "expected": "adia-release"
38
+ },
39
+ {
40
+ "id": "adia-release-pos-07",
41
+ "phrase": "F-N1 is warning about a CHANGELOG entry not covering a touched directory",
42
+ "expected": "adia-release"
43
+ },
44
+ {
45
+ "id": "adia-release-pos-08",
46
+ "phrase": "The release cut landed on the wrong branch, help me recover",
47
+ "expected": "adia-release"
48
+ },
49
+ {
50
+ "id": "adia-release-pos-09",
51
+ "phrase": "Author the release notes and Slack post for the last two versions",
52
+ "expected": "adia-release"
53
+ },
54
+ {
55
+ "id": "adia-release-pos-10",
56
+ "phrase": "This cut removes the variant prop from button-ui, write the MIGRATION GUIDE section",
57
+ "expected": "adia-release"
58
+ },
59
+ {
60
+ "id": "adia-release-pos-11",
61
+ "phrase": "Publish the adia-ui-forge plugin independently of the lockstep set",
62
+ "expected": "adia-release"
63
+ },
64
+ {
65
+ "id": "adia-release-pos-12",
66
+ "phrase": "Just verify the release gates are green without cutting anything",
67
+ "expected": "adia-release"
68
+ },
69
+ {
70
+ "id": "adia-release-pos-13",
71
+ "phrase": "Tags are pushed but zero publish workflows fired, what happened",
72
+ "expected": "adia-release"
73
+ },
74
+ {
75
+ "id": "adia-release-neg-01",
76
+ "phrase": "Restart the exe.dev VM, it's returning 502s",
77
+ "expected_not": "adia-release"
78
+ },
79
+ {
80
+ "id": "adia-release-neg-02",
81
+ "phrase": "Rsync the built dist folder to ui-kit.exe.xyz",
82
+ "expected_not": "adia-release"
83
+ },
84
+ {
85
+ "id": "adia-release-neg-03",
86
+ "phrase": "Sweep our consumer app for the renamed variant prop after the last breaking release",
87
+ "expected_not": "adia-release"
88
+ },
89
+ {
90
+ "id": "adia-release-neg-04",
91
+ "phrase": "Add a new size variant to the button-ui primitive",
92
+ "expected_not": "adia-release"
93
+ },
94
+ {
95
+ "id": "adia-release-neg-05",
96
+ "phrase": "Compose a settings screen for our app from the AdiaUI catalog",
97
+ "expected_not": "adia-release"
98
+ },
99
+ {
100
+ "id": "adia-release-neg-06",
101
+ "phrase": "zettel coverage dropped on the nightly eval, find out why",
102
+ "expected_not": "adia-release"
103
+ },
104
+ {
105
+ "id": "adia-release-neg-07",
106
+ "phrase": "sweep the component demos for native-primitive leaks before we cut",
107
+ "expected_not": "adia-release"
108
+ },
109
+ {
110
+ "id": "adia-release-neg-08",
111
+ "phrase": "the gemini adapter is dropping usage tokens on streamed responses",
112
+ "expected": "adia-llm-internals"
113
+ }
16
114
  ],
17
- "negatives": [
18
- "Restart the exe.dev VM, it's returning 502s",
19
- "Rsync the built dist folder to ui-kit.exe.xyz",
20
- "Sweep our consumer app for the renamed variant prop after the last breaking release",
21
- "Add a new size variant to the button-ui primitive",
22
- "Compose a settings screen for our app from the AdiaUI catalog",
23
- "zettel coverage dropped on the nightly eval, find out why",
24
- "sweep the component demos for native-primitive leaks before we cut",
25
- "the gemini adapter is dropping usage tokens on streamed responses"
26
- ],
27
- "_measured": {
115
+ "_measured_historical": {
28
116
  "as_of": "2026-07-18",
29
117
  "scorer": "routing_eval.py (nonoun-plugins/forge)",
30
118
  "f1": 1.0,
31
119
  "precision": 1.0,
32
120
  "recall": 1.0,
33
- "exit_code": 0
121
+ "exit_code": 0,
122
+ "note": "measured by the external routing_eval.py (nonoun-plugins/forge) against the pre-conversion positives/negatives shape; historical record only, not regenerated by run-skill-evals.mjs."
34
123
  }
35
124
  }
@@ -124,6 +124,20 @@ export function bridgeDecision(state) {
124
124
  if (!PASS.has(concl)) reasons.push(`check '${c.name}' landed ${concl}`);
125
125
  else gatingLanded++;
126
126
  }
127
+ // gh#(pr-bridge draft guard): a draft PR is the AUTHOR's own "not ready"
128
+ // signal — GitHub's API refuses to merge one anyway (GraphQL: "Pull
129
+ // Request is still a draft"), and the old code had no isDraft check, so
130
+ // bridgeDecision said MERGE, the gh pr merge call threw, and the whole
131
+ // script crashed uncaught. A caller piping through `grep -E "pr-bridge" |
132
+ // tail -N` (this script's own documented usage pattern) sees only the
133
+ // last N lines BEFORE the crash and no error — reads as quiet success.
134
+ // Unconditional stop (review nit: placed after the per-check loop above,
135
+ // not literally first — but every reasons.push() here is unconditional and
136
+ // the "stop" decision below fires on ANY non-empty reasons list, so a
137
+ // draft overrides an otherwise-perfect merge regardless of ordering):
138
+ // never attempt the merge call on a draft, no matter how green everything
139
+ // else is.
140
+ if (state.isDraft) reasons.push('PR is a DRAFT — the author has not marked it ready for review; never auto-merge a draft');
127
141
  if (state.reviewDecision === 'CHANGES_REQUESTED') reasons.push('a review requests changes');
128
142
  if (state.unresolvedThreads > 0) reasons.push(`${state.unresolvedThreads} unresolved review thread(s) — admin merge cannot bypass these`);
129
143
  if (state.mergeable === 'CONFLICTING') reasons.push('PR is CONFLICTING with the base branch');
@@ -144,7 +158,7 @@ export function bridgeDecision(state) {
144
158
  // ── GitHub reads ──────────────────────────────────────────────────
145
159
  function readPrState(prNumber) {
146
160
  const view = JSON.parse(sh(
147
- `gh pr view ${prNumber} --json state,mergeable,reviewDecision,statusCheckRollup,headRefName,baseRefName`,
161
+ `gh pr view ${prNumber} --json state,isDraft,mergeable,reviewDecision,statusCheckRollup,headRefName,baseRefName`,
148
162
  ));
149
163
  const checks = (view.statusCheckRollup || []).map((c) => ({
150
164
  name: c.name || c.context || '?',
@@ -156,6 +170,7 @@ function readPrState(prNumber) {
156
170
  )).data.repository.pullRequest.reviewThreads.nodes;
157
171
  return {
158
172
  state: view.state,
173
+ isDraft: view.isDraft,
159
174
  headRefName: view.headRefName,
160
175
  baseRefName: view.baseRefName,
161
176
  checks,
@@ -240,7 +255,19 @@ async function main() {
240
255
  }
241
256
  if (d.action === 'merge') {
242
257
  console.log(`\n[pr-bridge] all gates green — merging PR #${prNumber} (admin merge per house flow §5.7)`);
243
- sh(`gh pr merge ${prNumber} --admin --merge --delete-branch`);
258
+ // Defense in depth: the isDraft check above should catch this, but
259
+ // GitHub's own merge call is the actual authority — never let its
260
+ // rejection crash uncaught (a caller filtering output for the
261
+ // "pr-bridge" prefix, this script's own documented usage, would see
262
+ // the run go quiet mid-merge with no clear failure, easy to misread
263
+ // as success). Report loudly and exit 1 instead of throwing.
264
+ try {
265
+ sh(`gh pr merge ${prNumber} --admin --merge --delete-branch`);
266
+ } catch (e) {
267
+ console.error(`\n[pr-bridge] STOP — gh pr merge rejected PR #${prNumber}: ${e.message || e}`);
268
+ console.error(' Nothing else mutated by this run; investigate and merge manually if appropriate.');
269
+ process.exit(1);
270
+ }
244
271
  break;
245
272
  }
246
273
  if (Date.now() > deadline) {
@@ -269,10 +296,12 @@ async function main() {
269
296
  // ── Selftest — the predicate is the load-bearing part ─────────────
270
297
  function selftest() {
271
298
  const ck = (name, status, conclusion) => ({ name, status, conclusion });
272
- const base = { unresolvedThreads: 0, reviewDecision: 'REVIEW_REQUIRED', mergeable: 'MERGEABLE' };
299
+ const base = { unresolvedThreads: 0, reviewDecision: 'REVIEW_REQUIRED', mergeable: 'MERGEABLE', isDraft: false };
273
300
  const cases = [
274
301
  ['all green → merge',
275
302
  { ...base, checks: [ck('Static checks + MCP smoke', 'COMPLETED', 'SUCCESS'), ck('CodeRabbit', '', 'SUCCESS')] }, 'merge'],
303
+ ['DRAFT PR with all-green checks → stop, never attempt gh pr merge (real incident: PR #362)',
304
+ { ...base, isDraft: true, checks: [ck('Static checks + MCP smoke', 'COMPLETED', 'SUCCESS'), ck('CodeRabbit', '', 'SUCCESS')] }, 'stop'],
276
305
  ['fail-soft failure is advisory → merge',
277
306
  { ...base, checks: [ck('Static checks + MCP smoke', 'COMPLETED', 'SUCCESS'), ck('Audit health (fail-soft)', 'COMPLETED', 'FAILURE')] }, 'merge'],
278
307
  ['EMPTY check set → wait, never blind-merge (reviewer finding #1)',
@@ -313,4 +342,12 @@ function selftest() {
313
342
 
314
343
  const argv = process.argv.slice(2);
315
344
  if (argv[0] === 'selftest') selftest();
316
- else main().catch((e) => { console.error(e.message || e); process.exit(1); });
345
+ else main().catch((e) => {
346
+ // Reviewer finding (PR #364): every OTHER error path (readPrState()'s gh
347
+ // pr view/graphql calls on every poll, git push/gh pr create, the
348
+ // re-baseline sequence) throws up to this top-level handler, which had NO
349
+ // "[pr-bridge]" prefix — invisible under the exact `grep -E "pr-bridge" |
350
+ // tail -N` filter this PR exists to fix. Same class, different call site.
351
+ console.error(`[pr-bridge] STOP — ${e.message || e}`);
352
+ process.exit(1);
353
+ });
@@ -229,6 +229,21 @@ function shCapture(cmd, args) {
229
229
  // ── Step 1 — Re-baseline ──────────────────────────────────────────
230
230
  function step1ReBaseline(args) {
231
231
  console.log('\n=== Step 1 — Re-baseline ===');
232
+ // cut-procedure.md §Step 1 requires `git branch --show-current` == main
233
+ // before a cut starts — cutting on any other branch pushes a stale main ref
234
+ // while tags point at the wrong tip (recovery-paths.md §Scenario 8). The doc
235
+ // asserted this; nothing here ever checked it (gh#358). The one legitimate
236
+ // exception is resuming this SAME cut after step5Commit already created and
237
+ // checked out release/vX.Y.Z — allow only that exact branch name back in.
238
+ if ((args.mode === 'cut' || args.mode === 'from-scratch') && !args.dry) {
239
+ const branch = shQuiet('git branch --show-current', args).trim();
240
+ const releaseBranch = `release/v${args.version}`;
241
+ if (branch !== 'main' && branch !== releaseBranch) {
242
+ console.error(`ERROR: on branch '${branch}' — cut modes start from 'main' (or resume on '${releaseBranch}'). Switch branches before proceeding.`);
243
+ process.exit(1);
244
+ }
245
+ console.log(` branch: ${branch} (ok)`);
246
+ }
232
247
  console.log('git status --short:');
233
248
  sh('git status --short', args);
234
249
  console.log('\norigin/main..HEAD (unpushed):');
@@ -362,6 +377,30 @@ function step5Commit(args) {
362
377
  return;
363
378
  }
364
379
  console.log('\n=== Step 5 — Stage + Commit ===');
380
+ // cut-procedure.md §Step 5: "Branch FIRST, then stage" — this step never
381
+ // actually created release/vX.Y.Z, so the commit landed on whatever branch
382
+ // was checked out (main, on the v0.8.8 cut — gh#358). Idempotent: if the
383
+ // branch already exists (a resumed run), check it out rather than failing;
384
+ // step1ReBaseline's branch assertion is what stops a genuinely wrong branch
385
+ // from reaching this point.
386
+ const releaseBranch = `release/v${args.version}`;
387
+ const currentBranch = args.dry ? null : shQuiet('git branch --show-current', args).trim();
388
+ if (currentBranch !== releaseBranch) {
389
+ const exists = !args.dry && shQuiet(`git branch --list ${releaseBranch}`, args).trim().length > 0;
390
+ // Reviewer finding (PR #359): checking out an EXISTING release branch (a
391
+ // resumed run) on a dirty tree fails with a raw, opaque git error — Step 4
392
+ // (promote/bump/lockfile) runs immediately before this and dirties exactly
393
+ // the files a release branch differs from main in. A fresh `-b` create
394
+ // from clean main can't hit this, so the check only guards the resume path.
395
+ if (exists && !args.dry) {
396
+ const dirty = shQuiet('git status --porcelain', args).trim();
397
+ if (dirty) {
398
+ console.error(`ERROR: '${releaseBranch}' already exists, but the working tree is dirty — checking out would fail or silently carry these changes onto the resumed branch:\n${dirty}\n Commit, stash, or discard first, then re-run.`);
399
+ process.exit(1);
400
+ }
401
+ }
402
+ sh(`git checkout ${exists ? '' : '-b '}${releaseBranch}`, args);
403
+ }
365
404
  sh('git reset HEAD >/dev/null 2>&1 || true', args);
366
405
  const releaseFiles = [
367
406
  'package-lock.json',
@@ -512,11 +551,34 @@ async function step9Publish(args) {
512
551
  // Give GH a moment to index the runs created by the per-tag push in Step 8.
513
552
  if (!args.dry) sh('sleep 6', args);
514
553
  sh(`node ${SCRIPT_DIR}/dispatch-publish.mjs --version ${args.version} --scope ${args.scope} --verify-triggered`, args);
515
- console.log('\n Waiting for publishes to settle...');
516
- sh(`until [ "$(gh run list --workflow=publish-a2ui-validator.yml --limit 1 --json status -q '.[0].status')" = "completed" ]; do sleep 5; done`, args);
517
- console.log(' publishes settled');
554
+ // gh#354: this used to poll ONE workflow (publish-a2ui-validator.yml) as a
555
+ // sample-of-one proxy for all 11 publishes settling a straggler on any
556
+ // OTHER package's workflow made the fail-closed check below abort on a pure
557
+ // timing false alarm (hit live on the v0.8.8 cut: a2ui-retrieval published
558
+ // successfully ~1 minute after validator did, after the old proxy had
559
+ // already declared "settled" and the registry read caught it mid-flight).
560
+ // Poll the REGISTRY itself instead — the skill's own doctrine (SKILL.md:
561
+ // "the registry is the verify target, never a workflow's green check")
562
+ // applies here as much as it does to the final check.
563
+ console.log(`\n Waiting for the registry to confirm all ${PACKAGES.length} packages at ${args.version} (polling every 15s, up to 10min)...`);
564
+ sh(
565
+ `for i in $(seq 1 40); do ` +
566
+ `fail=0; ` +
567
+ `for pkg in ${PACKAGES.join(' ')}; do ` +
568
+ `v="$(npm view "${args.scope}/$pkg" version 2>/dev/null || true)"; ` +
569
+ `[ "$v" = "${args.version}" ] || fail=1; ` +
570
+ `done; ` +
571
+ `latest="$(npm view ${args.scope}/web-components dist-tags.latest 2>/dev/null || true)"; ` +
572
+ `[ "$latest" = "${args.version}" ] || fail=1; ` +
573
+ `[ "$fail" = 0 ] && { echo " ✓ registry confirms all ${PACKAGES.length} + latest, after $i check(s)"; exit 0; }; ` +
574
+ `sleep 15; ` +
575
+ `done; ` +
576
+ `echo " registry did not converge after 10min — the fail-closed check below will report exactly which package(s) are still stale"`,
577
+ args,
578
+ );
518
579
  // Fail-closed: the registry is the verify target (SKILL.md) — a missing or
519
- // stale package aborts the cut here, before GH releases + site deploy.
580
+ // stale package aborts the cut here, before GH releases + site deploy. This
581
+ // is now a final confirmation of the poll above, not the first real read.
520
582
  console.log(`\n Post-publish registry verification, all ${PACKAGES.length} at ${args.version} + npm latest (fail-closed):`);
521
583
  sh(
522
584
  `fail=0; ` +
@@ -648,6 +710,8 @@ function selftest() {
648
710
  `${GATE_ROSTER.length}-gate roster`,
649
711
  `✓ all ${GATE_ROSTER.length}/${GATE_ROSTER.length} gates passed`,
650
712
  'STOPPING per invariant 3',
713
+ // gh#358 — the release branch must actually be created, not just documented.
714
+ 'git checkout -b release/v9.9.9',
651
715
  ];
652
716
  for (const marker of need) {
653
717
  if (!out.includes(marker)) {
@@ -0,0 +1,20 @@
1
+ {
2
+ "skill": "adia-site-docs",
3
+ "note": "Trigger-routing suite (gh#369). expect=trigger: the skill should fire on the prompt in a fresh session. expect=no-trigger: a near-miss owned elsewhere (owner named per case) or generic knowledge with no AdiaUI skill involved. Cases derived from evals/routing-corpus.json's phrases array (gh#355 vetted corpus) plus the SKILL.md description's own trigger/NOT-fence language.",
4
+ "cases": [
5
+ { "id": "t01", "prompt": "Review this site docs page for consistency with the others", "expect": "trigger" },
6
+ { "id": "t02", "prompt": "This callout on the docs page reads as plain text, not a styled box — why?", "expect": "trigger" },
7
+ { "id": "t03", "prompt": "Add a new pattern page under site/pages/patterns for the kanban-board pattern", "expect": "trigger" },
8
+ { "id": "t04", "prompt": "Author a new getting-started guide page for theming dark mode", "expect": "trigger" },
9
+ { "id": "t05", "prompt": "Write a new architecture explainer page for the docs site", "expect": "trigger" },
10
+ { "id": "t06", "prompt": "Create a reference page documenting the MCP tools", "expect": "trigger" },
11
+ { "id": "t07", "prompt": "Audit site/pages/guides for the same callout bug we found elsewhere", "expect": "trigger" },
12
+ { "id": "t08", "prompt": "What's the shared skeleton every site docs page should follow?", "expect": "trigger" },
13
+ { "id": "n01", "prompt": "Review this component's own demo gallery in its .examples.html file", "expect": "no-trigger", "owner": "adia-author" },
14
+ { "id": "n02", "prompt": "Fix this gen-ui training exemplar page under site/pages/examples", "expect": "no-trigger", "owner": "adia-a2ui" },
15
+ { "id": "n03", "prompt": "Tune the zettel retrieval threshold for the chunk corpus", "expect": "no-trigger", "owner": "adia-a2ui" },
16
+ { "id": "n04", "prompt": "Add a new prop to the button-ui primitive", "expect": "no-trigger", "owner": "adia-author" },
17
+ { "id": "n05", "prompt": "Cut the next npm release for the 9 @adia-ai packages", "expect": "no-trigger", "owner": "adia-release" },
18
+ { "id": "n06", "prompt": "Deploy the docs site to ui-kit.exe.xyz", "expect": "no-trigger", "owner": "adia-deploy" }
19
+ ]
20
+ }