@jmylchreest/aide-plugin 0.0.56 → 0.0.58
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 +5 -2
- package/src/cli/codex-config.ts +428 -0
- package/src/cli/hook.ts +85 -0
- package/src/cli/index.ts +49 -12
- package/src/cli/install.ts +52 -25
- package/src/cli/status.ts +50 -17
- package/src/cli/uninstall.ts +29 -8
- package/src/core/mcp-sync.ts +208 -8
- package/src/core/types.ts +2 -2
- package/src/hooks/agent-cleanup.ts +91 -0
- package/src/hooks/comment-checker.ts +115 -0
- package/src/hooks/context-guard.ts +115 -0
- package/src/hooks/context-pruning.ts +216 -0
- package/src/hooks/hud-updater.ts +180 -0
- package/src/hooks/permission-handler.ts +173 -0
- package/src/hooks/persistence.ts +93 -0
- package/src/hooks/pre-compact.ts +127 -0
- package/src/hooks/pre-tool-enforcer.ts +120 -0
- package/src/hooks/session-end.ts +148 -0
- package/src/hooks/session-start.ts +488 -0
- package/src/hooks/session-summary.ts +147 -0
- package/src/hooks/skill-injector.ts +235 -0
- package/src/hooks/subagent-tracker.ts +525 -0
- package/src/hooks/task-completed.ts +445 -0
- package/src/hooks/tool-tracker.ts +89 -0
- package/src/hooks/write-guard.ts +95 -0
- package/src/lib/hook-utils.ts +53 -1
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Session Summary Hook (Stop)
|
|
4
|
+
*
|
|
5
|
+
* Captures a session summary from the transcript when a session ends.
|
|
6
|
+
* Includes files modified, tools used, user tasks, and git commits.
|
|
7
|
+
*
|
|
8
|
+
* Storage:
|
|
9
|
+
* - Uses `aide memory add` with category=session
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { debug, setDebugCwd } from "../lib/logger.js";
|
|
13
|
+
import { readStdin } from "../lib/hook-utils.js";
|
|
14
|
+
import { findAideBinary } from "../core/aide-client.js";
|
|
15
|
+
import {
|
|
16
|
+
buildSessionSummary,
|
|
17
|
+
getSessionCommits,
|
|
18
|
+
storeSessionSummary,
|
|
19
|
+
} from "../core/session-summary-logic.js";
|
|
20
|
+
import {
|
|
21
|
+
gatherPartials,
|
|
22
|
+
buildSummaryFromPartials,
|
|
23
|
+
cleanupPartials,
|
|
24
|
+
} from "../core/partial-memory.js";
|
|
25
|
+
|
|
26
|
+
const SOURCE = "session-summary";
|
|
27
|
+
|
|
28
|
+
interface HookInput {
|
|
29
|
+
hook_event_name: string;
|
|
30
|
+
session_id: string;
|
|
31
|
+
cwd: string;
|
|
32
|
+
transcript_path?: string;
|
|
33
|
+
stop_hook_active?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Generate and store a final session summary.
|
|
38
|
+
*
|
|
39
|
+
* Uses partials (if available) to enrich the transcript-based summary.
|
|
40
|
+
* After storing the final summary, cleans up all partials for this session
|
|
41
|
+
* by tagging them as "forget".
|
|
42
|
+
*/
|
|
43
|
+
function captureSessionSummary(
|
|
44
|
+
cwd: string,
|
|
45
|
+
sessionId: string,
|
|
46
|
+
transcriptPath: string,
|
|
47
|
+
): boolean {
|
|
48
|
+
const binary = findAideBinary({
|
|
49
|
+
cwd,
|
|
50
|
+
pluginRoot: process.env.AIDE_PLUGIN_ROOT || process.env.CLAUDE_PLUGIN_ROOT,
|
|
51
|
+
});
|
|
52
|
+
if (!binary) {
|
|
53
|
+
debug(SOURCE, "aide binary not found, cannot capture session summary");
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Try to build the richest possible summary:
|
|
58
|
+
// 1. Gather partials from this session
|
|
59
|
+
// 2. Build from transcript (Claude Code has this)
|
|
60
|
+
// 3. Merge partials data with transcript summary
|
|
61
|
+
const partials = gatherPartials(binary, cwd, sessionId);
|
|
62
|
+
let summary: string | null = null;
|
|
63
|
+
|
|
64
|
+
if (partials.length > 0) {
|
|
65
|
+
// Build from partials + git data
|
|
66
|
+
const commits = getSessionCommits(cwd);
|
|
67
|
+
summary = buildSummaryFromPartials(partials, commits, []);
|
|
68
|
+
debug(SOURCE, `Built summary from ${partials.length} partials`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Fall back to transcript-based summary if partials didn't produce enough
|
|
72
|
+
if (!summary) {
|
|
73
|
+
summary = buildSessionSummary(transcriptPath, cwd);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!summary) {
|
|
77
|
+
debug(SOURCE, "No summary generated (insufficient activity or transcript)");
|
|
78
|
+
// Still clean up partials even if no summary was generated
|
|
79
|
+
if (partials.length > 0) {
|
|
80
|
+
cleanupPartials(binary, cwd, sessionId);
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const stored = storeSessionSummary(binary, cwd, sessionId, summary);
|
|
86
|
+
if (stored) {
|
|
87
|
+
debug(SOURCE, `Stored final session summary for ${sessionId.slice(0, 8)}`);
|
|
88
|
+
// Clean up partials now that the final summary is stored
|
|
89
|
+
const cleaned = cleanupPartials(binary, cwd, sessionId);
|
|
90
|
+
debug(SOURCE, `Cleaned up ${cleaned} partials after final summary`);
|
|
91
|
+
} else {
|
|
92
|
+
debug(SOURCE, "Failed to store session summary");
|
|
93
|
+
}
|
|
94
|
+
return stored;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function main(): Promise<void> {
|
|
98
|
+
try {
|
|
99
|
+
const input = await readStdin();
|
|
100
|
+
if (!input.trim()) {
|
|
101
|
+
console.log(JSON.stringify({ continue: true }));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const data: HookInput = JSON.parse(input);
|
|
106
|
+
const cwd = data.cwd || process.cwd();
|
|
107
|
+
const sessionId = data.session_id || "unknown";
|
|
108
|
+
|
|
109
|
+
setDebugCwd(cwd);
|
|
110
|
+
debug(SOURCE, `Hook triggered: ${data.hook_event_name}`);
|
|
111
|
+
|
|
112
|
+
// For Stop hook, capture session summary
|
|
113
|
+
if (data.hook_event_name === "Stop" && data.transcript_path) {
|
|
114
|
+
// Don't capture if stop hook is already active (avoid recursion)
|
|
115
|
+
if (!data.stop_hook_active) {
|
|
116
|
+
debug(SOURCE, "Stop hook - capturing session summary");
|
|
117
|
+
captureSessionSummary(cwd, sessionId, data.transcript_path);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
console.log(JSON.stringify({ continue: true }));
|
|
122
|
+
} catch (err) {
|
|
123
|
+
debug(SOURCE, `Error: ${err}`);
|
|
124
|
+
console.log(JSON.stringify({ continue: true }));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
process.on("uncaughtException", (err) => {
|
|
129
|
+
debug(SOURCE, `UNCAUGHT EXCEPTION: ${err}`);
|
|
130
|
+
try {
|
|
131
|
+
console.log(JSON.stringify({ continue: true }));
|
|
132
|
+
} catch {
|
|
133
|
+
console.log('{"continue":true}');
|
|
134
|
+
}
|
|
135
|
+
process.exit(0);
|
|
136
|
+
});
|
|
137
|
+
process.on("unhandledRejection", (reason) => {
|
|
138
|
+
debug(SOURCE, `UNHANDLED REJECTION: ${reason}`);
|
|
139
|
+
try {
|
|
140
|
+
console.log(JSON.stringify({ continue: true }));
|
|
141
|
+
} catch {
|
|
142
|
+
console.log('{"continue":true}');
|
|
143
|
+
}
|
|
144
|
+
process.exit(0);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
main();
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Skill Injector Hook (UserPromptSubmit)
|
|
4
|
+
*
|
|
5
|
+
* Dynamically discovers and injects relevant skills based on prompt triggers.
|
|
6
|
+
* Searches both built-in skills and project-local .aide/skills/
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Recursive skill discovery
|
|
10
|
+
* - YAML frontmatter parsing for triggers
|
|
11
|
+
* - Caching with file watcher invalidation
|
|
12
|
+
* - Auto-creates .aide directories if needed
|
|
13
|
+
*
|
|
14
|
+
* Debug logging: Set AIDE_DEBUG=1 to enable tracing
|
|
15
|
+
* Logs written to: .aide/_logs/startup.log
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { existsSync, mkdirSync } from "fs";
|
|
19
|
+
import { join } from "path";
|
|
20
|
+
import { Logger, debug, setDebugCwd } from "../lib/logger.js";
|
|
21
|
+
import { readStdin, detectPlatform } from "../lib/hook-utils.js";
|
|
22
|
+
import {
|
|
23
|
+
discoverSkills as coreDiscoverSkills,
|
|
24
|
+
matchSkills as coreMatchSkills,
|
|
25
|
+
formatSkillsContext as coreFormatSkillsContext,
|
|
26
|
+
} from "../core/skill-matcher.js";
|
|
27
|
+
import type { Skill } from "../core/types.js";
|
|
28
|
+
|
|
29
|
+
const SOURCE = "skill-injector";
|
|
30
|
+
|
|
31
|
+
interface HookInput {
|
|
32
|
+
hook_event_name: string;
|
|
33
|
+
session_id: string;
|
|
34
|
+
cwd: string;
|
|
35
|
+
prompt?: string;
|
|
36
|
+
transcript_path?: string;
|
|
37
|
+
permission_mode?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface HookOutput {
|
|
41
|
+
continue: boolean;
|
|
42
|
+
hookSpecificOutput?: {
|
|
43
|
+
hookEventName: string;
|
|
44
|
+
additionalContext?: string;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Skill types, discovery, matching, and formatting are now in core
|
|
49
|
+
// Import aliases used below: coreDiscoverSkills, coreMatchSkills, coreFormatSkillsContext
|
|
50
|
+
|
|
51
|
+
// Module-level logger (initialized in main)
|
|
52
|
+
let log: Logger | null = null;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Ensure .aide directories exist (minimal version for skill-injector)
|
|
56
|
+
*/
|
|
57
|
+
function ensureDirectories(cwd: string): void {
|
|
58
|
+
const dirs = [
|
|
59
|
+
join(cwd, ".aide"),
|
|
60
|
+
join(cwd, ".aide", "skills"),
|
|
61
|
+
join(cwd, ".aide", "config"),
|
|
62
|
+
join(cwd, ".aide", "state"),
|
|
63
|
+
join(cwd, ".aide", "memory"),
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
for (const dir of dirs) {
|
|
67
|
+
if (!existsSync(dir)) {
|
|
68
|
+
try {
|
|
69
|
+
mkdirSync(dir, { recursive: true });
|
|
70
|
+
} catch (err) {
|
|
71
|
+
debugLog(`Failed to create directory ${dir}: ${err}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Discover all skills — delegates to core
|
|
79
|
+
*/
|
|
80
|
+
function discoverSkills(cwd: string): Skill[] {
|
|
81
|
+
log?.start("discoverSkills");
|
|
82
|
+
const pluginRoot =
|
|
83
|
+
process.env.AIDE_PLUGIN_ROOT || process.env.CLAUDE_PLUGIN_ROOT;
|
|
84
|
+
const skills = coreDiscoverSkills(cwd, pluginRoot || undefined);
|
|
85
|
+
log?.end("discoverSkills", { totalSkills: skills.length });
|
|
86
|
+
return skills;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Match skills to prompt — delegates to core
|
|
91
|
+
*/
|
|
92
|
+
function matchSkills(
|
|
93
|
+
prompt: string,
|
|
94
|
+
skills: Skill[],
|
|
95
|
+
maxResults = 3,
|
|
96
|
+
platform?: string,
|
|
97
|
+
): Skill[] {
|
|
98
|
+
log?.start("matchSkills");
|
|
99
|
+
const matched = coreMatchSkills(prompt, skills, maxResults, platform);
|
|
100
|
+
log?.end("matchSkills", { checked: skills.length, matched: matched.length });
|
|
101
|
+
return matched;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Format skills for injection — delegates to core
|
|
106
|
+
*/
|
|
107
|
+
function formatSkillsContext(skills: Skill[]): string {
|
|
108
|
+
return coreFormatSkillsContext(skills);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Debug helper - writes to debug.log (not stderr)
|
|
112
|
+
function debugLog(msg: string): void {
|
|
113
|
+
debug(SOURCE, msg);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Ensure we always output valid JSON, even on catastrophic errors
|
|
117
|
+
function outputContinue(): void {
|
|
118
|
+
try {
|
|
119
|
+
console.log(JSON.stringify({ continue: true }));
|
|
120
|
+
} catch {
|
|
121
|
+
// Last resort - raw JSON string
|
|
122
|
+
console.log('{"continue":true}');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Global error handlers to prevent hook crashes without JSON output
|
|
127
|
+
process.on("uncaughtException", (err) => {
|
|
128
|
+
debugLog(`UNCAUGHT EXCEPTION: ${err}`);
|
|
129
|
+
outputContinue();
|
|
130
|
+
process.exit(0);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
process.on("unhandledRejection", (reason) => {
|
|
134
|
+
debugLog(`UNHANDLED REJECTION: ${reason}`);
|
|
135
|
+
outputContinue();
|
|
136
|
+
process.exit(0);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
async function main(): Promise<void> {
|
|
140
|
+
const hookStart = Date.now();
|
|
141
|
+
debugLog(`Hook started at ${new Date().toISOString()}`);
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
debugLog("Reading stdin...");
|
|
145
|
+
const input = await readStdin();
|
|
146
|
+
debugLog(`Stdin read complete (${Date.now() - hookStart}ms)`);
|
|
147
|
+
|
|
148
|
+
if (!input.trim()) {
|
|
149
|
+
debugLog("Empty input, exiting");
|
|
150
|
+
console.log(JSON.stringify({ continue: true }));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const data: HookInput = JSON.parse(input);
|
|
155
|
+
const prompt = data.prompt || "";
|
|
156
|
+
const cwd = data.cwd || process.cwd();
|
|
157
|
+
|
|
158
|
+
// Switch debug logging to project-local logs
|
|
159
|
+
setDebugCwd(cwd);
|
|
160
|
+
|
|
161
|
+
debugLog(`Parsed input: cwd=${cwd}, prompt=${prompt.length} chars`);
|
|
162
|
+
|
|
163
|
+
// Initialize logger
|
|
164
|
+
log = new Logger("skill-injector", cwd);
|
|
165
|
+
log.start("total");
|
|
166
|
+
log.debug(`Prompt length: ${prompt.length} chars`);
|
|
167
|
+
debugLog(`Logger initialized, enabled=${log.isEnabled()}`);
|
|
168
|
+
|
|
169
|
+
// Ensure .aide directories exist
|
|
170
|
+
debugLog("ensureDirectories starting...");
|
|
171
|
+
log.start("ensureDirectories");
|
|
172
|
+
ensureDirectories(cwd);
|
|
173
|
+
log.end("ensureDirectories");
|
|
174
|
+
debugLog(`ensureDirectories complete (${Date.now() - hookStart}ms)`);
|
|
175
|
+
|
|
176
|
+
if (!prompt) {
|
|
177
|
+
debugLog("No prompt provided, exiting");
|
|
178
|
+
log.info("No prompt provided");
|
|
179
|
+
log.end("total");
|
|
180
|
+
log.flush();
|
|
181
|
+
console.log(JSON.stringify({ continue: true }));
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Discover and match skills
|
|
186
|
+
debugLog("discoverSkills starting...");
|
|
187
|
+
const skills = discoverSkills(cwd);
|
|
188
|
+
debugLog(
|
|
189
|
+
`discoverSkills complete: ${skills.length} skills (${Date.now() - hookStart}ms)`,
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
debugLog("matchSkills starting...");
|
|
193
|
+
const matched = matchSkills(prompt, skills, 3, detectPlatform());
|
|
194
|
+
debugLog(
|
|
195
|
+
`matchSkills complete: ${matched.length} matches (${Date.now() - hookStart}ms)`,
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
log.end("total");
|
|
199
|
+
|
|
200
|
+
if (matched.length > 0) {
|
|
201
|
+
log.info(
|
|
202
|
+
`Injecting ${matched.length} skills: ${matched.map((s) => s.name).join(", ")}`,
|
|
203
|
+
);
|
|
204
|
+
debugLog(`Flushing logs...`);
|
|
205
|
+
log.flush();
|
|
206
|
+
debugLog(`Hook complete (${Date.now() - hookStart}ms total)`);
|
|
207
|
+
|
|
208
|
+
const output: HookOutput = {
|
|
209
|
+
continue: true,
|
|
210
|
+
hookSpecificOutput: {
|
|
211
|
+
hookEventName: "UserPromptSubmit",
|
|
212
|
+
additionalContext: formatSkillsContext(matched),
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
console.log(JSON.stringify(output));
|
|
216
|
+
} else {
|
|
217
|
+
log.info("No matching skills");
|
|
218
|
+
debugLog(`Flushing logs...`);
|
|
219
|
+
log.flush();
|
|
220
|
+
debugLog(`Hook complete (${Date.now() - hookStart}ms total)`);
|
|
221
|
+
console.log(JSON.stringify({ continue: true }));
|
|
222
|
+
}
|
|
223
|
+
} catch (error) {
|
|
224
|
+
debugLog(`ERROR: ${error}`);
|
|
225
|
+
// Log error if logger is available
|
|
226
|
+
if (log) {
|
|
227
|
+
log.error("Skill injection failed", error);
|
|
228
|
+
log.flush();
|
|
229
|
+
}
|
|
230
|
+
// On error, allow continuation
|
|
231
|
+
console.log(JSON.stringify({ continue: true }));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
main();
|