wogiflow 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 (221) hide show
  1. package/.workflow/agents/reviewer.md +81 -0
  2. package/.workflow/agents/security.md +94 -0
  3. package/.workflow/agents/story-writer.md +58 -0
  4. package/.workflow/bridges/base-bridge.js +395 -0
  5. package/.workflow/bridges/claude-bridge.js +434 -0
  6. package/.workflow/bridges/index.js +130 -0
  7. package/.workflow/lib/assumption-detector.js +481 -0
  8. package/.workflow/lib/config-substitution.js +371 -0
  9. package/.workflow/lib/failure-categories.js +478 -0
  10. package/.workflow/state/app-map.md.template +15 -0
  11. package/.workflow/state/architecture.md.template +24 -0
  12. package/.workflow/state/component-index.json.template +5 -0
  13. package/.workflow/state/decisions.md.template +15 -0
  14. package/.workflow/state/feedback-patterns.md.template +9 -0
  15. package/.workflow/state/knowledge-sync.json.template +6 -0
  16. package/.workflow/state/progress.md.template +14 -0
  17. package/.workflow/state/ready.json.template +7 -0
  18. package/.workflow/state/request-log.md.template +14 -0
  19. package/.workflow/state/session-state.json.template +11 -0
  20. package/.workflow/state/stack.md.template +33 -0
  21. package/.workflow/state/testing.md.template +36 -0
  22. package/.workflow/templates/claude-md.hbs +257 -0
  23. package/.workflow/templates/correction-report.md +67 -0
  24. package/.workflow/templates/gemini-md.hbs +52 -0
  25. package/README.md +1802 -0
  26. package/bin/flow +205 -0
  27. package/lib/index.js +33 -0
  28. package/lib/installer.js +467 -0
  29. package/lib/release-channel.js +269 -0
  30. package/lib/skill-registry.js +526 -0
  31. package/lib/upgrader.js +401 -0
  32. package/lib/utils.js +305 -0
  33. package/package.json +64 -0
  34. package/scripts/flow +985 -0
  35. package/scripts/flow-adaptive-learning.js +1259 -0
  36. package/scripts/flow-aggregate.js +488 -0
  37. package/scripts/flow-archive +133 -0
  38. package/scripts/flow-auto-context.js +1015 -0
  39. package/scripts/flow-auto-learn.js +615 -0
  40. package/scripts/flow-bridge.js +223 -0
  41. package/scripts/flow-browser-suggest.js +316 -0
  42. package/scripts/flow-bug.js +247 -0
  43. package/scripts/flow-cascade.js +711 -0
  44. package/scripts/flow-changelog +85 -0
  45. package/scripts/flow-checkpoint.js +483 -0
  46. package/scripts/flow-cli.js +403 -0
  47. package/scripts/flow-code-intelligence.js +760 -0
  48. package/scripts/flow-complexity.js +502 -0
  49. package/scripts/flow-config-set.js +152 -0
  50. package/scripts/flow-constants.js +157 -0
  51. package/scripts/flow-context +152 -0
  52. package/scripts/flow-context-init.js +482 -0
  53. package/scripts/flow-context-monitor.js +384 -0
  54. package/scripts/flow-context-scoring.js +886 -0
  55. package/scripts/flow-correct.js +458 -0
  56. package/scripts/flow-damage-control.js +985 -0
  57. package/scripts/flow-deps +101 -0
  58. package/scripts/flow-diff.js +700 -0
  59. package/scripts/flow-done +151 -0
  60. package/scripts/flow-done.js +489 -0
  61. package/scripts/flow-durable-session.js +1541 -0
  62. package/scripts/flow-entropy-monitor.js +345 -0
  63. package/scripts/flow-export-profile +349 -0
  64. package/scripts/flow-export-scanner.js +1046 -0
  65. package/scripts/flow-figma-confirm.js +400 -0
  66. package/scripts/flow-figma-extract.js +496 -0
  67. package/scripts/flow-figma-generate.js +683 -0
  68. package/scripts/flow-figma-index.js +909 -0
  69. package/scripts/flow-figma-match.js +617 -0
  70. package/scripts/flow-figma-mcp-server.js +518 -0
  71. package/scripts/flow-figma-pipeline.js +414 -0
  72. package/scripts/flow-file-ops.js +301 -0
  73. package/scripts/flow-gate-confidence.js +825 -0
  74. package/scripts/flow-guided-edit.js +659 -0
  75. package/scripts/flow-health +185 -0
  76. package/scripts/flow-health.js +413 -0
  77. package/scripts/flow-hooks.js +556 -0
  78. package/scripts/flow-http-client.js +249 -0
  79. package/scripts/flow-hybrid-detect.js +167 -0
  80. package/scripts/flow-hybrid-interactive.js +591 -0
  81. package/scripts/flow-hybrid-test.js +152 -0
  82. package/scripts/flow-import-profile +439 -0
  83. package/scripts/flow-init +253 -0
  84. package/scripts/flow-instruction-richness.js +827 -0
  85. package/scripts/flow-jira-integration.js +579 -0
  86. package/scripts/flow-knowledge-router.js +522 -0
  87. package/scripts/flow-knowledge-sync.js +589 -0
  88. package/scripts/flow-linear-integration.js +631 -0
  89. package/scripts/flow-links.js +774 -0
  90. package/scripts/flow-log-manager.js +559 -0
  91. package/scripts/flow-loop-enforcer.js +1246 -0
  92. package/scripts/flow-loop-retry-learning.js +630 -0
  93. package/scripts/flow-lsp.js +923 -0
  94. package/scripts/flow-map-index +348 -0
  95. package/scripts/flow-map-sync +201 -0
  96. package/scripts/flow-memory-blocks.js +668 -0
  97. package/scripts/flow-memory-compactor.js +350 -0
  98. package/scripts/flow-memory-db.js +1110 -0
  99. package/scripts/flow-memory-sync.js +484 -0
  100. package/scripts/flow-metrics.js +353 -0
  101. package/scripts/flow-migrate-ids.js +370 -0
  102. package/scripts/flow-model-adapter.js +802 -0
  103. package/scripts/flow-model-router.js +884 -0
  104. package/scripts/flow-models.js +1231 -0
  105. package/scripts/flow-morning.js +517 -0
  106. package/scripts/flow-multi-approach.js +660 -0
  107. package/scripts/flow-new-feature +86 -0
  108. package/scripts/flow-onboard +1042 -0
  109. package/scripts/flow-orchestrate-llm.js +459 -0
  110. package/scripts/flow-orchestrate.js +3592 -0
  111. package/scripts/flow-output.js +123 -0
  112. package/scripts/flow-parallel-detector.js +399 -0
  113. package/scripts/flow-parallel-dispatch.js +987 -0
  114. package/scripts/flow-parallel.js +428 -0
  115. package/scripts/flow-pattern-enforcer.js +600 -0
  116. package/scripts/flow-prd-manager.js +282 -0
  117. package/scripts/flow-progress.js +323 -0
  118. package/scripts/flow-project-analyzer.js +975 -0
  119. package/scripts/flow-prompt-composer.js +487 -0
  120. package/scripts/flow-providers.js +1381 -0
  121. package/scripts/flow-queue.js +308 -0
  122. package/scripts/flow-ready +82 -0
  123. package/scripts/flow-ready.js +189 -0
  124. package/scripts/flow-regression.js +396 -0
  125. package/scripts/flow-response-parser.js +450 -0
  126. package/scripts/flow-resume.js +284 -0
  127. package/scripts/flow-rules-sync.js +439 -0
  128. package/scripts/flow-run-trace.js +718 -0
  129. package/scripts/flow-safety.js +587 -0
  130. package/scripts/flow-search +104 -0
  131. package/scripts/flow-security.js +481 -0
  132. package/scripts/flow-session-end +106 -0
  133. package/scripts/flow-session-end.js +437 -0
  134. package/scripts/flow-session-state.js +671 -0
  135. package/scripts/flow-setup-hooks +216 -0
  136. package/scripts/flow-setup-hooks.js +377 -0
  137. package/scripts/flow-skill-create.js +329 -0
  138. package/scripts/flow-skill-creator.js +572 -0
  139. package/scripts/flow-skill-generator.js +1046 -0
  140. package/scripts/flow-skill-learn.js +880 -0
  141. package/scripts/flow-skill-matcher.js +578 -0
  142. package/scripts/flow-spec-generator.js +820 -0
  143. package/scripts/flow-stack-wizard.js +895 -0
  144. package/scripts/flow-standup +162 -0
  145. package/scripts/flow-start +74 -0
  146. package/scripts/flow-start.js +235 -0
  147. package/scripts/flow-status +110 -0
  148. package/scripts/flow-status.js +301 -0
  149. package/scripts/flow-step-browser.js +83 -0
  150. package/scripts/flow-step-changelog.js +217 -0
  151. package/scripts/flow-step-comments.js +306 -0
  152. package/scripts/flow-step-complexity.js +234 -0
  153. package/scripts/flow-step-coverage.js +218 -0
  154. package/scripts/flow-step-knowledge.js +193 -0
  155. package/scripts/flow-step-pr-tests.js +364 -0
  156. package/scripts/flow-step-regression.js +89 -0
  157. package/scripts/flow-step-review.js +516 -0
  158. package/scripts/flow-step-security.js +162 -0
  159. package/scripts/flow-step-silent-failures.js +290 -0
  160. package/scripts/flow-step-simplifier.js +346 -0
  161. package/scripts/flow-story +105 -0
  162. package/scripts/flow-story.js +500 -0
  163. package/scripts/flow-suspend.js +252 -0
  164. package/scripts/flow-sync-daemon.js +654 -0
  165. package/scripts/flow-task-analyzer.js +606 -0
  166. package/scripts/flow-team-dashboard.js +748 -0
  167. package/scripts/flow-team-sync.js +752 -0
  168. package/scripts/flow-team.js +977 -0
  169. package/scripts/flow-tech-options.js +528 -0
  170. package/scripts/flow-templates.js +812 -0
  171. package/scripts/flow-tiered-learning.js +728 -0
  172. package/scripts/flow-trace +204 -0
  173. package/scripts/flow-transcript-chunking.js +1106 -0
  174. package/scripts/flow-transcript-digest.js +7918 -0
  175. package/scripts/flow-transcript-language.js +465 -0
  176. package/scripts/flow-transcript-parsing.js +1085 -0
  177. package/scripts/flow-transcript-stories.js +2194 -0
  178. package/scripts/flow-update-map +224 -0
  179. package/scripts/flow-utils.js +2242 -0
  180. package/scripts/flow-verification.js +644 -0
  181. package/scripts/flow-verify.js +1177 -0
  182. package/scripts/flow-voice-input.js +638 -0
  183. package/scripts/flow-watch +168 -0
  184. package/scripts/flow-workflow-steps.js +521 -0
  185. package/scripts/flow-workflow.js +1029 -0
  186. package/scripts/flow-worktree.js +489 -0
  187. package/scripts/hooks/adapters/base-adapter.js +102 -0
  188. package/scripts/hooks/adapters/claude-code.js +359 -0
  189. package/scripts/hooks/adapters/index.js +79 -0
  190. package/scripts/hooks/core/component-check.js +341 -0
  191. package/scripts/hooks/core/index.js +35 -0
  192. package/scripts/hooks/core/loop-check.js +241 -0
  193. package/scripts/hooks/core/session-context.js +294 -0
  194. package/scripts/hooks/core/task-gate.js +177 -0
  195. package/scripts/hooks/core/validation.js +230 -0
  196. package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
  197. package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
  198. package/scripts/hooks/entry/claude-code/session-end.js +87 -0
  199. package/scripts/hooks/entry/claude-code/session-start.js +46 -0
  200. package/scripts/hooks/entry/claude-code/stop.js +43 -0
  201. package/scripts/postinstall.js +139 -0
  202. package/templates/browser-test-flow.json +56 -0
  203. package/templates/bug-report.md +43 -0
  204. package/templates/component-detail.md +42 -0
  205. package/templates/component.stories.tsx +49 -0
  206. package/templates/context/constraints.md +83 -0
  207. package/templates/context/conventions.md +177 -0
  208. package/templates/context/stack.md +60 -0
  209. package/templates/correction-report.md +90 -0
  210. package/templates/feature-proposal.md +35 -0
  211. package/templates/hybrid/_base.md +254 -0
  212. package/templates/hybrid/_patterns.md +45 -0
  213. package/templates/hybrid/create-component.md +127 -0
  214. package/templates/hybrid/create-file.md +56 -0
  215. package/templates/hybrid/create-hook.md +145 -0
  216. package/templates/hybrid/create-service.md +70 -0
  217. package/templates/hybrid/fix-bug.md +33 -0
  218. package/templates/hybrid/modify-file.md +55 -0
  219. package/templates/story.md +68 -0
  220. package/templates/task.json +56 -0
  221. package/templates/trace.md +69 -0
@@ -0,0 +1,1015 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Auto Context Loading
5
+ *
6
+ * Intelligently loads relevant context before any task starts.
7
+ * Analyzes task descriptions and loads matching files from:
8
+ * - app-map.md (component registry)
9
+ * - component-index.json (auto-scanned files)
10
+ * - Codebase grep results
11
+ *
12
+ * Uses proactive context gathering approach.
13
+ *
14
+ * Usage as module:
15
+ * const { getAutoContext } = require('./flow-auto-context');
16
+ * const context = await getAutoContext('implement user authentication');
17
+ *
18
+ * Usage as CLI:
19
+ * flow auto-context "task description"
20
+ */
21
+
22
+ const fs = require('fs');
23
+ const path = require('path');
24
+ const { execSync } = require('child_process');
25
+ const {
26
+ getProjectRoot,
27
+ getConfig,
28
+ PATHS,
29
+ colors,
30
+ isAstGrepAvailable,
31
+ astGrepSearch,
32
+ AST_PATTERNS,
33
+ findReactComponents,
34
+ findCustomHooks,
35
+ findTypeDefinitions
36
+ } = require('./flow-utils');
37
+
38
+ // Semantic memory search (optional - may not be initialized)
39
+ let searchFacts = null;
40
+ try {
41
+ const memoryDb = require('./flow-memory-db');
42
+ searchFacts = memoryDb.searchFacts;
43
+ } catch {
44
+ // Memory DB not available - that's ok
45
+ }
46
+
47
+ const PROJECT_ROOT = getProjectRoot();
48
+
49
+ // ============================================================
50
+ // Index Freshness Check
51
+ // ============================================================
52
+
53
+ /**
54
+ * Check if component index is stale and refresh if needed
55
+ * @param {object} config - Config object with componentIndex settings
56
+ * @returns {boolean} - True if index was refreshed
57
+ */
58
+ function checkAndRefreshIndex(config) {
59
+ const indexPath = path.join(PATHS.state, 'component-index.json');
60
+
61
+ if (!fs.existsSync(indexPath)) {
62
+ return false; // No index to refresh
63
+ }
64
+
65
+ const staleAfterMinutes = config.componentIndex?.staleAfterMinutes || 60;
66
+ const scanOn = config.componentIndex?.scanOn || [];
67
+
68
+ // Only check if sessionStart is a trigger
69
+ if (!scanOn.includes('sessionStart')) {
70
+ return false;
71
+ }
72
+
73
+ try {
74
+ const stats = fs.statSync(indexPath);
75
+ const ageMs = Date.now() - stats.mtimeMs;
76
+ const staleMs = staleAfterMinutes * 60 * 1000;
77
+
78
+ if (ageMs > staleMs) {
79
+ // Index is stale - refresh it
80
+ execSync('bash scripts/flow-map-index scan --quiet', {
81
+ encoding: 'utf-8',
82
+ stdio: 'pipe',
83
+ timeout: 30000 // 30 second timeout
84
+ });
85
+ return true;
86
+ }
87
+ } catch {
88
+ // Ignore errors - stale check is best-effort
89
+ }
90
+
91
+ return false;
92
+ }
93
+
94
+ // ============================================================
95
+ // Keyword Extraction
96
+ // ============================================================
97
+
98
+ /**
99
+ * Extract keywords from task description
100
+ * Returns weighted keywords for context matching
101
+ */
102
+ function extractKeywords(description) {
103
+ const text = description.toLowerCase();
104
+ const words = text.match(/[a-z]+/g) || [];
105
+
106
+ // High-value keywords (likely component/feature names)
107
+ const highValue = new Set([
108
+ 'auth', 'authentication', 'login', 'logout', 'signup', 'register',
109
+ 'user', 'profile', 'account', 'settings', 'dashboard', 'admin',
110
+ 'form', 'modal', 'dialog', 'button', 'input', 'select', 'dropdown',
111
+ 'table', 'list', 'grid', 'card', 'nav', 'navigation', 'menu', 'sidebar',
112
+ 'header', 'footer', 'layout', 'page', 'view', 'screen',
113
+ 'api', 'service', 'hook', 'context', 'provider', 'store', 'state',
114
+ 'payment', 'checkout', 'cart', 'order', 'product', 'item',
115
+ 'search', 'filter', 'sort', 'pagination', 'infinite',
116
+ 'upload', 'download', 'file', 'image', 'media', 'avatar',
117
+ 'notification', 'alert', 'toast', 'message', 'error', 'success',
118
+ 'loading', 'spinner', 'skeleton', 'placeholder'
119
+ ]);
120
+
121
+ // Action keywords (help identify task type)
122
+ const actions = new Set([
123
+ 'add', 'create', 'implement', 'build', 'make',
124
+ 'fix', 'repair', 'resolve', 'debug', 'patch',
125
+ 'update', 'modify', 'change', 'edit', 'refactor',
126
+ 'remove', 'delete', 'clean', 'optimize',
127
+ 'test', 'validate', 'check', 'verify'
128
+ ]);
129
+
130
+ const result = {
131
+ high: [], // High-value component/feature keywords
132
+ medium: [], // Regular keywords
133
+ actions: [] // Action verbs
134
+ };
135
+
136
+ for (const word of words) {
137
+ if (word.length < 3) continue;
138
+
139
+ if (highValue.has(word)) {
140
+ result.high.push(word);
141
+ } else if (actions.has(word)) {
142
+ result.actions.push(word);
143
+ } else if (word.length >= 4) {
144
+ result.medium.push(word);
145
+ }
146
+ }
147
+
148
+ // Also extract PascalCase/camelCase terms (likely component names)
149
+ const caseTerms = description.match(/[A-Z][a-z]+(?:[A-Z][a-z]+)*/g) || [];
150
+ for (const term of caseTerms) {
151
+ if (!result.high.includes(term.toLowerCase())) {
152
+ result.high.push(term);
153
+ }
154
+ }
155
+
156
+ return result;
157
+ }
158
+
159
+ /**
160
+ * Infer task type from extracted keywords
161
+ * Used to customize AST-grep search patterns
162
+ */
163
+ function inferTaskType(keywords) {
164
+ const allKeywords = [...keywords.high, ...keywords.medium, ...keywords.actions].map(k => k.toLowerCase());
165
+
166
+ // PRIORITY 1: Check for action keywords first (fix, refactor take precedence)
167
+ // These override noun-based detection since "refactor auth service" should be refactor, not create-service
168
+
169
+ // Check for fix/bug keywords
170
+ if (allKeywords.some(k => ['fix', 'bug', 'issue', 'error', 'broken'].includes(k))) {
171
+ return 'fix-bug';
172
+ }
173
+
174
+ // Check for refactor keywords
175
+ if (allKeywords.some(k => ['refactor', 'cleanup', 'optimize', 'improve', 'reorganize'].includes(k))) {
176
+ return 'refactor';
177
+ }
178
+
179
+ // PRIORITY 2: Check for creation patterns (create/add/new + noun)
180
+
181
+ // Check for component-related keywords
182
+ if (allKeywords.some(k => ['component', 'button', 'form', 'modal', 'card', 'dialog', 'page', 'view', 'ui'].includes(k))) {
183
+ if (allKeywords.includes('create') || allKeywords.includes('add') || allKeywords.includes('new')) {
184
+ return 'create-component';
185
+ }
186
+ return 'modify-component';
187
+ }
188
+
189
+ // Check for hook-related keywords
190
+ // Common React hooks (lowercase, as keywords are lowercased)
191
+ const reactHooks = ['usestate', 'useeffect', 'usecontext', 'usereducer', 'usecallback',
192
+ 'usememo', 'useref', 'useimperativehandle', 'uselayouteffect', 'usedebugvalue',
193
+ 'usetransition', 'usedeferredvalue', 'useid', 'usesyncexternalstore', 'useinsertioneffect'];
194
+ const isHookKeyword = (k) => {
195
+ if (k === 'hook' || k === 'state' || k === 'effect') return true;
196
+ // Match known React hooks
197
+ if (reactHooks.includes(k)) return true;
198
+ // Match custom hooks: useXyz where it's not a common word like "user", "used", "useful"
199
+ if (k.startsWith('use') && k.length > 4 && !['user', 'used', 'uses', 'useful'].includes(k)) {
200
+ return true;
201
+ }
202
+ return false;
203
+ };
204
+ if (allKeywords.some(isHookKeyword)) {
205
+ if (allKeywords.includes('create') || allKeywords.includes('add') || allKeywords.includes('new')) {
206
+ return 'create-hook';
207
+ }
208
+ return 'modify-hook';
209
+ }
210
+
211
+ // Check for service/API keywords
212
+ if (allKeywords.some(k => ['api', 'service', 'fetch', 'request', 'endpoint'].includes(k))) {
213
+ if (allKeywords.includes('create') || allKeywords.includes('add') || allKeywords.includes('new')) {
214
+ return 'create-service';
215
+ }
216
+ return 'modify-service';
217
+ }
218
+
219
+ return 'generic';
220
+ }
221
+
222
+ // ============================================================
223
+ // Context Sources
224
+ // ============================================================
225
+
226
+ /**
227
+ * Search app-map.md for matching components
228
+ */
229
+ function searchAppMap(keywords) {
230
+ const results = [];
231
+ const appMapPath = PATHS.appMap;
232
+
233
+ if (!fs.existsSync(appMapPath)) return results;
234
+
235
+ try {
236
+ const content = fs.readFileSync(appMapPath, 'utf-8');
237
+ const lines = content.split('\n');
238
+
239
+ const allKeywords = [...keywords.high, ...keywords.medium];
240
+
241
+ for (const keyword of allKeywords) {
242
+ const regex = new RegExp(keyword, 'gi');
243
+ for (let i = 0; i < lines.length; i++) {
244
+ if (regex.test(lines[i])) {
245
+ // Extract component info from nearby lines
246
+ const contextStart = Math.max(0, i - 2);
247
+ const contextEnd = Math.min(lines.length, i + 5);
248
+ const context = lines.slice(contextStart, contextEnd).join('\n');
249
+
250
+ // Try to extract file path
251
+ const pathMatch = context.match(/`([^`]+\.(tsx?|jsx?|vue))`/);
252
+ if (pathMatch) {
253
+ results.push({
254
+ source: 'app-map',
255
+ keyword,
256
+ path: pathMatch[1],
257
+ context: context.slice(0, 200),
258
+ score: keywords.high.includes(keyword) ? 3 : 1
259
+ });
260
+ }
261
+ }
262
+ }
263
+ }
264
+ } catch {
265
+ // Ignore errors
266
+ }
267
+
268
+ return results;
269
+ }
270
+
271
+ /**
272
+ * Search component-index.json for matching files
273
+ * @param {object} keywords - Extracted keywords object
274
+ * @param {object} config - Config object with autoContext settings
275
+ */
276
+ function searchComponentIndex(keywords, config = null) {
277
+ const results = [];
278
+ const indexPath = path.join(PATHS.state, 'component-index.json');
279
+
280
+ if (!fs.existsSync(indexPath)) return results;
281
+
282
+ // Use config values if available
283
+ const cfg = config || getConfig();
284
+ const maxComponentMatches = cfg.autoContext?.maxComponentMatches || 15;
285
+
286
+ try {
287
+ const index = JSON.parse(fs.readFileSync(indexPath, 'utf-8'));
288
+ const components = index.components || [];
289
+
290
+ const allKeywords = [...keywords.high, ...keywords.medium];
291
+ let totalMatches = 0;
292
+
293
+ for (const comp of components) {
294
+ const name = (comp.name || '').toLowerCase();
295
+ const filePath = comp.path || '';
296
+
297
+ for (const keyword of allKeywords) {
298
+ const kw = keyword.toLowerCase();
299
+ if (name.includes(kw) || filePath.toLowerCase().includes(kw)) {
300
+ totalMatches++;
301
+ if (results.length < maxComponentMatches) {
302
+ results.push({
303
+ source: 'component-index',
304
+ keyword,
305
+ path: filePath,
306
+ name: comp.name,
307
+ exports: comp.exports || [],
308
+ score: keywords.high.includes(keyword) ? 3 : 1
309
+ });
310
+ }
311
+ break; // Don't add same component multiple times
312
+ }
313
+ }
314
+ }
315
+
316
+ // Add truncation notice if we limited results
317
+ if (totalMatches > maxComponentMatches) {
318
+ results.push({
319
+ source: 'truncation_notice',
320
+ message: `... and ${totalMatches - maxComponentMatches} more component matches (limited to ${maxComponentMatches})`,
321
+ score: 0
322
+ });
323
+ }
324
+ } catch {
325
+ // Ignore errors
326
+ }
327
+
328
+ return results;
329
+ }
330
+
331
+ /**
332
+ * Grep codebase for keyword matches
333
+ * @param {object} keywords - Extracted keywords object
334
+ * @param {number} maxResults - Maximum results to return
335
+ * @param {object} config - Config object with autoContext settings
336
+ */
337
+ function grepCodebase(keywords, maxResults = 10, config = null) {
338
+ const results = [];
339
+ const srcDir = path.join(PROJECT_ROOT, 'src');
340
+
341
+ if (!fs.existsSync(srcDir)) return results;
342
+
343
+ // Use config values if available
344
+ const cfg = config || getConfig();
345
+ const effectiveMaxResults = cfg.autoContext?.maxGrepResults || maxResults;
346
+ const maxContentLines = cfg.autoContext?.maxContentLines || 50;
347
+
348
+ // Only grep for high-value keywords to avoid noise
349
+ const searchKeywords = keywords.high.slice(0, 5);
350
+ let totalMatches = 0;
351
+
352
+ for (const keyword of searchKeywords) {
353
+ try {
354
+ // Case-insensitive grep for the keyword
355
+ const output = execSync(
356
+ `grep -ril "${keyword}" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" "${srcDir}" 2>/dev/null | head -20`,
357
+ { encoding: 'utf-8', timeout: 5000 }
358
+ );
359
+
360
+ const files = output.split('\n').filter(f => f.trim());
361
+ totalMatches += files.length;
362
+
363
+ for (const file of files) {
364
+ if (results.length >= effectiveMaxResults) break;
365
+
366
+ const relPath = path.relative(PROJECT_ROOT, file);
367
+ if (!results.some(r => r.path === relPath)) {
368
+ // Optionally read file content with truncation
369
+ let content = null;
370
+ if (cfg.autoContext?.includeContent) {
371
+ try {
372
+ const fullContent = fs.readFileSync(file, 'utf-8');
373
+ const lines = fullContent.split('\n');
374
+ if (lines.length > maxContentLines) {
375
+ content = [
376
+ ...lines.slice(0, maxContentLines),
377
+ `\n... ${lines.length - maxContentLines} more lines truncated ...`
378
+ ].join('\n');
379
+ } else {
380
+ content = fullContent;
381
+ }
382
+ } catch {
383
+ // Ignore read errors
384
+ }
385
+ }
386
+
387
+ results.push({
388
+ source: 'grep',
389
+ keyword,
390
+ path: relPath,
391
+ content,
392
+ score: 2
393
+ });
394
+ }
395
+ }
396
+ } catch {
397
+ // Ignore grep errors (no matches, timeout, etc.)
398
+ }
399
+
400
+ if (results.length >= effectiveMaxResults) break;
401
+ }
402
+
403
+ // Add truncation notice if we limited results
404
+ if (totalMatches > effectiveMaxResults) {
405
+ results.push({
406
+ source: 'truncation_notice',
407
+ message: `... and ${totalMatches - effectiveMaxResults} more grep matches (limited to ${effectiveMaxResults})`,
408
+ score: 0
409
+ });
410
+ }
411
+
412
+ return results;
413
+ }
414
+
415
+ /**
416
+ * Search ready.json for related tasks
417
+ */
418
+ function searchRelatedTasks(keywords) {
419
+ const results = [];
420
+
421
+ if (!fs.existsSync(PATHS.ready)) return results;
422
+
423
+ try {
424
+ const data = JSON.parse(fs.readFileSync(PATHS.ready, 'utf-8'));
425
+ const allTasks = [
426
+ ...(data.ready || []),
427
+ ...(data.inProgress || []),
428
+ ...(data.recentlyCompleted || []).slice(0, 5)
429
+ ];
430
+
431
+ const allKeywords = [...keywords.high, ...keywords.medium];
432
+
433
+ for (const task of allTasks) {
434
+ const title = typeof task === 'string' ? task : (task.title || task.id || '');
435
+ const titleLower = title.toLowerCase();
436
+
437
+ for (const keyword of allKeywords) {
438
+ if (titleLower.includes(keyword.toLowerCase())) {
439
+ results.push({
440
+ source: 'related-task',
441
+ keyword,
442
+ taskId: typeof task === 'string' ? task : task.id,
443
+ title,
444
+ score: 1
445
+ });
446
+ break;
447
+ }
448
+ }
449
+ }
450
+ } catch {
451
+ // Ignore errors
452
+ }
453
+
454
+ return results;
455
+ }
456
+
457
+ /**
458
+ * Search semantic memory (SQLite facts) for relevant context
459
+ * Returns facts that match the task description
460
+ *
461
+ * @param {object} keywords - Extracted keywords
462
+ * @param {object} config - Config object
463
+ */
464
+ async function searchSemanticMemory(keywords, config = null) {
465
+ // Skip if searchFacts not available or memory disabled
466
+ if (!searchFacts) return [];
467
+
468
+ const cfg = config || getConfig();
469
+ if (!cfg.memory?.enabled) return [];
470
+
471
+ const results = [];
472
+ const maxFacts = cfg.autoContext?.maxSemanticFacts || 5;
473
+
474
+ try {
475
+ // Build query from high-value keywords
476
+ const query = keywords.high.join(' ') || keywords.medium.slice(0, 3).join(' ');
477
+ if (!query) return [];
478
+
479
+ // Search for relevant facts
480
+ const facts = await searchFacts({
481
+ query,
482
+ limit: maxFacts,
483
+ trackAccess: true // Boost relevance when recalled
484
+ });
485
+
486
+ for (const fact of facts) {
487
+ // Only include facts with reasonable relevance (>40%)
488
+ if (fact.relevance >= 40) {
489
+ results.push({
490
+ source: 'semantic-memory',
491
+ fact: fact.fact,
492
+ category: fact.category,
493
+ relevance: fact.relevance,
494
+ score: Math.round(fact.relevance / 25) // Score 1-4 based on relevance
495
+ });
496
+ }
497
+ }
498
+ } catch (err) {
499
+ // Graceful fallback - memory search is optional
500
+ if (cfg.debug) {
501
+ console.warn(`Semantic memory search failed: ${err.message}`);
502
+ }
503
+ }
504
+
505
+ return results;
506
+ }
507
+
508
+ /**
509
+ * Enrich file results with LSP type information
510
+ * Runs AFTER initial grep/search phase completes
511
+ * v2.2: LSP enrichment for auto-context
512
+ *
513
+ * @param {Array} fileResults - Results with path property
514
+ * @param {object} config - Config object
515
+ */
516
+ async function enrichWithLSP(fileResults, config) {
517
+ // Skip if disabled
518
+ if (!config.autoContext?.lspEnrichment?.enabled) return fileResults;
519
+
520
+ // Lazy load LSP module to avoid circular dependencies
521
+ let getLSP, isLSPEnabled;
522
+ try {
523
+ const lspModule = require('./flow-lsp');
524
+ getLSP = lspModule.getLSP;
525
+ isLSPEnabled = lspModule.isLSPEnabled;
526
+ } catch {
527
+ return fileResults; // LSP module not available
528
+ }
529
+
530
+ if (!isLSPEnabled()) return fileResults;
531
+
532
+ const lsp = await getLSP();
533
+ if (!lsp) return fileResults;
534
+
535
+ const timeout = config.autoContext?.lspEnrichment?.timeoutMs || 2000;
536
+ const maxFiles = config.autoContext?.lspEnrichment?.maxFiles || 5;
537
+
538
+ // Only enrich top N JS/TS files to limit latency
539
+ const filesToEnrich = fileResults
540
+ .filter(r => r.path && /\.(ts|tsx|js|jsx)$/.test(r.path))
541
+ .slice(0, maxFiles);
542
+
543
+ if (filesToEnrich.length === 0) return fileResults;
544
+
545
+ try {
546
+ const enriched = await Promise.race([
547
+ Promise.all(filesToEnrich.map(async (result) => {
548
+ try {
549
+ const [symbols, diagnostics] = await Promise.all([
550
+ lsp.getDocumentSymbols(result.path),
551
+ lsp.getDiagnostics(result.path)
552
+ ]);
553
+
554
+ return {
555
+ ...result,
556
+ lsp: {
557
+ exports: (symbols || [])
558
+ .filter(s => ['function', 'class', 'interface', 'variable'].includes(s.kind))
559
+ .slice(0, 10)
560
+ .map(s => ({ name: s.name, kind: s.kind })),
561
+ errorCount: (diagnostics || []).filter(d => d.severity === 'error').length,
562
+ warningCount: (diagnostics || []).filter(d => d.severity === 'warning').length
563
+ }
564
+ };
565
+ } catch {
566
+ return result; // Graceful fallback for individual files
567
+ }
568
+ })),
569
+ // Timeout fallback - return original results if LSP takes too long
570
+ new Promise(resolve => setTimeout(() => resolve(filesToEnrich), timeout))
571
+ ]);
572
+
573
+ // Merge enriched results back into full list
574
+ const enrichedMap = new Map(enriched.map(r => [r.path, r]));
575
+ return fileResults.map(r => enrichedMap.get(r.path) || r);
576
+ } catch {
577
+ // If anything fails, return original results
578
+ return fileResults;
579
+ }
580
+ }
581
+
582
+ /**
583
+ * Search codebase using AST-grep for structural patterns
584
+ * Falls back gracefully if ast-grep is not installed
585
+ *
586
+ * @param {object} keywords - Extracted keywords
587
+ * @param {string} taskType - Type of task (create-component, create-hook, etc.)
588
+ * @param {object} config - Config object
589
+ */
590
+ function searchWithAstGrep(keywords, taskType = null, config = null) {
591
+ const cfg = config || getConfig();
592
+
593
+ // Skip if disabled or ast-grep not available
594
+ if (!cfg.autoContext?.useAstGrep || !isAstGrepAvailable()) {
595
+ return [];
596
+ }
597
+
598
+ const results = [];
599
+ const maxResults = cfg.autoContext?.maxAstGrepResults || 5;
600
+
601
+ try {
602
+ // Determine search strategy based on task type
603
+ if (taskType === 'create-component' || keywords.high.some(k =>
604
+ ['component', 'button', 'form', 'modal', 'card', 'list'].includes(k.toLowerCase())
605
+ )) {
606
+ // Find similar React components for reference
607
+ const components = findReactComponents({ maxResults: maxResults * 2 });
608
+ if (components) {
609
+ for (const comp of components.slice(0, maxResults)) {
610
+ results.push({
611
+ source: 'ast-grep',
612
+ type: 'component',
613
+ path: comp.file,
614
+ line: comp.line,
615
+ preview: comp.content?.slice(0, 100),
616
+ score: 2.5 // Higher than grep, lower than app-map
617
+ });
618
+ }
619
+ }
620
+ }
621
+
622
+ if (taskType === 'create-hook' || keywords.high.some(k =>
623
+ ['hook', 'usestate', 'useeffect', 'usememo'].includes(k.toLowerCase())
624
+ )) {
625
+ // Find existing hooks for patterns
626
+ const hooks = findCustomHooks({ maxResults });
627
+ if (hooks) {
628
+ for (const hook of hooks.slice(0, Math.ceil(maxResults / 2))) {
629
+ results.push({
630
+ source: 'ast-grep',
631
+ type: 'hook',
632
+ path: hook.file,
633
+ line: hook.line,
634
+ preview: hook.content?.slice(0, 100),
635
+ score: 2.5
636
+ });
637
+ }
638
+ }
639
+ }
640
+
641
+ // Search for type definitions matching keywords
642
+ for (const keyword of keywords.high.slice(0, 3)) {
643
+ const types = findTypeDefinitions(keyword, { maxResults: 2 });
644
+ if (types && types.length > 0) {
645
+ for (const type of types) {
646
+ if (!results.some(r => r.path === type.file)) {
647
+ results.push({
648
+ source: 'ast-grep',
649
+ type: 'type-definition',
650
+ keyword,
651
+ path: type.file,
652
+ line: type.line,
653
+ preview: type.content?.slice(0, 100),
654
+ score: 2.5
655
+ });
656
+ }
657
+ }
658
+ }
659
+ }
660
+
661
+ // Generic pattern search for high-value keywords
662
+ for (const keyword of keywords.high.slice(0, 2)) {
663
+ // Search for exported functions/consts with this name
664
+ const pattern = `export $_ ${keyword}$_`;
665
+ const matches = astGrepSearch(pattern, { maxResults: 2 });
666
+ if (matches) {
667
+ for (const match of matches) {
668
+ if (!results.some(r => r.path === match.file)) {
669
+ results.push({
670
+ source: 'ast-grep',
671
+ type: 'export',
672
+ keyword,
673
+ path: match.file,
674
+ line: match.line,
675
+ score: 2
676
+ });
677
+ }
678
+ }
679
+ }
680
+ }
681
+ } catch (err) {
682
+ // Graceful fallback - AST-grep is optional
683
+ if (cfg.debug) {
684
+ console.warn(`AST-grep search failed: ${err.message}`);
685
+ }
686
+ }
687
+
688
+ // Truncate if too many results
689
+ if (results.length > maxResults * 2) {
690
+ const truncated = results.slice(0, maxResults * 2);
691
+ truncated.push({
692
+ source: 'truncation_notice',
693
+ message: `... and ${results.length - maxResults * 2} more AST matches`,
694
+ score: 0
695
+ });
696
+ return truncated;
697
+ }
698
+
699
+ return results;
700
+ }
701
+
702
+ // ============================================================
703
+ // Main Context Loading
704
+ // ============================================================
705
+
706
+ /**
707
+ * Get auto-context for a task description
708
+ * Returns prioritized list of relevant files and context
709
+ * Now async to support semantic memory search
710
+ */
711
+ async function getAutoContext(description, options = {}) {
712
+ const config = getConfig();
713
+
714
+ // Check if auto-context is enabled
715
+ if (config.autoContext?.enabled === false) {
716
+ return { enabled: false, files: [], context: [] };
717
+ }
718
+
719
+ // v2.0: Check and refresh stale component index
720
+ if (config.componentIndex?.autoScan !== false) {
721
+ checkAndRefreshIndex(config);
722
+ }
723
+
724
+ const maxFiles = options.maxFiles || config.autoContext?.maxFilesToLoad || 10;
725
+ const showFiles = options.showFiles ?? config.autoContext?.showLoadedFiles ?? true;
726
+
727
+ // Extract keywords
728
+ const keywords = extractKeywords(description);
729
+
730
+ if (keywords.high.length === 0 && keywords.medium.length === 0) {
731
+ return {
732
+ enabled: true,
733
+ files: [],
734
+ context: [],
735
+ message: 'No specific keywords found in task description'
736
+ };
737
+ }
738
+
739
+ // Determine task type from keywords/options for ast-grep
740
+ const taskType = options.taskType || inferTaskType(keywords);
741
+
742
+ // v2.2: Search semantic memory (async)
743
+ const semanticResults = await searchSemanticMemory(keywords, config);
744
+
745
+ // Gather context from all sources (pass config for truncation settings)
746
+ const allResults = [
747
+ ...searchAppMap(keywords),
748
+ ...searchComponentIndex(keywords, config),
749
+ ...searchWithAstGrep(keywords, taskType, config), // AST-grep search (if enabled)
750
+ ...grepCodebase(keywords, 10, config),
751
+ ...searchRelatedTasks(keywords),
752
+ ...semanticResults // v2.2: Include semantic memory
753
+ ];
754
+
755
+ // Collect truncation notices separately
756
+ const truncationNotices = allResults.filter(r => r.source === 'truncation_notice');
757
+ const actualResults = allResults.filter(r => r.source !== 'truncation_notice');
758
+
759
+ // Dedupe by path and sort by score
760
+ const seen = new Set();
761
+ const unique = [];
762
+
763
+ for (const result of actualResults) {
764
+ const key = result.path || result.taskId || result.keyword;
765
+ if (!seen.has(key)) {
766
+ seen.add(key);
767
+ unique.push(result);
768
+ }
769
+ }
770
+
771
+ // Sort by score (higher first)
772
+ unique.sort((a, b) => (b.score || 0) - (a.score || 0));
773
+
774
+ // v2.2: LSP enrichment (async, with timeout)
775
+ const enrichedUnique = await enrichWithLSP(unique, config);
776
+
777
+ // Re-sort: prioritize files without errors (if LSP enrichment added data)
778
+ if (config.autoContext?.lspEnrichment?.prioritizeHealthyFiles !== false) {
779
+ enrichedUnique.sort((a, b) => {
780
+ // Files with LSP errors go to bottom
781
+ const aErrors = a.lsp?.errorCount || 0;
782
+ const bErrors = b.lsp?.errorCount || 0;
783
+ if (aErrors !== bErrors) return aErrors - bErrors;
784
+ // Then by original score
785
+ return (b.score || 0) - (a.score || 0);
786
+ });
787
+ }
788
+
789
+ // Take top results
790
+ const topResults = enrichedUnique.slice(0, maxFiles);
791
+
792
+ // Extract unique file paths
793
+ const files = topResults
794
+ .filter(r => r.path)
795
+ .map(r => r.path);
796
+
797
+ // Extract semantic memory facts (v2.2)
798
+ const semanticFacts = topResults
799
+ .filter(r => r.source === 'semantic-memory')
800
+ .map(r => ({
801
+ fact: r.fact,
802
+ category: r.category,
803
+ relevance: r.relevance
804
+ }));
805
+
806
+ // Build context summary
807
+ const context = {
808
+ keywords: {
809
+ high: keywords.high,
810
+ medium: keywords.medium.slice(0, 5),
811
+ actions: keywords.actions
812
+ },
813
+ sources: {
814
+ appMap: topResults.filter(r => r.source === 'app-map').length,
815
+ componentIndex: topResults.filter(r => r.source === 'component-index').length,
816
+ grep: topResults.filter(r => r.source === 'grep').length,
817
+ relatedTasks: topResults.filter(r => r.source === 'related-task').length,
818
+ semanticMemory: semanticFacts.length // v2.2
819
+ },
820
+ relatedTasks: topResults
821
+ .filter(r => r.source === 'related-task')
822
+ .map(r => ({ id: r.taskId, title: r.title })),
823
+ semanticFacts, // v2.2: Include learned facts
824
+ truncated: truncationNotices.length > 0,
825
+ truncationNotices: truncationNotices.map(t => t.message)
826
+ };
827
+
828
+ return {
829
+ enabled: true,
830
+ files,
831
+ results: topResults,
832
+ context,
833
+ semanticFacts, // v2.2: Top-level for easy access
834
+ truncationNotices,
835
+ message: files.length > 0
836
+ ? `Found ${files.length} relevant file(s)${semanticFacts.length > 0 ? `, ${semanticFacts.length} learned facts` : ''}${truncationNotices.length > 0 ? ' (results truncated)' : ''}`
837
+ : (semanticFacts.length > 0 ? `Found ${semanticFacts.length} learned fact(s)` : 'No directly relevant files found')
838
+ };
839
+ }
840
+
841
+ /**
842
+ * Format auto-context results for display
843
+ */
844
+ function formatAutoContext(result) {
845
+ if (!result.enabled) {
846
+ return `${colors.dim}Auto-context disabled${colors.reset}`;
847
+ }
848
+
849
+ let output = '';
850
+
851
+ if (result.files.length > 0) {
852
+ output += `${colors.cyan}📂 Auto-loaded context:${colors.reset}\n`;
853
+ for (const file of result.files.slice(0, 8)) {
854
+ // v2.2: Look up LSP enrichment data for this file
855
+ const fileResult = result.results?.find(r => r.path === file);
856
+ const lsp = fileResult?.lsp;
857
+
858
+ let icon = 'â€ĸ';
859
+ let suffix = '';
860
+ if (lsp) {
861
+ if (lsp.errorCount > 0) {
862
+ icon = '❌';
863
+ suffix = ` ${colors.red}(${lsp.errorCount} error${lsp.errorCount > 1 ? 's' : ''})${colors.reset}`;
864
+ } else if (lsp.warningCount > 0) {
865
+ icon = 'âš ī¸';
866
+ suffix = ` ${colors.yellow}(${lsp.warningCount} warning${lsp.warningCount > 1 ? 's' : ''})${colors.reset}`;
867
+ } else {
868
+ icon = '✓';
869
+ }
870
+ }
871
+ output += ` ${colors.dim}${icon}${colors.reset} ${file}${suffix}\n`;
872
+ }
873
+ if (result.files.length > 8) {
874
+ output += ` ${colors.dim}... and ${result.files.length - 8} more${colors.reset}\n`;
875
+ }
876
+
877
+ // v2.2: Show key exports from LSP-enriched files
878
+ const filesWithExports = result.results?.filter(r => r.lsp?.exports?.length > 0) || [];
879
+ if (filesWithExports.length > 0) {
880
+ output += `\n${colors.cyan}đŸ“Ļ Key exports:${colors.reset}\n`;
881
+ for (const fileInfo of filesWithExports.slice(0, 3)) {
882
+ const fileName = path.basename(fileInfo.path);
883
+ const exportNames = fileInfo.lsp.exports.slice(0, 5).map(e => e.name).join(', ');
884
+ const more = fileInfo.lsp.exports.length > 5 ? ` +${fileInfo.lsp.exports.length - 5}` : '';
885
+ output += ` ${colors.dim}${fileName}:${colors.reset} ${exportNames}${more}\n`;
886
+ }
887
+ }
888
+ } else {
889
+ output += `${colors.dim}No specific files matched. Proceeding with general context.${colors.reset}\n`;
890
+ }
891
+
892
+ if (result.context?.relatedTasks?.length > 0) {
893
+ output += `\n${colors.cyan}📋 Related tasks:${colors.reset}\n`;
894
+ for (const task of result.context.relatedTasks.slice(0, 3)) {
895
+ output += ` ${colors.dim}â€ĸ${colors.reset} ${task.id}: ${task.title}\n`;
896
+ }
897
+ }
898
+
899
+ // v2.2: Show semantic memory facts
900
+ if (result.semanticFacts?.length > 0) {
901
+ output += `\n${colors.cyan}🧠 Learned facts:${colors.reset}\n`;
902
+ for (const fact of result.semanticFacts.slice(0, 5)) {
903
+ const relevanceIcon = fact.relevance >= 70 ? '●' : fact.relevance >= 50 ? '◐' : '○';
904
+ output += ` ${colors.dim}${relevanceIcon}${colors.reset} ${fact.fact.slice(0, 100)}${fact.fact.length > 100 ? '...' : ''}\n`;
905
+ }
906
+ if (result.semanticFacts.length > 5) {
907
+ output += ` ${colors.dim}... and ${result.semanticFacts.length - 5} more${colors.reset}\n`;
908
+ }
909
+ }
910
+
911
+ // Show truncation notices if any
912
+ if (result.truncationNotices?.length > 0) {
913
+ output += `\n${colors.dim}â„šī¸ Results truncated:${colors.reset}\n`;
914
+ for (const notice of result.truncationNotices) {
915
+ output += ` ${colors.dim}${notice}${colors.reset}\n`;
916
+ }
917
+ }
918
+
919
+ return output;
920
+ }
921
+
922
+ // ============================================================
923
+ // CLI
924
+ // ============================================================
925
+
926
+ function showHelp() {
927
+ console.log(`
928
+ Wogi Flow - Auto Context Loading
929
+
930
+ Analyzes task descriptions and automatically loads relevant context.
931
+
932
+ Usage:
933
+ flow auto-context "task description"
934
+ flow auto-context --json "task description"
935
+
936
+ Options:
937
+ --json Output as JSON
938
+ --verbose Show all matched results
939
+ --max N Maximum files to load (default: 10)
940
+ --help, -h Show this help
941
+
942
+ Examples:
943
+ flow auto-context "implement user authentication"
944
+ flow auto-context "fix the login form validation"
945
+ flow auto-context "add a new Button variant"
946
+ `);
947
+ }
948
+
949
+ async function main() {
950
+ const args = process.argv.slice(2);
951
+
952
+ if (args.includes('--help') || args.includes('-h')) {
953
+ showHelp();
954
+ process.exit(0);
955
+ }
956
+
957
+ const jsonOutput = args.includes('--json');
958
+ const verbose = args.includes('--verbose');
959
+
960
+ // Extract max files option
961
+ const maxIndex = args.indexOf('--max');
962
+ const maxFiles = maxIndex >= 0 ? parseInt(args[maxIndex + 1]) || 10 : 10;
963
+
964
+ // Get description (everything that's not a flag)
965
+ const description = args
966
+ .filter(a => !a.startsWith('--') && !(maxIndex >= 0 && args[maxIndex + 1] === a))
967
+ .join(' ');
968
+
969
+ if (!description) {
970
+ console.log(`${colors.red}Error: Please provide a task description${colors.reset}`);
971
+ showHelp();
972
+ process.exit(1);
973
+ }
974
+
975
+ const result = await getAutoContext(description, { maxFiles });
976
+
977
+ if (jsonOutput) {
978
+ console.log(JSON.stringify(result, null, 2));
979
+ } else {
980
+ console.log(formatAutoContext(result));
981
+
982
+ if (verbose && result.results) {
983
+ console.log(`\n${colors.bold}All matches:${colors.reset}`);
984
+ for (const r of result.results) {
985
+ console.log(` [${r.source}] ${r.path || r.taskId || r.keyword} (score: ${r.score})`);
986
+ }
987
+ }
988
+ }
989
+ }
990
+
991
+ // ============================================================
992
+ // Exports
993
+ // ============================================================
994
+
995
+ module.exports = {
996
+ extractKeywords,
997
+ searchAppMap,
998
+ searchComponentIndex,
999
+ grepCodebase,
1000
+ searchRelatedTasks,
1001
+ searchWithAstGrep,
1002
+ searchSemanticMemory, // v2.2
1003
+ enrichWithLSP, // v2.2: LSP enrichment
1004
+ inferTaskType,
1005
+ checkAndRefreshIndex,
1006
+ getAutoContext,
1007
+ formatAutoContext
1008
+ };
1009
+
1010
+ if (require.main === module) {
1011
+ main().catch(err => {
1012
+ console.error(`Error: ${err.message}`);
1013
+ process.exit(1);
1014
+ });
1015
+ }