@akinet/akidevrule 3.1.0 โ 3.2.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 +17 -0
- package/README.md +12 -10
- package/install.mjs +306 -189
- package/package.json +1 -1
- package/payload/RULE-coding.md +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.2.0] - 2026-09-15
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **Installer auto-discovers and deploys to all Claude config directory profiles (`~/.claude*`, `$CLAUDE_CONFIG_DIR`, `--claude-dir`).**
|
|
7
|
+
- **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.
|
|
8
|
+
- **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.
|
|
9
|
+
- **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.
|
|
10
|
+
- **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.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **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.
|
|
14
|
+
|
|
15
|
+
## [3.1.1] - 2026-09-15
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- **`coding.B3` bans moving uncommitted work to attribute a failing check.** Evidence: during the 3.1.0 site sync a worker hit a typecheck failure and ran `git stash` on a site repo to re-check a clean HEAD, sweeping 17 of the owner's uncommitted files out of the tree; they were restored with `git stash pop`, nothing lost. Root cause: 3.1.0 made build/test self-authorized at ship time but said nothing about how a failure is attributed, and `agent.B5`'s git-mutation ban covers audits only. Mechanism: attribution reads the error against the changed files or rebuilds the committed tree in a separate `git worktree`; `stash`/`checkout -- <path>`/`restore`/`reset`/`clean` are never used for it. Rejected: widening `agent.B5` to all work, which would also forbid legitimate owner-ordered resets; the risk lives exactly where a check fails, so the ban sits in `coding.B3`.
|
|
19
|
+
|
|
3
20
|
## [3.1.0] - 2026-09-15
|
|
4
21
|
|
|
5
22
|
### Changed
|
package/README.md
CHANGED
|
@@ -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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akinet/akidevrule",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Aki's shared rule corpus + Agent Skills for Claude Code, Gemini/Antigravity, Codex, Kiro and Grok โ install and update with one command.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
package/payload/RULE-coding.md
CHANGED
|
@@ -39,6 +39,7 @@ A principle with the procedure that guarantees it โ apply to any edit of code
|
|
|
39
39
|
### B3. Verification
|
|
40
40
|
- Done means verified โ never claim success from intention alone.
|
|
41
41
|
- Verify by the **narrowest tool that actually settles the doubt**: static reading and type/lint/unit checks first. Never spin up a full build or dev server just to catch a typo a typecheck would catch.
|
|
42
|
+
- **Never move uncommitted work to attribute a failing check.** No `git stash`/`checkout -- <path>`/`restore`/`reset`/`clean` to learn whether a typecheck or build error pre-exists: read the error against the changed files, or rebuild the committed tree in a separate `git worktree`. The working tree may hold the owner's unfinished work, and a known pre-existing failure yields no information worth risking it.
|
|
42
43
|
- **Static reading IS verification** when the property is fully determined by visible code flow โ state what was read as the evidence and close the checklist item on that evidence. Escalate a tier (typecheck โ unit โ runtime) only when you can name the specific doubt that tier settles.
|
|
43
44
|
- **Never gate a done-transition on human manual testing for a check that static reading or an automated tier settles.** A plan's verify checklist stays fully detailed โ the violation is not the checklist, it is parking finished work as "waiting for manual test" on items whose truth the code flow already proves. Hand the human only what genuinely needs human runtime judgment: UX feel, visual rendering, live external integration.
|
|
44
45
|
- **Running the app is not a default verification step โ but not running it does not let you claim "Done".** Starting a dev server or making live network calls stays **user-triggered**, not self-authorized (cost and side effects are the user's call). A full build and the test suite are **not** in that category โ they are self-authorized, gated by the moment instead: **after edits** โ typecheck/lint/related unit tests, never a full build per edit; **committing a large batch** (spans several modules, or touches build config/dependencies) โ add the build; **ship/release/deploy** โ full build plus full test suite is mandatory and self-authorized, commands derived per `release.B7` step 6. When a change's real risk lives **only at runtime** โ hydration, layout/z-index, route/auth flow, a dynamically-built class a build step may purge โ and you cannot settle it statically, you may **not** report "Done": halt and report the state as **"unverified โ needs a runtime check"**, propose the exact command, and hand it to the user (see [[RULE-agent-behavior]] A3). "Done" for logic you only compiled is not done.
|