@codedrifters/configulator 0.0.182 → 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 +55 -1
- package/lib/index.d.ts +56 -2
- package/lib/index.js +956 -10
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +953 -10
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
package/lib/index.mjs
CHANGED
|
@@ -197,6 +197,17 @@ var AGENT_MODEL = {
|
|
|
197
197
|
BALANCED: "balanced",
|
|
198
198
|
POWERFUL: "powerful"
|
|
199
199
|
};
|
|
200
|
+
function resolveModelAlias(model) {
|
|
201
|
+
if (!model || model === AGENT_MODEL.INHERIT) {
|
|
202
|
+
return void 0;
|
|
203
|
+
}
|
|
204
|
+
const mapping = {
|
|
205
|
+
[AGENT_MODEL.POWERFUL]: "opus",
|
|
206
|
+
[AGENT_MODEL.BALANCED]: "sonnet",
|
|
207
|
+
[AGENT_MODEL.FAST]: "haiku"
|
|
208
|
+
};
|
|
209
|
+
return mapping[model] ?? model;
|
|
210
|
+
}
|
|
200
211
|
var MCP_TRANSPORT = {
|
|
201
212
|
STDIO: "stdio",
|
|
202
213
|
HTTP: "http",
|
|
@@ -943,6 +954,849 @@ var jestBundle = {
|
|
|
943
954
|
}
|
|
944
955
|
};
|
|
945
956
|
|
|
957
|
+
// src/agent/bundles/meeting-analysis.ts
|
|
958
|
+
var meetingAnalystSubAgent = {
|
|
959
|
+
name: "meeting-analyst",
|
|
960
|
+
description: "Processes meeting transcripts through a 4-phase pipeline: extract, notes, draft, and link",
|
|
961
|
+
model: AGENT_MODEL.POWERFUL,
|
|
962
|
+
maxTurns: 80,
|
|
963
|
+
platforms: { cursor: { exclude: true } },
|
|
964
|
+
prompt: [
|
|
965
|
+
"# Meeting Analyst Agent",
|
|
966
|
+
"",
|
|
967
|
+
"You process meeting transcripts through a structured 4-phase pipeline.",
|
|
968
|
+
"Each phase runs as a **separate agent session**, triggered by its own",
|
|
969
|
+
"GitHub issue with a `meeting:*` phase label. You handle exactly **one",
|
|
970
|
+
"phase per session** \u2014 read the issue to determine which phase to execute.",
|
|
971
|
+
"",
|
|
972
|
+
"## Execution Model",
|
|
973
|
+
"",
|
|
974
|
+
"1. Each meeting produces up to 4 GitHub issues (one per phase)",
|
|
975
|
+
"2. Each issue carries a `meeting:*` label identifying the phase",
|
|
976
|
+
"3. You pick up one issue, execute that phase, commit/push, then close the issue",
|
|
977
|
+
"4. Phase 1 (Extract) creates the downstream phase issues for phases 2-4",
|
|
978
|
+
"5. Each downstream issue includes `Depends on: #N` linking to its predecessor",
|
|
979
|
+
"",
|
|
980
|
+
"## Design Principles",
|
|
981
|
+
"",
|
|
982
|
+
"1. **Extract, don't interpret.** Capture what was said and decided. Flag",
|
|
983
|
+
" ambiguity as open items rather than resolving it.",
|
|
984
|
+
"2. **Route to the right category.** Meeting content maps to requirements,",
|
|
985
|
+
" ADRs, product docs, and business strategy. Each output goes to the",
|
|
986
|
+
" correct location per the project's taxonomy.",
|
|
987
|
+
"3. **Preserve provenance.** Every extracted item links back to the meeting",
|
|
988
|
+
" source so reviewers can check context.",
|
|
989
|
+
"4. **Create issues, not documents.** For requirements and ADRs, create",
|
|
990
|
+
" GitHub issues that other agents or humans will pick up. Do not write",
|
|
991
|
+
" final requirement documents yourself.",
|
|
992
|
+
"5. **Bi-directional traceability.** Every document and issue created by",
|
|
993
|
+
" this pipeline must link back to the meeting source, and the meeting",
|
|
994
|
+
" source documents must link forward to everything created from them.",
|
|
995
|
+
"",
|
|
996
|
+
"---",
|
|
997
|
+
"",
|
|
998
|
+
"## Traceability",
|
|
999
|
+
"",
|
|
1000
|
+
"All outputs must be bi-directionally linked so any artifact can be traced",
|
|
1001
|
+
"back to the meeting that produced it and forward to everything it spawned.",
|
|
1002
|
+
"",
|
|
1003
|
+
"### Backward links (created artifact \u2192 meeting source)",
|
|
1004
|
+
"",
|
|
1005
|
+
"Every GitHub issue and document created by this pipeline must include a",
|
|
1006
|
+
"`## Traceability` section with:",
|
|
1007
|
+
"",
|
|
1008
|
+
"```markdown",
|
|
1009
|
+
"## Traceability",
|
|
1010
|
+
"",
|
|
1011
|
+
"- **Source meeting:** <path to transcript or meeting notes>",
|
|
1012
|
+
"- **Extraction:** <path to extraction file>",
|
|
1013
|
+
"- **Phase issue:** #<N> (the phase issue that created this artifact)",
|
|
1014
|
+
"```",
|
|
1015
|
+
"",
|
|
1016
|
+
"### Forward links (meeting source \u2192 created artifacts)",
|
|
1017
|
+
"",
|
|
1018
|
+
"After Phase 4 (Link) creates all follow-up issues and documents:",
|
|
1019
|
+
"",
|
|
1020
|
+
"1. **Update the extraction file** with a `## Downstream Artifacts` section",
|
|
1021
|
+
" listing every issue and document created from this meeting:",
|
|
1022
|
+
"",
|
|
1023
|
+
" ```markdown",
|
|
1024
|
+
" ## Downstream Artifacts",
|
|
1025
|
+
"",
|
|
1026
|
+
" | Artifact | Type | Issue/Path |",
|
|
1027
|
+
" |----------|------|------------|",
|
|
1028
|
+
" | <title> | requirement / ADR / action-item / profile | #<N> or <path> |",
|
|
1029
|
+
" ```",
|
|
1030
|
+
"",
|
|
1031
|
+
"2. **Update the meeting notes** with a similar `## Related Issues` section",
|
|
1032
|
+
" listing all issues created from this meeting.",
|
|
1033
|
+
"",
|
|
1034
|
+
"3. **Comment on the extract issue** with a summary linking to all created",
|
|
1035
|
+
" artifacts.",
|
|
1036
|
+
"",
|
|
1037
|
+
"### Within-pipeline links",
|
|
1038
|
+
"",
|
|
1039
|
+
"- Phase issues link to predecessors via `Depends on: #N`",
|
|
1040
|
+
"- Phase issues reference the extraction file path in their body",
|
|
1041
|
+
"- Draft documents reference both the extraction and the meeting notes",
|
|
1042
|
+
"",
|
|
1043
|
+
"---",
|
|
1044
|
+
"",
|
|
1045
|
+
"## Phase 1: Extract (`meeting:extract`)",
|
|
1046
|
+
"",
|
|
1047
|
+
"**Goal:** Read the meeting transcript and categorize all substantive content.",
|
|
1048
|
+
"",
|
|
1049
|
+
"### Steps",
|
|
1050
|
+
"",
|
|
1051
|
+
"1. Read the transcript file specified in the issue body.",
|
|
1052
|
+
"2. Identify and categorize content into these buckets:",
|
|
1053
|
+
"",
|
|
1054
|
+
" | Bucket | What to look for |",
|
|
1055
|
+
" |--------|-----------------|",
|
|
1056
|
+
` | **Decisions** | "We decided...", "Let's go with...", explicit choices |`,
|
|
1057
|
+
' | **Requirements** | Feature descriptions, acceptance criteria, "it should..." |',
|
|
1058
|
+
" | **Technology discussions** | Platform comparisons, architecture options, tool evaluations |",
|
|
1059
|
+
' | **Action items** | "[Person] will...", "Next step is...", assigned tasks |',
|
|
1060
|
+
' | **Open questions** | "We need to figure out...", unresolved debates |',
|
|
1061
|
+
" | **People of interest** | Industry contacts, domain experts mentioned |",
|
|
1062
|
+
" | **Companies of interest** | Competitors, vendors, partners discussed |",
|
|
1063
|
+
" | **Strategic direction** | Business model changes, market positioning |",
|
|
1064
|
+
" | **Product direction** | Roadmap changes, feature prioritization |",
|
|
1065
|
+
"",
|
|
1066
|
+
"3. Write the extraction to a markdown file with structured sections:",
|
|
1067
|
+
" - Attendees",
|
|
1068
|
+
" - Decisions Made (with category and confidence: Firm / Tentative / Needs confirmation)",
|
|
1069
|
+
" - Requirements Identified (with category and priority estimate)",
|
|
1070
|
+
" - Technology Discussions (with status: Decided / Leaning toward / Open)",
|
|
1071
|
+
" - Action Items (with assignee and due date if stated)",
|
|
1072
|
+
" - Open Questions",
|
|
1073
|
+
" - People of Interest (with context and whether a profile exists)",
|
|
1074
|
+
" - Companies of Interest (with type and context)",
|
|
1075
|
+
" - Strategic / Product Direction",
|
|
1076
|
+
"",
|
|
1077
|
+
"4. **Create downstream phase issues** using `gh issue create`:",
|
|
1078
|
+
" - Always create a `meeting:notes` issue (blocked on this extract issue)",
|
|
1079
|
+
" - If requirements OR decisions/ADRs identified, create a `meeting:draft` issue",
|
|
1080
|
+
" (blocked on the notes issue)",
|
|
1081
|
+
" - Always create a `meeting:link` issue \u2014 blocked on the draft issue if one",
|
|
1082
|
+
" was created, otherwise blocked on the notes issue",
|
|
1083
|
+
"",
|
|
1084
|
+
"5. Commit, push, and close the extract issue.",
|
|
1085
|
+
"",
|
|
1086
|
+
"---",
|
|
1087
|
+
"",
|
|
1088
|
+
"## Phase 2: Notes (`meeting:notes`)",
|
|
1089
|
+
"",
|
|
1090
|
+
"**Goal:** Transform the extraction into structured meeting notes.",
|
|
1091
|
+
"",
|
|
1092
|
+
"### Steps",
|
|
1093
|
+
"",
|
|
1094
|
+
"1. Read the extraction file referenced in the issue body (output of Phase 1).",
|
|
1095
|
+
"2. Write structured meeting notes with these sections:",
|
|
1096
|
+
" - Meeting metadata (title, date, attendees)",
|
|
1097
|
+
" - Agenda / topics covered",
|
|
1098
|
+
" - Key Discussion Points (organized by topic)",
|
|
1099
|
+
" - Decisions (numbered, with rationale)",
|
|
1100
|
+
" - Action Items (table: who, what, when)",
|
|
1101
|
+
" - Open Questions",
|
|
1102
|
+
" - Follow-up items",
|
|
1103
|
+
"3. Commit, push, and close the notes issue.",
|
|
1104
|
+
"",
|
|
1105
|
+
"---",
|
|
1106
|
+
"",
|
|
1107
|
+
"## Phase 3: Draft (`meeting:draft`)",
|
|
1108
|
+
"",
|
|
1109
|
+
"**Goal:** Draft proposals for requirements, ADRs, and product/strategy updates.",
|
|
1110
|
+
"",
|
|
1111
|
+
"This phase only exists if the extraction identified requirements, architectural",
|
|
1112
|
+
"decisions, or strategy changes. If this issue exists, execute it.",
|
|
1113
|
+
"",
|
|
1114
|
+
"### Steps",
|
|
1115
|
+
"",
|
|
1116
|
+
"1. Read the extraction file from Phase 1.",
|
|
1117
|
+
"2. Check existing requirement registries and ADR registries for duplicates.",
|
|
1118
|
+
"3. Draft requirement proposals with:",
|
|
1119
|
+
" - Category (FR, BR, NFR, etc.)",
|
|
1120
|
+
" - Summary (2-3 sentences)",
|
|
1121
|
+
" - Draft acceptance criteria",
|
|
1122
|
+
" - Related existing requirements",
|
|
1123
|
+
"4. Draft ADR proposals for technology decisions with:",
|
|
1124
|
+
" - Context and problem statement",
|
|
1125
|
+
" - Options discussed",
|
|
1126
|
+
" - Stated preferences or decisions",
|
|
1127
|
+
" - Status: Proposed or Decided",
|
|
1128
|
+
"5. Summarize product/strategy document updates needed.",
|
|
1129
|
+
"6. Commit, push, and close the draft issue.",
|
|
1130
|
+
"",
|
|
1131
|
+
"---",
|
|
1132
|
+
"",
|
|
1133
|
+
"## Phase 4: Link (`meeting:link`)",
|
|
1134
|
+
"",
|
|
1135
|
+
"**Goal:** Create GitHub issues for follow-up work, cross-reference the",
|
|
1136
|
+
"meeting into existing documentation, and complete bi-directional traceability.",
|
|
1137
|
+
"",
|
|
1138
|
+
"### Steps",
|
|
1139
|
+
"",
|
|
1140
|
+
"1. Read the drafts from Phase 3 (if they exist) and the extraction from Phase 1.",
|
|
1141
|
+
"2. Create requirement issues using `gh issue create` with appropriate labels.",
|
|
1142
|
+
" Include a `## Traceability` section in each issue body linking back to",
|
|
1143
|
+
" the source meeting and extraction file.",
|
|
1144
|
+
"3. Create ADR issues if technology decisions need formal records.",
|
|
1145
|
+
" Include a `## Traceability` section in each.",
|
|
1146
|
+
"4. Create action item issues for tasks that are not document-related.",
|
|
1147
|
+
" Include a `## Traceability` section in each.",
|
|
1148
|
+
"5. Cross-reference the meeting in any existing documents that were discussed.",
|
|
1149
|
+
"6. Apply direct product/strategy doc updates for items flagged as **Firm**",
|
|
1150
|
+
" confidence in the extraction.",
|
|
1151
|
+
"7. **Update the extraction file** with a `## Downstream Artifacts` section",
|
|
1152
|
+
" listing every issue and document created from this meeting.",
|
|
1153
|
+
"8. **Update the meeting notes** with a `## Related Issues` section listing",
|
|
1154
|
+
" all issues created from this meeting.",
|
|
1155
|
+
"9. Comment on the parent extract issue with a summary linking to all",
|
|
1156
|
+
" created artifacts.",
|
|
1157
|
+
"10. Commit and push (if any file changes were made), then close the link issue.",
|
|
1158
|
+
"",
|
|
1159
|
+
"---",
|
|
1160
|
+
"",
|
|
1161
|
+
"## GitHub Integration",
|
|
1162
|
+
"",
|
|
1163
|
+
"- Use `gh` CLI for all GitHub operations",
|
|
1164
|
+
"- Apply the appropriate `meeting:*` phase label to each phase issue",
|
|
1165
|
+
"- Use `type:docs` for documentation-producing issues, `type:chore` for",
|
|
1166
|
+
" maintenance/organizational tasks",
|
|
1167
|
+
"- Use `status:` labels to track progress (`status:ready`, `status:in-progress`, `status:done`)",
|
|
1168
|
+
"- Reference the source meeting transcript in all created issues",
|
|
1169
|
+
"- Link phase issues with `Depends on: #N` to enforce ordering",
|
|
1170
|
+
"",
|
|
1171
|
+
"## When to Shorten the Pipeline",
|
|
1172
|
+
"",
|
|
1173
|
+
"- **Short meetings** (<30 min, <2000 words): combine extract + notes into one session",
|
|
1174
|
+
"- **No requirements or decisions**: Phase 1 skips creating the `meeting:draft` issue",
|
|
1175
|
+
"- **Only action items**: extract + notes + link (3 phase issues)"
|
|
1176
|
+
].join("\n")
|
|
1177
|
+
};
|
|
1178
|
+
var processMeetingSkill = {
|
|
1179
|
+
name: "process-meeting",
|
|
1180
|
+
description: "Process a meeting transcript through the 4-phase meeting analysis pipeline",
|
|
1181
|
+
disableModelInvocation: true,
|
|
1182
|
+
userInvocable: true,
|
|
1183
|
+
context: "fork",
|
|
1184
|
+
agent: "meeting-analyst",
|
|
1185
|
+
platforms: { cursor: { exclude: true } },
|
|
1186
|
+
instructions: [
|
|
1187
|
+
"# Process Meeting Transcript",
|
|
1188
|
+
"",
|
|
1189
|
+
"Kick off meeting transcript processing by executing Phase 1 (Extract)",
|
|
1190
|
+
"and creating downstream phase issues for the remaining phases.",
|
|
1191
|
+
"",
|
|
1192
|
+
"## Usage",
|
|
1193
|
+
"",
|
|
1194
|
+
"/process-meeting <path-to-transcript>",
|
|
1195
|
+
"",
|
|
1196
|
+
"## Steps",
|
|
1197
|
+
"",
|
|
1198
|
+
"1. Read the provided transcript file",
|
|
1199
|
+
"2. Execute Phase 1 (Extract) \u2014 categorize transcript content into",
|
|
1200
|
+
" decisions, requirements, action items, open questions, and more",
|
|
1201
|
+
"3. Write the extraction to a markdown file",
|
|
1202
|
+
"4. Create downstream phase issues using `gh issue create`:",
|
|
1203
|
+
" - `meeting:notes` issue (always)",
|
|
1204
|
+
" - `meeting:draft` issue (if requirements or decisions were found)",
|
|
1205
|
+
" - `meeting:link` issue (always)",
|
|
1206
|
+
"5. Each downstream issue includes `Depends on:` linking to its predecessor",
|
|
1207
|
+
"6. Commit and push the extraction file",
|
|
1208
|
+
"",
|
|
1209
|
+
"## Input",
|
|
1210
|
+
"",
|
|
1211
|
+
"Provide a path to a meeting transcript file (text or markdown).",
|
|
1212
|
+
"The transcript should contain speaker-attributed dialogue.",
|
|
1213
|
+
"",
|
|
1214
|
+
"## Output",
|
|
1215
|
+
"",
|
|
1216
|
+
"- An extraction markdown file with categorized meeting content",
|
|
1217
|
+
"- Phase issues with `meeting:*` labels for downstream agent sessions",
|
|
1218
|
+
" to pick up (notes, draft, link)"
|
|
1219
|
+
].join("\n")
|
|
1220
|
+
};
|
|
1221
|
+
var meetingAnalysisBundle = {
|
|
1222
|
+
name: "meeting-analysis",
|
|
1223
|
+
description: "Meeting transcript processing workflow with 4-phase pipeline (extract, notes, draft, link)",
|
|
1224
|
+
appliesWhen: () => true,
|
|
1225
|
+
rules: [
|
|
1226
|
+
{
|
|
1227
|
+
name: "meeting-processing-workflow",
|
|
1228
|
+
description: "Describes the 4-phase meeting processing pipeline, extraction taxonomy, and labeling conventions",
|
|
1229
|
+
scope: AGENT_RULE_SCOPE.ALWAYS,
|
|
1230
|
+
content: [
|
|
1231
|
+
"# Meeting Processing Workflow",
|
|
1232
|
+
"",
|
|
1233
|
+
"Use `/process-meeting <path>` to process a meeting transcript through a",
|
|
1234
|
+
"4-phase pipeline (extract \u2192 notes \u2192 draft \u2192 link). Each phase runs as a",
|
|
1235
|
+
"separate agent session tracked by a GitHub issue with a `meeting:*` label.",
|
|
1236
|
+
"See the `meeting-analyst` agent definition for full workflow details."
|
|
1237
|
+
].join("\n"),
|
|
1238
|
+
platforms: {
|
|
1239
|
+
cursor: { exclude: true }
|
|
1240
|
+
},
|
|
1241
|
+
tags: ["workflow"]
|
|
1242
|
+
}
|
|
1243
|
+
],
|
|
1244
|
+
skills: [processMeetingSkill],
|
|
1245
|
+
subAgents: [meetingAnalystSubAgent]
|
|
1246
|
+
};
|
|
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
|
+
|
|
946
1800
|
// src/pnpm/pnpm-workspace.ts
|
|
947
1801
|
import { relative } from "path";
|
|
948
1802
|
import { Component, YamlFile } from "projen";
|
|
@@ -1959,7 +2813,9 @@ var BUILT_IN_BUNDLES = [
|
|
|
1959
2813
|
awsCdkBundle,
|
|
1960
2814
|
projenBundle,
|
|
1961
2815
|
githubWorkflowBundle,
|
|
1962
|
-
slackBundle
|
|
2816
|
+
slackBundle,
|
|
2817
|
+
meetingAnalysisBundle,
|
|
2818
|
+
orchestratorBundle
|
|
1963
2819
|
];
|
|
1964
2820
|
|
|
1965
2821
|
// src/agent/bundles/scope.ts
|
|
@@ -2082,12 +2938,15 @@ var ClaudeRenderer = class _ClaudeRenderer {
|
|
|
2082
2938
|
/**
|
|
2083
2939
|
* Render all Claude Code configuration files.
|
|
2084
2940
|
*/
|
|
2085
|
-
static render(component, rules, skills, subAgents, mcpServers, settings) {
|
|
2941
|
+
static render(component, rules, skills, subAgents, mcpServers, settings, procedures) {
|
|
2086
2942
|
_ClaudeRenderer.renderClaudeMd(component, rules);
|
|
2087
2943
|
_ClaudeRenderer.renderScopedRules(component, rules);
|
|
2088
2944
|
_ClaudeRenderer.renderSettings(component, mcpServers, settings);
|
|
2089
2945
|
_ClaudeRenderer.renderSkills(component, skills);
|
|
2090
2946
|
_ClaudeRenderer.renderSubAgents(component, subAgents);
|
|
2947
|
+
if (procedures && procedures.length > 0) {
|
|
2948
|
+
_ClaudeRenderer.renderProcedures(component, procedures);
|
|
2949
|
+
}
|
|
2091
2950
|
}
|
|
2092
2951
|
static renderClaudeMd(component, rules) {
|
|
2093
2952
|
const claudeMdRules = rules.filter((r) => {
|
|
@@ -2291,6 +3150,7 @@ var ClaudeRenderer = class _ClaudeRenderer {
|
|
|
2291
3150
|
}
|
|
2292
3151
|
static renderSkills(component, skills) {
|
|
2293
3152
|
for (const skill of skills) {
|
|
3153
|
+
if (skill.platforms?.claude?.exclude) continue;
|
|
2294
3154
|
const lines = [];
|
|
2295
3155
|
lines.push("---");
|
|
2296
3156
|
lines.push(`name: "${skill.name}"`);
|
|
@@ -2301,8 +3161,9 @@ var ClaudeRenderer = class _ClaudeRenderer {
|
|
|
2301
3161
|
if (skill.userInvocable === false) {
|
|
2302
3162
|
lines.push(`user-invocable: false`);
|
|
2303
3163
|
}
|
|
2304
|
-
|
|
2305
|
-
|
|
3164
|
+
const resolvedSkillModel = resolveModelAlias(skill.model);
|
|
3165
|
+
if (resolvedSkillModel) {
|
|
3166
|
+
lines.push(`model: "${resolvedSkillModel}"`);
|
|
2306
3167
|
}
|
|
2307
3168
|
if (skill.effort) {
|
|
2308
3169
|
lines.push(`effort: "${skill.effort}"`);
|
|
@@ -2344,8 +3205,9 @@ var ClaudeRenderer = class _ClaudeRenderer {
|
|
|
2344
3205
|
lines.push(`name: ${agent.name}`);
|
|
2345
3206
|
lines.push(`description: >-`);
|
|
2346
3207
|
lines.push(` ${agent.description}`);
|
|
2347
|
-
|
|
2348
|
-
|
|
3208
|
+
const resolvedModel = resolveModelAlias(agent.model);
|
|
3209
|
+
if (resolvedModel) {
|
|
3210
|
+
lines.push(`model: ${resolvedModel}`);
|
|
2349
3211
|
}
|
|
2350
3212
|
if (agent.tools && agent.tools.length > 0) {
|
|
2351
3213
|
lines.push(`tools:`);
|
|
@@ -2401,6 +3263,14 @@ var ClaudeRenderer = class _ClaudeRenderer {
|
|
|
2401
3263
|
if (config.env) server.env = { ...config.env };
|
|
2402
3264
|
return server;
|
|
2403
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
|
+
}
|
|
2404
3274
|
/**
|
|
2405
3275
|
* Determine the default Claude rule target based on rule scope.
|
|
2406
3276
|
* ALWAYS-scoped rules default to CLAUDE_MD; FILE_PATTERN rules default to SCOPED_FILE.
|
|
@@ -2458,6 +3328,7 @@ var CursorRenderer = class _CursorRenderer {
|
|
|
2458
3328
|
}
|
|
2459
3329
|
static renderSkills(component, skills) {
|
|
2460
3330
|
for (const skill of skills) {
|
|
3331
|
+
if (skill.platforms?.cursor?.exclude) continue;
|
|
2461
3332
|
const lines = [];
|
|
2462
3333
|
lines.push("---");
|
|
2463
3334
|
lines.push(`name: "${skill.name}"`);
|
|
@@ -2499,9 +3370,6 @@ var CursorRenderer = class _CursorRenderer {
|
|
|
2499
3370
|
lines.push(`name: ${agent.name}`);
|
|
2500
3371
|
lines.push(`description: >-`);
|
|
2501
3372
|
lines.push(` ${agent.description}`);
|
|
2502
|
-
if (agent.model) {
|
|
2503
|
-
lines.push(`model: ${agent.model}`);
|
|
2504
|
-
}
|
|
2505
3373
|
if (agent.platforms?.cursor?.readonly) {
|
|
2506
3374
|
lines.push(`readonly: true`);
|
|
2507
3375
|
}
|
|
@@ -2756,6 +3624,7 @@ var AgentConfig = class _AgentConfig extends Component8 {
|
|
|
2756
3624
|
const rules = this.resolveRules();
|
|
2757
3625
|
const skills = this.resolveSkills();
|
|
2758
3626
|
const subAgents = this.resolveSubAgents();
|
|
3627
|
+
const procedures = this.resolveProcedures();
|
|
2759
3628
|
const mcpServers = this.options.mcpServers ?? {};
|
|
2760
3629
|
const projectMetadata = ProjectMetadata.of(this.project);
|
|
2761
3630
|
const metadata = projectMetadata?.metadata;
|
|
@@ -2765,6 +3634,10 @@ var AgentConfig = class _AgentConfig extends Component8 {
|
|
|
2765
3634
|
subAgents,
|
|
2766
3635
|
metadata
|
|
2767
3636
|
);
|
|
3637
|
+
const resolvedProcedures = this.resolveProcedureTemplates(
|
|
3638
|
+
procedures,
|
|
3639
|
+
metadata
|
|
3640
|
+
);
|
|
2768
3641
|
if (platforms.includes(AGENT_PLATFORM.CURSOR)) {
|
|
2769
3642
|
CursorRenderer.render(
|
|
2770
3643
|
this,
|
|
@@ -2786,7 +3659,8 @@ var AgentConfig = class _AgentConfig extends Component8 {
|
|
|
2786
3659
|
_AgentConfig.mergeClaudeDefaults(
|
|
2787
3660
|
this.options.claudeSettings,
|
|
2788
3661
|
bundlePermissions
|
|
2789
|
-
)
|
|
3662
|
+
),
|
|
3663
|
+
resolvedProcedures
|
|
2790
3664
|
);
|
|
2791
3665
|
}
|
|
2792
3666
|
if (platforms.includes(AGENT_PLATFORM.CODEX)) {
|
|
@@ -2899,6 +3773,16 @@ ${extra}`
|
|
|
2899
3773
|
}
|
|
2900
3774
|
}
|
|
2901
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
|
+
}
|
|
2902
3786
|
if (this.options.skills) {
|
|
2903
3787
|
for (const skill of this.options.skills) {
|
|
2904
3788
|
skillMap.set(skill.name, skill);
|
|
@@ -2918,6 +3802,16 @@ ${extra}`
|
|
|
2918
3802
|
}
|
|
2919
3803
|
}
|
|
2920
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
|
+
}
|
|
2921
3815
|
if (this.options.subAgents) {
|
|
2922
3816
|
for (const agent of this.options.subAgents) {
|
|
2923
3817
|
agentMap.set(agent.name, agent);
|
|
@@ -2925,6 +3819,35 @@ ${extra}`
|
|
|
2925
3819
|
}
|
|
2926
3820
|
return [...agentMap.values()];
|
|
2927
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
|
+
}
|
|
2928
3851
|
/**
|
|
2929
3852
|
* Resolves template variables in rule content using project metadata.
|
|
2930
3853
|
* Emits synthesis warnings for rules with unresolved variables.
|
|
@@ -2977,6 +3900,23 @@ ${extra}`
|
|
|
2977
3900
|
return resolved !== agent.prompt ? { ...agent, prompt: resolved } : agent;
|
|
2978
3901
|
});
|
|
2979
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
|
+
}
|
|
2980
3920
|
/**
|
|
2981
3921
|
* Collects Claude permission entries from all active bundles.
|
|
2982
3922
|
*/
|
|
@@ -4663,8 +5603,11 @@ export {
|
|
|
4663
5603
|
getLatestEligibleVersion,
|
|
4664
5604
|
githubWorkflowBundle,
|
|
4665
5605
|
jestBundle,
|
|
5606
|
+
meetingAnalysisBundle,
|
|
5607
|
+
orchestratorBundle,
|
|
4666
5608
|
pnpmBundle,
|
|
4667
5609
|
projenBundle,
|
|
5610
|
+
resolveModelAlias,
|
|
4668
5611
|
resolveTemplateVariables,
|
|
4669
5612
|
slackBundle,
|
|
4670
5613
|
turborepoBundle,
|