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
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
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
|
scripts/install-hooks.sh
ADDED
scripts/junie-status.sh
ADDED
|
@@ -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
|