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
scripts/__init__.py ADDED
File without changes
scripts/agents_sync.py ADDED
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env python3
2
+ """Generate the single-file guidelines for a tool that loads exactly one instruction file (Junie).
3
+
4
+ Concatenates `AGENTS.md`, the implementer subset of `agents/common/*.md` and
5
+ `agents/junie/INSTRUCTIONS.md` into `agents/junie/GUIDELINES.generated.md`. `--check` regenerates in
6
+ memory and fails when the file on disk differs, so `scripts/test.sh` catches drift. Standard library only.
7
+
8
+ Usage: scripts/agents_sync.py [--check]
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import sys
13
+ from pathlib import Path
14
+
15
+ ROOT = Path(__file__).resolve().parent.parent
16
+ TARGET = ROOT / "agents" / "junie" / "GUIDELINES.generated.md"
17
+ SOURCES = [
18
+ "AGENTS.md",
19
+ "agents/common/layout.md",
20
+ "agents/common/git-and-hooks.md",
21
+ "agents/junie/INSTRUCTIONS.md",
22
+ ]
23
+ HEADER = (
24
+ "<!-- GENERATED by scripts/agents_sync.py — do not edit by hand. "
25
+ "Run: python3 scripts/agents_sync.py -->\n"
26
+ "# VibeEmulator — Junie guidelines (generated)\n\n"
27
+ "The shared rules and the files a Junie implementer needs, concatenated for a tool that loads\n"
28
+ "exactly one guidelines file. Sources: " + ", ".join(SOURCES) + ".\n"
29
+ )
30
+
31
+
32
+ def render() -> str:
33
+ """Return the full generated guidelines text (deterministic, newline-terminated)."""
34
+ parts = [HEADER]
35
+ for rel in SOURCES:
36
+ text = (ROOT / rel).read_text(encoding="utf-8").rstrip("\n")
37
+ parts.append(f"\n---\n\n## From {rel}\n\n{text}\n")
38
+ return "".join(parts)
39
+
40
+
41
+ def main(argv: list[str]) -> int:
42
+ """Write the guidelines file, or with --check compare it and fail on drift."""
43
+ expected = render()
44
+ rel = TARGET.relative_to(ROOT)
45
+ if "--check" in argv:
46
+ if not TARGET.exists():
47
+ print(f"agents_sync: {rel} is missing; run: python3 scripts/agents_sync.py", file=sys.stderr)
48
+ return 1
49
+ if TARGET.read_text(encoding="utf-8") != expected:
50
+ print(f"agents_sync: {rel} is stale; run: python3 scripts/agents_sync.py", file=sys.stderr)
51
+ return 1
52
+ print("agents_sync: guidelines in sync")
53
+ return 0
54
+ TARGET.parent.mkdir(parents=True, exist_ok=True)
55
+ TARGET.write_text(expected, encoding="utf-8")
56
+ print(f"wrote {rel}")
57
+ return 0
58
+
59
+
60
+ if __name__ == "__main__":
61
+ sys.exit(main(sys.argv[1:]))
scripts/apply-plan.sh ADDED
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env bash
2
+ # Imports a validated sprint plan (tmp/plans/<name>.json) into the tracker through tasks.py.
3
+ # Previews the plan and needs a human "y" on a TTY (otherwise --yes). A plan is applied once.
4
+ #
5
+ # scripts/apply-plan.sh <name> [--yes] [--start]
6
+ #
7
+ # --start also creates the sprint branch (scripts/worktree.sh sprint start <n> <slug>).
8
+ # Exit: 0 applied | 1 usage/missing plan | 2 not confirmed | 4 plan invalid | 5 already applied
9
+ set -euo pipefail
10
+
11
+ root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
12
+ cd "$root"
13
+
14
+ name="${1:-}"
15
+ [[ "$name" =~ ^[a-z0-9][a-z0-9-]*$ ]] || { echo "usage: $0 <name> [--yes] [--start]" >&2; exit 1; }
16
+ shift
17
+ yes=0 start=0
18
+ while [ $# -gt 0 ]; do
19
+ case "$1" in
20
+ --yes) yes=1; shift ;;
21
+ --start) start=1; shift ;;
22
+ *) echo "unknown option '$1'" >&2; exit 1 ;;
23
+ esac
24
+ done
25
+
26
+ plan="tmp/plans/$name.json"
27
+ marker="tmp/plans/$name.applied"
28
+ [ -f "$plan" ] || { echo "error: no plan '$plan' (run scripts/plan-sprint.sh $name first)" >&2; exit 1; }
29
+ [ ! -f "$marker" ] || { echo "error: '$name' was already applied ($(cat "$marker"))" >&2; exit 5; }
30
+
31
+ python3 scripts/sprint_plan.py validate "$plan" --specs-written || exit 4
32
+ python3 scripts/sprint_plan.py preview "$plan"
33
+ echo ""
34
+ if [ "$yes" -ne 1 ]; then
35
+ if [ ! -t 0 ]; then
36
+ echo "error: not a TTY; re-run with --yes to apply without asking" >&2
37
+ exit 2
38
+ fi
39
+ read -r -p "Apply this plan to the tracker? [y/N] " answer
40
+ [[ "$answer" =~ ^[yY]([eE][sS])?$ ]] || { echo "not applied" >&2; exit 2; }
41
+ fi
42
+
43
+ mapping="$(python3 scripts/sprint_plan.py apply "$plan")"
44
+ printf '%s\n' "$mapping"
45
+ sprint_id="$(printf '%s\n' "$mapping" | head -1 | sed 's/.*-> \([0-9]*\)\..*/\1/')"
46
+ slug="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["sprint"]["slug"])' "$plan")"
47
+ printf 'sprint %s on %s\n' "$sprint_id" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >"$marker"
48
+ echo "applied: sprint $sprint_id ($slug)"
49
+ if [ "$start" -eq 1 ]; then
50
+ bash scripts/worktree.sh sprint start "$sprint_id" "$slug"
51
+ fi
scripts/build.sh ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ cd "$root"
6
+
7
+ exec dotnet build VibeEmulator.slnx "$@"
scripts/codex-task.sh ADDED
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env bash
2
+ # Delegates one task to the Codex CLI agent inside its own worktree, then verifies it.
3
+ # Same contract as junie-task.sh / kilo-task.sh (worktree, heartbeat, verification, patch).
4
+ #
5
+ # scripts/codex-task.sh <name> "<task text>" [--task-id <id>]
6
+ # scripts/codex-task.sh <name> --file tmp/tasks/<name>.md [--task-id <id>]
7
+ #
8
+ # --task-id reports progress to the task tracker (scripts/task-report.sh): the task is
9
+ # marked in progress at the start, and after the commit and verification it is closed
10
+ # automatically (done on success, blocked on failure); failures only warn.
11
+ #
12
+ # Env: CODEX_MODEL (default: Codex's own default), CODEX_SANDBOX (default workspace-write),
13
+ # CODEX_TIMEOUT (default 1200 s), CODEX_PING (default 30 s),
14
+ # CODEX_VERIFY_CMD (tests only: replaces the dotnet verification).
15
+ # Output: tmp/runs/<name>.{status,events.jsonl,result.txt,verify.log,summary.json},
16
+ # tmp/patches/<name>.patch. Every status line ends with "model=<model> runner=codex".
17
+ # Exit code: 0 verified, 1 verification failed, 3 Codex itself failed.
18
+ #
19
+ # Notes:
20
+ # - Codex runs in a workspace-write sandbox with the network off by default; `dotnet` writes to
21
+ # ~/.nuget and ~/.dotnet, so those are added to the sandbox (--add-dir) or restore/build fails.
22
+ # - The worktree is disposable; review the patch. Never bypass the sandbox or approval flags.
23
+ set -euo pipefail
24
+
25
+ root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
26
+ cd "$root"
27
+
28
+ runner="codex"
29
+ name="${1:?usage: codex-task.sh <name> \"<task>\" | --file <path> [--task-id <id>]}"
30
+ shift
31
+ if [ "${1:-}" = "--file" ]; then
32
+ task="$(cat "${2:?missing task file}")"
33
+ shift 2
34
+ else
35
+ task="${1:?missing task text}"
36
+ shift
37
+ fi
38
+ task_id=""
39
+ if [ "${1:-}" = "--task-id" ]; then
40
+ task_id="${2:?--task-id needs a value}"
41
+ fi
42
+
43
+ model="${CODEX_MODEL:-codex}"
44
+ sandbox="${CODEX_SANDBOX:-workspace-write}"
45
+ limit="${CODEX_TIMEOUT:-1200}"
46
+ ping_every="${CODEX_PING:-30}"
47
+
48
+ if [ -d "tmp/worktrees/$name" ]; then
49
+ wt="$root/tmp/worktrees/$name"
50
+ else
51
+ wt="$(bash scripts/worktree.sh new "$name")"
52
+ fi
53
+ mkdir -p tmp/runs
54
+
55
+ prompt="$task
56
+
57
+ Rules for this run:
58
+ - Work only inside this project directory (a disposable git worktree). Read AGENTS.md first and follow it.
59
+ - Do not commit, push or touch git. Do not add NuGet packages.
60
+ - You are done only when 'bash scripts/test.sh' is green AND
61
+ 'dotnet build VibeEmulator.slnx --no-incremental -warnaserror' reports 0 warnings and 0 errors
62
+ (StyleCop and mandatory XML documentation are enforced on production code).
63
+ - Respect the sandbox; do not bypass approval or network flags.
64
+ - Finish with a short summary of the files you created or changed."
65
+
66
+ status_file="$root/tmp/runs/$name.status"
67
+ events="$root/tmp/runs/$name.events.jsonl"
68
+ base="$(cat "tmp/meta/$name.base")"
69
+ started=$SECONDS
70
+ started_epoch="$(date +%s)"
71
+
72
+ report() { # report <state> [detail]: one line to stdout and the status file
73
+ local line
74
+ line="$(printf '[%02d:%02d] %s' $(((SECONDS - started) / 60)) $(((SECONDS - started) % 60)) "$1${2:+ - $2}")"
75
+ echo "$line model=$model runner=$runner"
76
+ echo "$line model=$model runner=$runner" >"$status_file"
77
+ }
78
+
79
+ # last_activity: the last non-empty output line of the agent.
80
+ last_activity() {
81
+ grep -v '^[[:space:]]*$' "$events" 2>/dev/null | tail -n 1 | cut -c1-110 || true
82
+ }
83
+
84
+ bash scripts/task-report.sh start "$task_id"
85
+ report "starting" "worktree=$wt${task_id:+ task=$task_id}"
86
+
87
+ codex_args=(exec -C "$wt" --skip-git-repo-check --sandbox "$sandbox" --json)
88
+ [ -n "${CODEX_MODEL:-}" ] && codex_args+=(-m "$model")
89
+ [ -d "$HOME/.nuget" ] && codex_args+=(--add-dir "$HOME/.nuget")
90
+ [ -d "$HOME/.dotnet" ] && codex_args+=(--add-dir "$HOME/.dotnet")
91
+
92
+ timeout "$limit" codex "${codex_args[@]}" "$prompt" \
93
+ >"$events" 2>"$root/tmp/runs/$name.stderr.log" &
94
+ codex_pid=$!
95
+
96
+ stalled=0
97
+ while kill -0 "$codex_pid" 2>/dev/null; do
98
+ sleep "$ping_every" & wait $! || true
99
+ kill -0 "$codex_pid" 2>/dev/null || break
100
+ changed="$(git -C "$wt" status --porcelain | wc -l)"
101
+ if [ "$changed" -eq 0 ] && [ $((SECONDS - started)) -ge "${RUNNER_STALL:-420}" ]; then
102
+ report "stalled" "no file changed after ${RUNNER_STALL:-420}s; killing the agent"
103
+ kill "$codex_pid" 2>/dev/null || true
104
+ stalled=1
105
+ break
106
+ fi
107
+ last="$(last_activity)"
108
+ report "codex running" "$changed file(s) changed; last: ${last:-<no output yet>}"
109
+ done
110
+ set +e
111
+ wait "$codex_pid"
112
+ codex_rc=$?
113
+ set -e
114
+ report "codex finished" "exit $codex_rc, verifying"
115
+
116
+ # With --json the last assistant text is the final message; fall back to the last output line.
117
+ python3 - "$events" >"$root/tmp/runs/$name.result.txt" <<'PY'
118
+ import json, sys
119
+ out = []
120
+ for line in open(sys.argv[1], errors="replace"):
121
+ line = line.strip()
122
+ if not line:
123
+ continue
124
+ try:
125
+ event = json.loads(line)
126
+ except ValueError:
127
+ out.append(line)
128
+ continue
129
+ text = event.get("text") or (event.get("message") or {}).get("content")
130
+ if isinstance(text, str):
131
+ out.append(text)
132
+ print(out[-1] if out else "")
133
+ PY
134
+
135
+ # Commit the agent's work on task/<name> (path guard first); agents never commit themselves.
136
+ commit_rc=0
137
+ if [ "$codex_rc" -eq 0 ]; then
138
+ title="$(printf '%s\n' "$task" | grep -m1 -v '^[[:space:]]*$' | cut -c1-72)"
139
+ bash scripts/worktree.sh commit "$name" --runner "$runner" --model "$model" \
140
+ ${task_id:+--task-id "$task_id"} --title "${title:-$name}" >"tmp/runs/$name.commit.log" 2>&1 || commit_rc=$?
141
+ fi
142
+ patch="$(bash scripts/worktree.sh patch "$name")"
143
+
144
+ verify_rc=0
145
+ {
146
+ if [ -n "${CODEX_VERIFY_CMD:-}" ]; then
147
+ echo "== verify (CODEX_VERIFY_CMD override) =="
148
+ (cd "$wt" && bash -c "$CODEX_VERIFY_CMD") || verify_rc=1
149
+ elif ! bash "$root/scripts/needs-dotnet-checks.sh" "$wt" "$base" HEAD; then
150
+ echo "== conditional verification (no .NET files changed): script tests =="
151
+ (cd "$wt" && bash scripts/test_worktree.sh && bash scripts/test_runners.sh \
152
+ && python3 -m unittest discover -s scripts -p "test_*.py") || verify_rc=1
153
+ else
154
+ echo "== format =="; (cd "$wt" && dotnet format VibeEmulator.slnx --verify-no-changes) || verify_rc=1
155
+ echo "== build =="; (cd "$wt" && dotnet build VibeEmulator.slnx --no-incremental -warnaserror) || verify_rc=1
156
+ echo "== test =="; (cd "$wt" && dotnet test VibeEmulator.slnx --no-build) || verify_rc=1
157
+ fi
158
+ } >"tmp/runs/$name.verify.log" 2>&1
159
+
160
+ if [ "$commit_rc" -ne 0 ]; then
161
+ verify_rc=1
162
+ echo "== commit failed (exit $commit_rc): see tmp/runs/$name.commit.log ==" >>"tmp/runs/$name.verify.log"
163
+ fi
164
+ finished="$(date +%s)"
165
+ files_changed="$(git -C "$wt" diff --cached --name-only "$base" | wc -l)"
166
+ if [ "$codex_rc" -ne 0 ]; then
167
+ verdict="agent_failed"
168
+ elif [ "$verify_rc" -ne 0 ]; then
169
+ verdict="verify_failed"
170
+ else
171
+ verdict="verified"
172
+ fi
173
+ if ! python3 scripts/run_summary.py "$root/tmp/runs/$name.summary.json" name="$name" runner="$runner" model="$model" \
174
+ task_id="$task_id" started="$started_epoch" finished="$finished" \
175
+ agent_exit="$codex_rc" verify_exit="$verify_rc" verdict="$verdict" \
176
+ patch="$patch" files_changed="$files_changed"; then
177
+ echo "warning: could not write run summary (tmp/runs/$name.summary.json)" >&2
178
+ fi
179
+
180
+ report "done" "codex exit $codex_rc, verification $([ "$verify_rc" -eq 0 ] && echo PASSED || echo FAILED)"
181
+ echo "runner: $runner"
182
+ echo "model: $model"
183
+ echo "codex exit: $codex_rc"
184
+ echo "verification: $([ "$verify_rc" -eq 0 ] && echo PASSED || echo FAILED) (tmp/runs/$name.verify.log)"
185
+ echo "patch: $patch"
186
+ git -C "$wt" diff --cached --stat "$base" | tail -n 15
187
+ if [ "$codex_rc" -ne 0 ]; then
188
+ bash scripts/task-report.sh note "$task_id" "codex '$name' failed (exit 3) model=$model; see tmp/runs/$name.verify.log"
189
+ bash scripts/task-report.sh close "$task_id" red "agent exit $codex_rc; see tmp/runs/$name.verify.log"
190
+ exit 3
191
+ elif [ "$verify_rc" -ne 0 ]; then
192
+ bash scripts/task-report.sh note "$task_id" "codex '$name' failed (exit 1) model=$model; see tmp/runs/$name.verify.log"
193
+ bash scripts/task-report.sh close "$task_id" red "verification failed; see tmp/runs/$name.verify.log"
194
+ exit 1
195
+ else
196
+ bash scripts/task-report.sh note "$task_id" "codex '$name' verified model=$model; patch tmp/patches/$name.patch"
197
+ bash scripts/task-report.sh close "$task_id" green
198
+ fi
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ cd "$root"
6
+
7
+ chmod +x .githooks/*
8
+ git config core.hooksPath .githooks
9
+ echo "Git hooks installed (core.hooksPath=.githooks)."
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env bash
2
+ # Shows the progress of a delegated Junie or Kilo run: scripts/junie-status.sh <name>
3
+ set -euo pipefail
4
+
5
+ root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
6
+ cd "$root"
7
+ name="${1:?usage: junie-status.sh <name>}"
8
+
9
+ [ -f "tmp/runs/$name.stdout.log" ] || [ -f "tmp/runs/$name.events.jsonl" ] || { echo "no run named '$name'" >&2; exit 1; }
10
+ echo "status: $(cat "tmp/runs/$name.status" 2>/dev/null || echo "(no heartbeat recorded)")"
11
+ # runner/model come from the run summary once the run has finished, and from the
12
+ # last status line while it is still running; runs that predate model recording
13
+ # simply have neither and print nothing here.
14
+ runner=""
15
+ model=""
16
+ if [ -f "tmp/runs/$name.summary.json" ]; then
17
+ runner="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get("runner", ""))' "tmp/runs/$name.summary.json" 2>/dev/null || true)"
18
+ model="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get("model", ""))' "tmp/runs/$name.summary.json" 2>/dev/null || true)"
19
+ else
20
+ last="$(cat "tmp/runs/$name.status" 2>/dev/null || true)"
21
+ runner="$(printf '%s\n' "$last" | sed -n 's/.*runner=\([^ ]*\).*/\1/p')"
22
+ model="$(printf '%s\n' "$last" | sed -n 's/.*model=\([^ ]*\).*/\1/p')"
23
+ fi
24
+ [ -z "$runner" ] || echo "runner: $runner"
25
+ [ -z "$model" ] || echo "model: $model"
26
+ if pgrep -f "(junie|kilo) .*$root/tmp/worktrees/$name" >/dev/null; then
27
+ echo "process: running"
28
+ else
29
+ echo "process: not running"
30
+ fi
31
+ if [ -d "tmp/worktrees/$name" ]; then
32
+ echo "changed files:"
33
+ git -C "tmp/worktrees/$name" status --porcelain | sed 's/^/ /'
34
+ fi
35
+ if [ -f "tmp/runs/$name.events.jsonl" ]; then
36
+ echo "recent activity (kilo):"
37
+ python3 - "tmp/runs/$name.events.jsonl" <<'PY' | tail -n 8
38
+ import json, sys
39
+ for line in open(sys.argv[1], errors="replace"):
40
+ try:
41
+ part = json.loads(line).get("part", {})
42
+ except ValueError:
43
+ continue
44
+ if part.get("type") == "tool":
45
+ print(" tool " + str(part.get("tool")))
46
+ elif part.get("type") == "text" and part.get("text"):
47
+ print(" " + " ".join(part["text"].split())[:130])
48
+ PY
49
+ fi
50
+ echo "recent output:"
51
+ tail -n 8 "tmp/runs/$name.stdout.log" 2>/dev/null | sed -E 's/\x1b\[[0-9;]*m//g' | cut -c1-140 | sed 's/^/ /'
52
+ [ ! -f "tmp/runs/$name.verify.log" ] || { echo "verify log tail:"; tail -n 5 "tmp/runs/$name.verify.log" | sed 's/^/ /'; }
scripts/junie-task.sh ADDED
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/env bash
2
+ # Delegates one task to the cheap Junie CLI agent inside its own worktree, then verifies it.
3
+ #
4
+ # scripts/junie-task.sh <name> "<task text>" [--task-id <id>]
5
+ # scripts/junie-task.sh <name> --file tmp/task.md [--task-id <id>]
6
+ #
7
+ # --task-id reports progress to the task tracker (scripts/task-report.sh): the task is
8
+ # marked in progress at the start, and after the commit and verification it is closed
9
+ # automatically (done on success, blocked on failure); failures only warn.
10
+ #
11
+ # Env: RUNNER_STALL (default 420 s: kill an agent that changed no file by then), JUNIE_MODEL (default junie-lite), JUNIE_TIMEOUT (default 1200 s), JUNIE_PING
12
+ # (default 30 s), JUNIE_VERIFY_CMD (tests only: replaces the dotnet verification).
13
+ #
14
+ # Output (stdout): a short report. Artifacts: tmp/runs/<name>.json (Junie result),
15
+ # tmp/runs/<name>.verify.log (build + tests), tmp/runs/<name>.summary.json (machine
16
+ # readable run summary) and tmp/patches/<name>.patch.
17
+ # Prints a heartbeat line every JUNIE_PING seconds (default 30) and mirrors it to
18
+ # tmp/runs/<name>.status; every line ends with "model=<model> runner=junie" so it can
19
+ # be parsed. Check it any time with scripts/junie-status.sh <name>.
20
+ # Exit code: 0 verified, 1 verification failed, 3 Junie itself failed.
21
+ set -euo pipefail
22
+
23
+ root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
24
+ cd "$root"
25
+
26
+ runner="junie"
27
+ name="${1:?usage: junie-task.sh <name> \"<task>\" | --file <path> [--task-id <id>]}"
28
+ shift
29
+ if [ "${1:-}" = "--file" ]; then
30
+ task="$(cat "${2:?missing task file}")"
31
+ shift 2
32
+ else
33
+ task="${1:?missing task text}"
34
+ shift
35
+ fi
36
+ task_id=""
37
+ if [ "${1:-}" = "--task-id" ]; then
38
+ task_id="${2:?--task-id needs a value}"
39
+ fi
40
+
41
+ model="${JUNIE_MODEL:-junie-lite}"
42
+ limit="${JUNIE_TIMEOUT:-1200}"
43
+
44
+ if [ -d "tmp/worktrees/$name" ]; then
45
+ wt="$root/tmp/worktrees/$name"
46
+ else
47
+ wt="$(bash scripts/worktree.sh new "$name" ${task_id:+--task-id "$task_id"})"
48
+ fi
49
+ mkdir -p tmp/runs
50
+
51
+ run_file="$root/agents/$runner/RUN.md"
52
+ [ -f "$run_file" ] || { echo "error: missing $run_file" >&2; exit 1; }
53
+ prompt="$task
54
+
55
+ $(cat "$run_file")"
56
+
57
+ status_file="$root/tmp/runs/$name.status"
58
+ log="$root/tmp/runs/$name.stdout.log"
59
+ ping_every="${JUNIE_PING:-30}"
60
+ base="$(cat "tmp/meta/$name.base")"
61
+ started=$SECONDS
62
+ started_epoch="$(date +%s)"
63
+
64
+ report() { # report <state> [detail]: one line to stdout and the status file
65
+ local line
66
+ line="$(printf '[%02d:%02d] %s' $(((SECONDS - started) / 60)) $(((SECONDS - started) % 60)) "$1${2:+ - $2}")"
67
+ echo "$line model=$model runner=$runner"
68
+ echo "$line model=$model runner=$runner" >"$status_file"
69
+ }
70
+
71
+ bash scripts/task-report.sh start "$task_id"
72
+ report "starting" "worktree=$wt${task_id:+ task=$task_id}"
73
+ GIT_CONFIG_COUNT=1 \
74
+ GIT_CONFIG_KEY_0=core.hooksPath \
75
+ GIT_CONFIG_VALUE_0="$root/scripts/coder-git-hooks" \
76
+ timeout "$limit" junie --skip-update-check --model="$model" --output-format=json \
77
+ --json-output-file="$root/tmp/runs/$name.json" --project="$wt" "$prompt" >"$log" 2>&1 &
78
+ junie_pid=$!
79
+
80
+ # Heartbeat: while Junie works, report elapsed time, changed files and its latest output line.
81
+ stalled=0
82
+ while kill -0 "$junie_pid" 2>/dev/null; do
83
+ sleep "$ping_every" & wait $! || true
84
+ kill -0 "$junie_pid" 2>/dev/null || break
85
+ changed="$(git -C "$wt" status --porcelain | wc -l)"
86
+ # Stall detector: an agent that has changed nothing after RUNNER_STALL seconds is stuck
87
+ # (seen with Kilo: 8+ minutes, 0 files). Kill it so the planner can switch runner early.
88
+ if [ "$changed" -eq 0 ] && [ $((SECONDS - started)) -ge "${RUNNER_STALL:-420}" ]; then
89
+ report "stalled" "no file changed after ${RUNNER_STALL:-420}s; killing the agent"
90
+ kill "$junie_pid" 2>/dev/null || true
91
+ stalled=1
92
+ break
93
+ fi
94
+ last="$(tail -n 5 "$log" | sed -E 's/\x1b\[[0-9;]*m//g' | grep -v '^\s*$' | tail -n 1 | cut -c1-110 || true)"
95
+ report "junie running" "$changed file(s) changed; last: ${last:-<no output yet>}"
96
+ done
97
+ set +e
98
+ wait "$junie_pid"
99
+ junie_rc=$?
100
+ set -e
101
+ report "junie finished" "exit $junie_rc, verifying"
102
+
103
+ # Commit the agent's work on task/<name> (path guard first); agents never commit themselves.
104
+ commit_rc=0
105
+ if [ "$junie_rc" -eq 0 ]; then
106
+ task_head="$(git -C "$wt" rev-parse HEAD)"
107
+ if [ "$task_head" != "$base" ]; then
108
+ commit_rc=1
109
+ printf 'Junie changed task HEAD itself; only the parent runner may commit.\n' >"tmp/runs/$name.commit.log"
110
+ elif ! git -C "$wt" add -A; then
111
+ commit_rc=1
112
+ printf 'Could not stage the Junie task diff for guarded commit.\n' >"tmp/runs/$name.commit.log"
113
+ else
114
+ changed_lines="$(git -C "$wt" diff --cached --numstat "$base" \
115
+ | awk '$1 ~ /^[0-9]+$/ && $2 ~ /^[0-9]+$/ { total += $1 + $2 } END { print total + 0 }')"
116
+ if [ "$changed_lines" -gt 300 ]; then
117
+ commit_rc=1
118
+ printf 'Junie task diff has %s added/deleted lines (limit 300); split the task before committing.\n' \
119
+ "$changed_lines" >"tmp/runs/$name.commit.log"
120
+ else
121
+ title="$(printf '%s\n' "$task" | grep -m1 -v '^[[:space:]]*$' | cut -c1-72)"
122
+ bash scripts/worktree.sh commit "$name" --runner "$runner" --model "$model" \
123
+ ${task_id:+--task-id "$task_id"} --title "${title:-$name}" >"tmp/runs/$name.commit.log" 2>&1 || commit_rc=$?
124
+ fi
125
+ fi
126
+ fi
127
+ patch="$(bash scripts/worktree.sh patch "$name")"
128
+
129
+ verify_rc=0
130
+ {
131
+ if [ -n "${JUNIE_VERIFY_CMD:-}" ]; then
132
+ echo "== verify (JUNIE_VERIFY_CMD override) =="
133
+ (cd "$wt" && bash -c "$JUNIE_VERIFY_CMD") || verify_rc=1
134
+ elif ! bash "$root/scripts/needs-dotnet-checks.sh" "$wt" "$base" HEAD; then
135
+ echo "== conditional verification (no .NET files changed): script tests =="
136
+ (cd "$wt" && bash scripts/test_worktree.sh && bash scripts/test_runners.sh \
137
+ && python3 -m unittest discover -s scripts -p "test_*.py") || verify_rc=1
138
+ else
139
+ echo "== format =="; (cd "$wt" && dotnet format VibeEmulator.slnx --verify-no-changes) || verify_rc=1
140
+ echo "== build =="; (cd "$wt" && dotnet build VibeEmulator.slnx --no-incremental -warnaserror) || verify_rc=1
141
+ echo "== test =="; (cd "$wt" && dotnet test VibeEmulator.slnx --no-build) || verify_rc=1
142
+ fi
143
+ } >"tmp/runs/$name.verify.log" 2>&1
144
+
145
+ if [ "$commit_rc" -ne 0 ]; then
146
+ verify_rc=1
147
+ echo "== commit failed (exit $commit_rc): see tmp/runs/$name.commit.log ==" >>"tmp/runs/$name.verify.log"
148
+ fi
149
+ finished="$(date +%s)"
150
+ files_changed="$(git -C "$wt" diff --cached --name-only "$base" | wc -l)"
151
+ if [ "$junie_rc" -ne 0 ]; then
152
+ verdict="agent_failed"
153
+ elif [ "$verify_rc" -ne 0 ]; then
154
+ verdict="verify_failed"
155
+ else
156
+ verdict="verified"
157
+ fi
158
+ if ! python3 scripts/run_summary.py "$root/tmp/runs/$name.summary.json" name="$name" runner="$runner" model="$model" \
159
+ task_id="$task_id" started="$started_epoch" finished="$finished" \
160
+ agent_exit="$junie_rc" verify_exit="$verify_rc" verdict="$verdict" \
161
+ patch="$patch" files_changed="$files_changed"; then
162
+ echo "warning: could not write run summary (tmp/runs/$name.summary.json)" >&2
163
+ fi
164
+
165
+ report "done" "junie exit $junie_rc, verification $([ "$verify_rc" -eq 0 ] && echo PASSED || echo FAILED)"
166
+ echo "runner: $runner"
167
+ echo "model: $model"
168
+ echo "junie exit: $junie_rc"
169
+ echo "verification: $([ "$verify_rc" -eq 0 ] && echo PASSED || echo FAILED) (tmp/runs/$name.verify.log)"
170
+ echo "patch: $patch"
171
+ git -C "$wt" diff --cached --stat "$base" | tail -n 15
172
+ if [ "$junie_rc" -ne 0 ]; then
173
+ bash scripts/task-report.sh note "$task_id" "junie '$name' failed (exit 3) model=$model; see tmp/runs/$name.verify.log"
174
+ bash scripts/task-report.sh close "$task_id" red "agent exit $junie_rc; see tmp/runs/$name.verify.log"
175
+ exit 3
176
+ elif [ "$verify_rc" -ne 0 ]; then
177
+ bash scripts/task-report.sh note "$task_id" "junie '$name' failed (exit 1) model=$model; see tmp/runs/$name.verify.log"
178
+ bash scripts/task-report.sh close "$task_id" red "verification failed; see tmp/runs/$name.verify.log"
179
+ exit 1
180
+ else
181
+ bash scripts/task-report.sh note "$task_id" "junie '$name' verified model=$model; patch tmp/patches/$name.patch"
182
+ bash scripts/task-report.sh close "$task_id" green
183
+ fi