@snappedly-tools/shipyard 0.9.2 → 0.11.0
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/README.md +9 -6
- package/dist/{chunk-NQRFVKCU.js → chunk-2SEMX2NX.js} +2 -2
- package/dist/{chunk-NQRFVKCU.js.map → chunk-2SEMX2NX.js.map} +1 -1
- package/dist/{chunk-57EEKW3R.js → chunk-BAP7BWQ2.js} +17 -8
- package/dist/chunk-BAP7BWQ2.js.map +1 -0
- package/dist/{chunk-HXSZM52J.js → chunk-IVE7QDGC.js} +17 -11
- package/dist/chunk-IVE7QDGC.js.map +1 -0
- package/dist/{chunk-JZUBT4WG.js → chunk-KSTSHZNH.js} +3 -3
- package/dist/{chunk-JZUBT4WG.js.map → chunk-KSTSHZNH.js.map} +1 -1
- package/dist/{createSandbox-DmbnWAZv.d.ts → createSandbox-Bqopwday.d.ts} +6 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.js +58 -13
- package/dist/index.js.map +1 -1
- package/dist/integrations/github.js +1 -1
- package/dist/main.js +26 -7
- package/dist/main.js.map +1 -1
- package/dist/sandboxes/docker.js +2 -2
- package/dist/templates/parallel-planner/block-scope.sh +6 -0
- package/dist/templates/parallel-planner/handoff.sh +7 -2
- package/dist/templates/parallel-planner/main.mts +41 -26
- package/dist/templates/parallel-planner/select-issues.mjs +20 -2
- package/dist/templates/parallel-planner/setup.sh +8 -1
- package/dist/templates/parallel-planner/triage-prompt.md +4 -2
- package/dist/templates/parallel-planner/verify-triage.sh +12 -3
- package/dist/templates/parallel-planner-with-review/block-scope.sh +6 -0
- package/dist/templates/parallel-planner-with-review/handoff.sh +7 -2
- package/dist/templates/parallel-planner-with-review/main.mts +42 -27
- package/dist/templates/parallel-planner-with-review/select-issues.mjs +20 -2
- package/dist/templates/parallel-planner-with-review/setup.sh +8 -1
- package/dist/templates/parallel-planner-with-review/triage-prompt.md +4 -2
- package/dist/templates/parallel-planner-with-review/verify-triage.sh +12 -3
- package/dist/templates/sequential-reviewer/block-scope.sh +6 -0
- package/dist/templates/sequential-reviewer/handoff.sh +7 -2
- package/dist/templates/sequential-reviewer/main.mts +35 -20
- package/dist/templates/sequential-reviewer/select-issues.mjs +20 -2
- package/dist/templates/sequential-reviewer/setup.sh +8 -1
- package/dist/templates/sequential-reviewer/triage-prompt.md +4 -2
- package/dist/templates/sequential-reviewer/verify-triage.sh +12 -3
- package/dist/templates/shared/block-scope.sh +6 -0
- package/dist/templates/shared/handoff.sh +7 -2
- package/dist/templates/shared/select-issues.mjs +20 -2
- package/dist/templates/shared/setup.sh +8 -1
- package/dist/templates/shared/triage-prompt.md +4 -2
- package/dist/templates/shared/verify-triage.sh +12 -3
- package/dist/templates/simple-loop/block-scope.sh +6 -0
- package/dist/templates/simple-loop/handoff.sh +7 -2
- package/dist/templates/simple-loop/main.mts +34 -19
- package/dist/templates/simple-loop/select-issues.mjs +20 -2
- package/dist/templates/simple-loop/setup.sh +8 -1
- package/dist/templates/simple-loop/triage-prompt.md +4 -2
- package/dist/templates/simple-loop/verify-triage.sh +12 -3
- package/dist/workflow.d.ts +1 -1
- package/dist/workflow.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-57EEKW3R.js.map +0 -1
- package/dist/chunk-HXSZM52J.js.map +0 -1
package/dist/sandboxes/docker.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { defaultImageName, docker } from '../chunk-
|
|
2
|
-
import '../chunk-
|
|
1
|
+
export { defaultImageName, docker } from '../chunk-KSTSHZNH.js';
|
|
2
|
+
import '../chunk-BAP7BWQ2.js';
|
|
3
3
|
//# sourceMappingURL=docker.js.map
|
|
4
4
|
//# sourceMappingURL=docker.js.map
|
|
@@ -108,6 +108,10 @@ Work was not handed off. To retry, resolve the problem, remove shipyard:blocked,
|
|
|
108
108
|
fi
|
|
109
109
|
|
|
110
110
|
if [[ "$branch" == "shipyard/spec-$root" ]]; then
|
|
111
|
+
if ! remove_issue_status "$root" ready-for-human; then
|
|
112
|
+
echo "Warning: could not clear spec triage status" >&2
|
|
113
|
+
status_synced=false
|
|
114
|
+
fi
|
|
111
115
|
if ! remove_issue_status "$root" shipyard:outstanding-tasks; then
|
|
112
116
|
echo "Warning: could not clear spec status" >&2
|
|
113
117
|
status_synced=false
|
|
@@ -122,9 +126,11 @@ else
|
|
|
122
126
|
fi
|
|
123
127
|
if [[ "$branch" == "shipyard/spec-$root" ]]; then
|
|
124
128
|
for affected_id in "${affected_ids[@]:1}"; do
|
|
129
|
+
remove_issue_status "$affected_id" ready-for-human || status=75
|
|
125
130
|
remove_issue_status "$affected_id" shipyard:pending || status=75
|
|
126
131
|
done
|
|
127
132
|
else
|
|
133
|
+
remove_issue_status "$root" ready-for-human || status=75
|
|
128
134
|
remove_issue_status "$root" shipyard:pending || status=75
|
|
129
135
|
fi
|
|
130
136
|
|
|
@@ -103,6 +103,7 @@ for ((i=1; i<${#scope_ids[@]}; i++)); do
|
|
|
103
103
|
echo "Could not mark ticket #${scope_ids[i]} complete; retry issue completion when GitHub is available" >&2
|
|
104
104
|
exit 75
|
|
105
105
|
fi
|
|
106
|
+
remove_issue_label "${scope_ids[i]}" ready-for-human
|
|
106
107
|
remove_issue_label "${scope_ids[i]}" shipyard:blocked
|
|
107
108
|
remove_issue_label "${scope_ids[i]}" shipyard:pending
|
|
108
109
|
done
|
|
@@ -128,7 +129,11 @@ if ! gh label create "$status_label" --repo "$repo" --color "$status_color" --de
|
|
|
128
129
|
echo "Warning: could not create $status_label" >&2
|
|
129
130
|
status_synced=false
|
|
130
131
|
fi
|
|
131
|
-
if
|
|
132
|
+
if gh issue edit "$issue" --repo "$repo" --add-label "$status_label"; then
|
|
133
|
+
if [[ "$status_label" == shipyard:complete ]]; then
|
|
134
|
+
remove_issue_label "$issue" ready-for-human
|
|
135
|
+
fi
|
|
136
|
+
else
|
|
132
137
|
if [[ "$branch" == "shipyard/issue-$issue" ]]; then
|
|
133
138
|
echo "Could not mark ticket #$issue complete; retry issue completion when GitHub is available" >&2
|
|
134
139
|
exit 75
|
|
@@ -170,6 +175,6 @@ if [[ "$status_synced" == true ]]; then
|
|
|
170
175
|
fi
|
|
171
176
|
done
|
|
172
177
|
else
|
|
173
|
-
echo "Warning: status labels need reconciliation;
|
|
178
|
+
echo "Warning: status labels need reconciliation; add shipyard to the ticket to retry" >&2
|
|
174
179
|
fi
|
|
175
180
|
printf '%s\n' "$url"
|
|
@@ -27,24 +27,27 @@ const roleModels = {
|
|
|
27
27
|
routine: readRoleModel("routine"),
|
|
28
28
|
strong: readRoleModel("strong"),
|
|
29
29
|
};
|
|
30
|
-
const
|
|
31
|
-
type
|
|
32
|
-
const
|
|
30
|
+
const REASONING_EFFORTS = shipyard.REASONING_EFFORTS;
|
|
31
|
+
type ReasoningEffort = shipyard.ReasoningEffort;
|
|
32
|
+
const readRoleReasoningEffort = (
|
|
33
33
|
role: ModelRole,
|
|
34
|
-
):
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
const
|
|
34
|
+
): ReasoningEffort | undefined => {
|
|
35
|
+
const envName = `SHIPYARD_${role.toUpperCase()}_REASONING_EFFORT`;
|
|
36
|
+
const legacyName = `SHIPYARD_CODEX_${role.toUpperCase()}_REASONING_EFFORT`;
|
|
37
|
+
const sharedEffort = process.env[envName]?.trim();
|
|
38
|
+
const effort =
|
|
39
|
+
sharedEffort ||
|
|
40
|
+
(CODEX_PROVIDER ? process.env[legacyName]?.trim() : undefined);
|
|
38
41
|
if (!effort) return undefined;
|
|
39
|
-
if (!(
|
|
42
|
+
if (!(REASONING_EFFORTS as readonly string[]).includes(effort))
|
|
40
43
|
throw new Error(
|
|
41
|
-
`${envName} must be one of ${
|
|
44
|
+
`${sharedEffort ? envName : legacyName} must be one of ${REASONING_EFFORTS.join(", ")}; received "${effort}"`,
|
|
42
45
|
);
|
|
43
|
-
return effort as
|
|
46
|
+
return effort as ReasoningEffort;
|
|
44
47
|
};
|
|
45
48
|
const roleEfforts = {
|
|
46
|
-
routine:
|
|
47
|
-
strong:
|
|
49
|
+
routine: readRoleReasoningEffort("routine"),
|
|
50
|
+
strong: readRoleReasoningEffort("strong"),
|
|
48
51
|
};
|
|
49
52
|
const readCodexRoleModel = (role: ModelRole, defaultModel: AgentModel) => {
|
|
50
53
|
if (!CODEX_PROVIDER || typeof defaultModel === "string") return defaultModel;
|
|
@@ -112,12 +115,14 @@ type Ticket = {
|
|
|
112
115
|
body: string;
|
|
113
116
|
state: string;
|
|
114
117
|
blockedBy: Array<{ id: string; title: string; state: string }>;
|
|
118
|
+
triageReady: boolean;
|
|
115
119
|
};
|
|
116
120
|
type Scope = {
|
|
117
121
|
id: string;
|
|
118
122
|
title: string;
|
|
119
123
|
branch: string;
|
|
120
124
|
kind: "standalone" | "spec";
|
|
125
|
+
triageReady?: boolean;
|
|
121
126
|
body?: string;
|
|
122
127
|
tickets?: Ticket[];
|
|
123
128
|
completedTicketIds?: string[];
|
|
@@ -166,15 +171,17 @@ const runWorker = async (scope: Scope, ticket: Ticket) => {
|
|
|
166
171
|
baseBranch: scope.branch,
|
|
167
172
|
sandbox: sandboxProvider,
|
|
168
173
|
hooks,
|
|
174
|
+
copyToWorktree: [".shipyard/setup.sh"],
|
|
169
175
|
});
|
|
170
176
|
try {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
177
|
+
if (!ticket.triageReady)
|
|
178
|
+
await sandbox.run({
|
|
179
|
+
name: `triage #${ticket.id}`,
|
|
180
|
+
maxIterations: 1,
|
|
181
|
+
agent: roleAgent("routine", shipyard.CODEX_MODELS.routine),
|
|
182
|
+
promptFile: "./.shipyard/triage-prompt.md",
|
|
183
|
+
promptArgs: { TASK_ID: ticket.id, BASE_BRANCH: targetBranch },
|
|
184
|
+
});
|
|
178
185
|
verifyTriage(ticket.id);
|
|
179
186
|
const implementation = await sandbox.run({
|
|
180
187
|
name: "implementer",
|
|
@@ -212,6 +219,7 @@ for (let iteration = 0; iteration < 10; iteration++) {
|
|
|
212
219
|
const plan = await shipyard.run({
|
|
213
220
|
hooks,
|
|
214
221
|
sandbox: sandboxProvider,
|
|
222
|
+
copyToWorktree: [".shipyard/setup.sh"],
|
|
215
223
|
name: "planner",
|
|
216
224
|
branchStrategy: { type: "branch", branch: plannerBranch },
|
|
217
225
|
maxIterations: 1,
|
|
@@ -257,6 +265,8 @@ for (let iteration = 0; iteration < 10; iteration++) {
|
|
|
257
265
|
repository,
|
|
258
266
|
"--add-label",
|
|
259
267
|
"shipyard:pending",
|
|
268
|
+
"--remove-label",
|
|
269
|
+
"shipyard",
|
|
260
270
|
]);
|
|
261
271
|
if (
|
|
262
272
|
scope.branch !==
|
|
@@ -271,6 +281,7 @@ for (let iteration = 0; iteration < 10; iteration++) {
|
|
|
271
281
|
branch: scope.branch,
|
|
272
282
|
sandbox: sandboxProvider,
|
|
273
283
|
hooks,
|
|
284
|
+
copyToWorktree: [".shipyard/setup.sh"],
|
|
274
285
|
});
|
|
275
286
|
await closeClean(seed);
|
|
276
287
|
const tickets = scope.tickets ?? [];
|
|
@@ -305,6 +316,7 @@ for (let iteration = 0; iteration < 10; iteration++) {
|
|
|
305
316
|
branch: scope.branch,
|
|
306
317
|
sandbox: sandboxProvider,
|
|
307
318
|
hooks,
|
|
319
|
+
copyToWorktree: [".shipyard/setup.sh"],
|
|
308
320
|
});
|
|
309
321
|
try {
|
|
310
322
|
for (const [index, outcome] of settled.entries()) {
|
|
@@ -430,17 +442,19 @@ for (let iteration = 0; iteration < 10; iteration++) {
|
|
|
430
442
|
branch: scope.branch,
|
|
431
443
|
sandbox: sandboxProvider,
|
|
432
444
|
hooks,
|
|
445
|
+
copyToWorktree: [".shipyard/setup.sh"],
|
|
433
446
|
});
|
|
434
447
|
let handoffEvidence: string;
|
|
435
448
|
try {
|
|
436
449
|
if (scope.kind === "standalone") {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
450
|
+
if (!scope.triageReady)
|
|
451
|
+
await integration.run({
|
|
452
|
+
name: `triage #${id}`,
|
|
453
|
+
maxIterations: 1,
|
|
454
|
+
agent: roleAgent("routine", shipyard.CODEX_MODELS.routine),
|
|
455
|
+
promptFile: "./.shipyard/triage-prompt.md",
|
|
456
|
+
promptArgs: { TASK_ID: id, BASE_BRANCH: targetBranch },
|
|
457
|
+
});
|
|
444
458
|
verifyTriage(id);
|
|
445
459
|
const standalone = await integration.run({
|
|
446
460
|
name: "implementer",
|
|
@@ -477,6 +491,7 @@ for (let iteration = 0; iteration < 10; iteration++) {
|
|
|
477
491
|
const publication = await shipyard.createSandbox({
|
|
478
492
|
branch: scope.branch,
|
|
479
493
|
sandbox: sandboxProvider,
|
|
494
|
+
copyToWorktree: [".shipyard/handoff.sh"],
|
|
480
495
|
});
|
|
481
496
|
try {
|
|
482
497
|
const scopeIds = [
|
|
@@ -124,6 +124,17 @@ const dependencies = (id) => {
|
|
|
124
124
|
};
|
|
125
125
|
const planningSpec = (body) =>
|
|
126
126
|
/work item type:\*?\*?\s*planning spec/i.test(body ?? "");
|
|
127
|
+
const triageStates = [
|
|
128
|
+
"needs-triage",
|
|
129
|
+
"needs-info",
|
|
130
|
+
"ready-for-agent",
|
|
131
|
+
"ready-for-human",
|
|
132
|
+
"wontfix",
|
|
133
|
+
];
|
|
134
|
+
const triageReady = (labels) =>
|
|
135
|
+
triageStates
|
|
136
|
+
.filter((state) => labels.some((label) => label.name === state))
|
|
137
|
+
.join() === "ready-for-agent";
|
|
127
138
|
const textParent = (body, issueId) => {
|
|
128
139
|
const match = /^##?\s*Parent\b[^\n]*\n/im.exec(body ?? "");
|
|
129
140
|
if (!match) return undefined;
|
|
@@ -476,9 +487,10 @@ for (const candidate of activated) {
|
|
|
476
487
|
),
|
|
477
488
|
);
|
|
478
489
|
selectedTicketIds = new Set(selectableTickets.map((ticket) => ticket.id));
|
|
479
|
-
const tickets = selectableTickets.map(({ labels
|
|
490
|
+
const tickets = selectableTickets.map(({ labels, ...ticket }) => ({
|
|
480
491
|
...ticket,
|
|
481
492
|
blockedBy: dependencies(ticket.id),
|
|
493
|
+
triageReady: triageReady(labels),
|
|
482
494
|
}));
|
|
483
495
|
const completedTicketIds = linkedTickets
|
|
484
496
|
.filter((ticket) =>
|
|
@@ -756,7 +768,13 @@ for (const candidate of activated) {
|
|
|
756
768
|
completedTicketIds,
|
|
757
769
|
outstandingTicketIds,
|
|
758
770
|
}
|
|
759
|
-
: {
|
|
771
|
+
: {
|
|
772
|
+
id,
|
|
773
|
+
title: candidate.title,
|
|
774
|
+
branch,
|
|
775
|
+
kind: "standalone",
|
|
776
|
+
triageReady: triageReady(candidate.labels ?? []),
|
|
777
|
+
},
|
|
760
778
|
);
|
|
761
779
|
} catch (error) {
|
|
762
780
|
if (!(error instanceof SelectionError)) throw error;
|
|
@@ -18,7 +18,14 @@ if [[ -f package.json ]]; then
|
|
|
18
18
|
else manager=npm; fi
|
|
19
19
|
fi
|
|
20
20
|
case "$manager" in
|
|
21
|
-
npm)
|
|
21
|
+
npm)
|
|
22
|
+
if [[ -f package-lock.json ]]; then
|
|
23
|
+
if ! npm ci; then
|
|
24
|
+
echo "npm ci failed; installing from package.json without changing package-lock.json" >&2
|
|
25
|
+
npm install --no-package-lock
|
|
26
|
+
fi
|
|
27
|
+
else npm install --no-package-lock
|
|
28
|
+
fi ;;
|
|
22
29
|
pnpm) corepack pnpm install ;;
|
|
23
30
|
yarn) corepack yarn install ;;
|
|
24
31
|
bun)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
Triage GitHub issue #{{TASK_ID}} before Shipyard implementation.
|
|
2
2
|
|
|
3
|
-
Follow `/triage` and the repository's
|
|
3
|
+
Follow `/triage` and the repository's triage label mapping. Read the full issue and comments. Verify the reported behavior against `origin/{{BASE_BRANCH}}`, the integration baseline. The checked-out task branch may contain unfinished changes from an earlier attempt. Do not close the issue as already complete based on the task branch or comments about that candidate.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Apply the appropriate category and state label and post the brief or triage notes required by the skill. The maintainer has authorized Shipyard to apply an evidence-based recommendation during this unattended intake. Derive acceptance criteria from the stated outcome. For example, equal spacing between named header items is a testable visual goal without a pixel target. Choose a representative supported viewport if none is specified. An inaccessible attachment does not require `needs-info` when the text establishes the current and desired behavior; record the verification limit in the brief. Do not treat equal CSS gap values alone as proof that the rendered gaps are equal. Apply `needs-info` and ask one concrete question only when missing details change the intended result or prevent meaningful verification. Use `needs-triage` only when the available facts leave a product or policy decision for the maintainer, not for routine layout or implementation choices. Preserve the `shipyard` or `shipyard:pending` label. Do not implement the issue or start a PR in this phase.
|
|
6
|
+
|
|
7
|
+
Report the applied state and evidence. Shipyard will independently re-read GitHub labels and implement only an open issue carrying `shipyard` or `shipyard:pending` and the sole triage state `ready-for-agent`.
|
|
@@ -11,9 +11,18 @@ gh issue view "$issue" --repo "$repo" --json state,labels |
|
|
|
11
11
|
const issue = JSON.parse(require("node:fs").readFileSync(0, "utf8"));
|
|
12
12
|
const labels = new Set(issue.labels.map((label) => label.name));
|
|
13
13
|
const states = ["needs-triage", "needs-info", "ready-for-agent", "ready-for-human", "wontfix"];
|
|
14
|
-
if (issue.state !== "OPEN" ||
|
|
15
|
-
|
|
16
|
-
console.error("Issue cannot be implemented: requires an open issue with shipyard
|
|
14
|
+
if (issue.state !== "OPEN" ||
|
|
15
|
+
!(labels.has("shipyard") || labels.has("shipyard:pending"))) {
|
|
16
|
+
console.error("Issue cannot be implemented: requires an open issue with shipyard or shipyard:pending");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
const triageStates = states.filter((state) => labels.has(state));
|
|
20
|
+
if (triageStates.join() === "needs-info") {
|
|
21
|
+
console.error("Issue needs information from the reporter before implementation");
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
if (triageStates.join() !== "ready-for-agent") {
|
|
25
|
+
console.error(`Issue cannot be implemented: triage state is ${triageStates.join() || "missing"}; requires only ready-for-agent`);
|
|
17
26
|
process.exit(1);
|
|
18
27
|
}
|
|
19
28
|
'
|
|
@@ -108,6 +108,10 @@ Work was not handed off. To retry, resolve the problem, remove shipyard:blocked,
|
|
|
108
108
|
fi
|
|
109
109
|
|
|
110
110
|
if [[ "$branch" == "shipyard/spec-$root" ]]; then
|
|
111
|
+
if ! remove_issue_status "$root" ready-for-human; then
|
|
112
|
+
echo "Warning: could not clear spec triage status" >&2
|
|
113
|
+
status_synced=false
|
|
114
|
+
fi
|
|
111
115
|
if ! remove_issue_status "$root" shipyard:outstanding-tasks; then
|
|
112
116
|
echo "Warning: could not clear spec status" >&2
|
|
113
117
|
status_synced=false
|
|
@@ -122,9 +126,11 @@ else
|
|
|
122
126
|
fi
|
|
123
127
|
if [[ "$branch" == "shipyard/spec-$root" ]]; then
|
|
124
128
|
for affected_id in "${affected_ids[@]:1}"; do
|
|
129
|
+
remove_issue_status "$affected_id" ready-for-human || status=75
|
|
125
130
|
remove_issue_status "$affected_id" shipyard:pending || status=75
|
|
126
131
|
done
|
|
127
132
|
else
|
|
133
|
+
remove_issue_status "$root" ready-for-human || status=75
|
|
128
134
|
remove_issue_status "$root" shipyard:pending || status=75
|
|
129
135
|
fi
|
|
130
136
|
|
|
@@ -103,6 +103,7 @@ for ((i=1; i<${#scope_ids[@]}; i++)); do
|
|
|
103
103
|
echo "Could not mark ticket #${scope_ids[i]} complete; retry issue completion when GitHub is available" >&2
|
|
104
104
|
exit 75
|
|
105
105
|
fi
|
|
106
|
+
remove_issue_label "${scope_ids[i]}" ready-for-human
|
|
106
107
|
remove_issue_label "${scope_ids[i]}" shipyard:blocked
|
|
107
108
|
remove_issue_label "${scope_ids[i]}" shipyard:pending
|
|
108
109
|
done
|
|
@@ -128,7 +129,11 @@ if ! gh label create "$status_label" --repo "$repo" --color "$status_color" --de
|
|
|
128
129
|
echo "Warning: could not create $status_label" >&2
|
|
129
130
|
status_synced=false
|
|
130
131
|
fi
|
|
131
|
-
if
|
|
132
|
+
if gh issue edit "$issue" --repo "$repo" --add-label "$status_label"; then
|
|
133
|
+
if [[ "$status_label" == shipyard:complete ]]; then
|
|
134
|
+
remove_issue_label "$issue" ready-for-human
|
|
135
|
+
fi
|
|
136
|
+
else
|
|
132
137
|
if [[ "$branch" == "shipyard/issue-$issue" ]]; then
|
|
133
138
|
echo "Could not mark ticket #$issue complete; retry issue completion when GitHub is available" >&2
|
|
134
139
|
exit 75
|
|
@@ -170,6 +175,6 @@ if [[ "$status_synced" == true ]]; then
|
|
|
170
175
|
fi
|
|
171
176
|
done
|
|
172
177
|
else
|
|
173
|
-
echo "Warning: status labels need reconciliation;
|
|
178
|
+
echo "Warning: status labels need reconciliation; add shipyard to the ticket to retry" >&2
|
|
174
179
|
fi
|
|
175
180
|
printf '%s\n' "$url"
|
|
@@ -27,24 +27,27 @@ const roleModels = {
|
|
|
27
27
|
routine: readRoleModel("routine"),
|
|
28
28
|
strong: readRoleModel("strong"),
|
|
29
29
|
};
|
|
30
|
-
const
|
|
31
|
-
type
|
|
32
|
-
const
|
|
30
|
+
const REASONING_EFFORTS = shipyard.REASONING_EFFORTS;
|
|
31
|
+
type ReasoningEffort = shipyard.ReasoningEffort;
|
|
32
|
+
const readRoleReasoningEffort = (
|
|
33
33
|
role: ModelRole,
|
|
34
|
-
):
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
const
|
|
34
|
+
): ReasoningEffort | undefined => {
|
|
35
|
+
const envName = `SHIPYARD_${role.toUpperCase()}_REASONING_EFFORT`;
|
|
36
|
+
const legacyName = `SHIPYARD_CODEX_${role.toUpperCase()}_REASONING_EFFORT`;
|
|
37
|
+
const sharedEffort = process.env[envName]?.trim();
|
|
38
|
+
const effort =
|
|
39
|
+
sharedEffort ||
|
|
40
|
+
(CODEX_PROVIDER ? process.env[legacyName]?.trim() : undefined);
|
|
38
41
|
if (!effort) return undefined;
|
|
39
|
-
if (!(
|
|
42
|
+
if (!(REASONING_EFFORTS as readonly string[]).includes(effort))
|
|
40
43
|
throw new Error(
|
|
41
|
-
`${envName} must be one of ${
|
|
44
|
+
`${sharedEffort ? envName : legacyName} must be one of ${REASONING_EFFORTS.join(", ")}; received "${effort}"`,
|
|
42
45
|
);
|
|
43
|
-
return effort as
|
|
46
|
+
return effort as ReasoningEffort;
|
|
44
47
|
};
|
|
45
48
|
const roleEfforts = {
|
|
46
|
-
routine:
|
|
47
|
-
strong:
|
|
49
|
+
routine: readRoleReasoningEffort("routine"),
|
|
50
|
+
strong: readRoleReasoningEffort("strong"),
|
|
48
51
|
};
|
|
49
52
|
const readCodexRoleModel = (role: ModelRole, defaultModel: AgentModel) => {
|
|
50
53
|
if (!CODEX_PROVIDER || typeof defaultModel === "string") return defaultModel;
|
|
@@ -112,12 +115,14 @@ type Ticket = {
|
|
|
112
115
|
body: string;
|
|
113
116
|
state: string;
|
|
114
117
|
blockedBy: Array<{ id: string; title: string; state: string }>;
|
|
118
|
+
triageReady: boolean;
|
|
115
119
|
};
|
|
116
120
|
type Scope = {
|
|
117
121
|
id: string;
|
|
118
122
|
title: string;
|
|
119
123
|
branch: string;
|
|
120
124
|
kind: "standalone" | "spec";
|
|
125
|
+
triageReady?: boolean;
|
|
121
126
|
body?: string;
|
|
122
127
|
tickets?: Ticket[];
|
|
123
128
|
completedTicketIds?: string[];
|
|
@@ -144,7 +149,7 @@ const approved = (
|
|
|
144
149
|
throw new Error(
|
|
145
150
|
`${stage} has unresolved review findings: ${result.stdout.trim().slice(-1200)}`,
|
|
146
151
|
);
|
|
147
|
-
return complete(result, stage)
|
|
152
|
+
return `Review: APPROVED\n${complete(result, stage)}`;
|
|
148
153
|
};
|
|
149
154
|
class TicketFailures extends Error {
|
|
150
155
|
constructor(readonly failures: Array<{ id: string; reason: string }>) {
|
|
@@ -176,15 +181,17 @@ const runWorker = async (scope: Scope, ticket: Ticket) => {
|
|
|
176
181
|
baseBranch: scope.branch,
|
|
177
182
|
sandbox: sandboxProvider,
|
|
178
183
|
hooks,
|
|
184
|
+
copyToWorktree: [".shipyard/setup.sh"],
|
|
179
185
|
});
|
|
180
186
|
try {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
187
|
+
if (!ticket.triageReady)
|
|
188
|
+
await sandbox.run({
|
|
189
|
+
name: `triage #${ticket.id}`,
|
|
190
|
+
maxIterations: 1,
|
|
191
|
+
agent: roleAgent("routine", shipyard.CODEX_MODELS.routine),
|
|
192
|
+
promptFile: "./.shipyard/triage-prompt.md",
|
|
193
|
+
promptArgs: { TASK_ID: ticket.id, BASE_BRANCH: targetBranch },
|
|
194
|
+
});
|
|
188
195
|
verifyTriage(ticket.id);
|
|
189
196
|
const implementation = await sandbox.run({
|
|
190
197
|
name: "implementer",
|
|
@@ -235,6 +242,7 @@ for (let iteration = 0; iteration < 10; iteration++) {
|
|
|
235
242
|
const plan = await shipyard.run({
|
|
236
243
|
hooks,
|
|
237
244
|
sandbox: sandboxProvider,
|
|
245
|
+
copyToWorktree: [".shipyard/setup.sh"],
|
|
238
246
|
name: "planner",
|
|
239
247
|
branchStrategy: { type: "branch", branch: plannerBranch },
|
|
240
248
|
maxIterations: 1,
|
|
@@ -280,6 +288,8 @@ for (let iteration = 0; iteration < 10; iteration++) {
|
|
|
280
288
|
repository,
|
|
281
289
|
"--add-label",
|
|
282
290
|
"shipyard:pending",
|
|
291
|
+
"--remove-label",
|
|
292
|
+
"shipyard",
|
|
283
293
|
]);
|
|
284
294
|
if (
|
|
285
295
|
scope.branch !==
|
|
@@ -294,6 +304,7 @@ for (let iteration = 0; iteration < 10; iteration++) {
|
|
|
294
304
|
branch: scope.branch,
|
|
295
305
|
sandbox: sandboxProvider,
|
|
296
306
|
hooks,
|
|
307
|
+
copyToWorktree: [".shipyard/setup.sh"],
|
|
297
308
|
});
|
|
298
309
|
await closeClean(seed);
|
|
299
310
|
const tickets = scope.tickets ?? [];
|
|
@@ -328,6 +339,7 @@ for (let iteration = 0; iteration < 10; iteration++) {
|
|
|
328
339
|
branch: scope.branch,
|
|
329
340
|
sandbox: sandboxProvider,
|
|
330
341
|
hooks,
|
|
342
|
+
copyToWorktree: [".shipyard/setup.sh"],
|
|
331
343
|
});
|
|
332
344
|
try {
|
|
333
345
|
for (const [index, outcome] of settled.entries()) {
|
|
@@ -453,17 +465,19 @@ for (let iteration = 0; iteration < 10; iteration++) {
|
|
|
453
465
|
branch: scope.branch,
|
|
454
466
|
sandbox: sandboxProvider,
|
|
455
467
|
hooks,
|
|
468
|
+
copyToWorktree: [".shipyard/setup.sh"],
|
|
456
469
|
});
|
|
457
470
|
let handoffEvidence: string;
|
|
458
471
|
try {
|
|
459
472
|
if (scope.kind === "standalone") {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
473
|
+
if (!scope.triageReady)
|
|
474
|
+
await integration.run({
|
|
475
|
+
name: `triage #${id}`,
|
|
476
|
+
maxIterations: 1,
|
|
477
|
+
agent: roleAgent("routine", shipyard.CODEX_MODELS.routine),
|
|
478
|
+
promptFile: "./.shipyard/triage-prompt.md",
|
|
479
|
+
promptArgs: { TASK_ID: id, BASE_BRANCH: targetBranch },
|
|
480
|
+
});
|
|
467
481
|
verifyTriage(id);
|
|
468
482
|
const standalone = await integration.run({
|
|
469
483
|
name: "implementer",
|
|
@@ -528,6 +542,7 @@ for (let iteration = 0; iteration < 10; iteration++) {
|
|
|
528
542
|
const publication = await shipyard.createSandbox({
|
|
529
543
|
branch: scope.branch,
|
|
530
544
|
sandbox: sandboxProvider,
|
|
545
|
+
copyToWorktree: [".shipyard/handoff.sh"],
|
|
531
546
|
});
|
|
532
547
|
try {
|
|
533
548
|
const scopeIds = [
|
|
@@ -124,6 +124,17 @@ const dependencies = (id) => {
|
|
|
124
124
|
};
|
|
125
125
|
const planningSpec = (body) =>
|
|
126
126
|
/work item type:\*?\*?\s*planning spec/i.test(body ?? "");
|
|
127
|
+
const triageStates = [
|
|
128
|
+
"needs-triage",
|
|
129
|
+
"needs-info",
|
|
130
|
+
"ready-for-agent",
|
|
131
|
+
"ready-for-human",
|
|
132
|
+
"wontfix",
|
|
133
|
+
];
|
|
134
|
+
const triageReady = (labels) =>
|
|
135
|
+
triageStates
|
|
136
|
+
.filter((state) => labels.some((label) => label.name === state))
|
|
137
|
+
.join() === "ready-for-agent";
|
|
127
138
|
const textParent = (body, issueId) => {
|
|
128
139
|
const match = /^##?\s*Parent\b[^\n]*\n/im.exec(body ?? "");
|
|
129
140
|
if (!match) return undefined;
|
|
@@ -476,9 +487,10 @@ for (const candidate of activated) {
|
|
|
476
487
|
),
|
|
477
488
|
);
|
|
478
489
|
selectedTicketIds = new Set(selectableTickets.map((ticket) => ticket.id));
|
|
479
|
-
const tickets = selectableTickets.map(({ labels
|
|
490
|
+
const tickets = selectableTickets.map(({ labels, ...ticket }) => ({
|
|
480
491
|
...ticket,
|
|
481
492
|
blockedBy: dependencies(ticket.id),
|
|
493
|
+
triageReady: triageReady(labels),
|
|
482
494
|
}));
|
|
483
495
|
const completedTicketIds = linkedTickets
|
|
484
496
|
.filter((ticket) =>
|
|
@@ -756,7 +768,13 @@ for (const candidate of activated) {
|
|
|
756
768
|
completedTicketIds,
|
|
757
769
|
outstandingTicketIds,
|
|
758
770
|
}
|
|
759
|
-
: {
|
|
771
|
+
: {
|
|
772
|
+
id,
|
|
773
|
+
title: candidate.title,
|
|
774
|
+
branch,
|
|
775
|
+
kind: "standalone",
|
|
776
|
+
triageReady: triageReady(candidate.labels ?? []),
|
|
777
|
+
},
|
|
760
778
|
);
|
|
761
779
|
} catch (error) {
|
|
762
780
|
if (!(error instanceof SelectionError)) throw error;
|
|
@@ -18,7 +18,14 @@ if [[ -f package.json ]]; then
|
|
|
18
18
|
else manager=npm; fi
|
|
19
19
|
fi
|
|
20
20
|
case "$manager" in
|
|
21
|
-
npm)
|
|
21
|
+
npm)
|
|
22
|
+
if [[ -f package-lock.json ]]; then
|
|
23
|
+
if ! npm ci; then
|
|
24
|
+
echo "npm ci failed; installing from package.json without changing package-lock.json" >&2
|
|
25
|
+
npm install --no-package-lock
|
|
26
|
+
fi
|
|
27
|
+
else npm install --no-package-lock
|
|
28
|
+
fi ;;
|
|
22
29
|
pnpm) corepack pnpm install ;;
|
|
23
30
|
yarn) corepack yarn install ;;
|
|
24
31
|
bun)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
Triage GitHub issue #{{TASK_ID}} before Shipyard implementation.
|
|
2
2
|
|
|
3
|
-
Follow `/triage` and the repository's
|
|
3
|
+
Follow `/triage` and the repository's triage label mapping. Read the full issue and comments. Verify the reported behavior against `origin/{{BASE_BRANCH}}`, the integration baseline. The checked-out task branch may contain unfinished changes from an earlier attempt. Do not close the issue as already complete based on the task branch or comments about that candidate.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Apply the appropriate category and state label and post the brief or triage notes required by the skill. The maintainer has authorized Shipyard to apply an evidence-based recommendation during this unattended intake. Derive acceptance criteria from the stated outcome. For example, equal spacing between named header items is a testable visual goal without a pixel target. Choose a representative supported viewport if none is specified. An inaccessible attachment does not require `needs-info` when the text establishes the current and desired behavior; record the verification limit in the brief. Do not treat equal CSS gap values alone as proof that the rendered gaps are equal. Apply `needs-info` and ask one concrete question only when missing details change the intended result or prevent meaningful verification. Use `needs-triage` only when the available facts leave a product or policy decision for the maintainer, not for routine layout or implementation choices. Preserve the `shipyard` or `shipyard:pending` label. Do not implement the issue or start a PR in this phase.
|
|
6
|
+
|
|
7
|
+
Report the applied state and evidence. Shipyard will independently re-read GitHub labels and implement only an open issue carrying `shipyard` or `shipyard:pending` and the sole triage state `ready-for-agent`.
|
|
@@ -11,9 +11,18 @@ gh issue view "$issue" --repo "$repo" --json state,labels |
|
|
|
11
11
|
const issue = JSON.parse(require("node:fs").readFileSync(0, "utf8"));
|
|
12
12
|
const labels = new Set(issue.labels.map((label) => label.name));
|
|
13
13
|
const states = ["needs-triage", "needs-info", "ready-for-agent", "ready-for-human", "wontfix"];
|
|
14
|
-
if (issue.state !== "OPEN" ||
|
|
15
|
-
|
|
16
|
-
console.error("Issue cannot be implemented: requires an open issue with shipyard
|
|
14
|
+
if (issue.state !== "OPEN" ||
|
|
15
|
+
!(labels.has("shipyard") || labels.has("shipyard:pending"))) {
|
|
16
|
+
console.error("Issue cannot be implemented: requires an open issue with shipyard or shipyard:pending");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
const triageStates = states.filter((state) => labels.has(state));
|
|
20
|
+
if (triageStates.join() === "needs-info") {
|
|
21
|
+
console.error("Issue needs information from the reporter before implementation");
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
if (triageStates.join() !== "ready-for-agent") {
|
|
25
|
+
console.error(`Issue cannot be implemented: triage state is ${triageStates.join() || "missing"}; requires only ready-for-agent`);
|
|
17
26
|
process.exit(1);
|
|
18
27
|
}
|
|
19
28
|
'
|
|
@@ -108,6 +108,10 @@ Work was not handed off. To retry, resolve the problem, remove shipyard:blocked,
|
|
|
108
108
|
fi
|
|
109
109
|
|
|
110
110
|
if [[ "$branch" == "shipyard/spec-$root" ]]; then
|
|
111
|
+
if ! remove_issue_status "$root" ready-for-human; then
|
|
112
|
+
echo "Warning: could not clear spec triage status" >&2
|
|
113
|
+
status_synced=false
|
|
114
|
+
fi
|
|
111
115
|
if ! remove_issue_status "$root" shipyard:outstanding-tasks; then
|
|
112
116
|
echo "Warning: could not clear spec status" >&2
|
|
113
117
|
status_synced=false
|
|
@@ -122,9 +126,11 @@ else
|
|
|
122
126
|
fi
|
|
123
127
|
if [[ "$branch" == "shipyard/spec-$root" ]]; then
|
|
124
128
|
for affected_id in "${affected_ids[@]:1}"; do
|
|
129
|
+
remove_issue_status "$affected_id" ready-for-human || status=75
|
|
125
130
|
remove_issue_status "$affected_id" shipyard:pending || status=75
|
|
126
131
|
done
|
|
127
132
|
else
|
|
133
|
+
remove_issue_status "$root" ready-for-human || status=75
|
|
128
134
|
remove_issue_status "$root" shipyard:pending || status=75
|
|
129
135
|
fi
|
|
130
136
|
|