@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.
Files changed (58) hide show
  1. package/README.md +56 -8
  2. package/dist/index.js +27 -9
  3. package/dist/index.js.map +1 -1
  4. package/dist/main.js +51 -15
  5. package/dist/main.js.map +1 -1
  6. package/dist/templates/parallel-planner/block-scope.sh +160 -0
  7. package/dist/templates/parallel-planner/conflict-prompt.md +22 -0
  8. package/dist/templates/parallel-planner/handoff.sh +175 -0
  9. package/dist/templates/parallel-planner/implement-prompt.md +10 -56
  10. package/dist/templates/parallel-planner/main.mts +434 -188
  11. package/dist/templates/parallel-planner/merge-prompt.md +9 -21
  12. package/dist/templates/parallel-planner/plan-prompt.md +5 -31
  13. package/dist/templates/parallel-planner/select-issues.mjs +788 -0
  14. package/dist/templates/parallel-planner/setup.sh +51 -0
  15. package/dist/templates/parallel-planner/spec-wave-prompt.md +16 -0
  16. package/dist/templates/parallel-planner/template.json +1 -1
  17. package/dist/templates/parallel-planner/triage-prompt.md +5 -0
  18. package/dist/templates/parallel-planner/verify-triage.sh +19 -0
  19. package/dist/templates/parallel-planner-with-review/block-scope.sh +160 -0
  20. package/dist/templates/parallel-planner-with-review/conflict-prompt.md +22 -0
  21. package/dist/templates/parallel-planner-with-review/handoff.sh +175 -0
  22. package/dist/templates/parallel-planner-with-review/implement-prompt.md +10 -56
  23. package/dist/templates/parallel-planner-with-review/main.mts +473 -205
  24. package/dist/templates/parallel-planner-with-review/merge-prompt.md +9 -21
  25. package/dist/templates/parallel-planner-with-review/plan-prompt.md +5 -31
  26. package/dist/templates/parallel-planner-with-review/review-prompt.md +11 -49
  27. package/dist/templates/parallel-planner-with-review/select-issues.mjs +788 -0
  28. package/dist/templates/parallel-planner-with-review/setup.sh +51 -0
  29. package/dist/templates/parallel-planner-with-review/spec-wave-prompt.md +16 -0
  30. package/dist/templates/parallel-planner-with-review/template.json +1 -1
  31. package/dist/templates/parallel-planner-with-review/triage-prompt.md +5 -0
  32. package/dist/templates/parallel-planner-with-review/verify-triage.sh +19 -0
  33. package/dist/templates/sequential-reviewer/block-scope.sh +160 -0
  34. package/dist/templates/sequential-reviewer/handoff.sh +175 -0
  35. package/dist/templates/sequential-reviewer/implement-prompt.md +12 -46
  36. package/dist/templates/sequential-reviewer/main.mts +199 -103
  37. package/dist/templates/sequential-reviewer/review-prompt.md +11 -49
  38. package/dist/templates/sequential-reviewer/select-issues.mjs +788 -0
  39. package/dist/templates/sequential-reviewer/setup.sh +51 -0
  40. package/dist/templates/sequential-reviewer/template.json +1 -1
  41. package/dist/templates/sequential-reviewer/triage-prompt.md +5 -0
  42. package/dist/templates/sequential-reviewer/verify-triage.sh +19 -0
  43. package/dist/templates/shared/block-scope.sh +160 -0
  44. package/dist/templates/shared/handoff.sh +175 -0
  45. package/dist/templates/shared/select-issues.mjs +788 -0
  46. package/dist/templates/shared/setup.sh +51 -0
  47. package/dist/templates/shared/triage-prompt.md +5 -0
  48. package/dist/templates/shared/verify-triage.sh +19 -0
  49. package/dist/templates/simple-loop/block-scope.sh +160 -0
  50. package/dist/templates/simple-loop/handoff.sh +175 -0
  51. package/dist/templates/simple-loop/main.mts +190 -45
  52. package/dist/templates/simple-loop/prompt.md +12 -46
  53. package/dist/templates/simple-loop/select-issues.mjs +788 -0
  54. package/dist/templates/simple-loop/setup.sh +51 -0
  55. package/dist/templates/simple-loop/template.json +1 -1
  56. package/dist/templates/simple-loop/triage-prompt.md +5 -0
  57. package/dist/templates/simple-loop/verify-triage.sh +19 -0
  58. package/package.json +1 -1
@@ -1,120 +1,216 @@
1
- // Sequential Reviewer — implement-then-review loop
2
- //
3
- // This template drives a two-phase workflow per issue:
4
- // Phase 1 (Implement): A Codex agent picks an open issue, works on it
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
- // Configuration
30
- // ---------------------------------------------------------------------------
31
-
32
- // Maximum number of implement→review cycles to run before stopping.
33
- // Each cycle works on one issue. Raise this to process more issues per run.
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: { onSandboxReady: [{ command: "npm install" }] },
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
- // Copy node_modules from the host into the worktree before each sandbox
43
- // starts. Avoids a full npm install from scratch; the hook above handles
44
- // platform-specific binaries and any packages added since the last copy.
45
- const copyToWorktree = ["node_modules"];
46
-
47
- // ---------------------------------------------------------------------------
48
- // Main loop
49
- // ---------------------------------------------------------------------------
50
-
51
- for (let iteration = 1; iteration <= MAX_ITERATIONS; iteration++) {
52
- console.log(`\n=== Iteration ${iteration}/${MAX_ITERATIONS} ===\n`);
53
-
54
- // Generate a unique branch name for this iteration.
55
- const branch = `shipyard/sequential-reviewer/${Date.now()}`;
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
- // Create a single sandbox that both the implementer and reviewer share.
58
- // This gives both agents a real, named branch that persists across phases.
59
- const sandbox = await shipyard.createSandbox({
60
- branch,
61
- sandbox: docker(),
62
- hooks,
63
- copyToWorktree,
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
- // Phase 1: Implement
69
- //
70
- // A Codex agent picks the next open issue, writes the
71
- // implementation (using RGR: Red → Green → Repeat → Refactor), and
72
- // commits the result.
73
- //
74
- // The agent signals completion via <promise>COMPLETE</promise> when done.
75
- // -----------------------------------------------------------------------
76
- // One iteration so each outer pass implements a single issue on its own
77
- // branch, then hands it to the reviewer. A higher value lets the agent
78
- // drain the whole backlog onto this one branch in a single pass, which
79
- // defeats the per-issue review.
80
- const implement = await sandbox.run({
81
- name: "implementer",
82
- maxIterations: 1,
83
- agent: shipyard.codex(shipyard.CODEX_MODELS.routine),
84
- promptFile: "./.shipyard/implement-prompt.md",
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
- if (!implement.commits.length) {
88
- // No commits means the backlog is empty or every remaining issue is
89
- // blocked — there is nothing left to implement or review, so stop.
90
- console.log("Implementation agent made no commits. Stopping.");
91
- break;
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
- console.log(`\nImplementation complete on branch: ${branch}`);
95
- console.log(`Commits: ${implement.commits.length}`);
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
- console.log("\nReview complete.");
115
- } finally {
116
- await sandbox.close();
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
- # TASK
1
+ # Independent review of issue #{{TASK_ID}}
2
2
 
3
- Review the code changes on branch `{{BRANCH}}` and improve code clarity, consistency, and maintainability while preserving exact functionality.
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
- # CONTEXT
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
- ## Branch diff
7
+ Only when review permits handoff, summarize the axis result, findings and dispositions, changed checks, and limitations:
8
8
 
9
- !`git diff {{TARGET_BRANCH}}...{{BRANCH}}`
9
+ <handoff>...</handoff>
10
+ <review>APPROVED</review>
11
+ <promise>COMPLETE</promise>
10
12
 
11
- ## Commits on this branch
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
- !`git log {{TARGET_BRANCH}}..{{BRANCH}} --oneline`
14
-
15
- # REVIEW PROCESS
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
+ ```