@mmerterden/multi-agent-pipeline 16.20.0 → 16.23.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 +231 -102
- package/README.md +6 -8
- package/README.tr.md +6 -8
- package/docs/architecture.md +3 -3
- package/docs/ecosystem.md +5 -5
- package/docs/features.md +1 -0
- package/install/templates/claude-hooks.json +12 -1
- package/install/templates/copilot-instructions.md +17 -2
- package/package.json +1 -1
- package/pipeline/agents/bulk-reader.md +57 -0
- package/pipeline/commands/multi-agent/SKILL.md +0 -5
- package/pipeline/commands/multi-agent/help/SKILL.md +0 -10
- package/pipeline/commands/multi-agent/ios-coding-standard/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/resume-local/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/setup/SKILL.md +7 -5
- package/pipeline/commands/multi-agent/sync/SKILL.md +13 -13
- package/pipeline/multi-agent-refs/cross-cli-contract.md +10 -12
- package/pipeline/multi-agent-refs/phases/modes.md +1 -1
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +7 -2
- package/pipeline/multi-agent-refs/phases/phase-3-dev.md +1 -1
- package/pipeline/multi-agent-refs/phases/phase-7-report.md +8 -1
- package/pipeline/multi-agent-refs/picker-contract.md +1 -1
- package/pipeline/multi-agent-refs/tracker-contract.md +46 -0
- package/pipeline/schemas/agent-state.schema.json +1 -1
- package/pipeline/schemas/bulk-read-output.schema.json +52 -0
- package/pipeline/schemas/prefs.schema.json +74 -19
- package/pipeline/schemas/token-budget.json +3 -3
- package/pipeline/scripts/bulk-read.sh +277 -0
- package/pipeline/scripts/check-read-size.py +335 -0
- package/pipeline/scripts/check-read-size.sh +86 -0
- package/pipeline/scripts/phase-tracker.sh +245 -3
- package/pipeline/scripts/pre-commit-check.sh +1 -0
- package/pipeline/scripts/uninstall.mjs +1 -0
- package/pipeline/skills/.skill-manifest.json +8 -24
- package/pipeline/skills/.skills-index.json +2 -46
- package/pipeline/skills/shared/README.md +4 -8
- package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +0 -8
- package/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-resume-local/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +12 -12
- package/pipeline/skills/shared/external/backlog/SKILL.md +10 -6
- package/pipeline/skills/skills-index.md +2 -6
- package/pipeline/commands/multi-agent/dev/SKILL.md +0 -17
- package/pipeline/commands/multi-agent/dev-autopilot/SKILL.md +0 -23
- package/pipeline/commands/multi-agent/dev-local/SKILL.md +0 -17
- package/pipeline/commands/multi-agent/dev-local-autopilot/SKILL.md +0 -21
- package/pipeline/skills/shared/core/multi-agent-dev/SKILL.md +0 -19
- package/pipeline/skills/shared/core/multi-agent-dev-autopilot/SKILL.md +0 -25
- package/pipeline/skills/shared/core/multi-agent-dev-local/SKILL.md +0 -19
- package/pipeline/skills/shared/core/multi-agent-dev-local-autopilot/SKILL.md +0 -23
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# check-read-size.sh - PreToolUse read gate for the multi-agent pipeline.
|
|
3
|
+
#
|
|
4
|
+
# The third deterministic hook, and the first on the READ side. The other two
|
|
5
|
+
# (pre-commit-check, agent-guard) inspect what the run writes; this one inspects
|
|
6
|
+
# what it pays to read.
|
|
7
|
+
#
|
|
8
|
+
# The bill it exists for: a phase that reads six 900-line files pays for 5,400
|
|
9
|
+
# lines at that phase's own rung, and the part it needed was a handful of
|
|
10
|
+
# symbols. `offload-ref.sh` already took the other half of that bill - the build
|
|
11
|
+
# log, the diff, the test output - by parking the payload and printing a pointer.
|
|
12
|
+
# Nothing looked at file reads, because nothing could: only a hook sees a tool
|
|
13
|
+
# call before it runs.
|
|
14
|
+
#
|
|
15
|
+
# Contract (Claude Code PreToolUse hook):
|
|
16
|
+
# - Reads the tool-call JSON on stdin.
|
|
17
|
+
# - Exit 2 -> BLOCK (reason on stderr, shown to the model).
|
|
18
|
+
# - Exit 0 -> allow.
|
|
19
|
+
#
|
|
20
|
+
# Modes - `prefs.global.bulkRead.mode`:
|
|
21
|
+
# off (default) nothing is inspected; the hook is a no-op.
|
|
22
|
+
# observe decide and LOG, never block. This is the measurement: run the
|
|
23
|
+
# pipeline normally and metrics.jsonl carries the read-size
|
|
24
|
+
# distribution, so the saving can be estimated before anything is
|
|
25
|
+
# built on top of it.
|
|
26
|
+
# enforce block a whole-file read over `minLines` and tell the model to
|
|
27
|
+
# delegate it to bulk-read.sh instead.
|
|
28
|
+
#
|
|
29
|
+
# Why observe is a real mode and not a debug flag: the honest order is measure,
|
|
30
|
+
# then route. A gate switched straight to enforce has no baseline to be
|
|
31
|
+
# compared against, and "we cut tokens" becomes a claim nothing can check.
|
|
32
|
+
#
|
|
33
|
+
# Why the development phase is exempt by default: in Claude Code, `Edit` requires
|
|
34
|
+
# the same file to have been read first. A gate that blocks reads in Phase 3
|
|
35
|
+
# blocks editing. The gate is for the phases that read to UNDERSTAND.
|
|
36
|
+
#
|
|
37
|
+
# Safety design (fail-OPEN): any internal error, missing python3, missing helper,
|
|
38
|
+
# empty payload -> exit 0. It never executes the inspected command, never writes
|
|
39
|
+
# to the inspected file, makes no network call, and prints no file content.
|
|
40
|
+
|
|
41
|
+
set -u
|
|
42
|
+
|
|
43
|
+
HERE="$(cd "$(dirname "$0")" 2>/dev/null && pwd || true)"
|
|
44
|
+
HELPER="$HERE/check-read-size.py"
|
|
45
|
+
[ -f "$HELPER" ] || exit 0
|
|
46
|
+
command -v python3 >/dev/null 2>&1 || exit 0
|
|
47
|
+
|
|
48
|
+
PAYLOAD="$(cat 2>/dev/null || true)"
|
|
49
|
+
[ -z "$PAYLOAD" ] && exit 0
|
|
50
|
+
|
|
51
|
+
DECISION="$(printf '%s' "$PAYLOAD" | python3 "$HELPER" 2>/dev/null || true)"
|
|
52
|
+
[ -z "$DECISION" ] && exit 0
|
|
53
|
+
|
|
54
|
+
VERDICT="$(printf '%s' "$DECISION" | cut -f1)"
|
|
55
|
+
FILE="$(printf '%s' "$DECISION" | cut -f2)"
|
|
56
|
+
LINES="$(printf '%s' "$DECISION" | cut -f3)"
|
|
57
|
+
WHY="$(printf '%s' "$DECISION" | cut -f4)"
|
|
58
|
+
|
|
59
|
+
# Telemetry is best-effort and never gates the decision. `log-metric.sh` already
|
|
60
|
+
# tolerates an unwritable directory, so the only guard needed here is the
|
|
61
|
+
# script's own presence.
|
|
62
|
+
note() {
|
|
63
|
+
[ -x "$HERE/log-metric.sh" ] || return 0
|
|
64
|
+
"$HERE/log-metric.sh" "${MULTI_AGENT_TASK_ID:-unknown}" "${MULTI_AGENT_PHASE:-0}" \
|
|
65
|
+
"read.$1" lines="${LINES:-0}" reason="${WHY:-}" >/dev/null 2>&1 || true
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
case "$VERDICT" in
|
|
69
|
+
OBSERVE)
|
|
70
|
+
note observed
|
|
71
|
+
exit 0 ;;
|
|
72
|
+
BLOCK)
|
|
73
|
+
note blocked
|
|
74
|
+
echo "BLOCKED by check-read-size: ${FILE} is ${LINES} lines." >&2
|
|
75
|
+
echo "Reading it whole costs this phase's rung for a file whose useful part is a few symbols." >&2
|
|
76
|
+
echo "" >&2
|
|
77
|
+
echo "Delegate it, then work from what comes back:" >&2
|
|
78
|
+
echo " bash \$HOME/.claude/scripts/bulk-read.sh --file '${FILE}' --question '<what you need from it>'" >&2
|
|
79
|
+
echo "" >&2
|
|
80
|
+
echo "The summary carries LINE NUMBERS, so a follow-up Read with an offset/limit around the" >&2
|
|
81
|
+
echo "region you actually need is the intended next step and passes this gate." >&2
|
|
82
|
+
echo "Full text stays on disk under .multi-agent/refs/ if the summary is not enough." >&2
|
|
83
|
+
exit 2 ;;
|
|
84
|
+
*)
|
|
85
|
+
exit 0 ;;
|
|
86
|
+
esac
|
|
@@ -64,6 +64,8 @@ usage:
|
|
|
64
64
|
phase-tracker.sh meta <phase_id> <key> <value>
|
|
65
65
|
phase-tracker.sh now <phase_id> "<text>"
|
|
66
66
|
phase-tracker.sh cost <phase_id>|total
|
|
67
|
+
phase-tracker.sh tiles
|
|
68
|
+
phase-tracker.sh report
|
|
67
69
|
phase-tracker.sh render
|
|
68
70
|
USAGE
|
|
69
71
|
exit 64
|
|
@@ -498,6 +500,167 @@ release_state_lock() {
|
|
|
498
500
|
# Never leave a lock behind if a mutating action dies mid-critical-section.
|
|
499
501
|
trap release_state_lock EXIT
|
|
500
502
|
|
|
503
|
+
# The cost table is keyed by family ("opus", "sonnet", "gpt-5.6"), but the name
|
|
504
|
+
# an agent has at hand is the full id ("claude-opus-5"). Storing the id verbatim
|
|
505
|
+
# priced every phase at "-" while the tracker looked perfectly healthy, so the
|
|
506
|
+
# family is resolved here, once, and an unresolvable name is said out loud
|
|
507
|
+
# instead of quietly costing nothing.
|
|
508
|
+
normalize_model() {
|
|
509
|
+
local raw="$1" lower
|
|
510
|
+
lower=$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]')
|
|
511
|
+
case "$lower" in
|
|
512
|
+
*terra*) printf 'gpt-5.6-terra' ;;
|
|
513
|
+
*gpt-5.6* | *gpt5.6*) printf 'gpt-5.6' ;;
|
|
514
|
+
*gpt-5.4* | *gpt5.4*) printf 'gpt-5.4' ;;
|
|
515
|
+
*opus*) printf 'opus' ;;
|
|
516
|
+
*sonnet*) printf 'sonnet' ;;
|
|
517
|
+
*haiku*) printf 'haiku' ;;
|
|
518
|
+
*fable*) printf 'fable' ;;
|
|
519
|
+
*) printf '%s' "$raw" ;;
|
|
520
|
+
esac
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
# Which CLI tree this copy runs from. The card below is identical on all three
|
|
524
|
+
# hosts, but the WIDGET is not: Claude Code has TaskCreate/TaskUpdate, Codex has
|
|
525
|
+
# update_plan, Copilot CLI has no task UI at all. Every "what to call next" hint
|
|
526
|
+
# has to know where it is, or it names a tool the host does not have.
|
|
527
|
+
SELF_PATH="${BASH_SOURCE[0]:-$0}"
|
|
528
|
+
host_kind() {
|
|
529
|
+
case "${MULTI_AGENT_HOST:-}" in
|
|
530
|
+
claude | codex | copilot)
|
|
531
|
+
printf '%s' "$MULTI_AGENT_HOST"
|
|
532
|
+
return 0
|
|
533
|
+
;;
|
|
534
|
+
esac
|
|
535
|
+
case "$SELF_PATH" in
|
|
536
|
+
*/.codex/*) printf 'codex' ;;
|
|
537
|
+
*/.copilot/*) printf 'copilot' ;;
|
|
538
|
+
*) printf 'claude' ;;
|
|
539
|
+
esac
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
# The completion line the contract requires, built from what was actually
|
|
543
|
+
# recorded. Phases with no recorded spend say so rather than printing zeros,
|
|
544
|
+
# which is the difference between "cost unknown" and "cost was nothing".
|
|
545
|
+
narration_line() {
|
|
546
|
+
local pid="$1" state name idx model t_in t_out usd
|
|
547
|
+
state="$(load_state)"
|
|
548
|
+
name=$(echo "$state" | jq -r --arg id "$pid" '(.phases[] | select(.id == $id) | .name) // ""')
|
|
549
|
+
idx=$(echo "$state" | jq -r --arg id "$pid" '[.phases[]?.id] | index($id) // "-"')
|
|
550
|
+
model=$(echo "$state" | jq -r --arg id "$pid" '(.phases[] | select(.id == $id) | .model) // ""')
|
|
551
|
+
t_in=$(echo "$state" | jq -r --arg id "$pid" '(.phases[] | select(.id == $id) | .tokens_in) // 0')
|
|
552
|
+
t_out=$(echo "$state" | jq -r --arg id "$pid" '(.phases[] | select(.id == $id) | .tokens_out) // 0')
|
|
553
|
+
if [ "$t_in" -eq 0 ] && [ "$t_out" -eq 0 ]; then
|
|
554
|
+
printf 'Phase %s %s done (no LLM calls)' "$pid" "$name"
|
|
555
|
+
return 0
|
|
556
|
+
fi
|
|
557
|
+
usd="-"
|
|
558
|
+
[ "$idx" != "-" ] && usd=$(phase_usd "$idx")
|
|
559
|
+
printf 'Phase %s %s done - ~%s in / ~%s out tokens (%s, ~$%s)' \
|
|
560
|
+
"$pid" "$name" "$(format_tokens "$t_in")" "$(format_tokens "$t_out")" \
|
|
561
|
+
"${model:-unknown model}" "$usd"
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
# Printed after every status change. The card goes to stdout, and on every host
|
|
565
|
+
# that collapses tool output the user never sees it - so the hint names the
|
|
566
|
+
# host's own widget call, which the agent makes in its own turn where the user
|
|
567
|
+
# does see it.
|
|
568
|
+
tracker_next_hint() {
|
|
569
|
+
local pid="$1" status="$2" name mirror
|
|
570
|
+
[ "${TRACKER_QUIET:-0}" = "1" ] && return 0
|
|
571
|
+
name=$(load_state | jq -r --arg id "$pid" '(.phases[] | select(.id == $id) | .name) // ""')
|
|
572
|
+
case "$(host_kind)" in
|
|
573
|
+
claude) mirror="TaskUpdate(\"Phase $pid $name\", status=\"$status\")" ;;
|
|
574
|
+
codex) mirror="update_plan: set step \"Phase $pid $name\" to $status (send the FULL step list, it is not a delta)" ;;
|
|
575
|
+
*) mirror="no task widget on this host - reprint the card above inside your reply text" ;;
|
|
576
|
+
esac
|
|
577
|
+
printf '\n-- NEXT (required) --\n %s\n' "$mirror"
|
|
578
|
+
case "$status" in
|
|
579
|
+
completed | failed)
|
|
580
|
+
printf ' narrate one line in outputLanguage: %s\n' "$(narration_line "$pid")"
|
|
581
|
+
;;
|
|
582
|
+
esac
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
# Elapsed with an hours bucket. format_elapsed stops at minutes because the card
|
|
586
|
+
# shows one phase at a time; a whole-run total reads as "102m" without this.
|
|
587
|
+
format_span() {
|
|
588
|
+
local s="$1"
|
|
589
|
+
[ -z "$s" ] && { echo "-"; return; }
|
|
590
|
+
[ "$s" -lt 0 ] 2>/dev/null && { echo "-"; return; }
|
|
591
|
+
if [ "$s" -lt 3600 ]; then
|
|
592
|
+
format_elapsed "$s"
|
|
593
|
+
else
|
|
594
|
+
printf '%dh %dm' "$((s / 3600))" "$(((s % 3600) / 60))"
|
|
595
|
+
fi
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
# The end-of-run report: what the pipeline spent, phase by phase, and what it
|
|
599
|
+
# has to say about phases it could not price. Printed by Phase 7 next to the
|
|
600
|
+
# work summary, which covers what actually changed on disk.
|
|
601
|
+
report() {
|
|
602
|
+
need_jq
|
|
603
|
+
local state task_id started
|
|
604
|
+
state="$(load_state)"
|
|
605
|
+
task_id=$(echo "$state" | jq -r '.task_id // "?"')
|
|
606
|
+
started=$(echo "$state" | jq -r '.started_at // ""')
|
|
607
|
+
|
|
608
|
+
printf '\n== Run report: %s ==\n' "$task_id"
|
|
609
|
+
printf '%-22s %-11s %10s %-16s %-16s %8s\n' \
|
|
610
|
+
"Phase" "Status" "Elapsed" "Tokens in/out" "Model" "USD"
|
|
611
|
+
printf -- '%s\n' "----------------------------------------------------------------------------------"
|
|
612
|
+
|
|
613
|
+
local total_in=0 total_out=0 unpriced=""
|
|
614
|
+
local row
|
|
615
|
+
while IFS=$'\037' read -r idx id name status p_start p_end t_in t_out model no_llm; do
|
|
616
|
+
[ -n "$id" ] || continue
|
|
617
|
+
local secs="" span="-" toks="-" usd
|
|
618
|
+
if [ -n "$p_start" ]; then
|
|
619
|
+
local e_start e_end
|
|
620
|
+
e_start=$(iso_to_epoch "$p_start")
|
|
621
|
+
e_end=$([ -n "$p_end" ] && iso_to_epoch "$p_end" || now_epoch)
|
|
622
|
+
[ -n "$e_start" ] && [ -n "$e_end" ] && secs=$((e_end - e_start))
|
|
623
|
+
[ -n "$secs" ] && span=$(format_span "$secs")
|
|
624
|
+
fi
|
|
625
|
+
if [ "${t_in:-0}" -gt 0 ] || [ "${t_out:-0}" -gt 0 ]; then
|
|
626
|
+
toks="$(format_tokens "${t_in:-0}") / $(format_tokens "${t_out:-0}")"
|
|
627
|
+
total_in=$((total_in + t_in))
|
|
628
|
+
total_out=$((total_out + t_out))
|
|
629
|
+
elif [ "$status" = "completed" ] && [ "$no_llm" != "true" ]; then
|
|
630
|
+
unpriced="$unpriced $id"
|
|
631
|
+
fi
|
|
632
|
+
usd=$(phase_usd "$idx")
|
|
633
|
+
printf '%-22s %-11s %10s %-16s %-16s %8s\n' \
|
|
634
|
+
"$id $name" "$status" "$span" "$toks" "${model:--}" "$usd"
|
|
635
|
+
done <<EOF
|
|
636
|
+
$(echo "$state" | jq -r '
|
|
637
|
+
[.phases[]?] | to_entries[] |
|
|
638
|
+
[ (.key|tostring), .value.id, (.value.name // ""), (.value.status // "pending"),
|
|
639
|
+
(.value.started_at // ""), (.value.completed_at // ""),
|
|
640
|
+
((.value.tokens_in // 0)|tostring), ((.value.tokens_out // 0)|tostring),
|
|
641
|
+
(.value.model // ""), ((.value.no_llm // false)|tostring) ] | join("\u001f")')
|
|
642
|
+
EOF
|
|
643
|
+
|
|
644
|
+
printf -- '%s\n' "----------------------------------------------------------------------------------"
|
|
645
|
+
local run_secs="" run_span="-"
|
|
646
|
+
if [ -n "$started" ]; then
|
|
647
|
+
local e_run last
|
|
648
|
+
e_run=$(iso_to_epoch "$started")
|
|
649
|
+
last=$(echo "$state" | jq -r '[.phases[]?.completed_at // empty] | max // ""')
|
|
650
|
+
local e_last
|
|
651
|
+
e_last=$([ -n "$last" ] && iso_to_epoch "$last" || now_epoch)
|
|
652
|
+
[ -n "$e_run" ] && [ -n "$e_last" ] && run_secs=$((e_last - e_run))
|
|
653
|
+
[ -n "$run_secs" ] && run_span=$(format_span "$run_secs")
|
|
654
|
+
fi
|
|
655
|
+
printf '%-22s %-11s %10s %-16s %-16s %8s\n' \
|
|
656
|
+
"Total" "" "$run_span" \
|
|
657
|
+
"$(format_tokens "$total_in") / $(format_tokens "$total_out")" "" "$(phase_usd total)"
|
|
658
|
+
if [ -n "$unpriced" ]; then
|
|
659
|
+
printf 'Cost unavailable - no tokens recorded for phase(s):%s\n' "$unpriced"
|
|
660
|
+
fi
|
|
661
|
+
printf '\n'
|
|
662
|
+
}
|
|
663
|
+
|
|
501
664
|
render() {
|
|
502
665
|
need_jq
|
|
503
666
|
local state
|
|
@@ -762,12 +925,49 @@ case "$ACTION" in
|
|
|
762
925
|
|
|
763
926
|
update)
|
|
764
927
|
need_jq
|
|
765
|
-
[ "$#" -ge 2 ] || { echo "update needs <phase_id> <status>" >&2; exit 64; }
|
|
766
|
-
PID="$1"; STATUS="$2"
|
|
928
|
+
[ "$#" -ge 2 ] || { echo "update needs <phase_id> <status> [--no-llm]" >&2; exit 64; }
|
|
929
|
+
PID="$1"; STATUS="$2"; shift 2
|
|
930
|
+
NO_LLM=0
|
|
931
|
+
while [ "$#" -gt 0 ]; do
|
|
932
|
+
case "$1" in
|
|
933
|
+
--no-llm) NO_LLM=1 ;;
|
|
934
|
+
*) echo "update: unknown option $1" >&2; exit 64 ;;
|
|
935
|
+
esac
|
|
936
|
+
shift
|
|
937
|
+
done
|
|
767
938
|
case "$STATUS" in
|
|
768
939
|
pending|in_progress|completed|failed|skipped) ;;
|
|
769
940
|
*) echo "bad status: $STATUS" >&2; exit 64 ;;
|
|
770
941
|
esac
|
|
942
|
+
# Accounting gate. A phase that ran LLM work and recorded nothing produces a
|
|
943
|
+
# completion line with nothing to say and a cost breakdown that reads as
|
|
944
|
+
# unavailable - which is exactly how per-phase token reporting went missing
|
|
945
|
+
# for months while every gate stayed green, because the gates lint the docs
|
|
946
|
+
# for the CALL and nothing checks that the call happened. Refusing the
|
|
947
|
+
# completion is recoverable (record, then re-run) and loses no work; a
|
|
948
|
+
# phase that genuinely made no LLM call says so with --no-llm.
|
|
949
|
+
if [ "$STATUS" = "completed" ] && [ "$NO_LLM" -eq 0 ]; then
|
|
950
|
+
case " ${TRACKER_LLM_PHASES:-1 2 3 4} " in
|
|
951
|
+
*" $PID "*)
|
|
952
|
+
RECORDED=$(load_state | jq -r --arg id "$PID" \
|
|
953
|
+
'(.phases[] | select(.id == $id) | (.tokens_in // 0) + (.tokens_out // 0)) // 0')
|
|
954
|
+
if [ "${RECORDED:-0}" -eq 0 ]; then
|
|
955
|
+
cat >&2 <<GATE
|
|
956
|
+
update: phase $PID has no recorded spend, so its completion line would have
|
|
957
|
+
nothing to report and the run report would price it as unavailable.
|
|
958
|
+
|
|
959
|
+
Record it first, then re-run this update:
|
|
960
|
+
phase-tracker.sh model $PID <model_name>
|
|
961
|
+
phase-tracker.sh tokens $PID <input_count> <output_count> [cached_count]
|
|
962
|
+
|
|
963
|
+
If the phase genuinely made no LLM call, say so explicitly:
|
|
964
|
+
phase-tracker.sh update $PID completed --no-llm
|
|
965
|
+
GATE
|
|
966
|
+
exit 3
|
|
967
|
+
fi
|
|
968
|
+
;;
|
|
969
|
+
esac
|
|
970
|
+
fi
|
|
771
971
|
NOW_ISO=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
772
972
|
acquire_state_lock
|
|
773
973
|
state=$(load_state)
|
|
@@ -782,10 +982,14 @@ case "$ACTION" in
|
|
|
782
982
|
else . end
|
|
783
983
|
)
|
|
784
984
|
')
|
|
985
|
+
if [ "$NO_LLM" -eq 1 ]; then
|
|
986
|
+
new=$(echo "$new" | jq --arg id "$PID" '.phases |= map(if .id == $id then .no_llm = true else . end)')
|
|
987
|
+
fi
|
|
785
988
|
save_state "$new"
|
|
786
989
|
release_state_lock
|
|
787
990
|
emit_otel_span "phase.update" "$PID" "$STATUS" "{\"status\": \"$STATUS\"}"
|
|
788
991
|
render
|
|
992
|
+
tracker_next_hint "$PID" "$STATUS"
|
|
789
993
|
usage_live_ping "$(basename "$TRACKER_DIR")" "$PID"
|
|
790
994
|
;;
|
|
791
995
|
|
|
@@ -893,7 +1097,11 @@ case "$ACTION" in
|
|
|
893
1097
|
model)
|
|
894
1098
|
need_jq
|
|
895
1099
|
[ "$#" -ge 2 ] || { echo "model needs <phase_id> <model_name>" >&2; exit 64; }
|
|
896
|
-
PID="$1"; MODEL="$2"
|
|
1100
|
+
PID="$1"; MODEL="$(normalize_model "$2")"
|
|
1101
|
+
if [ -f "$COST_TABLE" ] && ! jq -e --arg m "$MODEL" '.prices[$m]' "$COST_TABLE" >/dev/null 2>&1; then
|
|
1102
|
+
echo "phase-tracker: '$2' is not a priced model - this phase will report USD as '-'." >&2
|
|
1103
|
+
echo " priced names: $(jq -r '.prices | keys | join(", ")' "$COST_TABLE" 2>/dev/null)" >&2
|
|
1104
|
+
fi
|
|
897
1105
|
acquire_state_lock
|
|
898
1106
|
state=$(load_state)
|
|
899
1107
|
new=$(echo "$state" | jq --arg id "$PID" --arg m "$MODEL" '
|
|
@@ -948,6 +1156,40 @@ case "$ACTION" in
|
|
|
948
1156
|
fi
|
|
949
1157
|
;;
|
|
950
1158
|
|
|
1159
|
+
tiles)
|
|
1160
|
+
need_jq
|
|
1161
|
+
tiles_state=$(load_state)
|
|
1162
|
+
tiles_count=$(echo "$tiles_state" | jq '[.phases[]?] | length')
|
|
1163
|
+
[ "${tiles_count:-0}" -gt 0 ] || {
|
|
1164
|
+
echo "tiles: no phases registered - run 'add' for each phase first" >&2
|
|
1165
|
+
exit 64
|
|
1166
|
+
}
|
|
1167
|
+
case "$(host_kind)" in
|
|
1168
|
+
claude)
|
|
1169
|
+
echo "REQUIRED - create one native tile per phase, in this exact order,"
|
|
1170
|
+
echo "BEFORE any TaskUpdate. The widget renders by creation order, not by"
|
|
1171
|
+
echo "phase number, so an out-of-order call scrambles the stack."
|
|
1172
|
+
echo "$tiles_state" | jq -r '.phases[] | " TaskCreate(subject: \"Phase \(.id) \(.name)\")"'
|
|
1173
|
+
;;
|
|
1174
|
+
codex)
|
|
1175
|
+
echo "REQUIRED - register the plan in ONE update_plan call with this step"
|
|
1176
|
+
echo "list. update_plan takes the full list, not a delta, so every later"
|
|
1177
|
+
echo "boundary resends it with one step's status changed."
|
|
1178
|
+
echo "$tiles_state" | jq -c '{plan: [.phases[] | {step: "Phase \(.id) \(.name)", status: "pending"}]}'
|
|
1179
|
+
;;
|
|
1180
|
+
*)
|
|
1181
|
+
echo "Copilot CLI has no native task widget: the bordered card IS the widget."
|
|
1182
|
+
echo "Reprint it inside your reply at every phase boundary - tool output is"
|
|
1183
|
+
echo "collapsed, so a card left in stdout never reaches the user."
|
|
1184
|
+
render
|
|
1185
|
+
;;
|
|
1186
|
+
esac
|
|
1187
|
+
;;
|
|
1188
|
+
|
|
1189
|
+
report)
|
|
1190
|
+
report
|
|
1191
|
+
;;
|
|
1192
|
+
|
|
951
1193
|
render)
|
|
952
1194
|
render
|
|
953
1195
|
;;
|
|
@@ -194,6 +194,7 @@ scan_file() {
|
|
|
194
194
|
if (s ~ /^[0-9]+$/) next; # pure digits
|
|
195
195
|
if (s ~ /^[0-9a-f]+$/) next; # lowercase hex (git sha / md5 / sha-*)
|
|
196
196
|
if (s ~ /^[0-9A-F]+$/) next; # uppercase hex
|
|
197
|
+
if (s ~ /^([A-Z][a-z]+)+[A-Z]*$/) next; # CamelCase words only (label key / type name), never a credential
|
|
197
198
|
delete freq;
|
|
198
199
|
for (i = 1; i <= n; i++) { c = substr(s, i, 1); freq[c]++ }
|
|
199
200
|
H = 0;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "1.0.0",
|
|
3
|
-
"generatedAt": "2026-09-
|
|
4
|
-
"skillCount":
|
|
3
|
+
"generatedAt": "2026-09-08T16:31:56Z",
|
|
4
|
+
"skillCount": 207,
|
|
5
5
|
"entries": [
|
|
6
6
|
{
|
|
7
7
|
"path": "shared/core/apple-archive-compliance/SKILL.md",
|
|
@@ -43,22 +43,6 @@
|
|
|
43
43
|
"path": "shared/core/multi-agent-design-check/SKILL.md",
|
|
44
44
|
"sha256": "bdf5ef1b8e0b27eb216eed89382ced99552c4aadfb61bc9a4931e41e8316d92a"
|
|
45
45
|
},
|
|
46
|
-
{
|
|
47
|
-
"path": "shared/core/multi-agent-dev-autopilot/SKILL.md",
|
|
48
|
-
"sha256": "42f8f5008a32202f7a51cec57d278baeaedca6d2ce70200dfce2fdd0eb62aaa4"
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
"path": "shared/core/multi-agent-dev-local-autopilot/SKILL.md",
|
|
52
|
-
"sha256": "257aa9d8ff48b76c07809d675f139087522edec598d5fb5bb0fcbd17e2857aac"
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
"path": "shared/core/multi-agent-dev-local/SKILL.md",
|
|
56
|
-
"sha256": "ba7e75447935e1c7bfc77bbbf8a0affe9cc2f631c2acea091de1b68d4bb34964"
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
"path": "shared/core/multi-agent-dev/SKILL.md",
|
|
60
|
-
"sha256": "4ac1401ebdd6d8ff21b85d11b1dffbd8399046348693eca758e6877a2ae07184"
|
|
61
|
-
},
|
|
62
46
|
{
|
|
63
47
|
"path": "shared/core/multi-agent-diff-explain/SKILL.md",
|
|
64
48
|
"sha256": "7cb37a224e61d66e350202ecfc5fbc6c33da3fe0718504a093602f5cde5b648f"
|
|
@@ -81,11 +65,11 @@
|
|
|
81
65
|
},
|
|
82
66
|
{
|
|
83
67
|
"path": "shared/core/multi-agent-help/SKILL.md",
|
|
84
|
-
"sha256": "
|
|
68
|
+
"sha256": "87d45a04c782b03569b0ab9ee54da8414cbadd5a2a82a7701f38dc8a3ee94627"
|
|
85
69
|
},
|
|
86
70
|
{
|
|
87
71
|
"path": "shared/core/multi-agent-ios-coding-standard/SKILL.md",
|
|
88
|
-
"sha256": "
|
|
72
|
+
"sha256": "15a8d583cb7c772d5223c66c158062f25fdff4c41c6d353b7b70590a441dea38"
|
|
89
73
|
},
|
|
90
74
|
{
|
|
91
75
|
"path": "shared/core/multi-agent-issue/SKILL.md",
|
|
@@ -137,7 +121,7 @@
|
|
|
137
121
|
},
|
|
138
122
|
{
|
|
139
123
|
"path": "shared/core/multi-agent-resume-local/SKILL.md",
|
|
140
|
-
"sha256": "
|
|
124
|
+
"sha256": "a4bedf949ab648d977dd1409339d08af204341f555d69f71f58c1a54dffaff6d"
|
|
141
125
|
},
|
|
142
126
|
{
|
|
143
127
|
"path": "shared/core/multi-agent-resume/SKILL.md",
|
|
@@ -193,11 +177,11 @@
|
|
|
193
177
|
},
|
|
194
178
|
{
|
|
195
179
|
"path": "shared/core/multi-agent-store-ready/SKILL.md",
|
|
196
|
-
"sha256": "
|
|
180
|
+
"sha256": "0060d1d7a8ec1e8cce30975f125faf46e9e384d23100ae46e82c9f194a0bf9fa"
|
|
197
181
|
},
|
|
198
182
|
{
|
|
199
183
|
"path": "shared/core/multi-agent-sync/SKILL.md",
|
|
200
|
-
"sha256": "
|
|
184
|
+
"sha256": "c0e2c22d0f6da9a60f5552d6f47db0490a355ea4ce08722716470e03535e5418"
|
|
201
185
|
},
|
|
202
186
|
{
|
|
203
187
|
"path": "shared/core/multi-agent-test-accessibility/SKILL.md",
|
|
@@ -321,7 +305,7 @@
|
|
|
321
305
|
},
|
|
322
306
|
{
|
|
323
307
|
"path": "shared/external/backlog/SKILL.md",
|
|
324
|
-
"sha256": "
|
|
308
|
+
"sha256": "d6602ec8f7bf70a305f4590ace8e8b92e28b165ffda2efb2fd95c8c27d541e23"
|
|
325
309
|
},
|
|
326
310
|
{
|
|
327
311
|
"path": "shared/external/callkit-voip/SKILL.md",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "1.0.0",
|
|
3
|
-
"skillCount":
|
|
3
|
+
"skillCount": 207,
|
|
4
4
|
"entries": [
|
|
5
5
|
{
|
|
6
6
|
"name": "accessibility-compliance-accessibility-audit",
|
|
@@ -1036,50 +1036,6 @@
|
|
|
1036
1036
|
"triggerPaths": [],
|
|
1037
1037
|
"relativePath": "shared/core/multi-agent-design-check/SKILL.md"
|
|
1038
1038
|
},
|
|
1039
|
-
{
|
|
1040
|
-
"name": "multi-agent-dev",
|
|
1041
|
-
"description": "Removed in v16.0.0. Depth is a question the run asks, not a command name: start /multi-agent and answer Short. Invoke only to see that redirect.",
|
|
1042
|
-
"platform": null,
|
|
1043
|
-
"group": "core",
|
|
1044
|
-
"plugin": null,
|
|
1045
|
-
"invokeAs": "multi-agent-dev",
|
|
1046
|
-
"triggerKeywords": [],
|
|
1047
|
-
"triggerPaths": [],
|
|
1048
|
-
"relativePath": "shared/core/multi-agent-dev/SKILL.md"
|
|
1049
|
-
},
|
|
1050
|
-
{
|
|
1051
|
-
"name": "multi-agent-dev-autopilot",
|
|
1052
|
-
"description": "Removed in v16.0.0 and not replaced: an unsupervised run is always the full pipeline now. Invoke only to be pointed at /multi-agent:autopilot or the interactive picker.",
|
|
1053
|
-
"platform": null,
|
|
1054
|
-
"group": "core",
|
|
1055
|
-
"plugin": null,
|
|
1056
|
-
"invokeAs": "multi-agent-dev-autopilot",
|
|
1057
|
-
"triggerKeywords": [],
|
|
1058
|
-
"triggerPaths": [],
|
|
1059
|
-
"relativePath": "shared/core/multi-agent-dev-autopilot/SKILL.md"
|
|
1060
|
-
},
|
|
1061
|
-
{
|
|
1062
|
-
"name": "multi-agent-dev-local",
|
|
1063
|
-
"description": "Removed in v16.0.0. Its worktree-free twin is /multi-agent:local, which asks the same depth question; answer Short there. Invoke only to see that redirect.",
|
|
1064
|
-
"platform": null,
|
|
1065
|
-
"group": "core",
|
|
1066
|
-
"plugin": null,
|
|
1067
|
-
"invokeAs": "multi-agent-dev-local",
|
|
1068
|
-
"triggerKeywords": [],
|
|
1069
|
-
"triggerPaths": [],
|
|
1070
|
-
"relativePath": "shared/core/multi-agent-dev-local/SKILL.md"
|
|
1071
|
-
},
|
|
1072
|
-
{
|
|
1073
|
-
"name": "multi-agent-dev-local-autopilot",
|
|
1074
|
-
"description": "Retired alongside its worktree twin in v16.0.0, with nothing standing in for it. Invoke only to be pointed at /multi-agent:local-autopilot or the interactive picker.",
|
|
1075
|
-
"platform": null,
|
|
1076
|
-
"group": "core",
|
|
1077
|
-
"plugin": null,
|
|
1078
|
-
"invokeAs": "multi-agent-dev-local-autopilot",
|
|
1079
|
-
"triggerKeywords": [],
|
|
1080
|
-
"triggerPaths": [],
|
|
1081
|
-
"relativePath": "shared/core/multi-agent-dev-local-autopilot/SKILL.md"
|
|
1082
|
-
},
|
|
1083
1039
|
{
|
|
1084
1040
|
"name": "multi-agent-diff-explain",
|
|
1085
1041
|
"description": "Map Phase 4 triage findings to branch diff lines. Read-only post-hoc command, used after review to answer 'which finding lines up with which code change'. Use when a review finding has to be traced to the exact lines that caused it.",
|
|
@@ -1148,7 +1104,7 @@
|
|
|
1148
1104
|
},
|
|
1149
1105
|
{
|
|
1150
1106
|
"name": "multi-agent-ios-coding-standard",
|
|
1151
|
-
"description": "Audit an iOS module against the shared coding-standard registry (99 stable-ID rules), produce a remediation plan, then hand off to
|
|
1107
|
+
"description": "Audit an iOS module against the shared coding-standard registry (99 stable-ID rules), produce a remediation plan, then hand off to /multi-agent or :local. Use for a standards pass on a module, or when a review needs rule IDs rather than opinions.",
|
|
1152
1108
|
"platform": null,
|
|
1153
1109
|
"group": "core",
|
|
1154
1110
|
"plugin": null,
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
Single source of truth for skills delivered to both Claude Code (`~/.claude/skills/`) and Copilot CLI (`~/.copilot/skills/`) by the installer.
|
|
4
4
|
|
|
5
|
-
**Total:**
|
|
5
|
+
**Total:** 207 skills (54 core + 153 external). Auto-generated by `scripts/gen-skills-index.mjs` - do not edit by hand.
|
|
6
6
|
|
|
7
7
|
## Directory layout
|
|
8
8
|
|
|
9
|
-
- **`core/`** -
|
|
9
|
+
- **`core/`** - 54 `multi-agent*` orchestration skills that are pipeline-critical. Edits here are core-code changes.
|
|
10
10
|
- **`external/`** - 153 iOS / Android / generic skills imported from the upstream skill library. Mirrors of third-party guidance.
|
|
11
11
|
- Install destinations (ADR-0009): Claude Code gets NO local copy of `external/` - it loads those skills from the `multi-agent-plugins` marketplace, namespaced (`ai-<stack>-toolkit:<name>`); only the two compliance catalogs from `core/` land in `~/.claude/skills/`. Copilot CLI and Codex CLI receive a flat copy filtered to the enabled stacks. `external/` remains the single authoring source that `build-stack-plugins.mjs` publishes from.
|
|
12
12
|
|
|
@@ -14,7 +14,7 @@ Source layout is logical grouping only - skill discovery at runtime is unchang
|
|
|
14
14
|
|
|
15
15
|
## Categories
|
|
16
16
|
|
|
17
|
-
- [Pipeline Orchestration](#pipeline-orchestration) -
|
|
17
|
+
- [Pipeline Orchestration](#pipeline-orchestration) - 54
|
|
18
18
|
- [iOS / Apple Ecosystem](#ios-apple-ecosystem) - 90
|
|
19
19
|
- [Android / Kotlin](#android-kotlin) - 13
|
|
20
20
|
- [Web](#web) - 10
|
|
@@ -36,17 +36,13 @@ Source layout is logical grouping only - skill discovery at runtime is unchang
|
|
|
36
36
|
| [`multi-agent-complaint-analysis`](./core/multi-agent-complaint-analysis/) | `core` | Customer-complaint triage. Ingests complaints (paste, csv/xlsx/txt/json file, Jira issue, Confluence URL), fetches Graylog evidence per trx/ |
|
|
37
37
|
| [`multi-agent-create-jira`](./core/multi-agent-create-jira/) | `core` | Create a standards-compliant Jira issue (Task / Bug / Story): asks the type, mines project conventions, drafts from a standard template with |
|
|
38
38
|
| [`multi-agent-design-check`](./core/multi-agent-design-check/) | `core` | Mock-mode vs Figma design audit (iOS / Android, local-only). Pick repo + module, gate on mock support, enumerate every state driver into a c |
|
|
39
|
-
| [`multi-agent-dev`](./core/multi-agent-dev/) | `core` | Removed in v16.0.0. Depth is a question the run asks, not a command name: start /multi-agent and answer Short. Invoke only to see that redir |
|
|
40
|
-
| [`multi-agent-dev-autopilot`](./core/multi-agent-dev-autopilot/) | `core` | Removed in v16.0.0 and not replaced: an unsupervised run is always the full pipeline now. Invoke only to be pointed at /multi-agent:autopilo |
|
|
41
|
-
| [`multi-agent-dev-local`](./core/multi-agent-dev-local/) | `core` | Removed in v16.0.0. Its worktree-free twin is /multi-agent:local, which asks the same depth question; answer Short there. Invoke only to see |
|
|
42
|
-
| [`multi-agent-dev-local-autopilot`](./core/multi-agent-dev-local-autopilot/) | `core` | Retired alongside its worktree twin in v16.0.0, with nothing standing in for it. Invoke only to be pointed at /multi-agent:local-autopilot o |
|
|
43
39
|
| [`multi-agent-diff-explain`](./core/multi-agent-diff-explain/) | `core` | Map Phase 4 triage findings to branch diff lines. Read-only post-hoc command, used after review to answer 'which finding lines up with which |
|
|
44
40
|
| [`multi-agent-feedback`](./core/multi-agent-feedback/) | `core` | Send one message to the maintainer: a bug, an idea or a question. Only the text you type is sent - no logs, no repo names, no paths. Shows t |
|
|
45
41
|
| [`multi-agent-forget`](./core/multi-agent-forget/) | `core` | Remove a saved /multi-agent routine (created by /multi-agent:save): deletes its local-only command and its registry entry. Asks which one an |
|
|
46
42
|
| [`multi-agent-garbage-collect`](./core/multi-agent-garbage-collect/) | `core` | Sweep leftover /tmp scratch (picker state, review diffs, channel payloads, analysis drafts) from past runs. Dry-run first; confirms before d |
|
|
47
43
|
| [`multi-agent-graph`](./core/multi-agent-graph/) | `core` | Build and query this repo's code graph: a deterministic, LLM-free map of symbols, imports and references used to narrow Phase 1's Explore sc |
|
|
48
44
|
| [`multi-agent-help`](./core/multi-agent-help/) | `core` | Multi-agent pipeline usage guide - renders in EN or TR per prefs.global.outputLanguage (falls back to promptLanguage for backward compatib |
|
|
49
|
-
| [`multi-agent-ios-coding-standard`](./core/multi-agent-ios-coding-standard/) | `core` | Audit an iOS module against the shared coding-standard registry (99 stable-ID rules), produce a remediation plan, then hand off to
|
|
45
|
+
| [`multi-agent-ios-coding-standard`](./core/multi-agent-ios-coding-standard/) | `core` | Audit an iOS module against the shared coding-standard registry (99 stable-ID rules), produce a remediation plan, then hand off to /multi-ag |
|
|
50
46
|
| [`multi-agent-issue`](./core/multi-agent-issue/) | `core` | List unassigned GitHub issues, pick one, auto-assign, and launch the multi-agent pipeline. Use when a GitHub issue should be picked up and s |
|
|
51
47
|
| [`multi-agent-jira`](./core/multi-agent-jira/) | `core` | List open Jira issues, pick one, and launch the multi-agent pipeline. Use when a Jira issue should be picked up and started without knowing |
|
|
52
48
|
| [`multi-agent-kill`](./core/multi-agent-kill/) | `core` | Stop the given task, then remove its worktree and branch. Asks for confirmation. Use when a running or stuck task should be stopped and its |
|
|
@@ -87,10 +87,6 @@ Modes:
|
|
|
87
87
|
Dedicated dash commands (Copilot):
|
|
88
88
|
multi-agent-local, multi-agent-autopilot, multi-agent-local-autopilot
|
|
89
89
|
|
|
90
|
-
Removed in v16.0.0: multi-agent-dev -> multi-agent + Short;
|
|
91
|
-
multi-agent-dev-local -> multi-agent-local + Short; the two dev autopilot
|
|
92
|
-
names have no equivalent (fast-plus-unattended no longer exists).
|
|
93
|
-
|
|
94
90
|
------------------------------------------------------------
|
|
95
91
|
|
|
96
92
|
Utility Commands (dash form on Copilot, colon form on Claude Code):
|
|
@@ -264,10 +260,6 @@ Modlar:
|
|
|
264
260
|
Dedicated dash komutlar (Copilot):
|
|
265
261
|
multi-agent-local, multi-agent-autopilot, multi-agent-local-autopilot
|
|
266
262
|
|
|
267
|
-
v16.0.0'da kaldırıldı: multi-agent-dev -> multi-agent + Kısa;
|
|
268
|
-
multi-agent-dev-local -> multi-agent-local + Kısa; iki dev autopilot adının
|
|
269
|
-
birebir karşılığı yok (hızlı+gözetimsiz artık yok).
|
|
270
|
-
|
|
271
263
|
------------------------------------------------------------
|
|
272
264
|
|
|
273
265
|
Utility Komutları:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: multi-agent-ios-coding-standard
|
|
3
3
|
language: en
|
|
4
|
-
description: "Audit an iOS module against the shared coding-standard registry (99 stable-ID rules), produce a remediation plan, then hand off to
|
|
4
|
+
description: "Audit an iOS module against the shared coding-standard registry (99 stable-ID rules), produce a remediation plan, then hand off to /multi-agent or :local. Use for a standards pass on a module, or when a review needs rule IDs rather than opinions."
|
|
5
5
|
user-invocable: true
|
|
6
6
|
argument-hint: "[module name or path]"
|
|
7
7
|
---
|
|
@@ -23,7 +23,7 @@ Phases 1-3 (Analysis / Planning / Dev) are skipped by design - the branch's lo
|
|
|
23
23
|
|
|
24
24
|
## When to use it
|
|
25
25
|
|
|
26
|
-
- After
|
|
26
|
+
- After a `:local` run answered Short (which skips Review + Test) to run the full quality tail on the same branch.
|
|
27
27
|
- After hand-coding / hand-testing a change, to get review + build/test + PR + Jira write-up without re-running dev.
|
|
28
28
|
|
|
29
29
|
## When NOT to use it
|