@plainconceptsplatform/workflows 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/catalog-installation.d.ts +12 -0
- package/dist/catalog-installation.js +81 -0
- package/dist/catalog-installation.test.d.ts +1 -0
- package/dist/catalog-installation.test.js +85 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +57 -0
- package/dist/repository-inspection.d.ts +23 -0
- package/dist/repository-inspection.js +91 -0
- package/dist/repository-state.d.ts +18 -0
- package/dist/repository-state.js +77 -0
- package/dist/repository-state.test.d.ts +1 -0
- package/dist/repository-state.test.js +96 -0
- package/dist/workflow-catalog.d.ts +17 -0
- package/dist/workflow-catalog.js +39 -0
- package/dist/workflow-catalog.test.d.ts +1 -0
- package/dist/workflow-catalog.test.js +14 -0
- package/loops/actions/add-issue-labels/action.yml +29 -0
- package/loops/actions/agent-output.cjs +16 -0
- package/loops/actions/apply-agent-bundle/action.yml +23 -0
- package/loops/actions/apply-agent-bundle/apply-bundle.sh +45 -0
- package/loops/actions/apply-agent-comments/action.yml +41 -0
- package/loops/actions/apply-agent-labels/action.yml +54 -0
- package/loops/actions/apply-agent-output/action.yml +107 -0
- package/loops/actions/audit-close/action.yml +103 -0
- package/loops/actions/classify-route/action.yml +90 -0
- package/loops/actions/classify-route/classify-route.sh +172 -0
- package/loops/actions/cleanup-artifacts/action.yml +64 -0
- package/loops/actions/close-agent-issues/action.yml +42 -0
- package/loops/actions/create-agent-issues/action.yml +51 -0
- package/loops/actions/create-issue-comment/action.yml +28 -0
- package/loops/actions/download-agent-output/action.yml +52 -0
- package/loops/actions/identify-gate-subject/action.yml +96 -0
- package/loops/actions/link-pr-to-issue/action.yml +39 -0
- package/loops/actions/list-open-issues/action.yml +32 -0
- package/loops/actions/load-issue-context/action.yml +42 -0
- package/loops/actions/merge-agent-pr/action.yml +35 -0
- package/loops/actions/push-agent-branch/action.yml +44 -0
- package/loops/actions/remove-issue-labels/action.yml +34 -0
- package/loops/actions/stale-recovery/action.yml +287 -0
- package/loops/actions/update-agent-issues/action.yml +57 -0
- package/loops/actions/validate-refine-output/action.yml +43 -0
- package/loops/actions/validate-refine-output/validate-refine-output.sh +46 -0
- package/loops/actions/verify-composite-actions/action.yml +8 -0
- package/loops/actions/verify-composite-actions/verify-composite-actions.sh +50 -0
- package/loops/actions/verify-refine-output/action.yml +8 -0
- package/loops/actions/verify-refine-output/verify-refine-output.sh +65 -0
- package/loops/actions/verify-route-matrix/action.yml +8 -0
- package/loops/actions/verify-route-matrix/verify-route-matrix.sh +212 -0
- package/loops/aw/repo-config.md +12 -0
- package/loops/scripts/compile-agent-workflows.mjs +13 -0
- package/loops/workflows/agent-apply-review.md +372 -0
- package/loops/workflows/agent-audit.md +197 -0
- package/loops/workflows/agent-direct.md +362 -0
- package/loops/workflows/agent-implement.md +329 -0
- package/loops/workflows/agent-merge-gate.md +470 -0
- package/loops/workflows/agent-propose.md +342 -0
- package/loops/workflows/agent-refine.md +338 -0
- package/loops/workflows/shared/opencode-ci.md +26 -0
- package/loops/workflows/shared/platform-defaults.md +14 -0
- package/loops/workflows/work-router.yml +391 -0
- package/package.json +28 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
name: Clean up artifacts
|
|
2
|
+
description: Delete workflow artifacts older than the configured retention period.
|
|
3
|
+
inputs:
|
|
4
|
+
token:
|
|
5
|
+
description: GitHub token with actions write access.
|
|
6
|
+
required: true
|
|
7
|
+
artifact-retention-days:
|
|
8
|
+
description: Retention period in days. Invalid or empty values use 3.
|
|
9
|
+
required: false
|
|
10
|
+
default: ''
|
|
11
|
+
runs:
|
|
12
|
+
using: composite
|
|
13
|
+
steps:
|
|
14
|
+
- name: Delete expired artifacts
|
|
15
|
+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
16
|
+
env:
|
|
17
|
+
ARTIFACT_RETENTION_DAYS: ${{ inputs.artifact-retention-days }}
|
|
18
|
+
with:
|
|
19
|
+
github-token: ${{ inputs.token }}
|
|
20
|
+
script: |
|
|
21
|
+
const parsedDays = Number.parseInt(process.env.ARTIFACT_RETENTION_DAYS, 10);
|
|
22
|
+
const days = Number.isInteger(parsedDays) && parsedDays > 0 ? parsedDays : 3;
|
|
23
|
+
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
|
|
24
|
+
|
|
25
|
+
core.info(`Retention: ${days} days (cutoff ${cutoff.toISOString()})`);
|
|
26
|
+
|
|
27
|
+
const artifacts = await github.paginate(github.rest.actions.listArtifactsForRepo, {
|
|
28
|
+
...context.repo,
|
|
29
|
+
per_page: 100,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const expired = artifacts.filter((artifact) => new Date(artifact.created_at) < cutoff);
|
|
33
|
+
|
|
34
|
+
let deleted = 0;
|
|
35
|
+
const failed = [];
|
|
36
|
+
|
|
37
|
+
for (const artifact of expired) {
|
|
38
|
+
try {
|
|
39
|
+
await github.rest.actions.deleteArtifact({
|
|
40
|
+
...context.repo,
|
|
41
|
+
artifact_id: artifact.id,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
core.info(`Deleted ${artifact.name} (created ${artifact.created_at})`);
|
|
45
|
+
deleted += 1;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
core.warning(`Could not delete ${artifact.name} (${artifact.id}): ${error.message}`);
|
|
48
|
+
failed.push(artifact.name);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
await core.summary
|
|
53
|
+
.addHeading('Artifact cleanup', 2)
|
|
54
|
+
.addList([
|
|
55
|
+
`Retention: ${days} days (cutoff ${cutoff.toISOString()})`,
|
|
56
|
+
`Deleted: ${deleted}`,
|
|
57
|
+
`Failed to delete: ${failed.length}`,
|
|
58
|
+
`Within retention: ${artifacts.length - expired.length}`,
|
|
59
|
+
])
|
|
60
|
+
.write();
|
|
61
|
+
|
|
62
|
+
if (failed.length > 0) {
|
|
63
|
+
core.setFailed(`${failed.length} artifact(s) could not be deleted: ${failed.join(', ')}`);
|
|
64
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: Close agent issues
|
|
2
|
+
description: Close every close_issue item from agent_output.json.
|
|
3
|
+
inputs:
|
|
4
|
+
output-file:
|
|
5
|
+
description: Path to agent_output.json.
|
|
6
|
+
required: true
|
|
7
|
+
token:
|
|
8
|
+
description: GitHub token with issues:write.
|
|
9
|
+
required: true
|
|
10
|
+
runs:
|
|
11
|
+
using: composite
|
|
12
|
+
steps:
|
|
13
|
+
- name: Close issues
|
|
14
|
+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
15
|
+
env:
|
|
16
|
+
AGENT_OUTPUT_LIB: ${{ github.action_path }}/../agent-output.cjs
|
|
17
|
+
OUTPUT_FILE: ${{ inputs.output-file }}
|
|
18
|
+
with:
|
|
19
|
+
github-token: ${{ inputs.token }}
|
|
20
|
+
script: |
|
|
21
|
+
const { readAgentItems } = require(process.env.AGENT_OUTPUT_LIB);
|
|
22
|
+
const items = readAgentItems(process.env.OUTPUT_FILE, 'close_issue');
|
|
23
|
+
|
|
24
|
+
let closed = 0;
|
|
25
|
+
|
|
26
|
+
for (const item of items) {
|
|
27
|
+
if (!item.item_number) {
|
|
28
|
+
core.warning(`close_issue item has no item_number, skipping: ${JSON.stringify(item)}`);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
await github.rest.issues.update({
|
|
33
|
+
...context.repo,
|
|
34
|
+
issue_number: Number(item.item_number),
|
|
35
|
+
state: 'closed',
|
|
36
|
+
state_reason: item.state_reason === 'not_planned' ? 'not_planned' : 'completed',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
closed += 1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
core.info(`Closed ${closed} issue(s).`);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
name: Create agent issues
|
|
2
|
+
description: Create every create_issue item from agent_output.json.
|
|
3
|
+
inputs:
|
|
4
|
+
output-file:
|
|
5
|
+
description: Path to agent_output.json.
|
|
6
|
+
required: true
|
|
7
|
+
token:
|
|
8
|
+
description: GitHub token with issues:write.
|
|
9
|
+
required: true
|
|
10
|
+
outputs:
|
|
11
|
+
created-count:
|
|
12
|
+
description: Number of issues created.
|
|
13
|
+
value: ${{ steps.create.outputs.created-count }}
|
|
14
|
+
first-issue-number:
|
|
15
|
+
description: Issue number of the first issue created, or empty.
|
|
16
|
+
value: ${{ steps.create.outputs.first-issue-number }}
|
|
17
|
+
runs:
|
|
18
|
+
using: composite
|
|
19
|
+
steps:
|
|
20
|
+
- name: Create issues
|
|
21
|
+
id: create
|
|
22
|
+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
23
|
+
env:
|
|
24
|
+
AGENT_OUTPUT_LIB: ${{ github.action_path }}/../agent-output.cjs
|
|
25
|
+
OUTPUT_FILE: ${{ inputs.output-file }}
|
|
26
|
+
with:
|
|
27
|
+
github-token: ${{ inputs.token }}
|
|
28
|
+
script: |
|
|
29
|
+
const { readAgentItems } = require(process.env.AGENT_OUTPUT_LIB);
|
|
30
|
+
const items = readAgentItems(process.env.OUTPUT_FILE, 'create_issue');
|
|
31
|
+
|
|
32
|
+
const created = [];
|
|
33
|
+
|
|
34
|
+
for (const item of items) {
|
|
35
|
+
if (!item.title) {
|
|
36
|
+
core.warning(`create_issue item has no title, skipping: ${JSON.stringify(item)}`);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const { data } = await github.rest.issues.create({
|
|
41
|
+
...context.repo,
|
|
42
|
+
title: item.title,
|
|
43
|
+
body: item.body ?? '',
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
created.push(data.number);
|
|
47
|
+
core.info(`Created #${data.number}: ${item.title}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
core.setOutput('created-count', String(created.length));
|
|
51
|
+
core.setOutput('first-issue-number', created.length > 0 ? String(created[0]) : '');
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Create issue comment
|
|
2
|
+
description: Create a comment on an issue or pull request.
|
|
3
|
+
inputs:
|
|
4
|
+
token:
|
|
5
|
+
description: GitHub token used by github-script.
|
|
6
|
+
required: true
|
|
7
|
+
issue-number:
|
|
8
|
+
description: Issue or pull request number.
|
|
9
|
+
required: true
|
|
10
|
+
body:
|
|
11
|
+
description: Complete Markdown comment body.
|
|
12
|
+
required: true
|
|
13
|
+
runs:
|
|
14
|
+
using: composite
|
|
15
|
+
steps:
|
|
16
|
+
- name: Create comment
|
|
17
|
+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
18
|
+
env:
|
|
19
|
+
ISSUE_NUMBER: ${{ inputs.issue-number }}
|
|
20
|
+
BODY: ${{ inputs.body }}
|
|
21
|
+
with:
|
|
22
|
+
github-token: ${{ inputs.token }}
|
|
23
|
+
script: |
|
|
24
|
+
await github.rest.issues.createComment({
|
|
25
|
+
...context.repo,
|
|
26
|
+
issue_number: Number(process.env.ISSUE_NUMBER),
|
|
27
|
+
body: process.env.BODY,
|
|
28
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
name: Download agent output
|
|
2
|
+
description: Download the agent artifact and expose the output JSON and bundle paths.
|
|
3
|
+
inputs:
|
|
4
|
+
artifact-name:
|
|
5
|
+
description: >-
|
|
6
|
+
Artifact name to download. gh-aw prefixes it with the value it derives from the
|
|
7
|
+
workflow's inputs, so a workflow_call worker must pass the activation job's
|
|
8
|
+
artifact_prefix output followed by "agent", not the bare name.
|
|
9
|
+
required: true
|
|
10
|
+
outputs:
|
|
11
|
+
output-file:
|
|
12
|
+
description: Path to agent_output.json, or empty if not found.
|
|
13
|
+
value: ${{ steps.download.outputs.output-file }}
|
|
14
|
+
bundle-file:
|
|
15
|
+
description: Path to the git bundle, or empty if not found.
|
|
16
|
+
value: ${{ steps.download.outputs.bundle-file }}
|
|
17
|
+
item-count:
|
|
18
|
+
description: Number of items in the agent output.
|
|
19
|
+
value: ${{ steps.download.outputs.item-count }}
|
|
20
|
+
runs:
|
|
21
|
+
using: composite
|
|
22
|
+
steps:
|
|
23
|
+
- name: Download agent artifact
|
|
24
|
+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
25
|
+
with:
|
|
26
|
+
name: ${{ inputs.artifact-name }}
|
|
27
|
+
path: agent-artifact
|
|
28
|
+
- name: Locate output and bundle
|
|
29
|
+
id: download
|
|
30
|
+
shell: bash
|
|
31
|
+
env:
|
|
32
|
+
ARTIFACT_NAME: ${{ inputs.artifact-name }}
|
|
33
|
+
run: |
|
|
34
|
+
set -euo pipefail
|
|
35
|
+
OUTPUT_FILE=""
|
|
36
|
+
BUNDLE_FILE=""
|
|
37
|
+
ITEM_COUNT="0"
|
|
38
|
+
|
|
39
|
+
if [ -f "agent-artifact/agent_output.json" ]; then
|
|
40
|
+
OUTPUT_FILE="agent-artifact/agent_output.json"
|
|
41
|
+
ITEM_COUNT=$(jq '.items | length' "$OUTPUT_FILE")
|
|
42
|
+
BUNDLE_FILE=$(ls agent-artifact/aw-*.bundle 2>/dev/null | head -1 || true)
|
|
43
|
+
else
|
|
44
|
+
echo "::error::Artifact '$ARTIFACT_NAME' contained no agent_output.json. Nothing the agent proposed will be applied."
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
echo "Artifact '$ARTIFACT_NAME' carries $ITEM_COUNT item(s)."
|
|
49
|
+
|
|
50
|
+
echo "output-file=$OUTPUT_FILE" >> "$GITHUB_OUTPUT"
|
|
51
|
+
echo "bundle-file=$BUNDLE_FILE" >> "$GITHUB_OUTPUT"
|
|
52
|
+
echo "item-count=$ITEM_COUNT" >> "$GITHUB_OUTPUT"
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
name: Identify gate subject
|
|
2
|
+
description: Confirm the pull request is one we authored, resolve the issue it closes, and check that issue carries the required label.
|
|
3
|
+
inputs:
|
|
4
|
+
token:
|
|
5
|
+
description: GitHub token with issues and pull-requests read access.
|
|
6
|
+
required: true
|
|
7
|
+
pr-number:
|
|
8
|
+
description: Pull request number to gate.
|
|
9
|
+
required: true
|
|
10
|
+
ci-conclusion:
|
|
11
|
+
description: Conclusion reported by the CI run that triggered this gate.
|
|
12
|
+
required: true
|
|
13
|
+
linked-issue:
|
|
14
|
+
description: Issue the pull request closes, when the router already resolved it.
|
|
15
|
+
required: false
|
|
16
|
+
default: ''
|
|
17
|
+
require-label:
|
|
18
|
+
description: Label the closing issue must carry. Empty disables the check.
|
|
19
|
+
required: false
|
|
20
|
+
default: ''
|
|
21
|
+
outputs:
|
|
22
|
+
found:
|
|
23
|
+
description: Whether a valid gate subject was found.
|
|
24
|
+
value: ${{ steps.identify.outputs.found }}
|
|
25
|
+
pr:
|
|
26
|
+
description: Pull request number.
|
|
27
|
+
value: ${{ steps.identify.outputs.pr }}
|
|
28
|
+
issue:
|
|
29
|
+
description: Issue number the pull request closes.
|
|
30
|
+
value: ${{ steps.identify.outputs.issue }}
|
|
31
|
+
conclusion:
|
|
32
|
+
description: CI conclusion, passed through unchanged.
|
|
33
|
+
value: ${{ steps.identify.outputs.conclusion }}
|
|
34
|
+
runs:
|
|
35
|
+
using: composite
|
|
36
|
+
steps:
|
|
37
|
+
- name: Identify pull request and closing issue
|
|
38
|
+
id: identify
|
|
39
|
+
shell: bash
|
|
40
|
+
env:
|
|
41
|
+
GH_TOKEN: ${{ inputs.token }}
|
|
42
|
+
REPO: ${{ github.repository }}
|
|
43
|
+
PR_NUMBER: ${{ inputs.pr-number }}
|
|
44
|
+
CONCLUSION: ${{ inputs.ci-conclusion }}
|
|
45
|
+
LINKED_ISSUE: ${{ inputs.linked-issue }}
|
|
46
|
+
REQUIRE_LABEL: ${{ inputs.require-label }}
|
|
47
|
+
run: |
|
|
48
|
+
set -euo pipefail
|
|
49
|
+
|
|
50
|
+
none() {
|
|
51
|
+
echo "found=false" >> "$GITHUB_OUTPUT"
|
|
52
|
+
echo "$1"
|
|
53
|
+
exit 0
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
pr="$(gh pr view "$PR_NUMBER" --repo "$REPO" \
|
|
57
|
+
--json state,author,closingIssuesReferences,body)"
|
|
58
|
+
|
|
59
|
+
[ "$(jq -r '.state' <<<"$pr")" = "OPEN" ] || none "PR #$PR_NUMBER is not open"
|
|
60
|
+
|
|
61
|
+
# Use GitHub's bot flag because App login formats differ across APIs.
|
|
62
|
+
if [ "$(jq -r '.author.is_bot' <<<"$pr")" != "true" ]; then
|
|
63
|
+
none "PR #$PR_NUMBER was authored by $(jq -r '.author.login' <<<"$pr"), a human."
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
issue="$LINKED_ISSUE"
|
|
67
|
+
|
|
68
|
+
if [ -z "$issue" ]; then
|
|
69
|
+
issue="$(jq -r '.closingIssuesReferences[0].number // empty' <<<"$pr")"
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
if [ -z "$issue" ]; then
|
|
73
|
+
issue="$(jq -r '.body // ""' <<<"$pr" |
|
|
74
|
+
grep -oiE '(close[sd]?|fixe?[sd]?|resolve[sd]?) +#[0-9]+' |
|
|
75
|
+
grep -oE '[0-9]+' | head -n 1 || true)"
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
[ -n "$issue" ] || none "PR #$PR_NUMBER does not close a numbered issue"
|
|
79
|
+
|
|
80
|
+
if [ -n "$REQUIRE_LABEL" ]; then
|
|
81
|
+
labels="$(gh issue view "$issue" --repo "$REPO" --json labels \
|
|
82
|
+
--jq '[.labels[].name]')"
|
|
83
|
+
|
|
84
|
+
if ! jq -e --arg name "$REQUIRE_LABEL" 'index($name)' >/dev/null <<<"$labels"; then
|
|
85
|
+
none "issue #$issue does not carry $REQUIRE_LABEL (has: $(jq -r 'join(", ")' <<<"$labels"))"
|
|
86
|
+
fi
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
{
|
|
90
|
+
echo "found=true"
|
|
91
|
+
echo "pr=$PR_NUMBER"
|
|
92
|
+
echo "issue=$issue"
|
|
93
|
+
echo "conclusion=$CONCLUSION"
|
|
94
|
+
} >> "$GITHUB_OUTPUT"
|
|
95
|
+
|
|
96
|
+
echo "PR #$PR_NUMBER closes #$issue; CI concluded $CONCLUSION"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: Link pull request to issue
|
|
2
|
+
description: Ensure the pull request body carries a closing reference to its source issue.
|
|
3
|
+
inputs:
|
|
4
|
+
token:
|
|
5
|
+
description: GitHub token with pull-requests write access.
|
|
6
|
+
required: true
|
|
7
|
+
pr-number:
|
|
8
|
+
description: Pull request number.
|
|
9
|
+
required: true
|
|
10
|
+
issue-number:
|
|
11
|
+
description: Issue number the pull request should close.
|
|
12
|
+
required: true
|
|
13
|
+
runs:
|
|
14
|
+
using: composite
|
|
15
|
+
steps:
|
|
16
|
+
- name: Ensure a closing reference
|
|
17
|
+
shell: bash
|
|
18
|
+
env:
|
|
19
|
+
GH_TOKEN: ${{ inputs.token }}
|
|
20
|
+
REPO: ${{ github.repository }}
|
|
21
|
+
PR_NUMBER: ${{ inputs.pr-number }}
|
|
22
|
+
ISSUE_NUMBER: ${{ inputs.issue-number }}
|
|
23
|
+
run: |
|
|
24
|
+
set -euo pipefail
|
|
25
|
+
|
|
26
|
+
pr="$(gh pr view "$PR_NUMBER" --repo "$REPO" --json closingIssuesReferences,body)"
|
|
27
|
+
|
|
28
|
+
if [ "$(jq '.closingIssuesReferences | length' <<<"$pr")" -gt 0 ]; then
|
|
29
|
+
echo "PR #$PR_NUMBER already closes an issue."
|
|
30
|
+
exit 0
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
# A closing reference can only be created from the pull request body; there is no
|
|
34
|
+
# REST or GraphQL mutation for it.
|
|
35
|
+
body="$(jq -r '.body // ""' <<<"$pr")"
|
|
36
|
+
printf '%s\n\nCloses #%s\n' "$body" "$ISSUE_NUMBER" > /tmp/pr-body.md
|
|
37
|
+
|
|
38
|
+
gh pr edit "$PR_NUMBER" --repo "$REPO" --body-file /tmp/pr-body.md
|
|
39
|
+
echo "Added 'Closes #$ISSUE_NUMBER' to PR #$PR_NUMBER."
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: List open issues
|
|
2
|
+
description: |
|
|
3
|
+
Writes all open issues (number, title, labels) to `/tmp/gh-aw/agent/open-issues.json`
|
|
4
|
+
so the agent can check for semantic duplicates without spending turns discovering them.
|
|
5
|
+
inputs:
|
|
6
|
+
token:
|
|
7
|
+
description: GitHub token with issues:read permission.
|
|
8
|
+
required: true
|
|
9
|
+
repo:
|
|
10
|
+
description: Repository in `owner/name` format.
|
|
11
|
+
required: true
|
|
12
|
+
output-path:
|
|
13
|
+
description: Path to write the JSON output.
|
|
14
|
+
required: false
|
|
15
|
+
default: /tmp/gh-aw/agent/open-issues.json
|
|
16
|
+
runs:
|
|
17
|
+
using: composite
|
|
18
|
+
steps:
|
|
19
|
+
- name: List open issues
|
|
20
|
+
shell: bash
|
|
21
|
+
env:
|
|
22
|
+
GH_TOKEN: ${{ inputs.token }}
|
|
23
|
+
REPO: ${{ inputs.repo }}
|
|
24
|
+
OUTPUT_PATH: ${{ inputs.output-path }}
|
|
25
|
+
run: |
|
|
26
|
+
set -euo pipefail
|
|
27
|
+
mkdir -p "$(dirname "$OUTPUT_PATH")"
|
|
28
|
+
gh issue list --repo "$REPO" --state open --limit 500 \
|
|
29
|
+
--json number,title,labels \
|
|
30
|
+
--jq '[.[] | {number, title, labels: [.labels[].name]}]' \
|
|
31
|
+
> "$OUTPUT_PATH"
|
|
32
|
+
echo "Wrote $(jq 'length' "$OUTPUT_PATH") open issues to $OUTPUT_PATH"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: Load issue context
|
|
2
|
+
description: Write issue metadata and comment context to a JSON file.
|
|
3
|
+
inputs:
|
|
4
|
+
token:
|
|
5
|
+
description: GitHub token used by github-script.
|
|
6
|
+
required: true
|
|
7
|
+
issue-number:
|
|
8
|
+
description: Issue number to load.
|
|
9
|
+
required: true
|
|
10
|
+
output-path:
|
|
11
|
+
description: Absolute path where context JSON is written.
|
|
12
|
+
required: true
|
|
13
|
+
runs:
|
|
14
|
+
using: composite
|
|
15
|
+
steps:
|
|
16
|
+
- name: Load issue and comments
|
|
17
|
+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
18
|
+
env:
|
|
19
|
+
ISSUE_NUMBER: ${{ inputs.issue-number }}
|
|
20
|
+
OUTPUT_PATH: ${{ inputs.output-path }}
|
|
21
|
+
with:
|
|
22
|
+
github-token: ${{ inputs.token }}
|
|
23
|
+
script: |
|
|
24
|
+
const fs = require('fs');
|
|
25
|
+
const issueNumber = Number(process.env.ISSUE_NUMBER);
|
|
26
|
+
const issue = await github.rest.issues.get({ ...context.repo, issue_number: issueNumber });
|
|
27
|
+
const comments = await github.paginate(github.rest.issues.listComments, {
|
|
28
|
+
...context.repo, issue_number: issueNumber, per_page: 100,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
fs.mkdirSync(require('path').dirname(process.env.OUTPUT_PATH), { recursive: true });
|
|
32
|
+
fs.writeFileSync(process.env.OUTPUT_PATH, JSON.stringify({
|
|
33
|
+
number: issue.data.number,
|
|
34
|
+
title: issue.data.title,
|
|
35
|
+
body: issue.data.body,
|
|
36
|
+
labels: issue.data.labels.map(({ name }) => name),
|
|
37
|
+
comments: comments.map(({ user, author_association, body }) => ({
|
|
38
|
+
author: user.login,
|
|
39
|
+
association: author_association,
|
|
40
|
+
body,
|
|
41
|
+
})),
|
|
42
|
+
}, null, 2));
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: Merge agent pull request
|
|
2
|
+
description: Read merge_pull_request from agent_output.json and merge the PR via gh.
|
|
3
|
+
inputs:
|
|
4
|
+
output-file:
|
|
5
|
+
description: Path to agent_output.json.
|
|
6
|
+
required: true
|
|
7
|
+
token:
|
|
8
|
+
description: GitHub token with pull-requests:write and contents:write.
|
|
9
|
+
required: true
|
|
10
|
+
repo:
|
|
11
|
+
description: Target repository in owner/name format.
|
|
12
|
+
required: false
|
|
13
|
+
default: ${{ github.repository }}
|
|
14
|
+
runs:
|
|
15
|
+
using: composite
|
|
16
|
+
steps:
|
|
17
|
+
- name: Merge pull request
|
|
18
|
+
shell: bash
|
|
19
|
+
env:
|
|
20
|
+
GH_TOKEN: ${{ inputs.token }}
|
|
21
|
+
OUTPUT_FILE: ${{ inputs.output-file }}
|
|
22
|
+
REPO: ${{ inputs.repo }}
|
|
23
|
+
run: |
|
|
24
|
+
set -euo pipefail
|
|
25
|
+
[ -f "$OUTPUT_FILE" ] || exit 0
|
|
26
|
+
|
|
27
|
+
jq -c '.items[] | select(.type == "merge_pull_request")' "$OUTPUT_FILE" | while read -r item; do
|
|
28
|
+
PR_NUM=$(echo "$item" | jq -er '.pr_number // .item_number // empty')
|
|
29
|
+
[[ "$PR_NUM" =~ ^[1-9][0-9]*$ ]] || { echo "::error::Invalid pull request number: $PR_NUM"; exit 1; }
|
|
30
|
+
|
|
31
|
+
HEAD_SHA=$(gh pr view "$PR_NUM" --repo "$REPO" --json headRefOid --jq '.headRefOid')
|
|
32
|
+
[[ "$HEAD_SHA" =~ ^[0-9a-fA-F]{40}$ ]] || { echo "::error::Invalid pull request head SHA for #$PR_NUM"; exit 1; }
|
|
33
|
+
|
|
34
|
+
gh pr merge "$PR_NUM" --repo "$REPO" --squash --match-head-commit "$HEAD_SHA"
|
|
35
|
+
done
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: Push agent branch
|
|
2
|
+
description: Read push_to_pull_request_branch from agent_output.json and fast-forward from its git bundle.
|
|
3
|
+
inputs:
|
|
4
|
+
output-file:
|
|
5
|
+
description: Path to agent_output.json.
|
|
6
|
+
required: true
|
|
7
|
+
bundle-file:
|
|
8
|
+
description: Path to the git bundle from the agent artifact.
|
|
9
|
+
required: false
|
|
10
|
+
default: ''
|
|
11
|
+
token:
|
|
12
|
+
description: GitHub token with contents:write.
|
|
13
|
+
required: true
|
|
14
|
+
runs:
|
|
15
|
+
using: composite
|
|
16
|
+
steps:
|
|
17
|
+
- name: Configure git credentials
|
|
18
|
+
shell: bash
|
|
19
|
+
env:
|
|
20
|
+
GH_TOKEN: ${{ inputs.token }}
|
|
21
|
+
run: |
|
|
22
|
+
git config --global url."https://x-access-token:${GH_TOKEN}@github.com/".insteadOf "https://github.com/"
|
|
23
|
+
- name: Read pull request branch
|
|
24
|
+
id: request
|
|
25
|
+
shell: bash
|
|
26
|
+
env:
|
|
27
|
+
GH_TOKEN: ${{ inputs.token }}
|
|
28
|
+
OUTPUT_FILE: ${{ inputs.output-file }}
|
|
29
|
+
BUNDLE_FILE: ${{ inputs.bundle-file }}
|
|
30
|
+
run: |
|
|
31
|
+
set -euo pipefail
|
|
32
|
+
[ -f "$OUTPUT_FILE" ] || exit 0
|
|
33
|
+
|
|
34
|
+
ITEM=$(jq -c '.items[] | select(.type == "push_to_pull_request_branch")' "$OUTPUT_FILE")
|
|
35
|
+
[ -n "$ITEM" ] || exit 0
|
|
36
|
+
|
|
37
|
+
BRANCH=$(echo "$ITEM" | jq -r '.branch')
|
|
38
|
+
echo "branch=$BRANCH" >> "$GITHUB_OUTPUT"
|
|
39
|
+
- name: Apply bundle to pull request branch
|
|
40
|
+
if: steps.request.outputs.branch != '' && inputs.bundle-file != ''
|
|
41
|
+
uses: ./.github/actions/apply-agent-bundle
|
|
42
|
+
with:
|
|
43
|
+
bundle-file: ${{ inputs.bundle-file }}
|
|
44
|
+
target-branch: ${{ steps.request.outputs.branch }}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: Remove issue labels
|
|
2
|
+
description: Remove one or more labels from an issue or pull request.
|
|
3
|
+
inputs:
|
|
4
|
+
token:
|
|
5
|
+
description: GitHub token used by github-script.
|
|
6
|
+
required: true
|
|
7
|
+
issue-number:
|
|
8
|
+
description: Issue or pull request number.
|
|
9
|
+
required: true
|
|
10
|
+
labels:
|
|
11
|
+
description: Newline-delimited labels to remove.
|
|
12
|
+
required: true
|
|
13
|
+
runs:
|
|
14
|
+
using: composite
|
|
15
|
+
steps:
|
|
16
|
+
- name: Remove labels
|
|
17
|
+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
18
|
+
env:
|
|
19
|
+
ISSUE_NUMBER: ${{ inputs.issue-number }}
|
|
20
|
+
LABELS: ${{ inputs.labels }}
|
|
21
|
+
with:
|
|
22
|
+
github-token: ${{ inputs.token }}
|
|
23
|
+
script: |
|
|
24
|
+
for (const label of process.env.LABELS.split(/\r?\n/).filter(Boolean)) {
|
|
25
|
+
try {
|
|
26
|
+
await github.rest.issues.removeLabel({
|
|
27
|
+
...context.repo,
|
|
28
|
+
issue_number: Number(process.env.ISSUE_NUMBER),
|
|
29
|
+
name: label,
|
|
30
|
+
});
|
|
31
|
+
} catch (error) {
|
|
32
|
+
if (error.status !== 404) throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|