Skip to content

Commit 688c47f

Browse files
fix(auto-dev): the tick never triages a planner's digest issue (#80)
* fix(auto-dev): the tick never triages a planner's digest issue A planner's rolling digest is its output, not work, but the tick's step 4 walked it like any open issue and added a park question and a label to it, every run. Exclude issues whose body starts with a digest marker (config.autoDev.digestBodyMarkers) at the open-issues read, and test the exact jq filter the skill carries. Claude-Session: https://claude.ai/code/session_01T7j4GUt15DJp7G9UK4tMNE * fix(auto-dev): ignore empty digest markers An empty marker would match every body and drop the whole queue. Filter non-empty strings in the jq and document it, with a test. Claude-Session: https://claude.ai/code/session_01T7j4GUt15DJp7G9UK4tMNE
1 parent 1ec30dc commit 688c47f

10 files changed

Lines changed: 104 additions & 1 deletion

File tree

‎.github/workflows/validate.yml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ jobs:
100100
- name: The research-radar harvester fails closed, windows from its mark, and dedupes
101101
run: python3 scripts/test-research-radar.py
102102

103+
- name: The auto-dev tick's digest-skip filter drops planner digest issues
104+
run: ./scripts/test-auto-dev-digest-skip.sh
105+
103106
- name: A documented `gh pr merge --auto` always pins the head commit
104107
run: python3 scripts/check-merge-arming.py
105108

‎plugins/audits/references/config-schema.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ Read with whatever is convenient — the `Read` tool, or `jq` for a single value
151151
"skip": "auto:skip"
152152
},
153153
"excludedLabels": ["epic", "question", "wontfix", "duplicate", "invalid"], // never auto-build these
154+
"digestBodyMarkers": ["<!-- auto-dev-shadow-digest ", "<!-- auto-dev-digest "], // an open issue whose body STARTS with one of these (non-empty strings; empty ones are ignored) is a planner's rolling digest, not work: the tick never triages it. Default as shown if absent.
154155
"openPrsAsDraft": true,
155156
"prLabel": "auto:pr", // applied to every PR the pipeline opens, so external tooling (e.g. CodeRabbit) can treat automated PRs specially. Distinct from labels.automated (which the audits also use). Must already exist; bootstrap creates it.
156157
"extraPrLabels": [], // OPTIONAL extra label names (e.g. ["team:triage"]) applied to every PR the pipeline opens, alongside prLabel, everywhere prLabel is applied (create, re-stamp, REST fallbacks). Each must already exist; bootstrap creates them. For routing/ownership labels that do NOT gate review. Default [] if absent.

‎plugins/auto-dev/references/config-schema.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ Read with whatever is convenient — the `Read` tool, or `jq` for a single value
151151
"skip": "auto:skip"
152152
},
153153
"excludedLabels": ["epic", "question", "wontfix", "duplicate", "invalid"], // never auto-build these
154+
"digestBodyMarkers": ["<!-- auto-dev-shadow-digest ", "<!-- auto-dev-digest "], // an open issue whose body STARTS with one of these (non-empty strings; empty ones are ignored) is a planner's rolling digest, not work: the tick never triages it. Default as shown if absent.
154155
"openPrsAsDraft": true,
155156
"prLabel": "auto:pr", // applied to every PR the pipeline opens, so external tooling (e.g. CodeRabbit) can treat automated PRs specially. Distinct from labels.automated (which the audits also use). Must already exist; bootstrap creates it.
156157
"extraPrLabels": [], // OPTIONAL extra label names (e.g. ["team:triage"]) applied to every PR the pipeline opens, alongside prLabel, everywhere prLabel is applied (create, re-stamp, REST fallbacks). Each must already exist; bootstrap creates them. For routing/ownership labels that do NOT gate review. Default [] if absent.

‎plugins/auto-dev/skills/auto-dev/SKILL.md‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ on — is covered in [`references/scheduling.md`](references/scheduling.md).
106106

107107
**Eligibility:** all open issues, oldest first, EXCEPT issues labeled with the Skip state label (`config.autoDev.stateLabels.skip`) or any label in `config.autoDev.excludedLabels` (defaults: `epic`, `question`, `wontfix`, `duplicate`, `invalid`). Pull requests are never triaged as issues.
108108

109+
**Digest issues are never eligible.** A planner's rolling digest (an issue whose **body starts with** one of `config.autoDev.digestBodyMarkers`, default `["<!-- auto-dev-shadow-digest ", "<!-- auto-dev-digest "]`) is the planner's output, not work: it is rewritten on every run, so triaging it only generates a park question to answer and a label to remove. Exclude it at the open-issues read, before any state, age or label test, and do not count, label, comment on or park it. The match is anchored on the start of the body (after leading whitespace), so an ordinary issue that merely quotes a marker stays eligible.
110+
109111
## The tick algorithm
110112

111113
Work through these steps in order — the order **is** the priority. Execute the **first step that has work**, finish it, print the exit report, and stop. The ordering puts concrete, human-approved progress ahead of speculative grooming: advancing an open PR (step 2) and **building an approved issue (step 3) both outrank the triage pass (step 4)**, because a ready build is work the maintainer has already greenlit while triage only feeds the queue. With the default `config.autoDev.maxPrsInFlight` of `1`, an open PR blocks building until it merges, so most ticks either advance that PR or, finding it quiescent, fall through to triage. With a higher cap, an open PR **no longer blocks building**: a tick advances an open PR only when one actually needs work (CI red, unaddressed feedback, a draft to resume, or a review-window timeout that triggers a fallback self-review); when every open PR is quiescent (waiting on the maintainer's review or merge) and the in-flight count is below the cap, the tick builds the next Ready issue instead. That's what drains the queue overnight — each merge-blocked-but-quiescent PR simply frees the tick to build the following issue, up to the cap. Triage runs on the ticks that would otherwise idle (all PRs quiescent, and either nothing is Ready or the cap is reached).
@@ -185,12 +187,23 @@ entries (issues and PRs, because `open_issues_count` counts both) and is compare
185187
read on each side of the pages. `gathered == before == after` is complete; `gathered < min(before,
186188
after)` is truncated; anything else means an issue or PR opened or closed mid-read, so re-run the
187189
bracketed read once, and treat a second unsettled result as a failed read. Only after a read passes,
188-
filter `$ISSUES_FILE` to issues (`select(has("pull_request") | not)`) and project the fields the rest
190+
filter `$ISSUES_FILE` to issues (`select(has("pull_request") | not)`), drop digest issues with the
191+
filter below, and project the fields the rest
189192
of this skill uses (`number, title, labels: [.labels[].name], createdAt: .created_at, updatedAt:
190193
.updated_at`). The file is per-run (`mktemp`) so concurrent runs can't overwrite each other's pages.
191194
`rm -f "$ISSUES_FILE"` on every exit from the read: before the re-run, before stopping on a failed
192195
read, and once the projected list is in hand — not only at the end of a tick that got that far.
193196

197+
The digest filter (pass `--argjson markers` the configured `digestBodyMarkers`, or the default array above when the key is absent; empty strings are ignored, since an empty prefix would match every body):
198+
199+
```jq
200+
# auto-dev digest-skip filter
201+
[.[][] | select(has("pull_request") | not)
202+
| select(((.body // "") | sub("^\\s+"; "")) as $b
203+
| any($markers[] | select(type == "string" and length > 0); . as $m | $b | startswith($m))
204+
| not)]
205+
```
206+
194207
**When blocked**, the two PR queries become **List PRs by state** (filter `headRefName` by
195208
the branch prefix client-side exactly as the `jq` does now, and read `merged_at` for the closed
196209
pass), in the same reference. The open-issues read above already uses the REST form, so nothing

‎plugins/core/references/config-schema.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ Read with whatever is convenient — the `Read` tool, or `jq` for a single value
146146
"skip": "auto:skip"
147147
},
148148
"excludedLabels": ["epic", "question", "wontfix", "duplicate", "invalid"], // never auto-build these
149+
"digestBodyMarkers": ["<!-- auto-dev-shadow-digest ", "<!-- auto-dev-digest "], // an open issue whose body STARTS with one of these (non-empty strings; empty ones are ignored) is a planner's rolling digest, not work: the tick never triages it. Default as shown if absent.
149150
"openPrsAsDraft": true,
150151
"prLabel": "auto:pr", // applied to every PR the pipeline opens, so external tooling (e.g. CodeRabbit) can treat automated PRs specially. Distinct from labels.automated (which the audits also use). Must already exist; bootstrap creates it.
151152
"extraPrLabels": [], // OPTIONAL extra label names (e.g. ["team:triage"]) applied to every PR the pipeline opens, alongside prLabel, everywhere prLabel is applied (create, re-stamp, REST fallbacks). Each must already exist; bootstrap creates them. For routing/ownership labels that do NOT gate review. Default [] if absent.

‎plugins/deps-flow/references/config-schema.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ Read with whatever is convenient — the `Read` tool, or `jq` for a single value
151151
"skip": "auto:skip"
152152
},
153153
"excludedLabels": ["epic", "question", "wontfix", "duplicate", "invalid"], // never auto-build these
154+
"digestBodyMarkers": ["<!-- auto-dev-shadow-digest ", "<!-- auto-dev-digest "], // an open issue whose body STARTS with one of these (non-empty strings; empty ones are ignored) is a planner's rolling digest, not work: the tick never triages it. Default as shown if absent.
154155
"openPrsAsDraft": true,
155156
"prLabel": "auto:pr", // applied to every PR the pipeline opens, so external tooling (e.g. CodeRabbit) can treat automated PRs specially. Distinct from labels.automated (which the audits also use). Must already exist; bootstrap creates it.
156157
"extraPrLabels": [], // OPTIONAL extra label names (e.g. ["team:triage"]) applied to every PR the pipeline opens, alongside prLabel, everywhere prLabel is applied (create, re-stamp, REST fallbacks). Each must already exist; bootstrap creates them. For routing/ownership labels that do NOT gate review. Default [] if absent.

‎plugins/journal/references/config-schema.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ Read with whatever is convenient — the `Read` tool, or `jq` for a single value
151151
"skip": "auto:skip"
152152
},
153153
"excludedLabels": ["epic", "question", "wontfix", "duplicate", "invalid"], // never auto-build these
154+
"digestBodyMarkers": ["<!-- auto-dev-shadow-digest ", "<!-- auto-dev-digest "], // an open issue whose body STARTS with one of these (non-empty strings; empty ones are ignored) is a planner's rolling digest, not work: the tick never triages it. Default as shown if absent.
154155
"openPrsAsDraft": true,
155156
"prLabel": "auto:pr", // applied to every PR the pipeline opens, so external tooling (e.g. CodeRabbit) can treat automated PRs specially. Distinct from labels.automated (which the audits also use). Must already exist; bootstrap creates it.
156157
"extraPrLabels": [], // OPTIONAL extra label names (e.g. ["team:triage"]) applied to every PR the pipeline opens, alongside prLabel, everywhere prLabel is applied (create, re-stamp, REST fallbacks). Each must already exist; bootstrap creates them. For routing/ownership labels that do NOT gate review. Default [] if absent.

‎plugins/repo-ops/references/config-schema.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ Read with whatever is convenient — the `Read` tool, or `jq` for a single value
151151
"skip": "auto:skip"
152152
},
153153
"excludedLabels": ["epic", "question", "wontfix", "duplicate", "invalid"], // never auto-build these
154+
"digestBodyMarkers": ["<!-- auto-dev-shadow-digest ", "<!-- auto-dev-digest "], // an open issue whose body STARTS with one of these (non-empty strings; empty ones are ignored) is a planner's rolling digest, not work: the tick never triages it. Default as shown if absent.
154155
"openPrsAsDraft": true,
155156
"prLabel": "auto:pr", // applied to every PR the pipeline opens, so external tooling (e.g. CodeRabbit) can treat automated PRs specially. Distinct from labels.automated (which the audits also use). Must already exist; bootstrap creates it.
156157
"extraPrLabels": [], // OPTIONAL extra label names (e.g. ["team:triage"]) applied to every PR the pipeline opens, alongside prLabel, everywhere prLabel is applied (create, re-stamp, REST fallbacks). Each must already exist; bootstrap creates them. For routing/ownership labels that do NOT gate review. Default [] if absent.

‎plugins/research/references/config-schema.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ Read with whatever is convenient — the `Read` tool, or `jq` for a single value
151151
"skip": "auto:skip"
152152
},
153153
"excludedLabels": ["epic", "question", "wontfix", "duplicate", "invalid"], // never auto-build these
154+
"digestBodyMarkers": ["<!-- auto-dev-shadow-digest ", "<!-- auto-dev-digest "], // an open issue whose body STARTS with one of these (non-empty strings; empty ones are ignored) is a planner's rolling digest, not work: the tick never triages it. Default as shown if absent.
154155
"openPrsAsDraft": true,
155156
"prLabel": "auto:pr", // applied to every PR the pipeline opens, so external tooling (e.g. CodeRabbit) can treat automated PRs specially. Distinct from labels.automated (which the audits also use). Must already exist; bootstrap creates it.
156157
"extraPrLabels": [], // OPTIONAL extra label names (e.g. ["team:triage"]) applied to every PR the pipeline opens, alongside prLabel, everywhere prLabel is applied (create, re-stamp, REST fallbacks). Each must already exist; bootstrap creates them. For routing/ownership labels that do NOT gate review. Default [] if absent.
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Tests for the auto-dev tick's digest-skip filter. A planner's rolling digest issue is its own
4+
# output, not work; the tick must drop it at the open-issues read. The filter lives as a fenced
5+
# `jq` block in plugins/auto-dev/skills/auto-dev/SKILL.md (the text the tick follows), and this
6+
# script extracts that exact block and runs it, so removing or weakening the filter fails here.
7+
#
8+
# ./scripts/test-auto-dev-digest-skip.sh
9+
#
10+
# Requires: bash 3.2+, jq, awk.
11+
12+
set -uo pipefail
13+
export LC_ALL=C
14+
15+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
16+
SKILL="$ROOT/plugins/auto-dev/skills/auto-dev/SKILL.md"
17+
MARKERS='["<!-- auto-dev-shadow-digest ", "<!-- auto-dev-digest "]'
18+
19+
pass=0
20+
fail=0
21+
ok() { pass=$((pass + 1)); printf ' ok %s\n' "$1"; }
22+
bad() { fail=$((fail + 1)); printf ' FAIL %s\n' "$1"; [ $# -lt 2 ] || printf ' %s\n' "$2"; }
23+
24+
FILTER="$(mktemp "${TMPDIR:-/tmp}/auto-dev-digest-filter.XXXXXX")"
25+
trap 'rm -f "$FILTER"' EXIT
26+
27+
awk '/^```jq$/ {grab=1; next} grab && /^```$/ {exit} grab {print}' "$SKILL" > "$FILTER"
28+
29+
if ! grep -q 'auto-dev digest-skip filter' "$FILTER"; then
30+
bad "SKILL.md carries the digest-skip jq filter" "no fenced jq block marked 'auto-dev digest-skip filter'"
31+
printf '\n%d passed, %d failed\n' "$pass" "$fail"
32+
exit 1
33+
fi
34+
ok "SKILL.md carries the digest-skip jq filter"
35+
36+
# One paginated --slurp file: a list of pages, each a list of entries (issues and PRs).
37+
PAGES='[[
38+
{"number": 1, "body": "ordinary issue"},
39+
{"number": 2, "body": null},
40+
{"number": 3, "body": "<!-- auto-dev-shadow-digest repo=Vycari/foreman rev=7 -->\n## digest"},
41+
{"number": 4, "body": "\n <!-- auto-dev-digest rev=2 -->\n## digest"}
42+
],[
43+
{"number": 5, "body": "quoting one: <!-- auto-dev-shadow-digest repo=a rev=1 --> mid-body"},
44+
{"number": 6, "body": "<!-- auto-dev-shadow-digest repo=x rev=1 -->", "pull_request": {}},
45+
{"number": 7, "body": "<!-- auto-dev --> a marker comment is not a digest"}
46+
]]'
47+
48+
run_filter() { printf '%s' "$PAGES" | jq -c --argjson markers "$1" -f "$FILTER" 2>&1; }
49+
numbers() { jq -c '[.[].number]'; }
50+
51+
got="$(run_filter "$MARKERS" | numbers)"
52+
if [ "$got" = "[1,2,5,7]" ]; then
53+
ok "digest issues (either marker, leading whitespace) are dropped; PRs too; quoted markers stay"
54+
else
55+
bad "digest issues are dropped" "got $got, want [1,2,5,7]"
56+
fi
57+
58+
got="$(run_filter '["<!-- custom-digest "]' | numbers)"
59+
if [ "$got" = "[1,2,3,4,5,7]" ]; then
60+
ok "a configured marker list replaces the default"
61+
else
62+
bad "a configured marker list replaces the default" "got $got"
63+
fi
64+
65+
got="$(run_filter '[]' | numbers)"
66+
if [ "$got" = "[1,2,3,4,5,7]" ]; then
67+
ok "an empty marker list drops nothing"
68+
else
69+
bad "an empty marker list drops nothing" "got $got"
70+
fi
71+
72+
got="$(run_filter '["", "<!-- auto-dev-digest "]' | numbers)"
73+
if [ "$got" = "[1,2,3,5,7]" ]; then
74+
ok "an empty marker string is ignored, not treated as matching every body"
75+
else
76+
bad "an empty marker string is ignored" "got $got, want [1,2,3,5,7]"
77+
fi
78+
79+
printf '\n%d passed, %d failed\n' "$pass" "$fail"
80+
[ "$fail" -eq 0 ]

0 commit comments

Comments
 (0)