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.
Files changed (46) 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/lib/unified-wizard.js +569 -30
  6. package/package.json +1 -1
  7. package/scripts/MEMORY-ARCHITECTURE.md +150 -0
  8. package/scripts/flow +20 -19
  9. package/scripts/flow-auto-context.js +97 -3
  10. package/scripts/flow-conflict-resolver.js +735 -0
  11. package/scripts/flow-context-gatherer.js +520 -0
  12. package/scripts/flow-context-monitor.js +148 -19
  13. package/scripts/flow-damage-control.js +5 -1
  14. package/scripts/flow-export-profile +168 -1
  15. package/scripts/flow-import-profile +257 -6
  16. package/scripts/flow-instruction-richness.js +182 -18
  17. package/scripts/flow-knowledge-router.js +2 -0
  18. package/scripts/flow-knowledge-sync.js +2 -0
  19. package/scripts/{flow-transcript-chunking.js → flow-long-input-chunking.js} +4 -2
  20. package/scripts/{flow-transcript-parsing.js → flow-long-input-parsing.js} +35 -0
  21. package/scripts/{flow-transcript-stories.js → flow-long-input-stories.js} +86 -38
  22. package/scripts/{flow-transcript-digest.js → flow-long-input.js} +231 -15
  23. package/scripts/flow-memory-db.js +386 -1
  24. package/scripts/flow-memory-sync.js +2 -0
  25. package/scripts/flow-model-adapter.js +53 -29
  26. package/scripts/flow-model-router.js +246 -1
  27. package/scripts/flow-morning.js +94 -0
  28. package/scripts/flow-onboard +223 -10
  29. package/scripts/flow-orchestrate-validation.js +539 -0
  30. package/scripts/flow-orchestrate.js +16 -507
  31. package/scripts/flow-pattern-extractor.js +1265 -0
  32. package/scripts/flow-prompt-composer.js +222 -2
  33. package/scripts/flow-quality-guard.js +594 -0
  34. package/scripts/flow-section-index.js +713 -0
  35. package/scripts/flow-section-resolver.js +484 -0
  36. package/scripts/flow-session-end.js +188 -2
  37. package/scripts/flow-skill-create.js +19 -3
  38. package/scripts/flow-skill-matcher.js +122 -7
  39. package/scripts/flow-statusline-setup.js +218 -0
  40. package/scripts/flow-step-review.js +19 -0
  41. package/scripts/flow-tech-debt.js +734 -0
  42. package/scripts/flow-utils.js +2 -0
  43. package/scripts/hooks/core/long-input-gate.js +293 -0
  44. package/scripts/flow-parallel-detector.js +0 -399
  45. package/scripts/flow-parallel-dispatch.js +0 -987
  46. /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(e => {
435
- console.error(console.error('Error:', err.message));
620
+ main().catch(err => {
621
+ console.error('Error:', err.message);
436
622
  process.exit(1);
437
623
  });