@plainconceptsplatform/workflows 1.12.0 → 1.13.1
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 +18 -5
- package/dist/template-bundles.js +18 -4
- package/dist/workflow-catalog.d.ts +3 -1
- package/dist/workflow-catalog.js +3 -1
- 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 +62 -0
- package/loops/actions/merge-dependabot/merge-dependabot.mjs +373 -0
- package/loops/actions/verify-route-matrix/verify-route-matrix.sh +101 -0
- package/loops/docs/diagrams.md +20 -13
- package/loops/templates/ci/app-ci-dotnet-next.yml +4 -0
- package/loops/templates/ci/app-ci-node-monorepo.yml +5 -1
- package/loops/templates/dependabot-merge/.github/workflows/dependabot-merge.yml +80 -0
- 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 +23 -0
- package/package.json +2 -2
package/loops/docs/diagrams.md
CHANGED
|
@@ -100,7 +100,7 @@ flowchart LR
|
|
|
100
100
|
gate -.->|"CI failed or conflict"| fix("Fix, push, CI again") --> ci
|
|
101
101
|
gate -.->|".github, secrets<br/>or 10000+ lines"| human(("Human review"))
|
|
102
102
|
review("Someone reviews the PR") --> applyReview("apply-review") --> ci
|
|
103
|
-
audit("audit, weekly") -->|"files
|
|
103
|
+
audit("audit, weekly") -->|"files stories"| refine
|
|
104
104
|
|
|
105
105
|
classDef action fill:#eef0ff,stroke:#554cff,stroke-width:2px,color:#172033
|
|
106
106
|
classDef decision fill:#fff8e8,stroke:#c75b00,stroke-width:2px,color:#172033
|
|
@@ -114,9 +114,7 @@ flowchart LR
|
|
|
114
114
|
class merged success
|
|
115
115
|
```
|
|
116
116
|
|
|
117
|
-
Audit creates work rather than consuming it, and files into `refine` rather than straight to
|
|
118
|
-
`implement`, so a report of several unrelated findings becomes one properly sized issue per finding
|
|
119
|
-
instead of one pull request that has to fix them all.
|
|
117
|
+
Audit creates work rather than consuming it, and files into `refine` rather than straight to `implement`: one story issue per top finding, each under refine's size limit, plus a tracking report refine never sees, instead of one pull request that has to fix them all.
|
|
120
118
|
|
|
121
119
|
### The merge train
|
|
122
120
|
|
|
@@ -489,7 +487,7 @@ before anything changes. Applying them one at a time produces contradictory comm
|
|
|
489
487
|
|
|
490
488
|
## agent-audit.md
|
|
491
489
|
|
|
492
|
-
Creates work rather than consuming it. Read-only: it files
|
|
490
|
+
Creates work rather than consuming it. Read-only: it files a tracking report and one story issue per top finding, and changes nothing else.
|
|
493
491
|
|
|
494
492
|
```mermaid
|
|
495
493
|
flowchart TD
|
|
@@ -506,8 +504,9 @@ flowchart TD
|
|
|
506
504
|
auDedupe{"Already tracked, or<br/>previously rejected?"}
|
|
507
505
|
auDedupe -.->|"all of them"| auQuiet
|
|
508
506
|
auDedupe -->|"some are new"| auFile
|
|
509
|
-
auFile("
|
|
510
|
-
|
|
507
|
+
auFile("Report of every finding,<br/>plus one story per top 3") --> auMeasure
|
|
508
|
+
auMeasure("conclude (rung 6)<br/>each body measured with<br/>refine's rule, trimmed to fit") --> auReport
|
|
509
|
+
auReport(("Filed<br/>report: audit, not refine<br/>stories: refine, sub-issues"))
|
|
511
510
|
auQuiet(("Nothing to file<br/>the right outcome on a<br/>clean codebase"))
|
|
512
511
|
auIdle(("Idle<br/>backlog already full"))
|
|
513
512
|
|
|
@@ -517,12 +516,14 @@ flowchart TD
|
|
|
517
516
|
classDef idle fill:#202c40,stroke:#738198,stroke-width:2px,color:#ffffff
|
|
518
517
|
classDef success fill:#e8f8ec,stroke:#18883c,stroke-width:2px,color:#145a32
|
|
519
518
|
class auStart start
|
|
520
|
-
class auFacts,auRun,auScore,auFile action
|
|
519
|
+
class auFacts,auRun,auScore,auFile,auMeasure action
|
|
521
520
|
class auBack,auFilter,auDedupe decision
|
|
522
521
|
class auIdle idle
|
|
523
522
|
class auReport,auQuiet success
|
|
524
523
|
```
|
|
525
524
|
|
|
525
|
+
The report never goes to refine: refine's size gate refused every audit filed as one issue of every finding and three stories, from 2026-09-21 until 1.12.1. Each story is filed on its own, labelled `refine`, linked under the report and listed in it, so `audit-close` closes the report once its stories are closed. A story already tracked by an open issue is listed against that issue instead of filed again.
|
|
526
|
+
|
|
526
527
|
Memory matters here: open issues only cover what is still open, so a finding reported weeks ago and
|
|
527
528
|
consciously not acted on would come back every single run. The audit records its dispositions and
|
|
528
529
|
reads them next time.
|
|
@@ -549,8 +550,9 @@ flowchart TD
|
|
|
549
550
|
daDedupe{"New finding, proven<br/>on both sides?"}
|
|
550
551
|
daDedupe -.->|"none left"| daQuiet
|
|
551
552
|
daDedupe -->|"some"| daFile
|
|
552
|
-
daFile("
|
|
553
|
-
|
|
553
|
+
daFile("Report of every finding,<br/>plus one story per top 3") --> daMeasure
|
|
554
|
+
daMeasure("conclude (rung 6)<br/>each body measured with<br/>refine's rule, trimmed to fit") --> daReport
|
|
555
|
+
daReport(("Filed<br/>report: docs-audit, not refine<br/>stories: refine, sub-issues"))
|
|
554
556
|
daQuiet(("Nothing to file<br/>the documentation is current"))
|
|
555
557
|
daIdle(("Idle<br/>no agent, no spend"))
|
|
556
558
|
|
|
@@ -560,7 +562,7 @@ flowchart TD
|
|
|
560
562
|
classDef idle fill:#202c40,stroke:#738198,stroke-width:2px,color:#ffffff
|
|
561
563
|
classDef success fill:#e8f8ec,stroke:#18883c,stroke-width:2px,color:#145a32
|
|
562
564
|
class daStart start
|
|
563
|
-
class daFacts,daRun,daFile action
|
|
565
|
+
class daFacts,daRun,daFile,daMeasure action
|
|
564
566
|
class daBack,daMoved,daDedupe decision
|
|
565
567
|
class daIdle idle
|
|
566
568
|
class daReport,daQuiet success
|
|
@@ -569,6 +571,8 @@ flowchart TD
|
|
|
569
571
|
Reports join the audit chain: `audit-close` closes one when its children are closed, and marks it
|
|
570
572
|
`stale-audit` when nobody acted on it, which frees the slot.
|
|
571
573
|
|
|
574
|
+
It files the way the audit does, through the same `file-audit-issues` step: the report never goes to refine, which refused a single report of every finding once it grew past its size gate. Each top story is filed on its own, labelled `refine`, linked under the report and listed in it.
|
|
575
|
+
|
|
572
576
|
---
|
|
573
577
|
|
|
574
578
|
## agent-release.md
|
|
@@ -652,7 +656,8 @@ flowchart TB
|
|
|
652
656
|
hkBranch -->|yes| hkDelete("Delete the branch")
|
|
653
657
|
hkBranch -.->|"open PR, human PR,<br/>or the default branch"| hkDigest
|
|
654
658
|
hkDelete --> hkDigest
|
|
655
|
-
hkDigest("Rewrite one issue:<br/>Needs a human") -->
|
|
659
|
+
hkDigest("Rewrite one issue:<br/>Needs a human") --> hkDependabot
|
|
660
|
+
hkDependabot("Squash-merge Dependabot PRs<br/>whose checks are all green") --> hkEnd
|
|
656
661
|
hkEnd(("One place to look<br/>instead of four repos"))
|
|
657
662
|
|
|
658
663
|
classDef start fill:#ffffff,stroke:#172033,stroke-width:2px,color:#172033
|
|
@@ -660,7 +665,7 @@ flowchart TB
|
|
|
660
665
|
classDef decision fill:#fff8e8,stroke:#c75b00,stroke-width:2px,color:#172033
|
|
661
666
|
classDef success fill:#e8f8ec,stroke:#18883c,stroke-width:2px,color:#145a32
|
|
662
667
|
class hkCron start
|
|
663
|
-
class hkScan,hkRetry,hkStrand,hkParent,hkDelete,hkDigest,hkStackClose action
|
|
668
|
+
class hkScan,hkRetry,hkStrand,hkParent,hkDelete,hkDigest,hkStackClose,hkDependabot action
|
|
664
669
|
class hkPark,hkWhy,hkBudget,hkBranch,hkStack,hkStackWait decision
|
|
665
670
|
class hkEnd success
|
|
666
671
|
```
|
|
@@ -681,6 +686,8 @@ held for days after its announcement, a root closed by accident with children op
|
|
|
681
686
|
root pull request each get one line in the digest. No labels move, so nothing the router listens
|
|
682
687
|
for is raised by the sweep itself.
|
|
683
688
|
|
|
689
|
+
The last step merges Dependabot's updates through the `merge-dependabot` action: only pull requests Dependabot opened with every commit verified and its own, squash-merged once every check is green, a stale one asked once to rebase, a failing one listed in the job summary. It reads nothing of the pull request but the API, and `DEPENDABOT_MERGE` sets its scope. See `docs/dependabot-merge.md` in the package.
|
|
690
|
+
|
|
684
691
|
---
|
|
685
692
|
|
|
686
693
|
## agentics-app-errors
|
|
@@ -963,6 +963,9 @@ jobs:
|
|
|
963
963
|
# If a deploy pipeline calls this workflow with `uses:`, that caller must also grant
|
|
964
964
|
# `actions: write`. A called workflow may not request a scope its caller withheld, and the
|
|
965
965
|
# whole run fails to start with zero jobs and no annotation when it does.
|
|
966
|
+
#
|
|
967
|
+
# Not for Dependabot: its runs get a read-only token, so the dispatch would fail its pull request,
|
|
968
|
+
# and the router's maintenance run merges Dependabot updates instead of the train.
|
|
966
969
|
dispatch-merge-gate:
|
|
967
970
|
name: Dispatch merge gate
|
|
968
971
|
needs: [api, mutation-api, web, mutation-web, secret-scan, deps-and-iac-scan, sast-scan, sbom, engineering-excellence, platform-conformance, full-ci]
|
|
@@ -972,6 +975,7 @@ jobs:
|
|
|
972
975
|
github.event_name == 'pull_request' &&
|
|
973
976
|
github.event.pull_request.head.repo.full_name == github.repository &&
|
|
974
977
|
github.event.pull_request.draft == false &&
|
|
978
|
+
github.event.pull_request.user.login != 'dependabot[bot]' &&
|
|
975
979
|
(github.event.pull_request.user.login == 'app/github-actions' || endsWith(github.event.pull_request.user.login, '[bot]'))
|
|
976
980
|
runs-on: ${{ vars.CI_RUNNER || 'agents-arc' }}
|
|
977
981
|
timeout-minutes: 5
|
|
@@ -239,6 +239,9 @@ jobs:
|
|
|
239
239
|
# If a deploy pipeline calls this workflow with `uses:`, that caller must also grant
|
|
240
240
|
# `actions: write`. A called workflow may not request a scope its caller withheld, and the
|
|
241
241
|
# whole run fails to start with zero jobs and no annotation when it does.
|
|
242
|
+
#
|
|
243
|
+
# Not for Dependabot: its runs get a read-only token, so the dispatch would fail its pull request,
|
|
244
|
+
# and the router's maintenance run merges Dependabot updates instead of the train.
|
|
242
245
|
dispatch-merge-train:
|
|
243
246
|
name: Dispatch merge train
|
|
244
247
|
needs: [lint, web, mutation-web, e2e, desktop, mobile]
|
|
@@ -246,7 +249,8 @@ jobs:
|
|
|
246
249
|
always() &&
|
|
247
250
|
github.event_name == 'pull_request' &&
|
|
248
251
|
github.event.pull_request.head.repo.full_name == github.repository &&
|
|
249
|
-
github.event.pull_request.draft == false
|
|
252
|
+
github.event.pull_request.draft == false &&
|
|
253
|
+
github.event.pull_request.user.login != 'dependabot[bot]'
|
|
250
254
|
runs-on: agents-arc
|
|
251
255
|
timeout-minutes: 5
|
|
252
256
|
permissions:
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/templates/dependabot-merge/.github/workflows/dependabot-merge.yml. Update with `workflows update --force`; consumer edits may be overwritten.
|
|
2
|
+
name: "Dependabot: Merge"
|
|
3
|
+
|
|
4
|
+
# Every six hours, squash-merges the open Dependabot pull requests whose checks are all green, for a
|
|
5
|
+
# repository that does not run the work router (the router does the same in its maintenance run).
|
|
6
|
+
# Plain YAML, no model, and the pull request's code is never checked out: the checkout below is the
|
|
7
|
+
# default branch, for the action. Only pull requests Dependabot opened, with every commit
|
|
8
|
+
# Dependabot's, are touched. See docs/dependabot-merge.md in agentic-workflows.
|
|
9
|
+
#
|
|
10
|
+
# With the Platform App secrets (BOT_APP_ID / BOT_PRIVATE_KEY) the merge runs as the App, and the
|
|
11
|
+
# merge commit starts the default branch's push workflows. Without them it runs as GITHUB_TOKEN,
|
|
12
|
+
# and GitHub starts no workflow from a merge made with that token: nothing deploys on push until
|
|
13
|
+
# the next commit that is not a Dependabot merge.
|
|
14
|
+
#
|
|
15
|
+
# To turn it off: set DEPENDABOT_MERGE to "off" below, or the repository variable DEPENDABOT_MERGE
|
|
16
|
+
# to "off", or AGENTIC_WORKFLOWS_DISABLED to anything, or delete this file.
|
|
17
|
+
on:
|
|
18
|
+
schedule:
|
|
19
|
+
- cron: "41 */6 * * *"
|
|
20
|
+
workflow_dispatch:
|
|
21
|
+
inputs:
|
|
22
|
+
dry-run:
|
|
23
|
+
description: "Decide and report, merge nothing"
|
|
24
|
+
required: false
|
|
25
|
+
type: boolean
|
|
26
|
+
default: false
|
|
27
|
+
|
|
28
|
+
permissions:
|
|
29
|
+
contents: read
|
|
30
|
+
|
|
31
|
+
concurrency:
|
|
32
|
+
group: dependabot-merge
|
|
33
|
+
cancel-in-progress: false
|
|
34
|
+
|
|
35
|
+
env:
|
|
36
|
+
# "all", "minor" (minor and patch), "patch", or "off". CI is the gate.
|
|
37
|
+
DEPENDABOT_MERGE: "all"
|
|
38
|
+
# "true" also merges in a repository with no checks at all, once the head is an hour old.
|
|
39
|
+
DEPENDABOT_MERGE_WITHOUT_CHECKS: "true"
|
|
40
|
+
|
|
41
|
+
jobs:
|
|
42
|
+
merge:
|
|
43
|
+
name: Merge Dependabot pull requests
|
|
44
|
+
if: "!github.event.repository.fork && vars.AGENTIC_WORKFLOWS_DISABLED == ''"
|
|
45
|
+
runs-on: ${{ vars.CI_RUNNER || 'ubuntu-latest' }}
|
|
46
|
+
timeout-minutes: 15
|
|
47
|
+
permissions:
|
|
48
|
+
# Merging, deleting the merged branch, approving where a ruleset asks for it, and commenting
|
|
49
|
+
# `@dependabot rebase`. All of it through the API. checks and statuses: the reads run on
|
|
50
|
+
# this token, since the App installation may hold no Checks permission.
|
|
51
|
+
contents: write
|
|
52
|
+
pull-requests: write
|
|
53
|
+
issues: write
|
|
54
|
+
checks: read
|
|
55
|
+
statuses: read
|
|
56
|
+
env:
|
|
57
|
+
HAS_APP: ${{ secrets.BOT_APP_ID != '' && secrets.BOT_PRIVATE_KEY != '' }}
|
|
58
|
+
steps:
|
|
59
|
+
- name: Checkout the action
|
|
60
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
61
|
+
with:
|
|
62
|
+
persist-credentials: false
|
|
63
|
+
sparse-checkout: .github/actions/merge-dependabot
|
|
64
|
+
|
|
65
|
+
- name: Generate App token
|
|
66
|
+
id: app-token
|
|
67
|
+
if: env.HAS_APP == 'true'
|
|
68
|
+
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
|
69
|
+
with:
|
|
70
|
+
app-id: ${{ secrets.BOT_APP_ID }}
|
|
71
|
+
private-key: ${{ secrets.BOT_PRIVATE_KEY }}
|
|
72
|
+
|
|
73
|
+
- uses: ./.github/actions/merge-dependabot
|
|
74
|
+
with:
|
|
75
|
+
token: ${{ steps.app-token.outputs.token || github.token }}
|
|
76
|
+
read-token: ${{ github.token }}
|
|
77
|
+
scope: ${{ vars.DEPENDABOT_MERGE || env.DEPENDABOT_MERGE }}
|
|
78
|
+
merge-without-checks: ${{ env.DEPENDABOT_MERGE_WITHOUT_CHECKS }}
|
|
79
|
+
post-merge-event: ""
|
|
80
|
+
dry-run: ${{ github.event.inputs.dry-run || 'false' }}
|
|
@@ -19,15 +19,23 @@ env:
|
|
|
19
19
|
# One line: gh-aw joins a multi-line env value onto a single line when it compiles the lock.
|
|
20
20
|
AUDIT_FOCUS: "Architectural layer violations and dependencies pointing the wrong way. Missing or misleading tests around behaviour that already shipped. Security gaps: unvalidated input, missing authorization, secrets in code. Performance anti-patterns, N+1 queries in particular. Documentation that no longer matches the code it describes."
|
|
21
21
|
AUDIT_MARKER: "<!-- agent-audit -->"
|
|
22
|
+
# The largest issue the audit files, measured with refine's own rule (issue-size.cjs). Three
|
|
23
|
+
# quarters of agent-refine.md's REFINE_MAX_BODY_CHARS and REFINE_MAX_WORK_UNITS, so a story a
|
|
24
|
+
# person edits a little still refines. A body over these is trimmed before it is filed.
|
|
25
|
+
AUDIT_MAX_BODY_CHARS: "6000"
|
|
26
|
+
AUDIT_MAX_WORK_UNITS: "18"
|
|
27
|
+
AUDIT_MAX_STORIES: "3"
|
|
22
28
|
GIT_AUTHOR_NAME: "github-actions[bot]"
|
|
23
29
|
GIT_AUTHOR_EMAIL: "github-actions[bot]@users.noreply.github.com"
|
|
24
30
|
GIT_COMMITTER_NAME: "github-actions[bot]"
|
|
25
31
|
GIT_COMMITTER_EMAIL: "github-actions[bot]@users.noreply.github.com"
|
|
26
32
|
description: |
|
|
27
|
-
Read-only repository audit. Finds 5-7 problems, scores each 1-10, files a
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
Read-only repository audit. Finds 5-7 problems, scores each 1-10, and files a compact
|
|
34
|
+
tracking report of every finding, labelled `audit` + `bug`, plus one story issue for each of
|
|
35
|
+
the top 3, labelled `bug` + `refine` and linked to the report as sub-issues. Refine refines
|
|
36
|
+
each story on its own; the report never goes to Refine. Every body is measured with refine's
|
|
37
|
+
size rule before it is filed and trimmed to fit, because one issue holding every finding and
|
|
38
|
+
three stories was over refine's limit every time and was refused. Replaces .loops/recipes/guardrails-audit-loop.yaml.
|
|
31
39
|
|
|
32
40
|
Router-only worker: triggered exclusively via workflow_call from work-router.yml.
|
|
33
41
|
Contract input: trigger-kind(scheduled|manual).
|
|
@@ -54,7 +62,8 @@ on:
|
|
|
54
62
|
type: string
|
|
55
63
|
default: manual
|
|
56
64
|
|
|
57
|
-
# Rung 1. Do not pile reports on top of unactioned reports.
|
|
65
|
+
# Rung 1. Do not pile reports on top of unactioned reports. Only the report carries `audit`;
|
|
66
|
+
# its stories do not, or one audit would fill the three slots by itself.
|
|
58
67
|
#
|
|
59
68
|
# `-label:stale-audit` is what keeps this backpressure from becoming a stop. Three reports
|
|
60
69
|
# nobody ever actioned used to disable the weekly audit for good: the query counted them
|
|
@@ -115,19 +124,27 @@ jobs:
|
|
|
115
124
|
with:
|
|
116
125
|
client-id: ${{ secrets.BOT_APP_ID }}
|
|
117
126
|
private-key: ${{ secrets.BOT_PRIVATE_KEY }}
|
|
118
|
-
#
|
|
119
|
-
#
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if: needs.safe_outputs.outputs.created_issue_number != ''
|
|
124
|
-
uses: ./.github/actions/add-issue-labels
|
|
127
|
+
# Staged, so this is the only write: the report, then each story with its labels and a
|
|
128
|
+
# link back, then the report's list of stories. Each body passes refine's size rule first.
|
|
129
|
+
- name: Download agent output
|
|
130
|
+
id: output
|
|
131
|
+
uses: ./.github/actions/download-agent-output
|
|
125
132
|
with:
|
|
133
|
+
artifact-name: ${{ needs.activation.outputs.artifact_prefix }}agent
|
|
134
|
+
- name: File the report and its stories
|
|
135
|
+
uses: ./.github/actions/file-audit-issues
|
|
136
|
+
with:
|
|
137
|
+
output-file: ${{ steps.output.outputs.output-file }}
|
|
126
138
|
token: ${{ steps.app-token.outputs.token }}
|
|
127
|
-
|
|
128
|
-
|
|
139
|
+
max-body-chars: ${{ env.AUDIT_MAX_BODY_CHARS }}
|
|
140
|
+
max-work-units: ${{ env.AUDIT_MAX_WORK_UNITS }}
|
|
141
|
+
max-stories: ${{ env.AUDIT_MAX_STORIES }}
|
|
142
|
+
run-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
143
|
+
report-labels: |-
|
|
129
144
|
audit
|
|
130
145
|
bug
|
|
146
|
+
story-labels: |-
|
|
147
|
+
bug
|
|
131
148
|
refine
|
|
132
149
|
|
|
133
150
|
# An audit that produced nothing reported success. The whole run -- a full agent, its tokens,
|
|
@@ -138,7 +155,7 @@ jobs:
|
|
|
138
155
|
#
|
|
139
156
|
# This fires when the agent succeeded and the safe-outputs handler processed nothing at all.
|
|
140
157
|
#
|
|
141
|
-
# Step
|
|
158
|
+
# Step 7 of the prompt gives a clean codebase its own outcome -- call `noop` and stop -- and
|
|
142
159
|
# that outcome must not be reported as a failure. `noop` is a registered safe output here
|
|
143
160
|
# (`noop: max 1`), so the handler receives it as a message and the processed count is not zero,
|
|
144
161
|
# which keeps a deliberately empty audit out of this branch. That is read from the handler
|
|
@@ -171,8 +188,11 @@ safe-outputs:
|
|
|
171
188
|
# under noise nobody closes.
|
|
172
189
|
report-failure-as-issue: false
|
|
173
190
|
threat-detection: false
|
|
191
|
+
# The report and the stories are filed by `conclude`, after they are measured.
|
|
192
|
+
staged: true
|
|
193
|
+
# The report and up to three stories.
|
|
174
194
|
create-issue:
|
|
175
|
-
max:
|
|
195
|
+
max: 4
|
|
176
196
|
|
|
177
197
|
# The fleet is two machines, so this clock is also how long a stuck run can hold half of it.
|
|
178
198
|
# 240 went on to every worker at once when the provider was slow, which fixed the deaths and
|
|
@@ -227,21 +247,18 @@ timeout-minutes: 90
|
|
|
227
247
|
again. What this prevents is re-filing something the team looked at and chose to live
|
|
228
248
|
with, which is the noise that makes an audit backlog get ignored.
|
|
229
249
|
|
|
230
|
-
5.
|
|
231
|
-
conclude job will apply them. The body MUST have two sections:
|
|
250
|
+
5. File the report: call `create_issue` with title "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, highest score first, each exactly this shape and no longer than about 600 characters:
|
|
232
251
|
|
|
233
|
-
|
|
234
|
-
|
|
252
|
+
```
|
|
253
|
+
### <n>. <one-line problem> (score <s>)
|
|
254
|
+
**Where:** `path/to/file.ext:line`, `other/file.ext:line`
|
|
255
|
+
**Problem:** One or two sentences: what happens and its impact.
|
|
256
|
+
**Fix:** One or two sentences: the change that resolves it.
|
|
257
|
+
```
|
|
235
258
|
|
|
236
|
-
|
|
237
|
-
findings by score into user stories; that skill owns their shape. Name the likely files to
|
|
238
|
-
change. Mark this section clearly with a heading like `## Top 3 , To Implement`.
|
|
259
|
+
No bullet lists, no Gherkin, no user stories, no reproduction walkthroughs. 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.
|
|
239
260
|
|
|
240
|
-
|
|
241
|
-
because a report of several unrelated defects across different files is exactly the shape
|
|
242
|
-
that does not land as one pull request, splits it into one properly estimated issue per
|
|
243
|
-
finding. Write each finding so it survives that split: self-contained, naming its own
|
|
244
|
-
files and its own acceptance criteria, never "as above" or "same as finding 2".
|
|
261
|
+
6. File the stories: call skill("pc-plan-story") and write the top ${{ env.AUDIT_MAX_STORIES }} findings by score as user stories; that skill owns their shape. Call `create_issue` once per story, after the report, titled with the story's own title (never "Audit: ..."), no labels. Refine refines each story on its own, so each must stand alone: it names its own files and its own acceptance criteria, never "as above" or "see finding 2", and never mentions the other stories or the report. Keep each story well under ${{ env.AUDIT_MAX_BODY_CHARS }} characters and ${{ env.AUDIT_MAX_WORK_UNITS }} bullet lines: the workflow measures every issue against that limit before filing and cuts anything over it from the end.
|
|
245
262
|
|
|
246
|
-
|
|
263
|
+
7. If nothing met the bar, call `noop` and stop. Filing nothing is the right outcome when
|
|
247
264
|
the codebase is clean.
|
|
@@ -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.
|
|
@@ -1827,6 +1835,9 @@ jobs:
|
|
|
1827
1835
|
pull-requests: read
|
|
1828
1836
|
# actions: write cancels workflow runs stuck pending in the fleet queue.
|
|
1829
1837
|
actions: write
|
|
1838
|
+
# The Dependabot merge reads check runs and statuses with this token, not the App's.
|
|
1839
|
+
checks: read
|
|
1840
|
+
statuses: read
|
|
1830
1841
|
env:
|
|
1831
1842
|
BOT_APP_ID: ${{ secrets.BOT_APP_ID }}
|
|
1832
1843
|
BOT_PRIVATE_KEY: ${{ secrets.BOT_PRIVATE_KEY }}
|
|
@@ -1852,6 +1863,18 @@ jobs:
|
|
|
1852
1863
|
stale-pr-days: ${{ env.HOUSEKEEPING_STALE_PR_DAYS }}
|
|
1853
1864
|
digest-title: ${{ env.HOUSEKEEPING_DIGEST_TITLE }}
|
|
1854
1865
|
dry-run: ${{ github.event.inputs.dry-run || 'false' }}
|
|
1866
|
+
# Dependabot updates merge here rather than on a cron of their own. API calls only: no pull
|
|
1867
|
+
# request code is checked out. It runs even when the sweep above failed.
|
|
1868
|
+
- name: Merge Dependabot pull requests
|
|
1869
|
+
if: ${{ !cancelled() && steps.app-token.outcome == 'success' }}
|
|
1870
|
+
uses: ./.github/actions/merge-dependabot
|
|
1871
|
+
with:
|
|
1872
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
1873
|
+
read-token: ${{ github.token }}
|
|
1874
|
+
scope: ${{ vars.DEPENDABOT_MERGE || env.DEPENDABOT_MERGE }}
|
|
1875
|
+
merge-without-checks: ${{ env.DEPENDABOT_MERGE_WITHOUT_CHECKS }}
|
|
1876
|
+
skip-labels: do-not-merge blocked human-review ${{ env.MERGE_TRAIN_HOLD_LABEL }}
|
|
1877
|
+
dry-run: ${{ github.event.inputs.dry-run || 'false' }}
|
|
1855
1878
|
|
|
1856
1879
|
validate:
|
|
1857
1880
|
needs: classify
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plainconceptsplatform/workflows",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.1",
|
|
4
4
|
"description": "Install and update Platform GitHub agentic workflows.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"github-actions",
|
|
@@ -41,6 +41,6 @@
|
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/node": "^22.0.0",
|
|
43
43
|
"typescript": "^5.8.0",
|
|
44
|
-
"vitest": "^
|
|
44
|
+
"vitest": "^4.1.11"
|
|
45
45
|
}
|
|
46
46
|
}
|