@snappedly-tools/shipyard 0.7.0 → 0.9.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 +46 -43
- package/dist/{MountConfig-bZoCs4Dd.d.ts → MountConfig-K5ILnfht.d.ts} +1 -1
- package/dist/SandboxProvider-oUAwYlWm.d.ts +120 -0
- package/dist/{chunk-FDYOTN55.js → chunk-57EEKW3R.js} +361 -984
- package/dist/chunk-57EEKW3R.js.map +1 -0
- package/dist/{chunk-56TDSWFU.js → chunk-HXSZM52J.js} +3 -3
- package/dist/{chunk-56TDSWFU.js.map → chunk-HXSZM52J.js.map} +1 -1
- package/dist/{chunk-WAXEMZUV.js → chunk-JZUBT4WG.js} +241 -33
- package/dist/chunk-JZUBT4WG.js.map +1 -0
- package/dist/chunk-NQRFVKCU.js +1840 -0
- package/dist/chunk-NQRFVKCU.js.map +1 -0
- package/dist/chunk-Z5C4LHVP.js +137 -0
- package/dist/chunk-Z5C4LHVP.js.map +1 -0
- package/dist/createSandbox-DmbnWAZv.d.ts +739 -0
- package/dist/index.d.ts +99 -2606
- package/dist/index.js +412 -7487
- package/dist/index.js.map +1 -1
- package/dist/integrations/github.d.ts +52 -0
- package/dist/integrations/github.js +1049 -0
- package/dist/integrations/github.js.map +1 -0
- package/dist/integrations/releases.d.ts +136 -0
- package/dist/integrations/releases.js +500 -0
- package/dist/integrations/releases.js.map +1 -0
- package/dist/main.js +826 -472
- package/dist/main.js.map +1 -1
- package/dist/publication-BPoy_M9M.d.ts +1200 -0
- package/dist/sandboxes/docker.d.ts +2 -3
- package/dist/sandboxes/docker.js +2 -4
- package/dist/templates/parallel-planner/main.mts +78 -16
- package/dist/templates/parallel-planner/planner-branch.mts +151 -0
- package/dist/templates/parallel-planner/setup.sh +1 -0
- package/dist/templates/parallel-planner-with-review/main.mts +81 -19
- package/dist/templates/parallel-planner-with-review/planner-branch.mts +151 -0
- package/dist/templates/parallel-planner-with-review/setup.sh +1 -0
- package/dist/templates/sequential-reviewer/main.mts +59 -6
- package/dist/templates/sequential-reviewer/setup.sh +1 -0
- package/dist/templates/shared/setup.sh +1 -0
- package/dist/templates/simple-loop/main.mts +58 -5
- package/dist/templates/simple-loop/setup.sh +1 -0
- package/dist/workflow/coordinator/migrations/002_workflow_phase_records.sql +11 -0
- package/dist/workflow/coordinator/migrations/003_phase_record_schema_version.sql +6 -0
- package/dist/workflow.d.ts +472 -0
- package/dist/workflow.js +4345 -0
- package/dist/workflow.js.map +1 -0
- package/package.json +11 -15
- package/dist/SandboxProvider-XJQqEdSf.d.ts +0 -261
- package/dist/chunk-ACD46ZM4.js +0 -136
- package/dist/chunk-ACD46ZM4.js.map +0 -1
- package/dist/chunk-FDYOTN55.js.map +0 -1
- package/dist/chunk-KMGNFXKN.js +0 -38
- package/dist/chunk-KMGNFXKN.js.map +0 -1
- package/dist/chunk-SOJTAJTF.js +0 -78
- package/dist/chunk-SOJTAJTF.js.map +0 -1
- package/dist/chunk-WAXEMZUV.js.map +0 -1
- package/dist/sandboxes/no-sandbox.d.ts +0 -37
- package/dist/sandboxes/no-sandbox.js +0 -4
- package/dist/sandboxes/no-sandbox.js.map +0 -1
- package/dist/sandboxes/vercel.d.ts +0 -104
- package/dist/sandboxes/vercel.js +0 -166
- package/dist/sandboxes/vercel.js.map +0 -1
- package/dist/templates/blank/main.mts +0 -13
- package/dist/templates/blank/prompt.md +0 -12
- package/dist/templates/blank/template.json +0 -4
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { createInterface } from "node:readline/promises";
|
|
3
|
+
|
|
4
|
+
const PLANNER_BRANCH = "shipyard/planner";
|
|
5
|
+
|
|
6
|
+
type BranchPrompt = (message: string) => Promise<string>;
|
|
7
|
+
|
|
8
|
+
const askInTerminal: BranchPrompt = async (message) => {
|
|
9
|
+
const readline = createInterface({
|
|
10
|
+
input: process.stdin,
|
|
11
|
+
output: process.stdout,
|
|
12
|
+
});
|
|
13
|
+
try {
|
|
14
|
+
return await readline.question(message);
|
|
15
|
+
} finally {
|
|
16
|
+
readline.close();
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const refsConflict = (left: string, right: string): boolean =>
|
|
21
|
+
left.startsWith(`${right}/`) || right.startsWith(`${left}/`);
|
|
22
|
+
|
|
23
|
+
const nextPlannerBranch = (localBranches: readonly string[]): string => {
|
|
24
|
+
for (let suffix = 2; ; suffix++) {
|
|
25
|
+
for (const candidate of [
|
|
26
|
+
`${PLANNER_BRANCH}-${suffix}`,
|
|
27
|
+
`shipyard-planner-${suffix}`,
|
|
28
|
+
]) {
|
|
29
|
+
if (!localBranches.some((branch) => refsConflict(candidate, branch)))
|
|
30
|
+
return candidate;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const reusablePlannerBranch = (
|
|
36
|
+
localBranches: readonly string[],
|
|
37
|
+
): string | undefined => {
|
|
38
|
+
const candidates = localBranches
|
|
39
|
+
.map((branch) => ({
|
|
40
|
+
branch,
|
|
41
|
+
suffix: /^(?:shipyard\/planner-|shipyard-planner-)(\d+)$/.exec(
|
|
42
|
+
branch,
|
|
43
|
+
)?.[1],
|
|
44
|
+
}))
|
|
45
|
+
.filter((candidate) => candidate.suffix !== undefined)
|
|
46
|
+
.sort((left, right) => Number(left.suffix) - Number(right.suffix));
|
|
47
|
+
|
|
48
|
+
return candidates.find(({ branch }) =>
|
|
49
|
+
localBranches.every(
|
|
50
|
+
(other) => other === branch || !refsConflict(branch, other),
|
|
51
|
+
),
|
|
52
|
+
)?.branch;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const resolvePlannerBranch = async (
|
|
56
|
+
localBranches: readonly string[],
|
|
57
|
+
options: {
|
|
58
|
+
isInteractive?: boolean;
|
|
59
|
+
ask?: BranchPrompt;
|
|
60
|
+
checkedOutBranches?: readonly string[];
|
|
61
|
+
mergedBranches?: readonly string[];
|
|
62
|
+
deleteBranches?: (branches: readonly string[]) => void;
|
|
63
|
+
} = {},
|
|
64
|
+
): Promise<string> => {
|
|
65
|
+
const conflicts = localBranches.filter((branch) =>
|
|
66
|
+
refsConflict(PLANNER_BRANCH, branch),
|
|
67
|
+
);
|
|
68
|
+
if (conflicts.length === 0) return PLANNER_BRANCH;
|
|
69
|
+
|
|
70
|
+
const reusable = reusablePlannerBranch(localBranches);
|
|
71
|
+
if (reusable) return reusable;
|
|
72
|
+
|
|
73
|
+
const alternate = nextPlannerBranch(localBranches);
|
|
74
|
+
|
|
75
|
+
const conflictList = conflicts.map((branch) => ` - ${branch}`).join("\n");
|
|
76
|
+
const context =
|
|
77
|
+
`Planner branch '${PLANNER_BRANCH}' conflicts with local branch refs:\n${conflictList}\n` +
|
|
78
|
+
`Git cannot create both names. The listed local branches will be kept unless you choose deletion.`;
|
|
79
|
+
const isInteractive =
|
|
80
|
+
options.isInteractive ??
|
|
81
|
+
Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
82
|
+
if (!isInteractive) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`${context}\nRerun in an interactive terminal to keep them and use '${alternate}', or request local deletion after Git checks that they are merged and not checked out. No branches were changed.`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const checkedOutBranches = new Set(
|
|
89
|
+
options.checkedOutBranches ??
|
|
90
|
+
execFileSync("git", ["worktree", "list", "--porcelain"], {
|
|
91
|
+
encoding: "utf8",
|
|
92
|
+
})
|
|
93
|
+
.split(/\r?\n/)
|
|
94
|
+
.filter((line) => line.startsWith("branch refs/heads/"))
|
|
95
|
+
.map((line) => line.slice("branch refs/heads/".length)),
|
|
96
|
+
);
|
|
97
|
+
const mergedBranches = new Set(
|
|
98
|
+
options.mergedBranches ??
|
|
99
|
+
execFileSync(
|
|
100
|
+
"git",
|
|
101
|
+
["branch", "--merged", "HEAD", "--format=%(refname:short)"],
|
|
102
|
+
{ encoding: "utf8" },
|
|
103
|
+
)
|
|
104
|
+
.split(/\r?\n/)
|
|
105
|
+
.filter(Boolean),
|
|
106
|
+
);
|
|
107
|
+
const branchesSafeToDelete = conflicts.every(
|
|
108
|
+
(branch) => !checkedOutBranches.has(branch) && mergedBranches.has(branch),
|
|
109
|
+
);
|
|
110
|
+
const unsafeDeleteReasons = conflicts.flatMap((branch) => {
|
|
111
|
+
if (checkedOutBranches.has(branch))
|
|
112
|
+
return [`'${branch}' is checked out in a worktree`];
|
|
113
|
+
if (!mergedBranches.has(branch))
|
|
114
|
+
return [`'${branch}' has commits not merged into HEAD`];
|
|
115
|
+
return [];
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const ask = options.ask ?? askInTerminal;
|
|
119
|
+
const answer = await ask(
|
|
120
|
+
`${context}\n[Y] Keep them and use '${alternate}' (recommended)\n` +
|
|
121
|
+
`[d] Delete these local branches and use '${PLANNER_BRANCH}'${
|
|
122
|
+
branchesSafeToDelete
|
|
123
|
+
? ""
|
|
124
|
+
: ` (unavailable: ${unsafeDeleteReasons.join("; ")})`
|
|
125
|
+
}\n` +
|
|
126
|
+
`[n] Abort without changes\nChoose [Y/d/n]: `,
|
|
127
|
+
);
|
|
128
|
+
const choice = answer.trim().toLowerCase();
|
|
129
|
+
if (choice === "" || choice === "y" || choice === "yes") return alternate;
|
|
130
|
+
if (choice === "d" || choice === "delete") {
|
|
131
|
+
if (!branchesSafeToDelete) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
`Cannot safely delete the conflicting branches: ${unsafeDeleteReasons.join("; ")}. No branches were changed. Use '${alternate}' or inspect the listed refs manually.`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
const deleteBranches =
|
|
137
|
+
options.deleteBranches ??
|
|
138
|
+
((branches: readonly string[]) => {
|
|
139
|
+
for (const branch of branches)
|
|
140
|
+
execFileSync("git", ["branch", "-d", branch], {
|
|
141
|
+
encoding: "utf8",
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
deleteBranches(conflicts);
|
|
145
|
+
return PLANNER_BRANCH;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
throw new Error(
|
|
149
|
+
`Planner branch selection cancelled. No branches were changed. Inspect with 'git worktree list' and 'git branch --list "shipyard/planner*"', then rerun.`,
|
|
150
|
+
);
|
|
151
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
|
+
# The sandbox starts from a temporary Git bundle, so reset its origin before Git pushes.
|
|
4
5
|
if [[ -n ${GH_REPO:-} ]]; then
|
|
5
6
|
[[ "$GH_REPO" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]] || { echo "Invalid GH_REPO" >&2; exit 1; }
|
|
6
7
|
git remote set-url origin "https://github.com/$GH_REPO.git"
|
|
@@ -7,6 +7,55 @@ import { docker } from "@snappedly-tools/shipyard/sandboxes/docker";
|
|
|
7
7
|
|
|
8
8
|
if (process.loadEnvFile && existsSync(".shipyard/.env"))
|
|
9
9
|
process.loadEnvFile(".shipyard/.env");
|
|
10
|
+
type ModelRole = "routine" | "strong";
|
|
11
|
+
const CODEX_PROVIDER = true;
|
|
12
|
+
const agentFactory = shipyard.codex;
|
|
13
|
+
type AgentModel = Parameters<typeof agentFactory>[0];
|
|
14
|
+
const readRoleModel = (role: ModelRole): string | undefined => {
|
|
15
|
+
const envName = `SHIPYARD_${role.toUpperCase()}_MODEL`;
|
|
16
|
+
const model = process.env[envName];
|
|
17
|
+
if (model !== undefined && model.trim().length === 0)
|
|
18
|
+
throw new Error(`${envName} must not be empty`);
|
|
19
|
+
return model;
|
|
20
|
+
};
|
|
21
|
+
const roleModels = {
|
|
22
|
+
routine: readRoleModel("routine"),
|
|
23
|
+
strong: readRoleModel("strong"),
|
|
24
|
+
};
|
|
25
|
+
const CODEX_REASONING_EFFORTS = shipyard.CODEX_REASONING_EFFORTS;
|
|
26
|
+
type CodexReasoningEffort = shipyard.CodexReasoningEffort;
|
|
27
|
+
const readCodexReasoningEffort = (
|
|
28
|
+
role: ModelRole,
|
|
29
|
+
): CodexReasoningEffort | undefined => {
|
|
30
|
+
if (!CODEX_PROVIDER) return undefined;
|
|
31
|
+
const envName = `SHIPYARD_CODEX_${role.toUpperCase()}_REASONING_EFFORT`;
|
|
32
|
+
const effort = process.env[envName]?.trim();
|
|
33
|
+
if (!effort) return undefined;
|
|
34
|
+
if (!(CODEX_REASONING_EFFORTS as readonly string[]).includes(effort))
|
|
35
|
+
throw new Error(
|
|
36
|
+
`${envName} must be one of ${CODEX_REASONING_EFFORTS.join(", ")}; received "${effort}"`,
|
|
37
|
+
);
|
|
38
|
+
return effort as CodexReasoningEffort;
|
|
39
|
+
};
|
|
40
|
+
const roleEfforts = {
|
|
41
|
+
routine: readCodexReasoningEffort("routine"),
|
|
42
|
+
strong: readCodexReasoningEffort("strong"),
|
|
43
|
+
};
|
|
44
|
+
const readCodexRoleModel = (role: ModelRole, defaultModel: AgentModel) => {
|
|
45
|
+
if (!CODEX_PROVIDER || typeof defaultModel === "string") return defaultModel;
|
|
46
|
+
const envName = `SHIPYARD_CODEX_${role.toUpperCase()}_MODEL`;
|
|
47
|
+
const model = process.env[envName]?.trim();
|
|
48
|
+
return model ? { ...defaultModel, model } : defaultModel;
|
|
49
|
+
};
|
|
50
|
+
const roleAgent = (role: ModelRole, defaultModel: AgentModel) => {
|
|
51
|
+
const model = roleModels[role] ?? readCodexRoleModel(role, defaultModel);
|
|
52
|
+
const effort = roleEfforts[role];
|
|
53
|
+
if (typeof model !== "string")
|
|
54
|
+
return effort === undefined
|
|
55
|
+
? agentFactory(model)
|
|
56
|
+
: agentFactory(model, { effort });
|
|
57
|
+
return agentFactory(model, { effort: effort ?? null });
|
|
58
|
+
};
|
|
10
59
|
const targetBranch = execFileSync("git", ["branch", "--show-current"], {
|
|
11
60
|
encoding: "utf8",
|
|
12
61
|
}).trim();
|
|
@@ -21,7 +70,11 @@ if (
|
|
|
21
70
|
) {
|
|
22
71
|
throw new Error("Invalid target branch or GitHub repository");
|
|
23
72
|
}
|
|
24
|
-
|
|
73
|
+
const sandboxAuthOptions = {};
|
|
74
|
+
const sandboxProvider = docker({
|
|
75
|
+
env: { GH_REPO: repository },
|
|
76
|
+
...sandboxAuthOptions,
|
|
77
|
+
});
|
|
25
78
|
const MAX_ITERATIONS = 10;
|
|
26
79
|
const hooks = {
|
|
27
80
|
sandbox: {
|
|
@@ -126,7 +179,7 @@ for (let iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
|
|
|
126
179
|
]);
|
|
127
180
|
const sandbox = await shipyard.createSandbox({
|
|
128
181
|
branch: issue.branch,
|
|
129
|
-
sandbox:
|
|
182
|
+
sandbox: sandboxProvider,
|
|
130
183
|
hooks,
|
|
131
184
|
});
|
|
132
185
|
let evidence: string;
|
|
@@ -136,7 +189,7 @@ for (let iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
|
|
|
136
189
|
: [issue.id]) {
|
|
137
190
|
await sandbox.run({
|
|
138
191
|
name: `triage #${ticketId}`,
|
|
139
|
-
agent:
|
|
192
|
+
agent: roleAgent("routine", shipyard.CODEX_MODELS.routine),
|
|
140
193
|
maxIterations: 1,
|
|
141
194
|
promptFile: "./.shipyard/triage-prompt.md",
|
|
142
195
|
promptArgs: { TASK_ID: ticketId },
|
|
@@ -146,7 +199,7 @@ for (let iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
|
|
|
146
199
|
const implement = await sandbox.run({
|
|
147
200
|
name: "implementer",
|
|
148
201
|
maxIterations: 1,
|
|
149
|
-
agent:
|
|
202
|
+
agent: roleAgent("routine", shipyard.CODEX_MODELS.routine),
|
|
150
203
|
promptFile: "./.shipyard/implement-prompt.md",
|
|
151
204
|
promptArgs: {
|
|
152
205
|
TASK_ID: issue.id,
|
|
@@ -165,7 +218,7 @@ for (let iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
|
|
|
165
218
|
const review = await sandbox.run({
|
|
166
219
|
name: "reviewer",
|
|
167
220
|
maxIterations: 1,
|
|
168
|
-
agent:
|
|
221
|
+
agent: roleAgent("strong", shipyard.CODEX_MODELS.strong),
|
|
169
222
|
promptFile: "./.shipyard/review-prompt.md",
|
|
170
223
|
promptArgs: {
|
|
171
224
|
BRANCH: issue.branch,
|
|
@@ -191,7 +244,7 @@ for (let iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
|
|
|
191
244
|
|
|
192
245
|
const publication = await shipyard.createSandbox({
|
|
193
246
|
branch: issue.branch,
|
|
194
|
-
sandbox:
|
|
247
|
+
sandbox: sandboxProvider,
|
|
195
248
|
});
|
|
196
249
|
try {
|
|
197
250
|
publicationUncertain = true;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
|
+
# The sandbox starts from a temporary Git bundle, so reset its origin before Git pushes.
|
|
4
5
|
if [[ -n ${GH_REPO:-} ]]; then
|
|
5
6
|
[[ "$GH_REPO" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]] || { echo "Invalid GH_REPO" >&2; exit 1; }
|
|
6
7
|
git remote set-url origin "https://github.com/$GH_REPO.git"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
|
+
# The sandbox starts from a temporary Git bundle, so reset its origin before Git pushes.
|
|
4
5
|
if [[ -n ${GH_REPO:-} ]]; then
|
|
5
6
|
[[ "$GH_REPO" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]] || { echo "Invalid GH_REPO" >&2; exit 1; }
|
|
6
7
|
git remote set-url origin "https://github.com/$GH_REPO.git"
|
|
@@ -7,6 +7,55 @@ import { docker } from "@snappedly-tools/shipyard/sandboxes/docker";
|
|
|
7
7
|
|
|
8
8
|
if (process.loadEnvFile && existsSync(".shipyard/.env"))
|
|
9
9
|
process.loadEnvFile(".shipyard/.env");
|
|
10
|
+
type ModelRole = "routine" | "strong";
|
|
11
|
+
const CODEX_PROVIDER = true;
|
|
12
|
+
const agentFactory = shipyard.codex;
|
|
13
|
+
type AgentModel = Parameters<typeof agentFactory>[0];
|
|
14
|
+
const readRoleModel = (role: ModelRole): string | undefined => {
|
|
15
|
+
const envName = `SHIPYARD_${role.toUpperCase()}_MODEL`;
|
|
16
|
+
const model = process.env[envName];
|
|
17
|
+
if (model !== undefined && model.trim().length === 0)
|
|
18
|
+
throw new Error(`${envName} must not be empty`);
|
|
19
|
+
return model;
|
|
20
|
+
};
|
|
21
|
+
const roleModels = {
|
|
22
|
+
routine: readRoleModel("routine"),
|
|
23
|
+
strong: readRoleModel("strong"),
|
|
24
|
+
};
|
|
25
|
+
const CODEX_REASONING_EFFORTS = shipyard.CODEX_REASONING_EFFORTS;
|
|
26
|
+
type CodexReasoningEffort = shipyard.CodexReasoningEffort;
|
|
27
|
+
const readCodexReasoningEffort = (
|
|
28
|
+
role: ModelRole,
|
|
29
|
+
): CodexReasoningEffort | undefined => {
|
|
30
|
+
if (!CODEX_PROVIDER) return undefined;
|
|
31
|
+
const envName = `SHIPYARD_CODEX_${role.toUpperCase()}_REASONING_EFFORT`;
|
|
32
|
+
const effort = process.env[envName]?.trim();
|
|
33
|
+
if (!effort) return undefined;
|
|
34
|
+
if (!(CODEX_REASONING_EFFORTS as readonly string[]).includes(effort))
|
|
35
|
+
throw new Error(
|
|
36
|
+
`${envName} must be one of ${CODEX_REASONING_EFFORTS.join(", ")}; received "${effort}"`,
|
|
37
|
+
);
|
|
38
|
+
return effort as CodexReasoningEffort;
|
|
39
|
+
};
|
|
40
|
+
const roleEfforts = {
|
|
41
|
+
routine: readCodexReasoningEffort("routine"),
|
|
42
|
+
strong: readCodexReasoningEffort("strong"),
|
|
43
|
+
};
|
|
44
|
+
const readCodexRoleModel = (role: ModelRole, defaultModel: AgentModel) => {
|
|
45
|
+
if (!CODEX_PROVIDER || typeof defaultModel === "string") return defaultModel;
|
|
46
|
+
const envName = `SHIPYARD_CODEX_${role.toUpperCase()}_MODEL`;
|
|
47
|
+
const model = process.env[envName]?.trim();
|
|
48
|
+
return model ? { ...defaultModel, model } : defaultModel;
|
|
49
|
+
};
|
|
50
|
+
const roleAgent = (role: ModelRole, defaultModel: AgentModel) => {
|
|
51
|
+
const model = roleModels[role] ?? readCodexRoleModel(role, defaultModel);
|
|
52
|
+
const effort = roleEfforts[role];
|
|
53
|
+
if (typeof model !== "string")
|
|
54
|
+
return effort === undefined
|
|
55
|
+
? agentFactory(model)
|
|
56
|
+
: agentFactory(model, { effort });
|
|
57
|
+
return agentFactory(model, { effort: effort ?? null });
|
|
58
|
+
};
|
|
10
59
|
const targetBranch = execFileSync("git", ["branch", "--show-current"], {
|
|
11
60
|
encoding: "utf8",
|
|
12
61
|
}).trim();
|
|
@@ -21,7 +70,11 @@ if (
|
|
|
21
70
|
) {
|
|
22
71
|
throw new Error("Invalid target branch or GitHub repository");
|
|
23
72
|
}
|
|
24
|
-
|
|
73
|
+
const sandboxAuthOptions = {};
|
|
74
|
+
const sandboxProvider = docker({
|
|
75
|
+
env: { GH_REPO: repository },
|
|
76
|
+
...sandboxAuthOptions,
|
|
77
|
+
});
|
|
25
78
|
const hooks = {
|
|
26
79
|
sandbox: {
|
|
27
80
|
onSandboxReady: [
|
|
@@ -123,7 +176,7 @@ for (let iteration = 0; iteration < 3; iteration++) {
|
|
|
123
176
|
]);
|
|
124
177
|
const sandbox = await shipyard.createSandbox({
|
|
125
178
|
branch: issue.branch,
|
|
126
|
-
sandbox:
|
|
179
|
+
sandbox: sandboxProvider,
|
|
127
180
|
hooks,
|
|
128
181
|
});
|
|
129
182
|
let evidence: string;
|
|
@@ -133,7 +186,7 @@ for (let iteration = 0; iteration < 3; iteration++) {
|
|
|
133
186
|
: [issue.id]) {
|
|
134
187
|
await sandbox.run({
|
|
135
188
|
name: `triage #${ticketId}`,
|
|
136
|
-
agent:
|
|
189
|
+
agent: roleAgent("routine", shipyard.CODEX_MODELS.routine),
|
|
137
190
|
maxIterations: 1,
|
|
138
191
|
promptFile: "./.shipyard/triage-prompt.md",
|
|
139
192
|
promptArgs: { TASK_ID: ticketId },
|
|
@@ -142,7 +195,7 @@ for (let iteration = 0; iteration < 3; iteration++) {
|
|
|
142
195
|
}
|
|
143
196
|
const result = await sandbox.run({
|
|
144
197
|
name: "implementer",
|
|
145
|
-
agent:
|
|
198
|
+
agent: roleAgent("routine", shipyard.CODEX_MODELS.routine),
|
|
146
199
|
maxIterations: 1,
|
|
147
200
|
promptFile: "./.shipyard/prompt.md",
|
|
148
201
|
promptArgs: {
|
|
@@ -171,7 +224,7 @@ for (let iteration = 0; iteration < 3; iteration++) {
|
|
|
171
224
|
// branch so a later invocation can fast-forward the same PR.
|
|
172
225
|
const publication = await shipyard.createSandbox({
|
|
173
226
|
branch: issue.branch,
|
|
174
|
-
sandbox:
|
|
227
|
+
sandbox: sandboxProvider,
|
|
175
228
|
});
|
|
176
229
|
try {
|
|
177
230
|
publicationUncertain = true;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
|
+
# The sandbox starts from a temporary Git bundle, so reset its origin before Git pushes.
|
|
4
5
|
if [[ -n ${GH_REPO:-} ]]; then
|
|
5
6
|
[[ "$GH_REPO" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]] || { echo "Invalid GH_REPO" >&2; exit 1; }
|
|
6
7
|
git remote set-url origin "https://github.com/$GH_REPO.git"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- Durable records for triage investigations and bounded repair publication.
|
|
2
|
+
-- These records resume phase-specific work; the coordinator ledger remains
|
|
3
|
+
-- responsible for job, dispatch, lease, and event state.
|
|
4
|
+
|
|
5
|
+
CREATE TABLE IF NOT EXISTS shipyard_workflow_phase_records (
|
|
6
|
+
namespace TEXT NOT NULL CHECK (namespace IN ('triage', 'repair-batch')),
|
|
7
|
+
record_key TEXT NOT NULL,
|
|
8
|
+
record JSONB NOT NULL,
|
|
9
|
+
updated_at TIMESTAMPTZ NOT NULL,
|
|
10
|
+
PRIMARY KEY (namespace, record_key)
|
|
11
|
+
);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
-- Version phase records without breaking writers from the previous release.
|
|
2
|
+
-- The default assigns old-format inserts version 1; code rollback can leave the
|
|
3
|
+
-- additive column in place.
|
|
4
|
+
ALTER TABLE shipyard_workflow_phase_records
|
|
5
|
+
ADD COLUMN IF NOT EXISTS schema_version INTEGER NOT NULL DEFAULT 1
|
|
6
|
+
CHECK (schema_version = 1);
|