@akinet/akidevrule 3.3.1 → 3.4.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 +34 -0
- package/README.md +21 -20
- package/claude/CLAUDE.md +5 -6
- package/claude/agents/aki-challenger.md +1 -1
- package/claude/agents/aki-conduct.md +2 -2
- package/claude/agents/aki-hands.md +4 -4
- package/claude/agents/aki-judge.md +2 -2
- package/claude/agents/aki-maker.md +2 -2
- package/install.mjs +115 -292
- package/lib/permissions.mjs +244 -0
- package/package.json +5 -2
- package/payload/GEMINI.md +2 -0
- package/payload/METHOD-audit-frozen-reference.md +33 -0
- package/payload/METHOD-audit-zero-trust.md +1 -1
- package/payload/METHOD-deep-think.md +1 -1
- package/payload/RULE-agent-behavior.md +3 -2
- package/payload/RULE-coding.md +2 -1
- package/payload/RULE-docs.md +12 -1
- package/payload/RULE-pattern-core.md +1 -1
- package/payload/RULE-release.md +2 -2
- package/payload/RULE-ui-pattern.md +1 -1
- package/payload/index.md +11 -7
- package/skills/aki-article-writer/SKILL.md +1 -1
- package/skills/akidevsync-notes/SKILL.md +1 -1
- package/skills/akiflow/references/harness-facts.md +8 -6
- package/skills/akihelp/SKILL.md +7 -7
- package/skills/akihtmlreport/SKILL.md +1 -1
- package/skills/akilint/SKILL.md +1 -1
- package/skills/akirule/SKILL.md +45 -131
- package/skills/akiship/SKILL.md +3 -3
- package/skills/akithink/SKILL.md +8 -7
- package/skills/akiflow/scripts/council-cost.sh +0 -4
- package/skills/akiflow/scripts/council-open.sh +0 -4
- package/skills/akiflow/scripts/council-read.sh +0 -4
- package/skills/akiflow/scripts/council-verify.sh +0 -4
- package/skills/akiflow/scripts/scythe.sh +0 -4
package/install.mjs
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
localInstallPresent,
|
|
29
29
|
parseChangelogVersion,
|
|
30
30
|
} from "./claude/hooks/aki_version_check.mjs";
|
|
31
|
+
import { applyHarnessPreAllow, claudeAllowRules, listSkillScripts, ownershipTest, recoverSkillRoots, replaceOwned, scriptInvocations } from "./lib/permissions.mjs";
|
|
31
32
|
|
|
32
33
|
// ---------------------------------------------------------------------------
|
|
33
34
|
// Paths
|
|
@@ -46,6 +47,7 @@ const GEMINI_SKILLS_DIR = join(GEMINI_DIR, "config", "skills");
|
|
|
46
47
|
const CODEX_SKILLS_DIR = join(HOME, ".agents", "skills");
|
|
47
48
|
const KIRO_SKILLS_DIR = join(HOME, ".kiro", "skills");
|
|
48
49
|
const GROK_SKILLS_DIR = join(HOME, ".grok", "skills");
|
|
50
|
+
const SKILLS_SRC = join(REPO_ROOT, "skills");
|
|
49
51
|
|
|
50
52
|
const OLD_SKILLS = ["akidoc-rules", "akidoc-flow-audit", "akidoc-techbiz-optimizer", "akiadvise"];
|
|
51
53
|
|
|
@@ -327,111 +329,35 @@ function gitBranch(repo) {
|
|
|
327
329
|
// Antigravity rule map
|
|
328
330
|
// ---------------------------------------------------------------------------
|
|
329
331
|
|
|
330
|
-
|
|
332
|
+
const ROUTER_SRC = join(SKILLS_SRC, "akirule", "SKILL.md");
|
|
333
|
+
|
|
334
|
+
// The router's routes table is the single routing source; AG's native rule descriptions are derived from it.
|
|
335
|
+
function routerClauses() {
|
|
336
|
+
const table = readFileSync(ROUTER_SRC, "utf-8").matchAll(/^\| `((?:RULE|METHOD)-[^`]+\.md)` \| ([^|]+) \|/gm);
|
|
337
|
+
return new Map([...table].map(([, file, clause]) => [file, clause.trim()]));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Each entry: [ruleFile, trigger, globs, description] — globs is a raw JSON array string or ""; description only for core rules, which the router does not route.
|
|
331
341
|
const AG_RULE_MAP = [
|
|
332
|
-
["RULE-agent-behavior.md", "always_on", ""
|
|
333
|
-
[
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
],
|
|
339
|
-
[
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
],
|
|
345
|
-
[
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
],
|
|
351
|
-
[
|
|
352
|
-
"RULE-content-write.md",
|
|
353
|
-
"model_decision",
|
|
354
|
-
"UI copy, semantic stability, writing style and i18n. Load when writing user-facing text.",
|
|
355
|
-
"",
|
|
356
|
-
],
|
|
357
|
-
[
|
|
358
|
-
"RULE-stack-akiNuxtCf.md",
|
|
359
|
-
"glob",
|
|
360
|
-
"Nuxt, Vue, Cloudflare Pages and Workers, Tailwind, i18n, state and build conventions. Load when working in a Nuxt or Cloudflare project.",
|
|
361
|
-
'["**/*.vue", "**/*.ts", "nuxt.config.*", "server/**/*.ts"]',
|
|
362
|
-
],
|
|
363
|
-
[
|
|
364
|
-
"RULE-stack-tauri.md",
|
|
365
|
-
"glob",
|
|
366
|
-
"Tauri v2 and Rust conventions, including the never-block-the-UI rule for subprocess and network commands. Load when working in a Tauri project.",
|
|
367
|
-
'["src-tauri/**", "**/*.rs", "tauri.conf.json"]',
|
|
368
|
-
],
|
|
369
|
-
[
|
|
370
|
-
"RULE-ui-pattern.md",
|
|
371
|
-
"model_decision",
|
|
372
|
-
"Frontend design-system layer: the subtraction pass that runs before the class-tier ladder, class taxonomy, design tokens in whichever mechanism the installed framework version uses, the aggregate style-block budget, arbitrary-value policy, variant APIs and the audit playbook. Load when building, minimizing or auditing UI components and styles.",
|
|
373
|
-
"",
|
|
374
|
-
],
|
|
375
|
-
[
|
|
376
|
-
"RULE-seo.md",
|
|
377
|
-
"model_decision",
|
|
378
|
-
"Meta limits, schema.org, robots, sitemap, Open Graph and AI visibility. Load when working on SEO or page metadata.",
|
|
379
|
-
"",
|
|
380
|
-
],
|
|
381
|
-
[
|
|
382
|
-
"RULE-release.md",
|
|
383
|
-
"model_decision",
|
|
384
|
-
"CHANGELOG discipline, release versus deploy boundary, severity-driven version bumps and the pre-ship gate for finished-but-unpushed work. Load when preparing a release, writing a changelog, or checking whether finished work is actually shippable.",
|
|
385
|
-
"",
|
|
386
|
-
],
|
|
387
|
-
[
|
|
388
|
-
"RULE-db-design.md",
|
|
389
|
-
"model_decision",
|
|
390
|
-
"Immutability and event sourcing, normalization, bounded contexts, flat-query discipline. Load when designing a schema, migration or database refactor.",
|
|
391
|
-
"",
|
|
392
|
-
],
|
|
393
|
-
[
|
|
394
|
-
"RULE-biz.md",
|
|
395
|
-
"model_decision",
|
|
396
|
-
"Positioning, audience, USP, pricing, monetization and customer-psychology messaging rules. Load on any market-facing decision or when working on docs/biz content.",
|
|
397
|
-
"",
|
|
398
|
-
],
|
|
399
|
-
[
|
|
400
|
-
"METHOD-audit-flow.md",
|
|
401
|
-
"model_decision",
|
|
402
|
-
"Method for auditing end-to-end flow integrity. Load when guards and checks keep accumulating around a flow.",
|
|
403
|
-
"",
|
|
404
|
-
],
|
|
405
|
-
[
|
|
406
|
-
"METHOD-audit-zero-trust.md",
|
|
407
|
-
"model_decision",
|
|
408
|
-
"Strict mechanical-first audit: scope locked by command, detectors run before any opinion, findings split into exact machine matches versus pattern-level candidates, short findings-only report. Load when the user asks for an uncompromising sweep of a project or of a change and everything it touches.",
|
|
409
|
-
"",
|
|
410
|
-
],
|
|
411
|
-
[
|
|
412
|
-
"METHOD-deep-think.md",
|
|
413
|
-
"model_decision",
|
|
414
|
-
"Deep-think method: goal excavation, first principles, mandatory critique. Load for big, hard-to-reverse or goal-ambiguous decisions.",
|
|
415
|
-
"",
|
|
416
|
-
],
|
|
417
|
-
[
|
|
418
|
-
"METHOD-ux-psych.md",
|
|
419
|
-
"model_decision",
|
|
420
|
-
"UX psychology audit: cognitive load, recognition, feedback, defaults, motor cost and mental-model lenses with a persona walkthrough protocol. Load when evaluating an interface or user flow through user behavior.",
|
|
421
|
-
"",
|
|
422
|
-
],
|
|
423
|
-
[
|
|
424
|
-
"METHOD-proportionality.md",
|
|
425
|
-
"model_decision",
|
|
426
|
-
"Sizing a defense against its real threat: reach, capability, motive and blast radius measured before any guard, limit, quota or accepted risk is added, kept or removed; irreversibility outranks frequency; client-side limits are UX, never enforcement. Load whenever protection is being proposed, sized or dropped.",
|
|
427
|
-
"",
|
|
428
|
-
],
|
|
429
|
-
[
|
|
430
|
-
"METHOD-audit-subtraction.md",
|
|
431
|
-
"model_decision",
|
|
432
|
-
"Repo-wide subtraction sweep asking what no longer needs to exist, terminating on two consecutive rounds with no new findings, with Chesterton's Fence as the brake before any removal is called certain. Load when the request is to minimize or strip an existing codebase rather than to check it is correct.",
|
|
433
|
-
"",
|
|
434
|
-
],
|
|
342
|
+
["RULE-agent-behavior.md", "always_on", ""],
|
|
343
|
+
["RULE-coding.md", "model_decision", "", "Coding philosophy, source-of-truth discipline, error handling and security. Load when writing, reviewing or refactoring code."],
|
|
344
|
+
["RULE-pattern-core.md", "model_decision", "", 'Universal design laws: single source of truth, Rule of Three, single-responsibility "and"-test, composition over inheritance, naming by role. Load on any structural or decomposition decision.'],
|
|
345
|
+
["RULE-docs.md", "model_decision", ""],
|
|
346
|
+
["RULE-content-write.md", "model_decision", ""],
|
|
347
|
+
["RULE-stack-akiNuxtCf.md", "glob", '["**/*.vue", "nuxt.config.*", "wrangler.toml", "app/**", "server/**", "composables/**", "middleware/**", "plugins/**", "layouts/**"]'],
|
|
348
|
+
["RULE-stack-tauri.md", "glob", '["src-tauri/**", "**/*.rs", "tauri.conf.json"]'],
|
|
349
|
+
["RULE-ui-pattern.md", "model_decision", ""],
|
|
350
|
+
["RULE-seo.md", "model_decision", ""],
|
|
351
|
+
["RULE-release.md", "model_decision", ""],
|
|
352
|
+
["RULE-db-design.md", "model_decision", ""],
|
|
353
|
+
["RULE-biz.md", "model_decision", ""],
|
|
354
|
+
["METHOD-audit-flow.md", "model_decision", ""],
|
|
355
|
+
["METHOD-audit-zero-trust.md", "model_decision", ""],
|
|
356
|
+
["METHOD-deep-think.md", "model_decision", ""],
|
|
357
|
+
["METHOD-ux-psych.md", "model_decision", ""],
|
|
358
|
+
["METHOD-proportionality.md", "model_decision", ""],
|
|
359
|
+
["METHOD-audit-subtraction.md", "model_decision", ""],
|
|
360
|
+
["METHOD-audit-frozen-reference.md", "model_decision", ""],
|
|
435
361
|
];
|
|
436
362
|
|
|
437
363
|
function agDestName(ruleFile) {
|
|
@@ -440,34 +366,35 @@ function agDestName(ruleFile) {
|
|
|
440
366
|
return `akirule-${stem.toLowerCase()}.md`;
|
|
441
367
|
}
|
|
442
368
|
|
|
443
|
-
function
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
}
|
|
369
|
+
function agRuleDescription(ruleFile, core, clauses) {
|
|
370
|
+
if (core) return core;
|
|
371
|
+
if (!clauses.has(ruleFile)) throw new Error(`akirule has no route for ${ruleFile}`);
|
|
372
|
+
// agy denies view_file on ~/.gemini/config/rules/ (hardcoded protection boundary, measured 2026-09-26), so the description names the readable copy.
|
|
373
|
+
return `Load when the task ${clauses.get(ruleFile)}: view_file ~/.aki/akidevrule/${ruleFile} (this rules directory itself is not readable).`;
|
|
374
|
+
}
|
|
448
375
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
}
|
|
456
|
-
const dest = join(GEMINI_RULES_DIR, agDestName(ruleFile));
|
|
376
|
+
// Every rule file is rendered before any installed one is removed, so a bad map or router aborts with AG untouched.
|
|
377
|
+
function installAgRules() {
|
|
378
|
+
const clauses = routerClauses();
|
|
379
|
+
const mapped = new Set(AG_RULE_MAP.map(([f]) => f));
|
|
380
|
+
const unmapped = listDir(join(REPO_ROOT, "payload")).filter((n) => /^(RULE|METHOD)-.*\.md$/.test(n) && !mapped.has(n));
|
|
381
|
+
if (unmapped.length) throw new Error(`AG_RULE_MAP has no entry for: ${unmapped.join(", ")}`);
|
|
457
382
|
|
|
383
|
+
const rendered = AG_RULE_MAP.map(([ruleFile, trigger, globs, core]) => {
|
|
458
384
|
const lines = ["---", `trigger: ${trigger}`];
|
|
459
385
|
if (globs) lines.push(`globs: ${globs}`);
|
|
460
|
-
if (trigger !== "always_on"
|
|
461
|
-
lines.push("---");
|
|
462
|
-
lines.push("");
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
386
|
+
if (trigger !== "always_on") lines.push(`description: ${JSON.stringify(agRuleDescription(ruleFile, core, clauses))}`);
|
|
387
|
+
lines.push("---", "", `<!-- Generated by akidevrule from payload/${ruleFile}. Do not edit here. -->`, "");
|
|
388
|
+
lines.push(readFileSync(join(REPO_ROOT, "payload", ruleFile), "utf-8"));
|
|
389
|
+
return [join(GEMINI_RULES_DIR, agDestName(ruleFile)), lines.join("\n")];
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
mkdirSync(GEMINI_RULES_DIR, { recursive: true });
|
|
393
|
+
for (const name of listDir(GEMINI_RULES_DIR)) {
|
|
394
|
+
if (name.startsWith("akirule-") && name.endsWith(".md")) rmrf(join(GEMINI_RULES_DIR, name));
|
|
469
395
|
}
|
|
470
|
-
|
|
396
|
+
for (const [dest, content] of rendered) writeTextLf(dest, content);
|
|
397
|
+
return rendered.length;
|
|
471
398
|
}
|
|
472
399
|
|
|
473
400
|
// ---------------------------------------------------------------------------
|
|
@@ -505,21 +432,9 @@ function mergeSettings(settingsPath, installRoot, claudeDir) {
|
|
|
505
432
|
perms.allow = perms.allow.filter((x) => x !== readRule && x !== legacyReadRule);
|
|
506
433
|
perms.allow.push(readRule);
|
|
507
434
|
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
];
|
|
512
|
-
const managedBashRules = [
|
|
513
|
-
"Bash(python3 ~/.claude/skills/*)",
|
|
514
|
-
"Bash(python3 ~/.aki/akidevrule/agskills/*)",
|
|
515
|
-
];
|
|
516
|
-
const variantBashRule = `Bash(python3 ${toTildePath(join(claudeDir, "skills"))}/*)`;
|
|
517
|
-
if (!managedBashRules.includes(variantBashRule)) {
|
|
518
|
-
managedBashRules.push(variantBashRule);
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
perms.allow = perms.allow.filter((x) => !legacyBashRules.includes(x));
|
|
522
|
-
for (const rule of managedBashRules) if (!perms.allow.includes(rule)) perms.allow.push(rule);
|
|
435
|
+
const skillRoots = [join(PRIMARY_CLAUDE_DIR, "skills"), join(claudeDir, "skills")];
|
|
436
|
+
const invocations = scriptInvocations({ roots: skillRoots, scripts: listSkillScripts(SKILLS_SRC), home: HOME, isWin: IS_WIN });
|
|
437
|
+
perms.allow = replaceOwned(perms.allow, ownershipTest(akiSkillNames()), claudeAllowRules(invocations));
|
|
523
438
|
|
|
524
439
|
perms.additionalDirectories = perms.additionalDirectories.filter((d) => d !== legacyRoot);
|
|
525
440
|
if (!perms.additionalDirectories.includes(installRoot)) perms.additionalDirectories.push(installRoot);
|
|
@@ -558,148 +473,53 @@ function mergeSettings(settingsPath, installRoot, claudeDir) {
|
|
|
558
473
|
}
|
|
559
474
|
|
|
560
475
|
// ---------------------------------------------------------------------------
|
|
561
|
-
//
|
|
476
|
+
// Script pre-allow for the non-Claude harnesses (lib/permissions.mjs)
|
|
562
477
|
// ---------------------------------------------------------------------------
|
|
563
478
|
|
|
564
|
-
function
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
const scriptsDir = join(REPO_ROOT, "skills", "akiflow", "scripts");
|
|
568
|
-
const skillScripts = listDir(scriptsDir)
|
|
569
|
-
.filter((n) => n.endsWith(".py"))
|
|
570
|
-
.map((n) => `akiflow/scripts/${n}`)
|
|
571
|
-
.sort();
|
|
572
|
-
const claudeRoots = claudeDirs.map((d) => join(d, "skills"));
|
|
573
|
-
const skillRoots = [GEMINI_SKILLS_DIR, ...claudeRoots];
|
|
574
|
-
const launchers = IS_WIN ? ["py -3", "python", "python3"] : ["python3"];
|
|
575
|
-
|
|
576
|
-
const managedCommands = [];
|
|
577
|
-
for (const launcher of launchers) {
|
|
578
|
-
for (const root of skillRoots) {
|
|
579
|
-
for (const script of skillScripts) {
|
|
580
|
-
const target = join(root, script);
|
|
581
|
-
managedCommands.push(`command(${launcher} ${target})`);
|
|
582
|
-
const rel = relative(HOME, target);
|
|
583
|
-
if (rel && !rel.startsWith("..") && !isAbsolute(rel)) {
|
|
584
|
-
managedCommands.push(`command(${launcher} ~/${rel.split(sep).join("/")})`);
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
managedCommands.push(
|
|
590
|
-
`write_file(${join(HOME, ".aki", "agent-council")}/)`,
|
|
591
|
-
`read_file(${join(HOME, ".aki", "akidevrule")}/)`,
|
|
592
|
-
"write_file(~/.aki/agent-council/)",
|
|
593
|
-
"read_file(~/.aki/akidevrule/)"
|
|
594
|
-
);
|
|
595
|
-
|
|
596
|
-
const legacyCommands = [
|
|
597
|
-
"command(python3 ~/.gemini/config/skills/*)",
|
|
598
|
-
"command(python3 ~/.claude/skills/*)",
|
|
599
|
-
"command(python3 ~/.aki/akidevrule/agskills/*)",
|
|
600
|
-
];
|
|
601
|
-
|
|
602
|
-
const targetFiles = [
|
|
603
|
-
join(GEMINI_DIR, "antigravity-cli", "settings.json"),
|
|
604
|
-
join(GEMINI_DIR, "settings.json"),
|
|
605
|
-
];
|
|
606
|
-
|
|
607
|
-
for (const target of targetFiles) {
|
|
608
|
-
let data = {};
|
|
609
|
-
if (isFile(target)) data = JSON.parse(readFileSync(target, "utf-8"));
|
|
610
|
-
|
|
611
|
-
if (typeof data.permissions !== "object" || data.permissions === null || Array.isArray(data.permissions))
|
|
612
|
-
data.permissions = {};
|
|
613
|
-
const perms = data.permissions;
|
|
614
|
-
if (!Array.isArray(perms.allow)) perms.allow = [];
|
|
615
|
-
|
|
616
|
-
let changed = false;
|
|
617
|
-
for (const cmd of legacyCommands) {
|
|
618
|
-
const i = perms.allow.indexOf(cmd);
|
|
619
|
-
if (i !== -1) {
|
|
620
|
-
perms.allow.splice(i, 1);
|
|
621
|
-
changed = true;
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
for (const cmd of managedCommands) {
|
|
625
|
-
if (!perms.allow.includes(cmd)) {
|
|
626
|
-
perms.allow.push(cmd);
|
|
627
|
-
changed = true;
|
|
628
|
-
}
|
|
629
|
-
}
|
|
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
|
-
|
|
648
|
-
if (changed || !isFile(target)) {
|
|
649
|
-
mkdirSync(dirname(target), { recursive: true });
|
|
650
|
-
if (isFile(target)) {
|
|
651
|
-
backup(target);
|
|
652
|
-
pruneBackups(target);
|
|
653
|
-
}
|
|
654
|
-
writeTextLf(target, JSON.stringify(data, null, 2) + "\n");
|
|
655
|
-
}
|
|
656
|
-
}
|
|
479
|
+
function akiSkillNames() {
|
|
480
|
+
return listDir(SKILLS_SRC).filter((n) => isDir(join(SKILLS_SRC, n)));
|
|
657
481
|
}
|
|
658
482
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
const
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
"python3 ~/.kiro/skills/*",
|
|
673
|
-
"python3 ~/.gemini/config/skills/*",
|
|
674
|
-
"python3 ~/.aki/akidevrule/agskills/*",
|
|
675
|
-
];
|
|
676
|
-
for (const cDir of claudeDirs) {
|
|
677
|
-
const m = `python3 ${toTildePath(join(cDir, "skills"))}/*`;
|
|
678
|
-
if (!managedMatches.includes(m)) managedMatches.push(m);
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
if (!isFile(yamlPath)) {
|
|
682
|
-
const lines = [
|
|
683
|
-
"# Generated by akidevrule installer",
|
|
684
|
-
"rules:",
|
|
685
|
-
" - capability: shell",
|
|
686
|
-
" match:",
|
|
687
|
-
];
|
|
688
|
-
for (const m of managedMatches) lines.push(` - "${m}"`);
|
|
689
|
-
lines.push(" effect: allow\n");
|
|
690
|
-
writeTextLf(yamlPath, lines.join("\n"));
|
|
691
|
-
} else {
|
|
692
|
-
const content = readFileSync(yamlPath, "utf-8");
|
|
693
|
-
const needed = managedMatches.filter((m) => !content.includes(m));
|
|
694
|
-
if (needed.length) {
|
|
695
|
-
backup(yamlPath);
|
|
696
|
-
pruneBackups(yamlPath);
|
|
697
|
-
const appendLines = ["", " - capability: shell", " match:"];
|
|
698
|
-
for (const m of needed) appendLines.push(` - "${m}"`);
|
|
699
|
-
appendLines.push(" effect: allow\n");
|
|
700
|
-
writeTextLf(yamlPath, content.replace(/\s+$/, "") + "\n" + appendLines.join("\n"));
|
|
483
|
+
function preAllowHarnessScripts(claudeDirs) {
|
|
484
|
+
const skillNames = akiSkillNames();
|
|
485
|
+
const isOwned = ownershipTest(skillNames);
|
|
486
|
+
// Antigravity's allowlist is shared machine-wide, not per profile — a --claude-dir root omitted this run must not read as stale (lib/permissions.mjs recoverSkillRoots).
|
|
487
|
+
const knownRoots = new Set([PRIMARY_CLAUDE_DIR, ...claudeDirs.filter((d) => d !== PRIMARY_CLAUDE_DIR)].map((d) => join(d, "skills")));
|
|
488
|
+
for (const settingsFile of [join(GEMINI_DIR, "antigravity-cli", "settings.json"), join(GEMINI_DIR, "settings.json")]) {
|
|
489
|
+
if (!isFile(settingsFile)) continue;
|
|
490
|
+
try {
|
|
491
|
+
const data = JSON.parse(readFileSync(settingsFile, "utf-8"));
|
|
492
|
+
const allow = (data.permissions && data.permissions.allow) || [];
|
|
493
|
+
for (const root of recoverSkillRoots(allow, isOwned, HOME)) knownRoots.add(root);
|
|
494
|
+
} catch {
|
|
495
|
+
/* malformed file: preflightInstallSettings already validated this before install runs */
|
|
701
496
|
}
|
|
702
497
|
}
|
|
498
|
+
const ctx = {
|
|
499
|
+
home: HOME,
|
|
500
|
+
isWin: IS_WIN,
|
|
501
|
+
scripts: listSkillScripts(SKILLS_SRC),
|
|
502
|
+
akiSkillNames: skillNames,
|
|
503
|
+
claudeSkillRoots: [...knownRoots],
|
|
504
|
+
dirs: {
|
|
505
|
+
gemini: GEMINI_DIR,
|
|
506
|
+
geminiSkills: GEMINI_SKILLS_DIR,
|
|
507
|
+
kiro: join(HOME, ".kiro"),
|
|
508
|
+
kiroSkills: KIRO_SKILLS_DIR,
|
|
509
|
+
codex: join(HOME, ".codex"),
|
|
510
|
+
agentsSkills: CODEX_SKILLS_DIR,
|
|
511
|
+
cursor: join(HOME, ".cursor"),
|
|
512
|
+
opencode: join(HOME, ".config", "opencode"),
|
|
513
|
+
},
|
|
514
|
+
};
|
|
515
|
+
const io = {
|
|
516
|
+
backup(path) {
|
|
517
|
+
backup(path);
|
|
518
|
+
pruneBackups(path);
|
|
519
|
+
},
|
|
520
|
+
writeText: writeTextLf,
|
|
521
|
+
};
|
|
522
|
+
return applyHarnessPreAllow(ctx, io);
|
|
703
523
|
}
|
|
704
524
|
|
|
705
525
|
// ---------------------------------------------------------------------------
|
|
@@ -735,7 +555,10 @@ function installClaudeDir(claudeDir) {
|
|
|
735
555
|
backup(claudeMd);
|
|
736
556
|
pruneBackups(claudeMd);
|
|
737
557
|
|
|
738
|
-
const
|
|
558
|
+
const routerImport = "@~/.claude/skills/akirule/SKILL.md";
|
|
559
|
+
const template = readFileSync(join(REPO_ROOT, "claude", "CLAUDE.md"), "utf-8");
|
|
560
|
+
if (!template.includes(routerImport)) throw new Error(`claude/CLAUDE.md no longer imports ${routerImport}`);
|
|
561
|
+
const claudeMdSrc = template.replace(routerImport, `@${toTildePath(join(claudeDir, "skills", "akirule", "SKILL.md"))}`);
|
|
739
562
|
const propagateCmd = `node "${join(REPO_ROOT, "install.mjs")}"`;
|
|
740
563
|
const localMd = join(claudeDir, "CLAUDE.local.md");
|
|
741
564
|
const localMdTilde = toTildePath(localMd);
|
|
@@ -888,8 +711,8 @@ async function inspectStatus(claudeDirs) {
|
|
|
888
711
|
const data = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
889
712
|
const readRule = `Read(//${INSTALL_ROOT.replace(/^\/+/, "")}/**)`;
|
|
890
713
|
const allow = (data.permissions && data.permissions.allow) || [];
|
|
891
|
-
const
|
|
892
|
-
const bashOk = allow.
|
|
714
|
+
const isOwned = ownershipTest(akiSkillNames());
|
|
715
|
+
const bashOk = allow.some((e) => e.startsWith("Bash(") && isOwned(e.slice(5)));
|
|
893
716
|
const readOk = allow.includes(readRule);
|
|
894
717
|
const overrides = data.skillOverrides || {};
|
|
895
718
|
const akiOk = overrides.akirule === "on";
|
|
@@ -927,7 +750,7 @@ async function inspectStatus(claudeDirs) {
|
|
|
927
750
|
// print_summary (post-install)
|
|
928
751
|
// ---------------------------------------------------------------------------
|
|
929
752
|
|
|
930
|
-
function printSummary(claudeDirs) {
|
|
753
|
+
function printSummary(claudeDirs, preAllow) {
|
|
931
754
|
console.log(`\n${greenBold("=== INSTALL SUCCEEDED ===")}`);
|
|
932
755
|
|
|
933
756
|
const gitHash = gitShortHash(REPO_ROOT);
|
|
@@ -982,12 +805,13 @@ function printSummary(claudeDirs) {
|
|
|
982
805
|
console.log(` 🤖 Grok CLI : ${GROK_SKILLS_DIR}`);
|
|
983
806
|
|
|
984
807
|
console.log();
|
|
985
|
-
console.log(cyanBold("
|
|
986
|
-
console.log(" ⚙️
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
808
|
+
console.log(cyanBold("Skill scripts pre-allowed (no prompt when a skill runs its own scripts):"));
|
|
809
|
+
console.log(" ⚙️ claude settings.json in every target above — plus Read(~/.aki/akidevrule/**)");
|
|
810
|
+
for (const row of preAllow) {
|
|
811
|
+
const state = row.error ? `⚠️ skipped: ${row.error}` : `${row.rules} invocation(s)${row.changed ? "" : ", unchanged"}`;
|
|
812
|
+
console.log(` ⚙️ ${row.id.padEnd(12)} ${toTildePath(row.file)} — ${state}`);
|
|
813
|
+
}
|
|
814
|
+
console.log(" ℹ️ no file-based allowlist exists for Grok CLI or Ollama; approve their script runs interactively.");
|
|
991
815
|
|
|
992
816
|
console.log();
|
|
993
817
|
console.log(cyanBold("Hooks deployed:"));
|
|
@@ -1148,11 +972,10 @@ async function runInstall(claudeDirs) {
|
|
|
1148
972
|
console.log(" ℹ️ Antigravity discovers rules and skills at startup — restart the app or start a new agy session.");
|
|
1149
973
|
}
|
|
1150
974
|
|
|
1151
|
-
// --- 5.
|
|
1152
|
-
|
|
1153
|
-
mergeKiroPermissions(claudeDirs);
|
|
975
|
+
// --- 5. Script pre-allow for every other harness present ---
|
|
976
|
+
const preAllow = preAllowHarnessScripts(claudeDirs);
|
|
1154
977
|
|
|
1155
|
-
printSummary(claudeDirs);
|
|
978
|
+
printSummary(claudeDirs, preAllow);
|
|
1156
979
|
}
|
|
1157
980
|
|
|
1158
981
|
// ---------------------------------------------------------------------------
|