@cxi-lmai/ci-agent-platform 3.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +15 -1
  2. package/package.json +2 -2
  3. package/payload/INSTALL.md +6 -3
  4. package/payload/agents/agent-architect.md +1 -1
  5. package/payload/agents/code-reviewer.md +1 -1
  6. package/payload/agents/codebase-auditor.md +1 -1
  7. package/payload/agents/coder.md +3 -3
  8. package/payload/agents/decomposer.md +1 -1
  9. package/payload/agents/docs-sync.md +1 -1
  10. package/payload/agents/e2e-test-writer.md +1 -1
  11. package/payload/agents/performance-reviewer.md +1 -1
  12. package/payload/agents/release-mr.md +1 -1
  13. package/payload/agents/security-reviewer.md +1 -1
  14. package/payload/agents/test-fix.md +4 -4
  15. package/payload/agents/test-writer.md +3 -3
  16. package/payload/agents-omp/agent-architect.md +101 -0
  17. package/payload/agents-omp/code-reviewer.md +86 -0
  18. package/payload/agents-omp/codebase-auditor.md +73 -0
  19. package/payload/agents-omp/coder.md +57 -0
  20. package/payload/agents-omp/decomposer.md +70 -0
  21. package/payload/agents-omp/docs-sync.md +114 -0
  22. package/payload/agents-omp/e2e-test-writer.md +47 -0
  23. package/payload/agents-omp/migration-reviewer.md +99 -0
  24. package/payload/agents-omp/orchestrator.md +50 -0
  25. package/payload/agents-omp/performance-reviewer.md +81 -0
  26. package/payload/agents-omp/postmortem.md +82 -0
  27. package/payload/agents-omp/release-mr.md +274 -0
  28. package/payload/agents-omp/security-reviewer.md +121 -0
  29. package/payload/agents-omp/test-fix.md +33 -0
  30. package/payload/agents-omp/test-writer.md +39 -0
  31. package/payload/ci-templates/claude-pipeline.gitlab-ci.yml +10 -7
  32. package/payload/ci-templates/github/claude-issue-pipeline.yml +1 -1
  33. package/payload/ci-templates/github/claude-pipeline.yml +2 -2
  34. package/payload/ci-templates/github/claude-test-fix.yml +1 -1
  35. package/payload/ci-templates/scripts/code.sh +9 -8
  36. package/payload/ci-templates/scripts/lib/pipeline-common.sh +138 -10
  37. package/payload/ci-templates/scripts/lib/usage-capture-omp.sh +117 -0
  38. package/payload/ci-templates/scripts/orchestrate.sh +1 -1
  39. package/payload/ci-templates/scripts/postmortem.sh +1 -1
  40. package/payload/ci-templates/scripts/review-fix.sh +1 -1
  41. package/payload/ci-templates/scripts/review.sh +1 -1
  42. package/payload/ci-templates/scripts/test-fix.sh +1 -1
  43. package/payload/skills/fix-review-findings/SKILL.md +1 -1
  44. package/payload/skills/fix-tests/SKILL.md +2 -2
  45. package/payload/skills/implement-issue/SKILL.md +1 -1
  46. package/payload/skills/init-pipeline-config/SKILL.md +4 -4
  47. package/payload/skills/postmortem-mr/SKILL.md +1 -1
  48. package/payload/skills/review-mr/SKILL.md +1 -1
  49. package/payload/skills/triage-issue/SKILL.md +2 -2
@@ -9,6 +9,8 @@ set -u
9
9
  SCRIPT_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
10
  # shellcheck source=./usage-capture.sh
11
11
  source "$SCRIPT_LIB_DIR/usage-capture.sh"
12
+ # shellcheck source=./usage-capture-omp.sh
13
+ source "$SCRIPT_LIB_DIR/usage-capture-omp.sh"
12
14
  # shellcheck source=./platform.sh
13
15
  source "$SCRIPT_LIB_DIR/platform.sh"
14
16
 
@@ -27,9 +29,18 @@ pipe_defaults() {
27
29
  : "${PIPE_FIX_LOOP_CAP:=2}"
28
30
  : "${PIPE_CODER_CAP:=3}"
29
31
  : "${PIPE_ISSUE_SCAN:=20}"
30
- : "${PIPE_MODEL_TRIAGE:=haiku}"
31
- : "${PIPE_MODEL_CODE:=sonnet}"
32
- : "${PIPE_MODEL_REVIEW:=sonnet}"
32
+ : "${PIPE_HARNESS:=claude}"
33
+ # Model defaults track the active harness. Each PIPE_MODEL_* variable remains
34
+ # explicitly overridable.
35
+ if [ "$PIPE_HARNESS" = "omp" ]; then
36
+ : "${PIPE_MODEL_TRIAGE:=openrouter/anthropic/claude-haiku-4-5}"
37
+ : "${PIPE_MODEL_CODE:=openrouter/anthropic/claude-sonnet-5-0}"
38
+ : "${PIPE_MODEL_REVIEW:=openrouter/anthropic/claude-sonnet-5-0}"
39
+ else
40
+ : "${PIPE_MODEL_TRIAGE:=haiku}"
41
+ : "${PIPE_MODEL_CODE:=claude-sonnet-5-0}"
42
+ : "${PIPE_MODEL_REVIEW:=claude-sonnet-5-0}"
43
+ fi
33
44
  : "${PIPE_COMMIT_TESTFIX:=Fix test errors}"
34
45
  : "${PIPE_COMMIT_REVIEWFIX:=Fix review findings}"
35
46
  : "${PIPE_COMMIT_COVERAGE:=Add coverage tests}"
@@ -64,7 +75,7 @@ pipe_defaults() {
64
75
  PIPE_LABEL_STUCK PIPE_LABEL_BLOCKER PIPE_LABEL_BLOCKED PIPE_LABEL_DECOMPOSED \
65
76
  PIPE_RESULT_REVIEW PIPE_RESULT_REVIEWFIX PIPE_VERIFY_CMD PIPE_COMPILE_CMD \
66
77
  PIPE_TEST_REPORT_GLOB PIPE_COVERAGE_SIGNAL PIPE_SPEC_TEMPLATE_PATH \
67
- PIPE_MODEL_TRIAGE PIPE_MODEL_CODE PIPE_MODEL_REVIEW
78
+ PIPE_HARNESS PIPE_MODEL_TRIAGE PIPE_MODEL_CODE PIPE_MODEL_REVIEW
68
79
  }
69
80
 
70
81
  pipe_log() { echo "[pipeline] $*"; }
@@ -131,14 +142,22 @@ pipe_count_fix_commits() {
131
142
 
132
143
  pipe_agent_secret_var() {
133
144
  # Return success when an exported variable looks credential-bearing and is
134
- # not explicitly required by the Claude API itself or allowlisted for a
135
- # project verification command. The allowlist is comma/space separated.
145
+ # not explicitly required by the active harness's own auth or allowlisted
146
+ # for a project verification command. The allowlist is comma/space
147
+ # separated.
136
148
  local name="$1" allow=" ${PIPE_AGENT_ENV_ALLOWLIST:-} "
137
149
  allow=" ${allow//,/ } "
138
- # The Claude process must keep its own credential regardless of which of the
139
- # three supported auth variables the project uses.
140
- case "$name" in
141
- ANTHROPIC_API_KEY|ANTHROPIC_AUTH_TOKEN|CLAUDE_CODE_OAUTH_TOKEN) return 1 ;;
150
+ # Preserve only the active harness's built-in model credentials. Credentials
151
+ # for the inactive harness fall through to the generic secret-name check.
152
+ case "${PIPE_HARNESS:-claude}" in
153
+ omp)
154
+ [ "$name" = "OPENROUTER_API_KEY" ] && return 1
155
+ ;;
156
+ *)
157
+ case "$name" in
158
+ ANTHROPIC_API_KEY|ANTHROPIC_AUTH_TOKEN|CLAUDE_CODE_OAUTH_TOKEN) return 1 ;;
159
+ esac
160
+ ;;
142
161
  esac
143
162
  [[ "$allow" == *" $name "* ]] && return 1
144
163
  case "$name" in
@@ -213,6 +232,115 @@ pipe_run_claude() {
213
232
  rm -f "$stdin_file"
214
233
  }
215
234
 
235
+ # --- omp invocation -----------------------------------------------------
236
+ # Runs `omp -p "/<skill>"` wrapped in usage-capture, the omp equivalent of
237
+ # pipe_run_claude. Same non-root/su-user dance (unverified whether omp
238
+ # tolerates root under --yolo; the same-user fallback Claude Code needs is
239
+ # always safe, so it ships unconditionally either way).
240
+
241
+ pipe_translate_tools_to_omp() {
242
+ # $1 = Claude Code-style comma-separated tool list, e.g.
243
+ # "Agent,Read,Write,Edit,Glob,Grep,Bash". Echoes the omp-equivalent
244
+ # comma-separated list, deduplicated and order-preserving. Tokens with no
245
+ # omp equivalent (LS, NotebookRead, Skill, KillShell, BashOutput) are
246
+ # dropped; mcp__* and any unrecognized token pass through unchanged. See
247
+ # the translation table in
248
+ # docs/superpowers/specs/2026-08-26-omp-openrouter-harness-design.md.
249
+ local tools="$1" tok mapped seen="," result=""
250
+ IFS=',' read -ra tokens <<< "$tools"
251
+ for tok in "${tokens[@]}"; do
252
+ case "$tok" in
253
+ Agent) mapped=task ;;
254
+ Bash) mapped=bash ;;
255
+ Edit|MultiEdit) mapped=edit ;;
256
+ Glob) mapped=glob ;;
257
+ Grep) mapped="grep" ;;
258
+ Read|WebFetch) mapped="read" ;;
259
+ Write) mapped="write" ;;
260
+ TodoWrite) mapped=todo ;;
261
+ WebSearch) mapped=web_search ;;
262
+ LS|NotebookRead|Skill|KillShell|BashOutput) continue ;;
263
+ *) mapped="$tok" ;;
264
+ esac
265
+ case "$seen" in *",$mapped,"*) continue ;; esac
266
+ seen="$seen$mapped,"
267
+ result="$result,$mapped"
268
+ done
269
+ echo "${result#,}"
270
+ }
271
+
272
+ pipe_run_omp() {
273
+ # $1 = agent label for metrics, $2 = skill invocation (e.g. "/review-mr"),
274
+ # $3 = allowed tools in Claude Code naming (optional, translated
275
+ # internally), $4 = model for the main loop (optional, e.g.
276
+ # $PIPE_MODEL_REVIEW; subagents keep the models from their own
277
+ # frontmatter), stdin = extra prompt (optional, usually empty).
278
+ local label="$1" skill="$2" tools="${3:-Agent,Read,Write,Edit,Glob,Grep,Bash}" model="${4:-}"
279
+ local omp_tools; omp_tools=$(pipe_translate_tools_to_omp "$tools")
280
+ local stdin_file; stdin_file=$(mktemp)
281
+ cat > "$stdin_file" || true
282
+
283
+ if [ "$(id -u)" -eq 0 ]; then
284
+ local runuser="pipelinebot"
285
+ id "$runuser" &>/dev/null || useradd -m -s /bin/bash "$runuser"
286
+ chown -R "$runuser:$runuser" "$PWD" "$PIPE_CONTEXT_DIR" "$stdin_file" 2>/dev/null || true
287
+ local runner; runner=$(mktemp)
288
+ {
289
+ printf '#!/bin/bash\nset -e\nexport HOME=/home/%s\n' "$runuser"
290
+ # Re-export the runtime environment the skill and usage-capture need.
291
+ local v
292
+ for v in $(compgen -v | grep -E '^(PIPE_|CI_|GH_|GITHUB_|OPENROUTER_)'); do
293
+ pipe_agent_secret_var "$v" && continue
294
+ printf 'export %s=%q\n' "$v" "${!v}"
295
+ done
296
+ # `su -m` preserves the parent environment, so explicitly remove every
297
+ # detected credential after exporting the safe runtime values above.
298
+ for v in $(compgen -e); do
299
+ pipe_agent_secret_var "$v" && printf 'unset %q\n' "$v"
300
+ done
301
+ printf 'cd %q\n' "$PWD"
302
+ printf 'source %q\n' "$SCRIPT_LIB_DIR/usage-capture-omp.sh"
303
+ printf 'export PIPE_CAPTURE_MODEL=%q\n' "$model"
304
+ if [ -n "$model" ]; then
305
+ printf 'capture_omp %q -- --yolo --no-session --no-title --model %q --tools %q -p %q < %q\n' \
306
+ "$label" "$model" "$omp_tools" "$skill" "$stdin_file"
307
+ else
308
+ printf 'capture_omp %q -- --yolo --no-session --no-title --tools %q -p %q < %q\n' \
309
+ "$label" "$omp_tools" "$skill" "$stdin_file"
310
+ fi
311
+ } > "$runner"
312
+ chmod +x "$runner"
313
+ chown "$runuser:$runuser" "$runner"
314
+ su -m "$runuser" -s /bin/bash "$runner" || true
315
+ chown -R root:root "$PWD" 2>/dev/null || true
316
+ rm -f "$runner"
317
+ else
318
+ (
319
+ pipe_scrub_agent_secrets
320
+ export PIPE_CAPTURE_MODEL="$model"
321
+ if [ -n "$model" ]; then
322
+ capture_omp "$label" -- --yolo --no-session --no-title --model "$model" --tools "$omp_tools" -p "$skill" < "$stdin_file"
323
+ else
324
+ capture_omp "$label" -- --yolo --no-session --no-title --tools "$omp_tools" -p "$skill" < "$stdin_file"
325
+ fi
326
+ ) || true
327
+ fi
328
+ rm -f "$stdin_file"
329
+ }
330
+
331
+ # --- Harness dispatch ---------------------------------------------------
332
+ # Single entry point every job script calls. Same signature/contract as
333
+ # pipe_run_claude/pipe_run_omp themselves: $1 label, $2 skill, $3 tools
334
+ # (optional, Claude Code naming, translated internally when the target is
335
+ # omp), $4 model (optional).
336
+
337
+ pipe_run_agent() {
338
+ case "${PIPE_HARNESS:-claude}" in
339
+ omp) pipe_run_omp "$@" ;;
340
+ *) pipe_run_claude "$@" ;;
341
+ esac
342
+ }
343
+
216
344
  # --- dotenv -----------------------------------------------------------------
217
345
 
218
346
  # Load KEY=VALUE lines from a marker file into the current shell.
@@ -0,0 +1,117 @@
1
+ # shellcheck shell=bash
2
+ # Source-only library. Wraps an `omp --mode json` invocation, sums usage/cost
3
+ # from the agent_end event's message transcript, writes a metrics record, and
4
+ # surfaces the assistant text on stdout — the omp equivalent of
5
+ # usage-capture.sh's capture_claude. Same function contract, same metrics
6
+ # record shape (bar the harness-specific exit-code key), different event
7
+ # parsing because omp's schema differs from Claude's stream-json.
8
+ #
9
+ # Schema verified live against a real omp install (18.0.6): the final
10
+ # "agent_end" line carries {"type":"agent_end","messages":[...],
11
+ # "isTerminal":true}. Each assistant message in `messages[]` carries its own
12
+ # `usage` object (per-message, not session-cumulative) and top-level
13
+ # `model`/`provider`/`duration` fields. See
14
+ # docs/superpowers/specs/2026-08-26-omp-openrouter-harness-design.md section 3
15
+ # for the full verification record.
16
+ #
17
+ # Usage: capture_omp <agent_name> -- <args to pass to `omp`...>
18
+ # Stdin: forwarded to `omp` stdin
19
+ # Stdout: concatenated text from assistant turns (drop-in for `omp -p`)
20
+ # Side effect: writes one JSON record to $PIPE_METRICS_DIR
21
+
22
+ set -u
23
+
24
+ capture_omp() {
25
+ local agent="$1"; shift
26
+ [ "${1:-}" = "--" ] && shift
27
+
28
+ local metrics_dir="${PIPE_METRICS_DIR:-${PIPE_CONTEXT_DIR:-build/pipeline}/metrics}"
29
+ mkdir -p "$metrics_dir"
30
+
31
+ local job_name="${PIPE_JOB_NAME:-${CI_JOB_NAME:-job}}"
32
+ local job_id="${PIPE_JOB_ID:-${CI_JOB_ID:-$$}}"
33
+ local pipeline_id="${PIPE_PIPELINE_ID:-${CI_PIPELINE_ID:-}}"
34
+ local mr_iid="${PIPE_MR_IID:-${CI_MERGE_REQUEST_IID:-null}}"
35
+ local issue_iid="${PIPE_ISSUE_IID:-${CODER_ISSUE:-null}}"
36
+
37
+ local stream_file stderr_file
38
+ stream_file=$(mktemp)
39
+ stderr_file=$(mktemp)
40
+ local start_ns end_ns
41
+ start_ns=$(date +%s%N)
42
+ local exit_code=0
43
+
44
+ # Force JSON mode so we always get an agent_end event with the full
45
+ # message transcript.
46
+ omp --mode json "$@" \
47
+ >"$stream_file" 2>"$stderr_file" || exit_code=$?
48
+
49
+ end_ns=$(date +%s%N)
50
+ local wall_ms=$(( (end_ns - start_ns) / 1000000 ))
51
+
52
+ # Extract the final agent_end event (last line that begins with
53
+ # {"type":"agent_end"). Its usage is per-message (one API call each), not
54
+ # pre-aggregated like Claude's single "result" event, so sum it ourselves.
55
+ local result_line
56
+ result_line=$(grep '^{"type":"agent_end"' "$stream_file" | tail -1 || true)
57
+
58
+ local model input output cache_c cache_r cost duration
59
+ if [ -n "$result_line" ]; then
60
+ model=$(echo "$result_line" | jq -r '[.messages[]? | select(.role=="assistant") | ((.provider // "") + "/" + (.model // ""))] | last // ""')
61
+ input=$(echo "$result_line" | jq '[.messages[]? | select(.role=="assistant") | .usage.input // 0] | add // 0')
62
+ output=$(echo "$result_line" | jq '[.messages[]? | select(.role=="assistant") | .usage.output // 0] | add // 0')
63
+ cache_c=$(echo "$result_line" | jq '[.messages[]? | select(.role=="assistant") | .usage.cacheWrite // 0] | add // 0')
64
+ cache_r=$(echo "$result_line" | jq '[.messages[]? | select(.role=="assistant") | .usage.cacheRead // 0] | add // 0')
65
+ cost=$(echo "$result_line" | jq '[.messages[]? | select(.role=="assistant") | .usage.cost.total // 0] | add // 0')
66
+ duration=$(echo "$result_line" | jq '[.messages[]? | select(.role=="assistant") | .duration // 0] | add // 0')
67
+ else
68
+ model=""; input=0; output=0; cache_c=0; cache_r=0; cost=0; duration=$wall_ms
69
+ fi
70
+ [ -n "${PIPE_CAPTURE_MODEL:-}" ] && [ -z "$model" ] && model="$PIPE_CAPTURE_MODEL"
71
+
72
+ # Recover the user-facing text (concatenate all assistant text blocks) so
73
+ # downstream consumers that read `omp -p`'s stdout still work.
74
+ grep '^{"type":"message_end"' "$stream_file" \
75
+ | jq -r 'select(.message.role=="assistant") | .message.content[]? | select(.type=="text") | .text' \
76
+ || true
77
+
78
+ # Write metrics record. Same field names as capture_claude's record; only
79
+ # the harness-specific exit-code key differs (omp_exit_code vs
80
+ # claude_exit_code), by the same convention the two capture_* files
81
+ # already use for their own harness-labelled fields.
82
+ local record_stamp record_file
83
+ record_stamp=$(date +%s%N)
84
+ record_file="$metrics_dir/${job_name}-${job_id}-${record_stamp}.json"
85
+ jq -n \
86
+ --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
87
+ --arg agent "$agent" \
88
+ --arg event "omp_invocation" \
89
+ --arg model "$model" \
90
+ --arg pipeline_id "$pipeline_id" \
91
+ --arg job_id "$job_id" \
92
+ --arg job_name "$job_name" \
93
+ --argjson mr_iid "$mr_iid" \
94
+ --argjson issue_iid "$issue_iid" \
95
+ --argjson input "$input" \
96
+ --argjson output "$output" \
97
+ --argjson cache_c "$cache_c" \
98
+ --argjson cache_r "$cache_r" \
99
+ --argjson cost "$cost" \
100
+ --argjson duration "$duration" \
101
+ --argjson wall_ms "$wall_ms" \
102
+ --argjson exit_code "$exit_code" \
103
+ '{
104
+ ts: $ts, agent: $agent, event: $event, model: $model,
105
+ pipeline_id: $pipeline_id, job_id: $job_id, job_name: $job_name,
106
+ mr_iid: $mr_iid, issue_iid: $issue_iid,
107
+ input_tokens: $input, output_tokens: $output,
108
+ cache_creation_tokens: $cache_c, cache_read_tokens: $cache_r,
109
+ total_cost_usd: $cost, duration_ms: $duration, wall_ms: $wall_ms,
110
+ omp_exit_code: $exit_code
111
+ }' > "$record_file"
112
+
113
+ # If omp failed, forward stderr so debugging still works.
114
+ [ "$exit_code" -ne 0 ] && cat "$stderr_file" >&2
115
+ rm -f "$stream_file" "$stderr_file"
116
+ return "$exit_code"
117
+ }
@@ -119,7 +119,7 @@ while IFS= read -r ISSUE; do
119
119
 
120
120
  export PIPE_ISSUE_IID="$IID"
121
121
  rm -f "$PIPE_CONTEXT_DIR/triage.env" "$PIPE_CONTEXT_DIR/triage.json"
122
- pipe_run_claude orchestrator "/triage-issue" "Agent,Read,Write,Edit,Glob,Grep,Bash" "$PIPE_MODEL_TRIAGE" < /dev/null
122
+ pipe_run_agent orchestrator "/triage-issue" "Agent,Read,Write,Edit,Glob,Grep,Bash" "$PIPE_MODEL_TRIAGE" < /dev/null
123
123
 
124
124
  TRIAGE_ENV="$PIPE_CONTEXT_DIR/triage.env"
125
125
  TRIAGE_JSON="$PIPE_CONTEXT_DIR/triage.json"
@@ -38,7 +38,7 @@ fi
38
38
 
39
39
  # Run the postmortem skill unless a prior inline flow already produced the marker.
40
40
  if [ ! -f "$PIPE_CONTEXT_DIR/postmortem.env" ]; then
41
- pipe_run_claude postmortem "/postmortem-mr" "Agent,Read,Write,Edit,Glob,Grep,Bash" "$PIPE_MODEL_TRIAGE" < /dev/null
41
+ pipe_run_agent postmortem "/postmortem-mr" "Agent,Read,Write,Edit,Glob,Grep,Bash" "$PIPE_MODEL_TRIAGE" < /dev/null
42
42
  fi
43
43
 
44
44
  # Apply the runner side: labels, comment, metric.
@@ -40,7 +40,7 @@ echo "$NOTES" | jq -r --arg u "${PIPE_BOT_USER:-}" --arg m "$REVIEW_MARKER" \
40
40
  # 5. Run the fix skill. It caps, gathers prior fix diffs, delegates to the coder,
41
41
  # verifies, writes the result JSON, and the review-fix.env marker. On cap it
42
42
  # runs the shared postmortem flow (writing postmortem.md + postmortem.env).
43
- pipe_run_claude coder "/fix-review-findings" "Agent,Read,Write,Edit,Glob,Grep,Bash" "$PIPE_MODEL_CODE" < /dev/null
43
+ pipe_run_agent coder "/fix-review-findings" "Agent,Read,Write,Edit,Glob,Grep,Bash" "$PIPE_MODEL_CODE" < /dev/null
44
44
 
45
45
  # 6. Read the marker the skill wrote.
46
46
  RF_ENV="$PIPE_CONTEXT_DIR/review-fix.env"
@@ -52,7 +52,7 @@ pipe_log "diff base=$PIPE_DIFF_BASE incremental=$PIPE_IS_INCREMENTAL head=$PIPE_
52
52
 
53
53
  # 4. Run the review skill. It builds the diff, runs the reviewers, writes the
54
54
  # merged body to $PIPE_RESULT_REVIEW and the verdict to review.env.
55
- pipe_run_claude review "/review-mr" "Agent,Read,Write,Edit,Glob,Grep,Bash" "$PIPE_MODEL_REVIEW" < /dev/null
55
+ pipe_run_agent review "/review-mr" "Agent,Read,Write,Edit,Glob,Grep,Bash" "$PIPE_MODEL_REVIEW" < /dev/null
56
56
 
57
57
  # 5. Read the verdict the skill wrote (drives the status emoji), then post
58
58
  # the review comment (token work). The emoji goes after the marker line,
@@ -26,7 +26,7 @@ COVERAGE_BEFORE=$(pipe_count_fix_commits "$PIPE_COMMIT_COVERAGE")
26
26
  # case (tests | coverage | compile | none), caps, delegates to test-fix or
27
27
  # test-writer, verifies, and writes fix-tests.env. On cap it runs the shared
28
28
  # postmortem flow (writing postmortem.md + postmortem.env).
29
- pipe_run_claude test-fix "/fix-tests" "Agent,Read,Write,Edit,Glob,Grep,Bash" "$PIPE_MODEL_CODE" < /dev/null
29
+ pipe_run_agent test-fix "/fix-tests" "Agent,Read,Write,Edit,Glob,Grep,Bash" "$PIPE_MODEL_CODE" < /dev/null
30
30
 
31
31
  # 4. Read the marker the skill wrote.
32
32
  FT_ENV="$PIPE_CONTEXT_DIR/fix-tests.env"
@@ -41,7 +41,7 @@ git log "origin/$PIPE_TARGET_BRANCH"..HEAD --grep="$PIPE_COMMIT_REVIEWFIX" -p
41
41
 
42
42
  ## Step 5: delegate to the coder
43
43
 
44
- Spawn the `coder` agent (Agent tool, `subagent_type: coder`). Pass it:
44
+ Spawn the `coder` subagent. Pass it:
45
45
 
46
46
  - the review findings from `$PIPE_CONTEXT_DIR/review-body.md`,
47
47
  - the prior fix diffs from step 4, if any, with the instruction not to repeat those approaches,
@@ -44,8 +44,8 @@ Stop counting at the first subject that does not match. If the count is at or ab
44
44
 
45
45
  ## Step 5: delegate
46
46
 
47
- - **Test-fix and compilation cases**: spawn the `test-fix` agent (Agent tool, `subagent_type: test-fix`). Pass the failure details (test failures or the compilation output) and the exact commit subject `$PIPE_COMMIT_TESTFIX`. The agent reads the config and the testing document itself, fixes the root cause in the implementation or the test, and commits.
48
- - **Coverage case**: spawn the `test-writer` agent (`subagent_type: test-writer`). Pass the baseline and current coverage, the diff of new code (`git diff "origin/$PIPE_TARGET_BRANCH"..HEAD` scoped to source files), and the exact commit subject `$PIPE_COMMIT_COVERAGE`.
47
+ - **Test-fix and compilation cases**: spawn the `test-fix` subagent. Pass the failure details (test failures or the compilation output) and the exact commit subject `$PIPE_COMMIT_TESTFIX`. The agent reads the config and the testing document itself, fixes the root cause in the implementation or the test, and commits.
48
+ - **Coverage case**: spawn the `test-writer` subagent. Pass the baseline and current coverage, the diff of new code (`git diff "origin/$PIPE_TARGET_BRANCH"..HEAD` scoped to source files), and the exact commit subject `$PIPE_COMMIT_COVERAGE`.
49
49
 
50
50
  Tell the agent the commit subject must be exactly the value above, because the cap counter matches on it. Tell it not to run `git push`.
51
51
 
@@ -22,7 +22,7 @@ The repository is already checked out on the feature branch. Git is local, no to
22
22
 
23
23
  ## Step 3: delegate to the coder
24
24
 
25
- Spawn the `coder` agent (Agent tool, `subagent_type: coder`) with the issue IID, title, and description from the context file, instructing it to implement the issue. The coder reads the config and the Documentation Map itself, explores existing patterns, implements, writes tests through the test-writer agent, runs the project verify command in a self-correcting loop, and commits. Do not restate its internal rules.
25
+ Spawn the `coder` subagent with the issue IID, title, and description from the context file, instructing it to implement the issue. The coder reads the config and the Documentation Map itself, explores existing patterns, implements, writes tests through the test-writer agent, runs the project verify command in a self-correcting loop, and commits. Do not restate its internal rules.
26
26
 
27
27
  The coder must NOT push. Pushing is the runner's job. After the coder returns, confirm no push happened: the coder has no push step, but if the working tree or branch state shows an attempted push, note it in your output. The commits stay local for the runner to push.
28
28
 
@@ -37,7 +37,7 @@ Source paths: `<root>` below means the first candidate that actually contains `t
37
37
 
38
38
  Do not hand the user a homework list. Walk them through it:
39
39
 
40
- 1. Start with one confirmation of the detected basics in a single question: "The pipeline will target branch `<detected>` on `<platform>`. Correct?" Detected values are proposals to confirm, never open questions.
40
+ 1. Start with one confirmation of the detected basics: "The pipeline will target branch `<detected>` on `<platform>`. Correct?" Detected values are proposals to confirm, never open questions. When the detected platform is GitLab, ask in the same message which harness to run: Claude Code (default, needs `ANTHROPIC_API_KEY`) or omp + OpenRouter (needs `OPENROUTER_API_KEY` and accepts any OpenRouter model). Record the answer as `PIPE_HARNESS` (`claude` or `omp`). When the detected platform is GitHub, record `PIPE_HARNESS=claude`, state that omp and OpenRouter support is not shipped for GitHub yet, and do not offer omp as an option.
41
41
  2. Split the remaining `TODO`s into two groups. **Defaultable:** the repo gives a defensible answer (coverage tooling absent means the policy is `not used`, Domain Check candidates read from the code, standard paths). Apply these without asking and keep them for the summary in step 4. **Genuinely open:** the repo gives no signal at all (a convention nobody wrote down, a check only a human knows about). Only these earn a question. Branch naming is not a question: with no repository convention use `<issue-iid>-<kebab-title>`, which is exactly what the runner accepts.
42
42
  3. Ask one scheduling question because frequency is a project policy, not a detectable technical fact: should the issue loop stay manual/disabled, run nightly on selected days, or use a custom cron? Recommend manual/disabled until both smoke tests pass. For a schedule, record days, local time, and time zone. Convert to UTC only for GitHub Actions; GitLab stores the chosen cron time zone. Then ask the other genuinely open items one concrete question at a time, in config order, each with a suggested default when possible. In a typical repo this is one to three questions total. Do not ask for the bot account username: the runner resolves it from the token at runtime (`platform_resolve_bot_user`); the account itself gets created with the token in phase 4. Apply each answer to the config immediately; the user never edits the file by hand during this phase.
43
43
  4. Close with one review summary of everything that was set: detected, defaulted, and answered, with the applied Domain Checks listed item by item. Invite the user to add, remove, or change anything; apply the edits. This summary is the safety net that lets steps 2-3 default aggressively.
@@ -50,7 +50,7 @@ Do this yourself; it is mechanical. The human only approves the diff. When the `
50
50
  **GitLab** (detected in phase 1):
51
51
 
52
52
  1. Create `.claude-pipeline/` and copy `<root>/ci-templates/claude-pipeline.gitlab-ci.yml` and `<root>/ci-templates/scripts/` into it.
53
- 2. Edit the project `.gitlab-ci.yml`: ensure the stages the template needs (`orchestrate, code, test, review`), add the `include:` of the vendored template, and add the non-secret `PIPE_*` values from the config that differ from the template defaults (typically `PIPE_VERIFY_CMD`, `PIPE_TEST_REPORT_GLOB`) as top-level `variables:`. Always write `PIPE_TARGET_BRANCH` with the branch detected in phase 1, as a literal: a nested `$CI_DEFAULT_BRANCH` is not expanded inside `rules:` comparisons and would silently disable every MR job (E2E finding N-3). You know the values since phase 1; wiring them now keeps phase 4 free of commits. Do not touch the project's own jobs without asking.
53
+ 2. Edit the project `.gitlab-ci.yml`: ensure the stages the template needs (`orchestrate, code, test, review`), add the `include:` of the vendored template, and add the non-secret `PIPE_*` values from the config that differ from the template defaults (typically `PIPE_VERIFY_CMD`, `PIPE_TEST_REPORT_GLOB`) as top-level `variables:`. Always write `PIPE_TARGET_BRANCH` with the branch detected in phase 1, as a literal: a nested `$CI_DEFAULT_BRANCH` is not expanded inside `rules:` comparisons and would silently disable every MR job (E2E finding N-3). You know the values since phase 1; wiring them now keeps phase 4 free of commits. Do not touch the project's own jobs without asking. When phase 2 chose `omp`, add `PIPE_HARNESS: "omp"` to that same `variables:` block (the template default is `claude`).
54
54
  3. Check the project's own test job: when neither its `rules:` nor a `workflow:` block makes it run in `merge_request_event` pipelines, the template's `test-fix` job can never fire (its `needs: test` finds no test job in the MR pipeline, E2E finding N-4). Tell the user and offer a concrete diff that adds the missing rule. Apply it only after they agree.
55
55
  4. Copy `<root>/templates/spec-issue.template.md` to `.gitlab/issue_templates/Spec.md` (or the path the user chose in phase 2). When that file already exists, show the diff and ask before replacing it. An issue template is project-owned content, and the ground rule above covers the config and the CI file by name, so this one has to be said explicitly.
56
56
 
@@ -75,7 +75,7 @@ Never take the values. And never dump the whole checklist at once: this phase is
75
75
 
76
76
  The steps, in this order (GitLab has 7, GitHub 6, number the counter accordingly):
77
77
 
78
- 1. `ANTHROPIC_API_KEY`: where to create it, set as a masked CI variable (GitLab: Settings > CI/CD > Variables, not protected when MR pipelines run from unprotected branches; GitHub: repository secret).
78
+ 1. The model credential for the platform and chosen harness. For GitHub, guide only `ANTHROPIC_API_KEY` (https://console.anthropic.com/settings/keys) as a repository secret. For GitLab with Claude Code, guide `ANTHROPIC_API_KEY` as a masked CI variable. For GitLab with omp, guide `OPENROUTER_API_KEY` (https://openrouter.ai/settings/keys) as a masked CI variable. GitLab model credentials must not be protected when MR pipelines run from unprotected branches.
79
79
  2. `PIPE_BOT_TOKEN`: GitLab project access token with `api` + `write_repository`, masked and hidden but not protected when ordinary unprotected feature branches need MR review/fix jobs; GitHub PAT with issues, contents, pull-requests and Actions write. Explain that protected GitLab variables work only when the project's protected-MR conditions are satisfied.
80
80
  3. GitLab only: `PIPE_TRIGGER_TOKEN` (Settings > CI/CD > Pipeline trigger tokens) for the orchestrate-to-code dispatch.
81
81
  4. The non-secret `PIPE_*` variables that differ from the defaults: these were already wired into the CI file in phase 3, so this step is normally a one-line "already wired, skipping". Only when something changed during phase 4 edit the CI file again (part of the walkthrough, no extra approval beyond showing the diff).
@@ -91,6 +91,6 @@ The smoke tests are the last step and the wizard drives them. The review smoke r
91
91
 
92
92
  1. Announce it in one line and prepare it: a branch named by the phase 2 convention, one small harmless change, an MR/PR against the target branch carrying the wip label. Then ask the one question of this phase: confirm the push plus MR/PR creation (ground rule: never push silently). One yes covers both. On GitLab without `glab`, create the MR with git push options (`git push -o merge_request.create -o merge_request.target=<target> -o merge_request.label=<wip label> origin <branch>`), no token or CLI needed. When no `merge_request_event` pipeline appears within about a minute of the MR existing, create it yourself with `POST /projects/:id/merge_requests/:iid/pipelines` (the bot token is set by phase 4). On GitHub without `gh`, push and print the compare URL for the user to open the PR.
93
93
  2. Check the run yourself with a single status query (`glab ci status` / `gh run list` for the run), or one short bounded wait, then report. Do not launch a blind polling loop that blocks for many minutes. If the jobs have not started or finished yet, say so and give the user the one command to re-check, rather than waiting them out. Scheduled pipelines and crons are best-effort and can lag by minutes, so an unstarted scheduled run is expected, not a failure. Expect, once it runs: the `review` job runs, a review comment from the bot account appears on the MR/PR, `review.env` and a metrics JSON land in `$PIPE_CONTEXT_DIR`. Report the result with a link to the MR/PR and the bot comment.
94
- 3. On failure, read the job log yourself and say what is wrong and what to change. The common causes are a missing `ANTHROPIC_API_KEY`, a `PIPE_BOT_TOKEN` without comment/write scope, and an explicitly set `PIPE_BOT_USER` not matching the token's account (leave it unset; the runner resolves it from the token).
94
+ 3. On failure, read the job log yourself and say what is wrong and what to change. The common causes are a missing credential for the selected harness (`ANTHROPIC_API_KEY` or `OPENROUTER_API_KEY`), a `PIPE_BOT_TOKEN` without comment/write scope, and an explicitly set `PIPE_BOT_USER` not matching the token's account (leave it unset; the runner resolves it from the token).
95
95
  4. After the review smoke test passes, offer a separate end-to-end issue-loop smoke test. This is the only test that proves issue -> triage -> code -> MR/PR rather than only the review half, so it is worth running. Run it only after explicit approval, then watch it through MR/PR creation. Steps: create one small issue whose full spec sits in the issue DESCRIPTION following `spec-issue.template.md` (a spec pasted into a comment does not count, the pipeline reads the spec from the description), apply the ready label, then start orchestrate IMMEDIATELY with a manual run rather than a schedule. On GitLab that is Run pipeline on the target branch with variable `PIPE_ORCHESTRATE=1` (pipeline source `web`, which the orchestrate rule already allows), on GitHub the `workflow_dispatch` of the issue pipeline. Do not set up a cron schedule for this test. A cron is only for ongoing autonomy later and adds minutes of best-effort delay, while the manual run starts within seconds. It spends another triage plus coder run, pushes a feature branch, and opens an MR/PR.
96
96
  5. Close with the final recap, one table: every setting that matters (target branch, branch convention, labels, verify command, report path, image, caps, models, schedule), its value, and its origin (detected / default / answered). Under the table, state what was committed and pushed, both smoke-test results (or `not run`), and whether the issue schedule is active or manual. End with a clear "done": the user must never have to ask what state the repo is in.
@@ -25,7 +25,7 @@ The changed-file list is available locally from `git diff --name-only "origin/$P
25
25
 
26
26
  ## Step 3: run the postmortem agent
27
27
 
28
- Spawn the `postmortem` agent (Agent tool, `subagent_type: postmortem`). Pass the MR/PR meta, the changed files, the fix-attempt git log, the original failure text, and the context label. The agent reads the config itself and produces the structured report, which includes the mandatory line:
28
+ Spawn the `postmortem` subagent. Pass the MR/PR meta, the changed files, the fix-attempt git log, the original failure text, and the context label. The agent reads the config itself and produces the structured report, which includes the mandatory line:
29
29
 
30
30
  ```
31
31
  failure_category: <category>
@@ -33,7 +33,7 @@ When `$PIPE_IS_INCREMENTAL` is `true`, the diff is only what changed since the p
33
33
 
34
34
  ## Step 4: run the code-reviewer
35
35
 
36
- Spawn the `code-reviewer` agent (Agent tool, `subagent_type: code-reviewer`). Pass it:
36
+ Spawn the `code-reviewer` subagent. Pass it:
37
37
 
38
38
  - the MR/PR intent (title, description, linked issues) from the context file,
39
39
  - the scope note (incremental or initial),
@@ -22,7 +22,7 @@ Everything comes from CI variables and a context file the job prepared with the
22
22
 
23
23
  ## Step 3: run the orchestrator
24
24
 
25
- Spawn the `orchestrator` agent (Agent tool, `subagent_type: orchestrator`). Pass it the issue context (IID, title, description, comments) from the context file. The orchestrator reads the config, applies the spec-structure check and the capacity heuristic itself, and returns valid JSON, exactly one of:
25
+ Spawn the `orchestrator` subagent. Pass it the issue context (IID, title, description, comments) from the context file. The orchestrator reads the config, applies the spec-structure check and the capacity heuristic itself, and returns valid JSON, exactly one of:
26
26
 
27
27
  - `{"actionable":true,"branch":"IID-kebab-title"}`: right-sized, ready to implement.
28
28
  - `{"actionable":true,"scope":"too_large","decomposition":"short paragraph"}`: clear but too big.
@@ -32,7 +32,7 @@ Do not restate the orchestrator's internal rules in the prompt.
32
32
 
33
33
  ## Step 4: decompose when too large
34
34
 
35
- Only when the orchestrator returned `scope:"too_large"`, spawn the `decomposer` agent (Agent tool, `subagent_type: decomposer`). Pass it the parent issue (IID, title, description, comments) and tell it the spec template path is `$PIPE_SPEC_TEMPLATE_PATH`. It reads the template and the config itself and returns JSON:
35
+ Only when the orchestrator returned `scope:"too_large"`, spawn the `decomposer` subagent. Pass it the parent issue (IID, title, description, comments) and tell it the spec template path is `$PIPE_SPEC_TEMPLATE_PATH`. It reads the template and the config itself and returns JSON:
36
36
 
37
37
  ```
38
38
  {"confidence":"high|low","reason":"...","sub_issues":[{"key":"a","title":"...","spec_markdown":"...","depends_on":[]}]}