mirror of
https://github.com/PR0M3TH3AN/TORCH.git
synced 2026-10-28 16:31:56 +00:00
1160 lines
56 KiB
JavaScript
1160 lines
56 KiB
JavaScript
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 { startConsole } from './console/server.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 {
|
|
captureFleet, createFleetBrief, detachFleet, planAreaUp, planFleetDetach,
|
|
planFleetDown, planFleetUp, startFleet, stopFleet,
|
|
} from './runtime/lifecycle.mjs';
|
|
import { ResourceService } from './resources/service.mjs';
|
|
import { CANDIDATE_ACCEPTANCE_SCENARIOS, createVersionService } from './self-host/service.mjs';
|
|
import { observeProject } from './observability/snapshot.mjs';
|
|
import { ContextTelemetryService } from './telemetry/context.mjs';
|
|
import { ScheduleService } from './schedules/service.mjs';
|
|
import { assertLauncherDigest, ScheduleLauncherService } from './schedules/launcher.mjs';
|
|
import { createFleetDesignBrief } from './design/brief.mjs';
|
|
import {
|
|
createArchitectRunPlan, loadArchitectArtifact, runSessionArchitect, validateArchitectProposal,
|
|
} from './design/architect.mjs';
|
|
import { FleetEvolutionService } from './evolution/service.mjs';
|
|
import { ConvergenceService } from './convergence/service.mjs';
|
|
import {
|
|
attachForge, detachForge, forgeStatus, planForgeAttach, planForgeDetach,
|
|
} from './forge/service.mjs';
|
|
import {
|
|
configureDelivery, DeliveryService, planDeliveryConfiguration,
|
|
} from './delivery/service.mjs';
|
|
|
|
const HELP = `TORCH — portable agent fleet
|
|
|
|
Usage:
|
|
torch init [--repo <path>] [--spec <path>] [--json]
|
|
torch analyze [--repo <path>] [--spec <path>] [--json]
|
|
torch design [--repo <path>] [--spec <path>] [--output <path>] [--json]
|
|
torch bootstrap [--repo <path>] [--spec <path>] [--output <path>] [--json]
|
|
torch architect plan --brief <path> --provider <claude|codex> [--model <model>] [--max-budget-usd <amount>] [--json]
|
|
torch architect validate --brief <path> --response <path> [--json]
|
|
torch architect run --brief <path> --provider <claude|codex> --output <path> [--model <model>] [--max-budget-usd <amount>] --yes [--json]
|
|
torch domains [--repo <path>] [--spec <path>] [--output <path>] [--json]
|
|
torch install --proposal <path> [--runtime <claude|codex|claude,codex>] [--dry-run] [--yes] [--json]
|
|
torch worktrees [--parent <path>] [--dry-run] [--yes] [--json]
|
|
torch up [--fresh] [--only <id,id>] [--dry-run] [--yes] [--json]
|
|
torch down [--dry-run] [--yes] [--json]
|
|
torch capture [--json]
|
|
torch detach [--dry-run] [--yes] [--json]
|
|
torch list [--json]
|
|
torch brief [--area <id>] [--json]
|
|
torch identity --area <id> [--json]
|
|
torch agents [--json]
|
|
torch agent --area <id> [--json]
|
|
torch roster [--json]
|
|
torch who-owns (--path <path> | --capability <text>) [--json]
|
|
torch neighbours --area <id> [--json]
|
|
torch message --from <id> --to <id|all> --body <text> [--task <id>] [--path <path>] [--commit <sha>] [--json]
|
|
torch inbox --area <id> [--unacknowledged] [--limit <n>] [--json]
|
|
torch ack --area <id> --message <id> [--json]
|
|
torch status --area <id> --state <state> [--summary <text>] [--runtime <name>] [--session <id>] [--task <id>] [--json]
|
|
torch complete --area <id> --summary <text> [--task <id>] [--evidence <text>] [--commit <sha>] [--json]
|
|
torch blocked --area <id> --summary <text> [--task <id>] [--evidence <text>] [--path <path>] [--commit <sha>] [--json]
|
|
torch coordinate --from <id> --body <text> [--with <id,id>] [--task <id>] [--path <path>] [--json]
|
|
torch handoff --from <id> [--to <id>] (--path <path> | --task <id>) --reason <text> [--json]
|
|
torch backlog list [--state <state>] [--owner <id>] [--json]
|
|
torch backlog get --task <id> [--json]
|
|
torch backlog create --area <id> --title <text> --description <text> --accept <text,...> [--priority <priority>] [--domains <id,...>] [--depends <task,...>] [--json]
|
|
torch backlog transition --task <id> --area <id> --state <state> --revision <n> [--owner <id>] [--evidence <text,...>] [--commit <sha>] [--integration <id>] [--reason <text>] [--note <text>] [--json]
|
|
torch checks list [--json]
|
|
torch checks receipts [--commit <sha>] [--id <check>] [--area <id>] [--json]
|
|
torch checks plan --id <check> --area <id> [--json]
|
|
torch checks run --id <check> --area <id> --yes [--json]
|
|
torch resources list [--json]
|
|
torch resources status --id <resource> [--json]
|
|
torch resources acquire --id <resource> --area <id> [--json]
|
|
torch resources release --id <resource> --area <id> [--json]
|
|
torch resources cancel --id <resource> --area <id> [--json]
|
|
torch converge plan --area <id> [--json]
|
|
torch converge run --area <id> --yes [--json]
|
|
torch converge guards --area <id> [--json]
|
|
torch converge hold --area <id> --type <measurement|pin> --reason <text> [--json]
|
|
torch converge release --area <id> --type <measurement|pin> [--json]
|
|
torch integrate list [--state <state>] [--json]
|
|
torch integrate request --area <id> [--commit <sha>] [--json]
|
|
torch integrate evaluate --request <id> [--json]
|
|
torch integrate authorize --request <id> --area <id> [--json]
|
|
torch integrate plan --request <id> --area <id> [--json]
|
|
torch integrate land --request <id> --area <id> --yes [--json]
|
|
torch canonical plan [--json]
|
|
torch canonical create --yes [--json]
|
|
torch recoverability [--commit <sha>] [--json]
|
|
torch forge plan --remote <name> [--provider <name>] [--json]
|
|
torch forge attach --remote <name> [--provider <name>] --yes [--json]
|
|
torch forge status [--json]
|
|
torch forge detach [--dry-run] --yes [--json]
|
|
torch delivery list [--state <state>] [--json]
|
|
torch delivery get --delivery <id> [--json]
|
|
torch delivery create --area <id> [--commit <sha>] --label <text> --evidence <text,...> [--json]
|
|
torch delivery plan --delivery <id> --to <state> --by <id|owner> [--json]
|
|
torch delivery transition --delivery <id> --to <state> --by <id|owner> --evidence <text,...> [--yes] [--json]
|
|
torch delivery configure [--release-provider <name>] [--deployment-provider <name>] [--dry-run] --yes [--json]
|
|
torch candidate plan --source <path> [--version <version>] [--json]
|
|
torch candidate build --source <path> [--version <version>] --yes [--json]
|
|
torch candidate status [--json]
|
|
torch candidate test --version <version> [--json]
|
|
torch upgrade --version <version> [--dry-run] --yes [--json]
|
|
torch rollback [--dry-run] --yes [--json]
|
|
torch import combatrig --source <path> [--output <path>] [--json]
|
|
torch console snapshot [--repo <path>] [--json]
|
|
torch console serve [--repo <path>] [--host <address>] [--port <n>]
|
|
torch context report [--area <id>] [--task <id>] [--commit <sha>] [--measurement <class>] [--json]
|
|
torch context record --area <id> --source <text> --measurement <measured|estimated> [usage fields] [--json]
|
|
torch schedules list [--actor <id|owner>] [--json]
|
|
torch schedules runs [--id <schedule>] [--json]
|
|
torch schedules plan --id <schedule> --actor <id|owner> [--json]
|
|
torch schedules run --id <schedule> --actor <id|owner> [--yes] [--json]
|
|
torch schedules launcher <plan|status|install|remove> [--yes] [--json]
|
|
torch fleet changes [--state <state>] [--json]
|
|
torch fleet get --change <id> [--json]
|
|
torch fleet propose --from session-manager --proposal <path> [--json]
|
|
torch fleet propose-retirement --from session-manager --proposal <path> [--json]
|
|
torch fleet propose-merge --from session-manager --proposal <path> [--json]
|
|
torch fleet propose-split --from session-manager --proposal <path> [--json]
|
|
torch fleet approve --change <id> --by <owner> --yes [--json]
|
|
torch fleet reject --change <id> --by <owner> --reason <text> --yes [--json]
|
|
torch fleet plan --change <id> [--json]
|
|
torch fleet activate --change <id> --by <owner> --yes [--json]
|
|
torch fleet start --change <id> [--fresh] [--dry-run] --yes [--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 loadJsonFile(cwd, inputPath, { label = 'JSON input', code = 'INPUT_INVALID' } = {}) {
|
|
if (!inputPath) throw new TorchError(`${label} path is required`, { code });
|
|
try { return JSON.parse(readFileSync(resolve(cwd, inputPath), 'utf8')); } catch (error) {
|
|
throw new TorchError(`Cannot read ${label}: ${inputPath}`, { code, 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 runtimeStoppers(spawn) {
|
|
return new Map([
|
|
['claude', createClaudeAdapter({
|
|
runner: (executable, args) => spawn(executable, args, {
|
|
encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'],
|
|
}),
|
|
})],
|
|
['codex', createCodexAdapter()],
|
|
]);
|
|
}
|
|
|
|
function stopThrough(adapters, action) {
|
|
const adapter = adapters.get(action.runtime);
|
|
if (!adapter) throw new TorchError(`No runtime stopper for ${action.runtime}`, {
|
|
code: 'RUNTIME_ADAPTER_MISSING', details: { runtime: action.runtime },
|
|
});
|
|
return adapter.stopSession({ runtimeSessionId: action.runtimeSessionId });
|
|
}
|
|
|
|
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']],
|
|
['ai-fleet-bootstrap', ['SCN-ai-fleet-bootstrap']],
|
|
['ai-fleet-planning', ['SCN-ai-fleet-planning']],
|
|
['fleet-evolution', ['SCN-fleet-evolution', 'SCN-cli-fleet-evolution']],
|
|
['fleet-boundary-evolution', ['SCN-fleet-boundary-evolution']],
|
|
['cli-lifecycle-surface', ['SCN-cli-lifecycle-surface']],
|
|
['forge-migration', ['SCN-forge-migration']],
|
|
['delivery-lifecycle', ['SCN-delivery-lifecycle']],
|
|
['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']],
|
|
['product-surface', ['SCN-product-site', 'SCN-console-readonly']],
|
|
['operations-observability', ['SCN-context-locality', 'SCN-schedule-boundaries', 'SCN-cli-schedules']],
|
|
]);
|
|
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 ?? '<missing>'}`, {
|
|
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;
|
|
}
|
|
if (command === 'console') {
|
|
const operation = argv[1] ?? 'serve';
|
|
const root = resolve(cwd, optionValue(argv, '--repo') ?? '.');
|
|
if (operation === 'snapshot') {
|
|
print(observeProject({ repositoryRoot: root, env }), { json });
|
|
return 0;
|
|
}
|
|
if (operation === 'serve') {
|
|
const host = optionValue(argv, '--host') ?? '127.0.0.1';
|
|
const port = Number(optionValue(argv, '--port') ?? 4317);
|
|
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
|
throw new TorchError('Console port must be an integer from 0 through 65535', { code: 'CONSOLE_PORT_INVALID' });
|
|
}
|
|
startConsole({ repositoryRoot: root, env, host, port });
|
|
return 0;
|
|
}
|
|
throw new TorchError(`Unknown console operation: ${operation}`, { code: 'UNKNOWN_COMMAND' });
|
|
}
|
|
if (command === 'architect') {
|
|
const operation = argv[1] ?? 'plan';
|
|
const briefPath = resolve(cwd, optionValue(argv, '--brief') ?? '');
|
|
const brief = loadArchitectArtifact(briefPath, 'Fleet design brief');
|
|
if (operation === 'validate') {
|
|
const responsePath = resolve(cwd, optionValue(argv, '--response') ?? '');
|
|
const proposal = loadArchitectArtifact(responsePath, 'Session Architect proposal');
|
|
const validation = validateArchitectProposal({ brief, proposal });
|
|
print({ proposal, validation, mutationPerformed: false }, { json });
|
|
return validation.valid ? 0 : 1;
|
|
}
|
|
const provider = optionValue(argv, '--provider');
|
|
const model = optionValue(argv, '--model');
|
|
const budgetValue = optionValue(argv, '--max-budget-usd');
|
|
const maxBudgetUsd = budgetValue === undefined ? undefined : Number(budgetValue);
|
|
if (budgetValue !== undefined && (!Number.isFinite(maxBudgetUsd) || maxBudgetUsd <= 0)) {
|
|
throw new TorchError('--max-budget-usd must be a positive number', { code: 'ARCHITECT_BUDGET_INVALID' });
|
|
}
|
|
if (operation === 'plan') {
|
|
print(createArchitectRunPlan({ brief, provider, model, maxBudgetUsd }), { json });
|
|
return 0;
|
|
}
|
|
if (operation === 'run') {
|
|
if (!argv.includes('--yes')) {
|
|
throw new TorchError('Session Architect execution consumes provider quota. Review architect plan, then use --yes.', {
|
|
code: 'APPROVAL_REQUIRED',
|
|
});
|
|
}
|
|
if (provider === 'claude' && maxBudgetUsd === undefined) {
|
|
throw new TorchError('Claude architect execution requires an explicit --max-budget-usd limit.', {
|
|
code: 'ARCHITECT_BUDGET_REQUIRED',
|
|
});
|
|
}
|
|
const output = optionValue(argv, '--output');
|
|
if (!output) throw new TorchError('Architect run requires --output for the validated pending proposal.', { code: 'OUTPUT_REQUIRED' });
|
|
const result = await runSessionArchitect({
|
|
brief, provider, model, maxBudgetUsd, authorized: true,
|
|
executor: (plan) => spawn(plan.command, plan.args, {
|
|
cwd: plan.cwd, input: plan.input, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 300_000,
|
|
}),
|
|
});
|
|
const path = resolve(cwd, output);
|
|
writeNewFile(path, `${JSON.stringify(result.proposal, null, 2)}\n`);
|
|
print({ ...result, output: path, mutationPerformed: true }, { json });
|
|
return 0;
|
|
}
|
|
throw new TorchError(`Unknown architect operation: ${operation}`, { code: 'UNKNOWN_COMMAND' });
|
|
}
|
|
const designCommand = ['init', 'analyze', 'domains', 'design', 'bootstrap'].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 bootstrap with the same inputs for an AI Session Architect brief, or torch design for the deterministic baseline. Review the resulting proposal before install.',
|
|
}, { json });
|
|
return 0;
|
|
}
|
|
if (command === 'domains' || command === 'design' || command === 'bootstrap') {
|
|
const specifications = inspectSpecifications(optionValues(argv, '--spec'), {
|
|
cwd, repositoryRoot: repository.root,
|
|
});
|
|
const analysis = analyzeRepository(repository, { specifications });
|
|
const proposal = proposeDomains({ repository, analysis });
|
|
const artifact = command === 'bootstrap'
|
|
? createFleetDesignBrief({ repository, analysis, baseline: proposal })
|
|
: proposal;
|
|
const output = optionValue(argv, '--output');
|
|
if (output) {
|
|
const path = resolve(cwd, output);
|
|
writeNewFile(path, `${JSON.stringify(artifact, null, 2)}\n`);
|
|
print({ [command === 'bootstrap' ? 'brief' : 'proposal']: artifact, output: path, mutationPerformed: true }, { json });
|
|
} else {
|
|
print({ [command === 'bootstrap' ? 'brief' : 'proposal']: artifact, mutationPerformed: false }, { json });
|
|
}
|
|
return 0;
|
|
}
|
|
if (command === 'install') {
|
|
const proposalPath = optionValue(argv, '--proposal');
|
|
const runtimeOption = optionValue(argv, '--runtime');
|
|
const runtimes = runtimeOption === undefined ? undefined : commaList(runtimeOption);
|
|
if (argv.includes('--dry-run')) {
|
|
const proposal = loadProposal(cwd, proposalPath);
|
|
print({ ...planInstall({ repository, proposal, env, runtimes }), 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, runtimes }), { 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'),
|
|
only: optionValue(argv, '--only') === undefined ? undefined : commaList(optionValue(argv, '--only')),
|
|
});
|
|
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 stoppers = runtimeStoppers(spawn);
|
|
print(stopFleet({
|
|
plan, controlPlane: control,
|
|
stopRuntime: (action) => stopThrough(stoppers, action),
|
|
}), { json });
|
|
return 0;
|
|
} finally {
|
|
control.close();
|
|
}
|
|
}
|
|
if (command === 'capture') {
|
|
print(withControlPlane(repository, env, (control) => captureFleet({
|
|
repositoryRoot: repository.root, controlPlane: control,
|
|
})), { json });
|
|
return 0;
|
|
}
|
|
if (command === 'detach') {
|
|
const control = openControlPlane({ repositoryRoot: repository.root, env });
|
|
try {
|
|
const stoppers = runtimeStoppers(spawn);
|
|
const plan = planFleetDetach({
|
|
repositoryRoot: repository.root, controlPlane: control, adapters: stoppers,
|
|
});
|
|
if (argv.includes('--dry-run')) {
|
|
print(plan, { json });
|
|
return plan.canProceed ? 0 : 1;
|
|
}
|
|
if (!argv.includes('--yes')) {
|
|
throw new TorchError('Fleet detach stops runtime sessions while preserving organization and worktrees. Review --dry-run, then use --yes.', {
|
|
code: 'APPROVAL_REQUIRED',
|
|
});
|
|
}
|
|
print(detachFleet({
|
|
plan, controlPlane: control, stopRuntime: (action) => stopThrough(stoppers, action),
|
|
}), { 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' || command === 'list') {
|
|
print(withControlPlane(repository, env, (control) => ({ agents: control.listAgents() })), { json });
|
|
return 0;
|
|
}
|
|
if (command === 'brief') {
|
|
print(withControlPlane(repository, env, (control) => createFleetBrief({
|
|
repositoryRoot: repository.root, controlPlane: control, areaId: optionValue(argv, '--area'),
|
|
})), { 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 === 'fleet') {
|
|
const operation = argv[1] ?? 'changes';
|
|
const control = openControlPlane({ repositoryRoot: repository.root, env });
|
|
try {
|
|
const evolution = new FleetEvolutionService({ repositoryRoot: repository.root, controlPlane: control });
|
|
const changeId = optionValue(argv, '--change');
|
|
if (operation === 'changes') print({ changes: evolution.list({ state: optionValue(argv, '--state') }) }, { json });
|
|
else if (operation === 'get') print(evolution.get(changeId), { json });
|
|
else if (operation === 'propose') {
|
|
const input = loadJsonFile(cwd, optionValue(argv, '--proposal'), {
|
|
label: 'Fleet domain proposal', code: 'FLEET_PROPOSAL_INVALID',
|
|
});
|
|
print(evolution.proposeDomain({
|
|
...input, proposer: optionValue(argv, '--from') ?? input.proposer,
|
|
}), { json });
|
|
} else if (operation === 'propose-retirement') {
|
|
const input = loadJsonFile(cwd, optionValue(argv, '--proposal'), {
|
|
label: 'Fleet domain retirement proposal', code: 'FLEET_PROPOSAL_INVALID',
|
|
});
|
|
print(evolution.proposeRetirement({
|
|
...input, areaId: input.area_id ?? input.areaId,
|
|
proposer: optionValue(argv, '--from') ?? input.proposer,
|
|
}), { json });
|
|
} else if (operation === 'propose-merge' || operation === 'propose-split') {
|
|
const input = loadJsonFile(cwd, optionValue(argv, '--proposal'), {
|
|
label: 'Fleet boundary proposal', code: 'FLEET_PROPOSAL_INVALID',
|
|
});
|
|
const request = {
|
|
...input,
|
|
proposer: optionValue(argv, '--from') ?? input.proposer,
|
|
sourceDomains: input.sourceDomains ?? input.source_domains,
|
|
resultDomains: input.resultDomains ?? input.result_domains,
|
|
ownershipAssignments: input.ownershipAssignments ?? input.ownership_assignments,
|
|
expectedBenefit: input.expectedBenefit ?? (input.expected_benefit ? {
|
|
summary: input.expected_benefit.summary,
|
|
recurringWork: input.expected_benefit.recurring_work,
|
|
contextLocality: input.expected_benefit.context_locality,
|
|
coordinationCost: input.expected_benefit.coordination_cost,
|
|
} : undefined),
|
|
};
|
|
print(operation === 'propose-merge'
|
|
? evolution.proposeMerge(request) : evolution.proposeSplit(request), { json });
|
|
} else if (operation === 'approve') {
|
|
if (!argv.includes('--yes')) {
|
|
throw new TorchError('Owner approval of a Fleet change requires explicit --yes.', { code: 'APPROVAL_REQUIRED' });
|
|
}
|
|
print(evolution.approve({ changeId, approvedBy: optionValue(argv, '--by') }), { json });
|
|
} else if (operation === 'reject') {
|
|
if (!argv.includes('--yes')) {
|
|
throw new TorchError('Owner rejection of a Fleet change requires explicit --yes.', { code: 'APPROVAL_REQUIRED' });
|
|
}
|
|
print(evolution.reject({
|
|
changeId, rejectedBy: optionValue(argv, '--by'), reason: optionValue(argv, '--reason'),
|
|
}), { json });
|
|
} else if (operation === 'plan') print(evolution.planActivation(changeId), { json });
|
|
else if (operation === 'activate') {
|
|
if (!argv.includes('--yes')) {
|
|
throw new TorchError('Fleet activation changes tracked organization state and may create or remove a worktree. Review fleet plan, then use --yes.', {
|
|
code: 'APPROVAL_REQUIRED',
|
|
});
|
|
}
|
|
print(evolution.activate({ changeId, approvedBy: optionValue(argv, '--by') }), { json });
|
|
} else if (operation === 'start') {
|
|
const change = evolution.get(changeId);
|
|
if (change.type !== 'add-domain' || change.state !== 'active') {
|
|
throw new TorchError('Only an active Fleet domain can start a runtime session', {
|
|
code: 'FLEET_CHANGE_STATE_CONFLICT', details: { state: change.state },
|
|
});
|
|
}
|
|
const adapters = runtimeAdapters();
|
|
const plan = planAreaUp({
|
|
repositoryRoot: repository.root, controlPlane: control, areaId: change.domain.id,
|
|
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('Starting the new domain invokes its configured AI runtime. Review --dry-run, then use --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 });
|
|
} else throw new TorchError(`Unknown fleet operation: ${operation}`, { code: 'UNKNOWN_COMMAND' });
|
|
return 0;
|
|
} finally {
|
|
control.close();
|
|
}
|
|
}
|
|
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 === 'context') {
|
|
const operation = argv[1] ?? 'report';
|
|
const control = openControlPlane({ repositoryRoot: repository.root, env });
|
|
try {
|
|
const telemetry = new ContextTelemetryService({ controlPlane: control });
|
|
if (operation === 'report') print(telemetry.report({
|
|
areaId: optionValue(argv, '--area'), task: optionValue(argv, '--task'),
|
|
commit: optionValue(argv, '--commit'), measurement: optionValue(argv, '--measurement'),
|
|
}), { json });
|
|
else if (operation === 'record') print(telemetry.record({
|
|
areaId: optionValue(argv, '--area'), source: optionValue(argv, '--source'),
|
|
measurement: optionValue(argv, '--measurement'), runtime: optionValue(argv, '--runtime'),
|
|
runtimeSessionId: optionValue(argv, '--session'), task: optionValue(argv, '--task'),
|
|
commit: optionValue(argv, '--commit'), cachedInput: optionValue(argv, '--cached-input'),
|
|
uncachedInput: optionValue(argv, '--uncached-input'), cacheCreation: optionValue(argv, '--cache-creation'),
|
|
cacheRead: optionValue(argv, '--cache-read'), compactions: optionValue(argv, '--compactions'),
|
|
resumedPromptBytes: optionValue(argv, '--resumed-prompt-bytes'), costMicrousd: optionValue(argv, '--cost-microusd'),
|
|
verifiedItems: optionValue(argv, '--verified-items'),
|
|
}), { json });
|
|
else throw new TorchError(`Unknown context operation: ${operation}`, { code: 'UNKNOWN_COMMAND' });
|
|
return 0;
|
|
} finally {
|
|
control.close();
|
|
}
|
|
}
|
|
if (command === 'schedules') {
|
|
const operation = argv[1] ?? 'list';
|
|
if (operation === 'launcher') {
|
|
const launcherOperation = argv[2] ?? 'status';
|
|
const launcher = new ScheduleLauncherService({
|
|
repositoryRoot: repository.root, env,
|
|
command: [process.execPath, new URL('../bin/torch.mjs', import.meta.url).pathname],
|
|
});
|
|
if (launcherOperation === 'plan') {
|
|
const plan = launcher.plan();
|
|
print(plan, { json });
|
|
return plan.canProceed ? 0 : 1;
|
|
}
|
|
if (launcherOperation === 'status') {
|
|
print(launcher.status(), { json });
|
|
return 0;
|
|
}
|
|
if (!['install', 'remove'].includes(launcherOperation)) {
|
|
throw new TorchError(`Unknown schedule launcher operation: ${launcherOperation}`, { code: 'UNKNOWN_COMMAND' });
|
|
}
|
|
if (!argv.includes('--yes')) {
|
|
throw new TorchError('Persistent schedule launcher changes user systemd state. Review the plan, then re-run with --yes.', {
|
|
code: 'APPROVAL_REQUIRED',
|
|
});
|
|
}
|
|
print(launcherOperation === 'install' ? launcher.install() : launcher.remove(), { json });
|
|
return 0;
|
|
}
|
|
const control = openControlPlane({ repositoryRoot: repository.root, env });
|
|
try {
|
|
const schedules = new ScheduleService({ repositoryRoot: repository.root, controlPlane: control });
|
|
const scheduleId = optionValue(argv, '--id');
|
|
const actorId = optionValue(argv, '--actor') ?? 'owner';
|
|
if (operation === 'dispatch-system') {
|
|
if (!argv.includes('--yes')) throw new TorchError('System schedule dispatch requires installed-launcher approval.', { code: 'APPROVAL_REQUIRED' });
|
|
assertLauncherDigest(repository.root, optionValue(argv, '--launcher-digest'));
|
|
const dispatched = schedules.dispatchSystem({ actorId: 'owner', approved: true });
|
|
print(dispatched, { json });
|
|
return dispatched.runs.every((run) => run.result === 'succeeded') ? 0 : 1;
|
|
}
|
|
if (operation === 'list') print({ schedules: schedules.list({ actorId }) }, { json });
|
|
else if (operation === 'runs') print({ runs: schedules.runs({ scheduleId }) }, { json });
|
|
else if (operation === 'plan') print(schedules.plan({ scheduleId, actorId }), { json });
|
|
else if (operation === 'run') {
|
|
const result = schedules.run({ scheduleId, actorId, approved: argv.includes('--yes') });
|
|
print(result, { json });
|
|
return result.result === 'succeeded' ? 0 : 1;
|
|
} else throw new TorchError(`Unknown schedules 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 === 'converge') {
|
|
const operation = argv[1] ?? 'plan';
|
|
const control = openControlPlane({ repositoryRoot: repository.root, env });
|
|
try {
|
|
const convergence = new ConvergenceService({ repositoryRoot: repository.root, controlPlane: control });
|
|
const areaId = optionValue(argv, '--area');
|
|
if (operation === 'plan') print(convergence.plan({ areaId }), { json });
|
|
else if (operation === 'guards') print({ guards: convergence.guards(areaId) }, { json });
|
|
else if (operation === 'hold') {
|
|
const type = optionValue(argv, '--type');
|
|
if (!['measurement', 'pin'].includes(type)) {
|
|
throw new TorchError('CLI guards may only hold measurement or pin; check guards are automatic.', {
|
|
code: 'WORKTREE_GUARD_INVALID', details: { type: type ?? null },
|
|
});
|
|
}
|
|
print(convergence.hold({ areaId, type, reason: optionValue(argv, '--reason') }), { json });
|
|
} else if (operation === 'release') {
|
|
const type = optionValue(argv, '--type');
|
|
if (!['measurement', 'pin'].includes(type)) {
|
|
throw new TorchError('CLI guards may only release measurement or pin; check guards are automatic.', {
|
|
code: 'WORKTREE_GUARD_INVALID', details: { type: type ?? null },
|
|
});
|
|
}
|
|
print(convergence.release({ areaId, type }), { json });
|
|
} else if (operation === 'run') {
|
|
if (!argv.includes('--yes')) {
|
|
throw new TorchError('Convergence merges canonical state into a domain worktree. Review converge plan, then re-run with --yes.', {
|
|
code: 'APPROVAL_REQUIRED',
|
|
});
|
|
}
|
|
print(convergence.converge({ areaId }), { json });
|
|
} else throw new TorchError(`Unknown converge 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 === 'forge') {
|
|
const operation = argv[1] ?? 'status';
|
|
if (operation === 'status') {
|
|
const status = forgeStatus({ repositoryRoot: repository.root });
|
|
print(status, { json });
|
|
return status.available === false ? 1 : 0;
|
|
}
|
|
if (operation === 'plan') {
|
|
const plan = planForgeAttach({
|
|
repositoryRoot: repository.root, remote: optionValue(argv, '--remote'),
|
|
provider: optionValue(argv, '--provider') ?? 'generic-git',
|
|
});
|
|
print(plan, { json });
|
|
return plan.canProceed ? 0 : 1;
|
|
}
|
|
if (operation === 'attach') {
|
|
if (!argv.includes('--yes')) {
|
|
throw new TorchError('Forge attach changes tracked canonical-repository policy. Review forge plan, then use --yes.', {
|
|
code: 'APPROVAL_REQUIRED',
|
|
});
|
|
}
|
|
print(attachForge({
|
|
repositoryRoot: repository.root, remote: optionValue(argv, '--remote'),
|
|
provider: optionValue(argv, '--provider') ?? 'generic-git',
|
|
}), { json });
|
|
return 0;
|
|
}
|
|
if (operation === 'detach') {
|
|
if (argv.includes('--dry-run')) {
|
|
const plan = planForgeDetach({ repositoryRoot: repository.root });
|
|
print(plan, { json });
|
|
return plan.canProceed ? 0 : 1;
|
|
}
|
|
if (!argv.includes('--yes')) {
|
|
throw new TorchError('Forge detach changes tracked canonical-repository policy. Review the current status, then use --yes.', {
|
|
code: 'APPROVAL_REQUIRED',
|
|
});
|
|
}
|
|
print(detachForge({ repositoryRoot: repository.root }), { json });
|
|
return 0;
|
|
}
|
|
throw new TorchError(`Unknown forge operation: ${operation}`, { code: 'UNKNOWN_COMMAND' });
|
|
}
|
|
if (command === 'delivery') {
|
|
const operation = argv[1] ?? 'list';
|
|
if (operation === 'configure') {
|
|
const input = {
|
|
repositoryRoot: repository.root,
|
|
releaseProvider: optionValue(argv, '--release-provider') ?? 'none',
|
|
deploymentProvider: optionValue(argv, '--deployment-provider') ?? 'none',
|
|
};
|
|
if (argv.includes('--dry-run')) {
|
|
const plan = planDeliveryConfiguration(input);
|
|
print(plan, { json });
|
|
return plan.canProceed ? 0 : 1;
|
|
}
|
|
if (!argv.includes('--yes')) {
|
|
throw new TorchError('Delivery adapter configuration changes tracked project policy. Review --dry-run, then use --yes.', {
|
|
code: 'APPROVAL_REQUIRED',
|
|
});
|
|
}
|
|
print(configureDelivery(input), { json });
|
|
return 0;
|
|
}
|
|
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 delivery = new DeliveryService({
|
|
repositoryRoot: repository.root, controlPlane: control, checkService: checks,
|
|
});
|
|
const deliveryId = optionValue(argv, '--delivery');
|
|
if (operation === 'list') print({ deliveries: delivery.list({ state: optionValue(argv, '--state') }) }, { json });
|
|
else if (operation === 'get') print(delivery.get(deliveryId), { json });
|
|
else if (operation === 'create') print(delivery.create({
|
|
sourceArea: optionValue(argv, '--area'), commit: optionValue(argv, '--commit') ?? 'HEAD',
|
|
label: optionValue(argv, '--label'), evidence: commaList(optionValue(argv, '--evidence')),
|
|
}), { json });
|
|
else if (operation === 'plan') {
|
|
const plan = delivery.planTransition({
|
|
deliveryId, targetState: optionValue(argv, '--to'), actor: optionValue(argv, '--by'),
|
|
});
|
|
print(plan, { json });
|
|
return plan.canProceed ? 0 : 1;
|
|
} else if (operation === 'transition') {
|
|
print(delivery.transition({
|
|
deliveryId, targetState: optionValue(argv, '--to'), actor: optionValue(argv, '--by'),
|
|
evidence: commaList(optionValue(argv, '--evidence')), approved: argv.includes('--yes'),
|
|
}), { json });
|
|
} else throw new TorchError(`Unknown delivery operation: ${operation}`, { code: 'UNKNOWN_COMMAND' });
|
|
return 0;
|
|
} finally {
|
|
control.close();
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
}
|