@plainconceptsplatform/workflows 1.3.1 → 1.4.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/worker-env.js +2 -3
- package/loops/actions/add-issue-labels/action.yml +50 -50
- package/loops/actions/agent-output.cjs +17 -17
- package/loops/actions/apply-agent-bundle/action.yml +24 -24
- package/loops/actions/apply-agent-comments/action.yml +42 -42
- package/loops/actions/apply-agent-labels/action.yml +55 -55
- package/loops/actions/apply-agent-output/action.yml +5 -0
- package/loops/actions/cleanup-artifacts/action.yml +91 -91
- package/loops/actions/close-agent-issues/action.yml +43 -43
- package/loops/actions/create-agent-issues/action.yml +64 -3
- package/loops/actions/create-issue-comment/action.yml +29 -29
- package/loops/actions/download-agent-output/action.yml +53 -53
- package/loops/actions/housekeeping/action.yml +95 -1
- package/loops/actions/link-pr-to-issue/action.yml +40 -40
- package/loops/actions/list-open-issues/action.yml +33 -33
- package/loops/actions/load-issue-context/action.yml +45 -45
- package/loops/actions/merge-agent-pr/action.yml +49 -49
- package/loops/actions/push-agent-branch/action.yml +45 -45
- package/loops/actions/remove-issue-labels/action.yml +37 -37
- package/loops/actions/update-agent-issues/action.yml +58 -58
- package/loops/actions/validate-refine-output/validate-refine-output.sh +30 -1
- package/loops/actions/validate-review-output/action.yml +35 -35
- package/loops/actions/validate-triage-output/action.yml +36 -36
- package/loops/actions/verify-composite-actions/action.yml +9 -9
- package/loops/actions/verify-refine-output/action.yml +9 -9
- package/loops/actions/verify-refine-output/verify-refine-output.sh +17 -0
- package/loops/actions/verify-route-matrix/action.yml +9 -9
- package/loops/actions/verify-route-matrix/verify-route-matrix.sh +214 -7
- package/loops/docs/diagrams.md +90 -12
- package/loops/scripts/compile-agent-workflows.mjs +76 -2
- package/loops/templates/agentics/agentics-maintenance.yml +121 -121
- package/loops/templates/issues/bug_report.yml +109 -109
- package/loops/templates/issues/feature_request.yml +75 -75
- package/loops/templates/release/github-release.yml +30 -30
- package/loops/workflows/agent-implement.md +37 -7
- package/loops/workflows/agent-merge-gate.md +74 -6
- package/loops/workflows/agent-refine.md +152 -5
- package/loops/workflows/authorize-bot-work.yml +105 -105
- package/loops/workflows/shared/platform-defaults.md +23 -23
- package/loops/workflows/work-router.yml +330 -2
- package/package.json +1 -1
package/dist/worker-env.js
CHANGED
|
@@ -139,9 +139,8 @@ function engineEndpoint(content) {
|
|
|
139
139
|
// triage, refine, implement, apply-review and audit on RunnerLandingZone while its merge-gate
|
|
140
140
|
// and release stayed on agents-arc -- the pool was applied to the workers installed at the time
|
|
141
141
|
// and never to the ones added later, and nothing reported the split. The pool is package-owned
|
|
142
|
-
// now:
|
|
143
|
-
//
|
|
144
|
-
// cannot drift back without a red run. Per the standing rule for this package, anything a
|
|
142
|
+
// now: every job, router and release included, belongs on the Azure fleet (agents-arc, runner
|
|
143
|
+
// group `agentic`). The route matrix asserts it, so a repo cannot drift back without a red run. Per the standing rule for this package, anything a
|
|
145
144
|
// consumer really must vary belongs in `env:` at the top of the file, not in `runs-on`.
|
|
146
145
|
// GitHub evaluates no expression in a `workflow_run.workflows:` list or in a `cron:`, so the two
|
|
147
146
|
// router values that are also needed there cannot be read from `env:` at those two lines. The
|
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/add-issue-labels/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
-
name: Add issue labels
|
|
3
|
-
description: Add one or more labels to an issue or pull request.
|
|
4
|
-
inputs:
|
|
5
|
-
token:
|
|
6
|
-
description: GitHub token used by github-script.
|
|
7
|
-
required: true
|
|
8
|
-
issue-number:
|
|
9
|
-
description: Issue or pull request number.
|
|
10
|
-
required: true
|
|
11
|
-
labels:
|
|
12
|
-
description: Labels to add, one per line. A comma-separated list is accepted as well.
|
|
13
|
-
required: true
|
|
14
|
-
runs:
|
|
15
|
-
using: composite
|
|
16
|
-
steps:
|
|
17
|
-
- name: Add labels
|
|
18
|
-
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
19
|
-
env:
|
|
20
|
-
ISSUE_NUMBER: ${{ inputs.issue-number }}
|
|
21
|
-
LABELS: ${{ inputs.labels }}
|
|
22
|
-
with:
|
|
23
|
-
github-token: ${{ inputs.token }}
|
|
24
|
-
script: |
|
|
25
|
-
const labels = process.env.LABELS.split(/\r?\n|,/).map((label) => label.trim()).filter(Boolean);
|
|
26
|
-
if (labels.length === 0) {
|
|
27
|
-
core.notice('No labels to add.');
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Create what does not exist yet, so shipping a new label in the package does not
|
|
32
|
-
// require a person to go and make it in every repository first. Its sibling
|
|
33
|
-
// remove-issue-labels has swallowed 404 for a long time; this one had no guard at all,
|
|
34
|
-
// so a label the package introduced would fail the step in every consumer until
|
|
35
|
-
// somebody created it by hand. 422 is "already exists", which is the happy path on
|
|
36
|
-
// every run after the first.
|
|
37
|
-
for (const name of labels) {
|
|
38
|
-
try {
|
|
39
|
-
await github.rest.issues.createLabel({ ...context.repo, name, color: 'ededed' });
|
|
40
|
-
core.notice(`Created the ${name} label.`);
|
|
41
|
-
} catch (error) {
|
|
42
|
-
if (error.status !== 422) throw error;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
await github.rest.issues.addLabels({
|
|
47
|
-
...context.repo,
|
|
48
|
-
issue_number: Number(process.env.ISSUE_NUMBER),
|
|
49
|
-
labels,
|
|
50
|
-
});
|
|
1
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/add-issue-labels/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
+
name: Add issue labels
|
|
3
|
+
description: Add one or more labels to an issue or pull request.
|
|
4
|
+
inputs:
|
|
5
|
+
token:
|
|
6
|
+
description: GitHub token used by github-script.
|
|
7
|
+
required: true
|
|
8
|
+
issue-number:
|
|
9
|
+
description: Issue or pull request number.
|
|
10
|
+
required: true
|
|
11
|
+
labels:
|
|
12
|
+
description: Labels to add, one per line. A comma-separated list is accepted as well.
|
|
13
|
+
required: true
|
|
14
|
+
runs:
|
|
15
|
+
using: composite
|
|
16
|
+
steps:
|
|
17
|
+
- name: Add labels
|
|
18
|
+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
19
|
+
env:
|
|
20
|
+
ISSUE_NUMBER: ${{ inputs.issue-number }}
|
|
21
|
+
LABELS: ${{ inputs.labels }}
|
|
22
|
+
with:
|
|
23
|
+
github-token: ${{ inputs.token }}
|
|
24
|
+
script: |
|
|
25
|
+
const labels = process.env.LABELS.split(/\r?\n|,/).map((label) => label.trim()).filter(Boolean);
|
|
26
|
+
if (labels.length === 0) {
|
|
27
|
+
core.notice('No labels to add.');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Create what does not exist yet, so shipping a new label in the package does not
|
|
32
|
+
// require a person to go and make it in every repository first. Its sibling
|
|
33
|
+
// remove-issue-labels has swallowed 404 for a long time; this one had no guard at all,
|
|
34
|
+
// so a label the package introduced would fail the step in every consumer until
|
|
35
|
+
// somebody created it by hand. 422 is "already exists", which is the happy path on
|
|
36
|
+
// every run after the first.
|
|
37
|
+
for (const name of labels) {
|
|
38
|
+
try {
|
|
39
|
+
await github.rest.issues.createLabel({ ...context.repo, name, color: 'ededed' });
|
|
40
|
+
core.notice(`Created the ${name} label.`);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (error.status !== 422) throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
await github.rest.issues.addLabels({
|
|
47
|
+
...context.repo,
|
|
48
|
+
issue_number: Number(process.env.ISSUE_NUMBER),
|
|
49
|
+
labels,
|
|
50
|
+
});
|
|
@@ -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 };
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/apply-agent-bundle/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
-
name: Apply agent bundle
|
|
3
|
-
description: Fast-forward an intended branch to the single commit exported by a verified git bundle.
|
|
4
|
-
inputs:
|
|
5
|
-
bundle-file:
|
|
6
|
-
description: Path to the git bundle from the agent artifact.
|
|
7
|
-
required: true
|
|
8
|
-
target-branch:
|
|
9
|
-
description: Branch that must fast-forward to the bundle commit.
|
|
10
|
-
required: true
|
|
11
|
-
base-branch:
|
|
12
|
-
description: Existing branch used only when target-branch does not exist.
|
|
13
|
-
required: false
|
|
14
|
-
default: ''
|
|
15
|
-
runs:
|
|
16
|
-
using: composite
|
|
17
|
-
steps:
|
|
18
|
-
- name: Fast-forward branch from bundle
|
|
19
|
-
shell: bash
|
|
20
|
-
env:
|
|
21
|
-
BUNDLE_FILE: ${{ inputs.bundle-file }}
|
|
22
|
-
TARGET_BRANCH: ${{ inputs.target-branch }}
|
|
23
|
-
BASE_BRANCH: ${{ inputs.base-branch }}
|
|
24
|
-
run: bash "$GITHUB_ACTION_PATH/apply-bundle.sh" "$BUNDLE_FILE" "$TARGET_BRANCH" "$BASE_BRANCH"
|
|
1
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/apply-agent-bundle/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
+
name: Apply agent bundle
|
|
3
|
+
description: Fast-forward an intended branch to the single commit exported by a verified git bundle.
|
|
4
|
+
inputs:
|
|
5
|
+
bundle-file:
|
|
6
|
+
description: Path to the git bundle from the agent artifact.
|
|
7
|
+
required: true
|
|
8
|
+
target-branch:
|
|
9
|
+
description: Branch that must fast-forward to the bundle commit.
|
|
10
|
+
required: true
|
|
11
|
+
base-branch:
|
|
12
|
+
description: Existing branch used only when target-branch does not exist.
|
|
13
|
+
required: false
|
|
14
|
+
default: ''
|
|
15
|
+
runs:
|
|
16
|
+
using: composite
|
|
17
|
+
steps:
|
|
18
|
+
- name: Fast-forward branch from bundle
|
|
19
|
+
shell: bash
|
|
20
|
+
env:
|
|
21
|
+
BUNDLE_FILE: ${{ inputs.bundle-file }}
|
|
22
|
+
TARGET_BRANCH: ${{ inputs.target-branch }}
|
|
23
|
+
BASE_BRANCH: ${{ inputs.base-branch }}
|
|
24
|
+
run: bash "$GITHUB_ACTION_PATH/apply-bundle.sh" "$BUNDLE_FILE" "$TARGET_BRANCH" "$BASE_BRANCH"
|
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/apply-agent-comments/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
-
name: Apply agent comments
|
|
3
|
-
description: Post every add_comment item from agent_output.json.
|
|
4
|
-
inputs:
|
|
5
|
-
output-file:
|
|
6
|
-
description: Path to agent_output.json.
|
|
7
|
-
required: true
|
|
8
|
-
token:
|
|
9
|
-
description: GitHub token with issues:write and pull-requests:write.
|
|
10
|
-
required: true
|
|
11
|
-
runs:
|
|
12
|
-
using: composite
|
|
13
|
-
steps:
|
|
14
|
-
- name: Post comments
|
|
15
|
-
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
16
|
-
env:
|
|
17
|
-
AGENT_OUTPUT_LIB: ${{ github.action_path }}/../agent-output.cjs
|
|
18
|
-
OUTPUT_FILE: ${{ inputs.output-file }}
|
|
19
|
-
with:
|
|
20
|
-
github-token: ${{ inputs.token }}
|
|
21
|
-
script: |
|
|
22
|
-
const { readAgentItems } = require(process.env.AGENT_OUTPUT_LIB);
|
|
23
|
-
const items = readAgentItems(process.env.OUTPUT_FILE, 'add_comment');
|
|
24
|
-
|
|
25
|
-
let posted = 0;
|
|
26
|
-
|
|
27
|
-
for (const item of items) {
|
|
28
|
-
if (!item.item_number) {
|
|
29
|
-
core.warning(`add_comment item has no item_number, skipping: ${JSON.stringify(item)}`);
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
await github.rest.issues.createComment({
|
|
34
|
-
...context.repo,
|
|
35
|
-
issue_number: Number(item.item_number),
|
|
36
|
-
body: item.body,
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
posted += 1;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
core.info(`Posted ${posted} comment(s).`);
|
|
1
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/apply-agent-comments/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
+
name: Apply agent comments
|
|
3
|
+
description: Post every add_comment item from agent_output.json.
|
|
4
|
+
inputs:
|
|
5
|
+
output-file:
|
|
6
|
+
description: Path to agent_output.json.
|
|
7
|
+
required: true
|
|
8
|
+
token:
|
|
9
|
+
description: GitHub token with issues:write and pull-requests:write.
|
|
10
|
+
required: true
|
|
11
|
+
runs:
|
|
12
|
+
using: composite
|
|
13
|
+
steps:
|
|
14
|
+
- name: Post comments
|
|
15
|
+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
16
|
+
env:
|
|
17
|
+
AGENT_OUTPUT_LIB: ${{ github.action_path }}/../agent-output.cjs
|
|
18
|
+
OUTPUT_FILE: ${{ inputs.output-file }}
|
|
19
|
+
with:
|
|
20
|
+
github-token: ${{ inputs.token }}
|
|
21
|
+
script: |
|
|
22
|
+
const { readAgentItems } = require(process.env.AGENT_OUTPUT_LIB);
|
|
23
|
+
const items = readAgentItems(process.env.OUTPUT_FILE, 'add_comment');
|
|
24
|
+
|
|
25
|
+
let posted = 0;
|
|
26
|
+
|
|
27
|
+
for (const item of items) {
|
|
28
|
+
if (!item.item_number) {
|
|
29
|
+
core.warning(`add_comment item has no item_number, skipping: ${JSON.stringify(item)}`);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
await github.rest.issues.createComment({
|
|
34
|
+
...context.repo,
|
|
35
|
+
issue_number: Number(item.item_number),
|
|
36
|
+
body: item.body,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
posted += 1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
core.info(`Posted ${posted} comment(s).`);
|
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/apply-agent-labels/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
-
name: Apply agent labels
|
|
3
|
-
description: Apply every add_labels and remove_labels item from agent_output.json.
|
|
4
|
-
inputs:
|
|
5
|
-
output-file:
|
|
6
|
-
description: Path to agent_output.json.
|
|
7
|
-
required: true
|
|
8
|
-
token:
|
|
9
|
-
description: GitHub token with issues:write.
|
|
10
|
-
required: true
|
|
11
|
-
runs:
|
|
12
|
-
using: composite
|
|
13
|
-
steps:
|
|
14
|
-
- name: Apply label changes
|
|
15
|
-
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
16
|
-
env:
|
|
17
|
-
AGENT_OUTPUT_LIB: ${{ github.action_path }}/../agent-output.cjs
|
|
18
|
-
OUTPUT_FILE: ${{ inputs.output-file }}
|
|
19
|
-
with:
|
|
20
|
-
github-token: ${{ inputs.token }}
|
|
21
|
-
script: |
|
|
22
|
-
const { readAgentItems } = require(process.env.AGENT_OUTPUT_LIB);
|
|
23
|
-
const items = readAgentItems(process.env.OUTPUT_FILE)
|
|
24
|
-
.filter((item) => item.type === 'add_labels' || item.type === 'remove_labels');
|
|
25
|
-
|
|
26
|
-
let added = 0;
|
|
27
|
-
let removed = 0;
|
|
28
|
-
|
|
29
|
-
for (const item of items) {
|
|
30
|
-
const labels = (item.labels ?? []).map((label) => label.name).filter(Boolean);
|
|
31
|
-
|
|
32
|
-
if (!item.item_number || labels.length === 0) {
|
|
33
|
-
core.warning(`${item.type} item is not actionable, skipping: ${JSON.stringify(item)}`);
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const issue_number = Number(item.item_number);
|
|
38
|
-
|
|
39
|
-
if (item.type === 'add_labels') {
|
|
40
|
-
await github.rest.issues.addLabels({ ...context.repo, issue_number, labels });
|
|
41
|
-
added += labels.length;
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
for (const name of labels) {
|
|
46
|
-
try {
|
|
47
|
-
await github.rest.issues.removeLabel({ ...context.repo, issue_number, name });
|
|
48
|
-
removed += 1;
|
|
49
|
-
} catch (error) {
|
|
50
|
-
if (error.status !== 404) throw error;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
core.info(`Added ${added} label(s), removed ${removed}.`);
|
|
1
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/apply-agent-labels/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
+
name: Apply agent labels
|
|
3
|
+
description: Apply every add_labels and remove_labels item from agent_output.json.
|
|
4
|
+
inputs:
|
|
5
|
+
output-file:
|
|
6
|
+
description: Path to agent_output.json.
|
|
7
|
+
required: true
|
|
8
|
+
token:
|
|
9
|
+
description: GitHub token with issues:write.
|
|
10
|
+
required: true
|
|
11
|
+
runs:
|
|
12
|
+
using: composite
|
|
13
|
+
steps:
|
|
14
|
+
- name: Apply label changes
|
|
15
|
+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
16
|
+
env:
|
|
17
|
+
AGENT_OUTPUT_LIB: ${{ github.action_path }}/../agent-output.cjs
|
|
18
|
+
OUTPUT_FILE: ${{ inputs.output-file }}
|
|
19
|
+
with:
|
|
20
|
+
github-token: ${{ inputs.token }}
|
|
21
|
+
script: |
|
|
22
|
+
const { readAgentItems } = require(process.env.AGENT_OUTPUT_LIB);
|
|
23
|
+
const items = readAgentItems(process.env.OUTPUT_FILE)
|
|
24
|
+
.filter((item) => item.type === 'add_labels' || item.type === 'remove_labels');
|
|
25
|
+
|
|
26
|
+
let added = 0;
|
|
27
|
+
let removed = 0;
|
|
28
|
+
|
|
29
|
+
for (const item of items) {
|
|
30
|
+
const labels = (item.labels ?? []).map((label) => label.name).filter(Boolean);
|
|
31
|
+
|
|
32
|
+
if (!item.item_number || labels.length === 0) {
|
|
33
|
+
core.warning(`${item.type} item is not actionable, skipping: ${JSON.stringify(item)}`);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const issue_number = Number(item.item_number);
|
|
38
|
+
|
|
39
|
+
if (item.type === 'add_labels') {
|
|
40
|
+
await github.rest.issues.addLabels({ ...context.repo, issue_number, labels });
|
|
41
|
+
added += labels.length;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const name of labels) {
|
|
46
|
+
try {
|
|
47
|
+
await github.rest.issues.removeLabel({ ...context.repo, issue_number, name });
|
|
48
|
+
removed += 1;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (error.status !== 404) throw error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
core.info(`Added ${added} label(s), removed ${removed}.`);
|
|
@@ -39,6 +39,10 @@ inputs:
|
|
|
39
39
|
description: Issue an update_issue item targets when it names none.
|
|
40
40
|
required: false
|
|
41
41
|
default: ''
|
|
42
|
+
parent-issue:
|
|
43
|
+
description: The split parent; see create-agent-issues. Empty for anything but a split.
|
|
44
|
+
required: false
|
|
45
|
+
default: ''
|
|
42
46
|
outputs:
|
|
43
47
|
item-count:
|
|
44
48
|
description: Number of items the agent produced.
|
|
@@ -92,6 +96,7 @@ runs:
|
|
|
92
96
|
with:
|
|
93
97
|
output-file: ${{ steps.output.outputs.output-file }}
|
|
94
98
|
token: ${{ inputs.token }}
|
|
99
|
+
parent-issue: ${{ inputs.parent-issue }}
|
|
95
100
|
|
|
96
101
|
- name: Post agent comments
|
|
97
102
|
if: steps.output.outputs.item-count != '0'
|
|
@@ -1,91 +1,91 @@
|
|
|
1
|
-
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/cleanup-artifacts/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
-
name: Clean up artifacts
|
|
3
|
-
description: Delete workflow artifacts older than the configured retention period.
|
|
4
|
-
inputs:
|
|
5
|
-
token:
|
|
6
|
-
description: GitHub token with actions write access.
|
|
7
|
-
required: true
|
|
8
|
-
artifact-retention-days:
|
|
9
|
-
description: Retention period in days. Invalid or empty values use 3.
|
|
10
|
-
required: false
|
|
11
|
-
default: ''
|
|
12
|
-
runs:
|
|
13
|
-
using: composite
|
|
14
|
-
steps:
|
|
15
|
-
- name: Delete expired artifacts
|
|
16
|
-
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
17
|
-
env:
|
|
18
|
-
ARTIFACT_RETENTION_DAYS: ${{ inputs.artifact-retention-days }}
|
|
19
|
-
with:
|
|
20
|
-
github-token: ${{ inputs.token }}
|
|
21
|
-
script: |
|
|
22
|
-
const parsedDays = Number.parseInt(process.env.ARTIFACT_RETENTION_DAYS, 10);
|
|
23
|
-
const days = Number.isInteger(parsedDays) && parsedDays > 0 ? parsedDays : 3;
|
|
24
|
-
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
|
|
25
|
-
|
|
26
|
-
core.info(`Retention: ${days} days (cutoff ${cutoff.toISOString()})`);
|
|
27
|
-
|
|
28
|
-
const artifacts = await github.paginate(github.rest.actions.listArtifactsForRepo, {
|
|
29
|
-
...context.repo,
|
|
30
|
-
per_page: 100,
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
const expired = artifacts.filter((artifact) => new Date(artifact.created_at) < cutoff);
|
|
34
|
-
|
|
35
|
-
let deleted = 0;
|
|
36
|
-
const failed = [];
|
|
37
|
-
|
|
38
|
-
for (const artifact of expired) {
|
|
39
|
-
// GitHub answers a delete with a 5xx now and then. Three tries, because the
|
|
40
|
-
// alternative is a red daily run over one artifact that tomorrow's run would
|
|
41
|
-
// have removed anyway.
|
|
42
|
-
let lastError;
|
|
43
|
-
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
44
|
-
try {
|
|
45
|
-
await github.rest.actions.deleteArtifact({
|
|
46
|
-
...context.repo,
|
|
47
|
-
artifact_id: artifact.id,
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
core.info(`Deleted ${artifact.name} (created ${artifact.created_at})`);
|
|
51
|
-
deleted += 1;
|
|
52
|
-
lastError = undefined;
|
|
53
|
-
break;
|
|
54
|
-
} catch (error) {
|
|
55
|
-
lastError = error;
|
|
56
|
-
if (attempt < 3) await new Promise((wait) => setTimeout(wait, attempt * 2000));
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (lastError) {
|
|
61
|
-
core.warning(
|
|
62
|
-
`Could not delete ${artifact.name} (${artifact.id}) after 3 tries: ${lastError.message}`,
|
|
63
|
-
);
|
|
64
|
-
failed.push({ name: artifact.name, status: lastError.status });
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
await core.summary
|
|
69
|
-
.addHeading('Artifact cleanup', 2)
|
|
70
|
-
.addList([
|
|
71
|
-
`Retention: ${days} days (cutoff ${cutoff.toISOString()})`,
|
|
72
|
-
`Deleted: ${deleted}`,
|
|
73
|
-
`Failed to delete: ${failed.length}`,
|
|
74
|
-
`Within retention: ${artifacts.length - expired.length}`,
|
|
75
|
-
])
|
|
76
|
-
.write();
|
|
77
|
-
|
|
78
|
-
// Deleting nothing at all while expired artifacts exist is a permission or
|
|
79
|
-
// configuration problem, and has to be loud. One stubborn artifact among many is
|
|
80
|
-
// the API having a bad minute: tomorrow's run picks it up, and failing here only
|
|
81
|
-
// teaches people to ignore a red cleanup. A 4xx is never transient, so it fails
|
|
82
|
-
// whatever else succeeded.
|
|
83
|
-
const names = failed.map((entry) => entry.name).join(', ');
|
|
84
|
-
const structural = failed.some((entry) => entry.status && entry.status < 500);
|
|
85
|
-
if (failed.length > 0 && (deleted === 0 || structural)) {
|
|
86
|
-
core.setFailed(`${failed.length} artifact(s) could not be deleted: ${names}`);
|
|
87
|
-
} else if (failed.length > 0) {
|
|
88
|
-
core.warning(
|
|
89
|
-
`${failed.length} artifact(s) survived this run and will be retried tomorrow: ${names}`,
|
|
90
|
-
);
|
|
91
|
-
}
|
|
1
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/cleanup-artifacts/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
+
name: Clean up artifacts
|
|
3
|
+
description: Delete workflow artifacts older than the configured retention period.
|
|
4
|
+
inputs:
|
|
5
|
+
token:
|
|
6
|
+
description: GitHub token with actions write access.
|
|
7
|
+
required: true
|
|
8
|
+
artifact-retention-days:
|
|
9
|
+
description: Retention period in days. Invalid or empty values use 3.
|
|
10
|
+
required: false
|
|
11
|
+
default: ''
|
|
12
|
+
runs:
|
|
13
|
+
using: composite
|
|
14
|
+
steps:
|
|
15
|
+
- name: Delete expired artifacts
|
|
16
|
+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
17
|
+
env:
|
|
18
|
+
ARTIFACT_RETENTION_DAYS: ${{ inputs.artifact-retention-days }}
|
|
19
|
+
with:
|
|
20
|
+
github-token: ${{ inputs.token }}
|
|
21
|
+
script: |
|
|
22
|
+
const parsedDays = Number.parseInt(process.env.ARTIFACT_RETENTION_DAYS, 10);
|
|
23
|
+
const days = Number.isInteger(parsedDays) && parsedDays > 0 ? parsedDays : 3;
|
|
24
|
+
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
|
|
25
|
+
|
|
26
|
+
core.info(`Retention: ${days} days (cutoff ${cutoff.toISOString()})`);
|
|
27
|
+
|
|
28
|
+
const artifacts = await github.paginate(github.rest.actions.listArtifactsForRepo, {
|
|
29
|
+
...context.repo,
|
|
30
|
+
per_page: 100,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const expired = artifacts.filter((artifact) => new Date(artifact.created_at) < cutoff);
|
|
34
|
+
|
|
35
|
+
let deleted = 0;
|
|
36
|
+
const failed = [];
|
|
37
|
+
|
|
38
|
+
for (const artifact of expired) {
|
|
39
|
+
// GitHub answers a delete with a 5xx now and then. Three tries, because the
|
|
40
|
+
// alternative is a red daily run over one artifact that tomorrow's run would
|
|
41
|
+
// have removed anyway.
|
|
42
|
+
let lastError;
|
|
43
|
+
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
44
|
+
try {
|
|
45
|
+
await github.rest.actions.deleteArtifact({
|
|
46
|
+
...context.repo,
|
|
47
|
+
artifact_id: artifact.id,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
core.info(`Deleted ${artifact.name} (created ${artifact.created_at})`);
|
|
51
|
+
deleted += 1;
|
|
52
|
+
lastError = undefined;
|
|
53
|
+
break;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
lastError = error;
|
|
56
|
+
if (attempt < 3) await new Promise((wait) => setTimeout(wait, attempt * 2000));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (lastError) {
|
|
61
|
+
core.warning(
|
|
62
|
+
`Could not delete ${artifact.name} (${artifact.id}) after 3 tries: ${lastError.message}`,
|
|
63
|
+
);
|
|
64
|
+
failed.push({ name: artifact.name, status: lastError.status });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
await core.summary
|
|
69
|
+
.addHeading('Artifact cleanup', 2)
|
|
70
|
+
.addList([
|
|
71
|
+
`Retention: ${days} days (cutoff ${cutoff.toISOString()})`,
|
|
72
|
+
`Deleted: ${deleted}`,
|
|
73
|
+
`Failed to delete: ${failed.length}`,
|
|
74
|
+
`Within retention: ${artifacts.length - expired.length}`,
|
|
75
|
+
])
|
|
76
|
+
.write();
|
|
77
|
+
|
|
78
|
+
// Deleting nothing at all while expired artifacts exist is a permission or
|
|
79
|
+
// configuration problem, and has to be loud. One stubborn artifact among many is
|
|
80
|
+
// the API having a bad minute: tomorrow's run picks it up, and failing here only
|
|
81
|
+
// teaches people to ignore a red cleanup. A 4xx is never transient, so it fails
|
|
82
|
+
// whatever else succeeded.
|
|
83
|
+
const names = failed.map((entry) => entry.name).join(', ');
|
|
84
|
+
const structural = failed.some((entry) => entry.status && entry.status < 500);
|
|
85
|
+
if (failed.length > 0 && (deleted === 0 || structural)) {
|
|
86
|
+
core.setFailed(`${failed.length} artifact(s) could not be deleted: ${names}`);
|
|
87
|
+
} else if (failed.length > 0) {
|
|
88
|
+
core.warning(
|
|
89
|
+
`${failed.length} artifact(s) survived this run and will be retried tomorrow: ${names}`,
|
|
90
|
+
);
|
|
91
|
+
}
|