@jmylchreest/aide-plugin 0.0.65 → 0.1.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/package.json +3 -3
- package/skills/reflect/SKILL.md +289 -0
- package/skills/swarm/SKILL.md +26 -1
- package/skills/swarm-status/SKILL.md +107 -0
- package/src/core/context-guard.ts +2 -1
- package/src/core/mcp-sync.ts +5 -2
- package/src/core/read-tracking.ts +54 -31
- package/src/core/search-enrichment.ts +2 -1
- package/src/core/session-init.ts +97 -21
- package/src/core/skill-matcher.ts +18 -22
- package/src/core/tool-observe.ts +12 -0
- package/src/core/types.ts +25 -0
- package/src/hooks/agent-cleanup.ts +3 -1
- package/src/hooks/agent-signals.ts +249 -0
- package/src/hooks/comment-checker.ts +26 -0
- package/src/hooks/context-guard.ts +34 -5
- package/src/hooks/context-pruning.ts +27 -0
- package/src/hooks/pre-tool-enforcer.ts +23 -3
- package/src/hooks/reflect.ts +69 -0
- package/src/hooks/search-enrichment.ts +12 -4
- package/src/hooks/session-end.ts +35 -4
- package/src/hooks/session-start.ts +77 -43
- package/src/hooks/skill-injector.ts +42 -13
- package/src/hooks/subagent-tracker.ts +33 -3
- package/src/hooks/write-guard.ts +27 -4
- package/src/lib/aide-downloader.ts +20 -2
- package/src/lib/hook-utils.ts +63 -0
- package/src/lib/hud.ts +5 -2
- package/src/lib/logger.ts +18 -6
- package/src/lib/project-root.ts +174 -0
- package/src/opencode/hooks.ts +46 -5
- package/src/opencode/index.ts +2 -80
|
@@ -15,6 +15,7 @@ import { readStdin } from "../lib/hook-utils.js";
|
|
|
15
15
|
import { debug } from "../lib/logger.js";
|
|
16
16
|
import { checkContextGuard, checkSmartReadHint } from "../core/context-guard.js";
|
|
17
17
|
import { findAideBinary } from "../core/aide-client.js";
|
|
18
|
+
import { emitInjectionEvent } from "../core/read-tracking.js";
|
|
18
19
|
|
|
19
20
|
const SOURCE = "context-guard";
|
|
20
21
|
|
|
@@ -54,9 +55,28 @@ async function main(): Promise<void> {
|
|
|
54
55
|
const sessionId = data.session_id || "unknown";
|
|
55
56
|
|
|
56
57
|
const result = checkContextGuard(toolName, toolInput, cwd, sessionId);
|
|
58
|
+
const binary = findAideBinary({
|
|
59
|
+
cwd,
|
|
60
|
+
pluginRoot:
|
|
61
|
+
process.env.AIDE_PLUGIN_ROOT || process.env.CLAUDE_PLUGIN_ROOT,
|
|
62
|
+
});
|
|
57
63
|
|
|
58
64
|
if (result.shouldAdvise && result.advisory) {
|
|
59
65
|
debug(SOURCE, `Advising on large file read`);
|
|
66
|
+
if (binary) {
|
|
67
|
+
try {
|
|
68
|
+
emitInjectionEvent(binary, cwd, {
|
|
69
|
+
source: SOURCE,
|
|
70
|
+
subtype: "guard",
|
|
71
|
+
name: "large-file-advisory",
|
|
72
|
+
content: result.advisory,
|
|
73
|
+
sessionId,
|
|
74
|
+
attrs: { tool: toolName },
|
|
75
|
+
});
|
|
76
|
+
} catch {
|
|
77
|
+
// Non-fatal
|
|
78
|
+
}
|
|
79
|
+
}
|
|
60
80
|
const output: HookOutput = {
|
|
61
81
|
continue: true,
|
|
62
82
|
hookSpecificOutput: {
|
|
@@ -67,14 +87,23 @@ async function main(): Promise<void> {
|
|
|
67
87
|
console.log(JSON.stringify(output));
|
|
68
88
|
} else {
|
|
69
89
|
// Smart read hint: suggest code index for re-reads of unchanged files
|
|
70
|
-
const binary = findAideBinary({
|
|
71
|
-
cwd,
|
|
72
|
-
pluginRoot:
|
|
73
|
-
process.env.AIDE_PLUGIN_ROOT || process.env.CLAUDE_PLUGIN_ROOT,
|
|
74
|
-
});
|
|
75
90
|
const hintResult = checkSmartReadHint(toolName, toolInput, cwd, binary);
|
|
76
91
|
if (hintResult.shouldHint && hintResult.hint) {
|
|
77
92
|
debug(SOURCE, `Smart read hint triggered`);
|
|
93
|
+
if (binary) {
|
|
94
|
+
try {
|
|
95
|
+
emitInjectionEvent(binary, cwd, {
|
|
96
|
+
source: SOURCE,
|
|
97
|
+
subtype: "guard",
|
|
98
|
+
name: "smart-read-hint",
|
|
99
|
+
content: hintResult.hint,
|
|
100
|
+
sessionId,
|
|
101
|
+
attrs: { tool: toolName },
|
|
102
|
+
});
|
|
103
|
+
} catch {
|
|
104
|
+
// Non-fatal
|
|
105
|
+
}
|
|
106
|
+
}
|
|
78
107
|
const output: HookOutput = {
|
|
79
108
|
continue: true,
|
|
80
109
|
hookSpecificOutput: {
|
|
@@ -21,6 +21,8 @@ import type { ToolRecord } from "../core/context-pruning/types.js";
|
|
|
21
21
|
import { tmpdir } from "os";
|
|
22
22
|
import { join } from "path";
|
|
23
23
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
24
|
+
import { findAideBinary } from "../core/aide-client.js";
|
|
25
|
+
import { emitInjectionEvent } from "../core/read-tracking.js";
|
|
24
26
|
|
|
25
27
|
const SOURCE = "context-pruning";
|
|
26
28
|
|
|
@@ -184,6 +186,31 @@ async function main(): Promise<void> {
|
|
|
184
186
|
saveHistory(sessionId, tracker.getHistory(), explained);
|
|
185
187
|
}
|
|
186
188
|
|
|
189
|
+
try {
|
|
190
|
+
const binary = findAideBinary({
|
|
191
|
+
cwd,
|
|
192
|
+
pluginRoot:
|
|
193
|
+
process.env.AIDE_PLUGIN_ROOT || process.env.CLAUDE_PLUGIN_ROOT,
|
|
194
|
+
});
|
|
195
|
+
const injected = output.hookSpecificOutput?.additionalContext;
|
|
196
|
+
if (binary && injected) {
|
|
197
|
+
emitInjectionEvent(binary, cwd, {
|
|
198
|
+
source: SOURCE,
|
|
199
|
+
subtype: "pruning",
|
|
200
|
+
name: result.strategy || "prune",
|
|
201
|
+
content: injected,
|
|
202
|
+
sessionId,
|
|
203
|
+
attrs: {
|
|
204
|
+
tool: toolName,
|
|
205
|
+
strategy: result.strategy ?? "",
|
|
206
|
+
bytes_saved: String(result.bytesSaved ?? 0),
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
} catch {
|
|
211
|
+
// Non-fatal
|
|
212
|
+
}
|
|
213
|
+
|
|
187
214
|
console.log(JSON.stringify(output));
|
|
188
215
|
} else {
|
|
189
216
|
console.log(JSON.stringify({ continue: true }));
|
|
@@ -14,6 +14,7 @@ import { readStdin } from "../lib/hook-utils.js";
|
|
|
14
14
|
import { debug } from "../lib/logger.js";
|
|
15
15
|
import { evaluateToolUse } from "../core/tool-enforcement.js";
|
|
16
16
|
import { findAideBinary, getState } from "../core/aide-client.js";
|
|
17
|
+
import { emitInjectionEvent } from "../core/read-tracking.js";
|
|
17
18
|
|
|
18
19
|
const SOURCE = "pre-tool-enforcer";
|
|
19
20
|
|
|
@@ -49,17 +50,19 @@ async function main(): Promise<void> {
|
|
|
49
50
|
const toolName = data.tool_name || "";
|
|
50
51
|
const agentName = data.agent_name || "";
|
|
51
52
|
const cwd = data.cwd || process.cwd();
|
|
53
|
+
const sessionId = data.session_id || "";
|
|
52
54
|
|
|
53
55
|
// Resolve active mode from aide binary (source of truth: BBolt store)
|
|
54
56
|
let activeMode: string | null = null;
|
|
57
|
+
let aideBinary: string | null = null;
|
|
55
58
|
try {
|
|
56
|
-
|
|
59
|
+
aideBinary = findAideBinary({
|
|
57
60
|
cwd,
|
|
58
61
|
pluginRoot:
|
|
59
62
|
process.env.AIDE_PLUGIN_ROOT || process.env.CLAUDE_PLUGIN_ROOT,
|
|
60
63
|
});
|
|
61
|
-
if (
|
|
62
|
-
activeMode = getState(
|
|
64
|
+
if (aideBinary) {
|
|
65
|
+
activeMode = getState(aideBinary, cwd, "mode");
|
|
63
66
|
}
|
|
64
67
|
} catch (err) {
|
|
65
68
|
debug(SOURCE, `Failed to resolve active mode (non-fatal): ${err}`);
|
|
@@ -81,6 +84,23 @@ async function main(): Promise<void> {
|
|
|
81
84
|
}
|
|
82
85
|
|
|
83
86
|
if (result.reminder) {
|
|
87
|
+
if (aideBinary) {
|
|
88
|
+
try {
|
|
89
|
+
emitInjectionEvent(aideBinary, cwd, {
|
|
90
|
+
source: SOURCE,
|
|
91
|
+
subtype: "guard",
|
|
92
|
+
content: result.reminder,
|
|
93
|
+
sessionId,
|
|
94
|
+
attrs: {
|
|
95
|
+
tool: toolName,
|
|
96
|
+
...(agentName ? { agent: agentName } : {}),
|
|
97
|
+
...(activeMode ? { mode: activeMode } : {}),
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
} catch {
|
|
101
|
+
// Non-fatal — telemetry must not block tool use
|
|
102
|
+
}
|
|
103
|
+
}
|
|
84
104
|
const output: HookOutput = {
|
|
85
105
|
continue: true,
|
|
86
106
|
hookSpecificOutput: {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Reflect Hook (Stop)
|
|
4
|
+
*
|
|
5
|
+
* Runs the instinct parser catalogue against the session's observe events
|
|
6
|
+
* when AIDE_REFLECT is truthy (1/true/on/yes). Off by default.
|
|
7
|
+
*
|
|
8
|
+
* Fire-and-forget: never blocks Stop, never returns an error to the harness.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { execFileSync } from "child_process";
|
|
12
|
+
import { readStdin } from "../lib/hook-utils.js";
|
|
13
|
+
import { findAideBinary } from "../core/aide-client.js";
|
|
14
|
+
import { debug } from "../lib/logger.js";
|
|
15
|
+
|
|
16
|
+
const SOURCE = "reflect";
|
|
17
|
+
|
|
18
|
+
interface HookInput {
|
|
19
|
+
hook_event_name: string;
|
|
20
|
+
session_id: string;
|
|
21
|
+
cwd: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function main(): Promise<void> {
|
|
25
|
+
try {
|
|
26
|
+
// The CLI itself checks env + .aide/config/aide.json reflect.enabled
|
|
27
|
+
// and no-ops when disabled, so the hook can invoke unconditionally. This
|
|
28
|
+
// is a 1-process spawn at session end — negligible overhead even when
|
|
29
|
+
// disabled.
|
|
30
|
+
|
|
31
|
+
const input = await readStdin();
|
|
32
|
+
if (!input.trim()) {
|
|
33
|
+
console.log(JSON.stringify({}));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const data: HookInput = JSON.parse(input);
|
|
38
|
+
const cwd = data.cwd || process.cwd();
|
|
39
|
+
const sessionID = data.session_id;
|
|
40
|
+
if (!sessionID) {
|
|
41
|
+
console.log(JSON.stringify({}));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const binary = findAideBinary({ cwd });
|
|
46
|
+
if (!binary) {
|
|
47
|
+
console.log(JSON.stringify({}));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
execFileSync(binary, ["reflect", "run", `--session=${sessionID}`], {
|
|
53
|
+
cwd,
|
|
54
|
+
timeout: 10000,
|
|
55
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
56
|
+
});
|
|
57
|
+
debug(SOURCE, `reflect run session=${sessionID} ok`);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
debug(SOURCE, `reflect run failed (non-fatal): ${err}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.log(JSON.stringify({}));
|
|
63
|
+
} catch (err) {
|
|
64
|
+
debug(SOURCE, `error: ${err}`);
|
|
65
|
+
console.log(JSON.stringify({}));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
void main();
|
|
@@ -15,7 +15,7 @@ import { readStdin } from "../lib/hook-utils.js";
|
|
|
15
15
|
import { debug } from "../lib/logger.js";
|
|
16
16
|
import { checkSearchEnrichment } from "../core/search-enrichment.js";
|
|
17
17
|
import { findAideBinary } from "../core/aide-client.js";
|
|
18
|
-
import {
|
|
18
|
+
import { emitInjectionEvent } from "../core/read-tracking.js";
|
|
19
19
|
|
|
20
20
|
const SOURCE = "search-enrichment";
|
|
21
21
|
|
|
@@ -50,6 +50,7 @@ async function main(): Promise<void> {
|
|
|
50
50
|
const toolName = data.tool_name || "";
|
|
51
51
|
const toolInput = data.tool_input || {};
|
|
52
52
|
const cwd = data.cwd || process.cwd();
|
|
53
|
+
const sessionId = data.session_id || "";
|
|
53
54
|
|
|
54
55
|
const binary = findAideBinary({
|
|
55
56
|
cwd,
|
|
@@ -62,11 +63,18 @@ async function main(): Promise<void> {
|
|
|
62
63
|
if (result.shouldEnrich && result.enrichment) {
|
|
63
64
|
debug(SOURCE, `Enriching grep with code index context`);
|
|
64
65
|
|
|
65
|
-
// Record token event for search enrichment
|
|
66
66
|
if (binary) {
|
|
67
67
|
try {
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
// `name: "enrichment"` is load-bearing: the TokensPage by_delivery
|
|
69
|
+
// rollup keys on Event.Name via observeToTokenEvent.
|
|
70
|
+
emitInjectionEvent(binary, cwd, {
|
|
71
|
+
source: SOURCE,
|
|
72
|
+
subtype: "enrichment",
|
|
73
|
+
name: "enrichment",
|
|
74
|
+
content: result.enrichment,
|
|
75
|
+
sessionId,
|
|
76
|
+
attrs: { tool: toolName },
|
|
77
|
+
});
|
|
70
78
|
} catch {
|
|
71
79
|
// Non-fatal
|
|
72
80
|
}
|
package/src/hooks/session-end.ts
CHANGED
|
@@ -27,10 +27,41 @@ const T0 = performance.now();
|
|
|
27
27
|
console.log(JSON.stringify({ continue: true }));
|
|
28
28
|
|
|
29
29
|
const { spawn, execFileSync } = require("child_process") as typeof import("child_process");
|
|
30
|
-
const { existsSync, realpathSync, appendFileSync, mkdirSync, readFileSync } = require("fs") as typeof import("fs");
|
|
31
|
-
const { join } = require("path") as typeof import("path");
|
|
30
|
+
const { existsSync, realpathSync, appendFileSync, mkdirSync, readFileSync, statSync } = require("fs") as typeof import("fs");
|
|
31
|
+
const { join, dirname } = require("path") as typeof import("path");
|
|
32
32
|
const whichSync = (require("which") as typeof import("which")).sync;
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Inline walk-up for project root — mirrors lib/project-root.ts logic
|
|
36
|
+
* (priority: both markers > VCS > .aide/-only > cwd). Inlined here to keep
|
|
37
|
+
* this hook's startup cheap (no extra ES imports).
|
|
38
|
+
*/
|
|
39
|
+
function resolveRoot(cwd: string): string {
|
|
40
|
+
const override = process.env.AIDE_PROJECT_ROOT;
|
|
41
|
+
if (override) {
|
|
42
|
+
try {
|
|
43
|
+
if (statSync(override).isDirectory()) return override;
|
|
44
|
+
} catch { /* fall through */ }
|
|
45
|
+
}
|
|
46
|
+
const candidates: { dir: string; hasAide: boolean; hasVCS: boolean }[] = [];
|
|
47
|
+
let dir = cwd;
|
|
48
|
+
for (;;) {
|
|
49
|
+
const hasAide = existsSync(join(dir, ".aide"));
|
|
50
|
+
let hasVCS = false;
|
|
51
|
+
for (const m of [".git", ".hg", ".svn", ".bzr", ".fossil"]) {
|
|
52
|
+
if (existsSync(join(dir, m))) { hasVCS = true; break; }
|
|
53
|
+
}
|
|
54
|
+
if (hasAide || hasVCS) candidates.push({ dir, hasAide, hasVCS });
|
|
55
|
+
const parent = dirname(dir);
|
|
56
|
+
if (parent === dir) break;
|
|
57
|
+
dir = parent;
|
|
58
|
+
}
|
|
59
|
+
for (const c of candidates) if (c.hasAide && c.hasVCS) return c.dir;
|
|
60
|
+
for (const c of candidates) if (c.hasVCS) return c.dir;
|
|
61
|
+
for (const c of candidates) if (c.hasAide) return c.dir;
|
|
62
|
+
return cwd;
|
|
63
|
+
}
|
|
64
|
+
|
|
34
65
|
const SESSION_ID_RE = /^[a-zA-Z0-9_-]{1,128}$/;
|
|
35
66
|
|
|
36
67
|
/** Elapsed ms since T0. */
|
|
@@ -44,7 +75,7 @@ function ms(): string {
|
|
|
44
75
|
*/
|
|
45
76
|
function log(cwd: string, msg: string): void {
|
|
46
77
|
try {
|
|
47
|
-
const logDir = join(cwd, ".aide", "_logs");
|
|
78
|
+
const logDir = join(resolveRoot(cwd), ".aide", "_logs");
|
|
48
79
|
if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });
|
|
49
80
|
const line = `[${new Date().toISOString()}] [session-end] ${ms()} ${msg}\n`;
|
|
50
81
|
appendFileSync(join(logDir, "session-end.log"), line);
|
|
@@ -61,7 +92,7 @@ function findBinary(cwd?: string): string | null {
|
|
|
61
92
|
if (existsSync(p)) return p;
|
|
62
93
|
}
|
|
63
94
|
if (cwd) {
|
|
64
|
-
const p = join(cwd, ".aide", "bin", "aide");
|
|
95
|
+
const p = join(resolveRoot(cwd), ".aide", "bin", "aide");
|
|
65
96
|
if (existsSync(p)) return p;
|
|
66
97
|
}
|
|
67
98
|
try {
|
|
@@ -24,12 +24,14 @@ import { join, dirname } from "path";
|
|
|
24
24
|
import { fileURLToPath } from "url";
|
|
25
25
|
import { homedir } from "os";
|
|
26
26
|
import { Logger, debug, setDebugCwd } from "../lib/logger.js";
|
|
27
|
-
import { readStdin, detectPlatform } from "../lib/hook-utils.js";
|
|
27
|
+
import { readStdin, detectPlatform, isFalsy } from "../lib/hook-utils.js";
|
|
28
28
|
import { findAideBinary, ensureAideBinary } from "../lib/aide-downloader.js";
|
|
29
|
-
import {
|
|
29
|
+
import { findProjectRoot } from "../lib/project-root.js";
|
|
30
|
+
import { emitInjectionEvent } from "../core/read-tracking.js";
|
|
30
31
|
import {
|
|
31
32
|
ensureDirectories as coreEnsureDirectories,
|
|
32
33
|
loadConfig as coreLoadConfig,
|
|
34
|
+
loadGlobalConfig as coreLoadGlobalConfig,
|
|
33
35
|
initializeSession as coreInitializeSession,
|
|
34
36
|
cleanupStaleStateFiles as coreCleanupStaleStateFiles,
|
|
35
37
|
resetHudState as coreResetHudState,
|
|
@@ -340,13 +342,35 @@ async function main(): Promise<void> {
|
|
|
340
342
|
}
|
|
341
343
|
|
|
342
344
|
const data: HookInput = JSON.parse(input);
|
|
343
|
-
const
|
|
345
|
+
const launchedCwd = data.cwd || process.cwd();
|
|
344
346
|
const sessionId = data.session_id || "unknown";
|
|
345
347
|
|
|
348
|
+
// Resolve the project root so we never plant a sibling .aide/ in a
|
|
349
|
+
// subdirectory of a git repo. Mirrors the Go binary's findProjectRoot().
|
|
350
|
+
const { root: resolvedRoot, hasMarker } = findProjectRoot(launchedCwd);
|
|
351
|
+
if (!hasMarker) {
|
|
352
|
+
const requireGit = coreLoadGlobalConfig().requireGit ?? true;
|
|
353
|
+
if (requireGit) {
|
|
354
|
+
process.stderr.write(
|
|
355
|
+
`[aide] No .git/ or .aide/ found walking up from ${launchedCwd}. ` +
|
|
356
|
+
`Set \`requireGit\`: false in ~/.aide/config/aide.json to allow ` +
|
|
357
|
+
`init in arbitrary directories. Skipping AIDE bootstrap.\n`,
|
|
358
|
+
);
|
|
359
|
+
console.log(JSON.stringify({ continue: true }));
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
process.stderr.write(
|
|
363
|
+
`[aide] No project root found, falling back to ${launchedCwd} (requireGit=false).\n`,
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
const cwd = hasMarker ? resolvedRoot : launchedCwd;
|
|
367
|
+
|
|
346
368
|
// Switch debug logging to project-local logs
|
|
347
369
|
setDebugCwd(cwd);
|
|
348
370
|
|
|
349
|
-
debugLog(
|
|
371
|
+
debugLog(
|
|
372
|
+
`Parsed input: cwd=${cwd}, launchedCwd=${launchedCwd}, sessionId=${sessionId.slice(0, 8)}`,
|
|
373
|
+
);
|
|
350
374
|
|
|
351
375
|
// Initialize logger
|
|
352
376
|
log = new Logger("session-start", cwd);
|
|
@@ -365,32 +389,38 @@ async function main(): Promise<void> {
|
|
|
365
389
|
installHudWrapper(log);
|
|
366
390
|
debugLog(`installHudWrapper complete (${Date.now() - hookStart}ms)`);
|
|
367
391
|
|
|
368
|
-
// Sync MCP server configs across assistants (FS only, fast)
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
skipped
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
392
|
+
// Sync MCP server configs across assistants (FS only, fast).
|
|
393
|
+
// Opt-out via AIDE_MCP_SYNC=0 (defaults to enabled).
|
|
394
|
+
if (isFalsy(process.env.AIDE_MCP_SYNC)) {
|
|
395
|
+
debugLog("mcpSync disabled via AIDE_MCP_SYNC");
|
|
396
|
+
log.info("MCP sync disabled (AIDE_MCP_SYNC falsy)");
|
|
397
|
+
} else {
|
|
398
|
+
debugLog("mcpSync starting...");
|
|
399
|
+
log.start("mcpSync");
|
|
400
|
+
try {
|
|
401
|
+
const mcpResult = syncMcpServers(detectPlatform(), cwd);
|
|
402
|
+
const totalImported =
|
|
403
|
+
mcpResult.user.imported + mcpResult.project.imported;
|
|
404
|
+
const totalWritten =
|
|
405
|
+
mcpResult.user.serversWritten + mcpResult.project.serversWritten;
|
|
406
|
+
const totalSkipped = mcpResult.user.skipped + mcpResult.project.skipped;
|
|
407
|
+
log.end("mcpSync", {
|
|
408
|
+
userServers: mcpResult.user.serversWritten,
|
|
409
|
+
projectServers: mcpResult.project.serversWritten,
|
|
410
|
+
imported: totalImported,
|
|
411
|
+
skipped: totalSkipped,
|
|
412
|
+
});
|
|
413
|
+
if (totalImported > 0) {
|
|
414
|
+
debugLog(
|
|
415
|
+
`mcp-sync: imported ${totalImported} server(s), ${totalWritten} total`,
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
} catch (err) {
|
|
419
|
+
log.warn("MCP sync failed (non-fatal)", err);
|
|
420
|
+
log.end("mcpSync", { success: false, error: String(err) });
|
|
388
421
|
}
|
|
389
|
-
|
|
390
|
-
log.warn("MCP sync failed (non-fatal)", err);
|
|
391
|
-
log.end("mcpSync", { success: false, error: String(err) });
|
|
422
|
+
debugLog(`mcpSync complete (${Date.now() - hookStart}ms)`);
|
|
392
423
|
}
|
|
393
|
-
debugLog(`mcpSync complete (${Date.now() - hookStart}ms)`);
|
|
394
424
|
|
|
395
425
|
// Check that aide binary is available (auto-downloads if missing/outdated)
|
|
396
426
|
debugLog("checkAideBinary starting...");
|
|
@@ -459,26 +489,30 @@ async function main(): Promise<void> {
|
|
|
459
489
|
log.end("buildWelcomeContext");
|
|
460
490
|
debugLog(`buildWelcomeContext complete (${Date.now() - hookStart}ms)`);
|
|
461
491
|
|
|
462
|
-
// Record token events for context injection
|
|
463
492
|
try {
|
|
464
493
|
const binary = findAideBinary(cwd);
|
|
465
|
-
if (binary && context) {
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
494
|
+
if (binary && context && memories.sources) {
|
|
495
|
+
for (const src of memories.sources) {
|
|
496
|
+
const attrs: Record<string, string> = { scope: src.scope };
|
|
497
|
+
if (src.category) attrs.category = src.category;
|
|
498
|
+
if (src.tags && src.tags.length > 0) attrs.tags = src.tags.join(",");
|
|
499
|
+
if (src.sessionId) attrs.source_session_id = src.sessionId;
|
|
500
|
+
if (typeof src.score === "number") {
|
|
501
|
+
attrs.score_at_injection = src.score.toFixed(2);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
emitInjectionEvent(binary, cwd, {
|
|
505
|
+
source: SOURCE,
|
|
506
|
+
subtype: src.kind,
|
|
507
|
+
name: src.name,
|
|
508
|
+
content: src.content,
|
|
509
|
+
sessionId,
|
|
510
|
+
attrs: { ...attrs, source_id: src.id },
|
|
511
|
+
});
|
|
478
512
|
}
|
|
479
513
|
}
|
|
480
514
|
} catch {
|
|
481
|
-
// Non-fatal — don't break session start for
|
|
515
|
+
// Non-fatal — don't break session start for telemetry
|
|
482
516
|
}
|
|
483
517
|
|
|
484
518
|
log.end("total");
|
|
@@ -19,6 +19,7 @@ import { existsSync, mkdirSync } from "fs";
|
|
|
19
19
|
import { join } from "path";
|
|
20
20
|
import { Logger, debug, setDebugCwd } from "../lib/logger.js";
|
|
21
21
|
import { readStdin, detectPlatform } from "../lib/hook-utils.js";
|
|
22
|
+
import { findProjectRoot } from "../lib/project-root.js";
|
|
22
23
|
import {
|
|
23
24
|
discoverSkills as coreDiscoverSkills,
|
|
24
25
|
matchSkills as coreMatchSkills,
|
|
@@ -26,7 +27,12 @@ import {
|
|
|
26
27
|
} from "../core/skill-matcher.js";
|
|
27
28
|
import type { Skill } from "../core/types.js";
|
|
28
29
|
import { findAideBinary } from "../core/aide-client.js";
|
|
29
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
recordObserveEvent,
|
|
32
|
+
previewContent,
|
|
33
|
+
emitInjectionEvent,
|
|
34
|
+
} from "../core/read-tracking.js";
|
|
35
|
+
import { reflectEnabled } from "../lib/hook-utils.js";
|
|
30
36
|
|
|
31
37
|
const SOURCE = "skill-injector";
|
|
32
38
|
|
|
@@ -57,12 +63,15 @@ let log: Logger | null = null;
|
|
|
57
63
|
* Ensure .aide directories exist (minimal version for skill-injector)
|
|
58
64
|
*/
|
|
59
65
|
function ensureDirectories(cwd: string): void {
|
|
66
|
+
// Resolve to the canonical project root so we don't plant a stray .aide/
|
|
67
|
+
// in a subdir the harness happened to launch from. See lib/project-root.ts.
|
|
68
|
+
const { root } = findProjectRoot(cwd);
|
|
60
69
|
const dirs = [
|
|
61
|
-
join(
|
|
62
|
-
join(
|
|
63
|
-
join(
|
|
64
|
-
join(
|
|
65
|
-
join(
|
|
70
|
+
join(root, ".aide"),
|
|
71
|
+
join(root, ".aide", "skills"),
|
|
72
|
+
join(root, ".aide", "config"),
|
|
73
|
+
join(root, ".aide", "state"),
|
|
74
|
+
join(root, ".aide", "memory"),
|
|
66
75
|
];
|
|
67
76
|
|
|
68
77
|
for (const dir of dirs) {
|
|
@@ -156,6 +165,7 @@ async function main(): Promise<void> {
|
|
|
156
165
|
const data: HookInput = JSON.parse(input);
|
|
157
166
|
const prompt = data.prompt || "";
|
|
158
167
|
const cwd = data.cwd || process.cwd();
|
|
168
|
+
const sessionId = data.session_id || "";
|
|
159
169
|
|
|
160
170
|
// Switch debug logging to project-local logs
|
|
161
171
|
setDebugCwd(cwd);
|
|
@@ -175,6 +185,27 @@ async function main(): Promise<void> {
|
|
|
175
185
|
log.end("ensureDirectories");
|
|
176
186
|
debugLog(`ensureDirectories complete (${Date.now() - hookStart}ms)`);
|
|
177
187
|
|
|
188
|
+
// Emit a user-prompt observe event so the convergence detector can find
|
|
189
|
+
// corrective markers near edit sequences. Gated behind AIDE_REFLECT so
|
|
190
|
+
// sessions that aren't running reflect don't accrue extra events.
|
|
191
|
+
if (prompt && reflectEnabled(cwd)) {
|
|
192
|
+
try {
|
|
193
|
+
const binary = findAideBinary({ cwd });
|
|
194
|
+
if (binary) {
|
|
195
|
+
recordObserveEvent(binary, cwd, {
|
|
196
|
+
kind: "hook",
|
|
197
|
+
name: "user_prompt",
|
|
198
|
+
category: "input",
|
|
199
|
+
tokens: Math.round(prompt.length / 3.0),
|
|
200
|
+
session: sessionId,
|
|
201
|
+
attrs: { text: previewContent(prompt, 2000) },
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
} catch {
|
|
205
|
+
// Non-fatal
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
178
209
|
if (!prompt) {
|
|
179
210
|
debugLog("No prompt provided, exiting");
|
|
180
211
|
log.info("No prompt provided");
|
|
@@ -215,14 +246,12 @@ async function main(): Promise<void> {
|
|
|
215
246
|
if (binary) {
|
|
216
247
|
for (const skill of matched) {
|
|
217
248
|
const text = `### ${skill.name}\n${skill.description ?? ""}\n${skill.content}`;
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
kind: "injection",
|
|
221
|
-
name: skill.name,
|
|
222
|
-
category: "inject",
|
|
249
|
+
emitInjectionEvent(binary, cwd, {
|
|
250
|
+
source: SOURCE,
|
|
223
251
|
subtype: "skill",
|
|
224
|
-
|
|
225
|
-
|
|
252
|
+
name: skill.name,
|
|
253
|
+
content: text,
|
|
254
|
+
sessionId,
|
|
226
255
|
});
|
|
227
256
|
}
|
|
228
257
|
}
|
|
@@ -17,8 +17,9 @@
|
|
|
17
17
|
import { execFileSync } from "child_process";
|
|
18
18
|
import { basename } from "path";
|
|
19
19
|
import { Logger } from "../lib/logger.js";
|
|
20
|
-
import { readStdin, setMemoryState } from "../lib/hook-utils.js";
|
|
20
|
+
import { readStdin, setMemoryState, isFalsy } from "../lib/hook-utils.js";
|
|
21
21
|
import { findAideBinary } from "../core/aide-client.js";
|
|
22
|
+
import { emitInjectionEvent } from "../core/read-tracking.js";
|
|
22
23
|
import { refreshHud } from "../lib/hud.js";
|
|
23
24
|
|
|
24
25
|
// Global logger instance
|
|
@@ -112,8 +113,7 @@ function fetchSubagentMemories(cwd: string): {
|
|
|
112
113
|
decisions: [] as string[],
|
|
113
114
|
};
|
|
114
115
|
|
|
115
|
-
|
|
116
|
-
if (process.env.AIDE_MEMORY_INJECT === "0") {
|
|
116
|
+
if (isFalsy(process.env.AIDE_MEMORY_INJECT)) {
|
|
117
117
|
return result;
|
|
118
118
|
}
|
|
119
119
|
|
|
@@ -315,6 +315,11 @@ async function processSubagentStart(
|
|
|
315
315
|
setAgentState(cwd, agent_id, "type", type);
|
|
316
316
|
setAgentState(cwd, agent_id, "startedAt", new Date().toISOString());
|
|
317
317
|
setAgentState(cwd, agent_id, "session", session_id); // Track which session owns this agent
|
|
318
|
+
// Parent linkage for swarm queries: the orchestrator (session_id) spawned
|
|
319
|
+
// this subagent (agent_id). Watch filters key off parent_session.
|
|
320
|
+
// namespace stamps swarm-scoped memories the subagent creates.
|
|
321
|
+
setAgentState(cwd, agent_id, "parent_session", session_id);
|
|
322
|
+
setAgentState(cwd, agent_id, "namespace", `swarm:${session_id}`);
|
|
318
323
|
log?.end("registerAgent");
|
|
319
324
|
|
|
320
325
|
// Refresh HUD to show the new running agent
|
|
@@ -406,6 +411,31 @@ async function main(): Promise<void> {
|
|
|
406
411
|
hookEventName: "SubagentStart",
|
|
407
412
|
additionalContext,
|
|
408
413
|
};
|
|
414
|
+
try {
|
|
415
|
+
const binary = findAideBinary({
|
|
416
|
+
cwd,
|
|
417
|
+
pluginRoot:
|
|
418
|
+
process.env.AIDE_PLUGIN_ROOT || process.env.CLAUDE_PLUGIN_ROOT,
|
|
419
|
+
});
|
|
420
|
+
if (binary) {
|
|
421
|
+
const startData = data as SubagentStartInput;
|
|
422
|
+
emitInjectionEvent(binary, cwd, {
|
|
423
|
+
source: "subagent-tracker",
|
|
424
|
+
subtype: "signal",
|
|
425
|
+
name: "subagent-priming",
|
|
426
|
+
content: additionalContext,
|
|
427
|
+
sessionId: startData.session_id,
|
|
428
|
+
attrs: {
|
|
429
|
+
...(startData.agent_id ? { agent_id: startData.agent_id } : {}),
|
|
430
|
+
...(startData.agent_type
|
|
431
|
+
? { agent_type: startData.agent_type }
|
|
432
|
+
: {}),
|
|
433
|
+
},
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
} catch {
|
|
437
|
+
// Non-fatal
|
|
438
|
+
}
|
|
409
439
|
}
|
|
410
440
|
|
|
411
441
|
console.log(JSON.stringify(output));
|