@loucompanion/forge-bridge 0.1.3-dev.9bc5014a2270 → 0.1.3-dev.a58d7cd1092f

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 (168) hide show
  1. package/README.md +127 -0
  2. package/dist/bridge/bridge.base.d.ts +8 -1
  3. package/dist/bridge/bridge.service.d.ts +93 -3
  4. package/dist/bridge/bridge.service.js +1358 -47
  5. package/dist/bridge/bridge.types.d.ts +202 -1
  6. package/dist/bridge/runner-host-capabilities.d.ts +15 -0
  7. package/dist/bridge/runner-host-capabilities.js +93 -0
  8. package/dist/cleanup/cleanup.service.d.ts +14 -2
  9. package/dist/cleanup/cleanup.service.js +496 -3
  10. package/dist/cleanup/cleanup.types.d.ts +10 -0
  11. package/dist/cleanup/index.d.ts +1 -1
  12. package/dist/cleanup/index.js +1 -1
  13. package/dist/cleanup/internals.d.ts +2 -2
  14. package/dist/cleanup/internals.js +1 -1
  15. package/dist/cli/cli.help.js +47 -1
  16. package/dist/cli/cli.service.d.ts +45 -2
  17. package/dist/cli/cli.service.js +378 -26
  18. package/dist/cli/local-config.d.ts +7 -0
  19. package/dist/cli/local-config.js +51 -3
  20. package/dist/runtime/aspnet-runtime-prebuild.d.ts +36 -0
  21. package/dist/runtime/aspnet-runtime-prebuild.js +340 -0
  22. package/dist/runtime/attempt-node-runtime-launcher.d.ts +17 -0
  23. package/dist/runtime/attempt-node-runtime-launcher.js +105 -0
  24. package/dist/runtime/attempt-process-supervisor-registry.d.ts +20 -0
  25. package/dist/runtime/attempt-process-supervisor-registry.js +64 -0
  26. package/dist/runtime/attempt-process-supervisor.d.ts +97 -0
  27. package/dist/runtime/attempt-process-supervisor.js +386 -0
  28. package/dist/runtime/docker-engine-probe.d.ts +83 -0
  29. package/dist/runtime/docker-engine-probe.js +300 -0
  30. package/dist/runtime/generated-output-reset.d.ts +10 -0
  31. package/dist/runtime/generated-output-reset.js +103 -0
  32. package/dist/runtime/host-listener-inspector.d.ts +46 -0
  33. package/dist/runtime/host-listener-inspector.js +349 -0
  34. package/dist/runtime/host-port-broker.d.ts +76 -0
  35. package/dist/runtime/host-port-broker.js +333 -0
  36. package/dist/runtime/host-runtime-contract.d.ts +47 -0
  37. package/dist/runtime/host-runtime-contract.js +700 -0
  38. package/dist/runtime/host-runtime-lease.d.ts +197 -0
  39. package/dist/runtime/host-runtime-lease.js +2203 -0
  40. package/dist/runtime/host-runtime-promotion.d.ts +23 -0
  41. package/dist/runtime/host-runtime-promotion.js +159 -0
  42. package/dist/runtime/memory-capacity.d.ts +68 -0
  43. package/dist/runtime/memory-capacity.js +121 -0
  44. package/dist/runtime/node-host-runtime-adapter.d.ts +54 -0
  45. package/dist/runtime/node-host-runtime-adapter.js +284 -0
  46. package/dist/runtime/playwright-host-runtime-adapter.d.ts +21 -0
  47. package/dist/runtime/playwright-host-runtime-adapter.js +231 -0
  48. package/dist/runtime/runtime-isolation-lease.d.ts +67 -0
  49. package/dist/runtime/runtime-isolation-lease.js +158 -0
  50. package/dist/runtime/workspace-resource-estimator.d.ts +17 -0
  51. package/dist/runtime/workspace-resource-estimator.js +174 -0
  52. package/dist/sandbox/command-runner.d.ts +4 -0
  53. package/dist/sandbox/command-runner.js +47 -0
  54. package/dist/sandbox/contract.d.ts +5 -0
  55. package/dist/sandbox/contract.js +276 -0
  56. package/dist/sandbox/event-budget.d.ts +11 -0
  57. package/dist/sandbox/event-budget.js +27 -0
  58. package/dist/sandbox/image-policy.d.ts +35 -0
  59. package/dist/sandbox/image-policy.js +247 -0
  60. package/dist/sandbox/index.d.ts +5 -0
  61. package/dist/sandbox/index.js +5 -0
  62. package/dist/sandbox/lease-watchdog.d.ts +18 -0
  63. package/dist/sandbox/lease-watchdog.js +67 -0
  64. package/dist/sandbox/preflight.d.ts +84 -0
  65. package/dist/sandbox/preflight.js +521 -0
  66. package/dist/sandbox/process.d.ts +20 -0
  67. package/dist/sandbox/process.js +209 -0
  68. package/dist/sandbox/provider-auth/codex.d.ts +26 -0
  69. package/dist/sandbox/provider-auth/codex.js +212 -0
  70. package/dist/sandbox/repository-lock.d.ts +5 -0
  71. package/dist/sandbox/repository-lock.js +121 -0
  72. package/dist/sandbox/runtime.d.ts +297 -0
  73. package/dist/sandbox/runtime.js +2338 -0
  74. package/dist/sandbox/sandbox.types.d.ts +93 -0
  75. package/dist/sandbox/sandbox.types.js +1 -0
  76. package/dist/sandbox/smoke.d.ts +76 -0
  77. package/dist/sandbox/smoke.js +380 -0
  78. package/dist/sandbox/worker-supervisor.d.ts +13 -0
  79. package/dist/sandbox/worker-supervisor.js +36 -0
  80. package/dist/sandbox/worker.d.ts +1 -0
  81. package/dist/sandbox/worker.js +225 -0
  82. package/dist/sandbox/workspace.service.d.ts +13 -0
  83. package/dist/sandbox/workspace.service.js +72 -0
  84. package/dist/session/managed-attempt-tools.d.ts +21 -0
  85. package/dist/session/managed-attempt-tools.js +912 -0
  86. package/dist/session/managed-build-coordinator.d.ts +5 -0
  87. package/dist/session/managed-build-coordinator.js +105 -0
  88. package/dist/session/managed-build-receipt.d.ts +21 -0
  89. package/dist/session/managed-build-receipt.js +335 -0
  90. package/dist/session/phase-completion.artifacts.d.ts +17 -0
  91. package/dist/session/phase-completion.artifacts.js +256 -26
  92. package/dist/session/phase-completion.d.ts +12 -2
  93. package/dist/session/phase-completion.helpers.d.ts +5 -0
  94. package/dist/session/phase-completion.helpers.js +51 -10
  95. package/dist/session/phase-completion.js +14 -3
  96. package/dist/session/phase-completion.prompts.d.ts +3 -3
  97. package/dist/session/phase-completion.prompts.js +24 -8
  98. package/dist/session/phase-completion.runtime.d.ts +13 -1
  99. package/dist/session/phase-completion.runtime.js +189 -8
  100. package/dist/session/phase-completion.schemas.js +117 -4
  101. package/dist/session/phase-completion.validation.d.ts +4 -1
  102. package/dist/session/phase-completion.validation.js +153 -22
  103. package/dist/session/pre-publication-validation.d.ts +46 -0
  104. package/dist/session/pre-publication-validation.js +208 -0
  105. package/dist/session/provider-cli/ci-repair-readiness.d.ts +16 -0
  106. package/dist/session/provider-cli/ci-repair-readiness.js +110 -0
  107. package/dist/session/provider-cli/provider-cli.artifacts.d.ts +3 -2
  108. package/dist/session/provider-cli/provider-cli.artifacts.js +65 -7
  109. package/dist/session/provider-cli/provider-cli.base.d.ts +1 -0
  110. package/dist/session/provider-cli/provider-cli.deterministic.d.ts +2 -0
  111. package/dist/session/provider-cli/provider-cli.deterministic.js +66 -9
  112. package/dist/session/provider-cli/provider-cli.process.d.ts +26 -0
  113. package/dist/session/provider-cli/provider-cli.process.js +474 -23
  114. package/dist/session/provider-cli/provider-cli.prompts.d.ts +16 -2
  115. package/dist/session/provider-cli/provider-cli.prompts.js +224 -24
  116. package/dist/session/provider-cli/provider-cli.qa-sidecars.js +1 -0
  117. package/dist/session/provider-cli/provider-cli.runtime.d.ts +1 -1
  118. package/dist/session/provider-cli/provider-cli.runtime.js +6 -3
  119. package/dist/session/provider-cli/provider-cli.service.d.ts +26 -3
  120. package/dist/session/provider-cli/provider-cli.service.js +646 -90
  121. package/dist/session/provider-cli/provider-cli.types.d.ts +5 -0
  122. package/dist/session/provider-cli/provider-output-state.d.ts +2 -0
  123. package/dist/session/provider-cli/provider-output-state.js +91 -16
  124. package/dist/session/provider-cli/provider-usage.d.ts +7 -0
  125. package/dist/session/provider-cli/provider-usage.js +28 -5
  126. package/dist/session/session-redaction.d.ts +3 -0
  127. package/dist/session/session-redaction.js +38 -0
  128. package/dist/session/session.base.d.ts +6 -1
  129. package/dist/session/session.service.d.ts +8 -2
  130. package/dist/session/session.service.js +530 -42
  131. package/dist/session/session.types.d.ts +124 -1
  132. package/dist/session/session.types.js +231 -4
  133. package/dist/shared/format/format-bytes.d.ts +1 -0
  134. package/dist/shared/format/format-bytes.js +9 -0
  135. package/dist/shared/path/path.d.ts +1 -0
  136. package/dist/shared/path/path.js +21 -1
  137. package/dist/shared/process/dotnet-toolchain.d.ts +14 -0
  138. package/dist/shared/process/dotnet-toolchain.js +179 -0
  139. package/dist/shared/process/host-process-recovery.d.ts +12 -0
  140. package/dist/shared/process/host-process-recovery.js +78 -0
  141. package/dist/shared/process/host-process-supervisor.d.ts +59 -0
  142. package/dist/shared/process/host-process-supervisor.js +330 -0
  143. package/dist/shared/process/process.d.ts +1 -0
  144. package/dist/shared/process/process.js +1 -0
  145. package/dist/shared/process/windows-launch.d.ts +1 -1
  146. package/dist/shared/process/windows-launch.js +6 -3
  147. package/dist/shared/provider-capabilities.d.ts +1 -0
  148. package/dist/shared/provider-capabilities.js +3 -0
  149. package/dist/shared/redaction/redaction.js +16 -4
  150. package/dist/version.d.ts +1 -1
  151. package/dist/version.js +1 -1
  152. package/dist/worktree/artifact-injector.d.ts +3 -2
  153. package/dist/worktree/artifact-injector.js +43 -4
  154. package/dist/worktree/dependency-warmer.d.ts +36 -2
  155. package/dist/worktree/dependency-warmer.js +347 -11
  156. package/dist/worktree/index.d.ts +2 -1
  157. package/dist/worktree/index.js +1 -0
  158. package/dist/worktree/prior-phase-artifact-hydrator.d.ts +2 -0
  159. package/dist/worktree/prior-phase-artifact-hydrator.js +136 -0
  160. package/dist/worktree/worktree-paths.d.ts +4 -0
  161. package/dist/worktree/worktree-paths.js +30 -0
  162. package/dist/worktree/worktree.base.d.ts +3 -0
  163. package/dist/worktree/worktree.service.d.ts +13 -2
  164. package/dist/worktree/worktree.service.js +238 -47
  165. package/dist/worktree/worktree.types.d.ts +5 -1
  166. package/package.json +8 -4
  167. package/runtime/windows-job-supervisor.ps1 +444 -0
  168. package/runtime/windows-listener-owner.ps1 +33 -0
@@ -1,6 +1,6 @@
1
1
  import { GitClient } from "../../shared/git/git.js";
2
2
  import { type CompletionPhase, type CompletionToolRuntime, type DebugCompletionPhase, type QaCompletionGate } from "../phase-completion.js";
3
- import type { RunOnceDispatchPacket, RunnerDispatchArtifact } from "../session.types.js";
3
+ import type { RunOnceDispatchPacket, RunnerDispatchArtifact, RunnerDispatchSidecar } from "../session.types.js";
4
4
  import type { ProviderCliRunContext } from "./provider-cli.types.js";
5
5
  export declare function emitDraftGateArtifacts(emit: ProviderCliRunContext["emit"], worktreePath: string, workItemId: string): Promise<void>;
6
6
  export declare function emitDecomposeGateArtifacts(emit: ProviderCliRunContext["emit"], worktreePath: string, workItemId: string): Promise<void>;
@@ -18,8 +18,9 @@ export declare function isDebugCompletionPhaseForProvider(phase: CompletionPhase
18
18
  export declare function emitQaArtifactRecorded(emit: ProviderCliRunContext["emit"], worktreePath: string, artifact: RunnerDispatchArtifact): Promise<void>;
19
19
  export declare function readQaCompletionArtifactBundle(worktreePath: string, workItemId: string, ownerType?: string | null, ownerId?: string | null): Promise<RunnerDispatchArtifact | null>;
20
20
  export declare function hasCompleteQaArtifactBundle(worktreePath: string, workItemId: string, ownerType?: string | null, ownerId?: string | null): Promise<boolean>;
21
- export declare function enforceQaBrowserLaunchTargetGate(dispatch: RunOnceDispatchPacket, artifact: RunnerDispatchArtifact): void;
21
+ export declare function enforceQaBrowserLaunchTargetGate(dispatch: RunOnceDispatchPacket, artifact: RunnerDispatchArtifact): Promise<void>;
22
22
  export declare function validateQaBundleSidecarReferences(worktreePath: string, workItemId: string, ownerType?: string | null, ownerId?: string | null): Promise<void>;
23
+ export declare function readQaBundleSidecars(worktreePath: string, workItemId: string, ownerType?: string | null, ownerId?: string | null): Promise<RunnerDispatchSidecar[]>;
23
24
  export declare function emitQaStructuredEvents(emit: ProviderCliRunContext["emit"], artifact: RunnerDispatchArtifact): Promise<void>;
24
25
  interface QaGateArtifactSnapshot {
25
26
  readonly gate: QaCompletionGate;
@@ -4,7 +4,10 @@ import { artifactRootFor, artifactRootForOwner, debugCompletionPhaseOrder, draft
4
4
  import { ensureNoSymbolicLinkPathSegments, isPathUnderRoot, sha256File } from "../qa-sidecar-validation.js";
5
5
  import { hasLaunchTarget } from "./provider-cli.qa-sidecars.js";
6
6
  import { clip, isNodeError, messageFromError, normalizedString, parseJsonLine, stringField } from "./provider-output-state.js";
7
+ const maxTransportedQaSidecarBytes = 8 * 1024 * 1024;
8
+ const maxTransportedQaSidecarsTotalBytes = 16 * 1024 * 1024;
7
9
  const developCyclePhaseOrder = ["implementation", "self_review", "validation"];
10
+ const nonPublishableWorktreePaths = [".forge", ".local"];
8
11
  export async function emitDraftGateArtifacts(emit, worktreePath, workItemId) {
9
12
  const draftArtifactFiles = {
10
13
  draft_intent: "draft.intent.json",
@@ -147,7 +150,9 @@ export async function cleanupCompletionTools(completionTools, emit) {
147
150
  }
148
151
  export async function commitWorktreeChanges(git, worktreePath, workItemId) {
149
152
  await git.run(worktreePath, ["add", "-A"]);
150
- await git.run(worktreePath, ["reset", "--", ".forge"]).catch(() => "");
153
+ for (const reservedPath of nonPublishableWorktreePaths) {
154
+ await git.run(worktreePath, ["reset", "--", reservedPath]).catch(() => "");
155
+ }
151
156
  const changedFiles = (await git.run(worktreePath, ["diff", "--cached", "--name-only"]))
152
157
  .split(/\r?\n/)
153
158
  .map((line) => line.trim())
@@ -209,12 +214,13 @@ export async function readQaCompletionArtifactBundle(worktreePath, workItemId, o
209
214
  const planJson = await fs.readFile(path.join(artifactRoot, qaArtifactFileNames.plan), "utf8");
210
215
  const evidenceJson = await fs.readFile(path.join(artifactRoot, qaArtifactFileNames.evidence), "utf8");
211
216
  const findingsJson = await fs.readFile(path.join(artifactRoot, qaArtifactFileNames.findings), "utf8");
212
- await validateQaBundleSidecarReferences(worktreePath, workItemId, ownerType, ownerId);
217
+ const sidecars = await readQaBundleSidecars(worktreePath, workItemId, ownerType, ownerId);
213
218
  return {
214
219
  ...artifact,
215
220
  planJson,
216
221
  evidenceJson,
217
- findingsJson
222
+ findingsJson,
223
+ sidecars
218
224
  };
219
225
  }
220
226
  export async function hasCompleteQaArtifactBundle(worktreePath, workItemId, ownerType, ownerId) {
@@ -237,28 +243,55 @@ export async function hasCompleteQaArtifactBundle(worktreePath, workItemId, owne
237
243
  }
238
244
  return true;
239
245
  }
240
- export function enforceQaBrowserLaunchTargetGate(dispatch, artifact) {
246
+ export async function enforceQaBrowserLaunchTargetGate(dispatch, artifact) {
241
247
  const qaBrowser = dispatch.qaBrowser;
242
248
  if (!qaBrowser?.required || hasLaunchTarget(qaBrowser.launchTarget ?? null)) {
243
249
  return;
244
250
  }
251
+ const evidence = await readJsonObjectFile(path.join(path.dirname(artifact.path), qaArtifactFileNames.evidence));
252
+ if (hasPassingRepositoryPlaywrightEvidence(evidence)) {
253
+ return;
254
+ }
245
255
  const verdict = parseJsonLine(artifact.verdictJson) ?? {};
246
256
  const status = normalizedString(stringField(verdict, "status") ?? artifact.status ?? "");
247
257
  const blockerReason = normalizedString(stringField(verdict, "blocker_reason") ?? "");
248
258
  if (status !== "blocked" || blockerReason !== "missing_app_launch_target") {
249
- throw new Error("QA browser testing is required but no app launch target was provided; provider must return blocked with blocker_reason missing_app_launch_target.");
259
+ throw new Error("QA browser testing is required but neither an app launch target nor passing repository Playwright evidence was provided; provider must return blocked with blocker_reason missing_app_launch_target.");
260
+ }
261
+ }
262
+ function hasPassingRepositoryPlaywrightEvidence(evidence) {
263
+ const passingBrowserCheck = arrayField(evidence, "browser_checks").some(check => normalizedString(stringField(check, "status") ?? "") === "pass"
264
+ && Boolean(stringField(check, "url") ?? stringField(check, "route")));
265
+ if (!passingBrowserCheck) {
266
+ return false;
250
267
  }
268
+ return arrayField(evidence, "commands").some(command => {
269
+ const status = normalizedString(stringField(command, "status") ?? "");
270
+ const exitCode = command.exit_code;
271
+ const description = [stringField(command, "command"), stringField(command, "summary")]
272
+ .filter((value) => Boolean(value))
273
+ .join(" ");
274
+ return status === "pass"
275
+ && exitCode === 0
276
+ && /(?:playwright|test:e2e|e2e(?:\s|:|$)|browser(?:\s|-)?test)/iu.test(description);
277
+ });
251
278
  }
252
279
  export async function validateQaBundleSidecarReferences(worktreePath, workItemId, ownerType, ownerId) {
280
+ await readQaBundleSidecars(worktreePath, workItemId, ownerType, ownerId);
281
+ }
282
+ export async function readQaBundleSidecars(worktreePath, workItemId, ownerType, ownerId) {
253
283
  const artifactRoot = ownerType && ownerId
254
284
  ? artifactRootForOwner(worktreePath, ownerType, ownerId)
255
285
  : artifactRootFor(worktreePath, workItemId);
256
286
  const evidence = await readJsonObjectFile(path.join(artifactRoot, qaArtifactFileNames.evidence));
257
287
  const sidecars = arrayField(evidence, "sidecars");
258
288
  if (sidecars.length === 0) {
259
- return;
289
+ return [];
260
290
  }
261
291
  const qaSidecarRoot = path.resolve(artifactRoot, "qa");
292
+ const transported = [];
293
+ const transportedByPath = new Map();
294
+ let totalBytes = 0;
262
295
  for (const [index, sidecar] of sidecars.entries()) {
263
296
  const sidecarPath = stringField(sidecar, "path");
264
297
  const expectedHash = stringField(sidecar, "content_hash");
@@ -288,11 +321,36 @@ export async function validateQaBundleSidecarReferences(worktreePath, workItemId
288
321
  if (stat.size !== expectedBytes) {
289
322
  throw new Error(`QA sidecar ${index} bytes do not match the referenced file.`);
290
323
  }
324
+ if (stat.size > maxTransportedQaSidecarBytes) {
325
+ throw new Error(`QA sidecar ${index} exceeds the ${maxTransportedQaSidecarBytes}-byte transport limit.`);
326
+ }
291
327
  const actualHash = await sha256File(fullPath);
292
328
  if (actualHash !== expectedHash.trim().toLowerCase()) {
293
329
  throw new Error(`QA sidecar ${index} content_hash does not match the referenced file.`);
294
330
  }
331
+ const canonicalPath = path.relative(worktreePath, fullPath).split(path.sep).join("/");
332
+ const duplicateKey = canonicalPath.toLowerCase();
333
+ const existing = transportedByPath.get(duplicateKey);
334
+ if (existing) {
335
+ if (existing.contentHash !== actualHash || existing.bytes !== stat.size) {
336
+ throw new Error(`QA sidecar ${index} conflicts with another payload for ${canonicalPath}.`);
337
+ }
338
+ continue;
339
+ }
340
+ totalBytes += stat.size;
341
+ if (totalBytes > maxTransportedQaSidecarsTotalBytes) {
342
+ throw new Error(`QA sidecars exceed the ${maxTransportedQaSidecarsTotalBytes}-byte total transport limit.`);
343
+ }
344
+ const content = await fs.readFile(fullPath);
345
+ transportedByPath.set(duplicateKey, { contentHash: actualHash, bytes: stat.size });
346
+ transported.push({
347
+ path: canonicalPath,
348
+ contentBase64: content.toString("base64"),
349
+ contentHash: actualHash,
350
+ bytes: stat.size
351
+ });
295
352
  }
353
+ return transported;
296
354
  }
297
355
  export async function emitQaStructuredEvents(emit, artifact) {
298
356
  const artifactRoot = path.dirname(artifact.path);
@@ -444,7 +502,7 @@ export class LiveQaArtifactEmitter {
444
502
  if (!artifact) {
445
503
  return;
446
504
  }
447
- enforceQaBrowserLaunchTargetGate(this.dispatch, artifact);
505
+ await enforceQaBrowserLaunchTargetGate(this.dispatch, artifact);
448
506
  this.emittedBundle = true;
449
507
  await emitQaArtifactRecorded(this.emit, this.worktreePath, artifact);
450
508
  await emitQaStructuredEvents(this.emit, artifact);
@@ -2,6 +2,7 @@ import type { ProviderCliDraftContext, ProviderCliRunContext, ProviderCliRunResu
2
2
  export interface ProviderCliService {
3
3
  runImplementation(context: ProviderCliRunContext): Promise<ProviderCliRunResult>;
4
4
  runArchitect(context: ProviderCliRunContext): Promise<ProviderCliRunResult>;
5
+ runCiRepair(context: ProviderCliRunContext): Promise<ProviderCliRunResult>;
5
6
  runConflictResolve(context: ProviderCliRunContext): Promise<ProviderCliRunResult>;
6
7
  runQa(context: ProviderCliRunContext): Promise<ProviderCliRunResult>;
7
8
  runDraft(context: ProviderCliDraftContext): Promise<ProviderCliRunResult>;
@@ -8,5 +8,7 @@ export declare class DeterministicProviderCliService implements ProviderCliServi
8
8
  runImplementation(context: ProviderCliRunContext): Promise<ProviderCliRunResult>;
9
9
  runArchitect(context: ProviderCliRunContext): Promise<ProviderCliRunResult>;
10
10
  runQa(context: ProviderCliRunContext): Promise<ProviderCliRunResult>;
11
+ runCiRepair(context: ProviderCliRunContext): Promise<ProviderCliRunResult>;
11
12
  runConflictResolve(context: ProviderCliRunContext): Promise<ProviderCliRunResult>;
13
+ private runPullRequestRepair;
12
14
  }
@@ -5,9 +5,10 @@ import { buildSpecBody, selectLaneCommand } from "../../worktree/artifact-inject
5
5
  import { completionPhaseFromDispatch, debugCompletionPhaseOrder, expectedCompletionToolName, expectedDebugCompletionToolName, expectedQaCompletionToolName, qaCompletionGateOrder, readCompletionArtifact, usesDebugCycleCompletion, usesDevelopCycleCompletion, writeCompletionArtifact } from "../phase-completion.js";
6
6
  import { emitArchitectStructuredEvents, emitCompletionArtifactRecorded, emitQaArtifactRecorded, emitQaGateAccepted, emitQaStructuredEvents, isDebugCompletionPhaseForProvider, nextQaGate, phaseCompleteEventType, qaArtifactRootForDispatch, readQaCompletionArtifactBundle, readQaGateArtifact } from "./provider-cli.artifacts.js";
7
7
  import { deterministicBrowserCheck, deterministicBrowserSidecar, hasLaunchTarget, writeQaSidecarFiles } from "./provider-cli.qa-sidecars.js";
8
- import { buildProviderCliCommand, formatProviderCliCommand, isPullRequestCommentsDispatch, singleLine } from "./provider-cli.prompts.js";
8
+ import { buildProviderCliCommand, formatProviderCliCommand, isPullRequestCheckRepairDispatch, isPullRequestCommentsDispatch, singleLine } from "./provider-cli.prompts.js";
9
9
  import { estimatedDeterministicUsage, exactRedactionSecrets } from "./provider-cli.runtime.js";
10
10
  import { buildProviderUsageEvent } from "./provider-usage.js";
11
+ import { writeCiRepairReadinessManifest } from "./ci-repair-readiness.js";
11
12
  import { deterministicArchitectPayload, deterministicCompletionPayload, deterministicQaGatePayload } from "./provider-cli.deterministic-payloads.js";
12
13
  export class DeterministicProviderCliService {
13
14
  git;
@@ -202,26 +203,82 @@ export class DeterministicProviderCliService {
202
203
  artifacts: [qaArtifact]
203
204
  };
204
205
  }
206
+ async runCiRepair(context) {
207
+ return await this.runPullRequestRepair(context, "ci_repair");
208
+ }
205
209
  async runConflictResolve(context) {
210
+ return await this.runPullRequestRepair(context, "conflict_resolve");
211
+ }
212
+ async runPullRequestRepair(context, route) {
206
213
  const { dispatch, worktree } = context;
214
+ const repairChecks = route === "ci_repair" || isPullRequestCheckRepairDispatch(dispatch);
207
215
  const addressComments = isPullRequestCommentsDispatch(dispatch);
208
- await this.git.run(worktree.worktreePath, ["commit", "--allow-empty", "-m", `forge bridge ${addressComments ? "pr comments" : "conflict resolve"} for ${dispatch.workItem.id.slice(0, 8)}`]);
216
+ let repairArtifact = null;
217
+ let repairSpec = null;
218
+ if (repairChecks) {
219
+ const metadataRoot = path.join(worktree.worktreePath, ".forge", "work-items", dispatch.workItem.id);
220
+ await fs.mkdir(metadataRoot, { recursive: true });
221
+ const laneCommand = selectLaneCommand(dispatch.artifacts, dispatch.execution.lane, dispatch.cycleRun.phase);
222
+ if (!laneCommand)
223
+ throw new Error("Deterministic CI repair could not materialize the approved lane spec.");
224
+ const specBody = buildSpecBody(laneCommand, dispatch.workItem, "ci_repair");
225
+ const specPath = path.join(metadataRoot, "spec.md");
226
+ await fs.writeFile(specPath, specBody, "utf8");
227
+ await fs.writeFile(path.join(metadataRoot, "spec.meta.json"), `${JSON.stringify({ schema_version: 1, status: "approved", produced_by: "forge-lane-command" })}\n`, "utf8");
228
+ repairSpec = { repoPath: worktree.worktreePath, path: specPath, body: specBody, producedBy: "forge-lane-command", status: "approved" };
229
+ await writeCiRepairReadinessManifest(dispatch, worktree.worktreePath, specPath, context.runtimeEnvironment);
230
+ const reference = await writeCompletionArtifact({
231
+ worktreePath: worktree.worktreePath,
232
+ workItemId: dispatch.workItem.id,
233
+ phase: "ci_repair",
234
+ producedBy: "forge-deterministic-provider",
235
+ }, expectedCompletionToolName("ci_repair"), deterministicCiRepairPayload());
236
+ repairArtifact = await readCompletionArtifact(worktree.worktreePath, dispatch.workItem.id, "ci_repair");
237
+ if (!repairArtifact)
238
+ throw new Error(`Deterministic CI repair did not produce ${reference.path}.`);
239
+ await emitCompletionArtifactRecorded(context.emit, worktree.worktreePath, repairArtifact);
240
+ }
241
+ await this.git.run(worktree.worktreePath, ["commit", "--allow-empty", "-m", `forge bridge ${repairChecks ? "ci repair" : addressComments ? "pr comments" : "conflict resolve"} for ${dispatch.workItem.id.slice(0, 8)}`]);
209
242
  await context.emit(buildProviderUsageEvent(dispatch, estimatedDeterministicUsage()));
210
243
  await context.emit({
211
244
  role: "assistant",
212
245
  kind: "message",
213
- content: addressComments
214
- ? `Deterministic PR comment addressing completed for ${worktree.branchName}.`
215
- : `Deterministic conflict resolve completed for ${worktree.branchName}.`,
216
- eventType: addressComments ? "pr_comments_complete" : "conflict_resolve_complete"
246
+ content: repairChecks
247
+ ? `Deterministic CI repair completed for ${worktree.branchName}.`
248
+ : addressComments
249
+ ? `Deterministic PR comment addressing completed for ${worktree.branchName}.`
250
+ : `Deterministic conflict resolve completed for ${worktree.branchName}.`,
251
+ eventType: repairChecks ? "pr_checks_complete" : addressComments ? "pr_comments_complete" : "conflict_resolve_complete"
217
252
  });
218
253
  return {
219
- summary: addressComments
220
- ? `forge-bridge addressed PR comments for ${worktree.branchName}.`
221
- : `forge-bridge resolved conflicts for ${worktree.branchName}.`
254
+ summary: repairChecks
255
+ ? `forge-bridge repaired PR checks for ${worktree.branchName}.`
256
+ : addressComments
257
+ ? `forge-bridge addressed PR comments for ${worktree.branchName}.`
258
+ : `forge-bridge resolved conflicts for ${worktree.branchName}.`,
259
+ spec: repairSpec,
260
+ artifact: repairArtifact,
261
+ artifacts: repairArtifact ? [repairArtifact] : [],
222
262
  };
223
263
  }
224
264
  }
265
+ function deterministicCiRepairPayload() {
266
+ const command = { command: "forge deterministic ci-repair validation", exit_code: 0, status: "pass", summary: "Deterministic CI repair fixture passed." };
267
+ return {
268
+ status: "pass",
269
+ summary: "Deterministic CI repair handoff is ready for provider checks.",
270
+ failed_checks: [{ name: "CI / deterministic", provider_state: "failure", workflow: ".github/workflows/ci.yml", job: "deterministic", step: "fixture" }],
271
+ root_cause: { classification: "test expectation mismatch", summary: "Deterministic fixture failure repaired.", evidence: ["Fixture provider reported the expected failed check."] },
272
+ changed_files: [],
273
+ no_code_action: { action: "deterministic_fixture", reason: "The deterministic provider models the repair without a product diff." },
274
+ narrow_validation: [command],
275
+ workflow_validation: [{ step: "deterministic fixture", equivalence: "supported", command: command.command, exit_code: 0, status: "pass", summary: command.summary }],
276
+ broader_validation: [command],
277
+ remaining_risks: [],
278
+ provider_state: "ready_for_rerun",
279
+ ready_for_push: true,
280
+ };
281
+ }
225
282
  function deterministicDraftPayload(dispatch) {
226
283
  const kind = dispatch.draft?.kind ?? "work_item";
227
284
  if (kind === "work_order_decomposition") {
@@ -1,20 +1,28 @@
1
1
  import { type LaunchCommand } from "../../shared/process/windows-launch.js";
2
+ export declare const PROVIDER_OUTPUT_CAPTURE_LIMIT_CHARS: number;
2
3
  export type ProviderCliCommand = LaunchCommand;
3
4
  export interface ProviderProcessRunRequest {
4
5
  readonly command: string;
5
6
  readonly args: readonly string[];
6
7
  readonly cwd: string;
7
8
  readonly env?: Readonly<Record<string, string>>;
9
+ readonly stdin?: string;
10
+ readonly captureOutput?: boolean;
8
11
  }
9
12
  export interface ProviderProcessRunResult {
10
13
  readonly exitCode: number;
11
14
  readonly stdout: string;
12
15
  readonly stderr: string;
16
+ readonly peakMemoryBytes?: number;
17
+ readonly memorySampleCount?: number;
18
+ readonly memorySource?: string;
19
+ readonly memoryObservedAt?: string;
13
20
  }
14
21
  export interface ProviderProcessRunner {
15
22
  run(request: ProviderProcessRunRequest, handlers?: ProviderProcessHandlers, signal?: AbortSignal): Promise<ProviderProcessRunResult>;
16
23
  }
17
24
  export interface ProviderProcessHandlers {
25
+ onStarted?(): Promise<void> | void;
18
26
  onStdoutLine?(line: string): Promise<void> | void;
19
27
  onStderrLine?(line: string): Promise<void> | void;
20
28
  }
@@ -25,7 +33,25 @@ interface ProviderChildEnvironmentOptions {
25
33
  }
26
34
  export declare function buildProviderChildEnvironment(requestEnv?: Readonly<Record<string, string>>, options?: ProviderChildEnvironmentOptions): NodeJS.ProcessEnv;
27
35
  export declare class StreamingProviderProcessRunner implements ProviderProcessRunner {
36
+ private readonly terminalExitGraceMs;
37
+ constructor(terminalExitGraceMs?: number);
28
38
  run(request: ProviderProcessRunRequest, handlers?: ProviderProcessHandlers, signal?: AbortSignal): Promise<ProviderProcessRunResult>;
29
39
  }
40
+ /** Returns aggregate resident bytes for the exact supervised Unix process group. */
41
+ export declare function processGroupResidentMemoryBytes(processGroupId: number, platform?: NodeJS.Platform, procRoot?: string): number | null;
42
+ export declare function resolveCodexNpmDirectLaunch(commandPath: string, args: readonly string[]): LaunchCommand | null;
43
+ /**
44
+ * Avoids cmd.exe's nested-quote handling for package-manager shims. That
45
+ * quoting loses executable paths containing spaces after the command is
46
+ * wrapped by the Windows Job Object supervisor.
47
+ */
48
+ export declare function resolvePackageManagerDirectLaunch(commandPath: string, args: readonly string[]): LaunchCommand | null;
49
+ /**
50
+ * Resolves Windows commands without spawning where.exe. Runtime launches can
51
+ * happen while the machine is under enough process pressure for where.exe to
52
+ * miss its timeout; returning the unresolved package-manager name in that case
53
+ * makes Node fall back to a shell shim that cannot survive Job Object wrapping.
54
+ */
55
+ export declare function resolveWindowsCommandFromPath(command: string, pathValue?: string, pathExtValue?: string): string | null;
30
56
  export declare function chooseWindowsCommandCandidate(candidates: readonly string[]): string | null;
31
57
  export {};