@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.
@@ -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();