@adia-ai/adia-ui-forge 0.8.60 → 0.8.62
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 +50 -0
- package/package.json +1 -1
- package/plugin.yaml +1 -1
- package/scripts/lint-rules.generated.mjs +463 -137
- package/skills/a2ui-maintenance/references/eval-diagnostics.md +2 -2
- package/skills/a2ui-maintenance/references/pipeline-overview.md +3 -1
- package/skills/a2ui-maintenance/references/strategy-engines.md +12 -0
- package/skills/demo-audit/references/admin-shell-anatomy.md +18 -12
- 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 +62 -24
- 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
|
@@ -46,7 +46,7 @@ fluctuate).
|
|
|
46
46
|
## Phase 2 — fixes per bucket
|
|
47
47
|
|
|
48
48
|
- **A. Holdout alignment** — map each intent's `expected_chunk` to the actual
|
|
49
|
-
top-1; update `packages/gen-ui/engine/corpus/
|
|
49
|
+
top-1; update `packages/gen-ui/engine/evals/corpus/holdout-compose-from-chunks.jsonl`.
|
|
50
50
|
- **E. Measurement traps** — PascalCase→kebab (`AgentTrace` → `agent-trace`,
|
|
51
51
|
not `agenttrace`); substring false positives (`pane` vs `panel`,
|
|
52
52
|
`textarea-ui` contains `text-ui` — word boundaries); case sensitivity (`/i`).
|
|
@@ -82,7 +82,7 @@ the SKILL.md floors hold.
|
|
|
82
82
|
The two `check:*-eval-regression` scripts own the floor numbers — read the source
|
|
83
83
|
before quoting a number elsewhere; SKILL.md only mirrors them and can drift (it
|
|
84
84
|
once silently regressed to `cov≥40` before the mechanical gate existed). Zettel's
|
|
85
|
-
floors are a committed file, `evals/health/zettel-floor.json` (gh#1391)
|
|
85
|
+
floors are a committed file, `packages/gen-ui/engine/evals/health/zettel-floor.json` (gh#1391);
|
|
86
86
|
`scripts/release/check-zettel-eval-regression.mjs` loads it at runtime and refuses
|
|
87
87
|
to run without it, so re-baselining is a JSON diff, not a source edit. Free-form's
|
|
88
88
|
floors are still `ALERT_FLOOR`/`HARD_FLOOR` constants in
|
|
@@ -202,7 +202,9 @@ for any constant or decision lives in git and PR descriptions
|
|
|
202
202
|
(`THINKING_BUDGET_MONOLITHIC_THINKING`) and `free-form-composer/
|
|
203
203
|
index.js`'s ingredient-picker call, both its primary pick and its own
|
|
204
204
|
paraphrase-retry (`THINKING_BUDGET_FREE_FORM`), both starting at the
|
|
205
|
-
bridge's own `DEFAULT_THINKING_BUDGET
|
|
205
|
+
bridge's own `DEFAULT_THINKING_BUDGET`, lowered to 4000 (gh#3516 slice
|
|
206
|
+
3, conductor ruling 2026-09-08) after an n=5 measurement at 10000
|
|
207
|
+
showed no measurable quality gain with real cost. `auto` inherits
|
|
206
208
|
through the free-form picker call the moment it escalates that far -
|
|
207
209
|
`monolithic-thinking` itself is never reachable via `auto`'s own
|
|
208
210
|
escalation ladder. Every other LLM call site (`generate-pro.js`'s four
|
|
@@ -86,6 +86,18 @@ shows the results); `--strict` enforcement is deferred to TKT-0010
|
|
|
86
86
|
(27% of the corpus currently fails, mostly a schema-generation gap
|
|
87
87
|
around `data-*`/`span` attributes rather than corpus-content defects).
|
|
88
88
|
|
|
89
|
+
## Provider thinking opt-in kill switch (gh#3516, LLD-3516 slice 1)
|
|
90
|
+
|
|
91
|
+
`monolithic-thinking` and `free-form-composer` opt into provider thinking at
|
|
92
|
+
a fixed strategy-level budget (`THINKING_BUDGET_MONOLITHIC_THINKING`,
|
|
93
|
+
`THINKING_BUDGET_FREE_FORM`, both 4000 as of gh#3516 slice 3) via the shared
|
|
94
|
+
`compose/shared/thinking-opt-in.js` helper. `ADIA_THINKING_OPT_IN=0` drops
|
|
95
|
+
the `thinking`/`thinkingBudget` fields at both call sites entirely (not
|
|
96
|
+
`thinking: false`, the adapter never sees the keys), giving the
|
|
97
|
+
thinking-off baseline a runtime lever instead of a code revert. Unset (or
|
|
98
|
+
`"1"`) keeps current behavior. This is an operations knob, not a caller
|
|
99
|
+
option: no consumer threads it through `generateUI()`.
|
|
100
|
+
|
|
89
101
|
## Pitfalls
|
|
90
102
|
|
|
91
103
|
- **`STRONG_MATCH_THRESHOLD` was raised 22 → 40 post-incident.** Lowering it
|
|
@@ -17,7 +17,7 @@ the live admin-dashboard example.
|
|
|
17
17
|
`<select-ui avatar="…" value="…" variant="ghost">` context switcher —
|
|
18
18
|
NOT `<menu-ui>` (legacy pattern) — ★ commonly mis-implemented
|
|
19
19
|
4. Sidebar nav wrap around `<nav-ui>` — `<section>` (per the examples),
|
|
20
|
-
`<section-ui>` (card-style chrome), or `<
|
|
20
|
+
`<section-ui>` (card-style chrome), or `<page-scroll>` all accepted;
|
|
21
21
|
a bare `<nav-ui>` direct child overflows long lists
|
|
22
22
|
5. Sidebar `<admin-statusbar slot="footer">` with `<select-ui avatar="…">`
|
|
23
23
|
(user menu) — ★ commonly missing
|
|
@@ -28,17 +28,23 @@ the live admin-dashboard example.
|
|
|
28
28
|
★ spacer + actions commonly missing
|
|
29
29
|
8. `[data-actions]` contains `<popover-ui>` + `<theme-panel slot="content">`
|
|
30
30
|
(there is no `<theme-picker-ui>`)
|
|
31
|
-
9. `<
|
|
32
|
-
`<router-ui>` (or `<
|
|
33
|
-
**[
|
|
34
|
-
rename to `page-scroll` (both modes carried over);
|
|
35
|
-
|
|
31
|
+
9. `<page-scroll>` wrapping optional `<aside data-subnav hidden>` +
|
|
32
|
+
`<router-ui>` (or `<page-ui band>` directly for non-routed)
|
|
33
|
+
**[deleted, ADR-0098 / gh#3745]** `admin-scroll` was a wholesale
|
|
34
|
+
rename to `page-scroll` (both modes carried over); its one-release
|
|
35
|
+
deprecation window has closed and the module is deleted (gh#3745).
|
|
36
|
+
The `admin-page` family (`admin-page`/`admin-page-header`/
|
|
37
|
+
`admin-page-body`) is deleted outright per ADR-0098, no compat alias.
|
|
38
|
+
`page-scroll` wrapping `page-ui[band]` is the sole successor for the
|
|
39
|
+
routed and non-routed case alike.
|
|
36
40
|
10. `<admin-page>` with `<admin-page-header>` + `<admin-page-body>`
|
|
37
|
-
**[
|
|
38
|
-
|
|
39
|
-
is now the one canonical page-chrome
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
**[deleted, ADR-0098]** The `admin-page` family is deleted outright,
|
|
42
|
+
no compat alias and no open deprecation window, in favor of
|
|
43
|
+
`page-ui[band]`; `page-ui` is now the one canonical page-chrome
|
|
44
|
+
primitive (item 9 above shows the live composition: `page-scroll`
|
|
45
|
+
wrapping `page-ui[band]`). This item is kept only so this anatomy
|
|
46
|
+
still recognizes the retired shape when auditing legacy surfaces
|
|
47
|
+
that predate the migration; never author it in new surfaces.
|
|
42
48
|
11. `<admin-statusbar>` at content footer (version strip) — ★ commonly missing
|
|
43
49
|
12. Second `<admin-sidebar slot="trailing">` (inspector rail, hidden by
|
|
44
50
|
default) — strongly recommended
|
|
@@ -64,7 +70,7 @@ the live admin-dashboard example.
|
|
|
64
70
|
| content topbar missing `[data-spacer]` / `[data-actions]` | warning — part 7 |
|
|
65
71
|
| content missing trailing `<admin-statusbar>` | warning — part 11 |
|
|
66
72
|
| sidebar topbar contains only plain text | warning — part 3 context switcher |
|
|
67
|
-
| `<
|
|
73
|
+
| `<page-scroll>` missing around `<page-ui band>` | critical, part 9 (the enforcing script `audit-shell-composition.mjs` tiers this critical: without the scroll+chrome wrapper the content renders flush with the topbar, no margins). **[deleted, ADR-0098 / gh#3745]** `admin-scroll` and the `admin-page` family are both deleted outright, no compat alias; the successor is `page-scroll` wrapping `page-ui[band]` |
|
|
68
74
|
|
|
69
75
|
## Opt-out contract
|
|
70
76
|
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
import { execFileSync } from 'node:child_process';
|
|
21
21
|
import process from 'node:process';
|
|
22
22
|
import { assertMonorepoRoot } from './assert-monorepo-root.mjs';
|
|
23
|
+
import { warnIfStaleCopy } from './stale-copy-warning.mjs';
|
|
23
24
|
import { PACKAGE_ROSTER } from './package-paths.mjs';
|
|
24
25
|
|
|
25
26
|
// Instance data — fork-configurable. The npm scope the lockstep roster publishes under.
|
|
@@ -288,6 +289,13 @@ async function main() {
|
|
|
288
289
|
const args = parseArgs(process.argv.slice(2));
|
|
289
290
|
// Fail-fast guard: refuse to run npm/gh against a non-monorepo directory.
|
|
290
291
|
assertMonorepoRoot(process.cwd());
|
|
292
|
+
// Stale-plugin-cache self-check (ticket #3943 item 2), same primitive
|
|
293
|
+
// release-pack.mjs's own top-level check now calls.
|
|
294
|
+
warnIfStaleCopy(
|
|
295
|
+
import.meta.url,
|
|
296
|
+
process.cwd(),
|
|
297
|
+
'packages/plugins/adia-ui-forge/skills/package-release/scripts/dispatch-publish.mjs',
|
|
298
|
+
);
|
|
291
299
|
if (args.after) checkAfter(args.after, args.scope);
|
|
292
300
|
|
|
293
301
|
// Recovery mode — verify each publish workflow SUCCEEDED for its pushed tag
|
|
@@ -103,15 +103,15 @@ export const GATE_ROSTER = [
|
|
|
103
103
|
// on the handoff re-run, since the promoted heading was already on main).
|
|
104
104
|
{ n: 26, cmd: 'node scripts/release/check-dx-sweep-freshness.mjs', targetVersionArg: true, what: 'DX sweep fresh + not regression-RED' },
|
|
105
105
|
// gh#1135 (WS-4 SPEC REQ-06): a fresh free-form eval run feeds the health
|
|
106
|
-
// record gate 28 writes
|
|
106
|
+
// record gate 28 writes: without it, "latest" under packages/gen-ui/engine/evals/mcp/runs/ could
|
|
107
107
|
// be a stale leftover from an earlier ad-hoc run, not this cut's own state.
|
|
108
|
-
{ n: 27, cmd: 'npm run eval:diff -- --engine free-form', what: 'fresh free-form eval run (evals/health input)' },
|
|
109
|
-
// REQ-06: commits evals/health/<version>.json
|
|
108
|
+
{ n: 27, cmd: 'npm run eval:diff -- --engine free-form', what: 'fresh free-form eval run (packages/gen-ui/engine/evals/health input)' },
|
|
109
|
+
// REQ-06: commits packages/gen-ui/engine/evals/health/<version>.json; the version this record
|
|
110
110
|
// names is the CUT's target version from the start, never the
|
|
111
111
|
// pre/post-bump `hygieneVersion` gate 24 needs (a claim-validation
|
|
112
112
|
// concern this is not) — hence `targetVersionArg` rather than
|
|
113
113
|
// `versionArg`, a distinct flag release-pack.mjs's step3PreFlight reads.
|
|
114
|
-
{ n: 28, cmd: 'node scripts/release/write-eval-health.mjs', what: 'evals/health/<version>.json committed, AC-01/AC-02 run for real', targetVersionArg: true },
|
|
114
|
+
{ n: 28, cmd: 'node scripts/release/write-eval-health.mjs', what: 'packages/gen-ui/engine/evals/health/<version>.json committed, AC-01/AC-02 run for real', targetVersionArg: true },
|
|
115
115
|
// ADR-0048 (gh#1192) estate-split latch. Between P1 (mint @adia-ai/gen-ui)
|
|
116
116
|
// and P5 (shims + roster) the tree is correct in-repo but deliberately
|
|
117
117
|
// NOT publishable: the old package dirs are `private: true` stubs the
|
|
@@ -76,6 +76,7 @@ import { GATE_ROSTER } from './gate-roster.mjs';
|
|
|
76
76
|
// hand-duplicating them — bump.mjs guards its own main()/selftest() behind
|
|
77
77
|
// an entry-point check specifically so this import is side-effect-free.
|
|
78
78
|
import { PINNED_REFS, REPO_PINNED_REFS } from './bump.mjs';
|
|
79
|
+
import { warnIfStaleCopy } from './stale-copy-warning.mjs';
|
|
79
80
|
|
|
80
81
|
const SCRIPT_DIR = path.dirname(new URL(import.meta.url).pathname);
|
|
81
82
|
const REPO = process.cwd();
|
|
@@ -523,18 +524,18 @@ function step1ReBaseline(args) {
|
|
|
523
524
|
// since neither touches (a)'s files). This is the roster's one
|
|
524
525
|
// CSS-before-JS-shaped file-write ordering dependency: gate 27
|
|
525
526
|
// (`eval:diff --engine free-form`) writes a fresh
|
|
526
|
-
// evals/mcp/runs/<ISO>/ directory; gate 28
|
|
527
|
+
// packages/gen-ui/engine/evals/mcp/runs/<ISO>/ directory; gate 28
|
|
527
528
|
// (write-eval-health.mjs) reads whichever directory under
|
|
528
|
-
// evals/mcp/runs/ SORTS LEXICALLY LAST
|
|
529
|
+
// packages/gen-ui/engine/evals/mcp/runs/ SORTS LEXICALLY LAST, not specifically the
|
|
529
530
|
// latest free-form run — and expects free-form.json there. Gate
|
|
530
531
|
// 16 (`eval:diff --engine zettel`) writes its OWN fresh directory
|
|
531
532
|
// under that same root; if 16 and 27 ran concurrently and 16
|
|
532
533
|
// happened to finish after 27, gate 28 would silently pick 16's
|
|
533
534
|
// directory (no free-form.json) and commit
|
|
534
|
-
// evals/health/<version>.json with `engines.free-form: null
|
|
535
|
+
// packages/gen-ui/engine/evals/health/<version>.json with `engines.free-form: null`, a
|
|
535
536
|
// real, silent coverage loss the roster's fixed serial order
|
|
536
537
|
// prevents today only by construction. Keeping 16 before 27
|
|
537
|
-
// 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,
|
|
538
539
|
// preserves that guarantee.
|
|
539
540
|
//
|
|
540
541
|
// Fail-fast BETWEEN phases (a Phase 1 failure skips Phase 2 outright — no
|
|
@@ -1224,7 +1225,7 @@ function step5Commit(args) {
|
|
|
1224
1225
|
// targetVersionArg comment above). Under --skip-gates the record was
|
|
1225
1226
|
// never written (Step 3 returned early), so staging it would make the
|
|
1226
1227
|
// whole `git add` fail on a missing pathspec (PR #1204 review).
|
|
1227
|
-
...(args.skipGates ? [] : [`evals/health/${args.version}.json`]),
|
|
1228
|
+
...(args.skipGates ? [] : [`packages/gen-ui/engine/evals/health/${args.version}.json`]),
|
|
1228
1229
|
...LOCKSTEP_ROSTER.flatMap(({ name, dir, plugin }) => {
|
|
1229
1230
|
// Roster-driven (H3): each package stages its manifest + CHANGELOG;
|
|
1230
1231
|
// plugins also stage .claude-plugin/plugin.json (the /plugin-update
|
|
@@ -1265,6 +1266,34 @@ function step5Commit(args) {
|
|
|
1265
1266
|
// package.json is already staged above; web-modules' peer-pin entry
|
|
1266
1267
|
// targets that same package.json).
|
|
1267
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',
|
|
1268
1297
|
];
|
|
1269
1298
|
const dedupedReleaseFiles = [...new Set(releaseFiles)];
|
|
1270
1299
|
sh(`git add ${dedupedReleaseFiles.join(' ')}`, args);
|
|
@@ -1746,24 +1775,21 @@ async function main() {
|
|
|
1746
1775
|
// Fail-fast guard: refuse to git/npm/gh/curl against a non-monorepo directory.
|
|
1747
1776
|
assertMonorepoRoot(REPO);
|
|
1748
1777
|
console.log(`package-release release-pack — v${args.version} (${args.mode})${args.dry ? ' [DRY]' : ''}`);
|
|
1749
|
-
// Stale-plugin-cache self-check (v0.8.32 cut, first failure
|
|
1750
|
-
// plugin cache only refreshes on a
|
|
1751
|
-
//
|
|
1752
|
-
//
|
|
1753
|
-
//
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
console.error(` node ${repoCopy} <same args>\n`);
|
|
1765
|
-
}
|
|
1766
|
-
}
|
|
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
|
+
);
|
|
1767
1793
|
// gh#3342: --from-step10 resumes a handoff that already tagged, pushed,
|
|
1768
1794
|
// and published — skip straight to Step 10 (GH releases + site deploy)
|
|
1769
1795
|
// rather than a plain re-run, which would re-run the full pre-flight gate
|
|
@@ -2030,7 +2056,7 @@ async function selftest() {
|
|
|
2030
2056
|
// gh#1197 — Step 5's staging pathspec must carry gate 28's committed
|
|
2031
2057
|
// health record, or it rides untracked past the release commit
|
|
2032
2058
|
// (v0.8.35/v0.8.36 both did, PR #1196 swept them by hand).
|
|
2033
|
-
'evals/health/9.9.9.json',
|
|
2059
|
+
'packages/gen-ui/engine/evals/health/9.9.9.json',
|
|
2034
2060
|
// gh#1899 — Step 4 must regenerate the Codex plugin manifests post-bump
|
|
2035
2061
|
// (the v0.8.48 release PR #1897 shipped stale ones without this step).
|
|
2036
2062
|
'Step 4d.6 — regenerate Codex plugin manifests',
|
|
@@ -2075,6 +2101,18 @@ async function selftest() {
|
|
|
2075
2101
|
'[dry] npm run build:bundles',
|
|
2076
2102
|
'packages/web-components/dist',
|
|
2077
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',
|
|
2078
2116
|
// gh#2473 — Step 5.6's unstaged-tracked-files guard must actually run
|
|
2079
2117
|
// (as a no-op) on a clean --dry plan. Its real leftover-detection
|
|
2080
2118
|
// logic is unit-tested directly above via unstagedTrackedLeftovers();
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// stale-copy-warning.mjs: generalizes release-pack.mjs's own STALE-COPY
|
|
3
|
+
// WARNING (ticket #3943 item 2, gh#3764 line 55) into a small helper this
|
|
4
|
+
// skill's own scripts can share.
|
|
5
|
+
//
|
|
6
|
+
// WHY
|
|
7
|
+
//
|
|
8
|
+
// A script reached through an installed plugin cache
|
|
9
|
+
// (`~/.claude/plugins/cache/...`) only refreshes on a version bump, so
|
|
10
|
+
// running it mid-cycle can execute a version that predates the repo's own
|
|
11
|
+
// copy at the same relative path. This is invisible to a repo grep: the
|
|
12
|
+
// grep reads the repo's own, already-corrected file, while the running
|
|
13
|
+
// process reads the cache's stale one: the filer of ticket #3943 hit this
|
|
14
|
+
// exact shape twice in one night (gh#3886, and the filing session itself).
|
|
15
|
+
//
|
|
16
|
+
// This module names ONE detector and ONE formatter, both pure, plus a thin
|
|
17
|
+
// convenience wrapper a script calls once near its own entrypoint. It never
|
|
18
|
+
// errors and never exits the caller's process: per the ticket's own
|
|
19
|
+
// framing, this is a documented check, not a gate: the repo copy is
|
|
20
|
+
// "almost certainly newer," never provably so from bytes alone, so the
|
|
21
|
+
// right response is a loud, named warning, not a failure.
|
|
22
|
+
//
|
|
23
|
+
// PACKAGE-LOCAL BY DESIGN (ticket #3943's own check:pr-ready finding): this
|
|
24
|
+
// file lives here, inside package-release/scripts/, rather than the repo's
|
|
25
|
+
// top-level scripts/lib/, because this skill publishes as its own npm
|
|
26
|
+
// package (`@adia-ai/adia-ui-forge`, PACKAGE_ROSTER). A script inside that
|
|
27
|
+
// package importing a file OUTSIDE its own package root resolves fine in a
|
|
28
|
+
// pnpm worktree but is an unresolvable specifier once packed
|
|
29
|
+
// (`npm run check:packed-imports`, which caught exactly this on the first
|
|
30
|
+
// draft of this change: an escaped-root import out of the tarball). Same
|
|
31
|
+
// reasoning `bump.mjs`'s own inlined `isEntryPoint()` already states for
|
|
32
|
+
// not importing `scripts/lib/is-entry-point.mjs`; this file follows that
|
|
33
|
+
// same precedent (its own inlined entry-point check below, no cross-
|
|
34
|
+
// package import) rather than introducing a second copy of the hazard.
|
|
35
|
+
// A future repo-TOOLING script under the top-level `scripts/` tree (never
|
|
36
|
+
// packaged into any tarball) that wants this same pattern is free to copy
|
|
37
|
+
// this file's own two pure functions there; nothing here assumes only one
|
|
38
|
+
// copy may ever exist, the packaging boundary is exactly why one shared
|
|
39
|
+
// copy cannot serve both sides.
|
|
40
|
+
//
|
|
41
|
+
// USAGE (a script inside this same package, e.g. a sibling in
|
|
42
|
+
// package-release/scripts/):
|
|
43
|
+
//
|
|
44
|
+
// import { warnIfStaleCopy } from './stale-copy-warning.mjs';
|
|
45
|
+
// warnIfStaleCopy(import.meta.url, REPO_ROOT, 'path/relative/to/REPO_ROOT/this-script.mjs');
|
|
46
|
+
//
|
|
47
|
+
// SELFTEST:
|
|
48
|
+
//
|
|
49
|
+
// node packages/plugins/adia-ui-forge/skills/package-release/scripts/stale-copy-warning.mjs selftest
|
|
50
|
+
|
|
51
|
+
import fs from 'node:fs';
|
|
52
|
+
import os from 'node:os';
|
|
53
|
+
import path from 'node:path';
|
|
54
|
+
import { fileURLToPath } from 'node:url';
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Pure: compares `selfPath`'s own bytes against the repo's copy at
|
|
58
|
+
* `repoRoot/relativePath`. Returns `null` when there is nothing to warn
|
|
59
|
+
* about (no repo copy exists to compare against, `selfPath` already IS the
|
|
60
|
+
* repo copy, or the bytes agree); otherwise `{ repoCopyPath, selfPath }`,
|
|
61
|
+
* both resolved absolute paths.
|
|
62
|
+
* @param {{selfPath: string, repoRoot: string, relativePath: string, readFile?: Function, fileExists?: Function}} args
|
|
63
|
+
* @returns {{repoCopyPath: string, selfPath: string} | null}
|
|
64
|
+
*/
|
|
65
|
+
export function detectStaleCopy({ selfPath, repoRoot, relativePath, readFile = fs.readFileSync, fileExists = fs.existsSync }) {
|
|
66
|
+
const resolvedSelf = path.resolve(selfPath);
|
|
67
|
+
const repoCopyPath = path.resolve(repoRoot, relativePath);
|
|
68
|
+
if (!fileExists(repoCopyPath)) return null; // nothing at the repo's own path to compare against
|
|
69
|
+
if (resolvedSelf === repoCopyPath) return null; // already running the repo's own copy
|
|
70
|
+
let selfText;
|
|
71
|
+
let repoText;
|
|
72
|
+
try {
|
|
73
|
+
selfText = readFile(selfPath, 'utf8');
|
|
74
|
+
repoText = readFile(repoCopyPath, 'utf8');
|
|
75
|
+
} catch {
|
|
76
|
+
return null; // unreadable on either side; nothing this check can prove
|
|
77
|
+
}
|
|
78
|
+
if (selfText === repoText) return null;
|
|
79
|
+
return { repoCopyPath, selfPath: resolvedSelf };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Pure: renders `detectStaleCopy`'s own non-null result as the printable
|
|
84
|
+
* warning block, the same shape release-pack.mjs's own inline check used.
|
|
85
|
+
* @param {{repoCopyPath: string, selfPath: string}} hit
|
|
86
|
+
* @returns {string}
|
|
87
|
+
*/
|
|
88
|
+
export function formatStaleCopyWarning({ repoCopyPath, selfPath }) {
|
|
89
|
+
return [
|
|
90
|
+
'',
|
|
91
|
+
`⚠ STALE-COPY WARNING: this ${path.basename(selfPath)} differs from the repo's own copy at`,
|
|
92
|
+
` ${repoCopyPath}`,
|
|
93
|
+
" The installed plugin cache lags the repo between cuts (ticket #3943, gh#3764 line 55);",
|
|
94
|
+
' the repo copy is almost certainly newer, prefer running it:',
|
|
95
|
+
` node ${repoCopyPath} <same args>`,
|
|
96
|
+
'',
|
|
97
|
+
].join('\n');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Convenience wrapper for a script's own top-level self-check: resolves its
|
|
102
|
+
* own path from `import.meta.url`, compares it against
|
|
103
|
+
* `repoRoot/relativePath`, and logs (never throws, never exits the caller)
|
|
104
|
+
* when the two diverge. Call this once, near the top of a script's own
|
|
105
|
+
* entrypoint, before any real work.
|
|
106
|
+
* @param {string} selfUrl the caller's own `import.meta.url`
|
|
107
|
+
* @param {string} repoRoot the target repo's own root, resolved the same way the caller resolves it for everything else
|
|
108
|
+
* @param {string} relativePath this script's own path, relative to `repoRoot`
|
|
109
|
+
* @param {{log?: Function}} [opts]
|
|
110
|
+
* @returns {boolean} whether a warning was logged
|
|
111
|
+
*/
|
|
112
|
+
export function warnIfStaleCopy(selfUrl, repoRoot, relativePath, { log = console.error } = {}) {
|
|
113
|
+
const selfPath = fileURLToPath(selfUrl);
|
|
114
|
+
const hit = detectStaleCopy({ selfPath, repoRoot, relativePath });
|
|
115
|
+
if (hit) log(formatStaleCopyWarning(hit));
|
|
116
|
+
return hit !== null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ---- selftest -------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
function selftest() {
|
|
122
|
+
let ran = 0;
|
|
123
|
+
const assert = (cond, msg) => {
|
|
124
|
+
ran += 1;
|
|
125
|
+
if (!cond) throw new Error(`selftest failed: ${msg}`);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const sandbox = fs.mkdtempSync(path.join(os.tmpdir(), 'stale-copy-warning-'));
|
|
129
|
+
try {
|
|
130
|
+
const repoRoot = path.join(sandbox, 'repo');
|
|
131
|
+
const relativePath = path.join('scripts', 'thing.mjs');
|
|
132
|
+
const repoCopyPath = path.join(repoRoot, relativePath);
|
|
133
|
+
const cachePath = path.join(sandbox, 'cache-copy.mjs');
|
|
134
|
+
fs.mkdirSync(path.dirname(repoCopyPath), { recursive: true });
|
|
135
|
+
|
|
136
|
+
// Positive control: cache bytes differ from the repo's own copy.
|
|
137
|
+
fs.writeFileSync(repoCopyPath, 'v2 (repo, newer)\n');
|
|
138
|
+
fs.writeFileSync(cachePath, 'v1 (cache, stale)\n');
|
|
139
|
+
const diverged = detectStaleCopy({ selfPath: cachePath, repoRoot, relativePath });
|
|
140
|
+
assert(diverged !== null, 'differing bytes must be detected');
|
|
141
|
+
assert(diverged.repoCopyPath === path.resolve(repoCopyPath), 'the hit names the resolved repo copy path');
|
|
142
|
+
const warning = formatStaleCopyWarning(diverged);
|
|
143
|
+
assert(warning.includes('STALE-COPY WARNING'), 'the formatted warning names itself');
|
|
144
|
+
assert(warning.includes(repoCopyPath), 'the formatted warning cites the repo copy path');
|
|
145
|
+
|
|
146
|
+
// Negative control: bytes agree.
|
|
147
|
+
fs.writeFileSync(cachePath, 'v2 (repo, newer)\n');
|
|
148
|
+
assert(
|
|
149
|
+
detectStaleCopy({ selfPath: cachePath, repoRoot, relativePath }) === null,
|
|
150
|
+
'identical bytes must never warn',
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
// Negative control: the "self" path already IS the repo's own copy
|
|
154
|
+
// (release-pack.mjs's own first guard: never warn about itself).
|
|
155
|
+
assert(
|
|
156
|
+
detectStaleCopy({ selfPath: repoCopyPath, repoRoot, relativePath }) === null,
|
|
157
|
+
'running the repo copy directly must never warn',
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
// Negative control: no repo copy exists at all (nothing to compare
|
|
161
|
+
// against, e.g. a script the repo has since deleted).
|
|
162
|
+
assert(
|
|
163
|
+
detectStaleCopy({ selfPath: cachePath, repoRoot, relativePath: path.join('scripts', 'gone.mjs') }) === null,
|
|
164
|
+
'a missing repo copy must never warn (nothing provable)',
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// warnIfStaleCopy: the convenience wrapper logs on divergence and
|
|
168
|
+
// reports it, using a real file:// URL the way a real caller's
|
|
169
|
+
// import.meta.url would.
|
|
170
|
+
fs.writeFileSync(cachePath, 'v1 (cache, stale)\n');
|
|
171
|
+
let logged = '';
|
|
172
|
+
const warned = warnIfStaleCopy(
|
|
173
|
+
new URL(`file://${cachePath}`).href,
|
|
174
|
+
repoRoot,
|
|
175
|
+
relativePath,
|
|
176
|
+
{ log: (msg) => { logged += msg; } },
|
|
177
|
+
);
|
|
178
|
+
assert(warned === true, 'warnIfStaleCopy reports true when it warns');
|
|
179
|
+
assert(logged.includes('STALE-COPY WARNING'), 'warnIfStaleCopy actually logs the warning text');
|
|
180
|
+
|
|
181
|
+
// And stays silent (never throws, never exits) when bytes agree.
|
|
182
|
+
fs.writeFileSync(cachePath, 'v2 (repo, newer)\n');
|
|
183
|
+
let loggedAgain = '';
|
|
184
|
+
const clean = warnIfStaleCopy(
|
|
185
|
+
new URL(`file://${cachePath}`).href,
|
|
186
|
+
repoRoot,
|
|
187
|
+
relativePath,
|
|
188
|
+
{ log: (msg) => { loggedAgain += msg; } },
|
|
189
|
+
);
|
|
190
|
+
assert(clean === false, 'warnIfStaleCopy reports false when bytes agree');
|
|
191
|
+
assert(loggedAgain === '', 'warnIfStaleCopy logs nothing when bytes agree');
|
|
192
|
+
} finally {
|
|
193
|
+
fs.rmSync(sandbox, { recursive: true, force: true });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
console.log(`stale-copy-warning selftest, all assertions passed, ${ran} controls ran`);
|
|
197
|
+
return 0;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Realpath-safe entry-point check, inlined rather than imported from the
|
|
201
|
+
// top-level scripts/lib/is-entry-point.mjs (see the package-local-by-design
|
|
202
|
+
// note above); mirrors bump.mjs's own identical inlining in this same dir.
|
|
203
|
+
function isEntryPoint() {
|
|
204
|
+
if (!process.argv[1]) return false;
|
|
205
|
+
try {
|
|
206
|
+
return fs.realpathSync(fileURLToPath(import.meta.url)) === fs.realpathSync(path.resolve(process.argv[1]));
|
|
207
|
+
} catch {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (isEntryPoint()) {
|
|
213
|
+
const topArgv = process.argv.slice(2);
|
|
214
|
+
process.exit(topArgv[0] === 'selftest' ? selftest() : 0);
|
|
215
|
+
}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { execSync } from 'node:child_process';
|
|
16
16
|
import process from 'node:process';
|
|
17
17
|
import { assertMonorepoRoot } from './assert-monorepo-root.mjs';
|
|
18
|
+
import { warnIfStaleCopy } from './stale-copy-warning.mjs';
|
|
18
19
|
import { PACKAGE_ROSTER } from './package-paths.mjs';
|
|
19
20
|
|
|
20
21
|
// The roster is single-sourced in package-paths.mjs (H3) — the `adia-ui-*-v*`
|
|
@@ -60,6 +61,13 @@ function main() {
|
|
|
60
61
|
const args = parseArgs(process.argv.slice(2));
|
|
61
62
|
// Fail-fast guard: refuse to run git against a non-monorepo directory.
|
|
62
63
|
assertMonorepoRoot(args.repo);
|
|
64
|
+
// Stale-plugin-cache self-check (ticket #3943 item 2), same primitive
|
|
65
|
+
// release-pack.mjs's own top-level check now calls.
|
|
66
|
+
warnIfStaleCopy(
|
|
67
|
+
import.meta.url,
|
|
68
|
+
args.repo,
|
|
69
|
+
'packages/plugins/adia-ui-forge/skills/package-release/scripts/tag-lockstep.mjs',
|
|
70
|
+
);
|
|
63
71
|
const tags = buildTagList(args.version);
|
|
64
72
|
|
|
65
73
|
if (args.deleteMode) {
|
|
@@ -30,6 +30,7 @@ file, including the depth references those entries cross-link.
|
|
|
30
30
|
## Demos & doc surfaces
|
|
31
31
|
|
|
32
32
|
- [composite-demo-protocol.md](composite-demo-protocol.md) — what discipline governs any `packages/web-modules/**` demo edit, beyond what the `Pattern source:` gate can see?
|
|
33
|
+
- [examples-structure.md](examples-structure.md): what order do a primitive's own `<name>.examples.html` `<h2 variant="section">` blocks follow, and where does `Typography registers` sit relative to `Edge Cases` and the generated API tail?
|
|
33
34
|
- [canonical-pattern-index.md](canonical-pattern-index.md) — which canonical `.contents.html` files should I survey for a given UI type? Auto-generated; regenerate via `scripts/build-canonical-pattern-index.mjs`.
|
|
34
35
|
- [trait-pages.md](trait-pages.md) — which sections, in which order, must a `site/pages/traits/<name>/` detail page carry (ADR-0019 template)?
|
|
35
36
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# `<name>.examples.html` section order convention
|
|
2
|
+
|
|
3
|
+
Every primitive's own `packages/web-components/components/<name>/<name>.examples.html`
|
|
4
|
+
follows one binding `<h2 variant="section">` order. gh#4159: a merged-registers sweep
|
|
5
|
+
(gh#3843) found the order inconsistent across five files, `alert` had "Typography
|
|
6
|
+
registers" near the top, `code`/`command` had it after the generated API tail, and
|
|
7
|
+
`accordion`/`divider` had it before Edge Cases, with no reference doc anywhere stating
|
|
8
|
+
which shape was correct. `site-docs-authoring`'s own `intent.md` explicitly declines
|
|
9
|
+
`packages/web-components/components/*/*.examples.html` and hands it here; this file is
|
|
10
|
+
that hand-off's landing spot.
|
|
11
|
+
|
|
12
|
+
## Required order
|
|
13
|
+
|
|
14
|
+
1. **Variant/usage sections**, one `<h2 variant="section">` per prop value, state, or
|
|
15
|
+
usage recipe the component demonstrates (`inline`, `block with language`, `grouped`,
|
|
16
|
+
…). Author-ordered; no fixed sequence among these.
|
|
17
|
+
2. **`Combinations`**, sections composed together in context.
|
|
18
|
+
3. **`Typography registers`**, the three-register (`scale="ui-sm"` / regular /
|
|
19
|
+
`scale="content-md"`) showcase, when the component carries one. Hand-authored, so it
|
|
20
|
+
sits with the other hand-authored sections above, immediately after `Combinations`
|
|
21
|
+
and before `Edge Cases`, never after the generated API tail.
|
|
22
|
+
4. **`Edge Cases`**, boundary conditions and unusual content.
|
|
23
|
+
5. **Generated API sections**, in order: `Properties`, `Events`, `Methods` (when
|
|
24
|
+
applicable), `CSS Tokens`, `Slots`, `A2UI`, `Related`.
|
|
25
|
+
|
|
26
|
+
Rationale for `Typography registers` landing before `Edge Cases` rather than after the
|
|
27
|
+
API tail: it is hand-authored prose+markup like `Combinations`/`Edge Cases`, not a
|
|
28
|
+
generated table like `Properties`/`Events`/`CSS Tokens`; grouping it with the other
|
|
29
|
+
hand-authored sections keeps the generated-vs-authored boundary at one place in the file
|
|
30
|
+
instead of two.
|
|
31
|
+
|
|
32
|
+
## Why this also matters for `.examples.md`
|
|
33
|
+
|
|
34
|
+
`scripts/build/generate-examples-md.mjs` derives each `<name>.examples.md` from the
|
|
35
|
+
`.examples.html` source. Its main loop picks only the first `MAX_FRAGMENTS` (3)
|
|
36
|
+
`data-section` blocks in document order, so a section sitting far down the file (several
|
|
37
|
+
variant sections deep, as most shipped components run) would normally never be selected.
|
|
38
|
+
`Typography registers` is the one named exception: `processComponent()` guarantees its
|
|
39
|
+
inclusion regardless of ordinal position, so this convention is about authoring
|
|
40
|
+
coherence and readability, not about whether the section survives derivation at all.
|
|
41
|
+
|
|
42
|
+
## Enforcement
|
|
43
|
+
|
|
44
|
+
`generate-examples-md.mjs`'s own `checkSectionOrder()`, run as part of `npm run
|
|
45
|
+
check:examples-md-fresh` (`--verify` mode) and the plain generate run alike, parses
|
|
46
|
+
every `packages/web-components/components/*/*.examples.html`'s ordered `<h2
|
|
47
|
+
variant="section">` list and reds, naming the file and both indices, when `Typography
|
|
48
|
+
registers` sits after `Edge Cases`. Pre-existing violations outside a given PR's own
|
|
49
|
+
lane are grandfathered via `scripts/build/examples-registers-order-baseline.json` (a
|
|
50
|
+
ratchet: printed as an advisory finding, never silently dropped, and never allowed to
|
|
51
|
+
grow) so the gate never regresses unrelated work; shrink that list to zero as each
|
|
52
|
+
file's own section order gets fixed.
|
|
@@ -448,17 +448,18 @@ Consumers (CodeMirror layout, canvas redraw, dependent UI) listen on the shell o
|
|
|
448
448
|
|
|
449
449
|
### admin cluster (canonical reference)
|
|
450
450
|
|
|
451
|
-
**[deprecated 2026-09-01, ADR-0098]**
|
|
452
|
-
`admin-page-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
`
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
-
|
|
460
|
-
|
|
461
|
-
|
|
451
|
+
**[deprecated 2026-09-01, ADR-0098]** The inline child enumeration this section
|
|
452
|
+
used to carry is retired. `admin-page`/`admin-page-header`/`admin-page-body`
|
|
453
|
+
were deleted outright (no compat alias); `admin-scroll` was renamed wholesale
|
|
454
|
+
to `page-scroll`, and its own one-release deprecation window has since closed,
|
|
455
|
+
the module deleted (gh#3745). `page-ui[band]` + `page-scroll` are the canonical
|
|
456
|
+
page-chrome pair.
|
|
457
|
+
|
|
458
|
+
For `admin-shell`'s own live composition, the current bespoke children (the
|
|
459
|
+
`@scope`-per-tag CSS convention landed in gh#4103), and the full 13-part
|
|
460
|
+
canonical anatomy, see
|
|
461
|
+
[admin-shell-anatomy.md](../../demo-audit/references/admin-shell-anatomy.md),
|
|
462
|
+
kept in sync with `site/index.html`'s live example rather than restated here.
|
|
462
463
|
|
|
463
464
|
### chat cluster (replicated pattern)
|
|
464
465
|
|