tasks-toolkit 0.1.3__py3-none-any.whl

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 (65) hide show
  1. scripts/__init__.py +0 -0
  2. scripts/agents_sync.py +61 -0
  3. scripts/apply-plan.sh +51 -0
  4. scripts/build.sh +7 -0
  5. scripts/codex-task.sh +198 -0
  6. scripts/install-hooks.sh +9 -0
  7. scripts/junie-status.sh +52 -0
  8. scripts/junie-task.sh +183 -0
  9. scripts/kilo-task.sh +226 -0
  10. scripts/needs-dotnet-checks.sh +24 -0
  11. scripts/plan-sprint.sh +92 -0
  12. scripts/run-task.sh +79 -0
  13. scripts/run.sh +7 -0
  14. scripts/run_stats.py +141 -0
  15. scripts/run_summary.py +94 -0
  16. scripts/spec-lint.sh +139 -0
  17. scripts/sprint-auto.sh +79 -0
  18. scripts/sprint_auto_execute.py +160 -0
  19. scripts/sprint_auto_preflight.py +309 -0
  20. scripts/sprint_auto_prepare.py +271 -0
  21. scripts/sprint_auto_resume.py +153 -0
  22. scripts/sprint_auto_state.py +191 -0
  23. scripts/sprint_plan.py +238 -0
  24. scripts/task-report.sh +45 -0
  25. scripts/tasks.py +1813 -0
  26. scripts/tasks_cli.py +14 -0
  27. scripts/tasks_commands.py +11 -0
  28. scripts/tasks_init.py +140 -0
  29. scripts/tasks_mcp.py +309 -0
  30. scripts/tasks_model.py +173 -0
  31. scripts/tasks_plan_import.py +269 -0
  32. scripts/tasks_render.py +148 -0
  33. scripts/tasks_storage.py +373 -0
  34. scripts/tasks_watch.py +159 -0
  35. scripts/test.sh +43 -0
  36. scripts/test_agent_config.py +97 -0
  37. scripts/test_plan_sprint.sh +172 -0
  38. scripts/test_python_fast.sh +26 -0
  39. scripts/test_python_lint.sh +22 -0
  40. scripts/test_python_slow.sh +14 -0
  41. scripts/test_run_stats.py +64 -0
  42. scripts/test_runners.sh +588 -0
  43. scripts/test_spec_lint.sh +438 -0
  44. scripts/test_sprint_auto.py +50 -0
  45. scripts/test_sprint_auto_execute.py +112 -0
  46. scripts/test_sprint_auto_preflight.py +143 -0
  47. scripts/test_sprint_auto_prepare.py +150 -0
  48. scripts/test_sprint_auto_resume.py +104 -0
  49. scripts/test_sprint_auto_state.py +157 -0
  50. scripts/test_tasks.py +1678 -0
  51. scripts/test_tasks_mcp.py +233 -0
  52. scripts/test_tasks_plan_import.py +129 -0
  53. scripts/test_tasks_storage.py +67 -0
  54. scripts/test_tasks_watch.py +239 -0
  55. scripts/test_worktree.sh +2112 -0
  56. scripts/worktree.sh +992 -0
  57. scripts/worktree_mr.py +133 -0
  58. scripts/worktree_path.py +70 -0
  59. scripts/worktree_sprint.py +70 -0
  60. tasks_toolkit-0.1.3.dist-info/METADATA +121 -0
  61. tasks_toolkit-0.1.3.dist-info/RECORD +65 -0
  62. tasks_toolkit-0.1.3.dist-info/WHEEL +5 -0
  63. tasks_toolkit-0.1.3.dist-info/entry_points.txt +4 -0
  64. tasks_toolkit-0.1.3.dist-info/licenses/LICENSE +21 -0
  65. tasks_toolkit-0.1.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,438 @@
1
+ #!/usr/bin/env bash
2
+ # Tests for scripts/spec-lint.sh: valid specs, each missing section, empty
3
+ # sections, file count boundaries, size budget, command requirement, multiple
4
+ # violations, usage errors, SPEC_LINT=off, template, and run-task.sh integration.
5
+ # Plain bash, no external dependencies beyond coreutils/grep/awk.
6
+ # Prints PASS/FAIL per case. Exit code: 0 if all pass, 1 if any fail.
7
+ set -uo pipefail
8
+
9
+ passed=0
10
+ failed=0
11
+ total=0
12
+
13
+ pass() { passed=$((passed + 1)); total=$((total + 1)); echo " PASS: $1"; }
14
+ fail() { failed=$((failed + 1)); total=$((total + 1)); echo " FAIL: $1 — $2"; }
15
+
16
+ PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
17
+ LINT="$PROJECT_ROOT/scripts/spec-lint.sh"
18
+ RUN_TASK="$PROJECT_ROOT/scripts/run-task.sh"
19
+
20
+ # ── helpers ──────────────────────────────────────────────────────────────
21
+
22
+ tmpdir="$(mktemp -d)"
23
+ trap 'rm -rf "$tmpdir"' EXIT
24
+
25
+ write_spec() {
26
+ # $1 = filename, $2 = content
27
+ printf '%s\n' "$2" > "$tmpdir/$1"
28
+ }
29
+
30
+ run_lint() {
31
+ # $1 = spec file path; captures stdout and stderr separately
32
+ local rc=0
33
+ LINT_OUT="$(bash "$LINT" "$1" 2>"$tmpdir/lint.err")" || rc=$?
34
+ LINT_ERR="$(cat "$tmpdir/lint.err")"
35
+ LINT_RC=$rc
36
+ }
37
+
38
+ # ── test cases ───────────────────────────────────────────────────────────
39
+
40
+ test_valid_spec() {
41
+ write_spec valid.md "
42
+ ## Goal
43
+ Implement a new feature.
44
+
45
+ ## Files
46
+ - NEW src/NewFile.cs — the new module
47
+
48
+ ## Tests
49
+ Add unit tests in tests/NewFileTests.cs.
50
+
51
+ ## Acceptance
52
+ Run \`bash scripts/test.sh\` and verify green.
53
+
54
+ ## Live check
55
+ Run \`bash scripts/run.sh hash text -i hello\`"
56
+ run_lint "$tmpdir/valid.md"
57
+ if [ "$LINT_RC" -eq 0 ] && [ "$LINT_OUT" = "spec-lint: OK" ]; then
58
+ pass "valid spec exits 0 with OK"
59
+ else
60
+ fail "valid spec" "rc=$LINT_RC out='$LINT_OUT'"
61
+ fi
62
+ }
63
+
64
+ test_missing_goal() {
65
+ write_spec no-goal.md "
66
+ ## Files
67
+ - NEW src/X.cs
68
+
69
+ ## Tests
70
+ Tests for X.
71
+
72
+ ## Acceptance
73
+ Run \`bash scripts/test.sh\`.
74
+
75
+ ## Live check
76
+ Run \`bash scripts/run.sh\`"
77
+ run_lint "$tmpdir/no-goal.md"
78
+ if [ "$LINT_RC" -eq 1 ] && printf '%s' "$LINT_ERR" | grep -q "missing-section.*Goal"; then
79
+ pass "missing ## Goal detected"
80
+ else
81
+ fail "missing Goal" "rc=$LINT_RC err='$LINT_ERR'"
82
+ fi
83
+ }
84
+
85
+ test_missing_files() {
86
+ write_spec no-files.md "
87
+ ## Goal
88
+ Do something.
89
+
90
+ ## Tests
91
+ Tests.
92
+
93
+ ## Acceptance
94
+ Run \`bash scripts/test.sh\`.
95
+
96
+ ## Live check
97
+ Run \`bash scripts/run.sh\`"
98
+ run_lint "$tmpdir/no-files.md"
99
+ if [ "$LINT_RC" -eq 1 ] && printf '%s' "$LINT_ERR" | grep -q "missing-section.*Files"; then
100
+ pass "missing ## Files detected"
101
+ else
102
+ fail "missing Files" "rc=$LINT_RC err='$LINT_ERR'"
103
+ fi
104
+ }
105
+
106
+ test_missing_tests() {
107
+ write_spec no-tests.md "
108
+ ## Goal
109
+ Do something.
110
+
111
+ ## Files
112
+ - NEW src/X.cs
113
+
114
+ ## Acceptance
115
+ Run \`bash scripts/test.sh\`.
116
+
117
+ ## Live check
118
+ Run \`bash scripts/run.sh\`"
119
+ run_lint "$tmpdir/no-tests.md"
120
+ if [ "$LINT_RC" -eq 1 ] && printf '%s' "$LINT_ERR" | grep -q "missing-section.*Tests"; then
121
+ pass "missing ## Tests detected"
122
+ else
123
+ fail "missing Tests" "rc=$LINT_RC err='$LINT_ERR'"
124
+ fi
125
+ }
126
+
127
+ test_missing_acceptance() {
128
+ write_spec no-acceptance.md "
129
+ ## Goal
130
+ Do something.
131
+
132
+ ## Files
133
+ - NEW src/X.cs
134
+
135
+ ## Tests
136
+ Tests.
137
+
138
+ ## Live check
139
+ Run \`bash scripts/run.sh\`"
140
+ run_lint "$tmpdir/no-acceptance.md"
141
+ if [ "$LINT_RC" -eq 1 ] && printf '%s' "$LINT_ERR" | grep -q "missing-section.*Acceptance"; then
142
+ pass "missing ## Acceptance detected"
143
+ else
144
+ fail "missing Acceptance" "rc=$LINT_RC err='$LINT_ERR'"
145
+ fi
146
+ }
147
+
148
+ test_missing_live_check() {
149
+ write_spec no-live.md "
150
+ ## Goal
151
+ Do something.
152
+
153
+ ## Files
154
+ - NEW src/X.cs
155
+
156
+ ## Tests
157
+ Tests.
158
+
159
+ ## Acceptance
160
+ Run \`bash scripts/test.sh\`"
161
+ run_lint "$tmpdir/no-live.md"
162
+ if [ "$LINT_RC" -eq 1 ] && printf '%s' "$LINT_ERR" | grep -q "missing-section.*Live check"; then
163
+ pass "missing ## Live check detected"
164
+ else
165
+ fail "missing Live check" "rc=$LINT_RC err='$LINT_ERR'"
166
+ fi
167
+ }
168
+
169
+ test_empty_section() {
170
+ write_spec empty-sec.md "
171
+ ## Goal
172
+ Do something.
173
+
174
+ ## Files
175
+
176
+ ## Tests
177
+ Tests.
178
+
179
+ ## Acceptance
180
+ Run \`bash scripts/test.sh\`.
181
+
182
+ ## Live check
183
+ Run \`bash scripts/run.sh\`"
184
+ run_lint "$tmpdir/empty-sec.md"
185
+ if [ "$LINT_RC" -eq 1 ] && printf '%s' "$LINT_ERR" | grep -q "empty-section.*Files"; then
186
+ pass "empty ## Files detected"
187
+ else
188
+ fail "empty section" "rc=$LINT_RC err='$LINT_ERR'"
189
+ fi
190
+ }
191
+
192
+ test_zero_files() {
193
+ write_spec zero-files.md "
194
+ ## Goal
195
+ Do something.
196
+
197
+ ## Files
198
+ No files listed here.
199
+
200
+ ## Tests
201
+ Tests.
202
+
203
+ ## Acceptance
204
+ Run \`bash scripts/test.sh\`.
205
+
206
+ ## Live check
207
+ Run \`bash scripts/run.sh\`"
208
+ run_lint "$tmpdir/zero-files.md"
209
+ if [ "$LINT_RC" -eq 1 ] && printf '%s' "$LINT_ERR" | grep -q "files-count.*0"; then
210
+ pass "zero file entries detected"
211
+ else
212
+ fail "zero files" "rc=$LINT_RC err='$LINT_ERR'"
213
+ fi
214
+ }
215
+
216
+ test_nine_files() {
217
+ # 9 entries exceeds the max of 8
218
+ write_spec nine-files.md "
219
+ ## Goal
220
+ Do something.
221
+
222
+ ## Files
223
+ - NEW src/A.cs
224
+ - NEW src/B.cs
225
+ - NEW src/C.cs
226
+ - NEW src/D.cs
227
+ - NEW src/E.cs
228
+ - NEW src/F.cs
229
+ - NEW src/G.cs
230
+ - NEW src/H.cs
231
+ - NEW src/I.cs
232
+
233
+ ## Tests
234
+ Tests.
235
+
236
+ ## Acceptance
237
+ Run \`bash scripts/test.sh\`.
238
+
239
+ ## Live check
240
+ Run \`bash scripts/run.sh\`"
241
+ run_lint "$tmpdir/nine-files.md"
242
+ if [ "$LINT_RC" -eq 1 ] && printf '%s' "$LINT_ERR" | grep -q "files-count.*9"; then
243
+ pass "nine file entries detected"
244
+ else
245
+ fail "nine files" "rc=$LINT_RC err='$LINT_ERR'"
246
+ fi
247
+ }
248
+
249
+ test_too_many_lines() {
250
+ # Build a spec with 121 lines: 9 header lines + 112 padding lines
251
+ {
252
+ echo "## Goal"
253
+ echo "Goal text."
254
+ echo "## Files"
255
+ echo "- NEW src/X.cs"
256
+ echo "## Tests"
257
+ echo "Tests."
258
+ echo "## Acceptance"
259
+ echo "Run \`bash scripts/test.sh\`."
260
+ echo "## Live check"
261
+ # Pad to reach 121 lines total (9 header + 112 padding = 121)
262
+ local count=0
263
+ while [ "$count" -lt 112 ]; do
264
+ echo "padding line $count"
265
+ count=$((count + 1))
266
+ done
267
+ } > "$tmpdir/long.md"
268
+ run_lint "$tmpdir/long.md"
269
+ if [ "$LINT_RC" -eq 1 ] && printf '%s' "$LINT_ERR" | grep -q "size-lines.*121"; then
270
+ pass "121 lines detected"
271
+ else
272
+ fail "121 lines" "rc=$LINT_RC err='$LINT_ERR'"
273
+ fi
274
+ }
275
+
276
+ test_no_command() {
277
+ write_spec no-cmd.md "
278
+ ## Goal
279
+ Do something.
280
+
281
+ ## Files
282
+ - NEW src/X.cs
283
+
284
+ ## Tests
285
+ Tests.
286
+
287
+ ## Acceptance
288
+ Everything should work.
289
+
290
+ ## Live check
291
+ Just run it manually."
292
+ run_lint "$tmpdir/no-cmd.md"
293
+ if [ "$LINT_RC" -eq 1 ] && printf '%s' "$LINT_ERR" | grep -q "no-command"; then
294
+ pass "no concrete command detected"
295
+ else
296
+ fail "no command" "rc=$LINT_RC err='$LINT_ERR'"
297
+ fi
298
+ }
299
+
300
+ test_multiple_violations() {
301
+ # Missing Goal + empty Files + no command
302
+ write_spec multi.md "
303
+ ## Files
304
+
305
+ ## Tests
306
+ Tests.
307
+
308
+ ## Acceptance
309
+ It works.
310
+
311
+ ## Live check
312
+ Run it."
313
+ run_lint "$tmpdir/multi.md"
314
+ local missing_goal=0 empty_files=0 no_cmd=0
315
+ printf '%s' "$LINT_ERR" | grep -q "missing-section.*Goal" && missing_goal=1
316
+ printf '%s' "$LINT_ERR" | grep -q "empty-section.*Files" && empty_files=1
317
+ printf '%s' "$LINT_ERR" | grep -q "no-command" && no_cmd=1
318
+ if [ "$LINT_RC" -eq 1 ] && [ "$missing_goal" -eq 1 ] && [ "$empty_files" -eq 1 ] && [ "$no_cmd" -eq 1 ]; then
319
+ pass "multiple violations reported together"
320
+ else
321
+ fail "multiple violations" "rc=$LINT_RC missing_goal=$missing_goal empty_files=$empty_files no_cmd=$no_cmd err='$LINT_ERR'"
322
+ fi
323
+ }
324
+
325
+ test_no_arg() {
326
+ local rc=0 out
327
+ out="$(bash "$LINT" 2>&1)" || rc=$?
328
+ if [ "$rc" -eq 2 ]; then
329
+ pass "no argument exits 2"
330
+ else
331
+ fail "no argument" "rc=$rc out='$out'"
332
+ fi
333
+ }
334
+
335
+ test_missing_file() {
336
+ local rc=0 out
337
+ out="$(bash "$LINT" "/nonexistent/spec.md" 2>&1)" || rc=$?
338
+ if [ "$rc" -eq 2 ]; then
339
+ pass "missing file exits 2"
340
+ else
341
+ fail "missing file" "rc=$rc out='$out'"
342
+ fi
343
+ }
344
+
345
+ test_spec_lint_off() {
346
+ write_spec any.md "
347
+ ## Goal
348
+ Anything."
349
+ local rc=0 out
350
+ out="$(SPEC_LINT=off bash "$LINT" "$tmpdir/any.md" 2>&1)" || rc=$?
351
+ if [ "$rc" -eq 0 ] && printf '%s' "$out" | grep -q "skipped"; then
352
+ pass "SPEC_LINT=off skips validation"
353
+ else
354
+ fail "SPEC_LINT=off" "rc=$rc out='$out'"
355
+ fi
356
+ }
357
+
358
+ test_template_passes() {
359
+ run_lint "$PROJECT_ROOT/knowledge/spec-template.md"
360
+ if [ "$LINT_RC" -eq 0 ] && [ "$LINT_OUT" = "spec-lint: OK" ]; then
361
+ pass "knowledge/spec-template.md passes spec-lint"
362
+ else
363
+ fail "template" "rc=$LINT_RC out='$LINT_OUT' err='$LINT_ERR'"
364
+ fi
365
+ }
366
+
367
+ test_run_task_bad_spec() {
368
+ # run-task.sh should reject a bad spec before creating any worktree
369
+ write_spec bad.md "
370
+ ## Goal
371
+ Incomplete spec."
372
+
373
+ # Use a unique name to avoid collisions
374
+ local name="spec-lint-test-$$"
375
+
376
+ # Copy required scripts into a temp repo (minimal setup for run-task.sh)
377
+ local repo="$tmpdir/repo"
378
+ mkdir -p "$repo/scripts"
379
+ cp "$PROJECT_ROOT/scripts/spec-lint.sh" "$repo/scripts/spec-lint.sh"
380
+ cp "$PROJECT_ROOT/scripts/junie-task.sh" "$repo/scripts/junie-task.sh"
381
+ cp "$PROJECT_ROOT/scripts/kilo-task.sh" "$repo/scripts/kilo-task.sh"
382
+ cp "$PROJECT_ROOT/scripts/worktree.sh" "$repo/scripts/worktree.sh"
383
+ cp "$RUN_TASK" "$repo/scripts/run-task.sh"
384
+ mkdir -p "$repo/tmp/runs"
385
+
386
+ # Write the bad spec in the repo
387
+ cp "$tmpdir/bad.md" "$repo/spec.md"
388
+
389
+ local rc=0 out
390
+ cd "$repo"
391
+ out="$(bash scripts/run-task.sh "$name" --file spec.md 2>&1)" || rc=$?
392
+
393
+ # Should exit 1 (lint failure)
394
+ if [ "$rc" -eq 1 ] && printf '%s' "$out" | grep -q "spec-lint:"; then
395
+ pass "run-task.sh rejects bad spec (exit 1)"
396
+ else
397
+ fail "run-task bad spec" "rc=$rc out='$out'"
398
+ fi
399
+
400
+ # No worktree directory should be created
401
+ if [ ! -d "tmp/worktrees/$name" ]; then
402
+ pass "run-task.sh creates no worktree for bad spec"
403
+ else
404
+ fail "run-task worktree" "tmp/worktrees/$name exists"
405
+ fi
406
+
407
+ cd "$PROJECT_ROOT"
408
+ }
409
+
410
+ # ── main ─────────────────────────────────────────────────────────────────
411
+
412
+ echo "=== spec-lint tests ==="
413
+
414
+ test_valid_spec
415
+ test_missing_goal
416
+ test_missing_files
417
+ test_missing_tests
418
+ test_missing_acceptance
419
+ test_missing_live_check
420
+ test_empty_section
421
+ test_zero_files
422
+ test_nine_files
423
+ test_too_many_lines
424
+ test_no_command
425
+ test_multiple_violations
426
+ test_no_arg
427
+ test_missing_file
428
+ test_spec_lint_off
429
+ test_template_passes
430
+ test_run_task_bad_spec
431
+
432
+ echo ""
433
+ echo "=== Results: $passed/$total passed, $failed failed ==="
434
+
435
+ if [ "$failed" -gt 0 ]; then
436
+ exit 1
437
+ fi
438
+ exit 0
@@ -0,0 +1,50 @@
1
+ """Tests for scripts/sprint-auto.sh.
2
+
3
+ Run: bash scripts/test_sprint_auto.sh (if exists) or manual verification.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import os
8
+ import subprocess
9
+ import sys
10
+ import tempfile
11
+ import unittest
12
+ from pathlib import Path
13
+
14
+ SCRIPT = Path(__file__).resolve().parent.parent / "scripts" / "sprint-auto.sh"
15
+
16
+
17
+ class SprintAutoTests(unittest.TestCase):
18
+
19
+ def test_requires_yolo_force(self):
20
+ proc = subprocess.run(
21
+ ["bash", str(SCRIPT)], capture_output=True, text=True, timeout=10,
22
+ )
23
+ self.assertNotEqual(proc.returncode, 0)
24
+ self.assertIn("--yolo-force is required", proc.stderr)
25
+
26
+ def test_requires_goal(self):
27
+ proc = subprocess.run(
28
+ ["bash", str(SCRIPT), "--yolo-force"], capture_output=True, text=True, timeout=10,
29
+ )
30
+ self.assertNotEqual(proc.returncode, 0)
31
+ self.assertIn("--goal is required", proc.stderr)
32
+
33
+ def test_requires_run_name(self):
34
+ proc = subprocess.run(
35
+ ["bash", str(SCRIPT), "--yolo-force", "--goal", "test"], capture_output=True, text=True, timeout=10,
36
+ )
37
+ self.assertNotEqual(proc.returncode, 0)
38
+ self.assertIn("--run-name is required", proc.stderr)
39
+
40
+ def test_unknown_option(self):
41
+ proc = subprocess.run(
42
+ ["bash", str(SCRIPT), "--yolo-force", "--goal", "test", "--run-name", "r1", "--bogus"],
43
+ capture_output=True, text=True, timeout=10,
44
+ )
45
+ self.assertNotEqual(proc.returncode, 0)
46
+ self.assertIn("unknown option", proc.stderr)
47
+
48
+
49
+ if __name__ == "__main__":
50
+ unittest.main()
@@ -0,0 +1,112 @@
1
+ """Tests for scripts/sprint_auto_execute.py.
2
+
3
+ Run: PYTHONPATH=scripts python3 -m unittest test_sprint_auto_execute
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import json
8
+ import os
9
+ import sys
10
+ import tempfile
11
+ import unittest
12
+ from pathlib import Path
13
+
14
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
15
+ import sprint_auto_execute as sae
16
+
17
+
18
+ class ExecuteTests(unittest.TestCase):
19
+
20
+ def test_scope_conformity_ok(self):
21
+ ok, violations = sae.check_scope_conformity(
22
+ ["scripts/a.sh", "src/b.cs"], ["scripts/", "src/"]
23
+ )
24
+ self.assertTrue(ok)
25
+ self.assertEqual(violations, [])
26
+
27
+ def test_scope_conformity_violation(self):
28
+ ok, violations = sae.check_scope_conformity(
29
+ ["scripts/a.sh", "/etc/passwd"], ["scripts/"]
30
+ )
31
+ self.assertFalse(ok)
32
+ self.assertIn("/etc/passwd", violations)
33
+
34
+ def test_scope_conformity_empty_allowed(self):
35
+ # No allowed scopes → everything conforms (no restriction)
36
+ ok, violations = sae.check_scope_conformity(["any/file.py"], [])
37
+ self.assertTrue(ok)
38
+
39
+ def test_tracker_done_insufficient(self):
40
+ self.assertTrue(sae.verify_tracker_done_insufficient("1.1"))
41
+
42
+ def test_auto_accept_all_criteria(self):
43
+ accept, reason = sae.auto_accept_decision(True, True, 0, True)
44
+ self.assertTrue(accept)
45
+ self.assertEqual(reason, "all criteria met")
46
+
47
+ def test_auto_accept_scope_violation(self):
48
+ accept, reason = sae.auto_accept_decision(False, True, 0, True)
49
+ self.assertFalse(accept)
50
+ self.assertEqual(reason, "scope violation")
51
+
52
+ def test_auto_accept_checks_failed(self):
53
+ accept, reason = sae.auto_accept_decision(True, False, 0, True)
54
+ self.assertFalse(accept)
55
+ self.assertEqual(reason, "checks failed")
56
+
57
+ def test_auto_accept_red_flags(self):
58
+ accept, reason = sae.auto_accept_decision(True, True, 2, True)
59
+ self.assertFalse(accept)
60
+ self.assertEqual(reason, "2 red flag(s)")
61
+
62
+ def test_auto_accept_tracker_not_done(self):
63
+ accept, reason = sae.auto_accept_decision(True, True, 0, False)
64
+ self.assertFalse(accept)
65
+ self.assertEqual(reason, "tracker not done")
66
+
67
+ def test_execute_task_loop_all_accepted(self):
68
+ tasks = [
69
+ {"key": "a", "task_id": "1.1", "files": ["scripts/a.sh"], "checks_green": True, "red_flags": 0, "tracker_done": True},
70
+ ]
71
+ import tempfile
72
+ from pathlib import Path
73
+ tmp = Path(tempfile.mkdtemp())
74
+ state_path = tmp / "state.json"
75
+ import sprint_auto_state as sas
76
+ state = sas.RunState(run_name="test", allowed_scopes=["scripts/"])
77
+ sas.write_checkpoint(state_path, state)
78
+ state = sas.read_checkpoint(state_path)
79
+ results = sae.execute_task_loop(tmp, "1", tasks, state, ["scripts/"])
80
+ self.assertEqual(len(results), 1)
81
+ self.assertTrue(results[0]["accept"])
82
+ self.assertEqual(results[0]["status"], "accepted")
83
+
84
+ def test_execute_task_loop_scope_violation(self):
85
+ tasks = [
86
+ {"key": "a", "task_id": "1.1", "files": ["/etc/passwd"], "checks_green": True, "red_flags": 0, "tracker_done": True},
87
+ ]
88
+ import tempfile
89
+ from pathlib import Path
90
+ tmp = Path(tempfile.mkdtemp())
91
+ import sprint_auto_state as sas
92
+ state = sas.RunState(run_name="test2", allowed_scopes=["scripts/"])
93
+ results = sae.execute_task_loop(tmp, "1", tasks, state, ["scripts/"])
94
+ self.assertEqual(results[0]["status"], "scope_violation")
95
+ self.assertFalse(results[0]["accept"])
96
+
97
+ def test_execute_task_loop_red_flags(self):
98
+ tasks = [
99
+ {"key": "a", "task_id": "1.1", "files": ["scripts/a.sh"], "checks_green": True, "red_flags": 1, "tracker_done": True},
100
+ ]
101
+ import tempfile
102
+ from pathlib import Path
103
+ tmp = Path(tempfile.mkdtemp())
104
+ import sprint_auto_state as sas
105
+ state = sas.RunState(run_name="test3", allowed_scopes=["scripts/"])
106
+ results = sae.execute_task_loop(tmp, "1", tasks, state, ["scripts/"])
107
+ self.assertEqual(results[0]["status"], "rejected")
108
+ self.assertFalse(results[0]["accept"])
109
+
110
+
111
+ if __name__ == "__main__":
112
+ unittest.main()