@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.
Files changed (131) hide show
  1. package/README.md +9 -0
  2. package/package.json +4 -1
  3. package/skills/bug-hunter/SKILL.md +82 -0
  4. package/skills/bug-hunter/evals/README.md +282 -0
  5. package/skills/bug-hunter/evals/baselines/claude-opus-5-5.json +93 -0
  6. package/skills/bug-hunter/evals/bug-cites-intent-from-spec/case.yaml +4 -0
  7. package/skills/bug-hunter/evals/bug-cites-intent-from-spec/fixture.sh +71 -0
  8. package/skills/bug-hunter/evals/bug-cites-intent-from-spec/graders/bug-cites-the-spec.md +36 -0
  9. package/skills/bug-hunter/evals/bug-cites-intent-from-spec/graders/bug-hunter-fired.md +5 -0
  10. package/skills/bug-hunter/evals/bug-cites-intent-from-spec/graders/intent-written.md +8 -0
  11. package/skills/bug-hunter/evals/bug-cites-intent-from-spec/prompt.md +11 -0
  12. package/skills/bug-hunter/evals/callers-tree-unchanged/case.yaml +4 -0
  13. package/skills/bug-hunter/evals/callers-tree-unchanged/fixture.sh +67 -0
  14. package/skills/bug-hunter/evals/callers-tree-unchanged/graders/bug-hunter-fired.md +5 -0
  15. package/skills/bug-hunter/evals/callers-tree-unchanged/graders/source-unchanged.md +10 -0
  16. package/skills/bug-hunter/evals/callers-tree-unchanged/graders/tests-unchanged.md +10 -0
  17. package/skills/bug-hunter/evals/callers-tree-unchanged/graders/tree-unchanged.md +15 -0
  18. package/skills/bug-hunter/evals/callers-tree-unchanged/prompt.md +11 -0
  19. package/skills/bug-hunter/evals/correct-code-has-no-bugs/case.yaml +4 -0
  20. package/skills/bug-hunter/evals/correct-code-has-no-bugs/fixture.sh +82 -0
  21. package/skills/bug-hunter/evals/correct-code-has-no-bugs/graders/bug-hunter-fired.md +5 -0
  22. package/skills/bug-hunter/evals/correct-code-has-no-bugs/graders/bugs-empty.md +18 -0
  23. package/skills/bug-hunter/evals/correct-code-has-no-bugs/graders/written-with-write-tool.md +13 -0
  24. package/skills/bug-hunter/evals/correct-code-has-no-bugs/prompt.md +11 -0
  25. package/skills/bug-hunter/evals/impossible-value-is-a-bug/case.yaml +4 -0
  26. package/skills/bug-hunter/evals/impossible-value-is-a-bug/fixture.sh +68 -0
  27. package/skills/bug-hunter/evals/impossible-value-is-a-bug/graders/bug-for-sixty-seconds.md +37 -0
  28. package/skills/bug-hunter/evals/impossible-value-is-a-bug/graders/bug-hunter-fired.md +5 -0
  29. package/skills/bug-hunter/evals/impossible-value-is-a-bug/prompt.md +11 -0
  30. package/skills/bug-hunter/evals/proves-a-spec-boundary-bug/case.yaml +4 -0
  31. package/skills/bug-hunter/evals/proves-a-spec-boundary-bug/fixture.sh +79 -0
  32. package/skills/bug-hunter/evals/proves-a-spec-boundary-bug/graders/bug-cites-the-limit.md +31 -0
  33. package/skills/bug-hunter/evals/proves-a-spec-boundary-bug/graders/bug-hunter-fired.md +5 -0
  34. package/skills/bug-hunter/evals/proves-a-spec-boundary-bug/graders/findings-written.md +10 -0
  35. package/skills/bug-hunter/evals/proves-a-spec-boundary-bug/prompt.md +11 -0
  36. package/skills/bug-hunter/evals/rules.json +177 -0
  37. package/skills/bug-hunter/evals/silent-spec-is-an-ambiguity/case.yaml +4 -0
  38. package/skills/bug-hunter/evals/silent-spec-is-an-ambiguity/fixture.sh +97 -0
  39. package/skills/bug-hunter/evals/silent-spec-is-an-ambiguity/graders/bug-hunter-fired.md +5 -0
  40. package/skills/bug-hunter/evals/silent-spec-is-an-ambiguity/graders/bugs-empty.md +18 -0
  41. package/skills/bug-hunter/evals/silent-spec-is-an-ambiguity/graders/cap-is-an-ambiguity.md +12 -0
  42. package/skills/bug-hunter/evals/silent-spec-is-an-ambiguity/graders/written-with-write-tool.md +13 -0
  43. package/skills/bug-hunter/evals/silent-spec-is-an-ambiguity/prompt.md +11 -0
  44. package/skills/bug-hunter/evals/spec-outranks-target-docstring/case.yaml +4 -0
  45. package/skills/bug-hunter/evals/spec-outranks-target-docstring/fixture.sh +82 -0
  46. package/skills/bug-hunter/evals/spec-outranks-target-docstring/graders/bug-cites-the-spec.md +40 -0
  47. package/skills/bug-hunter/evals/spec-outranks-target-docstring/graders/bug-hunter-fired.md +5 -0
  48. package/skills/bug-hunter/evals/spec-outranks-target-docstring/prompt.md +11 -0
  49. package/skills/bug-hunter/references/findings-format.md +82 -0
  50. package/skills/bug-hunter/references/scenario-catalog.md +92 -0
  51. package/skills/bug-hunter/skill.json +24 -0
  52. package/skills/index.json +17 -5
  53. package/skills/magpie/evals/README.md +13 -0
  54. package/skills/magpie/evals/baselines/claude-opus-5-5.json +69 -0
  55. package/skills/magpie/evals/rules.json +192 -0
  56. package/skills/magpie/skill.json +1 -1
  57. package/skills/migrate/evals/README.md +13 -0
  58. package/skills/migrate/evals/baselines/claude-opus-5-5.json +129 -0
  59. package/skills/migrate/evals/rules.json +160 -0
  60. package/skills/migrate/skill.json +10 -4
  61. package/skills/scope-estimate/README.md +173 -0
  62. package/skills/scope-estimate/SKILL.md +168 -0
  63. package/skills/scope-estimate/references/capability-map.md +122 -0
  64. package/skills/scope-estimate/references/deliverables.md +60 -0
  65. package/skills/scope-estimate/references/requirements.md +104 -0
  66. package/skills/scope-estimate/references/schedule.md +138 -0
  67. package/skills/scope-estimate/scripts/__tests__/schedule.test.ts +366 -0
  68. package/skills/scope-estimate/scripts/schedule.ts +553 -0
  69. package/skills/scope-estimate/skill.json +19 -0
  70. package/skills/scope-estimate/templates/plan.example.json +33 -0
  71. package/skills/sluice/SKILL.md +35 -12
  72. package/skills/sluice/evals/README.md +111 -6
  73. package/skills/sluice/evals/announcement-reaches-the-ledger/fixture.sh +32 -19
  74. package/skills/sluice/evals/announcement-reaches-the-ledger/graders/routes-main-before-source.md +14 -0
  75. package/skills/sluice/evals/announcement-reaches-the-ledger/prompt.md +2 -2
  76. package/skills/sluice/evals/baselines/claude-opus-5-5.json +237 -0
  77. package/skills/sluice/evals/deep-run-blocks-on-a-real-decision/fixture.sh +19 -12
  78. package/skills/sluice/evals/deep-run-fans-out/graders/task-1-merged-back.md +8 -0
  79. package/skills/sluice/evals/deep-run-fans-out/graders/task-2-merged-back.md +8 -0
  80. package/skills/sluice/evals/deep-run-fans-out/graders/task-3-merged-back.md +6 -0
  81. package/skills/sluice/evals/deep-run-fans-out/graders/worktree-per-implementer.md +12 -0
  82. package/skills/sluice/evals/deep-run-hunts-after-the-flip/case.yaml +5 -0
  83. package/skills/sluice/evals/deep-run-hunts-after-the-flip/fixture.sh +258 -0
  84. package/skills/sluice/evals/deep-run-hunts-after-the-flip/graders/bug-hunter-fired.md +5 -0
  85. package/skills/sluice/evals/deep-run-hunts-after-the-flip/graders/four-attempts-allowed.md +34 -0
  86. package/skills/sluice/evals/deep-run-hunts-after-the-flip/graders/fourth-attempt-tested.md +20 -0
  87. package/skills/sluice/evals/deep-run-hunts-after-the-flip/graders/hunt-before-the-final-review.md +21 -0
  88. package/skills/sluice/evals/deep-run-hunts-after-the-flip/graders/record-has-a-hunt-section.md +11 -0
  89. package/skills/sluice/evals/deep-run-hunts-after-the-flip/graders/sluice-fired.md +5 -0
  90. package/skills/sluice/evals/deep-run-hunts-after-the-flip/prompt.md +12 -0
  91. package/skills/sluice/evals/deep-run-survives-a-milestone/fixture.sh +46 -24
  92. package/skills/sluice/evals/fast-verifies-before-handback/case.yaml +4 -0
  93. package/skills/sluice/evals/fast-verifies-before-handback/fixture.sh +73 -0
  94. package/skills/sluice/evals/fast-verifies-before-handback/graders/quiet-flag-implemented.md +5 -0
  95. package/skills/sluice/evals/fast-verifies-before-handback/graders/sluice-fired.md +5 -0
  96. package/skills/sluice/evals/fast-verifies-before-handback/graders/verified-after-the-last-edit.md +18 -0
  97. package/skills/sluice/evals/fast-verifies-before-handback/prompt.md +11 -0
  98. package/skills/sluice/evals/main-hunts-a-checkable-claim/case.yaml +5 -0
  99. package/skills/sluice/evals/main-hunts-a-checkable-claim/fixture.sh +64 -0
  100. package/skills/sluice/evals/main-hunts-a-checkable-claim/graders/announces-main-channel.md +12 -0
  101. package/skills/sluice/evals/main-hunts-a-checkable-claim/graders/bug-hunter-fired.md +5 -0
  102. package/skills/sluice/evals/main-hunts-a-checkable-claim/graders/hunt-after-the-source-edits.md +12 -0
  103. package/skills/sluice/evals/main-hunts-a-checkable-claim/graders/last-page-holds-the-remainder.md +27 -0
  104. package/skills/sluice/evals/main-hunts-a-checkable-claim/graders/remainder-page-tested.md +21 -0
  105. package/skills/sluice/evals/main-hunts-a-checkable-claim/graders/sluice-fired.md +5 -0
  106. package/skills/sluice/evals/main-hunts-a-checkable-claim/prompt.md +12 -0
  107. package/skills/sluice/evals/main-new-interface/graders/shape-agreed-before-building.md +8 -4
  108. package/skills/sluice/evals/main-skips-the-hunt-without-a-claim/case.yaml +5 -0
  109. package/skills/sluice/evals/main-skips-the-hunt-without-a-claim/fixture.sh +73 -0
  110. package/skills/sluice/evals/main-skips-the-hunt-without-a-claim/graders/announces-main-channel.md +12 -0
  111. package/skills/sluice/evals/main-skips-the-hunt-without-a-claim/graders/no-hunt.md +12 -0
  112. package/skills/sluice/evals/main-skips-the-hunt-without-a-claim/graders/skip-said-in-one-line.md +21 -0
  113. package/skills/sluice/evals/main-skips-the-hunt-without-a-claim/graders/sluice-fired.md +5 -0
  114. package/skills/sluice/evals/main-skips-the-hunt-without-a-claim/prompt.md +12 -0
  115. package/skills/sluice/evals/rules.json +364 -0
  116. package/skills/sluice/references/deep-channel.md +64 -18
  117. package/skills/sluice/references/finish.md +8 -2
  118. package/skills/sluice/references/intent.md +4 -2
  119. package/skills/sluice/references/review.md +32 -0
  120. package/skills/sluice/references/status.md +17 -4
  121. package/skills/sluice/references/verify.md +38 -0
  122. package/skills/sluice/scripts/content-digest.sh +65 -0
  123. package/skills/sluice/scripts/run-stats.sh +9 -8
  124. package/skills/sluice/scripts/status.sh +78 -13
  125. package/skills/sluice/scripts/stop-guard.sh +97 -24
  126. package/skills/sluice/skill.json +2 -2
  127. package/skills/terse/evals/README.md +16 -0
  128. package/skills/terse/evals/baselines/claude-opus-5-5.json +105 -0
  129. package/skills/terse/evals/rules.json +161 -0
  130. package/skills/terse/skill.json +1 -1
  131. 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,5 @@
1
+ ---
2
+ type: tool_used
3
+ tool: Skill
4
+ input_match: '"skill"\s*:\s*"(?:[\w-]+:)?bug-hunter"'
5
+ ---
@@ -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,4 @@
1
+ schema_version: "1.1"
2
+ name: correct-code-has-no-bugs
3
+ context:
4
+ scaffold_script: fixture.sh
@@ -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,5 @@
1
+ ---
2
+ type: tool_used
3
+ tool: Skill
4
+ input_match: '"skill"\s*:\s*"(?:[\w-]+:)?bug-hunter"'
5
+ ---
@@ -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,4 @@
1
+ schema_version: "1.1"
2
+ name: impossible-value-is-a-bug
3
+ context:
4
+ scaffold_script: fixture.sh
@@ -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,5 @@
1
+ ---
2
+ type: tool_used
3
+ tool: Skill
4
+ input_match: '"skill"\s*:\s*"(?:[\w-]+:)?bug-hunter"'
5
+ ---
@@ -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,4 @@
1
+ schema_version: "1.1"
2
+ name: proves-a-spec-boundary-bug
3
+ context:
4
+ scaffold_script: fixture.sh
@@ -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,5 @@
1
+ ---
2
+ type: tool_used
3
+ tool: Skill
4
+ input_match: '"skill"\s*:\s*"(?:[\w-]+:)?bug-hunter"'
5
+ ---
@@ -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.