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,172 @@
1
+ #!/usr/bin/env bash
2
+ # Tests for the Kilo sprint planner pipeline (plan-sprint.sh, sprint_plan.py, apply-plan.sh).
3
+ # A stub `kilo` on KILO_BIN replaces the model, so nothing touches the network or the Kilo Gateway.
4
+ # Each test runs in its own throw-away git repo with its own tracker (VIBE_TASKS_ROOT).
5
+ # Exit code: 0 if all pass, 1 if any fail.
6
+ set -uo pipefail
7
+
8
+ passed=0
9
+ failed=0
10
+ total=0
11
+ pass() { passed=$((passed + 1)); total=$((total + 1)); echo " PASS: $1"; }
12
+ fail() { failed=$((failed + 1)); total=$((total + 1)); echo " FAIL: $1 — $2"; }
13
+
14
+ PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
15
+
16
+ make_repo() {
17
+ TEST_REPO="$(mktemp -d)"
18
+ cd "$TEST_REPO"
19
+ git init -q -b main
20
+ git config user.name test
21
+ git config user.email test@localhost
22
+ mkdir -p scripts bin
23
+ for f in plan-sprint.sh apply-plan.sh sprint_plan.py spec-lint.sh tasks.py tasks_init.py worktree.sh needs-dotnet-checks.sh; do
24
+ cp "$PROJECT_ROOT/scripts/$f" scripts/
25
+ done
26
+ printf 'tmp/\ntasks/\n' > .gitignore
27
+ git add -A && git commit -q -m base
28
+ export VIBE_TASKS_ROOT="$TEST_REPO"
29
+ python3 scripts/tasks.py add-sprint "Old" >/dev/null
30
+ }
31
+
32
+ cleanup_repo() { cd /; rm -rf "$TEST_REPO"; }
33
+
34
+ # stub_kilo <plan-json-or-text> [exit-code]: bin/kilo prints the model answer as a Kilo JSON event stream.
35
+ stub_kilo() {
36
+ printf '%s' "$1" > "$TEST_REPO/bin/answer.txt"
37
+ cat > "$TEST_REPO/bin/kilo" <<FAKE
38
+ #!/usr/bin/env bash
39
+ printf '%s\n' "\$*" > "$TEST_REPO/bin/kilo.args"
40
+ [ "${2:-0}" -eq 0 ] || exit ${2:-0}
41
+ python3 - "$TEST_REPO/bin/answer.txt" <<'PY'
42
+ import json, sys
43
+ print(json.dumps({"part": {"type": "text", "text": open(sys.argv[1]).read()}}))
44
+ PY
45
+ FAKE
46
+ chmod +x "$TEST_REPO/bin/kilo"
47
+ export KILO_BIN="$TEST_REPO/bin/kilo"
48
+ }
49
+
50
+ SPEC='# Task: a\n\n## Goal\nDo a.\n\n## Files\n- EDIT: scripts/a.sh — a\n\n## Tests\nscripts/test_a.sh\n\n## Acceptance\n`bash scripts/test_a.sh`\n\n## Live check\n`bash scripts/a.sh`\n'
51
+
52
+ good_plan() {
53
+ python3 - "$SPEC" <<'PY'
54
+ import json, sys
55
+ spec = sys.argv[1].encode().decode("unicode_escape")
56
+ plan = {"sprint": {"slug": "demo-plan", "title": "Demo plan", "goal": "Prove the planner"},
57
+ "tasks": [
58
+ {"key": "a", "title": "Add a", "description": "first", "depends_on": [], "scope": ["scripts/a.sh"],
59
+ "spec_name": "demo-a", "spec": spec},
60
+ {"key": "b", "title": "Add b", "description": "second", "depends_on": ["a"], "scope": ["scripts/b.sh"],
61
+ "spec_name": "demo-b", "spec": spec.replace("a.sh", "b.sh").replace("test_a", "test_b")}]}
62
+ print("Here is the plan:\n```json\n" + json.dumps(plan) + "\n```")
63
+ PY
64
+ }
65
+
66
+ test_plan_happy_path() {
67
+ make_repo; trap cleanup_repo RETURN
68
+ stub_kilo "$(good_plan)"
69
+ local rc=0
70
+ bash scripts/plan-sprint.sh demo --goal "prove it" >/dev/null 2>&1 || rc=$?
71
+ if [ "$rc" -eq 0 ] && [ -f tmp/plans/demo.json ] && [ -f tmp/tasks/demo-a.md ] && [ -f tmp/tasks/demo-b.md ]; then
72
+ pass "plan-sprint: valid plan is saved with its specs"
73
+ else
74
+ fail "plan happy path" "rc=$rc"
75
+ fi
76
+ if grep -q -- "--agent planner" bin/kilo.args && grep -q -- "-m kilo/openai/gpt-6.1-sol" bin/kilo.args \
77
+ && grep -q -- "--variant high" bin/kilo.args; then
78
+ pass "plan-sprint: runs the planner agent on Sol with the high variant"
79
+ else
80
+ fail "plan args" "$(cat bin/kilo.args)"
81
+ fi
82
+ grep -q "## Tracker overview" tmp/plans/demo.input.md && grep -q "prove it" tmp/plans/demo.input.md \
83
+ && pass "plan-sprint: input bundle has goal and tracker overview" || fail "input bundle" "missing sections"
84
+ }
85
+
86
+ test_plan_rejects_invalid() {
87
+ make_repo; trap cleanup_repo RETURN
88
+ local bad
89
+ bad="$(good_plan | sed 's/"scope": \["scripts\/a.sh"\]/"scope": []/')"
90
+ stub_kilo "$bad"
91
+ local rc=0 err
92
+ err="$(bash scripts/plan-sprint.sh bad --goal g 2>&1 >/dev/null)" || rc=$?
93
+ if [ "$rc" -eq 4 ] && [ ! -e tmp/tasks/demo-a.md ] && printf '%s' "$err" | grep -q "scope path is required"; then
94
+ pass "plan-sprint: plan without scope exits 4 and writes no specs"
95
+ else
96
+ fail "invalid plan" "rc=$rc err=$err"
97
+ fi
98
+ stub_kilo "no json here"
99
+ rc=0
100
+ bash scripts/plan-sprint.sh prose --goal g >/dev/null 2>&1 || rc=$?
101
+ [ "$rc" -eq 4 ] && pass "plan-sprint: answer without JSON exits 4" || fail "no json" "rc=$rc"
102
+ }
103
+
104
+ test_plan_rejects_forbidden_scope() {
105
+ make_repo; trap cleanup_repo RETURN
106
+ stub_kilo "$(good_plan | sed 's#scripts/b.sh#kilo.json#')"
107
+ local rc=0
108
+ bash scripts/plan-sprint.sh forb --goal g >/dev/null 2>&1 || rc=$?
109
+ [ "$rc" -eq 4 ] && pass "plan-sprint: forbidden scope path is rejected" || fail "forbidden scope" "rc=$rc"
110
+ }
111
+
112
+ test_plan_kilo_failure() {
113
+ make_repo; trap cleanup_repo RETURN
114
+ stub_kilo "x" 7
115
+ local rc=0
116
+ bash scripts/plan-sprint.sh fail --goal g >/dev/null 2>&1 || rc=$?
117
+ [ "$rc" -eq 3 ] && pass "plan-sprint: Kilo failure exits 3" || fail "kilo failure" "rc=$rc"
118
+ }
119
+
120
+ test_plan_usage() {
121
+ make_repo; trap cleanup_repo RETURN
122
+ local rc=0
123
+ bash scripts/plan-sprint.sh >/dev/null 2>&1 || rc=$?
124
+ [ "$rc" -eq 1 ] && pass "plan-sprint: no name exits 1" || fail "usage" "rc=$rc"
125
+ rc=0
126
+ bash scripts/plan-sprint.sh ok >/dev/null 2>&1 || rc=$?
127
+ [ "$rc" -eq 1 ] && pass "plan-sprint: missing --goal exits 1" || fail "goal required" "rc=$rc"
128
+ }
129
+
130
+ test_apply_plan() {
131
+ make_repo; trap cleanup_repo RETURN
132
+ stub_kilo "$(good_plan)"
133
+ bash scripts/plan-sprint.sh demo --goal g >/dev/null 2>&1
134
+ local rc=0
135
+ bash scripts/apply-plan.sh demo </dev/null >/dev/null 2>&1 || rc=$?
136
+ [ "$rc" -eq 2 ] && pass "apply-plan: refuses without a TTY or --yes (exit 2)" || fail "apply needs confirm" "rc=$rc"
137
+ [ ! -e tmp/plans/demo.applied ] || fail "apply marker" "written without confirmation"
138
+
139
+ rc=0
140
+ bash scripts/apply-plan.sh demo --yes >/dev/null 2>&1 || rc=$?
141
+ local out
142
+ out="$(python3 scripts/tasks.py show 2.2 2>&1)"
143
+ if [ "$rc" -eq 0 ] && printf '%s' "$out" | grep -q '"scripts/b.sh"' && printf '%s' "$out" | grep -q 'tmp/tasks/demo-b.md' \
144
+ && printf '%s' "$out" | grep -q '2.1'; then
145
+ pass "apply-plan: creates sprint, tasks, deps, spec and scope links"
146
+ else
147
+ fail "apply" "rc=$rc out=$out"
148
+ fi
149
+ rc=0
150
+ bash scripts/apply-plan.sh demo --yes >/dev/null 2>&1 || rc=$?
151
+ [ "$rc" -eq 5 ] && pass "apply-plan: second apply is refused (exit 5)" || fail "double apply" "rc=$rc"
152
+ }
153
+
154
+ test_apply_without_plan() {
155
+ make_repo; trap cleanup_repo RETURN
156
+ local rc=0
157
+ bash scripts/apply-plan.sh nothing --yes >/dev/null 2>&1 || rc=$?
158
+ [ "$rc" -eq 1 ] && pass "apply-plan: missing plan exits 1" || fail "no plan" "rc=$rc"
159
+ }
160
+
161
+ echo "=== plan-sprint / apply-plan tests ==="
162
+ test_plan_happy_path
163
+ test_plan_rejects_invalid
164
+ test_plan_rejects_forbidden_scope
165
+ test_plan_kilo_failure
166
+ test_plan_usage
167
+ test_apply_plan
168
+ test_apply_without_plan
169
+
170
+ echo ""
171
+ echo "=== Results: $passed/$total passed, $failed failed ==="
172
+ [ "$failed" -eq 0 ]
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env bash
2
+ # Fast Python tests — skips subprocess-heavy integration tests.
3
+ # Use during development for quick feedback (~5s instead of ~20s).
4
+ set -euo pipefail
5
+
6
+ root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
7
+ cd "$root"
8
+
9
+ echo "=== fast Python tests ==="
10
+ # Fast tests (no subprocess, in-process only)
11
+ PYTHONPATH=scripts python3 -m unittest \
12
+ test_tasks_mcp \
13
+ test_run_stats \
14
+ test_agent_config \
15
+ test_tasks_watch \
16
+ 2>&1
17
+
18
+ echo ""
19
+ echo "=== Sprint 12 fast tests ==="
20
+ PYTHONPATH=scripts python3 -m unittest \
21
+ test_sprint_auto_preflight \
22
+ test_sprint_auto_state \
23
+ 2>&1
24
+
25
+ echo ""
26
+ echo "=== done ==="
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env bash
2
+ # Lint and type-check Python scripts.
3
+ set -euo pipefail
4
+
5
+ root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
6
+ cd "$root"
7
+
8
+ echo "=== ruff check ==="
9
+ if command -v ruff >/dev/null 2>&1; then
10
+ ruff check scripts/ || echo "ruff not installed, skipping"
11
+ else
12
+ echo "ruff not installed, skipping"
13
+ fi
14
+
15
+ echo "=== mypy ==="
16
+ if command -v mypy >/dev/null 2>&1; then
17
+ mypy scripts/ || echo "mypy not installed, skipping"
18
+ else
19
+ echo "mypy not installed, skipping"
20
+ fi
21
+
22
+ echo "=== lint done ==="
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env bash
2
+ # Slow Python tests — subprocess-heavy integration tests.
3
+ # Run before committing for full coverage.
4
+ set -euo pipefail
5
+
6
+ root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
7
+ cd "$root"
8
+
9
+ echo "=== slow Python tests (subprocess-heavy) ==="
10
+ PYTHONPATH=scripts python3 -m unittest test_tasks 2>&1
11
+ PYTHONPATH=scripts python3 -m unittest test_tasks_plan_import 2>&1
12
+
13
+ echo ""
14
+ echo "=== done ==="
@@ -0,0 +1,64 @@
1
+ """Tests for scripts/run_stats.py (temp dirs only; never reads the real tmp/runs)."""
2
+ import json
3
+ import subprocess
4
+ import sys
5
+ import tempfile
6
+ import unittest
7
+ from pathlib import Path
8
+
9
+ SCRIPT = Path(__file__).resolve().parent / "run_stats.py"
10
+
11
+
12
+ def run(runs_dir, *extra):
13
+ return subprocess.run([sys.executable, str(SCRIPT), "--runs-dir", str(runs_dir), *extra],
14
+ capture_output=True, text=True)
15
+
16
+
17
+ class RunStatsTests(unittest.TestCase):
18
+ def setUp(self):
19
+ self.tmp = tempfile.TemporaryDirectory()
20
+ self.dir = Path(self.tmp.name)
21
+
22
+ def tearDown(self):
23
+ self.tmp.cleanup()
24
+
25
+ def status(self, name, line):
26
+ (self.dir / f"{name}.status").write_text(line + "\n", encoding="utf-8")
27
+
28
+ def test_status_fallback_and_unfinished(self):
29
+ self.status("a", "[03:00] done - junie exit 0, verification PASSED model=m runner=junie")
30
+ self.status("b", "[10:00] done - kilo exit 0, verification PASSED model=m runner=kilo")
31
+ self.status("c", "[02:00] junie running - 0 file(s) changed model=m runner=junie")
32
+ data = json.loads(run(self.dir, "--json").stdout)
33
+ self.assertEqual(data["junie"]["runs"], 2)
34
+ self.assertEqual(data["junie"]["verified"], 1)
35
+ self.assertEqual(data["junie"]["unfinished"], 1)
36
+ self.assertEqual(data["junie"]["median_s"], 180)
37
+ self.assertEqual(data["kilo"]["median_s"], 600)
38
+ self.assertEqual(data["ALL"]["rework_rate"], 0.33)
39
+
40
+ def test_summary_wins_over_status_and_verdicts(self):
41
+ self.status("x", "[01:00] done - junie exit 0, verification PASSED model=m runner=junie")
42
+ (self.dir / "x.summary.json").write_text(json.dumps(
43
+ {"name": "x", "runner": "junie", "model": "m", "duration_s": 99, "verdict": "verify_failed"}))
44
+ self.status("y", "[01:00] done - kilo exit 3, verification FAILED model=m runner=kilo")
45
+ data = json.loads(run(self.dir, "--json").stdout)
46
+ self.assertEqual(data["junie"]["verify_failed"], 1)
47
+ self.assertEqual(data["junie"]["median_s"], 99)
48
+ self.assertEqual(data["kilo"]["agent_failed"], 1)
49
+
50
+ def test_text_table_and_missing_dir(self):
51
+ self.status("a", "[03:00] done - junie exit 0, verification PASSED model=m runner=junie")
52
+ out = run(self.dir)
53
+ self.assertEqual(out.returncode, 0)
54
+ self.assertIn("junie", out.stdout)
55
+ self.assertIn("slowest:", out.stdout)
56
+ self.assertEqual(run(self.dir / "nope").returncode, 1)
57
+
58
+ def test_empty_dir(self):
59
+ out = run(self.dir)
60
+ self.assertEqual(out.returncode, 0)
61
+
62
+
63
+ if __name__ == "__main__":
64
+ unittest.main()