@plainconceptsplatform/workflows 1.6.30 → 1.6.42
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/dist/index.js +12 -1
- package/loops/actions/housekeeping/action.yml +11 -1
- package/loops/actions/merge-train/merge-train.sh +8 -1
- package/loops/actions/rebase-stale-patch/action.yml +41 -0
- package/loops/actions/rebase-stale-patch/rebase-stale-patch.sh +94 -0
- package/loops/actions/verify-route-matrix/verify-route-matrix.sh +62 -2
- package/loops/workflows/agent-apply-review.md +7 -4
- package/loops/workflows/agent-audit.md +7 -4
- package/loops/workflows/agent-implement.md +42 -21
- package/loops/workflows/agent-merge-gate.md +7 -4
- package/loops/workflows/agent-refine.md +8 -5
- package/loops/workflows/agent-release.md +7 -4
- package/loops/workflows/agent-triage.md +7 -4
- package/loops/workflows/shared/opencode-ci.md +63 -8
- package/loops/workflows/work-router.yml +8 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { formatCatalog, listCatalog, searchCatalog } from "./catalog-listing.js"
|
|
|
5
5
|
import { packageVersion } from "./package-baseline.js";
|
|
6
6
|
import { routeNames, templateNames } from "./workflow-catalog.js";
|
|
7
7
|
import { runInteractive } from "./tui.js";
|
|
8
|
+
import { realpathSync } from "node:fs";
|
|
8
9
|
import { resolve } from "node:path";
|
|
9
10
|
import { fileURLToPath } from "node:url";
|
|
10
11
|
const HELP_TEXT = `Workflows CLI install and update Plain Concepts Platform agentic workflows.
|
|
@@ -269,7 +270,17 @@ function fail(message) {
|
|
|
269
270
|
console.error(message);
|
|
270
271
|
return 1;
|
|
271
272
|
}
|
|
272
|
-
|
|
273
|
+
// pnpm installs the package behind a symlink: argv[1] is the link and import.meta.url the real file,
|
|
274
|
+
// so a plain comparison never matched and the installed CLI exited silently, --version included.
|
|
275
|
+
function realPath(path) {
|
|
276
|
+
try {
|
|
277
|
+
return realpathSync(path);
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
return resolve(path);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (process.argv[1] !== undefined && realPath(process.argv[1]) === realPath(fileURLToPath(import.meta.url))) {
|
|
273
284
|
void run(process.argv.slice(2)).then((exitCode) => {
|
|
274
285
|
process.exitCode = exitCode;
|
|
275
286
|
});
|
|
@@ -311,7 +311,17 @@ runs:
|
|
|
311
311
|
});
|
|
312
312
|
const told = comments.find((c) => (c.body ?? '').includes('<!-- stack-complete -->'));
|
|
313
313
|
if (!told) {
|
|
314
|
-
|
|
314
|
+
// The router announces a finished stack when a child's pull request merges into it.
|
|
315
|
+
// A child closed any other way (by hand, or by this sweep) never raised that event,
|
|
316
|
+
// and Pliny-Bot #781 sat finished with no announcement, which is what the merge train
|
|
317
|
+
// waits for before it lets the root merge. Announce it here; the root's own gate and
|
|
318
|
+
// CI still decide whether it merges.
|
|
319
|
+
await act(`#${rootPr}: announcing the finished stack of #${issue.number}`, async () => {
|
|
320
|
+
await github.rest.issues.createComment({
|
|
321
|
+
...context.repo, issue_number: rootPr,
|
|
322
|
+
body: `Stack complete: every child of #${issue.number} has landed on the feature branch. This root pull request is ready to merge into ${context.payload.repository?.default_branch ?? 'main'}: the merge gate decides it next, and a person is asked only when its blast radius or a finding says so. <!-- stack-complete -->`,
|
|
323
|
+
});
|
|
324
|
+
});
|
|
315
325
|
} else if (age(told.created_at) > stalePrMs) {
|
|
316
326
|
needsHuman.push({ kind: 'stack-held', issue, pull: root, waited: age(told.created_at) });
|
|
317
327
|
}
|
|
@@ -444,7 +444,14 @@ for line in "${candidates[@]}"; do
|
|
|
444
444
|
--jq '[.check_runs[] | select(.conclusion == "failure") | .completed_at] | min // empty' 2>/dev/null || true)
|
|
445
445
|
red_for_hours=0
|
|
446
446
|
if [ -n "$red_since" ]; then
|
|
447
|
-
|
|
447
|
+
# A date that does not parse reads as epoch 0, which made main "red for 497899h" and ended the
|
|
448
|
+
# wait at once (Pliny-Bot repaired five pull requests for failures main had too). No date, no claim.
|
|
449
|
+
red_epoch=$(iso_epoch "$red_since")
|
|
450
|
+
if [ "$red_epoch" -gt 0 ]; then
|
|
451
|
+
red_for_hours=$(( (now_epoch - red_epoch) / 3600 ))
|
|
452
|
+
else
|
|
453
|
+
echo "::warning::Could not read when $base_ref went red ('$red_since'); waiting for it as if it just did."
|
|
454
|
+
fi
|
|
448
455
|
fi
|
|
449
456
|
if [ -n "$failed_here" ] && [ -n "$failed_base" ] &&
|
|
450
457
|
[ -z "$(comm -23 <(sort <<<"$failed_here") <(sort <<<"$failed_base"))" ]; then
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/rebase-stale-patch/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
+
name: Rebase a stale agent patch
|
|
3
|
+
description: When an agent's patch no longer applies because its base branch moved, rebase the run's bundle onto the base and open the pull request the agent asked for. A real conflict is left for a person.
|
|
4
|
+
inputs:
|
|
5
|
+
token:
|
|
6
|
+
description: App token that can push a branch, open a pull request and close an issue.
|
|
7
|
+
required: true
|
|
8
|
+
run-id:
|
|
9
|
+
description: The workflow run whose agent artifact holds the bundle.
|
|
10
|
+
required: true
|
|
11
|
+
artifact:
|
|
12
|
+
description: Name of the agent artifact, with the run's artifact prefix.
|
|
13
|
+
required: true
|
|
14
|
+
issue-number:
|
|
15
|
+
description: The issue the agent worked on.
|
|
16
|
+
required: true
|
|
17
|
+
default-branch:
|
|
18
|
+
description: Base used when the issue is not part of a stack.
|
|
19
|
+
required: true
|
|
20
|
+
outputs:
|
|
21
|
+
rescued:
|
|
22
|
+
description: '"true" when a pull request was opened from the rebased bundle.'
|
|
23
|
+
value: ${{ steps.rebase.outputs.rescued }}
|
|
24
|
+
pr-number:
|
|
25
|
+
description: The pull request opened, or empty.
|
|
26
|
+
value: ${{ steps.rebase.outputs.pr_number }}
|
|
27
|
+
runs:
|
|
28
|
+
using: composite
|
|
29
|
+
steps:
|
|
30
|
+
- name: Rebase the bundle and open its pull request
|
|
31
|
+
id: rebase
|
|
32
|
+
shell: bash
|
|
33
|
+
env:
|
|
34
|
+
GH_TOKEN: ${{ inputs.token }}
|
|
35
|
+
DOWNLOAD_TOKEN: ${{ github.token }}
|
|
36
|
+
REPO: ${{ github.repository }}
|
|
37
|
+
RUN_ID: ${{ inputs.run-id }}
|
|
38
|
+
ARTIFACT: ${{ inputs.artifact }}
|
|
39
|
+
ISSUE: ${{ inputs.issue-number }}
|
|
40
|
+
DEFAULT_BRANCH: ${{ inputs.default-branch }}
|
|
41
|
+
run: bash "${{ github.action_path }}/rebase-stale-patch.sh"
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/rebase-stale-patch/rebase-stale-patch.sh. Update with `workflows update --force`; consumer edits may be overwritten.
|
|
3
|
+
# gh-aw pushes an agent's commits onto the base as it is when the run ends. When the base moved
|
|
4
|
+
# under a long run and the commits no longer apply, gh-aw files the patch as an issue and the work
|
|
5
|
+
# waits for a person, although most of the time a plain rebase applies it: Pliny-Bot #787 and #794
|
|
6
|
+
# were both rebased by hand in minutes. This does that rebase here and opens the pull request the
|
|
7
|
+
# agent asked for. A conflict a rebase cannot settle stays with a person, exactly as before.
|
|
8
|
+
#
|
|
9
|
+
# Needs: GH_TOKEN (App), DOWNLOAD_TOKEN (the run's own token, for its artifacts), REPO, RUN_ID, ARTIFACT, ISSUE, DEFAULT_BRANCH, GITHUB_OUTPUT.
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
say() { echo "$1"; }
|
|
14
|
+
done_with() {
|
|
15
|
+
echo "rescued=$1" >> "$GITHUB_OUTPUT"
|
|
16
|
+
echo "pr_number=${2:-}" >> "$GITHUB_OUTPUT"
|
|
17
|
+
exit 0
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
work="$(mktemp -d)"
|
|
21
|
+
if ! GH_TOKEN="${DOWNLOAD_TOKEN:-$GH_TOKEN}" gh run download "$RUN_ID" --repo "$REPO" --name "$ARTIFACT" --dir "$work" >/dev/null 2>&1; then
|
|
22
|
+
say "No agent artifact to rebase from."
|
|
23
|
+
done_with false
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
bundle="$(find "$work" -maxdepth 1 -name '*.bundle' | head -1)"
|
|
27
|
+
output="$work/agent_output.json"
|
|
28
|
+
if [ -z "$bundle" ] || [ ! -f "$output" ]; then
|
|
29
|
+
say "The artifact holds no bundle or no agent output."
|
|
30
|
+
done_with false
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
request="$(jq -c '[.items[]? | select(.type == "create_pull_request")] | first // empty' "$output")"
|
|
34
|
+
if [ -z "$request" ]; then
|
|
35
|
+
say "The agent did not ask for a pull request."
|
|
36
|
+
done_with false
|
|
37
|
+
fi
|
|
38
|
+
branch="$(jq -r '.branch // empty' <<<"$request")"
|
|
39
|
+
title="$(jq -r '.title // empty' <<<"$request")"
|
|
40
|
+
jq -r '.body // ""' <<<"$request" > "$work/body.md"
|
|
41
|
+
if [ -z "$branch" ] || [ -z "$title" ]; then
|
|
42
|
+
say "The pull request request has no branch or title."
|
|
43
|
+
done_with false
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# A stack child targets its feature branch while that branch exists; otherwise the default branch.
|
|
47
|
+
base="$DEFAULT_BRANCH"
|
|
48
|
+
stack="$(gh issue view "$ISSUE" --repo "$REPO" --json body --jq '.body // ""' |
|
|
49
|
+
sed -nE 's/.*<!-- stack-root-branch: ([^ ]+) -->.*/\1/p' | head -1)"
|
|
50
|
+
if [ -n "$stack" ] && gh api "repos/$REPO/branches/$stack" >/dev/null 2>&1; then
|
|
51
|
+
base="$stack"
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
remote="https://x-access-token:${GH_TOKEN}@github.com/${REPO}.git"
|
|
55
|
+
# Full history: the bundle's prerequisites are commits of the base, and a rebase needs their blobs.
|
|
56
|
+
if git rev-parse --is-shallow-repository | grep -q true; then
|
|
57
|
+
git fetch --quiet --unshallow "$remote" "+refs/heads/${base}:refs/remotes/rescue-base/${base}"
|
|
58
|
+
else
|
|
59
|
+
git fetch --quiet "$remote" "+refs/heads/${base}:refs/remotes/rescue-base/${base}"
|
|
60
|
+
fi
|
|
61
|
+
git fetch --quiet "$bundle" "+refs/heads/*:refs/remotes/rescue-bundle/*"
|
|
62
|
+
head="refs/remotes/rescue-bundle/${branch}"
|
|
63
|
+
if ! git rev-parse --verify --quiet "$head" >/dev/null; then
|
|
64
|
+
head="$(git for-each-ref --format='%(refname)' refs/remotes/rescue-bundle | head -1)"
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
rescued_branch="${branch}-rebased-${RUN_ID}"
|
|
68
|
+
git checkout --quiet -B "$rescued_branch" "$head"
|
|
69
|
+
if ! git -c user.name="github-actions[bot]" -c user.email="41898282+github-actions[bot]@users.noreply.github.com" \
|
|
70
|
+
rebase --quiet "refs/remotes/rescue-base/${base}"; then
|
|
71
|
+
git rebase --abort || true
|
|
72
|
+
say "The rebase onto ${base} conflicts: left for a person."
|
|
73
|
+
done_with false
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
git push --quiet "$remote" "HEAD:refs/heads/${rescued_branch}"
|
|
77
|
+
|
|
78
|
+
grep -qiE "(closes|fixes|resolves) #${ISSUE}\b" "$work/body.md" || printf '\n\nCloses #%s\n' "$ISSUE" >> "$work/body.md"
|
|
79
|
+
printf '\n\nRebased onto `%s` by the workflow: the agent'"'"'s commits no longer applied after `%s` moved during the run.\n' \
|
|
80
|
+
"$base" "$base" >> "$work/body.md"
|
|
81
|
+
[[ "$title" == "[bot] "* ]] || title="[bot] $title"
|
|
82
|
+
url="$(gh pr create --repo "$REPO" --base "$base" --head "$rescued_branch" --title "$title" --body-file "$work/body.md")"
|
|
83
|
+
number="${url##*/}"
|
|
84
|
+
|
|
85
|
+
# The issue gh-aw filed with the patch is superseded by the pull request.
|
|
86
|
+
stray="$(gh issue list --repo "$REPO" --state open --limit 30 --json number,title 2>/dev/null |
|
|
87
|
+
jq -r --arg t "$title" '[.[] | select(.title == $t)] | first | .number // empty' || true)"
|
|
88
|
+
if [ -n "$stray" ]; then
|
|
89
|
+
gh issue close "$stray" --repo "$REPO" --reason "not planned" \
|
|
90
|
+
--comment "Superseded by #${number}: the patch was rebased onto ${base} and opened as a pull request." >/dev/null
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
say "Rebased onto ${base} and opened #${number}."
|
|
94
|
+
done_with true "$number"
|
|
@@ -1792,11 +1792,11 @@ fi
|
|
|
1792
1792
|
# full verification at minute forty-one after six scoped runs, and #751 spent fifteen of twenty-two
|
|
1793
1793
|
# minutes in twelve tiny worker tasks. These sentences are what keep a run near twenty minutes.
|
|
1794
1794
|
if worker_installed implement; then
|
|
1795
|
-
if grep -qF 'Run them once, on the final tree.' "$IMPLEMENT_WORKER_MD" && grep -qF 'on both paths, before the commands above.' "$IMPLEMENT_WORKER_MD" && grep -qF '
|
|
1795
|
+
if grep -qF 'Run them once, on the final tree.' "$IMPLEMENT_WORKER_MD" && grep -qF 'on both paths, before the commands above.' "$IMPLEMENT_WORKER_MD" && grep -qF 'tasks that do not depend on each' "$IMPLEMENT_WORKER_MD" && grep -qF 'WORK IN FEW TURNS' "$IMPLEMENT_WORKER_MD"; then
|
|
1796
1796
|
PASS=$((PASS + 1))
|
|
1797
1797
|
else
|
|
1798
1798
|
FAIL=$((FAIL + 1))
|
|
1799
|
-
echo "FAIL: the implement prompt must verify once on the final tree,
|
|
1799
|
+
echo "FAIL: the implement prompt must verify once on the final tree, let independent tasks run in parallel and work in few turns" >&2
|
|
1800
1800
|
fi
|
|
1801
1801
|
fi
|
|
1802
1802
|
# gh-aw leaves OPENCODE_MODEL empty, so every lead ran on opencode.ci.json's one model. Each worker
|
|
@@ -1824,6 +1824,9 @@ fi
|
|
|
1824
1824
|
if ! grep -qF 'jq --arg model "$AGENT_LEAD_MODEL"' "${WORKFLOWS_DIR}/shared/opencode-ci.md"; then
|
|
1825
1825
|
LEAD_OK=0; echo "FAIL: the CI config merge must write AGENT_LEAD_MODEL onto ci-workflow-agent" >&2
|
|
1826
1826
|
fi
|
|
1827
|
+
if ! grep -qF 'contents/${FRAGMENT}?ref=${DEFAULT_BRANCH}' "${WORKFLOWS_DIR}/shared/opencode-ci.md"; then
|
|
1828
|
+
LEAD_OK=0; echo "FAIL: the CI config merge must read opencode.ci.json from the default branch" >&2
|
|
1829
|
+
fi
|
|
1827
1830
|
if worker_installed implement && ! grep -qF 'subagent_type: planner' "$IMPLEMENT_WORKER_MD"; then
|
|
1828
1831
|
LEAD_OK=0; echo "FAIL: the implement prompt must run propose and archive in the planner agent" >&2
|
|
1829
1832
|
fi
|
|
@@ -2784,12 +2787,69 @@ fi
|
|
|
2784
2787
|
# No prompt rule stopped the model ending a turn in text; the compiled engine command continues the
|
|
2785
2788
|
# session when no safe output exists. Pliny-Bot #771 passed verify and exited with nothing delivered.
|
|
2786
2789
|
COMPILE_MJS="${HERE}/../../scripts/compile-agent-workflows.mjs"
|
|
2790
|
+
# In a consumer the action sits under .github/ and the script at the repository root.
|
|
2791
|
+
[ -f "$COMPILE_MJS" ] || COMPILE_MJS="${HERE}/../../../scripts/compile-agent-workflows.mjs"
|
|
2787
2792
|
if [ -f "$COMPILE_MJS" ] && grep -qF 'opencode run --continue' "$COMPILE_MJS" && grep -qF 'gh-aw/safeoutputs/outputs.jsonl' "$COMPILE_MJS"; then
|
|
2788
2793
|
PASS=$((PASS + 1))
|
|
2789
2794
|
else
|
|
2790
2795
|
FAIL=$((FAIL + 1))
|
|
2791
2796
|
echo "FAIL: the compiled engine command does not continue a session that delivered nothing" >&2
|
|
2792
2797
|
fi
|
|
2798
|
+
# A patch the base moved under is rebased and opened as a pull request before anyone is asked: Pliny-Bot
|
|
2799
|
+
# #787 and #794 sat as issues until rebased by hand. The rescue must run before the pull request steps,
|
|
2800
|
+
# and the push-failure flags must stay quiet when it worked.
|
|
2801
|
+
IMPL_MD="${WORKFLOWS_DIR}/agent-implement.md"
|
|
2802
|
+
if worker_installed implement; then
|
|
2803
|
+
if grep -qF 'uses: ./.github/actions/rebase-stale-patch' "$IMPL_MD" && grep -qF "steps.rescue.outputs.rescued == 'true'" "$IMPL_MD" && grep -qF "&& steps.rescue.outputs.rescued != 'true'" "$IMPL_MD" && [ -f "${HERE}/../rebase-stale-patch/rebase-stale-patch.sh" ]; then
|
|
2804
|
+
PASS=$((PASS + 1))
|
|
2805
|
+
else
|
|
2806
|
+
FAIL=$((FAIL + 1))
|
|
2807
|
+
echo "FAIL: implement does not rebase a patch its base moved under before parking it" >&2
|
|
2808
|
+
fi
|
|
2809
|
+
fi
|
|
2810
|
+
# The subagent monitor never sees a CI child finish and denies every spawn after the cap.
|
|
2811
|
+
if grep -qF 'for plugin in pc-system-reminders pc-subagent-monitor' "${WORKFLOWS_DIR}/shared/opencode-ci.md"; then
|
|
2812
|
+
PASS=$((PASS + 1))
|
|
2813
|
+
else
|
|
2814
|
+
FAIL=$((FAIL + 1))
|
|
2815
|
+
echo "FAIL: the CI setup must remove pc-subagent-monitor in CI" >&2
|
|
2816
|
+
fi
|
|
2817
|
+
# a published pull request is never flagged stalled, whatever the agent job's own result.
|
|
2818
|
+
if grep -A8 '^ incomplete:' "$IMPLEMENT_WORKER_MD" | grep -qF "needs.safe_outputs.outputs.created_pr_number == ''"; then
|
|
2819
|
+
PASS=$((PASS + 1))
|
|
2820
|
+
else
|
|
2821
|
+
FAIL=$((FAIL + 1))
|
|
2822
|
+
echo "FAIL: implement flags an issue stalled next to the pull request it just opened" >&2
|
|
2823
|
+
fi
|
|
2824
|
+
# A date the train cannot parse must not read as "main has been red since 1970".
|
|
2825
|
+
if grep -qF 'Could not read when $base_ref went red' "${HERE}/../merge-train/merge-train.sh"; then
|
|
2826
|
+
PASS=$((PASS + 1))
|
|
2827
|
+
else
|
|
2828
|
+
FAIL=$((FAIL + 1))
|
|
2829
|
+
echo "FAIL: the merge train treats an unparsable red-since date as an endless red main" >&2
|
|
2830
|
+
fi
|
|
2831
|
+
# An empty AGENT_TIER_MODEL must remove an override a reused runner kept from an earlier job.
|
|
2832
|
+
if grep -qF 'rm -f .opencode/harness.user.json' "${WORKFLOWS_DIR}/shared/opencode-ci.md"; then
|
|
2833
|
+
PASS=$((PASS + 1))
|
|
2834
|
+
else
|
|
2835
|
+
FAIL=$((FAIL + 1))
|
|
2836
|
+
echo "FAIL: a stale harness.user.json on a reused runner keeps every tier pinned" >&2
|
|
2837
|
+
fi
|
|
2838
|
+
# A stack whose last child closed outside the router is announced by housekeeping, or its root waits.
|
|
2839
|
+
if grep -qF "announcing the finished stack" "${HERE}/../housekeeping/action.yml"; then
|
|
2840
|
+
PASS=$((PASS + 1))
|
|
2841
|
+
else
|
|
2842
|
+
FAIL=$((FAIL + 1))
|
|
2843
|
+
echo "FAIL: housekeeping does not announce a stack whose children all closed outside the router" >&2
|
|
2844
|
+
fi
|
|
2845
|
+
# A stack child runs the feature branch's copy of the workflows, so the router brings the default
|
|
2846
|
+
# branch in before it dispatches one.
|
|
2847
|
+
if grep -qF 'bring $DEFAULT_BRANCH into $STACK_BRANCH before a child runs' "${WORKFLOWS_DIR}/work-router.yml"; then
|
|
2848
|
+
PASS=$((PASS + 1))
|
|
2849
|
+
else
|
|
2850
|
+
FAIL=$((FAIL + 1))
|
|
2851
|
+
echo "FAIL: stack children run the feature branch's stale workflows" >&2
|
|
2852
|
+
fi
|
|
2793
2853
|
# A stack root merges itself once every child has landed, through the same gate as any pull request.
|
|
2794
2854
|
# The people it still stops are the gate's own: a blast radius over the limit, a protected path, a
|
|
2795
2855
|
# finding. The gate must read the router's completion announcement, and the train must give the root
|
|
@@ -6,9 +6,12 @@ env:
|
|
|
6
6
|
# so it names its model here. `model:` below only satisfies the compiler. A repository may
|
|
7
7
|
# change it to any model its opencode.ci.json declares.
|
|
8
8
|
AGENT_LEAD_MODEL: "forge/glm-5-2"
|
|
9
|
-
#
|
|
10
|
-
#
|
|
9
|
+
# Every harness tier (plan, build, fast) on one model the CI's Forge account is granted.
|
|
10
|
+
# harness.json names quasar-438b and carina-60b, which that account is not granted: Pliny-Bot
|
|
11
|
+
# #823's build tasks failed on quasar. Empty runs the tiers harness.json names.
|
|
11
12
|
AGENT_TIER_MODEL: "plainconcepts/glm-5-2"
|
|
13
|
+
# The fast tier alone, while the CI's Forge account is not granted carina-60b.
|
|
14
|
+
AGENT_FAST_MODEL: "plainconcepts/glm-5-2"
|
|
12
15
|
# Printed by the prompt as the verification block. Consumers set their own commands.
|
|
13
16
|
VERIFY_COMMANDS: ""
|
|
14
17
|
REPO_RULES: "Apply only actionable outstanding reviewer feedback to the selected bot pull request. Make minimal changes that address each comment. Preserve architecture and do not weaken tests. Run full verification after changes."
|
|
@@ -499,8 +502,8 @@ safe-outputs:
|
|
|
499
502
|
# 240 went on to every worker at once when the provider was slow, which fixed the deaths and
|
|
500
503
|
# made every worker equally expensive to hang. These numbers are per worker: enough headroom
|
|
501
504
|
# for a slow gateway on the work it actually does, and not four hours for a run that reads one
|
|
502
|
-
# issue. Turns remain the guard against a confused agent looping;
|
|
503
|
-
#
|
|
505
|
+
# issue. Turns remain the guard against a confused agent looping; the credit ceiling meters the
|
|
506
|
+
# Forge models at Multiverse's list prices, set in shared/opencode-ci.md.
|
|
504
507
|
#
|
|
505
508
|
# Applies review comments to an existing branch and verifies the result.
|
|
506
509
|
timeout-minutes: 90
|
|
@@ -6,9 +6,12 @@ env:
|
|
|
6
6
|
# so it names its model here. `model:` below only satisfies the compiler. A repository may
|
|
7
7
|
# change it to any model its opencode.ci.json declares.
|
|
8
8
|
AGENT_LEAD_MODEL: "forge/glm-5-2"
|
|
9
|
-
#
|
|
10
|
-
#
|
|
9
|
+
# Every harness tier (plan, build, fast) on one model the CI's Forge account is granted.
|
|
10
|
+
# harness.json names quasar-438b and carina-60b, which that account is not granted: Pliny-Bot
|
|
11
|
+
# #823's build tasks failed on quasar. Empty runs the tiers harness.json names.
|
|
11
12
|
AGENT_TIER_MODEL: "plainconcepts/glm-5-2"
|
|
13
|
+
# The fast tier alone, while the CI's Forge account is not granted carina-60b.
|
|
14
|
+
AGENT_FAST_MODEL: "plainconcepts/glm-5-2"
|
|
12
15
|
REPO_RULES: "Read-only repository audit. Report only reproducible, actionable defects with evidence. Do not modify files, commit, push, or run write operations."
|
|
13
16
|
# Split out of REPO_RULES, which carried both the read-only discipline above and the list
|
|
14
17
|
# below. All four consuming repositories had customised the list and none could touch it
|
|
@@ -175,8 +178,8 @@ safe-outputs:
|
|
|
175
178
|
# 240 went on to every worker at once when the provider was slow, which fixed the deaths and
|
|
176
179
|
# made every worker equally expensive to hang. These numbers are per worker: enough headroom
|
|
177
180
|
# for a slow gateway on the work it actually does, and not four hours for a run that reads one
|
|
178
|
-
# issue. Turns remain the guard against a confused agent looping;
|
|
179
|
-
#
|
|
181
|
+
# issue. Turns remain the guard against a confused agent looping; the credit ceiling meters the
|
|
182
|
+
# Forge models at Multiverse's list prices, set in shared/opencode-ci.md.
|
|
180
183
|
#
|
|
181
184
|
# Sweeps the repository read-only and writes one findings issue. Observed around 45 minutes.
|
|
182
185
|
timeout-minutes: 90
|
|
@@ -6,9 +6,12 @@ env:
|
|
|
6
6
|
# so it names its model here. `model:` below only satisfies the compiler. A repository may
|
|
7
7
|
# change it to any model its opencode.ci.json declares.
|
|
8
8
|
AGENT_LEAD_MODEL: "forge/glm-5-2"
|
|
9
|
-
#
|
|
10
|
-
#
|
|
9
|
+
# Every harness tier (plan, build, fast) on one model the CI's Forge account is granted.
|
|
10
|
+
# harness.json names quasar-438b and carina-60b, which that account is not granted: Pliny-Bot
|
|
11
|
+
# #823's build tasks failed on quasar. Empty runs the tiers harness.json names.
|
|
11
12
|
AGENT_TIER_MODEL: "plainconcepts/glm-5-2"
|
|
13
|
+
# The fast tier alone, while the CI's Forge account is not granted carina-60b.
|
|
14
|
+
AGENT_FAST_MODEL: "plainconcepts/glm-5-2"
|
|
12
15
|
VERIFY_COMMANDS: ""
|
|
13
16
|
REPO_RULES: "Implement only the selected issue. Follow repository documentation and existing conventions. Do not weaken tests, lower coverage thresholds, or bypass checks."
|
|
14
17
|
# Split out of REPO_RULES because one field asked to carry architecture, testing, coverage and
|
|
@@ -243,7 +246,7 @@ jobs:
|
|
|
243
246
|
${{ env.REVIEW_LABEL }}
|
|
244
247
|
${{ env.STALLED_LABEL }}
|
|
245
248
|
conclude:
|
|
246
|
-
needs: [agent, safe_outputs]
|
|
249
|
+
needs: [activation, agent, safe_outputs]
|
|
247
250
|
if: >
|
|
248
251
|
always() &&
|
|
249
252
|
needs.agent.result == 'success' &&
|
|
@@ -276,13 +279,27 @@ jobs:
|
|
|
276
279
|
token: ${{ steps.app-token.outputs.token }}
|
|
277
280
|
issue-number: ${{ inputs.issue-number }}
|
|
278
281
|
labels: ${{ env.WORKING_LABEL }}
|
|
282
|
+
# gh-aw pushes onto the base as it is when the run ends; a base that moved under a long run
|
|
283
|
+
# makes the commits not apply and the work is filed as an issue. A plain rebase applies most
|
|
284
|
+
# of them (Pliny-Bot #787 and #794 were rebased by hand in minutes), so it is tried here, and
|
|
285
|
+
# every step below treats the rescued pull request as the one the agent opened.
|
|
286
|
+
- name: Rebase a patch the base moved under
|
|
287
|
+
id: rescue
|
|
288
|
+
if: needs.safe_outputs.outputs.created_pr_number == '' && contains(needs.agent.outputs.output_types, 'create_pull_request')
|
|
289
|
+
uses: ./.github/actions/rebase-stale-patch
|
|
290
|
+
with:
|
|
291
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
292
|
+
run-id: ${{ github.run_id }}
|
|
293
|
+
artifact: ${{ needs.activation.outputs.artifact_prefix }}agent
|
|
294
|
+
issue-number: ${{ inputs.issue-number }}
|
|
295
|
+
default-branch: ${{ github.event.repository.default_branch }}
|
|
279
296
|
- name: Verify PR closes the source issue
|
|
280
|
-
if: needs.safe_outputs.outputs.created_pr_number != ''
|
|
297
|
+
if: needs.safe_outputs.outputs.created_pr_number != '' || steps.rescue.outputs.rescued == 'true'
|
|
281
298
|
continue-on-error: true
|
|
282
299
|
uses: ./.github/actions/link-pr-to-issue
|
|
283
300
|
with:
|
|
284
301
|
token: ${{ steps.app-token.outputs.token }}
|
|
285
|
-
pr-number: ${{ needs.safe_outputs.outputs.created_pr_number }}
|
|
302
|
+
pr-number: ${{ needs.safe_outputs.outputs.created_pr_number || steps.rescue.outputs.pr-number }}
|
|
286
303
|
issue-number: ${{ inputs.issue-number }}
|
|
287
304
|
# GitHub only stores the PR-to-issue direction (Closes #N); the reverse lookup is a
|
|
288
305
|
# body-text search. Stamping hidden markers on the issue makes issue-to-branch exact:
|
|
@@ -290,13 +307,13 @@ jobs:
|
|
|
290
307
|
# the branch without guessing. Old markers are replaced, so a re-implement after a
|
|
291
308
|
# closed pull request re-stamps cleanly.
|
|
292
309
|
- name: Record the pull request and branch on the issue
|
|
293
|
-
if: needs.safe_outputs.outputs.created_pr_number != ''
|
|
310
|
+
if: needs.safe_outputs.outputs.created_pr_number != '' || steps.rescue.outputs.rescued == 'true'
|
|
294
311
|
continue-on-error: true
|
|
295
312
|
env:
|
|
296
313
|
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
297
314
|
REPO: ${{ github.repository }}
|
|
298
315
|
ISSUE: ${{ inputs.issue-number }}
|
|
299
|
-
PR_NUMBER: ${{ needs.safe_outputs.outputs.created_pr_number }}
|
|
316
|
+
PR_NUMBER: ${{ needs.safe_outputs.outputs.created_pr_number || steps.rescue.outputs.pr-number }}
|
|
300
317
|
run: |
|
|
301
318
|
set -euo pipefail
|
|
302
319
|
branch=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json headRefName --jq '.headRefName')
|
|
@@ -306,14 +323,14 @@ jobs:
|
|
|
306
323
|
gh issue edit "$ISSUE" --repo "$REPO" --body-file /tmp/issue-body.md
|
|
307
324
|
echo "Stamped PR #$PR_NUMBER and branch $branch on issue #$ISSUE"
|
|
308
325
|
- name: Mark issue as having a pull request pending
|
|
309
|
-
if: needs.safe_outputs.outputs.created_pr_number != ''
|
|
326
|
+
if: needs.safe_outputs.outputs.created_pr_number != '' || steps.rescue.outputs.rescued == 'true'
|
|
310
327
|
uses: ./.github/actions/add-issue-labels
|
|
311
328
|
with:
|
|
312
329
|
token: ${{ steps.app-token.outputs.token }}
|
|
313
330
|
issue-number: ${{ inputs.issue-number }}
|
|
314
331
|
labels: ${{ env.PR_PENDING_LABEL }}
|
|
315
332
|
- name: Reconcile the new bot pull request
|
|
316
|
-
if: needs.safe_outputs.outputs.created_pr_number != ''
|
|
333
|
+
if: needs.safe_outputs.outputs.created_pr_number != '' || steps.rescue.outputs.rescued == 'true'
|
|
317
334
|
env:
|
|
318
335
|
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
319
336
|
REPO: ${{ github.repository }}
|
|
@@ -354,7 +371,7 @@ jobs:
|
|
|
354
371
|
# comment exists to prevent (Odyssey #226). Distinguish them the same way the steps below
|
|
355
372
|
# do: gate on whether the agent actually asked for a pull request.
|
|
356
373
|
- name: Flag a patch that could not be pushed
|
|
357
|
-
if: needs.safe_outputs.outputs.created_pr_number == '' && needs.safe_outputs.outputs.process_safe_outputs_items_succeeded != '0' && needs.safe_outputs.outputs.process_safe_outputs_items_succeeded != '' && contains(needs.agent.outputs.output_types, 'create_pull_request')
|
|
374
|
+
if: needs.safe_outputs.outputs.created_pr_number == '' && needs.safe_outputs.outputs.process_safe_outputs_items_succeeded != '0' && needs.safe_outputs.outputs.process_safe_outputs_items_succeeded != '' && contains(needs.agent.outputs.output_types, 'create_pull_request') && steps.rescue.outputs.rescued != 'true'
|
|
358
375
|
uses: ./.github/actions/add-issue-labels
|
|
359
376
|
with:
|
|
360
377
|
token: ${{ steps.app-token.outputs.token }}
|
|
@@ -363,7 +380,7 @@ jobs:
|
|
|
363
380
|
${{ env.REVIEW_LABEL }}
|
|
364
381
|
${{ env.STALLED_LABEL }}
|
|
365
382
|
- name: Say where the patch went
|
|
366
|
-
if: needs.safe_outputs.outputs.created_pr_number == '' && needs.safe_outputs.outputs.process_safe_outputs_items_succeeded != '0' && needs.safe_outputs.outputs.process_safe_outputs_items_succeeded != '' && contains(needs.agent.outputs.output_types, 'create_pull_request')
|
|
383
|
+
if: needs.safe_outputs.outputs.created_pr_number == '' && needs.safe_outputs.outputs.process_safe_outputs_items_succeeded != '0' && needs.safe_outputs.outputs.process_safe_outputs_items_succeeded != '' && contains(needs.agent.outputs.output_types, 'create_pull_request') && steps.rescue.outputs.rescued != 'true'
|
|
367
384
|
uses: ./.github/actions/create-issue-comment
|
|
368
385
|
with:
|
|
369
386
|
token: ${{ steps.app-token.outputs.token }}
|
|
@@ -385,7 +402,7 @@ jobs:
|
|
|
385
402
|
# said "nothing landed" and neither could be acted on, while #175's run had produced more
|
|
386
403
|
# output than the run that succeeded in the same hour.
|
|
387
404
|
- name: Flag a request that was not applied
|
|
388
|
-
if: needs.safe_outputs.outputs.created_pr_number == '' && (needs.safe_outputs.outputs.process_safe_outputs_items_succeeded == '0' || needs.safe_outputs.outputs.process_safe_outputs_items_succeeded == '') && contains(needs.agent.outputs.output_types, 'create_pull_request')
|
|
405
|
+
if: needs.safe_outputs.outputs.created_pr_number == '' && (needs.safe_outputs.outputs.process_safe_outputs_items_succeeded == '0' || needs.safe_outputs.outputs.process_safe_outputs_items_succeeded == '') && contains(needs.agent.outputs.output_types, 'create_pull_request') && steps.rescue.outputs.rescued != 'true'
|
|
389
406
|
uses: ./.github/actions/add-issue-labels
|
|
390
407
|
with:
|
|
391
408
|
token: ${{ steps.app-token.outputs.token }}
|
|
@@ -394,7 +411,7 @@ jobs:
|
|
|
394
411
|
${{ env.REVIEW_LABEL }}
|
|
395
412
|
${{ env.STALLED_LABEL }}
|
|
396
413
|
- name: Say the request was not applied
|
|
397
|
-
if: needs.safe_outputs.outputs.created_pr_number == '' && (needs.safe_outputs.outputs.process_safe_outputs_items_succeeded == '0' || needs.safe_outputs.outputs.process_safe_outputs_items_succeeded == '') && contains(needs.agent.outputs.output_types, 'create_pull_request')
|
|
414
|
+
if: needs.safe_outputs.outputs.created_pr_number == '' && (needs.safe_outputs.outputs.process_safe_outputs_items_succeeded == '0' || needs.safe_outputs.outputs.process_safe_outputs_items_succeeded == '') && contains(needs.agent.outputs.output_types, 'create_pull_request') && steps.rescue.outputs.rescued != 'true'
|
|
398
415
|
uses: ./.github/actions/create-issue-comment
|
|
399
416
|
with:
|
|
400
417
|
token: ${{ steps.app-token.outputs.token }}
|
|
@@ -508,9 +525,13 @@ jobs:
|
|
|
508
525
|
[View this workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
|
|
509
526
|
incomplete:
|
|
510
527
|
needs: [agent, safe_outputs, eligibility]
|
|
528
|
+
# A published pull request is an outcome whatever the agent job's own result: Pliny-Bot #765
|
|
529
|
+
# opened #844 after 64 minutes, the agent job read as failed, and this job released the issue
|
|
530
|
+
# and flagged it stalled next to its open pull request.
|
|
511
531
|
if: >
|
|
512
532
|
always() &&
|
|
513
533
|
needs.eligibility.outputs.eligible == 'true' &&
|
|
534
|
+
needs.safe_outputs.outputs.created_pr_number == '' &&
|
|
514
535
|
(
|
|
515
536
|
needs.agent.result != 'success' ||
|
|
516
537
|
needs.safe_outputs.result != 'success'
|
|
@@ -692,8 +713,8 @@ safe-outputs:
|
|
|
692
713
|
# 240 went on to every worker at once when the provider was slow, which fixed the deaths and
|
|
693
714
|
# made every worker equally expensive to hang. These numbers are per worker: enough headroom
|
|
694
715
|
# for a slow gateway on the work it actually does, and not four hours for a run that reads one
|
|
695
|
-
# issue. Turns remain the guard against a confused agent looping;
|
|
696
|
-
#
|
|
716
|
+
# issue. Turns remain the guard against a confused agent looping; the credit ceiling meters the
|
|
717
|
+
# Forge models at Multiverse's list prices, set in shared/opencode-ci.md.
|
|
697
718
|
#
|
|
698
719
|
# Writes code, builds, runs a test suite and pushes a branch: the longest real work in the fleet.
|
|
699
720
|
timeout-minutes: 180
|
|
@@ -763,12 +784,12 @@ timeout-minutes: 180
|
|
|
763
784
|
`proposal.md` or `tasks.md` yourself.** A hand-written `tasks.md` carries no annotations,
|
|
764
785
|
and without them `pc-plan-apply` has no workers to spawn, so the whole change is done by
|
|
765
786
|
you alone, in sequence, on one model. That is the failure this step exists to prevent.
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
in
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
787
|
+
Let it plan the way it plans on a developer's machine: tasks that do not depend on each
|
|
788
|
+
other run in the same wave, in parallel, each on its tier's model. Keep the tests for code
|
|
789
|
+
in the same task as the code, and no task whose only job is to repair what an earlier task
|
|
790
|
+
broke (#696 ended with a twelve-minute task that fixed the test doubles the earlier ones
|
|
791
|
+
broke). A chain of tasks that each wait for the last is the slow shape: Pliny-Bot #794 ran
|
|
792
|
+
four workers in three waves, one at a time, for twenty-six of its fifty-nine minutes.
|
|
772
793
|
3. `pc-plan-apply`. The tasks are implemented by the subagents it spawns, in waves, each on
|
|
773
794
|
the model tier its annotation names. **In this phase you do not edit source files
|
|
774
795
|
yourself.** If it stops because a worker cannot be resolved, report that with
|
|
@@ -6,9 +6,12 @@ env:
|
|
|
6
6
|
# so it names its model here. `model:` below only satisfies the compiler. A repository may
|
|
7
7
|
# change it to any model its opencode.ci.json declares.
|
|
8
8
|
AGENT_LEAD_MODEL: "forge/glm-5-2"
|
|
9
|
-
#
|
|
10
|
-
#
|
|
9
|
+
# Every harness tier (plan, build, fast) on one model the CI's Forge account is granted.
|
|
10
|
+
# harness.json names quasar-438b and carina-60b, which that account is not granted: Pliny-Bot
|
|
11
|
+
# #823's build tasks failed on quasar. Empty runs the tiers harness.json names.
|
|
11
12
|
AGENT_TIER_MODEL: "plainconcepts/glm-5-2"
|
|
13
|
+
# The fast tier alone, while the CI's Forge account is not granted carina-60b.
|
|
14
|
+
AGENT_FAST_MODEL: "plainconcepts/glm-5-2"
|
|
12
15
|
VERIFY_COMMANDS: ""
|
|
13
16
|
REPO_RULES: "Review the selected pull request for defects and report what you verified. Do not decide the outcome: the workflow computes it from your report and from facts it measured before you ran."
|
|
14
17
|
# Where to look first, not what to escalate on. This list used to be the check that decided
|
|
@@ -1002,8 +1005,8 @@ safe-outputs:
|
|
|
1002
1005
|
# 240 went on to every worker at once when the provider was slow, which fixed the deaths and
|
|
1003
1006
|
# made every worker equally expensive to hang. These numbers are per worker: enough headroom
|
|
1004
1007
|
# for a slow gateway on the work it actually does, and not four hours for a run that reads one
|
|
1005
|
-
# issue. Turns remain the guard against a confused agent looping;
|
|
1006
|
-
#
|
|
1008
|
+
# issue. Turns remain the guard against a confused agent looping; the credit ceiling meters the
|
|
1009
|
+
# Forge models at Multiverse's list prices, set in shared/opencode-ci.md.
|
|
1007
1010
|
#
|
|
1008
1011
|
# Reads CI failure evidence and may fix, verify and re-push, so it can do implement's work on a smaller diff.
|
|
1009
1012
|
timeout-minutes: 120
|
|
@@ -5,10 +5,13 @@ env:
|
|
|
5
5
|
# every agent without its own model falls back to; the lead is where a wrong call costs a run,
|
|
6
6
|
# so it names its model here. `model:` below only satisfies the compiler. A repository may
|
|
7
7
|
# change it to any model its opencode.ci.json declares.
|
|
8
|
-
AGENT_LEAD_MODEL: "forge/glm-5-
|
|
9
|
-
#
|
|
10
|
-
#
|
|
8
|
+
AGENT_LEAD_MODEL: "forge/glm-5-3"
|
|
9
|
+
# Every harness tier (plan, build, fast) on one model the CI's Forge account is granted.
|
|
10
|
+
# harness.json names quasar-438b and carina-60b, which that account is not granted: Pliny-Bot
|
|
11
|
+
# #823's build tasks failed on quasar. Empty runs the tiers harness.json names.
|
|
11
12
|
AGENT_TIER_MODEL: "plainconcepts/glm-5-2"
|
|
13
|
+
# The fast tier alone, while the CI's Forge account is not granted carina-60b.
|
|
14
|
+
AGENT_FAST_MODEL: "plainconcepts/glm-5-2"
|
|
12
15
|
REPO_RULES: "Refine only the selected issue into a grounded, implementation-ready user story. Read repository documentation for domain context. Write acceptance criteria that match existing patterns. Where a story or its open questions decide how business data (named people, roles, addresses) is stored, resolve it toward the repository's convention for that data -- a database entity or a settings entry -- and record that choice in the acceptance criteria; do not carry a person's name or email address into the story as something the code reads from a constant. Do not implement code. Do not use web search or web fetch tools. The issue body already contains the research that motivated it; derive your domain context from the repository's own code and documentation only."
|
|
13
16
|
# The estimate decides whether a story gets split, and the prompt tells the agent these bands
|
|
14
17
|
# come from this repository's own merged pull requests. They have to actually come from it, or
|
|
@@ -882,8 +885,8 @@ safe-outputs:
|
|
|
882
885
|
# 240 went on to every worker at once when the provider was slow, which fixed the deaths and
|
|
883
886
|
# made every worker equally expensive to hang. These numbers are per worker: enough headroom
|
|
884
887
|
# for a slow gateway on the work it actually does, and not four hours for a run that reads one
|
|
885
|
-
# issue. Turns remain the guard against a confused agent looping;
|
|
886
|
-
#
|
|
888
|
+
# issue. Turns remain the guard against a confused agent looping; the credit ceiling meters the
|
|
889
|
+
# Forge models at Multiverse's list prices, set in shared/opencode-ci.md.
|
|
887
890
|
#
|
|
888
891
|
# Explores the repository per work unit, then rewrites one issue body. Measured: a run needing 36 turns died at 40 minutes with the story finished.
|
|
889
892
|
timeout-minutes: 90
|
|
@@ -6,9 +6,12 @@ env:
|
|
|
6
6
|
# so it names its model here. `model:` below only satisfies the compiler. A repository may
|
|
7
7
|
# change it to any model its opencode.ci.json declares.
|
|
8
8
|
AGENT_LEAD_MODEL: "forge/glm-5-2"
|
|
9
|
-
#
|
|
10
|
-
#
|
|
9
|
+
# Every harness tier (plan, build, fast) on one model the CI's Forge account is granted.
|
|
10
|
+
# harness.json names quasar-438b and carina-60b, which that account is not granted: Pliny-Bot
|
|
11
|
+
# #823's build tasks failed on quasar. Empty runs the tiers harness.json names.
|
|
11
12
|
AGENT_TIER_MODEL: "plainconcepts/glm-5-2"
|
|
13
|
+
# The fast tier alone, while the CI's Forge account is not granted carina-60b.
|
|
14
|
+
AGENT_FAST_MODEL: "plainconcepts/glm-5-2"
|
|
12
15
|
GIT_AUTHOR_NAME: "github-actions[bot]"
|
|
13
16
|
GIT_AUTHOR_EMAIL: "github-actions[bot]@users.noreply.github.com"
|
|
14
17
|
GIT_COMMITTER_NAME: "github-actions[bot]"
|
|
@@ -94,8 +97,8 @@ safe-outputs:
|
|
|
94
97
|
# 240 went on to every worker at once when the provider was slow, which fixed the deaths and
|
|
95
98
|
# made every worker equally expensive to hang. These numbers are per worker: enough headroom
|
|
96
99
|
# for a slow gateway on the work it actually does, and not four hours for a run that reads one
|
|
97
|
-
# issue. Turns remain the guard against a confused agent looping;
|
|
98
|
-
#
|
|
100
|
+
# issue. Turns remain the guard against a confused agent looping; the credit ceiling meters the
|
|
101
|
+
# Forge models at Multiverse's list prices, set in shared/opencode-ci.md.
|
|
99
102
|
#
|
|
100
103
|
# Reads a commit log already on disk and writes release notes. Nothing else.
|
|
101
104
|
timeout-minutes: 30
|
|
@@ -6,9 +6,12 @@ env:
|
|
|
6
6
|
# so it names its model here. `model:` below only satisfies the compiler. A repository may
|
|
7
7
|
# change it to any model its opencode.ci.json declares.
|
|
8
8
|
AGENT_LEAD_MODEL: "forge/glm-5-2"
|
|
9
|
-
#
|
|
10
|
-
#
|
|
9
|
+
# Every harness tier (plan, build, fast) on one model the CI's Forge account is granted.
|
|
10
|
+
# harness.json names quasar-438b and carina-60b, which that account is not granted: Pliny-Bot
|
|
11
|
+
# #823's build tasks failed on quasar. Empty runs the tiers harness.json names.
|
|
11
12
|
AGENT_TIER_MODEL: "plainconcepts/glm-5-2"
|
|
13
|
+
# The fast tier alone, while the CI's Forge account is not granted carina-60b.
|
|
14
|
+
AGENT_FAST_MODEL: "plainconcepts/glm-5-2"
|
|
12
15
|
REPO_RULES: "Triage issues opened by outside collaborators. Assess template completeness, security risk, change size, danger level, duplicates, clarity, reproducibility, acceptance criteria, and cross-cutting impact. When the request names specific people, email addresses, or business entities as the thing the code acts on, judge where that data is meant to live: business data that belongs in the database or a settings page is a design decision, not a wording detail, and it must not be left for the implementer to decide by hardcoding it. Do not implement code. Do not modify the issue body."
|
|
13
16
|
# What a product owner may ask for, and what has to become a maintainer-owned technical
|
|
14
17
|
# proposal instead. This is business policy, so it belongs to the repository rather than to
|
|
@@ -454,8 +457,8 @@ safe-outputs:
|
|
|
454
457
|
# 240 went on to every worker at once when the provider was slow, which fixed the deaths and
|
|
455
458
|
# made every worker equally expensive to hang. These numbers are per worker: enough headroom
|
|
456
459
|
# for a slow gateway on the work it actually does, and not four hours for a run that reads one
|
|
457
|
-
# issue. Turns remain the guard against a confused agent looping;
|
|
458
|
-
#
|
|
460
|
+
# issue. Turns remain the guard against a confused agent looping; the credit ceiling meters the
|
|
461
|
+
# Forge models at Multiverse's list prices, set in shared/opencode-ci.md.
|
|
459
462
|
#
|
|
460
463
|
# Reads one issue and the open-issue list, runs ten checks, writes one comment. No repository exploration, no build.
|
|
461
464
|
timeout-minutes: 45
|
|
@@ -13,6 +13,33 @@ description: |
|
|
|
13
13
|
shared baseline in the consumer copy. The merge step below is package-owned and required
|
|
14
14
|
for the agent to resolve its provider and its models.
|
|
15
15
|
|
|
16
|
+
# AI credits prices for the Forge models, so the AWF proxy can meter them against max-ai-credits
|
|
17
|
+
# instead of refusing them ("Model ... has no AI credits pricing", Atlas #2016). gh-aw maps this to
|
|
18
|
+
# apiProxy.providers in the AWF config, the operator overlay the proxy checks before its curated
|
|
19
|
+
# table and its models.dev snapshot. Without it glm-5-2 was metered at models.dev's glm-5 rate by
|
|
20
|
+
# prefix match, and carina-60b, quasar-438b and qwen-3-8-27b had no price at all.
|
|
21
|
+
#
|
|
22
|
+
# Rates are Multiverse's public list prices for the CompactifAI API, the provider Forge serves these
|
|
23
|
+
# from: https://docs.compactif.ai/pricing/ (also on https://multiversecomputing.com/compactifai/api),
|
|
24
|
+
# read 2026-10-18. Values are USD per token (the page's $/1M divided by 1e6); the proxy bills one AI
|
|
25
|
+
# credit per $0.01. Where the page shows no cached rate, prompt caching is off for that model and
|
|
26
|
+
# every input token is billed at the input rate, so cache_read equals input instead of the proxy's
|
|
27
|
+
# 10%-of-input default. The key is `openai` because Forge is the proxy's openai target.
|
|
28
|
+
models:
|
|
29
|
+
providers:
|
|
30
|
+
openai:
|
|
31
|
+
models:
|
|
32
|
+
carina-60b:
|
|
33
|
+
cost: { input: "0.00000005", cache_read: "0.00000005", output: "0.0000002" }
|
|
34
|
+
qwen-3-8-27b:
|
|
35
|
+
cost: { input: "0.0000004", cache_read: "0.0000004", output: "0.000002" }
|
|
36
|
+
glm-5-2:
|
|
37
|
+
cost: { input: "0.0000011", cache_read: "0.00000022", output: "0.0000035" }
|
|
38
|
+
glm-5-3:
|
|
39
|
+
cost: { input: "0.0000011", cache_read: "0.00000022", output: "0.0000035" }
|
|
40
|
+
quasar-438b:
|
|
41
|
+
cost: { input: "0.0000006", cache_read: "0.0000006", output: "0.0000018" }
|
|
42
|
+
|
|
16
43
|
# Consumer repositories should add stack-specific steps (NuGet cache, dotnet restore,
|
|
17
44
|
# OpenSpec, Playwright, etc.) after the shared baseline. The merge step at the end is
|
|
18
45
|
# package-owned and required for the agent to resolve its provider and models. Do not
|
|
@@ -182,9 +209,10 @@ pre-agent-steps:
|
|
|
182
209
|
# worker's prompt does not load, and has never run in CI; it stays off until it does.
|
|
183
210
|
#
|
|
184
211
|
# The tier models come from AGENT_TIER_MODEL when a worker sets it. The AWF proxy refuses
|
|
185
|
-
# a model it has no AI credits price for
|
|
186
|
-
#
|
|
187
|
-
# model through the gitignored per-user override the
|
|
212
|
+
# a model it has no AI credits price for; harness.json names carina-60b and quasar-438b,
|
|
213
|
+
# which had none until the `models.providers` prices at the top of this file (Atlas #2016).
|
|
214
|
+
# CI still pins every tier to one model, through the gitignored per-user override the
|
|
215
|
+
# plugin reads first, until a run on the newly priced models has passed.
|
|
188
216
|
run: |
|
|
189
217
|
set -euo pipefail
|
|
190
218
|
if [ ! -f .opencode/package.json ]; then
|
|
@@ -193,24 +221,51 @@ pre-agent-steps:
|
|
|
193
221
|
if [ ! -d .opencode/node_modules/@opencode/plugin ]; then
|
|
194
222
|
(cd .opencode && npm install --no-audit --no-fund --no-package-lock --ignore-scripts --loglevel=error)
|
|
195
223
|
fi
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
224
|
+
# pc-subagent-monitor frees a slot only on a child's idle or completed event, which a CI
|
|
225
|
+
# run never emits: after agents.maxConcurrent tasks it denied every spawn and Pliny-Bot
|
|
226
|
+
# #823 stopped half done. Off in CI until the plugin sees a child finish.
|
|
227
|
+
for plugin in pc-system-reminders pc-subagent-monitor; do
|
|
228
|
+
if [ -f ".opencode/plugins/${plugin}.js" ]; then
|
|
229
|
+
rm ".opencode/plugins/${plugin}.js"
|
|
230
|
+
# Tracked, so hide the deletion: an agent that sees it in `git status` restores it.
|
|
231
|
+
git update-index --skip-worktree ".opencode/plugins/${plugin}.js" 2>/dev/null || true
|
|
232
|
+
fi
|
|
233
|
+
done
|
|
234
|
+
# The runners are reused VMs and keep the workspace's ignored files between jobs, so an
|
|
235
|
+
# override an earlier run wrote is still there; an empty AGENT_TIER_MODEL has to remove it
|
|
236
|
+
# or the tiers stay pinned (Pliny-Bot #774 ran on glm-5-2 after the pin was dropped).
|
|
201
237
|
if [ -n "${AGENT_TIER_MODEL:-}" ]; then
|
|
202
238
|
jq -n --arg m "$AGENT_TIER_MODEL" '{models: {plan: $m, build: $m, fast: $m}}' > .opencode/harness.user.json
|
|
239
|
+
elif [ -n "${AGENT_FAST_MODEL:-}" ]; then
|
|
240
|
+
# One tier only: the CI's Forge account is not granted carina-60b, which harness.json
|
|
241
|
+
# names for fast work, so that tier alone moves to a model the account has.
|
|
242
|
+
jq -n --arg m "$AGENT_FAST_MODEL" '{models: {fast: $m}}' > .opencode/harness.user.json
|
|
243
|
+
else
|
|
244
|
+
rm -f .opencode/harness.user.json
|
|
203
245
|
fi
|
|
204
246
|
echo "Harness plugins: $(ls .opencode/plugins | tr '\n' ' ')"
|
|
205
247
|
echo "Tier models: $(jq -c '.models' .opencode/harness.user.json 2>/dev/null || jq -c '.models' .opencode/harness.json)"
|
|
206
248
|
|
|
207
249
|
- name: Merge the CI-only OpenCode provider into opencode.jsonc
|
|
250
|
+
env:
|
|
251
|
+
GH_TOKEN: ${{ github.token }}
|
|
252
|
+
DEFAULT_BRANCH: ${{ github.event.repository.default_branch || 'main' }}
|
|
208
253
|
run: |
|
|
209
254
|
set -euo pipefail
|
|
210
255
|
|
|
211
256
|
CONFIG=opencode.jsonc
|
|
212
257
|
FRAGMENT=opencode.ci.json
|
|
213
258
|
|
|
259
|
+
# The default branch's copy wins over the checkout's. A pull request branch cut before a
|
|
260
|
+
# model change keeps the old default: Pliny-Bot's gate on #836 ran forge/carina-60b,
|
|
261
|
+
# which the CI account is not granted, hours after main had moved off it.
|
|
262
|
+
if curl -fsSL -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github.raw" \
|
|
263
|
+
"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/contents/${FRAGMENT}?ref=${DEFAULT_BRANCH}" \
|
|
264
|
+
-o "${RUNNER_TEMP}/opencode.ci.json" && jq -e . "${RUNNER_TEMP}/opencode.ci.json" > /dev/null; then
|
|
265
|
+
FRAGMENT="${RUNNER_TEMP}/opencode.ci.json"
|
|
266
|
+
echo "Using $DEFAULT_BRANCH's opencode.ci.json"
|
|
267
|
+
fi
|
|
268
|
+
|
|
214
269
|
[ -f "$FRAGMENT" ] || { echo "::error::$FRAGMENT is missing from the checkout"; exit 1; }
|
|
215
270
|
|
|
216
271
|
# Pure JSON on purpose, not JSONC: jq cannot parse `//` comments, and a naive
|
|
@@ -134,7 +134,7 @@ env:
|
|
|
134
134
|
# times it tries before leaving the issue to a person. Only machine failures are retried; a
|
|
135
135
|
# verdict the agent reached on purpose is reported and never re-run, because re-running a
|
|
136
136
|
# decision reproduces it.
|
|
137
|
-
HOUSEKEEPING_RETRY_AFTER_HOURS: "
|
|
137
|
+
HOUSEKEEPING_RETRY_AFTER_HOURS: "1"
|
|
138
138
|
HOUSEKEEPING_MAX_RETRIES: "3"
|
|
139
139
|
# A bot pull request older than this with no merge-gate verdict at all is reported, never
|
|
140
140
|
# closed: the janitor deletes branches and labels, never someone's work.
|
|
@@ -756,10 +756,17 @@ jobs:
|
|
|
756
756
|
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
757
757
|
REPO: ${{ github.repository }}
|
|
758
758
|
STACK_BRANCH: ${{ needs.check-stack-branch.outputs.stack-branch }}
|
|
759
|
+
DEFAULT_BRANCH: ${{ github.event.repository.default_branch || 'main' }}
|
|
759
760
|
ISSUE_NUMBER: ${{ needs.classify.outputs.issue-number }}
|
|
760
761
|
ATTEMPTS: ${{ needs.classify.outputs.attempts }}
|
|
761
762
|
run: |
|
|
762
763
|
set -euo pipefail
|
|
764
|
+
# A dispatch on the feature ref runs the feature branch's own copy of the workflows, so a
|
|
765
|
+
# branch cut before a workflow fix keeps running the old one: Pliny-Bot #774 ran every
|
|
766
|
+
# tier on glm-5-2 from feat/767 after main had dropped the pin. Bring the default branch
|
|
767
|
+
# in first. A conflict (409) leaves the branch as it is; the run still goes ahead.
|
|
768
|
+
code=$(gh api -X POST "repos/$REPO/merges" -f base="$STACK_BRANCH" -f head="$DEFAULT_BRANCH" -f commit_message="chore: bring $DEFAULT_BRANCH into $STACK_BRANCH before a child runs" --silent -i 2>/dev/null | head -1 | awk '{print $2}' || true)
|
|
769
|
+
echo "Merged $DEFAULT_BRANCH into $STACK_BRANCH: HTTP ${code:-unknown} (201 merged, 204 up to date, 409 conflict)"
|
|
763
770
|
gh workflow run agent-implement.lock.yml --repo "$REPO" --ref "$STACK_BRANCH" \
|
|
764
771
|
-f issue-number="$ISSUE_NUMBER" \
|
|
765
772
|
-f attempts_so_far="$ATTEMPTS"
|