@adia-ai/adia-ui-forge 0.8.56 → 0.8.58
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 +3 -2
- package/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +47 -0
- package/README.md +3 -3
- package/__init__.py +1 -1
- package/commands/demo-audit.md +1 -1
- package/commands/gen-ui-review.md +1 -1
- package/commands/package-release.md +1 -1
- package/commands/site-deployment.md +1 -1
- package/package.json +2 -2
- package/plugin.yaml +1 -1
- package/prompts/demo-audit.md +1 -1
- package/prompts/gen-ui-review.md +1 -1
- package/prompts/package-release.md +1 -1
- package/prompts/site-deployment.md +1 -1
- package/scripts/forge-lint.mjs +168 -0
- package/scripts/lint-rules.generated.mjs +22210 -0
- package/scripts/site-postwrite-derivation-gate +23 -127
- package/skills/a2ui-maintenance/references/pipeline-overview.md +13 -11
- package/skills/demo-audit/references/admin-shell-anatomy.md +9 -1
- package/skills/demo-audit/references/app-shell-pitfalls.md +1 -1
- package/skills/demo-audit/references/visual-probe-triage.md +4 -1
- package/skills/gen-ui-review/SKILL.md +4 -1
- package/skills/gen-ui-review/references/loop-protocol.md +6 -5
- package/skills/package-release/references/changelog-discipline.md +6 -3
- package/skills/package-release/references/cut-procedure.md +40 -13
- package/skills/package-release/references/gates-catalog.md +5 -2
- package/skills/package-release/references/recovery-paths.md +6 -3
- package/skills/package-release/scripts/gate-roster.mjs +18 -7
- package/skills/primitive-authoring/SKILL.md +5 -2
- package/skills/primitive-authoring/agents/openai.yaml +1 -1
- package/skills/primitive-authoring/references/anti-patterns.md +40 -0
- package/skills/primitive-authoring/references/common-gotchas.md +6 -6
- package/skills/primitive-authoring/references/css-patterns.md +12 -0
- package/skills/primitive-authoring/references/form-control-sizing.md +10 -0
- package/skills/primitive-authoring/references/shell-patterns.md +8 -1
- package/skills/primitive-authoring/references/token-contract.md +19 -1
- package/skills/primitive-authoring/references/yaml-contract.md +43 -14
- package/skills/site-docs-authoring/SKILL.md +7 -9
- package/hooks/hooks.json +0 -44
- package/scripts/forge-lint +0 -315
|
@@ -1,88 +1,40 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""site-postwrite-derivation-gate — PostToolUse check: site source edits name their stale derivatives.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
(fail-open — this hook must never block editing with a crash).
|
|
4
|
+
site/sitemap.json feeds GENERATED surfaces (site/llms.txt, the patterns/
|
|
5
|
+
templates index) that go stale the moment a route or content path changes
|
|
6
|
+
without a regen. This hook feeds that regeneration obligation back at
|
|
7
|
+
write-time.
|
|
8
|
+
|
|
9
|
+
(Before ADR-0072 Decision 2 / gh#2410, this hook also flagged
|
|
10
|
+
site/pages/**/*.html fragment edits whose route the site-a2ui ledger
|
|
11
|
+
marked "converted" — the docs site rendered such routes from a GENERATED
|
|
12
|
+
site-a2ui/pages/<slug>.a2ui.json artifact, so a fragment-only edit changed
|
|
13
|
+
nothing users saw. That render path retired; every route now renders
|
|
14
|
+
straight from its fragment, so a fragment edit is never stale on its own
|
|
15
|
+
and needs no nudge.)
|
|
17
16
|
|
|
18
17
|
Usage:
|
|
19
18
|
site-postwrite-derivation-gate --hook # PostToolUse mode: event JSON on stdin
|
|
20
|
-
site-postwrite-derivation-gate selftest # prove matcher
|
|
19
|
+
site-postwrite-derivation-gate selftest # prove matcher on fixtures
|
|
21
20
|
"""
|
|
22
21
|
import json
|
|
23
22
|
import sys
|
|
24
23
|
|
|
25
24
|
SITEMAP_MARKER = "site/sitemap.json"
|
|
26
|
-
FRAGMENT_MARKER = "site/pages/"
|
|
27
|
-
|
|
28
|
-
# Generated files under site/pages/ are sidecar-prewrite-guard territory.
|
|
29
|
-
GENERATED_EXCLUDE = ("site/pages/patterns/index.html",)
|
|
30
25
|
|
|
31
26
|
|
|
32
27
|
def classify(path):
|
|
33
|
-
"""→
|
|
34
|
-
if
|
|
35
|
-
return
|
|
36
|
-
if path.endswith(SITEMAP_MARKER):
|
|
37
|
-
return "sitemap", path[: -len(SITEMAP_MARKER)], "/" + SITEMAP_MARKER
|
|
38
|
-
idx = path.find(FRAGMENT_MARKER)
|
|
39
|
-
if idx == -1 or not path.endswith(".html") or path[idx:] in GENERATED_EXCLUDE:
|
|
40
|
-
return None, None, None
|
|
41
|
-
return "fragment", path[:idx], "/" + path[idx:]
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def find_route(sitemap, rel):
|
|
45
|
-
"""Walk sections/items for the entry whose `content` equals rel; → route path or None."""
|
|
46
|
-
stack = [sitemap.get("sections") or []]
|
|
47
|
-
while stack:
|
|
48
|
-
node = stack.pop()
|
|
49
|
-
if isinstance(node, list):
|
|
50
|
-
stack.extend(node)
|
|
51
|
-
elif isinstance(node, dict):
|
|
52
|
-
if node.get("content") == rel and node.get("path"):
|
|
53
|
-
return node["path"]
|
|
54
|
-
stack.extend(v for k, v in node.items() if k in ("sections", "items", "children"))
|
|
28
|
+
"""→ "sitemap" if `path` is site/sitemap.json, else None."""
|
|
29
|
+
if path and path.endswith(SITEMAP_MARKER):
|
|
30
|
+
return "sitemap"
|
|
55
31
|
return None
|
|
56
32
|
|
|
57
33
|
|
|
58
|
-
def resolve_fragment(root, rel):
|
|
59
|
-
"""→ feedback reason for a stale-artifact edit, or None to stay quiet.
|
|
60
|
-
|
|
61
|
-
Raises on I/O / parse trouble — the caller fail-opens.
|
|
62
|
-
"""
|
|
63
|
-
with open(root + "site/sitemap.json", encoding="utf-8") as f:
|
|
64
|
-
route = find_route(json.load(f), rel)
|
|
65
|
-
if not route:
|
|
66
|
-
return None # not a routed page
|
|
67
|
-
with open(root + "site-a2ui/ledger.json", encoding="utf-8") as f:
|
|
68
|
-
rows = json.load(f).get("pages") or []
|
|
69
|
-
row = next((r for r in rows if r.get("route") == route), None)
|
|
70
|
-
if not row or row.get("status") != "converted":
|
|
71
|
-
return None # legacy-rendered: the fragment IS what users see
|
|
72
|
-
slug = row.get("slug") or route.lstrip("/").replace("/", "__")
|
|
73
|
-
return (
|
|
74
|
-
f"site-postwrite-derivation-gate · {rel} is the SOURCE for {route}, which renders "
|
|
75
|
-
f"from GENERATED site-a2ui/pages/{slug}.a2ui.json — the artifact is now stale and "
|
|
76
|
-
f"users still see the OLD content. Regenerate: `node scripts/build/site-a2ui.mjs "
|
|
77
|
-
f"--page {route}` (repo-wide check: `npm run verify:site-a2ui`)."
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
|
|
81
34
|
SITEMAP_REASON = (
|
|
82
35
|
"site-postwrite-derivation-gate · site/sitemap.json feeds GENERATED surfaces that are "
|
|
83
36
|
"now stale: site/llms.txt (`npm run build:llms`) and the patterns/templates index "
|
|
84
|
-
"(`npm run build:patterns-index`).
|
|
85
|
-
"site-a2ui route coverage: `node scripts/build/site-a2ui.mjs --verify`."
|
|
37
|
+
"(`npm run build:patterns-index`)."
|
|
86
38
|
)
|
|
87
39
|
|
|
88
40
|
|
|
@@ -92,80 +44,24 @@ def hook_mode():
|
|
|
92
44
|
except Exception:
|
|
93
45
|
return 0
|
|
94
46
|
tool_input = event.get("tool_input") or {}
|
|
95
|
-
|
|
96
|
-
if not kind:
|
|
97
|
-
return 0
|
|
98
|
-
if kind == "sitemap":
|
|
47
|
+
if classify(tool_input.get("file_path") or "") == "sitemap":
|
|
99
48
|
print(json.dumps({"decision": "block", "reason": SITEMAP_REASON}))
|
|
100
|
-
return 0
|
|
101
|
-
try:
|
|
102
|
-
reason = resolve_fragment(root, rel)
|
|
103
|
-
except Exception:
|
|
104
|
-
return 0 # fail-open: never block editing on our own error
|
|
105
|
-
if reason:
|
|
106
|
-
print(json.dumps({"decision": "block", "reason": reason}))
|
|
107
49
|
return 0
|
|
108
50
|
|
|
109
51
|
|
|
110
52
|
def selftest():
|
|
111
|
-
import os
|
|
112
|
-
import tempfile
|
|
113
|
-
|
|
114
53
|
scope_cases = [
|
|
115
|
-
("/repo/site/pages/architecture/ontology.html", "fragment"),
|
|
116
|
-
("site/pages/guides/testing.html", "fragment"),
|
|
117
54
|
("/repo/site/sitemap.json", "sitemap"),
|
|
118
|
-
("/repo/site/pages/
|
|
119
|
-
("
|
|
120
|
-
("/repo/site
|
|
55
|
+
("/repo/site/pages/architecture/ontology.html", None),
|
|
56
|
+
("site/pages/guides/testing.html", None),
|
|
57
|
+
("/repo/site/pages/patterns/index.html", None),
|
|
121
58
|
("/repo/site/site.js", None),
|
|
122
59
|
("/repo/site/pages/architecture/notes.txt", None),
|
|
123
60
|
("/repo/apps/tasks/app/index.html", None),
|
|
124
61
|
]
|
|
125
62
|
for path, expected in scope_cases:
|
|
126
|
-
if classify(path)
|
|
127
|
-
print(f"selftest: FAIL scope {path} → {classify(path)
|
|
128
|
-
return 1
|
|
129
|
-
with tempfile.TemporaryDirectory() as tmp:
|
|
130
|
-
root = tmp + os.sep
|
|
131
|
-
os.makedirs(root + "site")
|
|
132
|
-
os.makedirs(root + "site-a2ui")
|
|
133
|
-
with open(root + "site/sitemap.json", "w", encoding="utf-8") as f:
|
|
134
|
-
json.dump(
|
|
135
|
-
{
|
|
136
|
-
"sections": [
|
|
137
|
-
{
|
|
138
|
-
"items": [
|
|
139
|
-
{"path": "/site/a/one", "content": "/site/pages/a/one.html"},
|
|
140
|
-
{"path": "/site/a/two", "content": "/site/pages/a/two.html"},
|
|
141
|
-
]
|
|
142
|
-
}
|
|
143
|
-
]
|
|
144
|
-
},
|
|
145
|
-
f,
|
|
146
|
-
)
|
|
147
|
-
with open(root + "site-a2ui/ledger.json", "w", encoding="utf-8") as f:
|
|
148
|
-
json.dump(
|
|
149
|
-
{
|
|
150
|
-
"pages": [
|
|
151
|
-
{"route": "/site/a/one", "slug": "site__a__one", "status": "converted"},
|
|
152
|
-
{"route": "/site/a/two", "slug": "site__a__two", "status": "failed"},
|
|
153
|
-
]
|
|
154
|
-
},
|
|
155
|
-
f,
|
|
156
|
-
)
|
|
157
|
-
resolve_cases = [
|
|
158
|
-
("/site/pages/a/one.html", True), # converted → nudge
|
|
159
|
-
("/site/pages/a/two.html", False), # ledger row not converted → quiet
|
|
160
|
-
("/site/pages/a/three.html", False), # no sitemap entry → quiet
|
|
161
|
-
]
|
|
162
|
-
for rel, should_fire in resolve_cases:
|
|
163
|
-
fired = resolve_fragment(root, rel) is not None
|
|
164
|
-
if fired != should_fire:
|
|
165
|
-
print(f"selftest: FAIL resolve {rel} fired={fired} expected={should_fire}")
|
|
166
|
-
return 1
|
|
167
|
-
if "site__a__one.a2ui.json" not in resolve_fragment(root, "/site/pages/a/one.html"):
|
|
168
|
-
print("selftest: FAIL reason does not name the stale artifact")
|
|
63
|
+
if classify(path) != expected:
|
|
64
|
+
print(f"selftest: FAIL scope {path} → {classify(path)} expected {expected}")
|
|
169
65
|
return 1
|
|
170
66
|
print("selftest: PASS")
|
|
171
67
|
return 0
|
|
@@ -51,19 +51,21 @@ ruling was load-bearing on `wireFormat` defaulting to `'dialect'`, with a
|
|
|
51
51
|
**Named expiry**: the flag-flip ADR that makes `'v1'` the shipping default
|
|
52
52
|
had to re-rule site-a2ui's fitness (re-point vs retirement-by-attrition).
|
|
53
53
|
|
|
54
|
-
**[resolved, ADR-0072]** That trigger
|
|
55
|
-
retirement
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
**[resolved, ADR-0072 — retired 2026-08-31]** That trigger fired and was
|
|
55
|
+
ruled: retirement, not re-point (Decision 2). Passive attrition never
|
|
56
|
+
netted the promoted set down (355 pages and growing at last measurement —
|
|
57
|
+
regen work kept adding rows faster than breakage retired them), so the
|
|
58
|
+
operator converted it to an active drain (gh#2410): the mechanism —
|
|
59
|
+
builder, ledger, artifact tree, gates, hook — is gone. The docs site now
|
|
60
|
+
renders every route through the legacy template path only; site-a2ui is
|
|
61
|
+
no longer part of this pipeline. The dialect side's regression-corpus
|
|
62
|
+
coverage site-a2ui once provided (real-content-scale exercise of the
|
|
63
|
+
dialect renderer, the ADR-0061 lifecycle path, and the engine transpiler)
|
|
64
|
+
has no standing replacement — see ADR-0072 Decision 2 / gh#2410 for the
|
|
65
|
+
closure record.
|
|
64
66
|
|
|
65
67
|
All paths repo-relative. Specs worth reading before structural changes:
|
|
66
|
-
`.claude/docs/specs/a2ui-
|
|
68
|
+
`.claude/docs/specs/a2ui-v1.0-catalog-guide.md` (protocol + catalog format),
|
|
67
69
|
`.claude/docs/specs/genui-multiturn-architecture.md` (state cache, refiner,
|
|
68
70
|
op format), `.claude/docs/specs/genui-chunk-marker.md` (chunk attributes),
|
|
69
71
|
`.claude/docs/conventions/gen-ui-pipeline.md` (harvester wiring + embedding
|
|
@@ -30,7 +30,15 @@ the live admin-dashboard example.
|
|
|
30
30
|
(there is no `<theme-picker-ui>`)
|
|
31
31
|
9. `<admin-scroll>` wrapping optional `<aside data-subnav hidden>` +
|
|
32
32
|
`<router-ui>` (or `<admin-page>` directly for non-routed)
|
|
33
|
+
**[deprecated 2026-09-01, ADR-0098]** `admin-scroll` is a wholesale
|
|
34
|
+
rename to `page-scroll` (both modes carried over); `admin-scroll` stays
|
|
35
|
+
as a working compat alias for the deprecation window only.
|
|
33
36
|
10. `<admin-page>` with `<admin-page-header>` + `<admin-page-body>`
|
|
37
|
+
**[deprecated 2026-09-01, ADR-0098]** The `admin-page` family is
|
|
38
|
+
retired deprecate-then-delete in favor of `page-ui[band]` — `page-ui`
|
|
39
|
+
is now the one canonical page-chrome primitive; this anatomy still
|
|
40
|
+
describes the pre-migration shape for auditing legacy surfaces during
|
|
41
|
+
the deprecation window.
|
|
34
42
|
11. `<admin-statusbar>` at content footer (version strip) — ★ commonly missing
|
|
35
43
|
12. Second `<admin-sidebar slot="trailing">` (inspector rail, hidden by
|
|
36
44
|
default) — strongly recommended
|
|
@@ -56,7 +64,7 @@ the live admin-dashboard example.
|
|
|
56
64
|
| content topbar missing `[data-spacer]` / `[data-actions]` | warning — part 7 |
|
|
57
65
|
| content missing trailing `<admin-statusbar>` | warning — part 11 |
|
|
58
66
|
| sidebar topbar contains only plain text | warning — part 3 context switcher |
|
|
59
|
-
| `<admin-scroll>` missing around `<admin-page>` | critical — part 9 (the enforcing script `audit-shell-composition.mjs` tiers this critical: without the scroll+page wrapper the content renders flush with the topbar, no margins) |
|
|
67
|
+
| `<admin-scroll>` missing around `<admin-page>` | critical — part 9 (the enforcing script `audit-shell-composition.mjs` tiers this critical: without the scroll+page wrapper the content renders flush with the topbar, no margins) — **[deprecated 2026-09-01, ADR-0098]** `admin-scroll`/`admin-page` describe the pre-migration shape; the successor is `page-scroll` wrapping `page-ui[band]` |
|
|
60
68
|
|
|
61
69
|
## Opt-out contract
|
|
62
70
|
|
|
@@ -20,7 +20,7 @@ may stall — wait 30s, re-run.
|
|
|
20
20
|
| 4 | Top-level `await` without async setup wrap | `[setup-failed]` console error | `export default async function setup(host) { … }` |
|
|
21
21
|
| 5 | Vite import-analysis 500 on dynamic import | `[network-4xx] 500` for `./<name>.contents.js` | add `/* @vite-ignore */` to the dynamic import |
|
|
22
22
|
| 6 | icon-ui not imported despite `<icon-ui>` / `[icon=…]` / icon-rendering composites | `[icon-ui-missing]` | `import "/packages/web-components/components/icon/icon.js"` |
|
|
23
|
-
| 7 | `<admin-page-body>` emitted without its `<admin-page>` ancestor (gh#981) | *(no audit-app-shells.mjs tag — apps/-only script, doesn't sweep this surface)* | wrap in `<admin-page>` — `admin-page > admin-page-body { flex:1; … }` (`admin-shell.bespoke.css:144`) is a direct-child selector; without that literal parent, `admin-page-body` falls back to UA `display:inline` |
|
|
23
|
+
| 7 | `<admin-page-body>` emitted without its `<admin-page>` ancestor (gh#981) | *(no audit-app-shells.mjs tag — apps/-only script, doesn't sweep this surface)* | wrap in `<admin-page>` — `admin-page > admin-page-body { flex:1; … }` (`admin-shell.bespoke.css:144`) is a direct-child selector; without that literal parent, `admin-page-body` falls back to UA `display:inline` — **[deprecated 2026-09-01, ADR-0098]** `admin-page`/`admin-page-body` are retired deprecate-then-delete; author new surfaces with `page-ui[band]` instead |
|
|
24
24
|
|
|
25
25
|
Secondary signals: `[collapsed-element]` (registered but 0px tall —
|
|
26
26
|
`audit-app-shells.mjs`'s own threshold is <4px, so a shallower-but-still-broken
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
# Mode 1 — Component visual probe: probe classes + triage
|
|
2
2
|
|
|
3
|
+
`<plugin-root>` below is `$CLAUDE_PLUGIN_ROOT` in Claude Code; the plugin's installed directory
|
|
4
|
+
in Codex.
|
|
5
|
+
|
|
3
6
|
Detection layers, deepest last:
|
|
4
7
|
|
|
5
8
|
1. `npm run dogfood:visual-probe` — baseline per-page bar: no 4xx/5xx, no
|
|
6
9
|
console JS errors, non-zero body rect, ≥1 upgraded custom-element host.
|
|
7
10
|
Artifacts land in `qa/findings/visual-probe-<DATE>/`.
|
|
8
11
|
2. Bundled deep analyzer —
|
|
9
|
-
`node "
|
|
12
|
+
`node "<plugin-root>/skills/demo-audit/scripts/analyze.mjs"`.
|
|
10
13
|
Resolves the target checkout from `$ADIA_REPO_ROOT`, else cwd — always run
|
|
11
14
|
from (or point it at) the monorepo checkout, never the plugin install dir.
|
|
12
15
|
Flags: `--filter <slug>` · `--port N` (default 5173) · `--out PATH`
|
|
@@ -77,7 +77,10 @@ only that file, never the raw DOM/canvas/gallery-latest.json.** The
|
|
|
77
77
|
structural prompt-injection defense. Data model:
|
|
78
78
|
[loop-protocol](references/loop-protocol.md).
|
|
79
79
|
|
|
80
|
-
## Scripts (`node
|
|
80
|
+
## Scripts (`node <plugin-root>/skills/gen-ui-review/scripts/…`, from monorepo root)
|
|
81
|
+
|
|
82
|
+
`<plugin-root>` is `$CLAUDE_PLUGIN_ROOT` in Claude Code; the plugin's installed directory in
|
|
83
|
+
Codex.
|
|
81
84
|
|
|
82
85
|
| Command | Purpose |
|
|
83
86
|
| --- | --- |
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# Loop Protocol — one full review cycle
|
|
2
2
|
|
|
3
3
|
Five phases per prompt; human QA gate at cycle close; Phase 5 runs for FAILING
|
|
4
|
-
prompts only.
|
|
5
|
-
|
|
4
|
+
prompts only. `<plugin-root>` below is `$CLAUDE_PLUGIN_ROOT` in Claude Code; the plugin's
|
|
5
|
+
installed directory in Codex. Scripts ship in this skill at
|
|
6
|
+
`<plugin-root>/skills/gen-ui-review/scripts/` and are run from the
|
|
6
7
|
monorepo root (they read `apps/genui/…/gallery-latest.json` and write the
|
|
7
8
|
`review/` tree there). Corpus-pattern doctrine consumed by Phase 5:
|
|
8
9
|
[corpus-html-patterns.md](corpus-html-patterns.md).
|
|
@@ -79,7 +80,7 @@ primitive lookup (`TAG_TO_COMPONENT`, the authoritative table), attr
|
|
|
79
80
|
sanitization, overflow gate:
|
|
80
81
|
|
|
81
82
|
```text
|
|
82
|
-
node
|
|
83
|
+
node <plugin-root>/skills/gen-ui-review/scripts/gen-review-decompose.mjs
|
|
83
84
|
--cycle N [--group <slug>] [--prompt <slug>] [--port 5300] [--settle 2500] [--dry-run]
|
|
84
85
|
```
|
|
85
86
|
|
|
@@ -190,7 +191,7 @@ never get fix plans. Reads ONLY the decomposed file.
|
|
|
190
191
|
5. **Schema gate** (must exit 0 before touching the ledger):
|
|
191
192
|
|
|
192
193
|
```bash
|
|
193
|
-
node
|
|
194
|
+
node <plugin-root>/skills/gen-ui-review/scripts/validate-cycle-scores.mjs --cycle N --strict
|
|
194
195
|
```
|
|
195
196
|
|
|
196
197
|
6. **Update ledger** (`review/cycle-ledger.json`): `cycleNumber`,
|
|
@@ -203,7 +204,7 @@ never get fix plans. Reads ONLY the decomposed file.
|
|
|
203
204
|
7. **Exit condition**:
|
|
204
205
|
|
|
205
206
|
```bash
|
|
206
|
-
node
|
|
207
|
+
node <plugin-root>/skills/gen-ui-review/scripts/gen-review-status.mjs --check-exit
|
|
207
208
|
```
|
|
208
209
|
|
|
209
210
|
Exit 0 → `status: COMPLETE`; exit 1 → `status: OPEN` (the script lists the
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# `changelog-discipline.md` — Keep-a-Changelog mechanics + F-N1 enrichment
|
|
2
2
|
|
|
3
|
+
`<plugin-root>` below is `$CLAUDE_PLUGIN_ROOT` in Claude Code; the plugin's installed directory
|
|
4
|
+
in Codex.
|
|
5
|
+
|
|
3
6
|
> Load whenever a cut touches CHANGELOGs (always for author-from-scratch; for a
|
|
4
7
|
> handoff only if F-N1 warns). The monorepo uses Keep-a-Changelog per package;
|
|
5
8
|
> the cut **promotes** `## [Unreleased]` into `## [vX.Y.Z] — YYYY-MM-DD`.
|
|
@@ -18,7 +21,7 @@
|
|
|
18
21
|
The heading swap is all that happens; content under it stays:
|
|
19
22
|
|
|
20
23
|
```bash
|
|
21
|
-
node "
|
|
24
|
+
node "<plugin-root>/skills/package-release/scripts/promote-unreleased.mjs" \
|
|
22
25
|
--version 0.X.Y --date YYYY-MM-DD --packages web-components,web-modules,a2ui/corpus
|
|
23
26
|
```
|
|
24
27
|
|
|
@@ -70,7 +73,7 @@ Why this recurs: cross-package sweeps leave 1–3 incidental touches (a docstrin
|
|
|
70
73
|
## §Stubs — ride-along lockstep
|
|
71
74
|
|
|
72
75
|
```bash
|
|
73
|
-
node "
|
|
76
|
+
node "<plugin-root>/skills/package-release/scripts/insert-stub.mjs" \
|
|
74
77
|
--version 0.X.Y --date YYYY-MM-DD \
|
|
75
78
|
--substantive "<one-line> in @adia-ai/<pkg>" \
|
|
76
79
|
--xref "packages/web-modules/CHANGELOG.md#0XY--YYYY-MM-DD" \
|
|
@@ -111,7 +114,7 @@ This is cut-procedure §Step 4f: the SAME matcher F-N1 uses at tag time (every r
|
|
|
111
114
|
**Recovery — a warn AFTER tagging** (Step 4f skipped, or an interleaved merge added uncovered changes): the release commit is already merged via PR, so `--amend` is not possible —
|
|
112
115
|
|
|
113
116
|
1. Run `--pending-version X.Y.Z --fix`; `git add` the CHANGELOGs; `git commit -m "fix(release): F-N1 enrichment — vX.Y.Z"`; push the branch → PR → CI → merge.
|
|
114
|
-
2. The SHA moved: `node "
|
|
117
|
+
2. The SHA moved: `node "<plugin-root>/skills/package-release/scripts/tag-lockstep.mjs" --version X.Y.Z --delete`, then re-tag at `main`'s new post-merge HEAD.
|
|
115
118
|
3. Re-run F-N1; expect per-package clean (umbrella error stays, ignored). ONE recovery round — if a second warn appears, the cause is upstream (find what keeps merging into the window), not another enrichment.
|
|
116
119
|
|
|
117
120
|
## §Dating and anchors
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# `cut-procedure.md` — the standard lockstep cut
|
|
2
2
|
|
|
3
|
+
`<plugin-root>` below is `$CLAUDE_PLUGIN_ROOT` in Claude Code; the plugin's installed directory
|
|
4
|
+
in Codex.
|
|
5
|
+
|
|
3
6
|
> Load for any class-A lockstep cut (cut & ship · author from scratch · deploy
|
|
4
7
|
> handoff). Companions: [`gates-catalog.md`](gates-catalog.md) (gate roster +
|
|
5
8
|
> failure routing), [`changelog-discipline.md`](changelog-discipline.md) (promotion
|
|
@@ -13,7 +16,7 @@ Two entry variants, converging at Step 5:
|
|
|
13
16
|
- **Variant A — deploy handoff:** a peer pre-cut the release commit + CHANGELOG + bump + lockfile. Re-baseline, verify, run **Step 4e** (release docs + team notes — the peer's cut may not have generated them, and the pretag gate blocks Step 6 without them), then resume at Step 6 (tag).
|
|
14
17
|
- **Variant B — author from scratch:** source landed under `## [Unreleased]` with no bump. Do Step 4 (promotion + bump + lockfile), then the full tail.
|
|
15
18
|
|
|
16
|
-
``
|
|
19
|
+
`` `<plugin-root>/skills/package-release/scripts/release-pack.mjs` `` (bundled) mechanizes the sequence in two phases per invariant 3 — the cut modes stop at the release commit (PR → CI → merge happens between), `--mode handoff` runs tag→publish→deploy from post-merge main. **Run with `--go` for an operator-initiated release** (single authorization, §below); the steps below are the manual/diagnostic form and run straight through on the same one-go model.
|
|
17
20
|
|
|
18
21
|
| Step | Action | Mutates? |
|
|
19
22
|
| --- | --- | --- |
|
|
@@ -78,7 +81,6 @@ If `git diff <prev-tag>..HEAD --name-only` touches component yaml / `.a2ui.json`
|
|
|
78
81
|
```bash
|
|
79
82
|
node scripts/build/components.mjs # catalog + per-component sidecars
|
|
80
83
|
node scripts/build/generate-examples-md.mjs # .examples.md from .examples.html
|
|
81
|
-
node scripts/build/site-a2ui.mjs --stale # /site/components/* converted rows
|
|
82
84
|
npm run harvest:chunks # chunk corpus from site/apps/playgrounds/catalog
|
|
83
85
|
npm run check:embeddings-fresh # ← run this FIRST; if it passes, SKIP the rebuild below
|
|
84
86
|
npm run build:embeddings:chunks # ONLY if the line above failed (needs OPENAI_API_KEY)
|
|
@@ -89,8 +91,8 @@ npm run build:bundles # dist CSS+JS bundles
|
|
|
89
91
|
|
|
90
92
|
**The `data-chunk-*` qualifier was the trap** (gh#421). Two separate cuts lost a CI round-trip to it:
|
|
91
93
|
|
|
92
|
-
- **v0.8.14** edited two components' `*.examples.html` — not yaml, not `.contents.html`, no annotation — so it read as out of scope. It isn't: `.examples.html` feeds **
|
|
93
|
-
- **The 0.8.16 cycle** then added a brand-new `site/pages/guides/theming.html` with *no* annotations, ran `
|
|
94
|
+
- **v0.8.14** edited two components' `*.examples.html` — not yaml, not `.contents.html`, no annotation — so it read as out of scope. It isn't: `.examples.html` feeds **two** generators (`.examples.md` and the chunk harvest's source hashes; a third, the retired site-a2ui converted rows, applied historically). Every freshness gate this doc named came back clean, and CI still failed on `🔴 stale /site/components/{menu,popover}` plus `2 source file(s) changed since harvest`.
|
|
95
|
+
- **The 0.8.16 cycle** then added a brand-new `site/pages/guides/theming.html` with *no* annotations, ran `check:links`, `verify:llms`, and `verify:patterns-index` — all clean — and CI failed anyway: `1 source file(s) NEW since harvest`.
|
|
94
96
|
|
|
95
97
|
Root cause of both: `check-chunks-fresh` hashes **every** source file under the harvest globs and compares the set against the corpus record, so a new or changed file trips it whether or not the harvester extracts a chunk from it. The annotation governs what gets *harvested*, never what gets *hashed*. Hence the trigger list above names the directories, not the annotation.
|
|
96
98
|
|
|
@@ -121,7 +123,7 @@ npm run check:links # 15 intra-repo links
|
|
|
121
123
|
npm run eval:diff -- --engine zettel # 16 eval floors
|
|
122
124
|
npm run dogfood:status # 17 P0/P1 dogfood floor (static-only under npm-ci; run once more under bootstrap layout for full coverage, gh#1359)
|
|
123
125
|
npm run check:examples-md-fresh # 18 .examples.md vs .examples.html
|
|
124
|
-
|
|
126
|
+
# gate 19 (verify:site-a2ui) retired with the site-a2ui mechanism — ADR-0072 Decision 2 / gh#2410
|
|
125
127
|
npm run verify:contrast # 20 WCAG AA — canvas-text AND text-on-fill
|
|
126
128
|
npm run check:token-semantics-sync # 21 token-selection generated refs vs token sources
|
|
127
129
|
npm run check:demo-routes # 22 demo surfaces routed + patterns indexed
|
|
@@ -135,6 +137,9 @@ node scripts/release/check-estate-split-latch.mjs # 29 no lockstep cut
|
|
|
135
137
|
npm run check:catalog-tiers # 30 tier-index.json vs committed catalog (gh#1494 — ADR-0069 moved its PR-blocking half to derived-resync; the pre-cut roster re-asserts Class-R freshness before a tag. Gate 13 can't catch this: the harvester hashes tier-index.json as a SOURCE)
|
|
136
138
|
npm run check:codex-manifests-fresh # 31 Codex plugin.json + openai.yaml vs .claude-plugin/plugin.json SoT (gh#1888)
|
|
137
139
|
npm run check:harness-manifests-fresh # 32 Hermes/Pi plugin.yaml + __init__.py + prompts vs .claude-plugin/plugin.json + commands SoT (gh#1954)
|
|
140
|
+
npm run verify:patterns-index # 33 pattern-index.md (mcp + adia-ui-factory) vs corpus source
|
|
141
|
+
node scripts/release/check-yaml-events-vs-runtime.mjs --strict --strict-details # 34 yaml events: blocks vs runtime dispatch — no phantom/missing events (gh#2829)
|
|
142
|
+
node scripts/release/check-yaml-impl-coverage.mjs --strict # 35 yaml schema fields vs implementation coverage (gh#2829)
|
|
138
143
|
```
|
|
139
144
|
|
|
140
145
|
**Gate 29 was the ADR-0048 latch; since P5 it is a permanent invariant.** Between P1 and P5 the repo was correct in-repo but deliberately **not publishable** (old-name stubs marked `private: true` that the roster still mapped, plus dependency edges onto workspace packages no cut published), and a cut in that window would have shipped broken packages that npm cannot unpublish. **P5 cleared it by landing the real thing** — the six stubs became publishable shims and `PACKAGE_ROSTER` gained the three remaining new names, at which point all 9 offending edges resolved and the gate went green on its own. No gate logic was changed.
|
|
@@ -154,9 +159,9 @@ Any red → route via [`gates-catalog.md`](gates-catalog.md); fix at the source,
|
|
|
154
159
|
|
|
155
160
|
**Run 4a whenever a hand-authored `## [Unreleased]` section is still sitting uncommitted, not only on a strict Variant B.** `release-pack.mjs --mode cut` (a peer's pre-staged content, not yet promoted) needs it exactly as much as `--mode from-scratch` does — the v0.8.4 near-miss was `--mode cut` skipping this step entirely because the doc (and the script) only associated promotion with "from scratch". Both modes now run it and both hard-fail before the bump if any roster package still carries non-empty `[Unreleased]` content afterward.
|
|
156
161
|
|
|
157
|
-
**4a. Promote** `## [Unreleased]` → `## [vX.Y.Z] — YYYY-MM-DD` per package (``
|
|
162
|
+
**4a. Promote** `## [Unreleased]` → `## [vX.Y.Z] — YYYY-MM-DD` per package (`` `<plugin-root>/skills/package-release/scripts/promote-unreleased.mjs` ``); author fresh blocks for changed-but-unlogged packages; stub the pure ride-alongs (`` `<plugin-root>/skills/package-release/scripts/insert-stub.mjs` ``). Classification recipe + shapes: [`changelog-discipline.md`](changelog-discipline.md).
|
|
158
163
|
|
|
159
|
-
**4b. Bump.** PATCH vs MINOR: **MINOR is reserved for API-surface breaks only** (removed/renamed prop, attribute, slot, event, token, or tag). Visible behavior changes, re-scalings, and opt-in features stay PATCH; a CHANGELOG bullet saying "(MINOR behavior change)" is prose, not a semver directive. Unqualified "bump version" = PATCH; don't round-trip to ask. `node "
|
|
164
|
+
**4b. Bump.** PATCH vs MINOR: **MINOR is reserved for API-surface breaks only** (removed/renamed prop, attribute, slot, event, token, or tag). Visible behavior changes, re-scalings, and opt-in features stay PATCH; a CHANGELOG bullet saying "(MINOR behavior change)" is prose, not a semver directive. Unqualified "bump version" = PATCH; don't round-trip to ask. `node "<plugin-root>/skills/package-release/scripts/bump.mjs" --from X.Y.Z-1 --to X.Y.Z`. On a MINOR cut, also bump the internal `@adia-ai/*` `^ranges` separately (bump.mjs touches `"version"` fields only) — and a MINOR cut owes a MIGRATION GUIDE section ([`migration-guide-authoring.md`](migration-guide-authoring.md)).
|
|
160
165
|
|
|
161
166
|
**4c. Lockfile.** `npm install --package-lock-only --no-audit --no-fund` — must land in the release commit. The publish workflows open with `npm ci`, which hard-fails on a version/lockfile mismatch: a bump without the regenerated lockfile passes locally and breaks **every** publish at clean-install.
|
|
162
167
|
|
|
@@ -224,10 +229,10 @@ stub sections exist leaves it nothing to append to, and the gap resurfaces
|
|
|
224
229
|
as F-N1 warns at the push boundary, costing a tag move:
|
|
225
230
|
|
|
226
231
|
```bash
|
|
227
|
-
node "
|
|
232
|
+
node "<plugin-root>/skills/package-release/scripts/insert-stub.mjs" \
|
|
228
233
|
--version X.Y.Z --date YYYY-MM-DD --previous-version X.Y.Z-1 \
|
|
229
234
|
--substantive "<one-line>" --xref "<anchor>" --packages <missing-stubs> # 4a-stub — FIRST, only the missing ones (hard-errors on existing sections)
|
|
230
|
-
node "
|
|
235
|
+
node "<plugin-root>/skills/package-release/scripts/bump.mjs" --from X.Y.Z-1 --to X.Y.Z # 4b (skip if versions already moved)
|
|
231
236
|
npm install --package-lock-only --no-audit --no-fund # 4c
|
|
232
237
|
npm run check:lockstep # 4d
|
|
233
238
|
node scripts/build/derive-genui-catalog.mjs # 4d.5 — catalogId carries the bumped version (gh#617)
|
|
@@ -296,7 +301,7 @@ unresolved AND no review requests changes; any other state stops with the
|
|
|
296
301
|
evidence, never force-merges):
|
|
297
302
|
|
|
298
303
|
```bash
|
|
299
|
-
node "
|
|
304
|
+
node "<plugin-root>/skills/package-release/scripts/pr-bridge.mjs" \
|
|
300
305
|
--branch "release/vX.Y.Z" --title "release: vX.Y.Z lockstep" --body-file <path>
|
|
301
306
|
```
|
|
302
307
|
|
|
@@ -344,7 +349,7 @@ Stage its CHANGELOG edits into the release commit (the Step-5 allowlist already
|
|
|
344
349
|
Log the planned tag list (evidence table above), then tag **at `main`'s post-merge HEAD** (post-bump fixes belong in the tarball; the window's last merge is the tag point — exception: batch push tags each version at its own release-merge SHA):
|
|
345
350
|
|
|
346
351
|
```bash
|
|
347
|
-
node "
|
|
352
|
+
node "<plugin-root>/skills/package-release/scripts/tag-lockstep.mjs" \
|
|
348
353
|
--version X.Y.Z # umbrella vX.Y.Z + 10 <pkg>-vX.Y.Z (8 npm + 2 plugins)
|
|
349
354
|
```
|
|
350
355
|
|
|
@@ -370,7 +375,7 @@ a 14-package roster (`agent`, `persona` and `a2ui-protocol-mcp` missing), which
|
|
|
370
375
|
is three packages that would simply never publish.
|
|
371
376
|
|
|
372
377
|
```bash
|
|
373
|
-
PKGS=$(node -e "import('
|
|
378
|
+
PKGS=$(node -e "import('<plugin-root>/skills/package-release/scripts/package-paths.mjs')
|
|
374
379
|
.then(m => console.log(m.PACKAGE_ROSTER.filter(p => p.lockstep !== false).map(p => p.name).join(' ')))")
|
|
375
380
|
echo "$PKGS" # log it — this IS the tag list evidence
|
|
376
381
|
|
|
@@ -385,7 +390,7 @@ git -C "$REPO" push origin vX.Y.Z # umbrella last; triggers nothing
|
|
|
385
390
|
Log the current registry snapshot (per-package versions + `dist-tags.latest`) before dispatching. For batch pushes, verify ordering against that snapshot: **oldest version publishes and settles first** — `npm dist-tag latest` is set by publish order. Then:
|
|
386
391
|
|
|
387
392
|
```bash
|
|
388
|
-
node "
|
|
393
|
+
node "<plugin-root>/skills/package-release/scripts/dispatch-publish.mjs" \
|
|
389
394
|
--version X.Y.Z --verify-triggered # re-dispatches missing/dead runs; registry-gated (gh#763)
|
|
390
395
|
```
|
|
391
396
|
|
|
@@ -435,6 +440,28 @@ Deploy discipline (the release tenant of the demo-site host; VM/service ops belo
|
|
|
435
440
|
|
|
436
441
|
- Any docs **route** cited to the operator or in notes must exist in `site/sitemap.json` (`grep '"path":'`) — a plausible-looking route that isn't in the sitemap renders blank.
|
|
437
442
|
|
|
443
|
+
**Milestone close (ADR-0103, gh#2729, dated addendum 2026-09-01).** Every
|
|
444
|
+
lockstep cut has an open GitHub Milestone named `vX.Y.Z` (one per cut,
|
|
445
|
+
created ahead of time or by the first PR scheduled into it). Close it here,
|
|
446
|
+
after the GH releases above and before release notes:
|
|
447
|
+
|
|
448
|
+
```bash
|
|
449
|
+
number=$(gh api repos/adiahealth/gen-ui-kit/milestones --jq \
|
|
450
|
+
'.[] | select(.title == "vX.Y.Z") | .number')
|
|
451
|
+
gh api -X PATCH "repos/adiahealth/gen-ui-kit/milestones/$number" -f state=closed
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
Any issue still open in that milestone at close time either ships anyway
|
|
455
|
+
(re-tag it into the milestone that actually shipped it — never leave an
|
|
456
|
+
already-shipped issue's milestone wrong) or slips to the next milestone
|
|
457
|
+
(re-tag now, don't leave the closed milestone showing open issues). Create
|
|
458
|
+
the NEXT cut's milestone here too, so scheduling work for it doesn't wait on
|
|
459
|
+
this cut's tag:
|
|
460
|
+
|
|
461
|
+
```bash
|
|
462
|
+
gh api repos/adiahealth/gen-ui-kit/milestones -f title="vNEXT.Y.Z" -f state=open
|
|
463
|
+
```
|
|
464
|
+
|
|
438
465
|
Any Step-2 stashes: `git stash pop`; flag conflicts to the operator.
|
|
439
466
|
|
|
440
467
|
## §Step 11 — Author release notes (default)
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# `gates-catalog.md` — pre-flight gate roster + failure → recovery map
|
|
2
2
|
|
|
3
|
+
`<plugin-root>` below is `$CLAUDE_PLUGIN_ROOT` in Claude Code; the plugin's installed directory
|
|
4
|
+
in Codex.
|
|
5
|
+
|
|
3
6
|
> Load for a verify-only run or on any gate failure during a cut. Maps every
|
|
4
7
|
> release-flow gate × what it checks × typical failure × recovery. Gates are
|
|
5
8
|
> grouped by **failure category** — how the operator routes when one goes red —
|
|
@@ -16,7 +19,7 @@ Row layout per gate: **What** · **Typical failure** · **Recovery**.
|
|
|
16
19
|
|
|
17
20
|
- **What:** all 10 lockstep `@adia-ai/*` packages declare the same `version` (the class-B `adia-plugins` package is excluded — `lockstep: false`, `scripts/package-paths.mjs`); internal `@adia-ai/*` dep ranges match policy (`^X.Y.0` during PATCH cycles, bumped at MINOR).
|
|
18
21
|
- **Typical failure:** one package forgot to bump; a peer edited an internal range mid-PATCH; a `^0.0.x` range slipped in.
|
|
19
|
-
- **Recovery:** version drift → ``
|
|
22
|
+
- **Recovery:** version drift → `` `<plugin-root>/skills/package-release/scripts/bump.mjs` ``; range drift → `npm run check:lockstep:fix` auto-aligns, then re-run.
|
|
20
23
|
- **Why `^0.0.x` is forbidden:** npm pre-1.0 semver only widens the caret when major+minor aren't both zero — `^0.0.6` resolves to `>=0.0.6 <0.0.7`, locked to exactly 0.0.6. An internal dep pinned that way silently installs a *stale* sibling on every fresh `npm i` (this shipped a real ~4-day-latent bug before the lockstep policy). The `^X.Y.0` floor (Y≥1) widens correctly across patches; trust the gate, don't reason about caret semantics by hand. Moot at 1.0.0.
|
|
21
24
|
|
|
22
25
|
### `node scripts/release/check-release.mjs --all-pending` (F-N1, the release trip-wire)
|
|
@@ -212,7 +215,7 @@ npm run check:demo-shells
|
|
|
212
215
|
|
|
213
216
|
Add `verify:corpus` + `check:embeddings-fresh` if chunks were touched; `check:lightningcss-build` if CSS was touched; F-N1 if unpushed release tags exist.
|
|
214
217
|
|
|
215
|
-
**Full pre-cut sweep** — the 30-gate roster in [`cut-procedure.md`](cut-procedure.md) §Step 3, sourced from ``
|
|
218
|
+
**Full pre-cut sweep** — the 30-gate roster in [`cut-procedure.md`](cut-procedure.md) §Step 3, sourced from `` `<plugin-root>/skills/package-release/scripts/gate-roster.mjs` `` (the ONE list; `release-pack.mjs` imports and runs it in full — a subset run is impossible without editing that file). ~90s wall time.
|
|
216
219
|
|
|
217
220
|
**Omnibus** — `npm run check` invokes everything. Heavy; use when re-baselining a stale checkout.
|
|
218
221
|
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# `recovery-paths.md` — the 8 recovery scenarios
|
|
2
2
|
|
|
3
|
+
`<plugin-root>` below is `$CLAUDE_PLUGIN_ROOT` in Claude Code; the plugin's installed directory
|
|
4
|
+
in Codex.
|
|
5
|
+
|
|
3
6
|
> Load on any F-N1 / pre-flight failure, for a batch push, or for post-release
|
|
4
7
|
> recovery. Each scenario: the **shape** (what the repo state looks like), the
|
|
5
8
|
> **resolution** (commands + judgment calls), and what to record. Every scenario
|
|
@@ -48,7 +51,7 @@ Ambiguous → surface it, don't guess.
|
|
|
48
51
|
|
|
49
52
|
**Shape:** source + CHANGELOG entries landed under `## [Unreleased]`, no bump, no release commit.
|
|
50
53
|
|
|
51
|
-
**Resolution:** [`cut-procedure.md`](cut-procedure.md) Variant B; ``
|
|
54
|
+
**Resolution:** [`cut-procedure.md`](cut-procedure.md) Variant B; `` `<plugin-root>/skills/package-release/scripts/promote-unreleased.mjs` `` mechanizes the heading swap; fresh blocks per [`changelog-discipline.md`](changelog-discipline.md) §Authoring.
|
|
52
55
|
|
|
53
56
|
## §Scenario 4 — `[Unreleased]` extension (early cut + entangled fix)
|
|
54
57
|
|
|
@@ -80,11 +83,11 @@ Ambiguous → surface it, don't guess.
|
|
|
80
83
|
**Resolution:**
|
|
81
84
|
|
|
82
85
|
```bash
|
|
83
|
-
node "
|
|
86
|
+
node "<plugin-root>/skills/package-release/scripts/dispatch-publish.mjs" \
|
|
84
87
|
--version X.Y.Z --verify-triggered # re-dispatches packages with no run OR a dead (cancelled/failed/timed-out) run; registry-gated; idempotent
|
|
85
88
|
```
|
|
86
89
|
|
|
87
|
-
For a batch, preserve npm-latest ordering (`--after <prev>`). Verify against the registry, not the workflows. **Prevention:** push tags one-at-a-time ([`cut-procedure.md`](cut-procedure.md) §Step 8); ``
|
|
90
|
+
For a batch, preserve npm-latest ordering (`--after <prev>`). Verify against the registry, not the workflows. **Prevention:** push tags one-at-a-time ([`cut-procedure.md`](cut-procedure.md) §Step 8); `` `<plugin-root>/skills/package-release/scripts/release-pack.mjs` `` does this automatically and follows with `--verify-triggered`.
|
|
88
91
|
|
|
89
92
|
## §Scenario 8 — Cut on the wrong branch
|
|
90
93
|
|