@akinet/akidevrule 3.1.1 → 3.3.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 +20 -0
- package/README.md +13 -11
- package/install.mjs +306 -189
- package/package.json +1 -1
- package/payload/RULE-agent-behavior.md +7 -3
- package/payload/RULE-release.md +27 -4
- package/payload/index.md +2 -2
- package/skills/akirule/SKILL.md +1 -0
- package/skills/akiship/SKILL.md +16 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
## [3.3.0] - 2026-09-19
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- **`/akiship` and `release` are hardened: mandatory rule load, fail-closed checklist receipts, a migration doctrine, and post-deploy functional verification (`release.B5`, `B7`, `B11`; `akiship`).** Evidence: a release passed the full ritual and shipped an index on a new column created inside a constructor before the column existed, so the store failed to open on the upgraded production database; every gate stayed green because the migration was startup code (not a script the gate looked for), every test started from an empty database, the post-deploy check read only the version, and the health endpoint returned a hardcoded `ok`. Separately, the ritual has been run loosely on several recent releases, and an advisory checklist cannot be audited. Root cause: the gate modelled migration as a separate script, checked only by location; nothing forced upgrade-path rehearsal, function-level post-deploy verification, or proof that each step ran. Mechanism: `akiship` now opens with a CRITICAL mandatory `Read` of `RULE-release.md` and `RULE-docs.md` (a run without it is invalid); `B7` becomes fail-closed: one `S<n>` receipt line with quoted evidence per step, a missing line means NOT RUN and forbids commit/mint/tag/push/deploy, hedge words are scored unverified, and four self-interrogation questions are answered in the report; `B5` adds a five-point migration doctrine (detect by effect with a diff detector run every release, separate artifact, expand → migrate → deploy code → contract, rehearse from the previous state never from empty, asserted postconditions and a named rollback); new `B11` requires exercising a data path after every deploy and calls a constant-`ok` health endpoint a false instrument. Rejected: making the health endpoint fail the process on any subsystem error (a non-critical component would then take the whole service down; degrade visibly instead), and a per-project patch (the gap is in the shared gate). Tradeoff: every release now writes more evidence; that is the intended cost. `akirule` routes `migration`, `/health` and `post-deploy` signals to `RULE-release.md`.
|
|
9
|
+
- **`agent.A2`/`agent.A5` now narrow exploration at the source before crossing a worker boundary.** Evidence: three independent over-exploration incidents spent approximately 92k tokens and 30 minutes on the Gegrok/checkjoin investigation, 67,329 tokens with 16 tool uses over 568,376 ms on the first delegation-rule review, and 56,965 tokens with 8 tool uses over 442,002 ms on the ladder stress test. Root cause: A2 and A5 each established an asymmetric default-to-delegate policy, while the inline exception was limited to one already-known file and worker startup, briefing, latency, observability, and invisible cross-process spend arrived only after the license. Mechanism: A2 now owns direct batching and source narrowing and points to A5 as the sole route decision; A5 runs a bounded command, restricted read, pipeline, or batch when it can return the answer or a lossless decision digest, permits one bounded probe when the route is unclear and lets that probe finish the task, delegates only unavoidable context flooding with an exact brief, keeps conversation-dependent or ambiguous judgment in the caller, prefers a fresh worker over a fork, and denies recursive delegation without explicit depth and width. Rejected: fixed token/time/file thresholds (not reliably measurable or portable), automatic delegation on a third discovery operation (detects drift but chooses the wrong route for small work), and a penalty card (this is a pre-action decision defect, not a locally repairable output defect). Full evidence and six-round critique: `docs/plan/done/rebalance-exploration-delegation.md`.
|
|
10
|
+
|
|
11
|
+
## [3.2.0] - 2026-09-15
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- **Installer auto-discovers and deploys to all Claude config directory profiles (`~/.claude*`, `$CLAUDE_CONFIG_DIR`, `--claude-dir`).**
|
|
15
|
+
- **Evidence:** Users running alternate Claude Code profiles (such as `CLAUDE_CONFIG_DIR=~/.claude-9rt` or `~/.claude-prx` for proxy routing or multi-account quotas) noticed that running `./install.sh` / `node install.mjs` only installed skills, agents, hooks, and CLAUDE.md into the default `~/.claude/`. Alternate profiles were missing new agents, updated skills, and update hooks unless manually symlinked or copied per folder.
|
|
16
|
+
- **Root cause:** `install.mjs` hardcoded `CLAUDE_DIR = join(HOME, ".claude")` with no auto-detection, no `$CLAUDE_CONFIG_DIR` awareness, and no CLI parameter for alternate profile roots.
|
|
17
|
+
- **Mechanism:** `getClaudeDirs()` auto-discovers all `~/.claude*` directories under `$HOME`, respects `$CLAUDE_CONFIG_DIR` and `--claude-dir <path>`, deduplicates canonical paths across symlinks, and synchronizes skills, agents, hooks, `CLAUDE.md`, and `settings.json` across all detected profiles in a single install pass. New variant profiles automatically seed `CLAUDE.local.md` importing `@~/.claude/CLAUDE.local.md` to inherit machine-local facts. Antigravity and Kiro permission allowlists now expand to cover skill scripts across all detected Claude roots.
|
|
18
|
+
- **Tradeoff / rejected alternative:** Requiring the user to run `CLAUDE_CONFIG_DIR=... ./install.sh` separately for each profile was rejected — it is tedious, easy to forget, and leaves profiles silently drifting out of sync. Auto-discovering all sibling `~/.claude*` profiles by default makes a single install update the entire machine's Claude environments simultaneously.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- **Installer preflight now prevents partial installs, legacy migration follows confirmation, and Antigravity permission-only updates persist.** Evidence: malformed Claude, Gemini skills, or Antigravity settings could be handled during mutation; legacy root migration could run before an interactive cancellation; and Antigravity's `allowNonWorkspaceAccess`, `agentMode`, and `trustedWorkspaces` did not save when they were the only differences. Root cause: not every later-mutated JSON file was validated up front, malformed Gemini files were silently replaced or skipped, migration preceded confirmation, and those Antigravity fields did not update change tracking. Mechanism: before the prompt or any mutation, the installer requires every existing Claude `settings.json`, Gemini `config/skills.json`, Antigravity CLI settings, and Gemini settings to parse as an object; cancellation precedes migration; later merges parse directly and Antigravity fields mark their settings file changed. Tradeoff / rejected alternative: continuing with valid profiles or silently replacing malformed JSON was rejected because partial machine configuration and lost user configuration are harder to repair than one explicit failed install.
|
|
22
|
+
|
|
3
23
|
## [3.1.1] - 2026-09-15
|
|
4
24
|
|
|
5
25
|
### Fixed
|
package/README.md
CHANGED
|
@@ -72,7 +72,7 @@ Interpreter convention (documented once): the installer and hooks run on `node`
|
|
|
72
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. |
|
|
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 checklist unattended — diff-scoped hygiene (scythe, dead code, comment doc-refs on the accumulation only), 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. |
|
|
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). |
|
|
76
76
|
|
|
77
77
|
### Five agent definitions
|
|
78
78
|
|
|
@@ -267,8 +267,8 @@ flowchart TD
|
|
|
267
267
|
R_META[".source-repo & .version"]
|
|
268
268
|
end
|
|
269
269
|
|
|
270
|
-
%% TARGET 2: ~/.claude/
|
|
271
|
-
subgraph T2["🤖 2. Claude Code Agent (~/.claude/)"]
|
|
270
|
+
%% TARGET 2: ~/.claude/ and ~/.claude-*
|
|
271
|
+
subgraph T2["🤖 2. Claude Code Agent (~/.claude/ & ~/.claude-*)"]
|
|
272
272
|
C_MD["CLAUDE.md (Managed prompt)"]
|
|
273
273
|
C_LOCAL["CLAUDE.local.md (Machine local)"]
|
|
274
274
|
C_SKILLS["skills/<skill_name>/SKILL.md"]
|
|
@@ -308,14 +308,16 @@ flowchart TD
|
|
|
308
308
|
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
309
|
|
|
310
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`.
|
|
311
|
-
2.
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
311
|
+
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).
|
|
313
|
+
- 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
|
+
- 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
|
+
- 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
|
+
- 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/**`, skill script execution permissions (`Bash(python3 <target>/skills/*)` and `Bash(python3 ~/.claude/skills/*)`), `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`.
|
|
318
|
+
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 18 native rule files under `~/.gemini/config/rules/` with YAML `trigger` frontmatter. 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 each of akiflow's five scripts (the only skill whose scripts agy invokes directly) 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.
|
|
320
|
+
5. Syncs the same skill folders to `~/.agents/skills/` (Codex CLI), `~/.kiro/skills/` (Kiro CLI, plus pre-allowed shell permissions in `~/.kiro/settings/permissions.yaml`), 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.
|
|
319
321
|
|
|
320
322
|
Re-running the installer updates the same managed files cleanly.
|
|
321
323
|
|
package/install.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
copyFileSync,
|
|
11
11
|
rmSync,
|
|
12
12
|
renameSync,
|
|
13
|
+
realpathSync,
|
|
13
14
|
} from "node:fs";
|
|
14
15
|
import { homedir } from "node:os";
|
|
15
16
|
import { join, dirname, basename, extname, relative, isAbsolute, sep } from "node:path";
|
|
@@ -37,7 +38,7 @@ const HOME = homedir();
|
|
|
37
38
|
const INSTALL_ROOT = join(HOME, ".aki", "akidevrule");
|
|
38
39
|
|
|
39
40
|
const LEGACY_INSTALL_ROOT = join(HOME, ".aki", "claudedoc");
|
|
40
|
-
const
|
|
41
|
+
const PRIMARY_CLAUDE_DIR = join(HOME, ".claude");
|
|
41
42
|
const GEMINI_DIR = join(HOME, ".gemini");
|
|
42
43
|
const GEMINI_RULES_DIR = join(GEMINI_DIR, "config", "rules");
|
|
43
44
|
const GEMINI_SKILLS_DIR = join(GEMINI_DIR, "config", "skills");
|
|
@@ -50,6 +51,83 @@ const OLD_SKILLS = ["akidoc-rules", "akidoc-flow-audit", "akidoc-techbiz-optimiz
|
|
|
50
51
|
|
|
51
52
|
const IS_WIN = process.platform === "win32";
|
|
52
53
|
|
|
54
|
+
if (Number(process.versions.node.split(".")[0]) < 18) {
|
|
55
|
+
console.error("akidevrule requires Node.js 18 or later.");
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function expandTilde(p) {
|
|
60
|
+
if (!p) return "";
|
|
61
|
+
if (p === "~") return HOME;
|
|
62
|
+
if (p.startsWith("~" + sep) || p.startsWith("~/")) {
|
|
63
|
+
return join(HOME, p.slice(2));
|
|
64
|
+
}
|
|
65
|
+
return p;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function toTildePath(p) {
|
|
69
|
+
if (!p) return "";
|
|
70
|
+
if (p === HOME) return "~";
|
|
71
|
+
if (p.startsWith(HOME + sep) || p.startsWith(HOME + "/")) {
|
|
72
|
+
return "~" + p.slice(HOME.length).replace(/\\/g, "/");
|
|
73
|
+
}
|
|
74
|
+
return p.replace(/\\/g, "/");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function getClaudeDirs(argv = []) {
|
|
78
|
+
const dirs = new Set();
|
|
79
|
+
dirs.add(PRIMARY_CLAUDE_DIR);
|
|
80
|
+
|
|
81
|
+
// 1. Explicit CLI arguments: --claude-dir <path> or --claude-dir=<path>
|
|
82
|
+
for (let i = 0; i < argv.length; i++) {
|
|
83
|
+
const arg = argv[i];
|
|
84
|
+
if (arg === "--claude-dir" && argv[i + 1]) {
|
|
85
|
+
const p = expandTilde(argv[i + 1].trim());
|
|
86
|
+
dirs.add(isAbsolute(p) ? p : join(HOME, p));
|
|
87
|
+
} else if (arg.startsWith("--claude-dir=")) {
|
|
88
|
+
const p = expandTilde(arg.slice("--claude-dir=".length).trim());
|
|
89
|
+
dirs.add(isAbsolute(p) ? p : join(HOME, p));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 2. Environment variable: CLAUDE_CONFIG_DIR
|
|
94
|
+
if (process.env.CLAUDE_CONFIG_DIR) {
|
|
95
|
+
const p = expandTilde(process.env.CLAUDE_CONFIG_DIR.trim());
|
|
96
|
+
if (p) dirs.add(isAbsolute(p) ? p : join(HOME, p));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 3. Auto-discover all ~/.claude* directories in HOME
|
|
100
|
+
try {
|
|
101
|
+
for (const name of listDir(HOME)) {
|
|
102
|
+
if (!name.startsWith(".claude")) continue;
|
|
103
|
+
const full = join(HOME, name);
|
|
104
|
+
if (!isDir(full)) continue;
|
|
105
|
+
if (name.includes("backup") || name.endsWith(".bak")) continue;
|
|
106
|
+
dirs.add(full);
|
|
107
|
+
}
|
|
108
|
+
} catch {
|
|
109
|
+
/* ignore */
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 4. Deduplicate symlinks resolving to the same real directory
|
|
113
|
+
const canonicalMap = new Map();
|
|
114
|
+
for (const d of dirs) {
|
|
115
|
+
let canonical = d;
|
|
116
|
+
try {
|
|
117
|
+
canonical = realpathSync(d);
|
|
118
|
+
} catch {
|
|
119
|
+
/* directory might not exist yet */
|
|
120
|
+
}
|
|
121
|
+
if (!canonicalMap.has(canonical)) {
|
|
122
|
+
canonicalMap.set(canonical, d);
|
|
123
|
+
} else if (d === canonical) {
|
|
124
|
+
canonicalMap.set(canonical, d);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return Array.from(canonicalMap.values()).sort();
|
|
129
|
+
}
|
|
130
|
+
|
|
53
131
|
function pad2(n) {
|
|
54
132
|
return String(n).padStart(2, "0");
|
|
55
133
|
}
|
|
@@ -185,10 +263,10 @@ function syncAkiSkills(destRoot) {
|
|
|
185
263
|
}
|
|
186
264
|
}
|
|
187
265
|
|
|
188
|
-
function syncAkiAgents() {
|
|
266
|
+
function syncAkiAgents(destClaudeDir) {
|
|
189
267
|
const agentsSrc = join(REPO_ROOT, "claude", "agents");
|
|
190
268
|
if (!isDir(agentsSrc)) return;
|
|
191
|
-
const dest = join(
|
|
269
|
+
const dest = join(destClaudeDir, "agents");
|
|
192
270
|
mkdirSync(dest, { recursive: true });
|
|
193
271
|
for (const name of listDir(agentsSrc).sort()) {
|
|
194
272
|
if (name.endsWith(".md")) copyFileSync(join(agentsSrc, name), join(dest, name));
|
|
@@ -396,6 +474,22 @@ function installAgRules() {
|
|
|
396
474
|
// settings.json merge (Claude Code)
|
|
397
475
|
// ---------------------------------------------------------------------------
|
|
398
476
|
|
|
477
|
+
function validateJsonObject(path, label) {
|
|
478
|
+
if (!isFile(path)) return;
|
|
479
|
+
const data = JSON.parse(readFileSync(path, "utf-8"));
|
|
480
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
481
|
+
throw new Error(`${label} must be a JSON object: ${path}`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function preflightInstallSettings(claudeDirs) {
|
|
486
|
+
for (const claudeDir of claudeDirs) validateJsonObject(join(claudeDir, "settings.json"), "Claude settings");
|
|
487
|
+
if (!isDir(GEMINI_DIR)) return;
|
|
488
|
+
validateJsonObject(join(GEMINI_DIR, "config", "skills.json"), "Gemini skills config");
|
|
489
|
+
validateJsonObject(join(GEMINI_DIR, "antigravity-cli", "settings.json"), "Antigravity settings");
|
|
490
|
+
validateJsonObject(join(GEMINI_DIR, "settings.json"), "Antigravity settings");
|
|
491
|
+
}
|
|
492
|
+
|
|
399
493
|
function mergeSettings(settingsPath, installRoot, claudeDir) {
|
|
400
494
|
const data = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
401
495
|
|
|
@@ -419,6 +513,11 @@ function mergeSettings(settingsPath, installRoot, claudeDir) {
|
|
|
419
513
|
"Bash(python3 ~/.claude/skills/*)",
|
|
420
514
|
"Bash(python3 ~/.aki/akidevrule/agskills/*)",
|
|
421
515
|
];
|
|
516
|
+
const variantBashRule = `Bash(python3 ${toTildePath(join(claudeDir, "skills"))}/*)`;
|
|
517
|
+
if (!managedBashRules.includes(variantBashRule)) {
|
|
518
|
+
managedBashRules.push(variantBashRule);
|
|
519
|
+
}
|
|
520
|
+
|
|
422
521
|
perms.allow = perms.allow.filter((x) => !legacyBashRules.includes(x));
|
|
423
522
|
for (const rule of managedBashRules) if (!perms.allow.includes(rule)) perms.allow.push(rule);
|
|
424
523
|
|
|
@@ -462,7 +561,7 @@ function mergeSettings(settingsPath, installRoot, claudeDir) {
|
|
|
462
561
|
// Antigravity permissions merge
|
|
463
562
|
// ---------------------------------------------------------------------------
|
|
464
563
|
|
|
465
|
-
function mergeAntigravityPermissions() {
|
|
564
|
+
function mergeAntigravityPermissions(claudeDirs = []) {
|
|
466
565
|
if (!isDir(GEMINI_DIR)) return;
|
|
467
566
|
|
|
468
567
|
const scriptsDir = join(REPO_ROOT, "skills", "akiflow", "scripts");
|
|
@@ -470,7 +569,8 @@ function mergeAntigravityPermissions() {
|
|
|
470
569
|
.filter((n) => n.endsWith(".py"))
|
|
471
570
|
.map((n) => `akiflow/scripts/${n}`)
|
|
472
571
|
.sort();
|
|
473
|
-
const
|
|
572
|
+
const claudeRoots = claudeDirs.map((d) => join(d, "skills"));
|
|
573
|
+
const skillRoots = [GEMINI_SKILLS_DIR, ...claudeRoots];
|
|
474
574
|
const launchers = IS_WIN ? ["py -3", "python", "python3"] : ["python3"];
|
|
475
575
|
|
|
476
576
|
const managedCommands = [];
|
|
@@ -506,18 +606,7 @@ function mergeAntigravityPermissions() {
|
|
|
506
606
|
|
|
507
607
|
for (const target of targetFiles) {
|
|
508
608
|
let data = {};
|
|
509
|
-
if (isFile(target))
|
|
510
|
-
try {
|
|
511
|
-
data = JSON.parse(readFileSync(target, "utf-8"));
|
|
512
|
-
} catch (e) {
|
|
513
|
-
console.log(` \u26a0\ufe0f Antigravity: ${target} is not parseable JSON (${e}) \u2014 skipped, re-run the installer to retry.`);
|
|
514
|
-
continue;
|
|
515
|
-
}
|
|
516
|
-
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
517
|
-
console.log(` \u26a0\ufe0f Antigravity: ${target} top-level is not an object \u2014 skipped, fix it manually.`);
|
|
518
|
-
continue;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
609
|
+
if (isFile(target)) data = JSON.parse(readFileSync(target, "utf-8"));
|
|
521
610
|
|
|
522
611
|
if (typeof data.permissions !== "object" || data.permissions === null || Array.isArray(data.permissions))
|
|
523
612
|
data.permissions = {};
|
|
@@ -539,6 +628,23 @@ function mergeAntigravityPermissions() {
|
|
|
539
628
|
}
|
|
540
629
|
}
|
|
541
630
|
|
|
631
|
+
if (perms.allowNonWorkspaceAccess !== true) {
|
|
632
|
+
perms.allowNonWorkspaceAccess = true;
|
|
633
|
+
changed = true;
|
|
634
|
+
}
|
|
635
|
+
if (perms.agentMode !== true) {
|
|
636
|
+
perms.agentMode = true;
|
|
637
|
+
changed = true;
|
|
638
|
+
}
|
|
639
|
+
if (!Array.isArray(perms.trustedWorkspaces)) {
|
|
640
|
+
perms.trustedWorkspaces = [];
|
|
641
|
+
changed = true;
|
|
642
|
+
}
|
|
643
|
+
if (!perms.trustedWorkspaces.includes(HOME)) {
|
|
644
|
+
perms.trustedWorkspaces.push(HOME);
|
|
645
|
+
changed = true;
|
|
646
|
+
}
|
|
647
|
+
|
|
542
648
|
if (changed || !isFile(target)) {
|
|
543
649
|
mkdirSync(dirname(target), { recursive: true });
|
|
544
650
|
if (isFile(target)) {
|
|
@@ -554,7 +660,7 @@ function mergeAntigravityPermissions() {
|
|
|
554
660
|
// Kiro permissions merge
|
|
555
661
|
// ---------------------------------------------------------------------------
|
|
556
662
|
|
|
557
|
-
function mergeKiroPermissions() {
|
|
663
|
+
function mergeKiroPermissions(claudeDirs = []) {
|
|
558
664
|
const kiroDir = join(HOME, ".kiro");
|
|
559
665
|
if (!isDir(kiroDir)) return;
|
|
560
666
|
|
|
@@ -564,10 +670,13 @@ function mergeKiroPermissions() {
|
|
|
564
670
|
|
|
565
671
|
const managedMatches = [
|
|
566
672
|
"python3 ~/.kiro/skills/*",
|
|
567
|
-
"python3 ~/.claude/skills/*",
|
|
568
673
|
"python3 ~/.gemini/config/skills/*",
|
|
569
674
|
"python3 ~/.aki/akidevrule/agskills/*",
|
|
570
675
|
];
|
|
676
|
+
for (const cDir of claudeDirs) {
|
|
677
|
+
const m = `python3 ${toTildePath(join(cDir, "skills"))}/*`;
|
|
678
|
+
if (!managedMatches.includes(m)) managedMatches.push(m);
|
|
679
|
+
}
|
|
571
680
|
|
|
572
681
|
if (!isFile(yamlPath)) {
|
|
573
682
|
const lines = [
|
|
@@ -593,6 +702,76 @@ function mergeKiroPermissions() {
|
|
|
593
702
|
}
|
|
594
703
|
}
|
|
595
704
|
|
|
705
|
+
// ---------------------------------------------------------------------------
|
|
706
|
+
// Deploy single Claude target
|
|
707
|
+
// ---------------------------------------------------------------------------
|
|
708
|
+
|
|
709
|
+
function installClaudeDir(claudeDir) {
|
|
710
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
711
|
+
|
|
712
|
+
// 1. Skills
|
|
713
|
+
syncAkiSkills(join(claudeDir, "skills"));
|
|
714
|
+
|
|
715
|
+
// 2. Agents
|
|
716
|
+
syncAkiAgents(claudeDir);
|
|
717
|
+
|
|
718
|
+
// 3. Hooks
|
|
719
|
+
const hooksDest = join(claudeDir, "hooks");
|
|
720
|
+
mkdirSync(hooksDest, { recursive: true });
|
|
721
|
+
copyFileSync(join(REPO_ROOT, "claude", "hooks", "aki-update-check.mjs"), join(hooksDest, "aki-update-check.mjs"));
|
|
722
|
+
copyFileSync(join(REPO_ROOT, "claude", "hooks", "aki_version_check.mjs"), join(hooksDest, "aki_version_check.mjs"));
|
|
723
|
+
for (const legacy of ["aki-update-check.py", "aki_version_check.py"]) {
|
|
724
|
+
const p = join(hooksDest, legacy);
|
|
725
|
+
if (existsSync(p)) rmrf(p);
|
|
726
|
+
}
|
|
727
|
+
const pyCache = join(hooksDest, "__pycache__");
|
|
728
|
+
if (isDir(pyCache)) {
|
|
729
|
+
for (const n of readdirSync(pyCache)) if (n.startsWith("aki_version_check.")) rmrf(join(pyCache, n));
|
|
730
|
+
if (readdirSync(pyCache).length === 0) rmrf(pyCache);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// 4. CLAUDE.md
|
|
734
|
+
const claudeMd = join(claudeDir, "CLAUDE.md");
|
|
735
|
+
backup(claudeMd);
|
|
736
|
+
pruneBackups(claudeMd);
|
|
737
|
+
|
|
738
|
+
const claudeMdSrc = readFileSync(join(REPO_ROOT, "claude", "CLAUDE.md"), "utf-8");
|
|
739
|
+
const propagateCmd = `node "${join(REPO_ROOT, "install.mjs")}"`;
|
|
740
|
+
const localMd = join(claudeDir, "CLAUDE.local.md");
|
|
741
|
+
const localMdTilde = toTildePath(localMd);
|
|
742
|
+
const ruleSourceBlock =
|
|
743
|
+
"\n## akidevrule — edit source, not deployed copy (ABSOLUTE)\n\n" +
|
|
744
|
+
`The deployed rule files at \`${INSTALL_ROOT}\` are **overwritten on every install**.\n` +
|
|
745
|
+
"To change any shared rule:\n" +
|
|
746
|
+
`1. Edit in the **source repo**: \`${join(REPO_ROOT, "payload")}/\`\n` +
|
|
747
|
+
`2. Run \`${propagateCmd}\` to propagate.\n\n` +
|
|
748
|
+
`**NEVER edit files under \`${INSTALL_ROOT}\` directly** — changes will be silently lost on the next install.\n\n` +
|
|
749
|
+
`@${localMdTilde}\n`;
|
|
750
|
+
writeTextLf(claudeMd, claudeMdSrc + ruleSourceBlock);
|
|
751
|
+
|
|
752
|
+
// 5. CLAUDE.local.md (create-only if missing)
|
|
753
|
+
if (!isFile(localMd)) {
|
|
754
|
+
const isPrimary = claudeDir === PRIMARY_CLAUDE_DIR;
|
|
755
|
+
const templateContent = isPrimary
|
|
756
|
+
? "# Machine-local Claude instructions\n\n" +
|
|
757
|
+
"This file is machine-specific and never touched by akidevrule installs.\n" +
|
|
758
|
+
"Add any per-machine rules here (e.g. build constraints, IDE paths, remote flags).\n"
|
|
759
|
+
: "# Machine-local Claude instructions (profile variant)\n\n" +
|
|
760
|
+
"This file is machine-specific and never touched by akidevrule installs.\n" +
|
|
761
|
+
"@~/.claude/CLAUDE.local.md\n\n" +
|
|
762
|
+
"# Add any profile-specific instructions below:\n";
|
|
763
|
+
writeTextLf(localMd, templateContent);
|
|
764
|
+
console.log(`📝 Created ${localMd} (machine-local template)`);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// 6. settings.json
|
|
768
|
+
const settingsPath = join(claudeDir, "settings.json");
|
|
769
|
+
if (!isFile(settingsPath)) writeTextLf(settingsPath, "{}\n");
|
|
770
|
+
backup(settingsPath);
|
|
771
|
+
pruneBackups(settingsPath);
|
|
772
|
+
mergeSettings(settingsPath, INSTALL_ROOT, claudeDir);
|
|
773
|
+
}
|
|
774
|
+
|
|
596
775
|
// ---------------------------------------------------------------------------
|
|
597
776
|
// skills.json for Antigravity
|
|
598
777
|
// ---------------------------------------------------------------------------
|
|
@@ -600,14 +779,7 @@ function mergeKiroPermissions() {
|
|
|
600
779
|
function updateSkillsJson() {
|
|
601
780
|
const skillsJson = join(GEMINI_DIR, "config", "skills.json");
|
|
602
781
|
mkdirSync(dirname(skillsJson), { recursive: true });
|
|
603
|
-
|
|
604
|
-
if (existsSync(skillsJson)) {
|
|
605
|
-
try {
|
|
606
|
-
data = JSON.parse(readFileSync(skillsJson, "utf-8"));
|
|
607
|
-
} catch {
|
|
608
|
-
data = {};
|
|
609
|
-
}
|
|
610
|
-
}
|
|
782
|
+
const data = isFile(skillsJson) ? JSON.parse(readFileSync(skillsJson, "utf-8")) : {};
|
|
611
783
|
if (!Array.isArray(data.entries)) data.entries = [];
|
|
612
784
|
const absPath = join(INSTALL_ROOT, "agskills");
|
|
613
785
|
const tildePath = "~/.aki/akidevrule/agskills";
|
|
@@ -663,78 +835,71 @@ async function printVersionCheck() {
|
|
|
663
835
|
// inspect_status (pre-install preview)
|
|
664
836
|
// ---------------------------------------------------------------------------
|
|
665
837
|
|
|
666
|
-
async function inspectStatus() {
|
|
838
|
+
async function inspectStatus(claudeDirs) {
|
|
667
839
|
console.log(cyanBold("=== SYSTEM STATUS CHECK BEFORE INSTALL ==="));
|
|
668
840
|
|
|
669
841
|
const head = gitShortHash(REPO_ROOT);
|
|
670
842
|
const sameCheckout = !!head && installedCommit() === head && gitTreeClean(REPO_ROOT);
|
|
671
843
|
if (sameCheckout)
|
|
672
|
-
console.log(
|
|
844
|
+
console.log(`✅ Already installed from this exact commit (${head}, clean tree) — reinstalling refreshes identical content.`);
|
|
673
845
|
|
|
674
846
|
const { state, localVersion, remoteVersion } = await getVersionStatus();
|
|
675
847
|
if (state === STATE_CURRENT)
|
|
676
|
-
console.log(
|
|
848
|
+
console.log(`ℹ️ Installed release ${localVersion} matches remote; the overwrite below comes from this checkout, which may carry [Unreleased] work.`);
|
|
677
849
|
else if (state === STATE_UPDATE)
|
|
678
|
-
console.log(
|
|
850
|
+
console.log(`🔔 A newer akidevrule is available: ${localVersion} → ${remoteVersion} (this install only refreshes local files at the current repo checkout's version).`);
|
|
679
851
|
else if (state === STATE_MISSING)
|
|
680
|
-
console.log("
|
|
852
|
+
console.log("📦 Fresh install — akidevrule is not currently installed on this machine.");
|
|
681
853
|
else if (state === STATE_UNKNOWN)
|
|
682
|
-
console.log("
|
|
854
|
+
console.log("⚠️ Could not reach the remote CHANGELOG to compare versions (network/parse error) — proceeding with local install only.");
|
|
683
855
|
else if (state === STATE_AHEAD)
|
|
684
|
-
console.log(
|
|
856
|
+
console.log(`ℹ️ Local install (${localVersion || "Unreleased-only"}) is ahead of or diverged from remote (${remoteVersion}).`);
|
|
685
857
|
|
|
686
|
-
if (isDir(INSTALL_ROOT)) console.log(
|
|
687
|
-
else console.log(
|
|
858
|
+
if (isDir(INSTALL_ROOT)) console.log(`📦 Payload rules: will ${yellowBold("OVERWRITE")} ${INSTALL_ROOT}`);
|
|
859
|
+
else console.log(`📦 Payload rules: will ${greenBold("CREATE")} at ${INSTALL_ROOT}`);
|
|
688
860
|
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
861
|
+
console.log(`🤖 Claude config targets (${claudeDirs.length}): ${claudeDirs.map(toTildePath).join(", ")}`);
|
|
862
|
+
for (const claudeDir of claudeDirs) {
|
|
863
|
+
const claudeMd = join(claudeDir, "CLAUDE.md");
|
|
864
|
+
if (isFile(claudeMd)) console.log(` 📝 Global CLAUDE.md (${toTildePath(claudeDir)}): will ${yellowBold("OVERWRITE")} (backed up)`);
|
|
865
|
+
else console.log(` 📝 Global CLAUDE.md (${toTildePath(claudeDir)}): will ${greenBold("CREATE")}`);
|
|
866
|
+
|
|
867
|
+
const oldPresent = OLD_SKILLS.filter((s) => isDir(join(claudeDir, "skills", s)));
|
|
868
|
+
if (oldPresent.length) console.log(` 🗑️ Old skills in ${toTildePath(claudeDir)} will be REMOVED: ${redBold(oldPresent.join(" "))}`);
|
|
869
|
+
}
|
|
692
870
|
|
|
693
871
|
const skillsSrc = join(REPO_ROOT, "skills");
|
|
694
872
|
if (isDir(skillsSrc)) {
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
if (!isDir(skillDir)) continue;
|
|
698
|
-
const destSkill = join(CLAUDE_DIR, "skills", name, "SKILL.md");
|
|
699
|
-
if (isFile(destSkill)) console.log(`\ud83d\udd27 Skill ${name}: will ${yellowBold("OVERWRITE")} ${destSkill}`);
|
|
700
|
-
else console.log(`\ud83d\udd27 Skill ${name}: will ${greenBold("CREATE")} ${destSkill}`);
|
|
701
|
-
}
|
|
873
|
+
const skillList = listDir(skillsSrc).filter((n) => isDir(join(skillsSrc, n))).sort();
|
|
874
|
+
console.log(`🔧 Skills (${skillList.length}): will sync to all Claude targets (${skillList.join(", ")})`);
|
|
702
875
|
}
|
|
703
876
|
|
|
704
877
|
const agentsSrc = join(REPO_ROOT, "claude", "agents");
|
|
705
878
|
if (isDir(agentsSrc)) {
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
const destAgent = join(CLAUDE_DIR, "agents", name);
|
|
709
|
-
if (isFile(destAgent)) console.log(`\ud83e\udde0 Agent ${name}: will ${yellowBold("OVERWRITE")} ${destAgent}`);
|
|
710
|
-
else console.log(`\ud83e\udde0 Agent ${name}: will ${greenBold("CREATE")} ${destAgent}`);
|
|
711
|
-
}
|
|
879
|
+
const agentList = listDir(agentsSrc).filter((n) => n.endsWith(".md")).sort();
|
|
880
|
+
console.log(`🧠 Agents (${agentList.length}): will sync to all Claude targets (${agentList.map((n) => basename(n, ".md")).join(", ")})`);
|
|
712
881
|
}
|
|
713
882
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
} catch (e) {
|
|
734
|
-
console.log(` \u274c Error reading Claude settings.json: ${e}`);
|
|
883
|
+
console.log("⚙️ settings: checking permissions and skill overrides across platforms...");
|
|
884
|
+
for (const claudeDir of claudeDirs) {
|
|
885
|
+
const settingsPath = join(claudeDir, "settings.json");
|
|
886
|
+
if (isFile(settingsPath)) {
|
|
887
|
+
try {
|
|
888
|
+
const data = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
889
|
+
const readRule = `Read(//${INSTALL_ROOT.replace(/^\/+/, "")}/**)`;
|
|
890
|
+
const allow = (data.permissions && data.permissions.allow) || [];
|
|
891
|
+
const bashRule = `Bash(python3 ${toTildePath(join(claudeDir, "skills"))}/*)`;
|
|
892
|
+
const bashOk = allow.includes(bashRule) || allow.includes("Bash(python3 ~/.claude/skills/*)");
|
|
893
|
+
const readOk = allow.includes(readRule);
|
|
894
|
+
const overrides = data.skillOverrides || {};
|
|
895
|
+
const akiOk = overrides.akirule === "on";
|
|
896
|
+
console.log(` Claude (${toTildePath(claudeDir)}): Read=${readOk ? "✅" : "⚠️ missing"}, Bash=${bashOk ? "✅" : "⚠️ missing"}, akirule=${akiOk ? "✅" : "⚠️ will enable"}`);
|
|
897
|
+
} catch (e) {
|
|
898
|
+
console.log(` ❌ Error reading ${settingsPath}: ${e}`);
|
|
899
|
+
}
|
|
900
|
+
} else {
|
|
901
|
+
console.log(` ⚠️ Claude (${toTildePath(claudeDir)}): No settings.json yet. Will be CREATED.`);
|
|
735
902
|
}
|
|
736
|
-
} else {
|
|
737
|
-
console.log(" \u26a0\ufe0f Claude Code: No settings.json yet. Will be CREATED.");
|
|
738
903
|
}
|
|
739
904
|
|
|
740
905
|
if (isDir(GEMINI_DIR)) {
|
|
@@ -744,13 +909,13 @@ async function inspectStatus() {
|
|
|
744
909
|
const data = JSON.parse(readFileSync(agSettings, "utf-8"));
|
|
745
910
|
const allow = (data.permissions && data.permissions.allow) || [];
|
|
746
911
|
const probeRule = `command(python3 ${join(GEMINI_SKILLS_DIR, "akiflow", "scripts", "council_open.py")})`;
|
|
747
|
-
if (allow.includes(probeRule)) console.log("
|
|
748
|
-
else console.log("
|
|
912
|
+
if (allow.includes(probeRule)) console.log(" ✅ Antigravity CLI: per-script skill permissions already granted.");
|
|
913
|
+
else console.log(" ⚠️ Antigravity CLI: per-script skill permissions will be added.");
|
|
749
914
|
} catch (e) {
|
|
750
|
-
console.log(`
|
|
915
|
+
console.log(` ❌ Error reading Antigravity settings: ${e}`);
|
|
751
916
|
}
|
|
752
917
|
} else {
|
|
753
|
-
console.log("
|
|
918
|
+
console.log(" ⚠️ Antigravity CLI: settings.json will be updated with skill permissions.");
|
|
754
919
|
}
|
|
755
920
|
}
|
|
756
921
|
|
|
@@ -762,14 +927,17 @@ async function inspectStatus() {
|
|
|
762
927
|
// print_summary (post-install)
|
|
763
928
|
// ---------------------------------------------------------------------------
|
|
764
929
|
|
|
765
|
-
function printSummary() {
|
|
930
|
+
function printSummary(claudeDirs) {
|
|
766
931
|
console.log(`\n${greenBold("=== INSTALL SUCCEEDED ===")}`);
|
|
767
932
|
|
|
768
933
|
const gitHash = gitShortHash(REPO_ROOT);
|
|
769
934
|
const hashSuffix = gitHash ? ` (${gitHash})` : "";
|
|
770
|
-
console.log(
|
|
771
|
-
console.log(
|
|
772
|
-
console.log(
|
|
935
|
+
console.log(`📅 Time : ${dateTimeNow()}${hashSuffix}`);
|
|
936
|
+
console.log(`📁 Payload : ${INSTALL_ROOT}`);
|
|
937
|
+
console.log(`🤖 Claude targets (${claudeDirs.length}):`);
|
|
938
|
+
for (const dir of claudeDirs) {
|
|
939
|
+
console.log(` - ${toTildePath(dir)} (skills, agents, hooks, CLAUDE.md, settings.json)`);
|
|
940
|
+
}
|
|
773
941
|
console.log();
|
|
774
942
|
|
|
775
943
|
console.log(cyanBold("Rules deployed:"));
|
|
@@ -791,39 +959,39 @@ function printSummary() {
|
|
|
791
959
|
|
|
792
960
|
console.log();
|
|
793
961
|
console.log(cyanBold("Skills deployed:"));
|
|
794
|
-
const
|
|
795
|
-
if (isDir(
|
|
796
|
-
for (const name of listDir(
|
|
797
|
-
if (isDir(join(
|
|
962
|
+
const primarySkills = join(PRIMARY_CLAUDE_DIR, "skills");
|
|
963
|
+
if (isDir(primarySkills)) {
|
|
964
|
+
for (const name of listDir(primarySkills).sort()) {
|
|
965
|
+
if (isDir(join(primarySkills, name))) console.log(` 🔧 ${name}`);
|
|
798
966
|
}
|
|
799
967
|
}
|
|
800
968
|
|
|
801
969
|
const agentsSrc = join(REPO_ROOT, "claude", "agents");
|
|
802
970
|
if (isDir(agentsSrc)) {
|
|
803
971
|
console.log();
|
|
804
|
-
console.log(cyanBold(`Agents deployed (${join(
|
|
972
|
+
console.log(cyanBold(`Agents deployed (${claudeDirs.map(toTildePath).join(", ")} — your own agents there are untouched):`));
|
|
805
973
|
for (const name of listDir(agentsSrc).sort()) {
|
|
806
|
-
if (name.endsWith(".md")) console.log(`
|
|
974
|
+
if (name.endsWith(".md")) console.log(` 🧠 ${basename(name, ".md")}`);
|
|
807
975
|
}
|
|
808
976
|
}
|
|
809
977
|
|
|
810
978
|
console.log();
|
|
811
979
|
console.log(cyanBold("Other CLI skill roots synced (harmless if that CLI isn't installed):"));
|
|
812
|
-
console.log(`
|
|
813
|
-
console.log(`
|
|
814
|
-
console.log(`
|
|
980
|
+
console.log(` 🤖 Codex CLI : ${CODEX_SKILLS_DIR}`);
|
|
981
|
+
console.log(` 🤖 Kiro CLI : ${KIRO_SKILLS_DIR}`);
|
|
982
|
+
console.log(` 🤖 Grok CLI : ${GROK_SKILLS_DIR}`);
|
|
815
983
|
|
|
816
984
|
console.log();
|
|
817
985
|
console.log(cyanBold("Permissions configured:"));
|
|
818
|
-
console.log("
|
|
986
|
+
console.log(" ⚙️ Claude Code : Read(~/.aki/akidevrule/**), Bash(python3 <target>/skills/*)");
|
|
819
987
|
if (isDir(GEMINI_DIR))
|
|
820
|
-
console.log("
|
|
988
|
+
console.log(" ⚙️ Antigravity : per-script command() rules (akiflow scripts ×all roots ×2 path renderings ×the platform's python launchers), write_file(~/.aki/agent-council/), read_file(~/.aki/akidevrule/)");
|
|
821
989
|
if (isDir(join(HOME, ".kiro")))
|
|
822
|
-
console.log("
|
|
990
|
+
console.log(" ⚙️ Kiro CLI : capability:shell (python3 ~/.kiro/skills/*, ~/.claude*/skills/*)");
|
|
823
991
|
|
|
824
992
|
console.log();
|
|
825
993
|
console.log(cyanBold("Hooks deployed:"));
|
|
826
|
-
console.log("
|
|
994
|
+
console.log(" 📢 aki-update-check (SessionStart, notify-only) — notifies when a new rule version is available");
|
|
827
995
|
|
|
828
996
|
console.log(`\n${greenBold("==============================")}`);
|
|
829
997
|
}
|
|
@@ -846,15 +1014,8 @@ function ask(question) {
|
|
|
846
1014
|
// Main install logic
|
|
847
1015
|
// ---------------------------------------------------------------------------
|
|
848
1016
|
|
|
849
|
-
async function runInstall() {
|
|
850
|
-
|
|
851
|
-
if (isDir(LEGACY_INSTALL_ROOT) && !existsSync(INSTALL_ROOT)) {
|
|
852
|
-
mkdirSync(dirname(INSTALL_ROOT), { recursive: true });
|
|
853
|
-
renameSync(LEGACY_INSTALL_ROOT, INSTALL_ROOT);
|
|
854
|
-
console.log(`\ud83d\udce6 Migrated legacy install root: ${LEGACY_INSTALL_ROOT} \u2192 ${INSTALL_ROOT}`);
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
const sameCheckout = await inspectStatus();
|
|
1017
|
+
async function runInstall(claudeDirs) {
|
|
1018
|
+
const sameCheckout = await inspectStatus(claudeDirs);
|
|
858
1019
|
|
|
859
1020
|
// Skip the prompt only for a byte-identical overwrite (same commit, clean tree).
|
|
860
1021
|
if (process.stdin.isTTY && !sameCheckout) {
|
|
@@ -864,11 +1025,17 @@ async function runInstall() {
|
|
|
864
1025
|
process.exit(1);
|
|
865
1026
|
}
|
|
866
1027
|
}
|
|
1028
|
+
|
|
1029
|
+
// Legacy migration: ~/.aki/claudedoc -> ~/.aki/akidevrule
|
|
1030
|
+
if (isDir(LEGACY_INSTALL_ROOT) && !existsSync(INSTALL_ROOT)) {
|
|
1031
|
+
mkdirSync(dirname(INSTALL_ROOT), { recursive: true });
|
|
1032
|
+
renameSync(LEGACY_INSTALL_ROOT, INSTALL_ROOT);
|
|
1033
|
+
console.log(`📦 Migrated legacy install root: ${LEGACY_INSTALL_ROOT} → ${INSTALL_ROOT}`);
|
|
1034
|
+
}
|
|
867
1035
|
console.log("Installing...");
|
|
868
1036
|
|
|
869
1037
|
// --- 1. Payload -> INSTALL_ROOT ---
|
|
870
1038
|
mkdirSync(INSTALL_ROOT, { recursive: true });
|
|
871
|
-
mkdirSync(join(CLAUDE_DIR, "skills"), { recursive: true });
|
|
872
1039
|
|
|
873
1040
|
const payloadSrc = join(REPO_ROOT, "payload");
|
|
874
1041
|
const EXCLUDED = new Set(["ref-ECC", ".DS_Store", "GEMINI.md"]);
|
|
@@ -909,65 +1076,19 @@ async function runInstall() {
|
|
|
909
1076
|
if (branch) versionLines.push(`branch=${branch}`);
|
|
910
1077
|
}
|
|
911
1078
|
writeTextLf(join(INSTALL_ROOT, ".version"), versionLines.join("\n") + "\n");
|
|
1079
|
+
writeTextLf(join(INSTALL_ROOT, ".source-repo"), REPO_ROOT + "\n");
|
|
1080
|
+
|
|
1081
|
+
// --- 2. Claude targets (skills, agents, hooks, CLAUDE.md, settings.json) ---
|
|
1082
|
+
for (const cDir of claudeDirs) {
|
|
1083
|
+
installClaudeDir(cDir);
|
|
1084
|
+
}
|
|
912
1085
|
|
|
913
|
-
// ---
|
|
914
|
-
syncAkiSkills(join(CLAUDE_DIR, "skills"));
|
|
1086
|
+
// --- 3. Other CLI skill roots ---
|
|
915
1087
|
syncAkiSkills(CODEX_SKILLS_DIR);
|
|
916
1088
|
syncAkiSkills(KIRO_SKILLS_DIR);
|
|
917
1089
|
syncAkiSkills(GROK_SKILLS_DIR);
|
|
918
1090
|
|
|
919
|
-
// ---
|
|
920
|
-
syncAkiAgents();
|
|
921
|
-
|
|
922
|
-
// --- 4. Hooks + source-repo ---
|
|
923
|
-
const hooksDest = join(CLAUDE_DIR, "hooks");
|
|
924
|
-
mkdirSync(hooksDest, { recursive: true });
|
|
925
|
-
copyFileSync(join(REPO_ROOT, "claude", "hooks", "aki-update-check.mjs"), join(hooksDest, "aki-update-check.mjs"));
|
|
926
|
-
copyFileSync(join(REPO_ROOT, "claude", "hooks", "aki_version_check.mjs"), join(hooksDest, "aki_version_check.mjs"));
|
|
927
|
-
// Remove orphaned Python hooks left by a pre-3.0 install.
|
|
928
|
-
for (const legacy of ["aki-update-check.py", "aki_version_check.py"]) {
|
|
929
|
-
const p = join(hooksDest, legacy);
|
|
930
|
-
if (existsSync(p)) rmrf(p);
|
|
931
|
-
}
|
|
932
|
-
// hooks/ is shared with the user's own hooks, so only our bytecode is removed, never the whole cache.
|
|
933
|
-
const pyCache = join(hooksDest, "__pycache__");
|
|
934
|
-
if (isDir(pyCache)) {
|
|
935
|
-
for (const n of readdirSync(pyCache)) if (n.startsWith("aki_version_check.")) rmrf(join(pyCache, n));
|
|
936
|
-
if (readdirSync(pyCache).length === 0) rmrf(pyCache);
|
|
937
|
-
}
|
|
938
|
-
writeTextLf(join(INSTALL_ROOT, ".source-repo"), REPO_ROOT + "\n");
|
|
939
|
-
|
|
940
|
-
// --- 5. CLAUDE.md ---
|
|
941
|
-
mkdirSync(CLAUDE_DIR, { recursive: true });
|
|
942
|
-
backup(join(CLAUDE_DIR, "CLAUDE.md"));
|
|
943
|
-
console.log("\ud83e\uddf9 Pruning CLAUDE.md backups (keeping the 2 most recent):");
|
|
944
|
-
pruneBackups(join(CLAUDE_DIR, "CLAUDE.md"));
|
|
945
|
-
|
|
946
|
-
const claudeMdSrc = readFileSync(join(REPO_ROOT, "claude", "CLAUDE.md"), "utf-8");
|
|
947
|
-
const propagateCmd = `node "${join(REPO_ROOT, "install.mjs")}"`;
|
|
948
|
-
const ruleSourceBlock =
|
|
949
|
-
"\n## akidevrule \u2014 edit source, not deployed copy (ABSOLUTE)\n\n" +
|
|
950
|
-
`The deployed rule files at \`${INSTALL_ROOT}\` are **overwritten on every install**.\n` +
|
|
951
|
-
"To change any shared rule:\n" +
|
|
952
|
-
`1. Edit in the **source repo**: \`${join(REPO_ROOT, "payload")}/\`\n` +
|
|
953
|
-
`2. Run \`${propagateCmd}\` to propagate.\n\n` +
|
|
954
|
-
`**NEVER edit files under \`${INSTALL_ROOT}\` directly** \u2014 changes will be silently lost on the next install.\n\n` +
|
|
955
|
-
"@~/.claude/CLAUDE.local.md\n";
|
|
956
|
-
writeTextLf(join(CLAUDE_DIR, "CLAUDE.md"), claudeMdSrc + ruleSourceBlock);
|
|
957
|
-
|
|
958
|
-
// --- 6. CLAUDE.local.md (create-only) ---
|
|
959
|
-
const localMd = join(CLAUDE_DIR, "CLAUDE.local.md");
|
|
960
|
-
if (!isFile(localMd)) {
|
|
961
|
-
writeTextLf(
|
|
962
|
-
localMd,
|
|
963
|
-
"# Machine-local Claude instructions\n\n" +
|
|
964
|
-
"This file is machine-specific and never touched by akidevrule installs.\n" +
|
|
965
|
-
"Add any per-machine rules here (e.g. build constraints, IDE paths, remote flags).\n"
|
|
966
|
-
);
|
|
967
|
-
console.log(`\ud83d\udcdd Created ${localMd} (machine-local template)`);
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
// --- 7. GEMINI.md (only when ~/.gemini exists) ---
|
|
1091
|
+
// --- 4. GEMINI.md (only when ~/.gemini exists) ---
|
|
971
1092
|
if (isDir(GEMINI_DIR)) {
|
|
972
1093
|
const geminiFile = join(GEMINI_DIR, "GEMINI.md");
|
|
973
1094
|
const geminiLocal = join(GEMINI_DIR, "GEMINI.local.md");
|
|
@@ -983,11 +1104,11 @@ async function runInstall() {
|
|
|
983
1104
|
"This file is machine-specific and never touched by akidevrule installs.\n" +
|
|
984
1105
|
"Add machine-specific paths, CLIs, and emulator commands here.\n"
|
|
985
1106
|
);
|
|
986
|
-
console.log(
|
|
1107
|
+
console.log(`📝 Created ${geminiLocal} (machine-local template)`);
|
|
987
1108
|
}
|
|
988
1109
|
|
|
989
1110
|
backup(geminiFile);
|
|
990
|
-
console.log("
|
|
1111
|
+
console.log("🧹 Pruning GEMINI.md backups (keeping the 2 most recent):");
|
|
991
1112
|
pruneBackups(geminiFile);
|
|
992
1113
|
|
|
993
1114
|
const d = new Date();
|
|
@@ -995,50 +1116,43 @@ async function runInstall() {
|
|
|
995
1116
|
const geminiTemplate = readFileSync(join(REPO_ROOT, "payload", "GEMINI.md"), "utf-8");
|
|
996
1117
|
const geminiContent = geminiTemplate.split("__VERSION__").join(geminiVersion);
|
|
997
1118
|
|
|
1119
|
+
const propagateCmd = `node "${join(REPO_ROOT, "install.mjs")}"`;
|
|
998
1120
|
const geminiSourceBlock =
|
|
999
|
-
"\n## 15. Shared rule source
|
|
1000
|
-
`The deployed rule corpus at \`${INSTALL_ROOT}\`
|
|
1121
|
+
"\n## 15. Shared rule source — edit source, not deployed copy (ABSOLUTE)\n\n" +
|
|
1122
|
+
`The deployed rule corpus at \`${INSTALL_ROOT}\` are **overwritten on every install**.\n` +
|
|
1001
1123
|
"To change any shared rule:\n" +
|
|
1002
1124
|
`1. Edit in the **source repo**: \`${join(REPO_ROOT, "payload")}/\` (rules) or \`${join(REPO_ROOT, "claude")}/\` (runtime assets).\n` +
|
|
1003
|
-
`2. Read \`${join(REPO_ROOT, "CLAUDE.md")}\` first
|
|
1125
|
+
`2. Read \`${join(REPO_ROOT, "CLAUDE.md")}\` first — it lists which files must be updated together.\n` +
|
|
1004
1126
|
`3. Run \`${propagateCmd}\` to propagate.\n\n` +
|
|
1005
|
-
`**NEVER edit files under \`${INSTALL_ROOT}\` directly**
|
|
1127
|
+
`**NEVER edit files under \`${INSTALL_ROOT}\` directly** — changes will be silently lost on the next install.\n`;
|
|
1006
1128
|
|
|
1007
1129
|
const localContent = readFileSync(geminiLocal, "utf-8");
|
|
1008
1130
|
const fullGemini = geminiContent + geminiSourceBlock + "\n---\n\n" + localContent;
|
|
1009
1131
|
writeTextLf(geminiFile, fullGemini);
|
|
1010
1132
|
|
|
1011
|
-
console.log(
|
|
1133
|
+
console.log(`🤖 Installed ${geminiFile} (marker ${geminiMarker}${geminiVersion}])`);
|
|
1012
1134
|
if (hadUnmanaged) {
|
|
1013
|
-
console.log("
|
|
1135
|
+
console.log(" ⚠️ Your previous ~/.gemini/GEMINI.md was replaced (saved as *.akidevrule-backup-*).");
|
|
1014
1136
|
console.log(` Move any machine-local lines from that backup into ${geminiLocal}.`);
|
|
1015
1137
|
}
|
|
1016
1138
|
|
|
1017
|
-
// ---
|
|
1139
|
+
// --- Antigravity rules ---
|
|
1018
1140
|
const agCount = installAgRules();
|
|
1019
|
-
console.log(
|
|
1141
|
+
console.log(`🧭 Installed ${agCount} rule(s) to ${GEMINI_RULES_DIR} (read by AG, AG IDE and AGY)`);
|
|
1020
1142
|
|
|
1021
|
-
// ---
|
|
1143
|
+
// --- Antigravity skills ---
|
|
1022
1144
|
syncAkiSkills(GEMINI_SKILLS_DIR);
|
|
1023
1145
|
syncAkiSkills(join(INSTALL_ROOT, "agskills"));
|
|
1024
1146
|
updateSkillsJson();
|
|
1025
|
-
console.log(
|
|
1026
|
-
console.log("
|
|
1147
|
+
console.log(`💡 Deployed skills to ${GEMINI_SKILLS_DIR} & updated ~/.gemini/config/skills.json`);
|
|
1148
|
+
console.log(" ℹ️ Antigravity discovers rules and skills at startup — restart the app or start a new agy session.");
|
|
1027
1149
|
}
|
|
1028
1150
|
|
|
1029
|
-
// ---
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
backup(settingsPath);
|
|
1033
|
-
console.log("\ud83e\uddf9 Pruning settings.json backups (keeping the 2 most recent):");
|
|
1034
|
-
pruneBackups(settingsPath);
|
|
1035
|
-
mergeSettings(settingsPath, INSTALL_ROOT, CLAUDE_DIR);
|
|
1036
|
-
|
|
1037
|
-
// --- 11. Antigravity & Kiro permissions ---
|
|
1038
|
-
mergeAntigravityPermissions();
|
|
1039
|
-
mergeKiroPermissions();
|
|
1151
|
+
// --- 5. Antigravity & Kiro permissions ---
|
|
1152
|
+
mergeAntigravityPermissions(claudeDirs);
|
|
1153
|
+
mergeKiroPermissions(claudeDirs);
|
|
1040
1154
|
|
|
1041
|
-
printSummary();
|
|
1155
|
+
printSummary(claudeDirs);
|
|
1042
1156
|
}
|
|
1043
1157
|
|
|
1044
1158
|
// ---------------------------------------------------------------------------
|
|
@@ -1048,17 +1162,20 @@ async function runInstall() {
|
|
|
1048
1162
|
async function main() {
|
|
1049
1163
|
const argv = process.argv.slice(2);
|
|
1050
1164
|
if (argv.includes("-h") || argv.includes("--help")) {
|
|
1051
|
-
console.log("akidevrule installer
|
|
1165
|
+
console.log("akidevrule installer — deploys shared rule corpus and skills.");
|
|
1052
1166
|
console.log("");
|
|
1053
|
-
console.log("Usage: node install.mjs [--check]");
|
|
1054
|
-
console.log(" --check
|
|
1167
|
+
console.log("Usage: node install.mjs [--check] [--claude-dir <path>]");
|
|
1168
|
+
console.log(" --check Print installed vs latest akidevrule version and exit. No install, no overwrite.");
|
|
1169
|
+
console.log(" --claude-dir <path> Explicit Claude config directory to include (also auto-detects ~/.claude* and $CLAUDE_CONFIG_DIR).");
|
|
1055
1170
|
process.exit(0);
|
|
1056
1171
|
}
|
|
1057
1172
|
if (argv.includes("--check")) {
|
|
1058
1173
|
await printVersionCheck();
|
|
1059
1174
|
process.exit(0);
|
|
1060
1175
|
}
|
|
1061
|
-
|
|
1176
|
+
const claudeDirs = getClaudeDirs(argv);
|
|
1177
|
+
preflightInstallSettings(claudeDirs);
|
|
1178
|
+
await runInstall(claudeDirs);
|
|
1062
1179
|
}
|
|
1063
1180
|
|
|
1064
1181
|
main().catch((err) => {
|
package/package.json
CHANGED
|
@@ -29,7 +29,7 @@ Being called with a card means: re-read the root rule, fix **every** instance in
|
|
|
29
29
|
- **Read/Edit the file, never `cat`/`sed`/`head` to print-then-read it.** Bash is for what it is uniquely good at: multi-file scans and transforms, pipes and aggregation, genuinely shell-native tasks (git, npm, processes). Shelling out to read one known file spends a round trip to obtain what one tool call already returns.
|
|
30
30
|
- **Find every edit site before touching any of them, then apply the whole set in one pass.** Editing line by line as sites are discovered turns one change into N full-history round trips. If the sites are not all known yet, that is a signal to search first, not to start editing.
|
|
31
31
|
- **Batch independent calls into a single turn.** Two lookups that do not depend on each other go out together; waiting for the first to issue the second pays twice for nothing.
|
|
32
|
-
- **
|
|
32
|
+
- **Narrow and batch before crossing a process boundary.** Use the direct tools and in-shell aggregation rules above when they can return a bounded answer without flooding this context; `A5` alone decides when the remaining exploration belongs to a worker.
|
|
33
33
|
|
|
34
34
|
### A3. Communication vs task — a question is not a request
|
|
35
35
|
Classify every turn before acting: is it **communication** (a question, discussion, or explanation — "why/how/can we/should we/what if", thinking aloud) or a **task** (an imperative aimed at the code/repo: add, fix, change, remove, commit)?
|
|
@@ -52,9 +52,13 @@ The reader often context-switches across many tasks and reads in a terminal; opt
|
|
|
52
52
|
### A5. Delegating to a worker — more throughput, less spend
|
|
53
53
|
A worker is a subagent, or the same or another CLI called headlessly (`claude -p`, `agy -p`, equivalents).
|
|
54
54
|
|
|
55
|
-
**
|
|
55
|
+
**Narrow at the source before delegating.** If one bounded command, restricted read, pipeline, or batch can return the answer — or a digest that loses nothing the decision needs — run it in this thread. Repository size and raw input size alone do not justify a worker when aggregation prevents that raw volume from entering the context.
|
|
56
56
|
|
|
57
|
-
**
|
|
57
|
+
**Probe once only when the route cannot yet be specified.** If the target, question, or required output shape is unclear, run one bounded orientation probe. The probe may finish the task; never delegate work the probe already answered. If the residue still requires broad reading or comprehension that cannot be narrowed before entering a context, cross the process boundary with exact paths or targets, the exact question, and the exact output shape.
|
|
58
|
+
|
|
59
|
+
**Route by context need, not by the word “exploration.”** Conversation-dependent synthesis, ambiguous classification, and per-item judgment stay with the caller; delegate only the retrieval that feeds them. Prefer a fresh worker for a precise, context-free digest. Use a fork only when a bounded bulk task genuinely needs substantial accumulated context that would be expensive to restate. A worker never delegates again unless the caller explicitly grants a fan-out with bounded depth and width.
|
|
60
|
+
|
|
61
|
+
**When delegation is warranted, use the current default wide-context tier available, one shot** — on Antigravity that is `agy --model gemini-3.7-flash-high --mode plan -p "<prompt>"` (prompt last; `-p` swallows the next token; owner-set default, 2026-08-15). It holds a very large context; its failure mode is skimming, so the counter is prompt precision rather than a bigger model: name the exact paths, the exact question, and the exact output shape, and leave it nothing to improvise. Keep it to a single call — multi-turn on that CLI degrades badly.
|
|
58
62
|
|
|
59
63
|
**Know which kind of cheap you are buying.** A stateless cheap call is cheap *per call* and must re-receive its context every time. A persistent worker (`claude -p --session-id <uuid>`, later `--resume <uuid>`) is cheap *per turn after the first*, because its prefix is cached — roughly an eighth of the opening turn, then flat — and it keeps everything **it** was told, though nothing the caller knows. Use the first for one wide question, the second for a worker you will come back to. The session id is scoped to the directory it was created in.
|
|
60
64
|
- **A worker inherits nothing** — not your context, not your rules, not your router. Name the exact rule files it must read and the exact paths or targets it must look at. "Follow the project rules" loads nothing and reads as compliance.
|
package/payload/RULE-release.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Release & Versioning Rule
|
|
2
2
|
|
|
3
|
-
<!-- Address map: release.A1-5 · release.B1-
|
|
3
|
+
<!-- Address map: release.A1-5 · release.B1-11 · release.C1-4 (⟨Aki⟩) -->
|
|
4
4
|
|
|
5
5
|
## A. Versioning core
|
|
6
6
|
|
|
@@ -116,7 +116,7 @@ After updating CHANGELOG and the version bump, produce the GitHub Release withou
|
|
|
116
116
|
|
|
117
117
|
**`--generate-notes` alone is not a substitute for Title/Body above.** It derives content from merged PRs only; a repo that commits straight to trunk (no PR history) gets a near-empty body — footer line only. Pair it with `--notes-file` for real content, or, when release creation is CI-automated rather than run interactively, have the workflow itself extract the tagged version's CHANGELOG section into the notes file — the content requirement above still applies even though no one is typing the `gh release create` command by hand.
|
|
118
118
|
|
|
119
|
-
### B5. Migration
|
|
119
|
+
### B5. Migration doctrine & completeness gate — a schema or infra change is not "released" until it ran
|
|
120
120
|
|
|
121
121
|
A CHANGELOG or `releases.json` entry that describes a database schema change or any other infra-dependent change (migration, remote config, env var, cron/schedule registration — see [[RULE-coding]] B3) is a claim that the change is live. That claim is only true once two things both hold, not one:
|
|
122
122
|
|
|
@@ -125,6 +125,14 @@ A CHANGELOG or `releases.json` entry that describes a database schema change or
|
|
|
125
125
|
|
|
126
126
|
Do not report a plan, task, or release/deploy as complete when a migration/infra step it depends on has not cleared **both** conditions. A written migration script plus a "Added" changelog line with the actual execution still outstanding is exactly the failure this gate exists to catch — the code shipped, the database did not, and nothing else in the release checklist would have noticed.
|
|
127
127
|
|
|
128
|
+
#### Migration doctrine — MANDATORY. No project is exempt without a written declaration in its own `CLAUDE.md`.
|
|
129
|
+
|
|
130
|
+
1. **DETECT by effect, never by location.** A migration is ANY change to the shape of persisted data: a `migrations/` script, an ORM auto-sync, a `CREATE`/`ALTER`/`DROP` of a table, column, index or constraint, a schema-version bump, a JSON state-file shape change, a re-keyed cache — **wherever it lives, including code that runs at application startup or inside a constructor.** Run the detector over the accumulation diff (boundary commit, B1.5) on EVERY release and paste its output or the word `empty` into the report: `git diff <boundary>..HEAD | grep -nEi 'ALTER +TABLE|CREATE +(UNIQUE +)?(TABLE|INDEX)|DROP +(TABLE|INDEX|COLUMN)|ADD +COLUMN|user_version|migrat|schema'`. A hit IS a migration until a written line proves otherwise. "It only auto-applies at startup" and "it is just an index" are NOT exemptions: the failure this doctrine exists for was an index on a new column created before that column existed, inside a constructor, invisible to a gate that only looked for migration scripts.
|
|
131
|
+
2. **SEPARATE migration from application.** A migration is its own artifact — versioned, ordered, idempotent, recorded — executed as its own named deploy step. Application startup VERIFIES the schema version and reports `degraded` (B11); it NEVER mutates schema. A project that embeds migration in startup code (single-process embedded database) is a declared exception in its project `CLAUDE.md`, and this gate still treats that code as a migration in full: points 1 and 3–5 apply unchanged.
|
|
132
|
+
3. **ORDER: expand → migrate → deploy code → contract.** Additive (expand) migrations run BEFORE the code that needs them restarts, so old code keeps running on the new schema; destructive (contract) steps ship in a LATER release, after nothing reads the old shape. Restarting new code onto an un-migrated schema is a VIOLATION.
|
|
133
|
+
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
|
+
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
|
+
|
|
128
136
|
### B6. Content discipline
|
|
129
137
|
- Release note copy: no em/en dash (`—` `–`); short user-facing sentences, benefit first. See [[RULE-content-write]].
|
|
130
138
|
- Keep terminology stable across versions (e.g. always "Release Notes", not mixed synonyms). See [[RULE-content-write]] semantic stability.
|
|
@@ -136,17 +144,23 @@ The last moment a mistake is still cheap: the work is done, the tree is clean, a
|
|
|
136
144
|
|
|
137
145
|
Run in order; each step names the rule that owns it.
|
|
138
146
|
|
|
147
|
+
**FAIL-CLOSED CONTRACT — this gate is NOT advisory, and a skipped step is a failed step.**
|
|
148
|
+
- Every step 0–8 MUST leave a receipt line in the run's report: `S<n> PASS | FIXED | FAIL | N/A — <evidence>`. Evidence is quoted command output, a `file:line` actually read, or an observed fact — never an adjective. `N/A` REQUIRES the cited fact that makes the step inapplicable; an `N/A` with no cited fact is scored FAIL.
|
|
149
|
+
- A step with NO receipt line was NOT RUN. NOT RUN means the gate FAILED. A failed gate FORBIDS commit, mint, tag, push and deploy — no exception for urgency, "hotfix", "tiny diff", or "same as the last release".
|
|
150
|
+
- FORBIDDEN as evidence: "should", "presumably", "probably", "looks fine", "seems", "expected to". Each is an unverified claim and is scored `unverified` under step 7, never PASS.
|
|
151
|
+
- **Self-interrogation is MANDATORY and REPORTED, not silent.** Before closing, answer in writing: (1) which step was cheapest to skip, and what did I actually do for it; (2) if this release broke production within the hour, which unchecked step caused it; (3) does the diff touch anything persisted, and did step 3 run against the PREVIOUS state; (4) what did verification exercise that exists only on production — a data path, not a version string. An unanswered question is a FAIL.
|
|
152
|
+
|
|
139
153
|
0. **Leftover triage** — a tree that is not uniformly finished is classified first: finished / mid-edit / abandoned / accidental (the `/akigitcommit` step-0 taxonomy, under [[RULE-agent-behavior]] B5's read-only floor). Mid-edit vs abandoned is undecidable from the tree alone — that is an escalation (B8), never a guess.
|
|
140
154
|
1. **Release state** — derive it cold from the repo per B1, never from session memory. `Drifted` blocks everything until A5's recovery has run.
|
|
141
155
|
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.
|
|
142
|
-
3. **
|
|
156
|
+
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.
|
|
143
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).
|
|
144
158
|
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.
|
|
145
159
|
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.
|
|
146
160
|
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.
|
|
147
161
|
8. **Version decision** — mint or defer per A4/A5's materiality test. Do not mint a version to mark that a session ended.
|
|
148
162
|
|
|
149
|
-
Post-push CI is B10; stack deploy verification (C5, `stack.C8`) follows a green CI.
|
|
163
|
+
Post-push CI is B10; post-deploy functional verification is B11 (mandatory after every deploy or restart); stack deploy verification (C5, `stack.C8`) follows a green CI.
|
|
150
164
|
|
|
151
165
|
### B8. Autonomous full-release run — an explicit release order is the authorization
|
|
152
166
|
|
|
@@ -176,6 +190,15 @@ After any push or tag push, in any flow (not only `/akiship`): `gh run list --co
|
|
|
176
190
|
|
|
177
191
|
**Evidence.** CI failures went unnoticed because the ritual only verified stack deploys (C5): a non-deploying repo (CLI, library, npm package) had no post-push check at all, so a red workflow could sit unnoticed indefinitely.
|
|
178
192
|
|
|
193
|
+
### B11. Post-deploy verification — a version string proves the code, never the function
|
|
194
|
+
|
|
195
|
+
After ANY deploy or restart, in any flow (not only `/akiship`), verify FUNCTION, not identity.
|
|
196
|
+
- The check MUST exercise at least one real data path the release touched — an authenticated read that hits the changed table or route — and assert a success status AND a non-trivial payload. A `/version` or `/health` response, or a 200 from a static page, is an IDENTITY check: necessary, never sufficient.
|
|
197
|
+
- A health endpoint returning a constant `ok` regardless of subsystem state is a FALSE INSTRUMENT. It MUST derive its status from the real state of every required subsystem (`degraded` plus the subsystem names when one failed to open), and until it does, a gate that trusts it scores the verification `unverified`, never PASS. Degraded runtime state MUST be visible — health, a log line carrying the underlying error text, or the UI — and never swallowed by a bare `catch`. A component whose failure must not take the service down degrades VISIBLY; it is never silent and never fatal to unrelated work.
|
|
198
|
+
- Failure → execute the rollback or fix-forward path stated under B5 point 5, THEN report. "The process is up" is not Done.
|
|
199
|
+
|
|
200
|
+
**Evidence.** A release passed every gate and its post-deploy check because the check read only the version endpoint and the health endpoint returned a hardcoded `ok`; the usage store had failed to open on the upgraded database and the dashboard was dead for the whole window until a person opened it.
|
|
201
|
+
|
|
179
202
|
## C. ⟨Aki⟩ Web release artifacts
|
|
180
203
|
|
|
181
204
|
### C1. Two separate channels — do not merge them
|
package/payload/index.md
CHANGED
|
@@ -18,7 +18,7 @@ Provides reusable rules for agent behavior, coding, content, docs, and stack-spe
|
|
|
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
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 phrase list 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) |
|
|
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 phrase list 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 |
|
|
@@ -68,7 +68,7 @@ Some subjects legitimately live in several files: one **root rule** stating the
|
|
|
68
68
|
| Subject | Root | Domain applications |
|
|
69
69
|
|---|---|---|
|
|
70
70
|
| **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
|
-
| **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 |
|
|
71
|
+
| **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
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 |
|
|
73
73
|
| **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
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) |
|
package/skills/akirule/SKILL.md
CHANGED
|
@@ -71,6 +71,7 @@ Load if message or file path contains any of:
|
|
|
71
71
|
- **Keywords (release-ritual context — these load this rule file, they never start a run; activation is owned entirely by akiship's own gate, an imperative release order):** `akiship`, `full release`, `release trọn gói`, `chạy full release`, `ship đợt này`, `ship trọn gói`
|
|
72
72
|
- **Keywords (commit/push/deploy — load even without an explicit "release" word):** `commit`, `git commit`, `push`, `git push`, `deploy`, `deployment`, `git tag`, `ship it`, `commit và push`, `push lên`, `đẩy lên`, `triển khai`
|
|
73
73
|
- **Keywords (registry publish — `release.B9`):** `npm publish`, `publish`, `npm`, `npx`, `registry`, `crates.io`, `cargo publish`, `PyPI`, `twine`, `2FA`, `OTP`, `lên npm`
|
|
74
|
+
- **Keywords (migration & post-deploy — `release.B5`, `release.B11`):** `migration`, `migrate`, `schema change`, `ALTER TABLE`, `add column`, `db migration`, `health endpoint`, `/health`, `post-deploy`, `smoke test`, `chạy migration`, `đổi schema`
|
|
74
75
|
- **Keywords (post-push CI — `release.B10`):** `CI`, `GitHub Actions`, `workflow run`, `gh run`, `CI fail`, `CI đỏ`, `build fail`, `test fail`
|
|
75
76
|
- **Actions:** committing or pushing code, deploying, shipping a change that should be recorded for users or maintainers; bumping a version; checking whether finished-but-unpushed work is actually shippable (`release.B7`); running the full release ritual unattended (`release.B8`, `/akiship`); verifying CI after a push (`release.B10`)
|
|
76
77
|
|
package/skills/akiship/SKILL.md
CHANGED
|
@@ -7,7 +7,16 @@ description: Full release ritual end-to-end — front-loaded checks, then an una
|
|
|
7
7
|
|
|
8
8
|
Invoke with `/akiship` or an explicit release order, only as described in § Activation gate below. Goal: replace the daily hand-typed ritual ("resolve leftovers, sync every doc, lint, fix drift, changelog, commit, release…") with one invocation that runs to completion or stops once, early, with every blocker in a single batch.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
## CRITICAL — MANDATORY LOAD, BEFORE ANY OTHER TOOL CALL OF THE RUN (execute AND consult mode)
|
|
11
|
+
|
|
12
|
+
**This skill sequences; it owns no content.** The checklist is `RULE-release.md` (B5 migration doctrine, B7 fail-closed gate, B8 autonomy contract, B10 CI, B11 post-deploy verification) and doc sync is `RULE-docs.md`. Both are installed at `~/.aki/akidevrule/`.
|
|
13
|
+
|
|
14
|
+
1. `Read` `~/.aki/akidevrule/RULE-release.md` IN FULL and `~/.aki/akidevrule/RULE-docs.md` as the FIRST tool calls after this skill loads. Keyword routing, memory of an earlier session, this file's summary, and a rule that happens to be in context do NOT count as loading — only a `Read` performed in THIS run does.
|
|
15
|
+
2. Emit as the first line of the run: `[RULES] agent,coding,pattern (core) + release,docs (akiship) | missing: none`. Any file that could not be read goes under `missing:` and the run STOPS there.
|
|
16
|
+
3. A run that starts Phase 1 without those two `Read` calls is INVALID: every finding, commit, tag and deploy it produces is unauthorized and MUST be reported as such. Compliance is checked against the tool-call log, never against the receipt line (`agent.B2`).
|
|
17
|
+
|
|
18
|
+
If a step in this file disagrees with the rule file, the rule file wins — except the activation gate below, which this skill owns outright (`pattern.A1`) and which no rule file, keyword list, or routing table may widen.
|
|
19
|
+
|
|
11
20
|
|
|
12
21
|
## Activation gate — two conditions, both required, checked before anything else
|
|
13
22
|
|
|
@@ -36,7 +45,7 @@ Consult is the default whenever both readings are available. A withheld executio
|
|
|
36
45
|
Run B7 steps 2–7 in order, fixing findings as they surface (this is a gate, not an audit — no findings doc):
|
|
37
46
|
|
|
38
47
|
- **Hygiene, diff scope only**: `python3 ~/.claude/skills/akiflow/scripts/scythe.py <files changed since boundary>` for `[WRAP]`/`[YAP]`; dead code / redundant guards / duplication the accumulation introduced (`pattern.A8`); doc refs in touched comments still resolve (`docs.B3`). Never widen to the whole repo.
|
|
39
|
-
-
|
|
48
|
+
- **Migration & external-action completeness — FIRST gate step, every release.** Run the `release.B5` detector over the accumulation diff and paste its output. A hit (startup-embedded migration code included) obliges written answers to B5 points 2–5, including a rehearsal from the PREVIOUS state; a pending migration qualifying under `stack.C8`'s execution-ownership clause is run here, not deferred. Then record truthfulness (CHANGELOG + `releases.json` parity where it exists) and doc sync over every record surface B7 step 5 enumerates (plans → `done/`, `arch`/`feat` stamps per `docs.A4`, `README.md`, the task-note file via `akidevsync-notes`, any standards doc the project `CLAUDE.md` binds).
|
|
40
49
|
- **Build & test — mirror CI (B7 step 6)**: derive commands from `.github/workflows/*` first, else the manifest's own scripts; run them all locally; a failure blocks and is fixed in place, same as the hygiene step above; a CI-only leg (other-OS matrix, secrets) is named and left to `release.B10`.
|
|
41
50
|
- Verification honesty — anything else runtime-only, or a migration that does not qualify above, is carried to the final report as **unverified**, never silently assumed (`coding.B3`).
|
|
42
51
|
|
|
@@ -45,14 +54,17 @@ Run B7 steps 2–7 in order, fixing findings as they surface (this is a gate, no
|
|
|
45
54
|
1. Commit in logical groups per `/akigitcommit` (domain-grouped mode; anti-stage-loss rules apply in full). B8 pre-answers its confirmation step — "commit luôn" semantics.
|
|
46
55
|
2. Version decision per `release.A4`/`A5`: mint exactly once at the highest accumulated severity, or defer on the materiality test. Deferring is a normal outcome, not a failure.
|
|
47
56
|
3. Artifacts per the repo's own convention: bare tag only if the repo already tags (`release.A3` B8 exception); GitHub Release per `release.B4`; `releases.json` sync check per `release.C4`; registry publish per `release.B9` — tarball verified first, and an OTP-gated publish is the report's single hand-off with its `npm view` check.
|
|
48
|
-
4. **Push / deploy only if B8's push/deploy authorization holds for this invocation (named explicitly, or completion-intensity phrasing per `release.B8`).** Otherwise the run stays local-only. After any push, watch CI per `release.B10` — always, regardless of stack. If the stack additionally deploys on push, run live deploy verification per `release.C5` once CI is green.
|
|
57
|
+
4. **Push / deploy only if B8's push/deploy authorization holds for this invocation (named explicitly, or completion-intensity phrasing per `release.B8`).** Otherwise the run stays local-only. After any push, watch CI per `release.B10` — always, regardless of stack. If the stack additionally deploys on push, run live deploy verification per `release.C5` once CI is green. After ANY deploy or restart, `release.B11` is mandatory: verify a data path the release touched, not only the version.
|
|
49
58
|
|
|
50
59
|
## Report
|
|
51
60
|
|
|
52
|
-
|
|
61
|
+
**The FIRST block is the checklist receipt, and it is mandatory:** the `release.B7` lines `S0`–`S8` (`PASS | FIXED | FAIL | N/A — evidence`), the B5 detector output, and the four written self-interrogation answers. A report without them declares the run INCOMPLETE and says which steps were NOT RUN; an unreported step is a failed step (`release.B7` fail-closed contract), never an implicit pass.
|
|
62
|
+
|
|
63
|
+
Then one dense summary (`agent.A4`): state derived → findings fixed (counts per gate step) → commits made → version minted or deferred with the reason → artifacts created → CI results (`release.B10`) → any owner-worded criteria self-decided this run, as an `agent.A3` decision block (`Decided: X · because Y · rejected Z (why) · reopen if W`) → anything left **unverified**, each with the exact command that would settle it.
|
|
53
64
|
|
|
54
65
|
## Boundaries
|
|
55
66
|
|
|
67
|
+
- Never write `PASS` on a gate step without quoted evidence (`release.B7` fail-closed contract). "Should", "presumably", "looks fine" score `unverified`.
|
|
56
68
|
- Never run a phase above on a turn that failed either activation condition — answer in consult mode instead, and never treat your own consult answer as the go-ahead for a later turn.
|
|
57
69
|
- The B8 escalation floor is the only reason to stop mid-run; everything else is self-answered from repo, docs, and rules.
|
|
58
70
|
- Never push, deploy, or push tags without B8's push/deploy authorization (`release.B8`).
|