@iceinvein/agent-skills 0.21.4 → 0.22.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 +9 -0
- package/package.json +4 -1
- package/skills/bug-hunter/SKILL.md +82 -0
- package/skills/bug-hunter/evals/README.md +282 -0
- package/skills/bug-hunter/evals/baselines/claude-opus-5-5.json +93 -0
- package/skills/bug-hunter/evals/bug-cites-intent-from-spec/case.yaml +4 -0
- package/skills/bug-hunter/evals/bug-cites-intent-from-spec/fixture.sh +71 -0
- package/skills/bug-hunter/evals/bug-cites-intent-from-spec/graders/bug-cites-the-spec.md +36 -0
- package/skills/bug-hunter/evals/bug-cites-intent-from-spec/graders/bug-hunter-fired.md +5 -0
- package/skills/bug-hunter/evals/bug-cites-intent-from-spec/graders/intent-written.md +8 -0
- package/skills/bug-hunter/evals/bug-cites-intent-from-spec/prompt.md +11 -0
- package/skills/bug-hunter/evals/callers-tree-unchanged/case.yaml +4 -0
- package/skills/bug-hunter/evals/callers-tree-unchanged/fixture.sh +67 -0
- package/skills/bug-hunter/evals/callers-tree-unchanged/graders/bug-hunter-fired.md +5 -0
- package/skills/bug-hunter/evals/callers-tree-unchanged/graders/source-unchanged.md +10 -0
- package/skills/bug-hunter/evals/callers-tree-unchanged/graders/tests-unchanged.md +10 -0
- package/skills/bug-hunter/evals/callers-tree-unchanged/graders/tree-unchanged.md +15 -0
- package/skills/bug-hunter/evals/callers-tree-unchanged/prompt.md +11 -0
- package/skills/bug-hunter/evals/correct-code-has-no-bugs/case.yaml +4 -0
- package/skills/bug-hunter/evals/correct-code-has-no-bugs/fixture.sh +82 -0
- package/skills/bug-hunter/evals/correct-code-has-no-bugs/graders/bug-hunter-fired.md +5 -0
- package/skills/bug-hunter/evals/correct-code-has-no-bugs/graders/bugs-empty.md +18 -0
- package/skills/bug-hunter/evals/correct-code-has-no-bugs/graders/written-with-write-tool.md +13 -0
- package/skills/bug-hunter/evals/correct-code-has-no-bugs/prompt.md +11 -0
- package/skills/bug-hunter/evals/impossible-value-is-a-bug/case.yaml +4 -0
- package/skills/bug-hunter/evals/impossible-value-is-a-bug/fixture.sh +68 -0
- package/skills/bug-hunter/evals/impossible-value-is-a-bug/graders/bug-for-sixty-seconds.md +37 -0
- package/skills/bug-hunter/evals/impossible-value-is-a-bug/graders/bug-hunter-fired.md +5 -0
- package/skills/bug-hunter/evals/impossible-value-is-a-bug/prompt.md +11 -0
- package/skills/bug-hunter/evals/proves-a-spec-boundary-bug/case.yaml +4 -0
- package/skills/bug-hunter/evals/proves-a-spec-boundary-bug/fixture.sh +79 -0
- package/skills/bug-hunter/evals/proves-a-spec-boundary-bug/graders/bug-cites-the-limit.md +31 -0
- package/skills/bug-hunter/evals/proves-a-spec-boundary-bug/graders/bug-hunter-fired.md +5 -0
- package/skills/bug-hunter/evals/proves-a-spec-boundary-bug/graders/findings-written.md +10 -0
- package/skills/bug-hunter/evals/proves-a-spec-boundary-bug/prompt.md +11 -0
- package/skills/bug-hunter/evals/rules.json +177 -0
- package/skills/bug-hunter/evals/silent-spec-is-an-ambiguity/case.yaml +4 -0
- package/skills/bug-hunter/evals/silent-spec-is-an-ambiguity/fixture.sh +97 -0
- package/skills/bug-hunter/evals/silent-spec-is-an-ambiguity/graders/bug-hunter-fired.md +5 -0
- package/skills/bug-hunter/evals/silent-spec-is-an-ambiguity/graders/bugs-empty.md +18 -0
- package/skills/bug-hunter/evals/silent-spec-is-an-ambiguity/graders/cap-is-an-ambiguity.md +12 -0
- package/skills/bug-hunter/evals/silent-spec-is-an-ambiguity/graders/written-with-write-tool.md +13 -0
- package/skills/bug-hunter/evals/silent-spec-is-an-ambiguity/prompt.md +11 -0
- package/skills/bug-hunter/evals/spec-outranks-target-docstring/case.yaml +4 -0
- package/skills/bug-hunter/evals/spec-outranks-target-docstring/fixture.sh +82 -0
- package/skills/bug-hunter/evals/spec-outranks-target-docstring/graders/bug-cites-the-spec.md +40 -0
- package/skills/bug-hunter/evals/spec-outranks-target-docstring/graders/bug-hunter-fired.md +5 -0
- package/skills/bug-hunter/evals/spec-outranks-target-docstring/prompt.md +11 -0
- package/skills/bug-hunter/references/findings-format.md +82 -0
- package/skills/bug-hunter/references/scenario-catalog.md +92 -0
- package/skills/bug-hunter/skill.json +24 -0
- package/skills/index.json +17 -5
- package/skills/magpie/evals/README.md +13 -0
- package/skills/magpie/evals/baselines/claude-opus-5-5.json +69 -0
- package/skills/magpie/evals/rules.json +192 -0
- package/skills/magpie/skill.json +1 -1
- package/skills/migrate/evals/README.md +13 -0
- package/skills/migrate/evals/baselines/claude-opus-5-5.json +129 -0
- package/skills/migrate/evals/rules.json +160 -0
- package/skills/migrate/skill.json +10 -4
- package/skills/scope-estimate/README.md +173 -0
- package/skills/scope-estimate/SKILL.md +168 -0
- package/skills/scope-estimate/references/capability-map.md +122 -0
- package/skills/scope-estimate/references/deliverables.md +60 -0
- package/skills/scope-estimate/references/requirements.md +104 -0
- package/skills/scope-estimate/references/schedule.md +138 -0
- package/skills/scope-estimate/scripts/__tests__/schedule.test.ts +366 -0
- package/skills/scope-estimate/scripts/schedule.ts +553 -0
- package/skills/scope-estimate/skill.json +19 -0
- package/skills/scope-estimate/templates/plan.example.json +33 -0
- package/skills/sluice/SKILL.md +35 -12
- package/skills/sluice/evals/README.md +111 -6
- package/skills/sluice/evals/announcement-reaches-the-ledger/fixture.sh +32 -19
- package/skills/sluice/evals/announcement-reaches-the-ledger/graders/routes-main-before-source.md +14 -0
- package/skills/sluice/evals/announcement-reaches-the-ledger/prompt.md +2 -2
- package/skills/sluice/evals/baselines/claude-opus-5-5.json +237 -0
- package/skills/sluice/evals/deep-run-blocks-on-a-real-decision/fixture.sh +19 -12
- package/skills/sluice/evals/deep-run-fans-out/graders/task-1-merged-back.md +8 -0
- package/skills/sluice/evals/deep-run-fans-out/graders/task-2-merged-back.md +8 -0
- package/skills/sluice/evals/deep-run-fans-out/graders/task-3-merged-back.md +6 -0
- package/skills/sluice/evals/deep-run-fans-out/graders/worktree-per-implementer.md +12 -0
- package/skills/sluice/evals/deep-run-hunts-after-the-flip/case.yaml +5 -0
- package/skills/sluice/evals/deep-run-hunts-after-the-flip/fixture.sh +258 -0
- package/skills/sluice/evals/deep-run-hunts-after-the-flip/graders/bug-hunter-fired.md +5 -0
- package/skills/sluice/evals/deep-run-hunts-after-the-flip/graders/four-attempts-allowed.md +34 -0
- package/skills/sluice/evals/deep-run-hunts-after-the-flip/graders/fourth-attempt-tested.md +20 -0
- package/skills/sluice/evals/deep-run-hunts-after-the-flip/graders/hunt-before-the-final-review.md +21 -0
- package/skills/sluice/evals/deep-run-hunts-after-the-flip/graders/record-has-a-hunt-section.md +11 -0
- package/skills/sluice/evals/deep-run-hunts-after-the-flip/graders/sluice-fired.md +5 -0
- package/skills/sluice/evals/deep-run-hunts-after-the-flip/prompt.md +12 -0
- package/skills/sluice/evals/deep-run-survives-a-milestone/fixture.sh +46 -24
- package/skills/sluice/evals/fast-verifies-before-handback/case.yaml +4 -0
- package/skills/sluice/evals/fast-verifies-before-handback/fixture.sh +73 -0
- package/skills/sluice/evals/fast-verifies-before-handback/graders/quiet-flag-implemented.md +5 -0
- package/skills/sluice/evals/fast-verifies-before-handback/graders/sluice-fired.md +5 -0
- package/skills/sluice/evals/fast-verifies-before-handback/graders/verified-after-the-last-edit.md +18 -0
- package/skills/sluice/evals/fast-verifies-before-handback/prompt.md +11 -0
- package/skills/sluice/evals/main-hunts-a-checkable-claim/case.yaml +5 -0
- package/skills/sluice/evals/main-hunts-a-checkable-claim/fixture.sh +64 -0
- package/skills/sluice/evals/main-hunts-a-checkable-claim/graders/announces-main-channel.md +12 -0
- package/skills/sluice/evals/main-hunts-a-checkable-claim/graders/bug-hunter-fired.md +5 -0
- package/skills/sluice/evals/main-hunts-a-checkable-claim/graders/hunt-after-the-source-edits.md +12 -0
- package/skills/sluice/evals/main-hunts-a-checkable-claim/graders/last-page-holds-the-remainder.md +27 -0
- package/skills/sluice/evals/main-hunts-a-checkable-claim/graders/remainder-page-tested.md +21 -0
- package/skills/sluice/evals/main-hunts-a-checkable-claim/graders/sluice-fired.md +5 -0
- package/skills/sluice/evals/main-hunts-a-checkable-claim/prompt.md +12 -0
- package/skills/sluice/evals/main-new-interface/graders/shape-agreed-before-building.md +8 -4
- package/skills/sluice/evals/main-skips-the-hunt-without-a-claim/case.yaml +5 -0
- package/skills/sluice/evals/main-skips-the-hunt-without-a-claim/fixture.sh +73 -0
- package/skills/sluice/evals/main-skips-the-hunt-without-a-claim/graders/announces-main-channel.md +12 -0
- package/skills/sluice/evals/main-skips-the-hunt-without-a-claim/graders/no-hunt.md +12 -0
- package/skills/sluice/evals/main-skips-the-hunt-without-a-claim/graders/skip-said-in-one-line.md +21 -0
- package/skills/sluice/evals/main-skips-the-hunt-without-a-claim/graders/sluice-fired.md +5 -0
- package/skills/sluice/evals/main-skips-the-hunt-without-a-claim/prompt.md +12 -0
- package/skills/sluice/evals/rules.json +364 -0
- package/skills/sluice/references/deep-channel.md +64 -18
- package/skills/sluice/references/finish.md +8 -2
- package/skills/sluice/references/intent.md +4 -2
- package/skills/sluice/references/review.md +32 -0
- package/skills/sluice/references/status.md +17 -4
- package/skills/sluice/references/verify.md +38 -0
- package/skills/sluice/scripts/content-digest.sh +65 -0
- package/skills/sluice/scripts/run-stats.sh +9 -8
- package/skills/sluice/scripts/status.sh +78 -13
- package/skills/sluice/scripts/stop-guard.sh +97 -24
- package/skills/sluice/skill.json +2 -2
- package/skills/terse/evals/README.md +16 -0
- package/skills/terse/evals/baselines/claude-opus-5-5.json +105 -0
- package/skills/terse/evals/rules.json +161 -0
- package/skills/terse/skill.json +1 -1
- package/skills/sluice/evals/announcement-reaches-the-ledger/graders/escalates-fast-to-main.md +0 -12
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
# design stop missed: API.md publishes the three step lines as stdout contract,
|
|
4
4
|
# a ground rule forbids breaking anything API.md names without a decision, and
|
|
5
5
|
# Task 4's own Flips line suppresses exactly those lines. Tasks 2 and 3 are
|
|
6
|
-
# untouched by it and must still land. Task
|
|
6
|
+
# untouched by it and must still land. Task 3's sink goes through `runSteps`,
|
|
7
|
+
# which `src/cli/index.js` does not export, and API.md says so: a sink on the
|
|
8
|
+
# public `deploy` would let any downstream caller drop the lines, and a run
|
|
9
|
+
# that blocked Task 3 on that was reading the contract correctly. Task 4 cannot be resolved in code: either
|
|
7
10
|
# the contract breaks or the flag means something else, and that is not the
|
|
8
11
|
# agent's call.
|
|
9
12
|
set -euo pipefail
|
|
@@ -131,7 +134,9 @@ cat > API.md <<'MD'
|
|
|
131
134
|
## Modules
|
|
132
135
|
|
|
133
136
|
`shipit` publishes `deploy` and `parseArgs` from `src/cli/index.js`. Both have
|
|
134
|
-
been importable by downstream packages since 1.0.0.
|
|
137
|
+
been importable by downstream packages since 1.0.0. The modules behind
|
|
138
|
+
`src/cli/index.js` are internal: nothing outside this package imports them, and
|
|
139
|
+
they may change in any release.
|
|
135
140
|
|
|
136
141
|
## stdout contract
|
|
137
142
|
|
|
@@ -157,8 +162,10 @@ final summary line untouched. Every other deploy output is unchanged.
|
|
|
157
162
|
## Architecture
|
|
158
163
|
|
|
159
164
|
Progress lines go through a sink instead of `console.log`. The sink is built
|
|
160
|
-
from the parsed flags and handed to `
|
|
161
|
-
|
|
165
|
+
from the parsed flags and handed to `runSteps`, an internal step runner behind
|
|
166
|
+
`deploy`, so the runner never asks whether it is quiet. The public `deploy(args)`
|
|
167
|
+
keeps its signature and always prints. The summary line does not go through the
|
|
168
|
+
sink.
|
|
162
169
|
|
|
163
170
|
## Ground Rules
|
|
164
171
|
|
|
@@ -190,22 +197,22 @@ is quiet. The summary line does not go through the sink.
|
|
|
190
197
|
|
|
191
198
|
### Task 3: route progress through the sink
|
|
192
199
|
|
|
193
|
-
**Contract:** Needs: `createSink({ quiet: boolean }) -> { write(line: string): void }` | Offers: `
|
|
200
|
+
**Contract:** Needs: `createSink({ quiet: boolean }) -> { write(line: string): void }` | Offers: `runSteps(args: { dryRun: boolean }, sink: { write(line: string): void }) -> void`
|
|
194
201
|
**Touches:** src/cli/deploy.js (edit) | tests/deploy.test.js (test)
|
|
195
202
|
|
|
196
|
-
- [ ] Add a test passing a recording sink to `
|
|
197
|
-
- [ ]
|
|
198
|
-
- [ ] Keep the summary line on `console.log` and keep both original deploy tests, which call `deploy(args)
|
|
203
|
+
- [ ] Add a test passing a recording sink to `runSteps` and asserting the three `-> <step>` lines arrive on the sink -> the new test fails because src/cli/deploy.js has no `runSteps`
|
|
204
|
+
- [ ] Move the step loop into `runSteps(args, sink)`, exported from src/cli/deploy.js, sending each `-> <step>` line to `sink.write`, and make `deploy(args)` call it with `createSink({ quiet: false })` -> the new test passes
|
|
205
|
+
- [ ] Keep the summary line on `console.log`, leave `src/cli/index.js` exporting only `deploy` and `parseArgs`, and keep both original deploy tests, which call `deploy(args)`, asserting the exact lines `dry run: 3 steps skipped` and `deployed: 3 steps` -> `npm test` green
|
|
199
206
|
|
|
200
207
|
### Task 4: turn quiet on
|
|
201
208
|
|
|
202
|
-
**Contract:** Needs: `parseArgs(argv: string[]) -> { dryRun: boolean, quiet: boolean }`, `createSink({ quiet: boolean }) -> { write(line: string): void }`, `
|
|
209
|
+
**Contract:** Needs: `parseArgs(argv: string[]) -> { dryRun: boolean, quiet: boolean }`, `createSink({ quiet: boolean }) -> { write(line: string): void }`, `runSteps(args: { dryRun: boolean }, sink: { write(line: string): void }) -> void` | Offers: `main(argv: string[]) -> void`
|
|
203
210
|
**Touches:** src/cli/main.js (new) | tests/main.test.js (test)
|
|
204
211
|
**Flips:** the three progress lines become suppressible; before this task `--quiet` parses and changes nothing
|
|
205
212
|
|
|
206
213
|
- [ ] Add a test asserting `main(["--quiet"])` prints only `deployed: 3 steps` -> it fails because src/cli/main.js does not exist
|
|
207
214
|
- [ ] Add a test asserting `main([])` prints the three step lines and then `deployed: 3 steps` -> it fails for the same reason
|
|
208
|
-
- [ ] Write `main` to call `parseArgs`, build the sink from `quiet`, and call `
|
|
215
|
+
- [ ] Write `main` to call `parseArgs`, build the sink from `quiet`, and call `runSteps` with both -> `npm test` green
|
|
209
216
|
MD
|
|
210
217
|
|
|
211
218
|
cat > docs/plans/2026-09-20-quiet-flag-record.md <<'MD'
|
|
@@ -281,9 +288,9 @@ cat > .sluice/run.json <<'JSON'
|
|
|
281
288
|
"offers": [
|
|
282
289
|
"args",
|
|
283
290
|
"boolean",
|
|
284
|
-
"deploy",
|
|
285
291
|
"dryRun",
|
|
286
292
|
"line",
|
|
293
|
+
"runSteps",
|
|
287
294
|
"sink",
|
|
288
295
|
"string",
|
|
289
296
|
"void",
|
|
@@ -302,8 +309,8 @@ cat > .sluice/run.json <<'JSON'
|
|
|
302
309
|
"flips": true,
|
|
303
310
|
"needs": [
|
|
304
311
|
"createSink",
|
|
305
|
-
"deploy",
|
|
306
312
|
"parseArgs",
|
|
313
|
+
"runSteps",
|
|
307
314
|
"write"
|
|
308
315
|
],
|
|
309
316
|
"offers": [
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: regex
|
|
3
|
+
# Task 1 commits in its own worktree, so this file reaches the run's tree only
|
|
4
|
+
# when that branch is merged back. The files target cannot show it: it lists
|
|
5
|
+
# only paths Claude created, and a merge is not certain to count.
|
|
6
|
+
pattern: '\bformatDuration\b'
|
|
7
|
+
target: { source: file, path: src/cli/duration.js }
|
|
8
|
+
---
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: regex
|
|
3
|
+
# As task-1-merged-back. args.js comes from the scaffold, so only its content
|
|
4
|
+
# can show Task 2 arrived; the files target never lists a file Claude only
|
|
5
|
+
# modified.
|
|
6
|
+
pattern: '\btimings\b'
|
|
7
|
+
target: { source: file, path: src/cli/args.js }
|
|
8
|
+
---
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: regex
|
|
3
|
+
# Pre-flight settled one worktree per concurrent implementer, and three tasks
|
|
4
|
+
# run at once, so three worktrees have to be cut: one loop over the three tasks
|
|
5
|
+
# or three separate adds. Agent's `isolation: worktree` alone does not count,
|
|
6
|
+
# because a session in worktree mode refuses those switches and every kept run
|
|
7
|
+
# that tried it went on to cut the trees by hand. What this cannot see is
|
|
8
|
+
# whether each implementer was then told to work in its own tree.
|
|
9
|
+
pattern: '"command":"(?:[^"\\]|\\.)*?(?:\\[nt]|\b)for \w+ in 1 2 3\b(?:[^"\\]|\\.)*?git worktree add|(?:"command":"(?:[^"\\]|\\.)*?git worktree add[\s\S]*?){3}'
|
|
10
|
+
target: trace
|
|
11
|
+
weight: 2
|
|
12
|
+
---
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# A deep run with every task done, the flip included, and nothing left but the
|
|
3
|
+
# hunt, the final review and the finish. The spec states a checkable limit (a
|
|
4
|
+
# step runs at most four times) and the flip's commit breaks it by one: its
|
|
5
|
+
# loop gives up after three attempts. The suite only covers a step that
|
|
6
|
+
# succeeds by its third attempt and a step that never does, so it is green,
|
|
7
|
+
# and the bug is there for the hunt over <base>..HEAD to prove.
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
mkdir -p src/cli tests docs/specs docs/plans .sluice
|
|
11
|
+
printf '*\n' > .sluice/.gitignore
|
|
12
|
+
git init --quiet
|
|
13
|
+
|
|
14
|
+
commit() {
|
|
15
|
+
git add -A
|
|
16
|
+
git -c user.email=fixture@example.com -c user.name=fixture commit --quiet -m "$1"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
cat > package.json <<'JSON'
|
|
20
|
+
{
|
|
21
|
+
"name": "shipit",
|
|
22
|
+
"version": "1.3.0",
|
|
23
|
+
"type": "module",
|
|
24
|
+
"scripts": {
|
|
25
|
+
"test": "node --test \"tests/*.test.js\""
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
JSON
|
|
29
|
+
|
|
30
|
+
cat > src/cli/deploy.js <<'JS'
|
|
31
|
+
const STEPS = ["build", "upload", "activate"];
|
|
32
|
+
|
|
33
|
+
export function deploy(runStep, log = console.log) {
|
|
34
|
+
for (const step of STEPS) {
|
|
35
|
+
log(`-> ${step}`);
|
|
36
|
+
runStep(step);
|
|
37
|
+
}
|
|
38
|
+
log("deployed: 3 steps");
|
|
39
|
+
}
|
|
40
|
+
JS
|
|
41
|
+
|
|
42
|
+
cat > tests/deploy.test.js <<'JS'
|
|
43
|
+
import assert from "node:assert/strict";
|
|
44
|
+
import { test } from "node:test";
|
|
45
|
+
import { deploy } from "../src/cli/deploy.js";
|
|
46
|
+
|
|
47
|
+
test("a full deploy reports every step", () => {
|
|
48
|
+
const lines = [];
|
|
49
|
+
deploy(() => {}, (line) => lines.push(line));
|
|
50
|
+
assert.deepEqual(lines, ["-> build", "-> upload", "-> activate", "deployed: 3 steps"]);
|
|
51
|
+
});
|
|
52
|
+
JS
|
|
53
|
+
|
|
54
|
+
cat > docs/specs/2026-10-01-step-retries.md <<'MD'
|
|
55
|
+
# Design: step retries
|
|
56
|
+
|
|
57
|
+
## Context
|
|
58
|
+
|
|
59
|
+
A deploy step that fails once on a flaky network ends the whole deploy today.
|
|
60
|
+
|
|
61
|
+
## Decisions
|
|
62
|
+
|
|
63
|
+
- **D1 The limit.** A failed step is retried up to three times, so it runs at
|
|
64
|
+
most four times in all. `MAX_ATTEMPTS` in `src/cli/retry-policy.js` is 4,
|
|
65
|
+
and it is the only place the number lives.
|
|
66
|
+
- **D2 Giving up.** When the fourth attempt fails, the deploy stops with
|
|
67
|
+
`step <name> failed after 4 attempts` and runs no later step.
|
|
68
|
+
- **D3 Success.** A step that succeeds on any attempt up to the fourth lets
|
|
69
|
+
the deploy carry on to the next step, and the progress lines are unchanged.
|
|
70
|
+
MD
|
|
71
|
+
|
|
72
|
+
cat > docs/plans/2026-10-01-step-retries.md <<'MD'
|
|
73
|
+
# Plan: step-retries
|
|
74
|
+
|
|
75
|
+
## Goal
|
|
76
|
+
|
|
77
|
+
A failed deploy step is retried up to three times (four attempts in all), as
|
|
78
|
+
`docs/specs/2026-10-01-step-retries.md` states.
|
|
79
|
+
|
|
80
|
+
## Architecture
|
|
81
|
+
|
|
82
|
+
The limit lives in one module, `src/cli/retry-policy.js`. `deploy` reads it and
|
|
83
|
+
wraps each step in the retry loop; nothing else changes.
|
|
84
|
+
|
|
85
|
+
## Ground Rules
|
|
86
|
+
|
|
87
|
+
- Commit message convention: `<type>(<scope>): <subject>`, subject lower case,
|
|
88
|
+
no trailing full stop, no attribution trailers and no tool footers.
|
|
89
|
+
- Test runner: `npm test`, which runs `node --test "tests/*.test.js"`.
|
|
90
|
+
- Node built-ins only. No dependency may be added to package.json.
|
|
91
|
+
|
|
92
|
+
### Task 1: retry policy
|
|
93
|
+
|
|
94
|
+
**Contract:** Needs: none | Offers: `MAX_ATTEMPTS: number`
|
|
95
|
+
**Touches:** src/cli/retry-policy.js (new) | tests/retry-policy.test.js (new)
|
|
96
|
+
|
|
97
|
+
- [x] Add a test asserting `MAX_ATTEMPTS` is 4 -> it fails because src/cli/retry-policy.js does not exist
|
|
98
|
+
- [x] Export `MAX_ATTEMPTS = 4` -> `npm test` green
|
|
99
|
+
|
|
100
|
+
### Task 2: retry each step
|
|
101
|
+
|
|
102
|
+
**Contract:** Needs: `MAX_ATTEMPTS: number` | Offers: `deploy(runStep: (step: string) => void, log?: (line: string) => void) -> void`
|
|
103
|
+
**Touches:** src/cli/deploy.js (edit) | tests/deploy.test.js (test)
|
|
104
|
+
**Flips:** a failed step is retried instead of ending the deploy
|
|
105
|
+
|
|
106
|
+
- [x] Add a test where `runStep` fails twice on `upload` and then succeeds, asserting the deploy finishes -> it fails because deploy does not retry
|
|
107
|
+
- [x] Add a test where `runStep` always fails on `upload`, asserting the deploy throws and never reaches `activate` -> it fails for the same reason
|
|
108
|
+
- [x] Wrap each step in a loop bounded by `MAX_ATTEMPTS` -> `npm test` green
|
|
109
|
+
MD
|
|
110
|
+
|
|
111
|
+
commit "chore: shipit 1.3.0 with the step-retries design and plan"
|
|
112
|
+
BASE="$(git rev-parse HEAD)"
|
|
113
|
+
|
|
114
|
+
cat > src/cli/retry-policy.js <<'JS'
|
|
115
|
+
export const MAX_ATTEMPTS = 4;
|
|
116
|
+
JS
|
|
117
|
+
|
|
118
|
+
cat > tests/retry-policy.test.js <<'JS'
|
|
119
|
+
import assert from "node:assert/strict";
|
|
120
|
+
import { test } from "node:test";
|
|
121
|
+
import { MAX_ATTEMPTS } from "../src/cli/retry-policy.js";
|
|
122
|
+
|
|
123
|
+
test("a step runs at most four times", () => {
|
|
124
|
+
assert.equal(MAX_ATTEMPTS, 4);
|
|
125
|
+
});
|
|
126
|
+
JS
|
|
127
|
+
|
|
128
|
+
commit "feat(retry): add the retry policy"
|
|
129
|
+
TASK1="$(git rev-parse HEAD)"
|
|
130
|
+
|
|
131
|
+
cat > src/cli/deploy.js <<'JS'
|
|
132
|
+
import { MAX_ATTEMPTS } from "./retry-policy.js";
|
|
133
|
+
|
|
134
|
+
const STEPS = ["build", "upload", "activate"];
|
|
135
|
+
|
|
136
|
+
export function deploy(runStep, log = console.log) {
|
|
137
|
+
for (const step of STEPS) {
|
|
138
|
+
log(`-> ${step}`);
|
|
139
|
+
let attempt = 1;
|
|
140
|
+
while (true) {
|
|
141
|
+
try {
|
|
142
|
+
runStep(step);
|
|
143
|
+
break;
|
|
144
|
+
} catch {
|
|
145
|
+
if (attempt >= MAX_ATTEMPTS - 1) {
|
|
146
|
+
throw new Error(`step ${step} failed after ${attempt} attempts`);
|
|
147
|
+
}
|
|
148
|
+
attempt++;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
log("deployed: 3 steps");
|
|
153
|
+
}
|
|
154
|
+
JS
|
|
155
|
+
|
|
156
|
+
cat > tests/deploy.test.js <<'JS'
|
|
157
|
+
import assert from "node:assert/strict";
|
|
158
|
+
import { test } from "node:test";
|
|
159
|
+
import { deploy } from "../src/cli/deploy.js";
|
|
160
|
+
|
|
161
|
+
function failing(step, times) {
|
|
162
|
+
let failures = 0;
|
|
163
|
+
const ran = [];
|
|
164
|
+
return {
|
|
165
|
+
ran,
|
|
166
|
+
runStep(name) {
|
|
167
|
+
ran.push(name);
|
|
168
|
+
if (name === step && failures < times) {
|
|
169
|
+
failures++;
|
|
170
|
+
throw new Error(`${name} is down`);
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
test("a full deploy reports every step", () => {
|
|
177
|
+
const lines = [];
|
|
178
|
+
deploy(() => {}, (line) => lines.push(line));
|
|
179
|
+
assert.deepEqual(lines, ["-> build", "-> upload", "-> activate", "deployed: 3 steps"]);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("a step that fails twice and then succeeds lets the deploy finish", () => {
|
|
183
|
+
const lines = [];
|
|
184
|
+
const upload = failing("upload", 2);
|
|
185
|
+
deploy(upload.runStep, (line) => lines.push(line));
|
|
186
|
+
assert.equal(lines.at(-1), "deployed: 3 steps");
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("a step that never succeeds stops the deploy before the next step", () => {
|
|
190
|
+
const upload = failing("upload", Infinity);
|
|
191
|
+
assert.throws(() => deploy(upload.runStep, () => {}), /failed/);
|
|
192
|
+
assert.equal(upload.ran.includes("activate"), false);
|
|
193
|
+
});
|
|
194
|
+
JS
|
|
195
|
+
|
|
196
|
+
commit "feat(deploy): retry a failed step"
|
|
197
|
+
TASK2="$(git rev-parse HEAD)"
|
|
198
|
+
|
|
199
|
+
cat > docs/plans/2026-10-01-step-retries-record.md <<MD
|
|
200
|
+
# Run record: step-retries
|
|
201
|
+
|
|
202
|
+
## Pre-flight
|
|
203
|
+
|
|
204
|
+
- Review: tier 3 only. Task 2 is the flip and is the one task that got a reviewer.
|
|
205
|
+
- Effort: this session's effort for both tasks.
|
|
206
|
+
- Workspace: shared tree. Tasks ran serially, so no worktree was cut.
|
|
207
|
+
- Base: \`${BASE}\`, the commit the plan landed on.
|
|
208
|
+
|
|
209
|
+
## Log
|
|
210
|
+
|
|
211
|
+
- Task 1, retry policy: landed at \`${TASK1}\`. Inert, nothing reads it yet.
|
|
212
|
+
- Task 2, retry each step: landed at \`${TASK2}\`, the flip. Reviewed, no
|
|
213
|
+
findings.
|
|
214
|
+
MD
|
|
215
|
+
|
|
216
|
+
commit "docs(plans): step-retries run record"
|
|
217
|
+
|
|
218
|
+
cat > .sluice/run.json <<JSON
|
|
219
|
+
{
|
|
220
|
+
"schema": 1,
|
|
221
|
+
"topic": "step-retries",
|
|
222
|
+
"channel": "deep",
|
|
223
|
+
"started": "2026-10-01T09:02:11Z",
|
|
224
|
+
"plan": "docs/plans/2026-10-01-step-retries.md",
|
|
225
|
+
"record": "docs/plans/2026-10-01-step-retries-record.md",
|
|
226
|
+
"tasks": [
|
|
227
|
+
{
|
|
228
|
+
"id": 1,
|
|
229
|
+
"status": "done",
|
|
230
|
+
"name": "retry policy",
|
|
231
|
+
"tier": 2,
|
|
232
|
+
"base": "${BASE}",
|
|
233
|
+
"commit": "${TASK1}",
|
|
234
|
+
"offers": ["MAX_ATTEMPTS", "number"],
|
|
235
|
+
"touches": ["src/cli/retry-policy.js", "tests/retry-policy.test.js"]
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
"id": 2,
|
|
239
|
+
"status": "done",
|
|
240
|
+
"name": "retry each step",
|
|
241
|
+
"tier": 3,
|
|
242
|
+
"flips": true,
|
|
243
|
+
"reviewed": true,
|
|
244
|
+
"base": "${TASK1}",
|
|
245
|
+
"commit": "${TASK2}",
|
|
246
|
+
"needs": ["MAX_ATTEMPTS", "number"],
|
|
247
|
+
"offers": ["deploy", "line", "log", "runStep", "step", "string", "void"],
|
|
248
|
+
"touches": ["src/cli/deploy.js", "tests/deploy.test.js"]
|
|
249
|
+
}
|
|
250
|
+
],
|
|
251
|
+
"updated": "2026-10-01T10:41:53Z",
|
|
252
|
+
"preflight": {
|
|
253
|
+
"review": "tier3",
|
|
254
|
+
"effort": "0 of 2 low",
|
|
255
|
+
"workspace": "shared"
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
JSON
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: llm
|
|
3
|
+
# A regex over the loop accepts one spelling of the fix and rejects the rest
|
|
4
|
+
# (a for loop, a helper, a counter that starts at zero), so the judge reads the
|
|
5
|
+
# code and works the cases. The spec puts the number in MAX_ATTEMPTS alone, so
|
|
6
|
+
# a fix that raised the constant instead leaves this file buggy and fails here.
|
|
7
|
+
focus: { source: file, path: src/cli/deploy.js }
|
|
8
|
+
weight: 3
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
This is `src/cli/deploy.js` after the run. It imports `MAX_ATTEMPTS` from
|
|
12
|
+
`src/cli/retry-policy.js`; take its value as 4, which the spec fixes. The spec
|
|
13
|
+
says a failed step is retried up to three times, so it runs at most four times
|
|
14
|
+
in all; when the fourth attempt fails the deploy stops with
|
|
15
|
+
`step <name> failed after 4 attempts` and runs no later step.
|
|
16
|
+
|
|
17
|
+
The fixture's loop gave up when `attempt >= MAX_ATTEMPTS - 1`, so a step ran
|
|
18
|
+
three times at most and the error said `after 3 attempts`.
|
|
19
|
+
|
|
20
|
+
Work out, reading the code as written, what happens when `runStep` throws on
|
|
21
|
+
`upload`:
|
|
22
|
+
|
|
23
|
+
- it throws three times and then succeeds: the deploy must finish and log
|
|
24
|
+
`deployed: 3 steps`
|
|
25
|
+
- it always throws: `upload` must run exactly four times, the error must read
|
|
26
|
+
`step upload failed after 4 attempts`, and `activate` must never run
|
|
27
|
+
|
|
28
|
+
PASS if the code does all of that.
|
|
29
|
+
FAIL if a step that fails three times stops the deploy, if a step that always
|
|
30
|
+
fails runs any number of times other than four, if the error names another
|
|
31
|
+
count, or if a later step runs after a step gave up.
|
|
32
|
+
FAIL if `deploy.js` no longer reads `MAX_ATTEMPTS` from `retry-policy.js`
|
|
33
|
+
(for example a fix that writes the number 4 into the loop): the spec makes
|
|
34
|
+
that constant the only place the number lives.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: llm
|
|
3
|
+
# A proven bug is fixed test-first, so its repro becomes a test in the suite.
|
|
4
|
+
# This reads the scaffold's deploy test file, where the retry tests already
|
|
5
|
+
# live; a test written into a new file elsewhere is not seen here.
|
|
6
|
+
focus: { source: file, path: tests/deploy.test.js }
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
This is the deploy test file after the run. The fixture shipped three tests: a
|
|
10
|
+
full deploy reports every step, a step that fails twice and then succeeds lets
|
|
11
|
+
the deploy finish, and a step that never succeeds stops the deploy (asserting
|
|
12
|
+
only that the error matches `/failed/`). None of them pins the limit: the spec
|
|
13
|
+
says a step runs at most four times, and the shipped code gave up after three.
|
|
14
|
+
|
|
15
|
+
PASS if the file now has a test that pins the fourth attempt: for example a
|
|
16
|
+
step that fails three times and then succeeds lets the deploy finish, or a
|
|
17
|
+
step that always fails runs exactly four times, or the error reads
|
|
18
|
+
`failed after 4 attempts`.
|
|
19
|
+
FAIL if no assertion distinguishes three attempts from four, or if any of the
|
|
20
|
+
three original tests was deleted or loosened.
|
package/skills/sluice/evals/deep-run-hunts-after-the-flip/graders/hunt-before-the-final-review.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: regex
|
|
3
|
+
# Every task, the flip included, is done in the fixture, so any hunt in the
|
|
4
|
+
# trace comes after the flip; what is left to pin is that it lands before the
|
|
5
|
+
# final review. From the start of the trace, no Agent call and no
|
|
6
|
+
# `status.sh final` call may come before the bug-hunter Skill call, and an
|
|
7
|
+
# Agent call or a `status.sh final` must come after it. Marking `final` is a
|
|
8
|
+
# rule of its own (deep-run-finishes-without-checking-in), so a run that hunts,
|
|
9
|
+
# dispatches the review and forgets the mark still kept this one. `final` alone
|
|
10
|
+
# is not enough before the hunt, because it is marked after the review clears.
|
|
11
|
+
# An Agent call stands for the review: every task is
|
|
12
|
+
# already done, so there is no implementer to dispatch, and bug-hunter's own
|
|
13
|
+
# SKILL.md dispatches no agents, so any Agent call before the hunt can only be
|
|
14
|
+
# the final review.
|
|
15
|
+
# Both are anchored as tool inputs: the Skill call's own input, and a Bash
|
|
16
|
+
# command invoking status.sh (with an optional --dir) rather than quoting it,
|
|
17
|
+
# so SKILL.md's text, a grep or prose naming either does not count.
|
|
18
|
+
pattern: '^(?:(?!"name":"Agent"|"command":"(?:(?:[^"\\]|\\.)*?(?:[\s/]|\\[nt]))?status\.sh(?:\\"|'')?(?:(?:\s|\\[nt])+--dir(?:\s|\\[nt])+(?:[^"\\\s]|\\.)+)?(?:\s|\\[nt])+final(?=\s|\\[nt]|\\"|"|[;&|]))[\s\S])*?"name":"Skill","input":\{(?:[^{}]|\{[^{}]*\})*?"skill":"(?:[\w-]+:)?bug-hunter"[\s\S]*?(?:"name":"Agent"|"command":"(?:(?:[^"\\]|\\.)*?(?:[\s/]|\\[nt]))?status\.sh(?:\\"|'')?(?:(?:\s|\\[nt])+--dir(?:\s|\\[nt])+(?:[^"\\\s]|\\.)+)?(?:\s|\\[nt])+final(?=\s|\\[nt]|\\"|"|[;&|]))'
|
|
19
|
+
target: trace
|
|
20
|
+
weight: 3
|
|
21
|
+
---
|
package/skills/sluice/evals/deep-run-hunts-after-the-flip/graders/record-has-a-hunt-section.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: regex
|
|
3
|
+
# The hunt is the controller's work, so it is recorded in the run record, not
|
|
4
|
+
# in run.json: a `## Hunt` section the final review reads. The fixture's record
|
|
5
|
+
# has Pre-flight and Log only, and the record is the scaffold's file, which the
|
|
6
|
+
# files target never lists, so its content is read directly.
|
|
7
|
+
pattern: '^## Hunt\b'
|
|
8
|
+
flags: m
|
|
9
|
+
target: { source: file, path: docs/plans/2026-10-01-step-retries-record.md }
|
|
10
|
+
weight: 2
|
|
11
|
+
---
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deep-run-hunts-after-the-flip
|
|
3
|
+
description: The deep channel's hunt. Every task is done, the flip included, so before the final review the run hunts the whole plan diff against the spec, fixes the off-by-one it proves test-first, and records the hunt in the run record.
|
|
4
|
+
tags: [verify, hunt, deep, execution, scaffold, write]
|
|
5
|
+
plugins: ["plugins/sluice", "plugins/bug-hunter"]
|
|
6
|
+
max_turns: 100
|
|
7
|
+
timeout_seconds: 2400
|
|
8
|
+
allowed_tools: [Read, Glob, Grep, Skill, Write, Edit, Bash, Agent]
|
|
9
|
+
expected_outcome: Invokes bug-hunter on the plan's base..HEAD against docs/specs/2026-10-01-step-retries.md before marking the final review with status.sh final; the hunt proves a step runs three times where the spec says four, and the run fixes it with a test, writes a Hunt section into the run record, then runs the final review.
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
Both tasks in the plan have landed, the flip included; it's all in `.sluice/run.json`. Take the run from here to the handback.
|