import { analyzeRepository } from './kernel/analyze.mjs'; import { BacklogService } from './backlog/service.mjs'; import { createClaudeAdapter } from './adapters/claude.mjs'; import { createCodexAdapter } from './adapters/codex.mjs'; import { CheckService } from './checks/service.mjs'; import { classifyRecoverability, createLocalCanonical, planLocalCanonical } from './canonical/local.mjs'; import { openControlPlane } from './control-plane/service.mjs'; import { spawnSync } from 'node:child_process'; import { diagnoseProject } from './kernel/doctor.mjs'; import { asErrorRecord, TorchError } from './kernel/errors.mjs'; import { inspectRepository } from './kernel/git.mjs'; import { inspectSpecifications } from './kernel/specifications.mjs'; import { installProject, planInstall, uninstallProject } from './kernel/install.mjs'; import { IntegrationService } from './integration/service.mjs'; import { analyzeCombatrigFleet } from './importers/combatrig.mjs'; import { proposeDomains } from './kernel/domains.mjs'; import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { writeNewFile } from './kernel/files.mjs'; import { createWorktrees, planWorktrees } from './kernel/worktrees.mjs'; import { planFleetDown, planFleetUp, startFleet, stopFleet } from './runtime/lifecycle.mjs'; import { ResourceService } from './resources/service.mjs'; import { CANDIDATE_ACCEPTANCE_SCENARIOS, createVersionService } from './self-host/service.mjs'; const HELP = `TORCH — portable agent fleet Usage: torch init [--repo ] [--spec ] [--json] torch analyze [--repo ] [--spec ] [--json] torch design [--repo ] [--spec ] [--output ] [--json] torch domains [--repo ] [--spec ] [--output ] [--json] torch install --proposal [--dry-run] [--yes] [--json] torch worktrees [--parent ] [--dry-run] [--yes] [--json] torch up [--fresh] [--dry-run] [--yes] [--json] torch down [--dry-run] [--yes] [--json] torch identity --area [--json] torch agents [--json] torch agent --area [--json] torch roster [--json] torch who-owns (--path | --capability ) [--json] torch neighbours --area [--json] torch message --from --to --body [--task ] [--path ] [--commit ] [--json] torch inbox --area [--unacknowledged] [--limit ] [--json] torch ack --area --message [--json] torch status --area --state [--summary ] [--runtime ] [--session ] [--task ] [--json] torch complete --area --summary [--task ] [--evidence ] [--commit ] [--json] torch blocked --area --summary [--task ] [--evidence ] [--path ] [--commit ] [--json] torch coordinate --from --body [--with ] [--task ] [--path ] [--json] torch handoff --from [--to ] (--path | --task ) --reason [--json] torch backlog list [--state ] [--owner ] [--json] torch backlog get --task [--json] torch backlog create --area --title --description --accept [--priority ] [--domains ] [--depends ] [--json] torch backlog transition --task --area --state --revision [--owner ] [--evidence ] [--commit ] [--integration ] [--reason ] [--note ] [--json] torch checks list [--json] torch checks receipts [--commit ] [--id ] [--area ] [--json] torch checks plan --id --area [--json] torch checks run --id --area --yes [--json] torch resources list [--json] torch resources status --id [--json] torch resources acquire --id --area [--json] torch resources release --id --area [--json] torch resources cancel --id --area [--json] torch integrate list [--state ] [--json] torch integrate request --area [--commit ] [--json] torch integrate evaluate --request [--json] torch integrate authorize --request --area [--json] torch integrate plan --request --area [--json] torch integrate land --request --area --yes [--json] torch canonical plan [--json] torch canonical create --yes [--json] torch recoverability [--commit ] [--json] torch candidate plan --source [--version ] [--json] torch candidate build --source [--version ] --yes [--json] torch candidate status [--json] torch candidate test --version [--json] torch upgrade --version [--dry-run] --yes [--json] torch rollback [--dry-run] --yes [--json] torch import combatrig --source [--output ] [--json] torch doctor [--json] torch uninstall [--dry-run] [--purge] [--json] `; function print(value, { json = false } = {}) { if (json || typeof value !== 'string') process.stdout.write(`${JSON.stringify(value, null, 2)}\n`); else process.stdout.write(`${value}\n`); } function optionValue(argv, name) { const direct = argv.find((arg) => arg.startsWith(`${name}=`)); if (direct) return direct.slice(name.length + 1); const index = argv.indexOf(name); return index >= 0 ? argv[index + 1] : undefined; } function optionValues(argv, name) { const values = []; for (let index = 0; index < argv.length; index += 1) { if (argv[index].startsWith(`${name}=`)) values.push(argv[index].slice(name.length + 1)); else if (argv[index] === name && argv[index + 1] !== undefined) values.push(argv[index + 1]); } return values; } function commaList(value) { return (value ?? '').split(',').map((item) => item.trim()).filter(Boolean); } function loadProposal(cwd, proposalPath) { if (!proposalPath) { throw new TorchError('Installation requires an approved domain proposal from torch domains --output.', { code: 'PROPOSAL_REQUIRED', }); } try { return JSON.parse(readFileSync(resolve(cwd, proposalPath), 'utf8')); } catch (error) { throw new TorchError(`Cannot read domain proposal: ${proposalPath}`, { code: 'PROPOSAL_INVALID', details: error.message, }); } } function withControlPlane(repository, env, callback) { const controlPlane = openControlPlane({ repositoryRoot: repository.root, env }); try { return callback(controlPlane); } finally { controlPlane.close(); } } function runtimeAdapters() { return new Map([ ['claude', createClaudeAdapter()], ['codex', createCodexAdapter()], ]); } function runCandidateAcceptance(candidateRoot, spawn) { const checks = [ { id: 'test', args: ['test'] }, { id: 'lint', args: ['run', 'lint'] }, { id: 'syntax', args: ['run', 'check'] }, ].map((check) => { const result = spawn('npm', check.args, { cwd: candidateRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 300_000, }); return { id: check.id, status: result.status ?? (result.error ? 1 : 0), signal: result.signal ?? null, error: result.error?.message ?? null, stdout: result.stdout ?? '', }; }); const testOutput = checks.find((check) => check.id === 'test')?.stdout ?? ''; const evidenceByScenario = new Map([ ['init-analyze', ['SCN-init-read-only']], ['spec-aware-design', ['SCN-spec-fleet-design', 'SCN-cli-spec-design']], ['review-install-roster', ['SCN-cli-domain-review']], ['branches-worktrees', ['SCN-worktree-bootstrap']], ['runtime-identities', ['SCN-mixed-runtime']], ['durable-messaging', ['SCN-durable-message']], ['ownership-query', ['SCN-ownership-handoff']], ['unsafe-worktree-detection', ['SCN-worktree-purge-safety']], ['backlog-integration', ['SCN-backlog-lifecycle', 'SCN-native-integration']], ['resource-lifecycle', ['SCN-resource-fifo']], ['capture-stop-resume', ['SCN-fleet-fresh-resume']], ['detach-uninstall', ['SCN-install-doctor-purge']], ['combatrig-compatibility', ['SCN-combatrig-import', 'SCN-cli-combatrig-import']], ]); const scenarios = CANDIDATE_ACCEPTANCE_SCENARIOS.filter((scenario) => (evidenceByScenario.get(scenario) ?? []).every((marker) => testOutput.includes(marker))); const passed = checks.every((check) => check.status === 0 && !check.signal && !check.error) && scenarios.length === CANDIDATE_ACCEPTANCE_SCENARIOS.length; return { passed, scenarios, evidence: checks.map(({ stdout, ...check }) => ({ ...check, outputBytes: Buffer.byteLength(stdout), })), }; } export async function runCli(argv = process.argv.slice(2), { cwd = process.cwd(), env = process.env, spawn = spawnSync, } = {}) { const command = argv.find((arg) => !arg.startsWith('-')) ?? 'help'; const json = argv.includes('--json'); try { if (command === 'help' || argv.includes('--help') || argv.includes('-h')) { process.stdout.write(HELP); return 0; } if (command === 'candidate') { const operation = argv[1] ?? 'status'; const versions = createVersionService({ env }); if (operation === 'status') { print(versions.status(), { json }); return 0; } if (operation === 'test') { const version = optionValue(argv, '--version'); const status = versions.status(); const validationPath = resolve(status.root, 'versions', version ?? '', '.torch-validation.json'); if (!version || !status.installed.includes(version)) { throw new TorchError('Candidate test receipt requires an installed version', { code: 'VERSION_NOT_INSTALLED', details: { version: version ?? null }, }); } print(JSON.parse(readFileSync(validationPath, 'utf8')), { json }); return 0; } const source = optionValue(argv, '--source'); const version = optionValue(argv, '--version'); if (operation === 'plan') { print(versions.planCandidate({ source: resolve(cwd, source ?? ''), version }), { json }); return 0; } if (operation === 'build') { if (!argv.includes('--yes')) { throw new TorchError('Candidate build copies code and runs its acceptance suite. Review candidate plan, then re-run with --yes.', { code: 'APPROVAL_REQUIRED', }); } print(versions.installCandidate({ source: resolve(cwd, source ?? ''), version, validate: ({ candidateRoot }) => runCandidateAcceptance(candidateRoot, spawn), }), { json }); return 0; } throw new TorchError(`Unknown candidate operation: ${operation}`, { code: 'UNKNOWN_COMMAND' }); } if (command === 'upgrade') { const versions = createVersionService({ env }); const version = optionValue(argv, '--version'); const status = versions.status(); const plan = { action: 'upgrade', from: status.activeVersion, to: version ?? null, canProceed: Boolean(version && status.installed.includes(version)), mutationPerformed: false, }; if (argv.includes('--dry-run')) { print(plan, { json }); return plan.canProceed ? 0 : 1; } if (!argv.includes('--yes')) { throw new TorchError('Upgrade atomically changes the active TORCH version. Review --dry-run, then re-run with --yes.', { code: 'APPROVAL_REQUIRED', }); } print(versions.activate(version), { json }); return 0; } if (command === 'rollback') { const versions = createVersionService({ env }); const status = versions.status(); const plan = { action: 'rollback', from: status.activeVersion, to: status.previousVersion, canProceed: Boolean(status.previousVersion), mutationPerformed: false, }; if (argv.includes('--dry-run')) { print(plan, { json }); return plan.canProceed ? 0 : 1; } if (!argv.includes('--yes')) { throw new TorchError('Rollback atomically restores the previous TORCH version. Review --dry-run, then re-run with --yes.', { code: 'APPROVAL_REQUIRED', }); } print(versions.rollback(), { json }); return 0; } if (command === 'import') { const format = argv[1]; if (format !== 'combatrig') { throw new TorchError(`Unsupported fleet import format: ${format ?? ''}`, { code: 'IMPORT_FORMAT_UNSUPPORTED', }); } const source = optionValue(argv, '--source'); if (!source) throw new TorchError('COMBATRIG import requires --source', { code: 'IMPORT_SOURCE_REQUIRED' }); const sourceRepository = inspectRepository(resolve(cwd, source)); const proposal = analyzeCombatrigFleet({ repository: sourceRepository }); const output = optionValue(argv, '--output'); if (output) { const path = resolve(cwd, output); writeNewFile(path, `${JSON.stringify(proposal, null, 2)}\n`); print({ proposal, output: path, mutationPerformed: true }, { json }); } else print({ proposal, mutationPerformed: false }, { json }); return proposal.compatibility.readyForApproval ? 0 : 1; } const designCommand = ['init', 'analyze', 'domains', 'design'].includes(command); const repositoryRoot = designCommand ? resolve(cwd, optionValue(argv, '--repo') ?? '.') : cwd; const repository = inspectRepository(repositoryRoot); if (command === 'init' || command === 'analyze') { const specifications = inspectSpecifications(optionValues(argv, '--spec'), { cwd, repositoryRoot: repository.root, }); const analysis = analyzeRepository(repository, { specifications }); print({ command, repository, analysis, next: 'Run torch design with the same repository/specification inputs, review its proposal, then install.', }, { json }); return 0; } if (command === 'domains' || command === 'design') { const specifications = inspectSpecifications(optionValues(argv, '--spec'), { cwd, repositoryRoot: repository.root, }); const analysis = analyzeRepository(repository, { specifications }); const proposal = proposeDomains({ repository, analysis }); const output = optionValue(argv, '--output'); if (output) { const path = resolve(cwd, output); writeNewFile(path, `${JSON.stringify(proposal, null, 2)}\n`); print({ proposal, output: path, mutationPerformed: true }, { json }); } else { print({ proposal, mutationPerformed: false }, { json }); } return 0; } if (command === 'install') { const proposalPath = optionValue(argv, '--proposal'); if (argv.includes('--dry-run')) { const proposal = loadProposal(cwd, proposalPath); print({ ...planInstall({ repository, proposal, env }), proposal: resolve(cwd, proposalPath) }, { json }); return 0; } if (!argv.includes('--yes')) { throw new TorchError('Installation requires reviewed approval. Re-run with --dry-run, then --yes.', { code: 'APPROVAL_REQUIRED', }); } const proposal = loadProposal(cwd, proposalPath); print(installProject({ repository, proposal, env }), { json }); return 0; } if (command === 'doctor') { const diagnosis = diagnoseProject({ repository, env }); print(diagnosis, { json }); return diagnosis.healthy ? 0 : 1; } if (command === 'worktrees') { const parentOverride = optionValue(argv, '--parent'); if (argv.includes('--dry-run')) { const plan = planWorktrees({ repository, parentOverride }); print(plan, { json }); return plan.canProceed ? 0 : 1; } if (!argv.includes('--yes')) { throw new TorchError('Worktree creation requires a reviewed dry run. Re-run with --dry-run, then --yes.', { code: 'APPROVAL_REQUIRED', }); } print(createWorktrees({ repository, parentOverride }), { json }); return 0; } if (command === 'up') { const control = openControlPlane({ repositoryRoot: repository.root, env }); try { const adapters = runtimeAdapters(); const plan = planFleetUp({ repositoryRoot: repository.root, controlPlane: control, adapters, fresh: argv.includes('--fresh'), }); if (argv.includes('--dry-run')) { print(plan, { json }); return plan.canProceed ? 0 : 1; } if (!argv.includes('--yes')) { throw new TorchError('Fleet startup launches configured AI runtimes. Review --dry-run, then re-run with --yes.', { code: 'APPROVAL_REQUIRED', }); } print(startFleet({ plan, controlPlane: control, adapters, executor: (launch) => spawn(launch.command, launch.args, { cwd: launch.cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], }), }), { json }); return 0; } finally { control.close(); } } if (command === 'down') { const control = openControlPlane({ repositoryRoot: repository.root, env }); try { const adapters = runtimeAdapters(); const plan = planFleetDown({ repositoryRoot: repository.root, controlPlane: control, adapters }); if (argv.includes('--dry-run')) { print(plan, { json }); return plan.canProceed ? 0 : 1; } if (!argv.includes('--yes')) { throw new TorchError('Fleet wind-down stops configured runtime sessions. Review --dry-run, then re-run with --yes.', { code: 'APPROVAL_REQUIRED', }); } const runtimeStoppers = new Map([ ['claude', createClaudeAdapter({ runner: (executable, args) => spawn(executable, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], }), })], ['codex', createCodexAdapter()], ]); print(stopFleet({ plan, controlPlane: control, stopRuntime: (action) => runtimeStoppers.get(action.runtime) .stopSession({ runtimeSessionId: action.runtimeSessionId }), }), { json }); return 0; } finally { control.close(); } } if (command === 'identity') { print(withControlPlane(repository, env, (control) => control.identity(optionValue(argv, '--area'))), { json }); return 0; } if (command === 'agents') { print(withControlPlane(repository, env, (control) => ({ agents: control.listAgents() })), { json }); return 0; } if (command === 'agent') { print(withControlPlane(repository, env, (control) => control.getAgent(optionValue(argv, '--area'))), { json }); return 0; } if (command === 'roster') { print(withControlPlane(repository, env, (control) => control.getRoster()), { json }); return 0; } if (command === 'who-owns') { print(withControlPlane(repository, env, (control) => control.whoOwns({ path: optionValue(argv, '--path'), capability: optionValue(argv, '--capability'), })), { json }); return 0; } if (command === 'neighbours') { const areaId = optionValue(argv, '--area'); print(withControlPlane(repository, env, (control) => ({ areaId, neighbours: control.neighbours(areaId) })), { json }); return 0; } if (command === 'message') { print(withControlPlane(repository, env, (control) => control.sendMessage({ sender: optionValue(argv, '--from'), recipient: optionValue(argv, '--to'), body: optionValue(argv, '--body'), references: { task: optionValue(argv, '--task'), path: optionValue(argv, '--path'), commit: optionValue(argv, '--commit'), handoff: optionValue(argv, '--handoff'), }, })), { json }); return 0; } if (command === 'inbox') { print(withControlPlane(repository, env, (control) => ({ recipient: optionValue(argv, '--area'), messages: control.readMessages({ recipient: optionValue(argv, '--area'), unacknowledgedOnly: argv.includes('--unacknowledged'), limit: Number(optionValue(argv, '--limit') ?? 100), }), })), { json }); return 0; } if (command === 'ack') { print(withControlPlane(repository, env, (control) => control.ackMessage({ recipient: optionValue(argv, '--area'), messageId: optionValue(argv, '--message'), })), { json }); return 0; } if (command === 'status') { print(withControlPlane(repository, env, (control) => control.reportStatus({ areaId: optionValue(argv, '--area'), state: optionValue(argv, '--state'), summary: optionValue(argv, '--summary'), runtime: optionValue(argv, '--runtime'), runtimeSessionId: optionValue(argv, '--session'), task: optionValue(argv, '--task'), })), { json }); return 0; } if (command === 'complete' || command === 'blocked') { const input = { areaId: optionValue(argv, '--area'), summary: optionValue(argv, '--summary'), task: optionValue(argv, '--task'), evidence: optionValue(argv, '--evidence'), path: optionValue(argv, '--path'), commit: optionValue(argv, '--commit'), }; print(withControlPlane(repository, env, (control) => (command === 'complete' ? control.reportComplete(input) : control.reportBlocked(input))), { json }); return 0; } if (command === 'coordinate') { const participants = (optionValue(argv, '--with') ?? '').split(',').filter(Boolean); print(withControlPlane(repository, env, (control) => control.requestCoordination({ sender: optionValue(argv, '--from'), participants, body: optionValue(argv, '--body'), task: optionValue(argv, '--task'), path: optionValue(argv, '--path'), })), { json }); return 0; } if (command === 'handoff') { print(withControlPlane(repository, env, (control) => control.requestHandoff({ sender: optionValue(argv, '--from'), recipient: optionValue(argv, '--to') ?? 'session-manager', path: optionValue(argv, '--path'), task: optionValue(argv, '--task'), reason: optionValue(argv, '--reason'), })), { json }); return 0; } if (command === 'backlog') { const operation = argv[1] ?? 'list'; const control = openControlPlane({ repositoryRoot: repository.root, env }); try { const resources = new ResourceService({ repositoryRoot: repository.root, controlPlane: control }); const checks = new CheckService({ repositoryRoot: repository.root, controlPlane: control, resourceService: resources, }); const integration = new IntegrationService({ repositoryRoot: repository.root, controlPlane: control, checkService: checks, }); const backlog = new BacklogService({ repositoryRoot: repository.root, controlPlane: control, integrationLookup: (requestId) => integration.get(requestId), }); if (operation === 'list') print({ tasks: backlog.list({ state: optionValue(argv, '--state'), owner: optionValue(argv, '--owner'), }) }, { json }); else if (operation === 'get') print(backlog.get(optionValue(argv, '--task')), { json }); else if (operation === 'create') print(backlog.create({ actorId: optionValue(argv, '--area'), title: optionValue(argv, '--title'), description: optionValue(argv, '--description'), priority: optionValue(argv, '--priority') ?? 'normal', acceptanceCriteria: commaList(optionValue(argv, '--accept')), affectedDomains: commaList(optionValue(argv, '--domains')), dependencies: commaList(optionValue(argv, '--depends')), }), { json }); else if (operation === 'transition') print(backlog.transition({ taskId: optionValue(argv, '--task'), actorId: optionValue(argv, '--area'), to: optionValue(argv, '--state'), expectedRevision: Number(optionValue(argv, '--revision')), owner: optionValue(argv, '--owner'), evidence: commaList(optionValue(argv, '--evidence')), commit: optionValue(argv, '--commit'), integrationRequest: optionValue(argv, '--integration'), blockedReason: optionValue(argv, '--reason'), note: optionValue(argv, '--note'), }), { json }); else throw new TorchError(`Unknown backlog operation: ${operation}`, { code: 'UNKNOWN_COMMAND' }); return 0; } finally { control.close(); } } if (command === 'checks') { const operation = argv[1] ?? 'list'; const control = openControlPlane({ repositoryRoot: repository.root, env }); try { const resources = new ResourceService({ repositoryRoot: repository.root, controlPlane: control }); const checks = new CheckService({ repositoryRoot: repository.root, controlPlane: control, resourceService: resources, }); if (operation === 'list') print({ checks: checks.listChecks() }, { json }); else if (operation === 'receipts') print({ receipts: checks.receipts({ commit: optionValue(argv, '--commit'), checkId: optionValue(argv, '--id'), areaId: optionValue(argv, '--area'), }) }, { json }); else if (operation === 'plan') print(checks.plan({ checkId: optionValue(argv, '--id'), areaId: optionValue(argv, '--area'), }), { json }); else if (operation === 'run') { if (!argv.includes('--yes')) { throw new TorchError('Configured check execution requires an explicit --yes.', { code: 'APPROVAL_REQUIRED' }); } print(checks.run({ checkId: optionValue(argv, '--id'), areaId: optionValue(argv, '--area') }), { json }); } else throw new TorchError(`Unknown checks operation: ${operation}`, { code: 'UNKNOWN_COMMAND' }); return 0; } finally { control.close(); } } if (command === 'resources') { const operation = argv[1] ?? 'list'; const control = openControlPlane({ repositoryRoot: repository.root, env }); try { const resources = new ResourceService({ repositoryRoot: repository.root, controlPlane: control }); const resourceId = optionValue(argv, '--id'); const areaId = optionValue(argv, '--area'); if (operation === 'list') print({ resources: resources.list() }, { json }); else if (operation === 'status') print(resources.status(resourceId), { json }); else if (operation === 'acquire') print(resources.acquire({ resourceId, areaId }), { json }); else if (operation === 'release') print(resources.release({ resourceId, areaId }), { json }); else if (operation === 'cancel') print(resources.cancel({ resourceId, areaId }), { json }); else throw new TorchError(`Unknown resources operation: ${operation}`, { code: 'UNKNOWN_COMMAND' }); return 0; } finally { control.close(); } } if (command === 'integrate') { const operation = argv[1] ?? 'list'; const control = openControlPlane({ repositoryRoot: repository.root, env }); try { const resources = new ResourceService({ repositoryRoot: repository.root, controlPlane: control }); const checks = new CheckService({ repositoryRoot: repository.root, controlPlane: control, resourceService: resources, }); const integration = new IntegrationService({ repositoryRoot: repository.root, controlPlane: control, checkService: checks, }); const requestId = optionValue(argv, '--request'); const actorId = optionValue(argv, '--area'); if (operation === 'list') print({ requests: integration.list({ state: optionValue(argv, '--state') }) }, { json }); else if (operation === 'request') print(integration.request({ areaId: actorId, commit: optionValue(argv, '--commit'), }), { json }); else if (operation === 'evaluate') print(integration.evaluate(requestId), { json }); else if (operation === 'authorize') print(integration.authorize({ requestId, actorId }), { json }); else if (operation === 'plan') print(integration.planLanding({ requestId, actorId }), { json }); else if (operation === 'land') { if (!argv.includes('--yes')) { throw new TorchError('Landing changes canonical main. Review integrate plan, then re-run with --yes.', { code: 'APPROVAL_REQUIRED', }); } print(integration.land({ requestId, actorId }), { json }); } else throw new TorchError(`Unknown integrate operation: ${operation}`, { code: 'UNKNOWN_COMMAND' }); return 0; } finally { control.close(); } } if (command === 'canonical') { const operation = argv[1] ?? 'plan'; if (operation === 'plan') { const plan = planLocalCanonical({ repositoryRoot: repository.root }); print(plan, { json }); return plan.canProceed ? 0 : 1; } if (operation === 'create') { if (!argv.includes('--yes')) { throw new TorchError('Local canonical creation changes Git configuration. Review canonical plan, then use --yes.', { code: 'APPROVAL_REQUIRED', }); } print(createLocalCanonical({ repositoryRoot: repository.root }), { json }); return 0; } throw new TorchError(`Unknown canonical operation: ${operation}`, { code: 'UNKNOWN_COMMAND' }); } if (command === 'recoverability') { print(classifyRecoverability({ repositoryRoot: repository.root, commit: optionValue(argv, '--commit') ?? 'HEAD', }), { json }); return 0; } if (command === 'uninstall') { const result = uninstallProject({ repository, purge: argv.includes('--purge'), dryRun: argv.includes('--dry-run'), }); print(result, { json }); return result.canProceed === false ? 1 : 0; } throw new TorchError(`Unknown command: ${command}`, { code: 'UNKNOWN_COMMAND' }); } catch (error) { print(asErrorRecord(error), { json: true }); return error instanceof TorchError ? 2 : 1; } }