oxe-cc 1.12.0 → 1.16.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 (40) hide show
  1. package/.github/dependabot.yml +31 -0
  2. package/.github/workflows/ci.yml +141 -56
  3. package/.github/workflows/release.yml +114 -89
  4. package/CHANGELOG.md +866 -754
  5. package/README.md +600 -736
  6. package/bin/lib/oxe-agent-install.cjs +299 -284
  7. package/bin/lib/oxe-artifact-catalog.cjs +376 -0
  8. package/bin/lib/oxe-command-registry.cjs +31 -0
  9. package/bin/lib/oxe-context-engine.cjs +11 -11
  10. package/bin/lib/oxe-core-command-handlers.cjs +82 -0
  11. package/bin/lib/oxe-dashboard.cjs +140 -140
  12. package/bin/lib/oxe-manifest.cjs +20 -20
  13. package/bin/lib/oxe-npm-version.cjs +6 -4
  14. package/bin/lib/oxe-plugin-cli.cjs +95 -0
  15. package/bin/lib/oxe-plugins.cjs +94 -3
  16. package/bin/lib/oxe-process.cjs +67 -0
  17. package/bin/lib/oxe-project-health.cjs +2846 -2781
  18. package/bin/lib/oxe-runtime-semantics.cjs +68 -69
  19. package/bin/oxe-cc.js +369 -353
  20. package/docs/INTEGRATION.md +182 -0
  21. package/docs/QUALITY-GATES.md +46 -0
  22. package/docs/RELEASE-READINESS.md +86 -61
  23. package/docs/RUNTIME-SMOKE-MATRIX.md +137 -135
  24. package/docs/oxe-artifact-map.html +1172 -0
  25. package/lib/sdk/index.cjs +20 -0
  26. package/lib/sdk/index.d.ts +971 -876
  27. package/lib/sdk/index.types.ts +933 -0
  28. package/oxe/templates/PLUGINS.md +8 -1
  29. package/oxe/templates/STATE-REFERENCE.md +125 -0
  30. package/oxe/templates/STATE.md +11 -121
  31. package/oxe/workflows/help.md +2 -0
  32. package/package.json +129 -108
  33. package/packages/runtime/package.json +18 -18
  34. package/packages/runtime/src/evidence/evidence-store.ts +2 -2
  35. package/packages/runtime/src/scheduler/multi-agent-coordinator.ts +728 -728
  36. package/packages/runtime/src/workspace/strategies/git-worktree.ts +24 -24
  37. package/packages/runtime/tsconfig.json +8 -2
  38. package/vscode-extension/.vscodeignore +2 -0
  39. package/vscode-extension/package.json +193 -185
  40. package/vscode-extension/src/extension.js +11 -1
@@ -152,69 +152,69 @@ function parseVerify(md) {
152
152
  return { criteria, mentionedCriteria: Array.from(new Set((md.match(/\bA\d+\b/g) || []).map((x) => x.toUpperCase()))), failed: /\b(verify_failed|falhou|falha|reprovad)\b/i.test(md), passed: /\b(verify_complete|aprovad|passou|sucesso)\b/i.test(md) };
153
153
  }
154
154
 
155
- function confidenceBand(confidence, threshold) {
156
- const normalizedThreshold = health.normalizePlanConfidenceThreshold(threshold);
157
- if (confidence == null) return 'unknown';
158
- if (health.isExecutablePlanConfidence(confidence, normalizedThreshold)) return 'ready';
159
- if (confidence >= Math.max(80, normalizedThreshold - 10)) return 'rational_but_not_ready';
160
- if (confidence >= 50) return 'needs_refinement';
161
- return 'do_not_execute';
162
- }
163
-
164
- function computeReadiness(ctx, threshold) {
165
- if (ctx.workspaceMode === 'product_package' && ctx.releaseReadiness) {
166
- const blockers = Array.isArray(ctx.releaseReadiness.blockers) ? ctx.releaseReadiness.blockers.slice() : [];
167
- const warnings = Array.isArray(ctx.releaseReadiness.warnings) ? ctx.releaseReadiness.warnings.slice() : [];
168
- return {
169
- go: Boolean(ctx.releaseReadiness.ok),
170
- decision: ctx.releaseReadiness.ok ? 'go' : 'no-go',
171
- threshold: health.normalizePlanConfidenceThreshold(threshold),
172
- confidence: ctx.plan && ctx.plan.selfEvaluation ? ctx.plan.selfEvaluation.confidence : null,
173
- confidenceBand: confidenceBand(ctx.plan && ctx.plan.selfEvaluation ? ctx.plan.selfEvaluation.confidence : null, threshold),
174
- checkpointPending: false,
175
- blockers,
176
- warnings,
177
- };
178
- }
179
- const normalizedThreshold = health.normalizePlanConfidenceThreshold(threshold);
180
- const blockers = [];
181
- const warnings = [...ctx.diagnostics.reviewWarnings, ...ctx.diagnostics.runtimeWarnings, ...ctx.diagnostics.planWarnings];
182
- if (!ctx.plan.selfEvaluation || !ctx.plan.selfEvaluation.hasSection) blockers.push('self_evaluation:missing');
183
- if (ctx.plan.selfEvaluation && Array.isArray(ctx.plan.selfEvaluation.warnings) && ctx.plan.selfEvaluation.warnings.length) {
184
- blockers.push(`self_evaluation:warnings:${ctx.plan.selfEvaluation.warnings.length}`);
185
- }
186
- if (ctx.planReviewStatus !== 'approved') blockers.push(`review_status:${ctx.planReviewStatus || 'draft'}`);
187
- if (ctx.plan.selfEvaluation.bestPlan === 'não') blockers.push('best_plan:no');
188
- if (ctx.plan.selfEvaluation.confidence == null) blockers.push('confidence:missing');
189
- else if (!health.isExecutablePlanConfidence(ctx.plan.selfEvaluation.confidence, normalizedThreshold)) {
190
- blockers.push(`confidence:${ctx.plan.selfEvaluation.confidence}%<=${normalizedThreshold}%`);
191
- }
192
- if (!ctx.executionRationality || !ctx.executionRationality.implementationPackReady) {
193
- blockers.push('implementation_pack:not_ready');
194
- }
195
- if (!ctx.executionRationality || !ctx.executionRationality.referenceAnchorsReady) {
196
- blockers.push('reference_anchors:not_ready');
197
- }
198
- if (!ctx.executionRationality || !ctx.executionRationality.fixturePackReady) {
199
- blockers.push('fixture_pack:not_ready');
200
- }
201
- if (ctx.checkpoints.parsed.some((x) => /pending_approval/i.test(x.status))) blockers.push('checkpoint:pending_approval');
202
- if (ctx.runtime.parsed.status === 'blocked') blockers.push('runtime:blocked');
203
- if (ctx.spec.uncoveredCriteria.length) warnings.push(`${ctx.spec.uncoveredCriteria.length} critérios sem cobertura no plano`);
204
- if (ctx.executionRationality && Array.isArray(ctx.executionRationality.criticalExecutionGaps)) {
205
- warnings.push(...ctx.executionRationality.criticalExecutionGaps);
206
- }
207
- return {
208
- go: blockers.length === 0,
209
- decision: blockers.length === 0 ? 'go' : 'no-go',
210
- threshold: normalizedThreshold,
211
- confidence: ctx.plan.selfEvaluation.confidence,
212
- confidenceBand: confidenceBand(ctx.plan.selfEvaluation.confidence, normalizedThreshold),
213
- checkpointPending: blockers.includes('checkpoint:pending_approval'),
214
- blockers,
215
- warnings,
216
- };
217
- }
155
+ function confidenceBand(confidence, threshold) {
156
+ const normalizedThreshold = health.normalizePlanConfidenceThreshold(threshold);
157
+ if (confidence == null) return 'unknown';
158
+ if (health.isExecutablePlanConfidence(confidence, normalizedThreshold)) return 'ready';
159
+ if (confidence >= Math.max(80, normalizedThreshold - 10)) return 'rational_but_not_ready';
160
+ if (confidence >= 50) return 'needs_refinement';
161
+ return 'do_not_execute';
162
+ }
163
+
164
+ function computeReadiness(ctx, threshold) {
165
+ if (ctx.workspaceMode === 'product_package' && ctx.releaseReadiness) {
166
+ const blockers = Array.isArray(ctx.releaseReadiness.blockers) ? ctx.releaseReadiness.blockers.slice() : [];
167
+ const warnings = Array.isArray(ctx.releaseReadiness.warnings) ? ctx.releaseReadiness.warnings.slice() : [];
168
+ return {
169
+ go: Boolean(ctx.releaseReadiness.ok),
170
+ decision: ctx.releaseReadiness.ok ? 'go' : 'no-go',
171
+ threshold: health.normalizePlanConfidenceThreshold(threshold),
172
+ confidence: ctx.plan && ctx.plan.selfEvaluation ? ctx.plan.selfEvaluation.confidence : null,
173
+ confidenceBand: confidenceBand(ctx.plan && ctx.plan.selfEvaluation ? ctx.plan.selfEvaluation.confidence : null, threshold),
174
+ checkpointPending: false,
175
+ blockers,
176
+ warnings,
177
+ };
178
+ }
179
+ const normalizedThreshold = health.normalizePlanConfidenceThreshold(threshold);
180
+ const blockers = [];
181
+ const warnings = [...ctx.diagnostics.reviewWarnings, ...ctx.diagnostics.runtimeWarnings, ...ctx.diagnostics.planWarnings];
182
+ if (!ctx.plan.selfEvaluation || !ctx.plan.selfEvaluation.hasSection) blockers.push('self_evaluation:missing');
183
+ if (ctx.plan.selfEvaluation && Array.isArray(ctx.plan.selfEvaluation.warnings) && ctx.plan.selfEvaluation.warnings.length) {
184
+ blockers.push(`self_evaluation:warnings:${ctx.plan.selfEvaluation.warnings.length}`);
185
+ }
186
+ if (ctx.planReviewStatus !== 'approved') blockers.push(`review_status:${ctx.planReviewStatus || 'draft'}`);
187
+ if (ctx.plan.selfEvaluation.bestPlan === 'não') blockers.push('best_plan:no');
188
+ if (ctx.plan.selfEvaluation.confidence == null) blockers.push('confidence:missing');
189
+ else if (!health.isExecutablePlanConfidence(ctx.plan.selfEvaluation.confidence, normalizedThreshold)) {
190
+ blockers.push(`confidence:${ctx.plan.selfEvaluation.confidence}%<=${normalizedThreshold}%`);
191
+ }
192
+ if (!ctx.executionRationality || !ctx.executionRationality.implementationPackReady) {
193
+ blockers.push('implementation_pack:not_ready');
194
+ }
195
+ if (!ctx.executionRationality || !ctx.executionRationality.referenceAnchorsReady) {
196
+ blockers.push('reference_anchors:not_ready');
197
+ }
198
+ if (!ctx.executionRationality || !ctx.executionRationality.fixturePackReady) {
199
+ blockers.push('fixture_pack:not_ready');
200
+ }
201
+ if (ctx.checkpoints.parsed.some((x) => /pending_approval/i.test(x.status))) blockers.push('checkpoint:pending_approval');
202
+ if (ctx.runtime.parsed.status === 'blocked') blockers.push('runtime:blocked');
203
+ if (ctx.spec.uncoveredCriteria.length) warnings.push(`${ctx.spec.uncoveredCriteria.length} critérios sem cobertura no plano`);
204
+ if (ctx.executionRationality && Array.isArray(ctx.executionRationality.criticalExecutionGaps)) {
205
+ warnings.push(...ctx.executionRationality.criticalExecutionGaps);
206
+ }
207
+ return {
208
+ go: blockers.length === 0,
209
+ decision: blockers.length === 0 ? 'go' : 'no-go',
210
+ threshold: normalizedThreshold,
211
+ confidence: ctx.plan.selfEvaluation.confidence,
212
+ confidenceBand: confidenceBand(ctx.plan.selfEvaluation.confidence, normalizedThreshold),
213
+ checkpointPending: blockers.includes('checkpoint:pending_approval'),
214
+ blockers,
215
+ warnings,
216
+ };
217
+ }
218
218
 
219
219
  function buildCoverageMatrix(spec, plan, verify) {
220
220
  const taskMap = new Map();
@@ -236,27 +236,27 @@ function buildCoverageMatrix(spec, plan, verify) {
236
236
  }));
237
237
  }
238
238
 
239
- function computeCalibration(phase, confidence, verify, threshold) {
240
- const normalizedThreshold = health.normalizePlanConfidenceThreshold(threshold);
241
- if (confidence == null) return { status: 'pending', summary: 'Calibração indisponível antes do verify.' };
242
- const low = String(phase || '').toLowerCase();
243
- const completed = low === 'verify_complete' || verify.passed;
244
- const failed = low === 'verify_failed' || verify.failed;
245
- if (!completed && !failed) return { status: 'pending', summary: 'Calibração só fecha após verify.' };
246
- if (health.isExecutablePlanConfidence(confidence, normalizedThreshold) && failed) {
247
- return { status: 'overconfident', summary: `Confiança ${confidence}% acima do gate executável, mas o verify falhou.` };
248
- }
249
- if (!health.isExecutablePlanConfidence(confidence, normalizedThreshold) && failed) {
250
- return { status: 'calibrated-risk', summary: `O plano já sinalizava risco (${confidence}%) abaixo do gate executável e o verify confirmou a fragilidade.` };
251
- }
252
- if (!health.isExecutablePlanConfidence(confidence, normalizedThreshold) && completed) {
253
- return { status: 'underconfident', summary: `O resultado final foi melhor que a confiança inicial (${confidence}%), mas o plano ainda não superava o gate executável.` };
254
- }
255
- if (health.isExecutablePlanConfidence(confidence, normalizedThreshold) && completed) {
256
- return { status: 'well-calibrated', summary: `Alta confiança executável (${confidence}%) e verify coerente com a expectativa.` };
257
- }
258
- return { status: 'acceptable', summary: `Confiança ${confidence}% e verify dentro da faixa esperada.` };
259
- }
239
+ function computeCalibration(phase, confidence, verify, threshold) {
240
+ const normalizedThreshold = health.normalizePlanConfidenceThreshold(threshold);
241
+ if (confidence == null) return { status: 'pending', summary: 'Calibração indisponível antes do verify.' };
242
+ const low = String(phase || '').toLowerCase();
243
+ const completed = low === 'verify_complete' || verify.passed;
244
+ const failed = low === 'verify_failed' || verify.failed;
245
+ if (!completed && !failed) return { status: 'pending', summary: 'Calibração só fecha após verify.' };
246
+ if (health.isExecutablePlanConfidence(confidence, normalizedThreshold) && failed) {
247
+ return { status: 'overconfident', summary: `Confiança ${confidence}% acima do gate executável, mas o verify falhou.` };
248
+ }
249
+ if (!health.isExecutablePlanConfidence(confidence, normalizedThreshold) && failed) {
250
+ return { status: 'calibrated-risk', summary: `O plano já sinalizava risco (${confidence}%) abaixo do gate executável e o verify confirmou a fragilidade.` };
251
+ }
252
+ if (!health.isExecutablePlanConfidence(confidence, normalizedThreshold) && completed) {
253
+ return { status: 'underconfident', summary: `O resultado final foi melhor que a confiança inicial (${confidence}%), mas o plano ainda não superava o gate executável.` };
254
+ }
255
+ if (health.isExecutablePlanConfidence(confidence, normalizedThreshold) && completed) {
256
+ return { status: 'well-calibrated', summary: `Alta confiança executável (${confidence}%) e verify coerente com a expectativa.` };
257
+ }
258
+ return { status: 'acceptable', summary: `Confiança ${confidence}% e verify dentro da faixa esperada.` };
259
+ }
260
260
 
261
261
  function readRepositoryContext(codebaseDir) {
262
262
  const names = ['OVERVIEW.md', 'STACK.md', 'STRUCTURE.md', 'TESTING.md', 'CONCERNS.md', 'INTEGRATIONS.md'];
@@ -363,7 +363,7 @@ function loadDashboardContext(projectRoot, opts = {}) {
363
363
  const activeSession = opts.activeSession === undefined ? health.parseActiveSession(stateText) : opts.activeSession;
364
364
  const p = reviewPaths(projectRoot, activeSession || null);
365
365
  const rootScoped = reviewPaths(projectRoot, null);
366
- const report = health.buildHealthReport(projectRoot);
366
+ const report = health.buildHealthReport(projectRoot);
367
367
  const specText = readScopedText(p.spec, rootScoped.spec);
368
368
  const planText = readScopedText(p.plan, rootScoped.plan);
369
369
  const verifyText = readScopedText(p.verify, rootScoped.verify);
@@ -387,24 +387,24 @@ function loadDashboardContext(projectRoot, opts = {}) {
387
387
  const sessions = parseSessionsIndex(sessionsRaw);
388
388
  const sessionPath = activeSession ? path.join(projectRoot, '.oxe', activeSession, 'SESSION.md') : null;
389
389
  const sessionRaw = sessionPath ? readTextIfExists(sessionPath) || '' : '';
390
- const ctx = {
391
- projectRoot: path.resolve(projectRoot),
392
- workspaceMode: report.workspaceMode || 'oxe_project',
393
- releaseReadiness: report.releaseReadiness || null,
394
- activeSession: activeSession || null,
390
+ const ctx = {
391
+ projectRoot: path.resolve(projectRoot),
392
+ workspaceMode: report.workspaceMode || 'oxe_project',
393
+ releaseReadiness: report.releaseReadiness || null,
394
+ activeSession: activeSession || null,
395
395
  phase: report.phase || health.parseStatePhase(stateText),
396
396
  healthStatus: report.healthStatus,
397
397
  nextStep: report.next,
398
398
  planReviewStatus: report.planReviewStatus || 'draft',
399
399
  state: { path: globalPaths.state, raw: stateText, parsed: { phase: health.parseStatePhase(stateText), activeSession, runtimeStatus: firstMatch(stateText, /\*\*runtime_status:\*\*\s*([^\n]+)/i) } },
400
400
  spec: { path: p.spec, raw: specText, objective: spec.objective, criteria: spec.criteria, uncoveredCriteria: spec.criteria.filter((c) => !plan.tasks.some((t) => (t.aceite || []).includes(c.id))) },
401
- plan: { path: p.plan, raw: planText, tasks: plan.tasks, waves: plan.waves, totalTasks: plan.totalTasks, selfEvaluation: report.planSelfEvaluation },
402
- executionRationality: report.executionRationality || null,
403
- runtime: { path: p.runtime, raw: runtimeText, summary: summarizeText(runtimeText, 800), parsed: runtime },
401
+ plan: { path: p.plan, raw: planText, tasks: plan.tasks, waves: plan.waves, totalTasks: plan.totalTasks, selfEvaluation: report.planSelfEvaluation },
402
+ executionRationality: report.executionRationality || null,
403
+ runtime: { path: p.runtime, raw: runtimeText, summary: summarizeText(runtimeText, 800), parsed: runtime },
404
404
  activeRun: activeRunState,
405
405
  runtimeCanonical: activeRunState && activeRunState.canonical_state ? activeRunState.canonical_state : null,
406
406
  compiledGraph: activeRunState && activeRunState.compiled_graph ? activeRunState.compiled_graph : null,
407
- enterprise: {
407
+ enterprise: {
408
408
  runtimeMode: report.runtimeMode || null,
409
409
  fallbackMode: report.fallbackMode || null,
410
410
  verificationSummary: report.verificationSummary || null,
@@ -420,14 +420,14 @@ function loadDashboardContext(projectRoot, opts = {}) {
420
420
  promotionReadiness: report.promotionReadiness || null,
421
421
  recoveryState: report.recoveryState || null,
422
422
  multiAgent: report.multiAgent || null,
423
- providerCatalog: report.providerCatalog || null,
424
- implementationPackReady: report.implementationPackReady,
425
- referenceAnchorsReady: report.referenceAnchorsReady,
426
- fixturePackReady: report.fixturePackReady,
427
- executionRationalityReady: report.executionRationalityReady,
428
- criticalExecutionGaps: report.criticalExecutionGaps || [],
429
- warnings: report.enterpriseWarn || [],
430
- },
423
+ providerCatalog: report.providerCatalog || null,
424
+ implementationPackReady: report.implementationPackReady,
425
+ referenceAnchorsReady: report.referenceAnchorsReady,
426
+ fixturePackReady: report.fixturePackReady,
427
+ executionRationalityReady: report.executionRationalityReady,
428
+ criticalExecutionGaps: report.criticalExecutionGaps || [],
429
+ warnings: report.enterpriseWarn || [],
430
+ },
431
431
  tracing: { path: operational.operationalPaths(projectRoot, activeSession || null).events, events: traceEvents, summary: traceSummary },
432
432
  checkpoints: { path: p.checkpoints, raw: checkpointsText, parsed: checkpoints },
433
433
  verify: { path: p.verify, raw: verifyText, summary: summarizeText(verifyText, 800), parsed: verify },
@@ -473,44 +473,44 @@ function loadDashboardContext(projectRoot, opts = {}) {
473
473
  summary: `login=${ctx.azure.authStatus && ctx.azure.authStatus.login_active ? 'ativo' : 'ausente'} · subscription=${ctx.azure.profile && (ctx.azure.profile.subscription_name || ctx.azure.profile.subscription_id) || '—'} · total=${inventorySummary.total} · sb=${inventorySummary.servicebus || 0} · eg=${inventorySummary.eventgrid || 0} · sql=${inventorySummary.sql || 0}`,
474
474
  };
475
475
  }
476
- ctx.readiness = computeReadiness(ctx, report.planConfidenceThreshold || 90);
477
- ctx.coverage = buildCoverageMatrix(ctx.spec, ctx.plan, verify);
478
- ctx.calibration = computeCalibration(ctx.phase, ctx.plan.selfEvaluation.confidence, verify, ctx.readiness.threshold);
479
- ctx.visual = {
480
- flow: { nodes: [{ label: 'STATE', status: 'done' }, { label: 'SPEC', status: ctx.spec.raw ? 'done' : 'pending' }, { label: 'PLAN', status: ctx.plan.raw ? 'done' : 'pending' }, { label: 'REVIEW', status: ctx.planReviewStatus === 'approved' ? 'done' : /(rejected|needs_revision)/i.test(ctx.planReviewStatus) ? 'blocked' : 'active' }, { label: 'EXECUTE', status: ctx.runtime.parsed.status === 'running' ? 'active' : ctx.runtime.raw ? 'done' : 'pending' }, { label: 'CHECKPOINTS', status: ctx.readiness.checkpointPending ? 'active' : ctx.checkpoints.parsed.length ? 'done' : 'pending' }, { label: 'VERIFY', status: ctx.verify.raw ? 'done' : 'pending' }, { label: 'LESSONS', status: 'pending' }] },
481
- artifactGraph: [
482
- { id: 'state', label: 'STATE', path: ctx.state.path, detail: ctx.phase || 'índice global', status: 'done' },
483
- { id: 'spec', label: 'SPEC', path: ctx.spec.path, detail: ctx.spec.objective || 'contrato', status: ctx.spec.raw ? 'done' : 'pending' },
484
- { id: 'plan', label: 'PLAN', path: ctx.plan.path, detail: `${ctx.plan.totalTasks} tarefas`, status: ctx.plan.raw ? 'done' : 'pending' },
485
- {
486
- id: 'implementation-pack',
487
- label: 'IMPLEMENTATION PACK',
488
- path: ctx.executionRationality && ctx.executionRationality.implementationPack ? ctx.executionRationality.implementationPack.path : '—',
489
- detail: ctx.executionRationality && ctx.executionRationality.implementationPack ? `${ctx.executionRationality.implementationPack.taskCount || 0} contratos` : 'sem contrato',
490
- status: ctx.executionRationality && ctx.executionRationality.implementationPackReady ? 'done' : 'blocked',
491
- },
492
- {
493
- id: 'reference-anchors',
494
- label: 'REFERENCE ANCHORS',
495
- path: ctx.executionRationality && ctx.executionRationality.referenceAnchors ? ctx.executionRationality.referenceAnchors.path : '—',
496
- detail: ctx.executionRationality && ctx.executionRationality.referenceAnchors ? `${ctx.executionRationality.referenceAnchors.anchors ? ctx.executionRationality.referenceAnchors.anchors.length : 0} âncoras` : 'sem âncoras',
497
- status: ctx.executionRationality && ctx.executionRationality.referenceAnchorsReady ? 'done' : 'blocked',
498
- },
499
- {
500
- id: 'fixture-pack',
501
- label: 'FIXTURE PACK',
502
- path: ctx.executionRationality && ctx.executionRationality.fixturePack ? ctx.executionRationality.fixturePack.path : '—',
503
- detail: ctx.executionRationality && ctx.executionRationality.fixturePack ? `${ctx.executionRationality.fixturePack.fixtureCount || 0} fixtures` : 'sem fixtures',
504
- status: ctx.executionRationality && ctx.executionRationality.fixturePackReady ? 'done' : 'blocked',
505
- },
506
- { id: 'review', label: 'PLAN REVIEW', path: ctx.review.markdownPath, detail: ctx.planReviewStatus, status: ctx.planReviewStatus === 'approved' ? 'done' : 'active' },
507
- { id: 'runtime', label: 'RUNTIME', path: ctx.runtime.path, detail: ctx.runtime.parsed.status || 'sem status', status: ctx.runtime.raw ? 'active' : 'pending' },
508
- { id: 'active-run', label: 'ACTIVE RUN', path: operational.operationalPaths(projectRoot, activeSession || null).activeRun, detail: ctx.activeRun && ctx.activeRun.run_id ? `${ctx.activeRun.run_id} · ${ctx.activeRun.status}` : 'sem run ativo', status: ctx.activeRun ? 'active' : 'pending' },
509
- { id: 'events', label: 'TRACE', path: operational.operationalPaths(projectRoot, activeSession || null).events, detail: `${ctx.tracing.summary.total} evento(s)`, status: ctx.tracing.summary.total ? 'done' : 'pending' },
510
- { id: 'checkpoints', label: 'CHECKPOINTS', path: ctx.checkpoints.path, detail: `${ctx.checkpoints.parsed.length} gates`, status: ctx.readiness.checkpointPending ? 'active' : 'pending' },
511
- { id: 'verify', label: 'VERIFY', path: ctx.verify.path, detail: ctx.calibration.status, status: ctx.verify.raw ? 'done' : 'pending' },
512
- ],
513
- };
476
+ ctx.readiness = computeReadiness(ctx, report.planConfidenceThreshold || 90);
477
+ ctx.coverage = buildCoverageMatrix(ctx.spec, ctx.plan, verify);
478
+ ctx.calibration = computeCalibration(ctx.phase, ctx.plan.selfEvaluation.confidence, verify, ctx.readiness.threshold);
479
+ ctx.visual = {
480
+ flow: { nodes: [{ label: 'STATE', status: 'done' }, { label: 'SPEC', status: ctx.spec.raw ? 'done' : 'pending' }, { label: 'PLAN', status: ctx.plan.raw ? 'done' : 'pending' }, { label: 'REVIEW', status: ctx.planReviewStatus === 'approved' ? 'done' : /(rejected|needs_revision)/i.test(ctx.planReviewStatus) ? 'blocked' : 'active' }, { label: 'EXECUTE', status: ctx.runtime.parsed.status === 'running' ? 'active' : ctx.runtime.raw ? 'done' : 'pending' }, { label: 'CHECKPOINTS', status: ctx.readiness.checkpointPending ? 'active' : ctx.checkpoints.parsed.length ? 'done' : 'pending' }, { label: 'VERIFY', status: ctx.verify.raw ? 'done' : 'pending' }, { label: 'LESSONS', status: 'pending' }] },
481
+ artifactGraph: [
482
+ { id: 'state', label: 'STATE', path: ctx.state.path, detail: ctx.phase || 'índice global', status: 'done' },
483
+ { id: 'spec', label: 'SPEC', path: ctx.spec.path, detail: ctx.spec.objective || 'contrato', status: ctx.spec.raw ? 'done' : 'pending' },
484
+ { id: 'plan', label: 'PLAN', path: ctx.plan.path, detail: `${ctx.plan.totalTasks} tarefas`, status: ctx.plan.raw ? 'done' : 'pending' },
485
+ {
486
+ id: 'implementation-pack',
487
+ label: 'IMPLEMENTATION PACK',
488
+ path: ctx.executionRationality && ctx.executionRationality.implementationPack ? ctx.executionRationality.implementationPack.path : '—',
489
+ detail: ctx.executionRationality && ctx.executionRationality.implementationPack ? `${ctx.executionRationality.implementationPack.taskCount || 0} contratos` : 'sem contrato',
490
+ status: ctx.executionRationality && ctx.executionRationality.implementationPackReady ? 'done' : 'blocked',
491
+ },
492
+ {
493
+ id: 'reference-anchors',
494
+ label: 'REFERENCE ANCHORS',
495
+ path: ctx.executionRationality && ctx.executionRationality.referenceAnchors ? ctx.executionRationality.referenceAnchors.path : '—',
496
+ detail: ctx.executionRationality && ctx.executionRationality.referenceAnchors ? `${ctx.executionRationality.referenceAnchors.anchors ? ctx.executionRationality.referenceAnchors.anchors.length : 0} âncoras` : 'sem âncoras',
497
+ status: ctx.executionRationality && ctx.executionRationality.referenceAnchorsReady ? 'done' : 'blocked',
498
+ },
499
+ {
500
+ id: 'fixture-pack',
501
+ label: 'FIXTURE PACK',
502
+ path: ctx.executionRationality && ctx.executionRationality.fixturePack ? ctx.executionRationality.fixturePack.path : '—',
503
+ detail: ctx.executionRationality && ctx.executionRationality.fixturePack ? `${ctx.executionRationality.fixturePack.fixtureCount || 0} fixtures` : 'sem fixtures',
504
+ status: ctx.executionRationality && ctx.executionRationality.fixturePackReady ? 'done' : 'blocked',
505
+ },
506
+ { id: 'review', label: 'PLAN REVIEW', path: ctx.review.markdownPath, detail: ctx.planReviewStatus, status: ctx.planReviewStatus === 'approved' ? 'done' : 'active' },
507
+ { id: 'runtime', label: 'RUNTIME', path: ctx.runtime.path, detail: ctx.runtime.parsed.status || 'sem status', status: ctx.runtime.raw ? 'active' : 'pending' },
508
+ { id: 'active-run', label: 'ACTIVE RUN', path: operational.operationalPaths(projectRoot, activeSession || null).activeRun, detail: ctx.activeRun && ctx.activeRun.run_id ? `${ctx.activeRun.run_id} · ${ctx.activeRun.status}` : 'sem run ativo', status: ctx.activeRun ? 'active' : 'pending' },
509
+ { id: 'events', label: 'TRACE', path: operational.operationalPaths(projectRoot, activeSession || null).events, detail: `${ctx.tracing.summary.total} evento(s)`, status: ctx.tracing.summary.total ? 'done' : 'pending' },
510
+ { id: 'checkpoints', label: 'CHECKPOINTS', path: ctx.checkpoints.path, detail: `${ctx.checkpoints.parsed.length} gates`, status: ctx.readiness.checkpointPending ? 'active' : 'pending' },
511
+ { id: 'verify', label: 'VERIFY', path: ctx.verify.path, detail: ctx.calibration.status, status: ctx.verify.raw ? 'done' : 'pending' },
512
+ ],
513
+ };
514
514
  if (ctx.azure) {
515
515
  ctx.visual.artifactGraph.push(
516
516
  {
@@ -51,26 +51,26 @@ function writeFileManifest(home, files, version) {
51
51
  * Before overwriting with --force, backup files that diverged from last manifest.
52
52
  * @param {string} home
53
53
  * @param {Record<string, string>} prevManifest
54
- * @param {{ dryRun: boolean, force: boolean }} opts
55
- * @param {{ yellow: string, cyan: string, dim: string, reset: string }} colors
56
- * @param {{ scopeRoots?: string[] }} [scope]
57
- * @returns {string[]} modified paths
58
- */
59
- function backupModifiedFromManifest(home, prevManifest, opts, colors, scope = {}) {
60
- const { yellow, cyan, dim, reset } = colors;
61
- if (!opts.force || opts.dryRun) return [];
62
- const normalizedRoots = Array.isArray(scope.scopeRoots)
63
- ? scope.scopeRoots.map((root) => path.resolve(root))
64
- : [];
65
- const modified = [];
66
- for (const [absPath, oldHash] of Object.entries(prevManifest)) {
67
- if (normalizedRoots.length > 0 && !normalizedRoots.some((root) => absPath === root || absPath.startsWith(`${root}${path.sep}`))) {
68
- continue;
69
- }
70
- if (!fs.existsSync(absPath)) continue;
71
- let now;
72
- try {
73
- now = sha256File(absPath);
54
+ * @param {{ dryRun: boolean, force: boolean }} opts
55
+ * @param {{ yellow: string, cyan: string, dim: string, reset: string }} colors
56
+ * @param {{ scopeRoots?: string[] }} [scope]
57
+ * @returns {string[]} modified paths
58
+ */
59
+ function backupModifiedFromManifest(home, prevManifest, opts, colors, scope = {}) {
60
+ const { yellow, cyan, dim, reset } = colors;
61
+ if (!opts.force || opts.dryRun) return [];
62
+ const normalizedRoots = Array.isArray(scope.scopeRoots)
63
+ ? scope.scopeRoots.map((root) => path.resolve(root))
64
+ : [];
65
+ const modified = [];
66
+ for (const [absPath, oldHash] of Object.entries(prevManifest)) {
67
+ if (normalizedRoots.length > 0 && !normalizedRoots.some((root) => absPath === root || absPath.startsWith(`${root}${path.sep}`))) {
68
+ continue;
69
+ }
70
+ if (!fs.existsSync(absPath)) continue;
71
+ let now;
72
+ try {
73
+ now = sha256File(absPath);
74
74
  } catch {
75
75
  continue;
76
76
  }
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const { spawnSync } = require('child_process');
4
3
  const semver = require('semver');
4
+ const { runPackageManagerSync } = require('./oxe-process.cjs');
5
5
 
6
6
  /**
7
7
  * Extrai versão semver do stdout de `npm view <pkg> version`.
@@ -41,11 +41,13 @@ function isNewerThan(latest, current) {
41
41
  * @returns {{ ok: true, version: string } | { ok: false, error: string }}
42
42
  */
43
43
  function syncNpmViewVersion(packageName, spawnOpts = {}) {
44
- const r = spawnSync('npm', ['view', packageName, 'version'], {
44
+ const run = spawnOpts.runPackageManagerSync || runPackageManagerSync;
45
+ const options = { ...spawnOpts };
46
+ delete options.runPackageManagerSync;
47
+ const r = run('npm', ['view', packageName, 'version'], {
45
48
  encoding: 'utf8',
46
49
  env: process.env,
47
- shell: process.platform === 'win32',
48
- ...spawnOpts,
50
+ ...options,
49
51
  });
50
52
  if (r.error) return { ok: false, error: r.error.message || String(r.error) };
51
53
  if (r.status !== 0 && r.status !== null) {
@@ -0,0 +1,95 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const oxePlugins = require('./oxe-plugins.cjs');
5
+
6
+ const DEFAULT_COLORS = Object.freeze({
7
+ green: '\x1b[32m',
8
+ yellow: '\x1b[33m',
9
+ dim: '\x1b[2m',
10
+ red: '\x1b[31m',
11
+ reset: '\x1b[0m',
12
+ });
13
+
14
+ function defaultUseAnsiColors() {
15
+ if (process.env.NO_COLOR) return false;
16
+ if (process.env.FORCE_COLOR === '0') return false;
17
+ return process.stdout.isTTY === true;
18
+ }
19
+
20
+ /**
21
+ * Executa o subcomando `plugins` sem acoplar parsing e rendering ao CLI principal.
22
+ * @param {string[]} argv
23
+ * @param {{
24
+ * plugins?: typeof oxePlugins,
25
+ * cwd?: string,
26
+ * colors?: typeof DEFAULT_COLORS,
27
+ * useAnsiColors?: () => boolean,
28
+ * log?: (...args: unknown[]) => void,
29
+ * error?: (...args: unknown[]) => void,
30
+ * }} [options]
31
+ * @returns {number} exit code
32
+ */
33
+ function runPluginCommand(argv, options = {}) {
34
+ const plugins = options.plugins || oxePlugins;
35
+ const colors = options.colors || DEFAULT_COLORS;
36
+ const useAnsiColors = options.useAnsiColors || defaultUseAnsiColors;
37
+ const log = options.log || console.log;
38
+ const error = options.error || console.error;
39
+
40
+ let pluginsDir = options.cwd || process.cwd();
41
+ const pluginsArgv = argv.slice();
42
+ for (let i = 0; i < pluginsArgv.length; i++) {
43
+ if (pluginsArgv[i] === '--dir' && pluginsArgv[i + 1]) {
44
+ pluginsDir = path.resolve(pluginsArgv[i + 1]);
45
+ pluginsArgv.splice(i, 2);
46
+ i--;
47
+ }
48
+ }
49
+
50
+ const c = useAnsiColors();
51
+ const subCmd = pluginsArgv[0] || 'list';
52
+ const pluginTarget = pluginsArgv[1] || '';
53
+
54
+ if (subCmd === 'list') {
55
+ const result = plugins.loadPlugins(pluginsDir);
56
+ log(`\n ${c ? colors.green : ''}Plugins carregados:${c ? colors.reset : ''} ${result.plugins.length}`);
57
+ for (const plugin of result.plugins) {
58
+ log(` • ${plugin.name}${plugin.version ? ` (${plugin.version})` : ''} — hooks: ${Object.keys(plugin.hooks).join(', ')}`);
59
+ }
60
+ if (result.errors.length) {
61
+ log(`\n ${c ? colors.yellow : ''}Erros:${c ? colors.reset : ''}`);
62
+ for (const pluginError of result.errors) {
63
+ log(` ✗ ${pluginError.file}: ${pluginError.error}`);
64
+ }
65
+ }
66
+ return 0;
67
+ }
68
+
69
+ if (subCmd === 'install' && pluginTarget) {
70
+ const src = pluginTarget.startsWith('npm:') ? pluginTarget.slice(4) : pluginTarget;
71
+ const version = pluginsArgv[2] || '';
72
+ log(` Instalando plugin: ${src}${version ? `@${version}` : ''}...`);
73
+ const result = plugins.installNpmPlugin(pluginsDir, src, version || undefined);
74
+ if (result.ok) {
75
+ log(` ${c ? colors.green : ''}✓${c ? colors.reset : ''} Instalado em: ${result.path}`);
76
+ log(` ${c ? colors.dim : ''}Adicione ao .oxe/config.json: "plugins": [{ "source": "npm:${src}" }]${c ? colors.reset : ''}`);
77
+ return 0;
78
+ }
79
+ error(` ${c ? colors.red : ''}✗ Falha:${c ? colors.reset : ''} ${result.error}`);
80
+ return 1;
81
+ }
82
+
83
+ if (subCmd === 'remove' && pluginTarget) {
84
+ log(` ${c ? colors.yellow : ''}Remove "${pluginTarget}" de .oxe/config.json → plugins[] manualmente.${c ? colors.reset : ''}`);
85
+ log(` ${c ? colors.dim : ''}Arquivos npm: rm -rf .oxe/plugins/_npm/node_modules/${pluginTarget}${c ? colors.reset : ''}`);
86
+ return 0;
87
+ }
88
+
89
+ log(` ${c ? colors.yellow : ''}Uso: oxe-cc plugins list | install <npm-package> [version] | remove <id>${c ? colors.reset : ''}`);
90
+ return 0;
91
+ }
92
+
93
+ module.exports = {
94
+ runPluginCommand,
95
+ };
@@ -42,6 +42,68 @@
42
42
 
43
43
  const fs = require('fs');
44
44
  const path = require('path');
45
+ const {
46
+ resolvePackageManagerInvocation,
47
+ runPackageManagerSync,
48
+ } = require('./oxe-process.cjs');
49
+
50
+ const MAX_NPM_PACKAGE_NAME_LENGTH = 214;
51
+ const MAX_NPM_VERSION_LENGTH = 128;
52
+ const NPM_NAME_PART = /^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$/;
53
+ const NPM_VERSION_SPEC = /^[0-9A-Za-z*+._~^<>=-]+$/;
54
+
55
+ /**
56
+ * Valida um nome de pacote npm sem aceitar URLs, paths ou option injection.
57
+ * @param {unknown} pkgName
58
+ * @returns {{ valid: boolean, error: string }}
59
+ */
60
+ function validateNpmPackageName(pkgName) {
61
+ if (typeof pkgName !== 'string' || pkgName.length === 0) {
62
+ return { valid: false, error: 'Nome do pacote npm é obrigatório' };
63
+ }
64
+ if (pkgName !== pkgName.trim() || pkgName.length > MAX_NPM_PACKAGE_NAME_LENGTH) {
65
+ return { valid: false, error: 'Nome do pacote npm inválido' };
66
+ }
67
+
68
+ const parts = pkgName.startsWith('@')
69
+ ? pkgName.slice(1).split('/')
70
+ : [pkgName];
71
+ if (parts.length !== (pkgName.startsWith('@') ? 2 : 1) || parts.some((part) => !NPM_NAME_PART.test(part))) {
72
+ return { valid: false, error: 'Nome do pacote npm inválido' };
73
+ }
74
+ return { valid: true, error: '' };
75
+ }
76
+
77
+ /**
78
+ * Valida seletores npm usuais (versão exata, range sem espaços ou dist-tag).
79
+ * @param {unknown} version
80
+ * @returns {{ valid: boolean, error: string }}
81
+ */
82
+ function validateNpmVersion(version) {
83
+ if (version === undefined || version === null || version === '') {
84
+ return { valid: true, error: '' };
85
+ }
86
+ if (
87
+ typeof version !== 'string' ||
88
+ version !== version.trim() ||
89
+ version.length > MAX_NPM_VERSION_LENGTH ||
90
+ version.startsWith('-') ||
91
+ !NPM_VERSION_SPEC.test(version)
92
+ ) {
93
+ return { valid: false, error: 'Versão ou tag npm inválida' };
94
+ }
95
+ return { valid: true, error: '' };
96
+ }
97
+
98
+ /**
99
+ * No Windows, scripts `.cmd` exigem shell. Para manter `shell: false`, executa
100
+ * diretamente o npm-cli.js com o processo Node atual.
101
+ * @param {{ platform?: string, env?: NodeJS.ProcessEnv, nodeExecutable?: string }} [options]
102
+ * @returns {{ command: string, argsPrefix: string[] }}
103
+ */
104
+ function resolveNpmInvocation(options = {}) {
105
+ return resolvePackageManagerInvocation('npm', options);
106
+ }
45
107
 
46
108
  /**
47
109
  * @typedef {{
@@ -316,15 +378,41 @@ function resolvePluginSources(projectRoot, pluginsSources) {
316
378
  * @param {string} [version]
317
379
  * @returns {{ ok: boolean, path: string, error: string }}
318
380
  */
319
- function installNpmPlugin(projectRoot, pkgName, version) {
381
+ function installNpmPlugin(projectRoot, pkgName, version, options = {}) {
382
+ const packageValidation = validateNpmPackageName(pkgName);
383
+ if (!packageValidation.valid) {
384
+ return { ok: false, path: '', error: packageValidation.error };
385
+ }
386
+ const versionValidation = validateNpmVersion(version);
387
+ if (!versionValidation.valid) {
388
+ return { ok: false, path: '', error: versionValidation.error };
389
+ }
390
+
320
391
  const npmDir = path.join(projectRoot, '.oxe', 'plugins', '_npm');
321
392
  try {
322
393
  if (!fs.existsSync(npmDir)) {
323
394
  fs.mkdirSync(npmDir, { recursive: true });
324
395
  }
325
396
  const spec = version ? `${pkgName}@${version}` : pkgName;
326
- const { execSync } = require('child_process');
327
- execSync(`npm install --prefix "${npmDir}" ${spec}`, { stdio: 'pipe', timeout: 60000 });
397
+ const run = options.runPackageManagerSync || runPackageManagerSync;
398
+ const runOptions = { ...options };
399
+ delete runOptions.runPackageManagerSync;
400
+ const child = run('npm', [
401
+ 'install',
402
+ '--prefix',
403
+ npmDir,
404
+ '--',
405
+ spec,
406
+ ], {
407
+ stdio: 'pipe',
408
+ timeout: 60000,
409
+ ...runOptions,
410
+ });
411
+ if (child.error) throw child.error;
412
+ if (child.status !== 0) {
413
+ const stderr = child.stderr ? String(child.stderr).trim() : '';
414
+ throw new Error(stderr || `npm install terminou com código ${child.status}`);
415
+ }
328
416
  return { ok: true, path: path.join(npmDir, 'node_modules', pkgName), error: '' };
329
417
  } catch (e) {
330
418
  return { ok: false, path: '', error: e.message || String(e) };
@@ -338,4 +426,7 @@ module.exports = {
338
426
  initPluginsDir,
339
427
  resolvePluginSources,
340
428
  installNpmPlugin,
429
+ validateNpmPackageName,
430
+ validateNpmVersion,
431
+ resolveNpmInvocation,
341
432
  };