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.
- scripts/__init__.py +0 -0
- scripts/agents_sync.py +61 -0
- scripts/apply-plan.sh +51 -0
- scripts/build.sh +7 -0
- scripts/codex-task.sh +198 -0
- scripts/install-hooks.sh +9 -0
- scripts/junie-status.sh +52 -0
- scripts/junie-task.sh +183 -0
- scripts/kilo-task.sh +226 -0
- scripts/needs-dotnet-checks.sh +24 -0
- scripts/plan-sprint.sh +92 -0
- scripts/run-task.sh +79 -0
- scripts/run.sh +7 -0
- scripts/run_stats.py +141 -0
- scripts/run_summary.py +94 -0
- scripts/spec-lint.sh +139 -0
- scripts/sprint-auto.sh +79 -0
- scripts/sprint_auto_execute.py +160 -0
- scripts/sprint_auto_preflight.py +309 -0
- scripts/sprint_auto_prepare.py +271 -0
- scripts/sprint_auto_resume.py +153 -0
- scripts/sprint_auto_state.py +191 -0
- scripts/sprint_plan.py +238 -0
- scripts/task-report.sh +45 -0
- scripts/tasks.py +1813 -0
- scripts/tasks_cli.py +14 -0
- scripts/tasks_commands.py +11 -0
- scripts/tasks_init.py +140 -0
- scripts/tasks_mcp.py +309 -0
- scripts/tasks_model.py +173 -0
- scripts/tasks_plan_import.py +269 -0
- scripts/tasks_render.py +148 -0
- scripts/tasks_storage.py +373 -0
- scripts/tasks_watch.py +159 -0
- scripts/test.sh +43 -0
- scripts/test_agent_config.py +97 -0
- scripts/test_plan_sprint.sh +172 -0
- scripts/test_python_fast.sh +26 -0
- scripts/test_python_lint.sh +22 -0
- scripts/test_python_slow.sh +14 -0
- scripts/test_run_stats.py +64 -0
- scripts/test_runners.sh +588 -0
- scripts/test_spec_lint.sh +438 -0
- scripts/test_sprint_auto.py +50 -0
- scripts/test_sprint_auto_execute.py +112 -0
- scripts/test_sprint_auto_preflight.py +143 -0
- scripts/test_sprint_auto_prepare.py +150 -0
- scripts/test_sprint_auto_resume.py +104 -0
- scripts/test_sprint_auto_state.py +157 -0
- scripts/test_tasks.py +1678 -0
- scripts/test_tasks_mcp.py +233 -0
- scripts/test_tasks_plan_import.py +129 -0
- scripts/test_tasks_storage.py +67 -0
- scripts/test_tasks_watch.py +239 -0
- scripts/test_worktree.sh +2112 -0
- scripts/worktree.sh +992 -0
- scripts/worktree_mr.py +133 -0
- scripts/worktree_path.py +70 -0
- scripts/worktree_sprint.py +70 -0
- tasks_toolkit-0.1.3.dist-info/METADATA +121 -0
- tasks_toolkit-0.1.3.dist-info/RECORD +65 -0
- tasks_toolkit-0.1.3.dist-info/WHEEL +5 -0
- tasks_toolkit-0.1.3.dist-info/entry_points.txt +4 -0
- tasks_toolkit-0.1.3.dist-info/licenses/LICENSE +21 -0
- 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()
|