@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
package/src/lib/logger.ts
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
import { existsSync, mkdirSync, appendFileSync } from "fs";
|
|
23
23
|
import { join } from "path";
|
|
24
|
+
import { findProjectRoot } from "./project-root.js";
|
|
24
25
|
|
|
25
26
|
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
26
27
|
|
|
@@ -51,10 +52,14 @@ export class Logger {
|
|
|
51
52
|
constructor(source: string, cwd?: string) {
|
|
52
53
|
this.source = source;
|
|
53
54
|
this.cwd = cwd || process.cwd();
|
|
54
|
-
//
|
|
55
|
-
|
|
55
|
+
// Resolve to the canonical project root so logs land alongside the
|
|
56
|
+
// real .aide/ store, not in a stray subdir the harness launched from.
|
|
57
|
+
const { root } = findProjectRoot(this.cwd);
|
|
58
|
+
// Set debugLogCwd so isDebugEnabled() can check the sentinel file at
|
|
59
|
+
// the resolved root.
|
|
60
|
+
setDebugCwd(root);
|
|
56
61
|
this.enabled = isDebugEnabled();
|
|
57
|
-
this.logDir = join(
|
|
62
|
+
this.logDir = join(root, ".aide", "_logs");
|
|
58
63
|
this.logFile = join(this.logDir, "startup.log");
|
|
59
64
|
this.sessionStart = Date.now();
|
|
60
65
|
|
|
@@ -288,11 +293,13 @@ export function isDebugEnabled(): boolean {
|
|
|
288
293
|
const debugEnv = process.env.AIDE_DEBUG || "";
|
|
289
294
|
if (debugEnv === "1" || debugEnv === "true") return true;
|
|
290
295
|
|
|
291
|
-
// Check sentinel file (cached per cwd)
|
|
296
|
+
// Check sentinel file (cached per cwd). Resolve to project root so the
|
|
297
|
+
// sentinel works regardless of which subdir the hook fired from.
|
|
292
298
|
if (debugLogCwd !== debugSentinelCwd) {
|
|
293
299
|
debugSentinelCwd = debugLogCwd;
|
|
294
300
|
try {
|
|
295
|
-
|
|
301
|
+
const { root } = findProjectRoot(debugLogCwd);
|
|
302
|
+
debugSentinelResult = existsSync(join(root, ".aide", ".debug"));
|
|
296
303
|
} catch {
|
|
297
304
|
debugSentinelResult = false;
|
|
298
305
|
}
|
|
@@ -303,6 +310,10 @@ export function isDebugEnabled(): boolean {
|
|
|
303
310
|
/**
|
|
304
311
|
* Set the working directory for debug logging.
|
|
305
312
|
* Call this after parsing stdin to use project-local logs.
|
|
313
|
+
*
|
|
314
|
+
* Note: callers may pass either the raw cwd or an already-resolved project
|
|
315
|
+
* root. Logger's constructor resolves cwd → root before calling this; bare
|
|
316
|
+
* callers of debug() may pass cwd and we'll resolve at use-time.
|
|
306
317
|
*/
|
|
307
318
|
export function setDebugCwd(cwd: string): void {
|
|
308
319
|
debugLogCwd = cwd;
|
|
@@ -326,7 +337,8 @@ export function setDebugCwd(cwd: string): void {
|
|
|
326
337
|
export function debug(source: string, msg: string): void {
|
|
327
338
|
if (!isDebugEnabled()) return;
|
|
328
339
|
|
|
329
|
-
const
|
|
340
|
+
const { root } = findProjectRoot(debugLogCwd);
|
|
341
|
+
const logDir = join(root, ".aide", "_logs");
|
|
330
342
|
try {
|
|
331
343
|
if (!existsSync(logDir)) {
|
|
332
344
|
mkdirSync(logDir, { recursive: true });
|
package/src/lib/project-root.ts
CHANGED
|
@@ -9,11 +9,22 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Resolution order, matching main.go:findProjectRoot:
|
|
11
11
|
* 1. AIDE_PROJECT_ROOT env override (must be an existing directory).
|
|
12
|
-
* 2. Walk
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* 2. Walk the full ancestry from cwd to /, collecting candidates, then
|
|
13
|
+
* prefer:
|
|
14
|
+
* a. Closest ancestor with BOTH .aide/ and a VCS marker
|
|
15
|
+
* (.git/.hg/.svn/.bzr/.fossil) — handles the common case where
|
|
16
|
+
* the canonical root sits at the git repo root.
|
|
17
|
+
* b. Closest ancestor with a VCS marker only — .aide/ will be
|
|
18
|
+
* created there if needed.
|
|
19
|
+
* c. Closest ancestor with .aide/ only — standalone projects with
|
|
20
|
+
* no VCS.
|
|
21
|
+
* ~/.aide/ is skipped as a project marker unless cwd is $HOME.
|
|
16
22
|
* 3. No marker found: return { root: cwd, hasMarker: false }.
|
|
23
|
+
*
|
|
24
|
+
* The "both markers wins" priority is what stops a stray child .aide/ from
|
|
25
|
+
* shadowing the real project root: a sibling .aide/ created by an
|
|
26
|
+
* accidental CLI invocation lives in a subdir with no .git/, so the walk
|
|
27
|
+
* keeps going until it finds the parent that has both.
|
|
17
28
|
*/
|
|
18
29
|
|
|
19
30
|
import { basename, dirname, join, resolve } from "path";
|
|
@@ -53,42 +64,68 @@ export function findProjectRoot(cwd: string): ProjectRootResult {
|
|
|
53
64
|
const startCwd = resolve(cwd);
|
|
54
65
|
const home = homedir();
|
|
55
66
|
|
|
67
|
+
interface Candidate {
|
|
68
|
+
dir: string;
|
|
69
|
+
hasAide: boolean;
|
|
70
|
+
hasVCS: boolean;
|
|
71
|
+
vcsResolved: string;
|
|
72
|
+
}
|
|
73
|
+
const path: Candidate[] = [];
|
|
74
|
+
|
|
56
75
|
let dir = startCwd;
|
|
57
76
|
for (;;) {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
77
|
+
const cand: Candidate = { dir, hasAide: false, hasVCS: false, vcsResolved: "" };
|
|
78
|
+
|
|
79
|
+
if (existsSync(join(dir, ".aide"))) {
|
|
80
|
+
// Skip ~/.aide/ unless cwd is $HOME itself.
|
|
81
|
+
const isHomeAide = home && dir === home && startCwd !== home;
|
|
82
|
+
if (!isHomeAide) cand.hasAide = true;
|
|
65
83
|
}
|
|
66
84
|
|
|
67
|
-
const
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (stat.isDirectory()) {
|
|
72
|
-
return { root: dir, hasMarker: true };
|
|
73
|
-
}
|
|
74
|
-
if (stat.isFile()) {
|
|
75
|
-
const mainRoot = resolveWorktreeGitFile(gitPath);
|
|
76
|
-
if (mainRoot) {
|
|
77
|
-
return { root: mainRoot, hasMarker: true };
|
|
78
|
-
}
|
|
79
|
-
return { root: dir, hasMarker: true };
|
|
80
|
-
}
|
|
81
|
-
} catch {
|
|
82
|
-
return { root: dir, hasMarker: true };
|
|
83
|
-
}
|
|
85
|
+
const vcs = vcsMarkerAt(dir);
|
|
86
|
+
if (vcs.ok) {
|
|
87
|
+
cand.hasVCS = true;
|
|
88
|
+
cand.vcsResolved = vcs.resolved || dir;
|
|
84
89
|
}
|
|
85
90
|
|
|
91
|
+
if (cand.hasAide || cand.hasVCS) path.push(cand);
|
|
92
|
+
|
|
86
93
|
const parent = dirname(dir);
|
|
87
|
-
if (parent === dir)
|
|
88
|
-
return { root: startCwd, hasMarker: false };
|
|
89
|
-
}
|
|
94
|
+
if (parent === dir) break;
|
|
90
95
|
dir = parent;
|
|
91
96
|
}
|
|
97
|
+
|
|
98
|
+
for (const c of path) {
|
|
99
|
+
if (c.hasAide && c.hasVCS) return { root: c.vcsResolved, hasMarker: true };
|
|
100
|
+
}
|
|
101
|
+
for (const c of path) {
|
|
102
|
+
if (c.hasVCS) return { root: c.vcsResolved, hasMarker: true };
|
|
103
|
+
}
|
|
104
|
+
for (const c of path) {
|
|
105
|
+
if (c.hasAide) return { root: c.dir, hasMarker: true };
|
|
106
|
+
}
|
|
107
|
+
return { root: startCwd, hasMarker: false };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function vcsMarkerAt(dir: string): { ok: boolean; resolved: string } {
|
|
111
|
+
const gitPath = join(dir, ".git");
|
|
112
|
+
if (existsSync(gitPath)) {
|
|
113
|
+
try {
|
|
114
|
+
const st = statSync(gitPath);
|
|
115
|
+
if (st.isDirectory()) return { ok: true, resolved: dir };
|
|
116
|
+
if (st.isFile()) {
|
|
117
|
+
const mainRoot = resolveWorktreeGitFile(gitPath);
|
|
118
|
+
return { ok: true, resolved: mainRoot || dir };
|
|
119
|
+
}
|
|
120
|
+
return { ok: true, resolved: dir };
|
|
121
|
+
} catch {
|
|
122
|
+
return { ok: true, resolved: dir };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
for (const marker of [".hg", ".svn", ".bzr", ".fossil"]) {
|
|
126
|
+
if (existsSync(join(dir, marker))) return { ok: true, resolved: dir };
|
|
127
|
+
}
|
|
128
|
+
return { ok: false, resolved: "" };
|
|
92
129
|
}
|
|
93
130
|
|
|
94
131
|
/**
|
package/src/opencode/hooks.ts
CHANGED
|
@@ -52,12 +52,14 @@ import { checkWriteGuard } from "../core/write-guard.js";
|
|
|
52
52
|
import { checkSmartReadHint } from "../core/context-guard.js";
|
|
53
53
|
import { checkSearchEnrichment } from "../core/search-enrichment.js";
|
|
54
54
|
import { recordToolEvent } from "../core/tool-observe.js";
|
|
55
|
+
import { recordObserveEvent, previewContent } from "../core/read-tracking.js";
|
|
55
56
|
import {
|
|
56
57
|
checkComments,
|
|
57
58
|
getCheckableFilePath,
|
|
58
59
|
getContentToCheck,
|
|
59
60
|
} from "../core/comment-checker.js";
|
|
60
61
|
import { getState, setState } from "../core/aide-client.js";
|
|
62
|
+
import { isFalsy, reflectEnabled } from "../lib/hook-utils.js";
|
|
61
63
|
import { saveStateSnapshot } from "../core/pre-compact-logic.js";
|
|
62
64
|
import { cleanupSession } from "../core/cleanup.js";
|
|
63
65
|
import {
|
|
@@ -335,11 +337,16 @@ function initializeAide(state: AideState): void {
|
|
|
335
337
|
|
|
336
338
|
ensureDirectories(state.cwd);
|
|
337
339
|
|
|
338
|
-
// Sync MCP server configs across assistants (FS only, fast)
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
340
|
+
// Sync MCP server configs across assistants (FS only, fast).
|
|
341
|
+
// Opt-out via AIDE_MCP_SYNC=0 (defaults to enabled).
|
|
342
|
+
if (isFalsy(process.env.AIDE_MCP_SYNC)) {
|
|
343
|
+
debug(SOURCE, "MCP sync disabled (AIDE_MCP_SYNC falsy)");
|
|
344
|
+
} else {
|
|
345
|
+
try {
|
|
346
|
+
syncMcpServers("opencode", state.cwd);
|
|
347
|
+
} catch (err) {
|
|
348
|
+
debug(SOURCE, `MCP sync failed (non-fatal): ${err}`);
|
|
349
|
+
}
|
|
343
350
|
}
|
|
344
351
|
|
|
345
352
|
const config = loadConfig(state.cwd);
|
|
@@ -592,6 +599,22 @@ async function handleSessionIdle(
|
|
|
592
599
|
cleanupPartials(state.binary, state.cwd, sessionId);
|
|
593
600
|
}
|
|
594
601
|
}
|
|
602
|
+
|
|
603
|
+
// Reflect (extract instinct proposals). The CLI itself checks env +
|
|
604
|
+
// .aide/config/aide.json reflect.enabled and no-ops when disabled, so
|
|
605
|
+
// invoke unconditionally. Mirrors the Claude Code Stop hook.
|
|
606
|
+
if (state.binary) {
|
|
607
|
+
try {
|
|
608
|
+
execFileSync(state.binary, ["reflect", "run", `--session=${sessionId}`], {
|
|
609
|
+
cwd: state.cwd,
|
|
610
|
+
timeout: 10000,
|
|
611
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
612
|
+
});
|
|
613
|
+
debug(SOURCE, `reflect run session=${sessionId.slice(0, 8)} ok`);
|
|
614
|
+
} catch (err) {
|
|
615
|
+
debug(SOURCE, `reflect run failed (non-fatal): ${err}`);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
595
618
|
}
|
|
596
619
|
|
|
597
620
|
async function handleSessionDeleted(
|
|
@@ -647,6 +670,24 @@ async function handleMessagePartUpdated(
|
|
|
647
670
|
// this event fires after the transform (defensive against ordering).
|
|
648
671
|
state.lastUserPrompt = prompt;
|
|
649
672
|
|
|
673
|
+
// Emit a user-prompt observe event so the convergence detector can find
|
|
674
|
+
// corrective markers near edit sequences. Gated behind AIDE_REFLECT,
|
|
675
|
+
// mirroring src/hooks/skill-injector.ts for cross-harness parity.
|
|
676
|
+
if (state.binary && reflectEnabled(state.cwd)) {
|
|
677
|
+
try {
|
|
678
|
+
recordObserveEvent(state.binary, state.cwd, {
|
|
679
|
+
kind: "hook",
|
|
680
|
+
name: "user_prompt",
|
|
681
|
+
category: "input",
|
|
682
|
+
tokens: Math.round(prompt.length / 3.0),
|
|
683
|
+
session: extractSessionId(event),
|
|
684
|
+
attrs: { text: previewContent(prompt, 2000) },
|
|
685
|
+
});
|
|
686
|
+
} catch {
|
|
687
|
+
// Non-fatal
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
650
691
|
const skills = discoverSkills(state.cwd, state.pluginRoot ?? undefined);
|
|
651
692
|
const matched = matchSkills(prompt, skills, 3, "opencode");
|
|
652
693
|
|