@snappedly-tools/shipyard 0.11.1 → 0.11.2

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.
package/dist/main.js CHANGED
@@ -2400,7 +2400,7 @@ var runnerCommand = Command.make(
2400
2400
  purgeRunnerCommand
2401
2401
  ])
2402
2402
  );
2403
- var VERSION = "0.11.1" ;
2403
+ var VERSION = "0.11.2" ;
2404
2404
  var SHIPYARD_PACKAGE_NAME = "@snappedly-tools/shipyard";
2405
2405
  var isMissingPath = (error) => error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT";
2406
2406
  var lstatIfPresent = async (path) => {
@@ -96,7 +96,7 @@ const closeClean = async (sandbox: {
96
96
  if (preservedWorktreePath)
97
97
  throw new Error(`Sandbox has uncommitted work at ${preservedWorktreePath}`);
98
98
  };
99
- const verifyTriage = (ticketId: string) => {
99
+ const verifyTriage = (ticketId: string, triageOutput?: string) => {
100
100
  try {
101
101
  execFileSync("bash", [".shipyard/verify-triage.sh", ticketId, repository], {
102
102
  encoding: "utf8",
@@ -105,9 +105,31 @@ const verifyTriage = (ticketId: string) => {
105
105
  const detail = (error as { stderr?: string | Buffer }).stderr
106
106
  ?.toString()
107
107
  .trim();
108
- throw new Error(detail || `Could not verify triage for #${ticketId}`);
108
+ const reason = detail || `Could not verify triage for #${ticketId}`;
109
+ const output = triageOutput?.trim();
110
+ throw new Error(
111
+ output ? `${reason}; triage output: ${output.slice(-1200)}` : reason,
112
+ );
109
113
  }
110
114
  };
115
+ const verifyTriageAccess = async (
116
+ sandbox: {
117
+ exec: (
118
+ command: string,
119
+ ) => Promise<{ exitCode: number; stdout: string; stderr: string }>;
120
+ },
121
+ ticketId: string,
122
+ ) => {
123
+ if (!/^\d+$/.test(ticketId))
124
+ throw new Error(`Invalid issue number: ${ticketId}`);
125
+ const result = await sandbox.exec(
126
+ `gh issue view ${ticketId} --repo ${repository} --json number --jq .number`,
127
+ );
128
+ if (result.exitCode !== 0)
129
+ throw new Error(
130
+ `Cannot triage issue #${ticketId}: GitHub access failed in the sandbox: ${result.stderr.trim() || result.stdout.trim() || `exit ${result.exitCode}`}`,
131
+ );
132
+ };
111
133
  const planSchema = z.object({ issues: z.array(z.object({ id: z.string() })) });
112
134
  type Ticket = {
113
135
  id: string;
@@ -174,15 +196,19 @@ const runWorker = async (scope: Scope, ticket: Ticket) => {
174
196
  copyToWorktree: [".shipyard/setup.sh"],
175
197
  });
176
198
  try {
177
- if (!ticket.triageReady)
178
- await sandbox.run({
199
+ let triageOutput: string | undefined;
200
+ if (!ticket.triageReady) {
201
+ await verifyTriageAccess(sandbox, ticket.id);
202
+ const triage = await sandbox.run({
179
203
  name: `triage #${ticket.id}`,
180
204
  maxIterations: 1,
181
205
  agent: roleAgent("routine", shipyard.CODEX_MODELS.routine),
182
206
  promptFile: "./.shipyard/triage-prompt.md",
183
207
  promptArgs: { TASK_ID: ticket.id, BASE_BRANCH: targetBranch },
184
208
  });
185
- verifyTriage(ticket.id);
209
+ triageOutput = triage.stdout;
210
+ }
211
+ verifyTriage(ticket.id, triageOutput);
186
212
  const implementation = await sandbox.run({
187
213
  name: "implementer",
188
214
  maxIterations: 100,
@@ -447,15 +473,19 @@ for (let iteration = 0; iteration < 10; iteration++) {
447
473
  let handoffEvidence: string;
448
474
  try {
449
475
  if (scope.kind === "standalone") {
450
- if (!scope.triageReady)
451
- await integration.run({
476
+ let triageOutput: string | undefined;
477
+ if (!scope.triageReady) {
478
+ await verifyTriageAccess(integration, id);
479
+ const triage = await integration.run({
452
480
  name: `triage #${id}`,
453
481
  maxIterations: 1,
454
482
  agent: roleAgent("routine", shipyard.CODEX_MODELS.routine),
455
483
  promptFile: "./.shipyard/triage-prompt.md",
456
484
  promptArgs: { TASK_ID: id, BASE_BRANCH: targetBranch },
457
485
  });
458
- verifyTriage(id);
486
+ triageOutput = triage.stdout;
487
+ }
488
+ verifyTriage(id, triageOutput);
459
489
  const standalone = await integration.run({
460
490
  name: "implementer",
461
491
  maxIterations: 100,
@@ -96,7 +96,7 @@ const closeClean = async (sandbox: {
96
96
  if (preservedWorktreePath)
97
97
  throw new Error(`Sandbox has uncommitted work at ${preservedWorktreePath}`);
98
98
  };
99
- const verifyTriage = (ticketId: string) => {
99
+ const verifyTriage = (ticketId: string, triageOutput?: string) => {
100
100
  try {
101
101
  execFileSync("bash", [".shipyard/verify-triage.sh", ticketId, repository], {
102
102
  encoding: "utf8",
@@ -105,9 +105,31 @@ const verifyTriage = (ticketId: string) => {
105
105
  const detail = (error as { stderr?: string | Buffer }).stderr
106
106
  ?.toString()
107
107
  .trim();
108
- throw new Error(detail || `Could not verify triage for #${ticketId}`);
108
+ const reason = detail || `Could not verify triage for #${ticketId}`;
109
+ const output = triageOutput?.trim();
110
+ throw new Error(
111
+ output ? `${reason}; triage output: ${output.slice(-1200)}` : reason,
112
+ );
109
113
  }
110
114
  };
115
+ const verifyTriageAccess = async (
116
+ sandbox: {
117
+ exec: (
118
+ command: string,
119
+ ) => Promise<{ exitCode: number; stdout: string; stderr: string }>;
120
+ },
121
+ ticketId: string,
122
+ ) => {
123
+ if (!/^\d+$/.test(ticketId))
124
+ throw new Error(`Invalid issue number: ${ticketId}`);
125
+ const result = await sandbox.exec(
126
+ `gh issue view ${ticketId} --repo ${repository} --json number --jq .number`,
127
+ );
128
+ if (result.exitCode !== 0)
129
+ throw new Error(
130
+ `Cannot triage issue #${ticketId}: GitHub access failed in the sandbox: ${result.stderr.trim() || result.stdout.trim() || `exit ${result.exitCode}`}`,
131
+ );
132
+ };
111
133
  const planSchema = z.object({ issues: z.array(z.object({ id: z.string() })) });
112
134
  type Ticket = {
113
135
  id: string;
@@ -184,15 +206,19 @@ const runWorker = async (scope: Scope, ticket: Ticket) => {
184
206
  copyToWorktree: [".shipyard/setup.sh"],
185
207
  });
186
208
  try {
187
- if (!ticket.triageReady)
188
- await sandbox.run({
209
+ let triageOutput: string | undefined;
210
+ if (!ticket.triageReady) {
211
+ await verifyTriageAccess(sandbox, ticket.id);
212
+ const triage = await sandbox.run({
189
213
  name: `triage #${ticket.id}`,
190
214
  maxIterations: 1,
191
215
  agent: roleAgent("routine", shipyard.CODEX_MODELS.routine),
192
216
  promptFile: "./.shipyard/triage-prompt.md",
193
217
  promptArgs: { TASK_ID: ticket.id, BASE_BRANCH: targetBranch },
194
218
  });
195
- verifyTriage(ticket.id);
219
+ triageOutput = triage.stdout;
220
+ }
221
+ verifyTriage(ticket.id, triageOutput);
196
222
  const implementation = await sandbox.run({
197
223
  name: "implementer",
198
224
  maxIterations: 100,
@@ -470,15 +496,19 @@ for (let iteration = 0; iteration < 10; iteration++) {
470
496
  let handoffEvidence: string;
471
497
  try {
472
498
  if (scope.kind === "standalone") {
473
- if (!scope.triageReady)
474
- await integration.run({
499
+ let triageOutput: string | undefined;
500
+ if (!scope.triageReady) {
501
+ await verifyTriageAccess(integration, id);
502
+ const triage = await integration.run({
475
503
  name: `triage #${id}`,
476
504
  maxIterations: 1,
477
505
  agent: roleAgent("routine", shipyard.CODEX_MODELS.routine),
478
506
  promptFile: "./.shipyard/triage-prompt.md",
479
507
  promptArgs: { TASK_ID: id, BASE_BRANCH: targetBranch },
480
508
  });
481
- verifyTriage(id);
509
+ triageOutput = triage.stdout;
510
+ }
511
+ verifyTriage(id, triageOutput);
482
512
  const standalone = await integration.run({
483
513
  name: "implementer",
484
514
  maxIterations: 100,
@@ -93,7 +93,7 @@ const closeClean = async (sandbox: {
93
93
  if (preservedWorktreePath)
94
94
  throw new Error(`Sandbox has uncommitted work at ${preservedWorktreePath}`);
95
95
  };
96
- const verifyTriage = (ticketId: string) => {
96
+ const verifyTriage = (ticketId: string, triageOutput?: string) => {
97
97
  try {
98
98
  execFileSync("bash", [".shipyard/verify-triage.sh", ticketId, repository], {
99
99
  encoding: "utf8",
@@ -102,9 +102,31 @@ const verifyTriage = (ticketId: string) => {
102
102
  const detail = (error as { stderr?: string | Buffer }).stderr
103
103
  ?.toString()
104
104
  .trim();
105
- throw new Error(detail || `Could not verify triage for #${ticketId}`);
105
+ const reason = detail || `Could not verify triage for #${ticketId}`;
106
+ const output = triageOutput?.trim();
107
+ throw new Error(
108
+ output ? `${reason}; triage output: ${output.slice(-1200)}` : reason,
109
+ );
106
110
  }
107
111
  };
112
+ const verifyTriageAccess = async (
113
+ sandbox: {
114
+ exec: (
115
+ command: string,
116
+ ) => Promise<{ exitCode: number; stdout: string; stderr: string }>;
117
+ },
118
+ ticketId: string,
119
+ ) => {
120
+ if (!/^\d+$/.test(ticketId))
121
+ throw new Error(`Invalid issue number: ${ticketId}`);
122
+ const result = await sandbox.exec(
123
+ `gh issue view ${ticketId} --repo ${repository} --json number --jq .number`,
124
+ );
125
+ if (result.exitCode !== 0)
126
+ throw new Error(
127
+ `Cannot triage issue #${ticketId}: GitHub access failed in the sandbox: ${result.stderr.trim() || result.stdout.trim() || `exit ${result.exitCode}`}`,
128
+ );
129
+ };
108
130
 
109
131
  const handoffEvidence = (stdout: string): string | undefined =>
110
132
  [...stdout.matchAll(/<handoff>([\s\S]*?)<\/handoff>/g)].at(-1)?.[1]?.trim();
@@ -200,15 +222,19 @@ for (let iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
200
222
  ? issue.tickets?.find((ticket) => ticket.id === ticketId)
201
223
  ?.triageReady
202
224
  : issue.triageReady;
203
- if (!ready)
204
- await sandbox.run({
225
+ let triageOutput: string | undefined;
226
+ if (!ready) {
227
+ await verifyTriageAccess(sandbox, ticketId);
228
+ const triage = await sandbox.run({
205
229
  name: `triage #${ticketId}`,
206
230
  agent: roleAgent("routine", shipyard.CODEX_MODELS.routine),
207
231
  maxIterations: 1,
208
232
  promptFile: "./.shipyard/triage-prompt.md",
209
233
  promptArgs: { TASK_ID: ticketId, BASE_BRANCH: targetBranch },
210
234
  });
211
- verifyTriage(ticketId);
235
+ triageOutput = triage.stdout;
236
+ }
237
+ verifyTriage(ticketId, triageOutput);
212
238
  }
213
239
  const implement = await sandbox.run({
214
240
  name: "implementer",
@@ -92,7 +92,7 @@ const closeClean = async (sandbox: {
92
92
  if (preservedWorktreePath)
93
93
  throw new Error(`Sandbox has uncommitted work at ${preservedWorktreePath}`);
94
94
  };
95
- const verifyTriage = (ticketId: string) => {
95
+ const verifyTriage = (ticketId: string, triageOutput?: string) => {
96
96
  try {
97
97
  execFileSync("bash", [".shipyard/verify-triage.sh", ticketId, repository], {
98
98
  encoding: "utf8",
@@ -101,9 +101,31 @@ const verifyTriage = (ticketId: string) => {
101
101
  const detail = (error as { stderr?: string | Buffer }).stderr
102
102
  ?.toString()
103
103
  .trim();
104
- throw new Error(detail || `Could not verify triage for #${ticketId}`);
104
+ const reason = detail || `Could not verify triage for #${ticketId}`;
105
+ const output = triageOutput?.trim();
106
+ throw new Error(
107
+ output ? `${reason}; triage output: ${output.slice(-1200)}` : reason,
108
+ );
105
109
  }
106
110
  };
111
+ const verifyTriageAccess = async (
112
+ sandbox: {
113
+ exec: (
114
+ command: string,
115
+ ) => Promise<{ exitCode: number; stdout: string; stderr: string }>;
116
+ },
117
+ ticketId: string,
118
+ ) => {
119
+ if (!/^\d+$/.test(ticketId))
120
+ throw new Error(`Invalid issue number: ${ticketId}`);
121
+ const result = await sandbox.exec(
122
+ `gh issue view ${ticketId} --repo ${repository} --json number --jq .number`,
123
+ );
124
+ if (result.exitCode !== 0)
125
+ throw new Error(
126
+ `Cannot triage issue #${ticketId}: GitHub access failed in the sandbox: ${result.stderr.trim() || result.stdout.trim() || `exit ${result.exitCode}`}`,
127
+ );
128
+ };
107
129
 
108
130
  const blockScope = (
109
131
  scope: { id: string; branch: string; tickets?: Array<{ id: string }> },
@@ -197,15 +219,19 @@ for (let iteration = 0; iteration < 3; iteration++) {
197
219
  ? issue.tickets?.find((ticket) => ticket.id === ticketId)
198
220
  ?.triageReady
199
221
  : issue.triageReady;
200
- if (!ready)
201
- await sandbox.run({
222
+ let triageOutput: string | undefined;
223
+ if (!ready) {
224
+ await verifyTriageAccess(sandbox, ticketId);
225
+ const triage = await sandbox.run({
202
226
  name: `triage #${ticketId}`,
203
227
  agent: roleAgent("routine", shipyard.CODEX_MODELS.routine),
204
228
  maxIterations: 1,
205
229
  promptFile: "./.shipyard/triage-prompt.md",
206
230
  promptArgs: { TASK_ID: ticketId, BASE_BRANCH: targetBranch },
207
231
  });
208
- verifyTriage(ticketId);
232
+ triageOutput = triage.stdout;
233
+ }
234
+ verifyTriage(ticketId, triageOutput);
209
235
  }
210
236
  const result = await sandbox.run({
211
237
  name: "implementer",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snappedly-tools/shipyard",
3
- "version": "0.11.1",
3
+ "version": "0.11.2",
4
4
  "description": "Run AI coding agents in isolated sandboxes",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",