@on-par/factory-cli 0.9.11-next.6 → 0.9.11-next.61

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 (133) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/base-health-ci.d.ts +16 -2
  3. package/dist/cli/base-health-ci.d.ts.map +1 -1
  4. package/dist/cli/base-health-ci.js +30 -5
  5. package/dist/cli/base-health-ci.js.map +1 -1
  6. package/dist/cli/base-hold.d.ts +7 -13
  7. package/dist/cli/base-hold.d.ts.map +1 -1
  8. package/dist/cli/base-hold.js +44 -30
  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 +13 -2
  13. package/dist/cli/cost-json.js.map +1 -1
  14. package/dist/cli/daemon.d.ts +2 -15
  15. package/dist/cli/daemon.d.ts.map +1 -1
  16. package/dist/cli/daemon.js +5 -11
  17. package/dist/cli/daemon.js.map +1 -1
  18. package/dist/cli/doctor.d.ts.map +1 -1
  19. package/dist/cli/doctor.js +3 -1
  20. package/dist/cli/doctor.js.map +1 -1
  21. package/dist/cli/help-groups.d.ts.map +1 -1
  22. package/dist/cli/help-groups.js +4 -1
  23. package/dist/cli/help-groups.js.map +1 -1
  24. package/dist/cli/index.d.ts +14 -32
  25. package/dist/cli/index.d.ts.map +1 -1
  26. package/dist/cli/index.js +60 -173
  27. package/dist/cli/index.js.map +1 -1
  28. package/dist/cli/json-contract.d.ts +306 -0
  29. package/dist/cli/json-contract.d.ts.map +1 -0
  30. package/dist/cli/json-contract.js +208 -0
  31. package/dist/cli/json-contract.js.map +1 -0
  32. package/dist/cli/land-pr.d.ts.map +1 -1
  33. package/dist/cli/land-pr.js +5 -0
  34. package/dist/cli/land-pr.js.map +1 -1
  35. package/dist/cli/land.d.ts.map +1 -1
  36. package/dist/cli/land.js +19 -1
  37. package/dist/cli/land.js.map +1 -1
  38. package/dist/cli/lane-environment-pause-error.d.ts +3 -0
  39. package/dist/cli/lane-environment-pause-error.d.ts.map +1 -1
  40. package/dist/cli/lane-environment-pause-error.js +4 -0
  41. package/dist/cli/lane-environment-pause-error.js.map +1 -1
  42. package/dist/cli/logs.d.ts +14 -0
  43. package/dist/cli/logs.d.ts.map +1 -1
  44. package/dist/cli/logs.js +46 -4
  45. package/dist/cli/logs.js.map +1 -1
  46. package/dist/cli/program-observe.d.ts.map +1 -1
  47. package/dist/cli/program-observe.js +11 -2
  48. package/dist/cli/program-observe.js.map +1 -1
  49. package/dist/cli/queue-add-json.d.ts +3 -25
  50. package/dist/cli/queue-add-json.d.ts.map +1 -1
  51. package/dist/cli/queue-add-json.js.map +1 -1
  52. package/dist/cli/queue-list.d.ts +6 -24
  53. package/dist/cli/queue-list.d.ts.map +1 -1
  54. package/dist/cli/queue-list.js +6 -1
  55. package/dist/cli/queue-list.js.map +1 -1
  56. package/dist/cli/queue-preflight.d.ts +50 -0
  57. package/dist/cli/queue-preflight.d.ts.map +1 -0
  58. package/dist/cli/queue-preflight.js +327 -0
  59. package/dist/cli/queue-preflight.js.map +1 -0
  60. package/dist/cli/risk-policy.d.ts +8 -0
  61. package/dist/cli/risk-policy.d.ts.map +1 -0
  62. package/dist/cli/risk-policy.js +34 -0
  63. package/dist/cli/risk-policy.js.map +1 -0
  64. package/dist/cli/run-branch.d.ts +24 -0
  65. package/dist/cli/run-branch.d.ts.map +1 -0
  66. package/dist/cli/run-branch.js +38 -0
  67. package/dist/cli/run-branch.js.map +1 -0
  68. package/dist/cli/run-lane.d.ts.map +1 -1
  69. package/dist/cli/run-lane.js +9 -1
  70. package/dist/cli/run-lane.js.map +1 -1
  71. package/dist/cli/run.d.ts.map +1 -1
  72. package/dist/cli/run.js +17 -11
  73. package/dist/cli/run.js.map +1 -1
  74. package/dist/cli/runs-diffstat.d.ts +6 -25
  75. package/dist/cli/runs-diffstat.d.ts.map +1 -1
  76. package/dist/cli/runs-diffstat.js.map +1 -1
  77. package/dist/cli/runs.d.ts +4 -19
  78. package/dist/cli/runs.d.ts.map +1 -1
  79. package/dist/cli/runs.js +1 -1
  80. package/dist/cli/runs.js.map +1 -1
  81. package/dist/cli/self-update-stop.d.ts +7 -0
  82. package/dist/cli/self-update-stop.d.ts.map +1 -0
  83. package/dist/cli/self-update-stop.js +27 -0
  84. package/dist/cli/self-update-stop.js.map +1 -0
  85. package/dist/cli/ship-run-state.d.ts +5 -0
  86. package/dist/cli/ship-run-state.d.ts.map +1 -0
  87. package/dist/cli/ship-run-state.js +12 -0
  88. package/dist/cli/ship-run-state.js.map +1 -0
  89. package/dist/cli/ship-steps.d.ts +2 -14
  90. package/dist/cli/ship-steps.d.ts.map +1 -1
  91. package/dist/cli/ship-steps.js +19 -32
  92. package/dist/cli/ship-steps.js.map +1 -1
  93. package/dist/cli/ship.d.ts.map +1 -1
  94. package/dist/cli/ship.js +26 -24
  95. package/dist/cli/ship.js.map +1 -1
  96. package/dist/cli/slice-index.d.ts +16 -0
  97. package/dist/cli/slice-index.d.ts.map +1 -0
  98. package/dist/cli/slice-index.js +27 -0
  99. package/dist/cli/slice-index.js.map +1 -0
  100. package/dist/cli/status-json.d.ts +7 -28
  101. package/dist/cli/status-json.d.ts.map +1 -1
  102. package/dist/cli/status-json.js +3 -1
  103. package/dist/cli/status-json.js.map +1 -1
  104. package/dist/cli/status.d.ts.map +1 -1
  105. package/dist/cli/status.js +4 -1
  106. package/dist/cli/status.js.map +1 -1
  107. package/dist/cli/steerability-report.d.ts +9 -0
  108. package/dist/cli/steerability-report.d.ts.map +1 -0
  109. package/dist/cli/steerability-report.js +28 -0
  110. package/dist/cli/steerability-report.js.map +1 -0
  111. package/dist/cli/stop-resume-json.d.ts +2 -10
  112. package/dist/cli/stop-resume-json.d.ts.map +1 -1
  113. package/dist/cli/stop-resume-json.js +1 -0
  114. package/dist/cli/stop-resume-json.js.map +1 -1
  115. package/dist/cli/usage-json.d.ts +4 -13
  116. package/dist/cli/usage-json.d.ts.map +1 -1
  117. package/dist/cli/usage-json.js.map +1 -1
  118. package/dist/cli/worktree-reap.d.ts.map +1 -1
  119. package/dist/cli/worktree-reap.js +7 -1
  120. package/dist/cli/worktree-reap.js.map +1 -1
  121. package/package.json +8 -5
  122. package/schemas/cost.schema.json +114 -0
  123. package/schemas/daemon-status.schema.json +44 -0
  124. package/schemas/logs-line.schema.json +118 -0
  125. package/schemas/queue-add.schema.json +73 -0
  126. package/schemas/queue-list.schema.json +77 -0
  127. package/schemas/resume.schema.json +36 -0
  128. package/schemas/runs-diffstat.schema.json +81 -0
  129. package/schemas/runs.schema.json +75 -0
  130. package/schemas/status.schema.json +200 -0
  131. package/schemas/stop.schema.json +36 -0
  132. package/schemas/unpark.schema.json +57 -0
  133. package/schemas/usage.schema.json +28 -0
package/dist/cli/index.js CHANGED
@@ -10,15 +10,19 @@ import { LaneEnvironmentPauseError } from './lane-environment-pause-error.js';
10
10
  import { INVALID_BRANCH_PREFIX_MESSAGE, resolveBranchPrefixOption } from './branch-prefix.js';
11
11
  import { sweepLaneClaims, warnQueueDiagnostics } from './queue-startup.js';
12
12
  import { proposeTriageQueue } from './triage-open-prs.js';
13
+ import { makeQueuePreflightOps, preflightQueuedIssue } from './queue-preflight.js';
13
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';
14
- 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';
16
+ import { formatSteerabilityReport } from '@on-par/factory-core/internal';
15
17
  import { runTui } from '@on-par/factory-tui';
18
+ import { clearSelfUpdateStop } from './self-update-stop.js';
16
19
  import { admissionStateReaderFor, runQueueReconcile } from './admission.js';
17
20
  import { cmdClassifierBacktest, ClassifierBacktestInputError, } from './classifier-backtest.js';
21
+ import { buildSteerabilityReport } from './steerability-report.js';
18
22
  import { runHarnessCheckCommand } from './harness-check.js';
19
- import { findOpenPRForIssue, landIssue } from './land.js';
23
+ import { landIssue } from './land.js';
20
24
  import { STOP_SET_MESSAGE } from './stop-sentinel.js';
21
- import { AwaitingReviewError, CiFailedError, CiUnverifiedError, getPullRequestLandState, LandConflictError, LandFailureError, MergeGatedError, } from './land-pr.js';
25
+ import { AwaitingReviewError, CiFailedError, CiUnverifiedError, LandConflictError, LandFailureError, MergeGatedError, } from './land-pr.js';
22
26
  import { setLandRuntime } from './land-runtime.js';
23
27
  import { cmdRun } from './run.js';
24
28
  import { shipIssue } from './ship.js';
@@ -875,7 +879,7 @@ async function cmdModels(opts = {}) {
875
879
  const byModel = new Map(diagnoses.map((d) => [d.model, d]));
876
880
  for (const m of registry.list()) {
877
881
  const tiers = registry.getTiers(m).join('/');
878
- const est = registry.estimateCost(m, 1_000_000, 1_000_000);
882
+ const est = registry.estimateCost(m, { inputTokens: 1_000_000, outputTokens: 1_000_000 }).cost;
879
883
  const cost = est === null ? 'unpriced' : `$${est.toFixed(2)}/M`;
880
884
  const d = byModel.get(m);
881
885
  const avail = d ? modelStatusIcon(d) : styleText('red', '❌');
@@ -888,6 +892,7 @@ async function cmdModels(opts = {}) {
888
892
  console.log(` ${tier}: ${models.join(' ')}`);
889
893
  }
890
894
  }
895
+ const CACHED_RATE_MARKER = ' (cached at full input rate)';
891
896
  async function cmdCost(opts = {}) {
892
897
  const repoRoot = await getRepoRoot();
893
898
  const paths = getFactoryPaths(repoRoot);
@@ -911,7 +916,7 @@ async function cmdCost(opts = {}) {
911
916
  }
912
917
  console.log(styleText('bold', `== Costs for issue ${opts.issue} ==`));
913
918
  for (const c of filtered) {
914
- console.log(` ${c.task} ${c.model} ${c.cost === null || c.cost === undefined ? 'unknown' : `$${c.cost.toFixed(4)}`}${c.failoverReason ? ` [failover: ${c.failoverReason}]` : ''}`);
919
+ 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 : ''}`);
915
920
  }
916
921
  const t = aggregateCosts(filtered).total;
917
922
  console.log(' ---');
@@ -924,7 +929,14 @@ async function cmdCost(opts = {}) {
924
929
  }
925
930
  const byModel = new Map();
926
931
  for (const c of costs) {
927
- const e = byModel.get(c.model) ?? { tasks: 0, priced: 0, pricedCount: 0, unpriced: 0, failovers: 0 };
932
+ const e = byModel.get(c.model) ?? {
933
+ tasks: 0,
934
+ priced: 0,
935
+ pricedCount: 0,
936
+ unpriced: 0,
937
+ failovers: 0,
938
+ cachedRateMissing: 0,
939
+ };
928
940
  e.tasks++;
929
941
  if (c.unpriced === true || c.cost === null || c.cost === undefined)
930
942
  e.unpriced++;
@@ -934,13 +946,16 @@ async function cmdCost(opts = {}) {
934
946
  }
935
947
  if (c.failoverReason)
936
948
  e.failovers++;
949
+ if (c.cachedRateMissing === true)
950
+ e.cachedRateMissing++;
937
951
  byModel.set(c.model, e);
938
952
  }
939
953
  console.log(styleText('bold', '== Cost Summary =='));
940
- for (const [model, { tasks, priced, pricedCount, unpriced, failovers }] of byModel) {
954
+ for (const [model, { tasks, priced, pricedCount, unpriced, failovers, cachedRateMissing }] of byModel) {
941
955
  const failoverSuffix = failovers > 0 ? ` (${failovers} failover${failovers === 1 ? '' : 's'})` : '';
942
956
  const modelCost = formatCostTotal(pricedCount === 0 && unpriced > 0 ? null : priced, unpriced);
943
- console.log(` ${model}: ${tasks} tasks, ${modelCost}${failoverSuffix}`);
957
+ const cachedSuffix = cachedRateMissing > 0 ? CACHED_RATE_MARKER : '';
958
+ console.log(` ${model}: ${tasks} tasks, ${modelCost}${failoverSuffix}${cachedSuffix}`);
944
959
  }
945
960
  const grand = aggregateCosts(costs).total;
946
961
  console.log(' ---');
@@ -956,6 +971,7 @@ async function cmdCheck(issueRaw, opts) {
956
971
  const repoConfig = configLoaders.loadRepoConfig(repoRoot);
957
972
  const effective = resolveEffectiveConfig(repoConfig);
958
973
  const router = new coreOps.ModelRouter(applyRepoConfig(configLoaders.loadModelsConfig(), repoConfig), configLoaders.loadRoutesConfig(), false, undefined, effective.allowExperimental, effective.localOnly);
974
+ router.setCodexFullAccessTasks?.(configLoaders.loadFactoryConfigForRepo(getFactoryPaths(repoRoot).config).sandbox.codexFullAccessTasks);
959
975
  router.setLocalAuthSink?.(recordLocalAuthFailure);
960
976
  runModel = createDeepModelRunner(router, repoRoot);
961
977
  }
@@ -1049,12 +1065,27 @@ async function cmdClassifierReport(opts = {}) {
1049
1065
  const repoRoot = await getRepoRoot();
1050
1066
  const paths = getFactoryPaths(repoRoot);
1051
1067
  const report = summarizeClassifierOutcomes(parseClassifierOutcomes(readTextFileOrEmpty(paths.classifierOutcomes)));
1068
+ const steerability = await buildSteerabilityReport(paths);
1052
1069
  if (opts.json) {
1053
- console.log(JSON.stringify(report));
1070
+ console.log(JSON.stringify({ ...report, steerability }));
1054
1071
  return;
1055
1072
  }
1056
1073
  for (const line of formatClassifierReport(report))
1057
1074
  console.log(line);
1075
+ console.log('');
1076
+ for (const line of formatSteerabilityReport(steerability))
1077
+ console.log(line);
1078
+ }
1079
+ async function cmdReport(opts = {}) {
1080
+ if (!opts.steerability)
1081
+ throw new CliExitError('factory: report requires --steerability', 2);
1082
+ const report = await buildSteerabilityReport(getFactoryPaths(await getRepoRoot()));
1083
+ if (opts.json) {
1084
+ console.log(JSON.stringify(report));
1085
+ return;
1086
+ }
1087
+ for (const line of formatSteerabilityReport(report))
1088
+ console.log(line);
1058
1089
  }
1059
1090
  async function cmdClassifierBacktestEntry(opts) {
1060
1091
  const branchPrefix = branchPrefixOrExit(opts.branchPrefix);
@@ -1064,6 +1095,7 @@ async function cmdClassifierBacktestEntry(opts) {
1064
1095
  const repoConfig = configLoaders.loadRepoConfig(repoRoot);
1065
1096
  const effective = resolveEffectiveConfig(repoConfig);
1066
1097
  const router = new coreOps.ModelRouter(applyRepoConfig(configLoaders.loadModelsConfig(), repoConfig), configLoaders.loadRoutesConfig(), false, undefined, effective.allowExperimental, effective.localOnly);
1098
+ router.setCodexFullAccessTasks?.(configLoaders.loadFactoryConfigForRepo(paths.config).sandbox.codexFullAccessTasks);
1067
1099
  router.setLocalAuthSink?.(recordLocalAuthFailure);
1068
1100
  try {
1069
1101
  await cmdClassifierBacktest({ ...opts, branchPrefix }, {
@@ -1315,6 +1347,7 @@ async function cmdTui(opts = {}, runTuiImpl = runTui) {
1315
1347
  const routesConfig = configLoaders.loadRoutesConfig();
1316
1348
  const effective = resolveEffectiveConfig(repoConfig);
1317
1349
  const router = new coreOps.ModelRouter(modelsConfig, routesConfig, false, undefined, effective.allowExperimental, effective.localOnly);
1350
+ router.setCodexFullAccessTasks?.(configLoaders.loadFactoryConfigForRepo(paths.config).sandbox.codexFullAccessTasks);
1318
1351
  router.setLocalAuthSink?.(recordLocalAuthFailure);
1319
1352
  const effectiveConfigLines = describeEffectiveConfig({
1320
1353
  router,
@@ -1995,6 +2028,7 @@ async function cmdTriage(opts) {
1995
2028
  const routesConfig = configLoaders.loadRoutesConfig();
1996
2029
  const effective = resolveEffectiveConfig(repoConfig);
1997
2030
  const router = new coreOps.ModelRouter(modelsConfig, routesConfig, false, undefined, effective.allowExperimental, effective.localOnly);
2031
+ router.setCodexFullAccessTasks?.(configLoaders.loadFactoryConfigForRepo(paths.config).sandbox.codexFullAccessTasks);
1998
2032
  router.setLocalAuthSink?.(recordLocalAuthFailure);
1999
2033
  router.skipMissingHarnessClis(coreOps.isCommandAvailable);
2000
2034
  router.setCostSink((entry) => logCost(paths.costs, { ...entry, issue: '-' }));
@@ -2415,8 +2449,7 @@ async function cmdFactoryd(opts) {
2415
2449
  const previousUpdate = readUpdateState(updateFile);
2416
2450
  for (const stop of recoverStaleDrain(previousUpdate, (file) => {
2417
2451
  try {
2418
- unlinkSync(file);
2419
- return true;
2452
+ return clearSelfUpdateStop(file);
2420
2453
  }
2421
2454
  catch {
2422
2455
  return false;
@@ -2542,167 +2575,12 @@ export function laneQueueDeps(queue, lane) {
2542
2575
  countRemaining: async () => (await queue.list(lane)).length,
2543
2576
  };
2544
2577
  }
2578
+ export { preflightQueuedIssue };
2545
2579
  export function createQueuePreflightOps(octokit, owner, repo, opts = {}) {
2546
- return {
2547
- autoMergeStack: opts.autoMergeStack ?? false,
2548
- getPullRefs: async (prNumber) => {
2549
- const { data } = await octokit.rest.pulls.get({ owner, repo, pull_number: prNumber });
2550
- return { headRef: data.head.ref, baseRef: data.base.ref, headSha: data.head.sha };
2551
- },
2552
- expectedHeadRepoFullName: `${owner}/${repo}`,
2553
- findOpenPR: (issue) => findOpenPRForIssue(octokit, owner, repo, issue),
2554
- getLandState: (prNumber) => getPullRequestLandState(octokit, owner, repo, prNumber),
2580
+ return makeQueuePreflightOps(octokit, owner, repo, {
2555
2581
  watch: (ref) => internalOps.watchChecks({ octokit, owner, repo, ref }),
2556
- findSlicePlan: (issue) => findSlicePlanComment({ octokit, repo: `${owner}/${repo}`, issue }),
2557
- getPullState: async (prNumber) => {
2558
- const { data } = await octokit.rest.pulls.get({ owner, repo, pull_number: prNumber });
2559
- if (data.merged || data.merged_at)
2560
- return 'merged';
2561
- return data.state === 'open' ? 'open' : 'closed';
2562
- },
2563
- saveSlicePlan: async (issue, plan) => {
2564
- await upsertSlicePlanComment({ octokit, repo: `${owner}/${repo}`, issue, plan });
2565
- },
2566
- };
2567
- }
2568
- /** ADR-0147/ADR-0161: walk every pr-open slice PR bottom-up. Merged layers are recorded, a closed layer parks
2569
- * (naming the layers above it), and the lowest open layer defers, or is adopted when stack auto-merge is on and
2570
- * its base is main with green CI on the rebased head. Returns undefined to fall through to the Closes-PR
2571
- * preflight only when no slice PR is still open. */
2572
- async function preflightSlice(issue, deps) {
2573
- let lookup;
2574
- try {
2575
- lookup = await deps.findSlicePlan(issue);
2576
- }
2577
- catch (err) {
2578
- return { kind: 'park', reason: `slice plan lookup failed: ${errorDetail(err)}` };
2579
- }
2580
- if (lookup.status === 'invalid') {
2581
- return {
2582
- kind: 'park',
2583
- reason: `slice plan comment ${lookup.commentId} on #${issue} is unreadable (${lookup.error})`,
2584
- };
2585
- }
2586
- if (lookup.status === 'not-found')
2587
- return undefined;
2588
- const { plan } = lookup;
2589
- const n = plan.slices.length;
2590
- const open = plan.slices.filter((s) => s.state === 'pr-open');
2591
- if (open.length === 0)
2592
- return undefined;
2593
- let next = plan;
2594
- let lastMerged;
2595
- const save = async () => {
2596
- if (lastMerged === undefined)
2597
- return undefined;
2598
- try {
2599
- await deps.saveSlicePlan(issue, next);
2600
- }
2601
- catch (err) {
2602
- return {
2603
- kind: 'park',
2604
- reason: `${lastMerged.label} PR #${lastMerged.pr} merged but updating the slice plan failed: ${errorDetail(err)}`,
2605
- };
2606
- }
2607
- return undefined;
2608
- };
2609
- for (const [at, slice] of open.entries()) {
2610
- const label = `slice ${slice.index}/${n}`;
2611
- if (slice.prNumber === undefined) {
2612
- return { kind: 'park', reason: `${label} of #${issue} is pr-open but the slice plan records no PR number` };
2613
- }
2614
- const pr = slice.prNumber;
2615
- let state;
2616
- try {
2617
- state = await deps.getPullState(pr);
2618
- }
2619
- catch (err) {
2620
- return { kind: 'park', reason: `${label} PR #${pr} state lookup failed: ${errorDetail(err)}` };
2621
- }
2622
- if (state === 'merged') {
2623
- next = withSliceState(next, slice.index, 'merged', pr);
2624
- lastMerged = { label, pr };
2625
- continue;
2626
- }
2627
- if (state === 'closed') {
2628
- const above = open.slice(at + 1);
2629
- const suffix = above.length > 0
2630
- ? `; layers above it: ${above.map((a) => `slice ${a.index}/${n} (#${a.prNumber})`).join(', ')}`
2631
- : '';
2632
- return { kind: 'park', reason: `${label} PR #${pr} was closed without merging${suffix}` };
2633
- }
2634
- const saved = await save();
2635
- if (saved)
2636
- return saved;
2637
- if (!deps.autoMergeStack)
2638
- return { kind: 'defer' };
2639
- let refs;
2640
- try {
2641
- refs = await deps.getPullRefs(pr);
2642
- }
2643
- catch (err) {
2644
- return { kind: 'park', reason: `${label} PR #${pr} ref lookup failed: ${errorDetail(err)}` };
2645
- }
2646
- // GitHub has not retargeted the layer to main yet: the lower layer is still unmerged.
2647
- if (refs.baseRef !== 'main')
2648
- return { kind: 'defer' };
2649
- let outcome;
2650
- try {
2651
- outcome = await deps.watch(refs.headSha);
2652
- }
2653
- catch (err) {
2654
- return { kind: 'park', reason: `CI watch for ${label} PR #${pr} failed: ${errorDetail(err)}` };
2655
- }
2656
- if (outcome === 'failure')
2657
- return { kind: 'park', reason: `CI for ${label} PR #${pr} ended failure` };
2658
- if (outcome !== 'success')
2659
- return { kind: 'defer' };
2660
- return { kind: 'adopt', branch: refs.headRef, stackLayer: { index: slice.index, count: n, prNumber: pr } };
2661
- }
2662
- return (await save()) ?? undefined;
2663
- }
2664
- /** Resolve queued work from GitHub evidence without consuming model or worktree resources. */
2665
- export async function preflightQueuedIssue(issue, deps) {
2666
- if (issue.labels.includes('factory:in-progress'))
2667
- return { kind: 'defer' };
2668
- const slice = await preflightSlice(issue.number, deps);
2669
- if (slice)
2670
- return slice;
2671
- let pr;
2672
- try {
2673
- pr = await deps.findOpenPR(issue.number);
2674
- }
2675
- catch (err) {
2676
- return { kind: 'park', reason: `PR lookup failed: ${errorDetail(err)}` };
2677
- }
2678
- if (!pr)
2679
- return { kind: 'build' };
2680
- if (pr.headRepoFullName !== deps.expectedHeadRepoFullName) {
2681
- return { kind: 'park', reason: `PR #${pr.number} head is not in ${deps.expectedHeadRepoFullName}` };
2682
- }
2683
- let state;
2684
- try {
2685
- state = await deps.getLandState(pr.number);
2686
- }
2687
- catch (err) {
2688
- return { kind: 'park', reason: `PR #${pr.number} merge-state lookup failed: ${errorDetail(err)}` };
2689
- }
2690
- if (state.isDraft !== false) {
2691
- return { kind: 'park', reason: `PR #${pr.number} is draft or its draft state is unavailable` };
2692
- }
2693
- if (!state.mergeStateStatus || state.mergeStateStatus === 'UNKNOWN') {
2694
- return { kind: 'park', reason: `PR #${pr.number} merge state is unavailable` };
2695
- }
2696
- let outcome;
2697
- try {
2698
- outcome = await deps.watch(pr.headSha);
2699
- }
2700
- catch (err) {
2701
- return { kind: 'park', reason: `CI watch for PR #${pr.number} failed: ${errorDetail(err)}` };
2702
- }
2703
- if (outcome !== 'success')
2704
- return { kind: 'park', reason: `CI for PR #${pr.number} ended ${outcome}` };
2705
- return { kind: 'adopt', branch: pr.branch };
2582
+ ...opts,
2583
+ });
2706
2584
  }
2707
2585
  export async function planRunLanes(input) {
2708
2586
  if (input.localQueue) {
@@ -2831,7 +2709,7 @@ export async function cmdResumeApproved(opts = {}) {
2831
2709
  }
2832
2710
  const USAGE_UNAVAILABLE_LINE = '[factory] supervise: usage signal unavailable — proceeding without resume gate (set FACTORY_USAGE_ESTIMATOR=1 to gate on the heuristic)';
2833
2711
  export async function superviseLoop(deps) {
2834
- 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;
2712
+ 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;
2835
2713
  let cycle = 0;
2836
2714
  if (resumeAtSource === 'flag') {
2837
2715
  const detail = 'usage watchdog threshold set by flag: --usage-threshold';
@@ -2879,7 +2757,15 @@ export async function superviseLoop(deps) {
2879
2757
  }
2880
2758
  }
2881
2759
  emitEvent(eventsFile, 'resumed', 'usage', `supervise cycle ${cycle}: trailing usage (${source}) at ${Math.round(pct * 100)}% of cap — starting run`);
2882
- clearStop(stopFile);
2760
+ const marker = readMarker(stopFile);
2761
+ if (marker?.source === 'usage-watchdog') {
2762
+ clearStop(stopFile);
2763
+ }
2764
+ else if (marker) {
2765
+ writeLine(`[factory] supervise: STOP set by ${marker.source}${marker.at ? ` at ${marker.at}` : ''}; waiting for factory resume`);
2766
+ while (pathExists(stopFile))
2767
+ await sleep(pollMs);
2768
+ }
2883
2769
  if (ingest) {
2884
2770
  const appended = await ingest();
2885
2771
  if (appended > 0)
@@ -2958,7 +2844,7 @@ function logEventQuietly(eventsFile, type, msg) {
2958
2844
  /** Creates the STOP file in one exclusive open (no check-then-write race). Returns false when it already existed. */
2959
2845
  function createStopFile(stopFile) {
2960
2846
  try {
2961
- writeFileSync(stopFile, '', { flag: 'wx' });
2847
+ writeFileSync(stopFile, formatStopMarker('manual'), { flag: 'wx' });
2962
2848
  return true;
2963
2849
  }
2964
2850
  catch (err) {
@@ -3158,6 +3044,7 @@ export const PROGRAM_HANDLERS = {
3158
3044
  cmdRuns,
3159
3045
  cmdKpis,
3160
3046
  cmdClassifierReport,
3047
+ cmdReport,
3161
3048
  cmdClassifierBacktestEntry,
3162
3049
  cmdGarden,
3163
3050
  cmdTui,