@on-par/factory-cli 0.9.0 → 0.9.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/README.md +22 -5
- package/dist/cli/base-hold.d.ts +63 -0
- package/dist/cli/base-hold.d.ts.map +1 -0
- package/dist/cli/base-hold.js +122 -0
- package/dist/cli/base-hold.js.map +1 -0
- package/dist/cli/daemon.d.ts +111 -4
- package/dist/cli/daemon.d.ts.map +1 -1
- package/dist/cli/daemon.js +364 -6
- package/dist/cli/daemon.js.map +1 -1
- package/dist/cli/doctor.d.ts +11 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +55 -6
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/feedback.d.ts +18 -2
- package/dist/cli/feedback.d.ts.map +1 -1
- package/dist/cli/feedback.js +38 -4
- package/dist/cli/feedback.js.map +1 -1
- package/dist/cli/help-groups.d.ts.map +1 -1
- package/dist/cli/help-groups.js +10 -1
- package/dist/cli/help-groups.js.map +1 -1
- package/dist/cli/index.d.ts +24 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +222 -43
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/self-update.d.ts +68 -0
- package/dist/cli/self-update.d.ts.map +1 -0
- package/dist/cli/self-update.js +167 -0
- package/dist/cli/self-update.js.map +1 -0
- package/dist/cli/state-root.d.ts +5 -0
- package/dist/cli/state-root.d.ts.map +1 -0
- package/dist/cli/state-root.js +12 -0
- package/dist/cli/state-root.js.map +1 -0
- package/dist/cli/status-json.d.ts +4 -0
- package/dist/cli/status-json.d.ts.map +1 -1
- package/dist/cli/status-json.js +1 -0
- package/dist/cli/status-json.js.map +1 -1
- package/dist/cli/update-state.d.ts +46 -0
- package/dist/cli/update-state.d.ts.map +1 -0
- package/dist/cli/update-state.js +158 -0
- package/dist/cli/update-state.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/entry.d.ts +1 -1
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +9 -2
- package/dist/entry.js.map +1 -1
- package/dist/node-version.d.ts +10 -0
- package/dist/node-version.d.ts.map +1 -0
- package/dist/node-version.js +15 -0
- package/dist/node-version.js.map +1 -0
- package/package.json +12 -4
package/dist/cli/index.js
CHANGED
|
@@ -5,16 +5,17 @@ import { randomUUID } from 'node:crypto';
|
|
|
5
5
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, unlinkSync, writeFileSync, } from 'node:fs';
|
|
6
6
|
import { createRequire } from 'node:module';
|
|
7
7
|
import { arch, hostname as osHostname, release, platform, userInfo } from 'node:os';
|
|
8
|
-
import { basename, dirname, relative, resolve } from 'node:path';
|
|
8
|
+
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
9
9
|
import { promisify, styleText } from 'node:util';
|
|
10
10
|
import { BRANCH_PREFIX_MATCH_OPTION_DESCRIPTION, BRANCH_PREFIX_OPTION_DESCRIPTION, INVALID_BRANCH_PREFIX_MESSAGE, resolveBranchPrefixOption, } from './branch-prefix.js';
|
|
11
|
-
import { acquirePortLease, aggregateCosts, appendKpiHistoryLine, applyRepoConfig, buildPhase, checkPhase, clearProxyState, closedWorkSkipReason, computeHealthKpis, ConstitutionLoader, createDefaultWorkSourceRegistry, createFileApprovalGate, createLogger, createLaneProxy, defaultFindPortListeners, describeEffectiveConfig, detectPostMergeDefects, diagnoseModels, drainSteering, estimateTrailingSpend, fetchDefectSources, fetchHumanEventSources, fetchSubscriptionUsage, formatKpiLines, formatUsageReport, getConstitutionsDir, getFactoryPaths, GITHUB_ISSUE_SOURCE, hasUnresolvedPark, InvalidArtifactsDirError, InvalidWorkRequestInputError, InvalidWorkspaceError, inspectPortLeases, isCommandAvailable, isProxyRunning, kpisToHistoryRecord, laneBaseUrl, laneEnv, LaneFileGuard, laneHostLabel, listQueuedSteering, loadFactoryConfigForRepo, loadModelsConfig, loadRepoConfig, loadRoutesConfig, LOCAL_BRIEF_SOURCE, localOnlyWorkspace, joinClassifierOutcomes, mergeClassifierOutcomes, parseClassifierOutcomes, summarizeClassifierOutcomes, formatClassifierReport, mergedPrRefs, ModelRegistry, ModelRouter, parkReasonFor, parseKpiHistory, parseQueue, partitionLocalQueueByActivity, phaseSnapshotFile, planPhase, provisionLaneContainer, ProviderBreaker, readEvents, readPortLeases, readQueue, readUsage, reapOrphanProcesses, reapStalePortLeases,
|
|
12
|
-
import { acquirePidFile, currentSlice, egressSummaryMessage, findSlicePlanComment, 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, branchFor, claudeConfigDirOverride, claudeKeychainService, cleanupWorktree, createDaemonLogSink, factoryBranchIssue, factoryBranchPrefixes, getIssueTitle, LEGACY_BRANCH_PREFIX, createDockerEngine, createFactorydServer, createRunRuntime, createShipExecutor, createGithubQueue, createLocalSmallDryRun, createOctokitFilingClient, createOctokitGreenPrClient, createOctokitQueueClient, daemonRuntimePaths, DEFAULT_FACTORYD_PORT, ensureDir, FACTORY_RUNTIME_CONFIG_KEYS, findUnmergedGreenPrs, formatGcReport, gitFetch, isAutoMergeBlocked, listMicroVms, listOrphanContainers, logCost, logEvent, microVmName, planQueueMigration, readCosts, readGithubQueueSnapshot, reapLaneWorktree, reapOrphanContainers, reapOrphanMicroVm, releaseRuntimeFiles, releaseStaleClaims, resolveBranchPrefix, resolveEffectiveConfig, worktreeSandboxFor, resolveExperimental, resolveFilingPolicy, resolveLocalOnly, resolvePrClassifierPolicy, resolveReviewFloorRules, RunLockHeldError, runOvernightQueue, findOpenPR, setupWorktree, shellEscape, shouldProxyEgress, slugify, startEgressProxy, sweepWorktrees, watchChecks, withAdmissionGuard, withFileLock, withGitLock, withRunLock, wrapCommandInSandbox, writePortFile, } from '@on-par/factory-core/internal';
|
|
11
|
+
import { acquirePortLease, aggregateCosts, appendKpiHistoryLine, applyRepoConfig, buildPhase, checkPhase, clearProxyState, closedWorkSkipReason, computeHealthKpis, ConstitutionLoader, createDefaultWorkSourceRegistry, createFileApprovalGate, createLogger, createLaneProxy, defaultFindPortListeners, describeEffectiveConfig, detectPostMergeDefects, diagnoseModels, drainSteering, estimateTrailingSpend, fetchDefectSources, fetchHumanEventSources, fetchSubscriptionUsage, formatKpiLines, formatUsageReport, getConstitutionsDir, getFactoryPaths, GITHUB_ISSUE_SOURCE, hasUnresolvedPark, InvalidArtifactsDirError, InvalidWorkRequestInputError, InvalidWorkspaceError, inspectPortLeases, isCommandAvailable, isProxyRunning, kpiBranchPrefixes, kpisToHistoryRecord, laneBaseUrl, laneEnv, LaneFileGuard, laneHostLabel, listQueuedSteering, loadFactoryConfigForRepo, loadModelsConfig, loadRepoConfig, loadRoutesConfig, LOCAL_BRIEF_SOURCE, localOnlyWorkspace, joinClassifierOutcomes, mergeClassifierOutcomes, parseClassifierOutcomes, summarizeClassifierOutcomes, formatClassifierReport, mergedPrRefs, ModelRegistry, ModelRouter, parkReasonFor, parseKpiHistory, parseQueue, partitionLocalQueueByActivity, phaseSnapshotFile, planPhase, provisionLaneContainer, ProviderBreaker, readEvents, readPortLeases, readQueue, readUsage, reapOrphanProcesses, reapStalePortLeases, reconstructKpiEvents, recordLeasePgid, releasePortLease, renderKpiReport, renderKpiTrend, resolveAutoFailover, resolveCodexDisabled, resolveDefectWindowDays, resolveBuildPublish, resolveSizeGateMode, parseSizeGateMode, parseMaxSlices, resolveSizeGateMaxSlices, SIZE_GATE_MODES, resolveDesignRegressionBlock, resolveReadinessUpdateIssueBody, resolveEffectiveModelPins, resolveEfficiencyPolicy, resolveLaneBreakerThreshold, resolveEnvironmentPorts, resolveEnvironmentProxy, resolveArtifactsDir, resolveIngestConfig, resolveLocalOnlyPolicy, resolveMergePolicy, resolvePlanApproval, resolveProcessGroupGraceMs, resolveSandboxPolicy, resolveSkipCI, resolveTimeouts, resolveUsageCap, resolveWatchdogPolicy, ReworkHistory, rewriteQueueForDecomposition, runAutoIngest, runIssue, shipPhase, summarizeEvent, formatCostTotal, touchLastEvent, touchRunActivity, validateQueue, watchUsage, worktreeWorkspace, writeBenchmarkArtifacts, writeLocalRunReport, writePhaseSnapshot, writeProxyState, } from '@on-par/factory-core';
|
|
12
|
+
import { acquirePidFile, createPlanReuseStore, checkBaseHealth, currentSlice, egressSummaryMessage, findSlicePlanComment, 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, branchFor, claudeConfigDirOverride, claudeKeychainService, cleanupWorktree, createDaemonLogSink, factoryBranchIssue, factoryBranchPrefixes, getIssueTitle, LEGACY_BRANCH_PREFIX, createDockerEngine, createFactorydServer, createRunRuntime, createShipExecutor, createGithubQueue, createLocalSmallDryRun, createOctokitFilingClient, createOctokitGreenPrClient, createOctokitQueueClient, daemonRuntimePaths, DEFAULT_FACTORYD_PORT, defaultRegistryPath, ensureDir, FACTORY_RUNTIME_CONFIG_KEYS, findUnmergedGreenPrs, formatGcReport, gitFetch, isAutoMergeBlocked, listMicroVms, listOrphanContainers, logCost, logEvent, microVmName, planQueueMigration, readCosts, readGithubQueueSnapshot, reapLaneWorktree, reapOrphanContainers, reapOrphanMicroVm, releaseRuntimeFiles, releaseStaleClaims, resolveBranchPrefix, resolveEffectiveConfig, worktreeSandboxFor, resolveExperimental, resolveFilingPolicy, resolveLocalOnly, resolvePrClassifierPolicy, resolveReviewFloorRules, RunLockHeldError, runOvernightQueue, findOpenPR, parseRemoteSlug, setupWorktree, shellEscape, shouldProxyEgress, slugify, startEgressProxy, sweepWorktrees, watchChecks, watchChecksWithInfraRerun, withAdmissionGuard, withFileLock, withGitLock, withRunLock, wrapCommandInSandbox, writePortFile, } from '@on-par/factory-core/internal';
|
|
13
13
|
import { runTui } from '@on-par/factory-tui';
|
|
14
14
|
import { Command } from 'commander';
|
|
15
15
|
import { admissionStateReaderFor, runQueueReconcile } from './admission.js';
|
|
16
16
|
import { cmdClassifierBacktest, ClassifierBacktestInputError, } from './classifier-backtest.js';
|
|
17
17
|
import { runQueueClear } from './queue-clear.js';
|
|
18
|
+
import { applyStateRootOption } from './state-root.js';
|
|
18
19
|
import { buildCostJson } from './cost-json.js';
|
|
19
20
|
import { buildQueueAddJson } from './queue-add-json.js';
|
|
20
21
|
import { buildQueueListJson } from './queue-list.js';
|
|
@@ -22,17 +23,19 @@ import { runUnpark, UnparkError } from './unpark.js';
|
|
|
22
23
|
import { buildRunsJson, parseRunsLimit } from './runs.js';
|
|
23
24
|
import { buildRunDiffstatJson, NoRunError, parseDiffstatIssue } from './runs-diffstat.js';
|
|
24
25
|
import { buildStatusJson } from './status-json.js';
|
|
26
|
+
import { buildStatusUpdateJson, formatUpdateStartupLine, formatUpdateStatusLine, initialUpdateState, readUpdateState, updateStatePath, writeUpdateState, } from './update-state.js';
|
|
25
27
|
import { buildStopResumeJson } from './stop-resume-json.js';
|
|
26
28
|
import { buildUsageJson } from './usage-json.js';
|
|
27
29
|
import { parseResetIssues, runReset } from './reset.js';
|
|
28
|
-
import { cmdDaemonLogs, cmdDaemonStart, cmdDaemonStatus, cmdDaemonStop, DaemonCtlError, resolveFactorydConfig, } from './daemon.js';
|
|
29
|
-
import {
|
|
30
|
+
import { cmdDaemonLogs, cmdDaemonStart, cmdDaemonStatus, cmdDaemonStop, createAutoUpdateDeps, DaemonCtlError, detectInstall, loadDaemonFileConfig, recoverStaleDrain, resolveFactorydConfig, runAutoUpdateLoop, } from './daemon.js';
|
|
31
|
+
import { checkForUpdate, cmdSelfUpdateCheck } from './self-update.js';
|
|
32
|
+
import { analyzeEventLog, claudeKeychainCheck, doctorFailed, eventLogCheck, formatClaimReconcileReport, formatContainerReconcileReport, formatDoctorChecks, formatMicroVmReconcileReport, formatReconcileReport, formatWorktreeReconcileReport, hasSandboxRuntime, leaseChecks, keychainPreflightError, orphanContainerChecks, orphanMicroVmChecks, runDoctorChecks, sandboxAuthSkipDetail, sandboxClaudeAuthChecks, unmergedGreenPrChecks, } from './doctor.js';
|
|
30
33
|
import { DOCS_URL, formatOverview, missingClaudeCliMessage, missingTokenMessage, notInitializedMessage, } from './first-run.js';
|
|
31
34
|
import { cmdHostedSmoke } from './hosted.js';
|
|
32
35
|
import { cmdHostedQueue } from './hosted-queue.js';
|
|
33
36
|
import { cmdHostedRunner } from './hosted-runner.js';
|
|
34
37
|
import { resolveFactoryCheckoutCommit, runFilingPreview } from './filing.js';
|
|
35
|
-
import { buildFeedbackDeps, runFeedback } from './feedback.js';
|
|
38
|
+
import { buildFeedbackDeps, resolveFeedbackRepo, runFeedback } from './feedback.js';
|
|
36
39
|
import { ensureFactoryExcluded, writeSampleQueue } from './init-files.js';
|
|
37
40
|
import { cmdLogs } from './logs.js';
|
|
38
41
|
import { applyHelpGroups } from './help-groups.js';
|
|
@@ -40,6 +43,7 @@ import { mergeScopeNotice } from './merge-scope.js';
|
|
|
40
43
|
import { createFactoryOctokit, formatDeprecation } from './octokit.js';
|
|
41
44
|
import { DeepCheckError, createDeepModelRunner, runIssueCheck } from './ready-check.js';
|
|
42
45
|
import { childRunSucceeded, formatChildRunSummary, runChildrenInOrder } from './run-children.js';
|
|
46
|
+
import { createBaseHoldGate, formatBaseHoldCleared, formatBaseHoldStatus, readBaseHold, writeBaseHold, } from './base-hold.js';
|
|
43
47
|
import { readRunFlagOverrides, writeRunFlagOverrides } from './run-flags.js';
|
|
44
48
|
import { distFreshnessProbe, runStalenessGuard } from './staleness.js';
|
|
45
49
|
import { readStopFileStatus, stopSentinelCheck, stopSentinelRunSkipMessage } from './stop-sentinel.js';
|
|
@@ -487,10 +491,34 @@ export async function runFeedbackCommand(prUrl, opts, deps, log = console.log) {
|
|
|
487
491
|
throw err;
|
|
488
492
|
}
|
|
489
493
|
}
|
|
494
|
+
/** owner/name of the checkout's origin remote, or null when there is none or it is not a GitHub remote. Never throws. */
|
|
495
|
+
export async function readOriginSlug(shell = shellOps) {
|
|
496
|
+
try {
|
|
497
|
+
const { stdout } = await shell.exec('git remote get-url origin');
|
|
498
|
+
return parseRemoteSlug(stdout.trim());
|
|
499
|
+
}
|
|
500
|
+
catch {
|
|
501
|
+
return null;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
490
504
|
export async function cmdFeedback(prUrl, opts) {
|
|
491
505
|
const paths = getFactoryPaths(await getRepoRoot());
|
|
492
506
|
const prefix = opts.branchPrefix === undefined ? resolveBranchPrefix() : branchPrefixOrExit(opts.branchPrefix);
|
|
493
|
-
|
|
507
|
+
let repo;
|
|
508
|
+
try {
|
|
509
|
+
repo = resolveFeedbackRepo({
|
|
510
|
+
flag: opts.repo,
|
|
511
|
+
config: configLoaders.loadFactoryConfigForRepo(paths.config).filing.repo,
|
|
512
|
+
remote: await readOriginSlug(),
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
catch (err) {
|
|
516
|
+
throw new CliExitError(err instanceof Error ? err.message : String(err), 2);
|
|
517
|
+
}
|
|
518
|
+
await runFeedbackCommand(prUrl, opts, buildFeedbackDeps(getOctokit(), paths, prefix, createOctokitFilingClient, {
|
|
519
|
+
repo,
|
|
520
|
+
shareLocal: opts.includeLocalPaths === true,
|
|
521
|
+
}));
|
|
494
522
|
}
|
|
495
523
|
async function resolveFactoryCommit() {
|
|
496
524
|
return resolveFactoryCheckoutCommit(dirname(fileURLToPath(import.meta.url)), async (cmd, cwd) => (await shellOps.exec(cmd, { cwd })).stdout);
|
|
@@ -809,10 +837,24 @@ function readTextFileOrEmpty(path) {
|
|
|
809
837
|
throw err;
|
|
810
838
|
}
|
|
811
839
|
}
|
|
812
|
-
|
|
840
|
+
/** Local events plus human-* events reconstructed from GitHub; warns and marks the result partial when the fetch fails (#2342). */
|
|
841
|
+
async function reconstructHumanEventsForKpis(events, branchPrefix) {
|
|
842
|
+
let owner = '';
|
|
843
|
+
let repoName = '';
|
|
844
|
+
const recon = await reconstructKpiEvents(events, async (issues) => {
|
|
845
|
+
[owner, repoName] = (await getGitHubRepo()).split('/');
|
|
846
|
+
return fetchHumanEventSources(getOctokit(), owner, repoName, issues, kpiBranchPrefixes(branchPrefix));
|
|
847
|
+
});
|
|
848
|
+
if (recon.partial) {
|
|
849
|
+
console.error(styleText('yellow', `factory: GitHub human-event reconstruction failed (${recon.error}) — KPI snapshot marked partial (local log only)`));
|
|
850
|
+
}
|
|
851
|
+
return { ...recon, owner, repoName };
|
|
852
|
+
}
|
|
853
|
+
async function appendKpiSnapshot(paths, repoRoot, kpis, opts = {}) {
|
|
813
854
|
const record = kpisToHistoryRecord(kpis, new Date().toISOString().slice(0, 10), {
|
|
814
855
|
commitSha: await currentCommitSha(),
|
|
815
856
|
models: resolvedModelTiers(repoRoot, paths.root),
|
|
857
|
+
partial: opts.partial,
|
|
816
858
|
});
|
|
817
859
|
const updated = appendKpiHistoryLine(readTextFileOrEmpty(paths.kpiHistory), record);
|
|
818
860
|
ensureDir(paths.state);
|
|
@@ -938,20 +980,10 @@ async function cmdKpis(opts = {}) {
|
|
|
938
980
|
const paths = getFactoryPaths(repoRoot);
|
|
939
981
|
const events = existsSync(paths.events) ? readEvents(paths.events) : [];
|
|
940
982
|
const costs = existsSync(paths.costs) ? internalOps.readCosts(paths.costs) : [];
|
|
941
|
-
|
|
942
|
-
let
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
try {
|
|
946
|
-
const ghRepo = await getGitHubRepo();
|
|
947
|
-
[owner, repoName] = ghRepo.split('/');
|
|
948
|
-
const issues = new Set(events.map((e) => e.issue).filter((i) => /^\d+$/.test(i)));
|
|
949
|
-
prSources = await fetchHumanEventSources(getOctokit(), owner, repoName, issues, branchPrefix);
|
|
950
|
-
allEvents = [...events, ...reconstructHumanEvents(prSources, events)];
|
|
951
|
-
}
|
|
952
|
-
catch (err) {
|
|
953
|
-
console.error(styleText('yellow', `factory: GitHub human-event reconstruction unavailable (${err?.message ?? err}) — KPIs use the local log only`));
|
|
954
|
-
}
|
|
983
|
+
const recon = await reconstructHumanEventsForKpis(events, branchPrefix);
|
|
984
|
+
let allEvents = recon.events;
|
|
985
|
+
const prSources = recon.sources;
|
|
986
|
+
const { owner, repoName } = recon;
|
|
955
987
|
if (prSources.length > 0) {
|
|
956
988
|
try {
|
|
957
989
|
const windowDays = resolveDefectWindowDays(configLoaders.loadFactoryConfigForRepo(paths.config));
|
|
@@ -973,7 +1005,7 @@ async function cmdKpis(opts = {}) {
|
|
|
973
1005
|
const kpis = computeHealthKpis(allEvents, costs);
|
|
974
1006
|
let history;
|
|
975
1007
|
try {
|
|
976
|
-
({ history } = await appendKpiSnapshot(paths, repoRoot, kpis));
|
|
1008
|
+
({ history } = await appendKpiSnapshot(paths, repoRoot, kpis, { partial: recon.partial }));
|
|
977
1009
|
}
|
|
978
1010
|
catch (err) {
|
|
979
1011
|
console.error(styleText('yellow', `factory: KPI snapshot failed (${err?.message ?? err}) — showing the report without persisting a new snapshot`));
|
|
@@ -1085,6 +1117,7 @@ export async function cmdStatus(opts = {}) {
|
|
|
1085
1117
|
stop: existsSync(paths.stop),
|
|
1086
1118
|
breakers: await new ProviderBreaker(paths.breaker).list(),
|
|
1087
1119
|
active,
|
|
1120
|
+
update: buildStatusUpdateJson(readUpdateState(updateStatePath(daemonRuntimePaths().dir))),
|
|
1088
1121
|
now: Date.now(),
|
|
1089
1122
|
})));
|
|
1090
1123
|
return;
|
|
@@ -1128,6 +1161,12 @@ export async function cmdStatus(opts = {}) {
|
|
|
1128
1161
|
else {
|
|
1129
1162
|
console.log(' (no queue file)');
|
|
1130
1163
|
}
|
|
1164
|
+
const baseHold = readBaseHold(paths.baseHold);
|
|
1165
|
+
if (baseHold !== null && baseHold.repo === ghRepo) {
|
|
1166
|
+
console.log(styleText('bold', '\n== Base hold =='));
|
|
1167
|
+
for (const line of formatBaseHoldStatus(baseHold))
|
|
1168
|
+
console.log(line);
|
|
1169
|
+
}
|
|
1131
1170
|
console.log(styleText('bold', '\n== Queue =='));
|
|
1132
1171
|
if (!hasGitHubToken()) {
|
|
1133
1172
|
console.log(' (no GitHub token — run `gh auth login`)');
|
|
@@ -1154,6 +1193,8 @@ export async function cmdStatus(opts = {}) {
|
|
|
1154
1193
|
}
|
|
1155
1194
|
}
|
|
1156
1195
|
console.log(styleText('bold', '\n== Health =='));
|
|
1196
|
+
const update = buildStatusUpdateJson(readUpdateState(updateStatePath(daemonRuntimePaths().dir)));
|
|
1197
|
+
console.log(` ${formatUpdateStatusLine(update)}`);
|
|
1157
1198
|
if (opts.kpis) {
|
|
1158
1199
|
console.log(styleText('bold', '\n Effective config:'));
|
|
1159
1200
|
for (const line of describeEffectiveConfig({
|
|
@@ -1504,6 +1545,7 @@ export async function shipIssue(issueNum, opts, ctx, deps = {}) {
|
|
|
1504
1545
|
'held',
|
|
1505
1546
|
'conflict',
|
|
1506
1547
|
'ci-failed',
|
|
1548
|
+
'ci-infra',
|
|
1507
1549
|
'timeout',
|
|
1508
1550
|
]);
|
|
1509
1551
|
let terminalMessage;
|
|
@@ -1645,6 +1687,7 @@ export async function shipIssue(issueNum, opts, ctx, deps = {}) {
|
|
|
1645
1687
|
},
|
|
1646
1688
|
localOnly: Boolean(ctx?.localOnly),
|
|
1647
1689
|
blockUnresolvedRegressions: resolveDesignRegressionBlock(factoryConfig),
|
|
1690
|
+
readinessUpdateIssueBody: resolveReadinessUpdateIssueBody(factoryConfig),
|
|
1648
1691
|
publishFromBuild: resolveBuildPublish(factoryConfig),
|
|
1649
1692
|
sizeGateMode,
|
|
1650
1693
|
maxSlices,
|
|
@@ -1678,10 +1721,12 @@ export async function shipIssue(issueNum, opts, ctx, deps = {}) {
|
|
|
1678
1721
|
resolveBaseUrl,
|
|
1679
1722
|
getIssueSpend: () => issueSpend,
|
|
1680
1723
|
breaker,
|
|
1724
|
+
planReuse: ctx?.localOnly ? undefined : createPlanReuseStore(specPath),
|
|
1681
1725
|
planPhase: phases.planPhase,
|
|
1682
1726
|
buildPhase: phases.buildPhase,
|
|
1683
1727
|
checkPhase: phases.checkPhase,
|
|
1684
1728
|
shipPhase: phases.shipPhase,
|
|
1729
|
+
checkBaseHealth: deps.checkBaseHealth ?? checkBaseHealth,
|
|
1685
1730
|
resolveConstitution: () =>
|
|
1686
1731
|
// Resolved once here — runIssue calls this exactly once and reuses the value for
|
|
1687
1732
|
// every phase, so the build worker can never author the standards it is graded by.
|
|
@@ -2518,8 +2563,12 @@ async function cmdTriage(opts) {
|
|
|
2518
2563
|
const routesConfig = configLoaders.loadRoutesConfig();
|
|
2519
2564
|
const effective = resolveEffectiveConfig(repoConfig);
|
|
2520
2565
|
const router = new coreOps.ModelRouter(modelsConfig, routesConfig, false, undefined, effective.allowExperimental, effective.localOnly);
|
|
2521
|
-
|
|
2522
|
-
const
|
|
2566
|
+
// Triage always runs `claude -p`, so only claude-cli models with a flag are runnable.
|
|
2567
|
+
const registry = router.registryRef;
|
|
2568
|
+
const resolved = router.resolveAll('triage');
|
|
2569
|
+
const candidates = resolved.length > 0 ? resolved : ['claude-sonnet-5'];
|
|
2570
|
+
const model = candidates.find((m) => registry.getHarnessId(m) === 'claude-cli' && registry.getClaudeFlag(m));
|
|
2571
|
+
const flag = model ? registry.getClaudeFlag(model) : undefined;
|
|
2523
2572
|
const constitutionNote = product ? `Active constitution: ${product}.` : '';
|
|
2524
2573
|
const prompt = `Triage the open GitHub issues of ${ghRepo} for an autonomous software factory.
|
|
2525
2574
|
${constitutionNote}
|
|
@@ -2529,7 +2578,7 @@ and anything too vague. Group by lane (same-file issues together). Order by depe
|
|
|
2529
2578
|
Write ONLY the queue to ${paths.queueProposed} in format '<lane> <issue#>', with '#' comments
|
|
2530
2579
|
explaining exclusions.`;
|
|
2531
2580
|
let plannerError;
|
|
2532
|
-
logEvent(paths.events, 'triage', '-', `Triaging ${ghRepo} with ${model}`);
|
|
2581
|
+
logEvent(paths.events, 'triage', '-', `Triaging ${ghRepo} with ${flag ?? 'claude default model'}`);
|
|
2533
2582
|
await shellOps
|
|
2534
2583
|
.exec(`claude -p ${shellEscape(prompt)} ${flag ? `--model ${flag}` : ''} --allowedTools "Bash(gh issue:*)" "Bash(gh repo:*)" Read Glob Grep Write`)
|
|
2535
2584
|
.catch((err) => {
|
|
@@ -2915,7 +2964,9 @@ async function cmdProxy() {
|
|
|
2915
2964
|
async function cmdFactoryd(opts) {
|
|
2916
2965
|
let config;
|
|
2917
2966
|
try {
|
|
2918
|
-
|
|
2967
|
+
const registryFile = opts.registry ?? defaultRegistryPath();
|
|
2968
|
+
const fileConfig = loadDaemonFileConfig(join(dirname(registryFile), 'daemon.json'));
|
|
2969
|
+
config = resolveFactorydConfig({ ...opts, registry: registryFile }, { install: detectInstall(), fileConfig });
|
|
2919
2970
|
}
|
|
2920
2971
|
catch (err) {
|
|
2921
2972
|
if (err instanceof DaemonCtlError)
|
|
@@ -2935,6 +2986,29 @@ async function cmdFactoryd(opts) {
|
|
|
2935
2986
|
};
|
|
2936
2987
|
if (acquired.stalePid !== null)
|
|
2937
2988
|
log(`removed stale pid file (pid ${acquired.stalePid})`);
|
|
2989
|
+
for (const notice of config.notices)
|
|
2990
|
+
log(notice);
|
|
2991
|
+
const updateFile = updateStatePath(runtime.dir);
|
|
2992
|
+
const previousUpdate = readUpdateState(updateFile);
|
|
2993
|
+
for (const stop of recoverStaleDrain(previousUpdate, (file) => {
|
|
2994
|
+
try {
|
|
2995
|
+
unlinkSync(file);
|
|
2996
|
+
return true;
|
|
2997
|
+
}
|
|
2998
|
+
catch {
|
|
2999
|
+
return false;
|
|
3000
|
+
}
|
|
3001
|
+
})) {
|
|
3002
|
+
log(`update: removed stale STOP ${stop}`);
|
|
3003
|
+
}
|
|
3004
|
+
const updateState = initialUpdateState(config, previousUpdate);
|
|
3005
|
+
try {
|
|
3006
|
+
writeUpdateState(updateFile, updateState);
|
|
3007
|
+
}
|
|
3008
|
+
catch (err) {
|
|
3009
|
+
log(`could not write ${updateFile}: ${errorDetail(err)}`);
|
|
3010
|
+
}
|
|
3011
|
+
log(formatUpdateStartupLine(updateState));
|
|
2938
3012
|
const runRuntime = await createRunRuntime({
|
|
2939
3013
|
registryFile,
|
|
2940
3014
|
allowParallel: true,
|
|
@@ -2946,6 +3020,8 @@ async function cmdFactoryd(opts) {
|
|
|
2946
3020
|
const banner = [
|
|
2947
3021
|
`factoryd: listening on 127.0.0.1:${boundPort}`,
|
|
2948
3022
|
` registry: ${registryFile}`,
|
|
3023
|
+
` update mode: ${config.autoUpdate.mode} (channel ${config.autoUpdate.channel ?? 'none'}, install ${config.install.kind})`,
|
|
3024
|
+
` update state: ${updateFile}`,
|
|
2949
3025
|
` pid file: ${runtime.pidFile} (pid ${process.pid})`,
|
|
2950
3026
|
` port file: ${runtime.portFile}`,
|
|
2951
3027
|
` log file: ${runtime.logFile}`,
|
|
@@ -2957,8 +3033,26 @@ async function cmdFactoryd(opts) {
|
|
|
2957
3033
|
console.log(line);
|
|
2958
3034
|
for (const line of banner)
|
|
2959
3035
|
logSink(line);
|
|
3036
|
+
let shuttingDown = false;
|
|
3037
|
+
void runAutoUpdateLoop(config, {
|
|
3038
|
+
...createAutoUpdateDeps({
|
|
3039
|
+
registryFile,
|
|
3040
|
+
stateFile: updateFile,
|
|
3041
|
+
cliEntrypoint: fileURLToPath(new URL('../cli.js', import.meta.url)),
|
|
3042
|
+
check: () => checkForUpdate(config.autoUpdate.mode === 'prerelease' ? 'prerelease' : 'stable'),
|
|
3043
|
+
runRuntime,
|
|
3044
|
+
log,
|
|
3045
|
+
exit: async (code) => {
|
|
3046
|
+
await daemon.stop().catch(() => { });
|
|
3047
|
+
await releaseRuntimeFiles(runtime);
|
|
3048
|
+
process.exit(code);
|
|
3049
|
+
},
|
|
3050
|
+
}),
|
|
3051
|
+
stopped: () => shuttingDown,
|
|
3052
|
+
});
|
|
2960
3053
|
await new Promise((resolveShutdown) => {
|
|
2961
3054
|
const shutdown = () => {
|
|
3055
|
+
shuttingDown = true;
|
|
2962
3056
|
void daemon
|
|
2963
3057
|
.stop()
|
|
2964
3058
|
.catch(() => { })
|
|
@@ -3083,6 +3177,20 @@ async function cmdRun(opts = {}) {
|
|
|
3083
3177
|
pids.push(runLane(planned.lane, planned.issues, repoRoot, ghRepo, paths, {
|
|
3084
3178
|
...planned.deps,
|
|
3085
3179
|
laneBreakerThreshold,
|
|
3180
|
+
baseHoldGate: opts.localQueue === true
|
|
3181
|
+
? undefined
|
|
3182
|
+
: createBaseHoldGate({
|
|
3183
|
+
file: paths.baseHold,
|
|
3184
|
+
repo: ghRepo,
|
|
3185
|
+
onCleared: (c) => logEvent(paths.events, 'base-hold-cleared', planned.lane, formatBaseHoldCleared(c), {
|
|
3186
|
+
lane: planned.lane,
|
|
3187
|
+
}),
|
|
3188
|
+
git: (cmd) => shellOps.exec(cmd, { cwd: repoRoot }).then((r) => r.stdout),
|
|
3189
|
+
listCheckRuns: (ref) => {
|
|
3190
|
+
const [owner, repoName] = ghRepo.split('/');
|
|
3191
|
+
return createOctokitGreenPrClient(getOctokit()).listCheckRuns({ owner, repo: repoName, ref });
|
|
3192
|
+
},
|
|
3193
|
+
}),
|
|
3086
3194
|
commentIssue: async (issue, body) => {
|
|
3087
3195
|
const [owner, repoName] = ghRepo.split('/');
|
|
3088
3196
|
await getOctokit().rest.issues.createComment({ owner, repo: repoName, issue_number: issue, body });
|
|
@@ -3108,8 +3216,12 @@ async function cmdRun(opts = {}) {
|
|
|
3108
3216
|
try {
|
|
3109
3217
|
const events = existsSync(paths.events) ? readEvents(paths.events) : [];
|
|
3110
3218
|
const costs = existsSync(paths.costs) ? internalOps.readCosts(paths.costs) : [];
|
|
3111
|
-
const
|
|
3112
|
-
|
|
3219
|
+
const recon = await reconstructHumanEventsForKpis(events, branchPrefix);
|
|
3220
|
+
const kpis = computeHealthKpis(recon.events, costs);
|
|
3221
|
+
await appendKpiSnapshot(paths, repoRoot, kpis, { partial: recon.partial });
|
|
3222
|
+
if (recon.partial) {
|
|
3223
|
+
logEvent(paths.events, 'warn', 'all', `kpi snapshot partial: GitHub human-event reconstruction failed: ${recon.error}`);
|
|
3224
|
+
}
|
|
3113
3225
|
logEvent(paths.events, 'kpi-snapshot', 'all', `KPI snapshot appended to ${paths.kpiHistory}`);
|
|
3114
3226
|
}
|
|
3115
3227
|
catch (err) {
|
|
@@ -3388,7 +3500,7 @@ export function renderLanePausedComment(payload) {
|
|
|
3388
3500
|
].join('\n\n');
|
|
3389
3501
|
}
|
|
3390
3502
|
export async function runLane(lane, issues, repoRoot, ghRepo, paths, deps = {}) {
|
|
3391
|
-
const { ship = shipIssue, waitMerge = waitForMerge, pathExists = existsSync, emitEvent = logEvent, claimNext = async () => null, releaseIssue = async () => { }, heartbeat, reapWorktree = async () => { }, sweepFinished = async () => { }, countRemaining, laneBreakerThreshold = 0, commentIssue = async () => { }, } = deps;
|
|
3503
|
+
const { ship = shipIssue, waitMerge = waitForMerge, pathExists = existsSync, emitEvent = logEvent, claimNext = async () => null, releaseIssue = async () => { }, heartbeat, reapWorktree = async () => { }, sweepFinished = async () => { }, countRemaining, baseHoldGate, laneBreakerThreshold = 0, commentIssue = async () => { }, } = deps;
|
|
3392
3504
|
let streak = null;
|
|
3393
3505
|
let merged = 0;
|
|
3394
3506
|
let awaitingReview = 0;
|
|
@@ -3420,6 +3532,13 @@ export async function runLane(lane, issues, repoRoot, ghRepo, paths, deps = {})
|
|
|
3420
3532
|
};
|
|
3421
3533
|
for (let i = 0;; i++) {
|
|
3422
3534
|
if (i >= pending.length) {
|
|
3535
|
+
// A persisted base hold on the unchanged origin/main SHA means the base is still broken
|
|
3536
|
+
// (#2349): claiming would just cycle an issue again, so stop this lane without claiming.
|
|
3537
|
+
const gate = baseHoldGate ? await baseHoldGate().catch(() => ({ kind: 'claim' })) : null;
|
|
3538
|
+
if (gate?.kind === 'hold') {
|
|
3539
|
+
emitEvent(paths.events, 'warn', lane, `lane '${lane}' not claiming: base hold on ${gate.hold.baseSha.slice(0, 8)} (${gate.hold.failingChecks.join(', ')}) — origin/main unchanged and its CI is not green`, { lane });
|
|
3540
|
+
return;
|
|
3541
|
+
}
|
|
3423
3542
|
const claimed = await claimNext();
|
|
3424
3543
|
if (claimed === null)
|
|
3425
3544
|
break;
|
|
@@ -3503,6 +3622,17 @@ export async function runLane(lane, issues, repoRoot, ghRepo, paths, deps = {})
|
|
|
3503
3622
|
streak = null;
|
|
3504
3623
|
await reapWorktree(issue).catch(() => { });
|
|
3505
3624
|
await settle(issue, 'queued');
|
|
3625
|
+
// Persist the hold so a restarted supervisor remembers the broken base (#2348);
|
|
3626
|
+
// best-effort, never allowed to change lane flow.
|
|
3627
|
+
try {
|
|
3628
|
+
writeBaseHold(paths.baseHold, {
|
|
3629
|
+
repo: ghRepo,
|
|
3630
|
+
baseSha: err.baseSha,
|
|
3631
|
+
failingChecks: err.failingChecks,
|
|
3632
|
+
since: new Date().toISOString(),
|
|
3633
|
+
});
|
|
3634
|
+
}
|
|
3635
|
+
catch { }
|
|
3506
3636
|
const payload = {
|
|
3507
3637
|
lane,
|
|
3508
3638
|
signature: err.failureSignature ?? '',
|
|
@@ -3697,6 +3827,12 @@ export class CiFailedError extends Error {
|
|
|
3697
3827
|
* exactly like "we know it's red": never merge. */
|
|
3698
3828
|
export class CiUnverifiedError extends CiFailedError {
|
|
3699
3829
|
}
|
|
3830
|
+
/** Thrown when the one infra rerun (#2365) also failed for infrastructure reasons on the same head SHA (#2366).
|
|
3831
|
+
* A CiFailedError subclass so every guard still leaves the PR open and unmerged, but it parks as
|
|
3832
|
+
* 'ci-infra', which the ADR-0155 steward skips. */
|
|
3833
|
+
export class CiInfraError extends CiFailedError {
|
|
3834
|
+
parkReason = 'ci-infra';
|
|
3835
|
+
}
|
|
3700
3836
|
const MAX_MERGE_ATTEMPTS = 5;
|
|
3701
3837
|
const MERGE_RETRY_BASE_MS = 5_000;
|
|
3702
3838
|
/** True when GitHub refused a SHA-pinned merge because the PR head moved past the verified
|
|
@@ -3753,7 +3889,7 @@ export async function rebaseDirtyPullRequest(opts) {
|
|
|
3753
3889
|
}
|
|
3754
3890
|
}
|
|
3755
3891
|
export async function landOpenPullRequest(opts) {
|
|
3756
|
-
const { octokit, owner, repoName, issue, branch, worktree, prNumber, log, run, pathExists, sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)), skipCI = false, adminMerge = process.env.FACTORY_MERGE_ADMIN === '1', watch = internalOps.watchChecks, withLock = (fn) => fn(), ensureWorktree, } = opts;
|
|
3892
|
+
const { octokit, owner, repoName, issue, branch, worktree, prNumber, log, run, pathExists, sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)), skipCI = false, adminMerge = process.env.FACTORY_MERGE_ADMIN === '1', watch = internalOps.watchChecks, rerun, withLock = (fn) => fn(), ensureWorktree, } = opts;
|
|
3757
3893
|
/** Watches CI on the PR's current head commit and returns that SHA once it is green, so the
|
|
3758
3894
|
* merge can be pinned to exactly the commit CI verified. */
|
|
3759
3895
|
const watchCi = async () => {
|
|
@@ -3762,7 +3898,16 @@ export async function landOpenPullRequest(opts) {
|
|
|
3762
3898
|
try {
|
|
3763
3899
|
const { data: pr } = await octokit.rest.pulls.get({ owner, repo: repoName, pull_number: prNumber });
|
|
3764
3900
|
headSha = pr.head.sha;
|
|
3765
|
-
outcome = await
|
|
3901
|
+
outcome = await watchChecksWithInfraRerun({
|
|
3902
|
+
octokit,
|
|
3903
|
+
owner,
|
|
3904
|
+
repo: repoName,
|
|
3905
|
+
ref: headSha,
|
|
3906
|
+
watch,
|
|
3907
|
+
rerun,
|
|
3908
|
+
onRescued: ({ runIds }) => log('ci-rescued', `CI rescued for PR #${prNumber} on ${branch}: reran infra-failed run(s) ${runIds.join(', ')} once and the re-watch is green`),
|
|
3909
|
+
onRescueFailed: ({ runIds, outcome: failedOutcome }) => log('ci-rescue-failed', `CI rescue failed for PR #${prNumber} on ${branch}: reran infra-failed run(s) ${runIds.join(', ')} once and the re-watch ended ${failedOutcome}`),
|
|
3910
|
+
});
|
|
3766
3911
|
}
|
|
3767
3912
|
catch (err) {
|
|
3768
3913
|
const msg = `CI watch for ${branch} failed: ${errorDetail(err)} — refusing to merge without a green verdict`;
|
|
@@ -3771,6 +3916,11 @@ export async function landOpenPullRequest(opts) {
|
|
|
3771
3916
|
}
|
|
3772
3917
|
if (outcome === 'success')
|
|
3773
3918
|
return headSha;
|
|
3919
|
+
if (outcome === 'infra-failure') {
|
|
3920
|
+
const msg = `CI failed again for infrastructure reasons for PR #${prNumber} on ${branch} after one rerun — parked as ci-infra, not merged`;
|
|
3921
|
+
log('ci-infra', msg);
|
|
3922
|
+
throw new CiInfraError(msg, prNumber);
|
|
3923
|
+
}
|
|
3774
3924
|
if (outcome === 'failure') {
|
|
3775
3925
|
const msg = `CI failed for PR #${prNumber} on ${branch} — refusing to merge with a failing check`;
|
|
3776
3926
|
log('ci-failed', msg);
|
|
@@ -4184,14 +4334,17 @@ async function cmdDoctor(opts = {}) {
|
|
|
4184
4334
|
}
|
|
4185
4335
|
let sandboxed = 'skipped';
|
|
4186
4336
|
let sandboxDetail = 'skipped — host claude auth was not verified';
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4337
|
+
const runtimeAvailable = hasSandboxRuntime(process.platform, (cmd) => isCommandAvailableViaShell(shellOps, cmd));
|
|
4338
|
+
const skip = sandboxAuthSkipDetail({ runtimeAvailable, hostOk: host === 'ok', inRepo: repoRoot !== null });
|
|
4339
|
+
if (skip !== null) {
|
|
4340
|
+
sandboxDetail = skip;
|
|
4341
|
+
}
|
|
4342
|
+
else if (repoRoot !== null) {
|
|
4343
|
+
const policy = resolveSandboxPolicy(configLoaders.loadFactoryConfigForRepo(getFactoryPaths(repoRoot).config).sandbox, {
|
|
4344
|
+
worktree: repoRoot,
|
|
4345
|
+
repoRoot,
|
|
4346
|
+
isAvailable: (cmd) => isCommandAvailableViaShell(shellOps, cmd),
|
|
4347
|
+
});
|
|
4195
4348
|
if (!policy) {
|
|
4196
4349
|
sandboxDetail = 'skipped — sandbox disabled by config or FACTORY_SANDBOX';
|
|
4197
4350
|
}
|
|
@@ -4374,7 +4527,19 @@ export async function main(argv = process.argv, deps = {}) {
|
|
|
4374
4527
|
.description('Multi-agent software factory with boss-worker-checker orchestration')
|
|
4375
4528
|
.version(getCliVersion())
|
|
4376
4529
|
.addHelpText('before', PREREQUISITES_TEXT)
|
|
4377
|
-
.addHelpText('after', HELP_FOOTER_TEXT)
|
|
4530
|
+
.addHelpText('after', HELP_FOOTER_TEXT)
|
|
4531
|
+
.option('--state-root <path>', 'Keep factory state and config outside the checkout: an absolute path, ~/..., or "auto" (~/.factory/<owner>/<name>); env FACTORY_STATE_ROOT');
|
|
4532
|
+
program.hook('preAction', async () => {
|
|
4533
|
+
const flag = program.opts().stateRoot;
|
|
4534
|
+
if (flag === undefined && !process.env.FACTORY_STATE_ROOT?.trim())
|
|
4535
|
+
return;
|
|
4536
|
+
try {
|
|
4537
|
+
applyStateRootOption(flag, await getRepoRoot());
|
|
4538
|
+
}
|
|
4539
|
+
catch (e) {
|
|
4540
|
+
throw new CliExitError(`factory: ${e.message}`, 2);
|
|
4541
|
+
}
|
|
4542
|
+
});
|
|
4378
4543
|
program
|
|
4379
4544
|
.command('init')
|
|
4380
4545
|
.description('Set up .factory/ in this repo (config, constitution, queue file, .gitignore) and check model reachability')
|
|
@@ -4419,7 +4584,7 @@ export async function main(argv = process.argv, deps = {}) {
|
|
|
4419
4584
|
.command('status')
|
|
4420
4585
|
.description('Show active runs, the GitHub queue, provider health, and recent events')
|
|
4421
4586
|
.option('--kpis', 'Also show the full health KPIs and the effective config')
|
|
4422
|
-
.option('--json', 'Print one JSON object (schemaVersion 1): repo, product, stop, breaker, active claims; --kpis is ignored')
|
|
4587
|
+
.option('--json', 'Print one JSON object (schemaVersion 1): repo, product, stop, breaker, active claims, update; --kpis is ignored')
|
|
4423
4588
|
.action((opts) => cmdStatus(opts));
|
|
4424
4589
|
program
|
|
4425
4590
|
.command('runs')
|
|
@@ -4485,6 +4650,8 @@ export async function main(argv = process.argv, deps = {}) {
|
|
|
4485
4650
|
.command('feedback <pr-url>')
|
|
4486
4651
|
.description("File one factory issue (bug, factory:needs-triage) from a PR's human review findings")
|
|
4487
4652
|
.option('--note <text>', 'Use this as the issue title instead of the first finding')
|
|
4653
|
+
.option('--repo <owner/name>', 'Repo to file the issue in (default: filing.repo, then the origin remote)')
|
|
4654
|
+
.option('--include-local-paths', 'Include this host name and local design/log paths in the issue')
|
|
4488
4655
|
.option('--branch-prefix <prefix>', BRANCH_PREFIX_MATCH_OPTION_DESCRIPTION)
|
|
4489
4656
|
.action(cmdFeedback);
|
|
4490
4657
|
const filingCmd = program.command('filing').description('Inspect upstream factory reports (nothing is sent)');
|
|
@@ -4696,7 +4863,8 @@ export async function main(argv = process.argv, deps = {}) {
|
|
|
4696
4863
|
.description('Run factoryd in the foreground: a localhost-only HTTP API over the repo registry')
|
|
4697
4864
|
.option('--port <n>', `Port to bind on 127.0.0.1 (default ${DEFAULT_FACTORYD_PORT})`)
|
|
4698
4865
|
.option('--registry <file>', 'Registry file to serve (default ~/.factory/registry.json)')
|
|
4699
|
-
.option('--
|
|
4866
|
+
.option('--update-mode <mode>', 'Self-update channel: manual, stable (npm latest) or prerelease (npm next) (default: stable on npm installs, manual otherwise)')
|
|
4867
|
+
.option('--auto-update', 'Deprecated: same as --update-mode stable')
|
|
4700
4868
|
.action((opts) => cmdFactoryd(opts));
|
|
4701
4869
|
const daemonCtl = (fn) => fn().catch((err) => {
|
|
4702
4870
|
if (err instanceof DaemonCtlError)
|
|
@@ -4722,6 +4890,17 @@ export async function main(argv = process.argv, deps = {}) {
|
|
|
4722
4890
|
.option('-f, --follow', 'Keep tailing')
|
|
4723
4891
|
.option('-n, --lines <n>', 'Lines to print first (default 100)')
|
|
4724
4892
|
.action((opts) => daemonCtl(() => cmdDaemonLogs(opts, deps.daemon)));
|
|
4893
|
+
program
|
|
4894
|
+
.command('self-update')
|
|
4895
|
+
.description('Check npm for a newer factory release (read-only; nothing is installed)')
|
|
4896
|
+
.option('--check', 'Compare the running version with the npm dist-tag (required)')
|
|
4897
|
+
.option('--json', 'Print one JSON object instead of text')
|
|
4898
|
+
.option('--update-mode <mode>', 'Channel to check: stable (npm latest, default) or prerelease (npm next)')
|
|
4899
|
+
.action(async (opts) => {
|
|
4900
|
+
if (opts.check !== true)
|
|
4901
|
+
throw new CliExitError('factory self-update: only --check is supported', 2);
|
|
4902
|
+
process.exitCode = await daemonCtl(() => cmdSelfUpdateCheck(opts, deps.selfUpdate));
|
|
4903
|
+
});
|
|
4725
4904
|
const worktreeCmd = program.command('worktree').description('Clean up stale factory worktrees (gc)');
|
|
4726
4905
|
worktreeCmd
|
|
4727
4906
|
.command('gc')
|