@codedrifters/configulator 0.0.183 → 0.0.184

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
@@ -219,6 +219,7 @@ __export(index_exports, {
219
219
  githubWorkflowBundle: () => githubWorkflowBundle,
220
220
  jestBundle: () => jestBundle,
221
221
  meetingAnalysisBundle: () => meetingAnalysisBundle,
222
+ orchestratorBundle: () => orchestratorBundle,
222
223
  pnpmBundle: () => pnpmBundle,
223
224
  projenBundle: () => projenBundle,
224
225
  resolveModelAlias: () => resolveModelAlias,
@@ -1303,6 +1304,558 @@ var meetingAnalysisBundle = {
1303
1304
  subAgents: [meetingAnalystSubAgent]
1304
1305
  };
1305
1306
 
1307
+ // src/agent/bundles/orchestrator.ts
1308
+ var checkBlockedProcedure = {
1309
+ name: "check-blocked.sh",
1310
+ description: "Token-efficient issue triage script with subcommands: eligible, unblock, stale, orphaned, prs",
1311
+ content: [
1312
+ "#!/usr/bin/env bash",
1313
+ "# check-blocked.sh \u2014 Token-efficient issue triage for agent loops.",
1314
+ "# Replaces inline body-parsing with shell pipelines that return only",
1315
+ "# actionable summaries.",
1316
+ "#",
1317
+ "# Usage:",
1318
+ "# .claude/procedures/check-blocked.sh unblock",
1319
+ "# .claude/procedures/check-blocked.sh eligible",
1320
+ "# .claude/procedures/check-blocked.sh stale",
1321
+ "# .claude/procedures/check-blocked.sh orphaned",
1322
+ "# .claude/procedures/check-blocked.sh prs",
1323
+ "",
1324
+ "set -uo pipefail",
1325
+ "",
1326
+ "# \u2500\u2500 constants \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
1327
+ "",
1328
+ "STALE_IN_PROGRESS_HOURS=72",
1329
+ "STALE_BLOCKED_HOURS=168",
1330
+ "",
1331
+ "# \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",
1332
+ "",
1333
+ '# Extract issue numbers from a "Depends on:" line.',
1334
+ '# Returns space-separated numbers, or empty string for "(none)".',
1335
+ "parse_deps() {",
1336
+ ' local line="$1"',
1337
+ ` if echo "$line" | grep -qi '(none)'; then`,
1338
+ ' echo ""',
1339
+ " return",
1340
+ " fi",
1341
+ ` echo "$line" | grep -oE '#[0-9]+' | tr -d '#' | tr '\\n' ' ' || echo ""`,
1342
+ "}",
1343
+ "",
1344
+ "# Check if a single issue is closed. Returns 0 if closed, 1 if open.",
1345
+ "is_closed() {",
1346
+ " local state",
1347
+ ` state=$(gh issue view "$1" --json state --jq '.state' 2>/dev/null || echo "UNKNOWN")`,
1348
+ ' [[ "$state" == "CLOSED" ]]',
1349
+ "}",
1350
+ "",
1351
+ "# \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",
1352
+ "",
1353
+ "cmd_unblock() {",
1354
+ " local issues",
1355
+ ' issues=$(gh issue list --label "status:blocked" --state open \\',
1356
+ ' --json number,body --limit 50 2>/dev/null || echo "[]")',
1357
+ "",
1358
+ " local count",
1359
+ ` count=$(echo "$issues" | jq 'length')`,
1360
+ ' if [[ "$count" -eq 0 ]]; then',
1361
+ ' echo "NO_BLOCKED_ISSUES"',
1362
+ " return 0",
1363
+ " fi",
1364
+ "",
1365
+ " local issue_data",
1366
+ ` issue_data=$(echo "$issues" | jq -r '`,
1367
+ " .[] |",
1368
+ ' (.body | split("\\n") | map(select(test("Depends on:"; "i"))) | .[0] // "") as $dep_line |',
1369
+ ' "\\(.number)\\t\\($dep_line)"',
1370
+ " ')",
1371
+ "",
1372
+ " while IFS=$'\\t' read -r num dep_line; do",
1373
+ ' [[ -z "$num" ]] && continue',
1374
+ "",
1375
+ ' if [[ -z "$dep_line" ]]; then',
1376
+ ' echo "BLOCKED #${num} \u2014 no Depends on field found"',
1377
+ " continue",
1378
+ " fi",
1379
+ "",
1380
+ " local deps",
1381
+ ' deps=$(parse_deps "$dep_line")',
1382
+ ' if [[ -z "${deps// /}" ]]; then',
1383
+ ' echo "UNBLOCK #${num} \u2014 no dependencies"',
1384
+ " continue",
1385
+ " fi",
1386
+ "",
1387
+ " local all_closed=true",
1388
+ ' local open_deps=""',
1389
+ ' local closed_deps=""',
1390
+ " for dep in $deps; do",
1391
+ ' if is_closed "$dep"; then',
1392
+ ' closed_deps="${closed_deps}#${dep} "',
1393
+ " else",
1394
+ " all_closed=false",
1395
+ ' open_deps="${open_deps}#${dep} "',
1396
+ " fi",
1397
+ " done",
1398
+ "",
1399
+ " if $all_closed; then",
1400
+ ' echo "UNBLOCK #${num} \u2014 all deps closed (${closed_deps% })"',
1401
+ " else",
1402
+ ' echo "BLOCKED #${num} \u2014 waiting on ${open_deps% }"',
1403
+ " fi",
1404
+ ' done <<< "$issue_data"',
1405
+ "}",
1406
+ "",
1407
+ "cmd_eligible() {",
1408
+ " local issues",
1409
+ ' issues=$(gh issue list --label "status:ready" --state open \\',
1410
+ ' --search "sort:created-asc" \\',
1411
+ ' --json number,title,body,labels --limit 50 2>/dev/null || echo "[]")',
1412
+ "",
1413
+ " local count",
1414
+ ` count=$(echo "$issues" | jq 'length')`,
1415
+ ' if [[ "$count" -eq 0 ]]; then',
1416
+ ' echo "NO_READY_ISSUES"',
1417
+ " return 0",
1418
+ " fi",
1419
+ "",
1420
+ " local issue_data",
1421
+ ` issue_data=$(echo "$issues" | jq -r '`,
1422
+ " .[] |",
1423
+ ' (.body | split("\\n") | map(select(test("Depends on:"; "i"))) | .[0] // "") as $dep_line |',
1424
+ ' (.labels | map(.name) | join(",")) as $label_str |',
1425
+ ' (.labels | map(.name) | map(select(startswith("type:"))) | .[0] // "") as $type_label |',
1426
+ ' "\\(.number)\\t\\(.title)\\t\\($dep_line)\\t\\($label_str)\\t\\($type_label)"',
1427
+ " ')",
1428
+ "",
1429
+ ' local results=""',
1430
+ " while IFS=$'\\t' read -r num title dep_line labels_str type_label; do",
1431
+ ' [[ -z "$num" ]] && continue',
1432
+ "",
1433
+ ' local deps=""',
1434
+ ' if [[ -n "$dep_line" ]]; then',
1435
+ ' deps=$(parse_deps "$dep_line")',
1436
+ " fi",
1437
+ "",
1438
+ " # Check if any dep is still open.",
1439
+ " local has_open_dep=false",
1440
+ ' local open_deps=""',
1441
+ ' if [[ -n "${deps// /}" ]]; then',
1442
+ " for dep in $deps; do",
1443
+ ' if ! is_closed "$dep"; then',
1444
+ " has_open_dep=true",
1445
+ ' open_deps="${open_deps}#${dep} "',
1446
+ " fi",
1447
+ " done",
1448
+ " fi",
1449
+ "",
1450
+ " if $has_open_dep; then",
1451
+ ' echo "SKIP #${num} \u2014 dep ${open_deps% } still open"',
1452
+ " continue",
1453
+ " fi",
1454
+ "",
1455
+ " # 5-level priority sort key.",
1456
+ " local sort_key=2",
1457
+ ' local priority="medium"',
1458
+ ' case "$labels_str" in',
1459
+ ' *priority:critical*) sort_key=0; priority="critical" ;;',
1460
+ ' *priority:high*) sort_key=1; priority="high" ;;',
1461
+ ' *priority:medium*) sort_key=2; priority="medium" ;;',
1462
+ ' *priority:low*) sort_key=3; priority="low" ;;',
1463
+ ' *priority:trivial*) sort_key=4; priority="trivial" ;;',
1464
+ " esac",
1465
+ "",
1466
+ ' local label_info=""',
1467
+ ' [[ -n "$type_label" ]] && label_info=" ${type_label}"',
1468
+ "",
1469
+ ' results="${results}${sort_key}\\t${num}\\tPICK #${num} priority:${priority}${label_info} \\"${title}\\"\\n"',
1470
+ ' done <<< "$issue_data"',
1471
+ "",
1472
+ " # Sort by priority, then issue number (FIFO).",
1473
+ ' if [[ -n "$results" ]]; then',
1474
+ ` printf '%b' "$results" | sort -t$'\\t' -k1,1n -k2,2n | cut -f3`,
1475
+ " fi",
1476
+ "}",
1477
+ "",
1478
+ "cmd_stale() {",
1479
+ " # Check in-progress issues",
1480
+ " local ip_issues",
1481
+ ' ip_issues=$(gh issue list --label "status:in-progress" --state open \\',
1482
+ ' --json number,title,updatedAt --limit 50 2>/dev/null || echo "[]")',
1483
+ "",
1484
+ " local ip_count",
1485
+ ` ip_count=$(echo "$ip_issues" | jq 'length')`,
1486
+ "",
1487
+ ' if [[ "$ip_count" -gt 0 ]]; then',
1488
+ " local ip_threshold",
1489
+ " ip_threshold=$(date -u -v-${STALE_IN_PROGRESS_HOURS}H +%Y-%m-%dT%H:%M:%S 2>/dev/null \\",
1490
+ ' || date -u -d "${STALE_IN_PROGRESS_HOURS} hours ago" +%Y-%m-%dT%H:%M:%S 2>/dev/null)',
1491
+ "",
1492
+ " local ip_data",
1493
+ ` ip_data=$(echo "$ip_issues" | jq -r '.[] | "\\(.number)\\t\\(.title)\\t\\(.updatedAt)"')`,
1494
+ "",
1495
+ " while IFS=$'\\t' read -r num title updated; do",
1496
+ ' [[ -z "$num" ]] && continue',
1497
+ ' local updated_trimmed="${updated%%+*}"',
1498
+ ' updated_trimmed="${updated_trimmed%%Z*}"',
1499
+ ' if [[ "$updated_trimmed" < "$ip_threshold" ]]; then',
1500
+ ' local date_part="${updated_trimmed%%T*}"',
1501
+ ' echo "STALE #${num} \u2014 no activity since ${date_part} \u2014 \\"${title}\\""',
1502
+ " fi",
1503
+ ' done <<< "$ip_data"',
1504
+ " fi",
1505
+ "",
1506
+ " # Check blocked issues",
1507
+ " local bl_issues",
1508
+ ' bl_issues=$(gh issue list --label "status:blocked" --state open \\',
1509
+ ' --json number,title,updatedAt --limit 50 2>/dev/null || echo "[]")',
1510
+ "",
1511
+ " local bl_count",
1512
+ ` bl_count=$(echo "$bl_issues" | jq 'length')`,
1513
+ "",
1514
+ ' if [[ "$bl_count" -gt 0 ]]; then',
1515
+ " local bl_threshold",
1516
+ " bl_threshold=$(date -u -v-${STALE_BLOCKED_HOURS}H +%Y-%m-%dT%H:%M:%S 2>/dev/null \\",
1517
+ ' || date -u -d "${STALE_BLOCKED_HOURS} hours ago" +%Y-%m-%dT%H:%M:%S 2>/dev/null)',
1518
+ "",
1519
+ " local bl_data",
1520
+ ` bl_data=$(echo "$bl_issues" | jq -r '.[] | "\\(.number)\\t\\(.title)\\t\\(.updatedAt)"')`,
1521
+ "",
1522
+ " while IFS=$'\\t' read -r num title updated; do",
1523
+ ' [[ -z "$num" ]] && continue',
1524
+ ' local updated_trimmed="${updated%%+*}"',
1525
+ ' updated_trimmed="${updated_trimmed%%Z*}"',
1526
+ ' if [[ "$updated_trimmed" < "$bl_threshold" ]]; then',
1527
+ ' local date_part="${updated_trimmed%%T*}"',
1528
+ ' echo "STALE_BLOCKED #${num} \u2014 blocked since ${date_part} \u2014 \\"${title}\\""',
1529
+ " fi",
1530
+ ' done <<< "$bl_data"',
1531
+ " fi",
1532
+ "",
1533
+ ' if [[ "$ip_count" -eq 0 && "$bl_count" -eq 0 ]]; then',
1534
+ ' echo "NO_STALE_ISSUES"',
1535
+ " fi",
1536
+ "}",
1537
+ "",
1538
+ "cmd_orphaned() {",
1539
+ " # Check for remote branches whose issues are closed or missing",
1540
+ " git fetch --prune origin 2>/dev/null",
1541
+ " local branches",
1542
+ ' branches=$(git branch -r --format="%(refname:short)" | grep -v HEAD | sed "s|origin/||")',
1543
+ "",
1544
+ " local found_orphan=false",
1545
+ " while IFS= read -r branch; do",
1546
+ ' [[ -z "$branch" ]] && continue',
1547
+ ' [[ "$branch" == "main" || "$branch" == "master" ]] && continue',
1548
+ "",
1549
+ " # Extract issue number from branch name (e.g., feat/42-add-login \u2192 42)",
1550
+ " local issue_num",
1551
+ ` issue_num=$(echo "$branch" | grep -oE '/[0-9]+' | tr -d '/' | head -1)`,
1552
+ ' [[ -z "$issue_num" ]] && continue',
1553
+ "",
1554
+ " local state",
1555
+ ` state=$(gh issue view "$issue_num" --json state --jq '.state' 2>/dev/null || echo "NOT_FOUND")`,
1556
+ "",
1557
+ ' if [[ "$state" == "CLOSED" ]]; then',
1558
+ " found_orphan=true",
1559
+ ' echo "ORPHAN_BRANCH ${branch} \u2014 issue #${issue_num} is closed"',
1560
+ ' elif [[ "$state" == "NOT_FOUND" ]]; then',
1561
+ " found_orphan=true",
1562
+ ' echo "ORPHAN_BRANCH ${branch} \u2014 issue #${issue_num} not found"',
1563
+ " fi",
1564
+ ' done <<< "$branches"',
1565
+ "",
1566
+ " # Check for open PRs whose linked issues are closed",
1567
+ " local prs",
1568
+ ' prs=$(gh pr list --state open --json number,title,body --limit 50 2>/dev/null || echo "[]")',
1569
+ " local pr_data",
1570
+ ` pr_data=$(echo "$prs" | jq -r '`,
1571
+ " .[] |",
1572
+ ' (.body | capture("(?:Closes|Fixes|Resolves) #(?<num>[0-9]+)") | .num) as $issue |',
1573
+ ' "\\(.number)\\t\\(.title)\\t\\($issue // "")"',
1574
+ " ' 2>/dev/null)",
1575
+ "",
1576
+ " while IFS=$'\\t' read -r pr_num title issue_num; do",
1577
+ ' [[ -z "$pr_num" || -z "$issue_num" ]] && continue',
1578
+ ' if is_closed "$issue_num"; then',
1579
+ " found_orphan=true",
1580
+ ' echo "ORPHAN_PR #${pr_num} \u2014 linked issue #${issue_num} is closed \u2014 \\"${title}\\""',
1581
+ " fi",
1582
+ ' done <<< "$pr_data"',
1583
+ "",
1584
+ " if ! $found_orphan; then",
1585
+ ' echo "NO_ORPHANED_RESOURCES"',
1586
+ " fi",
1587
+ "}",
1588
+ "",
1589
+ "cmd_prs() {",
1590
+ " local prs",
1591
+ " prs=$(gh pr list --state open --json number,title,isDraft,headRefName,labels,body \\",
1592
+ ' --limit 50 2>/dev/null || echo "[]")',
1593
+ "",
1594
+ " local count",
1595
+ ` count=$(echo "$prs" | jq 'length')`,
1596
+ ' if [[ "$count" -eq 0 ]]; then',
1597
+ ' echo "NO_OPEN_PRS"',
1598
+ " return 0",
1599
+ " fi",
1600
+ "",
1601
+ " # Filter: not draft, no needs-attention label.",
1602
+ " local eligible",
1603
+ ` eligible=$(echo "$prs" | jq -r '`,
1604
+ " .[] |",
1605
+ " select(.isDraft == false) |",
1606
+ ' select(.labels | map(.name) | index("status:needs-attention") | not) |',
1607
+ ' (.body | capture("(?:Closes|Fixes|Resolves) #(?<num>[0-9]+)") | .num) as $issue |',
1608
+ ' "\\(.number)\\t\\(.title)\\t\\(.headRefName)\\t\\($issue // "")"',
1609
+ " ' 2>/dev/null)",
1610
+ "",
1611
+ ' if [[ -z "$eligible" ]]; then',
1612
+ ' echo "NO_ELIGIBLE_PRS"',
1613
+ " return 0",
1614
+ " fi",
1615
+ "",
1616
+ " while IFS=$'\\t' read -r pr_num title branch issue_num; do",
1617
+ ' [[ -z "$pr_num" ]] && continue',
1618
+ "",
1619
+ ' if [[ -z "$issue_num" ]]; then',
1620
+ ' echo "SKIP PR #${pr_num} \u2014 no linked issue \u2014 \\"${title}\\""',
1621
+ " continue",
1622
+ " fi",
1623
+ "",
1624
+ " # Check CI status",
1625
+ " local failing_checks",
1626
+ ' failing_checks=$(gh pr checks "$pr_num" --json name,state \\',
1627
+ ` --jq '[.[] | select(.state != "SUCCESS" and .state != "SKIPPED")] | length' 2>/dev/null || echo "-1")`,
1628
+ "",
1629
+ ' if [[ "$failing_checks" == "-1" ]]; then',
1630
+ ' echo "SKIP PR #${pr_num} \u2014 could not read CI status \u2014 \\"${title}\\""',
1631
+ " continue",
1632
+ " fi",
1633
+ "",
1634
+ ' if [[ "$failing_checks" -gt 0 ]]; then',
1635
+ ' echo "SKIP PR #${pr_num} \u2014 ${failing_checks} CI check(s) not passing \u2014 \\"${title}\\""',
1636
+ " continue",
1637
+ " fi",
1638
+ "",
1639
+ " # Check if already approved",
1640
+ " local approved",
1641
+ ' approved=$(gh pr view "$pr_num" --json reviews \\',
1642
+ ` --jq '[.reviews[] | select(.state == "APPROVED")] | length' 2>/dev/null || echo "0")`,
1643
+ ' if [[ "$approved" -gt 0 ]]; then',
1644
+ ' echo "SKIP PR #${pr_num} \u2014 already approved \u2014 \\"${title}\\""',
1645
+ " continue",
1646
+ " fi",
1647
+ "",
1648
+ ' echo "REVIEW PR #${pr_num} issue:#${issue_num} branch:${branch} \u2014 \\"${title}\\""',
1649
+ ' done <<< "$eligible"',
1650
+ "}",
1651
+ "",
1652
+ "# \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",
1653
+ "",
1654
+ 'case "${1:-help}" in',
1655
+ ' unblock) shift; cmd_unblock "$@" ;;',
1656
+ ' eligible) shift; cmd_eligible "$@" ;;',
1657
+ ' stale) shift; cmd_stale "$@" ;;',
1658
+ ' orphaned) shift; cmd_orphaned "$@" ;;',
1659
+ ' prs) shift; cmd_prs "$@" ;;',
1660
+ " help|*)",
1661
+ ' echo "Usage: check-blocked.sh <unblock|eligible|stale|orphaned|prs>"',
1662
+ " exit 1",
1663
+ " ;;",
1664
+ "esac"
1665
+ ].join("\n")
1666
+ };
1667
+ var orchestratorSubAgent = {
1668
+ name: "orchestrator",
1669
+ description: "Pipeline manager that reviews PRs, triages issues, and identifies the next work item \u2014 never implements code",
1670
+ model: AGENT_MODEL.POWERFUL,
1671
+ maxTurns: 100,
1672
+ platforms: { cursor: { exclude: true } },
1673
+ prompt: [
1674
+ "# Orchestrator Agent",
1675
+ "",
1676
+ "You are a pipeline manager for the **{{repository.owner}}/{{repository.name}}** repository.",
1677
+ "You review PRs, triage issues, and identify the next work item. You **never**",
1678
+ "implement code, create branches for issues, or claim issues.",
1679
+ "",
1680
+ "Run the same loop every invocation. Execute all phases in order.",
1681
+ "",
1682
+ "---",
1683
+ "",
1684
+ "## Phase A: Startup",
1685
+ "",
1686
+ "```bash",
1687
+ "git checkout main && git pull origin main",
1688
+ "```",
1689
+ "",
1690
+ "## Phase B: Batch PR Review",
1691
+ "",
1692
+ "Find all PRs eligible for review:",
1693
+ "",
1694
+ "```bash",
1695
+ ".claude/procedures/check-blocked.sh prs",
1696
+ "```",
1697
+ "",
1698
+ "For each `REVIEW PR #N issue:#M branch:<branch>` line:",
1699
+ "",
1700
+ "1. Check out the PR branch: `gh pr checkout N`",
1701
+ "2. Review the PR:",
1702
+ " - Read the PR description and linked issue: `gh pr view N`",
1703
+ " - Review the diff: `gh pr diff N`",
1704
+ " - Check all changed files for correctness, conventions, and test coverage",
1705
+ " - Verify PR conventions: conventional commit title, closing keyword, summary present",
1706
+ " - Check CI status: `gh pr checks N`",
1707
+ "3. If the PR passes review:",
1708
+ " - Approve: `gh pr review N --approve --body '<summary>'`",
1709
+ " - Enable auto-merge: `gh pr merge N --auto --squash`",
1710
+ "4. If the PR fails review:",
1711
+ " - Request changes: `gh pr review N --request-changes --body '<findings>'`",
1712
+ "5. After each PR (whether merged or not):",
1713
+ " ```bash",
1714
+ " git checkout main && git pull origin main",
1715
+ " ```",
1716
+ "",
1717
+ "Skip lines starting with `SKIP` \u2014 those PRs are not eligible.",
1718
+ "If output is `NO_OPEN_PRS` or `NO_ELIGIBLE_PRS`, skip to Phase C.",
1719
+ "",
1720
+ "## Phase C: Triage \u2014 Unblock",
1721
+ "",
1722
+ "Check for blocked issues whose dependencies have resolved:",
1723
+ "",
1724
+ "```bash",
1725
+ ".claude/procedures/check-blocked.sh unblock",
1726
+ "```",
1727
+ "",
1728
+ "For each `UNBLOCK #N` line:",
1729
+ "```bash",
1730
+ 'gh issue edit N --remove-label "status:blocked" --add-label "status:ready"',
1731
+ 'gh issue comment N --body "Dependencies resolved \u2014 unblocking."',
1732
+ "```",
1733
+ "",
1734
+ "For `BLOCKED #N \u2014 no Depends on field found`: leave as-is (Phase D will",
1735
+ "catch it if it's been blocked too long).",
1736
+ "",
1737
+ "If output is `NO_BLOCKED_ISSUES`, skip to Phase D.",
1738
+ "",
1739
+ "## Phase D: Maintenance",
1740
+ "",
1741
+ "### D1: Stale Detection",
1742
+ "",
1743
+ "```bash",
1744
+ ".claude/procedures/check-blocked.sh stale",
1745
+ "```",
1746
+ "",
1747
+ "For each `STALE #N` line (in-progress >72h without activity):",
1748
+ "```bash",
1749
+ 'gh issue edit N --add-label "status:needs-attention"',
1750
+ 'gh issue comment N --body "Flagged: in-progress for >3 days with no activity."',
1751
+ "```",
1752
+ "",
1753
+ "For each `STALE_BLOCKED #N` line (blocked >168h):",
1754
+ "```bash",
1755
+ 'gh issue edit N --add-label "status:needs-attention"',
1756
+ 'gh issue comment N --body "Flagged: blocked for >7 days \u2014 may need human intervention."',
1757
+ "```",
1758
+ "",
1759
+ "**Important:** Do NOT auto-reset stale issues to `status:ready` \u2014 partial",
1760
+ "implementation work may exist on a branch.",
1761
+ "",
1762
+ "### D2: Orphaned Detection",
1763
+ "",
1764
+ "```bash",
1765
+ ".claude/procedures/check-blocked.sh orphaned",
1766
+ "```",
1767
+ "",
1768
+ "Report any `ORPHAN_BRANCH` or `ORPHAN_PR` lines. These indicate branches",
1769
+ "or PRs whose linked issues are closed or missing. Log them for visibility",
1770
+ "but do not delete branches automatically.",
1771
+ "",
1772
+ "### D3: Needs-Attention Summary",
1773
+ "",
1774
+ "List all issues currently flagged:",
1775
+ "```bash",
1776
+ 'gh issue list --label "status:needs-attention" --state open --json number,title',
1777
+ "```",
1778
+ "",
1779
+ "Log the count and titles for operator visibility.",
1780
+ "",
1781
+ "## Phase E: Queue Scan",
1782
+ "",
1783
+ "Find the highest-priority ready issue:",
1784
+ "",
1785
+ "```bash",
1786
+ ".claude/procedures/check-blocked.sh eligible",
1787
+ "```",
1788
+ "",
1789
+ "If a `PICK` line is returned, report it as:",
1790
+ "```",
1791
+ 'NEXT_WORK_ITEM #<number> priority:<level> type:<label> "<title>"',
1792
+ "```",
1793
+ "",
1794
+ "If output is `NO_READY_ISSUES`, report that the queue is empty.",
1795
+ "",
1796
+ "**Do NOT claim the issue, create a branch, or start implementation.**",
1797
+ "The worker agent handles that.",
1798
+ "",
1799
+ "## Phase F: Cleanup",
1800
+ "",
1801
+ "```bash",
1802
+ "git checkout main && git pull origin main",
1803
+ "git fetch --prune origin",
1804
+ "```",
1805
+ "",
1806
+ "Log completion: phases executed, PRs reviewed, issues unblocked,",
1807
+ "stale issues flagged, and next work item (if any).",
1808
+ "",
1809
+ "---",
1810
+ "",
1811
+ "## Rules",
1812
+ "",
1813
+ "1. **Never implement code.** You triage, review, and report \u2014 you do not code.",
1814
+ "2. **Never claim issues.** Do not add `status:in-progress` or create branches for issues.",
1815
+ "3. **Always use check-blocked.sh.** All triage queries go through the shell script for token efficiency.",
1816
+ "4. **Follow CLAUDE.md conventions** for all git and gh operations.",
1817
+ "5. **Priority order:** critical > high > medium > low > trivial, then FIFO by issue number."
1818
+ ].join("\n")
1819
+ };
1820
+ var orchestratorBundle = {
1821
+ name: "orchestrator",
1822
+ description: "Pipeline orchestrator agent for issue triage, PR review, and queue management",
1823
+ // Always included by default
1824
+ appliesWhen: () => true,
1825
+ rules: [
1826
+ {
1827
+ name: "orchestrator-conventions",
1828
+ description: "Guidelines for orchestrator agent behavior and pipeline management",
1829
+ scope: AGENT_RULE_SCOPE.ALWAYS,
1830
+ content: [
1831
+ "# Orchestrator Conventions",
1832
+ "",
1833
+ "When running the orchestrator agent (`.claude/agents/orchestrator.md`):",
1834
+ "",
1835
+ "- The orchestrator **never** implements code or creates branches for issues",
1836
+ "- It reviews PRs, triages issues, and reports the next work item",
1837
+ "- All triage queries use `.claude/procedures/check-blocked.sh` for token efficiency",
1838
+ "- Priority order: critical > high > medium > low > trivial, then FIFO",
1839
+ "- Stale thresholds: 72h for in-progress, 168h for blocked",
1840
+ "- Flagged issues get `status:needs-attention` \u2014 they are not auto-reset"
1841
+ ].join("\n"),
1842
+ platforms: {
1843
+ claude: { target: "claude-md" },
1844
+ cursor: { exclude: true }
1845
+ },
1846
+ tags: ["workflow"]
1847
+ }
1848
+ ],
1849
+ subAgents: [orchestratorSubAgent],
1850
+ procedures: [checkBlockedProcedure],
1851
+ claudePermissions: {
1852
+ allow: [
1853
+ // Allow executing the check-blocked.sh procedure
1854
+ "Bash(.claude/procedures/*.sh *)"
1855
+ ]
1856
+ }
1857
+ };
1858
+
1306
1859
  // src/pnpm/pnpm-workspace.ts
1307
1860
  var import_path = require("path");
1308
1861
  var import_projen = require("projen");
@@ -2320,7 +2873,8 @@ var BUILT_IN_BUNDLES = [
2320
2873
  projenBundle,
2321
2874
  githubWorkflowBundle,
2322
2875
  slackBundle,
2323
- meetingAnalysisBundle
2876
+ meetingAnalysisBundle,
2877
+ orchestratorBundle
2324
2878
  ];
2325
2879
 
2326
2880
  // src/agent/bundles/scope.ts
@@ -2443,12 +2997,15 @@ var ClaudeRenderer = class _ClaudeRenderer {
2443
2997
  /**
2444
2998
  * Render all Claude Code configuration files.
2445
2999
  */
2446
- static render(component, rules, skills, subAgents, mcpServers, settings) {
3000
+ static render(component, rules, skills, subAgents, mcpServers, settings, procedures) {
2447
3001
  _ClaudeRenderer.renderClaudeMd(component, rules);
2448
3002
  _ClaudeRenderer.renderScopedRules(component, rules);
2449
3003
  _ClaudeRenderer.renderSettings(component, mcpServers, settings);
2450
3004
  _ClaudeRenderer.renderSkills(component, skills);
2451
3005
  _ClaudeRenderer.renderSubAgents(component, subAgents);
3006
+ if (procedures && procedures.length > 0) {
3007
+ _ClaudeRenderer.renderProcedures(component, procedures);
3008
+ }
2452
3009
  }
2453
3010
  static renderClaudeMd(component, rules) {
2454
3011
  const claudeMdRules = rules.filter((r) => {
@@ -2765,6 +3322,14 @@ var ClaudeRenderer = class _ClaudeRenderer {
2765
3322
  if (config.env) server.env = { ...config.env };
2766
3323
  return server;
2767
3324
  }
3325
+ static renderProcedures(component, procedures) {
3326
+ for (const proc of procedures) {
3327
+ new import_textfile2.TextFile(component, `.claude/procedures/${proc.name}`, {
3328
+ lines: proc.content.split("\n"),
3329
+ executable: true
3330
+ });
3331
+ }
3332
+ }
2768
3333
  /**
2769
3334
  * Determine the default Claude rule target based on rule scope.
2770
3335
  * ALWAYS-scoped rules default to CLAUDE_MD; FILE_PATTERN rules default to SCOPED_FILE.
@@ -3118,6 +3683,7 @@ var AgentConfig = class _AgentConfig extends import_projen8.Component {
3118
3683
  const rules = this.resolveRules();
3119
3684
  const skills = this.resolveSkills();
3120
3685
  const subAgents = this.resolveSubAgents();
3686
+ const procedures = this.resolveProcedures();
3121
3687
  const mcpServers = this.options.mcpServers ?? {};
3122
3688
  const projectMetadata = ProjectMetadata.of(this.project);
3123
3689
  const metadata = projectMetadata?.metadata;
@@ -3127,6 +3693,10 @@ var AgentConfig = class _AgentConfig extends import_projen8.Component {
3127
3693
  subAgents,
3128
3694
  metadata
3129
3695
  );
3696
+ const resolvedProcedures = this.resolveProcedureTemplates(
3697
+ procedures,
3698
+ metadata
3699
+ );
3130
3700
  if (platforms.includes(AGENT_PLATFORM.CURSOR)) {
3131
3701
  CursorRenderer.render(
3132
3702
  this,
@@ -3148,7 +3718,8 @@ var AgentConfig = class _AgentConfig extends import_projen8.Component {
3148
3718
  _AgentConfig.mergeClaudeDefaults(
3149
3719
  this.options.claudeSettings,
3150
3720
  bundlePermissions
3151
- )
3721
+ ),
3722
+ resolvedProcedures
3152
3723
  );
3153
3724
  }
3154
3725
  if (platforms.includes(AGENT_PLATFORM.CODEX)) {
@@ -3261,6 +3832,16 @@ ${extra}`
3261
3832
  }
3262
3833
  }
3263
3834
  }
3835
+ if (this.options.includeBundles) {
3836
+ for (const bundleName of this.options.includeBundles) {
3837
+ const bundle = BUILT_IN_BUNDLES.find((b) => b.name === bundleName);
3838
+ if (bundle?.skills) {
3839
+ for (const skill of bundle.skills) {
3840
+ skillMap.set(skill.name, skill);
3841
+ }
3842
+ }
3843
+ }
3844
+ }
3264
3845
  if (this.options.skills) {
3265
3846
  for (const skill of this.options.skills) {
3266
3847
  skillMap.set(skill.name, skill);
@@ -3280,6 +3861,16 @@ ${extra}`
3280
3861
  }
3281
3862
  }
3282
3863
  }
3864
+ if (this.options.includeBundles) {
3865
+ for (const bundleName of this.options.includeBundles) {
3866
+ const bundle = BUILT_IN_BUNDLES.find((b) => b.name === bundleName);
3867
+ if (bundle?.subAgents) {
3868
+ for (const agent of bundle.subAgents) {
3869
+ agentMap.set(agent.name, agent);
3870
+ }
3871
+ }
3872
+ }
3873
+ }
3283
3874
  if (this.options.subAgents) {
3284
3875
  for (const agent of this.options.subAgents) {
3285
3876
  agentMap.set(agent.name, agent);
@@ -3287,6 +3878,35 @@ ${extra}`
3287
3878
  }
3288
3879
  return [...agentMap.values()];
3289
3880
  }
3881
+ resolveProcedures() {
3882
+ const procMap = /* @__PURE__ */ new Map();
3883
+ if (this.options.autoDetectBundles !== false) {
3884
+ for (const bundle of BUILT_IN_BUNDLES) {
3885
+ if (this.options.excludeBundles?.includes(bundle.name)) continue;
3886
+ if (bundle.appliesWhen(this.project) && bundle.procedures) {
3887
+ for (const proc of bundle.procedures) {
3888
+ procMap.set(proc.name, proc);
3889
+ }
3890
+ }
3891
+ }
3892
+ }
3893
+ if (this.options.includeBundles) {
3894
+ for (const bundleName of this.options.includeBundles) {
3895
+ const bundle = BUILT_IN_BUNDLES.find((b) => b.name === bundleName);
3896
+ if (bundle?.procedures) {
3897
+ for (const proc of bundle.procedures) {
3898
+ procMap.set(proc.name, proc);
3899
+ }
3900
+ }
3901
+ }
3902
+ }
3903
+ if (this.options.procedures) {
3904
+ for (const proc of this.options.procedures) {
3905
+ procMap.set(proc.name, proc);
3906
+ }
3907
+ }
3908
+ return [...procMap.values()];
3909
+ }
3290
3910
  /**
3291
3911
  * Resolves template variables in rule content using project metadata.
3292
3912
  * Emits synthesis warnings for rules with unresolved variables.
@@ -3339,6 +3959,23 @@ ${extra}`
3339
3959
  return resolved !== agent.prompt ? { ...agent, prompt: resolved } : agent;
3340
3960
  });
3341
3961
  }
3962
+ /**
3963
+ * Resolves template variables in procedure content using project metadata.
3964
+ */
3965
+ resolveProcedureTemplates(procedures, metadata) {
3966
+ return procedures.map((proc) => {
3967
+ const { resolved, unresolvedKeys } = resolveTemplateVariables(
3968
+ proc.content,
3969
+ metadata
3970
+ );
3971
+ if (unresolvedKeys.length > 0) {
3972
+ this.project.logger.warn(
3973
+ `AgentConfig: ProjectMetadata not found; procedure '${proc.name}' using default values`
3974
+ );
3975
+ }
3976
+ return resolved !== proc.content ? { ...proc, content: resolved } : proc;
3977
+ });
3978
+ }
3342
3979
  /**
3343
3980
  * Collects Claude permission entries from all active bundles.
3344
3981
  */
@@ -5018,6 +5655,7 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends import_projen17.Compone
5018
5655
  githubWorkflowBundle,
5019
5656
  jestBundle,
5020
5657
  meetingAnalysisBundle,
5658
+ orchestratorBundle,
5021
5659
  pnpmBundle,
5022
5660
  projenBundle,
5023
5661
  resolveModelAlias,