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.
- 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/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
|
@@ -28,12 +28,23 @@ const {
|
|
|
28
28
|
safeJsonParse,
|
|
29
29
|
getConfig,
|
|
30
30
|
printHeader,
|
|
31
|
-
printSection
|
|
31
|
+
printSection,
|
|
32
|
+
estimateTokens
|
|
32
33
|
} = require('./flow-utils');
|
|
33
34
|
|
|
34
35
|
const { analyzeTask } = require('./flow-task-analyzer');
|
|
35
36
|
const { loadRegistry, loadStats } = require('./flow-models');
|
|
36
37
|
|
|
38
|
+
// Smart Context System integration
|
|
39
|
+
let contextGatherer = null;
|
|
40
|
+
let instructionRichness = null;
|
|
41
|
+
try {
|
|
42
|
+
contextGatherer = require('./flow-context-gatherer');
|
|
43
|
+
instructionRichness = require('./flow-instruction-richness');
|
|
44
|
+
} catch (err) {
|
|
45
|
+
// Smart Context modules not available
|
|
46
|
+
}
|
|
47
|
+
|
|
37
48
|
// Phase 3: Import cascade fallback (cached singleton)
|
|
38
49
|
let cascadeModule = null;
|
|
39
50
|
try {
|
|
@@ -654,6 +665,236 @@ function getRoutingConfig() {
|
|
|
654
665
|
};
|
|
655
666
|
}
|
|
656
667
|
|
|
668
|
+
// ============================================================
|
|
669
|
+
// Phase 4: Single Model Evaluation (Smart Context Integration)
|
|
670
|
+
// ============================================================
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Evaluate if a single model can handle a task.
|
|
674
|
+
* Used when user has only one additional model configured.
|
|
675
|
+
*
|
|
676
|
+
* @param {Object} params - Evaluation parameters
|
|
677
|
+
* @param {string} params.modelId - Model identifier
|
|
678
|
+
* @param {Object} params.analysis - Task analysis
|
|
679
|
+
* @param {string} params.taskDescription - Task description for context estimation
|
|
680
|
+
* @returns {Promise<Object>} Evaluation result
|
|
681
|
+
*/
|
|
682
|
+
async function evaluateSingleModel(params) {
|
|
683
|
+
const { modelId, analysis, taskDescription = '' } = params;
|
|
684
|
+
|
|
685
|
+
// Load registry
|
|
686
|
+
const registry = loadRegistry();
|
|
687
|
+
if (!registry) {
|
|
688
|
+
return { canHandle: false, reason: 'Model registry not found' };
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// Find model
|
|
692
|
+
const model = registry.models?.[modelId];
|
|
693
|
+
if (!model) {
|
|
694
|
+
return { canHandle: false, reason: `Model not found: ${modelId}` };
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// 1. Check capabilities
|
|
698
|
+
const requiredCaps = new Set(analysis.capabilities || []);
|
|
699
|
+
const modelCaps = new Set(model.capabilities || []);
|
|
700
|
+
const missingCaps = [];
|
|
701
|
+
|
|
702
|
+
for (const cap of requiredCaps) {
|
|
703
|
+
if (!modelCaps.has(cap)) {
|
|
704
|
+
missingCaps.push(cap);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
if (missingCaps.length > 0) {
|
|
709
|
+
return {
|
|
710
|
+
canHandle: false,
|
|
711
|
+
reason: 'Missing capabilities',
|
|
712
|
+
missing: missingCaps,
|
|
713
|
+
modelCapabilities: model.capabilities,
|
|
714
|
+
requiredCapabilities: [...requiredCaps]
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// 2. Check language proficiency
|
|
719
|
+
const primaryLang = analysis.languages?.primary;
|
|
720
|
+
if (primaryLang) {
|
|
721
|
+
const langCheck = checkLanguageProficiency(model, primaryLang);
|
|
722
|
+
if (!langCheck.meets) {
|
|
723
|
+
return {
|
|
724
|
+
canHandle: false,
|
|
725
|
+
reason: langCheck.reason,
|
|
726
|
+
proficiency: langCheck.proficiency,
|
|
727
|
+
required: langCheck.required
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// 3. Estimate context requirements
|
|
733
|
+
let contextRequirements = null;
|
|
734
|
+
let estimatedTaskTokens = 0;
|
|
735
|
+
|
|
736
|
+
if (contextGatherer && taskDescription) {
|
|
737
|
+
// Use Smart Context to estimate what context this task needs
|
|
738
|
+
const contextResult = await contextGatherer.gatherContext({
|
|
739
|
+
task: taskDescription,
|
|
740
|
+
model: modelId,
|
|
741
|
+
format: 'summary' // Use summary for estimation
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
contextRequirements = {
|
|
745
|
+
sectionsNeeded: contextResult.stats?.sectionsIncluded || 0,
|
|
746
|
+
tokensEstimated: contextResult.stats?.totalTokens || 0,
|
|
747
|
+
budgetUsed: contextResult.stats?.budgetUsed || '0%',
|
|
748
|
+
modelDensity: contextResult.stats?.modelPrefs?.density || 'standard'
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
// Estimate total task tokens (context + task + buffer for output)
|
|
752
|
+
const taskTextTokens = estimateTokens?.(taskDescription) || Math.ceil(taskDescription.length / 4);
|
|
753
|
+
const outputBuffer = model.maxOutputTokens ? model.maxOutputTokens * 0.5 : 4000;
|
|
754
|
+
|
|
755
|
+
estimatedTaskTokens = contextRequirements.tokensEstimated + taskTextTokens + outputBuffer;
|
|
756
|
+
} else {
|
|
757
|
+
// Fallback estimation without Smart Context
|
|
758
|
+
const taskTextTokens = estimateTokens?.(taskDescription) || Math.ceil(taskDescription.length / 4);
|
|
759
|
+
const baseContextEstimate = 5000; // Conservative base estimate
|
|
760
|
+
const outputBuffer = model.maxOutputTokens ? model.maxOutputTokens * 0.5 : 4000;
|
|
761
|
+
|
|
762
|
+
estimatedTaskTokens = taskTextTokens + baseContextEstimate + outputBuffer;
|
|
763
|
+
|
|
764
|
+
contextRequirements = {
|
|
765
|
+
sectionsNeeded: 'unknown',
|
|
766
|
+
tokensEstimated: baseContextEstimate,
|
|
767
|
+
budgetUsed: 'estimated',
|
|
768
|
+
modelDensity: 'standard'
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// 4. Check if task fits in context window
|
|
773
|
+
const contextWindow = model.contextWindow || 128000;
|
|
774
|
+
const usableContext = contextWindow * 0.7; // Reserve 30% buffer
|
|
775
|
+
|
|
776
|
+
if (estimatedTaskTokens > usableContext) {
|
|
777
|
+
return {
|
|
778
|
+
canHandle: false,
|
|
779
|
+
reason: 'Context too large for model',
|
|
780
|
+
estimatedTokens: estimatedTaskTokens,
|
|
781
|
+
contextWindow,
|
|
782
|
+
usableContext: Math.floor(usableContext),
|
|
783
|
+
overflow: estimatedTaskTokens - usableContext
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// 5. Calculate estimated cost
|
|
788
|
+
let estimatedCost = null;
|
|
789
|
+
if (model.pricing) {
|
|
790
|
+
const inputCost = (estimatedTaskTokens / 1000) * (model.pricing.inputPer1kTokens || 0);
|
|
791
|
+
const outputCost = ((model.maxOutputTokens || 4000) / 1000) * (model.pricing.outputPer1kTokens || 0);
|
|
792
|
+
estimatedCost = {
|
|
793
|
+
input: inputCost.toFixed(4),
|
|
794
|
+
output: outputCost.toFixed(4),
|
|
795
|
+
total: (inputCost + outputCost).toFixed(4),
|
|
796
|
+
currency: model.pricing.currency || 'USD'
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// Model can handle the task
|
|
801
|
+
return {
|
|
802
|
+
canHandle: true,
|
|
803
|
+
modelId,
|
|
804
|
+
displayName: model.displayName,
|
|
805
|
+
costTier: model.costTier,
|
|
806
|
+
contextRequirements,
|
|
807
|
+
estimatedTokens: estimatedTaskTokens,
|
|
808
|
+
contextWindow,
|
|
809
|
+
contextUsage: `${((estimatedTaskTokens / contextWindow) * 100).toFixed(1)}%`,
|
|
810
|
+
estimatedCost,
|
|
811
|
+
// Include model preferences for caller
|
|
812
|
+
modelPreferences: model.contextPreferences || {
|
|
813
|
+
density: 'standard',
|
|
814
|
+
explicitExamples: true,
|
|
815
|
+
patternHints: true,
|
|
816
|
+
minContextForQuality: 0.5
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Evaluate multiple models for a task and recommend the best one.
|
|
823
|
+
* Useful when user wants to compare options.
|
|
824
|
+
*
|
|
825
|
+
* @param {Object} params - Evaluation parameters
|
|
826
|
+
* @param {Object} params.analysis - Task analysis
|
|
827
|
+
* @param {string} params.taskDescription - Task description
|
|
828
|
+
* @param {string[]} params.modelIds - Models to evaluate (if not provided, evaluates all)
|
|
829
|
+
* @returns {Promise<Object>} Comparison result
|
|
830
|
+
*/
|
|
831
|
+
async function evaluateModelsForTask(params) {
|
|
832
|
+
const { analysis, taskDescription, modelIds = null } = params;
|
|
833
|
+
|
|
834
|
+
const registry = loadRegistry();
|
|
835
|
+
if (!registry) {
|
|
836
|
+
return { success: false, error: 'Model registry not found' };
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// Get models to evaluate
|
|
840
|
+
let models;
|
|
841
|
+
if (modelIds && modelIds.length > 0) {
|
|
842
|
+
models = modelIds.filter(id => registry.models?.[id]);
|
|
843
|
+
} else {
|
|
844
|
+
models = Object.keys(registry.models || {});
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
if (models.length === 0) {
|
|
848
|
+
return { success: false, error: 'No models to evaluate' };
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// Evaluate each model
|
|
852
|
+
const evaluations = await Promise.all(
|
|
853
|
+
models.map(async modelId => {
|
|
854
|
+
const result = await evaluateSingleModel({
|
|
855
|
+
modelId,
|
|
856
|
+
analysis,
|
|
857
|
+
taskDescription
|
|
858
|
+
});
|
|
859
|
+
return { modelId, ...result };
|
|
860
|
+
})
|
|
861
|
+
);
|
|
862
|
+
|
|
863
|
+
// Separate capable and incapable models
|
|
864
|
+
const capable = evaluations.filter(e => e.canHandle);
|
|
865
|
+
const incapable = evaluations.filter(e => !e.canHandle);
|
|
866
|
+
|
|
867
|
+
// Rank capable models by quality (capability + cost efficiency)
|
|
868
|
+
const ranked = capable.sort((a, b) => {
|
|
869
|
+
// Prefer models with better context efficiency
|
|
870
|
+
const efficiencyA = parseFloat(a.contextUsage) || 100;
|
|
871
|
+
const efficiencyB = parseFloat(b.contextUsage) || 100;
|
|
872
|
+
|
|
873
|
+
// Lower context usage is better
|
|
874
|
+
if (Math.abs(efficiencyA - efficiencyB) > 10) {
|
|
875
|
+
return efficiencyA - efficiencyB;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// Then by cost tier (quality-first)
|
|
879
|
+
const tierOrder = { premium: 0, standard: 1, economy: 2 };
|
|
880
|
+
return (tierOrder[a.costTier] || 1) - (tierOrder[b.costTier] || 1);
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
return {
|
|
884
|
+
success: true,
|
|
885
|
+
taskDescription,
|
|
886
|
+
totalEvaluated: models.length,
|
|
887
|
+
capableCount: capable.length,
|
|
888
|
+
recommended: ranked[0] || null,
|
|
889
|
+
alternatives: ranked.slice(1),
|
|
890
|
+
incapable: incapable.map(e => ({
|
|
891
|
+
modelId: e.modelId,
|
|
892
|
+
reason: e.reason,
|
|
893
|
+
details: e.missing || e.overflow || null
|
|
894
|
+
}))
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
|
|
657
898
|
// ============================================================
|
|
658
899
|
// Main Router
|
|
659
900
|
// ============================================================
|
|
@@ -864,6 +1105,10 @@ module.exports = {
|
|
|
864
1105
|
checkCascadeFallback,
|
|
865
1106
|
getRoutingConfig,
|
|
866
1107
|
|
|
1108
|
+
// Phase 4: Single model evaluation (Smart Context integration)
|
|
1109
|
+
evaluateSingleModel,
|
|
1110
|
+
evaluateModelsForTask,
|
|
1111
|
+
|
|
867
1112
|
// Registry/stats access
|
|
868
1113
|
loadRegistry,
|
|
869
1114
|
loadStats,
|
package/scripts/flow-morning.js
CHANGED
|
@@ -16,6 +16,7 @@ const path = require('path');
|
|
|
16
16
|
const { execSync } = require('child_process');
|
|
17
17
|
const {
|
|
18
18
|
PATHS,
|
|
19
|
+
PROJECT_ROOT,
|
|
19
20
|
fileExists,
|
|
20
21
|
readJson,
|
|
21
22
|
readFile,
|
|
@@ -421,6 +422,99 @@ function printBriefing(briefing) {
|
|
|
421
422
|
console.log('');
|
|
422
423
|
}
|
|
423
424
|
|
|
425
|
+
// Knowledge Sync Check
|
|
426
|
+
if (morningConfig.checkKnowledgeSync !== false) {
|
|
427
|
+
try {
|
|
428
|
+
const { checkAllDrift, markAsSynced } = require('./flow-knowledge-sync');
|
|
429
|
+
const driftStatus = checkAllDrift();
|
|
430
|
+
|
|
431
|
+
if (driftStatus.anyDrift) {
|
|
432
|
+
printSection('KNOWLEDGE SYNC');
|
|
433
|
+
|
|
434
|
+
// Show what drifted
|
|
435
|
+
const driftedCategories = [];
|
|
436
|
+
for (const [category, status] of Object.entries(driftStatus.categories)) {
|
|
437
|
+
if (status.status === 'drifted') {
|
|
438
|
+
console.log(` ${color('yellow', '\u26a0')} ${category}.md out of sync (${status.reason})`);
|
|
439
|
+
driftedCategories.push(category);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Auto-regenerate if enabled
|
|
444
|
+
if (morningConfig.autoRegenerateKnowledge !== false) {
|
|
445
|
+
try {
|
|
446
|
+
const { spawnSync } = require('child_process');
|
|
447
|
+
const scriptPath = path.join(PROJECT_ROOT, 'scripts', 'flow-onboard');
|
|
448
|
+
const result = spawnSync('node', [scriptPath, '--update-knowledge'], {
|
|
449
|
+
cwd: PROJECT_ROOT,
|
|
450
|
+
stdio: 'pipe',
|
|
451
|
+
timeout: 30000
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
if (result.status === 0) {
|
|
455
|
+
markAsSynced();
|
|
456
|
+
console.log(` ${color('green', '\u2713')} Auto-regenerated knowledge files`);
|
|
457
|
+
} else {
|
|
458
|
+
console.log(` ${color('yellow', '!')} Could not auto-regenerate`);
|
|
459
|
+
console.log(` Run: ${color('cyan', 'flow knowledge-sync regenerate')}`);
|
|
460
|
+
}
|
|
461
|
+
} catch (err) {
|
|
462
|
+
console.log(` ${color('yellow', '!')} Could not auto-regenerate: ${err.message}`);
|
|
463
|
+
console.log(` Run: ${color('cyan', 'flow knowledge-sync regenerate')}`);
|
|
464
|
+
}
|
|
465
|
+
} else {
|
|
466
|
+
console.log(` ${color('dim', 'Run:')} ${color('cyan', 'flow knowledge-sync regenerate')}`);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
console.log('');
|
|
470
|
+
}
|
|
471
|
+
} catch {
|
|
472
|
+
// Knowledge sync not available - skip silently
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Technical Debt Summary
|
|
477
|
+
if (morningConfig.showTechDebt !== false) {
|
|
478
|
+
try {
|
|
479
|
+
const { TechDebtManager } = require('./flow-tech-debt');
|
|
480
|
+
const debtManager = new TechDebtManager();
|
|
481
|
+
const stats = debtManager.getStats();
|
|
482
|
+
|
|
483
|
+
if (stats.totalOpen > 0) {
|
|
484
|
+
printSection('TECHNICAL DEBT');
|
|
485
|
+
|
|
486
|
+
// Summary line
|
|
487
|
+
const severityParts = [];
|
|
488
|
+
if (stats.bySeverity.critical > 0) severityParts.push(color('red', `${stats.bySeverity.critical} critical`));
|
|
489
|
+
if (stats.bySeverity.high > 0) severityParts.push(color('yellow', `${stats.bySeverity.high} high`));
|
|
490
|
+
if (stats.bySeverity.medium > 0) severityParts.push(color('blue', `${stats.bySeverity.medium} medium`));
|
|
491
|
+
if (stats.bySeverity.low > 0) severityParts.push(color('dim', `${stats.bySeverity.low} low`));
|
|
492
|
+
|
|
493
|
+
console.log(` Open issues: ${stats.totalOpen} (${severityParts.join(', ')})`);
|
|
494
|
+
|
|
495
|
+
// Aging warning
|
|
496
|
+
if (stats.agingCount > 0) {
|
|
497
|
+
console.log(` ${color('yellow', '\u26a0')} ${stats.agingCount} item${stats.agingCount !== 1 ? 's' : ''} aging (seen 3+ sessions)`);
|
|
498
|
+
|
|
499
|
+
// Auto-promote aging items to tasks
|
|
500
|
+
const promoted = debtManager.promoteAgingToTasks();
|
|
501
|
+
if (promoted.length > 0) {
|
|
502
|
+
console.log(` ${color('green', '\u2713')} ${promoted.length} aging item${promoted.length !== 1 ? 's' : ''} added to task queue`);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Auto-fixable hint
|
|
507
|
+
if (stats.autoFixable > 0) {
|
|
508
|
+
console.log(` ${color('green', '\u2713')} ${stats.autoFixable} auto-fixable - run ${color('cyan', '/wogi-debt fix')}`);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
console.log('');
|
|
512
|
+
}
|
|
513
|
+
} catch {
|
|
514
|
+
// Tech debt manager not available - skip silently
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
424
518
|
// Changes since last session
|
|
425
519
|
if (morningConfig.showChanges !== false) {
|
|
426
520
|
const changes = briefing.changesSinceLastSession;
|
package/scripts/flow-onboard
CHANGED
|
@@ -940,9 +940,176 @@ print_summary() {
|
|
|
940
940
|
echo ""
|
|
941
941
|
}
|
|
942
942
|
|
|
943
|
+
ask_onboard_mode() {
|
|
944
|
+
echo -e "${BOLD}How would you like to set up this project?${NC}"
|
|
945
|
+
echo ""
|
|
946
|
+
echo " ${CYAN}[1]${NC} Fresh start - Analyze this project only"
|
|
947
|
+
echo " Scan project structure, detect patterns, generate workflow files"
|
|
948
|
+
echo ""
|
|
949
|
+
echo " ${CYAN}[2]${NC} Import profile - Load patterns from exported profile"
|
|
950
|
+
echo " Import from a .zip profile exported from another project"
|
|
951
|
+
echo ""
|
|
952
|
+
echo " ${CYAN}[3]${NC} Learn from project - Copy patterns from another folder"
|
|
953
|
+
echo " Point to another project folder to extract and apply its patterns"
|
|
954
|
+
echo ""
|
|
955
|
+
read -p "Choice [1]: " onboard_choice
|
|
956
|
+
|
|
957
|
+
case "$onboard_choice" in
|
|
958
|
+
2) ONBOARD_MODE="import" ;;
|
|
959
|
+
3) ONBOARD_MODE="learn" ;;
|
|
960
|
+
*) ONBOARD_MODE="fresh" ;;
|
|
961
|
+
esac
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
handle_import_profile() {
|
|
965
|
+
echo ""
|
|
966
|
+
echo -e "${BOLD}Import from Profile${NC}"
|
|
967
|
+
echo ""
|
|
968
|
+
read -p "Path to profile ZIP: " profile_path
|
|
969
|
+
|
|
970
|
+
if [ -z "$profile_path" ] || [ ! -f "$profile_path" ]; then
|
|
971
|
+
echo -e "${RED}Error: Profile not found: $profile_path${NC}"
|
|
972
|
+
return 1
|
|
973
|
+
fi
|
|
974
|
+
|
|
975
|
+
echo ""
|
|
976
|
+
echo -e "${CYAN}Importing profile...${NC}"
|
|
977
|
+
"$SCRIPT_DIR/flow-import-profile" "$profile_path" --force
|
|
978
|
+
|
|
979
|
+
echo ""
|
|
980
|
+
echo -e "${GREEN}✓${NC} Profile imported. Continuing with project scan..."
|
|
981
|
+
echo ""
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
handle_learn_from_project() {
|
|
985
|
+
echo ""
|
|
986
|
+
echo -e "${BOLD}Learn from Another Project${NC}"
|
|
987
|
+
echo ""
|
|
988
|
+
read -p "Path to reference project: " reference_path
|
|
989
|
+
|
|
990
|
+
if [ -z "$reference_path" ] || [ ! -d "$reference_path" ]; then
|
|
991
|
+
echo -e "${RED}Error: Project folder not found: $reference_path${NC}"
|
|
992
|
+
return 1
|
|
993
|
+
fi
|
|
994
|
+
|
|
995
|
+
echo ""
|
|
996
|
+
echo -e "${CYAN}Scanning reference project for patterns...${NC}"
|
|
997
|
+
|
|
998
|
+
# Check for conflicts
|
|
999
|
+
echo -e "${DIM}Do you want to resolve any pattern conflicts interactively?${NC}"
|
|
1000
|
+
echo " [Y] Yes - I'll choose which patterns to adopt"
|
|
1001
|
+
echo " [n] No - Auto-accept the recommended patterns"
|
|
1002
|
+
echo ""
|
|
1003
|
+
read -p "Resolve conflicts? [Y/n]: " resolve_conflicts
|
|
1004
|
+
|
|
1005
|
+
local resolve_flag=""
|
|
1006
|
+
if [[ ! "$resolve_conflicts" =~ ^[Nn]$ ]]; then
|
|
1007
|
+
resolve_flag="--resolve-conflicts"
|
|
1008
|
+
fi
|
|
1009
|
+
|
|
1010
|
+
"$SCRIPT_DIR/flow-import-profile" --scan "$reference_path" $resolve_flag --force
|
|
1011
|
+
|
|
1012
|
+
echo ""
|
|
1013
|
+
echo -e "${GREEN}✓${NC} Patterns imported. Continuing with project scan..."
|
|
1014
|
+
echo ""
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
run_pattern_extraction() {
|
|
1018
|
+
# Run pattern extraction on current project to detect conflicts
|
|
1019
|
+
echo -e "${CYAN}Scanning for patterns in current project...${NC}"
|
|
1020
|
+
|
|
1021
|
+
local temp_patterns=$(mktemp)
|
|
1022
|
+
if node "$SCRIPT_DIR/flow-pattern-extractor.js" --format json --with-conflicts --output "$temp_patterns" 2>/dev/null; then
|
|
1023
|
+
local conflict_count=$(node -e "const p = require('$temp_patterns'); console.log(p.conflicts ? p.conflicts.length : 0)" 2>/dev/null || echo "0")
|
|
1024
|
+
|
|
1025
|
+
if [ "$conflict_count" -gt 0 ]; then
|
|
1026
|
+
echo -e "${YELLOW}!${NC} Found $conflict_count conflicting patterns in this codebase"
|
|
1027
|
+
echo ""
|
|
1028
|
+
echo "Some patterns in your codebase are inconsistent (e.g., old vs new code)."
|
|
1029
|
+
echo "Would you like to resolve these conflicts now?"
|
|
1030
|
+
echo ""
|
|
1031
|
+
read -p "Resolve conflicts? [y/N]: " resolve_now
|
|
1032
|
+
|
|
1033
|
+
if [[ "$resolve_now" =~ ^[Yy]$ ]]; then
|
|
1034
|
+
local conflicts_temp=$(mktemp)
|
|
1035
|
+
node -e "const p = require('$temp_patterns'); console.log(JSON.stringify(p.conflicts, null, 2))" > "$conflicts_temp" 2>/dev/null
|
|
1036
|
+
|
|
1037
|
+
local resolved_file=$(mktemp)
|
|
1038
|
+
if node "$SCRIPT_DIR/flow-conflict-resolver.js" --input "$conflicts_temp" --output "$resolved_file"; then
|
|
1039
|
+
# Apply resolved patterns to decisions.md
|
|
1040
|
+
local decisions_content=$(node "$SCRIPT_DIR/flow-conflict-resolver.js" --input "$resolved_file" --format decisions 2>/dev/null)
|
|
1041
|
+
if [ -n "$decisions_content" ]; then
|
|
1042
|
+
echo "" >> "$WORKFLOW_DIR/state/decisions.md"
|
|
1043
|
+
echo "---" >> "$WORKFLOW_DIR/state/decisions.md"
|
|
1044
|
+
echo "" >> "$WORKFLOW_DIR/state/decisions.md"
|
|
1045
|
+
echo "$decisions_content" >> "$WORKFLOW_DIR/state/decisions.md"
|
|
1046
|
+
echo -e "${GREEN}✓${NC} Pattern decisions saved to decisions.md"
|
|
1047
|
+
fi
|
|
1048
|
+
fi
|
|
1049
|
+
rm -f "$conflicts_temp" "$resolved_file"
|
|
1050
|
+
fi
|
|
1051
|
+
else
|
|
1052
|
+
echo -e "${GREEN}✓${NC} No conflicting patterns detected"
|
|
1053
|
+
fi
|
|
1054
|
+
|
|
1055
|
+
# Save patterns for reference
|
|
1056
|
+
mkdir -p "$WORKFLOW_DIR/extracted"
|
|
1057
|
+
mv "$temp_patterns" "$WORKFLOW_DIR/extracted/project-patterns.json"
|
|
1058
|
+
echo -e "${GREEN}✓${NC} Patterns saved to .workflow/extracted/project-patterns.json"
|
|
1059
|
+
else
|
|
1060
|
+
rm -f "$temp_patterns"
|
|
1061
|
+
fi
|
|
1062
|
+
echo ""
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
ask_additional_docs() {
|
|
1066
|
+
echo ""
|
|
1067
|
+
echo -e "${BOLD}Additional Documentation${NC}"
|
|
1068
|
+
echo ""
|
|
1069
|
+
echo "Do you have any additional documentation to process?"
|
|
1070
|
+
echo "(PRD, roadmap, specs, architecture docs, etc.)"
|
|
1071
|
+
echo ""
|
|
1072
|
+
echo " ${CYAN}[1]${NC} Enter file path"
|
|
1073
|
+
echo " ${CYAN}[2]${NC} Paste content directly"
|
|
1074
|
+
echo " ${CYAN}[3]${NC} Skip"
|
|
1075
|
+
echo ""
|
|
1076
|
+
read -p "Choice [3]: " doc_choice
|
|
1077
|
+
|
|
1078
|
+
case "$doc_choice" in
|
|
1079
|
+
1)
|
|
1080
|
+
read -p "Path to document: " doc_path
|
|
1081
|
+
if [ -f "$doc_path" ]; then
|
|
1082
|
+
# Check size
|
|
1083
|
+
local size=$(wc -c < "$doc_path")
|
|
1084
|
+
if [ "$size" -gt 50000 ]; then
|
|
1085
|
+
echo -e "${CYAN}Processing large document through long-input pipeline...${NC}"
|
|
1086
|
+
if [ -f "$SCRIPT_DIR/flow-long-input.js" ]; then
|
|
1087
|
+
node "$SCRIPT_DIR/flow-long-input.js" process "$doc_path" 2>/dev/null || true
|
|
1088
|
+
fi
|
|
1089
|
+
else
|
|
1090
|
+
ADDITIONAL_DOCS=$(cat "$doc_path" 2>/dev/null)
|
|
1091
|
+
echo -e "${GREEN}✓${NC} Document loaded"
|
|
1092
|
+
fi
|
|
1093
|
+
else
|
|
1094
|
+
echo -e "${YELLOW}File not found${NC}"
|
|
1095
|
+
fi
|
|
1096
|
+
;;
|
|
1097
|
+
2)
|
|
1098
|
+
echo "Paste your content (end with a line containing just 'END'):"
|
|
1099
|
+
local pasted=""
|
|
1100
|
+
while IFS= read -r line; do
|
|
1101
|
+
[[ "$line" == "END" ]] && break
|
|
1102
|
+
pasted+="$line"$'\n'
|
|
1103
|
+
done
|
|
1104
|
+
ADDITIONAL_DOCS="$pasted"
|
|
1105
|
+
echo -e "${GREEN}✓${NC} Content captured"
|
|
1106
|
+
;;
|
|
1107
|
+
esac
|
|
1108
|
+
}
|
|
1109
|
+
|
|
943
1110
|
main() {
|
|
944
1111
|
print_header
|
|
945
|
-
|
|
1112
|
+
|
|
946
1113
|
# Check if already onboarded
|
|
947
1114
|
if [ -f "$WORKFLOW_DIR/specs/project.md" ] && grep -q "Tech Stack" "$WORKFLOW_DIR/specs/project.md" 2>/dev/null; then
|
|
948
1115
|
echo -e "${YELLOW}⚠ This project appears to be already onboarded.${NC}"
|
|
@@ -954,14 +1121,30 @@ main() {
|
|
|
954
1121
|
fi
|
|
955
1122
|
echo ""
|
|
956
1123
|
fi
|
|
957
|
-
|
|
1124
|
+
|
|
958
1125
|
# Ensure workflow exists
|
|
959
1126
|
if [ ! -d "$WORKFLOW_DIR" ]; then
|
|
960
1127
|
echo -e "${YELLOW}Workflow not initialized. Running install first...${NC}"
|
|
961
1128
|
"$SCRIPT_DIR/flow-install" --quick "$(basename "$(pwd)")"
|
|
962
1129
|
echo ""
|
|
963
1130
|
fi
|
|
964
|
-
|
|
1131
|
+
|
|
1132
|
+
# Ask how user wants to set up the project
|
|
1133
|
+
echo -e "${BOLD}━━━ Setup Mode ━━━${NC}"
|
|
1134
|
+
echo ""
|
|
1135
|
+
ask_onboard_mode
|
|
1136
|
+
echo ""
|
|
1137
|
+
|
|
1138
|
+
# Handle import/learn modes
|
|
1139
|
+
case "$ONBOARD_MODE" in
|
|
1140
|
+
import)
|
|
1141
|
+
handle_import_profile || exit 1
|
|
1142
|
+
;;
|
|
1143
|
+
learn)
|
|
1144
|
+
handle_learn_from_project || exit 1
|
|
1145
|
+
;;
|
|
1146
|
+
esac
|
|
1147
|
+
|
|
965
1148
|
# Detection phase
|
|
966
1149
|
echo -e "${BOLD}━━━ Analyzing Project ━━━${NC}"
|
|
967
1150
|
echo ""
|
|
@@ -986,7 +1169,29 @@ main() {
|
|
|
986
1169
|
ask_current_state
|
|
987
1170
|
ask_known_issues
|
|
988
1171
|
ask_coding_preferences
|
|
989
|
-
|
|
1172
|
+
|
|
1173
|
+
# Pattern extraction - scan current project for patterns and conflicts
|
|
1174
|
+
# For fresh mode: detect internal conflicts in the codebase
|
|
1175
|
+
# For import/learn modes: detect conflicts between imported and local patterns
|
|
1176
|
+
echo ""
|
|
1177
|
+
echo -e "${BOLD}━━━ Pattern Analysis ━━━${NC}"
|
|
1178
|
+
echo ""
|
|
1179
|
+
|
|
1180
|
+
# Only run if there are enough files to analyze
|
|
1181
|
+
local file_count=$(find . -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" -o -name "*.py" \) \
|
|
1182
|
+
-not -path "*/node_modules/*" -not -path "*/.git/*" -not -path "*/dist/*" 2>/dev/null | wc -l | tr -d ' ')
|
|
1183
|
+
|
|
1184
|
+
if [ "$file_count" -gt 5 ]; then
|
|
1185
|
+
echo -e "${DIM}Found $file_count source files to analyze${NC}"
|
|
1186
|
+
run_pattern_extraction
|
|
1187
|
+
else
|
|
1188
|
+
echo -e "${DIM}Skipping pattern analysis (less than 5 source files)${NC}"
|
|
1189
|
+
fi
|
|
1190
|
+
|
|
1191
|
+
# Ask for additional documentation
|
|
1192
|
+
ask_additional_docs
|
|
1193
|
+
|
|
1194
|
+
echo ""
|
|
990
1195
|
echo -e "${BOLD}━━━ Generating Workflow Files ━━━${NC}"
|
|
991
1196
|
echo ""
|
|
992
1197
|
|
|
@@ -1031,13 +1236,21 @@ if [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then
|
|
|
1031
1236
|
echo ""
|
|
1032
1237
|
echo "Usage: flow onboard"
|
|
1033
1238
|
echo ""
|
|
1239
|
+
echo "Setup Options:"
|
|
1240
|
+
echo " 1. Fresh start - Analyze this project only"
|
|
1241
|
+
echo " 2. Import profile - Load patterns from exported .zip profile"
|
|
1242
|
+
echo " 3. Learn from project - Extract patterns from another folder"
|
|
1243
|
+
echo ""
|
|
1034
1244
|
echo "This will:"
|
|
1035
|
-
echo " 1.
|
|
1036
|
-
echo " 2.
|
|
1037
|
-
echo " 3.
|
|
1038
|
-
echo " 4.
|
|
1039
|
-
echo " 5.
|
|
1040
|
-
echo " 6.
|
|
1245
|
+
echo " 1. Ask how you want to set up (fresh/import/learn)"
|
|
1246
|
+
echo " 2. Detect your tech stack (language, framework, database)"
|
|
1247
|
+
echo " 3. Scan for components, pages, modules"
|
|
1248
|
+
echo " 4. Extract patterns and detect conflicts"
|
|
1249
|
+
echo " 5. Ask about your project (PRD, goals, issues)"
|
|
1250
|
+
echo " 6. Ask for additional documentation (PRD, roadmap)"
|
|
1251
|
+
echo " 7. Generate project.md, app-map.md, decisions.md"
|
|
1252
|
+
echo " 8. Create initial tasks from known issues"
|
|
1253
|
+
echo " 9. Suggest relevant skills to install"
|
|
1041
1254
|
exit 0
|
|
1042
1255
|
fi
|
|
1043
1256
|
|