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.
- package/.workflow/agents/reviewer.md +81 -0
- package/.workflow/agents/security.md +94 -0
- package/.workflow/agents/story-writer.md +58 -0
- package/.workflow/bridges/base-bridge.js +395 -0
- package/.workflow/bridges/claude-bridge.js +434 -0
- package/.workflow/bridges/index.js +130 -0
- package/.workflow/lib/assumption-detector.js +481 -0
- package/.workflow/lib/config-substitution.js +371 -0
- package/.workflow/lib/failure-categories.js +478 -0
- package/.workflow/state/app-map.md.template +15 -0
- package/.workflow/state/architecture.md.template +24 -0
- package/.workflow/state/component-index.json.template +5 -0
- package/.workflow/state/decisions.md.template +15 -0
- package/.workflow/state/feedback-patterns.md.template +9 -0
- package/.workflow/state/knowledge-sync.json.template +6 -0
- package/.workflow/state/progress.md.template +14 -0
- package/.workflow/state/ready.json.template +7 -0
- package/.workflow/state/request-log.md.template +14 -0
- package/.workflow/state/session-state.json.template +11 -0
- package/.workflow/state/stack.md.template +33 -0
- package/.workflow/state/testing.md.template +36 -0
- package/.workflow/templates/claude-md.hbs +257 -0
- package/.workflow/templates/correction-report.md +67 -0
- package/.workflow/templates/gemini-md.hbs +52 -0
- package/README.md +1802 -0
- package/bin/flow +205 -0
- package/lib/index.js +33 -0
- package/lib/installer.js +467 -0
- package/lib/release-channel.js +269 -0
- package/lib/skill-registry.js +526 -0
- package/lib/upgrader.js +401 -0
- package/lib/utils.js +305 -0
- package/package.json +64 -0
- package/scripts/flow +985 -0
- package/scripts/flow-adaptive-learning.js +1259 -0
- package/scripts/flow-aggregate.js +488 -0
- package/scripts/flow-archive +133 -0
- package/scripts/flow-auto-context.js +1015 -0
- package/scripts/flow-auto-learn.js +615 -0
- package/scripts/flow-bridge.js +223 -0
- package/scripts/flow-browser-suggest.js +316 -0
- package/scripts/flow-bug.js +247 -0
- package/scripts/flow-cascade.js +711 -0
- package/scripts/flow-changelog +85 -0
- package/scripts/flow-checkpoint.js +483 -0
- package/scripts/flow-cli.js +403 -0
- package/scripts/flow-code-intelligence.js +760 -0
- package/scripts/flow-complexity.js +502 -0
- package/scripts/flow-config-set.js +152 -0
- package/scripts/flow-constants.js +157 -0
- package/scripts/flow-context +152 -0
- package/scripts/flow-context-init.js +482 -0
- package/scripts/flow-context-monitor.js +384 -0
- package/scripts/flow-context-scoring.js +886 -0
- package/scripts/flow-correct.js +458 -0
- package/scripts/flow-damage-control.js +985 -0
- package/scripts/flow-deps +101 -0
- package/scripts/flow-diff.js +700 -0
- package/scripts/flow-done +151 -0
- package/scripts/flow-done.js +489 -0
- package/scripts/flow-durable-session.js +1541 -0
- package/scripts/flow-entropy-monitor.js +345 -0
- package/scripts/flow-export-profile +349 -0
- package/scripts/flow-export-scanner.js +1046 -0
- package/scripts/flow-figma-confirm.js +400 -0
- package/scripts/flow-figma-extract.js +496 -0
- package/scripts/flow-figma-generate.js +683 -0
- package/scripts/flow-figma-index.js +909 -0
- package/scripts/flow-figma-match.js +617 -0
- package/scripts/flow-figma-mcp-server.js +518 -0
- package/scripts/flow-figma-pipeline.js +414 -0
- package/scripts/flow-file-ops.js +301 -0
- package/scripts/flow-gate-confidence.js +825 -0
- package/scripts/flow-guided-edit.js +659 -0
- package/scripts/flow-health +185 -0
- package/scripts/flow-health.js +413 -0
- package/scripts/flow-hooks.js +556 -0
- package/scripts/flow-http-client.js +249 -0
- package/scripts/flow-hybrid-detect.js +167 -0
- package/scripts/flow-hybrid-interactive.js +591 -0
- package/scripts/flow-hybrid-test.js +152 -0
- package/scripts/flow-import-profile +439 -0
- package/scripts/flow-init +253 -0
- package/scripts/flow-instruction-richness.js +827 -0
- package/scripts/flow-jira-integration.js +579 -0
- package/scripts/flow-knowledge-router.js +522 -0
- package/scripts/flow-knowledge-sync.js +589 -0
- package/scripts/flow-linear-integration.js +631 -0
- package/scripts/flow-links.js +774 -0
- package/scripts/flow-log-manager.js +559 -0
- package/scripts/flow-loop-enforcer.js +1246 -0
- package/scripts/flow-loop-retry-learning.js +630 -0
- package/scripts/flow-lsp.js +923 -0
- package/scripts/flow-map-index +348 -0
- package/scripts/flow-map-sync +201 -0
- package/scripts/flow-memory-blocks.js +668 -0
- package/scripts/flow-memory-compactor.js +350 -0
- package/scripts/flow-memory-db.js +1110 -0
- package/scripts/flow-memory-sync.js +484 -0
- package/scripts/flow-metrics.js +353 -0
- package/scripts/flow-migrate-ids.js +370 -0
- package/scripts/flow-model-adapter.js +802 -0
- package/scripts/flow-model-router.js +884 -0
- package/scripts/flow-models.js +1231 -0
- package/scripts/flow-morning.js +517 -0
- package/scripts/flow-multi-approach.js +660 -0
- package/scripts/flow-new-feature +86 -0
- package/scripts/flow-onboard +1042 -0
- package/scripts/flow-orchestrate-llm.js +459 -0
- package/scripts/flow-orchestrate.js +3592 -0
- package/scripts/flow-output.js +123 -0
- package/scripts/flow-parallel-detector.js +399 -0
- package/scripts/flow-parallel-dispatch.js +987 -0
- package/scripts/flow-parallel.js +428 -0
- package/scripts/flow-pattern-enforcer.js +600 -0
- package/scripts/flow-prd-manager.js +282 -0
- package/scripts/flow-progress.js +323 -0
- package/scripts/flow-project-analyzer.js +975 -0
- package/scripts/flow-prompt-composer.js +487 -0
- package/scripts/flow-providers.js +1381 -0
- package/scripts/flow-queue.js +308 -0
- package/scripts/flow-ready +82 -0
- package/scripts/flow-ready.js +189 -0
- package/scripts/flow-regression.js +396 -0
- package/scripts/flow-response-parser.js +450 -0
- package/scripts/flow-resume.js +284 -0
- package/scripts/flow-rules-sync.js +439 -0
- package/scripts/flow-run-trace.js +718 -0
- package/scripts/flow-safety.js +587 -0
- package/scripts/flow-search +104 -0
- package/scripts/flow-security.js +481 -0
- package/scripts/flow-session-end +106 -0
- package/scripts/flow-session-end.js +437 -0
- package/scripts/flow-session-state.js +671 -0
- package/scripts/flow-setup-hooks +216 -0
- package/scripts/flow-setup-hooks.js +377 -0
- package/scripts/flow-skill-create.js +329 -0
- package/scripts/flow-skill-creator.js +572 -0
- package/scripts/flow-skill-generator.js +1046 -0
- package/scripts/flow-skill-learn.js +880 -0
- package/scripts/flow-skill-matcher.js +578 -0
- package/scripts/flow-spec-generator.js +820 -0
- package/scripts/flow-stack-wizard.js +895 -0
- package/scripts/flow-standup +162 -0
- package/scripts/flow-start +74 -0
- package/scripts/flow-start.js +235 -0
- package/scripts/flow-status +110 -0
- package/scripts/flow-status.js +301 -0
- package/scripts/flow-step-browser.js +83 -0
- package/scripts/flow-step-changelog.js +217 -0
- package/scripts/flow-step-comments.js +306 -0
- package/scripts/flow-step-complexity.js +234 -0
- package/scripts/flow-step-coverage.js +218 -0
- package/scripts/flow-step-knowledge.js +193 -0
- package/scripts/flow-step-pr-tests.js +364 -0
- package/scripts/flow-step-regression.js +89 -0
- package/scripts/flow-step-review.js +516 -0
- package/scripts/flow-step-security.js +162 -0
- package/scripts/flow-step-silent-failures.js +290 -0
- package/scripts/flow-step-simplifier.js +346 -0
- package/scripts/flow-story +105 -0
- package/scripts/flow-story.js +500 -0
- package/scripts/flow-suspend.js +252 -0
- package/scripts/flow-sync-daemon.js +654 -0
- package/scripts/flow-task-analyzer.js +606 -0
- package/scripts/flow-team-dashboard.js +748 -0
- package/scripts/flow-team-sync.js +752 -0
- package/scripts/flow-team.js +977 -0
- package/scripts/flow-tech-options.js +528 -0
- package/scripts/flow-templates.js +812 -0
- package/scripts/flow-tiered-learning.js +728 -0
- package/scripts/flow-trace +204 -0
- package/scripts/flow-transcript-chunking.js +1106 -0
- package/scripts/flow-transcript-digest.js +7918 -0
- package/scripts/flow-transcript-language.js +465 -0
- package/scripts/flow-transcript-parsing.js +1085 -0
- package/scripts/flow-transcript-stories.js +2194 -0
- package/scripts/flow-update-map +224 -0
- package/scripts/flow-utils.js +2242 -0
- package/scripts/flow-verification.js +644 -0
- package/scripts/flow-verify.js +1177 -0
- package/scripts/flow-voice-input.js +638 -0
- package/scripts/flow-watch +168 -0
- package/scripts/flow-workflow-steps.js +521 -0
- package/scripts/flow-workflow.js +1029 -0
- package/scripts/flow-worktree.js +489 -0
- package/scripts/hooks/adapters/base-adapter.js +102 -0
- package/scripts/hooks/adapters/claude-code.js +359 -0
- package/scripts/hooks/adapters/index.js +79 -0
- package/scripts/hooks/core/component-check.js +341 -0
- package/scripts/hooks/core/index.js +35 -0
- package/scripts/hooks/core/loop-check.js +241 -0
- package/scripts/hooks/core/session-context.js +294 -0
- package/scripts/hooks/core/task-gate.js +177 -0
- package/scripts/hooks/core/validation.js +230 -0
- package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
- package/scripts/hooks/entry/claude-code/session-end.js +87 -0
- package/scripts/hooks/entry/claude-code/session-start.js +46 -0
- package/scripts/hooks/entry/claude-code/stop.js +43 -0
- package/scripts/postinstall.js +139 -0
- package/templates/browser-test-flow.json +56 -0
- package/templates/bug-report.md +43 -0
- package/templates/component-detail.md +42 -0
- package/templates/component.stories.tsx +49 -0
- package/templates/context/constraints.md +83 -0
- package/templates/context/conventions.md +177 -0
- package/templates/context/stack.md +60 -0
- package/templates/correction-report.md +90 -0
- package/templates/feature-proposal.md +35 -0
- package/templates/hybrid/_base.md +254 -0
- package/templates/hybrid/_patterns.md +45 -0
- package/templates/hybrid/create-component.md +127 -0
- package/templates/hybrid/create-file.md +56 -0
- package/templates/hybrid/create-hook.md +145 -0
- package/templates/hybrid/create-service.md +70 -0
- package/templates/hybrid/fix-bug.md +33 -0
- package/templates/hybrid/modify-file.md +55 -0
- package/templates/story.md +68 -0
- package/templates/task.json +56 -0
- package/templates/trace.md +69 -0
|
@@ -0,0 +1,827 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow - Instruction Richness Module
|
|
5
|
+
*
|
|
6
|
+
* Controls how much detail to include for the local LLM.
|
|
7
|
+
*
|
|
8
|
+
* KEY INSIGHT: Local LLM tokens are FREE. The goal is to give the LLM
|
|
9
|
+
* everything it needs for 90%+ success rate. Failed executions cost more
|
|
10
|
+
* (in Claude retry tokens) than generous upfront context.
|
|
11
|
+
*
|
|
12
|
+
* This module provides GUIDANCE on context richness, not hard limits.
|
|
13
|
+
* When in doubt, include MORE context - it's free for the local LLM!
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* const { getInstructionRichness } = require('./flow-instruction-richness');
|
|
17
|
+
* const richness = getInstructionRichness('large');
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
|
|
23
|
+
// LSP integration for accurate type information
|
|
24
|
+
let lspModule = null;
|
|
25
|
+
try {
|
|
26
|
+
lspModule = require('./flow-lsp');
|
|
27
|
+
} catch (err) {
|
|
28
|
+
// LSP module not available, will use fallback
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ============================================================
|
|
32
|
+
// Instruction Richness Levels (Guidance, NOT Limits)
|
|
33
|
+
// ============================================================
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Richness levels guide what context to include for the local LLM.
|
|
37
|
+
*
|
|
38
|
+
* IMPORTANT: These are MINIMUMS for each complexity level.
|
|
39
|
+
* Always include MORE context if there's any doubt - local LLM tokens are free!
|
|
40
|
+
*
|
|
41
|
+
* The goal is 90%+ success rate, not minimizing tokens.
|
|
42
|
+
*/
|
|
43
|
+
const INSTRUCTION_RICHNESS = {
|
|
44
|
+
minimal: {
|
|
45
|
+
// Even "minimal" should include enough for success
|
|
46
|
+
includeProjectContext: true, // Always include
|
|
47
|
+
includeTypeDefinitions: true, // Always include - prevents type errors
|
|
48
|
+
includeRelatedCode: false,
|
|
49
|
+
includeExamples: false,
|
|
50
|
+
includePatterns: true, // Always include - consistency matters
|
|
51
|
+
includeFullFileContents: false,
|
|
52
|
+
templateVerbosity: 'standard', // Upgraded from 'concise'
|
|
53
|
+
description: 'Simple changes - but still include types and patterns for accuracy'
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
standard: {
|
|
57
|
+
includeProjectContext: true,
|
|
58
|
+
includeTypeDefinitions: true,
|
|
59
|
+
includeRelatedCode: true, // Include related code for context
|
|
60
|
+
includeExamples: true, // Include examples - they help!
|
|
61
|
+
includePatterns: true,
|
|
62
|
+
includeFullFileContents: false,
|
|
63
|
+
templateVerbosity: 'detailed', // Upgraded from 'standard'
|
|
64
|
+
description: 'Typical tasks - include examples and related code for best results'
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
rich: {
|
|
68
|
+
includeProjectContext: true,
|
|
69
|
+
includeTypeDefinitions: true,
|
|
70
|
+
includeRelatedCode: true,
|
|
71
|
+
includeExamples: true,
|
|
72
|
+
includePatterns: true,
|
|
73
|
+
includeFullFileContents: true, // Include full files for complex tasks
|
|
74
|
+
templateVerbosity: 'comprehensive',
|
|
75
|
+
description: 'Complex tasks - full context for highest success rate'
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
maximum: {
|
|
79
|
+
includeProjectContext: true,
|
|
80
|
+
includeTypeDefinitions: true,
|
|
81
|
+
includeRelatedCode: true,
|
|
82
|
+
includeExamples: true,
|
|
83
|
+
includePatterns: true,
|
|
84
|
+
includeFullFileContents: true,
|
|
85
|
+
templateVerbosity: 'comprehensive',
|
|
86
|
+
description: 'XL tasks - everything available, maximum context'
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// ============================================================
|
|
91
|
+
// Complexity to Richness Mapping
|
|
92
|
+
// ============================================================
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Maps complexity level (from flow-complexity.js) to instruction richness
|
|
96
|
+
*/
|
|
97
|
+
const COMPLEXITY_TO_RICHNESS = {
|
|
98
|
+
'small': 'minimal',
|
|
99
|
+
'medium': 'standard',
|
|
100
|
+
'large': 'rich',
|
|
101
|
+
'xl': 'maximum'
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Gets the instruction richness configuration for a complexity level
|
|
106
|
+
*
|
|
107
|
+
* @param {string} complexityLevel - 'small', 'medium', 'large', or 'xl'
|
|
108
|
+
* @param {Object} config - Optional config overrides from config.json
|
|
109
|
+
* @returns {Object} - Richness configuration
|
|
110
|
+
*/
|
|
111
|
+
function getInstructionRichness(complexityLevel, config = {}) {
|
|
112
|
+
// Map complexity to richness level
|
|
113
|
+
let richnessLevel = COMPLEXITY_TO_RICHNESS[complexityLevel] || 'standard';
|
|
114
|
+
|
|
115
|
+
// Check for minimum richness override in config
|
|
116
|
+
const minRichness = config.minRichness;
|
|
117
|
+
if (minRichness) {
|
|
118
|
+
const levels = ['minimal', 'standard', 'rich', 'maximum'];
|
|
119
|
+
const currentIndex = levels.indexOf(richnessLevel);
|
|
120
|
+
const minIndex = levels.indexOf(minRichness);
|
|
121
|
+
if (minIndex > currentIndex) {
|
|
122
|
+
richnessLevel = minRichness;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const richness = { ...INSTRUCTION_RICHNESS[richnessLevel] };
|
|
127
|
+
|
|
128
|
+
// Add level name for reference
|
|
129
|
+
richness.level = richnessLevel;
|
|
130
|
+
|
|
131
|
+
return richness;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ============================================================
|
|
135
|
+
// Context Loaders
|
|
136
|
+
// ============================================================
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Loads project context from workflow state
|
|
140
|
+
*/
|
|
141
|
+
function loadProjectContext(projectRoot) {
|
|
142
|
+
const contextPath = path.join(projectRoot, '.workflow', 'state', 'hybrid-context.md');
|
|
143
|
+
const projectPath = path.join(projectRoot, '.workflow', 'specs', 'project.md');
|
|
144
|
+
|
|
145
|
+
let context = '';
|
|
146
|
+
|
|
147
|
+
// Try hybrid-context first (optimized for hybrid mode)
|
|
148
|
+
if (fs.existsSync(contextPath)) {
|
|
149
|
+
context += fs.readFileSync(contextPath, 'utf-8');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Add project overview
|
|
153
|
+
if (fs.existsSync(projectPath)) {
|
|
154
|
+
const projectMd = fs.readFileSync(projectPath, 'utf-8');
|
|
155
|
+
// Extract just the summary section
|
|
156
|
+
const summaryMatch = projectMd.match(/## Summary[\s\S]*?(?=##|$)/);
|
|
157
|
+
if (summaryMatch) {
|
|
158
|
+
context += '\n\n### Project Summary\n' + summaryMatch[0];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return context || null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Loads ALL coding patterns from decisions.md
|
|
167
|
+
* Extracts all ## sections, not just Coding Standards and Component Architecture
|
|
168
|
+
*/
|
|
169
|
+
function loadPatterns(projectRoot) {
|
|
170
|
+
const decisionsPath = path.join(projectRoot, '.workflow', 'state', 'decisions.md');
|
|
171
|
+
|
|
172
|
+
if (!fs.existsSync(decisionsPath)) {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const content = fs.readFileSync(decisionsPath, 'utf-8');
|
|
177
|
+
|
|
178
|
+
// Extract ALL ## sections from decisions.md
|
|
179
|
+
// This includes: Naming Conventions, File Structure, Error Handling, etc.
|
|
180
|
+
const sections = content.match(/## [^\n]+[\s\S]*?(?=\n## |$)/g);
|
|
181
|
+
|
|
182
|
+
if (!sections || sections.length === 0) {
|
|
183
|
+
// Fallback: return full content if no sections found
|
|
184
|
+
return content.trim() || null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return sections.map(s => s.trim()).join('\n\n');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Extracts keywords from task description for relevance filtering
|
|
192
|
+
*/
|
|
193
|
+
function extractTaskKeywords(taskDescription) {
|
|
194
|
+
if (!taskDescription) return [];
|
|
195
|
+
|
|
196
|
+
// Extract meaningful words (nouns, component names, etc.)
|
|
197
|
+
const words = taskDescription
|
|
198
|
+
.replace(/[^a-zA-Z0-9\s]/g, ' ')
|
|
199
|
+
.split(/\s+/)
|
|
200
|
+
.filter(w => w.length > 2)
|
|
201
|
+
.map(w => w.toLowerCase());
|
|
202
|
+
|
|
203
|
+
// Also extract PascalCase component names
|
|
204
|
+
const pascalCaseNames = taskDescription.match(/[A-Z][a-z]+(?:[A-Z][a-z]+)*/g) || [];
|
|
205
|
+
|
|
206
|
+
return [...new Set([...words, ...pascalCaseNames.map(n => n.toLowerCase())])];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Checks if a type definition is relevant to the task
|
|
211
|
+
*/
|
|
212
|
+
function isTypeRelevant(typeDefinition, keywords, filename) {
|
|
213
|
+
if (!keywords || keywords.length === 0) return true;
|
|
214
|
+
|
|
215
|
+
const typeLower = typeDefinition.toLowerCase();
|
|
216
|
+
const filenameLower = filename.toLowerCase();
|
|
217
|
+
|
|
218
|
+
// Always include types that match the filename
|
|
219
|
+
if (typeLower.includes(filenameLower) || filenameLower.includes(typeLower.split(/\s+/)[1]?.toLowerCase() || '')) {
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Check if any keyword appears in the type definition
|
|
224
|
+
return keywords.some(keyword => typeLower.includes(keyword));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Finds TypeScript types relevant to a file path and task
|
|
229
|
+
* @param {string} projectRoot - Project root directory
|
|
230
|
+
* @param {string} filePath - Target file path
|
|
231
|
+
* @param {Object} options - Options including taskDescription and maxTypes
|
|
232
|
+
*/
|
|
233
|
+
function loadRelevantTypes(projectRoot, filePath, options = {}) {
|
|
234
|
+
if (!filePath) return null;
|
|
235
|
+
|
|
236
|
+
const { taskDescription = '', maxTypes = 5 } = options;
|
|
237
|
+
const keywords = extractTaskKeywords(taskDescription);
|
|
238
|
+
const types = [];
|
|
239
|
+
const dir = path.dirname(filePath);
|
|
240
|
+
const filename = path.basename(filePath, path.extname(filePath));
|
|
241
|
+
|
|
242
|
+
// Common type file locations - prioritize closest first
|
|
243
|
+
const typeLocations = [
|
|
244
|
+
path.join(dir, 'types.ts'),
|
|
245
|
+
path.join(dir, 'types.d.ts'),
|
|
246
|
+
path.join(dir, '..', 'types.ts'),
|
|
247
|
+
path.join(dir, '..', 'types', 'index.ts'),
|
|
248
|
+
path.join(dir, '..', 'api', 'types.ts'),
|
|
249
|
+
path.join(projectRoot, 'src', 'types', 'index.ts'),
|
|
250
|
+
path.join(projectRoot, 'src', 'types.ts')
|
|
251
|
+
];
|
|
252
|
+
|
|
253
|
+
for (const typePath of typeLocations) {
|
|
254
|
+
const fullPath = path.isAbsolute(typePath) ? typePath : path.join(projectRoot, typePath);
|
|
255
|
+
if (fs.existsSync(fullPath)) {
|
|
256
|
+
try {
|
|
257
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
258
|
+
// Extract interface/type definitions (simplified)
|
|
259
|
+
const typeMatches = content.match(/(?:export\s+)?(?:interface|type)\s+\w+[\s\S]*?(?=\n(?:export\s+)?(?:interface|type|const|function)|$)/g);
|
|
260
|
+
if (typeMatches) {
|
|
261
|
+
// Filter types by relevance
|
|
262
|
+
const relevantTypes = typeMatches.filter(t => isTypeRelevant(t, keywords, filename));
|
|
263
|
+
|
|
264
|
+
if (relevantTypes.length > 0) {
|
|
265
|
+
types.push(`// From ${path.relative(projectRoot, fullPath)}`);
|
|
266
|
+
types.push(...relevantTypes.slice(0, maxTypes - types.length));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
} catch {
|
|
270
|
+
// Ignore read errors
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Stop if we have enough types
|
|
275
|
+
if (types.length >= maxTypes) break;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return types.length > 0 ? types.join('\n\n') : null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* LSP-enhanced type loading
|
|
283
|
+
* Uses Language Server Protocol when available for accurate type info,
|
|
284
|
+
* falls back to regex-based loading otherwise.
|
|
285
|
+
*
|
|
286
|
+
* @param {string} projectRoot - Project root directory
|
|
287
|
+
* @param {string} filePath - Target file path
|
|
288
|
+
* @param {object} options - Options (taskDescription, maxTypes)
|
|
289
|
+
* @returns {Promise<string|null>} Formatted type information
|
|
290
|
+
*/
|
|
291
|
+
async function loadRelevantTypesWithLSP(projectRoot, filePath, options = {}) {
|
|
292
|
+
const { getConfig } = require('./flow-utils');
|
|
293
|
+
const config = getConfig();
|
|
294
|
+
|
|
295
|
+
// Check if LSP is enabled
|
|
296
|
+
if (!config.lsp?.enabled || !lspModule) {
|
|
297
|
+
return loadRelevantTypes(projectRoot, filePath, options);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
const lsp = await lspModule.getLSP(projectRoot);
|
|
302
|
+
if (!lsp) {
|
|
303
|
+
return loadRelevantTypes(projectRoot, filePath, options);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const types = [];
|
|
307
|
+
const { taskDescription = '', maxTypes = 5 } = options;
|
|
308
|
+
const keywords = extractTaskKeywords(taskDescription);
|
|
309
|
+
|
|
310
|
+
// If we have a target file, extract identifiers and get their types
|
|
311
|
+
const absPath = path.isAbsolute(filePath) ? filePath : path.join(projectRoot, filePath);
|
|
312
|
+
if (fs.existsSync(absPath)) {
|
|
313
|
+
const content = fs.readFileSync(absPath, 'utf-8');
|
|
314
|
+
const identifiers = extractIdentifiersForLSP(content, keywords);
|
|
315
|
+
|
|
316
|
+
// Get types for identified positions
|
|
317
|
+
for (const id of identifiers.slice(0, maxTypes)) {
|
|
318
|
+
try {
|
|
319
|
+
const typeInfo = await lsp.getTypeAtPosition(absPath, id.line, id.character);
|
|
320
|
+
if (typeInfo) {
|
|
321
|
+
types.push(`// ${id.name}\n${typeInfo}`);
|
|
322
|
+
}
|
|
323
|
+
} catch (err) {
|
|
324
|
+
// Skip individual errors
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// If LSP didn't find enough types, supplement with regex-based loading
|
|
330
|
+
if (types.length < maxTypes) {
|
|
331
|
+
const regexTypes = loadRelevantTypes(projectRoot, filePath, {
|
|
332
|
+
...options,
|
|
333
|
+
maxTypes: maxTypes - types.length
|
|
334
|
+
});
|
|
335
|
+
if (regexTypes) {
|
|
336
|
+
types.push(regexTypes);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return types.length > 0 ? types.join('\n\n') : null;
|
|
341
|
+
} catch (err) {
|
|
342
|
+
// Fallback to regex-based loading on any error
|
|
343
|
+
return loadRelevantTypes(projectRoot, filePath, options);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Extract identifiers from code that we want to get types for
|
|
349
|
+
* @param {string} content - File content
|
|
350
|
+
* @param {string[]} keywords - Task-related keywords to prioritize
|
|
351
|
+
* @returns {Array<{name: string, line: number, character: number}>}
|
|
352
|
+
*/
|
|
353
|
+
function extractIdentifiersForLSP(content, keywords = []) {
|
|
354
|
+
const identifiers = [];
|
|
355
|
+
const lines = content.split('\n');
|
|
356
|
+
|
|
357
|
+
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
358
|
+
const line = lines[lineNum];
|
|
359
|
+
|
|
360
|
+
// Look for function/const/interface declarations
|
|
361
|
+
const patterns = [
|
|
362
|
+
// function name(
|
|
363
|
+
/\bfunction\s+(\w+)\s*\(/g,
|
|
364
|
+
// const name = or const name:
|
|
365
|
+
/\bconst\s+(\w+)\s*[=:]/g,
|
|
366
|
+
// interface Name
|
|
367
|
+
/\binterface\s+(\w+)/g,
|
|
368
|
+
// type Name
|
|
369
|
+
/\btype\s+(\w+)\s*=/g,
|
|
370
|
+
// : TypeName (for type annotations)
|
|
371
|
+
/:\s*(\w+)(?:[<\s,\[\]>|&]|$)/g
|
|
372
|
+
];
|
|
373
|
+
|
|
374
|
+
for (const pattern of patterns) {
|
|
375
|
+
let match;
|
|
376
|
+
while ((match = pattern.exec(line)) !== null) {
|
|
377
|
+
const name = match[1];
|
|
378
|
+
const character = match.index + match[0].indexOf(name);
|
|
379
|
+
|
|
380
|
+
// Prioritize keywords
|
|
381
|
+
const priority = keywords.some(k =>
|
|
382
|
+
name.toLowerCase().includes(k.toLowerCase())
|
|
383
|
+
) ? 0 : 1;
|
|
384
|
+
|
|
385
|
+
identifiers.push({
|
|
386
|
+
name,
|
|
387
|
+
line: lineNum,
|
|
388
|
+
character,
|
|
389
|
+
priority
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Sort by priority (keyword matches first)
|
|
396
|
+
identifiers.sort((a, b) => a.priority - b.priority);
|
|
397
|
+
|
|
398
|
+
return identifiers;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Finds related code files (similar components, hooks, etc.)
|
|
403
|
+
*/
|
|
404
|
+
function loadRelatedCode(projectRoot, filePath, stepType) {
|
|
405
|
+
if (!filePath) return null;
|
|
406
|
+
|
|
407
|
+
const related = [];
|
|
408
|
+
const dir = path.dirname(filePath);
|
|
409
|
+
const ext = path.extname(filePath);
|
|
410
|
+
|
|
411
|
+
// Find siblings or similar files
|
|
412
|
+
const searchDirs = [dir, path.join(dir, '..'), path.join(dir, '..', '..')];
|
|
413
|
+
|
|
414
|
+
for (const searchDir of searchDirs) {
|
|
415
|
+
const fullSearchDir = path.isAbsolute(searchDir) ? searchDir : path.join(projectRoot, searchDir);
|
|
416
|
+
if (!fs.existsSync(fullSearchDir)) continue;
|
|
417
|
+
|
|
418
|
+
try {
|
|
419
|
+
const files = fs.readdirSync(fullSearchDir);
|
|
420
|
+
|
|
421
|
+
for (const file of files) {
|
|
422
|
+
if (!file.endsWith('.tsx') && !file.endsWith('.ts')) continue;
|
|
423
|
+
if (file.includes('.test.') || file.includes('.spec.')) continue;
|
|
424
|
+
|
|
425
|
+
const fullFilePath = path.join(fullSearchDir, file);
|
|
426
|
+
if (fullFilePath === filePath) continue;
|
|
427
|
+
|
|
428
|
+
// Limit to first 2 related files
|
|
429
|
+
if (related.length >= 2) break;
|
|
430
|
+
|
|
431
|
+
try {
|
|
432
|
+
const content = fs.readFileSync(fullFilePath, 'utf-8');
|
|
433
|
+
// Take first 50 lines as example
|
|
434
|
+
const preview = content.split('\n').slice(0, 50).join('\n');
|
|
435
|
+
related.push(`// Example from ${path.relative(projectRoot, fullFilePath)}\n${preview}`);
|
|
436
|
+
} catch {
|
|
437
|
+
// Ignore read errors
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
} catch {
|
|
441
|
+
// Ignore dir read errors
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (related.length >= 2) break;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return related.length > 0 ? related.join('\n\n---\n\n') : null;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Simple glob pattern matcher for finding example files
|
|
452
|
+
*/
|
|
453
|
+
function globSync(basePath, pattern) {
|
|
454
|
+
const results = [];
|
|
455
|
+
const parts = pattern.split('/');
|
|
456
|
+
|
|
457
|
+
const searchDir = (currentPath, remainingParts) => {
|
|
458
|
+
if (remainingParts.length === 0) {
|
|
459
|
+
if (fs.existsSync(currentPath) && fs.statSync(currentPath).isFile()) {
|
|
460
|
+
results.push(currentPath);
|
|
461
|
+
}
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const [current, ...rest] = remainingParts;
|
|
466
|
+
|
|
467
|
+
if (current === '**') {
|
|
468
|
+
try {
|
|
469
|
+
const entries = fs.readdirSync(currentPath, { withFileTypes: true });
|
|
470
|
+
for (const entry of entries) {
|
|
471
|
+
const fullPath = path.join(currentPath, entry.name);
|
|
472
|
+
if (entry.isDirectory()) {
|
|
473
|
+
// Continue with ** (recurse deeper)
|
|
474
|
+
searchDir(fullPath, remainingParts);
|
|
475
|
+
// Also try without ** (match at this level)
|
|
476
|
+
searchDir(fullPath, rest);
|
|
477
|
+
} else if (rest.length === 0) {
|
|
478
|
+
results.push(fullPath);
|
|
479
|
+
} else if (rest.length === 1 && matchGlobPart(entry.name, rest[0])) {
|
|
480
|
+
results.push(fullPath);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
} catch { /* ignore permission errors */ }
|
|
484
|
+
} else if (current.includes('*')) {
|
|
485
|
+
try {
|
|
486
|
+
const entries = fs.readdirSync(currentPath, { withFileTypes: true });
|
|
487
|
+
for (const entry of entries) {
|
|
488
|
+
if (matchGlobPart(entry.name, current)) {
|
|
489
|
+
const fullPath = path.join(currentPath, entry.name);
|
|
490
|
+
if (entry.isDirectory()) {
|
|
491
|
+
searchDir(fullPath, rest);
|
|
492
|
+
} else if (rest.length === 0) {
|
|
493
|
+
results.push(fullPath);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
} catch { /* ignore permission errors */ }
|
|
498
|
+
} else {
|
|
499
|
+
const nextPath = path.join(currentPath, current);
|
|
500
|
+
if (fs.existsSync(nextPath)) {
|
|
501
|
+
searchDir(nextPath, rest);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
// Handle src/ prefix - check both with and without
|
|
507
|
+
const srcPath = path.join(basePath, 'src');
|
|
508
|
+
if (fs.existsSync(srcPath)) {
|
|
509
|
+
searchDir(srcPath, parts);
|
|
510
|
+
}
|
|
511
|
+
searchDir(basePath, parts);
|
|
512
|
+
|
|
513
|
+
return [...new Set(results)]; // Dedupe
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Match a filename against a glob pattern part (e.g., *.tsx)
|
|
518
|
+
*/
|
|
519
|
+
function matchGlobPart(filename, pattern) {
|
|
520
|
+
const regex = new RegExp('^' + pattern.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$');
|
|
521
|
+
return regex.test(filename);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Truncate file content to a reasonable size for examples
|
|
526
|
+
*/
|
|
527
|
+
function truncateForExample(content, maxLines = 60) {
|
|
528
|
+
const lines = content.split('\n');
|
|
529
|
+
if (lines.length <= maxLines) return content;
|
|
530
|
+
|
|
531
|
+
// Keep imports and first part of file
|
|
532
|
+
const imports = [];
|
|
533
|
+
let importEnd = 0;
|
|
534
|
+
for (let i = 0; i < lines.length; i++) {
|
|
535
|
+
if (lines[i].startsWith('import ') || lines[i].startsWith('from ') || lines[i].trim() === '') {
|
|
536
|
+
imports.push(lines[i]);
|
|
537
|
+
importEnd = i + 1;
|
|
538
|
+
} else if (imports.length > 0) {
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const remaining = maxLines - imports.length - 3;
|
|
544
|
+
const body = lines.slice(importEnd, importEnd + remaining);
|
|
545
|
+
|
|
546
|
+
return [
|
|
547
|
+
...imports,
|
|
548
|
+
...body,
|
|
549
|
+
'',
|
|
550
|
+
`// ... (${lines.length - importEnd - remaining} more lines truncated)`,
|
|
551
|
+
''
|
|
552
|
+
].join('\n');
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Finds examples of similar implementations
|
|
557
|
+
*/
|
|
558
|
+
function loadSimilarExamples(projectRoot, stepType, maxExamples = 2) {
|
|
559
|
+
// Map step types to example search patterns
|
|
560
|
+
const patterns = {
|
|
561
|
+
'create-component': [
|
|
562
|
+
'components/**/*.tsx',
|
|
563
|
+
'features/**/components/*.tsx',
|
|
564
|
+
'app/**/components/*.tsx',
|
|
565
|
+
'ui/**/*.tsx'
|
|
566
|
+
],
|
|
567
|
+
'create-hook': [
|
|
568
|
+
'hooks/**/*.ts',
|
|
569
|
+
'hooks/**/*.tsx',
|
|
570
|
+
'features/**/hooks/*.ts',
|
|
571
|
+
'lib/hooks/*.ts'
|
|
572
|
+
],
|
|
573
|
+
'create-service': [
|
|
574
|
+
'services/**/*.ts',
|
|
575
|
+
'api/**/*.ts',
|
|
576
|
+
'lib/api/*.ts',
|
|
577
|
+
'features/**/api/*.ts'
|
|
578
|
+
],
|
|
579
|
+
'create-util': [
|
|
580
|
+
'utils/**/*.ts',
|
|
581
|
+
'lib/**/*.ts',
|
|
582
|
+
'helpers/**/*.ts'
|
|
583
|
+
],
|
|
584
|
+
'create-test': [
|
|
585
|
+
'**/*.test.ts',
|
|
586
|
+
'**/*.test.tsx',
|
|
587
|
+
'**/*.spec.ts',
|
|
588
|
+
'__tests__/**/*.ts'
|
|
589
|
+
],
|
|
590
|
+
'modify-file': [] // No examples needed for modifications
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
const searchPatterns = patterns[stepType] || patterns['create-component'];
|
|
594
|
+
if (searchPatterns.length === 0) return null;
|
|
595
|
+
|
|
596
|
+
const examples = [];
|
|
597
|
+
const seen = new Set();
|
|
598
|
+
|
|
599
|
+
for (const pattern of searchPatterns) {
|
|
600
|
+
if (examples.length >= maxExamples) break;
|
|
601
|
+
|
|
602
|
+
const files = globSync(projectRoot, pattern);
|
|
603
|
+
|
|
604
|
+
// Sort by file size (prefer smaller, simpler examples)
|
|
605
|
+
const sorted = files
|
|
606
|
+
.map(f => ({ path: f, size: fs.statSync(f).size }))
|
|
607
|
+
.sort((a, b) => a.size - b.size)
|
|
608
|
+
.slice(0, 5); // Consider top 5 smallest
|
|
609
|
+
|
|
610
|
+
for (const { path: filePath } of sorted) {
|
|
611
|
+
if (examples.length >= maxExamples) break;
|
|
612
|
+
if (seen.has(filePath)) continue;
|
|
613
|
+
|
|
614
|
+
try {
|
|
615
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
616
|
+
// Skip files that are too short (likely stubs) or too long
|
|
617
|
+
const lineCount = content.split('\n').length;
|
|
618
|
+
if (lineCount < 10 || lineCount > 500) continue;
|
|
619
|
+
|
|
620
|
+
seen.add(filePath);
|
|
621
|
+
const relativePath = path.relative(projectRoot, filePath);
|
|
622
|
+
const truncated = truncateForExample(content);
|
|
623
|
+
|
|
624
|
+
examples.push(`### Example: ${relativePath}\n\`\`\`typescript\n${truncated}\`\`\``);
|
|
625
|
+
} catch { /* ignore read errors */ }
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if (examples.length === 0) return null;
|
|
630
|
+
|
|
631
|
+
return `## Similar Examples in This Project\n\nUse these as reference for style and patterns:\n\n${examples.join('\n\n')}`;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// ============================================================
|
|
635
|
+
// Verbosity Guidance
|
|
636
|
+
// ============================================================
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Returns guidance text based on verbosity level
|
|
640
|
+
*
|
|
641
|
+
* NOTE: All levels now emphasize completeness over brevity.
|
|
642
|
+
* Local LLM tokens are free - include everything needed for success!
|
|
643
|
+
*/
|
|
644
|
+
function getVerbosityGuidance(verbosity) {
|
|
645
|
+
const guidance = {
|
|
646
|
+
standard: `
|
|
647
|
+
Include enough context for success. Local LLM tokens are free!
|
|
648
|
+
- Clear description of the task
|
|
649
|
+
- All imports with exact paths
|
|
650
|
+
- Type definitions for all interfaces
|
|
651
|
+
- Mention patterns to follow`,
|
|
652
|
+
|
|
653
|
+
detailed: `
|
|
654
|
+
Be thorough. Include everything the local LLM needs to succeed first try.
|
|
655
|
+
- Show exact import paths and type signatures
|
|
656
|
+
- Include ALL props for components being used
|
|
657
|
+
- Show existing patterns to follow
|
|
658
|
+
- Provide examples of similar code
|
|
659
|
+
- List all edge cases and error handling requirements`,
|
|
660
|
+
|
|
661
|
+
comprehensive: `
|
|
662
|
+
Maximum detail. The local LLM should have complete knowledge to implement
|
|
663
|
+
this without guessing anything. Local LLM tokens are FREE - don't hold back!
|
|
664
|
+
- Include full file contents of related files
|
|
665
|
+
- Show complete type definitions with all fields
|
|
666
|
+
- Provide multiple usage examples
|
|
667
|
+
- Document all integration points
|
|
668
|
+
- Include testing requirements
|
|
669
|
+
- Show exact prop values (variant="primary" not variant={variants.primary})`
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
return guidance[verbosity] || guidance.detailed;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// ============================================================
|
|
676
|
+
// Exports
|
|
677
|
+
// ============================================================
|
|
678
|
+
|
|
679
|
+
// ============================================================
|
|
680
|
+
// Lightweight Type Hints Generator
|
|
681
|
+
// ============================================================
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Generate lightweight type hints for common project types
|
|
685
|
+
* These are condensed summaries to help LLMs understand type shapes
|
|
686
|
+
* without loading entire type files.
|
|
687
|
+
*
|
|
688
|
+
* @param {string} projectRoot - Project root directory
|
|
689
|
+
* @param {Object} options - Options
|
|
690
|
+
* @returns {string|null} Formatted type hints
|
|
691
|
+
*/
|
|
692
|
+
function generateTypeHints(projectRoot, options = {}) {
|
|
693
|
+
const { maxTypes = 10, taskDescription = '' } = options;
|
|
694
|
+
const typeHints = [];
|
|
695
|
+
|
|
696
|
+
// Common type file locations
|
|
697
|
+
const typeFiles = [
|
|
698
|
+
path.join(projectRoot, 'src', 'types', 'index.ts'),
|
|
699
|
+
path.join(projectRoot, 'src', 'types.ts'),
|
|
700
|
+
path.join(projectRoot, 'types', 'index.ts'),
|
|
701
|
+
path.join(projectRoot, 'src', 'lib', 'types.ts')
|
|
702
|
+
];
|
|
703
|
+
|
|
704
|
+
for (const typePath of typeFiles) {
|
|
705
|
+
if (!fs.existsSync(typePath)) continue;
|
|
706
|
+
|
|
707
|
+
try {
|
|
708
|
+
const content = fs.readFileSync(typePath, 'utf-8');
|
|
709
|
+
|
|
710
|
+
// Extract interface/type definitions
|
|
711
|
+
const interfaceRegex = /(?:export\s+)?interface\s+(\w+)\s*(?:extends\s+[\w,\s]+)?\s*\{([^}]+)\}/g;
|
|
712
|
+
const typeRegex = /(?:export\s+)?type\s+(\w+)\s*=\s*([^;]+);/g;
|
|
713
|
+
|
|
714
|
+
let match;
|
|
715
|
+
|
|
716
|
+
// Extract interfaces with their key properties
|
|
717
|
+
while ((match = interfaceRegex.exec(content)) !== null && typeHints.length < maxTypes) {
|
|
718
|
+
const name = match[1];
|
|
719
|
+
const body = match[2];
|
|
720
|
+
|
|
721
|
+
// Extract key properties (first 5 lines or so)
|
|
722
|
+
const props = body
|
|
723
|
+
.split('\n')
|
|
724
|
+
.map(line => line.trim())
|
|
725
|
+
.filter(line => line && !line.startsWith('//'))
|
|
726
|
+
.slice(0, 5)
|
|
727
|
+
.map(line => ' ' + line);
|
|
728
|
+
|
|
729
|
+
if (props.length > 0) {
|
|
730
|
+
typeHints.push(`${name}: { ${props.join(' ').replace(/\s+/g, ' ').slice(0, 100)}${props.length > 5 ? ' ...' : ''} }`);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// Extract type aliases (simpler)
|
|
735
|
+
while ((match = typeRegex.exec(content)) !== null && typeHints.length < maxTypes) {
|
|
736
|
+
const name = match[1];
|
|
737
|
+
const value = match[2].trim().slice(0, 80);
|
|
738
|
+
|
|
739
|
+
// Only include simple types, not complex unions
|
|
740
|
+
if (!value.includes('\n') && value.length < 80) {
|
|
741
|
+
typeHints.push(`${name}: ${value}`);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
} catch (err) {
|
|
746
|
+
if (process.env.DEBUG) {
|
|
747
|
+
console.warn(`Could not parse types from ${typePath}: ${err.message}`);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
if (typeHints.length === 0) return null;
|
|
753
|
+
|
|
754
|
+
return `**Type Hints (condensed):**\n${typeHints.map(h => `- ${h}`).join('\n')}`;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
module.exports = {
|
|
758
|
+
INSTRUCTION_RICHNESS,
|
|
759
|
+
COMPLEXITY_TO_RICHNESS,
|
|
760
|
+
getInstructionRichness,
|
|
761
|
+
getVerbosityGuidance,
|
|
762
|
+
// Context loaders
|
|
763
|
+
loadProjectContext,
|
|
764
|
+
loadPatterns,
|
|
765
|
+
loadRelevantTypes,
|
|
766
|
+
loadRelevantTypesWithLSP, // LSP-enhanced version
|
|
767
|
+
loadRelatedCode,
|
|
768
|
+
loadSimilarExamples,
|
|
769
|
+
// Type hints
|
|
770
|
+
generateTypeHints,
|
|
771
|
+
// LSP helpers
|
|
772
|
+
extractIdentifiersForLSP
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
// ============================================================
|
|
776
|
+
// CLI for testing
|
|
777
|
+
// ============================================================
|
|
778
|
+
|
|
779
|
+
if (require.main === module) {
|
|
780
|
+
const args = process.argv.slice(2);
|
|
781
|
+
|
|
782
|
+
if (args.length === 0) {
|
|
783
|
+
console.log(`
|
|
784
|
+
Usage: node flow-instruction-richness.js <complexity-level>
|
|
785
|
+
|
|
786
|
+
Complexity levels: small, medium, large, xl
|
|
787
|
+
|
|
788
|
+
Examples:
|
|
789
|
+
node flow-instruction-richness.js small
|
|
790
|
+
node flow-instruction-richness.js large
|
|
791
|
+
`);
|
|
792
|
+
process.exit(0);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
const level = args[0];
|
|
796
|
+
const richness = getInstructionRichness(level);
|
|
797
|
+
|
|
798
|
+
console.log('\n═══════════════════════════════════════════════════════════');
|
|
799
|
+
console.log(' INSTRUCTION RICHNESS CONFIG (Local LLM is FREE!)');
|
|
800
|
+
console.log('═══════════════════════════════════════════════════════════\n');
|
|
801
|
+
|
|
802
|
+
console.log(`Complexity Level: ${level.toUpperCase()}`);
|
|
803
|
+
console.log(`Richness Level: ${richness.level.toUpperCase()}`);
|
|
804
|
+
console.log(`\n${richness.description}\n`);
|
|
805
|
+
|
|
806
|
+
console.log('───────────────────────────────────────────────────────────');
|
|
807
|
+
console.log(' CONTEXT TO INCLUDE');
|
|
808
|
+
console.log('───────────────────────────────────────────────────────────\n');
|
|
809
|
+
|
|
810
|
+
console.log(`Template Verbosity: ${richness.templateVerbosity}`);
|
|
811
|
+
console.log('');
|
|
812
|
+
console.log(`Include Project Context: ${richness.includeProjectContext ? '✅ Yes' : '❌ No'}`);
|
|
813
|
+
console.log(`Include Type Definitions: ${richness.includeTypeDefinitions ? '✅ Yes' : '❌ No'}`);
|
|
814
|
+
console.log(`Include Related Code: ${richness.includeRelatedCode ? '✅ Yes' : '❌ No'}`);
|
|
815
|
+
console.log(`Include Examples: ${richness.includeExamples ? '✅ Yes' : '❌ No'}`);
|
|
816
|
+
console.log(`Include Patterns: ${richness.includePatterns ? '✅ Yes' : '❌ No'}`);
|
|
817
|
+
console.log(`Include Full File Contents: ${richness.includeFullFileContents ? '✅ Yes' : '❌ No'}`);
|
|
818
|
+
|
|
819
|
+
console.log('\n───────────────────────────────────────────────────────────');
|
|
820
|
+
console.log(' GUIDANCE');
|
|
821
|
+
console.log('───────────────────────────────────────────────────────────');
|
|
822
|
+
console.log(getVerbosityGuidance(richness.templateVerbosity));
|
|
823
|
+
|
|
824
|
+
console.log('\n💡 Remember: Local LLM tokens are FREE! Include MORE context');
|
|
825
|
+
console.log(' when in doubt. Failed executions cost more than extra context.\n');
|
|
826
|
+
console.log('═══════════════════════════════════════════════════════════\n');
|
|
827
|
+
}
|