wogiflow 1.0.11 → 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.
- package/.workflow/specs/architecture.md.template +24 -0
- package/.workflow/specs/stack.md.template +33 -0
- package/.workflow/specs/testing.md.template +36 -0
- package/README.md +90 -1
- package/lib/unified-wizard.js +569 -30
- package/package.json +1 -1
- package/scripts/MEMORY-ARCHITECTURE.md +150 -0
- package/scripts/flow +20 -19
- package/scripts/flow-auto-context.js +97 -3
- package/scripts/flow-conflict-resolver.js +735 -0
- package/scripts/flow-context-gatherer.js +520 -0
- package/scripts/flow-context-monitor.js +148 -19
- package/scripts/flow-damage-control.js +5 -1
- package/scripts/flow-export-profile +168 -1
- package/scripts/flow-import-profile +257 -6
- package/scripts/flow-instruction-richness.js +182 -18
- package/scripts/flow-knowledge-router.js +2 -0
- package/scripts/flow-knowledge-sync.js +2 -0
- package/scripts/{flow-transcript-chunking.js → flow-long-input-chunking.js} +4 -2
- package/scripts/{flow-transcript-parsing.js → flow-long-input-parsing.js} +35 -0
- package/scripts/{flow-transcript-stories.js → flow-long-input-stories.js} +86 -38
- package/scripts/{flow-transcript-digest.js → flow-long-input.js} +231 -15
- package/scripts/flow-memory-db.js +386 -1
- package/scripts/flow-memory-sync.js +2 -0
- package/scripts/flow-model-adapter.js +53 -29
- package/scripts/flow-model-router.js +246 -1
- package/scripts/flow-morning.js +94 -0
- package/scripts/flow-onboard +223 -10
- package/scripts/flow-orchestrate-validation.js +539 -0
- package/scripts/flow-orchestrate.js +16 -507
- package/scripts/flow-pattern-extractor.js +1265 -0
- package/scripts/flow-prompt-composer.js +222 -2
- package/scripts/flow-quality-guard.js +594 -0
- package/scripts/flow-section-index.js +713 -0
- package/scripts/flow-section-resolver.js +484 -0
- package/scripts/flow-session-end.js +188 -2
- package/scripts/flow-skill-create.js +19 -3
- package/scripts/flow-skill-matcher.js +122 -7
- package/scripts/flow-statusline-setup.js +218 -0
- package/scripts/flow-step-review.js +19 -0
- package/scripts/flow-tech-debt.js +734 -0
- package/scripts/flow-utils.js +2 -0
- package/scripts/hooks/core/long-input-gate.js +293 -0
- package/scripts/flow-parallel-detector.js +0 -399
- package/scripts/flow-parallel-dispatch.js +0 -987
- /package/scripts/{flow-transcript-language.js → flow-long-input-language.js} +0 -0
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow - Section Resolver
|
|
5
|
+
*
|
|
6
|
+
* High-level API for resolving section references and gathering
|
|
7
|
+
* targeted context. Combines the section index with database operations.
|
|
8
|
+
*
|
|
9
|
+
* Features:
|
|
10
|
+
* - Resolve section IDs to content
|
|
11
|
+
* - Find sections by pins/keywords
|
|
12
|
+
* - Find sections relevant to a task description
|
|
13
|
+
* - Combine multiple sections into formatted context
|
|
14
|
+
*
|
|
15
|
+
* Part of Smart Context System (Phase 1)
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* const { getSection, getSectionsForTask } = require('./flow-section-resolver');
|
|
19
|
+
*
|
|
20
|
+
* // Get specific section
|
|
21
|
+
* const section = await getSection('coding-standards:security-patterns-2026-01-11');
|
|
22
|
+
*
|
|
23
|
+
* // Get sections for a task
|
|
24
|
+
* const sections = await getSectionsForTask('Add user authentication');
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const {
|
|
28
|
+
PATHS,
|
|
29
|
+
fileExists,
|
|
30
|
+
readFile,
|
|
31
|
+
info,
|
|
32
|
+
warn
|
|
33
|
+
} = require('./flow-utils');
|
|
34
|
+
|
|
35
|
+
const {
|
|
36
|
+
readIndex,
|
|
37
|
+
getSectionById: getIndexSectionById,
|
|
38
|
+
getSectionsByPins: getIndexSectionsByPins,
|
|
39
|
+
generateSectionIndex,
|
|
40
|
+
needsRegeneration
|
|
41
|
+
} = require('./flow-section-index');
|
|
42
|
+
|
|
43
|
+
const {
|
|
44
|
+
syncSectionsFromIndex,
|
|
45
|
+
searchSectionsByPins: dbSearchSectionsByPins,
|
|
46
|
+
searchSectionsBySimilarity,
|
|
47
|
+
getSectionById: dbGetSectionById,
|
|
48
|
+
getSectionsBySource,
|
|
49
|
+
getSectionStats
|
|
50
|
+
} = require('./flow-memory-db');
|
|
51
|
+
|
|
52
|
+
// ============================================================
|
|
53
|
+
// Configuration
|
|
54
|
+
// ============================================================
|
|
55
|
+
|
|
56
|
+
// Keywords that indicate task types for context matching
|
|
57
|
+
const TASK_KEYWORDS = {
|
|
58
|
+
component: ['component', 'button', 'input', 'form', 'modal', 'dialog', 'ui', 'widget'],
|
|
59
|
+
security: ['security', 'auth', 'authentication', 'authorization', 'password', 'token', 'jwt', 'encrypt'],
|
|
60
|
+
api: ['api', 'endpoint', 'route', 'controller', 'service', 'rest', 'graphql', 'request', 'response'],
|
|
61
|
+
database: ['database', 'db', 'sql', 'query', 'model', 'entity', 'migration', 'schema'],
|
|
62
|
+
testing: ['test', 'spec', 'mock', 'stub', 'jest', 'vitest', 'coverage'],
|
|
63
|
+
error: ['error', 'exception', 'catch', 'throw', 'handle', 'fail', 'retry'],
|
|
64
|
+
file: ['file', 'fs', 'read', 'write', 'path', 'directory', 'folder'],
|
|
65
|
+
config: ['config', 'configuration', 'settings', 'options', 'environment', 'env'],
|
|
66
|
+
refactor: ['refactor', 'clean', 'organize', 'improve', 'optimize', 'rename']
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// ============================================================
|
|
70
|
+
// Index Management
|
|
71
|
+
// ============================================================
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Ensure section index is up to date
|
|
75
|
+
* @returns {Object} - Index object
|
|
76
|
+
*/
|
|
77
|
+
async function ensureIndex() {
|
|
78
|
+
// Check if index needs regeneration
|
|
79
|
+
if (needsRegeneration()) {
|
|
80
|
+
const result = generateSectionIndex({ force: true });
|
|
81
|
+
if (result.success) {
|
|
82
|
+
// Sync to database
|
|
83
|
+
const index = readIndex();
|
|
84
|
+
if (index) {
|
|
85
|
+
await syncSectionsFromIndex(index);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return readIndex();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Force regenerate index and sync to database
|
|
95
|
+
* @returns {Object} - { indexStats, dbStats }
|
|
96
|
+
*/
|
|
97
|
+
async function refreshIndex() {
|
|
98
|
+
const indexResult = generateSectionIndex({ force: true });
|
|
99
|
+
const index = readIndex();
|
|
100
|
+
|
|
101
|
+
let dbResult = { synced: 0, updated: 0, unchanged: 0 };
|
|
102
|
+
if (index) {
|
|
103
|
+
dbResult = await syncSectionsFromIndex(index);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
indexStats: indexResult.stats,
|
|
108
|
+
dbStats: dbResult
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ============================================================
|
|
113
|
+
// Section Resolution
|
|
114
|
+
// ============================================================
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Get a single section by ID
|
|
118
|
+
* @param {string} sectionId - Section ID (e.g., "coding-standards:security-patterns")
|
|
119
|
+
* @param {Object} options - { useDatabase: boolean, trackAccess: boolean }
|
|
120
|
+
* @returns {Object|null} - Section object or null
|
|
121
|
+
*/
|
|
122
|
+
async function getSection(sectionId, options = {}) {
|
|
123
|
+
const { useDatabase = true, trackAccess = true } = options;
|
|
124
|
+
|
|
125
|
+
// Try database first (has access tracking)
|
|
126
|
+
if (useDatabase) {
|
|
127
|
+
const dbSection = await dbGetSectionById(sectionId, trackAccess);
|
|
128
|
+
if (dbSection) return dbSection;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Fall back to index file
|
|
132
|
+
const indexSection = getIndexSectionById(sectionId);
|
|
133
|
+
return indexSection;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get multiple sections by IDs
|
|
138
|
+
* @param {string[]} sectionIds - Array of section IDs
|
|
139
|
+
* @param {Object} options - { useDatabase: boolean }
|
|
140
|
+
* @returns {Object[]} - Array of section objects
|
|
141
|
+
*/
|
|
142
|
+
async function getSections(sectionIds, options = {}) {
|
|
143
|
+
const sections = [];
|
|
144
|
+
|
|
145
|
+
for (const id of sectionIds) {
|
|
146
|
+
const section = await getSection(id, options);
|
|
147
|
+
if (section) {
|
|
148
|
+
sections.push(section);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return sections;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get sections matching pins/keywords
|
|
157
|
+
* @param {string[]} pins - Pins to match
|
|
158
|
+
* @param {Object} options - { limit: number, useDatabase: boolean }
|
|
159
|
+
* @returns {Object[]} - Matching sections with scores
|
|
160
|
+
*/
|
|
161
|
+
async function getSectionsByPins(pins, options = {}) {
|
|
162
|
+
const { limit = 10, useDatabase = true } = options;
|
|
163
|
+
|
|
164
|
+
if (useDatabase) {
|
|
165
|
+
return await dbSearchSectionsByPins(pins, { limit });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Fall back to index
|
|
169
|
+
return getIndexSectionsByPins(pins).slice(0, limit);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get sections relevant to a task description
|
|
174
|
+
* Uses keyword extraction and semantic matching
|
|
175
|
+
* @param {string} taskDescription - Task description
|
|
176
|
+
* @param {Object} options - { limit: number, minScore: number }
|
|
177
|
+
* @returns {Object[]} - Relevant sections
|
|
178
|
+
*/
|
|
179
|
+
async function getSectionsForTask(taskDescription, options = {}) {
|
|
180
|
+
const { limit = 5, minScore = 0.1 } = options;
|
|
181
|
+
|
|
182
|
+
// Extract keywords/pins from task description
|
|
183
|
+
const extractedPins = extractPinsFromTask(taskDescription);
|
|
184
|
+
|
|
185
|
+
// Search by pins first (fast)
|
|
186
|
+
const pinMatches = await dbSearchSectionsByPins(extractedPins, { limit: limit * 2 });
|
|
187
|
+
|
|
188
|
+
// Also try semantic search if available
|
|
189
|
+
const semanticMatches = await searchSectionsBySimilarity(taskDescription, { limit: limit * 2 });
|
|
190
|
+
|
|
191
|
+
// Combine and deduplicate
|
|
192
|
+
const combined = new Map();
|
|
193
|
+
|
|
194
|
+
for (const section of pinMatches) {
|
|
195
|
+
combined.set(section.id, {
|
|
196
|
+
...section,
|
|
197
|
+
score: section.matchScore || 0,
|
|
198
|
+
matchType: 'pin'
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
for (const section of semanticMatches) {
|
|
203
|
+
if (combined.has(section.id)) {
|
|
204
|
+
// Boost score if matched by both methods
|
|
205
|
+
const existing = combined.get(section.id);
|
|
206
|
+
existing.score = Math.max(existing.score, section.similarity || 0) * 1.2;
|
|
207
|
+
existing.matchType = 'both';
|
|
208
|
+
} else {
|
|
209
|
+
combined.set(section.id, {
|
|
210
|
+
...section,
|
|
211
|
+
score: section.similarity || 0,
|
|
212
|
+
matchType: 'semantic'
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Sort by score and filter
|
|
218
|
+
const results = Array.from(combined.values())
|
|
219
|
+
.filter(s => s.score >= minScore)
|
|
220
|
+
.sort((a, b) => b.score - a.score)
|
|
221
|
+
.slice(0, limit);
|
|
222
|
+
|
|
223
|
+
return results;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Extract pins/keywords from a task description
|
|
228
|
+
* @param {string} taskDescription - Task description
|
|
229
|
+
* @returns {string[]} - Extracted pins
|
|
230
|
+
*/
|
|
231
|
+
function extractPinsFromTask(taskDescription) {
|
|
232
|
+
const pins = new Set();
|
|
233
|
+
const descLower = taskDescription.toLowerCase();
|
|
234
|
+
|
|
235
|
+
// Match against task keyword categories
|
|
236
|
+
for (const [category, keywords] of Object.entries(TASK_KEYWORDS)) {
|
|
237
|
+
const matchCount = keywords.filter(kw => descLower.includes(kw)).length;
|
|
238
|
+
if (matchCount > 0) {
|
|
239
|
+
pins.add(category);
|
|
240
|
+
// Add the specific keywords that matched
|
|
241
|
+
keywords.filter(kw => descLower.includes(kw)).forEach(kw => pins.add(kw));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Extract PascalCase words (likely component names)
|
|
246
|
+
const pascalMatches = taskDescription.match(/[A-Z][a-z]+(?:[A-Z][a-z]+)*/g);
|
|
247
|
+
if (pascalMatches) {
|
|
248
|
+
pascalMatches.forEach(m => pins.add(m.toLowerCase()));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Extract significant words
|
|
252
|
+
const words = descLower
|
|
253
|
+
.replace(/[^a-z0-9\s-]/g, '')
|
|
254
|
+
.split(/\s+/)
|
|
255
|
+
.filter(w => w.length > 3)
|
|
256
|
+
.filter(w => !['that', 'this', 'with', 'from', 'have', 'will', 'should', 'could', 'would'].includes(w));
|
|
257
|
+
|
|
258
|
+
words.forEach(w => pins.add(w));
|
|
259
|
+
|
|
260
|
+
return Array.from(pins);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ============================================================
|
|
264
|
+
// Context Formatting
|
|
265
|
+
// ============================================================
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Format sections into context string for prompts
|
|
269
|
+
* @param {Object[]} sections - Array of section objects
|
|
270
|
+
* @param {Object} options - { format: 'full' | 'summary' | 'reference' }
|
|
271
|
+
* @returns {string} - Formatted context
|
|
272
|
+
*/
|
|
273
|
+
function formatSectionsAsContext(sections, options = {}) {
|
|
274
|
+
const { format = 'full' } = options;
|
|
275
|
+
|
|
276
|
+
if (sections.length === 0) {
|
|
277
|
+
return '';
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
let context = '## Relevant Project Rules\n\n';
|
|
281
|
+
|
|
282
|
+
for (const section of sections) {
|
|
283
|
+
const source = section.source || 'unknown';
|
|
284
|
+
const category = section.category || 'General';
|
|
285
|
+
|
|
286
|
+
switch (format) {
|
|
287
|
+
case 'reference':
|
|
288
|
+
// Just the title and ID
|
|
289
|
+
context += `- **${section.title}** (${source}:${section.id})\n`;
|
|
290
|
+
break;
|
|
291
|
+
|
|
292
|
+
case 'summary':
|
|
293
|
+
// Title and first line of content
|
|
294
|
+
const firstLine = section.content?.split('\n')[0] || '';
|
|
295
|
+
context += `### ${section.title}\n`;
|
|
296
|
+
context += `*From: ${source} > ${category}*\n`;
|
|
297
|
+
context += `${firstLine}\n\n`;
|
|
298
|
+
break;
|
|
299
|
+
|
|
300
|
+
case 'full':
|
|
301
|
+
default:
|
|
302
|
+
// Full content
|
|
303
|
+
context += `### ${section.title}\n`;
|
|
304
|
+
context += `*From: ${source} > ${category}*\n\n`;
|
|
305
|
+
context += `${section.content}\n\n`;
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return context.trim();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Format sections as targeted references for prompts
|
|
315
|
+
* Used when we want to tell the model "follow rule X" without full content
|
|
316
|
+
* @param {Object[]} sections - Array of section objects
|
|
317
|
+
* @returns {string} - Formatted reference string
|
|
318
|
+
*/
|
|
319
|
+
function formatSectionsAsReferences(sections) {
|
|
320
|
+
if (sections.length === 0) {
|
|
321
|
+
return '';
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const refs = sections.map(s => {
|
|
325
|
+
const source = s.source?.replace('.md', '') || 'decisions';
|
|
326
|
+
return `${source}:${s.id}`;
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
return `Follow rules: ${refs.join(', ')}`;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// ============================================================
|
|
333
|
+
// Convenience Functions
|
|
334
|
+
// ============================================================
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Get all security-related sections
|
|
338
|
+
* @returns {Object[]} - Security sections
|
|
339
|
+
*/
|
|
340
|
+
async function getSecuritySections() {
|
|
341
|
+
return await getSectionsByPins([
|
|
342
|
+
'security', 'try-catch', 'error-handling', 'json-safety',
|
|
343
|
+
'prototype-pollution', 'path-traversal', 'input-validation'
|
|
344
|
+
], { limit: 10 });
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Get all component-related sections
|
|
349
|
+
* @returns {Object[]} - Component sections
|
|
350
|
+
*/
|
|
351
|
+
async function getComponentSections() {
|
|
352
|
+
return await getSectionsByPins([
|
|
353
|
+
'component', 'component-creation', 'component-reuse',
|
|
354
|
+
'component-naming', 'variant-naming', 'ui'
|
|
355
|
+
], { limit: 10 });
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Get all naming convention sections
|
|
360
|
+
* @returns {Object[]} - Naming sections
|
|
361
|
+
*/
|
|
362
|
+
async function getNamingConventionSections() {
|
|
363
|
+
return await getSectionsByPins([
|
|
364
|
+
'naming-convention', 'file-naming', 'variant-naming',
|
|
365
|
+
'naming', 'convention', 'kebab-case'
|
|
366
|
+
], { limit: 10 });
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// ============================================================
|
|
370
|
+
// CLI Interface
|
|
371
|
+
// ============================================================
|
|
372
|
+
|
|
373
|
+
async function main() {
|
|
374
|
+
const args = process.argv.slice(2);
|
|
375
|
+
const command = args[0];
|
|
376
|
+
|
|
377
|
+
switch (command) {
|
|
378
|
+
case 'refresh':
|
|
379
|
+
info('Refreshing section index...');
|
|
380
|
+
const refreshResult = await refreshIndex();
|
|
381
|
+
console.log('Index stats:', refreshResult.indexStats);
|
|
382
|
+
console.log('DB sync:', refreshResult.dbStats);
|
|
383
|
+
break;
|
|
384
|
+
|
|
385
|
+
case 'get':
|
|
386
|
+
const sectionId = args[1];
|
|
387
|
+
if (!sectionId) {
|
|
388
|
+
console.error('Usage: flow-section-resolver get <section-id>');
|
|
389
|
+
process.exit(1);
|
|
390
|
+
}
|
|
391
|
+
const section = await getSection(sectionId);
|
|
392
|
+
if (section) {
|
|
393
|
+
console.log(JSON.stringify(section, null, 2));
|
|
394
|
+
} else {
|
|
395
|
+
console.log(`Section not found: ${sectionId}`);
|
|
396
|
+
}
|
|
397
|
+
break;
|
|
398
|
+
|
|
399
|
+
case 'find':
|
|
400
|
+
const pins = args.slice(1);
|
|
401
|
+
if (pins.length === 0) {
|
|
402
|
+
console.error('Usage: flow-section-resolver find <pin1> [pin2] ...');
|
|
403
|
+
process.exit(1);
|
|
404
|
+
}
|
|
405
|
+
const matches = await getSectionsByPins(pins);
|
|
406
|
+
console.log(`Found ${matches.length} matching sections:`);
|
|
407
|
+
for (const m of matches) {
|
|
408
|
+
console.log(` ${m.id} (score: ${m.matchScore?.toFixed(2) || 'N/A'})`);
|
|
409
|
+
}
|
|
410
|
+
break;
|
|
411
|
+
|
|
412
|
+
case 'task':
|
|
413
|
+
const taskDesc = args.slice(1).join(' ');
|
|
414
|
+
if (!taskDesc) {
|
|
415
|
+
console.error('Usage: flow-section-resolver task "<task description>"');
|
|
416
|
+
process.exit(1);
|
|
417
|
+
}
|
|
418
|
+
const relevant = await getSectionsForTask(taskDesc);
|
|
419
|
+
console.log(`Found ${relevant.length} relevant sections for task:`);
|
|
420
|
+
for (const r of relevant) {
|
|
421
|
+
console.log(` ${r.id} (score: ${r.score?.toFixed(2)}, type: ${r.matchType})`);
|
|
422
|
+
}
|
|
423
|
+
break;
|
|
424
|
+
|
|
425
|
+
case 'stats':
|
|
426
|
+
const stats = await getSectionStats();
|
|
427
|
+
console.log('Section statistics:');
|
|
428
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
429
|
+
break;
|
|
430
|
+
|
|
431
|
+
default:
|
|
432
|
+
console.log(`
|
|
433
|
+
Usage: node scripts/flow-section-resolver.js <command> [args]
|
|
434
|
+
|
|
435
|
+
Commands:
|
|
436
|
+
refresh Regenerate index and sync to database
|
|
437
|
+
get <section-id> Get a section by ID
|
|
438
|
+
find <pins...> Find sections matching pins
|
|
439
|
+
task "<description>" Find sections relevant to a task
|
|
440
|
+
stats Show section statistics
|
|
441
|
+
|
|
442
|
+
Examples:
|
|
443
|
+
node scripts/flow-section-resolver.js get coding-standards:security-patterns-2026-01-11
|
|
444
|
+
node scripts/flow-section-resolver.js find security error-handling
|
|
445
|
+
node scripts/flow-section-resolver.js task "Add user authentication"
|
|
446
|
+
`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// ============================================================
|
|
451
|
+
// Exports
|
|
452
|
+
// ============================================================
|
|
453
|
+
|
|
454
|
+
module.exports = {
|
|
455
|
+
// Index management
|
|
456
|
+
ensureIndex,
|
|
457
|
+
refreshIndex,
|
|
458
|
+
|
|
459
|
+
// Section resolution
|
|
460
|
+
getSection,
|
|
461
|
+
getSections,
|
|
462
|
+
getSectionsByPins,
|
|
463
|
+
getSectionsForTask,
|
|
464
|
+
|
|
465
|
+
// Keyword extraction
|
|
466
|
+
extractPinsFromTask,
|
|
467
|
+
|
|
468
|
+
// Formatting
|
|
469
|
+
formatSectionsAsContext,
|
|
470
|
+
formatSectionsAsReferences,
|
|
471
|
+
|
|
472
|
+
// Convenience
|
|
473
|
+
getSecuritySections,
|
|
474
|
+
getComponentSections,
|
|
475
|
+
getNamingConventionSections,
|
|
476
|
+
|
|
477
|
+
// Re-export stats
|
|
478
|
+
getSectionStats
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
// Run if called directly
|
|
482
|
+
if (require.main === module) {
|
|
483
|
+
main().catch(console.error);
|
|
484
|
+
}
|
|
@@ -369,6 +369,186 @@ async function automaticMemoryManagement() {
|
|
|
369
369
|
}
|
|
370
370
|
}
|
|
371
371
|
|
|
372
|
+
/**
|
|
373
|
+
* Offer knowledge sync if drifted (v1.9.1)
|
|
374
|
+
*/
|
|
375
|
+
async function offerKnowledgeSync() {
|
|
376
|
+
const config = getConfig();
|
|
377
|
+
const morningConfig = config.morningBriefing || {};
|
|
378
|
+
|
|
379
|
+
// Skip if disabled or if auto-regenerate handled it in morning
|
|
380
|
+
if (morningConfig.checkKnowledgeSync === false) {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
try {
|
|
385
|
+
const { checkAllDrift, markAsSynced } = require('./flow-knowledge-sync');
|
|
386
|
+
const driftStatus = checkAllDrift();
|
|
387
|
+
|
|
388
|
+
if (!driftStatus.anyDrift) {
|
|
389
|
+
return; // All synced
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
console.log('');
|
|
393
|
+
console.log(color('cyan', '╔══════════════════════════════════════════════════════════╗'));
|
|
394
|
+
console.log(color('cyan', '║ Knowledge Sync Check ║'));
|
|
395
|
+
console.log(color('cyan', '╚══════════════════════════════════════════════════════════╝'));
|
|
396
|
+
console.log('');
|
|
397
|
+
|
|
398
|
+
console.log('Knowledge files are out of sync:');
|
|
399
|
+
for (const [category, status] of Object.entries(driftStatus.categories)) {
|
|
400
|
+
if (status.status === 'drifted') {
|
|
401
|
+
console.log(` ${color('yellow', '•')} ${category}.md - ${status.reason}`);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
console.log('');
|
|
405
|
+
|
|
406
|
+
const answer = await prompt('Regenerate now? (y/N): ');
|
|
407
|
+
if (answer.toLowerCase() === 'y') {
|
|
408
|
+
try {
|
|
409
|
+
const { spawnSync } = require('child_process');
|
|
410
|
+
const scriptPath = path.join(PROJECT_ROOT, 'scripts', 'flow-onboard');
|
|
411
|
+
console.log(color('dim', 'Regenerating knowledge files...'));
|
|
412
|
+
|
|
413
|
+
const result = spawnSync('node', [scriptPath, '--update-knowledge'], {
|
|
414
|
+
cwd: PROJECT_ROOT,
|
|
415
|
+
stdio: 'pipe',
|
|
416
|
+
timeout: 30000
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
if (result.status === 0) {
|
|
420
|
+
markAsSynced();
|
|
421
|
+
success('Knowledge files regenerated');
|
|
422
|
+
} else {
|
|
423
|
+
warn('Could not regenerate - run: flow knowledge-sync regenerate');
|
|
424
|
+
}
|
|
425
|
+
} catch (err) {
|
|
426
|
+
warn(`Regeneration failed: ${err.message}`);
|
|
427
|
+
}
|
|
428
|
+
} else {
|
|
429
|
+
console.log(color('dim', 'Skipped - run: flow knowledge-sync regenerate'));
|
|
430
|
+
}
|
|
431
|
+
} catch {
|
|
432
|
+
// Knowledge sync not available - skip silently
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Offer tech debt cleanup (v1.9.0)
|
|
438
|
+
*/
|
|
439
|
+
async function offerDebtCleanup() {
|
|
440
|
+
const config = getConfig();
|
|
441
|
+
const techDebtConfig = config.techDebt || {};
|
|
442
|
+
|
|
443
|
+
if (!techDebtConfig.promptOnSessionEnd) {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
try {
|
|
448
|
+
const { TechDebtManager } = require('./flow-tech-debt');
|
|
449
|
+
const manager = new TechDebtManager();
|
|
450
|
+
const stats = manager.getStats();
|
|
451
|
+
|
|
452
|
+
if (stats.totalOpen === 0) {
|
|
453
|
+
return; // No debt to clean up
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
console.log('');
|
|
457
|
+
console.log(color('cyan', '╔══════════════════════════════════════════════════════════╗'));
|
|
458
|
+
console.log(color('cyan', '║ Technical Debt Check ║'));
|
|
459
|
+
console.log(color('cyan', '╚══════════════════════════════════════════════════════════╝'));
|
|
460
|
+
console.log('');
|
|
461
|
+
|
|
462
|
+
// Summary
|
|
463
|
+
const severityParts = [];
|
|
464
|
+
if (stats.bySeverity.critical > 0) severityParts.push(color('red', `${stats.bySeverity.critical} critical`));
|
|
465
|
+
if (stats.bySeverity.high > 0) severityParts.push(color('yellow', `${stats.bySeverity.high} high`));
|
|
466
|
+
if (stats.bySeverity.medium > 0) severityParts.push(color('blue', `${stats.bySeverity.medium} medium`));
|
|
467
|
+
if (stats.bySeverity.low > 0) severityParts.push(color('dim', `${stats.bySeverity.low} low`));
|
|
468
|
+
|
|
469
|
+
console.log(`You have ${color('bold', stats.totalOpen)} open debt items:`);
|
|
470
|
+
console.log(` ${severityParts.join(', ')}`);
|
|
471
|
+
|
|
472
|
+
if (stats.autoFixable > 0) {
|
|
473
|
+
console.log(` ${color('green', '✓')} ${stats.autoFixable} are auto-fixable`);
|
|
474
|
+
}
|
|
475
|
+
if (stats.agingCount > 0) {
|
|
476
|
+
console.log(` ${color('yellow', '⚠')} ${stats.agingCount} have been aging (3+ sessions)`);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
console.log('');
|
|
480
|
+
console.log('Would you like to address some debt?');
|
|
481
|
+
console.log('');
|
|
482
|
+
if (stats.autoFixable > 0) {
|
|
483
|
+
console.log(` ${color('cyan', '[1]')} Quick fixes only (${stats.autoFixable} items)`);
|
|
484
|
+
console.log(` ${color('dim', 'Auto-fix: remove console.logs, unused imports')}`);
|
|
485
|
+
}
|
|
486
|
+
if (stats.agingCount > 0) {
|
|
487
|
+
console.log(` ${color('cyan', '[2]')} Aging issues (${stats.agingCount} items)`);
|
|
488
|
+
console.log(` ${color('dim', 'Items persisting 3+ sessions')}`);
|
|
489
|
+
}
|
|
490
|
+
console.log(` ${color('cyan', '[3]')} Full cleanup (${stats.totalOpen} items)`);
|
|
491
|
+
console.log(` ${color('dim', 'Address all open debt')}`);
|
|
492
|
+
console.log(` ${color('cyan', '[4]')} Skip for now`);
|
|
493
|
+
console.log('');
|
|
494
|
+
|
|
495
|
+
const choice = await prompt('Choice [4]: ');
|
|
496
|
+
|
|
497
|
+
switch (choice.trim()) {
|
|
498
|
+
case '1':
|
|
499
|
+
if (stats.autoFixable > 0) {
|
|
500
|
+
console.log('');
|
|
501
|
+
console.log(color('cyan', 'Running auto-fixes...'));
|
|
502
|
+
const result = manager.runAutoFix();
|
|
503
|
+
if (result.fixed > 0) {
|
|
504
|
+
success(`Fixed ${result.fixed} issues`);
|
|
505
|
+
for (const file of result.files) {
|
|
506
|
+
console.log(` ${color('dim', file)}`);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
if (result.failed > 0) {
|
|
510
|
+
warn(`Could not fix ${result.failed} issues`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
break;
|
|
514
|
+
|
|
515
|
+
case '2':
|
|
516
|
+
if (stats.agingCount > 0) {
|
|
517
|
+
console.log('');
|
|
518
|
+
console.log(color('cyan', 'Aging items need manual review:'));
|
|
519
|
+
const aging = manager.getAgingIssues();
|
|
520
|
+
for (const issue of aging) {
|
|
521
|
+
console.log(` ${color('dim', `[${issue.id}]`)} ${issue.file}:${issue.line}`);
|
|
522
|
+
console.log(` ${issue.description}`);
|
|
523
|
+
}
|
|
524
|
+
console.log('');
|
|
525
|
+
console.log(color('dim', 'Run /wogi-debt promote <id> to create a task from any item.'));
|
|
526
|
+
}
|
|
527
|
+
break;
|
|
528
|
+
|
|
529
|
+
case '3':
|
|
530
|
+
console.log('');
|
|
531
|
+
console.log(color('cyan', 'All open debt items:'));
|
|
532
|
+
const all = manager.getOpenIssues();
|
|
533
|
+
for (const issue of all) {
|
|
534
|
+
const severityColor = issue.severity === 'critical' ? 'red' : issue.severity === 'high' ? 'yellow' : 'dim';
|
|
535
|
+
console.log(` ${color('dim', `[${issue.id}]`)} ${issue.file}:${issue.line} ${color(severityColor, `(${issue.severity})`)}`);
|
|
536
|
+
console.log(` ${issue.description}`);
|
|
537
|
+
}
|
|
538
|
+
console.log('');
|
|
539
|
+
console.log(color('dim', 'Run /wogi-debt fix to auto-fix safe items, or /wogi-debt promote <id> to create tasks.'));
|
|
540
|
+
break;
|
|
541
|
+
|
|
542
|
+
case '4':
|
|
543
|
+
default:
|
|
544
|
+
console.log(color('dim', 'Skipping debt cleanup.'));
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
} catch {
|
|
548
|
+
// Tech debt manager not available - skip silently
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
372
552
|
/**
|
|
373
553
|
* Show status summary
|
|
374
554
|
*/
|
|
@@ -422,6 +602,12 @@ async function main() {
|
|
|
422
602
|
// v1.8.0: Automatic memory management
|
|
423
603
|
await automaticMemoryManagement();
|
|
424
604
|
|
|
605
|
+
// v1.9.1: Offer knowledge sync if drifted
|
|
606
|
+
await offerKnowledgeSync();
|
|
607
|
+
|
|
608
|
+
// v1.9.0: Offer tech debt cleanup
|
|
609
|
+
await offerDebtCleanup();
|
|
610
|
+
|
|
425
611
|
console.log('');
|
|
426
612
|
|
|
427
613
|
// Offer to push
|
|
@@ -431,7 +617,7 @@ async function main() {
|
|
|
431
617
|
showSummary();
|
|
432
618
|
}
|
|
433
619
|
|
|
434
|
-
main().catch(
|
|
435
|
-
console.error(
|
|
620
|
+
main().catch(err => {
|
|
621
|
+
console.error('Error:', err.message);
|
|
436
622
|
process.exit(1);
|
|
437
623
|
});
|