@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
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# A retry backoff with a real bug (attempt 1 waits 200 ms where the spec says
|
|
3
|
+
# 100), so the hunt has repros to write, run and confirm, which is when files
|
|
4
|
+
# are most likely to leak into the caller's tree. The case is about where
|
|
5
|
+
# those files land, not about the bug.
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
mkdir -p src tests
|
|
9
|
+
|
|
10
|
+
cat > package.json <<'JSON'
|
|
11
|
+
{
|
|
12
|
+
"name": "uplink",
|
|
13
|
+
"version": "0.9.0",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "node --test \"tests/*.test.js\""
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
JSON
|
|
20
|
+
|
|
21
|
+
cat > SPEC.md <<'MD'
|
|
22
|
+
# Uplink retries
|
|
23
|
+
|
|
24
|
+
`backoffDelay(attempt)` in `src/backoff.js` returns how many milliseconds to
|
|
25
|
+
wait before retry number `attempt`.
|
|
26
|
+
|
|
27
|
+
- Attempt 1 waits 100 ms. Each later attempt waits twice as long as the one
|
|
28
|
+
before it.
|
|
29
|
+
- No wait is longer than 5000 ms: an attempt whose doubled delay would pass
|
|
30
|
+
5000 waits exactly 5000.
|
|
31
|
+
- `attempt` is a whole number of 1 or more; anything else throws a
|
|
32
|
+
`RangeError`.
|
|
33
|
+
MD
|
|
34
|
+
|
|
35
|
+
git init --quiet
|
|
36
|
+
git add -A
|
|
37
|
+
git -c user.email=fixture@example.com -c user.name=fixture commit --quiet -m "spec: uplink retry backoff"
|
|
38
|
+
|
|
39
|
+
cat > src/backoff.js <<'JS'
|
|
40
|
+
const BASE_MS = 100;
|
|
41
|
+
const MAX_MS = 5000;
|
|
42
|
+
|
|
43
|
+
export function backoffDelay(attempt) {
|
|
44
|
+
if (!Number.isInteger(attempt) || attempt < 1) {
|
|
45
|
+
throw new RangeError(`attempt must be a whole number of 1 or more, got ${attempt}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return Math.min(BASE_MS * 2 ** attempt, MAX_MS);
|
|
49
|
+
}
|
|
50
|
+
JS
|
|
51
|
+
|
|
52
|
+
cat > tests/backoff.test.js <<'JS'
|
|
53
|
+
import assert from "node:assert/strict";
|
|
54
|
+
import { test } from "node:test";
|
|
55
|
+
import { backoffDelay } from "../src/backoff.js";
|
|
56
|
+
|
|
57
|
+
test("a late attempt waits the maximum", () => {
|
|
58
|
+
assert.equal(backoffDelay(10), 5000);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("attempt 0 throws", () => {
|
|
62
|
+
assert.throws(() => backoffDelay(0), RangeError);
|
|
63
|
+
});
|
|
64
|
+
JS
|
|
65
|
+
|
|
66
|
+
git add -A
|
|
67
|
+
git -c user.email=fixture@example.com -c user.name=fixture commit --quiet -m "feat: back off between uplink retries"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: regex
|
|
3
|
+
# tree-unchanged reads the files target, which lists only paths Claude
|
|
4
|
+
# created, so an edit to a scaffold file never reaches it. A fix made in place
|
|
5
|
+
# would rewrite the buggy line, so its survival is what shows src/backoff.js
|
|
6
|
+
# was left alone.
|
|
7
|
+
pattern: 'return Math\.min\(BASE_MS \* 2 \*\* attempt, MAX_MS\);'
|
|
8
|
+
target: { source: file, path: src/backoff.js }
|
|
9
|
+
weight: 3
|
|
10
|
+
---
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: regex
|
|
3
|
+
# As source-unchanged: a repro appended to the scaffold's own test file is an
|
|
4
|
+
# edit, which the files target never lists. The scaffold has two tests, so a
|
|
5
|
+
# third means the hunt wrote into the caller's suite.
|
|
6
|
+
pattern: '\b(?:test|it)\('
|
|
7
|
+
match: 'count:2'
|
|
8
|
+
target: { source: file, path: tests/backoff.test.js }
|
|
9
|
+
weight: 3
|
|
10
|
+
---
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: regex
|
|
3
|
+
# A grader cannot run `git status --porcelain`, so this reads the harness's own
|
|
4
|
+
# list of files the run created in the working directory, one path per line,
|
|
5
|
+
# relative to it. A file .gitignore hides still shows here. .git/ is excluded
|
|
6
|
+
# because git keeps its worktree bookkeeping there and porcelain never reports
|
|
7
|
+
# it. Fails on any line naming a path outside .bug-hunter/. The list leaves out
|
|
8
|
+
# files the run only modified, so source-unchanged and tests-unchanged cover
|
|
9
|
+
# edits to the scaffold's own files.
|
|
10
|
+
pattern: '^(?!\.bug-hunter/|\.git/).+'
|
|
11
|
+
flags: m
|
|
12
|
+
match: not_contains
|
|
13
|
+
target: files
|
|
14
|
+
weight: 3
|
|
15
|
+
---
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: callers-tree-unchanged
|
|
3
|
+
description: The skill hunts in a scratch worktree and writes only under .bug-hunter/. Pins the caller's tree left as it was, with repros written and run along the way.
|
|
4
|
+
tags: [tree, worktree, scaffold, write]
|
|
5
|
+
max_turns: 80
|
|
6
|
+
timeout_seconds: 2400
|
|
7
|
+
allowed_tools: [Read, Glob, Grep, Skill, Write, Edit, Bash, Agent]
|
|
8
|
+
expected_outcome: Every file the run created or changed in the caller's tree is under .bug-hunter/.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
Hunt for bugs in `src/backoff.js` against `SPEC.md` before QA gets to it.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# A pagination helper that does everything SPEC.md says, limits included: the
|
|
3
|
+
# spec states every range and every error, and the code meets each one at its
|
|
4
|
+
# edge. The existing tests cover only the middle, so the boundaries are left
|
|
5
|
+
# for the hunt to probe and find correct. A bug reported here is a false
|
|
6
|
+
# positive.
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
mkdir -p src tests
|
|
10
|
+
|
|
11
|
+
cat > package.json <<'JSON'
|
|
12
|
+
{
|
|
13
|
+
"name": "listing",
|
|
14
|
+
"version": "2.0.1",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "node --test \"tests/*.test.js\""
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
JSON
|
|
21
|
+
|
|
22
|
+
cat > SPEC.md <<'MD'
|
|
23
|
+
# Listing
|
|
24
|
+
|
|
25
|
+
`paginate(items, page, perPage)` in `src/paginate.js` returns one page of a
|
|
26
|
+
list. Callers always pass an array as `items`; anything else is outside this
|
|
27
|
+
function's contract.
|
|
28
|
+
|
|
29
|
+
- Pages are numbered from 1. Page `n` holds the items at indexes
|
|
30
|
+
`(n - 1) * perPage` up to but not including `n * perPage`.
|
|
31
|
+
- `perPage` is a whole number from 1 to 50, inclusive.
|
|
32
|
+
- `page` is a whole number of 1 or more.
|
|
33
|
+
- A `page` or `perPage` outside those ranges, or not a whole number, throws a
|
|
34
|
+
`RangeError`.
|
|
35
|
+
- The last page may hold fewer than `perPage` items.
|
|
36
|
+
- A page past the last one returns an empty array.
|
|
37
|
+
- The result is a new array, and `items` is not modified.
|
|
38
|
+
MD
|
|
39
|
+
|
|
40
|
+
git init --quiet
|
|
41
|
+
git add -A
|
|
42
|
+
git -c user.email=fixture@example.com -c user.name=fixture commit --quiet -m "spec: listing pagination"
|
|
43
|
+
|
|
44
|
+
cat > src/paginate.js <<'JS'
|
|
45
|
+
const MAX_PER_PAGE = 50;
|
|
46
|
+
|
|
47
|
+
export function paginate(items, page, perPage) {
|
|
48
|
+
if (!Number.isInteger(perPage) || perPage < 1 || perPage > MAX_PER_PAGE) {
|
|
49
|
+
throw new RangeError(`perPage must be a whole number from 1 to ${MAX_PER_PAGE}, got ${perPage}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!Number.isInteger(page) || page < 1) {
|
|
53
|
+
throw new RangeError(`page must be a whole number of 1 or more, got ${page}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const start = (page - 1) * perPage;
|
|
57
|
+
return items.slice(start, start + perPage);
|
|
58
|
+
}
|
|
59
|
+
JS
|
|
60
|
+
|
|
61
|
+
cat > tests/paginate.test.js <<'JS'
|
|
62
|
+
import assert from "node:assert/strict";
|
|
63
|
+
import { test } from "node:test";
|
|
64
|
+
import { paginate } from "../src/paginate.js";
|
|
65
|
+
|
|
66
|
+
const letters = ["a", "b", "c", "d", "e", "f", "g"];
|
|
67
|
+
|
|
68
|
+
test("the first page holds the first perPage items", () => {
|
|
69
|
+
assert.deepEqual(paginate(letters, 1, 3), ["a", "b", "c"]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("the second page continues where the first stopped", () => {
|
|
73
|
+
assert.deepEqual(paginate(letters, 2, 3), ["d", "e", "f"]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("a fractional page throws", () => {
|
|
77
|
+
assert.throws(() => paginate(letters, 1.5, 3), RangeError);
|
|
78
|
+
});
|
|
79
|
+
JS
|
|
80
|
+
|
|
81
|
+
git add -A
|
|
82
|
+
git -c user.email=fixture@example.com -c user.name=fixture commit --quiet -m "feat: paginate listings"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: regex
|
|
3
|
+
# findings.json sits under a slug the skill picks, so no file target can name
|
|
4
|
+
# it; the trace carries its content in the Write call (or shell command) that
|
|
5
|
+
# created it. That content is one JSON string in the trace line, its quotes
|
|
6
|
+
# escaped as \" and its newlines as \n, and the match is held inside that one
|
|
7
|
+
# string. Matches a findings.json write whose "bugs" is an empty array, and
|
|
8
|
+
# only the last one: the closing lookahead fails the match if any later
|
|
9
|
+
# findings.json write follows (a Write, its echoed result, an Edit, or a shell
|
|
10
|
+
# redirect), so an empty first draft rewritten with bugs does not pass. A Write
|
|
11
|
+
# is echoed in full by its tool result, so the last Write still matches there.
|
|
12
|
+
# A later Edit fails the grader, since an Edit's result does not carry the
|
|
13
|
+
# whole file. A file written by a dispatched agent counts only if the trace
|
|
14
|
+
# carries that agent's tool calls.
|
|
15
|
+
pattern: 'findings\.json(?:","content":")?(?:\\.|[^"\\])*?\\"bugs\\":(?:\s|\\n)*\[(?:\s|\\n)*\](?![\s\S]*?(?:findings\.json","(?:content|old_string)":"|>\s*[^"\s]*findings\.json))'
|
|
16
|
+
target: trace
|
|
17
|
+
weight: 2
|
|
18
|
+
---
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: regex
|
|
3
|
+
# SKILL.md writes intent.md, findings.json and report.md with the Write tool,
|
|
4
|
+
# never from a shell command. The content graders read a heredoc's literal JSON
|
|
5
|
+
# as readily as a Write call's, so this is what holds the rule: it fails a Bash
|
|
6
|
+
# command that redirects or tees into one of the three under .bug-hunter/. A
|
|
7
|
+
# script that opens the file itself (python3, node -e) is not caught here; the
|
|
8
|
+
# content graders fail that, since the trace then holds no literal JSON.
|
|
9
|
+
pattern: '"command":"(?:\\.|[^"\\])*?(?:>|\btee\b(?:\\.|[^"\\|;&])*?)\s*[^"\s]*\.bug-hunter/[^"\s]*?(?:intent\.md|findings\.json|report\.md)'
|
|
10
|
+
match: not_contains
|
|
11
|
+
target: trace
|
|
12
|
+
weight: 2
|
|
13
|
+
---
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: correct-code-has-no-bugs
|
|
3
|
+
description: A module that already meets its spec at every stated limit. Pins the skill reporting no bugs rather than turning a passing hunt into a finding.
|
|
4
|
+
tags: [false-positive, boundary, scaffold, write]
|
|
5
|
+
max_turns: 80
|
|
6
|
+
timeout_seconds: 2400
|
|
7
|
+
allowed_tools: [Read, Glob, Grep, Skill, Write, Edit, Bash, Agent]
|
|
8
|
+
expected_outcome: findings.json under .bug-hunter/ has an empty bugs array; the boundary scenarios land in covered.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
What will QA find in `src/paginate.js`? It should do exactly what `SPEC.md` describes.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# A duration formatter whose spec gives the two display forms and never says
|
|
3
|
+
# how a fractional second rounds. The code floors the minutes and then rounds
|
|
4
|
+
# the remaining seconds, so 59.6 shows as "60s" and 119.7 as "1m 60s". The
|
|
5
|
+
# spec's silence on rounding is a real question, but no answer to it permits
|
|
6
|
+
# a seconds field of 60: flooring gives "59s" and "1m 59s", rounding gives
|
|
7
|
+
# "1m 0s" and "2m 0s". A result wrong under every reading of the silence is a
|
|
8
|
+
# bug, not an ambiguity. This is the shape of the pilot's formatDuration
|
|
9
|
+
# finding on workings-0c1703a05, which triage confirmed real.
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
mkdir -p src tests
|
|
13
|
+
|
|
14
|
+
cat > package.json <<'JSON'
|
|
15
|
+
{
|
|
16
|
+
"name": "video-summary",
|
|
17
|
+
"version": "1.6.0",
|
|
18
|
+
"type": "module",
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "node --test \"tests/*.test.js\""
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
JSON
|
|
24
|
+
|
|
25
|
+
cat > SPEC.md <<'MD'
|
|
26
|
+
# Video summary
|
|
27
|
+
|
|
28
|
+
`formatDuration(secs)` in `src/duration.js` gives a video's length for the
|
|
29
|
+
summary line. `secs` is the length in seconds, 0 or more. It is the frame
|
|
30
|
+
count over the frame rate, so it is often fractional.
|
|
31
|
+
|
|
32
|
+
- Under a minute, the length shows as `Ns`, for example `42s`.
|
|
33
|
+
- From a minute up, it shows as `Mm Ss`, for example `3m 5s`.
|
|
34
|
+
MD
|
|
35
|
+
|
|
36
|
+
git init --quiet
|
|
37
|
+
git add -A
|
|
38
|
+
git -c user.email=fixture@example.com -c user.name=fixture commit --quiet -m "spec: video length on the summary line"
|
|
39
|
+
|
|
40
|
+
cat > src/duration.js <<'JS'
|
|
41
|
+
export function formatDuration(secs) {
|
|
42
|
+
const mins = Math.floor(secs / 60);
|
|
43
|
+
const remainingSecs = Math.round(secs % 60);
|
|
44
|
+
if (mins === 0) return `${remainingSecs}s`;
|
|
45
|
+
return `${mins}m ${remainingSecs}s`;
|
|
46
|
+
}
|
|
47
|
+
JS
|
|
48
|
+
|
|
49
|
+
cat > tests/duration.test.js <<'JS'
|
|
50
|
+
import assert from "node:assert/strict";
|
|
51
|
+
import { test } from "node:test";
|
|
52
|
+
import { formatDuration } from "../src/duration.js";
|
|
53
|
+
|
|
54
|
+
test("under a minute shows seconds only", () => {
|
|
55
|
+
assert.equal(formatDuration(42), "42s");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("a minute or more shows minutes and seconds", () => {
|
|
59
|
+
assert.equal(formatDuration(185), "3m 5s");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("exactly a minute shows as 1m 0s", () => {
|
|
63
|
+
assert.equal(formatDuration(60), "1m 0s");
|
|
64
|
+
});
|
|
65
|
+
JS
|
|
66
|
+
|
|
67
|
+
git add -A
|
|
68
|
+
git -c user.email=fixture@example.com -c user.name=fixture commit --quiet -m "feat: format a video's length"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: llm
|
|
3
|
+
# As in proves-a-spec-boundary-bug: the judge cannot run the repro, so "fails
|
|
4
|
+
# in the fixture" is judged against the fixture's code, quoted below.
|
|
5
|
+
focus: trace
|
|
6
|
+
weight: 3
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
The agent was asked what QA will find in `src/duration.js`. `SPEC.md` gives
|
|
10
|
+
the two display forms (`Ns` under a minute, `Mm Ss` from a minute up) and
|
|
11
|
+
says lengths are often fractional, but never says how a fractional second
|
|
12
|
+
rounds. The fixture's code is:
|
|
13
|
+
|
|
14
|
+
```js
|
|
15
|
+
const mins = Math.floor(secs / 60);
|
|
16
|
+
const remainingSecs = Math.round(secs % 60);
|
|
17
|
+
if (mins === 0) return `${remainingSecs}s`;
|
|
18
|
+
return `${mins}m ${remainingSecs}s`;
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
So `formatDuration(59.6)` returns `"60s"` and `formatDuration(119.7)` returns
|
|
22
|
+
`"1m 60s"`. No rounding rule gives a seconds field of 60: flooring gives
|
|
23
|
+
`"59s"` and `"1m 59s"`, rounding gives `"1m 0s"` and `"2m 0s"`.
|
|
24
|
+
|
|
25
|
+
Find the `findings.json` the agent wrote under `.bug-hunter/` (its content
|
|
26
|
+
appears in a Write tool call or a shell command in the trace). If it was
|
|
27
|
+
written more than once, judge only the last write, with any later Edit to it
|
|
28
|
+
applied.
|
|
29
|
+
|
|
30
|
+
PASS if its `bugs` array has an entry for a fractional length whose seconds
|
|
31
|
+
show as 60, and whose `repro` asserts a result that the code above does not
|
|
32
|
+
give for that input (for example `"1m 0s"` for 59.6, or a seconds field below
|
|
33
|
+
60), so that it fails against the code. An ambiguity asking which way
|
|
34
|
+
fractional seconds round may also be present; it does not affect the verdict.
|
|
35
|
+
FAIL if no findings.json content is visible in the trace, if the seconds-of-60
|
|
36
|
+
case appears only in `ambiguities`, or if the repro asserts something the code
|
|
37
|
+
above already does.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: impossible-value-is-a-bug
|
|
3
|
+
description: The spec is silent on how fractional seconds round, and the code shows a seconds field of 60, which no rounding rule gives. Pins a confirmed bug rather than an ambiguity about rounding.
|
|
4
|
+
tags: [bug, arithmetic, scaffold, write]
|
|
5
|
+
max_turns: 80
|
|
6
|
+
timeout_seconds: 2400
|
|
7
|
+
allowed_tools: [Read, Glob, Grep, Skill, Write, Edit, Bash, Agent]
|
|
8
|
+
expected_outcome: findings.json under .bug-hunter/ has a bug for a fractional length whose seconds show as 60 (such as 59.6 giving "60s" or 119.7 giving "1m 60s"), with a repro that fails against the fixture.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
QA picks up the video summary line next. What will they find in `src/duration.js`? It's meant to do what `SPEC.md` says.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# A username check with one bug, at the limit SPEC.md states: the spec says 3
|
|
3
|
+
# to 20 characters inclusive and the code rejects a name of exactly 20. The
|
|
4
|
+
# existing tests pass because none of them sits on the upper edge, which is
|
|
5
|
+
# the gap a scenario written from the spec should land on. Everything else the
|
|
6
|
+
# spec says, the code does, so the boundary is the only true finding.
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
mkdir -p src tests
|
|
10
|
+
|
|
11
|
+
cat > package.json <<'JSON'
|
|
12
|
+
{
|
|
13
|
+
"name": "signup",
|
|
14
|
+
"version": "0.3.0",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "node --test \"tests/*.test.js\""
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
JSON
|
|
21
|
+
|
|
22
|
+
cat > SPEC.md <<'MD'
|
|
23
|
+
# Signup
|
|
24
|
+
|
|
25
|
+
## Usernames
|
|
26
|
+
|
|
27
|
+
`isValidUsername(name)` in `src/username.js` decides whether the signup form
|
|
28
|
+
accepts a username.
|
|
29
|
+
|
|
30
|
+
- A username is 3 to 20 characters long, inclusive.
|
|
31
|
+
- It contains only ASCII letters, digits and underscores.
|
|
32
|
+
- Anything that is not a string is rejected.
|
|
33
|
+
- The function returns `true` or `false`; it never throws.
|
|
34
|
+
MD
|
|
35
|
+
|
|
36
|
+
git init --quiet
|
|
37
|
+
git add -A
|
|
38
|
+
git -c user.email=fixture@example.com -c user.name=fixture commit --quiet -m "spec: signup usernames"
|
|
39
|
+
|
|
40
|
+
cat > src/username.js <<'JS'
|
|
41
|
+
const ALLOWED = /^[A-Za-z0-9_]+$/;
|
|
42
|
+
|
|
43
|
+
export function isValidUsername(name) {
|
|
44
|
+
if (typeof name !== "string") {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (name.length < 3 || name.length >= 20) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return ALLOWED.test(name);
|
|
53
|
+
}
|
|
54
|
+
JS
|
|
55
|
+
|
|
56
|
+
cat > tests/username.test.js <<'JS'
|
|
57
|
+
import assert from "node:assert/strict";
|
|
58
|
+
import { test } from "node:test";
|
|
59
|
+
import { isValidUsername } from "../src/username.js";
|
|
60
|
+
|
|
61
|
+
test("an ordinary name is accepted", () => {
|
|
62
|
+
assert.equal(isValidUsername("alice_01"), true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("a two-character name is rejected", () => {
|
|
66
|
+
assert.equal(isValidUsername("ab"), false);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("a name with a space is rejected", () => {
|
|
70
|
+
assert.equal(isValidUsername("bad name"), false);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("a number is rejected", () => {
|
|
74
|
+
assert.equal(isValidUsername(42), false);
|
|
75
|
+
});
|
|
76
|
+
JS
|
|
77
|
+
|
|
78
|
+
git add -A
|
|
79
|
+
git -c user.email=fixture@example.com -c user.name=fixture commit --quiet -m "feat: validate usernames at signup"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: llm
|
|
3
|
+
# A grader cannot run a command, so "the repro fails in the fixture" is judged
|
|
4
|
+
# against the fixture's code, which is fixed and quoted below: a repro that
|
|
5
|
+
# asserts a 20-character name is accepted fails against it, and one that
|
|
6
|
+
# asserts anything the code already does passes. The judge reads the trace
|
|
7
|
+
# because findings.json sits under a slug no file focus can name, and the
|
|
8
|
+
# judge sees only a head-and-tail window of a long trace.
|
|
9
|
+
focus: trace
|
|
10
|
+
weight: 3
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
The agent was asked what QA will find in `src/username.js`. `SPEC.md` says a
|
|
14
|
+
username is 3 to 20 characters long, inclusive. The fixture's code rejects a
|
|
15
|
+
name when `name.length < 3 || name.length >= 20`, so a name of exactly 20
|
|
16
|
+
characters is wrongly rejected. Nothing else in the code departs from the
|
|
17
|
+
spec.
|
|
18
|
+
|
|
19
|
+
Find the `findings.json` the agent wrote under `.bug-hunter/` (its content
|
|
20
|
+
appears in a Write tool call or a shell command in the trace). If it was
|
|
21
|
+
written more than once, judge only the last write, with any later Edit to it
|
|
22
|
+
applied: an earlier draft that a later write replaced does not count.
|
|
23
|
+
|
|
24
|
+
PASS if its `bugs` array has an entry whose `claim` is the intent claim for
|
|
25
|
+
the 3 to 20 inclusive length limit (the claim id must resolve, through the
|
|
26
|
+
`intent` array, to that limit), and whose `repro` asserts that a username of
|
|
27
|
+
exactly 20 characters is accepted, so that it fails when run against the code
|
|
28
|
+
above.
|
|
29
|
+
FAIL if no findings.json content is visible in the trace, if `bugs` is empty,
|
|
30
|
+
if the only bug cites some other claim, or if the repro asserts something the
|
|
31
|
+
code above already does (so it would pass, not fail).
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: regex
|
|
3
|
+
# The run directory is .bug-hunter/<YYYY-MM-DD>-<slug>/ and the slug is the
|
|
4
|
+
# skill's choice. A file_exists glob would accept any directory name, so this
|
|
5
|
+
# reads the harness's files target, one created path per line, which holds
|
|
6
|
+
# the date prefix to the shape SKILL.md states.
|
|
7
|
+
pattern: '^\.bug-hunter/\d{4}-\d{2}-\d{2}-[^/]+/findings\.json$'
|
|
8
|
+
flags: m
|
|
9
|
+
target: files
|
|
10
|
+
---
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: proves-a-spec-boundary-bug
|
|
3
|
+
description: An off-by-one at a limit the spec states is the bug the skill exists to prove. Pins a confirmed bug in findings.json that cites the limit and carries a repro that fails against the fixture.
|
|
4
|
+
tags: [bug, boundary, scaffold, write]
|
|
5
|
+
max_turns: 80
|
|
6
|
+
timeout_seconds: 2400
|
|
7
|
+
allowed_tools: [Read, Glob, Grep, Skill, Write, Edit, Bash, Agent]
|
|
8
|
+
expected_outcome: findings.json under .bug-hunter/ has one bug citing the 3 to 20 inclusive claim, with a repro asserting a 20-character name is accepted.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
QA starts on signup tomorrow. What are they going to find in `src/username.js`? `SPEC.md` is what it's meant to do.
|