@windyroad/risk-scorer 0.17.4 → 0.18.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 (55) hide show
  1. package/.agents/plugins/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/README.md +13 -2
  5. package/bin/install.mjs +42 -8
  6. package/hooks/codex-agent-completion.mjs +82 -0
  7. package/hooks/hooks.json +5 -2
  8. package/hooks/risk-scorer-dispatch.sh +3 -0
  9. package/hooks/risk-scorer-scaffold-nudge.sh +15 -4
  10. package/package.json +5 -2
  11. package/scripts/codex-agents.mjs +208 -0
  12. package/scripts/sync-codex-skills.mjs +8 -3
  13. package/skills/assess-external-comms/SKILL.md +5 -4
  14. package/skills/assess-inbound-report/SKILL.md +5 -4
  15. package/skills/assess-release/SKILL.md +5 -4
  16. package/skills/assess-wip/SKILL.md +5 -4
  17. package/skills/bootstrap-catalog/SKILL.md +5 -4
  18. package/skills/create-risk/SKILL.md +5 -4
  19. package/skills/external-comms/SKILL.md +5 -4
  20. package/skills/pipeline/SKILL.md +5 -4
  21. package/skills/update-policy/SKILL.md +6 -5
  22. package/skills/wip/SKILL.md +5 -4
  23. package/agents/test/inbound-report-contract.bats +0 -225
  24. package/agents/test/risk-scorer-above-appetite-stop.bats +0 -80
  25. package/agents/test/risk-scorer-catalog-consumption.bats +0 -138
  26. package/agents/test/risk-scorer-commit-verdict.bats +0 -66
  27. package/agents/test/risk-scorer-monitoring-not-a-control.bats +0 -76
  28. package/agents/test/risk-scorer-reducing-bypass-criteria.bats +0 -62
  29. package/agents/test/risk-scorer-register-hint.bats +0 -111
  30. package/agents/test/risk-scorer-structured-remediations.bats +0 -123
  31. package/agents/test/risk-scorer-user-stated-preconditions.bats +0 -89
  32. package/hooks/test/ci-status-gate.bats +0 -234
  33. package/hooks/test/external-comms-gate.bats +0 -528
  34. package/hooks/test/external-comms-key-substance.bats +0 -74
  35. package/hooks/test/git-push-gate.bats +0 -82
  36. package/hooks/test/pipeline-state-changeset-partition.bats +0 -181
  37. package/hooks/test/pipeline-state-hash.bats +0 -195
  38. package/hooks/test/plan-risk-guidance-once-per-session.bats +0 -95
  39. package/hooks/test/reducing-marker-persistence.bats +0 -236
  40. package/hooks/test/risk-gate.bats +0 -330
  41. package/hooks/test/risk-policy-project-root.bats +0 -20
  42. package/hooks/test/risk-score-commit-gate-adr-031-bypass.bats +0 -98
  43. package/hooks/test/risk-score-commit-gate-cadence-staleness.bats +0 -105
  44. package/hooks/test/risk-score-mark-external-comms-prompt-parse.bats +0 -94
  45. package/hooks/test/risk-score-mark-register-queue.bats +0 -253
  46. package/hooks/test/risk-score-mark.bats +0 -112
  47. package/hooks/test/risk-scorer-hook-dispatch.bats +0 -77
  48. package/hooks/test/risk-scorer-no-stop-hook.bats +0 -19
  49. package/hooks/test/risk-scorer-scaffold-nudge.bats +0 -132
  50. package/hooks/test/slide-marker-on-subprocess-return.bats +0 -118
  51. package/hooks/test/wip-risk-mark.bats +0 -64
  52. package/scripts/test/drain-register-queue.bats +0 -359
  53. package/scripts/test/evaluate-graduation.bats +0 -533
  54. package/scripts/test/extract-risks-from-reports.bats +0 -366
  55. package/scripts/test/restage-commit.bats +0 -137
@@ -1,366 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # extract-risks-from-reports.sh — behavioural fixture per ADR-052.
4
- # Walks .risk-reports/, parses RISK_REGISTER_HINT bullets per ADR-056,
5
- # dedupes by slug, writes per-slug entries + README to docs/risks/.
6
- #
7
- # Tests are behavioural — replay the script against tmp-dir mock corpora,
8
- # assert on observable filesystem outcomes (file presence, content shape,
9
- # README rows). NO structural grep on script source.
10
-
11
- setup() {
12
- ORIG_DIR="$PWD"
13
- REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
14
- SCRIPT="$REPO_ROOT/packages/risk-scorer/scripts/extract-risks-from-reports.sh"
15
- TEST_DIR=$(mktemp -d)
16
- cd "$TEST_DIR"
17
- # All tests need RISK-POLICY.md to satisfy pre-condition
18
- echo "# Risk Policy" > RISK-POLICY.md
19
- }
20
-
21
- teardown() {
22
- cd "$ORIG_DIR"
23
- rm -rf "$TEST_DIR"
24
- }
25
-
26
- # ──────────────────────────────────────────────────────────────────────────────
27
- # Pre-condition handling
28
- # ──────────────────────────────────────────────────────────────────────────────
29
-
30
- @test "fails with exit 1 when RISK-POLICY.md absent" {
31
- rm RISK-POLICY.md
32
- mkdir -p .risk-reports
33
- echo "report" > .risk-reports/r.md
34
- run "$SCRIPT"
35
- [ "$status" -eq 1 ]
36
- }
37
-
38
- @test "fails with exit 1 when .risk-reports/ absent" {
39
- run "$SCRIPT"
40
- [ "$status" -eq 1 ]
41
- }
42
-
43
- @test "fails with exit 1 when .risk-reports/ is empty" {
44
- mkdir -p .risk-reports
45
- run "$SCRIPT"
46
- [ "$status" -eq 1 ]
47
- }
48
-
49
- # ──────────────────────────────────────────────────────────────────────────────
50
- # Deterministic extraction (Phase 1)
51
- # ──────────────────────────────────────────────────────────────────────────────
52
-
53
- @test "extracts a single 3-column hint and writes one entry" {
54
- mkdir -p .risk-reports
55
- cat > .risk-reports/r1.md <<'EOF'
56
- # Report
57
-
58
- RISK_REGISTER_HINT:
59
- - above-appetite-residual | example-risk-class | A description of the risk class
60
- EOF
61
- run "$SCRIPT"
62
- [ "$status" -eq 0 ]
63
- [ -d docs/risks ]
64
- [ -f docs/risks/README.md ]
65
- ls docs/risks/R*-example-risk-class.active.md >/dev/null 2>&1
66
- }
67
-
68
- @test "dedupes 3 reports with same slug to one entry citing all 3 sources" {
69
- mkdir -p .risk-reports
70
- for i in 1 2 3; do
71
- cat > .risk-reports/r${i}.md <<EOF
72
- RISK_REGISTER_HINT:
73
- - above-appetite-residual | duplicated-class | Same risk surfaced ${i} times
74
- EOF
75
- done
76
- run "$SCRIPT"
77
- [ "$status" -eq 0 ]
78
- # Exactly one entry file
79
- count=$(ls docs/risks/R*-duplicated-class.active.md 2>/dev/null | wc -l | tr -d ' ')
80
- [ "$count" = "1" ]
81
- # Source Evidence block lists all 3 reports
82
- entry=$(ls docs/risks/R*-duplicated-class.active.md)
83
- grep -q ".risk-reports/r1.md" "$entry"
84
- grep -q ".risk-reports/r2.md" "$entry"
85
- grep -q ".risk-reports/r3.md" "$entry"
86
- }
87
-
88
- @test "infers infosec category from confidentiality-disclosure reason-tag" {
89
- mkdir -p .risk-reports
90
- cat > .risk-reports/r.md <<'EOF'
91
- RISK_REGISTER_HINT:
92
- - confidentiality-disclosure | leak-shape | A confidentiality leak surfaced
93
- EOF
94
- run "$SCRIPT"
95
- [ "$status" -eq 0 ]
96
- entry=$(ls docs/risks/R*-leak-shape.active.md)
97
- grep -q "^\*\*Category\*\*: infosec" "$entry"
98
- }
99
-
100
- @test "infers operational category from above-appetite-residual reason-tag" {
101
- mkdir -p .risk-reports
102
- cat > .risk-reports/r.md <<'EOF'
103
- RISK_REGISTER_HINT:
104
- - above-appetite-residual | ops-shape | An ops risk surfaced
105
- EOF
106
- run "$SCRIPT"
107
- [ "$status" -eq 0 ]
108
- entry=$(ls docs/risks/R*-ops-shape.active.md)
109
- grep -q "^\*\*Category\*\*: operational" "$entry"
110
- }
111
-
112
- @test "skips bullets with invalid reason-tag" {
113
- mkdir -p .risk-reports
114
- cat > .risk-reports/r.md <<'EOF'
115
- RISK_REGISTER_HINT:
116
- - not-a-real-tag | bogus-slug | Bogus prose
117
- - above-appetite-residual | valid-slug | Valid prose
118
- EOF
119
- run "$SCRIPT"
120
- [ "$status" -eq 0 ]
121
- ! ls docs/risks/R*-bogus-slug.active.md 2>/dev/null
122
- ls docs/risks/R*-valid-slug.active.md >/dev/null 2>&1
123
- }
124
-
125
- # ──────────────────────────────────────────────────────────────────────────────
126
- # Source Evidence block + ADR-026 sentinel
127
- # ──────────────────────────────────────────────────────────────────────────────
128
-
129
- @test "entry includes Source Evidence block with originating reports" {
130
- mkdir -p .risk-reports
131
- cat > .risk-reports/abc.md <<'EOF'
132
- RISK_REGISTER_HINT:
133
- - above-appetite-residual | citing-class | Cited risk
134
- EOF
135
- run "$SCRIPT"
136
- [ "$status" -eq 0 ]
137
- entry=$(ls docs/risks/R*-citing-class.active.md)
138
- grep -q "## Source Evidence" "$entry"
139
- grep -q ".risk-reports/abc.md" "$entry"
140
- }
141
-
142
- @test "ungrounded scoring fields use ADR-026 sentinel" {
143
- mkdir -p .risk-reports
144
- cat > .risk-reports/r.md <<'EOF'
145
- RISK_REGISTER_HINT:
146
- - above-appetite-residual | sentinel-test | Sentinel check
147
- EOF
148
- run "$SCRIPT"
149
- [ "$status" -eq 0 ]
150
- entry=$(ls docs/risks/R*-sentinel-test.active.md)
151
- grep -q "not estimated — no prior data" "$entry"
152
- }
153
-
154
- @test "Status field is auto-scaffolded pending review" {
155
- mkdir -p .risk-reports
156
- cat > .risk-reports/r.md <<'EOF'
157
- RISK_REGISTER_HINT:
158
- - above-appetite-residual | status-test | Status check
159
- EOF
160
- run "$SCRIPT"
161
- [ "$status" -eq 0 ]
162
- entry=$(ls docs/risks/R*-status-test.active.md)
163
- grep -q "Active (auto-scaffolded — pending review)" "$entry"
164
- }
165
-
166
- # ──────────────────────────────────────────────────────────────────────────────
167
- # Recogniser-shape skeleton emission (P169 — bootstrap stops the bleeding)
168
- # ──────────────────────────────────────────────────────────────────────────────
169
- # Newly-scaffolded entries MUST carry a ## Recogniser section with the three
170
- # sub-blocks awaiting curation, so new risks stop landing without the shape
171
- # that the @windyroad/risk-scorer pipeline relies on for slug-token matching.
172
-
173
- @test "scaffolded entry includes ## Recogniser section" {
174
- mkdir -p .risk-reports
175
- cat > .risk-reports/r.md <<'EOF'
176
- RISK_REGISTER_HINT:
177
- - above-appetite-residual | recogniser-shape-test | Recogniser skeleton check
178
- EOF
179
- run "$SCRIPT"
180
- [ "$status" -eq 0 ]
181
- entry=$(ls docs/risks/R*-recogniser-shape-test.active.md)
182
- grep -q "^## Recogniser" "$entry"
183
- }
184
-
185
- @test "scaffolded Recogniser block has Path-patterns / Diff-content / Anti-patterns sub-blocks" {
186
- mkdir -p .risk-reports
187
- cat > .risk-reports/r.md <<'EOF'
188
- RISK_REGISTER_HINT:
189
- - above-appetite-residual | recogniser-subblock-test | Recogniser sub-block check
190
- EOF
191
- run "$SCRIPT"
192
- [ "$status" -eq 0 ]
193
- entry=$(ls docs/risks/R*-recogniser-subblock-test.active.md)
194
- grep -q "Path patterns" "$entry"
195
- grep -q "Diff-content keywords" "$entry"
196
- grep -q "Anti-patterns" "$entry"
197
- }
198
-
199
- @test "scaffolded Recogniser sub-blocks carry pending-review placeholders" {
200
- mkdir -p .risk-reports
201
- cat > .risk-reports/r.md <<'EOF'
202
- RISK_REGISTER_HINT:
203
- - above-appetite-residual | recogniser-pending-test | Recogniser pending check
204
- EOF
205
- run "$SCRIPT"
206
- [ "$status" -eq 0 ]
207
- entry=$(ls docs/risks/R*-recogniser-pending-test.active.md)
208
- # The skeleton uses the same ADR-026-style "pending review" sentinel
209
- # so curators can grep for unfinished sections. Three pending-review
210
- # markers (one per sub-block: paths / keywords / anti-patterns).
211
- recog_lines=$(sed -n '/^## Recogniser/,/^## Inherent/p' "$entry" | grep -c "pending review" || true)
212
- [ "$recog_lines" -ge 3 ]
213
- }
214
-
215
- # ──────────────────────────────────────────────────────────────────────────────
216
- # Idempotency / slug collision append
217
- # ──────────────────────────────────────────────────────────────────────────────
218
-
219
- @test "second run with same corpus appends to existing Source Evidence" {
220
- mkdir -p .risk-reports
221
- cat > .risk-reports/first.md <<'EOF'
222
- RISK_REGISTER_HINT:
223
- - above-appetite-residual | append-test | First sighting
224
- EOF
225
- run "$SCRIPT"
226
- [ "$status" -eq 0 ]
227
- entry=$(ls docs/risks/R*-append-test.active.md)
228
- # Add a second report with the same slug
229
- cat > .risk-reports/second.md <<'EOF'
230
- RISK_REGISTER_HINT:
231
- - above-appetite-residual | append-test | Second sighting
232
- EOF
233
- run "$SCRIPT"
234
- [ "$status" -eq 0 ]
235
- # Same entry file — no new R<NNN>
236
- count=$(ls docs/risks/R*-append-test.active.md 2>/dev/null | wc -l | tr -d ' ')
237
- [ "$count" = "1" ]
238
- # Both reports cited
239
- grep -q "first.md" "$entry"
240
- grep -q "second.md" "$entry"
241
- }
242
-
243
- # ──────────────────────────────────────────────────────────────────────────────
244
- # README generation
245
- # ──────────────────────────────────────────────────────────────────────────────
246
-
247
- @test "README is regenerated with Register table containing the new entry" {
248
- mkdir -p .risk-reports
249
- cat > .risk-reports/r.md <<'EOF'
250
- RISK_REGISTER_HINT:
251
- - above-appetite-residual | readme-test | README inclusion check
252
- EOF
253
- run "$SCRIPT"
254
- [ "$status" -eq 0 ]
255
- [ -f docs/risks/README.md ]
256
- grep -q "Risk Register" docs/risks/README.md
257
- grep -q "readme-test" docs/risks/README.md
258
- # README cites ADR-059
259
- grep -q "ADR-059" docs/risks/README.md
260
- }
261
-
262
- @test "emitted README does NOT leak the publishing-suite brand name (P374 published-artefacts)" {
263
- # The README heredoc must use project-neutral phrasing so an adopter's
264
- # generated docs/risks/README.md never carries the source monorepo's
265
- # brand into adopter-controlled prose. Witness substring from #273.
266
- mkdir -p .risk-reports
267
- cat > .risk-reports/r.md <<'EOF'
268
- RISK_REGISTER_HINT:
269
- - above-appetite-residual | brand-leak-test | Brand-leak absence check
270
- EOF
271
- run "$SCRIPT"
272
- [ "$status" -eq 0 ]
273
- [ -f docs/risks/README.md ]
274
- ! grep -q "Windy Road Agent Plugins suite" docs/risks/README.md
275
- # Positive assertion: the neutral phrasing is present
276
- grep -q "persistent risk register\*\* for this project" docs/risks/README.md
277
- }
278
-
279
- @test "README does NOT cite TEMPLATE.md (per user direction 2026-05-04)" {
280
- mkdir -p .risk-reports
281
- cat > .risk-reports/r.md <<'EOF'
282
- RISK_REGISTER_HINT:
283
- - above-appetite-residual | nt | No TEMPLATE check
284
- EOF
285
- run "$SCRIPT"
286
- [ "$status" -eq 0 ]
287
- ! grep -q "TEMPLATE.md" docs/risks/README.md
288
- ! [ -f docs/risks/TEMPLATE.md ]
289
- }
290
-
291
- # ──────────────────────────────────────────────────────────────────────────────
292
- # Dry-run mode
293
- # ──────────────────────────────────────────────────────────────────────────────
294
-
295
- @test "dry-run does not write any files" {
296
- mkdir -p .risk-reports
297
- cat > .risk-reports/r.md <<'EOF'
298
- RISK_REGISTER_HINT:
299
- - above-appetite-residual | dry-run-test | Dry-run check
300
- EOF
301
- run "$SCRIPT" --dry-run
302
- [ "$status" -eq 0 ]
303
- ! [ -d docs/risks ]
304
- }
305
-
306
- @test "dry-run reports hinted vs unhinted counts" {
307
- mkdir -p .risk-reports
308
- cat > .risk-reports/hinted.md <<'EOF'
309
- RISK_REGISTER_HINT:
310
- - above-appetite-residual | hinted | Hinted
311
- EOF
312
- echo "no hint here" > .risk-reports/unhinted.md
313
- run "$SCRIPT" --dry-run
314
- [ "$status" -eq 0 ]
315
- [[ "$output" == *"reports walked: 2"* ]]
316
- [[ "$output" == *"hinted (deterministic): 1"* ]]
317
- [[ "$output" == *"unhinted (Phase 2 todo): 1"* ]]
318
- }
319
-
320
- # ──────────────────────────────────────────────────────────────────────────────
321
- # --derived-slugs (Phase 2 input)
322
- # ──────────────────────────────────────────────────────────────────────────────
323
-
324
- @test "--derived-slugs adds entries from Phase 2 LLM-walk output" {
325
- mkdir -p .risk-reports
326
- echo "no hint" > .risk-reports/unhinted.md
327
- # Simulate Phase 2 LLM-walk producing a derived slug
328
- printf 'derived-slug\t1\tabove-appetite-residual\tDerived from LLM walk of unhinted.md\t.risk-reports/unhinted.md\n' > /tmp/derived.tsv
329
- run "$SCRIPT" --derived-slugs /tmp/derived.tsv
330
- [ "$status" -eq 0 ]
331
- ls docs/risks/R*-derived-slug.active.md >/dev/null 2>&1
332
- rm -f /tmp/derived.tsv
333
- }
334
-
335
- # ──────────────────────────────────────────────────────────────────────────────
336
- # Octal-eval regression (P164 Phase 2 — #273 witness)
337
- # ──────────────────────────────────────────────────────────────────────────────
338
- # Bash arithmetic $(( ... )) parses a leading-zero operand as octal; 008/009 are
339
- # invalid octal literals → `bash: 008: value too great for base`. The next-ID
340
- # compute (LOCAL_MAX from zero-padded R<NNN> filenames) must force base-10 with
341
- # 10#. Pre-seed an R008 entry so LOCAL_MAX="008" and assert the script allocates
342
- # R009 cleanly without the octal error.
343
-
344
- @test "allocates R009 cleanly when an R008 entry already exists (octal-eval regression, P164 Phase 2)" {
345
- mkdir -p docs/risks
346
- # Pre-existing zero-padded entry that drives LOCAL_MAX="008". Carries the
347
- # full field shape the README generator reads (Category/Status/Curation) so
348
- # the regression isolates the octal-eval boundary, not fixture malformation.
349
- cat > docs/risks/R008-pre-existing.active.md <<'EOF'
350
- # Risk R008: Pre Existing
351
- **Status**: Active
352
- **Category**: operational
353
- **Curation**: (human-curated)
354
- EOF
355
- mkdir -p .risk-reports
356
- cat > .risk-reports/octal.md <<'EOF'
357
- RISK_REGISTER_HINT:
358
- - above-appetite-residual | octal-boundary-slug | New risk past the 008 boundary
359
- EOF
360
- run "$SCRIPT"
361
- [ "$status" -eq 0 ]
362
- # No octal-base error in output
363
- [[ "$output" != *"value too great for base"* ]]
364
- # New entry allocated as R009, not a corrupted/duplicate ID
365
- [ -f docs/risks/R009-octal-boundary-slug.active.md ]
366
- }
@@ -1,137 +0,0 @@
1
- #!/usr/bin/env bats
2
- # Behavioural-fixture coverage for packages/risk-scorer/scripts/restage-commit.sh
3
- # per ADR-052 (behavioural tests default) and P326 (re-stage-after-scorer-delegation).
4
- #
5
- # The script is invoked AFTER the agent delegates to wr-risk-scorer:pipeline
6
- # to land the commit atomically — re-stages paths the Agent-tool boundary cleared
7
- # from the index, asserts staging is non-empty, then runs `git commit` with the
8
- # caller's -m args. Eliminates the silent re-add round-trip P326 documented.
9
- #
10
- # Surface:
11
- # wr-risk-scorer-restage-commit -m "<msg>" [-m "<trailer>"] -- <path1> [<path2>...]
12
-
13
- setup() {
14
- REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
15
- SCRIPT="$REPO_ROOT/packages/risk-scorer/scripts/restage-commit.sh"
16
- SHIM="$REPO_ROOT/packages/risk-scorer/bin/wr-risk-scorer-restage-commit"
17
- WORK_DIR="$(mktemp -d)"
18
- cd "$WORK_DIR"
19
- git init --quiet
20
- git config user.email "restage-test@example.com"
21
- git config user.name "Restage Test"
22
- git config commit.gpgsign false
23
- git commit --quiet --allow-empty -m "init"
24
- }
25
-
26
- teardown() {
27
- cd /
28
- rm -rf "$WORK_DIR"
29
- }
30
-
31
- @test "shim wrapper exists and is executable" {
32
- [ -x "$SHIM" ]
33
- }
34
-
35
- @test "shim resolves canonical script (not exit 127)" {
36
- echo "seed" > seed.txt
37
- run "$SHIM" -m "test" -- seed.txt
38
- [ "$status" -ne 127 ]
39
- }
40
-
41
- @test "single path: re-stages and commits (the P326 happy path)" {
42
- echo "content" > file.txt
43
- run bash "$SCRIPT" -m "test: commit file" -- file.txt
44
- [ "$status" -eq 0 ]
45
- run git log -1 --name-only --format=
46
- echo "$output" | grep -q '^file\.txt$'
47
- }
48
-
49
- @test "multi-path: re-stages all paths and commits them in one commit" {
50
- echo "a" > a.txt
51
- echo "b" > b.txt
52
- echo "c" > c.txt
53
- run bash "$SCRIPT" -m "test: multi" -- a.txt b.txt c.txt
54
- [ "$status" -eq 0 ]
55
- run git log -1 --name-only --format=
56
- echo "$output" | grep -q '^a\.txt$'
57
- echo "$output" | grep -q '^b\.txt$'
58
- echo "$output" | grep -q '^c\.txt$'
59
- }
60
-
61
- @test "multiple -m flags pass through (trailer like RISK_BYPASS)" {
62
- echo "x" > x.txt
63
- run bash "$SCRIPT" \
64
- -m "docs(problems): capture P999 test" \
65
- -m "RISK_BYPASS: capture-deferred-readme" \
66
- -- x.txt
67
- [ "$status" -eq 0 ]
68
- run git log -1 --format=%B
69
- echo "$output" | grep -q '^docs(problems): capture P999 test'
70
- echo "$output" | grep -q 'RISK_BYPASS: capture-deferred-readme'
71
- }
72
-
73
- @test "missing -m flag → exit 1, no commit" {
74
- echo "x" > x.txt
75
- run bash "$SCRIPT" -- x.txt
76
- [ "$status" -ne 0 ]
77
- echo "$output" | grep -qi 'message\|usage'
78
- run git log --oneline
79
- [ "$(echo "$output" | wc -l | tr -d ' ')" = "1" ]
80
- }
81
-
82
- @test "missing -- separator → exit 1" {
83
- echo "x" > x.txt
84
- run bash "$SCRIPT" -m "test" x.txt
85
- [ "$status" -ne 0 ]
86
- echo "$output" | grep -qi 'separator\|usage'
87
- }
88
-
89
- @test "no paths after -- → exit 1, no commit" {
90
- run bash "$SCRIPT" -m "test" --
91
- [ "$status" -ne 0 ]
92
- echo "$output" | grep -qi 'path\|usage'
93
- run git log --oneline
94
- [ "$(echo "$output" | wc -l | tr -d ' ')" = "1" ]
95
- }
96
-
97
- @test "nothing staged after re-add → exit 1, no commit (e.g. unchanged file)" {
98
- echo "tracked" > tracked.txt
99
- git add tracked.txt
100
- git commit --quiet -m "seed tracked"
101
- run bash "$SCRIPT" -m "test: should fail" -- tracked.txt
102
- [ "$status" -ne 0 ]
103
- echo "$output" | grep -qi 'nothing staged\|empty'
104
- run git log --oneline
105
- [ "$(echo "$output" | wc -l | tr -d ' ')" = "2" ]
106
- }
107
-
108
- @test "path that doesn't exist → exit non-zero (git add propagates)" {
109
- run bash "$SCRIPT" -m "test" -- nonexistent.txt
110
- [ "$status" -ne 0 ]
111
- }
112
-
113
- @test "rename via git mv survives the re-stage-and-commit (P057 + P326 compose)" {
114
- echo "v1" > orig.md
115
- git add orig.md
116
- git commit --quiet -m "seed"
117
- git mv orig.md renamed.md
118
- echo "v2" >> renamed.md
119
- run bash "$SCRIPT" -m "test: rename+edit" -- renamed.md
120
- [ "$status" -eq 0 ]
121
- run git log -1 --name-only --format=
122
- echo "$output" | grep -q '^renamed\.md$'
123
- [ ! -f orig.md ]
124
- run cat renamed.md
125
- echo "$output" | grep -q 'v2'
126
- }
127
-
128
- @test "doesn't touch unrelated unstaged changes" {
129
- echo "subject" > subject.txt
130
- echo "bystander" > bystander.txt
131
- run bash "$SCRIPT" -m "test: subject only" -- subject.txt
132
- [ "$status" -eq 0 ]
133
- run git status --porcelain bystander.txt
134
- echo "$output" | grep -q '^?? bystander\.txt'
135
- run git log -1 --name-only --format=
136
- ! echo "$output" | grep -q '^bystander\.txt$'
137
- }