oxe-cc 1.5.0 → 1.6.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 (50) hide show
  1. package/.cursor/commands/oxe-dashboard.md +2 -2
  2. package/.cursor/commands/oxe-execute.md +2 -2
  3. package/.cursor/commands/oxe-plan.md +2 -2
  4. package/.github/prompts/oxe-dashboard.prompt.md +2 -2
  5. package/.github/prompts/oxe-execute.prompt.md +2 -2
  6. package/.github/prompts/oxe-plan.prompt.md +2 -2
  7. package/AGENTS.md +1 -1
  8. package/CHANGELOG.md +52 -0
  9. package/README.md +17 -15
  10. package/bin/lib/oxe-context-engine.cjs +9 -4
  11. package/bin/lib/oxe-dashboard.cjs +140 -58
  12. package/bin/lib/oxe-project-health.cjs +486 -151
  13. package/bin/lib/oxe-rationality.cjs +385 -0
  14. package/bin/lib/oxe-release.cjs +76 -4
  15. package/bin/oxe-cc.js +113 -58
  16. package/commands/oxe/dashboard.md +2 -2
  17. package/commands/oxe/execute.md +2 -2
  18. package/commands/oxe/plan.md +2 -2
  19. package/docs/RELEASE-READINESS.md +8 -0
  20. package/docs/RUNTIME-SMOKE-MATRIX.md +9 -2
  21. package/lib/sdk/index.cjs +20 -11
  22. package/lib/sdk/index.d.ts +99 -34
  23. package/oxe/templates/CONFIG.md +3 -3
  24. package/oxe/templates/EXECUTION-RUNTIME.template.md +1 -1
  25. package/oxe/templates/FIXTURE-PACK.template.json +34 -0
  26. package/oxe/templates/FIXTURE-PACK.template.md +21 -0
  27. package/oxe/templates/IMPLEMENTATION-PACK.template.json +52 -0
  28. package/oxe/templates/IMPLEMENTATION-PACK.template.md +36 -0
  29. package/oxe/templates/INVESTIGATION.template.md +38 -38
  30. package/oxe/templates/PLAN.template.md +23 -14
  31. package/oxe/templates/REFERENCE-ANCHORS.template.md +24 -0
  32. package/oxe/templates/RESEARCH.template.md +11 -11
  33. package/oxe/templates/SPEC.template.md +6 -6
  34. package/oxe/templates/SUMMARY.template.md +20 -20
  35. package/oxe/templates/config.template.json +1 -1
  36. package/oxe/workflows/execute.md +18 -2
  37. package/oxe/workflows/milestone.md +12 -12
  38. package/oxe/workflows/next.md +1 -1
  39. package/oxe/workflows/plan.md +115 -57
  40. package/oxe/workflows/references/adaptive-discovery.md +27 -27
  41. package/oxe/workflows/references/flow-robustness-contract.md +80 -80
  42. package/oxe/workflows/references/session-path-resolution.md +71 -71
  43. package/oxe/workflows/references/workflow-runtime-contracts.json +36 -4
  44. package/oxe/workflows/verify.md +4 -4
  45. package/oxe/workflows/workstream.md +16 -16
  46. package/package.json +1 -1
  47. package/packages/runtime/package.json +1 -1
  48. package/vscode-extension/oxe-agents-1.5.1.vsix +0 -0
  49. package/vscode-extension/oxe-agents-1.6.0.vsix +0 -0
  50. package/vscode-extension/package.json +1 -1
@@ -0,0 +1,385 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+
5
+ function readTextIfExists(filePath) {
6
+ try {
7
+ return filePath && fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : null;
8
+ } catch {
9
+ return null;
10
+ }
11
+ }
12
+
13
+ function readJsonIfExists(filePath) {
14
+ const text = readTextIfExists(filePath);
15
+ if (!text) return { ok: false, data: null, error: null };
16
+ try {
17
+ return { ok: true, data: JSON.parse(text), error: null };
18
+ } catch (error) {
19
+ return {
20
+ ok: false,
21
+ data: null,
22
+ error: error instanceof Error ? error.message : String(error),
23
+ };
24
+ }
25
+ }
26
+
27
+ function parseAttrs(fragment) {
28
+ /** @type {Record<string, string>} */
29
+ const attrs = {};
30
+ const raw = String(fragment || '');
31
+ for (const match of raw.matchAll(/([A-Za-z0-9_:-]+)="([^"]*)"/g)) {
32
+ attrs[match[1]] = match[2];
33
+ }
34
+ return attrs;
35
+ }
36
+
37
+ function normalizeTaskMode(value) {
38
+ const mode = String(value || '').trim().toLowerCase();
39
+ if (!mode) return 'mutating';
40
+ return mode;
41
+ }
42
+
43
+ function isMutatingMode(mode) {
44
+ return !new Set(['docs_only', 'not_applicable', 'external', 'docs-only']).has(normalizeTaskMode(mode));
45
+ }
46
+
47
+ function isTaskRiskKeyword(task) {
48
+ const text = `${task.title || ''} ${task.body || ''}`.toLowerCase();
49
+ return /(parser|parse|layout|integra|integration|contrato|contract|migra|migration|fila|queue|service bus|event grid|builder|payload|transform|cnab|listener|schema|ddl)/.test(text);
50
+ }
51
+
52
+ /**
53
+ * @param {string | null} planPath
54
+ * @returns {Array<{
55
+ * id: string,
56
+ * title: string,
57
+ * files: string[],
58
+ * complexity: string | null,
59
+ * body: string,
60
+ * }>}
61
+ */
62
+ function parsePlanTasks(planPath) {
63
+ const raw = readTextIfExists(planPath);
64
+ if (!raw) return [];
65
+ const parts = raw.split(/^###\s+(T\d+)\s*[—-]?\s*/m);
66
+ if (parts.length < 3) return [];
67
+ /** @type {ReturnType<typeof parsePlanTasks>} */
68
+ const tasks = [];
69
+ for (let i = 1; i < parts.length; i += 2) {
70
+ const id = String(parts[i] || '').trim();
71
+ const rest = String(parts[i + 1] || '').split(/^###\s+T\d+/m)[0];
72
+ const title = (((rest.match(/^([^\n]+)/) || [null, ''])[1]) || '').trim();
73
+ const filesLine = (((rest.match(/\*\*Arquivos\s+prov[aá]veis:\*\*\s*([^\n]+)/i) || [null, ''])[1]) || '').trim();
74
+ const backtickPaths = Array.from(filesLine.matchAll(/`([^`]+)`/g)).map((match) => match[1].trim()).filter(Boolean);
75
+ const files = backtickPaths.length
76
+ ? backtickPaths
77
+ : filesLine.split(',').map((entry) => entry.trim()).filter(Boolean);
78
+ const complexity = (((rest.match(/\*\*Complexidade:\*\*\s*([A-Z]+)/i) || [null, ''])[1]) || '').trim() || null;
79
+ tasks.push({ id, title, files, complexity, body: rest });
80
+ }
81
+ return tasks;
82
+ }
83
+
84
+ /**
85
+ * @param {string | null} planAgentsPath
86
+ * @returns {string[]}
87
+ */
88
+ function readExternalRefs(planAgentsPath) {
89
+ const parsed = readJsonIfExists(planAgentsPath);
90
+ if (!parsed.ok || !parsed.data || typeof parsed.data !== 'object') return [];
91
+ const agents = Array.isArray(parsed.data.agents) ? parsed.data.agents : [];
92
+ return agents
93
+ .flatMap((agent) => Array.isArray(agent && agent.inputs) ? agent.inputs : [])
94
+ .map((input) => String(input || '').trim())
95
+ .filter((input) => /^external-ref:/i.test(input));
96
+ }
97
+
98
+ function summarizeImplementationPack(filePath, planTasks) {
99
+ const parsed = readJsonIfExists(filePath);
100
+ /** @type {string[]} */
101
+ const criticalGaps = [];
102
+ if (!parsed.ok) {
103
+ return {
104
+ path: filePath,
105
+ exists: Boolean(readTextIfExists(filePath)),
106
+ parseError: parsed.error,
107
+ ready: false,
108
+ tasks: [],
109
+ taskCount: 0,
110
+ mutatingTasks: 0,
111
+ criticalGaps: [`IMPLEMENTATION-PACK.json inválido ou ausente${parsed.error ? `: ${parsed.error}` : ''}`],
112
+ };
113
+ }
114
+ const data = parsed.data && typeof parsed.data === 'object' ? parsed.data : {};
115
+ const tasks = Array.isArray(data.tasks) ? data.tasks : [];
116
+ const byId = new Map(tasks.map((task) => [String(task && task.id || task && task.task_id || '').trim(), task]).filter((entry) => entry[0]));
117
+ const allowedModes = new Set(['mutating', 'docs_only', 'not_applicable', 'external', 'docs-only']);
118
+ for (const planTask of planTasks) {
119
+ if (!byId.has(planTask.id)) {
120
+ criticalGaps.push(`IMPLEMENTATION-PACK.json sem contrato para ${planTask.id}`);
121
+ }
122
+ }
123
+ let mutatingTasks = 0;
124
+ for (const task of tasks) {
125
+ const id = String(task && task.id || task && task.task_id || '').trim();
126
+ const mode = normalizeTaskMode(task && task.mode);
127
+ const exactPaths = Array.isArray(task && task.exact_paths) ? task.exact_paths.map((entry) => String(entry || '').trim()).filter(Boolean) : [];
128
+ const symbols = Array.isArray(task && task.symbols) ? task.symbols : [];
129
+ const contracts = Array.isArray(task && task.contracts) ? task.contracts : [];
130
+ const expectedChecks = Array.isArray(task && task.expected_checks) ? task.expected_checks : [];
131
+ const gaps = Array.isArray(task && task.critical_gaps) ? task.critical_gaps : [];
132
+ if (!id) {
133
+ criticalGaps.push('IMPLEMENTATION-PACK.json com task sem id');
134
+ continue;
135
+ }
136
+ if (!allowedModes.has(mode)) {
137
+ criticalGaps.push(`IMPLEMENTATION-PACK.json tarefa ${id} com mode inválido: ${mode}`);
138
+ }
139
+ if (task && task.ready === false) {
140
+ criticalGaps.push(`IMPLEMENTATION-PACK.json tarefa ${id} marcada como not ready`);
141
+ }
142
+ if (gaps.length) {
143
+ criticalGaps.push(`IMPLEMENTATION-PACK.json tarefa ${id} com critical_gaps abertos`);
144
+ }
145
+ if (isMutatingMode(mode)) {
146
+ mutatingTasks += 1;
147
+ if (!exactPaths.length) {
148
+ criticalGaps.push(`IMPLEMENTATION-PACK.json tarefa ${id} sem exact_paths`);
149
+ }
150
+ if (exactPaths.some((entry) => entry.includes('...'))) {
151
+ criticalGaps.push(`IMPLEMENTATION-PACK.json tarefa ${id} com exact_paths ambíguos (...)`);
152
+ }
153
+ if (String(task && task.write_set || '').trim().toLowerCase() !== 'closed') {
154
+ criticalGaps.push(`IMPLEMENTATION-PACK.json tarefa ${id} com write_set aberto`);
155
+ }
156
+ if (!symbols.length) {
157
+ criticalGaps.push(`IMPLEMENTATION-PACK.json tarefa ${id} sem symbols`);
158
+ }
159
+ if (!contracts.length) {
160
+ criticalGaps.push(`IMPLEMENTATION-PACK.json tarefa ${id} sem contracts`);
161
+ }
162
+ if (!expectedChecks.length) {
163
+ criticalGaps.push(`IMPLEMENTATION-PACK.json tarefa ${id} sem expected_checks`);
164
+ }
165
+ }
166
+ }
167
+ if (data.ready === false) {
168
+ criticalGaps.push('IMPLEMENTATION-PACK.json com ready=false');
169
+ }
170
+ if (Array.isArray(data.critical_gaps) && data.critical_gaps.length) {
171
+ criticalGaps.push(...data.critical_gaps.map((gap) => `IMPLEMENTATION-PACK.json: ${String(gap)}`));
172
+ }
173
+ return {
174
+ path: filePath,
175
+ exists: true,
176
+ parseError: null,
177
+ ready: Boolean(data.ready) && criticalGaps.length === 0,
178
+ tasks,
179
+ taskCount: tasks.length,
180
+ mutatingTasks,
181
+ criticalGaps: Array.from(new Set(criticalGaps)),
182
+ };
183
+ }
184
+
185
+ function summarizeReferenceAnchors(filePath, externalRefs) {
186
+ const raw = readTextIfExists(filePath);
187
+ /** @type {string[]} */
188
+ const criticalGaps = [];
189
+ if (!raw) {
190
+ return {
191
+ path: filePath,
192
+ exists: false,
193
+ ready: false,
194
+ anchors: [],
195
+ missingCriticalCount: externalRefs.length,
196
+ staleCount: 0,
197
+ criticalGaps: ['REFERENCE-ANCHORS.md ausente'],
198
+ };
199
+ }
200
+ const rootMatch = raw.match(/<reference_anchors\b([^>]*)>([\s\S]*?)<\/reference_anchors>/i);
201
+ if (!rootMatch) {
202
+ return {
203
+ path: filePath,
204
+ exists: true,
205
+ ready: false,
206
+ anchors: [],
207
+ missingCriticalCount: externalRefs.length,
208
+ staleCount: 0,
209
+ criticalGaps: ['REFERENCE-ANCHORS.md sem bloco <reference_anchors>'],
210
+ };
211
+ }
212
+ const rootAttrs = parseAttrs(rootMatch[1]);
213
+ const status = String(rootAttrs.status || '').toLowerCase();
214
+ /** @type {Array<Record<string, unknown>>} */
215
+ const anchors = [];
216
+ for (const match of rootMatch[2].matchAll(/<anchor\b([^>]*)>([\s\S]*?)<\/anchor>/gi)) {
217
+ const attrs = parseAttrs(match[1]);
218
+ anchors.push({
219
+ id: attrs.id || null,
220
+ task: attrs.task || null,
221
+ critical: String(attrs.critical || '').toLowerCase() === 'true',
222
+ status: String(attrs.status || 'missing').toLowerCase(),
223
+ sourceType: attrs.source_type || null,
224
+ path: attrs.path || null,
225
+ sourceRef: attrs.source_ref || null,
226
+ relevance: (((match[2].match(/<relevance>([\s\S]*?)<\/relevance>/i) || [null, ''])[1]) || '').trim(),
227
+ action: (((match[2].match(/<action>([\s\S]*?)<\/action>/i) || [null, ''])[1]) || '').trim(),
228
+ summary: (((match[2].match(/<summary>([\s\S]*?)<\/summary>/i) || [null, ''])[1]) || '').trim(),
229
+ });
230
+ }
231
+ if (status === 'not_applicable' && externalRefs.length) {
232
+ criticalGaps.push('REFERENCE-ANCHORS.md marcado como not_applicable, mas há external-ref no blueprint');
233
+ }
234
+ let missingCriticalCount = 0;
235
+ let staleCount = 0;
236
+ for (const anchor of anchors) {
237
+ const anchorStatus = String(anchor.status || 'missing').toLowerCase();
238
+ if (anchorStatus === 'stale') staleCount += 1;
239
+ if (anchor.critical && anchorStatus !== 'resolved') {
240
+ missingCriticalCount += 1;
241
+ criticalGaps.push(`REFERENCE-ANCHORS.md âncora crítica ${anchor.id || anchor.sourceRef || '?'} em estado ${anchorStatus}`);
242
+ }
243
+ }
244
+ for (const ref of externalRefs) {
245
+ const matched = anchors.find((anchor) => String(anchor.sourceRef || '').trim() === ref);
246
+ if (!matched) {
247
+ missingCriticalCount += 1;
248
+ criticalGaps.push(`REFERENCE-ANCHORS.md sem âncora materializada para ${ref}`);
249
+ } else if (String(matched.status || '').toLowerCase() !== 'resolved') {
250
+ missingCriticalCount += 1;
251
+ criticalGaps.push(`REFERENCE-ANCHORS.md com ${ref} em estado ${matched.status}`);
252
+ }
253
+ }
254
+ if (String(rootAttrs.ready || '').toLowerCase() === 'false') {
255
+ criticalGaps.push('REFERENCE-ANCHORS.md com ready=false');
256
+ }
257
+ return {
258
+ path: filePath,
259
+ exists: true,
260
+ ready: status === 'not_applicable'
261
+ ? criticalGaps.length === 0
262
+ : Boolean(String(rootAttrs.ready || '').toLowerCase() !== 'false') && criticalGaps.length === 0,
263
+ anchors,
264
+ missingCriticalCount,
265
+ staleCount,
266
+ criticalGaps: Array.from(new Set(criticalGaps)),
267
+ };
268
+ }
269
+
270
+ function summarizeFixturePack(filePath, planTasks, implementationPack) {
271
+ const parsed = readJsonIfExists(filePath);
272
+ /** @type {string[]} */
273
+ const criticalGaps = [];
274
+ if (!parsed.ok) {
275
+ return {
276
+ path: filePath,
277
+ exists: Boolean(readTextIfExists(filePath)),
278
+ parseError: parsed.error,
279
+ ready: false,
280
+ fixtures: [],
281
+ fixtureCount: 0,
282
+ criticalGaps: [`FIXTURE-PACK.json inválido ou ausente${parsed.error ? `: ${parsed.error}` : ''}`],
283
+ };
284
+ }
285
+ const data = parsed.data && typeof parsed.data === 'object' ? parsed.data : {};
286
+ const fixtures = Array.isArray(data.fixtures) ? data.fixtures : [];
287
+ const byTask = new Map();
288
+ for (const fixture of fixtures) {
289
+ const taskId = String(fixture && fixture.task_id || fixture && fixture.taskId || '').trim();
290
+ if (!taskId) continue;
291
+ if (!byTask.has(taskId)) byTask.set(taskId, []);
292
+ byTask.get(taskId).push(fixture);
293
+ }
294
+ if (data.ready === false) {
295
+ criticalGaps.push('FIXTURE-PACK.json com ready=false');
296
+ }
297
+ if (Array.isArray(data.critical_gaps) && data.critical_gaps.length) {
298
+ criticalGaps.push(...data.critical_gaps.map((gap) => `FIXTURE-PACK.json: ${String(gap)}`));
299
+ }
300
+ const implementationTasks = Array.isArray(implementationPack.tasks) ? implementationPack.tasks : [];
301
+ for (const task of implementationTasks) {
302
+ const taskId = String(task && task.id || task && task.task_id || '').trim();
303
+ if (!taskId) continue;
304
+ const planTask = planTasks.find((candidate) => candidate.id === taskId);
305
+ const requiresFixture = task && task.requires_fixture === true
306
+ || (planTask ? isTaskRiskKeyword(planTask) : false);
307
+ if (!requiresFixture) continue;
308
+ const entries = byTask.get(taskId) || [];
309
+ if (!entries.length) {
310
+ criticalGaps.push(`FIXTURE-PACK.json sem fixture para ${taskId}`);
311
+ continue;
312
+ }
313
+ const readyFixture = entries.find((entry) => String(entry && entry.status || '').toLowerCase() === 'ready');
314
+ if (!readyFixture) {
315
+ criticalGaps.push(`FIXTURE-PACK.json sem fixture ready para ${taskId}`);
316
+ continue;
317
+ }
318
+ if (!Array.isArray(readyFixture.expected_checks) || readyFixture.expected_checks.length === 0) {
319
+ criticalGaps.push(`FIXTURE-PACK.json fixture de ${taskId} sem expected_checks`);
320
+ }
321
+ if (Array.isArray(readyFixture.critical_gaps) && readyFixture.critical_gaps.length) {
322
+ criticalGaps.push(`FIXTURE-PACK.json fixture de ${taskId} com critical_gaps abertos`);
323
+ }
324
+ }
325
+ return {
326
+ path: filePath,
327
+ exists: true,
328
+ parseError: null,
329
+ ready: Boolean(data.ready) && criticalGaps.length === 0,
330
+ fixtures,
331
+ fixtureCount: fixtures.length,
332
+ criticalGaps: Array.from(new Set(criticalGaps)),
333
+ };
334
+ }
335
+
336
+ /**
337
+ * @param {{
338
+ * plan?: string | null,
339
+ * planAgents?: string | null,
340
+ * implementationPackJson?: string | null,
341
+ * implementationPackMd?: string | null,
342
+ * referenceAnchors?: string | null,
343
+ * fixturePackJson?: string | null,
344
+ * fixturePackMd?: string | null,
345
+ * }} paths
346
+ */
347
+ function buildExecutionRationality(paths = {}) {
348
+ const planTasks = parsePlanTasks(paths.plan || null);
349
+ const externalRefs = readExternalRefs(paths.planAgents || null);
350
+ const implementationPack = summarizeImplementationPack(paths.implementationPackJson || null, planTasks);
351
+ const referenceAnchors = summarizeReferenceAnchors(paths.referenceAnchors || null, externalRefs);
352
+ const fixturePack = summarizeFixturePack(paths.fixturePackJson || null, planTasks, implementationPack);
353
+ const criticalExecutionGaps = Array.from(
354
+ new Set([
355
+ ...implementationPack.criticalGaps,
356
+ ...referenceAnchors.criticalGaps,
357
+ ...fixturePack.criticalGaps,
358
+ ])
359
+ );
360
+ const applicable = Boolean(paths.plan && fs.existsSync(paths.plan));
361
+ return {
362
+ applicable,
363
+ planTaskCount: planTasks.length,
364
+ externalReferenceCount: externalRefs.length,
365
+ implementationPackReady: applicable ? implementationPack.ready : false,
366
+ referenceAnchorsReady: applicable ? referenceAnchors.ready : false,
367
+ fixturePackReady: applicable ? fixturePack.ready : false,
368
+ executionRationalityReady: applicable
369
+ ? implementationPack.ready && referenceAnchors.ready && fixturePack.ready && criticalExecutionGaps.length === 0
370
+ : false,
371
+ criticalExecutionGaps,
372
+ implementationPack,
373
+ referenceAnchors,
374
+ fixturePack,
375
+ };
376
+ }
377
+
378
+ module.exports = {
379
+ buildExecutionRationality,
380
+ parsePlanTasks,
381
+ readExternalRefs,
382
+ summarizeImplementationPack,
383
+ summarizeReferenceAnchors,
384
+ summarizeFixturePack,
385
+ };
@@ -5,6 +5,7 @@ const path = require('path');
5
5
  const { spawnSync } = require('child_process');
6
6
 
7
7
  const oxeManifest = require('./oxe-manifest.cjs');
8
+ const runtimeSemantics = require('./oxe-runtime-semantics.cjs');
8
9
 
9
10
  const REQUIRED_RUNTIMES = [
10
11
  'cursor',
@@ -269,6 +270,7 @@ function loadRuntimeSmokeReport(projectRoot) {
269
270
  && item.oxe_present
270
271
  && item.workflow_resolution_ok
271
272
  && item.wrapper_drift_ok !== false
273
+ && item.extra_checks_ok !== false
272
274
  && item.uninstall_ok
273
275
  );
274
276
  });
@@ -301,11 +303,42 @@ function readVersionSnapshot(projectRoot) {
301
303
  };
302
304
  }
303
305
 
306
+ function inspectCanonicalSource(projectRoot) {
307
+ const workflowsDir = path.join(projectRoot, 'oxe', 'workflows');
308
+ const referencesDir = path.join(workflowsDir, 'references');
309
+ const contractsPath = path.join(referencesDir, 'workflow-runtime-contracts.json');
310
+ const commandsDir = path.join(projectRoot, 'commands', 'oxe');
311
+ const workflowFiles = fs.existsSync(workflowsDir)
312
+ ? fs.readdirSync(workflowsDir).filter((name) => name.endsWith('.md'))
313
+ : [];
314
+ const registryIssues = runtimeSemantics.validateWorkflowContractsRegistry();
315
+ return {
316
+ workflowsDir,
317
+ referencesDir,
318
+ contractsPath,
319
+ commandsDir,
320
+ workflowsPresent: fs.existsSync(workflowsDir),
321
+ referencesPresent: fs.existsSync(referencesDir),
322
+ commandsPresent: fs.existsSync(commandsDir),
323
+ contractsPresent: fs.existsSync(contractsPath),
324
+ workflowCount: workflowFiles.length,
325
+ contractVersion: runtimeSemantics.CONTRACT_VERSION,
326
+ registryIssues,
327
+ ok: fs.existsSync(workflowsDir)
328
+ && fs.existsSync(referencesDir)
329
+ && fs.existsSync(commandsDir)
330
+ && fs.existsSync(contractsPath)
331
+ && workflowFiles.length > 0
332
+ && registryIssues.length === 0,
333
+ };
334
+ }
335
+
304
336
  function buildReleaseManifest(projectRoot, options = {}) {
305
337
  const packageRoot = path.resolve(options.packageRoot || projectRoot);
306
338
  const paths = releasePaths(projectRoot);
307
339
  const versions = readVersionSnapshot(projectRoot);
308
340
  const runtimeEntry = path.join(packageRoot, 'lib', 'runtime', 'index.js');
341
+ const canonicalSource = inspectCanonicalSource(projectRoot);
309
342
  const wrapperSync = options.skipWrapperSync ? {
310
343
  before: collectWrapperHashes(projectRoot),
311
344
  after: collectWrapperHashes(projectRoot),
@@ -313,6 +346,7 @@ function buildReleaseManifest(projectRoot, options = {}) {
313
346
  mismatches: [],
314
347
  ok: true,
315
348
  } : syncWrappers(projectRoot, packageRoot);
349
+ const semanticsAudit = runtimeSemantics.auditRuntimeTargets(projectRoot);
316
350
  const smoke = loadRuntimeSmokeReport(projectRoot);
317
351
  const recovery = loadRecoveryFixtureReport(projectRoot);
318
352
  const multiAgent = loadMultiAgentSoakReport(projectRoot);
@@ -332,6 +366,12 @@ function buildReleaseManifest(projectRoot, options = {}) {
332
366
  path: runtimeEntry,
333
367
  ok: fs.existsSync(runtimeEntry),
334
368
  },
369
+ canonical_source: canonicalSource,
370
+ semantics: {
371
+ contractVersion: runtimeSemantics.CONTRACT_VERSION,
372
+ registryPath: runtimeSemantics.CONTRACTS_PATH,
373
+ audit: semanticsAudit,
374
+ },
335
375
  wrappers: {
336
376
  hash_before_sync: wrapperSync.before,
337
377
  hash_after_sync: wrapperSync.after,
@@ -354,8 +394,8 @@ function buildReleaseManifest(projectRoot, options = {}) {
354
394
  return manifest;
355
395
  }
356
396
 
357
- function checkReleaseConsistency(projectRoot, options = {}) {
358
- const manifest = buildReleaseManifest(projectRoot, options);
397
+ function evaluateReleaseManifest(manifest, options = {}) {
398
+ const enforceSync = options.enforceSync !== false;
359
399
  const blockers = [];
360
400
  const warnings = [];
361
401
  const versions = manifest.versions;
@@ -378,10 +418,22 @@ function checkReleaseConsistency(projectRoot, options = {}) {
378
418
  if (!versions.changelog.date) blockers.push('CHANGELOG.md topo sem data');
379
419
  if (!versions.changelog.hasHighlights) blockers.push('CHANGELOG.md topo sem highlights');
380
420
  }
421
+ const canonical = manifest.canonical_source || {};
422
+ if (!canonical.ok) {
423
+ blockers.push('árvore canónica ausente ou inválida');
424
+ }
425
+ if (!canonical.workflowsPresent) blockers.push('oxe/workflows ausente');
426
+ if (!canonical.referencesPresent) blockers.push('oxe/workflows/references ausente');
427
+ if (!canonical.commandsPresent) blockers.push('commands/oxe ausente');
428
+ if (!canonical.contractsPresent) blockers.push('workflow-runtime-contracts.json ausente');
429
+ if (canonical.workflowCount === 0) blockers.push('oxe/workflows sem workflows canónicos');
430
+ if (Array.isArray(canonical.registryIssues) && canonical.registryIssues.length) {
431
+ blockers.push('workflow-runtime-contracts.json inválido');
432
+ }
381
433
  if (!manifest.runtime_compiled.ok) {
382
434
  blockers.push('runtime não compilado (lib/runtime/index.js ausente)');
383
435
  }
384
- if (!manifest.wrappers.sync.ok) {
436
+ if (enforceSync && !manifest.wrappers.sync.ok) {
385
437
  if (manifest.wrappers.sync.scripts.some((entry) => !entry.ok)) {
386
438
  blockers.push('sync de wrappers falhou');
387
439
  }
@@ -389,6 +441,10 @@ function checkReleaseConsistency(projectRoot, options = {}) {
389
441
  blockers.push('wrappers ficam dirty após sync-runtime-metadata/sync:cursor');
390
442
  }
391
443
  }
444
+ const semanticsAudit = manifest.semantics && manifest.semantics.audit;
445
+ if (!semanticsAudit || semanticsAudit.ok !== true) {
446
+ blockers.push('drift semântico entre workflows e wrappers');
447
+ }
392
448
  if (!manifest.reports.runtime_smoke.present || !manifest.reports.runtime_smoke.ok) {
393
449
  blockers.push('runtime smoke matrix incompleta ou com falhas');
394
450
  }
@@ -406,10 +462,23 @@ function checkReleaseConsistency(projectRoot, options = {}) {
406
462
  blockers,
407
463
  warnings,
408
464
  manifest,
409
- manifestPath: releasePaths(projectRoot).manifest,
465
+ manifestPath: releasePaths(manifest.project_root).manifest,
410
466
  };
411
467
  }
412
468
 
469
+ function checkReleaseConsistency(projectRoot, options = {}) {
470
+ const manifest = buildReleaseManifest(projectRoot, options);
471
+ return evaluateReleaseManifest(manifest, { enforceSync: true });
472
+ }
473
+
474
+ function inspectReleaseReadiness(projectRoot, options = {}) {
475
+ const manifest = buildReleaseManifest(projectRoot, {
476
+ ...options,
477
+ skipWrapperSync: true,
478
+ });
479
+ return evaluateReleaseManifest(manifest, { enforceSync: false });
480
+ }
481
+
413
482
  module.exports = {
414
483
  REQUIRED_RUNTIMES,
415
484
  WRAPPER_TARGETS,
@@ -419,5 +488,8 @@ module.exports = {
419
488
  loadRecoveryFixtureReport,
420
489
  loadMultiAgentSoakReport,
421
490
  buildReleaseManifest,
491
+ inspectCanonicalSource,
492
+ evaluateReleaseManifest,
493
+ inspectReleaseReadiness,
422
494
  checkReleaseConsistency,
423
495
  };