@akinet/akidevrule 3.4.0 → 3.6.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 CHANGED
@@ -1,5 +1,49 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.6.0] - 2026-09-30
4
+
5
+ ### Added
6
+ - **`claude/hooks/aki-route-guard.mjs` — a `PreToolUse` route gate that enforces the router's second hop by mechanism.** Evidence: `docs/research/rule-delivery-second-hop-sep29.md`, measured on the owner's own Claude Code transcripts — after the router was force-imported (3.5.0) the `[RULES]` receipt rose from 36% to 81% of edit sessions, but the routed file was actually read in 57% versus 56% before, and 26 of those 36 reads came only after the owner typed `akirule`; organic second hop ≈ 18%, at a cost of 126 reminder messages in four days. Root cause: the import fixed what text can fix (the routing is in context) and left the `Read` to a model decision, and the three miss classes — no receipt, receipt naming the wrong domain, receipt naming a file never opened — are three different model decisions no wording reaches; text had already been changed for this twice (2026-08-06, 2026-09-25). Mechanism: on `Edit|MultiEdit|Write|NotebookEdit` the hook maps the edited path to rule files (code extension → `coding` + `pattern`, `.md` → `docs`, `CHANGELOG.md`/`releases.json` → `release`, `.vue`/`.css`/`.scss`/`.tsx` → `ui`, `.vue`/`.ts` under `nuxt.config.*` → `stack`, `.rs`/`src-tauri/` → `tauri`, `.sql`/`migrations/` → `db`, `locales/` → `content`), drops any file the global `CLAUDE.md` already `@`-imports, scans the actor's transcript (the subagent's own file when `agent_id` is present) for a `Read` or a Bash `cat` of each remaining file, and denies the edit with a reason naming the unread ones. Zero model hops in the check; at most one denial per artifact type per session; a subagent is gated on its own transcript, which closes the `agent.A5` "worker inherits nothing" gap by mechanism; fail-open on any error, an unreadable transcript, or three denials for the same file, so a detection bug can never lock a session; `AKI_ROUTE_GUARD=0` disables it; never gated: the corpus under `~/.aki/`, the harness config dir, `/tmp/` and any `scratchpad/` path, and any path outside the session's `cwd` — a Q&A session that writes a throwaway script or a memory note must not pay for rules no project file is governed by. Registered by `install.mjs` with the same idempotent filter-then-push as the update-check hook, Claude Code only (Antigravity attaches rules natively; the skill-only harnesses have no hook surface). Rejected: `SessionStart` injection of rule text (each hook string is capped at 10,000 characters, below every rule file but `pattern`); a per-turn reminder hook (declined 2026-08-03 as one more model hop, and this gate is not one — the edit cannot proceed until the read happened); a state file (the transcript already records every tool call and every hook decision). Residue: routes with no artifact signature (`think`, `proportion`, `biz`, `ux`, the audits) and code discussed without an edit stay on the router's meaning clause.
7
+
8
+ - **`pattern.A9` — Draft, then commit once: exploration lives in a draft owned by the unit showing it; the record changes once, at an explicit commit event; cancel discards the draft and has nothing to undo.** Evidence: an agent implementing drag-and-drop column reordering in a Vue app wrote to the shared store and `localStorage` on every `dragenter`, with `pattern-core` in context, so one drag re-rendered and re-persisted the whole table many times and `Esc` could not cancel because the record had already changed. The owner named the model — dragging is a preview, dropping is the commit — and then asked for the root, not the symptom: the same shape already governs `release.A5` (`[Unreleased]` is the draft, the release event mints the version) and `agent.A3` (a question is answered, never acted on), and no law stated it, so A1 and A8 were not enough for the model to derive it. Mechanism: one law in `pattern.A`, a `ui` map bullet carrying the checkable grep (no store setter, `localStorage`, IPC or fetch inside a `dragenter`/`dragover`/`pointermove`/`input` handler), a lens row in `docs/arch/corpus-map.md` pointing at the three domain applications, and `pattern`/`ui` route signals for the gesture wording. Rejected after a subtraction pass: a `ui.B6` section of its own (shipped first, then cut — a domain section restating a law is the leaf, the law is the root), and placing the law only in `RULE-ui-pattern.md` (drag-and-drop is one manifestation; a form written to the store on every keystroke and an agent editing a file to answer a question are the same miss).
9
+
10
+ ### Changed
11
+ - **`claude/CLAUDE.md` template slimmed to two imports and two paragraphs; the installer block carries the edit-source rule once.** Evidence: plan Item 2 — "edit source, not the deployed copy" appeared three times on the owner's machine (template, installer block, `CLAUDE.local.md`), the router-import history duplicated the research doc, the `ref-ECC` guard referred to a directory the installer excludes and the repo does not ship, and the opening line "keep global context small" sat above 60 KB of imports. Mechanism: the template keeps the two imports, one paragraph on the gate and the lookup rule, one line pointing named corpora at `CLAUDE.local.md`; the installer block gains the one step only the template had (read the source repo's `CLAUDE.md` first) and names all three source directories. Template 3.5 KB → 0.9 KB.
12
+ - `.gitignore` ignores `.akidevsync/project.json*` (the desktop sync app's local state and its timestamped backups, never source).
13
+ - **`RULE-agent-behavior.md`: `B6` Precedence (moved from the manifest); `A2` states the cost unit as round trips rather than "every call re-sends the conversation" (true for tokens, misleading for money under caching); `C3`'s line-merging tells are one sentence each instead of a worked YAML paragraph. `RULE-coding.md` `B3`'s stash ban is a pointer to its root `agent.B3`** (plan Items 3c, 3d). **`coding.B3` and `B5` are one section** — what counts as verification and the six-rung ladder that decides who performs it were one topic written twice (7.7 KB → 4.6 KB); the address `coding.B5` is retired and every citation repointed to `B3` (`agent.B3`, `release.B9`, `akiopen`, `corpus-map`). **`agent.A5` trimmed 4.7 KB → 2.6 KB**: the mechanics stay (a worker inherits nothing, the receipt, both dials, read-only by mechanism, judgment stays with the caller); host tiers and stateless-versus-persistent cost economics live only in `skills/akiflow/references/harness-facts.md`, which the rule now points at. Rejected: delivering `A5` through a `PreToolUse` hook on `Agent` — `additionalContext` fires after the spawn is composed, so it is the per-turn reminder declined 2026-08-03; a deny gate cannot tell a rule-bearing brief from a bare lookup. `agent.A3`'s self-sufficiency kill-test cut to the length of its siblings. Resident context per Claude Code session 81,985 B (3.5.0) → 38,406 B.
14
+ - **`RULE-coding.md` and `RULE-pattern-core.md` leave the `@` imports: two resident files (`RULE-agent-behavior.md`, the router) instead of five; both rules are now router rows (`coding`, `pattern`) gated by the route guard on every code edit.** Evidence: the owner's proposal (2026-09-29) that the two files are not behavior-floor rules and should load at very high sensitivity instead of unconditionally, plus the second-hop measurement above showing that a router row alone would have left roughly four code sessions in five without them — which is why the 2026-08-06 promotion happened and why demotion was first rejected in the same research doc. Mechanism: with the gate, a code-file edit is denied until both files were read in that transcript, so the guarantee the imports provided is restored where it applies (a code turn) and dropped where it never applied (a session that touches no code — the majority for many downstream users of this corpus and akimcp, whose sessions are questions rather than edits). The `[RULES]` receipt's `(core)` set is therefore `agent` alone and `coding,pattern` appear under `(router)`. The router's rule 2 now states the tiering's purpose as a route: a lookup turn — one that only reads, counts, locates or explains what exists — routes nothing, and the `coding`/`pattern` clauses no longer say "any talk about code, when in doubt load" (evidence: an Antigravity session read both files, plus the project's `CLAUDE.md`, to answer "how big is this project's source", with the old clause in its rule description); the same clause now feeds Antigravity, since the installer derives the `akirule-coding`/`akirule-pattern-core` descriptions from the router instead of a hardcoded sentence that predated their route rows. Narrowing is safe on Claude Code because the gate catches an under-route at the first edit; on Antigravity, which has no gate, the clause keeps "ON for every code edit" unambiguous. `payload/GEMINI.md` no longer orders an unconditional session-start `view_file` of both files (it called them core rules, which they no longer are, and it was the mechanism actually loading them for every Antigravity session — a clean-context challenge caught it after the first sweep missed it); its receipt example drops `coding,pattern` from the always-present set. Rule 2 precedes rules 3 and 5, so a project binding (`stack`, `tauri` ON for the whole project) applies to task turns and not to a lookup. `index.md` drops the paragraph recounting the 2026-08-06 → 2026-09-30 import history — it is in this entry and the research doc, and a resident file pays for every line on every request (`docs.A5`). Tradeoff accepted knowingly: a design discussion with no edit, and any meaning-only route, now depends on the router's clause, which is the owner's "extreme sensitivity" reading of the two new route rows; the 2026-08-06 reopen trigger is unchanged in spirit — a `coding`/`pattern` violation traced to LOAD-fail by `aki-conduct` re-promotes the file by reverting one import line. Verified on the dev box 2026-09-30: a source-size question in a code project loaded no routed file on either Claude Code or Antigravity; `scripts/second_hop_audit.py` now measures `coding`/`pattern` too (era split 2026-09-30) for later re-checks. Updated together: `claude/CLAUDE.md`, `skills/akirule/SKILL.md` (two route rows, rule 2, receipt semantics, gate line), `payload/index.md` (manifest tiers, loading paragraph — then removed in this same batch, see Removed), `payload/RULE-pattern-core.md` tier line, `docs/arch/rule-delivery-architecture.md` (route-gate section), `README.md`, `skills/akihelp/SKILL.md`, `skills/akiship/SKILL.md` (receipt line), `install.mjs` (`AG_RULE_MAP` descriptions, `MultiEdit` in the matcher), `payload/GEMINI.md`, `.github/workflows/install-smoke.yml`; the repo's own `CLAUDE.md` gains the change-sweep rule this batch needed twice.
15
+ - **`agent.C3` fences every copy-verbatim artifact (prompt, template, file body, multi-line command block) with four backticks, never three.** Evidence: the owner asked for it after paste artifacts kept breaking — such artifacts routinely contain a fence of their own (a CLAUDE.md fragment, a skill body, a prompt with a code example), so a three-backtick wrapper closes at the inner fence and the rest renders as prose, which the reader copies incomplete without noticing. Mechanism: one line beside the existing prompt-never-hard-wrap bullet, since both exist for the same reason (the text is pasted verbatim, so the rendering must not alter it); the wider fence is CommonMark-standard and renders in every harness and terminal. Scoped to multi-line artifacts, inline code stays for a single token. Not given a `§0` penalty card and not detected by `scythe.py`: the frequency does not yet clear the `pattern.A2` bar, and a fence choice is visible at a glance.
16
+
17
+ - **`docs/ref/*.md` renamed to `fact-*.md` per `docs.A6`, each stamped (`docs.A4`) and given a trail line; the deployed TCC lookup moves to `~/.aki/akidevrule/docs/ref/fact-macos-codesign-tcc.md`.** Evidence: all four are claims about the outside world (two open standards, per-CLI permission dialects, macOS TCC), which `A6` says must carry the `fact-` name and a per-claim trail; deferred from the rule's own batch because one file is deployed by the installer. Mechanism: plain rename with every live reference updated (`CLAUDE.md`, `README.md`, `install.mjs`, `lib/permissions.mjs`, `docs/index.md`, the arch doc, `RULE-stack-tauri.md`, the cross-links); the installer already removes the deployed `docs/` tree before copying, so the old path is pruned on the next install with no extra code. The Agent Skills doc's format-identity claims had rested on secondary sources with no read date; verified 2026-09-30 against `code.claude.com/docs/en/skills` and `antigravity.google/docs/skills`, which corrected two details (Antigravity's workspace path is `.agents/skills/`, and the optional subfolder names differ per vendor and are not validated) and recorded the CLI's documented global path beside the measured `skills.json` mechanism this repo's installer actually relies on.
18
+
19
+ ### Removed
20
+ - **`payload/index.md` — the corpus no longer ships a manifest; the router is the only resident map.** Evidence: the router's Routes table and the manifest both answered "which file, when" (`docs/plan/done/resident-context-slim-sep28.md` Item 1); the manifest had regrown to 22.5 KB, a quarter of every session's resident context, and its Purpose column restated the router clause and the file itself. Root cause: the file was promoted to an import on 2026-08-06 to carry the receipt vocabulary and Precedence, then accumulated prose because it was resident and convenient. Mechanism: each route row now carries its topic (`RULE-coding.md` · `coding`), so the receipt vocabulary lives where the routing does; § Precedence is `agent.B6` (behavior belongs in the behavior floor, `docs.A5` item 5 repointed); the Groups table and the cross-cutting lens moved to `docs/arch/corpus-map.md`, a maintainer's address map that is not installed — `pattern.A7` no longer points at it, since every domain application is already listed inline. Runtime consumers retargeted in the same change: the installer's "Rules deployed" printout (derived from `AG_RULE_MAP` and the router clauses), `aki_version_check.mjs` (intact test on `RULE-agent-behavior.md`), the CI smoke assertion, `akihelp` step 1, `payload/GEMINI.md` receipt wording. `@akinet/akimcp` tests `index.md` only as an existence fallback beside `CHANGELOG.md`, so it keeps working. Rejected: keeping a trimmed `index.md` in `payload/` (the owner's ruling: a map the model does not need belongs to the repo, not the payload); keeping the lens resident inside the router (5.8 KB for a maintainer's dedupe record; reopen if a model restates a root rule in a domain edit because the lens was absent).
21
+
22
+ ### Fixed
23
+ - **`release_lint.py --latest` false-positive `[PARITY]` on every project with an open `[Unreleased]` section.** Evidence: running the B7-gate command on aiobox flagged `releases.json version 0.4.6 has no CHANGELOG entry` even though 0.4.6 has a full CHANGELOG entry — reproduced identically on akitao.com (real ecosystem repo, also mid-accumulation with `[Unreleased]` open). Root cause: `--latest` truncates `_changelog_blocks()` to the single newest block for the reverse `releases.json → CHANGELOG` parity check; the moment that newest block is `[Unreleased]` (the normal `release.A5` working state between releases), the truncated version list cannot contain the last *shipped* version, so the check misreports every prior release as missing. `lint_changelog` now returns both the `--latest`-scoped version list (still used for the forward "did I add this new entry to releases.json" check, which is exactly what `--latest` is meant to scope) and the full, untruncated version list; the reverse check now tests membership against the full list — the truncation still limits which `releases.json` entries get inspected (`items[:1]`), only the comparison target was wrong. Verified: `--all` output is byte-identical before/after on aiobox and 7 other ecosystem repos (zero regression); a synthetic case with a `releases.json` version absent from CHANGELOG entirely still fires `[PARITY]` in both modes (true positive preserved); a synthetic `[Unreleased]`-open-plus-matching-older-version case now exits 0 (the bug scenario, fixed). Rejected: skipping the reverse check whenever `--latest`'s top block is `Unreleased` — that would also blind the check to a `releases.json` entry added without any matching CHANGELOG entry anywhere, which is the exact drift `[PARITY]` exists to catch.
24
+
25
+ ## [3.5.0] - 2026-09-27
26
+
27
+ ### Added
28
+ - **`agent.B3` names the exact git commands that discard or hide tracked/uncommitted work** (`stash`, `checkout -- <path>`/`checkout .`, `restore .`, `reset --hard`, `clean -f[d]`, `push --force`, `branch -D`) as ask-before actions, never a shortcut past a failing check or an obstacle. Evidence: the generic "destructive or hard-to-reverse actions" line already covered this in principle, but stayed unnamed, and this corpus also runs on harnesses (Gemini/Antigravity, Codex, Kiro, Grok) with no built-in git safety protocol to fall back on. `coding.B3`'s narrower stash-for-check-attribution ban is the existing specific instance of this general rule.
29
+ - **`docs.A6` fact docs: `docs/ref/fact-*.md` holds claims about the outside world, and every claim carries its own trail — an official source with the date it was read or an artifact pinned by version with the path inside it, plus the research doc and section that verified it; changes land only through a research event (`## Amendments` or a successor doc) with the `A4` stamp rewritten in the same edit.** Evidence: the owner asked whether facts were held to the same bar as `biz/` decisions, and the corpus answered no — `A2` knew one kind of `ref/` ("verified by running its commands"), `A4` exempted all of `ref/` from the stamp on that reasoning, `B2` linked research → `ref/` one way only, `C3` audited `ref/` for commands alone, and this repo's own four `docs/ref/` lookups are all fact docs by this definition: none stamped, one with an evidence trail. The new `content.C2` fact-check made the gap concrete: it traces every product claim to a source but had nowhere to keep the trace, so every audit re-derives it. Mechanism: one root item naming three claim classes and their authorities — biz decided by the owner (`A3` now says so and why it wins), decision reached by research (`B2`), fact held by evidence (`A6`) — with `A2`, `A4`, `B2` Action, `C3` and `content.C2` pointing at it, and the class declared in the filename so `ls`, the index and a grep all see it. Rejected: requiring evidence on all of `ref/` (a command lookup is verified by running it, a date adds nothing); a frontmatter marker instead of the filename (invisible in a listing); renaming this repo's four existing lookups in this batch (one is deployed by `install.mjs` to a path `tauri.B7` cites, so the rename needs a stale-file prune on users' machines — scheduled in `docs/plan/ref-fact-migration.md`). The "second consumer" bar keeps one-off findings in research, so fact docs do not accumulate trivia.
30
+ - **`seo.A6` URL form: every same-site URL is stored and rendered site-relative; an absolute URL is produced only at the emission boundary, by one idempotent helper whose origin is the configured site URL.** Evidence: a news article on an Aki site rendered its hero `<img>` from `https://<site>/images/...` on localhost, so the dev page showed the production file and would have hidden a missing local asset. Root cause: the corpus taught the pattern, `seo.C1`'s `usePageSeo` example passed `ogImage: 'https://domain.com/...'`, the project's page-creation doc copied it, and every existing post followed it; one field fed both the rendered hero and `og:image`, and nothing separated the two consumers. The article skill already said to store the path relative, but it never said the composable must absolutize it, so following the skill alone would have emitted a relative `og:image`. Mechanism: A6 plus two `seo.C3` detectors (no own-origin `src`/`srcset`/`<a href>` in rendered output; every `og:image` and JSON-LD `image` absolute), `stack.A2` names `site.url` via `useSiteConfig()` as the single origin source, A5/C1 examples switched to relative paths. Rejected: a request-host origin (a preview deploy would emit preview canonical and OG URLs); migrating legacy absolute data (the idempotent helper makes it unnecessary).
31
+ - **`aki-article-writer` Phase 6.5 rendered-output pass: grep the built HTML for own-origin asset URLs, then read the full rendered text in every locale as the target reader before reporting done.** Evidence: the same run passed build and the SEO validator while the page carried the absolute hero URL, keyword variants in parentheses that read unnaturally, and a sentence missing its verb; the owner found all three by looking at the page. Root cause: every Phase 6 check read source strings or validator output, none read what the reader sees. Uses the build, which `coding.B3` self-authorizes, not a dev server.
32
+ - **`akiopen` skill: a session-opening brief that reports only what is still pending in a project.** Evidence: the owner opened every project with the same prompt — check the notes, the active plans, the inbox file, the tree — and the agent rebuilt the picture from memory each time, sometimes missing a surface (`ux.A2`, recognition over recall). The skill reads the five surfaces the corpus already defines (working tree, `.akidevsync/notes.json`, `docs/plan/` outside `done/`, top-level `docs/*.md` inbox files with open checklist items, `[Unreleased]` and plan hand-off lines) in one batched pass and reports at most seven items in a fixed problem / why-still-open / proposal / goal shape, ranked by severity, saying nothing about what is fine. Read-only by construction (`agent.B5`). Rejected after a subtraction pass on the design itself: a detector script (one caller, below `pattern.A2`'s bar — the scan is four shell lines), a SessionStart hook (Claude-only, runs in every directory, and `akiopen` is a deliberate act like `/akiship`, not a passive rule that must fire every turn), and a project-name-specific inbox path (an inbox is any top-level doc with open `- [ ]` items, so no ecosystem name is needed).
33
+ - **`skills/akiflow/scripts/release_lint.py` — mechanical lint for the release record surfaces, wired into `release.C4` and the B7 gate step 4.** Evidence: an audit of 25 project CHANGELOGs found 21 with sections out of order, up to 13 distinct orderings inside one file, 13 invented headings (`Internal`, `Docs`, `Notes`, `Verified`, `Refactored`, `Known`, `Migration`, …), two repos with version headings at H3 so the rule's own `grep '^## \['` state check returned nothing, and 4 of 11 `releases.json` sites with no `highlight` key at all. Root cause: `release.C1` named a vocabulary but no order (and B4 listed a different order), and nothing checked either. Verdict tags `[ORDER]`/`[SECTION]`/`[LEVEL]`/`[PARITY]`/`[TYPE]` fail the gate; `[HILITE]` is a review line (a version with a `new` change and no highlight, more than two highlights, a highlight not first, or a highlight on a `fixed`/`internal` line — the backfill across 22 repos found 8 of those, each on the fix a user would notice most, which is exactly the case C2 excludes because the tier means capability gained, not restored) answered in the receipt, never auto-fixed — whether a change deserves the headline is judgment. Same output grammar and exit codes as `scythe.py`; kept as a separate script because scythe's job is comment/prose format and this is release-record structure (`pattern.A3`). `--latest` scopes it to the newest version so old entries are never a per-release cost. Rejected: folding it into scythe (two responsibilities under one name), and backfilling every repo (historical entries are corrected only when touched; the shape matters going forward). Research: `docs/research/release-changelog-shape-highlight-sep26.md`.
34
+
35
+ ### Changed
36
+ - **Publishable prose routes more sensitively.** The `aki-article-writer` description and the router now name the act (writes, rewrites, translates or reviews an article, news/blog post, announcement or knowledge entry, including a release turned into a post) instead of "write a new article"; the router adds a `Publishable writing` line that invokes the skill and loads `content` + `seo`, and the `content` route names articles, posts and content data files. Evidence: the incident request asked for a release to be turned into a news post, which the old trigger ("write a new article, create content, draft a blog post") did not clearly name, and the run then wrote both articles in-thread at the end of a long release session instead of handing them to the Article Worker.
37
+ - **`release.C1` fixes one canonical CHANGELOG shape: `## [x.y.z] - date`, `### Section` one level below, sections at most once, in Keep a Changelog's order `Added, Changed, Deprecated, Removed, Fixed, Security`, no other heading.** Internal/tooling/docs work is `Changed` — no `Internal` section, because `releases.json` already carries the `internal` badge for the audience that needs it and a second taxonomy in the developer channel invites the drift the audit measured. Order is the standard's, not importance-ranked: an order chosen per release cannot be seen across releases, so it never converges. B4's GitHub Release body now says "C1's order" instead of naming its own.
38
+ - **`release.C2` defines the `highlight` tier for `releases.json`.** The field existed in 7 sites' pages and in one page comment on the reference site, nowhere in the rule, so agents wrote flat lists (akitao 0 of 53 versions, app.akinet 0 of 41, the reference site itself 8 versions with a new tool and no highlight). The rule now carries the criterion (the change a returning user came back for; never a fix, internal, or copy tweak), the shape (first line, at most two, zero is valid), the wording contract (benefit-first, mechanism as proof, `title` names the same thing), and the reason: a flat list gives a bug fix and a new tool the same weight, so the public page reads as a log instead of a product moving — the trust signal the page exists to send.
39
+ - **`release.C4` sync check is the script, not two greps.** The greps never checked order, vocabulary, badge keys or highlight, and the H2-only pattern silently passed the two repos whose versions sat at H3.
40
+ - **`release.B6` is now the release-copy contract: one user-facing text per release in three lengths (Headline, Short, Full) plus an announce verdict, printed as the last block of every `/akiship` run.** Before, the only user-facing wording rules were B4's GitHub Release title/body (so a CLI or desktop project with no GitHub Release got none) and three loose B6 lines about dashes and terminology; every other surface — `releases.json` title, a post, a notification, a store listing — was improvised per run. One contract, referenced by B4 (title = `v{version}: {Headline}`, body = Full) and C2 (`title` = Headline), so the wording is written once and read three times (`pattern.A1`). The announce verdict exists because the materiality test applies at the channel too: an internal-only version gets an honest one-liner and `Announce: no`, never a fourth "stability improvements" post. The block quotes what the run already wrote instead of composing a third variant, and never names a channel the project's own records do not.
41
+ - `payload/index.md` release row, `skills/akiship/SKILL.md` gate bullets and report, and `README.md` name the new surfaces.
42
+ - **`content.C2` gains a fourth sweep, fact-check: every claim about a product, feature, version or number must trace to that product's own repo or live page, or it is a finding.** Evidence: a published article on an Aki site stated a capability the product did not have, and the three existing sweeps (canonical-term drift, density, i18n coverage) all passed because none compares a sentence with a source. Root cause: `agent.B2` forbids speculation for the agent's own claims, but the content audit never applied it to shipped copy. Rejected: a new root axiom "write the truth" in `content.A1`, because `agent.B2` already is that rule and a restatement would be the duplication `pattern.A1` forbids. The `content.B3` FAQ bullet is folded into `B2`'s first-sentence rule, which already generalized it; `B2` now carries the FAQ case as its sharpest example instead of pointing at a copy.
43
+
44
+ ### Removed
45
+ - **`seo.B3` "Vietnamese keyword handling" and its copies in `aki-article-writer` (§3.4 and the §6.2 checklist line).** The section ordered an unaccented keyword in parentheses at a term's first mention in body copy and FAQ — the direct source of a "(hoan sao)" inserted into a sentence of a published news article. Its premise, that Google treats accented and unaccented Vietnamese as different queries, has no official source in either direction; every line under it inherited that premise, and the alternatives it offered were dead (meta `keywords` is not a ranking signal; `alternateName` variants already live in `seo.B1`). Nothing replaces it: brand-name variants stay in schema via `seo.B1` where only machines read them, and the Phase 6.5 rendered-output pass lists any visible SEO device as a review line. `seo.B4` Prerendering renumbers to `B3`; the repo `CLAUDE.md` Vietnamese-example line no longer cites the removed rule.
46
+
3
47
  ## [3.4.0] - 2026-09-26
4
48
 
5
49
  ### Fixed
package/README.md CHANGED
@@ -51,7 +51,7 @@ The installer is `install.mjs` — one cross-platform Node.js program (Node stdl
51
51
 
52
52
  Tooling — have these installed first:
53
53
 
54
- - **Node.js 18+** — the one hard requirement to install or update. The installer and the SessionStart update-check hook are Node; the 18 floor is the built-in `fetch` the update check uses. `install.sh` / `install.ps1` locate `node` and stop with one clear message if it is missing.
54
+ - **Node.js 18+** — the one hard requirement to install or update. The installer and the two Claude Code hooks are Node; the 18 floor is the built-in `fetch` the update check uses. `install.sh` / `install.ps1` locate `node` and stop with one clear message if it is missing.
55
55
  - **Python 3.7+** — only to *run* the akiflow skill helper scripts (`skills/akiflow/scripts/*.py`); never needed for install or update.
56
56
  - `git` — for the `git clone` remote install.
57
57
 
@@ -59,7 +59,7 @@ Interpreter convention (documented once): the installer and hooks run on `node`
59
59
 
60
60
  ## What you get
61
61
 
62
- ### Ten skills
62
+ ### Eleven skills
63
63
 
64
64
  | Skill | Invoke | Purpose |
65
65
  |---|---|---|
@@ -67,12 +67,13 @@ Interpreter convention (documented once): the installer and hooks run on `node`
67
67
  | `akiflow` | `/akiflow` | Lead-coordinated **agent council** for work needing more than one kind of judgment. The lead's job is two laws: **ANCHOR** — the owner's verbatim message is pinned as the run's immutable first block (`council_open.py` refuses to open a room without it) and every numbered requirement must quote a fragment of it; and **JUSTIFICATION** — every seat, check and script is OFF by default and turns on only when this run produces a reason, so there are no standing seats and no roster derived from a tier. It decomposes the request into owned work items, checks a three-condition activation gate, and convenes seats from the five definitions in `~/.claude/agents/` — one batch, each seat traced to a requirement, never picked from a menu. **Two shapes, discriminated by whether anything is actually being arbitrated.** A *council* is items with adversaries, for work where two competent seats could reach different defensible answers. A *dispatch* is lanes with exclusive file ownership, for a fan-out whose answer is already knowable and whose only real hazard is two workers writing the same file — `--convene` refuses an overlapping `writes:` before a token is spent. Dispatch drops the challenger, the debate and the three-condition gate; it keeps the anchor, the quoted requirements, the `[RULES]` receipts, the durable record and the whole closure gate. It exists because 19 of 70 live rooms posted no debate turn at all and 11 of those still did substantive fan-out work, paying council overhead for machinery they never used. Orthogonally, three modes discriminated by one question, *what changes outside the room*: `discuss`, `audit` (read-only by construction), `execute` (only `aki-maker` may write). The lead does no menial work and settles what doctrine answers, escalating only a one-way door, a contradiction with documented design, or scope expansion — then writes the owner's answer back into doctrine the same turn, so a question never escalates twice. `council_verify.py` refuses closure on a missing anchor, a requirement quoting nothing the owner wrote, a declared seat that left no trace anywhere in the session, a seat with no `[RULES]` receipt, or an unanswered reminder — reading each seat's own `<seat>.md` as well as its turns, and printing `SKIP` rather than `PASS` where there was nothing to check — and deliberately requires no named seat, since an earlier version that did forced a seat to exist in a run with nothing to enforce and was gamed rather than questioned. **A read is a subscription, not a purchase** — the cost model that shapes how the room is used: every turn re-sends the whole history, so a read of size `S` at turn `t` of a `T`-turn run is charged about `S × (T − t)`, and pulling a 50k-token room at turn 50 of 200 costs ~7.5M cache-read tokens from one call. Measured on a real run with three `aki-maker` seats doing every file edit, the lead still held 70% of cache-read and 66% of output: delegation moves the work but not the money, because what a run pays for is the lead's own accumulated context. Hence `council_read.py --grep` to locate for a few hundred bytes and `--turn` to pull only what the grep pointed at. Close-out reconciles declared model tiers against actual spend, cross-CLI calls added by hand since they never appear in the transcript. Design record: [`docs/arch/akiflow.md`](docs/arch/akiflow.md). |
68
68
  | `akithink` | `/akithink`, or self-run | Structured deep thinking: restate → goal excavation → first principles → mandatory critique → convergence into a `docs/` decision record. Self-run (model-invoked, non-interactive, decide-and-report) whenever the task is a decision rather than execution; interactive only when the owner asks. Recommends a top-tier model (Opus/Fable). |
69
69
  | `akihtmlreport` | `/akihtmlreport` | Distills a dense analysis already in the conversation into one self-contained, ultra-wide `REPORT.html` at the project root — no new analysis, no dropped detail — then opens it locally. Exactly one per project; asks before overwriting. |
70
- | `akihelp` | `/akihelp` | Live introduction to the whole installed Aki system, rendered by reading `index.md` and skill frontmatters at runtime — it can never go stale. Includes a **painpoint → what to say** table (sprawling CSS, docs that no longer match code, a half-finished tree, a pre-ship check, a hard-to-reverse decision, padded or hard-wrapped output, over-guarded flows, UX friction, pricing calls) built from that live state, with any row whose target is not installed dropped rather than shown. Closes on the caveat that governs everything else: the router is always present but the `Read` of a routed file is still model-dependent, so name the rule file in the prompt whenever the load must be deterministic. |
70
+ | `akihelp` | `/akihelp` | Live introduction to the whole installed Aki system, rendered by reading the router's Routes table and skill frontmatters at runtime — it can never go stale. Includes a **painpoint → what to say** table (sprawling CSS, docs that no longer match code, a half-finished tree, a pre-ship check, a hard-to-reverse decision, padded or hard-wrapped output, over-guarded flows, UX friction, pricing calls) built from that live state, with any row whose target is not installed dropped rather than shown. Closes on the caveat that governs everything else: the router is always present but the `Read` of a routed file is still model-dependent, so name the rule file in the prompt whenever the load must be deterministic. |
71
71
  | `akigitcommit` | `/akigitcommit` | Turns a messy working tree into a few clean, logically grouped Conventional Commits. Triages a half-finished tree first — finished vs mid-edit vs abandoned vs accidental, asking rather than guessing — then stages by explicit path, never `git add -A`, never pushes unasked. |
72
- | `aki-article-writer` | `/aki-article-writer` or natural language | Per-project article writing pipeline: research & fact-verification, SEO metadata, JSON-LD schema, UX-psychology-aware content, and a dedicated Image Scout subagent (Gemini Flash / Haiku) for search → download → visual inspection → ffmpeg processing → slug-named WebP output. One subagent per article; image work is always isolated to a separate lightweight subagent. |
72
+ | `aki-article-writer` | `/aki-article-writer` or natural language | Per-project pipeline for publishable site prose (articles, news/blog posts, announcements, knowledge entries, including a release turned into a post): research & fact-verification, SEO metadata, JSON-LD schema, UX-psychology-aware content, a rendered-output pass on the built HTML before delivery, and a dedicated Image Scout subagent (Gemini Flash / Haiku) for search → download → visual inspection → ffmpeg processing → slug-named WebP output. One subagent per article; image work is always isolated to a separate lightweight subagent. |
73
73
  | `akidevsync-notes` | natural language | Reads/edits a project's `.akidevsync/notes.json` — the per-project task list the Aki-Dev-Sync app itself writes (list/add/pin/mark-done/edit/delete tasks) via a bundled script that preserves the app's own JSON formatting, plus a workflow for cross-checking pinned notes against a shipped release (CHANGELOG + code) before marking them done. |
74
74
  | `akilint` | `/akilint` or a penalty card | Mechanical format lint for the penalty-card classes of `RULE-agent-behavior.md` §0: hard-wrapped code comments and markdown prose (`[WRAP]`) and oversize comments (`[YAP]`, always labeled *review* — a flag for judgment against `coding.B4`, never an auto-delete verdict). Thin wrapper over the shared `scythe.py` detector (deterministic line matching, exit-code aware, cannot fabricate evidence) — the same script akiflow's `aki-conduct` seat uses, so a card name means the same thing everywhere. `[FLUFF]` (density) is content judgment and explicitly out of a script's reach. |
75
- | `akiship` | `/akiship` | One-command full release: front-loads every check (release state, tree triage), then runs `RULE-release.md` B7's fail-closed checklist unattended (CRITICAL mandatory `Read` of `RULE-release.md` and `RULE-docs.md` first; a `S0`–`S8` receipt line with quoted evidence per step or the step counts as NOT RUN; written self-interrogation) — diff-scoped hygiene (scythe, dead code, comment doc-refs on the accumulation only), migration doctrine (`RULE-release.md` B5: detector over the diff on every release, startup-embedded migration counts, rehearsal from the PREVIOUS state) and external-action completeness, record truthfulness, build & test mirroring CI (B7 step 6), doc sync across every record surface (plans, `arch`/`feat`, README, task notes, bound standards docs), version mint or defer, registry publish for npm/crates/PyPI packages (`RULE-release.md` B9 — an OTP-gated publish is the single hand-off) — committing via `akigitcommit` with confirmation pre-answered. **Activation is an explicit release order**: the literal token `/akiship`, or an equally explicit imperative naming the ritual for this repo ("release trọn vẹn đi") — a completion word with no release object ("làm cho trọn vẹn"), or `/akiship` inside a question, activates nothing and gets a consult (answer in chat, change nothing). Governed by the B8 contract: that order is the authorization, blockers are reported once as a batch or the run completes with zero mid-run questions, and it stops only for public-history ambiguity, unclassifiable work, or a design contradiction — an owner-worded completion criterion is derived from the anchor plus the repo's own records and decided/reported (`Decided: X · because Y · rejected Z (why) · reopen if W`), escalated only when competing readings would produce different irreversible artifacts. Push/deploy stay opt-in — named explicitly, or via completion-intensity phrasing (canonical list in `RULE-release.md` B8, e.g. "trọn vẹn") — and after any push, CI is watched to green (`RULE-release.md` B10) regardless of whether the stack deploys, and after any deploy a data path the release touched is exercised (`RULE-release.md` B11). |
75
+ | `akiopen` | `/akiopen` or natural language | Session-opening brief: reads the working tree, `.akidevsync/notes.json`, active `docs/plan/` files, top-level `docs/*.md` inbox files, `[Unreleased]`, and hand-off lines in one pass, then reports only what is still pending — at most seven items in a fixed problem / why-still-open / proposal / goal shape, ranked by severity. Read-only. |
76
+ | `akiship` | `/akiship` | One-command full release: front-loads every check (release state, tree triage), then runs `RULE-release.md` B7's fail-closed checklist unattended (CRITICAL mandatory `Read` of `RULE-release.md` and `RULE-docs.md` first; a `S0`–`S8` receipt line with quoted evidence per step or the step counts as NOT RUN; written self-interrogation) — diff-scoped hygiene (scythe, dead code, comment doc-refs on the accumulation only), migration doctrine (`RULE-release.md` B5: detector over the diff on every release, startup-embedded migration counts, rehearsal from the PREVIOUS state) and external-action completeness, record truthfulness, build & test mirroring CI (B7 step 6), doc sync across every record surface (plans, `arch`/`feat`, README, task notes, bound standards docs), version mint or defer, registry publish for npm/crates/PyPI packages (`RULE-release.md` B9 — an OTP-gated publish is the single hand-off) — committing via `akigitcommit` with confirmation pre-answered — and ending every run with the release-copy block (`RULE-release.md` B6: headline / short / full plus an announce verdict, quoting the `releases.json` entry and GitHub Release body rather than writing a third text). **Activation is an explicit release order**: the literal token `/akiship`, or an equally explicit imperative naming the ritual for this repo ("release trọn vẹn đi") — a completion word with no release object ("làm cho trọn vẹn"), or `/akiship` inside a question, activates nothing and gets a consult (answer in chat, change nothing). Governed by the B8 contract: that order is the authorization, blockers are reported once as a batch or the run completes with zero mid-run questions, and it stops only for public-history ambiguity, unclassifiable work, or a design contradiction — an owner-worded completion criterion is derived from the anchor plus the repo's own records and decided/reported (`Decided: X · because Y · rejected Z (why) · reopen if W`), escalated only when competing readings would produce different irreversible artifacts. Push/deploy stay opt-in — named explicitly, or via completion-intensity phrasing (canonical list in `RULE-release.md` B8, e.g. "trọn vẹn") — and after any push, CI is watched to green (`RULE-release.md` B10) regardless of whether the stack deploys, and after any deploy a data path the release touched is exercised (`RULE-release.md` B11). |
76
77
 
77
78
  ### Five agent definitions
78
79
 
@@ -96,22 +97,21 @@ A catalog is not a roster. Five files on disk make it easy to pick seats from a
96
97
 
97
98
  - `RULE-*.md` — constraints: what the agent must or must not do (behavior, coding, design/patterns, docs, content, stacks — Nuxt/Cloudflare + Tauri, UI, SEO, release, DB design, business/market).
98
99
  - `METHOD-*.md` — analytical frameworks: how to reason through a specific class of problem. Heavy, loaded only when the task is genuinely analytical.
99
- - `index.md` — file manifest, precedence order, cross-cutting lens.
100
100
 
101
101
  Loading happens on two different mechanisms.
102
102
 
103
- **Core and router — harness-embedded.** `index.md`, `RULE-agent-behavior.md`, `RULE-coding.md`, `RULE-pattern-core.md` and the router `skills/akirule/SKILL.md` are `@`-imported by `~/.claude/CLAUDE.md`, which Claude Code reads mechanically at session start. No model decision is involved, so the four rule files genuinely apply to every task and the routing is always present. The router joined them for the same reason: as a skill it went uninvoked until the owner asked for it by name. The two rule files were promoted out of the router after "default ON" proved to be a statement of intent rather than a mechanism: routing them through a skill meant they loaded only when the model first chose to invoke that skill, and the rules needing the most owner correction were absent from the context rather than present and disobeyed. They cost context in every session, including sessions with no code in them — that is the price of the guarantee. `@` imports have this effect **only** inside `CLAUDE.md` — the same syntax written into a skill body looks like an import but loads nothing, because a skill body is read only after the model has already chosen to invoke the skill.
103
+ **Resident — harness-embedded.** `RULE-agent-behavior.md` and the router `skills/akirule/SKILL.md` are `@`-imported by `~/.claude/CLAUDE.md`, which Claude Code reads mechanically at session start. No model decision is involved, so the behavior floor applies to every task and the routing is always present. The router joined the imports because as a skill it went uninvoked until the owner asked for it by name. `@` imports have this effect **only** inside `CLAUDE.md` — the same syntax written into a skill body looks like an import but loads nothing, because a skill body is read only after the model has already chosen to invoke the skill.
104
104
 
105
- **Everything else — routed by meaning.** Each task turn is classified by the domains it touches and the act (create, decide, audit, ship), in any language; each route carries concept signals in English and Vietnamese as evidence, never as the test, so a paraphrase routes as well as the listed word. The one model-dependent hop left is the `Read` of a routed file. Sensitivity is deliberately high (err toward loading — a false positive costs a few tokens, a false negative causes wrong behavior).
105
+ **Everything else — routed by meaning, and gated where the artifact gives it away.** Each task turn is classified by the domains it touches and the act (create, decide, audit, ship), in any language; each route carries concept signals in English and Vietnamese as evidence, never as the test, so a paraphrase routes as well as the listed word. The one model-dependent hop left is the `Read` of a routed file, and on Claude Code the `aki-route-guard` PreToolUse hook (`claude/hooks/aki-route-guard.mjs`) enforces it for every route with an artifact signature: the first Edit/Write of each artifact type in a session — a code file (`coding` + `pattern`), `.md` (`docs`), `CHANGELOG.md` (`release`), `.vue`/`.css` (`ui`, plus `stack` in a Nuxt project), `.rs` (`tauri`), `.sql` (`db`), `locales/` (`content`) — is denied until those files have been read in that transcript; a subagent is gated on its own transcript. Zero model hops in the check, at most one denial per artifact type per session, fail-open on any error, `AKI_ROUTE_GUARD=0` disables it. `RULE-coding.md` and `RULE-pattern-core.md` were resident imports from 2026-08-06 to 2026-09-30 because the router as a skill went uninvoked; importing the router restored the routing but not the read (measured: the model read a routed file unprompted in about one session in five — `docs/research/rule-delivery-second-hop-sep29.md`), so the gate now restores the guarantee by mechanism and the two files load only on code turns. Meaning-only routes (`think`, `proportion`, `biz`, `ux`, the audits) stay on the router alone, with sensitivity deliberately high (err toward loading — a false positive costs a few tokens, a false negative causes wrong behavior). The one deliberate exception is a lookup: a turn that only reads, counts, locates or explains what exists routes nothing, because no rule's act is performed — those turns are the majority for many users of this corpus, and the tiering exists so they pay for nothing beyond the two resident files.
106
106
 
107
- - **Contextual and analytical — read on route match:** `RULE-docs.md` (structure and lifecycle, plus the docs-vs-code drift audit), `RULE-content-write.md` (UI copy and writing style, plus the content audit — canonical-term drift, density deletion test, i18n coverage), `RULE-stack-akiNuxtCf.md`, `RULE-stack-tauri.md` (Tauri v2 + Rust: never-block-the-UI, version SSOT, target context, the macOS TCC/Gatekeeper boundary for spawned sidecars), `RULE-ui-pattern.md` (design-system layer: the subtraction pass that runs before the tier ladder, class taxonomy, tokens, variant API, and the audit playbook), `RULE-seo.md`, `RULE-release.md`, `RULE-db-design.md`, `RULE-biz.md` (market-facing decisions: positioning, pricing, audience) — plus the analytical methods (tagged `Analytical` in `index.md`, loaded on route match like the rest): `METHOD-audit-flow.md` (refactors, multi-file bugs, fragile flows), `METHOD-audit-zero-trust.md` (strict mechanical-first audit: detectors before opinion, exact matches separated from pattern-level candidates), `METHOD-deep-think.md` (scope/architecture/value decisions, first-principles and critique-style thinking), `METHOD-ux-psych.md` (UX/user-behavior evaluation, onboarding and conversion flows), `METHOD-proportionality.md` (sizing a guard, limit or accepted risk against reach, capability, motive and blast radius — the lens that stops both over-engineering and client-side-limits-as-enforcement), `METHOD-audit-subtraction.md` (repo-wide "does this need to exist" sweep, terminating on two dry rounds), and `METHOD-audit-frozen-reference.md` (compliance audit for a clause naming a concrete external artifact as the canonical shape to match — resolve to an exact path, diff literally against it, never judge from memory of the rule's prose).
107
+ - **Contextual and analytical — read on route match:** `RULE-coding.md` (code-quality floor: source of truth, changing existing code, verification and the hand-off ladder, comment budget, runtime safety), `RULE-pattern-core.md` (the 9 structural laws, decomposition, the critique gate before any abstraction), `RULE-docs.md` (structure and lifecycle, evidence-bound `ref/fact-*` fact docs, plus the docs-vs-code drift audit), `RULE-content-write.md` (UI copy and writing style, plus the content audit — canonical-term drift, density deletion test, i18n coverage, fact-check), `RULE-stack-akiNuxtCf.md`, `RULE-stack-tauri.md` (Tauri v2 + Rust: never-block-the-UI, version SSOT, target context, the macOS TCC/Gatekeeper boundary for spawned sidecars), `RULE-ui-pattern.md` (design-system layer: the subtraction pass that runs before the tier ladder, class taxonomy, tokens, variant API, and the audit playbook), `RULE-seo.md` (metadata, schema, sitemap, and URL form: relative at rest, absolute only where a consumer requires it), `RULE-release.md`, `RULE-db-design.md`, `RULE-biz.md` (market-facing decisions: positioning, pricing, audience) — plus the analytical methods (`METHOD-*`, loaded on route match like the rest): `METHOD-audit-flow.md` (refactors, multi-file bugs, fragile flows), `METHOD-audit-zero-trust.md` (strict mechanical-first audit: detectors before opinion, exact matches separated from pattern-level candidates), `METHOD-deep-think.md` (scope/architecture/value decisions, first-principles and critique-style thinking), `METHOD-ux-psych.md` (UX/user-behavior evaluation, onboarding and conversion flows), `METHOD-proportionality.md` (sizing a guard, limit or accepted risk against reach, capability, motive and blast radius — the lens that stops both over-engineering and client-side-limits-as-enforcement), `METHOD-audit-subtraction.md` (repo-wide "does this need to exist" sweep, terminating on two dry rounds), and `METHOD-audit-frozen-reference.md` (compliance audit for a clause naming a concrete external artifact as the canonical shape to match — resolve to an exact path, diff literally against it, never judge from memory of the rule's prose).
108
108
  - **Full load on explicit request:** asking, in any wording, to load the whole corpus reads every `RULE-*`/`METHOD-*` file at once.
109
109
 
110
110
  No harness magic beyond the `CLAUDE.md` import: routes are instructions telling Claude to Read the file from `~/.aki/akidevrule/` when the task's domain matches; the full-load request is the escape hatch.
111
111
 
112
112
  ### Addressing — `topic.A1`, and the `⟨Aki⟩` flag
113
113
 
114
- Every rule/method file is internally organized into groups `A`/`B`/`C` and numbered items `1`/`2`/`3…`, so any single rule can be named precisely — `coding.B2` (changing existing code), `stack.C1` (canonical component names) — without touching routing or renaming any file (`topic` is the filename minus its `RULE-`/`METHOD-` prefix). The full group map lives in `payload/index.md`.
114
+ Every rule/method file is internally organized into groups `A`/`B`/`C` and numbered items `1`/`2`/`3…`, so any single rule can be named precisely — `coding.B2` (changing existing code), `stack.C1` (canonical component names) — without touching routing or renaming any file (`topic` is the filename minus its `RULE-`/`METHOD-` prefix). The full group map and the cross-cutting lens (which file holds the root of a subject several files touch) live in `docs/arch/corpus-map.md`, a maintainer's doc that is not installed.
115
115
 
116
116
  Three files (`RULE-seo.md`, `RULE-release.md`, `RULE-stack-akiNuxtCf.md`) mix universal rules with content specific to Aki's own AkiNuxtCf ecosystem (usePageSeo API, releases.json schema, canonical component names, …). That ecosystem-specific content is isolated into each file's **last group**, logically flagged `⟨Aki⟩`. It stays in this public repo and auto-loads like everything else — Aki is this repo's heaviest user, so auto-load stays more valuable than a clean public/private split — but the flag marks exactly what a stripped public export would drop. Every other file, and every group outside `⟨Aki⟩`, is 100% universal.
117
117
 
@@ -137,7 +137,7 @@ A `SessionStart` hook and the installer's `--check` flag both classify install s
137
137
 
138
138
  | State | Condition | Hook (SessionStart) | `install.mjs --check` |
139
139
  |---|---|---|---|
140
- | **missing** | no installed `CHANGELOG.md` (or `index.md`) | announces every session — no 24h throttle | prints "not installed" + the install command |
140
+ | **missing** | no installed `CHANGELOG.md` (or `RULE-agent-behavior.md`) | announces every session — no 24h throttle | prints "not installed" + the install command |
141
141
  | **current** | installed released semver == remote | silent | prints "up to date" (`run_install()` skips its y/n overwrite prompt only when `.version`'s `commit=` equals the checkout HEAD with a clean tree — the overwrite source is the checkout, not remote) |
142
142
  | **update** | remote newer than installed | announces `x.y.z → a.b.c` + the update command (24h throttle) | prints `x.y.z → a.b.c` |
143
143
  | **ahead** | installed newer than remote, or installed CHANGELOG is Unreleased-only | silent — never nag a dev machine | prints "ahead of remote" |
@@ -156,6 +156,7 @@ Install once; from then on the system has two kinds of surface. **Rules load the
156
156
  | "What is installed here and what do I say to it?" | `/akihelp` |
157
157
  | A big, hard-to-reverse, or goal-ambiguous decision | `/akithink` |
158
158
  | Work needing several kinds of judgment, or a parallel fan-out | `/akiflow` |
159
+ | Opening a project — what is still pending here? | `/akiopen` |
159
160
  | A messy working tree that needs clean commits | `/akigitcommit` |
160
161
  | Format lint — or someone called a penalty card | `/akilint` (or just say `[WRAP]` / `[YAP]`) |
161
162
  | Ship a release end-to-end | `/akiship` |
@@ -164,7 +165,7 @@ Install once; from then on the system has two kinds of surface. **Rules load the
164
165
 
165
166
  Three habits that make the system pay off:
166
167
 
167
- - **Cite rules by address, not by pasting them.** Every rule item has a stable address — `coding.B4`, `pattern.A2`, `agent.A3` — mapped in `payload/index.md`. One address in a prompt, review comment, or commit message names an exact obligation without duplicating its text.
168
+ - **Cite rules by address, not by pasting them.** Every rule item has a stable address — `coding.B4`, `pattern.A2`, `agent.A3` — mapped in `docs/arch/corpus-map.md`. One address in a prompt, review comment, or commit message names an exact obligation without duplicating its text.
168
169
  - **Bind each project with a short root `CLAUDE.md`** — project facts and stricter constraints only, referencing the shared corpus instead of copying it (see [Project binding & change policy](#project-binding--change-policy)).
169
170
  - **Edit rules in this repo, never in the installed copies.** Everything under `~/.aki/akidevrule`, `~/.claude/skills`, and the managed parts of `~/.claude/settings.json` is overwritten on every install; the change flow is always source repo → `node install.mjs` (or `npx @akinet/akidevrule@latest`).
170
171
 
@@ -172,7 +173,6 @@ Three habits that make the system pay off:
172
173
 
173
174
  ```text
174
175
  payload/ → installed to ~/.aki/akidevrule/
175
- index.md
176
176
  RULE-agent-behavior.md
177
177
  RULE-coding.md
178
178
  RULE-pattern-core.md
@@ -202,6 +202,7 @@ skills/ → shared Agent Skills corpus (SKILL.md open
202
202
  akiflow/scripts/council_cost.py (tallies per-agent token usage from the transcript at close-out)
203
203
  akiflow/scripts/council_verify.py (mechanical closure gate: ghost seats, missing evidence tags, unanswered REMINDs)
204
204
  akiflow/scripts/scythe.py (penalty-card lint [WRAP]/[YAP] — shared engine of /akilint and the enforcer's evidence sweeps)
205
+ akiflow/scripts/release_lint.py (release-record lint: CHANGELOG section order/vocabulary/level, releases.json parity, type keys, highlight review — RULE-release.md C4, B7 step 4)
205
206
  akiflow/scripts/*.sh (transitional Unix wrappers, one per script above — each execs its .py sibling)
206
207
  akiflow/references/harness-facts.md (subagent/cost/model facts, with sources)
207
208
  akithink/SKILL.md
@@ -209,12 +210,14 @@ skills/ → shared Agent Skills corpus (SKILL.md open
209
210
  akihelp/SKILL.md
210
211
  akigitcommit/SKILL.md
211
212
  akilint/SKILL.md
213
+ akiopen/SKILL.md
212
214
  akiship/SKILL.md
213
215
  aki-article-writer/SKILL.md
214
216
  aki-article-writer/references/article-workflow.md
215
217
 
216
218
  scripts/ → repo-only tooling, never installed
217
219
  test-agy-bias.sh (6-trap agy/Gemini bias regression suite, runnable by anyone with agy — docs/plan/done/agy-helpful-bias-containment.md §3)
220
+ second_hop_audit.py (measures, per route and era, whether Claude Code sessions read the routed rule before the first edit — docs/research/rule-delivery-second-hop-sep29.md)
218
221
 
219
222
  claude/ → Claude Code-only runtime assets, installed to ~/.claude/
220
223
  CLAUDE.md
@@ -225,14 +228,15 @@ claude/ → Claude Code-only runtime assets, installed
225
228
  agents/aki-maker.md
226
229
  hooks/aki-update-check.mjs
227
230
  hooks/aki_version_check.mjs (shared version-status parser, imported by both the hook and install.mjs --check)
231
+ hooks/aki-route-guard.mjs (PreToolUse route gate: denies the first edit of an artifact type until its routed rule was Read)
228
232
  fragments/settings.akidoc.fragment.json (illustrative reference only — never apply manually)
229
233
 
230
234
  docs/ → repo-internal records; one TCC lookup is installed
231
235
  index.md (master doc index)
232
- arch/ (current-state design records: rule delivery, akiflow)
236
+ arch/ (current-state design records: rule delivery, akiflow, corpus-map — the topic.A1 address map and the cross-cutting lens)
233
237
  plan/ · plan/done/ (execution plans; completed plans move to done/)
234
238
  research/ (event records: frozen body, dated amendments, a successor doc when the decision changes)
235
- ref/ (stable lookups; macos-codesign-tcc.md → ~/.aki/akidevrule/docs/ref/)
239
+ ref/ (stable lookups; fact-macos-codesign-tcc.md → ~/.aki/akidevrule/docs/ref/)
236
240
 
237
241
  CLAUDE.md (operating rules for agents working IN this repo — not installed anywhere)
238
242
  GEMINI.md (the per-project Antigravity bootstrap, serving this repo itself; copied into other projects by hand)
@@ -248,9 +252,9 @@ install.ps1 (thin launcher → node install.mjs)
248
252
  flowchart TD
249
253
  subgraph SRC["📦 Source: akidevrule Repo"]
250
254
  PAYLOAD["payload/ (18 raw rule files)"]
251
- TCCREF["docs/ref/macos-codesign-tcc.md"]
255
+ TCCREF["docs/ref/fact-macos-codesign-tcc.md"]
252
256
  PGEMINI["payload/GEMINI.md (template)"]
253
- CSKILLS["skills/ (10 skills, shared open standard)"]
257
+ CSKILLS["skills/ (11 skills, shared open standard)"]
254
258
  CCLAUDE["claude/CLAUDE.md (template)"]
255
259
  CAGENTS["claude/agents/ (5 agent definitions)"]
256
260
  CHOOKS["claude/hooks/aki-update-check.mjs + aki_version_check.mjs (shared parser)"]
@@ -262,7 +266,7 @@ flowchart TD
262
266
  %% TARGET 1: ~/.aki/akidevrule/
263
267
  subgraph T1["📂 1. Shared SSOT Rule Corpus (~/.aki/akidevrule/)"]
264
268
  R_CORPUS["*.md (Raw payload rules)"]
265
- R_TCC["docs/ref/macos-codesign-tcc.md"]
269
+ R_TCC["docs/ref/fact-macos-codesign-tcc.md"]
266
270
  R_AGSKILLS["agskills/ (Shared skill tree for AG)"]
267
271
  R_META[".source-repo & .version"]
268
272
  end
@@ -282,7 +286,7 @@ flowchart TD
282
286
  G_MD["GEMINI.md (Managed prompt global)"]
283
287
  G_LOCAL["GEMINI.local.md (Machine local)"]
284
288
  G_RULES["config/rules/akirule-*.md (one per rule file, YAML trigger)"]
285
- G_SKILLS["config/skills/ (10 skills, native auto-discovery)"]
289
+ G_SKILLS["config/skills/ (11 skills, native auto-discovery)"]
286
290
  G_SJSON["config/skills.json (Inherits agskills, absolute path)"]
287
291
  end
288
292
 
@@ -307,18 +311,18 @@ flowchart TD
307
311
 
308
312
  Targets 4-6 only get the shared skill corpus (no rule corpus / no `CLAUDE.md`/`GEMINI.md`-style overrides — those CLIs have no equivalent hard-load hook this baseline plugs into yet). Each sync is scoped per skill folder name via a Node `fs` copy plus a managed-names-only prune, same never-touch-the-rest guarantee as targets 2 and 3, and runs unconditionally — harmless if that CLI isn't installed on the machine, picked up the moment it is.
309
313
 
310
- 1. Syncs `payload/*` into `~/.aki/akidevrule/` (Node `fs` copy, excludes `ref-ECC/`), removes stale files left by renames, syncs `agskills/` for Antigravity skill inheritance, and deploys the full TCC lookup to `~/.aki/akidevrule/docs/ref/macos-codesign-tcc.md`.
314
+ 1. Syncs `payload/*` into `~/.aki/akidevrule/` (Node `fs` copy, excludes `ref-ECC/`), removes stale files left by renames, syncs `agskills/` for Antigravity skill inheritance, and deploys the full TCC lookup to `~/.aki/akidevrule/docs/ref/fact-macos-codesign-tcc.md`.
311
315
  2. Deploys to **all detected Claude config directories** — `~/.claude` (default primary), all existing `~/.claude*` profile variants (e.g. `~/.claude-9rt`, `~/.claude-prx`), plus `$CLAUDE_CONFIG_DIR` or `--claude-dir <path>` if provided:
312
- - Syncs every skill folder under `skills/*/` (whole directory, including any `references/` or `scripts/`) into `<target>/skills/`, one named folder at a time (copy + managed-names-only prune), removing only Aki's own old/renamed skill directories (`akidoc-*`, `akiadvise`) — any other skill you already have is never touched. `skills/` is a top-level, agent-neutral folder (siblings with `payload/`, not nested under `claude/`) because SKILL.md is a shared open standard both Claude Code and Antigravity/AGY consume identically — see [docs/ref/agent-skills-standard.md](docs/ref/agent-skills-standard.md).
316
+ - Syncs every skill folder under `skills/*/` (whole directory, including any `references/` or `scripts/`) into `<target>/skills/`, one named folder at a time (copy + managed-names-only prune), removing only Aki's own old/renamed skill directories (`akidoc-*`, `akiadvise`) — any other skill you already have is never touched. `skills/` is a top-level, agent-neutral folder (siblings with `payload/`, not nested under `claude/`) because SKILL.md is a shared open standard both Claude Code and Antigravity/AGY consume identically — see [docs/ref/fact-agent-skills-standard.md](docs/ref/fact-agent-skills-standard.md).
313
317
  - Copies `claude/agents/*.md` into `<target>/agents/` **file by file, never a directory mirror with `--delete`** — that folder is a shared namespace where your own agent definitions sit beside Aki's, exactly like `<target>/skills/`, so nothing you did not install is ever removed.
314
318
  - Replaces `<target>/CLAUDE.md` with the packaged guidance (timestamped backup first), appending this machine's source-repo path and an `@<target>/CLAUDE.local.md` import.
315
319
  - Creates `<target>/CLAUDE.local.md` **only if missing** — never overwritten afterward. On profile variants (`~/.claude-*`), the template imports `@~/.claude/CLAUDE.local.md` by default so machine-wide facts are inherited. Put per-machine/per-profile rules there; they survive every reinstall.
316
320
  - Before the confirmation prompt or any mutation, preflights every existing JSON file it may update: each detected profile's `settings.json`, `~/.gemini/config/skills.json`, `~/.gemini/antigravity-cli/settings.json`, and `~/.gemini/settings.json`. A malformed file or non-object root aborts the install with originals untouched. After preflight, updates `<target>/settings.json` with a timestamped backup: read permission for `~/.aki/akidevrule/**`, one `Bash(<launcher> <script>*)` rule per Aki skill script per rendering (absolute and `~/`-literal — Claude Code does not expand `~` before matching), `skillOverrides.akirule = "on"`, idempotent registration of the `SessionStart` update-check hook.
317
- - Installs `<target>/hooks/aki-update-check.mjs` plus its shared parser `<target>/hooks/aki_version_check.mjs`.
321
+ - Installs `<target>/hooks/aki-update-check.mjs` plus its shared parser `<target>/hooks/aki_version_check.mjs`, and `<target>/hooks/aki-route-guard.mjs` registered as a `PreToolUse` hook on `Edit|MultiEdit|Write|NotebookEdit` (idempotent, same filter-then-push as the update check).
318
322
  3. Writes `~/.aki/akidevrule/.version` with `installed=`/`version=`/`commit=`/`branch=` and records the source-repo path in `~/.aki/akidevrule/.source-repo` — `version=` is the just-installed CHANGELOG's latest released semver, the same value `install.mjs --check` and the hook compare against remote.
319
- 4. Installs `payload/GEMINI.md` to `~/.gemini/GEMINI.md` — Antigravity global behavior overrides, stamped with a version marker (`[AKIRULE-AG-OVERRIDES-…]`) on line 1. Generates one native rule file per `RULE-*`/`METHOD-*` under `~/.gemini/config/rules/` with YAML `trigger` frontmatter — `agent` `always_on`, the stacks `glob`, the rest `model_decision` — each description generated from its `akirule` route, so both harnesses route from one table. Deploys 10 skills directly to `~/.gemini/config/skills/` for native auto-discovery (synced per skill folder, same never-touch-the-rest guarantee as step 2), configures `~/.gemini/config/skills.json` with absolute paths as secondary, and merges skill execution permissions into `~/.gemini/antigravity-cli/settings.json` and `~/.gemini/settings.json` — a `command()` prefix rule for every `skills/*/scripts/*.py` per skill root, in both the expanded and the tilde-literal rendering (agy's matcher compares command strings literally — no glob expansion, and no tilde expansion in either direction — so a directory wildcard never matches and a rule only matches a command written the same way; see [docs/ref/cli-permission-allowlist-standard.md](docs/ref/cli-permission-allowlist-standard.md) §1.2) plus scoped `write_file`/`read_file` rules for the council workspace and rule corpus.
323
+ 4. Installs `payload/GEMINI.md` to `~/.gemini/GEMINI.md` — Antigravity global behavior overrides, stamped with a version marker (`[AKIRULE-AG-OVERRIDES-…]`) on line 1. Generates one native rule file per `RULE-*`/`METHOD-*` under `~/.gemini/config/rules/` with YAML `trigger` frontmatter — `agent` `always_on`, the stacks `glob`, the rest `model_decision` — each description generated from its `akirule` route, so both harnesses route from one table. Deploys 11 skills directly to `~/.gemini/config/skills/` for native auto-discovery (synced per skill folder, same never-touch-the-rest guarantee as step 2), configures `~/.gemini/config/skills.json` with absolute paths as secondary, and merges skill execution permissions into `~/.gemini/antigravity-cli/settings.json` and `~/.gemini/settings.json` — a `command()` prefix rule for every `skills/*/scripts/*.py` per skill root, in both the expanded and the tilde-literal rendering (agy's matcher compares command strings literally — no glob expansion, and no tilde expansion in either direction — so a directory wildcard never matches and a rule only matches a command written the same way; see [docs/ref/fact-cli-permission-allowlist-standard.md](docs/ref/fact-cli-permission-allowlist-standard.md) §1.2) plus scoped `write_file`/`read_file` rules for the council workspace and rule corpus.
320
324
  5. Syncs the same skill folders to `~/.agents/skills/` (Codex CLI, Cursor), `~/.kiro/skills/` (Kiro CLI), and `~/.grok/skills/` (Grok CLI) — each a plain global skills root these CLIs read natively, synced per skill folder name exactly like step 2. Skills-only: no rule corpus is generated for these targets.
321
- 6. Pre-allows every Aki skill script in each harness present on the machine, one adapter per rule dialect (`lib/permissions.mjs`): `~/.kiro/settings/permissions.yaml` (a marker-delimited managed block), `~/.codex/rules/akidevrule.rules` (a file akidevrule owns), `~/.cursor/cli-config.json` (`Shell(python3:<script>*)`, never a bare `Shell(python3)`), `~/.config/opencode/opencode.json` (`permission.bash`). Every rule names one exact script, in both path renderings; entries a previous install wrote are replaced, the user's own are kept. Grok CLI and Ollama have no file-based allowlist, so nothing is written for them — [docs/ref/cli-permission-allowlist-standard.md](docs/ref/cli-permission-allowlist-standard.md).
325
+ 6. Pre-allows every Aki skill script in each harness present on the machine, one adapter per rule dialect (`lib/permissions.mjs`): `~/.kiro/settings/permissions.yaml` (a marker-delimited managed block), `~/.codex/rules/akidevrule.rules` (a file akidevrule owns), `~/.cursor/cli-config.json` (`Shell(python3:<script>*)`, never a bare `Shell(python3)`), `~/.config/opencode/opencode.json` (`permission.bash`). Every rule names one exact script, in both path renderings; entries a previous install wrote are replaced, the user's own are kept. Grok CLI and Ollama have no file-based allowlist, so nothing is written for them — [docs/ref/fact-cli-permission-allowlist-standard.md](docs/ref/fact-cli-permission-allowlist-standard.md).
322
326
 
323
327
  Re-running the installer updates the same managed files cleanly.
324
328
 
@@ -342,18 +346,20 @@ No sudo, user-local, easy to inspect and delete, consistent with the Aki ecosyst
342
346
  ```bash
343
347
  rm -rf ~/.aki/akidevrule
344
348
  rm -rf ~/.aki/agent-council # /akiflow session workspaces (self-prunes at 30 days anyway)
345
- rm -rf ~/.claude/skills/{akirule,akiflow,akithink,akihtmlreport,akihelp,akigitcommit,akilint,akiship,aki-article-writer,akidevsync-notes}
346
- rm -rf ~/.agents/skills/{akirule,akiflow,akithink,akihtmlreport,akihelp,akigitcommit,akilint,akiship,aki-article-writer,akidevsync-notes} # Codex CLI
347
- rm -rf ~/.kiro/skills/{akirule,akiflow,akithink,akihtmlreport,akihelp,akigitcommit,akilint,akiship,aki-article-writer,akidevsync-notes} # Kiro CLI
348
- rm -rf ~/.grok/skills/{akirule,akiflow,akithink,akihtmlreport,akihelp,akigitcommit,akilint,akiship,aki-article-writer,akidevsync-notes} # Grok CLI (other, non-Aki skills already in this folder are untouched)
349
+ rm -rf ~/.claude/skills/{akirule,akiflow,akithink,akihtmlreport,akihelp,akigitcommit,akilint,akiopen,akiship,aki-article-writer,akidevsync-notes}
350
+ rm -rf ~/.agents/skills/{akirule,akiflow,akithink,akihtmlreport,akihelp,akigitcommit,akilint,akiopen,akiship,aki-article-writer,akidevsync-notes} # Codex CLI
351
+ rm -rf ~/.kiro/skills/{akirule,akiflow,akithink,akihtmlreport,akihelp,akigitcommit,akilint,akiopen,akiship,aki-article-writer,akidevsync-notes} # Kiro CLI
352
+ rm -rf ~/.grok/skills/{akirule,akiflow,akithink,akihtmlreport,akihelp,akigitcommit,akilint,akiopen,akiship,aki-article-writer,akidevsync-notes} # Grok CLI (other, non-Aki skills already in this folder are untouched)
349
353
  rm -f ~/.claude/agents/aki-{hands,judge,conduct,challenger,maker}.md # your own agents in that folder are untouched
350
- rm -f ~/.claude/hooks/aki-update-check.mjs ~/.claude/hooks/aki_version_check.mjs ~/.claude/hooks/aki-update-check.py ~/.claude/hooks/aki_version_check.py
354
+ rm -f ~/.claude/hooks/aki-update-check.mjs ~/.claude/hooks/aki_version_check.mjs ~/.claude/hooks/aki-route-guard.mjs
355
+ # then delete the two entries whose command names those hooks from hooks.SessionStart and hooks.PreToolUse in ~/.claude/settings.json
356
+ rm -f ~/.claude/hooks/aki-update-check.mjs ~/.claude/hooks/aki_version_check.mjs ~/.claude/hooks/aki-route-guard.mjs ~/.claude/hooks/aki-update-check.py ~/.claude/hooks/aki_version_check.py
351
357
  rm -f ~/.gemini/GEMINI.md # restore from a *.akidevrule-backup-* if needed; GEMINI.local.md is left untouched
352
358
  ```
353
359
 
354
360
  On **Windows** the same targets live under `%USERPROFILE%` (e.g. `%USERPROFILE%\.aki\akidevrule`, `%USERPROFILE%\.claude\skills\...`); remove them with `Remove-Item -Recurse -Force`.
355
361
 
356
- Then remove the akidevrule block from `~/.claude/CLAUDE.md` and its entries (permission, skillOverrides, SessionStart hook) from `~/.claude/settings.json` if desired.
362
+ Then remove the akidevrule block from `~/.claude/CLAUDE.md` and its entries (permission, skillOverrides, SessionStart and PreToolUse hooks) from `~/.claude/settings.json` if desired.
357
363
 
358
364
  ## Content for dev.akitao.com
359
365
 
package/claude/CLAUDE.md CHANGED
@@ -1,39 +1,8 @@
1
1
  # Aki global Claude Code guidance
2
2
 
3
- Keep global context small. Prefer current project files and runtime output over stale docs or memory.
4
-
5
- ## Core rules and the router — mechanically loaded, every session
6
-
7
- @~/.aki/akidevrule/index.md
8
3
  @~/.aki/akidevrule/RULE-agent-behavior.md
9
- @~/.aki/akidevrule/RULE-coding.md
10
- @~/.aki/akidevrule/RULE-pattern-core.md
11
4
  @~/.claude/skills/akirule/SKILL.md
12
5
 
13
- The harness embeds these five when it reads this file at session start — no model decision is involved. `index.md` is the corpus map; `RULE-agent-behavior.md` the behavior floor; `RULE-coding.md` the code-quality floor; `RULE-pattern-core.md` the structural floor; `akirule/SKILL.md` the router that decides which contextual rule files to read on each task turn.
14
-
15
- The router is imported rather than left as a skill because a skill runs only when the model decides to invoke it, and it went uninvoked until the owner asked by name. The same failure earlier promoted `RULE-coding.md` and `RULE-pattern-core.md` to core. All five are paid for in every session; that cost is the price of the guarantee.
16
-
17
- What stays best-effort is the second hop: a routed file enters context only when the model `Read`s it on a route match.
18
-
19
- ## Shared Aki rule source
20
-
21
- Aki's shared rule corpus lives at `~/.aki/akidevrule`.
22
-
23
- **IMPORTANT — editing shared rules:** The installed `~/.aki/akidevrule` directory is a **deployed copy**, not the source of truth. To change a shared rule:
24
- 1. Find the source repo: its absolute path on this machine is recorded in `~/.aki/akidevrule/.source-repo`, written by the installer on every install. Read that file — do not guess a location, and do not ask the user for something already recorded. Ask only if the recorded path no longer exists.
25
- 2. Edit under `<source-repo>/payload/` (shared rule corpus), `<source-repo>/skills/` (Agent Skills, shared with Antigravity), or `<source-repo>/claude/` (Claude Code-only runtime assets: global guidance, hooks, settings fragment).
26
- 3. **Read `<source-repo>/CLAUDE.md` before editing.** It carries that repo's own operating rules — which files must be updated together (`payload/index.md`, `skills/akirule/SKILL.md`, `README.md`, `CHANGELOG.md`), file-naming conventions, and non-goals. This step matters most when the request arrives from *another* project's working directory, where that file is not auto-loaded.
27
- 4. Run the installer to propagate changes to the installed copy — `node install.mjs` (any platform), or `./install.sh` / `.\install.ps1`.
28
-
29
- Never edit the installed `~/.aki/akidevrule` files directly — changes will be silently overwritten on the next install.
30
-
31
- ## Named local corpora
32
-
33
- Doc corpora that live outside any single project are often referred to by short name in conversation (e.g. "UNIDOC", "the standards doc"). Their names, paths, and usage notes are **machine-specific**, so they are recorded in `~/.claude/CLAUDE.local.md` — not in this shared file. When the user names a corpus you cannot resolve, read that file before searching the filesystem or asking.
34
-
35
- ## ref-ECC guard
36
-
37
- `~/.aki/akidevrule/ref-ECC` is intentionally very large. Do not scan, summarize, or bulk-load it by default.
6
+ The harness embeds both at session start: the behavior floor and the router. Every other rule enters context when the model `Read`s it on a route match. For routes with an artifact signature the `aki-route-guard` PreToolUse hook denies the first Edit/Write of that artifact type in a session until the routed files were read — the deny reason names them; read them in full, then retry. Meaning-only routes stay model-dependent: route on meaning, load when the domain is in doubt. A turn that only reads, counts or explains what exists routes nothing.
38
7
 
39
- Only use `ref-ECC` when the user explicitly asks for it or when a task has a specific, narrow need for that reference corpus. Prefer targeted file/path lookup over broad search to avoid context bloat.
8
+ Doc corpora named by short name in conversation ("UNIDOC", "the standards doc") are machine-specific: resolve them in `~/.claude/CLAUDE.local.md` before searching or asking.
@@ -36,12 +36,12 @@ Name every rule file you actually read — nothing else. You get one round, so t
36
36
 
37
37
  The mandate, rule manifest, receipt, and output contract above are the same on every lane. Only the substrate changes. This file *is* the definition when the lane is an in-harness Claude subagent; on every other lane the caller pastes the same four sections into the headless prompt, because **no other CLI reads this file**.
38
38
 
39
- **Position in this table is not precedence.** The size of the surface picks the lane, and for a wide read that is agy backgrounded — the discovery default (`agent.A5`).
39
+ **Position in this table is not precedence.** The size of the surface picks the lane, and for a wide read that is agy backgrounded — the discovery default (`skills/akiflow/references/harness-facts.md` § Cross-CLI worker).
40
40
 
41
41
  | Lane | Invocation | Pick it when | Context / rules the caller must pass |
42
42
  |---|---|---|---|
43
43
  | **Claude subagent · `haiku`** | in-harness spawn of this agent; the frontmatter carries `model: haiku` | the paths are known and few, the result must land in this session, or several hands must run **concurrently** — the Agent tool fans out natively where every headless lane needs its own backgrounding | nothing — the frontmatter carries the tools, and this body carries the manifest. Name the paths and the question in the prompt |
44
- | **agy headless · `gemini-3.8-flash-high`, backgrounded** | `agy --model gemini-3.8-flash-high --mode plan --output-format json -p "<prompt>"` — **prompt last** — launched in the background, because a real sweep costs seconds to a minute and there is no reason for the caller to block on it | the discovery default (`agent.A5`): a wide sweep over a large surface, or the Claude quota is the constraint. ~1M context on a separate quota; `--mode plan` makes read-only mechanical rather than worded. Its weakness is skimming, so pay for it in prompt precision, not a bigger model | `~/.gemini/GEMINI.md` auto-loads the behavior baseline, so the behavior floor is free — but **name the domain rule files and the exact absolute paths anyway**: `cwd` is not a scope boundary for agy |
44
+ | **agy headless · `gemini-3.8-flash-high`, backgrounded** | `agy --model gemini-3.8-flash-high --mode plan --output-format json -p "<prompt>"` — **prompt last** — launched in the background, because a real sweep costs seconds to a minute and there is no reason for the caller to block on it | the discovery default (`skills/akiflow/references/harness-facts.md` § Cross-CLI worker): a wide sweep over a large surface, or the Claude quota is the constraint. ~1M context on a separate quota; `--mode plan` makes read-only mechanical rather than worded. Its weakness is skimming, so pay for it in prompt precision, not a bigger model | `~/.gemini/GEMINI.md` auto-loads the behavior baseline, so the behavior floor is free — but **name the domain rule files and the exact absolute paths anyway**: `cwd` is not a scope boundary for agy |
45
45
  | **kiro-cli headless · `claude-sonnet-4.5`** | `kiro-cli chat --no-interactive --trust-tools=fs_read --model claude-sonnet-4.5 "<prompt>"` | a sweep that is wide **and** needs real reading comprehension — flash skims, and this lane buys a strong model on a **third** quota (owner's pick for hands, 2026-08-16). Not the cheap tier: sonnet-4.5 meters 1.3× against `qwen3-coder-next`'s 0.05×, so pick it for the comprehension, not the price | everything: no rule file loads by itself. Paste the four sections above plus exact paths. `--trust-tools=fs_read` is the read-only mechanism |
46
46
  | **cl-9rt (Claude via proxy gateway)** | `CLAUDE_CONFIG_DIR=~/.claude-9rt claude -p --tools "Read,Grep" --model <alias> --effort low "<prompt>"` | a parallel explore lane is wanted **concurrently** with this session, on separate metering | everything, as with kiro. `cl-9rt`/`cl-9rt-min` is a **shell alias** the owner defined interactively — it does not exist in a spawned/non-interactive shell. Always run the expanded literal command shown here, never the alias name. Note the gateway may route an alias to a non-Anthropic core — treat as bandwidth, never as judgment |
47
47
 
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env node
2
+ // PreToolUse route gate, fail-open. Design: docs/research/rule-delivery-second-hop-sep29.md
3
+ import { readFileSync, existsSync } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ import { join, dirname, basename, extname } from "node:path";
6
+
7
+ const HOME = homedir();
8
+ const RULE_DIR = join(HOME, ".aki", "akidevrule");
9
+ const CONFIG_DIR = process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude");
10
+ const MARK = "aki-route-guard";
11
+ const MAX_DENIALS_PER_RULE = 3; // a detection bug must never lock a session; after this many denials the rule is treated as read
12
+
13
+ const CODE_EXT = new Set(["ts", "tsx", "js", "jsx", "mjs", "cjs", "vue", "svelte", "rs", "py", "go", "rb", "php", "java", "kt", "swift", "c", "cc", "cpp", "h", "hpp", "cs", "sh", "bash", "zsh", "ps1", "sql", "css", "scss", "lua", "dart"]);
14
+ const FRONTEND_EXT = new Set(["vue", "svelte", "css", "scss", "tsx", "jsx"]);
15
+
16
+ function allow() {
17
+ process.exit(0);
18
+ }
19
+
20
+ /** Rule files a path routes to; only routes with an artifact signature (docs/research/rule-delivery-second-hop-sep29.md §2). */
21
+ function routesFor(filePath, cwd) {
22
+ const p = filePath.replace(/\\/g, "/");
23
+ const name = basename(p);
24
+ const ext = extname(name).slice(1).toLowerCase();
25
+ const rules = new Set();
26
+ if (CODE_EXT.has(ext)) {
27
+ rules.add("RULE-coding.md");
28
+ rules.add("RULE-pattern-core.md");
29
+ }
30
+ if (ext === "md") rules.add("RULE-docs.md");
31
+ if (name === "CHANGELOG.md" || name === "releases.json") rules.add("RULE-release.md");
32
+ if (FRONTEND_EXT.has(ext)) rules.add("RULE-ui-pattern.md");
33
+ if (ext === "sql" || /\/migrations\//.test(p)) rules.add("RULE-db-design.md");
34
+ if (/\/(locales|i18n|lang)\//.test(p)) rules.add("RULE-content-write.md");
35
+ if (ext === "rs" || /\/src-tauri\//.test(p) || name === "tauri.conf.json") rules.add("RULE-stack-tauri.md");
36
+ if ((ext === "vue" || ext === "ts") && isNuxtProject(cwd)) rules.add("RULE-stack-akiNuxtCf.md");
37
+ return rules;
38
+ }
39
+
40
+ /** Scratchpad, harness state and the corpus itself: files no project rule governs, so a Q&A session that writes a throwaway script or a memory note stays ungated. */
41
+ function isUngatedPath(filePath, cwd) {
42
+ const p = filePath.replace(/\\/g, "/");
43
+ if (p.includes("/.aki/")) return true;
44
+ if (p.startsWith(CONFIG_DIR.replace(/\\/g, "/") + "/")) return true;
45
+ if (/^\/tmp\//.test(p) || /\/scratchpad\//.test(p)) return true;
46
+ if (cwd && !p.startsWith(cwd.replace(/\\/g, "/") + "/")) return true;
47
+ return false;
48
+ }
49
+
50
+ function isNuxtProject(cwd) {
51
+ if (!cwd) return false;
52
+ return ["nuxt.config.ts", "nuxt.config.js", "nuxt.config.mjs"].some((f) => existsSync(join(cwd, f)));
53
+ }
54
+
55
+ /** Rule files the harness already embeds via `@` imports in the global CLAUDE.md; never gated. */
56
+ function residentRules() {
57
+ const out = new Set();
58
+ try {
59
+ for (const line of readFileSync(join(CONFIG_DIR, "CLAUDE.md"), "utf8").split("\n")) {
60
+ const m = line.match(/^@.*\/((?:RULE|METHOD)-[\w-]+\.md)\s*$/);
61
+ if (m) out.add(m[1]);
62
+ }
63
+ } catch {
64
+ /* no global file: nothing is resident */
65
+ }
66
+ return out;
67
+ }
68
+
69
+ /** The transcript that records this actor's own tool calls: the subagent file when the hook fires inside a subagent, else the session transcript. */
70
+ function transcriptFor(input) {
71
+ const main = input.transcript_path;
72
+ if (input.agent_id) {
73
+ const sub = join(dirname(main), input.session_id, "subagents", `agent-${input.agent_id}.jsonl`);
74
+ if (existsSync(sub)) return sub;
75
+ }
76
+ return main;
77
+ }
78
+
79
+ /** Scan a transcript once: which rule files were Read (Read tool, or a Bash cat/sed/head/bat of the file), and how many times this hook already denied for each. */
80
+ function scanTranscript(path, wanted) {
81
+ const read = new Set();
82
+ const denials = new Map();
83
+ let text;
84
+ try {
85
+ text = readFileSync(path, "utf8");
86
+ } catch {
87
+ return { read, denials, unreadable: true };
88
+ }
89
+ for (const line of text.split("\n")) {
90
+ if (!line.includes("akidevrule") && !line.includes(MARK)) continue;
91
+ let d;
92
+ try {
93
+ d = JSON.parse(line);
94
+ } catch {
95
+ continue;
96
+ }
97
+ if (d.type === "assistant") {
98
+ const content = d.message && Array.isArray(d.message.content) ? d.message.content : [];
99
+ for (const c of content) {
100
+ if (!c || c.type !== "tool_use" || !c.input) continue;
101
+ if (c.name === "Read") {
102
+ const target = String(c.input.file_path || "");
103
+ for (const r of wanted) if (target.endsWith(r)) read.add(r);
104
+ } else if (c.name === "Bash") {
105
+ const cmd = String(c.input.command || "");
106
+ for (const r of wanted) if (new RegExp(`\\b(cat|sed|head|bat|less)\\b[^|;&\\n]*${r.replace(".", "\\.")}`).test(cmd)) read.add(r);
107
+ }
108
+ }
109
+ } else if (d.attachment && typeof d.attachment.stdout === "string" && d.attachment.stdout.includes(MARK)) {
110
+ for (const r of wanted) if (d.attachment.stdout.includes(r)) denials.set(r, (denials.get(r) || 0) + 1);
111
+ }
112
+ }
113
+ return { read, denials, unreadable: false };
114
+ }
115
+
116
+ function deny(missing, filePath) {
117
+ const files = missing.map((r) => `~/.aki/akidevrule/${r}`).join(" and ");
118
+ const reason = `[${MARK}] Editing ${basename(filePath)} is gated on rule files not yet read in this session: ${missing.join(", ")}. Read ${files} in full with the Read tool, add them to the [RULES] receipt, then retry this edit.`;
119
+ process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: reason } }) + "\n");
120
+ process.exit(0);
121
+ }
122
+
123
+ function main() {
124
+ if (process.env.AKI_ROUTE_GUARD === "0") allow();
125
+ const input = JSON.parse(readFileSync(0, "utf8"));
126
+ const ti = input.tool_input || {};
127
+ const filePath = ti.file_path || ti.notebook_path;
128
+ if (!filePath || !input.transcript_path) allow();
129
+ if (isUngatedPath(filePath, input.cwd)) allow();
130
+ const wanted = [...routesFor(filePath, input.cwd)].filter((r) => existsSync(join(RULE_DIR, r)));
131
+ const resident = residentRules();
132
+ const gated = wanted.filter((r) => !resident.has(r));
133
+ if (gated.length === 0) allow();
134
+ const { read, denials, unreadable } = scanTranscript(transcriptFor(input), gated);
135
+ if (unreadable) allow();
136
+ const missing = gated.filter((r) => !read.has(r) && (denials.get(r) || 0) < MAX_DENIALS_PER_RULE);
137
+ if (missing.length === 0) allow();
138
+ deny(missing, filePath);
139
+ }
140
+
141
+ try {
142
+ main();
143
+ } catch {
144
+ allow();
145
+ }