autodocs-engine 0.9.8 → 0.10.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/README.md +123 -117
- package/dist/anti-pattern-detector.js +22 -0
- package/dist/anti-pattern-detector.js.map +1 -1
- package/dist/architecture-detector.js +33 -29
- package/dist/architecture-detector.js.map +1 -1
- package/dist/ast-parser.js +31 -3
- package/dist/ast-parser.js.map +1 -1
- package/dist/benchmark/scorer.js +3 -1
- package/dist/benchmark/scorer.js.map +1 -1
- package/dist/bin/autodocs-engine.js +7 -0
- package/dist/bin/autodocs-engine.js.map +1 -1
- package/dist/bin/serve.d.ts +1 -0
- package/dist/bin/serve.js +5 -1
- package/dist/bin/serve.js.map +1 -1
- package/dist/bin/setup-hooks.d.ts +1 -0
- package/dist/bin/setup-hooks.js +59 -0
- package/dist/bin/setup-hooks.js.map +1 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.js +4 -0
- package/dist/config.js.map +1 -1
- package/dist/convention-extractor.js +10 -0
- package/dist/convention-extractor.js.map +1 -1
- package/dist/detectors/api-patterns.d.ts +2 -0
- package/dist/detectors/api-patterns.js +101 -0
- package/dist/detectors/api-patterns.js.map +1 -0
- package/dist/detectors/async-patterns.d.ts +2 -0
- package/dist/detectors/async-patterns.js +79 -0
- package/dist/detectors/async-patterns.js.map +1 -0
- package/dist/detectors/error-handling.js +82 -30
- package/dist/detectors/error-handling.js.map +1 -1
- package/dist/detectors/state-management.d.ts +2 -0
- package/dist/detectors/state-management.js +75 -0
- package/dist/detectors/state-management.js.map +1 -0
- package/dist/execution-flow.d.ts +23 -0
- package/dist/execution-flow.js +286 -0
- package/dist/execution-flow.js.map +1 -0
- package/dist/git-history.js +18 -7
- package/dist/git-history.js.map +1 -1
- package/dist/implicit-coupling.d.ts +7 -0
- package/dist/implicit-coupling.js +40 -0
- package/dist/implicit-coupling.js.map +1 -0
- package/dist/import-chain.js +12 -3
- package/dist/import-chain.js.map +1 -1
- package/dist/index.js +20 -1
- package/dist/index.js.map +1 -1
- package/dist/llm/serializer.js +8 -6
- package/dist/llm/serializer.js.map +1 -1
- package/dist/mcp/cache.d.ts +11 -1
- package/dist/mcp/cache.js +49 -7
- package/dist/mcp/cache.js.map +1 -1
- package/dist/mcp/queries.d.ts +19 -3
- package/dist/mcp/queries.js +342 -62
- package/dist/mcp/queries.js.map +1 -1
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +42 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools.d.ts +1 -0
- package/dist/mcp/tools.js +162 -20
- package/dist/mcp/tools.js.map +1 -1
- package/dist/output-validator.js +73 -0
- package/dist/output-validator.js.map +1 -1
- package/dist/pipeline.js +37 -0
- package/dist/pipeline.js.map +1 -1
- package/dist/symbol-graph.js +4 -0
- package/dist/symbol-graph.js.map +1 -1
- package/dist/type-enricher.d.ts +17 -0
- package/dist/type-enricher.js +127 -0
- package/dist/type-enricher.js.map +1 -0
- package/dist/type-resolver.d.ts +13 -0
- package/dist/type-resolver.js +75 -0
- package/dist/type-resolver.js.map +1 -0
- package/dist/types.d.ts +37 -4
- package/dist/types.js +7 -2
- package/dist/types.js.map +1 -1
- package/hooks/autodocs-hook.cjs +227 -0
- package/package.json +3 -2
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// hooks/autodocs-hook.cjs — PreToolUse/PostToolUse handler for Claude Code
|
|
2
|
+
// Reads cached analysis from ~/.autodocs/cache/ and augments search results
|
|
3
|
+
// with callers, co-change partners, and execution flows.
|
|
4
|
+
//
|
|
5
|
+
// Design principles:
|
|
6
|
+
// - Silent on any error (never break the original tool)
|
|
7
|
+
// - <100ms response time (reads pre-computed JSON snapshot)
|
|
8
|
+
// - No analysis in hook process (MCP server writes the snapshot)
|
|
9
|
+
// - CommonJS for broad Node.js compatibility
|
|
10
|
+
|
|
11
|
+
const { execFileSync } = require("child_process");
|
|
12
|
+
const fs = require("fs");
|
|
13
|
+
const path = require("path");
|
|
14
|
+
const os = require("os");
|
|
15
|
+
const crypto = require("crypto");
|
|
16
|
+
|
|
17
|
+
// ─── Dispatch ──────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
const handlers = {
|
|
20
|
+
PreToolUse: handlePreToolUse,
|
|
21
|
+
PostToolUse: handlePostToolUse,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function main() {
|
|
25
|
+
try {
|
|
26
|
+
const input = JSON.parse(fs.readFileSync("/dev/stdin", "utf-8"));
|
|
27
|
+
const handler = handlers[input.hook_event_name];
|
|
28
|
+
if (handler) handler(input);
|
|
29
|
+
} catch {
|
|
30
|
+
// Silent — never break the original tool
|
|
31
|
+
if (process.env.AUTODOCS_DEBUG) {
|
|
32
|
+
process.stderr.write("[autodocs-hook] Error in main dispatch\n");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ─── PreToolUse: Augment searches with graph context ───────────────────────
|
|
38
|
+
|
|
39
|
+
function handlePreToolUse(input) {
|
|
40
|
+
const pattern = extractPattern(input.tool_name, input.tool_input);
|
|
41
|
+
if (!pattern || pattern.length < 3) return;
|
|
42
|
+
|
|
43
|
+
const cwd = input.cwd;
|
|
44
|
+
if (!cwd || !path.isAbsolute(cwd)) return;
|
|
45
|
+
|
|
46
|
+
const snapshot = loadSnapshot(cwd);
|
|
47
|
+
if (!snapshot) return;
|
|
48
|
+
|
|
49
|
+
const context = augment(snapshot, pattern);
|
|
50
|
+
if (!context) return;
|
|
51
|
+
|
|
52
|
+
emit("PreToolUse", context);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ─── PostToolUse: Detect index staleness after git mutations ───────────────
|
|
56
|
+
|
|
57
|
+
const GIT_MUTATION_RE = /\bgit\s+(commit|merge|rebase|cherry-pick|pull)(\s|$)/;
|
|
58
|
+
|
|
59
|
+
function handlePostToolUse(input) {
|
|
60
|
+
if (input.tool_name !== "Bash") return;
|
|
61
|
+
|
|
62
|
+
const cmd = input.tool_input?.command || "";
|
|
63
|
+
if (!GIT_MUTATION_RE.test(cmd)) return;
|
|
64
|
+
if (input.tool_output?.exit_code !== 0) return;
|
|
65
|
+
|
|
66
|
+
const cwd = input.cwd;
|
|
67
|
+
if (!cwd || !path.isAbsolute(cwd)) return;
|
|
68
|
+
|
|
69
|
+
const snapshot = loadSnapshot(cwd);
|
|
70
|
+
if (!snapshot) return;
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const head = execFileSync("git", ["rev-parse", "HEAD"], {
|
|
74
|
+
cwd,
|
|
75
|
+
encoding: "utf-8",
|
|
76
|
+
timeout: 3000,
|
|
77
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
78
|
+
}).trim();
|
|
79
|
+
|
|
80
|
+
// Cache key format: "HEAD_HASH:STATUS_HASH"
|
|
81
|
+
const snapshotHead = (snapshot.cacheKey || "").split(":")[0];
|
|
82
|
+
if (snapshotHead === head) return; // Still fresh
|
|
83
|
+
|
|
84
|
+
emit(
|
|
85
|
+
"PostToolUse",
|
|
86
|
+
"[autodocs] Analysis cache is stale after git change. The MCP server will re-analyze automatically on the next tool call.",
|
|
87
|
+
);
|
|
88
|
+
} catch {
|
|
89
|
+
// Silent
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ─── Pattern Extraction ────────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
function extractPattern(toolName, toolInput) {
|
|
96
|
+
if (!toolInput) return null;
|
|
97
|
+
|
|
98
|
+
// Grep: pattern is directly available
|
|
99
|
+
if (toolName === "Grep") {
|
|
100
|
+
return toolInput.pattern || null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Glob: extract meaningful name from glob syntax
|
|
104
|
+
if (toolName === "Glob") {
|
|
105
|
+
const raw = toolInput.pattern || "";
|
|
106
|
+
// "**/*.ts" → skip (too generic). "auth*.ts" → "auth". "**/authenticate/**" → "authenticate"
|
|
107
|
+
const match = raw.match(/[*/]([a-zA-Z][a-zA-Z0-9_-]{2,})/);
|
|
108
|
+
return match ? match[1] : null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Bash: parse grep/rg commands for the search pattern
|
|
112
|
+
if (toolName === "Bash") {
|
|
113
|
+
const cmd = toolInput.command || "";
|
|
114
|
+
if (!/\brg\b|\bgrep\b/.test(cmd)) return null;
|
|
115
|
+
|
|
116
|
+
// Simple token extraction: skip the command name and flags
|
|
117
|
+
const tokens = cmd.split(/\s+/);
|
|
118
|
+
const skipNextValue = new Set(["-e", "-f", "-A", "-B", "-C", "--glob", "--type", "-g", "-t"]);
|
|
119
|
+
let skipNext = false;
|
|
120
|
+
|
|
121
|
+
for (let i = 1; i < tokens.length; i++) {
|
|
122
|
+
if (skipNext) {
|
|
123
|
+
skipNext = false;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const t = tokens[i].replace(/^['"]|['"]$/g, "");
|
|
127
|
+
if (t.startsWith("-")) {
|
|
128
|
+
if (skipNextValue.has(t)) skipNext = true;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (t.length >= 3) return t;
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ─── Snapshot Loading ──────────────────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
function loadSnapshot(cwd) {
|
|
142
|
+
try {
|
|
143
|
+
const hash = crypto.createHash("sha256").update(cwd).digest("hex").slice(0, 12);
|
|
144
|
+
const cacheFile = path.join(os.homedir(), ".autodocs", "cache", `${hash}.json`);
|
|
145
|
+
return JSON.parse(fs.readFileSync(cacheFile, "utf-8"));
|
|
146
|
+
} catch {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ─── Augmentation ──────────────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
function augment(snapshot, pattern) {
|
|
154
|
+
// Find the primary package (largest)
|
|
155
|
+
const pkgs = snapshot.packages || [];
|
|
156
|
+
if (pkgs.length === 0) return null;
|
|
157
|
+
const pkg = pkgs.reduce((a, b) => ((a.publicAPI || []).length > (b.publicAPI || []).length ? a : b));
|
|
158
|
+
|
|
159
|
+
const patternLower = pattern.toLowerCase();
|
|
160
|
+
|
|
161
|
+
// Match against public API symbols
|
|
162
|
+
const matches = (pkg.publicAPI || []).filter((e) => e.name.toLowerCase().includes(patternLower));
|
|
163
|
+
if (matches.length === 0) return null;
|
|
164
|
+
|
|
165
|
+
const results = [];
|
|
166
|
+
for (const exp of matches.slice(0, 3)) {
|
|
167
|
+
const lines = [`**${exp.name}** (${exp.kind}) — \`${exp.sourceFile}\``];
|
|
168
|
+
|
|
169
|
+
// Callers from call graph
|
|
170
|
+
const callers = (pkg.callGraph || [])
|
|
171
|
+
.filter((e) => e.to === exp.name)
|
|
172
|
+
.map((e) => e.from)
|
|
173
|
+
.slice(0, 3);
|
|
174
|
+
if (callers.length > 0) lines.push(` Called by: ${callers.join(", ")}`);
|
|
175
|
+
|
|
176
|
+
// Callees from call graph
|
|
177
|
+
const callees = (pkg.callGraph || [])
|
|
178
|
+
.filter((e) => e.from === exp.name)
|
|
179
|
+
.map((e) => e.to)
|
|
180
|
+
.slice(0, 3);
|
|
181
|
+
if (callees.length > 0) lines.push(` Calls: ${callees.join(", ")}`);
|
|
182
|
+
|
|
183
|
+
// Co-change partners
|
|
184
|
+
const coChanges = (pkg.gitHistory?.coChangeEdges || [])
|
|
185
|
+
.filter((e) => e.file1 === exp.sourceFile || e.file2 === exp.sourceFile)
|
|
186
|
+
.sort((a, b) => b.jaccard - a.jaccard)
|
|
187
|
+
.slice(0, 2)
|
|
188
|
+
.map((e) => {
|
|
189
|
+
const partner = e.file1 === exp.sourceFile ? e.file2 : e.file1;
|
|
190
|
+
return `${partner} (${Math.round(e.jaccard * 100)}%)`;
|
|
191
|
+
});
|
|
192
|
+
if (coChanges.length > 0) lines.push(` Co-changes with: ${coChanges.join(", ")}`);
|
|
193
|
+
|
|
194
|
+
// Execution flows
|
|
195
|
+
const flows = (pkg.executionFlows || [])
|
|
196
|
+
.filter((f) => f.steps.includes(exp.name))
|
|
197
|
+
.slice(0, 2)
|
|
198
|
+
.map((f) => {
|
|
199
|
+
const idx = f.steps.indexOf(exp.name);
|
|
200
|
+
return `${f.label} (step ${idx + 1}/${f.length})`;
|
|
201
|
+
});
|
|
202
|
+
if (flows.length > 0) lines.push(` Flows: ${flows.join("; ")}`);
|
|
203
|
+
|
|
204
|
+
// Import count
|
|
205
|
+
if (exp.importCount > 0) lines.push(` Imported by: ${exp.importCount} files`);
|
|
206
|
+
|
|
207
|
+
results.push(lines.join("\n"));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (results.length === 0) return null;
|
|
211
|
+
return `[autodocs] ${results.length} related symbol${results.length > 1 ? "s" : ""} found:\n\n${results.join("\n\n")}`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ─── Output ────────────────────────────────────────────────────────────────
|
|
215
|
+
|
|
216
|
+
function emit(hookEventName, additionalContext) {
|
|
217
|
+
const response = {
|
|
218
|
+
hookSpecificOutput: {
|
|
219
|
+
hookEventName,
|
|
220
|
+
additionalContext,
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
process.stdout.write(JSON.stringify(response));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ─── Entry Point ───────────────────────────────────────────────────────────
|
|
227
|
+
main();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autodocs-engine",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Codebase intelligence
|
|
3
|
+
"version": "0.10.0",
|
|
4
|
+
"description": "Codebase intelligence for AI coding agents — MCP server with git co-change analysis, convention detection, execution flows, and type-aware analysis",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"dist/",
|
|
13
|
+
"hooks/",
|
|
13
14
|
"README.md",
|
|
14
15
|
"LICENSE"
|
|
15
16
|
],
|