@plainconceptsplatform/workflows 0.4.34 → 0.5.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/README.md +88 -88
- package/loops/actions/agent-output.cjs +17 -17
- package/loops/actions/audit-close/action.yml +24 -0
- package/loops/actions/classify-route/action.yml +1 -3
- package/loops/actions/classify-route/classify-route.sh +40 -35
- package/loops/actions/update-changelog/action.yml +113 -0
- package/loops/actions/validate-merge-gate-output/action.yml +40 -40
- package/loops/actions/validate-refine-output/validate-refine-output.sh +16 -3
- package/loops/actions/validate-review-output/action.yml +35 -35
- package/loops/actions/validate-triage-output/action.yml +36 -36
- package/loops/actions/verify-route-matrix/verify-route-matrix.sh +91 -36
- package/loops/scripts/compile-agent-workflows.mjs +195 -6
- package/loops/templates/agentics/agentics-checks.yml +86 -86
- package/loops/templates/agentics/agentics-maintenance.yml +121 -121
- package/loops/templates/ci/app-ci-dotnet-next.yml +268 -171
- package/loops/templates/ci/app-ci-node-monorepo.yml +208 -178
- package/loops/templates/issues/bug_report.yml +109 -109
- package/loops/templates/issues/feature_request.yml +75 -75
- package/loops/templates/opencode/opencode.ci.json +49 -47
- package/loops/templates/opencode/opencode.ci.json.md +46 -41
- package/loops/templates/release/github-release.yml +30 -30
- package/loops/workflows/agent-apply-review.md +465 -443
- package/loops/workflows/agent-audit.md +213 -197
- package/loops/workflows/agent-implement.md +538 -414
- package/loops/workflows/agent-merge-gate.md +730 -584
- package/loops/workflows/agent-refine.md +609 -418
- package/loops/workflows/agent-release.md +258 -0
- package/loops/workflows/agent-triage.md +447 -439
- package/loops/workflows/authorize-bot-work.yml +85 -82
- package/loops/workflows/shared/opencode-ci.md +206 -197
- package/loops/workflows/shared/platform-defaults.md +19 -16
- package/loops/workflows/work-router.yml +862 -632
- package/package.json +7 -8
- package/dist/action-validation.test.d.ts +0 -1
- package/dist/action-validation.test.js +0 -84
- package/dist/catalog-installation.d.ts +0 -30
- package/dist/catalog-installation.js +0 -379
- package/dist/catalog-installation.test.d.ts +0 -1
- package/dist/catalog-installation.test.js +0 -448
- package/dist/catalog-listing.d.ts +0 -13
- package/dist/catalog-listing.js +0 -70
- package/dist/catalog-listing.test.d.ts +0 -1
- package/dist/catalog-listing.test.js +0 -150
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -218
- package/dist/index.test.d.ts +0 -1
- package/dist/index.test.js +0 -273
- package/dist/repository-inspection.d.ts +0 -23
- package/dist/repository-inspection.js +0 -113
- package/dist/repository-inspection.test.d.ts +0 -1
- package/dist/repository-inspection.test.js +0 -77
- package/dist/repository-state.d.ts +0 -18
- package/dist/repository-state.js +0 -77
- package/dist/repository-state.test.d.ts +0 -1
- package/dist/repository-state.test.js +0 -96
- package/dist/route-processing.d.ts +0 -6
- package/dist/route-processing.js +0 -150
- package/dist/route-processing.test.d.ts +0 -1
- package/dist/route-processing.test.js +0 -350
- package/dist/stack-defaults.d.ts +0 -11
- package/dist/stack-defaults.js +0 -104
- package/dist/stack-defaults.test.d.ts +0 -1
- package/dist/stack-defaults.test.js +0 -266
- package/dist/tui.d.ts +0 -25
- package/dist/tui.js +0 -287
- package/dist/tui.test.d.ts +0 -1
- package/dist/tui.test.js +0 -246
- package/dist/workflow-catalog.d.ts +0 -25
- package/dist/workflow-catalog.js +0 -59
- package/dist/workflow-catalog.test.d.ts +0 -1
- package/dist/workflow-catalog.test.js +0 -29
- package/loops/workflows/agent-direct.md +0 -375
- package/loops/workflows/agent-propose.md +0 -342
package/README.md
CHANGED
|
@@ -1,88 +1,88 @@
|
|
|
1
|
-
# Workflows CLI
|
|
2
|
-
|
|
3
|
-
Install and update shared GitHub Agentic Workflows for Plain Concepts Platform repositories.
|
|
4
|
-
|
|
5
|
-
## Quick start
|
|
6
|
-
|
|
7
|
-
The primary entrypoint is the interactive TUI. Run it with no arguments:
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npx @plainconceptsplatform/workflows
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
The TUI lists all routes and templates with install status. Arrow keys navigate, space toggles, Enter installs. Selecting routes installs only those route workers plus mandatory files (opencode.ci.json, compile script, shared imports, actions, router, classifier, route matrix). Selecting only templates installs just those templates.
|
|
14
|
-
|
|
15
|
-
## Install
|
|
16
|
-
|
|
17
|
-
Before installing workflows, install and configure [`PlainConceptsPlatform/agent-harness`](https://github.com/PlainConceptsPlatform/agent-harness) in the consumer repository. Loop workers invoke the skills and commands it provides. Verify the required skills and commands are available before compiling workflows.
|
|
18
|
-
|
|
19
|
-
For non-interactive use (advanced):
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
npx @plainconceptsplatform/workflows@latest init
|
|
23
|
-
npx @plainconceptsplatform/workflows@latest add
|
|
24
|
-
npx @plainconceptsplatform/workflows@latest update
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
For a project-local development dependency:
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
pnpm add -D @plainconceptsplatform/workflows
|
|
31
|
-
pnpm exec workflows # launch interactive TUI
|
|
32
|
-
pnpm exec workflows init
|
|
33
|
-
pnpm exec workflows add
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
`init` inspects the repository and reports its stack and visibility. It does not create or manage repository configuration or a manifest.
|
|
37
|
-
|
|
38
|
-
`add` installs mandatory files (opencode.ci.json, compile script, shared imports, actions, router, classifier, route matrix) when called with no route arguments. Pass route names as positional arguments to install specific route workers alongside the mandatory files:
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
workflows add # mandatory files only, no worker .md files
|
|
42
|
-
workflows add implement refine direct # those route workers plus mandatory files
|
|
43
|
-
workflows add --template agentics-checks # named template only (no mandatory files)
|
|
44
|
-
workflows add refine --template agentics-checks --force # routes + template + mandatory, overwriting conflicts
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
Route names: refine, implement, direct, apply-review, merge-gate, audit, propose. Unknown arguments produce an error.
|
|
48
|
-
|
|
49
|
-
Use `workflows update --force` to force-overwrite managed files that differ from the package source.
|
|
50
|
-
|
|
51
|
-
Install optional standalone templates with `add --template`. Available templates are `agentics-checks`, `agentics-maintenance`, `app-ci-dotnet-next`, `app-ci-node-monorepo`, and `github-release`. CI templates are stack-specific copies, not a combined template. `github-release` publishes generated release notes when a `v*` tag is pushed. Edit their top-level `env:` values for repository paths, package names, and commands.
|
|
52
|
-
|
|
53
|
-
## List and search
|
|
54
|
-
|
|
55
|
-
List all available workflows, routes, and templates with install status:
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
npx --yes --package @plainconceptsplatform/workflows@latest workflows list
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
Each entry is marked `[x]` when the corresponding `.github/workflows/agent-*.md` file already exists in the current directory, or `[ ]` when it is not yet installed.
|
|
62
|
-
|
|
63
|
-
Search by name or description:
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
npx --yes --package @plainconceptsplatform/workflows@latest workflows search "ci"
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## Manual installation
|
|
70
|
-
|
|
71
|
-
The package includes `loops/`, a copyable equivalent of `.github/`:
|
|
72
|
-
|
|
73
|
-
- `loops/actions/` maps to `.github/actions/`
|
|
74
|
-
- `loops/workflows/` maps to `.github/workflows/`
|
|
75
|
-
- `loops/scripts/` maps to `scripts/`
|
|
76
|
-
|
|
77
|
-
Copy these files manually if you do not use the CLI. Each worker is self-contained. Edit its
|
|
78
|
-
top-level `env:` defaults directly for consumer-specific endpoint, model, labels, paths, and checks.
|
|
79
|
-
|
|
80
|
-
## Compile
|
|
81
|
-
|
|
82
|
-
Consumer repositories generate and commit `*.lock.yml` files:
|
|
83
|
-
|
|
84
|
-
```bash
|
|
85
|
-
node scripts/compile-agent-workflows.mjs --force
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
Do not commit generated locks to this package source repository.
|
|
1
|
+
# Workflows CLI
|
|
2
|
+
|
|
3
|
+
Install and update shared GitHub Agentic Workflows for Plain Concepts Platform repositories.
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
The primary entrypoint is the interactive TUI. Run it with no arguments:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx @plainconceptsplatform/workflows
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The TUI lists all routes and templates with install status. Arrow keys navigate, space toggles, Enter installs. Selecting routes installs only those route workers plus mandatory files (opencode.ci.json, compile script, shared imports, actions, router, classifier, route matrix). Selecting only templates installs just those templates.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
Before installing workflows, install and configure [`PlainConceptsPlatform/agent-harness`](https://github.com/PlainConceptsPlatform/agent-harness) in the consumer repository. Loop workers invoke the skills and commands it provides. Verify the required skills and commands are available before compiling workflows.
|
|
18
|
+
|
|
19
|
+
For non-interactive use (advanced):
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx @plainconceptsplatform/workflows@latest init
|
|
23
|
+
npx @plainconceptsplatform/workflows@latest add
|
|
24
|
+
npx @plainconceptsplatform/workflows@latest update
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
For a project-local development dependency:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pnpm add -D @plainconceptsplatform/workflows
|
|
31
|
+
pnpm exec workflows # launch interactive TUI
|
|
32
|
+
pnpm exec workflows init
|
|
33
|
+
pnpm exec workflows add
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`init` inspects the repository and reports its stack and visibility. It does not create or manage repository configuration or a manifest.
|
|
37
|
+
|
|
38
|
+
`add` installs mandatory files (opencode.ci.json, compile script, shared imports, actions, router, classifier, route matrix) when called with no route arguments. Pass route names as positional arguments to install specific route workers alongside the mandatory files:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
workflows add # mandatory files only, no worker .md files
|
|
42
|
+
workflows add implement refine direct # those route workers plus mandatory files
|
|
43
|
+
workflows add --template agentics-checks # named template only (no mandatory files)
|
|
44
|
+
workflows add refine --template agentics-checks --force # routes + template + mandatory, overwriting conflicts
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Route names: refine, implement, direct, apply-review, merge-gate, audit, propose. Unknown arguments produce an error.
|
|
48
|
+
|
|
49
|
+
Use `workflows update --force` to force-overwrite managed files that differ from the package source.
|
|
50
|
+
|
|
51
|
+
Install optional standalone templates with `add --template`. Available templates are `agentics-checks`, `agentics-maintenance`, `app-ci-dotnet-next`, `app-ci-node-monorepo`, and `github-release`. CI templates are stack-specific copies, not a combined template. `github-release` publishes generated release notes when a `v*` tag is pushed. Edit their top-level `env:` values for repository paths, package names, and commands.
|
|
52
|
+
|
|
53
|
+
## List and search
|
|
54
|
+
|
|
55
|
+
List all available workflows, routes, and templates with install status:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npx --yes --package @plainconceptsplatform/workflows@latest workflows list
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Each entry is marked `[x]` when the corresponding `.github/workflows/agent-*.md` file already exists in the current directory, or `[ ]` when it is not yet installed.
|
|
62
|
+
|
|
63
|
+
Search by name or description:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npx --yes --package @plainconceptsplatform/workflows@latest workflows search "ci"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Manual installation
|
|
70
|
+
|
|
71
|
+
The package includes `loops/`, a copyable equivalent of `.github/`:
|
|
72
|
+
|
|
73
|
+
- `loops/actions/` maps to `.github/actions/`
|
|
74
|
+
- `loops/workflows/` maps to `.github/workflows/`
|
|
75
|
+
- `loops/scripts/` maps to `scripts/`
|
|
76
|
+
|
|
77
|
+
Copy these files manually if you do not use the CLI. Each worker is self-contained. Edit its
|
|
78
|
+
top-level `env:` defaults directly for consumer-specific endpoint, model, labels, paths, and checks.
|
|
79
|
+
|
|
80
|
+
## Compile
|
|
81
|
+
|
|
82
|
+
Consumer repositories generate and commit `*.lock.yml` files:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
node scripts/compile-agent-workflows.mjs --force
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Do not commit generated locks to this package source repository.
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
// Managed by @plainconceptsplatform/workflows. Source: loops/actions/agent-output.cjs. Update with `workflows update --force`; consumer edits may be overwritten.
|
|
2
|
-
const fs = require('node:fs');
|
|
3
|
-
|
|
4
|
-
// Every apply-agent-* action reads the same artifact the same way: a missing file means the
|
|
5
|
-
// agent produced nothing, which is a normal outcome rather than a failure.
|
|
6
|
-
function readAgentItems(outputFile, type) {
|
|
7
|
-
if (!outputFile || !fs.existsSync(outputFile)) {
|
|
8
|
-
return [];
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const parsed = JSON.parse(fs.readFileSync(outputFile, 'utf8'));
|
|
12
|
-
const items = Array.isArray(parsed.items) ? parsed.items : [];
|
|
13
|
-
|
|
14
|
-
return type ? items.filter((item) => item.type === type) : items;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
module.exports = { readAgentItems };
|
|
1
|
+
// Managed by @plainconceptsplatform/workflows. Source: loops/actions/agent-output.cjs. Update with `workflows update --force`; consumer edits may be overwritten.
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
|
|
4
|
+
// Every apply-agent-* action reads the same artifact the same way: a missing file means the
|
|
5
|
+
// agent produced nothing, which is a normal outcome rather than a failure.
|
|
6
|
+
function readAgentItems(outputFile, type) {
|
|
7
|
+
if (!outputFile || !fs.existsSync(outputFile)) {
|
|
8
|
+
return [];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const parsed = JSON.parse(fs.readFileSync(outputFile, 'utf8'));
|
|
12
|
+
const items = Array.isArray(parsed.items) ? parsed.items : [];
|
|
13
|
+
|
|
14
|
+
return type ? items.filter((item) => item.type === type) : items;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = { readAgentItems };
|
|
@@ -81,6 +81,30 @@ runs:
|
|
|
81
81
|
? 'This report references no issues.'
|
|
82
82
|
: `All ${closedRefs} referenced issue(s) are closed.`;
|
|
83
83
|
|
|
84
|
+
// Do not close if there are open PRs that close this issue.
|
|
85
|
+
// The audit report may have spawned implement workers whose PRs
|
|
86
|
+
// are still under review — closing the report prematurely is misleading.
|
|
87
|
+
const openPRs = await github.paginate(github.rest.pulls.list, {
|
|
88
|
+
...context.repo,
|
|
89
|
+
state: 'open',
|
|
90
|
+
per_page: 100,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const closesThisReport = openPRs.some((pr) => {
|
|
94
|
+
const body = pr.body ?? '';
|
|
95
|
+
const patterns = [
|
|
96
|
+
new RegExp(`(?:^|[^A-Za-z0-9_/])#(?:closes?|fixes?|resolves?|closed?)\\s+#${report.number}\\b`, 'i'),
|
|
97
|
+
new RegExp(`(?:^|[^A-Za-z0-9_/])#${report.number}\\b`),
|
|
98
|
+
];
|
|
99
|
+
return patterns.some((p) => p.test(body));
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (closesThisReport) {
|
|
103
|
+
core.info(`#${report.number}: kept open, has open PR(s) that close it.`);
|
|
104
|
+
keptOpen += 1;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
84
108
|
await github.rest.issues.createComment({
|
|
85
109
|
...context.repo,
|
|
86
110
|
issue_number: report.number,
|
|
@@ -27,9 +27,6 @@ outputs:
|
|
|
27
27
|
refine-mode:
|
|
28
28
|
description: first or rerefine.
|
|
29
29
|
value: ${{ steps.classify.outputs.refine-mode }}
|
|
30
|
-
direct-mode:
|
|
31
|
-
description: first or continue.
|
|
32
|
-
value: ${{ steps.classify.outputs.direct-mode }}
|
|
33
30
|
triage-mode:
|
|
34
31
|
description: first or retriage.
|
|
35
32
|
value: ${{ steps.classify.outputs.triage-mode }}
|
|
@@ -47,6 +44,7 @@ runs:
|
|
|
47
44
|
ACTION: ${{ github.event.action }}
|
|
48
45
|
LABEL: ${{ github.event.label.name }}
|
|
49
46
|
ISSUE_LABELS: ${{ toJSON(github.event.issue.labels.*.name) }}
|
|
47
|
+
ISSUE_STATE: ${{ github.event.issue.state }}
|
|
50
48
|
EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
|
|
51
49
|
EVENT_PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
52
50
|
COMMENT_ON_PR: ${{ github.event.issue.pull_request != '' }}
|
|
@@ -11,11 +11,7 @@ set -euo pipefail
|
|
|
11
11
|
readonly AUDIT_CRON="17 1 * * 1"
|
|
12
12
|
readonly AUDIT_CLOSE_CRON="43 3 * * *"
|
|
13
13
|
readonly CLEANUP_ARTIFACTS_CRON="0 6 * * *"
|
|
14
|
-
readonly RECONCILE_BOT_PR_RUNS_CRON="17
|
|
15
|
-
# Daily, but it proposes far less often than daily: the worker holds one open
|
|
16
|
-
# proposal at a time and skips while that slot is filled. The cron is a heartbeat,
|
|
17
|
-
# the queue is the pacing.
|
|
18
|
-
readonly PROPOSE_CRON="29 7 * * *"
|
|
14
|
+
readonly RECONCILE_BOT_PR_RUNS_CRON="17 * * * *"
|
|
19
15
|
|
|
20
16
|
has_label() {
|
|
21
17
|
jq -e --arg name "$1" 'index($name)' >/dev/null 2>&1 <<<"${ISSUE_LABELS:-[]}"
|
|
@@ -28,17 +24,27 @@ is_issue_number() {
|
|
|
28
24
|
classify_route() {
|
|
29
25
|
local route="none" error=""
|
|
30
26
|
local issue_number="" pr_number="" ci_conclusion="" ci_run_id=""
|
|
31
|
-
local refine_mode=""
|
|
27
|
+
local refine_mode="" triage_mode="" trigger_kind=""
|
|
32
28
|
|
|
33
29
|
case "${EVENT:-}" in
|
|
34
30
|
issues)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
31
|
+
# A closed issue is finished work, but label edits on one still arrive as events.
|
|
32
|
+
if [ "${ISSUE_STATE:-}" = "closed" ]; then
|
|
33
|
+
error="issue is closed"
|
|
34
|
+
elif [ "${ACTION:-}" = "opened" ]; then
|
|
35
|
+
# Issue opened with a work label → skip triage.
|
|
36
|
+
# The label event will trigger authorize-bot-work → bot-working → the
|
|
37
|
+
# correct worker. Triage would only interfere.
|
|
38
|
+
if has_label refine || has_label implement; then
|
|
39
|
+
error="issue opened with a work label; triage skipped"
|
|
40
|
+
else
|
|
41
|
+
# Issue opened by an outside collaborator → triage. The authorize job
|
|
42
|
+
# gates the caller on is_outside_collaborator; this routes unconditionally
|
|
43
|
+
# so write+ openers classify to triage but the caller job skips them.
|
|
44
|
+
route="triage"
|
|
45
|
+
triage_mode="first"
|
|
46
|
+
issue_number="${EVENT_ISSUE_NUMBER:-}"
|
|
47
|
+
fi
|
|
42
48
|
elif [ "${ACTION:-}" = "labeled" ]; then
|
|
43
49
|
case "${LABEL:-}" in
|
|
44
50
|
bot-working)
|
|
@@ -53,12 +59,8 @@ classify_route() {
|
|
|
53
59
|
route="refine"
|
|
54
60
|
refine_mode="first"
|
|
55
61
|
issue_number="${EVENT_ISSUE_NUMBER:-}"
|
|
56
|
-
elif has_label direct; then
|
|
57
|
-
route="direct"
|
|
58
|
-
direct_mode="first"
|
|
59
|
-
issue_number="${EVENT_ISSUE_NUMBER:-}"
|
|
60
62
|
else
|
|
61
|
-
error="bot-working added but no work label
|
|
63
|
+
error="bot-working added but no work label found"
|
|
62
64
|
fi
|
|
63
65
|
;;
|
|
64
66
|
triage)
|
|
@@ -75,7 +77,7 @@ classify_route() {
|
|
|
75
77
|
triage_mode="first"
|
|
76
78
|
fi
|
|
77
79
|
;;
|
|
78
|
-
refine | implement
|
|
80
|
+
refine | implement)
|
|
79
81
|
# If the actor is a bot (e.g. refine→implement transition), route directly.
|
|
80
82
|
# If the actor is a human, authorize-bot-work.yml will add bot-working which triggers the workflow.
|
|
81
83
|
if [ "${ACTOR:-}" != "" ] && echo "${ACTOR:-}" | grep -q '\[bot\]$'; then
|
|
@@ -83,13 +85,11 @@ classify_route() {
|
|
|
83
85
|
issue_number="${EVENT_ISSUE_NUMBER:-}"
|
|
84
86
|
if [ "${LABEL:-}" = "refine" ]; then
|
|
85
87
|
refine_mode="first"
|
|
86
|
-
elif [ "${LABEL:-}" = "direct" ]; then
|
|
87
|
-
direct_mode="first"
|
|
88
88
|
fi
|
|
89
89
|
elif has_label bot-working; then
|
|
90
90
|
# Already has bot-working - the workflow is already running or queued.
|
|
91
91
|
# Don't re-trigger.
|
|
92
|
-
error="issue already has bot-working label; implement/refine
|
|
92
|
+
error="issue already has bot-working label; implement/refine already in progress"
|
|
93
93
|
else
|
|
94
94
|
error="waiting for bot to add bot-working label"
|
|
95
95
|
fi
|
|
@@ -102,6 +102,10 @@ classify_route() {
|
|
|
102
102
|
if [ "${COMMENT_ON_PR:-false}" = "true" ]; then
|
|
103
103
|
route="apply-review"
|
|
104
104
|
pr_number="${EVENT_ISSUE_NUMBER:-}"
|
|
105
|
+
elif [ "${ISSUE_STATE:-}" = "closed" ]; then
|
|
106
|
+
# A closing comment on a refine-labelled issue used to start a full re-refine, which
|
|
107
|
+
# held a runner for 35 minutes and filed split children under an already-shut parent.
|
|
108
|
+
error="issue is closed"
|
|
105
109
|
elif [ "${COMMENT_SENDER_TYPE:-}" = "Bot" ]; then
|
|
106
110
|
error="comment authored by a bot"
|
|
107
111
|
elif has_label triage; then
|
|
@@ -110,12 +114,8 @@ classify_route() {
|
|
|
110
114
|
issue_number="${EVENT_ISSUE_NUMBER:-}"
|
|
111
115
|
elif has_label implement; then
|
|
112
116
|
error="issue has implement label; comments do not re-trigger implement"
|
|
113
|
-
elif has_label direct; then
|
|
114
|
-
route="direct"
|
|
115
|
-
direct_mode="continue"
|
|
116
|
-
issue_number="${EVENT_ISSUE_NUMBER:-}"
|
|
117
117
|
elif ! has_label refine; then
|
|
118
|
-
error="issue does not carry the refine
|
|
118
|
+
error="issue does not carry the refine label"
|
|
119
119
|
else
|
|
120
120
|
route="refine"
|
|
121
121
|
refine_mode="rerefine"
|
|
@@ -143,10 +143,15 @@ classify_route() {
|
|
|
143
143
|
;;
|
|
144
144
|
|
|
145
145
|
workflow_run)
|
|
146
|
-
|
|
146
|
+
# Only a FAILED CI run on an attached pull request auto-dispatches the gate. A green
|
|
147
|
+
# run reaches merge through app-ci's dispatch-merge-gate and the reconcile belt, so
|
|
148
|
+
# routing success here would double-fire the gate for every passing pull request.
|
|
149
|
+
if [ "${RUN_CONCLUSION:-}" != "failure" ]; then
|
|
150
|
+
error="CI concluded '${RUN_CONCLUSION:-}'; the gate auto-triggers only on failure"
|
|
151
|
+
elif is_issue_number "${RUN_PR_NUMBER:-}"; then
|
|
147
152
|
route="merge-gate"
|
|
148
153
|
pr_number="${RUN_PR_NUMBER}"
|
|
149
|
-
ci_conclusion="
|
|
154
|
+
ci_conclusion="failure"
|
|
150
155
|
ci_run_id="${RUN_ID:-}"
|
|
151
156
|
else
|
|
152
157
|
error="CI run has no attached pull request"
|
|
@@ -167,14 +172,12 @@ classify_route() {
|
|
|
167
172
|
workflow_dispatch)
|
|
168
173
|
trigger_kind="manual"
|
|
169
174
|
case "${OPERATION:-}" in
|
|
170
|
-
refine | implement
|
|
175
|
+
refine | implement)
|
|
171
176
|
if is_issue_number "${INPUT_ISSUE_NUMBER:-}"; then
|
|
172
177
|
route="${OPERATION}"
|
|
173
178
|
issue_number="${INPUT_ISSUE_NUMBER}"
|
|
174
179
|
if [ "$OPERATION" = "refine" ]; then
|
|
175
180
|
refine_mode="${INPUT_MODE:-first}"
|
|
176
|
-
elif [ "$OPERATION" = "direct" ]; then
|
|
177
|
-
direct_mode="${INPUT_MODE:-first}"
|
|
178
181
|
fi
|
|
179
182
|
else
|
|
180
183
|
error="operation '${OPERATION}' needs a positive issue-number, got '${INPUT_ISSUE_NUMBER:-}'"
|
|
@@ -207,13 +210,16 @@ classify_route() {
|
|
|
207
210
|
error="operation 'merge-gate' needs a positive pr-number, got '${INPUT_PR_NUMBER:-}'"
|
|
208
211
|
fi
|
|
209
212
|
;;
|
|
210
|
-
audit
|
|
213
|
+
audit)
|
|
211
214
|
route="${OPERATION}"
|
|
212
215
|
trigger_kind="${INPUT_TRIGGER_KIND:-manual}"
|
|
213
216
|
;;
|
|
214
|
-
|
|
217
|
+
audit-close | cleanup-artifacts | reconcile-bot-pr-runs | validate)
|
|
215
218
|
route="${OPERATION}"
|
|
216
219
|
;;
|
|
220
|
+
release)
|
|
221
|
+
route="release"
|
|
222
|
+
;;
|
|
217
223
|
*)
|
|
218
224
|
error="unknown operation '${OPERATION:-}'"
|
|
219
225
|
;;
|
|
@@ -232,7 +238,6 @@ pr-number=${pr_number}
|
|
|
232
238
|
ci-conclusion=${ci_conclusion}
|
|
233
239
|
ci-run-id=${ci_run_id}
|
|
234
240
|
refine-mode=${refine_mode}
|
|
235
|
-
direct-mode=${direct_mode}
|
|
236
241
|
triage-mode=${triage_mode}
|
|
237
242
|
trigger-kind=${trigger_kind}
|
|
238
243
|
error=${error}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/update-changelog/action.yml. Update with `workflows update --force`; consumer edits may be overwritten.
|
|
2
|
+
name: Update changelog
|
|
3
|
+
description: Prepend a changelog entry for a landed issue and push it to the default branch.
|
|
4
|
+
|
|
5
|
+
inputs:
|
|
6
|
+
token:
|
|
7
|
+
description: Token with contents write on the repository.
|
|
8
|
+
required: true
|
|
9
|
+
issue-number:
|
|
10
|
+
description: Issue the landed commit implements.
|
|
11
|
+
required: true
|
|
12
|
+
commit-sha:
|
|
13
|
+
description: Commit the work landed as. Its subject becomes the summary.
|
|
14
|
+
required: true
|
|
15
|
+
changelog-path:
|
|
16
|
+
description: Path to changelog.json.
|
|
17
|
+
required: false
|
|
18
|
+
default: apps/web/src/shared/data/changelog.json
|
|
19
|
+
max-entries:
|
|
20
|
+
description: Newest entries to keep.
|
|
21
|
+
required: false
|
|
22
|
+
default: "20"
|
|
23
|
+
|
|
24
|
+
runs:
|
|
25
|
+
using: composite
|
|
26
|
+
steps:
|
|
27
|
+
- name: Prepend the entry and push
|
|
28
|
+
shell: bash
|
|
29
|
+
env:
|
|
30
|
+
GH_TOKEN: ${{ inputs.token }}
|
|
31
|
+
REPO: ${{ github.repository }}
|
|
32
|
+
ISSUE_NUMBER: ${{ inputs.issue-number }}
|
|
33
|
+
COMMIT_SHA: ${{ inputs.commit-sha }}
|
|
34
|
+
CHANGELOG_PATH: ${{ inputs.changelog-path }}
|
|
35
|
+
MAX_ENTRIES: ${{ inputs.max-entries }}
|
|
36
|
+
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
|
37
|
+
run: |
|
|
38
|
+
set -euo pipefail
|
|
39
|
+
|
|
40
|
+
# This used to be part of the implement prompt. It is deterministic work, and leaving it
|
|
41
|
+
# to the agent made every implement touch the same file: two runs whose branches were cut
|
|
42
|
+
# before the other merged conflicted here, produced correct code, and still failed to open
|
|
43
|
+
# a pull request. Doing it after the merge means exactly one writer, always against a
|
|
44
|
+
# current default branch.
|
|
45
|
+
|
|
46
|
+
host="${GITHUB_SERVER_URL#https://}"
|
|
47
|
+
git config user.name "github-actions[bot]"
|
|
48
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
49
|
+
git remote set-url origin "https://x-access-token:${GH_TOKEN}@${host}/${GITHUB_REPOSITORY}.git"
|
|
50
|
+
|
|
51
|
+
git fetch --quiet origin "$DEFAULT_BRANCH"
|
|
52
|
+
git checkout --quiet -B "$DEFAULT_BRANCH" "origin/$DEFAULT_BRANCH"
|
|
53
|
+
|
|
54
|
+
if [ ! -f "$CHANGELOG_PATH" ]; then
|
|
55
|
+
echo "::notice::$CHANGELOG_PATH does not exist; nothing to update."
|
|
56
|
+
exit 0
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# Idempotent: a re-run, a retry, or a second call for the same commit must not add a
|
|
60
|
+
# duplicate row.
|
|
61
|
+
short_sha="${COMMIT_SHA:0:7}"
|
|
62
|
+
if jq -e --arg c "$short_sha" '.changes[]? | select(.commit == $c)' "$CHANGELOG_PATH" >/dev/null 2>&1; then
|
|
63
|
+
echo "::notice::$short_sha is already in the changelog."
|
|
64
|
+
exit 0
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
title=$(gh issue view "$ISSUE_NUMBER" --repo "$REPO" --json title --jq '.title')
|
|
68
|
+
|
|
69
|
+
# The commit subject carries what actually changed. Strip the conventional-commit prefix
|
|
70
|
+
# and the trailing issue reference so the summary reads as prose rather than as a commit.
|
|
71
|
+
subject=$(git log -1 --format=%s "$COMMIT_SHA")
|
|
72
|
+
summary=$(printf '%s' "$subject" \
|
|
73
|
+
| sed -E 's/^[a-z]+(\([^)]*\))?!?: *//' \
|
|
74
|
+
| sed -E 's/ *\(#[0-9]+\) *$//')
|
|
75
|
+
[ -n "$summary" ] || summary="$title"
|
|
76
|
+
# Capitalise, because a commit subject conventionally starts lowercase and this is read
|
|
77
|
+
# by people rather than by tooling.
|
|
78
|
+
summary="$(printf '%s' "${summary:0:1}" | tr '[:lower:]' '[:upper:]')${summary:1}"
|
|
79
|
+
|
|
80
|
+
timestamp=$(git log -1 --format=%cI "$COMMIT_SHA")
|
|
81
|
+
|
|
82
|
+
tmp=$(mktemp)
|
|
83
|
+
jq --argjson n "$MAX_ENTRIES" \
|
|
84
|
+
--arg ts "$timestamp" \
|
|
85
|
+
--argjson issue "$ISSUE_NUMBER" \
|
|
86
|
+
--arg title "$title" \
|
|
87
|
+
--arg summary "$summary" \
|
|
88
|
+
--arg commit "$short_sha" \
|
|
89
|
+
'.changes = ([{timestamp: $ts, issue: $issue, title: $title, summary: $summary, commit: $commit}] + (.changes // []))[:$n]' \
|
|
90
|
+
"$CHANGELOG_PATH" > "$tmp"
|
|
91
|
+
mv "$tmp" "$CHANGELOG_PATH"
|
|
92
|
+
|
|
93
|
+
if git diff --quiet -- "$CHANGELOG_PATH"; then
|
|
94
|
+
echo "::notice::changelog unchanged."
|
|
95
|
+
exit 0
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
git add -- "$CHANGELOG_PATH"
|
|
99
|
+
git commit --quiet -m "docs(changelog): record #${ISSUE_NUMBER}"
|
|
100
|
+
|
|
101
|
+
# The default branch may have moved while this ran. Rebase and retry rather than fail:
|
|
102
|
+
# only this action writes the file, so a rebase here cannot produce a content conflict.
|
|
103
|
+
for attempt in 1 2 3; do
|
|
104
|
+
if git push --quiet origin "$DEFAULT_BRANCH"; then
|
|
105
|
+
echo "Recorded #${ISSUE_NUMBER} at ${short_sha} in the changelog."
|
|
106
|
+
exit 0
|
|
107
|
+
fi
|
|
108
|
+
echo "::notice::push rejected, rebasing (attempt ${attempt})"
|
|
109
|
+
git fetch --quiet origin "$DEFAULT_BRANCH"
|
|
110
|
+
git rebase --quiet "origin/$DEFAULT_BRANCH" || { git rebase --abort || true; break; }
|
|
111
|
+
done
|
|
112
|
+
|
|
113
|
+
echo "::warning::Could not push the changelog entry for #${ISSUE_NUMBER}."
|
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/validate-merge-gate-output/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
-
name: Validate merge-gate output
|
|
3
|
-
description: Verify agent_output.json contains a usable merge-gate verdict before any state changes.
|
|
4
|
-
inputs:
|
|
5
|
-
output-file:
|
|
6
|
-
description: Path to agent_output.json.
|
|
7
|
-
required: true
|
|
8
|
-
issue-number:
|
|
9
|
-
description: Issue number that the merge-gate outcome must target.
|
|
10
|
-
required: true
|
|
11
|
-
ci-conclusion:
|
|
12
|
-
description: CI conclusion supplied to the merge gate.
|
|
13
|
-
required: true
|
|
14
|
-
outputs:
|
|
15
|
-
valid:
|
|
16
|
-
description: Whether the output contains a usable merge-gate comment with a verdict.
|
|
17
|
-
value: ${{ steps.validate.outputs.valid }}
|
|
18
|
-
outcome:
|
|
19
|
-
description: "Deterministic outcome: merge, review, remediated, or invalid."
|
|
20
|
-
value: ${{ steps.validate.outputs.outcome }}
|
|
21
|
-
runs:
|
|
22
|
-
using: composite
|
|
23
|
-
steps:
|
|
24
|
-
- name: Validate merge-gate outcome
|
|
25
|
-
id: validate
|
|
26
|
-
shell: bash
|
|
27
|
-
env:
|
|
28
|
-
ISSUE_NUMBER: ${{ inputs.issue-number }}
|
|
29
|
-
CI_CONCLUSION: ${{ inputs.ci-conclusion }}
|
|
30
|
-
OUTPUT_FILE: ${{ inputs.output-file }}
|
|
31
|
-
run: |
|
|
32
|
-
set -euo pipefail
|
|
33
|
-
|
|
34
|
-
outcome="$(bash "${{ github.action_path }}/validate-merge-gate-output.sh" "$OUTPUT_FILE" "$ISSUE_NUMBER" "$CI_CONCLUSION")"
|
|
35
|
-
echo "outcome=$outcome" >> "$GITHUB_OUTPUT"
|
|
36
|
-
echo "valid=$([ "$outcome" != 'invalid' ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
|
37
|
-
|
|
38
|
-
if [ "$outcome" = 'invalid' ]; then
|
|
39
|
-
echo "::warning::Agent output has no usable merge-gate outcome. It will not be applied."
|
|
40
|
-
fi
|
|
1
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/validate-merge-gate-output/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
+
name: Validate merge-gate output
|
|
3
|
+
description: Verify agent_output.json contains a usable merge-gate verdict before any state changes.
|
|
4
|
+
inputs:
|
|
5
|
+
output-file:
|
|
6
|
+
description: Path to agent_output.json.
|
|
7
|
+
required: true
|
|
8
|
+
issue-number:
|
|
9
|
+
description: Issue number that the merge-gate outcome must target.
|
|
10
|
+
required: true
|
|
11
|
+
ci-conclusion:
|
|
12
|
+
description: CI conclusion supplied to the merge gate.
|
|
13
|
+
required: true
|
|
14
|
+
outputs:
|
|
15
|
+
valid:
|
|
16
|
+
description: Whether the output contains a usable merge-gate comment with a verdict.
|
|
17
|
+
value: ${{ steps.validate.outputs.valid }}
|
|
18
|
+
outcome:
|
|
19
|
+
description: "Deterministic outcome: merge, review, remediated, or invalid."
|
|
20
|
+
value: ${{ steps.validate.outputs.outcome }}
|
|
21
|
+
runs:
|
|
22
|
+
using: composite
|
|
23
|
+
steps:
|
|
24
|
+
- name: Validate merge-gate outcome
|
|
25
|
+
id: validate
|
|
26
|
+
shell: bash
|
|
27
|
+
env:
|
|
28
|
+
ISSUE_NUMBER: ${{ inputs.issue-number }}
|
|
29
|
+
CI_CONCLUSION: ${{ inputs.ci-conclusion }}
|
|
30
|
+
OUTPUT_FILE: ${{ inputs.output-file }}
|
|
31
|
+
run: |
|
|
32
|
+
set -euo pipefail
|
|
33
|
+
|
|
34
|
+
outcome="$(bash "${{ github.action_path }}/validate-merge-gate-output.sh" "$OUTPUT_FILE" "$ISSUE_NUMBER" "$CI_CONCLUSION")"
|
|
35
|
+
echo "outcome=$outcome" >> "$GITHUB_OUTPUT"
|
|
36
|
+
echo "valid=$([ "$outcome" != 'invalid' ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
|
37
|
+
|
|
38
|
+
if [ "$outcome" = 'invalid' ]; then
|
|
39
|
+
echo "::warning::Agent output has no usable merge-gate outcome. It will not be applied."
|
|
40
|
+
fi
|