total-recall-brain 3.41.3 → 3.41.4
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/bin/total-recall.mjs +10 -1
- package/frontend/dist/assets/{index-DFenQQEQ.js → index-CRzjQLu7.js} +77 -72
- package/frontend/dist/index.html +1 -1
- package/package.json +7 -5
- package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +48 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/login-projections.mjs +42 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/mesh-secrets.mjs +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/routes/secrets.mjs +5 -2
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-carriers.mjs +76 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-prune.mjs +37 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-sync.mjs +53 -17
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +12 -12
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +12 -12
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +23 -8
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +6 -2
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +23 -8
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +5 -1
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +23 -10
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +5 -3
- package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/grants.mjs +7 -3
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/mesh.mjs +61 -38
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/leader-election.mjs +57 -2
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh.mjs +220 -36
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/server/routes/mesh.mjs +7 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +65 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/BRICK.md +59 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +534 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +139 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +2849 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +26 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +376 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +37 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adapters/dsh.mjs +446 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +398 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +152 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +82 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +90 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +415 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/runtime.mjs +36 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/setup.mjs +35 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/tracker.mjs +133 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +233 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +32 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +845 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/capability/BRICK.md +64 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/capability/index.mjs +250 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +344 -14
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +2917 -140
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +9 -3
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +223 -5
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/config.mjs +129 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +355 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/upstream-watcher.mjs +375 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +35 -22
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +947 -323
- package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +23 -8
- package/scaffold/.agent/skills/total-recall/plugins/security/package.json +5 -1
- package/src/cli/config.mjs +3 -0
- package/src/cli/connect.mjs +12 -116
- package/src/cli/harness.mjs +0 -1
- package/src/cli/plugin/create.mjs +56 -3
- package/src/cli/plugin/index.mjs +15 -3
- package/src/cli/plugin/monetization.mjs +232 -12
- package/src/cli/preflight.mjs +161 -0
- package/src/core/cli-commands.mjs +2 -0
- package/src/core/context-compiler.mjs +8 -1
- package/src/core/default-plugins.mjs +1 -1
- package/src/core/meta-harness.mjs +16 -38
- package/src/core/plugin-dependencies.mjs +6 -1
- package/src/core/preflight-gate.mjs +77 -0
- package/src/core/preflight-receipt.mjs +123 -0
- package/src/core/runtime.mjs +0 -3
- package/src/core/surface.mjs +38 -1
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Harness-neutral gate decision. Every harness adapter normalises its native
|
|
3
|
+
* hook payload into `{ tool, command, path, cwd }` and asks `decide`; the answer
|
|
4
|
+
* is the same in every harness.
|
|
5
|
+
*
|
|
6
|
+
* Rules, in order:
|
|
7
|
+
* 1. Tools that only display or discover (`meta`, `ui`) are always open.
|
|
8
|
+
* 2. Reads of skill files are open: reading them is how the preflight is earned.
|
|
9
|
+
* 3. A single plain Total Recall preflight, context, recall or help command is
|
|
10
|
+
* open, so the gate can always be satisfied or repaired. Chaining, piping,
|
|
11
|
+
* substitution and redirects are not plain, so the exemption cannot carry
|
|
12
|
+
* other work and a capsule cannot be truncated through a pipe.
|
|
13
|
+
* 4. Everything else needs a fresh receipt for the directory.
|
|
14
|
+
* 5. With a receipt, a repository-wide source sweep still needs a recent
|
|
15
|
+
* Total Recall consult (the rule DSH enforced alone until now).
|
|
16
|
+
* Any error inside the gate denies: most harnesses fail open when a hook crashes.
|
|
17
|
+
*/
|
|
18
|
+
import { ACK_COMMAND } from './preflight-receipt.mjs';
|
|
19
|
+
|
|
20
|
+
export const TOOL_CLASSES = ['shell', 'read', 'write', 'search', 'web', 'mcp', 'agent', 'meta', 'ui', 'other'];
|
|
21
|
+
export const SWEEP_CONSULT_MS = 15 * 60 * 1000;
|
|
22
|
+
|
|
23
|
+
const SKILL_PATH = /\/\.(agent|claude)\/skills\//;
|
|
24
|
+
const SATISFYING = /^(cd\s+\S+\s*&&\s*)?(npx\s+)?(total-recall|node\s+\S*bin\/total-recall\.mjs)\s+(tr-preflight|preflight|context|recall|help|--help)(\s+[^;&|`<>$\n]*)?$/;
|
|
25
|
+
|
|
26
|
+
/** True when the whole command is one plain Total Recall invocation that satisfies or repairs the gate. */
|
|
27
|
+
export function isSatisfyingCommand(command) {
|
|
28
|
+
return SATISFYING.test(String(command || '').trim());
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A repository-wide search used to answer a question, as the DSH `tr-first.sh` rule defined it. */
|
|
32
|
+
export function isRepoSweep(command) {
|
|
33
|
+
const c = String(command || '');
|
|
34
|
+
if (c.includes('total-recall')) return false;
|
|
35
|
+
const sweep = /(^|[\s;&|(])(rg|ripgrep|ag)\s/.test(c)
|
|
36
|
+
|| /(^|[\s;&|(])grep\s+(-[A-Za-z]*[rR][A-Za-z]*|--recursive)(\s|$)/.test(c)
|
|
37
|
+
|| /(^|[\s;&|(])find\s+(\.|\/Users)/.test(c)
|
|
38
|
+
|| /(^|[\s;&|(])git\s+grep(\s|$)/.test(c);
|
|
39
|
+
if (!sweep) return false;
|
|
40
|
+
if (/\|\s*(grep|rg|ag)(\s|$)/.test(c)) return false; // filtering command output is not a source question
|
|
41
|
+
if (/node_modules|\.agent\/skills|\/tmp\/|\.git\//.test(c)) return false;
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const allow = (rule) => ({ decision: 'allow', rule });
|
|
46
|
+
const deny = (rule, reason, fix = ACK_COMMAND) => ({ decision: 'deny', rule, reason, fix });
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @param {{ tool: string, command?: string, path?: string, cwd?: string }} call
|
|
50
|
+
* @param {{ check: () => { ok: boolean, problems: string[] }, recentConsult?: () => boolean, readablePaths?: RegExp[] }} deps
|
|
51
|
+
*/
|
|
52
|
+
export function decide(call, deps) {
|
|
53
|
+
try {
|
|
54
|
+
const tool = TOOL_CLASSES.includes(call?.tool) ? call.tool : 'other';
|
|
55
|
+
if (tool === 'meta' || tool === 'ui') return allow(`class:${tool}`);
|
|
56
|
+
if (tool === 'read') {
|
|
57
|
+
const target = String(call.path || '');
|
|
58
|
+
if (SKILL_PATH.test(target)) return allow('read:skill-file');
|
|
59
|
+
// Persisted output of an allowed command is readable; each adapter names its host's paths (audit F-019).
|
|
60
|
+
if ((deps.readablePaths || []).some((re) => re.test(target))) return allow('read:persisted-output');
|
|
61
|
+
}
|
|
62
|
+
if (tool === 'shell' && isSatisfyingCommand(call.command)) return allow('shell:satisfying-command');
|
|
63
|
+
|
|
64
|
+
const receipt = deps.check();
|
|
65
|
+
if (!receipt.ok) {
|
|
66
|
+
return deny('receipt', `TR-PREFLIGHT gate: ${receipt.problems[0] || 'check failed'} (directory: ${call.cwd || 'unknown'}). `
|
|
67
|
+
+ 'No work may start until it passes. Read the complete context capsule (never pipe or truncate it), then acknowledge.');
|
|
68
|
+
}
|
|
69
|
+
if (tool === 'shell' && isRepoSweep(call.command) && !(deps.recentConsult ? deps.recentConsult() : false)) {
|
|
70
|
+
return deny('sweep', 'TR-PREFLIGHT gate: do not sweep the repository for an answer; Total Recall owns it. Run `total-recall help "<task>"` to find the owning command, or `total-recall recall "<topic>" --local`, then repeat this call.',
|
|
71
|
+
'total-recall help "<task>"');
|
|
72
|
+
}
|
|
73
|
+
return allow('receipt:fresh');
|
|
74
|
+
} catch (err) {
|
|
75
|
+
return deny('gate-error', `TR-PREFLIGHT gate error: ${err.message}. The gate fails closed.`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preflight receipt: the attestation that an agent read the mandatory skills and
|
|
3
|
+
* received a ready capsule for one repository. It is an ephemeral per-machine
|
|
4
|
+
* file (mode 0600), not application state: every hook reads it on every tool
|
|
5
|
+
* call, so it stays a plain file. Gate decisions are what go to the event log.
|
|
6
|
+
*
|
|
7
|
+
* The file location and fields are unchanged from the original `tr-preflight`
|
|
8
|
+
* command, so receipts written before this module remain valid.
|
|
9
|
+
*/
|
|
10
|
+
import crypto from 'node:crypto';
|
|
11
|
+
import fs from 'node:fs';
|
|
12
|
+
import os from 'node:os';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import { spawnSync } from 'node:child_process';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
|
|
17
|
+
export const SKILLS = ['.agent/skills/total-recall/SKILL.md', '.agent/skills/ssss/SKILL.md'];
|
|
18
|
+
export const DEFAULT_MAX_AGE_MS = 30 * 60 * 1000;
|
|
19
|
+
export const ACK_COMMAND = 'total-recall tr-preflight --ack --task "<task>" --action read,edit,test';
|
|
20
|
+
|
|
21
|
+
const BIN = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '..', 'bin', 'total-recall.mjs');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The nearest ancestor holding `.agent`, else the directory itself, with symlinks
|
|
25
|
+
* resolved. A harness may report a symlinked path (`/var` for `/private/var`)
|
|
26
|
+
* where the shell reports the real one; both must find the same receipt.
|
|
27
|
+
*/
|
|
28
|
+
export function findRoot(cwd = process.cwd()) {
|
|
29
|
+
const resolve = (p) => { try { return fs.realpathSync(p); } catch { return path.resolve(p); } };
|
|
30
|
+
const start = resolve(cwd);
|
|
31
|
+
let dir = start;
|
|
32
|
+
for (let i = 0; i < 40; i += 1) {
|
|
33
|
+
if (fs.existsSync(path.join(dir, '.agent'))) return dir;
|
|
34
|
+
const parent = path.dirname(dir);
|
|
35
|
+
if (parent === dir) break;
|
|
36
|
+
dir = parent;
|
|
37
|
+
}
|
|
38
|
+
return start;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const rootKey = (root) => crypto.createHash('sha256').update(root).digest('hex').slice(0, 16);
|
|
42
|
+
const stateDir = (home) => path.join(home, '.agent', 'tr-preflight');
|
|
43
|
+
export const receiptPath = (root, home = os.homedir()) => path.join(stateDir(home), `${rootKey(root)}.json`);
|
|
44
|
+
export const consultPath = (root, home = os.homedir()) => path.join(stateDir(home), `consult-${rootKey(root)}.stamp`);
|
|
45
|
+
|
|
46
|
+
const sha256 = (file) => crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
|
|
47
|
+
|
|
48
|
+
/** Hashes of the mandatory skills, or the list of ones that are missing. */
|
|
49
|
+
export function skillHashes(root) {
|
|
50
|
+
const hashes = {};
|
|
51
|
+
const missing = [];
|
|
52
|
+
for (const rel of SKILLS) {
|
|
53
|
+
const full = path.join(root, rel);
|
|
54
|
+
if (fs.existsSync(full)) hashes[rel] = sha256(full);
|
|
55
|
+
else missing.push(rel);
|
|
56
|
+
}
|
|
57
|
+
return { hashes, missing };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function readReceipt(root, home = os.homedir()) {
|
|
61
|
+
try { return JSON.parse(fs.readFileSync(receiptPath(root, home), 'utf8')); } catch { return null; }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @returns {{ ok: boolean, problems: string[], receipt: object|null, ageMs: number|null }}
|
|
66
|
+
* `problems` read the same as the original command so existing guidance still fits.
|
|
67
|
+
*/
|
|
68
|
+
export function checkReceipt(root, { now = Date.now(), maxAgeMs, home = os.homedir() } = {}) {
|
|
69
|
+
const { hashes, missing } = skillHashes(root);
|
|
70
|
+
if (missing.length) return { ok: false, problems: missing.map((rel) => `missing mandatory skill: ${rel}`), receipt: null, ageMs: null };
|
|
71
|
+
const receipt = readReceipt(root, home);
|
|
72
|
+
if (!receipt) return { ok: false, problems: ['no preflight receipt for this repository'], receipt: null, ageMs: null };
|
|
73
|
+
const problems = [];
|
|
74
|
+
const ageMs = now - Date.parse(receipt.at || 0);
|
|
75
|
+
// The window is decided when the receipt is written, so the hot path never loads configuration.
|
|
76
|
+
const limit = maxAgeMs ?? (Number.isFinite(receipt.max_age_ms) && receipt.max_age_ms > 0 ? receipt.max_age_ms : DEFAULT_MAX_AGE_MS);
|
|
77
|
+
if (!Number.isFinite(ageMs) || ageMs > limit) problems.push('preflight receipt is stale; re-run --ack');
|
|
78
|
+
for (const [rel, hash] of Object.entries(hashes)) {
|
|
79
|
+
if (!receipt.skills || receipt.skills[rel] !== hash) problems.push(`${rel} changed since the receipt; re-read and re-run --ack`);
|
|
80
|
+
}
|
|
81
|
+
if (receipt.ready !== true) problems.push('receipt does not record ready:true');
|
|
82
|
+
return { ok: problems.length === 0, problems, receipt, ageMs: Number.isFinite(ageMs) ? ageMs : null };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Run this package's own CLI, so `ack` uses exactly the semantics every caller gets. */
|
|
86
|
+
function runSelf(args, cwd) {
|
|
87
|
+
const r = spawnSync(process.execPath, [BIN, ...args], { cwd, encoding: 'utf8', timeout: 60000 });
|
|
88
|
+
return { ok: r.status === 0, out: r.stdout || '', err: r.stderr || '', code: r.status };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Record a receipt after proving the capsule is ready and the rules verify.
|
|
93
|
+
* `run` is injectable so the specs need no child process.
|
|
94
|
+
*/
|
|
95
|
+
export function acknowledge({ root, task = '', actions = 'read,edit,test', maxAgeMs, run = runSelf, now = Date.now(), home = os.homedir() }) {
|
|
96
|
+
const { hashes, missing } = skillHashes(root);
|
|
97
|
+
if (missing.length) return { ok: false, problems: missing.map((rel) => `missing mandatory skill: ${rel}`) };
|
|
98
|
+
const context = run(['context', task || '(no task given)', '--action', actions], root);
|
|
99
|
+
if (!/ready:true/.test(context.out)) {
|
|
100
|
+
return { ok: false, problems: ['total-recall context did not return ready:true',
|
|
101
|
+
context.code === 2 ? 'exit 2: capsule overflow; raise --budget or curate applicability, then retry' : `context exit=${context.code ?? '?'}`] };
|
|
102
|
+
}
|
|
103
|
+
const version = (context.out.match(/version:([0-9a-f]+)/) || [])[1] || null;
|
|
104
|
+
const rules = run(['rules', 'verify'], root);
|
|
105
|
+
if (!rules.ok) return { ok: false, problems: ['total-recall rules verify failed', (rules.out || rules.err).trim().split('\n').slice(-3).join(' | ')] };
|
|
106
|
+
const receipt = { at: new Date(now).toISOString(), repo: root, task, actions, ready: true, capsule_version: version, skills: hashes, rules_verify: 'ok',
|
|
107
|
+
...(Number.isFinite(maxAgeMs) && maxAgeMs > 0 ? { max_age_ms: maxAgeMs } : {}) };
|
|
108
|
+
const file = receiptPath(root, home);
|
|
109
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
110
|
+
fs.writeFileSync(file, `${JSON.stringify(receipt, null, 2)}\n`, { mode: 0o600 });
|
|
111
|
+
return { ok: true, problems: [], receipt, file };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Marks that Total Recall was consulted just now; read by the repository-sweep policy. */
|
|
115
|
+
export function touchConsult(root, { now = Date.now(), home = os.homedir() } = {}) {
|
|
116
|
+
const file = consultPath(root, home);
|
|
117
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
118
|
+
fs.writeFileSync(file, String(now), { mode: 0o600 });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function lastConsultMs(root, { home = os.homedir() } = {}) {
|
|
122
|
+
try { return Number(fs.readFileSync(consultPath(root, home), 'utf8')) || 0; } catch { return 0; }
|
|
123
|
+
}
|
package/src/core/runtime.mjs
CHANGED
|
@@ -162,10 +162,8 @@ export function loadRuntimeConfig(_configPath) {
|
|
|
162
162
|
const checkPaths = [
|
|
163
163
|
instructionsFilePath(),
|
|
164
164
|
path.join(surfaceDir(), 'AGENTS.md'),
|
|
165
|
-
path.join(surfaceDir(), 'GEMINI.md'),
|
|
166
165
|
path.join(agentDir, 'INSTRUCTIONS.md'),
|
|
167
166
|
path.join(agentDir, 'AGENTS.md'),
|
|
168
|
-
path.join(agentDir, 'GEMINI.md'),
|
|
169
167
|
path.join(path.dirname(agentDir), 'INSTRUCTIONS.md'),
|
|
170
168
|
path.join(path.dirname(agentDir), 'AGENTS.md'),
|
|
171
169
|
path.join(process.cwd(), 'INSTRUCTIONS.md'),
|
|
@@ -173,7 +171,6 @@ export function loadRuntimeConfig(_configPath) {
|
|
|
173
171
|
path.join(process.cwd(), 'Claude.md'),
|
|
174
172
|
// legacy mistaken brainDir locations (kept as last resort)
|
|
175
173
|
path.join(brainDir, 'INSTRUCTIONS.md'),
|
|
176
|
-
path.join(brainDir, 'GEMINI.md'),
|
|
177
174
|
path.join(brainDir, 'AGENTS.md'),
|
|
178
175
|
];
|
|
179
176
|
for (const p of checkPaths) {
|
package/src/core/surface.mjs
CHANGED
|
@@ -500,6 +500,30 @@ async function writeShim(shimPath, skillsDir, nodes = [], { vaultDir, derivedDir
|
|
|
500
500
|
const fullContent = prependDirectives(`${mdcHeader}${baseline}`, rulesBlock);
|
|
501
501
|
|
|
502
502
|
try {
|
|
503
|
+
const normShim = path.normalize(shimPath);
|
|
504
|
+
// Antigravity rules deduplication: .agents/rules/AGENTS.md should be a relative
|
|
505
|
+
// symlink to ../../AGENTS.md so Antigravity resolves realpath and loads it once.
|
|
506
|
+
if (normShim.endsWith(path.normalize('.agents/rules/AGENTS.md'))) {
|
|
507
|
+
if (!fs.existsSync(shimDir)) {
|
|
508
|
+
fs.mkdirSync(shimDir, { recursive: true });
|
|
509
|
+
}
|
|
510
|
+
const relTarget = path.join('..', '..', 'AGENTS.md');
|
|
511
|
+
try {
|
|
512
|
+
const stat = fs.lstatSync(shimPath, { throwIfNoEntry: false });
|
|
513
|
+
if (stat) {
|
|
514
|
+
if (!stat.isSymbolicLink() || fs.readlinkSync(shimPath) !== relTarget) {
|
|
515
|
+
fs.unlinkSync(shimPath);
|
|
516
|
+
fs.symlinkSync(relTarget, shimPath);
|
|
517
|
+
}
|
|
518
|
+
} else {
|
|
519
|
+
fs.symlinkSync(relTarget, shimPath);
|
|
520
|
+
}
|
|
521
|
+
return true;
|
|
522
|
+
} catch {
|
|
523
|
+
// Fall through to regular write if symlink cannot be created
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
503
527
|
const stat = fs.lstatSync(shimPath, { throwIfNoEntry: false });
|
|
504
528
|
if (stat) {
|
|
505
529
|
if (stat.isSymbolicLink()) {
|
|
@@ -552,7 +576,6 @@ const CLIENT_SHIMS = {
|
|
|
552
576
|
roo: ['.roo/rules/total-recall.md', '.clinerules/total-recall.md'],
|
|
553
577
|
'roo-code': ['.roo/rules/total-recall.md', '.clinerules/total-recall.md'],
|
|
554
578
|
antigravity: ['AGENTS.md', '.agents/rules/AGENTS.md'],
|
|
555
|
-
gemini: ['GEMINI.md', '.agents/rules/GEMINI.md'],
|
|
556
579
|
codex: ['AGENTS.md'],
|
|
557
580
|
grok: ['AGENTS.md'],
|
|
558
581
|
replit: ['replit.md'],
|
|
@@ -647,6 +670,20 @@ async function compilePointers(instructionsFile, skillsDir, nodes = [], { vaultD
|
|
|
647
670
|
// plugin-store.mjs likewise), so the peel was never correct for either.
|
|
648
671
|
const baseDir = agentDir;
|
|
649
672
|
|
|
673
|
+
// Eradicate deprecated Gemini shims so they do not consume context budgets or inject obsolete rules
|
|
674
|
+
const deprecatedGeminiPaths = [
|
|
675
|
+
path.join(baseDir, 'GEMINI.md'),
|
|
676
|
+
path.join(baseDir, '.agent', 'GEMINI.md'),
|
|
677
|
+
path.join(baseDir, '.agents', 'rules', 'GEMINI.md')
|
|
678
|
+
];
|
|
679
|
+
for (const depPath of deprecatedGeminiPaths) {
|
|
680
|
+
try {
|
|
681
|
+
if (fs.existsSync(depPath)) {
|
|
682
|
+
fs.unlinkSync(depPath);
|
|
683
|
+
}
|
|
684
|
+
} catch {}
|
|
685
|
+
}
|
|
686
|
+
|
|
650
687
|
// Determine which client shims to write.
|
|
651
688
|
// `baseDir/.agent/skills/...` only holds for a PROJECT brain. For the global
|
|
652
689
|
// brain baseDir already IS the agent dir, so that path became
|