@plainconceptsplatform/workflows 1.11.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.js +18 -5
- package/dist/template-bundles.js +18 -4
- package/dist/workflow-catalog.d.ts +3 -1
- package/dist/workflow-catalog.js +4 -2
- package/loops/actions/file-audit-issues/action.yml +193 -0
- package/loops/actions/file-audit-issues/audit-issues.cjs +245 -0
- package/loops/actions/issue-size.cjs +36 -0
- package/loops/actions/measure-issue-size/action.yml +47 -0
- package/loops/actions/merge-dependabot/action.yml +54 -0
- package/loops/actions/merge-dependabot/merge-dependabot.mjs +343 -0
- package/loops/actions/verify-route-matrix/verify-route-matrix.sh +96 -0
- package/loops/docs/diagrams.md +20 -13
- package/loops/templates/ci/app-ci-dotnet-next.yml +15 -9
- package/loops/templates/ci/app-ci-node-monorepo.yml +5 -1
- package/loops/templates/dependabot-merge/.github/workflows/dependabot-merge.yml +78 -0
- package/loops/templates/platform-template/.github/workflows/platform-template-sync.yml +74 -43
- package/loops/templates/platform-template/scripts/check-platform-conformance.mjs +317 -42
- package/loops/workflows/agent-audit.md +46 -29
- package/loops/workflows/agent-docs-audit.md +44 -18
- package/loops/workflows/agent-refine.md +17 -30
- package/loops/workflows/work-router.yml +19 -0
- package/package.json +1 -1
|
@@ -19,15 +19,23 @@ env:
|
|
|
19
19
|
# No commit in this many days means nothing can have drifted since the last run, so a scheduled
|
|
20
20
|
# run stops before the model starts. 17 is the longest gap between runs on days 1 and 15.
|
|
21
21
|
DOCS_AUDIT_QUIET_DAYS: "17"
|
|
22
|
+
# The largest issue the docs audit files, measured with refine's own rule (issue-size.cjs), as
|
|
23
|
+
# agent-audit.md does: three quarters of refine's limits. A body over these is trimmed first.
|
|
24
|
+
DOCS_AUDIT_MAX_BODY_CHARS: "6000"
|
|
25
|
+
DOCS_AUDIT_MAX_WORK_UNITS: "18"
|
|
26
|
+
DOCS_AUDIT_MAX_STORIES: "3"
|
|
22
27
|
GIT_AUTHOR_NAME: "github-actions[bot]"
|
|
23
28
|
GIT_AUTHOR_EMAIL: "github-actions[bot]@users.noreply.github.com"
|
|
24
29
|
GIT_COMMITTER_NAME: "github-actions[bot]"
|
|
25
30
|
GIT_COMMITTER_EMAIL: "github-actions[bot]@users.noreply.github.com"
|
|
26
31
|
description: |
|
|
27
32
|
Read-only documentation audit. Checks that the repository's documentation still matches its
|
|
28
|
-
code and the Platform documentation standard. When it does not, files a
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
code and the Platform documentation standard. When it does not, files a compact tracking report
|
|
34
|
+
of every finding, labelled `docs-audit` and never `refine`, plus one story issue for each of the
|
|
35
|
+
top 3 findings, labelled `documentation` + `refine` and linked to the report as sub-issues.
|
|
36
|
+
Every body is measured with refine's size rule before it is filed and trimmed to fit, because a
|
|
37
|
+
single report of every finding grows past refine's limit and is refused. When the documentation
|
|
38
|
+
is current, files nothing.
|
|
31
39
|
|
|
32
40
|
Router-only worker: triggered exclusively via workflow_call from work-router.yml.
|
|
33
41
|
Contract input: trigger-kind(scheduled|manual).
|
|
@@ -173,16 +181,27 @@ jobs:
|
|
|
173
181
|
with:
|
|
174
182
|
client-id: ${{ secrets.BOT_APP_ID }}
|
|
175
183
|
private-key: ${{ secrets.BOT_PRIVATE_KEY }}
|
|
176
|
-
#
|
|
177
|
-
#
|
|
178
|
-
- name:
|
|
179
|
-
|
|
180
|
-
uses: ./.github/actions/
|
|
184
|
+
# Staged, so this is the only write, as in agent-audit.md: the report, each story with its
|
|
185
|
+
# labels and a link back, then the report's list. The App token lets `refine` reach the router.
|
|
186
|
+
- name: Download agent output
|
|
187
|
+
id: output
|
|
188
|
+
uses: ./.github/actions/download-agent-output
|
|
181
189
|
with:
|
|
190
|
+
artifact-name: ${{ needs.activation.outputs.artifact_prefix }}agent
|
|
191
|
+
- name: File the report and its stories
|
|
192
|
+
uses: ./.github/actions/file-audit-issues
|
|
193
|
+
with:
|
|
194
|
+
output-file: ${{ steps.output.outputs.output-file }}
|
|
182
195
|
token: ${{ steps.app-token.outputs.token }}
|
|
183
|
-
|
|
184
|
-
|
|
196
|
+
max-body-chars: ${{ env.DOCS_AUDIT_MAX_BODY_CHARS }}
|
|
197
|
+
max-work-units: ${{ env.DOCS_AUDIT_MAX_WORK_UNITS }}
|
|
198
|
+
max-stories: ${{ env.DOCS_AUDIT_MAX_STORIES }}
|
|
199
|
+
run-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
200
|
+
report-title: '^docs audit:'
|
|
201
|
+
report-labels: |-
|
|
185
202
|
docs-audit
|
|
203
|
+
story-labels: |-
|
|
204
|
+
documentation
|
|
186
205
|
refine
|
|
187
206
|
|
|
188
207
|
# An agent that ended without a report or a noop would otherwise be a green run that audited
|
|
@@ -208,8 +227,11 @@ safe-outputs:
|
|
|
208
227
|
# A failed run is already a red run. An issue per failure buries the real backlog.
|
|
209
228
|
report-failure-as-issue: false
|
|
210
229
|
threat-detection: false
|
|
230
|
+
# The report and the stories are filed by `conclude`, after they are measured.
|
|
231
|
+
staged: true
|
|
232
|
+
# The report and up to three stories.
|
|
211
233
|
create-issue:
|
|
212
|
-
max:
|
|
234
|
+
max: 4
|
|
213
235
|
|
|
214
236
|
# Reads documents and the code they describe, and writes one issue. Unobserved so far: audit, the
|
|
215
237
|
# nearest worker, takes about 45 minutes over a whole repository, and this one reads less.
|
|
@@ -253,14 +275,18 @@ timeout-minutes: 60
|
|
|
253
275
|
7. If no finding is left, call `noop` and stop. Filing nothing is the right outcome when the
|
|
254
276
|
documentation is current.
|
|
255
277
|
|
|
256
|
-
8.
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
278
|
+
8. File the report: call `create_issue` with title "Docs audit: <YYYY-MM-DD>" and nothing else in the title. Do NOT specify labels; the conclude job applies them. The report is a compact tracking list, not a story: Refine never reads it. One entry per finding, the most misleading first, each exactly this shape and no longer than about 600 characters:
|
|
279
|
+
|
|
280
|
+
```
|
|
281
|
+
### <n>. <one-line stale statement>
|
|
282
|
+
**Document:** `path/to/doc.md:line`, what it says
|
|
283
|
+
**Evidence:** `path/to/code.ext:line` or the path that shows otherwise
|
|
284
|
+
**Fix:** What the document should say, or the section that has to be added.
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
No bullet lists and no user stories. Do not write any issue number (`#123`) in the report: the workflow adds the links to the stories, and every number in it is read as work the report waits on.
|
|
261
288
|
|
|
262
|
-
|
|
263
|
-
each finding to survive that: self-contained, naming its own files, never "as above".
|
|
289
|
+
9. File the stories: write the top ${{ env.DOCS_AUDIT_MAX_STORIES }} findings as short stories. Call `create_issue` once per story, after the report, titled with what the story fixes (never "Docs audit: ..."), no labels. Refine refines each story on its own, so each must stand alone: it names its document and the code that contradicts it, says what the document should say, and never says "as above" or mentions the other stories or the report. Keep each story well under ${{ env.DOCS_AUDIT_MAX_BODY_CHARS }} characters and ${{ env.DOCS_AUDIT_MAX_WORK_UNITS }} bullet lines: the workflow measures every issue against that limit before filing and cuts anything over it from the end.
|
|
264
290
|
|
|
265
291
|
Refer to people by role, never by name, and include no personal contact details, even when
|
|
266
292
|
a document you are quoting contains them.
|
|
@@ -47,7 +47,8 @@ env:
|
|
|
47
47
|
# one, Pliny-Bot #305 (13,398 chars, 92 work units), burned a full agent -- 17 minutes,
|
|
48
48
|
# 2.4M tokens -- and died at the provider's 32K output cap with zero outcome, twice.
|
|
49
49
|
# Both numbers are measured before the agent starts, so refusal is deterministic and
|
|
50
|
-
# costs ten seconds instead of half an hour.
|
|
50
|
+
# costs ten seconds instead of half an hour. The audit files under its own AUDIT_MAX_*,
|
|
51
|
+
# which must stay below these or refine refuses what it files.
|
|
51
52
|
REFINE_MAX_BODY_CHARS: "8000"
|
|
52
53
|
REFINE_MAX_WORK_UNITS: "24"
|
|
53
54
|
REFUSE_MARKER: "<!-- agent-refine-refused -->"
|
|
@@ -207,9 +208,8 @@ jobs:
|
|
|
207
208
|
# The size gate, rung 4. Measures the issue before any model starts, because an oversized
|
|
208
209
|
# body does not fail fast on its own: it produces an agent that explores for seventeen
|
|
209
210
|
# minutes and then dies mid-generation at the provider's response cap having written
|
|
210
|
-
# nothing, which reads as a green run with no outcome (Pliny-Bot #305, twice).
|
|
211
|
-
#
|
|
212
|
-
# smaller attempt.
|
|
211
|
+
# nothing, which reads as a green run with no outcome (Pliny-Bot #305, twice). No
|
|
212
|
+
# model, one issue read; over either limit it is a refusal, not a smaller attempt.
|
|
213
213
|
size_guard:
|
|
214
214
|
needs: [still_open, eligibility]
|
|
215
215
|
if: needs.still_open.outputs.open == 'true' && needs.eligibility.outputs.eligible == 'true'
|
|
@@ -219,36 +219,23 @@ jobs:
|
|
|
219
219
|
contents: read
|
|
220
220
|
issues: read
|
|
221
221
|
outputs:
|
|
222
|
-
too_big: ${{ steps.measure.outputs.
|
|
222
|
+
too_big: ${{ steps.measure.outputs.too-big }}
|
|
223
223
|
reason: ${{ steps.measure.outputs.reason }}
|
|
224
224
|
steps:
|
|
225
|
+
- name: Checkout workflow actions
|
|
226
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
227
|
+
with:
|
|
228
|
+
persist-credentials: false
|
|
229
|
+
# The rule lives in issue-size.cjs, which the audit also runs before it files, so the two
|
|
230
|
+
# cannot drift: every audit report from 2026-09-21 on was refused here for want of it.
|
|
225
231
|
- name: Measure the issue against the size limits
|
|
226
232
|
id: measure
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
run: |
|
|
234
|
-
set -euo pipefail
|
|
235
|
-
body=$(gh issue view "$ISSUE_NUMBER" --repo "$REPO" --json title,body --jq '.title + "\n\n" + (.body // "")')
|
|
236
|
-
chars=$(printf '%s' "$body" | wc -c)
|
|
237
|
-
# One work unit per markdown bullet, the same split the prompt's step 3 makes.
|
|
238
|
-
units=$(printf '%s' "$body" | grep -cE '^[[:space:]]*[-*] ' || true)
|
|
239
|
-
reason=""
|
|
240
|
-
too_big=false
|
|
241
|
-
if [ "$chars" -gt "$MAX_BODY_CHARS" ]; then
|
|
242
|
-
too_big=true
|
|
243
|
-
reason="body ${chars} chars > ${MAX_BODY_CHARS}"
|
|
244
|
-
fi
|
|
245
|
-
if [ "$units" -gt "$MAX_WORK_UNITS" ]; then
|
|
246
|
-
too_big=true
|
|
247
|
-
reason="${reason:+$reason; }work units ${units} > ${MAX_WORK_UNITS}"
|
|
248
|
-
fi
|
|
249
|
-
echo "too_big=$too_big" >> "$GITHUB_OUTPUT"
|
|
250
|
-
echo "reason=$reason" >> "$GITHUB_OUTPUT"
|
|
251
|
-
echo "::notice::measured $chars chars, $units work units (limits: ${MAX_BODY_CHARS} chars, ${MAX_WORK_UNITS} units) -> $too_big"
|
|
233
|
+
uses: ./.github/actions/measure-issue-size
|
|
234
|
+
with:
|
|
235
|
+
token: ${{ github.token }}
|
|
236
|
+
issue-number: ${{ inputs.issue-number }}
|
|
237
|
+
max-body-chars: ${{ env.REFINE_MAX_BODY_CHARS }}
|
|
238
|
+
max-work-units: ${{ env.REFINE_MAX_WORK_UNITS }}
|
|
252
239
|
# The deterministic refusal. Runs only when the size gate tripped, so it costs one
|
|
253
240
|
# comment and no model. The refine label stays: a human who shrinks the body and
|
|
254
241
|
# replies re-enters rerefine through the comment route, and the classifier ignores
|
|
@@ -151,6 +151,14 @@ env:
|
|
|
151
151
|
# The single issue that lists what genuinely needs a person. Set it empty to turn the digest
|
|
152
152
|
# off and keep the rest of the janitor.
|
|
153
153
|
HOUSEKEEPING_DIGEST_TITLE: "Needs a human"
|
|
154
|
+
# Which Dependabot pull requests the six-hourly maintenance squash-merges once every check is
|
|
155
|
+
# green: "all", "minor" (minor and patch), "patch", or "off". CI is the gate; only pull requests
|
|
156
|
+
# Dependabot opened, with every commit Dependabot's, are touched. A repository variable of the
|
|
157
|
+
# same name overrides this without a commit.
|
|
158
|
+
DEPENDABOT_MERGE: "all"
|
|
159
|
+
# "true" also merges a Dependabot pull request in a repository with no checks at all, once its
|
|
160
|
+
# head is an hour old. "false" leaves those for a person.
|
|
161
|
+
DEPENDABOT_MERGE_WITHOUT_CHECKS: "true"
|
|
154
162
|
# How long an audit report may sit open before it is labelled `stale-audit`. The audit's own
|
|
155
163
|
# backpressure ignores a stale report, so three abandoned reports can no longer disable the
|
|
156
164
|
# weekly audit for good; the digest lists them instead. Two missed weekly cycles.
|
|
@@ -1852,6 +1860,17 @@ jobs:
|
|
|
1852
1860
|
stale-pr-days: ${{ env.HOUSEKEEPING_STALE_PR_DAYS }}
|
|
1853
1861
|
digest-title: ${{ env.HOUSEKEEPING_DIGEST_TITLE }}
|
|
1854
1862
|
dry-run: ${{ github.event.inputs.dry-run || 'false' }}
|
|
1863
|
+
# Dependabot updates merge here rather than on a cron of their own. API calls only: no pull
|
|
1864
|
+
# request code is checked out. It runs even when the sweep above failed.
|
|
1865
|
+
- name: Merge Dependabot pull requests
|
|
1866
|
+
if: ${{ !cancelled() && steps.app-token.outcome == 'success' }}
|
|
1867
|
+
uses: ./.github/actions/merge-dependabot
|
|
1868
|
+
with:
|
|
1869
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
1870
|
+
scope: ${{ vars.DEPENDABOT_MERGE || env.DEPENDABOT_MERGE }}
|
|
1871
|
+
merge-without-checks: ${{ env.DEPENDABOT_MERGE_WITHOUT_CHECKS }}
|
|
1872
|
+
skip-labels: do-not-merge blocked human-review ${{ env.MERGE_TRAIN_HOLD_LABEL }}
|
|
1873
|
+
dry-run: ${{ github.event.inputs.dry-run || 'false' }}
|
|
1855
1874
|
|
|
1856
1875
|
validate:
|
|
1857
1876
|
needs: classify
|