@jmylchreest/aide-plugin 0.0.36 → 0.0.37
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/src/opencode/hooks.ts +29 -1
- package/src/opencode/index.ts +139 -4
- package/src/opencode/types.ts +48 -4
package/package.json
CHANGED
package/src/opencode/hooks.ts
CHANGED
|
@@ -98,6 +98,8 @@ interface AideState {
|
|
|
98
98
|
worktree: string;
|
|
99
99
|
/** Root of the aide plugin package (for finding bundled skills) */
|
|
100
100
|
pluginRoot: string | null;
|
|
101
|
+
/** Skip initializing aide for non-project directories */
|
|
102
|
+
skipInit: boolean;
|
|
101
103
|
sessionState: SessionState | null;
|
|
102
104
|
memories: MemoryInjection | null;
|
|
103
105
|
welcomeContext: string | null;
|
|
@@ -122,6 +124,7 @@ export async function createHooks(
|
|
|
122
124
|
worktree: string,
|
|
123
125
|
client: OpenCodeClient,
|
|
124
126
|
pluginRoot?: string,
|
|
127
|
+
options?: { skipInit?: boolean },
|
|
125
128
|
): Promise<Hooks> {
|
|
126
129
|
const state: AideState = {
|
|
127
130
|
initialized: false,
|
|
@@ -129,6 +132,7 @@ export async function createHooks(
|
|
|
129
132
|
cwd,
|
|
130
133
|
worktree,
|
|
131
134
|
pluginRoot: pluginRoot || null,
|
|
135
|
+
skipInit: options?.skipInit ?? false,
|
|
132
136
|
sessionState: null,
|
|
133
137
|
memories: null,
|
|
134
138
|
welcomeContext: null,
|
|
@@ -143,6 +147,18 @@ export async function createHooks(
|
|
|
143
147
|
// Run one-time initialization (directories, binary, config)
|
|
144
148
|
initializeAide(state);
|
|
145
149
|
|
|
150
|
+
try {
|
|
151
|
+
await client.app.log({
|
|
152
|
+
body: {
|
|
153
|
+
service: "aide",
|
|
154
|
+
level: "info",
|
|
155
|
+
message: `aide hooks init: cwd=${state.cwd} worktree=${state.worktree} pluginRoot=${state.pluginRoot ?? "unknown"} binary=${state.binary ?? "not-found"} skipInit=${state.skipInit}`,
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
} catch (err) {
|
|
159
|
+
debug(SOURCE, `Init log failed (non-fatal): ${err}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
146
162
|
return {
|
|
147
163
|
event: createEventHandler(state),
|
|
148
164
|
config: createConfigHandler(state),
|
|
@@ -250,6 +266,15 @@ function createCommandHandler(state: AideState): (
|
|
|
250
266
|
|
|
251
267
|
function initializeAide(state: AideState): void {
|
|
252
268
|
try {
|
|
269
|
+
if (state.skipInit) {
|
|
270
|
+
debug(
|
|
271
|
+
SOURCE,
|
|
272
|
+
`Initialization skipped (no git root detected): ${state.cwd}`,
|
|
273
|
+
);
|
|
274
|
+
state.initialized = true;
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
253
278
|
ensureDirectories(state.cwd);
|
|
254
279
|
|
|
255
280
|
// Sync MCP server configs across assistants (FS only, fast)
|
|
@@ -263,7 +288,10 @@ function initializeAide(state: AideState): void {
|
|
|
263
288
|
cleanupStaleStateFiles(state.cwd);
|
|
264
289
|
resetHudState(state.cwd);
|
|
265
290
|
|
|
266
|
-
state.binary = findAideBinary({
|
|
291
|
+
state.binary = findAideBinary({
|
|
292
|
+
cwd: state.cwd,
|
|
293
|
+
pluginRoot: state.pluginRoot || undefined,
|
|
294
|
+
});
|
|
267
295
|
|
|
268
296
|
if (state.binary) {
|
|
269
297
|
const projectName = getProjectName(state.cwd);
|
package/src/opencode/index.ts
CHANGED
|
@@ -8,6 +8,13 @@
|
|
|
8
8
|
* - As a local plugin: copy/symlink to .opencode/plugins/
|
|
9
9
|
* - As an npm package: add "@jmylchreest/aide-plugin" to opencode.json
|
|
10
10
|
*
|
|
11
|
+
* Environment variables:
|
|
12
|
+
* AIDE_FORCE_INIT=1 Force aide initialization even in non-git directories.
|
|
13
|
+
* By default, aide skips initialization when no .git/ or
|
|
14
|
+
* .aide/ directory is found. Set this in the MCP server's
|
|
15
|
+
* "environment" config to always create .aide/ in the
|
|
16
|
+
* working directory.
|
|
17
|
+
*
|
|
11
18
|
* @example opencode.json
|
|
12
19
|
* ```json
|
|
13
20
|
* {
|
|
@@ -17,7 +24,11 @@
|
|
|
17
24
|
* "aide": {
|
|
18
25
|
* "type": "local",
|
|
19
26
|
* "command": ["bunx", "-y", "@jmylchreest/aide-plugin", "mcp"],
|
|
20
|
-
* "environment": {
|
|
27
|
+
* "environment": {
|
|
28
|
+
* "AIDE_CODE_WATCH": "1",
|
|
29
|
+
* "AIDE_CODE_WATCH_DELAY": "30s",
|
|
30
|
+
* "AIDE_FORCE_INIT": "1"
|
|
31
|
+
* },
|
|
21
32
|
* "enabled": true
|
|
22
33
|
* }
|
|
23
34
|
* }
|
|
@@ -25,8 +36,9 @@
|
|
|
25
36
|
* ```
|
|
26
37
|
*/
|
|
27
38
|
|
|
28
|
-
import { dirname, join } from "path";
|
|
39
|
+
import { dirname, join, resolve } from "path";
|
|
29
40
|
import { fileURLToPath } from "url";
|
|
41
|
+
import { existsSync, statSync } from "fs";
|
|
30
42
|
import { createHooks } from "./hooks.js";
|
|
31
43
|
import type { Plugin, PluginInput, Hooks } from "./types.js";
|
|
32
44
|
|
|
@@ -37,9 +49,132 @@ const __dirname = dirname(__filename);
|
|
|
37
49
|
// index.ts lives in src/opencode/, so the package root is two levels up.
|
|
38
50
|
const pluginRoot = join(__dirname, "..", "..");
|
|
39
51
|
|
|
52
|
+
if (!process.env.AIDE_PLUGIN_ROOT) {
|
|
53
|
+
process.env.AIDE_PLUGIN_ROOT = pluginRoot;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Resolve the project root directory from the OpenCode plugin context.
|
|
58
|
+
*
|
|
59
|
+
* OpenCode provides three directory-related values:
|
|
60
|
+
* ctx.worktree — `git rev-parse --show-toplevel` (sandbox root, "/" for non-git)
|
|
61
|
+
* ctx.directory — `process.cwd()` or `--dir` (where OpenCode was invoked)
|
|
62
|
+
* ctx.project.worktree — `dirname(git rev-parse --git-common-dir)` (main repo root)
|
|
63
|
+
*
|
|
64
|
+
* Priority:
|
|
65
|
+
* 1. ctx.worktree — the git sandbox root (correct for both normal repos and worktrees)
|
|
66
|
+
* 2. ctx.directory — where OpenCode was invoked (fallback for non-git)
|
|
67
|
+
*
|
|
68
|
+
* Both ctx.worktree and ctx.directory are "/" for non-git projects, so we
|
|
69
|
+
* detect that case and skip initialization.
|
|
70
|
+
*/
|
|
71
|
+
function resolveProjectRoot(ctx: PluginInput): {
|
|
72
|
+
root: string;
|
|
73
|
+
hasProjectRoot: boolean;
|
|
74
|
+
} {
|
|
75
|
+
// ctx.worktree is the git working tree root (from `git rev-parse --show-toplevel`).
|
|
76
|
+
// For non-git projects, OpenCode sets this to "/".
|
|
77
|
+
const worktree = ctx.worktree;
|
|
78
|
+
const directory = ctx.directory;
|
|
79
|
+
|
|
80
|
+
// OpenCode sets worktree to "/" for non-git projects — treat as no project.
|
|
81
|
+
// Also guard against empty strings.
|
|
82
|
+
const isNonGitWorktree = !worktree || worktree === "/";
|
|
83
|
+
|
|
84
|
+
if (!isNonGitWorktree) {
|
|
85
|
+
// The worktree is a valid git root — use it directly.
|
|
86
|
+
// No need to walk up the filesystem; OpenCode already resolved it.
|
|
87
|
+
return { root: resolve(worktree), hasProjectRoot: true };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Worktree is "/" (non-git) — try to find a project root from the directory.
|
|
91
|
+
// This handles the case where the user is in a git repo but OpenCode's
|
|
92
|
+
// resolution somehow failed, or where .aide/ already exists.
|
|
93
|
+
if (directory && directory !== "/") {
|
|
94
|
+
const resolved = walkUpForProjectRoot(directory);
|
|
95
|
+
if (resolved) {
|
|
96
|
+
return { root: resolved, hasProjectRoot: true };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// No git root found anywhere.
|
|
101
|
+
// If AIDE_FORCE_INIT is set, treat the directory as the project root anyway.
|
|
102
|
+
const forceInit = !!process.env.AIDE_FORCE_INIT;
|
|
103
|
+
if (forceInit && directory && directory !== "/") {
|
|
104
|
+
return { root: resolve(directory), hasProjectRoot: true };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return { root: directory || "/", hasProjectRoot: false };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Walk up from `startDir` looking for .aide/ or .git/ directories.
|
|
112
|
+
* Returns the project root path, or null if none found.
|
|
113
|
+
*/
|
|
114
|
+
function walkUpForProjectRoot(startDir: string): string | null {
|
|
115
|
+
let dir = resolve(startDir);
|
|
116
|
+
for (;;) {
|
|
117
|
+
if (existsSync(join(dir, ".aide"))) {
|
|
118
|
+
return dir;
|
|
119
|
+
}
|
|
120
|
+
const gitPath = join(dir, ".git");
|
|
121
|
+
if (existsSync(gitPath)) {
|
|
122
|
+
try {
|
|
123
|
+
const stat = statSync(gitPath);
|
|
124
|
+
// .git can be a directory (normal repo) or a file (worktree pointer)
|
|
125
|
+
if (stat.isDirectory() || stat.isFile()) {
|
|
126
|
+
return dir;
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
return dir;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const parent = resolve(dir, "..");
|
|
133
|
+
if (parent === dir) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
dir = parent;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
40
140
|
export const AidePlugin: Plugin = async (ctx: PluginInput): Promise<Hooks> => {
|
|
41
|
-
|
|
42
|
-
|
|
141
|
+
// Log raw plugin input BEFORE any resolution for diagnostics.
|
|
142
|
+
// This is the key to understanding what OpenCode actually passes.
|
|
143
|
+
const rawLog = [
|
|
144
|
+
`aide plugin init (raw ctx):`,
|
|
145
|
+
` ctx.directory = ${JSON.stringify(ctx.directory)}`,
|
|
146
|
+
` ctx.worktree = ${JSON.stringify(ctx.worktree)}`,
|
|
147
|
+
` ctx.project = ${JSON.stringify(ctx.project, null, 2)?.split("\n").join("\n ")}`,
|
|
148
|
+
].join("\n");
|
|
149
|
+
|
|
150
|
+
// Best-effort log to OpenCode's log system
|
|
151
|
+
try {
|
|
152
|
+
await ctx.client.app.log({
|
|
153
|
+
body: { service: "aide", level: "info", message: rawLog },
|
|
154
|
+
});
|
|
155
|
+
} catch {
|
|
156
|
+
// Plugin may be called before the client is fully ready
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Also log to stderr for direct observability
|
|
160
|
+
console.error(rawLog);
|
|
161
|
+
|
|
162
|
+
const resolved = resolveProjectRoot(ctx);
|
|
163
|
+
|
|
164
|
+
const forceInit = !!process.env.AIDE_FORCE_INIT;
|
|
165
|
+
const resolvedLog = `aide plugin resolved: root=${resolved.root} hasProjectRoot=${resolved.hasProjectRoot}${forceInit ? " (AIDE_FORCE_INIT=1)" : ""}`;
|
|
166
|
+
try {
|
|
167
|
+
await ctx.client.app.log({
|
|
168
|
+
body: { service: "aide", level: "info", message: resolvedLog },
|
|
169
|
+
});
|
|
170
|
+
} catch {
|
|
171
|
+
// non-fatal
|
|
172
|
+
}
|
|
173
|
+
console.error(resolvedLog);
|
|
174
|
+
|
|
175
|
+
return createHooks(resolved.root, ctx.worktree, ctx.client, pluginRoot, {
|
|
176
|
+
skipInit: !resolved.hasProjectRoot,
|
|
177
|
+
});
|
|
43
178
|
};
|
|
44
179
|
|
|
45
180
|
export default AidePlugin;
|
package/src/opencode/types.ts
CHANGED
|
@@ -13,14 +13,58 @@
|
|
|
13
13
|
// Plugin Input (provided by OpenCode on plugin init)
|
|
14
14
|
// =============================================================================
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Project info as provided by OpenCode.
|
|
18
|
+
*
|
|
19
|
+
* Mirrors the Project.Info shape from sst/opencode:
|
|
20
|
+
* packages/opencode/src/project/project.ts
|
|
21
|
+
*
|
|
22
|
+
* Key fields:
|
|
23
|
+
* - id: root commit hash (or "global" for non-git)
|
|
24
|
+
* - worktree: dirname(git rev-parse --git-common-dir) — main repo root
|
|
25
|
+
* - vcs: "git" | undefined
|
|
26
|
+
* - sandboxes: list of active sandboxes / worktrees
|
|
27
|
+
*/
|
|
28
|
+
export interface OpenCodeProject {
|
|
29
|
+
id: string;
|
|
30
|
+
/** Main repository root (git common dir parent). NOT the sandbox/worktree. */
|
|
31
|
+
worktree: string;
|
|
32
|
+
/** Version control system type */
|
|
33
|
+
vcs?: string;
|
|
34
|
+
/** Optional display name */
|
|
35
|
+
name?: string;
|
|
36
|
+
/** Optional icon */
|
|
37
|
+
icon?: string;
|
|
38
|
+
/** Active sandboxes (worktree paths) */
|
|
39
|
+
sandboxes?: string[];
|
|
40
|
+
/** Timestamps */
|
|
41
|
+
time?: { created: number; updated: number };
|
|
42
|
+
/** Allow additional fields from future OpenCode versions */
|
|
43
|
+
[key: string]: unknown;
|
|
44
|
+
}
|
|
45
|
+
|
|
16
46
|
export interface PluginInput {
|
|
17
47
|
/** OpenCode SDK client for API interactions */
|
|
18
48
|
client: OpenCodeClient;
|
|
19
|
-
/**
|
|
20
|
-
|
|
21
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Current project information.
|
|
51
|
+
*
|
|
52
|
+
* NOTE: This is a full Project.Info from OpenCode, NOT a minimal
|
|
53
|
+
* { name, directory } object. The key field for project root is
|
|
54
|
+
* `project.worktree` (the git common dir parent), not
|
|
55
|
+
* `project.directory` (which does not exist in OpenCode's type).
|
|
56
|
+
*/
|
|
57
|
+
project: OpenCodeProject;
|
|
58
|
+
/**
|
|
59
|
+
* The directory OpenCode was invoked from.
|
|
60
|
+
* Typically process.cwd() or the --dir argument.
|
|
61
|
+
*/
|
|
22
62
|
directory: string;
|
|
23
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* Git sandbox / working tree root.
|
|
65
|
+
* Result of `git rev-parse --show-toplevel` from the cwd.
|
|
66
|
+
* For non-git projects this is "/".
|
|
67
|
+
*/
|
|
24
68
|
worktree: string;
|
|
25
69
|
/** URL of the running OpenCode server */
|
|
26
70
|
serverUrl: URL;
|