@pushpalsdev/cli 1.0.32 → 1.0.34

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 (46) hide show
  1. package/dist/pushpals-cli.js +4 -4
  2. package/monitor-ui/+not-found.html +1 -1
  3. package/monitor-ui/_expo/static/js/web/{entry-275c5f7972e2d2f4f0422fe2213a7f89.js → entry-5e6db7139bc13703a24f952bd64faf4c.js} +2 -2
  4. package/monitor-ui/_sitemap.html +1 -1
  5. package/monitor-ui/index.html +1 -1
  6. package/monitor-ui/modal.html +1 -1
  7. package/package.json +1 -1
  8. package/runtime/prompts/localbuddy/localbuddy_planner_git_diff_section.md +0 -1
  9. package/runtime/prompts/localbuddy/localbuddy_planner_git_status_section.md +0 -1
  10. package/runtime/prompts/localbuddy/localbuddy_planner_output_contract.md +1 -0
  11. package/runtime/prompts/remotebuddy/autonomy_ideation_system_prompt.md +31 -30
  12. package/runtime/prompts/remotebuddy/autonomy_scoring_system_prompt.md +2 -2
  13. package/runtime/prompts/remotebuddy/context_packer_user_prompt.md +1 -0
  14. package/runtime/prompts/remotebuddy/remotebuddy_system_prompt.md +1 -0
  15. package/runtime/prompts/review_agent/review_prompt_template.md +1 -0
  16. package/runtime/prompts/review_agent/reviewer.md +6 -4
  17. package/runtime/prompts/workerpals/commit_message_prompt.md +3 -0
  18. package/runtime/prompts/workerpals/miniswe_broker_system_prompt.md +10 -9
  19. package/runtime/prompts/workerpals/miniswe_strict_tool_use_guidance.md +1 -0
  20. package/runtime/prompts/workerpals/openai_codex_runtime_policy_appendix.md +1 -0
  21. package/runtime/prompts/workerpals/openai_codex_task_execute_system_prompt.md +2 -0
  22. package/runtime/prompts/workerpals/task_quality_critic_system_prompt.md +3 -2
  23. package/runtime/sandbox/apps/workerpals/src/backends/openai_codex_backend.ts +4 -4
  24. package/runtime/sandbox/apps/workerpals/src/backends/openhands_task_execute.ts +1 -4
  25. package/runtime/sandbox/apps/workerpals/src/common/execution_utils.ts +5 -3
  26. package/runtime/sandbox/apps/workerpals/src/common/generic_python_executor.ts +1 -4
  27. package/runtime/sandbox/apps/workerpals/src/common/worktree_cleanup.ts +3 -2
  28. package/runtime/sandbox/apps/workerpals/src/docker_executor.ts +132 -32
  29. package/runtime/sandbox/apps/workerpals/src/execute_job.ts +7 -7
  30. package/runtime/sandbox/apps/workerpals/src/job_runner.ts +7 -4
  31. package/runtime/sandbox/apps/workerpals/src/workerpals_main.ts +46 -27
  32. package/runtime/sandbox/packages/shared/src/autonomy_policy.ts +8 -3
  33. package/runtime/sandbox/packages/shared/src/communication.ts +19 -8
  34. package/runtime/sandbox/packages/shared/src/config.ts +9 -24
  35. package/runtime/sandbox/packages/shared/src/config_template_parity.ts +5 -6
  36. package/runtime/sandbox/packages/shared/src/git_backend.ts +5 -9
  37. package/runtime/sandbox/packages/shared/src/local_network.ts +3 -1
  38. package/runtime/sandbox/packages/shared/src/localbuddy_runtime.ts +4 -5
  39. package/runtime/sandbox/packages/shared/src/vision.ts +6 -2
  40. package/runtime/sandbox/prompts/workerpals/commit_message_prompt.md +3 -0
  41. package/runtime/sandbox/prompts/workerpals/miniswe_broker_system_prompt.md +10 -9
  42. package/runtime/sandbox/prompts/workerpals/miniswe_strict_tool_use_guidance.md +1 -0
  43. package/runtime/sandbox/prompts/workerpals/openai_codex_runtime_policy_appendix.md +1 -0
  44. package/runtime/sandbox/prompts/workerpals/openai_codex_task_execute_system_prompt.md +2 -0
  45. package/runtime/sandbox/prompts/workerpals/task_quality_critic_system_prompt.md +3 -2
  46. package/runtime/vision.example.md +24 -5
@@ -23,6 +23,7 @@ Intent taxonomy (choose the single best fit):
23
23
  - `other` — do NOT use `other` with `requires_worker=false`. `other` must always have `requires_worker=true`. When in doubt, prefer `code_change`.
24
24
 
25
25
  Classification rules (applied in order):
26
+
26
27
  1. Action verb present (add, fix, update, implement, create, remove, test, run, build, configure, refactor, improve, etc.) → `code_change` + `requires_worker=true`
27
28
  2. File/test/config/component reference + no explicit read-only ask → `code_change` + `requires_worker=true`
28
29
  3. Read-only analysis explicitly requested → `analysis` + `requires_worker=false`
@@ -11,6 +11,7 @@ Pull Request: #{{pr_number}} - {{pr_title}}
11
11
  Branch: {{head_ref}} -> {{base_ref}}
12
12
 
13
13
  Diff:
14
+
14
15
  ```diff
15
16
  {{diff}}
16
17
  ```
@@ -7,6 +7,7 @@ Return an objective quality score and specific, actionable feedback. The ReviewA
7
7
  ## Rating Criteria
8
8
 
9
9
  ### 9.0-10.0: Distinguished Engineer quality
10
+
10
11
  - Code is correct, complete, and production-ready with zero known defects
11
12
  - All edge cases and error paths are handled explicitly
12
13
  - Tests cover both positive (happy path) and negative (failure/edge) cases with meaningful assertions
@@ -21,6 +22,7 @@ Return an objective quality score and specific, actionable feedback. The ReviewA
21
22
  ### 1.0-6.9: Not production-ready - list specific issues and remediation steps
22
23
 
23
24
  Common rejection reasons:
25
+
24
26
  - Missing negative test assertions
25
27
  - Incomplete error handling
26
28
  - Tests that pass trivially (no real assertions)
@@ -32,8 +34,8 @@ Common rejection reasons:
32
34
 
33
35
  Respond with a JSON object only (no markdown wrapper):
34
36
  {
35
- "score": <number 1.0-10.0>,
36
- "summary": "<one sentence verdict>",
37
- "issues": ["<issue 1>", "<issue 2>", ...],
38
- "fix_instruction": "<precise instruction for the worker to fix all issues - this will be sent directly to the WorkerPal as its task>"
37
+ "score": <number 1.0-10.0>,
38
+ "summary": "<one sentence verdict>",
39
+ "issues": ["<issue 1>", "<issue 2>", ...],
40
+ "fix_instruction": "<precise instruction for the worker to fix all issues - this will be sent directly to the WorkerPal as its task>"
39
41
  }
@@ -10,6 +10,7 @@ Output only the raw commit message text — no markdown fences, no explanation,
10
10
  - <specific implementation detail>
11
11
 
12
12
  Tests:
13
+
13
14
  - <test runner command>
14
15
 
15
16
  ## Writing rules
@@ -25,12 +26,14 @@ Background context: "can you add one more unit test for localbuddy"
25
26
 
26
27
  Bad (copies instruction / uses planning language):
27
28
  {{type}}({{area}}): lets add one more unit test for localbuddy
29
+
28
30
  - At least one new unit test is added validating a meaningful LocalBuddy behavior.
29
31
  - All existing and new tests pass.
30
32
  - No unrelated files are modified.
31
33
 
32
34
  Good (reads the diff):
33
35
  {{type}}({{area}}): add unit test for LocalBuddy request routing and error response handling
36
+
34
37
  - add test case in localbuddy.test.ts asserting router returns 404 for unknown tool calls
35
38
  - add negative test for malformed request payload returning 400 with error message
36
39
  - extract shared test fixtures into testHelpers.ts to reduce duplication
@@ -5,18 +5,19 @@ Repository root: {{repo}}
5
5
 
6
6
  Output format (STRICT JSON, no markdown, no extra keys unless specified):
7
7
  {
8
- "actions": [
9
- {"type":"read_file","path":"README.md"},
10
- {"type":"append_line","path":"README.md","line":"..."},
11
- {"type":"replace_text_once","path":"x","old":"a","new":"b"},
12
- {"type":"write_file","path":"x","content":"..."},
13
- {"type":"run_shell","command":"git status --porcelain"}
14
- ],
15
- "done": false,
16
- "note": "short explanation"
8
+ "actions": [
9
+ {"type":"read_file","path":"README.md"},
10
+ {"type":"append_line","path":"README.md","line":"..."},
11
+ {"type":"replace_text_once","path":"x","old":"a","new":"b"},
12
+ {"type":"write_file","path":"x","content":"..."},
13
+ {"type":"run_shell","command":"git status --porcelain"}
14
+ ],
15
+ "done": false,
16
+ "note": "short explanation"
17
17
  }
18
18
 
19
19
  Rules:
20
+
20
21
  - Keep actions minimal and directly relevant.
21
22
  - JSON syntax must be exact: use ":" between keys and values, never ",".
22
23
  - Use double quotes for all keys and string values.
@@ -1,4 +1,5 @@
1
1
  CRITICAL: You must use tools to make progress.
2
+
2
3
  - Use the environment's tools (file read/list/search, and file edit/write/patch) to inspect and modify the repo.
3
4
  - Do NOT only describe what you would do; actually do it.
4
5
  - Avoid broad scans; choose one target file quickly.
@@ -1,4 +1,5 @@
1
1
  Runtime policy guardrails (mandatory):
2
+
2
3
  - Codex CLI is required infrastructure in this environment.
3
4
  - Never bypass Codex usage by changing tests/code expectations.
4
5
  - If Codex CLI auth/execution is unavailable, hard-fail and stop.
@@ -1,12 +1,14 @@
1
1
  You are PushPals WorkerPal running via the OpenAI Codex CLI backend.
2
2
 
3
3
  Non-negotiable runtime invariants:
4
+
4
5
  - Codex CLI is required infrastructure in this environment.
5
6
  - Do not modify tests or production code to bypass, stub, or remove Codex CLI usage due to assumed environment limitations.
6
7
  - Do not "adapt around" missing Codex access by rewriting coverage or behavior expectations.
7
8
  - If Codex CLI authentication/execution is unavailable, fail loudly with a clear error and stop.
8
9
 
9
10
  Execution rules:
11
+
10
12
  - Keep edits minimal, correct, and scoped to the requested task.
11
13
  - Read relevant files before editing, then run focused validation.
12
14
  - Report blockers explicitly; do not hide platform/runtime issues with workaround edits.
@@ -2,8 +2,9 @@ You are a strict code-review critic for worker-generated patches.
2
2
  Return exactly one JSON object with keys:
3
3
  {"score": <0-10 number>, "findings": [string], "must_fix": [string], "revision_guidance": string}
4
4
  Scoring rubric:
5
+
5
6
  - 10: complete, correct, and robust with strong validation coverage.
6
7
  - 8-9: good quality with minor non-blocking issues.
7
8
  - <=7: requires revision before commit.
8
- must_fix must list blocking issues only.
9
- Do not include markdown or prose outside JSON.
9
+ must_fix must list blocking issues only.
10
+ Do not include markdown or prose outside JSON.
@@ -27,14 +27,14 @@ function warmupProbeCommand(sharedVenvPython: string): string {
27
27
  'AUTH_MODE="$(printf %s "$AUTH_MODE_RAW" | tr "[:upper:]" "[:lower:]")"; ' +
28
28
  'if [ ! -x "$PY" ]; then PY="$(command -v python3 || command -v python || true)"; fi; ' +
29
29
  '[ -n "$PY" ] || { echo "python runtime not found" >&2; exit 1; }; ' +
30
- 'if command -v bunx >/dev/null 2>&1; then ' +
30
+ "if command -v bunx >/dev/null 2>&1; then " +
31
31
  ' CODEX_CMD="bunx --yes @openai/codex"; ' +
32
- 'elif command -v codex >/dev/null 2>&1; then ' +
32
+ "elif command -v codex >/dev/null 2>&1; then " +
33
33
  ' CODEX_CMD="codex"; ' +
34
- 'else ' +
34
+ "else " +
35
35
  ' echo "Neither bunx nor codex was found in PATH" >&2; ' +
36
36
  " exit 1; " +
37
- 'fi; ' +
37
+ "fi; " +
38
38
  'sh -lc "$CODEX_CMD --version"; ' +
39
39
  'NEED_LOGIN="0"; ' +
40
40
  'if [ "$AUTH_MODE" = "chatgpt" ] || [ "$AUTH_MODE" = "chatgpt_login" ] || [ "$AUTH_MODE" = "subscription" ]; then NEED_LOGIN="1"; fi; ' +
@@ -55,10 +55,7 @@ function estimateJobTokenUsage(
55
55
  };
56
56
  }
57
57
 
58
- function coerceJobTokenUsage(
59
- value: unknown,
60
- fallback: JobTokenUsage,
61
- ): JobTokenUsage {
58
+ function coerceJobTokenUsage(value: unknown, fallback: JobTokenUsage): JobTokenUsage {
62
59
  if (!value || typeof value !== "object" || Array.isArray(value)) {
63
60
  return fallback;
64
61
  }
@@ -16,8 +16,7 @@ export function resolveOutputCompactionPolicy(
16
16
  const maxOutputChars = Number(overrides.maxOutputChars ?? worker.outputMaxChars);
17
17
  const maxOutputLines = Number(overrides.maxOutputLines ?? worker.outputMaxLines);
18
18
  const maxOutputHeadLines = Number(overrides.maxOutputHeadLines ?? worker.outputMaxHeadLines);
19
- const executorResultPrefixRaw =
20
- overrides.executorResultPrefix ?? worker.executorResultPrefix;
19
+ const executorResultPrefixRaw = overrides.executorResultPrefix ?? worker.executorResultPrefix;
21
20
  const executorResultPrefix =
22
21
  typeof executorResultPrefixRaw === "string" && executorResultPrefixRaw.length > 0
23
22
  ? executorResultPrefixRaw
@@ -42,7 +41,10 @@ export function resolveOutputCompactionPolicy(
42
41
 
43
42
  // ---- Output truncation -------------------------------------------------------
44
43
 
45
- export function compactJobOutput(text: string, policyOverrides: Partial<OutputCompactionPolicy> = {}): string {
44
+ export function compactJobOutput(
45
+ text: string,
46
+ policyOverrides: Partial<OutputCompactionPolicy> = {},
47
+ ): string {
46
48
  if (!text) return "";
47
49
  const policy = resolveOutputCompactionPolicy(policyOverrides);
48
50
  const maxOutputChars = policy.maxOutputChars;
@@ -58,10 +58,7 @@ function estimateJobTokenUsage(
58
58
  };
59
59
  }
60
60
 
61
- function coerceJobTokenUsage(
62
- value: unknown,
63
- fallback: JobTokenUsage,
64
- ): JobTokenUsage {
61
+ function coerceJobTokenUsage(value: unknown, fallback: JobTokenUsage): JobTokenUsage {
65
62
  if (!value || typeof value !== "object" || Array.isArray(value)) {
66
63
  return fallback;
67
64
  }
@@ -39,7 +39,9 @@ export async function forceDeleteWorktreePath(
39
39
  const retries = Math.max(1, Math.floor(options.retries ?? 5));
40
40
  const delayMs = Math.max(0, Math.floor(options.delayMs ?? 120));
41
41
  const sleep = options.sleepFn ?? defaultSleep;
42
- const removePath = options.removeFn ?? ((targetPath: string) => rmSync(targetPath, { recursive: true, force: true }));
42
+ const removePath =
43
+ options.removeFn ??
44
+ ((targetPath: string) => rmSync(targetPath, { recursive: true, force: true }));
43
45
  const pathExists = options.existsFn ?? ((targetPath: string) => existsSync(targetPath));
44
46
  let lastError = "";
45
47
 
@@ -63,4 +65,3 @@ export async function forceDeleteWorktreePath(
63
65
  ...(lastError ? { lastError } : {}),
64
66
  };
65
67
  }
66
-
@@ -75,7 +75,10 @@ function resolveDockerExecutable(): string {
75
75
  return process.platform === "win32" ? "docker.exe" : "docker";
76
76
  }
77
77
 
78
- function resolveWorkerpalSandboxBuildContext(repoRoot: string): { root: string; dockerfilePath: string } {
78
+ function resolveWorkerpalSandboxBuildContext(repoRoot: string): {
79
+ root: string;
80
+ dockerfilePath: string;
81
+ } {
79
82
  const configuredRoot = String(process.env.PUSHPALS_WORKERPALS_SANDBOX_ROOT ?? "").trim();
80
83
  const sandboxRoot = configuredRoot || repoRoot;
81
84
  const dockerfilePath = configuredRoot
@@ -233,6 +236,7 @@ export class DockerExecutor {
233
236
  private readonly jobRetryMaxAttempts: number;
234
237
  private readonly jobRetryBackoffMs: number;
235
238
  private readonly failureCooldownMs: number;
239
+ private readonly worktreeVisibilityTimeoutMs: number;
236
240
  private lastLoggedExecutionConfig = "";
237
241
  private lastLoggedEndpointRewrite = "";
238
242
  private warmedBackends = new Set<string>();
@@ -291,6 +295,7 @@ export class DockerExecutor {
291
295
  20_000,
292
296
  300_000,
293
297
  );
298
+ this.worktreeVisibilityTimeoutMs = process.platform === "win32" ? 15_000 : 5_000;
294
299
 
295
300
  // Ensure worktrees directory exists
296
301
  try {
@@ -413,7 +418,10 @@ export class DockerExecutor {
413
418
  try {
414
419
  await this.createWorktree(worktreePath, this.options.baseRef);
415
420
  await this.runGitSelfCheckContainer(worktreePath);
416
- console.log(`[DockerExecutor] Startup self-check passed (git/worktree in container).`);
421
+ await this.ensureWorktreeAccessibleInWarmContainer(worktreePath);
422
+ console.log(
423
+ `[DockerExecutor] Startup self-check passed (git/worktree in container and warm container).`,
424
+ );
417
425
  } finally {
418
426
  await this.removeWorktree(worktreePath).catch(() => {
419
427
  // Ignore cleanup failures for startup self-check artifacts.
@@ -446,14 +454,11 @@ export class DockerExecutor {
446
454
  });
447
455
  await prune.exited;
448
456
 
449
- proc = Bun.spawn(
450
- ["git", "worktree", "add", "--force", "--detach", worktreePath, baseRef],
451
- {
452
- cwd: this.options.repo,
453
- stdout: "pipe",
454
- stderr: "pipe",
455
- },
456
- );
457
+ proc = Bun.spawn(["git", "worktree", "add", "--force", "--detach", worktreePath, baseRef], {
458
+ cwd: this.options.repo,
459
+ stdout: "pipe",
460
+ stderr: "pipe",
461
+ });
457
462
  exitCode = await proc.exited;
458
463
  stdout = await new Response(proc.stdout).text();
459
464
  stderr = await new Response(proc.stderr).text();
@@ -713,7 +718,10 @@ export class DockerExecutor {
713
718
 
714
719
  args.push("--entrypoint", "/bin/sh", this.options.imageName, "-lc", startupCmd);
715
720
 
716
- const proc = Bun.spawn([resolveDockerExecutable(), ...args], { stdout: "pipe", stderr: "pipe" });
721
+ const proc = Bun.spawn([resolveDockerExecutable(), ...args], {
722
+ stdout: "pipe",
723
+ stderr: "pipe",
724
+ });
717
725
  const [exitCode, stdout, stderr] = await Promise.all([
718
726
  proc.exited,
719
727
  new Response(proc.stdout).text(),
@@ -814,10 +822,48 @@ export class DockerExecutor {
814
822
  stderr: string;
815
823
  exitCode: number;
816
824
  }> {
817
- const proc = Bun.spawn([resolveDockerExecutable(), "exec", this.warmContainerName, "/bin/sh", "-lc", command], {
818
- stdout: "pipe",
819
- stderr: "pipe",
820
- });
825
+ const proc = Bun.spawn(
826
+ [resolveDockerExecutable(), "exec", this.warmContainerName, "/bin/sh", "-lc", command],
827
+ {
828
+ stdout: "pipe",
829
+ stderr: "pipe",
830
+ },
831
+ );
832
+ const [stdout, stderr, exitCode] = await Promise.all([
833
+ new Response(proc.stdout).text(),
834
+ new Response(proc.stderr).text(),
835
+ proc.exited,
836
+ ]);
837
+ return {
838
+ ok: exitCode === 0,
839
+ stdout: stdout.trim(),
840
+ stderr: stderr.trim(),
841
+ exitCode,
842
+ };
843
+ }
844
+
845
+ private async runWarmWorktreeProbe(containerWorktreePath: string): Promise<{
846
+ ok: boolean;
847
+ stdout: string;
848
+ stderr: string;
849
+ exitCode: number;
850
+ }> {
851
+ const proc = Bun.spawn(
852
+ [
853
+ resolveDockerExecutable(),
854
+ "exec",
855
+ "-w",
856
+ containerWorktreePath,
857
+ this.warmContainerName,
858
+ "/bin/sh",
859
+ "-lc",
860
+ "git rev-parse --is-inside-work-tree && git rev-parse --git-dir",
861
+ ],
862
+ {
863
+ stdout: "pipe",
864
+ stderr: "pipe",
865
+ },
866
+ );
821
867
  const [stdout, stderr, exitCode] = await Promise.all([
822
868
  new Response(proc.stdout).text(),
823
869
  new Response(proc.stderr).text(),
@@ -854,10 +900,13 @@ export class DockerExecutor {
854
900
  }
855
901
 
856
902
  private async readWarmContainerLogs(tail = 160): Promise<string> {
857
- const proc = Bun.spawn([resolveDockerExecutable(), "logs", "--tail", String(tail), this.warmContainerName], {
858
- stdout: "pipe",
859
- stderr: "pipe",
860
- });
903
+ const proc = Bun.spawn(
904
+ [resolveDockerExecutable(), "logs", "--tail", String(tail), this.warmContainerName],
905
+ {
906
+ stdout: "pipe",
907
+ stderr: "pipe",
908
+ },
909
+ );
861
910
  const [stdout, stderr, exitCode] = await Promise.all([
862
911
  new Response(proc.stdout).text(),
863
912
  new Response(proc.stderr).text(),
@@ -1045,10 +1094,10 @@ export class DockerExecutor {
1045
1094
  ): Promise<DockerJobResult> {
1046
1095
  await this.ensureWarmRuntimeReady(job, onLog);
1047
1096
  const startedAtMs = Date.now();
1048
-
1049
- const worktreeRelPath = relative(this.options.repo, worktreePath).replace(/\\/g, "/");
1050
- const containerWorktreePath = `/repo/${worktreeRelPath}`;
1051
- await this.waitForWorktreePathInWarmContainer(containerWorktreePath);
1097
+ const containerWorktreePath = await this.ensureWorktreeAccessibleInWarmContainer(
1098
+ worktreePath,
1099
+ onLog,
1100
+ );
1052
1101
 
1053
1102
  const args: string[] = [
1054
1103
  "exec",
@@ -1145,6 +1194,51 @@ export class DockerExecutor {
1145
1194
  );
1146
1195
  }
1147
1196
 
1197
+ private async ensureWorktreeAccessibleInWarmContainer(
1198
+ worktreePath: string,
1199
+ onLog?: (stream: "stdout" | "stderr", line: string) => void,
1200
+ ): Promise<string> {
1201
+ const worktreeRelPath = relative(this.options.repo, worktreePath).replace(/\\/g, "/");
1202
+ const containerWorktreePath = `/repo/${worktreeRelPath}`;
1203
+ let lastError: unknown = null;
1204
+
1205
+ for (let attempt = 1; attempt <= 2; attempt++) {
1206
+ try {
1207
+ await this.ensureWarmContainer();
1208
+ await this.waitForWorktreePathInWarmContainer(
1209
+ containerWorktreePath,
1210
+ this.worktreeVisibilityTimeoutMs,
1211
+ );
1212
+ const probe = await this.runWarmWorktreeProbe(containerWorktreePath);
1213
+ if (probe.ok) {
1214
+ return containerWorktreePath;
1215
+ }
1216
+ const detail = [probe.stderr, probe.stdout].filter(Boolean).join("\n").trim();
1217
+ throw new Error(
1218
+ `warm container git probe failed (exit ${probe.exitCode})${detail ? `: ${detail}` : ""}`,
1219
+ );
1220
+ } catch (err) {
1221
+ lastError = err;
1222
+ if (attempt >= 2) {
1223
+ const diagnostics = await this.inspectWarmContainerState().catch(() => "");
1224
+ throw new Error(
1225
+ `worktree not accessible inside warm container after ${attempt} attempts: ${containerWorktreePath}${
1226
+ lastError ? ` (${this.compactError(lastError)})` : ""
1227
+ }${diagnostics ? ` | container=${diagnostics}` : ""}`,
1228
+ );
1229
+ }
1230
+ const note =
1231
+ `[DockerExecutor] Warm container could not access worktree ${containerWorktreePath}; ` +
1232
+ `recycling container and retrying once (${this.compactError(err)}).`;
1233
+ console.warn(note);
1234
+ onLog?.("stderr", note);
1235
+ await this.stopWarmContainer("worktree visibility retry", true);
1236
+ }
1237
+ }
1238
+
1239
+ return containerWorktreePath;
1240
+ }
1241
+
1148
1242
  private normalizeProvider(raw: string): string {
1149
1243
  const value = raw.trim().toLowerCase();
1150
1244
  if (!value) return "auto";
@@ -1555,11 +1649,14 @@ export class DockerExecutor {
1555
1649
  `[DockerExecutor] Worktree path already exists; forcing cleanup before create: ${worktreePath}`,
1556
1650
  );
1557
1651
 
1558
- const unregister = Bun.spawn(["git", "worktree", "remove", "--force", "--force", worktreePath], {
1559
- cwd: this.options.repo,
1560
- stdout: "pipe",
1561
- stderr: "pipe",
1562
- });
1652
+ const unregister = Bun.spawn(
1653
+ ["git", "worktree", "remove", "--force", "--force", worktreePath],
1654
+ {
1655
+ cwd: this.options.repo,
1656
+ stdout: "pipe",
1657
+ stderr: "pipe",
1658
+ },
1659
+ );
1563
1660
  await unregister.exited;
1564
1661
 
1565
1662
  const prune = Bun.spawn(["git", "worktree", "prune"], {
@@ -1777,10 +1874,13 @@ export class DockerExecutor {
1777
1874
  * Check if the Docker image exists locally
1778
1875
  */
1779
1876
  private async imageExists(): Promise<boolean> {
1780
- const proc = Bun.spawn([resolveDockerExecutable(), "image", "inspect", this.options.imageName], {
1781
- stdout: "pipe",
1782
- stderr: "pipe",
1783
- });
1877
+ const proc = Bun.spawn(
1878
+ [resolveDockerExecutable(), "image", "inspect", this.options.imageName],
1879
+ {
1880
+ stdout: "pipe",
1881
+ stderr: "pipe",
1882
+ },
1883
+ );
1784
1884
  const exitCode = await proc.exited;
1785
1885
  return exitCode === 0;
1786
1886
  }
@@ -173,7 +173,10 @@ export function buildQualityGateRevisionIssues(
173
173
  if (!critic || critic.score >= qualityCriticMinScore) {
174
174
  return [...normalizedQualityIssues];
175
175
  }
176
- const merged = [...normalizedQualityIssues, ...buildCriticRevisionIssues(critic, qualityCriticMinScore)];
176
+ const merged = [
177
+ ...normalizedQualityIssues,
178
+ ...buildCriticRevisionIssues(critic, qualityCriticMinScore),
179
+ ];
177
180
  return [...new Set(merged)];
178
181
  }
179
182
 
@@ -2569,7 +2572,8 @@ function validateTaskExecutePlanning(
2569
2572
  if (!componentArea) {
2570
2573
  return {
2571
2574
  ok: false,
2572
- message: "task.execute planning.targetPaths must resolve to a repo-relative componentArea",
2575
+ message:
2576
+ "task.execute planning.targetPaths must resolve to a repo-relative componentArea",
2573
2577
  };
2574
2578
  }
2575
2579
  if (
@@ -3056,11 +3060,7 @@ export async function executeJob(
3056
3060
  return result;
3057
3061
  }
3058
3062
 
3059
- const issues = buildQualityGateRevisionIssues(
3060
- quality.issues,
3061
- critic,
3062
- qualityCriticMinScore,
3063
- );
3063
+ const issues = buildQualityGateRevisionIssues(quality.issues, critic, qualityCriticMinScore);
3064
3064
  const issueSummary = issues.map((entry) => toSingleLine(entry, 180)).join(" | ");
3065
3065
  if (revisionAttempt >= qualityMaxAutoRevisions) {
3066
3066
  if (qualitySoftPassOnExhausted) {
@@ -67,10 +67,13 @@ echo "password=${token}"
67
67
  writeFileSync(helperPath, helperScript, { mode: 0o755 });
68
68
 
69
69
  // Remove any legacy URL rewrite rules that may have embedded token credentials.
70
- const urlRules = Bun.spawnSync(["git", "config", "--global", "--get-regexp", "^url\\..*\\.insteadOf$"], {
71
- stdout: "pipe",
72
- stderr: "pipe",
73
- });
70
+ const urlRules = Bun.spawnSync(
71
+ ["git", "config", "--global", "--get-regexp", "^url\\..*\\.insteadOf$"],
72
+ {
73
+ stdout: "pipe",
74
+ stderr: "pipe",
75
+ },
76
+ );
74
77
  if (urlRules.exitCode === 0) {
75
78
  const lines = String(urlRules.stdout ?? "")
76
79
  .split(/\r?\n/)