@plainconceptsplatform/workflows 0.19.2 → 0.20.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.
Files changed (53) hide show
  1. package/dist/catalog-installation.js +12 -1
  2. package/dist/index.js +0 -0
  3. package/dist/stack-defaults.js +16 -16
  4. package/dist/worker-env.js +14 -0
  5. package/loops/actions/add-issue-labels/action.yml +50 -50
  6. package/loops/actions/agent-output.cjs +17 -17
  7. package/loops/actions/apply-agent-bundle/action.yml +24 -24
  8. package/loops/actions/apply-agent-comments/action.yml +42 -42
  9. package/loops/actions/apply-agent-labels/action.yml +55 -55
  10. package/loops/actions/apply-agent-output/action.yml +108 -108
  11. package/loops/actions/assess-blast-radius/action.yml +148 -0
  12. package/loops/actions/assess-blast-radius/assess-blast-radius.sh +138 -0
  13. package/loops/actions/classify-route/action.yml +100 -100
  14. package/loops/actions/cleanup-artifacts/action.yml +91 -91
  15. package/loops/actions/close-agent-issues/action.yml +43 -43
  16. package/loops/actions/create-agent-issues/action.yml +52 -52
  17. package/loops/actions/create-issue-comment/action.yml +29 -29
  18. package/loops/actions/download-agent-output/action.yml +53 -53
  19. package/loops/actions/housekeeping/action.yml +55 -2
  20. package/loops/actions/link-pr-to-issue/action.yml +40 -40
  21. package/loops/actions/list-open-issues/action.yml +33 -33
  22. package/loops/actions/load-issue-context/action.yml +45 -45
  23. package/loops/actions/merge-agent-pr/action.yml +49 -49
  24. package/loops/actions/push-agent-branch/action.yml +45 -45
  25. package/loops/actions/remove-issue-labels/action.yml +37 -37
  26. package/loops/actions/update-agent-issues/action.yml +58 -58
  27. package/loops/actions/validate-merge-gate-output/action.yml +62 -40
  28. package/loops/actions/validate-merge-gate-output/validate-merge-gate-output.sh +147 -31
  29. package/loops/actions/validate-refine-output/action.yml +48 -44
  30. package/loops/actions/validate-refine-output/validate-refine-output.sh +15 -4
  31. package/loops/actions/validate-review-output/action.yml +35 -35
  32. package/loops/actions/validate-triage-output/action.yml +36 -36
  33. package/loops/actions/verify-composite-actions/action.yml +9 -9
  34. package/loops/actions/verify-refine-output/action.yml +9 -9
  35. package/loops/actions/verify-refine-output/verify-refine-output.sh +6 -1
  36. package/loops/actions/verify-route-matrix/action.yml +9 -9
  37. package/loops/actions/verify-route-matrix/verify-gate-metrics.mjs +51 -0
  38. package/loops/actions/verify-route-matrix/verify-route-matrix.sh +329 -29
  39. package/loops/scripts/compile-agent-workflows.mjs +331 -331
  40. package/loops/templates/agentics/agentics-maintenance.yml +121 -121
  41. package/loops/templates/ci/app-ci-dotnet-next.yml +330 -330
  42. package/loops/templates/ci/app-ci-node-monorepo.yml +260 -260
  43. package/loops/templates/issues/bug_report.yml +109 -109
  44. package/loops/templates/issues/feature_request.yml +75 -75
  45. package/loops/templates/opencode/opencode.ci.json +55 -49
  46. package/loops/templates/opencode/opencode.ci.json.md +59 -49
  47. package/loops/templates/release/github-release.yml +30 -30
  48. package/loops/workflows/agent-merge-gate.md +367 -148
  49. package/loops/workflows/agent-refine.md +60 -16
  50. package/loops/workflows/authorize-bot-work.yml +105 -105
  51. package/loops/workflows/shared/opencode-ci.md +206 -206
  52. package/loops/workflows/shared/platform-defaults.md +19 -19
  53. package/package.json +12 -11
@@ -1,53 +1,53 @@
1
- # Managed by @plainconceptsplatform/workflows. Source: loops/actions/download-agent-output/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
- name: Download agent output
3
- description: Download the agent artifact and expose the output JSON and bundle paths.
4
- inputs:
5
- artifact-name:
6
- description: >-
7
- Artifact name to download. gh-aw prefixes it with the value it derives from the
8
- workflow's inputs, so a workflow_call worker must pass the activation job's
9
- artifact_prefix output followed by "agent", not the bare name.
10
- required: true
11
- outputs:
12
- output-file:
13
- description: Path to agent_output.json, or empty if not found.
14
- value: ${{ steps.download.outputs.output-file }}
15
- bundle-file:
16
- description: Path to the git bundle, or empty if not found.
17
- value: ${{ steps.download.outputs.bundle-file }}
18
- item-count:
19
- description: Number of items in the agent output.
20
- value: ${{ steps.download.outputs.item-count }}
21
- runs:
22
- using: composite
23
- steps:
24
- - name: Download agent artifact
25
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
26
- with:
27
- name: ${{ inputs.artifact-name }}
28
- path: agent-artifact
29
- - name: Locate output and bundle
30
- id: download
31
- shell: bash
32
- env:
33
- ARTIFACT_NAME: ${{ inputs.artifact-name }}
34
- run: |
35
- set -euo pipefail
36
- OUTPUT_FILE=""
37
- BUNDLE_FILE=""
38
- ITEM_COUNT="0"
39
-
40
- if [ -f "agent-artifact/agent_output.json" ]; then
41
- OUTPUT_FILE="agent-artifact/agent_output.json"
42
- ITEM_COUNT=$(jq '.items | length' "$OUTPUT_FILE")
43
- BUNDLE_FILE=$(ls agent-artifact/aw-*.bundle 2>/dev/null | head -1 || true)
44
- else
45
- echo "::error::Artifact '$ARTIFACT_NAME' contained no agent_output.json. Nothing the agent proposed will be applied."
46
- exit 1
47
- fi
48
-
49
- echo "Artifact '$ARTIFACT_NAME' carries $ITEM_COUNT item(s)."
50
-
51
- echo "output-file=$OUTPUT_FILE" >> "$GITHUB_OUTPUT"
52
- echo "bundle-file=$BUNDLE_FILE" >> "$GITHUB_OUTPUT"
53
- echo "item-count=$ITEM_COUNT" >> "$GITHUB_OUTPUT"
1
+ # Managed by @plainconceptsplatform/workflows. Source: loops/actions/download-agent-output/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
+ name: Download agent output
3
+ description: Download the agent artifact and expose the output JSON and bundle paths.
4
+ inputs:
5
+ artifact-name:
6
+ description: >-
7
+ Artifact name to download. gh-aw prefixes it with the value it derives from the
8
+ workflow's inputs, so a workflow_call worker must pass the activation job's
9
+ artifact_prefix output followed by "agent", not the bare name.
10
+ required: true
11
+ outputs:
12
+ output-file:
13
+ description: Path to agent_output.json, or empty if not found.
14
+ value: ${{ steps.download.outputs.output-file }}
15
+ bundle-file:
16
+ description: Path to the git bundle, or empty if not found.
17
+ value: ${{ steps.download.outputs.bundle-file }}
18
+ item-count:
19
+ description: Number of items in the agent output.
20
+ value: ${{ steps.download.outputs.item-count }}
21
+ runs:
22
+ using: composite
23
+ steps:
24
+ - name: Download agent artifact
25
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
26
+ with:
27
+ name: ${{ inputs.artifact-name }}
28
+ path: agent-artifact
29
+ - name: Locate output and bundle
30
+ id: download
31
+ shell: bash
32
+ env:
33
+ ARTIFACT_NAME: ${{ inputs.artifact-name }}
34
+ run: |
35
+ set -euo pipefail
36
+ OUTPUT_FILE=""
37
+ BUNDLE_FILE=""
38
+ ITEM_COUNT="0"
39
+
40
+ if [ -f "agent-artifact/agent_output.json" ]; then
41
+ OUTPUT_FILE="agent-artifact/agent_output.json"
42
+ ITEM_COUNT=$(jq '.items | length' "$OUTPUT_FILE")
43
+ BUNDLE_FILE=$(ls agent-artifact/aw-*.bundle 2>/dev/null | head -1 || true)
44
+ else
45
+ echo "::error::Artifact '$ARTIFACT_NAME' contained no agent_output.json. Nothing the agent proposed will be applied."
46
+ exit 1
47
+ fi
48
+
49
+ echo "Artifact '$ARTIFACT_NAME' carries $ITEM_COUNT item(s)."
50
+
51
+ echo "output-file=$OUTPUT_FILE" >> "$GITHUB_OUTPUT"
52
+ echo "bundle-file=$BUNDLE_FILE" >> "$GITHUB_OUTPUT"
53
+ echo "item-count=$ITEM_COUNT" >> "$GITHUB_OUTPUT"
@@ -75,6 +75,12 @@ runs:
75
75
  const names = (issue) => issue.labels.map((l) => (typeof l === 'string' ? l : l.name));
76
76
 
77
77
  const needsHuman = [];
78
+ // Gate telemetry. Every disposition the gate has ever posted is already on a pull
79
+ // request, marked, so the rate costs one extra listing rather than a store.
80
+ const dispositions = {};
81
+ const autoMerged = [];
82
+ const reverts = [];
83
+ const metricsWindowMs = 14 * 24 * 3600000;
78
84
  // Stalled work still inside its retry window. Separate from needsHuman because nobody
79
85
  // needs to act on it yet, and reported anyway because the alternative is silence: the
80
86
  // window is 6 hours and the budget is 3 attempts, so an issue that keeps stalling was
@@ -203,14 +209,45 @@ runs:
203
209
  ...context.repo, issue_number: pull.number, per_page: 100,
204
210
  });
205
211
  const verdict = [...comments].reverse().find((c) => (c.body ?? '').includes('<!-- agent-merge-gate -->') && (c.body ?? '').includes('**Verdict:**'));
206
- const parsed = verdict ? /\*\*Verdict:\*\*\s*(\w+)/.exec(verdict.body)?.[1] : undefined;
207
- if (parsed && parsed !== 'merge') {
212
+ const parsed = verdict ? /\*\*Verdict:\*\*\s*([\w-]+)/.exec(verdict.body)?.[1] : undefined;
213
+ // The gate's dispositions are auto-merge, human-review, owner-review and blocked.
214
+ // Only the first needs nobody. The character class has to allow the hyphen or every
215
+ // disposition reads as the bare word "auto" or "human" and nothing matches.
216
+ if (parsed) dispositions[parsed] = (dispositions[parsed] ?? 0) + 1;
217
+ if (parsed && parsed !== 'auto-merge') {
208
218
  needsHuman.push({ kind: 'pr-parked', pull, verdict: parsed, waited: age(verdict.created_at) });
209
219
  } else if (!verdict && age(pull.created_at) > stalePrMs) {
210
220
  needsHuman.push({ kind: 'pr-stale', pull, waited: age(pull.created_at) });
211
221
  }
212
222
  }
213
223
 
224
+ // 5b. The one number this whole gate exists to move. Open pull requests alone would
225
+ // only ever count the parked ones, because an auto-merged pull request closes within
226
+ // seconds of its verdict, so the rate would read as zero forever. Closed pull requests
227
+ // from the digest window are where the merges are.
228
+ for (const pull of await github.paginate(github.rest.pulls.list, {
229
+ ...context.repo, state: 'closed', sort: 'updated', direction: 'desc', per_page: 100,
230
+ })) {
231
+ if (age(pull.updated_at) > metricsWindowMs) break;
232
+ // A revert names the pull request it undoes, which is how an escaped defect is
233
+ // attributed back to the disposition that let it through. Reverts are frequently
234
+ // opened by a person, so this one line sits above the bot filter.
235
+ if (pull.merged_at) {
236
+ const undone = /revert .*#(\d+)/i.exec(pull.title ?? '');
237
+ if (undone) reverts.push(Number(undone[1]));
238
+ }
239
+ if (!isBot(pull.user?.login ?? '')) continue;
240
+ const comments = await github.paginate(github.rest.issues.listComments, {
241
+ ...context.repo, issue_number: pull.number, per_page: 100,
242
+ });
243
+ const verdict = [...comments].reverse().find((c) => (c.body ?? '').includes('<!-- agent-merge-gate -->') && (c.body ?? '').includes('**Verdict:**'));
244
+ const parsed = verdict ? /\*\*Verdict:\*\*\s*([\w-]+)/.exec(verdict.body)?.[1] : undefined;
245
+ if (parsed) dispositions[parsed] = (dispositions[parsed] ?? 0) + 1;
246
+ // An auto-merge followed by a revert is the number that has to stay at zero while
247
+ // the rate climbs. Anything else is a rate bought with escaped defects.
248
+ if (parsed === 'auto-merge' && pull.merged_at) autoMerged.push(pull.number);
249
+ }
250
+
214
251
  // 6. One digest. Everything above either fixed itself or is listed here, so there is a
215
252
  // single place to look instead of four repositories and three label queries.
216
253
  // Audit reports nobody actioned. audit-close labels these `stale-audit` once they are
@@ -243,6 +280,21 @@ runs:
243
280
  `- #${entry.issue.number} · stalled ${hours(entry.waited)}h ago · attempt ${entry.attempts + 1} of ${maxRetries} due in ~${hours(entry.due)}h · ${entry.issue.title}`);
244
281
  return `\n### Retrying by itself, no action needed (${retrying.length})\n\n${rows.join('\n')}\n`;
245
282
  };
283
+ // The gate's own scoreboard. A digest that lists only what went wrong tells you
284
+ // nothing about whether the thing is getting better.
285
+ const gateSection = () => {
286
+ const total = Object.values(dispositions).reduce((a, b) => a + b, 0);
287
+ if (total === 0) return '';
288
+ const auto = dispositions['auto-merge'] ?? 0;
289
+ const rate = Math.round((auto / total) * 100);
290
+ const rows = Object.entries(dispositions)
291
+ .sort((a, b) => b[1] - a[1])
292
+ .map(([name, count]) => `- \`${name}\` · ${count}`);
293
+ const reverted = autoMerged.filter((number) => reverts.includes(number)).length;
294
+ return `\n### Merge gate, last ${metricsWindowMs / (24 * 3600000)} days\n\n` +
295
+ `**${rate}% auto-merged** (${auto} of ${total} dispositions).\n\n${rows.join('\n')}\n\n` +
296
+ `Auto-merged pull requests later reverted: ${reverted}.\n`;
297
+ };
246
298
  const body = [
247
299
  DIGEST_MARKER,
248
300
  `_Updated ${new Date().toISOString().slice(0, 16).replace('T', ' ')} UTC. This issue is rewritten by the housekeeping job; edits to it are lost._`,
@@ -251,6 +303,7 @@ runs:
251
303
  section('Stalled beyond the retry budget', ['retries-exhausted', 'no-work-label']),
252
304
  section('Audit reports nobody actioned', ['stale-audit']),
253
305
  retryingSection(),
306
+ gateSection(),
254
307
  needsHuman.length === 0 ? '\nNothing is waiting on a person right now.\n' : '',
255
308
  `\n---\n\nThis run retried ${retried} stalled issue(s) and made ${planned.length} change(s) in total.`,
256
309
  ].filter(Boolean).join('\n');
@@ -1,40 +1,40 @@
1
- # Managed by @plainconceptsplatform/workflows. Source: loops/actions/link-pr-to-issue/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
- name: Link pull request to issue
3
- description: Ensure the pull request body carries a closing reference to its source issue.
4
- inputs:
5
- token:
6
- description: GitHub token with pull-requests write access.
7
- required: true
8
- pr-number:
9
- description: Pull request number.
10
- required: true
11
- issue-number:
12
- description: Issue number the pull request should close.
13
- required: true
14
- runs:
15
- using: composite
16
- steps:
17
- - name: Ensure a closing reference
18
- shell: bash
19
- env:
20
- GH_TOKEN: ${{ inputs.token }}
21
- REPO: ${{ github.repository }}
22
- PR_NUMBER: ${{ inputs.pr-number }}
23
- ISSUE_NUMBER: ${{ inputs.issue-number }}
24
- run: |
25
- set -euo pipefail
26
-
27
- pr="$(gh pr view "$PR_NUMBER" --repo "$REPO" --json closingIssuesReferences,body)"
28
-
29
- if [ "$(jq '.closingIssuesReferences | length' <<<"$pr")" -gt 0 ]; then
30
- echo "PR #$PR_NUMBER already closes an issue."
31
- exit 0
32
- fi
33
-
34
- # A closing reference can only be created from the pull request body; there is no
35
- # REST or GraphQL mutation for it.
36
- body="$(jq -r '.body // ""' <<<"$pr")"
37
- printf '%s\n\nCloses #%s\n' "$body" "$ISSUE_NUMBER" > /tmp/pr-body.md
38
-
39
- gh pr edit "$PR_NUMBER" --repo "$REPO" --body-file /tmp/pr-body.md
40
- echo "Added 'Closes #$ISSUE_NUMBER' to PR #$PR_NUMBER."
1
+ # Managed by @plainconceptsplatform/workflows. Source: loops/actions/link-pr-to-issue/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
+ name: Link pull request to issue
3
+ description: Ensure the pull request body carries a closing reference to its source issue.
4
+ inputs:
5
+ token:
6
+ description: GitHub token with pull-requests write access.
7
+ required: true
8
+ pr-number:
9
+ description: Pull request number.
10
+ required: true
11
+ issue-number:
12
+ description: Issue number the pull request should close.
13
+ required: true
14
+ runs:
15
+ using: composite
16
+ steps:
17
+ - name: Ensure a closing reference
18
+ shell: bash
19
+ env:
20
+ GH_TOKEN: ${{ inputs.token }}
21
+ REPO: ${{ github.repository }}
22
+ PR_NUMBER: ${{ inputs.pr-number }}
23
+ ISSUE_NUMBER: ${{ inputs.issue-number }}
24
+ run: |
25
+ set -euo pipefail
26
+
27
+ pr="$(gh pr view "$PR_NUMBER" --repo "$REPO" --json closingIssuesReferences,body)"
28
+
29
+ if [ "$(jq '.closingIssuesReferences | length' <<<"$pr")" -gt 0 ]; then
30
+ echo "PR #$PR_NUMBER already closes an issue."
31
+ exit 0
32
+ fi
33
+
34
+ # A closing reference can only be created from the pull request body; there is no
35
+ # REST or GraphQL mutation for it.
36
+ body="$(jq -r '.body // ""' <<<"$pr")"
37
+ printf '%s\n\nCloses #%s\n' "$body" "$ISSUE_NUMBER" > /tmp/pr-body.md
38
+
39
+ gh pr edit "$PR_NUMBER" --repo "$REPO" --body-file /tmp/pr-body.md
40
+ echo "Added 'Closes #$ISSUE_NUMBER' to PR #$PR_NUMBER."
@@ -1,33 +1,33 @@
1
- # Managed by @plainconceptsplatform/workflows. Source: loops/actions/list-open-issues/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
- name: List open issues
3
- description: |
4
- Writes all open issues (number, title, labels) to `/tmp/gh-aw/agent/open-issues.json`
5
- so the agent can check for semantic duplicates without spending turns discovering them.
6
- inputs:
7
- token:
8
- description: GitHub token with issues:read permission.
9
- required: true
10
- repo:
11
- description: Repository in `owner/name` format.
12
- required: true
13
- output-path:
14
- description: Path to write the JSON output.
15
- required: false
16
- default: /tmp/gh-aw/agent/open-issues.json
17
- runs:
18
- using: composite
19
- steps:
20
- - name: List open issues
21
- shell: bash
22
- env:
23
- GH_TOKEN: ${{ inputs.token }}
24
- REPO: ${{ inputs.repo }}
25
- OUTPUT_PATH: ${{ inputs.output-path }}
26
- run: |
27
- set -euo pipefail
28
- mkdir -p "$(dirname "$OUTPUT_PATH")"
29
- gh issue list --repo "$REPO" --state open --limit 500 \
30
- --json number,title,labels \
31
- --jq '[.[] | {number, title, labels: [.labels[].name]}]' \
32
- > "$OUTPUT_PATH"
33
- echo "Wrote $(jq 'length' "$OUTPUT_PATH") open issues to $OUTPUT_PATH"
1
+ # Managed by @plainconceptsplatform/workflows. Source: loops/actions/list-open-issues/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
+ name: List open issues
3
+ description: |
4
+ Writes all open issues (number, title, labels) to `/tmp/gh-aw/agent/open-issues.json`
5
+ so the agent can check for semantic duplicates without spending turns discovering them.
6
+ inputs:
7
+ token:
8
+ description: GitHub token with issues:read permission.
9
+ required: true
10
+ repo:
11
+ description: Repository in `owner/name` format.
12
+ required: true
13
+ output-path:
14
+ description: Path to write the JSON output.
15
+ required: false
16
+ default: /tmp/gh-aw/agent/open-issues.json
17
+ runs:
18
+ using: composite
19
+ steps:
20
+ - name: List open issues
21
+ shell: bash
22
+ env:
23
+ GH_TOKEN: ${{ inputs.token }}
24
+ REPO: ${{ inputs.repo }}
25
+ OUTPUT_PATH: ${{ inputs.output-path }}
26
+ run: |
27
+ set -euo pipefail
28
+ mkdir -p "$(dirname "$OUTPUT_PATH")"
29
+ gh issue list --repo "$REPO" --state open --limit 500 \
30
+ --json number,title,labels \
31
+ --jq '[.[] | {number, title, labels: [.labels[].name]}]' \
32
+ > "$OUTPUT_PATH"
33
+ echo "Wrote $(jq 'length' "$OUTPUT_PATH") open issues to $OUTPUT_PATH"
@@ -1,45 +1,45 @@
1
- # Managed by @plainconceptsplatform/workflows. Source: loops/actions/load-issue-context/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
- name: Load issue context
3
- description: Write issue metadata and comment context to a JSON file.
4
- inputs:
5
- token:
6
- description: GitHub token used by github-script.
7
- required: true
8
- issue-number:
9
- description: Issue number to load.
10
- required: true
11
- output-path:
12
- description: Absolute path where context JSON is written.
13
- required: true
14
- runs:
15
- using: composite
16
- steps:
17
- - name: Load issue and comments
18
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
19
- env:
20
- ISSUE_NUMBER: ${{ inputs.issue-number }}
21
- OUTPUT_PATH: ${{ inputs.output-path }}
22
- with:
23
- github-token: ${{ inputs.token }}
24
- script: |
25
- const fs = require('fs');
26
- const issueNumber = Number(process.env.ISSUE_NUMBER);
27
- const issue = await github.rest.issues.get({ ...context.repo, issue_number: issueNumber });
28
- const comments = await github.paginate(github.rest.issues.listComments, {
29
- ...context.repo, issue_number: issueNumber, per_page: 100,
30
- });
31
-
32
- fs.mkdirSync(require('path').dirname(process.env.OUTPUT_PATH), { recursive: true });
33
- fs.writeFileSync(process.env.OUTPUT_PATH, JSON.stringify({
34
- number: issue.data.number,
35
- title: issue.data.title,
36
- body: issue.data.body,
37
- author: issue.data.user.login,
38
- authorAssociation: issue.data.author_association,
39
- labels: issue.data.labels.map(({ name }) => name),
40
- comments: comments.map(({ user, author_association, body }) => ({
41
- author: user.login,
42
- association: author_association,
43
- body,
44
- })),
45
- }, null, 2));
1
+ # Managed by @plainconceptsplatform/workflows. Source: loops/actions/load-issue-context/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
+ name: Load issue context
3
+ description: Write issue metadata and comment context to a JSON file.
4
+ inputs:
5
+ token:
6
+ description: GitHub token used by github-script.
7
+ required: true
8
+ issue-number:
9
+ description: Issue number to load.
10
+ required: true
11
+ output-path:
12
+ description: Absolute path where context JSON is written.
13
+ required: true
14
+ runs:
15
+ using: composite
16
+ steps:
17
+ - name: Load issue and comments
18
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
19
+ env:
20
+ ISSUE_NUMBER: ${{ inputs.issue-number }}
21
+ OUTPUT_PATH: ${{ inputs.output-path }}
22
+ with:
23
+ github-token: ${{ inputs.token }}
24
+ script: |
25
+ const fs = require('fs');
26
+ const issueNumber = Number(process.env.ISSUE_NUMBER);
27
+ const issue = await github.rest.issues.get({ ...context.repo, issue_number: issueNumber });
28
+ const comments = await github.paginate(github.rest.issues.listComments, {
29
+ ...context.repo, issue_number: issueNumber, per_page: 100,
30
+ });
31
+
32
+ fs.mkdirSync(require('path').dirname(process.env.OUTPUT_PATH), { recursive: true });
33
+ fs.writeFileSync(process.env.OUTPUT_PATH, JSON.stringify({
34
+ number: issue.data.number,
35
+ title: issue.data.title,
36
+ body: issue.data.body,
37
+ author: issue.data.user.login,
38
+ authorAssociation: issue.data.author_association,
39
+ labels: issue.data.labels.map(({ name }) => name),
40
+ comments: comments.map(({ user, author_association, body }) => ({
41
+ author: user.login,
42
+ association: author_association,
43
+ body,
44
+ })),
45
+ }, null, 2));
@@ -1,49 +1,49 @@
1
- # Managed by @plainconceptsplatform/workflows. Source: loops/actions/merge-agent-pr/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
- name: Merge agent pull request
3
- description: Read merge_pull_request from agent_output.json and merge the PR via gh.
4
- inputs:
5
- output-file:
6
- description: Path to agent_output.json.
7
- required: true
8
- token:
9
- description: GitHub token with pull-requests:write and contents:write.
10
- required: true
11
- repo:
12
- description: Target repository in owner/name format.
13
- required: false
14
- default: ${{ github.repository }}
15
- runs:
16
- using: composite
17
- steps:
18
- - name: Merge pull request
19
- shell: bash
20
- env:
21
- GH_TOKEN: ${{ inputs.token }}
22
- OUTPUT_FILE: ${{ inputs.output-file }}
23
- REPO: ${{ inputs.repo }}
24
- run: |
25
- set -euo pipefail
26
- [ -f "$OUTPUT_FILE" ] || exit 0
27
-
28
- jq -c '.items[] | select(.type == "merge_pull_request")' "$OUTPUT_FILE" | while read -r item; do
29
- PR_NUM=$(echo "$item" | jq -er '.pr_number // .item_number // empty')
30
- [[ "$PR_NUM" =~ ^[1-9][0-9]*$ ]] || { echo "::error::Invalid pull request number: $PR_NUM"; exit 1; }
31
-
32
- HEAD_SHA=$(gh pr view "$PR_NUM" --repo "$REPO" --json headRefOid --jq '.headRefOid')
33
- [[ "$HEAD_SHA" =~ ^[0-9a-fA-F]{40}$ ]] || { echo "::error::Invalid pull request head SHA for #$PR_NUM"; exit 1; }
34
-
35
- HEAD_REF=$(gh pr view "$PR_NUM" --repo "$REPO" --json headRefName --jq '.headRefName')
36
-
37
- gh pr merge "$PR_NUM" --repo "$REPO" --squash --match-head-commit "$HEAD_SHA"
38
-
39
- # Delete the branch, because nothing else ever has: four repositories had accumulated
40
- # 47 non-main branches, every one the head of a pull request merged or closed months
41
- # ago. Done after the merge and with its failure swallowed rather than through
42
- # `gh pr merge --delete-branch`, which also tries to delete the local branch and can
43
- # turn a completed merge into a red step. `delete_branch_on_merge` on the repository
44
- # does the same job, but only where someone remembered to switch it on.
45
- if [ -n "$HEAD_REF" ] && [ "$HEAD_REF" != "null" ]; then
46
- gh api -X DELETE "repos/$REPO/git/refs/heads/$HEAD_REF" >/dev/null 2>&1 ||
47
- echo "::notice::branch $HEAD_REF was already gone or is protected"
48
- fi
49
- done
1
+ # Managed by @plainconceptsplatform/workflows. Source: loops/actions/merge-agent-pr/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
+ name: Merge agent pull request
3
+ description: Read merge_pull_request from agent_output.json and merge the PR via gh.
4
+ inputs:
5
+ output-file:
6
+ description: Path to agent_output.json.
7
+ required: true
8
+ token:
9
+ description: GitHub token with pull-requests:write and contents:write.
10
+ required: true
11
+ repo:
12
+ description: Target repository in owner/name format.
13
+ required: false
14
+ default: ${{ github.repository }}
15
+ runs:
16
+ using: composite
17
+ steps:
18
+ - name: Merge pull request
19
+ shell: bash
20
+ env:
21
+ GH_TOKEN: ${{ inputs.token }}
22
+ OUTPUT_FILE: ${{ inputs.output-file }}
23
+ REPO: ${{ inputs.repo }}
24
+ run: |
25
+ set -euo pipefail
26
+ [ -f "$OUTPUT_FILE" ] || exit 0
27
+
28
+ jq -c '.items[] | select(.type == "merge_pull_request")' "$OUTPUT_FILE" | while read -r item; do
29
+ PR_NUM=$(echo "$item" | jq -er '.pr_number // .item_number // empty')
30
+ [[ "$PR_NUM" =~ ^[1-9][0-9]*$ ]] || { echo "::error::Invalid pull request number: $PR_NUM"; exit 1; }
31
+
32
+ HEAD_SHA=$(gh pr view "$PR_NUM" --repo "$REPO" --json headRefOid --jq '.headRefOid')
33
+ [[ "$HEAD_SHA" =~ ^[0-9a-fA-F]{40}$ ]] || { echo "::error::Invalid pull request head SHA for #$PR_NUM"; exit 1; }
34
+
35
+ HEAD_REF=$(gh pr view "$PR_NUM" --repo "$REPO" --json headRefName --jq '.headRefName')
36
+
37
+ gh pr merge "$PR_NUM" --repo "$REPO" --squash --match-head-commit "$HEAD_SHA"
38
+
39
+ # Delete the branch, because nothing else ever has: four repositories had accumulated
40
+ # 47 non-main branches, every one the head of a pull request merged or closed months
41
+ # ago. Done after the merge and with its failure swallowed rather than through
42
+ # `gh pr merge --delete-branch`, which also tries to delete the local branch and can
43
+ # turn a completed merge into a red step. `delete_branch_on_merge` on the repository
44
+ # does the same job, but only where someone remembered to switch it on.
45
+ if [ -n "$HEAD_REF" ] && [ "$HEAD_REF" != "null" ]; then
46
+ gh api -X DELETE "repos/$REPO/git/refs/heads/$HEAD_REF" >/dev/null 2>&1 ||
47
+ echo "::notice::branch $HEAD_REF was already gone or is protected"
48
+ fi
49
+ done
@@ -1,45 +1,45 @@
1
- # Managed by @plainconceptsplatform/workflows. Source: loops/actions/push-agent-branch/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
- name: Push agent branch
3
- description: Read push_to_pull_request_branch from agent_output.json and fast-forward from its git bundle.
4
- inputs:
5
- output-file:
6
- description: Path to agent_output.json.
7
- required: true
8
- bundle-file:
9
- description: Path to the git bundle from the agent artifact.
10
- required: false
11
- default: ''
12
- token:
13
- description: GitHub token with contents:write.
14
- required: true
15
- runs:
16
- using: composite
17
- steps:
18
- - name: Configure git credentials
19
- shell: bash
20
- env:
21
- GH_TOKEN: ${{ inputs.token }}
22
- run: |
23
- git config --global url."https://x-access-token:${GH_TOKEN}@github.com/".insteadOf "https://github.com/"
24
- - name: Read pull request branch
25
- id: request
26
- shell: bash
27
- env:
28
- GH_TOKEN: ${{ inputs.token }}
29
- OUTPUT_FILE: ${{ inputs.output-file }}
30
- BUNDLE_FILE: ${{ inputs.bundle-file }}
31
- run: |
32
- set -euo pipefail
33
- [ -f "$OUTPUT_FILE" ] || exit 0
34
-
35
- ITEM=$(jq -c '.items[] | select(.type == "push_to_pull_request_branch")' "$OUTPUT_FILE")
36
- [ -n "$ITEM" ] || exit 0
37
-
38
- BRANCH=$(echo "$ITEM" | jq -r '.branch')
39
- echo "branch=$BRANCH" >> "$GITHUB_OUTPUT"
40
- - name: Apply bundle to pull request branch
41
- if: steps.request.outputs.branch != '' && inputs.bundle-file != ''
42
- uses: ./.github/actions/apply-agent-bundle
43
- with:
44
- bundle-file: ${{ inputs.bundle-file }}
45
- target-branch: ${{ steps.request.outputs.branch }}
1
+ # Managed by @plainconceptsplatform/workflows. Source: loops/actions/push-agent-branch/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
+ name: Push agent branch
3
+ description: Read push_to_pull_request_branch from agent_output.json and fast-forward from its git bundle.
4
+ inputs:
5
+ output-file:
6
+ description: Path to agent_output.json.
7
+ required: true
8
+ bundle-file:
9
+ description: Path to the git bundle from the agent artifact.
10
+ required: false
11
+ default: ''
12
+ token:
13
+ description: GitHub token with contents:write.
14
+ required: true
15
+ runs:
16
+ using: composite
17
+ steps:
18
+ - name: Configure git credentials
19
+ shell: bash
20
+ env:
21
+ GH_TOKEN: ${{ inputs.token }}
22
+ run: |
23
+ git config --global url."https://x-access-token:${GH_TOKEN}@github.com/".insteadOf "https://github.com/"
24
+ - name: Read pull request branch
25
+ id: request
26
+ shell: bash
27
+ env:
28
+ GH_TOKEN: ${{ inputs.token }}
29
+ OUTPUT_FILE: ${{ inputs.output-file }}
30
+ BUNDLE_FILE: ${{ inputs.bundle-file }}
31
+ run: |
32
+ set -euo pipefail
33
+ [ -f "$OUTPUT_FILE" ] || exit 0
34
+
35
+ ITEM=$(jq -c '.items[] | select(.type == "push_to_pull_request_branch")' "$OUTPUT_FILE")
36
+ [ -n "$ITEM" ] || exit 0
37
+
38
+ BRANCH=$(echo "$ITEM" | jq -r '.branch')
39
+ echo "branch=$BRANCH" >> "$GITHUB_OUTPUT"
40
+ - name: Apply bundle to pull request branch
41
+ if: steps.request.outputs.branch != '' && inputs.bundle-file != ''
42
+ uses: ./.github/actions/apply-agent-bundle
43
+ with:
44
+ bundle-file: ${{ inputs.bundle-file }}
45
+ target-branch: ${{ steps.request.outputs.branch }}