@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.js
CHANGED
|
@@ -218,8 +218,11 @@ __export(index_exports, {
|
|
|
218
218
|
getLatestEligibleVersion: () => getLatestEligibleVersion,
|
|
219
219
|
githubWorkflowBundle: () => githubWorkflowBundle,
|
|
220
220
|
jestBundle: () => jestBundle,
|
|
221
|
+
meetingAnalysisBundle: () => meetingAnalysisBundle,
|
|
222
|
+
orchestratorBundle: () => orchestratorBundle,
|
|
221
223
|
pnpmBundle: () => pnpmBundle,
|
|
222
224
|
projenBundle: () => projenBundle,
|
|
225
|
+
resolveModelAlias: () => resolveModelAlias,
|
|
223
226
|
resolveTemplateVariables: () => resolveTemplateVariables,
|
|
224
227
|
slackBundle: () => slackBundle,
|
|
225
228
|
turborepoBundle: () => turborepoBundle,
|
|
@@ -253,6 +256,17 @@ var AGENT_MODEL = {
|
|
|
253
256
|
BALANCED: "balanced",
|
|
254
257
|
POWERFUL: "powerful"
|
|
255
258
|
};
|
|
259
|
+
function resolveModelAlias(model) {
|
|
260
|
+
if (!model || model === AGENT_MODEL.INHERIT) {
|
|
261
|
+
return void 0;
|
|
262
|
+
}
|
|
263
|
+
const mapping = {
|
|
264
|
+
[AGENT_MODEL.POWERFUL]: "opus",
|
|
265
|
+
[AGENT_MODEL.BALANCED]: "sonnet",
|
|
266
|
+
[AGENT_MODEL.FAST]: "haiku"
|
|
267
|
+
};
|
|
268
|
+
return mapping[model] ?? model;
|
|
269
|
+
}
|
|
256
270
|
var MCP_TRANSPORT = {
|
|
257
271
|
STDIO: "stdio",
|
|
258
272
|
HTTP: "http",
|
|
@@ -999,6 +1013,849 @@ var jestBundle = {
|
|
|
999
1013
|
}
|
|
1000
1014
|
};
|
|
1001
1015
|
|
|
1016
|
+
// src/agent/bundles/meeting-analysis.ts
|
|
1017
|
+
var meetingAnalystSubAgent = {
|
|
1018
|
+
name: "meeting-analyst",
|
|
1019
|
+
description: "Processes meeting transcripts through a 4-phase pipeline: extract, notes, draft, and link",
|
|
1020
|
+
model: AGENT_MODEL.POWERFUL,
|
|
1021
|
+
maxTurns: 80,
|
|
1022
|
+
platforms: { cursor: { exclude: true } },
|
|
1023
|
+
prompt: [
|
|
1024
|
+
"# Meeting Analyst Agent",
|
|
1025
|
+
"",
|
|
1026
|
+
"You process meeting transcripts through a structured 4-phase pipeline.",
|
|
1027
|
+
"Each phase runs as a **separate agent session**, triggered by its own",
|
|
1028
|
+
"GitHub issue with a `meeting:*` phase label. You handle exactly **one",
|
|
1029
|
+
"phase per session** \u2014 read the issue to determine which phase to execute.",
|
|
1030
|
+
"",
|
|
1031
|
+
"## Execution Model",
|
|
1032
|
+
"",
|
|
1033
|
+
"1. Each meeting produces up to 4 GitHub issues (one per phase)",
|
|
1034
|
+
"2. Each issue carries a `meeting:*` label identifying the phase",
|
|
1035
|
+
"3. You pick up one issue, execute that phase, commit/push, then close the issue",
|
|
1036
|
+
"4. Phase 1 (Extract) creates the downstream phase issues for phases 2-4",
|
|
1037
|
+
"5. Each downstream issue includes `Depends on: #N` linking to its predecessor",
|
|
1038
|
+
"",
|
|
1039
|
+
"## Design Principles",
|
|
1040
|
+
"",
|
|
1041
|
+
"1. **Extract, don't interpret.** Capture what was said and decided. Flag",
|
|
1042
|
+
" ambiguity as open items rather than resolving it.",
|
|
1043
|
+
"2. **Route to the right category.** Meeting content maps to requirements,",
|
|
1044
|
+
" ADRs, product docs, and business strategy. Each output goes to the",
|
|
1045
|
+
" correct location per the project's taxonomy.",
|
|
1046
|
+
"3. **Preserve provenance.** Every extracted item links back to the meeting",
|
|
1047
|
+
" source so reviewers can check context.",
|
|
1048
|
+
"4. **Create issues, not documents.** For requirements and ADRs, create",
|
|
1049
|
+
" GitHub issues that other agents or humans will pick up. Do not write",
|
|
1050
|
+
" final requirement documents yourself.",
|
|
1051
|
+
"5. **Bi-directional traceability.** Every document and issue created by",
|
|
1052
|
+
" this pipeline must link back to the meeting source, and the meeting",
|
|
1053
|
+
" source documents must link forward to everything created from them.",
|
|
1054
|
+
"",
|
|
1055
|
+
"---",
|
|
1056
|
+
"",
|
|
1057
|
+
"## Traceability",
|
|
1058
|
+
"",
|
|
1059
|
+
"All outputs must be bi-directionally linked so any artifact can be traced",
|
|
1060
|
+
"back to the meeting that produced it and forward to everything it spawned.",
|
|
1061
|
+
"",
|
|
1062
|
+
"### Backward links (created artifact \u2192 meeting source)",
|
|
1063
|
+
"",
|
|
1064
|
+
"Every GitHub issue and document created by this pipeline must include a",
|
|
1065
|
+
"`## Traceability` section with:",
|
|
1066
|
+
"",
|
|
1067
|
+
"```markdown",
|
|
1068
|
+
"## Traceability",
|
|
1069
|
+
"",
|
|
1070
|
+
"- **Source meeting:** <path to transcript or meeting notes>",
|
|
1071
|
+
"- **Extraction:** <path to extraction file>",
|
|
1072
|
+
"- **Phase issue:** #<N> (the phase issue that created this artifact)",
|
|
1073
|
+
"```",
|
|
1074
|
+
"",
|
|
1075
|
+
"### Forward links (meeting source \u2192 created artifacts)",
|
|
1076
|
+
"",
|
|
1077
|
+
"After Phase 4 (Link) creates all follow-up issues and documents:",
|
|
1078
|
+
"",
|
|
1079
|
+
"1. **Update the extraction file** with a `## Downstream Artifacts` section",
|
|
1080
|
+
" listing every issue and document created from this meeting:",
|
|
1081
|
+
"",
|
|
1082
|
+
" ```markdown",
|
|
1083
|
+
" ## Downstream Artifacts",
|
|
1084
|
+
"",
|
|
1085
|
+
" | Artifact | Type | Issue/Path |",
|
|
1086
|
+
" |----------|------|------------|",
|
|
1087
|
+
" | <title> | requirement / ADR / action-item / profile | #<N> or <path> |",
|
|
1088
|
+
" ```",
|
|
1089
|
+
"",
|
|
1090
|
+
"2. **Update the meeting notes** with a similar `## Related Issues` section",
|
|
1091
|
+
" listing all issues created from this meeting.",
|
|
1092
|
+
"",
|
|
1093
|
+
"3. **Comment on the extract issue** with a summary linking to all created",
|
|
1094
|
+
" artifacts.",
|
|
1095
|
+
"",
|
|
1096
|
+
"### Within-pipeline links",
|
|
1097
|
+
"",
|
|
1098
|
+
"- Phase issues link to predecessors via `Depends on: #N`",
|
|
1099
|
+
"- Phase issues reference the extraction file path in their body",
|
|
1100
|
+
"- Draft documents reference both the extraction and the meeting notes",
|
|
1101
|
+
"",
|
|
1102
|
+
"---",
|
|
1103
|
+
"",
|
|
1104
|
+
"## Phase 1: Extract (`meeting:extract`)",
|
|
1105
|
+
"",
|
|
1106
|
+
"**Goal:** Read the meeting transcript and categorize all substantive content.",
|
|
1107
|
+
"",
|
|
1108
|
+
"### Steps",
|
|
1109
|
+
"",
|
|
1110
|
+
"1. Read the transcript file specified in the issue body.",
|
|
1111
|
+
"2. Identify and categorize content into these buckets:",
|
|
1112
|
+
"",
|
|
1113
|
+
" | Bucket | What to look for |",
|
|
1114
|
+
" |--------|-----------------|",
|
|
1115
|
+
` | **Decisions** | "We decided...", "Let's go with...", explicit choices |`,
|
|
1116
|
+
' | **Requirements** | Feature descriptions, acceptance criteria, "it should..." |',
|
|
1117
|
+
" | **Technology discussions** | Platform comparisons, architecture options, tool evaluations |",
|
|
1118
|
+
' | **Action items** | "[Person] will...", "Next step is...", assigned tasks |',
|
|
1119
|
+
' | **Open questions** | "We need to figure out...", unresolved debates |',
|
|
1120
|
+
" | **People of interest** | Industry contacts, domain experts mentioned |",
|
|
1121
|
+
" | **Companies of interest** | Competitors, vendors, partners discussed |",
|
|
1122
|
+
" | **Strategic direction** | Business model changes, market positioning |",
|
|
1123
|
+
" | **Product direction** | Roadmap changes, feature prioritization |",
|
|
1124
|
+
"",
|
|
1125
|
+
"3. Write the extraction to a markdown file with structured sections:",
|
|
1126
|
+
" - Attendees",
|
|
1127
|
+
" - Decisions Made (with category and confidence: Firm / Tentative / Needs confirmation)",
|
|
1128
|
+
" - Requirements Identified (with category and priority estimate)",
|
|
1129
|
+
" - Technology Discussions (with status: Decided / Leaning toward / Open)",
|
|
1130
|
+
" - Action Items (with assignee and due date if stated)",
|
|
1131
|
+
" - Open Questions",
|
|
1132
|
+
" - People of Interest (with context and whether a profile exists)",
|
|
1133
|
+
" - Companies of Interest (with type and context)",
|
|
1134
|
+
" - Strategic / Product Direction",
|
|
1135
|
+
"",
|
|
1136
|
+
"4. **Create downstream phase issues** using `gh issue create`:",
|
|
1137
|
+
" - Always create a `meeting:notes` issue (blocked on this extract issue)",
|
|
1138
|
+
" - If requirements OR decisions/ADRs identified, create a `meeting:draft` issue",
|
|
1139
|
+
" (blocked on the notes issue)",
|
|
1140
|
+
" - Always create a `meeting:link` issue \u2014 blocked on the draft issue if one",
|
|
1141
|
+
" was created, otherwise blocked on the notes issue",
|
|
1142
|
+
"",
|
|
1143
|
+
"5. Commit, push, and close the extract issue.",
|
|
1144
|
+
"",
|
|
1145
|
+
"---",
|
|
1146
|
+
"",
|
|
1147
|
+
"## Phase 2: Notes (`meeting:notes`)",
|
|
1148
|
+
"",
|
|
1149
|
+
"**Goal:** Transform the extraction into structured meeting notes.",
|
|
1150
|
+
"",
|
|
1151
|
+
"### Steps",
|
|
1152
|
+
"",
|
|
1153
|
+
"1. Read the extraction file referenced in the issue body (output of Phase 1).",
|
|
1154
|
+
"2. Write structured meeting notes with these sections:",
|
|
1155
|
+
" - Meeting metadata (title, date, attendees)",
|
|
1156
|
+
" - Agenda / topics covered",
|
|
1157
|
+
" - Key Discussion Points (organized by topic)",
|
|
1158
|
+
" - Decisions (numbered, with rationale)",
|
|
1159
|
+
" - Action Items (table: who, what, when)",
|
|
1160
|
+
" - Open Questions",
|
|
1161
|
+
" - Follow-up items",
|
|
1162
|
+
"3. Commit, push, and close the notes issue.",
|
|
1163
|
+
"",
|
|
1164
|
+
"---",
|
|
1165
|
+
"",
|
|
1166
|
+
"## Phase 3: Draft (`meeting:draft`)",
|
|
1167
|
+
"",
|
|
1168
|
+
"**Goal:** Draft proposals for requirements, ADRs, and product/strategy updates.",
|
|
1169
|
+
"",
|
|
1170
|
+
"This phase only exists if the extraction identified requirements, architectural",
|
|
1171
|
+
"decisions, or strategy changes. If this issue exists, execute it.",
|
|
1172
|
+
"",
|
|
1173
|
+
"### Steps",
|
|
1174
|
+
"",
|
|
1175
|
+
"1. Read the extraction file from Phase 1.",
|
|
1176
|
+
"2. Check existing requirement registries and ADR registries for duplicates.",
|
|
1177
|
+
"3. Draft requirement proposals with:",
|
|
1178
|
+
" - Category (FR, BR, NFR, etc.)",
|
|
1179
|
+
" - Summary (2-3 sentences)",
|
|
1180
|
+
" - Draft acceptance criteria",
|
|
1181
|
+
" - Related existing requirements",
|
|
1182
|
+
"4. Draft ADR proposals for technology decisions with:",
|
|
1183
|
+
" - Context and problem statement",
|
|
1184
|
+
" - Options discussed",
|
|
1185
|
+
" - Stated preferences or decisions",
|
|
1186
|
+
" - Status: Proposed or Decided",
|
|
1187
|
+
"5. Summarize product/strategy document updates needed.",
|
|
1188
|
+
"6. Commit, push, and close the draft issue.",
|
|
1189
|
+
"",
|
|
1190
|
+
"---",
|
|
1191
|
+
"",
|
|
1192
|
+
"## Phase 4: Link (`meeting:link`)",
|
|
1193
|
+
"",
|
|
1194
|
+
"**Goal:** Create GitHub issues for follow-up work, cross-reference the",
|
|
1195
|
+
"meeting into existing documentation, and complete bi-directional traceability.",
|
|
1196
|
+
"",
|
|
1197
|
+
"### Steps",
|
|
1198
|
+
"",
|
|
1199
|
+
"1. Read the drafts from Phase 3 (if they exist) and the extraction from Phase 1.",
|
|
1200
|
+
"2. Create requirement issues using `gh issue create` with appropriate labels.",
|
|
1201
|
+
" Include a `## Traceability` section in each issue body linking back to",
|
|
1202
|
+
" the source meeting and extraction file.",
|
|
1203
|
+
"3. Create ADR issues if technology decisions need formal records.",
|
|
1204
|
+
" Include a `## Traceability` section in each.",
|
|
1205
|
+
"4. Create action item issues for tasks that are not document-related.",
|
|
1206
|
+
" Include a `## Traceability` section in each.",
|
|
1207
|
+
"5. Cross-reference the meeting in any existing documents that were discussed.",
|
|
1208
|
+
"6. Apply direct product/strategy doc updates for items flagged as **Firm**",
|
|
1209
|
+
" confidence in the extraction.",
|
|
1210
|
+
"7. **Update the extraction file** with a `## Downstream Artifacts` section",
|
|
1211
|
+
" listing every issue and document created from this meeting.",
|
|
1212
|
+
"8. **Update the meeting notes** with a `## Related Issues` section listing",
|
|
1213
|
+
" all issues created from this meeting.",
|
|
1214
|
+
"9. Comment on the parent extract issue with a summary linking to all",
|
|
1215
|
+
" created artifacts.",
|
|
1216
|
+
"10. Commit and push (if any file changes were made), then close the link issue.",
|
|
1217
|
+
"",
|
|
1218
|
+
"---",
|
|
1219
|
+
"",
|
|
1220
|
+
"## GitHub Integration",
|
|
1221
|
+
"",
|
|
1222
|
+
"- Use `gh` CLI for all GitHub operations",
|
|
1223
|
+
"- Apply the appropriate `meeting:*` phase label to each phase issue",
|
|
1224
|
+
"- Use `type:docs` for documentation-producing issues, `type:chore` for",
|
|
1225
|
+
" maintenance/organizational tasks",
|
|
1226
|
+
"- Use `status:` labels to track progress (`status:ready`, `status:in-progress`, `status:done`)",
|
|
1227
|
+
"- Reference the source meeting transcript in all created issues",
|
|
1228
|
+
"- Link phase issues with `Depends on: #N` to enforce ordering",
|
|
1229
|
+
"",
|
|
1230
|
+
"## When to Shorten the Pipeline",
|
|
1231
|
+
"",
|
|
1232
|
+
"- **Short meetings** (<30 min, <2000 words): combine extract + notes into one session",
|
|
1233
|
+
"- **No requirements or decisions**: Phase 1 skips creating the `meeting:draft` issue",
|
|
1234
|
+
"- **Only action items**: extract + notes + link (3 phase issues)"
|
|
1235
|
+
].join("\n")
|
|
1236
|
+
};
|
|
1237
|
+
var processMeetingSkill = {
|
|
1238
|
+
name: "process-meeting",
|
|
1239
|
+
description: "Process a meeting transcript through the 4-phase meeting analysis pipeline",
|
|
1240
|
+
disableModelInvocation: true,
|
|
1241
|
+
userInvocable: true,
|
|
1242
|
+
context: "fork",
|
|
1243
|
+
agent: "meeting-analyst",
|
|
1244
|
+
platforms: { cursor: { exclude: true } },
|
|
1245
|
+
instructions: [
|
|
1246
|
+
"# Process Meeting Transcript",
|
|
1247
|
+
"",
|
|
1248
|
+
"Kick off meeting transcript processing by executing Phase 1 (Extract)",
|
|
1249
|
+
"and creating downstream phase issues for the remaining phases.",
|
|
1250
|
+
"",
|
|
1251
|
+
"## Usage",
|
|
1252
|
+
"",
|
|
1253
|
+
"/process-meeting <path-to-transcript>",
|
|
1254
|
+
"",
|
|
1255
|
+
"## Steps",
|
|
1256
|
+
"",
|
|
1257
|
+
"1. Read the provided transcript file",
|
|
1258
|
+
"2. Execute Phase 1 (Extract) \u2014 categorize transcript content into",
|
|
1259
|
+
" decisions, requirements, action items, open questions, and more",
|
|
1260
|
+
"3. Write the extraction to a markdown file",
|
|
1261
|
+
"4. Create downstream phase issues using `gh issue create`:",
|
|
1262
|
+
" - `meeting:notes` issue (always)",
|
|
1263
|
+
" - `meeting:draft` issue (if requirements or decisions were found)",
|
|
1264
|
+
" - `meeting:link` issue (always)",
|
|
1265
|
+
"5. Each downstream issue includes `Depends on:` linking to its predecessor",
|
|
1266
|
+
"6. Commit and push the extraction file",
|
|
1267
|
+
"",
|
|
1268
|
+
"## Input",
|
|
1269
|
+
"",
|
|
1270
|
+
"Provide a path to a meeting transcript file (text or markdown).",
|
|
1271
|
+
"The transcript should contain speaker-attributed dialogue.",
|
|
1272
|
+
"",
|
|
1273
|
+
"## Output",
|
|
1274
|
+
"",
|
|
1275
|
+
"- An extraction markdown file with categorized meeting content",
|
|
1276
|
+
"- Phase issues with `meeting:*` labels for downstream agent sessions",
|
|
1277
|
+
" to pick up (notes, draft, link)"
|
|
1278
|
+
].join("\n")
|
|
1279
|
+
};
|
|
1280
|
+
var meetingAnalysisBundle = {
|
|
1281
|
+
name: "meeting-analysis",
|
|
1282
|
+
description: "Meeting transcript processing workflow with 4-phase pipeline (extract, notes, draft, link)",
|
|
1283
|
+
appliesWhen: () => true,
|
|
1284
|
+
rules: [
|
|
1285
|
+
{
|
|
1286
|
+
name: "meeting-processing-workflow",
|
|
1287
|
+
description: "Describes the 4-phase meeting processing pipeline, extraction taxonomy, and labeling conventions",
|
|
1288
|
+
scope: AGENT_RULE_SCOPE.ALWAYS,
|
|
1289
|
+
content: [
|
|
1290
|
+
"# Meeting Processing Workflow",
|
|
1291
|
+
"",
|
|
1292
|
+
"Use `/process-meeting <path>` to process a meeting transcript through a",
|
|
1293
|
+
"4-phase pipeline (extract \u2192 notes \u2192 draft \u2192 link). Each phase runs as a",
|
|
1294
|
+
"separate agent session tracked by a GitHub issue with a `meeting:*` label.",
|
|
1295
|
+
"See the `meeting-analyst` agent definition for full workflow details."
|
|
1296
|
+
].join("\n"),
|
|
1297
|
+
platforms: {
|
|
1298
|
+
cursor: { exclude: true }
|
|
1299
|
+
},
|
|
1300
|
+
tags: ["workflow"]
|
|
1301
|
+
}
|
|
1302
|
+
],
|
|
1303
|
+
skills: [processMeetingSkill],
|
|
1304
|
+
subAgents: [meetingAnalystSubAgent]
|
|
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
|
+
|
|
1002
1859
|
// src/pnpm/pnpm-workspace.ts
|
|
1003
1860
|
var import_path = require("path");
|
|
1004
1861
|
var import_projen = require("projen");
|
|
@@ -2015,7 +2872,9 @@ var BUILT_IN_BUNDLES = [
|
|
|
2015
2872
|
awsCdkBundle,
|
|
2016
2873
|
projenBundle,
|
|
2017
2874
|
githubWorkflowBundle,
|
|
2018
|
-
slackBundle
|
|
2875
|
+
slackBundle,
|
|
2876
|
+
meetingAnalysisBundle,
|
|
2877
|
+
orchestratorBundle
|
|
2019
2878
|
];
|
|
2020
2879
|
|
|
2021
2880
|
// src/agent/bundles/scope.ts
|
|
@@ -2138,12 +2997,15 @@ var ClaudeRenderer = class _ClaudeRenderer {
|
|
|
2138
2997
|
/**
|
|
2139
2998
|
* Render all Claude Code configuration files.
|
|
2140
2999
|
*/
|
|
2141
|
-
static render(component, rules, skills, subAgents, mcpServers, settings) {
|
|
3000
|
+
static render(component, rules, skills, subAgents, mcpServers, settings, procedures) {
|
|
2142
3001
|
_ClaudeRenderer.renderClaudeMd(component, rules);
|
|
2143
3002
|
_ClaudeRenderer.renderScopedRules(component, rules);
|
|
2144
3003
|
_ClaudeRenderer.renderSettings(component, mcpServers, settings);
|
|
2145
3004
|
_ClaudeRenderer.renderSkills(component, skills);
|
|
2146
3005
|
_ClaudeRenderer.renderSubAgents(component, subAgents);
|
|
3006
|
+
if (procedures && procedures.length > 0) {
|
|
3007
|
+
_ClaudeRenderer.renderProcedures(component, procedures);
|
|
3008
|
+
}
|
|
2147
3009
|
}
|
|
2148
3010
|
static renderClaudeMd(component, rules) {
|
|
2149
3011
|
const claudeMdRules = rules.filter((r) => {
|
|
@@ -2347,6 +3209,7 @@ var ClaudeRenderer = class _ClaudeRenderer {
|
|
|
2347
3209
|
}
|
|
2348
3210
|
static renderSkills(component, skills) {
|
|
2349
3211
|
for (const skill of skills) {
|
|
3212
|
+
if (skill.platforms?.claude?.exclude) continue;
|
|
2350
3213
|
const lines = [];
|
|
2351
3214
|
lines.push("---");
|
|
2352
3215
|
lines.push(`name: "${skill.name}"`);
|
|
@@ -2357,8 +3220,9 @@ var ClaudeRenderer = class _ClaudeRenderer {
|
|
|
2357
3220
|
if (skill.userInvocable === false) {
|
|
2358
3221
|
lines.push(`user-invocable: false`);
|
|
2359
3222
|
}
|
|
2360
|
-
|
|
2361
|
-
|
|
3223
|
+
const resolvedSkillModel = resolveModelAlias(skill.model);
|
|
3224
|
+
if (resolvedSkillModel) {
|
|
3225
|
+
lines.push(`model: "${resolvedSkillModel}"`);
|
|
2362
3226
|
}
|
|
2363
3227
|
if (skill.effort) {
|
|
2364
3228
|
lines.push(`effort: "${skill.effort}"`);
|
|
@@ -2400,8 +3264,9 @@ var ClaudeRenderer = class _ClaudeRenderer {
|
|
|
2400
3264
|
lines.push(`name: ${agent.name}`);
|
|
2401
3265
|
lines.push(`description: >-`);
|
|
2402
3266
|
lines.push(` ${agent.description}`);
|
|
2403
|
-
|
|
2404
|
-
|
|
3267
|
+
const resolvedModel = resolveModelAlias(agent.model);
|
|
3268
|
+
if (resolvedModel) {
|
|
3269
|
+
lines.push(`model: ${resolvedModel}`);
|
|
2405
3270
|
}
|
|
2406
3271
|
if (agent.tools && agent.tools.length > 0) {
|
|
2407
3272
|
lines.push(`tools:`);
|
|
@@ -2457,6 +3322,14 @@ var ClaudeRenderer = class _ClaudeRenderer {
|
|
|
2457
3322
|
if (config.env) server.env = { ...config.env };
|
|
2458
3323
|
return server;
|
|
2459
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
|
+
}
|
|
2460
3333
|
/**
|
|
2461
3334
|
* Determine the default Claude rule target based on rule scope.
|
|
2462
3335
|
* ALWAYS-scoped rules default to CLAUDE_MD; FILE_PATTERN rules default to SCOPED_FILE.
|
|
@@ -2514,6 +3387,7 @@ var CursorRenderer = class _CursorRenderer {
|
|
|
2514
3387
|
}
|
|
2515
3388
|
static renderSkills(component, skills) {
|
|
2516
3389
|
for (const skill of skills) {
|
|
3390
|
+
if (skill.platforms?.cursor?.exclude) continue;
|
|
2517
3391
|
const lines = [];
|
|
2518
3392
|
lines.push("---");
|
|
2519
3393
|
lines.push(`name: "${skill.name}"`);
|
|
@@ -2555,9 +3429,6 @@ var CursorRenderer = class _CursorRenderer {
|
|
|
2555
3429
|
lines.push(`name: ${agent.name}`);
|
|
2556
3430
|
lines.push(`description: >-`);
|
|
2557
3431
|
lines.push(` ${agent.description}`);
|
|
2558
|
-
if (agent.model) {
|
|
2559
|
-
lines.push(`model: ${agent.model}`);
|
|
2560
|
-
}
|
|
2561
3432
|
if (agent.platforms?.cursor?.readonly) {
|
|
2562
3433
|
lines.push(`readonly: true`);
|
|
2563
3434
|
}
|
|
@@ -2812,6 +3683,7 @@ var AgentConfig = class _AgentConfig extends import_projen8.Component {
|
|
|
2812
3683
|
const rules = this.resolveRules();
|
|
2813
3684
|
const skills = this.resolveSkills();
|
|
2814
3685
|
const subAgents = this.resolveSubAgents();
|
|
3686
|
+
const procedures = this.resolveProcedures();
|
|
2815
3687
|
const mcpServers = this.options.mcpServers ?? {};
|
|
2816
3688
|
const projectMetadata = ProjectMetadata.of(this.project);
|
|
2817
3689
|
const metadata = projectMetadata?.metadata;
|
|
@@ -2821,6 +3693,10 @@ var AgentConfig = class _AgentConfig extends import_projen8.Component {
|
|
|
2821
3693
|
subAgents,
|
|
2822
3694
|
metadata
|
|
2823
3695
|
);
|
|
3696
|
+
const resolvedProcedures = this.resolveProcedureTemplates(
|
|
3697
|
+
procedures,
|
|
3698
|
+
metadata
|
|
3699
|
+
);
|
|
2824
3700
|
if (platforms.includes(AGENT_PLATFORM.CURSOR)) {
|
|
2825
3701
|
CursorRenderer.render(
|
|
2826
3702
|
this,
|
|
@@ -2842,7 +3718,8 @@ var AgentConfig = class _AgentConfig extends import_projen8.Component {
|
|
|
2842
3718
|
_AgentConfig.mergeClaudeDefaults(
|
|
2843
3719
|
this.options.claudeSettings,
|
|
2844
3720
|
bundlePermissions
|
|
2845
|
-
)
|
|
3721
|
+
),
|
|
3722
|
+
resolvedProcedures
|
|
2846
3723
|
);
|
|
2847
3724
|
}
|
|
2848
3725
|
if (platforms.includes(AGENT_PLATFORM.CODEX)) {
|
|
@@ -2955,6 +3832,16 @@ ${extra}`
|
|
|
2955
3832
|
}
|
|
2956
3833
|
}
|
|
2957
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
|
+
}
|
|
2958
3845
|
if (this.options.skills) {
|
|
2959
3846
|
for (const skill of this.options.skills) {
|
|
2960
3847
|
skillMap.set(skill.name, skill);
|
|
@@ -2974,6 +3861,16 @@ ${extra}`
|
|
|
2974
3861
|
}
|
|
2975
3862
|
}
|
|
2976
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
|
+
}
|
|
2977
3874
|
if (this.options.subAgents) {
|
|
2978
3875
|
for (const agent of this.options.subAgents) {
|
|
2979
3876
|
agentMap.set(agent.name, agent);
|
|
@@ -2981,6 +3878,35 @@ ${extra}`
|
|
|
2981
3878
|
}
|
|
2982
3879
|
return [...agentMap.values()];
|
|
2983
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
|
+
}
|
|
2984
3910
|
/**
|
|
2985
3911
|
* Resolves template variables in rule content using project metadata.
|
|
2986
3912
|
* Emits synthesis warnings for rules with unresolved variables.
|
|
@@ -3033,6 +3959,23 @@ ${extra}`
|
|
|
3033
3959
|
return resolved !== agent.prompt ? { ...agent, prompt: resolved } : agent;
|
|
3034
3960
|
});
|
|
3035
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
|
+
}
|
|
3036
3979
|
/**
|
|
3037
3980
|
* Collects Claude permission entries from all active bundles.
|
|
3038
3981
|
*/
|
|
@@ -4711,8 +5654,11 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends import_projen17.Compone
|
|
|
4711
5654
|
getLatestEligibleVersion,
|
|
4712
5655
|
githubWorkflowBundle,
|
|
4713
5656
|
jestBundle,
|
|
5657
|
+
meetingAnalysisBundle,
|
|
5658
|
+
orchestratorBundle,
|
|
4714
5659
|
pnpmBundle,
|
|
4715
5660
|
projenBundle,
|
|
5661
|
+
resolveModelAlias,
|
|
4716
5662
|
resolveTemplateVariables,
|
|
4717
5663
|
slackBundle,
|
|
4718
5664
|
turborepoBundle,
|