Merge pull request 'agents: review round 2 — split-PR flow, thread recency, mention boundaries, learnings cap, pm model' (#96) from fix/review-round2 into main
ci / lint (push) Successful in 10s
ci / lint (push) Successful in 10s
This commit was merged in pull request #96.
This commit is contained in:
@@ -11,6 +11,10 @@ jobs:
|
|||||||
# One run at a time PER ISSUE: two quick comments on the same issue would otherwise race —
|
# One run at a time PER ISSUE: two quick comments on the same issue would otherwise race —
|
||||||
# both checking out ai/issue-N, pushing (non-fast-forward loss) and double-posting. Queued
|
# both checking out ai/issue-N, pushing (non-fast-forward loss) and double-posting. Queued
|
||||||
# runs wait (no cancel) so every trigger is still processed, just serially.
|
# runs wait (no cancel) so every trigger is still processed, just serially.
|
||||||
|
# KNOWN CAVEAT: a run triggered on the PR thread groups under the PR number, not the origin
|
||||||
|
# issue (that mapping is only resolved later, in route.sh) — so an issue-thread run and a
|
||||||
|
# PR-thread run for the SAME work item can overlap. Accepted: they post to different threads,
|
||||||
|
# and the branch is only mutated by dev runs, which resume serially per thread.
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ai-agent-${{ github.repository }}-${{ github.event.issue.number }}
|
group: ai-agent-${{ github.repository }}-${{ github.event.issue.number }}
|
||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
@@ -116,6 +120,7 @@ jobs:
|
|||||||
run: bash "$SCRIPTS/route.sh"
|
run: bash "$SCRIPTS/route.sh"
|
||||||
|
|
||||||
- name: Install opencode + provider config (+ Playwright MCP for browser agents)
|
- name: Install opencode + provider config (+ Playwright MCP for browser agents)
|
||||||
|
if: steps.prep.outputs.mode != 'skip'
|
||||||
env:
|
env:
|
||||||
SCRIPTS: ${{ runner.temp }}/agents-scripts
|
SCRIPTS: ${{ runner.temp }}/agents-scripts
|
||||||
OLLAMA_URL: ${{ secrets.OLLAMA_URL }}
|
OLLAMA_URL: ${{ secrets.OLLAMA_URL }}
|
||||||
@@ -125,6 +130,7 @@ jobs:
|
|||||||
run: bash "$SCRIPTS/install-opencode.sh"
|
run: bash "$SCRIPTS/install-opencode.sh"
|
||||||
|
|
||||||
- name: Install caller-provided skills (from the caller repo's .gitea/agent-skills/)
|
- name: Install caller-provided skills (from the caller repo's .gitea/agent-skills/)
|
||||||
|
if: steps.prep.outputs.mode != 'skip'
|
||||||
# Framework skill-plugin hook. A consuming repo can ship its OWN opencode skills under
|
# Framework skill-plugin hook. A consuming repo can ship its OWN opencode skills under
|
||||||
# `.gitea/agent-skills/<name>/` (SKILL.md + skill.json + optional setup.sh) — e.g. homelab's
|
# `.gitea/agent-skills/<name>/` (SKILL.md + skill.json + optional setup.sh) — e.g. homelab's
|
||||||
# "ssh into the deploy host" skill. This installs the ones allowed for the running agent, so
|
# "ssh into the deploy host" skill. This installs the ones allowed for the running agent, so
|
||||||
@@ -139,6 +145,7 @@ jobs:
|
|||||||
run: bash "$SCRIPTS/install-caller-skills.sh"
|
run: bash "$SCRIPTS/install-caller-skills.sh"
|
||||||
|
|
||||||
- name: Set up `gitea-api` skill (let agents read/write issues, PRs, Actions across repos)
|
- name: Set up `gitea-api` skill (let agents read/write issues, PRs, Actions across repos)
|
||||||
|
if: steps.prep.outputs.mode != 'skip'
|
||||||
# Emits an opencode Skill file. The skill uses SELF_TOKEN — the running agent's OWN token
|
# Emits an opencode Skill file. The skill uses SELF_TOKEN — the running agent's OWN token
|
||||||
# (e.g. TOKEN_PM for @pm), injected into the Run-agent step below — so each agent talks to
|
# (e.g. TOKEN_PM for @pm), injected into the Run-agent step below — so each agent talks to
|
||||||
# Gitea as itself. This step only writes the doc; permission.skill scopes who may load it.
|
# Gitea as itself. This step only writes the doc; permission.skill scopes who may load it.
|
||||||
@@ -147,6 +154,7 @@ jobs:
|
|||||||
run: bash "$SCRIPTS/skill-gitea-api.sh"
|
run: bash "$SCRIPTS/skill-gitea-api.sh"
|
||||||
|
|
||||||
- name: Set up `gitea-admin` skill (@ops only — administer the Gitea instance)
|
- name: Set up `gitea-admin` skill (@ops only — administer the Gitea instance)
|
||||||
|
if: steps.prep.outputs.mode != 'skip'
|
||||||
# Instance administration (orgs/users/repos/labels/secrets/scoped tokens). The SKILL.md is
|
# Instance administration (orgs/users/repos/labels/secrets/scoped tokens). The SKILL.md is
|
||||||
# written ONLY for @ops (skill-gitea-admin.sh gates on NAME) and permission.skill also denies
|
# written ONLY for @ops (skill-gitea-admin.sh gates on NAME) and permission.skill also denies
|
||||||
# it to every other agent. It uses SELF_TOKEN (which for @ops is TOKEN_OPS), injected into the
|
# it to every other agent. It uses SELF_TOKEN (which for @ops is TOKEN_OPS), injected into the
|
||||||
@@ -157,6 +165,7 @@ jobs:
|
|||||||
run: bash "$SCRIPTS/skill-gitea-admin.sh"
|
run: bash "$SCRIPTS/skill-gitea-admin.sh"
|
||||||
|
|
||||||
- name: Inspect / fetch image attachments (download only for vision agents)
|
- name: Inspect / fetch image attachments (download only for vision agents)
|
||||||
|
if: steps.prep.outputs.mode != 'skip'
|
||||||
id: imgs
|
id: imgs
|
||||||
env:
|
env:
|
||||||
SCRIPTS: ${{ runner.temp }}/agents-scripts
|
SCRIPTS: ${{ runner.temp }}/agents-scripts
|
||||||
@@ -166,6 +175,7 @@ jobs:
|
|||||||
run: bash "$SCRIPTS/fetch-images.sh"
|
run: bash "$SCRIPTS/fetch-images.sh"
|
||||||
|
|
||||||
- name: Fetch the full issue thread (shared memory)
|
- name: Fetch the full issue thread (shared memory)
|
||||||
|
if: steps.prep.outputs.mode != 'skip'
|
||||||
env:
|
env:
|
||||||
SCRIPTS: ${{ runner.temp }}/agents-scripts
|
SCRIPTS: ${{ runner.temp }}/agents-scripts
|
||||||
GT: ${{ secrets.GITEA_TOKEN }}
|
GT: ${{ secrets.GITEA_TOKEN }}
|
||||||
@@ -173,6 +183,7 @@ jobs:
|
|||||||
run: bash "$SCRIPTS/fetch-thread.sh"
|
run: bash "$SCRIPTS/fetch-thread.sh"
|
||||||
|
|
||||||
- name: Run agent
|
- name: Run agent
|
||||||
|
if: steps.prep.outputs.mode != 'skip'
|
||||||
id: run
|
id: run
|
||||||
env:
|
env:
|
||||||
SCRIPTS: ${{ runner.temp }}/agents-scripts
|
SCRIPTS: ${{ runner.temp }}/agents-scripts
|
||||||
@@ -198,6 +209,7 @@ jobs:
|
|||||||
run: bash "$SCRIPTS/run-agent.sh"
|
run: bash "$SCRIPTS/run-agent.sh"
|
||||||
|
|
||||||
- name: Build run report (tool calls + input/output tokens + $ cost) from the event stream
|
- name: Build run report (tool calls + input/output tokens + $ cost) from the event stream
|
||||||
|
if: steps.prep.outputs.mode != 'skip'
|
||||||
id: log
|
id: log
|
||||||
env:
|
env:
|
||||||
SCRIPTS: ${{ runner.temp }}/agents-scripts
|
SCRIPTS: ${{ runner.temp }}/agents-scripts
|
||||||
@@ -206,6 +218,7 @@ jobs:
|
|||||||
run: bash "$SCRIPTS/build-activity-log.sh"
|
run: bash "$SCRIPTS/build-activity-log.sh"
|
||||||
|
|
||||||
- name: Publish — PR (dev agents) or comment (pm), always reply in the issue
|
- name: Publish — PR (dev agents) or comment (pm), always reply in the issue
|
||||||
|
if: steps.prep.outputs.mode != 'skip'
|
||||||
env:
|
env:
|
||||||
SCRIPTS: ${{ runner.temp }}/agents-scripts
|
SCRIPTS: ${{ runner.temp }}/agents-scripts
|
||||||
GT: ${{ secrets.GITEA_TOKEN }}
|
GT: ${{ secrets.GITEA_TOKEN }}
|
||||||
@@ -232,7 +245,7 @@ jobs:
|
|||||||
# This best-effort step opens a PR for the pushed branch so nothing is silently lost. Runs from
|
# This best-effort step opens a PR for the pushed branch so nothing is silently lost. Runs from
|
||||||
# $SCRIPTS (outside the workspace) so it works even if the tree was mangled by the agent.
|
# $SCRIPTS (outside the workspace) so it works even if the tree was mangled by the agent.
|
||||||
- name: Rescue — open a PR for pushed work if the run failed
|
- name: Rescue — open a PR for pushed work if the run failed
|
||||||
if: failure()
|
if: failure() && steps.prep.outputs.mode != 'skip'
|
||||||
env:
|
env:
|
||||||
SCRIPTS: ${{ runner.temp }}/agents-scripts
|
SCRIPTS: ${{ runner.temp }}/agents-scripts
|
||||||
GT: ${{ secrets.GITEA_TOKEN }}
|
GT: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"pm": {
|
"pm": {
|
||||||
"model": "ollama-cloud/gemma4:cloud",
|
"model": "ollama-cloud/minimax-m3:cloud",
|
||||||
"vision": true,
|
"vision": true,
|
||||||
"mode": "comment",
|
"mode": "comment",
|
||||||
"skills": [
|
"skills": [
|
||||||
|
|||||||
@@ -4,14 +4,25 @@
|
|||||||
# to its real author (@pm/@qa/@junior/…). Strip the hidden `<!-- 🤖 … -->` loop-prevention marker
|
# to its real author (@pm/@qa/@junior/…). Strip the hidden `<!-- 🤖 … -->` loop-prevention marker
|
||||||
# from bodies — it's plumbing, not conversation, and would just waste prompt tokens.
|
# from bodies — it's plumbing, not conversation, and would just waste prompt tokens.
|
||||||
#
|
#
|
||||||
|
# PAGINATION: Gitea returns comments ASCENDING and `limit` caps a single page — a bare ?limit=100
|
||||||
|
# used to keep the OLDEST 100 comments and silently drop the newest (the exact opposite of what an
|
||||||
|
# agent needs on a long thread). Fetch all pages (up to 10 = 500 comments) and keep the LAST 100.
|
||||||
|
#
|
||||||
# Required env (provided by the workflow step): GT NUM GITHUB_SERVER_URL GITHUB_REPOSITORY
|
# Required env (provided by the workflow step): GT NUM GITHUB_SERVER_URL GITHUB_REPOSITORY
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
|
API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
|
||||||
curl -sS -H "Authorization: token $GT" "$API/issues/$NUM/comments?limit=100" 2>/dev/null \
|
: > /tmp/thread_pages.json
|
||||||
| jq -r '.[] |
|
for page in $(seq 1 10); do
|
||||||
( if (.user.login == "ffaerber") then "@ffaerber (the maintainer)"
|
pg=$(curl -sS -H "Authorization: token $GT" "$API/issues/$NUM/comments?limit=50&page=$page" 2>/dev/null) || pg='[]'
|
||||||
else "@" + .user.login end ) as $who |
|
n=$(printf '%s' "$pg" | jq 'if type=="array" then length else 0 end' 2>/dev/null || echo 0)
|
||||||
"### comment by \($who):\n\(.body | gsub("\\s*<!-- 🤖 agent reply — do not trigger -->"; ""))\n"' \
|
[ "${n:-0}" -gt 0 ] && printf '%s\n' "$pg" >> /tmp/thread_pages.json
|
||||||
> /tmp/thread.md 2>/dev/null || true
|
[ "${n:-0}" -lt 50 ] && break
|
||||||
echo "thread comments fetched: $(grep -c '^### comment by ' /tmp/thread.md 2>/dev/null || echo 0)"
|
done
|
||||||
|
jq -rs '
|
||||||
|
add // [] | .[-100:] | .[] |
|
||||||
|
( if (.user.login == "ffaerber") then "@ffaerber (the maintainer)"
|
||||||
|
else "@" + .user.login end ) as $who |
|
||||||
|
"### comment by \($who):\n\(.body | gsub("\\s*<!-- 🤖 agent reply — do not trigger -->"; ""))\n"' \
|
||||||
|
/tmp/thread_pages.json > /tmp/thread.md 2>/dev/null || : > /tmp/thread.md
|
||||||
|
echo "thread comments fetched: $(grep -c '^### comment by ' /tmp/thread.md 2>/dev/null || echo 0) (newest 100 kept)"
|
||||||
|
|||||||
@@ -44,8 +44,14 @@ trig() { if [ -z "$TTOK" ]; then echo "no trigger token — cannot fire on #$1";
|
|||||||
# Origin issue for this run (route.sh resolves it from the branch on PR threads), and a resolver for
|
# Origin issue for this run (route.sh resolves it from the branch on PR threads), and a resolver for
|
||||||
# the open PR built from its branch (ai/issue-<issue>). Lets @pm/@qa cross between the issue and PR.
|
# the open PR built from its branch (ai/issue-<issue>). Lets @pm/@qa cross between the issue and PR.
|
||||||
ISSN="${ISSNUM:-$NUM}"
|
ISSN="${ISSNUM:-$NUM}"
|
||||||
resolve_pr() { curl -sS "${hdr[@]}" "$API/pulls?state=open&limit=50" \
|
# All OPEN PRs belonging to this issue, oldest→newest. Matches ai/issue-N AND the ai/issue-N-<slug>
|
||||||
| jq -r --arg br "ai/issue-$ISSN" 'if type=="array" then (map(select(.head.ref==$br))|.[0].number // empty) else empty end' 2>/dev/null; }
|
# split branches AGENTS.md tells devs to use — an exact-only match silently stalled the flow on
|
||||||
|
# slugged branches (DELEGATE:@qa found "no open PR"; autopilot MERGE_PR couldn't merge).
|
||||||
|
resolve_prs() { curl -sS "${hdr[@]}" "$API/pulls?state=open&limit=50" \
|
||||||
|
| jq -r --arg br "ai/issue-$ISSN" 'if type=="array" then
|
||||||
|
([ .[] | select(.head.ref==$br or (.head.ref|startswith($br+"-"))) | .number ] | sort | join(" "))
|
||||||
|
else "" end' 2>/dev/null; }
|
||||||
|
resolve_pr() { resolve_prs | awk '{print $NF}'; } # newest open PR (empty if none)
|
||||||
# Remove the 'autopilot' label from an issue by resolving its ID first (Gitea's DELETE label
|
# Remove the 'autopilot' label from an issue by resolving its ID first (Gitea's DELETE label
|
||||||
# endpoint is by ID, not name). Arg $1 = issue number. Used as the autopilot kill switch.
|
# endpoint is by ID, not name). Arg $1 = issue number. Used as the autopilot kill switch.
|
||||||
del_autopilot_label() {
|
del_autopilot_label() {
|
||||||
@@ -194,9 +200,13 @@ if [ "$MODE" != "pr" ]; then
|
|||||||
# (@qa never merges — it approves and hands back here.) Merge with the PAT (TTOK), not the built-in
|
# (@qa never merges — it approves and hands back here.) Merge with the PAT (TTOK), not the built-in
|
||||||
# token, so the push to main fires the deploy. TOKEN_PM must carry write:repository.
|
# token, so the push to main fires the deploy. TOKEN_PM must carry write:repository.
|
||||||
if [ "$NAME" = "pm" ] && [ "$AUTOPILOT" = "true" ] && grep -qiE '^[[:space:]]*MERGE_PR[[:space:]]*$' /tmp/agent_out.md; then
|
if [ "$NAME" = "pm" ] && [ "$AUTOPILOT" = "true" ] && grep -qiE '^[[:space:]]*MERGE_PR[[:space:]]*$' /tmp/agent_out.md; then
|
||||||
PRN=$(resolve_pr)
|
PRS=$(resolve_prs); PRN=${PRS##* }; CNT=$(echo "$PRS" | wc -w)
|
||||||
if [ -z "$PRN" ]; then
|
if [ -z "$PRN" ]; then
|
||||||
echo "MERGE_PR but no open PR found for issue #$ISSN"
|
echo "MERGE_PR but no open PR found for issue #$ISSN"
|
||||||
|
elif [ "$CNT" -gt 1 ]; then
|
||||||
|
# Split-PR work: auto-merging just one of several open PRs is half a change deployed.
|
||||||
|
del_autopilot_label "$ISSN"
|
||||||
|
post_to "$ISSN" "⚠️ This issue has $CNT open PRs (#${PRS// /, #}) — autopilot only merges single-PR work. Removed the autopilot label; @ffaerber please review and merge them in order."
|
||||||
else
|
else
|
||||||
echo "@pm autopilot: merging PR #$PRN (issue #$ISSN)"
|
echo "@pm autopilot: merging PR #$PRN (issue #$ISSN)"
|
||||||
mc=$(curl -sS -o /tmp/merge_resp.txt -w '%{http_code}' -X POST \
|
mc=$(curl -sS -o /tmp/merge_resp.txt -w '%{http_code}' -X POST \
|
||||||
@@ -221,7 +231,7 @@ if [ "$MODE" != "pr" ]; then
|
|||||||
# pm → qa → merge), and run-agent.sh injects LEARNINGS.md into every future prompt — closing the loop.
|
# pm → qa → merge), and run-agent.sh injects LEARNINGS.md into every future prompt — closing the loop.
|
||||||
if [ "$NAME" = "pm" ] && grep -qiE '^[[:space:]]*RETRO[[:space:]]*$' /tmp/agent_out.md; then
|
if [ "$NAME" = "pm" ] && grep -qiE '^[[:space:]]*RETRO[[:space:]]*$' /tmp/agent_out.md; then
|
||||||
PRN=$(curl -sS "${hdr[@]}" "$API/pulls?state=all&limit=50" \
|
PRN=$(curl -sS "${hdr[@]}" "$API/pulls?state=all&limit=50" \
|
||||||
| jq -r --arg br "ai/issue-$ISSN" 'if type=="array" then ([.[]|select(.head.ref==$br)] | sort_by(.number) | last | .number // empty) else empty end' 2>/dev/null)
|
| jq -r --arg br "ai/issue-$ISSN" 'if type=="array" then ([.[]|select(.head.ref==$br or (.head.ref|startswith($br+"-")))] | sort_by(.number) | last | .number // empty) else empty end' 2>/dev/null)
|
||||||
rbody=$(printf 'Retrospective for issue #%s%s.\n\nRead the FULL issue thread%s using the gitea-api skill (issue comments%s and the PR diff). Identify what went wrong, slow, or needed human correction — missed wiring, review misses, bounced rounds, unclear delegation, missing context.\n\nThen APPEND the distilled learnings to `LEARNINGS.md` at the repo root (create it with a short header if missing). Rules for entries:\n- 3 to 6 bullets max, each ONE line: `symptom -> rule for next time`.\n- Concrete and checkable (name the file/step/marker), not generic advice.\n- Do not repeat an existing bullet; refine it instead.\n- Do not rewrite unrelated parts of the file.\n\nThese learnings are injected into every future agent prompt, so quality over quantity.' \
|
rbody=$(printf 'Retrospective for issue #%s%s.\n\nRead the FULL issue thread%s using the gitea-api skill (issue comments%s and the PR diff). Identify what went wrong, slow, or needed human correction — missed wiring, review misses, bounced rounds, unclear delegation, missing context.\n\nThen APPEND the distilled learnings to `LEARNINGS.md` at the repo root (create it with a short header if missing). Rules for entries:\n- 3 to 6 bullets max, each ONE line: `symptom -> rule for next time`.\n- Concrete and checkable (name the file/step/marker), not generic advice.\n- Do not repeat an existing bullet; refine it instead.\n- Do not rewrite unrelated parts of the file.\n\nThese learnings are injected into every future agent prompt, so quality over quantity.' \
|
||||||
"$ISSN" "${PRN:+ / PR #$PRN}" "${PRN:+ and PR #$PRN thread}" "${PRN:+, PR comments}")
|
"$ISSN" "${PRN:+ / PR #$PRN}" "${PRN:+ and PR #$PRN thread}" "${PRN:+, PR comments}")
|
||||||
rnum=$(curl -sS -X POST "${hdr[@]}" "$API/issues" \
|
rnum=$(curl -sS -X POST "${hdr[@]}" "$API/issues" \
|
||||||
@@ -242,8 +252,10 @@ if [ "$MODE" != "pr" ]; then
|
|||||||
# the PR. Chain terminates: normal → @pm tells the creator (no marker); autopilot → @pm merges above.
|
# the PR. Chain terminates: normal → @pm tells the creator (no marker); autopilot → @pm merges above.
|
||||||
if [ -n "$target" ] && [ "$target" != "$NAME" ]; then
|
if [ -n "$target" ] && [ "$target" != "$NAME" ]; then
|
||||||
if [ "$target" = "qa" ]; then
|
if [ "$target" = "qa" ]; then
|
||||||
PRN=$(resolve_pr)
|
PRS=$(resolve_prs); PRN=${PRS##* }; CNT=$(echo "$PRS" | wc -w)
|
||||||
if [ -n "$PRN" ]; then
|
if [ -n "$PRN" ] && [ "$CNT" -gt 1 ]; then
|
||||||
|
trig "$ISSN" "@qa please review the $CNT open PRs for issue #$ISSN (#${PRS// /, #}) — put your recommendations on each PR; approve only when ALL are good."
|
||||||
|
elif [ -n "$PRN" ]; then
|
||||||
trig "$ISSN" "@qa please review PR #$PRN for issue #$ISSN — put your recommendations on the PR, or approve."
|
trig "$ISSN" "@qa please review PR #$PRN for issue #$ISSN — put your recommendations on the PR, or approve."
|
||||||
else
|
else
|
||||||
echo "DELEGATE:@qa but no open PR yet for issue #$ISSN — not firing"
|
echo "DELEGATE:@qa but no open PR yet for issue #$ISSN — not firing"
|
||||||
|
|||||||
@@ -31,11 +31,22 @@ name=""
|
|||||||
# load-bearing for the flow's trigger comments: "@pm — @qa approved …" must route to @pm (pm is
|
# load-bearing for the flow's trigger comments: "@pm — @qa approved …" must route to @pm (pm is
|
||||||
# checked first), while "@junior please address @qa's review …" must route to the dev (devs are
|
# checked first), while "@junior please address @qa's review …" must route to the dev (devs are
|
||||||
# checked before qa). If you add an agent or reword a trigger in publish.sh, re-check this order.
|
# checked before qa). If you add an agent or reword a trigger in publish.sh, re-check this order.
|
||||||
|
# WORD-BOUNDARY match, not substring: "@internal" or "x@internet.com" must NOT route to @intern
|
||||||
|
# (the workflow gate can only do contains(), so this is where its false positives get filtered).
|
||||||
for a in pm junior senior lead qa ops intern; do
|
for a in pm junior senior lead qa ops intern; do
|
||||||
case "$scan" in *"@$a"*) name=$a; break;; esac
|
if printf '%s' "$scan" | grep -qE "(^|[^[:alnum:]_])@$a([^[:alnum:]_-]|\$)"; then name=$a; break; fi
|
||||||
done
|
done
|
||||||
if [ -z "$name" ]; then
|
if [ -z "$name" ]; then
|
||||||
if [ -z "$CID" ]; then name=pm; else echo "no known agent mentioned"; exit 1; fi
|
if [ -z "$CID" ]; then
|
||||||
|
name=pm
|
||||||
|
else
|
||||||
|
# Not an agent task (e.g. the gate's contains() matched "@internal"). Skip GRACEFULLY: emit
|
||||||
|
# mode=skip so every later step no-ops — a red run for a non-agent comment is just noise.
|
||||||
|
echo "no known agent mentioned (word-boundary) — skipping run"
|
||||||
|
{ echo "name=none"; echo "model=none"; echo "vision=false"; echo "mode=skip"; echo "skills=[]";
|
||||||
|
echo "branch=main"; echo "new=false"; echo "autopilot=false"; echo "issnum=$NUM"; } >> "$GITHUB_OUTPUT"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
model=$(jq -r --arg a "$name" '.[$a].model' /tmp/agents.json)
|
model=$(jq -r --arg a "$name" '.[$a].model' /tmp/agents.json)
|
||||||
vision=$(jq -r --arg a "$name" '.[$a].vision' /tmp/agents.json)
|
vision=$(jq -r --arg a "$name" '.[$a].vision' /tmp/agents.json)
|
||||||
|
|||||||
@@ -109,8 +109,18 @@ fi
|
|||||||
# TEAM LEARNINGS — distilled from past retros (see the RETRO flow in publish.sh). Lives at the
|
# TEAM LEARNINGS — distilled from past retros (see the RETRO flow in publish.sh). Lives at the
|
||||||
# CALLER repo root as LEARNINGS.md, maintained by retro PRs. Injected into EVERY agent's prompt
|
# CALLER repo root as LEARNINGS.md, maintained by retro PRs. Injected into EVERY agent's prompt
|
||||||
# (capped) so past mistakes actually change future behavior — this is the feedback loop.
|
# (capped) so past mistakes actually change future behavior — this is the feedback loop.
|
||||||
|
# Cap is LINE-aware and keeps the NEWEST entries: retros append at the bottom, so a byte-cap from
|
||||||
|
# the top would silently drop the latest lessons first (and cut mid-bullet).
|
||||||
LEARN=""
|
LEARN=""
|
||||||
[ -s LEARNINGS.md ] && LEARN=$(head -c 4000 LEARNINGS.md)
|
if [ -s LEARNINGS.md ]; then
|
||||||
|
if [ "$(wc -l < LEARNINGS.md)" -gt 80 ]; then
|
||||||
|
LEARN=$(printf '%s\n_(older learnings truncated — full list in LEARNINGS.md)_\n%s' \
|
||||||
|
"$(head -n 3 LEARNINGS.md)" "$(tail -n 70 LEARNINGS.md)")
|
||||||
|
else
|
||||||
|
LEARN=$(cat LEARNINGS.md)
|
||||||
|
fi
|
||||||
|
LEARN=$(printf '%s' "$LEARN" | head -c 8000)
|
||||||
|
fi
|
||||||
[ -n "$LEARN" ] && LEARN="
|
[ -n "$LEARN" ] && LEARN="
|
||||||
TEAM LEARNINGS (distilled from past retros in this repo — APPLY them; they exist because a
|
TEAM LEARNINGS (distilled from past retros in this repo — APPLY them; they exist because a
|
||||||
previous task went wrong without them):
|
previous task went wrong without them):
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Shared **AI dev-team** workflow for Gitea Actions, reusable across repos. It giv
|
|||||||
|
|
||||||
| Agent | Model | Vision | Mode | Skills | Role |
|
| Agent | Model | Vision | Mode | Skills | Role |
|
||||||
|-------|-------|:------:|------|--------|------|
|
|-------|-------|:------:|------|--------|------|
|
||||||
| `@pm` | `ollama-cloud/gemma4:cloud` | yes | comment | `gitea-api` | Product manager & orchestrator — plans, picks the dev, hands finished PRs to `@qa`, reports back to the issue creator (autopilot: merges approved PRs itself). Issue thread only; never edits files, never reads the PR diff. |
|
| `@pm` | `ollama-cloud/minimax-m3:cloud` | yes | comment | `gitea-api` | Product manager & orchestrator — plans, picks the dev, hands finished PRs to `@qa`, reports back to the issue creator (autopilot: merges approved PRs itself). Issue thread only; never edits files, never reads the PR diff. |
|
||||||
| `@junior` | `ollama-cloud/kimi-k2.7-code:cloud` | no | pr | — | Junior dev — small, low-risk changes (mostly YAML/compose/config). Text-only, cannot read images. Defers complex or image tasks to `@senior` or `@lead`. |
|
| `@junior` | `ollama-cloud/kimi-k2.7-code:cloud` | no | pr | — | Junior dev — small, low-risk changes (mostly YAML/compose/config). Text-only, cannot read images. Defers complex or image tasks to `@senior` or `@lead`. |
|
||||||
| `@senior` | `ollama-cloud/glm-5.2:cloud` | no | pr | `gitea-api` | Senior dev — complex, multi-file implementation (GLM-5.2 via Ollama Cloud, text-only). |
|
| `@senior` | `ollama-cloud/glm-5.2:cloud` | no | pr | `gitea-api` | Senior dev — complex, multi-file implementation (GLM-5.2 via Ollama Cloud, text-only). |
|
||||||
| `@lead` | `anthropic/claude-opus-4-8` | yes | pr | `gitea-api` | Tech lead — the hardest problems, architecture, and final calls. |
|
| `@lead` | `anthropic/claude-opus-4-8` | yes | pr | `gitea-api` | Tech lead — the hardest problems, architecture, and final calls. |
|
||||||
|
|||||||
Reference in New Issue
Block a user