@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.d.mts +34 -1
- package/lib/index.d.ts +35 -2
- package/lib/index.js +641 -3
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +640 -3
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
package/lib/index.mjs
CHANGED
|
@@ -1245,6 +1245,558 @@ var meetingAnalysisBundle = {
|
|
|
1245
1245
|
subAgents: [meetingAnalystSubAgent]
|
|
1246
1246
|
};
|
|
1247
1247
|
|
|
1248
|
+
// src/agent/bundles/orchestrator.ts
|
|
1249
|
+
var checkBlockedProcedure = {
|
|
1250
|
+
name: "check-blocked.sh",
|
|
1251
|
+
description: "Token-efficient issue triage script with subcommands: eligible, unblock, stale, orphaned, prs",
|
|
1252
|
+
content: [
|
|
1253
|
+
"#!/usr/bin/env bash",
|
|
1254
|
+
"# check-blocked.sh \u2014 Token-efficient issue triage for agent loops.",
|
|
1255
|
+
"# Replaces inline body-parsing with shell pipelines that return only",
|
|
1256
|
+
"# actionable summaries.",
|
|
1257
|
+
"#",
|
|
1258
|
+
"# Usage:",
|
|
1259
|
+
"# .claude/procedures/check-blocked.sh unblock",
|
|
1260
|
+
"# .claude/procedures/check-blocked.sh eligible",
|
|
1261
|
+
"# .claude/procedures/check-blocked.sh stale",
|
|
1262
|
+
"# .claude/procedures/check-blocked.sh orphaned",
|
|
1263
|
+
"# .claude/procedures/check-blocked.sh prs",
|
|
1264
|
+
"",
|
|
1265
|
+
"set -uo pipefail",
|
|
1266
|
+
"",
|
|
1267
|
+
"# \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",
|
|
1268
|
+
"",
|
|
1269
|
+
"STALE_IN_PROGRESS_HOURS=72",
|
|
1270
|
+
"STALE_BLOCKED_HOURS=168",
|
|
1271
|
+
"",
|
|
1272
|
+
"# \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",
|
|
1273
|
+
"",
|
|
1274
|
+
'# Extract issue numbers from a "Depends on:" line.',
|
|
1275
|
+
'# Returns space-separated numbers, or empty string for "(none)".',
|
|
1276
|
+
"parse_deps() {",
|
|
1277
|
+
' local line="$1"',
|
|
1278
|
+
` if echo "$line" | grep -qi '(none)'; then`,
|
|
1279
|
+
' echo ""',
|
|
1280
|
+
" return",
|
|
1281
|
+
" fi",
|
|
1282
|
+
` echo "$line" | grep -oE '#[0-9]+' | tr -d '#' | tr '\\n' ' ' || echo ""`,
|
|
1283
|
+
"}",
|
|
1284
|
+
"",
|
|
1285
|
+
"# Check if a single issue is closed. Returns 0 if closed, 1 if open.",
|
|
1286
|
+
"is_closed() {",
|
|
1287
|
+
" local state",
|
|
1288
|
+
` state=$(gh issue view "$1" --json state --jq '.state' 2>/dev/null || echo "UNKNOWN")`,
|
|
1289
|
+
' [[ "$state" == "CLOSED" ]]',
|
|
1290
|
+
"}",
|
|
1291
|
+
"",
|
|
1292
|
+
"# \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",
|
|
1293
|
+
"",
|
|
1294
|
+
"cmd_unblock() {",
|
|
1295
|
+
" local issues",
|
|
1296
|
+
' issues=$(gh issue list --label "status:blocked" --state open \\',
|
|
1297
|
+
' --json number,body --limit 50 2>/dev/null || echo "[]")',
|
|
1298
|
+
"",
|
|
1299
|
+
" local count",
|
|
1300
|
+
` count=$(echo "$issues" | jq 'length')`,
|
|
1301
|
+
' if [[ "$count" -eq 0 ]]; then',
|
|
1302
|
+
' echo "NO_BLOCKED_ISSUES"',
|
|
1303
|
+
" return 0",
|
|
1304
|
+
" fi",
|
|
1305
|
+
"",
|
|
1306
|
+
" local issue_data",
|
|
1307
|
+
` issue_data=$(echo "$issues" | jq -r '`,
|
|
1308
|
+
" .[] |",
|
|
1309
|
+
' (.body | split("\\n") | map(select(test("Depends on:"; "i"))) | .[0] // "") as $dep_line |',
|
|
1310
|
+
' "\\(.number)\\t\\($dep_line)"',
|
|
1311
|
+
" ')",
|
|
1312
|
+
"",
|
|
1313
|
+
" while IFS=$'\\t' read -r num dep_line; do",
|
|
1314
|
+
' [[ -z "$num" ]] && continue',
|
|
1315
|
+
"",
|
|
1316
|
+
' if [[ -z "$dep_line" ]]; then',
|
|
1317
|
+
' echo "BLOCKED #${num} \u2014 no Depends on field found"',
|
|
1318
|
+
" continue",
|
|
1319
|
+
" fi",
|
|
1320
|
+
"",
|
|
1321
|
+
" local deps",
|
|
1322
|
+
' deps=$(parse_deps "$dep_line")',
|
|
1323
|
+
' if [[ -z "${deps// /}" ]]; then',
|
|
1324
|
+
' echo "UNBLOCK #${num} \u2014 no dependencies"',
|
|
1325
|
+
" continue",
|
|
1326
|
+
" fi",
|
|
1327
|
+
"",
|
|
1328
|
+
" local all_closed=true",
|
|
1329
|
+
' local open_deps=""',
|
|
1330
|
+
' local closed_deps=""',
|
|
1331
|
+
" for dep in $deps; do",
|
|
1332
|
+
' if is_closed "$dep"; then',
|
|
1333
|
+
' closed_deps="${closed_deps}#${dep} "',
|
|
1334
|
+
" else",
|
|
1335
|
+
" all_closed=false",
|
|
1336
|
+
' open_deps="${open_deps}#${dep} "',
|
|
1337
|
+
" fi",
|
|
1338
|
+
" done",
|
|
1339
|
+
"",
|
|
1340
|
+
" if $all_closed; then",
|
|
1341
|
+
' echo "UNBLOCK #${num} \u2014 all deps closed (${closed_deps% })"',
|
|
1342
|
+
" else",
|
|
1343
|
+
' echo "BLOCKED #${num} \u2014 waiting on ${open_deps% }"',
|
|
1344
|
+
" fi",
|
|
1345
|
+
' done <<< "$issue_data"',
|
|
1346
|
+
"}",
|
|
1347
|
+
"",
|
|
1348
|
+
"cmd_eligible() {",
|
|
1349
|
+
" local issues",
|
|
1350
|
+
' issues=$(gh issue list --label "status:ready" --state open \\',
|
|
1351
|
+
' --search "sort:created-asc" \\',
|
|
1352
|
+
' --json number,title,body,labels --limit 50 2>/dev/null || echo "[]")',
|
|
1353
|
+
"",
|
|
1354
|
+
" local count",
|
|
1355
|
+
` count=$(echo "$issues" | jq 'length')`,
|
|
1356
|
+
' if [[ "$count" -eq 0 ]]; then',
|
|
1357
|
+
' echo "NO_READY_ISSUES"',
|
|
1358
|
+
" return 0",
|
|
1359
|
+
" fi",
|
|
1360
|
+
"",
|
|
1361
|
+
" local issue_data",
|
|
1362
|
+
` issue_data=$(echo "$issues" | jq -r '`,
|
|
1363
|
+
" .[] |",
|
|
1364
|
+
' (.body | split("\\n") | map(select(test("Depends on:"; "i"))) | .[0] // "") as $dep_line |',
|
|
1365
|
+
' (.labels | map(.name) | join(",")) as $label_str |',
|
|
1366
|
+
' (.labels | map(.name) | map(select(startswith("type:"))) | .[0] // "") as $type_label |',
|
|
1367
|
+
' "\\(.number)\\t\\(.title)\\t\\($dep_line)\\t\\($label_str)\\t\\($type_label)"',
|
|
1368
|
+
" ')",
|
|
1369
|
+
"",
|
|
1370
|
+
' local results=""',
|
|
1371
|
+
" while IFS=$'\\t' read -r num title dep_line labels_str type_label; do",
|
|
1372
|
+
' [[ -z "$num" ]] && continue',
|
|
1373
|
+
"",
|
|
1374
|
+
' local deps=""',
|
|
1375
|
+
' if [[ -n "$dep_line" ]]; then',
|
|
1376
|
+
' deps=$(parse_deps "$dep_line")',
|
|
1377
|
+
" fi",
|
|
1378
|
+
"",
|
|
1379
|
+
" # Check if any dep is still open.",
|
|
1380
|
+
" local has_open_dep=false",
|
|
1381
|
+
' local open_deps=""',
|
|
1382
|
+
' if [[ -n "${deps// /}" ]]; then',
|
|
1383
|
+
" for dep in $deps; do",
|
|
1384
|
+
' if ! is_closed "$dep"; then',
|
|
1385
|
+
" has_open_dep=true",
|
|
1386
|
+
' open_deps="${open_deps}#${dep} "',
|
|
1387
|
+
" fi",
|
|
1388
|
+
" done",
|
|
1389
|
+
" fi",
|
|
1390
|
+
"",
|
|
1391
|
+
" if $has_open_dep; then",
|
|
1392
|
+
' echo "SKIP #${num} \u2014 dep ${open_deps% } still open"',
|
|
1393
|
+
" continue",
|
|
1394
|
+
" fi",
|
|
1395
|
+
"",
|
|
1396
|
+
" # 5-level priority sort key.",
|
|
1397
|
+
" local sort_key=2",
|
|
1398
|
+
' local priority="medium"',
|
|
1399
|
+
' case "$labels_str" in',
|
|
1400
|
+
' *priority:critical*) sort_key=0; priority="critical" ;;',
|
|
1401
|
+
' *priority:high*) sort_key=1; priority="high" ;;',
|
|
1402
|
+
' *priority:medium*) sort_key=2; priority="medium" ;;',
|
|
1403
|
+
' *priority:low*) sort_key=3; priority="low" ;;',
|
|
1404
|
+
' *priority:trivial*) sort_key=4; priority="trivial" ;;',
|
|
1405
|
+
" esac",
|
|
1406
|
+
"",
|
|
1407
|
+
' local label_info=""',
|
|
1408
|
+
' [[ -n "$type_label" ]] && label_info=" ${type_label}"',
|
|
1409
|
+
"",
|
|
1410
|
+
' results="${results}${sort_key}\\t${num}\\tPICK #${num} priority:${priority}${label_info} \\"${title}\\"\\n"',
|
|
1411
|
+
' done <<< "$issue_data"',
|
|
1412
|
+
"",
|
|
1413
|
+
" # Sort by priority, then issue number (FIFO).",
|
|
1414
|
+
' if [[ -n "$results" ]]; then',
|
|
1415
|
+
` printf '%b' "$results" | sort -t$'\\t' -k1,1n -k2,2n | cut -f3`,
|
|
1416
|
+
" fi",
|
|
1417
|
+
"}",
|
|
1418
|
+
"",
|
|
1419
|
+
"cmd_stale() {",
|
|
1420
|
+
" # Check in-progress issues",
|
|
1421
|
+
" local ip_issues",
|
|
1422
|
+
' ip_issues=$(gh issue list --label "status:in-progress" --state open \\',
|
|
1423
|
+
' --json number,title,updatedAt --limit 50 2>/dev/null || echo "[]")',
|
|
1424
|
+
"",
|
|
1425
|
+
" local ip_count",
|
|
1426
|
+
` ip_count=$(echo "$ip_issues" | jq 'length')`,
|
|
1427
|
+
"",
|
|
1428
|
+
' if [[ "$ip_count" -gt 0 ]]; then',
|
|
1429
|
+
" local ip_threshold",
|
|
1430
|
+
" ip_threshold=$(date -u -v-${STALE_IN_PROGRESS_HOURS}H +%Y-%m-%dT%H:%M:%S 2>/dev/null \\",
|
|
1431
|
+
' || date -u -d "${STALE_IN_PROGRESS_HOURS} hours ago" +%Y-%m-%dT%H:%M:%S 2>/dev/null)',
|
|
1432
|
+
"",
|
|
1433
|
+
" local ip_data",
|
|
1434
|
+
` ip_data=$(echo "$ip_issues" | jq -r '.[] | "\\(.number)\\t\\(.title)\\t\\(.updatedAt)"')`,
|
|
1435
|
+
"",
|
|
1436
|
+
" while IFS=$'\\t' read -r num title updated; do",
|
|
1437
|
+
' [[ -z "$num" ]] && continue',
|
|
1438
|
+
' local updated_trimmed="${updated%%+*}"',
|
|
1439
|
+
' updated_trimmed="${updated_trimmed%%Z*}"',
|
|
1440
|
+
' if [[ "$updated_trimmed" < "$ip_threshold" ]]; then',
|
|
1441
|
+
' local date_part="${updated_trimmed%%T*}"',
|
|
1442
|
+
' echo "STALE #${num} \u2014 no activity since ${date_part} \u2014 \\"${title}\\""',
|
|
1443
|
+
" fi",
|
|
1444
|
+
' done <<< "$ip_data"',
|
|
1445
|
+
" fi",
|
|
1446
|
+
"",
|
|
1447
|
+
" # Check blocked issues",
|
|
1448
|
+
" local bl_issues",
|
|
1449
|
+
' bl_issues=$(gh issue list --label "status:blocked" --state open \\',
|
|
1450
|
+
' --json number,title,updatedAt --limit 50 2>/dev/null || echo "[]")',
|
|
1451
|
+
"",
|
|
1452
|
+
" local bl_count",
|
|
1453
|
+
` bl_count=$(echo "$bl_issues" | jq 'length')`,
|
|
1454
|
+
"",
|
|
1455
|
+
' if [[ "$bl_count" -gt 0 ]]; then',
|
|
1456
|
+
" local bl_threshold",
|
|
1457
|
+
" bl_threshold=$(date -u -v-${STALE_BLOCKED_HOURS}H +%Y-%m-%dT%H:%M:%S 2>/dev/null \\",
|
|
1458
|
+
' || date -u -d "${STALE_BLOCKED_HOURS} hours ago" +%Y-%m-%dT%H:%M:%S 2>/dev/null)',
|
|
1459
|
+
"",
|
|
1460
|
+
" local bl_data",
|
|
1461
|
+
` bl_data=$(echo "$bl_issues" | jq -r '.[] | "\\(.number)\\t\\(.title)\\t\\(.updatedAt)"')`,
|
|
1462
|
+
"",
|
|
1463
|
+
" while IFS=$'\\t' read -r num title updated; do",
|
|
1464
|
+
' [[ -z "$num" ]] && continue',
|
|
1465
|
+
' local updated_trimmed="${updated%%+*}"',
|
|
1466
|
+
' updated_trimmed="${updated_trimmed%%Z*}"',
|
|
1467
|
+
' if [[ "$updated_trimmed" < "$bl_threshold" ]]; then',
|
|
1468
|
+
' local date_part="${updated_trimmed%%T*}"',
|
|
1469
|
+
' echo "STALE_BLOCKED #${num} \u2014 blocked since ${date_part} \u2014 \\"${title}\\""',
|
|
1470
|
+
" fi",
|
|
1471
|
+
' done <<< "$bl_data"',
|
|
1472
|
+
" fi",
|
|
1473
|
+
"",
|
|
1474
|
+
' if [[ "$ip_count" -eq 0 && "$bl_count" -eq 0 ]]; then',
|
|
1475
|
+
' echo "NO_STALE_ISSUES"',
|
|
1476
|
+
" fi",
|
|
1477
|
+
"}",
|
|
1478
|
+
"",
|
|
1479
|
+
"cmd_orphaned() {",
|
|
1480
|
+
" # Check for remote branches whose issues are closed or missing",
|
|
1481
|
+
" git fetch --prune origin 2>/dev/null",
|
|
1482
|
+
" local branches",
|
|
1483
|
+
' branches=$(git branch -r --format="%(refname:short)" | grep -v HEAD | sed "s|origin/||")',
|
|
1484
|
+
"",
|
|
1485
|
+
" local found_orphan=false",
|
|
1486
|
+
" while IFS= read -r branch; do",
|
|
1487
|
+
' [[ -z "$branch" ]] && continue',
|
|
1488
|
+
' [[ "$branch" == "main" || "$branch" == "master" ]] && continue',
|
|
1489
|
+
"",
|
|
1490
|
+
" # Extract issue number from branch name (e.g., feat/42-add-login \u2192 42)",
|
|
1491
|
+
" local issue_num",
|
|
1492
|
+
` issue_num=$(echo "$branch" | grep -oE '/[0-9]+' | tr -d '/' | head -1)`,
|
|
1493
|
+
' [[ -z "$issue_num" ]] && continue',
|
|
1494
|
+
"",
|
|
1495
|
+
" local state",
|
|
1496
|
+
` state=$(gh issue view "$issue_num" --json state --jq '.state' 2>/dev/null || echo "NOT_FOUND")`,
|
|
1497
|
+
"",
|
|
1498
|
+
' if [[ "$state" == "CLOSED" ]]; then',
|
|
1499
|
+
" found_orphan=true",
|
|
1500
|
+
' echo "ORPHAN_BRANCH ${branch} \u2014 issue #${issue_num} is closed"',
|
|
1501
|
+
' elif [[ "$state" == "NOT_FOUND" ]]; then',
|
|
1502
|
+
" found_orphan=true",
|
|
1503
|
+
' echo "ORPHAN_BRANCH ${branch} \u2014 issue #${issue_num} not found"',
|
|
1504
|
+
" fi",
|
|
1505
|
+
' done <<< "$branches"',
|
|
1506
|
+
"",
|
|
1507
|
+
" # Check for open PRs whose linked issues are closed",
|
|
1508
|
+
" local prs",
|
|
1509
|
+
' prs=$(gh pr list --state open --json number,title,body --limit 50 2>/dev/null || echo "[]")',
|
|
1510
|
+
" local pr_data",
|
|
1511
|
+
` pr_data=$(echo "$prs" | jq -r '`,
|
|
1512
|
+
" .[] |",
|
|
1513
|
+
' (.body | capture("(?:Closes|Fixes|Resolves) #(?<num>[0-9]+)") | .num) as $issue |',
|
|
1514
|
+
' "\\(.number)\\t\\(.title)\\t\\($issue // "")"',
|
|
1515
|
+
" ' 2>/dev/null)",
|
|
1516
|
+
"",
|
|
1517
|
+
" while IFS=$'\\t' read -r pr_num title issue_num; do",
|
|
1518
|
+
' [[ -z "$pr_num" || -z "$issue_num" ]] && continue',
|
|
1519
|
+
' if is_closed "$issue_num"; then',
|
|
1520
|
+
" found_orphan=true",
|
|
1521
|
+
' echo "ORPHAN_PR #${pr_num} \u2014 linked issue #${issue_num} is closed \u2014 \\"${title}\\""',
|
|
1522
|
+
" fi",
|
|
1523
|
+
' done <<< "$pr_data"',
|
|
1524
|
+
"",
|
|
1525
|
+
" if ! $found_orphan; then",
|
|
1526
|
+
' echo "NO_ORPHANED_RESOURCES"',
|
|
1527
|
+
" fi",
|
|
1528
|
+
"}",
|
|
1529
|
+
"",
|
|
1530
|
+
"cmd_prs() {",
|
|
1531
|
+
" local prs",
|
|
1532
|
+
" prs=$(gh pr list --state open --json number,title,isDraft,headRefName,labels,body \\",
|
|
1533
|
+
' --limit 50 2>/dev/null || echo "[]")',
|
|
1534
|
+
"",
|
|
1535
|
+
" local count",
|
|
1536
|
+
` count=$(echo "$prs" | jq 'length')`,
|
|
1537
|
+
' if [[ "$count" -eq 0 ]]; then',
|
|
1538
|
+
' echo "NO_OPEN_PRS"',
|
|
1539
|
+
" return 0",
|
|
1540
|
+
" fi",
|
|
1541
|
+
"",
|
|
1542
|
+
" # Filter: not draft, no needs-attention label.",
|
|
1543
|
+
" local eligible",
|
|
1544
|
+
` eligible=$(echo "$prs" | jq -r '`,
|
|
1545
|
+
" .[] |",
|
|
1546
|
+
" select(.isDraft == false) |",
|
|
1547
|
+
' select(.labels | map(.name) | index("status:needs-attention") | not) |',
|
|
1548
|
+
' (.body | capture("(?:Closes|Fixes|Resolves) #(?<num>[0-9]+)") | .num) as $issue |',
|
|
1549
|
+
' "\\(.number)\\t\\(.title)\\t\\(.headRefName)\\t\\($issue // "")"',
|
|
1550
|
+
" ' 2>/dev/null)",
|
|
1551
|
+
"",
|
|
1552
|
+
' if [[ -z "$eligible" ]]; then',
|
|
1553
|
+
' echo "NO_ELIGIBLE_PRS"',
|
|
1554
|
+
" return 0",
|
|
1555
|
+
" fi",
|
|
1556
|
+
"",
|
|
1557
|
+
" while IFS=$'\\t' read -r pr_num title branch issue_num; do",
|
|
1558
|
+
' [[ -z "$pr_num" ]] && continue',
|
|
1559
|
+
"",
|
|
1560
|
+
' if [[ -z "$issue_num" ]]; then',
|
|
1561
|
+
' echo "SKIP PR #${pr_num} \u2014 no linked issue \u2014 \\"${title}\\""',
|
|
1562
|
+
" continue",
|
|
1563
|
+
" fi",
|
|
1564
|
+
"",
|
|
1565
|
+
" # Check CI status",
|
|
1566
|
+
" local failing_checks",
|
|
1567
|
+
' failing_checks=$(gh pr checks "$pr_num" --json name,state \\',
|
|
1568
|
+
` --jq '[.[] | select(.state != "SUCCESS" and .state != "SKIPPED")] | length' 2>/dev/null || echo "-1")`,
|
|
1569
|
+
"",
|
|
1570
|
+
' if [[ "$failing_checks" == "-1" ]]; then',
|
|
1571
|
+
' echo "SKIP PR #${pr_num} \u2014 could not read CI status \u2014 \\"${title}\\""',
|
|
1572
|
+
" continue",
|
|
1573
|
+
" fi",
|
|
1574
|
+
"",
|
|
1575
|
+
' if [[ "$failing_checks" -gt 0 ]]; then',
|
|
1576
|
+
' echo "SKIP PR #${pr_num} \u2014 ${failing_checks} CI check(s) not passing \u2014 \\"${title}\\""',
|
|
1577
|
+
" continue",
|
|
1578
|
+
" fi",
|
|
1579
|
+
"",
|
|
1580
|
+
" # Check if already approved",
|
|
1581
|
+
" local approved",
|
|
1582
|
+
' approved=$(gh pr view "$pr_num" --json reviews \\',
|
|
1583
|
+
` --jq '[.reviews[] | select(.state == "APPROVED")] | length' 2>/dev/null || echo "0")`,
|
|
1584
|
+
' if [[ "$approved" -gt 0 ]]; then',
|
|
1585
|
+
' echo "SKIP PR #${pr_num} \u2014 already approved \u2014 \\"${title}\\""',
|
|
1586
|
+
" continue",
|
|
1587
|
+
" fi",
|
|
1588
|
+
"",
|
|
1589
|
+
' echo "REVIEW PR #${pr_num} issue:#${issue_num} branch:${branch} \u2014 \\"${title}\\""',
|
|
1590
|
+
' done <<< "$eligible"',
|
|
1591
|
+
"}",
|
|
1592
|
+
"",
|
|
1593
|
+
"# \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",
|
|
1594
|
+
"",
|
|
1595
|
+
'case "${1:-help}" in',
|
|
1596
|
+
' unblock) shift; cmd_unblock "$@" ;;',
|
|
1597
|
+
' eligible) shift; cmd_eligible "$@" ;;',
|
|
1598
|
+
' stale) shift; cmd_stale "$@" ;;',
|
|
1599
|
+
' orphaned) shift; cmd_orphaned "$@" ;;',
|
|
1600
|
+
' prs) shift; cmd_prs "$@" ;;',
|
|
1601
|
+
" help|*)",
|
|
1602
|
+
' echo "Usage: check-blocked.sh <unblock|eligible|stale|orphaned|prs>"',
|
|
1603
|
+
" exit 1",
|
|
1604
|
+
" ;;",
|
|
1605
|
+
"esac"
|
|
1606
|
+
].join("\n")
|
|
1607
|
+
};
|
|
1608
|
+
var orchestratorSubAgent = {
|
|
1609
|
+
name: "orchestrator",
|
|
1610
|
+
description: "Pipeline manager that reviews PRs, triages issues, and identifies the next work item \u2014 never implements code",
|
|
1611
|
+
model: AGENT_MODEL.POWERFUL,
|
|
1612
|
+
maxTurns: 100,
|
|
1613
|
+
platforms: { cursor: { exclude: true } },
|
|
1614
|
+
prompt: [
|
|
1615
|
+
"# Orchestrator Agent",
|
|
1616
|
+
"",
|
|
1617
|
+
"You are a pipeline manager for the **{{repository.owner}}/{{repository.name}}** repository.",
|
|
1618
|
+
"You review PRs, triage issues, and identify the next work item. You **never**",
|
|
1619
|
+
"implement code, create branches for issues, or claim issues.",
|
|
1620
|
+
"",
|
|
1621
|
+
"Run the same loop every invocation. Execute all phases in order.",
|
|
1622
|
+
"",
|
|
1623
|
+
"---",
|
|
1624
|
+
"",
|
|
1625
|
+
"## Phase A: Startup",
|
|
1626
|
+
"",
|
|
1627
|
+
"```bash",
|
|
1628
|
+
"git checkout main && git pull origin main",
|
|
1629
|
+
"```",
|
|
1630
|
+
"",
|
|
1631
|
+
"## Phase B: Batch PR Review",
|
|
1632
|
+
"",
|
|
1633
|
+
"Find all PRs eligible for review:",
|
|
1634
|
+
"",
|
|
1635
|
+
"```bash",
|
|
1636
|
+
".claude/procedures/check-blocked.sh prs",
|
|
1637
|
+
"```",
|
|
1638
|
+
"",
|
|
1639
|
+
"For each `REVIEW PR #N issue:#M branch:<branch>` line:",
|
|
1640
|
+
"",
|
|
1641
|
+
"1. Check out the PR branch: `gh pr checkout N`",
|
|
1642
|
+
"2. Review the PR:",
|
|
1643
|
+
" - Read the PR description and linked issue: `gh pr view N`",
|
|
1644
|
+
" - Review the diff: `gh pr diff N`",
|
|
1645
|
+
" - Check all changed files for correctness, conventions, and test coverage",
|
|
1646
|
+
" - Verify PR conventions: conventional commit title, closing keyword, summary present",
|
|
1647
|
+
" - Check CI status: `gh pr checks N`",
|
|
1648
|
+
"3. If the PR passes review:",
|
|
1649
|
+
" - Approve: `gh pr review N --approve --body '<summary>'`",
|
|
1650
|
+
" - Enable auto-merge: `gh pr merge N --auto --squash`",
|
|
1651
|
+
"4. If the PR fails review:",
|
|
1652
|
+
" - Request changes: `gh pr review N --request-changes --body '<findings>'`",
|
|
1653
|
+
"5. After each PR (whether merged or not):",
|
|
1654
|
+
" ```bash",
|
|
1655
|
+
" git checkout main && git pull origin main",
|
|
1656
|
+
" ```",
|
|
1657
|
+
"",
|
|
1658
|
+
"Skip lines starting with `SKIP` \u2014 those PRs are not eligible.",
|
|
1659
|
+
"If output is `NO_OPEN_PRS` or `NO_ELIGIBLE_PRS`, skip to Phase C.",
|
|
1660
|
+
"",
|
|
1661
|
+
"## Phase C: Triage \u2014 Unblock",
|
|
1662
|
+
"",
|
|
1663
|
+
"Check for blocked issues whose dependencies have resolved:",
|
|
1664
|
+
"",
|
|
1665
|
+
"```bash",
|
|
1666
|
+
".claude/procedures/check-blocked.sh unblock",
|
|
1667
|
+
"```",
|
|
1668
|
+
"",
|
|
1669
|
+
"For each `UNBLOCK #N` line:",
|
|
1670
|
+
"```bash",
|
|
1671
|
+
'gh issue edit N --remove-label "status:blocked" --add-label "status:ready"',
|
|
1672
|
+
'gh issue comment N --body "Dependencies resolved \u2014 unblocking."',
|
|
1673
|
+
"```",
|
|
1674
|
+
"",
|
|
1675
|
+
"For `BLOCKED #N \u2014 no Depends on field found`: leave as-is (Phase D will",
|
|
1676
|
+
"catch it if it's been blocked too long).",
|
|
1677
|
+
"",
|
|
1678
|
+
"If output is `NO_BLOCKED_ISSUES`, skip to Phase D.",
|
|
1679
|
+
"",
|
|
1680
|
+
"## Phase D: Maintenance",
|
|
1681
|
+
"",
|
|
1682
|
+
"### D1: Stale Detection",
|
|
1683
|
+
"",
|
|
1684
|
+
"```bash",
|
|
1685
|
+
".claude/procedures/check-blocked.sh stale",
|
|
1686
|
+
"```",
|
|
1687
|
+
"",
|
|
1688
|
+
"For each `STALE #N` line (in-progress >72h without activity):",
|
|
1689
|
+
"```bash",
|
|
1690
|
+
'gh issue edit N --add-label "status:needs-attention"',
|
|
1691
|
+
'gh issue comment N --body "Flagged: in-progress for >3 days with no activity."',
|
|
1692
|
+
"```",
|
|
1693
|
+
"",
|
|
1694
|
+
"For each `STALE_BLOCKED #N` line (blocked >168h):",
|
|
1695
|
+
"```bash",
|
|
1696
|
+
'gh issue edit N --add-label "status:needs-attention"',
|
|
1697
|
+
'gh issue comment N --body "Flagged: blocked for >7 days \u2014 may need human intervention."',
|
|
1698
|
+
"```",
|
|
1699
|
+
"",
|
|
1700
|
+
"**Important:** Do NOT auto-reset stale issues to `status:ready` \u2014 partial",
|
|
1701
|
+
"implementation work may exist on a branch.",
|
|
1702
|
+
"",
|
|
1703
|
+
"### D2: Orphaned Detection",
|
|
1704
|
+
"",
|
|
1705
|
+
"```bash",
|
|
1706
|
+
".claude/procedures/check-blocked.sh orphaned",
|
|
1707
|
+
"```",
|
|
1708
|
+
"",
|
|
1709
|
+
"Report any `ORPHAN_BRANCH` or `ORPHAN_PR` lines. These indicate branches",
|
|
1710
|
+
"or PRs whose linked issues are closed or missing. Log them for visibility",
|
|
1711
|
+
"but do not delete branches automatically.",
|
|
1712
|
+
"",
|
|
1713
|
+
"### D3: Needs-Attention Summary",
|
|
1714
|
+
"",
|
|
1715
|
+
"List all issues currently flagged:",
|
|
1716
|
+
"```bash",
|
|
1717
|
+
'gh issue list --label "status:needs-attention" --state open --json number,title',
|
|
1718
|
+
"```",
|
|
1719
|
+
"",
|
|
1720
|
+
"Log the count and titles for operator visibility.",
|
|
1721
|
+
"",
|
|
1722
|
+
"## Phase E: Queue Scan",
|
|
1723
|
+
"",
|
|
1724
|
+
"Find the highest-priority ready issue:",
|
|
1725
|
+
"",
|
|
1726
|
+
"```bash",
|
|
1727
|
+
".claude/procedures/check-blocked.sh eligible",
|
|
1728
|
+
"```",
|
|
1729
|
+
"",
|
|
1730
|
+
"If a `PICK` line is returned, report it as:",
|
|
1731
|
+
"```",
|
|
1732
|
+
'NEXT_WORK_ITEM #<number> priority:<level> type:<label> "<title>"',
|
|
1733
|
+
"```",
|
|
1734
|
+
"",
|
|
1735
|
+
"If output is `NO_READY_ISSUES`, report that the queue is empty.",
|
|
1736
|
+
"",
|
|
1737
|
+
"**Do NOT claim the issue, create a branch, or start implementation.**",
|
|
1738
|
+
"The worker agent handles that.",
|
|
1739
|
+
"",
|
|
1740
|
+
"## Phase F: Cleanup",
|
|
1741
|
+
"",
|
|
1742
|
+
"```bash",
|
|
1743
|
+
"git checkout main && git pull origin main",
|
|
1744
|
+
"git fetch --prune origin",
|
|
1745
|
+
"```",
|
|
1746
|
+
"",
|
|
1747
|
+
"Log completion: phases executed, PRs reviewed, issues unblocked,",
|
|
1748
|
+
"stale issues flagged, and next work item (if any).",
|
|
1749
|
+
"",
|
|
1750
|
+
"---",
|
|
1751
|
+
"",
|
|
1752
|
+
"## Rules",
|
|
1753
|
+
"",
|
|
1754
|
+
"1. **Never implement code.** You triage, review, and report \u2014 you do not code.",
|
|
1755
|
+
"2. **Never claim issues.** Do not add `status:in-progress` or create branches for issues.",
|
|
1756
|
+
"3. **Always use check-blocked.sh.** All triage queries go through the shell script for token efficiency.",
|
|
1757
|
+
"4. **Follow CLAUDE.md conventions** for all git and gh operations.",
|
|
1758
|
+
"5. **Priority order:** critical > high > medium > low > trivial, then FIFO by issue number."
|
|
1759
|
+
].join("\n")
|
|
1760
|
+
};
|
|
1761
|
+
var orchestratorBundle = {
|
|
1762
|
+
name: "orchestrator",
|
|
1763
|
+
description: "Pipeline orchestrator agent for issue triage, PR review, and queue management",
|
|
1764
|
+
// Always included by default
|
|
1765
|
+
appliesWhen: () => true,
|
|
1766
|
+
rules: [
|
|
1767
|
+
{
|
|
1768
|
+
name: "orchestrator-conventions",
|
|
1769
|
+
description: "Guidelines for orchestrator agent behavior and pipeline management",
|
|
1770
|
+
scope: AGENT_RULE_SCOPE.ALWAYS,
|
|
1771
|
+
content: [
|
|
1772
|
+
"# Orchestrator Conventions",
|
|
1773
|
+
"",
|
|
1774
|
+
"When running the orchestrator agent (`.claude/agents/orchestrator.md`):",
|
|
1775
|
+
"",
|
|
1776
|
+
"- The orchestrator **never** implements code or creates branches for issues",
|
|
1777
|
+
"- It reviews PRs, triages issues, and reports the next work item",
|
|
1778
|
+
"- All triage queries use `.claude/procedures/check-blocked.sh` for token efficiency",
|
|
1779
|
+
"- Priority order: critical > high > medium > low > trivial, then FIFO",
|
|
1780
|
+
"- Stale thresholds: 72h for in-progress, 168h for blocked",
|
|
1781
|
+
"- Flagged issues get `status:needs-attention` \u2014 they are not auto-reset"
|
|
1782
|
+
].join("\n"),
|
|
1783
|
+
platforms: {
|
|
1784
|
+
claude: { target: "claude-md" },
|
|
1785
|
+
cursor: { exclude: true }
|
|
1786
|
+
},
|
|
1787
|
+
tags: ["workflow"]
|
|
1788
|
+
}
|
|
1789
|
+
],
|
|
1790
|
+
subAgents: [orchestratorSubAgent],
|
|
1791
|
+
procedures: [checkBlockedProcedure],
|
|
1792
|
+
claudePermissions: {
|
|
1793
|
+
allow: [
|
|
1794
|
+
// Allow executing the check-blocked.sh procedure
|
|
1795
|
+
"Bash(.claude/procedures/*.sh *)"
|
|
1796
|
+
]
|
|
1797
|
+
}
|
|
1798
|
+
};
|
|
1799
|
+
|
|
1248
1800
|
// src/pnpm/pnpm-workspace.ts
|
|
1249
1801
|
import { relative } from "path";
|
|
1250
1802
|
import { Component, YamlFile } from "projen";
|
|
@@ -2262,7 +2814,8 @@ var BUILT_IN_BUNDLES = [
|
|
|
2262
2814
|
projenBundle,
|
|
2263
2815
|
githubWorkflowBundle,
|
|
2264
2816
|
slackBundle,
|
|
2265
|
-
meetingAnalysisBundle
|
|
2817
|
+
meetingAnalysisBundle,
|
|
2818
|
+
orchestratorBundle
|
|
2266
2819
|
];
|
|
2267
2820
|
|
|
2268
2821
|
// src/agent/bundles/scope.ts
|
|
@@ -2385,12 +2938,15 @@ var ClaudeRenderer = class _ClaudeRenderer {
|
|
|
2385
2938
|
/**
|
|
2386
2939
|
* Render all Claude Code configuration files.
|
|
2387
2940
|
*/
|
|
2388
|
-
static render(component, rules, skills, subAgents, mcpServers, settings) {
|
|
2941
|
+
static render(component, rules, skills, subAgents, mcpServers, settings, procedures) {
|
|
2389
2942
|
_ClaudeRenderer.renderClaudeMd(component, rules);
|
|
2390
2943
|
_ClaudeRenderer.renderScopedRules(component, rules);
|
|
2391
2944
|
_ClaudeRenderer.renderSettings(component, mcpServers, settings);
|
|
2392
2945
|
_ClaudeRenderer.renderSkills(component, skills);
|
|
2393
2946
|
_ClaudeRenderer.renderSubAgents(component, subAgents);
|
|
2947
|
+
if (procedures && procedures.length > 0) {
|
|
2948
|
+
_ClaudeRenderer.renderProcedures(component, procedures);
|
|
2949
|
+
}
|
|
2394
2950
|
}
|
|
2395
2951
|
static renderClaudeMd(component, rules) {
|
|
2396
2952
|
const claudeMdRules = rules.filter((r) => {
|
|
@@ -2707,6 +3263,14 @@ var ClaudeRenderer = class _ClaudeRenderer {
|
|
|
2707
3263
|
if (config.env) server.env = { ...config.env };
|
|
2708
3264
|
return server;
|
|
2709
3265
|
}
|
|
3266
|
+
static renderProcedures(component, procedures) {
|
|
3267
|
+
for (const proc of procedures) {
|
|
3268
|
+
new TextFile2(component, `.claude/procedures/${proc.name}`, {
|
|
3269
|
+
lines: proc.content.split("\n"),
|
|
3270
|
+
executable: true
|
|
3271
|
+
});
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
2710
3274
|
/**
|
|
2711
3275
|
* Determine the default Claude rule target based on rule scope.
|
|
2712
3276
|
* ALWAYS-scoped rules default to CLAUDE_MD; FILE_PATTERN rules default to SCOPED_FILE.
|
|
@@ -3060,6 +3624,7 @@ var AgentConfig = class _AgentConfig extends Component8 {
|
|
|
3060
3624
|
const rules = this.resolveRules();
|
|
3061
3625
|
const skills = this.resolveSkills();
|
|
3062
3626
|
const subAgents = this.resolveSubAgents();
|
|
3627
|
+
const procedures = this.resolveProcedures();
|
|
3063
3628
|
const mcpServers = this.options.mcpServers ?? {};
|
|
3064
3629
|
const projectMetadata = ProjectMetadata.of(this.project);
|
|
3065
3630
|
const metadata = projectMetadata?.metadata;
|
|
@@ -3069,6 +3634,10 @@ var AgentConfig = class _AgentConfig extends Component8 {
|
|
|
3069
3634
|
subAgents,
|
|
3070
3635
|
metadata
|
|
3071
3636
|
);
|
|
3637
|
+
const resolvedProcedures = this.resolveProcedureTemplates(
|
|
3638
|
+
procedures,
|
|
3639
|
+
metadata
|
|
3640
|
+
);
|
|
3072
3641
|
if (platforms.includes(AGENT_PLATFORM.CURSOR)) {
|
|
3073
3642
|
CursorRenderer.render(
|
|
3074
3643
|
this,
|
|
@@ -3090,7 +3659,8 @@ var AgentConfig = class _AgentConfig extends Component8 {
|
|
|
3090
3659
|
_AgentConfig.mergeClaudeDefaults(
|
|
3091
3660
|
this.options.claudeSettings,
|
|
3092
3661
|
bundlePermissions
|
|
3093
|
-
)
|
|
3662
|
+
),
|
|
3663
|
+
resolvedProcedures
|
|
3094
3664
|
);
|
|
3095
3665
|
}
|
|
3096
3666
|
if (platforms.includes(AGENT_PLATFORM.CODEX)) {
|
|
@@ -3203,6 +3773,16 @@ ${extra}`
|
|
|
3203
3773
|
}
|
|
3204
3774
|
}
|
|
3205
3775
|
}
|
|
3776
|
+
if (this.options.includeBundles) {
|
|
3777
|
+
for (const bundleName of this.options.includeBundles) {
|
|
3778
|
+
const bundle = BUILT_IN_BUNDLES.find((b) => b.name === bundleName);
|
|
3779
|
+
if (bundle?.skills) {
|
|
3780
|
+
for (const skill of bundle.skills) {
|
|
3781
|
+
skillMap.set(skill.name, skill);
|
|
3782
|
+
}
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3785
|
+
}
|
|
3206
3786
|
if (this.options.skills) {
|
|
3207
3787
|
for (const skill of this.options.skills) {
|
|
3208
3788
|
skillMap.set(skill.name, skill);
|
|
@@ -3222,6 +3802,16 @@ ${extra}`
|
|
|
3222
3802
|
}
|
|
3223
3803
|
}
|
|
3224
3804
|
}
|
|
3805
|
+
if (this.options.includeBundles) {
|
|
3806
|
+
for (const bundleName of this.options.includeBundles) {
|
|
3807
|
+
const bundle = BUILT_IN_BUNDLES.find((b) => b.name === bundleName);
|
|
3808
|
+
if (bundle?.subAgents) {
|
|
3809
|
+
for (const agent of bundle.subAgents) {
|
|
3810
|
+
agentMap.set(agent.name, agent);
|
|
3811
|
+
}
|
|
3812
|
+
}
|
|
3813
|
+
}
|
|
3814
|
+
}
|
|
3225
3815
|
if (this.options.subAgents) {
|
|
3226
3816
|
for (const agent of this.options.subAgents) {
|
|
3227
3817
|
agentMap.set(agent.name, agent);
|
|
@@ -3229,6 +3819,35 @@ ${extra}`
|
|
|
3229
3819
|
}
|
|
3230
3820
|
return [...agentMap.values()];
|
|
3231
3821
|
}
|
|
3822
|
+
resolveProcedures() {
|
|
3823
|
+
const procMap = /* @__PURE__ */ new Map();
|
|
3824
|
+
if (this.options.autoDetectBundles !== false) {
|
|
3825
|
+
for (const bundle of BUILT_IN_BUNDLES) {
|
|
3826
|
+
if (this.options.excludeBundles?.includes(bundle.name)) continue;
|
|
3827
|
+
if (bundle.appliesWhen(this.project) && bundle.procedures) {
|
|
3828
|
+
for (const proc of bundle.procedures) {
|
|
3829
|
+
procMap.set(proc.name, proc);
|
|
3830
|
+
}
|
|
3831
|
+
}
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
if (this.options.includeBundles) {
|
|
3835
|
+
for (const bundleName of this.options.includeBundles) {
|
|
3836
|
+
const bundle = BUILT_IN_BUNDLES.find((b) => b.name === bundleName);
|
|
3837
|
+
if (bundle?.procedures) {
|
|
3838
|
+
for (const proc of bundle.procedures) {
|
|
3839
|
+
procMap.set(proc.name, proc);
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
3844
|
+
if (this.options.procedures) {
|
|
3845
|
+
for (const proc of this.options.procedures) {
|
|
3846
|
+
procMap.set(proc.name, proc);
|
|
3847
|
+
}
|
|
3848
|
+
}
|
|
3849
|
+
return [...procMap.values()];
|
|
3850
|
+
}
|
|
3232
3851
|
/**
|
|
3233
3852
|
* Resolves template variables in rule content using project metadata.
|
|
3234
3853
|
* Emits synthesis warnings for rules with unresolved variables.
|
|
@@ -3281,6 +3900,23 @@ ${extra}`
|
|
|
3281
3900
|
return resolved !== agent.prompt ? { ...agent, prompt: resolved } : agent;
|
|
3282
3901
|
});
|
|
3283
3902
|
}
|
|
3903
|
+
/**
|
|
3904
|
+
* Resolves template variables in procedure content using project metadata.
|
|
3905
|
+
*/
|
|
3906
|
+
resolveProcedureTemplates(procedures, metadata) {
|
|
3907
|
+
return procedures.map((proc) => {
|
|
3908
|
+
const { resolved, unresolvedKeys } = resolveTemplateVariables(
|
|
3909
|
+
proc.content,
|
|
3910
|
+
metadata
|
|
3911
|
+
);
|
|
3912
|
+
if (unresolvedKeys.length > 0) {
|
|
3913
|
+
this.project.logger.warn(
|
|
3914
|
+
`AgentConfig: ProjectMetadata not found; procedure '${proc.name}' using default values`
|
|
3915
|
+
);
|
|
3916
|
+
}
|
|
3917
|
+
return resolved !== proc.content ? { ...proc, content: resolved } : proc;
|
|
3918
|
+
});
|
|
3919
|
+
}
|
|
3284
3920
|
/**
|
|
3285
3921
|
* Collects Claude permission entries from all active bundles.
|
|
3286
3922
|
*/
|
|
@@ -4968,6 +5604,7 @@ export {
|
|
|
4968
5604
|
githubWorkflowBundle,
|
|
4969
5605
|
jestBundle,
|
|
4970
5606
|
meetingAnalysisBundle,
|
|
5607
|
+
orchestratorBundle,
|
|
4971
5608
|
pnpmBundle,
|
|
4972
5609
|
projenBundle,
|
|
4973
5610
|
resolveModelAlias,
|