@jmylchreest/aide-plugin 0.0.57 → 0.0.59

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,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();