@codedrifters/configulator 0.0.408 → 0.0.409
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.d.mts +36 -1
- package/lib/index.d.ts +36 -1
- package/lib/index.js +459 -20
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +457 -20
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -373,6 +373,7 @@ __export(index_exports, {
|
|
|
373
373
|
buildUnblockDependentsProcedure: () => buildUnblockDependentsProcedure,
|
|
374
374
|
bundleNameForWorkflowRule: () => bundleNameForWorkflowRule,
|
|
375
375
|
businessModelsBundle: () => businessModelsBundle,
|
|
376
|
+
checkClosingKeywordsProcedure: () => checkClosingKeywordsProcedure,
|
|
376
377
|
checkDocSamplesProcedure: () => checkDocSamplesProcedure,
|
|
377
378
|
checkLinksProcedure: () => checkLinksProcedure,
|
|
378
379
|
classifyIssueScope: () => classifyIssueScope,
|
|
@@ -447,6 +448,7 @@ __export(index_exports, {
|
|
|
447
448
|
renderCdkRollback: () => renderCdkRollback,
|
|
448
449
|
renderCdkSynth: () => renderCdkSynth,
|
|
449
450
|
renderCdkWatch: () => renderCdkWatch,
|
|
451
|
+
renderCheckClosingKeywordsScript: () => renderCheckClosingKeywordsScript,
|
|
450
452
|
renderCheckDocSamplesProcedure: () => renderCheckDocSamplesProcedure,
|
|
451
453
|
renderCheckLinksProcedure: () => renderCheckLinksProcedure,
|
|
452
454
|
renderCustomDocSectionBlock: () => renderCustomDocSectionBlock,
|
|
@@ -11757,6 +11759,278 @@ var docsSyncBundle = buildDocsSyncBundle();
|
|
|
11757
11759
|
|
|
11758
11760
|
// src/agent/bundles/github-workflow.ts
|
|
11759
11761
|
var import_github = require("projen/lib/github");
|
|
11762
|
+
|
|
11763
|
+
// src/agent/bundles/closing-keywords.ts
|
|
11764
|
+
function renderCheckClosingKeywordsScript() {
|
|
11765
|
+
return [
|
|
11766
|
+
"#!/usr/bin/env bash",
|
|
11767
|
+
"# check-closing-keywords.sh \u2014 Detect PR bodies that strand issues.",
|
|
11768
|
+
"#",
|
|
11769
|
+
"# GitHub honours exactly ONE issue reference per closing keyword. A",
|
|
11770
|
+
"# body that writes `Closes #593, #594, #595` closes #593 and leaves",
|
|
11771
|
+
"# #594 and #595 open forever. The only correct form is one keyword",
|
|
11772
|
+
"# per issue, each on its own line:",
|
|
11773
|
+
"#",
|
|
11774
|
+
"# Closes #593",
|
|
11775
|
+
"# Closes #594",
|
|
11776
|
+
"#",
|
|
11777
|
+
"# Usage:",
|
|
11778
|
+
"# .claude/procedures/check-closing-keywords.sh <pr-number>",
|
|
11779
|
+
"# .claude/procedures/check-closing-keywords.sh --body-file <path>",
|
|
11780
|
+
"#",
|
|
11781
|
+
"# Output (one stable line per reference, then a summary line):",
|
|
11782
|
+
"# CLOSES #<n> line=<L>",
|
|
11783
|
+
"# A reference GitHub WILL honour (the first one after a keyword).",
|
|
11784
|
+
"# STRANDED #<n> line=<L> keyword=<kw> first=#<n>",
|
|
11785
|
+
"# A reference chained onto an earlier one. GitHub will NOT",
|
|
11786
|
+
"# close it \u2014 the issue is stranded.",
|
|
11787
|
+
"# CLOSING_KEYWORDS_OK closes=<K>",
|
|
11788
|
+
"# CLOSING_KEYWORDS_MALFORMED closes=<K> stranded=<S>",
|
|
11789
|
+
"# NO_CLOSING_KEYWORD",
|
|
11790
|
+
"#",
|
|
11791
|
+
"# Exit codes:",
|
|
11792
|
+
"# 0 \u2014 at least one closing keyword, every one well-formed",
|
|
11793
|
+
"# 1 \u2014 at least one stranded reference (malformed body)",
|
|
11794
|
+
"# 2 \u2014 argument error",
|
|
11795
|
+
"# 3 \u2014 required command not on PATH",
|
|
11796
|
+
"# 4 \u2014 could not read the PR body",
|
|
11797
|
+
"# 5 \u2014 no closing keyword found at all",
|
|
11798
|
+
"",
|
|
11799
|
+
"set -uo pipefail",
|
|
11800
|
+
"",
|
|
11801
|
+
"# \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",
|
|
11802
|
+
"",
|
|
11803
|
+
"err() {",
|
|
11804
|
+
' printf "check-closing-keywords.sh: %s\\n" "$*" >&2',
|
|
11805
|
+
"}",
|
|
11806
|
+
"",
|
|
11807
|
+
"usage() {",
|
|
11808
|
+
" cat >&2 <<'USAGE'",
|
|
11809
|
+
"Usage: check-closing-keywords.sh <pr-number>",
|
|
11810
|
+
" check-closing-keywords.sh --body-file <path>",
|
|
11811
|
+
"",
|
|
11812
|
+
" <pr-number> Positive integer PR number; the body is read",
|
|
11813
|
+
" via `gh pr view <n> --json body`.",
|
|
11814
|
+
" --body-file <path> Read the body from a file instead. Use this to",
|
|
11815
|
+
" check a drafted body BEFORE `gh pr create`.",
|
|
11816
|
+
"",
|
|
11817
|
+
"Emits one CLOSES / STRANDED line per issue reference, then a summary.",
|
|
11818
|
+
"Exits 1 when any reference is stranded by a multi-issue keyword.",
|
|
11819
|
+
"USAGE",
|
|
11820
|
+
"}",
|
|
11821
|
+
"",
|
|
11822
|
+
"# \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",
|
|
11823
|
+
"",
|
|
11824
|
+
'mode=""',
|
|
11825
|
+
'arg=""',
|
|
11826
|
+
"",
|
|
11827
|
+
"while [[ $# -gt 0 ]]; do",
|
|
11828
|
+
' case "$1" in',
|
|
11829
|
+
" --body-file)",
|
|
11830
|
+
' if [[ $# -lt 2 || -z "${2:-}" ]]; then',
|
|
11831
|
+
' err "--body-file requires a value"',
|
|
11832
|
+
" usage",
|
|
11833
|
+
" exit 2",
|
|
11834
|
+
" fi",
|
|
11835
|
+
' if [[ -n "$mode" ]]; then',
|
|
11836
|
+
' err "specify either a PR number or --body-file, not both"',
|
|
11837
|
+
" exit 2",
|
|
11838
|
+
" fi",
|
|
11839
|
+
' mode="file"',
|
|
11840
|
+
' arg="$2"',
|
|
11841
|
+
" shift 2",
|
|
11842
|
+
" ;;",
|
|
11843
|
+
" -h|--help)",
|
|
11844
|
+
" usage",
|
|
11845
|
+
" exit 0",
|
|
11846
|
+
" ;;",
|
|
11847
|
+
" -*)",
|
|
11848
|
+
' err "unknown option: $1"',
|
|
11849
|
+
" usage",
|
|
11850
|
+
" exit 2",
|
|
11851
|
+
" ;;",
|
|
11852
|
+
" *)",
|
|
11853
|
+
' if [[ -n "$mode" ]]; then',
|
|
11854
|
+
' err "unexpected extra argument: $1"',
|
|
11855
|
+
" usage",
|
|
11856
|
+
" exit 2",
|
|
11857
|
+
" fi",
|
|
11858
|
+
' mode="pr"',
|
|
11859
|
+
' arg="$1"',
|
|
11860
|
+
" shift",
|
|
11861
|
+
" ;;",
|
|
11862
|
+
" esac",
|
|
11863
|
+
"done",
|
|
11864
|
+
"",
|
|
11865
|
+
'if [[ -z "$mode" ]]; then',
|
|
11866
|
+
' err "expected a PR number or --body-file <path>"',
|
|
11867
|
+
" usage",
|
|
11868
|
+
" exit 2",
|
|
11869
|
+
"fi",
|
|
11870
|
+
"",
|
|
11871
|
+
"# \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",
|
|
11872
|
+
"",
|
|
11873
|
+
'body=""',
|
|
11874
|
+
"",
|
|
11875
|
+
'if [[ "$mode" == "file" ]]; then',
|
|
11876
|
+
' if [[ ! -r "$arg" ]]; then',
|
|
11877
|
+
' err "body file not readable: $arg"',
|
|
11878
|
+
" exit 4",
|
|
11879
|
+
" fi",
|
|
11880
|
+
' body=$(cat "$arg")',
|
|
11881
|
+
"else",
|
|
11882
|
+
' if ! [[ "$arg" =~ ^[1-9][0-9]*$ ]]; then',
|
|
11883
|
+
` err "PR number must be a positive integer (got: '$arg')"`,
|
|
11884
|
+
" exit 2",
|
|
11885
|
+
" fi",
|
|
11886
|
+
" if ! command -v gh >/dev/null 2>&1; then",
|
|
11887
|
+
' err "required command not found on PATH: gh"',
|
|
11888
|
+
" exit 3",
|
|
11889
|
+
" fi",
|
|
11890
|
+
` if ! body=$(gh pr view "$arg" --json body -q '.body' 2>/dev/null); then`,
|
|
11891
|
+
' err "could not read the body of PR #${arg} via gh"',
|
|
11892
|
+
" exit 4",
|
|
11893
|
+
" fi",
|
|
11894
|
+
"fi",
|
|
11895
|
+
"",
|
|
11896
|
+
"# \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",
|
|
11897
|
+
"#",
|
|
11898
|
+
"# Regexes live in single-quoted variables so backticks and other shell",
|
|
11899
|
+
"# metacharacters inside them are never expanded by [[ ]].",
|
|
11900
|
+
"#",
|
|
11901
|
+
"# kw_re matches a closing keyword plus its FIRST issue reference. The",
|
|
11902
|
+
"# leading (^|[^[:alnum:]_]) is a word boundary so `prefixes #1` is not",
|
|
11903
|
+
"# read as `fixes #1`.",
|
|
11904
|
+
"#",
|
|
11905
|
+
"# sep_re / ws_re walk the tail AFTER that first reference, matching",
|
|
11906
|
+
"# only references CHAINED directly onto it by a separator a human",
|
|
11907
|
+
"# would mistake for a list: `,` `/` `&` `+` `and` `, and`, or",
|
|
11908
|
+
"# bare whitespace. Anything else \u2014 a period, a dash, a word, an",
|
|
11909
|
+
"# opening paren \u2014 terminates the chain. That is what keeps prose like",
|
|
11910
|
+
"# `Closes #893. See #607 and #860 for context.` from false-positiving:",
|
|
11911
|
+
"# the `.` after #893 ends the chain before `#607` is ever considered.",
|
|
11912
|
+
"",
|
|
11913
|
+
"fence_re='^[[:space:]]{0,3}(```+|~~~+)'",
|
|
11914
|
+
"quote_re='^[[:space:]]*>'",
|
|
11915
|
+
"kw_re='(^|[^[:alnum:]_])(closes|closed|close|fixes|fixed|fix|resolves|resolved|resolve)[[:space:]]*:?[[:space:]]*#([0-9]+)(.*)$'",
|
|
11916
|
+
"sep_re='^[[:space:]]*(,[[:space:]]*and|,|/|&|[+]|and)[[:space:]]*#([0-9]+)(.*)$'",
|
|
11917
|
+
"ws_re='^[[:space:]]+#([0-9]+)(.*)$'",
|
|
11918
|
+
"",
|
|
11919
|
+
"# Keywords are case-insensitive on GitHub, so the scan is too.",
|
|
11920
|
+
"shopt -s nocasematch",
|
|
11921
|
+
"",
|
|
11922
|
+
"# A single backtick, held in a variable so the code-span stripper",
|
|
11923
|
+
"# never has to quote one inline.",
|
|
11924
|
+
"bt='`'",
|
|
11925
|
+
"",
|
|
11926
|
+
"# Remove inline code spans (`...`) from a line. A PR body that",
|
|
11927
|
+
"# DISCUSSES the malformed form \u2014 writing `Closes #593, #594` inside",
|
|
11928
|
+
"# backticks \u2014 must not trip the detector.",
|
|
11929
|
+
"strip_code_spans() {",
|
|
11930
|
+
' local s="$1"',
|
|
11931
|
+
' local out=""',
|
|
11932
|
+
' while [[ "$s" == *"$bt"*"$bt"* ]]; do',
|
|
11933
|
+
' out="$out${s%%$bt*}"',
|
|
11934
|
+
' s="${s#*$bt}"',
|
|
11935
|
+
' s="${s#*$bt}"',
|
|
11936
|
+
" done",
|
|
11937
|
+
` printf '%s%s' "$out" "$s"`,
|
|
11938
|
+
"}",
|
|
11939
|
+
"",
|
|
11940
|
+
"# \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",
|
|
11941
|
+
"",
|
|
11942
|
+
"line_no=0",
|
|
11943
|
+
"in_fence=0",
|
|
11944
|
+
'fence_marker=""',
|
|
11945
|
+
"closes_count=0",
|
|
11946
|
+
"stranded_count=0",
|
|
11947
|
+
"",
|
|
11948
|
+
'while IFS= read -r raw || [[ -n "$raw" ]]; do',
|
|
11949
|
+
" line_no=$((line_no + 1))",
|
|
11950
|
+
" # gh returns CRLF bodies; strip the CR so end-of-line matching works.",
|
|
11951
|
+
` raw="\${raw//$'\\r'/}"`,
|
|
11952
|
+
"",
|
|
11953
|
+
" # Fenced code blocks are quoted content, never the PR's own intent.",
|
|
11954
|
+
' if [[ "$raw" =~ $fence_re ]]; then',
|
|
11955
|
+
' marker="${BASH_REMATCH[1]:0:1}"',
|
|
11956
|
+
" if [[ $in_fence -eq 0 ]]; then",
|
|
11957
|
+
" in_fence=1",
|
|
11958
|
+
' fence_marker="$marker"',
|
|
11959
|
+
' elif [[ "$marker" == "$fence_marker" ]]; then',
|
|
11960
|
+
" in_fence=0",
|
|
11961
|
+
' fence_marker=""',
|
|
11962
|
+
" fi",
|
|
11963
|
+
" continue",
|
|
11964
|
+
" fi",
|
|
11965
|
+
" if [[ $in_fence -eq 1 ]]; then",
|
|
11966
|
+
" continue",
|
|
11967
|
+
" fi",
|
|
11968
|
+
"",
|
|
11969
|
+
" # Blockquoted text is someone else's words being cited.",
|
|
11970
|
+
' if [[ "$raw" =~ $quote_re ]]; then',
|
|
11971
|
+
" continue",
|
|
11972
|
+
" fi",
|
|
11973
|
+
"",
|
|
11974
|
+
' line=$(strip_code_spans "$raw")',
|
|
11975
|
+
"",
|
|
11976
|
+
' rest="$line"',
|
|
11977
|
+
' while [[ "$rest" =~ $kw_re ]]; do',
|
|
11978
|
+
' kw="${BASH_REMATCH[2]}"',
|
|
11979
|
+
' first="${BASH_REMATCH[3]}"',
|
|
11980
|
+
' tail="${BASH_REMATCH[4]}"',
|
|
11981
|
+
"",
|
|
11982
|
+
` printf 'CLOSES #%s line=%s\\n' "$first" "$line_no"`,
|
|
11983
|
+
" closes_count=$((closes_count + 1))",
|
|
11984
|
+
"",
|
|
11985
|
+
" # Walk every reference chained onto this keyword's first one.",
|
|
11986
|
+
' t="$tail"',
|
|
11987
|
+
" while :; do",
|
|
11988
|
+
' if [[ "$t" =~ $sep_re ]]; then',
|
|
11989
|
+
' extra="${BASH_REMATCH[2]}"',
|
|
11990
|
+
' t="${BASH_REMATCH[3]}"',
|
|
11991
|
+
' elif [[ "$t" =~ $ws_re ]]; then',
|
|
11992
|
+
' extra="${BASH_REMATCH[1]}"',
|
|
11993
|
+
' t="${BASH_REMATCH[2]}"',
|
|
11994
|
+
" else",
|
|
11995
|
+
" break",
|
|
11996
|
+
" fi",
|
|
11997
|
+
" printf 'STRANDED #%s line=%s keyword=%s first=#%s\\n' \\",
|
|
11998
|
+
' "$extra" "$line_no" "$kw" "$first"',
|
|
11999
|
+
" stranded_count=$((stranded_count + 1))",
|
|
12000
|
+
" done",
|
|
12001
|
+
"",
|
|
12002
|
+
' rest="$t"',
|
|
12003
|
+
" done",
|
|
12004
|
+
'done <<< "$body"',
|
|
12005
|
+
"",
|
|
12006
|
+
"# \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",
|
|
12007
|
+
"",
|
|
12008
|
+
"if [[ $closes_count -eq 0 ]]; then",
|
|
12009
|
+
' echo "NO_CLOSING_KEYWORD"',
|
|
12010
|
+
" exit 5",
|
|
12011
|
+
"fi",
|
|
12012
|
+
"",
|
|
12013
|
+
"if [[ $stranded_count -gt 0 ]]; then",
|
|
12014
|
+
" printf 'CLOSING_KEYWORDS_MALFORMED closes=%s stranded=%s\\n' \\",
|
|
12015
|
+
' "$closes_count" "$stranded_count"',
|
|
12016
|
+
' err "GitHub honours only ONE issue reference per closing keyword."',
|
|
12017
|
+
' err "Give every issue its own keyword, one per line:"',
|
|
12018
|
+
' err " Closes #123"',
|
|
12019
|
+
' err " Closes #456"',
|
|
12020
|
+
" exit 1",
|
|
12021
|
+
"fi",
|
|
12022
|
+
"",
|
|
12023
|
+
`printf 'CLOSING_KEYWORDS_OK closes=%s\\n' "$closes_count"`,
|
|
12024
|
+
"exit 0"
|
|
12025
|
+
].join("\n");
|
|
12026
|
+
}
|
|
12027
|
+
var checkClosingKeywordsProcedure = {
|
|
12028
|
+
name: "check-closing-keywords.sh",
|
|
12029
|
+
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.",
|
|
12030
|
+
content: renderCheckClosingKeywordsScript()
|
|
12031
|
+
};
|
|
12032
|
+
|
|
12033
|
+
// src/agent/bundles/github-workflow.ts
|
|
11760
12034
|
var setIssueTypeProcedure = {
|
|
11761
12035
|
name: "set-issue-type.sh",
|
|
11762
12036
|
description: "Assign a GitHub issue type (Feature / Task / Epic / Bug / etc.) via the updateIssueIssueType GraphQL mutation in a single step",
|
|
@@ -12431,8 +12705,24 @@ function buildGithubWorkflowBundle(buildPolicy = DEFAULT_BUILD_POLICY) {
|
|
|
12431
12705
|
"5. **Push the branch** to origin: `git push -u origin <branch>`",
|
|
12432
12706
|
"6. **Create the PR** using `gh pr create`:",
|
|
12433
12707
|
" - **Title**: use a conventional commit style title (e.g., `feat(scope): short description`)",
|
|
12434
|
-
" - **Body**: include `Closes #<issue-number>` (derived from the branch name) and a brief summary of changes",
|
|
12435
|
-
"7. **
|
|
12708
|
+
" - **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.",
|
|
12709
|
+
"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`.",
|
|
12710
|
+
"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.",
|
|
12711
|
+
"",
|
|
12712
|
+
"### Closing Keywords",
|
|
12713
|
+
"",
|
|
12714
|
+
"**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:",
|
|
12715
|
+
"",
|
|
12716
|
+
"```markdown",
|
|
12717
|
+
"Closes #593 <- correct: repeat the keyword, one issue each",
|
|
12718
|
+
"Closes #594",
|
|
12719
|
+
"",
|
|
12720
|
+
"Closes #593, #594 <- WRONG: closes #593, strands #594",
|
|
12721
|
+
"Closes #821 / #837 <- WRONG: closes #821, strands #837",
|
|
12722
|
+
"Closes #821 and #837 <- WRONG: closes #821, strands #837",
|
|
12723
|
+
"```",
|
|
12724
|
+
"",
|
|
12725
|
+
"`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.",
|
|
12436
12726
|
"",
|
|
12437
12727
|
"### PR Body Template",
|
|
12438
12728
|
"",
|
|
@@ -12449,10 +12739,18 @@ function buildGithubWorkflowBundle(buildPolicy = DEFAULT_BUILD_POLICY) {
|
|
|
12449
12739
|
"- [ ] Relevant changes have been reviewed",
|
|
12450
12740
|
"```",
|
|
12451
12741
|
"",
|
|
12742
|
+
"When the PR closes more than one issue, the closing block expands to one keyword per issue \u2014 it never becomes a list:",
|
|
12743
|
+
"",
|
|
12744
|
+
"```markdown",
|
|
12745
|
+
"Closes #<first-issue-number>",
|
|
12746
|
+
"Closes #<second-issue-number>",
|
|
12747
|
+
"```",
|
|
12748
|
+
"",
|
|
12452
12749
|
"### Important",
|
|
12453
12750
|
"",
|
|
12454
12751
|
"- Always derive the issue number from the branch name (e.g., `feat/42-add-login` \u2192 `#42`)",
|
|
12455
12752
|
"- Use conventional commit format for the PR title",
|
|
12753
|
+
"- **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",
|
|
12456
12754
|
"- Delegate merge to the `pr-reviewer` sub-agent \u2014 do not merge manually and do not enable auto-merge directly"
|
|
12457
12755
|
].join("\n"),
|
|
12458
12756
|
tags: ["workflow"]
|
|
@@ -12485,7 +12783,11 @@ function buildGithubWorkflowBundle(buildPolicy = DEFAULT_BUILD_POLICY) {
|
|
|
12485
12783
|
}
|
|
12486
12784
|
],
|
|
12487
12785
|
skills: [cleanMergedBranchesSkill],
|
|
12488
|
-
procedures: [
|
|
12786
|
+
procedures: [
|
|
12787
|
+
setIssueTypeProcedure,
|
|
12788
|
+
cleanMergedBranchesProcedure,
|
|
12789
|
+
checkClosingKeywordsProcedure
|
|
12790
|
+
]
|
|
12489
12791
|
};
|
|
12490
12792
|
}
|
|
12491
12793
|
var githubWorkflowBundle = buildGithubWorkflowBundle();
|
|
@@ -19573,6 +19875,27 @@ var issueWorkerSubAgent = {
|
|
|
19573
19875
|
'Closes #<issue-number>"',
|
|
19574
19876
|
"```",
|
|
19575
19877
|
"",
|
|
19878
|
+
"**One closing keyword per issue, each on its own line.** GitHub honours",
|
|
19879
|
+
"only one issue reference per keyword, so `Closes #A, #B` closes `#A` and",
|
|
19880
|
+
"leaves `#B` open forever \u2014 the same is true of `Closes #A / #B` and",
|
|
19881
|
+
"`Closes #A and #B`. A PR that closes several issues repeats the keyword:",
|
|
19882
|
+
"",
|
|
19883
|
+
"```",
|
|
19884
|
+
"Closes #<first-issue-number>",
|
|
19885
|
+
"Closes #<second-issue-number>",
|
|
19886
|
+
"```",
|
|
19887
|
+
"",
|
|
19888
|
+
"Verify it mechanically before moving on \u2014 this exits non-zero and names",
|
|
19889
|
+
"every issue the body would strand:",
|
|
19890
|
+
"",
|
|
19891
|
+
"```bash",
|
|
19892
|
+
".claude/procedures/check-closing-keywords.sh <pr-number>",
|
|
19893
|
+
"```",
|
|
19894
|
+
"",
|
|
19895
|
+
"If it reports `CLOSING_KEYWORDS_MALFORMED`, fix the body with",
|
|
19896
|
+
"`gh pr edit <pr-number> --body-file <path>` and re-run until it exits",
|
|
19897
|
+
"`0`. See the **Closing Keywords** section of the `pr-workflow` rule.",
|
|
19898
|
+
"",
|
|
19576
19899
|
"Do **not** enable auto-merge yourself. The `pr-reviewer` sub-agent owns",
|
|
19577
19900
|
"review and merge for every PR \u2014 it verifies the diff against the issue's",
|
|
19578
19901
|
"acceptance criteria and enables squash auto-merge only when all checks",
|
|
@@ -21252,8 +21575,32 @@ var prReviewerSubAgent = {
|
|
|
21252
21575
|
" resolve CI via the **CI Verification** section's Actions-runs",
|
|
21253
21576
|
" fallback before deciding eligibility \u2014 do **not** treat the 403 as",
|
|
21254
21577
|
" either a pass or an automatic disqualification.",
|
|
21255
|
-
"3. The PR body contains
|
|
21256
|
-
" `Closes #N`, `Fixes #N`,
|
|
21578
|
+
"3. The PR body contains at least one linked issue via a closing",
|
|
21579
|
+
" keyword (`Closes #N`, `Fixes #N`, `Resolves #N`, case-insensitive).",
|
|
21580
|
+
" Resolve this mechanically \u2014 do **not** eyeball the body:",
|
|
21581
|
+
"",
|
|
21582
|
+
"```bash",
|
|
21583
|
+
".claude/procedures/check-closing-keywords.sh <pr-number>",
|
|
21584
|
+
"```",
|
|
21585
|
+
"",
|
|
21586
|
+
" The procedure is the single source of truth for closing-keyword",
|
|
21587
|
+
" parsing in this pipeline. It emits one `CLOSES #<n> line=<L>` line",
|
|
21588
|
+
" per reference GitHub will honour, one",
|
|
21589
|
+
" `STRANDED #<n> line=<L> keyword=<kw> first=#<n>` line per reference",
|
|
21590
|
+
" GitHub will silently ignore, then a summary line. It skips fenced",
|
|
21591
|
+
" code blocks, blockquotes, and inline code spans, so a body that",
|
|
21592
|
+
" merely discusses closing keywords does not confuse it.",
|
|
21593
|
+
"",
|
|
21594
|
+
" - Exit `5` / `NO_CLOSING_KEYWORD` \u2014 check (3) **fails**; the PR has",
|
|
21595
|
+
" no linked issue.",
|
|
21596
|
+
" - Exit `0` / `CLOSING_KEYWORDS_OK` \u2014 check (3) passes. Record the",
|
|
21597
|
+
" `CLOSES` set as the **linked-issue set** for the rest of the pass.",
|
|
21598
|
+
" - Exit `1` / `CLOSING_KEYWORDS_MALFORMED` \u2014 check (3) **passes**",
|
|
21599
|
+
" (there is a linked issue), but the body would strand every",
|
|
21600
|
+
" `STRANDED` issue. This is **not** an eligibility rejection \u2014 the",
|
|
21601
|
+
" code is not at fault. Record both the `CLOSES` and `STRANDED`",
|
|
21602
|
+
" sets as the linked-issue set and carry the malformed finding into",
|
|
21603
|
+
" Phase 4 and Phase 5.",
|
|
21257
21604
|
"",
|
|
21258
21605
|
"If the **CI** check (2) or the **linked-issue** check (3) fails, post",
|
|
21259
21606
|
"a short comment explaining the reason and stop. Do not proceed to",
|
|
@@ -21321,16 +21668,29 @@ var prReviewerSubAgent = {
|
|
|
21321
21668
|
"gh pr checks <pr-number>",
|
|
21322
21669
|
"```",
|
|
21323
21670
|
"",
|
|
21324
|
-
"
|
|
21325
|
-
"
|
|
21671
|
+
"Use the **linked-issue set** already computed in Phase 1.5 \u2014 the union",
|
|
21672
|
+
"of the `CLOSES` and `STRANDED` issue numbers the",
|
|
21673
|
+
"`check-closing-keywords.sh` run reported. Do not re-derive it by",
|
|
21674
|
+
"reading the body yourself.",
|
|
21326
21675
|
"",
|
|
21327
|
-
"
|
|
21328
|
-
"
|
|
21676
|
+
"Most PRs link exactly one issue, and every phase below reads naturally",
|
|
21677
|
+
"in the singular. When the set has more than one member, treat **every**",
|
|
21678
|
+
"member as a linked issue: fetch each one, union their acceptance",
|
|
21679
|
+
"criteria in Phase 3, and close each one in Phase 5. The **primary**",
|
|
21680
|
+
"linked issue \u2014 the one cited in the sticky summary and the structured",
|
|
21681
|
+
"report \u2014 is the first `CLOSES` entry.",
|
|
21682
|
+
"",
|
|
21683
|
+
"Then fetch each linked issue, including its `issueType` so Phase 2.75",
|
|
21684
|
+
"can evaluate the issue-type portion of the review policy:",
|
|
21329
21685
|
"",
|
|
21330
21686
|
"```bash",
|
|
21331
21687
|
"gh issue view <issue-number> --json number,title,body,labels,state,issueType",
|
|
21332
21688
|
"```",
|
|
21333
21689
|
"",
|
|
21690
|
+
"When several issues are linked, Phase 2.75's issue-type rule matches if",
|
|
21691
|
+
"**any** linked issue's type is in `human-required.issue-types` \u2014 the",
|
|
21692
|
+
"conservative reading, consistent with the policy's mixed-signal rule.",
|
|
21693
|
+
"",
|
|
21334
21694
|
"## Phase 2.5: Gather Comments",
|
|
21335
21695
|
"",
|
|
21336
21696
|
"Before deciding review mode, collect every human comment on the PR so",
|
|
@@ -21628,6 +21988,14 @@ var prReviewerSubAgent = {
|
|
|
21628
21988
|
"",
|
|
21629
21989
|
"- **Convention compliance** \u2014 PR title uses a conventional commit prefix,",
|
|
21630
21990
|
" body includes a closing keyword, branch name follows project conventions",
|
|
21991
|
+
"- **Closing-keyword integrity** \u2014 the Phase 1.5",
|
|
21992
|
+
" `check-closing-keywords.sh` run exited `0`. A",
|
|
21993
|
+
" `CLOSING_KEYWORDS_MALFORMED` result is a **Suggested** finding (not",
|
|
21994
|
+
" blocking): the diff is unaffected, and Phase 5 closes the stranded",
|
|
21995
|
+
" issues deterministically, so gating the merge on it would wedge a",
|
|
21996
|
+
" correct PR over a body typo. Record every `STRANDED #<n>` in the",
|
|
21997
|
+
" findings and in the sticky summary so the malformed body is visible",
|
|
21998
|
+
" rather than silent.",
|
|
21631
21999
|
"- **Test coverage** \u2014 new or changed behavior has tests",
|
|
21632
22000
|
"- **CI status** \u2014 resolve CI via the **CI Verification** section",
|
|
21633
22001
|
" (primary check-runs read, with the Actions-runs fallback on a",
|
|
@@ -21990,7 +22358,38 @@ var prReviewerSubAgent = {
|
|
|
21990
22358
|
"",
|
|
21991
22359
|
"### If all acceptance criteria are met and CI is green",
|
|
21992
22360
|
"",
|
|
21993
|
-
"
|
|
22361
|
+
"#### Pre-merge closing-keyword check",
|
|
22362
|
+
"",
|
|
22363
|
+
"Run this **before** branching on the review mode \u2014 it applies in both",
|
|
22364
|
+
"modes and is the last cheap moment to catch a body that would strand",
|
|
22365
|
+
"issues on merge:",
|
|
22366
|
+
"",
|
|
22367
|
+
"```bash",
|
|
22368
|
+
".claude/procedures/check-closing-keywords.sh <pr-number>",
|
|
22369
|
+
"```",
|
|
22370
|
+
"",
|
|
22371
|
+
"Re-run it here rather than reusing the Phase 1.5 result: the body may",
|
|
22372
|
+
"have been edited since, and the merge is about to make the answer",
|
|
22373
|
+
"permanent.",
|
|
22374
|
+
"",
|
|
22375
|
+
"- **Exit `0`** \u2014 nothing to do; every linked issue will auto-close.",
|
|
22376
|
+
"- **Exit `1`** \u2014 the body strands at least one issue. Do **not** block",
|
|
22377
|
+
" the merge; post one comment naming the stranded issues and the",
|
|
22378
|
+
" corrected block, then continue. Phase 5 closes the survivors",
|
|
22379
|
+
" explicitly, so the malformed body costs a comment, not a wedged PR.",
|
|
22380
|
+
"",
|
|
22381
|
+
"```bash",
|
|
22382
|
+
"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:",
|
|
22383
|
+
"",
|
|
22384
|
+
"Closes #<first>",
|
|
22385
|
+
"Closes #<stranded>",
|
|
22386
|
+
"",
|
|
22387
|
+
"Merging anyway \u2014 Phase 5 will close the stranded issues explicitly.'",
|
|
22388
|
+
"```",
|
|
22389
|
+
"",
|
|
22390
|
+
"Carry the full stranded set into Phase 5 verbatim.",
|
|
22391
|
+
"",
|
|
22392
|
+
"Then branch on the **review mode** decided in Phase 2.75:",
|
|
21994
22393
|
"",
|
|
21995
22394
|
"#### Mode `auto-merge`",
|
|
21996
22395
|
"",
|
|
@@ -22178,6 +22577,7 @@ var prReviewerSubAgent = {
|
|
|
22178
22577
|
"**Outstanding:** <list of comments still open with their classification and author>",
|
|
22179
22578
|
"**Pushbacks:** <list of disputes with reasons>",
|
|
22180
22579
|
"**Label reconciliation:** <consistent | corrections applied this pass>",
|
|
22580
|
+
"**Closing keywords:** ok | malformed \u2014 strands #<n>[, #<n>\u2026]",
|
|
22181
22581
|
"**Last pass:** <ISO timestamp>",
|
|
22182
22582
|
"```",
|
|
22183
22583
|
"",
|
|
@@ -22202,6 +22602,11 @@ var prReviewerSubAgent = {
|
|
|
22202
22602
|
" the corrections applied this pass (the detected violation combo,",
|
|
22203
22603
|
" the label(s) removed, and the resulting self-consistent set).",
|
|
22204
22604
|
" Every reconciliation correction is recorded here for auditability.",
|
|
22605
|
+
"- **Closing keywords** \u2014 the",
|
|
22606
|
+
" `.claude/procedures/check-closing-keywords.sh` verdict: `ok`, or",
|
|
22607
|
+
" `malformed` plus every `STRANDED #<n>` the body would leave open.",
|
|
22608
|
+
" Recorded on every pass so a body that strands issues is visible to a",
|
|
22609
|
+
" human before the merge, not only after Phase 5 repairs it.",
|
|
22205
22610
|
"- **Last pass** \u2014 the ISO 8601 timestamp of this run.",
|
|
22206
22611
|
"",
|
|
22207
22612
|
"### Step 3: Create or edit in place",
|
|
@@ -22586,42 +22991,64 @@ var prReviewerSubAgent = {
|
|
|
22586
22991
|
"gh pr view <pr-number> --json state --jq '.state'",
|
|
22587
22992
|
"```",
|
|
22588
22993
|
"",
|
|
22589
|
-
"- **`MERGED`** \u2014 clean up locally **and** verify
|
|
22994
|
+
"- **`MERGED`** \u2014 clean up locally **and** verify **every** linked issue",
|
|
22995
|
+
" closed:",
|
|
22590
22996
|
" ```bash",
|
|
22591
22997
|
" git checkout {{repository.defaultBranch}}",
|
|
22592
22998
|
" git pull origin {{repository.defaultBranch}}",
|
|
22593
22999
|
" git fetch --prune origin",
|
|
22594
23000
|
" git branch -d <branch-name> 2>/dev/null || git branch -D <branch-name> 2>/dev/null || true",
|
|
22595
23001
|
" ```",
|
|
22596
|
-
"
|
|
22597
|
-
"
|
|
23002
|
+
"",
|
|
23003
|
+
" **Post-merge closure verification.** Iterate the **full linked-issue",
|
|
23004
|
+
" set** from Phase 1.5 \u2014 the union of the `CLOSES` and `STRANDED`",
|
|
23005
|
+
" numbers `check-closing-keywords.sh` reported \u2014 not just the primary",
|
|
23006
|
+
" issue. This loop is the structural guard against the stranded-issue",
|
|
23007
|
+
" defect class: GitHub honours only one issue reference per closing",
|
|
23008
|
+
" keyword, so a body written as `Closes #A, #B` closes `#A` and leaves",
|
|
23009
|
+
" `#B` open forever with no signal anywhere. Because the reviewer",
|
|
23010
|
+
" already knows the full set, every survivor is closed here",
|
|
23011
|
+
" deterministically instead of being discovered months later by an",
|
|
23012
|
+
" audit.",
|
|
23013
|
+
"",
|
|
23014
|
+
" For **each** issue in the set, in order:",
|
|
23015
|
+
"",
|
|
23016
|
+
" 1. Transition it to `status:done` (replaces whichever of",
|
|
23017
|
+
" `status:ready-for-review` or `status:in-progress` it was carrying):",
|
|
22598
23018
|
" ```bash",
|
|
22599
23019
|
" gh issue edit <issue-number> \\",
|
|
22600
23020
|
' --remove-label "status:ready-for-review" \\',
|
|
22601
23021
|
' --remove-label "status:in-progress" \\',
|
|
22602
23022
|
' --add-label "status:done"',
|
|
22603
23023
|
" ```",
|
|
22604
|
-
"
|
|
23024
|
+
" 2. Check its state:",
|
|
22605
23025
|
" ```bash",
|
|
22606
23026
|
" gh issue view <issue-number> --json state --jq '.state'",
|
|
22607
23027
|
" ```",
|
|
22608
|
-
" If the issue is **not** `CLOSED`, close it explicitly (this covers",
|
|
22609
|
-
"
|
|
23028
|
+
" 3. If the issue is **not** `CLOSED`, close it explicitly (this covers",
|
|
23029
|
+
" both a missing closing keyword and an issue stranded by a",
|
|
23030
|
+
" multi-issue keyword):",
|
|
22610
23031
|
" ```bash",
|
|
22611
23032
|
" gh issue close <issue-number> --reason completed",
|
|
22612
23033
|
" gh issue comment <issue-number> --body 'PR #<pr-number> merged. Closing issue.'",
|
|
22613
23034
|
" ```",
|
|
22614
|
-
" Once `status:done` is applied (either via the edit above or the",
|
|
22615
|
-
"
|
|
22616
|
-
"
|
|
22617
|
-
"
|
|
23035
|
+
" 4. Once `status:done` is applied (either via the edit above or the",
|
|
23036
|
+
" explicit close path), run the targeted unblock sweep so any",
|
|
23037
|
+
" dependents waiting on this issue flip from `status:blocked` to",
|
|
23038
|
+
" `status:ready` immediately:",
|
|
22618
23039
|
" ```bash",
|
|
22619
23040
|
" .claude/procedures/unblock-dependents.sh <issue-number>",
|
|
22620
23041
|
" ```",
|
|
23042
|
+
"",
|
|
22621
23043
|
" Log the sweep's output (one line per processed dependent:",
|
|
22622
23044
|
" `UNBLOCKED #<n>` / `STILL_BLOCKED #<n>` / `NO_DEPENDENTS #<m>`)",
|
|
22623
23045
|
" in the PR review summary. See the **Agent-driven unblocking**",
|
|
22624
23046
|
" section in `CLAUDE.md` for the full contract.",
|
|
23047
|
+
"",
|
|
23048
|
+
" Report one `Issue #<n>: <closed-by-github | closed-explicitly>` line",
|
|
23049
|
+
" per member of the set. Any issue that needed the explicit close was",
|
|
23050
|
+
" stranded by the PR body \u2014 say so in the summary so the authoring",
|
|
23051
|
+
" defect is visible, not just repaired.",
|
|
22625
23052
|
"- **`CLOSED` (not merged)** \u2014 leave the branch in place; report the closure.",
|
|
22626
23053
|
"- **Still `OPEN`** \u2014 auto-merge is pending; report and stop without deleting.",
|
|
22627
23054
|
"",
|
|
@@ -22634,6 +23061,8 @@ var prReviewerSubAgent = {
|
|
|
22634
23061
|
"```",
|
|
22635
23062
|
"PR #<number> \u2014 <title>",
|
|
22636
23063
|
"Linked issue: #<issue-number>",
|
|
23064
|
+
"Linked issue set: #<n> [, #<n>\u2026] (from check-closing-keywords.sh)",
|
|
23065
|
+
"Closing keywords: <ok | malformed: strands #<n>[, #<n>\u2026]>",
|
|
22637
23066
|
"Review mode: <auto-merge | human-required>",
|
|
22638
23067
|
"Reason: <short explanation from Phase 2.75>",
|
|
22639
23068
|
"Verdict: AUTO_MERGE_ENABLED | AWAITING_HUMAN | DELEGATED_TO_WORKER | NEEDS_CHANGES | INELIGIBLE | BLOCKED",
|
|
@@ -22674,6 +23103,14 @@ var prReviewerSubAgent = {
|
|
|
22674
23103
|
" PR in turn.",
|
|
22675
23104
|
"2. **Never merge without a linked issue.** If the PR body has no",
|
|
22676
23105
|
" `Closes #N` / `Fixes #N` / `Resolves #N`, comment and stop.",
|
|
23106
|
+
"2a. **Never merge without verifying every linked issue closes.** Resolve",
|
|
23107
|
+
" the linked-issue set with",
|
|
23108
|
+
" `.claude/procedures/check-closing-keywords.sh <pr-number>`, never by",
|
|
23109
|
+
" eyeballing the body. GitHub honours only one issue reference per",
|
|
23110
|
+
" closing keyword, so a `STRANDED` line means that issue will stay",
|
|
23111
|
+
" open after the merge unless Phase 5 closes it. Phase 5 must iterate",
|
|
23112
|
+
" the **whole** set \u2014 `CLOSES` plus `STRANDED` \u2014 and explicitly close",
|
|
23113
|
+
" every issue GitHub left open.",
|
|
22677
23114
|
"3. **Never merge with failing CI.** Even if every criterion is met,",
|
|
22678
23115
|
" block on red checks. Resolve CI status via the **CI",
|
|
22679
23116
|
" Verification** section: read check-runs first, and only on a",
|
|
@@ -42332,6 +42769,7 @@ export const collections = {
|
|
|
42332
42769
|
buildUnblockDependentsProcedure,
|
|
42333
42770
|
bundleNameForWorkflowRule,
|
|
42334
42771
|
businessModelsBundle,
|
|
42772
|
+
checkClosingKeywordsProcedure,
|
|
42335
42773
|
checkDocSamplesProcedure,
|
|
42336
42774
|
checkLinksProcedure,
|
|
42337
42775
|
classifyIssueScope,
|
|
@@ -42406,6 +42844,7 @@ export const collections = {
|
|
|
42406
42844
|
renderCdkRollback,
|
|
42407
42845
|
renderCdkSynth,
|
|
42408
42846
|
renderCdkWatch,
|
|
42847
|
+
renderCheckClosingKeywordsScript,
|
|
42409
42848
|
renderCheckDocSamplesProcedure,
|
|
42410
42849
|
renderCheckLinksProcedure,
|
|
42411
42850
|
renderCustomDocSectionBlock,
|