@codedrifters/configulator 0.0.327 → 0.0.329
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 +208 -6
- package/lib/index.d.ts +209 -7
- package/lib/index.js +1525 -640
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +1521 -640
- package/lib/index.mjs.map +1 -1
- package/package.json +5 -5
package/lib/index.js
CHANGED
|
@@ -237,6 +237,7 @@ __export(index_exports, {
|
|
|
237
237
|
DEFAULT_ISSUE_TEMPLATES_REQUIRE_REFERENCE: () => DEFAULT_ISSUE_TEMPLATES_REQUIRE_REFERENCE,
|
|
238
238
|
DEFAULT_OFF_PEAK_CRON_EXAMPLE: () => DEFAULT_OFF_PEAK_CRON_EXAMPLE,
|
|
239
239
|
DEFAULT_PARTIAL_UNBLOCK_COMMENT_TEMPLATE: () => DEFAULT_PARTIAL_UNBLOCK_COMMENT_TEMPLATE,
|
|
240
|
+
DEFAULT_PATHS_EXEMPT_FROM_SIZE: () => DEFAULT_PATHS_EXEMPT_FROM_SIZE,
|
|
240
241
|
DEFAULT_PRIORITY_LABELS: () => DEFAULT_PRIORITY_LABELS,
|
|
241
242
|
DEFAULT_PRODUCT_CONTEXT_PATH: () => DEFAULT_PRODUCT_CONTEXT_PATH,
|
|
242
243
|
DEFAULT_PROGRESS_FILES_ENABLED: () => DEFAULT_PROGRESS_FILES_ENABLED,
|
|
@@ -339,6 +340,7 @@ __export(index_exports, {
|
|
|
339
340
|
buildMeetingAnalysisBundle: () => buildMeetingAnalysisBundle,
|
|
340
341
|
buildOrchestratorConventionsContent: () => buildOrchestratorConventionsContent,
|
|
341
342
|
buildPeopleProfileBundle: () => buildPeopleProfileBundle,
|
|
343
|
+
buildPrReviewBundle: () => buildPrReviewBundle,
|
|
342
344
|
buildRegulatoryResearchBundle: () => buildRegulatoryResearchBundle,
|
|
343
345
|
buildReport: () => buildReport,
|
|
344
346
|
buildRequirementsAnalystBundle: () => buildRequirementsAnalystBundle,
|
|
@@ -469,6 +471,7 @@ __export(index_exports, {
|
|
|
469
471
|
resolveOrchestratorAssets: () => resolveOrchestratorAssets,
|
|
470
472
|
resolveOutdirFromPackageName: () => resolveOutdirFromPackageName,
|
|
471
473
|
resolveOverrideForLabels: () => resolveOverrideForLabels,
|
|
474
|
+
resolvePrReviewPolicy: () => resolvePrReviewPolicy,
|
|
472
475
|
resolveProgressFiles: () => resolveProgressFiles,
|
|
473
476
|
resolveReactViteSiteProjectOutdir: () => resolveReactViteSiteProjectOutdir,
|
|
474
477
|
resolveRunRatio: () => resolveRunRatio,
|
|
@@ -492,6 +495,7 @@ __export(index_exports, {
|
|
|
492
495
|
validateIssueDefaultsConfig: () => validateIssueDefaultsConfig,
|
|
493
496
|
validateIssueTemplatesConfig: () => validateIssueTemplatesConfig,
|
|
494
497
|
validateMonorepoLayout: () => validateMonorepoLayout,
|
|
498
|
+
validatePrReviewPolicyConfig: () => validatePrReviewPolicyConfig,
|
|
495
499
|
validateProgressFilesConfig: () => validateProgressFilesConfig,
|
|
496
500
|
validateRunRatioConfig: () => validateRunRatioConfig,
|
|
497
501
|
validateScheduledTasksConfig: () => validateScheduledTasksConfig,
|
|
@@ -2585,6 +2589,55 @@ function renderSharedEditingRuleContent(se) {
|
|
|
2585
2589
|
}
|
|
2586
2590
|
}
|
|
2587
2591
|
lines.push(
|
|
2592
|
+
"## Defer Shared-Index Commit to Final Pre-Push Step",
|
|
2593
|
+
"",
|
|
2594
|
+
"Even with the single-entry, deterministic-sort discipline above,",
|
|
2595
|
+
"two sessions that **prepare** their row inserts at the same time",
|
|
2596
|
+
"still race on push: whichever session pushes second sees the",
|
|
2597
|
+
"first session's commit on the remote and has to rebase its own",
|
|
2598
|
+
"commit on top, regenerating the same insert position calculation",
|
|
2599
|
+
"against a now-changed file. Repeated rebases multiply the chance",
|
|
2600
|
+
"of a mis-merge that silently drops a row.",
|
|
2601
|
+
"",
|
|
2602
|
+
"Sessions that produce both content and a shared-index row insert",
|
|
2603
|
+
"shrink this race window by deferring the index commit to the",
|
|
2604
|
+
"**final pre-push step** \u2014 after every content commit has landed",
|
|
2605
|
+
"locally, and immediately before `git push`:",
|
|
2606
|
+
"",
|
|
2607
|
+
"1. **Commit content first.** Write and commit every non-index",
|
|
2608
|
+
" change that the session produces (profile body, transcript",
|
|
2609
|
+
" extraction, requirement document, etc.) in its own focused",
|
|
2610
|
+
" commit or commits.",
|
|
2611
|
+
"2. **Rebase against the remote default branch** before touching",
|
|
2612
|
+
" the shared index:",
|
|
2613
|
+
"",
|
|
2614
|
+
" ```bash",
|
|
2615
|
+
" git fetch origin",
|
|
2616
|
+
` git pull --${se.conflictStrategy} origin <default-branch>`,
|
|
2617
|
+
" ```",
|
|
2618
|
+
"",
|
|
2619
|
+
"3. **Re-read the shared index** from the now-up-to-date working",
|
|
2620
|
+
" tree. Another session may have appended a row while this",
|
|
2621
|
+
" session's content commits were in flight.",
|
|
2622
|
+
"4. **Re-compute the insert position** in declared sort order",
|
|
2623
|
+
" against the freshly-read rows. Do not assume the position",
|
|
2624
|
+
" computed earlier in the session is still correct.",
|
|
2625
|
+
"5. **Insert the row and commit the index edit on its own** \u2014",
|
|
2626
|
+
" one focused commit whose only file is the shared index. Run",
|
|
2627
|
+
" the commit-path verification (above) against that commit",
|
|
2628
|
+
" before continuing.",
|
|
2629
|
+
"6. **Push immediately.** The shorter the wall-clock gap between",
|
|
2630
|
+
" the rebase / re-read in step 2 and the push, the smaller the",
|
|
2631
|
+
" window in which another session can land a competing row.",
|
|
2632
|
+
"",
|
|
2633
|
+
"Per-agent workflows that touch a shared index file should call",
|
|
2634
|
+
"out this defer-to-final-commit sequence explicitly \u2014 see the",
|
|
2635
|
+
"`meeting-analyst` and `software-profile-analyst` sub-agent",
|
|
2636
|
+
"prompts for the canonical wording. The pattern is mechanical",
|
|
2637
|
+
"(rebase, re-read, re-compute, focused commit, push) rather than",
|
|
2638
|
+
"editorial, so the same recipe applies to every shared-index",
|
|
2639
|
+
"row-producing agent regardless of what content surrounds it.",
|
|
2640
|
+
"",
|
|
2588
2641
|
"## Merge-Conflict Resolution",
|
|
2589
2642
|
"",
|
|
2590
2643
|
"When `git push` reports a conflict on a shared index file (two",
|
|
@@ -2632,9 +2685,14 @@ function renderSharedEditingBundleHook(se, bundleLabel) {
|
|
|
2632
2685
|
"the latest default branch before editing, insert exactly one row",
|
|
2633
2686
|
"in deterministic sort position, commit the index edit in its own",
|
|
2634
2687
|
"focused commit, and verify the row is present in the commit",
|
|
2635
|
-
"before pushing.
|
|
2636
|
-
"
|
|
2637
|
-
"
|
|
2688
|
+
"before pushing. **Defer the index commit to the final pre-push",
|
|
2689
|
+
"step** \u2014 land every content commit first, then rebase against",
|
|
2690
|
+
"the remote default branch, re-read the index, re-compute the",
|
|
2691
|
+
"insert position, write the row, commit the index on its own,",
|
|
2692
|
+
"and push immediately. See the `shared-editing-safety` rule for",
|
|
2693
|
+
"the full protocol, the list of files covered, the",
|
|
2694
|
+
"defer-to-final-commit sequence, and the merge-conflict",
|
|
2695
|
+
"resolution recipe."
|
|
2638
2696
|
].join("\n");
|
|
2639
2697
|
}
|
|
2640
2698
|
function renderSharedEditingHelperScript(_se) {
|
|
@@ -10095,6 +10153,395 @@ var setIssueTypeProcedure = {
|
|
|
10095
10153
|
`echo "$result" | jq -c '.data.updateIssueIssueType.issue'`
|
|
10096
10154
|
].join("\n")
|
|
10097
10155
|
};
|
|
10156
|
+
var cleanMergedBranchesProcedure = {
|
|
10157
|
+
name: "clean-merged-branches.sh",
|
|
10158
|
+
description: "Analyse local branches and classify each as MERGED, UNMERGED, EMPTY, or SKIP_WORKTREE against a base branch (default: main). Analysis-only \u2014 never deletes branches. Handles squash merges via content equality.",
|
|
10159
|
+
content: [
|
|
10160
|
+
"#!/usr/bin/env bash",
|
|
10161
|
+
"# clean-merged-branches.sh \u2014 Analyse local branches against a base.",
|
|
10162
|
+
"#",
|
|
10163
|
+
"# Reports each local branch (other than HEAD and the base branch) as",
|
|
10164
|
+
"# MERGED, UNMERGED, EMPTY, or SKIP_WORKTREE. MERGED means every file the",
|
|
10165
|
+
"# branch added or modified now matches the base \u2014 so the branch content",
|
|
10166
|
+
"# is fully on the base even when the merge was a squash.",
|
|
10167
|
+
"#",
|
|
10168
|
+
"# This procedure is analysis-only. It never runs `git branch -D`. The",
|
|
10169
|
+
"# /clean-merged-branches slash-command skill wraps it with the deletion",
|
|
10170
|
+
"# prompt; the procedure itself is safe to invoke non-interactively from",
|
|
10171
|
+
"# any agent.",
|
|
10172
|
+
"#",
|
|
10173
|
+
"# Usage:",
|
|
10174
|
+
"# .claude/procedures/clean-merged-branches.sh # default base: main",
|
|
10175
|
+
"# .claude/procedures/clean-merged-branches.sh --base develop",
|
|
10176
|
+
"#",
|
|
10177
|
+
"# Output (one line per branch, stable format for grepping):",
|
|
10178
|
+
"# MERGED <branch> files=<n>",
|
|
10179
|
+
"# UNMERGED <branch> files=<n> differs=<n>",
|
|
10180
|
+
"# EMPTY <branch>",
|
|
10181
|
+
"# SKIP_WORKTREE <branch> worktree=<path>",
|
|
10182
|
+
"#",
|
|
10183
|
+
"# Exit codes:",
|
|
10184
|
+
"# 0 \u2014 analysis completed (whether or not any MERGED branches were found)",
|
|
10185
|
+
"# 2 \u2014 argument error",
|
|
10186
|
+
"# 3 \u2014 required command not on PATH",
|
|
10187
|
+
"# 4 \u2014 not a git repository",
|
|
10188
|
+
"",
|
|
10189
|
+
"set -uo pipefail",
|
|
10190
|
+
"",
|
|
10191
|
+
"# \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",
|
|
10192
|
+
"",
|
|
10193
|
+
"err() {",
|
|
10194
|
+
' printf "clean-merged-branches.sh: %s\\n" "$*" >&2',
|
|
10195
|
+
"}",
|
|
10196
|
+
"",
|
|
10197
|
+
"usage() {",
|
|
10198
|
+
" cat >&2 <<'USAGE'",
|
|
10199
|
+
"Usage: clean-merged-branches.sh [--base <name>]",
|
|
10200
|
+
"",
|
|
10201
|
+
" --base <name> Base branch to compare against (default: main; falls",
|
|
10202
|
+
" back to whatever origin/HEAD points at if main is",
|
|
10203
|
+
" not present locally).",
|
|
10204
|
+
"",
|
|
10205
|
+
"Reports MERGED / UNMERGED / EMPTY / SKIP_WORKTREE for every local",
|
|
10206
|
+
"branch other than HEAD and the base branch. Does not delete anything.",
|
|
10207
|
+
"USAGE",
|
|
10208
|
+
"}",
|
|
10209
|
+
"",
|
|
10210
|
+
"# \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",
|
|
10211
|
+
"",
|
|
10212
|
+
'base=""',
|
|
10213
|
+
"while [[ $# -gt 0 ]]; do",
|
|
10214
|
+
' case "$1" in',
|
|
10215
|
+
" --base)",
|
|
10216
|
+
' if [[ $# -lt 2 || -z "${2:-}" ]]; then',
|
|
10217
|
+
' err "--base requires a value"',
|
|
10218
|
+
" usage",
|
|
10219
|
+
" exit 2",
|
|
10220
|
+
" fi",
|
|
10221
|
+
' base="$2"',
|
|
10222
|
+
" shift 2",
|
|
10223
|
+
" ;;",
|
|
10224
|
+
" -h|--help)",
|
|
10225
|
+
" usage",
|
|
10226
|
+
" exit 0",
|
|
10227
|
+
" ;;",
|
|
10228
|
+
" *)",
|
|
10229
|
+
' err "unknown argument: $1"',
|
|
10230
|
+
" usage",
|
|
10231
|
+
" exit 2",
|
|
10232
|
+
" ;;",
|
|
10233
|
+
" esac",
|
|
10234
|
+
"done",
|
|
10235
|
+
"",
|
|
10236
|
+
"# \u2500\u2500 dependency checks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
10237
|
+
"",
|
|
10238
|
+
"for cmd in git; do",
|
|
10239
|
+
' if ! command -v "$cmd" >/dev/null 2>&1; then',
|
|
10240
|
+
' err "required command not found on PATH: $cmd"',
|
|
10241
|
+
" exit 3",
|
|
10242
|
+
" fi",
|
|
10243
|
+
"done",
|
|
10244
|
+
"",
|
|
10245
|
+
"# \u2500\u2500 repo + base resolution \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
10246
|
+
"",
|
|
10247
|
+
"if ! git rev-parse --git-dir >/dev/null 2>&1; then",
|
|
10248
|
+
' err "not inside a git repository"',
|
|
10249
|
+
" exit 4",
|
|
10250
|
+
"fi",
|
|
10251
|
+
"",
|
|
10252
|
+
"# Default base = main if it exists locally, otherwise whatever",
|
|
10253
|
+
"# origin/HEAD points at (e.g. master, trunk, develop).",
|
|
10254
|
+
'if [[ -z "$base" ]]; then',
|
|
10255
|
+
' if git show-ref --verify --quiet "refs/heads/main"; then',
|
|
10256
|
+
' base="main"',
|
|
10257
|
+
" else",
|
|
10258
|
+
" origin_head=$(git symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null || true)",
|
|
10259
|
+
' if [[ -n "$origin_head" ]]; then',
|
|
10260
|
+
' base="${origin_head#origin/}"',
|
|
10261
|
+
" else",
|
|
10262
|
+
' base="main"',
|
|
10263
|
+
" fi",
|
|
10264
|
+
" fi",
|
|
10265
|
+
"fi",
|
|
10266
|
+
"",
|
|
10267
|
+
"# Verify the chosen base actually exists as a local branch.",
|
|
10268
|
+
'if ! git show-ref --verify --quiet "refs/heads/$base"; then',
|
|
10269
|
+
` err "base branch '$base' does not exist locally"`,
|
|
10270
|
+
" exit 4",
|
|
10271
|
+
"fi",
|
|
10272
|
+
"",
|
|
10273
|
+
"# Refresh remote-tracking refs and prune deleted upstream branches",
|
|
10274
|
+
"# so the gone-upstream signal is fresh (best effort \u2014 never fatal).",
|
|
10275
|
+
"git fetch --prune origin >/dev/null 2>&1 || true",
|
|
10276
|
+
"",
|
|
10277
|
+
'current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")',
|
|
10278
|
+
"",
|
|
10279
|
+
"# \u2500\u2500 worktree map (branch -> worktree path, excluding the current) \u2500\u2500\u2500",
|
|
10280
|
+
"",
|
|
10281
|
+
"# Use git worktree list --porcelain to detect any branch checked out in",
|
|
10282
|
+
"# a separate worktree. We must skip those because `git branch -D` would",
|
|
10283
|
+
"# refuse to delete a branch that is checked out elsewhere.",
|
|
10284
|
+
"#",
|
|
10285
|
+
"# We use parallel arrays (not an associative array) to stay compatible",
|
|
10286
|
+
"# with bash 3 \u2014 macOS still ships bash 3.2 as /bin/bash, and the script",
|
|
10287
|
+
"# is invoked via #!/usr/bin/env bash which picks whichever bash is",
|
|
10288
|
+
"# first on PATH. Lookups are O(n) but n is tiny (worktree count).",
|
|
10289
|
+
"worktree_branches=()",
|
|
10290
|
+
"worktree_paths=()",
|
|
10291
|
+
'cwd_top=$(git rev-parse --show-toplevel 2>/dev/null || echo "")',
|
|
10292
|
+
'wt_path=""',
|
|
10293
|
+
"while IFS= read -r line; do",
|
|
10294
|
+
' case "$line" in',
|
|
10295
|
+
" worktree\\ *)",
|
|
10296
|
+
' wt_path="${line#worktree }"',
|
|
10297
|
+
" ;;",
|
|
10298
|
+
" branch\\ *)",
|
|
10299
|
+
' wt_branch="${line#branch refs/heads/}"',
|
|
10300
|
+
" # Skip the worktree that matches the current top-level \u2014 that one",
|
|
10301
|
+
" # is the *current* checkout; skipping HEAD is handled separately.",
|
|
10302
|
+
' if [[ -n "$wt_path" && "$wt_path" != "$cwd_top" ]]; then',
|
|
10303
|
+
' worktree_branches+=("$wt_branch")',
|
|
10304
|
+
' worktree_paths+=("$wt_path")',
|
|
10305
|
+
" fi",
|
|
10306
|
+
' wt_path=""',
|
|
10307
|
+
" ;;",
|
|
10308
|
+
' "")',
|
|
10309
|
+
' wt_path=""',
|
|
10310
|
+
" ;;",
|
|
10311
|
+
" esac",
|
|
10312
|
+
"done < <(git worktree list --porcelain 2>/dev/null)",
|
|
10313
|
+
"",
|
|
10314
|
+
"# Echo the worktree path for the given branch, or empty string if the",
|
|
10315
|
+
"# branch is not checked out in another worktree.",
|
|
10316
|
+
"lookup_worktree() {",
|
|
10317
|
+
' local needle="$1"',
|
|
10318
|
+
" local i",
|
|
10319
|
+
' for i in "${!worktree_branches[@]}"; do',
|
|
10320
|
+
' if [[ "${worktree_branches[$i]}" == "$needle" ]]; then',
|
|
10321
|
+
` printf '%s\\n' "\${worktree_paths[$i]}"`,
|
|
10322
|
+
" return 0",
|
|
10323
|
+
" fi",
|
|
10324
|
+
" done",
|
|
10325
|
+
" return 0",
|
|
10326
|
+
"}",
|
|
10327
|
+
"",
|
|
10328
|
+
"# \u2500\u2500 walk local branches \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
10329
|
+
"",
|
|
10330
|
+
'echo "Analysing local branches against ${base}\u2026"',
|
|
10331
|
+
"echo",
|
|
10332
|
+
"",
|
|
10333
|
+
"while IFS= read -r branch; do",
|
|
10334
|
+
' [[ -z "$branch" ]] && continue',
|
|
10335
|
+
' [[ "$branch" == "$base" ]] && continue',
|
|
10336
|
+
' [[ "$branch" == "$current" ]] && continue',
|
|
10337
|
+
"",
|
|
10338
|
+
' wt=$(lookup_worktree "$branch")',
|
|
10339
|
+
' if [[ -n "$wt" ]]; then',
|
|
10340
|
+
` printf 'SKIP_WORKTREE %s worktree=%s\\n' "$branch" "$wt"`,
|
|
10341
|
+
" continue",
|
|
10342
|
+
" fi",
|
|
10343
|
+
"",
|
|
10344
|
+
' mb=$(git merge-base "$base" "$branch" 2>/dev/null || true)',
|
|
10345
|
+
' if [[ -z "$mb" ]]; then',
|
|
10346
|
+
" # No common ancestor with base \u2014 treat as UNMERGED (don't risk it).",
|
|
10347
|
+
` printf 'UNMERGED %s files=? differs=?\\n' "$branch"`,
|
|
10348
|
+
" continue",
|
|
10349
|
+
" fi",
|
|
10350
|
+
"",
|
|
10351
|
+
' files=$(git diff --name-only "$mb" "$branch" 2>/dev/null || true)',
|
|
10352
|
+
' if [[ -z "$files" ]]; then',
|
|
10353
|
+
` printf 'EMPTY %s\\n' "$branch"`,
|
|
10354
|
+
" continue",
|
|
10355
|
+
" fi",
|
|
10356
|
+
"",
|
|
10357
|
+
" # Count how many of those files still differ from the base today.",
|
|
10358
|
+
" # We use NUL-delimited git output to survive filenames with spaces.",
|
|
10359
|
+
" diff_count=0",
|
|
10360
|
+
" while IFS= read -r -d '' f; do",
|
|
10361
|
+
" diff_count=$((diff_count + 1))",
|
|
10362
|
+
' done < <(git diff -z --name-only "$branch" "$base" -- $files 2>/dev/null)',
|
|
10363
|
+
"",
|
|
10364
|
+
` total=$(echo "$files" | wc -l | tr -d ' ')`,
|
|
10365
|
+
"",
|
|
10366
|
+
' if [[ "$diff_count" -eq 0 ]]; then',
|
|
10367
|
+
` printf 'MERGED %s files=%s\\n' "$branch" "$total"`,
|
|
10368
|
+
" else",
|
|
10369
|
+
` printf 'UNMERGED %s files=%s differs=%s\\n' "$branch" "$total" "$diff_count"`,
|
|
10370
|
+
" fi",
|
|
10371
|
+
"done < <(git branch --format='%(refname:short)')",
|
|
10372
|
+
"",
|
|
10373
|
+
"exit 0"
|
|
10374
|
+
].join("\n")
|
|
10375
|
+
};
|
|
10376
|
+
var cleanMergedBranchesSkill = {
|
|
10377
|
+
name: "clean-merged-branches",
|
|
10378
|
+
description: "Report every local branch as MERGED or UNMERGED against the base (handles squash merges via content equality), then prompt the user to force-delete the MERGED list. Defaults base to main; --base overrides. Skips current branch, base branch, and any branch checked out in another worktree.",
|
|
10379
|
+
disableModelInvocation: true,
|
|
10380
|
+
userInvocable: true,
|
|
10381
|
+
platforms: { cursor: { exclude: true } },
|
|
10382
|
+
instructions: [
|
|
10383
|
+
"# Clean Merged Branches",
|
|
10384
|
+
"",
|
|
10385
|
+
"Identify local branches whose content is fully on the base branch \u2014",
|
|
10386
|
+
"including branches that were squash-merged (where `git branch -d`",
|
|
10387
|
+
"refuses because the branch commits are not ancestors of the base) \u2014",
|
|
10388
|
+
"report them to the user, and force-delete them after explicit",
|
|
10389
|
+
"confirmation.",
|
|
10390
|
+
"",
|
|
10391
|
+
"## Usage",
|
|
10392
|
+
"",
|
|
10393
|
+
"```",
|
|
10394
|
+
"/clean-merged-branches # default base: main",
|
|
10395
|
+
"/clean-merged-branches --base develop # custom base branch",
|
|
10396
|
+
"```",
|
|
10397
|
+
"",
|
|
10398
|
+
"### Flags",
|
|
10399
|
+
"",
|
|
10400
|
+
"- **`--base <name>`** \u2014 base branch to compare against. Defaults to",
|
|
10401
|
+
" `main`; falls back to whatever `origin/HEAD` points at if `main`",
|
|
10402
|
+
" does not exist locally.",
|
|
10403
|
+
"",
|
|
10404
|
+
"## What This Skill Does",
|
|
10405
|
+
"",
|
|
10406
|
+
"1. **Analyse.** Runs `.claude/procedures/clean-merged-branches.sh`",
|
|
10407
|
+
" (passing through any `--base` flag). The procedure walks every",
|
|
10408
|
+
" local branch and prints one stable log line per branch:",
|
|
10409
|
+
" - `MERGED <branch> files=<n>` \u2014 every file the branch added",
|
|
10410
|
+
" or modified now matches the base. Safe to delete.",
|
|
10411
|
+
" - `UNMERGED <branch> files=<n> differs=<n>` \u2014 branch content",
|
|
10412
|
+
" still differs from the base. Do NOT delete.",
|
|
10413
|
+
" - `EMPTY <branch>` \u2014 branch has no file changes vs. its",
|
|
10414
|
+
" merge-base. Safe to delete.",
|
|
10415
|
+
" - `SKIP_WORKTREE <branch> worktree=<path>` \u2014 branch is checked",
|
|
10416
|
+
" out in another worktree. Skipped (cannot be deleted while it's",
|
|
10417
|
+
" in use).",
|
|
10418
|
+
"",
|
|
10419
|
+
"2. **Confirm.** If the report turns up at least one `MERGED` (or",
|
|
10420
|
+
" `EMPTY`) branch, print the exact deletion list and a single",
|
|
10421
|
+
" `[y/N]` prompt. Empty input defaults to **no** and aborts.",
|
|
10422
|
+
"",
|
|
10423
|
+
"3. **Delete.** On explicit `y` / `Y`, run `git branch -D <branch>`",
|
|
10424
|
+
" for each confirmed branch and emit one `DELETED <branch>` line",
|
|
10425
|
+
" per success. On any other answer (including empty input),",
|
|
10426
|
+
" abort with `Aborted. No branches deleted.` and exit cleanly.",
|
|
10427
|
+
"",
|
|
10428
|
+
"If the report finds zero deletable branches, exit cleanly without",
|
|
10429
|
+
"prompting \u2014 there is nothing to confirm.",
|
|
10430
|
+
"",
|
|
10431
|
+
"## Behaviour Guarantees",
|
|
10432
|
+
"",
|
|
10433
|
+
"- **Always skip the current branch and the base branch.** The",
|
|
10434
|
+
" procedure enforces this; the skill never needs to filter again.",
|
|
10435
|
+
"- **Never delete without an explicit `y`.** The default on empty",
|
|
10436
|
+
" input is no. The skill never runs `git branch -D` until after",
|
|
10437
|
+
" the prompt returns `y` or `Y`.",
|
|
10438
|
+
"- **Content equality is the proof.** A branch classifies as",
|
|
10439
|
+
" `MERGED` when every file it added or modified matches the base",
|
|
10440
|
+
" today \u2014 handles squash merges, file deletions, and renames.",
|
|
10441
|
+
" The `[origin/<branch>: gone]` indicator is a useful secondary",
|
|
10442
|
+
" signal but is NOT the deletion gate.",
|
|
10443
|
+
"- **Safe across worktrees.** Branches checked out in another",
|
|
10444
|
+
" worktree are logged as `SKIP_WORKTREE` and excluded from the",
|
|
10445
|
+
" deletion list.",
|
|
10446
|
+
"",
|
|
10447
|
+
"## Output",
|
|
10448
|
+
"",
|
|
10449
|
+
"The procedure's per-branch log lines first, then (if any deletable",
|
|
10450
|
+
"branches exist) the confirmation prompt, then one `DELETED <branch>`",
|
|
10451
|
+
"line per successful deletion or `Aborted. No branches deleted.`",
|
|
10452
|
+
"on abort.",
|
|
10453
|
+
"",
|
|
10454
|
+
"## Implementation Recipe",
|
|
10455
|
+
"",
|
|
10456
|
+
"```bash",
|
|
10457
|
+
"# 1. Run the analysis-only procedure and capture its output.",
|
|
10458
|
+
'report=$(.claude/procedures/clean-merged-branches.sh "$@")',
|
|
10459
|
+
'printf "%s\\n" "$report"',
|
|
10460
|
+
"",
|
|
10461
|
+
"# 2. Extract the MERGED + EMPTY branch names from the report.",
|
|
10462
|
+
"mergeable=()",
|
|
10463
|
+
"while IFS= read -r line; do",
|
|
10464
|
+
' case "$line" in',
|
|
10465
|
+
' "MERGED "*|"EMPTY "*)',
|
|
10466
|
+
" # The branch name is the second whitespace-delimited token.",
|
|
10467
|
+
` branch=$(echo "$line" | awk '{print $2}')`,
|
|
10468
|
+
' [[ -n "$branch" ]] && mergeable+=("$branch")',
|
|
10469
|
+
" ;;",
|
|
10470
|
+
" esac",
|
|
10471
|
+
'done <<< "$report"',
|
|
10472
|
+
"",
|
|
10473
|
+
"# 3. If nothing to delete, exit cleanly without prompting.",
|
|
10474
|
+
"if [[ ${#mergeable[@]} -eq 0 ]]; then",
|
|
10475
|
+
" echo",
|
|
10476
|
+
' echo "No merged branches to delete."',
|
|
10477
|
+
" exit 0",
|
|
10478
|
+
"fi",
|
|
10479
|
+
"",
|
|
10480
|
+
"# 4. Show the list and prompt the user once.",
|
|
10481
|
+
"echo",
|
|
10482
|
+
'echo "The following ${#mergeable[@]} branches are safe to delete:"',
|
|
10483
|
+
`printf ' %s\\n' "\${mergeable[@]}"`,
|
|
10484
|
+
"echo",
|
|
10485
|
+
'read -r -p "Force-delete all ${#mergeable[@]} with git branch -D? [y/N] " answer',
|
|
10486
|
+
"",
|
|
10487
|
+
"# 5. On explicit y/Y only, delete each branch.",
|
|
10488
|
+
'if [[ "$answer" == "y" || "$answer" == "Y" ]]; then',
|
|
10489
|
+
' for b in "${mergeable[@]}"; do',
|
|
10490
|
+
' if git branch -D "$b" >/dev/null 2>&1; then',
|
|
10491
|
+
' echo "DELETED $b"',
|
|
10492
|
+
" else",
|
|
10493
|
+
' echo "DELETE_FAILED $b" >&2',
|
|
10494
|
+
" fi",
|
|
10495
|
+
" done",
|
|
10496
|
+
"else",
|
|
10497
|
+
' echo "Aborted. No branches deleted."',
|
|
10498
|
+
"fi",
|
|
10499
|
+
"```",
|
|
10500
|
+
"",
|
|
10501
|
+
"## Composability",
|
|
10502
|
+
"",
|
|
10503
|
+
"The procedure (`.claude/procedures/clean-merged-branches.sh`) is",
|
|
10504
|
+
"analysis-only and safe to invoke from any agent. The skill is the",
|
|
10505
|
+
"interactive entry point \u2014 use it when a human is at the keyboard.",
|
|
10506
|
+
"Background workers (orchestrator, maintenance-audit) should call",
|
|
10507
|
+
"the procedure directly and report its output without acting on it."
|
|
10508
|
+
].join("\n"),
|
|
10509
|
+
referenceFiles: [
|
|
10510
|
+
{
|
|
10511
|
+
path: "evals/evals.json",
|
|
10512
|
+
content: JSON.stringify(
|
|
10513
|
+
{
|
|
10514
|
+
skill_name: "clean-merged-branches",
|
|
10515
|
+
evals: [
|
|
10516
|
+
{
|
|
10517
|
+
id: 1,
|
|
10518
|
+
prompt: "/clean-merged-branches",
|
|
10519
|
+
expected_output: "The skill runs the analysis-only `clean-merged-branches.sh` procedure against the current checkout's base branch (default: `main`). Each local branch other than HEAD and `main` is reported on one stable line as `MERGED`, `UNMERGED`, `EMPTY`, or `SKIP_WORKTREE` with a file count. When at least one branch classifies as `MERGED` or `EMPTY`, the skill prints the exact deletion list and prompts `Force-delete all <n> with git branch -D? [y/N]`. On explicit `y` / `Y` it force-deletes each branch with `git branch -D` and emits one `DELETED <branch>` line per success. Empty input or any other answer aborts with `Aborted. No branches deleted.` and no branches are deleted. When zero branches are deletable, the skill exits cleanly without prompting.",
|
|
10520
|
+
files: [],
|
|
10521
|
+
product_context_refs: []
|
|
10522
|
+
},
|
|
10523
|
+
{
|
|
10524
|
+
id: 2,
|
|
10525
|
+
prompt: "/clean-merged-branches --base develop",
|
|
10526
|
+
expected_output: "The skill forwards `--base develop` to the procedure, which compares every local branch (other than HEAD and `develop`) against `develop` instead of `main`. Branches whose content matches `develop` classify as `MERGED`; others as `UNMERGED`. The base-branch override is also reflected in the deletion prompt (`...with git branch -D?`), and the skill never deletes `develop` itself or the current HEAD. If `develop` does not exist as a local branch, the procedure exits non-zero with a clear diagnostic and the skill aborts without prompting.",
|
|
10527
|
+
files: [],
|
|
10528
|
+
product_context_refs: []
|
|
10529
|
+
},
|
|
10530
|
+
{
|
|
10531
|
+
id: 3,
|
|
10532
|
+
prompt: "/clean-merged-branches \u2014 I have a branch called feat/old-feature that I checked out in a sibling worktree under /tmp/work. Confirm it's skipped.",
|
|
10533
|
+
expected_output: "The procedure detects `feat/old-feature` via `git worktree list --porcelain` and reports `SKIP_WORKTREE feat/old-feature worktree=/tmp/work` instead of classifying it. The branch is excluded from the deletion list shown at the confirmation prompt. Even if the user answers `y`, the skill never runs `git branch -D feat/old-feature` because the branch is not in the mergeable list.",
|
|
10534
|
+
files: [],
|
|
10535
|
+
product_context_refs: []
|
|
10536
|
+
}
|
|
10537
|
+
]
|
|
10538
|
+
},
|
|
10539
|
+
null,
|
|
10540
|
+
2
|
|
10541
|
+
)
|
|
10542
|
+
}
|
|
10543
|
+
]
|
|
10544
|
+
};
|
|
10098
10545
|
var githubWorkflowBundle = {
|
|
10099
10546
|
name: "github-workflow",
|
|
10100
10547
|
description: "GitHub issue and PR workflow automation patterns",
|
|
@@ -10242,9 +10689,36 @@ var githubWorkflowBundle = {
|
|
|
10242
10689
|
"- Delegate merge to the `pr-reviewer` sub-agent \u2014 do not merge manually and do not enable auto-merge directly"
|
|
10243
10690
|
].join("\n"),
|
|
10244
10691
|
tags: ["workflow"]
|
|
10692
|
+
},
|
|
10693
|
+
{
|
|
10694
|
+
name: "branch-cleanup",
|
|
10695
|
+
description: "Local-branch hygiene helpers shipped with the github-workflow bundle, including the /clean-merged-branches skill for safely force-deleting branches whose content has already merged into the base (handles squash merges).",
|
|
10696
|
+
scope: AGENT_RULE_SCOPE.ALWAYS,
|
|
10697
|
+
content: [
|
|
10698
|
+
"# Branch Cleanup",
|
|
10699
|
+
"",
|
|
10700
|
+
"Local branches accumulate after every merged PR. In squash-merge",
|
|
10701
|
+
"repositories `git branch -d` refuses to delete them because the",
|
|
10702
|
+
"commit hash on the base differs, even when the branch content is",
|
|
10703
|
+
"fully merged. The `github-workflow` bundle ships two affordances",
|
|
10704
|
+
"that use content-equality (not commit-graph reachability) to",
|
|
10705
|
+
"identify branches safe to force-delete:",
|
|
10706
|
+
"",
|
|
10707
|
+
"- `/clean-merged-branches` \u2014 interactive slash-command skill that",
|
|
10708
|
+
" classifies every local branch, prompts for confirmation, then",
|
|
10709
|
+
" runs `git branch -D` on the confirmed list. See",
|
|
10710
|
+
" `.claude/skills/clean-merged-branches/SKILL.md` for usage,",
|
|
10711
|
+
" output format, and the squash-merge verification algorithm.",
|
|
10712
|
+
"- `.claude/procedures/clean-merged-branches.sh` \u2014 analysis-only",
|
|
10713
|
+
" procedure for non-interactive agent use (orchestrator,",
|
|
10714
|
+
" maintenance-audit). NEVER deletes \u2014 only reports `MERGED` /",
|
|
10715
|
+
" `UNMERGED` / `EMPTY` / `SKIP_WORKTREE` lines."
|
|
10716
|
+
].join("\n"),
|
|
10717
|
+
tags: ["workflow"]
|
|
10245
10718
|
}
|
|
10246
10719
|
],
|
|
10247
|
-
|
|
10720
|
+
skills: [cleanMergedBranchesSkill],
|
|
10721
|
+
procedures: [setIssueTypeProcedure, cleanMergedBranchesProcedure]
|
|
10248
10722
|
};
|
|
10249
10723
|
|
|
10250
10724
|
// src/agent/bundles/industry-discovery.ts
|
|
@@ -11881,12 +12355,14 @@ function buildMeetingAnalystSubAgent(tier) {
|
|
|
11881
12355
|
" Interest (signal threshold still applies); capture customer",
|
|
11882
12356
|
" pain points as candidate BR (not FR).",
|
|
11883
12357
|
"",
|
|
11884
|
-
"5. **
|
|
11885
|
-
" `<meetingsRoot>/insights/index.md` and",
|
|
12358
|
+
"5. **Plan the `insights/` tree index update \u2014 do not commit it",
|
|
12359
|
+
" yet.** Ensure `<meetingsRoot>/insights/index.md` and",
|
|
11886
12360
|
" `<meetingsRoot>/insights/{type}/index.md` exist (create them",
|
|
11887
|
-
" following the `section-index` convention if missing)
|
|
11888
|
-
"
|
|
11889
|
-
"
|
|
12361
|
+
" following the `section-index` convention if missing). Note",
|
|
12362
|
+
" the basename row this phase will eventually append, but",
|
|
12363
|
+
" **defer** the row insert and its commit to step 8 below so",
|
|
12364
|
+
" the shared-index commit can land in the smallest possible",
|
|
12365
|
+
" window before push.",
|
|
11890
12366
|
"",
|
|
11891
12367
|
"6. **Create downstream phase issues** using `gh issue create`:",
|
|
11892
12368
|
" - Always create a `meeting:notes` issue (blocked on this extract issue)",
|
|
@@ -11897,7 +12373,35 @@ function buildMeetingAnalystSubAgent(tier) {
|
|
|
11897
12373
|
" - Always create a `meeting:link` issue \u2014 blocked on the draft issue if one",
|
|
11898
12374
|
" was created, otherwise blocked on the notes issue",
|
|
11899
12375
|
"",
|
|
11900
|
-
"7. Commit
|
|
12376
|
+
"7. **Commit the extraction content first.** Stage and commit",
|
|
12377
|
+
" the `insights/{type}/<basename>.md` file (and any newly",
|
|
12378
|
+
" created `insights/index.md` / `insights/{type}/index.md`",
|
|
12379
|
+
" stub pages from step 5 that do not yet exist on the remote)",
|
|
12380
|
+
" in a single focused commit. **Do not push yet.**",
|
|
12381
|
+
"",
|
|
12382
|
+
"8. **Defer the shared-index row insert to a final pre-push",
|
|
12383
|
+
" commit.** Per the `shared-editing-safety` rule's",
|
|
12384
|
+
" **Defer Shared-Index Commit to Final Pre-Push Step**",
|
|
12385
|
+
" subsection, the index update for an existing",
|
|
12386
|
+
" `insights/{type}/index.md` is a **shared-index row insert**",
|
|
12387
|
+
" that races other parallel meeting sessions writing rows into",
|
|
12388
|
+
" the same partition file. Apply the deferred sequence:",
|
|
12389
|
+
"",
|
|
12390
|
+
" ```bash",
|
|
12391
|
+
" git fetch origin",
|
|
12392
|
+
" git pull --rebase origin <default-branch>",
|
|
12393
|
+
" ```",
|
|
12394
|
+
"",
|
|
12395
|
+
" Re-read `insights/{type}/index.md` from the now-up-to-date",
|
|
12396
|
+
" working tree, re-compute the alphabetical insert position",
|
|
12397
|
+
" for the current meeting's basename row (another session may",
|
|
12398
|
+
" have appended a sibling row in the meantime), insert exactly",
|
|
12399
|
+
" one row, and commit the index edit in its **own focused",
|
|
12400
|
+
" commit** whose only file is the shared index. Run the",
|
|
12401
|
+
" commit-path verification step (`git show HEAD:<index-path>`",
|
|
12402
|
+
" + grep count) against that commit before pushing.",
|
|
12403
|
+
"",
|
|
12404
|
+
"9. **Push and close.** Push the branch and close the extract issue.",
|
|
11901
12405
|
"",
|
|
11902
12406
|
"---",
|
|
11903
12407
|
"",
|
|
@@ -11957,13 +12461,41 @@ function buildMeetingAnalystSubAgent(tier) {
|
|
|
11957
12461
|
" - Action Items (table: who, what, when)",
|
|
11958
12462
|
" - Open Questions",
|
|
11959
12463
|
" - Follow-up items",
|
|
11960
|
-
"4. **
|
|
11961
|
-
" `<meetingsRoot>/notes/index.md` and",
|
|
12464
|
+
"4. **Plan the `notes/` tree index update \u2014 do not commit it",
|
|
12465
|
+
" yet.** Ensure `<meetingsRoot>/notes/index.md` and",
|
|
11962
12466
|
" `<meetingsRoot>/notes/{type}/index.md` exist (create them",
|
|
11963
|
-
" following the `section-index` convention if missing)
|
|
11964
|
-
"
|
|
11965
|
-
"
|
|
11966
|
-
"
|
|
12467
|
+
" following the `section-index` convention if missing). Note",
|
|
12468
|
+
" the basename row this phase will eventually append, but",
|
|
12469
|
+
" **defer** the row insert and its commit to step 6 below so",
|
|
12470
|
+
" the shared-index commit can land in the smallest possible",
|
|
12471
|
+
" window before push.",
|
|
12472
|
+
"5. **Commit the notes content first.** Stage and commit the",
|
|
12473
|
+
" `notes/{type}/<basename>.md` file (and any newly created",
|
|
12474
|
+
" `notes/index.md` / `notes/{type}/index.md` stub pages from",
|
|
12475
|
+
" step 4 that do not yet exist on the remote) in a single",
|
|
12476
|
+
" focused commit. **Do not push yet.**",
|
|
12477
|
+
"6. **Defer the shared-index row insert to a final pre-push",
|
|
12478
|
+
" commit.** Per the `shared-editing-safety` rule's",
|
|
12479
|
+
" **Defer Shared-Index Commit to Final Pre-Push Step**",
|
|
12480
|
+
" subsection, the index update for an existing",
|
|
12481
|
+
" `notes/{type}/index.md` is a **shared-index row insert**",
|
|
12482
|
+
" that races other parallel meeting sessions writing rows",
|
|
12483
|
+
" into the same partition file. Apply the deferred sequence:",
|
|
12484
|
+
"",
|
|
12485
|
+
" ```bash",
|
|
12486
|
+
" git fetch origin",
|
|
12487
|
+
" git pull --rebase origin <default-branch>",
|
|
12488
|
+
" ```",
|
|
12489
|
+
"",
|
|
12490
|
+
" Re-read `notes/{type}/index.md` from the now-up-to-date",
|
|
12491
|
+
" working tree, re-compute the alphabetical insert position",
|
|
12492
|
+
" for the current meeting's basename row (another session may",
|
|
12493
|
+
" have appended a sibling row in the meantime), insert exactly",
|
|
12494
|
+
" one row, and commit the index edit in its **own focused",
|
|
12495
|
+
" commit** whose only file is the shared index. Run the",
|
|
12496
|
+
" commit-path verification step against that commit before",
|
|
12497
|
+
" pushing.",
|
|
12498
|
+
"7. Push and close the notes issue.",
|
|
11967
12499
|
"",
|
|
11968
12500
|
"---",
|
|
11969
12501
|
"",
|
|
@@ -15605,12 +16137,25 @@ var issueWorkerSubAgent = {
|
|
|
15605
16137
|
" `file` (and optional `line`). Track `comment_id` per item so you can",
|
|
15606
16138
|
" report which items were handled and which (if any) failed.",
|
|
15607
16139
|
"",
|
|
15608
|
-
" **Synthetic rebase items.**
|
|
15609
|
-
"
|
|
15610
|
-
"
|
|
15611
|
-
"
|
|
15612
|
-
"
|
|
15613
|
-
"
|
|
16140
|
+
" **Synthetic rebase items.** Two `comment_id` values flag the",
|
|
16141
|
+
" reviewer's signal that the PR's head branch is BEHIND the default",
|
|
16142
|
+
" branch with merge conflicts that `gh pr update-branch` could not",
|
|
16143
|
+
" resolve. For either item the work is **not** an editorial change",
|
|
16144
|
+
" \u2014 it is a rebase plus conflict resolution. The two ids select",
|
|
16145
|
+
" different recipes:",
|
|
16146
|
+
"",
|
|
16147
|
+
" - `synthetic:rebase-behind-main` \u2014 generic conflict. Resolve",
|
|
16148
|
+
" each conflicting file by hand (read both sides, reconcile,",
|
|
16149
|
+
" stage), then `git rebase --continue` until the rebase",
|
|
16150
|
+
" completes.",
|
|
16151
|
+
" - `synthetic:rebase-shared-index` \u2014 every conflicting file is a",
|
|
16152
|
+
" row-insert race on a shared-index file (registry / index /",
|
|
16153
|
+
" feature-matrix under a Starlight content root). Apply the",
|
|
16154
|
+
" explicit re-insert recipe carried in the item's `instruction`",
|
|
16155
|
+
" field \u2014 do **not** hand-merge.",
|
|
16156
|
+
"",
|
|
16157
|
+
" **For `synthetic:rebase-behind-main`.** Run the following",
|
|
16158
|
+
" sequence:",
|
|
15614
16159
|
"",
|
|
15615
16160
|
" ```bash",
|
|
15616
16161
|
" git fetch origin",
|
|
@@ -15634,6 +16179,75 @@ var issueWorkerSubAgent = {
|
|
|
15634
16179
|
" history. Push with a regular non-force `git push origin <branch>`",
|
|
15635
16180
|
" and report the rebased head SHA as the worker's commit.",
|
|
15636
16181
|
"",
|
|
16182
|
+
" **For `synthetic:rebase-shared-index`.** Apply the typed",
|
|
16183
|
+
" re-insert recipe step-by-step. Read the recipe verbatim from the",
|
|
16184
|
+
" item's `instruction` field; the steps below summarise the",
|
|
16185
|
+
" contract the reviewer encodes and the precondition guards you",
|
|
16186
|
+
" must enforce:",
|
|
16187
|
+
"",
|
|
16188
|
+
" 1. **Pull and rebase** onto the default branch:",
|
|
16189
|
+
"",
|
|
16190
|
+
" ```bash",
|
|
16191
|
+
" git fetch origin",
|
|
16192
|
+
" git pull --rebase origin {{repository.defaultBranch}}",
|
|
16193
|
+
" ```",
|
|
16194
|
+
"",
|
|
16195
|
+
" 2. **For each conflicting file**, inspect the conflict markers",
|
|
16196
|
+
" against the shared-index glob set and the precondition",
|
|
16197
|
+
" guards. The shared-index globs (sourced from the",
|
|
16198
|
+
" `shared-editing-safety` rule) are:",
|
|
16199
|
+
"",
|
|
16200
|
+
...DEFAULT_SHARED_INDEX_PATHS.map((p) => ` - \`${p}\``),
|
|
16201
|
+
"",
|
|
16202
|
+
" **Precondition guards.** Before re-inserting, confirm every",
|
|
16203
|
+
" `<<<<<<<` / `=======` / `>>>>>>>` hunk in the file touches",
|
|
16204
|
+
" only data rows (lines starting with `| ` that are not the",
|
|
16205
|
+
" table header or `|---|---|` separator). If any hunk touches",
|
|
16206
|
+
" the frontmatter (lines between the opening / closing `---`",
|
|
16207
|
+
" fences), the page H1, surrounding prose paragraphs, the",
|
|
16208
|
+
" table header row, or the separator row, **stop**, run",
|
|
16209
|
+
" `git rebase --abort`, and record the item as `failed` with",
|
|
16210
|
+
" the structured marker `BLOCKED <reason>` (e.g.",
|
|
16211
|
+
" `BLOCKED conflict touches table header in <path>`). Do not",
|
|
16212
|
+
" hand-merge \u2014 the typed recipe applies only to pure",
|
|
16213
|
+
" row-insert races.",
|
|
16214
|
+
"",
|
|
16215
|
+
" When the guards pass, read the rebased version of the file",
|
|
16216
|
+
" (it now contains the other PR's row), extract this PR's row",
|
|
16217
|
+
" from the `<<<<<<<` side of the conflict markers, re-insert",
|
|
16218
|
+
" that row in declared sort order using the file's documented",
|
|
16219
|
+
" sort key (alphabetical on the first column by default), and",
|
|
16220
|
+
" stage the file:",
|
|
16221
|
+
"",
|
|
16222
|
+
" ```bash",
|
|
16223
|
+
" git add <path>",
|
|
16224
|
+
" ```",
|
|
16225
|
+
"",
|
|
16226
|
+
" 3. **Run the commit-path verification** for each re-inserted",
|
|
16227
|
+
" row. The marker must appear exactly once in the staged file",
|
|
16228
|
+
" (zero means missing, more than one means duplicated by a",
|
|
16229
|
+
" mis-merge):",
|
|
16230
|
+
"",
|
|
16231
|
+
" ```bash",
|
|
16232
|
+
' count=$(git show :<path> | grep -Fc "<row-unique-marker>")',
|
|
16233
|
+
' [ "$count" = "1" ] || { echo "BLOCKED verification failed for <path>"; exit 1; }',
|
|
16234
|
+
" ```",
|
|
16235
|
+
"",
|
|
16236
|
+
" 4. **Continue the rebase and push** with a non-force push once",
|
|
16237
|
+
" every file is staged and verified:",
|
|
16238
|
+
"",
|
|
16239
|
+
" ```bash",
|
|
16240
|
+
" git rebase --continue",
|
|
16241
|
+
" git push origin <branch>",
|
|
16242
|
+
" ```",
|
|
16243
|
+
"",
|
|
16244
|
+
" On any `BLOCKED` precondition failure, exit non-zero with the",
|
|
16245
|
+
" `BLOCKED <reason>` line, run `git rebase --abort`, record the",
|
|
16246
|
+
" item as `failed`, and proceed to the report step. The reviewer",
|
|
16247
|
+
" will see the `failed` outcome on the next pass and fall through",
|
|
16248
|
+
" to the human-required hand-off via `review:awaiting-human`. Do",
|
|
16249
|
+
" not force-push under any circumstance.",
|
|
16250
|
+
"",
|
|
15637
16251
|
"4. When complete, prepare a short structured report (PR number, commit",
|
|
15638
16252
|
" SHAs you will push, items handled by `comment_id`, items that failed",
|
|
15639
16253
|
" to apply) \u2014 you will return this after Phase 6.",
|
|
@@ -15697,11 +16311,11 @@ var issueWorkerSubAgent = {
|
|
|
15697
16311
|
"",
|
|
15698
16312
|
"**Synthetic-rebase items skip the `fix(review)` commit.** When the",
|
|
15699
16313
|
"fix-list contained a `comment_id` of `synthetic:rebase-behind-main`",
|
|
15700
|
-
"and you completed the rebase in
|
|
15701
|
-
"
|
|
15702
|
-
"wrap in a `fix(review)`
|
|
15703
|
-
"`git
|
|
15704
|
-
"directly:",
|
|
16314
|
+
"or `synthetic:rebase-shared-index` and you completed the rebase in",
|
|
16315
|
+
"Phase 4, the rebased commit history is itself the deliverable \u2014",
|
|
16316
|
+
"there is no per-item editorial change to wrap in a `fix(review)`",
|
|
16317
|
+
"commit. Skip `git add` / `git commit` / `git pull --rebase` for",
|
|
16318
|
+
"that item and push the rebased branch directly:",
|
|
15705
16319
|
"",
|
|
15706
16320
|
"```bash",
|
|
15707
16321
|
"git push origin <branch-name>",
|
|
@@ -17112,6 +17726,32 @@ var pnpmBundle = {
|
|
|
17112
17726
|
]
|
|
17113
17727
|
};
|
|
17114
17728
|
|
|
17729
|
+
// src/agent/bundles/pr-review-policy.ts
|
|
17730
|
+
var DEFAULT_PATHS_EXEMPT_FROM_SIZE = [
|
|
17731
|
+
"docs/**"
|
|
17732
|
+
];
|
|
17733
|
+
function resolvePrReviewPolicy(config) {
|
|
17734
|
+
const pathsExemptFromSize = config?.autoMerge?.pathsExemptFromSize ?? DEFAULT_PATHS_EXEMPT_FROM_SIZE;
|
|
17735
|
+
assertValidPathsExemptFromSize(pathsExemptFromSize);
|
|
17736
|
+
return {
|
|
17737
|
+
autoMerge: {
|
|
17738
|
+
pathsExemptFromSize: [...pathsExemptFromSize]
|
|
17739
|
+
}
|
|
17740
|
+
};
|
|
17741
|
+
}
|
|
17742
|
+
function validatePrReviewPolicyConfig(config) {
|
|
17743
|
+
return resolvePrReviewPolicy(config);
|
|
17744
|
+
}
|
|
17745
|
+
function assertValidPathsExemptFromSize(paths) {
|
|
17746
|
+
for (const path8 of paths) {
|
|
17747
|
+
if (typeof path8 !== "string" || path8.trim().length === 0) {
|
|
17748
|
+
throw new Error(
|
|
17749
|
+
"prReviewPolicy.autoMerge.pathsExemptFromSize entries must be non-empty strings"
|
|
17750
|
+
);
|
|
17751
|
+
}
|
|
17752
|
+
}
|
|
17753
|
+
}
|
|
17754
|
+
|
|
17115
17755
|
// src/agent/bundles/pr-review.ts
|
|
17116
17756
|
var prReviewerSubAgent = {
|
|
17117
17757
|
name: "pr-reviewer",
|
|
@@ -17302,11 +17942,16 @@ var prReviewerSubAgent = {
|
|
|
17302
17942
|
"### Step 2: Evaluate in precedence order",
|
|
17303
17943
|
"",
|
|
17304
17944
|
"Walk the following checks in order. The **first match wins** and fixes",
|
|
17305
|
-
"the mode; record the triggering condition as the `reason
|
|
17306
|
-
"
|
|
17307
|
-
"`human-required
|
|
17308
|
-
"
|
|
17309
|
-
"
|
|
17945
|
+
"the mode; record the triggering condition as the `reason` **and**",
|
|
17946
|
+
"record the numeric **`matched_rule`** index (one of `2`, `3`, `4`,",
|
|
17947
|
+
"`5`, or `6` for any human-required match \u2014 see the rule numbers",
|
|
17948
|
+
"below) so later phases can branch on which precedence rule fired.",
|
|
17949
|
+
"When rule 1 (force-auto label) or rule 7 (default) fixes the mode,",
|
|
17950
|
+
"record `matched_rule: null` \u2014 only the human-required rules carry an",
|
|
17951
|
+
"actionable index. Mixed-match PRs (signals from both sides) resolve",
|
|
17952
|
+
"conservatively to `human-required` \u2014 force-auto only wins when it is",
|
|
17953
|
+
"the single highest match and no later human-required signal changes",
|
|
17954
|
+
"the outcome under step 2c.",
|
|
17310
17955
|
"",
|
|
17311
17956
|
"1. **Force-auto label** \u2014 if the PR carries any label listed under",
|
|
17312
17957
|
" `auto-merge.labels-that-force-auto` (e.g. `review:auto-ok`), set",
|
|
@@ -17323,9 +17968,18 @@ var prReviewerSubAgent = {
|
|
|
17323
17968
|
" (fetched in Phase 2) matches any entry in",
|
|
17324
17969
|
" `human-required.issue-types` (case-insensitive), set",
|
|
17325
17970
|
" `mode = human-required`.",
|
|
17326
|
-
"6. **Size thresholds** \u2014
|
|
17327
|
-
" `
|
|
17328
|
-
"
|
|
17971
|
+
"6. **Size thresholds** \u2014 first read",
|
|
17972
|
+
" `auto-merge.paths-exempt-from-size` from the policy (defaults to",
|
|
17973
|
+
" the documented carve-out list). For every file in the PR diff,",
|
|
17974
|
+
" evaluate whether the file path matches at least one glob in the",
|
|
17975
|
+
" carve-out list. If **every** changed path matches the carve-out,",
|
|
17976
|
+
" **skip rule #6** entirely and continue to rule #7 \u2014 the PR is",
|
|
17977
|
+
" exempt from the size threshold regardless of its `files` or",
|
|
17978
|
+
" `insertions` count. Otherwise (any changed path falls outside",
|
|
17979
|
+
" the carve-out list), apply the size check: if the PR exceeds",
|
|
17980
|
+
" either threshold under `human-required.size` (`files` count or",
|
|
17981
|
+
" `insertions` count), set `mode = human-required` and record the",
|
|
17982
|
+
" triggered axis (files vs. insertions) as the reason.",
|
|
17329
17983
|
"7. **Default** \u2014 if no rule above matched, apply the `default` field",
|
|
17330
17984
|
" from the policy (typically `auto-merge`).",
|
|
17331
17985
|
"",
|
|
@@ -17339,14 +17993,23 @@ var prReviewerSubAgent = {
|
|
|
17339
17993
|
"",
|
|
17340
17994
|
"### Step 3: Record the decision",
|
|
17341
17995
|
"",
|
|
17342
|
-
"Persist the evaluated mode
|
|
17343
|
-
"any downstream summary writer can
|
|
17996
|
+
"Persist the evaluated mode, the reason, and the matched-rule index",
|
|
17997
|
+
"for later phases so Phase 4 and any downstream summary writer can",
|
|
17998
|
+
"cite them:",
|
|
17344
17999
|
"",
|
|
17345
18000
|
"```",
|
|
17346
18001
|
"Review mode: <auto-merge | human-required>",
|
|
17347
18002
|
"Reason: <short explanation \u2014 label name, path+glob, issue type, size threshold, default>",
|
|
18003
|
+
"Matched rule: <2 | 3 | 4 | 5 | 6 | null>",
|
|
17348
18004
|
"```",
|
|
17349
18005
|
"",
|
|
18006
|
+
"`Matched rule` is the numeric index of the precedence rule that",
|
|
18007
|
+
"fixed the mode in Step 2. It is populated only for human-required",
|
|
18008
|
+
"matches (rules 2\u20136) \u2014 force-auto (rule 1) and default (rule 7)",
|
|
18009
|
+
"record `null`. Phase 4's `gh pr update-branch` gate consults this",
|
|
18010
|
+
"field to decide whether the size-only carve-out (rule 6) allows the",
|
|
18011
|
+
"bot to keep a human-required branch fresh against the default branch.",
|
|
18012
|
+
"",
|
|
17350
18013
|
"Phases 3 (acceptance-criteria comparison) and CI verification run",
|
|
17351
18014
|
"unchanged regardless of mode. Only the terminal action in Phase 4",
|
|
17352
18015
|
"branches on the decided mode.",
|
|
@@ -17798,9 +18461,42 @@ var prReviewerSubAgent = {
|
|
|
17798
18461
|
"gh pr view <pr-number> --json mergeStateStatus --jq '.mergeStateStatus'",
|
|
17799
18462
|
"```",
|
|
17800
18463
|
"",
|
|
17801
|
-
"
|
|
17802
|
-
"
|
|
17803
|
-
"
|
|
18464
|
+
"Before running `gh pr update-branch`, evaluate the **eligibility",
|
|
18465
|
+
"gate** below. The step runs when `mergeStateStatus == BEHIND` **AND**",
|
|
18466
|
+
"either of the following holds:",
|
|
18467
|
+
"",
|
|
18468
|
+
"- The review mode decided in Phase 2.75 is `auto-merge`, **or**",
|
|
18469
|
+
"- The review mode is `human-required` **and** `matched_rule == 6`",
|
|
18470
|
+
" (size threshold is the sole trigger that fixed the mode).",
|
|
18471
|
+
"",
|
|
18472
|
+
"All other `human-required` matches (rules 2\u20135) continue to block",
|
|
18473
|
+
"`update-branch`. Concretely, **never** run `gh pr update-branch`",
|
|
18474
|
+
"when the mode is `human-required` and any of the following fired:",
|
|
18475
|
+
"",
|
|
18476
|
+
"- rule 2 (`review:human-required` label),",
|
|
18477
|
+
"- rule 3 (any `labels-that-force-human` label such as `priority:critical`),",
|
|
18478
|
+
"- rule 4 (`human-required.paths` glob match), or",
|
|
18479
|
+
"- rule 5 (`human-required.issue-types` match).",
|
|
18480
|
+
"",
|
|
18481
|
+
"The rationale: rule 6 fires on the **volume** of the diff alone \u2014",
|
|
18482
|
+
"there is nothing about the changed paths or labels that suggests a",
|
|
18483
|
+
"human reviewer has explicit ownership of the branch's lifecycle.",
|
|
18484
|
+
"Pushing the default branch into a size-tripped human-required PR",
|
|
18485
|
+
"keeps it from sitting stale while the human is still drafting",
|
|
18486
|
+
"their review. By contrast, each of rules 2\u20135 signals a human",
|
|
18487
|
+
"reviewer who owns the branch's lifecycle; silently pushing main",
|
|
18488
|
+
"into those PRs expands the diff under review without their consent.",
|
|
18489
|
+
"",
|
|
18490
|
+
"When the gate **denies** `update-branch` (`human-required` mode and",
|
|
18491
|
+
"`matched_rule` in 2\u20135), record",
|
|
18492
|
+
"`Branch updated: not eligible (human-required by rule <N>)` and skip",
|
|
18493
|
+
"the rest of this sub-section. The human reviewer keeps branch-",
|
|
18494
|
+
"lifecycle ownership.",
|
|
18495
|
+
"",
|
|
18496
|
+
"When the gate **permits** `update-branch`, attempt to bring the head",
|
|
18497
|
+
"branch current with the default branch via `gh pr update-branch`",
|
|
18498
|
+
"(default merge strategy \u2014 **never** `--rebase`, which would rewrite",
|
|
18499
|
+
"commits on a published branch):",
|
|
17804
18500
|
"",
|
|
17805
18501
|
"```bash",
|
|
17806
18502
|
"gh pr update-branch <pr-number>",
|
|
@@ -17809,8 +18505,9 @@ var prReviewerSubAgent = {
|
|
|
17809
18505
|
"Branch on the outcome:",
|
|
17810
18506
|
"",
|
|
17811
18507
|
"- **Success** \u2014 record `Branch updated: yes` for the per-PR report and",
|
|
17812
|
-
" stop. Auto-merge will fire when required checks pass
|
|
17813
|
-
" SHA. Do **not** poll for the merge here \u2014 Phase 5
|
|
18508
|
+
" stop. Auto-merge (when enabled) will fire when required checks pass",
|
|
18509
|
+
" on the new head SHA. Do **not** poll for the merge here \u2014 Phase 5",
|
|
18510
|
+
" owns polling.",
|
|
17814
18511
|
"- **Failure for reasons other than a merge conflict** (permission",
|
|
17815
18512
|
" denied, branch protection refusing the merge commit, transient",
|
|
17816
18513
|
" network error) \u2014 record `Branch updated: failed (<reason>)`, post a",
|
|
@@ -17824,13 +18521,6 @@ var prReviewerSubAgent = {
|
|
|
17824
18521
|
"an `update-branch` attempt \u2014 every other state either has nothing to do",
|
|
17825
18522
|
"or is already gated on a different signal that Phase 5 picks up.",
|
|
17826
18523
|
"",
|
|
17827
|
-
"Never run `gh pr update-branch` on a PR whose review mode is",
|
|
17828
|
-
"`human-required`. Pushing main into a human-required PR expands the",
|
|
17829
|
-
"scope of the diff the human is reviewing without their consent. The",
|
|
17830
|
-
"`update-branch` step lives **only** under the `Mode auto-merge` branch",
|
|
17831
|
-
"of this phase \u2014 `human-required` skips straight to its hand-off block",
|
|
17832
|
-
"below.",
|
|
17833
|
-
"",
|
|
17834
18524
|
"##### Conflict-resolution delegation (BEHIND + conflicts)",
|
|
17835
18525
|
"",
|
|
17836
18526
|
"When `gh pr update-branch <pr-number>` fails because the merge would",
|
|
@@ -17844,9 +18534,13 @@ var prReviewerSubAgent = {
|
|
|
17844
18534
|
"fall through to the fallback at the end of this sub-section instead.",
|
|
17845
18535
|
"",
|
|
17846
18536
|
"1. **Review mode is `auto-merge`.** Never delegate conflict",
|
|
17847
|
-
" resolution on `human-required` PRs \u2014 pushing
|
|
17848
|
-
" them expands the diff
|
|
17849
|
-
"
|
|
18537
|
+
" resolution on `human-required` PRs \u2014 pushing worker-resolved",
|
|
18538
|
+
" merge content into them expands the diff under review without",
|
|
18539
|
+
" the human reviewer's consent. (Unlike the `update-branch` step",
|
|
18540
|
+
" above, which permits a size-only `human-required` carve-out, the",
|
|
18541
|
+
" conflict-resolution delegation flow is auto-merge-only across",
|
|
18542
|
+
" the board: a worker rebase push is a stronger branch mutation",
|
|
18543
|
+
" than the merge-commit `gh pr update-branch` performs.)",
|
|
17850
18544
|
"2. **Delegation invocation guard permits the hand-off** \u2014 the PR",
|
|
17851
18545
|
" carries the `origin:issue-worker` label, **or** the reviewer was",
|
|
17852
18546
|
" invoked with `--allow-human-author`. The same guard used for the",
|
|
@@ -17871,8 +18565,53 @@ var prReviewerSubAgent = {
|
|
|
17871
18565
|
" and conflicts there should be resolved by re-running synth, not by",
|
|
17872
18566
|
" merging the conflict markers.",
|
|
17873
18567
|
"",
|
|
17874
|
-
"When every guard above passes,
|
|
17875
|
-
"
|
|
18568
|
+
"When every guard above passes, **classify each conflicting file**",
|
|
18569
|
+
"before composing the fix-list. The classification picks one of two",
|
|
18570
|
+
"typed recipes \u2014 a precise `shared-index` resolver when every",
|
|
18571
|
+
"conflict is a row-insert race on a registry / index / feature-matrix",
|
|
18572
|
+
"file, or the generic rebase recipe in every other case.",
|
|
18573
|
+
"",
|
|
18574
|
+
"**Classification step.** For each conflicting path reported by the",
|
|
18575
|
+
"failed `update-branch`, decide whether the file is `shared-index` or",
|
|
18576
|
+
"`generic` against these criteria (the shared-index glob set comes",
|
|
18577
|
+
"from the `shared-editing-safety` rule \u2014 see the bundle's",
|
|
18578
|
+
"`shared-editing.ts` for the canonical constant):",
|
|
18579
|
+
"",
|
|
18580
|
+
"- `shared-index` \u2014 the path matches one of the shared-editing glob",
|
|
18581
|
+
" patterns:",
|
|
18582
|
+
...DEFAULT_SHARED_INDEX_PATHS.map((p) => ` - \`${p}\``),
|
|
18583
|
+
" **AND** the conflict diff is bounded to row insertions only.",
|
|
18584
|
+
" Inspect the merge conflict diff for the file: every `<<<<<<<` /",
|
|
18585
|
+
" `=======` / `>>>>>>>` hunk must touch only data rows (lines that",
|
|
18586
|
+
" start with `| ` and are not the table header or the `|---|---|`",
|
|
18587
|
+
" separator). The conflict must **not** touch:",
|
|
18588
|
+
" - The frontmatter block (lines between the opening and",
|
|
18589
|
+
" closing `---` fences at the top of the file).",
|
|
18590
|
+
" - The page H1 or any surrounding prose paragraphs.",
|
|
18591
|
+
" - The table header row (the first `| Column | ... |` row of",
|
|
18592
|
+
" any table).",
|
|
18593
|
+
" - The separator row (`|---|---|...|`).",
|
|
18594
|
+
" If the conflict hunks touch any of the above, classify the file",
|
|
18595
|
+
" as `generic` \u2014 the mechanical row-insert recipe cannot safely",
|
|
18596
|
+
" reconcile structural edits.",
|
|
18597
|
+
"- `generic` \u2014 anything else (path outside the shared-editing glob",
|
|
18598
|
+
" set, **or** conflict hunks touch the frontmatter / header /",
|
|
18599
|
+
" separator / surrounding prose).",
|
|
18600
|
+
"",
|
|
18601
|
+
"**Branching emit step.**",
|
|
18602
|
+
"",
|
|
18603
|
+
"- **Every conflicting file is `shared-index`** \u2014 emit the typed",
|
|
18604
|
+
' fix-list item with `comment_id: "synthetic:rebase-shared-index"`',
|
|
18605
|
+
" carrying the explicit re-insert recipe (see step 3 below). The",
|
|
18606
|
+
" worker mechanically re-inserts each row in declared sort order",
|
|
18607
|
+
" against the rebased file.",
|
|
18608
|
+
"- **Any conflicting file is `generic`** \u2014 emit the existing generic",
|
|
18609
|
+
' fix-list item with `comment_id: "synthetic:rebase-behind-main"`',
|
|
18610
|
+
" (see step 3 below). The worker performs a hand-merge of the",
|
|
18611
|
+
" conflict markers.",
|
|
18612
|
+
"",
|
|
18613
|
+
"Hand off to `issue-worker` with the chosen single synthetic",
|
|
18614
|
+
"fix-list item:",
|
|
17876
18615
|
"",
|
|
17877
18616
|
"1. **Disable auto-merge** so a fast CI pass cannot land the PR",
|
|
17878
18617
|
" mid-delegation (idempotent \u2014 safe no-op when auto-merge was never",
|
|
@@ -17891,9 +18630,18 @@ var prReviewerSubAgent = {
|
|
|
17891
18630
|
" ```",
|
|
17892
18631
|
"",
|
|
17893
18632
|
"3. **Post a fix-list comment** containing exactly one synthetic item",
|
|
17894
|
-
" describing the rebase. The `comment_id`
|
|
17895
|
-
"
|
|
17896
|
-
"
|
|
18633
|
+
" describing the rebase. The `comment_id` field selects the typed",
|
|
18634
|
+
" recipe:",
|
|
18635
|
+
"",
|
|
18636
|
+
" - `synthetic:rebase-behind-main` \u2014 generic conflict; the worker",
|
|
18637
|
+
" resolves conflicting hunks by hand.",
|
|
18638
|
+
" - `synthetic:rebase-shared-index` \u2014 every conflict is a pure",
|
|
18639
|
+
" row-insert race on a shared-index file; the worker re-inserts",
|
|
18640
|
+
" each row in declared sort order against the rebased file.",
|
|
18641
|
+
"",
|
|
18642
|
+
" The next reviewer pass identifies the item by its `comment_id`.",
|
|
18643
|
+
"",
|
|
18644
|
+
" **Generic shape (`synthetic:rebase-behind-main`):**",
|
|
17897
18645
|
"",
|
|
17898
18646
|
" ```markdown",
|
|
17899
18647
|
" ## Reviewer: fix list for @issue-worker",
|
|
@@ -17912,6 +18660,27 @@ var prReviewerSubAgent = {
|
|
|
17912
18660
|
" ```",
|
|
17913
18661
|
" ```",
|
|
17914
18662
|
"",
|
|
18663
|
+
" **Typed shape (`synthetic:rebase-shared-index`).** The",
|
|
18664
|
+
" `instruction` field carries the full re-insert recipe \u2014 the",
|
|
18665
|
+
" worker reads it imperatively, so spell every step out:",
|
|
18666
|
+
"",
|
|
18667
|
+
" ```markdown",
|
|
18668
|
+
" ## Reviewer: fix list for @issue-worker",
|
|
18669
|
+
"",
|
|
18670
|
+
" - [ ] @reviewer \u2014 rebase onto origin/<default-branch> and re-insert shared-index rows in: <space-separated list of conflicting files>",
|
|
18671
|
+
"",
|
|
18672
|
+
" ```json fix-list",
|
|
18673
|
+
" {",
|
|
18674
|
+
' "pr": <pr-number>,',
|
|
18675
|
+
' "branch": "<head-ref-name>",',
|
|
18676
|
+
' "generated_at": "<ISO-8601 timestamp>",',
|
|
18677
|
+
' "items": [',
|
|
18678
|
+
' {"comment_id": "synthetic:rebase-shared-index", "author": "reviewer", "file": "<first-conflicting-file>", "instruction": "Branch is BEHIND default-branch with shared-index row-insert conflicts only. Apply this recipe: (1) Fetch and rebase: `git fetch origin && git pull --rebase origin <default-branch>`. (2) For each conflicting shared-index file: read the rebased version (it now contains the other PR\'s row), extract this PR\'s row from the `<<<<<<<` side of the conflict markers, re-insert that row in declared sort order using the file\'s documented sort key (alphabetical on the first column by default), stage the file with `git add <path>`. (3) Run the commit-path verification for each row: `count=$(git show :<path> | grep -Fc <row-unique-marker>) && [ \\"$count\\" = \\"1\\" ] || exit 1`. (4) `git rebase --continue` and push with non-force `git push origin <branch>`. (5) If any conflict marker touches the frontmatter, table header row, `|---|---|` separator, or surrounding prose, abort the recipe and emit `BLOCKED <reason>` \u2014 the precondition guards from the reviewer\'s classification step were violated."}',
|
|
18679
|
+
" ]",
|
|
18680
|
+
" }",
|
|
18681
|
+
" ```",
|
|
18682
|
+
" ```",
|
|
18683
|
+
"",
|
|
17915
18684
|
"4. **Invoke `issue-worker` in feedback mode** with the same prompt",
|
|
17916
18685
|
" shape used by the in-scope-fix flow: include the literal phrase",
|
|
17917
18686
|
" `feedback mode: PR #<n>` plus the repository identifier",
|
|
@@ -17956,7 +18725,16 @@ var prReviewerSubAgent = {
|
|
|
17956
18725
|
" gh pr edit <pr-number> --add-label 'review:awaiting-human'",
|
|
17957
18726
|
" ```",
|
|
17958
18727
|
"",
|
|
17959
|
-
"2.
|
|
18728
|
+
"2. **If `matched_rule == 6`** (size threshold was the sole trigger),",
|
|
18729
|
+
" run the `Update the branch when `mergeStateStatus` is `BEHIND``",
|
|
18730
|
+
" step from the `Mode auto-merge` branch above before exiting. The",
|
|
18731
|
+
" eligibility gate documented in that sub-section explicitly permits",
|
|
18732
|
+
" `gh pr update-branch` on size-only human-required PRs so the bot",
|
|
18733
|
+
" keeps the branch fresh against the default branch while the human",
|
|
18734
|
+
" reviews. Skip this sub-step for `matched_rule` in 2\u20135 \u2014 the gate",
|
|
18735
|
+
" denies `update-branch` there and the human owns branch-lifecycle.",
|
|
18736
|
+
"",
|
|
18737
|
+
"3. Exit cleanly after the acceptance-criteria check completes and any",
|
|
17960
18738
|
" summary comment the reviewer posts. Proceed to Phase 5 only if a",
|
|
17961
18739
|
" merge occurred \u2014 in `human-required` mode the reviewer stops at",
|
|
17962
18740
|
" the hand-off and does not poll for merge.",
|
|
@@ -18201,13 +18979,20 @@ var prReviewerSubAgent = {
|
|
|
18201
18979
|
" AC-drift pushback, any failed-fix pushback, and any human-required",
|
|
18202
18980
|
" hand-off all keep auto-merge disabled until the human resolves",
|
|
18203
18981
|
" the underlying state.",
|
|
18204
|
-
"15. **
|
|
18205
|
-
"
|
|
18206
|
-
"
|
|
18207
|
-
"
|
|
18208
|
-
"
|
|
18982
|
+
"15. **Restrict `gh pr update-branch` on `human-required` PRs.** The",
|
|
18983
|
+
" `update-branch` step is permitted when the review mode is",
|
|
18984
|
+
" `auto-merge`, **or** when the mode is `human-required` **and**",
|
|
18985
|
+
" Phase 2.75's `matched_rule == 6` (size threshold was the sole",
|
|
18986
|
+
" trigger). All other human-required matches (rule 2 force-human",
|
|
18987
|
+
" label, rule 3 listed force-human label, rule 4 path glob, rule 5",
|
|
18988
|
+
" issue type) continue to block `update-branch`: pushing main into",
|
|
18989
|
+
" those PRs expands the diff under review without the human",
|
|
18990
|
+
" reviewer's consent. The same restriction applies to delegating",
|
|
18209
18991
|
" conflict resolution to `issue-worker`: never delegate a rebase",
|
|
18210
|
-
" on a `human-required` PR
|
|
18992
|
+
" on a `human-required` PR regardless of `matched_rule` \u2014 the",
|
|
18993
|
+
" typed-recipe delegation flow stays auto-merge-only because a",
|
|
18994
|
+
" worker push to a human-required branch is a stronger mutation",
|
|
18995
|
+
" than the merge-commit `gh pr update-branch` performs.",
|
|
18211
18996
|
"16. **Never delegate conflict resolution involving generated or",
|
|
18212
18997
|
" projen-managed files.** When `gh pr update-branch` fails on a",
|
|
18213
18998
|
" BEHIND PR with conflicts and any conflicting path is a lockfile,",
|
|
@@ -18375,364 +19160,426 @@ var reviewPrsSkill = {
|
|
|
18375
19160
|
"comment on that PR and continue with the next."
|
|
18376
19161
|
].join("\n")
|
|
18377
19162
|
};
|
|
18378
|
-
|
|
18379
|
-
|
|
18380
|
-
|
|
18381
|
-
|
|
18382
|
-
|
|
18383
|
-
|
|
18384
|
-
|
|
18385
|
-
|
|
18386
|
-
|
|
18387
|
-
|
|
18388
|
-
|
|
18389
|
-
|
|
18390
|
-
|
|
18391
|
-
|
|
18392
|
-
|
|
18393
|
-
|
|
18394
|
-
|
|
18395
|
-
|
|
18396
|
-
|
|
18397
|
-
|
|
18398
|
-
|
|
18399
|
-
|
|
18400
|
-
|
|
18401
|
-
|
|
18402
|
-
|
|
18403
|
-
|
|
18404
|
-
|
|
18405
|
-
|
|
18406
|
-
|
|
18407
|
-
|
|
18408
|
-
|
|
18409
|
-
|
|
18410
|
-
|
|
18411
|
-
|
|
18412
|
-
|
|
18413
|
-
|
|
18414
|
-
|
|
18415
|
-
|
|
18416
|
-
|
|
18417
|
-
|
|
18418
|
-
|
|
18419
|
-
|
|
18420
|
-
|
|
18421
|
-
|
|
18422
|
-
|
|
18423
|
-
|
|
18424
|
-
|
|
18425
|
-
|
|
18426
|
-
|
|
18427
|
-
|
|
18428
|
-
|
|
18429
|
-
|
|
18430
|
-
|
|
18431
|
-
|
|
18432
|
-
|
|
18433
|
-
|
|
18434
|
-
|
|
18435
|
-
|
|
18436
|
-
|
|
18437
|
-
|
|
18438
|
-
|
|
18439
|
-
|
|
18440
|
-
|
|
18441
|
-
|
|
18442
|
-
|
|
18443
|
-
|
|
18444
|
-
|
|
18445
|
-
|
|
18446
|
-
|
|
18447
|
-
|
|
18448
|
-
|
|
18449
|
-
|
|
18450
|
-
|
|
18451
|
-
|
|
18452
|
-
|
|
18453
|
-
|
|
18454
|
-
|
|
18455
|
-
|
|
18456
|
-
|
|
18457
|
-
|
|
18458
|
-
|
|
18459
|
-
|
|
18460
|
-
|
|
18461
|
-
|
|
18462
|
-
|
|
18463
|
-
|
|
18464
|
-
|
|
18465
|
-
|
|
18466
|
-
|
|
18467
|
-
|
|
18468
|
-
|
|
18469
|
-
|
|
18470
|
-
|
|
18471
|
-
|
|
18472
|
-
|
|
18473
|
-
|
|
18474
|
-
|
|
18475
|
-
|
|
18476
|
-
|
|
18477
|
-
|
|
18478
|
-
|
|
18479
|
-
|
|
18480
|
-
|
|
18481
|
-
|
|
18482
|
-
|
|
18483
|
-
|
|
18484
|
-
|
|
18485
|
-
|
|
18486
|
-
|
|
18487
|
-
|
|
18488
|
-
|
|
18489
|
-
|
|
18490
|
-
|
|
18491
|
-
|
|
18492
|
-
|
|
18493
|
-
|
|
18494
|
-
|
|
18495
|
-
|
|
18496
|
-
|
|
18497
|
-
|
|
18498
|
-
|
|
18499
|
-
|
|
18500
|
-
|
|
18501
|
-
|
|
18502
|
-
|
|
18503
|
-
|
|
18504
|
-
|
|
18505
|
-
|
|
18506
|
-
|
|
18507
|
-
|
|
18508
|
-
|
|
18509
|
-
|
|
18510
|
-
|
|
18511
|
-
|
|
18512
|
-
|
|
19163
|
+
function buildPrReviewBundle(policy = resolvePrReviewPolicy()) {
|
|
19164
|
+
return {
|
|
19165
|
+
name: "pr-review",
|
|
19166
|
+
description: "Pull request review workflow: verifies PRs against their linked issues' acceptance criteria and orchestrates squash-merge, single or looped over all eligible PRs",
|
|
19167
|
+
// Default-apply: the PR review workflow is safe to include everywhere,
|
|
19168
|
+
// and keeping review/merge policy centralised in the pr-reviewer agent
|
|
19169
|
+
// means consumers get consistent behaviour out of the box. Consumers can
|
|
19170
|
+
// still exclude it explicitly via `excludeBundles` if desired.
|
|
19171
|
+
appliesWhen: () => true,
|
|
19172
|
+
rules: [
|
|
19173
|
+
{
|
|
19174
|
+
name: "pr-review-policy",
|
|
19175
|
+
description: "Declarative policy that tells the pr-reviewer which PRs may auto-merge and which must wait for a human reviewer",
|
|
19176
|
+
scope: AGENT_RULE_SCOPE.ALWAYS,
|
|
19177
|
+
content: [
|
|
19178
|
+
"# PR Review Policy",
|
|
19179
|
+
"",
|
|
19180
|
+
"The `pr-reviewer` sub-agent evaluates every PR it reviews against the",
|
|
19181
|
+
"policy below and routes the PR into one of two modes:",
|
|
19182
|
+
"",
|
|
19183
|
+
"- **`auto-merge`** \u2014 the reviewer may enable squash auto-merge once",
|
|
19184
|
+
" all acceptance criteria are met and CI is green.",
|
|
19185
|
+
"- **`human-required`** \u2014 the reviewer runs the full AC/CI check but",
|
|
19186
|
+
" never calls `gh pr merge --auto`. It applies the",
|
|
19187
|
+
" `review:awaiting-human` label and hands off to a human reviewer.",
|
|
19188
|
+
"",
|
|
19189
|
+
"## Policy",
|
|
19190
|
+
"",
|
|
19191
|
+
"```yaml",
|
|
19192
|
+
"version: 1",
|
|
19193
|
+
"default: auto-merge",
|
|
19194
|
+
"",
|
|
19195
|
+
"human-required:",
|
|
19196
|
+
" paths:",
|
|
19197
|
+
' - "docs/src/content/docs/requirements/architectural-decisions/**"',
|
|
19198
|
+
' - "docs/src/content/docs/project-context.md"',
|
|
19199
|
+
' - ".github/workflows/**"',
|
|
19200
|
+
' - ".github/CODEOWNERS"',
|
|
19201
|
+
' - ".projenrc.ts"',
|
|
19202
|
+
' - "projenrc/**"',
|
|
19203
|
+
' - "CLAUDE.md"',
|
|
19204
|
+
' - ".claude/**"',
|
|
19205
|
+
' - "packages/**/package.json"',
|
|
19206
|
+
" issue-types:",
|
|
19207
|
+
" - release",
|
|
19208
|
+
" - hotfix",
|
|
19209
|
+
" size:",
|
|
19210
|
+
" files: 10",
|
|
19211
|
+
" insertions: 500",
|
|
19212
|
+
" labels-that-force-human:",
|
|
19213
|
+
' - "review:human-required"',
|
|
19214
|
+
' - "priority:critical"',
|
|
19215
|
+
"",
|
|
19216
|
+
"auto-merge:",
|
|
19217
|
+
" labels-that-force-auto:",
|
|
19218
|
+
' - "review:auto-ok"',
|
|
19219
|
+
" paths-exempt-from-size:",
|
|
19220
|
+
...renderPathsExemptFromSizeYaml(
|
|
19221
|
+
policy.autoMerge.pathsExemptFromSize
|
|
19222
|
+
),
|
|
19223
|
+
"```",
|
|
19224
|
+
"",
|
|
19225
|
+
"## Precedence",
|
|
19226
|
+
"",
|
|
19227
|
+
"The reviewer walks the following checks in order. The **first match**",
|
|
19228
|
+
"fixes the mode; any mixed-match PR (signals from both sides) resolves",
|
|
19229
|
+
"conservatively to `human-required` \u2014 `auto-merge` only wins when the",
|
|
19230
|
+
"force-auto label is the single top-priority match.",
|
|
19231
|
+
"",
|
|
19232
|
+
"1. **`auto-merge.labels-that-force-auto`** \u2014 if the PR carries any of",
|
|
19233
|
+
" these labels (e.g. `review:auto-ok`), the mode is `auto-merge`",
|
|
19234
|
+
" outright. This is the only escape hatch from the conservative",
|
|
19235
|
+
" default; it requires a maintainer to apply the label explicitly.",
|
|
19236
|
+
"2. **`review:human-required` label** \u2014 reserved force-human label;",
|
|
19237
|
+
" if present (and no force-auto label beat it in step 1), the mode",
|
|
19238
|
+
" is `human-required`.",
|
|
19239
|
+
"3. **`human-required.labels-that-force-human`** \u2014 any listed label on",
|
|
19240
|
+
" the PR (e.g. `priority:critical`) forces `human-required`.",
|
|
19241
|
+
"4. **`human-required.paths`** \u2014 if any file in the PR diff matches",
|
|
19242
|
+
" any glob here, the mode is `human-required`. Matching uses",
|
|
19243
|
+
" standard glob semantics (`**` for recursive directories,",
|
|
19244
|
+
" `*` for a single path segment).",
|
|
19245
|
+
"5. **`human-required.issue-types`** \u2014 if the linked issue's GitHub",
|
|
19246
|
+
" issue type matches any entry (case-insensitive), the mode is",
|
|
19247
|
+
" `human-required`.",
|
|
19248
|
+
"6. **`human-required.size`** \u2014 first read",
|
|
19249
|
+
" `auto-merge.paths-exempt-from-size` from the policy block above.",
|
|
19250
|
+
" For every file in the PR diff, evaluate whether the file path",
|
|
19251
|
+
" matches at least one glob in that carve-out list. If **every**",
|
|
19252
|
+
" changed path matches the carve-out, **skip rule #6** entirely",
|
|
19253
|
+
" and continue to rule #7 \u2014 the PR is exempt from the size",
|
|
19254
|
+
" threshold regardless of its `files` or `insertions` count.",
|
|
19255
|
+
" Otherwise (any changed path falls outside the carve-out list),",
|
|
19256
|
+
" apply the size check: if the PR exceeds either the `files`",
|
|
19257
|
+
" count or the `insertions` count, the mode is `human-required`.",
|
|
19258
|
+
"7. **`default`** \u2014 applied only when no rule above matched",
|
|
19259
|
+
" (normally `auto-merge`).",
|
|
19260
|
+
"",
|
|
19261
|
+
"The `auto-merge.paths-exempt-from-size` carve-out exists so",
|
|
19262
|
+
"**doc-only PRs** that routinely exceed the 500-insertion size",
|
|
19263
|
+
"threshold (large migrations, bulk additions, refresh passes)",
|
|
19264
|
+
"are not forced into `human-required` mode for a reason that does",
|
|
19265
|
+
"not reflect production risk. The default carve-out exempts the",
|
|
19266
|
+
"entire `docs/**` tree \u2014 every consumer of configulator places its",
|
|
19267
|
+
"Starlight docs site there. A PR mixing docs and code still falls",
|
|
19268
|
+
"into `human-required` at rule #6 because the non-docs path fails",
|
|
19269
|
+
"the carve-out check, so the rule only relaxes the threshold for",
|
|
19270
|
+
"PRs whose **every** changed path is doc-only.",
|
|
19271
|
+
"",
|
|
19272
|
+
"The `pr-reviewer` sub-agent records the decided mode, the",
|
|
19273
|
+
"triggering reason, and the numeric **matched-rule index** (2\u20136",
|
|
19274
|
+
"for human-required matches; `null` for rule 1 force-auto or",
|
|
19275
|
+
"rule 7 default) in its Phase 2.75 output. Downstream phases and",
|
|
19276
|
+
"the sticky summary cite the specific rule that applied.",
|
|
19277
|
+
"",
|
|
19278
|
+
"### Rule-#6 carve-out for `gh pr update-branch`",
|
|
19279
|
+
"",
|
|
19280
|
+
"The reviewer's BEHIND-branch refresh step (`gh pr update-branch`)",
|
|
19281
|
+
"is normally restricted to `auto-merge` PRs because pushing the",
|
|
19282
|
+
"default branch into a `human-required` PR expands the diff the",
|
|
19283
|
+
"human is reviewing without their consent. A narrow exception",
|
|
19284
|
+
"applies when rule #6 (size threshold) is the **sole** trigger",
|
|
19285
|
+
"for `human-required` mode: the bot may still run `gh pr",
|
|
19286
|
+
"update-branch` so a code-heavy size-tripped PR does not sit",
|
|
19287
|
+
"BEHIND while the human drafts their review.",
|
|
19288
|
+
"",
|
|
19289
|
+
"The exception is keyed on the matched-rule index recorded in",
|
|
19290
|
+
"Phase 2.75. All other `human-required` triggers \u2014 rule 2",
|
|
19291
|
+
"(`review:human-required` label), rule 3 (any",
|
|
19292
|
+
"`labels-that-force-human` label such as `priority:critical`),",
|
|
19293
|
+
"rule 4 (`human-required.paths` glob match), and rule 5",
|
|
19294
|
+
"(`human-required.issue-types` match) \u2014 continue to block",
|
|
19295
|
+
"`update-branch` because each one signals a human reviewer who",
|
|
19296
|
+
"has explicit ownership of the branch's lifecycle.",
|
|
19297
|
+
"",
|
|
19298
|
+
"This carve-out is largely belt-and-suspenders given the doc-only",
|
|
19299
|
+
"size carve-out above. Doc-only PRs that trip rule #6 now route",
|
|
19300
|
+
"directly to `auto-merge`, so the rule-#6 `update-branch` carve-",
|
|
19301
|
+
"out only kicks in for **code-heavy** PRs that legitimately trip",
|
|
19302
|
+
"rule #6 (mixed-content diffs whose non-doc paths fail the",
|
|
19303
|
+
"`paths-exempt-from-size` check, or consumers that disable the",
|
|
19304
|
+
"doc-only carve-out entirely)."
|
|
19305
|
+
].join("\n"),
|
|
19306
|
+
tags: ["policy", "review"]
|
|
18513
19307
|
},
|
|
18514
|
-
|
|
18515
|
-
|
|
18516
|
-
|
|
18517
|
-
|
|
18518
|
-
|
|
18519
|
-
|
|
18520
|
-
|
|
18521
|
-
|
|
18522
|
-
|
|
18523
|
-
|
|
18524
|
-
|
|
18525
|
-
|
|
18526
|
-
|
|
18527
|
-
|
|
18528
|
-
|
|
18529
|
-
|
|
18530
|
-
|
|
18531
|
-
|
|
18532
|
-
|
|
18533
|
-
|
|
18534
|
-
|
|
18535
|
-
|
|
18536
|
-
|
|
18537
|
-
|
|
18538
|
-
|
|
18539
|
-
|
|
18540
|
-
|
|
18541
|
-
|
|
18542
|
-
|
|
18543
|
-
|
|
18544
|
-
|
|
18545
|
-
|
|
18546
|
-
|
|
18547
|
-
|
|
18548
|
-
|
|
18549
|
-
|
|
18550
|
-
|
|
18551
|
-
|
|
18552
|
-
|
|
18553
|
-
|
|
18554
|
-
|
|
18555
|
-
"
|
|
18556
|
-
|
|
18557
|
-
|
|
18558
|
-
|
|
18559
|
-
|
|
18560
|
-
"| `-1` | Declined as out-of-scope. A separate tracking issue was created; the reviewer's reply links to it. | **Yes** |",
|
|
18561
|
-
"",
|
|
18562
|
-
"Terminal reactions (`+1`, `rocket`, `-1`) are applied **only after**",
|
|
18563
|
-
"the corresponding action has truly completed \u2014 the fix accepted,",
|
|
18564
|
-
"the commit landed on the branch, or the out-of-scope tracking",
|
|
18565
|
-
"issue created and linked in a reply. The reviewer never applies a",
|
|
18566
|
-
"terminal reaction pre-emptively.",
|
|
18567
|
-
"",
|
|
18568
|
-
"A comment carrying only `eyes` or `thinking_face` from the",
|
|
18569
|
-
"reviewer is **non-terminal** and will be re-evaluated on the next",
|
|
18570
|
-
"pass. A comment carrying any terminal reaction authored by the",
|
|
18571
|
-
"reviewer is dropped from future classification.",
|
|
18572
|
-
"",
|
|
18573
|
-
"GitHub's reactions API uses `confused` as the content string for",
|
|
18574
|
-
"the `thinking_face` reaction (`content=confused` when POSTing).",
|
|
18575
|
-
"",
|
|
18576
|
-
"### Resolving a Pushback",
|
|
18577
|
-
"",
|
|
18578
|
-
"When the reviewer pushes back on a comment with `thinking_face`,",
|
|
18579
|
-
"auto-merge is blocked until the dispute is resolved. Humans have",
|
|
18580
|
-
"three ways to clear a pushback:",
|
|
18581
|
-
"",
|
|
18582
|
-
"1. **Withdraw the comment.** Delete the comment, or edit out the",
|
|
18583
|
-
" disputed request, then re-invoke `/review-pr <n>`. The reviewer",
|
|
18584
|
-
" drops the withdrawn item from its queue on the next pass.",
|
|
18585
|
-
"2. **Reply with clarification.** Post a reply on the same thread",
|
|
18586
|
-
" that addresses the reviewer's objection (cite the acceptance",
|
|
18587
|
-
" criterion you meant, supply the missing context, or concede the",
|
|
18588
|
-
" point). Re-invoke `/review-pr <n>` \u2014 the reviewer re-classifies",
|
|
18589
|
-
" the thread and may promote `thinking_face` to `+1` if the",
|
|
18590
|
-
" clarification satisfies it.",
|
|
18591
|
-
"3. **Force through with `review:auto-ok`.** Apply the",
|
|
18592
|
-
" `review:auto-ok` label to the PR as an explicit maintainer",
|
|
18593
|
-
" override. The reviewer will log the override in the sticky",
|
|
18594
|
-
" `## Reviewer notes` comment and proceed with auto-merge even",
|
|
18595
|
-
" though the dispute was never resolved by reply or withdrawal.",
|
|
18596
|
-
"",
|
|
18597
|
-
"### Fix-List Comment Format",
|
|
18598
|
-
"",
|
|
18599
|
-
"When Phase 4 delegates in-scope fixes to `issue-worker`, it posts",
|
|
18600
|
-
"a single PR-level comment whose body carries both a human-readable",
|
|
18601
|
-
"checkbox summary and a fenced ```json fix-list``` block. The JSON",
|
|
18602
|
-
"block is the authoritative payload the worker parses; the",
|
|
18603
|
-
"checkbox list is for humans reading the PR.",
|
|
18604
|
-
"",
|
|
18605
|
-
"```markdown",
|
|
18606
|
-
"## Reviewer: fix list for @issue-worker",
|
|
18607
|
-
"",
|
|
18608
|
-
"- [ ] @<author> \u2014 <instruction summary> (<file>:<line>)",
|
|
18609
|
-
"",
|
|
18610
|
-
"```json fix-list",
|
|
18611
|
-
"{",
|
|
18612
|
-
' "pr": <pr-number>,',
|
|
18613
|
-
' "branch": "<head-ref-name>",',
|
|
18614
|
-
' "generated_at": "<ISO-8601 timestamp>",',
|
|
18615
|
-
' "items": [',
|
|
18616
|
-
' {"comment_id": "<id>", "author": "<login>", "file": "<path>", "line": <n>, "instruction": "<imperative instruction>"}',
|
|
18617
|
-
" ]",
|
|
18618
|
-
"}",
|
|
18619
|
-
"```",
|
|
18620
|
-
"```",
|
|
18621
|
-
"",
|
|
18622
|
-
"Each `items[]` entry corresponds to one in-scope comment the",
|
|
18623
|
-
"reviewer queued on this pass. The `comment_id` is preserved",
|
|
18624
|
-
"exactly as returned by the GitHub API so that `issue-worker` can",
|
|
18625
|
-
"report per-item outcomes and the reviewer can apply `rocket` or",
|
|
18626
|
-
"`thinking_face` to the correct source comment on the next pass.",
|
|
18627
|
-
"",
|
|
18628
|
-
"### Sticky `## Reviewer notes` Comment",
|
|
18629
|
-
"",
|
|
18630
|
-
"Every PR has **one** canonical reviewer-notes comment. The",
|
|
18631
|
-
"reviewer creates it on the first pass, then **edits it in place**",
|
|
18632
|
-
"on every subsequent pass via",
|
|
18633
|
-
"`gh api .../issues/comments/<id> -X PATCH`. It is never",
|
|
18634
|
-
"duplicated and never replaced by a fresh per-pass summary.",
|
|
18635
|
-
"",
|
|
18636
|
-
"This sticky comment is the **single human-facing source of truth**",
|
|
18637
|
-
"for the PR's current state. Humans scanning the PR should read",
|
|
18638
|
-
"the sticky first, before scrolling back through individual threads.",
|
|
18639
|
-
"It carries, at a minimum:",
|
|
18640
|
-
"",
|
|
18641
|
-
"- **Mode** \u2014 `auto-merge` or `human-required`, with the Phase 2.75",
|
|
18642
|
-
" reason that chose that mode.",
|
|
18643
|
-
"- **AC status** \u2014 met, partial, or missing, with evidence links",
|
|
18644
|
-
" to files or tests.",
|
|
18645
|
-
"- **CI status** \u2014 green, pending, or red.",
|
|
18646
|
-
"- **Outstanding** \u2014 comments still carrying a non-terminal",
|
|
18647
|
-
" reviewer reaction (`eyes`, open `thinking_face`).",
|
|
18648
|
-
"- **Pushbacks** \u2014 every unresolved `thinking_face` the reviewer",
|
|
18649
|
-
" has left, with the reason captured in its pushback reply.",
|
|
18650
|
-
"- **Last pass** \u2014 the ISO 8601 timestamp of the most recent run.",
|
|
18651
|
-
"",
|
|
18652
|
-
"The sticky is updated on every pass \u2014 including passes that ended",
|
|
18653
|
-
"in a pushback-gated skip, a `NEEDS_CHANGES` findings comment, or",
|
|
18654
|
-
"a `human-required` hand-off \u2014 so it never goes stale while the",
|
|
18655
|
-
"reviewer is actively processing the PR.",
|
|
18656
|
-
"",
|
|
18657
|
-
"### Label Glossary",
|
|
18658
|
-
"",
|
|
18659
|
-
"Five review-workflow labels drive the feedback loop. Consumers",
|
|
18660
|
-
"that adopt this workflow are responsible for creating them in",
|
|
18661
|
-
"their own repos (the same way they create `priority:*` and",
|
|
18662
|
-
"`status:*` labels).",
|
|
18663
|
-
"",
|
|
18664
|
-
"| Label | Purpose |",
|
|
18665
|
-
"|-------|---------|",
|
|
18666
|
-
"| `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`. |",
|
|
18667
|
-
"| `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. |",
|
|
18668
|
-
"| `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. |",
|
|
18669
|
-
"| `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). |",
|
|
18670
|
-
"| `review:fixing` | Short-lived lease held by the reviewer while an `issue-worker` feedback-mode delegation is mid-run. Released automatically at the end of Phase 4 step (g). Contention on this label means a prior delegation crashed without releasing it and needs human investigation. |",
|
|
18671
|
-
"",
|
|
18672
|
-
"### Reviewing Human-Authored PRs: the `--allow-human-author` Flag",
|
|
18673
|
-
"",
|
|
18674
|
-
"By default the reviewer only **delegates** in-scope fixes on",
|
|
18675
|
-
"bot-authored PRs \u2014 those carrying the `origin:issue-worker`",
|
|
18676
|
-
"label. Running `/review-pr <n>` or `/review-prs` on a",
|
|
18677
|
-
"human-authored PR still produces a full review (reactions,",
|
|
18678
|
-
"replies, sticky summary, and auto-merge when the policy allows",
|
|
18679
|
-
"it) but skips the delegation hand-off to `issue-worker` \u2014 the",
|
|
18680
|
-
"human author is expected to apply the fixes themselves.",
|
|
18681
|
-
"",
|
|
18682
|
-
"Pass `--allow-human-author` to opt into delegation on",
|
|
18683
|
-
"human-authored PRs for a single invocation:",
|
|
18684
|
-
"",
|
|
18685
|
-
"```",
|
|
18686
|
-
"/review-pr <pr-number> --allow-human-author",
|
|
18687
|
-
"/review-prs --allow-human-author",
|
|
18688
|
-
"```",
|
|
18689
|
-
"",
|
|
18690
|
-
"The flag does **not** persist across invocations. Subsequent",
|
|
18691
|
-
"invocations return to the bot-only default and require the flag",
|
|
18692
|
-
"to be re-supplied if delegation on a human-authored PR is desired",
|
|
18693
|
-
"again."
|
|
18694
|
-
].join("\n"),
|
|
18695
|
-
platforms: {
|
|
18696
|
-
cursor: { exclude: true }
|
|
19308
|
+
{
|
|
19309
|
+
name: "pr-review-workflow",
|
|
19310
|
+
description: "Describes the /review-pr and /review-prs skills and their delegation to the pr-reviewer sub-agent",
|
|
19311
|
+
scope: AGENT_RULE_SCOPE.ALWAYS,
|
|
19312
|
+
content: [
|
|
19313
|
+
"# PR Review Workflow",
|
|
19314
|
+
"",
|
|
19315
|
+
"Two skills are available, both backed by the same `pr-reviewer`",
|
|
19316
|
+
"sub-agent:",
|
|
19317
|
+
"",
|
|
19318
|
+
"- **`/review-pr <pr-number>`** \u2014 review a single targeted PR.",
|
|
19319
|
+
"- **`/review-prs`** \u2014 loop over every eligible open PR in the",
|
|
19320
|
+
" repository and review each one in turn.",
|
|
19321
|
+
"",
|
|
19322
|
+
"The `pr-reviewer` sub-agent:",
|
|
19323
|
+
"",
|
|
19324
|
+
"1. Runs a pre-flight eligibility filter (mergeable, CI not failing,",
|
|
19325
|
+
" has a linked issue). Ineligible PRs get a short comment and are",
|
|
19326
|
+
" skipped.",
|
|
19327
|
+
"2. Fetches the PR, its diff, CI status, and the linked issue",
|
|
19328
|
+
"3. **Evaluates the PR Review Policy** (see the `PR Review Policy`",
|
|
19329
|
+
" section above) to decide whether the PR is `auto-merge` or",
|
|
19330
|
+
" `human-required`, and records the triggering reason",
|
|
19331
|
+
"4. Builds a checklist from the issue's acceptance criteria",
|
|
19332
|
+
"5. Verifies the diff satisfies each criterion and that CI is green",
|
|
19333
|
+
"6. **Enables squash auto-merge** (with `--delete-branch`) when all",
|
|
19334
|
+
" checks pass **and** the review mode is `auto-merge`",
|
|
19335
|
+
"7. **Applies `review:awaiting-human`** and hands off to a human",
|
|
19336
|
+
" reviewer when the review mode is `human-required` (no auto-merge,",
|
|
19337
|
+
" even if every acceptance criterion is met)",
|
|
19338
|
+
"8. **Comments with grouped findings** when any check fails (plain",
|
|
19339
|
+
" `gh pr comment`, not a formal `--request-changes` review)",
|
|
19340
|
+
"9. After a successful merge, verifies the linked issue is closed",
|
|
19341
|
+
" and closes it explicitly if the merge commit did not",
|
|
19342
|
+
"10. Cleans up the local branch after merge",
|
|
19343
|
+
"",
|
|
19344
|
+
"The reviewer **never** implements code and **never** pushes commits",
|
|
19345
|
+
"to a PR's branch \u2014 it only reviews, decides, and orchestrates merge",
|
|
19346
|
+
"or comment. In loop mode, a failed review for one PR never stops",
|
|
19347
|
+
"the loop; the reviewer comments and moves on. See the `pr-reviewer`",
|
|
19348
|
+
"agent definition for the full phase-by-phase contract."
|
|
19349
|
+
].join("\n"),
|
|
19350
|
+
platforms: {
|
|
19351
|
+
cursor: { exclude: true }
|
|
19352
|
+
},
|
|
19353
|
+
tags: ["workflow"]
|
|
18697
19354
|
},
|
|
18698
|
-
|
|
18699
|
-
|
|
18700
|
-
|
|
18701
|
-
|
|
18702
|
-
|
|
18703
|
-
|
|
18704
|
-
|
|
18705
|
-
|
|
18706
|
-
|
|
18707
|
-
|
|
18708
|
-
|
|
18709
|
-
|
|
18710
|
-
|
|
18711
|
-
|
|
18712
|
-
|
|
18713
|
-
|
|
18714
|
-
|
|
18715
|
-
|
|
18716
|
-
|
|
18717
|
-
|
|
18718
|
-
|
|
18719
|
-
|
|
18720
|
-
|
|
18721
|
-
|
|
18722
|
-
|
|
18723
|
-
|
|
18724
|
-
|
|
18725
|
-
|
|
18726
|
-
|
|
18727
|
-
|
|
18728
|
-
|
|
18729
|
-
|
|
18730
|
-
|
|
18731
|
-
|
|
18732
|
-
|
|
18733
|
-
|
|
18734
|
-
|
|
18735
|
-
|
|
19355
|
+
{
|
|
19356
|
+
name: "pr-review-feedback-protocol",
|
|
19357
|
+
description: "Documents the human-in-the-loop feedback loop on PR review: reaction state machine, pushback resolution, fix-list comment format, sticky reviewer-notes comment, label glossary, and human-author opt-in flag.",
|
|
19358
|
+
scope: AGENT_RULE_SCOPE.ALWAYS,
|
|
19359
|
+
content: [
|
|
19360
|
+
"# PR Review Feedback Protocol",
|
|
19361
|
+
"",
|
|
19362
|
+
"## Human-in-the-Loop Feedback Protocol",
|
|
19363
|
+
"",
|
|
19364
|
+
"The PR review pipeline is a **human-in-the-loop feedback loop**.",
|
|
19365
|
+
"Reviewers (human or agent) leave comments on the PR; the",
|
|
19366
|
+
"`pr-reviewer` sub-agent classifies each comment, reacts to it,",
|
|
19367
|
+
"delegates in-scope fixes to `issue-worker`, and updates a single",
|
|
19368
|
+
"sticky `## Reviewer notes` comment that is the canonical record of",
|
|
19369
|
+
"PR state. The sections below document the conventions humans need",
|
|
19370
|
+
"to read and drive that loop.",
|
|
19371
|
+
"",
|
|
19372
|
+
"### Trigger Model: Human-Triggered, Single-Pass",
|
|
19373
|
+
"",
|
|
19374
|
+
"Each reviewer pass runs exactly once and does not self-chain. A",
|
|
19375
|
+
"human re-invokes `/review-pr <n>` (or `/review-prs`) whenever the",
|
|
19376
|
+
"PR state changes enough to warrant another look \u2014 a new comment,",
|
|
19377
|
+
"a new commit, a resolved pushback, a label flip. The reviewer",
|
|
19378
|
+
"never reschedules itself and never loops back after handing off to",
|
|
19379
|
+
"`issue-worker`: the worker's run is the terminal step of that",
|
|
19380
|
+
"pass, and a human must re-invoke the reviewer to see the follow-up",
|
|
19381
|
+
"reactions and the auto-merge re-enablement decision.",
|
|
19382
|
+
"",
|
|
19383
|
+
"This keeps the loop cheap to reason about: every agent action is",
|
|
19384
|
+
"traceable to a specific human invocation, and there is no",
|
|
19385
|
+
"background automation to pause or cancel.",
|
|
19386
|
+
"",
|
|
19387
|
+
"### Reaction State Machine",
|
|
19388
|
+
"",
|
|
19389
|
+
"The reviewer signals its disposition toward each human comment via",
|
|
19390
|
+
"GitHub reactions on that comment. Five reactions carry meaning in",
|
|
19391
|
+
"this workflow; every other reaction is ignored.",
|
|
19392
|
+
"",
|
|
19393
|
+
"| Reaction | Meaning | Terminal? |",
|
|
19394
|
+
"|----------|---------|-----------|",
|
|
19395
|
+
"| `eyes` | Seen by reviewer; no terminal decision yet. Queued for processing on this or a later pass. | No |",
|
|
19396
|
+
"| `+1` | Reviewer accepted the comment's request; a fix has been queued or has already landed. | **Yes** |",
|
|
19397
|
+
"| `rocket` | The accepted fix has landed on the branch. The reviewer's reply cites the commit SHA that applied it. | **Yes** |",
|
|
19398
|
+
"| `thinking_face` | Reviewer pushback \u2014 the comment conflicts with an acceptance criterion, a CLAUDE.md convention, the project-context doc, or is ambiguous. **Blocks auto-merge** until resolved. | No |",
|
|
19399
|
+
"| `-1` | Declined as out-of-scope. A separate tracking issue was created; the reviewer's reply links to it. | **Yes** |",
|
|
19400
|
+
"",
|
|
19401
|
+
"Terminal reactions (`+1`, `rocket`, `-1`) are applied **only after**",
|
|
19402
|
+
"the corresponding action has truly completed \u2014 the fix accepted,",
|
|
19403
|
+
"the commit landed on the branch, or the out-of-scope tracking",
|
|
19404
|
+
"issue created and linked in a reply. The reviewer never applies a",
|
|
19405
|
+
"terminal reaction pre-emptively.",
|
|
19406
|
+
"",
|
|
19407
|
+
"A comment carrying only `eyes` or `thinking_face` from the",
|
|
19408
|
+
"reviewer is **non-terminal** and will be re-evaluated on the next",
|
|
19409
|
+
"pass. A comment carrying any terminal reaction authored by the",
|
|
19410
|
+
"reviewer is dropped from future classification.",
|
|
19411
|
+
"",
|
|
19412
|
+
"GitHub's reactions API uses `confused` as the content string for",
|
|
19413
|
+
"the `thinking_face` reaction (`content=confused` when POSTing).",
|
|
19414
|
+
"",
|
|
19415
|
+
"### Resolving a Pushback",
|
|
19416
|
+
"",
|
|
19417
|
+
"When the reviewer pushes back on a comment with `thinking_face`,",
|
|
19418
|
+
"auto-merge is blocked until the dispute is resolved. Humans have",
|
|
19419
|
+
"three ways to clear a pushback:",
|
|
19420
|
+
"",
|
|
19421
|
+
"1. **Withdraw the comment.** Delete the comment, or edit out the",
|
|
19422
|
+
" disputed request, then re-invoke `/review-pr <n>`. The reviewer",
|
|
19423
|
+
" drops the withdrawn item from its queue on the next pass.",
|
|
19424
|
+
"2. **Reply with clarification.** Post a reply on the same thread",
|
|
19425
|
+
" that addresses the reviewer's objection (cite the acceptance",
|
|
19426
|
+
" criterion you meant, supply the missing context, or concede the",
|
|
19427
|
+
" point). Re-invoke `/review-pr <n>` \u2014 the reviewer re-classifies",
|
|
19428
|
+
" the thread and may promote `thinking_face` to `+1` if the",
|
|
19429
|
+
" clarification satisfies it.",
|
|
19430
|
+
"3. **Force through with `review:auto-ok`.** Apply the",
|
|
19431
|
+
" `review:auto-ok` label to the PR as an explicit maintainer",
|
|
19432
|
+
" override. The reviewer will log the override in the sticky",
|
|
19433
|
+
" `## Reviewer notes` comment and proceed with auto-merge even",
|
|
19434
|
+
" though the dispute was never resolved by reply or withdrawal.",
|
|
19435
|
+
"",
|
|
19436
|
+
"### Fix-List Comment Format",
|
|
19437
|
+
"",
|
|
19438
|
+
"When Phase 4 delegates in-scope fixes to `issue-worker`, it posts",
|
|
19439
|
+
"a single PR-level comment whose body carries both a human-readable",
|
|
19440
|
+
"checkbox summary and a fenced ```json fix-list``` block. The JSON",
|
|
19441
|
+
"block is the authoritative payload the worker parses; the",
|
|
19442
|
+
"checkbox list is for humans reading the PR.",
|
|
19443
|
+
"",
|
|
19444
|
+
"```markdown",
|
|
19445
|
+
"## Reviewer: fix list for @issue-worker",
|
|
19446
|
+
"",
|
|
19447
|
+
"- [ ] @<author> \u2014 <instruction summary> (<file>:<line>)",
|
|
19448
|
+
"",
|
|
19449
|
+
"```json fix-list",
|
|
19450
|
+
"{",
|
|
19451
|
+
' "pr": <pr-number>,',
|
|
19452
|
+
' "branch": "<head-ref-name>",',
|
|
19453
|
+
' "generated_at": "<ISO-8601 timestamp>",',
|
|
19454
|
+
' "items": [',
|
|
19455
|
+
' {"comment_id": "<id>", "author": "<login>", "file": "<path>", "line": <n>, "instruction": "<imperative instruction>"}',
|
|
19456
|
+
" ]",
|
|
19457
|
+
"}",
|
|
19458
|
+
"```",
|
|
19459
|
+
"```",
|
|
19460
|
+
"",
|
|
19461
|
+
"Each `items[]` entry corresponds to one in-scope comment the",
|
|
19462
|
+
"reviewer queued on this pass. The `comment_id` is preserved",
|
|
19463
|
+
"exactly as returned by the GitHub API so that `issue-worker` can",
|
|
19464
|
+
"report per-item outcomes and the reviewer can apply `rocket` or",
|
|
19465
|
+
"`thinking_face` to the correct source comment on the next pass.",
|
|
19466
|
+
"",
|
|
19467
|
+
"### Sticky `## Reviewer notes` Comment",
|
|
19468
|
+
"",
|
|
19469
|
+
"Every PR has **one** canonical reviewer-notes comment. The",
|
|
19470
|
+
"reviewer creates it on the first pass, then **edits it in place**",
|
|
19471
|
+
"on every subsequent pass via",
|
|
19472
|
+
"`gh api .../issues/comments/<id> -X PATCH`. It is never",
|
|
19473
|
+
"duplicated and never replaced by a fresh per-pass summary.",
|
|
19474
|
+
"",
|
|
19475
|
+
"This sticky comment is the **single human-facing source of truth**",
|
|
19476
|
+
"for the PR's current state. Humans scanning the PR should read",
|
|
19477
|
+
"the sticky first, before scrolling back through individual threads.",
|
|
19478
|
+
"It carries, at a minimum:",
|
|
19479
|
+
"",
|
|
19480
|
+
"- **Mode** \u2014 `auto-merge` or `human-required`, with the Phase 2.75",
|
|
19481
|
+
" reason that chose that mode.",
|
|
19482
|
+
"- **AC status** \u2014 met, partial, or missing, with evidence links",
|
|
19483
|
+
" to files or tests.",
|
|
19484
|
+
"- **CI status** \u2014 green, pending, or red.",
|
|
19485
|
+
"- **Outstanding** \u2014 comments still carrying a non-terminal",
|
|
19486
|
+
" reviewer reaction (`eyes`, open `thinking_face`).",
|
|
19487
|
+
"- **Pushbacks** \u2014 every unresolved `thinking_face` the reviewer",
|
|
19488
|
+
" has left, with the reason captured in its pushback reply.",
|
|
19489
|
+
"- **Last pass** \u2014 the ISO 8601 timestamp of the most recent run.",
|
|
19490
|
+
"",
|
|
19491
|
+
"The sticky is updated on every pass \u2014 including passes that ended",
|
|
19492
|
+
"in a pushback-gated skip, a `NEEDS_CHANGES` findings comment, or",
|
|
19493
|
+
"a `human-required` hand-off \u2014 so it never goes stale while the",
|
|
19494
|
+
"reviewer is actively processing the PR.",
|
|
19495
|
+
"",
|
|
19496
|
+
"### Label Glossary",
|
|
19497
|
+
"",
|
|
19498
|
+
"Five review-workflow labels drive the feedback loop. Consumers",
|
|
19499
|
+
"that adopt this workflow are responsible for creating them in",
|
|
19500
|
+
"their own repos (the same way they create `priority:*` and",
|
|
19501
|
+
"`status:*` labels).",
|
|
19502
|
+
"",
|
|
19503
|
+
"| Label | Purpose |",
|
|
19504
|
+
"|-------|---------|",
|
|
19505
|
+
"| `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`. |",
|
|
19506
|
+
"| `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. |",
|
|
19507
|
+
"| `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. |",
|
|
19508
|
+
"| `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). |",
|
|
19509
|
+
"| `review:fixing` | Short-lived lease held by the reviewer while an `issue-worker` feedback-mode delegation is mid-run. Released automatically at the end of Phase 4 step (g). Contention on this label means a prior delegation crashed without releasing it and needs human investigation. |",
|
|
19510
|
+
"",
|
|
19511
|
+
"### Reviewing Human-Authored PRs: the `--allow-human-author` Flag",
|
|
19512
|
+
"",
|
|
19513
|
+
"By default the reviewer only **delegates** in-scope fixes on",
|
|
19514
|
+
"bot-authored PRs \u2014 those carrying the `origin:issue-worker`",
|
|
19515
|
+
"label. Running `/review-pr <n>` or `/review-prs` on a",
|
|
19516
|
+
"human-authored PR still produces a full review (reactions,",
|
|
19517
|
+
"replies, sticky summary, and auto-merge when the policy allows",
|
|
19518
|
+
"it) but skips the delegation hand-off to `issue-worker` \u2014 the",
|
|
19519
|
+
"human author is expected to apply the fixes themselves.",
|
|
19520
|
+
"",
|
|
19521
|
+
"Pass `--allow-human-author` to opt into delegation on",
|
|
19522
|
+
"human-authored PRs for a single invocation:",
|
|
19523
|
+
"",
|
|
19524
|
+
"```",
|
|
19525
|
+
"/review-pr <pr-number> --allow-human-author",
|
|
19526
|
+
"/review-prs --allow-human-author",
|
|
19527
|
+
"```",
|
|
19528
|
+
"",
|
|
19529
|
+
"The flag does **not** persist across invocations. Subsequent",
|
|
19530
|
+
"invocations return to the bot-only default and require the flag",
|
|
19531
|
+
"to be re-supplied if delegation on a human-authored PR is desired",
|
|
19532
|
+
"again."
|
|
19533
|
+
].join("\n"),
|
|
19534
|
+
platforms: {
|
|
19535
|
+
cursor: { exclude: true }
|
|
19536
|
+
},
|
|
19537
|
+
tags: ["workflow"]
|
|
19538
|
+
}
|
|
19539
|
+
],
|
|
19540
|
+
skills: [reviewPrSkill, reviewPrsSkill],
|
|
19541
|
+
subAgents: [prReviewerSubAgent],
|
|
19542
|
+
labels: [
|
|
19543
|
+
{
|
|
19544
|
+
name: "type:pr-review",
|
|
19545
|
+
color: "5319E7",
|
|
19546
|
+
description: "PR review tasks"
|
|
19547
|
+
},
|
|
19548
|
+
{
|
|
19549
|
+
name: "origin:issue-worker",
|
|
19550
|
+
color: "5319E7",
|
|
19551
|
+
description: "PR opened by the issue-worker agent"
|
|
19552
|
+
},
|
|
19553
|
+
{
|
|
19554
|
+
name: "review:auto-ok",
|
|
19555
|
+
color: "0E8A16",
|
|
19556
|
+
description: "Force auto-merge regardless of policy"
|
|
19557
|
+
},
|
|
19558
|
+
{
|
|
19559
|
+
name: "review:human-required",
|
|
19560
|
+
color: "D93F0B",
|
|
19561
|
+
description: "Force human review regardless of policy"
|
|
19562
|
+
},
|
|
19563
|
+
{
|
|
19564
|
+
name: "review:awaiting-human",
|
|
19565
|
+
color: "FBCA04",
|
|
19566
|
+
description: "Reviewer handed off; awaiting human merge decision"
|
|
19567
|
+
},
|
|
19568
|
+
{
|
|
19569
|
+
name: "review:fixing",
|
|
19570
|
+
color: "D4C5F9",
|
|
19571
|
+
description: "Short-lived lease while issue-worker applies feedback fixes"
|
|
19572
|
+
}
|
|
19573
|
+
]
|
|
19574
|
+
};
|
|
19575
|
+
}
|
|
19576
|
+
function renderPathsExemptFromSizeYaml(paths) {
|
|
19577
|
+
if (paths.length === 0) {
|
|
19578
|
+
return [" []"];
|
|
19579
|
+
}
|
|
19580
|
+
return paths.map((path8) => ` - "${path8}"`);
|
|
19581
|
+
}
|
|
19582
|
+
var prReviewBundle = buildPrReviewBundle();
|
|
18736
19583
|
|
|
18737
19584
|
// src/agent/bundles/projen.ts
|
|
18738
19585
|
var projenBundle = {
|
|
@@ -25795,8 +26642,39 @@ function buildSoftwareProfileAnalystSubAgent(paths, issueDefaults, tier) {
|
|
|
25795
26642
|
" every profile is mapped back to the capability model regardless",
|
|
25796
26643
|
" of whether any adjacent products were surfaced.",
|
|
25797
26644
|
"",
|
|
25798
|
-
"7. **Commit
|
|
25799
|
-
"
|
|
26645
|
+
"7. **Commit any profile updates first \u2014 do not touch the",
|
|
26646
|
+
" matrix yet.** If step 4 surfaced edits to the per-product",
|
|
26647
|
+
" profile file (e.g. notes added to `## Risks / Open",
|
|
26648
|
+
" Questions` about column drift), stage and commit those",
|
|
26649
|
+
" profile changes in a focused commit. **Do not push yet,**",
|
|
26650
|
+
" and **do not** include the matrix file in this commit.",
|
|
26651
|
+
"",
|
|
26652
|
+
"8. **Defer the feature-matrix row insert to a final pre-push",
|
|
26653
|
+
" commit.** Per the `shared-editing-safety` rule's",
|
|
26654
|
+
" **Defer Shared-Index Commit to Final Pre-Push Step**",
|
|
26655
|
+
" subsection, the feature matrix is the canonical example of",
|
|
26656
|
+
" a **shared index file** \u2014 multiple software-profile",
|
|
26657
|
+
" sessions racing to append rows for different products is",
|
|
26658
|
+
" the exact contention this rule exists to mitigate. Apply",
|
|
26659
|
+
" the deferred sequence:",
|
|
26660
|
+
"",
|
|
26661
|
+
" ```bash",
|
|
26662
|
+
" git fetch origin",
|
|
26663
|
+
" git pull --rebase origin <default-branch>",
|
|
26664
|
+
" ```",
|
|
26665
|
+
"",
|
|
26666
|
+
" Re-read `<MATRIX_FILE>` from the now-up-to-date working",
|
|
26667
|
+
" tree, re-compute the deterministic insert position for the",
|
|
26668
|
+
" current product's rows (another session may have appended",
|
|
26669
|
+
" rows for a different product, or extended the segment",
|
|
26670
|
+
" columns, in the meantime), insert the rows in sort order,",
|
|
26671
|
+
" and commit the matrix edit in its **own focused commit**",
|
|
26672
|
+
" whose only file is `<MATRIX_FILE>`. Run the commit-path",
|
|
26673
|
+
" verification step (`git show HEAD:<MATRIX_FILE>` + grep",
|
|
26674
|
+
" count on the product slug as the unique marker) against",
|
|
26675
|
+
" that commit before pushing.",
|
|
26676
|
+
"",
|
|
26677
|
+
"9. **Push and close.** Push the branch and close the matrix issue.",
|
|
25800
26678
|
"",
|
|
25801
26679
|
"---",
|
|
25802
26680
|
"",
|
|
@@ -28368,7 +29246,7 @@ var VERSION = {
|
|
|
28368
29246
|
/**
|
|
28369
29247
|
* Version of Astro to pin for AstroProject scaffolding.
|
|
28370
29248
|
*/
|
|
28371
|
-
ASTRO_VERSION: "6.3.
|
|
29249
|
+
ASTRO_VERSION: "6.3.7",
|
|
28372
29250
|
/**
|
|
28373
29251
|
* CDK CLI for workflows and command line operations.
|
|
28374
29252
|
*
|
|
@@ -28380,7 +29258,7 @@ var VERSION = {
|
|
|
28380
29258
|
*
|
|
28381
29259
|
* CLI and lib are versioned separately, so this is the lib version.
|
|
28382
29260
|
*/
|
|
28383
|
-
AWS_CDK_LIB_VERSION: "2.
|
|
29261
|
+
AWS_CDK_LIB_VERSION: "2.257.0",
|
|
28384
29262
|
/**
|
|
28385
29263
|
* Version of the AWS Constructs library to use.
|
|
28386
29264
|
*/
|
|
@@ -28397,11 +29275,11 @@ var VERSION = {
|
|
|
28397
29275
|
/**
|
|
28398
29276
|
* Version of PNPM to use in workflows at github actions.
|
|
28399
29277
|
*/
|
|
28400
|
-
PNPM_VERSION: "11.2.
|
|
29278
|
+
PNPM_VERSION: "11.2.2",
|
|
28401
29279
|
/**
|
|
28402
29280
|
* Version of Projen to use.
|
|
28403
29281
|
*/
|
|
28404
|
-
PROJEN_VERSION: "0.99.
|
|
29282
|
+
PROJEN_VERSION: "0.99.64",
|
|
28405
29283
|
/**
|
|
28406
29284
|
* Version of `actions/setup-node` to use in GitHub workflows.
|
|
28407
29285
|
* Tracks the version projen currently emits (see node_modules/projen/lib/github/workflows.js).
|
|
@@ -29153,7 +30031,7 @@ function renderPriorityRulesSection(rules) {
|
|
|
29153
30031
|
}
|
|
29154
30032
|
|
|
29155
30033
|
// src/agent/bundles/index.ts
|
|
29156
|
-
function buildBuiltInBundles(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS, defaultAgentTier = AGENT_MODEL.BALANCED, bundleAgentTiers = /* @__PURE__ */ new Map()) {
|
|
30034
|
+
function buildBuiltInBundles(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS, defaultAgentTier = AGENT_MODEL.BALANCED, bundleAgentTiers = /* @__PURE__ */ new Map(), prReviewPolicy = resolvePrReviewPolicy()) {
|
|
29157
30035
|
const tierFor = (bundle) => bundleAgentTiers.get(bundle) ?? defaultAgentTier;
|
|
29158
30036
|
return [
|
|
29159
30037
|
buildBaseBundle(paths),
|
|
@@ -29170,7 +30048,7 @@ function buildBuiltInBundles(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAUL
|
|
|
29170
30048
|
buildMeetingAnalysisBundle(tierFor("meeting-analysis")),
|
|
29171
30049
|
agendaBundle,
|
|
29172
30050
|
orchestratorBundle,
|
|
29173
|
-
|
|
30051
|
+
buildPrReviewBundle(prReviewPolicy),
|
|
29174
30052
|
buildRequirementsAnalystBundle(paths, issueDefaults),
|
|
29175
30053
|
buildRequirementsWriterBundle(paths, issueDefaults),
|
|
29176
30054
|
buildRequirementsReviewerBundle(paths, issueDefaults),
|
|
@@ -29234,210 +30112,10 @@ function prefix(rel, entry) {
|
|
|
29234
30112
|
return path.posix.join(rel, entry);
|
|
29235
30113
|
}
|
|
29236
30114
|
|
|
29237
|
-
// src/agent/renderers/
|
|
30115
|
+
// src/agent/renderers/claude-renderer.ts
|
|
29238
30116
|
var import_projen6 = require("projen");
|
|
29239
30117
|
var import_textfile2 = require("projen/lib/textfile");
|
|
29240
|
-
var GENERATED_MARKER = "
|
|
29241
|
-
var CursorRenderer = class _CursorRenderer {
|
|
29242
|
-
/**
|
|
29243
|
-
* Render all Cursor configuration files.
|
|
29244
|
-
*/
|
|
29245
|
-
static render(component, rules, skills, subAgents, mcpServers, settings) {
|
|
29246
|
-
_CursorRenderer.renderRules(component, rules);
|
|
29247
|
-
_CursorRenderer.renderSkills(component, skills);
|
|
29248
|
-
_CursorRenderer.renderSubAgents(component, subAgents);
|
|
29249
|
-
_CursorRenderer.renderMcpServers(component, mcpServers);
|
|
29250
|
-
_CursorRenderer.renderHooks(component, settings);
|
|
29251
|
-
_CursorRenderer.renderIgnoreFiles(component, settings);
|
|
29252
|
-
}
|
|
29253
|
-
static renderRules(component, rules) {
|
|
29254
|
-
for (const rule of rules) {
|
|
29255
|
-
if (rule.platforms?.cursor?.exclude) continue;
|
|
29256
|
-
const lines = [];
|
|
29257
|
-
const description = rule.platforms?.cursor?.description ?? rule.description;
|
|
29258
|
-
const isAlways = rule.scope === AGENT_RULE_SCOPE.ALWAYS;
|
|
29259
|
-
lines.push("---");
|
|
29260
|
-
lines.push(`description: "${description}"`);
|
|
29261
|
-
lines.push(`alwaysApply: ${isAlways}`);
|
|
29262
|
-
if (!isAlways && rule.filePatterns && rule.filePatterns.length > 0) {
|
|
29263
|
-
lines.push(`path: ${JSON.stringify([...rule.filePatterns])}`);
|
|
29264
|
-
}
|
|
29265
|
-
lines.push("---");
|
|
29266
|
-
lines.push("");
|
|
29267
|
-
lines.push(...rule.content.split("\n"));
|
|
29268
|
-
new import_textfile2.TextFile(component, `.cursor/rules/${rule.name}.mdc`, { lines });
|
|
29269
|
-
}
|
|
29270
|
-
}
|
|
29271
|
-
static renderSkills(component, skills) {
|
|
29272
|
-
for (const skill of skills) {
|
|
29273
|
-
if (skill.platforms?.cursor?.exclude) continue;
|
|
29274
|
-
const lines = [];
|
|
29275
|
-
lines.push("---");
|
|
29276
|
-
lines.push(`name: "${skill.name}"`);
|
|
29277
|
-
lines.push(`description: "${skill.description}"`);
|
|
29278
|
-
if (skill.disableModelInvocation) {
|
|
29279
|
-
lines.push(`disable-model-invocation: true`);
|
|
29280
|
-
}
|
|
29281
|
-
if (skill.userInvocable === false) {
|
|
29282
|
-
lines.push(`user-invocable: false`);
|
|
29283
|
-
}
|
|
29284
|
-
if (skill.context) {
|
|
29285
|
-
lines.push(`context: "${skill.context}"`);
|
|
29286
|
-
}
|
|
29287
|
-
if (skill.agent) {
|
|
29288
|
-
lines.push(`agent: "${skill.agent}"`);
|
|
29289
|
-
}
|
|
29290
|
-
if (skill.shell) {
|
|
29291
|
-
lines.push(`shell: "${skill.shell}"`);
|
|
29292
|
-
}
|
|
29293
|
-
if (skill.allowedTools && skill.allowedTools.length > 0) {
|
|
29294
|
-
lines.push(`allowed-tools:`);
|
|
29295
|
-
for (const tool of skill.allowedTools) {
|
|
29296
|
-
lines.push(` - "${tool}"`);
|
|
29297
|
-
}
|
|
29298
|
-
}
|
|
29299
|
-
lines.push("---");
|
|
29300
|
-
lines.push("");
|
|
29301
|
-
lines.push(...skill.instructions.split("\n"));
|
|
29302
|
-
new import_textfile2.TextFile(component, `.cursor/skills/${skill.name}/SKILL.md`, {
|
|
29303
|
-
lines
|
|
29304
|
-
});
|
|
29305
|
-
if (skill.referenceFiles && skill.referenceFiles.length > 0) {
|
|
29306
|
-
for (const file of skill.referenceFiles) {
|
|
29307
|
-
new import_textfile2.TextFile(component, `.cursor/skills/${skill.name}/${file.path}`, {
|
|
29308
|
-
lines: file.content.split("\n")
|
|
29309
|
-
});
|
|
29310
|
-
}
|
|
29311
|
-
}
|
|
29312
|
-
}
|
|
29313
|
-
}
|
|
29314
|
-
static renderSubAgents(component, subAgents) {
|
|
29315
|
-
for (const agent of subAgents) {
|
|
29316
|
-
if (agent.platforms?.cursor?.exclude) continue;
|
|
29317
|
-
const lines = [];
|
|
29318
|
-
lines.push("---");
|
|
29319
|
-
lines.push(`name: ${agent.name}`);
|
|
29320
|
-
lines.push(`description: >-`);
|
|
29321
|
-
lines.push(` ${agent.description}`);
|
|
29322
|
-
if (agent.platforms?.cursor?.readonly) {
|
|
29323
|
-
lines.push(`readonly: true`);
|
|
29324
|
-
}
|
|
29325
|
-
if (agent.platforms?.cursor?.isBackground) {
|
|
29326
|
-
lines.push(`is_background: true`);
|
|
29327
|
-
}
|
|
29328
|
-
lines.push("---");
|
|
29329
|
-
lines.push("");
|
|
29330
|
-
lines.push(...agent.prompt.split("\n"));
|
|
29331
|
-
new import_textfile2.TextFile(component, `.cursor/agents/${agent.name}.md`, { lines });
|
|
29332
|
-
}
|
|
29333
|
-
}
|
|
29334
|
-
static renderMcpServers(component, mcpServers) {
|
|
29335
|
-
const serverNames = Object.keys(mcpServers);
|
|
29336
|
-
if (serverNames.length === 0) return;
|
|
29337
|
-
const obj = { mcpServers: {} };
|
|
29338
|
-
const servers = obj.mcpServers;
|
|
29339
|
-
for (const [name, config] of Object.entries(mcpServers)) {
|
|
29340
|
-
const server = {};
|
|
29341
|
-
if (config.transport) server.transport = config.transport;
|
|
29342
|
-
if (config.command) server.command = config.command;
|
|
29343
|
-
if (config.args) server.args = [...config.args];
|
|
29344
|
-
if (config.url) server.url = config.url;
|
|
29345
|
-
if (config.headers && Object.keys(config.headers).length > 0) {
|
|
29346
|
-
server.headers = { ...config.headers };
|
|
29347
|
-
}
|
|
29348
|
-
if (config.env) server.env = { ...config.env };
|
|
29349
|
-
servers[name] = server;
|
|
29350
|
-
}
|
|
29351
|
-
new import_projen6.JsonFile(component, ".cursor/mcp.json", { obj });
|
|
29352
|
-
}
|
|
29353
|
-
static renderHooks(component, settings) {
|
|
29354
|
-
if (!settings?.hooks) return;
|
|
29355
|
-
const hooks = {};
|
|
29356
|
-
const hookEntries = settings.hooks;
|
|
29357
|
-
for (const [event, actions] of Object.entries(hookEntries)) {
|
|
29358
|
-
if (actions && actions.length > 0) {
|
|
29359
|
-
hooks[event] = actions.map((h) => ({
|
|
29360
|
-
command: h.command
|
|
29361
|
-
}));
|
|
29362
|
-
}
|
|
29363
|
-
}
|
|
29364
|
-
if (Object.keys(hooks).length === 0) return;
|
|
29365
|
-
new import_projen6.JsonFile(component, ".cursor/hooks.json", {
|
|
29366
|
-
obj: { version: 1, hooks }
|
|
29367
|
-
});
|
|
29368
|
-
}
|
|
29369
|
-
static renderIgnoreFiles(component, settings) {
|
|
29370
|
-
if (settings?.ignorePatterns && settings.ignorePatterns.length > 0) {
|
|
29371
|
-
new import_textfile2.TextFile(component, ".cursorignore", {
|
|
29372
|
-
lines: [GENERATED_MARKER, "", ...settings.ignorePatterns]
|
|
29373
|
-
});
|
|
29374
|
-
}
|
|
29375
|
-
if (settings?.indexingIgnorePatterns && settings.indexingIgnorePatterns.length > 0) {
|
|
29376
|
-
new import_textfile2.TextFile(component, ".cursorindexingignore", {
|
|
29377
|
-
lines: [GENERATED_MARKER, "", ...settings.indexingIgnorePatterns]
|
|
29378
|
-
});
|
|
29379
|
-
}
|
|
29380
|
-
}
|
|
29381
|
-
};
|
|
29382
|
-
|
|
29383
|
-
// src/agent/template-resolver.ts
|
|
29384
|
-
var FALLBACKS = {
|
|
29385
|
-
"repository.owner": "<owner>",
|
|
29386
|
-
"repository.name": "<repo>",
|
|
29387
|
-
"repository.defaultBranch": "main",
|
|
29388
|
-
"organization.name": "<organization>",
|
|
29389
|
-
"organization.githubOrg": "<org>",
|
|
29390
|
-
"githubProject.name": "<project-name>",
|
|
29391
|
-
"githubProject.number": "<project-number>",
|
|
29392
|
-
"githubProject.nodeId": "<project-node-id>",
|
|
29393
|
-
docsPath: "<docs-path>",
|
|
29394
|
-
// The monorepo-layout seed block is additive: when absent, the
|
|
29395
|
-
// seeded `project-context.md` template reads cleanly without it.
|
|
29396
|
-
// Fall back to an empty string so no placeholder text leaks into
|
|
29397
|
-
// rendered prompts for repos that predate the layout contract.
|
|
29398
|
-
monorepoLayoutSeedBlock: ""
|
|
29399
|
-
};
|
|
29400
|
-
var TEMPLATE_RE = /\{\{(\w+(?:\.\w+)*)\}\}/g;
|
|
29401
|
-
function getNestedValue(obj, path8) {
|
|
29402
|
-
const parts = path8.split(".");
|
|
29403
|
-
let current = obj;
|
|
29404
|
-
for (const part of parts) {
|
|
29405
|
-
if (current == null || typeof current !== "object") {
|
|
29406
|
-
return void 0;
|
|
29407
|
-
}
|
|
29408
|
-
current = current[part];
|
|
29409
|
-
}
|
|
29410
|
-
if (current == null) {
|
|
29411
|
-
return void 0;
|
|
29412
|
-
}
|
|
29413
|
-
return String(current);
|
|
29414
|
-
}
|
|
29415
|
-
function resolveTemplateVariables(template, metadata) {
|
|
29416
|
-
if (!TEMPLATE_RE.test(template)) {
|
|
29417
|
-
return { resolved: template, unresolvedKeys: [] };
|
|
29418
|
-
}
|
|
29419
|
-
const unresolvedKeys = [];
|
|
29420
|
-
TEMPLATE_RE.lastIndex = 0;
|
|
29421
|
-
const resolved = template.replace(TEMPLATE_RE, (_match, key) => {
|
|
29422
|
-
if (metadata) {
|
|
29423
|
-
const value = getNestedValue(
|
|
29424
|
-
metadata,
|
|
29425
|
-
key
|
|
29426
|
-
);
|
|
29427
|
-
if (value !== void 0) {
|
|
29428
|
-
return value;
|
|
29429
|
-
}
|
|
29430
|
-
}
|
|
29431
|
-
unresolvedKeys.push(key);
|
|
29432
|
-
return FALLBACKS[key] ?? `<${key}>`;
|
|
29433
|
-
});
|
|
29434
|
-
return { resolved, unresolvedKeys };
|
|
29435
|
-
}
|
|
29436
|
-
|
|
29437
|
-
// src/agent/renderers/claude-renderer.ts
|
|
29438
|
-
var import_projen7 = require("projen");
|
|
29439
|
-
var import_textfile3 = require("projen/lib/textfile");
|
|
29440
|
-
var GENERATED_MARKER2 = "<!-- ~~ Generated by @codedrifters/configulator. Edits welcome \u2014 please contribute improvements back. ~~ -->";
|
|
30118
|
+
var GENERATED_MARKER = "<!-- ~~ Generated by @codedrifters/configulator. Edits welcome \u2014 please contribute improvements back. ~~ -->";
|
|
29441
30119
|
var ClaudeRenderer = class _ClaudeRenderer {
|
|
29442
30120
|
/**
|
|
29443
30121
|
* Render all Claude Code configuration files.
|
|
@@ -29462,12 +30140,12 @@ var ClaudeRenderer = class _ClaudeRenderer {
|
|
|
29462
30140
|
return target === CLAUDE_RULE_TARGET.CLAUDE_MD;
|
|
29463
30141
|
});
|
|
29464
30142
|
if (claudeMdRules.length === 0) return;
|
|
29465
|
-
const lines = [
|
|
30143
|
+
const lines = [GENERATED_MARKER, ""];
|
|
29466
30144
|
for (let i = 0; i < claudeMdRules.length; i++) {
|
|
29467
30145
|
if (i > 0) lines.push("", "---", "");
|
|
29468
30146
|
lines.push(...claudeMdRules[i].content.split("\n"));
|
|
29469
30147
|
}
|
|
29470
|
-
new
|
|
30148
|
+
new import_textfile2.TextFile(component, "CLAUDE.md", { lines });
|
|
29471
30149
|
}
|
|
29472
30150
|
static renderScopedRules(component, rules) {
|
|
29473
30151
|
const scopedRules = rules.filter((r) => {
|
|
@@ -29487,7 +30165,7 @@ var ClaudeRenderer = class _ClaudeRenderer {
|
|
|
29487
30165
|
lines.push("");
|
|
29488
30166
|
}
|
|
29489
30167
|
lines.push(...rule.content.split("\n"));
|
|
29490
|
-
new
|
|
30168
|
+
new import_textfile2.TextFile(component, `.claude/rules/${rule.name}.md`, { lines });
|
|
29491
30169
|
}
|
|
29492
30170
|
}
|
|
29493
30171
|
static renderSettings(component, mcpServers, settings) {
|
|
@@ -29612,7 +30290,7 @@ var ClaudeRenderer = class _ClaudeRenderer {
|
|
|
29612
30290
|
hasContent = true;
|
|
29613
30291
|
}
|
|
29614
30292
|
if (!hasContent) return;
|
|
29615
|
-
new
|
|
30293
|
+
new import_projen6.JsonFile(component, ".claude/settings.json", { obj });
|
|
29616
30294
|
}
|
|
29617
30295
|
static buildSandboxObj(sandbox) {
|
|
29618
30296
|
const obj = {};
|
|
@@ -29699,12 +30377,12 @@ var ClaudeRenderer = class _ClaudeRenderer {
|
|
|
29699
30377
|
lines.push("---");
|
|
29700
30378
|
lines.push("");
|
|
29701
30379
|
lines.push(...skill.instructions.split("\n"));
|
|
29702
|
-
new
|
|
30380
|
+
new import_textfile2.TextFile(component, `.claude/skills/${skill.name}/SKILL.md`, {
|
|
29703
30381
|
lines
|
|
29704
30382
|
});
|
|
29705
30383
|
if (skill.referenceFiles && skill.referenceFiles.length > 0) {
|
|
29706
30384
|
for (const file of skill.referenceFiles) {
|
|
29707
|
-
new
|
|
30385
|
+
new import_textfile2.TextFile(component, `.claude/skills/${skill.name}/${file.path}`, {
|
|
29708
30386
|
lines: file.content.split("\n")
|
|
29709
30387
|
});
|
|
29710
30388
|
}
|
|
@@ -29762,7 +30440,7 @@ var ClaudeRenderer = class _ClaudeRenderer {
|
|
|
29762
30440
|
lines.push("---");
|
|
29763
30441
|
lines.push("");
|
|
29764
30442
|
lines.push(...agent.prompt.split("\n"));
|
|
29765
|
-
new
|
|
30443
|
+
new import_textfile2.TextFile(component, `.claude/agents/${agent.name}.md`, { lines });
|
|
29766
30444
|
}
|
|
29767
30445
|
}
|
|
29768
30446
|
static buildMcpServerObj(config) {
|
|
@@ -29779,7 +30457,7 @@ var ClaudeRenderer = class _ClaudeRenderer {
|
|
|
29779
30457
|
}
|
|
29780
30458
|
static renderProcedures(component, procedures) {
|
|
29781
30459
|
for (const proc of procedures) {
|
|
29782
|
-
new
|
|
30460
|
+
new import_textfile2.TextFile(component, `.claude/procedures/${proc.name}`, {
|
|
29783
30461
|
lines: proc.content.split("\n"),
|
|
29784
30462
|
executable: true
|
|
29785
30463
|
});
|
|
@@ -29806,7 +30484,7 @@ var ClaudeRenderer = class _ClaudeRenderer {
|
|
|
29806
30484
|
lines.push("---");
|
|
29807
30485
|
lines.push("");
|
|
29808
30486
|
lines.push(...command.content.split("\n"));
|
|
29809
|
-
new
|
|
30487
|
+
new import_textfile2.TextFile(component, `.claude/commands/${command.name}.md`, { lines });
|
|
29810
30488
|
}
|
|
29811
30489
|
}
|
|
29812
30490
|
/**
|
|
@@ -29818,6 +30496,206 @@ var ClaudeRenderer = class _ClaudeRenderer {
|
|
|
29818
30496
|
}
|
|
29819
30497
|
};
|
|
29820
30498
|
|
|
30499
|
+
// src/agent/renderers/cursor-renderer.ts
|
|
30500
|
+
var import_projen7 = require("projen");
|
|
30501
|
+
var import_textfile3 = require("projen/lib/textfile");
|
|
30502
|
+
var GENERATED_MARKER2 = "# ~~ Generated by @codedrifters/configulator. Edits welcome \u2014 please contribute improvements back. ~~";
|
|
30503
|
+
var CursorRenderer = class _CursorRenderer {
|
|
30504
|
+
/**
|
|
30505
|
+
* Render all Cursor configuration files.
|
|
30506
|
+
*/
|
|
30507
|
+
static render(component, rules, skills, subAgents, mcpServers, settings) {
|
|
30508
|
+
_CursorRenderer.renderRules(component, rules);
|
|
30509
|
+
_CursorRenderer.renderSkills(component, skills);
|
|
30510
|
+
_CursorRenderer.renderSubAgents(component, subAgents);
|
|
30511
|
+
_CursorRenderer.renderMcpServers(component, mcpServers);
|
|
30512
|
+
_CursorRenderer.renderHooks(component, settings);
|
|
30513
|
+
_CursorRenderer.renderIgnoreFiles(component, settings);
|
|
30514
|
+
}
|
|
30515
|
+
static renderRules(component, rules) {
|
|
30516
|
+
for (const rule of rules) {
|
|
30517
|
+
if (rule.platforms?.cursor?.exclude) continue;
|
|
30518
|
+
const lines = [];
|
|
30519
|
+
const description = rule.platforms?.cursor?.description ?? rule.description;
|
|
30520
|
+
const isAlways = rule.scope === AGENT_RULE_SCOPE.ALWAYS;
|
|
30521
|
+
lines.push("---");
|
|
30522
|
+
lines.push(`description: "${description}"`);
|
|
30523
|
+
lines.push(`alwaysApply: ${isAlways}`);
|
|
30524
|
+
if (!isAlways && rule.filePatterns && rule.filePatterns.length > 0) {
|
|
30525
|
+
lines.push(`path: ${JSON.stringify([...rule.filePatterns])}`);
|
|
30526
|
+
}
|
|
30527
|
+
lines.push("---");
|
|
30528
|
+
lines.push("");
|
|
30529
|
+
lines.push(...rule.content.split("\n"));
|
|
30530
|
+
new import_textfile3.TextFile(component, `.cursor/rules/${rule.name}.mdc`, { lines });
|
|
30531
|
+
}
|
|
30532
|
+
}
|
|
30533
|
+
static renderSkills(component, skills) {
|
|
30534
|
+
for (const skill of skills) {
|
|
30535
|
+
if (skill.platforms?.cursor?.exclude) continue;
|
|
30536
|
+
const lines = [];
|
|
30537
|
+
lines.push("---");
|
|
30538
|
+
lines.push(`name: "${skill.name}"`);
|
|
30539
|
+
lines.push(`description: "${skill.description}"`);
|
|
30540
|
+
if (skill.disableModelInvocation) {
|
|
30541
|
+
lines.push(`disable-model-invocation: true`);
|
|
30542
|
+
}
|
|
30543
|
+
if (skill.userInvocable === false) {
|
|
30544
|
+
lines.push(`user-invocable: false`);
|
|
30545
|
+
}
|
|
30546
|
+
if (skill.context) {
|
|
30547
|
+
lines.push(`context: "${skill.context}"`);
|
|
30548
|
+
}
|
|
30549
|
+
if (skill.agent) {
|
|
30550
|
+
lines.push(`agent: "${skill.agent}"`);
|
|
30551
|
+
}
|
|
30552
|
+
if (skill.shell) {
|
|
30553
|
+
lines.push(`shell: "${skill.shell}"`);
|
|
30554
|
+
}
|
|
30555
|
+
if (skill.allowedTools && skill.allowedTools.length > 0) {
|
|
30556
|
+
lines.push(`allowed-tools:`);
|
|
30557
|
+
for (const tool of skill.allowedTools) {
|
|
30558
|
+
lines.push(` - "${tool}"`);
|
|
30559
|
+
}
|
|
30560
|
+
}
|
|
30561
|
+
lines.push("---");
|
|
30562
|
+
lines.push("");
|
|
30563
|
+
lines.push(...skill.instructions.split("\n"));
|
|
30564
|
+
new import_textfile3.TextFile(component, `.cursor/skills/${skill.name}/SKILL.md`, {
|
|
30565
|
+
lines
|
|
30566
|
+
});
|
|
30567
|
+
if (skill.referenceFiles && skill.referenceFiles.length > 0) {
|
|
30568
|
+
for (const file of skill.referenceFiles) {
|
|
30569
|
+
new import_textfile3.TextFile(component, `.cursor/skills/${skill.name}/${file.path}`, {
|
|
30570
|
+
lines: file.content.split("\n")
|
|
30571
|
+
});
|
|
30572
|
+
}
|
|
30573
|
+
}
|
|
30574
|
+
}
|
|
30575
|
+
}
|
|
30576
|
+
static renderSubAgents(component, subAgents) {
|
|
30577
|
+
for (const agent of subAgents) {
|
|
30578
|
+
if (agent.platforms?.cursor?.exclude) continue;
|
|
30579
|
+
const lines = [];
|
|
30580
|
+
lines.push("---");
|
|
30581
|
+
lines.push(`name: ${agent.name}`);
|
|
30582
|
+
lines.push(`description: >-`);
|
|
30583
|
+
lines.push(` ${agent.description}`);
|
|
30584
|
+
if (agent.platforms?.cursor?.readonly) {
|
|
30585
|
+
lines.push(`readonly: true`);
|
|
30586
|
+
}
|
|
30587
|
+
if (agent.platforms?.cursor?.isBackground) {
|
|
30588
|
+
lines.push(`is_background: true`);
|
|
30589
|
+
}
|
|
30590
|
+
lines.push("---");
|
|
30591
|
+
lines.push("");
|
|
30592
|
+
lines.push(...agent.prompt.split("\n"));
|
|
30593
|
+
new import_textfile3.TextFile(component, `.cursor/agents/${agent.name}.md`, { lines });
|
|
30594
|
+
}
|
|
30595
|
+
}
|
|
30596
|
+
static renderMcpServers(component, mcpServers) {
|
|
30597
|
+
const serverNames = Object.keys(mcpServers);
|
|
30598
|
+
if (serverNames.length === 0) return;
|
|
30599
|
+
const obj = { mcpServers: {} };
|
|
30600
|
+
const servers = obj.mcpServers;
|
|
30601
|
+
for (const [name, config] of Object.entries(mcpServers)) {
|
|
30602
|
+
const server = {};
|
|
30603
|
+
if (config.transport) server.transport = config.transport;
|
|
30604
|
+
if (config.command) server.command = config.command;
|
|
30605
|
+
if (config.args) server.args = [...config.args];
|
|
30606
|
+
if (config.url) server.url = config.url;
|
|
30607
|
+
if (config.headers && Object.keys(config.headers).length > 0) {
|
|
30608
|
+
server.headers = { ...config.headers };
|
|
30609
|
+
}
|
|
30610
|
+
if (config.env) server.env = { ...config.env };
|
|
30611
|
+
servers[name] = server;
|
|
30612
|
+
}
|
|
30613
|
+
new import_projen7.JsonFile(component, ".cursor/mcp.json", { obj });
|
|
30614
|
+
}
|
|
30615
|
+
static renderHooks(component, settings) {
|
|
30616
|
+
if (!settings?.hooks) return;
|
|
30617
|
+
const hooks = {};
|
|
30618
|
+
const hookEntries = settings.hooks;
|
|
30619
|
+
for (const [event, actions] of Object.entries(hookEntries)) {
|
|
30620
|
+
if (actions && actions.length > 0) {
|
|
30621
|
+
hooks[event] = actions.map((h) => ({
|
|
30622
|
+
command: h.command
|
|
30623
|
+
}));
|
|
30624
|
+
}
|
|
30625
|
+
}
|
|
30626
|
+
if (Object.keys(hooks).length === 0) return;
|
|
30627
|
+
new import_projen7.JsonFile(component, ".cursor/hooks.json", {
|
|
30628
|
+
obj: { version: 1, hooks }
|
|
30629
|
+
});
|
|
30630
|
+
}
|
|
30631
|
+
static renderIgnoreFiles(component, settings) {
|
|
30632
|
+
if (settings?.ignorePatterns && settings.ignorePatterns.length > 0) {
|
|
30633
|
+
new import_textfile3.TextFile(component, ".cursorignore", {
|
|
30634
|
+
lines: [GENERATED_MARKER2, "", ...settings.ignorePatterns]
|
|
30635
|
+
});
|
|
30636
|
+
}
|
|
30637
|
+
if (settings?.indexingIgnorePatterns && settings.indexingIgnorePatterns.length > 0) {
|
|
30638
|
+
new import_textfile3.TextFile(component, ".cursorindexingignore", {
|
|
30639
|
+
lines: [GENERATED_MARKER2, "", ...settings.indexingIgnorePatterns]
|
|
30640
|
+
});
|
|
30641
|
+
}
|
|
30642
|
+
}
|
|
30643
|
+
};
|
|
30644
|
+
|
|
30645
|
+
// src/agent/template-resolver.ts
|
|
30646
|
+
var FALLBACKS = {
|
|
30647
|
+
"repository.owner": "<owner>",
|
|
30648
|
+
"repository.name": "<repo>",
|
|
30649
|
+
"repository.defaultBranch": "main",
|
|
30650
|
+
"organization.name": "<organization>",
|
|
30651
|
+
"organization.githubOrg": "<org>",
|
|
30652
|
+
"githubProject.name": "<project-name>",
|
|
30653
|
+
"githubProject.number": "<project-number>",
|
|
30654
|
+
"githubProject.nodeId": "<project-node-id>",
|
|
30655
|
+
docsPath: "<docs-path>",
|
|
30656
|
+
// The monorepo-layout seed block is additive: when absent, the
|
|
30657
|
+
// seeded `project-context.md` template reads cleanly without it.
|
|
30658
|
+
// Fall back to an empty string so no placeholder text leaks into
|
|
30659
|
+
// rendered prompts for repos that predate the layout contract.
|
|
30660
|
+
monorepoLayoutSeedBlock: ""
|
|
30661
|
+
};
|
|
30662
|
+
var TEMPLATE_RE = /\{\{(\w+(?:\.\w+)*)\}\}/g;
|
|
30663
|
+
function getNestedValue(obj, path8) {
|
|
30664
|
+
const parts = path8.split(".");
|
|
30665
|
+
let current = obj;
|
|
30666
|
+
for (const part of parts) {
|
|
30667
|
+
if (current == null || typeof current !== "object") {
|
|
30668
|
+
return void 0;
|
|
30669
|
+
}
|
|
30670
|
+
current = current[part];
|
|
30671
|
+
}
|
|
30672
|
+
if (current == null) {
|
|
30673
|
+
return void 0;
|
|
30674
|
+
}
|
|
30675
|
+
return String(current);
|
|
30676
|
+
}
|
|
30677
|
+
function resolveTemplateVariables(template, metadata) {
|
|
30678
|
+
if (!TEMPLATE_RE.test(template)) {
|
|
30679
|
+
return { resolved: template, unresolvedKeys: [] };
|
|
30680
|
+
}
|
|
30681
|
+
const unresolvedKeys = [];
|
|
30682
|
+
TEMPLATE_RE.lastIndex = 0;
|
|
30683
|
+
const resolved = template.replace(TEMPLATE_RE, (_match, key) => {
|
|
30684
|
+
if (metadata) {
|
|
30685
|
+
const value = getNestedValue(
|
|
30686
|
+
metadata,
|
|
30687
|
+
key
|
|
30688
|
+
);
|
|
30689
|
+
if (value !== void 0) {
|
|
30690
|
+
return value;
|
|
30691
|
+
}
|
|
30692
|
+
}
|
|
30693
|
+
unresolvedKeys.push(key);
|
|
30694
|
+
return FALLBACKS[key] ?? `<${key}>`;
|
|
30695
|
+
});
|
|
30696
|
+
return { resolved, unresolvedKeys };
|
|
30697
|
+
}
|
|
30698
|
+
|
|
29821
30699
|
// src/agent/renderers/codex-renderer.ts
|
|
29822
30700
|
var CodexRenderer = class {
|
|
29823
30701
|
static render(_component, _rules, _skills, _subAgents) {
|
|
@@ -30034,6 +30912,7 @@ var SHARED_EDITING_BUNDLE_HOOKS = [
|
|
|
30034
30912
|
["customer-profile-workflow", "customer-profile"],
|
|
30035
30913
|
["industry-discovery-workflow", "industry-discovery"],
|
|
30036
30914
|
["meeting-agenda-workflow", "agenda"],
|
|
30915
|
+
["meeting-processing-workflow", "meeting-analyst"],
|
|
30037
30916
|
["people-profile-workflow", "people-profile"],
|
|
30038
30917
|
["regulatory-research-workflow", "regulatory-research"],
|
|
30039
30918
|
["requirements-reviewer-workflow", "requirements-reviewer"],
|
|
@@ -30223,7 +31102,8 @@ var AgentConfig = class _AgentConfig extends import_projen8.Component {
|
|
|
30223
31102
|
this.resolvedPaths,
|
|
30224
31103
|
resolveIssueDefaults(this.options.issueDefaults),
|
|
30225
31104
|
resolveDefaultAgentTier(this.options),
|
|
30226
|
-
resolveBundleAgentTiers(this.options)
|
|
31105
|
+
resolveBundleAgentTiers(this.options),
|
|
31106
|
+
resolvePrReviewPolicy(this.options.prReviewPolicy)
|
|
30227
31107
|
);
|
|
30228
31108
|
}
|
|
30229
31109
|
return this.cachedBundles;
|
|
@@ -30265,6 +31145,7 @@ var AgentConfig = class _AgentConfig extends import_projen8.Component {
|
|
|
30265
31145
|
super.preSynthesize();
|
|
30266
31146
|
validateAgentTierConfig(this.options.tiers);
|
|
30267
31147
|
validateScopeGateConfig(this.options.scopeGate);
|
|
31148
|
+
validatePrReviewPolicyConfig(this.options.prReviewPolicy);
|
|
30268
31149
|
const resolvedRunRatio = resolveRunRatio(this.options.runRatio);
|
|
30269
31150
|
if (resolvedRunRatio.enabled) {
|
|
30270
31151
|
this.project.gitignore.addPatterns(`/${resolvedRunRatio.stateFilePath}`);
|
|
@@ -36791,6 +37672,7 @@ export const collections = {
|
|
|
36791
37672
|
DEFAULT_ISSUE_TEMPLATES_REQUIRE_REFERENCE,
|
|
36792
37673
|
DEFAULT_OFF_PEAK_CRON_EXAMPLE,
|
|
36793
37674
|
DEFAULT_PARTIAL_UNBLOCK_COMMENT_TEMPLATE,
|
|
37675
|
+
DEFAULT_PATHS_EXEMPT_FROM_SIZE,
|
|
36794
37676
|
DEFAULT_PRIORITY_LABELS,
|
|
36795
37677
|
DEFAULT_PRODUCT_CONTEXT_PATH,
|
|
36796
37678
|
DEFAULT_PROGRESS_FILES_ENABLED,
|
|
@@ -36893,6 +37775,7 @@ export const collections = {
|
|
|
36893
37775
|
buildMeetingAnalysisBundle,
|
|
36894
37776
|
buildOrchestratorConventionsContent,
|
|
36895
37777
|
buildPeopleProfileBundle,
|
|
37778
|
+
buildPrReviewBundle,
|
|
36896
37779
|
buildRegulatoryResearchBundle,
|
|
36897
37780
|
buildReport,
|
|
36898
37781
|
buildRequirementsAnalystBundle,
|
|
@@ -37023,6 +37906,7 @@ export const collections = {
|
|
|
37023
37906
|
resolveOrchestratorAssets,
|
|
37024
37907
|
resolveOutdirFromPackageName,
|
|
37025
37908
|
resolveOverrideForLabels,
|
|
37909
|
+
resolvePrReviewPolicy,
|
|
37026
37910
|
resolveProgressFiles,
|
|
37027
37911
|
resolveReactViteSiteProjectOutdir,
|
|
37028
37912
|
resolveRunRatio,
|
|
@@ -37046,6 +37930,7 @@ export const collections = {
|
|
|
37046
37930
|
validateIssueDefaultsConfig,
|
|
37047
37931
|
validateIssueTemplatesConfig,
|
|
37048
37932
|
validateMonorepoLayout,
|
|
37933
|
+
validatePrReviewPolicyConfig,
|
|
37049
37934
|
validateProgressFilesConfig,
|
|
37050
37935
|
validateRunRatioConfig,
|
|
37051
37936
|
validateScheduledTasksConfig,
|