oxe-cc 1.10.0 → 1.11.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/CHANGELOG.md +39 -0
- package/README.md +32 -32
- package/bin/lib/oxe-operational.cjs +413 -236
- package/bin/lib/oxe-project-health.cjs +2072 -2054
- package/bin/oxe-cc.js +133 -84
- package/lib/sdk/index.cjs +3 -3
- package/package.json +3 -1
- package/packages/runtime/package.json +1 -1
- package/vscode-extension/package.json +1 -1
|
@@ -86,6 +86,19 @@ function createExecutionContext(projectRoot, activeSession, options = {}) {
|
|
|
86
86
|
const gateManager = (runtime && typeof runtime.GateManager === 'function')
|
|
87
87
|
? new runtime.GateManager(projectRoot, activeSession || null, runId)
|
|
88
88
|
: null;
|
|
89
|
+
// Auto-wire PolicyEngine from .oxe/config.json runtime.policy when not explicitly injected
|
|
90
|
+
let policyEngine = options.policyEngine || null;
|
|
91
|
+
if (!policyEngine && runtime && typeof runtime.PolicyEngine === 'function') {
|
|
92
|
+
try {
|
|
93
|
+
const cfgPath = path.join(projectRoot, '.oxe', 'config.json');
|
|
94
|
+
if (fs.existsSync(cfgPath)) {
|
|
95
|
+
const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
|
|
96
|
+
if (cfg.runtime && cfg.runtime.policy && typeof cfg.runtime.policy === 'object') {
|
|
97
|
+
policyEngine = runtime.PolicyEngine.fromConfig(cfg.runtime.policy);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
} catch {}
|
|
101
|
+
}
|
|
89
102
|
return {
|
|
90
103
|
projectRoot,
|
|
91
104
|
sessionId: activeSession || null,
|
|
@@ -93,7 +106,7 @@ function createExecutionContext(projectRoot, activeSession, options = {}) {
|
|
|
93
106
|
executor: options.executor || null,
|
|
94
107
|
workspaceManager: options.workspaceManager || null,
|
|
95
108
|
gateManager,
|
|
96
|
-
policyEngine
|
|
109
|
+
policyEngine,
|
|
97
110
|
policyActor: options.policyActor || 'runtime',
|
|
98
111
|
quota: options.quota || null,
|
|
99
112
|
pluginRegistry: options.pluginRegistry || null,
|
|
@@ -104,6 +117,44 @@ function createExecutionContext(projectRoot, activeSession, options = {}) {
|
|
|
104
117
|
};
|
|
105
118
|
}
|
|
106
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Persists LLM provider config to .oxe/config.json under runtime.provider.
|
|
122
|
+
* @param {string} projectRoot
|
|
123
|
+
* @param {{ baseUrl?: string, model?: string, apiKeyEnv?: string, maxTurns?: number }} providerConfig
|
|
124
|
+
*/
|
|
125
|
+
function saveRuntimeProviderConfig(projectRoot, providerConfig) {
|
|
126
|
+
const configPath = path.join(projectRoot, '.oxe', 'config.json');
|
|
127
|
+
let cfg = {};
|
|
128
|
+
try { cfg = JSON.parse(fs.readFileSync(configPath, 'utf8')); } catch {}
|
|
129
|
+
if (!cfg.runtime || typeof cfg.runtime !== 'object') cfg.runtime = {};
|
|
130
|
+
cfg.runtime.provider = {
|
|
131
|
+
baseUrl: providerConfig.baseUrl || 'https://api.anthropic.com/v1',
|
|
132
|
+
model: providerConfig.model || 'claude-sonnet-4-6',
|
|
133
|
+
};
|
|
134
|
+
if (providerConfig.apiKeyEnv) cfg.runtime.provider.apiKeyEnv = providerConfig.apiKeyEnv;
|
|
135
|
+
if (providerConfig.maxTurns != null) cfg.runtime.provider.maxTurns = providerConfig.maxTurns;
|
|
136
|
+
const oxeDir = path.join(projectRoot, '.oxe');
|
|
137
|
+
if (!fs.existsSync(oxeDir)) fs.mkdirSync(oxeDir, { recursive: true });
|
|
138
|
+
fs.writeFileSync(configPath, JSON.stringify(cfg, null, 2) + '\n', 'utf8');
|
|
139
|
+
return cfg.runtime.provider;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Reads persisted LLM provider config from .oxe/config.json runtime.provider.
|
|
144
|
+
* @param {string} projectRoot
|
|
145
|
+
* @returns {{ baseUrl?: string, model?: string, apiKeyEnv?: string, maxTurns?: number } | null}
|
|
146
|
+
*/
|
|
147
|
+
function loadRuntimeProviderConfig(projectRoot) {
|
|
148
|
+
try {
|
|
149
|
+
const cfg = JSON.parse(fs.readFileSync(path.join(projectRoot, '.oxe', 'config.json'), 'utf8'));
|
|
150
|
+
return (cfg.runtime && cfg.runtime.provider && typeof cfg.runtime.provider === 'object')
|
|
151
|
+
? cfg.runtime.provider
|
|
152
|
+
: null;
|
|
153
|
+
} catch {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
107
158
|
function buildRuntimePluginRegistry(projectRoot) {
|
|
108
159
|
const runtime = loadRuntimeModule();
|
|
109
160
|
if (!runtime || typeof runtime.PluginRegistry !== 'function') return null;
|
|
@@ -535,6 +586,37 @@ function compileExecutionGraphFromArtifacts(projectRoot, activeSession, options
|
|
|
535
586
|
if (lintHints.length) validationErrors.push(...lintHints);
|
|
536
587
|
|
|
537
588
|
const compiledGraph = runtime.toSerializable(graph);
|
|
589
|
+
|
|
590
|
+
// Gap B: enrich node verify.command from SPEC criteria howToVerify when linked via acceptance_refs.
|
|
591
|
+
// Extracts commands from backtick patterns (e.g. "Run `npm test`") and appends to existing command
|
|
592
|
+
// or sets as command when none exists. Only runs when spec has criteria.
|
|
593
|
+
if (parsedSpec.criteria && parsedSpec.criteria.length > 0 && compiledGraph.nodes) {
|
|
594
|
+
const criteriaMap = new Map(parsedSpec.criteria.map(c => [c.id, c]));
|
|
595
|
+
const nodeList = Array.isArray(compiledGraph.nodes)
|
|
596
|
+
? compiledGraph.nodes
|
|
597
|
+
: Object.values(compiledGraph.nodes);
|
|
598
|
+
for (const node of nodeList) {
|
|
599
|
+
if (!node || !node.verify) continue;
|
|
600
|
+
const refs = Array.isArray(node.verify.acceptance_refs) ? node.verify.acceptance_refs : [];
|
|
601
|
+
const criteriaCommands = [];
|
|
602
|
+
for (const ref of refs) {
|
|
603
|
+
const criterion = criteriaMap.get(ref);
|
|
604
|
+
if (criterion && criterion.howToVerify) {
|
|
605
|
+
const cmdMatch = criterion.howToVerify.match(/`([^`]+)`/);
|
|
606
|
+
if (cmdMatch) criteriaCommands.push(cmdMatch[1].trim());
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
if (criteriaCommands.length === 0) continue;
|
|
610
|
+
const unique = [...new Set(criteriaCommands)];
|
|
611
|
+
if (node.verify.command) {
|
|
612
|
+
const extra = unique.filter(c => !node.verify.command.includes(c));
|
|
613
|
+
if (extra.length > 0) node.verify.command += ' && ' + extra.join(' && ');
|
|
614
|
+
} else {
|
|
615
|
+
node.verify.command = unique.join(' && ');
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
538
620
|
const current = options.runState || readRunState(projectRoot, activeSession) || {};
|
|
539
621
|
const runId = current.run_id || makeRunId();
|
|
540
622
|
const next = writeRunState(projectRoot, activeSession, {
|
|
@@ -722,52 +804,52 @@ async function resolveRuntimeGate(projectRoot, activeSession, options = {}) {
|
|
|
722
804
|
};
|
|
723
805
|
}
|
|
724
806
|
|
|
725
|
-
// Gap 5: route execution to MultiAgentCoordinator when plan-agents.json exists
|
|
726
|
-
function buildRuntimeExecutePreflight(projectRoot, activeSession, runState) {
|
|
727
|
-
const health = loadProjectHealth();
|
|
728
|
-
const blockers = [];
|
|
729
|
-
const warnings = [];
|
|
730
|
-
let report = null;
|
|
731
|
-
if (health && typeof health.buildHealthReport === 'function') {
|
|
732
|
-
report = health.buildHealthReport(projectRoot);
|
|
733
|
-
if (report.planSelfEvaluation && report.planSelfEvaluation.executable === false) {
|
|
734
|
-
const confidence = report.planSelfEvaluation.confidence;
|
|
735
|
-
const threshold = report.planConfidenceThreshold || 90;
|
|
736
|
-
blockers.push(`plan_confidence:${confidence == null ? 'missing' : `${confidence}%`}<=${threshold}%`);
|
|
737
|
-
}
|
|
738
|
-
if (report.executionRationality && report.executionRationality.applicable && !report.executionRationalityReady) {
|
|
739
|
-
const gaps = Array.isArray(report.criticalExecutionGaps) ? report.criticalExecutionGaps : [];
|
|
740
|
-
blockers.push(`execution_rationality:${gaps[0] || 'not_ready'}`);
|
|
741
|
-
}
|
|
742
|
-
if (report.fallbackMode && report.fallbackMode !== 'none') {
|
|
743
|
-
warnings.push(`fallback_mode:${report.fallbackMode}`);
|
|
744
|
-
}
|
|
745
|
-
} else {
|
|
746
|
-
warnings.push('health_report_unavailable');
|
|
747
|
-
}
|
|
748
|
-
const runId = runState && runState.run_id ? runState.run_id : null;
|
|
749
|
-
const queue = readRuntimeGates(projectRoot, activeSession, { runId });
|
|
750
|
-
if (queue.pending.length > 0) {
|
|
751
|
-
blockers.push(`pending_gates:${queue.pending.length}`);
|
|
752
|
-
}
|
|
753
|
-
return {
|
|
754
|
-
ok: blockers.length === 0,
|
|
755
|
-
blockers,
|
|
756
|
-
warnings,
|
|
757
|
-
runId,
|
|
758
|
-
gateQueue: {
|
|
759
|
-
pending: queue.pending.length,
|
|
760
|
-
stale: queue.staleCount || 0,
|
|
761
|
-
},
|
|
762
|
-
confidence: report && report.planSelfEvaluation ? report.planSelfEvaluation.confidence : null,
|
|
763
|
-
confidenceThreshold: report ? report.planConfidenceThreshold || 90 : 90,
|
|
764
|
-
executionRationalityReady: report ? Boolean(report.executionRationalityReady) : false,
|
|
765
|
-
};
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
async function runRuntimeExecute(projectRoot, activeSession, options = {}) {
|
|
769
|
-
const runtime = loadRuntimeModule();
|
|
770
|
-
if (!runtime) throw new Error('Runtime package não está disponível. Rode npm run build:runtime.');
|
|
807
|
+
// Gap 5: route execution to MultiAgentCoordinator when plan-agents.json exists
|
|
808
|
+
function buildRuntimeExecutePreflight(projectRoot, activeSession, runState) {
|
|
809
|
+
const health = loadProjectHealth();
|
|
810
|
+
const blockers = [];
|
|
811
|
+
const warnings = [];
|
|
812
|
+
let report = null;
|
|
813
|
+
if (health && typeof health.buildHealthReport === 'function') {
|
|
814
|
+
report = health.buildHealthReport(projectRoot);
|
|
815
|
+
if (report.planSelfEvaluation && report.planSelfEvaluation.executable === false) {
|
|
816
|
+
const confidence = report.planSelfEvaluation.confidence;
|
|
817
|
+
const threshold = report.planConfidenceThreshold || 90;
|
|
818
|
+
blockers.push(`plan_confidence:${confidence == null ? 'missing' : `${confidence}%`}<=${threshold}%`);
|
|
819
|
+
}
|
|
820
|
+
if (report.executionRationality && report.executionRationality.applicable && !report.executionRationalityReady) {
|
|
821
|
+
const gaps = Array.isArray(report.criticalExecutionGaps) ? report.criticalExecutionGaps : [];
|
|
822
|
+
blockers.push(`execution_rationality:${gaps[0] || 'not_ready'}`);
|
|
823
|
+
}
|
|
824
|
+
if (report.fallbackMode && report.fallbackMode !== 'none') {
|
|
825
|
+
warnings.push(`fallback_mode:${report.fallbackMode}`);
|
|
826
|
+
}
|
|
827
|
+
} else {
|
|
828
|
+
warnings.push('health_report_unavailable');
|
|
829
|
+
}
|
|
830
|
+
const runId = runState && runState.run_id ? runState.run_id : null;
|
|
831
|
+
const queue = readRuntimeGates(projectRoot, activeSession, { runId });
|
|
832
|
+
if (queue.pending.length > 0) {
|
|
833
|
+
blockers.push(`pending_gates:${queue.pending.length}`);
|
|
834
|
+
}
|
|
835
|
+
return {
|
|
836
|
+
ok: blockers.length === 0,
|
|
837
|
+
blockers,
|
|
838
|
+
warnings,
|
|
839
|
+
runId,
|
|
840
|
+
gateQueue: {
|
|
841
|
+
pending: queue.pending.length,
|
|
842
|
+
stale: queue.staleCount || 0,
|
|
843
|
+
},
|
|
844
|
+
confidence: report && report.planSelfEvaluation ? report.planSelfEvaluation.confidence : null,
|
|
845
|
+
confidenceThreshold: report ? report.planConfidenceThreshold || 90 : 90,
|
|
846
|
+
executionRationalityReady: report ? Boolean(report.executionRationalityReady) : false,
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
async function runRuntimeExecute(projectRoot, activeSession, options = {}) {
|
|
851
|
+
const runtime = loadRuntimeModule();
|
|
852
|
+
if (!runtime) throw new Error('Runtime package não está disponível. Rode npm run build:runtime.');
|
|
771
853
|
const parsers = loadSdkParsers();
|
|
772
854
|
if (!parsers) throw new Error('SDK parsers não disponíveis.');
|
|
773
855
|
|
|
@@ -787,50 +869,52 @@ function buildRuntimeExecutePreflight(projectRoot, activeSession, runState) {
|
|
|
787
869
|
|
|
788
870
|
// Resolve compiled graph from run state or compile on demand
|
|
789
871
|
let current = options.runState || readRunState(projectRoot, activeSession);
|
|
790
|
-
if (!current || !current.compiled_graph) {
|
|
791
|
-
current = compileExecutionGraphFromArtifacts(projectRoot, activeSession, { runState: current }).run;
|
|
792
|
-
}
|
|
793
|
-
if (!current || !current.compiled_graph) {
|
|
794
|
-
throw new Error('Nenhum grafo compilado encontrado. Execute oxe-cc runtime compile primeiro.');
|
|
795
|
-
}
|
|
796
|
-
const preflight = buildRuntimeExecutePreflight(projectRoot, activeSession, current);
|
|
797
|
-
if (!preflight.ok && !options.skipPreflight) {
|
|
798
|
-
const reason = preflight.blockers[0] || 'runtime_execute_preflight_failed';
|
|
799
|
-
appendEvent(projectRoot, activeSession, {
|
|
800
|
-
type: 'WorkItemBlocked',
|
|
801
|
-
run_id: current.run_id,
|
|
802
|
-
payload: {
|
|
803
|
-
reason: 'runtime_execute_preflight_failed',
|
|
804
|
-
blockers: preflight.blockers,
|
|
805
|
-
},
|
|
806
|
-
});
|
|
807
|
-
return {
|
|
808
|
-
mode: 'preflight',
|
|
809
|
-
agentPlan: null,
|
|
810
|
-
result: {
|
|
811
|
-
run_id: current.run_id,
|
|
812
|
-
status: 'blocked',
|
|
813
|
-
completed: [],
|
|
814
|
-
failed: [],
|
|
815
|
-
blocked: ['runtime_execute_preflight'],
|
|
816
|
-
reason,
|
|
817
|
-
},
|
|
818
|
-
run: current,
|
|
819
|
-
preflight,
|
|
820
|
-
};
|
|
821
|
-
}
|
|
822
|
-
const graph = runtime.fromSerializable
|
|
823
|
-
? runtime.fromSerializable(current.compiled_graph)
|
|
824
|
-
: current.compiled_graph;
|
|
825
|
-
|
|
826
|
-
// Detect plan-agents.json
|
|
872
|
+
if (!current || !current.compiled_graph) {
|
|
873
|
+
current = compileExecutionGraphFromArtifacts(projectRoot, activeSession, { runState: current }).run;
|
|
874
|
+
}
|
|
875
|
+
if (!current || !current.compiled_graph) {
|
|
876
|
+
throw new Error('Nenhum grafo compilado encontrado. Execute oxe-cc runtime compile primeiro.');
|
|
877
|
+
}
|
|
878
|
+
const preflight = buildRuntimeExecutePreflight(projectRoot, activeSession, current);
|
|
879
|
+
if (!preflight.ok && !options.skipPreflight) {
|
|
880
|
+
const reason = preflight.blockers[0] || 'runtime_execute_preflight_failed';
|
|
881
|
+
appendEvent(projectRoot, activeSession, {
|
|
882
|
+
type: 'WorkItemBlocked',
|
|
883
|
+
run_id: current.run_id,
|
|
884
|
+
payload: {
|
|
885
|
+
reason: 'runtime_execute_preflight_failed',
|
|
886
|
+
blockers: preflight.blockers,
|
|
887
|
+
},
|
|
888
|
+
});
|
|
889
|
+
return {
|
|
890
|
+
mode: 'preflight',
|
|
891
|
+
agentPlan: null,
|
|
892
|
+
result: {
|
|
893
|
+
run_id: current.run_id,
|
|
894
|
+
status: 'blocked',
|
|
895
|
+
completed: [],
|
|
896
|
+
failed: [],
|
|
897
|
+
blocked: ['runtime_execute_preflight'],
|
|
898
|
+
reason,
|
|
899
|
+
},
|
|
900
|
+
run: current,
|
|
901
|
+
preflight,
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
const graph = runtime.fromSerializable
|
|
905
|
+
? runtime.fromSerializable(current.compiled_graph)
|
|
906
|
+
: current.compiled_graph;
|
|
907
|
+
|
|
908
|
+
// Detect plan-agents.json: explicit override > session path > root path
|
|
827
909
|
const rootAgentPlan = path.join(projectRoot, '.oxe', 'plan-agents.json');
|
|
828
910
|
const sessAgentPlan = activeSession
|
|
829
911
|
? path.join(projectRoot, '.oxe', activeSession, 'plan', 'plan-agents.json')
|
|
830
912
|
: null;
|
|
831
|
-
const agentPlanPath = (
|
|
832
|
-
?
|
|
833
|
-
: (fs.existsSync(
|
|
913
|
+
const agentPlanPath = (options.agentsPlanPath && fs.existsSync(options.agentsPlanPath))
|
|
914
|
+
? options.agentsPlanPath
|
|
915
|
+
: (sessAgentPlan && fs.existsSync(sessAgentPlan))
|
|
916
|
+
? sessAgentPlan
|
|
917
|
+
: (fs.existsSync(rootAgentPlan) ? rootAgentPlan : null);
|
|
834
918
|
|
|
835
919
|
// Build ctx with GateManager (Gap 1)
|
|
836
920
|
const ctx = createExecutionContext(projectRoot, activeSession, {
|
|
@@ -848,40 +932,40 @@ function buildRuntimeExecutePreflight(projectRoot, activeSession, runState) {
|
|
|
848
932
|
// Gap 5: multi-agent path if plan-agents.json exists
|
|
849
933
|
if (agentPlanPath) {
|
|
850
934
|
let agentPlan;
|
|
851
|
-
try {
|
|
852
|
-
agentPlan = JSON.parse(fs.readFileSync(agentPlanPath, 'utf8'));
|
|
853
|
-
} catch (err) {
|
|
854
|
-
throw new Error(`plan-agents.json inválido: ${err.message}`);
|
|
855
|
-
}
|
|
856
|
-
const planErrors = validateMultiAgentPlan(agentPlan);
|
|
857
|
-
if (planErrors.length > 0) {
|
|
858
|
-
throw new Error(`plan-agents.json inválido: ${planErrors.join('; ')}`);
|
|
859
|
-
}
|
|
860
|
-
if (typeof runtime.MultiAgentCoordinator !== 'function') {
|
|
861
|
-
throw new Error('Runtime não exporta MultiAgentCoordinator. Verifique a versão do runtime.');
|
|
862
|
-
}
|
|
863
|
-
if (typeof runtime.GitWorktreeManager !== 'function' && !options.workspaceManager) {
|
|
864
|
-
throw new Error('Runtime não exporta GitWorktreeManager. Multi-agent real exige backend git_worktree.');
|
|
865
|
-
}
|
|
866
|
-
const agents = agentPlan.agents.map((spec) => ({
|
|
867
|
-
id: spec.id,
|
|
868
|
-
executor: options.executorFactory ? options.executorFactory(spec) : (options.executor || executor),
|
|
869
|
-
workspaceManager: options.workspaceManager || new runtime.GitWorktreeManager(projectRoot),
|
|
870
|
-
assignedTaskIds: Array.isArray(spec.tasks) ? spec.tasks : [],
|
|
871
|
-
}));
|
|
935
|
+
try {
|
|
936
|
+
agentPlan = JSON.parse(fs.readFileSync(agentPlanPath, 'utf8'));
|
|
937
|
+
} catch (err) {
|
|
938
|
+
throw new Error(`plan-agents.json inválido: ${err.message}`);
|
|
939
|
+
}
|
|
940
|
+
const planErrors = validateMultiAgentPlan(agentPlan);
|
|
941
|
+
if (planErrors.length > 0) {
|
|
942
|
+
throw new Error(`plan-agents.json inválido: ${planErrors.join('; ')}`);
|
|
943
|
+
}
|
|
944
|
+
if (typeof runtime.MultiAgentCoordinator !== 'function') {
|
|
945
|
+
throw new Error('Runtime não exporta MultiAgentCoordinator. Verifique a versão do runtime.');
|
|
946
|
+
}
|
|
947
|
+
if (typeof runtime.GitWorktreeManager !== 'function' && !options.workspaceManager) {
|
|
948
|
+
throw new Error('Runtime não exporta GitWorktreeManager. Multi-agent real exige backend git_worktree.');
|
|
949
|
+
}
|
|
950
|
+
const agents = agentPlan.agents.map((spec) => ({
|
|
951
|
+
id: spec.id,
|
|
952
|
+
executor: options.executorFactory ? options.executorFactory(spec) : (options.executor || executor),
|
|
953
|
+
workspaceManager: options.workspaceManager || new runtime.GitWorktreeManager(projectRoot),
|
|
954
|
+
assignedTaskIds: Array.isArray(spec.tasks) ? spec.tasks : [],
|
|
955
|
+
}));
|
|
872
956
|
const coordinator = new runtime.MultiAgentCoordinator();
|
|
873
957
|
const result = await coordinator.run(graph, {
|
|
874
958
|
mode: agentPlan.mode || 'parallel',
|
|
875
959
|
agents,
|
|
876
960
|
projectRoot,
|
|
877
961
|
sessionId: activeSession || null,
|
|
878
|
-
runId: current.run_id,
|
|
879
|
-
heartbeatTimeoutMs: options.heartbeatTimeoutMs ?? 120000,
|
|
880
|
-
applyWorkspaceMerges: true,
|
|
881
|
-
onEvent: ctx.onEvent,
|
|
882
|
-
});
|
|
883
|
-
return { mode: agentPlan.mode || 'parallel', agentPlan, result, run: current, preflight };
|
|
884
|
-
}
|
|
962
|
+
runId: current.run_id,
|
|
963
|
+
heartbeatTimeoutMs: options.heartbeatTimeoutMs ?? 120000,
|
|
964
|
+
applyWorkspaceMerges: true,
|
|
965
|
+
onEvent: ctx.onEvent,
|
|
966
|
+
});
|
|
967
|
+
return { mode: agentPlan.mode || 'parallel', agentPlan, result, run: current, preflight };
|
|
968
|
+
}
|
|
885
969
|
|
|
886
970
|
// Single-agent fallback
|
|
887
971
|
if (typeof runtime.Scheduler !== 'function') {
|
|
@@ -889,8 +973,8 @@ function buildRuntimeExecutePreflight(projectRoot, activeSession, runState) {
|
|
|
889
973
|
}
|
|
890
974
|
const scheduler = new runtime.Scheduler();
|
|
891
975
|
const result = await scheduler.run(graph, ctx);
|
|
892
|
-
return { mode: 'single', agentPlan: null, result, run: current, preflight };
|
|
893
|
-
}
|
|
976
|
+
return { mode: 'single', agentPlan: null, result, run: current, preflight };
|
|
977
|
+
}
|
|
894
978
|
|
|
895
979
|
function readRuntimeMultiAgentStatus(projectRoot, activeSession, options = {}) {
|
|
896
980
|
const runtime = loadRuntimeModule();
|
|
@@ -905,49 +989,49 @@ function readRuntimeMultiAgentStatus(projectRoot, activeSession, options = {}) {
|
|
|
905
989
|
workspaceIsolationEnforced: false,
|
|
906
990
|
agents: [],
|
|
907
991
|
ownership: [],
|
|
908
|
-
orphanReassignments: [],
|
|
909
|
-
handoffs: [],
|
|
910
|
-
arbitrationResults: [],
|
|
911
|
-
workspaceMergeReport: null,
|
|
912
|
-
worktrees: [],
|
|
913
|
-
mergeBlockers: [],
|
|
914
|
-
mergeReadiness: null,
|
|
915
|
-
arbitrationRequired: false,
|
|
916
|
-
health: 'unknown',
|
|
917
|
-
nextAction: 'Execute ou compile uma run antes de inspecionar status multi-agent.',
|
|
918
|
-
summary: null,
|
|
919
|
-
};
|
|
920
|
-
}
|
|
992
|
+
orphanReassignments: [],
|
|
993
|
+
handoffs: [],
|
|
994
|
+
arbitrationResults: [],
|
|
995
|
+
workspaceMergeReport: null,
|
|
996
|
+
worktrees: [],
|
|
997
|
+
mergeBlockers: [],
|
|
998
|
+
mergeReadiness: null,
|
|
999
|
+
arbitrationRequired: false,
|
|
1000
|
+
health: 'unknown',
|
|
1001
|
+
nextAction: 'Execute ou compile uma run antes de inspecionar status multi-agent.',
|
|
1002
|
+
summary: null,
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
921
1005
|
const runDir = path.join(projectRoot, '.oxe', 'runs', runId);
|
|
922
1006
|
const statePath = path.join(runDir, 'multi-agent-state.json');
|
|
923
|
-
const summaryPath = path.join(runDir, 'multi-agent-summary.json');
|
|
924
|
-
const handoffsPath = path.join(runDir, 'handoffs.json');
|
|
925
|
-
const arbitrationPath = path.join(runDir, 'arbitration-results.json');
|
|
926
|
-
const workspaceMergePath = path.join(runDir, 'workspace-merge-report.json');
|
|
1007
|
+
const summaryPath = path.join(runDir, 'multi-agent-summary.json');
|
|
1008
|
+
const handoffsPath = path.join(runDir, 'handoffs.json');
|
|
1009
|
+
const arbitrationPath = path.join(runDir, 'arbitration-results.json');
|
|
1010
|
+
const workspaceMergePath = path.join(runDir, 'workspace-merge-report.json');
|
|
927
1011
|
const state = runtime && typeof runtime.loadMultiAgentState === 'function'
|
|
928
1012
|
? runtime.loadMultiAgentState(projectRoot, runId)
|
|
929
1013
|
: readJsonIfExists(statePath);
|
|
930
|
-
const summary = runtime && typeof runtime.loadMultiAgentSummary === 'function'
|
|
931
|
-
? runtime.loadMultiAgentSummary(projectRoot, runId)
|
|
932
|
-
: readJsonIfExists(summaryPath);
|
|
933
|
-
const workspaceMergeReport = runtime && typeof runtime.loadWorkspaceMergeReport === 'function'
|
|
934
|
-
? runtime.loadWorkspaceMergeReport(projectRoot, runId)
|
|
935
|
-
: readJsonIfExists(workspaceMergePath);
|
|
936
|
-
const handoffs = readJsonIfExists(handoffsPath);
|
|
937
|
-
const arbitrationResults = readJsonIfExists(arbitrationPath);
|
|
938
|
-
const mergeBlockers = workspaceMergeReport && Array.isArray(workspaceMergeReport.blockers) ? workspaceMergeReport.blockers : [];
|
|
939
|
-
const mergeReadiness = workspaceMergeReport && workspaceMergeReport.merge_readiness ? workspaceMergeReport.merge_readiness : null;
|
|
940
|
-
let nextAction = null;
|
|
941
|
-
if (!state) {
|
|
942
|
-
nextAction = 'Execute runtime com plan-agents.json válido para materializar o estado multi-agent.';
|
|
943
|
-
} else if (mergeBlockers.length > 0) {
|
|
944
|
-
nextAction = 'Resolva os merge blockers do workspace antes de promover ou aplicar novos resultados.';
|
|
945
|
-
} else if (mergeReadiness === 'partial') {
|
|
946
|
-
nextAction = 'Conclua verify/evidence pós-merge ou aplique os worktrees pendentes antes de fechar a run.';
|
|
947
|
-
} else if (mergeReadiness === 'ready') {
|
|
948
|
-
nextAction = 'Multi-agent merge pronto; avance para runtime verify ou promotion conforme o ciclo.';
|
|
949
|
-
}
|
|
950
|
-
return {
|
|
1014
|
+
const summary = runtime && typeof runtime.loadMultiAgentSummary === 'function'
|
|
1015
|
+
? runtime.loadMultiAgentSummary(projectRoot, runId)
|
|
1016
|
+
: readJsonIfExists(summaryPath);
|
|
1017
|
+
const workspaceMergeReport = runtime && typeof runtime.loadWorkspaceMergeReport === 'function'
|
|
1018
|
+
? runtime.loadWorkspaceMergeReport(projectRoot, runId)
|
|
1019
|
+
: readJsonIfExists(workspaceMergePath);
|
|
1020
|
+
const handoffs = readJsonIfExists(handoffsPath);
|
|
1021
|
+
const arbitrationResults = readJsonIfExists(arbitrationPath);
|
|
1022
|
+
const mergeBlockers = workspaceMergeReport && Array.isArray(workspaceMergeReport.blockers) ? workspaceMergeReport.blockers : [];
|
|
1023
|
+
const mergeReadiness = workspaceMergeReport && workspaceMergeReport.merge_readiness ? workspaceMergeReport.merge_readiness : null;
|
|
1024
|
+
let nextAction = null;
|
|
1025
|
+
if (!state) {
|
|
1026
|
+
nextAction = 'Execute runtime com plan-agents.json válido para materializar o estado multi-agent.';
|
|
1027
|
+
} else if (mergeBlockers.length > 0) {
|
|
1028
|
+
nextAction = 'Resolva os merge blockers do workspace antes de promover ou aplicar novos resultados.';
|
|
1029
|
+
} else if (mergeReadiness === 'partial') {
|
|
1030
|
+
nextAction = 'Conclua verify/evidence pós-merge ou aplique os worktrees pendentes antes de fechar a run.';
|
|
1031
|
+
} else if (mergeReadiness === 'ready') {
|
|
1032
|
+
nextAction = 'Multi-agent merge pronto; avance para runtime verify ou promotion conforme o ciclo.';
|
|
1033
|
+
}
|
|
1034
|
+
return {
|
|
951
1035
|
path: statePath,
|
|
952
1036
|
enabled: Boolean(state),
|
|
953
1037
|
runId,
|
|
@@ -956,44 +1040,44 @@ function readRuntimeMultiAgentStatus(projectRoot, activeSession, options = {}) {
|
|
|
956
1040
|
agents: state && Array.isArray(state.agent_results) ? state.agent_results : [],
|
|
957
1041
|
ownership: state && Array.isArray(state.ownership) ? state.ownership : [],
|
|
958
1042
|
orphanReassignments: state && Array.isArray(state.orphan_reassignments) ? state.orphan_reassignments : [],
|
|
959
|
-
handoffs: Array.isArray(handoffs) ? handoffs : [],
|
|
960
|
-
arbitrationResults: Array.isArray(arbitrationResults) ? arbitrationResults : [],
|
|
961
|
-
workspaceMergeReport: workspaceMergeReport || null,
|
|
962
|
-
worktrees: workspaceMergeReport && Array.isArray(workspaceMergeReport.records) ? workspaceMergeReport.records : [],
|
|
963
|
-
mergeBlockers,
|
|
964
|
-
mergeReadiness,
|
|
965
|
-
arbitrationRequired: Boolean(workspaceMergeReport && workspaceMergeReport.arbitration_required),
|
|
966
|
-
health: summary && summary.health ? summary.health : (mergeBlockers.length > 0 ? 'degraded' : 'unknown'),
|
|
967
|
-
nextAction,
|
|
968
|
-
summary: summary || null,
|
|
969
|
-
};
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
function validateMultiAgentPlan(agentPlan) {
|
|
973
|
-
const errors = [];
|
|
974
|
-
const allowedModes = new Set(['parallel', 'competitive', 'cooperative']);
|
|
975
|
-
const mode = agentPlan && agentPlan.mode ? String(agentPlan.mode) : 'parallel';
|
|
976
|
-
if (!allowedModes.has(mode)) errors.push(`mode inválido: ${mode}`);
|
|
977
|
-
if (!Array.isArray(agentPlan && agentPlan.agents) || agentPlan.agents.length === 0) {
|
|
978
|
-
errors.push('campo "agents" vazio ou ausente');
|
|
979
|
-
return errors;
|
|
980
|
-
}
|
|
981
|
-
const schemaVersion = Number(agentPlan.schema_version || agentPlan.schema || 0);
|
|
982
|
-
const seen = new Set();
|
|
983
|
-
for (const [idx, spec] of agentPlan.agents.entries()) {
|
|
984
|
-
const id = spec && spec.id ? String(spec.id) : '';
|
|
985
|
-
if (!id) errors.push(`agents[${idx}].id ausente`);
|
|
986
|
-
if (id && seen.has(id)) errors.push(`agent duplicado: ${id}`);
|
|
987
|
-
if (id) seen.add(id);
|
|
988
|
-
if (schemaVersion >= 3 && !spec.persona) errors.push(`${id || `agents[${idx}]`}.persona ausente`);
|
|
989
|
-
if (schemaVersion >= 3 && !spec.model_hint) errors.push(`${id || `agents[${idx}]`}.model_hint ausente`);
|
|
990
|
-
if (spec.tasks != null && !Array.isArray(spec.tasks)) errors.push(`${id || `agents[${idx}]`}.tasks deve ser array`);
|
|
991
|
-
}
|
|
992
|
-
if ((mode === 'competitive' || mode === 'cooperative') && agentPlan.agents.length < 2) {
|
|
993
|
-
errors.push(`${mode} exige pelo menos 2 agentes`);
|
|
994
|
-
}
|
|
995
|
-
return errors;
|
|
996
|
-
}
|
|
1043
|
+
handoffs: Array.isArray(handoffs) ? handoffs : [],
|
|
1044
|
+
arbitrationResults: Array.isArray(arbitrationResults) ? arbitrationResults : [],
|
|
1045
|
+
workspaceMergeReport: workspaceMergeReport || null,
|
|
1046
|
+
worktrees: workspaceMergeReport && Array.isArray(workspaceMergeReport.records) ? workspaceMergeReport.records : [],
|
|
1047
|
+
mergeBlockers,
|
|
1048
|
+
mergeReadiness,
|
|
1049
|
+
arbitrationRequired: Boolean(workspaceMergeReport && workspaceMergeReport.arbitration_required),
|
|
1050
|
+
health: summary && summary.health ? summary.health : (mergeBlockers.length > 0 ? 'degraded' : 'unknown'),
|
|
1051
|
+
nextAction,
|
|
1052
|
+
summary: summary || null,
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
function validateMultiAgentPlan(agentPlan) {
|
|
1057
|
+
const errors = [];
|
|
1058
|
+
const allowedModes = new Set(['parallel', 'competitive', 'cooperative']);
|
|
1059
|
+
const mode = agentPlan && agentPlan.mode ? String(agentPlan.mode) : 'parallel';
|
|
1060
|
+
if (!allowedModes.has(mode)) errors.push(`mode inválido: ${mode}`);
|
|
1061
|
+
if (!Array.isArray(agentPlan && agentPlan.agents) || agentPlan.agents.length === 0) {
|
|
1062
|
+
errors.push('campo "agents" vazio ou ausente');
|
|
1063
|
+
return errors;
|
|
1064
|
+
}
|
|
1065
|
+
const schemaVersion = Number(agentPlan.schema_version || agentPlan.schema || 0);
|
|
1066
|
+
const seen = new Set();
|
|
1067
|
+
for (const [idx, spec] of agentPlan.agents.entries()) {
|
|
1068
|
+
const id = spec && spec.id ? String(spec.id) : '';
|
|
1069
|
+
if (!id) errors.push(`agents[${idx}].id ausente`);
|
|
1070
|
+
if (id && seen.has(id)) errors.push(`agent duplicado: ${id}`);
|
|
1071
|
+
if (id) seen.add(id);
|
|
1072
|
+
if (schemaVersion >= 3 && !spec.persona) errors.push(`${id || `agents[${idx}]`}.persona ausente`);
|
|
1073
|
+
if (schemaVersion >= 3 && !spec.model_hint) errors.push(`${id || `agents[${idx}]`}.model_hint ausente`);
|
|
1074
|
+
if (spec.tasks != null && !Array.isArray(spec.tasks)) errors.push(`${id || `agents[${idx}]`}.tasks deve ser array`);
|
|
1075
|
+
}
|
|
1076
|
+
if ((mode === 'competitive' || mode === 'cooperative') && agentPlan.agents.length < 2) {
|
|
1077
|
+
errors.push(`${mode} exige pelo menos 2 agentes`);
|
|
1078
|
+
}
|
|
1079
|
+
return errors;
|
|
1080
|
+
}
|
|
997
1081
|
|
|
998
1082
|
function loadRuntimeVerificationArtifacts(projectRoot, runState) {
|
|
999
1083
|
const runtime = loadRuntimeModule();
|
|
@@ -1019,7 +1103,7 @@ function loadRuntimeVerificationArtifacts(projectRoot, runState) {
|
|
|
1019
1103
|
return { manifest, residualRisks, evidenceCoverage };
|
|
1020
1104
|
}
|
|
1021
1105
|
|
|
1022
|
-
function countVerificationEvidenceRefs(runState, verificationArtifacts) {
|
|
1106
|
+
function countVerificationEvidenceRefs(runState, verificationArtifacts) {
|
|
1023
1107
|
if (verificationArtifacts && verificationArtifacts.manifest && Array.isArray(verificationArtifacts.manifest.checks)) {
|
|
1024
1108
|
return verificationArtifacts.manifest.checks.reduce((sum, check) => {
|
|
1025
1109
|
return sum + (Array.isArray(check.evidence_refs) ? check.evidence_refs.length : 0);
|
|
@@ -1031,26 +1115,26 @@ function countVerificationEvidenceRefs(runState, verificationArtifacts) {
|
|
|
1031
1115
|
}, 0);
|
|
1032
1116
|
}
|
|
1033
1117
|
return 0;
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
function detectOrphanWorktrees(projectRoot, runId) {
|
|
1037
|
-
const workspacesDir = path.join(projectRoot, '.oxe', 'workspaces');
|
|
1038
|
-
if (!fs.existsSync(workspacesDir)) return [];
|
|
1039
|
-
const active = new Set();
|
|
1040
|
-
const mergeReport = readJsonIfExists(path.join(projectRoot, '.oxe', 'runs', runId, 'workspace-merge-report.json'));
|
|
1041
|
-
for (const record of (mergeReport && Array.isArray(mergeReport.records) ? mergeReport.records : [])) {
|
|
1042
|
-
if (record && record.workspace_id) active.add(String(record.workspace_id));
|
|
1043
|
-
}
|
|
1044
|
-
return fs.readdirSync(workspacesDir, { withFileTypes: true })
|
|
1045
|
-
.filter((entry) => entry.isDirectory())
|
|
1046
|
-
.map((entry) => entry.name)
|
|
1047
|
-
.filter((name) => !active.has(name))
|
|
1048
|
-
.map((name) => ({
|
|
1049
|
-
workspace_id: name,
|
|
1050
|
-
path: path.join(workspacesDir, name),
|
|
1051
|
-
next_action: 'inspecione o worktree órfão; depois remova com git worktree remove --force se não houver diffs úteis',
|
|
1052
|
-
}));
|
|
1053
|
-
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
function detectOrphanWorktrees(projectRoot, runId) {
|
|
1121
|
+
const workspacesDir = path.join(projectRoot, '.oxe', 'workspaces');
|
|
1122
|
+
if (!fs.existsSync(workspacesDir)) return [];
|
|
1123
|
+
const active = new Set();
|
|
1124
|
+
const mergeReport = readJsonIfExists(path.join(projectRoot, '.oxe', 'runs', runId, 'workspace-merge-report.json'));
|
|
1125
|
+
for (const record of (mergeReport && Array.isArray(mergeReport.records) ? mergeReport.records : [])) {
|
|
1126
|
+
if (record && record.workspace_id) active.add(String(record.workspace_id));
|
|
1127
|
+
}
|
|
1128
|
+
return fs.readdirSync(workspacesDir, { withFileTypes: true })
|
|
1129
|
+
.filter((entry) => entry.isDirectory())
|
|
1130
|
+
.map((entry) => entry.name)
|
|
1131
|
+
.filter((name) => !active.has(name))
|
|
1132
|
+
.map((name) => ({
|
|
1133
|
+
workspace_id: name,
|
|
1134
|
+
path: path.join(workspacesDir, name),
|
|
1135
|
+
next_action: 'inspecione o worktree órfão; depois remova com git worktree remove --force se não houver diffs úteis',
|
|
1136
|
+
}));
|
|
1137
|
+
}
|
|
1054
1138
|
|
|
1055
1139
|
function buildRuntimeModeStatus(runState) {
|
|
1056
1140
|
if (!runState) {
|
|
@@ -1222,17 +1306,17 @@ function writeRecoverySummaryMarkdown(projectRoot, activeSession, runState, reco
|
|
|
1222
1306
|
'',
|
|
1223
1307
|
'## Work items órfãos',
|
|
1224
1308
|
'',
|
|
1225
|
-
...(Array.isArray(recoverySummary.orphan_work_items) && recoverySummary.orphan_work_items.length
|
|
1226
|
-
? recoverySummary.orphan_work_items.map((item) => `- ${item}`)
|
|
1227
|
-
: ['- Nenhum']),
|
|
1228
|
-
'',
|
|
1229
|
-
'## Worktrees órfãos',
|
|
1230
|
-
'',
|
|
1231
|
-
...(Array.isArray(recoverySummary.orphan_worktrees) && recoverySummary.orphan_worktrees.length
|
|
1232
|
-
? recoverySummary.orphan_worktrees.map((item) => `- ${item.workspace_id} · ${item.next_action}`)
|
|
1233
|
-
: ['- Nenhum']),
|
|
1234
|
-
'',
|
|
1235
|
-
'## Tentativas incompletas',
|
|
1309
|
+
...(Array.isArray(recoverySummary.orphan_work_items) && recoverySummary.orphan_work_items.length
|
|
1310
|
+
? recoverySummary.orphan_work_items.map((item) => `- ${item}`)
|
|
1311
|
+
: ['- Nenhum']),
|
|
1312
|
+
'',
|
|
1313
|
+
'## Worktrees órfãos',
|
|
1314
|
+
'',
|
|
1315
|
+
...(Array.isArray(recoverySummary.orphan_worktrees) && recoverySummary.orphan_worktrees.length
|
|
1316
|
+
? recoverySummary.orphan_worktrees.map((item) => `- ${item.workspace_id} · ${item.next_action}`)
|
|
1317
|
+
: ['- Nenhum']),
|
|
1318
|
+
'',
|
|
1319
|
+
'## Tentativas incompletas',
|
|
1236
1320
|
'',
|
|
1237
1321
|
...(recoverySummary.consistency && Array.isArray(recoverySummary.consistency.incomplete_attempts) && recoverySummary.consistency.incomplete_attempts.length
|
|
1238
1322
|
? recoverySummary.consistency.incomplete_attempts.map((item) => `- ${item.work_item_id} · ${item.attempt_id || 'attempt'} · ${item.outcome || 'unknown'}`)
|
|
@@ -1337,6 +1421,94 @@ async function runRuntimeVerify(projectRoot, activeSession, options = {}) {
|
|
|
1337
1421
|
};
|
|
1338
1422
|
}
|
|
1339
1423
|
|
|
1424
|
+
// Marks SPEC.md checklist items as [x] when the run is completed.
|
|
1425
|
+
// Finds **DoD Wave N:** blocks and numbered checklist sections (e.g. "21.1 MVP"),
|
|
1426
|
+
// marks unchecked items only within sections whose waves are fully completed.
|
|
1427
|
+
function applySpecChecklistSync(specPath, canonicalLive, compiledGraph) {
|
|
1428
|
+
if (!canonicalLive || (canonicalLive.run && canonicalLive.run.status !== 'completed')) return;
|
|
1429
|
+
if (!specPath || !fs.existsSync(specPath)) return;
|
|
1430
|
+
|
|
1431
|
+
const completedIds = new Set(
|
|
1432
|
+
canonicalLive.completedWorkItems instanceof Set
|
|
1433
|
+
? [...canonicalLive.completedWorkItems]
|
|
1434
|
+
: Array.isArray(canonicalLive.completedWorkItems)
|
|
1435
|
+
? canonicalLive.completedWorkItems
|
|
1436
|
+
: []
|
|
1437
|
+
);
|
|
1438
|
+
if (completedIds.size === 0) return;
|
|
1439
|
+
|
|
1440
|
+
const waves = Array.isArray(compiledGraph && compiledGraph.waves) ? compiledGraph.waves : [];
|
|
1441
|
+
|
|
1442
|
+
// Which wave numbers are fully completed (all node_ids in completedIds)?
|
|
1443
|
+
const completedWaveNums = new Set();
|
|
1444
|
+
for (const wave of waves) {
|
|
1445
|
+
if (Array.isArray(wave.node_ids) && wave.node_ids.length > 0 &&
|
|
1446
|
+
wave.node_ids.every(id => completedIds.has(id))) {
|
|
1447
|
+
completedWaveNums.add(wave.wave_number);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
// Are ALL waves completed? (run is complete = yes, since status === 'completed')
|
|
1452
|
+
const allWavesDone = waves.length === 0 || waves.every(w =>
|
|
1453
|
+
Array.isArray(w.node_ids) && w.node_ids.every(id => completedIds.has(id))
|
|
1454
|
+
);
|
|
1455
|
+
|
|
1456
|
+
const lines = fs.readFileSync(specPath, 'utf8').split('\n');
|
|
1457
|
+
let changed = false;
|
|
1458
|
+
|
|
1459
|
+
// Tracking state: which section type are we marking?
|
|
1460
|
+
// 'none' | 'dod_wave' | 'checklist_mvp' | 'checklist_full'
|
|
1461
|
+
let markMode = 'none';
|
|
1462
|
+
|
|
1463
|
+
const result = lines.map((line, i) => {
|
|
1464
|
+
// Detect DoD Wave block header: **DoD Wave N:** or **DoD Wave N:** (with optional emoji/text)
|
|
1465
|
+
const dodMatch = line.match(/\*\*DoD Wave\s+(\d+):/i);
|
|
1466
|
+
if (dodMatch) {
|
|
1467
|
+
const waveNum = parseInt(dodMatch[1], 10);
|
|
1468
|
+
markMode = completedWaveNums.has(waveNum) ? 'dod_wave' : 'none';
|
|
1469
|
+
return line;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
// Detect numbered checklist section headers (e.g. "### 21.1 MVP", "### 21.2 v1.0.0")
|
|
1473
|
+
const checklistMatch = line.match(/^#{2,4}\s+\d+\.\d+\s+(.+)/i);
|
|
1474
|
+
if (checklistMatch) {
|
|
1475
|
+
const sectionTitle = checklistMatch[1].toLowerCase();
|
|
1476
|
+
// MVP / v0.x.x → mark when run is fully complete (pre-release milestones)
|
|
1477
|
+
// v1.0.0 or any X.Y.Z ≥ 1.0.0 → never auto-mark (requires explicit human sign-off)
|
|
1478
|
+
if (/mvp|v?0\.\d+\.\d+/i.test(sectionTitle) && allWavesDone) {
|
|
1479
|
+
markMode = 'checklist_mvp';
|
|
1480
|
+
} else {
|
|
1481
|
+
markMode = 'none';
|
|
1482
|
+
}
|
|
1483
|
+
return line;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
// Exit section on any top-level heading (##, ###) that isn't a checklist header
|
|
1487
|
+
if (/^#{1,3}\s/.test(line) && !checklistMatch) {
|
|
1488
|
+
markMode = 'none';
|
|
1489
|
+
return line;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
// Exit DoD section on **Limite técnico or **Condição para replanejar etc.
|
|
1493
|
+
if (markMode === 'dod_wave' && /^\*\*(?:Limite|Condição|Nota|Atenção|Warning)/.test(line)) {
|
|
1494
|
+
markMode = 'none';
|
|
1495
|
+
return line;
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
// Mark unchecked items in active section
|
|
1499
|
+
if (markMode !== 'none' && /^- \[ \]/.test(line)) {
|
|
1500
|
+
changed = true;
|
|
1501
|
+
return line.replace(/^- \[ \]/, '- [x]');
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
return line;
|
|
1505
|
+
});
|
|
1506
|
+
|
|
1507
|
+
if (changed) {
|
|
1508
|
+
fs.writeFileSync(specPath, result.join('\n'), 'utf8');
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1340
1512
|
function projectRuntimeArtifacts(projectRoot, activeSession, options = {}) {
|
|
1341
1513
|
const runtime = loadRuntimeModule();
|
|
1342
1514
|
if (!runtime || typeof runtime.ProjectionEngine !== 'function' || typeof runtime.fromSerializable !== 'function') {
|
|
@@ -1406,6 +1578,8 @@ function projectRuntimeArtifacts(projectRoot, activeSession, options = {}) {
|
|
|
1406
1578
|
fs.writeFileSync(path.join(op.executionRoot, 'COMMIT-SUMMARY.md'), projections.commitSummary + '\n', 'utf8');
|
|
1407
1579
|
fs.writeFileSync(path.join(op.executionRoot, 'PROMOTION-SUMMARY.md'), projections.promotionSummary + '\n', 'utf8');
|
|
1408
1580
|
fs.writeFileSync(path.join(op.executionRoot, 'PR-SUMMARY.md'), projections.prSummary + '\n', 'utf8');
|
|
1581
|
+
// Sync SPEC.md checklist: mark completed wave DoD sections and MVP checklist when run closes
|
|
1582
|
+
applySpecChecklistSync(paths.spec, canonicalLive, current.compiled_graph);
|
|
1409
1583
|
}
|
|
1410
1584
|
const next = writeRunState(projectRoot, activeSession, {
|
|
1411
1585
|
...current,
|
|
@@ -1595,14 +1769,14 @@ function recoverRuntimeState(projectRoot, activeSession, options = {}) {
|
|
|
1595
1769
|
journal,
|
|
1596
1770
|
verificationArtifacts
|
|
1597
1771
|
);
|
|
1598
|
-
const recoverySummary = {
|
|
1599
|
-
recovered_at: new Date().toISOString(),
|
|
1600
|
-
journal_state: journal.scheduler_state,
|
|
1601
|
-
orphan_work_items: orphanWorkItems,
|
|
1602
|
-
orphan_worktrees: detectOrphanWorktrees(projectRoot, current.run_id),
|
|
1603
|
-
pending_gates: readRuntimeGates(projectRoot, activeSession, { runId: current.run_id }).pending.map((gate) => gate.gate_id),
|
|
1604
|
-
consistency,
|
|
1605
|
-
};
|
|
1772
|
+
const recoverySummary = {
|
|
1773
|
+
recovered_at: new Date().toISOString(),
|
|
1774
|
+
journal_state: journal.scheduler_state,
|
|
1775
|
+
orphan_work_items: orphanWorkItems,
|
|
1776
|
+
orphan_worktrees: detectOrphanWorktrees(projectRoot, current.run_id),
|
|
1777
|
+
pending_gates: readRuntimeGates(projectRoot, activeSession, { runId: current.run_id }).pending.map((gate) => gate.gate_id),
|
|
1778
|
+
consistency,
|
|
1779
|
+
};
|
|
1606
1780
|
const runDir = path.join(projectRoot, '.oxe', 'runs', current.run_id);
|
|
1607
1781
|
ensureDir(runDir);
|
|
1608
1782
|
const recoverySummaryPath = path.join(runDir, 'recovery-summary.json');
|
|
@@ -2484,9 +2658,11 @@ module.exports = {
|
|
|
2484
2658
|
buildRecoveryConsistency,
|
|
2485
2659
|
readRuntimeGates,
|
|
2486
2660
|
resolveRuntimeGate,
|
|
2487
|
-
createExecutionContext,
|
|
2488
|
-
|
|
2489
|
-
|
|
2661
|
+
createExecutionContext,
|
|
2662
|
+
saveRuntimeProviderConfig,
|
|
2663
|
+
loadRuntimeProviderConfig,
|
|
2664
|
+
buildRuntimeExecutePreflight,
|
|
2665
|
+
runRuntimeExecute,
|
|
2490
2666
|
runRuntimeVerify,
|
|
2491
2667
|
projectRuntimeArtifacts,
|
|
2492
2668
|
runRuntimeCiChecks,
|
|
@@ -2496,4 +2672,5 @@ module.exports = {
|
|
|
2496
2672
|
replayEvents,
|
|
2497
2673
|
replayRuntimeState,
|
|
2498
2674
|
readRuntimeMultiAgentStatus,
|
|
2675
|
+
applySpecChecklistSync,
|
|
2499
2676
|
};
|