@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.
- package/README.md +15 -1
- package/package.json +2 -2
- package/payload/INSTALL.md +6 -3
- package/payload/agents/agent-architect.md +1 -1
- package/payload/agents/code-reviewer.md +1 -1
- package/payload/agents/codebase-auditor.md +1 -1
- package/payload/agents/coder.md +3 -3
- package/payload/agents/decomposer.md +1 -1
- package/payload/agents/docs-sync.md +1 -1
- package/payload/agents/e2e-test-writer.md +1 -1
- package/payload/agents/performance-reviewer.md +1 -1
- package/payload/agents/release-mr.md +1 -1
- package/payload/agents/security-reviewer.md +1 -1
- package/payload/agents/test-fix.md +4 -4
- package/payload/agents/test-writer.md +3 -3
- package/payload/agents-omp/agent-architect.md +101 -0
- package/payload/agents-omp/code-reviewer.md +86 -0
- package/payload/agents-omp/codebase-auditor.md +73 -0
- package/payload/agents-omp/coder.md +57 -0
- package/payload/agents-omp/decomposer.md +70 -0
- package/payload/agents-omp/docs-sync.md +114 -0
- package/payload/agents-omp/e2e-test-writer.md +47 -0
- package/payload/agents-omp/migration-reviewer.md +99 -0
- package/payload/agents-omp/orchestrator.md +50 -0
- package/payload/agents-omp/performance-reviewer.md +81 -0
- package/payload/agents-omp/postmortem.md +82 -0
- package/payload/agents-omp/release-mr.md +274 -0
- package/payload/agents-omp/security-reviewer.md +121 -0
- package/payload/agents-omp/test-fix.md +33 -0
- package/payload/agents-omp/test-writer.md +39 -0
- package/payload/ci-templates/claude-pipeline.gitlab-ci.yml +10 -7
- package/payload/ci-templates/github/claude-issue-pipeline.yml +1 -1
- package/payload/ci-templates/github/claude-pipeline.yml +2 -2
- package/payload/ci-templates/github/claude-test-fix.yml +1 -1
- package/payload/ci-templates/scripts/code.sh +9 -8
- package/payload/ci-templates/scripts/lib/pipeline-common.sh +138 -10
- package/payload/ci-templates/scripts/lib/usage-capture-omp.sh +117 -0
- package/payload/ci-templates/scripts/orchestrate.sh +1 -1
- package/payload/ci-templates/scripts/postmortem.sh +1 -1
- package/payload/ci-templates/scripts/review-fix.sh +1 -1
- package/payload/ci-templates/scripts/review.sh +1 -1
- package/payload/ci-templates/scripts/test-fix.sh +1 -1
- package/payload/skills/fix-review-findings/SKILL.md +1 -1
- package/payload/skills/fix-tests/SKILL.md +2 -2
- package/payload/skills/implement-issue/SKILL.md +1 -1
- package/payload/skills/init-pipeline-config/SKILL.md +4 -4
- package/payload/skills/postmortem-mr/SKILL.md +1 -1
- package/payload/skills/review-mr/SKILL.md +1 -1
- 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
|
-
: "${
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
135
|
-
# project verification command. The allowlist is comma/space
|
|
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
|
-
#
|
|
139
|
-
#
|
|
140
|
-
case "$
|
|
141
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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`
|
|
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`
|
|
48
|
-
- **Coverage case**: spawn the `test-writer`
|
|
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`
|
|
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
|
|
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
|
|
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
|
|
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`
|
|
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`
|
|
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`
|
|
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`
|
|
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":[]}]}
|