@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.
Files changed (69) hide show
  1. package/README.md +27 -5
  2. package/package.json +2 -2
  3. package/payload/INSTALL.md +14 -5
  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 +220 -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/agent-architect.sh +233 -0
  36. package/payload/ci-templates/scripts/code.sh +35 -35
  37. package/payload/ci-templates/scripts/codebase-audit.sh +252 -0
  38. package/payload/ci-templates/scripts/coverage-ratchet.sh +77 -0
  39. package/payload/ci-templates/scripts/cve-fix.sh +246 -0
  40. package/payload/ci-templates/scripts/docs-sync.sh +225 -0
  41. package/payload/ci-templates/scripts/e2e-test-gen.sh +201 -0
  42. package/payload/ci-templates/scripts/lib/failure-notice.sh +104 -0
  43. package/payload/ci-templates/scripts/lib/issue-loop.sh +155 -40
  44. package/payload/ci-templates/scripts/lib/pipeline-common.sh +233 -31
  45. package/payload/ci-templates/scripts/lib/platform.sh +270 -13
  46. package/payload/ci-templates/scripts/lib/usage-capture-omp.sh +123 -0
  47. package/payload/ci-templates/scripts/lib/usage-capture.sh +7 -1
  48. package/payload/ci-templates/scripts/metrics-snapshot.sh +377 -0
  49. package/payload/ci-templates/scripts/orchestrate.sh +56 -38
  50. package/payload/ci-templates/scripts/postmortem.sh +11 -1
  51. package/payload/ci-templates/scripts/review-fix.sh +23 -12
  52. package/payload/ci-templates/scripts/review.sh +12 -8
  53. package/payload/ci-templates/scripts/test-fix.sh +8 -1
  54. package/payload/skills/agent-architect/SKILL.md +45 -0
  55. package/payload/skills/codebase-audit/SKILL.md +84 -0
  56. package/payload/skills/cve-fix/SKILL.md +98 -0
  57. package/payload/skills/docs-sync/SKILL.md +74 -0
  58. package/payload/skills/e2e-test-gen/SKILL.md +65 -0
  59. package/payload/skills/fix-review-findings/SKILL.md +3 -3
  60. package/payload/skills/fix-tests/SKILL.md +4 -4
  61. package/payload/skills/implement-issue/SKILL.md +1 -1
  62. package/payload/skills/init-pipeline-config/SKILL.md +4 -4
  63. package/payload/skills/postmortem-mr/SKILL.md +1 -1
  64. package/payload/skills/review-mr/SKILL.md +1 -1
  65. package/payload/skills/triage-issue/SKILL.md +2 -2
  66. package/payload/templates/memory-index.template.md +34 -0
  67. package/payload/templates/pipeline-config.template.md +11 -1
  68. package/payload/templates/review_suppressions.template.md +55 -0
  69. package/payload/templates/spec-issue.template.md +39 -7
@@ -48,6 +48,7 @@ variables:
48
48
  # PIPE_PLATFORM is autodetected from GITLAB_CI now, so setting it is optional.
49
49
  # It is kept here as an explicit, harmless override.
50
50
  PIPE_PLATFORM: "gitlab"
51
+ PIPE_HARNESS: "claude" # "claude" (default, Claude Code CLI) or "omp" (Oh My Pi + OpenRouter)
51
52
  PIPE_CONFIG_PATH: ".claude/pipeline-config.md"
52
53
  PIPE_CONTEXT_DIR: "build/pipeline"
53
54
 
@@ -81,26 +82,83 @@ variables:
81
82
  PIPE_VERIFY_CMD: "" # compile + unit tests, run after a fix
82
83
  PIPE_COMPILE_CMD: "" # compile only (defaults to PIPE_VERIFY_CMD)
83
84
  PIPE_TEST_REPORT_GLOB: "" # e.g. build/test-results/**/TEST-*.xml
85
+ PIPE_COVERAGE_RATCHET: "0" # set to "1" to gate MR coverage against the target branch
86
+ PIPE_COVERAGE_REPORT: "" # path to the coverage report generated by the project
87
+ PIPE_COVERAGE_REPORT_KIND: "jacoco" # report parser; only jacoco is currently supported
84
88
  PIPE_COVERAGE_SIGNAL: "" # path to a coverage-drop signal file
89
+ # To enable the gate, configure both report and signal paths. Its baseline is
90
+ # the target branch's latest successful pipeline `coverage` value; the
91
+ # project's test job must publish that value (through `coverage:` or a report)
92
+ # or the baseline is 0 and no drop can fire.
93
+
94
+
95
+ # --- CVE remediation (optional) -------------------------------------------
96
+ # A project-local security scanner supplies PIPE_CVE_LIST. The platform does
97
+ # not select a scanner or prescribe its severity filtering.
98
+ PIPE_CVE_FIX: "0" # set to "1" to enable CVE remediation on MR/PR pipelines
99
+ PIPE_DEPENDENCY_MANIFEST: "build.gradle"
100
+ PIPE_CVE_SUPPRESSIONS: "owasp-suppressions.xml"
101
+
102
+ # --- Issue-driven E2E test generation (optional) --------------------------
103
+ # A scheduled run selects test-ready issues. PIPE_E2E_VERIFY_CMD must be the
104
+ # project's explicit E2E command and accept the generated test path as its
105
+ # final argument. This generic template does not choose a framework, runtime
106
+ # image, install command, credentials, or URL.
107
+ PIPE_E2E_TEST_GEN: "0"
108
+ PIPE_LABEL_TESTREADY: "test-ready"
109
+ PIPE_LABEL_E2E_SCOPE: ""
110
+ PIPE_E2E_TEST_DIR: "e2e/tests"
111
+ PIPE_E2E_BASE_URL: ""
112
+ PIPE_E2E_VERIFY_CMD: ""
113
+
114
+ # --- Documentation sync (optional) ----------------------------------------
115
+ PIPE_DOCS_SYNC: "0" # set to "1" to enable the docs-sync job
116
+ PIPE_DOCS_ROOTS: "docs,CLAUDE.md,.claude/memory" # paths docs-sync may edit and commit
117
+
118
+ # --- Codebase audit (optional) ---------------------------------------------
119
+ PIPE_CODEBASE_AUDIT: "0" # set to "1" in a schedule to run the periodic audit
120
+ PIPE_LABEL_AUDIT: "codebase-audit" # label on the findings issue and its auto-fix MR/PR
121
+
122
+ # --- Agent architect (optional) --------------------------------------------
123
+ # A scheduled evidence-backed review of agent definitions. Default to
124
+ # report-only; set this false or 0 only after reviewing a dry-run report.
125
+ PIPE_AGENT_ARCHITECT: "0"
126
+ PIPE_ARCHITECT_WINDOW_DAYS: "7"
127
+ PIPE_ARCHITECT_DRY_RUN: "true"
128
+ PIPE_LABEL_IMPROVEMENT: "pipe-improvement"
129
+
130
+ # --- Metrics aggregation (optional) ---------------------------------------
131
+ PIPE_METRICS_SNAPSHOT: "0" # set to "1" in a schedule to aggregate
132
+ PIPE_METRICS_OUTPUT_DIR: "data/metrics/snapshots"
133
+ PIPE_METRICS_WINDOW_DAYS: "1"
134
+ PIPE_METRICS_COMMIT_REPO: "false" # "true" also commits the snapshot
135
+ PIPE_METRICS_REVIEW_AGENTS: "code-reviewer,review-verdict,review-fix"
136
+ PIPE_METRICS_DEFECT_REVERT: "Reverts !"
137
+ PIPE_METRICS_DEFECT_REGRESSION: "Fixes regression from !"
138
+ PIPE_METRICS_DEFECT_REINTRODUCE: "Reintroduces work from !"
139
+ PIPE_METRICS_SNAPSHOT_DATE: "" # override the snapshot date, replay only
140
+ PIPE_METRICS_RAW_DIR: "" # OFFLINE only: pre-staged records
141
+ PIPE_METRICS_MRS_FILE: "" # OFFLINE only: merge request list
85
142
 
86
143
  # --- Commit subjects (drive the fix-loop cap counters) ---------------------
87
144
  PIPE_COMMIT_TESTFIX: "Fix test errors"
88
145
  PIPE_COMMIT_REVIEWFIX: "Fix review findings"
89
146
  PIPE_COMMIT_COVERAGE: "Add coverage tests"
147
+ PIPE_COMMIT_DOCSSYNC: "Update docs per docs-sync findings"
148
+ PIPE_COMMIT_AUDIT: "docs: apply convention audit findings"
149
+ PIPE_COMMIT_CVEFIX: "Update dependencies to resolve CVEs"
90
150
 
91
151
  # --- Caps + models ---------------------------------------------------------
92
152
  PIPE_FIX_LOOP_CAP: "2"
93
- # Main-loop model per job group, passed as `claude --model` by the runner.
94
- # Aliases track the current model, same convention as the agent frontmatter.
95
- # Subagents keep the models from their own frontmatter.
96
- PIPE_MODEL_TRIAGE: "haiku"
97
- PIPE_MODEL_CODE: "sonnet"
98
- PIPE_MODEL_REVIEW: "sonnet"
153
+ # Main-loop models are passed as --model by the active harness. Defaults live
154
+ # in `pipeline-common.sh` under `scripts/lib/`. Users may explicitly set any
155
+ # PIPE_MODEL_* project CI variable. Subagents retain their frontmatter models.
99
156
  PIPE_AGENT_ENV_ALLOWLIST: "" # credential-like env names the agent-run build must receive (comma-separated; prefer empty)
100
157
 
101
158
  # --- Result files (defaults live under PIPE_CONTEXT_DIR) --------------------
102
159
  PIPE_RESULT_REVIEW: "$PIPE_CONTEXT_DIR/code-review-result.txt"
103
160
  PIPE_RESULT_REVIEWFIX: "$PIPE_CONTEXT_DIR/review-fix-result.json"
161
+ PIPE_RESULT_DOCSSYNC: "$PIPE_CONTEXT_DIR/docs-sync.json"
104
162
 
105
163
  # --- Hidden base job: shared config + runtime variable mapping ---------------
106
164
  # Maps GitLab's CI_ runtime variables onto the PIPE_ names the scripts read, and
@@ -125,7 +183,20 @@ variables:
125
183
  # ship it, plain docker images (node:22-bookworm) do not, so install it
126
184
  # here (finding from the GitLab pilot: silent HTTP 400s + exit 127).
127
185
  - command -v jq >/dev/null 2>&1 || (apt-get update -qq && apt-get install -y -qq jq)
128
- - command -v claude >/dev/null 2>&1 || npm install -g @anthropic-ai/claude-code
186
+ # node:22-bookworm does not include xmllint. Install it only for the
187
+ # opt-in ratchet. Unsupported custom-image installation stays non-fatal so
188
+ # coverage-ratchet can issue its explicit missing-tool error when it runs.
189
+ - |
190
+ if [ "$PIPE_COVERAGE_RATCHET" = "1" ] && ! command -v xmllint >/dev/null 2>&1; then
191
+ command -v apt-get >/dev/null 2>&1 &&
192
+ (apt-get update -qq && apt-get install -y -qq libxml2-utils) || true
193
+ fi
194
+ - |
195
+ if [ "$PIPE_HARNESS" = "omp" ]; then
196
+ command -v omp >/dev/null 2>&1 || npm install -g @oh-my-pi/pi-coding-agent
197
+ else
198
+ command -v claude >/dev/null 2>&1 || npm install -g @anthropic-ai/claude-code
199
+ fi
129
200
  artifacts:
130
201
  paths:
131
202
  - $PIPE_CONTEXT_DIR/metrics/
@@ -173,6 +244,42 @@ review-fix:
173
244
  - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $PIPE_TARGET_BRANCH && $CI_MERGE_REQUEST_LABELS =~ $PIPE_LABEL_WIP'
174
245
  when: on_failure
175
246
 
247
+ # =============================================================================
248
+ # docs-sync: check whether the MR's code changes need documentation updates.
249
+ # Optional and off by default, because it spends tokens on every MR: enable it
250
+ # by setting PIPE_DOCS_SYNC=1. On a wip MR the agent applies the updates and
251
+ # this job commits and pushes them; on any other MR it posts an advisory
252
+ # comment only. It never gates the pipeline.
253
+ # =============================================================================
254
+ docs-sync:
255
+ extends: .claude-base
256
+ stage: review
257
+ timeout: 30m
258
+ variables:
259
+ GIT_STRATEGY: clone
260
+ script:
261
+ - bash "$PIPE_SCRIPTS_DIR/docs-sync.sh"
262
+ allow_failure: true
263
+ rules:
264
+ - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $PIPE_TARGET_BRANCH && $PIPE_DOCS_SYNC == "1"'
265
+
266
+ # =============================================================================
267
+ # cve-fix: optionally remediate HIGH/CRITICAL vulnerabilities supplied by a
268
+ # project-local scanner in PIPE_CVE_LIST. The coder only runs on an autonomous
269
+ # MR/PR; human-authored MRs receive a manual-update comment instead.
270
+ # =============================================================================
271
+ cve-fix:
272
+ extends: .claude-base
273
+ stage: review
274
+ timeout: 45m
275
+ variables:
276
+ GIT_STRATEGY: clone
277
+ script:
278
+ - bash "$PIPE_SCRIPTS_DIR/cve-fix.sh"
279
+ allow_failure: true
280
+ rules:
281
+ - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $PIPE_TARGET_BRANCH && $PIPE_CVE_FIX == "1"'
282
+
176
283
  # =============================================================================
177
284
  # orchestrate: triage ready issues and fire the coder (issue -> code loop).
178
285
  # Runs on a scheduled pipeline that sets PIPE_ORCHESTRATE=1 (kept off by default
@@ -195,6 +302,45 @@ orchestrate:
195
302
  - if: '$CI_PIPELINE_SOURCE == "web" && $PIPE_ORCHESTRATE == "1"'
196
303
 
197
304
  # =============================================================================
305
+ # codebase-audit: periodic convention-drift scan. Scans recently merged MRs/PRs
306
+ # and compares patterns against documented conventions via the codebase-auditor
307
+ # agent. Posts findings as an issue labeled $PIPE_LABEL_AUDIT. If the report
308
+ # suggests documentation updates and a push-capable identity is configured,
309
+ # spawns the coder agent to implement them and opens an MR/PR.
310
+ # Opt-in and off by default: enable by setting PIPE_CODEBASE_AUDIT=1 on a
311
+ # separate scheduled pipeline (e.g. monthly).
312
+ # =============================================================================
313
+ codebase-audit:
314
+ extends: .claude-base
315
+ stage: orchestrate
316
+ timeout: 30m
317
+ variables:
318
+ GIT_STRATEGY: clone
319
+ script:
320
+ - bash "$PIPE_SCRIPTS_DIR/codebase-audit.sh"
321
+ allow_failure: true
322
+ rules:
323
+ - if: '$CI_PIPELINE_SOURCE == "schedule" && $PIPE_CODEBASE_AUDIT == "1"'
324
+
325
+
326
+ # =============================================================================
327
+ # agent-architect: weekly, evidence-backed improvement proposals for shipped
328
+ # agent definitions. Schedule-only and opt-in because it spends model tokens;
329
+ # set DRY_RUN=true for an initial report-only trial.
330
+ # =============================================================================
331
+ agent-architect:
332
+ extends: .claude-base
333
+ stage: review
334
+ timeout: 30m
335
+ variables:
336
+ GIT_STRATEGY: clone
337
+ DRY_RUN: "$PIPE_ARCHITECT_DRY_RUN"
338
+ script:
339
+ - bash "$PIPE_SCRIPTS_DIR/agent-architect.sh"
340
+ allow_failure: true
341
+ rules:
342
+ - if: '$CI_PIPELINE_SOURCE == "schedule" && $PIPE_AGENT_ARCHITECT == "1"'
343
+ # =============================================================================
198
344
  # code: implement one issue and open the MR (issue -> code loop).
199
345
  # Triggered by orchestrate via the Pipeline Trigger API with CODER_ISSUE and
200
346
  # CODER_BRANCH set. The coder agent runs with --dangerously-skip-permissions on
@@ -211,6 +357,48 @@ code:
211
357
  rules:
212
358
  - if: '$CODER_ISSUE && $CODER_BRANCH'
213
359
 
360
+ # =============================================================================
361
+ # e2e-test-gen: scheduled, opt-in generation for issues carrying the configured
362
+ # test-ready label (and optional E2E scope label). The project supplies the
363
+ # verification command and any framework/runtime setup through its own CI.
364
+ # =============================================================================
365
+ e2e-test-gen:
366
+ extends: .claude-base
367
+ stage: test
368
+ timeout: 45m
369
+ variables:
370
+ GIT_STRATEGY: clone
371
+ script:
372
+ - bash "$PIPE_SCRIPTS_DIR/e2e-test-gen.sh"
373
+ allow_failure: true
374
+ rules:
375
+ - if: '$CI_PIPELINE_SOURCE == "schedule" && $PIPE_E2E_TEST_GEN == "1"'
376
+
377
+ # =============================================================================
378
+ # coverage-ratchet: optional MR coverage gate. Reads the project's coverage
379
+ # artifact and, for a wip coverage drop, fails into test-fix with its signal.
380
+ # =============================================================================
381
+ coverage-ratchet:
382
+ extends: .claude-base
383
+ stage: test
384
+ needs:
385
+ - job: test
386
+ artifacts: true
387
+ optional: true
388
+ variables:
389
+ GIT_STRATEGY: clone
390
+ script:
391
+ - bash "$PIPE_SCRIPTS_DIR/coverage-ratchet.sh"
392
+ artifacts:
393
+ paths:
394
+ - $PIPE_CONTEXT_DIR/metrics/
395
+ - $PIPE_COVERAGE_SIGNAL
396
+ when: always
397
+ expire_in: 60 days
398
+ rules:
399
+ - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $PIPE_TARGET_BRANCH && $PIPE_COVERAGE_RATCHET == "1"'
400
+ when: always
401
+
214
402
  # =============================================================================
215
403
  # test-fix: on failure of YOUR project's `test` job, on a wip MR, fix tests /
216
404
  # compilation / coverage. Consumes the test job's artifacts via needs.
@@ -223,6 +411,9 @@ test-fix:
223
411
  - job: test
224
412
  artifacts: true
225
413
  optional: true
414
+ - job: coverage-ratchet
415
+ artifacts: true
416
+ optional: true
226
417
  variables:
227
418
  GIT_STRATEGY: clone
228
419
  script:
@@ -231,3 +422,25 @@ test-fix:
231
422
  rules:
232
423
  - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $PIPE_TARGET_BRANCH && $CI_MERGE_REQUEST_LABELS =~ $PIPE_LABEL_WIP'
233
424
  when: on_failure
425
+
426
+ # =============================================================================
427
+ # metrics-snapshot: aggregates one day of pipeline metrics into a JSON snapshot.
428
+ # Optional and off by default. Enable by setting PIPE_METRICS_SNAPSHOT=1 on a
429
+ # scheduled pipeline. Writes an artifact; set PIPE_METRICS_COMMIT_REPO=true to
430
+ # also commit the snapshot to the target branch.
431
+ # =============================================================================
432
+ metrics-snapshot:
433
+ extends: .claude-base
434
+ stage: orchestrate
435
+ timeout: 30m
436
+ variables:
437
+ GIT_DEPTH: "0"
438
+ script:
439
+ - bash "$PIPE_SCRIPTS_DIR/metrics-snapshot.sh"
440
+ artifacts:
441
+ paths:
442
+ - $PIPE_METRICS_OUTPUT_DIR/
443
+ when: always
444
+ expire_in: 90 days
445
+ rules:
446
+ - if: '$CI_PIPELINE_SOURCE == "schedule" && $PIPE_METRICS_SNAPSHOT == "1"'
@@ -74,7 +74,7 @@ env:
74
74
  PIPE_GIT_NAME: ${{ vars.PIPE_GIT_NAME || 'Pipeline Bot' }}
75
75
  PIPE_GIT_EMAIL: ${{ vars.PIPE_GIT_EMAIL || 'bot@pipeline.ci' }}
76
76
  PIPE_MODEL_TRIAGE: ${{ vars.PIPE_MODEL_TRIAGE || 'haiku' }}
77
- PIPE_MODEL_CODE: ${{ vars.PIPE_MODEL_CODE || 'sonnet' }}
77
+ PIPE_MODEL_CODE: ${{ vars.PIPE_MODEL_CODE || 'claude-sonnet-5-0' }}
78
78
  PIPE_AGENT_ENV_ALLOWLIST: ${{ vars.PIPE_AGENT_ENV_ALLOWLIST || '' }}
79
79
  # Runtime mapping onto the PIPE_ names the scripts read.
80
80
  PIPE_REPO: ${{ github.repository }}
@@ -44,8 +44,8 @@ env:
44
44
  PIPE_COMMIT_REVIEWFIX: ${{ vars.PIPE_COMMIT_REVIEWFIX || 'Fix review findings' }}
45
45
  PIPE_GIT_NAME: ${{ vars.PIPE_GIT_NAME || 'Pipeline Bot' }}
46
46
  PIPE_GIT_EMAIL: ${{ vars.PIPE_GIT_EMAIL || 'bot@pipeline.ci' }}
47
- PIPE_MODEL_REVIEW: ${{ vars.PIPE_MODEL_REVIEW || 'sonnet' }}
48
- PIPE_MODEL_CODE: ${{ vars.PIPE_MODEL_CODE || 'sonnet' }}
47
+ PIPE_MODEL_REVIEW: ${{ vars.PIPE_MODEL_REVIEW || 'claude-sonnet-5-0' }}
48
+ PIPE_MODEL_CODE: ${{ vars.PIPE_MODEL_CODE || 'claude-sonnet-5-0' }}
49
49
  PIPE_AGENT_ENV_ALLOWLIST: ${{ vars.PIPE_AGENT_ENV_ALLOWLIST || '' }}
50
50
  # Runtime mapping onto the PIPE_ names the scripts read.
51
51
  PIPE_REPO: ${{ github.repository }}
@@ -49,7 +49,7 @@ env:
49
49
  PIPE_COMMIT_COVERAGE: ${{ vars.PIPE_COMMIT_COVERAGE || 'Add coverage tests' }}
50
50
  PIPE_GIT_NAME: ${{ vars.PIPE_GIT_NAME || 'Pipeline Bot' }}
51
51
  PIPE_GIT_EMAIL: ${{ vars.PIPE_GIT_EMAIL || 'bot@pipeline.ci' }}
52
- PIPE_MODEL_CODE: ${{ vars.PIPE_MODEL_CODE || 'sonnet' }}
52
+ PIPE_MODEL_CODE: ${{ vars.PIPE_MODEL_CODE || 'claude-sonnet-5-0' }}
53
53
  PIPE_AGENT_ENV_ALLOWLIST: ${{ vars.PIPE_AGENT_ENV_ALLOWLIST || '' }}
54
54
  PIPE_TEST_ARTIFACT: ${{ vars.PIPE_TEST_ARTIFACT || 'test-reports' }}
55
55
  PIPE_REPO: ${{ github.repository }}
@@ -0,0 +1,233 @@
1
+ #!/bin/bash
2
+ # Runner for the opt-in weekly agent-architect job. It collects bounded,
3
+ # forge-neutral evidence; the /agent-architect skill performs the analysis.
4
+ set -e
5
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
6
+ # shellcheck source=lib/pipeline-common.sh
7
+ source "$SCRIPT_DIR/lib/pipeline-common.sh"
8
+ # shellcheck source=lib/issue-loop.sh
9
+ source "$SCRIPT_DIR/lib/issue-loop.sh"
10
+ pipe_defaults
11
+
12
+ ARCHITECT_ENV="$PIPE_CONTEXT_DIR/architect.env"
13
+ CONTEXT_FILE="$PIPE_CONTEXT_DIR/architect-context.md"
14
+ REPORT_FILE="$PIPE_CONTEXT_DIR/architect-report.md"
15
+ PROPOSALS_FILE="$PIPE_CONTEXT_DIR/architect-proposals.json"
16
+ if [ "${DRY_RUN+x}" = x ]; then
17
+ DRY_RUN="$DRY_RUN"
18
+ else
19
+ DRY_RUN="${PIPE_ARCHITECT_DRY_RUN:-true}"
20
+ fi
21
+ WINDOW_DAYS="${PIPE_ARCHITECT_WINDOW_DAYS:-7}"
22
+
23
+ write_marker() {
24
+ printf 'ARCHITECT_STATUS=%s\nARCHITECT_PROPOSALS=%s\nARCHITECT_ISSUES_FILED=%s\n' \
25
+ "$1" "$2" "$3" > "$ARCHITECT_ENV"
26
+ }
27
+
28
+ if [ "${PIPE_AGENT_ARCHITECT:-0}" != "1" ]; then
29
+ write_marker disabled 0 0
30
+ pipe_log "Agent architect is disabled"
31
+ exit 0
32
+ fi
33
+
34
+ if ! [[ "$WINDOW_DAYS" =~ ^[1-9][0-9]*$ ]]; then
35
+ write_marker invalid_window 0 0
36
+ pipe_log "ERROR: PIPE_ARCHITECT_WINDOW_DAYS must be a positive integer"
37
+ exit 1
38
+ fi
39
+
40
+ if date --version >/dev/null 2>&1; then
41
+ SINCE=$(date -u -d "$WINDOW_DAYS days ago" '+%Y-%m-%dT%H:%M:%SZ')
42
+ else
43
+ SINCE=$(date -u -v-"$WINDOW_DAYS"d '+%Y-%m-%dT%H:%M:%SZ')
44
+ fi
45
+ WEEK_TAG=$(date -u +%G-W%V)
46
+
47
+ # The updated-since helper has the window semantics the source job needs. It
48
+ # returns both states, so retain only merged MRs/PRs targeting this project’s
49
+ # configured integration branch and cap the prompt input before any per-item IO.
50
+ RECENT_MRS=$(platform_merge_requests_updated_since "$SINCE") || {
51
+ write_marker fetch_failed 0 0
52
+ pipe_log "ERROR: failed to fetch recently updated MRs/PRs"
53
+ exit 1
54
+ }
55
+ MRS=$(jq -c --arg target "$PIPE_TARGET_BRANCH" \
56
+ '[.[] | select(.state == "merged" and .target_branch == $target)] | .[0:30]' <<< "$RECENT_MRS") || {
57
+ write_marker fetch_failed 0 0
58
+ pipe_log "ERROR: unexpected merged MR/PR response"
59
+ exit 1
60
+ }
61
+ MR_COUNT=$(jq -r 'length' <<< "$MRS")
62
+ if ! [[ "$MR_COUNT" =~ ^[0-9]+$ ]]; then
63
+ write_marker fetch_failed 0 0
64
+ pipe_log "ERROR: could not count merged MRs/PRs"
65
+ exit 1
66
+ fi
67
+ if [ "$MR_COUNT" = 0 ]; then
68
+ write_marker no_evidence 0 0
69
+ pipe_log "No merged MRs/PRs in the last $WINDOW_DAYS days"
70
+ exit 0
71
+ fi
72
+
73
+ # Stuck issues are current actionable context. Stuck MRs/PRs are selected from
74
+ # the same bounded recent window, so neither evidence source can expand prompt
75
+ # size without a hard limit.
76
+ STUCK_ISSUES=$(issues_by_labels "$PIPE_LABEL_STUCK" "" opened 20) || {
77
+ pipe_log "WARNING: failed to fetch open $PIPE_LABEL_STUCK issues; continuing without them"
78
+ STUCK_ISSUES='[]'
79
+ }
80
+ STUCK_MRS=$(jq -c --arg label "$PIPE_LABEL_STUCK" \
81
+ '[.[] | select((.labels // []) | index($label))] | .[0:20]' <<< "$RECENT_MRS") || STUCK_MRS='[]'
82
+ OPEN_IMPROVEMENTS=$(issues_by_label_created "$PIPE_LABEL_IMPROVEMENT" opened 10) || {
83
+ pipe_log "WARNING: failed to fetch open $PIPE_LABEL_IMPROVEMENT issues; continuing without dedup context"
84
+ OPEN_IMPROVEMENTS='[]'
85
+ }
86
+
87
+ # Resolve the configured bot identity once so automatic comments do not become
88
+ # evidence. A failed lookup merely leaves the filter empty rather than failing a
89
+ # read-only scheduled analysis.
90
+ platform_resolve_bot_user || true
91
+
92
+ MR_EVIDENCE=""
93
+ while IFS= read -r IID; do
94
+ TITLE=$(jq -r --argjson iid "$IID" '.[] | select(.iid == $iid) | .title' <<< "$MRS")
95
+ AUTHOR=$(jq -r --argjson iid "$IID" '.[] | select(.iid == $iid) | .author' <<< "$MRS")
96
+ LABELS=$(jq -r --argjson iid "$IID" '.[] | select(.iid == $iid) | (.labels // []) | join(",")' <<< "$MRS")
97
+ FILES=$(platform_mr_changes "$IID" 15 2>/dev/null | sed 's/^/ /' || true)
98
+ NOTES=$(platform_mr_notes "$IID" 20 asc 2>/dev/null \
99
+ | jq -r --arg bot "${PIPE_BOT_USER:-}" \
100
+ '[.[] | select($bot == "" or .author != $bot)
101
+ | " [" + .author + "]: " + ((.body | gsub("[\\r\\n]+"; " "))[0:200])]
102
+ | .[0:20] | join("\n")' 2>/dev/null || true)
103
+ MR_EVIDENCE="${MR_EVIDENCE}
104
+ ### MR/PR !${IID}: ${TITLE}
105
+ Author: ${AUTHOR} | Labels: ${LABELS}
106
+ Changed files:
107
+ ${FILES:- (none)}
108
+ Human review comments:
109
+ ${NOTES:- (none)}
110
+ "
111
+ done < <(jq -r '.[].iid' <<< "$MRS")
112
+
113
+ STUCK_EVIDENCE=""
114
+ while IFS= read -r IID; do
115
+ TITLE=$(jq -r --argjson iid "$IID" '.[] | select(.iid == $iid) | .title' <<< "$STUCK_ISSUES")
116
+ DESCRIPTION=$(jq -r --argjson iid "$IID" '.[] | select(.iid == $iid) | .description // "(none)"' <<< "$STUCK_ISSUES" | head -c 400)
117
+ STUCK_EVIDENCE="${STUCK_EVIDENCE}
118
+ ### Stuck issue #${IID}: ${TITLE}
119
+ ${DESCRIPTION}
120
+ "
121
+ done < <(jq -r '.[].iid' <<< "$STUCK_ISSUES")
122
+ while IFS= read -r IID; do
123
+ TITLE=$(jq -r --argjson iid "$IID" '.[] | select(.iid == $iid) | .title' <<< "$STUCK_MRS")
124
+ NOTES=$(platform_mr_notes "$IID" 15 asc 2>/dev/null \
125
+ | jq -r --arg bot "${PIPE_BOT_USER:-}" \
126
+ '[.[] | select($bot == "" or .author != $bot)
127
+ | " [" + .author + "]: " + ((.body | gsub("[\\r\\n]+"; " "))[0:200])]
128
+ | .[0:15] | join("\n")' 2>/dev/null || true)
129
+ STUCK_EVIDENCE="${STUCK_EVIDENCE}
130
+ ### Stuck MR/PR !${IID}: ${TITLE}
131
+ Human comments:
132
+ ${NOTES:- (none)}
133
+ "
134
+ done < <(jq -r '.[].iid' <<< "$STUCK_MRS")
135
+
136
+ OPEN_CONTEXT=$(jq -r '.[] | "Issue #\(.iid) (\(.created_at[0:10])): \(.title)\n\(.description[0:500])\n---"' <<< "$OPEN_IMPROVEMENTS" 2>/dev/null | head -c 6000 || true)
137
+ rm -f "$ARCHITECT_ENV" "$REPORT_FILE" "$PROPOSALS_FILE"
138
+ {
139
+ echo "# Agent architect context"
140
+ echo
141
+ echo "Time window: last $WINDOW_DAYS days (since $SINCE)"
142
+ echo "Week: $WEEK_TAG"
143
+ echo "Ready label: $PIPE_LABEL_READY"
144
+ echo "Improvement label: $PIPE_LABEL_IMPROVEMENT"
145
+ echo
146
+ echo "## Merged MRs/PRs"
147
+ printf '%s\n' "$MR_EVIDENCE"
148
+ echo "## Stuck items"
149
+ printf '%s\n' "${STUCK_EVIDENCE:-(none)}"
150
+ if [ -n "$OPEN_CONTEXT" ]; then
151
+ echo
152
+ echo "## Already-open improvement issues — do not re-propose these"
153
+ printf '%s\n' "$OPEN_CONTEXT"
154
+ fi
155
+ } > "$CONTEXT_FILE"
156
+
157
+ REPORT=$(pipe_run_agent agent-architect "/agent-architect" "Agent,Read,Glob,Grep" "$PIPE_MODEL_REVIEW" < /dev/null)
158
+ printf '%s\n' "$REPORT" > "$REPORT_FILE"
159
+ if [ ! -s "$REPORT_FILE" ]; then
160
+ write_marker no_output 0 0
161
+ pipe_failure_log_notice_files "the weekly agent-architect analysis" \
162
+ "review the recent changes and stuck items by hand" \
163
+ "$PIPE_AGENT_STDERR" "$REPORT_FILE" | while IFS= read -r line; do pipe_log "$line"; done
164
+ exit 0
165
+ fi
166
+
167
+ # Retain at most five complete delimiter blocks. The JSON file keeps body text
168
+ # out of shell words and is also the sole source for issue creation below.
169
+ if ! command -v python3 >/dev/null 2>&1; then
170
+ write_marker parser_failed 0 0
171
+ pipe_log "WARNING: python3 is unavailable; cannot parse architect proposals"
172
+ exit 0
173
+ fi
174
+ if ! python3 - "$REPORT_FILE" "$PROPOSALS_FILE" <<'PY'
175
+ import json
176
+ import re
177
+ import sys
178
+
179
+ report_path, proposals_path = sys.argv[1:]
180
+ text = open(report_path, encoding="utf-8").read()
181
+ proposals = []
182
+ for block in re.findall(r"<!-- ISSUE-START -->(.*?)<!-- ISSUE-END -->", text, re.S)[:5]:
183
+ title = next((line[7:].strip() for line in block.splitlines() if line.startswith("Title: ")), "")
184
+ if not title or len(title) > 72:
185
+ continue
186
+ proposals.append({"title": title, "body": block.strip()[:8000]})
187
+ with open(proposals_path, "w", encoding="utf-8") as output:
188
+ json.dump(proposals, output)
189
+ PY
190
+ then
191
+ rm -f "$PROPOSALS_FILE"
192
+ write_marker parser_failed 0 0
193
+ pipe_log "WARNING: could not parse architect proposals"
194
+ exit 0
195
+ fi
196
+ if ! jq -e 'type == "array"' "$PROPOSALS_FILE" >/dev/null 2>&1; then
197
+ rm -f "$PROPOSALS_FILE"
198
+ write_marker parser_failed 0 0
199
+ pipe_log "WARNING: architect proposal parser produced invalid output"
200
+ exit 0
201
+ fi
202
+ PROPOSAL_COUNT=$(jq -r 'length' "$PROPOSALS_FILE")
203
+ if [ "$PROPOSAL_COUNT" = 0 ]; then
204
+ write_marker no_proposals 0 0
205
+ pipe_log "No concrete architect proposals"
206
+ exit 0
207
+ fi
208
+
209
+ case "$DRY_RUN" in
210
+ false|0) ;;
211
+ *)
212
+ write_marker ok "$PROPOSAL_COUNT" 0
213
+ pipe_log "DRY RUN: $PROPOSAL_COUNT proposal(s), no issues filed"
214
+ cat "$REPORT_FILE"
215
+ exit 0
216
+ ;;
217
+ esac
218
+
219
+ issue_ensure_label "$PIPE_LABEL_IMPROVEMENT" "#6F42C1" "Agent improvement proposals"
220
+ ISSUES_FILE="$PIPE_CONTEXT_DIR/architect-issue.md"
221
+ ISSUES_FILED=0
222
+ while IFS= read -r proposal; do
223
+ TITLE=$(jq -r '.title' <<< "$proposal")
224
+ jq -r '.body' <<< "$proposal" > "$ISSUES_FILE"
225
+ ISSUE_IID=$(issue_create_file "$TITLE" "$ISSUES_FILE" "$PIPE_LABEL_READY,$PIPE_LABEL_IMPROVEMENT")
226
+ if [ -n "$ISSUE_IID" ]; then
227
+ ISSUES_FILED=$((ISSUES_FILED + 1))
228
+ pipe_log "Filed architect proposal as #$ISSUE_IID"
229
+ else
230
+ pipe_log "WARNING: could not file architect proposal: $TITLE"
231
+ fi
232
+ done < <(jq -c '.[]' "$PROPOSALS_FILE")
233
+ write_marker ok "$PROPOSAL_COUNT" "$ISSUES_FILED"
@@ -7,14 +7,15 @@
7
7
  #
8
8
  # Generalized from a single-project original: the delegation prompt and the
9
9
  # summary generation moved into the skill, platform calls go through
10
- # lib/issue-loop.sh, and the coder runs non-root via pipe_run_claude.
10
+ # lib/issue-loop.sh, and the coder runs via pipe_run_agent.
11
11
  #
12
- # SECURITY NOTE: the coder agent runs with --dangerously-skip-permissions (it
13
- # must edit files and run the build unattended). This is a deliberate
14
- # choice for trusted inputs. Run only on an isolated ephemeral runner and, where
15
- # the image allows, as an unprivileged user (see pipe_run_claude). This does not
16
- # make arbitrary public issue or contributor content safe; see the README trust
17
- # boundary. Do not run this job on a shared or privileged machine.
12
+ # SECURITY NOTE: the coder agent uses the active harness's unattended approval
13
+ # mode because it must edit files and run the build without interaction. This is
14
+ # a deliberate choice for trusted inputs. Run only on an isolated ephemeral
15
+ # runner and, where the image allows, through the harness dispatch's shared
16
+ # unprivileged-user wrapper. This does not make arbitrary public issue or
17
+ # contributor content safe; see the README trust boundary. Do not run this job
18
+ # on a shared or privileged machine.
18
19
  set -e
19
20
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
20
21
  # shellcheck source=lib/pipeline-common.sh
@@ -51,7 +52,7 @@ pipe_log "Implementing #$CODER_ISSUE: $ISSUE_TITLE"
51
52
 
52
53
  # --- 4. Run the implement-issue skill (delegates to the coder agent) --------
53
54
  rm -f "$PIPE_CONTEXT_DIR/implement.env" "$PIPE_CONTEXT_DIR/mr-summary.md"
54
- pipe_run_claude coder "/implement-issue" "Agent,Read,Write,Edit,Glob,Grep,Bash" "$PIPE_MODEL_CODE" < /dev/null
55
+ pipe_run_agent coder "/implement-issue" "Agent,Read,Write,Edit,Glob,Grep,Bash" "$PIPE_MODEL_CODE" < /dev/null
55
56
 
56
57
  # --- 5. Authoritative commit count (the runner owns the push decision) -------
57
58
  COMMITS=$(git rev-list "origin/$PIPE_TARGET_BRANCH..HEAD" --count 2>/dev/null || echo 0)
@@ -60,24 +61,21 @@ COMMITS=$(git rev-list "origin/$PIPE_TARGET_BRANCH..HEAD" --count 2>/dev/null ||
60
61
  # issue_implemented is emitted only when commits exist (E2E finding N-7:
61
62
  # emitting it before this check produced the metric for stuck runs too).
62
63
  if [ "${COMMITS:-0}" = "0" ]; then
63
- pipe_log "Coder made no commits, marking #$CODER_ISSUE stuck"
64
- pipe_metric_event coder issue_stuck \
65
- "$(jq -n --argjson i "$CODER_ISSUE" '{issue_iid: $i, reason: "no_commits"}')"
66
- # The agent's own account of why it stopped is the most useful thing in a
67
- # stuck comment, and it was being read and then dropped.
68
- EXPL=$(head -c 3000 "$PIPE_CONTEXT_DIR/implement.env" 2>/dev/null || true)
69
- issue_comment "$CODER_ISSUE" "šŸ¤– **Coder agent made no commits for issue #$CODER_ISSUE.**
70
-
71
- The implementation agent ran but produced no commits. A human needs to take over.
72
- ${EXPL:+
73
- What the agent reported before stopping:
74
-
75
- \`\`\`
76
- $EXPL
77
- \`\`\`
78
- }
79
- ${PIPE_JOB_URL:+CI job: $PIPE_JOB_URL}"
80
- issue_set_labels "$CODER_ISSUE" "$PIPE_LABEL_STUCK" "$PIPE_LABEL_READY,$PIPE_LABEL_WIP"
64
+ NOTICE_FILE="$PIPE_CONTEXT_DIR/implementation-failure.md"
65
+ {
66
+ printf 'šŸ¤– '
67
+ pipe_failure_notice_files "implementation of issue #$CODER_ISSUE" "implement this issue by hand" \
68
+ "$PIPE_AGENT_STDERR" "$PIPE_CONTEXT_DIR/implement.env"
69
+ } > "$NOTICE_FILE"
70
+ issue_comment_file "$CODER_ISSUE" "$NOTICE_FILE"
71
+ if pipe_is_credit_exhaustion_files "$PIPE_AGENT_STDERR" "$PIPE_CONTEXT_DIR/implement.env"; then
72
+ pipe_log "Coder credit exhausted; #$CODER_ISSUE remains $PIPE_LABEL_READY"
73
+ else
74
+ pipe_log "Coder made no commits, marking #$CODER_ISSUE stuck"
75
+ pipe_metric_event coder issue_stuck \
76
+ "$(jq -n --argjson i "$CODER_ISSUE" '{issue_iid: $i, reason: "no_commits"}')"
77
+ issue_set_labels "$CODER_ISSUE" "$PIPE_LABEL_STUCK" "$PIPE_LABEL_READY,$PIPE_LABEL_WIP"
78
+ fi
81
79
  exit 0
82
80
  fi
83
81
 
@@ -88,25 +86,27 @@ pipe_metric_event coder issue_implemented \
88
86
  git push -u origin "$CODER_BRANCH"
89
87
 
90
88
  # --- 8. Open the MR/PR ------------------------------------------------------
91
- SUMMARY=$(head -c 6000 "$PIPE_CONTEXT_DIR/mr-summary.md" 2>/dev/null || true)
92
- [ -z "$SUMMARY" ] && SUMMARY="Implemented by the ci-agent-platform coder agent."
89
+ MR_DESC_FILE="$PIPE_CONTEXT_DIR/mr-description.md"
90
+ {
91
+ printf 'Closes #%s\n\n' "$CODER_ISSUE"
92
+ if [ -s "$PIPE_CONTEXT_DIR/mr-summary.md" ]; then
93
+ cat "$PIPE_CONTEXT_DIR/mr-summary.md"
94
+ else
95
+ printf 'Implemented by the ci-agent-platform coder agent.'
96
+ fi
97
+ printf '\n\n---\nšŸ¤– Implemented by the ci-agent-platform coder\n'
98
+ } > "$MR_DESC_FILE"
93
99
 
94
100
  # Inherit the issue labels onto the MR/PR: drop the ready label, add wip.
95
101
  MR_LABELS=$( { echo "$ISSUE_LABELS" | tr ',' '\n' | grep -vxF "$PIPE_LABEL_READY" | grep -v '^$'; echo "$PIPE_LABEL_WIP"; } \
96
102
  | awk '!seen[$0]++' | paste -sd, - )
97
103
 
98
104
  MR_TITLE="Resolve \"$ISSUE_TITLE\""
99
- MR_DESC="Closes #$CODER_ISSUE
100
-
101
- $SUMMARY
102
-
103
- ---
104
- šŸ¤– Implemented by the ci-agent-platform coder"
105
105
 
106
106
  # Ensure the MR/PR labels exist (repo/project may not have them yet).
107
107
  issue_ensure_label "$PIPE_LABEL_WIP" "#3498DB" "Pipeline is working on this change"
108
108
 
109
- MR_URL=$(mr_create "$CODER_BRANCH" "$PIPE_TARGET_BRANCH" "$MR_TITLE" "$MR_DESC" "$MR_LABELS")
109
+ MR_URL=$(mr_create_file "$CODER_BRANCH" "$PIPE_TARGET_BRANCH" "$MR_TITLE" "$MR_DESC_FILE" "$MR_LABELS")
110
110
  if [ -n "$MR_URL" ]; then
111
111
  pipe_log "Opened MR/PR: $MR_URL"
112
112
  else