oxe-cc 1.2.1 → 1.4.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 (281) 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/AGENTS.md +5 -3
  73. package/CHANGELOG.md +72 -10
  74. package/LICENSE +21 -674
  75. package/README.md +631 -535
  76. package/bin/banner.txt +6 -6
  77. package/bin/lib/oxe-agent-install.cjs +69 -69
  78. package/bin/lib/oxe-azure.cjs +1445 -1445
  79. package/bin/lib/oxe-context-engine.cjs +867 -867
  80. package/bin/lib/oxe-dashboard.cjs +76 -28
  81. package/bin/lib/oxe-operational.cjs +2144 -1340
  82. package/bin/lib/oxe-project-health.cjs +483 -1
  83. package/bin/lib/oxe-runtime-semantics.cjs +12 -0
  84. package/bin/oxe-cc.js +554 -152
  85. package/commands/oxe/ask.md +2 -2
  86. package/commands/oxe/capabilities.md +2 -2
  87. package/commands/oxe/checkpoint.md +2 -2
  88. package/commands/oxe/compact.md +2 -2
  89. package/commands/oxe/dashboard.md +2 -2
  90. package/commands/oxe/debug.md +2 -2
  91. package/commands/oxe/discuss.md +2 -2
  92. package/commands/oxe/execute.md +5 -2
  93. package/commands/oxe/forensics.md +2 -2
  94. package/commands/oxe/help.md +2 -2
  95. package/commands/oxe/loop.md +2 -2
  96. package/commands/oxe/milestone.md +2 -2
  97. package/commands/oxe/next.md +2 -2
  98. package/commands/oxe/obs.md +2 -2
  99. package/commands/oxe/oxe.md +2 -2
  100. package/commands/oxe/plan-agent.md +2 -2
  101. package/commands/oxe/plan.md +2 -2
  102. package/commands/oxe/project.md +2 -2
  103. package/commands/oxe/quick.md +2 -2
  104. package/commands/oxe/research.md +2 -2
  105. package/commands/oxe/retro.md +2 -2
  106. package/commands/oxe/review-pr.md +2 -2
  107. package/commands/oxe/route.md +2 -2
  108. package/commands/oxe/scan.md +2 -2
  109. package/commands/oxe/security.md +2 -2
  110. package/commands/oxe/session.md +2 -2
  111. package/commands/oxe/ship.md +2 -2
  112. package/commands/oxe/skill.md +2 -2
  113. package/commands/oxe/spec.md +2 -2
  114. package/commands/oxe/ui-review.md +2 -2
  115. package/commands/oxe/ui-spec.md +2 -2
  116. package/commands/oxe/update.md +2 -2
  117. package/commands/oxe/validate-gaps.md +2 -2
  118. package/commands/oxe/verify.md +5 -2
  119. package/commands/oxe/workstream.md +2 -2
  120. package/lib/runtime/delivery/branch-manager.d.ts +1 -0
  121. package/lib/runtime/delivery/branch-manager.js +7 -0
  122. package/lib/runtime/delivery/ci-checks.js +34 -1
  123. package/lib/runtime/delivery/delivery-records.d.ts +34 -0
  124. package/lib/runtime/delivery/delivery-records.js +48 -0
  125. package/lib/runtime/delivery/index.d.ts +1 -0
  126. package/lib/runtime/delivery/index.js +1 -0
  127. package/lib/runtime/delivery/promotion-pipeline.d.ts +26 -2
  128. package/lib/runtime/delivery/promotion-pipeline.js +111 -14
  129. package/lib/runtime/gate/gate-manager.d.ts +41 -0
  130. package/lib/runtime/gate/gate-manager.js +108 -1
  131. package/lib/runtime/index.d.ts +2 -2
  132. package/lib/runtime/index.js +3 -1
  133. package/lib/runtime/models/gate-decision.d.ts +4 -1
  134. package/lib/runtime/models/workspace.d.ts +3 -0
  135. package/lib/runtime/plugins/capability-adapter.d.ts +12 -0
  136. package/lib/runtime/plugins/capability-adapter.js +204 -0
  137. package/lib/runtime/plugins/capability-matrix.d.ts +5 -0
  138. package/lib/runtime/plugins/capability-matrix.js +48 -17
  139. package/lib/runtime/plugins/index.d.ts +1 -0
  140. package/lib/runtime/plugins/index.js +1 -0
  141. package/lib/runtime/plugins/plugin-abi.d.ts +2 -0
  142. package/lib/runtime/plugins/plugin-manifest.d.ts +1 -1
  143. package/lib/runtime/plugins/plugin-manifest.js +6 -2
  144. package/lib/runtime/plugins/plugin-registry.d.ts +46 -0
  145. package/lib/runtime/plugins/plugin-registry.js +79 -2
  146. package/lib/runtime/policy/policy-engine.d.ts +19 -0
  147. package/lib/runtime/policy/policy-engine.js +76 -4
  148. package/lib/runtime/projection/projection-engine.d.ts +9 -1
  149. package/lib/runtime/projection/projection-engine.js +73 -3
  150. package/lib/runtime/scheduler/multi-agent-coordinator.d.ts +43 -1
  151. package/lib/runtime/scheduler/multi-agent-coordinator.js +151 -39
  152. package/lib/runtime/scheduler/run-journal.d.ts +1 -1
  153. package/lib/runtime/scheduler/scheduler.d.ts +19 -1
  154. package/lib/runtime/scheduler/scheduler.js +258 -13
  155. package/lib/runtime/verification/verification-compiler.d.ts +43 -0
  156. package/lib/runtime/verification/verification-compiler.js +137 -0
  157. package/lib/runtime/verification/verification-manifest.d.ts +9 -0
  158. package/lib/runtime/verification/verification-manifest.js +56 -6
  159. package/lib/runtime/workspace/strategies/ephemeral-container.d.ts +1 -0
  160. package/lib/runtime/workspace/strategies/ephemeral-container.js +4 -0
  161. package/lib/runtime/workspace/strategies/git-worktree.d.ts +1 -0
  162. package/lib/runtime/workspace/strategies/git-worktree.js +2 -0
  163. package/lib/runtime/workspace/strategies/inplace.d.ts +1 -0
  164. package/lib/runtime/workspace/strategies/inplace.js +2 -0
  165. package/lib/runtime/workspace/workspace-manager.d.ts +2 -1
  166. package/lib/sdk/README.md +20 -8
  167. package/lib/sdk/index.cjs +33 -24
  168. package/lib/sdk/index.d.ts +149 -14
  169. package/oxe/templates/ACTIVE-RUN.template.json +32 -32
  170. package/oxe/templates/CAPABILITIES.template.md +7 -7
  171. package/oxe/templates/CAPABILITY.template.md +45 -45
  172. package/oxe/templates/CHECKPOINTS.template.md +7 -7
  173. package/oxe/templates/EXECUTION-RUNTIME.template.md +68 -68
  174. package/oxe/templates/HYPOTHESES.template.md +33 -33
  175. package/oxe/templates/LESSONS-METRICS.template.json +13 -13
  176. package/oxe/templates/NOTES.template.md +16 -16
  177. package/oxe/templates/PLAN-REVIEW.template.md +31 -31
  178. package/oxe/templates/SESSION.template.md +34 -34
  179. package/oxe/templates/SKILL.template.md +26 -26
  180. package/oxe/templates/STATE.md +55 -55
  181. package/oxe/templates/WORKFLOW_AUTHORING.md +18 -18
  182. package/oxe/workflows/ask.md +96 -96
  183. package/oxe/workflows/capabilities.md +25 -25
  184. package/oxe/workflows/dashboard.md +33 -33
  185. package/oxe/workflows/discuss.md +12 -12
  186. package/oxe/workflows/execute.md +14 -0
  187. package/oxe/workflows/help.md +352 -352
  188. package/oxe/workflows/next.md +22 -22
  189. package/oxe/workflows/oxe.md +6 -6
  190. package/oxe/workflows/plan-agent.md +9 -9
  191. package/oxe/workflows/plan.md +51 -20
  192. package/oxe/workflows/quick.md +10 -10
  193. package/oxe/workflows/references/reasoning-discovery.md +28 -28
  194. package/oxe/workflows/references/reasoning-execution.md +29 -29
  195. package/oxe/workflows/references/reasoning-planning.md +32 -32
  196. package/oxe/workflows/references/reasoning-review.md +29 -29
  197. package/oxe/workflows/references/reasoning-status.md +24 -24
  198. package/oxe/workflows/references/robustness-elevation.md +295 -295
  199. package/oxe/workflows/references/workflow-runtime-contracts.json +952 -930
  200. package/oxe/workflows/route.md +16 -16
  201. package/oxe/workflows/session.md +213 -213
  202. package/oxe/workflows/ship.md +142 -142
  203. package/oxe/workflows/skill.md +44 -44
  204. package/oxe/workflows/ui-review.md +36 -36
  205. package/oxe/workflows/verify-audit.md +73 -73
  206. package/oxe/workflows/verify.md +10 -0
  207. package/package.json +92 -92
  208. package/packages/runtime/package.json +16 -15
  209. package/packages/runtime/src/audit/audit-trail.ts +243 -243
  210. package/packages/runtime/src/audit/index.ts +2 -2
  211. package/packages/runtime/src/audit/policy-pack.ts +62 -62
  212. package/packages/runtime/src/compiler/graph-compiler.ts +245 -245
  213. package/packages/runtime/src/compiler/index.ts +1 -1
  214. package/packages/runtime/src/context/context-pack-builder.ts +259 -259
  215. package/packages/runtime/src/context/context-pack-store.ts +197 -197
  216. package/packages/runtime/src/context/context-profiles.ts +60 -60
  217. package/packages/runtime/src/context/index.ts +3 -3
  218. package/packages/runtime/src/decision/decision-engine.ts +174 -174
  219. package/packages/runtime/src/decision/decision-memo.ts +211 -211
  220. package/packages/runtime/src/decision/index.ts +2 -2
  221. package/packages/runtime/src/delivery/branch-manager.ts +91 -84
  222. package/packages/runtime/src/delivery/ci-checks.ts +285 -252
  223. package/packages/runtime/src/delivery/delivery-records.ts +75 -0
  224. package/packages/runtime/src/delivery/index.ts +5 -4
  225. package/packages/runtime/src/delivery/pr-manager.ts +112 -112
  226. package/packages/runtime/src/delivery/promotion-pipeline.ts +334 -180
  227. package/packages/runtime/src/events/bus.ts +92 -92
  228. package/packages/runtime/src/events/catalog.ts +29 -29
  229. package/packages/runtime/src/events/envelope.ts +14 -14
  230. package/packages/runtime/src/events/index.ts +3 -3
  231. package/packages/runtime/src/evidence/evidence-store.ts +130 -130
  232. package/packages/runtime/src/evidence/index.ts +1 -1
  233. package/packages/runtime/src/gate/gate-manager.ts +289 -137
  234. package/packages/runtime/src/gate/index.ts +1 -1
  235. package/packages/runtime/src/index.ts +41 -37
  236. package/packages/runtime/src/models/attempt.ts +19 -19
  237. package/packages/runtime/src/models/evidence.ts +21 -21
  238. package/packages/runtime/src/models/gate-decision.ts +25 -21
  239. package/packages/runtime/src/models/index.ts +8 -8
  240. package/packages/runtime/src/models/run.ts +24 -24
  241. package/packages/runtime/src/models/session.ts +11 -11
  242. package/packages/runtime/src/models/verification-result.ts +10 -10
  243. package/packages/runtime/src/models/work-item.ts +25 -25
  244. package/packages/runtime/src/models/workspace.ts +31 -28
  245. package/packages/runtime/src/plugins/capability-adapter.ts +206 -0
  246. package/packages/runtime/src/plugins/capability-matrix.ts +126 -83
  247. package/packages/runtime/src/plugins/index.ts +5 -4
  248. package/packages/runtime/src/plugins/plugin-abi.ts +97 -95
  249. package/packages/runtime/src/plugins/plugin-manifest.ts +118 -113
  250. package/packages/runtime/src/plugins/plugin-registry.ts +232 -124
  251. package/packages/runtime/src/policy/index.ts +1 -1
  252. package/packages/runtime/src/policy/policy-engine.ts +330 -244
  253. package/packages/runtime/src/projection/index.ts +1 -1
  254. package/packages/runtime/src/projection/projection-engine.ts +328 -249
  255. package/packages/runtime/src/reducers/debug-reducer.ts +36 -36
  256. package/packages/runtime/src/reducers/index.ts +2 -2
  257. package/packages/runtime/src/reducers/run-state-reducer.ts +269 -269
  258. package/packages/runtime/src/scheduler/agent-registry.ts +132 -132
  259. package/packages/runtime/src/scheduler/agent-roles.ts +109 -109
  260. package/packages/runtime/src/scheduler/index.ts +4 -4
  261. package/packages/runtime/src/scheduler/multi-agent-coordinator.ts +521 -333
  262. package/packages/runtime/src/scheduler/run-journal.ts +62 -62
  263. package/packages/runtime/src/scheduler/scheduler.ts +722 -441
  264. package/packages/runtime/src/verification/index.ts +2 -2
  265. package/packages/runtime/src/verification/verification-compiler.ts +436 -225
  266. package/packages/runtime/src/verification/verification-manifest.ts +252 -192
  267. package/packages/runtime/src/workspace/index.ts +5 -5
  268. package/packages/runtime/src/workspace/strategies/ephemeral-container.ts +126 -121
  269. package/packages/runtime/src/workspace/strategies/git-worktree.ts +79 -77
  270. package/packages/runtime/src/workspace/strategies/inplace.ts +38 -35
  271. package/packages/runtime/src/workspace/workspace-manager.ts +16 -15
  272. package/packages/runtime/tsconfig.json +17 -17
  273. package/vscode-extension/.vscodeignore +7 -7
  274. package/vscode-extension/LICENSE +21 -0
  275. package/vscode-extension/oxe-agents-1.0.0.vsix +0 -0
  276. package/vscode-extension/oxe-agents-1.4.0.vsix +0 -0
  277. package/vscode-extension/package.json +184 -184
  278. package/vscode-extension/src/extension.js +310 -310
  279. package/vscode-extension/src/shared/contextLoader.js +137 -137
  280. package/vscode-extension/src/shared/contractBuilder.js +159 -159
  281. package/vscode-extension/src/shared/stateReader.js +101 -101
@@ -31,6 +31,7 @@ const ALLOWED_CONFIG_KEYS = [
31
31
  'scale_adaptive',
32
32
  'permissions',
33
33
  'azure',
34
+ 'runtime',
34
35
  ];
35
36
 
36
37
  /**
@@ -159,6 +160,13 @@ function loadOxeConfigMerged(targetProject) {
159
160
  resource_graph_auto_install: true,
160
161
  vpn_required: false,
161
162
  },
163
+ runtime: {
164
+ quotas: {
165
+ max_work_items_per_run: null,
166
+ max_mutations_per_run: null,
167
+ max_retries_per_run: null,
168
+ },
169
+ },
162
170
  };
163
171
 
164
172
  const sources = { system: null, user: null, project: null };
@@ -383,6 +391,25 @@ function validateConfigShape(cfg) {
383
391
  }
384
392
  }
385
393
  }
394
+ if (cfg.runtime != null) {
395
+ if (typeof cfg.runtime !== 'object' || Array.isArray(cfg.runtime)) {
396
+ typeErrors.push('runtime deve ser um objeto');
397
+ } else {
398
+ const runtimeCfg = /** @type {Record<string, unknown>} */ (cfg.runtime);
399
+ if (runtimeCfg.quotas != null) {
400
+ if (typeof runtimeCfg.quotas !== 'object' || Array.isArray(runtimeCfg.quotas)) {
401
+ typeErrors.push('runtime.quotas deve ser um objeto');
402
+ } else {
403
+ for (const key of ['max_work_items_per_run', 'max_mutations_per_run', 'max_retries_per_run']) {
404
+ const value = runtimeCfg.quotas[key];
405
+ if (value != null && (typeof value !== 'number' || Number.isNaN(value))) {
406
+ typeErrors.push(`runtime.quotas.${key} deve ser número ou null`);
407
+ }
408
+ }
409
+ }
410
+ }
411
+ }
412
+ }
386
413
  return { unknownKeys, typeErrors };
387
414
  }
388
415
 
@@ -615,6 +642,442 @@ function readJsonFileSafe(filePath) {
615
642
  }
616
643
  }
617
644
 
645
+ function readExecutionGates(target, activeSession) {
646
+ const gatesPath = activeSession
647
+ ? path.join(target, '.oxe', ...String(activeSession).split('/'), 'execution', 'GATES.json')
648
+ : path.join(target, '.oxe', 'execution', 'GATES.json');
649
+ const raw = readJsonFileSafe(gatesPath);
650
+ const gates = raw.ok && Array.isArray(raw.data) ? raw.data : [];
651
+ const pending = gates.filter((gate) => gate && gate.status === 'pending');
652
+ const stalePending = pending.filter((gate) => {
653
+ const requestedAt = Date.parse(String(gate.requested_at || ''));
654
+ return Number.isFinite(requestedAt) && Date.now() - requestedAt > 24 * 60 * 60 * 1000;
655
+ });
656
+ return {
657
+ path: gatesPath,
658
+ gateSlaHours: 24,
659
+ total: gates.length,
660
+ pending,
661
+ stalePending,
662
+ staleGateCount: stalePending.length,
663
+ };
664
+ }
665
+
666
+ function parseAuditTrailFile(filePath) {
667
+ if (!fs.existsSync(filePath)) return [];
668
+ try {
669
+ return fs.readFileSync(filePath, 'utf8')
670
+ .split(/\r?\n/)
671
+ .filter(Boolean)
672
+ .map((line) => JSON.parse(line))
673
+ .filter((entry) => entry && typeof entry === 'object');
674
+ } catch {
675
+ return [];
676
+ }
677
+ }
678
+
679
+ function toNullableNumber(value) {
680
+ if (value == null || value === '') return null;
681
+ const parsed = Number(value);
682
+ return Number.isFinite(parsed) ? parsed : null;
683
+ }
684
+
685
+ function summarizeAuditTrail(target, runId) {
686
+ const auditPath = path.join(target, '.oxe', 'AUDIT-TRAIL.ndjson');
687
+ const entries = parseAuditTrailFile(auditPath);
688
+ const scoped = runId ? entries.filter((entry) => entry.run_id === runId) : entries;
689
+ const actions = {};
690
+ const actors = new Set();
691
+ let oldest = null;
692
+ let newest = null;
693
+ let warn = 0;
694
+ let critical = 0;
695
+ for (const entry of scoped) {
696
+ const action = String(entry.action || 'unknown');
697
+ actions[action] = (actions[action] || 0) + 1;
698
+ if (entry.actor) actors.add(String(entry.actor));
699
+ if (entry.severity === 'warn') warn += 1;
700
+ if (entry.severity === 'critical') critical += 1;
701
+ if (!oldest || String(entry.timestamp || '') < oldest) oldest = String(entry.timestamp || '');
702
+ if (!newest || String(entry.timestamp || '') > newest) newest = String(entry.timestamp || '');
703
+ }
704
+ return {
705
+ path: auditPath,
706
+ totalEntries: entries.length,
707
+ runEntries: scoped.length,
708
+ warn,
709
+ critical,
710
+ oldest,
711
+ newest,
712
+ actors: Array.from(actors),
713
+ actions,
714
+ };
715
+ }
716
+
717
+ function countRetryConsumption(attemptEntries) {
718
+ if (!attemptEntries || typeof attemptEntries !== 'object') return 0;
719
+ if (Array.isArray(attemptEntries)) {
720
+ const grouped = new Map();
721
+ for (const attempt of attemptEntries) {
722
+ const key = attempt && typeof attempt === 'object'
723
+ ? String(attempt.work_item_id || attempt.workItemId || attempt.node_id || attempt.nodeId || 'unknown')
724
+ : 'unknown';
725
+ grouped.set(key, (grouped.get(key) || 0) + 1);
726
+ }
727
+ return Array.from(grouped.values()).reduce((sum, count) => sum + Math.max(0, count - 1), 0);
728
+ }
729
+ return Object.values(attemptEntries).reduce((sum, attempts) => {
730
+ if (Array.isArray(attempts)) return sum + Math.max(0, attempts.length - 1);
731
+ return sum;
732
+ }, 0);
733
+ }
734
+
735
+ function summarizeQuota(config, activeRun, graphNodes, attemptEntries) {
736
+ const runtimeConfig = config && config.runtime && typeof config.runtime === 'object' ? config.runtime : {};
737
+ const quotaConfig = runtimeConfig.quotas && typeof runtimeConfig.quotas === 'object' ? runtimeConfig.quotas : {};
738
+ const workItems = Array.isArray(activeRun && activeRun.canonical_state && activeRun.canonical_state.workItems)
739
+ ? activeRun.canonical_state.workItems
740
+ : [];
741
+ const workItemsConsumed = graphNodes.length > 0 ? graphNodes.length : workItems.length;
742
+ const mutationsConsumed = graphNodes.length > 0
743
+ ? graphNodes.filter((node) => Array.isArray(node.mutation_scope) && node.mutation_scope.length > 0).length
744
+ : workItems.filter((item) => Array.isArray(item.mutation_scope) && item.mutation_scope.length > 0).length;
745
+ const retriesConsumed = countRetryConsumption(attemptEntries);
746
+ const limits = {
747
+ maxWorkItemsPerRun: toNullableNumber(quotaConfig.max_work_items_per_run),
748
+ maxMutationsPerRun: toNullableNumber(quotaConfig.max_mutations_per_run),
749
+ maxRetriesPerRun: toNullableNumber(quotaConfig.max_retries_per_run),
750
+ };
751
+ const violations = [];
752
+ if (limits.maxWorkItemsPerRun != null && workItemsConsumed > limits.maxWorkItemsPerRun) {
753
+ violations.push(`work_items ${workItemsConsumed}/${limits.maxWorkItemsPerRun}`);
754
+ }
755
+ if (limits.maxMutationsPerRun != null && mutationsConsumed > limits.maxMutationsPerRun) {
756
+ violations.push(`mutations ${mutationsConsumed}/${limits.maxMutationsPerRun}`);
757
+ }
758
+ if (limits.maxRetriesPerRun != null && retriesConsumed > limits.maxRetriesPerRun) {
759
+ violations.push(`retries ${retriesConsumed}/${limits.maxRetriesPerRun}`);
760
+ }
761
+ return {
762
+ limits,
763
+ consumed: {
764
+ workItems: workItemsConsumed,
765
+ mutations: mutationsConsumed,
766
+ retries: retriesConsumed,
767
+ },
768
+ violations,
769
+ exceeded: violations.length > 0,
770
+ };
771
+ }
772
+
773
+ function summarizePromotion(runDir, activeRun) {
774
+ const record = activeRun && activeRun.delivery && activeRun.delivery.promotion_record
775
+ ? activeRun.delivery.promotion_record
776
+ : readJsonFileSafe(path.join(runDir, 'promotion-record.json')).data;
777
+ if (!record) return null;
778
+ return {
779
+ status: record.status || null,
780
+ targetKind: record.target_kind || null,
781
+ remote: record.remote || null,
782
+ targetRef: record.target_ref || null,
783
+ prUrl: record.pr_url || null,
784
+ prNumber: record.pr_number != null ? Number(record.pr_number) : null,
785
+ coveragePercent: record.coverage_percent != null ? Number(record.coverage_percent) : null,
786
+ reasons: Array.isArray(record.reasons) ? record.reasons : [],
787
+ path: path.join(runDir, 'promotion-record.json'),
788
+ };
789
+ }
790
+
791
+ function summarizePolicyCoverage(runDir, mutationNodes, policyDecisions) {
792
+ const coveredMutationIds = new Set(
793
+ policyDecisions
794
+ .map((decision) => decision && decision.work_item_id ? String(decision.work_item_id) : null)
795
+ .filter(Boolean)
796
+ );
797
+ const mutationIds = mutationNodes
798
+ .map((node) => node && node.id ? String(node.id) : null)
799
+ .filter(Boolean);
800
+ const uncoveredMutationIds = mutationIds.filter((id) => !coveredMutationIds.has(id));
801
+ const coveragePercent = mutationIds.length > 0
802
+ ? Math.round(((mutationIds.length - uncoveredMutationIds.length) / mutationIds.length) * 100)
803
+ : 100;
804
+ return {
805
+ path: path.join(runDir, 'policy-decisions.json'),
806
+ totalDecisions: policyDecisions.length,
807
+ mutationNodes: mutationIds.length,
808
+ coveredMutations: mutationIds.length - uncoveredMutationIds.length,
809
+ uncoveredMutations: uncoveredMutationIds.length,
810
+ uncoveredMutationIds,
811
+ coveragePercent,
812
+ };
813
+ }
814
+
815
+ function summarizePromotionReadiness(verificationSummary, residualRiskSummary, evidenceCoverage, pendingGates, policyCoverage, promotionSummary, quotaSummary) {
816
+ const blockers = [];
817
+ const minimumCoverage = 100;
818
+ if (!verificationSummary) {
819
+ blockers.push('verification_manifest ausente');
820
+ } else if (!verificationSummary.allPassed || verificationSummary.fail > 0 || verificationSummary.error > 0) {
821
+ blockers.push('verify_failed');
822
+ }
823
+ if (pendingGates && Array.isArray(pendingGates.pending) && pendingGates.pending.length > 0) {
824
+ blockers.push('pending_gates');
825
+ }
826
+ if (residualRiskSummary && residualRiskSummary.highOrCritical > 0) {
827
+ blockers.push('high_or_critical_risks');
828
+ }
829
+ if (evidenceCoverage && Number(evidenceCoverage.coverage_percent || 0) < minimumCoverage) {
830
+ blockers.push('coverage_below_threshold');
831
+ }
832
+ if (policyCoverage && policyCoverage.uncoveredMutations > 0) {
833
+ blockers.push('policy_uncovered_mutations');
834
+ }
835
+ if (quotaSummary && quotaSummary.exceeded) {
836
+ blockers.push('quota_exceeded');
837
+ }
838
+ return {
839
+ status: blockers.length > 0 ? 'blocked' : 'ready',
840
+ blockers,
841
+ targetKind: promotionSummary && promotionSummary.targetKind ? promotionSummary.targetKind : 'pr_draft',
842
+ minimumCoverage,
843
+ coveragePercent: evidenceCoverage && evidenceCoverage.coverage_percent != null ? Number(evidenceCoverage.coverage_percent) : null,
844
+ pendingGateCount: pendingGates && Array.isArray(pendingGates.pending) ? pendingGates.pending.length : 0,
845
+ highOrCriticalRisks: residualRiskSummary ? residualRiskSummary.highOrCritical : 0,
846
+ uncoveredMutationCount: policyCoverage ? policyCoverage.uncoveredMutations : 0,
847
+ quotaExceeded: Boolean(quotaSummary && quotaSummary.exceeded),
848
+ };
849
+ }
850
+
851
+ function summarizeRecoveryState(target, activeSession, activeRun, verificationArtifacts) {
852
+ if (!activeRun || !activeRun.run_id) {
853
+ return {
854
+ status: 'not_started',
855
+ recoverCount: 0,
856
+ journalState: null,
857
+ markdownPath: null,
858
+ issues: [],
859
+ };
860
+ }
861
+ const journalPath = path.join(target, '.oxe', 'runs', activeRun.run_id, 'journal.json');
862
+ const journal = readJsonFileSafe(journalPath).data;
863
+ const consistency = operational.buildRecoveryConsistency(
864
+ target,
865
+ activeSession,
866
+ activeRun,
867
+ journal,
868
+ verificationArtifacts
869
+ );
870
+ const recoverySummary = activeRun.recovery_summary && typeof activeRun.recovery_summary === 'object'
871
+ ? activeRun.recovery_summary
872
+ : null;
873
+ const issues = Array.isArray(consistency.issues) ? consistency.issues : [];
874
+ const recoverCount = Number(activeRun.metrics && activeRun.metrics.recover_count || 0);
875
+ const summaryPath = recoverySummary && recoverySummary.markdown_ref
876
+ ? path.join(target, recoverySummary.markdown_ref)
877
+ : (activeSession
878
+ ? path.join(target, '.oxe', ...String(activeSession).split('/'), 'execution', 'RECOVERY-SUMMARY.md')
879
+ : path.join(target, '.oxe', 'RECOVERY-SUMMARY.md'));
880
+ const status = issues.length > 0
881
+ ? 'warning'
882
+ : activeRun.status === 'paused'
883
+ ? 'recoverable'
884
+ : recoverySummary
885
+ ? 'recovered'
886
+ : 'clean';
887
+ return {
888
+ status,
889
+ recoverCount,
890
+ recoveredAt: recoverySummary ? recoverySummary.recovered_at || null : null,
891
+ journalState: recoverySummary ? recoverySummary.journal_state || null : (journal && journal.scheduler_state ? journal.scheduler_state : null),
892
+ markdownPath: summaryPath,
893
+ orphanWorkItems: recoverySummary && Array.isArray(recoverySummary.orphan_work_items) ? recoverySummary.orphan_work_items : [],
894
+ pendingGatesRehydrated: consistency.pending_gates_rehydrated,
895
+ policyDecisionsRehydrated: consistency.policy_decisions_rehydrated,
896
+ evidenceRefsTracked: consistency.evidence_refs_tracked,
897
+ consistency,
898
+ issues,
899
+ };
900
+ }
901
+
902
+ function summarizeEnterpriseRuntime(target, activeRun, activeSession, config) {
903
+ const pendingGates = readExecutionGates(target, activeSession);
904
+ const auditSummary = summarizeAuditTrail(target, activeRun && activeRun.run_id ? activeRun.run_id : null);
905
+ const runtimeMode = operational.buildRuntimeModeStatus(activeRun);
906
+ const providerCatalog = operational.buildRuntimeProviderCatalog(target);
907
+ if (!activeRun || !activeRun.run_id) {
908
+ return {
909
+ runtimeMode,
910
+ fallbackMode: runtimeMode.fallback_mode,
911
+ verificationSummary: null,
912
+ residualRiskSummary: null,
913
+ evidenceCoverage: null,
914
+ pendingGates,
915
+ gateQueue: pendingGates,
916
+ policyDecisionSummary: { total: 0, denied: 0, gated: 0, overridesWithoutRationale: 0 },
917
+ policyCoverage: {
918
+ path: null,
919
+ totalDecisions: 0,
920
+ mutationNodes: 0,
921
+ coveredMutations: 0,
922
+ uncoveredMutations: 0,
923
+ uncoveredMutationIds: [],
924
+ coveragePercent: 100,
925
+ },
926
+ quotaSummary: summarizeQuota(config, null, [], {}),
927
+ auditSummary,
928
+ promotionSummary: null,
929
+ promotionReadiness: {
930
+ status: 'blocked',
931
+ blockers: ['verification_manifest ausente'],
932
+ targetKind: 'pr_draft',
933
+ minimumCoverage: 100,
934
+ coveragePercent: null,
935
+ pendingGateCount: pendingGates.pending.length,
936
+ highOrCriticalRisks: 0,
937
+ uncoveredMutationCount: 0,
938
+ },
939
+ recoveryState: {
940
+ status: 'not_started',
941
+ recoverCount: 0,
942
+ journalState: null,
943
+ markdownPath: null,
944
+ issues: [],
945
+ },
946
+ multiAgent: null,
947
+ providerCatalog,
948
+ enterpriseWarnings: providerCatalog.load_errors ? [...providerCatalog.load_errors] : [],
949
+ };
950
+ }
951
+
952
+ const runDir = path.join(target, '.oxe', 'runs', activeRun.run_id);
953
+ const manifest = activeRun.verification_manifest || readJsonFileSafe(path.join(runDir, 'verification-manifest.json')).data;
954
+ const risks = activeRun.residual_risks || readJsonFileSafe(path.join(runDir, 'residual-risk-ledger.json')).data || readJsonFileSafe(path.join(runDir, 'residual-risks.json')).data;
955
+ const evidenceCoverage = activeRun.verification_evidence_coverage || readJsonFileSafe(path.join(runDir, 'evidence-coverage.json')).data || (
956
+ manifest && manifest.summary
957
+ ? {
958
+ total_checks: Array.isArray(manifest.checks) ? manifest.checks.length : Number(manifest.summary.total || 0),
959
+ checks_with_evidence: Array.isArray(manifest.checks) ? manifest.checks.filter((check) => Array.isArray(check.evidence_refs) && check.evidence_refs.length > 0).length : 0,
960
+ total_evidence_refs: Array.isArray(manifest.checks) ? manifest.checks.reduce((sum, check) => sum + (Array.isArray(check.evidence_refs) ? check.evidence_refs.length : 0), 0) : 0,
961
+ coverage_percent: Array.isArray(manifest.checks) && manifest.checks.length > 0
962
+ ? Math.round((manifest.checks.filter((check) => Array.isArray(check.evidence_refs) && check.evidence_refs.length > 0).length / manifest.checks.length) * 100)
963
+ : 100,
964
+ }
965
+ : null
966
+ );
967
+ const policyDecisionsRaw = readJsonFileSafe(path.join(runDir, 'policy-decisions.json'));
968
+ const policyDecisions = Array.isArray(policyDecisionsRaw.data) ? policyDecisionsRaw.data : [];
969
+ const allGraphNodes = activeRun.compiled_graph && activeRun.compiled_graph.nodes && typeof activeRun.compiled_graph.nodes === 'object'
970
+ ? Object.values(activeRun.compiled_graph.nodes)
971
+ : [];
972
+ const mutationNodes = allGraphNodes.filter((node) => Array.isArray(node.mutation_scope) && node.mutation_scope.length > 0);
973
+ const attemptEntries = activeRun.canonical_state && activeRun.canonical_state.attempts && typeof activeRun.canonical_state.attempts === 'object'
974
+ ? activeRun.canonical_state.attempts
975
+ : {};
976
+ const retryExceeded = mutationNodes
977
+ .map((node) => {
978
+ const attempts = Array.isArray(attemptEntries[node.id]) ? attemptEntries[node.id].length : 0;
979
+ const maxRetries = node.policy && typeof node.policy.max_retries === 'number' ? node.policy.max_retries : null;
980
+ return maxRetries != null && attempts > maxRetries + 1 ? { nodeId: node.id, attempts, maxRetries } : null;
981
+ })
982
+ .filter(Boolean);
983
+ const quotaSummary = summarizeQuota(config, activeRun, allGraphNodes, attemptEntries);
984
+ const promotionSummary = summarizePromotion(runDir, activeRun);
985
+ const multiAgent = operational.readRuntimeMultiAgentStatus
986
+ ? operational.readRuntimeMultiAgentStatus(target, activeSession, { runId: activeRun.run_id })
987
+ : null;
988
+ const policyCoverage = summarizePolicyCoverage(runDir, mutationNodes, policyDecisions);
989
+ const residualRiskSummary = risks
990
+ ? {
991
+ total: Array.isArray(risks.risks) ? risks.risks.length : 0,
992
+ highOrCritical: Array.isArray(risks.risks)
993
+ ? risks.risks.filter((risk) => risk.severity === 'high' || risk.severity === 'critical').length
994
+ : 0,
995
+ ledgerPath: path.join(runDir, 'residual-risk-ledger.json'),
996
+ }
997
+ : null;
998
+ const verificationSummary = manifest
999
+ ? {
1000
+ total: Number(manifest.summary && manifest.summary.total || 0),
1001
+ pass: Number(manifest.summary && manifest.summary.pass || 0),
1002
+ fail: Number(manifest.summary && manifest.summary.fail || 0),
1003
+ skip: Number(manifest.summary && manifest.summary.skip || 0),
1004
+ error: Number(manifest.summary && manifest.summary.error || 0),
1005
+ allPassed: Boolean(manifest.summary && manifest.summary.all_passed),
1006
+ profile: manifest.profile || null,
1007
+ manifestPath: path.join(runDir, 'verification-manifest.json'),
1008
+ }
1009
+ : null;
1010
+ const promotionReadiness = summarizePromotionReadiness(
1011
+ verificationSummary,
1012
+ residualRiskSummary,
1013
+ evidenceCoverage,
1014
+ pendingGates,
1015
+ policyCoverage,
1016
+ promotionSummary,
1017
+ quotaSummary
1018
+ );
1019
+ const recoveryState = summarizeRecoveryState(
1020
+ target,
1021
+ activeSession,
1022
+ activeRun,
1023
+ { manifest, residualRisks: risks, evidenceCoverage }
1024
+ );
1025
+ const enterpriseWarnings = [];
1026
+ if (pendingGates.stalePending.length > 0) {
1027
+ enterpriseWarnings.push(`${pendingGates.stalePending.length} gate(s) pendente(s) há mais de 24h.`);
1028
+ }
1029
+ if (mutationNodes.length > 0 && policyDecisions.length === 0) {
1030
+ enterpriseWarnings.push('Há mutações no grafo compilado sem decisões de policy persistidas para a run ativa.');
1031
+ }
1032
+ const overridesWithoutRationale = policyDecisions.filter((decision) => decision.override && !(decision.rationale || decision.reason));
1033
+ if (overridesWithoutRationale.length > 0) {
1034
+ enterpriseWarnings.push(`${overridesWithoutRationale.length} override(s) de policy sem justificativa persistida.`);
1035
+ }
1036
+ if (retryExceeded.length > 0) {
1037
+ enterpriseWarnings.push(`${retryExceeded.length} work item(s) ultrapassaram o budget de retry configurado.`);
1038
+ }
1039
+ if (quotaSummary.exceeded) {
1040
+ enterpriseWarnings.push(`Runtime enterprise excedeu quotas configuradas: ${quotaSummary.violations.join(', ')}.`);
1041
+ }
1042
+ if (auditSummary.critical > 0) {
1043
+ enterpriseWarnings.push(`${auditSummary.critical} entrada(s) críticas no audit trail da run ativa.`);
1044
+ }
1045
+ if (policyCoverage.uncoveredMutations > 0) {
1046
+ enterpriseWarnings.push(`${policyCoverage.uncoveredMutations} mutation scope(s) ainda sem coverage de policy persistida.`);
1047
+ }
1048
+ if (providerCatalog.load_errors && providerCatalog.load_errors.length > 0) {
1049
+ enterpriseWarnings.push(...providerCatalog.load_errors);
1050
+ }
1051
+ if (recoveryState.issues.length > 0) {
1052
+ enterpriseWarnings.push(...recoveryState.issues);
1053
+ }
1054
+
1055
+ return {
1056
+ runtimeMode,
1057
+ fallbackMode: runtimeMode.fallback_mode,
1058
+ verificationSummary,
1059
+ residualRiskSummary,
1060
+ evidenceCoverage,
1061
+ pendingGates,
1062
+ gateQueue: pendingGates,
1063
+ policyDecisionSummary: {
1064
+ total: policyDecisions.length,
1065
+ denied: policyDecisions.filter((decision) => decision.allowed === false).length,
1066
+ gated: policyDecisions.filter((decision) => decision.gate_required === true).length,
1067
+ overridesWithoutRationale: overridesWithoutRationale.length,
1068
+ },
1069
+ policyCoverage,
1070
+ quotaSummary,
1071
+ auditSummary,
1072
+ promotionSummary,
1073
+ promotionReadiness,
1074
+ recoveryState,
1075
+ multiAgent,
1076
+ providerCatalog,
1077
+ enterpriseWarnings,
1078
+ };
1079
+ }
1080
+
618
1081
  /**
619
1082
  * @param {string} dir
620
1083
  * @returns {string[]}
@@ -649,7 +1112,7 @@ function hasOtherManagedInstructionBlocks(filePath) {
649
1112
  .replace(/<!-- oxe-cc:install-begin -->[\s\S]*?<!-- oxe-cc:install-end -->/g, '')
650
1113
  .trim();
651
1114
  if (!withoutOxe) return false;
652
- return /<!--[^>]*(managed|configuration|install-begin|install-end)[^>]*-->/i.test(withoutOxe);
1115
+ return /<!--[^>]*(managed|configuration|install-begin|install-end)[^>]*-->/i.test(withoutOxe);
653
1116
  }
654
1117
 
655
1118
  /**
@@ -1429,6 +1892,7 @@ function buildHealthReport(target) {
1429
1892
  const activeRun = operational.readRunState(target, activeSession);
1430
1893
  const eventsSummary = operational.summarizeEvents(operational.readEvents(target, activeSession));
1431
1894
  const memoryLayers = operational.buildMemoryLayers(target, activeSession);
1895
+ const enterpriseRuntime = summarizeEnterpriseRuntime(target, activeRun, activeSession, config);
1432
1896
  const azureActive = azure.isAzureContextEnabled(target, config);
1433
1897
  const azureReport = azureActive
1434
1898
  ? azure.azureDoctor(target, config, {
@@ -1599,6 +2063,7 @@ function buildHealthReport(target) {
1599
2063
  phaseWarn.length +
1600
2064
  runtimeWarn.length +
1601
2065
  reviewWarn.length +
2066
+ enterpriseRuntime.enterpriseWarnings.length +
1602
2067
  specWarn.length +
1603
2068
  planWarn.length +
1604
2069
  capabilityWarn.length +
@@ -1644,6 +2109,23 @@ function buildHealthReport(target) {
1644
2109
  activeRun,
1645
2110
  eventsSummary,
1646
2111
  memoryLayers,
2112
+ runtimeMode: enterpriseRuntime.runtimeMode,
2113
+ fallbackMode: enterpriseRuntime.fallbackMode,
2114
+ verificationSummary: enterpriseRuntime.verificationSummary,
2115
+ residualRiskSummary: enterpriseRuntime.residualRiskSummary,
2116
+ evidenceCoverage: enterpriseRuntime.evidenceCoverage,
2117
+ pendingGates: enterpriseRuntime.pendingGates,
2118
+ gateQueue: enterpriseRuntime.gateQueue,
2119
+ policyDecisionSummary: enterpriseRuntime.policyDecisionSummary,
2120
+ policyCoverage: enterpriseRuntime.policyCoverage,
2121
+ quotaSummary: enterpriseRuntime.quotaSummary,
2122
+ auditSummary: enterpriseRuntime.auditSummary,
2123
+ promotionSummary: enterpriseRuntime.promotionSummary,
2124
+ promotionReadiness: enterpriseRuntime.promotionReadiness,
2125
+ recoveryState: enterpriseRuntime.recoveryState,
2126
+ multiAgent: enterpriseRuntime.multiAgent,
2127
+ providerCatalog: enterpriseRuntime.providerCatalog,
2128
+ enterpriseWarn: enterpriseRuntime.enterpriseWarnings,
1647
2129
  azureActive,
1648
2130
  azure: azureReport
1649
2131
  ? {
@@ -119,6 +119,9 @@ function getWorkflowContract(slug) {
119
119
  blocking_conditions: uniqueStrings(workflow.blocking_conditions || modeDefaults.blocking_conditions || []),
120
120
  output_sections: uniqueStrings(workflow.output_sections || modeDefaults.output_sections || []),
121
121
  extraction_intent: String(workflow.extraction_intent || 'status_read'),
122
+ runtime_default_commands: Array.isArray(workflow.runtime_default_commands) ? workflow.runtime_default_commands.slice() : [],
123
+ runtime_primary_artifacts: Array.isArray(workflow.runtime_primary_artifacts) ? workflow.runtime_primary_artifacts.slice() : [],
124
+ runtime_fallback_note: workflow.runtime_fallback_note ? String(workflow.runtime_fallback_note) : '',
122
125
  auditor_artifacts: Array.isArray(workflow.auditor_artifacts) ? workflow.auditor_artifacts.slice() : [],
123
126
  auditor_excluded: Array.isArray(workflow.auditor_excluded) ? workflow.auditor_excluded.slice() : [],
124
127
  reference: MODE_REFERENCES[mode] || null,
@@ -291,6 +294,15 @@ function buildReasoningContractBlock(meta, options = {}) {
291
294
  if (blocking.length) {
292
295
  lines.push(`- **Bloqueios formais:** ${blocking.join(' · ')}`);
293
296
  }
297
+ if (Array.isArray(contract.runtime_default_commands) && contract.runtime_default_commands.length) {
298
+ lines.push(`- **Caminho runtime padrão:** ${contract.runtime_default_commands.map((cmd) => `\`${cmd}\``).join(' → ')}`);
299
+ }
300
+ if (Array.isArray(contract.runtime_primary_artifacts) && contract.runtime_primary_artifacts.length) {
301
+ lines.push(`- **Artefatos canónicos primários:** ${contract.runtime_primary_artifacts.map((artifact) => `\`${artifact}\``).join(' · ')}`);
302
+ }
303
+ if (contract.runtime_fallback_note) {
304
+ lines.push(`- **Fallback runtime:** ${contract.runtime_fallback_note}`);
305
+ }
294
306
  if (includeReference && MODE_REFERENCES[mode]) {
295
307
  lines.push(`- **Referência canónica:** \`${MODE_REFERENCES[mode]}\``);
296
308
  }