@adia-ai/adia-ui-forge 0.1.2 → 0.1.3

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 (85) hide show
  1. package/.claude-plugin/plugin.json +2 -2
  2. package/CHANGELOG.md +8 -0
  3. package/agents/README.md +209 -0
  4. package/agents/a2ui-engineer.md +57 -0
  5. package/agents/author.md +56 -0
  6. package/agents/release-engineer.md +85 -0
  7. package/agents/repo-steward.md +56 -0
  8. package/agents/routing-corpus.json +89 -0
  9. package/agents/spec-architect.md +53 -0
  10. package/agents/tech-lead.md +57 -0
  11. package/agents/verifier.md +55 -0
  12. package/package.json +3 -2
  13. package/skills/adia-ui-authoring/SKILL.md +2 -2
  14. package/skills/adia-ui-authoring/assets/case-studies/theme-panel-promotion.md +1 -1
  15. package/skills/adia-ui-authoring/references/module-promotion.md +2 -2
  16. package/skills/adia-ui-gen-review/skill.json +1 -0
  17. package/skills/adia-ui-ops/CHANGELOG.md +291 -0
  18. package/skills/adia-ui-ops/SKILL.md +401 -0
  19. package/skills/adia-ui-ops/references/INDEX.md +158 -0
  20. package/skills/adia-ui-ops/references/audit-cadence.md +263 -0
  21. package/skills/adia-ui-ops/references/audit-patterns/archive-link-sweep.md +96 -0
  22. package/skills/adia-ui-ops/references/audit-patterns/audit-history-ledger.md +162 -0
  23. package/skills/adia-ui-ops/references/audit-patterns/browser-bundle-node-imports.md +154 -0
  24. package/skills/adia-ui-ops/references/audit-patterns/changelog-unreleased-bloat.md +75 -0
  25. package/skills/adia-ui-ops/references/audit-patterns/coverage-gaps.md +187 -0
  26. package/skills/adia-ui-ops/references/audit-patterns/entry-file-coverage.md +122 -0
  27. package/skills/adia-ui-ops/references/audit-patterns/format-hygiene.md +217 -0
  28. package/skills/adia-ui-ops/references/audit-patterns/lockstep-versioning.md +113 -0
  29. package/skills/adia-ui-ops/references/audit-patterns/memory-fragmentation.md +180 -0
  30. package/skills/adia-ui-ops/references/audit-patterns/orphan-detection.md +202 -0
  31. package/skills/adia-ui-ops/references/audit-patterns/pointer-validation.md +180 -0
  32. package/skills/adia-ui-ops/references/audit-patterns/redundancy-detection.md +210 -0
  33. package/skills/adia-ui-ops/references/audit-patterns/spec-dating-sweep.md +91 -0
  34. package/skills/adia-ui-ops/references/audit-patterns/stale-content.md +182 -0
  35. package/skills/adia-ui-ops/references/audit-patterns/staleness-tooling.md +156 -0
  36. package/skills/adia-ui-ops/references/audit-patterns/token-waste-detection.md +196 -0
  37. package/skills/adia-ui-ops/references/deploy-playbooks.md +259 -0
  38. package/skills/adia-ui-ops/references/doc-types/adr-pattern.md +210 -0
  39. package/skills/adia-ui-ops/references/doc-types/architecture-md.md +190 -0
  40. package/skills/adia-ui-ops/references/doc-types/changelog.md +183 -0
  41. package/skills/adia-ui-ops/references/doc-types/decisions-log.md +146 -0
  42. package/skills/adia-ui-ops/references/doc-types/plan-roadmap.md +182 -0
  43. package/skills/adia-ui-ops/references/doc-types/postmortem-pattern.md +206 -0
  44. package/skills/adia-ui-ops/references/genres/prose-and-writing.md +149 -0
  45. package/skills/adia-ui-ops/references/guidance/context-budget.md +137 -0
  46. package/skills/adia-ui-ops/references/guidance/llm-doc-writing.md +116 -0
  47. package/skills/adia-ui-ops/references/guidance/reliability-dial.md +186 -0
  48. package/skills/adia-ui-ops/references/recipes/adr-introduction.md +211 -0
  49. package/skills/adia-ui-ops/references/recipes/audit-existing-repo.md +234 -0
  50. package/skills/adia-ui-ops/references/recipes/cold-start-harvest.md +263 -0
  51. package/skills/adia-ui-ops/references/recipes/concurrent-learnings-merge.md +158 -0
  52. package/skills/adia-ui-ops/references/recipes/continuous-learning-loop.md +169 -0
  53. package/skills/adia-ui-ops/references/recipes/external-reference-verification.md +158 -0
  54. package/skills/adia-ui-ops/references/recipes/findings-index-readout.md +126 -0
  55. package/skills/adia-ui-ops/references/recipes/greenfield-setup.md +252 -0
  56. package/skills/adia-ui-ops/references/recipes/harvest-repo-brain.md +169 -0
  57. package/skills/adia-ui-ops/references/recipes/import-repo-brain-harvest.md +153 -0
  58. package/skills/adia-ui-ops/references/recipes/memory-organization.md +182 -0
  59. package/skills/adia-ui-ops/references/recipes/recommend-then-validate.md +199 -0
  60. package/skills/adia-ui-ops/references/recipes/self-healing-hooks.md +366 -0
  61. package/skills/adia-ui-ops/references/recipes/skill-stewardship-loop.md +113 -0
  62. package/skills/adia-ui-ops/references/standards/agents-md-spec.md +138 -0
  63. package/skills/adia-ui-ops/references/standards/claude-md-convention.md +123 -0
  64. package/skills/adia-ui-ops/references/standards/cross-tool-matrix.md +85 -0
  65. package/skills/adia-ui-ops/references/standards/readme-conventions.md +232 -0
  66. package/skills/adia-ui-ops/references/teach-protocol.md +215 -0
  67. package/skills/adia-ui-ops/scripts/audit-ops-roster.mjs +104 -0
  68. package/skills/adia-ui-ops/skill.json +65 -0
  69. package/skills/adia-ui-release/CHANGELOG.md +43 -0
  70. package/skills/adia-ui-release/SKILL.md +34 -6
  71. package/skills/adia-ui-release/references/independent-package-release.md +129 -0
  72. package/skills/adia-ui-release/references/recovery-paths.md +49 -2
  73. package/skills/adia-ui-release/scripts/dispatch-publish.mjs +68 -5
  74. package/skills/adia-ui-release/scripts/insert-stub.mjs +5 -3
  75. package/skills/adia-ui-release/scripts/make-ledger.mjs +22 -1
  76. package/skills/adia-ui-release/scripts/package-paths.mjs +44 -0
  77. package/skills/adia-ui-release/scripts/promote-unreleased.mjs +3 -2
  78. package/skills/adia-ui-release/scripts/release-pack.mjs +36 -7
  79. package/skills/adia-ui-release/scripts/tag-lockstep.mjs +10 -2
  80. package/skills/adia-ui-release/skill.json +9 -7
  81. package/skills/dogfood-sweep/CHANGELOG.md +37 -0
  82. package/skills/dogfood-sweep/README.md +105 -0
  83. package/skills/dogfood-sweep/SKILL.md +1000 -0
  84. package/skills/dogfood-sweep/analyze.mjs +600 -0
  85. package/skills/dogfood-sweep/skill.json +31 -0
@@ -0,0 +1,129 @@
1
+ # `independent-package-release.md` — class-B cut (an independently-versioned package outside the lockstep)
2
+
3
+ > Loaded by **mode 12 (Independent-package release)**. Load when releasing a package
4
+ > that versions **independently** of the lockstep set — it is excluded from the
5
+ > lockstep-coherence gate, carries no umbrella tag, and triggers no demo-site deploy.
6
+ > For the lockstep cut (the primary path), use `cycle-happy-path.md` (modes 1–4).
7
+
8
+ > **Worked example — the @adia-ai monorepo's `packages/plugins/*`.** The concrete
9
+ > package names, gate names, and workflow files below are **the worked example** of
10
+ > this class, drawn from the @adia-ai monorepo's two Claude Code plugins —
11
+ > `@adia-ai/adia-ui-factory` (versioned `0.2.x`) and `@adia-ai/adia-ui-forge`
12
+ > (versioned `0.1.x`) — which live under `packages/plugins/*`, version
13
+ > independently, and are EXCLUDED from the 9-package lockstep. A _different_
14
+ > @adia-ai-style monorepo with its own independently-versioned package keeps this
15
+ > skeleton and substitutes its own package path, exclusion comment, and publish
16
+ > workflow. `$REPO` below = the monorepo root.
17
+
18
+ ## §Why a separate class — two release classes in one monorepo
19
+
20
+ A lockstep monorepo can host packages that do **not** move with the lockstep set:
21
+ plugins, tools, or any package whose release cadence is deliberately decoupled.
22
+ These are a **class-B independent-package release**, distinct from the **class-A
23
+ lockstep cut** the rest of this skill describes.
24
+
25
+ | | Class A — lockstep cut (modes 1–4) | Class B — independent-package release (mode 12) |
26
+ | --- | --- | --- |
27
+ | **Versioning** | The whole set bumps together | Each package on its own version line |
28
+ | **Lockstep gate** | Enforced (`check:lockstep` 9/9) | **Excluded** — the gate skips the package by design |
29
+ | **Umbrella tag** | Yes (`vX.Y.Z` + per-package) | **None** — per-package tags only (versions differ) |
30
+ | **`dist-tag` ordering** | Cross-package publish ordering matters | N/A (independent versions; no cross-package `latest` race) |
31
+ | **Demo-site deploy** | Yes (the worked example's `ui-kit.exe.xyz`) | **None** |
32
+ | **Publish trigger** | Tag-triggered per-package workflow | Tag-triggered per-package workflow (same mechanism) |
33
+
34
+ `SKILL.md §ReleaseInvariants` govern the **class-A lockstep cut**. Class B's
35
+ invariants are the §Posture operator-confirmation checkpoints plus the onboarding
36
+ trip-wires below — and the same verify-against-reality discipline (verify the npm
37
+ registry, not the workflow's reported "success").
38
+
39
+ > **Worked example.** In the @adia-ai monorepo, `scripts/release/check-lockstep.mjs`
40
+ > skips `packages/plugins/*` by design (there's a comment there saying so), and the
41
+ > plugins publish via their own tag-triggered workflows
42
+ > (`.github/workflows/publish-adia-ui-{factory,forge}.yml`) — exactly like the
43
+ > framework packages but on independent versions. No umbrella tag, no `dist-tag`
44
+ > cross-package ordering, no EXE deploy.
45
+
46
+ ## §New-package onboarding — the three trip-wires a FIRST independent cut hits
47
+
48
+ A package that is **new to the release tooling** fails three gates the established
49
+ set long since passed. These are **substrate** fixes (release scripts / lockfile /
50
+ CHANGELOG), not skill edits — per the §Teach decision tree, branch A (substrate).
51
+ The skill only *cites* them here. Onboard the package **once**; the fixes land as a
52
+ normal substrate PR on `main` *before* tagging.
53
+
54
+ 1. **Lockfile.** A new workspace package isn't in the lockfile → the pre-commit
55
+ lockfile-check hook blocks the commit, and the publish workflow's clean install
56
+ (`npm ci`) fails the same way.
57
+ *Fix:* regenerate the lockfile only and stage it —
58
+ `npm install --package-lock-only && git add package-lock.json`.
59
+ 2. **Release-trip-wire package registry.** The release trip-wire (F-N1,
60
+ `check:release`) validates each pushed tag against a registry of known packages.
61
+ An unregistered tag fails the **pre-push** hook with an `unknown package` error.
62
+ *Fix:* register the package in that list — its tag-prefix + path.
63
+ *(Worked example: `scripts/release/check-release.mjs` has a hardcoded `PACKAGES`
64
+ array — the framework 9; add
65
+ `{ tagPrefix: '<name>', path: 'packages/plugins/<name>' }`.)*
66
+ 3. **CHANGELOG bracket form.** The trip-wire's entry check requires the
67
+ Keep-a-Changelog `## [<version>]` (brackets) heading. A bare `## <version>`
68
+ heading fails with a "no `## [<version>]` entry" error.
69
+ *Fix:* bracket the version headings.
70
+
71
+ ## §Procedure
72
+
73
+ > The gate/workflow names below are the @adia-ai worked example; substitute your
74
+ > monorepo's own pre-publish gate and publish workflow.
75
+
76
+ 0. **Re-baseline** (the multi-agent baseline assumption). `git fetch`; know HEAD +
77
+ whether the shared working tree is on a peer's branch.
78
+ 1. **Land the package(s) on `main`** via PR — the usual PR-merge gate: resolve
79
+ **every** review thread (e.g. CodeRabbit) before an admin-merge. A PR-merge gate
80
+ that requires resolved review threads before `gh pr merge --admin` is a hard
81
+ precondition — an admin merge over unresolved threads is the failure this guards
82
+ against. *(See `SKILL.md §Posture` / your monorepo's merge convention.)*
83
+ 2. **Pre-publish gate.** Run the per-package version-coherence check (the worked
84
+ example: `npm run verify:plugins` → N/N synced — `package.json` ↔ the plugin
85
+ manifest versions). The lockstep gate still passes for the lockstep set (the
86
+ independent package is excluded). Confirm the names are free on npm:
87
+ `npm view @adia-ai/<pkg> version` → `E404` means available.
88
+ 3. **Tag at the published commit** — `git tag <pkg>-vX.Y.Z` per package (versions
89
+ differ; **no umbrella tag**). The tag must point at a commit whose `package.json`
90
+ + bracketed CHANGELOG match: dry-run the release trip-wire over the tag list
91
+ (worked example: `node scripts/release/check-release.mjs <tags>` → clean) before
92
+ pushing. **If the shared working tree is on a peer's branch, push the tags from a
93
+ throwaway worktree on `origin/main`** — never carry a peer's branch state into a
94
+ tag push.
95
+ 4. **Push the tags** → the per-package publish workflows run the clean-install →
96
+ pre-publish-gate → publish sequence (worked example:
97
+ `npm ci → verify:plugins → npm publish --access public` with the repo's npm token).
98
+ 5. **Verify (against npm, not the workflow).** `npm view @adia-ai/<pkg> version`
99
+ returns the new version for each package. Watch each workflow to completion
100
+ (`gh run watch <id> --exit-status`) — but the registry, not the workflow's green
101
+ check, is the source of truth.
102
+ 6. **Marketplace cut-over** (if a plugin marketplace repo exists). Point the
103
+ marketplace manifest's per-plugin `source` at npm —
104
+ `{ "source": "npm", "package": "@adia-ai/<pkg>" }` — and `git rm -r` any vendored
105
+ copies (the canonical home is the monorepo). Validate the JSON, commit, push.
106
+ Consumers then add the marketplace + install the plugin from npm (e.g.
107
+ `/plugin marketplace add <org>/<repo>` → `/plugin install <pkg>@<repo>`).
108
+ 7. **Audit-history ledger** — same as class A; record the **per-package** versions
109
+ (independent — they differ per package).
110
+
111
+ ## §Verify Target
112
+
113
+ The published package(s) on the **npm registry** at their independent versions, plus
114
+ (if a marketplace exists) it references npm. **NOT** "the whole lockstep set + the
115
+ demo site" — that's class A. Verify against the registry, never the workflow's
116
+ reported "success."
117
+
118
+ ## §Gotchas — from the first independent cut (worked example: 2026-06-07, factory v0.2.2 + forge v0.1.2)
119
+
120
+ - The independent package is **NOT** lockstep — never add it to the lockstep gate,
121
+ and never tag it with the lockstep umbrella tag.
122
+ - Publish-workflow YAMLs are **token-bearing** — keep `persist-credentials: false`
123
+ on the checkout step (match the established publish workflows' action-pinning
124
+ convention).
125
+ - A marketplace repo's **org + visibility is an operator call** — creating a public
126
+ repo is outward-facing; confirm before `gh repo create`. The repo home may differ
127
+ from the primary org (the marketplace manifest's `owner` field is the signal; a
128
+ member without org repo-create rights falls back to a personal repo).
129
+ - Run a **secret-scan of the full history** before pushing any repo public.
@@ -1,4 +1,4 @@
1
- # `recovery-paths.md` — the 5 recovery scenarios
1
+ # `recovery-paths.md` — the 7 recovery scenarios
2
2
 
3
3
  > Loaded by mode 2 (Author from scratch) when `[Unreleased]` promotion requires extension beyond the peer's early release commit; by mode 4 (Batch push); on F-N1 or pre-flight failure in mode 1; and by mode 7 (Post-release recovery — Phase 3 expansion).
4
4
 
@@ -177,6 +177,48 @@ For option 2:
177
177
 
178
178
  ---
179
179
 
180
+ ## §Scenario 7 — Tags pushed but ZERO publish workflows triggered (the batch-tag-push skip)
181
+
182
+ **Shape:** The cut commit is pushed, all 10 tags (umbrella + 9 per-package) exist on `origin` — but **no `publish-<pkg>.yml` workflow ran**. `gh run list --workflow=publish-<pkg>.yml` shows no run for the new tag. npm `latest` is still the previous version. The packages are NOT on the registry at the new version. Nothing errored; the publishes simply never started.
183
+
184
+ **Root cause:** GitHub's `on: push: tags:` trigger fires a single `create` event for the whole ref-set when you push many tags in **one** `git push` invocation (`git push origin <tag1> <tag2> … <tag10>` or `git push origin --tags`). GitHub Actions routinely **drops** that batched create event — so ZERO publish-on-tag workflows trigger. This is not a flake you can retry by re-pushing (the tags already exist on the remote; a no-op push fires nothing). Memory: `feedback_publish_workflow_batch_push`.
185
+
186
+ **Canonical case:** the v0.7.14 cut. All 10 tags pushed in one `git push origin <10 tags>`; zero `publish-*` runs fired. Recovered by manually `gh workflow run publish-<pkg>.yml` (workflow_dispatch) for each package.
187
+
188
+ **Resolution (recover):**
189
+
190
+ 1. Confirm the symptom — for each package: `gh run list --workflow=publish-<pkg>.yml --branch <pkg>-vX.Y.Z --limit 1`. Empty for all (or most) = the batch skip.
191
+ 2. **Re-dispatch the misses via `workflow_dispatch`** (the tags are valid refs; the workflow just never ran for them). The bundled helper does exactly this, idempotently:
192
+ ```bash
193
+ node "${CLAUDE_PLUGIN_ROOT}/skills/adia-ui-release/scripts/dispatch-publish.mjs" \
194
+ --version X.Y.Z --verify-triggered
195
+ ```
196
+ `--verify-triggered` checks each `publish-<pkg>.yml` for an existing run on the `<pkg>-vX.Y.Z` tag and re-dispatches ONLY the packages with no run (so it's safe to run after a *partial* trigger too — it won't double-publish the ones that fired). `--dry` previews without calling `gh`.
197
+ 3. For a **batch push** (Scenario 2), preserve npm-latest ordering: re-dispatch + settle the OLDER version's 9 before the newer version's 9. `--after X.Y.Z-1` gates on `npm dist-tag latest` to enforce it.
198
+ 4. Wait for settle + verify against the **registry** (not the workflow's "success"): `npm view <scope>/<pkg> version` for all 9 + `npm view <scope>/web-components dist-tags.latest`.
199
+
200
+ **Resolution (prevent — next cut):** push per-package tags **one-at-a-time** so each gets its own create event:
201
+ ```bash
202
+ git push origin main
203
+ for t in web-components-vX.Y.Z web-modules-vX.Y.Z … a2ui-validator-vX.Y.Z; do
204
+ git push origin "$t"
205
+ done
206
+ git push origin vX.Y.Z # umbrella last — triggers nothing
207
+ ```
208
+ `release-pack.mjs` Step 8 now does this automatically, and Step 9 runs `--verify-triggered` to fill any gap; the `tag-lockstep.mjs` push hint prints the per-tag loop + the verify-triggered command. The one-at-a-time push is the primary fix; `--verify-triggered` is the belt-and-suspenders safety net.
209
+
210
+ **What to write in the ledger:**
211
+
212
+ ```json
213
+ "publish_workflows": {
214
+ "trigger_anomaly": "Batched tag push (git push origin <10 tags>) triggered ZERO publish-on-tag workflows (the GitHub batch-push create-event skip). Recovered by dispatch-publish.mjs --verify-triggered → re-dispatched N/9 via workflow_dispatch.",
215
+ "dispatched": "9/9 (workflow_dispatch)",
216
+ "conclusions": "9/9 success — verified on npm registry, not workflow self-report"
217
+ }
218
+ ```
219
+
220
+ ---
221
+
180
222
  ## §Decision flowchart — which scenario applies?
181
223
 
182
224
  ```text
@@ -200,6 +242,11 @@ Multiple unpushed release commits exist?
200
242
  Peer commit's CHANGELOG references a different version than its bump?
201
243
  └── Scenario 1 (version-skip correction)
202
244
 
245
+ Tags pushed (all 10 exist on origin) but NO publish-<pkg>.yml ran +
246
+ npm latest unchanged?
247
+ └── Scenario 7 (batch-tag-push skip) — re-dispatch misses via
248
+ dispatch-publish.mjs --verify-triggered; next cut push tags one-at-a-time
249
+
203
250
  Working tree has uncommitted source files when you start the cycle?
204
251
  └── Apply multi-agent-baseline.md § classification taxonomy.
205
252
  Stash strays per § Discipline 4 if they're release-relevant +
@@ -210,6 +257,6 @@ Working tree has uncommitted source files when you start the cycle?
210
257
 
211
258
  ## §When this reference is "done v1"
212
259
 
213
- - Each of the 5 (now 6) scenarios has a documented case study in `assets/case-studies/` (Phase 3).
260
+ - Each of the 5 (now 7) scenarios has a documented case study in `assets/case-studies/` (Phase 3).
214
261
  - The decision flowchart correctly routes the next 5 release cycles with no operator override.
215
262
  - Each scenario's ledger-template fragment is reused verbatim by the `make-ledger.mjs` helper (Phase 3) for that scenario.
@@ -41,6 +41,7 @@ function parseArgs(argv) {
41
41
  const args = {
42
42
  version: null, after: null, dry: false, sleepBefore: 4,
43
43
  scope: process.env.ADIA_NPM_SCOPE || DEFAULT_SCOPE,
44
+ verifyTriggered: false,
44
45
  };
45
46
  for (let i = 0; i < argv.length; i++) {
46
47
  const k = argv[i];
@@ -49,13 +50,19 @@ function parseArgs(argv) {
49
50
  else if (k === '--dry') args.dry = true;
50
51
  else if (k === '--sleep-before') args.sleepBefore = parseInt(argv[++i], 10);
51
52
  else if (k === '--scope') args.scope = argv[++i];
53
+ else if (k === '--verify-triggered') args.verifyTriggered = true;
52
54
  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]');
55
+ console.log('Usage: node dispatch-publish.mjs --version X.Y.Z [--after X.Y.Z-1] [--verify-triggered] [--dry] [--sleep-before <seconds>] [--scope @org]');
54
56
  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.');
57
+ console.log('--after Verify npm latest is at the given version before dispatching this one.');
58
+ console.log(' Used in batch push to ensure publish ordering.');
59
+ console.log('--verify-triggered Instead of dispatching, check whether each per-package publish-<pkg>.yml');
60
+ console.log(' ALREADY has a run for the <pkg>-vX.Y.Z tag (i.e. push-on-tag fired), and');
61
+ console.log(' re-dispatch ONLY the misses. This is the recovery for the batch-tag-push');
62
+ console.log(' skip (recovery-paths.md §Scenario 7) — a single `git push <10 tags>` can');
63
+ console.log(' trigger ZERO publish workflows. Idempotent: re-dispatches nothing if all 9 fired.');
64
+ console.log(`--scope npm scope the packages publish under (default: ${DEFAULT_SCOPE};`);
65
+ console.log(' or set $ADIA_NPM_SCOPE). The --after npm-latest check uses this scope.');
59
66
  process.exit(0);
60
67
  }
61
68
  }
@@ -115,12 +122,68 @@ async function sleep(ms) {
115
122
  return new Promise((res) => setTimeout(res, ms));
116
123
  }
117
124
 
125
+ // Has publish-<pkg>.yml already run for the <pkg>-vX.Y.Z tag? Tag-triggered runs
126
+ // report the tag in the `headBranch` field, so we filter the run list by it.
127
+ // Returns true if at least one run exists for that tag, false otherwise.
128
+ function triggeredForTag(pkg, version, dry) {
129
+ const tag = `${pkg}-v${version}`;
130
+ const cmd = `gh run list --workflow="publish-${pkg}.yml" --branch "${tag}" --limit 1 --json databaseId -q '.[0].databaseId // empty'`;
131
+ if (dry) {
132
+ console.log(` [dry] ${cmd}`);
133
+ return false; // dry preview assumes not-triggered so the re-dispatch path is shown
134
+ }
135
+ try {
136
+ const out = execSync(cmd, { encoding: 'utf8' }).trim();
137
+ return out.length > 0;
138
+ } catch {
139
+ // gh error (auth / rate limit / missing workflow) — treat as "unknown".
140
+ // Conservative: report not-triggered so the miss is re-dispatched rather than
141
+ // silently skipped. A redundant re-dispatch is harmless (publish is idempotent
142
+ // per the workflow's own guard); a skipped publish is the failure we're fixing.
143
+ return false;
144
+ }
145
+ }
146
+
147
+ // The batch-tag-push-skip recovery: verify each per-package publish workflow
148
+ // actually triggered off its pushed tag, and re-dispatch ONLY the misses.
149
+ // Idempotent — re-dispatches nothing if all 9 fired.
150
+ function verifyTriggeredAndRedispatch(version, dry) {
151
+ console.log(`\nVerifying publish-on-tag triggered for all ${PACKAGES.length} packages (v${version}):`);
152
+ const misses = [];
153
+ for (const pkg of PACKAGES) {
154
+ if (triggeredForTag(pkg, version, dry)) {
155
+ console.log(` ✓ ${pkg} — publish run found for ${pkg}-v${version}`);
156
+ } else {
157
+ console.log(` ✗ ${pkg} — NO publish run for ${pkg}-v${version} (batch-push skip) → will re-dispatch`);
158
+ misses.push(pkg);
159
+ }
160
+ }
161
+ if (misses.length === 0) {
162
+ console.log(`\n[verify-triggered] all ${PACKAGES.length} publish workflows triggered — nothing to re-dispatch.`);
163
+ return 0;
164
+ }
165
+ console.log(`\n[verify-triggered] ${misses.length} miss(es) — re-dispatching via workflow_dispatch:`);
166
+ let redispatched = 0;
167
+ for (const pkg of misses) {
168
+ if (dispatch(pkg, version, dry)) redispatched++;
169
+ }
170
+ console.log(`\n[verify-triggered] re-dispatched ${redispatched}/${misses.length} ${dry ? '(dry)' : ''}`);
171
+ return redispatched === misses.length ? 0 : 1;
172
+ }
173
+
118
174
  async function main() {
119
175
  const args = parseArgs(process.argv.slice(2));
120
176
  // Fail-fast guard: refuse to run npm/gh against a non-monorepo directory.
121
177
  assertMonorepoRoot(process.cwd());
122
178
  if (args.after) checkAfter(args.after, args.scope);
123
179
 
180
+ // Recovery mode — verify each publish workflow triggered off its pushed tag,
181
+ // re-dispatch only the misses (the batch-tag-push skip). Skips the blind dispatch.
182
+ if (args.verifyTriggered) {
183
+ const rc = verifyTriggeredAndRedispatch(args.version, args.dry);
184
+ process.exit(rc);
185
+ }
186
+
124
187
  if (args.sleepBefore > 0 && !args.dry) {
125
188
  console.log(`Sleeping ${args.sleepBefore}s to let GH index the new tags...`);
126
189
  await sleep(args.sleepBefore * 1000);
@@ -16,8 +16,8 @@
16
16
  // cycles.
17
17
 
18
18
  import fs from 'node:fs';
19
- import path from 'node:path';
20
19
  import process from 'node:process';
20
+ import { resolvePackageChangelog } from './package-paths.mjs';
21
21
 
22
22
  function parseArgs(argv) {
23
23
  const args = {
@@ -72,7 +72,8 @@ function buildStub(version, date, substantive, xref) {
72
72
  }
73
73
 
74
74
  function insertStub(repo, pkg, anchor, stubBlock, version, dry) {
75
- const p = path.join(repo, 'packages', pkg, 'CHANGELOG.md');
75
+ // Accept both name-form (a2ui-corpus) and path-form (a2ui/corpus). See package-paths.mjs.
76
+ const p = resolvePackageChangelog(repo, pkg);
76
77
  if (!fs.existsSync(p)) {
77
78
  console.error(` ERROR ${pkg} — CHANGELOG.md not found at ${p}`);
78
79
  return 'missing';
@@ -101,7 +102,8 @@ function main() {
101
102
  const PREV_HEADING_RE = new RegExp(`^## \\[${args.previous.replace(/\./g, '\\.')}\\] — \\d{4}-\\d{2}-\\d{2}$`, 'm');
102
103
  const stubBlock = buildStub(args.version, args.date, args.substantive, args.xref);
103
104
  const results = args.packages.map((pkg) => {
104
- const p = path.join(args.repo, 'packages', pkg, 'CHANGELOG.md');
105
+ // Accept both name-form (a2ui-corpus) and path-form (a2ui/corpus). See package-paths.mjs.
106
+ const p = resolvePackageChangelog(args.repo, pkg);
105
107
  if (!fs.existsSync(p)) {
106
108
  console.error(` ERROR ${pkg} — not found`);
107
109
  return 'missing';
@@ -84,6 +84,16 @@ function parseArgs(argv) {
84
84
  args.tagCommit = execSync(`git -C "${args.repo}" rev-parse HEAD`).toString().trim();
85
85
  }
86
86
  if (!args.releaseCommit) args.releaseCommit = args.tagCommit;
87
+ // Auto-fill repoSlug from the git remote when not explicitly passed — avoids the
88
+ // '<org>/<repo>' placeholder shipping in the ledger (CodeRabbit-flagged on the
89
+ // v0.7.17 cut). Falls back to the placeholder if there's no origin remote.
90
+ if (args.repoSlug === '<org>/<repo>') {
91
+ try {
92
+ const url = execSync(`git -C "${args.repo}" remote get-url origin`).toString().trim();
93
+ const m = url.match(/[:/]([^/]+\/[^/]+?)(?:\.git)?\/?$/);
94
+ if (m) args.repoSlug = m[1];
95
+ } catch { /* no remote — leave the placeholder */ }
96
+ }
87
97
  return args;
88
98
  }
89
99
 
@@ -130,7 +140,7 @@ function buildLedger(args) {
130
140
  note: 'TODO: paste gate-by-gate summary here. See ledger-discipline.md § canonical schema.',
131
141
  };
132
142
  ledger.publish_workflows = {
133
- dispatched: '9/9 via gh workflow run --ref <pkg>-vX.Y.Z',
143
+ dispatched: 'TODO: dispatch method (per-package tag-push, one-at-a-time / gh workflow run)',
134
144
  conclusions: '9/9 success',
135
145
  };
136
146
  ledger.notes = args.notes.length > 0 ? args.notes : ['TODO: at least one note per cycle.'];
@@ -157,6 +167,17 @@ function writeOrPrint(ledger, args) {
157
167
  }
158
168
  fs.writeFileSync(target, content);
159
169
  console.log(`[make-ledger] wrote ${target}`);
170
+ // Nudge: the gate summary + notes can't be auto-derived — warn if TODO stubs
171
+ // would ship (the v0.7.17 ledger shipped with these; CodeRabbit-flagged).
172
+ const stubs = [];
173
+ if (/TODO/.test(ledger.verification?.note || '')) stubs.push('verification');
174
+ if (/TODO/.test(ledger.publish_workflows?.dispatched || '')) stubs.push('publish_workflows.dispatched');
175
+ if ((ledger.notes || []).some((n) => /TODO/.test(n))) stubs.push('notes');
176
+ if (ledger.repo === '<org>/<repo>') stubs.push('repo');
177
+ if (stubs.length) {
178
+ console.warn(`\n⚠ [make-ledger] ${stubs.length} field(s) still placeholder — FILL before committing: ${stubs.join(', ')}`);
179
+ console.warn(` (repo auto-fills from the git remote; pass --note "..." + hand-edit verification / publish_workflows)`);
180
+ }
160
181
  console.log(`\n[next] verify the ledger:`);
161
182
  console.log(` cat ${target} | jq .`);
162
183
  console.log(`\n[next] commit + push:`);
@@ -0,0 +1,44 @@
1
+ // package-paths.mjs — package name-vs-path-form normalization for the
2
+ // standalone CHANGELOG helpers.
3
+ //
4
+ // THE PROBLEM (bit all 6 a2ui packages at the v0.7.14 cut):
5
+ // The 6 a2ui packages live on disk under `packages/a2ui/<x>/` but carry the
6
+ // flat npm-name `a2ui-<x>` everywhere else in the tooling (release-pack.mjs,
7
+ // dispatch-publish.mjs, tag-lockstep.mjs all use the NAME form). The two
8
+ // standalone helpers (promote-unreleased.mjs, insert-stub.mjs) joined
9
+ // `packages/<arg>/CHANGELOG.md` with the raw arg, so calling them with the
10
+ // name form silently 404'd: `CHANGELOG.md not found at packages/a2ui-corpus/`.
11
+ // release-pack.mjs maps internally (`a2ui-corpus` → `packages/a2ui/corpus`),
12
+ // so the two forms diverged: name-form failed standalone, path-form failed
13
+ // when handed to the orchestrator-style callers.
14
+ //
15
+ // THE FIX:
16
+ // Accept BOTH forms. Try `packages/<arg>` first (path form, backward-compat).
17
+ // If that directory is absent AND the arg matches `a2ui-<x>`, fall back to
18
+ // `packages/a2ui/<x>`. So `a2ui-corpus` (name) and `a2ui/corpus` (path) both
19
+ // resolve to `packages/a2ui/corpus/`.
20
+ //
21
+ // Pure Node, stdlib only — keeps the scripts free of runtime deps.
22
+
23
+ import fs from 'node:fs';
24
+ import path from 'node:path';
25
+
26
+ // Resolve a package arg to its on-disk directory under packages/, accepting
27
+ // BOTH the name form (`a2ui-corpus`) and the path form (`a2ui/corpus`).
28
+ // Returns the resolved relative directory (e.g. `a2ui/corpus`); does not
29
+ // require the directory to exist (so callers can render a legible not-found
30
+ // error against the resolved path).
31
+ export function resolvePackageDir(repo, pkg) {
32
+ if (fs.existsSync(path.join(repo, 'packages', pkg))) return pkg;
33
+ const m = /^a2ui-(.+)$/.exec(pkg);
34
+ if (m) {
35
+ const nested = path.join('a2ui', m[1]);
36
+ if (fs.existsSync(path.join(repo, 'packages', nested))) return nested;
37
+ }
38
+ return pkg; // fall through with the original arg so the not-found error names what was asked for
39
+ }
40
+
41
+ // Resolve a package arg to its CHANGELOG.md path, accepting both forms.
42
+ export function resolvePackageChangelog(repo, pkg) {
43
+ return path.join(repo, 'packages', resolvePackageDir(repo, pkg), 'CHANGELOG.md');
44
+ }
@@ -20,8 +20,8 @@
20
20
  // 2 — bad args / file I/O error
21
21
 
22
22
  import fs from 'node:fs';
23
- import path from 'node:path';
24
23
  import process from 'node:process';
24
+ import { resolvePackageChangelog } from './package-paths.mjs';
25
25
 
26
26
  function parseArgs(argv) {
27
27
  const args = { version: null, date: null, packages: null, dry: false, repo: process.cwd() };
@@ -52,7 +52,8 @@ function parseArgs(argv) {
52
52
  }
53
53
 
54
54
  function promote(repo, pkg, version, date, dry) {
55
- const p = path.join(repo, 'packages', pkg, 'CHANGELOG.md');
55
+ // Accept both name-form (a2ui-corpus) and path-form (a2ui/corpus). See package-paths.mjs.
56
+ const p = resolvePackageChangelog(repo, pkg);
56
57
  if (!fs.existsSync(p)) {
57
58
  console.error(` ERROR ${pkg} — CHANGELOG.md not found at ${p}`);
58
59
  return 'missing';
@@ -269,21 +269,50 @@ function step7Fn1(args) {
269
269
  }
270
270
 
271
271
  // ── Step 8 — Push ────────────────────────────────────────────────
272
+ // Pushes per-package tags ONE-AT-A-TIME, not as a single batch.
273
+ //
274
+ // THE BATCH-PUSH SKIP (bit the v0.7.14 cut): `git push origin <10 tags>` in one
275
+ // invocation fires the push-on-tag `create` event ONCE for the whole ref-set,
276
+ // and GitHub Actions routinely drops it — ZERO publish-on-tag workflows trigger.
277
+ // (Memory: feedback_publish_workflow_batch_push; recovery-paths.md §Scenario 7.)
278
+ // Pushing each per-package tag separately gives the per-package publish-<pkg>.yml
279
+ // `on: push: tags:` trigger its own create event. The umbrella tag is pushed
280
+ // last (it triggers nothing — convention only).
281
+ //
282
+ // Step 9 dispatches all 9 publishes via workflow_dispatch regardless, so the
283
+ // orchestrated cut does NOT rely on push-on-tag firing. The one-at-a-time push
284
+ // is defensive (a cut aborted after Step 8 still has its publishes triggered)
285
+ // AND keeps the standalone tag-lockstep.mjs hint honest.
272
286
  async function step8Push(args) {
273
- console.log('\n=== Step 8 — Push main + tags ===');
287
+ console.log('\n=== Step 8 — Push main + tags (per-tag, NOT batched) ===');
274
288
  const count = shQuiet('git rev-list --count origin/main..HEAD', args).trim();
275
289
  console.log(` commits to push: ${count}`);
276
- await checkpoint(`Ready to push ${count} commit(s) + 10 tags`, args);
290
+ await checkpoint(`Ready to push ${count} commit(s) + 10 tags (one-at-a-time)`, args);
277
291
  sh('git push origin main', args);
278
- const tagList = [`v${args.version}`, ...PACKAGES.map((p) => `${p}-v${args.version}`)].join(' ');
279
- sh(`git push origin ${tagList}`, args);
292
+ const perPkgTags = PACKAGES.map((p) => `${p}-v${args.version}`);
293
+ for (const t of perPkgTags) {
294
+ sh(`git push origin ${t}`, args);
295
+ }
296
+ // Umbrella tag last — triggers no workflow (convention only).
297
+ sh(`git push origin v${args.version}`, args);
298
+ console.log(`\n ${perPkgTags.length} per-package tags + 1 umbrella pushed individually.`);
299
+ console.log(' NOTE: a single `git push origin <all tags>` can skip ALL publish-on-tag');
300
+ console.log(' triggers (the GitHub batch-push skip). Step 9 dispatches via');
301
+ console.log(' workflow_dispatch + verifies each triggered — see recovery-paths.md §Scenario 7.');
280
302
  }
281
303
 
282
304
  // ── Step 9 — Publish ─────────────────────────────────────────────
305
+ // Step 8 pushed per-package tags one-at-a-time, so push-on-tag may have fired
306
+ // for some/all packages. --verify-triggered checks which publish-<pkg>.yml runs
307
+ // exist for the <pkg>-vX.Y.Z tags and re-dispatches ONLY the misses — closing the
308
+ // batch-tag-push skip without double-publishing the ones that already fired.
309
+ // Idempotent: if all 9 triggered (or none did), it does the right thing.
283
310
  async function step9Publish(args) {
284
- console.log('\n=== Step 9 — Dispatch publishes ===');
285
- await checkpoint('Ready to dispatch 9 npm publishes', args);
286
- sh(`node ${SCRIPT_DIR}/dispatch-publish.mjs --version ${args.version} --scope ${args.scope}`, args);
311
+ console.log('\n=== Step 9 — Verify-triggered + re-dispatch misses ===');
312
+ await checkpoint('Ready to verify the 9 publish triggers + re-dispatch any misses', args);
313
+ // Give GH a moment to index the runs created by the per-tag push in Step 8.
314
+ if (!args.dry) sh('sleep 6', args);
315
+ sh(`node ${SCRIPT_DIR}/dispatch-publish.mjs --version ${args.version} --scope ${args.scope} --verify-triggered`, args);
287
316
  console.log('\n Waiting for publishes to settle...');
288
317
  sh(`until [ "$(gh run list --workflow=publish-a2ui-validator.yml --limit 1 --json status -q '.[0].status')" = "completed" ]; do sleep 5; done`, args);
289
318
  console.log(' ✓ publishes settled');
@@ -94,11 +94,19 @@ function main() {
94
94
  console.log(`\n[tag-lockstep] ${tags.length} tags created at ${targetSha.slice(0, 9)} ${args.dry ? '(dry)' : ''}`);
95
95
 
96
96
  if (!args.dry) {
97
+ const perPkg = tags.filter((t) => t !== `v${args.version}`);
97
98
  console.log(`\n[next] run F-N1:`);
98
99
  console.log(` node scripts/release/check-release.mjs --all-pending`);
99
- console.log(`\n[next] when F-N1 is 9/9 clean, push:`);
100
+ console.log(`\n[next] when F-N1 is 9/9 clean, push main + tags ONE-AT-A-TIME:`);
101
+ console.log(` # A single \`git push origin <all tags>\` can trigger ZERO publish-on-tag`);
102
+ console.log(` # workflows (the GitHub batch-push skip — it bit the v0.7.14 cut).`);
103
+ console.log(` # Push each per-package tag separately so each publish-<pkg>.yml fires.`);
100
104
  console.log(` git push origin main`);
101
- console.log(` git push origin ${tags.join(' \\\n ')}`);
105
+ console.log(` for t in ${perPkg.join(' ')}; do git push origin "$t"; done`);
106
+ console.log(` git push origin v${args.version} # umbrella last (triggers nothing)`);
107
+ console.log(`\n[next] VERIFY each publish actually triggered + re-dispatch any misses:`);
108
+ console.log(` node "$(dirname "$0")/dispatch-publish.mjs" --version ${args.version} --verify-triggered`);
109
+ console.log(` # (recovery-paths.md §Scenario 7 — tags pushed but no publish triggered)`);
102
110
  }
103
111
  }
104
112
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "adia-ui-release",
3
- "version": "0.1.0",
4
- "description": "Release engineer for an @adia-ai-style lockstep monorepo. Cuts, tags, publishes, deploys, authors release notes + audit-history ledgers AND authors the MIGRATION GUIDE when a cut breaks an API (the producer side of migrations, folded in from the former adia-ui-migration skill). Use whenever the user wants to CUT A RELEASE / SHIP A VERSION / PUBLISH PACKAGES / BATCH-PUSH ACCUMULATED RELEASES / RECOVER FROM A VERSION-SKIP / WRITE RELEASE NOTES / PROMOTE [UNRELEASED] CHANGELOG / AUTHOR A MIGRATION GUIDE FOR A BREAKING CUT. Triggers on \"cut vX.Y.Z\", \"initiate a release\", \"publish the npm packages\", \"tag this commit and push the release\", \"batch push\", \"F-N1 warned\", \"GitHub release notes\", \"promote unreleased\", \"version skip recovery\", \"verify pre-flight gates\", \"the cycle just shipped write up what happened\", \"author a migration guide / write the migration recipe for this breaking change\". VERIFICATION is load-bearing: every cut verifies against reality (npm registry + production endpoint + GH release), not self-checks. Carries the portable release DISCIPLINE with the @adia-ai monorepo's 9-package lockstep + demo-site deploy as the clearly-labeled worked example. Does NOT trigger for: the CONSUMER side of migration (sweeping a downstream app the separate consumer/app-author plugin), long-running VM ops (provision/restart/key rotation), authoring new code, or generation-pipeline work.",
3
+ "version": "0.2.2",
4
+ "description": "Release engineer for an @adia-ai-style lockstep monorepo. Cuts, tags, publishes, deploys, authors release notes + audit-history ledgers \u2014 AND authors the MIGRATION GUIDE when a cut breaks an API (the producer side of migrations, folded in from the former adia-ui-migration skill). Also ships an INDEPENDENT-PACKAGE (class-B) release path for packages versioned independently of the lockstep set (excluded from the lockstep gate, no umbrella tag, no demo-site deploy \u2014 e.g. plugins). Use whenever the user wants to CUT A RELEASE / SHIP A VERSION / PUBLISH PACKAGES / BATCH-PUSH ACCUMULATED RELEASES / RECOVER FROM A VERSION-SKIP / WRITE RELEASE NOTES / PROMOTE [UNRELEASED] CHANGELOG / AUTHOR A MIGRATION GUIDE FOR A BREAKING CUT / SHIP AN INDEPENDENTLY-VERSIONED PACKAGE OR PLUGIN. Triggers on \"cut vX.Y.Z\", \"initiate a release\", \"publish the npm packages\", \"tag this commit and push the release\", \"batch push\", \"F-N1 warned\", \"GitHub release notes\", \"promote unreleased\", \"version skip recovery\", \"verify pre-flight gates\", \"the cycle just shipped \u2014 write up what happened\", \"author a migration guide / write the migration recipe for this breaking change\", \"release the plugin / ship an independently-versioned package / publish a package outside the lockstep\". VERIFICATION is load-bearing: every cut verifies against reality (npm registry + production endpoint + GH release), not self-checks. Carries the portable release DISCIPLINE with the @adia-ai monorepo's 9-package lockstep + demo-site deploy as the clearly-labeled worked example. Does NOT trigger for: the CONSUMER side of migration (sweeping a downstream app \u2014 the separate consumer/app-author plugin), long-running VM ops (provision/restart/key rotation), authoring new code, or generation-pipeline work.",
5
5
  "status": "stable",
6
6
  "authors": [
7
7
  "kim.granlund"
@@ -36,10 +36,12 @@
36
36
  "references/exe-deploy.md",
37
37
  "references/ledger-discipline.md",
38
38
  "references/migration-guide-authoring.md",
39
+ "references/independent-package-release.md",
39
40
  "references/teach-protocol.md",
40
41
  "scripts/bump.mjs",
41
42
  "scripts/promote-unreleased.mjs",
42
43
  "scripts/insert-stub.mjs",
44
+ "scripts/package-paths.mjs",
43
45
  "scripts/tag-lockstep.mjs",
44
46
  "scripts/dispatch-publish.mjs",
45
47
  "scripts/make-ledger.mjs",
@@ -64,12 +66,12 @@
64
66
  "ops-postmortem"
65
67
  ],
66
68
  "absorbs": [
67
- "adia-ui-migration DONE: producer side folded in as references/migration-guide-authoring.md + mode 11 (§MigrationGuideAuthoring). The consumer side stays in the separate consumer/app-author plugin.",
68
- "release-publish / release-notes / verification-sweep DONE in the source skill: consolidated into modes 1/3/4 (cut), 5/9 (notes), 6 (verify)."
69
+ "adia-ui-migration \u2014 DONE: producer side folded in as references/migration-guide-authoring.md + mode 11 (\u00a7MigrationGuideAuthoring). The consumer side stays in the separate consumer/app-author plugin.",
70
+ "release-publish / release-notes / verification-sweep \u2014 DONE in the source skill: consolidated into modes 1/3/4 (cut), 5/9 (notes), 6 (verify)."
69
71
  ],
70
72
  "references": [
71
- "adia-ui-a2ui corpus-freshness checks route there for remediation (verify:corpus / embeddings drift)",
72
- "adia-ui-dogfood the static-HTML probes (native-primitive-leak, shell-composition) whose findings fold into release notes",
73
- "ops-postmortem incident genre during post-publish recovery (mode 7)"
73
+ "adia-ui-a2ui \u2014 corpus-freshness checks route there for remediation (verify:corpus / embeddings drift)",
74
+ "adia-ui-dogfood \u2014 the static-HTML probes (native-primitive-leak, shell-composition) whose findings fold into release notes",
75
+ "ops-postmortem \u2014 incident genre during post-publish recovery (mode 7)"
74
76
  ]
75
77
  }
@@ -0,0 +1,37 @@
1
+ # Changelog — dogfood-sweep
2
+
3
+ ## [0.1.0] stable — 2026-05-24
4
+
5
+ **MINOR** — first versioned cut. Adds `skill.json` + `CHANGELOG.md` to bring
6
+ the skill into rollup-family conventions (per META-REFACTOR-SPEC Round 0).
7
+ No procedural changes to the 6 modes.
8
+
9
+ The skill is otherwise unchanged: SKILL.md still carries all 6 modes inline
10
+ (component visual probe / app-shell QA / HTML typo sweep / native-primitive
11
+ leak / admin-shell composition / component anatomy + card header). The
12
+ inversion to `references/mode-N-*.md` is Round 2+ work per the per-skill
13
+ REFACTOR-SPEC at `docs/REFACTOR-SPEC.md`.
14
+
15
+ ### Reconstructed history (pre-v0.1.0)
16
+
17
+ - **2026-05-04** — Added probe #6 (missing component CSS detection) to mode 1
18
+ after the swap-to-primitive-but-forget-the-link class slipped past a real
19
+ PR. The bug class: agent uses `<button-ui>` markup but forgets the
20
+ `<link rel="stylesheet">` for the button component CSS; element registers,
21
+ markup looks right, listbox renders unstyled.
22
+
23
+ - **post-§3 commit** — Native-primitive-leak audit (mode 4) intent-marker
24
+ detection window widened from 80 → 200 chars after a peer agent found that
25
+ realistic preceding-line comments (indent + comment prefix + reason text +
26
+ closing `-->` + newline) sat at exactly the 80-char boundary and were not
27
+ detected.
28
+
29
+ - **v2.27.4-era** — Mode 6 (Component Anatomy + Card Header Sweep) added per
30
+ FB-52 §11: card-ui `<header><div>` wrapper anti-pattern + missing anatomy
31
+ sections per `anatomy-sweep.mjs` expectations.
32
+
33
+ - **Pre-versioned origin** — Mode 1 (component visual probe via Chromium)
34
+ authored as the initial skill. Modes 2–5 absorbed from substrate-side
35
+ `scripts/dev/audit-*.mjs` work over multiple cycles. The skill grew its
36
+ 6-mode catalog organically; this v0.1.0 cut is the first time the manifest
37
+ reflects the actual content.