@snappedly-tools/shipyard 0.9.2 → 0.11.0-staging.36280043078
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
|
@@ -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"
|
|
@@ -22,24 +22,27 @@ const roleModels = {
|
|
|
22
22
|
routine: readRoleModel("routine"),
|
|
23
23
|
strong: readRoleModel("strong"),
|
|
24
24
|
};
|
|
25
|
-
const
|
|
26
|
-
type
|
|
27
|
-
const
|
|
25
|
+
const REASONING_EFFORTS = shipyard.REASONING_EFFORTS;
|
|
26
|
+
type ReasoningEffort = shipyard.ReasoningEffort;
|
|
27
|
+
const readRoleReasoningEffort = (
|
|
28
28
|
role: ModelRole,
|
|
29
|
-
):
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
const
|
|
29
|
+
): ReasoningEffort | undefined => {
|
|
30
|
+
const envName = `SHIPYARD_${role.toUpperCase()}_REASONING_EFFORT`;
|
|
31
|
+
const legacyName = `SHIPYARD_CODEX_${role.toUpperCase()}_REASONING_EFFORT`;
|
|
32
|
+
const sharedEffort = process.env[envName]?.trim();
|
|
33
|
+
const effort =
|
|
34
|
+
sharedEffort ||
|
|
35
|
+
(CODEX_PROVIDER ? process.env[legacyName]?.trim() : undefined);
|
|
33
36
|
if (!effort) return undefined;
|
|
34
|
-
if (!(
|
|
37
|
+
if (!(REASONING_EFFORTS as readonly string[]).includes(effort))
|
|
35
38
|
throw new Error(
|
|
36
|
-
`${envName} must be one of ${
|
|
39
|
+
`${sharedEffort ? envName : legacyName} must be one of ${REASONING_EFFORTS.join(", ")}; received "${effort}"`,
|
|
37
40
|
);
|
|
38
|
-
return effort as
|
|
41
|
+
return effort as ReasoningEffort;
|
|
39
42
|
};
|
|
40
43
|
const roleEfforts = {
|
|
41
|
-
routine:
|
|
42
|
-
strong:
|
|
44
|
+
routine: readRoleReasoningEffort("routine"),
|
|
45
|
+
strong: readRoleReasoningEffort("strong"),
|
|
43
46
|
};
|
|
44
47
|
const readCodexRoleModel = (role: ModelRole, defaultModel: AgentModel) => {
|
|
45
48
|
if (!CODEX_PROVIDER || typeof defaultModel === "string") return defaultModel;
|
|
@@ -136,6 +139,7 @@ for (let iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
|
|
|
136
139
|
title: string;
|
|
137
140
|
branch: string;
|
|
138
141
|
kind: "standalone" | "spec";
|
|
142
|
+
triageReady?: boolean;
|
|
139
143
|
body?: string;
|
|
140
144
|
tickets?: Array<{
|
|
141
145
|
id: string;
|
|
@@ -143,6 +147,7 @@ for (let iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
|
|
|
143
147
|
body: string;
|
|
144
148
|
state: string;
|
|
145
149
|
blockedBy: Array<{ id: string; title: string; state: string }>;
|
|
150
|
+
triageReady: boolean;
|
|
146
151
|
}>;
|
|
147
152
|
completedTicketIds?: string[];
|
|
148
153
|
outstandingTicketIds?: string[];
|
|
@@ -176,24 +181,33 @@ for (let iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
|
|
|
176
181
|
repository,
|
|
177
182
|
"--add-label",
|
|
178
183
|
"shipyard:pending",
|
|
184
|
+
"--remove-label",
|
|
185
|
+
"shipyard",
|
|
179
186
|
]);
|
|
180
187
|
const sandbox = await shipyard.createSandbox({
|
|
181
188
|
branch: issue.branch,
|
|
182
189
|
sandbox: sandboxProvider,
|
|
183
190
|
hooks,
|
|
191
|
+
copyToWorktree: [".shipyard/setup.sh"],
|
|
184
192
|
});
|
|
185
193
|
let evidence: string;
|
|
186
194
|
try {
|
|
187
195
|
for (const ticketId of issue.kind === "spec"
|
|
188
196
|
? (issue.tickets ?? []).map((ticket) => ticket.id)
|
|
189
197
|
: [issue.id]) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
198
|
+
const ready =
|
|
199
|
+
issue.kind === "spec"
|
|
200
|
+
? issue.tickets?.find((ticket) => ticket.id === ticketId)
|
|
201
|
+
?.triageReady
|
|
202
|
+
: issue.triageReady;
|
|
203
|
+
if (!ready)
|
|
204
|
+
await sandbox.run({
|
|
205
|
+
name: `triage #${ticketId}`,
|
|
206
|
+
agent: roleAgent("routine", shipyard.CODEX_MODELS.routine),
|
|
207
|
+
maxIterations: 1,
|
|
208
|
+
promptFile: "./.shipyard/triage-prompt.md",
|
|
209
|
+
promptArgs: { TASK_ID: ticketId, BASE_BRANCH: targetBranch },
|
|
210
|
+
});
|
|
197
211
|
verifyTriage(ticketId);
|
|
198
212
|
}
|
|
199
213
|
const implement = await sandbox.run({
|
|
@@ -237,7 +251,7 @@ for (let iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
|
|
|
237
251
|
`Issue #${issue.id} has unresolved review findings: ${review.stdout.trim().slice(-1200)}`,
|
|
238
252
|
);
|
|
239
253
|
}
|
|
240
|
-
evidence = `${implementationEvidence}\n\n${reviewEvidence}`;
|
|
254
|
+
evidence = `${implementationEvidence}\n\nReview: APPROVED\n${reviewEvidence}`;
|
|
241
255
|
} finally {
|
|
242
256
|
await closeClean(sandbox);
|
|
243
257
|
}
|
|
@@ -245,6 +259,7 @@ for (let iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
|
|
|
245
259
|
const publication = await shipyard.createSandbox({
|
|
246
260
|
branch: issue.branch,
|
|
247
261
|
sandbox: sandboxProvider,
|
|
262
|
+
copyToWorktree: [".shipyard/handoff.sh"],
|
|
248
263
|
});
|
|
249
264
|
try {
|
|
250
265
|
publicationUncertain = true;
|
|
@@ -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"
|
|
@@ -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"
|
|
@@ -22,24 +22,27 @@ const roleModels = {
|
|
|
22
22
|
routine: readRoleModel("routine"),
|
|
23
23
|
strong: readRoleModel("strong"),
|
|
24
24
|
};
|
|
25
|
-
const
|
|
26
|
-
type
|
|
27
|
-
const
|
|
25
|
+
const REASONING_EFFORTS = shipyard.REASONING_EFFORTS;
|
|
26
|
+
type ReasoningEffort = shipyard.ReasoningEffort;
|
|
27
|
+
const readRoleReasoningEffort = (
|
|
28
28
|
role: ModelRole,
|
|
29
|
-
):
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
const
|
|
29
|
+
): ReasoningEffort | undefined => {
|
|
30
|
+
const envName = `SHIPYARD_${role.toUpperCase()}_REASONING_EFFORT`;
|
|
31
|
+
const legacyName = `SHIPYARD_CODEX_${role.toUpperCase()}_REASONING_EFFORT`;
|
|
32
|
+
const sharedEffort = process.env[envName]?.trim();
|
|
33
|
+
const effort =
|
|
34
|
+
sharedEffort ||
|
|
35
|
+
(CODEX_PROVIDER ? process.env[legacyName]?.trim() : undefined);
|
|
33
36
|
if (!effort) return undefined;
|
|
34
|
-
if (!(
|
|
37
|
+
if (!(REASONING_EFFORTS as readonly string[]).includes(effort))
|
|
35
38
|
throw new Error(
|
|
36
|
-
`${envName} must be one of ${
|
|
39
|
+
`${sharedEffort ? envName : legacyName} must be one of ${REASONING_EFFORTS.join(", ")}; received "${effort}"`,
|
|
37
40
|
);
|
|
38
|
-
return effort as
|
|
41
|
+
return effort as ReasoningEffort;
|
|
39
42
|
};
|
|
40
43
|
const roleEfforts = {
|
|
41
|
-
routine:
|
|
42
|
-
strong:
|
|
44
|
+
routine: readRoleReasoningEffort("routine"),
|
|
45
|
+
strong: readRoleReasoningEffort("strong"),
|
|
43
46
|
};
|
|
44
47
|
const readCodexRoleModel = (role: ModelRole, defaultModel: AgentModel) => {
|
|
45
48
|
if (!CODEX_PROVIDER || typeof defaultModel === "string") return defaultModel;
|
|
@@ -133,6 +136,7 @@ for (let iteration = 0; iteration < 3; iteration++) {
|
|
|
133
136
|
title: string;
|
|
134
137
|
branch: string;
|
|
135
138
|
kind: "standalone" | "spec";
|
|
139
|
+
triageReady?: boolean;
|
|
136
140
|
body?: string;
|
|
137
141
|
tickets?: Array<{
|
|
138
142
|
id: string;
|
|
@@ -140,6 +144,7 @@ for (let iteration = 0; iteration < 3; iteration++) {
|
|
|
140
144
|
body: string;
|
|
141
145
|
state: string;
|
|
142
146
|
blockedBy: Array<{ id: string; title: string; state: string }>;
|
|
147
|
+
triageReady: boolean;
|
|
143
148
|
}>;
|
|
144
149
|
completedTicketIds?: string[];
|
|
145
150
|
outstandingTicketIds?: string[];
|
|
@@ -173,24 +178,33 @@ for (let iteration = 0; iteration < 3; iteration++) {
|
|
|
173
178
|
repository,
|
|
174
179
|
"--add-label",
|
|
175
180
|
"shipyard:pending",
|
|
181
|
+
"--remove-label",
|
|
182
|
+
"shipyard",
|
|
176
183
|
]);
|
|
177
184
|
const sandbox = await shipyard.createSandbox({
|
|
178
185
|
branch: issue.branch,
|
|
179
186
|
sandbox: sandboxProvider,
|
|
180
187
|
hooks,
|
|
188
|
+
copyToWorktree: [".shipyard/setup.sh"],
|
|
181
189
|
});
|
|
182
190
|
let evidence: string;
|
|
183
191
|
try {
|
|
184
192
|
for (const ticketId of issue.kind === "spec"
|
|
185
193
|
? (issue.tickets ?? []).map((ticket) => ticket.id)
|
|
186
194
|
: [issue.id]) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
195
|
+
const ready =
|
|
196
|
+
issue.kind === "spec"
|
|
197
|
+
? issue.tickets?.find((ticket) => ticket.id === ticketId)
|
|
198
|
+
?.triageReady
|
|
199
|
+
: issue.triageReady;
|
|
200
|
+
if (!ready)
|
|
201
|
+
await sandbox.run({
|
|
202
|
+
name: `triage #${ticketId}`,
|
|
203
|
+
agent: roleAgent("routine", shipyard.CODEX_MODELS.routine),
|
|
204
|
+
maxIterations: 1,
|
|
205
|
+
promptFile: "./.shipyard/triage-prompt.md",
|
|
206
|
+
promptArgs: { TASK_ID: ticketId, BASE_BRANCH: targetBranch },
|
|
207
|
+
});
|
|
194
208
|
verifyTriage(ticketId);
|
|
195
209
|
}
|
|
196
210
|
const result = await sandbox.run({
|
|
@@ -225,6 +239,7 @@ for (let iteration = 0; iteration < 3; iteration++) {
|
|
|
225
239
|
const publication = await shipyard.createSandbox({
|
|
226
240
|
branch: issue.branch,
|
|
227
241
|
sandbox: sandboxProvider,
|
|
242
|
+
copyToWorktree: [".shipyard/handoff.sh"],
|
|
228
243
|
});
|
|
229
244
|
try {
|
|
230
245
|
publicationUncertain = true;
|
|
@@ -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`.
|