@codedrifters/configulator 0.0.298 → 0.0.299

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.js CHANGED
@@ -277,6 +277,7 @@ __export(index_exports, {
277
277
  SUPPRESSED_WORKFLOW_RULE_NAMES: () => SUPPRESSED_WORKFLOW_RULE_NAMES,
278
278
  SampleLang: () => SampleLang,
279
279
  StarlightProject: () => StarlightProject,
280
+ TIER_AWARE_BUNDLE_NAMES: () => TIER_AWARE_BUNDLE_NAMES,
280
281
  TestRunner: () => TestRunner,
281
282
  TsDocCoverageKind: () => TsDocCoverageKind,
282
283
  TurboRepo: () => TurboRepo,
@@ -409,6 +410,7 @@ __export(index_exports, {
409
410
  resolveAgentTiers: () => resolveAgentTiers,
410
411
  resolveAstroProjectOutdir: () => resolveAstroProjectOutdir,
411
412
  resolveAwsCdkProjectOutdir: () => resolveAwsCdkProjectOutdir,
413
+ resolveBundleAgentTiers: () => resolveBundleAgentTiers,
412
414
  resolveDefaultAgentTier: () => resolveDefaultAgentTier,
413
415
  resolveIssueDefaults: () => resolveIssueDefaults,
414
416
  resolveIssueTemplates: () => resolveIssueTemplates,
@@ -1357,6 +1359,47 @@ var awsCdkBundle = {
1357
1359
  "- When adding a `NodejsFunction` with a bundled handler, ensure the `entry` path is explicitly configured",
1358
1360
  "- Verify the entry is included in the build tool config (e.g., tsup entry list) so the runtime can find the handler",
1359
1361
  "",
1362
+ "## Lambda Construct Encapsulation",
1363
+ "",
1364
+ "When a Lambda construct is event-driven, it should own:",
1365
+ "",
1366
+ "- The EventBridge Rule (or other trigger) that routes events to itself.",
1367
+ "- The IAM permissions it needs on resources passed in via props.",
1368
+ "",
1369
+ "Pass the resources (event bus, table, queue, etc.) into the Lambda",
1370
+ "construct's props rather than defining triggers and grants in the",
1371
+ "enclosing workflow or service. The Lambda construct then becomes",
1372
+ 'self-describing \u2014 "what triggers me, what I write to" lives in one',
1373
+ "file \u2014 and reverting or refactoring a single Lambda doesn't ripple",
1374
+ "through unrelated constructs.",
1375
+ "",
1376
+ "```typescript",
1377
+ "export interface MyLambdaProps {",
1378
+ " readonly eventBus: IEventBus;",
1379
+ " readonly dataStoreTable: ITable;",
1380
+ "}",
1381
+ "",
1382
+ "export class MyLambda extends Construct {",
1383
+ " public readonly lambda: NodejsFunction;",
1384
+ " public readonly rule: Rule;",
1385
+ "",
1386
+ " constructor(scope: Construct, props: MyLambdaProps) {",
1387
+ ' super(scope, "my-lambda");',
1388
+ "",
1389
+ ' this.lambda = new NodejsFunction(this, "handler", { /* ... */ });',
1390
+ " props.dataStoreTable.grantReadWriteData(this.lambda);",
1391
+ ' this.rule = new Rule(this, "rule", {',
1392
+ " eventBus: props.eventBus,",
1393
+ " eventPattern: { /* ... */ },",
1394
+ " targets: [new LambdaFunction(this.lambda)],",
1395
+ " });",
1396
+ " }",
1397
+ "}",
1398
+ "```",
1399
+ "",
1400
+ "A workflow or service construct then composes these Lambda constructs",
1401
+ "without owning their triggers or grants directly.",
1402
+ "",
1360
1403
  "## CDK Testing",
1361
1404
  "",
1362
1405
  "- Mock `Code.fromAsset` in any test file that synthesizes CDK stacks",
@@ -13470,11 +13513,10 @@ function shellSingleQuote(value) {
13470
13513
  }
13471
13514
 
13472
13515
  // src/agent/bundles/orchestrator.ts
13473
- function buildCheckBlockedScript(tiers, scopeGate, runRatio) {
13516
+ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
13474
13517
  const tierCase = renderAgentTierCaseStatement(tiers);
13475
13518
  const scopeHelper = renderScopeGateShellHelpers(scopeGate);
13476
13519
  const scopeHelperIndented = scopeHelper.split("\n").map((line) => line.length > 0 ? line : "").join("\n");
13477
- const runRatioHelper = renderRunRatioShellHelpers(runRatio);
13478
13520
  return [
13479
13521
  "#!/usr/bin/env bash",
13480
13522
  "# check-blocked.sh \u2014 Token-efficient issue triage for agent loops.",
@@ -13486,9 +13528,9 @@ function buildCheckBlockedScript(tiers, scopeGate, runRatio) {
13486
13528
  "# .claude/procedures/check-blocked.sh eligible",
13487
13529
  "# .claude/procedures/check-blocked.sh stale",
13488
13530
  "# .claude/procedures/check-blocked.sh orphaned",
13531
+ "# .claude/procedures/check-blocked.sh maintenance",
13489
13532
  "# .claude/procedures/check-blocked.sh prs",
13490
13533
  "# .claude/procedures/check-blocked.sh scope <issue-number>",
13491
- "# .claude/procedures/check-blocked.sh tick",
13492
13534
  "",
13493
13535
  "set -uo pipefail",
13494
13536
  "",
@@ -13502,9 +13544,6 @@ function buildCheckBlockedScript(tiers, scopeGate, runRatio) {
13502
13544
  `SCOPE_AC_MEDIUM_MAX=${scopeGate.acceptanceCriteria.mediumMax}`,
13503
13545
  `SCOPE_SOURCES_SMALL_MAX=${scopeGate.sources.smallMax}`,
13504
13546
  `SCOPE_SOURCES_MEDIUM_MAX=${scopeGate.sources.mediumMax}`,
13505
- `RUN_RATIO_ENABLED=${runRatio.enabled ? "1" : "0"}`,
13506
- `RUN_RATIO_DISPATCH_PER_HOUSEKEEPING=${runRatio.ratio}`,
13507
- `ORCHESTRATOR_STATE_FILE="${runRatio.stateFilePath}"`,
13508
13547
  "",
13509
13548
  "# \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",
13510
13549
  "",
@@ -13535,8 +13574,6 @@ function buildCheckBlockedScript(tiers, scopeGate, runRatio) {
13535
13574
  "",
13536
13575
  scopeHelperIndented,
13537
13576
  "",
13538
- runRatioHelper,
13539
- "",
13540
13577
  "# \u2500\u2500 subcommands \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
13541
13578
  "",
13542
13579
  "cmd_unblock() {",
@@ -13547,7 +13584,7 @@ function buildCheckBlockedScript(tiers, scopeGate, runRatio) {
13547
13584
  " local count",
13548
13585
  ` count=$(echo "$issues" | jq 'length')`,
13549
13586
  ' if [[ "$count" -eq 0 ]]; then',
13550
- ' echo "NO_BLOCKED_ISSUES"',
13587
+ ' echo "TRIAGE_DONE unblocked=0 still_blocked=0"',
13551
13588
  " return 0",
13552
13589
  " fi",
13553
13590
  "",
@@ -13558,18 +13595,36 @@ function buildCheckBlockedScript(tiers, scopeGate, runRatio) {
13558
13595
  ' "\\(.number)\\t\\($dep_line)"',
13559
13596
  " ')",
13560
13597
  "",
13598
+ " # Counters for the summary line. The orchestrator reads only",
13599
+ " # TRIAGE_DONE; the per-issue lines below are for log visibility,",
13600
+ " # mirroring unblock-dependents.sh.",
13601
+ " local unblocked_count=0",
13602
+ " local still_blocked_count=0",
13603
+ "",
13561
13604
  " while IFS=$'\\t' read -r num dep_line; do",
13562
13605
  ' [[ -z "$num" ]] && continue',
13563
13606
  "",
13564
13607
  ' if [[ -z "$dep_line" ]]; then',
13565
13608
  ' echo "BLOCKED #${num} \u2014 no Depends on field found"',
13609
+ " still_blocked_count=$((still_blocked_count + 1))",
13566
13610
  " continue",
13567
13611
  " fi",
13568
13612
  "",
13569
13613
  " local deps",
13570
13614
  ' deps=$(parse_deps "$dep_line")',
13571
13615
  ' if [[ -z "${deps// /}" ]]; then',
13572
- ' echo "UNBLOCK #${num} \u2014 no dependencies"',
13616
+ " # Empty dependency list \u2014 treat as eligible for unblocking.",
13617
+ ' if gh issue edit "$num" \\',
13618
+ ' --remove-label "status:blocked" \\',
13619
+ ' --add-label "status:ready" >/dev/null 2>&1; then',
13620
+ ' gh issue comment "$num" \\',
13621
+ ' --body "Dependencies resolved \u2014 unblocking." >/dev/null 2>&1 || true',
13622
+ ' echo "UNBLOCKED #${num} \u2014 no dependencies"',
13623
+ " unblocked_count=$((unblocked_count + 1))",
13624
+ " else",
13625
+ ' echo "UNBLOCK_FAILED #${num} \u2014 label flip failed (no dependencies)"',
13626
+ " still_blocked_count=$((still_blocked_count + 1))",
13627
+ " fi",
13573
13628
  " continue",
13574
13629
  " fi",
13575
13630
  "",
@@ -13586,11 +13641,30 @@ function buildCheckBlockedScript(tiers, scopeGate, runRatio) {
13586
13641
  " done",
13587
13642
  "",
13588
13643
  " if $all_closed; then",
13589
- ' echo "UNBLOCK #${num} \u2014 all deps closed (${closed_deps% })"',
13644
+ " # The label flip is the load-bearing edit; if it fails we must",
13645
+ " # report the failure so the orchestrator does not double-count.",
13646
+ " # The comment is best-effort (its absence is recoverable on the",
13647
+ " # next sweep), so a failed `gh issue comment` does not block the",
13648
+ " # unblock from being recorded as successful.",
13649
+ ' if gh issue edit "$num" \\',
13650
+ ' --remove-label "status:blocked" \\',
13651
+ ' --add-label "status:ready" >/dev/null 2>&1; then',
13652
+ ' gh issue comment "$num" \\',
13653
+ ' --body "Dependencies resolved \u2014 unblocking." >/dev/null 2>&1 || true',
13654
+ ' echo "UNBLOCKED #${num} \u2014 all deps closed (${closed_deps% })"',
13655
+ " unblocked_count=$((unblocked_count + 1))",
13656
+ " else",
13657
+ ' echo "UNBLOCK_FAILED #${num} \u2014 label flip failed (all deps closed: ${closed_deps% })"',
13658
+ " still_blocked_count=$((still_blocked_count + 1))",
13659
+ " fi",
13590
13660
  " else",
13591
- ' echo "BLOCKED #${num} \u2014 waiting on ${open_deps% }"',
13661
+ ' echo "STILL_BLOCKED #${num} \u2014 waiting on ${open_deps% }"',
13662
+ " still_blocked_count=$((still_blocked_count + 1))",
13592
13663
  " fi",
13593
13664
  ' done <<< "$issue_data"',
13665
+ "",
13666
+ " # Single summary line consumed by the orchestrator.",
13667
+ ' echo "TRIAGE_DONE unblocked=${unblocked_count} still_blocked=${still_blocked_count}"',
13594
13668
  "}",
13595
13669
  "",
13596
13670
  "cmd_eligible() {",
@@ -13782,6 +13856,101 @@ function buildCheckBlockedScript(tiers, scopeGate, runRatio) {
13782
13856
  " fi",
13783
13857
  "}",
13784
13858
  "",
13859
+ "cmd_maintenance() {",
13860
+ " # Phase D maintenance sweep: flag stale issues with",
13861
+ " # `status:needs-attention`, count orphan branches/PRs (no",
13862
+ " # auto-deletion), and emit a single MAINTENANCE_DONE summary",
13863
+ " # line so the orchestrator never iterates raw stale/orphan",
13864
+ " # output. Mirrors the discipline of cmd_unblock \u2014 per-issue",
13865
+ " # informational lines (FLAGGED #N, ORPHAN_BRANCH \u2026) survive",
13866
+ " # for log visibility but are not load-bearing for the",
13867
+ " # orchestrator.",
13868
+ " #",
13869
+ " # SAFETY: this subcommand NEVER auto-resets stale issues to",
13870
+ " # `status:ready`. It only adds `status:needs-attention`; the",
13871
+ " # existing `status:in-progress` or `status:blocked` label",
13872
+ " # stays in place so partial implementation work on a branch",
13873
+ " # remains visible to humans.",
13874
+ "",
13875
+ " local flagged_stale_count=0",
13876
+ " local flagged_blocked_count=0",
13877
+ " local orphan_branches_count=0",
13878
+ " local orphan_prs_count=0",
13879
+ "",
13880
+ " # \u2500\u2500 stale detection + flagging \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
13881
+ " local stale_output",
13882
+ " stale_output=$(cmd_stale)",
13883
+ "",
13884
+ " while IFS= read -r line; do",
13885
+ ' [[ -z "$line" ]] && continue',
13886
+ ' case "$line" in',
13887
+ " 'STALE #'*)",
13888
+ " # Extract the issue number after the '#' and before the ' '.",
13889
+ " local num=${line#STALE #}",
13890
+ " num=${num%% *}",
13891
+ ' if gh issue edit "$num" --add-label "status:needs-attention" >/dev/null 2>&1; then',
13892
+ ' gh issue comment "$num" \\',
13893
+ ' --body "Flagged: in-progress for >3 days with no activity." >/dev/null 2>&1 || true',
13894
+ ' echo "FLAGGED_STALE #${num} \u2014 added status:needs-attention"',
13895
+ " flagged_stale_count=$((flagged_stale_count + 1))",
13896
+ " else",
13897
+ ' echo "FLAG_FAILED #${num} \u2014 could not add status:needs-attention (stale)"',
13898
+ " fi",
13899
+ " # Surface the original informational line for log visibility.",
13900
+ ' echo "$line"',
13901
+ " ;;",
13902
+ " 'STALE_BLOCKED #'*)",
13903
+ " local num=${line#STALE_BLOCKED #}",
13904
+ " num=${num%% *}",
13905
+ ' if gh issue edit "$num" --add-label "status:needs-attention" >/dev/null 2>&1; then',
13906
+ ' gh issue comment "$num" \\',
13907
+ ' --body "Flagged: blocked for >7 days \u2014 may need human intervention." >/dev/null 2>&1 || true',
13908
+ ' echo "FLAGGED_BLOCKED #${num} \u2014 added status:needs-attention"',
13909
+ " flagged_blocked_count=$((flagged_blocked_count + 1))",
13910
+ " else",
13911
+ ' echo "FLAG_FAILED #${num} \u2014 could not add status:needs-attention (blocked)"',
13912
+ " fi",
13913
+ ' echo "$line"',
13914
+ " ;;",
13915
+ " 'NO_STALE_ISSUES')",
13916
+ " # Surface the no-op marker for log visibility; counters",
13917
+ " # stay at zero.",
13918
+ ' echo "$line"',
13919
+ " ;;",
13920
+ " esac",
13921
+ ' done <<< "$stale_output"',
13922
+ "",
13923
+ " # \u2500\u2500 orphan detection (count only \u2014 no auto-deletion) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
13924
+ " local orphan_output",
13925
+ " orphan_output=$(cmd_orphaned)",
13926
+ "",
13927
+ " while IFS= read -r line; do",
13928
+ ' [[ -z "$line" ]] && continue',
13929
+ ' case "$line" in',
13930
+ " 'ORPHAN_BRANCH '*)",
13931
+ " orphan_branches_count=$((orphan_branches_count + 1))",
13932
+ ' echo "$line"',
13933
+ " ;;",
13934
+ " 'ORPHAN_PR '*)",
13935
+ " orphan_prs_count=$((orphan_prs_count + 1))",
13936
+ ' echo "$line"',
13937
+ " ;;",
13938
+ " 'NO_ORPHANED_RESOURCES')",
13939
+ ' echo "$line"',
13940
+ " ;;",
13941
+ " esac",
13942
+ ' done <<< "$orphan_output"',
13943
+ "",
13944
+ " # \u2500\u2500 needs-attention total \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
13945
+ " local needs_attention_total",
13946
+ ' needs_attention_total=$(gh issue list --label "status:needs-attention" --state open \\',
13947
+ " --json number --limit 100 2>/dev/null | jq 'length' 2>/dev/null || echo 0)",
13948
+ " needs_attention_total=${needs_attention_total:-0}",
13949
+ "",
13950
+ " # Single summary line consumed by the orchestrator.",
13951
+ ' echo "MAINTENANCE_DONE flagged_stale=${flagged_stale_count} flagged_blocked=${flagged_blocked_count} orphan_branches=${orphan_branches_count} orphan_prs=${orphan_prs_count} needs_attention_total=${needs_attention_total}"',
13952
+ "}",
13953
+ "",
13785
13954
  "cmd_prs() {",
13786
13955
  " local prs",
13787
13956
  " prs=$(gh pr list --state open --json number,title,isDraft,headRefName,labels,body \\",
@@ -13920,31 +14089,18 @@ function buildCheckBlockedScript(tiers, scopeGate, runRatio) {
13920
14089
  " fi",
13921
14090
  "}",
13922
14091
  "",
13923
- "cmd_tick() {",
13924
- " # DEPRECATED: the orchestrator no longer maintains a dispatch /",
13925
- " # housekeeping run-counter \u2014 every invocation runs the full",
13926
- " # end-to-end cycle. This subcommand is retained as a no-op for one",
13927
- " # release so any consumer mid-flight does not error out, then will",
13928
- " # be removed entirely. Existing state files at",
13929
- ' # "$ORCHESTRATOR_STATE_FILE" can be left alone (they will become',
13930
- " # orphaned).",
13931
- ' echo "tick: deprecated no-op (orchestrator runs a single linear cycle every invocation)" >&2',
13932
- ' echo "run=0 type=dispatch (deprecated)"',
13933
- " return 0",
13934
- "}",
13935
- "",
13936
14092
  "# \u2500\u2500 main \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
13937
14093
  "",
13938
14094
  'case "${1:-help}" in',
13939
- ' unblock) shift; cmd_unblock "$@" ;;',
13940
- ' eligible) shift; cmd_eligible "$@" ;;',
13941
- ' stale) shift; cmd_stale "$@" ;;',
13942
- ' orphaned) shift; cmd_orphaned "$@" ;;',
13943
- ' prs) shift; cmd_prs "$@" ;;',
13944
- ' scope) shift; cmd_scope "$@" ;;',
13945
- ' tick) shift; cmd_tick "$@" ;;',
14095
+ ' unblock) shift; cmd_unblock "$@" ;;',
14096
+ ' eligible) shift; cmd_eligible "$@" ;;',
14097
+ ' stale) shift; cmd_stale "$@" ;;',
14098
+ ' orphaned) shift; cmd_orphaned "$@" ;;',
14099
+ ' maintenance) shift; cmd_maintenance "$@" ;;',
14100
+ ' prs) shift; cmd_prs "$@" ;;',
14101
+ ' scope) shift; cmd_scope "$@" ;;',
13946
14102
  " help|*)",
13947
- ' echo "Usage: check-blocked.sh <unblock|eligible|stale|orphaned|prs|scope|tick>"',
14103
+ ' echo "Usage: check-blocked.sh <unblock|eligible|stale|orphaned|maintenance|prs|scope>"',
13948
14104
  " exit 1",
13949
14105
  " ;;",
13950
14106
  "esac"
@@ -13953,7 +14109,7 @@ function buildCheckBlockedScript(tiers, scopeGate, runRatio) {
13953
14109
  function buildCheckBlockedProcedure(tiers, scopeGate = resolveScopeGate(), runRatio = resolveRunRatio()) {
13954
14110
  return {
13955
14111
  name: "check-blocked.sh",
13956
- description: "Token-efficient issue triage script with subcommands: eligible, unblock, stale, orphaned, prs, scope, and (deprecated) tick. Sorts eligible issues by priority desc \u2192 funnel tier asc \u2192 issue number asc; the scope subcommand classifies a single issue against the scope-gate thresholds. The tick subcommand is a deprecation no-op retained for one release (the orchestrator no longer maintains a dispatch/housekeeping run counter).",
14112
+ description: "Token-efficient issue triage script with subcommands: eligible, unblock, stale, orphaned, maintenance, prs, scope. Sorts eligible issues by priority desc \u2192 funnel tier asc \u2192 issue number asc; the scope subcommand classifies a single issue against the scope-gate thresholds; the unblock subcommand applies the `status:blocked` \u2192 `status:ready` label flip itself, posts the canned unblock comment, and emits a single `TRIAGE_DONE unblocked=N still_blocked=M` summary line; the maintenance subcommand flags stale issues with `status:needs-attention` (never auto-resets to `status:ready`), counts orphan branches/PRs, and emits a single `MAINTENANCE_DONE flagged_stale=N flagged_blocked=M orphan_branches=A orphan_prs=B needs_attention_total=T` summary line.",
13957
14113
  content: buildCheckBlockedScript(tiers, scopeGate, runRatio)
13958
14114
  };
13959
14115
  }
@@ -13972,6 +14128,59 @@ function buildUnblockDependentsProcedure(unblockDependents = resolveUnblockDepen
13972
14128
  var unblockDependentsProcedure = buildUnblockDependentsProcedure(
13973
14129
  resolveUnblockDependents()
13974
14130
  );
14131
+ function buildPrSweepScript() {
14132
+ return [
14133
+ "#!/usr/bin/env bash",
14134
+ "# pr-sweep.sh \u2014 Token-efficient PR-eligibility filter for the",
14135
+ "# orchestrator's Phase B PR review sweep. Emits one summary line",
14136
+ "# per open PR; the orchestrator reads only the PR numbers and",
14137
+ "# dispatches `pr-reviewer` per eligible PR.",
14138
+ "#",
14139
+ "# Skip rules:",
14140
+ "# - isDraft = true \u2192 reason=draft",
14141
+ "# - review:human-required label \u2192 reason=human-required",
14142
+ "# - review:awaiting-human label \u2192 reason=awaiting-human",
14143
+ "#",
14144
+ "# Usage:",
14145
+ "# .claude/procedures/pr-sweep.sh",
14146
+ "",
14147
+ "set -uo pipefail",
14148
+ "",
14149
+ "prs=$(gh pr list --state open --json number,isDraft,labels \\",
14150
+ ' --limit 50 2>/dev/null || echo "[]")',
14151
+ "",
14152
+ `count=$(echo "$prs" | jq 'length')`,
14153
+ 'if [[ "$count" -eq 0 ]]; then',
14154
+ ' echo "NO_OPEN_PRS"',
14155
+ " exit 0",
14156
+ "fi",
14157
+ "",
14158
+ "# Emit one PR_ELIGIBLE / PR_SKIP line per open PR, sorted by PR",
14159
+ "# number ascending so the order is deterministic across runs.",
14160
+ `echo "$prs" | jq -r '`,
14161
+ " sort_by(.number) |",
14162
+ " .[] |",
14163
+ " (.labels | map(.name)) as $labels |",
14164
+ " if .isDraft then",
14165
+ ' "PR_SKIP #\\(.number) reason=draft"',
14166
+ ' elif ($labels | index("review:human-required")) then',
14167
+ ' "PR_SKIP #\\(.number) reason=human-required"',
14168
+ ' elif ($labels | index("review:awaiting-human")) then',
14169
+ ' "PR_SKIP #\\(.number) reason=awaiting-human"',
14170
+ " else",
14171
+ ' "PR_ELIGIBLE #\\(.number)"',
14172
+ " end",
14173
+ "'"
14174
+ ].join("\n");
14175
+ }
14176
+ function buildPrSweepProcedure() {
14177
+ return {
14178
+ name: "pr-sweep.sh",
14179
+ description: "Token-efficient PR-eligibility filter for the orchestrator's Phase B PR review sweep. Emits one `PR_ELIGIBLE #<n>` or `PR_SKIP #<n> reason=<draft|human-required|awaiting-human>` line per open PR; the orchestrator reads only the eligible numbers and dispatches `pr-reviewer` per eligible PR.",
14180
+ content: buildPrSweepScript()
14181
+ };
14182
+ }
14183
+ var prSweepProcedure = buildPrSweepProcedure();
13975
14184
  var orchestratorSubAgent = {
13976
14185
  name: "orchestrator",
13977
14186
  description: "End-to-end pipeline manager that runs one full cycle every invocation: triage \u2192 maintenance \u2192 queue scan \u2192 delegate the picked issue to the issue-worker \u2192 cleanup",
@@ -13982,13 +14191,16 @@ var orchestratorSubAgent = {
13982
14191
  "# Orchestrator Agent",
13983
14192
  "",
13984
14193
  "You are the pipeline orchestrator for the **{{repository.owner}}/{{repository.name}}** repository.",
13985
- "Each invocation runs **one full end-to-end cycle**. The cycle triages",
13986
- "blocked issues, runs maintenance scans, picks the next ready issue, and",
13987
- "**delegates implementation to the `issue-worker`** sub-agent in scheduled",
13988
- "mode. The orchestrator itself never implements code, creates branches, or",
13989
- "pushes commits \u2014 it routes work to other agents. Approved-PR merging is",
13990
- "owned by the `pr-reviewer` sub-agent (invoked via `/review-pr` /",
13991
- "`/review-prs`); the orchestrator does not run a merge sweep.",
14194
+ "Each invocation runs **one full end-to-end cycle**. The cycle pulls",
14195
+ "the default branch, sweeps eligible open PRs through the `pr-reviewer`",
14196
+ "sub-agent, triages blocked issues, runs maintenance scans, picks the",
14197
+ "next ready issue, and **delegates implementation to the `issue-worker`**",
14198
+ "sub-agent in scheduled mode. The orchestrator itself never implements",
14199
+ "code, creates branches, pushes commits, or merges PRs \u2014 it routes",
14200
+ "work to other agents. The merge decision is still owned by the",
14201
+ "`pr-reviewer` sub-agent; the orchestrator only chooses which PRs the",
14202
+ "reviewer should look at and dispatches one reviewer session per",
14203
+ "eligible PR.",
13992
14204
  "",
13993
14205
  "Run the phases below in order on every invocation. There is no",
13994
14206
  "dispatch/housekeeping fork \u2014 every phase runs every time.",
@@ -13996,16 +14208,20 @@ var orchestratorSubAgent = {
13996
14208
  "Phase ordering (each runs exactly once per invocation):",
13997
14209
  "",
13998
14210
  "1. **Phase A \u2014 Startup.** Pull the default branch.",
13999
- "2. **Phase C \u2014 Triage / Unblock.** Flip dependents that have all",
14211
+ "2. **Phase B \u2014 PR Review Sweep.** Read the procedure-script's",
14212
+ " eligibility summary and dispatch the `pr-reviewer` sub-agent",
14213
+ " in scheduled mode against each eligible open PR. Failures on",
14214
+ " one PR never stop the sweep.",
14215
+ "3. **Phase C \u2014 Triage / Unblock.** Flip dependents that have all",
14000
14216
  " their dependencies closed back to `status:ready`.",
14001
- "3. **Phase D \u2014 Maintenance.** Stale-issue and orphaned-resource scan",
14217
+ "4. **Phase D \u2014 Maintenance.** Stale-issue and orphaned-resource scan",
14002
14218
  " plus a needs-attention summary.",
14003
- "4. **Phase E \u2014 Queue Scan.** Pick the highest-priority `PICK` line",
14219
+ "5. **Phase E \u2014 Queue Scan.** Pick the highest-priority `PICK` line",
14004
14220
  " and run the scope gate. If the result is `large`, flag and stop.",
14005
- "5. **Phase G \u2014 Delegate Implementation.** Hand the picked issue off",
14221
+ "6. **Phase G \u2014 Delegate Implementation.** Hand the picked issue off",
14006
14222
  " to the `issue-worker` sub-agent in scheduled mode. The worker",
14007
14223
  " handles claim \u2192 branch \u2192 implement \u2192 commit \u2192 PR autonomously.",
14008
- "6. **Phase F \u2014 Cleanup.** Return to the default branch and log the",
14224
+ "7. **Phase F \u2014 Cleanup.** Return to the default branch and log the",
14009
14225
  " run summary.",
14010
14226
  "",
14011
14227
  "Phase letters are stable identifiers \u2014 letters skipped in the list",
@@ -14024,76 +14240,160 @@ var orchestratorSubAgent = {
14024
14240
  "git checkout main && git pull origin main",
14025
14241
  "```",
14026
14242
  "",
14027
- "## Phase C: Triage \u2014 Unblock",
14243
+ "## Phase B: PR Review Sweep",
14244
+ "",
14245
+ "Sweep every open PR through the `pr-reviewer` sub-agent before the",
14246
+ "queue scan so a single `/orchestrate` invocation merges ready PRs",
14247
+ "and dispatches new issue work in one pass.",
14248
+ "",
14249
+ "### Step 1 \u2014 read the eligibility summary",
14028
14250
  "",
14029
- "Check for blocked issues whose dependencies have resolved:",
14251
+ "Run the bundled `pr-sweep.sh` procedure and read **only** the",
14252
+ "summary lines it emits. Do **not** iterate raw `gh pr list` JSON",
14253
+ "yourself \u2014 the procedure is the single source of PR-eligibility",
14254
+ "metadata, mirroring the discipline of Phase E's bucketed queue scan.",
14030
14255
  "",
14031
14256
  "```bash",
14032
- ".claude/procedures/check-blocked.sh unblock",
14257
+ ".claude/procedures/pr-sweep.sh",
14033
14258
  "```",
14034
14259
  "",
14035
- "This phase is the **fallback safety net** for agent-driven",
14036
- "unblocking. Every agent that applies `status:done` already runs",
14037
- "`.claude/procedures/unblock-dependents.sh <n>` as part of that",
14038
- "transition, so in steady state Phase C should usually report",
14039
- "`NO_BLOCKED_ISSUES` or leave dependents untouched. Phase C still",
14040
- "runs on every cycle so issues that slipped through \u2014 human",
14041
- "closures, manual `status:done` edits, or crashes mid-sweep \u2014 get",
14042
- "picked up within one cycle. See the **Agent-driven",
14043
- "unblocking** section in `CLAUDE.md` for the per-agent contract.",
14260
+ "Each line follows one of these shapes:",
14044
14261
  "",
14045
- "For each `UNBLOCK #N` line:",
14046
- "```bash",
14047
- 'gh issue edit N --remove-label "status:blocked" --add-label "status:ready"',
14048
- 'gh issue comment N --body "Dependencies resolved \u2014 unblocking."',
14262
+ "```",
14263
+ "PR_ELIGIBLE #<n>",
14264
+ "PR_SKIP #<n> reason=draft",
14265
+ "PR_SKIP #<n> reason=human-required",
14266
+ "PR_SKIP #<n> reason=awaiting-human",
14049
14267
  "```",
14050
14268
  "",
14051
- "For `BLOCKED #N \u2014 no Depends on field found`: leave as-is (Phase D will",
14052
- "catch it if it's been blocked too long).",
14269
+ "Skip rules (PRs that never reach the reviewer in this sweep):",
14053
14270
  "",
14054
- "If output is `NO_BLOCKED_ISSUES`, continue to Phase D.",
14271
+ "- `isDraft: true` \u2192 `reason=draft` (the author has not yet asked for review)",
14272
+ "- Carries the `review:human-required` label \u2192 `reason=human-required`",
14273
+ " (operator override \u2014 the reviewer's auto-merge path is forbidden)",
14274
+ "- Carries the `review:awaiting-human` label \u2192 `reason=awaiting-human`",
14275
+ " (the reviewer already handed off; a human is the next actor)",
14055
14276
  "",
14056
- "## Phase D: Maintenance",
14277
+ "If the script emits `NO_OPEN_PRS`, log the empty result and skip",
14278
+ "directly to Phase C.",
14057
14279
  "",
14058
- "### D1: Stale Detection",
14280
+ "### Step 2 \u2014 dispatch the reviewer per eligible PR",
14059
14281
  "",
14060
- "```bash",
14061
- ".claude/procedures/check-blocked.sh stale",
14062
- "```",
14282
+ "For each `PR_ELIGIBLE #<n>` line in the order the procedure",
14283
+ "emitted them, invoke the `pr-reviewer` sub-agent **in scheduled",
14284
+ "mode** with the brief below. Substitute `<n>` with the PR number",
14285
+ "from the line.",
14286
+ "",
14287
+ "> `scheduled mode`: review PR #<n>. Run the full review pipeline",
14288
+ "> (eligibility, AC checklist, CI status, policy decision, sticky",
14289
+ "> reviewer-notes update) and either enable squash auto-merge or",
14290
+ "> apply `review:awaiting-human`. Do not pause for approval.",
14291
+ "> Return a one-line summary as the final line of your response in",
14292
+ "> the form `REVIEWER_DONE pr:#<n> outcome:<merged|awaiting-human|commented>`",
14293
+ "> on success or `REVIEWER_FAILED pr:#<n> reason:<short>` on failure.",
14294
+ "",
14295
+ "The orchestrator does **not** call `gh pr merge`, apply review",
14296
+ "labels, or post review comments directly \u2014 the reviewer's existing",
14297
+ "logic owns merge dispatch, AC verification, and the sticky",
14298
+ "`## Reviewer notes` comment. Phase B is a thin invoker, not an",
14299
+ "absorber.",
14300
+ "",
14301
+ "### Step 3 \u2014 failure semantics",
14302
+ "",
14303
+ "A single PR's reviewer failure (missing or malformed",
14304
+ "`REVIEWER_*` final line, sub-agent error, CI flake) **never stops",
14305
+ "the sweep**. Log the failure as",
14306
+ "`REVIEWER_FAILED pr:#<n> reason:<observed>` and continue to the",
14307
+ "next eligible PR. The next orchestrator invocation re-scans the",
14308
+ "PR list and re-dispatches the reviewer if the PR is still",
14309
+ "eligible.",
14310
+ "",
14311
+ "After every eligible PR has been processed (or zero eligible PRs",
14312
+ "were emitted), continue to Phase C regardless of outcomes.",
14313
+ "",
14314
+ "## Phase C: Triage \u2014 Unblock",
14315
+ "",
14316
+ "Run the bundled `check-blocked.sh unblock` procedure and read",
14317
+ "**only** the final `TRIAGE_DONE` summary line it emits. The",
14318
+ "procedure applies the label flips (`status:blocked` \u2192",
14319
+ "`status:ready`) and posts the canned",
14320
+ "`Dependencies resolved \u2014 unblocking.` comment itself, mirroring",
14321
+ "the discipline of Phase B (`pr-sweep.sh`) and Phase E",
14322
+ "(`check-blocked.sh eligible`) \u2014 the orchestrator never iterates",
14323
+ "raw `gh` responses, only the procedure's summary lines.",
14063
14324
  "",
14064
- "For each `STALE #N` line (in-progress >72h without activity):",
14065
14325
  "```bash",
14066
- 'gh issue edit N --add-label "status:needs-attention"',
14067
- 'gh issue comment N --body "Flagged: in-progress for >3 days with no activity."',
14326
+ ".claude/procedures/check-blocked.sh unblock",
14068
14327
  "```",
14069
14328
  "",
14070
- "For each `STALE_BLOCKED #N` line (blocked >168h):",
14071
- "```bash",
14072
- 'gh issue edit N --add-label "status:needs-attention"',
14073
- 'gh issue comment N --body "Flagged: blocked for >7 days \u2014 may need human intervention."',
14329
+ "The script emits one summary line in this shape:",
14330
+ "",
14074
14331
  "```",
14332
+ "TRIAGE_DONE unblocked=<N> still_blocked=<M>",
14333
+ "```",
14334
+ "",
14335
+ "Per-issue informational lines (`UNBLOCKED #N`, `UNBLOCK_FAILED #N`,",
14336
+ "`STILL_BLOCKED #N`, `BLOCKED #N \u2014 no Depends on field found`) are",
14337
+ "emitted for log visibility but are **not** load-bearing for the",
14338
+ "orchestrator \u2014 partial failures (one bad `gh` call) do not abort",
14339
+ "the sweep, they are simply counted toward `still_blocked`.",
14340
+ "",
14341
+ "This phase is the **fallback safety net** for agent-driven",
14342
+ "unblocking. Every agent that applies `status:done` already runs",
14343
+ "`.claude/procedures/unblock-dependents.sh <n>` as part of that",
14344
+ "transition, so in steady state Phase C should usually report",
14345
+ "`TRIAGE_DONE unblocked=0 still_blocked=0` or leave dependents",
14346
+ "untouched. Phase C still runs on every cycle so issues that",
14347
+ "slipped through \u2014 human closures, manual `status:done` edits, or",
14348
+ "crashes mid-sweep \u2014 get picked up within one cycle. See the",
14349
+ "**Agent-driven unblocking** section in `CLAUDE.md` for the",
14350
+ "per-agent contract.",
14351
+ "",
14352
+ "Log the summary line and continue to Phase D regardless of",
14353
+ "outcomes \u2014 a non-zero `still_blocked` count is normal (issues",
14354
+ "with open dependencies stay blocked) and Phase D will surface",
14355
+ "any that have been blocked too long.",
14075
14356
  "",
14076
- "**Important:** Do NOT auto-reset stale issues to `status:ready` \u2014 partial",
14077
- "implementation work may exist on a branch.",
14357
+ "## Phase D: Maintenance",
14078
14358
  "",
14079
- "### D2: Orphaned Detection",
14359
+ "Run the bundled `check-blocked.sh maintenance` procedure and read",
14360
+ "**only** the final `MAINTENANCE_DONE` summary line it emits. The",
14361
+ "procedure folds the stale-detection, orphan-detection, and",
14362
+ "needs-attention summary that earlier revisions split across D1,",
14363
+ "D2, and D3 into a single sweep \u2014 applying the",
14364
+ "`status:needs-attention` label and posting the canned flag comment",
14365
+ "for each stale / stale-blocked issue itself, mirroring the",
14366
+ "discipline of Phase B (`pr-sweep.sh`) and Phase C",
14367
+ "(`check-blocked.sh unblock`).",
14080
14368
  "",
14081
14369
  "```bash",
14082
- ".claude/procedures/check-blocked.sh orphaned",
14370
+ ".claude/procedures/check-blocked.sh maintenance",
14083
14371
  "```",
14084
14372
  "",
14085
- "Report any `ORPHAN_BRANCH` or `ORPHAN_PR` lines. These indicate branches",
14086
- "or PRs whose linked issues are closed or missing. Log them for visibility",
14087
- "but do not delete branches automatically.",
14088
- "",
14089
- "### D3: Needs-Attention Summary",
14373
+ "The script emits one summary line in this shape:",
14090
14374
  "",
14091
- "List all issues currently flagged:",
14092
- "```bash",
14093
- 'gh issue list --label "status:needs-attention" --state open --json number,title',
14375
+ "```",
14376
+ "MAINTENANCE_DONE flagged_stale=<N> flagged_blocked=<M> orphan_branches=<A> orphan_prs=<B> needs_attention_total=<T>",
14094
14377
  "```",
14095
14378
  "",
14096
- "Log the count and titles for operator visibility.",
14379
+ "Per-issue / per-orphan informational lines (`FLAGGED_STALE #N`,",
14380
+ "`FLAGGED_BLOCKED #N`, `STALE #N \u2014 \u2026`, `STALE_BLOCKED #N \u2014 \u2026`,",
14381
+ "`ORPHAN_BRANCH \u2026`, `ORPHAN_PR #N \u2014 \u2026`, `FLAG_FAILED #N \u2014 \u2026`) are",
14382
+ "emitted for log visibility but are **not** load-bearing for the",
14383
+ "orchestrator \u2014 partial failures (one bad `gh` call) do not abort",
14384
+ "the sweep, they are simply omitted from the `flagged_*` counters.",
14385
+ "",
14386
+ "**Important:** the script never auto-resets stale issues to",
14387
+ "`status:ready`. It only adds `status:needs-attention`; the",
14388
+ "existing `status:in-progress` or `status:blocked` label stays in",
14389
+ "place so partial implementation work on a branch remains visible",
14390
+ "to humans. Orphan branches and PRs are surfaced in the counters",
14391
+ "but are never deleted by the orchestrator \u2014 a human reviews the",
14392
+ "log lines and prunes orphans manually.",
14393
+ "",
14394
+ "Log the summary line and continue to Phase E regardless of",
14395
+ "outcomes \u2014 a non-zero `flagged_*` or `orphan_*` count is",
14396
+ "informational, not a failure.",
14097
14397
  "",
14098
14398
  "## Phase E: Queue Scan",
14099
14399
  "",
@@ -14253,27 +14553,30 @@ var orchestratorSubAgent = {
14253
14553
  "",
14254
14554
  "1. **Never implement code.** You triage, scan, and delegate \u2014 you do not code.",
14255
14555
  "2. **Never claim issues.** Do not add `status:in-progress` or create branches for issues. Phase G's `issue-worker` delegation is the only path that flips an issue's claim labels.",
14256
- "3. **Never merge PRs.** Approved-PR merging is owned by the `pr-reviewer` sub-agent (invoked via `/review-pr` / `/review-prs`). The orchestrator does not run a merge sweep, does not call `gh pr merge`, and does not enable auto-merge.",
14257
- "4. **Always use check-blocked.sh.** All triage queries go through the shell script for token efficiency.",
14258
- "5. **Follow CLAUDE.md conventions** for all git and gh operations.",
14259
- "6. **Priority order:** critical > high > medium > low > trivial, then **funnel tier asc** (lower tier wins ties), then FIFO by issue number.",
14260
- "7. **Never dispatch a `large` issue.** Always run the scope check",
14556
+ "3. **Always use procedure scripts.** Issue triage queries go through `.claude/procedures/check-blocked.sh`; PR-eligibility filtering goes through `.claude/procedures/pr-sweep.sh`. Never iterate raw `gh issue list` / `gh pr list` JSON in-context.",
14557
+ "4. **Follow CLAUDE.md conventions** for all git and gh operations.",
14558
+ "5. **Priority order:** critical > high > medium > low > trivial, then **funnel tier asc** (lower tier wins ties), then FIFO by issue number.",
14559
+ "6. **Never dispatch a `large` issue.** Always run the scope check",
14261
14560
  " on the top `PICK` line before reporting `NEXT_WORK_ITEM`. A",
14262
14561
  " `large` issue must be flagged with `status:needs-attention` and",
14263
14562
  " handed a decomposition proposal \u2014 never claimed, never branched,",
14264
14563
  " never delegated to the `issue-worker`.",
14265
- "8. **Sweep dependents whenever you apply `status:done`.** Every",
14564
+ "7. **Sweep dependents whenever you apply `status:done`.** Every",
14266
14565
  " `status:done` transition must be immediately followed by",
14267
14566
  " `.claude/procedures/unblock-dependents.sh <n>` so downstream",
14268
14567
  " `Depends on: #<n>` issues flip to `status:ready` without",
14269
14568
  " waiting for the next cycle. See the **Agent-driven unblocking**",
14270
14569
  " section in `CLAUDE.md` for the contract.",
14271
- "9. **Always invoke `issue-worker` in scheduled mode.** Phase G's",
14272
- " delegation prompt must contain the literal phrase",
14273
- " `scheduled mode` so the worker skips its interactive approval",
14274
- " pause and runs the implementation cycle autonomously. Without",
14275
- " the phrase the worker pauses for human approval and the",
14276
- " delegation stalls."
14570
+ "8. **Always invoke child sub-agents in scheduled mode.** Phase B's",
14571
+ " `pr-reviewer` dispatch and Phase G's `issue-worker` delegation",
14572
+ " prompts must each contain the literal phrase `scheduled mode`",
14573
+ " so the child sub-agent skips its interactive approval pause and",
14574
+ " runs autonomously. Without the phrase the child pauses for",
14575
+ " human approval and the delegation stalls.",
14576
+ "9. **A failed reviewer never stops the sweep.** Phase B continues",
14577
+ " to the next eligible PR after a `REVIEWER_FAILED` line and",
14578
+ " proceeds to Phase C regardless of how many PRs failed. Do not",
14579
+ " abort the cycle on a single PR's reviewer failure."
14277
14580
  ].join("\n")
14278
14581
  };
14279
14582
  var issueWorkerSubAgent = {
@@ -14748,9 +15051,9 @@ var ORCHESTRATOR_CONVENTIONS_PREAMBLE = [
14748
15051
  "",
14749
15052
  "When running the orchestrator agent (`.claude/agents/orchestrator.md`):",
14750
15053
  "",
14751
- "- The orchestrator runs **one full end-to-end cycle every invocation**: triage / unblock \u2192 maintenance \u2192 queue scan \u2192 delegate to `issue-worker` \u2192 cleanup",
14752
- "- The orchestrator **never** implements code, creates branches, pushes commits, **or merges PRs** \u2014 it triages issues, picks the next work item, and delegates implementation to other sub-agents. Approved-PR merging is owned by the `pr-reviewer` sub-agent (invoked via `/review-pr` / `/review-prs`).",
14753
- "- All triage queries use `.claude/procedures/check-blocked.sh` for token efficiency",
15054
+ "- The orchestrator runs **one full end-to-end cycle every invocation**: startup \u2192 PR review sweep \u2192 triage / unblock \u2192 maintenance \u2192 queue scan \u2192 delegate to `issue-worker` \u2192 cleanup",
15055
+ "- The orchestrator **never** implements code, creates branches, or pushes commits \u2014 it routes work to other sub-agents. The merge decision is still owned by the `pr-reviewer` sub-agent; Phase B only chooses which open PRs the reviewer should look at and dispatches one reviewer session per eligible PR (skipping drafts and any PR carrying `review:human-required` or `review:awaiting-human`).",
15056
+ "- All triage queries use `.claude/procedures/check-blocked.sh` for token efficiency. PR-eligibility filtering for Phase B uses `.claude/procedures/pr-sweep.sh`, which emits `PR_ELIGIBLE` / `PR_SKIP` lines so the orchestrator never iterates raw `gh pr list` JSON.",
14754
15057
  "- The queue scan reads only `priority:*` and `status:*` labels \u2014 type-routing (which typed agent handles a given `type:*` label) is the `issue-worker`'s concern, not the orchestrator's. The orchestrator's funnel-tier sort is a tie-breaker on `priority:*`, not a routing decision.",
14755
15058
  "- Priority order: critical > high > medium > low > trivial, then **funnel tier asc** (lower tier wins ties), then FIFO by issue number. Phase E's queue scan walks each priority bucket in turn (one `gh issue list` call per bucket, **capped at 50 issues per bucket**) and short-circuits on the first bucket whose survivors clear the `Depends on:` filter, so every higher-priority issue is visible even when the global ready backlog is much larger than 50.",
14756
15059
  "- Stale thresholds: 72h for in-progress, 168h for blocked",
@@ -14758,11 +15061,11 @@ var ORCHESTRATOR_CONVENTIONS_PREAMBLE = [
14758
15061
  "",
14759
15062
  "## Depth-0 invocation requirement",
14760
15063
  "",
14761
- 'The orchestrator agent **must run as the top-level (depth-0) session** when its Phase G needs to delegate work to the `issue-worker` sub-agent. The Claude Code harness forbids nested sub-agent spawning \u2014 *"Subagents cannot spawn other subagents. If your workflow requires nested delegation, use Skills or chain subagents from the main conversation."* (see <https://code.claude.com/docs/en/sub-agents>). If the orchestrator itself were spawned as a depth-1 sub-agent (e.g. via `Agent(subagent_type: "orchestrator")` from another session), the `Agent` tool needed to reach the `issue-worker` at depth-2 would not be available and Phase G would silently abort.',
15064
+ 'The orchestrator agent **must run as the top-level (depth-0) session** because both Phase B (PR review sweep) and Phase G (issue delegation) need to spawn child sub-agents. Phase B dispatches the `pr-reviewer` sub-agent once per eligible open PR; Phase G dispatches the `issue-worker` sub-agent against the picked issue. The Claude Code harness forbids nested sub-agent spawning \u2014 *"Subagents cannot spawn other subagents. If your workflow requires nested delegation, use Skills or chain subagents from the main conversation."* (see <https://code.claude.com/docs/en/sub-agents>). If the orchestrator itself were spawned as a depth-1 sub-agent (e.g. via `Agent(subagent_type: "orchestrator")` from another session), the `Agent` tool needed to reach `pr-reviewer` and `issue-worker` at depth-2 would not be available and both Phase B and Phase G would silently abort.',
14762
15065
  "",
14763
- 'Practical implication for scheduled-task wiring: the `worker-orchestrator` scheduled task\'s `SKILL.md` instructs the **scheduled-task session itself** to read `.claude/agents/orchestrator.md` and execute its phase pipeline in-session, then use the `Agent` tool to delegate the picked work item to `issue-worker` (depth-1). The scheduled task does **not** call `Agent(subagent_type: "orchestrator")` \u2014 that would put the orchestrator at depth-1 and break the chain.'
15066
+ 'Practical implication for scheduled-task wiring: the `worker-orchestrator` scheduled task\'s `SKILL.md` instructs the **scheduled-task session itself** to read `.claude/agents/orchestrator.md` and execute its phase pipeline in-session, then use the `Agent` tool to delegate to `pr-reviewer` (Phase B) and `issue-worker` (Phase G) at depth-1. The scheduled task does **not** call `Agent(subagent_type: "orchestrator")` \u2014 that would put the orchestrator at depth-1 and break both delegation chains.'
14764
15067
  ].join("\n");
14765
- function buildOrchestratorConventionsContent(tiers, scopeGate = resolveScopeGate(), runRatio = resolveRunRatio(), scheduledTasks = resolveScheduledTasks(), unblockDependents = resolveUnblockDependents(), excludeBundles = []) {
15068
+ function buildOrchestratorConventionsContent(tiers, scopeGate = resolveScopeGate(), _runRatio = resolveRunRatio(), scheduledTasks = resolveScheduledTasks(), unblockDependents = resolveUnblockDependents(), excludeBundles = []) {
14766
15069
  return [
14767
15070
  ORCHESTRATOR_CONVENTIONS_PREAMBLE,
14768
15071
  "",
@@ -14770,8 +15073,6 @@ function buildOrchestratorConventionsContent(tiers, scopeGate = resolveScopeGate
14770
15073
  "",
14771
15074
  renderScopeGateSection(scopeGate, excludeBundles),
14772
15075
  "",
14773
- renderRunRatioSection(runRatio),
14774
- "",
14775
15076
  renderScheduledTasksSection(scheduledTasks),
14776
15077
  "",
14777
15078
  renderUnblockDependentsSection(unblockDependents)
@@ -14840,13 +15141,18 @@ var checkBlockedCommand = {
14840
15141
  'bash .claude/procedures/check-blocked.sh "$@"',
14841
15142
  "```",
14842
15143
  "",
14843
- "Pass any extra arguments through verbatim. The script walks every",
14844
- "`status:blocked` issue, re-checks each `Depends on:` reference, and",
14845
- "prints `UNBLOCKED` / `STILL_BLOCKED` lines for issues that should",
14846
- "transition to `status:ready`.",
14847
- "",
14848
- "Summarise the output (counts, the first few candidates) \u2014 do **not**",
14849
- "transition labels yourself; that is the orchestrator's responsibility.",
15144
+ "Pass any extra arguments through verbatim. The `unblock` subcommand",
15145
+ "walks every `status:blocked` issue, re-checks each `Depends on:`",
15146
+ "reference, applies the `status:blocked` \u2192 `status:ready` label flip",
15147
+ "itself, posts the canned `Dependencies resolved \u2014 unblocking.`",
15148
+ "comment, and emits a single",
15149
+ "`TRIAGE_DONE unblocked=<N> still_blocked=<M>` summary line. Per-issue",
15150
+ "informational lines (`UNBLOCKED`, `UNBLOCK_FAILED`, `STILL_BLOCKED`)",
15151
+ "are also emitted for log visibility.",
15152
+ "",
15153
+ "Summarise the output (the `TRIAGE_DONE` counts and the first few",
15154
+ "per-issue lines) \u2014 do **not** apply additional label flips yourself;",
15155
+ "the script already transitioned every eligible issue.",
14850
15156
  ""
14851
15157
  ].join("\n")
14852
15158
  };
@@ -14896,7 +15202,11 @@ var orchestratorBundle = {
14896
15202
  }
14897
15203
  ],
14898
15204
  subAgents: [orchestratorSubAgent, issueWorkerSubAgent],
14899
- procedures: [checkBlockedProcedure, unblockDependentsProcedure],
15205
+ procedures: [
15206
+ checkBlockedProcedure,
15207
+ unblockDependentsProcedure,
15208
+ prSweepProcedure
15209
+ ],
14900
15210
  commands: [orchestrateCommand, checkBlockedCommand, scanCommand],
14901
15211
  claudePermissions: {
14902
15212
  allow: [
@@ -27964,7 +28274,8 @@ function renderPriorityRulesSection(rules) {
27964
28274
  }
27965
28275
 
27966
28276
  // src/agent/bundles/index.ts
27967
- function buildBuiltInBundles(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS, defaultAgentTier = AGENT_MODEL.BALANCED) {
28277
+ function buildBuiltInBundles(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS, defaultAgentTier = AGENT_MODEL.BALANCED, bundleAgentTiers = /* @__PURE__ */ new Map()) {
28278
+ const tierFor = (bundle) => bundleAgentTiers.get(bundle) ?? defaultAgentTier;
27968
28279
  return [
27969
28280
  buildBaseBundle(paths),
27970
28281
  upstreamConfigulatorDocsBundle,
@@ -27977,7 +28288,7 @@ function buildBuiltInBundles(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAUL
27977
28288
  projenBundle,
27978
28289
  githubWorkflowBundle,
27979
28290
  slackBundle,
27980
- buildMeetingAnalysisBundle(defaultAgentTier),
28291
+ buildMeetingAnalysisBundle(tierFor("meeting-analysis")),
27981
28292
  agendaBundle,
27982
28293
  orchestratorBundle,
27983
28294
  prReviewBundle,
@@ -27985,16 +28296,28 @@ function buildBuiltInBundles(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAUL
27985
28296
  buildRequirementsWriterBundle(paths, issueDefaults),
27986
28297
  buildRequirementsReviewerBundle(paths, issueDefaults),
27987
28298
  buildResearchPipelineBundle(paths, issueDefaults),
27988
- buildCompanyProfileBundle(paths, issueDefaults, defaultAgentTier),
27989
- buildCustomerProfileBundle(paths, issueDefaults, defaultAgentTier),
27990
- buildPeopleProfileBundle(paths, issueDefaults, defaultAgentTier),
27991
- buildSoftwareProfileBundle(paths, issueDefaults, defaultAgentTier),
28299
+ buildCompanyProfileBundle(paths, issueDefaults, tierFor("company-profile")),
28300
+ buildCustomerProfileBundle(
28301
+ paths,
28302
+ issueDefaults,
28303
+ tierFor("customer-profile")
28304
+ ),
28305
+ buildPeopleProfileBundle(paths, issueDefaults, tierFor("people-profile")),
28306
+ buildSoftwareProfileBundle(
28307
+ paths,
28308
+ issueDefaults,
28309
+ tierFor("software-profile")
28310
+ ),
27992
28311
  buildIndustryDiscoveryBundle(paths, issueDefaults),
27993
28312
  buildBusinessModelsBundle(paths, issueDefaults),
27994
28313
  buildBcmWriterBundle(paths, issueDefaults),
27995
28314
  buildStandardsResearchBundle(paths, issueDefaults),
27996
28315
  buildRegulatoryResearchBundle(paths, issueDefaults),
27997
- buildMaintenanceAuditBundle(paths, issueDefaults, defaultAgentTier),
28316
+ buildMaintenanceAuditBundle(
28317
+ paths,
28318
+ issueDefaults,
28319
+ tierFor("maintenance-audit")
28320
+ ),
27998
28321
  buildDocsSyncBundle(paths)
27999
28322
  ];
28000
28323
  }
@@ -28629,8 +28952,8 @@ var CopilotRenderer = class {
28629
28952
  };
28630
28953
 
28631
28954
  // src/agent/agent-config.ts
28632
- function resolveDefaultAgentTier(options) {
28633
- switch (options?.defaultAgentTier) {
28955
+ function mapAgentTier(value) {
28956
+ switch (value) {
28634
28957
  case "powerful":
28635
28958
  return AGENT_MODEL.POWERFUL;
28636
28959
  case "fast":
@@ -28640,6 +28963,34 @@ function resolveDefaultAgentTier(options) {
28640
28963
  return AGENT_MODEL.BALANCED;
28641
28964
  }
28642
28965
  }
28966
+ function resolveDefaultAgentTier(options) {
28967
+ return mapAgentTier(options?.defaultAgentTier);
28968
+ }
28969
+ var TIER_AWARE_BUNDLE_NAMES = [
28970
+ "company-profile",
28971
+ "customer-profile",
28972
+ "maintenance-audit",
28973
+ "meeting-analysis",
28974
+ "people-profile",
28975
+ "software-profile"
28976
+ ];
28977
+ function resolveBundleAgentTiers(options) {
28978
+ const overrides = options?.bundleAgentTiers;
28979
+ if (!overrides) {
28980
+ return /* @__PURE__ */ new Map();
28981
+ }
28982
+ const validNames = new Set(TIER_AWARE_BUNDLE_NAMES);
28983
+ const out = /* @__PURE__ */ new Map();
28984
+ for (const [name, value] of Object.entries(overrides)) {
28985
+ if (!validNames.has(name)) {
28986
+ throw new Error(
28987
+ `bundleAgentTiers contains unknown bundle name "${name}". Valid names are: ${TIER_AWARE_BUNDLE_NAMES.join(", ")}.`
28988
+ );
28989
+ }
28990
+ out.set(name, mapAgentTier(value));
28991
+ }
28992
+ return out;
28993
+ }
28643
28994
  var DEFAULT_CLAUDE_ALLOW = [
28644
28995
  // ── Git ──────────────────────────────────────────────────────────────
28645
28996
  "Bash(git add *)",
@@ -28992,7 +29343,8 @@ var AgentConfig = class _AgentConfig extends import_projen8.Component {
28992
29343
  this.cachedBundles = buildBuiltInBundles(
28993
29344
  this.resolvedPaths,
28994
29345
  resolveIssueDefaults(this.options.issueDefaults),
28995
- resolveDefaultAgentTier(this.options)
29346
+ resolveDefaultAgentTier(this.options),
29347
+ resolveBundleAgentTiers(this.options)
28996
29348
  );
28997
29349
  }
28998
29350
  return this.cachedBundles;
@@ -34246,6 +34598,7 @@ var TypeScriptConfig = class extends import_projen24.Component {
34246
34598
  SUPPRESSED_WORKFLOW_RULE_NAMES,
34247
34599
  SampleLang,
34248
34600
  StarlightProject,
34601
+ TIER_AWARE_BUNDLE_NAMES,
34249
34602
  TestRunner,
34250
34603
  TsDocCoverageKind,
34251
34604
  TurboRepo,
@@ -34378,6 +34731,7 @@ var TypeScriptConfig = class extends import_projen24.Component {
34378
34731
  resolveAgentTiers,
34379
34732
  resolveAstroProjectOutdir,
34380
34733
  resolveAwsCdkProjectOutdir,
34734
+ resolveBundleAgentTiers,
34381
34735
  resolveDefaultAgentTier,
34382
34736
  resolveIssueDefaults,
34383
34737
  resolveIssueTemplates,