@mmerterden/multi-agent-pipeline 20.23.2 → 20.25.0
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.
- package/CHANGELOG.md +46 -0
- package/README.md +8 -6
- package/README.tr.md +7 -5
- package/docs/architecture.md +3 -3
- package/docs/ecosystem.md +5 -5
- package/docs/facts.json +7 -6
- package/docs/features.md +9 -4
- package/install/catalog-history.json +1 -1
- package/install/templates/claude-hooks.json +14 -1
- package/install/templates/codex-hooks.json +18 -0
- package/manifest.json +126 -98
- package/package.json +1 -1
- package/pipeline/agents/task-clarifier.md +1 -1
- package/pipeline/commands/multi-agent/analysis-jira/SKILL.md +4 -2
- package/pipeline/commands/multi-agent/deps/SKILL.md +61 -0
- package/pipeline/commands/multi-agent/model/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/postmortem/SKILL.md +90 -0
- package/pipeline/commands/multi-agent/review/SKILL.md +5 -5
- package/pipeline/commands/multi-agent/route-on/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/setup/SKILL.md +7 -5
- package/pipeline/commands/multi-agent/sync/SKILL.md +4 -4
- package/pipeline/commands/multi-agent/testflight-validation/SKILL.md +2 -3
- package/pipeline/contract/fixtures/runs-failed.json +1 -1
- package/pipeline/contract/fixtures/runs-old-schema.json +1 -1
- package/pipeline/contract/fixtures/runs-pr-opened-redacted.json +1 -1
- package/pipeline/contract/fixtures/runs-pr-opened.json +1 -1
- package/pipeline/contract/fixtures/runs-running.json +1 -1
- package/pipeline/lib/analysis-jira-write.sh +70 -0
- package/pipeline/lib/credential-inventory.sh +5 -5
- package/pipeline/lib/decisions.mjs +130 -0
- package/pipeline/lib/fetch-crashlytics.sh +6 -8
- package/pipeline/lib/model-rung.sh +1 -1
- package/pipeline/lib/plan-todos.sh +177 -37
- package/pipeline/lib/post-pr-review.sh +4 -6
- package/pipeline/lib/route-state.sh +1 -1
- package/pipeline/lib/shadow-git.sh +4 -5
- package/pipeline/lib/task-graph.mjs +446 -0
- package/pipeline/lib/tdd-evidence.mjs +579 -0
- package/pipeline/multi-agent-refs/analysis-template-corporate.md +2 -0
- package/pipeline/multi-agent-refs/analysis-template.md +4 -2
- package/pipeline/multi-agent-refs/cross-cli-contract.md +10 -11
- package/pipeline/multi-agent-refs/features/analysis-jira.md +42 -0
- package/pipeline/multi-agent-refs/features/autopilot-decisions.md +34 -0
- package/pipeline/multi-agent-refs/features/cost-analysis.md +4 -0
- package/pipeline/multi-agent-refs/features/dep-bump.md +176 -0
- package/pipeline/multi-agent-refs/features/model-fallback.md +21 -14
- package/pipeline/multi-agent-refs/features/mutation-probe.md +58 -0
- package/pipeline/multi-agent-refs/features/plan-checks.md +67 -0
- package/pipeline/multi-agent-refs/features/plan-todos.md +40 -1
- package/pipeline/multi-agent-refs/features/rework-escalation.md +49 -0
- package/pipeline/multi-agent-refs/features/scope-check.md +1 -0
- package/pipeline/multi-agent-refs/help/en.md +2 -0
- package/pipeline/multi-agent-refs/help/tr.md +2 -0
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +1 -1
- package/pipeline/multi-agent-refs/phases/phase-1-plan.md +2 -3
- package/pipeline/multi-agent-refs/phases/phase-2-dev.md +7 -7
- package/pipeline/multi-agent-refs/phases/phase-3-review.md +18 -16
- package/pipeline/multi-agent-refs/phases/phase-5-report.md +3 -3
- package/pipeline/multi-agent-refs/picker-contract.md +3 -2
- package/pipeline/multi-agent-refs/response-shape.md +65 -0
- package/pipeline/multi-agent-refs/tracker-contract.md +6 -6
- package/pipeline/rules/response-shape.md +18 -0
- package/pipeline/schemas/agent-state.schema.json +36 -1
- package/pipeline/schemas/phases.json +4 -0
- package/pipeline/schemas/plan-todos.schema.json +104 -3
- package/pipeline/schemas/planning-output.schema.json +46 -0
- package/pipeline/schemas/prefs.schema.json +122 -3
- package/pipeline/scripts/_code-graph.mjs +2 -2
- package/pipeline/scripts/_jsonl.mjs +151 -0
- package/pipeline/scripts/_plan-overlap.mjs +231 -0
- package/pipeline/scripts/_redact.mjs +124 -10
- package/pipeline/scripts/_transcript-hosts.mjs +406 -0
- package/pipeline/scripts/analysis-story-tree.mjs +153 -8
- package/pipeline/scripts/autopilot-publish.mjs +12 -1
- package/pipeline/scripts/build-stack-plugins.mjs +3 -3
- package/pipeline/scripts/capture-flush.sh +7 -7
- package/pipeline/scripts/code-graph-rules/android.json +1 -1
- package/pipeline/scripts/cost-analyze.mjs +32 -43
- package/pipeline/scripts/cost-table.json +35 -19
- package/pipeline/scripts/decisions-log.mjs +118 -0
- package/pipeline/scripts/dep-bump.mjs +1556 -0
- package/pipeline/scripts/edit-syntax-gate.mjs +362 -0
- package/pipeline/scripts/edit-syntax-gate.sh +32 -0
- package/pipeline/scripts/find-polluter.mjs +281 -0
- package/pipeline/scripts/gate-ledger.mjs +46 -13
- package/pipeline/scripts/gc-abandoned.sh +2 -2
- package/pipeline/scripts/gen-mode-dispatch.mjs +3 -3
- package/pipeline/scripts/git-path.mjs +1 -2
- package/pipeline/scripts/host-hooks.mjs +265 -0
- package/pipeline/scripts/jira-attach.sh +1 -1
- package/pipeline/scripts/keychain.py +3 -3
- package/pipeline/scripts/mutation-probe.mjs +484 -0
- package/pipeline/scripts/output-quality-check.sh +1 -1
- package/pipeline/scripts/phase-tracker.sh +5 -6
- package/pipeline/scripts/phase0-exit-gate.mjs +3 -3
- package/pipeline/scripts/plan-complexity.mjs +259 -0
- package/pipeline/scripts/pre-push-check.sh +3 -6
- package/pipeline/scripts/review-decision-gate.mjs +33 -1
- package/pipeline/scripts/rework-escalation.mjs +264 -0
- package/pipeline/scripts/test-strength.mjs +5 -3
- package/pipeline/scripts/transcript-index.mjs +1131 -0
- package/pipeline/scripts/uninstall.mjs +90 -4
- package/pipeline/scripts/update-issue-progress.sh +4 -6
- package/pipeline/scripts/usage-report.mjs +2 -2
- package/pipeline/scripts/validate-planning.mjs +48 -6
- package/pipeline/skills/.skill-manifest.json +20 -12
- package/pipeline/skills/.skills-index.json +23 -1
- package/pipeline/skills/shared/README.md +5 -3
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +6 -6
- package/pipeline/skills/shared/core/multi-agent-analysis-jira/SKILL.md +4 -2
- package/pipeline/skills/shared/core/multi-agent-deps/SKILL.md +60 -0
- package/pipeline/skills/shared/core/multi-agent-model/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-postmortem/SKILL.md +69 -0
- package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +5 -5
- package/pipeline/skills/shared/core/multi-agent-route-on/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +7 -5
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +4 -4
- package/pipeline/skills/shared/core/multi-agent-testflight-validation/SKILL.md +2 -3
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +15 -0
- package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +13 -0
- package/pipeline/skills/shared/external/debugging-strategies/SKILL.md +8 -0
- package/pipeline/skills/shared/external/ios-coding-standard/references/rules.yml +2 -2
- package/pipeline/skills/skills-index.md +3 -1
|
@@ -69,7 +69,7 @@ host_note() {
|
|
|
69
69
|
echo "Claude Code: the fable rung is Fable 5. This switch is live here."
|
|
70
70
|
;;
|
|
71
71
|
copilot)
|
|
72
|
-
echo "Copilot CLI: Fable
|
|
72
|
+
echo "Copilot CLI: Fable is not offered; its personas never sat on this rung."
|
|
73
73
|
echo "This setting is recorded but changes nothing on this host."
|
|
74
74
|
;;
|
|
75
75
|
codex)
|
|
@@ -13,11 +13,25 @@
|
|
|
13
13
|
# Subcommands:
|
|
14
14
|
# init <task-id> [title] Initialize with empty todos[] (Phase 2 usually pipes the JSON instead)
|
|
15
15
|
# set <task-id> <plan-json> Replace the whole plan from a JSON blob on stdin or arg
|
|
16
|
+
# (exit 2 when a dep names no todo id exactly)
|
|
16
17
|
# start <task-id> <todo-id> Mark in_progress, set startedAt; refuses if deps unsatisfied
|
|
17
|
-
#
|
|
18
|
+
# or the todo awaits re-plan confirmation
|
|
19
|
+
# complete <task-id> <todo-id> [notes] [--no-tests <reason>]
|
|
20
|
+
# Mark completed, set completedAt; refuses what start refuses
|
|
21
|
+
# (unsatisfied deps on a pending todo, re-plan unconfirmed); checks TDD evidence per
|
|
22
|
+
# prefs.global.tddEvidence.mode (off | warn | enforce, default warn)
|
|
23
|
+
# tdd-red <task-id> <todo-id> <results> [--tests a,b] [--already-passing <reason>]
|
|
24
|
+
# Record the RED run (a named test fails) from JUnit XML / xcresult / log
|
|
25
|
+
# (under enforce, --tests needs results that name failing tests)
|
|
26
|
+
# tdd-green <task-id> <todo-id> <results> Record the GREEN run (no failures, RED's tests among them)
|
|
18
27
|
# fail <task-id> <todo-id> <reason> Mark failed
|
|
19
28
|
# skip <task-id> <todo-id> <reason> Mark skipped (counts as satisfied for dependents)
|
|
20
|
-
# next <task-id> Print next
|
|
29
|
+
# next <task-id> Print the next ready todo (deps-respecting, ranked by what it unblocks); empty if none, blocked todos named on stderr
|
|
30
|
+
# drift <task-id> <todo-id> [--files <path|->] [--not-done N] [--reason <text>]
|
|
31
|
+
# The todo ended off-plan (files outside its files[],
|
|
32
|
+
# planned items not done, or a stated reason): flag its
|
|
33
|
+
# pending transitive dependents needsReplan; prints JSON
|
|
34
|
+
# confirm-replan <task-id> <todo-id> [note] Confirm a flagged todo's plan still holds, so it can start
|
|
21
35
|
# list <task-id> Render Markdown checklist
|
|
22
36
|
# status <task-id> Print one-line summary: "2/5 done, 1 in progress"
|
|
23
37
|
# show <task-id> Print the full plan JSON
|
|
@@ -59,7 +73,7 @@ CMD="${1:-}"
|
|
|
59
73
|
shift || true
|
|
60
74
|
|
|
61
75
|
usage() {
|
|
62
|
-
sed -n '2,
|
|
76
|
+
sed -n '2,37p' "$0" >&2
|
|
63
77
|
exit 64
|
|
64
78
|
}
|
|
65
79
|
|
|
@@ -71,10 +85,9 @@ resolve_state() {
|
|
|
71
85
|
local task_id="$1"
|
|
72
86
|
local found=""
|
|
73
87
|
# multi-repo-pipeline.sh writes agent-state.json under a per-project
|
|
74
|
-
# directory ($HOME/.claude/logs/multi-agent/{project}/{taskId}/)
|
|
75
|
-
#
|
|
76
|
-
#
|
|
77
|
-
# This globbed ONLY the nested layout; flat-layout runs resolved to nothing.
|
|
88
|
+
# directory ($HOME/.claude/logs/multi-agent/{project}/{taskId}/), other
|
|
89
|
+
# writers write it flat; no writer uses ~/.claude/projects/*/state/.
|
|
90
|
+
# ma_resolve_run_file checks both layouts.
|
|
78
91
|
found="$(ma_resolve_run_file "$task_id" agent-state.json 2>/dev/null || true)"
|
|
79
92
|
if [ -z "$found" ]; then
|
|
80
93
|
echo "plan-todos: agent-state.json not found for task=$task_id" >&2
|
|
@@ -115,12 +128,8 @@ do_set() {
|
|
|
115
128
|
if [ -z "$plan_blob" ] || [ "$plan_blob" = "-" ]; then
|
|
116
129
|
plan_blob=$(cat)
|
|
117
130
|
fi
|
|
118
|
-
# A planning-output document is accepted directly and converted here
|
|
119
|
-
#
|
|
120
|
-
# The conversion used to live as a jq blob inside phase-1-plan.md, which
|
|
121
|
-
# made the mapping from `tasks[]` to `todos[]` a thing two files defined - and
|
|
122
|
-
# the phase doc was the copy nothing tested. Accepting both shapes costs four
|
|
123
|
-
# lines and removes the second definition.
|
|
131
|
+
# A planning-output document is accepted directly and converted here, so the
|
|
132
|
+
# mapping from `tasks[]` to `todos[]` has one tested definition.
|
|
124
133
|
if jq -e '.tasks and (.todos | not)' <<<"$plan_blob" >/dev/null 2>&1; then
|
|
125
134
|
plan_blob=$(jq '{
|
|
126
135
|
title: (.summary // .title // "plan"),
|
|
@@ -129,7 +138,9 @@ do_set() {
|
|
|
129
138
|
task: (.title // .subject // ""),
|
|
130
139
|
status: "pending",
|
|
131
140
|
deps: (.dependsOn // .blockedBy // [])
|
|
132
|
-
}
|
|
141
|
+
} + (if (.files | type) == "array" then {files: .files} else {} end)
|
|
142
|
+
+ (if (.creates | type) == "array" then {creates: .creates} else {} end)
|
|
143
|
+
+ (if .type then {type: .type} else {} end) ]
|
|
133
144
|
}' <<<"$plan_blob")
|
|
134
145
|
fi
|
|
135
146
|
|
|
@@ -144,6 +155,23 @@ do_set() {
|
|
|
144
155
|
echo "plan-todos: every todo must be an object with string .id and .task fields" >&2
|
|
145
156
|
exit 2
|
|
146
157
|
fi
|
|
158
|
+
# Every dependency must name a todo exactly. Ids are compared as written, so
|
|
159
|
+
# `t1` against a todo `T1` would leave the dependent blocked for good; the
|
|
160
|
+
# error names the id that differs only by case.
|
|
161
|
+
local bad_deps
|
|
162
|
+
bad_deps=$(jq -r '
|
|
163
|
+
[.todos[].id] as $ids
|
|
164
|
+
| [.todos[] | .id as $owner | (.deps // [])[]
|
|
165
|
+
| select((type != "string") or (. as $d | $ids | index($d) | not))
|
|
166
|
+
| . as $d
|
|
167
|
+
| ($ids | map(select(($d | type) == "string" and ascii_downcase == ($d | ascii_downcase)))) as $near
|
|
168
|
+
| "\($owner) depends on \($d | tojson), which is no todo id"
|
|
169
|
+
+ (if ($near | length) > 0 then " (did you mean \($near | map(tojson) | join(", "))?)" else "" end)]
|
|
170
|
+
| .[]' <<<"$plan_blob" 2>/dev/null)
|
|
171
|
+
if [ -n "$bad_deps" ]; then
|
|
172
|
+
printf 'plan-todos: %s\n' "$bad_deps" >&2
|
|
173
|
+
exit 2
|
|
174
|
+
fi
|
|
147
175
|
local state_path; state_path=$(resolve_state "$task_id")
|
|
148
176
|
local now; now=$(iso_now)
|
|
149
177
|
local new
|
|
@@ -169,6 +197,22 @@ deps_satisfied() {
|
|
|
169
197
|
' <<<"$state" >/dev/null 2>&1
|
|
170
198
|
}
|
|
171
199
|
|
|
200
|
+
refuse_unconfirmed_replan() {
|
|
201
|
+
# $1 state JSON, $2 task_id, $3 todo_id. A todo planned on top of one that
|
|
202
|
+
# drifted waits for a person to confirm its plan: exits 2 until confirm-replan.
|
|
203
|
+
local replan
|
|
204
|
+
replan=$(jq -r --arg id "$3" '
|
|
205
|
+
(.plan.todos // []) | map(select(.id == $id))[0].needsReplan
|
|
206
|
+
| select(. != null and (.confirmedAt // "") == "")
|
|
207
|
+
| "\(.because)\t\(.reason // "")"
|
|
208
|
+
' <<<"$1")
|
|
209
|
+
if [ -n "$replan" ]; then
|
|
210
|
+
printf "plan-todos: '%s' awaits re-plan confirmation: %s drifted (%s). Check its plan, then: plan-todos.sh confirm-replan %s %s\n" \
|
|
211
|
+
"$3" "${replan%%$'\t'*}" "${replan#*$'\t'}" "$2" "$3" >&2
|
|
212
|
+
exit 2
|
|
213
|
+
fi
|
|
214
|
+
}
|
|
215
|
+
|
|
172
216
|
do_start() {
|
|
173
217
|
local task_id="${1:-}"; local todo_id="${2:-}"
|
|
174
218
|
[ -z "$task_id" ] || [ -z "$todo_id" ] && { echo "usage: start <task-id> <todo-id>" >&2; exit 64; }
|
|
@@ -184,6 +228,7 @@ do_start() {
|
|
|
184
228
|
echo "plan-todos: deps for '$todo_id' not satisfied (still pending/in_progress/failed)" >&2
|
|
185
229
|
exit 2
|
|
186
230
|
fi
|
|
231
|
+
refuse_unconfirmed_replan "$state" "$task_id" "$todo_id"
|
|
187
232
|
local now; now=$(iso_now)
|
|
188
233
|
local new
|
|
189
234
|
new=$(jq --arg id "$todo_id" --arg t "$now" '
|
|
@@ -202,18 +247,85 @@ require_todo() {
|
|
|
202
247
|
fi
|
|
203
248
|
}
|
|
204
249
|
|
|
250
|
+
TDD_EVIDENCE="$_MA_RP_HERE/tdd-evidence.mjs"
|
|
251
|
+
|
|
252
|
+
tdd_mode() {
|
|
253
|
+
local prefs="${MULTI_AGENT_PREFS:-$HOME/.claude/multi-agent-preferences.json}" mode
|
|
254
|
+
mode=$(jq -r '.global.tddEvidence.mode // "warn"' "$prefs" 2>/dev/null) || mode=warn
|
|
255
|
+
case "$mode" in off|warn|enforce) printf '%s' "$mode" ;; *) printf 'warn' ;; esac
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
write_tdd() {
|
|
259
|
+
# $1 state path, $2 todo_id, $3 tdd record JSON
|
|
260
|
+
local new
|
|
261
|
+
new=$(jq --arg id "$2" --argjson r "$3" '
|
|
262
|
+
.plan.todos |= map(if .id == $id then .tdd = $r else . end)
|
|
263
|
+
' "$1")
|
|
264
|
+
write_state "$1" "$new"
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
do_tdd_run() {
|
|
268
|
+
# $1 red|green, then <task-id> <todo-id> <results> [flags passed to tdd-evidence.mjs]
|
|
269
|
+
local kind="$1"; shift
|
|
270
|
+
local task_id="${1:-}"; local todo_id="${2:-}"; local results="${3:-}"
|
|
271
|
+
[ -z "$task_id" ] || [ -z "$todo_id" ] || [ -z "$results" ] && { echo "usage: tdd-$kind <task-id> <todo-id> <results-file> [flags]" >&2; exit 64; }
|
|
272
|
+
shift 3
|
|
273
|
+
# Only the caller-owned flags pass through; --state, --todo, --results and
|
|
274
|
+
# --mode come from the pipeline, so a trailing copy cannot override them.
|
|
275
|
+
local extra=()
|
|
276
|
+
while [ $# -gt 0 ]; do
|
|
277
|
+
case "$1" in
|
|
278
|
+
--tests|--already-passing)
|
|
279
|
+
[ $# -ge 2 ] || { echo "tdd-$kind: $1 needs a value" >&2; exit 64; }
|
|
280
|
+
extra+=("$1" "$2"); shift 2 ;;
|
|
281
|
+
*) echo "tdd-$kind: unsupported flag '$1' (only --tests and --already-passing)" >&2; exit 64 ;;
|
|
282
|
+
esac
|
|
283
|
+
done
|
|
284
|
+
local state_path; state_path=$(resolve_state "$task_id")
|
|
285
|
+
require_todo "$state_path" "$todo_id"
|
|
286
|
+
local record mode_flag=()
|
|
287
|
+
[ "$kind" = "red" ] && mode_flag=(--mode "$(tdd_mode)")
|
|
288
|
+
record=$(node "$TDD_EVIDENCE" "$kind" --state "$state_path" --todo "$todo_id" --results "$results" ${mode_flag[@]+"${mode_flag[@]}"} ${extra[@]+"${extra[@]}"}) || exit 2
|
|
289
|
+
write_tdd "$state_path" "$todo_id" "$record"
|
|
290
|
+
}
|
|
291
|
+
|
|
205
292
|
do_complete() {
|
|
206
|
-
local task_id="${1:-}"; local todo_id="${2:-}"; local notes="
|
|
207
|
-
[ -z "$task_id" ] || [ -z "$todo_id" ] && { echo "usage: complete <task-id> <todo-id> [notes]" >&2; exit 64; }
|
|
293
|
+
local task_id="${1:-}"; local todo_id="${2:-}"; local notes="" no_tests=""
|
|
294
|
+
[ -z "$task_id" ] || [ -z "$todo_id" ] && { echo "usage: complete <task-id> <todo-id> [notes] [--no-tests <reason>]" >&2; exit 64; }
|
|
295
|
+
shift 2
|
|
296
|
+
while [ $# -gt 0 ]; do
|
|
297
|
+
case "$1" in
|
|
298
|
+
--no-tests) no_tests="${2:-}"; [ -z "$no_tests" ] && { echo "complete: --no-tests needs a reason" >&2; exit 64; }; shift 2 ;;
|
|
299
|
+
*) notes="$1"; shift ;;
|
|
300
|
+
esac
|
|
301
|
+
done
|
|
208
302
|
local state_path; state_path=$(resolve_state "$task_id")
|
|
209
303
|
require_todo "$state_path" "$todo_id"
|
|
304
|
+
local state; state=$(cat "$state_path")
|
|
305
|
+
refuse_unconfirmed_replan "$state" "$task_id" "$todo_id"
|
|
306
|
+
# A todo that never started skipped the deps check start makes.
|
|
307
|
+
if [ "$(jq -r --arg id "$todo_id" '(.plan.todos // []) | map(select(.id == $id))[0].status // "pending"' <<<"$state")" = "pending" ] \
|
|
308
|
+
&& ! deps_satisfied "$state" "$todo_id"; then
|
|
309
|
+
echo "plan-todos: deps for '$todo_id' not satisfied (still pending/in_progress/failed); it cannot complete before they do" >&2
|
|
310
|
+
exit 2
|
|
311
|
+
fi
|
|
312
|
+
local verdict action
|
|
313
|
+
verdict=$(node "$TDD_EVIDENCE" check --state "$state_path" --todo "$todo_id" --mode "$(tdd_mode)" \
|
|
314
|
+
${no_tests:+--no-tests "$no_tests"}) || exit 2
|
|
315
|
+
action=$(jq -r '.action' <<<"$verdict")
|
|
316
|
+
if [ "$action" = "refuse" ]; then
|
|
317
|
+
echo "plan-todos: $(jq -r '.message' <<<"$verdict") - tddEvidence.mode is enforce" >&2
|
|
318
|
+
exit 2
|
|
319
|
+
fi
|
|
320
|
+
[ "$action" = "warn" ] && echo "plan-todos: warning: $(jq -r '.message' <<<"$verdict"); recorded as unverified" >&2
|
|
210
321
|
local now; now=$(iso_now)
|
|
211
322
|
local new
|
|
212
|
-
new=$(jq --arg id "$todo_id" --arg t "$now" --arg n "$notes" '
|
|
323
|
+
new=$(jq --arg id "$todo_id" --arg t "$now" --arg n "$notes" --argjson v "$verdict" '
|
|
213
324
|
.plan.todos |= map(
|
|
214
325
|
if .id == $id then
|
|
215
326
|
.status = "completed" | .completedAt = $t
|
|
216
327
|
| if ($n != "") then .notes = $n else . end
|
|
328
|
+
| if ($v.tdd != null) then .tdd = $v.tdd else . end
|
|
217
329
|
else . end
|
|
218
330
|
)
|
|
219
331
|
' "$state_path")
|
|
@@ -254,25 +366,47 @@ do_next() {
|
|
|
254
366
|
local task_id="${1:-}"
|
|
255
367
|
[ -z "$task_id" ] && { echo "usage: next <task-id>" >&2; exit 64; }
|
|
256
368
|
local state_path; state_path=$(resolve_state "$task_id")
|
|
257
|
-
#
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
' "$
|
|
369
|
+
# Ready set ranked by the work each todo unblocks; blocked todos are named on stderr.
|
|
370
|
+
node "$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/task-graph.mjs" next "$state_path" --task-id "$task_id"
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
do_drift() {
|
|
374
|
+
local task_id="${1:-}"; local todo_id="${2:-}"
|
|
375
|
+
[ -z "$task_id" ] || [ -z "$todo_id" ] && { echo "usage: drift <task-id> <todo-id> [--files <path|->] [--not-done N] [--reason <text>]" >&2; exit 64; }
|
|
376
|
+
shift 2
|
|
377
|
+
local state_path; state_path=$(resolve_state "$task_id")
|
|
378
|
+
require_todo "$state_path" "$todo_id"
|
|
379
|
+
local result rc=0
|
|
380
|
+
result=$(node "$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/task-graph.mjs" drift "$state_path" "$todo_id" "$@") || rc=$?
|
|
381
|
+
[ "$rc" -eq 0 ] || exit "$rc"
|
|
382
|
+
if [ "$(jq -r '.drifted' <<<"$result")" = "true" ]; then
|
|
383
|
+
local new
|
|
384
|
+
new=$(jq --argjson r "$result" '.plan.todos = $r.todos' "$state_path")
|
|
385
|
+
write_state "$state_path" "$new"
|
|
386
|
+
fi
|
|
387
|
+
jq -c 'del(.todos)' <<<"$result"
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
do_confirm_replan() {
|
|
391
|
+
local task_id="${1:-}"; local todo_id="${2:-}"; local note="${3:-}"
|
|
392
|
+
[ -z "$task_id" ] || [ -z "$todo_id" ] && { echo "usage: confirm-replan <task-id> <todo-id> [note]" >&2; exit 64; }
|
|
393
|
+
local state_path; state_path=$(resolve_state "$task_id")
|
|
394
|
+
require_todo "$state_path" "$todo_id"
|
|
395
|
+
if ! jq -e --arg id "$todo_id" '(.plan.todos // []) | map(select(.id == $id))[0].needsReplan | type == "object"' "$state_path" >/dev/null 2>&1; then
|
|
396
|
+
echo "plan-todos: '$todo_id' has no re-plan flag to confirm" >&2
|
|
397
|
+
exit 2
|
|
398
|
+
fi
|
|
399
|
+
local now; now=$(iso_now)
|
|
400
|
+
local new
|
|
401
|
+
new=$(jq --arg id "$todo_id" --arg t "$now" --arg n "$note" '
|
|
402
|
+
.plan.todos |= map(
|
|
403
|
+
if .id == $id then
|
|
404
|
+
.needsReplan.confirmedAt = $t
|
|
405
|
+
| if ($n != "") then .needsReplan.note = $n else . end
|
|
406
|
+
else . end
|
|
407
|
+
)
|
|
408
|
+
' "$state_path")
|
|
409
|
+
write_state "$state_path" "$new"
|
|
276
410
|
}
|
|
277
411
|
|
|
278
412
|
do_list() {
|
|
@@ -294,7 +428,9 @@ do_list() {
|
|
|
294
428
|
| ($marks[.status] // "- [ ]") + " **" + (.id // "") + "** " + (.task // "")
|
|
295
429
|
+ (if (.notes // "") != "" then " _(" + .notes + ")_" else "" end)
|
|
296
430
|
+ (if (.failureReason // "") != "" then " _**failed:** " + .failureReason + "_" else "" end)
|
|
297
|
-
+ (if (.skipReason // "") != "" then " _**skipped:** " + .skipReason + "_" else "" end)
|
|
431
|
+
+ (if (.skipReason // "") != "" then " _**skipped:** " + .skipReason + "_" else "" end)
|
|
432
|
+
+ (if (.needsReplan // null) != null and (.needsReplan.confirmedAt // "") == ""
|
|
433
|
+
then " _**needs re-plan:** " + (.needsReplan.because // "?") + " drifted_" else "" end))
|
|
298
434
|
)
|
|
299
435
|
| .[]
|
|
300
436
|
' "$state_path"
|
|
@@ -332,9 +468,13 @@ case "$CMD" in
|
|
|
332
468
|
set) do_set "$@" ;;
|
|
333
469
|
start) do_start "$@" ;;
|
|
334
470
|
complete) do_complete "$@" ;;
|
|
471
|
+
tdd-red) do_tdd_run red "$@" ;;
|
|
472
|
+
tdd-green) do_tdd_run green "$@" ;;
|
|
335
473
|
fail) do_fail "$@" ;;
|
|
336
474
|
skip) do_skip "$@" ;;
|
|
337
475
|
next) do_next "$@" ;;
|
|
476
|
+
drift) do_drift "$@" ;;
|
|
477
|
+
confirm-replan) do_confirm_replan "$@" ;;
|
|
338
478
|
list) do_list "$@" ;;
|
|
339
479
|
status) do_status "$@" ;;
|
|
340
480
|
show) do_show "$@" ;;
|
|
@@ -107,12 +107,10 @@ if [ -z "$TASK_ID" ]; then
|
|
|
107
107
|
fi
|
|
108
108
|
|
|
109
109
|
# Resolve agent-state.json. multi-repo-pipeline.sh writes it under a
|
|
110
|
-
# per-project directory ($HOME/.claude/logs/multi-agent/{project}/{taskId}/)
|
|
111
|
-
# -
|
|
112
|
-
#
|
|
113
|
-
#
|
|
114
|
-
# write flat, so on a real machine the majority of runs resolved to nothing and
|
|
115
|
-
# this script reported "no agent-state.json" for a run whose state existed.
|
|
110
|
+
# per-project directory ($HOME/.claude/logs/multi-agent/{project}/{taskId}/),
|
|
111
|
+
# while phase-tracker.sh and most Phase 0 paths write it flat; no writer uses
|
|
112
|
+
# ~/.claude/projects/*/state/. ma_resolve_run_file checks both layouts, so a
|
|
113
|
+
# run whose state exists is always found.
|
|
116
114
|
AGENT_STATE="$(ma_resolve_run_file "$TASK_ID" agent-state.json 2>/dev/null || true)"
|
|
117
115
|
|
|
118
116
|
if [ -z "$AGENT_STATE" ]; then
|
|
@@ -77,7 +77,7 @@ free_savings() {
|
|
|
77
77
|
since=$(( $(date +%s) - 7 * 86400 ))
|
|
78
78
|
jq -rs --argjson since "$since" --slurpfile ct "$COST_TABLE" '
|
|
79
79
|
map(select((.ts | sub("\\.[0-9]+Z$"; "Z") | fromdateiso8601? // 0) >= $since))
|
|
80
|
-
| ($ct[0].prices.haiku // {inPerMtok:
|
|
80
|
+
| ($ct[0].prices.haiku // {inPerMtok: 0.5, outPerMtok: 2.5}) as $h
|
|
81
81
|
| {
|
|
82
82
|
n: length,
|
|
83
83
|
ok: (map(select(.ok and (.cached | not))) | length),
|
|
@@ -227,11 +227,10 @@ do_prune() {
|
|
|
227
227
|
printf 'shadow-git: pruned shadow repo for %s\n' "$task_id" >&2
|
|
228
228
|
else
|
|
229
229
|
[ ! -d "$sd/.git" ] && { err "no shadow for $task_id"; exit 1; }
|
|
230
|
-
# Retention never touches a real work-tree:
|
|
231
|
-
#
|
|
232
|
-
#
|
|
233
|
-
#
|
|
234
|
-
# for a "keep the last N days" policy. Every git call below is
|
|
230
|
+
# Retention never touches a real work-tree: a `reset --hard` with
|
|
231
|
+
# --work-tree=$(pwd) would silently rewrite whatever directory prune is
|
|
232
|
+
# invoked from. It keeps every commit inside the window and drops the
|
|
233
|
+
# older ones, as a "keep the last N days" policy requires. Every git call below is
|
|
235
234
|
# git-dir-only plumbing (log/rev-parse/commit-tree/update-ref/gc - no
|
|
236
235
|
# checkout, no reset, no clean); the one --work-tree `sg` still wants
|
|
237
236
|
# points at $sd itself, which prune already owns.
|