@snappedly-tools/shipyard 0.5.0 → 0.6.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 +56 -8
- package/dist/index.js +27 -9
- package/dist/index.js.map +1 -1
- package/dist/main.js +51 -15
- package/dist/main.js.map +1 -1
- package/dist/templates/parallel-planner/block-scope.sh +160 -0
- package/dist/templates/parallel-planner/conflict-prompt.md +22 -0
- package/dist/templates/parallel-planner/handoff.sh +175 -0
- package/dist/templates/parallel-planner/implement-prompt.md +10 -56
- package/dist/templates/parallel-planner/main.mts +434 -188
- package/dist/templates/parallel-planner/merge-prompt.md +9 -21
- package/dist/templates/parallel-planner/plan-prompt.md +5 -31
- package/dist/templates/parallel-planner/select-issues.mjs +788 -0
- package/dist/templates/parallel-planner/setup.sh +51 -0
- package/dist/templates/parallel-planner/spec-wave-prompt.md +16 -0
- package/dist/templates/parallel-planner/template.json +1 -1
- package/dist/templates/parallel-planner/triage-prompt.md +5 -0
- package/dist/templates/parallel-planner/verify-triage.sh +19 -0
- package/dist/templates/parallel-planner-with-review/block-scope.sh +160 -0
- package/dist/templates/parallel-planner-with-review/conflict-prompt.md +22 -0
- package/dist/templates/parallel-planner-with-review/handoff.sh +175 -0
- package/dist/templates/parallel-planner-with-review/implement-prompt.md +10 -56
- package/dist/templates/parallel-planner-with-review/main.mts +473 -205
- package/dist/templates/parallel-planner-with-review/merge-prompt.md +9 -21
- package/dist/templates/parallel-planner-with-review/plan-prompt.md +5 -31
- package/dist/templates/parallel-planner-with-review/review-prompt.md +11 -49
- package/dist/templates/parallel-planner-with-review/select-issues.mjs +788 -0
- package/dist/templates/parallel-planner-with-review/setup.sh +51 -0
- package/dist/templates/parallel-planner-with-review/spec-wave-prompt.md +16 -0
- package/dist/templates/parallel-planner-with-review/template.json +1 -1
- package/dist/templates/parallel-planner-with-review/triage-prompt.md +5 -0
- package/dist/templates/parallel-planner-with-review/verify-triage.sh +19 -0
- package/dist/templates/sequential-reviewer/block-scope.sh +160 -0
- package/dist/templates/sequential-reviewer/handoff.sh +175 -0
- package/dist/templates/sequential-reviewer/implement-prompt.md +12 -46
- package/dist/templates/sequential-reviewer/main.mts +199 -103
- package/dist/templates/sequential-reviewer/review-prompt.md +11 -49
- package/dist/templates/sequential-reviewer/select-issues.mjs +788 -0
- package/dist/templates/sequential-reviewer/setup.sh +51 -0
- package/dist/templates/sequential-reviewer/template.json +1 -1
- package/dist/templates/sequential-reviewer/triage-prompt.md +5 -0
- package/dist/templates/sequential-reviewer/verify-triage.sh +19 -0
- package/dist/templates/shared/block-scope.sh +160 -0
- package/dist/templates/shared/handoff.sh +175 -0
- package/dist/templates/shared/select-issues.mjs +788 -0
- package/dist/templates/shared/setup.sh +51 -0
- package/dist/templates/shared/triage-prompt.md +5 -0
- package/dist/templates/shared/verify-triage.sh +19 -0
- package/dist/templates/simple-loop/block-scope.sh +160 -0
- package/dist/templates/simple-loop/handoff.sh +175 -0
- package/dist/templates/simple-loop/main.mts +190 -45
- package/dist/templates/simple-loop/prompt.md +12 -46
- package/dist/templates/simple-loop/select-issues.mjs +788 -0
- package/dist/templates/simple-loop/setup.sh +51 -0
- package/dist/templates/simple-loop/template.json +1 -1
- package/dist/templates/simple-loop/triage-prompt.md +5 -0
- package/dist/templates/simple-loop/verify-triage.sh +19 -0
- package/package.json +1 -1
|
@@ -1,120 +1,216 @@
|
|
|
1
|
-
// Sequential Reviewer
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
// on a dedicated branch, commits the changes, and signals
|
|
6
|
-
// completion.
|
|
7
|
-
// Phase 2 (Review): A second Codex agent reviews the branch diff and either
|
|
8
|
-
// approves it or makes corrections directly on the branch.
|
|
9
|
-
//
|
|
10
|
-
// Both phases share a single sandbox created via createSandbox(), so the
|
|
11
|
-
// implementer and reviewer work on the same explicit branch.
|
|
12
|
-
//
|
|
13
|
-
// The outer loop repeats up to MAX_ITERATIONS times, processing one issue per
|
|
14
|
-
// iteration and stopping early once the backlog is exhausted (an implement
|
|
15
|
-
// phase that produces no commits). This is a middle-complexity option between
|
|
16
|
-
// the simple-loop (no review gate) and the parallel-planner (concurrent
|
|
17
|
-
// execution with a planning phase).
|
|
18
|
-
// Generated entrypoint: .shipyard/main.mts
|
|
19
|
-
//
|
|
20
|
-
// Usage:
|
|
21
|
-
// npx shipyard run
|
|
22
|
-
// Or add to package.json:
|
|
23
|
-
// "scripts": { "shipyard": "shipyard run" }
|
|
24
|
-
|
|
1
|
+
// Sequential Reviewer: one implementation and one independent review per
|
|
2
|
+
// activated issue scope. Publication follows successful review.
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
25
5
|
import * as shipyard from "@snappedly-tools/shipyard";
|
|
26
6
|
import { docker } from "@snappedly-tools/shipyard/sandboxes/docker";
|
|
27
7
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
8
|
+
if (process.loadEnvFile && existsSync(".shipyard/.env"))
|
|
9
|
+
process.loadEnvFile(".shipyard/.env");
|
|
10
|
+
const targetBranch = execFileSync("git", ["branch", "--show-current"], {
|
|
11
|
+
encoding: "utf8",
|
|
12
|
+
}).trim();
|
|
13
|
+
const repository = execFileSync(
|
|
14
|
+
"gh",
|
|
15
|
+
["repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner"],
|
|
16
|
+
{ encoding: "utf8" },
|
|
17
|
+
).trim();
|
|
18
|
+
if (
|
|
19
|
+
!/^[A-Za-z0-9._/-]+$/.test(targetBranch) ||
|
|
20
|
+
!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repository)
|
|
21
|
+
) {
|
|
22
|
+
throw new Error("Invalid target branch or GitHub repository");
|
|
23
|
+
}
|
|
24
|
+
process.env.GH_REPO = repository;
|
|
34
25
|
const MAX_ITERATIONS = 10;
|
|
35
|
-
|
|
36
|
-
// Hooks run inside the sandbox before the agent starts each iteration.
|
|
37
|
-
// npm install ensures the sandbox always has fresh dependencies.
|
|
38
26
|
const hooks = {
|
|
39
|
-
sandbox: {
|
|
27
|
+
sandbox: {
|
|
28
|
+
onSandboxReady: [
|
|
29
|
+
{ command: "timeout 300 bash .shipyard/setup.sh", timeoutMs: 300_000 },
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
const closeClean = async (sandbox: {
|
|
34
|
+
close: () => Promise<{ preservedWorktreePath?: string }>;
|
|
35
|
+
}) => {
|
|
36
|
+
const { preservedWorktreePath } = await sandbox.close();
|
|
37
|
+
if (preservedWorktreePath)
|
|
38
|
+
throw new Error(`Sandbox has uncommitted work at ${preservedWorktreePath}`);
|
|
39
|
+
};
|
|
40
|
+
const verifyTriage = (ticketId: string) => {
|
|
41
|
+
try {
|
|
42
|
+
execFileSync("bash", [".shipyard/verify-triage.sh", ticketId, repository], {
|
|
43
|
+
encoding: "utf8",
|
|
44
|
+
});
|
|
45
|
+
} catch (error) {
|
|
46
|
+
const detail = (error as { stderr?: string | Buffer }).stderr
|
|
47
|
+
?.toString()
|
|
48
|
+
.trim();
|
|
49
|
+
throw new Error(detail || `Could not verify triage for #${ticketId}`);
|
|
50
|
+
}
|
|
40
51
|
};
|
|
41
52
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
const handoffEvidence = (stdout: string): string | undefined =>
|
|
54
|
+
[...stdout.matchAll(/<handoff>([\s\S]*?)<\/handoff>/g)].at(-1)?.[1]?.trim();
|
|
55
|
+
const blockScope = (
|
|
56
|
+
scope: { id: string; branch: string; tickets?: Array<{ id: string }> },
|
|
57
|
+
error: unknown,
|
|
58
|
+
) => {
|
|
59
|
+
const reason = (error instanceof Error ? error.message : String(error)).slice(
|
|
60
|
+
0,
|
|
61
|
+
3000,
|
|
62
|
+
);
|
|
63
|
+
execFileSync(
|
|
64
|
+
"bash",
|
|
65
|
+
[
|
|
66
|
+
".shipyard/block-scope.sh",
|
|
67
|
+
scope.id,
|
|
68
|
+
scope.id,
|
|
69
|
+
repository,
|
|
70
|
+
[scope.id, ...(scope.tickets ?? []).map((ticket) => ticket.id)].join(","),
|
|
71
|
+
scope.branch,
|
|
72
|
+
],
|
|
73
|
+
{ input: reason, encoding: "utf8" },
|
|
74
|
+
);
|
|
75
|
+
console.error(`Shipyard blocked issue #${scope.id}: ${reason}`);
|
|
76
|
+
};
|
|
56
77
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
78
|
+
for (let iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
|
|
79
|
+
const issues = JSON.parse(
|
|
80
|
+
execFileSync("node", [".shipyard/select-issues.mjs"], { encoding: "utf8" }),
|
|
81
|
+
) as Array<{
|
|
82
|
+
id: string;
|
|
83
|
+
title: string;
|
|
84
|
+
branch: string;
|
|
85
|
+
kind: "standalone" | "spec";
|
|
86
|
+
body?: string;
|
|
87
|
+
tickets?: Array<{
|
|
88
|
+
id: string;
|
|
89
|
+
title: string;
|
|
90
|
+
body: string;
|
|
91
|
+
state: string;
|
|
92
|
+
blockedBy: Array<{ id: string; title: string; state: string }>;
|
|
93
|
+
}>;
|
|
94
|
+
completedTicketIds?: string[];
|
|
95
|
+
outstandingTicketIds?: string[];
|
|
96
|
+
}>;
|
|
97
|
+
const issue = issues[0];
|
|
98
|
+
if (!issue) break;
|
|
65
99
|
|
|
100
|
+
let handedOff = false;
|
|
101
|
+
let publicationUncertain = false;
|
|
66
102
|
try {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
103
|
+
execFileSync("gh", [
|
|
104
|
+
"label",
|
|
105
|
+
"create",
|
|
106
|
+
"shipyard:pending",
|
|
107
|
+
"--repo",
|
|
108
|
+
repository,
|
|
109
|
+
"--color",
|
|
110
|
+
"1D76DB",
|
|
111
|
+
"--description",
|
|
112
|
+
"Shipyard is working on this ticket",
|
|
113
|
+
"--force",
|
|
114
|
+
]);
|
|
115
|
+
for (const ticketId of issue.kind === "spec"
|
|
116
|
+
? (issue.tickets ?? []).map((ticket) => ticket.id)
|
|
117
|
+
: [issue.id])
|
|
118
|
+
execFileSync("gh", [
|
|
119
|
+
"issue",
|
|
120
|
+
"edit",
|
|
121
|
+
ticketId,
|
|
122
|
+
"--repo",
|
|
123
|
+
repository,
|
|
124
|
+
"--add-label",
|
|
125
|
+
"shipyard:pending",
|
|
126
|
+
]);
|
|
127
|
+
const sandbox = await shipyard.createSandbox({
|
|
128
|
+
branch: issue.branch,
|
|
129
|
+
sandbox: docker(),
|
|
130
|
+
hooks,
|
|
85
131
|
});
|
|
132
|
+
let evidence: string;
|
|
133
|
+
try {
|
|
134
|
+
for (const ticketId of issue.kind === "spec"
|
|
135
|
+
? (issue.tickets ?? []).map((ticket) => ticket.id)
|
|
136
|
+
: [issue.id]) {
|
|
137
|
+
await sandbox.run({
|
|
138
|
+
name: `triage #${ticketId}`,
|
|
139
|
+
agent: shipyard.codex(shipyard.CODEX_MODELS.strong),
|
|
140
|
+
maxIterations: 1,
|
|
141
|
+
promptFile: "./.shipyard/triage-prompt.md",
|
|
142
|
+
promptArgs: { TASK_ID: ticketId },
|
|
143
|
+
});
|
|
144
|
+
verifyTriage(ticketId);
|
|
145
|
+
}
|
|
146
|
+
const implement = await sandbox.run({
|
|
147
|
+
name: "implementer",
|
|
148
|
+
maxIterations: 1,
|
|
149
|
+
agent: shipyard.codex(shipyard.CODEX_MODELS.routine),
|
|
150
|
+
promptFile: "./.shipyard/implement-prompt.md",
|
|
151
|
+
promptArgs: {
|
|
152
|
+
TASK_ID: issue.id,
|
|
153
|
+
ISSUE_TITLE: issue.title,
|
|
154
|
+
BRANCH: issue.branch,
|
|
155
|
+
SCOPE: JSON.stringify(issue),
|
|
156
|
+
SKILL: issue.kind === "spec" ? "/implement-spec" : "/implement",
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
const implementationEvidence = handoffEvidence(implement.stdout);
|
|
160
|
+
if (!implement.completionSignal || !implementationEvidence)
|
|
161
|
+
throw new Error(
|
|
162
|
+
`Issue #${issue.id} has no verified implementation evidence: ${implement.stdout.trim().slice(-1200)}`,
|
|
163
|
+
);
|
|
86
164
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
165
|
+
const review = await sandbox.run({
|
|
166
|
+
name: "reviewer",
|
|
167
|
+
maxIterations: 1,
|
|
168
|
+
agent: shipyard.codex(shipyard.CODEX_MODELS.strong),
|
|
169
|
+
promptFile: "./.shipyard/review-prompt.md",
|
|
170
|
+
promptArgs: {
|
|
171
|
+
BRANCH: issue.branch,
|
|
172
|
+
SCOPE: JSON.stringify(issue),
|
|
173
|
+
SKILL: issue.kind === "spec" ? "/implement-spec" : "/implement",
|
|
174
|
+
TASK_ID: issue.id,
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
const reviewEvidence = handoffEvidence(review.stdout);
|
|
178
|
+
if (
|
|
179
|
+
!review.completionSignal ||
|
|
180
|
+
!review.stdout.includes("<review>APPROVED</review>") ||
|
|
181
|
+
!reviewEvidence
|
|
182
|
+
) {
|
|
183
|
+
throw new Error(
|
|
184
|
+
`Issue #${issue.id} has unresolved review findings: ${review.stdout.trim().slice(-1200)}`,
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
evidence = `${implementationEvidence}\n\n${reviewEvidence}`;
|
|
188
|
+
} finally {
|
|
189
|
+
await closeClean(sandbox);
|
|
92
190
|
}
|
|
93
191
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
// -----------------------------------------------------------------------
|
|
98
|
-
// Phase 2: Review
|
|
99
|
-
//
|
|
100
|
-
// A second Codex agent reviews the diff of the branch produced by
|
|
101
|
-
// Phase 1. It uses the {{BRANCH}} prompt argument to inspect the right
|
|
102
|
-
// branch, and either approves or makes corrections directly on the branch.
|
|
103
|
-
// -----------------------------------------------------------------------
|
|
104
|
-
await sandbox.run({
|
|
105
|
-
name: "reviewer",
|
|
106
|
-
maxIterations: 1,
|
|
107
|
-
agent: shipyard.codex(shipyard.CODEX_MODELS.strong),
|
|
108
|
-
promptFile: "./.shipyard/review-prompt.md",
|
|
109
|
-
promptArgs: {
|
|
110
|
-
BRANCH: branch,
|
|
111
|
-
},
|
|
192
|
+
const publication = await shipyard.createSandbox({
|
|
193
|
+
branch: issue.branch,
|
|
194
|
+
sandbox: docker(),
|
|
112
195
|
});
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
196
|
+
try {
|
|
197
|
+
publicationUncertain = true;
|
|
198
|
+
const handoff = await publication.exec(
|
|
199
|
+
`bash .shipyard/handoff.sh ${issue.id} ${issue.branch} ${targetBranch} ${repository} ${[issue.id, ...(issue.tickets ?? []).map((ticket) => ticket.id)].join(",")} ${issue.outstandingTicketIds?.join(",") || "-"} ${issue.completedTicketIds?.join(",") || "-"}`,
|
|
200
|
+
{ stdin: evidence },
|
|
201
|
+
);
|
|
202
|
+
publicationUncertain = handoff.exitCode === 75;
|
|
203
|
+
if (handoff.exitCode !== 0)
|
|
204
|
+
throw new Error(
|
|
205
|
+
`PR handoff for #${issue.id} failed: ${handoff.stderr || handoff.stdout}`,
|
|
206
|
+
);
|
|
207
|
+
console.log(handoff.stdout.trim());
|
|
208
|
+
handedOff = true;
|
|
209
|
+
} finally {
|
|
210
|
+
await publication.close();
|
|
211
|
+
}
|
|
212
|
+
} catch (error) {
|
|
213
|
+
if (handedOff || publicationUncertain) throw error;
|
|
214
|
+
blockScope(issue, error);
|
|
117
215
|
}
|
|
118
216
|
}
|
|
119
|
-
|
|
120
|
-
console.log("\nAll done.");
|
|
@@ -1,55 +1,17 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Independent review of issue #{{TASK_ID}}
|
|
2
2
|
|
|
3
|
-
Review the
|
|
3
|
+
Review the complete scope on branch `{{BRANCH}}` against `{{TARGET_BRANCH}}`, the issue requirements, repository policy, and standards in `.shipyard/CODING_STANDARDS.md`. Follow `/code-review`. Inspect the exact diff, tests, security and correctness; use existing check evidence where valid. This template deliberately has a separate reviewer stage.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Resolve findings on this branch when authorized, run affected checks, and commit fixes. Do not close the issue, create a PR, merge to the target, or start another review chain. If any required finding remains open or checks are missing, report it and omit approval.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Only when review permits handoff, summarize the axis result, findings and dispositions, changed checks, and limitations:
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
<handoff>...</handoff>
|
|
10
|
+
<review>APPROVED</review>
|
|
11
|
+
<promise>COMPLETE</promise>
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
For a planning spec, review the integrated parent and all scoped children together. Read all issue bodies and comments. Resolved scope:
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
1. **Understand the change**: Read the diff and commits above to understand the intent.
|
|
18
|
-
|
|
19
|
-
2. **Analyze for improvements**: Look for opportunities to:
|
|
20
|
-
- Reduce unnecessary complexity and nesting
|
|
21
|
-
- Eliminate redundant code and abstractions
|
|
22
|
-
- Improve readability through clear variable and function names
|
|
23
|
-
- Consolidate related logic
|
|
24
|
-
- Remove unnecessary comments that describe obvious code
|
|
25
|
-
- Avoid nested ternary operators - prefer switch statements or if/else chains
|
|
26
|
-
- Choose clarity over brevity - explicit code is often better than overly compact code
|
|
27
|
-
|
|
28
|
-
3. **Check correctness**:
|
|
29
|
-
- Does the implementation match the intent? Are edge cases handled?
|
|
30
|
-
- Are new/changed behaviours covered by tests?
|
|
31
|
-
- Are there unsafe casts, `any` types, or unchecked assumptions?
|
|
32
|
-
- Does the change introduce injection vulnerabilities, credential leaks, or other security issues?
|
|
33
|
-
|
|
34
|
-
4. **Maintain balance**: Avoid over-simplification that could:
|
|
35
|
-
- Reduce code clarity or maintainability
|
|
36
|
-
- Create overly clever solutions that are hard to understand
|
|
37
|
-
- Combine too many concerns into single functions or components
|
|
38
|
-
- Remove helpful abstractions that improve code organization
|
|
39
|
-
- Make the code harder to debug or extend
|
|
40
|
-
|
|
41
|
-
5. **Apply project standards**: Follow the coding standards defined in @.shipyard/CODING_STANDARDS.md
|
|
42
|
-
|
|
43
|
-
6. **Preserve functionality**: Never change what the code does - only how it does it. All original features, outputs, and behaviors must remain intact.
|
|
44
|
-
|
|
45
|
-
# EXECUTION
|
|
46
|
-
|
|
47
|
-
If you find improvements to make:
|
|
48
|
-
|
|
49
|
-
1. Make the changes directly on this branch
|
|
50
|
-
2. Run tests and type checking to ensure nothing is broken
|
|
51
|
-
3. Commit describing the refinements
|
|
52
|
-
|
|
53
|
-
If the code is already clean and well-structured, do nothing.
|
|
54
|
-
|
|
55
|
-
Once complete, output <promise>COMPLETE</promise>.
|
|
15
|
+
```json
|
|
16
|
+
{{SCOPE}}
|
|
17
|
+
```
|