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,886 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* flow-context-scoring.js
|
|
5
|
+
*
|
|
6
|
+
* Phase 4.2: Context Priority Scoring System
|
|
7
|
+
*
|
|
8
|
+
* Smarter context selection than "include everything".
|
|
9
|
+
* Scores context items by relevance and fits them within token budgets.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* node flow-context-scoring.js score --task "<description>" --files <files>
|
|
13
|
+
* node flow-context-scoring.js budget --tokens 50000 --task "<description>"
|
|
14
|
+
* node flow-context-scoring.js analyze --file <file>
|
|
15
|
+
*
|
|
16
|
+
* @module flow-context-scoring
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
|
|
22
|
+
// ============================================================
|
|
23
|
+
// Imports
|
|
24
|
+
// ============================================================
|
|
25
|
+
|
|
26
|
+
const PROJECT_ROOT = path.resolve(__dirname, '..');
|
|
27
|
+
|
|
28
|
+
const {
|
|
29
|
+
getConfig,
|
|
30
|
+
parseFlags,
|
|
31
|
+
info,
|
|
32
|
+
success,
|
|
33
|
+
warn,
|
|
34
|
+
error,
|
|
35
|
+
color,
|
|
36
|
+
outputJson,
|
|
37
|
+
printHeader,
|
|
38
|
+
printSection,
|
|
39
|
+
estimateTokens: utilsEstimateTokens
|
|
40
|
+
} = require('./flow-utils');
|
|
41
|
+
|
|
42
|
+
// ============================================================
|
|
43
|
+
// Constants
|
|
44
|
+
// ============================================================
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Context priority weights.
|
|
48
|
+
* Higher = more important to include.
|
|
49
|
+
*/
|
|
50
|
+
const CONTEXT_PRIORITIES = {
|
|
51
|
+
// Must-have context
|
|
52
|
+
required_types: 1.0, // Type definitions referenced in task
|
|
53
|
+
target_file: 0.95, // The file being modified
|
|
54
|
+
error_context: 0.93, // Error messages, stack traces
|
|
55
|
+
|
|
56
|
+
// High-value context
|
|
57
|
+
direct_imports: 0.90, // Files directly imported by target
|
|
58
|
+
interface_definitions: 0.88, // Interface/type definitions
|
|
59
|
+
api_contracts: 0.85, // API schemas, endpoints
|
|
60
|
+
|
|
61
|
+
// Medium-value context
|
|
62
|
+
related_imports: 0.80, // Secondary imports
|
|
63
|
+
test_files: 0.75, // Related test files
|
|
64
|
+
patterns: 0.70, // Pattern examples from decisions.md
|
|
65
|
+
similar_implementations: 0.65, // Similar code elsewhere
|
|
66
|
+
|
|
67
|
+
// Lower-value context
|
|
68
|
+
documentation: 0.50, // README, docs
|
|
69
|
+
examples: 0.45, // Example code
|
|
70
|
+
config_files: 0.40, // Config files
|
|
71
|
+
full_files: 0.30, // Full file contents (vs snippets)
|
|
72
|
+
|
|
73
|
+
// Minimal value
|
|
74
|
+
package_info: 0.20, // package.json
|
|
75
|
+
changelog: 0.10, // CHANGELOG
|
|
76
|
+
generated_files: 0.05 // Auto-generated code
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Context categories for grouping.
|
|
81
|
+
*/
|
|
82
|
+
const CONTEXT_CATEGORIES = {
|
|
83
|
+
REQUIRED: 'required',
|
|
84
|
+
HIGH: 'high',
|
|
85
|
+
MEDIUM: 'medium',
|
|
86
|
+
LOW: 'low',
|
|
87
|
+
MINIMAL: 'minimal'
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Default context scoring configuration.
|
|
92
|
+
*/
|
|
93
|
+
const DEFAULT_CONTEXT_CONFIG = {
|
|
94
|
+
enabled: true,
|
|
95
|
+
maxContextTokens: 100000,
|
|
96
|
+
reserveOutputTokens: 8000,
|
|
97
|
+
priorities: CONTEXT_PRIORITIES,
|
|
98
|
+
includeMinScore: 0.3,
|
|
99
|
+
snippetMaxLines: 50,
|
|
100
|
+
fullFileMaxLines: 200
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// ============================================================
|
|
104
|
+
// Configuration
|
|
105
|
+
// ============================================================
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get context scoring configuration from config.json with defaults.
|
|
109
|
+
* @returns {Object} Context scoring configuration
|
|
110
|
+
*/
|
|
111
|
+
function getContextConfig() {
|
|
112
|
+
const config = getConfig();
|
|
113
|
+
return {
|
|
114
|
+
...DEFAULT_CONTEXT_CONFIG,
|
|
115
|
+
...(config.contextScoring || {})
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ============================================================
|
|
120
|
+
// Context Item Types
|
|
121
|
+
// ============================================================
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Create a context item.
|
|
125
|
+
* @param {Object} params - Item parameters
|
|
126
|
+
* @returns {Object} Context item
|
|
127
|
+
*/
|
|
128
|
+
function createContextItem({
|
|
129
|
+
id,
|
|
130
|
+
type,
|
|
131
|
+
content,
|
|
132
|
+
source,
|
|
133
|
+
relevance = 0,
|
|
134
|
+
tokens = 0,
|
|
135
|
+
metadata = {}
|
|
136
|
+
}) {
|
|
137
|
+
const baseScore = CONTEXT_PRIORITIES[type] || 0.5;
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
id: id || `ctx-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
141
|
+
type,
|
|
142
|
+
content,
|
|
143
|
+
source,
|
|
144
|
+
baseScore,
|
|
145
|
+
relevance,
|
|
146
|
+
finalScore: baseScore * (0.5 + relevance * 0.5), // Combine base and relevance
|
|
147
|
+
tokens: tokens || estimateTokens(content),
|
|
148
|
+
category: categorizeScore(baseScore),
|
|
149
|
+
metadata,
|
|
150
|
+
included: false
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Categorize a score into a category.
|
|
156
|
+
* @param {number} score - Score value
|
|
157
|
+
* @returns {string} Category
|
|
158
|
+
*/
|
|
159
|
+
function categorizeScore(score) {
|
|
160
|
+
if (score >= 0.9) return CONTEXT_CATEGORIES.REQUIRED;
|
|
161
|
+
if (score >= 0.7) return CONTEXT_CATEGORIES.HIGH;
|
|
162
|
+
if (score >= 0.5) return CONTEXT_CATEGORIES.MEDIUM;
|
|
163
|
+
if (score >= 0.2) return CONTEXT_CATEGORIES.LOW;
|
|
164
|
+
return CONTEXT_CATEGORIES.MINIMAL;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ============================================================
|
|
168
|
+
// Token Estimation
|
|
169
|
+
// ============================================================
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Estimate tokens for a piece of content.
|
|
173
|
+
* Uses centralized estimateTokens with hybrid char+line estimation.
|
|
174
|
+
* @param {string} content - Content to estimate
|
|
175
|
+
* @returns {number} Estimated tokens
|
|
176
|
+
*/
|
|
177
|
+
function estimateTokens(content) {
|
|
178
|
+
return utilsEstimateTokens(content, { useLineEstimate: true });
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Estimate tokens for a file.
|
|
183
|
+
* @param {string} filePath - Path to file
|
|
184
|
+
* @returns {number} Estimated tokens
|
|
185
|
+
*/
|
|
186
|
+
function estimateFileTokens(filePath) {
|
|
187
|
+
try {
|
|
188
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
189
|
+
return estimateTokens(content);
|
|
190
|
+
} catch {
|
|
191
|
+
return 0;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ============================================================
|
|
196
|
+
// Relevance Scoring
|
|
197
|
+
// ============================================================
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Score relevance of content to a task.
|
|
201
|
+
* @param {string} content - Content to score
|
|
202
|
+
* @param {Object} taskContext - Task context
|
|
203
|
+
* @returns {number} Relevance score (0-1)
|
|
204
|
+
*/
|
|
205
|
+
function scoreRelevance(content, taskContext) {
|
|
206
|
+
if (!content || !taskContext) return 0;
|
|
207
|
+
|
|
208
|
+
const contentLower = content.toLowerCase();
|
|
209
|
+
let score = 0;
|
|
210
|
+
let matches = 0;
|
|
211
|
+
|
|
212
|
+
// Check for keyword matches
|
|
213
|
+
const keywords = taskContext.keywords || [];
|
|
214
|
+
for (const keyword of keywords) {
|
|
215
|
+
if (contentLower.includes(keyword.toLowerCase())) {
|
|
216
|
+
score += 0.1;
|
|
217
|
+
matches++;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Check for file references
|
|
222
|
+
const files = taskContext.files || [];
|
|
223
|
+
for (const file of files) {
|
|
224
|
+
const fileName = path.basename(file).toLowerCase();
|
|
225
|
+
if (contentLower.includes(fileName)) {
|
|
226
|
+
score += 0.2;
|
|
227
|
+
matches++;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Check for type/interface references
|
|
232
|
+
const types = taskContext.types || [];
|
|
233
|
+
for (const type of types) {
|
|
234
|
+
if (content.includes(type)) {
|
|
235
|
+
score += 0.15;
|
|
236
|
+
matches++;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Check for function/method references
|
|
241
|
+
const functions = taskContext.functions || [];
|
|
242
|
+
for (const func of functions) {
|
|
243
|
+
if (content.includes(func)) {
|
|
244
|
+
score += 0.15;
|
|
245
|
+
matches++;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Boost if multiple matches
|
|
250
|
+
if (matches > 3) score *= 1.2;
|
|
251
|
+
if (matches > 5) score *= 1.3;
|
|
252
|
+
|
|
253
|
+
return Math.min(1, score);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Extract task context from a description.
|
|
258
|
+
* @param {string} description - Task description
|
|
259
|
+
* @returns {Object} Extracted context
|
|
260
|
+
*/
|
|
261
|
+
function extractTaskContext(description) {
|
|
262
|
+
const context = {
|
|
263
|
+
keywords: [],
|
|
264
|
+
files: [],
|
|
265
|
+
types: [],
|
|
266
|
+
functions: []
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
// Extract file references
|
|
270
|
+
const filePattern = /[a-zA-Z0-9_-]+\.(ts|tsx|js|jsx|json|md|css|scss|html|vue|svelte)/g;
|
|
271
|
+
let match;
|
|
272
|
+
while ((match = filePattern.exec(description)) !== null) {
|
|
273
|
+
context.files.push(match[0]);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Extract type/interface names (PascalCase)
|
|
277
|
+
const typePattern = /\b([A-Z][a-zA-Z]+(?:Type|Interface|Props|State|Config|Options|Params))\b/g;
|
|
278
|
+
while ((match = typePattern.exec(description)) !== null) {
|
|
279
|
+
context.types.push(match[1]);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Extract function names (camelCase verbs)
|
|
283
|
+
const funcPattern = /\b(get|set|create|update|delete|fetch|load|save|handle|process|validate|render|format|parse|build|init)[A-Z][a-zA-Z]+/g;
|
|
284
|
+
while ((match = funcPattern.exec(description)) !== null) {
|
|
285
|
+
context.functions.push(match[0]);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Extract significant keywords
|
|
289
|
+
const keywords = description
|
|
290
|
+
.toLowerCase()
|
|
291
|
+
.replace(/[^a-z0-9\s]/g, ' ')
|
|
292
|
+
.split(/\s+/)
|
|
293
|
+
.filter(word => word.length > 3)
|
|
294
|
+
.filter(word => !['with', 'that', 'this', 'from', 'have', 'will', 'should', 'would', 'could'].includes(word));
|
|
295
|
+
|
|
296
|
+
context.keywords = [...new Set(keywords)].slice(0, 20);
|
|
297
|
+
|
|
298
|
+
return context;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// ============================================================
|
|
302
|
+
// Context Collection
|
|
303
|
+
// ============================================================
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Collect context items for a task.
|
|
307
|
+
* @param {Object} params - Collection parameters
|
|
308
|
+
* @returns {Array} Context items
|
|
309
|
+
*/
|
|
310
|
+
function collectContext({ description, targetFiles = [], additionalContext = [] }) {
|
|
311
|
+
const items = [];
|
|
312
|
+
const taskContext = extractTaskContext(description);
|
|
313
|
+
|
|
314
|
+
// Add target files (highest priority)
|
|
315
|
+
for (const file of targetFiles) {
|
|
316
|
+
try {
|
|
317
|
+
if (!fs.existsSync(file)) continue;
|
|
318
|
+
|
|
319
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
320
|
+
items.push(createContextItem({
|
|
321
|
+
type: 'target_file',
|
|
322
|
+
content,
|
|
323
|
+
source: file,
|
|
324
|
+
relevance: 1.0,
|
|
325
|
+
metadata: { isTarget: true }
|
|
326
|
+
}));
|
|
327
|
+
|
|
328
|
+
// Extract imports from target file
|
|
329
|
+
const imports = extractImports(content, file);
|
|
330
|
+
for (const imp of imports) {
|
|
331
|
+
// Validate resolvedPath exists before using
|
|
332
|
+
if (!imp.resolvedPath) continue;
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
if (!fs.existsSync(imp.resolvedPath)) continue;
|
|
336
|
+
|
|
337
|
+
const importContent = fs.readFileSync(imp.resolvedPath, 'utf8');
|
|
338
|
+
items.push(createContextItem({
|
|
339
|
+
type: 'direct_imports',
|
|
340
|
+
content: importContent,
|
|
341
|
+
source: imp.resolvedPath,
|
|
342
|
+
relevance: scoreRelevance(importContent, taskContext),
|
|
343
|
+
metadata: { importedFrom: file, importPath: imp.path }
|
|
344
|
+
}));
|
|
345
|
+
} catch {
|
|
346
|
+
// Skip unreadable import files
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
} catch {
|
|
350
|
+
// Skip unreadable target files
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Add additional context with scoring
|
|
355
|
+
for (const ctx of additionalContext) {
|
|
356
|
+
const relevance = scoreRelevance(ctx.content, taskContext);
|
|
357
|
+
items.push(createContextItem({
|
|
358
|
+
type: ctx.type || 'full_files',
|
|
359
|
+
content: ctx.content,
|
|
360
|
+
source: ctx.source,
|
|
361
|
+
relevance,
|
|
362
|
+
metadata: ctx.metadata || {}
|
|
363
|
+
}));
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Add patterns from decisions.md
|
|
367
|
+
const decisionsPath = path.join(PROJECT_ROOT, '.workflow', 'state', 'decisions.md');
|
|
368
|
+
try {
|
|
369
|
+
if (fs.existsSync(decisionsPath)) {
|
|
370
|
+
const decisions = fs.readFileSync(decisionsPath, 'utf8');
|
|
371
|
+
const relevance = scoreRelevance(decisions, taskContext);
|
|
372
|
+
if (relevance > 0.3) {
|
|
373
|
+
items.push(createContextItem({
|
|
374
|
+
type: 'patterns',
|
|
375
|
+
content: decisions,
|
|
376
|
+
source: decisionsPath,
|
|
377
|
+
relevance
|
|
378
|
+
}));
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
} catch {
|
|
382
|
+
// Skip if decisions.md is unreadable
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return items;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Extract imports from a file.
|
|
390
|
+
* @param {string} content - File content
|
|
391
|
+
* @param {string} filePath - File path
|
|
392
|
+
* @returns {Array} Import information
|
|
393
|
+
*/
|
|
394
|
+
function extractImports(content, filePath) {
|
|
395
|
+
const imports = [];
|
|
396
|
+
const dir = path.dirname(filePath);
|
|
397
|
+
|
|
398
|
+
// ES6 imports
|
|
399
|
+
const es6Pattern = /import\s+(?:[\w{},\s*]+\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
400
|
+
let match;
|
|
401
|
+
while ((match = es6Pattern.exec(content)) !== null) {
|
|
402
|
+
const importPath = match[1];
|
|
403
|
+
if (importPath.startsWith('.')) {
|
|
404
|
+
const resolvedPath = resolveImportPath(importPath, dir);
|
|
405
|
+
if (resolvedPath) {
|
|
406
|
+
imports.push({ path: importPath, resolvedPath });
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// CommonJS requires
|
|
412
|
+
const cjsPattern = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
413
|
+
while ((match = cjsPattern.exec(content)) !== null) {
|
|
414
|
+
const importPath = match[1];
|
|
415
|
+
if (importPath.startsWith('.')) {
|
|
416
|
+
const resolvedPath = resolveImportPath(importPath, dir);
|
|
417
|
+
if (resolvedPath) {
|
|
418
|
+
imports.push({ path: importPath, resolvedPath });
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return imports;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Resolve an import path to an actual file.
|
|
428
|
+
* @param {string} importPath - Import path
|
|
429
|
+
* @param {string} baseDir - Base directory
|
|
430
|
+
* @returns {string|null} Resolved path or null
|
|
431
|
+
*/
|
|
432
|
+
function resolveImportPath(importPath, baseDir) {
|
|
433
|
+
const extensions = ['', '.ts', '.tsx', '.js', '.jsx', '/index.ts', '/index.tsx', '/index.js'];
|
|
434
|
+
|
|
435
|
+
for (const ext of extensions) {
|
|
436
|
+
try {
|
|
437
|
+
const fullPath = path.resolve(baseDir, importPath + ext);
|
|
438
|
+
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
|
|
439
|
+
return fullPath;
|
|
440
|
+
}
|
|
441
|
+
} catch {
|
|
442
|
+
// Skip if path check fails (permissions, symlink issues)
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// ============================================================
|
|
450
|
+
// Budget Fitting
|
|
451
|
+
// ============================================================
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Fit context items within a token budget.
|
|
455
|
+
* @param {Array} items - Context items
|
|
456
|
+
* @param {number} budget - Token budget
|
|
457
|
+
* @param {Object} options - Fitting options
|
|
458
|
+
* @returns {Object} Fitting result
|
|
459
|
+
*/
|
|
460
|
+
function fitToBudget(items, budget, options = {}) {
|
|
461
|
+
const config = getContextConfig();
|
|
462
|
+
const minScore = options.minScore || config.includeMinScore;
|
|
463
|
+
|
|
464
|
+
// Sort by final score (descending)
|
|
465
|
+
const sorted = [...items].sort((a, b) => b.finalScore - a.finalScore);
|
|
466
|
+
|
|
467
|
+
let usedTokens = 0;
|
|
468
|
+
const included = [];
|
|
469
|
+
const excluded = [];
|
|
470
|
+
|
|
471
|
+
for (const item of sorted) {
|
|
472
|
+
// Skip if below minimum score
|
|
473
|
+
if (item.finalScore < minScore) {
|
|
474
|
+
excluded.push({ ...item, reason: 'Below minimum score' });
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Check if it fits
|
|
479
|
+
if (usedTokens + item.tokens <= budget) {
|
|
480
|
+
item.included = true;
|
|
481
|
+
included.push(item);
|
|
482
|
+
usedTokens += item.tokens;
|
|
483
|
+
} else {
|
|
484
|
+
// Try to include a snippet instead
|
|
485
|
+
if (options.allowSnippets && item.tokens > config.snippetMaxLines * TOKENS_PER_LINE) {
|
|
486
|
+
const snippet = createSnippet(item, config.snippetMaxLines);
|
|
487
|
+
if (usedTokens + snippet.tokens <= budget) {
|
|
488
|
+
snippet.included = true;
|
|
489
|
+
included.push(snippet);
|
|
490
|
+
usedTokens += snippet.tokens;
|
|
491
|
+
excluded.push({ ...item, reason: 'Included as snippet' });
|
|
492
|
+
} else {
|
|
493
|
+
excluded.push({ ...item, reason: 'Exceeds budget (even as snippet)' });
|
|
494
|
+
}
|
|
495
|
+
} else {
|
|
496
|
+
excluded.push({ ...item, reason: 'Exceeds budget' });
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return {
|
|
502
|
+
included,
|
|
503
|
+
excluded,
|
|
504
|
+
usedTokens,
|
|
505
|
+
budget,
|
|
506
|
+
utilizationPercent: (usedTokens / budget) * 100,
|
|
507
|
+
summary: {
|
|
508
|
+
totalItems: items.length,
|
|
509
|
+
includedCount: included.length,
|
|
510
|
+
excludedCount: excluded.length,
|
|
511
|
+
byCategory: summarizeByCategory(included)
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Create a snippet from a context item.
|
|
518
|
+
* @param {Object} item - Context item
|
|
519
|
+
* @param {number} maxLines - Maximum lines
|
|
520
|
+
* @returns {Object} Snippet item
|
|
521
|
+
*/
|
|
522
|
+
function createSnippet(item, maxLines) {
|
|
523
|
+
const lines = item.content.split('\n');
|
|
524
|
+
const snippetLines = lines.slice(0, maxLines);
|
|
525
|
+
|
|
526
|
+
if (lines.length > maxLines) {
|
|
527
|
+
snippetLines.push(`... (${lines.length - maxLines} more lines)`);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
return {
|
|
531
|
+
...item,
|
|
532
|
+
id: `${item.id}-snippet`,
|
|
533
|
+
content: snippetLines.join('\n'),
|
|
534
|
+
tokens: estimateTokens(snippetLines.join('\n')),
|
|
535
|
+
metadata: {
|
|
536
|
+
...item.metadata,
|
|
537
|
+
isSnippet: true,
|
|
538
|
+
originalLines: lines.length,
|
|
539
|
+
snippetLines: maxLines
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Summarize items by category.
|
|
546
|
+
* @param {Array} items - Items to summarize
|
|
547
|
+
* @returns {Object} Summary by category
|
|
548
|
+
*/
|
|
549
|
+
function summarizeByCategory(items) {
|
|
550
|
+
const summary = {};
|
|
551
|
+
|
|
552
|
+
for (const category of Object.values(CONTEXT_CATEGORIES)) {
|
|
553
|
+
summary[category] = {
|
|
554
|
+
count: 0,
|
|
555
|
+
tokens: 0
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
for (const item of items) {
|
|
560
|
+
if (summary[item.category]) {
|
|
561
|
+
summary[item.category].count++;
|
|
562
|
+
summary[item.category].tokens += item.tokens;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
return summary;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// ============================================================
|
|
570
|
+
// Analysis
|
|
571
|
+
// ============================================================
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Analyze a file for context scoring.
|
|
575
|
+
* @param {string} filePath - Path to file
|
|
576
|
+
* @returns {Object} Analysis result
|
|
577
|
+
*/
|
|
578
|
+
function analyzeFile(filePath) {
|
|
579
|
+
// Validate path is within project directory (prevent path traversal)
|
|
580
|
+
const resolvedPath = path.resolve(filePath);
|
|
581
|
+
if (!resolvedPath.startsWith(PROJECT_ROOT)) {
|
|
582
|
+
return { error: 'File must be within project directory' };
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
586
|
+
return { error: 'File not found' };
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
let content;
|
|
590
|
+
try {
|
|
591
|
+
content = fs.readFileSync(resolvedPath, 'utf8');
|
|
592
|
+
} catch {
|
|
593
|
+
return { error: 'Failed to read file' };
|
|
594
|
+
}
|
|
595
|
+
const lines = content.split('\n');
|
|
596
|
+
const tokens = estimateTokens(content);
|
|
597
|
+
|
|
598
|
+
// Determine file type
|
|
599
|
+
const ext = path.extname(filePath);
|
|
600
|
+
let fileType = 'full_files';
|
|
601
|
+
|
|
602
|
+
if (/\.(d\.ts|types\.ts)$/.test(filePath)) {
|
|
603
|
+
fileType = 'required_types';
|
|
604
|
+
} else if (/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filePath)) {
|
|
605
|
+
fileType = 'test_files';
|
|
606
|
+
} else if (/\.(md|txt)$/.test(filePath)) {
|
|
607
|
+
fileType = 'documentation';
|
|
608
|
+
} else if (/\.(json|yaml|yml|toml)$/.test(filePath)) {
|
|
609
|
+
fileType = 'config_files';
|
|
610
|
+
} else if (/\.generated\.|\.min\./.test(filePath)) {
|
|
611
|
+
fileType = 'generated_files';
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const baseScore = CONTEXT_PRIORITIES[fileType] || 0.5;
|
|
615
|
+
|
|
616
|
+
// Extract structure info
|
|
617
|
+
const exports = extractExports(content);
|
|
618
|
+
const imports = extractImports(content, filePath);
|
|
619
|
+
|
|
620
|
+
return {
|
|
621
|
+
path: filePath,
|
|
622
|
+
lines: lines.length,
|
|
623
|
+
tokens,
|
|
624
|
+
fileType,
|
|
625
|
+
baseScore,
|
|
626
|
+
category: categorizeScore(baseScore),
|
|
627
|
+
exports,
|
|
628
|
+
importCount: imports.length,
|
|
629
|
+
wouldFitIn: {
|
|
630
|
+
small: tokens <= 2000 ? 'full' : tokens <= 5000 ? 'snippet' : 'summary',
|
|
631
|
+
medium: tokens <= 10000 ? 'full' : tokens <= 20000 ? 'snippet' : 'summary',
|
|
632
|
+
large: tokens <= 50000 ? 'full' : 'snippet'
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Extract exports from a file.
|
|
639
|
+
* @param {string} content - File content
|
|
640
|
+
* @returns {Array} Export information
|
|
641
|
+
*/
|
|
642
|
+
function extractExports(content) {
|
|
643
|
+
const exports = [];
|
|
644
|
+
|
|
645
|
+
// Named exports
|
|
646
|
+
const namedPattern = /export\s+(?:const|let|var|function|class|interface|type|enum)\s+(\w+)/g;
|
|
647
|
+
let match;
|
|
648
|
+
while ((match = namedPattern.exec(content)) !== null) {
|
|
649
|
+
exports.push({ name: match[1], type: 'named' });
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// Default export
|
|
653
|
+
if (/export\s+default/.test(content)) {
|
|
654
|
+
exports.push({ name: 'default', type: 'default' });
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// module.exports
|
|
658
|
+
const cjsPattern = /module\.exports\s*=\s*\{([^}]+)\}/;
|
|
659
|
+
const cjsMatch = content.match(cjsPattern);
|
|
660
|
+
if (cjsMatch) {
|
|
661
|
+
const names = cjsMatch[1].match(/\w+/g) || [];
|
|
662
|
+
for (const name of names) {
|
|
663
|
+
exports.push({ name, type: 'cjs' });
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
return exports;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// ============================================================
|
|
671
|
+
// CLI Output
|
|
672
|
+
// ============================================================
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Print context scoring results.
|
|
676
|
+
* @param {Object} result - Fitting result
|
|
677
|
+
*/
|
|
678
|
+
function printScoringResult(result) {
|
|
679
|
+
printHeader('CONTEXT SCORING RESULT');
|
|
680
|
+
|
|
681
|
+
printSection('Budget');
|
|
682
|
+
console.log(` ${color('dim', 'Total budget:')} ${result.budget} tokens`);
|
|
683
|
+
console.log(` ${color('dim', 'Used:')} ${result.usedTokens} tokens (${result.utilizationPercent.toFixed(1)}%)`);
|
|
684
|
+
console.log(` ${color('dim', 'Remaining:')} ${result.budget - result.usedTokens} tokens`);
|
|
685
|
+
|
|
686
|
+
printSection('Included Items');
|
|
687
|
+
const byCategory = result.summary.byCategory;
|
|
688
|
+
for (const [category, data] of Object.entries(byCategory)) {
|
|
689
|
+
if (data.count > 0) {
|
|
690
|
+
const icon = category === 'required' ? '🔴' :
|
|
691
|
+
category === 'high' ? '🟠' :
|
|
692
|
+
category === 'medium' ? '🟡' :
|
|
693
|
+
category === 'low' ? '🟢' : '⚪';
|
|
694
|
+
console.log(` ${icon} ${category}: ${data.count} items (${data.tokens} tokens)`);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
console.log('');
|
|
699
|
+
for (const item of result.included.slice(0, 10)) {
|
|
700
|
+
const scoreBar = '█'.repeat(Math.round(item.finalScore * 10));
|
|
701
|
+
console.log(` ${color('dim', item.type.padEnd(20))} ${scoreBar} ${item.finalScore.toFixed(2)}`);
|
|
702
|
+
console.log(` ${color('dim', item.source)} (${item.tokens} tokens)`);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
if (result.included.length > 10) {
|
|
706
|
+
console.log(color('dim', ` ... and ${result.included.length - 10} more items`));
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
if (result.excluded.length > 0) {
|
|
710
|
+
printSection('Excluded Items');
|
|
711
|
+
console.log(color('dim', ` ${result.excluded.length} items excluded`));
|
|
712
|
+
const reasons = {};
|
|
713
|
+
for (const item of result.excluded) {
|
|
714
|
+
reasons[item.reason] = (reasons[item.reason] || 0) + 1;
|
|
715
|
+
}
|
|
716
|
+
for (const [reason, count] of Object.entries(reasons)) {
|
|
717
|
+
console.log(` ${color('dim', reason)}: ${count}`);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Print file analysis.
|
|
724
|
+
* @param {Object} analysis - File analysis
|
|
725
|
+
*/
|
|
726
|
+
function printFileAnalysis(analysis) {
|
|
727
|
+
if (analysis.error) {
|
|
728
|
+
error(analysis.error);
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
printHeader('FILE ANALYSIS');
|
|
733
|
+
|
|
734
|
+
console.log(` ${color('dim', 'Path:')} ${analysis.path}`);
|
|
735
|
+
console.log(` ${color('dim', 'Lines:')} ${analysis.lines}`);
|
|
736
|
+
console.log(` ${color('dim', 'Tokens:')} ${analysis.tokens}`);
|
|
737
|
+
console.log(` ${color('dim', 'Type:')} ${analysis.fileType}`);
|
|
738
|
+
console.log(` ${color('dim', 'Base score:')} ${analysis.baseScore.toFixed(2)}`);
|
|
739
|
+
console.log(` ${color('dim', 'Category:')} ${analysis.category}`);
|
|
740
|
+
|
|
741
|
+
printSection('Exports');
|
|
742
|
+
if (analysis.exports.length === 0) {
|
|
743
|
+
console.log(color('dim', ' No exports found'));
|
|
744
|
+
} else {
|
|
745
|
+
for (const exp of analysis.exports.slice(0, 10)) {
|
|
746
|
+
console.log(` ${exp.type === 'default' ? '★' : '○'} ${exp.name}`);
|
|
747
|
+
}
|
|
748
|
+
if (analysis.exports.length > 10) {
|
|
749
|
+
console.log(color('dim', ` ... and ${analysis.exports.length - 10} more`));
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
printSection('Context Budget Fit');
|
|
754
|
+
console.log(` ${color('dim', 'Small context (10k):')} ${analysis.wouldFitIn.small}`);
|
|
755
|
+
console.log(` ${color('dim', 'Medium context (50k):')} ${analysis.wouldFitIn.medium}`);
|
|
756
|
+
console.log(` ${color('dim', 'Large context (200k):')} ${analysis.wouldFitIn.large}`);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// ============================================================
|
|
760
|
+
// Exports
|
|
761
|
+
// ============================================================
|
|
762
|
+
|
|
763
|
+
module.exports = {
|
|
764
|
+
// Core functions
|
|
765
|
+
createContextItem,
|
|
766
|
+
collectContext,
|
|
767
|
+
fitToBudget,
|
|
768
|
+
analyzeFile,
|
|
769
|
+
|
|
770
|
+
// Scoring
|
|
771
|
+
scoreRelevance,
|
|
772
|
+
extractTaskContext,
|
|
773
|
+
estimateTokens,
|
|
774
|
+
|
|
775
|
+
// Configuration
|
|
776
|
+
getContextConfig,
|
|
777
|
+
CONTEXT_PRIORITIES,
|
|
778
|
+
CONTEXT_CATEGORIES,
|
|
779
|
+
DEFAULT_CONTEXT_CONFIG
|
|
780
|
+
};
|
|
781
|
+
|
|
782
|
+
// ============================================================
|
|
783
|
+
// CLI Entry Point
|
|
784
|
+
// ============================================================
|
|
785
|
+
|
|
786
|
+
function main() {
|
|
787
|
+
const { positional, flags } = parseFlags(process.argv.slice(2));
|
|
788
|
+
const command = positional[0];
|
|
789
|
+
|
|
790
|
+
if (flags.help || !command) {
|
|
791
|
+
console.log(`
|
|
792
|
+
Usage: flow context <command> [options]
|
|
793
|
+
|
|
794
|
+
Commands:
|
|
795
|
+
score Score context items for a task
|
|
796
|
+
budget Fit context within a token budget
|
|
797
|
+
analyze Analyze a file for context inclusion
|
|
798
|
+
|
|
799
|
+
Options:
|
|
800
|
+
--task "<desc>" Task description for relevance scoring
|
|
801
|
+
--files <f1,f2> Files to include (comma-separated)
|
|
802
|
+
--tokens <n> Token budget (default: 100000)
|
|
803
|
+
--json Output as JSON
|
|
804
|
+
--help Show this help
|
|
805
|
+
|
|
806
|
+
Examples:
|
|
807
|
+
flow context score --task "Add user authentication" --files src/auth.ts
|
|
808
|
+
flow context budget --tokens 50000 --task "Fix login bug"
|
|
809
|
+
flow context analyze --file src/components/Button.tsx
|
|
810
|
+
`);
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
switch (command) {
|
|
815
|
+
case 'score': {
|
|
816
|
+
const task = flags.task;
|
|
817
|
+
const files = flags.files ? flags.files.split(',') : [];
|
|
818
|
+
|
|
819
|
+
if (!task) {
|
|
820
|
+
error('Please provide a task with --task');
|
|
821
|
+
process.exit(1);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
const items = collectContext({
|
|
825
|
+
description: task,
|
|
826
|
+
targetFiles: files
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
if (flags.json) {
|
|
830
|
+
outputJson(items);
|
|
831
|
+
} else {
|
|
832
|
+
printHeader('CONTEXT ITEMS SCORED');
|
|
833
|
+
for (const item of items.slice(0, 20)) {
|
|
834
|
+
console.log(` ${item.finalScore.toFixed(2)} ${item.type} - ${item.source}`);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
break;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
case 'budget': {
|
|
841
|
+
const task = flags.task || 'General task';
|
|
842
|
+
const files = flags.files ? flags.files.split(',') : [];
|
|
843
|
+
const budget = parseInt(flags.tokens) || 100000;
|
|
844
|
+
|
|
845
|
+
const items = collectContext({
|
|
846
|
+
description: task,
|
|
847
|
+
targetFiles: files
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
const result = fitToBudget(items, budget, { allowSnippets: true });
|
|
851
|
+
|
|
852
|
+
if (flags.json) {
|
|
853
|
+
outputJson(result);
|
|
854
|
+
} else {
|
|
855
|
+
printScoringResult(result);
|
|
856
|
+
}
|
|
857
|
+
break;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
case 'analyze': {
|
|
861
|
+
const file = flags.file || positional[1];
|
|
862
|
+
|
|
863
|
+
if (!file) {
|
|
864
|
+
error('Please provide a file with --file or as argument');
|
|
865
|
+
process.exit(1);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
const analysis = analyzeFile(file);
|
|
869
|
+
|
|
870
|
+
if (flags.json) {
|
|
871
|
+
outputJson(analysis);
|
|
872
|
+
} else {
|
|
873
|
+
printFileAnalysis(analysis);
|
|
874
|
+
}
|
|
875
|
+
break;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
default:
|
|
879
|
+
error(`Unknown command: ${command}`);
|
|
880
|
+
process.exit(1);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
if (require.main === module) {
|
|
885
|
+
main();
|
|
886
|
+
}
|