@akinet/akidevrule 3.3.1 → 3.5.0
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/CHANGELOG.md +56 -0
- package/README.md +34 -29
- package/claude/CLAUDE.md +5 -6
- package/claude/agents/aki-challenger.md +1 -1
- package/claude/agents/aki-conduct.md +2 -2
- package/claude/agents/aki-hands.md +4 -4
- package/claude/agents/aki-judge.md +2 -2
- package/claude/agents/aki-maker.md +2 -2
- package/install.mjs +115 -292
- package/lib/permissions.mjs +244 -0
- package/package.json +5 -2
- package/payload/GEMINI.md +2 -0
- package/payload/METHOD-audit-frozen-reference.md +33 -0
- package/payload/METHOD-audit-zero-trust.md +1 -1
- package/payload/METHOD-deep-think.md +1 -1
- package/payload/RULE-agent-behavior.md +4 -2
- package/payload/RULE-coding.md +2 -1
- package/payload/RULE-content-write.md +3 -3
- package/payload/RULE-docs.md +27 -6
- package/payload/RULE-pattern-core.md +1 -1
- package/payload/RULE-release.md +37 -17
- package/payload/RULE-seo.md +15 -13
- package/payload/RULE-stack-akiNuxtCf.md +1 -0
- package/payload/RULE-ui-pattern.md +1 -1
- package/payload/index.md +14 -10
- package/skills/aki-article-writer/SKILL.md +7 -7
- package/skills/aki-article-writer/references/article-workflow.md +11 -15
- package/skills/akidevsync-notes/SKILL.md +1 -1
- package/skills/akiflow/references/harness-facts.md +8 -6
- package/skills/akiflow/scripts/release_lint.py +157 -0
- package/skills/akihelp/SKILL.md +7 -7
- package/skills/akihtmlreport/SKILL.md +1 -1
- package/skills/akilint/SKILL.md +1 -1
- package/skills/akiopen/SKILL.md +38 -0
- package/skills/akirule/SKILL.md +46 -130
- package/skills/akiship/SKILL.md +6 -3
- package/skills/akithink/SKILL.md +8 -7
- package/skills/akiflow/scripts/council-cost.sh +0 -4
- package/skills/akiflow/scripts/council-open.sh +0 -4
- package/skills/akiflow/scripts/council-read.sh +0 -4
- package/skills/akiflow/scripts/council-verify.sh +0 -4
- package/skills/akiflow/scripts/scythe.sh +0 -4
package/payload/RULE-release.md
CHANGED
|
@@ -106,11 +106,7 @@ After updating CHANGELOG and the version bump, produce the GitHub Release withou
|
|
|
106
106
|
- **Otherwise** (no `gh`, or the user will publish manually) → output the copy-ready block below instead.
|
|
107
107
|
- Before minting, cross-check tags against Releases (`gh release list` vs `git tag`) and offer to backfill any tag that has no matching Release, so the Releases page has no gaps.
|
|
108
108
|
|
|
109
|
-
**Title:** `v{version}: {
|
|
110
|
-
- Good: `v1.5.1: fix production icons blank, caret, grid gap`
|
|
111
|
-
- Bad: `v1.5.1: patch fixes`, `v1.5.1: various improvements`
|
|
112
|
-
|
|
113
|
-
**Body:** same `#### Fixed` / `#### Changed` / `#### Added` sections as CHANGELOG, but each bullet trimmed to one short sentence — symptom first, no file paths, no internal jargon.
|
|
109
|
+
**Title and body are B6's tiers, written once:** title `v{version}: {Headline}`, body = the Full tier. Nothing about their wording lives here.
|
|
114
110
|
|
|
115
111
|
**Compare link (GitHub-hosted repos — mandatory footer):** the notes end with `**Full Changelog**: <repo-url>/compare/<prev-tag>...<new-tag>` — or use `gh release create --generate-notes`, which inserts it automatically. The Release page renders notes only, never a diff, and the tag itself points at a single commit (usually the version-mint commit, a tiny diff) — without this line there is no one-click view of the commits accumulated since the previous release. First release with no prior tag: link `<repo-url>/commits/<new-tag>` instead.
|
|
116
112
|
|
|
@@ -133,10 +129,32 @@ Do not report a plan, task, or release/deploy as complete when a migration/infra
|
|
|
133
129
|
4. **REHEARSE from the PREVIOUS state, never from empty.** A test or dry-run that starts from a fresh database exercises `CREATE`, not the migration, and proves NOTHING about an upgrade. REQUIRED evidence: the migration executed against (a) a schema generated or snapshotted from the previous release, AND (b) for any data-dependent change (unique index, `NOT NULL` backfill, type change, dedupe) a COPY of real target data. State which was run and quote its output. "The tests pass" is not evidence.
|
|
134
130
|
5. **POSTCONDITIONS asserted, ROLLBACK named.** After the real run, assert expected columns, indexes and row counts by query (condition 1 above), and record the backup or fix-forward path BEFORE any destructive step ([[RULE-agent-behavior]] B3). A migration with no stated rollback or fix-forward path does NOT ship.
|
|
135
131
|
|
|
136
|
-
### B6.
|
|
137
|
-
|
|
138
|
-
-
|
|
139
|
-
-
|
|
132
|
+
### B6. Release copy — one user-facing text per release, three lengths, printed by default
|
|
133
|
+
The developer record and the user-facing copy are two channels (C1); this item owns the user-facing one for every project type, and every surface that shows a release to a person renders it: the GitHub Release (B4), `releases.json` (C2, web only), an in-app "what's new", a store listing, a post or notification the owner sends by hand. One source, three lengths, all telling one story (`biz.C4`):
|
|
134
|
+
- **Headline** — at most 12 words naming the one thing the user gets: the C2 highlight when there is one, else the most user-visible change. It is the GitHub Release title after `v{version}: ` (A3: the `v` is render-time only) and the `releases.json` `title`. Good: `fix production icons blank, caret, grid gap`; bad: `patch fixes`, `various improvements`, `bug fixes`.
|
|
135
|
+
- **Short** — one or two sentences for a post, a notification, a chat message: the headline's benefit, the concrete mechanism as proof, the link. Says nothing the Full tier does not.
|
|
136
|
+
- **Full** — the highlight first as its own line, then every other change as one sentence each, grouped under C1's sections in C1's order, symptom first for fixes; the compare-link footer per B4 when GitHub-hosted.
|
|
137
|
+
Wording in every tier: benefit first, then proof (`biz.C1`); what the user can now do, never the file, route, symbol or component; no em/en dash, short sentences (`content.B2`); the audience's language per C1's table (English default, plus Vietnamese where the product is bilingual); one canonical term per concept across versions ("Release Notes", never a synonym — `content.A3`). Internal-only work is one honest line ("under-the-hood improvements…", C3), never dressed as a feature.
|
|
138
|
+
**Choosing the headline — a protocol with a written verdict, never a feeling.** The agent that wrote the code is the worst judge of what users gained: it ranks by effort spent, by what landed last, or by what the owner talked about most, and none of those is value. So the choice is made from the user's side, in five steps, and the reasoning is written into the run's receipt where the owner can overrule it:
|
|
139
|
+
1. **Candidates** — every change in the accumulation that alters what a user can do: a new tool, page, mode, format, integration, or an option inside one. `fixed` and `internal` are excluded by construction; a fix that unblocks a core flow may become the *Headline* when nothing else qualifies, but it is never a `highlight` (C2), because the highlight tier means capability gained, not capability restored.
|
|
140
|
+
2. **Three kill-tests per candidate, from the primary audience's seat** (`docs/biz/`, `biz.A1`; when no audience is recorded, the person the product's front page addresses). *Return:* would someone who last used the product before this version come back, or use it differently, because of this? *Tell:* can it be said in one sentence that person would repeat to a peer, with a concrete verb and no file, route, component or internal term? *Before/after:* is there something they could not do before, or could only do with a workaround? A polish, copy, layout or speed change fails the third unless it removes a workaround; an admin-only or owner-only capability fails the first; a candidate whose Tell sentence needs internal vocabulary fails the second. One failed test disqualifies.
|
|
141
|
+
3. **Rank survivors by reach × delta**, both estimated and labeled so: reach is how many of that audience meet it in a normal session (every session > a common task > a niche path); delta is the size of the gain (a new tool or mode > a new option inside an existing tool > a removed workaround).
|
|
142
|
+
4. **Cut to one.** A second only when it is independent of the first (a different job, not a sub-feature of it) and ranks close; three never — a third means the release bundled two releases (A5 materiality) or the ranking is undecided, and undecided resolves to one, not two. Zero survivors is a normal result: the Headline then names the most user-visible fix or change, and the announce verdict is judged on that.
|
|
143
|
+
5. **Cross-check the surfaces before writing.** The Headline, the first `changes[]` line, the `highlight` flag, the Short tier and the GitHub Release title must all point at the same thing; when the title you would naturally write names something the highlight does not, the selection is wrong, not the title. Then write the verdict into the receipt: one line per candidate — `highlight: <thing> — passes return/tell/before-after, reach every session, delta new mode` or `rejected: <thing> — fails return (admin-only)` — so a `release_lint.py` `[HILITE]` line is answered by this record and the owner can overrule a choice without re-deriving it.
|
|
144
|
+
|
|
145
|
+
**Announce verdict.** The copy ends with `Announce: yes` when the version carries a highlight or a fix the user would notice, or `Announce: no — <reason>` for an internal-only or invisible-fix accumulation: a follower who reads three "stability improvements" posts in a week stops reading the fourth, so the materiality test (A5) applies at the channel too. Channels are the project's own, read from `docs/biz/`, the project `CLAUDE.md` or an existing post history — never invented; with none recorded the verdict stands alone.
|
|
146
|
+
**Where it is produced.** Every `/akiship` run ends with this block, a deferred version included (`deferred — no copy`), and any other run that mints a version prints it in its closing report. It is reused, never rewritten: the `releases.json` entry and the GitHub Release body already are this copy, so the block quotes them rather than composing a third variant; where neither exists (CLI, desktop app without a GitHub Release) the block is the copy's only home and the owner pastes it where it goes.
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
## Release copy — {version} ({date})
|
|
150
|
+
Headline: …
|
|
151
|
+
Short: …
|
|
152
|
+
Full:
|
|
153
|
+
…
|
|
154
|
+
Announce: yes | no — <reason>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Doc/version moves are part of the change, not an afterthought ([[RULE-docs]]).
|
|
140
158
|
|
|
141
159
|
### B7. Pre-ship gate — work finished, nothing pushed yet
|
|
142
160
|
|
|
@@ -154,7 +172,7 @@ Run in order; each step names the rule that owns it.
|
|
|
154
172
|
1. **Release state** — derive it cold from the repo per B1, never from session memory. `Drifted` blocks everything until A5's recovery has run.
|
|
155
173
|
2. **Hygiene sweep — scoped to the accumulation, never the whole repo.** On the files touched since the boundary commit (B1.5): scythe `[WRAP]`/`[YAP]` lint ([[RULE-agent-behavior]] §0), dead code / redundant guards / duplication the accumulation itself introduced (`pattern.A8`; subtract-class detectors at diff scope), and doc references in touched comments still resolving ([[RULE-docs]] B3). A repo-wide subtraction or zero-trust sweep is a separately scheduled audit, never a per-release cost — diff scope is what keeps this gate affordable at many releases per day. Unlike an audit, findings here are fixed in place: this is a gate, not a report.
|
|
156
174
|
3. **Migration & external-action completeness — the B5 detector runs FIRST, on EVERY release, without exception.** Paste its output (or `empty`) into the receipt. A hit obliges a written answer to each of B5 points 2–5: is it separate, is the order expand → migrate → deploy → contract, was it rehearsed from the PREVIOUS state (which one, quoted output), are postconditions and rollback stated. Startup-embedded migration code counts. Then every other change whose "done" lives outside the repo (remote config, env vars, cron registrations, cache purges) is confirmed live, and each script sits in its completion location ([[RULE-coding]] B3). A green build proves nothing about the database; a green test on an empty database proves nothing about an upgrade.
|
|
157
|
-
4. **Record truthfulness** — every closed problem has its `CHANGELOG.md` entry, and no entry claims something step 3 has not cleared (B2). Web stacks additionally need `releases.json` parity (C3).
|
|
175
|
+
4. **Record truthfulness** — every closed problem has its `CHANGELOG.md` entry, and no entry claims something step 3 has not cleared (B2). Web stacks additionally need `releases.json` parity (C3). Shape is mechanical: `python3 ~/.claude/skills/akiflow/scripts/release_lint.py --latest .` (C4) must exit 0; a `[HILITE]` review line is answered in writing per C2, never silently passed.
|
|
158
176
|
5. **Doc sync — every record surface the accumulation touched, not only `docs/`.** Enumerate, then check each against the diff: plans whose work shipped moved to `docs/plan/done/`; `arch`/`feat` docs match what is about to ship ([[RULE-docs]] B1, B3); `README.md` wherever the accumulation changed setup, commands, layout, or a documented behavior; the project's task-note file when one exists (`.akidevsync/notes.json`, edited only through the `akidevsync-notes` skill — a note whose fix is in this accumulation is marked done with the matching CHANGELOG line, an unmatched or unverified one stays open and is named in the report); and any external standards doc the project `CLAUDE.md` binds the project to, updated in place when the accumulation changed a convention that doc owns. A surface skipped because it was not in `docs/` is the same drift finding as a stale doc.
|
|
159
177
|
6. **Build & test — mirror CI.** Commands are derived, never invented: the jobs `.github/workflows/*` run on push/tag take priority; a repo with no such workflow falls back to the manifest's own scripts (`npm run typecheck`/`build`/`test`, `cargo build`/`cargo test`, equivalent). Run every one of them locally, self-authorized ([[RULE-coding]] B3 — ship/release is the moment full build+test is mandatory, not optional). A failure blocks the gate and is fixed in place, same as step 2. A CI step that cannot be reproduced locally (an other-OS matrix leg, a job needing secrets) is named explicitly and left to B10 to catch post-push. A repo with no build/test command at all says so plainly — that is a finding, not a silent pass. This step sits after 2–5 because those fix code and docs first, and the build must cover what is actually about to ship.
|
|
160
178
|
7. **Verification honesty** — anything only checkable at runtime is reported as unverified rather than assumed ([[RULE-coding]] B3). "Untested but I expect it works" is a valid gate output; a silent "Done" is not.
|
|
@@ -169,9 +187,9 @@ The B7 gate plus its surrounding ritual (fix findings → sync docs → CHANGELO
|
|
|
169
187
|
- **A valid `/akiship` invocation = standing authorization for every enumerated step.** Explicitly invoking the full run authorizes: fixing gate findings, CHANGELOG/`releases.json` edits, grouped commits (the akigitcommit confirm step is pre-answered — "commit luôn" semantics), the version mint per A4/A5, and tag/GitHub Release strictly per the repo's existing convention (A3, B4). Push and deploy are included only when the invocation names them **or carries a completion-intensity signal** (the same trigger set as the next bullet) — a plain `/akiship` with no intensity marker stays local-only; deploy still additionally requires the stack to auto-deploy on push (owned by the stack rule, not this contract).
|
|
170
188
|
- **Front-load the asks.** Derive B1 state and run B7 step 0 first; every escalation found is reported once, as one batch, and the run stops there. A clean front check means the run completes with zero mid-run questions — an automation that stalls on a question halfway through has failed this rule.
|
|
171
189
|
- **Escalation floor (canonical — `/akiship` references this list, never restates it, `pattern.A1`) — stop only for:** (1) public-history ambiguity — cannot determine whether a version actually shipped, or a `Mismatch`/`Drifted` state whose recovery would rewrite published versions (A5, B1); (2) work the tree cannot classify — mid-edit vs abandoned (B7 step 0); (3) contradiction with documented design, or scope beyond what the invocation named ([[RULE-agent-behavior]] B3).
|
|
172
|
-
- **Completion-intensity phrasing collapses condition (2) and unlocks push/deploy/GitHub-Release, never conditions (1) or (3).** The canonical
|
|
190
|
+
- **Completion-intensity phrasing collapses condition (2) and unlocks push/deploy/GitHub-Release, never conditions (1) or (3).** The canonical definition — every other file (the `/akiship` skill, `README.md`) points here (`pattern.A1`): wording in any language that insists the run finish everything, end to end — read **only inside a valid invocation**, where it modifies a run already authorized to start and never creates that authorization, does two things: resolves B7 step 0's mid-edit-vs-abandoned ambiguity toward **mid-edit by default** (finish and integrate the leftover instead of stopping to ask), and satisfies the previous bullet's push/deploy naming requirement, so the run pushes commits and tags, creates the GitHub Release, and runs post-push CI watch (B10) and deploy verification (C5) without a separate mid-run confirmation. Conditions (1) and (3) gate on irreversibility (a published-version rewrite) and correctness (a documented-design contradiction), not on effort, so no phrasing intensity waives them — a "nghiêm trọng"/major-contradiction hit still stops the run.
|
|
173
191
|
- **A question the repo already answers is a violation.** Anything determined by the repo, its docs, these rules, or the invocation itself — bump level (A4), tag or no tag (existing convention), changelog channel and tone (C1) — is self-answered, never asked — and every remaining candidate question runs through `agent.A3`'s kill-tests first. Over-asking inside an authorized run is the same failure as acting unasked (`agent.A3`, `think.B5`).
|
|
174
|
-
- **A criterion stated in the owner's own words — what "
|
|
192
|
+
- **A criterion stated in the owner's own words — what "complete" must include, which leftovers count as debt versus future plan — is resolved, not escalated by default.** Derive the default from the anchor wording plus the repo's own records (plans, task notes, CHANGELOG), decide, and list each such call in the report's `agent.A3` decision block (`Decided: X · because Y · rejected Z (why) · reopen if W`) so the owner can overrule after the fact. Ask mid-run only when the competing readings would produce different irreversible artifacts (a published tag, a minted version, a registry publish) — `agent.A3`'s escalation, not a default reflex on ambiguous wording.
|
|
175
193
|
|
|
176
194
|
### B9. Registry-published package (npm, crates.io, PyPI, …) — the registry version is the release
|
|
177
195
|
|
|
@@ -204,17 +222,20 @@ After ANY deploy or restart, in any flow (not only `/akiship`), verify FUNCTION,
|
|
|
204
222
|
### C1. Two separate channels — do not merge them
|
|
205
223
|
| File | Audience | Language | Tone |
|
|
206
224
|
|------|----------|----------|------|
|
|
207
|
-
| `CHANGELOG.md` | developer / technical | English only | Precise, may name files/symbols. Keep a Changelog
|
|
225
|
+
| `CHANGELOG.md` | developer / technical | English only | Precise, may name files/symbols. Keep a Changelog shape, closed and ordered — see below |
|
|
208
226
|
| `app/data/releases.json` | public / end user | Bilingual EN + VI if the site is multilingual (default EN); EN-only if single-language | Popular, user-friendly, benefit-first. No jargon, no file paths |
|
|
209
227
|
|
|
210
228
|
The changelog explains *what changed and why* for maintainers. The release note tells users *what they get*. Write them separately; do not paste changelog lines into the release note.
|
|
211
229
|
|
|
230
|
+
**CHANGELOG shape — one canonical form, mechanically checked (`release_lint.py`, C4).** Version heading `## [x.y.z] - YYYY-MM-DD` (`## [Unreleased]` while open), sections `### <Name>` at exactly one level below, each at most once per version, in this fixed order and from this closed vocabulary: `Added`, `Changed`, `Deprecated`, `Removed`, `Fixed`, `Security`. Sections that ship nothing are omitted, never left empty. There is no `Internal`, `Docs`, `Notes`, `Verified`, `Refactored` or any other heading: internal, tooling and docs work is `Changed`; a verification or caveat is a clause on the bullet it qualifies; a decision's reasoning lives in the bullet or in `docs/research/`. The order is the standard's, not importance-ranked: an order chosen per release is unobservable across releases and drifts the moment two people, or two sessions, write entries. Evidence: 21 of 25 audited projects had entries out of order, 9 distinct orderings within one file, 13 invented headings across the set, and this rule itself named two different orders in two sections.
|
|
231
|
+
|
|
212
232
|
`releases.json` exists **only where a public web page renders it** (the Nuxt stack's release-notes page). Tauri, CLI, and other non-web projects keep `CHANGELOG.md` only — a release-notes file nothing renders is dead data; do not create one. Where `releases.json` does not exist, every rule below that mentions it simply does not apply.
|
|
213
233
|
|
|
214
234
|
### C2. releases.json schema
|
|
215
235
|
- Single-language site: `{ version, date, title, changes: [{ type, text }] }`
|
|
216
236
|
- Multilingual site: localize the human text — `title: { en, vi }`, `changes: [{ type, text: { en, vi } }]`. Keep `version`, `date`, `type` locale-neutral. Default/fallback language is English.
|
|
217
237
|
- `type` is one of `new` | `improved` | `fixed` | `internal` (stable badge keys).
|
|
238
|
+
- `highlight: true` (optional, locale-neutral) marks the line B6's headline protocol selected — a capability gained, chosen from the primary audience's seat through the three kill-tests and the reach × delta ranking, with the verdict written in the run's receipt. It is a tier, never a filter: every change still ships as a line (C3); the page renders the highlighted line as a distinct card, because a flat list gives a bug fix and a new tool the same weight, so the reader's eye has nothing to land on and the page reads as a log, not a product moving. Shape: the highlighted line is the **first** in `changes[]`; at most one, two only when B6 step 4 allows it; never on `fixed` or `internal`; zero is correct when no candidate survives. The `title` is B6's Headline tier and names the same thing the highlight marks (one story per surface, `biz.C4`); a title that headlines something the highlight does not, or vice versa, means one of them is wrong. Write the highlighted line benefit-first with the concrete mechanism as proof (`biz.C1`, `content.B2`): what the user can now do, then how — never the file, route or component that does it. Mechanical review: `release_lint.py` emits `[HILITE]` for a version with a `new` change and no highlight — a candidate, answered by B6 step 5's per-candidate verdict lines in the gate receipt, never silently passed.
|
|
218
239
|
|
|
219
240
|
### C3. No version gaps, and no content gaps, in releases.json
|
|
220
241
|
Every version that appears in `CHANGELOG.md` MUST also appear in `releases.json` (no missing version), and every `Added`/`Changed`/`Fixed`/`Removed` section in that version's CHANGELOG entry must be represented by at least one `changes[]` line in `releases.json` (no missing content) — skipping a version, or silently dropping a whole category of its work, because it reads as "internal" or "technical" is not allowed. This page is the one place both a human visitor and a crawling/LLM bot judge whether the product is actively maintained; a version that reads as empty is worse than one that reads as unglamorous.
|
|
@@ -228,14 +249,13 @@ Every version that appears in `CHANGELOG.md` MUST also appear in `releases.json`
|
|
|
228
249
|
Never leave a gap like `1.0.5 → 1.0.7` or `0.1.0 → 0.1.3` in releases.json. A one-line entry is better than a missing version.
|
|
229
250
|
|
|
230
251
|
### C4. Sync check — required before closing a task
|
|
231
|
-
After editing `CHANGELOG.md` or `releases.json`, run:
|
|
252
|
+
After editing `CHANGELOG.md` or `releases.json`, run from the project root:
|
|
232
253
|
|
|
233
254
|
```
|
|
234
|
-
|
|
235
|
-
grep -E '^## \[' CHANGELOG.md
|
|
255
|
+
python3 ~/.claude/skills/akiflow/scripts/release_lint.py --latest .
|
|
236
256
|
```
|
|
237
257
|
|
|
238
|
-
|
|
258
|
+
Exit 0 is the pass. Verdict tags — `[ORDER]`, `[SECTION]`, `[LEVEL]` (C1 shape), `[PARITY]` (a version in one surface and not the other, C3), `[TYPE]` (a badge key outside C2) — are fixed before the task closes. `[HILITE]` is a review line (C2), answered, never auto-fixed. Without `--latest` the script sweeps every version — that is an audit run (`agent.B5`), never a per-task cost; historical entries are corrected only when a task already touches them, never as a backfill sweep.
|
|
239
259
|
|
|
240
260
|
### C5. Live production verification
|
|
241
261
|
Never trust a deployment CLI's success status alone. A web deploy is only verified when the live production URL explicitly returns the new version data.
|
package/payload/RULE-seo.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# SEO Rule — Nuxt + Cloudflare Stack
|
|
2
2
|
|
|
3
|
-
<!-- Address map: seo.A1-
|
|
3
|
+
<!-- Address map: seo.A1-6 · seo.B1-3 · seo.C1-3 (⟨Aki⟩) -->
|
|
4
4
|
|
|
5
5
|
## Scope
|
|
6
6
|
Cross-project rules for all Nuxt 4 + Cloudflare Pages sites. For project-specific keyword strategy and schema values, see the project's own `docs/ref/seo.md` or equivalent.
|
|
@@ -78,6 +78,15 @@ sitemap: {
|
|
|
78
78
|
- **Location**: `public/ogimage/[slug].jpg` or `.png`
|
|
79
79
|
- **Fallback**: if page-level image is absent, the composable falls back to the site-wide default OG image
|
|
80
80
|
- Never reference an OG image path that doesn't exist in `public/`
|
|
81
|
+
- Referenced site-relative (`/ogimage/slug.jpg`); the SEO composable makes it absolute (A6)
|
|
82
|
+
|
|
83
|
+
### A6. URL form — relative at rest, absolute only at emission
|
|
84
|
+
|
|
85
|
+
- Every same-site URL is stored and rendered root-relative (`/images/x.jpg`, `/path/`): content data, internal links, `<img src>`/`srcset`, asset references. An own-origin literal (`https://domain.com/...`) there breaks localhost and preview deploys, and hides a missing local asset behind the production copy.
|
|
86
|
+
- Consumers that require an absolute URL (`og:image`, `twitter:image`, `og:url`, canonical, hreflang, JSON-LD `url`/`image`/`logo`, sitemap, RSS, email, share text) get it from one helper at the emission boundary (SEO composable, schema builder, feed generator), never from the stored value. The helper is idempotent (relative → absolute, absolute → unchanged), so legacy absolute data keeps working without a migration.
|
|
87
|
+
- The origin comes from the one configured site URL (`pattern.A1`), never from the request host: a preview deploy must still emit production canonical and OG URLs.
|
|
88
|
+
- A subpath deploy (`baseURL` ≠ `/`) prefixes through the framework's base-URL mechanism, never by string concatenation per call site.
|
|
89
|
+
- Detected mechanically in C3.
|
|
81
90
|
|
|
82
91
|
## B. AI visibility & entity
|
|
83
92
|
|
|
@@ -91,7 +100,7 @@ These rules help content appear in AI-generated answers (Perplexity, ChatGPT, Ge
|
|
|
91
100
|
- **alternateName in Organization/WebSite schema**: include all brand spelling variants (accented + unaccented + lowercase + domain form) so AI can resolve them to a single entity
|
|
92
101
|
- **knowsAbout**: list the topics the brand covers — helps AI cite the site as a relevant source
|
|
93
102
|
|
|
94
|
-
> ⚠️ **`llms.txt` is not an AI-visibility strategy (2026).** A log study across 137,000 domains found **97% of `llms.txt` files received zero requests over a full month**; no major LLM vendor has committed to reading the format, and Google's John Mueller has compared it to the meta keywords tag — a standard proposed by publishers that no consumer agreed to honour. Keep the file if it already exists (it costs nothing and is genuinely useful for *internal* agents reading the site), but never list it as an SEO/GEO deliverable and never let it substitute for the thing that does work: getting the content into the server-rendered HTML (
|
|
103
|
+
> ⚠️ **`llms.txt` is not an AI-visibility strategy (2026).** A log study across 137,000 domains found **97% of `llms.txt` files received zero requests over a full month**; no major LLM vendor has committed to reading the format, and Google's John Mueller has compared it to the meta keywords tag — a standard proposed by publishers that no consumer agreed to honour. Keep the file if it already exists (it costs nothing and is genuinely useful for *internal* agents reading the site), but never list it as an SEO/GEO deliverable and never let it substitute for the thing that does work: getting the content into the server-rendered HTML (B3).
|
|
95
104
|
|
|
96
105
|
### B2. Entity & ecosystem linking
|
|
97
106
|
|
|
@@ -103,16 +112,7 @@ For sites that belong to a multi-site ecosystem or brand family:
|
|
|
103
112
|
|
|
104
113
|
Keep the concrete domain list (parent org URL, sibling sites) in the project's own docs — one source of truth per ecosystem, not hardcoded in shared rules.
|
|
105
114
|
|
|
106
|
-
### B3.
|
|
107
|
-
|
|
108
|
-
Google treats accented and unaccented Vietnamese as different queries (`vst là gì` ≠ `vst la gi`). To cover both without degrading UX:
|
|
109
|
-
|
|
110
|
-
- **Embed the unaccented form in parentheses** in the first mention of a term in body copy or FAQ: *"...VST (vst la gi)..."*
|
|
111
|
-
- **Or include it in** `keywords` meta or `alternateName` in schema
|
|
112
|
-
- **Never** put unaccented forms in H1, H2, visible headings, or the FAQ question text — it looks unprofessional
|
|
113
|
-
- **Meta title and description**: use correctly accented Vietnamese; unaccented coverage comes from schema + body copy
|
|
114
|
-
|
|
115
|
-
### B4. Prerendering & SSR
|
|
115
|
+
### B3. Prerendering & SSR
|
|
116
116
|
|
|
117
117
|
- SEO-critical content must be in the HTML at crawl time — not injected by client-side JS. **This is the single highest-evidence rule in this file for AI visibility**: roughly 69% of AI crawlers (GPTBot, OAI-SearchBot, ClaudeBot, Claude-SearchBot, PerplexityBot) do **not** execute JavaScript, so a client-only SPA is simply invisible to them. Googlebot and Gemini do render; ChatGPT, Claude and Perplexity do not. Prerendering beats every schema tweak combined.
|
|
118
118
|
- Public pages: prerender/SSG preferred
|
|
@@ -129,7 +129,7 @@ Every public page must call `usePageSeo()`. Canonical URL is derived automatical
|
|
|
129
129
|
usePageSeo({
|
|
130
130
|
title: 'Page Topic', // Max 60 chars total — NO brand suffix (see @nuxtjs/seo note below)
|
|
131
131
|
description: 'Action-oriented…', // Max 155 chars, unique per page
|
|
132
|
-
ogImage: '
|
|
132
|
+
ogImage: '/ogimage/slug.jpg', // optional, site-relative — the composable absolutizes it (A6)
|
|
133
133
|
ogImageAlt: 'Description of image', // optional
|
|
134
134
|
noindex: true, // optional, for admin/private pages
|
|
135
135
|
})
|
|
@@ -161,6 +161,8 @@ Run `scripts/validate-seo.js` (or equivalent) after every build. At minimum it s
|
|
|
161
161
|
- [ ] All descriptions ≤ 155 chars
|
|
162
162
|
- [ ] No em dash (`—`) or en dash (`–`) in title or description
|
|
163
163
|
- [ ] All canonical URLs end with `/`
|
|
164
|
+
- [ ] No rendered `src`, `srcset` or `<a href>` carries the site's own origin (A6); `<head>` `link`/`meta` are exempt, they are emission targets
|
|
165
|
+
- [ ] Every `og:image`, `twitter:image` and JSON-LD `image` is an absolute `https://` URL
|
|
164
166
|
- [ ] Homepage `Organization` schema has `alternateName` and `sameAs`
|
|
165
167
|
- [ ] `/admin/**` pages absent from sitemap output
|
|
166
168
|
- [ ] Skip redirect stub files (`http-equiv="refresh"`) — they have no SEO content to validate
|
|
@@ -27,6 +27,7 @@ Nuxt 4 · Vue 3 · Tailwind v4 · @nuxtjs/i18n · @nuxtjs/seo · SweetAlert2 ·
|
|
|
27
27
|
- `crypto.subtle` operates on bytes — feed it `new TextEncoder().encode(str)`, not the string.
|
|
28
28
|
- Do not enable `nodejs_compat` in `wrangler.toml` unless upstream issues are confirmed fixed
|
|
29
29
|
- Trailing slash: `trailingSlash: true` everywhere (routing, canonical, og:url, sitemap, schema.org) — canonical config lives in the i18n section below
|
|
30
|
+
- Site origin: `site.url` in `nuxt.config` is the single source, read through `useSiteConfig().url` (nuxt-site-config, bundled with `@nuxtjs/seo`) inside the one URL helper the SEO composable and schema use; never a `'https://domain'` literal in a composable, page, component or content record. URL form: `seo.A6`
|
|
30
31
|
- Never call h3's `readBody()` in a DELETE handler. On workerd, reading a body the runtime never actually attached to a DELETE request hangs the promise instead of rejecting it — the platform then kills the request as a bare 500 with no stack trace. This reproduces on real Cloudflare Pages/Workers but NOT under `nuxt dev` (Node), so it survives local testing and only surfaces in production. Pass the id (or any DELETE payload) via query string on both client and server instead — never body.
|
|
31
32
|
- After the response, keep the isolate alive with `event.context.cloudflare.context.waitUntil` (the path live notify modules use on tachnhac / vstshop / tuvi / kinhdich). Nitro 2.13 also wraps the same CF context as `event.waitUntil`. Do not copy Nitro v3 docs onto this stack: v3 puts the platform on `event.req.runtime.cloudflare`, and `event.req.waitUntil` does not exist on 2.13, so the isolate dies. `nuxt dev` cannot prove isolate lifetime. A 200 that returns before the background work is not evidence the work ran.
|
|
32
33
|
|
|
@@ -106,7 +106,7 @@ Invariant: a `:hover`-shown popup/menu/tooltip stays open while the pointer trav
|
|
|
106
106
|
|
|
107
107
|
## C. Audit playbook — cleaning existing code
|
|
108
108
|
|
|
109
|
-
**Triggers for this section:**
|
|
109
|
+
**Triggers for this section:** any request to clean up, deduplicate, audit or refactor classes, tokens or styles. Pair with `METHOD-audit-flow.md` for the flow-level mindset; this section is the concrete UI grep layer. Run the steps in order — do not skip.
|
|
110
110
|
|
|
111
111
|
### C1. Inventory by scan (quantify before refactoring by feel)
|
|
112
112
|
|
package/payload/index.md
CHANGED
|
@@ -12,13 +12,13 @@ Provides reusable rules for agent behavior, coding, content, docs, and stack-spe
|
|
|
12
12
|
| `RULE-agent-behavior.md` | `agent` | Core — `@` import in `~/.claude/CLAUDE.md` | public | Core: full text already in context every turn — read it directly, not this summary |
|
|
13
13
|
| `RULE-coding.md` | `coding` | Core — `@` import in `~/.claude/CLAUDE.md` | public | Core: full text already in context every turn — read it directly, not this summary |
|
|
14
14
|
| `RULE-pattern-core.md` | `pattern` | Core — `@` import in `~/.claude/CLAUDE.md` | public | Core: full text already in context every turn — read it directly, not this summary |
|
|
15
|
-
| `RULE-docs.md` | `docs` | Contextual | public | Docs structure (incl. mandatory `docs/biz/` backbone), `updated <date> <version>` anchor stamp on every `arch|biz|feat` doc, plan lifecycle, research doc schema (event record: start time/purpose/strategy/checklist/result+verification/decision+cross-refs; frozen body, dated `## Amendments` for errata, successor doc only when the Decision changes), doc-sync behavior, drift audit (when it runs vs the two situations it does not, research+plan doc pair, comparison checklist, wrong/stale/incomplete/cosmetic severity) |
|
|
16
|
-
| `RULE-content-write.md` | `content` | Contextual | public | UI copy, semantic stability, writing style (density enforced by deletion test), i18n, content audit (`content.C2` — canonical-term drift, density deletion test, i18n coverage, severity-classified per `docs.C4`) |
|
|
15
|
+
| `RULE-docs.md` | `docs` | Contextual | public | Docs structure (incl. mandatory `docs/biz/` backbone), `updated <date> <version>` anchor stamp on every `arch|biz|feat` doc and every `ref/fact-*`, fact docs (`A6`: three claim classes — biz decided by the owner, decision reached by research, fact held only by evidence; every fact carries its source with read date or pinned artifact version plus the research section that verified it, changes only through a research event, `content.C2` reads it first), the auto-loaded instruction file's five-test admission bar (`A5`: nameable harm, majority-of-requests reach, not derivable, not a restatement, facts not behavior), plan lifecycle, research doc schema (event record: start time/purpose/strategy/checklist/result+verification/decision+cross-refs; frozen body, dated `## Amendments` for errata, successor doc only when the Decision changes), doc-sync behavior, drift audit (when it runs vs the two situations it does not, research+plan doc pair, comparison checklist, wrong/stale/incomplete/cosmetic severity) |
|
|
16
|
+
| `RULE-content-write.md` | `content` | Contextual | public | UI copy, semantic stability, writing style (density enforced by deletion test), i18n, content audit (`content.C2` — canonical-term drift, density deletion test, i18n coverage, fact-check of product claims against the product's own repo or live page, severity-classified per `docs.C4`) |
|
|
17
17
|
| `RULE-stack-akiNuxtCf.md` | `stack` | Contextual | **mixed** — group C is ⟨Aki⟩ | Nuxt/Vue/Cloudflare Pages/Workers, Tailwind, i18n, canonical component names, state (useState-first), build & TypeScript, admin layout isolation, dev workflow scripts (killport/D1), layout chrome (breadcrumb/scroll-to-top), layout width (single source of truth in the layout, pages/apps never redeclare max-w), deploy verification after push |
|
|
18
18
|
| `RULE-stack-tauri.md` | `tauri` | Contextual | public | Tauri v2 + Rust: absolute never-block-the-UI rule for any command running a subprocess/network call (`spawn_blocking`), titlebar boundary, version SSOT, IPC capability silent-fail, serde default for persisted JSON, cfg(target_os) scoping, subprocess PATH-resolution cold-start race, salient target context (ship platform) surfaced in the project CLAUDE.md, macOS TCC/Gatekeeper boundary for spawned sidecars (responsible-process attribution, FDA vs Files & Folders vs Developer Tools, sticky denials, ad-hoc signing losing grants on every rebuild, and the read-only scope limit of the whole chain) |
|
|
19
19
|
| `RULE-ui-pattern.md` | `ui` | Contextual | public | Frontend enforcement of pattern-core: subtraction pass before any tier (delete/inherit/hoist — the ladder packages repetition, only this removes it), 4-tier class taxonomy with the second copy as the STOP (the ≥3 threshold is repo-wide and unobservable inside one file), inline `style=` as a runtime-only escape hatch, `<style>`-block budget measured in aggregate against the shared layer, design tokens in whichever mechanism the installed framework version uses with one theme source per project, arbitrary-value policy, atomic structure, variant API, two-way lookup-then-record pattern duty, UI audit/refactor playbook led by the inversion check |
|
|
20
|
-
| `RULE-seo.md` | `seo` | Contextual | **mixed** — group C is ⟨Aki⟩ | Meta limits, schema.org matrix, robots, sitemap, OG, AI visibility, entity linking |
|
|
21
|
-
| `RULE-release.md` | `release` | Contextual | **mixed** — group C is ⟨Aki⟩ | CHANGELOG.md mandatory in every project, release notes vs changelog split, GitHub Release compare-link footer, releases.json (web-only), release vs deploy boundary, cold-start version reconstruction, severity-driven bump, version minted only at the release event (`[Unreleased]` buffer, no local drift ahead of production), audit mode, pre-ship gate expanded into the full-release checklist (B7: leftover triage, diff-scoped hygiene, build & test mirroring CI as a mandatory step before verification honesty and the version decision), autonomous-run contract (B8: an explicit release order is the authorization — activation owned by akiship's own gate, this rule is never itself a trigger; asks front-loaded into one batch, three-case escalation floor and completion-intensity
|
|
20
|
+
| `RULE-seo.md` | `seo` | Contextual | **mixed** — group C is ⟨Aki⟩ | Meta limits, schema.org matrix, robots, sitemap, OG, URL form (relative at rest, absolute only at the emission boundary), AI visibility, entity linking |
|
|
21
|
+
| `RULE-release.md` | `release` | Contextual | **mixed** — group C is ⟨Aki⟩ | CHANGELOG.md mandatory in every project, release notes vs changelog split, one canonical CHANGELOG shape (C1: fixed Keep a Changelog section order and closed vocabulary, no invented headings), `releases.json` highlight tier (C2: the one line a returning user came back for, first in the list, at most two, never a fix), both mechanically checked by `release_lint.py` (C4, wired into the B7 gate), release copy as one text in three lengths with an announce verdict, the last block of every `/akiship` run and the single source for the GitHub Release title/body and the `releases.json` title (B6), GitHub Release compare-link footer, releases.json (web-only), release vs deploy boundary, cold-start version reconstruction, severity-driven bump, version minted only at the release event (`[Unreleased]` buffer, no local drift ahead of production), audit mode, pre-ship gate expanded into the full-release checklist (B7: leftover triage, diff-scoped hygiene, build & test mirroring CI as a mandatory step before verification honesty and the version decision), autonomous-run contract (B8: an explicit release order is the authorization — activation owned by akiship's own gate, this rule is never itself a trigger; asks front-loaded into one batch, three-case escalation floor and completion-intensity definition owned solely by B8, owner-worded criteria decided and reported rather than escalated by default; entry point `/akiship`), registry-published packages (B9: the registry version is the release, publish mechanism derived from existing convention and sibling packages, account/scope/2FA probed, OTP publish as the single hand-off, tarball verified before the irreversible publish), post-push CI watch (B10: a push or tag push is not Done until every triggered workflow is green, red fixed forward with a new commit never a history rewrite), migration doctrine (B5: detect by effect including startup-embedded code, separate artifact, expand → migrate → deploy → contract, rehearse from the PREVIOUS state never from empty, postconditions + named rollback), fail-closed gate contract (B7: a receipt line per step or the step was NOT RUN, self-interrogation reported, forbidden evidence words), post-deploy functional verification (B11: a version string proves code not function, a constant-`ok` health endpoint is a false instrument) |
|
|
22
22
|
| `RULE-db-design.md` | `db` | Contextual | public | Immutability & Event Sourcing, 1NF, Bounded Context (DDD), flat-query discipline — load when designing schema/migration/DB refactor |
|
|
23
23
|
| `RULE-biz.md` | `biz` | Contextual | public | Positioning & audience (one primary audience, falsifiable USP, `docs/biz/` as SSoT, niche-first), offer & pricing (value-based, few tiers, validate before building), messaging & customer psychology (benefit-first, anxiety at decision points, no dark patterns) — load on any market-facing decision |
|
|
24
24
|
| `METHOD-audit-flow.md` | `flow` | Analytical | public | Flow integrity audit method |
|
|
@@ -27,14 +27,15 @@ Provides reusable rules for agent behavior, coding, content, docs, and stack-spe
|
|
|
27
27
|
| `METHOD-audit-zero-trust.md` | `zero-trust` | Analytical | public | Strict mechanical-first audit: scope locked by command (project-wide or change-plus-callers), detectors run before any opinion, findings split into CERTAIN (exact machine match — a verdict) vs SUGGESTED (pattern/naming — a candidate judgment must settle), signature propagation across the locked scope, short findings-only report. Read-only like every audit |
|
|
28
28
|
| `METHOD-proportionality.md` | `proportion` | Analytical | public | Sizing a defense against its real threat: four measures before any verdict (reach against the `docs/biz/` audience, capability ladder, motive, blast radius by recoverability), every number labeled measured or estimated; asymmetry law (irreversibility outranks frequency), the `coding.C4`/`biz.C3` floor that is never sizeable, the cheapest-sufficient-control ladder (impossible by shape → one trust boundary → detect → accept-and-record) with client-side limits classified as UX and never enforcement; verdict record carries a reopen trigger. Seated in akiflow as `risk-sizing` |
|
|
29
29
|
| `METHOD-audit-subtraction.md` | `subtract` | Analytical | public | Repo-wide "does this need to exist" sweep: inherits zero-trust's scope-lock, detector-first order, CERTAIN/SUGGESTED classes and signature propagation, changes only the question. Loop-until-dry termination (two empty rounds) because no detector returns "minimal", nine domain passes each delegating detectors to the rule that owns them, subtraction severity classes including the mandatory *load-bearing but ugly* class, Chesterton's Fence as the brake before any CERTAIN removal. Read-only; bulk sweeps route to workers, judgment does not |
|
|
30
|
+
| `METHOD-audit-frozen-reference.md` | `frozen-ref` | Analytical | public | Compliance audit for a clause that names a concrete external artifact as the canonical shape to match (a pinned version, a byte-identical component, a frozen page layout) — never judged from memory of the rule's prose. Resolve the reference to an exact path before judging (§A); when more than one reference implementation exists, read at least two, since a single reference can itself have drifted from the written rule; a comparison table is built one literal unit per row (a function name, a config key, a flow step) with `path:line` on both sides and a verdict of MATCH/RENAMED/MISSING/EXTRA/STRUCTURAL-DIFF — never softened to "acceptable variance" inside the audit (§B); standard-vs-practice gaps (the rule and its own reference disagree) are reported separately from target defects (§C). Inherits zero-trust's evidence discipline and read-only floor |
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
Five files load mechanically, not by routing: this `index.md`, `RULE-agent-behavior.md`, `RULE-coding.md`, `RULE-pattern-core.md` and the router `~/.claude/skills/akirule/SKILL.md` are `@`-imported by `~/.claude/CLAUDE.md`, which the harness reads at session start. Every other file is routed by meaning — the domain a task touches, with concept signals as evidence, never as the test — and enters context when the model `Read`s it on a route match.
|
|
32
33
|
|
|
33
|
-
The two rule files were promoted out of
|
|
34
|
+
The two rule files were promoted out of the router because "default ON" was a description of intent, not a mechanism: a router that runs only when the model chooses to invoke it cannot guarantee anything, and the rules the owner had to re-state most often (`coding.B4` comment budget, `pattern.A2` Rule of Three, `pattern.A8` fix-at-the-root) turned out to be missing from the context rather than present and ignored. A rule that must hold unconditionally belongs in an `@` import. The router followed for the same reason: as a skill it went uninvoked until the owner asked by name, so the routing itself is now imported and only the second hop — the `Read` of a routed file — stays model-dependent. The cost — both files in every session, including sessions that touch no code — is the price of that guarantee and was accepted knowingly.
|
|
34
35
|
|
|
35
36
|
## Addressing scheme — `topic.A1`
|
|
36
37
|
|
|
37
|
-
Every file is internally organized into groups **A/B/C** (a topic's broad themes) and numbered items **1/2/3…** within each group — e.g. `coding.B2` (Changing existing code), `stack.C1` (Canonical component names). `topic` is the manifest's Topic column above — usually the filename with its `RULE-`/`METHOD-` prefix dropped; the audit methods keep their short topics (`flow`, `zero-trust`, `subtract`). This is purely a recall/reference convention — it does not change routing (still governed by `akirule/SKILL.md`) and does not rename any file.
|
|
38
|
+
Every file is internally organized into groups **A/B/C** (a topic's broad themes) and numbered items **1/2/3…** within each group — e.g. `coding.B2` (Changing existing code), `stack.C1` (Canonical component names). `topic` is the manifest's Topic column above — usually the filename with its `RULE-`/`METHOD-` prefix dropped; the audit methods keep their short topics (`flow`, `zero-trust`, `subtract`, `frozen-ref`). This is purely a recall/reference convention — it does not change routing (still governed by `akirule/SKILL.md`) and does not rename any file.
|
|
38
39
|
|
|
39
40
|
**`⟨Aki⟩`** marks a group (always the last group in its file) that is specific to Aki's own AkiNuxtCf ecosystem rather than universal — currently `seo.C`, `release.C`, `stack.C`. These groups stay in this public repo (auto-load is more useful to Aki, the heaviest user, than a clean public/ private split), but are logically separable if a stripped public export is ever needed. Everything outside a `⟨Aki⟩` group is universal and applies to any project on the matching stack.
|
|
40
41
|
|
|
@@ -58,6 +59,7 @@ Every file is internally organized into groups **A/B/C** (a topic's broad themes
|
|
|
58
59
|
| `zero-trust` | A Scope-lock · B Mechanical pass first · C Evidence classes · D Signature propagation · E Adversarial self-challenge · F Report |
|
|
59
60
|
| `proportion` | A Dimensioning · B Verdict · C Output & reuse |
|
|
60
61
|
| `subtract` | A Scope & terminating condition · B The passes · C Output · D Runner |
|
|
62
|
+
| `frozen-ref` | A Locate every reference · B Build the comparison mechanically · C Report |
|
|
61
63
|
|
|
62
64
|
Full item-level breakdown: `docs/research/public-private-abc-restructure.md`.
|
|
63
65
|
|
|
@@ -69,13 +71,15 @@ Some subjects legitimately live in several files: one **root rule** stating the
|
|
|
69
71
|
|---|---|---|
|
|
70
72
|
| **Naming** | `pattern.A7` — name by role, never by concrete value | `agent.C1` file names · `ui.A` design tokens · `stack.C1` ⟨Aki⟩ canonical component names · `release.A3` version/tag format · `content.A3` semantic stability (renaming an existing concept) |
|
|
71
73
|
| **External-action completeness** ("done" needs the outside world to move, not just the file) | `coding.B3` — a change requiring a separate action against an external system isn't done when the file describing it is written | `release.B5` ⟨Aki⟩ CHANGELOG/release entry not truthful until a migration/infra step actually ran · `stack.C8` ⟨Aki⟩ D1 migration must run `--remote` and move to `scripts/done/`, a green build alone proves nothing about the database · `release.B10` a push or tag push is not Done until every triggered CI workflow is confirmed green · `release.B11` a deploy is not Done until a data path the release touched is exercised, not only the version |
|
|
72
|
-
| **Audit reports, never fixes** (and the output depends on whether the baseline is stable) | `agent.B5` — an audit writes only its report; never mutates git state, never auto-classifies ambiguous work | `docs.C` docs-vs-reality, research+plan doc pair on a published baseline · `content.C2` canonical-term drift, density deletion test, i18n coverage sweeps · `release.B7` pre-ship pass/fail gate, no doc · `ui.C` class/token audit playbook · `flow` flow and state drift · `zero-trust` mechanical-first strict sweep, evidence weighted by the mechanism that produced it · `subtract` repo-wide does-this-need-to-exist sweep, terminating on two dry rounds |
|
|
74
|
+
| **Audit reports, never fixes** (and the output depends on whether the baseline is stable) | `agent.B5` — an audit writes only its report; never mutates git state, never auto-classifies ambiguous work | `docs.C` docs-vs-reality, research+plan doc pair on a published baseline · `content.C2` canonical-term drift, density deletion test, i18n coverage, fact-check sweeps · `release.B7` pre-ship pass/fail gate, no doc · `ui.C` class/token audit playbook · `flow` flow and state drift · `zero-trust` mechanical-first strict sweep, evidence weighted by the mechanism that produced it · `subtract` repo-wide does-this-need-to-exist sweep, terminating on two dry rounds |
|
|
73
75
|
| **Sizing a control against its real threat** (severity is impact **and** who can actually reach it) | `proportion.A` — reach, capability, motive, blast radius, each labeled measured or estimated, before any guard is added, kept, or removed | `coding.C1` no defensive guards for impossible internal states · `coding.C4` the security floor this sizing never argues below · `pattern.A2` risk-weighted extraction at the 2nd occurrence for auth/money/permissions · `think.A1` one-way vs two-way door depth · `think.B5` when an edge-case is promoted above the MVP · `ux.C1` findings ranked by severity, never padded flat |
|
|
74
|
-
| **Density — the deletion test** (a line exists only if deleting it loses information the reader needs) | `agent.A4` — report density: conclusion-first, no padding, no trimming of load-bearing detail | `coding.B4` code comments (naming first; comment only what code cannot say) · `docs.B3` doc prose · `content.B2` product copy · akiflow Step 4 output-hygiene floor (the enforcement tier for subagents, which inherit no router) · mechanical detection: `skills/akiflow/scripts/scythe.py` (`[WRAP]`/`[YAP]` only — `[FLUFF]` stays judgment, `agent` §0) |
|
|
76
|
+
| **Density — the deletion test** (a line exists only if deleting it loses information the reader needs) | `agent.A4` — report density: conclusion-first, no padding, no trimming of load-bearing detail | `coding.B4` code comments (naming first; comment only what code cannot say) · `docs.B3` doc prose · `docs.A5` the auto-loaded instruction file (deletion test plus a majority-of-requests reach bar, since every line is paid on every request) · `content.B2` product copy · akiflow Step 4 output-hygiene floor (the enforcement tier for subagents, which inherit no router) · mechanical detection: `skills/akiflow/scripts/scythe.py` (`[WRAP]`/`[YAP]` only — `[FLUFF]` stays judgment, `agent` §0) |
|
|
75
77
|
| **Subtraction before abstraction** (packaging repetition is second-best; not needing it is first) | `think.B4` — what can be deleted, skipped, merged, delayed, or made manual | `pattern.B3` first bullet of the critique gate · `ui.A1` delete/inherit/hoist pass ahead of the tier ladder · `subtract` the repo-wide audit form of the same question, read-only and detector-driven · akiflow's `aki-challenger`, which closes every solution-shaped item on "what can be cut?" |
|
|
76
78
|
| **Interrupting the owner** (a question must survive the kill-tests before it costs a read and an answer) | `agent.A3` — impact, already-authorized, silence≠contradiction, reversibility as the fourth, plus escalation outcomes and a `Decided: X · because Y · rejected Z (why) · reopen if W` decision block for what does get self-answered | `coding.B3` one human hand-off ledger per run, deduped by flow · `coding.B5` the six-rung ladder a check must fail before it may be handed to the owner at all, and the one-line reason each survivor carries · `release.B8` a question the repo already answers is a violation; owner-worded criteria decided and reported, escalated only when readings diverge on an irreversible artifact · akiflow Step 4 seat-raised `CONFLICT` filtered through the lead's kill-test pass |
|
|
77
79
|
|
|
78
|
-
|
|
80
|
+
| **Literal parity with a frozen external artifact** (a clause claiming byte/structural identity with a named reference is not satisfied by "looks compliant") | `frozen-ref.A` — resolve the reference to an exact path before judging; read ≥2 implementations when more than one exists, since one reference can itself have drifted | `stack.C1` ⟨Aki⟩ canonical component names — the naming half of this problem · `pattern.A7` root naming rule that frozen-reference structural clauses do not override · `zero-trust.C` evidence-class discipline this method inherits |
|
|
81
|
+
|
|
82
|
+
Add a lens row only when a subject has actually caused a miss — `pattern.A2` (Rule of Three) applies to this rule corpus too, and so did a real production incident where a migration script shipped in CHANGELOG but was never executed against remote D1 (2026-07-23). The `frozen-ref` row above was added after a private project spent over ten audit sessions re-discovering the same structural drift against its own frozen reference each time, because compliance was judged from the rule's prose instead of a literal diff against the reference file.
|
|
79
83
|
|
|
80
84
|
## Precedence
|
|
81
85
|
When rules conflict, use this order:
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: aki-article-writer
|
|
3
3
|
description: >-
|
|
4
|
-
Per-project
|
|
5
|
-
JSON-LD
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
Per-project pipeline for publishable site prose: research & fact-verification, SEO
|
|
5
|
+
metadata, JSON-LD, UX-psychology-aware content, a rendered-output pass, and a
|
|
6
|
+
separate Image Scout subagent for images. Activate whenever the task writes,
|
|
7
|
+
rewrites, translates or reviews an article, news or blog post, announcement or
|
|
8
|
+
knowledge entry for any site, in any wording or language, including turning a
|
|
9
|
+
release, changelog or finding into a post.
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
# aki-article-writer
|
|
13
13
|
|
|
14
|
-
Invoke with `/aki-article-writer
|
|
14
|
+
Invoke with `/aki-article-writer`, or whenever a task writes, rewrites, translates or reviews publishable site prose (article, news/blog post, announcement, knowledge entry), in any wording — including a release or finding to be turned into a post.
|
|
15
15
|
|
|
16
16
|
This skill delegates one full article to a dedicated **Article Worker subagent**. The worker spawns a separate **Image Scout subagent** (lightweight model) for all image work, keeping both agents' contexts clean and independent.
|
|
17
17
|
|
|
@@ -92,7 +92,7 @@ Select the correct schema type based on project:
|
|
|
92
92
|
|
|
93
93
|
### 2.5 URL canonical & trailing slash
|
|
94
94
|
|
|
95
|
-
All canonical URLs, sitemap entries, `og:url`, internal links, and JSON-LD `url` fields must end with `/`. Required for Cloudflare Pages compatibility.
|
|
95
|
+
All canonical URLs, sitemap entries, `og:url`, internal links, and JSON-LD `url` fields must end with `/`. Required for Cloudflare Pages compatibility. Those absolute URLs are produced at emission; every URL stored in the record or rendered in the body is site-relative (`seo.A6`).
|
|
96
96
|
|
|
97
97
|
---
|
|
98
98
|
|
|
@@ -130,16 +130,7 @@ Example:
|
|
|
130
130
|
|
|
131
131
|
Use exactly one canonical term for each concept throughout the article. Synonym variation may seem stylistically rich but confuses both readers and AI crawlers. Pick the term, define it once, use it consistently.
|
|
132
132
|
|
|
133
|
-
### 3.4
|
|
134
|
-
|
|
135
|
-
Google treats `vst là gì` and `vst la gi` as different queries. To cover both without degrading readability:
|
|
136
|
-
|
|
137
|
-
- Embed the unaccented form in parentheses at its **first occurrence** in body copy or FAQ: `…VST (vst la gi) là loại phần mềm…`
|
|
138
|
-
- Or place it in `keywords` meta or `alternateName` in schema
|
|
139
|
-
|
|
140
|
-
**Never** place unaccented forms in H1, H2, H3, or FAQ question text — it degrades the visual quality of the interface.
|
|
141
|
-
|
|
142
|
-
### 3.5 Anxiety handling at CTA
|
|
133
|
+
### 3.4 Anxiety handling at CTA
|
|
143
134
|
|
|
144
135
|
At every call-to-action point (sign-up, purchase, download, consult), identify the dominant user anxiety at that moment and answer it right there — not on a distant FAQ page:
|
|
145
136
|
|
|
@@ -150,12 +141,12 @@ At every call-to-action point (sign-up, purchase, download, consult), identify t
|
|
|
150
141
|
| Compatibility | "Hỗ trợ Win/Mac, tương thích mọi DAW phổ biến" |
|
|
151
142
|
| Privacy | "Không lưu dữ liệu cá nhân, xoá tài khoản bất cứ lúc nào" |
|
|
152
143
|
|
|
153
|
-
### 3.
|
|
144
|
+
### 3.5 Internal & external links
|
|
154
145
|
|
|
155
146
|
- **Internal links:** ≥ 2 links to related articles or service pages within the same project
|
|
156
147
|
- **External links:** link to authoritative sources when citing data; add `rel="noopener"`
|
|
157
148
|
|
|
158
|
-
### 3.
|
|
149
|
+
### 3.6 SSR / prerender requirement
|
|
159
150
|
|
|
160
151
|
69% of AI crawlers (ChatGPT, ClaudeBot, PerplexityBot, OAI-SearchBot) do not execute JavaScript. All article content, meta tags, and schema must be present in the server-rendered HTML at crawl time — never client-side only.
|
|
161
152
|
|
|
@@ -331,7 +322,7 @@ Embed with markdown image syntax directly in body content:
|
|
|
331
322
|
### `article_arch: component` (single-image mode)
|
|
332
323
|
|
|
333
324
|
Do **not** write `![]()` anywhere in body/paragraph fields — the renderer does not parse it and will print the literal text. Instead:
|
|
334
|
-
1. Set the content record's existing share-image field (e.g. `ogImage`) to `/images/articles/<slug>.<ext
|
|
325
|
+
1. Set the content record's existing share-image field (e.g. `ogImage`) to `/images/articles/<slug>.<ext>`, site-relative. Confirm the SEO composable and schema make it absolute (`seo.A6`); if either passes it through raw, fix that helper once — never write the site origin into the record, even when neighbouring records do.
|
|
335
326
|
2. Confirm the shared render component (e.g. `ContentArticle.vue`) already displays that field as a hero image above the body. If it does not yet, that is a one-time component change to flag to the user — do not route around it with markdown text in a paragraph.
|
|
336
327
|
3. No separate body images in this mode; one image serves as both hero and OG/share image.
|
|
337
328
|
|
|
@@ -354,7 +345,6 @@ Article Worker runs through the full checklist before reporting completion.
|
|
|
354
345
|
- [ ] No `UNVERIFIED` claim appears in the published text
|
|
355
346
|
- [ ] No banned openers in any paragraph or FAQ answer
|
|
356
347
|
- [ ] No paragraph exceeds 5 lines
|
|
357
|
-
- [ ] Unaccented Vietnamese keyword embedded in parentheses at first occurrence in body / FAQ (vi locale only)
|
|
358
348
|
- [ ] CTA point has an anxiety-answering line
|
|
359
349
|
- [ ] ≥ 1 H2 is a direct question (ends with `?`)
|
|
360
350
|
- [ ] ≥ 2 internal links
|
|
@@ -370,6 +360,12 @@ Article Worker runs through the full checklist before reporting completion.
|
|
|
370
360
|
### 6.4 SSR / prerender
|
|
371
361
|
- [ ] Article content, meta tags, and schema are present in server-rendered HTML, not deferred to client-side JS
|
|
372
362
|
|
|
363
|
+
### 6.5 Rendered-output pass
|
|
364
|
+
Source strings, a green build and a passing SEO validator do not show what the reader sees. After the build, open the article's built HTML (every locale) — the build is self-authorized, no dev server needed (`coding.B3`):
|
|
365
|
+
- [ ] `grep` the page body: no `src`, `srcset` or `<a href>` carries the site's own origin; `og:image` and JSON-LD `image` are absolute (`seo.C3`)
|
|
366
|
+
- [ ] Read the full rendered text top to bottom as the target reader, in each locale, and fix anything that reader would stumble on — before reporting the article done
|
|
367
|
+
- [ ] Any SEO device visible in the text (a parenthetical keyword variant, a repeated keyphrase) is listed as a review line, never shipped silently
|
|
368
|
+
|
|
373
369
|
---
|
|
374
370
|
|
|
375
371
|
## Antigravity / AGY note
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: akidevsync-notes
|
|
3
|
-
description: Read and edit a project's `.akidevsync/notes.json` task/note file — the per-project task list written by the Aki-Dev-Sync app (github.com/lacvietanh/aki-dev-sync). Use
|
|
3
|
+
description: Read and edit a project's `.akidevsync/notes.json` task/note file — the per-project task list written by the Aki-Dev-Sync app (github.com/lacvietanh/aki-dev-sync). Use whenever the task reads or changes that project task list — listing, adding, pinning, completing, editing or deleting a task or note, in any wording. Also use when asked to cross-check pinned/open notes against what a release actually shipped (CHANGELOG, code) before marking them done.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# akidevsync-notes — edit a project's Aki-Dev-Sync task file safely
|
|
@@ -8,6 +8,8 @@ The skill's rules are consequences of these facts. If a fact changes, the rule i
|
|
|
8
8
|
|
|
9
9
|
Every entry carries the date it was checked, because all of it is version-bound and expected to rot.
|
|
10
10
|
|
|
11
|
+
**CRITICAL — this file is facts, not policy: update it the moment a provider ships or retires a model, changes a flag, or a probe contradicts an entry.** Every model id below is a live default that other files quote by reference (`agent.A5`, `claude/agents/aki-hands.md`, `docs/arch/akiflow.md`); a stale id here is silently wrong everywhere. Never ask the owner whether to record an observed fact — record it, date it, and re-point the default when a newer tier of the same family is present (`agy models`, `claude --help`, `kiro-cli`).
|
|
12
|
+
|
|
11
13
|
## Worker invocation quick-facts
|
|
12
14
|
|
|
13
15
|
The lookup table: literal command, read-only mechanism, and the one silent failure each lane hides. Every section below this one is the *why* — a caller assigning a lane needs none of it.
|
|
@@ -16,7 +18,7 @@ The lookup table: literal command, read-only mechanism, and the one silent failu
|
|
|
16
18
|
|
|
17
19
|
| Lane | Literal command | Read-only by | Silent failure to check |
|
|
18
20
|
|---|---|---|---|
|
|
19
|
-
| **agy flash** — discovery default | `agy --model gemini-3.
|
|
21
|
+
| **agy flash** — discovery default | `agy --model gemini-3.8-flash-high --mode plan --output-format json -p "<prompt>"` | `--mode plan` (mechanism, not wording) | a denied call still returns `status: "SUCCESS"` with empty `response`; `-p` takes the next token as its value, so any flag written after it is sent as prompt text |
|
|
20
22
|
| **kiro-cli** | `kiro-cli chat --no-interactive --trust-tools=fs_read --model claude-sonnet-4.5 --effort high "<prompt>"` | `--trust-tools=fs_read` | none recorded; `--effort` is operative on every Kiro model, unlike `claude` + haiku |
|
|
21
23
|
| **claude via proxy gateway** | `CLAUDE_CONFIG_DIR=~/.claude-9rt claude -p --tools "Read,Grep" --model <alias> --effort low "<prompt>"` | `--tools` allowlist | `cl-9rt` is a shell alias and does not exist in a non-interactive shell — run the expanded literal; the gateway may route an alias to a non-Anthropic core |
|
|
22
24
|
| **claude in-harness subagent** | Agent tool, `model` passed explicitly | the agent file's `tools:` frontmatter | an omitted `model` inherits the caller's top tier; the Agent tool has no `effort` parameter at all, so a declared effort is decorative |
|
|
@@ -61,10 +63,10 @@ Probe exactly two things, once, at the moment of assigning a lane: liveness/quot
|
|
|
61
63
|
|
|
62
64
|
## Cross-CLI worker (Claude Code lead → agy headless)
|
|
63
65
|
|
|
64
|
-
Verified by real runs, 2026-08-01; model re-probed 2026-
|
|
66
|
+
Verified by real runs, 2026-08-01; model re-probed 2026-09-26. Invocation, flag order load-bearing:
|
|
65
67
|
|
|
66
68
|
```
|
|
67
|
-
agy --model gemini-3.
|
|
69
|
+
agy --model gemini-3.8-flash-high --mode plan --output-format json -p "<prompt>"
|
|
68
70
|
```
|
|
69
71
|
|
|
70
72
|
| Fact | Design consequence |
|
|
@@ -77,7 +79,7 @@ agy --model gemini-3.7-flash-high --mode plan --output-format json -p "<prompt>"
|
|
|
77
79
|
| **[obs]** Measured on a real read-only repo sweep: 8.2s wall / 3.4s model time, correct answer; ~20–26k tokens of fixed input overhead per call (agy's system prompt plus `~/.gemini/GEMINI.md`). *A prior version of this row cited one observed `cache_read_tokens: 32621` as evidence that repeat calls hit a warm cache. That reading was too generous — see § Stateful workers, where a controlled three-turn test shows the cache is unreliable and the latency curve is the real constraint.* | The fixed overhead means this mechanism pays for itself on a non-trivial sweep, not on a one-line lookup — the same shape as the "self-contained question" cutoff already in the Step 2 mechanism table. |
|
|
78
80
|
| **[obs]** The `json`/`stream-json` output carries `usage`: `input_tokens`, `output_tokens`, `thinking_tokens`, `cache_read_tokens`, plus `conversation_id`. | Cost is measurable per call — but it is invisible to `council_cost.py`, which only parses the Claude Code session transcript. The lead must add it to the close-out tally by hand (`SKILL.md` Step 6). |
|
|
79
81
|
| **[obs]** agy 1.1.9 expands skills in print mode, so `agy -p "/akiflow …"` resolves the skill; `akiflow` is already deployed to agy at `~/.gemini/config/skills/akiflow`. | A cross-CLI call can invoke the skill itself, not just an ad hoc prompt — relevant if a future revision routes part of a run through agy directly. |
|
|
80
|
-
| **[owner]** + **[obs]** Model choice inside agy is not free-form. **`gemini-3.
|
|
82
|
+
| **[owner]** + **[obs]** Model choice inside agy is not free-form. **`gemini-3.8-flash-high` is the default discovery tier** (owner rule, 2026-08-15: the newest Flash `-high` tier is the default; re-pointed 2026-09-26 when `gemini-3.8-flash-*` shipped — see § agy headless below for the full re-probed model list): ~1M context, generous quota. Its weakness is carelessness, not capacity — it skims. The counter is prompt precision, not a bigger model: name the exact paths, the exact question, and the exact output shape, leaving it nothing to improvise. **`claude-sonnet-4-6` / `claude-opus-4-6-thinking` inside agy are quota-scarce even on a Pro plan** (owner-reported) and additionally sit on the no-cache resume curve above. | Route discovery to `gemini-3.8-flash-high` by default and hand it a fully-specified task. Reach for agy's Claude tiers only for a single-shot, self-contained, high-value call where context and cache are demonstrably under control — never for a conversation, never as a habit. When strong-model judgment is needed *and* stateful, that is a Claude session id, not agy. |
|
|
81
83
|
| **[obs]** A flash-tier worker (`gemini-*-flash-*`, any generation) is for **retrieval, never for judgment**. | akiflow's thinking floor turns on the FACT/CONSTRAINT/ASSUMPTION distinction, which the skill already names as the one unrecoverable error to mislabel — exactly what a cheap model does worst. Hard rule wherever this mechanism is used, in the same voice as the existing "never downgrade implementation to save cost": retrieval only. |
|
|
82
84
|
|
|
83
85
|
## Cost model
|
|
@@ -110,7 +112,7 @@ Skills are deployed unmodified to five hosts, and **[doc]** Cursor additionally
|
|
|
110
112
|
|---|---|---|---|---|---|
|
|
111
113
|
| Claude Code | `haiku` (no `--effort`) | `sonnet` | `opus`, or `inherit` from the lead | agent frontmatter `model:`; Agent tool `model`; `claude -p --model <alias> --effort <e>` | **[obs]** verified across this file |
|
|
112
114
|
| Cursor (IDE + `agent` CLI) | Composer family — the current id from Cursor's model picker (`composer-2`-style), never an API-pool Claude/GPT model | `inherit` (the session's model) | `inherit`, or the session's top API-pool model | `.cursor/agents/*.md` or `~/.claude/agents/*.md` frontmatter `model: inherit \| <id>[effort=…]`; `agent -p --model <id>` | **[doc]** field and syntax; **UNCONFIRMED** how Cursor treats a Claude alias (`haiku`) it cannot resolve — reopen trigger: one measured Cursor run of an `aki-hands` spawn |
|
|
113
|
-
| Antigravity `agy` | `gemini-3.
|
|
115
|
+
| Antigravity `agy` | `gemini-3.8-flash-high` (§ Cross-CLI worker) | `gemini-3.1-pro-low` | `gemini-3.1-pro-high` (`claude-opus-4-6-thinking` is quota-scarce, § agy headless) | `agy --model <slug>` — effort is inside the slug; agy 1.1.6+ agent markdown carries `model` | **[obs]** 2026-09-26 |
|
|
114
116
|
| Codex CLI | UNCONFIRMED low-cost alias | `[agents] default_subagent_model` in `config.toml`; `codex exec -c model=<id> -c model_reasoning_effort=medium` | same model, `model_reasoning_effort=xhigh` | `config.toml [agents]`, per-agent `model`; `codex exec -c …` | **[doc, secondary]** 2026-09-08, model ids drift monthly — read them from `codex` itself |
|
|
115
117
|
| Kiro CLI | `qwen3-coder-next` (0.05×) or `claude-haiku-4.5` (0.4×) | `auto` (1×) or `claude-sonnet-4.5` (1.3×) | Opus-class, ~22× — rarely worth it on this host | `kiro-cli chat --no-interactive --model <id> --effort <e>`; custom agent JSON `model` | **[obs]** 2026-08-02 list; multipliers re-read with `--list-models` |
|
|
116
118
|
| Grok CLI | UNCONFIRMED | `grok-build-0.1` default | UNCONFIRMED | `grok -p` (`--model` flag unconfirmed) | **[doc, secondary]** 2026-09-08 |
|
|
@@ -150,7 +152,7 @@ Full narrative and the measurements behind these rows: `docs/research/headless-c
|
|
|
150
152
|
|
|
151
153
|
### agy headless — see § Cross-CLI worker above
|
|
152
154
|
|
|
153
|
-
**[obs]** Re-probed 2026-
|
|
155
|
+
**[obs]** Re-probed 2026-09-26 (agy 1.2.11; the 2026-08-15 list predates the `gemini-3.8-flash-*` release — do not cite it), `agy models`: `gemini-3.8-flash-{low,medium,high}`, `gemini-3.7-flash-{low,medium,high}`, `gemini-3.6-flash-{low,medium,high}`, `gemini-3.1-pro-{low,high}`, **`claude-sonnet-4-6`**, **`claude-opus-4-6-thinking`**, `gpt-oss-120b-medium`. Also present: `--json-schema`, `--effort`, `--agent`, `--add-dir`, `--print-timeout`, `--disable-slash-commands`, and an `agents` subcommand (empty on this machine — no custom agy agents defined).
|
|
154
156
|
|
|
155
157
|
*Consequence:* a Claude-family model can be reached **on the Antigravity quota**. The vendor paying and the model reasoning are independent choices, which is a second axis the Step 2 mechanism table did not previously have.
|
|
156
158
|
|