@limo-labs/limo-cli 0.1.0-alpha.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 (42) hide show
  1. package/README.md +238 -0
  2. package/dist/agents/analyst.d.ts +24 -0
  3. package/dist/agents/analyst.js +128 -0
  4. package/dist/agents/editor.d.ts +26 -0
  5. package/dist/agents/editor.js +157 -0
  6. package/dist/agents/planner-validator.d.ts +7 -0
  7. package/dist/agents/planner-validator.js +125 -0
  8. package/dist/agents/planner.d.ts +56 -0
  9. package/dist/agents/planner.js +186 -0
  10. package/dist/agents/writer.d.ts +25 -0
  11. package/dist/agents/writer.js +164 -0
  12. package/dist/commands/analyze.d.ts +14 -0
  13. package/dist/commands/analyze.js +562 -0
  14. package/dist/index.d.ts +2 -0
  15. package/dist/index.js +41 -0
  16. package/dist/report/diagrams.d.ts +27 -0
  17. package/dist/report/diagrams.js +74 -0
  18. package/dist/report/graphCompiler.d.ts +37 -0
  19. package/dist/report/graphCompiler.js +277 -0
  20. package/dist/report/markdownGenerator.d.ts +71 -0
  21. package/dist/report/markdownGenerator.js +148 -0
  22. package/dist/tools/additional.d.ts +116 -0
  23. package/dist/tools/additional.js +349 -0
  24. package/dist/tools/extended.d.ts +101 -0
  25. package/dist/tools/extended.js +586 -0
  26. package/dist/tools/index.d.ts +86 -0
  27. package/dist/tools/index.js +362 -0
  28. package/dist/types/agents.types.d.ts +139 -0
  29. package/dist/types/agents.types.js +6 -0
  30. package/dist/types/graphSemantics.d.ts +99 -0
  31. package/dist/types/graphSemantics.js +104 -0
  32. package/dist/utils/debug.d.ts +28 -0
  33. package/dist/utils/debug.js +125 -0
  34. package/dist/utils/limoConfigParser.d.ts +21 -0
  35. package/dist/utils/limoConfigParser.js +274 -0
  36. package/dist/utils/reviewMonitor.d.ts +20 -0
  37. package/dist/utils/reviewMonitor.js +121 -0
  38. package/package.json +62 -0
  39. package/prompts/analyst.md +343 -0
  40. package/prompts/editor.md +196 -0
  41. package/prompts/planner.md +388 -0
  42. package/prompts/writer.md +218 -0
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Graph Semantic Structure (Intermediate Representation)
3
+ *
4
+ * This IR defines the STRUCTURE of a graph, not its visual representation.
5
+ * LLM outputs this IR, and the code compiles it to DOT deterministically.
6
+ *
7
+ * CRITICAL CONSTRAINTS:
8
+ * - LLM MUST NOT output DOT/Graphviz syntax
9
+ * - LLM MUST NOT specify layout, coordinates, styles, or visual properties
10
+ * - Graph quality depends on structural modeling, not LLM's text expression
11
+ */
12
+ /**
13
+ * DOT reserved keywords that cannot be used as node IDs
14
+ */
15
+ const DOT_KEYWORDS = new Set(['node', 'edge', 'graph', 'digraph', 'subgraph', 'strict']);
16
+ /**
17
+ * Validate graph semantic structure
18
+ */
19
+ export function validateGraphSemantics(graph) {
20
+ const errors = [];
21
+ const warnings = [];
22
+ // Check for empty graph
23
+ if (graph.nodes.length === 0) {
24
+ errors.push('Graph must contain at least one node');
25
+ }
26
+ // Check for duplicate node IDs
27
+ const nodeIds = new Set();
28
+ for (const node of graph.nodes) {
29
+ if (nodeIds.has(node.id)) {
30
+ errors.push(`Duplicate node ID: ${node.id}`);
31
+ }
32
+ nodeIds.add(node.id);
33
+ // Validate node ID legality
34
+ // Check for DOT reserved keywords
35
+ if (DOT_KEYWORDS.has(node.id.toLowerCase())) {
36
+ warnings.push(`Node ID '${node.id}' is a DOT reserved keyword. ` +
37
+ `It will be sanitized to 'n_${node.id}' during compilation.`);
38
+ }
39
+ // Check for digit-starting IDs
40
+ if (/^[0-9]/.test(node.id)) {
41
+ warnings.push(`Node ID '${node.id}' starts with a digit. ` +
42
+ `It will be sanitized to 'n_${node.id}' during compilation.`);
43
+ }
44
+ // Check for special characters
45
+ if (/[^a-zA-Z0-9_]/.test(node.id)) {
46
+ warnings.push(`Node ID '${node.id}' contains special characters. ` +
47
+ `They will be replaced with underscores during compilation.`);
48
+ }
49
+ }
50
+ // Check for duplicate group IDs
51
+ if (graph.groups) {
52
+ const groupIds = new Set();
53
+ for (const group of graph.groups) {
54
+ if (groupIds.has(group.id)) {
55
+ errors.push(`Duplicate group ID: ${group.id}`);
56
+ }
57
+ groupIds.add(group.id);
58
+ // Validate group ID legality
59
+ if (DOT_KEYWORDS.has(group.id.toLowerCase())) {
60
+ warnings.push(`Group ID '${group.id}' is a DOT reserved keyword. ` +
61
+ `It will be sanitized during compilation.`);
62
+ }
63
+ }
64
+ }
65
+ // Validate edges reference existing nodes
66
+ for (const edge of graph.edges) {
67
+ if (!nodeIds.has(edge.from)) {
68
+ errors.push(`Edge references non-existent source node: ${edge.from}`);
69
+ }
70
+ if (!nodeIds.has(edge.to)) {
71
+ errors.push(`Edge references non-existent target node: ${edge.to}`);
72
+ }
73
+ if (edge.from === edge.to) {
74
+ warnings.push(`Self-loop detected: ${edge.from} -> ${edge.to}`);
75
+ }
76
+ }
77
+ // Validate node groups reference existing groups
78
+ if (graph.groups) {
79
+ const groupIds = new Set(graph.groups.map(g => g.id));
80
+ for (const node of graph.nodes) {
81
+ if (node.group && !groupIds.has(node.group)) {
82
+ errors.push(`Node ${node.id} references non-existent group: ${node.group}`);
83
+ }
84
+ }
85
+ // Check for circular group references
86
+ for (const group of graph.groups) {
87
+ if (group.parent && !groupIds.has(group.parent)) {
88
+ errors.push(`Group ${group.id} references non-existent parent: ${group.parent}`);
89
+ }
90
+ }
91
+ }
92
+ // Warn if graph is too complex
93
+ if (graph.nodes.length > 50) {
94
+ warnings.push(`Graph has ${graph.nodes.length} nodes, consider splitting into multiple diagrams`);
95
+ }
96
+ if (graph.edges.length > 100) {
97
+ warnings.push(`Graph has ${graph.edges.length} edges, consider simplifying`);
98
+ }
99
+ return {
100
+ valid: errors.length === 0,
101
+ errors: errors.length > 0 ? errors : undefined,
102
+ warnings: warnings.length > 0 ? warnings : undefined
103
+ };
104
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Debug utilities for Limo CLI
3
+ * Provides structured logging with different levels
4
+ */
5
+ export declare class DebugLogger {
6
+ private startTime;
7
+ constructor();
8
+ private get enabled();
9
+ private get apiDebugEnabled();
10
+ private getTimestamp;
11
+ debug(...args: any[]): void;
12
+ info(...args: any[]): void;
13
+ warn(...args: any[]): void;
14
+ error(...args: any[]): void;
15
+ success(...args: any[]): void;
16
+ api(direction: 'request' | 'response', data: any): void;
17
+ toolCall(toolName: string, params: any): void;
18
+ toolResult(toolName: string, result: any): void;
19
+ agentStart(agentName: string, inputs: any): void;
20
+ agentEnd(agentName: string, outputs: any): void;
21
+ section(title: string): void;
22
+ object(label: string, obj: any): void;
23
+ memory(action: 'store' | 'recall', key: string, data?: any): void;
24
+ progress(current: number, total: number, message: string): void;
25
+ }
26
+ export declare const debug: DebugLogger;
27
+ export declare function isDebugEnabled(): boolean;
28
+ export declare function isApiDebugEnabled(): boolean;
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Debug utilities for Limo CLI
3
+ * Provides structured logging with different levels
4
+ */
5
+ import chalk from 'chalk';
6
+ export class DebugLogger {
7
+ constructor() {
8
+ this.startTime = Date.now();
9
+ }
10
+ get enabled() {
11
+ return process.env.LIMO_DEBUG === 'true';
12
+ }
13
+ get apiDebugEnabled() {
14
+ return process.env.LIMO_DEBUG_API === 'true';
15
+ }
16
+ getTimestamp() {
17
+ const elapsed = Date.now() - this.startTime;
18
+ const seconds = (elapsed / 1000).toFixed(3);
19
+ return chalk.gray(`[+${seconds}s]`);
20
+ }
21
+ debug(...args) {
22
+ if (this.enabled) {
23
+ console.log(this.getTimestamp(), chalk.cyan('[DEBUG]'), ...args);
24
+ }
25
+ }
26
+ info(...args) {
27
+ console.log(this.getTimestamp(), chalk.blue('[INFO]'), ...args);
28
+ }
29
+ warn(...args) {
30
+ console.log(this.getTimestamp(), chalk.yellow('[WARN]'), ...args);
31
+ }
32
+ error(...args) {
33
+ console.log(this.getTimestamp(), chalk.red('[ERROR]'), ...args);
34
+ }
35
+ success(...args) {
36
+ console.log(this.getTimestamp(), chalk.green('[SUCCESS]'), ...args);
37
+ }
38
+ api(direction, data) {
39
+ if (this.apiDebugEnabled) {
40
+ const prefix = direction === 'request' ? '→' : '←';
41
+ console.log(this.getTimestamp(), chalk.magenta(`[API ${prefix}]`));
42
+ if (typeof data === 'string') {
43
+ // Truncate long strings
44
+ const truncated = data.length > 500 ? data.substring(0, 500) + '...' : data;
45
+ console.log(chalk.gray(truncated));
46
+ }
47
+ else {
48
+ console.log(JSON.stringify(data, null, 2));
49
+ }
50
+ }
51
+ }
52
+ toolCall(toolName, params) {
53
+ if (this.enabled) {
54
+ console.log(this.getTimestamp(), chalk.yellow('[TOOL]'), toolName);
55
+ console.log(chalk.gray(' Parameters:'), JSON.stringify(params, null, 2));
56
+ }
57
+ }
58
+ toolResult(toolName, result) {
59
+ if (this.enabled) {
60
+ const status = result.success ? chalk.green('✓') : chalk.red('✗');
61
+ console.log(this.getTimestamp(), chalk.yellow('[TOOL]'), toolName, status);
62
+ if (result.error) {
63
+ console.log(chalk.red(' Error:'), result.error);
64
+ }
65
+ else if (result.data) {
66
+ const dataStr = typeof result.data === 'string'
67
+ ? result.data.substring(0, 200)
68
+ : JSON.stringify(result.data, null, 2).substring(0, 200);
69
+ console.log(chalk.gray(' Result:'), dataStr + (dataStr.length >= 200 ? '...' : ''));
70
+ }
71
+ }
72
+ }
73
+ agentStart(agentName, inputs) {
74
+ if (this.enabled) {
75
+ console.log('\n' + chalk.bgCyan.black(` AGENT START: ${agentName.toUpperCase()} `));
76
+ console.log(chalk.gray('Inputs:'), JSON.stringify(inputs, null, 2));
77
+ }
78
+ }
79
+ agentEnd(agentName, outputs) {
80
+ if (this.enabled) {
81
+ console.log(chalk.bgGreen.black(` AGENT END: ${agentName.toUpperCase()} `));
82
+ console.log(chalk.gray('Outputs:'), JSON.stringify(outputs, null, 2));
83
+ console.log('');
84
+ }
85
+ }
86
+ section(title) {
87
+ if (this.enabled) {
88
+ console.log('\n' + chalk.cyan('═'.repeat(60)));
89
+ console.log(chalk.cyan.bold(` ${title}`));
90
+ console.log(chalk.cyan('═'.repeat(60)) + '\n');
91
+ }
92
+ }
93
+ object(label, obj) {
94
+ if (this.enabled) {
95
+ console.log(this.getTimestamp(), chalk.cyan('[OBJECT]'), label);
96
+ console.log(JSON.stringify(obj, null, 2));
97
+ }
98
+ }
99
+ memory(action, key, data) {
100
+ if (this.enabled) {
101
+ const icon = action === 'store' ? '💾' : '🔍';
102
+ console.log(this.getTimestamp(), chalk.magenta(`[MEMORY ${icon}]`), key);
103
+ if (data && action === 'store') {
104
+ console.log(chalk.gray(' Content length:'), data.content?.length || 0);
105
+ console.log(chalk.gray(' Importance:'), data.importance);
106
+ console.log(chalk.gray(' Category:'), data.category);
107
+ }
108
+ }
109
+ }
110
+ progress(current, total, message) {
111
+ if (this.enabled) {
112
+ const percentage = ((current / total) * 100).toFixed(0);
113
+ console.log(this.getTimestamp(), chalk.blue('[PROGRESS]'), `[${current}/${total}]`, `${percentage}%`, message);
114
+ }
115
+ }
116
+ }
117
+ // Global singleton instance
118
+ export const debug = new DebugLogger();
119
+ // Helper function to check if debug is enabled
120
+ export function isDebugEnabled() {
121
+ return process.env.LIMO_DEBUG === 'true';
122
+ }
123
+ export function isApiDebugEnabled() {
124
+ return process.env.LIMO_DEBUG_API === 'true';
125
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * LIMO.md Configuration Parser
3
+ *
4
+ * Parses user-provided LIMO.md files to extract analysis requirements,
5
+ * focus areas, constraints, and private context.
6
+ */
7
+ /**
8
+ * LIMO configuration interface
9
+ */
10
+ export interface LimoConfiguration {
11
+ rawContent: string;
12
+ focusAreas?: string[];
13
+ excludeModules?: string[];
14
+ scopeDescription?: string;
15
+ privateContext?: string;
16
+ constraints?: string[];
17
+ }
18
+ /**
19
+ * Parse LIMO.md file and extract configuration
20
+ */
21
+ export declare function parseLimoConfig(workspaceRoot: string): Promise<LimoConfiguration | undefined>;
@@ -0,0 +1,274 @@
1
+ /**
2
+ * LIMO.md Configuration Parser
3
+ *
4
+ * Parses user-provided LIMO.md files to extract analysis requirements,
5
+ * focus areas, constraints, and private context.
6
+ */
7
+ import * as fs from 'fs/promises';
8
+ import * as path from 'path';
9
+ import { debug } from './debug.js';
10
+ /**
11
+ * Valid analysis module names
12
+ */
13
+ const VALID_MODULES = [
14
+ 'architecture',
15
+ 'dependencies',
16
+ 'code-quality',
17
+ 'security',
18
+ 'database',
19
+ 'testing',
20
+ 'migration',
21
+ 'migration-verification'
22
+ ];
23
+ /**
24
+ * Parse LIMO.md file and extract configuration
25
+ */
26
+ export async function parseLimoConfig(workspaceRoot) {
27
+ const limoPath = path.join(workspaceRoot, 'LIMO.md');
28
+ try {
29
+ await fs.access(limoPath);
30
+ }
31
+ catch {
32
+ debug.info('No LIMO.md file found - using default analysis scope');
33
+ return undefined;
34
+ }
35
+ try {
36
+ const content = await fs.readFile(limoPath, 'utf-8');
37
+ debug.info('Found LIMO.md configuration file');
38
+ // Parse configuration (flexible, keyword-based parsing)
39
+ return {
40
+ rawContent: content,
41
+ focusAreas: extractFocusAreas(content),
42
+ excludeModules: extractExcludeModules(content),
43
+ scopeDescription: extractScopeDescription(content),
44
+ privateContext: extractPrivateContext(content),
45
+ constraints: extractConstraints(content)
46
+ };
47
+ }
48
+ catch (error) {
49
+ debug.warn('Failed to parse LIMO.md - using default analysis scope', error);
50
+ return undefined;
51
+ }
52
+ }
53
+ /**
54
+ * Extract focus areas from LIMO.md content
55
+ * Looks for sections like "Focus Areas" or keywords like "only analyze", "focus on"
56
+ */
57
+ function extractFocusAreas(content) {
58
+ const areas = [];
59
+ // Look for explicit focus areas section
60
+ const focusSectionMatch = content.match(/##?\s*Focus\s*Areas?[:\s]*\n([\s\S]*?)(?=\n##|$)/i);
61
+ if (focusSectionMatch) {
62
+ const focusSection = focusSectionMatch[1];
63
+ // Extract list items
64
+ const listItems = focusSection.match(/^[\s-*]+(.+)$/gm);
65
+ if (listItems) {
66
+ listItems.forEach(item => {
67
+ const cleaned = item.replace(/^[\s-*]+/, '').trim().toLowerCase();
68
+ if (VALID_MODULES.includes(cleaned)) {
69
+ areas.push(cleaned);
70
+ }
71
+ });
72
+ }
73
+ }
74
+ // Look for "Include:" or "What to analyze:" sections
75
+ const includeMatch = content.match(/(?:Include|What to analyze)[:\s]*\n([\s\S]*?)(?=\n##|(?:Exclude|What to skip)|$)/i);
76
+ if (includeMatch) {
77
+ const includeSection = includeMatch[1];
78
+ const listItems = includeSection.match(/^[\s-*]+(.+)$/gm);
79
+ if (listItems) {
80
+ listItems.forEach(item => {
81
+ const cleaned = item.replace(/^[\s-*]+/, '').trim().toLowerCase();
82
+ if (VALID_MODULES.includes(cleaned)) {
83
+ areas.push(cleaned);
84
+ }
85
+ });
86
+ }
87
+ }
88
+ // Look for keywords in natural language
89
+ const naturalLanguagePatterns = [
90
+ /(?:only|just)\s+(?:analyze|focus\s+on|check)\s+(?:the\s+)?(\w+(?:\s+and\s+\w+)*)/gi,
91
+ /focus\s+(?:on|areas?)[:\s]+(.+?)(?:\.|$)/gi,
92
+ /concentrate\s+on[:\s]+(.+?)(?:\.|$)/gi
93
+ ];
94
+ naturalLanguagePatterns.forEach(pattern => {
95
+ let match;
96
+ while ((match = pattern.exec(content)) !== null) {
97
+ const mentioned = match[1].toLowerCase().split(/\s+(?:and|,)\s+/);
98
+ mentioned.forEach(word => {
99
+ const cleaned = word.trim().replace(/[.,;!?]/, '');
100
+ if (VALID_MODULES.includes(cleaned)) {
101
+ areas.push(cleaned);
102
+ }
103
+ });
104
+ }
105
+ });
106
+ // Validate and deduplicate
107
+ const validAreas = [...new Set(areas)].filter(area => VALID_MODULES.includes(area));
108
+ const invalidAreas = [...new Set(areas)].filter(area => !VALID_MODULES.includes(area));
109
+ if (invalidAreas.length > 0) {
110
+ debug.warn(`Invalid module names in LIMO.md: ${invalidAreas.join(', ')}`);
111
+ debug.warn(`Valid modules: ${VALID_MODULES.join(', ')}`);
112
+ }
113
+ return validAreas.length > 0 ? validAreas : undefined;
114
+ }
115
+ /**
116
+ * Extract excluded modules from LIMO.md content
117
+ * Looks for keywords like "skip", "ignore", "don't analyze", "exclude"
118
+ */
119
+ function extractExcludeModules(content) {
120
+ const excludes = [];
121
+ // Look for explicit exclude section
122
+ const excludeSectionMatch = content.match(/##?\s*(?:Exclude|What to skip)[:\s]*\n([\s\S]*?)(?=\n##|$)/i);
123
+ if (excludeSectionMatch) {
124
+ const excludeSection = excludeSectionMatch[1];
125
+ // Extract list items
126
+ const listItems = excludeSection.match(/^[\s-*]+(.+)$/gm);
127
+ if (listItems) {
128
+ listItems.forEach(item => {
129
+ const cleaned = item.replace(/^[\s-*]+/, '').trim().toLowerCase();
130
+ if (VALID_MODULES.includes(cleaned)) {
131
+ excludes.push(cleaned);
132
+ }
133
+ });
134
+ }
135
+ }
136
+ // Look for "Exclude:" in focus areas section
137
+ const excludeMatch = content.match(/Exclude[:\s]*\n([\s\S]*?)(?=\n##|\n\*\*|$)/i);
138
+ if (excludeMatch) {
139
+ const excludeSection = excludeMatch[1];
140
+ const listItems = excludeSection.match(/^[\s-*]+(.+)$/gm);
141
+ if (listItems) {
142
+ listItems.forEach(item => {
143
+ const cleaned = item.replace(/^[\s-*]+/, '').trim().toLowerCase();
144
+ if (VALID_MODULES.includes(cleaned)) {
145
+ excludes.push(cleaned);
146
+ }
147
+ });
148
+ }
149
+ }
150
+ // Look for keywords in natural language
151
+ const naturalLanguagePatterns = [
152
+ /(?:skip|ignore|don't\s+analyze|exclude)\s+(?:the\s+)?(\w+(?:\s+and\s+\w+)*)/gi,
153
+ /no\s+(\w+)\s+(?:analysis|module)/gi
154
+ ];
155
+ naturalLanguagePatterns.forEach(pattern => {
156
+ let match;
157
+ while ((match = pattern.exec(content)) !== null) {
158
+ const mentioned = match[1].toLowerCase().split(/\s+(?:and|,)\s+/);
159
+ mentioned.forEach(word => {
160
+ const cleaned = word.trim().replace(/[.,;!?]/, '');
161
+ if (VALID_MODULES.includes(cleaned)) {
162
+ excludes.push(cleaned);
163
+ }
164
+ });
165
+ }
166
+ });
167
+ // Validate and deduplicate
168
+ const validExcludes = [...new Set(excludes)].filter(area => VALID_MODULES.includes(area));
169
+ return validExcludes.length > 0 ? validExcludes : undefined;
170
+ }
171
+ /**
172
+ * Extract scope description from LIMO.md content
173
+ * Looks for sections describing what user wants analyzed
174
+ */
175
+ function extractScopeDescription(content) {
176
+ // Look for "Analysis Scope" section
177
+ const scopeSectionMatch = content.match(/##?\s*Analysis\s*Scope[:\s]*\n([\s\S]*?)(?=\n##|$)/i);
178
+ if (scopeSectionMatch) {
179
+ const scopeSection = scopeSectionMatch[1].trim();
180
+ // Remove empty lines and HTML comments
181
+ const cleaned = scopeSection
182
+ .replace(/<!--[\s\S]*?-->/g, '')
183
+ .replace(/^\s*$/gm, '')
184
+ .trim();
185
+ if (cleaned && cleaned.length > 10) {
186
+ return cleaned;
187
+ }
188
+ }
189
+ // Look for "What to analyze:" section
190
+ const whatToAnalyzeMatch = content.match(/\*\*What to analyze:\*\*\s*\n([\s\S]*?)(?=\n\*\*|$)/i);
191
+ if (whatToAnalyzeMatch) {
192
+ const whatSection = whatToAnalyzeMatch[1].trim();
193
+ const cleaned = whatSection
194
+ .replace(/<!--[\s\S]*?-->/g, '')
195
+ .replace(/^\s*$/gm, '')
196
+ .trim();
197
+ if (cleaned && cleaned.length > 10) {
198
+ return cleaned;
199
+ }
200
+ }
201
+ // Look for natural language descriptions
202
+ const descriptionPatterns = [
203
+ /(?:analyze|focus\s+on|examine)\s+(?:only|just)?\s*(?:the\s+)?(.+?)(?:\.|$)/i,
204
+ /scope[:\s]+(.+?)(?:\.|$)/i
205
+ ];
206
+ for (const pattern of descriptionPatterns) {
207
+ const match = content.match(pattern);
208
+ if (match && match[1].length > 10) {
209
+ return match[1].trim();
210
+ }
211
+ }
212
+ return undefined;
213
+ }
214
+ /**
215
+ * Extract private/company context from LIMO.md content
216
+ * Looks for sections with company-specific info, constraints, etc.
217
+ */
218
+ function extractPrivateContext(content) {
219
+ // Look for context sections
220
+ const contextPatterns = [
221
+ /##?\s*(?:Private|Company|Domain|Project)\s*Context[:\s]*\n([\s\S]*?)(?=\n##|$)/i,
222
+ /##?\s*(?:Production|Environment|Company)\s*(?:Environment|Context|Info)[:\s]*\n([\s\S]*?)(?=\n##|$)/i,
223
+ /##?\s*(?:Security|Business)\s*Requirements?[:\s]*\n([\s\S]*?)(?=\n##|$)/i
224
+ ];
225
+ for (const pattern of contextPatterns) {
226
+ const match = content.match(pattern);
227
+ if (match) {
228
+ const context = match[1].trim();
229
+ // Remove HTML comments
230
+ const cleaned = context
231
+ .replace(/<!--[\s\S]*?-->/g, '')
232
+ .replace(/^\s*$/gm, '')
233
+ .trim();
234
+ if (cleaned && cleaned.length > 10) {
235
+ return cleaned;
236
+ }
237
+ }
238
+ }
239
+ return undefined;
240
+ }
241
+ /**
242
+ * Extract constraints from LIMO.md content
243
+ * Looks for specific constraints like "no external dependencies", "must use X"
244
+ */
245
+ function extractConstraints(content) {
246
+ const constraints = [];
247
+ // Look for constraints section
248
+ const constraintsSectionMatch = content.match(/##?\s*Constraints?[:\s]*\n([\s\S]*?)(?=\n##|$)/i);
249
+ if (constraintsSectionMatch) {
250
+ const constraintsSection = constraintsSectionMatch[1];
251
+ // Extract list items or numbered items
252
+ const listItems = constraintsSection.match(/^[\s-*\d.]+(.+)$/gm);
253
+ if (listItems) {
254
+ listItems.forEach(item => {
255
+ const cleaned = item.replace(/^[\s-*\d.]+/, '').trim();
256
+ if (cleaned && cleaned.length > 5) {
257
+ constraints.push(cleaned);
258
+ }
259
+ });
260
+ }
261
+ }
262
+ // Look for "must" or "cannot" statements
263
+ const mustPattern = /(?:must|cannot|should not|can't|don't)\s+(.+?)(?:\.|$)/gi;
264
+ let match;
265
+ while ((match = mustPattern.exec(content)) !== null) {
266
+ const constraint = match[0].trim();
267
+ if (constraint.length > 10 && constraint.length < 200) {
268
+ constraints.push(constraint);
269
+ }
270
+ }
271
+ // Deduplicate
272
+ const uniqueConstraints = [...new Set(constraints)];
273
+ return uniqueConstraints.length > 0 ? uniqueConstraints : undefined;
274
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Review Folder Monitor
3
+ * Watches for new files in the review/ directory and checks for comments
4
+ */
5
+ export declare class ReviewMonitor {
6
+ private reviewPath;
7
+ private watcher;
8
+ private processedFiles;
9
+ constructor(workspaceRoot: string);
10
+ start(): Promise<void>;
11
+ stop(): void;
12
+ private processExistingFiles;
13
+ private processFile;
14
+ private extractComments;
15
+ getStats(): {
16
+ totalFiles: number;
17
+ processedFiles: number;
18
+ };
19
+ }
20
+ export default ReviewMonitor;