@plainconceptsplatform/workflows 1.9.0 → 1.10.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 (49) hide show
  1. package/README.md +1 -1
  2. package/dist/catalog-installation.js +2 -2
  3. package/dist/package-baseline.js +4 -1
  4. package/dist/route-processing.js +4 -1
  5. package/dist/worker-env.d.ts +1 -1
  6. package/dist/worker-env.js +6 -2
  7. package/dist/workflow-catalog.d.ts +3 -1
  8. package/dist/workflow-catalog.js +6 -0
  9. package/loops/actions/add-issue-labels/action.yml +50 -50
  10. package/loops/actions/agent-output.cjs +17 -17
  11. package/loops/actions/apply-agent-bundle/action.yml +24 -24
  12. package/loops/actions/apply-agent-comments/action.yml +42 -42
  13. package/loops/actions/apply-agent-labels/action.yml +55 -55
  14. package/loops/actions/audit-close/action.yml +13 -6
  15. package/loops/actions/classify-route/classify-route.sh +3 -1
  16. package/loops/actions/cleanup-artifacts/action.yml +91 -91
  17. package/loops/actions/close-agent-issues/action.yml +43 -43
  18. package/loops/actions/collect-doc-drift/action.yml +29 -0
  19. package/loops/actions/collect-doc-drift/collect-doc-drift.mjs +133 -0
  20. package/loops/actions/create-issue-comment/action.yml +29 -29
  21. package/loops/actions/download-agent-output/action.yml +53 -53
  22. package/loops/actions/identify-gate-subject/action.yml +4 -0
  23. package/loops/actions/land-pull-request/action.yml +10 -0
  24. package/loops/actions/land-pull-request/land-pull-request.sh +14 -0
  25. package/loops/actions/link-pr-to-issue/action.yml +40 -40
  26. package/loops/actions/list-open-issues/action.yml +33 -33
  27. package/loops/actions/load-issue-context/action.yml +45 -45
  28. package/loops/actions/merge-agent-pr/action.yml +49 -49
  29. package/loops/actions/remove-issue-labels/action.yml +37 -37
  30. package/loops/actions/report-workflow-errors/action.yml +1 -1
  31. package/loops/actions/update-agent-issues/action.yml +58 -58
  32. package/loops/actions/validate-refine-output/action.yml +6 -1
  33. package/loops/actions/validate-refine-output/validate-refine-output.sh +20 -2
  34. package/loops/actions/validate-review-output/action.yml +35 -35
  35. package/loops/actions/validate-triage-output/action.yml +36 -36
  36. package/loops/actions/verify-composite-actions/action.yml +9 -9
  37. package/loops/actions/verify-refine-output/action.yml +9 -9
  38. package/loops/actions/verify-route-matrix/action.yml +9 -9
  39. package/loops/actions/verify-route-matrix/verify-route-matrix.sh +38 -5
  40. package/loops/docs/diagrams.md +45 -2
  41. package/loops/templates/agentics/agentics-maintenance.yml +121 -121
  42. package/loops/templates/issues/bug_report.yml +109 -109
  43. package/loops/templates/issues/feature_request.yml +75 -75
  44. package/loops/workflows/agent-docs-audit.md +266 -0
  45. package/loops/workflows/agent-merge-gate.md +11 -7
  46. package/loops/workflows/agent-refine.md +61 -11
  47. package/loops/workflows/shared/platform-defaults.md +53 -53
  48. package/loops/workflows/work-router.yml +120 -3
  49. package/package.json +1 -1
@@ -13,6 +13,18 @@ set -euo pipefail
13
13
 
14
14
  out() { echo "landed=$1" >> "${GITHUB_OUTPUT:-/dev/null}"; }
15
15
 
16
+ # GitHub does not always create a push run for a merge the App performs. A consumer that deploys on
17
+ # push listens for this dispatch instead, so the merge still deploys.
18
+ dispatch_post_merge() {
19
+ [ "$base" = "${DEFAULT_BRANCH:-}" ] || return 0
20
+ gh api -X POST "repos/$REPO/dispatches" \
21
+ -f event_type="${POST_MERGE_EVENT:-agentic-post-merge}" \
22
+ -f "client_payload[sha]=$HEAD_SHA" \
23
+ -f "client_payload[pr]=$PR" \
24
+ -f "client_payload[base]=$base" >/dev/null ||
25
+ echo "::warning::The merge landed, but the ${POST_MERGE_EVENT:-agentic-post-merge} dispatch failed; a consumer that deploys on it did not start."
26
+ }
27
+
16
28
  pr_json=$(gh pr view "$PR" --repo "$REPO" --json headRefOid,baseRefName,headRefName,state)
17
29
  state=$(jq -r .state <<<"$pr_json")
18
30
  [ "$state" = OPEN ] || { echo "#$PR is $state; nothing to land."; out ""; exit 0; }
@@ -43,6 +55,7 @@ if [ -z "$queue" ]; then
43
55
  # The branch goes as well: nothing else deletes it where `delete_branch_on_merge` is off.
44
56
  gh api -X DELETE "repos/$REPO/git/refs/heads/$head_ref" >/dev/null 2>&1 || true
45
57
  echo "Merged #$PR into $base."
58
+ dispatch_post_merge
46
59
  out merged
47
60
  exit 0
48
61
  fi
@@ -87,6 +100,7 @@ case "$status" in
87
100
  ;;
88
101
  merged)
89
102
  echo "#$PR merged into $base."
103
+ dispatch_post_merge
90
104
  out merged
91
105
  ;;
92
106
  *)
@@ -1,40 +1,40 @@
1
- # Managed by @plainconceptsplatform/workflows. Source: loops/actions/link-pr-to-issue/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
- name: Link pull request to issue
3
- description: Ensure the pull request body carries a closing reference to its source issue.
4
- inputs:
5
- token:
6
- description: GitHub token with pull-requests write access.
7
- required: true
8
- pr-number:
9
- description: Pull request number.
10
- required: true
11
- issue-number:
12
- description: Issue number the pull request should close.
13
- required: true
14
- runs:
15
- using: composite
16
- steps:
17
- - name: Ensure a closing reference
18
- shell: bash
19
- env:
20
- GH_TOKEN: ${{ inputs.token }}
21
- REPO: ${{ github.repository }}
22
- PR_NUMBER: ${{ inputs.pr-number }}
23
- ISSUE_NUMBER: ${{ inputs.issue-number }}
24
- run: |
25
- set -euo pipefail
26
-
27
- pr="$(gh pr view "$PR_NUMBER" --repo "$REPO" --json closingIssuesReferences,body)"
28
-
29
- if [ "$(jq '.closingIssuesReferences | length' <<<"$pr")" -gt 0 ]; then
30
- echo "PR #$PR_NUMBER already closes an issue."
31
- exit 0
32
- fi
33
-
34
- # A closing reference can only be created from the pull request body; there is no
35
- # REST or GraphQL mutation for it.
36
- body="$(jq -r '.body // ""' <<<"$pr")"
37
- printf '%s\n\nCloses #%s\n' "$body" "$ISSUE_NUMBER" > /tmp/pr-body.md
38
-
39
- gh pr edit "$PR_NUMBER" --repo "$REPO" --body-file /tmp/pr-body.md
40
- echo "Added 'Closes #$ISSUE_NUMBER' to PR #$PR_NUMBER."
1
+ # Managed by @plainconceptsplatform/workflows. Source: loops/actions/link-pr-to-issue/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
+ name: Link pull request to issue
3
+ description: Ensure the pull request body carries a closing reference to its source issue.
4
+ inputs:
5
+ token:
6
+ description: GitHub token with pull-requests write access.
7
+ required: true
8
+ pr-number:
9
+ description: Pull request number.
10
+ required: true
11
+ issue-number:
12
+ description: Issue number the pull request should close.
13
+ required: true
14
+ runs:
15
+ using: composite
16
+ steps:
17
+ - name: Ensure a closing reference
18
+ shell: bash
19
+ env:
20
+ GH_TOKEN: ${{ inputs.token }}
21
+ REPO: ${{ github.repository }}
22
+ PR_NUMBER: ${{ inputs.pr-number }}
23
+ ISSUE_NUMBER: ${{ inputs.issue-number }}
24
+ run: |
25
+ set -euo pipefail
26
+
27
+ pr="$(gh pr view "$PR_NUMBER" --repo "$REPO" --json closingIssuesReferences,body)"
28
+
29
+ if [ "$(jq '.closingIssuesReferences | length' <<<"$pr")" -gt 0 ]; then
30
+ echo "PR #$PR_NUMBER already closes an issue."
31
+ exit 0
32
+ fi
33
+
34
+ # A closing reference can only be created from the pull request body; there is no
35
+ # REST or GraphQL mutation for it.
36
+ body="$(jq -r '.body // ""' <<<"$pr")"
37
+ printf '%s\n\nCloses #%s\n' "$body" "$ISSUE_NUMBER" > /tmp/pr-body.md
38
+
39
+ gh pr edit "$PR_NUMBER" --repo "$REPO" --body-file /tmp/pr-body.md
40
+ echo "Added 'Closes #$ISSUE_NUMBER' to PR #$PR_NUMBER."
@@ -1,33 +1,33 @@
1
- # Managed by @plainconceptsplatform/workflows. Source: loops/actions/list-open-issues/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
- name: List open issues
3
- description: |
4
- Writes all open issues (number, title, labels) to `/tmp/gh-aw/agent/open-issues.json`
5
- so the agent can check for semantic duplicates without spending turns discovering them.
6
- inputs:
7
- token:
8
- description: GitHub token with issues:read permission.
9
- required: true
10
- repo:
11
- description: Repository in `owner/name` format.
12
- required: true
13
- output-path:
14
- description: Path to write the JSON output.
15
- required: false
16
- default: /tmp/gh-aw/agent/open-issues.json
17
- runs:
18
- using: composite
19
- steps:
20
- - name: List open issues
21
- shell: bash
22
- env:
23
- GH_TOKEN: ${{ inputs.token }}
24
- REPO: ${{ inputs.repo }}
25
- OUTPUT_PATH: ${{ inputs.output-path }}
26
- run: |
27
- set -euo pipefail
28
- mkdir -p "$(dirname "$OUTPUT_PATH")"
29
- gh issue list --repo "$REPO" --state open --limit 500 \
30
- --json number,title,labels \
31
- --jq '[.[] | {number, title, labels: [.labels[].name]}]' \
32
- > "$OUTPUT_PATH"
33
- echo "Wrote $(jq 'length' "$OUTPUT_PATH") open issues to $OUTPUT_PATH"
1
+ # Managed by @plainconceptsplatform/workflows. Source: loops/actions/list-open-issues/action.yml. Update with workflows update --force; consumer edits may be overwritten.
2
+ name: List open issues
3
+ description: |
4
+ Writes all open issues (number, title, labels) to `/tmp/gh-aw/agent/open-issues.json`
5
+ so the agent can check for semantic duplicates without spending turns discovering them.
6
+ inputs:
7
+ token:
8
+ description: GitHub token with issues:read permission.
9
+ required: true
10
+ repo:
11
+ description: Repository in `owner/name` format.
12
+ required: true
13
+ output-path:
14
+ description: Path to write the JSON output.
15
+ required: false
16
+ default: /tmp/gh-aw/agent/open-issues.json
17
+ runs:
18
+ using: composite
19
+ steps:
20
+ - name: List open issues
21
+ shell: bash
22
+ env:
23
+ GH_TOKEN: ${{ inputs.token }}
24
+ REPO: ${{ inputs.repo }}
25
+ OUTPUT_PATH: ${{ inputs.output-path }}
26
+ run: |
27
+ set -euo pipefail
28
+ mkdir -p "$(dirname "$OUTPUT_PATH")"
29
+ gh issue list --repo "$REPO" --state open --limit 500 \
30
+ --json number,title,labels \
31
+ --jq '[.[] | {number, title, labels: [.labels[].name]}]' \
32
+ > "$OUTPUT_PATH"
33
+ echo "Wrote $(jq 'length' "$OUTPUT_PATH") open issues to $OUTPUT_PATH"
@@ -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,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
+ }
@@ -108,7 +108,7 @@ runs:
108
108
  'release-tag.yml',
109
109
  'agentics-checks.yml',
110
110
  'agentics-maintenance.yml',
111
- ...['refine', 'implement', 'triage', 'apply-review', 'merge-gate', 'audit', 'release']
111
+ ...['refine', 'implement', 'triage', 'apply-review', 'merge-gate', 'audit', 'docs-audit', 'release']
112
112
  .map((route) => `agent-${route}.lock.yml`),
113
113
  ]);
114
114
  const ownedName = (path) => (path ?? '').split('/').pop() ?? '';
@@ -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).`);
@@ -17,6 +17,10 @@ inputs:
17
17
  issue-number:
18
18
  description: Issue number that every refinement outcome must target.
19
19
  required: true
20
+ questions-line:
21
+ description: The sentence that opens the batched questions comment. A comment carrying it makes the outcome questions.
22
+ required: false
23
+ default: I have some questions about this issue
20
24
  outputs:
21
25
  valid:
22
26
  description: Whether the output contains a usable replacement body or clarification comment.
@@ -36,10 +40,11 @@ runs:
36
40
  ISSUE_NUMBER: ${{ inputs.issue-number }}
37
41
  MARKER: ${{ inputs.marker }}
38
42
  OUTPUT_FILE: ${{ inputs.output-file }}
43
+ QUESTIONS_LINE: ${{ inputs.questions-line }}
39
44
  run: |
40
45
  set -euo pipefail
41
46
 
42
- outcome="$(bash "${{ github.action_path }}/validate-refine-output.sh" "$OUTPUT_FILE" "$MARKER" "$COMMENT_PREFIX" "$ISSUE_NUMBER" "$DRAFT_MARKER")"
47
+ outcome="$(bash "${{ github.action_path }}/validate-refine-output.sh" "$OUTPUT_FILE" "$MARKER" "$COMMENT_PREFIX" "$ISSUE_NUMBER" "$DRAFT_MARKER" "$QUESTIONS_LINE")"
43
48
  echo "outcome=$outcome" >> "$GITHUB_OUTPUT"
44
49
  echo "valid=$([ "$outcome" != 'invalid' ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
45
50
 
@@ -9,13 +9,17 @@ marker="$2"
9
9
  comment_prefix="$3"
10
10
  issue_number="$4"
11
11
  draft_marker="$5"
12
+ # The sentence the prompt opens every batch of questions with, and the mark it leaves in the draft
13
+ # wherever a question is still open.
14
+ questions_line="${6:-I have some questions about this issue}"
15
+ pending_mark="${7:-_pending see questions below_}"
12
16
 
13
17
  if [ ! -f "$output_file" ] || ! jq -e '.items | arrays' "$output_file" >/dev/null 2>&1; then
14
18
  echo invalid
15
19
  exit 0
16
20
  fi
17
21
 
18
- jq -r --arg marker "$marker" --arg prefix "$comment_prefix" --arg issue "$issue_number" --arg draft_marker "$draft_marker" '
22
+ jq -r --arg marker "$marker" --arg prefix "$comment_prefix" --arg issue "$issue_number" --arg draft_marker "$draft_marker" --arg questions_line "$questions_line" --arg pending_mark "$pending_mark" '
19
23
  def has_replacement_body:
20
24
  any(.items[]; .type == "update_issue" and
21
25
  (.item_number == null or (.item_number | tostring) == $issue) and
@@ -32,6 +36,18 @@ jq -r --arg marker "$marker" --arg prefix "$comment_prefix" --arg issue "$issue_
32
36
  (.body | type == "string") and
33
37
  (.body | contains($draft_marker)));
34
38
 
39
+ # The batched questions, by the sentence that opens them. It decides the outcome on its own:
40
+ # Bots #561, #871 and Numa #821 asked, left a draft without the draft marker, were read as
41
+ # complete, and implement started two seconds later on a story nobody had answered.
42
+ def asks_questions:
43
+ any(.items[]; .type == "add_comment" and (.item_number | tostring) == $issue and
44
+ (.body | type == "string") and (.body | contains($questions_line)));
45
+
46
+ def has_pending_marks:
47
+ any(.items[]; .type == "update_issue" and
48
+ (.item_number == null or (.item_number | tostring) == $issue) and
49
+ (.body | type == "string") and (.body | contains($pending_mark)));
50
+
35
51
  # A split writes children, which are the only items allowed to target something other than
36
52
  # the source issue: they do not exist yet, so they carry no number at all.
37
53
  def child_count:
@@ -108,7 +124,9 @@ jq -r --arg marker "$marker" --arg prefix "$comment_prefix" --arg issue "$issue_
108
124
  # demanding a draft from both.
109
125
  if child_count >= 2 and has_replacement_body and has_only_split_items and root_pr_count <= 1 then "split"
110
126
  elif child_count > 0 then "invalid"
111
- elif has_replacement_body and (has_draft_body | not) and has_only_source_items then "complete"
127
+ elif asks_questions and ((has_update | not) or has_replacement_body) and has_only_source_items
128
+ and (reported_incomplete | not) then "questions"
129
+ elif has_replacement_body and (has_draft_body | not) and (has_pending_marks | not) and has_only_source_items then "complete"
112
130
  elif has_clarification and ((has_update | not) or has_draft_body) and has_only_source_items
113
131
  and (reported_incomplete | not) then "questions"
114
132
  else "invalid"