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.
- package/.github/dependabot.yml +31 -0
- package/.github/workflows/ci.yml +141 -56
- package/.github/workflows/release.yml +114 -89
- package/CHANGELOG.md +866 -754
- package/README.md +600 -736
- package/bin/lib/oxe-agent-install.cjs +299 -284
- package/bin/lib/oxe-artifact-catalog.cjs +376 -0
- package/bin/lib/oxe-command-registry.cjs +31 -0
- package/bin/lib/oxe-context-engine.cjs +11 -11
- package/bin/lib/oxe-core-command-handlers.cjs +82 -0
- package/bin/lib/oxe-dashboard.cjs +140 -140
- package/bin/lib/oxe-manifest.cjs +20 -20
- package/bin/lib/oxe-npm-version.cjs +6 -4
- package/bin/lib/oxe-plugin-cli.cjs +95 -0
- package/bin/lib/oxe-plugins.cjs +94 -3
- package/bin/lib/oxe-process.cjs +67 -0
- package/bin/lib/oxe-project-health.cjs +2846 -2781
- package/bin/lib/oxe-runtime-semantics.cjs +68 -69
- package/bin/oxe-cc.js +369 -353
- package/docs/INTEGRATION.md +182 -0
- package/docs/QUALITY-GATES.md +46 -0
- package/docs/RELEASE-READINESS.md +86 -61
- package/docs/RUNTIME-SMOKE-MATRIX.md +137 -135
- package/docs/oxe-artifact-map.html +1172 -0
- package/lib/sdk/index.cjs +20 -0
- package/lib/sdk/index.d.ts +971 -876
- package/lib/sdk/index.types.ts +933 -0
- package/oxe/templates/PLUGINS.md +8 -1
- package/oxe/templates/STATE-REFERENCE.md +125 -0
- package/oxe/templates/STATE.md +11 -121
- package/oxe/workflows/help.md +2 -0
- package/package.json +129 -108
- package/packages/runtime/package.json +18 -18
- package/packages/runtime/src/evidence/evidence-store.ts +2 -2
- package/packages/runtime/src/scheduler/multi-agent-coordinator.ts +728 -728
- package/packages/runtime/src/workspace/strategies/git-worktree.ts +24 -24
- package/packages/runtime/tsconfig.json +8 -2
- package/vscode-extension/.vscodeignore +2 -0
- package/vscode-extension/package.json +193 -185
- 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
|
{
|
package/bin/lib/oxe-manifest.cjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
+
};
|
package/bin/lib/oxe-plugins.cjs
CHANGED
|
@@ -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
|
|
327
|
-
|
|
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
|
};
|