wogiflow 1.0.12 → 1.0.13

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 (45) hide show
  1. package/.workflow/specs/architecture.md.template +24 -0
  2. package/.workflow/specs/stack.md.template +33 -0
  3. package/.workflow/specs/testing.md.template +36 -0
  4. package/README.md +90 -1
  5. package/package.json +1 -1
  6. package/scripts/MEMORY-ARCHITECTURE.md +150 -0
  7. package/scripts/flow +20 -19
  8. package/scripts/flow-auto-context.js +97 -3
  9. package/scripts/flow-conflict-resolver.js +735 -0
  10. package/scripts/flow-context-gatherer.js +520 -0
  11. package/scripts/flow-context-monitor.js +148 -19
  12. package/scripts/flow-damage-control.js +5 -1
  13. package/scripts/flow-export-profile +168 -1
  14. package/scripts/flow-import-profile +257 -6
  15. package/scripts/flow-instruction-richness.js +182 -18
  16. package/scripts/flow-knowledge-router.js +2 -0
  17. package/scripts/flow-knowledge-sync.js +2 -0
  18. package/scripts/{flow-transcript-chunking.js → flow-long-input-chunking.js} +4 -2
  19. package/scripts/{flow-transcript-parsing.js → flow-long-input-parsing.js} +35 -0
  20. package/scripts/{flow-transcript-stories.js → flow-long-input-stories.js} +86 -38
  21. package/scripts/{flow-transcript-digest.js → flow-long-input.js} +231 -15
  22. package/scripts/flow-memory-db.js +386 -1
  23. package/scripts/flow-memory-sync.js +2 -0
  24. package/scripts/flow-model-adapter.js +53 -29
  25. package/scripts/flow-model-router.js +246 -1
  26. package/scripts/flow-morning.js +94 -0
  27. package/scripts/flow-onboard +223 -10
  28. package/scripts/flow-orchestrate-validation.js +539 -0
  29. package/scripts/flow-orchestrate.js +16 -507
  30. package/scripts/flow-pattern-extractor.js +1265 -0
  31. package/scripts/flow-prompt-composer.js +222 -2
  32. package/scripts/flow-quality-guard.js +594 -0
  33. package/scripts/flow-section-index.js +713 -0
  34. package/scripts/flow-section-resolver.js +484 -0
  35. package/scripts/flow-session-end.js +188 -2
  36. package/scripts/flow-skill-create.js +19 -3
  37. package/scripts/flow-skill-matcher.js +122 -7
  38. package/scripts/flow-statusline-setup.js +218 -0
  39. package/scripts/flow-step-review.js +19 -0
  40. package/scripts/flow-tech-debt.js +734 -0
  41. package/scripts/flow-utils.js +2 -0
  42. package/scripts/hooks/core/long-input-gate.js +293 -0
  43. package/scripts/flow-parallel-detector.js +0 -399
  44. package/scripts/flow-parallel-dispatch.js +0 -987
  45. /package/scripts/{flow-transcript-language.js → flow-long-input-language.js} +0 -0
@@ -0,0 +1,520 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Dynamic Context Gatherer
5
+ *
6
+ * Intelligently gathers context for tasks based on:
7
+ * - Task description analysis
8
+ * - Section-level references (not full files)
9
+ * - Model context preferences
10
+ * - Token budget optimization
11
+ *
12
+ * Replaces hardcoded limits with dynamic, quality-aware selection.
13
+ *
14
+ * Part of Smart Context System (Phase 2)
15
+ *
16
+ * Usage:
17
+ * const { gatherContext } = require('./flow-context-gatherer');
18
+ *
19
+ * const context = await gatherContext({
20
+ * task: 'Add user authentication',
21
+ * model: 'claude-sonnet-4',
22
+ * maxTokens: 50000
23
+ * });
24
+ */
25
+
26
+ const fs = require('fs');
27
+ const path = require('path');
28
+
29
+ const {
30
+ PATHS,
31
+ getConfig,
32
+ fileExists,
33
+ readFile,
34
+ estimateTokens,
35
+ info,
36
+ warn,
37
+ success
38
+ } = require('./flow-utils');
39
+
40
+ const {
41
+ getSectionsForTask,
42
+ getSecuritySections,
43
+ getComponentSections,
44
+ getNamingConventionSections,
45
+ formatSectionsAsContext,
46
+ ensureIndex
47
+ } = require('./flow-section-resolver');
48
+
49
+ // Use model preferences from instruction-richness (single source of truth)
50
+ const { getModelContextPreferences } = require('./flow-instruction-richness');
51
+
52
+ // ============================================================
53
+ // Configuration
54
+ // ============================================================
55
+
56
+ /**
57
+ * Default context gathering configuration
58
+ */
59
+ const DEFAULT_CONFIG = {
60
+ strategy: 'dynamic', // 'dynamic' | 'fixed'
61
+ maxContextTokens: 100000,
62
+ reserveOutputTokens: 8000,
63
+ minRelevanceScore: 0.1,
64
+ fallbackToFullContext: true,
65
+ includeContent: true,
66
+ useSectionReferences: true,
67
+ fallbackLimits: {
68
+ maxFilesHard: 50,
69
+ maxTokensHard: 150000
70
+ },
71
+ // Maximum budget overflow allowed for forced includes (10%)
72
+ maxBudgetOverflow: 0.1
73
+ };
74
+
75
+ /**
76
+ * Context type priorities
77
+ */
78
+ const CONTEXT_PRIORITIES = {
79
+ security_rules: 1.0,
80
+ required_patterns: 0.95,
81
+ target_sections: 0.90,
82
+ component_rules: 0.85,
83
+ naming_conventions: 0.80,
84
+ related_sections: 0.70,
85
+ general_patterns: 0.60
86
+ };
87
+
88
+ // ============================================================
89
+ // Configuration Loading
90
+ // ============================================================
91
+
92
+ /**
93
+ * Get context gathering configuration
94
+ */
95
+ function getContextConfig() {
96
+ const config = getConfig();
97
+ return {
98
+ ...DEFAULT_CONFIG,
99
+ ...(config.autoContext || {})
100
+ };
101
+ }
102
+
103
+ /**
104
+ * Get model context preferences
105
+ * Uses the single source of truth from flow-instruction-richness.js
106
+ * @param {string} modelName - Model name
107
+ * @returns {Object} - Context preferences for the model
108
+ */
109
+ function getModelPreferences(modelName) {
110
+ return getModelContextPreferences(modelName);
111
+ }
112
+
113
+ // ============================================================
114
+ // Task Analysis
115
+ // ============================================================
116
+
117
+ /**
118
+ * Analyze task to determine context requirements
119
+ * @param {string} taskDescription - Task description
120
+ * @returns {Object} - Task analysis
121
+ */
122
+ function analyzeTask(taskDescription) {
123
+ const descLower = taskDescription.toLowerCase();
124
+
125
+ const analysis = {
126
+ needsSecurityContext: false,
127
+ needsComponentContext: false,
128
+ needsNamingContext: false,
129
+ needsAPIContext: false,
130
+ needsFileContext: false,
131
+ complexity: 'medium',
132
+ estimatedContextNeeds: 'standard'
133
+ };
134
+
135
+ // Security keywords
136
+ if (/security|auth|password|token|encrypt|validate|sanitize|injection/i.test(descLower)) {
137
+ analysis.needsSecurityContext = true;
138
+ analysis.estimatedContextNeeds = 'high';
139
+ }
140
+
141
+ // Component keywords
142
+ if (/component|button|input|form|modal|dialog|ui|widget|screen/i.test(descLower)) {
143
+ analysis.needsComponentContext = true;
144
+ }
145
+
146
+ // Naming/style keywords
147
+ if (/name|rename|naming|convention|style|format|file/i.test(descLower)) {
148
+ analysis.needsNamingContext = true;
149
+ }
150
+
151
+ // API keywords
152
+ if (/api|endpoint|route|controller|service|request|response/i.test(descLower)) {
153
+ analysis.needsAPIContext = true;
154
+ analysis.estimatedContextNeeds = 'high';
155
+ }
156
+
157
+ // File operation keywords
158
+ if (/file|read|write|fs|path|directory/i.test(descLower)) {
159
+ analysis.needsFileContext = true;
160
+ analysis.needsSecurityContext = true; // File ops need security rules
161
+ }
162
+
163
+ // Complexity estimation
164
+ const wordCount = taskDescription.split(/\s+/).length;
165
+ if (wordCount < 10) {
166
+ analysis.complexity = 'small';
167
+ analysis.estimatedContextNeeds = 'minimal';
168
+ } else if (wordCount > 50) {
169
+ analysis.complexity = 'large';
170
+ analysis.estimatedContextNeeds = 'high';
171
+ }
172
+
173
+ return analysis;
174
+ }
175
+
176
+ // ============================================================
177
+ // Context Gathering
178
+ // ============================================================
179
+
180
+ /**
181
+ * Gather all potentially relevant sections for a task
182
+ * @param {string} taskDescription - Task description
183
+ * @param {Object} taskAnalysis - Task analysis from analyzeTask()
184
+ * @returns {Object[]} - All relevant sections with priorities
185
+ */
186
+ async function gatherAllSections(taskDescription, taskAnalysis) {
187
+ const sections = [];
188
+
189
+ // Ensure index is up to date
190
+ await ensureIndex();
191
+
192
+ // 1. Get sections directly matching task description
193
+ const taskSections = await getSectionsForTask(taskDescription, {
194
+ limit: 10,
195
+ minScore: 0.05
196
+ });
197
+
198
+ for (const section of taskSections) {
199
+ sections.push({
200
+ ...section,
201
+ priority: CONTEXT_PRIORITIES.target_sections,
202
+ reason: 'Matches task description'
203
+ });
204
+ }
205
+
206
+ // 2. Add security sections if needed
207
+ if (taskAnalysis.needsSecurityContext) {
208
+ const securitySections = await getSecuritySections();
209
+ for (const section of securitySections) {
210
+ if (!sections.find(s => s.id === section.id)) {
211
+ sections.push({
212
+ ...section,
213
+ priority: CONTEXT_PRIORITIES.security_rules,
214
+ reason: 'Task involves security-sensitive operations'
215
+ });
216
+ }
217
+ }
218
+ }
219
+
220
+ // 3. Add component sections if needed
221
+ if (taskAnalysis.needsComponentContext) {
222
+ const componentSections = await getComponentSections();
223
+ for (const section of componentSections) {
224
+ if (!sections.find(s => s.id === section.id)) {
225
+ sections.push({
226
+ ...section,
227
+ priority: CONTEXT_PRIORITIES.component_rules,
228
+ reason: 'Task involves UI components'
229
+ });
230
+ }
231
+ }
232
+ }
233
+
234
+ // 4. Add naming convention sections if needed
235
+ if (taskAnalysis.needsNamingContext) {
236
+ const namingSections = await getNamingConventionSections();
237
+ for (const section of namingSections) {
238
+ if (!sections.find(s => s.id === section.id)) {
239
+ sections.push({
240
+ ...section,
241
+ priority: CONTEXT_PRIORITIES.naming_conventions,
242
+ reason: 'Task involves naming/style'
243
+ });
244
+ }
245
+ }
246
+ }
247
+
248
+ return sections;
249
+ }
250
+
251
+ /**
252
+ * Score and rank sections for inclusion
253
+ * @param {Object[]} sections - Sections to score
254
+ * @param {Object} taskAnalysis - Task analysis
255
+ * @param {Object} modelPrefs - Model preferences
256
+ * @returns {Object[]} - Scored and sorted sections
257
+ */
258
+ function scoreSections(sections, taskAnalysis, modelPrefs) {
259
+ return sections.map(section => {
260
+ let score = section.priority || 0.5;
261
+
262
+ // Boost based on match score from section resolver
263
+ if (section.score) {
264
+ score = score * 0.7 + section.score * 0.3;
265
+ }
266
+
267
+ // Boost security sections for security-sensitive tasks
268
+ if (taskAnalysis.needsSecurityContext && section.category?.includes('Security')) {
269
+ score *= 1.2;
270
+ }
271
+
272
+ // Model-specific adjustments
273
+ if (modelPrefs.density === 'concise' && section.content?.length > 1000) {
274
+ score *= 0.8; // Prefer shorter sections for concise models
275
+ }
276
+ if (modelPrefs.density === 'comprehensive') {
277
+ score *= 1.1; // Include more for comprehensive models
278
+ }
279
+
280
+ return {
281
+ ...section,
282
+ finalScore: Math.min(score, 1.0)
283
+ };
284
+ }).sort((a, b) => b.finalScore - a.finalScore);
285
+ }
286
+
287
+ /**
288
+ * Fit sections within token budget
289
+ * @param {Object[]} scoredSections - Scored sections
290
+ * @param {number} tokenBudget - Available tokens
291
+ * @param {Object} modelPrefs - Model preferences
292
+ * @returns {Object} - { selected, excluded, totalTokens }
293
+ */
294
+ function fitWithinBudget(scoredSections, tokenBudget, modelPrefs) {
295
+ const selected = [];
296
+ const excluded = [];
297
+ let totalTokens = 0;
298
+
299
+ // Reserve minimum context for quality
300
+ const minBudget = tokenBudget * modelPrefs.minContextForQuality;
301
+
302
+ // Hard ceiling to prevent budget overflow (configured max overflow, default 10%)
303
+ const config = getContextConfig();
304
+ const maxOverflow = config.maxBudgetOverflow || 0.1;
305
+ const hardCeiling = tokenBudget * (1 + maxOverflow);
306
+
307
+ for (const section of scoredSections) {
308
+ const sectionTokens = estimateTokens(section.content || '');
309
+
310
+ if (totalTokens + sectionTokens <= tokenBudget) {
311
+ selected.push({
312
+ ...section,
313
+ tokens: sectionTokens
314
+ });
315
+ totalTokens += sectionTokens;
316
+ } else if (totalTokens < minBudget && totalTokens + sectionTokens <= hardCeiling) {
317
+ // Force include if below minimum quality threshold, but respect hard ceiling
318
+ selected.push({
319
+ ...section,
320
+ tokens: sectionTokens,
321
+ forcedInclude: true
322
+ });
323
+ totalTokens += sectionTokens;
324
+ } else {
325
+ excluded.push({
326
+ ...section,
327
+ tokens: sectionTokens,
328
+ reason: totalTokens >= hardCeiling ? 'Hard ceiling exceeded' : 'Token budget exceeded'
329
+ });
330
+ }
331
+ }
332
+
333
+ return {
334
+ selected,
335
+ excluded,
336
+ totalTokens,
337
+ budgetUsed: totalTokens / tokenBudget
338
+ };
339
+ }
340
+
341
+ // ============================================================
342
+ // Main API
343
+ // ============================================================
344
+
345
+ /**
346
+ * Gather context for a task
347
+ * @param {Object} params - { task, model, maxTokens, format }
348
+ * @returns {Object} - { context, sections, stats }
349
+ */
350
+ async function gatherContext(params) {
351
+ const {
352
+ task,
353
+ model = 'claude-sonnet-4',
354
+ maxTokens = null,
355
+ format = 'full' // 'full' | 'summary' | 'reference'
356
+ } = params;
357
+
358
+ const config = getContextConfig();
359
+ const modelPrefs = getModelPreferences(model);
360
+
361
+ // Calculate token budget
362
+ const tokenBudget = maxTokens || (config.maxContextTokens - config.reserveOutputTokens);
363
+
364
+ // Analyze task
365
+ const taskAnalysis = analyzeTask(task);
366
+
367
+ // Gather all potentially relevant sections
368
+ const allSections = await gatherAllSections(task, taskAnalysis);
369
+
370
+ // Score and rank
371
+ const scoredSections = scoreSections(allSections, taskAnalysis, modelPrefs);
372
+
373
+ // Fit within budget
374
+ const budgetResult = fitWithinBudget(scoredSections, tokenBudget, modelPrefs);
375
+
376
+ // Format sections as context
377
+ const context = formatSectionsAsContext(budgetResult.selected, { format });
378
+
379
+ // Build stats
380
+ const stats = {
381
+ taskAnalysis,
382
+ model,
383
+ modelPrefs: {
384
+ density: modelPrefs.density,
385
+ minContextForQuality: modelPrefs.minContextForQuality
386
+ },
387
+ tokenBudget,
388
+ totalSectionsConsidered: allSections.length,
389
+ sectionsIncluded: budgetResult.selected.length,
390
+ sectionsExcluded: budgetResult.excluded.length,
391
+ totalTokens: budgetResult.totalTokens,
392
+ budgetUsed: `${(budgetResult.budgetUsed * 100).toFixed(1)}%`
393
+ };
394
+
395
+ return {
396
+ context,
397
+ sections: budgetResult.selected,
398
+ excluded: budgetResult.excluded,
399
+ stats
400
+ };
401
+ }
402
+
403
+ /**
404
+ * Quick gather - get minimal context for simple tasks
405
+ * @param {string} task - Task description
406
+ * @returns {string} - Context string
407
+ */
408
+ async function quickGather(task) {
409
+ const result = await gatherContext({
410
+ task,
411
+ model: 'claude-opus-4-5',
412
+ maxTokens: 10000,
413
+ format: 'summary'
414
+ });
415
+ return result.context;
416
+ }
417
+
418
+ /**
419
+ * Full gather - get comprehensive context
420
+ * @param {string} task - Task description
421
+ * @param {string} model - Model name
422
+ * @returns {Object} - Full result with sections and stats
423
+ */
424
+ async function fullGather(task, model = 'claude-sonnet-4') {
425
+ return await gatherContext({
426
+ task,
427
+ model,
428
+ format: 'full'
429
+ });
430
+ }
431
+
432
+ // ============================================================
433
+ // CLI Interface
434
+ // ============================================================
435
+
436
+ async function main() {
437
+ const args = process.argv.slice(2);
438
+ const taskDesc = args.join(' ');
439
+
440
+ if (!taskDesc) {
441
+ console.log(`
442
+ Usage: node scripts/flow-context-gatherer.js "<task description>"
443
+
444
+ Options (via environment):
445
+ MODEL=<model> Model to optimize for (default: claude-sonnet-4)
446
+ TOKENS=<n> Max tokens to use (default: 92000)
447
+ FORMAT=<fmt> Output format: full, summary, reference (default: full)
448
+
449
+ Examples:
450
+ node scripts/flow-context-gatherer.js "Add user authentication"
451
+ MODEL=claude-opus-4-5 node scripts/flow-context-gatherer.js "Fix security bug"
452
+ `);
453
+ process.exit(0);
454
+ }
455
+
456
+ const model = process.env.MODEL || 'claude-sonnet-4';
457
+ const maxTokens = parseInt(process.env.TOKENS) || null;
458
+ const format = process.env.FORMAT || 'full';
459
+
460
+ info(`Gathering context for: "${taskDesc}"`);
461
+ info(`Model: ${model}`);
462
+
463
+ const result = await gatherContext({
464
+ task: taskDesc,
465
+ model,
466
+ maxTokens,
467
+ format
468
+ });
469
+
470
+ console.log('\n--- STATS ---');
471
+ console.log(JSON.stringify(result.stats, null, 2));
472
+
473
+ console.log('\n--- INCLUDED SECTIONS ---');
474
+ for (const section of result.sections) {
475
+ console.log(` ${section.id} (score: ${section.finalScore?.toFixed(2)}, tokens: ${section.tokens})`);
476
+ }
477
+
478
+ if (result.excluded.length > 0) {
479
+ console.log('\n--- EXCLUDED SECTIONS ---');
480
+ for (const section of result.excluded.slice(0, 5)) {
481
+ console.log(` ${section.id} (reason: ${section.reason})`);
482
+ }
483
+ if (result.excluded.length > 5) {
484
+ console.log(` ... and ${result.excluded.length - 5} more`);
485
+ }
486
+ }
487
+
488
+ console.log('\n--- CONTEXT ---');
489
+ console.log(result.context);
490
+ }
491
+
492
+ // ============================================================
493
+ // Exports
494
+ // ============================================================
495
+
496
+ module.exports = {
497
+ // Main API
498
+ gatherContext,
499
+ quickGather,
500
+ fullGather,
501
+
502
+ // Utilities
503
+ analyzeTask,
504
+ gatherAllSections,
505
+ scoreSections,
506
+ fitWithinBudget,
507
+
508
+ // Configuration
509
+ getContextConfig,
510
+ getModelPreferences,
511
+
512
+ // Constants
513
+ CONTEXT_PRIORITIES,
514
+ DEFAULT_CONFIG
515
+ };
516
+
517
+ // Run if called directly
518
+ if (require.main === module) {
519
+ main().catch(console.error);
520
+ }