solvdex 1.0.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.
Files changed (113) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +274 -0
  3. package/dist/hooks/error-lookup.d.ts +4 -0
  4. package/dist/hooks/error-lookup.d.ts.map +1 -0
  5. package/dist/hooks/error-lookup.js +92 -0
  6. package/dist/hooks/error-lookup.js.map +1 -0
  7. package/dist/hooks/post-task.d.ts +15 -0
  8. package/dist/hooks/post-task.d.ts.map +1 -0
  9. package/dist/hooks/post-task.js +246 -0
  10. package/dist/hooks/post-task.js.map +1 -0
  11. package/dist/hooks/prompt-enrich.d.ts +16 -0
  12. package/dist/hooks/prompt-enrich.d.ts.map +1 -0
  13. package/dist/hooks/prompt-enrich.js +141 -0
  14. package/dist/hooks/prompt-enrich.js.map +1 -0
  15. package/dist/hooks/session-start-cli.d.ts +3 -0
  16. package/dist/hooks/session-start-cli.d.ts.map +1 -0
  17. package/dist/hooks/session-start-cli.js +81 -0
  18. package/dist/hooks/session-start-cli.js.map +1 -0
  19. package/dist/hooks/session-start.d.ts +4 -0
  20. package/dist/hooks/session-start.d.ts.map +1 -0
  21. package/dist/hooks/session-start.js +134 -0
  22. package/dist/hooks/session-start.js.map +1 -0
  23. package/dist/src/audit.d.ts +63 -0
  24. package/dist/src/audit.d.ts.map +1 -0
  25. package/dist/src/audit.js +229 -0
  26. package/dist/src/audit.js.map +1 -0
  27. package/dist/src/cache.d.ts +54 -0
  28. package/dist/src/cache.d.ts.map +1 -0
  29. package/dist/src/cache.js +167 -0
  30. package/dist/src/cache.js.map +1 -0
  31. package/dist/src/config.d.ts +52 -0
  32. package/dist/src/config.d.ts.map +1 -0
  33. package/dist/src/config.js +175 -0
  34. package/dist/src/config.js.map +1 -0
  35. package/dist/src/entry.d.ts +154 -0
  36. package/dist/src/entry.d.ts.map +1 -0
  37. package/dist/src/entry.js +469 -0
  38. package/dist/src/entry.js.map +1 -0
  39. package/dist/src/errors.d.ts +65 -0
  40. package/dist/src/errors.d.ts.map +1 -0
  41. package/dist/src/errors.js +121 -0
  42. package/dist/src/errors.js.map +1 -0
  43. package/dist/src/frontmatter.d.ts +28 -0
  44. package/dist/src/frontmatter.d.ts.map +1 -0
  45. package/dist/src/frontmatter.js +111 -0
  46. package/dist/src/frontmatter.js.map +1 -0
  47. package/dist/src/index.d.ts +35 -0
  48. package/dist/src/index.d.ts.map +1 -0
  49. package/dist/src/index.js +188 -0
  50. package/dist/src/index.js.map +1 -0
  51. package/dist/src/maturity.d.ts +31 -0
  52. package/dist/src/maturity.d.ts.map +1 -0
  53. package/dist/src/maturity.js +96 -0
  54. package/dist/src/maturity.js.map +1 -0
  55. package/dist/src/quality.d.ts +23 -0
  56. package/dist/src/quality.d.ts.map +1 -0
  57. package/dist/src/quality.js +236 -0
  58. package/dist/src/quality.js.map +1 -0
  59. package/dist/src/search.d.ts +35 -0
  60. package/dist/src/search.d.ts.map +1 -0
  61. package/dist/src/search.js +263 -0
  62. package/dist/src/search.js.map +1 -0
  63. package/dist/src/similarity.d.ts +42 -0
  64. package/dist/src/similarity.d.ts.map +1 -0
  65. package/dist/src/similarity.js +111 -0
  66. package/dist/src/similarity.js.map +1 -0
  67. package/dist/src/stats.d.ts +56 -0
  68. package/dist/src/stats.d.ts.map +1 -0
  69. package/dist/src/stats.js +198 -0
  70. package/dist/src/stats.js.map +1 -0
  71. package/dist/src/templates.d.ts +63 -0
  72. package/dist/src/templates.d.ts.map +1 -0
  73. package/dist/src/templates.js +347 -0
  74. package/dist/src/templates.js.map +1 -0
  75. package/dist/src/transfer.d.ts +92 -0
  76. package/dist/src/transfer.d.ts.map +1 -0
  77. package/dist/src/transfer.js +215 -0
  78. package/dist/src/transfer.js.map +1 -0
  79. package/dist/src/types.d.ts +270 -0
  80. package/dist/src/types.d.ts.map +1 -0
  81. package/dist/src/types.js +153 -0
  82. package/dist/src/types.js.map +1 -0
  83. package/dist/src/validate.d.ts +90 -0
  84. package/dist/src/validate.d.ts.map +1 -0
  85. package/dist/src/validate.js +295 -0
  86. package/dist/src/validate.js.map +1 -0
  87. package/hooks/error-lookup.ts +110 -0
  88. package/hooks/hooks.json +49 -0
  89. package/hooks/post-task.ts +309 -0
  90. package/hooks/prompt-enrich.ts +162 -0
  91. package/hooks/session-start-cli.ts +96 -0
  92. package/hooks/session-start.ts +159 -0
  93. package/package.json +40 -0
  94. package/scripts/error-lookup.py +64 -0
  95. package/scripts/post-task.py +60 -0
  96. package/scripts/prompt-enrich.py +64 -0
  97. package/scripts/session-start.py +64 -0
  98. package/skills/wiki/SKILL.md +61 -0
  99. package/skills/wiki-add/SKILL.md +90 -0
  100. package/skills/wiki-browse/SKILL.md +108 -0
  101. package/skills/wiki-capture/SKILL.md +265 -0
  102. package/skills/wiki-explorer/SKILL.md +223 -0
  103. package/skills/wiki-export/SKILL.md +101 -0
  104. package/skills/wiki-fix/SKILL.md +86 -0
  105. package/skills/wiki-flag/SKILL.md +47 -0
  106. package/skills/wiki-import/SKILL.md +128 -0
  107. package/skills/wiki-init/SKILL.md +72 -0
  108. package/skills/wiki-scan/SKILL.md +98 -0
  109. package/skills/wiki-search/SKILL.md +86 -0
  110. package/skills/wiki-stats/SKILL.md +129 -0
  111. package/skills/wiki-status/SKILL.md +78 -0
  112. package/skills/wiki-test-trigger/SKILL.md +173 -0
  113. package/skills/wiki-validate/SKILL.md +62 -0
@@ -0,0 +1,309 @@
1
+ // hooks/post-task.ts
2
+ import {
3
+ wikiExists,
4
+ createEntry,
5
+ generateStubContent,
6
+ WikiEntry,
7
+ CONFIDENCE,
8
+ PostTaskOutput
9
+ } from '../src/index.js';
10
+
11
+ interface TaskContext {
12
+ projectRoot: string;
13
+ taskDescription: string;
14
+ outcome: 'success' | 'failure';
15
+ errorEncountered?: string;
16
+ errorResolved?: boolean;
17
+ filesModified?: string[];
18
+ userConfirmation?: string;
19
+ codeChanges?: string; // For detecting workarounds
20
+ actionHistory?: string[]; // For repeated_pattern detection
21
+ }
22
+
23
+ // Default enabled signals (can be overridden by future config)
24
+ const DEFAULT_SIGNALS = [
25
+ 'error_resolved',
26
+ 'user_confirmation',
27
+ 'explicit_save',
28
+ 'repeated_pattern',
29
+ 'workaround_applied'
30
+ ];
31
+
32
+ export async function onPostTask(context: TaskContext): Promise<PostTaskOutput | null> {
33
+ try {
34
+ const { projectRoot } = context;
35
+
36
+ if (!wikiExists(projectRoot)) {
37
+ return null;
38
+ }
39
+
40
+ // Check signals
41
+ const signal = detectSignal(context, DEFAULT_SIGNALS);
42
+
43
+ if (!signal) {
44
+ return null;
45
+ }
46
+
47
+ // Extract entry details
48
+ const entryDetails = extractEntryDetails(context, signal);
49
+
50
+ // Check for duplicates before creating (but don't block on it)
51
+ let isDuplicate = false;
52
+ try {
53
+ const { checkDuplicate } = await import('../src/index.js');
54
+ const dupCheck = await checkDuplicate(
55
+ projectRoot,
56
+ entryDetails.title,
57
+ entryDetails.tags,
58
+ entryDetails.category
59
+ );
60
+ isDuplicate = dupCheck.isDuplicate;
61
+
62
+ if (isDuplicate) {
63
+ console.log(`📚 Solvdex: Skipping duplicate "${entryDetails.title}"`);
64
+ console.log(` Similar to: "${dupCheck.matchedEntry?.frontmatter.title}"`);
65
+ return null;
66
+ }
67
+ } catch {
68
+ // Duplicate check failure is non-critical, proceed with creation
69
+ }
70
+
71
+ // Create entry
72
+ const entry = createEntry(projectRoot, {
73
+ category: entryDetails.category,
74
+ title: entryDetails.title,
75
+ tags: entryDetails.tags,
76
+ content: entryDetails.content,
77
+ trigger: entryDetails.trigger,
78
+ autoCapture: true,
79
+ stub: true,
80
+ source: `auto-capture:${signal}`
81
+ });
82
+
83
+ // Output for Claude Code Stop hook integration (user-facing console logs)
84
+ console.log(`📚 Solvdex: Captured "${entry.frontmatter.title}"`);
85
+ console.log(` Category: ${entry.category}`);
86
+ console.log(` Signal: ${signal}`);
87
+
88
+ // Structured output for Claude Code (includes both old and new format for backward compatibility)
89
+ const output: PostTaskOutput = {
90
+ // Old format (for backward compatibility)
91
+ type: 'wiki_captured',
92
+ message: `Wiki: Saved "${entry.frontmatter.title}" to ${entry.category}/`,
93
+ entry,
94
+ signal,
95
+ // New format (Claude Code integration)
96
+ continue: true,
97
+ hookSpecificOutput: {
98
+ type: 'entry_captured',
99
+ message: `Captured "${entry.frontmatter.title}" to ${entry.category}/`,
100
+ entryPath: entry.path
101
+ },
102
+ additionalContext: formatEntryForContext(entry, signal)
103
+ };
104
+
105
+ // Output JSON marker + JSON (MUST be last output)
106
+ console.log('\n__HOOK_OUTPUT__');
107
+ console.log(JSON.stringify(output));
108
+
109
+ return output;
110
+ } catch (error) {
111
+ // Graceful degradation - log error but don't crash
112
+ console.error(`[Solvdex] Error in post-task hook: ${error instanceof Error ? error.message : 'Unknown error'}`);
113
+ return null;
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Detects if similar actions have been performed 3+ times.
119
+ * Returns the repeated action if found.
120
+ */
121
+ function detectRepeatedPattern(actionHistory: string[]): string | null {
122
+ if (!actionHistory || actionHistory.length < 3) {
123
+ return null;
124
+ }
125
+
126
+ // Count occurrences of each action
127
+ const counts: Record<string, number> = {};
128
+ for (const action of actionHistory) {
129
+ const normalized = action.toLowerCase().trim();
130
+ counts[normalized] = (counts[normalized] || 0) + 1;
131
+ }
132
+
133
+ // Find action repeated 3+ times
134
+ for (const [action, count] of Object.entries(counts)) {
135
+ if (count >= 3) {
136
+ return action;
137
+ }
138
+ }
139
+
140
+ return null;
141
+ }
142
+
143
+ function detectSignal(context: TaskContext, enabledSignals: string[]): string | null {
144
+ // Signal 1: Error resolved
145
+ if (
146
+ enabledSignals.includes('error_resolved') &&
147
+ context.errorEncountered &&
148
+ context.errorResolved &&
149
+ context.outcome === 'success'
150
+ ) {
151
+ return 'error_resolved';
152
+ }
153
+
154
+ // Signal 2: User confirmation
155
+ if (
156
+ enabledSignals.includes('user_confirmation') &&
157
+ context.userConfirmation &&
158
+ isPositiveConfirmation(context.userConfirmation)
159
+ ) {
160
+ return 'user_confirmation';
161
+ }
162
+
163
+ // Signal 3: Explicit save request
164
+ if (
165
+ enabledSignals.includes('explicit_save') &&
166
+ context.userConfirmation &&
167
+ isExplicitSaveRequest(context.userConfirmation)
168
+ ) {
169
+ return 'explicit_save';
170
+ }
171
+
172
+ // Signal 4: Repeated pattern
173
+ if (enabledSignals.includes('repeated_pattern') && context.actionHistory) {
174
+ const repeatedAction = detectRepeatedPattern(context.actionHistory);
175
+ if (repeatedAction) {
176
+ return 'repeated_pattern';
177
+ }
178
+ }
179
+
180
+ // Signal 5: Workaround applied (detect HACK/WORKAROUND comments)
181
+ if (
182
+ enabledSignals.includes('workaround_applied') &&
183
+ context.codeChanges &&
184
+ hasWorkaroundMarkers(context.codeChanges)
185
+ ) {
186
+ return 'workaround_applied';
187
+ }
188
+
189
+ return null;
190
+ }
191
+
192
+ function isPositiveConfirmation(text: string): boolean {
193
+ const positivePatterns = [
194
+ 'that fixed it',
195
+ 'works now',
196
+ 'perfect',
197
+ 'that worked',
198
+ 'thank you',
199
+ 'great',
200
+ 'awesome',
201
+ 'solved'
202
+ ];
203
+
204
+ const textLower = text.toLowerCase();
205
+ return positivePatterns.some(p => textLower.includes(p));
206
+ }
207
+
208
+ function isExplicitSaveRequest(text: string): boolean {
209
+ const savePatterns = [
210
+ 'save this to wiki',
211
+ 'remember this',
212
+ 'add to wiki',
213
+ 'wiki this',
214
+ 'document this'
215
+ ];
216
+
217
+ const textLower = text.toLowerCase();
218
+ return savePatterns.some(p => textLower.includes(p));
219
+ }
220
+
221
+ function hasWorkaroundMarkers(codeChanges: string): boolean {
222
+ const workaroundPatterns = [
223
+ /\/\/\s*(HACK|WORKAROUND|TODO|FIXME):/i,
224
+ /\/\*\s*(HACK|WORKAROUND|TODO|FIXME):/i,
225
+ /#\s*(HACK|WORKAROUND|TODO|FIXME):/i
226
+ ];
227
+
228
+ return workaroundPatterns.some(pattern => pattern.test(codeChanges));
229
+ }
230
+
231
+ function extractEntryDetails(context: TaskContext, signal: string) {
232
+ // Determine category based on signal and task keywords (7 categories)
233
+ let category = 'issues'; // default
234
+
235
+ if (signal === 'error_resolved') {
236
+ category = 'issues';
237
+ } else if (signal === 'workaround_applied') {
238
+ category = 'gotchas';
239
+ } else if (signal === 'repeated_pattern') {
240
+ category = 'patterns';
241
+ } else if (context.taskDescription) {
242
+ const task = context.taskDescription.toLowerCase();
243
+
244
+ // Testing signals
245
+ if (task.includes('test') || task.includes('mock') || task.includes('fixture')) {
246
+ category = 'testing';
247
+ }
248
+ // Documentation signals
249
+ else if (task.includes('document') || task.includes('readme') || task.includes('guide')) {
250
+ category = 'docs';
251
+ }
252
+ // Security signals
253
+ else if (task.includes('auth') || task.includes('security') || task.includes('permission')) {
254
+ category = 'security';
255
+ }
256
+ // Performance signals
257
+ else if (task.includes('slow') || task.includes('optimize') || task.includes('performance')) {
258
+ category = 'performance';
259
+ }
260
+ // Pattern signals
261
+ else if (task.includes('pattern') || task.includes('reusable')) {
262
+ category = 'patterns';
263
+ }
264
+ // Gotcha signals
265
+ else if (task.includes('careful') || task.includes('avoid') || task.includes('pitfall')) {
266
+ category = 'gotchas';
267
+ }
268
+ }
269
+
270
+ // Generate title from task or error
271
+ const title = context.errorEncountered
272
+ ? `Fix: ${context.errorEncountered.substring(0, 50)}`
273
+ : context.taskDescription?.substring(0, 50) || 'Auto-captured entry';
274
+
275
+ // Extract tags from context
276
+ const tags: string[] = [];
277
+ if (context.filesModified) {
278
+ // Add file extension tags
279
+ const extensions = new Set(
280
+ context.filesModified
281
+ .map(f => f.split('.').pop())
282
+ .filter(Boolean)
283
+ );
284
+ tags.push(...Array.from(extensions) as string[]);
285
+ }
286
+ if (signal) {
287
+ tags.push(`auto:${signal}`);
288
+ }
289
+
290
+ // Generate stub content
291
+ const stubContent = generateStubContent(category);
292
+
293
+ return {
294
+ category,
295
+ title: title.replace(/[^\w\s-]/g, '').trim(),
296
+ tags,
297
+ content: stubContent,
298
+ trigger: context.errorEncountered
299
+ };
300
+ }
301
+
302
+ /**
303
+ * Formats a captured entry into a readable context string for Claude Code.
304
+ */
305
+ function formatEntryForContext(entry: WikiEntry, signal: string): string {
306
+ return `## Captured Entry: ${entry.frontmatter.title}\nCategory: ${entry.category}\nSignal: ${signal}\nPath: ${entry.path}\n\n${entry.content.substring(0, 300)}...`;
307
+ }
308
+
309
+ export default onPostTask;
@@ -0,0 +1,162 @@
1
+ // hooks/prompt-enrich.ts
2
+ import { wikiExists, searchWiki, PromptEnrichOutput, SearchResult } from '../src/index.js';
3
+
4
+ /**
5
+ * Hook input from Claude Code's UserPromptSubmit event.
6
+ */
7
+ interface HookInput {
8
+ projectRoot: string;
9
+ prompt: string;
10
+ filePaths?: string[];
11
+ }
12
+
13
+ /**
14
+ * Category keywords for detecting relevant wiki categories from prompt.
15
+ */
16
+ const CATEGORY_KEYWORDS: Record<string, string[]> = {
17
+ issues: ['error', 'fix', 'bug', 'broken', 'fails', 'crash', 'exception'],
18
+ patterns: ['pattern', 'template', 'reusable', 'design', 'approach'],
19
+ gotchas: ['careful', 'watch out', 'pitfall', 'gotcha', 'trap', 'avoid'],
20
+ testing: ['test', 'mock', 'fixture', 'assert', 'spec', 'coverage'],
21
+ docs: ['document', 'readme', 'guide', 'tutorial', 'api docs'],
22
+ security: ['auth', 'token', 'vulnerability', 'permission', 'csrf', 'xss', 'injection'],
23
+ performance: ['slow', 'optimize', 'memory', 'latency', 'benchmark', 'profiling']
24
+ };
25
+
26
+ /**
27
+ * Path hints for detecting relevant categories from file paths.
28
+ */
29
+ const PATH_HINTS: Record<string, string[]> = {
30
+ testing: ['test', 'spec', 'fixture', 'mock', '__tests__'],
31
+ docs: ['doc', 'readme', 'wiki', 'guide'],
32
+ security: ['auth', 'secret', 'cred', 'permission'],
33
+ performance: ['perf', 'bench', 'profile', 'optim']
34
+ };
35
+
36
+ /**
37
+ * UserPromptSubmit hook - enriches prompts with relevant wiki context.
38
+ * Outputs to stdout for Claude Code to include in context.
39
+ */
40
+ export async function onPromptEnrich(input: HookInput): Promise<PromptEnrichOutput | null> {
41
+ try {
42
+ const { projectRoot, prompt, filePaths } = input;
43
+
44
+ if (!wikiExists(projectRoot)) {
45
+ return null;
46
+ }
47
+
48
+ // Detect relevant categories from prompt keywords
49
+ const relevantCategories = detectRelevantCategories(prompt, filePaths || []);
50
+
51
+ // Search wiki for relevant entries
52
+ const searchResults = await searchWiki(projectRoot, { query: prompt });
53
+
54
+ if (searchResults.length === 0 && relevantCategories.length === 0) {
55
+ return null;
56
+ }
57
+
58
+ // Format entries for output
59
+ const entries = searchResults.slice(0, 5).map(r => ({
60
+ title: r.entry.frontmatter.title,
61
+ category: r.entry.category,
62
+ path: r.entry.path,
63
+ confidence: r.entry.frontmatter.confidence
64
+ }));
65
+
66
+ // Record usage for matched entries
67
+ if (entries.length > 0) {
68
+ try {
69
+ const { recordEntryUsage } = await import('../src/index.js');
70
+ for (const entry of entries) {
71
+ recordEntryUsage(projectRoot, entry.path);
72
+ }
73
+ } catch {
74
+ // Usage tracking failure is non-critical
75
+ }
76
+ }
77
+
78
+ // Output for Claude Code hook integration (user-facing console logs)
79
+ if (entries.length > 0) {
80
+ console.log(`\u{1F4DA} Solvdex: Found ${entries.length} relevant entries for your task:`);
81
+ for (const entry of entries.slice(0, 3)) {
82
+ console.log(` - ${entry.title} (${entry.category}, confidence: ${entry.confidence})`);
83
+ }
84
+ if (entries.length > 3) {
85
+ console.log(` ... and ${entries.length - 3} more`);
86
+ }
87
+ } else if (relevantCategories.length > 0) {
88
+ console.log(`\u{1F4DA} Solvdex: Check ${relevantCategories.join(', ')} for related knowledge`);
89
+ }
90
+
91
+ // Structured output for Claude Code (includes both old and new format for backward compatibility)
92
+ const output: PromptEnrichOutput = {
93
+ // Old format (for backward compatibility)
94
+ type: 'wiki_context',
95
+ message: entries.length > 0
96
+ ? `Found ${entries.length} relevant wiki entries`
97
+ : `Relevant categories: ${relevantCategories.join(', ')}`,
98
+ entries,
99
+ // New format (Claude Code integration)
100
+ continue: true,
101
+ hookSpecificOutput: {
102
+ type: 'context_added',
103
+ message: entries.length > 0
104
+ ? `Found ${entries.length} relevant wiki entries`
105
+ : `Relevant categories: ${relevantCategories.join(', ')}`,
106
+ entryCount: entries.length
107
+ },
108
+ additionalContext: entries.length > 0
109
+ ? formatEntriesForContext(searchResults.slice(0, 5))
110
+ : undefined
111
+ };
112
+
113
+ // Output JSON marker + JSON (MUST be last output)
114
+ console.log('\n__HOOK_OUTPUT__');
115
+ console.log(JSON.stringify(output));
116
+
117
+ return output;
118
+ } catch (error) {
119
+ // Graceful degradation - log error but don't crash
120
+ console.error(`[Solvdex] Error in prompt-enrich hook: ${error instanceof Error ? error.message : 'Unknown error'}`);
121
+ return null;
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Detects relevant categories based on prompt keywords and file paths.
127
+ */
128
+ function detectRelevantCategories(prompt: string, filePaths: string[]): string[] {
129
+ const detected: Set<string> = new Set();
130
+ const promptLower = prompt.toLowerCase();
131
+
132
+ // Check keywords in prompt
133
+ for (const [category, keywords] of Object.entries(CATEGORY_KEYWORDS)) {
134
+ if (keywords.some(kw => promptLower.includes(kw))) {
135
+ detected.add(category);
136
+ }
137
+ }
138
+
139
+ // Check path hints
140
+ for (const filePath of filePaths) {
141
+ const pathLower = filePath.toLowerCase();
142
+ for (const [category, hints] of Object.entries(PATH_HINTS)) {
143
+ if (hints.some(hint => pathLower.includes(hint))) {
144
+ detected.add(category);
145
+ }
146
+ }
147
+ }
148
+
149
+ return Array.from(detected);
150
+ }
151
+
152
+ /**
153
+ * Formats search results into a readable context string for Claude Code.
154
+ */
155
+ function formatEntriesForContext(results: SearchResult[]): string {
156
+ return results.map(r => {
157
+ const e = r.entry;
158
+ return `## ${e.frontmatter.title} (${e.category})\nConfidence: ${e.frontmatter.confidence}\nTags: ${e.frontmatter.tags.join(', ')}\n\n${e.content.substring(0, 300)}...`;
159
+ }).join('\n\n');
160
+ }
161
+
162
+ export default onPromptEnrich;
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+ // CLI wrapper for session-start hook
3
+ import { wikiExists, searchWiki, DEFAULT_CATEGORIES } from '../src/index.js';
4
+
5
+ interface SessionStartInput {
6
+ projectRoot: string;
7
+ taskDescription?: string;
8
+ filePaths?: string[];
9
+ }
10
+
11
+ const CATEGORY_KEYWORDS: Record<string, string[]> = {
12
+ issues: ['error', 'fix', 'bug', 'broken', 'fails', 'crash', 'exception', 'trouble'],
13
+ patterns: ['pattern', 'template', 'reusable', 'design', 'approach', 'best practice'],
14
+ gotchas: ['careful', 'watch out', 'pitfall', 'gotcha', 'trap', 'avoid', 'never'],
15
+ testing: ['test', 'mock', 'fixture', 'assert', 'spec', 'coverage', 'jest', 'vitest'],
16
+ docs: ['document', 'readme', 'guide', 'tutorial', 'api docs', 'documentation'],
17
+ security: ['auth', 'token', 'vulnerability', 'permission', 'csrf', 'xss', 'injection', 'security'],
18
+ performance: ['slow', 'optimize', 'memory', 'latency', 'benchmark', 'profiling', 'performance']
19
+ };
20
+
21
+ const CATEGORY_PATH_HINTS: Record<string, string[]> = {
22
+ testing: ['test', 'spec', 'fixture', 'mock', '__tests__'],
23
+ docs: ['doc', 'readme', 'wiki', 'guide', 'docs'],
24
+ security: ['auth', 'secret', 'cred', 'permission', 'security'],
25
+ performance: ['perf', 'bench', 'profile', 'optim']
26
+ };
27
+
28
+ function detectCategoryFromPaths(paths: string[]): string[] {
29
+ const detected: Set<string> = new Set();
30
+ for (const filePath of paths) {
31
+ const pathLower = filePath.toLowerCase();
32
+ for (const [category, hints] of Object.entries(CATEGORY_PATH_HINTS)) {
33
+ if (hints.some(hint => pathLower.includes(hint))) {
34
+ detected.add(category);
35
+ }
36
+ }
37
+ }
38
+ return Array.from(detected);
39
+ }
40
+
41
+ function isRelevantCategory(task: string, category: string): boolean {
42
+ const categoryKeywords = CATEGORY_KEYWORDS[category] || [];
43
+ const taskLower = task.toLowerCase();
44
+ return categoryKeywords.some(kw => taskLower.includes(kw));
45
+ }
46
+
47
+ async function main() {
48
+ try {
49
+ // Read JSON from stdin
50
+ const chunks: Buffer[] = [];
51
+ for await (const chunk of process.stdin) {
52
+ chunks.push(chunk);
53
+ }
54
+ const inputData: SessionStartInput = JSON.parse(Buffer.concat(chunks).toString());
55
+
56
+ const { projectRoot, taskDescription, filePaths } = inputData;
57
+
58
+ if (!wikiExists(projectRoot) || !taskDescription) {
59
+ process.exit(0);
60
+ }
61
+
62
+ // Check which categories might be relevant
63
+ const relevantCategories: string[] = [];
64
+ for (const category of DEFAULT_CATEGORIES) {
65
+ if (isRelevantCategory(taskDescription, category)) {
66
+ relevantCategories.push(category);
67
+ }
68
+ }
69
+
70
+ // Also check paths for category hints
71
+ if (filePaths && filePaths.length > 0) {
72
+ const pathCategories = detectCategoryFromPaths(filePaths);
73
+ for (const cat of pathCategories) {
74
+ if (!relevantCategories.includes(cat)) {
75
+ relevantCategories.push(cat);
76
+ }
77
+ }
78
+ }
79
+
80
+ // Search wiki for relevant entries
81
+ const searchResults = await searchWiki(projectRoot, { query: taskDescription });
82
+
83
+ if (searchResults.length > 0) {
84
+ console.log(`📚 Solvdex: Found ${searchResults.length} relevant entries`);
85
+ for (const r of searchResults.slice(0, 3)) {
86
+ console.log(` - ${r.entry.frontmatter.title} (${r.entry.category})`);
87
+ }
88
+ }
89
+
90
+ process.exit(0);
91
+ } catch (error) {
92
+ process.exit(0); // Silent fail
93
+ }
94
+ }
95
+
96
+ main();