@codedrifters/configulator 0.0.408 → 0.0.410

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.mjs CHANGED
@@ -877,6 +877,53 @@ function buildTitlePrefixTypeMap() {
877
877
  return map;
878
878
  }
879
879
 
880
+ // src/agent/bundles/origin-label.ts
881
+ var ORIGIN_ISSUE_WORKER_LABEL = "origin:issue-worker";
882
+ var ORIGIN_AGENT_LABEL = "origin:agent";
883
+ var DELEGATION_ELIGIBLE_ORIGIN_PHRASE = `an origin label (\`${ORIGIN_ISSUE_WORKER_LABEL}\` or \`${ORIGIN_AGENT_LABEL}\`)`;
884
+ var DELEGATION_ELIGIBLE_ORIGIN_LIST = `\`${ORIGIN_ISSUE_WORKER_LABEL}\` or \`${ORIGIN_AGENT_LABEL}\``;
885
+ function renderOriginStampSection() {
886
+ return [
887
+ "## Origin label",
888
+ "",
889
+ `Every PR you open must carry the \`${ORIGIN_AGENT_LABEL}\` label. It is`,
890
+ "the umbrella origin label for **agent-opened** PRs: the `pr-reviewer`",
891
+ "reads it to decide whether it may autonomously delegate in-scope",
892
+ "review fixes back to the `issue-worker`. A PR that reaches the",
893
+ "reviewer with no origin label looks human-authored, so the reviewer",
894
+ "skips the delegation hand-off and the PR sits waiting for a human",
895
+ "instead of being self-healed by the pipeline.",
896
+ "",
897
+ "Pass the label on the `gh pr create` invocation:",
898
+ "",
899
+ "```bash",
900
+ 'gh pr create --title "<type>(<scope>): <description>" \\',
901
+ ` --label '${ORIGIN_AGENT_LABEL}' \\`,
902
+ ' --body "<summary>',
903
+ "",
904
+ 'Closes #<issue-number>"',
905
+ "```",
906
+ "",
907
+ "If your project's PR workflow opens the PR for you and the label is",
908
+ "missing afterwards, add it immediately \u2014 before handing the PR to",
909
+ "review:",
910
+ "",
911
+ "```bash",
912
+ `gh pr edit <pr-number> --add-label '${ORIGIN_AGENT_LABEL}'`,
913
+ "```",
914
+ "",
915
+ `Stamp \`${ORIGIN_AGENT_LABEL}\`, never \`${ORIGIN_ISSUE_WORKER_LABEL}\` \u2014`,
916
+ `\`${ORIGIN_ISSUE_WORKER_LABEL}\` means the PR was opened by the`,
917
+ "`issue-worker` agent specifically, and claiming it on an analyst PR",
918
+ "would be false. Both labels are delegation-eligible, so the umbrella",
919
+ "label buys the same autonomous fix hand-off without the false claim.",
920
+ "",
921
+ `The \`${ORIGIN_AGENT_LABEL}\` label ships with the \`pr-review\` bundle's`,
922
+ "label set, so it is already present in the repository's label",
923
+ "registry \u2014 you never need to create it."
924
+ ];
925
+ }
926
+
880
927
  // src/agent/bundles/project-context.ts
881
928
  var PROJECT_CONTEXT_PATH = "docs/src/content/docs/project-context.md";
882
929
  var SUBPROJECT_ROLE_GUIDANCE = [
@@ -1150,6 +1197,8 @@ var agendaAnalystSubAgent = {
1150
1197
  " label.",
1151
1198
  "4. Commit, push, open a PR, and transition the issue to",
1152
1199
  " `status:ready-for-review` per your project's PR workflow.",
1200
+ " Stamp every PR you open with the `origin:agent` label \u2014 see the",
1201
+ " **Origin label** section below.",
1153
1202
  "",
1154
1203
  "---",
1155
1204
  "",
@@ -1157,6 +1206,10 @@ var agendaAnalystSubAgent = {
1157
1206
  "",
1158
1207
  "---",
1159
1208
  "",
1209
+ ...renderOriginStampSection(),
1210
+ "",
1211
+ "---",
1212
+ "",
1160
1213
  "## Phase 1: Draft (`agenda:draft`)",
1161
1214
  "",
1162
1215
  "**Goal:** Produce a well-structured `agenda.md` in the meeting's",
@@ -5973,6 +6026,8 @@ function buildBcmWriterSubAgent(paths, issueDefaults) {
5973
6026
  "3. Execute the phase handler that matches the issue's `bcm:*` label.",
5974
6027
  "4. Commit, push, open a PR (if applicable), and close the issue per",
5975
6028
  " your project's PR workflow.",
6029
+ " Stamp every PR you open with the `origin:agent` label \u2014 see the",
6030
+ " **Origin label** section below.",
5976
6031
  "",
5977
6032
  "---",
5978
6033
  "",
@@ -5980,6 +6035,10 @@ function buildBcmWriterSubAgent(paths, issueDefaults) {
5980
6035
  "",
5981
6036
  "---",
5982
6037
  "",
6038
+ ...renderOriginStampSection(),
6039
+ "",
6040
+ "---",
6041
+ "",
5983
6042
  "## Phase 1: Outline (`bcm:outline`)",
5984
6043
  "",
5985
6044
  "**Goal:** Identify the capability (name-as-noun, business object, tier)",
@@ -7277,6 +7336,8 @@ function buildBusinessModelsAnalystSubAgent(paths) {
7277
7336
  " `business-models:*` label.",
7278
7337
  "4. Commit, push, open a PR (if applicable), and close the issue per",
7279
7338
  " your project's PR workflow.",
7339
+ " Stamp every PR you open with the `origin:agent` label \u2014 see the",
7340
+ " **Origin label** section below.",
7280
7341
  "",
7281
7342
  "---",
7282
7343
  "",
@@ -7284,6 +7345,10 @@ function buildBusinessModelsAnalystSubAgent(paths) {
7284
7345
  "",
7285
7346
  "---",
7286
7347
  "",
7348
+ ...renderOriginStampSection(),
7349
+ "",
7350
+ "---",
7351
+ "",
7287
7352
  "## Phase 1: Scan (`business-models:scan`)",
7288
7353
  "",
7289
7354
  "**Goal:** Read upstream industry and profile artifacts to identify",
@@ -8180,6 +8245,8 @@ function buildCompanyProfileAnalystSubAgent(paths, issueDefaults, tier) {
8180
8245
  " label.",
8181
8246
  "4. Commit, push, open a PR (if applicable), and close the issue per",
8182
8247
  " your project's PR workflow.",
8248
+ " Stamp every PR you open with the `origin:agent` label \u2014 see the",
8249
+ " **Origin label** section below.",
8183
8250
  "",
8184
8251
  "---",
8185
8252
  "",
@@ -8187,6 +8254,10 @@ function buildCompanyProfileAnalystSubAgent(paths, issueDefaults, tier) {
8187
8254
  "",
8188
8255
  "---",
8189
8256
  "",
8257
+ ...renderOriginStampSection(),
8258
+ "",
8259
+ "---",
8260
+ "",
8190
8261
  "## Phase 1: Research (`company:research`)",
8191
8262
  "",
8192
8263
  "**Goal:** Gather public sources into a bounded research-notes file.",
@@ -9625,6 +9696,8 @@ function buildCustomerProfileAnalystSubAgent(paths, issueDefaults, tier) {
9625
9696
  " label.",
9626
9697
  "4. Commit, push, open a PR (if applicable), and close the issue per",
9627
9698
  " your project's PR workflow.",
9699
+ " Stamp every PR you open with the `origin:agent` label \u2014 see the",
9700
+ " **Origin label** section below.",
9628
9701
  "",
9629
9702
  "---",
9630
9703
  "",
@@ -9632,6 +9705,10 @@ function buildCustomerProfileAnalystSubAgent(paths, issueDefaults, tier) {
9632
9705
  "",
9633
9706
  "---",
9634
9707
  "",
9708
+ ...renderOriginStampSection(),
9709
+ "",
9710
+ "---",
9711
+ "",
9635
9712
  "## Phase 1: Discover (`customer:discover`)",
9636
9713
  "",
9637
9714
  "**Goal:** Identify and segment the customer archetypes relevant to",
@@ -11384,6 +11461,278 @@ var docsSyncBundle = buildDocsSyncBundle();
11384
11461
 
11385
11462
  // src/agent/bundles/github-workflow.ts
11386
11463
  import { GitHub } from "projen/lib/github";
11464
+
11465
+ // src/agent/bundles/closing-keywords.ts
11466
+ function renderCheckClosingKeywordsScript() {
11467
+ return [
11468
+ "#!/usr/bin/env bash",
11469
+ "# check-closing-keywords.sh \u2014 Detect PR bodies that strand issues.",
11470
+ "#",
11471
+ "# GitHub honours exactly ONE issue reference per closing keyword. A",
11472
+ "# body that writes `Closes #593, #594, #595` closes #593 and leaves",
11473
+ "# #594 and #595 open forever. The only correct form is one keyword",
11474
+ "# per issue, each on its own line:",
11475
+ "#",
11476
+ "# Closes #593",
11477
+ "# Closes #594",
11478
+ "#",
11479
+ "# Usage:",
11480
+ "# .claude/procedures/check-closing-keywords.sh <pr-number>",
11481
+ "# .claude/procedures/check-closing-keywords.sh --body-file <path>",
11482
+ "#",
11483
+ "# Output (one stable line per reference, then a summary line):",
11484
+ "# CLOSES #<n> line=<L>",
11485
+ "# A reference GitHub WILL honour (the first one after a keyword).",
11486
+ "# STRANDED #<n> line=<L> keyword=<kw> first=#<n>",
11487
+ "# A reference chained onto an earlier one. GitHub will NOT",
11488
+ "# close it \u2014 the issue is stranded.",
11489
+ "# CLOSING_KEYWORDS_OK closes=<K>",
11490
+ "# CLOSING_KEYWORDS_MALFORMED closes=<K> stranded=<S>",
11491
+ "# NO_CLOSING_KEYWORD",
11492
+ "#",
11493
+ "# Exit codes:",
11494
+ "# 0 \u2014 at least one closing keyword, every one well-formed",
11495
+ "# 1 \u2014 at least one stranded reference (malformed body)",
11496
+ "# 2 \u2014 argument error",
11497
+ "# 3 \u2014 required command not on PATH",
11498
+ "# 4 \u2014 could not read the PR body",
11499
+ "# 5 \u2014 no closing keyword found at all",
11500
+ "",
11501
+ "set -uo pipefail",
11502
+ "",
11503
+ "# \u2500\u2500 helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
11504
+ "",
11505
+ "err() {",
11506
+ ' printf "check-closing-keywords.sh: %s\\n" "$*" >&2',
11507
+ "}",
11508
+ "",
11509
+ "usage() {",
11510
+ " cat >&2 <<'USAGE'",
11511
+ "Usage: check-closing-keywords.sh <pr-number>",
11512
+ " check-closing-keywords.sh --body-file <path>",
11513
+ "",
11514
+ " <pr-number> Positive integer PR number; the body is read",
11515
+ " via `gh pr view <n> --json body`.",
11516
+ " --body-file <path> Read the body from a file instead. Use this to",
11517
+ " check a drafted body BEFORE `gh pr create`.",
11518
+ "",
11519
+ "Emits one CLOSES / STRANDED line per issue reference, then a summary.",
11520
+ "Exits 1 when any reference is stranded by a multi-issue keyword.",
11521
+ "USAGE",
11522
+ "}",
11523
+ "",
11524
+ "# \u2500\u2500 argument parsing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
11525
+ "",
11526
+ 'mode=""',
11527
+ 'arg=""',
11528
+ "",
11529
+ "while [[ $# -gt 0 ]]; do",
11530
+ ' case "$1" in',
11531
+ " --body-file)",
11532
+ ' if [[ $# -lt 2 || -z "${2:-}" ]]; then',
11533
+ ' err "--body-file requires a value"',
11534
+ " usage",
11535
+ " exit 2",
11536
+ " fi",
11537
+ ' if [[ -n "$mode" ]]; then',
11538
+ ' err "specify either a PR number or --body-file, not both"',
11539
+ " exit 2",
11540
+ " fi",
11541
+ ' mode="file"',
11542
+ ' arg="$2"',
11543
+ " shift 2",
11544
+ " ;;",
11545
+ " -h|--help)",
11546
+ " usage",
11547
+ " exit 0",
11548
+ " ;;",
11549
+ " -*)",
11550
+ ' err "unknown option: $1"',
11551
+ " usage",
11552
+ " exit 2",
11553
+ " ;;",
11554
+ " *)",
11555
+ ' if [[ -n "$mode" ]]; then',
11556
+ ' err "unexpected extra argument: $1"',
11557
+ " usage",
11558
+ " exit 2",
11559
+ " fi",
11560
+ ' mode="pr"',
11561
+ ' arg="$1"',
11562
+ " shift",
11563
+ " ;;",
11564
+ " esac",
11565
+ "done",
11566
+ "",
11567
+ 'if [[ -z "$mode" ]]; then',
11568
+ ' err "expected a PR number or --body-file <path>"',
11569
+ " usage",
11570
+ " exit 2",
11571
+ "fi",
11572
+ "",
11573
+ "# \u2500\u2500 read the body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
11574
+ "",
11575
+ 'body=""',
11576
+ "",
11577
+ 'if [[ "$mode" == "file" ]]; then',
11578
+ ' if [[ ! -r "$arg" ]]; then',
11579
+ ' err "body file not readable: $arg"',
11580
+ " exit 4",
11581
+ " fi",
11582
+ ' body=$(cat "$arg")',
11583
+ "else",
11584
+ ' if ! [[ "$arg" =~ ^[1-9][0-9]*$ ]]; then',
11585
+ ` err "PR number must be a positive integer (got: '$arg')"`,
11586
+ " exit 2",
11587
+ " fi",
11588
+ " if ! command -v gh >/dev/null 2>&1; then",
11589
+ ' err "required command not found on PATH: gh"',
11590
+ " exit 3",
11591
+ " fi",
11592
+ ` if ! body=$(gh pr view "$arg" --json body -q '.body' 2>/dev/null); then`,
11593
+ ' err "could not read the body of PR #${arg} via gh"',
11594
+ " exit 4",
11595
+ " fi",
11596
+ "fi",
11597
+ "",
11598
+ "# \u2500\u2500 parsing contract \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
11599
+ "#",
11600
+ "# Regexes live in single-quoted variables so backticks and other shell",
11601
+ "# metacharacters inside them are never expanded by [[ ]].",
11602
+ "#",
11603
+ "# kw_re matches a closing keyword plus its FIRST issue reference. The",
11604
+ "# leading (^|[^[:alnum:]_]) is a word boundary so `prefixes #1` is not",
11605
+ "# read as `fixes #1`.",
11606
+ "#",
11607
+ "# sep_re / ws_re walk the tail AFTER that first reference, matching",
11608
+ "# only references CHAINED directly onto it by a separator a human",
11609
+ "# would mistake for a list: `,` `/` `&` `+` `and` `, and`, or",
11610
+ "# bare whitespace. Anything else \u2014 a period, a dash, a word, an",
11611
+ "# opening paren \u2014 terminates the chain. That is what keeps prose like",
11612
+ "# `Closes #893. See #607 and #860 for context.` from false-positiving:",
11613
+ "# the `.` after #893 ends the chain before `#607` is ever considered.",
11614
+ "",
11615
+ "fence_re='^[[:space:]]{0,3}(```+|~~~+)'",
11616
+ "quote_re='^[[:space:]]*>'",
11617
+ "kw_re='(^|[^[:alnum:]_])(closes|closed|close|fixes|fixed|fix|resolves|resolved|resolve)[[:space:]]*:?[[:space:]]*#([0-9]+)(.*)$'",
11618
+ "sep_re='^[[:space:]]*(,[[:space:]]*and|,|/|&|[+]|and)[[:space:]]*#([0-9]+)(.*)$'",
11619
+ "ws_re='^[[:space:]]+#([0-9]+)(.*)$'",
11620
+ "",
11621
+ "# Keywords are case-insensitive on GitHub, so the scan is too.",
11622
+ "shopt -s nocasematch",
11623
+ "",
11624
+ "# A single backtick, held in a variable so the code-span stripper",
11625
+ "# never has to quote one inline.",
11626
+ "bt='`'",
11627
+ "",
11628
+ "# Remove inline code spans (`...`) from a line. A PR body that",
11629
+ "# DISCUSSES the malformed form \u2014 writing `Closes #593, #594` inside",
11630
+ "# backticks \u2014 must not trip the detector.",
11631
+ "strip_code_spans() {",
11632
+ ' local s="$1"',
11633
+ ' local out=""',
11634
+ ' while [[ "$s" == *"$bt"*"$bt"* ]]; do',
11635
+ ' out="$out${s%%$bt*}"',
11636
+ ' s="${s#*$bt}"',
11637
+ ' s="${s#*$bt}"',
11638
+ " done",
11639
+ ` printf '%s%s' "$out" "$s"`,
11640
+ "}",
11641
+ "",
11642
+ "# \u2500\u2500 scan \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
11643
+ "",
11644
+ "line_no=0",
11645
+ "in_fence=0",
11646
+ 'fence_marker=""',
11647
+ "closes_count=0",
11648
+ "stranded_count=0",
11649
+ "",
11650
+ 'while IFS= read -r raw || [[ -n "$raw" ]]; do',
11651
+ " line_no=$((line_no + 1))",
11652
+ " # gh returns CRLF bodies; strip the CR so end-of-line matching works.",
11653
+ ` raw="\${raw//$'\\r'/}"`,
11654
+ "",
11655
+ " # Fenced code blocks are quoted content, never the PR's own intent.",
11656
+ ' if [[ "$raw" =~ $fence_re ]]; then',
11657
+ ' marker="${BASH_REMATCH[1]:0:1}"',
11658
+ " if [[ $in_fence -eq 0 ]]; then",
11659
+ " in_fence=1",
11660
+ ' fence_marker="$marker"',
11661
+ ' elif [[ "$marker" == "$fence_marker" ]]; then',
11662
+ " in_fence=0",
11663
+ ' fence_marker=""',
11664
+ " fi",
11665
+ " continue",
11666
+ " fi",
11667
+ " if [[ $in_fence -eq 1 ]]; then",
11668
+ " continue",
11669
+ " fi",
11670
+ "",
11671
+ " # Blockquoted text is someone else's words being cited.",
11672
+ ' if [[ "$raw" =~ $quote_re ]]; then',
11673
+ " continue",
11674
+ " fi",
11675
+ "",
11676
+ ' line=$(strip_code_spans "$raw")',
11677
+ "",
11678
+ ' rest="$line"',
11679
+ ' while [[ "$rest" =~ $kw_re ]]; do',
11680
+ ' kw="${BASH_REMATCH[2]}"',
11681
+ ' first="${BASH_REMATCH[3]}"',
11682
+ ' tail="${BASH_REMATCH[4]}"',
11683
+ "",
11684
+ ` printf 'CLOSES #%s line=%s\\n' "$first" "$line_no"`,
11685
+ " closes_count=$((closes_count + 1))",
11686
+ "",
11687
+ " # Walk every reference chained onto this keyword's first one.",
11688
+ ' t="$tail"',
11689
+ " while :; do",
11690
+ ' if [[ "$t" =~ $sep_re ]]; then',
11691
+ ' extra="${BASH_REMATCH[2]}"',
11692
+ ' t="${BASH_REMATCH[3]}"',
11693
+ ' elif [[ "$t" =~ $ws_re ]]; then',
11694
+ ' extra="${BASH_REMATCH[1]}"',
11695
+ ' t="${BASH_REMATCH[2]}"',
11696
+ " else",
11697
+ " break",
11698
+ " fi",
11699
+ " printf 'STRANDED #%s line=%s keyword=%s first=#%s\\n' \\",
11700
+ ' "$extra" "$line_no" "$kw" "$first"',
11701
+ " stranded_count=$((stranded_count + 1))",
11702
+ " done",
11703
+ "",
11704
+ ' rest="$t"',
11705
+ " done",
11706
+ 'done <<< "$body"',
11707
+ "",
11708
+ "# \u2500\u2500 summary \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
11709
+ "",
11710
+ "if [[ $closes_count -eq 0 ]]; then",
11711
+ ' echo "NO_CLOSING_KEYWORD"',
11712
+ " exit 5",
11713
+ "fi",
11714
+ "",
11715
+ "if [[ $stranded_count -gt 0 ]]; then",
11716
+ " printf 'CLOSING_KEYWORDS_MALFORMED closes=%s stranded=%s\\n' \\",
11717
+ ' "$closes_count" "$stranded_count"',
11718
+ ' err "GitHub honours only ONE issue reference per closing keyword."',
11719
+ ' err "Give every issue its own keyword, one per line:"',
11720
+ ' err " Closes #123"',
11721
+ ' err " Closes #456"',
11722
+ " exit 1",
11723
+ "fi",
11724
+ "",
11725
+ `printf 'CLOSING_KEYWORDS_OK closes=%s\\n' "$closes_count"`,
11726
+ "exit 0"
11727
+ ].join("\n");
11728
+ }
11729
+ var checkClosingKeywordsProcedure = {
11730
+ name: "check-closing-keywords.sh",
11731
+ description: "Parse a PR body (by PR number or --body-file) and report every closing-keyword issue reference as CLOSES (GitHub will honour it) or STRANDED (chained onto an earlier reference and silently ignored). Exits 1 when any reference is stranded. Skips fenced code blocks, blockquotes, and inline code spans.",
11732
+ content: renderCheckClosingKeywordsScript()
11733
+ };
11734
+
11735
+ // src/agent/bundles/github-workflow.ts
11387
11736
  var setIssueTypeProcedure = {
11388
11737
  name: "set-issue-type.sh",
11389
11738
  description: "Assign a GitHub issue type (Feature / Task / Epic / Bug / etc.) via the updateIssueIssueType GraphQL mutation in a single step",
@@ -12058,8 +12407,24 @@ function buildGithubWorkflowBundle(buildPolicy = DEFAULT_BUILD_POLICY) {
12058
12407
  "5. **Push the branch** to origin: `git push -u origin <branch>`",
12059
12408
  "6. **Create the PR** using `gh pr create`:",
12060
12409
  " - **Title**: use a conventional commit style title (e.g., `feat(scope): short description`)",
12061
- " - **Body**: include `Closes #<issue-number>` (derived from the branch name) and a brief summary of changes",
12062
- "7. **Delegate review and merge to the `pr-reviewer` sub-agent.** After the PR is created, invoke the `/review-pr <pr-number>` skill (or otherwise hand the new PR number to the `pr-reviewer` sub-agent). The reviewer verifies the diff against the linked issue's acceptance criteria and enables squash auto-merge when all checks pass. Do **not** run `gh pr merge --auto` yourself \u2014 review/merge policy lives solely in the `pr-reviewer` agent.",
12410
+ " - **Body**: include one `Closes #<issue-number>` line per issue the PR closes (derived from the branch name and any additional issues in scope) and a brief summary of changes. See **Closing Keywords** below \u2014 never list several issues after a single keyword.",
12411
+ "7. **Verify the closing keywords** with `.claude/procedures/check-closing-keywords.sh --body-file <path>` on the body you are about to post, then `.claude/procedures/check-closing-keywords.sh <pr-number>` on the body that landed. Exit `0` means every referenced issue will auto-close; exit `1` prints a `STRANDED #<n>` line per issue GitHub would silently leave open \u2014 fix the body with `gh pr edit <pr-number> --body-file <path>` and re-run until it exits `0`.",
12412
+ "8. **Delegate review and merge to the `pr-reviewer` sub-agent.** After the PR is created, invoke the `/review-pr <pr-number>` skill (or otherwise hand the new PR number to the `pr-reviewer` sub-agent). The reviewer verifies the diff against the linked issue's acceptance criteria and enables squash auto-merge when all checks pass. Do **not** run `gh pr merge --auto` yourself \u2014 review/merge policy lives solely in the `pr-reviewer` agent.",
12413
+ "",
12414
+ "### Closing Keywords",
12415
+ "",
12416
+ "**GitHub honours exactly ONE issue reference per closing keyword.** `Closes #593, #594, #595` closes **#593 only** \u2014 #594 and #595 stay open forever with no warning anywhere. `Closes #821 / #837` and `Closes #821 and #837` strand the second issue the same way. Give every issue its own keyword, one per line:",
12417
+ "",
12418
+ "```markdown",
12419
+ "Closes #593 <- correct: repeat the keyword, one issue each",
12420
+ "Closes #594",
12421
+ "",
12422
+ "Closes #593, #594 <- WRONG: closes #593, strands #594",
12423
+ "Closes #821 / #837 <- WRONG: closes #821, strands #837",
12424
+ "Closes #821 and #837 <- WRONG: closes #821, strands #837",
12425
+ "```",
12426
+ "",
12427
+ "`Fixes` and `Resolves` behave identically \u2014 the rule is the keyword-to-issue ratio, not the verb. Non-closing references (`Refs #849`, `Related to #607`) are unaffected. `.claude/procedures/check-closing-keywords.sh` is the mechanical check; it skips fenced code blocks, blockquotes, and inline code spans, so a body that *discusses* the malformed form does not trip it.",
12063
12428
  "",
12064
12429
  "### PR Body Template",
12065
12430
  "",
@@ -12076,10 +12441,18 @@ function buildGithubWorkflowBundle(buildPolicy = DEFAULT_BUILD_POLICY) {
12076
12441
  "- [ ] Relevant changes have been reviewed",
12077
12442
  "```",
12078
12443
  "",
12444
+ "When the PR closes more than one issue, the closing block expands to one keyword per issue \u2014 it never becomes a list:",
12445
+ "",
12446
+ "```markdown",
12447
+ "Closes #<first-issue-number>",
12448
+ "Closes #<second-issue-number>",
12449
+ "```",
12450
+ "",
12079
12451
  "### Important",
12080
12452
  "",
12081
12453
  "- Always derive the issue number from the branch name (e.g., `feat/42-add-login` \u2192 `#42`)",
12082
12454
  "- Use conventional commit format for the PR title",
12455
+ "- **One closing keyword per issue, each on its own line.** Never `Closes #A, #B` \u2014 GitHub closes only `#A`. Run `.claude/procedures/check-closing-keywords.sh` before and after `gh pr create`; a non-zero exit means the PR would strand an issue",
12083
12456
  "- Delegate merge to the `pr-reviewer` sub-agent \u2014 do not merge manually and do not enable auto-merge directly"
12084
12457
  ].join("\n"),
12085
12458
  tags: ["workflow"]
@@ -12112,7 +12485,11 @@ function buildGithubWorkflowBundle(buildPolicy = DEFAULT_BUILD_POLICY) {
12112
12485
  }
12113
12486
  ],
12114
12487
  skills: [cleanMergedBranchesSkill],
12115
- procedures: [setIssueTypeProcedure, cleanMergedBranchesProcedure]
12488
+ procedures: [
12489
+ setIssueTypeProcedure,
12490
+ cleanMergedBranchesProcedure,
12491
+ checkClosingKeywordsProcedure
12492
+ ]
12116
12493
  };
12117
12494
  }
12118
12495
  var githubWorkflowBundle = buildGithubWorkflowBundle();
@@ -12277,6 +12654,8 @@ function buildIndustryDiscoveryAnalystSubAgent(paths, issueDefaults) {
12277
12654
  " label.",
12278
12655
  "4. Commit, push, open a PR (if applicable), and close the issue per",
12279
12656
  " your project's PR workflow.",
12657
+ " Stamp every PR you open with the `origin:agent` label \u2014 see the",
12658
+ " **Origin label** section below.",
12280
12659
  "",
12281
12660
  "---",
12282
12661
  "",
@@ -12284,6 +12663,10 @@ function buildIndustryDiscoveryAnalystSubAgent(paths, issueDefaults) {
12284
12663
  "",
12285
12664
  "---",
12286
12665
  "",
12666
+ ...renderOriginStampSection(),
12667
+ "",
12668
+ "---",
12669
+ "",
12287
12670
  "## Phase 1: Discover (`industry:discover`)",
12288
12671
  "",
12289
12672
  "**Goal:** Enumerate candidate industry verticals into a bounded",
@@ -12836,6 +13219,8 @@ function buildMaintenanceAuditSubAgent(paths, tier) {
12836
13219
  " label.",
12837
13220
  "4. Commit, push, open a PR (if applicable), and close the issue per",
12838
13221
  " your project's PR workflow.",
13222
+ " Stamp every PR you open with the `origin:agent` label \u2014 see the",
13223
+ " **Origin label** section below.",
12839
13224
  "",
12840
13225
  ...renderIssueTypeAssignmentBlanket(),
12841
13226
  "",
@@ -12845,6 +13230,10 @@ function buildMaintenanceAuditSubAgent(paths, tier) {
12845
13230
  "",
12846
13231
  "---",
12847
13232
  "",
13233
+ ...renderOriginStampSection(),
13234
+ "",
13235
+ "---",
13236
+ "",
12848
13237
  "## Phase 1: Scan (`maint:scan`)",
12849
13238
  "",
12850
13239
  "**Goal:** Walk the configured doc tree, check every cross-reference",
@@ -13737,6 +14126,10 @@ function buildMeetingAnalystSubAgent(tier) {
13737
14126
  "",
13738
14127
  "---",
13739
14128
  "",
14129
+ ...renderOriginStampSection(),
14130
+ "",
14131
+ "---",
14132
+ "",
13740
14133
  "## Phase 1: Extract (`meeting:extract`)",
13741
14134
  "",
13742
14135
  "**Goal:** Read the available meeting inputs (notes, transcript, or both)",
@@ -19200,6 +19593,27 @@ var issueWorkerSubAgent = {
19200
19593
  'Closes #<issue-number>"',
19201
19594
  "```",
19202
19595
  "",
19596
+ "**One closing keyword per issue, each on its own line.** GitHub honours",
19597
+ "only one issue reference per keyword, so `Closes #A, #B` closes `#A` and",
19598
+ "leaves `#B` open forever \u2014 the same is true of `Closes #A / #B` and",
19599
+ "`Closes #A and #B`. A PR that closes several issues repeats the keyword:",
19600
+ "",
19601
+ "```",
19602
+ "Closes #<first-issue-number>",
19603
+ "Closes #<second-issue-number>",
19604
+ "```",
19605
+ "",
19606
+ "Verify it mechanically before moving on \u2014 this exits non-zero and names",
19607
+ "every issue the body would strand:",
19608
+ "",
19609
+ "```bash",
19610
+ ".claude/procedures/check-closing-keywords.sh <pr-number>",
19611
+ "```",
19612
+ "",
19613
+ "If it reports `CLOSING_KEYWORDS_MALFORMED`, fix the body with",
19614
+ "`gh pr edit <pr-number> --body-file <path>` and re-run until it exits",
19615
+ "`0`. See the **Closing Keywords** section of the `pr-workflow` rule.",
19616
+ "",
19203
19617
  "Do **not** enable auto-merge yourself. The `pr-reviewer` sub-agent owns",
19204
19618
  "review and merge for every PR \u2014 it verifies the diff against the issue's",
19205
19619
  "acceptance criteria and enables squash auto-merge only when all checks",
@@ -19750,6 +20164,8 @@ function buildPeopleProfileAnalystSubAgent(paths, issueDefaults, tier) {
19750
20164
  " label.",
19751
20165
  "4. Commit, push, open a PR (if applicable), and close the issue per",
19752
20166
  " your project's PR workflow.",
20167
+ " Stamp every PR you open with the `origin:agent` label \u2014 see the",
20168
+ " **Origin label** section below.",
19753
20169
  "",
19754
20170
  "---",
19755
20171
  "",
@@ -19757,6 +20173,10 @@ function buildPeopleProfileAnalystSubAgent(paths, issueDefaults, tier) {
19757
20173
  "",
19758
20174
  "---",
19759
20175
  "",
20176
+ ...renderOriginStampSection(),
20177
+ "",
20178
+ "---",
20179
+ "",
19760
20180
  "## Phase 1: Research (`people:research`)",
19761
20181
  "",
19762
20182
  "**Goal:** Gather public sources into a bounded research-notes file.",
@@ -20719,14 +21139,14 @@ var prReviewerSubAgent = {
20719
21139
  "",
20720
21140
  "- **`--allow-human-author`** \u2014 opt-in flag that allows the reviewer to",
20721
21141
  " process PRs authored by humans in addition to bot-authored PRs",
20722
- " (those carrying the `origin:issue-worker` label). When absent, the",
21142
+ ` (those carrying ${DELEGATION_ELIGIBLE_ORIGIN_LIST}). When absent, the`,
20723
21143
  " reviewer only processes bot-authored PRs; when present, human-authored",
20724
21144
  " PRs are also eligible for this invocation only. The flag does **not**",
20725
21145
  " persist across invocations.",
20726
21146
  "",
20727
21147
  "Phase 4's invocation guard enforces the bot-only default and the",
20728
- "`--allow-human-author` opt-out. PRs without the `origin:issue-worker`",
20729
- "label are still classified, commented on, reacted to, and (in",
21148
+ "`--allow-human-author` opt-out. PRs carrying **no** origin label are",
21149
+ "still classified, commented on, reacted to, and (in",
20730
21150
  "`auto-merge` mode) auto-merged when the AC/CI checks pass \u2014 only the",
20731
21151
  "delegation hand-off to `issue-worker` is gated.",
20732
21152
  "",
@@ -20879,8 +21299,32 @@ var prReviewerSubAgent = {
20879
21299
  " resolve CI via the **CI Verification** section's Actions-runs",
20880
21300
  " fallback before deciding eligibility \u2014 do **not** treat the 403 as",
20881
21301
  " either a pass or an automatic disqualification.",
20882
- "3. The PR body contains a linked issue via one of the closing keywords:",
20883
- " `Closes #N`, `Fixes #N`, or `Resolves #N` (case-insensitive).",
21302
+ "3. The PR body contains at least one linked issue via a closing",
21303
+ " keyword (`Closes #N`, `Fixes #N`, `Resolves #N`, case-insensitive).",
21304
+ " Resolve this mechanically \u2014 do **not** eyeball the body:",
21305
+ "",
21306
+ "```bash",
21307
+ ".claude/procedures/check-closing-keywords.sh <pr-number>",
21308
+ "```",
21309
+ "",
21310
+ " The procedure is the single source of truth for closing-keyword",
21311
+ " parsing in this pipeline. It emits one `CLOSES #<n> line=<L>` line",
21312
+ " per reference GitHub will honour, one",
21313
+ " `STRANDED #<n> line=<L> keyword=<kw> first=#<n>` line per reference",
21314
+ " GitHub will silently ignore, then a summary line. It skips fenced",
21315
+ " code blocks, blockquotes, and inline code spans, so a body that",
21316
+ " merely discusses closing keywords does not confuse it.",
21317
+ "",
21318
+ " - Exit `5` / `NO_CLOSING_KEYWORD` \u2014 check (3) **fails**; the PR has",
21319
+ " no linked issue.",
21320
+ " - Exit `0` / `CLOSING_KEYWORDS_OK` \u2014 check (3) passes. Record the",
21321
+ " `CLOSES` set as the **linked-issue set** for the rest of the pass.",
21322
+ " - Exit `1` / `CLOSING_KEYWORDS_MALFORMED` \u2014 check (3) **passes**",
21323
+ " (there is a linked issue), but the body would strand every",
21324
+ " `STRANDED` issue. This is **not** an eligibility rejection \u2014 the",
21325
+ " code is not at fault. Record both the `CLOSES` and `STRANDED`",
21326
+ " sets as the linked-issue set and carry the malformed finding into",
21327
+ " Phase 4 and Phase 5.",
20884
21328
  "",
20885
21329
  "If the **CI** check (2) or the **linked-issue** check (3) fails, post",
20886
21330
  "a short comment explaining the reason and stop. Do not proceed to",
@@ -20948,16 +21392,29 @@ var prReviewerSubAgent = {
20948
21392
  "gh pr checks <pr-number>",
20949
21393
  "```",
20950
21394
  "",
20951
- "Extract the linked issue number from the PR body using the closing keywords",
20952
- "(`Closes #N`, `Fixes #N`, or `Resolves #N`) identified in Phase 1.5.",
21395
+ "Use the **linked-issue set** already computed in Phase 1.5 \u2014 the union",
21396
+ "of the `CLOSES` and `STRANDED` issue numbers the",
21397
+ "`check-closing-keywords.sh` run reported. Do not re-derive it by",
21398
+ "reading the body yourself.",
21399
+ "",
21400
+ "Most PRs link exactly one issue, and every phase below reads naturally",
21401
+ "in the singular. When the set has more than one member, treat **every**",
21402
+ "member as a linked issue: fetch each one, union their acceptance",
21403
+ "criteria in Phase 3, and close each one in Phase 5. The **primary**",
21404
+ "linked issue \u2014 the one cited in the sticky summary and the structured",
21405
+ "report \u2014 is the first `CLOSES` entry.",
20953
21406
  "",
20954
- "Then fetch the linked issue, including its `issueType` so Phase 2.75 can",
20955
- "evaluate the issue-type portion of the review policy:",
21407
+ "Then fetch each linked issue, including its `issueType` so Phase 2.75",
21408
+ "can evaluate the issue-type portion of the review policy:",
20956
21409
  "",
20957
21410
  "```bash",
20958
21411
  "gh issue view <issue-number> --json number,title,body,labels,state,issueType",
20959
21412
  "```",
20960
21413
  "",
21414
+ "When several issues are linked, Phase 2.75's issue-type rule matches if",
21415
+ "**any** linked issue's type is in `human-required.issue-types` \u2014 the",
21416
+ "conservative reading, consistent with the policy's mixed-signal rule.",
21417
+ "",
20961
21418
  "## Phase 2.5: Gather Comments",
20962
21419
  "",
20963
21420
  "Before deciding review mode, collect every human comment on the PR so",
@@ -21255,6 +21712,14 @@ var prReviewerSubAgent = {
21255
21712
  "",
21256
21713
  "- **Convention compliance** \u2014 PR title uses a conventional commit prefix,",
21257
21714
  " body includes a closing keyword, branch name follows project conventions",
21715
+ "- **Closing-keyword integrity** \u2014 the Phase 1.5",
21716
+ " `check-closing-keywords.sh` run exited `0`. A",
21717
+ " `CLOSING_KEYWORDS_MALFORMED` result is a **Suggested** finding (not",
21718
+ " blocking): the diff is unaffected, and Phase 5 closes the stranded",
21719
+ " issues deterministically, so gating the merge on it would wedge a",
21720
+ " correct PR over a body typo. Record every `STRANDED #<n>` in the",
21721
+ " findings and in the sticky summary so the malformed body is visible",
21722
+ " rather than silent.",
21258
21723
  "- **Test coverage** \u2014 new or changed behavior has tests",
21259
21724
  "- **CI status** \u2014 resolve CI via the **CI Verification** section",
21260
21725
  " (primary check-runs read, with the Actions-runs fallback on a",
@@ -21412,12 +21877,17 @@ var prReviewerSubAgent = {
21412
21877
  "Before delegating any in-scope fix to `issue-worker`, verify the",
21413
21878
  "delegation is permitted by author identity:",
21414
21879
  "",
21415
- "- If the PR carries the `origin:issue-worker` label, delegation is",
21416
- " permitted.",
21880
+ `- If the PR carries the \`${ORIGIN_ISSUE_WORKER_LABEL}\` label **or** the`,
21881
+ ` \`${ORIGIN_AGENT_LABEL}\` label, delegation is permitted. Both are`,
21882
+ " agent-origin labels and both are delegation-eligible:",
21883
+ ` \`${ORIGIN_ISSUE_WORKER_LABEL}\` marks a PR opened by the \`issue-worker\``,
21884
+ ` agent specifically; \`${ORIGIN_AGENT_LABEL}\` is the umbrella label the`,
21885
+ " phased analyst/writer family stamps on the PRs it opens. Treat them",
21886
+ " identically for this guard \u2014 neither requires `--allow-human-author`.",
21417
21887
  "- If the invocation prompt contains the literal flag",
21418
21888
  " `--allow-human-author`, delegation is permitted for this invocation",
21419
21889
  " only.",
21420
- "- Otherwise (human-authored PR with no opt-in flag) the reviewer",
21890
+ "- Otherwise (no agent-origin label and no opt-in flag) the reviewer",
21421
21891
  " **must not delegate**. It still classifies comments (Phase 3.5),",
21422
21892
  " posts replies, applies reactions, updates the sticky summary, and",
21423
21893
  " (when in `auto-merge` mode and the AC/CI checks pass) enables",
@@ -21617,7 +22087,38 @@ var prReviewerSubAgent = {
21617
22087
  "",
21618
22088
  "### If all acceptance criteria are met and CI is green",
21619
22089
  "",
21620
- "Branch on the **review mode** decided in Phase 2.75:",
22090
+ "#### Pre-merge closing-keyword check",
22091
+ "",
22092
+ "Run this **before** branching on the review mode \u2014 it applies in both",
22093
+ "modes and is the last cheap moment to catch a body that would strand",
22094
+ "issues on merge:",
22095
+ "",
22096
+ "```bash",
22097
+ ".claude/procedures/check-closing-keywords.sh <pr-number>",
22098
+ "```",
22099
+ "",
22100
+ "Re-run it here rather than reusing the Phase 1.5 result: the body may",
22101
+ "have been edited since, and the merge is about to make the answer",
22102
+ "permanent.",
22103
+ "",
22104
+ "- **Exit `0`** \u2014 nothing to do; every linked issue will auto-close.",
22105
+ "- **Exit `1`** \u2014 the body strands at least one issue. Do **not** block",
22106
+ " the merge; post one comment naming the stranded issues and the",
22107
+ " corrected block, then continue. Phase 5 closes the survivors",
22108
+ " explicitly, so the malformed body costs a comment, not a wedged PR.",
22109
+ "",
22110
+ "```bash",
22111
+ "gh pr comment <pr-number> --body 'GitHub honours only one issue reference per closing keyword, so this body would close #<first> and silently leave #<stranded\u2026> open. Give each issue its own keyword, one per line:",
22112
+ "",
22113
+ "Closes #<first>",
22114
+ "Closes #<stranded>",
22115
+ "",
22116
+ "Merging anyway \u2014 Phase 5 will close the stranded issues explicitly.'",
22117
+ "```",
22118
+ "",
22119
+ "Carry the full stranded set into Phase 5 verbatim.",
22120
+ "",
22121
+ "Then branch on the **review mode** decided in Phase 2.75:",
21621
22122
  "",
21622
22123
  "#### Mode `auto-merge`",
21623
22124
  "",
@@ -21805,6 +22306,7 @@ var prReviewerSubAgent = {
21805
22306
  "**Outstanding:** <list of comments still open with their classification and author>",
21806
22307
  "**Pushbacks:** <list of disputes with reasons>",
21807
22308
  "**Label reconciliation:** <consistent | corrections applied this pass>",
22309
+ "**Closing keywords:** ok | malformed \u2014 strands #<n>[, #<n>\u2026]",
21808
22310
  "**Last pass:** <ISO timestamp>",
21809
22311
  "```",
21810
22312
  "",
@@ -21829,6 +22331,11 @@ var prReviewerSubAgent = {
21829
22331
  " the corrections applied this pass (the detected violation combo,",
21830
22332
  " the label(s) removed, and the resulting self-consistent set).",
21831
22333
  " Every reconciliation correction is recorded here for auditability.",
22334
+ "- **Closing keywords** \u2014 the",
22335
+ " `.claude/procedures/check-closing-keywords.sh` verdict: `ok`, or",
22336
+ " `malformed` plus every `STRANDED #<n>` the body would leave open.",
22337
+ " Recorded on every pass so a body that strands issues is visible to a",
22338
+ " human before the merge, not only after Phase 5 repairs it.",
21832
22339
  "- **Last pass** \u2014 the ISO 8601 timestamp of this run.",
21833
22340
  "",
21834
22341
  "### Step 3: Create or edit in place",
@@ -21912,9 +22419,11 @@ var prReviewerSubAgent = {
21912
22419
  " reviewed diff than the clean merge-commit `gh pr update-branch`",
21913
22420
  " performs on a no-conflict branch.)",
21914
22421
  "2. **Delegation invocation guard permits the hand-off** \u2014 the PR",
21915
- " carries the `origin:issue-worker` label, **or** the reviewer was",
22422
+ ` carries the \`${ORIGIN_ISSUE_WORKER_LABEL}\` label or the`,
22423
+ ` \`${ORIGIN_AGENT_LABEL}\` label, **or** the reviewer was`,
21916
22424
  " invoked with `--allow-human-author`. The same guard used for the",
21917
- " in-scope-fix delegation flow applies here unchanged.",
22425
+ " in-scope-fix delegation flow applies here unchanged \u2014 both",
22426
+ " agent-origin labels are delegation-eligible.",
21918
22427
  "3. **The `review:fixing` lease is currently free.** If",
21919
22428
  " `review:fixing` is already on the PR, another delegation is",
21920
22429
  " in-flight; skip conflict-resolution delegation, log the contention",
@@ -22213,42 +22722,64 @@ var prReviewerSubAgent = {
22213
22722
  "gh pr view <pr-number> --json state --jq '.state'",
22214
22723
  "```",
22215
22724
  "",
22216
- "- **`MERGED`** \u2014 clean up locally **and** verify the linked issue closed:",
22725
+ "- **`MERGED`** \u2014 clean up locally **and** verify **every** linked issue",
22726
+ " closed:",
22217
22727
  " ```bash",
22218
22728
  " git checkout {{repository.defaultBranch}}",
22219
22729
  " git pull origin {{repository.defaultBranch}}",
22220
22730
  " git fetch --prune origin",
22221
22731
  " git branch -d <branch-name> 2>/dev/null || git branch -D <branch-name> 2>/dev/null || true",
22222
22732
  " ```",
22223
- " Transition the linked issue to `status:done` (replaces whichever of",
22224
- " `status:ready-for-review` or `status:in-progress` it was carrying):",
22733
+ "",
22734
+ " **Post-merge closure verification.** Iterate the **full linked-issue",
22735
+ " set** from Phase 1.5 \u2014 the union of the `CLOSES` and `STRANDED`",
22736
+ " numbers `check-closing-keywords.sh` reported \u2014 not just the primary",
22737
+ " issue. This loop is the structural guard against the stranded-issue",
22738
+ " defect class: GitHub honours only one issue reference per closing",
22739
+ " keyword, so a body written as `Closes #A, #B` closes `#A` and leaves",
22740
+ " `#B` open forever with no signal anywhere. Because the reviewer",
22741
+ " already knows the full set, every survivor is closed here",
22742
+ " deterministically instead of being discovered months later by an",
22743
+ " audit.",
22744
+ "",
22745
+ " For **each** issue in the set, in order:",
22746
+ "",
22747
+ " 1. Transition it to `status:done` (replaces whichever of",
22748
+ " `status:ready-for-review` or `status:in-progress` it was carrying):",
22225
22749
  " ```bash",
22226
22750
  " gh issue edit <issue-number> \\",
22227
22751
  ' --remove-label "status:ready-for-review" \\',
22228
22752
  ' --remove-label "status:in-progress" \\',
22229
22753
  ' --add-label "status:done"',
22230
22754
  " ```",
22231
- " Then check the linked issue state:",
22755
+ " 2. Check its state:",
22232
22756
  " ```bash",
22233
22757
  " gh issue view <issue-number> --json state --jq '.state'",
22234
22758
  " ```",
22235
- " If the issue is **not** `CLOSED`, close it explicitly (this covers",
22236
- " malformed or missing closing keywords on the merge commit):",
22759
+ " 3. If the issue is **not** `CLOSED`, close it explicitly (this covers",
22760
+ " both a missing closing keyword and an issue stranded by a",
22761
+ " multi-issue keyword):",
22237
22762
  " ```bash",
22238
22763
  " gh issue close <issue-number> --reason completed",
22239
22764
  " gh issue comment <issue-number> --body 'PR #<pr-number> merged. Closing issue.'",
22240
22765
  " ```",
22241
- " Once `status:done` is applied (either via the edit above or the",
22242
- " explicit close path), run the targeted unblock sweep so any",
22243
- " dependents waiting on this issue flip from `status:blocked` to",
22244
- " `status:ready` immediately:",
22766
+ " 4. Once `status:done` is applied (either via the edit above or the",
22767
+ " explicit close path), run the targeted unblock sweep so any",
22768
+ " dependents waiting on this issue flip from `status:blocked` to",
22769
+ " `status:ready` immediately:",
22245
22770
  " ```bash",
22246
22771
  " .claude/procedures/unblock-dependents.sh <issue-number>",
22247
22772
  " ```",
22773
+ "",
22248
22774
  " Log the sweep's output (one line per processed dependent:",
22249
22775
  " `UNBLOCKED #<n>` / `STILL_BLOCKED #<n>` / `NO_DEPENDENTS #<m>`)",
22250
22776
  " in the PR review summary. See the **Agent-driven unblocking**",
22251
22777
  " section in `CLAUDE.md` for the full contract.",
22778
+ "",
22779
+ " Report one `Issue #<n>: <closed-by-github | closed-explicitly>` line",
22780
+ " per member of the set. Any issue that needed the explicit close was",
22781
+ " stranded by the PR body \u2014 say so in the summary so the authoring",
22782
+ " defect is visible, not just repaired.",
22252
22783
  "- **`CLOSED` (not merged)** \u2014 leave the branch in place; report the closure.",
22253
22784
  "- **Still `OPEN`** \u2014 auto-merge is pending; report and stop without deleting.",
22254
22785
  "",
@@ -22261,6 +22792,8 @@ var prReviewerSubAgent = {
22261
22792
  "```",
22262
22793
  "PR #<number> \u2014 <title>",
22263
22794
  "Linked issue: #<issue-number>",
22795
+ "Linked issue set: #<n> [, #<n>\u2026] (from check-closing-keywords.sh)",
22796
+ "Closing keywords: <ok | malformed: strands #<n>[, #<n>\u2026]>",
22264
22797
  "Review mode: <auto-merge | human-required>",
22265
22798
  "Reason: <short explanation from Phase 2.75>",
22266
22799
  "Verdict: AUTO_MERGE_ENABLED | AWAITING_HUMAN | DELEGATED_TO_WORKER | NEEDS_CHANGES | INELIGIBLE | BLOCKED",
@@ -22301,6 +22834,14 @@ var prReviewerSubAgent = {
22301
22834
  " PR in turn.",
22302
22835
  "2. **Never merge without a linked issue.** If the PR body has no",
22303
22836
  " `Closes #N` / `Fixes #N` / `Resolves #N`, comment and stop.",
22837
+ "2a. **Never merge without verifying every linked issue closes.** Resolve",
22838
+ " the linked-issue set with",
22839
+ " `.claude/procedures/check-closing-keywords.sh <pr-number>`, never by",
22840
+ " eyeballing the body. GitHub honours only one issue reference per",
22841
+ " closing keyword, so a `STRANDED` line means that issue will stay",
22842
+ " open after the merge unless Phase 5 closes it. Phase 5 must iterate",
22843
+ " the **whole** set \u2014 `CLOSES` plus `STRANDED` \u2014 and explicitly close",
22844
+ " every issue GitHub left open.",
22304
22845
  "3. **Never merge with failing CI.** Even if every criterion is met,",
22305
22846
  " block on red checks. Resolve CI status via the **CI",
22306
22847
  " Verification** section: read check-runs first, and only on a",
@@ -22313,7 +22854,7 @@ var prReviewerSubAgent = {
22313
22854
  " to `human-required`, Phase 4 applies `review:awaiting-human` and",
22314
22855
  " exits. Even if every acceptance criterion is met and CI is green,",
22315
22856
  " `gh pr merge --auto` is forbidden in that mode.",
22316
- "6. **Never push commits to the PR's branch yourself.** The reviewer is a decision-maker and orchestrator, not an implementer. When in-scope review comments require code changes **and** the PR carries the `origin:issue-worker` label (or the invocation prompt explicitly allows human-authored PRs via `--allow-human-author`), delegate the fix **by marker, not by spawn**: post a fix-list comment on the PR, apply the `review:needs-worker` label, hold the `review:fixing` lease, and exit returning `delegated`. Do **not** invoke `issue-worker` via the `Agent` tool \u2014 the reviewer often runs at depth-1 where nested sub-agent spawning is forbidden, so an in-session dispatch would silently abort. A top-level `issue-worker` session pulls the marker and applies the fix; a later reviewer confirm pass releases the lease. You must never call `git commit`, `git push`, `git rebase`, or `gh pr edit` against the head branch from this agent \u2014 all branch mutations belong to `issue-worker`.",
22857
+ `6. **Never push commits to the PR's branch yourself.** The reviewer is a decision-maker and orchestrator, not an implementer. When in-scope review comments require code changes **and** the PR carries ${DELEGATION_ELIGIBLE_ORIGIN_PHRASE} (or the invocation prompt explicitly allows human-authored PRs via \`--allow-human-author\`), delegate the fix **by marker, not by spawn**: post a fix-list comment on the PR, apply the \`review:needs-worker\` label, hold the \`review:fixing\` lease, and exit returning \`delegated\`. Do **not** invoke \`issue-worker\` via the \`Agent\` tool \u2014 the reviewer often runs at depth-1 where nested sub-agent spawning is forbidden, so an in-session dispatch would silently abort. A top-level \`issue-worker\` session pulls the marker and applies the fix; a later reviewer confirm pass releases the lease. You must never call \`git commit\`, \`git push\`, \`git rebase\`, or \`gh pr edit\` against the head branch from this agent \u2014 all branch mutations belong to \`issue-worker\`.`,
22317
22858
  "7. **Do not implement code.** You review, decide, and orchestrate. If",
22318
22859
  " the PR needs code changes and delegation is not permitted by Rule",
22319
22860
  " #6, comment and stop \u2014 do not attempt to fix it yourself.",
@@ -22413,8 +22954,8 @@ var reviewPrSkill = {
22413
22954
  "",
22414
22955
  "- **`--allow-human-author`** \u2014 opt the reviewer into processing",
22415
22956
  " human-authored PRs for **this invocation only**. By default, the",
22416
- " reviewer only processes bot-authored PRs (those carrying the",
22417
- " `origin:issue-worker` label); pass this flag to review a",
22957
+ " reviewer only processes bot-authored PRs (those carrying",
22958
+ ` ${DELEGATION_ELIGIBLE_ORIGIN_LIST}); pass this flag to review a`,
22418
22959
  " human-authored PR. The flag does not persist \u2014 subsequent",
22419
22960
  " invocations return to the bot-only default.",
22420
22961
  "",
@@ -22433,7 +22974,7 @@ var reviewPrSkill = {
22433
22974
  "",
22434
22975
  "Passing `--allow-human-author` opts the reviewer into processing",
22435
22976
  "human-authored PRs for this invocation only (the default scope is",
22436
- "bot-authored PRs labeled `origin:issue-worker`).",
22977
+ `bot-authored PRs carrying ${DELEGATION_ELIGIBLE_ORIGIN_LIST}).`,
22437
22978
  "",
22438
22979
  "## Input",
22439
22980
  "",
@@ -22475,8 +23016,8 @@ var reviewPrsSkill = {
22475
23016
  "",
22476
23017
  "- **`--allow-human-author`** \u2014 opt the reviewer into processing",
22477
23018
  " human-authored PRs for **this invocation only**. By default, the",
22478
- " loop only processes bot-authored PRs (those carrying the",
22479
- " `origin:issue-worker` label); pass this flag to include",
23019
+ " loop only processes bot-authored PRs (those carrying",
23020
+ ` ${DELEGATION_ELIGIBLE_ORIGIN_LIST}); pass this flag to include`,
22480
23021
  " human-authored PRs in the queue. The flag does not persist \u2014",
22481
23022
  " subsequent invocations return to the bot-only default.",
22482
23023
  "",
@@ -22528,7 +23069,7 @@ var reviewPrsSkill = {
22528
23069
  "",
22529
23070
  "Passing `--allow-human-author` opts the reviewer into including",
22530
23071
  "human-authored PRs in the queue for this invocation only (the default",
22531
- "scope is bot-authored PRs labeled `origin:issue-worker`).",
23072
+ `scope is bot-authored PRs carrying ${DELEGATION_ELIGIBLE_ORIGIN_LIST}).`,
22532
23073
  "",
22533
23074
  "## Output",
22534
23075
  "",
@@ -23174,7 +23715,7 @@ function buildPrReviewBundle(policy = resolvePrReviewPolicy()) {
23174
23715
  "",
23175
23716
  "### Label Glossary",
23176
23717
  "",
23177
- "Six review-workflow labels drive the feedback loop. They are",
23718
+ "Seven review-workflow labels drive the feedback loop. They are",
23178
23719
  "**contributed by the pr-review bundle's label set** and auto-sync",
23179
23720
  "into every consumer's label registry \u2014 the same way the",
23180
23721
  "`type:*` / `priority:*` / `status:*` taxonomy ships. Consumers do",
@@ -23184,7 +23725,8 @@ function buildPrReviewBundle(policy = resolvePrReviewPolicy()) {
23184
23725
  "",
23185
23726
  "| Label | Purpose |",
23186
23727
  "|-------|---------|",
23187
- "| `origin:issue-worker` | PR was opened by the `issue-worker` agent. Eligible for auto-delegation of in-scope fixes. Human-authored PRs lack this label and will not trigger delegation unless the reviewer is invoked with `--allow-human-author`. |",
23728
+ `| \`${ORIGIN_ISSUE_WORKER_LABEL}\` | PR was opened by the \`issue-worker\` agent specifically. Eligible for auto-delegation of in-scope fixes. |`,
23729
+ `| \`${ORIGIN_AGENT_LABEL}\` | **Umbrella origin label for agent-opened PRs.** Stamped by every phased analyst/writer agent (people-profile, company-profile, software-profile, customer-profile, bcm-writer, industry-discovery, business-models, standards-research, regulatory-research, maintenance-audit, meeting-analysis, agenda, research-pipeline, requirements-analyst, requirements-reviewer, requirements-writer) on the PRs it opens. Delegation-eligible on exactly the same terms as \`${ORIGIN_ISSUE_WORKER_LABEL}\` \u2014 the analyst family stamps this instead of \`${ORIGIN_ISSUE_WORKER_LABEL}\` because claiming the latter on a non-worker PR would be false. Human-authored PRs carry neither origin label and will not trigger delegation unless the reviewer is invoked with \`--allow-human-author\`. This label gates fix-**delegation** only; sweep and auto-merge eligibility have never been origin-gated. |`,
23188
23730
  "| `review:human-required` | Force human review regardless of what the policy would otherwise decide. The reviewer never enables auto-merge on a PR carrying this label. |",
23189
23731
  "| `review:auto-ok` | Force auto-merge regardless of what the policy would otherwise decide. **Also resolves outstanding `thinking_face` pushbacks** as an explicit maintainer override; the reviewer logs the override in the sticky summary. |",
23190
23732
  "| `review:awaiting-human` | Set by the reviewer when it completes its work on a `human-required` PR and is handing off the final merge decision. Cleared by a human (or by `review:auto-ok` flipping the PR back to `auto-merge` mode). |",
@@ -23194,8 +23736,8 @@ function buildPrReviewBundle(policy = resolvePrReviewPolicy()) {
23194
23736
  "### Reviewing Human-Authored PRs: the `--allow-human-author` Flag",
23195
23737
  "",
23196
23738
  "By default the reviewer only **delegates** in-scope fixes on",
23197
- "bot-authored PRs \u2014 those carrying the `origin:issue-worker`",
23198
- "label. Running `/review-pr <n>` or `/review-prs` on a",
23739
+ `bot-authored PRs \u2014 those carrying ${DELEGATION_ELIGIBLE_ORIGIN_PHRASE}.`,
23740
+ "Running `/review-pr <n>` or `/review-prs` on a",
23199
23741
  "human-authored PR still produces a full review (reactions,",
23200
23742
  "replies, sticky summary, and auto-merge when the policy allows",
23201
23743
  "it) but skips the delegation hand-off to `issue-worker` \u2014 the",
@@ -23229,10 +23771,15 @@ function buildPrReviewBundle(policy = resolvePrReviewPolicy()) {
23229
23771
  description: "PR review tasks"
23230
23772
  },
23231
23773
  {
23232
- name: "origin:issue-worker",
23774
+ name: ORIGIN_ISSUE_WORKER_LABEL,
23233
23775
  color: "5319E7",
23234
23776
  description: "PR opened by the issue-worker agent"
23235
23777
  },
23778
+ {
23779
+ name: ORIGIN_AGENT_LABEL,
23780
+ color: "5319E7",
23781
+ description: "PR opened by an agent in this project's pipeline; eligible for autonomous fix delegation"
23782
+ },
23236
23783
  {
23237
23784
  name: "review:auto-ok",
23238
23785
  color: "0E8A16",
@@ -23884,6 +24431,8 @@ function buildRegulatoryResearchAnalystSubAgent(paths, issueDefaults) {
23884
24431
  " label.",
23885
24432
  "4. Commit, push, open a PR (if applicable), and close the issue per",
23886
24433
  " your project's PR workflow.",
24434
+ " Stamp every PR you open with the `origin:agent` label \u2014 see the",
24435
+ " **Origin label** section below.",
23887
24436
  "",
23888
24437
  "---",
23889
24438
  "",
@@ -23891,6 +24440,10 @@ function buildRegulatoryResearchAnalystSubAgent(paths, issueDefaults) {
23891
24440
  "",
23892
24441
  "---",
23893
24442
  "",
24443
+ ...renderOriginStampSection(),
24444
+ "",
24445
+ "---",
24446
+ "",
23894
24447
  "## Phase 1: Scan (`regulatory:scan`)",
23895
24448
  "",
23896
24449
  "**Goal:** Enumerate regulations applicable to a given scope \u2014",
@@ -27091,6 +27644,8 @@ function buildRequirementsWriterSubAgent(paths) {
27091
27644
  "4. Commit, push, open a PR, and close the issue per your project's",
27092
27645
  " PR workflow. Closing the PR transitions the issue to",
27093
27646
  " `status:done` per the standard label conventions.",
27647
+ " Stamp every PR you open with the `origin:agent` label \u2014 see the",
27648
+ " **Origin label** section below.",
27094
27649
  "",
27095
27650
  "---",
27096
27651
  "",
@@ -27098,6 +27653,10 @@ function buildRequirementsWriterSubAgent(paths) {
27098
27653
  "",
27099
27654
  "---",
27100
27655
  "",
27656
+ ...renderOriginStampSection(),
27657
+ "",
27658
+ "---",
27659
+ "",
27101
27660
  "## The `req:write` Issue Schema",
27102
27661
  "",
27103
27662
  "This section is the **authoritative schema** for `req:write` issue",
@@ -27733,6 +28292,8 @@ function buildRequirementsAnalystSubAgent(paths, issueDefaults) {
27733
28292
  "3. Execute the phase handler that matches the issue's `req:*` label.",
27734
28293
  "4. Commit, push, open a PR (if applicable), and close the issue per your",
27735
28294
  " project's PR workflow.",
28295
+ " Stamp every PR you open with the `origin:agent` label \u2014 see the",
28296
+ " **Origin label** section below.",
27736
28297
  "",
27737
28298
  "---",
27738
28299
  "",
@@ -27740,6 +28301,10 @@ function buildRequirementsAnalystSubAgent(paths, issueDefaults) {
27740
28301
  "",
27741
28302
  "---",
27742
28303
  "",
28304
+ ...renderOriginStampSection(),
28305
+ "",
28306
+ "---",
28307
+ "",
27743
28308
  "## Phase 1: Scan (`req:scan`)",
27744
28309
  "",
27745
28310
  "**Goal:** Read source documents, identify where requirements are missing,",
@@ -28336,6 +28901,10 @@ function buildRequirementsReviewerSubAgent(paths, issueDefaults) {
28336
28901
  "",
28337
28902
  "---",
28338
28903
  "",
28904
+ ...renderOriginStampSection(),
28905
+ "",
28906
+ "---",
28907
+ "",
28339
28908
  "## Phase Router",
28340
28909
  "",
28341
28910
  "Before reading any documents, classify the session by reading the",
@@ -29715,6 +30284,8 @@ function buildResearchAnalystSubAgent(paths) {
29715
30284
  " label.",
29716
30285
  "4. Commit, push, open a PR (if applicable), and close the issue per",
29717
30286
  " your project's PR workflow.",
30287
+ " Stamp every PR you open with the `origin:agent` label \u2014 see the",
30288
+ " **Origin label** section below.",
29718
30289
  "",
29719
30290
  "---",
29720
30291
  "",
@@ -29722,6 +30293,10 @@ function buildResearchAnalystSubAgent(paths) {
29722
30293
  "",
29723
30294
  "---",
29724
30295
  "",
30296
+ ...renderOriginStampSection(),
30297
+ "",
30298
+ "---",
30299
+ "",
29725
30300
  "## Phase 1: Scope (`research:scope`)",
29726
30301
  "",
29727
30302
  "**Goal:** Turn a natural-language research question into a bounded",
@@ -30460,6 +31035,8 @@ function buildSoftwareProfileAnalystSubAgent(paths, issueDefaults, tier) {
30460
31035
  " label.",
30461
31036
  "4. Commit, push, open a PR (if applicable), and close the issue per",
30462
31037
  " your project's PR workflow.",
31038
+ " Stamp every PR you open with the `origin:agent` label \u2014 see the",
31039
+ " **Origin label** section below.",
30463
31040
  "",
30464
31041
  "---",
30465
31042
  "",
@@ -30467,6 +31044,10 @@ function buildSoftwareProfileAnalystSubAgent(paths, issueDefaults, tier) {
30467
31044
  "",
30468
31045
  "---",
30469
31046
  "",
31047
+ ...renderOriginStampSection(),
31048
+ "",
31049
+ "---",
31050
+ "",
30470
31051
  "## Phase 1: Research (`software:research`)",
30471
31052
  "",
30472
31053
  "**Goal:** Gather public sources into a bounded research-notes file.",
@@ -31598,6 +32179,8 @@ function buildStandardsResearchAnalystSubAgent(paths, issueDefaults) {
31598
32179
  " label.",
31599
32180
  "4. Commit, push, open a PR (if applicable), and close the issue per",
31600
32181
  " your project's PR workflow.",
32182
+ " Stamp every PR you open with the `origin:agent` label \u2014 see the",
32183
+ " **Origin label** section below.",
31601
32184
  "",
31602
32185
  "---",
31603
32186
  "",
@@ -31605,6 +32188,10 @@ function buildStandardsResearchAnalystSubAgent(paths, issueDefaults) {
31605
32188
  "",
31606
32189
  "---",
31607
32190
  "",
32191
+ ...renderOriginStampSection(),
32192
+ "",
32193
+ "---",
32194
+ "",
31608
32195
  "## Phase 1: Scope (`standards:scope`)",
31609
32196
  "",
31610
32197
  "**Goal:** Plan the research campaign for a standard \u2014 identify which",
@@ -41971,6 +42558,7 @@ export {
41971
42558
  buildUnblockDependentsProcedure,
41972
42559
  bundleNameForWorkflowRule,
41973
42560
  businessModelsBundle,
42561
+ checkClosingKeywordsProcedure,
41974
42562
  checkDocSamplesProcedure,
41975
42563
  checkLinksProcedure,
41976
42564
  classifyIssueScope,
@@ -42045,6 +42633,7 @@ export {
42045
42633
  renderCdkRollback,
42046
42634
  renderCdkSynth,
42047
42635
  renderCdkWatch,
42636
+ renderCheckClosingKeywordsScript,
42048
42637
  renderCheckDocSamplesProcedure,
42049
42638
  renderCheckLinksProcedure,
42050
42639
  renderCustomDocSectionBlock,