dual-brain 0.1.22 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/dual-brain.mjs +676 -265
- package/package.json +16 -2
- package/src/awareness.mjs +343 -0
- package/src/calibration.mjs +148 -0
- package/src/cost-tracker.mjs +184 -0
- package/src/decide.mjs +162 -10
- package/src/dispatch.mjs +40 -2
- package/src/doctor.mjs +716 -1
- package/src/fx.mjs +276 -0
- package/src/intelligence.mjs +423 -0
- package/src/ledger.mjs +196 -0
- package/src/living-docs.mjs +210 -0
- package/src/models.mjs +363 -0
- package/src/pipeline.mjs +367 -8
- package/src/prompt-intel.mjs +325 -0
- package/src/think-engine.mjs +428 -0
package/src/pipeline.mjs
CHANGED
|
@@ -28,6 +28,12 @@ export function createPipelineRun(trigger = '', prompt = '') {
|
|
|
28
28
|
trigger,
|
|
29
29
|
prompt,
|
|
30
30
|
|
|
31
|
+
// Phase 0: Intelligence
|
|
32
|
+
projectBrief: null, // from deriveProjectState
|
|
33
|
+
taskBrief: null, // from deriveTaskContext
|
|
34
|
+
contradictions: [], // from detectContradictions
|
|
35
|
+
situationBrief: null, // formatted string from formatBrief
|
|
36
|
+
|
|
31
37
|
// Phase 1: Context
|
|
32
38
|
context: null,
|
|
33
39
|
failureHistory: null, // result of checkFailureHistory — even empty counts as "queried"
|
|
@@ -54,6 +60,22 @@ export function createPipelineRun(trigger = '', prompt = '') {
|
|
|
54
60
|
// Phase 5: Outcome
|
|
55
61
|
outcome: null,
|
|
56
62
|
|
|
63
|
+
// Ledger + calibration
|
|
64
|
+
taskId: null, // ledger task ID for this run
|
|
65
|
+
openTasks: [], // pending tasks from ledger
|
|
66
|
+
calibration: null, // user calibration state
|
|
67
|
+
adaptation: null, // behavior adaptation from calibration
|
|
68
|
+
|
|
69
|
+
// Prompt intelligence + environment
|
|
70
|
+
promptAnalysis: null, // from analyzePrompt
|
|
71
|
+
enrichedPrompt: null, // from enrichPrompt
|
|
72
|
+
environment: null, // from scanEnvironment
|
|
73
|
+
modelSuggestion: null, // from suggestModel
|
|
74
|
+
|
|
75
|
+
// Think-engine fields
|
|
76
|
+
thinkResult: null, // from think-engine
|
|
77
|
+
decisionPreflight: null, // from lookupDecision
|
|
78
|
+
|
|
57
79
|
completedAt: null,
|
|
58
80
|
};
|
|
59
81
|
}
|
|
@@ -367,7 +389,7 @@ export function buildExecutionPlan(contextPack, trigger, options = {}) {
|
|
|
367
389
|
effort: depthToEffort[reasoningDepth] ?? detection.effort,
|
|
368
390
|
};
|
|
369
391
|
|
|
370
|
-
const decision = decideRoute({ profile, detection: detectionWithDepth, cwd: contextPack.cwd });
|
|
392
|
+
const decision = decideRoute({ profile, detection: detectionWithDepth, cwd: contextPack.cwd, thinkResult: options.thinkResult });
|
|
371
393
|
|
|
372
394
|
// Resolve full model ID for display (mirrors dispatch.mjs CLAUDE_MODEL_IDS)
|
|
373
395
|
const CLAUDE_MODEL_IDS = { opus: 'claude-opus-4-6', sonnet: 'claude-sonnet-4-6', haiku: 'claude-haiku-4-5-20251001' };
|
|
@@ -633,15 +655,164 @@ export async function runPipeline(trigger, prompt, options = {}) {
|
|
|
633
655
|
const run = createPipelineRun(trigger, prompt);
|
|
634
656
|
|
|
635
657
|
try {
|
|
658
|
+
// ── Phase 0: Situational awareness ───────────────────────────────────────
|
|
659
|
+
|
|
660
|
+
try {
|
|
661
|
+
const { deriveProjectState, deriveTaskContext, detectContradictions, formatBrief } = await import('./intelligence.mjs');
|
|
662
|
+
run.projectBrief = await deriveProjectState(options.cwd || process.cwd());
|
|
663
|
+
run.taskBrief = deriveTaskContext(prompt, options.recentEvents || []);
|
|
664
|
+
run.situationBrief = formatBrief(run.projectBrief, run.taskBrief);
|
|
665
|
+
} catch (e) {
|
|
666
|
+
// intelligence module not available — continue without it (degraded)
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Doctor: discover capabilities (cached per process via discovery log)
|
|
670
|
+
try {
|
|
671
|
+
const { discover, verifyAll } = await import('./doctor.mjs');
|
|
672
|
+
const doctorCwd = options.cwd || process.cwd();
|
|
673
|
+
discover(doctorCwd); // writes to .dual-brain/discoveries.jsonl (idempotent)
|
|
674
|
+
verifyAll(doctorCwd); // writes to .dual-brain/verifications.jsonl
|
|
675
|
+
} catch (e) {
|
|
676
|
+
// doctor not available — non-blocking
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Ledger: check open tasks + create task for this run
|
|
680
|
+
try {
|
|
681
|
+
const { getOpenTasks, createTask, reconcile } = await import('./ledger.mjs');
|
|
682
|
+
const cwd = options.cwd || process.cwd();
|
|
683
|
+
|
|
684
|
+
// Check for stale tasks on session start
|
|
685
|
+
run.openTasks = getOpenTasks(cwd);
|
|
686
|
+
const staleTasks = reconcile(cwd);
|
|
687
|
+
|
|
688
|
+
// Create a ledger task for this pipeline run
|
|
689
|
+
const task = createTask({
|
|
690
|
+
intent: prompt,
|
|
691
|
+
owner: 'head',
|
|
692
|
+
priority: run.projectBrief?.recentFailures?.length > 0 ? 'high' : 'medium',
|
|
693
|
+
files: options.files || []
|
|
694
|
+
}, cwd);
|
|
695
|
+
run.taskId = task.id;
|
|
696
|
+
} catch (e) {
|
|
697
|
+
// ledger not available — continue degraded
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// Append open tasks to situation brief if any exist
|
|
701
|
+
if (run.openTasks.length > 0) {
|
|
702
|
+
const preview = run.openTasks.slice(0, 3).map(t => t.intent).join(', ');
|
|
703
|
+
const pendingLine = `PENDING TASKS: ${run.openTasks.length} open (${preview})`;
|
|
704
|
+
run.situationBrief = run.situationBrief
|
|
705
|
+
? `${run.situationBrief}\n${pendingLine}`
|
|
706
|
+
: pendingLine;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// Calibration: analyze user input and adapt
|
|
710
|
+
try {
|
|
711
|
+
const { analyzeInput, getAdaptation, detectCorrection, updateCalibration } = await import('./calibration.mjs');
|
|
712
|
+
const { getProjectState, updateProject } = await import('./living-docs.mjs');
|
|
713
|
+
const cwd = options.cwd || process.cwd();
|
|
714
|
+
|
|
715
|
+
const projectState = getProjectState(cwd);
|
|
716
|
+
const currentCal = projectState?.project?.userCalibration || { specificity: 3, corrections: 3, autonomy: 3, interactions: 0 };
|
|
717
|
+
const isCorrection = detectCorrection(prompt);
|
|
718
|
+
|
|
719
|
+
run.calibration = updateCalibration(currentCal, prompt, isCorrection);
|
|
720
|
+
run.adaptation = getAdaptation(run.calibration);
|
|
721
|
+
|
|
722
|
+
// Persist updated calibration
|
|
723
|
+
updateProject({ userCalibration: run.calibration }, cwd);
|
|
724
|
+
} catch (e) {
|
|
725
|
+
// calibration not available — continue degraded
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// Environment awareness
|
|
729
|
+
try {
|
|
730
|
+
const { scanEnvironment, getCapabilitySummary } = await import('./awareness.mjs');
|
|
731
|
+
run.environment = scanEnvironment(cwd);
|
|
732
|
+
|
|
733
|
+
// Add capabilities to situation brief
|
|
734
|
+
if (run.situationBrief && run.environment) {
|
|
735
|
+
const caps = getCapabilitySummary(run.environment);
|
|
736
|
+
if (caps.length > 0) {
|
|
737
|
+
run.situationBrief += '\nCAPABILITIES: ' + caps.join(', ');
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
} catch (e) {
|
|
741
|
+
// awareness not available
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// Knowledge preflight — check if we already know the answer
|
|
745
|
+
try {
|
|
746
|
+
const { lookupDecision, triageQuestion } = await import('./think-engine.mjs');
|
|
747
|
+
const cwd = options.cwd || process.cwd();
|
|
748
|
+
|
|
749
|
+
run.decisionPreflight = lookupDecision(prompt, options.tags || [], cwd);
|
|
750
|
+
|
|
751
|
+
// If exact reuse found, we can short-circuit
|
|
752
|
+
if (run.decisionPreflight.recommendation === 'reuse' && run.decisionPreflight.candidates[0]) {
|
|
753
|
+
// Add cached decision info to situation brief
|
|
754
|
+
if (run.situationBrief) {
|
|
755
|
+
run.situationBrief += '\nCACHED DECISION: Found prior decision with ' +
|
|
756
|
+
Math.round(run.decisionPreflight.candidates[0].relevance * 100) + '% relevance';
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// Triage to determine thinking tier
|
|
761
|
+
const triage = triageQuestion(prompt, run.projectBrief, run.decisionPreflight);
|
|
762
|
+
run.thinkResult = { tier: triage.recommendedTier, estimatedTokens: triage.estimatedTokens, triage };
|
|
763
|
+
|
|
764
|
+
// Add to situation brief
|
|
765
|
+
if (run.situationBrief) {
|
|
766
|
+
run.situationBrief += '\nTHINK TIER: ' + triage.recommendedTier + ' (' + triage.estimatedTokens + ' tokens est.)';
|
|
767
|
+
}
|
|
768
|
+
} catch (e) {
|
|
769
|
+
// think-engine not available
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// Prompt intelligence
|
|
773
|
+
try {
|
|
774
|
+
const { analyzePrompt, enrichPrompt, shouldBlock, getBlockReason } = await import('./prompt-intel.mjs');
|
|
775
|
+
|
|
776
|
+
run.promptAnalysis = analyzePrompt(prompt, run.projectBrief, run.calibration);
|
|
777
|
+
|
|
778
|
+
// Hard block on dangerous intent
|
|
779
|
+
if (shouldBlock(run.promptAnalysis)) {
|
|
780
|
+
const reason = getBlockReason(run.promptAnalysis);
|
|
781
|
+
if (run.taskId) {
|
|
782
|
+
try {
|
|
783
|
+
const { failTask } = await import('./ledger.mjs');
|
|
784
|
+
failTask(run.taskId, 'Blocked by risk detection: ' + reason, cwd);
|
|
785
|
+
} catch (e) {}
|
|
786
|
+
}
|
|
787
|
+
run.completedAt = Date.now();
|
|
788
|
+
return {
|
|
789
|
+
success: false,
|
|
790
|
+
gateFailure: 'risk',
|
|
791
|
+
reason: 'Prompt blocked: ' + reason,
|
|
792
|
+
promptAnalysis: run.promptAnalysis,
|
|
793
|
+
run
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// Enrich prompt if intervention says so
|
|
798
|
+
if (run.promptAnalysis.intervention === 'silent_enrich' || run.promptAnalysis.intervention === 'confirm_rewrite') {
|
|
799
|
+
run.enrichedPrompt = enrichPrompt(prompt, run.projectBrief, run.promptAnalysis);
|
|
800
|
+
}
|
|
801
|
+
} catch (e) {
|
|
802
|
+
// prompt-intel not available
|
|
803
|
+
}
|
|
804
|
+
|
|
636
805
|
// ── Phase 1: Context ──────────────────────────────────────────────────────
|
|
637
806
|
|
|
807
|
+
const effectivePrompt = run.enrichedPrompt || prompt;
|
|
808
|
+
|
|
638
809
|
// Build context pack
|
|
639
|
-
run.context = await buildContextPack(
|
|
810
|
+
run.context = await buildContextPack(effectivePrompt, files, cwd);
|
|
640
811
|
|
|
641
812
|
// Query failure history (must happen before context gate)
|
|
642
813
|
try {
|
|
643
814
|
const { checkFailureHistory } = await import('./failure-memory.mjs');
|
|
644
|
-
run.failureHistory = await checkFailureHistory(
|
|
815
|
+
run.failureHistory = await checkFailureHistory(effectivePrompt, files, cwd);
|
|
645
816
|
} catch {
|
|
646
817
|
// failure-memory.mjs unavailable — set to empty result so gate still passes
|
|
647
818
|
run.failureHistory = { hasPriorFailures: false, failureCount: 0, lastFailure: null, escalation: { recommended: false } };
|
|
@@ -650,7 +821,7 @@ export async function runPipeline(trigger, prompt, options = {}) {
|
|
|
650
821
|
// Query relevant outcomes (must happen before context gate)
|
|
651
822
|
try {
|
|
652
823
|
const { getRelevantOutcomes } = await import('./outcome.mjs');
|
|
653
|
-
run.priorOutcomes = await getRelevantOutcomes(
|
|
824
|
+
run.priorOutcomes = await getRelevantOutcomes(effectivePrompt, files, cwd);
|
|
654
825
|
} catch {
|
|
655
826
|
// outcome.mjs unavailable — set to empty array so gate still passes
|
|
656
827
|
run.priorOutcomes = [];
|
|
@@ -664,12 +835,62 @@ export async function runPipeline(trigger, prompt, options = {}) {
|
|
|
664
835
|
|
|
665
836
|
// ── Phase 2: Plan ─────────────────────────────────────────────────────────
|
|
666
837
|
|
|
667
|
-
run.plan = buildExecutionPlan(run.context, trigger, { forceDepth, forceChallenger });
|
|
838
|
+
run.plan = buildExecutionPlan(run.context, trigger, { forceDepth, forceChallenger, thinkResult: run.thinkResult });
|
|
839
|
+
|
|
840
|
+
// Model intelligence
|
|
841
|
+
try {
|
|
842
|
+
const { suggestModel, getRegistryAge } = await import('./models.mjs');
|
|
843
|
+
const availableProviders = [];
|
|
844
|
+
if (run.environment?.secrets?.ANTHROPIC_API_KEY || run.environment?.claudeCode?.isInsideClaude) availableProviders.push('anthropic');
|
|
845
|
+
if (run.environment?.secrets?.OPENAI_API_KEY) availableProviders.push('openai');
|
|
846
|
+
|
|
847
|
+
const intent = run.promptAnalysis?.intent?.type || 'execute';
|
|
848
|
+
const risk = run.plan?.risk || 'medium';
|
|
849
|
+
const complexity = run.plan?.complexity || 'medium';
|
|
850
|
+
|
|
851
|
+
run.modelSuggestion = suggestModel(intent, risk, complexity, availableProviders);
|
|
852
|
+
|
|
853
|
+
// Warn if model registry is stale
|
|
854
|
+
const age = getRegistryAge();
|
|
855
|
+
if (age > 30 && run.situationBrief) {
|
|
856
|
+
run.situationBrief += '\nWARNING: Model registry is ' + age + ' days old';
|
|
857
|
+
}
|
|
858
|
+
} catch (e) {
|
|
859
|
+
// models not available
|
|
860
|
+
}
|
|
668
861
|
|
|
669
862
|
if (verbose || dryRun) {
|
|
670
863
|
log(formatExecutionPlan(run.plan));
|
|
671
864
|
}
|
|
672
865
|
|
|
866
|
+
// Contradiction detection
|
|
867
|
+
if (run.projectBrief && run.plan) {
|
|
868
|
+
try {
|
|
869
|
+
const { detectContradictions } = await import('./intelligence.mjs');
|
|
870
|
+
const planForCheck = {
|
|
871
|
+
description: run.plan.description || prompt,
|
|
872
|
+
targetFiles: run.plan.targetFiles || run.plan.files || [],
|
|
873
|
+
assumptions: run.plan.assumptions || {}
|
|
874
|
+
};
|
|
875
|
+
run.contradictions = detectContradictions(run.projectBrief, run.taskBrief, planForCheck);
|
|
876
|
+
|
|
877
|
+
// Any blocking contradiction fails the pipeline
|
|
878
|
+
const blockers = run.contradictions.filter(c => c.severity === 'block');
|
|
879
|
+
if (blockers.length > 0) {
|
|
880
|
+
run.completedAt = Date.now();
|
|
881
|
+
return {
|
|
882
|
+
success: false,
|
|
883
|
+
gateFailure: 'contradiction',
|
|
884
|
+
reason: blockers.map(b => b.message).join('; '),
|
|
885
|
+
contradictions: blockers,
|
|
886
|
+
run
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
} catch (e) {
|
|
890
|
+
// contradiction detection failed — continue (degraded)
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
673
894
|
// Gate 2: Planning gate
|
|
674
895
|
if (!runGate(run, 'planning', planningGate)) {
|
|
675
896
|
run.completedAt = Date.now();
|
|
@@ -684,8 +905,21 @@ export async function runPipeline(trigger, prompt, options = {}) {
|
|
|
684
905
|
|
|
685
906
|
if (dryRun) {
|
|
686
907
|
run.completedAt = Date.now();
|
|
687
|
-
// Return legacy-compatible shape for dry-run callers
|
|
688
|
-
return {
|
|
908
|
+
// Return legacy-compatible shape plus intelligence fields for dry-run callers
|
|
909
|
+
return {
|
|
910
|
+
plan: run.plan,
|
|
911
|
+
result: null,
|
|
912
|
+
verification: null,
|
|
913
|
+
run,
|
|
914
|
+
// Intelligence fields (mirrors full execution return)
|
|
915
|
+
projectBrief: run.projectBrief,
|
|
916
|
+
contradictions: run.contradictions,
|
|
917
|
+
promptAnalysis: run.promptAnalysis,
|
|
918
|
+
environment: run.environment,
|
|
919
|
+
modelSuggestion: run.modelSuggestion,
|
|
920
|
+
thinkResult: run.thinkResult,
|
|
921
|
+
decisionPreflight: run.decisionPreflight,
|
|
922
|
+
};
|
|
689
923
|
}
|
|
690
924
|
|
|
691
925
|
// Gate 4: Execution gate (cleared to work?)
|
|
@@ -705,14 +939,58 @@ export async function runPipeline(trigger, prompt, options = {}) {
|
|
|
705
939
|
|
|
706
940
|
run.result = await dispatch({
|
|
707
941
|
decision,
|
|
708
|
-
prompt,
|
|
942
|
+
prompt: effectivePrompt,
|
|
709
943
|
files,
|
|
710
944
|
cwd,
|
|
711
945
|
dryRun: false,
|
|
712
946
|
verbose,
|
|
713
947
|
profile: run.context.profile,
|
|
948
|
+
situationBrief: run.situationBrief,
|
|
949
|
+
adaptation: run.adaptation,
|
|
950
|
+
modelSuggestion: run.modelSuggestion,
|
|
714
951
|
});
|
|
715
952
|
|
|
953
|
+
// Update ledger task with result
|
|
954
|
+
if (run.taskId) {
|
|
955
|
+
const { updateTask, failTask } = await import('./ledger.mjs');
|
|
956
|
+
const ledgerCwd = options.cwd || process.cwd();
|
|
957
|
+
|
|
958
|
+
if (run.result && !run.result.error) {
|
|
959
|
+
// updateTask throws if proof/result is missing — let that propagate so
|
|
960
|
+
// the outcome gate catches it rather than silently succeeding.
|
|
961
|
+
updateTask(run.taskId, {
|
|
962
|
+
status: 'done',
|
|
963
|
+
result: typeof run.result === 'string' ? run.result : JSON.stringify(run.result).slice(0, 500),
|
|
964
|
+
proof: run.verification ? 'Pipeline verification passed' : 'Execution completed',
|
|
965
|
+
files: run.result.filesChanged || run.plan?.targetFiles || []
|
|
966
|
+
}, ledgerCwd);
|
|
967
|
+
} else {
|
|
968
|
+
try {
|
|
969
|
+
failTask(run.taskId, run.result?.error || 'Pipeline execution failed', ledgerCwd);
|
|
970
|
+
} catch (e) {
|
|
971
|
+
// failTask failure is non-blocking
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// Record action in living docs
|
|
977
|
+
try {
|
|
978
|
+
const { appendAction } = await import('./living-docs.mjs');
|
|
979
|
+
const cwd = options.cwd || process.cwd();
|
|
980
|
+
|
|
981
|
+
appendAction({
|
|
982
|
+
type: trigger || 'task',
|
|
983
|
+
intent: prompt,
|
|
984
|
+
status: (run.result && !run.result.error) ? 'done' : 'failed',
|
|
985
|
+
owner: 'head',
|
|
986
|
+
files: run.result?.filesChanged || run.plan?.targetFiles || [],
|
|
987
|
+
proof: run.verification ? JSON.stringify(run.verification).slice(0, 200) : null,
|
|
988
|
+
result: typeof run.result === 'string' ? run.result.slice(0, 300) : null
|
|
989
|
+
}, cwd);
|
|
990
|
+
} catch (e) {
|
|
991
|
+
// living docs not available — non-blocking
|
|
992
|
+
}
|
|
993
|
+
|
|
716
994
|
// ── Phase 4: Verification ─────────────────────────────────────────────────
|
|
717
995
|
|
|
718
996
|
run.verification = await verify(run.result, run.plan, cwd);
|
|
@@ -726,6 +1004,62 @@ export async function runPipeline(trigger, prompt, options = {}) {
|
|
|
726
1004
|
_incrementFailureCache(prompt);
|
|
727
1005
|
}
|
|
728
1006
|
|
|
1007
|
+
// Track cost after verification (fail-silent — advisory only)
|
|
1008
|
+
try {
|
|
1009
|
+
const { trackCost } = await import('./cost-tracker.mjs');
|
|
1010
|
+
const tokensEstimated =
|
|
1011
|
+
(run.result?.usage?.inputTokens ?? run.result?.tokensUsed?.input ?? 0) +
|
|
1012
|
+
(run.result?.usage?.outputTokens ?? run.result?.tokensUsed?.output ?? 0);
|
|
1013
|
+
trackCost({
|
|
1014
|
+
action: trigger || 'execute',
|
|
1015
|
+
model: run.result?.model ?? run.plan?._decision?.model ?? 'default',
|
|
1016
|
+
tier: run.plan?.tier ?? 'standard',
|
|
1017
|
+
tokensEstimated,
|
|
1018
|
+
wasCacheHit: false,
|
|
1019
|
+
tokensSaved: 0,
|
|
1020
|
+
}, cwd);
|
|
1021
|
+
} catch (e) {
|
|
1022
|
+
// cost-tracker not available — non-blocking
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
// Living docs: update state after significant execution (fail-silent — advisory only)
|
|
1026
|
+
try {
|
|
1027
|
+
const { updateState } = await import('./living-docs.mjs');
|
|
1028
|
+
const docsCwd = options.cwd || process.cwd();
|
|
1029
|
+
const successFlag = run.result && !run.result.error && run.verification.ok;
|
|
1030
|
+
const stateEntry =
|
|
1031
|
+
`# Current State\n\nLast run: ${new Date().toISOString()}\n` +
|
|
1032
|
+
`Task: ${prompt.slice(0, 120)}\n` +
|
|
1033
|
+
`Status: ${successFlag ? 'completed' : 'failed'}\n` +
|
|
1034
|
+
`Tier: ${run.plan?.tier ?? 'unknown'}\n` +
|
|
1035
|
+
`Model: ${run.plan?.primaryModel ?? 'unknown'}\n`;
|
|
1036
|
+
updateState(stateEntry, docsCwd);
|
|
1037
|
+
} catch (e) {
|
|
1038
|
+
// living-docs not available — non-blocking
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
// Doctor: record learning from this execution outcome (fail-silent)
|
|
1042
|
+
try {
|
|
1043
|
+
const { recordLearning } = await import('./doctor.mjs');
|
|
1044
|
+
const doctorCwd = options.cwd || process.cwd();
|
|
1045
|
+
const successFlag = run.result && !run.result.error && run.verification.ok;
|
|
1046
|
+
recordLearning({
|
|
1047
|
+
taskType: run.context?.detection?.intent ?? 'unknown',
|
|
1048
|
+
prompt,
|
|
1049
|
+
model: run.result?.model ?? run.plan?._decision?.model ?? '',
|
|
1050
|
+
provider: run.result?.provider ?? run.plan?.primaryProvider ?? '',
|
|
1051
|
+
tier: run.plan?.tier ?? '',
|
|
1052
|
+
reasoningDepth: run.plan?.reasoningDepth ?? 'low',
|
|
1053
|
+
wasEnriched: !!run.enrichedPrompt,
|
|
1054
|
+
wasDualBrain: !!(run.plan?.useChallenger && run.plan?.challengerModel),
|
|
1055
|
+
success: successFlag,
|
|
1056
|
+
duration: run.completedAt ? (Date.now() - run.startedAt) : 0,
|
|
1057
|
+
filesChanged: (run.result?.filesChanged ?? []).length,
|
|
1058
|
+
}, doctorCwd);
|
|
1059
|
+
} catch (e) {
|
|
1060
|
+
// doctor not available — non-blocking
|
|
1061
|
+
}
|
|
1062
|
+
|
|
729
1063
|
// ── Phase 5: Outcome ──────────────────────────────────────────────────────
|
|
730
1064
|
|
|
731
1065
|
await recordOutcomeSafe(run);
|
|
@@ -736,6 +1070,23 @@ export async function runPipeline(trigger, prompt, options = {}) {
|
|
|
736
1070
|
return { success: false, gateFailure: 'outcome', reason: run.gates.outcome.reason, run };
|
|
737
1071
|
}
|
|
738
1072
|
|
|
1073
|
+
// Persist decision for future recall
|
|
1074
|
+
if (run.result && !run.result?.error) {
|
|
1075
|
+
try {
|
|
1076
|
+
const { persistDecision } = await import('./think-engine.mjs');
|
|
1077
|
+
const cwd = options.cwd || process.cwd();
|
|
1078
|
+
persistDecision(
|
|
1079
|
+
prompt,
|
|
1080
|
+
typeof run.result === 'string' ? run.result : JSON.stringify(run.result).slice(0, 1000),
|
|
1081
|
+
run.thinkResult?.tier || 'standard',
|
|
1082
|
+
{ tags: options.tags || [], projectBrief: run.projectBrief },
|
|
1083
|
+
cwd
|
|
1084
|
+
);
|
|
1085
|
+
} catch (e) {
|
|
1086
|
+
// persist failed — non-blocking
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
739
1090
|
} catch (err) {
|
|
740
1091
|
log(`[pipeline] error in pipeline step: ${err.message}`);
|
|
741
1092
|
run.result = { status: 'error', error: err.message };
|
|
@@ -751,6 +1102,14 @@ export async function runPipeline(trigger, prompt, options = {}) {
|
|
|
751
1102
|
return {
|
|
752
1103
|
success: true,
|
|
753
1104
|
run,
|
|
1105
|
+
// Intelligence fields for callers to inspect
|
|
1106
|
+
projectBrief: run.projectBrief,
|
|
1107
|
+
contradictions: run.contradictions,
|
|
1108
|
+
promptAnalysis: run.promptAnalysis,
|
|
1109
|
+
environment: run.environment,
|
|
1110
|
+
modelSuggestion: run.modelSuggestion,
|
|
1111
|
+
thinkResult: run.thinkResult,
|
|
1112
|
+
decisionPreflight: run.decisionPreflight,
|
|
754
1113
|
// Legacy compatibility
|
|
755
1114
|
plan: run.plan,
|
|
756
1115
|
result: run.result,
|