@ai-hero/sandcastle 0.4.4 → 0.4.6

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 (89) hide show
  1. package/README.md +48 -19
  2. package/dist/AgentProvider.d.ts +8 -2
  3. package/dist/AgentProvider.d.ts.map +1 -1
  4. package/dist/AgentProvider.js +34 -14
  5. package/dist/AgentProvider.js.map +1 -1
  6. package/dist/{CopyToSandbox.d.ts → CopyToWorkspace.d.ts} +2 -2
  7. package/dist/CopyToWorkspace.d.ts.map +1 -0
  8. package/dist/{CopyToSandbox.js → CopyToWorkspace.js} +2 -2
  9. package/dist/CopyToWorkspace.js.map +1 -0
  10. package/dist/ErrorHandler.d.ts.map +1 -1
  11. package/dist/ErrorHandler.js +3 -0
  12. package/dist/ErrorHandler.js.map +1 -1
  13. package/dist/InitService.d.ts +24 -0
  14. package/dist/InitService.d.ts.map +1 -1
  15. package/dist/InitService.js +117 -11
  16. package/dist/InitService.js.map +1 -1
  17. package/dist/Orchestrator.d.ts.map +1 -1
  18. package/dist/Orchestrator.js +4 -1
  19. package/dist/Orchestrator.js.map +1 -1
  20. package/dist/PodmanLifecycle.d.ts +17 -0
  21. package/dist/PodmanLifecycle.d.ts.map +1 -0
  22. package/dist/PodmanLifecycle.js +45 -0
  23. package/dist/PodmanLifecycle.js.map +1 -0
  24. package/dist/PromptArgumentSubstitution.d.ts +5 -0
  25. package/dist/PromptArgumentSubstitution.d.ts.map +1 -1
  26. package/dist/PromptArgumentSubstitution.js +22 -0
  27. package/dist/PromptArgumentSubstitution.js.map +1 -1
  28. package/dist/PromptPreprocessor.d.ts.map +1 -1
  29. package/dist/PromptPreprocessor.js +6 -0
  30. package/dist/PromptPreprocessor.js.map +1 -1
  31. package/dist/SandboxFactory.d.ts +3 -3
  32. package/dist/SandboxFactory.d.ts.map +1 -1
  33. package/dist/SandboxFactory.js +3 -3
  34. package/dist/SandboxFactory.js.map +1 -1
  35. package/dist/SandboxLifecycle.d.ts.map +1 -1
  36. package/dist/SandboxLifecycle.js +7 -7
  37. package/dist/SandboxLifecycle.js.map +1 -1
  38. package/dist/SandboxProvider.d.ts +73 -1
  39. package/dist/SandboxProvider.d.ts.map +1 -1
  40. package/dist/SandboxProvider.js.map +1 -1
  41. package/dist/cli.d.ts +10 -6
  42. package/dist/cli.d.ts.map +1 -1
  43. package/dist/cli.js +113 -113
  44. package/dist/cli.js.map +1 -1
  45. package/dist/createSandbox.d.ts +23 -1
  46. package/dist/createSandbox.d.ts.map +1 -1
  47. package/dist/createSandbox.js +63 -6
  48. package/dist/createSandbox.js.map +1 -1
  49. package/dist/errors.d.ts +9 -1
  50. package/dist/errors.d.ts.map +1 -1
  51. package/dist/errors.js +3 -0
  52. package/dist/errors.js.map +1 -1
  53. package/dist/index.d.ts +5 -3
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +1 -0
  56. package/dist/index.js.map +1 -1
  57. package/dist/interactive.d.ts +52 -0
  58. package/dist/interactive.d.ts.map +1 -0
  59. package/dist/interactive.js +231 -0
  60. package/dist/interactive.js.map +1 -0
  61. package/dist/run.d.ts +1 -1
  62. package/dist/run.d.ts.map +1 -1
  63. package/dist/run.js +5 -5
  64. package/dist/run.js.map +1 -1
  65. package/dist/sandboxes/docker.d.ts.map +1 -1
  66. package/dist/sandboxes/docker.js +26 -1
  67. package/dist/sandboxes/docker.js.map +1 -1
  68. package/dist/sandboxes/no-sandbox.d.ts +25 -0
  69. package/dist/sandboxes/no-sandbox.d.ts.map +1 -0
  70. package/dist/sandboxes/no-sandbox.js +91 -0
  71. package/dist/sandboxes/no-sandbox.js.map +1 -0
  72. package/dist/sandboxes/podman.d.ts +10 -2
  73. package/dist/sandboxes/podman.d.ts.map +1 -1
  74. package/dist/sandboxes/podman.js +79 -11
  75. package/dist/sandboxes/podman.js.map +1 -1
  76. package/dist/templates/blank/prompt.md +1 -1
  77. package/dist/templates/parallel-planner/implement-prompt.md +2 -2
  78. package/dist/templates/parallel-planner/main.mts +12 -12
  79. package/dist/templates/parallel-planner/plan-prompt.md +3 -3
  80. package/dist/templates/parallel-planner-with-review/implement-prompt.md +2 -2
  81. package/dist/templates/parallel-planner-with-review/main.mts +11 -11
  82. package/dist/templates/parallel-planner-with-review/plan-prompt.md +3 -3
  83. package/dist/templates/sequential-reviewer/implement-prompt.md +2 -2
  84. package/dist/templates/sequential-reviewer/main.mts +3 -3
  85. package/dist/templates/simple-loop/main.mts +2 -2
  86. package/dist/templates/simple-loop/prompt.md +2 -2
  87. package/package.json +8 -2
  88. package/dist/CopyToSandbox.d.ts.map +0 -1
  89. package/dist/CopyToSandbox.js.map +0 -1
@@ -0,0 +1,25 @@
1
+ /**
2
+ * No-sandbox provider — runs the agent directly on the host with no container isolation.
3
+ *
4
+ * Usage:
5
+ * import { noSandbox } from "sandcastle/sandboxes/no-sandbox";
6
+ * await interactive({ agent: claudeCode("claude-opus-4-6"), sandbox: noSandbox() });
7
+ *
8
+ * Only valid for `interactive()` — not accepted by `run()` or `createSandbox()`.
9
+ * Does not pass `--dangerously-skip-permissions` to the agent — the user manages
10
+ * permissions themselves.
11
+ */
12
+ import type { NoSandboxProvider } from "../SandboxProvider.js";
13
+ export interface NoSandboxOptions {
14
+ /** Environment variables injected by this provider. Merged at launch time. */
15
+ readonly env?: Record<string, string>;
16
+ }
17
+ /**
18
+ * Create a no-sandbox provider.
19
+ *
20
+ * The returned provider runs the agent directly on the host. All three
21
+ * branch strategies are supported (head, merge-to-head, branch),
22
+ * defaulting to head.
23
+ */
24
+ export declare const noSandbox: (options?: NoSandboxOptions | undefined) => NoSandboxProvider;
25
+ //# sourceMappingURL=no-sandbox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-sandbox.d.ts","sourceRoot":"","sources":["../../src/sandboxes/no-sandbox.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EACV,iBAAiB,EAIlB,MAAM,uBAAuB,CAAC;AAE/B,MAAM,WAAW,gBAAgB;IAC/B,8EAA8E;IAC9E,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACvC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,+DA2FpB,CAAC"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * No-sandbox provider — runs the agent directly on the host with no container isolation.
3
+ *
4
+ * Usage:
5
+ * import { noSandbox } from "sandcastle/sandboxes/no-sandbox";
6
+ * await interactive({ agent: claudeCode("claude-opus-4-6"), sandbox: noSandbox() });
7
+ *
8
+ * Only valid for `interactive()` — not accepted by `run()` or `createSandbox()`.
9
+ * Does not pass `--dangerously-skip-permissions` to the agent — the user manages
10
+ * permissions themselves.
11
+ */
12
+ import { spawn } from "node:child_process";
13
+ import { createInterface } from "node:readline";
14
+ /**
15
+ * Create a no-sandbox provider.
16
+ *
17
+ * The returned provider runs the agent directly on the host. All three
18
+ * branch strategies are supported (head, merge-to-head, branch),
19
+ * defaulting to head.
20
+ */
21
+ export const noSandbox = (options) => ({
22
+ tag: "none",
23
+ name: "no-sandbox",
24
+ env: options?.env ?? {},
25
+ create: async (createOptions) => {
26
+ const workspacePath = createOptions.workspacePath;
27
+ const processEnv = { ...process.env, ...createOptions.env };
28
+ const handle = {
29
+ workspacePath,
30
+ exec: (command, opts) => {
31
+ // sudo is a no-op for no-sandbox — the user is already on the host
32
+ const cwd = opts?.cwd ?? workspacePath;
33
+ return new Promise((resolve, reject) => {
34
+ const proc = spawn("sh", ["-c", command], {
35
+ cwd,
36
+ env: processEnv,
37
+ stdio: ["ignore", "pipe", "pipe"],
38
+ });
39
+ const stdoutChunks = [];
40
+ const stderrChunks = [];
41
+ if (opts?.onLine) {
42
+ const rl = createInterface({ input: proc.stdout });
43
+ rl.on("line", (line) => {
44
+ stdoutChunks.push(line);
45
+ opts.onLine(line);
46
+ });
47
+ }
48
+ else {
49
+ proc.stdout.on("data", (chunk) => {
50
+ stdoutChunks.push(chunk.toString());
51
+ });
52
+ }
53
+ proc.stderr.on("data", (chunk) => {
54
+ stderrChunks.push(chunk.toString());
55
+ });
56
+ proc.on("error", (error) => {
57
+ reject(new Error(`exec failed: ${error.message}`));
58
+ });
59
+ proc.on("close", (code) => {
60
+ resolve({
61
+ stdout: stdoutChunks.join(opts?.onLine ? "\n" : ""),
62
+ stderr: stderrChunks.join(""),
63
+ exitCode: code ?? 0,
64
+ });
65
+ });
66
+ });
67
+ },
68
+ interactiveExec: (args, opts) => {
69
+ return new Promise((resolve, reject) => {
70
+ const [cmd, ...rest] = args;
71
+ const proc = spawn(cmd, rest, {
72
+ cwd: opts.cwd ?? workspacePath,
73
+ env: processEnv,
74
+ stdio: [opts.stdin, opts.stdout, opts.stderr],
75
+ });
76
+ proc.on("error", (error) => {
77
+ reject(new Error(`exec failed: ${error.message}`));
78
+ });
79
+ proc.on("close", (code) => {
80
+ resolve({ exitCode: code ?? 0 });
81
+ });
82
+ });
83
+ },
84
+ close: async () => {
85
+ // No-op — no container to tear down
86
+ },
87
+ };
88
+ return handle;
89
+ },
90
+ });
91
+ //# sourceMappingURL=no-sandbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-sandbox.js","sourceRoot":"","sources":["../../src/sandboxes/no-sandbox.ts"],"names":[],"mappings":"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"}
@@ -18,6 +18,14 @@ export interface PodmanOptions {
18
18
  * - `false` — disable labeling entirely.
19
19
  */
20
20
  readonly selinuxLabel?: "z" | "Z" | false;
21
+ /**
22
+ * User namespace mode for rootless Podman.
23
+ *
24
+ * - `"keep-id"` (default) — maps host UID 1:1 into the container,
25
+ * so bind-mounted files have correct ownership. Required for rootless Podman.
26
+ * - `false` — disable; use for rootful Podman setups.
27
+ */
28
+ readonly userns?: "keep-id" | false;
21
29
  /**
22
30
  * Additional host directories to bind-mount into the sandbox.
23
31
  *
@@ -33,8 +41,8 @@ export interface PodmanOptions {
33
41
  *
34
42
  * The returned provider creates Podman containers with bind-mounts
35
43
  * for the worktree and git directories. Calls the `podman` binary
36
- * on PATH directly no Podman Machine detection or special
37
- * macOS/Windows handling.
44
+ * on PATH directly. On macOS/Windows, verifies that a Podman Machine
45
+ * is running before container creation.
38
46
  */
39
47
  export declare const podman: (options?: PodmanOptions | undefined) => SandboxProvider;
40
48
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"podman.d.ts","sourceRoot":"","sources":["../../src/sandboxes/podman.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,EAEL,KAAK,eAAe,EAIrB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,aAAa;IAC5B,qEAAqE;IACrE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,KAAK,CAAC;IAC1C;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IACzC,uHAAuH;IACvH,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACvC;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM,0DAiLlB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,6BAI5B,CAAC"}
1
+ {"version":3,"file":"podman.d.ts","sourceRoot":"","sources":["../../src/sandboxes/podman.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAUH,OAAO,EAEL,KAAK,eAAe,EAKrB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,aAAa;IAC5B,qEAAqE;IACrE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,KAAK,CAAC;IAC1C;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,GAAG,KAAK,CAAC;IACpC;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IACzC,uHAAuH;IACvH,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACvC;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM,0DA2NlB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,6BAI5B,CAAC"}
@@ -5,7 +5,7 @@
5
5
  * import { podman } from "sandcastle/sandboxes/podman";
6
6
  * await run({ agent: claudeCode("claude-opus-4-6"), sandbox: podman() });
7
7
  */
8
- import { execFile, execFileSync, spawn } from "node:child_process";
8
+ import { execFile, execFileSync, spawn, } from "node:child_process";
9
9
  import { randomUUID } from "node:crypto";
10
10
  import { createInterface } from "node:readline";
11
11
  import { createBindMountSandboxProvider, } from "../SandboxProvider.js";
@@ -16,12 +16,13 @@ import { homedir } from "node:os";
16
16
  *
17
17
  * The returned provider creates Podman containers with bind-mounts
18
18
  * for the worktree and git directories. Calls the `podman` binary
19
- * on PATH directly no Podman Machine detection or special
20
- * macOS/Windows handling.
19
+ * on PATH directly. On macOS/Windows, verifies that a Podman Machine
20
+ * is running before container creation.
21
21
  */
22
22
  export const podman = (options) => {
23
23
  const configuredImageName = options?.imageName;
24
24
  const selinuxLabel = options?.selinuxLabel ?? "z";
25
+ const userns = options?.userns ?? "keep-id";
25
26
  const userMounts = options?.mounts ? resolveUserMounts(options.mounts) : [];
26
27
  return createBindMountSandboxProvider({
27
28
  name: "podman",
@@ -30,16 +31,16 @@ export const podman = (options) => {
30
31
  const containerName = `sandcastle-${randomUUID()}`;
31
32
  const workspacePath = createOptions.mounts.find((m) => m.hostPath === createOptions.worktreePath)?.sandboxPath ?? "/home/agent/workspace";
32
33
  // Build volume mount strings with optional SELinux label (internal + user mounts)
33
- const labelSuffix = selinuxLabel ? `:${selinuxLabel}` : "";
34
34
  const allMounts = [...createOptions.mounts, ...userMounts];
35
- const volumeMounts = allMounts.map((m) => {
36
- const base = `${m.hostPath}:${m.sandboxPath}`;
37
- if (m.readonly)
38
- return `${base}:ro${labelSuffix}`;
39
- return `${base}${labelSuffix}`;
40
- });
35
+ const volumeMounts = allMounts.map((m) => formatVolumeMount(m, selinuxLabel));
41
36
  // Resolve image name
42
37
  const imageName = configuredImageName ?? defaultImageName(createOptions.hostRepoPath);
38
+ // Pre-flight: check Podman Machine on macOS/Windows
39
+ if (process.platform === "darwin" || process.platform === "win32") {
40
+ await checkPodmanMachine();
41
+ }
42
+ // Pre-flight: verify image exists locally
43
+ await checkImageExists(imageName);
43
44
  const hostUid = process.getuid?.() ?? 1000;
44
45
  const hostGid = process.getgid?.() ?? 1000;
45
46
  const env = { ...createOptions.env, HOME: "/home/agent" };
@@ -48,6 +49,7 @@ export const podman = (options) => {
48
49
  `${key}=${value}`,
49
50
  ]);
50
51
  const volumeArgs = volumeMounts.flatMap((v) => ["-v", v]);
52
+ const usernsArgs = userns ? [`--userns=${userns}`] : [];
51
53
  // Start container via podman run
52
54
  await new Promise((resolve, reject) => {
53
55
  execFile("podman", [
@@ -57,12 +59,14 @@ export const podman = (options) => {
57
59
  containerName,
58
60
  "--user",
59
61
  `${hostUid}:${hostGid}`,
62
+ ...usernsArgs,
60
63
  "-w",
61
64
  workspacePath,
62
65
  ...envArgs,
63
66
  ...volumeArgs,
64
- imageName,
67
+ "--entrypoint",
65
68
  "sleep",
69
+ imageName,
66
70
  "infinity",
67
71
  ], (error) => {
68
72
  if (error) {
@@ -78,6 +82,7 @@ export const podman = (options) => {
78
82
  try {
79
83
  execFileSync("podman", ["rm", "-f", containerName], {
80
84
  stdio: "ignore",
85
+ timeout: 5000,
81
86
  });
82
87
  }
83
88
  catch {
@@ -142,6 +147,31 @@ export const podman = (options) => {
142
147
  });
143
148
  });
144
149
  },
150
+ interactiveExec: (args, opts) => {
151
+ return new Promise((resolve, reject) => {
152
+ const podmanArgs = ["exec"];
153
+ // Allocate a pseudo-terminal when stdin looks like a TTY
154
+ if ("isTTY" in opts.stdin &&
155
+ opts.stdin.isTTY) {
156
+ podmanArgs.push("-it");
157
+ }
158
+ else {
159
+ podmanArgs.push("-i");
160
+ }
161
+ if (opts.cwd)
162
+ podmanArgs.push("-w", opts.cwd);
163
+ podmanArgs.push(containerName, ...args);
164
+ const proc = spawn("podman", podmanArgs, {
165
+ stdio: [opts.stdin, opts.stdout, opts.stderr],
166
+ });
167
+ proc.on("error", (error) => {
168
+ reject(new Error(`podman exec failed: ${error.message}`));
169
+ });
170
+ proc.on("close", (code) => {
171
+ resolve({ exitCode: code ?? 0 });
172
+ });
173
+ });
174
+ },
145
175
  close: async () => {
146
176
  process.removeListener("exit", onExit);
147
177
  process.removeListener("SIGINT", onSignal);
@@ -193,4 +223,42 @@ const resolveUserMounts = (mounts) => mounts.map((m) => {
193
223
  ...(m.readonly ? { readonly: true } : {}),
194
224
  };
195
225
  });
226
+ const checkImageExists = (imageName) => new Promise((resolve, reject) => {
227
+ execFile("podman", ["image", "inspect", imageName], (error) => {
228
+ if (error) {
229
+ reject(new Error(`Image '${imageName}' not found locally. Build it first with 'podman build -t ${imageName} .'`));
230
+ }
231
+ else {
232
+ resolve();
233
+ }
234
+ });
235
+ });
236
+ const podmanMachineError = () => new Error("Podman Machine is not running. Run 'podman machine init && podman machine start' first.");
237
+ const checkPodmanMachine = () => new Promise((resolve, reject) => {
238
+ execFile("podman", ["machine", "list", "--format", "json"], (error, stdout) => {
239
+ if (error) {
240
+ reject(podmanMachineError());
241
+ return;
242
+ }
243
+ try {
244
+ const machines = JSON.parse(stdout.toString());
245
+ if (machines.some((m) => m.Running)) {
246
+ resolve();
247
+ }
248
+ else {
249
+ reject(podmanMachineError());
250
+ }
251
+ }
252
+ catch {
253
+ reject(podmanMachineError());
254
+ }
255
+ });
256
+ });
257
+ const formatVolumeMount = (mount, selinuxLabel) => {
258
+ const base = `${mount.hostPath}:${mount.sandboxPath}`;
259
+ const options = [mount.readonly ? "ro" : undefined, selinuxLabel || undefined]
260
+ .filter((option) => option !== undefined)
261
+ .join(",");
262
+ return options ? `${base}:${options}` : base;
263
+ };
196
264
  //# sourceMappingURL=podman.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"podman.js","sourceRoot":"","sources":["../../src/sandboxes/podman.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EACL,8BAA8B,GAK/B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAyBlC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,OAAuB,EAAmB,EAAE;IACjE,MAAM,mBAAmB,GAAG,OAAO,EAAE,SAAS,CAAC;IAC/C,MAAM,YAAY,GAAG,OAAO,EAAE,YAAY,IAAI,GAAG,CAAC;IAClD,MAAM,UAAU,GAAG,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,iBAAiB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAE5E,OAAO,8BAA8B,CAAC;QACpC,IAAI,EAAE,QAAQ;QACd,GAAG,EAAE,OAAO,EAAE,GAAG;QACjB,MAAM,EAAE,KAAK,EACX,aAAqC,EACJ,EAAE;YACnC,MAAM,aAAa,GAAG,cAAc,UAAU,EAAE,EAAE,CAAC;YAEnD,MAAM,aAAa,GACjB,aAAa,CAAC,MAAM,CAAC,IAAI,CACvB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,aAAa,CAAC,YAAY,CACjD,EAAE,WAAW,IAAI,uBAAuB,CAAC;YAE5C,kFAAkF;YAClF,MAAM,WAAW,GAAG,YAAY,CAAC,CAAC,CAAC,IAAI,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3D,MAAM,SAAS,GAAG,CAAC,GAAG,aAAa,CAAC,MAAM,EAAE,GAAG,UAAU,CAAC,CAAC;YAC3D,MAAM,YAAY,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;gBACvC,MAAM,IAAI,GAAG,GAAG,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;gBAC9C,IAAI,CAAC,CAAC,QAAQ;oBAAE,OAAO,GAAG,IAAI,MAAM,WAAW,EAAE,CAAC;gBAClD,OAAO,GAAG,IAAI,GAAG,WAAW,EAAE,CAAC;YACjC,CAAC,CAAC,CAAC;YAEH,qBAAqB;YACrB,MAAM,SAAS,GACb,mBAAmB,IAAI,gBAAgB,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC;YAEtE,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,IAAI,IAAI,CAAC;YAC3C,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,IAAI,IAAI,CAAC;YAE3C,MAAM,GAAG,GAAG,EAAE,GAAG,aAAa,CAAC,GAAG,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;YAC1D,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;gBAC5D,IAAI;gBACJ,GAAG,GAAG,IAAI,KAAK,EAAE;aAClB,CAAC,CAAC;YACH,MAAM,UAAU,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;YAE1D,iCAAiC;YACjC,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBAC1C,QAAQ,CACN,QAAQ,EACR;oBACE,KAAK;oBACL,IAAI;oBACJ,QAAQ;oBACR,aAAa;oBACb,QAAQ;oBACR,GAAG,OAAO,IAAI,OAAO,EAAE;oBACvB,IAAI;oBACJ,aAAa;oBACb,GAAG,OAAO;oBACV,GAAG,UAAU;oBACb,SAAS;oBACT,OAAO;oBACP,UAAU;iBACX,EACD,CAAC,KAAK,EAAE,EAAE;oBACR,IAAI,KAAK,EAAE,CAAC;wBACV,MAAM,CAAC,IAAI,KAAK,CAAC,sBAAsB,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;oBAC3D,CAAC;yBAAM,CAAC;wBACN,OAAO,EAAE,CAAC;oBACZ,CAAC;gBACH,CAAC,CACF,CAAC;YACJ,CAAC,CAAC,CAAC;YAEH,qCAAqC;YACrC,MAAM,MAAM,GAAG,GAAG,EAAE;gBAClB,IAAI,CAAC;oBACH,YAAY,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,aAAa,CAAC,EAAE;wBAClD,KAAK,EAAE,QAAQ;qBAChB,CAAC,CAAC;gBACL,CAAC;gBAAC,MAAM,CAAC;oBACP,iBAAiB;gBACnB,CAAC;YACH,CAAC,CAAC;YACF,MAAM,QAAQ,GAAG,GAAG,EAAE;gBACpB,MAAM,EAAE,CAAC;gBACT,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC,CAAC;YACF,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YAC3B,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;YAEhC,MAAM,MAAM,GAA2B;gBACrC,aAAa;gBAEb,IAAI,EAAE,CACJ,OAAe,EACf,IAIC,EACoB,EAAE;oBACvB,MAAM,gBAAgB,GAAG,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,QAAQ,OAAO,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;oBAClE,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;oBACtB,IAAI,IAAI,EAAE,GAAG;wBAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;oBACzC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,EAAE,IAAI,EAAE,gBAAgB,CAAC,CAAC;oBAEvD,IAAI,IAAI,EAAE,MAAM,EAAE,CAAC;wBACjB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;wBAC3B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;4BACrC,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE;gCACjC,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;6BAClC,CAAC,CAAC;4BAEH,MAAM,YAAY,GAAa,EAAE,CAAC;4BAClC,MAAM,YAAY,GAAa,EAAE,CAAC;4BAElC,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,MAAO,EAAE,CAAC,CAAC;4BACpD,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;gCACrB,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gCACxB,MAAM,CAAC,IAAI,CAAC,CAAC;4BACf,CAAC,CAAC,CAAC;4BAEH,IAAI,CAAC,MAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;gCACxC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;4BACtC,CAAC,CAAC,CAAC;4BAEH,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;gCACzB,MAAM,CAAC,IAAI,KAAK,CAAC,uBAAuB,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;4BAC5D,CAAC,CAAC,CAAC;4BAEH,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;gCACxB,OAAO,CAAC;oCACN,MAAM,EAAE,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;oCAC/B,MAAM,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;oCAC7B,QAAQ,EAAE,IAAI,IAAI,CAAC;iCACpB,CAAC,CAAC;4BACL,CAAC,CAAC,CAAC;wBACL,CAAC,CAAC,CAAC;oBACL,CAAC;oBAED,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;wBACrC,QAAQ,CACN,QAAQ,EACR,IAAI,EACJ,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,EAC/B,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;4BACxB,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gCACtC,MAAM,CAAC,IAAI,KAAK,CAAC,uBAAuB,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;4BAC5D,CAAC;iCAAM,CAAC;gCACN,OAAO,CAAC;oCACN,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE;oCACzB,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE;oCACzB,QAAQ,EAAE,OAAO,KAAK,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;iCAC3D,CAAC,CAAC;4BACL,CAAC;wBACH,CAAC,CACF,CAAC;oBACJ,CAAC,CAAC,CAAC;gBACL,CAAC;gBAED,KAAK,EAAE,KAAK,IAAmB,EAAE;oBAC/B,OAAO,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;oBACvC,OAAO,CAAC,cAAc,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;oBAC3C,OAAO,CAAC,cAAc,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;oBAC5C,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;wBAC1C,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,EAAE;4BACxD,IAAI,KAAK,EAAE,CAAC;gCACV,MAAM,CAAC,IAAI,KAAK,CAAC,qBAAqB,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;4BAC1D,CAAC;iCAAM,CAAC;gCACN,OAAO,EAAE,CAAC;4BACZ,CAAC;wBACH,CAAC,CAAC,CAAC;oBACL,CAAC,CAAC,CAAC;gBACL,CAAC;aACF,CAAC;YAEF,OAAO,MAAM,CAAC;QAChB,CAAC;KACF,CAAC,CAAC;AACL,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,OAAe,EAAU,EAAE;IAC1D,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,OAAO,CAAC;IACxE,MAAM,SAAS,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,eAAe,EAAE,GAAG,CAAC,CAAC;IACtE,OAAO,cAAc,SAAS,EAAE,CAAC;AACnC,CAAC,CAAC;AAEF,MAAM,WAAW,GAAG,CAAC,CAAS,EAAU,EAAE;IACxC,IAAI,CAAC,KAAK,GAAG;QAAE,OAAO,OAAO,EAAE,CAAC;IAChC,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,OAAO,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACtD,OAAO,CAAC,CAAC;AACX,CAAC,CAAC;AAEF,MAAM,iBAAiB,GAAG,CACxB,MAA8B,EACwC,EAAE,CACxE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;IACf,MAAM,gBAAgB,GAAG,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;IAEjD,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CACb,kCAAkC,CAAC,CAAC,QAAQ,EAAE;YAC5C,CAAC,CAAC,CAAC,QAAQ,KAAK,gBAAgB;gBAC9B,CAAC,CAAC,iBAAiB,gBAAgB,GAAG;gBACtC,CAAC,CAAC,EAAE,CAAC,CACV,CAAC;IACJ,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,gBAAgB;QAC1B,WAAW,EAAE,CAAC,CAAC,WAAW;QAC1B,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC1C,CAAC;AACJ,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"podman.js","sourceRoot":"","sources":["../../src/sandboxes/podman.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  # Context
2
2
 
3
3
  <!-- Use !`command` to pull in dynamic context. Commands run inside the sandbox. -->
4
- <!-- Example: !`git log --oneline -10` or !`gh issue list --label Sandcastle --json number,title` -->
4
+ <!-- Example: !`git log --oneline -10` or !`{{LIST_TASKS_COMMAND}}` -->
5
5
 
6
6
  # Task
7
7
 
@@ -1,8 +1,8 @@
1
1
  # TASK
2
2
 
3
- Fix issue #{{ISSUE_NUMBER}}: {{ISSUE_TITLE}}
3
+ Fix issue {{TASK_ID}}: {{ISSUE_TITLE}}
4
4
 
5
- Pull in the issue using `gh issue view`. If it has a parent PRD, pull that in too.
5
+ Pull in the issue using `{{VIEW_TASK_COMMAND}}`. If it has a parent PRD, pull that in too.
6
6
 
7
7
  Only work on the issue specified.
8
8
 
@@ -36,7 +36,7 @@ const hooks = {
36
36
  // Copy node_modules from the host into the worktree before each sandbox
37
37
  // starts. Avoids a full npm install from scratch; the hook above handles
38
38
  // platform-specific binaries and any packages added since the last copy.
39
- const copyToSandbox = ["node_modules"];
39
+ const copyToWorkspace = ["node_modules"];
40
40
 
41
41
  // ---------------------------------------------------------------------------
42
42
  // Main loop
@@ -56,7 +56,7 @@ for (let iteration = 1; iteration <= MAX_ITERATIONS; iteration++) {
56
56
  // -------------------------------------------------------------------------
57
57
  const plan = await sandcastle.run({
58
58
  hooks,
59
- copyToSandbox,
59
+ copyToWorkspace,
60
60
  sandbox: docker(),
61
61
  branchStrategy: { type: "merge-to-head" },
62
62
  name: "planner",
@@ -76,9 +76,9 @@ for (let iteration = 1; iteration <= MAX_ITERATIONS; iteration++) {
76
76
  );
77
77
  }
78
78
 
79
- // The plan JSON contains an array of issues, each with number, title, branch.
79
+ // The plan JSON contains an array of issues, each with id, title, branch.
80
80
  const { issues } = JSON.parse(planMatch[1]!) as {
81
- issues: { number: number; title: string; branch: string }[];
81
+ issues: { id: string; title: string; branch: string }[];
82
82
  };
83
83
 
84
84
  if (issues.length === 0) {
@@ -91,7 +91,7 @@ for (let iteration = 1; iteration <= MAX_ITERATIONS; iteration++) {
91
91
  `Planning complete. ${issues.length} issue(s) to work in parallel:`,
92
92
  );
93
93
  for (const issue of issues) {
94
- console.log(` #${issue.number}: ${issue.title} → ${issue.branch}`);
94
+ console.log(` ${issue.id}: ${issue.title} → ${issue.branch}`);
95
95
  }
96
96
 
97
97
  // -------------------------------------------------------------------------
@@ -107,7 +107,7 @@ for (let iteration = 1; iteration <= MAX_ITERATIONS; iteration++) {
107
107
  issues.map((issue) =>
108
108
  sandcastle.run({
109
109
  hooks,
110
- copyToSandbox,
110
+ copyToWorkspace,
111
111
  // Each agent starts on its own branch via branchStrategy on run().
112
112
  sandbox: docker(),
113
113
  branchStrategy: { type: "branch", branch: issue.branch },
@@ -117,11 +117,11 @@ for (let iteration = 1; iteration <= MAX_ITERATIONS; iteration++) {
117
117
  // Sonnet for execution: fast and capable enough for typical issue work.
118
118
  agent: sandcastle.claudeCode("claude-sonnet-4-6"),
119
119
  promptFile: "./.sandcastle/implement-prompt.md",
120
- // Prompt arguments substitute {{ISSUE_NUMBER}}, {{ISSUE_TITLE}},
120
+ // Prompt arguments substitute {{TASK_ID}}, {{ISSUE_TITLE}},
121
121
  // and {{BRANCH}} placeholders in implement-prompt.md before the
122
122
  // agent sees the prompt.
123
123
  promptArgs: {
124
- ISSUE_NUMBER: String(issue.number),
124
+ TASK_ID: issue.id,
125
125
  ISSUE_TITLE: issue.title,
126
126
  BRANCH: issue.branch,
127
127
  },
@@ -133,7 +133,7 @@ for (let iteration = 1; iteration <= MAX_ITERATIONS; iteration++) {
133
133
  for (const [i, outcome] of settled.entries()) {
134
134
  if (outcome.status === "rejected") {
135
135
  console.error(
136
- ` ✗ #${issues[i]!.number} (${issues[i]!.branch}) failed: ${outcome.reason}`,
136
+ ` ✗ ${issues[i]!.id} (${issues[i]!.branch}) failed: ${outcome.reason}`,
137
137
  );
138
138
  }
139
139
  }
@@ -182,7 +182,7 @@ for (let iteration = 1; iteration <= MAX_ITERATIONS; iteration++) {
182
182
  // -------------------------------------------------------------------------
183
183
  await sandcastle.run({
184
184
  hooks,
185
- copyToSandbox,
185
+ copyToWorkspace,
186
186
  sandbox: docker(),
187
187
  branchStrategy: { type: "merge-to-head" },
188
188
  name: "merger",
@@ -193,9 +193,9 @@ for (let iteration = 1; iteration <= MAX_ITERATIONS; iteration++) {
193
193
  promptArgs: {
194
194
  // A markdown list of branch names, one per line.
195
195
  BRANCHES: completedBranches.map((b) => `- ${b}`).join("\n"),
196
- // A markdown list of issue numbers and titles, one per line.
196
+ // A markdown list of issue IDs and titles, one per line.
197
197
  ISSUES: completedIssues
198
- .map((i) => `- #${i.number}: ${i.title}`)
198
+ .map((i) => `- ${i.id}: ${i.title}`)
199
199
  .join("\n"),
200
200
  },
201
201
  });
@@ -4,7 +4,7 @@ Here are the open issues in the repo:
4
4
 
5
5
  <issues-json>
6
6
 
7
- !`gh issue list --state open --label Sandcastle --json number,title,body,labels,comments --jq '[.[] | {number, title, body, labels: [.labels[].name], comments: [.comments[].body]}]'`
7
+ !`{{LIST_TASKS_COMMAND}}`
8
8
 
9
9
  </issues-json>
10
10
 
@@ -20,14 +20,14 @@ An issue B is **blocked by** issue A if:
20
20
 
21
21
  An issue is **unblocked** if it has zero blocking dependencies on other open issues.
22
22
 
23
- For each unblocked issue, assign a branch name using the format `sandcastle/issue-{number}-{slug}`.
23
+ For each unblocked issue, assign a branch name using the format `sandcastle/issue-{id}-{slug}`.
24
24
 
25
25
  # OUTPUT
26
26
 
27
27
  Output your plan as a JSON object wrapped in `<plan>` tags:
28
28
 
29
29
  <plan>
30
- {"issues": [{"number": 42, "title": "Fix auth bug", "branch": "sandcastle/issue-42-fix-auth-bug"}]}
30
+ {"issues": [{"id": "42", "title": "Fix auth bug", "branch": "sandcastle/issue-42-fix-auth-bug"}]}
31
31
  </plan>
32
32
 
33
33
  Include only unblocked issues. If every issue is blocked, include the single highest-priority candidate (the one with the fewest or weakest dependencies).
@@ -1,8 +1,8 @@
1
1
  # TASK
2
2
 
3
- Fix issue #{{ISSUE_NUMBER}}: {{ISSUE_TITLE}}
3
+ Fix issue {{TASK_ID}}: {{ISSUE_TITLE}}
4
4
 
5
- Pull in the issue using `gh issue view`. If it has a parent PRD, pull that in too.
5
+ Pull in the issue using `{{VIEW_TASK_COMMAND}}`. If it has a parent PRD, pull that in too.
6
6
 
7
7
  Only work on the issue specified.
8
8
 
@@ -41,7 +41,7 @@ const hooks = {
41
41
  // Copy node_modules from the host into the worktree before each sandbox
42
42
  // starts. Avoids a full npm install from scratch; the hook above handles
43
43
  // platform-specific binaries and any packages added since the last copy.
44
- const copyToSandbox = ["node_modules"];
44
+ const copyToWorkspace = ["node_modules"];
45
45
 
46
46
  // Cap the number of concurrent sandboxes to avoid resource exhaustion.
47
47
  const MAX_CONCURRENCY = 4;
@@ -64,7 +64,7 @@ for (let iteration = 1; iteration <= MAX_ITERATIONS; iteration++) {
64
64
  // -------------------------------------------------------------------------
65
65
  const plan = await sandcastle.run({
66
66
  hooks,
67
- copyToSandbox,
67
+ copyToWorkspace,
68
68
  sandbox: docker(),
69
69
  branchStrategy: { type: "merge-to-head" },
70
70
  name: "planner",
@@ -84,9 +84,9 @@ for (let iteration = 1; iteration <= MAX_ITERATIONS; iteration++) {
84
84
  );
85
85
  }
86
86
 
87
- // The plan JSON contains an array of issues, each with number, title, branch.
87
+ // The plan JSON contains an array of issues, each with id, title, branch.
88
88
  const { issues } = JSON.parse(planMatch[1]!) as {
89
- issues: { number: number; title: string; branch: string }[];
89
+ issues: { id: string; title: string; branch: string }[];
90
90
  };
91
91
 
92
92
  if (issues.length === 0) {
@@ -99,7 +99,7 @@ for (let iteration = 1; iteration <= MAX_ITERATIONS; iteration++) {
99
99
  `Planning complete. ${issues.length} issue(s) to work in parallel:`,
100
100
  );
101
101
  for (const issue of issues) {
102
- console.log(` #${issue.number}: ${issue.title} → ${issue.branch}`);
102
+ console.log(` ${issue.id}: ${issue.title} → ${issue.branch}`);
103
103
  }
104
104
 
105
105
  // -------------------------------------------------------------------------
@@ -142,7 +142,7 @@ for (let iteration = 1; iteration <= MAX_ITERATIONS; iteration++) {
142
142
  branch: issue.branch,
143
143
  sandbox: docker(),
144
144
  hooks,
145
- copyToSandbox,
145
+ copyToWorkspace,
146
146
  });
147
147
 
148
148
  try {
@@ -153,7 +153,7 @@ for (let iteration = 1; iteration <= MAX_ITERATIONS; iteration++) {
153
153
  agent: sandcastle.claudeCode("claude-sonnet-4-6"),
154
154
  promptFile: "./.sandcastle/implement-prompt.md",
155
155
  promptArgs: {
156
- ISSUE_NUMBER: String(issue.number),
156
+ TASK_ID: issue.id,
157
157
  ISSUE_TITLE: issue.title,
158
158
  BRANCH: issue.branch,
159
159
  },
@@ -186,7 +186,7 @@ for (let iteration = 1; iteration <= MAX_ITERATIONS; iteration++) {
186
186
  for (const [i, outcome] of settled.entries()) {
187
187
  if (outcome.status === "rejected") {
188
188
  console.error(
189
- ` ✗ #${issues[i]!.number} (${issues[i]!.branch}) failed: ${outcome.reason}`,
189
+ ` ✗ ${issues[i]!.id} (${issues[i]!.branch}) failed: ${outcome.reason}`,
190
190
  );
191
191
  }
192
192
  }
@@ -228,7 +228,7 @@ for (let iteration = 1; iteration <= MAX_ITERATIONS; iteration++) {
228
228
  // -------------------------------------------------------------------------
229
229
  await sandcastle.run({
230
230
  hooks,
231
- copyToSandbox,
231
+ copyToWorkspace,
232
232
  sandbox: docker(),
233
233
  branchStrategy: { type: "merge-to-head" },
234
234
  name: "merger",
@@ -238,9 +238,9 @@ for (let iteration = 1; iteration <= MAX_ITERATIONS; iteration++) {
238
238
  promptArgs: {
239
239
  // A markdown list of branch names, one per line.
240
240
  BRANCHES: completedBranches.map((b) => `- ${b}`).join("\n"),
241
- // A markdown list of issue numbers and titles, one per line.
241
+ // A markdown list of issue IDs and titles, one per line.
242
242
  ISSUES: completedIssues
243
- .map((i) => `- #${i.number}: ${i.title}`)
243
+ .map((i) => `- ${i.id}: ${i.title}`)
244
244
  .join("\n"),
245
245
  },
246
246
  });
@@ -4,7 +4,7 @@ Here are the open issues in the repo:
4
4
 
5
5
  <issues-json>
6
6
 
7
- !`gh issue list --state open --label Sandcastle --json number,title,body,labels,comments --jq '[.[] | {number, title, body, labels: [.labels[].name], comments: [.comments[].body]}]'`
7
+ !`{{LIST_TASKS_COMMAND}}`
8
8
 
9
9
  </issues-json>
10
10
 
@@ -20,14 +20,14 @@ An issue B is **blocked by** issue A if:
20
20
 
21
21
  An issue is **unblocked** if it has zero blocking dependencies on other open issues.
22
22
 
23
- For each unblocked issue, assign a branch name using the format `sandcastle/issue-{number}-{slug}`.
23
+ For each unblocked issue, assign a branch name using the format `sandcastle/issue-{id}-{slug}`.
24
24
 
25
25
  # OUTPUT
26
26
 
27
27
  Output your plan as a JSON object wrapped in `<plan>` tags:
28
28
 
29
29
  <plan>
30
- {"issues": [{"number": 42, "title": "Fix auth bug", "branch": "sandcastle/issue-42-fix-auth-bug"}]}
30
+ {"issues": [{"id": "42", "title": "Fix auth bug", "branch": "sandcastle/issue-42-fix-auth-bug"}]}
31
31
  </plan>
32
32
 
33
33
  Include only unblocked issues. If every issue is blocked, include the single highest-priority candidate (the one with the fewest or weakest dependencies).
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Open issues
4
4
 
5
- !`gh issue list --label Sandcastle --json number,title,body --limit 20`
5
+ !`{{LIST_TASKS_COMMAND}}`
6
6
 
7
7
  ## Recent RALPH commits (last 10)
8
8
 
@@ -35,7 +35,7 @@ Pick the highest-priority open issue that is not blocked by another open issue.
35
35
  - List key decisions made
36
36
  - List files changed
37
37
  - Note any blockers for the next iteration
38
- 6. **Close** — close the issue with `gh issue close <number> --comment "..."` explaining what was done.
38
+ 6. **Close** — close the issue with `{{CLOSE_TASK_COMMAND}}` explaining what was done.
39
39
 
40
40
  ## Rules
41
41