@cxi-lmai/ci-agent-platform 3.0.0 → 3.1.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/README.md +27 -5
- package/package.json +2 -2
- package/payload/INSTALL.md +14 -5
- 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 +220 -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/agent-architect.sh +233 -0
- package/payload/ci-templates/scripts/code.sh +35 -35
- package/payload/ci-templates/scripts/codebase-audit.sh +252 -0
- package/payload/ci-templates/scripts/coverage-ratchet.sh +77 -0
- package/payload/ci-templates/scripts/cve-fix.sh +246 -0
- package/payload/ci-templates/scripts/docs-sync.sh +225 -0
- package/payload/ci-templates/scripts/e2e-test-gen.sh +201 -0
- package/payload/ci-templates/scripts/lib/failure-notice.sh +104 -0
- package/payload/ci-templates/scripts/lib/issue-loop.sh +155 -40
- package/payload/ci-templates/scripts/lib/pipeline-common.sh +233 -31
- package/payload/ci-templates/scripts/lib/platform.sh +270 -13
- package/payload/ci-templates/scripts/lib/usage-capture-omp.sh +123 -0
- package/payload/ci-templates/scripts/lib/usage-capture.sh +7 -1
- package/payload/ci-templates/scripts/metrics-snapshot.sh +377 -0
- package/payload/ci-templates/scripts/orchestrate.sh +56 -38
- package/payload/ci-templates/scripts/postmortem.sh +11 -1
- package/payload/ci-templates/scripts/review-fix.sh +23 -12
- package/payload/ci-templates/scripts/review.sh +12 -8
- package/payload/ci-templates/scripts/test-fix.sh +8 -1
- package/payload/skills/agent-architect/SKILL.md +45 -0
- package/payload/skills/codebase-audit/SKILL.md +84 -0
- package/payload/skills/cve-fix/SKILL.md +98 -0
- package/payload/skills/docs-sync/SKILL.md +74 -0
- package/payload/skills/e2e-test-gen/SKILL.md +65 -0
- package/payload/skills/fix-review-findings/SKILL.md +3 -3
- package/payload/skills/fix-tests/SKILL.md +4 -4
- 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
- package/payload/templates/memory-index.template.md +34 -0
- package/payload/templates/pipeline-config.template.md +11 -1
- package/payload/templates/review_suppressions.template.md +55 -0
- package/payload/templates/spec-issue.template.md +39 -7
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Aggregates one window of pipeline metrics into a single JSON snapshot.
|
|
3
|
+
#
|
|
4
|
+
# Two record kinds share the metrics directory. An invocation record carries an
|
|
5
|
+
# `event` ending in `_invocation` plus the token and cost fields of one agent
|
|
6
|
+
# call, and every other record is a custom pipeline event. Older runners
|
|
7
|
+
# appended custom events to events.jsonl, which is still read when present.
|
|
8
|
+
#
|
|
9
|
+
# Inputs:
|
|
10
|
+
# - artifacts of pipelines finished inside the window, fetched through the
|
|
11
|
+
# platform abstraction, skipped entirely when OFFLINE=true
|
|
12
|
+
# - records written by this run's own jobs, under PIPE_METRICS_DIR
|
|
13
|
+
#
|
|
14
|
+
# Environment:
|
|
15
|
+
# PIPE_METRICS_WINDOW_DAYS days of history to aggregate
|
|
16
|
+
# PIPE_METRICS_OUTPUT_DIR directory the snapshot is written to
|
|
17
|
+
# PIPE_METRICS_COMMIT_REPO "true" also commits the snapshot to the target branch
|
|
18
|
+
# PIPE_METRICS_REVIEW_AGENTS comma-separated agents counted as code review
|
|
19
|
+
# PIPE_METRICS_DEFECT_* commit subjects that mark a post-merge defect
|
|
20
|
+
# PIPE_METRICS_SNAPSHOT_DATE overrides the snapshot date, default today UTC
|
|
21
|
+
# OFFLINE "true" skips every platform call
|
|
22
|
+
# PIPE_METRICS_RAW_DIR OFFLINE only, directory of pre-staged records
|
|
23
|
+
# PIPE_METRICS_MRS_FILE OFFLINE only, JSON array of merge requests
|
|
24
|
+
#
|
|
25
|
+
# Writes PIPE_METRICS_OUTPUT_DIR/<date>.json. The snapshot is a job artifact;
|
|
26
|
+
# committing it back to the repository is opt-in because a project may refuse
|
|
27
|
+
# generated data on its integration branch.
|
|
28
|
+
set -euo pipefail
|
|
29
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
30
|
+
# shellcheck source=lib/pipeline-common.sh
|
|
31
|
+
source "$SCRIPT_DIR/lib/pipeline-common.sh"
|
|
32
|
+
pipe_defaults
|
|
33
|
+
|
|
34
|
+
SNAPSHOT_DATE="${PIPE_METRICS_SNAPSHOT_DATE:-$(date -u +%Y-%m-%d)}"
|
|
35
|
+
mkdir -p "$PIPE_METRICS_OUTPUT_DIR"
|
|
36
|
+
|
|
37
|
+
TMP=$(mktemp -d)
|
|
38
|
+
trap 'rm -rf "$TMP"' EXIT
|
|
39
|
+
|
|
40
|
+
RAW_DIR="$TMP/raw"
|
|
41
|
+
mkdir -p "$RAW_DIR"
|
|
42
|
+
MRS_FILE="$TMP/mrs.json"
|
|
43
|
+
echo '[]' > "$MRS_FILE"
|
|
44
|
+
|
|
45
|
+
# Escape a configured marker so it can be used inside a regular expression.
|
|
46
|
+
re_escape() { printf '%s' "$1" | sed 's/[][\\.^$*+?(){}|]/\\&/g'; }
|
|
47
|
+
|
|
48
|
+
# Artifact archives are read with whichever extractor the runner image has.
|
|
49
|
+
# Only JSON members are unpacked, at any depth: a pipeline's other artifacts
|
|
50
|
+
# (build output, test reports, packaged binaries) share these archives, and
|
|
51
|
+
# unpacking them wholesale would both cost time and feed foreign documents into
|
|
52
|
+
# the aggregation. Depth-independence is required because the two forges lay
|
|
53
|
+
# their archives out differently: job archives keep repository-relative paths,
|
|
54
|
+
# while uploaded-artifact archives are rooted at the uploaded directory itself.
|
|
55
|
+
# collect_records applies the record-shape filter that decides what counts.
|
|
56
|
+
metrics_extract() {
|
|
57
|
+
local zip="$1" dest="$2"
|
|
58
|
+
if command -v unzip >/dev/null 2>&1; then
|
|
59
|
+
unzip -o -q "$zip" '*.json' '*.jsonl' -d "$dest" 2>/dev/null || true
|
|
60
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
61
|
+
python3 -c 'import sys, zipfile
|
|
62
|
+
try:
|
|
63
|
+
with zipfile.ZipFile(sys.argv[1]) as archive:
|
|
64
|
+
wanted = [m for m in archive.namelist()
|
|
65
|
+
if not m.endswith("/") and (m.endswith(".json") or m.endswith(".jsonl"))]
|
|
66
|
+
archive.extractall(sys.argv[2], wanted)
|
|
67
|
+
except Exception:
|
|
68
|
+
pass' "$zip" "$dest" || true
|
|
69
|
+
else
|
|
70
|
+
pipe_log "WARNING: no unzip and no python3, cannot read artifact $zip"
|
|
71
|
+
fi
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
# Phase 1: collect the window's records
|
|
76
|
+
# ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
if [ "${OFFLINE:-false}" = "true" ]; then
|
|
79
|
+
if [ -n "${PIPE_METRICS_RAW_DIR:-}" ] && [ -d "${PIPE_METRICS_RAW_DIR}" ]; then
|
|
80
|
+
RAW_DIR="$PIPE_METRICS_RAW_DIR"
|
|
81
|
+
fi
|
|
82
|
+
if [ -n "${PIPE_METRICS_MRS_FILE:-}" ] && [ -s "${PIPE_METRICS_MRS_FILE}" ]; then
|
|
83
|
+
MRS_FILE="$PIPE_METRICS_MRS_FILE"
|
|
84
|
+
fi
|
|
85
|
+
pipe_log "Offline mode: reading staged records from $RAW_DIR"
|
|
86
|
+
else
|
|
87
|
+
SINCE=$(date -u -d "$PIPE_METRICS_WINDOW_DAYS days ago" '+%Y-%m-%dT%H:%M:%SZ')
|
|
88
|
+
pipe_log "Collecting pipelines finished since $SINCE"
|
|
89
|
+
|
|
90
|
+
PIPELINES=$(platform_completed_pipelines "$PIPE_METRICS_WINDOW_DAYS" || echo '[]')
|
|
91
|
+
pipe_log "Found $(printf '%s' "$PIPELINES" | jq -r 'length') pipeline(s)"
|
|
92
|
+
|
|
93
|
+
while read -r PIPELINE_ID; do
|
|
94
|
+
[ -n "$PIPELINE_ID" ] || continue
|
|
95
|
+
ARTIFACTS=$(platform_pipeline_artifacts "$PIPELINE_ID" || echo '[]')
|
|
96
|
+
while read -r JOB_ID; do
|
|
97
|
+
[ -n "$JOB_ID" ] || continue
|
|
98
|
+
ZIP="$TMP/$PIPELINE_ID-$JOB_ID.zip"
|
|
99
|
+
# One unreadable job must not abort the whole aggregation.
|
|
100
|
+
if platform_download_artifact "$JOB_ID" "$ZIP"; then
|
|
101
|
+
DEST="$RAW_DIR/$PIPELINE_ID-$JOB_ID"
|
|
102
|
+
mkdir -p "$DEST"
|
|
103
|
+
metrics_extract "$ZIP" "$DEST"
|
|
104
|
+
else
|
|
105
|
+
pipe_log "WARNING: artifacts for job $JOB_ID are unavailable, skipping"
|
|
106
|
+
fi
|
|
107
|
+
rm -f "$ZIP"
|
|
108
|
+
done < <(printf '%s' "$ARTIFACTS" | jq -r '.[] | select(.artifact_available) | .job_id')
|
|
109
|
+
done < <(printf '%s' "$PIPELINES" | jq -r '.[].id')
|
|
110
|
+
|
|
111
|
+
if ! platform_merge_requests_updated_since "$SINCE" > "$MRS_FILE"; then
|
|
112
|
+
pipe_log "WARNING: merge request list unavailable, continuing without it"
|
|
113
|
+
echo '[]' > "$MRS_FILE"
|
|
114
|
+
fi
|
|
115
|
+
platform_resolve_bot_user || true
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
SEARCH_DIRS=("$RAW_DIR")
|
|
119
|
+
if [ -d "$PIPE_METRICS_DIR" ] && [ "$PIPE_METRICS_DIR" != "$RAW_DIR" ]; then
|
|
120
|
+
SEARCH_DIRS+=("$PIPE_METRICS_DIR")
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
# ---------------------------------------------------------------------------
|
|
124
|
+
# Phase 2 and 3: invocation records and custom events
|
|
125
|
+
# ---------------------------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
# One record file that is truncated, or that is not a record at all, must cost
|
|
128
|
+
# only itself: parse each file on its own and skip the ones that do not read as
|
|
129
|
+
# a JSON object. Slurping every file in one pass would turn a single unreadable
|
|
130
|
+
# file into an empty, silently wrong snapshot.
|
|
131
|
+
#
|
|
132
|
+
# A job that consumes an upstream job's artifacts republishes the records it
|
|
133
|
+
# received, so the same record can arrive under several per-job directories.
|
|
134
|
+
# Record file names are unique per record, so identical duplicates are
|
|
135
|
+
# byte-identical and collapse on their content.
|
|
136
|
+
# A record is a JSON object carrying a string `agent` and a string `event`.
|
|
137
|
+
# Anything else found next to the records, for example a project manifest that
|
|
138
|
+
# a collected artifact happened to contain, is not a record and is ignored.
|
|
139
|
+
IS_RECORD='(type == "object") and ((.agent | type) == "string") and ((.event | type) == "string")'
|
|
140
|
+
|
|
141
|
+
collect_records() { # $1 = jq predicate selecting the record kind
|
|
142
|
+
local file
|
|
143
|
+
{
|
|
144
|
+
while IFS= read -r -d '' file; do
|
|
145
|
+
jq -c "select($IS_RECORD) | select($1)" "$file" 2>/dev/null || true
|
|
146
|
+
done < <(find "${SEARCH_DIRS[@]}" -type f -name '*.json' -print0 2>/dev/null)
|
|
147
|
+
} | jq -s -c 'unique_by(tojson)' 2>/dev/null || echo '[]'
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
INVOCATIONS=$(collect_records '(.event // "") | endswith("_invocation")')
|
|
151
|
+
EVENT_FILES=$(collect_records '((.event // "") | endswith("_invocation")) | not')
|
|
152
|
+
EVENT_LINES=$({
|
|
153
|
+
while IFS= read -r -d '' file; do
|
|
154
|
+
jq -c "select($IS_RECORD)" "$file" 2>/dev/null || true
|
|
155
|
+
done < <(find "${SEARCH_DIRS[@]}" -type f -name 'events.jsonl' -print0 2>/dev/null)
|
|
156
|
+
} | jq -s -c 'unique_by(tojson)' 2>/dev/null || echo '[]')
|
|
157
|
+
EVENTS=$(jq -nc --argjson a "$EVENT_FILES" --argjson b "$EVENT_LINES" '($a + $b) | unique_by(tojson)')
|
|
158
|
+
|
|
159
|
+
pipe_log "Collected $(printf '%s' "$INVOCATIONS" | jq -r 'length') invocation record(s)"
|
|
160
|
+
pipe_log "Collected $(printf '%s' "$EVENTS" | jq -r 'length') event(s)"
|
|
161
|
+
|
|
162
|
+
# ---------------------------------------------------------------------------
|
|
163
|
+
# Phase 4: merge request view with defect detection
|
|
164
|
+
# ---------------------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
RE_REVERT=$(re_escape "$PIPE_METRICS_DEFECT_REVERT")
|
|
167
|
+
RE_REGRESSION=$(re_escape "$PIPE_METRICS_DEFECT_REGRESSION")
|
|
168
|
+
RE_REINTRODUCE=$(re_escape "$PIPE_METRICS_DEFECT_REINTRODUCE")
|
|
169
|
+
|
|
170
|
+
MRS_VIEW=$(jq -c \
|
|
171
|
+
--arg revert "$RE_REVERT" \
|
|
172
|
+
--arg regression "$RE_REGRESSION" \
|
|
173
|
+
--arg reintroduce "$RE_REINTRODUCE" \
|
|
174
|
+
'[.[] | . as $mr | ($mr.description // "") as $body | {
|
|
175
|
+
iid: $mr.iid,
|
|
176
|
+
title: ($mr.title // ""),
|
|
177
|
+
state: ($mr.state // ""),
|
|
178
|
+
author: ($mr.author // ""),
|
|
179
|
+
labels: ($mr.labels // []),
|
|
180
|
+
created_at: ($mr.created_at // ""),
|
|
181
|
+
merged_at: $mr.merged_at,
|
|
182
|
+
description: $body,
|
|
183
|
+
defect_type: (
|
|
184
|
+
if ($body | test($revert; "i")) then "revert"
|
|
185
|
+
elif ($body | test($regression; "i")) then "regression-fix"
|
|
186
|
+
elif ($body | test($reintroduce; "i")) then "reintroduces"
|
|
187
|
+
else null end
|
|
188
|
+
),
|
|
189
|
+
references: [
|
|
190
|
+
$body
|
|
191
|
+
| scan("(?:" + $revert + "|" + $regression + "|" + $reintroduce + ")[[:space:]]*[!#]?([0-9]+)"; "i")[]?
|
|
192
|
+
| select(. != null)
|
|
193
|
+
| tonumber
|
|
194
|
+
]
|
|
195
|
+
}]' "$MRS_FILE")
|
|
196
|
+
|
|
197
|
+
pipe_log "Processed $(printf '%s' "$MRS_VIEW" | jq -r 'length') merge request(s)"
|
|
198
|
+
|
|
199
|
+
# ---------------------------------------------------------------------------
|
|
200
|
+
# Phase 5: cost and tokens per agent
|
|
201
|
+
# ---------------------------------------------------------------------------
|
|
202
|
+
|
|
203
|
+
BY_AGENT=$(printf '%s' "$INVOCATIONS" | jq -c '
|
|
204
|
+
group_by(.agent)
|
|
205
|
+
| map({
|
|
206
|
+
key: (.[0].agent // "unknown"),
|
|
207
|
+
value: {
|
|
208
|
+
count: length,
|
|
209
|
+
cost_usd: (map(.total_cost_usd // 0) | add // 0),
|
|
210
|
+
input_tokens: (map(.input_tokens // 0) | add // 0),
|
|
211
|
+
output_tokens: (map(.output_tokens // 0) | add // 0),
|
|
212
|
+
cache_read: (map(.cache_read_tokens // 0) | add // 0),
|
|
213
|
+
cache_creation: (map(.cache_creation_tokens // 0) | add // 0)
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
| from_entries')
|
|
217
|
+
|
|
218
|
+
# ---------------------------------------------------------------------------
|
|
219
|
+
# Phase 5b: split each agent's cost by the author of the change it acted on
|
|
220
|
+
#
|
|
221
|
+
# Review jobs run on bot and human merge requests alike, so the cost of the
|
|
222
|
+
# autonomous loop is only separable when each invocation is attributed to the
|
|
223
|
+
# author of the merge request it reviewed. Invocations without a merge request
|
|
224
|
+
# are "none", and a merge request outside this window is "unknown".
|
|
225
|
+
# ---------------------------------------------------------------------------
|
|
226
|
+
|
|
227
|
+
AUTHOR_TAG='
|
|
228
|
+
($mrs[0] | map({key: (.iid | tostring), value: .author}) | from_entries) as $authors
|
|
229
|
+
| map(. + {author_type: (
|
|
230
|
+
if (.mr_iid == null) then "none"
|
|
231
|
+
else ($authors[(.mr_iid | tostring)] // null) as $author
|
|
232
|
+
| if $author == null then "unknown"
|
|
233
|
+
elif $author == $bot then "bot"
|
|
234
|
+
else "human" end
|
|
235
|
+
end)})'
|
|
236
|
+
|
|
237
|
+
BY_AGENT_AUTHOR=$(printf '%s' "$INVOCATIONS" | jq -c \
|
|
238
|
+
--argjson mrs "[$MRS_VIEW]" --arg bot "${PIPE_BOT_USER:-}" "
|
|
239
|
+
$AUTHOR_TAG
|
|
240
|
+
| group_by(.agent)
|
|
241
|
+
| map({
|
|
242
|
+
key: (.[0].agent // \"unknown\"),
|
|
243
|
+
value: (group_by(.author_type) | map({
|
|
244
|
+
key: .[0].author_type,
|
|
245
|
+
value: {
|
|
246
|
+
count: length,
|
|
247
|
+
cost_usd: (map(.total_cost_usd // 0) | add // 0),
|
|
248
|
+
input_tokens: (map(.input_tokens // 0) | add // 0),
|
|
249
|
+
output_tokens: (map(.output_tokens // 0) | add // 0),
|
|
250
|
+
cache_read: (map(.cache_read_tokens // 0) | add // 0),
|
|
251
|
+
cache_creation: (map(.cache_creation_tokens // 0) | add // 0)
|
|
252
|
+
}
|
|
253
|
+
}) | from_entries)
|
|
254
|
+
})
|
|
255
|
+
| from_entries")
|
|
256
|
+
|
|
257
|
+
REVIEW_COST_BY_AUTHOR=$(printf '%s' "$INVOCATIONS" | jq -c \
|
|
258
|
+
--argjson mrs "[$MRS_VIEW]" --arg bot "${PIPE_BOT_USER:-}" \
|
|
259
|
+
--arg review_agents "$PIPE_METRICS_REVIEW_AGENTS" "
|
|
260
|
+
(\$review_agents | split(\",\") | map(select(length > 0))) as \$reviewers
|
|
261
|
+
| $AUTHOR_TAG
|
|
262
|
+
| map(select((.agent // \"\") | IN(\$reviewers[])))
|
|
263
|
+
| {
|
|
264
|
+
bot: (map(select(.author_type == \"bot\") | .total_cost_usd // 0) | add // 0),
|
|
265
|
+
human: (map(select(.author_type == \"human\") | .total_cost_usd // 0) | add // 0),
|
|
266
|
+
unknown: (map(select(.author_type == \"unknown\") | .total_cost_usd // 0) | add // 0)
|
|
267
|
+
}")
|
|
268
|
+
|
|
269
|
+
# ---------------------------------------------------------------------------
|
|
270
|
+
# Phase 6: escalation failure categories
|
|
271
|
+
# ---------------------------------------------------------------------------
|
|
272
|
+
|
|
273
|
+
FAILURE_CATEGORIES=$(printf '%s' "$EVENTS" | jq -c '
|
|
274
|
+
[.[] | select(.event == "dev_stuck") | (.failure_category // .category // "other")]
|
|
275
|
+
| group_by(.)
|
|
276
|
+
| map({key: .[0], value: length})
|
|
277
|
+
| from_entries')
|
|
278
|
+
|
|
279
|
+
# ---------------------------------------------------------------------------
|
|
280
|
+
# Phase 6b: post-merge defects counted from commit subjects
|
|
281
|
+
#
|
|
282
|
+
# The three subjects are conventions, not guesses: they are configured in the
|
|
283
|
+
# project's pipeline config so the aggregator and the humans writing commits
|
|
284
|
+
# agree. Offline runs have no repository history to scan.
|
|
285
|
+
# ---------------------------------------------------------------------------
|
|
286
|
+
|
|
287
|
+
count_subject() { # $1 = escaped subject prefix, stdin = commit messages
|
|
288
|
+
local matches
|
|
289
|
+
matches=$(grep -cE "^$1" || true)
|
|
290
|
+
printf '%s' "${matches:-0}"
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if [ "${OFFLINE:-false}" = "true" ]; then
|
|
294
|
+
POST_MERGE_DEFECTS='{"reverts":0,"fixes":0,"reintroduces":0}'
|
|
295
|
+
else
|
|
296
|
+
GIT_LOG_FILE="$TMP/commit-subjects.txt"
|
|
297
|
+
git log --since="$SINCE" --format=%B > "$GIT_LOG_FILE" 2>/dev/null || : > "$GIT_LOG_FILE"
|
|
298
|
+
DEFECT_REVERTS=$(count_subject "$RE_REVERT" < "$GIT_LOG_FILE")
|
|
299
|
+
DEFECT_FIXES=$(count_subject "$RE_REGRESSION" < "$GIT_LOG_FILE")
|
|
300
|
+
DEFECT_REINTRODUCES=$(count_subject "$RE_REINTRODUCE" < "$GIT_LOG_FILE")
|
|
301
|
+
pipe_log "Post-merge defects: reverts=$DEFECT_REVERTS fixes=$DEFECT_FIXES reintroduces=$DEFECT_REINTRODUCES"
|
|
302
|
+
POST_MERGE_DEFECTS=$(jq -nc \
|
|
303
|
+
--argjson reverts "$DEFECT_REVERTS" \
|
|
304
|
+
--argjson fixes "$DEFECT_FIXES" \
|
|
305
|
+
--argjson reintroduces "$DEFECT_REINTRODUCES" \
|
|
306
|
+
'{reverts: $reverts, fixes: $fixes, reintroduces: $reintroduces}')
|
|
307
|
+
fi
|
|
308
|
+
|
|
309
|
+
# ---------------------------------------------------------------------------
|
|
310
|
+
# Phase 7: write the snapshot
|
|
311
|
+
# ---------------------------------------------------------------------------
|
|
312
|
+
|
|
313
|
+
OUT_FILE="$PIPE_METRICS_OUTPUT_DIR/$SNAPSHOT_DATE.json"
|
|
314
|
+
jq -n \
|
|
315
|
+
--arg date "$SNAPSHOT_DATE" \
|
|
316
|
+
--arg bot "${PIPE_BOT_USER:-}" \
|
|
317
|
+
--argjson invocations "$INVOCATIONS" \
|
|
318
|
+
--argjson events "$EVENTS" \
|
|
319
|
+
--argjson mrs "$MRS_VIEW" \
|
|
320
|
+
--argjson by_agent "$BY_AGENT" \
|
|
321
|
+
--argjson by_agent_author "$BY_AGENT_AUTHOR" \
|
|
322
|
+
--argjson review_cost_by_author "$REVIEW_COST_BY_AUTHOR" \
|
|
323
|
+
--argjson failure_categories "$FAILURE_CATEGORIES" \
|
|
324
|
+
--argjson post_merge_defects "$POST_MERGE_DEFECTS" \
|
|
325
|
+
'{
|
|
326
|
+
date: $date,
|
|
327
|
+
invocations: $invocations,
|
|
328
|
+
events: $events,
|
|
329
|
+
mrs: $mrs,
|
|
330
|
+
totals: {
|
|
331
|
+
cost_usd: ($invocations | map(.total_cost_usd // 0) | add // 0),
|
|
332
|
+
input_tokens: ($invocations | map(.input_tokens // 0) | add // 0),
|
|
333
|
+
output_tokens: ($invocations | map(.output_tokens // 0) | add // 0),
|
|
334
|
+
cache_read_tokens: ($invocations | map(.cache_read_tokens // 0) | add // 0),
|
|
335
|
+
cache_creation_tokens: ($invocations | map(.cache_creation_tokens // 0) | add // 0),
|
|
336
|
+
operations_count: ($invocations | length),
|
|
337
|
+
merged_mrs: ($mrs | map(select(.state == "merged")) | length),
|
|
338
|
+
bot_mrs: ($mrs | map(select($bot != "" and .author == $bot)) | length),
|
|
339
|
+
defect_references: ($mrs | map(.references | length) | add // 0),
|
|
340
|
+
orchestrator_decisions: ($events | map(select(.event == "orchestrator_decision")) | length),
|
|
341
|
+
review_rounds: ($events | map(select(.event == "review_findings")) | length),
|
|
342
|
+
dev_stuck: ($events | map(select(.event == "dev_stuck")) | length)
|
|
343
|
+
},
|
|
344
|
+
by_agent: $by_agent,
|
|
345
|
+
by_agent_author: $by_agent_author,
|
|
346
|
+
review_cost_by_author: $review_cost_by_author,
|
|
347
|
+
failure_categories: $failure_categories,
|
|
348
|
+
post_merge_defects: $post_merge_defects
|
|
349
|
+
}' > "$OUT_FILE"
|
|
350
|
+
|
|
351
|
+
pipe_log "Wrote $OUT_FILE"
|
|
352
|
+
|
|
353
|
+
# ---------------------------------------------------------------------------
|
|
354
|
+
# Phase 8: commit the snapshot, only when the project opted in
|
|
355
|
+
# ---------------------------------------------------------------------------
|
|
356
|
+
|
|
357
|
+
if [ "${OFFLINE:-false}" != "true" ] && [ "$PIPE_METRICS_COMMIT_REPO" = "true" ]; then
|
|
358
|
+
# The snapshot is written on whatever ref the schedule ran, so it is untracked
|
|
359
|
+
# there. Move it aside before switching: if the target branch already carries a
|
|
360
|
+
# snapshot for this date, git would refuse to overwrite an untracked file and
|
|
361
|
+
# the job would fail with a git error instead of reaching the no-op branch.
|
|
362
|
+
pipe_git_identity
|
|
363
|
+
git fetch origin "$PIPE_TARGET_BRANCH"
|
|
364
|
+
cp "$OUT_FILE" "$TMP/snapshot.json"
|
|
365
|
+
git checkout -f -B "$PIPE_TARGET_BRANCH" "origin/$PIPE_TARGET_BRANCH"
|
|
366
|
+
mkdir -p "$PIPE_METRICS_OUTPUT_DIR"
|
|
367
|
+
cp "$TMP/snapshot.json" "$OUT_FILE"
|
|
368
|
+
git add "$OUT_FILE"
|
|
369
|
+
if git diff --cached --quiet; then
|
|
370
|
+
pipe_log "Snapshot already current, nothing to commit"
|
|
371
|
+
else
|
|
372
|
+
# [skip ci] keeps a data commit from starting another pipeline.
|
|
373
|
+
git commit -m "chore(metrics): snapshot $SNAPSHOT_DATE [skip ci]"
|
|
374
|
+
git push origin "$PIPE_TARGET_BRANCH"
|
|
375
|
+
pipe_log "Pushed snapshot to $PIPE_TARGET_BRANCH"
|
|
376
|
+
fi
|
|
377
|
+
fi
|
|
@@ -119,12 +119,18 @@ 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"
|
|
126
126
|
DECISION=$(pipe_get_env "$TRIAGE_ENV" TRIAGE_DECISION)
|
|
127
127
|
if [ -z "$DECISION" ] || [ ! -s "$TRIAGE_JSON" ]; then
|
|
128
|
+
if pipe_is_credit_exhaustion_files "$PIPE_AGENT_STDERR" "$TRIAGE_JSON"; then
|
|
129
|
+
pipe_failure_log_notice_files "triage of issue #$IID" "triage this issue by hand" \
|
|
130
|
+
"$PIPE_AGENT_STDERR" "$TRIAGE_JSON" | while IFS= read -r line; do pipe_log "$line"; done
|
|
131
|
+
pipe_log " model credit exhausted; aborting triage batch"
|
|
132
|
+
break
|
|
133
|
+
fi
|
|
128
134
|
pipe_log " no usable triage output for #$IID, skipping"
|
|
129
135
|
continue
|
|
130
136
|
fi
|
|
@@ -140,26 +146,30 @@ while IFS= read -r ISSUE; do
|
|
|
140
146
|
;;
|
|
141
147
|
|
|
142
148
|
ask)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
+
QUESTION_FILE="$PIPE_CONTEXT_DIR/triage-question.md"
|
|
150
|
+
jq -r '.question // "Please provide more detail before this issue can be implemented."' "$TRIAGE_JSON" > "$QUESTION_FILE"
|
|
151
|
+
COMMENT_FILE="$PIPE_CONTEXT_DIR/triage-clarification.md"
|
|
152
|
+
{
|
|
153
|
+
printf '🤖 **The pipeline needs clarification before implementing:**\n\n'
|
|
154
|
+
cat "$QUESTION_FILE"
|
|
155
|
+
printf '\n\nPut the complete spec in the issue **description**, not in a comment: the pipeline reads only the description.\n'
|
|
156
|
+
} > "$COMMENT_FILE"
|
|
157
|
+
issue_comment_file "$IID" "$COMMENT_FILE"
|
|
149
158
|
issue_set_labels "$IID" "$PIPE_LABEL_BLOCKER" "$PIPE_LABEL_READY"
|
|
150
|
-
emit_decision_metric "$IID" clarification-needed ask "
|
|
159
|
+
emit_decision_metric "$IID" clarification-needed ask "Clarification requested"
|
|
151
160
|
pipe_log " asked for clarification on #$IID"
|
|
152
161
|
;;
|
|
153
162
|
|
|
154
163
|
decompose_failed)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
$
|
|
161
|
-
|
|
162
|
-
|
|
164
|
+
SUGGEST_FILE="$PIPE_CONTEXT_DIR/triage-suggestion.md"
|
|
165
|
+
jq -r '.suggestion // "Please split this issue into smaller pieces."' "$TRIAGE_JSON" > "$SUGGEST_FILE"
|
|
166
|
+
COMMENT_FILE="$PIPE_CONTEXT_DIR/triage-decomposition-suggestion.md"
|
|
167
|
+
{
|
|
168
|
+
printf '**This issue is clear but too large for a single agent session.**\n\nSuggested decomposition:\n\n'
|
|
169
|
+
cat "$SUGGEST_FILE"
|
|
170
|
+
printf '\n\nPlease split it into smaller issues and label each `%s`.\n' "$PIPE_LABEL_READY"
|
|
171
|
+
} > "$COMMENT_FILE"
|
|
172
|
+
issue_comment_file "$IID" "$COMMENT_FILE"
|
|
163
173
|
issue_set_labels "$IID" "$PIPE_LABEL_BLOCKER" "$PIPE_LABEL_READY"
|
|
164
174
|
emit_decision_metric "$IID" clarification-needed ask "Too large; decomposition suggested"
|
|
165
175
|
pipe_log " posted decomposition suggestion for #$IID"
|
|
@@ -167,13 +177,14 @@ Please split it into smaller issues and label each \`$PIPE_LABEL_READY\`."
|
|
|
167
177
|
|
|
168
178
|
decompose)
|
|
169
179
|
# Re-validate deterministically before mutating the platform.
|
|
170
|
-
if !
|
|
180
|
+
if ! pipe_validate_decomposition_file "$TRIAGE_JSON"; then
|
|
171
181
|
pipe_log " decomposition failed the gate: $DECOMP_REJECT_REASON"
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
182
|
+
COMMENT_FILE="$PIPE_CONTEXT_DIR/triage-decomposition-rejected.md"
|
|
183
|
+
{
|
|
184
|
+
printf '**This issue is too large and automatic decomposition did not produce a valid split.**\n\nReason: %s\n\n' "$DECOMP_REJECT_REASON"
|
|
185
|
+
printf 'Please split it manually into smaller issues and label each `%s`.\n' "$PIPE_LABEL_READY"
|
|
186
|
+
} > "$COMMENT_FILE"
|
|
187
|
+
issue_comment_file "$IID" "$COMMENT_FILE"
|
|
177
188
|
issue_set_labels "$IID" "$PIPE_LABEL_BLOCKER" "$PIPE_LABEL_READY"
|
|
178
189
|
emit_decision_metric "$IID" clarification-needed ask "Decomposition rejected by gate"
|
|
179
190
|
continue
|
|
@@ -181,7 +192,8 @@ Please split it manually into smaller issues and label each \`$PIPE_LABEL_READY\
|
|
|
181
192
|
|
|
182
193
|
CONFIDENCE=$(pipe_get_env "$TRIAGE_ENV" TRIAGE_CONFIDENCE)
|
|
183
194
|
CONFIDENCE=${CONFIDENCE:-$(jq -r '.confidence // "low"' "$TRIAGE_JSON")}
|
|
184
|
-
|
|
195
|
+
REASON_FILE="$PIPE_CONTEXT_DIR/triage-reason.md"
|
|
196
|
+
jq -r '.reason // ""' "$TRIAGE_JSON" > "$REASON_FILE"
|
|
185
197
|
SUB_COUNT=$(jq '.sub_issues | length' "$TRIAGE_JSON")
|
|
186
198
|
pipe_log " decomposing #$IID into $SUB_COUNT sub-issues (confidence=$CONFIDENCE)"
|
|
187
199
|
|
|
@@ -189,11 +201,14 @@ Please split it manually into smaller issues and label each \`$PIPE_LABEL_READY\
|
|
|
189
201
|
declare -A KEY_TO_IID=()
|
|
190
202
|
CREATED=()
|
|
191
203
|
FAILED=false
|
|
204
|
+
SUB_INDEX=0
|
|
192
205
|
while IFS= read -r SUB; do
|
|
193
|
-
SUB_KEY=$(
|
|
194
|
-
SUB_TITLE=$(
|
|
195
|
-
|
|
196
|
-
|
|
206
|
+
SUB_KEY=$(printf '%s' "$SUB" | jq -r '.key')
|
|
207
|
+
SUB_TITLE=$(printf '%s' "$SUB" | jq -r '.title')
|
|
208
|
+
SUB_SPEC_FILE="$PIPE_CONTEXT_DIR/sub-issue-${SUB_INDEX}.md"
|
|
209
|
+
printf '%s' "$SUB" | jq -r '.spec_markdown' > "$SUB_SPEC_FILE"
|
|
210
|
+
SUB_INDEX=$((SUB_INDEX + 1))
|
|
211
|
+
HAS_DEPS=$(printf '%s' "$SUB" | jq '(.depends_on // []) | length > 0')
|
|
197
212
|
if [ "$CONFIDENCE" = "high" ] && [ "$HAS_DEPS" = "false" ]; then
|
|
198
213
|
SUB_LABEL="$PIPE_LABEL_READY"
|
|
199
214
|
elif [ "$CONFIDENCE" = "low" ]; then
|
|
@@ -201,7 +216,7 @@ Please split it manually into smaller issues and label each \`$PIPE_LABEL_READY\
|
|
|
201
216
|
else
|
|
202
217
|
SUB_LABEL="$PIPE_LABEL_BLOCKED"
|
|
203
218
|
fi
|
|
204
|
-
NEW_IID=$(
|
|
219
|
+
NEW_IID=$(issue_create_file "$SUB_TITLE" "$SUB_SPEC_FILE" "$SUB_LABEL")
|
|
205
220
|
if [ -z "$NEW_IID" ]; then
|
|
206
221
|
pipe_log " ERROR: failed to create sub-issue for key '$SUB_KEY'"
|
|
207
222
|
FAILED=true
|
|
@@ -214,7 +229,10 @@ Please split it manually into smaller issues and label each \`$PIPE_LABEL_READY\
|
|
|
214
229
|
|
|
215
230
|
if [ "$FAILED" = "true" ]; then
|
|
216
231
|
CHILD_LIST=$(printf '#%s ' "${CREATED[@]:-}")
|
|
217
|
-
|
|
232
|
+
COMMENT_FILE="$PIPE_CONTEXT_DIR/triage-decomposition-partial.md"
|
|
233
|
+
printf '**Auto-decomposition partially failed.** Created so far: %s. Relabeled `%s` to prevent re-decomposition. Please finish manually.\n' \
|
|
234
|
+
"${CHILD_LIST:-none}" "$PIPE_LABEL_BLOCKER" > "$COMMENT_FILE"
|
|
235
|
+
issue_comment_file "$IID" "$COMMENT_FILE"
|
|
218
236
|
issue_set_labels "$IID" "$PIPE_LABEL_BLOCKER" "$PIPE_LABEL_READY"
|
|
219
237
|
unset KEY_TO_IID
|
|
220
238
|
continue
|
|
@@ -238,15 +256,15 @@ Please split it manually into smaller issues and label each \`$PIPE_LABEL_READY\
|
|
|
238
256
|
# so the GitHub release poll can find them without native links).
|
|
239
257
|
CHILD_LIST=$(printf '#%s ' "${CREATED[@]}")
|
|
240
258
|
issue_set_labels "$IID" "$PIPE_LABEL_DECOMPOSED" "$PIPE_LABEL_READY"
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
259
|
+
COMMENT_FILE="$PIPE_CONTEXT_DIR/triage-decomposed.md"
|
|
260
|
+
{
|
|
261
|
+
printf '**Auto-decomposed by the pipeline** (confidence: `%s`).\n\nReason: ' "$CONFIDENCE"
|
|
262
|
+
cat "$REASON_FILE"
|
|
263
|
+
printf '\n\nSplit into %s sub-issues: %s\n\n' "$SUB_COUNT" "$CHILD_LIST"
|
|
264
|
+
printf 'Sub-issues with prerequisites are labeled `%s` and are promoted automatically when their blockers close. This parent closes automatically when all children close.\n\n' "$PIPE_LABEL_BLOCKED"
|
|
265
|
+
printf '<!-- pipe-children: %s -->\n' "$CHILD_LIST"
|
|
266
|
+
} > "$COMMENT_FILE"
|
|
267
|
+
issue_comment_file "$IID" "$COMMENT_FILE"
|
|
250
268
|
emit_decision_metric "$IID" actionable decompose "Decomposed into $SUB_COUNT sub-issues (confidence: $CONFIDENCE)" \
|
|
251
269
|
"$(jq -n --argjson sc "$SUB_COUNT" --arg conf "$CONFIDENCE" '{sub_issue_count: $sc, confidence: $conf}')"
|
|
252
270
|
|
|
@@ -38,7 +38,17 @@ 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
|
+
if [ ! -s "$PIPE_CONTEXT_DIR/postmortem.md" ]; then
|
|
43
|
+
if [ -s "$PIPE_AGENT_STDERR" ]; then
|
|
44
|
+
pipe_failure_notice_files "postmortem diagnosis" "investigate this failure by hand" \
|
|
45
|
+
"$PIPE_AGENT_STDERR" > "$PIPE_CONTEXT_DIR/postmortem.md"
|
|
46
|
+
if pipe_is_credit_exhaustion_files "$PIPE_AGENT_STDERR"; then
|
|
47
|
+
platform_post_comment_file "$PIPE_CONTEXT_DIR/postmortem.md"
|
|
48
|
+
exit 0
|
|
49
|
+
fi
|
|
50
|
+
fi
|
|
51
|
+
fi
|
|
42
52
|
fi
|
|
43
53
|
|
|
44
54
|
# 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"
|
|
@@ -59,26 +59,37 @@ pipe_metric_event review-fix review_findings_applied \
|
|
|
59
59
|
"$(jq -n --argjson a "${RF_APPLIED:-0}" --argjson d "${RF_DISMISSED:-0}" '{applied: $a, dismissed: $d}')"
|
|
60
60
|
|
|
61
61
|
post_dismissed() {
|
|
62
|
-
local
|
|
63
|
-
|
|
64
|
-
[ -
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
62
|
+
local dismissed_file comment_file
|
|
63
|
+
dismissed_file="$PIPE_CONTEXT_DIR/review-dismissed.md"
|
|
64
|
+
jq -er '.dismissed // [] | map("- **" + .finding + "**: " + .reason) | join("\n") | select(length > 0)' \
|
|
65
|
+
"$PIPE_RESULT_REVIEWFIX" > "$dismissed_file" 2>/dev/null || true
|
|
66
|
+
[ -s "$dismissed_file" ] || return 0
|
|
67
|
+
comment_file="$PIPE_CONTEXT_DIR/review-dismissed-comment.md"
|
|
68
|
+
{
|
|
69
|
+
printf '**Review findings dismissed**\n\nThe auto-fix agent determined the following findings were false positives and require no code change:\n\n'
|
|
70
|
+
cat "$dismissed_file"
|
|
71
|
+
} > "$comment_file"
|
|
72
|
+
platform_post_comment_file "$comment_file"
|
|
70
73
|
}
|
|
71
74
|
|
|
72
75
|
# 7. Act on the outcome.
|
|
73
76
|
if [ "$RF_ESCALATE" = "true" ]; then
|
|
74
77
|
pipe_log "Skill signalled escalation"
|
|
78
|
+
if pipe_is_credit_exhaustion_files "$PIPE_AGENT_STDERR"; then
|
|
79
|
+
COMMENT_FILE="$PIPE_CONTEXT_DIR/review-fix-credit-exhausted.md"
|
|
80
|
+
pipe_failure_notice_files "review fixes" "resolve the review findings by hand" \
|
|
81
|
+
"$PIPE_AGENT_STDERR" > "$COMMENT_FILE"
|
|
82
|
+
platform_post_comment_file "$COMMENT_FILE"
|
|
83
|
+
exit 0
|
|
84
|
+
fi
|
|
75
85
|
pipe_apply_escalation "$CATEGORY" "review-fix"
|
|
76
86
|
elif [ "$COMMIT_MADE" = "true" ]; then
|
|
77
87
|
if pipe_rebase_and_push; then
|
|
78
88
|
ATTEMPT=$((FIX_COUNT_BEFORE + 1))
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
89
|
+
COMMENT_FILE="$PIPE_CONTEXT_DIR/review-fix-applied.md"
|
|
90
|
+
printf '**Auto-fix applied (attempt %s/%s)**\n\nFixes committed for confirmed bugs from the code review. A new pipeline will run to verify and re-review the changes.\n' \
|
|
91
|
+
"$ATTEMPT" "$PIPE_FIX_LOOP_CAP" > "$COMMENT_FILE"
|
|
92
|
+
platform_post_comment_file "$COMMENT_FILE"
|
|
82
93
|
post_dismissed
|
|
83
94
|
fi
|
|
84
95
|
elif [ "$RF_CHANGED" = "false" ]; then
|