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

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,6 +1,6 @@
1
1
  {
2
2
  "name": "adia-forge",
3
- "version": "0.8.10",
3
+ "version": "0.8.12",
4
4
  "description": "Maintain the adia-ui (@adia-ai) framework itself \u2014 author primitives and shells, run the A2UI generation pipeline and its corpus, review gen-UI quality, sweep QA, cut releases, deploy. The maintainer counterpart to adia-factory (the consumer/app-author plugin).",
5
5
  "author": {
6
6
  "name": "Kim",
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog — adia-forge
2
2
 
3
+ ## [0.8.12] — 2026-07-24
4
+
5
+ ### Fixed
6
+ - **`AGENTS.md`, `adia-author/SKILL.md`, `adia-author/references/code-style.md`: primitive count corrected 127 → 125** — the actual `packages/web-components/components/*/` directory count (excluding `index.js`/`index.d.ts`/`overview.*`); `AGENTS.md` had two separate stale mentions, one already partially corrected in a prior pass and one missed.
7
+
8
+ ### Maintenance
9
+ - **`.claude-plugin/plugin.json` version bump** — moves in lockstep with package.json (the `/plugin update` cache key).
10
+ - **`skills/` touched in this release window** (2 file(s), e.g. `adia-author/SKILL.md`) — carried by the entries above.
11
+
12
+ ## [0.8.11] — 2026-07-23
13
+
14
+ ### Fixed
15
+ - **`adia-author`: dist-bundle regen joins the structural-gate sequence** (`skills/adia-author/SKILL.md`, `references/authoring-cycle.md`) — a new/changed component CSS `@import` or barrel export drifts `dist/` bundles, and the skill's verify sequence never said so; gh#390's PR failed CI's `check:css-bundles-fresh`/`check:js-bundles-fresh` exactly this way. The gate sequence and the barrel-registration step now both name `build:bundle-css`/`build:bundle-js` + the two freshness checks.
16
+ - **`adia-release`: `release-pack.mjs --mode handoff` validates `--gh-notes-file` at parse time** — the flag was only checked at Step 10 (GH releases), AFTER tags and npm publish were irreversible; the v0.8.10 cut died there live. Selftest case added.
17
+ - **`adia-release`: mid-Step-4 resume path documented** (`references/cut-procedure.md` §4-resume) — once any CHANGELOG promotion lands, re-invoking the orchestrator hard-errors on the idempotency guard ("already has ## [X.Y.Z]") forever; the doc now names the standalone-pieces sequence (bump → lockfile → lockstep → 4f → 4e → Step 5/5.7) instead of leaving the operator to reverse-engineer it mid-cut.
18
+ - **`adia-release`: stub-package ownership rule** (`references/changelog-discipline.md` §Stubs) — `insert-stub.mjs` inserts, never promotes; a hand-written `[Unreleased]` in a `--stub-packages` target survives as an orphaned second block and fails the loud guard (v0.8.10 lost a cycle to this across 6 packages). Rule: the tool owns stub packages; hand-written content means the package belongs in `--substantive-packages`.
19
+ - **`release-pretag-docs-gate`: a `cd <path> && …` command prefix now resolves the gate against THAT checkout, not the session cwd** (`bin/release-pretag-docs-gate`) — a session pinned to a stale worktree (own copy of the gate script, branch without the release's CHANGELOG sections) false-FAILed the v0.8.10 handoff even though the command explicitly cd'd to the clean primary checkout. Selftest fixtures added for the prefix resolution.
20
+
21
+ ### Maintenance
22
+ - **`.claude-plugin/plugin.json` version bump** — moves in lockstep with package.json (the `/plugin update` cache key).
23
+
3
24
  ## [0.8.10] — 2026-07-20
4
25
 
5
26
  ### Added
@@ -89,6 +89,33 @@ def classify(command):
89
89
  return None
90
90
 
91
91
 
92
+ CD_PREFIX = re.compile(r"""^\s*cd\s+(?:--\s+)?(['"]?)([^'";&|\n]+)\1\s*(?:&&|;)""")
93
+
94
+
95
+ def resolve_start_dir(command, cwd):
96
+ """A command that opens with `cd <path> && …` targets THAT checkout, not
97
+ the session's cwd. Live false FAIL (v0.8.10 handoff, 2026-07-20): the
98
+ session sat pinned in a stale worktree (its own copy of the gate script +
99
+ a branch with no [0.8.10] CHANGELOG sections) while the command cd'd to
100
+ the primary checkout on post-merge main — the gate validated the stale
101
+ worktree and denied a genuinely clean release. Prefer the cd target when
102
+ it is a real directory; otherwise fall back to the event cwd."""
103
+ m = CD_PREFIX.match(command or "")
104
+ if m:
105
+ # Mirror the shell: ~ expands only when the operand is UNQUOTED —
106
+ # `cd "~"` targets a literal ./~ (and typically fails), so resolving
107
+ # $HOME here would let a quoted-tilde push bypass the gate whenever
108
+ # $HOME lacks the gate script (review finding, PR #394).
109
+ target = m.group(2).strip()
110
+ if not m.group(1):
111
+ target = os.path.expanduser(target)
112
+ if not os.path.isabs(target) and cwd:
113
+ target = os.path.join(cwd, target)
114
+ if os.path.isdir(target):
115
+ return target
116
+ return cwd
117
+
118
+
92
119
  def find_repo_gate(start):
93
120
  """Walk up from `start` to the git root looking for the gate script —
94
121
  a release command launched from a subdirectory must not bypass the
@@ -125,10 +152,11 @@ def hook_mode():
125
152
  sys.exit(0)
126
153
  if event.get("tool_name") != "Bash":
127
154
  sys.exit(0)
128
- version = classify((event.get("tool_input") or {}).get("command", ""))
155
+ command = (event.get("tool_input") or {}).get("command", "")
156
+ version = classify(command)
129
157
  if not version:
130
158
  sys.exit(0)
131
- root, gate = find_repo_gate(event.get("cwd"))
159
+ root, gate = find_repo_gate(resolve_start_dir(command, event.get("cwd")))
132
160
  if not gate:
133
161
  sys.exit(0) # consumer repo — nothing to enforce here
134
162
  if version == AMBIGUOUS:
@@ -214,6 +242,27 @@ def selftest():
214
242
  if gate is not None:
215
243
  print("selftest FAIL: consumer repo must yield no gate", file=sys.stderr)
216
244
  sys.exit(1)
245
+ # resolve_start_dir: a `cd <path> && …` prefix must win over the session
246
+ # cwd (the v0.8.10 pinned-worktree false FAIL); anything else falls back.
247
+ with tempfile.TemporaryDirectory() as td:
248
+ real = os.path.join(td, "checkout")
249
+ os.makedirs(real)
250
+ cases_dir = [
251
+ (f"cd {real} && node release-pack.mjs --mode handoff --version 0.9.0", "/elsewhere", real),
252
+ (f'cd "{real}" && git push origin v0.9.0', "/elsewhere", real),
253
+ (f"cd {td}/missing && node release-pack.mjs --mode handoff --version 0.9.0", "/elsewhere", "/elsewhere"),
254
+ ("node release-pack.mjs --mode handoff --version 0.9.0", "/elsewhere", "/elsewhere"),
255
+ ("", "/elsewhere", "/elsewhere"),
256
+ # Quoted tilde: bash cd's to a literal ./~, NOT $HOME — the gate
257
+ # must not resolve $HOME either (quoted-tilde bypass, PR #394).
258
+ ('cd "~" && git push origin v0.9.0', "/elsewhere", "/elsewhere"),
259
+ ]
260
+ for cmd, cwd, want in cases_dir:
261
+ got = resolve_start_dir(cmd, cwd)
262
+ if got != want:
263
+ print(f"selftest FAIL: resolve_start_dir({cmd!r}, {cwd!r}) = {got!r}, want {want!r}", file=sys.stderr)
264
+ sys.exit(1)
265
+
217
266
  print("selftest OK")
218
267
 
219
268
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adia-ai/adia-ui-forge",
3
- "version": "0.8.10",
3
+ "version": "0.8.12",
4
4
  "description": "Maintain the adia-ui (@adia-ai) framework itself \u2014 author primitives and shells, run the A2UI generation pipeline and its corpus, review gen-UI quality, sweep QA, cut releases, deploy. The maintainer counterpart to adia-factory (the consumer/app-author plugin).",
5
5
  "keywords": [
6
6
  "adia-ui",
@@ -34,7 +34,7 @@ wins any tie with this skill. Monorepo source read while authoring (yaml, CSS,
34
34
 
35
35
  | Task shape | Load |
36
36
  | --- | --- |
37
- | NEW primitive ("add a component", "build a `<foo-ui>`") | [primitive-audit.md](references/primitive-audit.md) — MUST clear this §0 audit before authoring any new component or interactive surface (127 primitives exist; skipping it caused the table-toolbar rewrite) — then [authoring-cycle.md](references/authoring-cycle.md) |
37
+ | NEW primitive ("add a component", "build a `<foo-ui>`") | [primitive-audit.md](references/primitive-audit.md) — MUST clear this §0 audit before authoring any new component or interactive surface (125 primitives exist; skipping it caused the table-toolbar rewrite) — then [authoring-cycle.md](references/authoring-cycle.md) |
38
38
  | MODIFY existing primitive (prop / CSS / yaml) | [authoring-cycle.md](references/authoring-cycle.md), from Step 2 |
39
39
  | Shell / bespoke cluster child (`<admin-*>` `<chat-*>` `<editor-*>` `<simple-*>`) | [shell-patterns.md](references/shell-patterns.md) |
40
40
  | Promote repeated inline UI → shared module | [module-promotion.md](references/module-promotion.md) |
@@ -101,6 +101,8 @@ npm run smoke:engines # green
101
101
  node scripts/dev/audit-native-primitive-leak.mjs # 0 critical leaks
102
102
  node scripts/dev/audit-shell-composition.mjs # 0 critical defects
103
103
  node scripts/dev/audit-template-child-conflict.mjs # 0 critical (gh#284 shape — non-null template + slots.default)
104
+ npm run build:bundle-css && npm run build:bundle-js # regenerate dist bundles — any component CSS/JS change drifts them
105
+ npm run check:css-bundles-fresh && npm run check:js-bundles-fresh # both must be clean (CI gates; gh#390's PR failed here)
104
106
  ```
105
107
 
106
108
  A failed gate is the artifact: fix at the source, re-run the narrowest gate,
@@ -200,7 +200,11 @@ node scripts/build/components.mjs --verify # "clean — N files up-to-date"
200
200
 
201
201
  A primitive also needs BOTH barrel registrations — the CSS `@import` in
202
202
  `styles/components.css` AND the JS `export` in `components/index.js`
203
- (`scripts/audit/check-components-js-barrel.mjs` gates the second).
203
+ (`scripts/audit/check-components-js-barrel.mjs` gates the second). Either
204
+ registration drifts the built `dist/` bundles — regenerate them in the same
205
+ change (`npm run build:bundle-css && npm run build:bundle-js`) and commit the
206
+ `dist/` updates, or CI's `check:{css,js}-bundles-fresh` gates fail on the PR
207
+ (gh#390's PR shipped without this and failed exactly there).
204
208
 
205
209
  ## Step 5 — Run the project's verification gates
206
210
 
@@ -22,7 +22,7 @@ Every interactive surface, form control, layout container, and content block shi
22
22
  <input type="email" />
23
23
  ```
24
24
 
25
- The catalog has 127 component directories under `packages/web-components/components/`. Before composing, confirm via `get_component_map` (the a2ui MCP) that the one you want exists — and what its real prop names are.
25
+ The catalog has 125 component directories under `packages/web-components/components/`. Before composing, confirm via `get_component_map` (the a2ui MCP) that the one you want exists — and what its real prop names are.
26
26
 
27
27
  ## Layout primitives
28
28
 
@@ -92,6 +92,7 @@ The inserted block:
92
92
 
93
93
  - **Make the PATCH-cut asymmetry visible** when an entry mentions a dependency: version bumps to `X.Y.Z` while internal ranges hold at `^X.Y.0` — spell it as `dependencies["@adia-ai/<x>"]: ^X.Y.0 (covers X.Y.Z)` so it doesn't read as a bug.
94
94
  - **Stale stubs are an F-N1 hazard.** A "no source changes" stub on a package that DID change earns a warn; the fix is authoring (§Authoring), not stubbing.
95
+ - **The tool owns stub packages — never hand-write their `[Unreleased]`.** `insert-stub.mjs` INSERTS a fresh dated block at the top; it does not promote or replace existing content. A hand-authored `[Unreleased]` stub in a `--stub-packages` target survives the insert as a second, orphaned block below it, and the orchestrator's loud guard then fails the whole run ("N packages still have non-empty [Unreleased]") — the v0.8.10 cut lost a full cycle to exactly this across 6 packages. Ride-along packages get NO hand-written entry at cut time: leave them untouched and list them in `--stub-packages`. (A package that deserves hand-written content isn't a stub — it belongs in `--substantive-packages`.)
95
96
 
96
97
  ## §F-N1 diff-coverage — mechanized at cut time (Step 4f)
97
98
 
@@ -127,6 +127,34 @@ tag-creation/tag-push/publish-dispatch commands until it passes, and
127
127
  shipped tarballs with unpromoted `[Unreleased]` CHANGELOG headers because
128
128
  this class of check ran only after tagging).
129
129
 
130
+ **4-resume. Resuming a cut that died mid-Step-4** (v0.8.10 hit this live):
131
+ `release-pack.mjs` always re-runs the FULL sequence from Step 1, and
132
+ `promote-unreleased.mjs` hard-errors on already-promoted packages ("already
133
+ has ## [X.Y.Z]") — so once ANY promotion has landed, re-invoking the
134
+ orchestrator can never get past Step 4. That error is the idempotency guard
135
+ working, not a corrupted state. Do NOT hand-edit CHANGELOGs back to
136
+ `[Unreleased]` to appease it. Instead, verify the promotion state with the
137
+ authoritative gate — `node scripts/release/check-release-docs.mjs --version
138
+ X.Y.Z` covers every lockstep package (including the nested `a2ui/*` and
139
+ `plugins/*` paths a shallow `packages/*` glob misses) and fails on any
140
+ leftover `[Unreleased]` content or missing `[X.Y.Z]` heading; it will still
141
+ flag the not-yet-generated `docs/releases/vX.Y.Z.md`, which 4e below creates.
142
+ Then run the remaining steps as the standalone pieces the Mechanization
143
+ section already names, in this order:
144
+
145
+ ```bash
146
+ node "${CLAUDE_PLUGIN_ROOT}/skills/adia-release/scripts/bump.mjs" --from X.Y.Z-1 --to X.Y.Z # 4b
147
+ npm install --package-lock-only --no-audit --no-fund # 4c
148
+ npm run check:lockstep # 4d
149
+ node scripts/release/check-release.mjs --pending-version X.Y.Z --fix # 4f
150
+ node scripts/release/generate-release-notes.mjs --version X.Y.Z --write # 4e
151
+ node scripts/release/check-release-docs.mjs --version X.Y.Z # 4e gate
152
+ node scripts/release/check-cut-hygiene.mjs --version X.Y.Z # 4g — post-bump proof
153
+ # then Step 5 by hand (branch, stage, commit) — Step 5.5's freshness
154
+ # trip-wire runs on the staged set exactly as on a normal cut, don't skip it —
155
+ # and Step 5.7 via pr-bridge.mjs
156
+ ```
157
+
130
158
  ## §Step 5 — Stage and commit (on a release branch)
131
159
 
132
160
  The release commit lands via PR, never a direct push to `main` (repo
@@ -143,11 +143,29 @@ function parseArgs(argv) {
143
143
  console.error(`error: --mode must be cut|from-scratch|handoff (got: ${args.mode})`);
144
144
  process.exit(2);
145
145
  }
146
+ // Handoff needs the GH-notes body at Step 10 — validate at PARSE time, not
147
+ // there: on the v0.8.10 cut the missing flag surfaced only after tags and
148
+ // npm publish were already irreversible (Step 10 is the LAST step). An arg
149
+ // error must never cost half a shipped release.
150
+ if (args.mode === 'handoff') {
151
+ if (!args.ghNotesFile) {
152
+ console.error('error: --mode handoff requires --gh-notes-file (Step 10 creates the 11 GH');
153
+ console.error(' releases from it; tags/publish would land first, then the run would');
154
+ console.error(' die on the missing flag — v0.8.10 hit exactly this).');
155
+ process.exit(2);
156
+ }
157
+ if (!args.dry && !(fs.existsSync(args.ghNotesFile) && fs.statSync(args.ghNotesFile).isFile())) {
158
+ console.error(`error: GH notes file not found or not a regular file: ${args.ghNotesFile}`);
159
+ process.exit(2);
160
+ }
161
+ }
146
162
  return args;
147
163
  }
148
164
 
149
165
  function help() {
150
166
  console.log(`Usage:
167
+ node release-pack.mjs --mode handoff --version X.Y.Z --date YYYY-MM-DD \\
168
+ --previous-version X.Y.Z-1 --gh-notes-file /tmp/release-v$X.Y.Z.md # REQUIRED for handoff
151
169
  node release-pack.mjs --mode cut --version X.Y.Z --date YYYY-MM-DD \\
152
170
  --previous-version X.Y.Z-1 \\
153
171
  --commit-message-file /tmp/v$X.Y.Z-commit.txt \\
@@ -734,6 +752,21 @@ function selftest() {
734
752
  process.exit(1);
735
753
  }
736
754
 
755
+ // Handoff without --gh-notes-file must die at PARSE time (v0.8.10: the old
756
+ // Step-10-only check fired after tags+publish were irreversible).
757
+ let handoffFailed = false;
758
+ let handoffOut = '';
759
+ try {
760
+ execSync(`node "${scriptPath}" --mode handoff --version 9.9.9 --date 2026-01-01 --previous-version 9.9.8 --dry`, { cwd: REPO, encoding: 'utf8' });
761
+ } catch (e) {
762
+ handoffFailed = true;
763
+ handoffOut = (e.stdout || '') + (e.stderr || '');
764
+ }
765
+ if (!handoffFailed || !handoffOut.includes('--gh-notes-file')) {
766
+ console.error('selftest FAIL: --mode handoff without --gh-notes-file must hard-reject at parse time');
767
+ process.exit(1);
768
+ }
769
+
737
770
  console.log('selftest OK');
738
771
  } finally {
739
772
  fs.rmSync(tmp, { recursive: true, force: true });