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,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()
|