@plainconceptsplatform/workflows 1.13.0 → 1.14.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/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/cleanup-artifacts/action.yml +91 -91
- package/loops/actions/close-agent-issues/action.yml +43 -43
- package/loops/actions/create-issue-comment/action.yml +29 -29
- package/loops/actions/download-agent-output/action.yml +53 -53
- 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/merge-dependabot/action.yml +10 -2
- package/loops/actions/merge-dependabot/merge-dependabot.mjs +47 -17
- package/loops/actions/remove-issue-labels/action.yml +37 -37
- package/loops/actions/update-agent-issues/action.yml +58 -58
- 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-route-matrix/action.yml +9 -9
- package/loops/actions/verify-route-matrix/verify-route-matrix.sh +7 -2
- package/loops/templates/agentics/agentics-maintenance.yml +121 -121
- package/loops/templates/dependabot-merge/.github/workflows/dependabot-merge.yml +3 -1
- package/loops/templates/issues/bug_report.yml +109 -109
- package/loops/templates/issues/feature_request.yml +75 -75
- package/loops/workflows/agent-implement.md +13 -12
- package/loops/workflows/shared/opencode-ci.md +17 -5
- package/loops/workflows/shared/platform-defaults.md +53 -53
- package/loops/workflows/work-router.yml +4 -0
- package/package.json +2 -2
|
@@ -19,6 +19,13 @@ export const NO_CHECKS_GRACE_MINUTES = 60;
|
|
|
19
19
|
|
|
20
20
|
export const isDependabot = (login) => DEPENDABOT_LOGINS.has(login ?? "");
|
|
21
21
|
|
|
22
|
+
// A read the token may not make. The pull request is skipped and the run goes on.
|
|
23
|
+
export class ReadDenied extends Error {
|
|
24
|
+
constructor(what, permission) {
|
|
25
|
+
super(`cannot read ${what}: grant ${permission}: read to the App or the workflow token`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
22
29
|
export function authorshipProblem(pr, commits, repository) {
|
|
23
30
|
if (!isDependabot(pr.user?.login)) return `opened by ${pr.user?.login ?? "unknown"}`;
|
|
24
31
|
if (String(pr.head?.repo?.full_name).toLowerCase() !== repository.toLowerCase()) return "its branch is in another repository";
|
|
@@ -196,7 +203,7 @@ export function createClient({ token, apiUrl = "https://api.github.com", fetchIm
|
|
|
196
203
|
let next = path;
|
|
197
204
|
while (next && items.length < limit) {
|
|
198
205
|
const page = await request("GET", next);
|
|
199
|
-
if (page.status >= 400) throw new Error(`GET ${next} answered ${page.status}: ${JSON.stringify(page.data)}`);
|
|
206
|
+
if (page.status >= 400) throw Object.assign(new Error(`GET ${next} answered ${page.status}: ${JSON.stringify(page.data)}`), { status: page.status });
|
|
200
207
|
items.push(...(Array.isArray(page.data) ? page.data : page.data?.check_runs ?? []));
|
|
201
208
|
next = /<([^>]+)>;\s*rel="next"/.exec(page.link)?.[1];
|
|
202
209
|
}
|
|
@@ -207,9 +214,16 @@ export function createClient({ token, apiUrl = "https://api.github.com", fetchIm
|
|
|
207
214
|
|
|
208
215
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
209
216
|
|
|
217
|
+
const denied = async (what, permission, read) => {
|
|
218
|
+
try { return await read(); } catch (error) {
|
|
219
|
+
throw error.status === 403 ? new ReadDenied(what, permission) : error;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
210
223
|
async function readPullRequest(client, repository, number, wait) {
|
|
211
224
|
for (let attempt = 0; ; attempt++) {
|
|
212
|
-
const { data } = await client.request("GET", `/repos/${repository}/pulls/${number}`);
|
|
225
|
+
const { status, data } = await client.request("GET", `/repos/${repository}/pulls/${number}`);
|
|
226
|
+
if (status === 403) throw new ReadDenied("pull requests", "Pull requests");
|
|
213
227
|
if (data?.mergeable !== null || attempt >= 3) return data;
|
|
214
228
|
await wait(2000);
|
|
215
229
|
}
|
|
@@ -228,8 +242,9 @@ async function requiredChecks(client, repository, branch) {
|
|
|
228
242
|
}
|
|
229
243
|
|
|
230
244
|
async function readChecks(client, repository, sha, required) {
|
|
231
|
-
const checkRuns = await client.paginate(`/repos/${repository}/commits/${sha}/check-runs?per_page=100`);
|
|
245
|
+
const checkRuns = await denied("checks", "Checks", () => client.paginate(`/repos/${repository}/commits/${sha}/check-runs?per_page=100`));
|
|
232
246
|
const combined = await client.request("GET", `/repos/${repository}/commits/${sha}/status?per_page=100`);
|
|
247
|
+
if (combined.status === 403) throw new ReadDenied("commit statuses", "Commit statuses");
|
|
233
248
|
return summarizeChecks({ checkRuns, statuses: combined.data?.statuses ?? [], required });
|
|
234
249
|
}
|
|
235
250
|
|
|
@@ -267,27 +282,39 @@ async function land(client, repository, pr, config) {
|
|
|
267
282
|
return { action: "error", reason: `merge answered ${merged.status}: ${message}` };
|
|
268
283
|
}
|
|
269
284
|
|
|
270
|
-
|
|
285
|
+
// `client` writes (approve, merge, comment) and `reader` reads. The App token writes, so its merge
|
|
286
|
+
// starts the push workflows; the workflow token reads, as the App may hold no Checks permission.
|
|
287
|
+
export async function run({ client, reader = client, repository, config, now = Date.now(), log = console.log, wait = sleep }) {
|
|
271
288
|
const rows = [];
|
|
289
|
+
const deniedReads = new Set();
|
|
272
290
|
if (config.scope === "off") return { rows, off: true };
|
|
273
291
|
|
|
274
|
-
const { data: repo } = await
|
|
292
|
+
const { data: repo } = await reader.request("GET", `/repos/${repository}`);
|
|
275
293
|
const defaultBranch = repo.default_branch;
|
|
276
|
-
const open = await
|
|
294
|
+
const open = await reader.paginate(`/repos/${repository}/pulls?state=open&sort=created&direction=asc&per_page=100`);
|
|
277
295
|
const candidates = open.filter((pr) => isDependabot(pr.user?.login)).slice(0, MAX_EXAMINED);
|
|
278
|
-
const required = candidates.length > 0 ? await requiredChecks(
|
|
296
|
+
const required = candidates.length > 0 ? await requiredChecks(reader, repository, defaultBranch) : [];
|
|
279
297
|
let merges = 0;
|
|
280
298
|
|
|
281
299
|
for (const listed of candidates) {
|
|
282
|
-
if (
|
|
300
|
+
if (reader.rateRemaining() < MIN_RATE_REMAINING) {
|
|
283
301
|
rows.push({ number: listed.number, title: listed.title, bump: "", action: "wait", reason: "API rate limit nearly spent" });
|
|
284
302
|
continue;
|
|
285
303
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
304
|
+
let pr, commits, comments, checks;
|
|
305
|
+
try {
|
|
306
|
+
pr = await readPullRequest(reader, repository, listed.number, wait);
|
|
307
|
+
commits = await denied("pull request commits", "Pull requests", () => reader.paginate(`/repos/${repository}/pulls/${pr.number}/commits?per_page=100`, 250));
|
|
308
|
+
comments = await denied("issue comments", "Issues", () => reader.paginate(`/repos/${repository}/issues/${pr.number}/comments?per_page=100`));
|
|
309
|
+
const mergeableNow = pr.mergeable === true && !["dirty", "behind"].includes(pr.mergeable_state);
|
|
310
|
+
checks = mergeableNow ? await readChecks(reader, repository, pr.head.sha, required) : { state: "none", failing: [], pending: [] };
|
|
311
|
+
} catch (error) {
|
|
312
|
+
if (!(error instanceof ReadDenied)) throw error;
|
|
313
|
+
deniedReads.add(error.message);
|
|
314
|
+
log(`::warning::#${listed.number} skipped: ${error.message}`);
|
|
315
|
+
rows.push({ number: listed.number, title: listed.title, bump: "", action: "skip", reason: error.message });
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
291
318
|
let outcome = decide({
|
|
292
319
|
pr, commits, defaultBranch, repository, comments, checks, now,
|
|
293
320
|
skipLabels: config.skipLabels, scope: config.scope, mergeWithoutChecks: config.mergeWithoutChecks,
|
|
@@ -305,12 +332,13 @@ export async function run({ client, repository, config, now = Date.now(), log =
|
|
|
305
332
|
log(`#${pr.number} ${outcome.action}: ${outcome.reason}`);
|
|
306
333
|
rows.push({ number: pr.number, title: pr.title, bump: outcome.bump, action: outcome.action, reason: outcome.reason });
|
|
307
334
|
}
|
|
308
|
-
return { rows, off: false };
|
|
335
|
+
return { rows, off: false, denied: [...deniedReads] };
|
|
309
336
|
}
|
|
310
337
|
|
|
311
|
-
export function renderSummary({ rows, off }, config) {
|
|
338
|
+
export function renderSummary({ rows, off, denied: deniedReads = [] }, config) {
|
|
312
339
|
const lines = [`## Dependabot merge (${config.dryRun ? "dry run, " : ""}scope: ${config.scope})`, ""];
|
|
313
340
|
if (off) return [...lines, "DEPENDABOT_MERGE is off: nothing was examined.", ""].join("\n");
|
|
341
|
+
for (const message of deniedReads) lines.push("> [!WARNING]", `> ${message}.`, "");
|
|
314
342
|
if (rows.length === 0) return [...lines, "No open Dependabot pull requests.", ""].join("\n");
|
|
315
343
|
lines.push("| PR | Update | Bump | Result | Why |", "|---|---|---|---|---|");
|
|
316
344
|
const cell = (value) => String(value ?? "").replace(/\|/g, "\\|").replace(/\n/g, " ");
|
|
@@ -323,8 +351,10 @@ async function main() {
|
|
|
323
351
|
SCOPE: process.env.SCOPE, MAX_MERGES: process.env.MAX_MERGES, MERGE_WITHOUT_CHECKS: process.env.MERGE_WITHOUT_CHECKS,
|
|
324
352
|
SKIP_LABELS: process.env.SKIP_LABELS, DRY_RUN: process.env.DRY_RUN, POST_MERGE_EVENT: process.env.POST_MERGE_EVENT,
|
|
325
353
|
});
|
|
326
|
-
const
|
|
327
|
-
const
|
|
354
|
+
const apiUrl = process.env.GITHUB_API_URL || "https://api.github.com";
|
|
355
|
+
const client = createClient({ token: process.env.GH_TOKEN, apiUrl });
|
|
356
|
+
const reader = process.env.READ_TOKEN ? createClient({ token: process.env.READ_TOKEN, apiUrl }) : client;
|
|
357
|
+
const result = await run({ client, reader, repository: process.env.REPO, config });
|
|
328
358
|
const summary = renderSummary(result, config);
|
|
329
359
|
if (process.env.GITHUB_STEP_SUMMARY) appendFileSync(process.env.GITHUB_STEP_SUMMARY, `${summary}\n`);
|
|
330
360
|
else console.log(summary);
|
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/remove-issue-labels/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
-
name: Remove issue labels
|
|
3
|
-
description: Remove one or more labels from 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 remove, one per line. A comma-separated list is accepted as well.
|
|
13
|
-
required: true
|
|
14
|
-
runs:
|
|
15
|
-
using: composite
|
|
16
|
-
steps:
|
|
17
|
-
- name: Remove 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
|
-
// Callers wrote `labels: a,b` while this split on newlines, so one label named "a,b"
|
|
26
|
-
// was removed: a 404, swallowed below, and neither label ever came off.
|
|
27
|
-
for (const label of process.env.LABELS.split(/\r?\n|,/).map((label) => label.trim()).filter(Boolean)) {
|
|
28
|
-
try {
|
|
29
|
-
await github.rest.issues.removeLabel({
|
|
30
|
-
...context.repo,
|
|
31
|
-
issue_number: Number(process.env.ISSUE_NUMBER),
|
|
32
|
-
name: label,
|
|
33
|
-
});
|
|
34
|
-
} catch (error) {
|
|
35
|
-
if (error.status !== 404) throw error;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
1
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/remove-issue-labels/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
+
name: Remove issue labels
|
|
3
|
+
description: Remove one or more labels from 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 remove, one per line. A comma-separated list is accepted as well.
|
|
13
|
+
required: true
|
|
14
|
+
runs:
|
|
15
|
+
using: composite
|
|
16
|
+
steps:
|
|
17
|
+
- name: Remove 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
|
+
// Callers wrote `labels: a,b` while this split on newlines, so one label named "a,b"
|
|
26
|
+
// was removed: a 404, swallowed below, and neither label ever came off.
|
|
27
|
+
for (const label of process.env.LABELS.split(/\r?\n|,/).map((label) => label.trim()).filter(Boolean)) {
|
|
28
|
+
try {
|
|
29
|
+
await github.rest.issues.removeLabel({
|
|
30
|
+
...context.repo,
|
|
31
|
+
issue_number: Number(process.env.ISSUE_NUMBER),
|
|
32
|
+
name: label,
|
|
33
|
+
});
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (error.status !== 404) throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -1,58 +1,58 @@
|
|
|
1
|
-
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/update-agent-issues/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
-
name: Update agent issues
|
|
3
|
-
description: Apply every update_issue 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
|
-
fallback-issue-number:
|
|
12
|
-
description: Issue an item targets when it names none.
|
|
13
|
-
required: false
|
|
14
|
-
default: ''
|
|
15
|
-
runs:
|
|
16
|
-
using: composite
|
|
17
|
-
steps:
|
|
18
|
-
- name: Update issues
|
|
19
|
-
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
20
|
-
env:
|
|
21
|
-
AGENT_OUTPUT_LIB: ${{ github.action_path }}/../agent-output.cjs
|
|
22
|
-
OUTPUT_FILE: ${{ inputs.output-file }}
|
|
23
|
-
FALLBACK_ISSUE_NUMBER: ${{ inputs.fallback-issue-number }}
|
|
24
|
-
with:
|
|
25
|
-
github-token: ${{ inputs.token }}
|
|
26
|
-
script: |
|
|
27
|
-
const { readAgentItems } = require(process.env.AGENT_OUTPUT_LIB);
|
|
28
|
-
const items = readAgentItems(process.env.OUTPUT_FILE, 'update_issue');
|
|
29
|
-
|
|
30
|
-
let updated = 0;
|
|
31
|
-
|
|
32
|
-
for (const item of items) {
|
|
33
|
-
const target = item.item_number ?? process.env.FALLBACK_ISSUE_NUMBER;
|
|
34
|
-
|
|
35
|
-
if (!target) {
|
|
36
|
-
core.warning(`update_issue item has no item_number and no fallback, skipping: ${JSON.stringify(item)}`);
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const changes = {};
|
|
41
|
-
if (item.title) changes.title = item.title;
|
|
42
|
-
if (item.body) changes.body = item.body;
|
|
43
|
-
|
|
44
|
-
if (Object.keys(changes).length === 0) {
|
|
45
|
-
core.warning(`update_issue item changes nothing, skipping: ${JSON.stringify(item)}`);
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
await github.rest.issues.update({
|
|
50
|
-
...context.repo,
|
|
51
|
-
issue_number: Number(target),
|
|
52
|
-
...changes,
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
updated += 1;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
core.info(`Updated ${updated} issue(s).`);
|
|
1
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/update-agent-issues/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
+
name: Update agent issues
|
|
3
|
+
description: Apply every update_issue 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
|
+
fallback-issue-number:
|
|
12
|
+
description: Issue an item targets when it names none.
|
|
13
|
+
required: false
|
|
14
|
+
default: ''
|
|
15
|
+
runs:
|
|
16
|
+
using: composite
|
|
17
|
+
steps:
|
|
18
|
+
- name: Update issues
|
|
19
|
+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
20
|
+
env:
|
|
21
|
+
AGENT_OUTPUT_LIB: ${{ github.action_path }}/../agent-output.cjs
|
|
22
|
+
OUTPUT_FILE: ${{ inputs.output-file }}
|
|
23
|
+
FALLBACK_ISSUE_NUMBER: ${{ inputs.fallback-issue-number }}
|
|
24
|
+
with:
|
|
25
|
+
github-token: ${{ inputs.token }}
|
|
26
|
+
script: |
|
|
27
|
+
const { readAgentItems } = require(process.env.AGENT_OUTPUT_LIB);
|
|
28
|
+
const items = readAgentItems(process.env.OUTPUT_FILE, 'update_issue');
|
|
29
|
+
|
|
30
|
+
let updated = 0;
|
|
31
|
+
|
|
32
|
+
for (const item of items) {
|
|
33
|
+
const target = item.item_number ?? process.env.FALLBACK_ISSUE_NUMBER;
|
|
34
|
+
|
|
35
|
+
if (!target) {
|
|
36
|
+
core.warning(`update_issue item has no item_number and no fallback, skipping: ${JSON.stringify(item)}`);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const changes = {};
|
|
41
|
+
if (item.title) changes.title = item.title;
|
|
42
|
+
if (item.body) changes.body = item.body;
|
|
43
|
+
|
|
44
|
+
if (Object.keys(changes).length === 0) {
|
|
45
|
+
core.warning(`update_issue item changes nothing, skipping: ${JSON.stringify(item)}`);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
await github.rest.issues.update({
|
|
50
|
+
...context.repo,
|
|
51
|
+
issue_number: Number(target),
|
|
52
|
+
...changes,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
updated += 1;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
core.info(`Updated ${updated} issue(s).`);
|
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/validate-review-output/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
-
name: Validate review output
|
|
3
|
-
description: Verify an apply-review outcome accounts for every unresolved review thread.
|
|
4
|
-
inputs:
|
|
5
|
-
output-file:
|
|
6
|
-
description: Path to agent_output.json.
|
|
7
|
-
required: true
|
|
8
|
-
pr-number:
|
|
9
|
-
description: Pull request receiving the review feedback.
|
|
10
|
-
required: true
|
|
11
|
-
review-threads-file:
|
|
12
|
-
description: JSON file containing unresolved review threads.
|
|
13
|
-
required: true
|
|
14
|
-
outputs:
|
|
15
|
-
valid:
|
|
16
|
-
description: Whether the agent output is a valid review outcome.
|
|
17
|
-
value: ${{ steps.validate.outputs.valid }}
|
|
18
|
-
outcome:
|
|
19
|
-
description: "Deterministic review outcome: implemented, already-satisfied, needs-human, or invalid."
|
|
20
|
-
value: ${{ steps.validate.outputs.outcome }}
|
|
21
|
-
runs:
|
|
22
|
-
using: composite
|
|
23
|
-
steps:
|
|
24
|
-
- name: Validate review outcome
|
|
25
|
-
id: validate
|
|
26
|
-
shell: bash
|
|
27
|
-
env:
|
|
28
|
-
OUTPUT_FILE: ${{ inputs.output-file }}
|
|
29
|
-
PR_NUMBER: ${{ inputs.pr-number }}
|
|
30
|
-
REVIEW_THREADS_FILE: ${{ inputs.review-threads-file }}
|
|
31
|
-
run: |
|
|
32
|
-
set -euo pipefail
|
|
33
|
-
outcome="$(bash "${{ github.action_path }}/validate-review-output.sh" "$OUTPUT_FILE" "$PR_NUMBER" "$REVIEW_THREADS_FILE")"
|
|
34
|
-
echo "outcome=$outcome" >> "$GITHUB_OUTPUT"
|
|
35
|
-
echo "valid=$([ "$outcome" != 'invalid' ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
|
1
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/validate-review-output/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
+
name: Validate review output
|
|
3
|
+
description: Verify an apply-review outcome accounts for every unresolved review thread.
|
|
4
|
+
inputs:
|
|
5
|
+
output-file:
|
|
6
|
+
description: Path to agent_output.json.
|
|
7
|
+
required: true
|
|
8
|
+
pr-number:
|
|
9
|
+
description: Pull request receiving the review feedback.
|
|
10
|
+
required: true
|
|
11
|
+
review-threads-file:
|
|
12
|
+
description: JSON file containing unresolved review threads.
|
|
13
|
+
required: true
|
|
14
|
+
outputs:
|
|
15
|
+
valid:
|
|
16
|
+
description: Whether the agent output is a valid review outcome.
|
|
17
|
+
value: ${{ steps.validate.outputs.valid }}
|
|
18
|
+
outcome:
|
|
19
|
+
description: "Deterministic review outcome: implemented, already-satisfied, needs-human, or invalid."
|
|
20
|
+
value: ${{ steps.validate.outputs.outcome }}
|
|
21
|
+
runs:
|
|
22
|
+
using: composite
|
|
23
|
+
steps:
|
|
24
|
+
- name: Validate review outcome
|
|
25
|
+
id: validate
|
|
26
|
+
shell: bash
|
|
27
|
+
env:
|
|
28
|
+
OUTPUT_FILE: ${{ inputs.output-file }}
|
|
29
|
+
PR_NUMBER: ${{ inputs.pr-number }}
|
|
30
|
+
REVIEW_THREADS_FILE: ${{ inputs.review-threads-file }}
|
|
31
|
+
run: |
|
|
32
|
+
set -euo pipefail
|
|
33
|
+
outcome="$(bash "${{ github.action_path }}/validate-review-output.sh" "$OUTPUT_FILE" "$PR_NUMBER" "$REVIEW_THREADS_FILE")"
|
|
34
|
+
echo "outcome=$outcome" >> "$GITHUB_OUTPUT"
|
|
35
|
+
echo "valid=$([ "$outcome" != 'invalid' ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/validate-triage-output/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
-
name: Validate triage output
|
|
3
|
-
description: Verify agent_output.json contains a usable triage verdict before any GitHub writes.
|
|
4
|
-
inputs:
|
|
5
|
-
output-file:
|
|
6
|
-
description: Path to agent_output.json.
|
|
7
|
-
required: true
|
|
8
|
-
issue-number:
|
|
9
|
-
description: Issue number that every triage outcome must target.
|
|
10
|
-
required: true
|
|
11
|
-
outputs:
|
|
12
|
-
valid:
|
|
13
|
-
description: Whether the output contains a usable triage comment with a verdict.
|
|
14
|
-
value: ${{ steps.validate.outputs.valid }}
|
|
15
|
-
outcome:
|
|
16
|
-
description: "Deterministic triage outcome: pass, needs-info, needs-maintainer, block, or invalid."
|
|
17
|
-
value: ${{ steps.validate.outputs.outcome }}
|
|
18
|
-
runs:
|
|
19
|
-
using: composite
|
|
20
|
-
steps:
|
|
21
|
-
- name: Validate triage outcome
|
|
22
|
-
id: validate
|
|
23
|
-
shell: bash
|
|
24
|
-
env:
|
|
25
|
-
ISSUE_NUMBER: ${{ inputs.issue-number }}
|
|
26
|
-
OUTPUT_FILE: ${{ inputs.output-file }}
|
|
27
|
-
run: |
|
|
28
|
-
set -euo pipefail
|
|
29
|
-
|
|
30
|
-
outcome="$(bash "${{ github.action_path }}/validate-triage-output.sh" "$OUTPUT_FILE" "$ISSUE_NUMBER")"
|
|
31
|
-
echo "outcome=$outcome" >> "$GITHUB_OUTPUT"
|
|
32
|
-
echo "valid=$([ "$outcome" != 'invalid' ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
|
33
|
-
|
|
34
|
-
if [ "$outcome" = 'invalid' ]; then
|
|
35
|
-
echo "::warning::Agent output has no usable triage outcome. It will not be applied."
|
|
36
|
-
fi
|
|
1
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/validate-triage-output/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
+
name: Validate triage output
|
|
3
|
+
description: Verify agent_output.json contains a usable triage verdict before any GitHub writes.
|
|
4
|
+
inputs:
|
|
5
|
+
output-file:
|
|
6
|
+
description: Path to agent_output.json.
|
|
7
|
+
required: true
|
|
8
|
+
issue-number:
|
|
9
|
+
description: Issue number that every triage outcome must target.
|
|
10
|
+
required: true
|
|
11
|
+
outputs:
|
|
12
|
+
valid:
|
|
13
|
+
description: Whether the output contains a usable triage comment with a verdict.
|
|
14
|
+
value: ${{ steps.validate.outputs.valid }}
|
|
15
|
+
outcome:
|
|
16
|
+
description: "Deterministic triage outcome: pass, needs-info, needs-maintainer, block, or invalid."
|
|
17
|
+
value: ${{ steps.validate.outputs.outcome }}
|
|
18
|
+
runs:
|
|
19
|
+
using: composite
|
|
20
|
+
steps:
|
|
21
|
+
- name: Validate triage outcome
|
|
22
|
+
id: validate
|
|
23
|
+
shell: bash
|
|
24
|
+
env:
|
|
25
|
+
ISSUE_NUMBER: ${{ inputs.issue-number }}
|
|
26
|
+
OUTPUT_FILE: ${{ inputs.output-file }}
|
|
27
|
+
run: |
|
|
28
|
+
set -euo pipefail
|
|
29
|
+
|
|
30
|
+
outcome="$(bash "${{ github.action_path }}/validate-triage-output.sh" "$OUTPUT_FILE" "$ISSUE_NUMBER")"
|
|
31
|
+
echo "outcome=$outcome" >> "$GITHUB_OUTPUT"
|
|
32
|
+
echo "valid=$([ "$outcome" != 'invalid' ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
|
33
|
+
|
|
34
|
+
if [ "$outcome" = 'invalid' ]; then
|
|
35
|
+
echo "::warning::Agent output has no usable triage outcome. It will not be applied."
|
|
36
|
+
fi
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/verify-composite-actions/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
-
name: Verify composite actions
|
|
3
|
-
description: Check every local composite action manifest parses and uses only contexts a composite action actually has.
|
|
4
|
-
runs:
|
|
5
|
-
using: composite
|
|
6
|
-
steps:
|
|
7
|
-
- name: Validate composite action manifests
|
|
8
|
-
shell: bash
|
|
9
|
-
run: bash "${GITHUB_ACTION_PATH}/verify-composite-actions.sh"
|
|
1
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/verify-composite-actions/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
+
name: Verify composite actions
|
|
3
|
+
description: Check every local composite action manifest parses and uses only contexts a composite action actually has.
|
|
4
|
+
runs:
|
|
5
|
+
using: composite
|
|
6
|
+
steps:
|
|
7
|
+
- name: Validate composite action manifests
|
|
8
|
+
shell: bash
|
|
9
|
+
run: bash "${GITHUB_ACTION_PATH}/verify-composite-actions.sh"
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/verify-refine-output/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
-
name: Verify refine output validation
|
|
3
|
-
description: Exercise deterministic Refine output validation regressions.
|
|
4
|
-
runs:
|
|
5
|
-
using: composite
|
|
6
|
-
steps:
|
|
7
|
-
- name: Verify refinement outcomes
|
|
8
|
-
shell: bash
|
|
9
|
-
run: bash "${{ github.action_path }}/verify-refine-output.sh"
|
|
1
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/verify-refine-output/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
+
name: Verify refine output validation
|
|
3
|
+
description: Exercise deterministic Refine output validation regressions.
|
|
4
|
+
runs:
|
|
5
|
+
using: composite
|
|
6
|
+
steps:
|
|
7
|
+
- name: Verify refinement outcomes
|
|
8
|
+
shell: bash
|
|
9
|
+
run: bash "${{ github.action_path }}/verify-refine-output.sh"
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/verify-route-matrix/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
-
name: Verify route matrix
|
|
3
|
-
description: Exercise the router's classifier against every supported event, and check that each route and dispatch operation has a job in work-router.yml.
|
|
4
|
-
runs:
|
|
5
|
-
using: composite
|
|
6
|
-
steps:
|
|
7
|
-
- name: Run route matrix verification
|
|
8
|
-
shell: bash
|
|
9
|
-
run: bash "${GITHUB_ACTION_PATH}/verify-route-matrix.sh"
|
|
1
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/verify-route-matrix/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
+
name: Verify route matrix
|
|
3
|
+
description: Exercise the router's classifier against every supported event, and check that each route and dispatch operation has a job in work-router.yml.
|
|
4
|
+
runs:
|
|
5
|
+
using: composite
|
|
6
|
+
steps:
|
|
7
|
+
- name: Run route matrix verification
|
|
8
|
+
shell: bash
|
|
9
|
+
run: bash "${GITHUB_ACTION_PATH}/verify-route-matrix.sh"
|
|
@@ -1303,6 +1303,11 @@ if [ -f "${DEPENDABOT_DIR}/action.yml" ] && grep -q '^ housekeeping:$' "$ROUTER
|
|
|
1303
1303
|
db_step=$(printf '%s\n' "$hk_job" | sed -n '/uses: \.\/\.github\/actions\/merge-dependabot/,/^ - /p')
|
|
1304
1304
|
printf '%s' "$db_step" | grep -q 'token: ${{ steps.app-token.outputs.token }}' ||
|
|
1305
1305
|
{ DB_OK=0; echo "FAIL: the maintenance run does not merge Dependabot pull requests with the App token" >&2; }
|
|
1306
|
+
# The App installation may hold no Checks permission: the reads run on the job token.
|
|
1307
|
+
if ! printf '%s' "$db_step" | grep -q 'read-token: ${{ github.token }}' ||
|
|
1308
|
+
! printf '%s\n' "$hk_job" | grep -q '^ checks: read$'; then
|
|
1309
|
+
DB_OK=0; echo "FAIL: the Dependabot merge must read checks with GITHUB_TOKEN granted checks: read" >&2
|
|
1310
|
+
fi
|
|
1306
1311
|
for knob in DEPENDABOT_MERGE DEPENDABOT_MERGE_WITHOUT_CHECKS; do
|
|
1307
1312
|
if [ -z "$(router_env "$knob")" ] || ! printf '%s' "$db_step" | grep -q "env.${knob}"; then
|
|
1308
1313
|
DB_OK=0
|
|
@@ -1911,8 +1916,8 @@ fi
|
|
|
1911
1916
|
if ! grep -qF 'contents/${FRAGMENT}?ref=${DEFAULT_BRANCH}' "${WORKFLOWS_DIR}/shared/opencode-ci.md"; then
|
|
1912
1917
|
LEAD_OK=0; echo "FAIL: the CI config merge must read opencode.ci.json from the default branch" >&2
|
|
1913
1918
|
fi
|
|
1914
|
-
if worker_installed implement && ! grep -qF 'subagent_type: planner' "$IMPLEMENT_WORKER_MD"; then
|
|
1915
|
-
LEAD_OK=0; echo "FAIL: the implement prompt must run propose and archive in the planner agent" >&2
|
|
1919
|
+
if worker_installed implement && ! grep -qF 'subagent_type: planner-sub' "$IMPLEMENT_WORKER_MD"; then
|
|
1920
|
+
LEAD_OK=0; echo "FAIL: the implement prompt must run propose and archive in the planner-sub agent" >&2
|
|
1916
1921
|
fi
|
|
1917
1922
|
if [ "$LEAD_OK" -eq 1 ]; then PASS=$((PASS + 1)); else FAIL=$((FAIL + 1)); fi
|
|
1918
1923
|
# The harness plugins only load with .opencode/node_modules installed; without it every CI run
|