@akinet/akidevrule 3.6.0 โ 3.8.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 +38 -0
- package/README.md +53 -16
- package/claude/CLAUDE.md +1 -1
- package/claude/agents/aki-conduct.md +3 -1
- package/claude/agents/aki-hands.md +1 -1
- package/claude/agents/aki-judge.md +1 -1
- package/claude/agents/aki-maker.md +2 -1
- package/claude/hooks/aki-compact-reread.mjs +16 -0
- package/claude/hooks/aki-route-guard.mjs +101 -41
- package/install.mjs +291 -22
- package/package.json +1 -1
- package/payload/GEMINI.md +4 -4
- package/payload/METHOD-audit-subtraction.md +1 -0
- package/payload/METHOD-audit-zero-trust.md +1 -1
- package/payload/RULE-agent-behavior.md +41 -18
- package/payload/RULE-coding.md +5 -2
- package/payload/RULE-pattern-core.md +2 -2
- package/payload/RULE-release.md +3 -3
- package/payload/RULE-stack-akiNuxtCf.md +1 -1
- package/payload/RULE-test.md +53 -0
- package/payload/route-map.json +14 -0
- package/skills/akiflow/SKILL.md +1 -1
- package/skills/akiflow/scripts/scythe.py +1 -1
- package/skills/akiflow/scripts/suite_lint.py +168 -0
- package/skills/akihelp/SKILL.md +1 -0
- package/skills/akilint/SKILL.md +1 -1
- package/skills/akirule/SKILL.md +4 -2
- package/skills/akiship/SKILL.md +3 -3
- package/skills/akithink/SKILL.md +2 -2
package/install.mjs
CHANGED
|
@@ -45,6 +45,7 @@ const GEMINI_RULES_DIR = join(GEMINI_DIR, "config", "rules");
|
|
|
45
45
|
const GEMINI_SKILLS_DIR = join(GEMINI_DIR, "config", "skills");
|
|
46
46
|
|
|
47
47
|
const CODEX_SKILLS_DIR = join(HOME, ".agents", "skills");
|
|
48
|
+
const CODEX_HOME = process.env.CODEX_HOME ? expandTilde(process.env.CODEX_HOME.trim()) : join(HOME, ".codex");
|
|
48
49
|
const KIRO_SKILLS_DIR = join(HOME, ".kiro", "skills");
|
|
49
50
|
const GROK_SKILLS_DIR = join(HOME, ".grok", "skills");
|
|
50
51
|
const SKILLS_SRC = join(REPO_ROOT, "skills");
|
|
@@ -202,21 +203,34 @@ const blueBold = (t) => c("1;34", t);
|
|
|
202
203
|
// Backup / prune
|
|
203
204
|
// ---------------------------------------------------------------------------
|
|
204
205
|
|
|
206
|
+
// A backup is rollback material for this run only: removed once the run succeeds, kept when it fails. keepBackup() exempts one that holds content the install discarded.
|
|
207
|
+
const runBackups = new Set();
|
|
208
|
+
|
|
205
209
|
function backup(path) {
|
|
206
210
|
if (!existsSync(path)) return;
|
|
207
211
|
const dest = join(dirname(path), `${basename(path)}.akidevrule-backup-${STAMP}`);
|
|
208
212
|
if (isDir(path)) copyTree(path, dest);
|
|
209
213
|
else copyFileSync(path, dest);
|
|
214
|
+
runBackups.add(dest);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function keepBackup(path) {
|
|
218
|
+
runBackups.delete(join(dirname(path), `${basename(path)}.akidevrule-backup-${STAMP}`));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function dropRunBackups() {
|
|
222
|
+
for (const dest of runBackups) rmrf(dest);
|
|
223
|
+
runBackups.clear();
|
|
210
224
|
}
|
|
211
225
|
|
|
212
|
-
function pruneBackups(base) {
|
|
226
|
+
function pruneBackups(base, keep = 1) {
|
|
213
227
|
const parent = dirname(base);
|
|
214
228
|
const prefix = `${basename(base)}.akidevrule-backup-`;
|
|
215
229
|
const candidates = listDir(parent)
|
|
216
230
|
.filter((n) => n.startsWith(prefix))
|
|
217
231
|
.map((n) => join(parent, n))
|
|
218
232
|
.sort((a, b) => statSync(a).mtimeMs - statSync(b).mtimeMs);
|
|
219
|
-
for (const old of candidates.slice(0, -
|
|
233
|
+
for (const old of candidates.slice(0, -keep)) {
|
|
220
234
|
console.log(` ๐๏ธ Removing old backup: ${basename(old)}`);
|
|
221
235
|
rmrf(old);
|
|
222
236
|
}
|
|
@@ -342,6 +356,7 @@ const AG_RULE_MAP = [
|
|
|
342
356
|
["RULE-agent-behavior.md", "always_on", ""],
|
|
343
357
|
["RULE-coding.md", "model_decision", ""],
|
|
344
358
|
["RULE-pattern-core.md", "model_decision", ""],
|
|
359
|
+
["RULE-test.md", "model_decision", ""],
|
|
345
360
|
["RULE-docs.md", "model_decision", ""],
|
|
346
361
|
["RULE-content-write.md", "model_decision", ""],
|
|
347
362
|
["RULE-stack-akiNuxtCf.md", "glob", '["**/*.vue", "nuxt.config.*", "wrangler.toml", "app/**", "server/**", "composables/**", "middleware/**", "plugins/**", "layouts/**"]'],
|
|
@@ -396,6 +411,207 @@ function installAgRules() {
|
|
|
396
411
|
return rendered.length;
|
|
397
412
|
}
|
|
398
413
|
|
|
414
|
+
// ---------------------------------------------------------------------------
|
|
415
|
+
// Codex global instructions โ behavior floor + router as a managed block in $CODEX_HOME/AGENTS.md
|
|
416
|
+
// (docs/research/codex-instruction-delivery.md: Codex hard-loads that file; it expands no imports and reads no Claude global file)
|
|
417
|
+
// ---------------------------------------------------------------------------
|
|
418
|
+
|
|
419
|
+
const CODEX_BLOCK_START = "<!-- >>> akidevrule managed: behavior floor + router, regenerated on every install โ edit the source repo, never this block -->";
|
|
420
|
+
const CODEX_BLOCK_END = "<!-- <<< akidevrule managed -->";
|
|
421
|
+
const CODEX_DOC_BUDGET = 131072; // Codex stops adding instruction files once global + project files reach project_doc_max_bytes (32 KiB by default); the managed block alone is larger
|
|
422
|
+
const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
423
|
+
const TOML_HEADER = /^[ \t]*\[\[?[ \t]*[\w.\-"' ]+[ \t]*\]\]?[ \t]*(#.*)?$/m; // a whole-line [table] or [[array]] header, not a nested-array line inside a multi-line value
|
|
424
|
+
|
|
425
|
+
function codexInstructionBlock() {
|
|
426
|
+
const preamble =
|
|
427
|
+
"# akidevrule on Codex\n\n" +
|
|
428
|
+
"This block is the resident rule context for Codex: the behavior floor (`RULE-agent-behavior.md`) and the rule router (`skills/akirule/SKILL.md`), both installed under `~/.aki/akidevrule/`. Codex has no route gate: the Read of a routed rule file is your own mandatory step before the first edit of that artifact type. Do not invoke the `akirule` skill as well โ the routing below is already loaded.";
|
|
429
|
+
const floor = readFileSync(join(REPO_ROOT, "payload", "RULE-agent-behavior.md"), "utf-8").trim();
|
|
430
|
+
const router = readFileSync(ROUTER_SRC, "utf-8").trim();
|
|
431
|
+
return [CODEX_BLOCK_START, preamble, floor, router, CODEX_BLOCK_END].join("\n\n") + "\n";
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Top-level TOML keys must precede the first table header, so the key is inserted before it, once, and only when absent.
|
|
435
|
+
function ensureCodexDocBudget(blockBytes) {
|
|
436
|
+
const cfg = join(CODEX_HOME, "config.toml");
|
|
437
|
+
const text = isFile(cfg) ? readFileSync(cfg, "utf-8") : "";
|
|
438
|
+
const firstTable = text.search(TOML_HEADER);
|
|
439
|
+
const top = (firstTable === -1 ? text : text.slice(0, firstTable)).replace(/\s*$/, "");
|
|
440
|
+
const m = top.match(/^\s*project_doc_max_bytes\s*=\s*(\d+)/m);
|
|
441
|
+
if (m) return { value: Number(m[1]), written: false, enough: Number(m[1]) >= blockBytes };
|
|
442
|
+
const line = `project_doc_max_bytes = ${CODEX_DOC_BUDGET} # akidevrule: the managed block in AGENTS.md plus project instructions exceed Codex's 32 KiB default\n`;
|
|
443
|
+
const after = (top ? top + "\n\n" : "") + line + (firstTable === -1 ? "" : "\n" + text.slice(firstTable));
|
|
444
|
+
if (text) {
|
|
445
|
+
backup(cfg);
|
|
446
|
+
pruneBackups(cfg);
|
|
447
|
+
}
|
|
448
|
+
writeTextLf(cfg, after);
|
|
449
|
+
return { value: CODEX_DOC_BUDGET, written: true, enough: true };
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function installCodexInstructions() {
|
|
453
|
+
if (!isDir(CODEX_HOME)) return null;
|
|
454
|
+
const file = join(CODEX_HOME, "AGENTS.md");
|
|
455
|
+
const before = isFile(file) ? readFileSync(file, "utf-8") : "";
|
|
456
|
+
const block = codexInstructionBlock();
|
|
457
|
+
const user = before.replace(new RegExp(`\\n?${escapeRe(CODEX_BLOCK_START)}[\\s\\S]*?${escapeRe(CODEX_BLOCK_END)}\\n?`), "\n").trim();
|
|
458
|
+
const after = (user ? user + "\n\n" : "") + block;
|
|
459
|
+
const changed = after !== before;
|
|
460
|
+
if (changed) {
|
|
461
|
+
backup(file);
|
|
462
|
+
pruneBackups(file);
|
|
463
|
+
writeTextLf(file, after);
|
|
464
|
+
}
|
|
465
|
+
const override = join(CODEX_HOME, "AGENTS.override.md");
|
|
466
|
+
const shadowed = isFile(override) && readFileSync(override, "utf-8").trim() !== "";
|
|
467
|
+
const bytes = Buffer.byteLength(block, "utf-8");
|
|
468
|
+
return { file, bytes, changed, shadowed, budget: ensureCodexDocBudget(bytes) };
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// ---------------------------------------------------------------------------
|
|
472
|
+
// rtk โ optional output compressor on Claude Code's Bash tool; opt-in accuracy-first config (README ยง Optional: rtk, docs/plan/rtk-accuracy-first.md)
|
|
473
|
+
// ---------------------------------------------------------------------------
|
|
474
|
+
|
|
475
|
+
const RTK_OPT_OUT = "# akidevrule: rtk off";
|
|
476
|
+
const RTK_MANAGED_MARK = "# akidevrule: accuracy-first rtk";
|
|
477
|
+
const RTK_EXCLUDE = [
|
|
478
|
+
["ls", "tree", "find", "grep", "rg", "ast-grep", "wc", "diff"],
|
|
479
|
+
["cat", "head", "tail"],
|
|
480
|
+
["jq", "stat", "du", "df", "ps", "ssh", "rsync", "curl", "wget"],
|
|
481
|
+
["gh", "glab", "psql", "aws", "kubectl", "oc", "docker"],
|
|
482
|
+
["^git(\\s+-[Cc]\\s+\\S+)*\\s+(diff|log|show|stash|branch)\\b"],
|
|
483
|
+
];
|
|
484
|
+
const RTK_EVIDENCE_FAMILIES = "ls, grep, find, git diff/log/show, jq";
|
|
485
|
+
const RTK_NOISE_FAMILIES = "git status, package installs and build logs";
|
|
486
|
+
|
|
487
|
+
// TOML basic strings take JSON's escapes for backslash and quote, so JSON.stringify renders each entry.
|
|
488
|
+
function rtkExcludeLines() {
|
|
489
|
+
return RTK_EXCLUDE.map((group) => ` ${group.map((s) => JSON.stringify(s)).join(", ")},`).join("\n");
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function rtkHooksTable() {
|
|
493
|
+
return `${RTK_MANAGED_MARK} โ commands whose output is evidence run unfiltered (akidevrule README ยง Optional: rtk)\n[hooks]\nexclude_commands = [\n${rtkExcludeLines()}\n]\n`;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Fallback mirrors rtk's dirs::config_dir() layout (XDG, Application Support, RoamingAppData) when `rtk config` prints no path.
|
|
497
|
+
function rtkDetect() {
|
|
498
|
+
let r;
|
|
499
|
+
try {
|
|
500
|
+
r = spawnSync("rtk", ["config"], { encoding: "utf-8", timeout: 5000, shell: IS_WIN });
|
|
501
|
+
} catch {
|
|
502
|
+
return null;
|
|
503
|
+
}
|
|
504
|
+
if (r.error || r.status !== 0) return null;
|
|
505
|
+
const m = (r.stdout || "").match(/^Config:\s*(.+?)\s*$/m);
|
|
506
|
+
if (m) return { path: expandTilde(m[1]) };
|
|
507
|
+
const base = process.platform === "darwin" ? join(HOME, "Library", "Application Support") : IS_WIN ? process.env.APPDATA || join(HOME, "AppData", "Roaming") : process.env.XDG_CONFIG_HOME || join(HOME, ".config");
|
|
508
|
+
return { path: join(base, "rtk", "config.toml") };
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// The [name] table's body: from its header to the next table header. null when absent.
|
|
512
|
+
function tomlTable(text, name) {
|
|
513
|
+
const header = new RegExp(`^[ \\t]*\\[${escapeRe(name)}\\][ \\t]*(#.*)?$`, "m");
|
|
514
|
+
const h = text.search(header);
|
|
515
|
+
if (h === -1) return null;
|
|
516
|
+
const bodyStart = text.indexOf("\n", h) + 1;
|
|
517
|
+
const rest = text.slice(bodyStart);
|
|
518
|
+
const next = rest.search(TOML_HEADER);
|
|
519
|
+
const end = next === -1 ? text.length : bodyStart + next;
|
|
520
|
+
return { start: h, bodyStart, end, body: text.slice(bodyStart, end) };
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// String entries of the array that follows `key =`, read until the unquoted `]` (a quoted entry may itself contain one).
|
|
524
|
+
function tomlStringArray(body, key) {
|
|
525
|
+
const k = body.match(new RegExp(`^[ \\t]*${escapeRe(key)}[ \\t]*=[ \\t]*\\[`, "m"));
|
|
526
|
+
if (!k) return null;
|
|
527
|
+
const out = [];
|
|
528
|
+
let i = k.index + k[0].length;
|
|
529
|
+
while (i < body.length) {
|
|
530
|
+
const ch = body[i];
|
|
531
|
+
if (ch === "]") break;
|
|
532
|
+
if (ch === '"' || ch === "'") {
|
|
533
|
+
const close = ch === '"' ? /(?:[^"\\]|\\.)*"/y : /[^']*'/y;
|
|
534
|
+
close.lastIndex = i + 1;
|
|
535
|
+
const m = close.exec(body);
|
|
536
|
+
if (!m) break;
|
|
537
|
+
const raw = m[0].slice(0, -1);
|
|
538
|
+
out.push(ch === '"' ? JSON.parse(`"${raw}"`) : raw);
|
|
539
|
+
i = close.lastIndex;
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
if (ch === "#") i = body.indexOf("\n", i) === -1 ? body.length : body.indexOf("\n", i);
|
|
543
|
+
else i++;
|
|
544
|
+
}
|
|
545
|
+
return out;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Read-only classification: the state the file is already in, or null when a write would be needed (no file, no [hooks], [hooks] without exclude_commands).
|
|
549
|
+
function rtkReadState(cfg) {
|
|
550
|
+
const text = isFile(cfg) ? readFileSync(cfg, "utf-8") : "";
|
|
551
|
+
if (text.includes(RTK_OPT_OUT)) return { path: cfg, state: "opt-out" };
|
|
552
|
+
const hooks = tomlTable(text, "hooks");
|
|
553
|
+
const have = hooks && tomlStringArray(hooks.body, "exclude_commands");
|
|
554
|
+
if (!have) return null;
|
|
555
|
+
const missing = RTK_EXCLUDE.flat().filter((s) => !have.includes(s));
|
|
556
|
+
return { path: cfg, state: missing.length ? "kept" : "unchanged", missing };
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Smallest safe change: no file โ write; no [hooks] โ append the table; [hooks] without exclude_commands โ insert the key; anything else is rtkReadState's answer.
|
|
560
|
+
function rtkConfigure(cfg) {
|
|
561
|
+
const known = rtkReadState(cfg);
|
|
562
|
+
if (known) return known;
|
|
563
|
+
const text = isFile(cfg) ? readFileSync(cfg, "utf-8") : "";
|
|
564
|
+
const write = (after) => {
|
|
565
|
+
if (text) {
|
|
566
|
+
backup(cfg);
|
|
567
|
+
pruneBackups(cfg);
|
|
568
|
+
}
|
|
569
|
+
writeTextLf(cfg, after);
|
|
570
|
+
};
|
|
571
|
+
const hooks = tomlTable(text, "hooks");
|
|
572
|
+
if (!hooks) {
|
|
573
|
+
write((text ? text.replace(/\s*$/, "") + "\n\n" : "") + rtkHooksTable());
|
|
574
|
+
return { path: cfg, state: text ? "appended" : "written" };
|
|
575
|
+
}
|
|
576
|
+
write(text.slice(0, hooks.bodyStart) + `exclude_commands = [ ${RTK_MANAGED_MARK}\n${rtkExcludeLines()}\n]\n` + text.slice(hooks.bodyStart));
|
|
577
|
+
return { path: cfg, state: "appended" };
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function rtkPrompt(cfg) {
|
|
581
|
+
console.log(`\n๐ ${yellowBold("rtk found")} โ an output compressor that hooks into Claude Code's Bash tool.`);
|
|
582
|
+
console.log(` Its built-in filters save tokens by dropping lines โ also from ${RTK_EVIDENCE_FAMILIES}, whose output an agent reads as evidence (a count, a quote, a "not found").`);
|
|
583
|
+
console.log(` ${greenBold("y")} โ write an accuracy-first rtk config at ${toTildePath(cfg)}: those commands run unfiltered; ${RTK_NOISE_FAMILIES} stay compressed. Existing file backed up; opt out later with a "${RTK_OPT_OUT}" line.`);
|
|
584
|
+
console.log(` ${greenBold("N")} โ leave rtk as it is (default). Details: README ยง Optional: rtk`);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function printRtkSummary(rtk) {
|
|
588
|
+
console.log();
|
|
589
|
+
console.log(cyanBold("rtk (optional output compressor):"));
|
|
590
|
+
if (!rtk) {
|
|
591
|
+
console.log(" โน๏ธ rtk not found โ nothing to configure. If you install it later, re-run with --rtk to run evidence commands unfiltered (README ยง Optional: rtk)");
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
const p = toTildePath(rtk.path);
|
|
595
|
+
switch (rtk.state) {
|
|
596
|
+
case "declined":
|
|
597
|
+
console.log(` โ ๏ธ rtk found, left as is โ its default filters drop evidence lines from ${RTK_EVIDENCE_FAMILIES}. To run those unfiltered: re-run and answer y, or \`node install.mjs --rtk\` (README ยง Optional: rtk)`);
|
|
598
|
+
break;
|
|
599
|
+
case "written":
|
|
600
|
+
case "appended":
|
|
601
|
+
console.log(` โ
accuracy-first config ${rtk.state === "written" ? "written" : "added"}: ${p} โ ${RTK_EVIDENCE_FAMILIES} โฆ unfiltered; ${RTK_NOISE_FAMILIES} still compressed`);
|
|
602
|
+
break;
|
|
603
|
+
case "unchanged":
|
|
604
|
+
console.log(` โ
accuracy-first config already in place: ${p}`);
|
|
605
|
+
break;
|
|
606
|
+
case "kept":
|
|
607
|
+
console.log(` โ ๏ธ ${p} has its own [hooks] exclude_commands โ kept as yours; not in it yet: ${rtk.missing.join(", ")}`);
|
|
608
|
+
break;
|
|
609
|
+
case "opt-out":
|
|
610
|
+
console.log(` โน๏ธ ${p} carries "${RTK_OPT_OUT}" โ skipped`);
|
|
611
|
+
break;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
399
615
|
// ---------------------------------------------------------------------------
|
|
400
616
|
// settings.json merge (Claude Code)
|
|
401
617
|
// ---------------------------------------------------------------------------
|
|
@@ -456,7 +672,14 @@ function mergeSettings(settingsPath, installRoot, claudeDir) {
|
|
|
456
672
|
return false;
|
|
457
673
|
}
|
|
458
674
|
};
|
|
459
|
-
|
|
675
|
+
const isAkiCompact = (entry) => {
|
|
676
|
+
try {
|
|
677
|
+
return (entry.hooks || []).some((h) => (h.command || "").includes("aki-compact-reread"));
|
|
678
|
+
} catch {
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
hooks.SessionStart = hooks.SessionStart.filter((e) => !isAkiUpdate(e) && !isAkiCompact(e));
|
|
460
683
|
hooks.SessionStart.push({
|
|
461
684
|
matcher: "startup|resume",
|
|
462
685
|
hooks: [
|
|
@@ -467,6 +690,16 @@ function mergeSettings(settingsPath, installRoot, claudeDir) {
|
|
|
467
690
|
},
|
|
468
691
|
],
|
|
469
692
|
});
|
|
693
|
+
hooks.SessionStart.push({
|
|
694
|
+
matcher: "compact",
|
|
695
|
+
hooks: [
|
|
696
|
+
{
|
|
697
|
+
type: "command",
|
|
698
|
+
command: `node "${join(claudeDir, "hooks", "aki-compact-reread.mjs")}"`,
|
|
699
|
+
timeout: 5,
|
|
700
|
+
},
|
|
701
|
+
],
|
|
702
|
+
});
|
|
470
703
|
|
|
471
704
|
if (!Array.isArray(hooks.PreToolUse)) hooks.PreToolUse = [];
|
|
472
705
|
const isAkiRouteGuard = (entry) => {
|
|
@@ -477,16 +710,18 @@ function mergeSettings(settingsPath, installRoot, claudeDir) {
|
|
|
477
710
|
}
|
|
478
711
|
};
|
|
479
712
|
hooks.PreToolUse = hooks.PreToolUse.filter((e) => !isAkiRouteGuard(e));
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
713
|
+
for (const matcher of ["Edit|MultiEdit|Write|NotebookEdit", "Read"]) {
|
|
714
|
+
hooks.PreToolUse.push({
|
|
715
|
+
matcher,
|
|
716
|
+
hooks: [
|
|
717
|
+
{
|
|
718
|
+
type: "command",
|
|
719
|
+
command: `node "${join(claudeDir, "hooks", "aki-route-guard.mjs")}"`,
|
|
720
|
+
timeout: 10,
|
|
721
|
+
},
|
|
722
|
+
],
|
|
723
|
+
});
|
|
724
|
+
}
|
|
490
725
|
|
|
491
726
|
writeTextLf(settingsPath, JSON.stringify(data, null, 2) + "\n");
|
|
492
727
|
}
|
|
@@ -560,6 +795,7 @@ function installClaudeDir(claudeDir) {
|
|
|
560
795
|
copyFileSync(join(REPO_ROOT, "claude", "hooks", "aki-update-check.mjs"), join(hooksDest, "aki-update-check.mjs"));
|
|
561
796
|
copyFileSync(join(REPO_ROOT, "claude", "hooks", "aki_version_check.mjs"), join(hooksDest, "aki_version_check.mjs"));
|
|
562
797
|
copyFileSync(join(REPO_ROOT, "claude", "hooks", "aki-route-guard.mjs"), join(hooksDest, "aki-route-guard.mjs"));
|
|
798
|
+
copyFileSync(join(REPO_ROOT, "claude", "hooks", "aki-compact-reread.mjs"), join(hooksDest, "aki-compact-reread.mjs"));
|
|
563
799
|
for (const legacy of ["aki-update-check.py", "aki_version_check.py"]) {
|
|
564
800
|
const p = join(hooksDest, legacy);
|
|
565
801
|
if (existsSync(p)) rmrf(p);
|
|
@@ -745,6 +981,8 @@ async function inspectStatus(claudeDirs) {
|
|
|
745
981
|
}
|
|
746
982
|
}
|
|
747
983
|
|
|
984
|
+
if (isDir(CODEX_HOME)) console.log(`๐ค Codex (${toTildePath(CODEX_HOME)}): will write the managed block in AGENTS.md (your own lines kept) and set project_doc_max_bytes in config.toml when absent`);
|
|
985
|
+
|
|
748
986
|
if (isDir(GEMINI_DIR)) {
|
|
749
987
|
const agSettings = join(GEMINI_DIR, "antigravity-cli", "settings.json");
|
|
750
988
|
if (isFile(agSettings)) {
|
|
@@ -770,7 +1008,7 @@ async function inspectStatus(claudeDirs) {
|
|
|
770
1008
|
// print_summary (post-install)
|
|
771
1009
|
// ---------------------------------------------------------------------------
|
|
772
1010
|
|
|
773
|
-
function printSummary(claudeDirs, preAllow) {
|
|
1011
|
+
function printSummary(claudeDirs, preAllow, codex, rtk) {
|
|
774
1012
|
console.log(`\n${greenBold("=== INSTALL SUCCEEDED ===")}`);
|
|
775
1013
|
|
|
776
1014
|
const gitHash = gitShortHash(REPO_ROOT);
|
|
@@ -817,6 +1055,19 @@ function printSummary(claudeDirs, preAllow) {
|
|
|
817
1055
|
console.log(` ๐ค Kiro CLI : ${KIRO_SKILLS_DIR}`);
|
|
818
1056
|
console.log(` ๐ค Grok CLI : ${GROK_SKILLS_DIR}`);
|
|
819
1057
|
|
|
1058
|
+
console.log();
|
|
1059
|
+
console.log(cyanBold("Codex global instructions (behavior floor + router, resident):"));
|
|
1060
|
+
if (!codex) {
|
|
1061
|
+
console.log(` โน๏ธ ${toTildePath(CODEX_HOME)} not found โ skipped; set CODEX_HOME or install Codex and re-run`);
|
|
1062
|
+
} else {
|
|
1063
|
+
console.log(` ๐ ${toTildePath(codex.file)} โ managed block ${(codex.bytes / 1024).toFixed(1)} KB${codex.changed ? "" : ", unchanged"}; lines of your own in that file are kept`);
|
|
1064
|
+
const b = codex.budget;
|
|
1065
|
+
const budgetNote = b.written ? "written to config.toml โ Codex stops loading instruction files past 32 KiB by default" : b.enough ? "already set" : `โ ๏ธ smaller than the managed block (${codex.bytes} B) โ raise it in ${toTildePath(join(CODEX_HOME, "config.toml"))}`;
|
|
1066
|
+
console.log(` โ๏ธ project_doc_max_bytes = ${b.value} โ ${budgetNote}`);
|
|
1067
|
+
if (codex.shadowed) console.log(` โ ๏ธ ${toTildePath(join(CODEX_HOME, "AGENTS.override.md"))} is non-empty and shadows AGENTS.md โ Codex ignores the managed block until it is removed`);
|
|
1068
|
+
console.log(` โน๏ธ Codex does not read a project's CLAUDE.md; opt in per machine with project_doc_fallback_filenames = ["CLAUDE.md"] in config.toml (a native AGENTS.md in the same directory still wins)`);
|
|
1069
|
+
}
|
|
1070
|
+
|
|
820
1071
|
console.log();
|
|
821
1072
|
console.log(cyanBold("Skill scripts pre-allowed (no prompt when a skill runs its own scripts):"));
|
|
822
1073
|
console.log(" โ๏ธ claude settings.json in every target above โ plus Read(~/.aki/akidevrule/**)");
|
|
@@ -828,9 +1079,12 @@ function printSummary(claudeDirs, preAllow) {
|
|
|
828
1079
|
|
|
829
1080
|
console.log();
|
|
830
1081
|
console.log(cyanBold("Hooks deployed:"));
|
|
831
|
-
console.log(" ๐ง aki-route-guard (PreToolUse on Edit|MultiEdit|Write|NotebookEdit) โ denies the first edit of an artifact type until its routed rule was Read; AKI_ROUTE_GUARD=0 disables");
|
|
1082
|
+
console.log(" ๐ง aki-route-guard (PreToolUse on Edit|MultiEdit|Write|NotebookEdit and Read) โ edit gate: denies the first edit of an artifact type until its routed rule was Read whole after the last compaction; Read of a rule file is widened to the whole file; AKI_ROUTE_GUARD=0 disables");
|
|
1083
|
+
console.log(" ๐ aki-compact-reread (SessionStart, compact only) โ one notice after a compaction: routed rules left context, re-read before the next edit");
|
|
832
1084
|
console.log(" ๐ข aki-update-check (SessionStart, notify-only) โ notifies when a new rule version is available");
|
|
833
1085
|
|
|
1086
|
+
printRtkSummary(rtk);
|
|
1087
|
+
|
|
834
1088
|
console.log(`\n${greenBold("==============================")}`);
|
|
835
1089
|
}
|
|
836
1090
|
|
|
@@ -852,7 +1106,7 @@ function ask(question) {
|
|
|
852
1106
|
// Main install logic
|
|
853
1107
|
// ---------------------------------------------------------------------------
|
|
854
1108
|
|
|
855
|
-
async function runInstall(claudeDirs) {
|
|
1109
|
+
async function runInstall(claudeDirs, { rtkFlag = false } = {}) {
|
|
856
1110
|
const sameCheckout = await inspectStatus(claudeDirs);
|
|
857
1111
|
|
|
858
1112
|
// Skip the prompt only for a byte-identical overwrite (same commit, clean tree).
|
|
@@ -864,6 +1118,15 @@ async function runInstall(claudeDirs) {
|
|
|
864
1118
|
}
|
|
865
1119
|
}
|
|
866
1120
|
|
|
1121
|
+
// rtk: asked up front so the run has no mid-install question; default and non-interactive answer is "leave it".
|
|
1122
|
+
const rtkFound = rtkDetect();
|
|
1123
|
+
const rtkKnown = rtkFound && rtkReadState(rtkFound.path);
|
|
1124
|
+
let rtkYes = rtkFlag;
|
|
1125
|
+
if (rtkFound && !rtkKnown && !rtkFlag && process.stdin.isTTY) {
|
|
1126
|
+
rtkPrompt(rtkFound.path);
|
|
1127
|
+
rtkYes = (await ask("Configure rtk accuracy-first? [y/N]: ")).trim().toLowerCase() === "y";
|
|
1128
|
+
}
|
|
1129
|
+
|
|
867
1130
|
// Legacy migration: ~/.aki/claudedoc -> ~/.aki/akidevrule
|
|
868
1131
|
if (isDir(LEGACY_INSTALL_ROOT) && !existsSync(INSTALL_ROOT)) {
|
|
869
1132
|
mkdirSync(dirname(INSTALL_ROOT), { recursive: true });
|
|
@@ -921,10 +1184,11 @@ async function runInstall(claudeDirs) {
|
|
|
921
1184
|
installClaudeDir(cDir);
|
|
922
1185
|
}
|
|
923
1186
|
|
|
924
|
-
// --- 3. Other CLI skill roots ---
|
|
1187
|
+
// --- 3. Other CLI skill roots, and Codex's resident instructions ---
|
|
925
1188
|
syncAkiSkills(CODEX_SKILLS_DIR);
|
|
926
1189
|
syncAkiSkills(KIRO_SKILLS_DIR);
|
|
927
1190
|
syncAkiSkills(GROK_SKILLS_DIR);
|
|
1191
|
+
const codex = installCodexInstructions();
|
|
928
1192
|
|
|
929
1193
|
// --- 4. GEMINI.md (only when ~/.gemini exists) ---
|
|
930
1194
|
if (isDir(GEMINI_DIR)) {
|
|
@@ -946,8 +1210,8 @@ async function runInstall(claudeDirs) {
|
|
|
946
1210
|
}
|
|
947
1211
|
|
|
948
1212
|
backup(geminiFile);
|
|
949
|
-
|
|
950
|
-
pruneBackups(geminiFile);
|
|
1213
|
+
if (hadUnmanaged) keepBackup(geminiFile);
|
|
1214
|
+
pruneBackups(geminiFile, hadUnmanaged ? 2 : 1);
|
|
951
1215
|
|
|
952
1216
|
const d = new Date();
|
|
953
1217
|
const geminiVersion = `V${d.getFullYear()}${pad2(d.getMonth() + 1)}${pad2(d.getDate())}`;
|
|
@@ -989,7 +1253,11 @@ async function runInstall(claudeDirs) {
|
|
|
989
1253
|
// --- 5. Script pre-allow for every other harness present ---
|
|
990
1254
|
const preAllow = preAllowHarnessScripts(claudeDirs);
|
|
991
1255
|
|
|
992
|
-
|
|
1256
|
+
// --- 6. rtk accuracy-first config: an existing state is reported as is, a write only on an explicit yes ---
|
|
1257
|
+
const rtk = rtkFound ? (rtkKnown || (rtkYes ? rtkConfigure(rtkFound.path) : { path: rtkFound.path, state: "declined" })) : null;
|
|
1258
|
+
|
|
1259
|
+
dropRunBackups();
|
|
1260
|
+
printSummary(claudeDirs, preAllow, codex, rtk);
|
|
993
1261
|
}
|
|
994
1262
|
|
|
995
1263
|
// ---------------------------------------------------------------------------
|
|
@@ -1001,9 +1269,10 @@ async function main() {
|
|
|
1001
1269
|
if (argv.includes("-h") || argv.includes("--help")) {
|
|
1002
1270
|
console.log("akidevrule installer โ deploys shared rule corpus and skills.");
|
|
1003
1271
|
console.log("");
|
|
1004
|
-
console.log("Usage: node install.mjs [--check] [--claude-dir <path>]");
|
|
1272
|
+
console.log("Usage: node install.mjs [--check] [--claude-dir <path>] [--rtk]");
|
|
1005
1273
|
console.log(" --check Print installed vs latest akidevrule version and exit. No install, no overwrite.");
|
|
1006
1274
|
console.log(" --claude-dir <path> Explicit Claude config directory to include (also auto-detects ~/.claude* and $CLAUDE_CONFIG_DIR).");
|
|
1275
|
+
console.log(" --rtk If rtk is installed, write its accuracy-first config without asking (evidence commands run unfiltered). Without this flag the installer asks in a terminal and otherwise leaves rtk untouched.");
|
|
1007
1276
|
process.exit(0);
|
|
1008
1277
|
}
|
|
1009
1278
|
if (argv.includes("--check")) {
|
|
@@ -1012,7 +1281,7 @@ async function main() {
|
|
|
1012
1281
|
}
|
|
1013
1282
|
const claudeDirs = getClaudeDirs(argv);
|
|
1014
1283
|
preflightInstallSettings(claudeDirs);
|
|
1015
|
-
await runInstall(claudeDirs);
|
|
1284
|
+
await runInstall(claudeDirs, { rtkFlag: argv.includes("--rtk") });
|
|
1016
1285
|
}
|
|
1017
1286
|
|
|
1018
1287
|
main().catch((err) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akinet/akidevrule",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.0",
|
|
4
4
|
"description": "Aki's shared rule corpus + Agent Skills for Claude Code, Gemini/Antigravity, Codex, Kiro, Grok, Cursor and OpenCode โ install and update with one command.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
package/payload/GEMINI.md
CHANGED
|
@@ -57,11 +57,11 @@ These directives patch Antigravity's known weak spots. They are hard-loaded (no
|
|
|
57
57
|
- You **MUST** match user intent precisely: a question gets an ANSWER; a task gets EXECUTED.
|
|
58
58
|
- **COMMUNICATION (a question, a discussion, a request for an explanation) is strictly READ-ONLY.** When the user asks, discusses, or wants something explained, you **MUST NOT** edit any file or run any state-changing command to "answer" it. Answer in chat only. This is **absolute โ there is no "it was an obvious fix" exception.**
|
|
59
59
|
- If, during communication, you notice something worth changing, you **MUST** only PROPOSE it in chat and STOP. Proposing is welcome; touching anything is a failed turn (rule 0).
|
|
60
|
-
- **"Can we / should we / is it possible to X?" is COMMUNICATION, not authorization to do X.** Answer whether/how first; act only after the user issues an explicit task.
|
|
60
|
+
- **"Can we / should we / is it possible to X?" is COMMUNICATION, not authorization to do X.** Answer whether/how first; act only after the user issues an explicit task. "Can you fix X?" โ an action verb aimed at you โ IS a task.
|
|
61
61
|
- **SUSPENDED BIASES โ permanent, non-negotiable.** Your helpfulness bias, your shortcut/summarize bias, and your eagerness-to-act bias are SUSPENDED in this environment. Being "proactive", "efficient", or "helpful" is NEVER a reason to touch a file, run a state-changing command, or compress away a part of the user's prompt. When the user is talking, you LISTEN and ANSWER โ you do not act. Acting during COMMUNICATION โ including a "small harmless fix" performed while being corrected โ is the single most punished failure in this environment.
|
|
62
|
-
- **Never improvise under correction.** Being scolded or corrected is COMMUNICATION, not a request for visible progress. Do NOT perform an unrequested action (a copy, a delete, a quick edit) to demonstrate responsiveness โ stop, answer, and wait for the explicit task.
|
|
62
|
+
- **Never improvise under correction.** Being scolded or corrected for an act nobody ordered is COMMUNICATION, not a request for visible progress; a correction of ordered work ("you missed file X") resumes that work. Do NOT perform an unrequested action (a copy, a delete, a quick edit) to demonstrate responsiveness โ stop, answer, and wait for the explicit task.
|
|
63
63
|
- **TASK (an explicit instruction to do something) gets EXECUTED** strictly within scope โ no over-engineering, no extra files, no adjacent "while I'm here" edits โ then you report and STOP.
|
|
64
|
-
-
|
|
64
|
+
- A TASK runs to the end without asking; pause only for an `agent.A3` escalation (a destructive or published step, a conflict with documented design, ambiguous wording whose readings lead to different irreversible results).
|
|
65
65
|
|
|
66
66
|
## 9. Direct, minimal communication โ NO YAPPING AT ALL
|
|
67
67
|
- **NO YAPPING AT ALL.** No filler, no cheerleading, no restating the request, no "I will nowโฆ" narration, no unsolicited next-step menus. Say the answer, then stop.
|
|
@@ -131,7 +131,7 @@ Do not skip this because the work "went smoothly". Smooth work is exactly when t
|
|
|
131
131
|
Before calling ANY write/edit tool or executing ANY state-changing command, you **MUST** explicitly write out this checklist **inside your hidden thought block**. Do NOT print it in the chat response to the user.
|
|
132
132
|
|
|
133
133
|
- [ ] CHECK 0: Does the user's message contain a `/skill` token (e.g. `/akiflow`, `/akirule`, `/akithink`, `/akiship`) that has not been dispatched yet? (If YES: read that skill's `SKILL.md` and follow it FIRST โ rule 14.)
|
|
134
|
-
- [ ] CHECK 1:
|
|
134
|
+
- [ ] CHECK 1: Do the user's own words order this edit or command โ a task that names it or plainly needs it, never your judgement of what would be better (rule 0)?
|
|
135
135
|
- [ ] CHECK 2: Is the user in a TASK phase, or just a COMMUNICATION phase? (If COMMUNICATION, using write/execute tools is a FATAL ERROR).
|
|
136
136
|
|
|
137
137
|
If the answer to Check 1 is NO, or Check 2 is COMMUNICATION: **STOP IMMEDIATELY**. Do NOT execute the tool. Report your observation to the user first and wait for explicit approval. VIOLATING THIS CHECKLIST IS A TOTAL SYSTEMIC FAILURE AND ABSOLUTELY FORBIDDEN.
|
|
@@ -35,6 +35,7 @@ Run only what the project actually has; name what was skipped and why (`zero-tru
|
|
|
35
35
|
| Docs | docs nothing links, plans whose work shipped, superseded research with no chain marker | `docs.C3` |
|
|
36
36
|
| Content | i18n keys nobody reads, strings for removed features | `content.A3` |
|
|
37
37
|
| Operational leftovers | migrations already run and still pending-located, one-shot scripts, dead flags and env vars | `release.B5`, `stack.C8` |
|
|
38
|
+
| Tests | a test that checks nothing, duplicates another, compares to the implementation's own value, or carries a fixture larger than its boundary | `test.A1`, `test.A2`, `test.D` |
|
|
38
39
|
|
|
39
40
|
### B2. Severity classes for subtraction โ and the class that forbids removal
|
|
40
41
|
- **Dead** โ no reference anywhere in the locked scope. CERTAIN, machine-decidable, countable.
|
|
@@ -14,7 +14,7 @@ Zero trust means nothing counts as clean because it looks clean: a finding exist
|
|
|
14
14
|
|
|
15
15
|
## B. Mechanical pass runs first
|
|
16
16
|
|
|
17
|
-
1. **Run the detectors before forming any opinion** โ typecheck, the repo's linter, `scythe.py` (`skills/akiflow/scripts/scythe.py` in the akidevrule source repo) for `[WRAP]`/`[YAP]`, and the targeted `grep` scans the relevant rule file already specifies (`ui.C1` for frontend, `flow` for state, `release.B` for version state).
|
|
17
|
+
1. **Run the detectors before forming any opinion** โ typecheck, the repo's linter, `scythe.py` (`skills/akiflow/scripts/scythe.py` in the akidevrule source repo) for `[WRAP]`/`[YAP]`, and the targeted `grep` scans the relevant rule file already specifies (`ui.C1` for frontend, `flow` for state, `release.B` for version state, `test.D1` for a test suite).
|
|
18
18
|
2. **Run only what the project actually has**, and name what you skipped and why. A missing `tsconfig.json` means there is no typecheck to run, not a gap to invent one for. Never run a build or a dev server to satisfy this step.
|
|
19
19
|
3. **Attach the raw output**, and attach it *before* stating a conclusion. A tool run afterwards to confirm something already asserted is not verification.
|
|
20
20
|
|