@on-par/factory-cli 0.9.11-next.4 → 0.9.11-next.41

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.
Files changed (110) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/base-health-ci.d.ts +4 -0
  3. package/dist/cli/base-health-ci.d.ts.map +1 -0
  4. package/dist/cli/base-health-ci.js +9 -0
  5. package/dist/cli/base-health-ci.js.map +1 -0
  6. package/dist/cli/base-hold.d.ts +14 -11
  7. package/dist/cli/base-hold.d.ts.map +1 -1
  8. package/dist/cli/base-hold.js +43 -20
  9. package/dist/cli/base-hold.js.map +1 -1
  10. package/dist/cli/cost-json.d.ts +5 -31
  11. package/dist/cli/cost-json.d.ts.map +1 -1
  12. package/dist/cli/cost-json.js.map +1 -1
  13. package/dist/cli/daemon.d.ts +2 -15
  14. package/dist/cli/daemon.d.ts.map +1 -1
  15. package/dist/cli/daemon.js +5 -11
  16. package/dist/cli/daemon.js.map +1 -1
  17. package/dist/cli/doctor.js +1 -1
  18. package/dist/cli/doctor.js.map +1 -1
  19. package/dist/cli/index.d.ts +10 -48
  20. package/dist/cli/index.d.ts.map +1 -1
  21. package/dist/cli/index.js +27 -186
  22. package/dist/cli/index.js.map +1 -1
  23. package/dist/cli/json-contract.d.ts +299 -0
  24. package/dist/cli/json-contract.d.ts.map +1 -0
  25. package/dist/cli/json-contract.js +206 -0
  26. package/dist/cli/json-contract.js.map +1 -0
  27. package/dist/cli/land-pr.d.ts.map +1 -1
  28. package/dist/cli/land-pr.js +5 -0
  29. package/dist/cli/land-pr.js.map +1 -1
  30. package/dist/cli/lane-environment-pause-error.d.ts +19 -0
  31. package/dist/cli/lane-environment-pause-error.d.ts.map +1 -0
  32. package/dist/cli/lane-environment-pause-error.js +24 -0
  33. package/dist/cli/lane-environment-pause-error.js.map +1 -0
  34. package/dist/cli/logs.d.ts +14 -0
  35. package/dist/cli/logs.d.ts.map +1 -1
  36. package/dist/cli/logs.js +46 -4
  37. package/dist/cli/logs.js.map +1 -1
  38. package/dist/cli/program-observe.d.ts.map +1 -1
  39. package/dist/cli/program-observe.js +5 -2
  40. package/dist/cli/program-observe.js.map +1 -1
  41. package/dist/cli/queue-add-json.d.ts +3 -25
  42. package/dist/cli/queue-add-json.d.ts.map +1 -1
  43. package/dist/cli/queue-add-json.js.map +1 -1
  44. package/dist/cli/queue-list.d.ts +6 -24
  45. package/dist/cli/queue-list.d.ts.map +1 -1
  46. package/dist/cli/queue-list.js +6 -1
  47. package/dist/cli/queue-list.js.map +1 -1
  48. package/dist/cli/queue-preflight.d.ts +45 -0
  49. package/dist/cli/queue-preflight.d.ts.map +1 -0
  50. package/dist/cli/queue-preflight.js +277 -0
  51. package/dist/cli/queue-preflight.js.map +1 -0
  52. package/dist/cli/run-branch.d.ts +24 -0
  53. package/dist/cli/run-branch.d.ts.map +1 -0
  54. package/dist/cli/run-branch.js +38 -0
  55. package/dist/cli/run-branch.js.map +1 -0
  56. package/dist/cli/run-lane.d.ts.map +1 -1
  57. package/dist/cli/run-lane.js +5 -2
  58. package/dist/cli/run-lane.js.map +1 -1
  59. package/dist/cli/run.d.ts.map +1 -1
  60. package/dist/cli/run.js +10 -3
  61. package/dist/cli/run.js.map +1 -1
  62. package/dist/cli/runs-diffstat.d.ts +6 -25
  63. package/dist/cli/runs-diffstat.d.ts.map +1 -1
  64. package/dist/cli/runs-diffstat.js.map +1 -1
  65. package/dist/cli/runs.d.ts +4 -19
  66. package/dist/cli/runs.d.ts.map +1 -1
  67. package/dist/cli/runs.js +1 -1
  68. package/dist/cli/runs.js.map +1 -1
  69. package/dist/cli/self-update-stop.d.ts +7 -0
  70. package/dist/cli/self-update-stop.d.ts.map +1 -0
  71. package/dist/cli/self-update-stop.js +27 -0
  72. package/dist/cli/self-update-stop.js.map +1 -0
  73. package/dist/cli/ship-run-state.d.ts +5 -0
  74. package/dist/cli/ship-run-state.d.ts.map +1 -0
  75. package/dist/cli/ship-run-state.js +12 -0
  76. package/dist/cli/ship-run-state.js.map +1 -0
  77. package/dist/cli/ship-steps.d.ts +1 -1
  78. package/dist/cli/ship-steps.d.ts.map +1 -1
  79. package/dist/cli/ship-steps.js +5 -2
  80. package/dist/cli/ship-steps.js.map +1 -1
  81. package/dist/cli/ship.d.ts.map +1 -1
  82. package/dist/cli/ship.js +25 -21
  83. package/dist/cli/ship.js.map +1 -1
  84. package/dist/cli/status-json.d.ts +7 -28
  85. package/dist/cli/status-json.d.ts.map +1 -1
  86. package/dist/cli/status-json.js +3 -1
  87. package/dist/cli/status-json.js.map +1 -1
  88. package/dist/cli/status.d.ts.map +1 -1
  89. package/dist/cli/status.js +4 -1
  90. package/dist/cli/status.js.map +1 -1
  91. package/dist/cli/stop-resume-json.d.ts +2 -10
  92. package/dist/cli/stop-resume-json.d.ts.map +1 -1
  93. package/dist/cli/stop-resume-json.js +1 -0
  94. package/dist/cli/stop-resume-json.js.map +1 -1
  95. package/dist/cli/usage-json.d.ts +4 -13
  96. package/dist/cli/usage-json.d.ts.map +1 -1
  97. package/dist/cli/usage-json.js.map +1 -1
  98. package/package.json +8 -5
  99. package/schemas/cost.schema.json +100 -0
  100. package/schemas/daemon-status.schema.json +44 -0
  101. package/schemas/logs-line.schema.json +114 -0
  102. package/schemas/queue-add.schema.json +73 -0
  103. package/schemas/queue-list.schema.json +77 -0
  104. package/schemas/resume.schema.json +36 -0
  105. package/schemas/runs-diffstat.schema.json +81 -0
  106. package/schemas/runs.schema.json +75 -0
  107. package/schemas/status.schema.json +200 -0
  108. package/schemas/stop.schema.json +36 -0
  109. package/schemas/unpark.schema.json +57 -0
  110. 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, findSlicePlanComment, upsertSlicePlanComment, withSliceState, 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, } from '@on-par/factory-core/internal';
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 { findOpenPRForIssue, landIssue } from './land.js';
21
+ import { landIssue } from './land.js';
19
22
  import { STOP_SET_MESSAGE } from './stop-sentinel.js';
20
- import { AwaitingReviewError, CiFailedError, CiUnverifiedError, getPullRequestLandState, LandConflictError, LandFailureError, MergeGatedError, } from './land-pr.js';
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;
@@ -955,6 +958,7 @@ async function cmdCheck(issueRaw, opts) {
955
958
  const repoConfig = configLoaders.loadRepoConfig(repoRoot);
956
959
  const effective = resolveEffectiveConfig(repoConfig);
957
960
  const router = new coreOps.ModelRouter(applyRepoConfig(configLoaders.loadModelsConfig(), repoConfig), configLoaders.loadRoutesConfig(), false, undefined, effective.allowExperimental, effective.localOnly);
961
+ router.setCodexFullAccessTasks?.(configLoaders.loadFactoryConfigForRepo(getFactoryPaths(repoRoot).config).sandbox.codexFullAccessTasks);
958
962
  router.setLocalAuthSink?.(recordLocalAuthFailure);
959
963
  runModel = createDeepModelRunner(router, repoRoot);
960
964
  }
@@ -1063,6 +1067,7 @@ async function cmdClassifierBacktestEntry(opts) {
1063
1067
  const repoConfig = configLoaders.loadRepoConfig(repoRoot);
1064
1068
  const effective = resolveEffectiveConfig(repoConfig);
1065
1069
  const router = new coreOps.ModelRouter(applyRepoConfig(configLoaders.loadModelsConfig(), repoConfig), configLoaders.loadRoutesConfig(), false, undefined, effective.allowExperimental, effective.localOnly);
1070
+ router.setCodexFullAccessTasks?.(configLoaders.loadFactoryConfigForRepo(paths.config).sandbox.codexFullAccessTasks);
1066
1071
  router.setLocalAuthSink?.(recordLocalAuthFailure);
1067
1072
  try {
1068
1073
  await cmdClassifierBacktest({ ...opts, branchPrefix }, {
@@ -1314,6 +1319,7 @@ async function cmdTui(opts = {}, runTuiImpl = runTui) {
1314
1319
  const routesConfig = configLoaders.loadRoutesConfig();
1315
1320
  const effective = resolveEffectiveConfig(repoConfig);
1316
1321
  const router = new coreOps.ModelRouter(modelsConfig, routesConfig, false, undefined, effective.allowExperimental, effective.localOnly);
1322
+ router.setCodexFullAccessTasks?.(configLoaders.loadFactoryConfigForRepo(paths.config).sandbox.codexFullAccessTasks);
1317
1323
  router.setLocalAuthSink?.(recordLocalAuthFailure);
1318
1324
  const effectiveConfigLines = describeEffectiveConfig({
1319
1325
  router,
@@ -1366,24 +1372,6 @@ export class LaneParkError extends Error {
1366
1372
  return this.outcome.failingChecks;
1367
1373
  }
1368
1374
  }
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
1375
  /** Resolves the stable lane URL a build/check agent should use, by probing whether
1388
1376
  * a factory proxy (started by `factory run`/`supervise` or `factory proxy`) is
1389
1377
  * currently alive for the configured domain. Never throws — an absent/dead proxy
@@ -2012,6 +2000,7 @@ async function cmdTriage(opts) {
2012
2000
  const routesConfig = configLoaders.loadRoutesConfig();
2013
2001
  const effective = resolveEffectiveConfig(repoConfig);
2014
2002
  const router = new coreOps.ModelRouter(modelsConfig, routesConfig, false, undefined, effective.allowExperimental, effective.localOnly);
2003
+ router.setCodexFullAccessTasks?.(configLoaders.loadFactoryConfigForRepo(paths.config).sandbox.codexFullAccessTasks);
2015
2004
  router.setLocalAuthSink?.(recordLocalAuthFailure);
2016
2005
  router.skipMissingHarnessClis(coreOps.isCommandAvailable);
2017
2006
  router.setCostSink((entry) => logCost(paths.costs, { ...entry, issue: '-' }));
@@ -2432,8 +2421,7 @@ async function cmdFactoryd(opts) {
2432
2421
  const previousUpdate = readUpdateState(updateFile);
2433
2422
  for (const stop of recoverStaleDrain(previousUpdate, (file) => {
2434
2423
  try {
2435
- unlinkSync(file);
2436
- return true;
2424
+ return clearSelfUpdateStop(file);
2437
2425
  }
2438
2426
  catch {
2439
2427
  return false;
@@ -2559,167 +2547,12 @@ export function laneQueueDeps(queue, lane) {
2559
2547
  countRemaining: async () => (await queue.list(lane)).length,
2560
2548
  };
2561
2549
  }
2550
+ export { preflightQueuedIssue };
2562
2551
  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),
2552
+ return makeQueuePreflightOps(octokit, owner, repo, {
2572
2553
  watch: (ref) => internalOps.watchChecks({ octokit, owner, repo, ref }),
2573
- findSlicePlan: (issue) => findSlicePlanComment({ octokit, repo: `${owner}/${repo}`, issue }),
2574
- getPullState: async (prNumber) => {
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 };
2554
+ ...opts,
2555
+ });
2723
2556
  }
2724
2557
  export async function planRunLanes(input) {
2725
2558
  if (input.localQueue) {
@@ -2848,7 +2681,7 @@ export async function cmdResumeApproved(opts = {}) {
2848
2681
  }
2849
2682
  const USAGE_UNAVAILABLE_LINE = '[factory] supervise: usage signal unavailable — proceeding without resume gate (set FACTORY_USAGE_ESTIMATOR=1 to gate on the heuristic)';
2850
2683
  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;
2684
+ 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
2685
  let cycle = 0;
2853
2686
  if (resumeAtSource === 'flag') {
2854
2687
  const detail = 'usage watchdog threshold set by flag: --usage-threshold';
@@ -2896,7 +2729,15 @@ export async function superviseLoop(deps) {
2896
2729
  }
2897
2730
  }
2898
2731
  emitEvent(eventsFile, 'resumed', 'usage', `supervise cycle ${cycle}: trailing usage (${source}) at ${Math.round(pct * 100)}% of cap — starting run`);
2899
- clearStop(stopFile);
2732
+ const marker = readMarker(stopFile);
2733
+ if (marker?.source === 'usage-watchdog') {
2734
+ clearStop(stopFile);
2735
+ }
2736
+ else if (marker) {
2737
+ writeLine(`[factory] supervise: STOP set by ${marker.source}${marker.at ? ` at ${marker.at}` : ''}; waiting for factory resume`);
2738
+ while (pathExists(stopFile))
2739
+ await sleep(pollMs);
2740
+ }
2900
2741
  if (ingest) {
2901
2742
  const appended = await ingest();
2902
2743
  if (appended > 0)
@@ -2975,7 +2816,7 @@ function logEventQuietly(eventsFile, type, msg) {
2975
2816
  /** Creates the STOP file in one exclusive open (no check-then-write race). Returns false when it already existed. */
2976
2817
  function createStopFile(stopFile) {
2977
2818
  try {
2978
- writeFileSync(stopFile, '', { flag: 'wx' });
2819
+ writeFileSync(stopFile, formatStopMarker('manual'), { flag: 'wx' });
2979
2820
  return true;
2980
2821
  }
2981
2822
  catch (err) {