@snappedly-tools/shipyard 0.10.0 → 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 +5 -4
- package/dist/main.js +1 -1
- package/dist/templates/parallel-planner/main.mts +18 -14
- package/dist/templates/parallel-planner/select-issues.mjs +20 -2
- package/dist/templates/parallel-planner/triage-prompt.md +2 -2
- package/dist/templates/parallel-planner/verify-triage.sh +12 -3
- package/dist/templates/parallel-planner-with-review/main.mts +18 -14
- package/dist/templates/parallel-planner-with-review/select-issues.mjs +20 -2
- package/dist/templates/parallel-planner-with-review/triage-prompt.md +2 -2
- package/dist/templates/parallel-planner-with-review/verify-triage.sh +12 -3
- package/dist/templates/sequential-reviewer/main.mts +15 -7
- package/dist/templates/sequential-reviewer/select-issues.mjs +20 -2
- package/dist/templates/sequential-reviewer/triage-prompt.md +2 -2
- package/dist/templates/sequential-reviewer/verify-triage.sh +12 -3
- package/dist/templates/shared/select-issues.mjs +20 -2
- package/dist/templates/shared/triage-prompt.md +2 -2
- package/dist/templates/shared/verify-triage.sh +12 -3
- package/dist/templates/simple-loop/main.mts +15 -7
- package/dist/templates/simple-loop/select-issues.mjs +20 -2
- package/dist/templates/simple-loop/triage-prompt.md +2 -2
- package/dist/templates/simple-loop/verify-triage.sh +12 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,10 +86,11 @@ npx shipyard uninstall
|
|
|
86
86
|
|
|
87
87
|
Then:
|
|
88
88
|
|
|
89
|
-
1. Write an issue with a clear goal
|
|
90
|
-
`ready-for-agent`
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
1. Write an issue with a clear goal. If it is already triaged and approved for
|
|
90
|
+
agent implementation, add `ready-for-agent`. Add `shipyard` last to activate
|
|
91
|
+
the issue.
|
|
92
|
+
2. Run Shipyard. It triages issues without `ready-for-agent`, verifies ready
|
|
93
|
+
issues, implements them in Docker, reviews the work, and opens a pull request.
|
|
93
94
|
3. Inspect the pull request and merge it when you are happy with the result.
|
|
94
95
|
|
|
95
96
|
## Choose a workflow
|
package/dist/main.js
CHANGED
|
@@ -2400,7 +2400,7 @@ var runnerCommand = Command.make(
|
|
|
2400
2400
|
purgeRunnerCommand
|
|
2401
2401
|
])
|
|
2402
2402
|
);
|
|
2403
|
-
var VERSION = "0.
|
|
2403
|
+
var VERSION = "0.11.0" ;
|
|
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) => {
|
|
@@ -115,12 +115,14 @@ type Ticket = {
|
|
|
115
115
|
body: string;
|
|
116
116
|
state: string;
|
|
117
117
|
blockedBy: Array<{ id: string; title: string; state: string }>;
|
|
118
|
+
triageReady: boolean;
|
|
118
119
|
};
|
|
119
120
|
type Scope = {
|
|
120
121
|
id: string;
|
|
121
122
|
title: string;
|
|
122
123
|
branch: string;
|
|
123
124
|
kind: "standalone" | "spec";
|
|
125
|
+
triageReady?: boolean;
|
|
124
126
|
body?: string;
|
|
125
127
|
tickets?: Ticket[];
|
|
126
128
|
completedTicketIds?: string[];
|
|
@@ -172,13 +174,14 @@ const runWorker = async (scope: Scope, ticket: Ticket) => {
|
|
|
172
174
|
copyToWorktree: [".shipyard/setup.sh"],
|
|
173
175
|
});
|
|
174
176
|
try {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
+
});
|
|
182
185
|
verifyTriage(ticket.id);
|
|
183
186
|
const implementation = await sandbox.run({
|
|
184
187
|
name: "implementer",
|
|
@@ -444,13 +447,14 @@ for (let iteration = 0; iteration < 10; iteration++) {
|
|
|
444
447
|
let handoffEvidence: string;
|
|
445
448
|
try {
|
|
446
449
|
if (scope.kind === "standalone") {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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
|
+
});
|
|
454
458
|
verifyTriage(id);
|
|
455
459
|
const standalone = await integration.run({
|
|
456
460
|
name: "implementer",
|
|
@@ -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;
|
|
@@ -2,6 +2,6 @@ Triage GitHub issue #{{TASK_ID}} before Shipyard implementation.
|
|
|
2
2
|
|
|
3
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
|
-
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.
|
|
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
6
|
|
|
7
|
-
Report the applied state and evidence. Shipyard will independently re-read GitHub labels and implement only an open issue carrying
|
|
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
|
'
|
|
@@ -115,12 +115,14 @@ type Ticket = {
|
|
|
115
115
|
body: string;
|
|
116
116
|
state: string;
|
|
117
117
|
blockedBy: Array<{ id: string; title: string; state: string }>;
|
|
118
|
+
triageReady: boolean;
|
|
118
119
|
};
|
|
119
120
|
type Scope = {
|
|
120
121
|
id: string;
|
|
121
122
|
title: string;
|
|
122
123
|
branch: string;
|
|
123
124
|
kind: "standalone" | "spec";
|
|
125
|
+
triageReady?: boolean;
|
|
124
126
|
body?: string;
|
|
125
127
|
tickets?: Ticket[];
|
|
126
128
|
completedTicketIds?: string[];
|
|
@@ -182,13 +184,14 @@ const runWorker = async (scope: Scope, ticket: Ticket) => {
|
|
|
182
184
|
copyToWorktree: [".shipyard/setup.sh"],
|
|
183
185
|
});
|
|
184
186
|
try {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
+
});
|
|
192
195
|
verifyTriage(ticket.id);
|
|
193
196
|
const implementation = await sandbox.run({
|
|
194
197
|
name: "implementer",
|
|
@@ -467,13 +470,14 @@ for (let iteration = 0; iteration < 10; iteration++) {
|
|
|
467
470
|
let handoffEvidence: string;
|
|
468
471
|
try {
|
|
469
472
|
if (scope.kind === "standalone") {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
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
|
+
});
|
|
477
481
|
verifyTriage(id);
|
|
478
482
|
const standalone = await integration.run({
|
|
479
483
|
name: "implementer",
|
|
@@ -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;
|
|
@@ -2,6 +2,6 @@ Triage GitHub issue #{{TASK_ID}} before Shipyard implementation.
|
|
|
2
2
|
|
|
3
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
|
-
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.
|
|
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
6
|
|
|
7
|
-
Report the applied state and evidence. Shipyard will independently re-read GitHub labels and implement only an open issue carrying
|
|
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
|
'
|
|
@@ -139,6 +139,7 @@ for (let iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
|
|
|
139
139
|
title: string;
|
|
140
140
|
branch: string;
|
|
141
141
|
kind: "standalone" | "spec";
|
|
142
|
+
triageReady?: boolean;
|
|
142
143
|
body?: string;
|
|
143
144
|
tickets?: Array<{
|
|
144
145
|
id: string;
|
|
@@ -146,6 +147,7 @@ for (let iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
|
|
|
146
147
|
body: string;
|
|
147
148
|
state: string;
|
|
148
149
|
blockedBy: Array<{ id: string; title: string; state: string }>;
|
|
150
|
+
triageReady: boolean;
|
|
149
151
|
}>;
|
|
150
152
|
completedTicketIds?: string[];
|
|
151
153
|
outstandingTicketIds?: string[];
|
|
@@ -193,13 +195,19 @@ for (let iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
|
|
|
193
195
|
for (const ticketId of issue.kind === "spec"
|
|
194
196
|
? (issue.tickets ?? []).map((ticket) => ticket.id)
|
|
195
197
|
: [issue.id]) {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
+
});
|
|
203
211
|
verifyTriage(ticketId);
|
|
204
212
|
}
|
|
205
213
|
const implement = await sandbox.run({
|
|
@@ -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;
|
|
@@ -2,6 +2,6 @@ Triage GitHub issue #{{TASK_ID}} before Shipyard implementation.
|
|
|
2
2
|
|
|
3
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
|
-
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.
|
|
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
6
|
|
|
7
|
-
Report the applied state and evidence. Shipyard will independently re-read GitHub labels and implement only an open issue carrying
|
|
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
|
'
|
|
@@ -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;
|
|
@@ -2,6 +2,6 @@ Triage GitHub issue #{{TASK_ID}} before Shipyard implementation.
|
|
|
2
2
|
|
|
3
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
|
-
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.
|
|
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
6
|
|
|
7
|
-
Report the applied state and evidence. Shipyard will independently re-read GitHub labels and implement only an open issue carrying
|
|
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
|
'
|
|
@@ -136,6 +136,7 @@ for (let iteration = 0; iteration < 3; iteration++) {
|
|
|
136
136
|
title: string;
|
|
137
137
|
branch: string;
|
|
138
138
|
kind: "standalone" | "spec";
|
|
139
|
+
triageReady?: boolean;
|
|
139
140
|
body?: string;
|
|
140
141
|
tickets?: Array<{
|
|
141
142
|
id: string;
|
|
@@ -143,6 +144,7 @@ for (let iteration = 0; iteration < 3; iteration++) {
|
|
|
143
144
|
body: string;
|
|
144
145
|
state: string;
|
|
145
146
|
blockedBy: Array<{ id: string; title: string; state: string }>;
|
|
147
|
+
triageReady: boolean;
|
|
146
148
|
}>;
|
|
147
149
|
completedTicketIds?: string[];
|
|
148
150
|
outstandingTicketIds?: string[];
|
|
@@ -190,13 +192,19 @@ for (let iteration = 0; iteration < 3; iteration++) {
|
|
|
190
192
|
for (const ticketId of issue.kind === "spec"
|
|
191
193
|
? (issue.tickets ?? []).map((ticket) => ticket.id)
|
|
192
194
|
: [issue.id]) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
+
});
|
|
200
208
|
verifyTriage(ticketId);
|
|
201
209
|
}
|
|
202
210
|
const result = await sandbox.run({
|
|
@@ -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;
|
|
@@ -2,6 +2,6 @@ Triage GitHub issue #{{TASK_ID}} before Shipyard implementation.
|
|
|
2
2
|
|
|
3
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
|
-
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.
|
|
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
6
|
|
|
7
|
-
Report the applied state and evidence. Shipyard will independently re-read GitHub labels and implement only an open issue carrying
|
|
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
|
'
|