@jmylchreest/aide-plugin 0.0.66 → 0.1.1
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 +1 -1
- 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 +74 -14
- package/src/core/skill-matcher.ts +18 -22
- package/src/core/tool-observe.ts +12 -0
- package/src/core/types.ts +15 -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 +51 -41
- 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/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 +68 -31
- package/src/opencode/hooks.ts +46 -5
|
@@ -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,10 +24,10 @@ 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
29
|
import { findProjectRoot } from "../lib/project-root.js";
|
|
30
|
-
import {
|
|
30
|
+
import { emitInjectionEvent } from "../core/read-tracking.js";
|
|
31
31
|
import {
|
|
32
32
|
ensureDirectories as coreEnsureDirectories,
|
|
33
33
|
loadConfig as coreLoadConfig,
|
|
@@ -389,32 +389,38 @@ async function main(): Promise<void> {
|
|
|
389
389
|
installHudWrapper(log);
|
|
390
390
|
debugLog(`installHudWrapper complete (${Date.now() - hookStart}ms)`);
|
|
391
391
|
|
|
392
|
-
// Sync MCP server configs across assistants (FS only, fast)
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
skipped
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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) });
|
|
412
421
|
}
|
|
413
|
-
|
|
414
|
-
log.warn("MCP sync failed (non-fatal)", err);
|
|
415
|
-
log.end("mcpSync", { success: false, error: String(err) });
|
|
422
|
+
debugLog(`mcpSync complete (${Date.now() - hookStart}ms)`);
|
|
416
423
|
}
|
|
417
|
-
debugLog(`mcpSync complete (${Date.now() - hookStart}ms)`);
|
|
418
424
|
|
|
419
425
|
// Check that aide binary is available (auto-downloads if missing/outdated)
|
|
420
426
|
debugLog("checkAideBinary starting...");
|
|
@@ -483,26 +489,30 @@ async function main(): Promise<void> {
|
|
|
483
489
|
log.end("buildWelcomeContext");
|
|
484
490
|
debugLog(`buildWelcomeContext complete (${Date.now() - hookStart}ms)`);
|
|
485
491
|
|
|
486
|
-
// Record token events for context injection
|
|
487
492
|
try {
|
|
488
493
|
const binary = findAideBinary(cwd);
|
|
489
|
-
if (binary && context) {
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
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
|
+
});
|
|
502
512
|
}
|
|
503
513
|
}
|
|
504
514
|
} catch {
|
|
505
|
-
// Non-fatal — don't break session start for
|
|
515
|
+
// Non-fatal — don't break session start for telemetry
|
|
506
516
|
}
|
|
507
517
|
|
|
508
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));
|
package/src/hooks/write-guard.ts
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
import { readStdin } from "../lib/hook-utils.js";
|
|
13
13
|
import { debug } from "../lib/logger.js";
|
|
14
14
|
import { checkWriteGuard } from "../core/write-guard.js";
|
|
15
|
+
import { findAideBinary } from "../core/aide-client.js";
|
|
16
|
+
import { emitInjectionEvent } from "../core/read-tracking.js";
|
|
15
17
|
|
|
16
18
|
const SOURCE = "write-guard";
|
|
17
19
|
|
|
@@ -48,14 +50,35 @@ async function main(): Promise<void> {
|
|
|
48
50
|
const toolName = data.tool_name || "";
|
|
49
51
|
const toolInput = data.tool_input || {};
|
|
50
52
|
const cwd = data.cwd || process.cwd();
|
|
53
|
+
const sessionId = data.session_id || "";
|
|
51
54
|
|
|
52
55
|
const result = checkWriteGuard(toolName, toolInput, cwd);
|
|
53
56
|
|
|
54
57
|
if (!result.allowed) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
const filePath =
|
|
59
|
+
(toolInput.file_path as string) ||
|
|
60
|
+
(toolInput.filePath as string) ||
|
|
61
|
+
(toolInput.path as string) ||
|
|
62
|
+
"";
|
|
63
|
+
debug(SOURCE, `Advisory: Write to existing file: ${filePath}`);
|
|
64
|
+
try {
|
|
65
|
+
const binary = findAideBinary({
|
|
66
|
+
cwd,
|
|
67
|
+
pluginRoot:
|
|
68
|
+
process.env.AIDE_PLUGIN_ROOT || process.env.CLAUDE_PLUGIN_ROOT,
|
|
69
|
+
});
|
|
70
|
+
if (binary && result.message) {
|
|
71
|
+
emitInjectionEvent(binary, cwd, {
|
|
72
|
+
source: SOURCE,
|
|
73
|
+
subtype: "guard",
|
|
74
|
+
content: result.message,
|
|
75
|
+
sessionId,
|
|
76
|
+
attrs: { tool: toolName, ...(filePath ? { file: filePath } : {}) },
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
// Non-fatal
|
|
81
|
+
}
|
|
59
82
|
const output: HookOutput = {
|
|
60
83
|
continue: true,
|
|
61
84
|
hookSpecificOutput: {
|
package/src/lib/hook-utils.ts
CHANGED
|
@@ -93,6 +93,69 @@ export function detectPlatform(): "claude-code" | "codex" {
|
|
|
93
93
|
return "claude-code";
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
import { existsSync, readFileSync } from "fs";
|
|
97
|
+
import { join } from "path";
|
|
98
|
+
import { findProjectRoot } from "./project-root.js";
|
|
99
|
+
|
|
100
|
+
const TRUTHY = new Set(["1", "true", "on", "yes"]);
|
|
101
|
+
const FALSY = new Set(["0", "false", "off", "no"]);
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* isTruthy reports whether an env-var value should be treated as "on".
|
|
105
|
+
* Accepts the same set as the Go config helper: 1/true/on/yes
|
|
106
|
+
* (case-insensitive, whitespace-trimmed). Use for opt-in flags.
|
|
107
|
+
*/
|
|
108
|
+
export function isTruthy(v: string | undefined): boolean {
|
|
109
|
+
if (!v) return false;
|
|
110
|
+
return TRUTHY.has(v.trim().toLowerCase());
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* isFalsy reports whether an env-var value was explicitly set to disable.
|
|
115
|
+
* Accepts 0/false/off/no. Unset/empty/unknown values return false so the
|
|
116
|
+
* caller's default-on behaviour wins. Use for opt-out flags.
|
|
117
|
+
*/
|
|
118
|
+
export function isFalsy(v: string | undefined): boolean {
|
|
119
|
+
if (!v) return false;
|
|
120
|
+
return FALSY.has(v.trim().toLowerCase());
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* reflectEnabled mirrors the Go-side config.ResolveReflectEnabled precedence
|
|
125
|
+
* so TS hooks that need to gate on the reflect setting see the same answer
|
|
126
|
+
* as `aide reflect run`. Precedence:
|
|
127
|
+
*
|
|
128
|
+
* 1. AIDE_REFLECT env (recognised truthy/falsy values win)
|
|
129
|
+
* 2. .aide/config/aide.json `reflect.enabled` at the resolved project
|
|
130
|
+
* root (walks up from cwd via findProjectRoot — does NOT just look
|
|
131
|
+
* at cwd/.aide/)
|
|
132
|
+
* 3. default false
|
|
133
|
+
*
|
|
134
|
+
* Used by skill-injector.ts and opencode/hooks.ts to gate the user_prompt
|
|
135
|
+
* observe-event emit that convergence detection depends on.
|
|
136
|
+
*/
|
|
137
|
+
export function reflectEnabled(cwd: string): boolean {
|
|
138
|
+
const env = process.env.AIDE_REFLECT;
|
|
139
|
+
if (env !== undefined && env !== "") {
|
|
140
|
+
const norm = env.trim().toLowerCase();
|
|
141
|
+
if (TRUTHY.has(norm)) return true;
|
|
142
|
+
if (FALSY.has(norm)) return false;
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
const { root } = findProjectRoot(cwd);
|
|
146
|
+
const cfgPath = join(root, ".aide", "config", "aide.json");
|
|
147
|
+
if (existsSync(cfgPath)) {
|
|
148
|
+
const cfg = JSON.parse(readFileSync(cfgPath, "utf-8")) as {
|
|
149
|
+
reflect?: { enabled?: boolean };
|
|
150
|
+
};
|
|
151
|
+
return cfg?.reflect?.enabled === true;
|
|
152
|
+
}
|
|
153
|
+
} catch {
|
|
154
|
+
// Unreadable / malformed config — treat as unset.
|
|
155
|
+
}
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
|
|
96
159
|
/**
|
|
97
160
|
* Get the plugin root directory from environment variables.
|
|
98
161
|
*/
|
package/src/lib/hud.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
|
9
9
|
import { join } from "path";
|
|
10
10
|
import { execFileSync } from "child_process";
|
|
11
11
|
import { runAide, findAideBinary } from "./hook-utils.js";
|
|
12
|
+
import { findProjectRoot } from "./project-root.js";
|
|
12
13
|
|
|
13
14
|
// Cache the aide version for the session (won't change)
|
|
14
15
|
let aideVersionCache: string | null = null;
|
|
@@ -149,7 +150,8 @@ export function getAgentStates(cwd: string): AgentState[] {
|
|
|
149
150
|
* Load HUD configuration
|
|
150
151
|
*/
|
|
151
152
|
export function loadHudConfig(cwd: string): HudConfig {
|
|
152
|
-
const
|
|
153
|
+
const { root } = findProjectRoot(cwd);
|
|
154
|
+
const configPath = join(root, ".aide", "config", "hud.json");
|
|
153
155
|
|
|
154
156
|
if (existsSync(configPath)) {
|
|
155
157
|
try {
|
|
@@ -444,7 +446,8 @@ export function formatHud(
|
|
|
444
446
|
* Write HUD output to state file
|
|
445
447
|
*/
|
|
446
448
|
export function writeHudOutput(cwd: string, output: string): void {
|
|
447
|
-
const
|
|
449
|
+
const { root } = findProjectRoot(cwd);
|
|
450
|
+
const stateDir = join(root, ".aide", "state");
|
|
448
451
|
if (!existsSync(stateDir)) {
|
|
449
452
|
try {
|
|
450
453
|
mkdirSync(stateDir, { recursive: true });
|