@on-par/factory-cli 0.9.5 → 0.9.6-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.d.ts +182 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +62 -430
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/program-advanced.d.ts +4 -0
- package/dist/cli/program-advanced.d.ts.map +1 -0
- package/dist/cli/program-advanced.js +126 -0
- package/dist/cli/program-advanced.js.map +1 -0
- package/dist/cli/program-observe.d.ts +4 -0
- package/dist/cli/program-observe.d.ts.map +1 -0
- package/dist/cli/program-observe.js +85 -0
- package/dist/cli/program-observe.js.map +1 -0
- package/dist/cli/program-queue.d.ts +4 -0
- package/dist/cli/program-queue.d.ts.map +1 -0
- package/dist/cli/program-queue.js +87 -0
- package/dist/cli/program-queue.js.map +1 -0
- package/dist/cli/program-run-work.d.ts +4 -0
- package/dist/cli/program-run-work.d.ts.map +1 -0
- package/dist/cli/program-run-work.js +102 -0
- package/dist/cli/program-run-work.js.map +1 -0
- package/dist/cli/program-setup.d.ts +4 -0
- package/dist/cli/program-setup.d.ts.map +1 -0
- package/dist/cli/program-setup.js +35 -0
- package/dist/cli/program-setup.js.map +1 -0
- package/dist/cli/program.d.ts +4 -0
- package/dist/cli/program.d.ts.map +1 -0
- package/dist/cli/program.js +38 -0
- package/dist/cli/program.js.map +1 -0
- package/package.json +3 -3
package/dist/cli/index.js
CHANGED
|
@@ -7,11 +7,10 @@ import { createRequire } from 'node:module';
|
|
|
7
7
|
import { arch, hostname as osHostname, release, platform, userInfo } from 'node:os';
|
|
8
8
|
import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
9
9
|
import { promisify, styleText } from 'node:util';
|
|
10
|
-
import {
|
|
10
|
+
import { INVALID_BRANCH_PREFIX_MESSAGE, resolveBranchPrefixOption } from './branch-prefix.js';
|
|
11
11
|
import { acquirePortLease, aggregateCosts, appendKpiHistoryLine, applyRepoConfig, buildPhase, checkPhase, clearProxyState, computeHealthKpis, ConstitutionLoader, createDefaultWorkSourceRegistry, createLogger, createLaneProxy, defaultFindPortListeners, describeEffectiveConfig, detectPostMergeDefects, diagnoseModels, probeDeclaredHarnesses, estimateTrailingSpend, fetchDefectSources, fetchHumanEventSources, fetchSubscriptionUsage, formatKpiLines, formatUsageReport, getConstitutionsDir, getFactoryPaths, GITHUB_ISSUE_SOURCE, hasUnresolvedPark, InvalidArtifactsDirError, InvalidWorkRequestInputError, InvalidWorkspaceError, inspectPortLeases, isCommandAvailable, isProxyRunning, kpiBranchPrefixes, KNOWN_HARNESS_IDS, kpisToHistoryRecord, laneBaseUrl, laneEnv, laneHostLabel, loadFactoryConfig, loadFactoryConfigForRepo, loadModelsConfig, loadRepoConfig, loadRoutesConfig, LOCAL_BRIEF_SOURCE, joinClassifierOutcomes, mergeClassifierOutcomes, parseClassifierOutcomes, summarizeClassifierOutcomes, formatClassifierReport, mergedPrRefs, ModelRegistry, ModelRouter, parkReasonFor, parseKpiHistory, parseQueue, partitionLocalQueueByActivity, planPhase, provisionLaneContainer, ProviderBreaker, readEvents, readPortLeases, readQueue, readUsage, reapOrphanProcesses, reapStalePortLeases, reconstructKpiEvents, recordLeasePgid, releasePortLease, renderKpiReport, renderKpiTrend, resolveCodexDisabled, resolveDefectWindowDays, resolveSizeGateMode, parseSizeGateMode, parseMaxSlices, SIZE_GATE_MODES, resolveEffectiveBuildRoute, resolveEffectiveModelPins, resolveLaneBreakerThreshold, resolveEnvironmentProxy, resolveArtifactsDir, resolveIngestConfig, resolveLocalOnlyPolicy, resolveMergePolicy, resolveProcessGroupGraceMs, resolveSandboxPolicy, resolveSkipCI, resolveTimeouts, resolveUsageCap, resolveWatchdogPolicy, runAutoIngest, shipPhase, formatCostTotal, validateQueue, watchUsage, writeLocalRunReport, writeProxyState, } from '@on-par/factory-core';
|
|
12
|
-
import { acquirePidFile, createCheckResumeStore, findSlicePlanComment, recordSliceMerged, upsertSlicePlanComment, withSliceState, DEFAULT_GARDEN_MAX_CLUSTERS, DEFAULT_GARDEN_SINCE, capGardenClusters, clusterGarden, dedupGardenClusters, filterGardenOnlyNew, filterGardenWindow, markGardenTrackingUnknown, parseGardenDuration, readHarvestEvents, renderGardenJson, renderGardenReport, formatWorktreeLocation, laneWorktreePath, resolveWorktreeRoot, migrateRepoConfigToYaml, REPO_CONFIG_YAML_HEADER, claudeConfigDirOverride, claudeKeychainService, cleanupWorktree, createDaemonLogSink, factoryBranchIssue, factoryBranchPrefixes,
|
|
12
|
+
import { acquirePidFile, createCheckResumeStore, findSlicePlanComment, recordSliceMerged, upsertSlicePlanComment, withSliceState, DEFAULT_GARDEN_MAX_CLUSTERS, DEFAULT_GARDEN_SINCE, capGardenClusters, clusterGarden, dedupGardenClusters, filterGardenOnlyNew, filterGardenWindow, markGardenTrackingUnknown, parseGardenDuration, readHarvestEvents, renderGardenJson, renderGardenReport, formatWorktreeLocation, laneWorktreePath, resolveWorktreeRoot, migrateRepoConfigToYaml, REPO_CONFIG_YAML_HEADER, claudeConfigDirOverride, claudeKeychainService, cleanupWorktree, createDaemonLogSink, factoryBranchIssue, factoryBranchPrefixes, createDockerEngine, createFactorydServer, createRunRuntime, createShipExecutor, createGithubQueue, createLocalSmallDryRun, createOctokitFilingClient, createOctokitGreenPrClient, createOctokitQueueClient, daemonRuntimePaths, defaultRegistryPath, ensureDir, FACTORY_RUNTIME_CONFIG_KEYS, findUnmergedGreenPrs, formatGcReport, gitFetch, listMicroVms, listOrphanContainers, logCost, logEvent, microVmName, planQueueMigration, readCosts, readGithubQueueSnapshot, reapLaneWorktree, reapOrphanContainers, reapOrphanMicroVm, releaseRuntimeFiles, releaseStaleClaims, resolveBranchPrefix, resolveEffectiveConfig, worktreeSandboxFor, resolveExperimental, resolveLocalOnly, resolvePrClassifierPolicy, resolveRunUsageHarness, resolveReviewFloorRules, RunLockHeldError, runOvernightQueue, parseRemoteSlug, setupWorktree, shellEscape, listBuiltInCheckers, slugify, sweepWorktrees, watchChecks, withAdmissionGuard, withFileLock, withGitLock, withRunLock, wrapCommandInSandbox, writePortFile, buildTriagePrompt, normalizeTriageQueue, parseTriageCandidates, codexChatgptSessionState, hasClaudeLoginEvidence, claudeStoredSessionState, localAuthFailureSinceLogin, recordLocalAuthFailure, hasOpencodeLoginEvidence, } from '@on-par/factory-core/internal';
|
|
13
13
|
import { runTui } from '@on-par/factory-tui';
|
|
14
|
-
import { Command } from 'commander';
|
|
15
14
|
import { admissionStateReaderFor, runQueueReconcile } from './admission.js';
|
|
16
15
|
import { cmdClassifierBacktest, ClassifierBacktestInputError, } from './classifier-backtest.js';
|
|
17
16
|
import { findOpenPRForIssue, landIssue, waitForMerge } from './land.js';
|
|
@@ -20,7 +19,7 @@ import { setLandRuntime } from './land-runtime.js';
|
|
|
20
19
|
import { shipIssue } from './ship.js';
|
|
21
20
|
import { setShipRuntime } from './ship-runtime.js';
|
|
22
21
|
import { runQueueClear } from './queue-clear.js';
|
|
23
|
-
import {
|
|
22
|
+
import { buildProgram } from './program.js';
|
|
24
23
|
import { buildCostJson } from './cost-json.js';
|
|
25
24
|
import { buildQueueAddJson } from './queue-add-json.js';
|
|
26
25
|
import { buildQueueListJson } from './queue-list.js';
|
|
@@ -43,7 +42,6 @@ import { resolveFactoryCheckoutCommit, runFilingPreview } from './filing.js';
|
|
|
43
42
|
import { buildFeedbackDeps, resolveFeedbackRepo, runFeedback } from './feedback.js';
|
|
44
43
|
import { ensureFactoryExcluded, writeSampleQueue } from './init-files.js';
|
|
45
44
|
import { cmdLogs } from './logs.js';
|
|
46
|
-
import { applyHelpGroups } from './help-groups.js';
|
|
47
45
|
import { mergeScopeNotice } from './merge-scope.js';
|
|
48
46
|
import { selectOctokit } from './github-stub.js';
|
|
49
47
|
import { createFactoryOctokit } from './octokit.js';
|
|
@@ -3932,6 +3930,64 @@ async function resumeFromCheck(issueNum) {
|
|
|
3932
3930
|
}
|
|
3933
3931
|
});
|
|
3934
3932
|
}
|
|
3933
|
+
/** index.ts bindings the commander registrations in program*.ts call (#2793); type-only imports there avoid a runtime cycle. */
|
|
3934
|
+
export const PROGRAM_HANDLERS = {
|
|
3935
|
+
cmdInit,
|
|
3936
|
+
cmdMigrate,
|
|
3937
|
+
cmdConstitution,
|
|
3938
|
+
cmdModels,
|
|
3939
|
+
cmdDoctor,
|
|
3940
|
+
cmdCost,
|
|
3941
|
+
cmdUsage,
|
|
3942
|
+
cmdStatus,
|
|
3943
|
+
cmdRuns,
|
|
3944
|
+
cmdKpis,
|
|
3945
|
+
cmdClassifierReport,
|
|
3946
|
+
cmdClassifierBacktestEntry,
|
|
3947
|
+
cmdGarden,
|
|
3948
|
+
cmdTui,
|
|
3949
|
+
cmdLogs,
|
|
3950
|
+
cmdFeedback,
|
|
3951
|
+
cmdFilingPreview,
|
|
3952
|
+
cmdTriage,
|
|
3953
|
+
cmdTriageAccept,
|
|
3954
|
+
cmdUnpark,
|
|
3955
|
+
cmdQueueMigrate,
|
|
3956
|
+
cmdQueueAdd,
|
|
3957
|
+
cmdQueueReconcile,
|
|
3958
|
+
cmdQueueList,
|
|
3959
|
+
cmdQueueClear,
|
|
3960
|
+
cmdHostedSmoke,
|
|
3961
|
+
cmdHostedRunner,
|
|
3962
|
+
cmdHostedQueue,
|
|
3963
|
+
cmdCheck,
|
|
3964
|
+
cmdShip,
|
|
3965
|
+
cmdRunIssue,
|
|
3966
|
+
cmdRunBrief,
|
|
3967
|
+
cmdLocalSmallDryRun,
|
|
3968
|
+
cmdLocalSmallOvernight,
|
|
3969
|
+
cmdLand,
|
|
3970
|
+
cmdResumeApproved,
|
|
3971
|
+
cmdRun,
|
|
3972
|
+
cmdProxy,
|
|
3973
|
+
cmdFactoryd,
|
|
3974
|
+
cmdDaemonStart,
|
|
3975
|
+
cmdDaemonStop,
|
|
3976
|
+
cmdDaemonStatus,
|
|
3977
|
+
cmdDaemonLogs,
|
|
3978
|
+
cmdSelfUpdateCheck,
|
|
3979
|
+
cmdWorktreeGc,
|
|
3980
|
+
cmdReset,
|
|
3981
|
+
cmdSupervise,
|
|
3982
|
+
cmdStop,
|
|
3983
|
+
cmdResume,
|
|
3984
|
+
parseIssueArg,
|
|
3985
|
+
getRepoRoot,
|
|
3986
|
+
getCliVersion,
|
|
3987
|
+
CliExitError,
|
|
3988
|
+
PREREQUISITES_TEXT,
|
|
3989
|
+
HELP_FOOTER_TEXT,
|
|
3990
|
+
};
|
|
3935
3991
|
// ---------- main ----------
|
|
3936
3992
|
export async function main(argv = process.argv, deps = {}) {
|
|
3937
3993
|
if (argv.slice(2).length === 0) {
|
|
@@ -3949,431 +4005,7 @@ export async function main(argv = process.argv, deps = {}) {
|
|
|
3949
4005
|
process.exitCode = staleExit;
|
|
3950
4006
|
return;
|
|
3951
4007
|
}
|
|
3952
|
-
const program =
|
|
3953
|
-
program
|
|
3954
|
-
.name('factory')
|
|
3955
|
-
.description('Multi-agent software factory with boss-worker-checker orchestration')
|
|
3956
|
-
.version(getCliVersion())
|
|
3957
|
-
.addHelpText('before', PREREQUISITES_TEXT)
|
|
3958
|
-
.addHelpText('after', HELP_FOOTER_TEXT)
|
|
3959
|
-
.option('--state-root <path>', 'Keep factory state and config outside the checkout: an absolute path, ~/..., or "auto" (~/.factory/<owner>/<name>); env FACTORY_STATE_ROOT');
|
|
3960
|
-
program.hook('preAction', async () => {
|
|
3961
|
-
const flag = program.opts().stateRoot;
|
|
3962
|
-
if (flag === undefined && !process.env.FACTORY_STATE_ROOT?.trim())
|
|
3963
|
-
return;
|
|
3964
|
-
try {
|
|
3965
|
-
applyStateRootOption(flag, await getRepoRoot());
|
|
3966
|
-
}
|
|
3967
|
-
catch (e) {
|
|
3968
|
-
throw new CliExitError(`factory: ${e.message}`, 2);
|
|
3969
|
-
}
|
|
3970
|
-
});
|
|
3971
|
-
program
|
|
3972
|
-
.command('init')
|
|
3973
|
-
.description('Set up .factory/ in this repo (config, constitution, queue file, .gitignore) and check model reachability')
|
|
3974
|
-
.option('--force', 'Overwrite an existing .factory config, constitution, and .gitignore')
|
|
3975
|
-
.action((opts) => cmdInit(opts));
|
|
3976
|
-
program
|
|
3977
|
-
.command('migrate')
|
|
3978
|
-
.description('Upgrade .factory/ to the v2 layout (config, constitution.md, .gitignore, state/); --to-yaml converts config.json to config.yaml')
|
|
3979
|
-
.option('--dry-run', 'Print planned changes without writing')
|
|
3980
|
-
.option('--to-yaml', 'Convert .factory/config.json to .factory/config.yaml (removes the JSON only after a round-trip check)')
|
|
3981
|
-
.action((opts) => cmdMigrate(opts));
|
|
3982
|
-
program
|
|
3983
|
-
.command('constitution')
|
|
3984
|
-
.description('Manage product constitutions — .factory/constitution.md in this repo is the source of truth')
|
|
3985
|
-
.option('--init [product]', 'Write .factory/constitution.md in this repo, scaffolded from the template (an optional name overrides the display name)')
|
|
3986
|
-
.option('--list', 'List available constitutions with their checkers')
|
|
3987
|
-
.option('--checkers', 'List every built-in checker name with a one-line description')
|
|
3988
|
-
.option('--product <name>', 'Seed .factory/constitution.md from the bundled constitution for <name>; replaces an unmodified scaffold without --force')
|
|
3989
|
-
.option('--force', 'With --init or --product, overwrite an existing .factory/constitution.md')
|
|
3990
|
-
.action((opts) => cmdConstitution(opts));
|
|
3991
|
-
program
|
|
3992
|
-
.command('models')
|
|
3993
|
-
.description('List models with their tiers, cost per 1M tokens, and availability')
|
|
3994
|
-
.option('--doctor', 'Probe provider CLIs and env keys; report per-model reachability')
|
|
3995
|
-
.action(cmdModels);
|
|
3996
|
-
program
|
|
3997
|
-
.command('doctor')
|
|
3998
|
-
.description('Check your environment (git, agent install and sign-in, gh auth, GitHub token, npm, sandbox, .factory/)')
|
|
3999
|
-
.option('--reconcile', 'Reap stale port leases, dead-run worktrees, stale issue claims, orphan sf-job-*/factory.managed containers, and orphan factory-* sbx VMs')
|
|
4000
|
-
.option('--probe-inference', 'Also send one real prompt with claude -p on the host and in the sandbox to verify Claude auth (uses quota)')
|
|
4001
|
-
.action((opts) => cmdDoctor(opts));
|
|
4002
|
-
program
|
|
4003
|
-
.command('cost')
|
|
4004
|
-
.description('Show recorded model spend by model (--issue for one issue)')
|
|
4005
|
-
.option('--issue <number>', 'Show per-entry detail for one issue')
|
|
4006
|
-
.option('--json', 'Print one JSON object (schemaVersion 1): rows, perModel, total; unpriced cost is null')
|
|
4007
|
-
.action((opts) => cmdCost(opts));
|
|
4008
|
-
program
|
|
4009
|
-
.command('usage')
|
|
4010
|
-
.description('Report 5-hour subscription usage (falls back to a list-price estimate)')
|
|
4011
|
-
.option('--json', 'Print one JSON object (schemaVersion 1) instead of text')
|
|
4012
|
-
.action((opts) => cmdUsage({}, opts));
|
|
4013
|
-
program
|
|
4014
|
-
.command('status')
|
|
4015
|
-
.description('Show active runs, the GitHub queue, provider health, and recent events')
|
|
4016
|
-
.option('--kpis', 'Also show the full health KPIs and the effective config')
|
|
4017
|
-
.option('--json', 'Print one JSON object (schemaVersion 1): repo, product, stop, breaker, active claims, update; --kpis is ignored')
|
|
4018
|
-
.action((opts) => cmdStatus(opts));
|
|
4019
|
-
program
|
|
4020
|
-
.command('runs')
|
|
4021
|
-
.description('Print per-issue run state from the local runs directory as one JSON object, newest first (read-only)')
|
|
4022
|
-
.option('--json', 'Print one JSON object (schemaVersion 1) — required')
|
|
4023
|
-
.option('--limit <n>', 'Show at most N runs (default 50)')
|
|
4024
|
-
.option('--diffstat <issue>', "Print per-file added/deleted line counts for one issue's run branch vs origin/<default branch>")
|
|
4025
|
-
.action(async (opts) => {
|
|
4026
|
-
await cmdRuns(opts);
|
|
4027
|
-
});
|
|
4028
|
-
program
|
|
4029
|
-
.command('kpis')
|
|
4030
|
-
.description('Compute factory health KPIs and record a trend snapshot')
|
|
4031
|
-
.option('--branch-prefix <prefix>', BRANCH_PREFIX_MATCH_OPTION_DESCRIPTION)
|
|
4032
|
-
.action(async (opts) => {
|
|
4033
|
-
await cmdKpis(opts);
|
|
4034
|
-
});
|
|
4035
|
-
const classifierCmd = program
|
|
4036
|
-
.command('classifier')
|
|
4037
|
-
.description('Inspect or backtest the shadow PR classifier track record');
|
|
4038
|
-
classifierCmd
|
|
4039
|
-
.command('report')
|
|
4040
|
-
.description('Confusion table, rule-of-three slip bounds and model-vs-floor agreement from classifier-outcomes.jsonl')
|
|
4041
|
-
.option('--json', 'Print one JSON object with the same numbers')
|
|
4042
|
-
.action((opts) => cmdClassifierReport(opts));
|
|
4043
|
-
classifierCmd
|
|
4044
|
-
.command('backtest')
|
|
4045
|
-
.description('Replay the floor and shadow classifier over merged factory PRs and print the outcome report')
|
|
4046
|
-
.requiredOption('--since <date>', 'Only PRs merged on or after this date (YYYY-MM-DD)')
|
|
4047
|
-
.option('--limit <n>', 'Classify at most N PRs (oldest merge first)')
|
|
4048
|
-
.option('--labels <file>', 'CSV of hand labels `pr,class` (class: defect|clean|gated) that override heuristic outcomes')
|
|
4049
|
-
.option('--max-cost <usd>', 'Stop before the next PR once estimated model spend reaches this many USD')
|
|
4050
|
-
.option('--branch-prefix <prefix>', BRANCH_PREFIX_MATCH_OPTION_DESCRIPTION)
|
|
4051
|
-
.option('--json', 'Print one JSON object instead of the table')
|
|
4052
|
-
.action((opts) => cmdClassifierBacktestEntry(opts));
|
|
4053
|
-
program
|
|
4054
|
-
.command('garden')
|
|
4055
|
-
.description('Read-only report of recurring failure patterns in local events, deduped against open issues by GitHub search')
|
|
4056
|
-
.option('--report', 'Print recurring CHECK failure-signature clusters as markdown')
|
|
4057
|
-
.option('--since <duration>', 'Only consider events from this window, e.g. 7d, 36h, 2w', DEFAULT_GARDEN_SINCE)
|
|
4058
|
-
.option('--max-clusters <n>', 'Print at most this many clusters', String(DEFAULT_GARDEN_MAX_CLUSTERS))
|
|
4059
|
-
.option('--json', 'Print the same cluster data as JSON')
|
|
4060
|
-
.option('--out <file>', 'Write the report to this file instead of stdout')
|
|
4061
|
-
.option('--only-new', 'Omit clusters an open issue already tracks; keep new and tracked: unknown clusters')
|
|
4062
|
-
.action((opts) => cmdGarden(opts));
|
|
4063
|
-
program
|
|
4064
|
-
.command('tui')
|
|
4065
|
-
.description('Live read-only view of the current run (q to quit)')
|
|
4066
|
-
.option('--local-queue', 'Read .factory/queue instead of claiming issues from GitHub Issues')
|
|
4067
|
-
.action((opts) => cmdTui(opts, deps.runTui ?? runTui));
|
|
4068
|
-
program
|
|
4069
|
-
.command('logs')
|
|
4070
|
-
.description('Print pipeline events from .factory/events.ndjson (--follow to tail)')
|
|
4071
|
-
.option('--follow', 'Keep watching for new events')
|
|
4072
|
-
.option('--json', 'Emit one JSON object per line')
|
|
4073
|
-
.option('--issue <number>', 'Only show events for this issue')
|
|
4074
|
-
.action(async (opts) => {
|
|
4075
|
-
const repoRoot = await getRepoRoot();
|
|
4076
|
-
const paths = getFactoryPaths(repoRoot);
|
|
4077
|
-
await cmdLogs(opts, { eventsFile: paths.events });
|
|
4078
|
-
});
|
|
4079
|
-
program
|
|
4080
|
-
.command('feedback <pr-url>')
|
|
4081
|
-
.description("File one factory issue (bug, factory:needs-triage) from a PR's human review findings")
|
|
4082
|
-
.option('--note <text>', 'Use this as the issue title instead of the first finding')
|
|
4083
|
-
.option('--repo <owner/name>', 'Repo to file the issue in (default: filing.repo, then the origin remote)')
|
|
4084
|
-
.option('--include-local-paths', 'Include this host name and local design/log paths in the issue')
|
|
4085
|
-
.option('--branch-prefix <prefix>', BRANCH_PREFIX_MATCH_OPTION_DESCRIPTION)
|
|
4086
|
-
.action(cmdFeedback);
|
|
4087
|
-
const filingCmd = program.command('filing').description('Inspect upstream factory reports (nothing is sent)');
|
|
4088
|
-
filingCmd
|
|
4089
|
-
.command('preview <run-id>')
|
|
4090
|
-
.description('Print the exact redacted [factory-report] title and body that would be sent upstream; sends nothing')
|
|
4091
|
-
.option('--branch-prefix <prefix>', BRANCH_PREFIX_MATCH_OPTION_DESCRIPTION)
|
|
4092
|
-
.action((runId, opts) => cmdFilingPreview(runId, opts));
|
|
4093
|
-
const triage = program
|
|
4094
|
-
.command('triage')
|
|
4095
|
-
.description('Have a model propose a queue from open issues into .factory/state/queue.proposed')
|
|
4096
|
-
.option('--product <name>', 'Product constitution to scope triage')
|
|
4097
|
-
.option('--model <id>', 'Model to triage with (any registered model, Claude or Codex)')
|
|
4098
|
-
.action(cmdTriage);
|
|
4099
|
-
triage
|
|
4100
|
-
.command('accept')
|
|
4101
|
-
.description('Validate and atomically promote .factory/state/queue.proposed to .factory/state/queue')
|
|
4102
|
-
.option('--force', 'Skip validation and promote as-is')
|
|
4103
|
-
.action(async (opts) => {
|
|
4104
|
-
await cmdTriageAccept(opts);
|
|
4105
|
-
});
|
|
4106
|
-
program
|
|
4107
|
-
.command('unpark <issue>')
|
|
4108
|
-
.description('Unpark a factory:parked issue: remove factory:parked and re-queue it in its lane with a fresh order label (same path as `queue add`)')
|
|
4109
|
-
.option('--lane <lane>', "Lane to re-queue into (default: the issue's existing factory:lane:* label)")
|
|
4110
|
-
.option('--json', 'Print one JSON object (schemaVersion 1): ok, action, issue, lane, order, labelsBefore, labelsAfter')
|
|
4111
|
-
.action(async (issue, opts) => {
|
|
4112
|
-
await cmdUnpark(issue, opts);
|
|
4113
|
-
});
|
|
4114
|
-
const queue = program
|
|
4115
|
-
.command('queue')
|
|
4116
|
-
.description('Manage the GitHub-label work queue (list, add, clear, reconcile, migrate)');
|
|
4117
|
-
queue
|
|
4118
|
-
.command('migrate')
|
|
4119
|
-
.description('Copy valid .factory/queue lane order into GitHub issue labels')
|
|
4120
|
-
.option('--file <path>', 'Read the legacy queue from an explicit path instead of .factory/queue')
|
|
4121
|
-
.option('--dry-run', 'Print the intended label updates without mutating GitHub')
|
|
4122
|
-
.action((opts) => cmdQueueMigrate(opts));
|
|
4123
|
-
queue
|
|
4124
|
-
.command('add <lane> <issues...>')
|
|
4125
|
-
.description('Queue explicit GitHub issues into a lane — applies factory:queued + factory:lane:<lane> (and an order label), creating any missing factory labels idempotently')
|
|
4126
|
-
.option('--json', 'Print one JSON object (schemaVersion 1): ok, action, lane, issues[{number, outcome, order, labelsAdded}], labelsCreated')
|
|
4127
|
-
.action(async (lane, issues, opts) => {
|
|
4128
|
-
await cmdQueueAdd(lane, issues, opts);
|
|
4129
|
-
});
|
|
4130
|
-
queue
|
|
4131
|
-
.command('reconcile')
|
|
4132
|
-
.description('Report queued GitHub issues that conflict with Factory App admission state, with the repair-flow pointer for each')
|
|
4133
|
-
.option('--lane <lane>', 'Only reconcile one lane')
|
|
4134
|
-
.action(async (opts) => {
|
|
4135
|
-
await cmdQueueReconcile(opts);
|
|
4136
|
-
});
|
|
4137
|
-
queue
|
|
4138
|
-
.command('list')
|
|
4139
|
-
.description('Print queued, in-progress and parked GitHub-queue issues per lane as one JSON object (read-only)')
|
|
4140
|
-
.option('--json', 'Print one JSON object (schemaVersion 1) — required')
|
|
4141
|
-
.option('--lane <lane>', 'Only list this lane (factory:lane:<lane>)')
|
|
4142
|
-
.action(async (opts) => {
|
|
4143
|
-
await cmdQueueList(opts);
|
|
4144
|
-
});
|
|
4145
|
-
queue
|
|
4146
|
-
.command('clear')
|
|
4147
|
-
.description('Empty the GitHub-backed queue: removes factory:queued and factory:order:* from every unclaimed queued issue (lane labels kept; claimed issues skipped). Preview with --dry-run; limit to one lane with --lane')
|
|
4148
|
-
.option('--dry-run', 'Print the preview and exit 0 without changing any label')
|
|
4149
|
-
.option('--yes', 'Confirm and remove the queue labels')
|
|
4150
|
-
.option('--lane <lane>', 'Only clear queued issues in this lane (factory:lane:<lane>)')
|
|
4151
|
-
.action(async (opts) => {
|
|
4152
|
-
await cmdQueueClear(opts);
|
|
4153
|
-
});
|
|
4154
|
-
const hosted = program
|
|
4155
|
-
.command('hosted')
|
|
4156
|
-
.description('Experimental remote-runner execution (needs FACTORY_HOSTED_EXEC=1)');
|
|
4157
|
-
hosted
|
|
4158
|
-
.command('smoke')
|
|
4159
|
-
.description('Local end-to-end hosted-exec smoke: create → lease → Docker run → result → cleanup')
|
|
4160
|
-
.option('--repo <slug>', 'owner/repo to clone and run against', 'on-par/software-factory')
|
|
4161
|
-
.option('--image <image>', 'Container image for the smoke run', 'node:20-alpine')
|
|
4162
|
-
.action(async (opts) => {
|
|
4163
|
-
await cmdHostedSmoke(opts);
|
|
4164
|
-
});
|
|
4165
|
-
hosted
|
|
4166
|
-
.command('runner')
|
|
4167
|
-
.description('Register capabilities with the local control plane and lease one compatible job, then exit')
|
|
4168
|
-
.option('--url <url>', 'Control-plane base URL', 'http://127.0.0.1:8799')
|
|
4169
|
-
.option('--runner-id <id>', 'Runner identity (default runner-<pid>)')
|
|
4170
|
-
.option('--capabilities <csv>', 'Comma-separated capability list', 'git,node')
|
|
4171
|
-
.option('--timeout <ms>', 'Bounded wait window for a compatible job, in ms', '30000')
|
|
4172
|
-
.option('--poll-interval <ms>', 'Delay between poll attempts, in ms', '2000')
|
|
4173
|
-
.option('--lease-ttl <ms>', 'Lease TTL, in ms', '300000')
|
|
4174
|
-
.option('--heartbeat-interval <ms>', 'Expected heartbeat interval, in ms', '30000')
|
|
4175
|
-
.action(async (opts) => {
|
|
4176
|
-
await cmdHostedRunner(opts);
|
|
4177
|
-
});
|
|
4178
|
-
hosted
|
|
4179
|
-
.command('queue')
|
|
4180
|
-
.description('Queue one job to the local control plane, tail it to terminal, print the result')
|
|
4181
|
-
.option('--url <url>', 'Control-plane base URL', 'http://127.0.0.1:8799')
|
|
4182
|
-
.option('--repo <slug>', 'owner/repo the job runs against', 'on-par/software-factory')
|
|
4183
|
-
.option('--task <text>', 'Opaque task payload')
|
|
4184
|
-
.option('--capabilities <csv>', 'Comma-separated required capabilities', 'git,node')
|
|
4185
|
-
.option('--authority <authority>', 'Required authority', 'repo:read')
|
|
4186
|
-
.option('--timeout <ms>', 'Bounded wait for a terminal result, in ms', '120000')
|
|
4187
|
-
.option('--poll-interval <ms>', 'Delay between summary polls, in ms', '1000')
|
|
4188
|
-
.option('--job-id <id>', 'Explicit job id (default server-generated)')
|
|
4189
|
-
.action(async (opts) => {
|
|
4190
|
-
await cmdHostedQueue(opts);
|
|
4191
|
-
});
|
|
4192
|
-
program
|
|
4193
|
-
.command('check <issue>')
|
|
4194
|
-
.alias('ready')
|
|
4195
|
-
.description('Check an issue before queuing: required fields and whether the size gate runs it as-is or would split it, plus an advisory INVEST report (read-only, optional --deep model review; exit 0 ready, 1 missing fields or ungradeable criteria, 2 invalid --deep output, 3 would split)')
|
|
4196
|
-
.option('--json', 'Print one JSON object with fields, criteria findings, size verdict, INVEST findings, and reasons')
|
|
4197
|
-
.option('--deep', 'Also call the triage-tier model once: preview the proposed split when the issue would split, otherwise suggest missing criteria and scope concerns (prints model id and cost; never files anything; exit 2 on invalid model output)')
|
|
4198
|
-
.action(cmdCheck);
|
|
4199
|
-
program
|
|
4200
|
-
.command('ship <issue>')
|
|
4201
|
-
.description('Plan → build → check one GitHub issue and open a ready-for-review PR; never merges (merge later with land)')
|
|
4202
|
-
.option('--product <name>', 'Override active product constitution')
|
|
4203
|
-
.option('--no-auto-rework', 'Disable automatic rework loop')
|
|
4204
|
-
.option('--interactive', 'Pause before opening the PR and wait for approval from the TUI')
|
|
4205
|
-
.option('--approve-plan', 'Pause after PLAN freezes the spec and wait for approval before BUILD')
|
|
4206
|
-
.option('--no-sandbox', 'Disable the containment sandbox for agent runs (dangerous)')
|
|
4207
|
-
.option('--branch-prefix <prefix>', BRANCH_PREFIX_OPTION_DESCRIPTION)
|
|
4208
|
-
.action(async (issueNum, opts) => {
|
|
4209
|
-
await cmdShip(parseIssueArg(issueNum), opts);
|
|
4210
|
-
});
|
|
4211
|
-
program
|
|
4212
|
-
.command('run-issue <issue>')
|
|
4213
|
-
.description('Like ship, but loads the issue first and exits 2 before any worktree or PR if it cannot be found')
|
|
4214
|
-
.option('--product <name>', 'Override active product constitution')
|
|
4215
|
-
.option('--no-auto-rework', 'Disable automatic rework loop')
|
|
4216
|
-
.option('--interactive', 'Pause before opening the PR and wait for approval from the TUI')
|
|
4217
|
-
.option('--approve-plan', 'Pause after PLAN freezes the spec and wait for approval before BUILD')
|
|
4218
|
-
.option('--no-sandbox', 'Disable the containment sandbox for agent runs (dangerous)')
|
|
4219
|
-
.option('--branch-prefix <prefix>', BRANCH_PREFIX_OPTION_DESCRIPTION)
|
|
4220
|
-
.option('--run-children', 'File mode only: when the size gate files child issues, run them (slice mode never files issues)')
|
|
4221
|
-
.option('--size-gate <mode>', 'Size gate mode for this run: file (file child issues / park), slice (ship as sequential slice PRs under the same issue; never files issues), off (skip both gates) or stack (stacked PR set, ADR-0161; runs as slice until stacks are built); beats sizeGate.mode in the config')
|
|
4222
|
-
.option('--max-slices <count>', 'Slice mode: most slices a not-yet-started slice plan may have (1-20); an over-cap plan is recorded and parks; beats sizeGate.maxSlices (default 10)')
|
|
4223
|
-
.action(async (issueNum, opts) => {
|
|
4224
|
-
await cmdRunIssue(parseIssueArg(issueNum), opts);
|
|
4225
|
-
});
|
|
4226
|
-
program
|
|
4227
|
-
.command('run-brief <file>')
|
|
4228
|
-
.description('Like run-issue, but the work comes from a local Markdown brief instead of a GitHub issue')
|
|
4229
|
-
.option('--product <name>', 'Override active product constitution')
|
|
4230
|
-
.option('--no-auto-rework', 'Disable automatic rework loop')
|
|
4231
|
-
.option('--interactive', 'Pause before opening the PR and wait for approval from the TUI')
|
|
4232
|
-
.option('--approve-plan', 'Pause after PLAN freezes the spec and wait for approval before BUILD')
|
|
4233
|
-
.option('--no-sandbox', 'Disable the containment sandbox for agent runs (dangerous)')
|
|
4234
|
-
.option('--workspace <dir>', 'Local-only: run PLAN/BUILD/CHECK in this caller-provided git workspace and disable PR creation, merging, queue polling, and GitHub mutations')
|
|
4235
|
-
.option('--artifacts <dir>', 'Local-only: write a versioned benchmark artifact manifest (manifest.json, request.json, events.ndjson, diff.patch) to this directory; requires --workspace')
|
|
4236
|
-
.option('--branch-prefix <prefix>', BRANCH_PREFIX_OPTION_DESCRIPTION)
|
|
4237
|
-
.action(async (file, opts) => {
|
|
4238
|
-
await cmdRunBrief(file, opts);
|
|
4239
|
-
});
|
|
4240
|
-
program
|
|
4241
|
-
.command('local-small-dry-run <issue>')
|
|
4242
|
-
.description('Create a bounded local-small step plan and first context pack without changing source files')
|
|
4243
|
-
.option('--spec <path>', 'Frozen spec path; defaults to .factory/plans/issue-<n>.md')
|
|
4244
|
-
.option('--output <path>', 'Artifact directory; defaults to .factory/local-small/issue-<n>')
|
|
4245
|
-
.action(async (issueNum, opts) => {
|
|
4246
|
-
await cmdLocalSmallDryRun(parseIssueArg(issueNum), opts);
|
|
4247
|
-
});
|
|
4248
|
-
program
|
|
4249
|
-
.command('local-small-overnight')
|
|
4250
|
-
.description('Process a curated local-small queue one issue at a time: review-only PRs, never merges, parks on ambiguity')
|
|
4251
|
-
.option('--queue <path>', 'Curated queue file; defaults to .factory/local-small/overnight-queue')
|
|
4252
|
-
.option('--state <path>', 'Resume-state file; defaults to .factory/local-small/overnight-state.json')
|
|
4253
|
-
.action(async (opts) => {
|
|
4254
|
-
await cmdLocalSmallOvernight(opts);
|
|
4255
|
-
});
|
|
4256
|
-
program
|
|
4257
|
-
.command('land <issue>')
|
|
4258
|
-
.description("Squash-merge the issue's open PR once CI is green, then remove its worktree (left open if CI fails or review is pending)")
|
|
4259
|
-
.option('--branch-prefix <prefix>', BRANCH_PREFIX_MATCH_OPTION_DESCRIPTION)
|
|
4260
|
-
.option('--allow-gated', 'Merge even when the issue or PR carries the no-auto-merge gate label (human override; writes an AUDIT line and a merge-gated-override event)')
|
|
4261
|
-
.action(async (issueNum, opts) => {
|
|
4262
|
-
await cmdLand(parseIssueArg(issueNum), opts);
|
|
4263
|
-
});
|
|
4264
|
-
program
|
|
4265
|
-
.command('resume-approved')
|
|
4266
|
-
.description(`Land open factory PRs (factory/*, plus legacy ${LEGACY_BRANCH_PREFIX}/*) whose review is now approved; skip the rest`)
|
|
4267
|
-
.option('--branch-prefix <prefix>', BRANCH_PREFIX_MATCH_OPTION_DESCRIPTION)
|
|
4268
|
-
.action(async (opts) => {
|
|
4269
|
-
await cmdResumeApproved(opts);
|
|
4270
|
-
});
|
|
4271
|
-
program
|
|
4272
|
-
.command('run')
|
|
4273
|
-
.description('Claim queued issues (factory:queued labels) and ship them, lanes in parallel; merges only when auto-merge is on')
|
|
4274
|
-
.option('--local-queue', 'Read .factory/queue instead of claiming issues from GitHub Issues')
|
|
4275
|
-
.option('--auto-merge', 'Merge eligible PRs autonomously, overriding .factory/config.yaml and FACTORY_MERGE')
|
|
4276
|
-
.option('--no-auto-merge', 'Never merge autonomously, overriding .factory/config.yaml and FACTORY_MERGE')
|
|
4277
|
-
.option('--pr-classifier', 'Hold PRs whose review floor is B or C for a human (applies no-auto-merge), overriding .factory/config.json and FACTORY_PR_CLASSIFIER')
|
|
4278
|
-
.option('--no-pr-classifier', 'Never run the PR classifier gate, overriding .factory/config.json and FACTORY_PR_CLASSIFIER')
|
|
4279
|
-
.option('--usage-watch', 'Enforce the usage gate for this run, overriding .factory/config.yaml and FACTORY_USAGE_WATCH')
|
|
4280
|
-
.option('--no-usage-watch', 'Skip the usage gate for this run, overriding .factory/config.yaml and FACTORY_USAGE_WATCH')
|
|
4281
|
-
.option('--usage-threshold <fraction>', "Override .factory/config.yaml and FACTORY_STOP_AT for this run's usage threshold")
|
|
4282
|
-
.option('--usage-poll <seconds>', "Override .factory/config.yaml and FACTORY_USAGE_POLL for this run's usage poll")
|
|
4283
|
-
.option('--branch-prefix <prefix>', BRANCH_PREFIX_OPTION_DESCRIPTION)
|
|
4284
|
-
.action((opts) => cmdRun(opts));
|
|
4285
|
-
program
|
|
4286
|
-
.command('proxy')
|
|
4287
|
-
.description('Run the opt-in lane reverse proxy in the foreground (run and supervise start it themselves)')
|
|
4288
|
-
.action(cmdProxy);
|
|
4289
|
-
const daemonCmd = program
|
|
4290
|
-
.command('daemon')
|
|
4291
|
-
.description('Manage factoryd, the long-running background service for this user');
|
|
4292
|
-
daemonCmd
|
|
4293
|
-
.command('run')
|
|
4294
|
-
.description('Run factoryd in the foreground: a localhost-only HTTP API over the repo registry')
|
|
4295
|
-
.option('--port <n>', `Port to bind on 127.0.0.1 (default ${DEFAULT_FACTORYD_PORT})`)
|
|
4296
|
-
.option('--registry <file>', 'Registry file to serve (default ~/.factory/registry.json)')
|
|
4297
|
-
.option('--update-mode <mode>', 'Self-update channel: manual, stable (npm latest) or prerelease (npm next) (default: stable on npm installs, manual otherwise)')
|
|
4298
|
-
.option('--auto-update', 'Deprecated: same as --update-mode stable')
|
|
4299
|
-
.action((opts) => cmdFactoryd(opts));
|
|
4300
|
-
const daemonCtl = (fn) => fn().catch((err) => {
|
|
4301
|
-
if (err instanceof DaemonCtlError)
|
|
4302
|
-
throw new CliExitError(err.message, err.code);
|
|
4303
|
-
throw err;
|
|
4304
|
-
});
|
|
4305
|
-
daemonCmd
|
|
4306
|
-
.command('start')
|
|
4307
|
-
.description('Install and load the com.onpar.factoryd LaunchAgent (macOS only; KeepAlive, RunAtLoad)')
|
|
4308
|
-
.action(() => daemonCtl(() => cmdDaemonStart(deps.daemon)));
|
|
4309
|
-
daemonCmd
|
|
4310
|
-
.command('stop')
|
|
4311
|
-
.description('Unload the LaunchAgent (macOS only; plist stays installed)')
|
|
4312
|
-
.action(() => daemonCtl(() => cmdDaemonStop(deps.daemon)));
|
|
4313
|
-
daemonCmd
|
|
4314
|
-
.command('status')
|
|
4315
|
-
.description('Report factoryd pid, uptime, and attached repos')
|
|
4316
|
-
.option('--json', 'Print one JSON object (schemaVersion 1) instead of text')
|
|
4317
|
-
.action((opts) => daemonCtl(() => cmdDaemonStatus(deps.daemon, { json: opts.json })));
|
|
4318
|
-
daemonCmd
|
|
4319
|
-
.command('logs')
|
|
4320
|
-
.description('Print or tail ~/.factory/daemon.log')
|
|
4321
|
-
.option('-f, --follow', 'Keep tailing')
|
|
4322
|
-
.option('-n, --lines <n>', 'Lines to print first (default 100)')
|
|
4323
|
-
.action((opts) => daemonCtl(() => cmdDaemonLogs(opts, deps.daemon)));
|
|
4324
|
-
program
|
|
4325
|
-
.command('self-update')
|
|
4326
|
-
.description('Check npm for a newer factory release (read-only; nothing is installed)')
|
|
4327
|
-
.option('--check', 'Compare the running version with the npm dist-tag (required)')
|
|
4328
|
-
.option('--json', 'Print one JSON object instead of text')
|
|
4329
|
-
.option('--update-mode <mode>', 'Channel to check: stable (npm latest, default) or prerelease (npm next)')
|
|
4330
|
-
.action(async (opts) => {
|
|
4331
|
-
if (opts.check !== true)
|
|
4332
|
-
throw new CliExitError('factory self-update: only --check is supported', 2);
|
|
4333
|
-
process.exitCode = await daemonCtl(() => cmdSelfUpdateCheck(opts, deps.selfUpdate));
|
|
4334
|
-
});
|
|
4335
|
-
const worktreeCmd = program.command('worktree').description('Clean up stale factory worktrees (gc)');
|
|
4336
|
-
worktreeCmd
|
|
4337
|
-
.command('gc')
|
|
4338
|
-
.description('Remove stale factory worktrees (merged/closed branches or older than TTL) and scrub credentials')
|
|
4339
|
-
.option('--dry-run', 'Preview what would be removed without deleting anything')
|
|
4340
|
-
.option('--ttl-days <n>', 'Override worktree.gcTtlDays from .factory/config.yaml (default 7)')
|
|
4341
|
-
.option('--branch-prefix <prefix>', BRANCH_PREFIX_MATCH_OPTION_DESCRIPTION)
|
|
4342
|
-
.action(cmdWorktreeGc);
|
|
4343
|
-
program
|
|
4344
|
-
.command('reset <issue...>')
|
|
4345
|
-
.description("Remove an issue's local factory state (worktree, local branch, plan/phase files, rework history, logs, leases) so the next run starts fresh. A worktree or branch with uncommitted changes or unpushed commits is kept unless --force is passed. Do not run while that issue's lane is active. Preview with --dry-run")
|
|
4346
|
-
.option('--dry-run', 'List every worktree, branch, file, history entry, log dir, claim and lease that would be removed, without changing anything')
|
|
4347
|
-
.option('--force', 'Remove worktrees and branches even when they have uncommitted changes or unpushed commits')
|
|
4348
|
-
.option('--branch-prefix <prefix>', BRANCH_PREFIX_MATCH_OPTION_DESCRIPTION)
|
|
4349
|
-
.action(cmdReset);
|
|
4350
|
-
program
|
|
4351
|
-
.command('supervise')
|
|
4352
|
-
.description('Unattended loop: wait for usage headroom, run the queue, repeat until it is empty')
|
|
4353
|
-
.option('--now', 'Skip the initial headroom wait')
|
|
4354
|
-
.option('--local-queue', 'Read .factory/queue instead of claiming issues from GitHub Issues')
|
|
4355
|
-
.option('--auto-merge', 'Merge eligible PRs autonomously, overriding .factory/config.yaml and FACTORY_MERGE')
|
|
4356
|
-
.option('--no-auto-merge', 'Never merge autonomously, overriding .factory/config.yaml and FACTORY_MERGE')
|
|
4357
|
-
.option('--usage-watch', 'Enforce the usage gate for this run, overriding .factory/config.yaml and FACTORY_USAGE_WATCH')
|
|
4358
|
-
.option('--no-usage-watch', 'Skip the usage gate for this run, overriding .factory/config.yaml and FACTORY_USAGE_WATCH')
|
|
4359
|
-
.option('--usage-threshold <fraction>', "Override .factory/config.yaml and FACTORY_RESUME_AT for this run's usage threshold")
|
|
4360
|
-
.option('--usage-poll <seconds>', "Override .factory/config.yaml and FACTORY_USAGE_POLL for this run's usage poll")
|
|
4361
|
-
.option('--branch-prefix <prefix>', BRANCH_PREFIX_OPTION_DESCRIPTION)
|
|
4362
|
-
.action(async (opts) => {
|
|
4363
|
-
await cmdSupervise(opts);
|
|
4364
|
-
});
|
|
4365
|
-
program
|
|
4366
|
-
.command('stop')
|
|
4367
|
-
.description('Tell running lanes to halt after their current issue')
|
|
4368
|
-
.option('--json', 'Print one JSON object (schemaVersion 1): action, repo, stopFlag before/after')
|
|
4369
|
-
.action((opts) => cmdStop(opts));
|
|
4370
|
-
program
|
|
4371
|
-
.command('resume [issue]')
|
|
4372
|
-
.description('Clear a stop so lanes pick up new issues again; with <issue> --from check, re-run CHECK on a build kept after an environment failure')
|
|
4373
|
-
.option('--json', 'Print one JSON object (schemaVersion 1): action, repo, stopFlag before/after')
|
|
4374
|
-
.option('--from <phase>', 'Resume a kept build at this phase (only: check)')
|
|
4375
|
-
.action((issue, opts) => cmdResume(issue, opts));
|
|
4376
|
-
applyHelpGroups(program);
|
|
4008
|
+
const program = buildProgram(PROGRAM_HANDLERS, deps);
|
|
4377
4009
|
const previousLoaders = configLoaders;
|
|
4378
4010
|
const previousGitOps = gitOps;
|
|
4379
4011
|
const previousInternalOps = internalOps;
|