@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.
Files changed (41) hide show
  1. package/dist/worker-env.js +2 -3
  2. package/loops/actions/add-issue-labels/action.yml +50 -50
  3. package/loops/actions/agent-output.cjs +17 -17
  4. package/loops/actions/apply-agent-bundle/action.yml +24 -24
  5. package/loops/actions/apply-agent-comments/action.yml +42 -42
  6. package/loops/actions/apply-agent-labels/action.yml +55 -55
  7. package/loops/actions/apply-agent-output/action.yml +5 -0
  8. package/loops/actions/cleanup-artifacts/action.yml +91 -91
  9. package/loops/actions/close-agent-issues/action.yml +43 -43
  10. package/loops/actions/create-agent-issues/action.yml +64 -3
  11. package/loops/actions/create-issue-comment/action.yml +29 -29
  12. package/loops/actions/download-agent-output/action.yml +53 -53
  13. package/loops/actions/housekeeping/action.yml +95 -1
  14. package/loops/actions/link-pr-to-issue/action.yml +40 -40
  15. package/loops/actions/list-open-issues/action.yml +33 -33
  16. package/loops/actions/load-issue-context/action.yml +45 -45
  17. package/loops/actions/merge-agent-pr/action.yml +49 -49
  18. package/loops/actions/push-agent-branch/action.yml +45 -45
  19. package/loops/actions/remove-issue-labels/action.yml +37 -37
  20. package/loops/actions/update-agent-issues/action.yml +58 -58
  21. package/loops/actions/validate-refine-output/validate-refine-output.sh +30 -1
  22. package/loops/actions/validate-review-output/action.yml +35 -35
  23. package/loops/actions/validate-triage-output/action.yml +36 -36
  24. package/loops/actions/verify-composite-actions/action.yml +9 -9
  25. package/loops/actions/verify-refine-output/action.yml +9 -9
  26. package/loops/actions/verify-refine-output/verify-refine-output.sh +17 -0
  27. package/loops/actions/verify-route-matrix/action.yml +9 -9
  28. package/loops/actions/verify-route-matrix/verify-route-matrix.sh +214 -7
  29. package/loops/docs/diagrams.md +90 -12
  30. package/loops/scripts/compile-agent-workflows.mjs +76 -2
  31. package/loops/templates/agentics/agentics-maintenance.yml +121 -121
  32. package/loops/templates/issues/bug_report.yml +109 -109
  33. package/loops/templates/issues/feature_request.yml +75 -75
  34. package/loops/templates/release/github-release.yml +30 -30
  35. package/loops/workflows/agent-implement.md +37 -7
  36. package/loops/workflows/agent-merge-gate.md +74 -6
  37. package/loops/workflows/agent-refine.md +152 -5
  38. package/loops/workflows/authorize-bot-work.yml +105 -105
  39. package/loops/workflows/shared/platform-defaults.md +23 -23
  40. package/loops/workflows/work-router.yml +330 -2
  41. package/package.json +1 -1
@@ -1,45 +1,45 @@
1
- # Managed by @plainconceptsplatform/workflows. Source: loops/actions/load-issue-context/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
- name: Load issue context
3
- description: Write issue metadata and comment context to a JSON file.
4
- inputs:
5
- token:
6
- description: GitHub token used by github-script.
7
- required: true
8
- issue-number:
9
- description: Issue number to load.
10
- required: true
11
- output-path:
12
- description: Absolute path where context JSON is written.
13
- required: true
14
- runs:
15
- using: composite
16
- steps:
17
- - name: Load issue and comments
18
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
19
- env:
20
- ISSUE_NUMBER: ${{ inputs.issue-number }}
21
- OUTPUT_PATH: ${{ inputs.output-path }}
22
- with:
23
- github-token: ${{ inputs.token }}
24
- script: |
25
- const fs = require('fs');
26
- const issueNumber = Number(process.env.ISSUE_NUMBER);
27
- const issue = await github.rest.issues.get({ ...context.repo, issue_number: issueNumber });
28
- const comments = await github.paginate(github.rest.issues.listComments, {
29
- ...context.repo, issue_number: issueNumber, per_page: 100,
30
- });
31
-
32
- fs.mkdirSync(require('path').dirname(process.env.OUTPUT_PATH), { recursive: true });
33
- fs.writeFileSync(process.env.OUTPUT_PATH, JSON.stringify({
34
- number: issue.data.number,
35
- title: issue.data.title,
36
- body: issue.data.body,
37
- author: issue.data.user.login,
38
- authorAssociation: issue.data.author_association,
39
- labels: issue.data.labels.map(({ name }) => name),
40
- comments: comments.map(({ user, author_association, body }) => ({
41
- author: user.login,
42
- association: author_association,
43
- body,
44
- })),
45
- }, null, 2));
1
+ # Managed by @plainconceptsplatform/workflows. Source: loops/actions/load-issue-context/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
+ name: Load issue context
3
+ description: Write issue metadata and comment context to a JSON file.
4
+ inputs:
5
+ token:
6
+ description: GitHub token used by github-script.
7
+ required: true
8
+ issue-number:
9
+ description: Issue number to load.
10
+ required: true
11
+ output-path:
12
+ description: Absolute path where context JSON is written.
13
+ required: true
14
+ runs:
15
+ using: composite
16
+ steps:
17
+ - name: Load issue and comments
18
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
19
+ env:
20
+ ISSUE_NUMBER: ${{ inputs.issue-number }}
21
+ OUTPUT_PATH: ${{ inputs.output-path }}
22
+ with:
23
+ github-token: ${{ inputs.token }}
24
+ script: |
25
+ const fs = require('fs');
26
+ const issueNumber = Number(process.env.ISSUE_NUMBER);
27
+ const issue = await github.rest.issues.get({ ...context.repo, issue_number: issueNumber });
28
+ const comments = await github.paginate(github.rest.issues.listComments, {
29
+ ...context.repo, issue_number: issueNumber, per_page: 100,
30
+ });
31
+
32
+ fs.mkdirSync(require('path').dirname(process.env.OUTPUT_PATH), { recursive: true });
33
+ fs.writeFileSync(process.env.OUTPUT_PATH, JSON.stringify({
34
+ number: issue.data.number,
35
+ title: issue.data.title,
36
+ body: issue.data.body,
37
+ author: issue.data.user.login,
38
+ authorAssociation: issue.data.author_association,
39
+ labels: issue.data.labels.map(({ name }) => name),
40
+ comments: comments.map(({ user, author_association, body }) => ({
41
+ author: user.login,
42
+ association: author_association,
43
+ body,
44
+ })),
45
+ }, null, 2));
@@ -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).`);
@@ -60,6 +60,27 @@ jq -r --arg marker "$marker" --arg prefix "$comment_prefix" --arg issue "$issue_
60
60
  ) or
61
61
  .type == "create_issue");
62
62
 
63
+ # A stack split also opens the root pull request: the one pull request refine may open, and
64
+ # only alongside the children it integrates. Lyceum #45 emitted the parent body, two children
65
+ # and that root, and was called invalid because a pull request is not a source item, so no
66
+ # stack split had ever been accepted. Complete and questions keep has_only_source_items, so a
67
+ # pull request anywhere but a split is still invalid.
68
+ def root_pr_count:
69
+ [.items[] | select(.type == "create_pull_request")] | length;
70
+
71
+ def has_only_split_items:
72
+ all(.items[] | select(is_run_signal | not);
73
+ (
74
+ .type == "update_issue" and
75
+ (.item_number == null or (.item_number | tostring) == $issue)
76
+ ) or
77
+ (
78
+ .type == "add_comment" and
79
+ (.item_number | tostring) == $issue
80
+ ) or
81
+ .type == "create_issue" or
82
+ .type == "create_pull_request");
83
+
63
84
  # The agent saying it did not finish. Survivable when it left real work behind, never
64
85
  # when all it left was a comment: a failed run must not be read as a question, or the
65
86
  # loop waits for an answer to something nobody asked.
@@ -77,7 +98,15 @@ jq -r --arg marker "$marker" --arg prefix "$comment_prefix" --arg issue "$issue_
77
98
  # complete refinement. A body carrying the draft marker is a temporal draft, not a
78
99
  # finished refinement: it only counts as the questions outcome when the matching
79
100
  # batched-questions comment is also there.
80
- if child_count >= 2 and has_replacement_body and has_only_source_items then "split"
101
+ #
102
+ # A comment with no update at all is a question asked before anything was drafted, which
103
+ # the worker is allowed to do. A comment alongside an update that carries no draft is not:
104
+ # that is the shape of a run that called update_issue and delivered no body, which Lyceum
105
+ # #44 produced when the safe-outputs bridge passed 19 bytes instead of the 16,175 the
106
+ # agent piped in. Requiring the draft unconditionally also rejected the first shape and
107
+ # kept this branch red for eleven days; the two are told apart by has_update, not by
108
+ # demanding a draft from both.
109
+ if child_count >= 2 and has_replacement_body and has_only_split_items and root_pr_count <= 1 then "split"
81
110
  elif child_count > 0 then "invalid"
82
111
  elif has_replacement_body and (has_draft_body | not) and has_only_source_items then "complete"
83
112
  elif has_clarification and ((has_update | not) or has_draft_body) and has_only_source_items
@@ -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"