@on-par/factory-cli 0.9.5-next.9 → 0.9.6-next.1

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.
@@ -0,0 +1,273 @@
1
+ // packages/cli/src/cli/ship-steps.ts — named steps of the ship flow: run-report/benchmark writers, sandbox logging, RunRequest builder (#2788)
2
+ import { exec as execCb } from 'node:child_process';
3
+ import { writeFileSync } from 'node:fs';
4
+ import { join, resolve } from 'node:path';
5
+ import { promisify, styleText } from 'node:util';
6
+ import { GITHUB_ISSUE_SOURCE, listQueuedSteering, localOnlyWorkspace, resolveBuildPublish, resolveDesignRegressionBlock, resolveReadinessUpdateIssueBody, rewriteQueueForDecomposition, worktreeWorkspace, writeBenchmarkArtifacts, } from '@on-par/factory-core';
7
+ import { currentSlice, findOpenPR, findSlicePlanComment, resolveFilingPolicy, resolveReviewFloorRules, withFileLock, withGitLock, } from '@on-par/factory-core/internal';
8
+ import { recordGitPushes, resolveGitHubMode, GITHUB_STUB_CALLS_FILE } from './github-stub.js';
9
+ import { shipRuntime } from './ship-runtime.js';
10
+ // Captures the message of the last terminal event runIssue emits (ready/fail/
11
+ // escalate/held/conflict/ci-failed/timeout) so the LaneParkError the ship adapter
12
+ // raises carries the same diagnostic text shipIssue used to throw with —
13
+ // RunOutcome's parked variant carries only the ParkReason enum, not free text.
14
+ export const TERMINAL_EVENT_KINDS = new Set([
15
+ 'ready',
16
+ 'environment-released',
17
+ 'fail',
18
+ 'escalate',
19
+ 'held',
20
+ 'conflict',
21
+ 'config',
22
+ 'ci-failed',
23
+ 'ci-infra',
24
+ 'timeout',
25
+ ]);
26
+ /** FACTORY_GITHUB=stub (#2652): record each `git push` ship runs in the stub's calls.jsonl. */
27
+ export function stubAwareShipPhase(shipPhase) {
28
+ let shipPhaseForMode = shipPhase;
29
+ if (resolveGitHubMode(process.env) === 'stub') {
30
+ const base = shipPhase;
31
+ const stubCalls = join(resolve(process.env.FACTORY_GITHUB_STUB_DIR ?? ''), GITHUB_STUB_CALLS_FILE);
32
+ const defaultShipRunner = promisify(execCb);
33
+ const wrapped = (o) => base({ ...o, run: recordGitPushes(o.run ?? defaultShipRunner, stubCalls, o.branch) });
34
+ shipPhaseForMode = wrapped;
35
+ }
36
+ return shipPhaseForMode;
37
+ }
38
+ /** Logs the sandbox state for this run and returns the policy to wrap agent commands in, if any. */
39
+ export function logSandboxPolicy(opts, sandboxPolicy, log) {
40
+ let activeSandboxPolicy;
41
+ if (opts.sandbox === false) {
42
+ console.error(styleText('yellow', 'factory: sandbox disabled by --no-sandbox — agent runs are UNCONTAINED'));
43
+ log('sandbox-disabled', 'sandbox disabled by --no-sandbox');
44
+ }
45
+ else if (!sandboxPolicy) {
46
+ log('sandbox-disabled', 'sandbox disabled by config/FACTORY_SANDBOX');
47
+ }
48
+ else if (sandboxPolicy.runtime === 'none') {
49
+ log('sandbox-unavailable', 'no sandbox runtime found (sandbox-exec/firejail) — running uncontained');
50
+ }
51
+ else if (sandboxPolicy.runtime === 'docker-sandbox') {
52
+ // docker-sandbox's containment is the microVM created by worktreeWorkspace in shipIssue
53
+ // (#653), not a command-prefix wrap — activeSandboxPolicy stays undefined because
54
+ // wrapCommandInSandbox remains a no-op for this runtime. createMicroVm logs the
55
+ // actual outcome ('sandbox' on success, 'sandbox-unavailable' on fallback), so no
56
+ // speculative log is emitted here.
57
+ }
58
+ else {
59
+ activeSandboxPolicy = sandboxPolicy;
60
+ if (sandboxPolicy.allowHosts.length > 0) {
61
+ log('sandbox-degraded', `host-level egress filtering unavailable in v1; intended allowlist: ${sandboxPolicy.allowHosts.join(', ')}`);
62
+ }
63
+ }
64
+ return activeSandboxPolicy;
65
+ }
66
+ /** ADR-0147: slice k >= 2 builds on its own branch, so the slice plan is read before the worktree exists. */
67
+ export async function findSliceIndex({ sizeGateMode, ctx, work, octokit, ghRepo, issueNum, }) {
68
+ const { errorDetail, LaneParkError } = shipRuntime();
69
+ let sliceIndex;
70
+ // ADR-0161: stack runs as slice until stacked PRs are built.
71
+ if ((sizeGateMode === 'slice' || sizeGateMode === 'stack') &&
72
+ !ctx?.localOnly &&
73
+ !ctx?.resumeFrom &&
74
+ work.kind === GITHUB_ISSUE_SOURCE) {
75
+ try {
76
+ const lookup = await findSlicePlanComment({ octokit, repo: ghRepo, issue: issueNum });
77
+ if (lookup.status === 'found')
78
+ sliceIndex = currentSlice(lookup.plan)?.index;
79
+ }
80
+ catch (err) {
81
+ throw new LaneParkError(`slice plan lookup failed for #${issueNum}: ${errorDetail(err)} — parked before creating a worktree`, 'escalate');
82
+ }
83
+ }
84
+ return sliceIndex;
85
+ }
86
+ /** Sets up the run's workspace: reuse a kept worktree, create a factory worktree, or wrap the local-only one. */
87
+ export async function setupRunWorkspace({ ctx, repoRoot, branch, worktree, log, worktreeSandbox, octokit, ghOwner, ghName, git, paths, }) {
88
+ let workspace;
89
+ if (ctx?.resumeFrom) {
90
+ workspace = { path: worktree, dispose: async () => { } };
91
+ log('worktree', `reusing kept worktree at ${worktree} (resume --from check)`);
92
+ }
93
+ else if (!ctx?.localOnly) {
94
+ workspace = await worktreeWorkspace({
95
+ repoRoot,
96
+ branch,
97
+ worktreePath: worktree,
98
+ log,
99
+ sandbox: worktreeSandbox,
100
+ findOpenPr: (br) => findOpenPR(octokit, ghOwner, ghName, br),
101
+ setup: (root, br, wt, sp, sandbox, setupLog, setupOpts) => withGitLock(root, () => withFileLock(paths.gitLock,
102
+ // setupWorktree fetches origin itself before creating the worktree (#1167),
103
+ // so no explicit gitFetch here — still under the git + file locks.
104
+ () => git.setupWorktree(root, br, wt, sp, sandbox, setupLog, setupOpts), { onSteal: (pid) => log('lock-stolen', `stole ${paths.gitLock} from dead holder pid ${pid ?? 'unknown'}`) })),
105
+ });
106
+ log('worktree', `Worktree ready at ${worktree}`);
107
+ }
108
+ else {
109
+ workspace = localOnlyWorkspace(ctx.localOnly);
110
+ log('workspace', `local-only: using caller-provided workspace ${worktree} (no factory worktree created)`);
111
+ }
112
+ return workspace;
113
+ }
114
+ /** Rewrites the queue for a decomposed issue, then throws so the lane moves on to the children. */
115
+ export function decomposeQueue({ issueNum, childIssues, paths, planLog, }) {
116
+ const { readTextFileOrEmpty, errorDetail, IssueDecomposedError } = shipRuntime();
117
+ const childList = childIssues.map((n) => `#${n}`).join(', ');
118
+ try {
119
+ const before = readTextFileOrEmpty(paths.queue);
120
+ const rewrite = rewriteQueueForDecomposition(before, { issue: issueNum, childIssues });
121
+ if (rewrite.changed) {
122
+ writeFileSync(paths.queue, rewrite.content);
123
+ planLog('decompose_filed', `queue entry for #${issueNum} replaced with ${childList}`);
124
+ }
125
+ else {
126
+ planLog('decompose_filed', `#${issueNum} had no queue entry to replace — continuing the lane with ${childList}`);
127
+ }
128
+ }
129
+ catch (err) {
130
+ planLog('decompose_file_failed', `queue rewrite for #${issueNum} failed: ${errorDetail(err)}`);
131
+ }
132
+ throw new IssueDecomposedError(`issue #${issueNum} decomposed into ${childList}`, childIssues);
133
+ }
134
+ /** Maps the core RunOutcome to the shipped branch name, or throws the park/pause error the lane expects. */
135
+ export function finishOutcome({ outcome, opts, ctx, paths, issueNum, worktree, terminalMessage, log, }) {
136
+ const { LaneParkError, LaneEnvironmentPauseError } = shipRuntime();
137
+ if (opts.interactive && !ctx?.localOnly && (outcome.state === 'ready' || outcome.state === 'shipped')) {
138
+ const leftover = listQueuedSteering(paths.steering, issueNum);
139
+ if (leftover.length > 0) {
140
+ log('steering_unconsumed', `${leftover.length} steering message(s) not consumed (no worker phase remained)`);
141
+ }
142
+ }
143
+ if (outcome.state === 'ready' || outcome.state === 'shipped') {
144
+ if (ctx?.localOnly) {
145
+ console.log(styleText('green', `✅ Local-only run complete in ${worktree} (no PR — publishing disabled)`));
146
+ }
147
+ else {
148
+ console.log(styleText('green', `✅ Issue #${issueNum} → ${terminalMessage ?? 'ready'}`));
149
+ }
150
+ return outcome.branch;
151
+ }
152
+ if (outcome.state === 'released') {
153
+ throw new LaneEnvironmentPauseError(terminalMessage ?? 'environment failure on base', {
154
+ baseSha: outcome.baseSha,
155
+ failingChecks: outcome.failingChecks,
156
+ failureSignature: outcome.failureSignature,
157
+ buildPreserved: outcome.buildPreserved,
158
+ });
159
+ }
160
+ const reason = outcome.state === 'escalated' ? 'escalate' : outcome.reason;
161
+ const message = outcome.state === 'escalated' ? outcome.reason : (terminalMessage ?? `run parked: ${outcome.reason}`);
162
+ throw new LaneParkError(message, reason, outcome.state === 'parked'
163
+ ? { failureSignature: outcome.failureSignature, failingChecks: outcome.failingChecks }
164
+ : undefined);
165
+ }
166
+ export function buildRunRequest({ issueNum, ghRepo, repoRoot, branch, specPath, work, product, runStartedAt, lane, opts, ctx, autoRework, planApprovalEnabled, factoryConfig, repoConfig, classifierPolicy, loaders, efficiency, sizeGateMode, maxSlices, timeouts, modelPins, codexOff, failoverSettings, activeSandboxPolicy, processGroupGraceMs, paths, }) {
167
+ const { repoConfigFileFor } = shipRuntime();
168
+ return {
169
+ issue: issueNum,
170
+ repo: ghRepo,
171
+ branch,
172
+ specPath,
173
+ work,
174
+ workSource: ctx?.workSource,
175
+ product,
176
+ startedAt: runStartedAt,
177
+ lane,
178
+ options: {
179
+ interactive: Boolean(opts.interactive),
180
+ autoRework,
181
+ approvePlan: planApprovalEnabled,
182
+ sandboxDisabled: opts.sandbox === false,
183
+ },
184
+ localOnly: Boolean(ctx?.localOnly),
185
+ blockUnresolvedRegressions: resolveDesignRegressionBlock(factoryConfig),
186
+ readinessUpdateIssueBody: resolveReadinessUpdateIssueBody(factoryConfig),
187
+ publishFromBuild: resolveBuildPublish(factoryConfig),
188
+ sizeGateMode,
189
+ maxSlices,
190
+ prClassifier: classifierPolicy.enabled
191
+ ? {
192
+ rules: resolveReviewFloorRules(repoConfig),
193
+ gateLabel: resolveFilingPolicy(factoryConfig).selfFixLabel,
194
+ modelPin: repoConfig?.models?.pins?.classifier,
195
+ }
196
+ : undefined,
197
+ timeouts,
198
+ modelPins,
199
+ codexDisabled: codexOff,
200
+ skipCI: loaders.resolveSkipCI(factoryConfig),
201
+ failover: failoverSettings,
202
+ efficiency: { maxReworkRounds: efficiency.maxReworkRounds, fastPath: efficiency.fastPath },
203
+ sandboxPolicy: activeSandboxPolicy,
204
+ processGroupGraceMs,
205
+ preferredRoute: repoConfig?.route,
206
+ ...(ctx?.resumeFrom
207
+ ? {
208
+ resumeFrom: {
209
+ phase: 'check',
210
+ diffBase: ctx.resumeFrom.diffBase,
211
+ route: ctx.resumeFrom.route,
212
+ model: ctx.resumeFrom.model,
213
+ buildMinutes: ctx.resumeFrom.buildMinutes,
214
+ },
215
+ }
216
+ : {}),
217
+ eventsFile: paths.events,
218
+ logsDir: paths.logs,
219
+ baselineCachePath: paths.baselineCache,
220
+ repoConfigFile: repoConfigFileFor(repoRoot, paths.config),
221
+ flakyLedgerPath: paths.flakyLedger,
222
+ };
223
+ }
224
+ export async function maybeWriteLocalRunReport(opts) {
225
+ if (process.env.FACTORY_LOCAL_ONLY !== '1')
226
+ return undefined;
227
+ const report = await (opts.writeLocalRunReport ?? shipRuntime().defaultWriteLocalRunReport())({
228
+ issue: opts.issueNum,
229
+ eventsFile: opts.paths.events,
230
+ reportsDir: opts.paths.reports,
231
+ startedAt: opts.startedAt,
232
+ outcome: opts.outcome,
233
+ profile: 'local-only',
234
+ branch: opts.branch,
235
+ worktree: opts.worktree,
236
+ specPath: opts.specPath,
237
+ route: opts.route,
238
+ reason: opts.reason,
239
+ });
240
+ console.log(styleText('cyan', `local-only report: ${report.path}`));
241
+ return report.path;
242
+ }
243
+ export async function maybeWriteBenchmarkArtifacts(opts) {
244
+ if (!opts.ctx?.localOnly || !opts.ctx.artifactsDir)
245
+ return;
246
+ try {
247
+ const { manifestPath } = await writeBenchmarkArtifacts({
248
+ issue: opts.issueNum,
249
+ artifactsDir: opts.ctx.artifactsDir,
250
+ eventsFile: opts.paths.events,
251
+ costsFile: opts.paths.costs,
252
+ startedAt: opts.startedAt,
253
+ outcome: opts.outcome,
254
+ workspace: opts.ctx.localOnly.workspace,
255
+ branch: opts.branch,
256
+ specPath: opts.specPath,
257
+ route: opts.route,
258
+ request: opts.ctx.workRequest,
259
+ checkSummary: opts.checkSummary,
260
+ reworkRounds: opts.reworkRounds,
261
+ failure: opts.failure,
262
+ reportPath: opts.reportPath,
263
+ diffBase: opts.diffBase,
264
+ });
265
+ opts.log('benchmark-artifacts', `manifest written to ${manifestPath}`);
266
+ console.log(styleText('cyan', `benchmark artifacts: ${manifestPath}`));
267
+ }
268
+ catch (err) {
269
+ // Never let artifact emission mask the run outcome.
270
+ opts.log('benchmark-artifacts-failed', `could not write benchmark artifacts: ${err.message}`);
271
+ }
272
+ }
273
+ //# sourceMappingURL=ship-steps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ship-steps.js","sourceRoot":"","sources":["../../src/cli/ship-steps.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import type { LocalOnlyPolicy, RunOutcome, WorkRequest, WorkRequestSourceKind } from '@on-par/factory-core';
2
+ import { getFactoryPaths, type SizeGateMode } from '@on-par/factory-core';
3
+ import type { CheckResumeRecord } from '@on-par/factory-core/internal';
4
+ import type { CliDeps, ConfigLoaderKey, CoreOpKey, GitOpKey, ShellOpKey } from './index.js';
5
+ export declare function shipIssue(issueNum: number, opts: {
6
+ product?: string;
7
+ autoRework?: boolean;
8
+ interactive?: boolean;
9
+ sandbox?: boolean;
10
+ approvePlan?: boolean;
11
+ branchPrefix?: string;
12
+ /** `run-issue --size-gate` (ADR-0147); undefined defers to sizeGate.mode in the config. */
13
+ sizeGate?: SizeGateMode;
14
+ /** run-issue --max-slices (ADR-0156); undefined defers to sizeGate.maxSlices. */
15
+ maxSlices?: number;
16
+ /** `--pr-classifier` / `--no-pr-classifier` (#1724); undefined defers to config/env. */
17
+ prClassifier?: boolean;
18
+ }, ctx?: {
19
+ repoRoot: string;
20
+ ghRepo: string;
21
+ /** Resolved factory state paths, supplied by daemon lanes with an external state root. */
22
+ paths?: ReturnType<typeof getFactoryPaths>;
23
+ lane?: string;
24
+ workRequest?: WorkRequest;
25
+ /** Receives the core RunOutcome before shipIssue maps it to a return/throw — lets
26
+ * `run-issue --run-children` read the PR number as data (#1747). */
27
+ onOutcome?: (outcome: RunOutcome) => void;
28
+ /** Input source for planPhase to resolve; defaults inside planPhase to this run's GitHub issue. */
29
+ workSource?: {
30
+ kind: WorkRequestSourceKind;
31
+ params: unknown;
32
+ };
33
+ /** Local-only policy (#508): run in this caller-provided workspace, skip
34
+ * worktree creation, disable publishing, and skip the SHIP phase. */
35
+ localOnly?: LocalOnlyPolicy;
36
+ /** Benchmark artifact directory (#509) — only set for local-only runs. */
37
+ artifactsDir?: string;
38
+ /** Resume at CHECK on a kept worktree (#2564). */
39
+ resumeFrom?: CheckResumeRecord;
40
+ }, deps?: Pick<CliDeps, 'octokit' | 'planPhase' | 'buildPhase' | 'checkPhase' | 'shipPhase' | 'checkBaseHealth' | ConfigLoaderKey | GitOpKey | ShellOpKey | CoreOpKey>): Promise<string>;
41
+ //# sourceMappingURL=ship.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ship.d.ts","sourceRoot":"","sources":["../../src/cli/ship.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAOV,eAAe,EAEf,UAAU,EAIV,WAAW,EACX,qBAAqB,EACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAML,eAAe,EAiBf,KAAK,YAAY,EAKlB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,iBAAiB,EAA2C,MAAM,+BAA+B,CAAC;AAkBhH,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAe5F,wBAAsB,SAAS,CAC7B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE;IACJ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,iFAAiF;IACjF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wFAAwF;IACxF,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,EACD,GAAG,CAAC,EAAE;IACJ,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,0FAA0F;IAC1F,KAAK,CAAC,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;yEACqE;IACrE,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,IAAI,CAAC;IAC1C,mGAAmG;IACnG,UAAU,CAAC,EAAE;QAAE,IAAI,EAAE,qBAAqB,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC;IAC9D;0EACsE;IACtE,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,UAAU,CAAC,EAAE,iBAAiB,CAAC;CAChC,EACD,IAAI,GAAE,IAAI,CACR,OAAO,EACL,SAAS,GACT,WAAW,GACX,YAAY,GACZ,YAAY,GACZ,WAAW,GACX,iBAAiB,GACjB,eAAe,GACf,QAAQ,GACR,UAAU,GACV,SAAS,CACP,GACL,OAAO,CAAC,MAAM,CAAC,CA8VjB"}
@@ -0,0 +1,293 @@
1
+ // packages/cli/src/cli/ship.ts — issue-level ship flow: resolve config, build RunRequest/RunPorts, run runIssue, map the outcome (#2788)
2
+ import { resolve } from 'node:path';
3
+ import { styleText } from 'node:util';
4
+ import { applyRepoConfig, closedWorkSkipReason, createDefaultWorkSourceRegistry, createFileApprovalGate, drainSteering, getFactoryPaths, GITHUB_ISSUE_SOURCE, LaneFileGuard, phaseSnapshotFile, ProviderBreaker, resolveAutoFailover, resolveCodexDisabled, resolveEfficiencyPolicy, resolveEnvironmentPorts, resolveEnvironmentProxy, resolvePlanApproval, resolveProcessGroupGraceMs, resolveSandboxPolicy, resolveSizeGateMaxSlices, resolveSizeGateMode, ReworkHistory, runIssue, summarizeEvent, touchLastEvent, touchRunActivity, writePhaseSnapshot, } from '@on-par/factory-core';
5
+ import { branchFor, checkBaseHealth, createCheckResumeStore, createPlanReuseStore, egressHostMessage, egressSummaryMessage, logCost, logEvent, recordLocalAuthFailure, resolveEffectiveConfig, resolvePrClassifierPolicy, shouldProxyEgress, startEgressProxy, worktreeSandboxFor, } from '@on-par/factory-core/internal';
6
+ import { formatDeprecation } from './octokit.js';
7
+ import { shipRuntime } from './ship-runtime.js';
8
+ import { buildRunRequest, decomposeQueue, findSliceIndex, finishOutcome, logSandboxPolicy, maybeWriteBenchmarkArtifacts, maybeWriteLocalRunReport, setupRunWorkspace, stubAwareShipPhase, TERMINAL_EVENT_KINDS, } from './ship-steps.js';
9
+ export async function shipIssue(issueNum, opts, ctx, deps = {}) {
10
+ const { resolveConfigLoaders, resolveGitOps, resolveShellOps, resolveCoreOps, resolvePhaseOps, getRepoRoot, getGitHubRepo, getOctokit, worktreePathFor, isCommandAvailableViaShell, readActiveProduct, resolveEnvironmentAcquirer, resolveLaneBaseUrl, IssueSkippedError, } = shipRuntime();
11
+ const loaders = resolveConfigLoaders(deps);
12
+ const git = resolveGitOps(deps);
13
+ const shell = resolveShellOps(deps);
14
+ const core = resolveCoreOps(deps);
15
+ const phases = resolvePhaseOps(deps);
16
+ const shipPhaseForMode = stubAwareShipPhase(phases.shipPhase);
17
+ const repoRoot = ctx?.repoRoot ?? (await getRepoRoot(shell));
18
+ const ghRepo = ctx?.ghRepo ?? (await getGitHubRepo(shell));
19
+ const paths = ctx?.paths ?? getFactoryPaths(repoRoot);
20
+ // GitHub REST deprecations seen on this run's client become one warn event per route (#2218).
21
+ const octokit = (deps.octokit ??
22
+ (() => getOctokit(shell, {
23
+ log: (d) => logEvent(paths.events, 'github_api_deprecated', issueNum, formatDeprecation(d), { lane: ctx?.lane }),
24
+ })))();
25
+ const [ghOwner, ghName] = ghRepo.split('/');
26
+ const repoConfig = loaders.loadRepoConfig(repoRoot, paths.root);
27
+ const factoryConfig = loaders.loadFactoryConfigForRepo(paths.config);
28
+ const classifierPolicy = resolvePrClassifierPolicy(factoryConfig, process.env, { classifier: opts.prClassifier });
29
+ const timeouts = loaders.resolveTimeouts(factoryConfig);
30
+ const failoverSettings = resolveAutoFailover(factoryConfig);
31
+ const breaker = new ProviderBreaker(paths.breaker);
32
+ const reworkHistory = new ReworkHistory(paths.reworkHistory);
33
+ const laneFileGuard = new LaneFileGuard(paths.laneFiles);
34
+ const efficiency = resolveEfficiencyPolicy(repoConfig);
35
+ const policy = {
36
+ models: applyRepoConfig(loaders.loadModelsConfig(), repoConfig),
37
+ routes: loaders.loadRoutesConfig(),
38
+ sandbox: factoryConfig.sandbox,
39
+ budget: { perIssueCapUsd: efficiency.perIssueCapUsd },
40
+ effective: opts.branchPrefix === undefined
41
+ ? resolveEffectiveConfig(repoConfig)
42
+ : { ...resolveEffectiveConfig(repoConfig), branchPrefix: opts.branchPrefix },
43
+ };
44
+ const router = new core.ModelRouter(policy.models, policy.routes, false, undefined, policy.effective.allowExperimental, policy.effective.localOnly);
45
+ router.setLocalAuthSink?.(recordLocalAuthFailure);
46
+ router.skipMissingHarnessClis(core.isCommandAvailable);
47
+ let issueSpend = 0;
48
+ let laneSandboxRuntime = 'none';
49
+ const laneWorkspaceBackend = factoryConfig.workspace?.backend === 'disposable-docker' ? 'disposable-docker' : 'worktree';
50
+ router.setCostSink((entry) => {
51
+ issueSpend += entry.cost ?? 0;
52
+ logCost(paths.costs, {
53
+ ...entry,
54
+ issue: String(issueNum),
55
+ sandboxRuntime: laneSandboxRuntime,
56
+ reworkRoundCount: laneReworkRounds,
57
+ workspaceBackend: laneWorkspaceBackend,
58
+ });
59
+ });
60
+ const modelPins = loaders.resolveEffectiveModelPins(router.registryRef, repoConfig);
61
+ const codexOff = resolveCodexDisabled(repoConfig);
62
+ const constitutionLoader = new core.ConstitutionLoader();
63
+ const product = opts.product ?? readActiveProduct(paths.product);
64
+ const autoRework = opts.autoRework ?? true;
65
+ const work = ctx?.workRequest ??
66
+ (await createDefaultWorkSourceRegistry({ octokit }).resolve(GITHUB_ISSUE_SOURCE, {
67
+ repo: ghRepo,
68
+ issue: issueNum,
69
+ }));
70
+ const issueTitle = work.title;
71
+ const sizeGateMode = opts.sizeGate ?? resolveSizeGateMode(factoryConfig);
72
+ const maxSlices = opts.maxSlices ?? resolveSizeGateMaxSlices(factoryConfig);
73
+ const sliceIndex = await findSliceIndex({ sizeGateMode, ctx, work, octokit, ghRepo, issueNum });
74
+ const branch = ctx?.resumeFrom?.branch ?? branchFor(issueNum, issueTitle, policy.effective.branchPrefix, sliceIndex);
75
+ const worktree = ctx?.resumeFrom
76
+ ? ctx.resumeFrom.worktree
77
+ : ctx?.localOnly
78
+ ? ctx.localOnly.workspace
79
+ : worktreePathFor(repoRoot, ghRepo, issueNum, policy.effective.branchPrefix, loaders);
80
+ const specPath = resolve(paths.plans, `issue-${issueNum}.md`);
81
+ const runStartedAt = new Date().toISOString();
82
+ const lane = ctx?.lane;
83
+ let terminalMessage;
84
+ let laneReworkRounds = 0;
85
+ const mkLog = (phase) => (type, msg, extra) => {
86
+ if (TERMINAL_EVENT_KINDS.has(type))
87
+ terminalMessage = msg;
88
+ if (type === 'rework')
89
+ laneReworkRounds++;
90
+ // checkPhase logs each skipped checker as a 'check' event prefixed "SKIPPED: " —
91
+ // echo it to the terminal the way shipIssue used to print it directly.
92
+ if (type === 'check' && msg.startsWith('SKIPPED: ')) {
93
+ console.error(styleText('yellow', ` SKIP: ${msg.slice('SKIPPED: '.length)}`));
94
+ }
95
+ // Likewise for each failing checker, so a parked run still names which checker
96
+ // failed and why — the parked RunOutcome carries only an aggregate count (#675).
97
+ if (type === 'check' && msg.startsWith('FAILED: ')) {
98
+ console.error(styleText('red', ` FAIL: ${msg.slice('FAILED: '.length)}`));
99
+ }
100
+ logEvent(paths.events, type, issueNum, msg, { ...extra, lane, phase });
101
+ // Best-effort lastEvent summary (#1327) for factory.run.snapshot — an
102
+ // observability side channel, so a write failure is swallowed, not logged (that
103
+ // would re-enter this same function for every event this chokepoint fires on).
104
+ touchLastEvent(phaseSnapshotFile(paths.runs, issueNum), summarizeEvent(type, msg)).catch(() => { });
105
+ };
106
+ const log = mkLog();
107
+ log('issue-title', issueTitle);
108
+ if (modelPins.plan) {
109
+ const source = modelPins.sources.plan === 'repo' ? '.factory/config.json' : 'FACTORY_PLAN_MODEL';
110
+ log('model-override', `plan model pinned to ${modelPins.plan} (${source})`);
111
+ }
112
+ if (modelPins.build) {
113
+ const source = modelPins.sources.build === 'repo' ? '.factory/config.json' : 'FACTORY_BUILD_MODEL';
114
+ log('model-override', `build model pinned to ${modelPins.build} (${source})`);
115
+ }
116
+ const skipReason = closedWorkSkipReason(work);
117
+ if (skipReason) {
118
+ log('skipped-already-closed', skipReason);
119
+ console.log(styleText('yellow', `skipped: ${skipReason}`));
120
+ throw new IssueSkippedError(skipReason, 'already-closed');
121
+ }
122
+ const sandboxPolicy = resolveSandboxPolicy(policy.sandbox, {
123
+ worktree,
124
+ repoRoot,
125
+ cliDisabled: opts.sandbox === false,
126
+ laneId: lane,
127
+ isAvailable: (cmd) => isCommandAvailableViaShell(shell, cmd),
128
+ });
129
+ laneSandboxRuntime = sandboxPolicy?.runtime ?? 'none';
130
+ const worktreeSandbox = worktreeSandboxFor(sandboxPolicy?.runtime, {
131
+ allowHosts: sandboxPolicy?.allowHosts ?? [],
132
+ });
133
+ const activeSandboxPolicy = logSandboxPolicy(opts, sandboxPolicy, log);
134
+ // Setup worktree FIRST — PLAN needs cwd=worktree to run claude. Worktree cleanup
135
+ // stays a CLI concern (not called here, matching today — a worktree persists past
136
+ // a successful run for GC/sweep commands to reclaim later).
137
+ const workspace = await setupRunWorkspace({
138
+ ctx,
139
+ repoRoot,
140
+ branch,
141
+ worktree,
142
+ log,
143
+ worktreeSandbox,
144
+ octokit,
145
+ ghOwner,
146
+ ghName,
147
+ git,
148
+ paths,
149
+ });
150
+ const planApprovalEnabled = opts.approvePlan ?? resolvePlanApproval(factoryConfig);
151
+ const processGroupGraceMs = resolveProcessGroupGraceMs(factoryConfig);
152
+ const portsSettings = resolveEnvironmentPorts(factoryConfig);
153
+ if (!portsSettings.enabled) {
154
+ log('environment_lease', 'port leasing disabled (environment.ports.enabled=false or FACTORY_ENV_PORTS=0)');
155
+ }
156
+ // acquireEnvironment.recordPgid/release only persist/deallocate the lease — process-
157
+ // group tracking and killing is runIssue's own unconditional tracker (Invariant 3),
158
+ // so a lease-disabled/failed run still tears down every spawned agent process.
159
+ const acquireEnvironment = portsSettings.enabled
160
+ ? resolveEnvironmentAcquirer({
161
+ laneSandboxRuntime: sandboxPolicy?.runtime ?? 'none',
162
+ paths,
163
+ worktree,
164
+ branch,
165
+ range: portsSettings.range,
166
+ processGroupGraceMs,
167
+ log,
168
+ findPortListeners: core.defaultFindPortListeners,
169
+ reapOrphanProcesses: core.reapOrphanProcesses,
170
+ })
171
+ : undefined;
172
+ const proxySettings = resolveEnvironmentProxy(factoryConfig);
173
+ const resolveBaseUrl = (appPort) => resolveLaneBaseUrl(paths, proxySettings, worktree, appPort);
174
+ const request = buildRunRequest({
175
+ issueNum,
176
+ ghRepo,
177
+ repoRoot,
178
+ branch,
179
+ specPath,
180
+ work,
181
+ product,
182
+ runStartedAt,
183
+ lane,
184
+ opts,
185
+ ctx,
186
+ autoRework,
187
+ planApprovalEnabled,
188
+ factoryConfig,
189
+ repoConfig,
190
+ classifierPolicy,
191
+ loaders,
192
+ efficiency,
193
+ sizeGateMode,
194
+ maxSlices,
195
+ timeouts,
196
+ modelPins,
197
+ codexOff,
198
+ failoverSettings,
199
+ activeSandboxPolicy,
200
+ processGroupGraceMs,
201
+ paths,
202
+ });
203
+ const ports = {
204
+ router,
205
+ octokit,
206
+ workspace,
207
+ events: mkLog,
208
+ acquireEnvironment,
209
+ resolveBaseUrl,
210
+ getIssueSpend: () => issueSpend,
211
+ breaker,
212
+ planReuse: ctx?.localOnly ? undefined : createPlanReuseStore(specPath),
213
+ checkResume: ctx?.localOnly ? undefined : createCheckResumeStore(specPath),
214
+ planPhase: phases.planPhase,
215
+ buildPhase: phases.buildPhase,
216
+ checkPhase: phases.checkPhase,
217
+ shipPhase: shipPhaseForMode,
218
+ checkBaseHealth: deps.checkBaseHealth ?? checkBaseHealth,
219
+ resolveConstitution: () =>
220
+ // Resolved once here — runIssue calls this exactly once and reuses the value for
221
+ // every phase, so the build worker can never author the standards it is graded by.
222
+ // A local-only workspace is caller-provided, not a checkout of repoRoot, so it keeps its own constitution.
223
+ constitutionLoader.resolve(worktree, product, factoryConfig.paths?.constitution, ctx?.localOnly ? undefined : repoRoot),
224
+ createApprovalGate: () => createFileApprovalGate({ dir: paths.approvals, timeoutMs: timeouts.approval * 1000 }),
225
+ drainSteering: () => drainSteering(paths.steering, issueNum, worktree),
226
+ reworkHistory,
227
+ laneFileGuard,
228
+ recordPhase: (phase) => {
229
+ const now = new Date().toISOString();
230
+ return writePhaseSnapshot(phaseSnapshotFile(paths.runs, issueNum), {
231
+ issue: issueNum,
232
+ phase,
233
+ updatedAt: now,
234
+ lastActivityAt: now,
235
+ });
236
+ },
237
+ onActivity: () => touchRunActivity(phaseSnapshotFile(paths.runs, issueNum), new Date().toISOString()),
238
+ onDecomposed: (childIssues) => {
239
+ decomposeQueue({ issueNum, childIssues, paths, planLog: mkLog('plan') });
240
+ },
241
+ writeLocalRunReport: (info) => maybeWriteLocalRunReport({
242
+ issueNum,
243
+ paths,
244
+ startedAt: runStartedAt,
245
+ outcome: info.outcome,
246
+ branch: info.branch,
247
+ worktree,
248
+ specPath,
249
+ route: info.route,
250
+ reason: info.reason,
251
+ writeLocalRunReport: core.writeLocalRunReport,
252
+ }),
253
+ writeBenchmarkArtifacts: (info) => maybeWriteBenchmarkArtifacts({
254
+ issueNum,
255
+ paths,
256
+ ctx,
257
+ startedAt: runStartedAt,
258
+ outcome: info.outcome === 'parked' ? 'failed' : info.outcome,
259
+ branch: info.branch,
260
+ specPath,
261
+ route: info.route,
262
+ checkSummary: info.checkSummary,
263
+ reworkRounds: info.reworkRounds,
264
+ failure: info.failure,
265
+ reportPath: info.reportPath,
266
+ diffBase: info.diffBase,
267
+ log,
268
+ }),
269
+ };
270
+ // #2214: report-only per-lane egress proxy for sandbox-exec. Started here, after worktree setup,
271
+ // so the finally below is the single place that stops it on every exit path.
272
+ const proxied = shouldProxyEgress(request.sandboxPolicy) ? request.sandboxPolicy : undefined;
273
+ const egressProxy = proxied
274
+ ? await startEgressProxy({
275
+ allowHosts: proxied.allowHosts,
276
+ blockTelemetry: proxied.blockTelemetry,
277
+ onHost: (host, allowed) => log('egress_host', egressHostMessage(host, allowed)),
278
+ })
279
+ : undefined;
280
+ let outcome;
281
+ try {
282
+ outcome = await runIssue(proxied && egressProxy ? { ...request, sandboxPolicy: { ...proxied, proxyUrl: egressProxy.url } } : request, policy, ports);
283
+ }
284
+ finally {
285
+ if (proxied && egressProxy) {
286
+ log('egress_summary', egressSummaryMessage(egressProxy.hosts(), proxied.allowHosts));
287
+ await egressProxy.close();
288
+ }
289
+ }
290
+ ctx?.onOutcome?.(outcome);
291
+ return finishOutcome({ outcome, opts, ctx, paths, issueNum, worktree, terminalMessage, log });
292
+ }
293
+ //# sourceMappingURL=ship.js.map