import { writeInfo, withOptionalLocation } from './cliOutputs'; import { runSddAutoSync, runSddLearn, runSddSyncDocs, runSddEvidenceScaffold, runSddStateSync, readSddStatus, evaluateSddPolicy, openSddSession, refreshSddSession, closeSddSession, } from '../sdd'; import { evaluateAiGate } from '../gate/evaluateAiGate'; import { readLifecyclePolicyValidationSnapshot } from './policyValidationSnapshot'; import { runOpenSpecBootstrap } from './openSpecBootstrap'; import { buildPreWriteAutomationTrace, type PreWriteAutomationTrace } from './preWriteAutomation'; import { runEnterpriseAiGateCheck } from '../mcp/aiGateCheck'; import { resolvePreWriteEnforcement, type PreWriteEnforcementResolution, } from '../policy/preWriteEnforcement'; import { readLifecycleExperimentalFeaturesSnapshot } from './experimentalFeaturesSnapshot'; import { GitService } from '../git/GitService'; import { evaluatePreWriteAgentGuard } from './preWriteLease'; import { type ParsedArgs, type LifecycleCliDependencies, resolvePreWriteNextAction, resolvePreWriteBlockedRemediation, buildPreWriteValidationEnvelope, buildPreWriteValidationPanel, PRE_WRITE_OPENSPEC_AUTOREMEDIABLE_CODES, resolveAiGateViolationLocation, resolveSddDecisionLocation, type PreWriteOpenSpecBootstrapTrace, buildPreWriteExperimentalDisabledResult, buildSddExperimentalEnableAdvisoryCommand, runRawPreWriteAiGateCheck, buildPreWriteExperimentalEnableAdvisoryCommand, prioritizePreWriteAiGateViolations, } from './cli'; export const runSddCommand = async (parsed: ParsedArgs, activeDependencies: LifecycleCliDependencies): Promise => { if (parsed.sddCommand === 'status') { const sddStatus = readSddStatus(); if (parsed.json) { writeInfo(JSON.stringify(sddStatus, null, 2)); } else { writeInfo(`[pumuki][sdd] repo: ${sddStatus.repoRoot}`); writeInfo( `[pumuki][sdd] openspec: installed=${sddStatus.openspec.installed ? 'yes' : 'no'} version=${sddStatus.openspec.version ?? 'unknown'}` ); writeInfo( `[pumuki][sdd] openspec compatibility: compatible=${sddStatus.openspec.compatible ? 'yes' : 'no'} minimum=${sddStatus.openspec.minimumVersion} recommended=${sddStatus.openspec.recommendedVersion} parsed=${sddStatus.openspec.parsedVersion ?? 'unknown'}` ); writeInfo( `[pumuki][sdd] openspec project initialized: ${sddStatus.openspec.projectInitialized ? 'yes' : 'no'}` ); writeInfo( `[pumuki][sdd] session: active=${sddStatus.session.active ? 'yes' : 'no'} valid=${sddStatus.session.valid ? 'yes' : 'no'} change=${sddStatus.session.changeId ?? 'none'}` ); if (typeof sddStatus.session.remainingSeconds === 'number') { writeInfo( `[pumuki][sdd] session remaining seconds: ${sddStatus.session.remainingSeconds}` ); } } return 0; } if (parsed.sddCommand === 'guard') { const requestedStage = parsed.sddStage ?? 'PRE_WRITE'; if (requestedStage !== 'PRE_WRITE') { if (parsed.json) { writeInfo(JSON.stringify({ allowed: false, stage: requestedStage, code: 'PRE_WRITE_AGENT_GUARD_UNSUPPORTED_STAGE', message: 'Agent pre-write guard only supports PRE_WRITE.', }, null, 2)); } else { writeInfo('[pumuki][sdd] guard allowed=no code=PRE_WRITE_AGENT_GUARD_UNSUPPORTED_STAGE'); writeInfo('[pumuki][sdd] Agent pre-write guard only supports PRE_WRITE.'); } return 1; } const guard = evaluatePreWriteAgentGuard({ repoRoot: process.cwd(), git: new GitService(), }); if (parsed.json) { writeInfo(JSON.stringify(guard, null, 2)); } else { writeInfo( `[pumuki][sdd] guard stage=PRE_WRITE allowed=${guard.allowed ? 'yes' : 'no'} code=${guard.code}` ); writeInfo(`[pumuki][sdd] ${guard.message}`); if (!guard.allowed) { writeInfo(`[pumuki][sdd] solution: ${guard.expected_fix}`); } } return guard.allowed ? 0 : 1; } if (parsed.sddCommand === 'validate') { const requestedStage = parsed.sddStage ?? 'PRE_COMMIT'; const preWriteEnforcement = requestedStage === 'PRE_WRITE' ? resolvePreWriteEnforcement() : { mode: 'strict', source: 'default', blocking: true, layer: 'experimental', activationVariable: 'PUMUKI_EXPERIMENTAL_PRE_WRITE', legacyActivationVariable: 'PUMUKI_PREWRITE_ENFORCEMENT', } satisfies PreWriteEnforcementResolution; const policyValidation = readLifecyclePolicyValidationSnapshot(process.cwd()); const experimentalFeatures = readLifecycleExperimentalFeaturesSnapshot(); if (requestedStage === 'PRE_WRITE' && preWriteEnforcement.mode === 'off') { const disabledResult = buildPreWriteExperimentalDisabledResult({ stage: requestedStage, status: readSddStatus(process.cwd()), }); if (parsed.json) { writeInfo( JSON.stringify( { sdd: disabledResult, pre_write_enforcement: preWriteEnforcement, experimental_features: experimentalFeatures, policy_validation: policyValidation, automation: { attempted: false, actions: [], }, bootstrap: { enabled: false, attempted: false, status: 'SKIPPED', actions: [], details: 'PRE_WRITE experimental/off-by-env.', }, next_action: { reason: 'PRE_WRITE_EXPERIMENTAL_DISABLED', command: buildPreWriteExperimentalEnableAdvisoryCommand(process.cwd()), }, }, null, 2 ) ); } else { writeInfo( `[pumuki][sdd] stage=${disabledResult.stage} allowed=yes code=${disabledResult.decision.code}` ); writeInfo( withOptionalLocation( `[pumuki][sdd] ${disabledResult.decision.message}`, resolveSddDecisionLocation(disabledResult) ) ); writeInfo( `[pumuki][sdd] pre-write enforcement: mode=${preWriteEnforcement.mode} source=${preWriteEnforcement.source} blocking=no` ); writeInfo( `[pumuki][sdd] next action (PRE_WRITE_EXPERIMENTAL_DISABLED): ${buildPreWriteExperimentalEnableAdvisoryCommand(process.cwd())}` ); } return 0; } let result = evaluateSddPolicy({ stage: requestedStage, }); const preWriteAutoBootstrapEnabled = process.env.PUMUKI_PREWRITE_AUTO_BOOTSTRAP !== '0'; const preWriteBootstrapTrace: PreWriteOpenSpecBootstrapTrace = { enabled: preWriteAutoBootstrapEnabled, attempted: false, status: 'SKIPPED', actions: [], }; if ( result.stage === 'PRE_WRITE' && !result.decision.allowed && PRE_WRITE_OPENSPEC_AUTOREMEDIABLE_CODES.has(result.decision.code) ) { if (preWriteAutoBootstrapEnabled) { preWriteBootstrapTrace.attempted = true; try { const bootstrap = runOpenSpecBootstrap({ repoRoot: process.cwd(), }); preWriteBootstrapTrace.status = 'OK'; preWriteBootstrapTrace.actions = [...bootstrap.actions]; if (bootstrap.skippedReason) { preWriteBootstrapTrace.details = `skipped=${bootstrap.skippedReason}`; } } catch (error) { preWriteBootstrapTrace.status = 'FAILED'; preWriteBootstrapTrace.details = error instanceof Error ? error.message : 'Unknown OpenSpec bootstrap error'; } result = evaluateSddPolicy({ stage: requestedStage, }); } else { preWriteBootstrapTrace.details = 'auto bootstrap disabled via PUMUKI_PREWRITE_AUTO_BOOTSTRAP=0'; } } const shouldEvaluateAiGate = result.stage === 'PRE_WRITE'; let aiGate: ReturnType | null = shouldEvaluateAiGate ? runEnterpriseAiGateCheck({ repoRoot: process.cwd(), stage: result.stage, requireMcpReceipt: true, }).result : null; const automationTrace: PreWriteAutomationTrace = { attempted: false, actions: [], }; if (result.stage === 'PRE_WRITE' && aiGate) { const auto = await buildPreWriteAutomationTrace({ repoRoot: process.cwd(), sdd: result, aiGate, runPlatformGate: activeDependencies.runPlatformGate, }); aiGate = auto.aiGate; automationTrace.attempted = auto.trace.attempted; automationTrace.actions = auto.trace.actions; } const rawPreWriteAiGate = result.stage === 'PRE_WRITE' && aiGate ? runRawPreWriteAiGateCheck({ repoRoot: process.cwd(), requireMcpReceipt: true, }) : null; const nextAction = resolvePreWriteNextAction({ sdd: result, aiGate: rawPreWriteAiGate ?? aiGate, }); const effectiveAiGate = rawPreWriteAiGate ?? aiGate; let reportedAiGate = effectiveAiGate ? prioritizePreWriteAiGateViolations(effectiveAiGate, nextAction) : null; if ( result.stage === 'PRE_WRITE' && result.decision.allowed && reportedAiGate?.allowed ) { try { const lease = activeDependencies.writePreWriteLease({ repoRoot: process.cwd(), git: new GitService(), allowExistingCodeChanges: true, }); if (!lease.valid) { reportedAiGate = { ...reportedAiGate, status: 'BLOCKED', allowed: false, violations: [ ...reportedAiGate.violations, { code: lease.code, severity: 'ERROR', message: lease.message, }, ], }; } } catch (error) { reportedAiGate = { ...reportedAiGate, status: 'BLOCKED', allowed: false, violations: [ ...reportedAiGate.violations, { code: 'PRE_WRITE_LEASE_WRITE_FAILED', severity: 'ERROR', message: error instanceof Error ? `PRE_WRITE validation passed but could not write the required lease: ${error.message}` : 'PRE_WRITE validation passed but could not write the required lease.', }, ], }; } } const sddExperimentalNextAction = !aiGate && result.decision.code === 'SDD_EXPERIMENTAL_DISABLED' ? { reason: 'SDD_EXPERIMENTAL_DISABLED', command: buildSddExperimentalEnableAdvisoryCommand(result.stage, process.cwd()), } : undefined; if (parsed.json) { writeInfo( JSON.stringify( reportedAiGate ? buildPreWriteValidationEnvelope( result, reportedAiGate, preWriteEnforcement, experimentalFeatures, policyValidation, automationTrace, preWriteBootstrapTrace, nextAction ) : { ...result, experimental_features: experimentalFeatures, policy_validation: policyValidation, next_action: sddExperimentalNextAction, }, null, 2 ) ); } else { writeInfo( `[pumuki][sdd] stage=${result.stage} allowed=${result.decision.allowed ? 'yes' : 'no'} code=${result.decision.code}` ); writeInfo( `[pumuki][sdd] policy-as-code: PRE_COMMIT=${policyValidation.stages.PRE_COMMIT.validationCode ?? 'n/a'} strict=${policyValidation.stages.PRE_COMMIT.strict ? 'yes' : 'no'} ` + `PRE_PUSH=${policyValidation.stages.PRE_PUSH.validationCode ?? 'n/a'} strict=${policyValidation.stages.PRE_PUSH.strict ? 'yes' : 'no'} ` + `CI=${policyValidation.stages.CI.validationCode ?? 'n/a'} strict=${policyValidation.stages.CI.strict ? 'yes' : 'no'}` ); writeInfo( `[pumuki][sdd] experimental: SDD=${experimentalFeatures.features.sdd.mode} source=${experimentalFeatures.features.sdd.source} layer=${experimentalFeatures.features.sdd.layer} blocking=${experimentalFeatures.features.sdd.blocking ? 'yes' : 'no'} env=${experimentalFeatures.features.sdd.activationVariable}` ); writeInfo( withOptionalLocation( `[pumuki][sdd] ${result.decision.message}`, resolveSddDecisionLocation(result) ) ); if (result.validation) { writeInfo( `[pumuki][sdd] validation: ok=${result.validation.ok ? 'yes' : 'no'} failed=${result.validation.totals.failed} errors=${result.validation.issues.errors}` ); } if (result.stage === 'PRE_WRITE') { writeInfo( `[pumuki][sdd] openspec auto-bootstrap: enabled=${preWriteBootstrapTrace.enabled ? 'yes' : 'no'} attempted=${preWriteBootstrapTrace.attempted ? 'yes' : 'no'} status=${preWriteBootstrapTrace.status} actions=${preWriteBootstrapTrace.actions.join(',') || 'none'}` ); writeInfo( `[pumuki][sdd] pre-write enforcement: mode=${preWriteEnforcement.mode} source=${preWriteEnforcement.source} blocking=${preWriteEnforcement.blocking ? 'yes' : 'no'}` ); if (preWriteBootstrapTrace.details) { writeInfo( `[pumuki][sdd] openspec auto-bootstrap details: ${preWriteBootstrapTrace.details}` ); } } if (reportedAiGate) { writeInfo(buildPreWriteValidationPanel({ sdd: result, aiGate: reportedAiGate, automation: automationTrace, })); writeInfo( `[pumuki][ai-gate] stage=${reportedAiGate.stage} status=${reportedAiGate.status} violations=${reportedAiGate.violations.length}` ); for (const violation of reportedAiGate.violations) { writeInfo( withOptionalLocation( `[pumuki][ai-gate] ${violation.code}: ${violation.message}`, resolveAiGateViolationLocation(violation.code) ) ); } } if (nextAction) { writeInfo(`[pumuki][sdd] next action (${nextAction.reason}): ${nextAction.command}`); } if (sddExperimentalNextAction) { writeInfo( `[pumuki][sdd] next action (${sddExperimentalNextAction.reason}): ${sddExperimentalNextAction.command}` ); } } if (result.stage === 'PRE_WRITE' && aiGate) { activeDependencies.emitAuditSummaryNotificationFromAiGate({ repoRoot: process.cwd(), stage: result.stage, aiGateResult: reportedAiGate ?? aiGate, }); } if (!result.decision.allowed && preWriteEnforcement.blocking) { activeDependencies.emitGateBlockedNotification({ repoRoot: process.cwd(), stage: result.stage, totalViolations: reportedAiGate?.violations.length ?? aiGate?.violations.length ?? 0, causeCode: result.decision.code, causeMessage: result.decision.message, remediation: resolvePreWriteBlockedRemediation({ causeCode: result.decision.code, nextAction, }), }); return 1; } if (reportedAiGate && !reportedAiGate.allowed && preWriteEnforcement.blocking) { const firstViolation = reportedAiGate.violations[0]; const causeCode = firstViolation?.code ?? 'AI_GATE_BLOCKED'; const causeMessage = firstViolation?.message ?? 'AI gate blocked PRE_WRITE stage.'; activeDependencies.emitGateBlockedNotification({ repoRoot: process.cwd(), stage: result.stage, totalViolations: reportedAiGate.violations.length, causeCode, causeMessage, remediation: resolvePreWriteBlockedRemediation({ causeCode, nextAction, }), blockingCauses: reportedAiGate.violations.map((violation) => ({ code: violation.code, message: violation.message, ruleId: violation.code, file: resolveAiGateViolationLocation(violation.code), remediation: resolvePreWriteBlockedRemediation({ causeCode: violation.code, nextAction, }), })), }); return 1; } return 0; } if (parsed.sddCommand === 'session') { if (parsed.sddSessionAction === 'open') { const session = openSddSession({ changeId: parsed.sddChangeId ?? '', ttlMinutes: parsed.sddTtlMinutes, }); if (parsed.json) { writeInfo(JSON.stringify(session, null, 2)); } else { writeInfo( `[pumuki][sdd] session opened: change=${session.changeId} ttlMinutes=${session.ttlMinutes ?? 'unknown'} valid=${session.valid ? 'yes' : 'no'}` ); } return 0; } if (parsed.sddSessionAction === 'refresh') { const session = refreshSddSession({ ttlMinutes: parsed.sddTtlMinutes, }); if (parsed.json) { writeInfo(JSON.stringify(session, null, 2)); } else { writeInfo( `[pumuki][sdd] session refreshed: change=${session.changeId ?? 'none'} ttlMinutes=${session.ttlMinutes ?? 'unknown'} valid=${session.valid ? 'yes' : 'no'}` ); } return 0; } const session = closeSddSession(); if (parsed.json) { writeInfo(JSON.stringify(session, null, 2)); } else { writeInfo('[pumuki][sdd] session closed'); } return 0; } if (parsed.sddCommand === 'sync-docs') { const syncResult = runSddSyncDocs({ repoRoot: process.cwd(), dryRun: parsed.sddSyncDocsDryRun === true, change: parsed.sddSyncDocsChange, stage: parsed.sddSyncDocsStage, task: parsed.sddSyncDocsTask, fromEvidencePath: parsed.sddSyncDocsFromEvidence, }); if (parsed.json) { writeInfo(JSON.stringify(syncResult, null, 2)); } else { writeInfo( `[pumuki][sdd] sync-docs dry_run=${syncResult.dryRun ? 'yes' : 'no'} updated=${syncResult.updated ? 'yes' : 'no'} files=${syncResult.files.length} change=${syncResult.context.change ?? 'none'} stage=${syncResult.context.stage ?? 'none'} task=${syncResult.context.task ?? 'none'} from_evidence=${syncResult.context.fromEvidencePath ?? 'default'}` ); for (const file of syncResult.files) { writeInfo( `[pumuki][sdd] file=${file.path} updated=${file.updated ? 'yes' : 'no'} before=${file.beforeDigest} after=${file.afterDigest}` ); if (file.updated) { writeInfo(file.diffMarkdown); } } } return 0; } if (parsed.sddCommand === 'learn') { const learnResult = runSddLearn({ repoRoot: process.cwd(), dryRun: parsed.sddLearnDryRun === true, change: parsed.sddLearnChange, stage: parsed.sddLearnStage, task: parsed.sddLearnTask, fromEvidencePath: parsed.sddLearnFromEvidence, }); if (parsed.json) { writeInfo(JSON.stringify(learnResult, null, 2)); } else { writeInfo( `[pumuki][sdd] learn dry_run=${learnResult.dryRun ? 'yes' : 'no'} change=${learnResult.context.change} stage=${learnResult.context.stage ?? 'none'} task=${learnResult.context.task ?? 'none'} from_evidence=${learnResult.context.fromEvidencePath ?? 'default'} written=${learnResult.learning.written ? 'yes' : 'no'} digest=${learnResult.learning.digest}` ); writeInfo( `[pumuki][sdd] learning_path=${learnResult.learning.path} failed=${learnResult.learning.artifact.failed_patterns.length} successful=${learnResult.learning.artifact.successful_patterns.length} anomalies=${learnResult.learning.artifact.gate_anomalies.length} updates=${learnResult.learning.artifact.rule_updates.length}` ); } return 0; } if (parsed.sddCommand === 'auto-sync') { const autoSyncResult = runSddAutoSync({ repoRoot: process.cwd(), dryRun: parsed.sddAutoSyncDryRun === true, change: parsed.sddAutoSyncChange, stage: parsed.sddAutoSyncStage, task: parsed.sddAutoSyncTask, fromEvidencePath: parsed.sddAutoSyncFromEvidence, }); if (parsed.json) { writeInfo(JSON.stringify(autoSyncResult, null, 2)); } else { writeInfo( `[pumuki][sdd] auto-sync dry_run=${autoSyncResult.dryRun ? 'yes' : 'no'} change=${autoSyncResult.context.change} stage=${autoSyncResult.context.stage ?? 'none'} task=${autoSyncResult.context.task ?? 'none'} from_evidence=${autoSyncResult.context.fromEvidencePath ?? 'default'} updated=${autoSyncResult.syncDocs.updated ? 'yes' : 'no'} files=${autoSyncResult.syncDocs.files.length} learning_written=${autoSyncResult.learning.written ? 'yes' : 'no'}` ); writeInfo( `[pumuki][sdd] learning_path=${autoSyncResult.learning.path} digest=${autoSyncResult.learning.digest}` ); for (const file of autoSyncResult.syncDocs.files) { writeInfo( `[pumuki][sdd] file=${file.path} updated=${file.updated ? 'yes' : 'no'} before=${file.beforeDigest} after=${file.afterDigest}` ); } } return 0; } if (parsed.sddCommand === 'evidence') { const evidenceResult = runSddEvidenceScaffold({ repoRoot: process.cwd(), dryRun: parsed.sddEvidenceDryRun === true, scenarioId: parsed.sddEvidenceScenarioId, testCommand: parsed.sddEvidenceTestCommand, testStatus: parsed.sddEvidenceTestStatus, testOutputPath: parsed.sddEvidenceTestOutput, fromEvidencePath: parsed.sddEvidenceFromEvidence, }); if (parsed.json) { writeInfo(JSON.stringify(evidenceResult, null, 2)); } else { writeInfo( `[pumuki][sdd] evidence dry_run=${evidenceResult.dryRun ? 'yes' : 'no'} scenario=${evidenceResult.context.scenarioId} status=${evidenceResult.context.testStatus} output=${evidenceResult.output.path} written=${evidenceResult.output.written ? 'yes' : 'no'}` ); writeInfo( `[pumuki][sdd] test_command=${evidenceResult.context.testCommand} test_output=${evidenceResult.context.testOutputPath ?? 'none'} from_evidence=${evidenceResult.context.fromEvidencePath ?? 'default'}` ); writeInfo( `[pumuki][sdd] source_digest=${evidenceResult.artifact.ai_evidence.digest} generated_at=${evidenceResult.artifact.generated_at}` ); } return 0; } if (parsed.sddCommand === 'state-sync') { const stateSyncResult = runSddStateSync({ repoRoot: process.cwd(), dryRun: parsed.sddStateSyncDryRun === true, scenarioId: parsed.sddStateSyncScenarioId, status: parsed.sddStateSyncStatus, fromEvidencePath: parsed.sddStateSyncFromEvidence, boardPath: parsed.sddStateSyncBoardPath, force: parsed.sddStateSyncForce === true, }); if (parsed.json) { writeInfo(JSON.stringify(stateSyncResult, null, 2)); } else { writeInfo( `[pumuki][sdd] state-sync dry_run=${stateSyncResult.dryRun ? 'yes' : 'no'} scenario=${stateSyncResult.context.scenarioId} status=${stateSyncResult.context.desiredStatus} updated=${stateSyncResult.board.updated ? 'yes' : 'no'} conflict=${stateSyncResult.board.conflict ? 'yes' : 'no'} written=${stateSyncResult.board.written ? 'yes' : 'no'}` ); writeInfo( `[pumuki][sdd] state-sync source=${stateSyncResult.context.fromEvidencePath ?? 'default'} board=${stateSyncResult.context.boardPath} entries=${stateSyncResult.board.entries} decision=${stateSyncResult.decision.code}` ); if (stateSyncResult.decision.nextAction) { writeInfo(`[pumuki][sdd] state-sync next action: ${stateSyncResult.decision.nextAction}`); } } if (!stateSyncResult.decision.allowed) { return 1; } return 0; } return 0; };