@plainconceptsplatform/workflows 0.20.1 → 0.20.3

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 (50) hide show
  1. package/dist/index.js +0 -0
  2. package/dist/stack-defaults.js +16 -16
  3. package/dist/workflow-catalog.d.ts +1 -1
  4. package/dist/workflow-catalog.js +2 -0
  5. package/loops/actions/add-issue-labels/action.yml +50 -50
  6. package/loops/actions/agent-output.cjs +17 -17
  7. package/loops/actions/apply-agent-bundle/action.yml +24 -24
  8. package/loops/actions/apply-agent-comments/action.yml +42 -42
  9. package/loops/actions/apply-agent-labels/action.yml +55 -55
  10. package/loops/actions/apply-agent-output/action.yml +108 -108
  11. package/loops/actions/classify-route/action.yml +100 -100
  12. package/loops/actions/cleanup-artifacts/action.yml +91 -91
  13. package/loops/actions/close-agent-issues/action.yml +43 -43
  14. package/loops/actions/collect-app-errors/action.yml +297 -0
  15. package/loops/actions/collect-app-errors/group-and-redact.mjs +252 -0
  16. package/loops/actions/collect-app-errors/query-app-errors.sh +104 -0
  17. package/loops/actions/create-agent-issues/action.yml +52 -52
  18. package/loops/actions/create-issue-comment/action.yml +29 -29
  19. package/loops/actions/download-agent-output/action.yml +53 -53
  20. package/loops/actions/housekeeping/action.yml +7 -1
  21. package/loops/actions/link-pr-to-issue/action.yml +40 -40
  22. package/loops/actions/list-open-issues/action.yml +33 -33
  23. package/loops/actions/load-issue-context/action.yml +45 -45
  24. package/loops/actions/merge-agent-pr/action.yml +49 -49
  25. package/loops/actions/push-agent-branch/action.yml +45 -45
  26. package/loops/actions/remove-issue-labels/action.yml +37 -37
  27. package/loops/actions/update-agent-issues/action.yml +58 -58
  28. package/loops/actions/validate-review-output/action.yml +35 -35
  29. package/loops/actions/validate-review-output/validate-review-output.sh +16 -2
  30. package/loops/actions/validate-triage-output/action.yml +36 -36
  31. package/loops/actions/verify-app-errors/action.yml +11 -0
  32. package/loops/actions/verify-app-errors/verify-app-errors.mjs +216 -0
  33. package/loops/actions/verify-composite-actions/action.yml +9 -9
  34. package/loops/actions/verify-refine-output/action.yml +9 -9
  35. package/loops/actions/verify-route-matrix/action.yml +9 -9
  36. package/loops/actions/verify-route-matrix/verify-gate-metrics.mjs +15 -0
  37. package/loops/actions/verify-route-matrix/verify-route-matrix.sh +134 -0
  38. package/loops/scripts/compile-agent-workflows.mjs +331 -331
  39. package/loops/templates/agentics/agentics-app-errors.yml +168 -0
  40. package/loops/templates/agentics/agentics-maintenance.yml +121 -121
  41. package/loops/templates/ci/app-ci-dotnet-next.yml +330 -330
  42. package/loops/templates/ci/app-ci-node-monorepo.yml +260 -260
  43. package/loops/templates/issues/bug_report.yml +109 -109
  44. package/loops/templates/issues/feature_request.yml +75 -75
  45. package/loops/templates/release/github-release.yml +30 -30
  46. package/loops/workflows/authorize-bot-work.yml +105 -105
  47. package/loops/workflows/shared/opencode-ci.md +206 -206
  48. package/loops/workflows/shared/platform-defaults.md +19 -19
  49. package/loops/workflows/work-router.yml +7 -0
  50. package/package.json +9 -8
@@ -1,49 +1,49 @@
1
- # Managed by @plainconceptsplatform/workflows. Source: loops/actions/merge-agent-pr/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
- name: Merge agent pull request
3
- description: Read merge_pull_request from agent_output.json and merge the PR via gh.
4
- inputs:
5
- output-file:
6
- description: Path to agent_output.json.
7
- required: true
8
- token:
9
- description: GitHub token with pull-requests:write and contents:write.
10
- required: true
11
- repo:
12
- description: Target repository in owner/name format.
13
- required: false
14
- default: ${{ github.repository }}
15
- runs:
16
- using: composite
17
- steps:
18
- - name: Merge pull request
19
- shell: bash
20
- env:
21
- GH_TOKEN: ${{ inputs.token }}
22
- OUTPUT_FILE: ${{ inputs.output-file }}
23
- REPO: ${{ inputs.repo }}
24
- run: |
25
- set -euo pipefail
26
- [ -f "$OUTPUT_FILE" ] || exit 0
27
-
28
- jq -c '.items[] | select(.type == "merge_pull_request")' "$OUTPUT_FILE" | while read -r item; do
29
- PR_NUM=$(echo "$item" | jq -er '.pr_number // .item_number // empty')
30
- [[ "$PR_NUM" =~ ^[1-9][0-9]*$ ]] || { echo "::error::Invalid pull request number: $PR_NUM"; exit 1; }
31
-
32
- HEAD_SHA=$(gh pr view "$PR_NUM" --repo "$REPO" --json headRefOid --jq '.headRefOid')
33
- [[ "$HEAD_SHA" =~ ^[0-9a-fA-F]{40}$ ]] || { echo "::error::Invalid pull request head SHA for #$PR_NUM"; exit 1; }
34
-
35
- HEAD_REF=$(gh pr view "$PR_NUM" --repo "$REPO" --json headRefName --jq '.headRefName')
36
-
37
- gh pr merge "$PR_NUM" --repo "$REPO" --squash --match-head-commit "$HEAD_SHA"
38
-
39
- # Delete the branch, because nothing else ever has: four repositories had accumulated
40
- # 47 non-main branches, every one the head of a pull request merged or closed months
41
- # ago. Done after the merge and with its failure swallowed rather than through
42
- # `gh pr merge --delete-branch`, which also tries to delete the local branch and can
43
- # turn a completed merge into a red step. `delete_branch_on_merge` on the repository
44
- # does the same job, but only where someone remembered to switch it on.
45
- if [ -n "$HEAD_REF" ] && [ "$HEAD_REF" != "null" ]; then
46
- gh api -X DELETE "repos/$REPO/git/refs/heads/$HEAD_REF" >/dev/null 2>&1 ||
47
- echo "::notice::branch $HEAD_REF was already gone or is protected"
48
- fi
49
- done
1
+ # Managed by @plainconceptsplatform/workflows. Source: loops/actions/merge-agent-pr/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
+ name: Merge agent pull request
3
+ description: Read merge_pull_request from agent_output.json and merge the PR via gh.
4
+ inputs:
5
+ output-file:
6
+ description: Path to agent_output.json.
7
+ required: true
8
+ token:
9
+ description: GitHub token with pull-requests:write and contents:write.
10
+ required: true
11
+ repo:
12
+ description: Target repository in owner/name format.
13
+ required: false
14
+ default: ${{ github.repository }}
15
+ runs:
16
+ using: composite
17
+ steps:
18
+ - name: Merge pull request
19
+ shell: bash
20
+ env:
21
+ GH_TOKEN: ${{ inputs.token }}
22
+ OUTPUT_FILE: ${{ inputs.output-file }}
23
+ REPO: ${{ inputs.repo }}
24
+ run: |
25
+ set -euo pipefail
26
+ [ -f "$OUTPUT_FILE" ] || exit 0
27
+
28
+ jq -c '.items[] | select(.type == "merge_pull_request")' "$OUTPUT_FILE" | while read -r item; do
29
+ PR_NUM=$(echo "$item" | jq -er '.pr_number // .item_number // empty')
30
+ [[ "$PR_NUM" =~ ^[1-9][0-9]*$ ]] || { echo "::error::Invalid pull request number: $PR_NUM"; exit 1; }
31
+
32
+ HEAD_SHA=$(gh pr view "$PR_NUM" --repo "$REPO" --json headRefOid --jq '.headRefOid')
33
+ [[ "$HEAD_SHA" =~ ^[0-9a-fA-F]{40}$ ]] || { echo "::error::Invalid pull request head SHA for #$PR_NUM"; exit 1; }
34
+
35
+ HEAD_REF=$(gh pr view "$PR_NUM" --repo "$REPO" --json headRefName --jq '.headRefName')
36
+
37
+ gh pr merge "$PR_NUM" --repo "$REPO" --squash --match-head-commit "$HEAD_SHA"
38
+
39
+ # Delete the branch, because nothing else ever has: four repositories had accumulated
40
+ # 47 non-main branches, every one the head of a pull request merged or closed months
41
+ # ago. Done after the merge and with its failure swallowed rather than through
42
+ # `gh pr merge --delete-branch`, which also tries to delete the local branch and can
43
+ # turn a completed merge into a red step. `delete_branch_on_merge` on the repository
44
+ # does the same job, but only where someone remembered to switch it on.
45
+ if [ -n "$HEAD_REF" ] && [ "$HEAD_REF" != "null" ]; then
46
+ gh api -X DELETE "repos/$REPO/git/refs/heads/$HEAD_REF" >/dev/null 2>&1 ||
47
+ echo "::notice::branch $HEAD_REF was already gone or is protected"
48
+ fi
49
+ done
@@ -1,45 +1,45 @@
1
- # Managed by @plainconceptsplatform/workflows. Source: loops/actions/push-agent-branch/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
- name: Push agent branch
3
- description: Read push_to_pull_request_branch from agent_output.json and fast-forward from its git bundle.
4
- inputs:
5
- output-file:
6
- description: Path to agent_output.json.
7
- required: true
8
- bundle-file:
9
- description: Path to the git bundle from the agent artifact.
10
- required: false
11
- default: ''
12
- token:
13
- description: GitHub token with contents:write.
14
- required: true
15
- runs:
16
- using: composite
17
- steps:
18
- - name: Configure git credentials
19
- shell: bash
20
- env:
21
- GH_TOKEN: ${{ inputs.token }}
22
- run: |
23
- git config --global url."https://x-access-token:${GH_TOKEN}@github.com/".insteadOf "https://github.com/"
24
- - name: Read pull request branch
25
- id: request
26
- shell: bash
27
- env:
28
- GH_TOKEN: ${{ inputs.token }}
29
- OUTPUT_FILE: ${{ inputs.output-file }}
30
- BUNDLE_FILE: ${{ inputs.bundle-file }}
31
- run: |
32
- set -euo pipefail
33
- [ -f "$OUTPUT_FILE" ] || exit 0
34
-
35
- ITEM=$(jq -c '.items[] | select(.type == "push_to_pull_request_branch")' "$OUTPUT_FILE")
36
- [ -n "$ITEM" ] || exit 0
37
-
38
- BRANCH=$(echo "$ITEM" | jq -r '.branch')
39
- echo "branch=$BRANCH" >> "$GITHUB_OUTPUT"
40
- - name: Apply bundle to pull request branch
41
- if: steps.request.outputs.branch != '' && inputs.bundle-file != ''
42
- uses: ./.github/actions/apply-agent-bundle
43
- with:
44
- bundle-file: ${{ inputs.bundle-file }}
45
- target-branch: ${{ steps.request.outputs.branch }}
1
+ # Managed by @plainconceptsplatform/workflows. Source: loops/actions/push-agent-branch/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
+ name: Push agent branch
3
+ description: Read push_to_pull_request_branch from agent_output.json and fast-forward from its git bundle.
4
+ inputs:
5
+ output-file:
6
+ description: Path to agent_output.json.
7
+ required: true
8
+ bundle-file:
9
+ description: Path to the git bundle from the agent artifact.
10
+ required: false
11
+ default: ''
12
+ token:
13
+ description: GitHub token with contents:write.
14
+ required: true
15
+ runs:
16
+ using: composite
17
+ steps:
18
+ - name: Configure git credentials
19
+ shell: bash
20
+ env:
21
+ GH_TOKEN: ${{ inputs.token }}
22
+ run: |
23
+ git config --global url."https://x-access-token:${GH_TOKEN}@github.com/".insteadOf "https://github.com/"
24
+ - name: Read pull request branch
25
+ id: request
26
+ shell: bash
27
+ env:
28
+ GH_TOKEN: ${{ inputs.token }}
29
+ OUTPUT_FILE: ${{ inputs.output-file }}
30
+ BUNDLE_FILE: ${{ inputs.bundle-file }}
31
+ run: |
32
+ set -euo pipefail
33
+ [ -f "$OUTPUT_FILE" ] || exit 0
34
+
35
+ ITEM=$(jq -c '.items[] | select(.type == "push_to_pull_request_branch")' "$OUTPUT_FILE")
36
+ [ -n "$ITEM" ] || exit 0
37
+
38
+ BRANCH=$(echo "$ITEM" | jq -r '.branch')
39
+ echo "branch=$BRANCH" >> "$GITHUB_OUTPUT"
40
+ - name: Apply bundle to pull request branch
41
+ if: steps.request.outputs.branch != '' && inputs.bundle-file != ''
42
+ uses: ./.github/actions/apply-agent-bundle
43
+ with:
44
+ bundle-file: ${{ inputs.bundle-file }}
45
+ target-branch: ${{ steps.request.outputs.branch }}
@@ -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"
@@ -15,16 +15,30 @@ if [ ! -f "$output_file" ] || [ ! -f "$threads_file" ] \
15
15
  exit 0
16
16
  fi
17
17
 
18
+ # Every jq error becomes "invalid" rather than a non-zero exit, the way the merge gate's
19
+ # validator already does it. A crash here fails the step, which skips the apply job and parks
20
+ # the issue as stalled -- while the agent's fix is already pushed and sitting on the branch.
21
+ # Output the workflow cannot read is an outcome to report, not a reason to fall over.
18
22
  jq -r --arg pr "$pr_number" --slurpfile threads "$threads_file" '
23
+ def decide:
19
24
  .items as $items
20
25
  | ($threads[0] | map(select(.isResolved == false and .isOutdated == false) | .id) | sort) as $expected
21
26
  | ($items | [.[] | select(.type == "add_comment" and (.item_number | tostring) == $pr and (.body | type == "string"))]) as $comments
22
27
  | ($comments | map(.body | if test("\\*\\*Review outcome:\\*\\*\\s*(implemented|already-satisfied|needs-human)"; "i") then capture("\\*\\*Review outcome:\\*\\*\\s*(?<v>implemented|already-satisfied|needs-human)"; "i").v | ascii_downcase else empty end) | .[0] // "invalid") as $outcome
23
- | ($comments | map(.body | scan("PRRT_[A-Za-z0-9_=-]+")) | add | unique | sort) as $reported
28
+ # `add` on an empty array is null, not [], and `unique` then dies with "Cannot iterate over
29
+ # null". That is not an edge case: it is every pull-request-level review. Requesting changes
30
+ # from the Files tab, or a plain review body with no inline comment, creates a review and no
31
+ # thread, so both sides of this comparison are empty and the validator crashed instead of
32
+ # agreeing. The agent had already read the feedback, pushed the fix and reported it; the run
33
+ # was marked failed, the issue was labelled stalled, and the board said nothing landed while
34
+ # the commit sat on the branch.
35
+ | ($comments | map(.body | [scan("PRRT_[A-Za-z0-9_=-]+")]) | add // [] | unique | sort) as $reported
24
36
  | ([$items[] | select(.type == "push_to_pull_request_branch")] | length) as $pushes
25
37
  | if $expected != $reported then "invalid"
26
38
  elif $outcome == "implemented" and $pushes == 1 then "implemented"
27
39
  elif ($outcome == "already-satisfied" or $outcome == "needs-human") and $pushes == 0 then $outcome
28
40
  else "invalid"
29
- end
41
+ end;
42
+
43
+ try decide catch "invalid"
30
44
  ' "$output_file"
@@ -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
@@ -0,0 +1,11 @@
1
+ # Managed by @plainconceptsplatform/workflows. Source: loops/actions/verify-app-errors/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
+ name: Verify application error collection
3
+ description: >-
4
+ Run the grouping and redaction that collect-app-errors ships, against rows shaped the way
5
+ Application Insights returns them. It executes the module rather than reading it.
6
+ runs:
7
+ using: composite
8
+ steps:
9
+ - name: Group and redact, for real
10
+ shell: bash
11
+ run: node "${GITHUB_ACTION_PATH}/verify-app-errors.mjs"