@jmylchreest/aide-plugin 0.0.43 → 0.0.45
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/survey/SKILL.md +174 -0
- package/src/core/comment-checker.ts +52 -26
- package/src/lib/aide-downloader.ts +1 -2
- package/src/opencode/index.ts +74 -13
package/package.json
CHANGED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: survey
|
|
3
|
+
description: Explore codebase structure, entry points, tech stack, hotspots, and call graphs
|
|
4
|
+
triggers:
|
|
5
|
+
- survey
|
|
6
|
+
- codebase structure
|
|
7
|
+
- what is this codebase
|
|
8
|
+
- tech stack
|
|
9
|
+
- entry points
|
|
10
|
+
- entrypoints
|
|
11
|
+
- what modules
|
|
12
|
+
- what packages
|
|
13
|
+
- code churn
|
|
14
|
+
- hotspots
|
|
15
|
+
- call graph
|
|
16
|
+
- who calls
|
|
17
|
+
- what calls
|
|
18
|
+
- orient me
|
|
19
|
+
- onboard
|
|
20
|
+
- codebase overview
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
# Codebase Survey
|
|
24
|
+
|
|
25
|
+
**Recommended model tier:** balanced (sonnet) - this skill performs structured queries
|
|
26
|
+
|
|
27
|
+
Understand the structure, technology, entry points, and change hotspots of a codebase.
|
|
28
|
+
Survey describes WHAT the codebase IS — not code problems (use `findings` for that).
|
|
29
|
+
|
|
30
|
+
## Available Tools
|
|
31
|
+
|
|
32
|
+
### 1. Survey Stats (`mcp__plugin_aide_aide__survey_stats`)
|
|
33
|
+
|
|
34
|
+
**Start here.** Get an overview of what has been surveyed: total entries, breakdown by analyzer and kind.
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
Is the codebase surveyed?
|
|
38
|
+
→ Uses survey_stats
|
|
39
|
+
→ Returns: counts by analyzer (topology, entrypoints, churn) and kind
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 2. Survey Run (`mcp__plugin_aide_aide__survey_run`)
|
|
43
|
+
|
|
44
|
+
Run analyzers to populate survey data. Three analyzers available:
|
|
45
|
+
|
|
46
|
+
- **topology** — Modules, packages, workspaces, build systems, tech stack detection
|
|
47
|
+
- **entrypoints** — main() functions, HTTP handlers, gRPC services, CLI roots (cobra/urfave). Uses code index when available; falls back to file scanning
|
|
48
|
+
- **churn** — Git history hotspots (files/dirs that change most often)
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
Survey this codebase
|
|
52
|
+
→ Uses survey_run (no analyzer param = run all)
|
|
53
|
+
→ Returns: entry counts per analyzer
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 3. Survey List (`mcp__plugin_aide_aide__survey_list`)
|
|
57
|
+
|
|
58
|
+
Browse entries filtered by analyzer, kind, or file path. No search query needed.
|
|
59
|
+
|
|
60
|
+
**Kinds:** module, entrypoint, dependency, tech_stack, churn, submodule, workspace, arch_pattern
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
What modules are in this codebase?
|
|
64
|
+
→ Uses survey_list with kind=module
|
|
65
|
+
→ Returns: all module entries
|
|
66
|
+
|
|
67
|
+
What technologies does this use?
|
|
68
|
+
→ Uses survey_list with kind=tech_stack
|
|
69
|
+
→ Returns: detected frameworks, languages, build systems
|
|
70
|
+
|
|
71
|
+
What files change most?
|
|
72
|
+
→ Uses survey_list with kind=churn
|
|
73
|
+
→ Returns: high-churn files ranked by commit count
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 4. Survey Search (`mcp__plugin_aide_aide__survey_search`)
|
|
77
|
+
|
|
78
|
+
Full-text search across entry names, titles, and details. Use when looking for specific modules or technologies.
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
Find anything related to "auth"
|
|
82
|
+
→ Uses survey_search with query="auth"
|
|
83
|
+
→ Returns: modules, entrypoints, churn entries matching "auth"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 5. Call Graph (`mcp__plugin_aide_aide__survey_graph`)
|
|
87
|
+
|
|
88
|
+
Build a call graph for a symbol showing callers, callees, or both. BFS traversal over the code index.
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
Who calls BuildCallGraph?
|
|
92
|
+
→ Uses survey_graph with symbol="BuildCallGraph" direction="callers"
|
|
93
|
+
→ Returns: graph of calling symbols with file:line locations
|
|
94
|
+
|
|
95
|
+
What does handleSurveyRun call?
|
|
96
|
+
→ Uses survey_graph with symbol="handleSurveyRun" direction="callees"
|
|
97
|
+
→ Returns: graph of called symbols
|
|
98
|
+
|
|
99
|
+
Show call neighborhood of RunTopology
|
|
100
|
+
→ Uses survey_graph with symbol="RunTopology" direction="both"
|
|
101
|
+
→ Returns: both callers and callees
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Parameters:**
|
|
105
|
+
|
|
106
|
+
- `symbol` (required): Function/method name
|
|
107
|
+
- `direction`: "both" (default), "callers", "callees"
|
|
108
|
+
- `max_depth`: BFS hops (default 2)
|
|
109
|
+
- `max_nodes`: Max nodes (default 50)
|
|
110
|
+
|
|
111
|
+
**Requires:** Code index must be populated (`aide code index`).
|
|
112
|
+
|
|
113
|
+
## Workflow
|
|
114
|
+
|
|
115
|
+
### Orienting in an unfamiliar codebase
|
|
116
|
+
|
|
117
|
+
1. **Check survey status:**
|
|
118
|
+
- Use `survey_stats` to see if data exists
|
|
119
|
+
- If empty, run `survey_run` to populate
|
|
120
|
+
|
|
121
|
+
2. **Understand the structure:**
|
|
122
|
+
- `survey_list kind=module` — What are the major modules?
|
|
123
|
+
- `survey_list kind=tech_stack` — What technologies are used?
|
|
124
|
+
- `survey_list kind=workspace` — Is this a monorepo?
|
|
125
|
+
|
|
126
|
+
3. **Find entry points:**
|
|
127
|
+
- `survey_list kind=entrypoint` — Where does execution start?
|
|
128
|
+
- Identifies main() functions, HTTP handlers, CLI roots
|
|
129
|
+
|
|
130
|
+
4. **Identify hotspots:**
|
|
131
|
+
- `survey_list kind=churn` — What files change most? (complexity/bug magnets)
|
|
132
|
+
|
|
133
|
+
5. **Trace call relationships:**
|
|
134
|
+
- `survey_graph symbol="handleRequest"` — Map the call neighborhood
|
|
135
|
+
- Use `direction=callers` to find who invokes a function
|
|
136
|
+
- Use `direction=callees` to understand what a function depends on
|
|
137
|
+
|
|
138
|
+
### Answering specific questions
|
|
139
|
+
|
|
140
|
+
| Question | Tool | Parameters |
|
|
141
|
+
| ----------------------------- | --------------- | --------------------------- |
|
|
142
|
+
| "What is this codebase?" | `survey_list` | kind=module |
|
|
143
|
+
| "What tech stack?" | `survey_list` | kind=tech_stack |
|
|
144
|
+
| "Where are the entry points?" | `survey_list` | kind=entrypoint |
|
|
145
|
+
| "What changes most?" | `survey_list` | kind=churn |
|
|
146
|
+
| "Is there an auth module?" | `survey_search` | query="auth" |
|
|
147
|
+
| "Who calls this function?" | `survey_graph` | symbol=X, direction=callers |
|
|
148
|
+
| "What does this call?" | `survey_graph` | symbol=X, direction=callees |
|
|
149
|
+
|
|
150
|
+
## Survey vs Findings vs Code Search
|
|
151
|
+
|
|
152
|
+
| Tool | Purpose | Example |
|
|
153
|
+
| --------------- | -------------------- | ---------------------------------------- |
|
|
154
|
+
| **Survey** | WHAT the codebase IS | Modules, tech stack, entry points, churn |
|
|
155
|
+
| **Findings** | Code PROBLEMS | Complexity, security issues, duplication |
|
|
156
|
+
| **Code Search** | Symbol DEFINITIONS | Find function signatures, call sites |
|
|
157
|
+
|
|
158
|
+
Survey gives you the big picture. Code search gives you specific symbols. Findings gives you problems to fix.
|
|
159
|
+
|
|
160
|
+
## Prerequisites
|
|
161
|
+
|
|
162
|
+
- **Survey data:** Run `aide survey run` or use `survey_run` tool
|
|
163
|
+
- **Code index (for entrypoints + graph):** Run `aide code index`
|
|
164
|
+
- **Git history (for churn):** Must be a git repository (uses go-git, no git binary needed)
|
|
165
|
+
|
|
166
|
+
**Binary location:** The aide binary is at `.aide/bin/aide`. If it's on your `$PATH`, you can use `aide` directly.
|
|
167
|
+
|
|
168
|
+
## Notes
|
|
169
|
+
|
|
170
|
+
- Survey results are cached in BoltDB — re-run analyzers to refresh after significant changes
|
|
171
|
+
- Topology analyzer inspects the filesystem (build files, directory structure)
|
|
172
|
+
- Entrypoints analyzer uses the code index when available; falls back to file scanning
|
|
173
|
+
- Churn analyzer uses go-git to read git history directly (no git binary required)
|
|
174
|
+
- Call graph is computed on demand from the code index (not stored)
|
|
@@ -126,6 +126,48 @@ function isObviousComment(line: string): boolean {
|
|
|
126
126
|
return OBVIOUS_PATTERNS.some((p) => p.test(line));
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Track /* ... * / block comment state.
|
|
131
|
+
* Returns [shouldSkip, newInBlockState].
|
|
132
|
+
*/
|
|
133
|
+
function isInBlockComment(
|
|
134
|
+
trimmed: string,
|
|
135
|
+
inBlock: boolean,
|
|
136
|
+
): [boolean, boolean] {
|
|
137
|
+
if (trimmed.startsWith("/*")) {
|
|
138
|
+
// Single-line block comments: /* ... */
|
|
139
|
+
const closed = trimmed.includes("*/");
|
|
140
|
+
return [true, !closed];
|
|
141
|
+
}
|
|
142
|
+
if (inBlock) {
|
|
143
|
+
const closed = trimmed.includes("*/");
|
|
144
|
+
return [true, !closed];
|
|
145
|
+
}
|
|
146
|
+
return [false, false];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Track Python/Ruby docstring state (triple quotes).
|
|
151
|
+
* Returns [shouldSkip, newInDocstringState].
|
|
152
|
+
*/
|
|
153
|
+
function isInDocstring(
|
|
154
|
+
trimmed: string,
|
|
155
|
+
ext: string,
|
|
156
|
+
inDocstring: boolean,
|
|
157
|
+
): [boolean, boolean] {
|
|
158
|
+
if (ext !== ".py" && ext !== ".rb") {
|
|
159
|
+
return [false, inDocstring];
|
|
160
|
+
}
|
|
161
|
+
const tripleQuoteCount = (trimmed.match(/"""|'''/g) || []).length;
|
|
162
|
+
if (tripleQuoteCount === 1) {
|
|
163
|
+
return [true, !inDocstring];
|
|
164
|
+
}
|
|
165
|
+
if (inDocstring) {
|
|
166
|
+
return [true, inDocstring];
|
|
167
|
+
}
|
|
168
|
+
return [false, inDocstring];
|
|
169
|
+
}
|
|
170
|
+
|
|
129
171
|
/**
|
|
130
172
|
* Extract comment lines from code content.
|
|
131
173
|
* Returns only single-line comments (// or #), not block comments or docstrings.
|
|
@@ -137,39 +179,23 @@ function extractCommentLines(
|
|
|
137
179
|
const lines = content.split("\n");
|
|
138
180
|
const comments: { line: string; lineNumber: number }[] = [];
|
|
139
181
|
const usesHash = HASH_COMMENT_EXTENSIONS.has(ext);
|
|
140
|
-
let
|
|
141
|
-
let
|
|
182
|
+
let inBlock = false;
|
|
183
|
+
let inDoc = false;
|
|
142
184
|
|
|
143
185
|
for (let i = 0; i < lines.length; i++) {
|
|
144
186
|
const trimmed = lines[i].trim();
|
|
145
187
|
|
|
146
|
-
// Track block comments (/* ... */)
|
|
147
|
-
if (!
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if (trimmed.includes("*/")) {
|
|
152
|
-
inBlockComment = false;
|
|
153
|
-
}
|
|
154
|
-
continue;
|
|
155
|
-
}
|
|
156
|
-
if (inBlockComment) {
|
|
157
|
-
if (trimmed.includes("*/")) {
|
|
158
|
-
inBlockComment = false;
|
|
159
|
-
}
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
188
|
+
// Track block comments (/* ... */), but not inside docstrings
|
|
189
|
+
if (!inDoc) {
|
|
190
|
+
const [skipBlock, newBlock] = isInBlockComment(trimmed, inBlock);
|
|
191
|
+
inBlock = newBlock;
|
|
192
|
+
if (skipBlock) continue;
|
|
162
193
|
}
|
|
163
194
|
|
|
164
195
|
// Track Python/Ruby docstrings
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
inDocstring = !inDocstring;
|
|
169
|
-
continue;
|
|
170
|
-
}
|
|
171
|
-
if (inDocstring) continue;
|
|
172
|
-
}
|
|
196
|
+
const [skipDoc, newDoc] = isInDocstring(trimmed, ext, inDoc);
|
|
197
|
+
inDoc = newDoc;
|
|
198
|
+
if (skipDoc) continue;
|
|
173
199
|
|
|
174
200
|
// Single-line comments
|
|
175
201
|
if (trimmed.startsWith("//")) {
|
|
@@ -351,7 +351,7 @@ export async function ensureAideBinary(cwd?: string): Promise<EnsureResult> {
|
|
|
351
351
|
|
|
352
352
|
// Step 1: Check for existing binary
|
|
353
353
|
let binaryPath = findAideBinary(cwd);
|
|
354
|
-
|
|
354
|
+
const binaryVersion = binaryPath ? getBinaryVersion(binaryPath) : null;
|
|
355
355
|
|
|
356
356
|
// Step 2: Check version match
|
|
357
357
|
// Skip download if the binary is a dev build with base version >= plugin version
|
|
@@ -407,7 +407,6 @@ export async function ensureAideBinary(cwd?: string): Promise<EnsureResult> {
|
|
|
407
407
|
|
|
408
408
|
if (result.success && result.path) {
|
|
409
409
|
binaryPath = result.path;
|
|
410
|
-
binaryVersion = getBinaryVersion(result.path);
|
|
411
410
|
downloaded = true;
|
|
412
411
|
} else {
|
|
413
412
|
// Download failed - return error with manual instructions
|
package/src/opencode/index.ts
CHANGED
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
* ```
|
|
37
37
|
*/
|
|
38
38
|
|
|
39
|
-
import { dirname, join, resolve } from "path";
|
|
39
|
+
import { basename, dirname, join, resolve } from "path";
|
|
40
40
|
import { fileURLToPath } from "url";
|
|
41
|
-
import { existsSync, statSync } from "fs";
|
|
41
|
+
import { existsSync, readFileSync, statSync } from "fs";
|
|
42
42
|
import { createHooks } from "./hooks.js";
|
|
43
43
|
import type { Plugin, PluginInput, Hooks } from "./types.js";
|
|
44
44
|
|
|
@@ -62,8 +62,16 @@ if (!process.env.AIDE_PLUGIN_ROOT) {
|
|
|
62
62
|
* ctx.project.worktree — `dirname(git rev-parse --git-common-dir)` (main repo root)
|
|
63
63
|
*
|
|
64
64
|
* Priority:
|
|
65
|
-
* 1. ctx.worktree — the
|
|
66
|
-
* 2. ctx.
|
|
65
|
+
* 1. ctx.project.worktree — the main repo root (shared across all worktrees)
|
|
66
|
+
* 2. ctx.worktree — the git sandbox root (fallback if project.worktree missing)
|
|
67
|
+
* 3. ctx.directory — where OpenCode was invoked (fallback for non-git)
|
|
68
|
+
*
|
|
69
|
+
* IMPORTANT: We use ctx.project.worktree (git common dir parent) rather than
|
|
70
|
+
* ctx.worktree (show-toplevel) so that all git worktrees resolve to the SAME
|
|
71
|
+
* main repository root. This matches the Go binary's findProjectRoot() which
|
|
72
|
+
* follows .git worktree pointers to the main repo. Without this, each worktree
|
|
73
|
+
* would create its own .aide/ directory while the Go binary opens the database
|
|
74
|
+
* in the main repo's .aide/, causing BoltDB/SQLite lock contention.
|
|
67
75
|
*
|
|
68
76
|
* Both ctx.worktree and ctx.directory are "/" for non-git projects, so we
|
|
69
77
|
* detect that case and skip initialization.
|
|
@@ -72,18 +80,23 @@ function resolveProjectRoot(ctx: PluginInput): {
|
|
|
72
80
|
root: string;
|
|
73
81
|
hasProjectRoot: boolean;
|
|
74
82
|
} {
|
|
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
83
|
const directory = ctx.directory;
|
|
79
84
|
|
|
80
|
-
//
|
|
81
|
-
//
|
|
85
|
+
// Prefer ctx.project.worktree — this is dirname(git rev-parse --git-common-dir),
|
|
86
|
+
// i.e. the main repo root that is shared across all worktrees.
|
|
87
|
+
// This matches the Go binary's resolveWorktreeRoot() behavior.
|
|
88
|
+
const projectWorktree = ctx.project?.worktree;
|
|
89
|
+
if (projectWorktree && projectWorktree !== "/") {
|
|
90
|
+
return { root: resolve(projectWorktree), hasProjectRoot: true };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Fallback: ctx.worktree is git rev-parse --show-toplevel (per-worktree root).
|
|
94
|
+
// For normal (non-worktree) repos, this equals the main repo root anyway.
|
|
95
|
+
// For non-git projects, OpenCode sets this to "/".
|
|
96
|
+
const worktree = ctx.worktree;
|
|
82
97
|
const isNonGitWorktree = !worktree || worktree === "/";
|
|
83
98
|
|
|
84
99
|
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
100
|
return { root: resolve(worktree), hasProjectRoot: true };
|
|
88
101
|
}
|
|
89
102
|
|
|
@@ -110,6 +123,10 @@ function resolveProjectRoot(ctx: PluginInput): {
|
|
|
110
123
|
/**
|
|
111
124
|
* Walk up from `startDir` looking for .aide/ or .git/ directories.
|
|
112
125
|
* Returns the project root path, or null if none found.
|
|
126
|
+
*
|
|
127
|
+
* For git worktrees, .git is a file containing "gitdir: <path>".
|
|
128
|
+
* We follow it to the main repo root, matching the Go binary's
|
|
129
|
+
* resolveWorktreeRoot() behavior.
|
|
113
130
|
*/
|
|
114
131
|
function walkUpForProjectRoot(startDir: string): string | null {
|
|
115
132
|
let dir = resolve(startDir);
|
|
@@ -121,8 +138,16 @@ function walkUpForProjectRoot(startDir: string): string | null {
|
|
|
121
138
|
if (existsSync(gitPath)) {
|
|
122
139
|
try {
|
|
123
140
|
const stat = statSync(gitPath);
|
|
124
|
-
|
|
125
|
-
|
|
141
|
+
if (stat.isDirectory()) {
|
|
142
|
+
// Normal git repo
|
|
143
|
+
return dir;
|
|
144
|
+
}
|
|
145
|
+
if (stat.isFile()) {
|
|
146
|
+
// Worktree: .git is a file containing "gitdir: <path>"
|
|
147
|
+
// Follow it to the main repo root.
|
|
148
|
+
const mainRoot = resolveWorktreeGitFile(gitPath);
|
|
149
|
+
if (mainRoot) return mainRoot;
|
|
150
|
+
// Fallback to current dir if resolution fails
|
|
126
151
|
return dir;
|
|
127
152
|
}
|
|
128
153
|
} catch {
|
|
@@ -137,6 +162,42 @@ function walkUpForProjectRoot(startDir: string): string | null {
|
|
|
137
162
|
}
|
|
138
163
|
}
|
|
139
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Read a .git worktree file and resolve to the main repository root.
|
|
167
|
+
* Mirrors the Go binary's resolveWorktreeRoot() in main.go.
|
|
168
|
+
*
|
|
169
|
+
* The file contains "gitdir: /path/to/repo/.git/worktrees/<name>".
|
|
170
|
+
* We walk up from that gitdir path to find the .git directory,
|
|
171
|
+
* then return its parent.
|
|
172
|
+
*/
|
|
173
|
+
function resolveWorktreeGitFile(gitFilePath: string): string | null {
|
|
174
|
+
try {
|
|
175
|
+
const content = readFileSync(gitFilePath, "utf-8").trim();
|
|
176
|
+
if (!content.startsWith("gitdir:")) return null;
|
|
177
|
+
|
|
178
|
+
let gitdir = content.slice("gitdir:".length).trim();
|
|
179
|
+
// Make absolute if relative
|
|
180
|
+
if (!gitdir.startsWith("/")) {
|
|
181
|
+
gitdir = resolve(dirname(gitFilePath), gitdir);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Walk up from .git/worktrees/<name> to find the .git directory,
|
|
185
|
+
// then return its parent as the repo root.
|
|
186
|
+
let candidate = gitdir;
|
|
187
|
+
for (;;) {
|
|
188
|
+
const parent = dirname(candidate);
|
|
189
|
+
if (parent === candidate) break;
|
|
190
|
+
if (basename(candidate) === ".git") {
|
|
191
|
+
return parent;
|
|
192
|
+
}
|
|
193
|
+
candidate = parent;
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
} catch {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
140
201
|
export const AidePlugin: Plugin = async (ctx: PluginInput): Promise<Hooks> => {
|
|
141
202
|
// Log raw plugin input BEFORE any resolution for diagnostics.
|
|
142
203
|
// This is the key to understanding what OpenCode actually passes.
|