@mmerterden/multi-agent-pipeline 10.9.0 → 10.12.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 +143 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/pipeline/commands/multi-agent/channels.md +42 -6
- package/pipeline/commands/multi-agent/finish.md +15 -6
- package/pipeline/commands/multi-agent/generate-bug.md +33 -0
- package/pipeline/commands/multi-agent/generate-task.md +33 -0
- package/pipeline/commands/multi-agent/help.md +12 -0
- package/pipeline/commands/multi-agent/manual-test.md +9 -2
- package/pipeline/commands/multi-agent/refs/channels/pr.md +1 -1
- package/pipeline/commands/multi-agent/refs/cross-cli-contract.md +7 -6
- package/pipeline/commands/multi-agent/refs/generate-issue.md +197 -0
- package/pipeline/commands/multi-agent/refs/keychain.md +12 -0
- package/pipeline/commands/multi-agent/refs/phases/phase-0-init.md +31 -2
- package/pipeline/commands/multi-agent/refs/phases/phase-3-dev.md +2 -0
- package/pipeline/commands/multi-agent/refs/phases/phase-4-review.md +2 -0
- package/pipeline/commands/multi-agent/refs/phases/phase-5-test.md +6 -1
- package/pipeline/commands/multi-agent/refs/phases/phase-7-report.md +5 -1
- package/pipeline/commands/multi-agent/refs/progress-contract.md +1 -0
- package/pipeline/commands/multi-agent/refs/tracker-contract.md +56 -13
- package/pipeline/commands/multi-agent/resume.md +6 -2
- package/pipeline/commands/multi-agent/sync.md +5 -5
- package/pipeline/commands/multi-agent.md +4 -0
- package/pipeline/lib/figma-mcp-refresh.sh +79 -0
- package/pipeline/preferences-template.json +2 -1
- package/pipeline/schemas/prefs.schema.json +11 -0
- package/pipeline/scripts/fixtures/install-layout.tsv +6 -6
- package/pipeline/scripts/phase-tracker.sh +134 -22
- package/pipeline/scripts/smoke-channels-approval-gate.sh +60 -0
- package/pipeline/scripts/smoke-generate-issue.sh +114 -0
- package/pipeline/scripts/smoke-phase-tracker.sh +69 -0
- package/pipeline/scripts/smoke-progress-contract.sh +9 -0
- package/pipeline/scripts/smoke-tasklist-ordering.sh +1 -0
- package/pipeline/scripts/smoke-token-preflight.sh +82 -0
- package/pipeline/scripts/smoke-tracker-contract.sh +62 -0
- package/pipeline/skills/.skills-index.json +22 -4
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +4 -2
- package/pipeline/skills/shared/core/multi-agent-generate-bug/SKILL.md +39 -0
- package/pipeline/skills/shared/core/multi-agent-generate-task/SKILL.md +39 -0
- package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +4 -0
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +5 -5
- package/pipeline/skills/skills-index.md +5 -3
|
@@ -179,6 +179,17 @@
|
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
},
|
|
182
|
+
"tokenScripts": {
|
|
183
|
+
"type": "object",
|
|
184
|
+
"additionalProperties": {
|
|
185
|
+
"type": [
|
|
186
|
+
"string",
|
|
187
|
+
"null"
|
|
188
|
+
]
|
|
189
|
+
},
|
|
190
|
+
"default": {},
|
|
191
|
+
"description": "Maps service ids to user-owned token generation scripts (absolute paths, personal - never synced). Used by the Phase 0 token pre-flight: figma_mcp silent renewal reads the .figma-oauth.json next to tokenScripts.figma_mcp; the 'Regenerate now (script)' option runs the script itself."
|
|
192
|
+
},
|
|
182
193
|
"platformIdentityRouting": {
|
|
183
194
|
"type": "object",
|
|
184
195
|
"description": "v2.1.0+. Maps repo URL glob patterns to identity names. Used in Phase 0 to auto-select git identity + PAT based on repo origin. Prevents corporate identity leaking into personal repos and vice versa.",
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
.claude/CLAUDE.md 1
|
|
2
2
|
.claude/agents 8
|
|
3
|
-
.claude/commands
|
|
4
|
-
.claude/lib
|
|
3
|
+
.claude/commands 92
|
|
4
|
+
.claude/lib 24
|
|
5
5
|
.claude/multi-agent-preferences.json 1
|
|
6
6
|
.claude/rules 12
|
|
7
7
|
.claude/schemas 23
|
|
8
|
-
.claude/scripts
|
|
8
|
+
.claude/scripts 174
|
|
9
9
|
.claude/settings.json 1
|
|
10
10
|
.claude/skills 560
|
|
11
11
|
.copilot/agents 8
|
|
12
12
|
.copilot/copilot-instructions.md 1
|
|
13
|
-
.copilot/lib
|
|
13
|
+
.copilot/lib 24
|
|
14
14
|
.copilot/schemas 23
|
|
15
|
-
.copilot/scripts
|
|
16
|
-
.copilot/skills
|
|
15
|
+
.copilot/scripts 174
|
|
16
|
+
.copilot/skills 598
|
|
@@ -14,12 +14,14 @@
|
|
|
14
14
|
#
|
|
15
15
|
# Commands:
|
|
16
16
|
# init <task_id> Reset tracker for this task
|
|
17
|
-
# add <phase_id> "<name>" Register a phase (status=pending)
|
|
17
|
+
# add <phase_id> "<name>" Register a phase (status=pending); idempotent - existing id is a no-op
|
|
18
18
|
# update <phase_id> <status> Change status + re-render
|
|
19
19
|
# sub <phase_id> <sub_id> "<name>" [status] Add or update a sub-phase
|
|
20
|
-
# tokens <phase_id> <in> <out>
|
|
20
|
+
# tokens <phase_id> <in> <out> [cached] Add to phase's token totals (in = fresh input, cache-exclusive)
|
|
21
21
|
# model <phase_id> <model_name> Tag phase with the LLM that produced its tokens (v8.3.0)
|
|
22
22
|
# meta <phase_id> <key> <value> Set phase context (account, repos, files, now, ...)
|
|
23
|
+
# now <phase_id> "<text>" Set the phase's live current-action line (quiet, 60-char cap)
|
|
24
|
+
# cost <phase_id>|total Print est. USD for one phase or the whole run ("-" when unpriceable)
|
|
23
25
|
# render Print current snapshot, no change
|
|
24
26
|
#
|
|
25
27
|
# Status enum: pending | in_progress | completed | failed | skipped
|
|
@@ -57,8 +59,11 @@ usage:
|
|
|
57
59
|
phase-tracker.sh add <phase_id> "<name>"
|
|
58
60
|
phase-tracker.sh update <phase_id> <pending|in_progress|completed|failed|skipped>
|
|
59
61
|
phase-tracker.sh sub <phase_id> <sub_id> "<name>" [status]
|
|
60
|
-
phase-tracker.sh tokens <phase_id> <in> <out>
|
|
62
|
+
phase-tracker.sh tokens <phase_id> <in> <out> [cached]
|
|
63
|
+
phase-tracker.sh model <phase_id> <model_name>
|
|
61
64
|
phase-tracker.sh meta <phase_id> <key> <value>
|
|
65
|
+
phase-tracker.sh now <phase_id> "<text>"
|
|
66
|
+
phase-tracker.sh cost <phase_id>|total
|
|
62
67
|
phase-tracker.sh render
|
|
63
68
|
USAGE
|
|
64
69
|
exit 64
|
|
@@ -239,6 +244,37 @@ format_elapsed() {
|
|
|
239
244
|
fi
|
|
240
245
|
}
|
|
241
246
|
|
|
247
|
+
# Cost table ships next to this script in every install tree (repo,
|
|
248
|
+
# ~/.claude/scripts, ~/.copilot/scripts). Pricing math matches
|
|
249
|
+
# render-agent-log-cost.sh exactly: tokens_in is FRESH input (cache-exclusive,
|
|
250
|
+
# per the disjoint token-count contract), tokens_cached is priced at the
|
|
251
|
+
# discounted cacheReadPerMtok rate, floored to cents. A missing/unparseable
|
|
252
|
+
# table never fails a caller - USD simply renders empty / "-".
|
|
253
|
+
COST_TABLE="$(cd "$(dirname "$0")" && pwd)/cost-table.json"
|
|
254
|
+
|
|
255
|
+
# Print est. USD ("0.21") for one phase index or, with "total", the sum across
|
|
256
|
+
# priceable phases. Prints "-" when nothing is priceable. Never non-zero exit.
|
|
257
|
+
phase_usd() {
|
|
258
|
+
local selector="$1" # integer phase index into .phases[], or "total"
|
|
259
|
+
[ -f "$COST_TABLE" ] || { echo "-"; return 0; }
|
|
260
|
+
local state; state="$(load_state)"
|
|
261
|
+
echo "$state" | jq -r --slurpfile prices "$COST_TABLE" --arg sel "$selector" '
|
|
262
|
+
def usd_of(p): ($prices[0].prices[p.model // ""] // null) as $rate |
|
|
263
|
+
if $rate == null then null
|
|
264
|
+
else ( ((p.tokens_in // 0) / 1000000) * $rate.inPerMtok
|
|
265
|
+
+ ((p.tokens_cached // 0) / 1000000) * ($rate.cacheReadPerMtok // $rate.inPerMtok)
|
|
266
|
+
+ ((p.tokens_out // 0) / 1000000) * $rate.outPerMtok )
|
|
267
|
+
end;
|
|
268
|
+
def floor_cents(u): if u == null then null else ((u * 100) | floor) / 100 end;
|
|
269
|
+
if $sel == "total" then
|
|
270
|
+
([.phases[] | usd_of(.) | select(. != null)] | if length == 0 then "-" else (add | floor_cents(.) | tostring) end)
|
|
271
|
+
else
|
|
272
|
+
(.phases[($sel | tonumber)] // null) as $p |
|
|
273
|
+
if $p == null then "-" else (usd_of($p) | floor_cents(.) | if . == null then "-" else tostring end) end
|
|
274
|
+
end
|
|
275
|
+
' 2>/dev/null || echo "-"
|
|
276
|
+
}
|
|
277
|
+
|
|
242
278
|
# Read state, atomically write it back. Atomic = write to .tmp, rename.
|
|
243
279
|
load_state() {
|
|
244
280
|
if [ ! -f "$TRACKER_FILE" ]; then
|
|
@@ -272,10 +308,25 @@ render() {
|
|
|
272
308
|
# consecutive tabs (tab is in the default whitespace IFS class), which
|
|
273
309
|
# silently drops empty middle columns and shifts later fields.
|
|
274
310
|
# Column order: status, started_at, completed_at, id, name,
|
|
275
|
-
# tokens_in, tokens_out, sub_count, meta_keys_csv
|
|
311
|
+
# tokens_in, tokens_out, sub_count, meta_keys_csv, usd
|
|
312
|
+
# The usd column is computed in the same pass with the shared pricing math;
|
|
313
|
+
# a missing/unparseable cost table yields empty usd cells, never a failure.
|
|
314
|
+
local prices_json='{"prices":{}}'
|
|
315
|
+
if [ -f "$COST_TABLE" ]; then
|
|
316
|
+
prices_json=$(cat "$COST_TABLE" 2>/dev/null) || prices_json='{"prices":{}}'
|
|
317
|
+
echo "$prices_json" | jq empty 2>/dev/null || prices_json='{"prices":{}}'
|
|
318
|
+
fi
|
|
276
319
|
local phase_data
|
|
277
|
-
phase_data=$(echo "$state" | jq -r '
|
|
278
|
-
.
|
|
320
|
+
phase_data=$(echo "$state" | jq -r --argjson prices "$prices_json" '
|
|
321
|
+
def usd_of(p): ($prices.prices[p.model // ""] // null) as $rate |
|
|
322
|
+
if $rate == null then null
|
|
323
|
+
else ( ((p.tokens_in // 0) / 1000000) * $rate.inPerMtok
|
|
324
|
+
+ ((p.tokens_cached // 0) / 1000000) * ($rate.cacheReadPerMtok // $rate.inPerMtok)
|
|
325
|
+
+ ((p.tokens_out // 0) / 1000000) * $rate.outPerMtok )
|
|
326
|
+
end;
|
|
327
|
+
.phases // []
|
|
328
|
+
| sort_by(.id | (tonumber? // 9999))
|
|
329
|
+
| .[] | [
|
|
279
330
|
(.status // ""),
|
|
280
331
|
(.started_at // ""),
|
|
281
332
|
(.completed_at // ""),
|
|
@@ -284,20 +335,21 @@ render() {
|
|
|
284
335
|
((.tokens_in // 0) | tostring),
|
|
285
336
|
((.tokens_out // 0) | tostring),
|
|
286
337
|
((.subs // []) | length | tostring),
|
|
287
|
-
((.meta // {}) | keys_unsorted | join(","))
|
|
338
|
+
((.meta // {}) | keys_unsorted | join(",")),
|
|
339
|
+
(usd_of(.) | if . == null then "" else (((. * 100) | floor) / 100 | tostring) end)
|
|
288
340
|
] | join("")
|
|
289
341
|
')
|
|
290
342
|
|
|
291
343
|
local phase_count=0
|
|
292
344
|
local -a row_status=() row_started=() row_completed=() row_id=() row_name=()
|
|
293
|
-
local -a row_tin=() row_tout=() row_subcount=() row_metakeys=()
|
|
345
|
+
local -a row_tin=() row_tout=() row_subcount=() row_metakeys=() row_usd=()
|
|
294
346
|
if [ -n "$phase_data" ]; then
|
|
295
|
-
local _s _st _ed _id _nm _ti _to _sc _mk
|
|
296
|
-
while IFS=$'\x1f' read -r _s _st _ed _id _nm _ti _to _sc _mk; do
|
|
347
|
+
local _s _st _ed _id _nm _ti _to _sc _mk _us
|
|
348
|
+
while IFS=$'\x1f' read -r _s _st _ed _id _nm _ti _to _sc _mk _us; do
|
|
297
349
|
row_status+=("$_s"); row_started+=("$_st"); row_completed+=("$_ed")
|
|
298
350
|
row_id+=("$_id"); row_name+=("$_nm")
|
|
299
351
|
row_tin+=("$_ti"); row_tout+=("$_to")
|
|
300
|
-
row_subcount+=("$_sc"); row_metakeys+=("$_mk")
|
|
352
|
+
row_subcount+=("$_sc"); row_metakeys+=("$_mk"); row_usd+=("$_us")
|
|
301
353
|
phase_count=$((phase_count + 1))
|
|
302
354
|
done <<< "$phase_data"
|
|
303
355
|
fi
|
|
@@ -367,6 +419,16 @@ render() {
|
|
|
367
419
|
local tok_label=""
|
|
368
420
|
[ "$p_ttotal" -gt 0 ] && tok_label="$(format_tokens $p_ttotal) tok"
|
|
369
421
|
|
|
422
|
+
# Est. USD suffix - only when the phase has a priced model AND tokens.
|
|
423
|
+
# "0" (floored below one cent) still renders as ~$0.00 so a priced phase
|
|
424
|
+
# is visibly priced; an unpriceable phase shows no USD at all.
|
|
425
|
+
local p_usd="${row_usd[$i]}"
|
|
426
|
+
local usd_label=""
|
|
427
|
+
if [ -n "$p_usd" ] && [ "$p_ttotal" -gt 0 ]; then
|
|
428
|
+
usd_label=$(printf '~$%.2f' "$p_usd" 2>/dev/null) || usd_label=""
|
|
429
|
+
fi
|
|
430
|
+
[ -n "$usd_label" ] && [ -n "$tok_label" ] && tok_label="${tok_label} · ${usd_label}"
|
|
431
|
+
|
|
370
432
|
local right_block=""
|
|
371
433
|
if [ -n "$el_label" ] && [ -n "$tok_label" ]; then
|
|
372
434
|
right_block=$(printf "%b%s%b ${C_DIM}·${C_RESET} %b%s%b" "$el_color" "$el_label" "$C_RESET" "$C_DIM" "$tok_label" "$C_RESET")
|
|
@@ -385,8 +447,8 @@ render() {
|
|
|
385
447
|
# Same Unit Separator rationale as the phase batch above.
|
|
386
448
|
if [ "$pstatus" = "in_progress" ] && [ -n "${row_metakeys[$i]}" ]; then
|
|
387
449
|
local meta_data key val
|
|
388
|
-
meta_data=$(echo "$state" | jq -r --
|
|
389
|
-
.phases[$
|
|
450
|
+
meta_data=$(echo "$state" | jq -r --arg pid "$pid" '
|
|
451
|
+
([.phases[] | select(.id == $pid)][0].meta) // {} | to_entries[] | [.key, (.value | tostring)] | join("")
|
|
390
452
|
')
|
|
391
453
|
while IFS=$'\x1f' read -r key val; do
|
|
392
454
|
[ -z "$key" ] && continue
|
|
@@ -399,8 +461,8 @@ render() {
|
|
|
399
461
|
local sub_count="${row_subcount[$i]}"
|
|
400
462
|
if [ "$sub_count" != "0" ] && [ "$sub_count" -gt 0 ] 2>/dev/null; then
|
|
401
463
|
local sub_data
|
|
402
|
-
sub_data=$(echo "$state" | jq -r --
|
|
403
|
-
.phases[$
|
|
464
|
+
sub_data=$(echo "$state" | jq -r --arg pid "$pid" '
|
|
465
|
+
([.phases[] | select(.id == $pid)][0].subs) // [] | .[] | [(.id // ""), (.name // ""), (.status // "")] | join("")
|
|
404
466
|
')
|
|
405
467
|
local j=0
|
|
406
468
|
local sid sname sstatus sg connector
|
|
@@ -417,14 +479,22 @@ render() {
|
|
|
417
479
|
done
|
|
418
480
|
fi
|
|
419
481
|
|
|
420
|
-
# Total
|
|
421
|
-
local total_tokens
|
|
482
|
+
# Total footer - tokens + est. USD (priceable phases) + cached tokens.
|
|
483
|
+
local total_tokens total_cached
|
|
422
484
|
total_tokens=$(echo "$state" | jq '[.phases[] | ((.tokens_in // 0) + (.tokens_out // 0))] | add // 0')
|
|
485
|
+
total_cached=$(echo "$state" | jq '[.phases[] | (.tokens_cached // 0)] | add // 0')
|
|
423
486
|
if [ "$total_tokens" -gt 0 ]; then
|
|
424
|
-
local total_label
|
|
487
|
+
local total_label total_usd total_suffix=""
|
|
425
488
|
total_label=$(format_tokens "$total_tokens")
|
|
489
|
+
total_usd=$(phase_usd total)
|
|
490
|
+
if [ -n "$total_usd" ] && [ "$total_usd" != "-" ]; then
|
|
491
|
+
total_suffix=$(printf ' · ~$%.2f' "$total_usd" 2>/dev/null) || total_suffix=""
|
|
492
|
+
fi
|
|
493
|
+
if [ "$total_cached" -gt 0 ] 2>/dev/null; then
|
|
494
|
+
total_suffix="${total_suffix} ($(format_tokens "$total_cached") cached)"
|
|
495
|
+
fi
|
|
426
496
|
printf "${C_BOLD}${C_BLUE}│${C_RESET} ${C_DIM}%s${C_RESET}\n" "$(printf '%*s' 56 '' | tr ' ' '─')"
|
|
427
|
-
printf "${C_BOLD}${C_BLUE}│${C_RESET} ${C_BOLD}Total${C_RESET} ${C_BOLD}${C_CYAN}%s tok${C_RESET}\n" "$total_label"
|
|
497
|
+
printf "${C_BOLD}${C_BLUE}│${C_RESET} ${C_BOLD}Total${C_RESET} ${C_BOLD}${C_CYAN}%s tok%s${C_RESET}\n" "$total_label" "$total_suffix"
|
|
428
498
|
fi
|
|
429
499
|
|
|
430
500
|
printf "${C_BOLD}${C_BLUE}╰────────────────────────────────────────────────────────────╯${C_RESET}\n"
|
|
@@ -459,8 +529,14 @@ case "$ACTION" in
|
|
|
459
529
|
[ "$#" -ge 2 ] || { echo "add needs <phase_id> <name>" >&2; exit 64; }
|
|
460
530
|
PID="$1"; PNAME="$2"
|
|
461
531
|
state=$(load_state)
|
|
462
|
-
|
|
463
|
-
|
|
532
|
+
# Idempotent: a phase id that already exists is a no-op (name, status, and
|
|
533
|
+
# token history are preserved). This is what lets continuation commands
|
|
534
|
+
# (/multi-agent:finish, resume) re-declare their phase set against a
|
|
535
|
+
# pre-existing tracker without duplicating or resetting tiles.
|
|
536
|
+
new=$(echo "$state" | jq --arg id "$PID" --arg name "$PNAME" '
|
|
537
|
+
if any(.phases[]?; .id == $id) then .
|
|
538
|
+
else .phases += [{"id":$id,"name":$name,"status":"pending","subs":[]}]
|
|
539
|
+
end')
|
|
464
540
|
save_state "$new"
|
|
465
541
|
render
|
|
466
542
|
;;
|
|
@@ -581,12 +657,48 @@ case "$ACTION" in
|
|
|
581
657
|
fi
|
|
582
658
|
;;
|
|
583
659
|
|
|
660
|
+
now)
|
|
661
|
+
need_jq
|
|
662
|
+
[ "$#" -ge 2 ] || { echo "now needs <phase_id> <text>" >&2; exit 64; }
|
|
663
|
+
PID="$1"; TEXT="$2"
|
|
664
|
+
# Live current-action line for the active phase. Quiet by construction:
|
|
665
|
+
# progress lines are frequent, so `now` never renders - the card picks the
|
|
666
|
+
# value up at the next boundary render/update. Truncated to 60 chars.
|
|
667
|
+
if [ "${#TEXT}" -gt 60 ]; then
|
|
668
|
+
TEXT="${TEXT:0:59}…"
|
|
669
|
+
fi
|
|
670
|
+
state=$(load_state)
|
|
671
|
+
new=$(echo "$state" | jq --arg id "$PID" --arg v "$TEXT" '
|
|
672
|
+
.phases |= map(
|
|
673
|
+
if .id == $id then .meta = ((.meta // {}) + {"Now": $v}) else . end
|
|
674
|
+
)')
|
|
675
|
+
save_state "$new"
|
|
676
|
+
emit_otel_span "phase.now" "$PID" "$TEXT" "{}"
|
|
677
|
+
;;
|
|
678
|
+
|
|
679
|
+
cost)
|
|
680
|
+
need_jq
|
|
681
|
+
[ "$#" -ge 1 ] || { echo "cost needs <phase_id>|total" >&2; exit 64; }
|
|
682
|
+
SEL="$1"
|
|
683
|
+
if [ "$SEL" = "total" ]; then
|
|
684
|
+
phase_usd total
|
|
685
|
+
else
|
|
686
|
+
# Resolve phase id -> array index (ids are strings like "3").
|
|
687
|
+
IDX=$(load_state | jq -r --arg id "$SEL" '[.phases[]?.id] | index($id) // "-"')
|
|
688
|
+
if [ "$IDX" = "-" ] || [ -z "$IDX" ]; then
|
|
689
|
+
echo "-"
|
|
690
|
+
else
|
|
691
|
+
phase_usd "$IDX"
|
|
692
|
+
fi
|
|
693
|
+
fi
|
|
694
|
+
;;
|
|
695
|
+
|
|
584
696
|
render)
|
|
585
697
|
render
|
|
586
698
|
;;
|
|
587
699
|
|
|
588
700
|
*)
|
|
589
|
-
echo "phase-tracker: unknown action '$ACTION' (use init|add|update|sub|tokens|model|meta|render)" >&2
|
|
701
|
+
echo "phase-tracker: unknown action '$ACTION' (use init|add|update|sub|tokens|model|meta|now|cost|render)" >&2
|
|
590
702
|
exit 64
|
|
591
703
|
;;
|
|
592
704
|
esac
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# smoke-channels-approval-gate.sh - contract for the channels Step 6 body preview + approval gate.
|
|
3
|
+
#
|
|
4
|
+
# Verifies:
|
|
5
|
+
# 1. channels.md has the Step 6 gate section with the four decision options.
|
|
6
|
+
# 2. The two (and only two) bypass cases are documented: autopilot + --dry-run.
|
|
7
|
+
# 3. Step 7 dispatch is gated on the Step 6 approval.
|
|
8
|
+
# 4. Byte-identical post rule + re-approval-on-regeneration rule present.
|
|
9
|
+
# 5. Phase 7 Step 1.5 (GitHub issue comment) references the same gate.
|
|
10
|
+
# 6. Required rules include the approval-before-post bullet.
|
|
11
|
+
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
15
|
+
ROOT="$(cd "$HERE/../.." && pwd)"
|
|
16
|
+
CHANNELS="$ROOT/pipeline/commands/multi-agent/channels.md"
|
|
17
|
+
PHASE7="$ROOT/pipeline/commands/multi-agent/refs/phases/phase-7-report.md"
|
|
18
|
+
|
|
19
|
+
PASS=0
|
|
20
|
+
FAIL=0
|
|
21
|
+
pass() { PASS=$((PASS+1)); echo " ✓ $1"; }
|
|
22
|
+
fail() { FAIL=$((FAIL+1)); echo " ✗ $1"; }
|
|
23
|
+
|
|
24
|
+
for f in "$CHANNELS" "$PHASE7"; do
|
|
25
|
+
[ -f "$f" ] || { echo "error: missing $f" >&2; exit 1; }
|
|
26
|
+
done
|
|
27
|
+
|
|
28
|
+
echo "→ 1. Step 6 gate section + decision options"
|
|
29
|
+
grep -Fq "Step 6 - Body preview + approval gate" "$CHANNELS" && pass "gate section present" || fail "gate section missing"
|
|
30
|
+
for opt in "Approve & post" "Edit" "Skip channels" "Cancel"; do
|
|
31
|
+
grep -Fq "$opt" "$CHANNELS" && pass "option: $opt" || fail "option missing: $opt"
|
|
32
|
+
done
|
|
33
|
+
|
|
34
|
+
echo ""
|
|
35
|
+
echo "→ 2. bypass cases are exactly autopilot + --dry-run"
|
|
36
|
+
grep -Fq "Skipped in exactly two cases" "$CHANNELS" && pass "two-case rule stated" || fail "two-case rule missing"
|
|
37
|
+
grep -Fq "MULTI_AGENT_AUTOPILOT=1" "$CHANNELS" && pass "autopilot bypass documented" || fail "autopilot bypass missing"
|
|
38
|
+
grep -Eq 'dry-run.*(redundant|without posting)' "$CHANNELS" && pass "dry-run bypass documented" || fail "dry-run bypass missing"
|
|
39
|
+
|
|
40
|
+
echo ""
|
|
41
|
+
echo "→ 3. dispatch gated on approval"
|
|
42
|
+
grep -Fq "NEVER starts before the Step 6 gate resolves" "$CHANNELS" && pass "Step 6 gated" || fail "Step 6 gate wiring missing"
|
|
43
|
+
|
|
44
|
+
echo ""
|
|
45
|
+
echo "→ 4. byte-identical + re-approval rules"
|
|
46
|
+
grep -Fq "byte-identical to the approved preview" "$CHANNELS" && pass "byte-identical rule" || fail "byte-identical rule missing"
|
|
47
|
+
grep -Fq "invalidates the approval" "$CHANNELS" && pass "re-approval on regeneration" || fail "re-approval rule missing"
|
|
48
|
+
|
|
49
|
+
echo ""
|
|
50
|
+
echo "→ 5. Phase 7 issue-comment path uses the same gate"
|
|
51
|
+
grep -Fq "same preview + approval gate as channels Step 6" "$PHASE7" && pass "Phase 7 Step 1.5 gated" || fail "Phase 7 Step 1.5 gate reference missing"
|
|
52
|
+
grep -Fq "Autopilot posts directly" "$PHASE7" && pass "Phase 7 autopilot bypass" || fail "Phase 7 autopilot bypass missing"
|
|
53
|
+
|
|
54
|
+
echo ""
|
|
55
|
+
echo "→ 6. required-rules bullet"
|
|
56
|
+
grep -Fq "**Approval before post**" "$CHANNELS" && pass "required-rules bullet present" || fail "required-rules bullet missing"
|
|
57
|
+
|
|
58
|
+
echo ""
|
|
59
|
+
echo "══ channels-approval-gate smoke: $PASS passed, $FAIL failed ══"
|
|
60
|
+
[ "$FAIL" -eq 0 ]
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# smoke-generate-issue.sh - contract for /multi-agent:generate-task + generate-bug.
|
|
3
|
+
#
|
|
4
|
+
# Verifies:
|
|
5
|
+
# 1. All 5 files exist (2 commands, shared ref, 2 Copilot SKILL.md).
|
|
6
|
+
# 2. Frontmatter keys present on both surfaces.
|
|
7
|
+
# 3. Commands delegate to refs/generate-issue.md and declare their ISSUE_TYPE.
|
|
8
|
+
# 4. Approval gate documented and marked unskippable in the ref.
|
|
9
|
+
# 5. Convention mining + createmeta + Agile sprint endpoints present.
|
|
10
|
+
# 6. UTF-8 verbatim POST path (--rawfile + --data-binary) present.
|
|
11
|
+
# 7. Profile section vocab: Task (Goal/Scope/Acceptance), Bug (Steps/Expected/Actual/Environment).
|
|
12
|
+
# 8. Registration: dispatcher rows, inventory lists, count consistency (37).
|
|
13
|
+
# 9. Forbidden strings absent (em-dash, section sign, the M-word, unicode_escape).
|
|
14
|
+
|
|
15
|
+
set -euo pipefail
|
|
16
|
+
|
|
17
|
+
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
18
|
+
ROOT="$(cd "$HERE/../.." && pwd)"
|
|
19
|
+
CMD_TASK="$ROOT/pipeline/commands/multi-agent/generate-task.md"
|
|
20
|
+
CMD_BUG="$ROOT/pipeline/commands/multi-agent/generate-bug.md"
|
|
21
|
+
REF="$ROOT/pipeline/commands/multi-agent/refs/generate-issue.md"
|
|
22
|
+
SKILL_TASK="$ROOT/pipeline/skills/shared/core/multi-agent-generate-task/SKILL.md"
|
|
23
|
+
SKILL_BUG="$ROOT/pipeline/skills/shared/core/multi-agent-generate-bug/SKILL.md"
|
|
24
|
+
DISPATCHER="$ROOT/pipeline/commands/multi-agent.md"
|
|
25
|
+
CONTRACT="$ROOT/pipeline/commands/multi-agent/refs/cross-cli-contract.md"
|
|
26
|
+
SYNC="$ROOT/pipeline/commands/multi-agent/sync.md"
|
|
27
|
+
HELP="$ROOT/pipeline/commands/multi-agent/help.md"
|
|
28
|
+
|
|
29
|
+
PASS=0
|
|
30
|
+
FAIL=0
|
|
31
|
+
pass() { PASS=$((PASS+1)); echo " ✓ $1"; }
|
|
32
|
+
fail() { FAIL=$((FAIL+1)); echo " ✗ $1"; }
|
|
33
|
+
|
|
34
|
+
echo "→ 1. files exist"
|
|
35
|
+
for f in "$CMD_TASK" "$CMD_BUG" "$REF" "$SKILL_TASK" "$SKILL_BUG"; do
|
|
36
|
+
if [ -f "$f" ]; then pass "exists: ${f#$ROOT/}"; else fail "missing: ${f#$ROOT/}"; fi
|
|
37
|
+
done
|
|
38
|
+
[ "$FAIL" -eq 0 ] || { echo "══ generate-issue smoke: $PASS passed, $FAIL failed ══"; exit 1; }
|
|
39
|
+
|
|
40
|
+
echo ""
|
|
41
|
+
echo "→ 2. frontmatter keys"
|
|
42
|
+
for f in "$CMD_TASK" "$CMD_BUG"; do
|
|
43
|
+
head -5 "$f" | grep -q '^description:' && pass "description: ${f##*/}" || fail "description missing: ${f##*/}"
|
|
44
|
+
head -5 "$f" | grep -q '^argument-hint:' && pass "argument-hint: ${f##*/}" || fail "argument-hint missing: ${f##*/}"
|
|
45
|
+
done
|
|
46
|
+
for f in "$SKILL_TASK" "$SKILL_BUG"; do
|
|
47
|
+
head -8 "$f" | grep -q '^name: multi-agent-generate-' && pass "name: ${f%/*}" || fail "name missing: $f"
|
|
48
|
+
head -8 "$f" | grep -q '^language: en' && pass "language en" || fail "language missing: $f"
|
|
49
|
+
head -8 "$f" | grep -q '^user-invocable: true' && pass "user-invocable" || fail "user-invocable missing: $f"
|
|
50
|
+
done
|
|
51
|
+
|
|
52
|
+
echo ""
|
|
53
|
+
echo "→ 3. commands delegate to the shared ref with a profile"
|
|
54
|
+
for f in "$CMD_TASK" "$CMD_BUG"; do
|
|
55
|
+
grep -Fq "refs/generate-issue.md" "$f" && pass "ref link: ${f##*/}" || fail "ref link missing: ${f##*/}"
|
|
56
|
+
done
|
|
57
|
+
grep -Fq '`ISSUE_TYPE` | `Task`' "$CMD_TASK" && pass "ISSUE_TYPE=Task declared" || fail "ISSUE_TYPE=Task missing"
|
|
58
|
+
grep -Fq '`ISSUE_TYPE` | `Bug`' "$CMD_BUG" && pass "ISSUE_TYPE=Bug declared" || fail "ISSUE_TYPE=Bug missing"
|
|
59
|
+
|
|
60
|
+
echo ""
|
|
61
|
+
echo "→ 4. approval gate documented and unskippable"
|
|
62
|
+
grep -Fq "Full preview + approval gate (never skipped)" "$REF" && pass "gate section present" || fail "gate section missing"
|
|
63
|
+
for marker in "Approve" "Edit" "Cancel" "no bypass" "Nothing was created"; do
|
|
64
|
+
grep -Fqi "$marker" "$REF" && pass "gate marker: $marker" || fail "gate marker missing: $marker"
|
|
65
|
+
done
|
|
66
|
+
grep -Fq "no autopilot variant" "$REF" && pass "no-autopilot rule" || fail "no-autopilot rule missing"
|
|
67
|
+
|
|
68
|
+
echo ""
|
|
69
|
+
echo "→ 5. mining + createmeta + agile endpoints"
|
|
70
|
+
for marker in "rest/api/2/search" "rest/api/2/issue/createmeta" "rest/agile/1.0/board" "sprint?state=active" 'sprint/${SPRINT_ID}/issue' "remotelink" "attachments"; do
|
|
71
|
+
grep -Fq "$marker" "$REF" && pass "endpoint marker: $marker" || fail "endpoint marker missing: $marker"
|
|
72
|
+
done
|
|
73
|
+
grep -Fq "gh-sprint" "$REF" && pass "generic sprint-field detection" || fail "generic sprint-field detection missing"
|
|
74
|
+
|
|
75
|
+
echo ""
|
|
76
|
+
echo "→ 6. UTF-8 verbatim POST path"
|
|
77
|
+
grep -Fq -- "--rawfile" "$REF" && pass "--rawfile present" || fail "--rawfile missing"
|
|
78
|
+
grep -Fq -- "--data-binary" "$REF" && pass "--data-binary present" || fail "--data-binary missing"
|
|
79
|
+
grep -Fqi "humanizer" "$REF" && pass "humanizer pass referenced" || fail "humanizer missing"
|
|
80
|
+
|
|
81
|
+
echo ""
|
|
82
|
+
echo "→ 7. profile section vocabulary"
|
|
83
|
+
for s in "Goal" "Scope" "Acceptance Criteria"; do
|
|
84
|
+
grep -Fq "$s" "$CMD_TASK" && pass "task section: $s" || fail "task section missing: $s"
|
|
85
|
+
done
|
|
86
|
+
for s in "Steps to Reproduce" "Expected Result" "Actual Result" "Environment"; do
|
|
87
|
+
grep -Fq "$s" "$CMD_BUG" && pass "bug section: $s" || fail "bug section missing: $s"
|
|
88
|
+
done
|
|
89
|
+
|
|
90
|
+
echo ""
|
|
91
|
+
echo "→ 8. registration + count consistency (37)"
|
|
92
|
+
grep -Fq '| `generate-task' "$DISPATCHER" && pass "dispatcher routing: generate-task" || fail "dispatcher routing missing: generate-task"
|
|
93
|
+
grep -Fq '| `generate-bug' "$DISPATCHER" && pass "dispatcher routing: generate-bug" || fail "dispatcher routing missing: generate-bug"
|
|
94
|
+
grep -Fq "generate-task.md" "$DISPATCHER" && pass "dispatcher loading row" || fail "dispatcher loading row missing"
|
|
95
|
+
grep -Fq "## 1. Command Inventory (37 commands)" "$CONTRACT" && pass "contract count = 37" || fail "contract count wrong"
|
|
96
|
+
grep -Eq '(^|[ ,])generate-bug, generate-task([ ,]|$)' "$CONTRACT" && pass "contract list has both" || fail "contract list missing entries"
|
|
97
|
+
grep -Fq "**37 commands are synced**" "$SYNC" && pass "sync count = 37" || fail "sync count wrong"
|
|
98
|
+
SYNC_SKILL="$ROOT/pipeline/skills/shared/core/multi-agent-sync/SKILL.md"
|
|
99
|
+
grep -Fq "**37 commands are synced**" "$SYNC_SKILL" && pass "sync SKILL count = 37" || fail "sync SKILL count wrong (Copilot parity)"
|
|
100
|
+
grep -Fq "generate-task" "$ROOT/pipeline/skills/shared/core/multi-agent-help/SKILL.md" && pass "help SKILL lists generators" || fail "help SKILL missing generators (Copilot parity)"
|
|
101
|
+
grep -c "generate-task" "$HELP" | awk '{exit !($1>=2)}' && pass "help.md has EN + TR entries" || fail "help.md entries incomplete"
|
|
102
|
+
|
|
103
|
+
echo ""
|
|
104
|
+
echo "→ 9. forbidden strings absent"
|
|
105
|
+
MWORD="MAN""DATORY"
|
|
106
|
+
for f in "$CMD_TASK" "$CMD_BUG" "$REF" "$SKILL_TASK" "$SKILL_BUG"; do
|
|
107
|
+
if grep -q $'—\|§\|…' "$f"; then fail "forbidden unicode punctuation in ${f##*/}"; else pass "punctuation clean: ${f##*/}"; fi
|
|
108
|
+
if grep -q "$MWORD" "$f"; then fail "forbidden word in ${f##*/}"; else pass "vocabulary clean: ${f##*/}"; fi
|
|
109
|
+
if grep -q "unicode_escape" "$f" && ! grep -q "Never round-trip" "$f"; then fail "unicode_escape misuse in ${f##*/}"; else pass "encoding rule clean: ${f##*/}"; fi
|
|
110
|
+
done
|
|
111
|
+
|
|
112
|
+
echo ""
|
|
113
|
+
echo "══ generate-issue smoke: $PASS passed, $FAIL failed ══"
|
|
114
|
+
[ "$FAIL" -eq 0 ]
|
|
@@ -250,6 +250,75 @@ STATE_LIVE="$HOME/.claude/logs/multi-agent/TOK-LIVE/tracker-state.json"
|
|
|
250
250
|
TIN_LIVE=$(jq -r '.phases[0].tokens_in // 0' "$STATE_LIVE")
|
|
251
251
|
[ "$TIN_LIVE" = "600" ] && pass "TRACKER_QUIET still accumulates state (500+100=600)" || fail "quiet mode broke accumulation (got $TIN_LIVE)"
|
|
252
252
|
|
|
253
|
+
echo ""
|
|
254
|
+
echo "→ 20. now action: quiet, truncating current-action line"
|
|
255
|
+
run_plain init NOW-1 >/dev/null
|
|
256
|
+
run_plain add 3 "Dev" >/dev/null
|
|
257
|
+
run_plain update 3 in_progress >/dev/null
|
|
258
|
+
NOW_OUT=$(run_plain now 3 "editing TopBarView.swift")
|
|
259
|
+
if [ -n "$NOW_OUT" ]; then
|
|
260
|
+
fail "now must not render (got: $NOW_OUT)"
|
|
261
|
+
else
|
|
262
|
+
pass "now is quiet (no stdout)"
|
|
263
|
+
fi
|
|
264
|
+
STATE_NOW="$HOME/.claude/logs/multi-agent/NOW-1/tracker-state.json"
|
|
265
|
+
NOW_VAL=$(jq -r '.phases[0].meta.Now' "$STATE_NOW")
|
|
266
|
+
[ "$NOW_VAL" = "editing TopBarView.swift" ] && pass "now stored in meta.Now" || fail "meta.Now wrong: $NOW_VAL"
|
|
267
|
+
run_plain now 3 "this is a deliberately very long current action description that exceeds sixty characters"
|
|
268
|
+
NOW_LEN=$(jq -r '.phases[0].meta.Now | length' "$STATE_NOW")
|
|
269
|
+
[ "$NOW_LEN" = "60" ] && pass "now truncates to 60 chars" || fail "expected 60 chars, got $NOW_LEN"
|
|
270
|
+
RENDER_NOW=$(run_plain render)
|
|
271
|
+
echo "$RENDER_NOW" | grep -q "Now:" && pass "render shows Now: line for active phase" || fail "render missing Now: line"
|
|
272
|
+
|
|
273
|
+
echo ""
|
|
274
|
+
echo "→ 21. add is idempotent (continuation contract)"
|
|
275
|
+
run_plain tokens 3 1000 200 >/dev/null
|
|
276
|
+
run_plain add 3 "DevRenamed" >/dev/null
|
|
277
|
+
CNT=$(jq '.phases | length' "$STATE_NOW")
|
|
278
|
+
[ "$CNT" = "1" ] && pass "duplicate add does not append" || fail "expected 1 phase, got $CNT"
|
|
279
|
+
NAME3=$(jq -r '.phases[0].name' "$STATE_NOW")
|
|
280
|
+
[ "$NAME3" = "Dev" ] && pass "duplicate add preserves original name" || fail "name overwritten: $NAME3"
|
|
281
|
+
TIN3=$(jq -r '.phases[0].tokens_in' "$STATE_NOW")
|
|
282
|
+
[ "$TIN3" = "1000" ] && pass "duplicate add preserves token history" || fail "tokens lost: $TIN3"
|
|
283
|
+
|
|
284
|
+
echo ""
|
|
285
|
+
echo "→ 22. cost action prices via cost-table.json"
|
|
286
|
+
run_plain init COST-1 >/dev/null
|
|
287
|
+
run_plain add 3 "Dev" >/dev/null
|
|
288
|
+
run_plain update 3 in_progress >/dev/null
|
|
289
|
+
TRACKER_QUIET=1 run_plain model 3 opus >/dev/null
|
|
290
|
+
TRACKER_QUIET=1 run_plain tokens 3 100000 20000 >/dev/null
|
|
291
|
+
# opus: 0.1*5 + 0.02*25 = 0.5 + 0.5 = 1.00
|
|
292
|
+
COST3=$(run_plain cost 3)
|
|
293
|
+
[ "$COST3" = "1" ] && pass "cost 3 = 1 (opus 100k in + 20k out)" || fail "expected 1, got $COST3"
|
|
294
|
+
TRACKER_QUIET=1 run_plain tokens 3 0 0 50000 >/dev/null
|
|
295
|
+
# + 50k cached * 0.5/M = 0.025 -> total 1.025 -> floored 1.02
|
|
296
|
+
COST3C=$(run_plain cost 3)
|
|
297
|
+
[ "$COST3C" = "1.02" ] && pass "cached tokens priced at cacheReadPerMtok (1.02)" || fail "expected 1.02, got $COST3C"
|
|
298
|
+
COSTTOT=$(run_plain cost total)
|
|
299
|
+
[ "$COSTTOT" = "1.02" ] && pass "cost total sums priceable phases" || fail "expected 1.02, got $COSTTOT"
|
|
300
|
+
run_plain add 4 "Review" >/dev/null
|
|
301
|
+
TRACKER_QUIET=1 run_plain tokens 4 5000 1000 >/dev/null
|
|
302
|
+
COST4=$(run_plain cost 4)
|
|
303
|
+
[ "$COST4" = "-" ] && pass "cost is '-' for a phase without a model" || fail "expected -, got $COST4"
|
|
304
|
+
COSTBAD=$(run_plain cost 9)
|
|
305
|
+
[ "$COSTBAD" = "-" ] && pass "cost is '-' for unknown phase id" || fail "expected -, got $COSTBAD"
|
|
306
|
+
|
|
307
|
+
echo ""
|
|
308
|
+
echo "→ 23. render shows per-tile USD and cached footer"
|
|
309
|
+
RENDER_COST=$(run_plain render)
|
|
310
|
+
echo "$RENDER_COST" | grep -q '~\$1.02' && pass "tile shows ~\$ estimate for priced phase" || fail "render missing tile USD"
|
|
311
|
+
echo "$RENDER_COST" | grep -q "cached" && pass "footer shows cached tokens" || fail "render missing cached footer"
|
|
312
|
+
echo "$RENDER_COST" | grep -qE "Total.*~\\\$" && pass "footer shows total USD" || fail "render missing total USD"
|
|
313
|
+
|
|
314
|
+
echo ""
|
|
315
|
+
echo "→ 24. render sorts phases by numeric id (continuation ordering)"
|
|
316
|
+
run_plain init ORDER-1 >/dev/null
|
|
317
|
+
for pp in "0:Init" "5:Test"; do run_plain add "${pp%%:*}" "${pp#*:}" >/dev/null; done
|
|
318
|
+
for pp in "4:Review" "6:Commit"; do run_plain add "${pp%%:*}" "${pp#*:}" >/dev/null; done
|
|
319
|
+
ORDER_OUT=$(run_plain render | grep -oE "Phase [0-9]+" | tr -d 'Phase ' | tr '\n' ',')
|
|
320
|
+
[ "$ORDER_OUT" = "0,4,5,6," ] && pass "phases render sorted by id (0,4,5,6)" || fail "order wrong: $ORDER_OUT"
|
|
321
|
+
|
|
253
322
|
echo ""
|
|
254
323
|
echo "══ phase-tracker smoke: $PASS passed, $FAIL failed ══"
|
|
255
324
|
[ "$FAIL" -eq 0 ]
|
|
@@ -113,6 +113,15 @@ if [ "$ADOPTED" -eq 0 ]; then
|
|
|
113
113
|
warn "no phases have adopted yet - this is expected until WS-4/5/6 land"
|
|
114
114
|
fi
|
|
115
115
|
|
|
116
|
+
echo ""
|
|
117
|
+
echo "→ Live mirror cross-link (tracker coupling)"
|
|
118
|
+
if grep -q "Live mirror to the phase tracker" "$CONTRACT" \
|
|
119
|
+
&& grep -q "phase-tracker.sh now" "$CONTRACT"; then
|
|
120
|
+
pass "contract cross-links the tracker mirror rule"
|
|
121
|
+
else
|
|
122
|
+
fail "contract missing the live-mirror cross-link to tracker-contract.md"
|
|
123
|
+
fi
|
|
124
|
+
|
|
116
125
|
echo ""
|
|
117
126
|
echo "══ progress-contract smoke: $PASS passed, $WARN warnings, $FAIL failed ══"
|
|
118
127
|
[ "$FAIL" -eq 0 ]
|
|
@@ -57,6 +57,7 @@ MODE_ENTRY_DOCS=(
|
|
|
57
57
|
"pipeline/commands/multi-agent/dev-autopilot.md"
|
|
58
58
|
"pipeline/commands/multi-agent/dev-local.md"
|
|
59
59
|
"pipeline/commands/multi-agent/dev-local-autopilot.md"
|
|
60
|
+
"pipeline/commands/multi-agent/finish.md"
|
|
60
61
|
)
|
|
61
62
|
for rel in "${MODE_ENTRY_DOCS[@]}"; do
|
|
62
63
|
abs="$ROOT/$rel"
|