oxe-cc 1.2.1 → 1.3.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.
Files changed (276) hide show
  1. package/.cursor/commands/oxe-ask.md +2 -2
  2. package/.cursor/commands/oxe-capabilities.md +2 -2
  3. package/.cursor/commands/oxe-checkpoint.md +2 -2
  4. package/.cursor/commands/oxe-compact.md +2 -2
  5. package/.cursor/commands/oxe-dashboard.md +2 -2
  6. package/.cursor/commands/oxe-debug.md +2 -2
  7. package/.cursor/commands/oxe-discuss.md +2 -2
  8. package/.cursor/commands/oxe-execute.md +5 -2
  9. package/.cursor/commands/oxe-forensics.md +2 -2
  10. package/.cursor/commands/oxe-help.md +2 -2
  11. package/.cursor/commands/oxe-loop.md +2 -2
  12. package/.cursor/commands/oxe-milestone.md +2 -2
  13. package/.cursor/commands/oxe-next.md +2 -2
  14. package/.cursor/commands/oxe-obs.md +2 -2
  15. package/.cursor/commands/oxe-plan-agent.md +2 -2
  16. package/.cursor/commands/oxe-plan.md +2 -2
  17. package/.cursor/commands/oxe-project.md +2 -2
  18. package/.cursor/commands/oxe-quick.md +2 -2
  19. package/.cursor/commands/oxe-research.md +2 -2
  20. package/.cursor/commands/oxe-retro.md +2 -2
  21. package/.cursor/commands/oxe-review-pr.md +2 -2
  22. package/.cursor/commands/oxe-route.md +2 -2
  23. package/.cursor/commands/oxe-scan.md +2 -2
  24. package/.cursor/commands/oxe-security.md +2 -2
  25. package/.cursor/commands/oxe-session.md +2 -2
  26. package/.cursor/commands/oxe-ship.md +2 -2
  27. package/.cursor/commands/oxe-skill.md +2 -2
  28. package/.cursor/commands/oxe-spec.md +2 -2
  29. package/.cursor/commands/oxe-ui-review.md +2 -2
  30. package/.cursor/commands/oxe-ui-spec.md +2 -2
  31. package/.cursor/commands/oxe-update.md +2 -2
  32. package/.cursor/commands/oxe-validate-gaps.md +2 -2
  33. package/.cursor/commands/oxe-verify.md +5 -2
  34. package/.cursor/commands/oxe-workstream.md +2 -2
  35. package/.cursor/commands/oxe.md +2 -2
  36. package/.github/copilot-instructions.md +13 -13
  37. package/.github/prompts/oxe-ask.prompt.md +2 -2
  38. package/.github/prompts/oxe-capabilities.prompt.md +2 -2
  39. package/.github/prompts/oxe-checkpoint.prompt.md +2 -2
  40. package/.github/prompts/oxe-compact.prompt.md +2 -2
  41. package/.github/prompts/oxe-dashboard.prompt.md +2 -2
  42. package/.github/prompts/oxe-debug.prompt.md +2 -2
  43. package/.github/prompts/oxe-discuss.prompt.md +2 -2
  44. package/.github/prompts/oxe-execute.prompt.md +5 -2
  45. package/.github/prompts/oxe-forensics.prompt.md +2 -2
  46. package/.github/prompts/oxe-help.prompt.md +2 -2
  47. package/.github/prompts/oxe-loop.prompt.md +2 -2
  48. package/.github/prompts/oxe-milestone.prompt.md +2 -2
  49. package/.github/prompts/oxe-next.prompt.md +2 -2
  50. package/.github/prompts/oxe-obs.prompt.md +2 -2
  51. package/.github/prompts/oxe-plan-agent.prompt.md +2 -2
  52. package/.github/prompts/oxe-plan.prompt.md +2 -2
  53. package/.github/prompts/oxe-project.prompt.md +2 -2
  54. package/.github/prompts/oxe-quick.prompt.md +2 -2
  55. package/.github/prompts/oxe-research.prompt.md +2 -2
  56. package/.github/prompts/oxe-retro.prompt.md +2 -2
  57. package/.github/prompts/oxe-review-pr.prompt.md +2 -2
  58. package/.github/prompts/oxe-route.prompt.md +2 -2
  59. package/.github/prompts/oxe-scan.prompt.md +2 -2
  60. package/.github/prompts/oxe-security.prompt.md +2 -2
  61. package/.github/prompts/oxe-session.prompt.md +2 -2
  62. package/.github/prompts/oxe-ship.prompt.md +2 -2
  63. package/.github/prompts/oxe-skill.prompt.md +2 -2
  64. package/.github/prompts/oxe-spec.prompt.md +2 -2
  65. package/.github/prompts/oxe-ui-review.prompt.md +2 -2
  66. package/.github/prompts/oxe-ui-spec.prompt.md +2 -2
  67. package/.github/prompts/oxe-update.prompt.md +2 -2
  68. package/.github/prompts/oxe-validate-gaps.prompt.md +2 -2
  69. package/.github/prompts/oxe-verify.prompt.md +5 -2
  70. package/.github/prompts/oxe-workstream.prompt.md +2 -2
  71. package/.github/prompts/oxe.prompt.md +2 -2
  72. package/CHANGELOG.md +52 -17
  73. package/README.md +610 -551
  74. package/bin/banner.txt +1 -1
  75. package/bin/lib/oxe-agent-install.cjs +69 -69
  76. package/bin/lib/oxe-azure.cjs +1445 -1445
  77. package/bin/lib/oxe-context-engine.cjs +867 -867
  78. package/bin/lib/oxe-dashboard.cjs +76 -28
  79. package/bin/lib/oxe-operational.cjs +2144 -1340
  80. package/bin/lib/oxe-project-health.cjs +483 -1
  81. package/bin/lib/oxe-runtime-semantics.cjs +12 -0
  82. package/bin/oxe-cc.js +554 -152
  83. package/commands/oxe/ask.md +2 -2
  84. package/commands/oxe/capabilities.md +2 -2
  85. package/commands/oxe/checkpoint.md +2 -2
  86. package/commands/oxe/compact.md +2 -2
  87. package/commands/oxe/dashboard.md +2 -2
  88. package/commands/oxe/debug.md +2 -2
  89. package/commands/oxe/discuss.md +2 -2
  90. package/commands/oxe/execute.md +5 -2
  91. package/commands/oxe/forensics.md +2 -2
  92. package/commands/oxe/help.md +2 -2
  93. package/commands/oxe/loop.md +2 -2
  94. package/commands/oxe/milestone.md +2 -2
  95. package/commands/oxe/next.md +2 -2
  96. package/commands/oxe/obs.md +2 -2
  97. package/commands/oxe/oxe.md +2 -2
  98. package/commands/oxe/plan-agent.md +2 -2
  99. package/commands/oxe/plan.md +2 -2
  100. package/commands/oxe/project.md +2 -2
  101. package/commands/oxe/quick.md +2 -2
  102. package/commands/oxe/research.md +2 -2
  103. package/commands/oxe/retro.md +2 -2
  104. package/commands/oxe/review-pr.md +2 -2
  105. package/commands/oxe/route.md +2 -2
  106. package/commands/oxe/scan.md +2 -2
  107. package/commands/oxe/security.md +2 -2
  108. package/commands/oxe/session.md +2 -2
  109. package/commands/oxe/ship.md +2 -2
  110. package/commands/oxe/skill.md +2 -2
  111. package/commands/oxe/spec.md +2 -2
  112. package/commands/oxe/ui-review.md +2 -2
  113. package/commands/oxe/ui-spec.md +2 -2
  114. package/commands/oxe/update.md +2 -2
  115. package/commands/oxe/validate-gaps.md +2 -2
  116. package/commands/oxe/verify.md +5 -2
  117. package/commands/oxe/workstream.md +2 -2
  118. package/lib/runtime/delivery/branch-manager.d.ts +1 -0
  119. package/lib/runtime/delivery/branch-manager.js +7 -0
  120. package/lib/runtime/delivery/ci-checks.js +34 -1
  121. package/lib/runtime/delivery/delivery-records.d.ts +34 -0
  122. package/lib/runtime/delivery/delivery-records.js +48 -0
  123. package/lib/runtime/delivery/index.d.ts +1 -0
  124. package/lib/runtime/delivery/index.js +1 -0
  125. package/lib/runtime/delivery/promotion-pipeline.d.ts +26 -2
  126. package/lib/runtime/delivery/promotion-pipeline.js +111 -14
  127. package/lib/runtime/gate/gate-manager.d.ts +41 -0
  128. package/lib/runtime/gate/gate-manager.js +108 -1
  129. package/lib/runtime/index.d.ts +2 -2
  130. package/lib/runtime/index.js +3 -1
  131. package/lib/runtime/models/gate-decision.d.ts +4 -1
  132. package/lib/runtime/models/workspace.d.ts +3 -0
  133. package/lib/runtime/plugins/capability-adapter.d.ts +12 -0
  134. package/lib/runtime/plugins/capability-adapter.js +204 -0
  135. package/lib/runtime/plugins/capability-matrix.d.ts +5 -0
  136. package/lib/runtime/plugins/capability-matrix.js +48 -17
  137. package/lib/runtime/plugins/index.d.ts +1 -0
  138. package/lib/runtime/plugins/index.js +1 -0
  139. package/lib/runtime/plugins/plugin-abi.d.ts +2 -0
  140. package/lib/runtime/plugins/plugin-manifest.d.ts +1 -1
  141. package/lib/runtime/plugins/plugin-manifest.js +6 -2
  142. package/lib/runtime/plugins/plugin-registry.d.ts +46 -0
  143. package/lib/runtime/plugins/plugin-registry.js +79 -2
  144. package/lib/runtime/policy/policy-engine.d.ts +19 -0
  145. package/lib/runtime/policy/policy-engine.js +76 -4
  146. package/lib/runtime/projection/projection-engine.d.ts +9 -1
  147. package/lib/runtime/projection/projection-engine.js +73 -3
  148. package/lib/runtime/scheduler/multi-agent-coordinator.d.ts +43 -1
  149. package/lib/runtime/scheduler/multi-agent-coordinator.js +151 -39
  150. package/lib/runtime/scheduler/run-journal.d.ts +1 -1
  151. package/lib/runtime/scheduler/scheduler.d.ts +19 -1
  152. package/lib/runtime/scheduler/scheduler.js +258 -13
  153. package/lib/runtime/verification/verification-compiler.d.ts +43 -0
  154. package/lib/runtime/verification/verification-compiler.js +137 -0
  155. package/lib/runtime/verification/verification-manifest.d.ts +9 -0
  156. package/lib/runtime/verification/verification-manifest.js +56 -6
  157. package/lib/runtime/workspace/strategies/ephemeral-container.d.ts +1 -0
  158. package/lib/runtime/workspace/strategies/ephemeral-container.js +4 -0
  159. package/lib/runtime/workspace/strategies/git-worktree.d.ts +1 -0
  160. package/lib/runtime/workspace/strategies/git-worktree.js +2 -0
  161. package/lib/runtime/workspace/strategies/inplace.d.ts +1 -0
  162. package/lib/runtime/workspace/strategies/inplace.js +2 -0
  163. package/lib/runtime/workspace/workspace-manager.d.ts +2 -1
  164. package/lib/sdk/README.md +9 -9
  165. package/lib/sdk/index.cjs +33 -24
  166. package/lib/sdk/index.d.ts +149 -14
  167. package/oxe/templates/ACTIVE-RUN.template.json +32 -32
  168. package/oxe/templates/CAPABILITIES.template.md +7 -7
  169. package/oxe/templates/CAPABILITY.template.md +45 -45
  170. package/oxe/templates/CHECKPOINTS.template.md +7 -7
  171. package/oxe/templates/EXECUTION-RUNTIME.template.md +68 -68
  172. package/oxe/templates/HYPOTHESES.template.md +33 -33
  173. package/oxe/templates/LESSONS-METRICS.template.json +13 -13
  174. package/oxe/templates/NOTES.template.md +16 -16
  175. package/oxe/templates/PLAN-REVIEW.template.md +31 -31
  176. package/oxe/templates/SESSION.template.md +34 -34
  177. package/oxe/templates/SKILL.template.md +26 -26
  178. package/oxe/templates/STATE.md +55 -55
  179. package/oxe/templates/WORKFLOW_AUTHORING.md +18 -18
  180. package/oxe/workflows/ask.md +96 -96
  181. package/oxe/workflows/capabilities.md +25 -25
  182. package/oxe/workflows/dashboard.md +33 -33
  183. package/oxe/workflows/discuss.md +12 -12
  184. package/oxe/workflows/execute.md +14 -0
  185. package/oxe/workflows/help.md +352 -352
  186. package/oxe/workflows/next.md +22 -22
  187. package/oxe/workflows/oxe.md +6 -6
  188. package/oxe/workflows/plan-agent.md +9 -9
  189. package/oxe/workflows/quick.md +10 -10
  190. package/oxe/workflows/references/reasoning-discovery.md +28 -28
  191. package/oxe/workflows/references/reasoning-execution.md +29 -29
  192. package/oxe/workflows/references/reasoning-planning.md +32 -32
  193. package/oxe/workflows/references/reasoning-review.md +29 -29
  194. package/oxe/workflows/references/reasoning-status.md +24 -24
  195. package/oxe/workflows/references/robustness-elevation.md +295 -295
  196. package/oxe/workflows/references/workflow-runtime-contracts.json +952 -930
  197. package/oxe/workflows/route.md +16 -16
  198. package/oxe/workflows/session.md +213 -213
  199. package/oxe/workflows/ship.md +142 -142
  200. package/oxe/workflows/skill.md +44 -44
  201. package/oxe/workflows/ui-review.md +36 -36
  202. package/oxe/workflows/verify-audit.md +73 -73
  203. package/oxe/workflows/verify.md +10 -0
  204. package/package.json +92 -92
  205. package/packages/runtime/package.json +17 -17
  206. package/packages/runtime/src/audit/audit-trail.ts +243 -243
  207. package/packages/runtime/src/audit/index.ts +2 -2
  208. package/packages/runtime/src/audit/policy-pack.ts +62 -62
  209. package/packages/runtime/src/compiler/graph-compiler.ts +245 -245
  210. package/packages/runtime/src/compiler/index.ts +1 -1
  211. package/packages/runtime/src/context/context-pack-builder.ts +259 -259
  212. package/packages/runtime/src/context/context-pack-store.ts +197 -197
  213. package/packages/runtime/src/context/context-profiles.ts +60 -60
  214. package/packages/runtime/src/context/index.ts +3 -3
  215. package/packages/runtime/src/decision/decision-engine.ts +174 -174
  216. package/packages/runtime/src/decision/decision-memo.ts +211 -211
  217. package/packages/runtime/src/decision/index.ts +2 -2
  218. package/packages/runtime/src/delivery/branch-manager.ts +91 -84
  219. package/packages/runtime/src/delivery/ci-checks.ts +285 -252
  220. package/packages/runtime/src/delivery/delivery-records.ts +75 -0
  221. package/packages/runtime/src/delivery/index.ts +5 -4
  222. package/packages/runtime/src/delivery/pr-manager.ts +112 -112
  223. package/packages/runtime/src/delivery/promotion-pipeline.ts +334 -180
  224. package/packages/runtime/src/events/bus.ts +92 -92
  225. package/packages/runtime/src/events/catalog.ts +29 -29
  226. package/packages/runtime/src/events/envelope.ts +14 -14
  227. package/packages/runtime/src/events/index.ts +3 -3
  228. package/packages/runtime/src/evidence/evidence-store.ts +130 -130
  229. package/packages/runtime/src/evidence/index.ts +1 -1
  230. package/packages/runtime/src/gate/gate-manager.ts +289 -137
  231. package/packages/runtime/src/gate/index.ts +1 -1
  232. package/packages/runtime/src/index.ts +41 -37
  233. package/packages/runtime/src/models/attempt.ts +19 -19
  234. package/packages/runtime/src/models/evidence.ts +21 -21
  235. package/packages/runtime/src/models/gate-decision.ts +25 -21
  236. package/packages/runtime/src/models/index.ts +8 -8
  237. package/packages/runtime/src/models/run.ts +24 -24
  238. package/packages/runtime/src/models/session.ts +11 -11
  239. package/packages/runtime/src/models/verification-result.ts +10 -10
  240. package/packages/runtime/src/models/work-item.ts +25 -25
  241. package/packages/runtime/src/models/workspace.ts +31 -28
  242. package/packages/runtime/src/plugins/capability-adapter.ts +206 -0
  243. package/packages/runtime/src/plugins/capability-matrix.ts +126 -83
  244. package/packages/runtime/src/plugins/index.ts +5 -4
  245. package/packages/runtime/src/plugins/plugin-abi.ts +97 -95
  246. package/packages/runtime/src/plugins/plugin-manifest.ts +118 -113
  247. package/packages/runtime/src/plugins/plugin-registry.ts +232 -124
  248. package/packages/runtime/src/policy/index.ts +1 -1
  249. package/packages/runtime/src/policy/policy-engine.ts +330 -244
  250. package/packages/runtime/src/projection/index.ts +1 -1
  251. package/packages/runtime/src/projection/projection-engine.ts +328 -249
  252. package/packages/runtime/src/reducers/debug-reducer.ts +36 -36
  253. package/packages/runtime/src/reducers/index.ts +2 -2
  254. package/packages/runtime/src/reducers/run-state-reducer.ts +269 -269
  255. package/packages/runtime/src/scheduler/agent-registry.ts +132 -132
  256. package/packages/runtime/src/scheduler/agent-roles.ts +109 -109
  257. package/packages/runtime/src/scheduler/index.ts +4 -4
  258. package/packages/runtime/src/scheduler/multi-agent-coordinator.ts +521 -333
  259. package/packages/runtime/src/scheduler/run-journal.ts +62 -62
  260. package/packages/runtime/src/scheduler/scheduler.ts +722 -441
  261. package/packages/runtime/src/verification/index.ts +2 -2
  262. package/packages/runtime/src/verification/verification-compiler.ts +436 -225
  263. package/packages/runtime/src/verification/verification-manifest.ts +252 -192
  264. package/packages/runtime/src/workspace/index.ts +5 -5
  265. package/packages/runtime/src/workspace/strategies/ephemeral-container.ts +126 -121
  266. package/packages/runtime/src/workspace/strategies/git-worktree.ts +79 -77
  267. package/packages/runtime/src/workspace/strategies/inplace.ts +38 -35
  268. package/packages/runtime/src/workspace/workspace-manager.ts +16 -15
  269. package/packages/runtime/tsconfig.json +17 -17
  270. package/vscode-extension/.vscodeignore +7 -7
  271. package/vscode-extension/oxe-agents-1.0.0.vsix +0 -0
  272. package/vscode-extension/package.json +185 -185
  273. package/vscode-extension/src/extension.js +310 -310
  274. package/vscode-extension/src/shared/contextLoader.js +137 -137
  275. package/vscode-extension/src/shared/contractBuilder.js +159 -159
  276. package/vscode-extension/src/shared/stateReader.js +101 -101
@@ -1,1340 +1,2144 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const { spawnSync } = require('child_process');
6
-
7
- const VALID_RUN_STATUSES = new Set([
8
- 'planned',
9
- 'running',
10
- 'paused',
11
- 'waiting_approval',
12
- 'failed',
13
- 'completed',
14
- 'replaying',
15
- 'aborted',
16
- ]);
17
-
18
- const VALID_APPROVAL_POLICIES = new Set([
19
- 'always_allow',
20
- 'require_approval',
21
- 'require_approval_if_external_side_effect',
22
- 'deny_unless_overridden',
23
- ]);
24
-
25
- const VALID_CAPABILITY_TYPES = new Set(['script', 'mcp', 'automation', 'local']);
26
-
27
- function readTextIfExists(filePath) {
28
- try {
29
- return fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : null;
30
- } catch {
31
- return null;
32
- }
33
- }
34
-
35
- function ensureDir(dirPath) {
36
- fs.mkdirSync(dirPath, { recursive: true });
37
- }
38
-
39
- function ensureDirForFile(filePath) {
40
- ensureDir(path.dirname(filePath));
41
- }
42
-
43
- function parseFrontmatter(text) {
44
- const match = String(text || '').match(/^---\r?\n([\s\S]*?)\r?\n---/);
45
- if (!match) return {};
46
- const out = {};
47
- for (const line of match[1].split(/\r?\n/)) {
48
- const trimmed = line.trim();
49
- if (!trimmed || trimmed.startsWith('#')) continue;
50
- const idx = trimmed.indexOf(':');
51
- if (idx === -1) continue;
52
- const key = trimmed.slice(0, idx).trim();
53
- const value = trimmed.slice(idx + 1).trim();
54
- out[key] = value;
55
- }
56
- return out;
57
- }
58
-
59
- function parseArrayField(value) {
60
- const raw = String(value || '').trim();
61
- if (!raw) return [];
62
- if (raw === '[]') return [];
63
- if (/^\[.*\]$/.test(raw)) {
64
- return raw
65
- .slice(1, -1)
66
- .split(',')
67
- .map((item) => item.trim().replace(/^['"`]|['"`]$/g, ''))
68
- .filter(Boolean);
69
- }
70
- return raw.split(',').map((item) => item.trim()).filter(Boolean);
71
- }
72
-
73
- function loadRuntimeModule() {
74
- try {
75
- return require('../../lib/runtime/index.js');
76
- } catch {
77
- return null;
78
- }
79
- }
80
-
81
- function loadSdkParsers() {
82
- try {
83
- const sdk = require('../../lib/sdk/index.cjs');
84
- if (sdk && typeof sdk.parsePlan === 'function' && typeof sdk.parseSpec === 'function') {
85
- return { parsePlan: sdk.parsePlan, parseSpec: sdk.parseSpec };
86
- }
87
- return null;
88
- } catch {
89
- return null;
90
- }
91
- }
92
-
93
- function loadProjectHealth() {
94
- try {
95
- return require('./oxe-project-health.cjs');
96
- } catch {
97
- return null;
98
- }
99
- }
100
-
101
- function readJsonIfExists(filePath) {
102
- const raw = readTextIfExists(filePath);
103
- if (!raw) return null;
104
- try {
105
- return JSON.parse(raw);
106
- } catch {
107
- return null;
108
- }
109
- }
110
-
111
- function preferExistingPath(primaryPath, fallbackPath) {
112
- if (primaryPath && fs.existsSync(primaryPath)) return primaryPath;
113
- return fallbackPath || primaryPath || null;
114
- }
115
-
116
- function resolveRuntimeArtifactPaths(projectRoot, activeSession) {
117
- const health = loadProjectHealth();
118
- if (!health || typeof health.oxePaths !== 'function' || typeof health.scopedOxePaths !== 'function') {
119
- const oxeDir = path.join(projectRoot, '.oxe');
120
- if (!activeSession) {
121
- return {
122
- state: path.join(oxeDir, 'STATE.md'),
123
- spec: path.join(oxeDir, 'SPEC.md'),
124
- plan: path.join(oxeDir, 'PLAN.md'),
125
- verify: path.join(oxeDir, 'VERIFY.md'),
126
- };
127
- }
128
- const sessionRoot = path.join(oxeDir, ...String(activeSession).split('/'));
129
- return {
130
- state: path.join(oxeDir, 'STATE.md'),
131
- spec: preferExistingPath(path.join(sessionRoot, 'spec', 'SPEC.md'), path.join(oxeDir, 'SPEC.md')),
132
- plan: preferExistingPath(path.join(sessionRoot, 'plan', 'PLAN.md'), path.join(oxeDir, 'PLAN.md')),
133
- verify: preferExistingPath(path.join(sessionRoot, 'verification', 'VERIFY.md'), path.join(oxeDir, 'VERIFY.md')),
134
- };
135
- }
136
- const base = health.oxePaths(projectRoot);
137
- const scoped = health.scopedOxePaths(projectRoot, activeSession || null);
138
- return {
139
- state: base.state,
140
- spec: preferExistingPath(scoped.spec, base.spec),
141
- plan: preferExistingPath(scoped.plan, base.plan),
142
- verify: preferExistingPath(scoped.verify, base.verify),
143
- };
144
- }
145
-
146
- function normalizeRuntimeEventType(type) {
147
- const raw = String(type || '').trim();
148
- if (!raw) return 'RunStarted';
149
- const directMap = {
150
- SessionCreated: 'SessionCreated',
151
- RunStarted: 'RunStarted',
152
- GraphCompiled: 'GraphCompiled',
153
- WorkItemReady: 'WorkItemReady',
154
- WorkspaceAllocated: 'WorkspaceAllocated',
155
- AttemptStarted: 'AttemptStarted',
156
- ToolInvoked: 'ToolInvoked',
157
- ToolCompleted: 'ToolCompleted',
158
- ToolFailed: 'ToolFailed',
159
- EvidenceCollected: 'EvidenceCollected',
160
- PolicyEvaluated: 'PolicyEvaluated',
161
- GateRequested: 'GateRequested',
162
- GateResolved: 'GateResolved',
163
- VerificationStarted: 'VerificationStarted',
164
- VerificationCompleted: 'VerificationCompleted',
165
- RetryScheduled: 'RetryScheduled',
166
- WorkItemCompleted: 'WorkItemCompleted',
167
- WorkItemBlocked: 'WorkItemBlocked',
168
- RunCompleted: 'RunCompleted',
169
- RetroPublished: 'RetroPublished',
170
- LessonPromoted: 'LessonPromoted',
171
- };
172
- if (directMap[raw]) return directMap[raw];
173
- const lower = raw.toLowerCase();
174
- const legacyMap = {
175
- session_created: 'SessionCreated',
176
- run_started: 'RunStarted',
177
- graph_compiled: 'GraphCompiled',
178
- work_item_ready: 'WorkItemReady',
179
- task_ready: 'WorkItemReady',
180
- workspace_allocated: 'WorkspaceAllocated',
181
- attempt_started: 'AttemptStarted',
182
- tool_invoked: 'ToolInvoked',
183
- tool_completed: 'ToolCompleted',
184
- tool_failed: 'ToolFailed',
185
- evidence_collected: 'EvidenceCollected',
186
- policy_evaluated: 'PolicyEvaluated',
187
- gate_requested: 'GateRequested',
188
- gate_resolved: 'GateResolved',
189
- checkpoint_opened: 'GateRequested',
190
- checkpoint_resolved: 'GateResolved',
191
- verification_started: 'VerificationStarted',
192
- verification_completed: 'VerificationCompleted',
193
- verify_complete: 'VerificationCompleted',
194
- retry_scheduled: 'RetryScheduled',
195
- work_item_completed: 'WorkItemCompleted',
196
- task_completed: 'WorkItemCompleted',
197
- work_item_blocked: 'WorkItemBlocked',
198
- task_blocked: 'WorkItemBlocked',
199
- run_completed: 'RunCompleted',
200
- retro_published: 'RetroPublished',
201
- lesson_promoted: 'LessonPromoted',
202
- };
203
- return legacyMap[lower] || raw;
204
- }
205
-
206
- function toRuntimeEventEnvelope(event = {}, activeSession) {
207
- const type = normalizeRuntimeEventType(event.type);
208
- const payload = event.payload && typeof event.payload === 'object' ? { ...event.payload } : {};
209
- const workItemId = event.work_item_id || event.task_id || payload.work_item_id || payload.task_id || null;
210
- const attemptId = event.attempt_id || payload.attempt_id || null;
211
- if (type === 'RunStarted') {
212
- payload.run_id = payload.run_id || event.run_id || null;
213
- payload.session_id = payload.session_id || activeSession || event.session_id || null;
214
- payload.graph_version = payload.graph_version || 'legacy';
215
- payload.started_at = payload.started_at || event.timestamp || new Date().toISOString();
216
- payload.ended_at = payload.ended_at == null ? null : payload.ended_at;
217
- payload.status = payload.status || 'running';
218
- payload.initiator = payload.initiator || 'scheduler';
219
- payload.mode = payload.mode || 'por_onda';
220
- } else if (type === 'RunCompleted') {
221
- payload.run_id = payload.run_id || event.run_id || null;
222
- payload.status = payload.status || 'completed';
223
- } else if (type === 'WorkItemReady') {
224
- payload.work_item_id = payload.work_item_id || workItemId;
225
- payload.title = payload.title || workItemId || 'work-item';
226
- payload.type = payload.type || 'task';
227
- payload.depends_on = Array.isArray(payload.depends_on) ? payload.depends_on : [];
228
- payload.mutation_scope = Array.isArray(payload.mutation_scope) ? payload.mutation_scope : [];
229
- payload.policy_ref = payload.policy_ref || null;
230
- payload.verify_ref = Array.isArray(payload.verify_ref) ? payload.verify_ref : [];
231
- payload.status = payload.status || 'pending';
232
- payload.workspace_strategy = payload.workspace_strategy || 'inplace';
233
- payload.run_id = payload.run_id || event.run_id || null;
234
- } else if (type === 'AttemptStarted') {
235
- payload.attempt_number = payload.attempt_number || 1;
236
- } else if (type === 'WorkspaceAllocated') {
237
- payload.workspace_id = payload.workspace_id || `ws-${workItemId || 'runtime'}`;
238
- payload.strategy = payload.strategy || payload.workspace_strategy || 'inplace';
239
- payload.root_path = payload.root_path || null;
240
- payload.base_commit = payload.base_commit || null;
241
- payload.branch = payload.branch || null;
242
- payload.container_ref = payload.container_ref || null;
243
- payload.status = payload.status || 'ready';
244
- }
245
- return {
246
- id: String(event.event_id || ''),
247
- type,
248
- timestamp: event.timestamp || new Date().toISOString(),
249
- session_id: activeSession || event.session_id || null,
250
- run_id: event.run_id || payload.run_id || null,
251
- work_item_id: workItemId,
252
- attempt_id: attemptId,
253
- causation_id: event.causation_id || payload.causation_id || null,
254
- correlation_id: event.correlation_id || payload.correlation_id || null,
255
- payload,
256
- };
257
- }
258
-
259
- function serializeCanonicalState(state) {
260
- if (!state || typeof state !== 'object') return null;
261
- return {
262
- run: state.run || null,
263
- workItems: Array.from((state.workItems && state.workItems.values()) || []),
264
- attempts: Object.fromEntries(Array.from((state.attempts && state.attempts.entries()) || [])),
265
- workspaces: Array.from((state.workspaces && state.workspaces.values()) || []),
266
- completedWorkItems: Array.from(state.completedWorkItems || []),
267
- failedWorkItems: Array.from(state.failedWorkItems || []),
268
- blockedWorkItems: Array.from(state.blockedWorkItems || []),
269
- summary: {
270
- work_item_count: state.workItems instanceof Map ? state.workItems.size : 0,
271
- attempt_count: state.attempts instanceof Map
272
- ? Array.from(state.attempts.values()).reduce((acc, list) => acc + (Array.isArray(list) ? list.length : 0), 0)
273
- : 0,
274
- workspace_count: state.workspaces instanceof Map ? state.workspaces.size : 0,
275
- completed: state.completedWorkItems instanceof Set ? state.completedWorkItems.size : 0,
276
- failed: state.failedWorkItems instanceof Set ? state.failedWorkItems.size : 0,
277
- blocked: state.blockedWorkItems instanceof Set ? state.blockedWorkItems.size : 0,
278
- },
279
- };
280
- }
281
-
282
- function hydrateCanonicalState(serialized) {
283
- const safe = serialized && typeof serialized === 'object' ? serialized : {};
284
- return {
285
- run: safe.run || null,
286
- workItems: new Map(
287
- Array.isArray(safe.workItems)
288
- ? safe.workItems
289
- .filter((item) => item && item.work_item_id)
290
- .map((item) => [item.work_item_id, item])
291
- : []
292
- ),
293
- attempts: new Map(Object.entries(safe.attempts && typeof safe.attempts === 'object' ? safe.attempts : {})),
294
- workspaces: new Map(
295
- Array.isArray(safe.workspaces)
296
- ? safe.workspaces
297
- .filter((item) => item && item.workspace_id)
298
- .map((item) => [item.workspace_id, item])
299
- : []
300
- ),
301
- completedWorkItems: new Set(Array.isArray(safe.completedWorkItems) ? safe.completedWorkItems : []),
302
- failedWorkItems: new Set(Array.isArray(safe.failedWorkItems) ? safe.failedWorkItems : []),
303
- blockedWorkItems: new Set(Array.isArray(safe.blockedWorkItems) ? safe.blockedWorkItems : []),
304
- };
305
- }
306
-
307
- function mergeCanonicalStateWithRunState(serializedState, runState = {}, activeSession, compiledGraph) {
308
- const live = hydrateCanonicalState(serializedState);
309
- const runId = runState.run_id || (live.run && live.run.run_id) || null;
310
- if (!live.run && runId) {
311
- live.run = {
312
- run_id: runId,
313
- session_id: activeSession || null,
314
- graph_version: (compiledGraph && compiledGraph.metadata && compiledGraph.metadata.plan_hash) || 'legacy',
315
- started_at: runState.created_at || new Date().toISOString(),
316
- ended_at: /completed|failed|aborted|cancelled/.test(String(runState.status || '')) ? (runState.updated_at || null) : null,
317
- status: runState.status || 'planned',
318
- initiator: 'scheduler',
319
- mode: runState.cursor && runState.cursor.mode === 'task'
320
- ? 'por_tarefa'
321
- : runState.cursor && runState.cursor.mode === 'wave'
322
- ? 'por_onda'
323
- : 'completo',
324
- };
325
- }
326
-
327
- const compiledNodes = compiledGraph && compiledGraph.nodes && typeof compiledGraph.nodes === 'object'
328
- ? Object.values(compiledGraph.nodes)
329
- : [];
330
- for (const node of compiledNodes) {
331
- if (!node || !node.id || live.workItems.has(node.id)) continue;
332
- live.workItems.set(node.id, {
333
- work_item_id: node.id,
334
- run_id: runId,
335
- title: node.title || node.id,
336
- type: 'task',
337
- depends_on: Array.isArray(node.depends_on) ? node.depends_on : [],
338
- mutation_scope: Array.isArray(node.mutation_scope) ? node.mutation_scope : [],
339
- policy_ref: node.policy && node.policy.requires_human_approval ? 'human_approval' : null,
340
- verify_ref: node.verify && Array.isArray(node.verify.acceptance_refs) ? node.verify.acceptance_refs : [],
341
- status: 'pending',
342
- workspace_strategy: node.workspace_strategy || 'inplace',
343
- });
344
- }
345
-
346
- const activeTasks = Array.isArray(runState.active_tasks) ? runState.active_tasks.map(String) : [];
347
- for (const taskId of activeTasks) {
348
- const currentItem = live.workItems.get(taskId) || {
349
- work_item_id: taskId,
350
- run_id: runId,
351
- title: taskId,
352
- type: 'task',
353
- depends_on: [],
354
- mutation_scope: [],
355
- policy_ref: null,
356
- verify_ref: [],
357
- status: 'pending',
358
- workspace_strategy: 'inplace',
359
- };
360
- live.workItems.set(taskId, { ...currentItem, status: 'running' });
361
- }
362
-
363
- for (const blockedId of Array.isArray(runState.pending_checkpoints) ? runState.pending_checkpoints.map(String) : []) {
364
- if (!live.blockedWorkItems.has(blockedId)) live.blockedWorkItems.add(blockedId);
365
- }
366
-
367
- if (Array.isArray(runState.failures)) {
368
- for (const failure of runState.failures) {
369
- const ref = failure && typeof failure === 'object'
370
- ? String(failure.work_item_id || failure.task_id || failure.id || '')
371
- : '';
372
- if (ref) {
373
- live.failedWorkItems.add(ref);
374
- const item = live.workItems.get(ref);
375
- if (item) live.workItems.set(ref, { ...item, status: 'failed' });
376
- }
377
- }
378
- }
379
-
380
- if (Array.isArray(runState.evidence) && runState.evidence.length && live.run) {
381
- live.run = { ...live.run, evidence_count: runState.evidence.length };
382
- }
383
-
384
- for (const completedId of live.completedWorkItems) {
385
- const item = live.workItems.get(completedId);
386
- if (item) live.workItems.set(completedId, { ...item, status: 'completed' });
387
- }
388
- for (const failedId of live.failedWorkItems) {
389
- const item = live.workItems.get(failedId);
390
- if (item) live.workItems.set(failedId, { ...item, status: 'failed' });
391
- }
392
- for (const blockedId of live.blockedWorkItems) {
393
- const item = live.workItems.get(blockedId);
394
- if (item) live.workItems.set(blockedId, { ...item, status: 'blocked' });
395
- }
396
- return live;
397
- }
398
-
399
- function reduceCanonicalRunStateLive(projectRoot, activeSession, options = {}) {
400
- const runtime = loadRuntimeModule();
401
- if (!runtime || typeof runtime.reduce !== 'function') return null;
402
- const currentRun = options.runState || readRunState(projectRoot, activeSession) || null;
403
- const runId = options.runId || (currentRun && currentRun.run_id) || null;
404
- let events = readEvents(projectRoot, activeSession);
405
- if (runId) events = events.filter((event) => !event.run_id || event.run_id === runId);
406
- const reduced = runtime.reduce(events.map((event) => toRuntimeEventEnvelope(event, activeSession)));
407
- return mergeCanonicalStateWithRunState(
408
- serializeCanonicalState(reduced),
409
- currentRun || {},
410
- activeSession,
411
- currentRun && currentRun.compiled_graph ? currentRun.compiled_graph : null
412
- );
413
- }
414
-
415
- function reduceCanonicalRunState(projectRoot, activeSession, options = {}) {
416
- return serializeCanonicalState(reduceCanonicalRunStateLive(projectRoot, activeSession, options));
417
- }
418
-
419
- function compileExecutionGraphFromArtifacts(projectRoot, activeSession, options = {}) {
420
- const runtime = loadRuntimeModule();
421
- const parsers = loadSdkParsers();
422
- if (!runtime || typeof runtime.compile !== 'function' || typeof runtime.toSerializable !== 'function') {
423
- throw new Error('Runtime package não está disponível. Rode npm run build:runtime.');
424
- }
425
- if (!parsers) {
426
- throw new Error('Parsers do SDK indisponíveis para compilar o grafo.');
427
- }
428
- const artifactPaths = resolveRuntimeArtifactPaths(projectRoot, activeSession);
429
- const specText = readTextIfExists(artifactPaths.spec);
430
- const planText = readTextIfExists(artifactPaths.plan);
431
- if (!specText) throw new Error(`SPEC.md ausente em ${artifactPaths.spec}`);
432
- if (!planText) throw new Error(`PLAN.md ausente em ${artifactPaths.plan}`);
433
- const parsedSpec = parsers.parseSpec(specText);
434
- const parsedPlan = parsers.parsePlan(planText);
435
- const graph = runtime.compile(parsedPlan, parsedSpec, options.compilerOptions || {});
436
- const validationErrors = typeof runtime.validateGraph === 'function' ? runtime.validateGraph(graph) : [];
437
- const compiledGraph = runtime.toSerializable(graph);
438
- const current = options.runState || readRunState(projectRoot, activeSession) || {};
439
- const runId = current.run_id || makeRunId();
440
- const next = writeRunState(projectRoot, activeSession, {
441
- ...current,
442
- run_id: runId,
443
- status: current.status || 'planned',
444
- compiled_graph: compiledGraph,
445
- graph_version: compiledGraph.metadata && compiledGraph.metadata.plan_hash ? compiledGraph.metadata.plan_hash : 'compiled',
446
- canonical_state: serializeCanonicalState(
447
- mergeCanonicalStateWithRunState(
448
- reduceCanonicalRunState(projectRoot, activeSession, { runState: { ...current, run_id: runId } }),
449
- { ...current, run_id: runId },
450
- activeSession,
451
- compiledGraph
452
- )
453
- ),
454
- projections: current.projections || {},
455
- });
456
- appendEvent(projectRoot, activeSession, {
457
- type: 'GraphCompiled',
458
- run_id: next.run_id,
459
- payload: {
460
- node_count: compiledGraph.metadata && compiledGraph.metadata.node_count || 0,
461
- wave_count: compiledGraph.metadata && compiledGraph.metadata.wave_count || 0,
462
- plan_hash: compiledGraph.metadata && compiledGraph.metadata.plan_hash || null,
463
- spec_hash: compiledGraph.metadata && compiledGraph.metadata.spec_hash || null,
464
- },
465
- });
466
- return {
467
- run: next,
468
- graph: compiledGraph,
469
- validationErrors,
470
- parsedPlan,
471
- parsedSpec,
472
- paths: artifactPaths,
473
- };
474
- }
475
-
476
- function compileVerificationSuiteFromArtifacts(projectRoot, activeSession, options = {}) {
477
- const runtime = loadRuntimeModule();
478
- const parsers = loadSdkParsers();
479
- if (!runtime || typeof runtime.compileVerification !== 'function') {
480
- throw new Error('Runtime package não está disponível. Rode npm run build:runtime.');
481
- }
482
- if (!parsers) {
483
- throw new Error('Parsers do SDK indisponíveis para compilar verification suite.');
484
- }
485
- const artifactPaths = resolveRuntimeArtifactPaths(projectRoot, activeSession);
486
- const specText = readTextIfExists(artifactPaths.spec);
487
- const planText = readTextIfExists(artifactPaths.plan);
488
- if (!specText || !planText) {
489
- throw new Error('SPEC.md e PLAN.md são obrigatórios para compilar a suite de verificação.');
490
- }
491
- const suite = runtime.compileVerification(parsers.parseSpec(specText), parsers.parsePlan(planText), options.compilerOptions || {});
492
- const current = options.runState || readRunState(projectRoot, activeSession) || {};
493
- const runId = current.run_id || makeRunId();
494
- const next = writeRunState(projectRoot, activeSession, {
495
- ...current,
496
- run_id: runId,
497
- status: current.status || 'planned',
498
- verification_suite: suite,
499
- });
500
- appendEvent(projectRoot, activeSession, {
501
- type: 'verification_suite_compiled',
502
- run_id: next.run_id,
503
- payload: {
504
- total_checks: Array.isArray(suite.checks) ? suite.checks.length : 0,
505
- spec_hash: suite.spec_hash || null,
506
- plan_hash: suite.plan_hash || null,
507
- },
508
- });
509
- return { run: next, suite, paths: artifactPaths };
510
- }
511
-
512
- function projectRuntimeArtifacts(projectRoot, activeSession, options = {}) {
513
- const runtime = loadRuntimeModule();
514
- if (!runtime || typeof runtime.ProjectionEngine !== 'function' || typeof runtime.fromSerializable !== 'function') {
515
- throw new Error('Runtime package não está disponível. Rode npm run build:runtime.');
516
- }
517
- let current = options.runState || readRunState(projectRoot, activeSession);
518
- if (!current) {
519
- current = writeRunState(projectRoot, activeSession, {});
520
- }
521
- if (!current.compiled_graph) {
522
- current = compileExecutionGraphFromArtifacts(projectRoot, activeSession, { runState: current }).run;
523
- }
524
- const graph = runtime.fromSerializable(current.compiled_graph);
525
- const canonicalLive = current.canonical_state
526
- ? mergeCanonicalStateWithRunState(current.canonical_state, current, activeSession, current.compiled_graph)
527
- : reduceCanonicalRunStateLive(projectRoot, activeSession, { runState: current });
528
- if (!canonicalLive) {
529
- throw new Error('Não foi possível reconstruir o estado canônico da run.');
530
- }
531
- const canonicalState = serializeCanonicalState(canonicalLive);
532
- const projector = new runtime.ProjectionEngine();
533
- const verificationResults = Array.isArray(current.verification_results) ? current.verification_results : [];
534
- const verificationCheckResults = Array.isArray(current.verification_check_results) ? current.verification_check_results : [];
535
- const projections = {
536
- plan: projector.projectPlan(canonicalLive, graph),
537
- verify: projector.projectVerify(canonicalLive, verificationResults, verificationCheckResults),
538
- state: projector.projectState(canonicalLive),
539
- runSummary: projector.projectRunSummary(canonicalLive),
540
- prSummary: projector.projectPRSummary(canonicalLive, graph),
541
- };
542
- const paths = resolveRuntimeArtifactPaths(projectRoot, activeSession);
543
- const op = operationalPaths(projectRoot, activeSession);
544
- const projectionRefs = {
545
- plan_ref: path.relative(projectRoot, paths.plan).replace(/\\/g, '/'),
546
- verify_ref: path.relative(projectRoot, paths.verify).replace(/\\/g, '/'),
547
- state_ref: path.relative(projectRoot, paths.state).replace(/\\/g, '/'),
548
- run_summary_ref: path.relative(projectRoot, path.join(op.executionRoot, 'RUN-SUMMARY.md')).replace(/\\/g, '/'),
549
- pr_summary_ref: path.relative(projectRoot, path.join(op.executionRoot, 'PR-SUMMARY.md')).replace(/\\/g, '/'),
550
- generated_at: new Date().toISOString(),
551
- };
552
- if (options.write !== false) {
553
- ensureDirForFile(paths.plan);
554
- ensureDirForFile(paths.verify);
555
- ensureDirForFile(paths.state);
556
- fs.writeFileSync(paths.plan, projections.plan + '\n', 'utf8');
557
- fs.writeFileSync(paths.verify, projections.verify + '\n', 'utf8');
558
- fs.writeFileSync(paths.state, projections.state + '\n', 'utf8');
559
- fs.writeFileSync(path.join(op.executionRoot, 'RUN-SUMMARY.md'), projections.runSummary + '\n', 'utf8');
560
- fs.writeFileSync(path.join(op.executionRoot, 'PR-SUMMARY.md'), projections.prSummary + '\n', 'utf8');
561
- }
562
- const next = writeRunState(projectRoot, activeSession, {
563
- ...current,
564
- canonical_state: canonicalState,
565
- projections: projectionRefs,
566
- });
567
- return { run: next, projections, paths: { ...paths, runSummary: path.join(op.executionRoot, 'RUN-SUMMARY.md'), prSummary: path.join(op.executionRoot, 'PR-SUMMARY.md') } };
568
- }
569
-
570
- async function runRuntimeCiChecks(projectRoot, activeSession, options = {}) {
571
- const runtime = loadRuntimeModule();
572
- if (!runtime || typeof runtime.runCIChecks !== 'function' || typeof runtime.summarizeCIResults !== 'function') {
573
- throw new Error('Runtime package não está disponível. Rode npm run build:runtime.');
574
- }
575
- let current = options.runState || readRunState(projectRoot, activeSession);
576
- if (!current) {
577
- current = compileExecutionGraphFromArtifacts(projectRoot, activeSession).run;
578
- }
579
- const runId = options.runId || (current && current.run_id) || null;
580
- const results = await runtime.runCIChecks({
581
- projectRoot,
582
- sessionId: activeSession || null,
583
- runId,
584
- });
585
- const summary = runtime.summarizeCIResults(results);
586
- const next = writeRunState(projectRoot, activeSession, {
587
- ...(current || {}),
588
- run_id: runId || makeRunId(),
589
- status: current && current.status ? current.status : 'planned',
590
- ci_checks: {
591
- generated_at: new Date().toISOString(),
592
- summary,
593
- results,
594
- },
595
- });
596
- appendEvent(projectRoot, activeSession, {
597
- type: 'runtime_ci_completed',
598
- run_id: next.run_id,
599
- payload: {
600
- total: summary.total,
601
- pass: summary.pass,
602
- fail: summary.fail,
603
- skip: summary.skip,
604
- error: summary.error,
605
- allPassed: summary.allPassed,
606
- },
607
- });
608
- return { run: next, runId: next.run_id, results, summary };
609
- }
610
-
611
- function operationalPaths(projectRoot, activeSession) {
612
- const oxeDir = path.join(projectRoot, '.oxe');
613
- const scopeRoot = activeSession ? path.join(oxeDir, ...String(activeSession).split('/')) : oxeDir;
614
- const executionRoot = activeSession ? path.join(scopeRoot, 'execution') : oxeDir;
615
- const runsDir = path.join(executionRoot, 'runs');
616
- return {
617
- oxeDir,
618
- scopeRoot,
619
- executionRoot,
620
- runsDir,
621
- events: path.join(executionRoot, 'OXE-EVENTS.ndjson'),
622
- activeRun: path.join(executionRoot, 'ACTIVE-RUN.json'),
623
- projectLessons: path.join(oxeDir, 'global', 'LESSONS.md'),
624
- sessionManifest: activeSession ? path.join(scopeRoot, 'SESSION.md') : null,
625
- verify: activeSession ? path.join(scopeRoot, 'verification', 'VERIFY.md') : path.join(oxeDir, 'VERIFY.md'),
626
- investigations: activeSession ? path.join(scopeRoot, 'research', 'INVESTIGATIONS.md') : path.join(oxeDir, 'INVESTIGATIONS.md'),
627
- capabilitiesDir: path.join(oxeDir, 'capabilities'),
628
- capabilitiesIndex: path.join(oxeDir, 'CAPABILITIES.md'),
629
- checkpoints: activeSession ? path.join(scopeRoot, 'execution', 'CHECKPOINTS.md') : path.join(oxeDir, 'CHECKPOINTS.md'),
630
- };
631
- }
632
-
633
- function makeRunId() {
634
- return `oxe-run-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
635
- }
636
-
637
- function dedupeNodes(nodes) {
638
- const byId = new Map();
639
- for (const node of nodes || []) {
640
- if (!node || !node.id) continue;
641
- byId.set(node.id, { ...byId.get(node.id), ...node });
642
- }
643
- return Array.from(byId.values());
644
- }
645
-
646
- function dedupeEdges(edges) {
647
- const byKey = new Map();
648
- for (const edge of edges || []) {
649
- if (!edge || !edge.from || !edge.to) continue;
650
- const key = [edge.from, edge.to, edge.type || 'link', edge.label || ''].join('::');
651
- byKey.set(key, { ...byKey.get(key), ...edge });
652
- }
653
- return Array.from(byKey.values());
654
- }
655
-
656
- function buildOperationalGraph(runState = {}) {
657
- const currentWave = runState.current_wave == null ? null : Number(runState.current_wave);
658
- const activeTasks = Array.isArray(runState.active_tasks) ? runState.active_tasks.map(String) : [];
659
- const pendingCheckpoints = Array.isArray(runState.pending_checkpoints) ? runState.pending_checkpoints.map(String) : [];
660
- const azureContext = runState.provider_context && runState.provider_context.azure && typeof runState.provider_context.azure === 'object'
661
- ? runState.provider_context.azure
662
- : null;
663
- const baseNodes = (((runState.graph || {}).nodes) || []).map((node) => ({ ...node }));
664
- const baseEdges = (((runState.graph || {}).edges) || []).map((edge) => ({ ...edge }));
665
- const generatedNodes = [
666
- {
667
- id: `run:${runState.run_id || 'active'}`,
668
- label: runState.run_id || 'active-run',
669
- kind: 'run',
670
- status: runState.status || 'planned',
671
- detail: runState.cursor && runState.cursor.mode ? `mode:${runState.cursor.mode}` : 'run ativo',
672
- },
673
- {
674
- id: 'agent:main-executor',
675
- label: 'main-executor',
676
- kind: 'agent',
677
- status: /running|replaying/.test(String(runState.status || '')) ? 'active' : 'planned',
678
- detail: activeTasks.length ? activeTasks.join(', ') : 'sem tarefas ativas',
679
- },
680
- ];
681
- const generatedEdges = [
682
- {
683
- from: `run:${runState.run_id || 'active'}`,
684
- to: 'agent:main-executor',
685
- type: 'handoff',
686
- status: /running|replaying/.test(String(runState.status || '')) ? 'active' : 'planned',
687
- reason: 'orquestração principal',
688
- },
689
- ];
690
- if (currentWave != null) {
691
- generatedNodes.push({
692
- id: `wave:${currentWave}`,
693
- label: `wave-${currentWave}`,
694
- kind: 'wave',
695
- status: runState.status || 'planned',
696
- detail: activeTasks.length ? `${activeTasks.length} tarefa(s)` : 'sem tarefas ativas',
697
- });
698
- generatedEdges.push({
699
- from: `run:${runState.run_id || 'active'}`,
700
- to: `wave:${currentWave}`,
701
- type: 'contains',
702
- status: 'done',
703
- });
704
- }
705
- for (const taskId of activeTasks) {
706
- generatedNodes.push({
707
- id: `task:${taskId}`,
708
- label: taskId,
709
- kind: 'task',
710
- status: /paused|waiting_approval/.test(String(runState.status || '')) ? 'blocked' : 'active',
711
- detail: currentWave != null ? `wave ${currentWave}` : 'task ativa',
712
- });
713
- generatedEdges.push({
714
- from: currentWave != null ? `wave:${currentWave}` : 'agent:main-executor',
715
- to: `task:${taskId}`,
716
- type: 'executes',
717
- status: /running|replaying/.test(String(runState.status || '')) ? 'active' : 'planned',
718
- });
719
- }
720
- for (const checkpointId of pendingCheckpoints) {
721
- generatedNodes.push({
722
- id: `checkpoint:${checkpointId}`,
723
- label: checkpointId,
724
- kind: 'checkpoint',
725
- status: 'pending_approval',
726
- detail: 'aprovação pendente',
727
- });
728
- generatedEdges.push({
729
- from: activeTasks.length ? `task:${activeTasks[activeTasks.length - 1]}` : (currentWave != null ? `wave:${currentWave}` : `run:${runState.run_id || 'active'}`),
730
- to: `checkpoint:${checkpointId}`,
731
- type: 'gate',
732
- status: 'blocked',
733
- reason: 'checkpoint pendente',
734
- });
735
- }
736
- if (azureContext) {
737
- const azureStatus = azureContext.login_active
738
- ? (azureContext.pending_approval_count > 0 ? 'blocked' : 'active')
739
- : 'warning';
740
- generatedNodes.push({
741
- id: 'provider:azure',
742
- label: 'azure',
743
- kind: 'provider',
744
- status: azureStatus,
745
- detail: azureContext.subscription_name || azureContext.subscription_id || azureContext.cloud || 'contexto azure',
746
- });
747
- generatedEdges.push({
748
- from: `run:${runState.run_id || 'active'}`,
749
- to: 'provider:azure',
750
- type: 'provider',
751
- status: azureStatus,
752
- reason: 'contexto cloud ativo',
753
- });
754
- const lastOperation = azureContext.last_operation && typeof azureContext.last_operation === 'object'
755
- ? azureContext.last_operation
756
- : null;
757
- if (lastOperation) {
758
- const capabilityId = String(lastOperation.capability_id || lastOperation.domain || 'azure-operation');
759
- generatedNodes.push({
760
- id: `capability:${capabilityId}`,
761
- label: capabilityId,
762
- kind: 'capability',
763
- status: String(lastOperation.phase || lastOperation.status || 'planned'),
764
- detail: lastOperation.command_display || lastOperation.operation || 'operação Azure',
765
- });
766
- generatedEdges.push({
767
- from: 'provider:azure',
768
- to: `capability:${capabilityId}`,
769
- type: 'tool_call',
770
- status: String(lastOperation.phase || lastOperation.status || 'planned'),
771
- reason: lastOperation.operation || lastOperation.domain || 'ação Azure',
772
- });
773
- const refs = Array.isArray(lastOperation.resource_refs) ? lastOperation.resource_refs : [];
774
- for (let index = 0; index < refs.length; index += 1) {
775
- const ref = refs[index];
776
- const normalized = ref && typeof ref === 'object' ? ref : { name: String(ref || 'resource') };
777
- const resourceId = normalized.id || normalized.name || normalized.resource_id || normalized.type || `resource-${index + 1}`;
778
- generatedNodes.push({
779
- id: `resource:${resourceId}`,
780
- label: normalized.name || normalized.type || resourceId,
781
- kind: 'resource',
782
- status: String(lastOperation.phase || lastOperation.status || 'planned'),
783
- detail: [normalized.type, normalized.resource_group, normalized.location].filter(Boolean).join(' · ') || azureContext.subscription_name || 'recurso Azure',
784
- });
785
- generatedEdges.push({
786
- from: `capability:${capabilityId}`,
787
- to: `resource:${resourceId}`,
788
- type: 'targets',
789
- status: String(lastOperation.phase || lastOperation.status || 'planned'),
790
- reason: normalized.scope || normalized.kind || 'recurso alvo',
791
- });
792
- }
793
- if (lastOperation.pending_checkpoint_id) {
794
- generatedNodes.push({
795
- id: `checkpoint:${lastOperation.pending_checkpoint_id}`,
796
- label: String(lastOperation.pending_checkpoint_id),
797
- kind: 'checkpoint',
798
- status: 'pending_approval',
799
- detail: 'gate Azure pendente',
800
- });
801
- generatedEdges.push({
802
- from: `capability:${capabilityId}`,
803
- to: `checkpoint:${lastOperation.pending_checkpoint_id}`,
804
- type: 'gate',
805
- status: 'blocked',
806
- reason: 'approval policy do provider Azure',
807
- });
808
- }
809
- }
810
- }
811
- return {
812
- nodes: dedupeNodes([...baseNodes, ...generatedNodes]),
813
- edges: dedupeEdges([...baseEdges, ...generatedEdges]),
814
- };
815
- }
816
-
817
- function appendEvent(projectRoot, activeSession, event = {}) {
818
- const p = operationalPaths(projectRoot, activeSession);
819
- ensureDirForFile(p.events);
820
- const entry = {
821
- event_id: event.event_id || `evt-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
822
- type: String(event.type || 'custom'),
823
- timestamp: event.timestamp || new Date().toISOString(),
824
- run_id: event.run_id || null,
825
- session_id: activeSession || event.session_id || null,
826
- wave_id: event.wave_id || null,
827
- task_id: event.task_id || event.work_item_id || null,
828
- work_item_id: event.work_item_id || event.task_id || null,
829
- attempt_id: event.attempt_id || null,
830
- agent_id: event.agent_id || null,
831
- causation_id: event.causation_id || null,
832
- correlation_id: event.correlation_id || null,
833
- payload: event.payload && typeof event.payload === 'object' ? event.payload : {},
834
- };
835
- fs.appendFileSync(p.events, `${JSON.stringify(entry)}\n`, 'utf8');
836
- return entry;
837
- }
838
-
839
- function readEvents(projectRoot, activeSession) {
840
- const p = operationalPaths(projectRoot, activeSession);
841
- const raw = readTextIfExists(p.events) || '';
842
- return raw
843
- .split(/\r?\n/)
844
- .map((line) => line.trim())
845
- .filter(Boolean)
846
- .map((line) => {
847
- try {
848
- return JSON.parse(line);
849
- } catch {
850
- return null;
851
- }
852
- })
853
- .filter(Boolean);
854
- }
855
-
856
- function summarizeEvents(events) {
857
- const byType = {};
858
- for (const event of events) {
859
- const key = String(event.type || 'custom');
860
- byType[key] = (byType[key] || 0) + 1;
861
- }
862
- return {
863
- total: events.length,
864
- byType,
865
- lastEvent: events.length ? events[events.length - 1] : null,
866
- };
867
- }
868
-
869
- function writeRunState(projectRoot, activeSession, runState = {}) {
870
- const p = operationalPaths(projectRoot, activeSession);
871
- const runId = String(runState.run_id || makeRunId());
872
- const canonicalState = runState.canonical_state ? serializeCanonicalState(hydrateCanonicalState(runState.canonical_state)) : null;
873
- const payload = {
874
- run_id: runId,
875
- status: VALID_RUN_STATUSES.has(String(runState.status || 'planned')) ? String(runState.status || 'planned') : 'planned',
876
- created_at: runState.created_at || new Date().toISOString(),
877
- updated_at: runState.updated_at || new Date().toISOString(),
878
- plan_ref: runState.plan_ref || 'PLAN.md',
879
- session_id: activeSession || null,
880
- current_wave: runState.current_wave == null ? null : Number(runState.current_wave),
881
- cursor: {
882
- wave: runState.cursor && runState.cursor.wave != null ? Number(runState.cursor.wave) : null,
883
- task: runState.cursor && runState.cursor.task ? String(runState.cursor.task) : null,
884
- mode: runState.cursor && runState.cursor.mode ? String(runState.cursor.mode) : null,
885
- },
886
- active_tasks: Array.isArray(runState.active_tasks) ? runState.active_tasks.map(String) : [],
887
- pending_checkpoints: Array.isArray(runState.pending_checkpoints) ? runState.pending_checkpoints.map(String) : [],
888
- retries: Array.isArray(runState.retries) ? runState.retries : [],
889
- failures: Array.isArray(runState.failures) ? runState.failures : [],
890
- evidence: Array.isArray(runState.evidence) ? runState.evidence : [],
891
- provider_context: runState.provider_context && typeof runState.provider_context === 'object'
892
- ? runState.provider_context
893
- : {},
894
- graph: runState.graph && typeof runState.graph === 'object'
895
- ? {
896
- nodes: Array.isArray(runState.graph.nodes) ? runState.graph.nodes : [],
897
- edges: Array.isArray(runState.graph.edges) ? runState.graph.edges : [],
898
- }
899
- : { nodes: [], edges: [] },
900
- compiled_graph: runState.compiled_graph && typeof runState.compiled_graph === 'object'
901
- ? runState.compiled_graph
902
- : null,
903
- graph_version: runState.graph_version || null,
904
- canonical_state: canonicalState,
905
- verification_suite: runState.verification_suite && typeof runState.verification_suite === 'object'
906
- ? runState.verification_suite
907
- : null,
908
- verification_results: Array.isArray(runState.verification_results) ? runState.verification_results : [],
909
- verification_check_results: Array.isArray(runState.verification_check_results) ? runState.verification_check_results : [],
910
- ci_checks: runState.ci_checks && typeof runState.ci_checks === 'object' ? runState.ci_checks : null,
911
- projections: runState.projections && typeof runState.projections === 'object' ? runState.projections : {},
912
- metrics: runState.metrics && typeof runState.metrics === 'object' ? runState.metrics : {},
913
- };
914
- payload.graph = buildOperationalGraph(payload);
915
- ensureDir(p.runsDir);
916
- fs.writeFileSync(path.join(p.runsDir, `${runId}.json`), JSON.stringify(payload, null, 2), 'utf8');
917
- fs.writeFileSync(
918
- p.activeRun,
919
- JSON.stringify(
920
- {
921
- run_id: runId,
922
- status: payload.status,
923
- updated_at: payload.updated_at,
924
- current_wave: payload.current_wave,
925
- cursor: payload.cursor,
926
- provider_context: payload.provider_context,
927
- canonical_state: payload.canonical_state,
928
- compiled_graph: payload.compiled_graph,
929
- graph_version: payload.graph_version,
930
- },
931
- null,
932
- 2
933
- ),
934
- 'utf8'
935
- );
936
- return payload;
937
- }
938
-
939
- function readRunState(projectRoot, activeSession) {
940
- const p = operationalPaths(projectRoot, activeSession);
941
- const activeRaw = readTextIfExists(p.activeRun);
942
- let runId = null;
943
- if (activeRaw) {
944
- try {
945
- runId = JSON.parse(activeRaw).run_id || null;
946
- } catch {
947
- runId = null;
948
- }
949
- }
950
- if (!runId && fs.existsSync(p.runsDir)) {
951
- const files = fs.readdirSync(p.runsDir).filter((name) => name.endsWith('.json')).sort();
952
- if (files.length) runId = files[files.length - 1].replace(/\.json$/i, '');
953
- }
954
- if (!runId) return null;
955
- const filePath = path.join(p.runsDir, `${runId}.json`);
956
- const raw = readTextIfExists(filePath);
957
- if (!raw) return null;
958
- try {
959
- return JSON.parse(raw);
960
- } catch {
961
- return null;
962
- }
963
- }
964
-
965
- function readGitActivity(projectRoot, since) {
966
- if (!projectRoot || !since) return [];
967
- try {
968
- const result = spawnSync(
969
- 'git',
970
- ['log', `--since=${since}`, '--pretty=format:%H %aI'],
971
- { cwd: projectRoot, encoding: 'utf8', timeout: 5000 }
972
- );
973
- if (result.status !== 0 || result.error || !result.stdout) return [];
974
- return result.stdout
975
- .split(/\r?\n/)
976
- .map((line) => line.trim())
977
- .filter(Boolean)
978
- .map((line) => {
979
- const idx = line.indexOf(' ');
980
- return {
981
- hash: idx !== -1 ? line.slice(0, idx) : line,
982
- timestamp: idx !== -1 ? line.slice(idx + 1) : '',
983
- };
984
- });
985
- } catch {
986
- return [];
987
- }
988
- }
989
-
990
- function verifyGitEvidence(runState, projectRoot) {
991
- const warns = [];
992
- if (!runState || !projectRoot || !runState.created_at) return warns;
993
- const commits = readGitActivity(projectRoot, runState.created_at);
994
- if (
995
- commits.length === 0 &&
996
- (String(runState.status || '') === 'completed' ||
997
- (Array.isArray(runState.evidence) && runState.evidence.length > 0))
998
- ) {
999
- warns.push('Nenhum commit git encontrado desde o início do run — confirme se o trabalho foi commitado');
1000
- }
1001
- return warns;
1002
- }
1003
-
1004
- function runtimeStateWarnings(runState, checkpoints = [], projectRoot = null) {
1005
- const warns = [];
1006
- if (!runState) return warns;
1007
- if (!VALID_RUN_STATUSES.has(String(runState.status || ''))) {
1008
- warns.push(`ACTIVE-RUN inválido: status "${runState.status}" fora do contrato`);
1009
- }
1010
- const cursorWave = runState.cursor && runState.cursor.wave != null ? Number(runState.cursor.wave) : null;
1011
- if (cursorWave != null && runState.current_wave != null && Number(cursorWave) !== Number(runState.current_wave)) {
1012
- warns.push('ACTIVE-RUN com cursor de onda divergente do current_wave');
1013
- }
1014
- const pending = new Set((runState.pending_checkpoints || []).map(String));
1015
- for (const cp of checkpoints) {
1016
- if (/pending_approval/i.test(String(cp.status || '')) && !pending.has(String(cp.id))) {
1017
- warns.push(`Checkpoint ${cp.id} pendente no índice, mas ausente do ACTIVE-RUN`);
1018
- }
1019
- }
1020
- for (const edge of (((runState.graph || {}).edges) || [])) {
1021
- if (edge.type === 'handoff' && (!edge.from || !edge.to)) {
1022
- warns.push('Grafo operacional contém handoff sem origem ou destino');
1023
- break;
1024
- }
1025
- }
1026
- if (projectRoot) {
1027
- for (const w of verifyGitEvidence(runState, projectRoot)) {
1028
- warns.push(w);
1029
- }
1030
- }
1031
- return warns;
1032
- }
1033
-
1034
- function parseCapabilityManifest(text) {
1035
- const fm = parseFrontmatter(text);
1036
- const approval = String(fm.approval_policy || fm.policy || '').trim() || null;
1037
- const sideEffects = parseArrayField(fm.side_effects || '');
1038
- const envs = parseArrayField(fm.requires_env || '');
1039
- const evidence = parseArrayField(fm.evidence_outputs || '');
1040
- const sessionCompat = parseArrayField(fm.session_compatibility || fm.session_scope || '');
1041
- const objectiveMatch = String(text || '').match(/##\s*Objetivo\s*\n+([\s\S]*?)(?=\n##\s|\n#[^\#]|$)/i);
1042
- const desc = objectiveMatch ? objectiveMatch[1].split(/\r?\n/).map((line) => line.replace(/^-\s+/, '').trim()).filter(Boolean)[0] || '' : '';
1043
- return {
1044
- id: String(fm.id || '').trim(),
1045
- version: String(fm.version || '').trim() || '1',
1046
- type: String(fm.type || 'local').trim(),
1047
- status: String(fm.status || 'active').trim(),
1048
- scope: String(fm.scope || 'mixed').trim(),
1049
- entrypoint: String(fm.entrypoint || '').trim(),
1050
- approvalPolicy: approval,
1051
- sideEffects,
1052
- requiresEnv: envs,
1053
- evidenceOutputs: evidence,
1054
- sessionCompatibility: sessionCompat,
1055
- description: desc || 'Capability local do projeto',
1056
- };
1057
- }
1058
-
1059
- function readCapabilityCatalog(projectRoot) {
1060
- const p = operationalPaths(projectRoot, null);
1061
- if (!fs.existsSync(p.capabilitiesDir)) return [];
1062
- return fs
1063
- .readdirSync(p.capabilitiesDir, { withFileTypes: true })
1064
- .filter((entry) => entry.isDirectory())
1065
- .map((entry) => {
1066
- const manifestPath = path.join(p.capabilitiesDir, entry.name, 'CAPABILITY.md');
1067
- const raw = readTextIfExists(manifestPath);
1068
- if (!raw) return null;
1069
- return { ...parseCapabilityManifest(raw), manifestPath };
1070
- })
1071
- .filter(Boolean)
1072
- .sort((a, b) => a.id.localeCompare(b.id));
1073
- }
1074
-
1075
- function capabilityCatalogWarnings(projectRoot) {
1076
- const warns = [];
1077
- for (const cap of readCapabilityCatalog(projectRoot)) {
1078
- if (!cap.id) warns.push(`Capability em ${cap.manifestPath} sem id`);
1079
- if (!VALID_CAPABILITY_TYPES.has(cap.type)) warns.push(`Capability ${cap.id || cap.manifestPath}: type "${cap.type}" inválido`);
1080
- if (!cap.approvalPolicy || !VALID_APPROVAL_POLICIES.has(cap.approvalPolicy)) {
1081
- warns.push(`Capability ${cap.id || cap.manifestPath}: approval_policy ausente ou inválida`);
1082
- }
1083
- if (!cap.entrypoint) warns.push(`Capability ${cap.id || cap.manifestPath}: entrypoint ausente`);
1084
- if (!cap.evidenceOutputs.length) warns.push(`Capability ${cap.id || cap.manifestPath}: evidence_outputs ausente`);
1085
- }
1086
- return warns;
1087
- }
1088
-
1089
- function buildMemoryLayers(projectRoot, activeSession) {
1090
- const p = operationalPaths(projectRoot, activeSession);
1091
- return {
1092
- readOrder: ['runtime_state', 'session_memory', 'project_memory', 'evidence'],
1093
- runtime_state: {
1094
- source: activeSession ? path.join('.oxe', activeSession, 'execution', 'STATE.md') : '.oxe/STATE.md + ACTIVE-RUN.json',
1095
- exists: fs.existsSync(activeSession ? path.join(projectRoot, '.oxe', activeSession, 'execution', 'STATE.md') : path.join(projectRoot, '.oxe', 'STATE.md')),
1096
- },
1097
- session_memory: {
1098
- source: activeSession ? path.join('.oxe', activeSession, 'SESSION.md') : null,
1099
- exists: Boolean(p.sessionManifest && fs.existsSync(p.sessionManifest)),
1100
- },
1101
- project_memory: {
1102
- source: '.oxe/global/LESSONS.md',
1103
- exists: fs.existsSync(p.projectLessons),
1104
- },
1105
- evidence: {
1106
- source: [
1107
- activeSession ? path.join('.oxe', activeSession, 'research', 'INVESTIGATIONS.md') : '.oxe/INVESTIGATIONS.md',
1108
- activeSession ? path.join('.oxe', activeSession, 'verification', 'VERIFY.md') : '.oxe/VERIFY.md',
1109
- ],
1110
- exists: fs.existsSync(p.investigations) || fs.existsSync(p.verify),
1111
- },
1112
- };
1113
- }
1114
-
1115
- function applyRuntimeAction(projectRoot, activeSession, input = {}) {
1116
- const action = String(input.action || 'status').toLowerCase();
1117
- const now = new Date().toISOString();
1118
- const current = readRunState(projectRoot, activeSession);
1119
- const wave = input.wave == null || input.wave === '' ? null : Number(input.wave);
1120
- const task = input.task ? String(input.task) : null;
1121
- const mode = input.mode ? String(input.mode) : (task ? 'task' : wave != null ? 'wave' : 'complete');
1122
- const pendingCheckpoints = Array.isArray(input.pending_checkpoints)
1123
- ? input.pending_checkpoints.map(String)
1124
- : current && Array.isArray(current.pending_checkpoints)
1125
- ? current.pending_checkpoints
1126
- : [];
1127
-
1128
- if (action === 'start') {
1129
- const next = writeRunState(projectRoot, activeSession, {
1130
- run_id: input.run_id || makeRunId(),
1131
- status: 'running',
1132
- created_at: now,
1133
- updated_at: now,
1134
- plan_ref: input.plan_ref || 'PLAN.md',
1135
- current_wave: wave,
1136
- cursor: { wave, task, mode },
1137
- active_tasks: task ? [task] : Array.isArray(input.active_tasks) ? input.active_tasks.map(String) : [],
1138
- pending_checkpoints: pendingCheckpoints,
1139
- retries: [],
1140
- failures: [],
1141
- evidence: [],
1142
- metrics: {
1143
- transitions: 1,
1144
- pause_count: 0,
1145
- replay_count: 0,
1146
- last_action: 'start',
1147
- },
1148
- });
1149
- appendEvent(projectRoot, activeSession, {
1150
- type: 'run_started',
1151
- run_id: next.run_id,
1152
- wave_id: wave != null ? `wave-${wave}` : null,
1153
- task_id: task,
1154
- payload: { reason: input.reason || 'run inicializado', mode },
1155
- });
1156
- return next;
1157
- }
1158
-
1159
- if (!current) {
1160
- throw new Error('Nenhum ACTIVE-RUN disponível para esta ação.');
1161
- }
1162
-
1163
- const next = {
1164
- ...current,
1165
- updated_at: now,
1166
- current_wave: wave != null ? wave : current.current_wave,
1167
- cursor: {
1168
- wave: wave != null ? wave : current.cursor && current.cursor.wave != null ? current.cursor.wave : current.current_wave,
1169
- task: task || (current.cursor ? current.cursor.task : null),
1170
- mode: input.mode ? String(input.mode) : (current.cursor && current.cursor.mode) || mode,
1171
- },
1172
- active_tasks: task ? [task] : Array.isArray(input.active_tasks) ? input.active_tasks.map(String) : current.active_tasks || [],
1173
- pending_checkpoints: pendingCheckpoints,
1174
- metrics: {
1175
- ...(current.metrics || {}),
1176
- transitions: Number((current.metrics || {}).transitions || 0) + 1,
1177
- last_action: action,
1178
- },
1179
- };
1180
-
1181
- if (action === 'pause') {
1182
- next.status = 'paused';
1183
- next.metrics.pause_count = Number((current.metrics || {}).pause_count || 0) + 1;
1184
- } else if (action === 'resume') {
1185
- next.status = pendingCheckpoints.length ? 'waiting_approval' : 'running';
1186
- } else if (action === 'replay') {
1187
- next.status = 'replaying';
1188
- next.metrics.replay_count = Number((current.metrics || {}).replay_count || 0) + 1;
1189
- } else {
1190
- throw new Error(`Ação de runtime desconhecida: ${action}`);
1191
- }
1192
-
1193
- const saved = writeRunState(projectRoot, activeSession, next);
1194
- appendEvent(projectRoot, activeSession, {
1195
- type: action === 'pause' ? 'run_paused' : action === 'resume' ? 'run_resumed' : 'run_replay_requested',
1196
- run_id: saved.run_id,
1197
- wave_id: saved.current_wave != null ? `wave-${saved.current_wave}` : null,
1198
- task_id: saved.cursor && saved.cursor.task ? saved.cursor.task : null,
1199
- payload: {
1200
- reason: input.reason || '',
1201
- mode: saved.cursor && saved.cursor.mode ? saved.cursor.mode : null,
1202
- pending_checkpoints: saved.pending_checkpoints || [],
1203
- },
1204
- });
1205
- return saved;
1206
- }
1207
-
1208
- /**
1209
- * Reconstrói a timeline de eventos de OXE-EVENTS.ndjson com deltas entre transições.
1210
- * @param {string} projectRoot
1211
- * @param {string|null} activeSession
1212
- * @param {{ fromEventId?: string, runId?: string, waveId?: number, limit?: number, writeReport?: boolean }} [options]
1213
- */
1214
- function replayEvents(projectRoot, activeSession, options = {}) {
1215
- let events = readEvents(projectRoot, activeSession);
1216
-
1217
- if (options.runId) {
1218
- events = events.filter((e) => e.run_id === options.runId);
1219
- }
1220
- if (options.fromEventId) {
1221
- const idx = events.findIndex((e) => e.event_id === options.fromEventId);
1222
- if (idx >= 0) events = events.slice(idx);
1223
- }
1224
- if (options.waveId != null) {
1225
- const waveTag = `wave-${options.waveId}`;
1226
- events = events.filter((e) => e.wave_id === waveTag || String(e.wave_id) === String(options.waveId));
1227
- }
1228
- if (options.limit && options.limit > 0) {
1229
- events = events.slice(0, options.limit);
1230
- }
1231
-
1232
- // Ordenar por timestamp
1233
- events.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
1234
-
1235
- // Calcular deltas
1236
- for (let i = 0; i < events.length; i++) {
1237
- if (i === 0) {
1238
- events[i]._delta_ms = 0;
1239
- } else {
1240
- const prev = new Date(events[i - 1].timestamp).getTime();
1241
- const curr = new Date(events[i].timestamp).getTime();
1242
- events[i]._delta_ms = curr - prev;
1243
- }
1244
- }
1245
-
1246
- const firstTs = events.length ? new Date(events[0].timestamp).getTime() : null;
1247
- const lastTs = events.length ? new Date(events[events.length - 1].timestamp).getTime() : null;
1248
- const duration_ms = firstTs != null && lastTs != null ? lastTs - firstTs : null;
1249
-
1250
- const waveIds = [...new Set(
1251
- events
1252
- .filter((e) => e.wave_id != null)
1253
- .map((e) => {
1254
- const m = String(e.wave_id).match(/^wave-(\d+)$/);
1255
- return m ? Number(m[1]) : null;
1256
- })
1257
- .filter((n) => n != null)
1258
- )].sort((a, b) => a - b);
1259
-
1260
- const taskSequence = [...new Set(events.filter((e) => e.task_id || e.work_item_id).map((e) => e.task_id || e.work_item_id))];
1261
- const checkpointSequence = events
1262
- .filter((e) => String(e.type).includes('checkpoint'))
1263
- .map((e) => e.event_id);
1264
- const failureEvents = events.filter((e) =>
1265
- String(e.type).includes('fail') || String(e.type).includes('error')
1266
- );
1267
-
1268
- const report = {
1269
- events,
1270
- totalEvents: events.length,
1271
- duration_ms,
1272
- runId: options.runId || (events.length ? events[0].run_id : null),
1273
- waveIds,
1274
- taskSequence,
1275
- checkpointSequence,
1276
- failureEvents,
1277
- };
1278
-
1279
- if (options.writeReport) {
1280
- const p = operationalPaths(projectRoot, activeSession);
1281
- const scopeRoot = path.dirname(p.events);
1282
- const reportPath = path.join(scopeRoot, 'REPLAY-SESSION.md');
1283
- const lines = [
1284
- '# OXE — Replay Session',
1285
- '',
1286
- `- **Data:** ${new Date().toISOString().slice(0, 10)}`,
1287
- `- **Run:** ${report.runId || '—'}`,
1288
- `- **Total eventos:** ${report.totalEvents}`,
1289
- `- **Duração:** ${report.duration_ms != null ? `${(report.duration_ms / 1000).toFixed(1)}s` : ''}`,
1290
- `- **Ondas:** ${report.waveIds.join(', ') || '—'}`,
1291
- `- **Tarefas:** ${report.taskSequence.join(', ') || '—'}`,
1292
- `- **Falhas:** ${report.failureEvents.length}`,
1293
- '',
1294
- '## Timeline',
1295
- '',
1296
- '| # | Tipo | Wave | Task | Delta | Timestamp |',
1297
- '|---|------|------|------|-------|-----------|',
1298
- ];
1299
- for (let i = 0; i < events.length; i++) {
1300
- const e = events[i];
1301
- const delta = i > 0 ? `+${(e._delta_ms / 1000).toFixed(1)}s` : '—';
1302
- lines.push(`| ${i + 1} | ${e.type} | ${e.wave_id || ''} | ${e.task_id || e.work_item_id || ''} | ${delta} | ${e.timestamp} |`);
1303
- }
1304
- ensureDirForFile(reportPath);
1305
- fs.writeFileSync(reportPath, lines.join('\n') + '\n', 'utf8');
1306
- report._reportPath = reportPath;
1307
- }
1308
-
1309
- return report;
1310
- }
1311
-
1312
- module.exports = {
1313
- VALID_RUN_STATUSES,
1314
- VALID_APPROVAL_POLICIES,
1315
- VALID_CAPABILITY_TYPES,
1316
- operationalPaths,
1317
- makeRunId,
1318
- appendEvent,
1319
- readEvents,
1320
- summarizeEvents,
1321
- writeRunState,
1322
- readRunState,
1323
- readGitActivity,
1324
- verifyGitEvidence,
1325
- runtimeStateWarnings,
1326
- parseCapabilityManifest,
1327
- readCapabilityCatalog,
1328
- capabilityCatalogWarnings,
1329
- buildMemoryLayers,
1330
- buildOperationalGraph,
1331
- serializeCanonicalState,
1332
- hydrateCanonicalState,
1333
- reduceCanonicalRunState,
1334
- compileExecutionGraphFromArtifacts,
1335
- compileVerificationSuiteFromArtifacts,
1336
- projectRuntimeArtifacts,
1337
- runRuntimeCiChecks,
1338
- applyRuntimeAction,
1339
- replayEvents,
1340
- };
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { spawnSync } = require('child_process');
6
+
7
+ const VALID_RUN_STATUSES = new Set([
8
+ 'planned',
9
+ 'running',
10
+ 'paused',
11
+ 'waiting_approval',
12
+ 'blocked',
13
+ 'failed',
14
+ 'completed',
15
+ 'replaying',
16
+ 'aborted',
17
+ ]);
18
+
19
+ const VALID_APPROVAL_POLICIES = new Set([
20
+ 'always_allow',
21
+ 'require_approval',
22
+ 'require_approval_if_external_side_effect',
23
+ 'deny_unless_overridden',
24
+ ]);
25
+
26
+ const VALID_CAPABILITY_TYPES = new Set(['script', 'mcp', 'automation', 'local']);
27
+
28
+ function readTextIfExists(filePath) {
29
+ try {
30
+ return fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : null;
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+
36
+ function ensureDir(dirPath) {
37
+ fs.mkdirSync(dirPath, { recursive: true });
38
+ }
39
+
40
+ function ensureDirForFile(filePath) {
41
+ ensureDir(path.dirname(filePath));
42
+ }
43
+
44
+ function parseFrontmatter(text) {
45
+ const match = String(text || '').match(/^---\r?\n([\s\S]*?)\r?\n---/);
46
+ if (!match) return {};
47
+ const out = {};
48
+ for (const line of match[1].split(/\r?\n/)) {
49
+ const trimmed = line.trim();
50
+ if (!trimmed || trimmed.startsWith('#')) continue;
51
+ const idx = trimmed.indexOf(':');
52
+ if (idx === -1) continue;
53
+ const key = trimmed.slice(0, idx).trim();
54
+ const value = trimmed.slice(idx + 1).trim();
55
+ out[key] = value;
56
+ }
57
+ return out;
58
+ }
59
+
60
+ function parseArrayField(value) {
61
+ const raw = String(value || '').trim();
62
+ if (!raw) return [];
63
+ if (raw === '[]') return [];
64
+ if (/^\[.*\]$/.test(raw)) {
65
+ return raw
66
+ .slice(1, -1)
67
+ .split(',')
68
+ .map((item) => item.trim().replace(/^['"`]|['"`]$/g, ''))
69
+ .filter(Boolean);
70
+ }
71
+ return raw.split(',').map((item) => item.trim()).filter(Boolean);
72
+ }
73
+
74
+ function loadRuntimeModule() {
75
+ try {
76
+ return require('../../lib/runtime/index.js');
77
+ } catch {
78
+ return null;
79
+ }
80
+ }
81
+
82
+ function buildRuntimePluginRegistry(projectRoot) {
83
+ const runtime = loadRuntimeModule();
84
+ if (!runtime || typeof runtime.PluginRegistry !== 'function') return null;
85
+ const registry = new runtime.PluginRegistry();
86
+ const pluginDir = path.join(projectRoot, '.oxe', 'plugins');
87
+ if (fs.existsSync(pluginDir) && typeof registry.loadFromDirectory === 'function') {
88
+ registry.loadFromDirectory(pluginDir);
89
+ }
90
+ if (typeof registry.registerProjectCapabilities === 'function') {
91
+ registry.registerProjectCapabilities(projectRoot);
92
+ }
93
+ return registry;
94
+ }
95
+
96
+ function loadSdkParsers() {
97
+ try {
98
+ const sdk = require('../../lib/sdk/index.cjs');
99
+ if (sdk && typeof sdk.parsePlan === 'function' && typeof sdk.parseSpec === 'function') {
100
+ return { parsePlan: sdk.parsePlan, parseSpec: sdk.parseSpec };
101
+ }
102
+ return null;
103
+ } catch {
104
+ return null;
105
+ }
106
+ }
107
+
108
+ function loadProjectHealth() {
109
+ try {
110
+ return require('./oxe-project-health.cjs');
111
+ } catch {
112
+ return null;
113
+ }
114
+ }
115
+
116
+ function readJsonIfExists(filePath) {
117
+ const raw = readTextIfExists(filePath);
118
+ if (!raw) return null;
119
+ try {
120
+ return JSON.parse(raw);
121
+ } catch {
122
+ return null;
123
+ }
124
+ }
125
+
126
+ function preferExistingPath(primaryPath, fallbackPath) {
127
+ if (primaryPath && fs.existsSync(primaryPath)) return primaryPath;
128
+ return fallbackPath || primaryPath || null;
129
+ }
130
+
131
+ function resolveRuntimeArtifactPaths(projectRoot, activeSession) {
132
+ const health = loadProjectHealth();
133
+ if (!health || typeof health.oxePaths !== 'function' || typeof health.scopedOxePaths !== 'function') {
134
+ const oxeDir = path.join(projectRoot, '.oxe');
135
+ if (!activeSession) {
136
+ return {
137
+ state: path.join(oxeDir, 'STATE.md'),
138
+ spec: path.join(oxeDir, 'SPEC.md'),
139
+ plan: path.join(oxeDir, 'PLAN.md'),
140
+ verify: path.join(oxeDir, 'VERIFY.md'),
141
+ };
142
+ }
143
+ const sessionRoot = path.join(oxeDir, ...String(activeSession).split('/'));
144
+ return {
145
+ state: path.join(oxeDir, 'STATE.md'),
146
+ spec: preferExistingPath(path.join(sessionRoot, 'spec', 'SPEC.md'), path.join(oxeDir, 'SPEC.md')),
147
+ plan: preferExistingPath(path.join(sessionRoot, 'plan', 'PLAN.md'), path.join(oxeDir, 'PLAN.md')),
148
+ verify: preferExistingPath(path.join(sessionRoot, 'verification', 'VERIFY.md'), path.join(oxeDir, 'VERIFY.md')),
149
+ };
150
+ }
151
+ const base = health.oxePaths(projectRoot);
152
+ const scoped = health.scopedOxePaths(projectRoot, activeSession || null);
153
+ return {
154
+ state: base.state,
155
+ spec: preferExistingPath(scoped.spec, base.spec),
156
+ plan: preferExistingPath(scoped.plan, base.plan),
157
+ verify: preferExistingPath(scoped.verify, base.verify),
158
+ };
159
+ }
160
+
161
+ function normalizeRuntimeEventType(type) {
162
+ const raw = String(type || '').trim();
163
+ if (!raw) return 'RunStarted';
164
+ const directMap = {
165
+ SessionCreated: 'SessionCreated',
166
+ RunStarted: 'RunStarted',
167
+ GraphCompiled: 'GraphCompiled',
168
+ WorkItemReady: 'WorkItemReady',
169
+ WorkspaceAllocated: 'WorkspaceAllocated',
170
+ AttemptStarted: 'AttemptStarted',
171
+ ToolInvoked: 'ToolInvoked',
172
+ ToolCompleted: 'ToolCompleted',
173
+ ToolFailed: 'ToolFailed',
174
+ EvidenceCollected: 'EvidenceCollected',
175
+ PolicyEvaluated: 'PolicyEvaluated',
176
+ GateRequested: 'GateRequested',
177
+ GateResolved: 'GateResolved',
178
+ VerificationStarted: 'VerificationStarted',
179
+ VerificationCompleted: 'VerificationCompleted',
180
+ RetryScheduled: 'RetryScheduled',
181
+ WorkItemCompleted: 'WorkItemCompleted',
182
+ WorkItemBlocked: 'WorkItemBlocked',
183
+ RunCompleted: 'RunCompleted',
184
+ RetroPublished: 'RetroPublished',
185
+ LessonPromoted: 'LessonPromoted',
186
+ };
187
+ if (directMap[raw]) return directMap[raw];
188
+ const lower = raw.toLowerCase();
189
+ const legacyMap = {
190
+ session_created: 'SessionCreated',
191
+ run_started: 'RunStarted',
192
+ graph_compiled: 'GraphCompiled',
193
+ work_item_ready: 'WorkItemReady',
194
+ task_ready: 'WorkItemReady',
195
+ workspace_allocated: 'WorkspaceAllocated',
196
+ attempt_started: 'AttemptStarted',
197
+ tool_invoked: 'ToolInvoked',
198
+ tool_completed: 'ToolCompleted',
199
+ tool_failed: 'ToolFailed',
200
+ evidence_collected: 'EvidenceCollected',
201
+ policy_evaluated: 'PolicyEvaluated',
202
+ gate_requested: 'GateRequested',
203
+ gate_resolved: 'GateResolved',
204
+ checkpoint_opened: 'GateRequested',
205
+ checkpoint_resolved: 'GateResolved',
206
+ verification_started: 'VerificationStarted',
207
+ verification_completed: 'VerificationCompleted',
208
+ verify_complete: 'VerificationCompleted',
209
+ retry_scheduled: 'RetryScheduled',
210
+ work_item_completed: 'WorkItemCompleted',
211
+ task_completed: 'WorkItemCompleted',
212
+ work_item_blocked: 'WorkItemBlocked',
213
+ task_blocked: 'WorkItemBlocked',
214
+ run_completed: 'RunCompleted',
215
+ retro_published: 'RetroPublished',
216
+ lesson_promoted: 'LessonPromoted',
217
+ };
218
+ return legacyMap[lower] || raw;
219
+ }
220
+
221
+ function toRuntimeEventEnvelope(event = {}, activeSession) {
222
+ const type = normalizeRuntimeEventType(event.type);
223
+ const payload = event.payload && typeof event.payload === 'object' ? { ...event.payload } : {};
224
+ const workItemId = event.work_item_id || event.task_id || payload.work_item_id || payload.task_id || null;
225
+ const attemptId = event.attempt_id || payload.attempt_id || null;
226
+ if (type === 'RunStarted') {
227
+ payload.run_id = payload.run_id || event.run_id || null;
228
+ payload.session_id = payload.session_id || activeSession || event.session_id || null;
229
+ payload.graph_version = payload.graph_version || 'legacy';
230
+ payload.started_at = payload.started_at || event.timestamp || new Date().toISOString();
231
+ payload.ended_at = payload.ended_at == null ? null : payload.ended_at;
232
+ payload.status = payload.status || 'running';
233
+ payload.initiator = payload.initiator || 'scheduler';
234
+ payload.mode = payload.mode || 'por_onda';
235
+ } else if (type === 'RunCompleted') {
236
+ payload.run_id = payload.run_id || event.run_id || null;
237
+ payload.status = payload.status || 'completed';
238
+ } else if (type === 'WorkItemReady') {
239
+ payload.work_item_id = payload.work_item_id || workItemId;
240
+ payload.title = payload.title || workItemId || 'work-item';
241
+ payload.type = payload.type || 'task';
242
+ payload.depends_on = Array.isArray(payload.depends_on) ? payload.depends_on : [];
243
+ payload.mutation_scope = Array.isArray(payload.mutation_scope) ? payload.mutation_scope : [];
244
+ payload.policy_ref = payload.policy_ref || null;
245
+ payload.verify_ref = Array.isArray(payload.verify_ref) ? payload.verify_ref : [];
246
+ payload.status = payload.status || 'pending';
247
+ payload.workspace_strategy = payload.workspace_strategy || 'inplace';
248
+ payload.run_id = payload.run_id || event.run_id || null;
249
+ } else if (type === 'AttemptStarted') {
250
+ payload.attempt_number = payload.attempt_number || 1;
251
+ } else if (type === 'WorkspaceAllocated') {
252
+ payload.workspace_id = payload.workspace_id || `ws-${workItemId || 'runtime'}`;
253
+ payload.strategy = payload.strategy || payload.workspace_strategy || 'inplace';
254
+ payload.root_path = payload.root_path || null;
255
+ payload.base_commit = payload.base_commit || null;
256
+ payload.branch = payload.branch || null;
257
+ payload.container_ref = payload.container_ref || null;
258
+ payload.status = payload.status || 'ready';
259
+ }
260
+ return {
261
+ id: String(event.event_id || ''),
262
+ type,
263
+ timestamp: event.timestamp || new Date().toISOString(),
264
+ session_id: activeSession || event.session_id || null,
265
+ run_id: event.run_id || payload.run_id || null,
266
+ work_item_id: workItemId,
267
+ attempt_id: attemptId,
268
+ causation_id: event.causation_id || payload.causation_id || null,
269
+ correlation_id: event.correlation_id || payload.correlation_id || null,
270
+ payload,
271
+ };
272
+ }
273
+
274
+ function serializeCanonicalState(state) {
275
+ if (!state || typeof state !== 'object') return null;
276
+ return {
277
+ run: state.run || null,
278
+ workItems: Array.from((state.workItems && state.workItems.values()) || []),
279
+ attempts: Object.fromEntries(Array.from((state.attempts && state.attempts.entries()) || [])),
280
+ workspaces: Array.from((state.workspaces && state.workspaces.values()) || []),
281
+ completedWorkItems: Array.from(state.completedWorkItems || []),
282
+ failedWorkItems: Array.from(state.failedWorkItems || []),
283
+ blockedWorkItems: Array.from(state.blockedWorkItems || []),
284
+ summary: {
285
+ work_item_count: state.workItems instanceof Map ? state.workItems.size : 0,
286
+ attempt_count: state.attempts instanceof Map
287
+ ? Array.from(state.attempts.values()).reduce((acc, list) => acc + (Array.isArray(list) ? list.length : 0), 0)
288
+ : 0,
289
+ workspace_count: state.workspaces instanceof Map ? state.workspaces.size : 0,
290
+ completed: state.completedWorkItems instanceof Set ? state.completedWorkItems.size : 0,
291
+ failed: state.failedWorkItems instanceof Set ? state.failedWorkItems.size : 0,
292
+ blocked: state.blockedWorkItems instanceof Set ? state.blockedWorkItems.size : 0,
293
+ },
294
+ };
295
+ }
296
+
297
+ function hydrateCanonicalState(serialized) {
298
+ const safe = serialized && typeof serialized === 'object' ? serialized : {};
299
+ return {
300
+ run: safe.run || null,
301
+ workItems: new Map(
302
+ Array.isArray(safe.workItems)
303
+ ? safe.workItems
304
+ .filter((item) => item && item.work_item_id)
305
+ .map((item) => [item.work_item_id, item])
306
+ : []
307
+ ),
308
+ attempts: new Map(Object.entries(safe.attempts && typeof safe.attempts === 'object' ? safe.attempts : {})),
309
+ workspaces: new Map(
310
+ Array.isArray(safe.workspaces)
311
+ ? safe.workspaces
312
+ .filter((item) => item && item.workspace_id)
313
+ .map((item) => [item.workspace_id, item])
314
+ : []
315
+ ),
316
+ completedWorkItems: new Set(Array.isArray(safe.completedWorkItems) ? safe.completedWorkItems : []),
317
+ failedWorkItems: new Set(Array.isArray(safe.failedWorkItems) ? safe.failedWorkItems : []),
318
+ blockedWorkItems: new Set(Array.isArray(safe.blockedWorkItems) ? safe.blockedWorkItems : []),
319
+ };
320
+ }
321
+
322
+ function mergeCanonicalStateWithRunState(serializedState, runState = {}, activeSession, compiledGraph) {
323
+ const live = hydrateCanonicalState(serializedState);
324
+ const runId = runState.run_id || (live.run && live.run.run_id) || null;
325
+ if (!live.run && runId) {
326
+ live.run = {
327
+ run_id: runId,
328
+ session_id: activeSession || null,
329
+ graph_version: (compiledGraph && compiledGraph.metadata && compiledGraph.metadata.plan_hash) || 'legacy',
330
+ started_at: runState.created_at || new Date().toISOString(),
331
+ ended_at: /completed|failed|blocked|aborted|cancelled/.test(String(runState.status || '')) ? (runState.updated_at || null) : null,
332
+ status: runState.status || 'planned',
333
+ initiator: 'scheduler',
334
+ mode: runState.cursor && runState.cursor.mode === 'task'
335
+ ? 'por_tarefa'
336
+ : runState.cursor && runState.cursor.mode === 'wave'
337
+ ? 'por_onda'
338
+ : 'completo',
339
+ };
340
+ }
341
+
342
+ const compiledNodes = compiledGraph && compiledGraph.nodes && typeof compiledGraph.nodes === 'object'
343
+ ? Object.values(compiledGraph.nodes)
344
+ : [];
345
+ for (const node of compiledNodes) {
346
+ if (!node || !node.id || live.workItems.has(node.id)) continue;
347
+ live.workItems.set(node.id, {
348
+ work_item_id: node.id,
349
+ run_id: runId,
350
+ title: node.title || node.id,
351
+ type: 'task',
352
+ depends_on: Array.isArray(node.depends_on) ? node.depends_on : [],
353
+ mutation_scope: Array.isArray(node.mutation_scope) ? node.mutation_scope : [],
354
+ policy_ref: node.policy && node.policy.requires_human_approval ? 'human_approval' : null,
355
+ verify_ref: node.verify && Array.isArray(node.verify.acceptance_refs) ? node.verify.acceptance_refs : [],
356
+ status: 'pending',
357
+ workspace_strategy: node.workspace_strategy || 'inplace',
358
+ });
359
+ }
360
+
361
+ const activeTasks = Array.isArray(runState.active_tasks) ? runState.active_tasks.map(String) : [];
362
+ for (const taskId of activeTasks) {
363
+ const currentItem = live.workItems.get(taskId) || {
364
+ work_item_id: taskId,
365
+ run_id: runId,
366
+ title: taskId,
367
+ type: 'task',
368
+ depends_on: [],
369
+ mutation_scope: [],
370
+ policy_ref: null,
371
+ verify_ref: [],
372
+ status: 'pending',
373
+ workspace_strategy: 'inplace',
374
+ };
375
+ live.workItems.set(taskId, { ...currentItem, status: 'running' });
376
+ }
377
+
378
+ for (const blockedId of Array.isArray(runState.pending_checkpoints) ? runState.pending_checkpoints.map(String) : []) {
379
+ if (!live.blockedWorkItems.has(blockedId)) live.blockedWorkItems.add(blockedId);
380
+ }
381
+
382
+ if (Array.isArray(runState.failures)) {
383
+ for (const failure of runState.failures) {
384
+ const ref = failure && typeof failure === 'object'
385
+ ? String(failure.work_item_id || failure.task_id || failure.id || '')
386
+ : '';
387
+ if (ref) {
388
+ live.failedWorkItems.add(ref);
389
+ const item = live.workItems.get(ref);
390
+ if (item) live.workItems.set(ref, { ...item, status: 'failed' });
391
+ }
392
+ }
393
+ }
394
+
395
+ if (Array.isArray(runState.evidence) && runState.evidence.length && live.run) {
396
+ live.run = { ...live.run, evidence_count: runState.evidence.length };
397
+ }
398
+
399
+ for (const completedId of live.completedWorkItems) {
400
+ const item = live.workItems.get(completedId);
401
+ if (item) live.workItems.set(completedId, { ...item, status: 'completed' });
402
+ }
403
+ for (const failedId of live.failedWorkItems) {
404
+ const item = live.workItems.get(failedId);
405
+ if (item) live.workItems.set(failedId, { ...item, status: 'failed' });
406
+ }
407
+ for (const blockedId of live.blockedWorkItems) {
408
+ const item = live.workItems.get(blockedId);
409
+ if (item) live.workItems.set(blockedId, { ...item, status: 'blocked' });
410
+ }
411
+ return live;
412
+ }
413
+
414
+ function reduceCanonicalRunStateLive(projectRoot, activeSession, options = {}) {
415
+ const runtime = loadRuntimeModule();
416
+ if (!runtime || typeof runtime.reduce !== 'function') return null;
417
+ const currentRun = options.runState || readRunState(projectRoot, activeSession) || null;
418
+ const runId = options.runId || (currentRun && currentRun.run_id) || null;
419
+ let events = readEvents(projectRoot, activeSession);
420
+ if (runId) events = events.filter((event) => !event.run_id || event.run_id === runId);
421
+ const reduced = runtime.reduce(events.map((event) => toRuntimeEventEnvelope(event, activeSession)));
422
+ return mergeCanonicalStateWithRunState(
423
+ serializeCanonicalState(reduced),
424
+ currentRun || {},
425
+ activeSession,
426
+ currentRun && currentRun.compiled_graph ? currentRun.compiled_graph : null
427
+ );
428
+ }
429
+
430
+ function reduceCanonicalRunState(projectRoot, activeSession, options = {}) {
431
+ return serializeCanonicalState(reduceCanonicalRunStateLive(projectRoot, activeSession, options));
432
+ }
433
+
434
+ function compileExecutionGraphFromArtifacts(projectRoot, activeSession, options = {}) {
435
+ const runtime = loadRuntimeModule();
436
+ const parsers = loadSdkParsers();
437
+ if (!runtime || typeof runtime.compile !== 'function' || typeof runtime.toSerializable !== 'function') {
438
+ throw new Error('Runtime package não está disponível. Rode npm run build:runtime.');
439
+ }
440
+ if (!parsers) {
441
+ throw new Error('Parsers do SDK indisponíveis para compilar o grafo.');
442
+ }
443
+ const artifactPaths = resolveRuntimeArtifactPaths(projectRoot, activeSession);
444
+ const specText = readTextIfExists(artifactPaths.spec);
445
+ const planText = readTextIfExists(artifactPaths.plan);
446
+ if (!specText) throw new Error(`SPEC.md ausente em ${artifactPaths.spec}`);
447
+ if (!planText) throw new Error(`PLAN.md ausente em ${artifactPaths.plan}`);
448
+ const parsedSpec = parsers.parseSpec(specText);
449
+ const parsedPlan = parsers.parsePlan(planText);
450
+ const graph = runtime.compile(parsedPlan, parsedSpec, options.compilerOptions || {});
451
+ const validationErrors = typeof runtime.validateGraph === 'function' ? runtime.validateGraph(graph) : [];
452
+ const compiledGraph = runtime.toSerializable(graph);
453
+ const current = options.runState || readRunState(projectRoot, activeSession) || {};
454
+ const runId = current.run_id || makeRunId();
455
+ const next = writeRunState(projectRoot, activeSession, {
456
+ ...current,
457
+ run_id: runId,
458
+ status: current.status || 'planned',
459
+ compiled_graph: compiledGraph,
460
+ graph_version: compiledGraph.metadata && compiledGraph.metadata.plan_hash ? compiledGraph.metadata.plan_hash : 'compiled',
461
+ canonical_state: serializeCanonicalState(
462
+ mergeCanonicalStateWithRunState(
463
+ reduceCanonicalRunState(projectRoot, activeSession, { runState: { ...current, run_id: runId } }),
464
+ { ...current, run_id: runId },
465
+ activeSession,
466
+ compiledGraph
467
+ )
468
+ ),
469
+ projections: current.projections || {},
470
+ });
471
+ appendEvent(projectRoot, activeSession, {
472
+ type: 'GraphCompiled',
473
+ run_id: next.run_id,
474
+ payload: {
475
+ node_count: compiledGraph.metadata && compiledGraph.metadata.node_count || 0,
476
+ wave_count: compiledGraph.metadata && compiledGraph.metadata.wave_count || 0,
477
+ plan_hash: compiledGraph.metadata && compiledGraph.metadata.plan_hash || null,
478
+ spec_hash: compiledGraph.metadata && compiledGraph.metadata.spec_hash || null,
479
+ },
480
+ });
481
+ return {
482
+ run: next,
483
+ graph: compiledGraph,
484
+ validationErrors,
485
+ parsedPlan,
486
+ parsedSpec,
487
+ paths: artifactPaths,
488
+ };
489
+ }
490
+
491
+ function compileVerificationSuiteFromArtifacts(projectRoot, activeSession, options = {}) {
492
+ const runtime = loadRuntimeModule();
493
+ const parsers = loadSdkParsers();
494
+ if (!runtime || typeof runtime.compileVerification !== 'function') {
495
+ throw new Error('Runtime package não está disponível. Rode npm run build:runtime.');
496
+ }
497
+ if (!parsers) {
498
+ throw new Error('Parsers do SDK indisponíveis para compilar verification suite.');
499
+ }
500
+ const artifactPaths = resolveRuntimeArtifactPaths(projectRoot, activeSession);
501
+ const specText = readTextIfExists(artifactPaths.spec);
502
+ const planText = readTextIfExists(artifactPaths.plan);
503
+ if (!specText || !planText) {
504
+ throw new Error('SPEC.md e PLAN.md são obrigatórios para compilar a suite de verificação.');
505
+ }
506
+ const suite = runtime.compileVerification(parsers.parseSpec(specText), parsers.parsePlan(planText), options.compilerOptions || {});
507
+ const current = options.runState || readRunState(projectRoot, activeSession) || {};
508
+ const runId = current.run_id || makeRunId();
509
+ const next = writeRunState(projectRoot, activeSession, {
510
+ ...current,
511
+ run_id: runId,
512
+ status: current.status || 'planned',
513
+ verification_suite: suite,
514
+ });
515
+ appendEvent(projectRoot, activeSession, {
516
+ type: 'verification_suite_compiled',
517
+ run_id: next.run_id,
518
+ payload: {
519
+ total_checks: Array.isArray(suite.checks) ? suite.checks.length : 0,
520
+ spec_hash: suite.spec_hash || null,
521
+ plan_hash: suite.plan_hash || null,
522
+ },
523
+ });
524
+ return { run: next, suite, paths: artifactPaths };
525
+ }
526
+
527
+ function runResultFromRunState(runState) {
528
+ const canonical = runState && runState.canonical_state && typeof runState.canonical_state === 'object'
529
+ ? hydrateCanonicalState(runState.canonical_state)
530
+ : null;
531
+ return {
532
+ run_id: runState && runState.run_id ? runState.run_id : makeRunId(),
533
+ status: String(runState && runState.status || 'planned'),
534
+ completed: canonical ? Array.from(canonical.completedWorkItems || []) : [],
535
+ failed: canonical ? Array.from(canonical.failedWorkItems || []) : [],
536
+ blocked: canonical ? Array.from(canonical.blockedWorkItems || []) : [],
537
+ };
538
+ }
539
+
540
+ function gateStoragePath(projectRoot, activeSession) {
541
+ return activeSession
542
+ ? path.join(projectRoot, '.oxe', ...String(activeSession).split('/'), 'execution', 'GATES.json')
543
+ : path.join(projectRoot, '.oxe', 'execution', 'GATES.json');
544
+ }
545
+
546
+ function readRuntimeGates(projectRoot, activeSession, options = {}) {
547
+ const gatesPath = gateStoragePath(projectRoot, activeSession);
548
+ const raw = readJsonIfExists(gatesPath);
549
+ const gates = Array.isArray(raw) ? raw : [];
550
+ const runId = options.runId || null;
551
+ const gateSlaHours = Number(options.gateSlaHours || options.gate_sla_hours || 24);
552
+ const status = String(options.status || 'all');
553
+ const scope = options.scope ? String(options.scope) : null;
554
+ const workItemId = options.task || options.workItemId || null;
555
+ const action = options.action || null;
556
+ const filtered = gates.filter((gate) => {
557
+ if (runId && gate.run_id && gate.run_id !== runId) return false;
558
+ if (scope && gate.scope !== scope) return false;
559
+ if (workItemId && gate.work_item_id !== workItemId) return false;
560
+ if (action && gate.action !== action) return false;
561
+ if (status !== 'all') {
562
+ const requestedAt = Date.parse(String(gate.requested_at || ''));
563
+ const isStale = gate.status === 'pending'
564
+ && Number.isFinite(requestedAt)
565
+ && Date.now() - requestedAt > gateSlaHours * 60 * 60 * 1000;
566
+ if (status === 'stale') return isStale;
567
+ return gate.status === status;
568
+ }
569
+ return true;
570
+ });
571
+ const pending = filtered.filter((gate) => gate && gate.status === 'pending');
572
+ const stalePending = pending.filter((gate) => {
573
+ const requestedAt = Date.parse(String(gate.requested_at || ''));
574
+ return Number.isFinite(requestedAt) && Date.now() - requestedAt > gateSlaHours * 60 * 60 * 1000;
575
+ });
576
+ const resolvedRecent = filtered.filter((gate) => {
577
+ if (gate.status !== 'resolved') return false;
578
+ const resolvedAt = Date.parse(String(gate.resolved_at || ''));
579
+ return Number.isFinite(resolvedAt) && Date.now() - resolvedAt <= gateSlaHours * 60 * 60 * 1000;
580
+ });
581
+ const byRun = {};
582
+ const byScope = {};
583
+ for (const gate of filtered) {
584
+ const runKey = gate.run_id || 'unscoped';
585
+ const scopeKey = gate.scope || 'unknown';
586
+ byRun[runKey] = (byRun[runKey] || 0) + 1;
587
+ byScope[scopeKey] = (byScope[scopeKey] || 0) + 1;
588
+ }
589
+ return {
590
+ path: gatesPath,
591
+ total: filtered.length,
592
+ gateSlaHours,
593
+ pending,
594
+ stalePending,
595
+ staleCount: stalePending.length,
596
+ resolvedRecent,
597
+ byRun,
598
+ byScope,
599
+ all: filtered,
600
+ filters: { runId, status, scope, workItemId, action },
601
+ };
602
+ }
603
+
604
+ async function resolveRuntimeGate(projectRoot, activeSession, options = {}) {
605
+ const runtime = loadRuntimeModule();
606
+ if (!runtime || typeof runtime.GateManager !== 'function') {
607
+ throw new Error('Runtime package não está disponível. Rode npm run build:runtime.');
608
+ }
609
+ const current = readRunState(projectRoot, activeSession);
610
+ const runId = options.runId || (current && current.run_id);
611
+ if (!runId) throw new Error('Nenhum run ativo para resolver gates.');
612
+ const manager = new runtime.GateManager(projectRoot, activeSession || null, runId);
613
+ const gate = manager.get(String(options.gateId || ''));
614
+ if (!gate) throw new Error(`Gate ${options.gateId} não encontrado.`);
615
+ const mappedDecision = options.decision === 'approve'
616
+ ? 'approved'
617
+ : options.decision === 'reject'
618
+ ? 'rejected'
619
+ : 'approved_with_caveats';
620
+ const resolved = await runtime.resolveGate(manager, gate.gate_id, {
621
+ decision: mappedDecision,
622
+ actor: String(options.actor || ''),
623
+ reason: options.reason ? String(options.reason) : undefined,
624
+ });
625
+ const queue = readRuntimeGates(projectRoot, activeSession, {
626
+ runId,
627
+ gateSlaHours: options.gateSlaHours || options.gate_sla_hours || 24,
628
+ });
629
+ return {
630
+ gate: resolved,
631
+ queue,
632
+ impact: {
633
+ pendingRemaining: queue.pending.length,
634
+ staleRemaining: queue.staleCount || 0,
635
+ runId,
636
+ },
637
+ };
638
+ }
639
+
640
+ function readRuntimeMultiAgentStatus(projectRoot, activeSession, options = {}) {
641
+ const runtime = loadRuntimeModule();
642
+ const current = readRunState(projectRoot, activeSession);
643
+ const runId = options.runId || (current && current.run_id) || null;
644
+ if (!runId) {
645
+ return {
646
+ path: null,
647
+ enabled: false,
648
+ runId: null,
649
+ mode: null,
650
+ workspaceIsolationEnforced: false,
651
+ agents: [],
652
+ ownership: [],
653
+ orphanReassignments: [],
654
+ handoffs: [],
655
+ arbitrationResults: [],
656
+ };
657
+ }
658
+ const runDir = path.join(projectRoot, '.oxe', 'runs', runId);
659
+ const statePath = path.join(runDir, 'multi-agent-state.json');
660
+ const handoffsPath = path.join(runDir, 'handoffs.json');
661
+ const arbitrationPath = path.join(runDir, 'arbitration-results.json');
662
+ const state = runtime && typeof runtime.loadMultiAgentState === 'function'
663
+ ? runtime.loadMultiAgentState(projectRoot, runId)
664
+ : readJsonIfExists(statePath);
665
+ const handoffs = readJsonIfExists(handoffsPath);
666
+ const arbitrationResults = readJsonIfExists(arbitrationPath);
667
+ return {
668
+ path: statePath,
669
+ enabled: Boolean(state),
670
+ runId,
671
+ mode: state && state.mode ? state.mode : null,
672
+ workspaceIsolationEnforced: Boolean(state && state.workspace_isolation_enforced),
673
+ agents: state && Array.isArray(state.agent_results) ? state.agent_results : [],
674
+ ownership: state && Array.isArray(state.ownership) ? state.ownership : [],
675
+ orphanReassignments: state && Array.isArray(state.orphan_reassignments) ? state.orphan_reassignments : [],
676
+ handoffs: Array.isArray(handoffs) ? handoffs : [],
677
+ arbitrationResults: Array.isArray(arbitrationResults) ? arbitrationResults : [],
678
+ };
679
+ }
680
+
681
+ function loadRuntimeVerificationArtifacts(projectRoot, runState) {
682
+ const runtime = loadRuntimeModule();
683
+ if (!runtime || !runState || !runState.run_id) {
684
+ return {
685
+ manifest: null,
686
+ residualRisks: null,
687
+ evidenceCoverage: null,
688
+ };
689
+ }
690
+ const manifest = runState.verification_manifest
691
+ || (typeof runtime.loadManifest === 'function' ? runtime.loadManifest(projectRoot, runState.run_id) : null)
692
+ || null;
693
+ const residualRisks = runState.residual_risks
694
+ || (typeof runtime.loadRiskLedger === 'function' ? runtime.loadRiskLedger(projectRoot, runState.run_id) : null)
695
+ || null;
696
+ const evidenceCoverage = runState.verification_evidence_coverage
697
+ || (typeof runtime.loadEvidenceCoverage === 'function' ? runtime.loadEvidenceCoverage(projectRoot, runState.run_id) : null)
698
+ || (manifest && typeof runtime.summarizeEvidenceCoverage === 'function'
699
+ ? runtime.summarizeEvidenceCoverage(manifest)
700
+ : null)
701
+ || null;
702
+ return { manifest, residualRisks, evidenceCoverage };
703
+ }
704
+
705
+ function countVerificationEvidenceRefs(runState, verificationArtifacts) {
706
+ if (verificationArtifacts && verificationArtifacts.manifest && Array.isArray(verificationArtifacts.manifest.checks)) {
707
+ return verificationArtifacts.manifest.checks.reduce((sum, check) => {
708
+ return sum + (Array.isArray(check.evidence_refs) ? check.evidence_refs.length : 0);
709
+ }, 0);
710
+ }
711
+ if (Array.isArray(runState && runState.verification_results)) {
712
+ return runState.verification_results.reduce((sum, result) => {
713
+ return sum + (Array.isArray(result.evidence_refs) ? result.evidence_refs.length : 0);
714
+ }, 0);
715
+ }
716
+ return 0;
717
+ }
718
+
719
+ function buildRuntimeModeStatus(runState) {
720
+ if (!runState) {
721
+ return {
722
+ runtime_mode: 'legacy',
723
+ fallback_mode: 'legacy',
724
+ source: 'absent',
725
+ reason: 'Nenhum ACTIVE-RUN encontrado para o escopo atual.',
726
+ enterprise_available: false,
727
+ fallback_recorded: true,
728
+ };
729
+ }
730
+ const hasEnterpriseArtifacts = Boolean(
731
+ runState.compiled_graph
732
+ || runState.canonical_state
733
+ || runState.verification_manifest
734
+ || runState.verification_evidence_coverage
735
+ || runState.delivery
736
+ || runState.recovery_summary
737
+ );
738
+ return {
739
+ runtime_mode: hasEnterpriseArtifacts ? 'enterprise' : 'legacy',
740
+ fallback_mode: hasEnterpriseArtifacts ? 'none' : 'legacy',
741
+ source: hasEnterpriseArtifacts ? 'canonical_state' : 'legacy_state',
742
+ reason: hasEnterpriseArtifacts
743
+ ? 'Run com artefatos canónicos do runtime enterprise persistidos.'
744
+ : 'Run sem artefatos canónicos do runtime; fluxo degradado para legado.',
745
+ enterprise_available: hasEnterpriseArtifacts,
746
+ fallback_recorded: !hasEnterpriseArtifacts,
747
+ };
748
+ }
749
+
750
+ function buildRuntimeProviderCatalog(projectRoot) {
751
+ const runtime = loadRuntimeModule();
752
+ if (!runtime || typeof runtime.PluginRegistry !== 'function') {
753
+ return {
754
+ available: false,
755
+ plugin_dir: path.join(projectRoot, '.oxe', 'plugins'),
756
+ loaded_capabilities: [],
757
+ loaded_plugins: [],
758
+ load_errors: ['Runtime package não está disponível. Rode npm run build:runtime.'],
759
+ summary: null,
760
+ matrix: null,
761
+ };
762
+ }
763
+ const registry = new runtime.PluginRegistry();
764
+ const pluginDir = path.join(projectRoot, '.oxe', 'plugins');
765
+ const loadedCapabilities = typeof registry.registerProjectCapabilities === 'function'
766
+ ? registry.registerProjectCapabilities(projectRoot)
767
+ : [];
768
+ const loadedPlugins = typeof registry.loadFromDirectory === 'function'
769
+ ? registry.loadFromDirectory(pluginDir)
770
+ : [];
771
+ const loadErrors = typeof registry.loadErrorsSnapshot === 'function'
772
+ ? registry.loadErrorsSnapshot()
773
+ : [];
774
+ const summary = typeof runtime.registrySummary === 'function'
775
+ ? runtime.registrySummary(registry)
776
+ : (typeof registry.summary === 'function' ? registry.summary() : null);
777
+ const matrix = typeof runtime.resolveCapabilityMatrix === 'function'
778
+ ? runtime.resolveCapabilityMatrix(registry)
779
+ : (typeof registry.capabilityMatrix === 'function' ? registry.capabilityMatrix() : null);
780
+ return {
781
+ available: true,
782
+ plugin_dir: pluginDir,
783
+ loaded_capabilities: loadedCapabilities,
784
+ loaded_plugins: loadedPlugins,
785
+ load_errors: loadErrors,
786
+ summary,
787
+ matrix,
788
+ };
789
+ }
790
+
791
+ function buildRecoveryConsistency(projectRoot, activeSession, runState, journal, verificationArtifacts) {
792
+ const op = operationalPaths(projectRoot, activeSession);
793
+ const activeRunRef = readJsonIfExists(op.activeRun);
794
+ const runFile = runState && runState.run_id ? path.join(op.runsDir, `${runState.run_id}.json`) : null;
795
+ const runDir = runState && runState.run_id ? path.join(projectRoot, '.oxe', 'runs', runState.run_id) : null;
796
+ const allEvents = readEvents(projectRoot, activeSession);
797
+ const runEvents = runState && runState.run_id ? allEvents.filter((event) => event.run_id === runState.run_id) : [];
798
+ const issues = [];
799
+ if (!activeRunRef || activeRunRef.run_id !== (runState && runState.run_id)) {
800
+ issues.push('ACTIVE-RUN.json não referencia o mesmo run persistido em .oxe/runs/.');
801
+ }
802
+ if (!runFile || !fs.existsSync(runFile)) {
803
+ issues.push('Arquivo canónico da run ausente em .oxe/runs/<run>.json.');
804
+ }
805
+ if (!journal) {
806
+ issues.push('Journal ausente para recover/replay.');
807
+ }
808
+ if (runEvents.length === 0) {
809
+ issues.push('Nenhum evento NDJSON encontrado para a run ativa.');
810
+ }
811
+ if (!runState || !runState.canonical_state) {
812
+ issues.push('canonical_state ausente no ACTIVE-RUN.');
813
+ }
814
+ const pendingGates = readRuntimeGates(projectRoot, activeSession, { runId: runState && runState.run_id ? runState.run_id : null });
815
+ const policyDecisionPath = runState && runState.run_id ? path.join(projectRoot, '.oxe', 'runs', runState.run_id, 'policy-decisions.json') : null;
816
+ const policyDecisions = policyDecisionPath && fs.existsSync(policyDecisionPath)
817
+ ? (Array.isArray(readJsonIfExists(policyDecisionPath)) ? readJsonIfExists(policyDecisionPath) : [])
818
+ : [];
819
+ const promotionRecordPath = runDir ? path.join(runDir, 'promotion-record.json') : null;
820
+ const promotionRecord = promotionRecordPath ? readJsonIfExists(promotionRecordPath) : null;
821
+ const attempts = runState && runState.canonical_state && runState.canonical_state.attempts && typeof runState.canonical_state.attempts === 'object'
822
+ ? runState.canonical_state.attempts
823
+ : {};
824
+ const incompleteAttempts = Object.entries(attempts)
825
+ .flatMap(([workItemId, entries]) => Array.isArray(entries) ? entries.map((entry) => ({ workItemId, entry })) : [])
826
+ .filter(({ entry }) => {
827
+ const outcome = entry && typeof entry === 'object' ? String(entry.outcome || '') : '';
828
+ return !outcome || outcome === 'running' || outcome === 'pending';
829
+ });
830
+ if (incompleteAttempts.length > 0) {
831
+ issues.push(`${incompleteAttempts.length} tentativa(s) incompleta(s) ainda persistida(s) no estado canônico.`);
832
+ }
833
+ return {
834
+ active_run_path: op.activeRun,
835
+ run_file_path: runFile,
836
+ run_dir: runDir,
837
+ journal_path: runDir ? path.join(runDir, 'journal.json') : null,
838
+ events_path: op.events,
839
+ gates_path: op.gates,
840
+ policy_decisions_path: policyDecisionPath,
841
+ verification_manifest_path: runDir ? path.join(runDir, 'verification-manifest.json') : null,
842
+ residual_risk_path: runDir ? path.join(runDir, 'residual-risk-ledger.json') : null,
843
+ evidence_coverage_path: runDir ? path.join(runDir, 'evidence-coverage.json') : null,
844
+ promotion_record_path: promotionRecordPath,
845
+ run_id: runState && runState.run_id ? runState.run_id : null,
846
+ active_run_synced: Boolean(activeRunRef && runState && activeRunRef.run_id === runState.run_id),
847
+ run_file_exists: Boolean(runFile && fs.existsSync(runFile)),
848
+ journal_exists: Boolean(journal),
849
+ event_count: runEvents.length,
850
+ issues,
851
+ pending_gates_rehydrated: pendingGates.pending.length,
852
+ policy_decisions_rehydrated: Array.isArray(policyDecisions) ? policyDecisions.length : 0,
853
+ evidence_refs_tracked: countVerificationEvidenceRefs(runState, verificationArtifacts),
854
+ verification_artifacts_present: Boolean(verificationArtifacts && verificationArtifacts.manifest),
855
+ promotion_attempt_present: Boolean(promotionRecord),
856
+ promotion_status: promotionRecord && promotionRecord.status ? promotionRecord.status : null,
857
+ incomplete_attempts: incompleteAttempts.map(({ workItemId, entry }) => ({
858
+ work_item_id: workItemId,
859
+ attempt_id: entry && typeof entry === 'object' ? entry.attempt_id || null : null,
860
+ outcome: entry && typeof entry === 'object' ? entry.outcome || null : null,
861
+ })),
862
+ };
863
+ }
864
+
865
+ function writeRecoverySummaryMarkdown(projectRoot, activeSession, runState, recoverySummary) {
866
+ const op = operationalPaths(projectRoot, activeSession);
867
+ const summaryPath = path.join(op.executionRoot, 'RECOVERY-SUMMARY.md');
868
+ const lines = [
869
+ '# OXE Recovery Summary',
870
+ '',
871
+ `- **Data:** ${new Date().toISOString()}`,
872
+ `- **Run:** ${runState && runState.run_id ? runState.run_id : '—'}`,
873
+ `- **Estado pós-recover:** ${runState && runState.status ? runState.status : '—'}`,
874
+ `- **Journal:** ${recoverySummary.journal_state || '—'}`,
875
+ `- **Pending gates reidratados:** ${recoverySummary.consistency && recoverySummary.consistency.pending_gates_rehydrated != null ? recoverySummary.consistency.pending_gates_rehydrated : 0}`,
876
+ `- **Policy decisions reidratadas:** ${recoverySummary.consistency && recoverySummary.consistency.policy_decisions_rehydrated != null ? recoverySummary.consistency.policy_decisions_rehydrated : 0}`,
877
+ `- **Evidence refs rastreados:** ${recoverySummary.consistency && recoverySummary.consistency.evidence_refs_tracked != null ? recoverySummary.consistency.evidence_refs_tracked : 0}`,
878
+ `- **Promotion attempt:** ${recoverySummary.consistency && recoverySummary.consistency.promotion_status ? recoverySummary.consistency.promotion_status : '—'}`,
879
+ '',
880
+ '## Work items órfãos',
881
+ '',
882
+ ...(Array.isArray(recoverySummary.orphan_work_items) && recoverySummary.orphan_work_items.length
883
+ ? recoverySummary.orphan_work_items.map((item) => `- ${item}`)
884
+ : ['- Nenhum']),
885
+ '',
886
+ '## Tentativas incompletas',
887
+ '',
888
+ ...(recoverySummary.consistency && Array.isArray(recoverySummary.consistency.incomplete_attempts) && recoverySummary.consistency.incomplete_attempts.length
889
+ ? recoverySummary.consistency.incomplete_attempts.map((item) => `- ${item.work_item_id} · ${item.attempt_id || 'attempt'} · ${item.outcome || 'unknown'}`)
890
+ : ['- Nenhuma']),
891
+ '',
892
+ '## Consistência',
893
+ '',
894
+ ...(recoverySummary.consistency && Array.isArray(recoverySummary.consistency.issues) && recoverySummary.consistency.issues.length
895
+ ? recoverySummary.consistency.issues.map((issue) => `- ${issue}`)
896
+ : ['- Sem inconsistências críticas detectadas.']),
897
+ ];
898
+ ensureDirForFile(summaryPath);
899
+ fs.writeFileSync(summaryPath, lines.join('\n') + '\n', 'utf8');
900
+ return summaryPath;
901
+ }
902
+
903
+ async function runRuntimeVerify(projectRoot, activeSession, options = {}) {
904
+ const runtime = loadRuntimeModule();
905
+ const parsers = loadSdkParsers();
906
+ if (!runtime || typeof runtime.verifyRun !== 'function') {
907
+ throw new Error('Runtime package não está disponível. Rode npm run build:runtime.');
908
+ }
909
+ if (!parsers) {
910
+ throw new Error('Parsers do SDK indisponíveis para executar verification suite.');
911
+ }
912
+ let current = options.runState || readRunState(projectRoot, activeSession);
913
+ if (!current) {
914
+ current = compileExecutionGraphFromArtifacts(projectRoot, activeSession).run;
915
+ }
916
+ if (!current.verification_suite) {
917
+ current = compileVerificationSuiteFromArtifacts(projectRoot, activeSession, { runState: current }).run;
918
+ }
919
+ const artifactPaths = resolveRuntimeArtifactPaths(projectRoot, activeSession);
920
+ const specText = readTextIfExists(artifactPaths.spec);
921
+ const planText = readTextIfExists(artifactPaths.plan);
922
+ if (!specText || !planText) {
923
+ throw new Error('SPEC.md e PLAN.md são obrigatórios para runtime verify.');
924
+ }
925
+ const parsedSpec = parsers.parseSpec(specText);
926
+ const parsedPlan = parsers.parsePlan(planText);
927
+ const suite = current.verification_suite
928
+ || runtime.compileVerification(parsedSpec, parsedPlan, options.compilerOptions || {});
929
+ const registry = buildRuntimePluginRegistry(projectRoot);
930
+ const evidenceStore = new runtime.EvidenceStore(projectRoot);
931
+ const targetWorkItem = options.workItemId
932
+ || options.task
933
+ || (current.cursor && current.cursor.task)
934
+ || (Array.isArray(current.active_tasks) && current.active_tasks[0])
935
+ || 'run';
936
+ appendEvent(projectRoot, activeSession, {
937
+ type: 'VerificationStarted',
938
+ run_id: current.run_id,
939
+ work_item_id: targetWorkItem,
940
+ payload: {
941
+ total_checks: Array.isArray(suite.checks) ? suite.checks.length : 0,
942
+ },
943
+ });
944
+ const report = await runtime.verifyRun({
945
+ projectRoot,
946
+ runId: current.run_id,
947
+ workItemId: String(targetWorkItem),
948
+ cwd: options.cwd || projectRoot,
949
+ suite,
950
+ pluginRegistry: registry || undefined,
951
+ evidenceStore,
952
+ attemptNumber: options.attemptNumber || 1,
953
+ timeoutMs: options.timeoutMs,
954
+ });
955
+ const nextStatus = report.status === 'passed'
956
+ ? current.status === 'completed' ? 'completed' : 'running'
957
+ : report.status === 'failed'
958
+ ? 'failed'
959
+ : 'blocked';
960
+ const next = writeRunState(projectRoot, activeSession, {
961
+ ...current,
962
+ status: nextStatus,
963
+ verification_suite: suite,
964
+ verification_results: report.verification_results,
965
+ verification_check_results: report.check_results,
966
+ verification_manifest: report.manifest,
967
+ residual_risks: report.risk_ledger,
968
+ verification_evidence_coverage: report.evidence_coverage,
969
+ verification_gaps: report.gaps,
970
+ });
971
+ appendEvent(projectRoot, activeSession, {
972
+ type: 'VerificationCompleted',
973
+ run_id: next.run_id,
974
+ work_item_id: targetWorkItem,
975
+ payload: {
976
+ status: report.status,
977
+ total_checks: report.manifest && report.manifest.summary ? report.manifest.summary.total : 0,
978
+ fail: report.manifest && report.manifest.summary ? report.manifest.summary.fail : 0,
979
+ error: report.manifest && report.manifest.summary ? report.manifest.summary.error : 0,
980
+ gaps: report.gaps,
981
+ },
982
+ });
983
+ const projected = projectRuntimeArtifacts(projectRoot, activeSession, { runState: next, write: true });
984
+ return {
985
+ run: projected.run,
986
+ report,
987
+ projected,
988
+ };
989
+ }
990
+
991
+ function projectRuntimeArtifacts(projectRoot, activeSession, options = {}) {
992
+ const runtime = loadRuntimeModule();
993
+ if (!runtime || typeof runtime.ProjectionEngine !== 'function' || typeof runtime.fromSerializable !== 'function') {
994
+ throw new Error('Runtime package não está disponível. Rode npm run build:runtime.');
995
+ }
996
+ let current = options.runState || readRunState(projectRoot, activeSession);
997
+ if (!current) {
998
+ current = writeRunState(projectRoot, activeSession, {});
999
+ }
1000
+ if (!current.compiled_graph) {
1001
+ current = compileExecutionGraphFromArtifacts(projectRoot, activeSession, { runState: current }).run;
1002
+ }
1003
+ const graph = runtime.fromSerializable(current.compiled_graph);
1004
+ const canonicalLive = current.canonical_state
1005
+ ? mergeCanonicalStateWithRunState(current.canonical_state, current, activeSession, current.compiled_graph)
1006
+ : reduceCanonicalRunStateLive(projectRoot, activeSession, { runState: current });
1007
+ if (!canonicalLive) {
1008
+ throw new Error('Não foi possível reconstruir o estado canônico da run.');
1009
+ }
1010
+ const canonicalState = serializeCanonicalState(canonicalLive);
1011
+ const projector = new runtime.ProjectionEngine();
1012
+ const verificationResults = Array.isArray(current.verification_results) ? current.verification_results : [];
1013
+ const verificationCheckResults = Array.isArray(current.verification_check_results) ? current.verification_check_results : [];
1014
+ const verificationArtifacts = loadRuntimeVerificationArtifacts(projectRoot, current);
1015
+ const projections = {
1016
+ plan: projector.projectPlan(canonicalLive, graph),
1017
+ verify: projector.projectVerify(
1018
+ canonicalLive,
1019
+ verificationResults,
1020
+ verificationCheckResults,
1021
+ verificationArtifacts.manifest,
1022
+ verificationArtifacts.residualRisks,
1023
+ verificationArtifacts.evidenceCoverage
1024
+ ),
1025
+ state: projector.projectState(canonicalLive),
1026
+ runSummary: projector.projectRunSummary(canonicalLive),
1027
+ commitSummary: typeof projector.projectCommitSummary === 'function'
1028
+ ? projector.projectCommitSummary(canonicalLive, graph)
1029
+ : projector.projectRunSummary(canonicalLive),
1030
+ promotionSummary: typeof projector.projectPromotionSummary === 'function'
1031
+ ? projector.projectPromotionSummary(canonicalLive, graph)
1032
+ : projector.projectPRSummary(canonicalLive, graph),
1033
+ prSummary: projector.projectPRSummary(canonicalLive, graph),
1034
+ };
1035
+ const paths = resolveRuntimeArtifactPaths(projectRoot, activeSession);
1036
+ const op = operationalPaths(projectRoot, activeSession);
1037
+ const projectionRefs = {
1038
+ plan_ref: path.relative(projectRoot, paths.plan).replace(/\\/g, '/'),
1039
+ verify_ref: path.relative(projectRoot, paths.verify).replace(/\\/g, '/'),
1040
+ state_ref: path.relative(projectRoot, paths.state).replace(/\\/g, '/'),
1041
+ run_summary_ref: path.relative(projectRoot, path.join(op.executionRoot, 'RUN-SUMMARY.md')).replace(/\\/g, '/'),
1042
+ commit_summary_ref: path.relative(projectRoot, path.join(op.executionRoot, 'COMMIT-SUMMARY.md')).replace(/\\/g, '/'),
1043
+ promotion_summary_ref: path.relative(projectRoot, path.join(op.executionRoot, 'PROMOTION-SUMMARY.md')).replace(/\\/g, '/'),
1044
+ pr_summary_ref: path.relative(projectRoot, path.join(op.executionRoot, 'PR-SUMMARY.md')).replace(/\\/g, '/'),
1045
+ generated_at: new Date().toISOString(),
1046
+ };
1047
+ if (options.write !== false) {
1048
+ ensureDirForFile(paths.plan);
1049
+ ensureDirForFile(paths.verify);
1050
+ ensureDirForFile(paths.state);
1051
+ fs.writeFileSync(paths.plan, projections.plan + '\n', 'utf8');
1052
+ fs.writeFileSync(paths.verify, projections.verify + '\n', 'utf8');
1053
+ fs.writeFileSync(paths.state, projections.state + '\n', 'utf8');
1054
+ fs.writeFileSync(path.join(op.executionRoot, 'RUN-SUMMARY.md'), projections.runSummary + '\n', 'utf8');
1055
+ fs.writeFileSync(path.join(op.executionRoot, 'COMMIT-SUMMARY.md'), projections.commitSummary + '\n', 'utf8');
1056
+ fs.writeFileSync(path.join(op.executionRoot, 'PROMOTION-SUMMARY.md'), projections.promotionSummary + '\n', 'utf8');
1057
+ fs.writeFileSync(path.join(op.executionRoot, 'PR-SUMMARY.md'), projections.prSummary + '\n', 'utf8');
1058
+ }
1059
+ const next = writeRunState(projectRoot, activeSession, {
1060
+ ...current,
1061
+ canonical_state: canonicalState,
1062
+ verification_manifest: verificationArtifacts.manifest,
1063
+ residual_risks: verificationArtifacts.residualRisks,
1064
+ verification_evidence_coverage: verificationArtifacts.evidenceCoverage,
1065
+ projections: projectionRefs,
1066
+ });
1067
+ return {
1068
+ run: next,
1069
+ projections,
1070
+ paths: {
1071
+ ...paths,
1072
+ runSummary: path.join(op.executionRoot, 'RUN-SUMMARY.md'),
1073
+ commitSummary: path.join(op.executionRoot, 'COMMIT-SUMMARY.md'),
1074
+ promotionSummary: path.join(op.executionRoot, 'PROMOTION-SUMMARY.md'),
1075
+ prSummary: path.join(op.executionRoot, 'PR-SUMMARY.md'),
1076
+ },
1077
+ };
1078
+ }
1079
+
1080
+ async function runRuntimeCiChecks(projectRoot, activeSession, options = {}) {
1081
+ const runtime = loadRuntimeModule();
1082
+ if (!runtime || typeof runtime.runCIChecks !== 'function' || typeof runtime.summarizeCIResults !== 'function') {
1083
+ throw new Error('Runtime package não está disponível. Rode npm run build:runtime.');
1084
+ }
1085
+ let current = options.runState || readRunState(projectRoot, activeSession);
1086
+ if (!current) {
1087
+ current = compileExecutionGraphFromArtifacts(projectRoot, activeSession).run;
1088
+ }
1089
+ const runId = options.runId || (current && current.run_id) || null;
1090
+ const results = await runtime.runCIChecks({
1091
+ projectRoot,
1092
+ sessionId: activeSession || null,
1093
+ runId,
1094
+ });
1095
+ const summary = runtime.summarizeCIResults(results);
1096
+ const ciResultsPath = path.join(projectRoot, '.oxe', 'runs', runId || makeRunId(), 'ci-results.json');
1097
+ ensureDirForFile(ciResultsPath);
1098
+ fs.writeFileSync(ciResultsPath, JSON.stringify({
1099
+ run_id: runId,
1100
+ generated_at: new Date().toISOString(),
1101
+ summary,
1102
+ results,
1103
+ }, null, 2), 'utf8');
1104
+ const next = writeRunState(projectRoot, activeSession, {
1105
+ ...(current || {}),
1106
+ run_id: runId || makeRunId(),
1107
+ status: current && current.status ? current.status : 'planned',
1108
+ ci_checks: {
1109
+ generated_at: new Date().toISOString(),
1110
+ summary,
1111
+ results,
1112
+ path: path.relative(projectRoot, ciResultsPath).replace(/\\/g, '/'),
1113
+ },
1114
+ });
1115
+ appendEvent(projectRoot, activeSession, {
1116
+ type: 'runtime_ci_completed',
1117
+ run_id: next.run_id,
1118
+ payload: {
1119
+ total: summary.total,
1120
+ pass: summary.pass,
1121
+ fail: summary.fail,
1122
+ skip: summary.skip,
1123
+ error: summary.error,
1124
+ allPassed: summary.allPassed,
1125
+ },
1126
+ });
1127
+ return { run: next, runId: next.run_id, results, summary, path: ciResultsPath };
1128
+ }
1129
+
1130
+ async function runRuntimePromotion(projectRoot, activeSession, options = {}) {
1131
+ const runtime = loadRuntimeModule();
1132
+ if (!runtime || typeof runtime.PromotionPipeline !== 'function' || typeof runtime.BranchManager !== 'function' || typeof runtime.PRManager !== 'function') {
1133
+ throw new Error('Runtime package não está disponível. Rode npm run build:runtime.');
1134
+ }
1135
+ let current = options.runState || readRunState(projectRoot, activeSession);
1136
+ if (!current) {
1137
+ throw new Error('Nenhum ACTIVE-RUN disponível para promover.');
1138
+ }
1139
+ const verificationArtifacts = loadRuntimeVerificationArtifacts(projectRoot, current);
1140
+ if (!verificationArtifacts.manifest) {
1141
+ throw new Error('Manifest de verify ausente — execute `oxe-cc runtime verify` antes de promover.');
1142
+ }
1143
+ const branchManager = new runtime.BranchManager(projectRoot);
1144
+ const prManager = new runtime.PRManager(projectRoot);
1145
+ const pipeline = new runtime.PromotionPipeline(projectRoot, branchManager, prManager);
1146
+ const runResult = runResultFromRunState(current);
1147
+ const gates = readRuntimeGates(projectRoot, activeSession, { runId: current.run_id }).all;
1148
+ const commitRecord = pipeline.loadCommitRecord(current.run_id)
1149
+ || pipeline.recordLocalCommit(runResult, verificationArtifacts.manifest, verificationArtifacts.residualRisks, {
1150
+ commitSha: branchManager.currentCommit(),
1151
+ summaryPath: current.projections && current.projections.commit_summary_ref ? current.projections.commit_summary_ref : null,
1152
+ });
1153
+ const promotion = await pipeline.promote(
1154
+ runResult,
1155
+ verificationArtifacts.manifest,
1156
+ verificationArtifacts.residualRisks,
1157
+ {
1158
+ targetKind: options.targetKind || 'pr_draft',
1159
+ remote: options.remote || 'origin',
1160
+ baseBranch: options.baseBranch || 'main',
1161
+ targetRef: options.targetRef || options.baseBranch || 'main',
1162
+ minimumCoverage: options.minimumCoverage == null ? 100 : Number(options.minimumCoverage),
1163
+ draftPR: options.draftPR !== false,
1164
+ },
1165
+ gates,
1166
+ verificationArtifacts.evidenceCoverage
1167
+ );
1168
+ const health = loadProjectHealth();
1169
+ const healthReport = health && typeof health.buildHealthReport === 'function'
1170
+ ? health.buildHealthReport(projectRoot)
1171
+ : null;
1172
+ const promotionReadiness = healthReport && healthReport.promotionReadiness ? healthReport.promotionReadiness : null;
1173
+ const readinessPath = path.join(projectRoot, '.oxe', 'runs', current.run_id, 'promotion-readiness.json');
1174
+ ensureDirForFile(readinessPath);
1175
+ fs.writeFileSync(readinessPath, JSON.stringify({
1176
+ run_id: current.run_id,
1177
+ generated_at: new Date().toISOString(),
1178
+ readiness: promotionReadiness,
1179
+ promotion,
1180
+ }, null, 2), 'utf8');
1181
+ const next = writeRunState(projectRoot, activeSession, {
1182
+ ...current,
1183
+ delivery: {
1184
+ commit_record: commitRecord,
1185
+ promotion_record: promotion,
1186
+ promotion_readiness_ref: path.relative(projectRoot, readinessPath).replace(/\\/g, '/'),
1187
+ },
1188
+ });
1189
+ appendEvent(projectRoot, activeSession, {
1190
+ type: promotion.status === 'blocked' ? 'GateRequested' : 'ToolCompleted',
1191
+ run_id: next.run_id,
1192
+ payload: {
1193
+ promotion_target: promotion.target_kind,
1194
+ promotion_status: promotion.status,
1195
+ remote: promotion.remote,
1196
+ target_ref: promotion.target_ref,
1197
+ pr_url: promotion.pr_url,
1198
+ },
1199
+ });
1200
+ const projected = projectRuntimeArtifacts(projectRoot, activeSession, { runState: next, write: true });
1201
+ return {
1202
+ run: projected.run,
1203
+ commitRecord,
1204
+ promotion,
1205
+ promotionReadiness,
1206
+ projected,
1207
+ };
1208
+ }
1209
+
1210
+ function recoverRuntimeState(projectRoot, activeSession, options = {}) {
1211
+ const runtime = loadRuntimeModule();
1212
+ if (!runtime || typeof runtime.loadJournal !== 'function') {
1213
+ throw new Error('Runtime package não está disponível. Rode npm run build:runtime.');
1214
+ }
1215
+ const current = options.runState || readRunState(projectRoot, activeSession);
1216
+ if (!current || !current.run_id) {
1217
+ throw new Error('Nenhum ACTIVE-RUN disponível para recover.');
1218
+ }
1219
+ const journal = runtime.loadJournal(projectRoot, current.run_id);
1220
+ if (!journal) {
1221
+ throw new Error(`Journal ausente para run ${current.run_id}.`);
1222
+ }
1223
+ const canonicalLive = reduceCanonicalRunStateLive(projectRoot, activeSession, {
1224
+ runId: current.run_id,
1225
+ runState: current,
1226
+ }) || mergeCanonicalStateWithRunState(current.canonical_state, current, activeSession, current.compiled_graph);
1227
+ const graphNodes = current.compiled_graph && current.compiled_graph.nodes && typeof current.compiled_graph.nodes === 'object'
1228
+ ? new Set(Object.keys(current.compiled_graph.nodes))
1229
+ : new Set();
1230
+ const orphanWorkItems = Array.from(new Set([
1231
+ ...((journal.pending_gates || []).filter((id) => id && !graphNodes.has(String(id)))),
1232
+ ...((current.active_tasks || []).filter((id) => id && !graphNodes.has(String(id)))),
1233
+ ]));
1234
+ const nextStatus = journal.scheduler_state === 'paused'
1235
+ ? 'paused'
1236
+ : journal.scheduler_state === 'blocked'
1237
+ ? 'blocked'
1238
+ : current.status || 'planned';
1239
+ const verificationArtifacts = loadRuntimeVerificationArtifacts(projectRoot, current);
1240
+ const consistency = buildRecoveryConsistency(
1241
+ projectRoot,
1242
+ activeSession,
1243
+ current,
1244
+ journal,
1245
+ verificationArtifacts
1246
+ );
1247
+ const recoverySummary = {
1248
+ recovered_at: new Date().toISOString(),
1249
+ journal_state: journal.scheduler_state,
1250
+ orphan_work_items: orphanWorkItems,
1251
+ pending_gates: readRuntimeGates(projectRoot, activeSession, { runId: current.run_id }).pending.map((gate) => gate.gate_id),
1252
+ consistency,
1253
+ };
1254
+ const runDir = path.join(projectRoot, '.oxe', 'runs', current.run_id);
1255
+ ensureDir(runDir);
1256
+ const recoverySummaryPath = path.join(runDir, 'recovery-summary.json');
1257
+ fs.writeFileSync(recoverySummaryPath, JSON.stringify(recoverySummary, null, 2), 'utf8');
1258
+ const summaryPath = writeRecoverySummaryMarkdown(projectRoot, activeSession, current, recoverySummary);
1259
+ recoverySummary.json_ref = path.relative(projectRoot, recoverySummaryPath).replace(/\\/g, '/');
1260
+ recoverySummary.markdown_ref = path.relative(projectRoot, summaryPath).replace(/\\/g, '/');
1261
+ const next = writeRunState(projectRoot, activeSession, {
1262
+ ...current,
1263
+ status: nextStatus,
1264
+ canonical_state: serializeCanonicalState(canonicalLive),
1265
+ recovery_summary: recoverySummary,
1266
+ metrics: {
1267
+ ...(current.metrics || {}),
1268
+ recover_count: Number((current.metrics || {}).recover_count || 0) + 1,
1269
+ last_action: 'recover',
1270
+ },
1271
+ });
1272
+ appendEvent(projectRoot, activeSession, {
1273
+ type: 'RunStarted',
1274
+ run_id: next.run_id,
1275
+ payload: {
1276
+ recovered: true,
1277
+ orphan_work_items: orphanWorkItems,
1278
+ journal_state: journal.scheduler_state,
1279
+ },
1280
+ });
1281
+ return {
1282
+ run: next,
1283
+ journal,
1284
+ recoverySummary,
1285
+ };
1286
+ }
1287
+
1288
+ function operationalPaths(projectRoot, activeSession) {
1289
+ const oxeDir = path.join(projectRoot, '.oxe');
1290
+ const scopeRoot = activeSession ? path.join(oxeDir, ...String(activeSession).split('/')) : oxeDir;
1291
+ const executionRoot = activeSession ? path.join(scopeRoot, 'execution') : oxeDir;
1292
+ const runsDir = path.join(executionRoot, 'runs');
1293
+ return {
1294
+ oxeDir,
1295
+ scopeRoot,
1296
+ executionRoot,
1297
+ runsDir,
1298
+ events: path.join(executionRoot, 'OXE-EVENTS.ndjson'),
1299
+ activeRun: path.join(executionRoot, 'ACTIVE-RUN.json'),
1300
+ projectLessons: path.join(oxeDir, 'global', 'LESSONS.md'),
1301
+ sessionManifest: activeSession ? path.join(scopeRoot, 'SESSION.md') : null,
1302
+ verify: activeSession ? path.join(scopeRoot, 'verification', 'VERIFY.md') : path.join(oxeDir, 'VERIFY.md'),
1303
+ investigations: activeSession ? path.join(scopeRoot, 'research', 'INVESTIGATIONS.md') : path.join(oxeDir, 'INVESTIGATIONS.md'),
1304
+ capabilitiesDir: path.join(oxeDir, 'capabilities'),
1305
+ capabilitiesIndex: path.join(oxeDir, 'CAPABILITIES.md'),
1306
+ checkpoints: activeSession ? path.join(scopeRoot, 'execution', 'CHECKPOINTS.md') : path.join(oxeDir, 'CHECKPOINTS.md'),
1307
+ gates: gateStoragePath(projectRoot, activeSession),
1308
+ auditTrail: path.join(oxeDir, 'AUDIT-TRAIL.ndjson'),
1309
+ };
1310
+ }
1311
+
1312
+ function makeRunId() {
1313
+ return `oxe-run-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
1314
+ }
1315
+
1316
+ function dedupeNodes(nodes) {
1317
+ const byId = new Map();
1318
+ for (const node of nodes || []) {
1319
+ if (!node || !node.id) continue;
1320
+ byId.set(node.id, { ...byId.get(node.id), ...node });
1321
+ }
1322
+ return Array.from(byId.values());
1323
+ }
1324
+
1325
+ function dedupeEdges(edges) {
1326
+ const byKey = new Map();
1327
+ for (const edge of edges || []) {
1328
+ if (!edge || !edge.from || !edge.to) continue;
1329
+ const key = [edge.from, edge.to, edge.type || 'link', edge.label || ''].join('::');
1330
+ byKey.set(key, { ...byKey.get(key), ...edge });
1331
+ }
1332
+ return Array.from(byKey.values());
1333
+ }
1334
+
1335
+ function buildOperationalGraph(runState = {}) {
1336
+ const currentWave = runState.current_wave == null ? null : Number(runState.current_wave);
1337
+ const activeTasks = Array.isArray(runState.active_tasks) ? runState.active_tasks.map(String) : [];
1338
+ const pendingCheckpoints = Array.isArray(runState.pending_checkpoints) ? runState.pending_checkpoints.map(String) : [];
1339
+ const multiAgent = runState.multi_agent && typeof runState.multi_agent === 'object'
1340
+ ? runState.multi_agent
1341
+ : null;
1342
+ const azureContext = runState.provider_context && runState.provider_context.azure && typeof runState.provider_context.azure === 'object'
1343
+ ? runState.provider_context.azure
1344
+ : null;
1345
+ const baseNodes = (((runState.graph || {}).nodes) || []).map((node) => ({ ...node }));
1346
+ const baseEdges = (((runState.graph || {}).edges) || []).map((edge) => ({ ...edge }));
1347
+ const generatedNodes = [
1348
+ {
1349
+ id: `run:${runState.run_id || 'active'}`,
1350
+ label: runState.run_id || 'active-run',
1351
+ kind: 'run',
1352
+ status: runState.status || 'planned',
1353
+ detail: runState.cursor && runState.cursor.mode ? `mode:${runState.cursor.mode}` : 'run ativo',
1354
+ },
1355
+ {
1356
+ id: 'agent:main-executor',
1357
+ label: 'main-executor',
1358
+ kind: 'agent',
1359
+ status: /running|replaying/.test(String(runState.status || '')) ? 'active' : 'planned',
1360
+ detail: activeTasks.length ? activeTasks.join(', ') : 'sem tarefas ativas',
1361
+ },
1362
+ ];
1363
+ const generatedEdges = [
1364
+ {
1365
+ from: `run:${runState.run_id || 'active'}`,
1366
+ to: 'agent:main-executor',
1367
+ type: 'handoff',
1368
+ status: /running|replaying/.test(String(runState.status || '')) ? 'active' : 'planned',
1369
+ reason: 'orquestração principal',
1370
+ },
1371
+ ];
1372
+ if (currentWave != null) {
1373
+ generatedNodes.push({
1374
+ id: `wave:${currentWave}`,
1375
+ label: `wave-${currentWave}`,
1376
+ kind: 'wave',
1377
+ status: runState.status || 'planned',
1378
+ detail: activeTasks.length ? `${activeTasks.length} tarefa(s)` : 'sem tarefas ativas',
1379
+ });
1380
+ generatedEdges.push({
1381
+ from: `run:${runState.run_id || 'active'}`,
1382
+ to: `wave:${currentWave}`,
1383
+ type: 'contains',
1384
+ status: 'done',
1385
+ });
1386
+ }
1387
+ for (const taskId of activeTasks) {
1388
+ generatedNodes.push({
1389
+ id: `task:${taskId}`,
1390
+ label: taskId,
1391
+ kind: 'task',
1392
+ status: /paused|waiting_approval/.test(String(runState.status || '')) ? 'blocked' : 'active',
1393
+ detail: currentWave != null ? `wave ${currentWave}` : 'task ativa',
1394
+ });
1395
+ generatedEdges.push({
1396
+ from: currentWave != null ? `wave:${currentWave}` : 'agent:main-executor',
1397
+ to: `task:${taskId}`,
1398
+ type: 'executes',
1399
+ status: /running|replaying/.test(String(runState.status || '')) ? 'active' : 'planned',
1400
+ });
1401
+ }
1402
+ for (const checkpointId of pendingCheckpoints) {
1403
+ generatedNodes.push({
1404
+ id: `checkpoint:${checkpointId}`,
1405
+ label: checkpointId,
1406
+ kind: 'checkpoint',
1407
+ status: 'pending_approval',
1408
+ detail: 'aprovação pendente',
1409
+ });
1410
+ generatedEdges.push({
1411
+ from: activeTasks.length ? `task:${activeTasks[activeTasks.length - 1]}` : (currentWave != null ? `wave:${currentWave}` : `run:${runState.run_id || 'active'}`),
1412
+ to: `checkpoint:${checkpointId}`,
1413
+ type: 'gate',
1414
+ status: 'blocked',
1415
+ reason: 'checkpoint pendente',
1416
+ });
1417
+ }
1418
+ if (multiAgent && multiAgent.enabled) {
1419
+ const ownership = Array.isArray(multiAgent.ownership) ? multiAgent.ownership : [];
1420
+ const handoffs = Array.isArray(multiAgent.handoffs) ? multiAgent.handoffs : [];
1421
+ const agents = Array.isArray(multiAgent.agents) ? multiAgent.agents : [];
1422
+ for (const agent of agents) {
1423
+ const agentId = String(agent.agent_id || agent.id || 'agent');
1424
+ generatedNodes.push({
1425
+ id: `agent:${agentId}`,
1426
+ label: agentId,
1427
+ kind: 'agent',
1428
+ status: String(agent.status || agent.outcome || 'active'),
1429
+ detail: agent.role || agent.profile || multiAgent.mode || 'multi-agent',
1430
+ });
1431
+ generatedEdges.push({
1432
+ from: `run:${runState.run_id || 'active'}`,
1433
+ to: `agent:${agentId}`,
1434
+ type: 'handoff',
1435
+ status: String(agent.status || 'active'),
1436
+ reason: 'coordenação multi-agent',
1437
+ });
1438
+ }
1439
+ for (const item of ownership) {
1440
+ const taskId = String(item.work_item_id || item.task_id || '');
1441
+ const agentId = String(item.agent_id || '');
1442
+ if (!taskId || !agentId) continue;
1443
+ generatedNodes.push({
1444
+ id: `task:${taskId}`,
1445
+ label: taskId,
1446
+ kind: 'task',
1447
+ status: String(item.status || 'assigned'),
1448
+ detail: `owner ${agentId}`,
1449
+ });
1450
+ generatedEdges.push({
1451
+ from: `agent:${agentId}`,
1452
+ to: `task:${taskId}`,
1453
+ type: 'owns',
1454
+ status: String(item.status || 'assigned'),
1455
+ reason: 'ownership canónica',
1456
+ });
1457
+ }
1458
+ for (const handoff of handoffs) {
1459
+ const fromAgent = String(handoff.from_agent_id || handoff.from || '');
1460
+ const toAgent = String(handoff.to_agent_id || handoff.to || '');
1461
+ if (!fromAgent || !toAgent) continue;
1462
+ generatedEdges.push({
1463
+ from: `agent:${fromAgent}`,
1464
+ to: `agent:${toAgent}`,
1465
+ type: 'handoff',
1466
+ status: String(handoff.status || 'completed'),
1467
+ reason: handoff.reason || handoff.work_item_id || 'handoff cooperativo',
1468
+ });
1469
+ }
1470
+ }
1471
+ if (azureContext) {
1472
+ const azureStatus = azureContext.login_active
1473
+ ? (azureContext.pending_approval_count > 0 ? 'blocked' : 'active')
1474
+ : 'warning';
1475
+ generatedNodes.push({
1476
+ id: 'provider:azure',
1477
+ label: 'azure',
1478
+ kind: 'provider',
1479
+ status: azureStatus,
1480
+ detail: azureContext.subscription_name || azureContext.subscription_id || azureContext.cloud || 'contexto azure',
1481
+ });
1482
+ generatedEdges.push({
1483
+ from: `run:${runState.run_id || 'active'}`,
1484
+ to: 'provider:azure',
1485
+ type: 'provider',
1486
+ status: azureStatus,
1487
+ reason: 'contexto cloud ativo',
1488
+ });
1489
+ const lastOperation = azureContext.last_operation && typeof azureContext.last_operation === 'object'
1490
+ ? azureContext.last_operation
1491
+ : null;
1492
+ if (lastOperation) {
1493
+ const capabilityId = String(lastOperation.capability_id || lastOperation.domain || 'azure-operation');
1494
+ generatedNodes.push({
1495
+ id: `capability:${capabilityId}`,
1496
+ label: capabilityId,
1497
+ kind: 'capability',
1498
+ status: String(lastOperation.phase || lastOperation.status || 'planned'),
1499
+ detail: lastOperation.command_display || lastOperation.operation || 'operação Azure',
1500
+ });
1501
+ generatedEdges.push({
1502
+ from: 'provider:azure',
1503
+ to: `capability:${capabilityId}`,
1504
+ type: 'tool_call',
1505
+ status: String(lastOperation.phase || lastOperation.status || 'planned'),
1506
+ reason: lastOperation.operation || lastOperation.domain || 'ação Azure',
1507
+ });
1508
+ const refs = Array.isArray(lastOperation.resource_refs) ? lastOperation.resource_refs : [];
1509
+ for (let index = 0; index < refs.length; index += 1) {
1510
+ const ref = refs[index];
1511
+ const normalized = ref && typeof ref === 'object' ? ref : { name: String(ref || 'resource') };
1512
+ const resourceId = normalized.id || normalized.name || normalized.resource_id || normalized.type || `resource-${index + 1}`;
1513
+ generatedNodes.push({
1514
+ id: `resource:${resourceId}`,
1515
+ label: normalized.name || normalized.type || resourceId,
1516
+ kind: 'resource',
1517
+ status: String(lastOperation.phase || lastOperation.status || 'planned'),
1518
+ detail: [normalized.type, normalized.resource_group, normalized.location].filter(Boolean).join(' · ') || azureContext.subscription_name || 'recurso Azure',
1519
+ });
1520
+ generatedEdges.push({
1521
+ from: `capability:${capabilityId}`,
1522
+ to: `resource:${resourceId}`,
1523
+ type: 'targets',
1524
+ status: String(lastOperation.phase || lastOperation.status || 'planned'),
1525
+ reason: normalized.scope || normalized.kind || 'recurso alvo',
1526
+ });
1527
+ }
1528
+ if (lastOperation.pending_checkpoint_id) {
1529
+ generatedNodes.push({
1530
+ id: `checkpoint:${lastOperation.pending_checkpoint_id}`,
1531
+ label: String(lastOperation.pending_checkpoint_id),
1532
+ kind: 'checkpoint',
1533
+ status: 'pending_approval',
1534
+ detail: 'gate Azure pendente',
1535
+ });
1536
+ generatedEdges.push({
1537
+ from: `capability:${capabilityId}`,
1538
+ to: `checkpoint:${lastOperation.pending_checkpoint_id}`,
1539
+ type: 'gate',
1540
+ status: 'blocked',
1541
+ reason: 'approval policy do provider Azure',
1542
+ });
1543
+ }
1544
+ }
1545
+ }
1546
+ return {
1547
+ nodes: dedupeNodes([...baseNodes, ...generatedNodes]),
1548
+ edges: dedupeEdges([...baseEdges, ...generatedEdges]),
1549
+ };
1550
+ }
1551
+
1552
+ function appendEvent(projectRoot, activeSession, event = {}) {
1553
+ const p = operationalPaths(projectRoot, activeSession);
1554
+ ensureDirForFile(p.events);
1555
+ const entry = {
1556
+ event_id: event.event_id || `evt-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
1557
+ type: String(event.type || 'custom'),
1558
+ timestamp: event.timestamp || new Date().toISOString(),
1559
+ run_id: event.run_id || null,
1560
+ session_id: activeSession || event.session_id || null,
1561
+ wave_id: event.wave_id || null,
1562
+ task_id: event.task_id || event.work_item_id || null,
1563
+ work_item_id: event.work_item_id || event.task_id || null,
1564
+ attempt_id: event.attempt_id || null,
1565
+ agent_id: event.agent_id || null,
1566
+ causation_id: event.causation_id || null,
1567
+ correlation_id: event.correlation_id || null,
1568
+ payload: event.payload && typeof event.payload === 'object' ? event.payload : {},
1569
+ };
1570
+ fs.appendFileSync(p.events, `${JSON.stringify(entry)}\n`, 'utf8');
1571
+ return entry;
1572
+ }
1573
+
1574
+ function readEvents(projectRoot, activeSession) {
1575
+ const p = operationalPaths(projectRoot, activeSession);
1576
+ const raw = readTextIfExists(p.events) || '';
1577
+ return raw
1578
+ .split(/\r?\n/)
1579
+ .map((line) => line.trim())
1580
+ .filter(Boolean)
1581
+ .map((line) => {
1582
+ try {
1583
+ return JSON.parse(line);
1584
+ } catch {
1585
+ return null;
1586
+ }
1587
+ })
1588
+ .filter(Boolean);
1589
+ }
1590
+
1591
+ function summarizeEvents(events) {
1592
+ const byType = {};
1593
+ for (const event of events) {
1594
+ const key = String(event.type || 'custom');
1595
+ byType[key] = (byType[key] || 0) + 1;
1596
+ }
1597
+ return {
1598
+ total: events.length,
1599
+ byType,
1600
+ lastEvent: events.length ? events[events.length - 1] : null,
1601
+ };
1602
+ }
1603
+
1604
+ function writeRunState(projectRoot, activeSession, runState = {}) {
1605
+ const p = operationalPaths(projectRoot, activeSession);
1606
+ const runId = String(runState.run_id || makeRunId());
1607
+ const canonicalState = runState.canonical_state ? serializeCanonicalState(hydrateCanonicalState(runState.canonical_state)) : null;
1608
+ const payload = {
1609
+ run_id: runId,
1610
+ status: VALID_RUN_STATUSES.has(String(runState.status || 'planned')) ? String(runState.status || 'planned') : 'planned',
1611
+ created_at: runState.created_at || new Date().toISOString(),
1612
+ updated_at: runState.updated_at || new Date().toISOString(),
1613
+ plan_ref: runState.plan_ref || 'PLAN.md',
1614
+ session_id: activeSession || null,
1615
+ current_wave: runState.current_wave == null ? null : Number(runState.current_wave),
1616
+ cursor: {
1617
+ wave: runState.cursor && runState.cursor.wave != null ? Number(runState.cursor.wave) : null,
1618
+ task: runState.cursor && runState.cursor.task ? String(runState.cursor.task) : null,
1619
+ mode: runState.cursor && runState.cursor.mode ? String(runState.cursor.mode) : null,
1620
+ },
1621
+ active_tasks: Array.isArray(runState.active_tasks) ? runState.active_tasks.map(String) : [],
1622
+ pending_checkpoints: Array.isArray(runState.pending_checkpoints) ? runState.pending_checkpoints.map(String) : [],
1623
+ retries: Array.isArray(runState.retries) ? runState.retries : [],
1624
+ failures: Array.isArray(runState.failures) ? runState.failures : [],
1625
+ evidence: Array.isArray(runState.evidence) ? runState.evidence : [],
1626
+ provider_context: runState.provider_context && typeof runState.provider_context === 'object'
1627
+ ? runState.provider_context
1628
+ : {},
1629
+ graph: runState.graph && typeof runState.graph === 'object'
1630
+ ? {
1631
+ nodes: Array.isArray(runState.graph.nodes) ? runState.graph.nodes : [],
1632
+ edges: Array.isArray(runState.graph.edges) ? runState.graph.edges : [],
1633
+ }
1634
+ : { nodes: [], edges: [] },
1635
+ compiled_graph: runState.compiled_graph && typeof runState.compiled_graph === 'object'
1636
+ ? runState.compiled_graph
1637
+ : null,
1638
+ graph_version: runState.graph_version || null,
1639
+ canonical_state: canonicalState,
1640
+ verification_suite: runState.verification_suite && typeof runState.verification_suite === 'object'
1641
+ ? runState.verification_suite
1642
+ : null,
1643
+ verification_results: Array.isArray(runState.verification_results) ? runState.verification_results : [],
1644
+ verification_check_results: Array.isArray(runState.verification_check_results) ? runState.verification_check_results : [],
1645
+ verification_manifest: runState.verification_manifest && typeof runState.verification_manifest === 'object'
1646
+ ? runState.verification_manifest
1647
+ : null,
1648
+ residual_risks: runState.residual_risks && typeof runState.residual_risks === 'object'
1649
+ ? runState.residual_risks
1650
+ : null,
1651
+ verification_evidence_coverage: runState.verification_evidence_coverage && typeof runState.verification_evidence_coverage === 'object'
1652
+ ? runState.verification_evidence_coverage
1653
+ : null,
1654
+ verification_gaps: Array.isArray(runState.verification_gaps) ? runState.verification_gaps.map(String) : [],
1655
+ ci_checks: runState.ci_checks && typeof runState.ci_checks === 'object' ? runState.ci_checks : null,
1656
+ delivery: runState.delivery && typeof runState.delivery === 'object' ? runState.delivery : null,
1657
+ recovery_summary: runState.recovery_summary && typeof runState.recovery_summary === 'object' ? runState.recovery_summary : null,
1658
+ multi_agent: runState.multi_agent && typeof runState.multi_agent === 'object' ? runState.multi_agent : null,
1659
+ projections: runState.projections && typeof runState.projections === 'object' ? runState.projections : {},
1660
+ metrics: runState.metrics && typeof runState.metrics === 'object' ? runState.metrics : {},
1661
+ };
1662
+ payload.graph = buildOperationalGraph(payload);
1663
+ ensureDir(p.runsDir);
1664
+ fs.writeFileSync(path.join(p.runsDir, `${runId}.json`), JSON.stringify(payload, null, 2), 'utf8');
1665
+ fs.writeFileSync(
1666
+ p.activeRun,
1667
+ JSON.stringify(
1668
+ {
1669
+ run_id: runId,
1670
+ status: payload.status,
1671
+ updated_at: payload.updated_at,
1672
+ current_wave: payload.current_wave,
1673
+ cursor: payload.cursor,
1674
+ provider_context: payload.provider_context,
1675
+ canonical_state: payload.canonical_state,
1676
+ compiled_graph: payload.compiled_graph,
1677
+ graph_version: payload.graph_version,
1678
+ multi_agent: payload.multi_agent,
1679
+ },
1680
+ null,
1681
+ 2
1682
+ ),
1683
+ 'utf8'
1684
+ );
1685
+ return payload;
1686
+ }
1687
+
1688
+ function readRunState(projectRoot, activeSession) {
1689
+ const p = operationalPaths(projectRoot, activeSession);
1690
+ const activeRaw = readTextIfExists(p.activeRun);
1691
+ let runId = null;
1692
+ if (activeRaw) {
1693
+ try {
1694
+ runId = JSON.parse(activeRaw).run_id || null;
1695
+ } catch {
1696
+ runId = null;
1697
+ }
1698
+ }
1699
+ if (!runId && fs.existsSync(p.runsDir)) {
1700
+ const files = fs.readdirSync(p.runsDir).filter((name) => name.endsWith('.json')).sort();
1701
+ if (files.length) runId = files[files.length - 1].replace(/\.json$/i, '');
1702
+ }
1703
+ if (!runId) return null;
1704
+ const filePath = path.join(p.runsDir, `${runId}.json`);
1705
+ const raw = readTextIfExists(filePath);
1706
+ if (!raw) return null;
1707
+ try {
1708
+ return JSON.parse(raw);
1709
+ } catch {
1710
+ return null;
1711
+ }
1712
+ }
1713
+
1714
+ function readGitActivity(projectRoot, since) {
1715
+ if (!projectRoot || !since) return [];
1716
+ try {
1717
+ const result = spawnSync(
1718
+ 'git',
1719
+ ['log', `--since=${since}`, '--pretty=format:%H %aI'],
1720
+ { cwd: projectRoot, encoding: 'utf8', timeout: 5000 }
1721
+ );
1722
+ if (result.status !== 0 || result.error || !result.stdout) return [];
1723
+ return result.stdout
1724
+ .split(/\r?\n/)
1725
+ .map((line) => line.trim())
1726
+ .filter(Boolean)
1727
+ .map((line) => {
1728
+ const idx = line.indexOf(' ');
1729
+ return {
1730
+ hash: idx !== -1 ? line.slice(0, idx) : line,
1731
+ timestamp: idx !== -1 ? line.slice(idx + 1) : '',
1732
+ };
1733
+ });
1734
+ } catch {
1735
+ return [];
1736
+ }
1737
+ }
1738
+
1739
+ function verifyGitEvidence(runState, projectRoot) {
1740
+ const warns = [];
1741
+ if (!runState || !projectRoot || !runState.created_at) return warns;
1742
+ const commits = readGitActivity(projectRoot, runState.created_at);
1743
+ if (
1744
+ commits.length === 0 &&
1745
+ (String(runState.status || '') === 'completed' ||
1746
+ (Array.isArray(runState.evidence) && runState.evidence.length > 0))
1747
+ ) {
1748
+ warns.push('Nenhum commit git encontrado desde o início do run — confirme se o trabalho foi commitado');
1749
+ }
1750
+ return warns;
1751
+ }
1752
+
1753
+ function runtimeStateWarnings(runState, checkpoints = [], projectRoot = null) {
1754
+ const warns = [];
1755
+ if (!runState) return warns;
1756
+ if (!VALID_RUN_STATUSES.has(String(runState.status || ''))) {
1757
+ warns.push(`ACTIVE-RUN inválido: status "${runState.status}" fora do contrato`);
1758
+ }
1759
+ const cursorWave = runState.cursor && runState.cursor.wave != null ? Number(runState.cursor.wave) : null;
1760
+ if (cursorWave != null && runState.current_wave != null && Number(cursorWave) !== Number(runState.current_wave)) {
1761
+ warns.push('ACTIVE-RUN com cursor de onda divergente do current_wave');
1762
+ }
1763
+ const pending = new Set((runState.pending_checkpoints || []).map(String));
1764
+ for (const cp of checkpoints) {
1765
+ if (/pending_approval/i.test(String(cp.status || '')) && !pending.has(String(cp.id))) {
1766
+ warns.push(`Checkpoint ${cp.id} pendente no índice, mas ausente do ACTIVE-RUN`);
1767
+ }
1768
+ }
1769
+ for (const edge of (((runState.graph || {}).edges) || [])) {
1770
+ if (edge.type === 'handoff' && (!edge.from || !edge.to)) {
1771
+ warns.push('Grafo operacional contém handoff sem origem ou destino');
1772
+ break;
1773
+ }
1774
+ }
1775
+ if (projectRoot) {
1776
+ for (const w of verifyGitEvidence(runState, projectRoot)) {
1777
+ warns.push(w);
1778
+ }
1779
+ }
1780
+ return warns;
1781
+ }
1782
+
1783
+ function parseCapabilityManifest(text) {
1784
+ const fm = parseFrontmatter(text);
1785
+ const approval = String(fm.approval_policy || fm.policy || '').trim() || null;
1786
+ const sideEffects = parseArrayField(fm.side_effects || '');
1787
+ const envs = parseArrayField(fm.requires_env || '');
1788
+ const evidence = parseArrayField(fm.evidence_outputs || '');
1789
+ const sessionCompat = parseArrayField(fm.session_compatibility || fm.session_scope || '');
1790
+ const objectiveMatch = String(text || '').match(/##\s*Objetivo\s*\n+([\s\S]*?)(?=\n##\s|\n#[^\#]|$)/i);
1791
+ const desc = objectiveMatch ? objectiveMatch[1].split(/\r?\n/).map((line) => line.replace(/^-\s+/, '').trim()).filter(Boolean)[0] || '' : '';
1792
+ return {
1793
+ id: String(fm.id || '').trim(),
1794
+ version: String(fm.version || '').trim() || '1',
1795
+ type: String(fm.type || 'local').trim(),
1796
+ status: String(fm.status || 'active').trim(),
1797
+ scope: String(fm.scope || 'mixed').trim(),
1798
+ entrypoint: String(fm.entrypoint || '').trim(),
1799
+ approvalPolicy: approval,
1800
+ sideEffects,
1801
+ requiresEnv: envs,
1802
+ evidenceOutputs: evidence,
1803
+ sessionCompatibility: sessionCompat,
1804
+ description: desc || 'Capability local do projeto',
1805
+ };
1806
+ }
1807
+
1808
+ function readCapabilityCatalog(projectRoot) {
1809
+ const p = operationalPaths(projectRoot, null);
1810
+ if (!fs.existsSync(p.capabilitiesDir)) return [];
1811
+ return fs
1812
+ .readdirSync(p.capabilitiesDir, { withFileTypes: true })
1813
+ .filter((entry) => entry.isDirectory())
1814
+ .map((entry) => {
1815
+ const manifestPath = path.join(p.capabilitiesDir, entry.name, 'CAPABILITY.md');
1816
+ const raw = readTextIfExists(manifestPath);
1817
+ if (!raw) return null;
1818
+ return { ...parseCapabilityManifest(raw), manifestPath };
1819
+ })
1820
+ .filter(Boolean)
1821
+ .sort((a, b) => a.id.localeCompare(b.id));
1822
+ }
1823
+
1824
+ function capabilityCatalogWarnings(projectRoot) {
1825
+ const warns = [];
1826
+ for (const cap of readCapabilityCatalog(projectRoot)) {
1827
+ if (!cap.id) warns.push(`Capability em ${cap.manifestPath} sem id`);
1828
+ if (!VALID_CAPABILITY_TYPES.has(cap.type)) warns.push(`Capability ${cap.id || cap.manifestPath}: type "${cap.type}" inválido`);
1829
+ if (!cap.approvalPolicy || !VALID_APPROVAL_POLICIES.has(cap.approvalPolicy)) {
1830
+ warns.push(`Capability ${cap.id || cap.manifestPath}: approval_policy ausente ou inválida`);
1831
+ }
1832
+ if (!cap.entrypoint) warns.push(`Capability ${cap.id || cap.manifestPath}: entrypoint ausente`);
1833
+ if (!cap.evidenceOutputs.length) warns.push(`Capability ${cap.id || cap.manifestPath}: evidence_outputs ausente`);
1834
+ }
1835
+ return warns;
1836
+ }
1837
+
1838
+ function buildMemoryLayers(projectRoot, activeSession) {
1839
+ const p = operationalPaths(projectRoot, activeSession);
1840
+ return {
1841
+ readOrder: ['runtime_state', 'session_memory', 'project_memory', 'evidence'],
1842
+ runtime_state: {
1843
+ source: activeSession ? path.join('.oxe', activeSession, 'execution', 'STATE.md') : '.oxe/STATE.md + ACTIVE-RUN.json',
1844
+ exists: fs.existsSync(activeSession ? path.join(projectRoot, '.oxe', activeSession, 'execution', 'STATE.md') : path.join(projectRoot, '.oxe', 'STATE.md')),
1845
+ },
1846
+ session_memory: {
1847
+ source: activeSession ? path.join('.oxe', activeSession, 'SESSION.md') : null,
1848
+ exists: Boolean(p.sessionManifest && fs.existsSync(p.sessionManifest)),
1849
+ },
1850
+ project_memory: {
1851
+ source: '.oxe/global/LESSONS.md',
1852
+ exists: fs.existsSync(p.projectLessons),
1853
+ },
1854
+ evidence: {
1855
+ source: [
1856
+ activeSession ? path.join('.oxe', activeSession, 'research', 'INVESTIGATIONS.md') : '.oxe/INVESTIGATIONS.md',
1857
+ activeSession ? path.join('.oxe', activeSession, 'verification', 'VERIFY.md') : '.oxe/VERIFY.md',
1858
+ ],
1859
+ exists: fs.existsSync(p.investigations) || fs.existsSync(p.verify),
1860
+ },
1861
+ };
1862
+ }
1863
+
1864
+ function applyRuntimeAction(projectRoot, activeSession, input = {}) {
1865
+ const action = String(input.action || 'status').toLowerCase();
1866
+ const now = new Date().toISOString();
1867
+ const current = readRunState(projectRoot, activeSession);
1868
+ const wave = input.wave == null || input.wave === '' ? null : Number(input.wave);
1869
+ const task = input.task ? String(input.task) : null;
1870
+ const mode = input.mode ? String(input.mode) : (task ? 'task' : wave != null ? 'wave' : 'complete');
1871
+ const pendingCheckpoints = Array.isArray(input.pending_checkpoints)
1872
+ ? input.pending_checkpoints.map(String)
1873
+ : current && Array.isArray(current.pending_checkpoints)
1874
+ ? current.pending_checkpoints
1875
+ : [];
1876
+
1877
+ if (action === 'start') {
1878
+ const next = writeRunState(projectRoot, activeSession, {
1879
+ run_id: input.run_id || makeRunId(),
1880
+ status: 'running',
1881
+ created_at: now,
1882
+ updated_at: now,
1883
+ plan_ref: input.plan_ref || 'PLAN.md',
1884
+ current_wave: wave,
1885
+ cursor: { wave, task, mode },
1886
+ active_tasks: task ? [task] : Array.isArray(input.active_tasks) ? input.active_tasks.map(String) : [],
1887
+ pending_checkpoints: pendingCheckpoints,
1888
+ retries: [],
1889
+ failures: [],
1890
+ evidence: [],
1891
+ metrics: {
1892
+ transitions: 1,
1893
+ pause_count: 0,
1894
+ replay_count: 0,
1895
+ last_action: 'start',
1896
+ },
1897
+ });
1898
+ appendEvent(projectRoot, activeSession, {
1899
+ type: 'run_started',
1900
+ run_id: next.run_id,
1901
+ wave_id: wave != null ? `wave-${wave}` : null,
1902
+ task_id: task,
1903
+ payload: { reason: input.reason || 'run inicializado', mode },
1904
+ });
1905
+ return next;
1906
+ }
1907
+
1908
+ if (!current) {
1909
+ throw new Error('Nenhum ACTIVE-RUN disponível para esta ação.');
1910
+ }
1911
+
1912
+ const next = {
1913
+ ...current,
1914
+ updated_at: now,
1915
+ current_wave: wave != null ? wave : current.current_wave,
1916
+ cursor: {
1917
+ wave: wave != null ? wave : current.cursor && current.cursor.wave != null ? current.cursor.wave : current.current_wave,
1918
+ task: task || (current.cursor ? current.cursor.task : null),
1919
+ mode: input.mode ? String(input.mode) : (current.cursor && current.cursor.mode) || mode,
1920
+ },
1921
+ active_tasks: task ? [task] : Array.isArray(input.active_tasks) ? input.active_tasks.map(String) : current.active_tasks || [],
1922
+ pending_checkpoints: pendingCheckpoints,
1923
+ metrics: {
1924
+ ...(current.metrics || {}),
1925
+ transitions: Number((current.metrics || {}).transitions || 0) + 1,
1926
+ last_action: action,
1927
+ },
1928
+ };
1929
+
1930
+ if (action === 'pause') {
1931
+ next.status = 'paused';
1932
+ next.metrics.pause_count = Number((current.metrics || {}).pause_count || 0) + 1;
1933
+ } else if (action === 'resume') {
1934
+ next.status = pendingCheckpoints.length ? 'waiting_approval' : 'running';
1935
+ } else if (action === 'replay') {
1936
+ next.status = 'replaying';
1937
+ next.metrics.replay_count = Number((current.metrics || {}).replay_count || 0) + 1;
1938
+ } else {
1939
+ throw new Error(`Ação de runtime desconhecida: ${action}`);
1940
+ }
1941
+
1942
+ const saved = writeRunState(projectRoot, activeSession, next);
1943
+ appendEvent(projectRoot, activeSession, {
1944
+ type: action === 'pause' ? 'run_paused' : action === 'resume' ? 'run_resumed' : 'run_replay_requested',
1945
+ run_id: saved.run_id,
1946
+ wave_id: saved.current_wave != null ? `wave-${saved.current_wave}` : null,
1947
+ task_id: saved.cursor && saved.cursor.task ? saved.cursor.task : null,
1948
+ payload: {
1949
+ reason: input.reason || '',
1950
+ mode: saved.cursor && saved.cursor.mode ? saved.cursor.mode : null,
1951
+ pending_checkpoints: saved.pending_checkpoints || [],
1952
+ },
1953
+ });
1954
+ return saved;
1955
+ }
1956
+
1957
+ /**
1958
+ * Reconstrói a timeline de eventos de OXE-EVENTS.ndjson com deltas entre transições.
1959
+ * @param {string} projectRoot
1960
+ * @param {string|null} activeSession
1961
+ * @param {{ fromEventId?: string, runId?: string, waveId?: number, limit?: number, writeReport?: boolean }} [options]
1962
+ */
1963
+ function replayEvents(projectRoot, activeSession, options = {}) {
1964
+ let events = readEvents(projectRoot, activeSession);
1965
+
1966
+ if (options.runId) {
1967
+ events = events.filter((e) => e.run_id === options.runId);
1968
+ }
1969
+ if (options.fromEventId) {
1970
+ const idx = events.findIndex((e) => e.event_id === options.fromEventId);
1971
+ if (idx >= 0) events = events.slice(idx);
1972
+ }
1973
+ if (options.waveId != null) {
1974
+ const waveTag = `wave-${options.waveId}`;
1975
+ events = events.filter((e) => e.wave_id === waveTag || String(e.wave_id) === String(options.waveId));
1976
+ }
1977
+ if (options.limit && options.limit > 0) {
1978
+ events = events.slice(0, options.limit);
1979
+ }
1980
+
1981
+ // Ordenar por timestamp
1982
+ events.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
1983
+
1984
+ // Calcular deltas
1985
+ for (let i = 0; i < events.length; i++) {
1986
+ if (i === 0) {
1987
+ events[i]._delta_ms = 0;
1988
+ } else {
1989
+ const prev = new Date(events[i - 1].timestamp).getTime();
1990
+ const curr = new Date(events[i].timestamp).getTime();
1991
+ events[i]._delta_ms = curr - prev;
1992
+ }
1993
+ }
1994
+
1995
+ const firstTs = events.length ? new Date(events[0].timestamp).getTime() : null;
1996
+ const lastTs = events.length ? new Date(events[events.length - 1].timestamp).getTime() : null;
1997
+ const duration_ms = firstTs != null && lastTs != null ? lastTs - firstTs : null;
1998
+
1999
+ const waveIds = [...new Set(
2000
+ events
2001
+ .filter((e) => e.wave_id != null)
2002
+ .map((e) => {
2003
+ const m = String(e.wave_id).match(/^wave-(\d+)$/);
2004
+ return m ? Number(m[1]) : null;
2005
+ })
2006
+ .filter((n) => n != null)
2007
+ )].sort((a, b) => a - b);
2008
+
2009
+ const taskSequence = [...new Set(events.filter((e) => e.task_id || e.work_item_id).map((e) => e.task_id || e.work_item_id))];
2010
+ const checkpointSequence = events
2011
+ .filter((e) => String(e.type).includes('checkpoint'))
2012
+ .map((e) => e.event_id);
2013
+ const failureEvents = events.filter((e) =>
2014
+ String(e.type).includes('fail') || String(e.type).includes('error')
2015
+ );
2016
+
2017
+ const report = {
2018
+ events,
2019
+ totalEvents: events.length,
2020
+ duration_ms,
2021
+ runId: options.runId || (events.length ? events[0].run_id : null),
2022
+ waveIds,
2023
+ taskSequence,
2024
+ checkpointSequence,
2025
+ failureEvents,
2026
+ };
2027
+
2028
+ if (options.writeReport) {
2029
+ const p = operationalPaths(projectRoot, activeSession);
2030
+ const scopeRoot = path.dirname(p.events);
2031
+ const reportPath = path.join(scopeRoot, 'REPLAY-SESSION.md');
2032
+ const lines = [
2033
+ '# OXE — Replay Session',
2034
+ '',
2035
+ `- **Data:** ${new Date().toISOString().slice(0, 10)}`,
2036
+ `- **Run:** ${report.runId || '—'}`,
2037
+ `- **Total eventos:** ${report.totalEvents}`,
2038
+ `- **Duração:** ${report.duration_ms != null ? `${(report.duration_ms / 1000).toFixed(1)}s` : '—'}`,
2039
+ `- **Ondas:** ${report.waveIds.join(', ') || '—'}`,
2040
+ `- **Tarefas:** ${report.taskSequence.join(', ') || '—'}`,
2041
+ `- **Falhas:** ${report.failureEvents.length}`,
2042
+ '',
2043
+ '## Timeline',
2044
+ '',
2045
+ '| # | Tipo | Wave | Task | Delta | Timestamp |',
2046
+ '|---|------|------|------|-------|-----------|',
2047
+ ];
2048
+ for (let i = 0; i < events.length; i++) {
2049
+ const e = events[i];
2050
+ const delta = i > 0 ? `+${(e._delta_ms / 1000).toFixed(1)}s` : '—';
2051
+ lines.push(`| ${i + 1} | ${e.type} | ${e.wave_id || '—'} | ${e.task_id || e.work_item_id || '—'} | ${delta} | ${e.timestamp} |`);
2052
+ }
2053
+ ensureDirForFile(reportPath);
2054
+ fs.writeFileSync(reportPath, lines.join('\n') + '\n', 'utf8');
2055
+ report._reportPath = reportPath;
2056
+ }
2057
+
2058
+ return report;
2059
+ }
2060
+
2061
+ function replayRuntimeState(projectRoot, activeSession, options = {}) {
2062
+ const current = options.runState || readRunState(projectRoot, activeSession);
2063
+ const runId = options.runId || (current && current.run_id) || null;
2064
+ const replay = replayEvents(projectRoot, activeSession, {
2065
+ runId: runId || undefined,
2066
+ fromEventId: options.fromEventId || undefined,
2067
+ waveId: options.waveId != null ? options.waveId : options.wave,
2068
+ limit: options.limit,
2069
+ writeReport: options.writeReport || false,
2070
+ });
2071
+ const journalPath = runId ? path.join(projectRoot, '.oxe', 'runs', runId, 'journal.json') : null;
2072
+ const journal = journalPath ? readJsonIfExists(journalPath) : null;
2073
+ const verificationArtifacts = current ? loadRuntimeVerificationArtifacts(projectRoot, current) : {
2074
+ manifest: null,
2075
+ residualRisks: null,
2076
+ evidenceCoverage: null,
2077
+ };
2078
+ const consistency = current && journal
2079
+ ? buildRecoveryConsistency(projectRoot, activeSession, current, journal, verificationArtifacts)
2080
+ : null;
2081
+ const gateQueue = readRuntimeGates(projectRoot, activeSession, { runId });
2082
+ const policyDecisionsPath = runId ? path.join(projectRoot, '.oxe', 'runs', runId, 'policy-decisions.json') : null;
2083
+ const policyDecisions = policyDecisionsPath ? readJsonIfExists(policyDecisionsPath) : null;
2084
+ const promotionRecordPath = runId ? path.join(projectRoot, '.oxe', 'runs', runId, 'promotion-record.json') : null;
2085
+ const promotionRecord = promotionRecordPath ? readJsonIfExists(promotionRecordPath) : null;
2086
+ const summary = {
2087
+ run_id: runId,
2088
+ generated_at: new Date().toISOString(),
2089
+ replay,
2090
+ gateQueue,
2091
+ policyDecisions: Array.isArray(policyDecisions) ? policyDecisions : [],
2092
+ verification: verificationArtifacts,
2093
+ promotion: promotionRecord || null,
2094
+ consistency,
2095
+ };
2096
+ if (options.writeReport && runId) {
2097
+ const reportPath = path.join(projectRoot, '.oxe', 'runs', runId, 'replay-report.json');
2098
+ ensureDirForFile(reportPath);
2099
+ fs.writeFileSync(reportPath, JSON.stringify(summary, null, 2), 'utf8');
2100
+ summary.report_path = reportPath;
2101
+ }
2102
+ return summary;
2103
+ }
2104
+
2105
+ module.exports = {
2106
+ VALID_RUN_STATUSES,
2107
+ VALID_APPROVAL_POLICIES,
2108
+ VALID_CAPABILITY_TYPES,
2109
+ operationalPaths,
2110
+ makeRunId,
2111
+ appendEvent,
2112
+ readEvents,
2113
+ summarizeEvents,
2114
+ writeRunState,
2115
+ readRunState,
2116
+ readGitActivity,
2117
+ verifyGitEvidence,
2118
+ runtimeStateWarnings,
2119
+ parseCapabilityManifest,
2120
+ readCapabilityCatalog,
2121
+ capabilityCatalogWarnings,
2122
+ buildMemoryLayers,
2123
+ buildOperationalGraph,
2124
+ serializeCanonicalState,
2125
+ hydrateCanonicalState,
2126
+ reduceCanonicalRunState,
2127
+ compileExecutionGraphFromArtifacts,
2128
+ compileVerificationSuiteFromArtifacts,
2129
+ buildRuntimePluginRegistry,
2130
+ buildRuntimeModeStatus,
2131
+ buildRuntimeProviderCatalog,
2132
+ buildRecoveryConsistency,
2133
+ readRuntimeGates,
2134
+ resolveRuntimeGate,
2135
+ runRuntimeVerify,
2136
+ projectRuntimeArtifacts,
2137
+ runRuntimeCiChecks,
2138
+ runRuntimePromotion,
2139
+ recoverRuntimeState,
2140
+ applyRuntimeAction,
2141
+ replayEvents,
2142
+ replayRuntimeState,
2143
+ readRuntimeMultiAgentStatus,
2144
+ };