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
@@ -1,867 +1,867 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const crypto = require('crypto');
6
-
7
- const health = require('./oxe-project-health.cjs');
8
- const operational = require('./oxe-operational.cjs');
9
- const azure = require('./oxe-azure.cjs');
10
- const runtimeSemantics = require('./oxe-runtime-semantics.cjs');
11
-
12
- function ensureDir(dirPath) {
13
- fs.mkdirSync(dirPath, { recursive: true });
14
- }
15
-
16
- function readTextIfExists(filePath) {
17
- try {
18
- return fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : null;
19
- } catch {
20
- return null;
21
- }
22
- }
23
-
24
- function writeJson(filePath, value) {
25
- try {
26
- ensureDir(path.dirname(filePath));
27
- fs.writeFileSync(filePath, JSON.stringify(value, null, 2) + '\n', 'utf8');
28
- } catch (err) {
29
- throw new Error(`Falha ao escrever ${filePath}: ${err instanceof Error ? err.message : String(err)}`);
30
- }
31
- }
32
-
33
- function sha256Text(text) {
34
- return crypto.createHash('sha256').update(String(text || ''), 'utf8').digest('hex');
35
- }
36
-
37
- function sha256File(filePath) {
38
- try {
39
- return sha256Text(fs.readFileSync(filePath, 'utf8'));
40
- } catch {
41
- return null;
42
- }
43
- }
44
-
45
- function toIso(value) {
46
- if (!value) return null;
47
- const d = value instanceof Date ? value : new Date(value);
48
- return Number.isNaN(d.getTime()) ? null : d.toISOString();
49
- }
50
-
51
- function hoursSince(iso) {
52
- if (!iso) return null;
53
- const ms = Date.parse(String(iso));
54
- if (Number.isNaN(ms)) return null;
55
- return Math.floor((Date.now() - ms) / (1000 * 60 * 60));
56
- }
57
-
58
- function summarizeText(text, maxChars = 640, maxLines = 12) {
59
- const normalized = String(text || '').replace(/\r\n/g, '\n').trim();
60
- if (!normalized) return '';
61
- const lines = normalized
62
- .split('\n')
63
- .map((line) => line.trim())
64
- .filter(Boolean)
65
- .slice(0, maxLines);
66
- const joined = lines.join(' ');
67
- return joined.length > maxChars ? `${joined.slice(0, maxChars - 1)}…` : joined;
68
- }
69
-
70
- const INTENT_SECTION_KEYWORDS = {
71
- execution_input: ['onda', 'tarefa', 'hipótese', 'hipotese', 'wave', 'task', 'bloqueio', 'checkpoint', 'execu'],
72
- verification: ['gap', 'critério', 'criterio', 'finding', 'auditoria', 'resultado', 'evidência', 'evidencia', 'falha', 'lacuna'],
73
- planning_input: ['objetivo', 'autoavaliação', 'autoavaliacao', 'confiança', 'confianca', 'plano', 'requisito', 'risco', 'hipótese', 'hipotese'],
74
- critical_check: ['bloqueio', 'falha', 'gap', 'erro', 'crítico', 'critico', 'p0', 'p1'],
75
- status_read: ['fase', 'estado', 'próximo', 'proximo', 'status', 'resumo', 'sessão', 'sessao', 'snapshot'],
76
- };
77
-
78
- const DEFAULT_PRESERVE_MARKERS = [
79
- 'P0', 'P1', 'bloqueado', 'BLOQUEADO', 'FALHA', 'crítico', 'CRÍTICO',
80
- 'gaps', 'GAPS', 'bloqueante', 'BLOQUEANTE', 'crítica', 'CRÍTICA',
81
- ];
82
-
83
- /**
84
- * Extração semântica orientada a intenção — preserva marcadores críticos independentemente
85
- * de posição, prioriza seções relevantes ao workflow e preenche o orçamento restante
86
- * com conteúdo adicional.
87
- *
88
- * @param {string} text
89
- * @param {{ intent?: string, maxChars?: number, preserveMarkers?: string[] }} [options]
90
- * @returns {string}
91
- */
92
- function extractSemanticFragment(text, options = {}) {
93
- const intent = String(options.intent || 'status_read');
94
- const maxChars = Math.max(200, Number(options.maxChars) || 1200);
95
- const preserveMarkers = Array.isArray(options.preserveMarkers)
96
- ? options.preserveMarkers
97
- : DEFAULT_PRESERVE_MARKERS;
98
-
99
- const normalized = String(text || '').replace(/\r\n/g, '\n').trim();
100
- if (!normalized) return '';
101
-
102
- // Sem headings — fallback para summarizeText com limite maior
103
- if (!normalized.includes('\n## ') && !normalized.startsWith('## ') &&
104
- !normalized.includes('\n# ') && !normalized.startsWith('# ')) {
105
- return summarizeText(normalized, maxChars, 20);
106
- }
107
-
108
- // Parsear em seções delimitadas por headings (# ou ##)
109
- const sections = [];
110
- let current = { heading: '', lines: [] };
111
- for (const line of normalized.split('\n')) {
112
- if (/^#{1,3} /.test(line)) {
113
- sections.push(current);
114
- current = { heading: line, lines: [] };
115
- } else {
116
- current.lines.push(line);
117
- }
118
- }
119
- sections.push(current);
120
-
121
- const kws = INTENT_SECTION_KEYWORDS[intent] || INTENT_SECTION_KEYWORDS.status_read;
122
-
123
- // Pontuar cada seção: 3=crítica, 2=relevante ao intent, 1=primeira seção, 0=resto
124
- const scored = sections.map((s, i) => {
125
- const h = s.heading.toLowerCase();
126
- const hasCritical = [s.heading, ...s.lines].some((l) =>
127
- preserveMarkers.some((m) => l.includes(m))
128
- );
129
- const isRelevant = kws.some((k) => h.includes(k));
130
- const relevance = hasCritical ? 3 : isRelevant ? 2 : i === 0 ? 1 : 0;
131
- return { ...s, relevance, index: i };
132
- });
133
-
134
- // Ordenar: críticas primeiro, depois por intent, depois por posição original
135
- const sorted = [...scored].sort((a, b) => {
136
- if (b.relevance !== a.relevance) return b.relevance - a.relevance;
137
- return a.index - b.index;
138
- });
139
-
140
- // Preencher orçamento gulodamente
141
- const parts = [];
142
- let budget = maxChars;
143
-
144
- for (const section of sorted) {
145
- if (budget <= 0) break;
146
- const chunk = [section.heading, ...section.lines.filter(Boolean)]
147
- .filter(Boolean)
148
- .join('\n')
149
- .trim();
150
- if (!chunk) continue;
151
- if (chunk.length <= budget) {
152
- parts.push({ index: section.index, text: chunk });
153
- budget -= chunk.length + 2;
154
- } else if (budget > 80) {
155
- parts.push({ index: section.index, text: `${chunk.slice(0, budget - 1)}…` });
156
- budget = 0;
157
- }
158
- }
159
-
160
- // Reordenar na sequência original do documento
161
- parts.sort((a, b) => a.index - b.index);
162
- const result = parts.map((p) => p.text).join('\n\n').trim();
163
- return result.length > maxChars ? `${result.slice(0, maxChars - 1)}…` : result;
164
- }
165
-
166
- function sanitizeSession(activeSession) {
167
- return String(activeSession || '')
168
- .replace(/^sessions\//, '')
169
- .replace(/[\\/]/g, '__')
170
- .replace(/[^A-Za-z0-9._-]+/g, '-');
171
- }
172
-
173
- function contextPaths(projectRoot, activeSession) {
174
- const base = health.oxePaths(projectRoot);
175
- const stateText = readTextIfExists(base.state) || '';
176
- const resolvedSession = activeSession === undefined ? health.parseActiveSession(stateText) : activeSession;
177
- const sessionKey = sanitizeSession(resolvedSession);
178
- const phase = health.parseStatePhase(stateText) || 'unknown';
179
- const safePhase = String(phase).replace(/[^A-Za-z0-9._-]+/g, '-');
180
- const root = path.join(base.oxe, 'context');
181
- const packsDir = path.join(root, 'packs');
182
- const summariesDir = path.join(root, 'summaries');
183
- return {
184
- root,
185
- index: path.join(root, 'index.json'),
186
- packsDir,
187
- summariesDir,
188
- projectSummaryJson: path.join(summariesDir, 'project.json'),
189
- projectSummaryMd: path.join(summariesDir, 'project.md'),
190
- sessionSummaryJson: resolvedSession ? path.join(summariesDir, `session-${sessionKey}.json`) : null,
191
- sessionSummaryMd: resolvedSession ? path.join(summariesDir, `session-${sessionKey}.md`) : null,
192
- phaseSummaryJson: path.join(summariesDir, `phase-${safePhase}.json`),
193
- phaseSummaryMd: path.join(summariesDir, `phase-${safePhase}.md`),
194
- defaultPackJson: (workflow) => path.join(packsDir, `${workflow}.json`),
195
- defaultPackMd: (workflow) => path.join(packsDir, `${workflow}.md`),
196
- sessionPackJson: (workflow) => resolvedSession ? path.join(packsDir, `session-${sessionKey}-${workflow}.json`) : null,
197
- sessionPackMd: (workflow) => resolvedSession ? path.join(packsDir, `session-${sessionKey}-${workflow}.md`) : null,
198
- activeSession: resolvedSession || null,
199
- phase,
200
- };
201
- }
202
-
203
- function inferScope(activeSession, preferSession) {
204
- if (preferSession && activeSession) return 'session';
205
- return 'project';
206
- }
207
-
208
- function resolveArtifactCandidates(projectRoot, activeSession) {
209
- const base = health.oxePaths(projectRoot);
210
- const scoped = health.scopedOxePaths(projectRoot, activeSession || null);
211
- const ctx = contextPaths(projectRoot, activeSession);
212
- const azurePaths = azure.azurePaths(projectRoot);
213
- const preferSession = Boolean(activeSession);
214
- const withFallback = (alias, semanticType, primaryPath, fallbackPath = null, scope = inferScope(activeSession, true)) => ({
215
- alias,
216
- semantic_type: semanticType,
217
- scope,
218
- primary: primaryPath,
219
- fallback: fallbackPath && fallbackPath !== primaryPath ? fallbackPath : null,
220
- });
221
- return {
222
- state: withFallback('state', 'state', base.state, null, 'project'),
223
- session_manifest: withFallback('session_manifest', 'session', scoped.sessionManifest || null, null, 'session'),
224
- sessions_index: withFallback('sessions_index', 'session_index', base.sessionsIndex, null, 'project'),
225
- execution_state: withFallback('execution_state', 'state', scoped.executionState || null, null, 'session'),
226
- spec: withFallback('spec', 'spec', scoped.spec, base.spec),
227
- discuss: withFallback('discuss', 'discuss', scoped.discuss, base.discuss),
228
- plan: withFallback('plan', 'plan', scoped.plan, base.plan),
229
- plan_agents: withFallback('plan_agents', 'plan_agents', base.planAgents, null, 'project'),
230
- quick: withFallback('quick', 'plan', scoped.quick, base.quick),
231
- runtime: withFallback('runtime', 'runtime', scoped.runtime, base.runtime),
232
- checkpoints: withFallback('checkpoints', 'checkpoints', scoped.checkpoints, base.checkpoints),
233
- verify: withFallback('verify', 'verify', scoped.verify, base.verify),
234
- summary: withFallback('summary', 'summary', scoped.summary, base.summary),
235
- plan_review: withFallback('plan_review', 'review', scoped.planReview, base.planReview),
236
- review_comments: withFallback('review_comments', 'review_comments', scoped.planReviewComments, base.planReviewComments),
237
- active_run: withFallback('active_run', 'runtime', operational.operationalPaths(projectRoot, activeSession || null).activeRun, null, preferSession ? 'session' : 'project'),
238
- events: withFallback('events', 'trace', operational.operationalPaths(projectRoot, activeSession || null).events, null, preferSession ? 'session' : 'project'),
239
- capabilities_index: withFallback('capabilities_index', 'capabilities', base.capabilitiesIndex, null, 'project'),
240
- investigations_index: withFallback('investigations_index', 'investigations', scoped.investigationsIndex, base.investigationsIndex),
241
- global_lessons: withFallback('global_lessons', 'memory', base.globalLessons, base.lessons, 'project'),
242
- codebase_overview: withFallback('codebase_overview', 'codebase', path.join(base.codebase, 'OVERVIEW.md'), null, 'project'),
243
- codebase_stack: withFallback('codebase_stack', 'codebase', path.join(base.codebase, 'STACK.md'), null, 'project'),
244
- codebase_structure: withFallback('codebase_structure', 'codebase', path.join(base.codebase, 'STRUCTURE.md'), null, 'project'),
245
- codebase_testing: withFallback('codebase_testing', 'codebase', path.join(base.codebase, 'TESTING.md'), null, 'project'),
246
- codebase_integrations: withFallback('codebase_integrations', 'codebase', path.join(base.codebase, 'INTEGRATIONS.md'), null, 'project'),
247
- codebase_concerns: withFallback('codebase_concerns', 'codebase', path.join(base.codebase, 'CONCERNS.md'), null, 'project'),
248
- azure_inventory: withFallback('azure_inventory', 'provider', azurePaths.inventoryMd, null, 'project'),
249
- azure_servicebus: withFallback('azure_servicebus', 'provider', azurePaths.serviceBusMd, null, 'project'),
250
- azure_eventgrid: withFallback('azure_eventgrid', 'provider', azurePaths.eventGridMd, null, 'project'),
251
- azure_sql: withFallback('azure_sql', 'provider', azurePaths.sqlMd, null, 'project'),
252
- copilot_manifest: withFallback('copilot_manifest', 'install_manifest', base.copilotManifest, null, 'project'),
253
- runtime_semantics_manifest: withFallback('runtime_semantics_manifest', 'install_manifest', path.join(base.installDir, 'runtime-semantics.json'), null, 'project'),
254
- project_summary: withFallback('project_summary', 'summary', ctx.projectSummaryJson, null, 'project'),
255
- session_summary: withFallback('session_summary', 'summary', ctx.sessionSummaryJson, null, 'session'),
256
- phase_summary: withFallback('phase_summary', 'summary', ctx.phaseSummaryJson, null, 'project'),
257
- context_pack_dashboard: withFallback('context_pack_dashboard', 'context_pack', ctx.defaultPackJson('dashboard'), null, 'project'),
258
- calibration: withFallback('calibration', 'calibration', path.join(base.oxe, 'calibration.json'), null, 'project'),
259
- lessons_metrics: withFallback('lessons_metrics', 'metrics', path.join(base.oxe, 'lessons-metrics.json'), null, 'project'),
260
- };
261
- }
262
-
263
- function buildArtifactRecord(candidate) {
264
- const primaryExists = Boolean(candidate.primary && fs.existsSync(candidate.primary));
265
- const fallbackExists = Boolean(candidate.fallback && fs.existsSync(candidate.fallback));
266
- const chosenPath = primaryExists ? candidate.primary : fallbackExists ? candidate.fallback : candidate.primary || candidate.fallback || null;
267
- const chosenExists = Boolean(chosenPath && fs.existsSync(chosenPath));
268
- const chosenText = chosenExists ? readTextIfExists(chosenPath) || '' : '';
269
- const primaryHash = primaryExists ? sha256File(candidate.primary) : null;
270
- const fallbackHash = fallbackExists ? sha256File(candidate.fallback) : null;
271
- const stat = chosenExists ? fs.statSync(chosenPath) : null;
272
- return {
273
- alias: candidate.alias,
274
- semantic_type: candidate.semantic_type,
275
- scope: candidate.scope,
276
- primary_path: candidate.primary || null,
277
- primary_exists: primaryExists,
278
- fallback_path: candidate.fallback || null,
279
- fallback_exists: fallbackExists,
280
- path: chosenPath,
281
- exists: chosenExists,
282
- using_fallback: !primaryExists && fallbackExists,
283
- hash: chosenExists ? sha256Text(chosenText) : null,
284
- primary_hash: primaryHash,
285
- fallback_hash: fallbackHash,
286
- updated_at: stat ? stat.mtime.toISOString() : null,
287
- age_hours: stat ? hoursSince(stat.mtime.toISOString()) : null,
288
- size_bytes: stat ? stat.size : 0,
289
- summary: chosenExists ? summarizeText(chosenText) : '',
290
- conflict: Boolean(primaryExists && fallbackExists && primaryHash && fallbackHash && primaryHash !== fallbackHash),
291
- };
292
- }
293
-
294
- function buildProjectSummary(projectRoot, activeSession, options = {}) {
295
- const base = health.oxePaths(projectRoot);
296
- const codebaseDir = base.codebase;
297
- const overview = summarizeText(readTextIfExists(path.join(codebaseDir, 'OVERVIEW.md')) || '');
298
- const stack = summarizeText(readTextIfExists(path.join(codebaseDir, 'STACK.md')) || '');
299
- const concerns = summarizeText(readTextIfExists(path.join(codebaseDir, 'CONCERNS.md')) || '');
300
- const lessons = summarizeText(readTextIfExists(base.globalLessons) || '');
301
- const stateText = readTextIfExists(base.state) || '';
302
- const payload = {
303
- summary_type: 'project',
304
- generated_at: new Date().toISOString(),
305
- project_root: path.resolve(projectRoot),
306
- phase: health.parseStatePhase(stateText),
307
- active_session: activeSession || health.parseActiveSession(stateText),
308
- overview,
309
- stack,
310
- concerns,
311
- lessons,
312
- };
313
- const md =
314
- '# OXE Context Summary — Project\n\n' +
315
- `- **Gerado em:** ${payload.generated_at}\n` +
316
- `- **Fase:** ${payload.phase || '—'}\n` +
317
- `- **Sessão ativa:** ${payload.active_session || 'modo legado'}\n\n` +
318
- '## Overview\n\n' +
319
- `${overview || '—'}\n\n` +
320
- '## Stack\n\n' +
321
- `${stack || '—'}\n\n` +
322
- '## Concerns\n\n' +
323
- `${concerns || '—'}\n\n` +
324
- '## Lessons\n\n' +
325
- `${lessons || '—'}\n`;
326
- if (options.write !== false) {
327
- const ctx = contextPaths(projectRoot, activeSession);
328
- try {
329
- writeJson(ctx.projectSummaryJson, payload);
330
- ensureDir(path.dirname(ctx.projectSummaryMd));
331
- fs.writeFileSync(ctx.projectSummaryMd, md, 'utf8');
332
- } catch (err) {
333
- throw new Error(`buildProjectSummary: ${err instanceof Error ? err.message : String(err)}`);
334
- }
335
- }
336
- return { json: payload, markdown: md };
337
- }
338
-
339
- function buildSessionSummary(projectRoot, activeSession, options = {}) {
340
- if (!activeSession) return null;
341
- const scoped = health.scopedOxePaths(projectRoot, activeSession);
342
- const stateText = readTextIfExists(health.oxePaths(projectRoot).state) || '';
343
- const sessionText = readTextIfExists(scoped.sessionManifest) || '';
344
- const spec = summarizeText(readTextIfExists(scoped.spec) || '');
345
- const plan = summarizeText(readTextIfExists(scoped.plan) || '');
346
- const runtime = summarizeText(readTextIfExists(scoped.runtime) || '');
347
- const verify = summarizeText(readTextIfExists(scoped.verify) || '');
348
- const payload = {
349
- summary_type: 'session',
350
- generated_at: new Date().toISOString(),
351
- session: activeSession,
352
- phase: health.parseStatePhase(stateText),
353
- session_manifest: summarizeText(sessionText),
354
- spec,
355
- plan,
356
- runtime,
357
- verify,
358
- };
359
- const md =
360
- '# OXE Context Summary — Session\n\n' +
361
- `- **Gerado em:** ${payload.generated_at}\n` +
362
- `- **Sessão:** ${payload.session}\n` +
363
- `- **Fase:** ${payload.phase || '—'}\n\n` +
364
- '## Session Manifest\n\n' +
365
- `${payload.session_manifest || '—'}\n\n` +
366
- '## SPEC\n\n' +
367
- `${spec || '—'}\n\n` +
368
- '## PLAN\n\n' +
369
- `${plan || '—'}\n\n` +
370
- '## Runtime\n\n' +
371
- `${runtime || '—'}\n\n` +
372
- '## VERIFY\n\n' +
373
- `${verify || '—'}\n`;
374
- if (options.write !== false) {
375
- const ctx = contextPaths(projectRoot, activeSession);
376
- try {
377
- if (ctx.sessionSummaryJson) writeJson(ctx.sessionSummaryJson, payload);
378
- if (ctx.sessionSummaryMd) {
379
- ensureDir(path.dirname(ctx.sessionSummaryMd));
380
- fs.writeFileSync(ctx.sessionSummaryMd, md, 'utf8');
381
- }
382
- } catch (err) {
383
- throw new Error(`buildSessionSummary: ${err instanceof Error ? err.message : String(err)}`);
384
- }
385
- }
386
- return { json: payload, markdown: md };
387
- }
388
-
389
- function buildPhaseSummary(projectRoot, activeSession, options = {}) {
390
- const base = health.oxePaths(projectRoot);
391
- const stateText = readTextIfExists(base.state) || '';
392
- const payload = {
393
- summary_type: 'phase',
394
- generated_at: new Date().toISOString(),
395
- phase: health.parseStatePhase(stateText),
396
- active_session: activeSession || health.parseActiveSession(stateText),
397
- next_step: firstNonEmpty([
398
- summarizeText(readTextIfExists(base.state) || '', 220, 8),
399
- ]),
400
- runtime_status: firstMatch(stateText, /\*\*runtime_status:\*\*\s*([^\n]+)/i),
401
- plan_review_status: health.parsePlanReviewStatus(stateText),
402
- };
403
- const md =
404
- '# OXE Context Summary — Phase\n\n' +
405
- `- **Gerado em:** ${payload.generated_at}\n` +
406
- `- **Fase:** ${payload.phase || '—'}\n` +
407
- `- **Sessão ativa:** ${payload.active_session || 'modo legado'}\n` +
408
- `- **runtime_status:** ${payload.runtime_status || '—'}\n` +
409
- `- **plan_review_status:** ${payload.plan_review_status || '—'}\n\n` +
410
- '## Snapshot\n\n' +
411
- `${payload.next_step || '—'}\n`;
412
- if (options.write !== false) {
413
- const ctx = contextPaths(projectRoot, activeSession);
414
- try {
415
- writeJson(ctx.phaseSummaryJson, payload);
416
- ensureDir(path.dirname(ctx.phaseSummaryMd));
417
- fs.writeFileSync(ctx.phaseSummaryMd, md, 'utf8');
418
- } catch (err) {
419
- throw new Error(`buildPhaseSummary: ${err instanceof Error ? err.message : String(err)}`);
420
- }
421
- }
422
- return { json: payload, markdown: md };
423
- }
424
-
425
- function firstMatch(text, regex) {
426
- const match = String(text || '').match(regex);
427
- return match ? match[1].trim() : null;
428
- }
429
-
430
- function firstNonEmpty(values) {
431
- for (const value of values || []) {
432
- if (value && String(value).trim()) return String(value).trim();
433
- }
434
- return null;
435
- }
436
-
437
- function buildContextIndex(projectRoot, activeSession, options = {}) {
438
- const writeOpt = { write: options.write !== false };
439
- const summaryErrors = [];
440
- for (const [label, fn, args] of [
441
- ['project', buildProjectSummary, [projectRoot, activeSession, writeOpt]],
442
- ['session', buildSessionSummary, [projectRoot, activeSession, writeOpt]],
443
- ['phase', buildPhaseSummary, [projectRoot, activeSession, writeOpt]],
444
- ]) {
445
- try {
446
- fn(...args);
447
- } catch (err) {
448
- summaryErrors.push(`${label}: ${err instanceof Error ? err.message : String(err)}`);
449
- }
450
- }
451
- if (summaryErrors.length) {
452
- process.stderr.write(`[oxe] WARN buildContextIndex — falha ao escrever summaries: ${summaryErrors.join('; ')}\n`);
453
- }
454
- const stateText = readTextIfExists(health.oxePaths(projectRoot).state) || '';
455
- const resolvedSession = activeSession === undefined ? health.parseActiveSession(stateText) : activeSession;
456
- const candidates = resolveArtifactCandidates(projectRoot, resolvedSession || null);
457
- const artifacts = Object.keys(candidates)
458
- .sort()
459
- .map((alias) => buildArtifactRecord(candidates[alias]));
460
- const payload = {
461
- schema_version: 1,
462
- generated_at: new Date().toISOString(),
463
- project_root: path.resolve(projectRoot),
464
- active_session: resolvedSession || null,
465
- phase: health.parseStatePhase(stateText),
466
- artifacts,
467
- stats: {
468
- total: artifacts.length,
469
- existing: artifacts.filter((item) => item.exists).length,
470
- missing: artifacts.filter((item) => !item.exists).length,
471
- conflicts: artifacts.filter((item) => item.conflict).length,
472
- },
473
- };
474
- if (options.write !== false) {
475
- const ctx = contextPaths(projectRoot, resolvedSession || null);
476
- ensureDir(ctx.root);
477
- writeJson(ctx.index, payload);
478
- }
479
- return payload;
480
- }
481
-
482
- function computePackFreshness(pack, contract) {
483
- const generatedAt = pack.generated_at || null;
484
- const sourceTimes = (pack.selected_artifacts || [])
485
- .map((artifact) => artifact.updated_at)
486
- .filter(Boolean)
487
- .map((value) => Date.parse(String(value)))
488
- .filter((value) => !Number.isNaN(value));
489
- const latestSource = sourceTimes.length ? new Date(Math.max(...sourceTimes)).toISOString() : null;
490
- const generatedMs = generatedAt ? Date.parse(String(generatedAt)) : Number.NaN;
491
- const latestSourceMs = latestSource ? Date.parse(latestSource) : Number.NaN;
492
- const packAgeHours = hoursSince(generatedAt);
493
- const maxPackAgeHours = contract && contract.freshness_policy && contract.freshness_policy.pack_max_age_hours != null
494
- ? Number(contract.freshness_policy.pack_max_age_hours)
495
- : 12;
496
- const staleByAge = packAgeHours != null && maxPackAgeHours > 0 ? packAgeHours > maxPackAgeHours : false;
497
- const staleBySource = !Number.isNaN(generatedMs) && !Number.isNaN(latestSourceMs) ? generatedMs < latestSourceMs : false;
498
- return {
499
- generated_at: generatedAt,
500
- latest_source_at: latestSource,
501
- pack_age_hours: packAgeHours,
502
- max_pack_age_hours: maxPackAgeHours,
503
- stale: staleByAge || staleBySource || Boolean(pack.fallback_required),
504
- reason: staleBySource ? 'source_newer_than_pack' : staleByAge ? 'pack_age_exceeded' : pack.fallback_required ? 'fallback_required' : 'fresh',
505
- };
506
- }
507
-
508
- function computeContextQuality(pack) {
509
- const requiredMissing = (pack.gaps || []).filter((gap) => gap.severity === 'critical').length;
510
- const optionalMissing = (pack.gaps || []).filter((gap) => gap.severity !== 'critical').length;
511
- const conflicts = (pack.conflicts || []).length;
512
- const fallbackCount = (pack.selected_artifacts || []).filter((artifact) => artifact.using_fallback).length;
513
- let score = 100;
514
- score -= requiredMissing * 25;
515
- score -= optionalMissing * 5;
516
- score -= conflicts * 12;
517
- score -= fallbackCount * 6;
518
- if ((pack.selected_artifacts || []).length === 0) score -= 40;
519
- score = Math.max(0, Math.min(100, score));
520
- const status = score >= 85 ? 'excellent' : score >= 70 ? 'good' : score >= 50 ? 'fragile' : 'critical';
521
- return {
522
- score,
523
- status,
524
- requiredMissing,
525
- optionalMissing,
526
- conflicts,
527
- fallbackCount,
528
- };
529
- }
530
-
531
- function renderPackMarkdown(pack) {
532
- const selected = (pack.selected_artifacts || [])
533
- .map((artifact) => `- **${artifact.alias}** (${artifact.scope}) -> ${artifact.exists ? artifact.path : 'ausente'}${artifact.using_fallback ? ' [fallback]' : ''}`)
534
- .join('\n') || '- Nenhum artefato selecionado';
535
- const gaps = (pack.gaps || [])
536
- .map((gap) => `- [${gap.severity}] ${gap.alias}: ${gap.reason}`)
537
- .join('\n') || '- Nenhuma lacuna';
538
- const conflicts = (pack.conflicts || [])
539
- .map((conflict) => `- ${conflict.alias}: ${conflict.reason}`)
540
- .join('\n') || '- Nenhum conflito';
541
- const sections = (pack.contract && pack.contract.output_sections || []).join(' · ') || '—';
542
- return (
543
- `# OXE Context Pack — ${pack.workflow}\n\n` +
544
- `- **Gerado em:** ${pack.generated_at}\n` +
545
- `- **Sessão ativa:** ${pack.active_session || 'modo legado'}\n` +
546
- `- **Tier:** ${pack.context_tier}\n` +
547
- `- **Semantics hash:** \`${pack.semantics_hash}\`\n` +
548
- `- **Quality score:** ${pack.context_quality.score}\n` +
549
- `- **Fallback required:** ${pack.fallback_required ? 'sim' : 'não'}\n\n` +
550
- '## Read Order\n\n' +
551
- `${(pack.read_order || []).map((alias) => `- ${alias}`).join('\n') || '- Nenhuma ordem resolvida'}\n\n` +
552
- '## Selected Artifacts\n\n' +
553
- `${selected}\n\n` +
554
- '## Gaps\n\n' +
555
- `${gaps}\n\n` +
556
- '## Conflicts\n\n' +
557
- `${conflicts}\n\n` +
558
- '## Output Contract\n\n' +
559
- `${sections}\n`
560
- );
561
- }
562
-
563
- /**
564
- * Extrai o vetor de confiança de um PLAN.md (bloco <confidence_vector>).
565
- * @param {string} planText
566
- * @returns {{ cycle: string|null, generated_at: string|null, dimensions: Array<{ name: string, score: number, weight: number, note: string }>, global: { score: number, gate: string } } | null}
567
- */
568
- function parseConfidenceVector(planText) {
569
- const text = String(planText || '');
570
- const blockMatch = text.match(/<confidence_vector\s+([^>]*)>([\s\S]*?)<\/confidence_vector>/i);
571
- if (!blockMatch) return null;
572
-
573
- const attrs = blockMatch[1];
574
- const body = blockMatch[2];
575
-
576
- const cycle = (attrs.match(/\bcycle=["']([^"']+)["']/) || [])[1] || null;
577
- const generated_at = (attrs.match(/\bgenerated_at=["']([^"']+)["']/) || [])[1] || null;
578
-
579
- // Extrair dimensões: <dim name="..." score="..." weight="..." note="..." />
580
- const dimensions = [];
581
- const dimPattern = /<dim\s+([^/]*)\s*\/>/gi;
582
- let m;
583
- while ((m = dimPattern.exec(body)) !== null) {
584
- const dAttrs = m[1];
585
- const name = (dAttrs.match(/\bname=["']([^"']+)["']/) || [])[1] || '';
586
- const score = parseFloat((dAttrs.match(/\bscore=["']([^"']+)["']/) || [])[1] || '0');
587
- const weight = parseFloat((dAttrs.match(/\bweight=["']([^"']+)["']/) || [])[1] || '0');
588
- const note = (dAttrs.match(/\bnote=["']([^"']+)["']/) || [])[1] || '';
589
- if (name) dimensions.push({ name, score: isNaN(score) ? 0 : score, weight: isNaN(weight) ? 0 : weight, note });
590
- }
591
-
592
- // Extrair global: <global score="..." gate="..." />
593
- const globalMatch = body.match(/<global\s+([^/]*)\s*\/>/i);
594
- const globalAttrs = globalMatch ? globalMatch[1] : '';
595
- const globalScore = parseFloat((globalAttrs.match(/\bscore=["']([^"']+)["']/) || [])[1] || '0');
596
- const gate = (globalAttrs.match(/\bgate=["']([^"']+)["']/) || [])[1] || 'proceed_with_risk';
597
-
598
- // Se não há global explícito, calcular como média ponderada
599
- let computedScore = globalScore;
600
- if (!globalMatch && dimensions.length > 0) {
601
- const totalWeight = dimensions.reduce((s, d) => s + d.weight, 0);
602
- computedScore = totalWeight > 0
603
- ? dimensions.reduce((s, d) => s + d.score * d.weight, 0) / totalWeight
604
- : 0;
605
- }
606
-
607
- return {
608
- cycle,
609
- generated_at,
610
- dimensions,
611
- global: { score: isNaN(computedScore) ? 0 : Math.round(computedScore * 100) / 100, gate },
612
- };
613
- }
614
-
615
- /**
616
- * Extrai hipóteses críticas de um PLAN.md.
617
- * Suporta tags XML (<hypothesis ...>) e fallback para tabela Markdown.
618
- * @param {string} planText
619
- * @returns {Array<{ id: string, condition: string, validation: string, on_failure: string, checkpoint: string|null, status: string }>}
620
- */
621
- function parseHypotheses(planText) {
622
- const text = String(planText || '');
623
- const results = [];
624
-
625
- // Formato 1: tags XML
626
- const xmlPattern = /<hypothesis\s+([^>]*)>([\s\S]*?)<\/hypothesis>/gi;
627
- let m;
628
- while ((m = xmlPattern.exec(text)) !== null) {
629
- const attrs = m[1];
630
- const body = m[2];
631
- const id = (attrs.match(/\bid=["']([^"']+)["']/) || [])[1] || '';
632
- const checkpoint = (attrs.match(/\bcheckpoint=["']([^"']+)["']/) || [])[1] || null;
633
- const status = (attrs.match(/\bstatus=["']([^"']+)["']/) || [])[1] || 'pending';
634
- const condition = (body.match(/<condition>([\s\S]*?)<\/condition>/) || [])[1]?.trim() || '';
635
- const validation = (body.match(/<validation>([\s\S]*?)<\/validation>/) || [])[1]?.trim() || '';
636
- const on_failure = (body.match(/<on_failure>([\s\S]*?)<\/on_failure>/) || [])[1]?.trim() || '';
637
- if (id) results.push({ id, condition, validation, on_failure, checkpoint, status });
638
- }
639
- if (results.length > 0) return results;
640
-
641
- // Formato 2: tabela Markdown (fallback)
642
- // Encontrar a seção e extrair linhas até o próximo heading
643
- const sectionIdx = text.search(/##\s*Hip.teses\s*Cr.ticas/im);
644
- if (sectionIdx !== -1) {
645
- const afterSection = text.slice(sectionIdx);
646
- // Parar no próximo heading ## ou # (excluindo o próprio)
647
- const nextHeadingMatch = afterSection.slice(3).match(/\n#{1,3} /);
648
- const sectionText = nextHeadingMatch
649
- ? afterSection.slice(0, nextHeadingMatch.index + 3 + 1)
650
- : afterSection;
651
- const rows = sectionText.split('\n').filter((l) => l.trimStart().startsWith('|'));
652
- for (const row of rows) {
653
- const cells = row.split('|').map((c) => c.trim()).filter(Boolean);
654
- if (cells.length >= 2 && /^H\d+$/i.test(cells[0])) {
655
- results.push({
656
- id: cells[0],
657
- condition: cells[1] || '',
658
- validation: cells[2] || '',
659
- on_failure: cells[3] || '',
660
- checkpoint: cells[4] || null,
661
- status: cells[5] || 'pending',
662
- });
663
- }
664
- }
665
- }
666
- return results;
667
- }
668
-
669
- function buildContextPack(projectRoot, input = {}) {
670
- const workflow = String(input.workflow || '').trim();
671
- if (!workflow) {
672
- throw new Error('workflow é obrigatório para buildContextPack');
673
- }
674
- const contract = runtimeSemantics.getWorkflowContract(workflow);
675
- if (!contract) {
676
- throw new Error(`Workflow sem contrato canónico: ${workflow}`);
677
- }
678
- const stateText = readTextIfExists(health.oxePaths(projectRoot).state) || '';
679
- const activeSession = input.activeSession === undefined ? health.parseActiveSession(stateText) : input.activeSession;
680
- const tier = ['minimal', 'standard', 'full'].includes(String(input.tier || 'standard'))
681
- ? String(input.tier || 'standard')
682
- : 'standard';
683
- const mode = String(input.mode || 'standard');
684
- const ctx = contextPaths(projectRoot, activeSession || null);
685
- const index = buildContextIndex(projectRoot, activeSession || null, { write: input.write !== false });
686
- const byAlias = new Map((index.artifacts || []).map((artifact) => [artifact.alias, artifact]));
687
-
688
- // Modo auditor: usa auditor_artifacts do contrato e exclui auditor_excluded
689
- let selectedAliases;
690
- if (mode === 'auditor' && contract.auditor_artifacts && contract.auditor_artifacts.length > 0) {
691
- const excluded = new Set(contract.auditor_excluded || []);
692
- selectedAliases = Array.from(
693
- new Set(contract.auditor_artifacts.filter((a) => !excluded.has(a)))
694
- );
695
- } else {
696
- selectedAliases = Array.from(
697
- new Set(['state', ...(contract.context_tiers[tier] || contract.context_tiers.standard || [])])
698
- );
699
- }
700
- const intent = String(contract.extraction_intent || 'status_read');
701
- const selectedArtifacts = selectedAliases.map((alias) => {
702
- const artifact = byAlias.get(alias);
703
- if (artifact) {
704
- const rawText = artifact.exists ? readTextIfExists(artifact.path) || '' : '';
705
- const semanticSummary = rawText
706
- ? extractSemanticFragment(rawText, { intent })
707
- : '';
708
- return {
709
- ...artifact,
710
- required: contract.required_artifacts.includes(alias),
711
- selected_because: contract.required_artifacts.includes(alias) ? 'required_artifact' : 'context_tier',
712
- semantic_summary: semanticSummary,
713
- };
714
- }
715
- return {
716
- alias,
717
- path: null,
718
- exists: false,
719
- scope: 'unknown',
720
- semantic_type: 'unknown',
721
- using_fallback: false,
722
- required: contract.required_artifacts.includes(alias),
723
- selected_because: contract.required_artifacts.includes(alias) ? 'required_artifact' : 'context_tier',
724
- updated_at: null,
725
- age_hours: null,
726
- hash: null,
727
- summary: '',
728
- semantic_summary: '',
729
- };
730
- });
731
- const gaps = [];
732
- for (const artifact of selectedArtifacts) {
733
- if (!artifact.exists) {
734
- gaps.push({
735
- alias: artifact.alias,
736
- severity: artifact.required ? 'critical' : 'warning',
737
- reason: artifact.required ? 'required_artifact_missing' : 'selected_artifact_missing',
738
- });
739
- }
740
- }
741
- const conflicts = selectedArtifacts
742
- .filter((artifact) => artifact.conflict)
743
- .map((artifact) => ({
744
- alias: artifact.alias,
745
- reason: 'session_and_root_artifacts_diverge',
746
- primary_path: artifact.primary_path,
747
- fallback_path: artifact.fallback_path,
748
- }));
749
- const pack = {
750
- schema_version: 1,
751
- workflow,
752
- mode,
753
- active_session: activeSession || null,
754
- context_tier: tier,
755
- generated_at: new Date().toISOString(),
756
- semantics_hash: runtimeSemantics.computeSemanticsHash(workflow),
757
- contract,
758
- read_order: selectedArtifacts.filter((artifact) => artifact.exists).map((artifact) => artifact.alias),
759
- selected_artifacts: selectedArtifacts,
760
- gaps,
761
- conflicts,
762
- fallback_required: gaps.some((gap) => gap.severity === 'critical') || selectedArtifacts.some((artifact) => artifact.using_fallback),
763
- summaries: {
764
- project: ctx.projectSummaryJson,
765
- session: ctx.sessionSummaryJson,
766
- phase: ctx.phaseSummaryJson,
767
- },
768
- };
769
- // Extrair hipóteses críticas do PLAN.md se disponível no pack
770
- const planArtifact = selectedArtifacts.find((a) => a.alias === 'plan' && a.exists);
771
- const hypotheses = planArtifact ? parseHypotheses(readTextIfExists(planArtifact.path) || '') : [];
772
- pack.context_quality = computeContextQuality(pack);
773
- pack.freshness = computePackFreshness(pack, contract);
774
- pack.hypotheses = hypotheses;
775
- pack.markdown = renderPackMarkdown(pack);
776
- if (input.write !== false) {
777
- try {
778
- ensureDir(ctx.packsDir);
779
- const defaultJson = ctx.defaultPackJson(workflow);
780
- const defaultMd = ctx.defaultPackMd(workflow);
781
- writeJson(defaultJson, pack);
782
- fs.writeFileSync(defaultMd, pack.markdown, 'utf8');
783
- if (ctx.sessionPackJson(workflow)) {
784
- writeJson(ctx.sessionPackJson(workflow), pack);
785
- }
786
- if (ctx.sessionPackMd(workflow)) {
787
- fs.writeFileSync(ctx.sessionPackMd(workflow), pack.markdown, 'utf8');
788
- }
789
- } catch (err) {
790
- throw new Error(`buildContextPack (${workflow}): falha ao persistir pack — ${err instanceof Error ? err.message : String(err)}`);
791
- }
792
- }
793
- return pack;
794
- }
795
-
796
- function resolvePackFile(projectRoot, workflow, activeSession) {
797
- const ctx = contextPaths(projectRoot, activeSession);
798
- const candidates = [ctx.sessionPackJson(workflow), ctx.defaultPackJson(workflow)].filter(Boolean);
799
- for (const filePath of candidates) {
800
- if (fs.existsSync(filePath)) return filePath;
801
- }
802
- return ctx.defaultPackJson(workflow);
803
- }
804
-
805
- function inspectContextPack(projectRoot, input = {}) {
806
- const workflow = String(input.workflow || '').trim();
807
- if (!workflow) {
808
- throw new Error('workflow é obrigatório para inspectContextPack');
809
- }
810
- const stateText = readTextIfExists(health.oxePaths(projectRoot).state) || '';
811
- const activeSession = input.activeSession === undefined ? health.parseActiveSession(stateText) : input.activeSession;
812
- const filePath = resolvePackFile(projectRoot, workflow, activeSession || null);
813
- let pack = null;
814
- if (fs.existsSync(filePath)) {
815
- try {
816
- pack = JSON.parse(fs.readFileSync(filePath, 'utf8'));
817
- } catch {
818
- pack = null;
819
- }
820
- }
821
- if (!pack) {
822
- pack = buildContextPack(projectRoot, {
823
- workflow,
824
- tier: input.tier || 'standard',
825
- activeSession: activeSession || null,
826
- write: false,
827
- });
828
- } else {
829
- const contract = runtimeSemantics.getWorkflowContract(workflow);
830
- pack.contract = contract;
831
- pack.freshness = computePackFreshness(pack, contract);
832
- pack.context_quality = computeContextQuality(pack);
833
- }
834
- pack.path = filePath;
835
- return pack;
836
- }
837
-
838
- function buildAllContextPacks(projectRoot, input = {}) {
839
- const workflows = input.workflow
840
- ? [String(input.workflow)]
841
- : runtimeSemantics.getAllWorkflowContracts().map((contract) => contract.workflow_slug);
842
- return workflows.map((workflow) => buildContextPack(projectRoot, {
843
- workflow,
844
- tier: input.tier || 'standard',
845
- activeSession: input.activeSession,
846
- write: input.write !== false,
847
- }));
848
- }
849
-
850
- module.exports = {
851
- buildAllContextPacks,
852
- buildContextIndex,
853
- buildContextPack,
854
- buildPhaseSummary,
855
- buildProjectSummary,
856
- buildSessionSummary,
857
- computeContextQuality,
858
- computePackFreshness,
859
- contextPaths,
860
- extractSemanticFragment,
861
- inspectContextPack,
862
- parseConfidenceVector,
863
- parseHypotheses,
864
- resolveArtifactCandidates,
865
- resolvePackFile,
866
- summarizeText,
867
- };
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const crypto = require('crypto');
6
+
7
+ const health = require('./oxe-project-health.cjs');
8
+ const operational = require('./oxe-operational.cjs');
9
+ const azure = require('./oxe-azure.cjs');
10
+ const runtimeSemantics = require('./oxe-runtime-semantics.cjs');
11
+
12
+ function ensureDir(dirPath) {
13
+ fs.mkdirSync(dirPath, { recursive: true });
14
+ }
15
+
16
+ function readTextIfExists(filePath) {
17
+ try {
18
+ return fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : null;
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+
24
+ function writeJson(filePath, value) {
25
+ try {
26
+ ensureDir(path.dirname(filePath));
27
+ fs.writeFileSync(filePath, JSON.stringify(value, null, 2) + '\n', 'utf8');
28
+ } catch (err) {
29
+ throw new Error(`Falha ao escrever ${filePath}: ${err instanceof Error ? err.message : String(err)}`);
30
+ }
31
+ }
32
+
33
+ function sha256Text(text) {
34
+ return crypto.createHash('sha256').update(String(text || ''), 'utf8').digest('hex');
35
+ }
36
+
37
+ function sha256File(filePath) {
38
+ try {
39
+ return sha256Text(fs.readFileSync(filePath, 'utf8'));
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+
45
+ function toIso(value) {
46
+ if (!value) return null;
47
+ const d = value instanceof Date ? value : new Date(value);
48
+ return Number.isNaN(d.getTime()) ? null : d.toISOString();
49
+ }
50
+
51
+ function hoursSince(iso) {
52
+ if (!iso) return null;
53
+ const ms = Date.parse(String(iso));
54
+ if (Number.isNaN(ms)) return null;
55
+ return Math.floor((Date.now() - ms) / (1000 * 60 * 60));
56
+ }
57
+
58
+ function summarizeText(text, maxChars = 640, maxLines = 12) {
59
+ const normalized = String(text || '').replace(/\r\n/g, '\n').trim();
60
+ if (!normalized) return '';
61
+ const lines = normalized
62
+ .split('\n')
63
+ .map((line) => line.trim())
64
+ .filter(Boolean)
65
+ .slice(0, maxLines);
66
+ const joined = lines.join(' ');
67
+ return joined.length > maxChars ? `${joined.slice(0, maxChars - 1)}…` : joined;
68
+ }
69
+
70
+ const INTENT_SECTION_KEYWORDS = {
71
+ execution_input: ['onda', 'tarefa', 'hipótese', 'hipotese', 'wave', 'task', 'bloqueio', 'checkpoint', 'execu'],
72
+ verification: ['gap', 'critério', 'criterio', 'finding', 'auditoria', 'resultado', 'evidência', 'evidencia', 'falha', 'lacuna'],
73
+ planning_input: ['objetivo', 'autoavaliação', 'autoavaliacao', 'confiança', 'confianca', 'plano', 'requisito', 'risco', 'hipótese', 'hipotese'],
74
+ critical_check: ['bloqueio', 'falha', 'gap', 'erro', 'crítico', 'critico', 'p0', 'p1'],
75
+ status_read: ['fase', 'estado', 'próximo', 'proximo', 'status', 'resumo', 'sessão', 'sessao', 'snapshot'],
76
+ };
77
+
78
+ const DEFAULT_PRESERVE_MARKERS = [
79
+ 'P0', 'P1', 'bloqueado', 'BLOQUEADO', 'FALHA', 'crítico', 'CRÍTICO',
80
+ 'gaps', 'GAPS', 'bloqueante', 'BLOQUEANTE', 'crítica', 'CRÍTICA',
81
+ ];
82
+
83
+ /**
84
+ * Extração semântica orientada a intenção — preserva marcadores críticos independentemente
85
+ * de posição, prioriza seções relevantes ao workflow e preenche o orçamento restante
86
+ * com conteúdo adicional.
87
+ *
88
+ * @param {string} text
89
+ * @param {{ intent?: string, maxChars?: number, preserveMarkers?: string[] }} [options]
90
+ * @returns {string}
91
+ */
92
+ function extractSemanticFragment(text, options = {}) {
93
+ const intent = String(options.intent || 'status_read');
94
+ const maxChars = Math.max(200, Number(options.maxChars) || 1200);
95
+ const preserveMarkers = Array.isArray(options.preserveMarkers)
96
+ ? options.preserveMarkers
97
+ : DEFAULT_PRESERVE_MARKERS;
98
+
99
+ const normalized = String(text || '').replace(/\r\n/g, '\n').trim();
100
+ if (!normalized) return '';
101
+
102
+ // Sem headings — fallback para summarizeText com limite maior
103
+ if (!normalized.includes('\n## ') && !normalized.startsWith('## ') &&
104
+ !normalized.includes('\n# ') && !normalized.startsWith('# ')) {
105
+ return summarizeText(normalized, maxChars, 20);
106
+ }
107
+
108
+ // Parsear em seções delimitadas por headings (# ou ##)
109
+ const sections = [];
110
+ let current = { heading: '', lines: [] };
111
+ for (const line of normalized.split('\n')) {
112
+ if (/^#{1,3} /.test(line)) {
113
+ sections.push(current);
114
+ current = { heading: line, lines: [] };
115
+ } else {
116
+ current.lines.push(line);
117
+ }
118
+ }
119
+ sections.push(current);
120
+
121
+ const kws = INTENT_SECTION_KEYWORDS[intent] || INTENT_SECTION_KEYWORDS.status_read;
122
+
123
+ // Pontuar cada seção: 3=crítica, 2=relevante ao intent, 1=primeira seção, 0=resto
124
+ const scored = sections.map((s, i) => {
125
+ const h = s.heading.toLowerCase();
126
+ const hasCritical = [s.heading, ...s.lines].some((l) =>
127
+ preserveMarkers.some((m) => l.includes(m))
128
+ );
129
+ const isRelevant = kws.some((k) => h.includes(k));
130
+ const relevance = hasCritical ? 3 : isRelevant ? 2 : i === 0 ? 1 : 0;
131
+ return { ...s, relevance, index: i };
132
+ });
133
+
134
+ // Ordenar: críticas primeiro, depois por intent, depois por posição original
135
+ const sorted = [...scored].sort((a, b) => {
136
+ if (b.relevance !== a.relevance) return b.relevance - a.relevance;
137
+ return a.index - b.index;
138
+ });
139
+
140
+ // Preencher orçamento gulodamente
141
+ const parts = [];
142
+ let budget = maxChars;
143
+
144
+ for (const section of sorted) {
145
+ if (budget <= 0) break;
146
+ const chunk = [section.heading, ...section.lines.filter(Boolean)]
147
+ .filter(Boolean)
148
+ .join('\n')
149
+ .trim();
150
+ if (!chunk) continue;
151
+ if (chunk.length <= budget) {
152
+ parts.push({ index: section.index, text: chunk });
153
+ budget -= chunk.length + 2;
154
+ } else if (budget > 80) {
155
+ parts.push({ index: section.index, text: `${chunk.slice(0, budget - 1)}…` });
156
+ budget = 0;
157
+ }
158
+ }
159
+
160
+ // Reordenar na sequência original do documento
161
+ parts.sort((a, b) => a.index - b.index);
162
+ const result = parts.map((p) => p.text).join('\n\n').trim();
163
+ return result.length > maxChars ? `${result.slice(0, maxChars - 1)}…` : result;
164
+ }
165
+
166
+ function sanitizeSession(activeSession) {
167
+ return String(activeSession || '')
168
+ .replace(/^sessions\//, '')
169
+ .replace(/[\\/]/g, '__')
170
+ .replace(/[^A-Za-z0-9._-]+/g, '-');
171
+ }
172
+
173
+ function contextPaths(projectRoot, activeSession) {
174
+ const base = health.oxePaths(projectRoot);
175
+ const stateText = readTextIfExists(base.state) || '';
176
+ const resolvedSession = activeSession === undefined ? health.parseActiveSession(stateText) : activeSession;
177
+ const sessionKey = sanitizeSession(resolvedSession);
178
+ const phase = health.parseStatePhase(stateText) || 'unknown';
179
+ const safePhase = String(phase).replace(/[^A-Za-z0-9._-]+/g, '-');
180
+ const root = path.join(base.oxe, 'context');
181
+ const packsDir = path.join(root, 'packs');
182
+ const summariesDir = path.join(root, 'summaries');
183
+ return {
184
+ root,
185
+ index: path.join(root, 'index.json'),
186
+ packsDir,
187
+ summariesDir,
188
+ projectSummaryJson: path.join(summariesDir, 'project.json'),
189
+ projectSummaryMd: path.join(summariesDir, 'project.md'),
190
+ sessionSummaryJson: resolvedSession ? path.join(summariesDir, `session-${sessionKey}.json`) : null,
191
+ sessionSummaryMd: resolvedSession ? path.join(summariesDir, `session-${sessionKey}.md`) : null,
192
+ phaseSummaryJson: path.join(summariesDir, `phase-${safePhase}.json`),
193
+ phaseSummaryMd: path.join(summariesDir, `phase-${safePhase}.md`),
194
+ defaultPackJson: (workflow) => path.join(packsDir, `${workflow}.json`),
195
+ defaultPackMd: (workflow) => path.join(packsDir, `${workflow}.md`),
196
+ sessionPackJson: (workflow) => resolvedSession ? path.join(packsDir, `session-${sessionKey}-${workflow}.json`) : null,
197
+ sessionPackMd: (workflow) => resolvedSession ? path.join(packsDir, `session-${sessionKey}-${workflow}.md`) : null,
198
+ activeSession: resolvedSession || null,
199
+ phase,
200
+ };
201
+ }
202
+
203
+ function inferScope(activeSession, preferSession) {
204
+ if (preferSession && activeSession) return 'session';
205
+ return 'project';
206
+ }
207
+
208
+ function resolveArtifactCandidates(projectRoot, activeSession) {
209
+ const base = health.oxePaths(projectRoot);
210
+ const scoped = health.scopedOxePaths(projectRoot, activeSession || null);
211
+ const ctx = contextPaths(projectRoot, activeSession);
212
+ const azurePaths = azure.azurePaths(projectRoot);
213
+ const preferSession = Boolean(activeSession);
214
+ const withFallback = (alias, semanticType, primaryPath, fallbackPath = null, scope = inferScope(activeSession, true)) => ({
215
+ alias,
216
+ semantic_type: semanticType,
217
+ scope,
218
+ primary: primaryPath,
219
+ fallback: fallbackPath && fallbackPath !== primaryPath ? fallbackPath : null,
220
+ });
221
+ return {
222
+ state: withFallback('state', 'state', base.state, null, 'project'),
223
+ session_manifest: withFallback('session_manifest', 'session', scoped.sessionManifest || null, null, 'session'),
224
+ sessions_index: withFallback('sessions_index', 'session_index', base.sessionsIndex, null, 'project'),
225
+ execution_state: withFallback('execution_state', 'state', scoped.executionState || null, null, 'session'),
226
+ spec: withFallback('spec', 'spec', scoped.spec, base.spec),
227
+ discuss: withFallback('discuss', 'discuss', scoped.discuss, base.discuss),
228
+ plan: withFallback('plan', 'plan', scoped.plan, base.plan),
229
+ plan_agents: withFallback('plan_agents', 'plan_agents', base.planAgents, null, 'project'),
230
+ quick: withFallback('quick', 'plan', scoped.quick, base.quick),
231
+ runtime: withFallback('runtime', 'runtime', scoped.runtime, base.runtime),
232
+ checkpoints: withFallback('checkpoints', 'checkpoints', scoped.checkpoints, base.checkpoints),
233
+ verify: withFallback('verify', 'verify', scoped.verify, base.verify),
234
+ summary: withFallback('summary', 'summary', scoped.summary, base.summary),
235
+ plan_review: withFallback('plan_review', 'review', scoped.planReview, base.planReview),
236
+ review_comments: withFallback('review_comments', 'review_comments', scoped.planReviewComments, base.planReviewComments),
237
+ active_run: withFallback('active_run', 'runtime', operational.operationalPaths(projectRoot, activeSession || null).activeRun, null, preferSession ? 'session' : 'project'),
238
+ events: withFallback('events', 'trace', operational.operationalPaths(projectRoot, activeSession || null).events, null, preferSession ? 'session' : 'project'),
239
+ capabilities_index: withFallback('capabilities_index', 'capabilities', base.capabilitiesIndex, null, 'project'),
240
+ investigations_index: withFallback('investigations_index', 'investigations', scoped.investigationsIndex, base.investigationsIndex),
241
+ global_lessons: withFallback('global_lessons', 'memory', base.globalLessons, base.lessons, 'project'),
242
+ codebase_overview: withFallback('codebase_overview', 'codebase', path.join(base.codebase, 'OVERVIEW.md'), null, 'project'),
243
+ codebase_stack: withFallback('codebase_stack', 'codebase', path.join(base.codebase, 'STACK.md'), null, 'project'),
244
+ codebase_structure: withFallback('codebase_structure', 'codebase', path.join(base.codebase, 'STRUCTURE.md'), null, 'project'),
245
+ codebase_testing: withFallback('codebase_testing', 'codebase', path.join(base.codebase, 'TESTING.md'), null, 'project'),
246
+ codebase_integrations: withFallback('codebase_integrations', 'codebase', path.join(base.codebase, 'INTEGRATIONS.md'), null, 'project'),
247
+ codebase_concerns: withFallback('codebase_concerns', 'codebase', path.join(base.codebase, 'CONCERNS.md'), null, 'project'),
248
+ azure_inventory: withFallback('azure_inventory', 'provider', azurePaths.inventoryMd, null, 'project'),
249
+ azure_servicebus: withFallback('azure_servicebus', 'provider', azurePaths.serviceBusMd, null, 'project'),
250
+ azure_eventgrid: withFallback('azure_eventgrid', 'provider', azurePaths.eventGridMd, null, 'project'),
251
+ azure_sql: withFallback('azure_sql', 'provider', azurePaths.sqlMd, null, 'project'),
252
+ copilot_manifest: withFallback('copilot_manifest', 'install_manifest', base.copilotManifest, null, 'project'),
253
+ runtime_semantics_manifest: withFallback('runtime_semantics_manifest', 'install_manifest', path.join(base.installDir, 'runtime-semantics.json'), null, 'project'),
254
+ project_summary: withFallback('project_summary', 'summary', ctx.projectSummaryJson, null, 'project'),
255
+ session_summary: withFallback('session_summary', 'summary', ctx.sessionSummaryJson, null, 'session'),
256
+ phase_summary: withFallback('phase_summary', 'summary', ctx.phaseSummaryJson, null, 'project'),
257
+ context_pack_dashboard: withFallback('context_pack_dashboard', 'context_pack', ctx.defaultPackJson('dashboard'), null, 'project'),
258
+ calibration: withFallback('calibration', 'calibration', path.join(base.oxe, 'calibration.json'), null, 'project'),
259
+ lessons_metrics: withFallback('lessons_metrics', 'metrics', path.join(base.oxe, 'lessons-metrics.json'), null, 'project'),
260
+ };
261
+ }
262
+
263
+ function buildArtifactRecord(candidate) {
264
+ const primaryExists = Boolean(candidate.primary && fs.existsSync(candidate.primary));
265
+ const fallbackExists = Boolean(candidate.fallback && fs.existsSync(candidate.fallback));
266
+ const chosenPath = primaryExists ? candidate.primary : fallbackExists ? candidate.fallback : candidate.primary || candidate.fallback || null;
267
+ const chosenExists = Boolean(chosenPath && fs.existsSync(chosenPath));
268
+ const chosenText = chosenExists ? readTextIfExists(chosenPath) || '' : '';
269
+ const primaryHash = primaryExists ? sha256File(candidate.primary) : null;
270
+ const fallbackHash = fallbackExists ? sha256File(candidate.fallback) : null;
271
+ const stat = chosenExists ? fs.statSync(chosenPath) : null;
272
+ return {
273
+ alias: candidate.alias,
274
+ semantic_type: candidate.semantic_type,
275
+ scope: candidate.scope,
276
+ primary_path: candidate.primary || null,
277
+ primary_exists: primaryExists,
278
+ fallback_path: candidate.fallback || null,
279
+ fallback_exists: fallbackExists,
280
+ path: chosenPath,
281
+ exists: chosenExists,
282
+ using_fallback: !primaryExists && fallbackExists,
283
+ hash: chosenExists ? sha256Text(chosenText) : null,
284
+ primary_hash: primaryHash,
285
+ fallback_hash: fallbackHash,
286
+ updated_at: stat ? stat.mtime.toISOString() : null,
287
+ age_hours: stat ? hoursSince(stat.mtime.toISOString()) : null,
288
+ size_bytes: stat ? stat.size : 0,
289
+ summary: chosenExists ? summarizeText(chosenText) : '',
290
+ conflict: Boolean(primaryExists && fallbackExists && primaryHash && fallbackHash && primaryHash !== fallbackHash),
291
+ };
292
+ }
293
+
294
+ function buildProjectSummary(projectRoot, activeSession, options = {}) {
295
+ const base = health.oxePaths(projectRoot);
296
+ const codebaseDir = base.codebase;
297
+ const overview = summarizeText(readTextIfExists(path.join(codebaseDir, 'OVERVIEW.md')) || '');
298
+ const stack = summarizeText(readTextIfExists(path.join(codebaseDir, 'STACK.md')) || '');
299
+ const concerns = summarizeText(readTextIfExists(path.join(codebaseDir, 'CONCERNS.md')) || '');
300
+ const lessons = summarizeText(readTextIfExists(base.globalLessons) || '');
301
+ const stateText = readTextIfExists(base.state) || '';
302
+ const payload = {
303
+ summary_type: 'project',
304
+ generated_at: new Date().toISOString(),
305
+ project_root: path.resolve(projectRoot),
306
+ phase: health.parseStatePhase(stateText),
307
+ active_session: activeSession || health.parseActiveSession(stateText),
308
+ overview,
309
+ stack,
310
+ concerns,
311
+ lessons,
312
+ };
313
+ const md =
314
+ '# OXE Context Summary — Project\n\n' +
315
+ `- **Gerado em:** ${payload.generated_at}\n` +
316
+ `- **Fase:** ${payload.phase || '—'}\n` +
317
+ `- **Sessão ativa:** ${payload.active_session || 'modo legado'}\n\n` +
318
+ '## Overview\n\n' +
319
+ `${overview || '—'}\n\n` +
320
+ '## Stack\n\n' +
321
+ `${stack || '—'}\n\n` +
322
+ '## Concerns\n\n' +
323
+ `${concerns || '—'}\n\n` +
324
+ '## Lessons\n\n' +
325
+ `${lessons || '—'}\n`;
326
+ if (options.write !== false) {
327
+ const ctx = contextPaths(projectRoot, activeSession);
328
+ try {
329
+ writeJson(ctx.projectSummaryJson, payload);
330
+ ensureDir(path.dirname(ctx.projectSummaryMd));
331
+ fs.writeFileSync(ctx.projectSummaryMd, md, 'utf8');
332
+ } catch (err) {
333
+ throw new Error(`buildProjectSummary: ${err instanceof Error ? err.message : String(err)}`);
334
+ }
335
+ }
336
+ return { json: payload, markdown: md };
337
+ }
338
+
339
+ function buildSessionSummary(projectRoot, activeSession, options = {}) {
340
+ if (!activeSession) return null;
341
+ const scoped = health.scopedOxePaths(projectRoot, activeSession);
342
+ const stateText = readTextIfExists(health.oxePaths(projectRoot).state) || '';
343
+ const sessionText = readTextIfExists(scoped.sessionManifest) || '';
344
+ const spec = summarizeText(readTextIfExists(scoped.spec) || '');
345
+ const plan = summarizeText(readTextIfExists(scoped.plan) || '');
346
+ const runtime = summarizeText(readTextIfExists(scoped.runtime) || '');
347
+ const verify = summarizeText(readTextIfExists(scoped.verify) || '');
348
+ const payload = {
349
+ summary_type: 'session',
350
+ generated_at: new Date().toISOString(),
351
+ session: activeSession,
352
+ phase: health.parseStatePhase(stateText),
353
+ session_manifest: summarizeText(sessionText),
354
+ spec,
355
+ plan,
356
+ runtime,
357
+ verify,
358
+ };
359
+ const md =
360
+ '# OXE Context Summary — Session\n\n' +
361
+ `- **Gerado em:** ${payload.generated_at}\n` +
362
+ `- **Sessão:** ${payload.session}\n` +
363
+ `- **Fase:** ${payload.phase || '—'}\n\n` +
364
+ '## Session Manifest\n\n' +
365
+ `${payload.session_manifest || '—'}\n\n` +
366
+ '## SPEC\n\n' +
367
+ `${spec || '—'}\n\n` +
368
+ '## PLAN\n\n' +
369
+ `${plan || '—'}\n\n` +
370
+ '## Runtime\n\n' +
371
+ `${runtime || '—'}\n\n` +
372
+ '## VERIFY\n\n' +
373
+ `${verify || '—'}\n`;
374
+ if (options.write !== false) {
375
+ const ctx = contextPaths(projectRoot, activeSession);
376
+ try {
377
+ if (ctx.sessionSummaryJson) writeJson(ctx.sessionSummaryJson, payload);
378
+ if (ctx.sessionSummaryMd) {
379
+ ensureDir(path.dirname(ctx.sessionSummaryMd));
380
+ fs.writeFileSync(ctx.sessionSummaryMd, md, 'utf8');
381
+ }
382
+ } catch (err) {
383
+ throw new Error(`buildSessionSummary: ${err instanceof Error ? err.message : String(err)}`);
384
+ }
385
+ }
386
+ return { json: payload, markdown: md };
387
+ }
388
+
389
+ function buildPhaseSummary(projectRoot, activeSession, options = {}) {
390
+ const base = health.oxePaths(projectRoot);
391
+ const stateText = readTextIfExists(base.state) || '';
392
+ const payload = {
393
+ summary_type: 'phase',
394
+ generated_at: new Date().toISOString(),
395
+ phase: health.parseStatePhase(stateText),
396
+ active_session: activeSession || health.parseActiveSession(stateText),
397
+ next_step: firstNonEmpty([
398
+ summarizeText(readTextIfExists(base.state) || '', 220, 8),
399
+ ]),
400
+ runtime_status: firstMatch(stateText, /\*\*runtime_status:\*\*\s*([^\n]+)/i),
401
+ plan_review_status: health.parsePlanReviewStatus(stateText),
402
+ };
403
+ const md =
404
+ '# OXE Context Summary — Phase\n\n' +
405
+ `- **Gerado em:** ${payload.generated_at}\n` +
406
+ `- **Fase:** ${payload.phase || '—'}\n` +
407
+ `- **Sessão ativa:** ${payload.active_session || 'modo legado'}\n` +
408
+ `- **runtime_status:** ${payload.runtime_status || '—'}\n` +
409
+ `- **plan_review_status:** ${payload.plan_review_status || '—'}\n\n` +
410
+ '## Snapshot\n\n' +
411
+ `${payload.next_step || '—'}\n`;
412
+ if (options.write !== false) {
413
+ const ctx = contextPaths(projectRoot, activeSession);
414
+ try {
415
+ writeJson(ctx.phaseSummaryJson, payload);
416
+ ensureDir(path.dirname(ctx.phaseSummaryMd));
417
+ fs.writeFileSync(ctx.phaseSummaryMd, md, 'utf8');
418
+ } catch (err) {
419
+ throw new Error(`buildPhaseSummary: ${err instanceof Error ? err.message : String(err)}`);
420
+ }
421
+ }
422
+ return { json: payload, markdown: md };
423
+ }
424
+
425
+ function firstMatch(text, regex) {
426
+ const match = String(text || '').match(regex);
427
+ return match ? match[1].trim() : null;
428
+ }
429
+
430
+ function firstNonEmpty(values) {
431
+ for (const value of values || []) {
432
+ if (value && String(value).trim()) return String(value).trim();
433
+ }
434
+ return null;
435
+ }
436
+
437
+ function buildContextIndex(projectRoot, activeSession, options = {}) {
438
+ const writeOpt = { write: options.write !== false };
439
+ const summaryErrors = [];
440
+ for (const [label, fn, args] of [
441
+ ['project', buildProjectSummary, [projectRoot, activeSession, writeOpt]],
442
+ ['session', buildSessionSummary, [projectRoot, activeSession, writeOpt]],
443
+ ['phase', buildPhaseSummary, [projectRoot, activeSession, writeOpt]],
444
+ ]) {
445
+ try {
446
+ fn(...args);
447
+ } catch (err) {
448
+ summaryErrors.push(`${label}: ${err instanceof Error ? err.message : String(err)}`);
449
+ }
450
+ }
451
+ if (summaryErrors.length) {
452
+ process.stderr.write(`[oxe] WARN buildContextIndex — falha ao escrever summaries: ${summaryErrors.join('; ')}\n`);
453
+ }
454
+ const stateText = readTextIfExists(health.oxePaths(projectRoot).state) || '';
455
+ const resolvedSession = activeSession === undefined ? health.parseActiveSession(stateText) : activeSession;
456
+ const candidates = resolveArtifactCandidates(projectRoot, resolvedSession || null);
457
+ const artifacts = Object.keys(candidates)
458
+ .sort()
459
+ .map((alias) => buildArtifactRecord(candidates[alias]));
460
+ const payload = {
461
+ schema_version: 1,
462
+ generated_at: new Date().toISOString(),
463
+ project_root: path.resolve(projectRoot),
464
+ active_session: resolvedSession || null,
465
+ phase: health.parseStatePhase(stateText),
466
+ artifacts,
467
+ stats: {
468
+ total: artifacts.length,
469
+ existing: artifacts.filter((item) => item.exists).length,
470
+ missing: artifacts.filter((item) => !item.exists).length,
471
+ conflicts: artifacts.filter((item) => item.conflict).length,
472
+ },
473
+ };
474
+ if (options.write !== false) {
475
+ const ctx = contextPaths(projectRoot, resolvedSession || null);
476
+ ensureDir(ctx.root);
477
+ writeJson(ctx.index, payload);
478
+ }
479
+ return payload;
480
+ }
481
+
482
+ function computePackFreshness(pack, contract) {
483
+ const generatedAt = pack.generated_at || null;
484
+ const sourceTimes = (pack.selected_artifacts || [])
485
+ .map((artifact) => artifact.updated_at)
486
+ .filter(Boolean)
487
+ .map((value) => Date.parse(String(value)))
488
+ .filter((value) => !Number.isNaN(value));
489
+ const latestSource = sourceTimes.length ? new Date(Math.max(...sourceTimes)).toISOString() : null;
490
+ const generatedMs = generatedAt ? Date.parse(String(generatedAt)) : Number.NaN;
491
+ const latestSourceMs = latestSource ? Date.parse(latestSource) : Number.NaN;
492
+ const packAgeHours = hoursSince(generatedAt);
493
+ const maxPackAgeHours = contract && contract.freshness_policy && contract.freshness_policy.pack_max_age_hours != null
494
+ ? Number(contract.freshness_policy.pack_max_age_hours)
495
+ : 12;
496
+ const staleByAge = packAgeHours != null && maxPackAgeHours > 0 ? packAgeHours > maxPackAgeHours : false;
497
+ const staleBySource = !Number.isNaN(generatedMs) && !Number.isNaN(latestSourceMs) ? generatedMs < latestSourceMs : false;
498
+ return {
499
+ generated_at: generatedAt,
500
+ latest_source_at: latestSource,
501
+ pack_age_hours: packAgeHours,
502
+ max_pack_age_hours: maxPackAgeHours,
503
+ stale: staleByAge || staleBySource || Boolean(pack.fallback_required),
504
+ reason: staleBySource ? 'source_newer_than_pack' : staleByAge ? 'pack_age_exceeded' : pack.fallback_required ? 'fallback_required' : 'fresh',
505
+ };
506
+ }
507
+
508
+ function computeContextQuality(pack) {
509
+ const requiredMissing = (pack.gaps || []).filter((gap) => gap.severity === 'critical').length;
510
+ const optionalMissing = (pack.gaps || []).filter((gap) => gap.severity !== 'critical').length;
511
+ const conflicts = (pack.conflicts || []).length;
512
+ const fallbackCount = (pack.selected_artifacts || []).filter((artifact) => artifact.using_fallback).length;
513
+ let score = 100;
514
+ score -= requiredMissing * 25;
515
+ score -= optionalMissing * 5;
516
+ score -= conflicts * 12;
517
+ score -= fallbackCount * 6;
518
+ if ((pack.selected_artifacts || []).length === 0) score -= 40;
519
+ score = Math.max(0, Math.min(100, score));
520
+ const status = score >= 85 ? 'excellent' : score >= 70 ? 'good' : score >= 50 ? 'fragile' : 'critical';
521
+ return {
522
+ score,
523
+ status,
524
+ requiredMissing,
525
+ optionalMissing,
526
+ conflicts,
527
+ fallbackCount,
528
+ };
529
+ }
530
+
531
+ function renderPackMarkdown(pack) {
532
+ const selected = (pack.selected_artifacts || [])
533
+ .map((artifact) => `- **${artifact.alias}** (${artifact.scope}) -> ${artifact.exists ? artifact.path : 'ausente'}${artifact.using_fallback ? ' [fallback]' : ''}`)
534
+ .join('\n') || '- Nenhum artefato selecionado';
535
+ const gaps = (pack.gaps || [])
536
+ .map((gap) => `- [${gap.severity}] ${gap.alias}: ${gap.reason}`)
537
+ .join('\n') || '- Nenhuma lacuna';
538
+ const conflicts = (pack.conflicts || [])
539
+ .map((conflict) => `- ${conflict.alias}: ${conflict.reason}`)
540
+ .join('\n') || '- Nenhum conflito';
541
+ const sections = (pack.contract && pack.contract.output_sections || []).join(' · ') || '—';
542
+ return (
543
+ `# OXE Context Pack — ${pack.workflow}\n\n` +
544
+ `- **Gerado em:** ${pack.generated_at}\n` +
545
+ `- **Sessão ativa:** ${pack.active_session || 'modo legado'}\n` +
546
+ `- **Tier:** ${pack.context_tier}\n` +
547
+ `- **Semantics hash:** \`${pack.semantics_hash}\`\n` +
548
+ `- **Quality score:** ${pack.context_quality.score}\n` +
549
+ `- **Fallback required:** ${pack.fallback_required ? 'sim' : 'não'}\n\n` +
550
+ '## Read Order\n\n' +
551
+ `${(pack.read_order || []).map((alias) => `- ${alias}`).join('\n') || '- Nenhuma ordem resolvida'}\n\n` +
552
+ '## Selected Artifacts\n\n' +
553
+ `${selected}\n\n` +
554
+ '## Gaps\n\n' +
555
+ `${gaps}\n\n` +
556
+ '## Conflicts\n\n' +
557
+ `${conflicts}\n\n` +
558
+ '## Output Contract\n\n' +
559
+ `${sections}\n`
560
+ );
561
+ }
562
+
563
+ /**
564
+ * Extrai o vetor de confiança de um PLAN.md (bloco <confidence_vector>).
565
+ * @param {string} planText
566
+ * @returns {{ cycle: string|null, generated_at: string|null, dimensions: Array<{ name: string, score: number, weight: number, note: string }>, global: { score: number, gate: string } } | null}
567
+ */
568
+ function parseConfidenceVector(planText) {
569
+ const text = String(planText || '');
570
+ const blockMatch = text.match(/<confidence_vector\s+([^>]*)>([\s\S]*?)<\/confidence_vector>/i);
571
+ if (!blockMatch) return null;
572
+
573
+ const attrs = blockMatch[1];
574
+ const body = blockMatch[2];
575
+
576
+ const cycle = (attrs.match(/\bcycle=["']([^"']+)["']/) || [])[1] || null;
577
+ const generated_at = (attrs.match(/\bgenerated_at=["']([^"']+)["']/) || [])[1] || null;
578
+
579
+ // Extrair dimensões: <dim name="..." score="..." weight="..." note="..." />
580
+ const dimensions = [];
581
+ const dimPattern = /<dim\s+([^/]*)\s*\/>/gi;
582
+ let m;
583
+ while ((m = dimPattern.exec(body)) !== null) {
584
+ const dAttrs = m[1];
585
+ const name = (dAttrs.match(/\bname=["']([^"']+)["']/) || [])[1] || '';
586
+ const score = parseFloat((dAttrs.match(/\bscore=["']([^"']+)["']/) || [])[1] || '0');
587
+ const weight = parseFloat((dAttrs.match(/\bweight=["']([^"']+)["']/) || [])[1] || '0');
588
+ const note = (dAttrs.match(/\bnote=["']([^"']+)["']/) || [])[1] || '';
589
+ if (name) dimensions.push({ name, score: isNaN(score) ? 0 : score, weight: isNaN(weight) ? 0 : weight, note });
590
+ }
591
+
592
+ // Extrair global: <global score="..." gate="..." />
593
+ const globalMatch = body.match(/<global\s+([^/]*)\s*\/>/i);
594
+ const globalAttrs = globalMatch ? globalMatch[1] : '';
595
+ const globalScore = parseFloat((globalAttrs.match(/\bscore=["']([^"']+)["']/) || [])[1] || '0');
596
+ const gate = (globalAttrs.match(/\bgate=["']([^"']+)["']/) || [])[1] || 'proceed_with_risk';
597
+
598
+ // Se não há global explícito, calcular como média ponderada
599
+ let computedScore = globalScore;
600
+ if (!globalMatch && dimensions.length > 0) {
601
+ const totalWeight = dimensions.reduce((s, d) => s + d.weight, 0);
602
+ computedScore = totalWeight > 0
603
+ ? dimensions.reduce((s, d) => s + d.score * d.weight, 0) / totalWeight
604
+ : 0;
605
+ }
606
+
607
+ return {
608
+ cycle,
609
+ generated_at,
610
+ dimensions,
611
+ global: { score: isNaN(computedScore) ? 0 : Math.round(computedScore * 100) / 100, gate },
612
+ };
613
+ }
614
+
615
+ /**
616
+ * Extrai hipóteses críticas de um PLAN.md.
617
+ * Suporta tags XML (<hypothesis ...>) e fallback para tabela Markdown.
618
+ * @param {string} planText
619
+ * @returns {Array<{ id: string, condition: string, validation: string, on_failure: string, checkpoint: string|null, status: string }>}
620
+ */
621
+ function parseHypotheses(planText) {
622
+ const text = String(planText || '');
623
+ const results = [];
624
+
625
+ // Formato 1: tags XML
626
+ const xmlPattern = /<hypothesis\s+([^>]*)>([\s\S]*?)<\/hypothesis>/gi;
627
+ let m;
628
+ while ((m = xmlPattern.exec(text)) !== null) {
629
+ const attrs = m[1];
630
+ const body = m[2];
631
+ const id = (attrs.match(/\bid=["']([^"']+)["']/) || [])[1] || '';
632
+ const checkpoint = (attrs.match(/\bcheckpoint=["']([^"']+)["']/) || [])[1] || null;
633
+ const status = (attrs.match(/\bstatus=["']([^"']+)["']/) || [])[1] || 'pending';
634
+ const condition = (body.match(/<condition>([\s\S]*?)<\/condition>/) || [])[1]?.trim() || '';
635
+ const validation = (body.match(/<validation>([\s\S]*?)<\/validation>/) || [])[1]?.trim() || '';
636
+ const on_failure = (body.match(/<on_failure>([\s\S]*?)<\/on_failure>/) || [])[1]?.trim() || '';
637
+ if (id) results.push({ id, condition, validation, on_failure, checkpoint, status });
638
+ }
639
+ if (results.length > 0) return results;
640
+
641
+ // Formato 2: tabela Markdown (fallback)
642
+ // Encontrar a seção e extrair linhas até o próximo heading
643
+ const sectionIdx = text.search(/##\s*Hip.teses\s*Cr.ticas/im);
644
+ if (sectionIdx !== -1) {
645
+ const afterSection = text.slice(sectionIdx);
646
+ // Parar no próximo heading ## ou # (excluindo o próprio)
647
+ const nextHeadingMatch = afterSection.slice(3).match(/\n#{1,3} /);
648
+ const sectionText = nextHeadingMatch
649
+ ? afterSection.slice(0, nextHeadingMatch.index + 3 + 1)
650
+ : afterSection;
651
+ const rows = sectionText.split('\n').filter((l) => l.trimStart().startsWith('|'));
652
+ for (const row of rows) {
653
+ const cells = row.split('|').map((c) => c.trim()).filter(Boolean);
654
+ if (cells.length >= 2 && /^H\d+$/i.test(cells[0])) {
655
+ results.push({
656
+ id: cells[0],
657
+ condition: cells[1] || '',
658
+ validation: cells[2] || '',
659
+ on_failure: cells[3] || '',
660
+ checkpoint: cells[4] || null,
661
+ status: cells[5] || 'pending',
662
+ });
663
+ }
664
+ }
665
+ }
666
+ return results;
667
+ }
668
+
669
+ function buildContextPack(projectRoot, input = {}) {
670
+ const workflow = String(input.workflow || '').trim();
671
+ if (!workflow) {
672
+ throw new Error('workflow é obrigatório para buildContextPack');
673
+ }
674
+ const contract = runtimeSemantics.getWorkflowContract(workflow);
675
+ if (!contract) {
676
+ throw new Error(`Workflow sem contrato canónico: ${workflow}`);
677
+ }
678
+ const stateText = readTextIfExists(health.oxePaths(projectRoot).state) || '';
679
+ const activeSession = input.activeSession === undefined ? health.parseActiveSession(stateText) : input.activeSession;
680
+ const tier = ['minimal', 'standard', 'full'].includes(String(input.tier || 'standard'))
681
+ ? String(input.tier || 'standard')
682
+ : 'standard';
683
+ const mode = String(input.mode || 'standard');
684
+ const ctx = contextPaths(projectRoot, activeSession || null);
685
+ const index = buildContextIndex(projectRoot, activeSession || null, { write: input.write !== false });
686
+ const byAlias = new Map((index.artifacts || []).map((artifact) => [artifact.alias, artifact]));
687
+
688
+ // Modo auditor: usa auditor_artifacts do contrato e exclui auditor_excluded
689
+ let selectedAliases;
690
+ if (mode === 'auditor' && contract.auditor_artifacts && contract.auditor_artifacts.length > 0) {
691
+ const excluded = new Set(contract.auditor_excluded || []);
692
+ selectedAliases = Array.from(
693
+ new Set(contract.auditor_artifacts.filter((a) => !excluded.has(a)))
694
+ );
695
+ } else {
696
+ selectedAliases = Array.from(
697
+ new Set(['state', ...(contract.context_tiers[tier] || contract.context_tiers.standard || [])])
698
+ );
699
+ }
700
+ const intent = String(contract.extraction_intent || 'status_read');
701
+ const selectedArtifacts = selectedAliases.map((alias) => {
702
+ const artifact = byAlias.get(alias);
703
+ if (artifact) {
704
+ const rawText = artifact.exists ? readTextIfExists(artifact.path) || '' : '';
705
+ const semanticSummary = rawText
706
+ ? extractSemanticFragment(rawText, { intent })
707
+ : '';
708
+ return {
709
+ ...artifact,
710
+ required: contract.required_artifacts.includes(alias),
711
+ selected_because: contract.required_artifacts.includes(alias) ? 'required_artifact' : 'context_tier',
712
+ semantic_summary: semanticSummary,
713
+ };
714
+ }
715
+ return {
716
+ alias,
717
+ path: null,
718
+ exists: false,
719
+ scope: 'unknown',
720
+ semantic_type: 'unknown',
721
+ using_fallback: false,
722
+ required: contract.required_artifacts.includes(alias),
723
+ selected_because: contract.required_artifacts.includes(alias) ? 'required_artifact' : 'context_tier',
724
+ updated_at: null,
725
+ age_hours: null,
726
+ hash: null,
727
+ summary: '',
728
+ semantic_summary: '',
729
+ };
730
+ });
731
+ const gaps = [];
732
+ for (const artifact of selectedArtifacts) {
733
+ if (!artifact.exists) {
734
+ gaps.push({
735
+ alias: artifact.alias,
736
+ severity: artifact.required ? 'critical' : 'warning',
737
+ reason: artifact.required ? 'required_artifact_missing' : 'selected_artifact_missing',
738
+ });
739
+ }
740
+ }
741
+ const conflicts = selectedArtifacts
742
+ .filter((artifact) => artifact.conflict)
743
+ .map((artifact) => ({
744
+ alias: artifact.alias,
745
+ reason: 'session_and_root_artifacts_diverge',
746
+ primary_path: artifact.primary_path,
747
+ fallback_path: artifact.fallback_path,
748
+ }));
749
+ const pack = {
750
+ schema_version: 1,
751
+ workflow,
752
+ mode,
753
+ active_session: activeSession || null,
754
+ context_tier: tier,
755
+ generated_at: new Date().toISOString(),
756
+ semantics_hash: runtimeSemantics.computeSemanticsHash(workflow),
757
+ contract,
758
+ read_order: selectedArtifacts.filter((artifact) => artifact.exists).map((artifact) => artifact.alias),
759
+ selected_artifacts: selectedArtifacts,
760
+ gaps,
761
+ conflicts,
762
+ fallback_required: gaps.some((gap) => gap.severity === 'critical') || selectedArtifacts.some((artifact) => artifact.using_fallback),
763
+ summaries: {
764
+ project: ctx.projectSummaryJson,
765
+ session: ctx.sessionSummaryJson,
766
+ phase: ctx.phaseSummaryJson,
767
+ },
768
+ };
769
+ // Extrair hipóteses críticas do PLAN.md se disponível no pack
770
+ const planArtifact = selectedArtifacts.find((a) => a.alias === 'plan' && a.exists);
771
+ const hypotheses = planArtifact ? parseHypotheses(readTextIfExists(planArtifact.path) || '') : [];
772
+ pack.context_quality = computeContextQuality(pack);
773
+ pack.freshness = computePackFreshness(pack, contract);
774
+ pack.hypotheses = hypotheses;
775
+ pack.markdown = renderPackMarkdown(pack);
776
+ if (input.write !== false) {
777
+ try {
778
+ ensureDir(ctx.packsDir);
779
+ const defaultJson = ctx.defaultPackJson(workflow);
780
+ const defaultMd = ctx.defaultPackMd(workflow);
781
+ writeJson(defaultJson, pack);
782
+ fs.writeFileSync(defaultMd, pack.markdown, 'utf8');
783
+ if (ctx.sessionPackJson(workflow)) {
784
+ writeJson(ctx.sessionPackJson(workflow), pack);
785
+ }
786
+ if (ctx.sessionPackMd(workflow)) {
787
+ fs.writeFileSync(ctx.sessionPackMd(workflow), pack.markdown, 'utf8');
788
+ }
789
+ } catch (err) {
790
+ throw new Error(`buildContextPack (${workflow}): falha ao persistir pack — ${err instanceof Error ? err.message : String(err)}`);
791
+ }
792
+ }
793
+ return pack;
794
+ }
795
+
796
+ function resolvePackFile(projectRoot, workflow, activeSession) {
797
+ const ctx = contextPaths(projectRoot, activeSession);
798
+ const candidates = [ctx.sessionPackJson(workflow), ctx.defaultPackJson(workflow)].filter(Boolean);
799
+ for (const filePath of candidates) {
800
+ if (fs.existsSync(filePath)) return filePath;
801
+ }
802
+ return ctx.defaultPackJson(workflow);
803
+ }
804
+
805
+ function inspectContextPack(projectRoot, input = {}) {
806
+ const workflow = String(input.workflow || '').trim();
807
+ if (!workflow) {
808
+ throw new Error('workflow é obrigatório para inspectContextPack');
809
+ }
810
+ const stateText = readTextIfExists(health.oxePaths(projectRoot).state) || '';
811
+ const activeSession = input.activeSession === undefined ? health.parseActiveSession(stateText) : input.activeSession;
812
+ const filePath = resolvePackFile(projectRoot, workflow, activeSession || null);
813
+ let pack = null;
814
+ if (fs.existsSync(filePath)) {
815
+ try {
816
+ pack = JSON.parse(fs.readFileSync(filePath, 'utf8'));
817
+ } catch {
818
+ pack = null;
819
+ }
820
+ }
821
+ if (!pack) {
822
+ pack = buildContextPack(projectRoot, {
823
+ workflow,
824
+ tier: input.tier || 'standard',
825
+ activeSession: activeSession || null,
826
+ write: false,
827
+ });
828
+ } else {
829
+ const contract = runtimeSemantics.getWorkflowContract(workflow);
830
+ pack.contract = contract;
831
+ pack.freshness = computePackFreshness(pack, contract);
832
+ pack.context_quality = computeContextQuality(pack);
833
+ }
834
+ pack.path = filePath;
835
+ return pack;
836
+ }
837
+
838
+ function buildAllContextPacks(projectRoot, input = {}) {
839
+ const workflows = input.workflow
840
+ ? [String(input.workflow)]
841
+ : runtimeSemantics.getAllWorkflowContracts().map((contract) => contract.workflow_slug);
842
+ return workflows.map((workflow) => buildContextPack(projectRoot, {
843
+ workflow,
844
+ tier: input.tier || 'standard',
845
+ activeSession: input.activeSession,
846
+ write: input.write !== false,
847
+ }));
848
+ }
849
+
850
+ module.exports = {
851
+ buildAllContextPacks,
852
+ buildContextIndex,
853
+ buildContextPack,
854
+ buildPhaseSummary,
855
+ buildProjectSummary,
856
+ buildSessionSummary,
857
+ computeContextQuality,
858
+ computePackFreshness,
859
+ contextPaths,
860
+ extractSemanticFragment,
861
+ inspectContextPack,
862
+ parseConfidenceVector,
863
+ parseHypotheses,
864
+ resolveArtifactCandidates,
865
+ resolvePackFile,
866
+ summarizeText,
867
+ };