@on-par/factory-cli 0.9.11-next.5 → 0.9.11-next.50
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 +1 -1
- package/dist/cli/base-health-ci.d.ts +18 -0
- package/dist/cli/base-health-ci.d.ts.map +1 -0
- package/dist/cli/base-health-ci.js +34 -0
- package/dist/cli/base-health-ci.js.map +1 -0
- package/dist/cli/base-hold.d.ts +16 -11
- package/dist/cli/base-hold.d.ts.map +1 -1
- package/dist/cli/base-hold.js +64 -20
- package/dist/cli/base-hold.js.map +1 -1
- package/dist/cli/cost-json.d.ts +5 -31
- package/dist/cli/cost-json.d.ts.map +1 -1
- package/dist/cli/cost-json.js +13 -2
- package/dist/cli/cost-json.js.map +1 -1
- package/dist/cli/daemon.d.ts +2 -15
- package/dist/cli/daemon.d.ts.map +1 -1
- package/dist/cli/daemon.js +5 -11
- package/dist/cli/daemon.js.map +1 -1
- package/dist/cli/doctor.js +1 -1
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +10 -48
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +43 -191
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/json-contract.d.ts +306 -0
- package/dist/cli/json-contract.d.ts.map +1 -0
- package/dist/cli/json-contract.js +208 -0
- package/dist/cli/json-contract.js.map +1 -0
- package/dist/cli/land-pr.d.ts.map +1 -1
- package/dist/cli/land-pr.js +5 -0
- package/dist/cli/land-pr.js.map +1 -1
- package/dist/cli/lane-environment-pause-error.d.ts +22 -0
- package/dist/cli/lane-environment-pause-error.d.ts.map +1 -0
- package/dist/cli/lane-environment-pause-error.js +28 -0
- package/dist/cli/lane-environment-pause-error.js.map +1 -0
- package/dist/cli/logs.d.ts +14 -0
- package/dist/cli/logs.d.ts.map +1 -1
- package/dist/cli/logs.js +46 -4
- package/dist/cli/logs.js.map +1 -1
- package/dist/cli/program-observe.d.ts.map +1 -1
- package/dist/cli/program-observe.js +5 -2
- package/dist/cli/program-observe.js.map +1 -1
- package/dist/cli/queue-add-json.d.ts +3 -25
- package/dist/cli/queue-add-json.d.ts.map +1 -1
- package/dist/cli/queue-add-json.js.map +1 -1
- package/dist/cli/queue-list.d.ts +6 -24
- package/dist/cli/queue-list.d.ts.map +1 -1
- package/dist/cli/queue-list.js +6 -1
- package/dist/cli/queue-list.js.map +1 -1
- package/dist/cli/queue-preflight.d.ts +45 -0
- package/dist/cli/queue-preflight.d.ts.map +1 -0
- package/dist/cli/queue-preflight.js +277 -0
- package/dist/cli/queue-preflight.js.map +1 -0
- package/dist/cli/run-branch.d.ts +24 -0
- package/dist/cli/run-branch.d.ts.map +1 -0
- package/dist/cli/run-branch.js +38 -0
- package/dist/cli/run-branch.js.map +1 -0
- package/dist/cli/run-lane.d.ts.map +1 -1
- package/dist/cli/run-lane.js +6 -2
- package/dist/cli/run-lane.js.map +1 -1
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +17 -3
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/runs-diffstat.d.ts +6 -25
- package/dist/cli/runs-diffstat.d.ts.map +1 -1
- package/dist/cli/runs-diffstat.js.map +1 -1
- package/dist/cli/runs.d.ts +4 -19
- package/dist/cli/runs.d.ts.map +1 -1
- package/dist/cli/runs.js +1 -1
- package/dist/cli/runs.js.map +1 -1
- package/dist/cli/self-update-stop.d.ts +7 -0
- package/dist/cli/self-update-stop.d.ts.map +1 -0
- package/dist/cli/self-update-stop.js +27 -0
- package/dist/cli/self-update-stop.js.map +1 -0
- package/dist/cli/ship-run-state.d.ts +5 -0
- package/dist/cli/ship-run-state.d.ts.map +1 -0
- package/dist/cli/ship-run-state.js +12 -0
- package/dist/cli/ship-run-state.js.map +1 -0
- package/dist/cli/ship-steps.d.ts +1 -1
- package/dist/cli/ship-steps.d.ts.map +1 -1
- package/dist/cli/ship-steps.js +15 -12
- package/dist/cli/ship-steps.js.map +1 -1
- package/dist/cli/ship.d.ts.map +1 -1
- package/dist/cli/ship.js +27 -24
- package/dist/cli/ship.js.map +1 -1
- package/dist/cli/status-json.d.ts +7 -28
- package/dist/cli/status-json.d.ts.map +1 -1
- package/dist/cli/status-json.js +3 -1
- package/dist/cli/status-json.js.map +1 -1
- package/dist/cli/status.d.ts.map +1 -1
- package/dist/cli/status.js +4 -1
- package/dist/cli/status.js.map +1 -1
- package/dist/cli/stop-resume-json.d.ts +2 -10
- package/dist/cli/stop-resume-json.d.ts.map +1 -1
- package/dist/cli/stop-resume-json.js +1 -0
- package/dist/cli/stop-resume-json.js.map +1 -1
- package/dist/cli/usage-json.d.ts +4 -13
- package/dist/cli/usage-json.d.ts.map +1 -1
- package/dist/cli/usage-json.js.map +1 -1
- package/dist/cli/worktree-reap.d.ts.map +1 -1
- package/dist/cli/worktree-reap.js +7 -1
- package/dist/cli/worktree-reap.js.map +1 -1
- package/package.json +8 -5
- package/schemas/cost.schema.json +114 -0
- package/schemas/daemon-status.schema.json +44 -0
- package/schemas/logs-line.schema.json +118 -0
- package/schemas/queue-add.schema.json +73 -0
- package/schemas/queue-list.schema.json +77 -0
- package/schemas/resume.schema.json +36 -0
- package/schemas/runs-diffstat.schema.json +81 -0
- package/schemas/runs.schema.json +75 -0
- package/schemas/status.schema.json +200 -0
- package/schemas/stop.schema.json +36 -0
- package/schemas/unpark.schema.json +57 -0
- package/schemas/usage.schema.json +28 -0
package/dist/cli/index.js
CHANGED
|
@@ -6,18 +6,21 @@ import { createRequire } from 'node:module';
|
|
|
6
6
|
import { arch, hostname as osHostname, release, platform, userInfo } from 'node:os';
|
|
7
7
|
import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
8
8
|
import { promisify, styleText } from 'node:util';
|
|
9
|
+
import { LaneEnvironmentPauseError } from './lane-environment-pause-error.js';
|
|
9
10
|
import { INVALID_BRANCH_PREFIX_MESSAGE, resolveBranchPrefixOption } from './branch-prefix.js';
|
|
10
11
|
import { sweepLaneClaims, warnQueueDiagnostics } from './queue-startup.js';
|
|
11
12
|
import { proposeTriageQueue } from './triage-open-prs.js';
|
|
13
|
+
import { makeQueuePreflightOps, preflightQueuedIssue } from './queue-preflight.js';
|
|
12
14
|
import { acquirePortLease, aggregateCosts, appendKpiHistoryLine, applyRepoConfig, buildPhase, checkPhase, clearProxyState, computeHealthKpis, ConstitutionLoader, createDefaultWorkSourceRegistry, createLogger, createLaneProxy, defaultFindPortListeners, describeEffectiveConfig, detectPostMergeDefects, diagnoseModels, probeDeclaredHarnesses, estimateTrailingSpend, fetchDefectSources, fetchSubscriptionUsage, formatUsageReport, getConstitutionsDir, getFactoryPaths, GITHUB_ISSUE_SOURCE, hasUnresolvedPark, InvalidArtifactsDirError, InvalidWorkRequestInputError, InvalidWorkspaceError, isCommandAvailable, isProxyRunning, kpiBranchPrefixes, kpisToHistoryRecord, laneBaseUrl, laneEnv, laneHostLabel, loadFactoryConfigForRepo, loadModelsConfig, loadRepoConfig, loadRoutesConfig, LOCAL_BRIEF_SOURCE, joinClassifierOutcomes, mergeClassifierOutcomes, parseClassifierOutcomes, summarizeClassifierOutcomes, formatClassifierReport, mergedPrRefs, ModelRegistry, ModelRouter, parkReasonFor, parseKpiHistory, parseQueue, planPhase, readEvents, readPortLeases, readQueue, readUsage, reapOrphanProcesses, reconstructKpiEvents, recordLeasePgid, releasePortLease, renderKpiReport, renderKpiTrend, resolveDefectWindowDays, parseSizeGateMode, parseMaxSlices, SIZE_GATE_MODES, resolveEffectiveModelPins, resolveEnvironmentProxy, resolveArtifactsDir, resolveLocalOnlyPolicy, resolveMergePolicy, resolveSkipCI, resolveTimeouts, resolveUsageCap, resolveWatchdogPolicy, runAutoIngest, shipPhase, formatCostTotal, validateQueue, watchUsage, writeLocalRunReport, writeProxyState, } from '@on-par/factory-core';
|
|
13
|
-
import { acquirePidFile, createCheckResumeStore,
|
|
15
|
+
import { acquirePidFile, createCheckResumeStore, DEFAULT_GARDEN_MAX_CLUSTERS, DEFAULT_GARDEN_SINCE, capGardenClusters, clusterGarden, dedupGardenClusters, filterGardenOnlyNew, filterGardenWindow, markGardenTrackingUnknown, parseGardenDuration, readHarvestEvents, renderGardenJson, renderGardenReport, laneWorktreePath, migrateRepoConfigToYaml, REPO_CONFIG_YAML_HEADER, claudeConfigDirOverride, claudeKeychainService, cleanupWorktree, createDaemonLogSink, factoryBranchIssue, factoryBranchPrefixes, createFactorydServer, createRunRuntime, createShipExecutor, createGithubQueue, createLocalSmallDryRun, createOctokitFilingClient, createOctokitGreenPrClient, createOctokitQueueClient, daemonRuntimePaths, defaultRegistryPath, ensureDir, fetchHumanEventSourcesCached, FACTORY_RUNTIME_CONFIG_KEYS, findUnmergedGreenPrs, formatGcReport, gitFetch, listMicroVms, listOrphanContainers, logCost, logEvent, planQueueMigration, pollBlockedParks, readCosts, readGithubQueueSnapshot, reapCleanShipWorktree, reapLaneWorktree, reapOrphanContainers, reapOrphanMicroVm, releaseRuntimeFiles, releaseStaleClaims, resolveBranchPrefix, resolveEffectiveConfig, resolveExperimental, resolveLocalOnly, resolvePrClassifierPolicy, resolveReviewFloorRules, RunLockHeldError, runOvernightQueue, parseRemoteSlug, setupWorktree, shellEscape, listBuiltInCheckers, sweepWorktrees, watchChecks, withFileLock, withGitLock, withRunLock, writePortFile, buildTriagePrompt, parseTriageCandidates, recordLocalAuthFailure, laneSlug, formatStopMarker, readStopMarker, } from '@on-par/factory-core/internal';
|
|
14
16
|
import { runTui } from '@on-par/factory-tui';
|
|
17
|
+
import { clearSelfUpdateStop } from './self-update-stop.js';
|
|
15
18
|
import { admissionStateReaderFor, runQueueReconcile } from './admission.js';
|
|
16
19
|
import { cmdClassifierBacktest, ClassifierBacktestInputError, } from './classifier-backtest.js';
|
|
17
20
|
import { runHarnessCheckCommand } from './harness-check.js';
|
|
18
|
-
import {
|
|
21
|
+
import { landIssue } from './land.js';
|
|
19
22
|
import { STOP_SET_MESSAGE } from './stop-sentinel.js';
|
|
20
|
-
import { AwaitingReviewError, CiFailedError, CiUnverifiedError,
|
|
23
|
+
import { AwaitingReviewError, CiFailedError, CiUnverifiedError, LandConflictError, LandFailureError, MergeGatedError, } from './land-pr.js';
|
|
21
24
|
import { setLandRuntime } from './land-runtime.js';
|
|
22
25
|
import { cmdRun } from './run.js';
|
|
23
26
|
import { shipIssue } from './ship.js';
|
|
@@ -338,7 +341,7 @@ setShipRuntime(() => ({
|
|
|
338
341
|
IssueSkippedError,
|
|
339
342
|
IssueDecomposedError,
|
|
340
343
|
}));
|
|
341
|
-
export { shipIssue };
|
|
344
|
+
export { LaneEnvironmentPauseError, shipIssue };
|
|
342
345
|
export function hasGitHubToken(env = process.env, tryToken) {
|
|
343
346
|
if (env.GITHUB_TOKEN || env.GH_TOKEN)
|
|
344
347
|
return true;
|
|
@@ -874,7 +877,7 @@ async function cmdModels(opts = {}) {
|
|
|
874
877
|
const byModel = new Map(diagnoses.map((d) => [d.model, d]));
|
|
875
878
|
for (const m of registry.list()) {
|
|
876
879
|
const tiers = registry.getTiers(m).join('/');
|
|
877
|
-
const est = registry.estimateCost(m, 1_000_000, 1_000_000);
|
|
880
|
+
const est = registry.estimateCost(m, { inputTokens: 1_000_000, outputTokens: 1_000_000 }).cost;
|
|
878
881
|
const cost = est === null ? 'unpriced' : `$${est.toFixed(2)}/M`;
|
|
879
882
|
const d = byModel.get(m);
|
|
880
883
|
const avail = d ? modelStatusIcon(d) : styleText('red', '❌');
|
|
@@ -887,6 +890,7 @@ async function cmdModels(opts = {}) {
|
|
|
887
890
|
console.log(` ${tier}: ${models.join(' ')}`);
|
|
888
891
|
}
|
|
889
892
|
}
|
|
893
|
+
const CACHED_RATE_MARKER = ' (cached at full input rate)';
|
|
890
894
|
async function cmdCost(opts = {}) {
|
|
891
895
|
const repoRoot = await getRepoRoot();
|
|
892
896
|
const paths = getFactoryPaths(repoRoot);
|
|
@@ -910,7 +914,7 @@ async function cmdCost(opts = {}) {
|
|
|
910
914
|
}
|
|
911
915
|
console.log(styleText('bold', `== Costs for issue ${opts.issue} ==`));
|
|
912
916
|
for (const c of filtered) {
|
|
913
|
-
console.log(` ${c.task} ${c.model} ${c.cost === null || c.cost === undefined ? 'unknown' : `$${c.cost.toFixed(4)}`}${c.failoverReason ? ` [failover: ${c.failoverReason}]` : ''}`);
|
|
917
|
+
console.log(` ${c.task} ${c.model} ${c.cost === null || c.cost === undefined ? 'unknown' : `$${c.cost.toFixed(4)}`}${c.failoverReason ? ` [failover: ${c.failoverReason}]` : ''}${c.cachedRateMissing === true ? CACHED_RATE_MARKER : ''}`);
|
|
914
918
|
}
|
|
915
919
|
const t = aggregateCosts(filtered).total;
|
|
916
920
|
console.log(' ---');
|
|
@@ -923,7 +927,14 @@ async function cmdCost(opts = {}) {
|
|
|
923
927
|
}
|
|
924
928
|
const byModel = new Map();
|
|
925
929
|
for (const c of costs) {
|
|
926
|
-
const e = byModel.get(c.model) ?? {
|
|
930
|
+
const e = byModel.get(c.model) ?? {
|
|
931
|
+
tasks: 0,
|
|
932
|
+
priced: 0,
|
|
933
|
+
pricedCount: 0,
|
|
934
|
+
unpriced: 0,
|
|
935
|
+
failovers: 0,
|
|
936
|
+
cachedRateMissing: 0,
|
|
937
|
+
};
|
|
927
938
|
e.tasks++;
|
|
928
939
|
if (c.unpriced === true || c.cost === null || c.cost === undefined)
|
|
929
940
|
e.unpriced++;
|
|
@@ -933,13 +944,16 @@ async function cmdCost(opts = {}) {
|
|
|
933
944
|
}
|
|
934
945
|
if (c.failoverReason)
|
|
935
946
|
e.failovers++;
|
|
947
|
+
if (c.cachedRateMissing === true)
|
|
948
|
+
e.cachedRateMissing++;
|
|
936
949
|
byModel.set(c.model, e);
|
|
937
950
|
}
|
|
938
951
|
console.log(styleText('bold', '== Cost Summary =='));
|
|
939
|
-
for (const [model, { tasks, priced, pricedCount, unpriced, failovers }] of byModel) {
|
|
952
|
+
for (const [model, { tasks, priced, pricedCount, unpriced, failovers, cachedRateMissing }] of byModel) {
|
|
940
953
|
const failoverSuffix = failovers > 0 ? ` (${failovers} failover${failovers === 1 ? '' : 's'})` : '';
|
|
941
954
|
const modelCost = formatCostTotal(pricedCount === 0 && unpriced > 0 ? null : priced, unpriced);
|
|
942
|
-
|
|
955
|
+
const cachedSuffix = cachedRateMissing > 0 ? CACHED_RATE_MARKER : '';
|
|
956
|
+
console.log(` ${model}: ${tasks} tasks, ${modelCost}${failoverSuffix}${cachedSuffix}`);
|
|
943
957
|
}
|
|
944
958
|
const grand = aggregateCosts(costs).total;
|
|
945
959
|
console.log(' ---');
|
|
@@ -955,6 +969,7 @@ async function cmdCheck(issueRaw, opts) {
|
|
|
955
969
|
const repoConfig = configLoaders.loadRepoConfig(repoRoot);
|
|
956
970
|
const effective = resolveEffectiveConfig(repoConfig);
|
|
957
971
|
const router = new coreOps.ModelRouter(applyRepoConfig(configLoaders.loadModelsConfig(), repoConfig), configLoaders.loadRoutesConfig(), false, undefined, effective.allowExperimental, effective.localOnly);
|
|
972
|
+
router.setCodexFullAccessTasks?.(configLoaders.loadFactoryConfigForRepo(getFactoryPaths(repoRoot).config).sandbox.codexFullAccessTasks);
|
|
958
973
|
router.setLocalAuthSink?.(recordLocalAuthFailure);
|
|
959
974
|
runModel = createDeepModelRunner(router, repoRoot);
|
|
960
975
|
}
|
|
@@ -1063,6 +1078,7 @@ async function cmdClassifierBacktestEntry(opts) {
|
|
|
1063
1078
|
const repoConfig = configLoaders.loadRepoConfig(repoRoot);
|
|
1064
1079
|
const effective = resolveEffectiveConfig(repoConfig);
|
|
1065
1080
|
const router = new coreOps.ModelRouter(applyRepoConfig(configLoaders.loadModelsConfig(), repoConfig), configLoaders.loadRoutesConfig(), false, undefined, effective.allowExperimental, effective.localOnly);
|
|
1081
|
+
router.setCodexFullAccessTasks?.(configLoaders.loadFactoryConfigForRepo(paths.config).sandbox.codexFullAccessTasks);
|
|
1066
1082
|
router.setLocalAuthSink?.(recordLocalAuthFailure);
|
|
1067
1083
|
try {
|
|
1068
1084
|
await cmdClassifierBacktest({ ...opts, branchPrefix }, {
|
|
@@ -1314,6 +1330,7 @@ async function cmdTui(opts = {}, runTuiImpl = runTui) {
|
|
|
1314
1330
|
const routesConfig = configLoaders.loadRoutesConfig();
|
|
1315
1331
|
const effective = resolveEffectiveConfig(repoConfig);
|
|
1316
1332
|
const router = new coreOps.ModelRouter(modelsConfig, routesConfig, false, undefined, effective.allowExperimental, effective.localOnly);
|
|
1333
|
+
router.setCodexFullAccessTasks?.(configLoaders.loadFactoryConfigForRepo(paths.config).sandbox.codexFullAccessTasks);
|
|
1317
1334
|
router.setLocalAuthSink?.(recordLocalAuthFailure);
|
|
1318
1335
|
const effectiveConfigLines = describeEffectiveConfig({
|
|
1319
1336
|
router,
|
|
@@ -1366,24 +1383,6 @@ export class LaneParkError extends Error {
|
|
|
1366
1383
|
return this.outcome.failingChecks;
|
|
1367
1384
|
}
|
|
1368
1385
|
}
|
|
1369
|
-
/** CHECK found every failing checker also failing on the base SHA (#1928): the issue is
|
|
1370
|
-
* released to queued (not parked) and the lane pauses. */
|
|
1371
|
-
export class LaneEnvironmentPauseError extends Error {
|
|
1372
|
-
baseSha;
|
|
1373
|
-
failingChecks;
|
|
1374
|
-
failureSignature;
|
|
1375
|
-
/** BUILD output was kept for `factory resume --from check` (#2564); runLane must not reap it. */
|
|
1376
|
-
buildPreserved;
|
|
1377
|
-
constructor(message, failure) {
|
|
1378
|
-
super(message);
|
|
1379
|
-
if (failure.buildPreserved !== undefined)
|
|
1380
|
-
this.buildPreserved = failure.buildPreserved;
|
|
1381
|
-
this.baseSha = failure.baseSha;
|
|
1382
|
-
this.failingChecks = failure.failingChecks;
|
|
1383
|
-
if (failure.failureSignature !== undefined)
|
|
1384
|
-
this.failureSignature = failure.failureSignature;
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1387
1386
|
/** Resolves the stable lane URL a build/check agent should use, by probing whether
|
|
1388
1387
|
* a factory proxy (started by `factory run`/`supervise` or `factory proxy`) is
|
|
1389
1388
|
* currently alive for the configured domain. Never throws — an absent/dead proxy
|
|
@@ -2012,6 +2011,7 @@ async function cmdTriage(opts) {
|
|
|
2012
2011
|
const routesConfig = configLoaders.loadRoutesConfig();
|
|
2013
2012
|
const effective = resolveEffectiveConfig(repoConfig);
|
|
2014
2013
|
const router = new coreOps.ModelRouter(modelsConfig, routesConfig, false, undefined, effective.allowExperimental, effective.localOnly);
|
|
2014
|
+
router.setCodexFullAccessTasks?.(configLoaders.loadFactoryConfigForRepo(paths.config).sandbox.codexFullAccessTasks);
|
|
2015
2015
|
router.setLocalAuthSink?.(recordLocalAuthFailure);
|
|
2016
2016
|
router.skipMissingHarnessClis(coreOps.isCommandAvailable);
|
|
2017
2017
|
router.setCostSink((entry) => logCost(paths.costs, { ...entry, issue: '-' }));
|
|
@@ -2432,8 +2432,7 @@ async function cmdFactoryd(opts) {
|
|
|
2432
2432
|
const previousUpdate = readUpdateState(updateFile);
|
|
2433
2433
|
for (const stop of recoverStaleDrain(previousUpdate, (file) => {
|
|
2434
2434
|
try {
|
|
2435
|
-
|
|
2436
|
-
return true;
|
|
2435
|
+
return clearSelfUpdateStop(file);
|
|
2437
2436
|
}
|
|
2438
2437
|
catch {
|
|
2439
2438
|
return false;
|
|
@@ -2559,167 +2558,12 @@ export function laneQueueDeps(queue, lane) {
|
|
|
2559
2558
|
countRemaining: async () => (await queue.list(lane)).length,
|
|
2560
2559
|
};
|
|
2561
2560
|
}
|
|
2561
|
+
export { preflightQueuedIssue };
|
|
2562
2562
|
export function createQueuePreflightOps(octokit, owner, repo, opts = {}) {
|
|
2563
|
-
return {
|
|
2564
|
-
autoMergeStack: opts.autoMergeStack ?? false,
|
|
2565
|
-
getPullRefs: async (prNumber) => {
|
|
2566
|
-
const { data } = await octokit.rest.pulls.get({ owner, repo, pull_number: prNumber });
|
|
2567
|
-
return { headRef: data.head.ref, baseRef: data.base.ref, headSha: data.head.sha };
|
|
2568
|
-
},
|
|
2569
|
-
expectedHeadRepoFullName: `${owner}/${repo}`,
|
|
2570
|
-
findOpenPR: (issue) => findOpenPRForIssue(octokit, owner, repo, issue),
|
|
2571
|
-
getLandState: (prNumber) => getPullRequestLandState(octokit, owner, repo, prNumber),
|
|
2563
|
+
return makeQueuePreflightOps(octokit, owner, repo, {
|
|
2572
2564
|
watch: (ref) => internalOps.watchChecks({ octokit, owner, repo, ref }),
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
const { data } = await octokit.rest.pulls.get({ owner, repo, pull_number: prNumber });
|
|
2576
|
-
if (data.merged || data.merged_at)
|
|
2577
|
-
return 'merged';
|
|
2578
|
-
return data.state === 'open' ? 'open' : 'closed';
|
|
2579
|
-
},
|
|
2580
|
-
saveSlicePlan: async (issue, plan) => {
|
|
2581
|
-
await upsertSlicePlanComment({ octokit, repo: `${owner}/${repo}`, issue, plan });
|
|
2582
|
-
},
|
|
2583
|
-
};
|
|
2584
|
-
}
|
|
2585
|
-
/** ADR-0147/ADR-0161: walk every pr-open slice PR bottom-up. Merged layers are recorded, a closed layer parks
|
|
2586
|
-
* (naming the layers above it), and the lowest open layer defers, or is adopted when stack auto-merge is on and
|
|
2587
|
-
* its base is main with green CI on the rebased head. Returns undefined to fall through to the Closes-PR
|
|
2588
|
-
* preflight only when no slice PR is still open. */
|
|
2589
|
-
async function preflightSlice(issue, deps) {
|
|
2590
|
-
let lookup;
|
|
2591
|
-
try {
|
|
2592
|
-
lookup = await deps.findSlicePlan(issue);
|
|
2593
|
-
}
|
|
2594
|
-
catch (err) {
|
|
2595
|
-
return { kind: 'park', reason: `slice plan lookup failed: ${errorDetail(err)}` };
|
|
2596
|
-
}
|
|
2597
|
-
if (lookup.status === 'invalid') {
|
|
2598
|
-
return {
|
|
2599
|
-
kind: 'park',
|
|
2600
|
-
reason: `slice plan comment ${lookup.commentId} on #${issue} is unreadable (${lookup.error})`,
|
|
2601
|
-
};
|
|
2602
|
-
}
|
|
2603
|
-
if (lookup.status === 'not-found')
|
|
2604
|
-
return undefined;
|
|
2605
|
-
const { plan } = lookup;
|
|
2606
|
-
const n = plan.slices.length;
|
|
2607
|
-
const open = plan.slices.filter((s) => s.state === 'pr-open');
|
|
2608
|
-
if (open.length === 0)
|
|
2609
|
-
return undefined;
|
|
2610
|
-
let next = plan;
|
|
2611
|
-
let lastMerged;
|
|
2612
|
-
const save = async () => {
|
|
2613
|
-
if (lastMerged === undefined)
|
|
2614
|
-
return undefined;
|
|
2615
|
-
try {
|
|
2616
|
-
await deps.saveSlicePlan(issue, next);
|
|
2617
|
-
}
|
|
2618
|
-
catch (err) {
|
|
2619
|
-
return {
|
|
2620
|
-
kind: 'park',
|
|
2621
|
-
reason: `${lastMerged.label} PR #${lastMerged.pr} merged but updating the slice plan failed: ${errorDetail(err)}`,
|
|
2622
|
-
};
|
|
2623
|
-
}
|
|
2624
|
-
return undefined;
|
|
2625
|
-
};
|
|
2626
|
-
for (const [at, slice] of open.entries()) {
|
|
2627
|
-
const label = `slice ${slice.index}/${n}`;
|
|
2628
|
-
if (slice.prNumber === undefined) {
|
|
2629
|
-
return { kind: 'park', reason: `${label} of #${issue} is pr-open but the slice plan records no PR number` };
|
|
2630
|
-
}
|
|
2631
|
-
const pr = slice.prNumber;
|
|
2632
|
-
let state;
|
|
2633
|
-
try {
|
|
2634
|
-
state = await deps.getPullState(pr);
|
|
2635
|
-
}
|
|
2636
|
-
catch (err) {
|
|
2637
|
-
return { kind: 'park', reason: `${label} PR #${pr} state lookup failed: ${errorDetail(err)}` };
|
|
2638
|
-
}
|
|
2639
|
-
if (state === 'merged') {
|
|
2640
|
-
next = withSliceState(next, slice.index, 'merged', pr);
|
|
2641
|
-
lastMerged = { label, pr };
|
|
2642
|
-
continue;
|
|
2643
|
-
}
|
|
2644
|
-
if (state === 'closed') {
|
|
2645
|
-
const above = open.slice(at + 1);
|
|
2646
|
-
const suffix = above.length > 0
|
|
2647
|
-
? `; layers above it: ${above.map((a) => `slice ${a.index}/${n} (#${a.prNumber})`).join(', ')}`
|
|
2648
|
-
: '';
|
|
2649
|
-
return { kind: 'park', reason: `${label} PR #${pr} was closed without merging${suffix}` };
|
|
2650
|
-
}
|
|
2651
|
-
const saved = await save();
|
|
2652
|
-
if (saved)
|
|
2653
|
-
return saved;
|
|
2654
|
-
if (!deps.autoMergeStack)
|
|
2655
|
-
return { kind: 'defer' };
|
|
2656
|
-
let refs;
|
|
2657
|
-
try {
|
|
2658
|
-
refs = await deps.getPullRefs(pr);
|
|
2659
|
-
}
|
|
2660
|
-
catch (err) {
|
|
2661
|
-
return { kind: 'park', reason: `${label} PR #${pr} ref lookup failed: ${errorDetail(err)}` };
|
|
2662
|
-
}
|
|
2663
|
-
// GitHub has not retargeted the layer to main yet: the lower layer is still unmerged.
|
|
2664
|
-
if (refs.baseRef !== 'main')
|
|
2665
|
-
return { kind: 'defer' };
|
|
2666
|
-
let outcome;
|
|
2667
|
-
try {
|
|
2668
|
-
outcome = await deps.watch(refs.headSha);
|
|
2669
|
-
}
|
|
2670
|
-
catch (err) {
|
|
2671
|
-
return { kind: 'park', reason: `CI watch for ${label} PR #${pr} failed: ${errorDetail(err)}` };
|
|
2672
|
-
}
|
|
2673
|
-
if (outcome === 'failure')
|
|
2674
|
-
return { kind: 'park', reason: `CI for ${label} PR #${pr} ended failure` };
|
|
2675
|
-
if (outcome !== 'success')
|
|
2676
|
-
return { kind: 'defer' };
|
|
2677
|
-
return { kind: 'adopt', branch: refs.headRef, stackLayer: { index: slice.index, count: n, prNumber: pr } };
|
|
2678
|
-
}
|
|
2679
|
-
return (await save()) ?? undefined;
|
|
2680
|
-
}
|
|
2681
|
-
/** Resolve queued work from GitHub evidence without consuming model or worktree resources. */
|
|
2682
|
-
export async function preflightQueuedIssue(issue, deps) {
|
|
2683
|
-
if (issue.labels.includes('factory:in-progress'))
|
|
2684
|
-
return { kind: 'defer' };
|
|
2685
|
-
const slice = await preflightSlice(issue.number, deps);
|
|
2686
|
-
if (slice)
|
|
2687
|
-
return slice;
|
|
2688
|
-
let pr;
|
|
2689
|
-
try {
|
|
2690
|
-
pr = await deps.findOpenPR(issue.number);
|
|
2691
|
-
}
|
|
2692
|
-
catch (err) {
|
|
2693
|
-
return { kind: 'park', reason: `PR lookup failed: ${errorDetail(err)}` };
|
|
2694
|
-
}
|
|
2695
|
-
if (!pr)
|
|
2696
|
-
return { kind: 'build' };
|
|
2697
|
-
if (pr.headRepoFullName !== deps.expectedHeadRepoFullName) {
|
|
2698
|
-
return { kind: 'park', reason: `PR #${pr.number} head is not in ${deps.expectedHeadRepoFullName}` };
|
|
2699
|
-
}
|
|
2700
|
-
let state;
|
|
2701
|
-
try {
|
|
2702
|
-
state = await deps.getLandState(pr.number);
|
|
2703
|
-
}
|
|
2704
|
-
catch (err) {
|
|
2705
|
-
return { kind: 'park', reason: `PR #${pr.number} merge-state lookup failed: ${errorDetail(err)}` };
|
|
2706
|
-
}
|
|
2707
|
-
if (state.isDraft !== false) {
|
|
2708
|
-
return { kind: 'park', reason: `PR #${pr.number} is draft or its draft state is unavailable` };
|
|
2709
|
-
}
|
|
2710
|
-
if (!state.mergeStateStatus || state.mergeStateStatus === 'UNKNOWN') {
|
|
2711
|
-
return { kind: 'park', reason: `PR #${pr.number} merge state is unavailable` };
|
|
2712
|
-
}
|
|
2713
|
-
let outcome;
|
|
2714
|
-
try {
|
|
2715
|
-
outcome = await deps.watch(pr.headSha);
|
|
2716
|
-
}
|
|
2717
|
-
catch (err) {
|
|
2718
|
-
return { kind: 'park', reason: `CI watch for PR #${pr.number} failed: ${errorDetail(err)}` };
|
|
2719
|
-
}
|
|
2720
|
-
if (outcome !== 'success')
|
|
2721
|
-
return { kind: 'park', reason: `CI for PR #${pr.number} ended ${outcome}` };
|
|
2722
|
-
return { kind: 'adopt', branch: pr.branch };
|
|
2565
|
+
...opts,
|
|
2566
|
+
});
|
|
2723
2567
|
}
|
|
2724
2568
|
export async function planRunLanes(input) {
|
|
2725
2569
|
if (input.localQueue) {
|
|
@@ -2848,7 +2692,7 @@ export async function cmdResumeApproved(opts = {}) {
|
|
|
2848
2692
|
}
|
|
2849
2693
|
const USAGE_UNAVAILABLE_LINE = '[factory] supervise: usage signal unavailable — proceeding without resume gate (set FACTORY_USAGE_ESTIMATOR=1 to gate on the heuristic)';
|
|
2850
2694
|
export async function superviseLoop(deps) {
|
|
2851
|
-
const { cap, resumeAt, pollMs, watch = true, watchSource = 'default', repoConfigLabel, resumeAtSource = 'default', pollSource = 'default', estimator = false, stopFile, eventsFile, now, runQueue, readUsageFn = () => readUsage({ cap, estimator }), pathExists = existsSync, clearStop = (path) => rmSync(path, { force: true }), sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)), emitEvent = logEvent, writeLine = (line) => console.log(line), ingest, } = deps;
|
|
2695
|
+
const { cap, resumeAt, pollMs, watch = true, watchSource = 'default', repoConfigLabel, resumeAtSource = 'default', pollSource = 'default', estimator = false, stopFile, eventsFile, now, runQueue, readUsageFn = () => readUsage({ cap, estimator }), pathExists = existsSync, clearStop = (path) => rmSync(path, { force: true }), readStopMarker: readMarker = readStopMarker, sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)), emitEvent = logEvent, writeLine = (line) => console.log(line), ingest, } = deps;
|
|
2852
2696
|
let cycle = 0;
|
|
2853
2697
|
if (resumeAtSource === 'flag') {
|
|
2854
2698
|
const detail = 'usage watchdog threshold set by flag: --usage-threshold';
|
|
@@ -2896,7 +2740,15 @@ export async function superviseLoop(deps) {
|
|
|
2896
2740
|
}
|
|
2897
2741
|
}
|
|
2898
2742
|
emitEvent(eventsFile, 'resumed', 'usage', `supervise cycle ${cycle}: trailing usage (${source}) at ${Math.round(pct * 100)}% of cap — starting run`);
|
|
2899
|
-
|
|
2743
|
+
const marker = readMarker(stopFile);
|
|
2744
|
+
if (marker?.source === 'usage-watchdog') {
|
|
2745
|
+
clearStop(stopFile);
|
|
2746
|
+
}
|
|
2747
|
+
else if (marker) {
|
|
2748
|
+
writeLine(`[factory] supervise: STOP set by ${marker.source}${marker.at ? ` at ${marker.at}` : ''}; waiting for factory resume`);
|
|
2749
|
+
while (pathExists(stopFile))
|
|
2750
|
+
await sleep(pollMs);
|
|
2751
|
+
}
|
|
2900
2752
|
if (ingest) {
|
|
2901
2753
|
const appended = await ingest();
|
|
2902
2754
|
if (appended > 0)
|
|
@@ -2975,7 +2827,7 @@ function logEventQuietly(eventsFile, type, msg) {
|
|
|
2975
2827
|
/** Creates the STOP file in one exclusive open (no check-then-write race). Returns false when it already existed. */
|
|
2976
2828
|
function createStopFile(stopFile) {
|
|
2977
2829
|
try {
|
|
2978
|
-
writeFileSync(stopFile, '', { flag: 'wx' });
|
|
2830
|
+
writeFileSync(stopFile, formatStopMarker('manual'), { flag: 'wx' });
|
|
2979
2831
|
return true;
|
|
2980
2832
|
}
|
|
2981
2833
|
catch (err) {
|