@plainconceptsplatform/workflows 0.17.0 → 0.20.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.
Files changed (60) hide show
  1. package/dist/catalog-installation.js +25 -13
  2. package/dist/stack-defaults.js +16 -16
  3. package/dist/worker-env.js +24 -21
  4. package/loops/actions/add-issue-labels/action.yml +50 -50
  5. package/loops/actions/agent-output.cjs +17 -17
  6. package/loops/actions/apply-agent-bundle/action.yml +24 -24
  7. package/loops/actions/apply-agent-comments/action.yml +42 -42
  8. package/loops/actions/apply-agent-labels/action.yml +55 -55
  9. package/loops/actions/apply-agent-output/action.yml +108 -108
  10. package/loops/actions/assess-blast-radius/action.yml +148 -0
  11. package/loops/actions/assess-blast-radius/assess-blast-radius.sh +138 -0
  12. package/loops/actions/classify-route/action.yml +100 -100
  13. package/loops/actions/cleanup-artifacts/action.yml +91 -91
  14. package/loops/actions/close-agent-issues/action.yml +43 -43
  15. package/loops/actions/create-agent-issues/action.yml +52 -52
  16. package/loops/actions/create-issue-comment/action.yml +29 -29
  17. package/loops/actions/download-agent-output/action.yml +53 -53
  18. package/loops/actions/housekeeping/action.yml +55 -2
  19. package/loops/actions/identify-gate-subject/action.yml +134 -122
  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/require-open-issue/action.yml +59 -0
  27. package/loops/actions/update-agent-issues/action.yml +58 -58
  28. package/loops/actions/validate-merge-gate-output/action.yml +62 -40
  29. package/loops/actions/validate-merge-gate-output/validate-merge-gate-output.sh +147 -31
  30. package/loops/actions/validate-refine-output/action.yml +48 -44
  31. package/loops/actions/validate-refine-output/validate-refine-output.sh +15 -4
  32. package/loops/actions/validate-review-output/action.yml +35 -35
  33. package/loops/actions/validate-triage-output/action.yml +36 -36
  34. package/loops/actions/verify-composite-actions/action.yml +9 -9
  35. package/loops/actions/verify-refine-output/action.yml +9 -9
  36. package/loops/actions/verify-refine-output/verify-refine-output.sh +6 -1
  37. package/loops/actions/verify-route-matrix/action.yml +9 -9
  38. package/loops/actions/verify-route-matrix/verify-gate-metrics.mjs +51 -0
  39. package/loops/actions/verify-route-matrix/verify-route-matrix.sh +630 -8
  40. package/loops/scripts/compile-agent-workflows.mjs +331 -331
  41. package/loops/templates/agentics/agentics-maintenance.yml +121 -121
  42. package/loops/templates/ci/app-ci-dotnet-next.yml +330 -330
  43. package/loops/templates/ci/app-ci-node-monorepo.yml +260 -260
  44. package/loops/templates/issues/bug_report.yml +109 -109
  45. package/loops/templates/issues/feature_request.yml +75 -75
  46. package/loops/templates/opencode/opencode.ci.json +55 -49
  47. package/loops/templates/opencode/opencode.ci.json.md +59 -49
  48. package/loops/templates/release/github-release.yml +30 -30
  49. package/loops/workflows/agent-apply-review.md +33 -3
  50. package/loops/workflows/agent-audit.md +36 -0
  51. package/loops/workflows/agent-implement.md +77 -5
  52. package/loops/workflows/agent-merge-gate.md +368 -148
  53. package/loops/workflows/agent-refine.md +93 -17
  54. package/loops/workflows/agent-release.md +4 -4
  55. package/loops/workflows/agent-triage.md +33 -1
  56. package/loops/workflows/authorize-bot-work.yml +105 -105
  57. package/loops/workflows/shared/opencode-ci.md +206 -206
  58. package/loops/workflows/shared/platform-defaults.md +19 -19
  59. package/loops/workflows/work-router.yml +25 -11
  60. package/package.json +2 -2
@@ -151,11 +151,17 @@ export async function installCatalog(repositoryPath, options = {}) {
151
151
  // (the shared CI setup and the OpenCode config) get them on every run and stay stable. A worker
152
152
  // gets its VERIFY_COMMANDS default once, when it is first installed: after that the value is the
153
153
  // consumer's, and the env merge keeps it.
154
+ //
155
+ // Except when the consumer has no value. "First install only" meant a worker installed before
156
+ // this injection existed kept `VERIFY_COMMANDS: ""` for good, and mergeWorkerEnv then preserved
157
+ // the empty string as the consumer's deliberate choice. All four consuming repositories were in
158
+ // that state, so every merge-gate repair pushed a fix with the verification block interpolating
159
+ // to nothing. An empty value is the absence of a choice, so an update fills it.
154
160
  function applyStackDefaults(files, inspection, existing) {
155
161
  const defaults = generateStackDefaults(inspection);
156
162
  const result = new Map(files);
157
163
  for (const [target, content] of result) {
158
- if (isWorker(target) && !existing.has(target)) {
164
+ if (isWorker(target) && (!existing.has(target) || hasEmptyVerifyCommands(existing.get(target)))) {
159
165
  result.set(target, injectStackEnv(content, defaults));
160
166
  }
161
167
  else if (target.endsWith("opencode-ci.md")) {
@@ -167,6 +173,11 @@ function applyStackDefaults(files, inspection, existing) {
167
173
  }
168
174
  return result;
169
175
  }
176
+ // A worker whose VERIFY_COMMANDS is present but empty. Anything else, including a value the
177
+ // consumer wrote, is left alone.
178
+ function hasEmptyVerifyCommands(current) {
179
+ return typeof current === "string" && /^ {2}VERIFY_COMMANDS: *(""|'')? *$/m.test(current);
180
+ }
170
181
  // Directories that belong wholly to the package. A file here that carries our ownership header
171
182
  // and is no longer in the package was deleted upstream, and without this it would sit in every
172
183
  // consumer forever: that is how `stale-recovery` and `update-changelog` outlived the code that
@@ -306,18 +317,19 @@ async function preCommitHookUpdate(repositoryPath) {
306
317
  }
307
318
  const content = (await readFile(hookPath, "utf8"))
308
319
  .replace("pnpm exec if git diff --cached --name-only -- .github | grep -q .; then", "if git diff --cached --name-only -- .github | grep -q .; then");
309
- if (content.includes("compile-agent-workflows")) {
310
- const legacyLines = `${compileLine}\n${stageLine}\n${actionLockLine}\n`;
311
- if (content.includes(managedLines))
312
- return { target, content };
313
- if (content.includes(legacyLines))
314
- return { target, content: content.replace(legacyLines, managedLines) };
315
- const suffix = content.endsWith("\n") || content === "" ? "" : "\n";
316
- return { target, content: `${content}${suffix}${managedLines}` };
317
- }
318
- return { target, content: content.endsWith("\n") || content === ""
319
- ? `${content}${managedLines}`
320
- : `${content}\n${managedLines}` };
320
+ // Replace whatever managed block is there, never append beside it. The previous version
321
+ // recognised only two exact shapes -- the current block and the pre-`if` legacy lines -- and
322
+ // appended when it matched neither. A consumer holding an *older* wrapped block therefore
323
+ // gained a second one, so the compiler ran twice on every commit and one of the two staged a
324
+ // lock path that exists nowhere. All five consumers were carrying two blocks by the time this
325
+ // was noticed, and every future edit to `managedLines` would have added another. Nothing
326
+ // failed: running the compiler twice is only wasteful, so it never surfaced.
327
+ const managedBlock = /^[ \t]*if git diff --cached --name-only -- \.github \| grep -q \.; then\n(?:.*\n)*?[ \t]*fi\n?/gm;
328
+ const legacyLines = `${compileLine}\n${stageLine}\n${actionLockLine}\n`;
329
+ let stripped = content.replace(managedBlock, block => block.includes("compile-agent-workflows") ? "" : block);
330
+ stripped = stripped.split(legacyLines).join("");
331
+ const body = stripped.replace(/\n{3,}/g, "\n\n").replace(/\s+$/, "");
332
+ return { target, content: body === "" ? managedLines : `${body}\n${managedLines}` };
321
333
  }
322
334
  export async function runCompileIfAvailable(repositoryPath) {
323
335
  const script = join(repositoryPath, "scripts", "compile-agent-workflows.mjs");
@@ -31,31 +31,31 @@ export function generateOpencodeCi(baseContent, inspection) {
31
31
  if (inspection.stackHints.solutionFiles.length > 0) {
32
32
  const solutionPath = inspection.stackHints.solutionFiles[0].replaceAll("\\", "/");
33
33
  if (!result.includes("name: Cache NuGet packages")) {
34
- const nugetCache = ` - name: Cache NuGet packages
35
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
36
- with:
37
- path: ~/.nuget/packages
38
- key: nuget-\${{ runner.os }}-\${{ hashFiles('**/*.slnx', '**/Directory.Packages.props') }}
39
- restore-keys: nuget-\${{ runner.os }}-
40
-
34
+ const nugetCache = ` - name: Cache NuGet packages
35
+ uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
36
+ with:
37
+ path: ~/.nuget/packages
38
+ key: nuget-\${{ runner.os }}-\${{ hashFiles('**/*.slnx', '**/Directory.Packages.props') }}
39
+ restore-keys: nuget-\${{ runner.os }}-
40
+
41
41
  `;
42
42
  result = insertBeforeMarker(result, nugetCache, " - name: Install workspace dependencies");
43
43
  }
44
44
  if (!result.includes("name: Restore .NET dependencies")) {
45
- const restore = ` - name: Restore .NET dependencies
46
- run: dotnet restore ${solutionPath}
47
-
45
+ const restore = ` - name: Restore .NET dependencies
46
+ run: dotnet restore ${solutionPath}
47
+
48
48
  `;
49
49
  result = insertBeforeMarker(result, restore, " - name: Install workspace dependencies");
50
50
  }
51
51
  }
52
52
  if (inspection.stackHints.openSpec && !result.includes("name: Install OpenSpec CLI")) {
53
- const openspecStep = ` - name: Install OpenSpec CLI
54
- run: |
55
- set -euo pipefail
56
- npm install -g "@fission-ai/openspec@1.8.0"
57
- openspec --version
58
-
53
+ const openspecStep = ` - name: Install OpenSpec CLI
54
+ run: |
55
+ set -euo pipefail
56
+ npm install -g "@fission-ai/openspec@1.8.0"
57
+ openspec --version
58
+
59
59
  `;
60
60
  result = insertBeforeMarker(result, openspecStep, " - name: Install workspace dependencies");
61
61
  }
@@ -74,6 +74,10 @@ export function entryValue(entry) {
74
74
  const first = entry.lines[0].replace(ENTRY, "$2").trim();
75
75
  return [first, ...entry.lines.slice(1).map((line) => line.trim())].join("\n").trim();
76
76
  }
77
+ // A value that is present but says nothing: empty, or empty quotes.
78
+ function isBlank(value) {
79
+ return value === "" || value === '""' || value === "''";
80
+ }
77
81
  export function mergeWorkerEnv(packageContent, consumerContent, baselineContent, parse = parseWorkerEnv) {
78
82
  const report = { keptEnv: [], updatedDefaults: [], consumerOnlyEnv: [], droppedEnv: [] };
79
83
  const pkg = parse(packageContent);
@@ -91,6 +95,16 @@ export function mergeWorkerEnv(packageContent, consumerContent, baselineContent,
91
95
  merged.push(entry);
92
96
  continue;
93
97
  }
98
+ // An empty value is the absence of a choice, not a choice of nothing. Treating it as the
99
+ // consumer's is how `VERIFY_COMMANDS: ""` survived in all four consuming repositories with
100
+ // no baseline to compare against, leaving the merge gate pushing repairs it had verified
101
+ // with an empty command block. If the package has something and the consumer has nothing,
102
+ // the package wins.
103
+ if (isBlank(entryValue(mine)) && !isBlank(entryValue(entry))) {
104
+ merged.push(entry);
105
+ report.updatedDefaults.push(entry.key);
106
+ continue;
107
+ }
94
108
  const base = baselineByKey.get(entry.key);
95
109
  if (base !== undefined && entryValue(base) === entryValue(mine)) {
96
110
  // Still the previous default: the package changed it, the consumer never did.
@@ -120,26 +134,15 @@ export function mergeWorkerEnv(packageContent, consumerContent, baselineContent,
120
134
  function engineEndpoint(content) {
121
135
  return /^ OPENAI_BASE_URL: (.+)$/m.exec(content)?.[1];
122
136
  }
123
- // Which self-hosted pool a worker's agent jobs run on. gh-aw cannot share runs-on through an
124
- // import, so every worker names it. ubuntu-latest is excluded, being GitHub's own runner, used
125
- // by the deterministic jobs everywhere and chosen by nobody.
126
- function runnerPools(content) {
127
- const found = new Set();
128
- for (const match of content.matchAll(/^\s*runs-on(?:-slim)?: (\S+)\s*$/gm)) {
129
- if (match[1] !== "ubuntu-latest")
130
- found.add(match[1]);
131
- }
132
- return [...found];
133
- }
134
- function preserveRunnerPool(packageContent, consumerContent) {
135
- const mine = runnerPools(consumerContent);
136
- const theirs = runnerPools(packageContent);
137
- // Only an unambiguous swap. A consumer naming several pools has drifted rather than
138
- // decided, and guessing which it meant is worse than leaving the package's.
139
- if (mine.length !== 1 || theirs.length !== 1 || mine[0] === theirs[0])
140
- return packageContent;
141
- return packageContent.replace(/^(\s*runs-on(?:-slim)?: )(\S+)(\s*)$/gm, (line, prefix, pool, tail) => pool === "ubuntu-latest" ? line : `${prefix}${mine[0]}${tail}`);
142
- }
137
+ // The runner pool used to be preserved from the consumer here: a repository naming a single
138
+ // non-ubuntu pool had every worker rewritten to it. That is how Pliny-Bot ended up running
139
+ // triage, refine, implement, apply-review and audit on RunnerLandingZone while its merge-gate
140
+ // and release stayed on agents-arc -- the pool was applied to the workers installed at the time
141
+ // and never to the ones added later, and nothing reported the split. The pool is package-owned
142
+ // now: the agent jobs belong on the Azure fleet (agents-arc, runner group `agentic`), and the
143
+ // router and release worker name RunnerLandingZone. The route matrix asserts both, so a repo
144
+ // cannot drift back without a red run. Per the standing rule for this package, anything a
145
+ // consumer really must vary belongs in `env:` at the top of the file, not in `runs-on`.
143
146
  // GitHub evaluates no expression in a `workflow_run.workflows:` list or in a `cron:`, so the two
144
147
  // router values that are also needed there cannot be read from `env:` at those two lines. The
145
148
  // installer copies them in instead, which is what keeps the pair in step across an update; the
@@ -175,5 +178,5 @@ export function mergeWorker(packageContent, consumerContent, baselineContent) {
175
178
  if (endpoint !== undefined) {
176
179
  result = result.replace(/^ OPENAI_BASE_URL: .+$/m, ` OPENAI_BASE_URL: ${endpoint}`);
177
180
  }
178
- return { content: preserveRunnerPool(result, consumerContent), report };
181
+ return { content: result, report };
179
182
  }
@@ -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}.`);