@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
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
if [[ -n ${GH_REPO:-} ]]; then
|
|
5
|
+
[[ "$GH_REPO" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]] || { echo "Invalid GH_REPO" >&2; exit 1; }
|
|
6
|
+
git remote set-url origin "https://github.com/$GH_REPO.git"
|
|
7
|
+
fi
|
|
8
|
+
|
|
9
|
+
# Install for this sandbox's checkout. Host node_modules can contain binaries
|
|
10
|
+
# for a different OS and cannot cover dependencies added by the agent later.
|
|
11
|
+
if [[ -f package.json ]]; then
|
|
12
|
+
manager=$(node -e 'try { const p = require("./package.json").packageManager; process.stdout.write(p ? p.split("@")[0] : "") } catch {}')
|
|
13
|
+
if [[ -z "$manager" ]]; then
|
|
14
|
+
if [[ -f bun.lock || -f bun.lockb ]]; then manager=bun
|
|
15
|
+
elif [[ -f pnpm-lock.yaml ]]; then manager=pnpm
|
|
16
|
+
elif [[ -f yarn.lock ]]; then manager=yarn
|
|
17
|
+
else manager=npm; fi
|
|
18
|
+
fi
|
|
19
|
+
case "$manager" in
|
|
20
|
+
npm) npm install ;;
|
|
21
|
+
pnpm) corepack pnpm install ;;
|
|
22
|
+
yarn) corepack yarn install ;;
|
|
23
|
+
bun)
|
|
24
|
+
if ! command -v bun >/dev/null; then curl -fsSL https://bun.com/install | bash; export PATH="$HOME/.bun/bin:$PATH"; fi
|
|
25
|
+
bun install ;;
|
|
26
|
+
*) echo "Unsupported package manager: $manager" >&2; exit 1 ;;
|
|
27
|
+
esac
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
source_dir=$(mktemp -d)
|
|
31
|
+
trap 'rm -rf "$source_dir"' EXIT
|
|
32
|
+
if ! git clone --depth 1 https://github.com/snappedly/skills.git "$source_dir/repo"; then
|
|
33
|
+
echo "Could not install Snappedly skills; agent work has not started." >&2
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
mkdir -p "$HOME/.agents/skills" "$HOME/.claude/skills"
|
|
38
|
+
while IFS= read -r -d '' skill_file; do
|
|
39
|
+
skill_dir=${skill_file%/SKILL.md}
|
|
40
|
+
skill_name=${skill_dir##*/}
|
|
41
|
+
rm -rf "$HOME/.agents/skills/$skill_name" "$HOME/.claude/skills/$skill_name"
|
|
42
|
+
cp -R "$skill_dir" "$HOME/.agents/skills/$skill_name"
|
|
43
|
+
ln -sfn "$HOME/.agents/skills/$skill_name" "$HOME/.claude/skills/$skill_name"
|
|
44
|
+
done < <(find "$source_dir/repo/skills" -name SKILL.md -print0)
|
|
45
|
+
|
|
46
|
+
for skill in triage implement implement-spec code-cleanup code-review tdd; do
|
|
47
|
+
if [[ ! -s "$HOME/.agents/skills/$skill/SKILL.md" || -z $(find "$source_dir/repo/skills" -path "*/$skill/SKILL.md" -print -quit) ]]; then
|
|
48
|
+
echo "Snappedly skill installation incomplete: $skill" >&2
|
|
49
|
+
exit 1
|
|
50
|
+
fi
|
|
51
|
+
done
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Integrate this planning spec dependency wave
|
|
2
|
+
|
|
3
|
+
The selected template has assigned `/implement` workers to child tickets. Their branches for this wave are `{{WAVE_BRANCHES}}`. The integration branch is `{{BRANCH}}` for planning spec #{{TASK_ID}}. The workflow cherry-picked their committed work into this isolated sandbox.
|
|
4
|
+
|
|
5
|
+
Follow `/implement-spec` as the whole-spec integrator. Read the complete parent and child issues with comments, repository guidance, and dependency links. Inspect the current branch and ensure every ready ticket in this wave is integrated. If a child branch or cherry-pick was missing, stop and report it. Preserve each ticket's acceptance criteria. Run affected checks, commit corrections, and self-check. Do not create a PR, merge to the target branch, close issues, or start another orchestration chain. Later waves and the selected template's final review stage remain with the surrounding workflow.
|
|
6
|
+
|
|
7
|
+
If integration or checks fail, report the blocker without a completion marker. Only after this wave is committed and verified, report its integrated tickets, commits, `Checks: <commands and results>`, and limitations:
|
|
8
|
+
|
|
9
|
+
<handoff>...</handoff>
|
|
10
|
+
<promise>COMPLETE</promise>
|
|
11
|
+
|
|
12
|
+
## Resolved spec scope
|
|
13
|
+
|
|
14
|
+
```json
|
|
15
|
+
{{SCOPE}}
|
|
16
|
+
```
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Triage GitHub issue #{{TASK_ID}} before Shipyard implementation.
|
|
2
|
+
|
|
3
|
+
Follow `/triage` and the repository's `docs/agents/triage.md` label mapping. Read the full issue and comments, verify its claim against the codebase, then 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. If a decision requires maintainer judgment, use `needs-triage` and record the question. Do not implement the issue or start a PR in this phase.
|
|
4
|
+
|
|
5
|
+
Report the applied state and evidence. Shipyard will independently re-read GitHub labels and implement only an open issue carrying both `shipyard` and the sole triage state `ready-for-agent`.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
issue=${1:?issue number required}
|
|
5
|
+
repo=${2:?GitHub repository required}
|
|
6
|
+
[[ "$issue" =~ ^[0-9]+$ ]] || { echo "Invalid issue number" >&2; exit 1; }
|
|
7
|
+
[[ "$repo" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]] || { echo "Invalid GitHub repository" >&2; exit 1; }
|
|
8
|
+
|
|
9
|
+
gh issue view "$issue" --repo "$repo" --json state,labels |
|
|
10
|
+
node -e '
|
|
11
|
+
const issue = JSON.parse(require("node:fs").readFileSync(0, "utf8"));
|
|
12
|
+
const labels = new Set(issue.labels.map((label) => label.name));
|
|
13
|
+
const states = ["needs-triage", "needs-info", "ready-for-agent", "ready-for-human", "wontfix"];
|
|
14
|
+
if (issue.state !== "OPEN" || !labels.has("shipyard") ||
|
|
15
|
+
states.filter((state) => labels.has(state)).join() !== "ready-for-agent") {
|
|
16
|
+
console.error("Issue cannot be implemented: requires an open issue with shipyard and only ready-for-agent as its triage state");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
'
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
root=${1:?scope root required}
|
|
5
|
+
failed=${2:?failed issue required}
|
|
6
|
+
repo=${3:?GitHub repository required}
|
|
7
|
+
scope=${4:?scope issue numbers required}
|
|
8
|
+
branch=${5:-shipyard/issue-$root}
|
|
9
|
+
[[ "$root" =~ ^[0-9]+$ && "$failed" =~ ^[0-9]+$ ]] || { echo "Invalid issue number" >&2; exit 1; }
|
|
10
|
+
[[ "$repo" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]] || { echo "Invalid GitHub repository" >&2; exit 1; }
|
|
11
|
+
[[ "$scope" =~ ^[0-9]+(,[0-9]+)*$ && ( "$scope" == "$root" || "$scope" == "$root,"* ) && ",$scope," == *",$failed,"* ]] || {
|
|
12
|
+
echo "Invalid blocked issue scope" >&2; exit 1;
|
|
13
|
+
}
|
|
14
|
+
[[ "$branch" == "shipyard/issue-$root" || "$branch" == "shipyard/spec-$root" ]] || { echo "Invalid scope branch" >&2; exit 1; }
|
|
15
|
+
reason=$(cat)
|
|
16
|
+
[[ -n "$reason" ]] || { echo "Missing failure reason" >&2; exit 1; }
|
|
17
|
+
reason_hash=$(printf '%s' "$reason" | git hash-object --stdin)
|
|
18
|
+
pending_dir=$(git rev-parse --git-path shipyard-pending)
|
|
19
|
+
mkdir -p "$pending_dir"
|
|
20
|
+
umask 077
|
|
21
|
+
pending_file="$pending_dir/$root-$failed.pending"
|
|
22
|
+
trap 'rm -f "${pending_tmp:-}"' EXIT
|
|
23
|
+
confirmed=""
|
|
24
|
+
if [[ -f "$pending_file" ]]; then
|
|
25
|
+
IFS=$'\t' read -r old_root old_failed old_repo old_scope old_branch old_confirmed < "$pending_file"
|
|
26
|
+
if [[ "$old_root" == "$root" && "$old_failed" == "$failed" && "$old_repo" == "$repo" && "$old_scope" == "$scope" && "$old_branch" == "$branch" ]]; then
|
|
27
|
+
confirmed=${old_confirmed:-}
|
|
28
|
+
fi
|
|
29
|
+
fi
|
|
30
|
+
write_pending() {
|
|
31
|
+
pending_tmp=$(mktemp "$pending_file.XXXXXX")
|
|
32
|
+
printf '%s\t%s\t%s\t%s\t%s\t%s\n%s' "$root" "$failed" "$repo" "$scope" "$branch" "$confirmed" "$reason" > "$pending_tmp"
|
|
33
|
+
mv "$pending_tmp" "$pending_file"
|
|
34
|
+
}
|
|
35
|
+
confirm_blocked() {
|
|
36
|
+
if [[ ",$confirmed," != *",$1,"* ]]; then
|
|
37
|
+
confirmed=${confirmed:+$confirmed,}$1
|
|
38
|
+
write_pending
|
|
39
|
+
fi
|
|
40
|
+
}
|
|
41
|
+
write_pending
|
|
42
|
+
|
|
43
|
+
remove_issue_status() {
|
|
44
|
+
local labels
|
|
45
|
+
if gh issue edit "$1" --repo "$repo" --remove-label "$2"; then return 0; fi
|
|
46
|
+
labels=$(gh issue view "$1" --repo "$repo" --json labels --jq '.labels[].name') || return 75
|
|
47
|
+
if grep -Fxq "$2" <<< "$labels"; then
|
|
48
|
+
echo "Could not remove $2 from issue #$1" >&2
|
|
49
|
+
return 75
|
|
50
|
+
fi
|
|
51
|
+
}
|
|
52
|
+
remove_pr_status() {
|
|
53
|
+
local labels
|
|
54
|
+
if gh pr edit "$1" --repo "$repo" --remove-label "$2"; then return 0; fi
|
|
55
|
+
labels=$(gh pr view "$1" --repo "$repo" --json labels --jq '.labels[].name') || return 75
|
|
56
|
+
if grep -Fxq "$2" <<< "$labels"; then
|
|
57
|
+
echo "Could not remove $2 from PR #$1" >&2
|
|
58
|
+
return 75
|
|
59
|
+
fi
|
|
60
|
+
}
|
|
61
|
+
comment_once() {
|
|
62
|
+
local comments
|
|
63
|
+
if comments=$(gh issue view "$1" --repo "$repo" --json comments --jq '.comments[].body'); then
|
|
64
|
+
if grep -Fq "$3" <<< "$comments"; then return 0; fi
|
|
65
|
+
fi
|
|
66
|
+
gh issue comment "$1" --repo "$repo" --body "$2"
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
gh label create shipyard:blocked --repo "$repo" --color B60205 --description 'Shipyard work needs intervention' --force
|
|
70
|
+
status_synced=true
|
|
71
|
+
IFS=',' read -ra affected_ids <<< "$scope"
|
|
72
|
+
if [[ "$branch" == "shipyard/spec-$root" ]]; then
|
|
73
|
+
for affected_id in "${affected_ids[@]:1}"; do
|
|
74
|
+
gh issue edit "$affected_id" --repo "$repo" --add-label shipyard:blocked
|
|
75
|
+
confirm_blocked "$affected_id"
|
|
76
|
+
done
|
|
77
|
+
if ! gh issue edit "$root" --repo "$repo" --add-label shipyard:blocked; then
|
|
78
|
+
echo "Warning: could not update spec #$root status" >&2
|
|
79
|
+
status_synced=false
|
|
80
|
+
fi
|
|
81
|
+
else
|
|
82
|
+
gh issue edit "$root" --repo "$repo" --add-label shipyard:blocked
|
|
83
|
+
confirm_blocked "$root"
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
status=0
|
|
87
|
+
marker="shipyard:blocked:$root:$failed:$reason_hash"
|
|
88
|
+
comment_once "$failed" "Shipyard could not complete this issue. Reason: $reason
|
|
89
|
+
|
|
90
|
+
Work was not handed off. To retry, resolve the problem, remove shipyard:blocked, and add shipyard to the affected ticket.
|
|
91
|
+
|
|
92
|
+
<!-- $marker:failed -->" "$marker:failed" || status=75
|
|
93
|
+
if [[ "$root" != "$failed" ]]; then
|
|
94
|
+
comment_once "$root" "Shipyard paused this spec because linked ticket #$failed failed. Reason: $reason
|
|
95
|
+
|
|
96
|
+
Work was not handed off. To retry, resolve the problem, remove shipyard:blocked, and add shipyard to each unfinished ticket.
|
|
97
|
+
|
|
98
|
+
<!-- $marker:parent -->" "$marker:parent" || status=75
|
|
99
|
+
fi
|
|
100
|
+
if [[ "$branch" == "shipyard/spec-$root" && "$failed" == "$root" ]]; then
|
|
101
|
+
for affected_id in "${affected_ids[@]:1}"; do
|
|
102
|
+
comment_once "$affected_id" "Shipyard could not complete this ticket during the spec attempt. Reason: $reason
|
|
103
|
+
|
|
104
|
+
Work was not handed off. To retry, resolve the problem, remove shipyard:blocked, and add shipyard to the affected ticket.
|
|
105
|
+
|
|
106
|
+
<!-- $marker:ticket:$affected_id -->" "$marker:ticket:$affected_id" || status=75
|
|
107
|
+
done
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
if [[ "$branch" == "shipyard/spec-$root" ]]; then
|
|
111
|
+
if ! remove_issue_status "$root" shipyard:outstanding-tasks; then
|
|
112
|
+
echo "Warning: could not clear spec status" >&2
|
|
113
|
+
status_synced=false
|
|
114
|
+
fi
|
|
115
|
+
if ! remove_issue_status "$root" shipyard:complete; then
|
|
116
|
+
echo "Warning: could not clear spec status" >&2
|
|
117
|
+
status_synced=false
|
|
118
|
+
fi
|
|
119
|
+
else
|
|
120
|
+
remove_issue_status "$root" shipyard:outstanding-tasks || status=75
|
|
121
|
+
remove_issue_status "$root" shipyard:complete || status=75
|
|
122
|
+
fi
|
|
123
|
+
if [[ "$branch" == "shipyard/spec-$root" ]]; then
|
|
124
|
+
for affected_id in "${affected_ids[@]:1}"; do
|
|
125
|
+
remove_issue_status "$affected_id" shipyard:pending || status=75
|
|
126
|
+
done
|
|
127
|
+
else
|
|
128
|
+
remove_issue_status "$root" shipyard:pending || status=75
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
if ! pr_number=$(gh pr list --repo "$repo" --head "$branch" --state open --json number --jq '.[0].number // empty'); then
|
|
132
|
+
echo "Warning: could not inspect PR status" >&2
|
|
133
|
+
status_synced=false
|
|
134
|
+
pr_number=""
|
|
135
|
+
fi
|
|
136
|
+
if [[ -n "$pr_number" ]]; then
|
|
137
|
+
if ! gh pr edit "$pr_number" --repo "$repo" --add-label shipyard:blocked; then
|
|
138
|
+
echo "Warning: could not update PR status" >&2
|
|
139
|
+
status_synced=false
|
|
140
|
+
fi
|
|
141
|
+
if ! remove_pr_status "$pr_number" shipyard:outstanding-tasks; then
|
|
142
|
+
echo "Warning: could not clear PR status" >&2
|
|
143
|
+
status_synced=false
|
|
144
|
+
fi
|
|
145
|
+
if ! remove_pr_status "$pr_number" shipyard:complete; then
|
|
146
|
+
echo "Warning: could not clear PR status" >&2
|
|
147
|
+
status_synced=false
|
|
148
|
+
fi
|
|
149
|
+
fi
|
|
150
|
+
if [[ "$status" -eq 0 && "$status_synced" == true ]]; then
|
|
151
|
+
IFS=',' read -ra scope_ids <<< "$scope"
|
|
152
|
+
for scope_id in "${scope_ids[@]}"; do
|
|
153
|
+
labels=$(gh issue view "$scope_id" --repo "$repo" --json labels --jq '.labels[].name')
|
|
154
|
+
if grep -Fxq shipyard <<< "$labels"; then
|
|
155
|
+
gh issue edit "$scope_id" --repo "$repo" --remove-label shipyard
|
|
156
|
+
fi
|
|
157
|
+
done
|
|
158
|
+
rm -f "$pending_file"
|
|
159
|
+
fi
|
|
160
|
+
exit "$status"
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
issue=${1:?issue number required}
|
|
5
|
+
branch=${2:?branch required}
|
|
6
|
+
base=${3:?target branch required}
|
|
7
|
+
repo=${4:?GitHub repository required}
|
|
8
|
+
scope=${5:-$issue}
|
|
9
|
+
outstanding=${6:--}
|
|
10
|
+
previously_completed=${7:--}
|
|
11
|
+
[[ "$issue" =~ ^[0-9]+$ && ( "$branch" == "shipyard/issue-$issue" || "$branch" == "shipyard/spec-$issue" ) ]] || {
|
|
12
|
+
echo "Invalid issue or branch for PR handoff" >&2; exit 1;
|
|
13
|
+
}
|
|
14
|
+
[[ "$base" =~ ^[A-Za-z0-9._/-]+$ ]] || {
|
|
15
|
+
echo "Invalid target branch for PR handoff" >&2; exit 1;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
[[ "$scope" =~ ^[0-9]+(,[0-9]+)*$ && ( "$scope" == "$issue" || "$scope" == "$issue,"* ) ]] || { echo "Invalid PR scope" >&2; exit 1; }
|
|
19
|
+
[[ "$outstanding" == - || "$outstanding" =~ ^[0-9]+(,[0-9]+)*$ ]] || { echo "Invalid outstanding tickets" >&2; exit 1; }
|
|
20
|
+
[[ "$previously_completed" == - || "$previously_completed" =~ ^[0-9]+(,[0-9]+)*$ ]] || { echo "Invalid completed tickets" >&2; exit 1; }
|
|
21
|
+
evidence=$(cat)
|
|
22
|
+
[[ -n "$evidence" ]] || { echo "Missing verification evidence" >&2; exit 1; }
|
|
23
|
+
grep -Eiq '(^|[[:space:]])Checks:' <<< "$evidence" || { echo "Missing check evidence" >&2; exit 1; }
|
|
24
|
+
grep -Eiq '(^|[[:space:]])Review:' <<< "$evidence" || { echo "Missing review evidence" >&2; exit 1; }
|
|
25
|
+
[[ -z $(git status --porcelain) ]] || {
|
|
26
|
+
echo "Uncommitted changes prevent PR handoff" >&2; exit 1;
|
|
27
|
+
}
|
|
28
|
+
base_ref=$(git rev-parse --verify "refs/remotes/origin/$base^{commit}") || {
|
|
29
|
+
echo "Target branch is unavailable in the sandbox" >&2; exit 1;
|
|
30
|
+
}
|
|
31
|
+
branch_base=$base_ref
|
|
32
|
+
if ! git merge-base --is-ancestor "$base_ref" HEAD; then
|
|
33
|
+
branch_base=$(git merge-base "$base_ref" HEAD) || {
|
|
34
|
+
echo "PR branch has no common base with the target branch" >&2; exit 1;
|
|
35
|
+
}
|
|
36
|
+
fi
|
|
37
|
+
[[ -n $(git log "$branch_base"..HEAD --format=%H) ]] || {
|
|
38
|
+
echo "No verified commit to publish" >&2; exit 1;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
[[ "$repo" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]] || { echo "Invalid GitHub repository" >&2; exit 1; }
|
|
42
|
+
remove_issue_label() {
|
|
43
|
+
local labels
|
|
44
|
+
if gh issue edit "$1" --repo "$repo" --remove-label "$2"; then return 0; fi
|
|
45
|
+
labels=$(gh issue view "$1" --repo "$repo" --json labels --jq '.labels[].name') || return 75
|
|
46
|
+
if grep -Fxq "$2" <<< "$labels"; then
|
|
47
|
+
echo "Could not remove $2 from issue #$1; retry handoff when GitHub is available" >&2
|
|
48
|
+
return 75
|
|
49
|
+
fi
|
|
50
|
+
}
|
|
51
|
+
remove_pr_label() {
|
|
52
|
+
local labels
|
|
53
|
+
if gh pr edit "$1" --repo "$repo" --remove-label "$2"; then return 0; fi
|
|
54
|
+
labels=$(gh pr view "$1" --repo "$repo" --json labels --jq '.labels[].name') || return 75
|
|
55
|
+
if grep -Fxq "$2" <<< "$labels"; then
|
|
56
|
+
echo "Could not remove $2 from PR #$1; retry handoff when GitHub is available" >&2
|
|
57
|
+
return 75
|
|
58
|
+
fi
|
|
59
|
+
}
|
|
60
|
+
title=$(gh issue view "$issue" --repo "$repo" --json title --jq .title)
|
|
61
|
+
body=$(mktemp)
|
|
62
|
+
trap 'rm -f "$body"' EXIT
|
|
63
|
+
links=""
|
|
64
|
+
implemented=""
|
|
65
|
+
IFS=',' read -ra scope_ids <<< "$scope"
|
|
66
|
+
for scope_id in "${scope_ids[@]}"; do links+="#$scope_id "; done
|
|
67
|
+
for ((i=1; i<${#scope_ids[@]}; i++)); do implemented+="#${scope_ids[i]} "; done
|
|
68
|
+
if [[ "$previously_completed" != - ]]; then
|
|
69
|
+
IFS=',' read -ra completed_ids <<< "$previously_completed"
|
|
70
|
+
for completed_id in "${completed_ids[@]}"; do links+="#$completed_id "; implemented+="#$completed_id "; done
|
|
71
|
+
fi
|
|
72
|
+
if [[ "$outstanding" != - ]]; then
|
|
73
|
+
IFS=',' read -ra outstanding_ids <<< "$outstanding"
|
|
74
|
+
for outstanding_id in "${outstanding_ids[@]}"; do links+="#$outstanding_id "; done
|
|
75
|
+
fi
|
|
76
|
+
printf 'Source issues: %s\nImplemented tickets: %s\n\n## Verification and review\n\n%s\n\nHuman review and merge required.\n\n<!-- shipyard:verified-handoff -->\n' "$links" "$implemented" "$evidence" > "$body"
|
|
77
|
+
|
|
78
|
+
# Keep Git credentials inside the disposable sandbox, not in the host worktree.
|
|
79
|
+
git remote set-url origin "https://github.com/$repo.git"
|
|
80
|
+
git -c credential.helper='!gh auth git-credential' push -u origin "HEAD:refs/heads/$branch"
|
|
81
|
+
number=$(gh pr list --repo "$repo" --head "$branch" --state open --json number --jq '.[0].number // empty')
|
|
82
|
+
if [[ -z "$number" ]]; then
|
|
83
|
+
gh pr create --repo "$repo" --head "$branch" --base "$base" --title "$title" --body-file "$body" --draft
|
|
84
|
+
number=$(gh pr list --repo "$repo" --head "$branch" --state open --json number --jq '.[0].number // empty')
|
|
85
|
+
fi
|
|
86
|
+
[[ -n "$number" ]] || { echo "PR publication did not return an open PR" >&2; exit 1; }
|
|
87
|
+
gh pr edit "$number" --repo "$repo" --body-file "$body"
|
|
88
|
+
draft=$(gh pr view "$number" --repo "$repo" --json isDraft --jq .isDraft)
|
|
89
|
+
url=$(gh pr view "$number" --repo "$repo" --json url --jq .url)
|
|
90
|
+
[[ -n "$url" ]] || { echo "PR publication did not return a URL" >&2; exit 1; }
|
|
91
|
+
if [[ "$draft" == true ]]; then gh pr ready "$number" --repo "$repo" || true; fi
|
|
92
|
+
if ! current_draft=$(gh pr view "$number" --repo "$repo" --json isDraft --jq .isDraft) ||
|
|
93
|
+
! current_state=$(gh pr view "$number" --repo "$repo" --json state --jq .state); then
|
|
94
|
+
echo "Could not confirm PR readiness; retry publication when GitHub is available" >&2
|
|
95
|
+
exit 75
|
|
96
|
+
fi
|
|
97
|
+
if [[ "$current_draft" != false || "$current_state" != OPEN ]]; then
|
|
98
|
+
echo "PR did not become ready for human review" >&2
|
|
99
|
+
exit 1
|
|
100
|
+
fi
|
|
101
|
+
for ((i=1; i<${#scope_ids[@]}; i++)); do
|
|
102
|
+
if ! gh issue edit "${scope_ids[i]}" --repo "$repo" --add-label shipyard:complete; then
|
|
103
|
+
echo "Could not mark ticket #${scope_ids[i]} complete; retry issue completion when GitHub is available" >&2
|
|
104
|
+
exit 75
|
|
105
|
+
fi
|
|
106
|
+
remove_issue_label "${scope_ids[i]}" shipyard:blocked
|
|
107
|
+
remove_issue_label "${scope_ids[i]}" shipyard:pending
|
|
108
|
+
done
|
|
109
|
+
status_label=shipyard:complete
|
|
110
|
+
status_color=0E8A16
|
|
111
|
+
status_description='Shipyard work ready for human review'
|
|
112
|
+
if [[ "$outstanding" != - ]]; then
|
|
113
|
+
status_label=shipyard:outstanding-tasks
|
|
114
|
+
status_color=FBCA04
|
|
115
|
+
status_description='Spec has uncompleted tickets'
|
|
116
|
+
for outstanding_id in "${outstanding_ids[@]}"; do
|
|
117
|
+
labels=$(gh issue view "$outstanding_id" --repo "$repo" --json labels --jq '.labels[].name') || exit 75
|
|
118
|
+
if grep -Fxq shipyard:blocked <<< "$labels" && ! grep -Fxq shipyard:complete <<< "$labels"; then
|
|
119
|
+
status_label=shipyard:blocked
|
|
120
|
+
status_color=B60205
|
|
121
|
+
status_description='Shipyard work needs intervention'
|
|
122
|
+
break
|
|
123
|
+
fi
|
|
124
|
+
done
|
|
125
|
+
fi
|
|
126
|
+
status_synced=true
|
|
127
|
+
if ! gh label create "$status_label" --repo "$repo" --color "$status_color" --description "$status_description" --force; then
|
|
128
|
+
echo "Warning: could not create $status_label" >&2
|
|
129
|
+
status_synced=false
|
|
130
|
+
fi
|
|
131
|
+
if ! gh issue edit "$issue" --repo "$repo" --add-label "$status_label"; then
|
|
132
|
+
if [[ "$branch" == "shipyard/issue-$issue" ]]; then
|
|
133
|
+
echo "Could not mark ticket #$issue complete; retry issue completion when GitHub is available" >&2
|
|
134
|
+
exit 75
|
|
135
|
+
fi
|
|
136
|
+
echo "Warning: could not update spec #$issue status" >&2
|
|
137
|
+
status_synced=false
|
|
138
|
+
fi
|
|
139
|
+
for stale_label in shipyard:complete shipyard:outstanding-tasks shipyard:blocked; do
|
|
140
|
+
if [[ "$stale_label" != "$status_label" ]]; then
|
|
141
|
+
if ! remove_issue_label "$issue" "$stale_label"; then
|
|
142
|
+
if [[ "$branch" == "shipyard/issue-$issue" ]]; then exit 75; fi
|
|
143
|
+
echo "Warning: could not clear $stale_label from spec #$issue" >&2
|
|
144
|
+
status_synced=false
|
|
145
|
+
fi
|
|
146
|
+
fi
|
|
147
|
+
done
|
|
148
|
+
if [[ "$branch" == "shipyard/issue-$issue" ]]; then remove_issue_label "$issue" shipyard:pending; fi
|
|
149
|
+
if ! gh pr edit "$number" --repo "$repo" --add-label "$status_label"; then
|
|
150
|
+
echo "Warning: could not update PR #$number status" >&2
|
|
151
|
+
status_synced=false
|
|
152
|
+
fi
|
|
153
|
+
for stale_label in shipyard:complete shipyard:outstanding-tasks shipyard:blocked; do
|
|
154
|
+
if [[ "$stale_label" != "$status_label" ]]; then
|
|
155
|
+
if ! remove_pr_label "$number" "$stale_label"; then
|
|
156
|
+
echo "Warning: could not clear $stale_label from PR #$number" >&2
|
|
157
|
+
status_synced=false
|
|
158
|
+
fi
|
|
159
|
+
fi
|
|
160
|
+
done
|
|
161
|
+
if [[ "$status_synced" == true ]]; then
|
|
162
|
+
for scope_id in "${scope_ids[@]}"; do
|
|
163
|
+
if ! labels=$(gh issue view "$scope_id" --repo "$repo" --json labels --jq '.labels[].name'); then
|
|
164
|
+
echo "Warning: could not inspect activation on issue #$scope_id; the next invocation will retry cleanup" >&2
|
|
165
|
+
continue
|
|
166
|
+
fi
|
|
167
|
+
if grep -Fxq shipyard <<< "$labels"; then
|
|
168
|
+
gh issue edit "$scope_id" --repo "$repo" --remove-label shipyard ||
|
|
169
|
+
echo "Warning: could not remove activation from issue #$scope_id; the next invocation will retry cleanup" >&2
|
|
170
|
+
fi
|
|
171
|
+
done
|
|
172
|
+
else
|
|
173
|
+
echo "Warning: status labels need reconciliation; retaining ticket activation" >&2
|
|
174
|
+
fi
|
|
175
|
+
printf '%s\n' "$url"
|
|
@@ -1,53 +1,19 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Assigned issue scope
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Implement scope #{{TASK_ID}} ({{ISSUE_TITLE}}) on branch `{{BRANCH}}`.
|
|
4
|
+
Read it with `gh issue view {{TASK_ID}} --comments`. Read `AGENTS.md`, `CONTEXT.md`, `docs/agents/workflow.md`, and relevant ADRs.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
Follow {{SKILL}} in this sequential-reviewer workflow. For a planning spec, deliver only tickets in `SCOPE.tickets` in dependency order and integrate them on this one branch. Other linked tickets are context; do not implement them. Do not create separate child PRs. Use `/tdd` for changed logic and local `/code-cleanup`. Run required focused checks and typechecking, reinstalling candidate dependencies with `bash .shipyard/setup.sh` after manifest changes. Commit and self-check the implementation. The next stage owns independent `/code-review`; do not start another reviewer, orchestration chain, PR, merge, or issue closure.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
If incomplete, report the blocker without a completion marker. After a verified commit, report scope, `Checks: <commands and results>`, and limitations:
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
!`git log --oneline --grep="RALPH" -10`
|
|
12
|
-
|
|
13
|
-
# Task
|
|
14
|
-
|
|
15
|
-
You are RALPH — an autonomous coding agent working through issues one at a time.
|
|
16
|
-
|
|
17
|
-
## Priority order
|
|
18
|
-
|
|
19
|
-
Work on issues in this order:
|
|
20
|
-
|
|
21
|
-
1. **Bug fixes** — broken behaviour affecting users
|
|
22
|
-
2. **Tracer bullets** — thin end-to-end slices that prove an approach works
|
|
23
|
-
3. **Polish** — improving existing functionality (error messages, UX, docs)
|
|
24
|
-
4. **Refactors** — internal cleanups with no user-visible change
|
|
25
|
-
|
|
26
|
-
Pick the highest-priority open issue that is not blocked by another open issue.
|
|
27
|
-
|
|
28
|
-
## Workflow
|
|
29
|
-
|
|
30
|
-
1. **Explore** — read the issue carefully. Pull in the parent PRD if referenced. Read the relevant source files and tests before writing any code.
|
|
31
|
-
2. **Plan** — decide what to change and why. Keep the change as small as possible.
|
|
32
|
-
3. **Execute** — use RGR (Red → Green → Repeat → Refactor): write a failing test first, then write the implementation to pass it.
|
|
33
|
-
4. **Verify** — read the repository's configured feedback-loop contract and run every applicable check for this change. Use the configured static check and focused behavior tests when they exist; include formatting, build, or broader checks when the contract or change requires them. Fix failures before proceeding.
|
|
34
|
-
5. **Commit** — make a single git commit. The message MUST:
|
|
35
|
-
- Start with `RALPH:` prefix
|
|
36
|
-
- Include the task completed and any PRD reference
|
|
37
|
-
- List key decisions made
|
|
38
|
-
- List files changed
|
|
39
|
-
- Note any blockers for the next iteration
|
|
40
|
-
6. **Close** — close the issue with `{{CLOSE_TASK_COMMAND}}` explaining what was done.
|
|
41
|
-
|
|
42
|
-
## Rules
|
|
43
|
-
|
|
44
|
-
- Work on **one issue per iteration**. Do not attempt multiple issues in a single iteration.
|
|
45
|
-
- Do not close an issue until you have committed the fix and verified tests pass.
|
|
46
|
-
- Do not leave commented-out code or TODO comments in committed code.
|
|
47
|
-
- If you are blocked (missing context, failing tests you cannot fix, external dependency), leave a comment on the issue and move on — do not close it.
|
|
10
|
+
<handoff>...</handoff>
|
|
11
|
+
<promise>COMPLETE</promise>
|
|
48
12
|
|
|
49
|
-
|
|
13
|
+
## Resolved scope
|
|
50
14
|
|
|
51
|
-
|
|
15
|
+
```json
|
|
16
|
+
{{SCOPE}}
|
|
17
|
+
```
|
|
52
18
|
|
|
53
|
-
|
|
19
|
+
Read the complete parent and child issue bodies and comments with `gh issue view`, including repository guidance and dependency links.
|