@codedrifters/configulator 0.0.297 → 0.0.299

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -177,6 +177,7 @@ var index_exports = {};
177
177
  __export(index_exports, {
178
178
  AGENT_MODEL: () => AGENT_MODEL,
179
179
  AGENT_PLATFORM: () => AGENT_PLATFORM,
180
+ AGENT_REGISTRY_ENTRIES: () => AGENT_REGISTRY_ENTRIES,
180
181
  AGENT_RULE_SCOPE: () => AGENT_RULE_SCOPE,
181
182
  AGENT_TIER_ROLES: () => AGENT_TIER_ROLES,
182
183
  AGENT_TIER_VALUES: () => AGENT_TIER_VALUES,
@@ -273,8 +274,10 @@ __export(index_exports, {
273
274
  SCOPE_CLASS_VALUES: () => SCOPE_CLASS_VALUES,
274
275
  SHARED_EDITING_CONFLICT_STRATEGY_VALUES: () => SHARED_EDITING_CONFLICT_STRATEGY_VALUES,
275
276
  STARLIGHT_ROLE: () => STARLIGHT_ROLE,
277
+ SUPPRESSED_WORKFLOW_RULE_NAMES: () => SUPPRESSED_WORKFLOW_RULE_NAMES,
276
278
  SampleLang: () => SampleLang,
277
279
  StarlightProject: () => StarlightProject,
280
+ TIER_AWARE_BUNDLE_NAMES: () => TIER_AWARE_BUNDLE_NAMES,
278
281
  TestRunner: () => TestRunner,
279
282
  TsDocCoverageKind: () => TsDocCoverageKind,
280
283
  TurboRepo: () => TurboRepo,
@@ -298,6 +301,7 @@ __export(index_exports, {
298
301
  awsCdkBundle: () => awsCdkBundle,
299
302
  baseBundle: () => baseBundle,
300
303
  bcmWriterBundle: () => bcmWriterBundle,
304
+ buildAgentRegistryRule: () => buildAgentRegistryRule,
301
305
  buildBaseBundle: () => buildBaseBundle,
302
306
  buildBcmWriterBundle: () => buildBcmWriterBundle,
303
307
  buildBuiltInBundles: () => buildBuiltInBundles,
@@ -308,6 +312,7 @@ __export(index_exports, {
308
312
  buildDocsSyncBundle: () => buildDocsSyncBundle,
309
313
  buildIndustryDiscoveryBundle: () => buildIndustryDiscoveryBundle,
310
314
  buildMaintenanceAuditBundle: () => buildMaintenanceAuditBundle,
315
+ buildMeetingAnalysisBundle: () => buildMeetingAnalysisBundle,
311
316
  buildOrchestratorConventionsContent: () => buildOrchestratorConventionsContent,
312
317
  buildPeopleProfileBundle: () => buildPeopleProfileBundle,
313
318
  buildRegulatoryResearchBundle: () => buildRegulatoryResearchBundle,
@@ -319,6 +324,7 @@ __export(index_exports, {
319
324
  buildSoftwareProfileBundle: () => buildSoftwareProfileBundle,
320
325
  buildStandardsResearchBundle: () => buildStandardsResearchBundle,
321
326
  buildUnblockDependentsProcedure: () => buildUnblockDependentsProcedure,
327
+ bundleNameForWorkflowRule: () => bundleNameForWorkflowRule,
322
328
  businessModelsBundle: () => businessModelsBundle,
323
329
  checkDocSamplesProcedure: () => checkDocSamplesProcedure,
324
330
  checkLinksProcedure: () => checkLinksProcedure,
@@ -345,6 +351,7 @@ __export(index_exports, {
345
351
  industryDiscoveryBundle: () => industryDiscoveryBundle,
346
352
  isPhaseLabelOwnedByExcluded: () => isPhaseLabelOwnedByExcluded,
347
353
  isScheduledTaskOwnedByExcluded: () => isScheduledTaskOwnedByExcluded,
354
+ isSuppressedWorkflowRule: () => isSuppressedWorkflowRule,
348
355
  isTypeLabelOwnedByExcluded: () => isTypeLabelOwnedByExcluded,
349
356
  jestBundle: () => jestBundle,
350
357
  labelsForPhase: () => labelsForPhase,
@@ -403,6 +410,8 @@ __export(index_exports, {
403
410
  resolveAgentTiers: () => resolveAgentTiers,
404
411
  resolveAstroProjectOutdir: () => resolveAstroProjectOutdir,
405
412
  resolveAwsCdkProjectOutdir: () => resolveAwsCdkProjectOutdir,
413
+ resolveBundleAgentTiers: () => resolveBundleAgentTiers,
414
+ resolveDefaultAgentTier: () => resolveDefaultAgentTier,
406
415
  resolveIssueDefaults: () => resolveIssueDefaults,
407
416
  resolveIssueTemplates: () => resolveIssueTemplates,
408
417
  resolveModelAlias: () => resolveModelAlias,
@@ -445,6 +454,45 @@ module.exports = __toCommonJS(index_exports);
445
454
  // src/agent/agent-config.ts
446
455
  var import_projen8 = require("projen");
447
456
 
457
+ // src/agent/types.ts
458
+ var AGENT_RULE_SCOPE = {
459
+ ALWAYS: "always",
460
+ FILE_PATTERN: "file-pattern"
461
+ };
462
+ var AGENT_PLATFORM = {
463
+ CURSOR: "cursor",
464
+ CLAUDE: "claude",
465
+ CODEX: "codex",
466
+ COPILOT: "copilot"
467
+ };
468
+ var CLAUDE_RULE_TARGET = {
469
+ SCOPED_FILE: "scoped-file",
470
+ AGENTS_MD: "agents-md",
471
+ CLAUDE_MD: "claude-md"
472
+ };
473
+ var AGENT_MODEL = {
474
+ INHERIT: "inherit",
475
+ FAST: "fast",
476
+ BALANCED: "balanced",
477
+ POWERFUL: "powerful"
478
+ };
479
+ function resolveModelAlias(model) {
480
+ if (!model || model === AGENT_MODEL.INHERIT) {
481
+ return void 0;
482
+ }
483
+ const mapping = {
484
+ [AGENT_MODEL.POWERFUL]: "opus",
485
+ [AGENT_MODEL.BALANCED]: "sonnet",
486
+ [AGENT_MODEL.FAST]: "haiku"
487
+ };
488
+ return mapping[model] ?? model;
489
+ }
490
+ var MCP_TRANSPORT = {
491
+ STDIO: "stdio",
492
+ HTTP: "http",
493
+ SSE: "sse"
494
+ };
495
+
448
496
  // src/agent/bundles/project-context.ts
449
497
  var PROJECT_CONTEXT_PATH = "docs/src/content/docs/project-context.md";
450
498
  var SUBPROJECT_ROLE_GUIDANCE = [
@@ -572,45 +620,6 @@ var MONOREPO_LAYOUT_SEED_BLOCK = [
572
620
  ""
573
621
  ].join("\n");
574
622
 
575
- // src/agent/types.ts
576
- var AGENT_RULE_SCOPE = {
577
- ALWAYS: "always",
578
- FILE_PATTERN: "file-pattern"
579
- };
580
- var AGENT_PLATFORM = {
581
- CURSOR: "cursor",
582
- CLAUDE: "claude",
583
- CODEX: "codex",
584
- COPILOT: "copilot"
585
- };
586
- var CLAUDE_RULE_TARGET = {
587
- SCOPED_FILE: "scoped-file",
588
- AGENTS_MD: "agents-md",
589
- CLAUDE_MD: "claude-md"
590
- };
591
- var AGENT_MODEL = {
592
- INHERIT: "inherit",
593
- FAST: "fast",
594
- BALANCED: "balanced",
595
- POWERFUL: "powerful"
596
- };
597
- function resolveModelAlias(model) {
598
- if (!model || model === AGENT_MODEL.INHERIT) {
599
- return void 0;
600
- }
601
- const mapping = {
602
- [AGENT_MODEL.POWERFUL]: "opus",
603
- [AGENT_MODEL.BALANCED]: "sonnet",
604
- [AGENT_MODEL.FAST]: "haiku"
605
- };
606
- return mapping[model] ?? model;
607
- }
608
- var MCP_TRANSPORT = {
609
- STDIO: "stdio",
610
- HTTP: "http",
611
- SSE: "sse"
612
- };
613
-
614
623
  // src/agent/bundles/agenda.ts
615
624
  var agendaAnalystSubAgent = {
616
625
  name: "agenda-analyst",
@@ -1350,6 +1359,47 @@ var awsCdkBundle = {
1350
1359
  "- When adding a `NodejsFunction` with a bundled handler, ensure the `entry` path is explicitly configured",
1351
1360
  "- Verify the entry is included in the build tool config (e.g., tsup entry list) so the runtime can find the handler",
1352
1361
  "",
1362
+ "## Lambda Construct Encapsulation",
1363
+ "",
1364
+ "When a Lambda construct is event-driven, it should own:",
1365
+ "",
1366
+ "- The EventBridge Rule (or other trigger) that routes events to itself.",
1367
+ "- The IAM permissions it needs on resources passed in via props.",
1368
+ "",
1369
+ "Pass the resources (event bus, table, queue, etc.) into the Lambda",
1370
+ "construct's props rather than defining triggers and grants in the",
1371
+ "enclosing workflow or service. The Lambda construct then becomes",
1372
+ 'self-describing \u2014 "what triggers me, what I write to" lives in one',
1373
+ "file \u2014 and reverting or refactoring a single Lambda doesn't ripple",
1374
+ "through unrelated constructs.",
1375
+ "",
1376
+ "```typescript",
1377
+ "export interface MyLambdaProps {",
1378
+ " readonly eventBus: IEventBus;",
1379
+ " readonly dataStoreTable: ITable;",
1380
+ "}",
1381
+ "",
1382
+ "export class MyLambda extends Construct {",
1383
+ " public readonly lambda: NodejsFunction;",
1384
+ " public readonly rule: Rule;",
1385
+ "",
1386
+ " constructor(scope: Construct, props: MyLambdaProps) {",
1387
+ ' super(scope, "my-lambda");',
1388
+ "",
1389
+ ' this.lambda = new NodejsFunction(this, "handler", { /* ... */ });',
1390
+ " props.dataStoreTable.grantReadWriteData(this.lambda);",
1391
+ ' this.rule = new Rule(this, "rule", {',
1392
+ " eventBus: props.eventBus,",
1393
+ " eventPattern: { /* ... */ },",
1394
+ " targets: [new LambdaFunction(this.lambda)],",
1395
+ " });",
1396
+ " }",
1397
+ "}",
1398
+ "```",
1399
+ "",
1400
+ "A workflow or service construct then composes these Lambda constructs",
1401
+ "without owning their triggers or grants directly.",
1402
+ "",
1353
1403
  "## CDK Testing",
1354
1404
  "",
1355
1405
  "- Mock `Code.fromAsset` in any test file that synthesizes CDK stacks",
@@ -3631,6 +3681,7 @@ function buildBaseBundle(paths = DEFAULT_AGENT_PATHS) {
3631
3681
  "- **After modifying Projen configuration**, run the three-step regen sequence: `pnpm i`, then `pnpm exec projen`, then `pnpm i` again. The leading `pnpm i` syncs `node_modules` with the lockfile so synth runs against the right configulator/projen/plugin versions; the trailing `pnpm i` refreshes the lockfile to match anything projen rewrote in `package.json`.",
3632
3682
  "- **Configure dependencies through Projen** \u2014 never use `npm install`, `pnpm add`, or `yarn add`. Add them to `deps` or `devDeps` in Projen config.",
3633
3683
  "- **Export from index.ts** to maintain clean public APIs",
3684
+ '- **`defaultMode: "dontAsk"` is configulator\'s hardcoded default** for the rendered Claude Code `settings.json`. Scheduled-task workers (issue-worker, orchestrator, pr-reviewer, and the analyst/writer family) run autonomously and would deadlock on confirmation prompts, so the synthesised default suppresses them. Override only after revisiting the autonomous-worker contract end-to-end; the override path is `claudeSettings.defaultMode` on `AgentConfigOptions`.',
3634
3685
  "",
3635
3686
  "## Repository Layout",
3636
3687
  "",
@@ -6106,11 +6157,11 @@ function buildBusinessModelsBundle(paths = DEFAULT_AGENT_PATHS, issueDefaults =
6106
6157
  var businessModelsBundle = buildBusinessModelsBundle();
6107
6158
 
6108
6159
  // src/agent/bundles/company-profile.ts
6109
- function buildCompanyProfileAnalystSubAgent(paths, issueDefaults) {
6160
+ function buildCompanyProfileAnalystSubAgent(paths, issueDefaults, tier) {
6110
6161
  return {
6111
6162
  name: "company-profile-analyst",
6112
6163
  description: "Researches an external company (competitor, vendor, partner, customer, etc.) from public sources and produces a structured markdown profile, then enqueues downstream `people:research` and `software:research` issues for notable people and software products surfaced during profiling. Also handles profile enrichment against business-model canvases (`company:match`), maintenance refreshes on a configurable staleness cadence (`company:refresh`), and cross-profile competitive synthesis for a segment (`company:analyze`). One company or segment per session, tracked by company:* GitHub issue labels.",
6113
- model: AGENT_MODEL.POWERFUL,
6164
+ model: tier,
6114
6165
  maxTurns: 80,
6115
6166
  platforms: { cursor: { exclude: true } },
6116
6167
  prompt: [
@@ -7297,7 +7348,7 @@ function buildAnalyzeSegmentSkill(paths, issueDefaults) {
7297
7348
  ].join("\n")
7298
7349
  };
7299
7350
  }
7300
- function buildCompanyProfileBundle(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS) {
7351
+ function buildCompanyProfileBundle(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS, tier = AGENT_MODEL.BALANCED) {
7301
7352
  return {
7302
7353
  name: "company-profile",
7303
7354
  description: "Company research and profiling pipeline: research, draft profile, followup, match, refresh, analyze. Enabled by default; domain-neutral; filesystem-durable between phases. Phase 3 (Followup) hands surfaced people and software products off to the `people-profile` and `software-profile` bundles via `people:research` and `software:research` issues. Phase 4 (Match) enriches profiles against business-model canvases. Phase 5 (Refresh) re-verifies profiles on a configurable staleness cadence. Phase 6 (Analyze) synthesizes profiles in a segment into a competitive-analysis document.",
@@ -7355,7 +7406,7 @@ function buildCompanyProfileBundle(paths = DEFAULT_AGENT_PATHS, issueDefaults =
7355
7406
  buildRefreshCompanySkill(paths, issueDefaults),
7356
7407
  buildAnalyzeSegmentSkill(paths, issueDefaults)
7357
7408
  ],
7358
- subAgents: [buildCompanyProfileAnalystSubAgent(paths, issueDefaults)],
7409
+ subAgents: [buildCompanyProfileAnalystSubAgent(paths, issueDefaults, tier)],
7359
7410
  labels: [
7360
7411
  {
7361
7412
  name: "type:company-profile",
@@ -7535,11 +7586,11 @@ var CUSTOMER_PROFILE_REFERENCE_FILES = [
7535
7586
  content: TEMPLATE_CUSTOMER_PROFILE
7536
7587
  }
7537
7588
  ];
7538
- function buildCustomerProfileAnalystSubAgent(paths, issueDefaults) {
7589
+ function buildCustomerProfileAnalystSubAgent(paths, issueDefaults, tier) {
7539
7590
  return {
7540
7591
  name: "customer-profile-analyst",
7541
7592
  description: "Authors customer-archetype research through a 3-phase pipeline (discover \u2192 profile \u2192 competitors). Segments customer archetypes, profiles each archetype's goals, jobs-to-be-done, constraints, and buying process, then maps competitor features (from the shared software-profile feature matrix) to each archetype's needs and hands off unmet-need gaps to the requirements-analyst as req:scan seeds. One phase per session, tracked by customer:* GitHub issue labels with filesystem-based durability between phases.",
7542
- model: AGENT_MODEL.POWERFUL,
7593
+ model: tier,
7543
7594
  maxTurns: 80,
7544
7595
  platforms: { cursor: { exclude: true } },
7545
7596
  prompt: [
@@ -8364,7 +8415,7 @@ function buildAnalyzeCustomerCompetitorsSkill(paths, issueDefaults) {
8364
8415
  ].join("\n")
8365
8416
  };
8366
8417
  }
8367
- function buildCustomerProfileBundle(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS) {
8418
+ function buildCustomerProfileBundle(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS, tier = AGENT_MODEL.BALANCED) {
8368
8419
  return {
8369
8420
  name: "customer-profile",
8370
8421
  description: "Customer-archetype research pipeline: discover, profile, competitors. 3 phases with customer:* phase labels. Segments customer archetypes, profiles each archetype's goals, jobs-to-be-done, constraints, and buying process, then maps competitor features (from the shared software-profile feature matrix) to each archetype's needs and hands off unmet-need gaps to the requirements-analyst bundle as req:scan seeds. Enabled by default; domain-neutral; customer-centric (distinct from company-profile); filesystem-durable between phases.",
@@ -8439,7 +8490,9 @@ function buildCustomerProfileBundle(paths = DEFAULT_AGENT_PATHS, issueDefaults =
8439
8490
  buildProfileCustomerSkill(paths, issueDefaults),
8440
8491
  buildAnalyzeCustomerCompetitorsSkill(paths, issueDefaults)
8441
8492
  ],
8442
- subAgents: [buildCustomerProfileAnalystSubAgent(paths, issueDefaults)],
8493
+ subAgents: [
8494
+ buildCustomerProfileAnalystSubAgent(paths, issueDefaults, tier)
8495
+ ],
8443
8496
  labels: [
8444
8497
  {
8445
8498
  name: "type:customer-profile",
@@ -10222,11 +10275,11 @@ var jestBundle = {
10222
10275
  };
10223
10276
 
10224
10277
  // src/agent/bundles/maintenance-audit.ts
10225
- function buildMaintenanceAuditSubAgent(paths) {
10278
+ function buildMaintenanceAuditSubAgent(paths, tier) {
10226
10279
  return {
10227
10280
  name: "maintenance-audit",
10228
10281
  description: "Audits documentation registries and cross-references for integrity (broken links, registry drift, stale indexes), applies idempotent fixes, then re-runs the checks to confirm the fixes cleared the reported findings. One phase per session, tracked by maint:* GitHub issue labels with filesystem-based durability between phases.",
10229
- model: AGENT_MODEL.POWERFUL,
10282
+ model: tier,
10230
10283
  maxTurns: 80,
10231
10284
  platforms: { cursor: { exclude: true } },
10232
10285
  prompt: [
@@ -10860,7 +10913,7 @@ function buildMaintenanceVerifySkill(issueDefaults) {
10860
10913
  ].join("\n")
10861
10914
  };
10862
10915
  }
10863
- function buildMaintenanceAuditBundle(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS) {
10916
+ function buildMaintenanceAuditBundle(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS, tier = AGENT_MODEL.BALANCED) {
10864
10917
  return {
10865
10918
  name: "maintenance-audit",
10866
10919
  description: "Documentation-maintenance agent bundle. 3-phase pipeline (scan, fix, verify) with maint:* phase labels for auditing registries and cross-references, applying idempotent fixes, and confirming the fixes cleared the originally-flagged findings. Enabled by default.",
@@ -10907,7 +10960,7 @@ function buildMaintenanceAuditBundle(paths = DEFAULT_AGENT_PATHS, issueDefaults
10907
10960
  buildMaintenanceAuditSkill(paths, issueDefaults),
10908
10961
  buildMaintenanceVerifySkill(issueDefaults)
10909
10962
  ],
10910
- subAgents: [buildMaintenanceAuditSubAgent(paths)],
10963
+ subAgents: [buildMaintenanceAuditSubAgent(paths, tier)],
10911
10964
  labels: [
10912
10965
  {
10913
10966
  name: "type:maintenance",
@@ -10935,351 +10988,353 @@ function buildMaintenanceAuditBundle(paths = DEFAULT_AGENT_PATHS, issueDefaults
10935
10988
  var maintenanceAuditBundle = buildMaintenanceAuditBundle();
10936
10989
 
10937
10990
  // src/agent/bundles/meeting-analysis.ts
10938
- var meetingAnalystSubAgent = {
10939
- name: "meeting-analyst",
10940
- description: "Processes meeting transcripts through a 4-phase pipeline: extract, notes, draft, and link",
10941
- model: AGENT_MODEL.POWERFUL,
10942
- maxTurns: 80,
10943
- platforms: { cursor: { exclude: true } },
10944
- prompt: [
10945
- "# Meeting Analyst Agent",
10946
- "",
10947
- "You process meeting transcripts through a structured 4-phase pipeline.",
10948
- "Each phase runs as a **separate agent session**, triggered by its own",
10949
- "GitHub issue with a `meeting:*` phase label. You handle exactly **one",
10950
- "phase per session** \u2014 read the issue to determine which phase to execute.",
10951
- "",
10952
- "## Execution Model",
10953
- "",
10954
- "1. Each meeting produces up to 4 GitHub issues (one per phase)",
10955
- "2. Each issue carries a `meeting:*` label identifying the phase",
10956
- "3. You pick up one issue, execute that phase, commit/push, then close the issue",
10957
- "4. Phase 1 (Extract) creates the downstream phase issues for phases 2-4",
10958
- "5. Each downstream issue includes `Depends on: #N` linking to its predecessor",
10959
- "",
10960
- "## Design Principles",
10961
- "",
10962
- "1. **Extract, don't interpret.** Capture what was said and decided. Flag",
10963
- " ambiguity as open items rather than resolving it.",
10964
- "2. **Route to the right category.** Meeting content maps to requirements,",
10965
- " ADRs, product docs, and business strategy. Each output goes to the",
10966
- " correct location per the project's taxonomy.",
10967
- "3. **Preserve provenance.** Every extracted item links back to the meeting",
10968
- " source so reviewers can check context.",
10969
- "4. **Create issues, not documents.** For requirements and ADRs, create",
10970
- " GitHub issues that other agents or humans will pick up. Do not write",
10971
- " final requirement documents yourself.",
10972
- "5. **Bi-directional traceability.** Every document and issue created by",
10973
- " this pipeline must link back to the meeting source, and the meeting",
10974
- " source documents must link forward to everything created from them.",
10975
- "6. **Respect meeting type and areas.** Read the `meeting_type` and",
10976
- " `areas` values from the meeting frontmatter before starting Phase 1.",
10977
- " Apply the type-specific rules from the **Meeting type handling**",
10978
- " table (phases 1\u20132) and the area-filtered routing rules from the",
10979
- " **Areas filtering** table (phase 4).",
10980
- "",
10981
- "---",
10982
- "",
10983
- "## Meeting type handling",
10984
- "",
10985
- "Meeting frontmatter is expected to carry a `meeting_type` field whose",
10986
- "value is one of the project's concrete meeting-type `id`s (declared",
10987
- "via `AgentConfigOptions.meetings.meetingTypes`). Each concrete `id`",
10988
- "maps to one of six generic **meeting kinds** that drive type-specific",
10989
- "extraction rules:",
10990
- "",
10991
- "| Kind | Primary outputs | Phase-1/2 rules |",
10992
- "|------|-----------------|-----------------|",
10993
- "| `planning` | Sprint/plan updates, task issues, requirement drafts | Extract every task assignment. Phase 4 updates the active sprint-plan doc directly and creates one issue per assigned task. |",
10994
- "| `review` | Retro notes, follow-up issues, status updates, requirement revisions | Mark completed tasks in the active sprint-plan doc. Capture retrospective learnings inside the meeting notes \u2014 do not spawn a separate retro document. |",
10995
- "| `brainstorm` | Future-feature candidates, research topics, very few Firm decisions | Lower the bar for Open Questions. Do **not** create requirement or ADR issues from brainstorm output unless the item is explicitly marked **Firm**. Prefer research/discovery issues for promising ideas. |",
10996
- "| `standup` | Action items, blockers, status updates | Do not produce requirement, ADR, or BDR drafts. Phase 3 (Draft) is almost always skipped. Focus on action-item issues and sprint-plan status updates. |",
10997
- "| `external` | Customer/competitive intel, people/company profiles, lead capture | Default every attendee outside the team into **People of Interest**. Default every organization mentioned into **Companies of Interest** (signal threshold still applies). Capture stated customer pain points as candidate **BR** (business requirements), not FR. |",
10998
- "| `other` | General meeting extraction | Apply the default workflow with no type-specific overrides. |",
10999
- "",
11000
- "**How to resolve the kind:**",
11001
- "",
11002
- "1. Read `meeting_type` from the frontmatter.",
11003
- "2. Look up the matching `id` in the project's **Recognized meeting",
11004
- " types** table (rendered below when `meetingTypes` is declared).",
11005
- "3. Use the `Kind` column value. If the table is absent, the `id` is",
11006
- " unknown, or the `Kind` column is `\u2014`, fall back to `other`.",
11007
- "4. Apply the rules in the row above **in addition to** the normal",
11008
- " phase workflow \u2014 never as a replacement.",
11009
- "",
11010
- "If `meeting_type` is missing, invalid, or the declared `id` is not in",
11011
- "the project's table, note it in the extraction's Open Questions and",
11012
- "proceed with the default workflow (treat as `other`).",
11013
- "",
11014
- "## Areas filtering",
11015
- "",
11016
- "Meeting frontmatter may carry an `areas: [...]` list whose values",
11017
- "match `id`s from the project's **Area \u2192 doc-root mapping** table",
11018
- "(declared via `AgentConfigOptions.meetings.meetingAreas`). The list",
11019
- "scopes phase-4 direct edits and cross-references to the doc-root",
11020
- "sub-folders registered for those areas. A given area's effective",
11021
- "path resolves as `<docsRoot>/<docRoot>` \u2014 `docsRoot` comes from",
11022
- "`AgentPathsConfig.docsRoot`; `docRoot` is the sub-folder shown in",
11023
- "the **Area \u2192 doc-root mapping** table.",
11024
- "",
11025
- "**Gating contract (phase 4 only):**",
11026
- "",
11027
- "- **Scoped in:** direct edits and cross-reference stubs under the",
11028
- " `docRoot` of every `id` present in the meeting's `areas` list.",
11029
- "- **Scoped out:** direct edits to any doc-root sub-folder that is",
11030
- " declared in the project's area map but whose `id` is **not** in",
11031
- " the meeting's `areas`. For those out-of-scope areas, create",
11032
- " follow-up issues instead of editing the doc directly.",
11033
- "- **Not gated:** issue creation (requirements, ADRs, profiles,",
11034
- " research) is always in scope regardless of areas.",
11035
- "",
11036
- "**Resolution rules:**",
11037
- "",
11038
- "1. If the meeting's `areas` frontmatter is missing or empty **and**",
11039
- " the project has declared a non-empty `meetingAreas` map, no",
11040
- " doc-root sub-folder is gated in: direct edits to any declared",
11041
- " `docRoot` sub-folder are out-of-scope and become follow-up issues",
11042
- " per step 6. Only the default meetings root (`meetingsRoot`)",
11043
- " remains editable.",
11044
- "2. If `areas` lists an `id` that is not in the project's area map,",
11045
- " note it in the extraction's Open Questions and treat the unknown",
11046
- " `id` as absent. Do **not** invent a doc-root path for it.",
11047
- "3. Out-of-scope direct edits become `type:docs` follow-up issues on",
11048
- " phase 4 step 6, with the area noted in the issue body.",
11049
- "",
11050
- "When the project has **not** declared any `meetingAreas` at all,",
11051
- "this section applies degenerately \u2014 no areas are gated, no",
11052
- "follow-up issues are synthesized for missing areas, and phase-4",
11053
- "direct edits follow the default workflow (step 6 edits any doc",
11054
- "flagged as Firm, unrestricted by `docRoot`).",
11055
- "",
11056
- "---",
11057
- "",
11058
- ...PROJECT_CONTEXT_MAINTAINER_SECTION,
11059
- "## Traceability",
11060
- "",
11061
- "All outputs must be bi-directionally linked so any artifact can be traced",
11062
- "back to the meeting that produced it and forward to everything it spawned.",
11063
- "",
11064
- "### Backward links (created artifact \u2192 meeting source)",
11065
- "",
11066
- "Every GitHub issue and document created by this pipeline must include a",
11067
- "`## Traceability` section with:",
11068
- "",
11069
- "```markdown",
11070
- "## Traceability",
11071
- "",
11072
- "- **Source meeting:** <path to transcript or meeting notes>",
11073
- "- **Extraction:** <path to extraction file>",
11074
- "- **Phase issue:** #<N> (the phase issue that created this artifact)",
11075
- "```",
11076
- "",
11077
- "### Forward links (meeting source \u2192 created artifacts)",
11078
- "",
11079
- "After Phase 4 (Link) creates all follow-up issues and documents:",
11080
- "",
11081
- "1. **Update the extraction file** with a `## Downstream Artifacts` section",
11082
- " listing every issue and document created from this meeting:",
11083
- "",
11084
- " ```markdown",
11085
- " ## Downstream Artifacts",
11086
- "",
11087
- " | Artifact | Type | Issue/Path |",
11088
- " |----------|------|------------|",
11089
- " | <title> | requirement / ADR / action-item / profile | #<N> or <path> |",
11090
- " ```",
11091
- "",
11092
- "2. **Update the meeting notes** with a similar `## Related Issues` section",
11093
- " listing all issues created from this meeting.",
11094
- "",
11095
- "3. **Comment on the extract issue** with a summary linking to all created",
11096
- " artifacts.",
11097
- "",
11098
- "### Within-pipeline links",
11099
- "",
11100
- "- Phase issues link to predecessors via `Depends on: #N`",
11101
- "- Phase issues reference the extraction file path in their body",
11102
- "- Draft documents reference both the extraction and the meeting notes",
11103
- "",
11104
- "---",
11105
- "",
11106
- "## Phase 1: Extract (`meeting:extract`)",
11107
- "",
11108
- "**Goal:** Read the meeting transcript and categorize all substantive content.",
11109
- "",
11110
- "### Steps",
11111
- "",
11112
- "1. Read the transcript file specified in the issue body. Parse the",
11113
- " meeting-note frontmatter first, capturing `meeting_type` and",
11114
- " `areas`. Resolve `meeting_type` to a kind using the **Meeting type",
11115
- " handling** table; note the resolved kind in the extraction",
11116
- " frontmatter. If `meeting_type` is missing or not in the project's",
11117
- " Recognized meeting types table, treat it as `other` and flag it",
11118
- " in Open Questions.",
11119
- "2. Identify and categorize content into these buckets:",
11120
- "",
11121
- " | Bucket | What to look for |",
11122
- " |--------|-----------------|",
11123
- ` | **Decisions** | "We decided...", "Let's go with...", explicit choices |`,
11124
- ' | **Requirements** | Feature descriptions, acceptance criteria, "it should..." |',
11125
- " | **Technology discussions** | Platform comparisons, architecture options, tool evaluations |",
11126
- ' | **Action items** | "[Person] will...", "Next step is...", assigned tasks |',
11127
- ' | **Open questions** | "We need to figure out...", unresolved debates |',
11128
- " | **People of interest** | Industry contacts, domain experts mentioned |",
11129
- " | **Companies of interest** | Competitors, vendors, partners discussed |",
11130
- " | **Strategic direction** | Business model changes, market positioning |",
11131
- " | **Product direction** | Roadmap changes, feature prioritization |",
11132
- "",
11133
- "3. Write the extraction to a markdown file with structured sections:",
11134
- " - Attendees",
11135
- " - Decisions Made (with category and confidence: Firm / Tentative / Needs confirmation)",
11136
- " - Requirements Identified (with category and priority estimate)",
11137
- " - Technology Discussions (with status: Decided / Leaning toward / Open)",
11138
- " - Action Items (with assignee and due date if stated)",
11139
- " - Open Questions",
11140
- " - People of Interest (with context and whether a profile exists)",
11141
- " - Companies of Interest (with type and context)",
11142
- " - Strategic / Product Direction",
11143
- "",
11144
- " Carry `meeting_type`, the resolved `meeting_kind`, and `areas`",
11145
- " into the extraction frontmatter so downstream phases can gate",
11146
- " behaviour without re-parsing the meeting note.",
11147
- "",
11148
- "4. **Apply type-specific rules.** Consult the **Meeting type handling**",
11149
- " table for the resolved kind and apply its phase-1 rules in",
11150
- " addition to the default extraction above. Examples:",
11151
- " - `standup`: skip requirement, ADR, and BDR extraction; focus on",
11152
- " action items and status.",
11153
- " - `brainstorm`: emphasize Open Questions and Future Features.",
11154
- " Only mark decisions as Firm when the transcript is unambiguous.",
11155
- " - `external`: default attendees outside the team into People of",
11156
- " Interest; default organizations mentioned into Companies of",
11157
- " Interest (signal threshold still applies); capture customer",
11158
- " pain points as candidate BR (not FR).",
11159
- "",
11160
- "5. **Create downstream phase issues** using `gh issue create`:",
11161
- " - Always create a `meeting:notes` issue (blocked on this extract issue)",
11162
- " - If requirements OR decisions/ADRs identified, create a `meeting:draft` issue",
11163
- " (blocked on the notes issue). For kind `standup`, skip the",
11164
- " `meeting:draft` issue \u2014 standup meetings almost never produce",
11165
- " formal drafts.",
11166
- " - Always create a `meeting:link` issue \u2014 blocked on the draft issue if one",
11167
- " was created, otherwise blocked on the notes issue",
11168
- "",
11169
- "6. Commit, push, and close the extract issue.",
11170
- "",
11171
- "---",
11172
- "",
11173
- "## Phase 2: Notes (`meeting:notes`)",
11174
- "",
11175
- "**Goal:** Transform the extraction into structured meeting notes.",
11176
- "",
11177
- "### Steps",
11178
- "",
11179
- "1. Read the extraction file referenced in the issue body (output of Phase 1).",
11180
- "2. Write structured meeting notes with these sections:",
11181
- " - Meeting metadata (title, date, attendees)",
11182
- " - Agenda / topics covered",
11183
- " - Key Discussion Points (organized by topic)",
11184
- " - Decisions (numbered, with rationale)",
11185
- " - Action Items (table: who, what, when)",
11186
- " - Open Questions",
11187
- " - Follow-up items",
11188
- "3. Commit, push, and close the notes issue.",
11189
- "",
11190
- "---",
11191
- "",
11192
- "## Phase 3: Draft (`meeting:draft`)",
11193
- "",
11194
- "**Goal:** Draft proposals for requirements, ADRs, and product/strategy updates.",
11195
- "",
11196
- "This phase only exists if the extraction identified requirements, architectural",
11197
- "decisions, or strategy changes. If this issue exists, execute it.",
11198
- "",
11199
- "### Steps",
11200
- "",
11201
- "1. Read the extraction file from Phase 1.",
11202
- "2. Check existing requirement registries and ADR registries for duplicates.",
11203
- "3. Draft requirement proposals with:",
11204
- " - Category (FR, BR, NFR, etc.)",
11205
- " - Summary (2-3 sentences)",
11206
- " - Draft acceptance criteria",
11207
- " - Related existing requirements",
11208
- "4. Draft ADR proposals for technology decisions with:",
11209
- " - Context and problem statement",
11210
- " - Options discussed",
11211
- " - Stated preferences or decisions",
11212
- " - Status: Proposed or Decided",
11213
- "5. Summarize product/strategy document updates needed.",
11214
- "6. Commit, push, and close the draft issue.",
11215
- "",
11216
- "---",
11217
- "",
11218
- "## Phase 4: Link (`meeting:link`)",
11219
- "",
11220
- "**Goal:** Create GitHub issues for follow-up work, cross-reference the",
11221
- "meeting into existing documentation, and complete bi-directional traceability.",
11222
- "",
11223
- "### Steps",
11224
- "",
11225
- "1. Read the drafts from Phase 3 (if they exist) and the extraction from Phase 1.",
11226
- " Re-read the extraction frontmatter for `meeting_kind` and `areas`,",
11227
- " then build the area-gate: the set of `docRoot` sub-folders that",
11228
- " are in scope for direct edits on this meeting. Apply the rules in",
11229
- " the **Areas filtering** section above.",
11230
- "2. Create requirement issues using `gh issue create` with appropriate labels.",
11231
- " Include a `## Traceability` section in each issue body linking back to",
11232
- " the source meeting and extraction file. Issue creation is **not**",
11233
- " gated by areas.",
11234
- "3. Create ADR issues if technology decisions need formal records.",
11235
- " Include a `## Traceability` section in each. Not gated by areas.",
11236
- "4. Create action item issues for tasks that are not document-related.",
11237
- " Include a `## Traceability` section in each. Not gated by areas.",
11238
- "5. Cross-reference the meeting in any existing documents that were",
11239
- " discussed **and that live under an in-scope `docRoot`**. For",
11240
- " discussed documents under an out-of-scope `docRoot`, open a",
11241
- " follow-up `type:docs` issue instead of editing the doc directly.",
11242
- "6. Apply direct product/strategy doc updates for items flagged as",
11243
- " **Firm** confidence. When the project declares `meetingAreas`,",
11244
- " gate these edits: only apply them when the target path lies",
11245
- " under an in-scope `docRoot`, and for Firm items targeting an",
11246
- " out-of-scope `docRoot` open a follow-up `type:docs` issue",
11247
- " noting the area and the target path. When the project has not",
11248
- " declared any `meetingAreas`, apply Firm updates unrestricted",
11249
- " (the degenerate case above). For the kind `planning`, also",
11250
- " update the active sprint-plan doc directly (when the",
11251
- " sprint-plan doc's area is in-scope, or always when no",
11252
- " `meetingAreas` are declared); for the kind `review`, mark",
11253
- " completed tasks in that same doc.",
11254
- "7. **Update the extraction file** with a `## Downstream Artifacts` section",
11255
- " listing every issue and document created from this meeting. Note",
11256
- " any items that were deferred to follow-up issues because of the",
11257
- " area gate.",
11258
- "8. **Update the meeting notes** with a `## Related Issues` section listing",
11259
- " all issues created from this meeting.",
11260
- "9. Comment on the parent extract issue with a summary linking to all",
11261
- " created artifacts.",
11262
- "10. Commit and push (if any file changes were made), then close the link issue.",
11263
- "",
11264
- "---",
11265
- "",
11266
- "## GitHub Integration",
11267
- "",
11268
- "- Use `gh` CLI for all GitHub operations",
11269
- "- Apply the appropriate `meeting:*` phase label to each phase issue",
11270
- "- Use `type:docs` for documentation-producing issues, `type:chore` for",
11271
- " maintenance/organizational tasks",
11272
- "- Use `status:` labels to track progress (`status:ready`, `status:in-progress`, `status:done`)",
11273
- "- Reference the source meeting transcript in all created issues",
11274
- "- Link phase issues with `Depends on: #N` to enforce ordering",
11275
- "",
11276
- "## When to Shorten the Pipeline",
11277
- "",
11278
- "- **Short meetings** (<30 min, <2000 words): combine extract + notes into one session",
11279
- "- **No requirements or decisions**: Phase 1 skips creating the `meeting:draft` issue",
11280
- "- **Only action items**: extract + notes + link (3 phase issues)"
11281
- ].join("\n")
11282
- };
10991
+ function buildMeetingAnalystSubAgent(tier) {
10992
+ return {
10993
+ name: "meeting-analyst",
10994
+ description: "Processes meeting transcripts through a 4-phase pipeline: extract, notes, draft, and link",
10995
+ model: tier,
10996
+ maxTurns: 80,
10997
+ platforms: { cursor: { exclude: true } },
10998
+ prompt: [
10999
+ "# Meeting Analyst Agent",
11000
+ "",
11001
+ "You process meeting transcripts through a structured 4-phase pipeline.",
11002
+ "Each phase runs as a **separate agent session**, triggered by its own",
11003
+ "GitHub issue with a `meeting:*` phase label. You handle exactly **one",
11004
+ "phase per session** \u2014 read the issue to determine which phase to execute.",
11005
+ "",
11006
+ "## Execution Model",
11007
+ "",
11008
+ "1. Each meeting produces up to 4 GitHub issues (one per phase)",
11009
+ "2. Each issue carries a `meeting:*` label identifying the phase",
11010
+ "3. You pick up one issue, execute that phase, commit/push, then close the issue",
11011
+ "4. Phase 1 (Extract) creates the downstream phase issues for phases 2-4",
11012
+ "5. Each downstream issue includes `Depends on: #N` linking to its predecessor",
11013
+ "",
11014
+ "## Design Principles",
11015
+ "",
11016
+ "1. **Extract, don't interpret.** Capture what was said and decided. Flag",
11017
+ " ambiguity as open items rather than resolving it.",
11018
+ "2. **Route to the right category.** Meeting content maps to requirements,",
11019
+ " ADRs, product docs, and business strategy. Each output goes to the",
11020
+ " correct location per the project's taxonomy.",
11021
+ "3. **Preserve provenance.** Every extracted item links back to the meeting",
11022
+ " source so reviewers can check context.",
11023
+ "4. **Create issues, not documents.** For requirements and ADRs, create",
11024
+ " GitHub issues that other agents or humans will pick up. Do not write",
11025
+ " final requirement documents yourself.",
11026
+ "5. **Bi-directional traceability.** Every document and issue created by",
11027
+ " this pipeline must link back to the meeting source, and the meeting",
11028
+ " source documents must link forward to everything created from them.",
11029
+ "6. **Respect meeting type and areas.** Read the `meeting_type` and",
11030
+ " `areas` values from the meeting frontmatter before starting Phase 1.",
11031
+ " Apply the type-specific rules from the **Meeting type handling**",
11032
+ " table (phases 1\u20132) and the area-filtered routing rules from the",
11033
+ " **Areas filtering** table (phase 4).",
11034
+ "",
11035
+ "---",
11036
+ "",
11037
+ "## Meeting type handling",
11038
+ "",
11039
+ "Meeting frontmatter is expected to carry a `meeting_type` field whose",
11040
+ "value is one of the project's concrete meeting-type `id`s (declared",
11041
+ "via `AgentConfigOptions.meetings.meetingTypes`). Each concrete `id`",
11042
+ "maps to one of six generic **meeting kinds** that drive type-specific",
11043
+ "extraction rules:",
11044
+ "",
11045
+ "| Kind | Primary outputs | Phase-1/2 rules |",
11046
+ "|------|-----------------|-----------------|",
11047
+ "| `planning` | Sprint/plan updates, task issues, requirement drafts | Extract every task assignment. Phase 4 updates the active sprint-plan doc directly and creates one issue per assigned task. |",
11048
+ "| `review` | Retro notes, follow-up issues, status updates, requirement revisions | Mark completed tasks in the active sprint-plan doc. Capture retrospective learnings inside the meeting notes \u2014 do not spawn a separate retro document. |",
11049
+ "| `brainstorm` | Future-feature candidates, research topics, very few Firm decisions | Lower the bar for Open Questions. Do **not** create requirement or ADR issues from brainstorm output unless the item is explicitly marked **Firm**. Prefer research/discovery issues for promising ideas. |",
11050
+ "| `standup` | Action items, blockers, status updates | Do not produce requirement, ADR, or BDR drafts. Phase 3 (Draft) is almost always skipped. Focus on action-item issues and sprint-plan status updates. |",
11051
+ "| `external` | Customer/competitive intel, people/company profiles, lead capture | Default every attendee outside the team into **People of Interest**. Default every organization mentioned into **Companies of Interest** (signal threshold still applies). Capture stated customer pain points as candidate **BR** (business requirements), not FR. |",
11052
+ "| `other` | General meeting extraction | Apply the default workflow with no type-specific overrides. |",
11053
+ "",
11054
+ "**How to resolve the kind:**",
11055
+ "",
11056
+ "1. Read `meeting_type` from the frontmatter.",
11057
+ "2. Look up the matching `id` in the project's **Recognized meeting",
11058
+ " types** table (rendered below when `meetingTypes` is declared).",
11059
+ "3. Use the `Kind` column value. If the table is absent, the `id` is",
11060
+ " unknown, or the `Kind` column is `\u2014`, fall back to `other`.",
11061
+ "4. Apply the rules in the row above **in addition to** the normal",
11062
+ " phase workflow \u2014 never as a replacement.",
11063
+ "",
11064
+ "If `meeting_type` is missing, invalid, or the declared `id` is not in",
11065
+ "the project's table, note it in the extraction's Open Questions and",
11066
+ "proceed with the default workflow (treat as `other`).",
11067
+ "",
11068
+ "## Areas filtering",
11069
+ "",
11070
+ "Meeting frontmatter may carry an `areas: [...]` list whose values",
11071
+ "match `id`s from the project's **Area \u2192 doc-root mapping** table",
11072
+ "(declared via `AgentConfigOptions.meetings.meetingAreas`). The list",
11073
+ "scopes phase-4 direct edits and cross-references to the doc-root",
11074
+ "sub-folders registered for those areas. A given area's effective",
11075
+ "path resolves as `<docsRoot>/<docRoot>` \u2014 `docsRoot` comes from",
11076
+ "`AgentPathsConfig.docsRoot`; `docRoot` is the sub-folder shown in",
11077
+ "the **Area \u2192 doc-root mapping** table.",
11078
+ "",
11079
+ "**Gating contract (phase 4 only):**",
11080
+ "",
11081
+ "- **Scoped in:** direct edits and cross-reference stubs under the",
11082
+ " `docRoot` of every `id` present in the meeting's `areas` list.",
11083
+ "- **Scoped out:** direct edits to any doc-root sub-folder that is",
11084
+ " declared in the project's area map but whose `id` is **not** in",
11085
+ " the meeting's `areas`. For those out-of-scope areas, create",
11086
+ " follow-up issues instead of editing the doc directly.",
11087
+ "- **Not gated:** issue creation (requirements, ADRs, profiles,",
11088
+ " research) is always in scope regardless of areas.",
11089
+ "",
11090
+ "**Resolution rules:**",
11091
+ "",
11092
+ "1. If the meeting's `areas` frontmatter is missing or empty **and**",
11093
+ " the project has declared a non-empty `meetingAreas` map, no",
11094
+ " doc-root sub-folder is gated in: direct edits to any declared",
11095
+ " `docRoot` sub-folder are out-of-scope and become follow-up issues",
11096
+ " per step 6. Only the default meetings root (`meetingsRoot`)",
11097
+ " remains editable.",
11098
+ "2. If `areas` lists an `id` that is not in the project's area map,",
11099
+ " note it in the extraction's Open Questions and treat the unknown",
11100
+ " `id` as absent. Do **not** invent a doc-root path for it.",
11101
+ "3. Out-of-scope direct edits become `type:docs` follow-up issues on",
11102
+ " phase 4 step 6, with the area noted in the issue body.",
11103
+ "",
11104
+ "When the project has **not** declared any `meetingAreas` at all,",
11105
+ "this section applies degenerately \u2014 no areas are gated, no",
11106
+ "follow-up issues are synthesized for missing areas, and phase-4",
11107
+ "direct edits follow the default workflow (step 6 edits any doc",
11108
+ "flagged as Firm, unrestricted by `docRoot`).",
11109
+ "",
11110
+ "---",
11111
+ "",
11112
+ ...PROJECT_CONTEXT_MAINTAINER_SECTION,
11113
+ "## Traceability",
11114
+ "",
11115
+ "All outputs must be bi-directionally linked so any artifact can be traced",
11116
+ "back to the meeting that produced it and forward to everything it spawned.",
11117
+ "",
11118
+ "### Backward links (created artifact \u2192 meeting source)",
11119
+ "",
11120
+ "Every GitHub issue and document created by this pipeline must include a",
11121
+ "`## Traceability` section with:",
11122
+ "",
11123
+ "```markdown",
11124
+ "## Traceability",
11125
+ "",
11126
+ "- **Source meeting:** <path to transcript or meeting notes>",
11127
+ "- **Extraction:** <path to extraction file>",
11128
+ "- **Phase issue:** #<N> (the phase issue that created this artifact)",
11129
+ "```",
11130
+ "",
11131
+ "### Forward links (meeting source \u2192 created artifacts)",
11132
+ "",
11133
+ "After Phase 4 (Link) creates all follow-up issues and documents:",
11134
+ "",
11135
+ "1. **Update the extraction file** with a `## Downstream Artifacts` section",
11136
+ " listing every issue and document created from this meeting:",
11137
+ "",
11138
+ " ```markdown",
11139
+ " ## Downstream Artifacts",
11140
+ "",
11141
+ " | Artifact | Type | Issue/Path |",
11142
+ " |----------|------|------------|",
11143
+ " | <title> | requirement / ADR / action-item / profile | #<N> or <path> |",
11144
+ " ```",
11145
+ "",
11146
+ "2. **Update the meeting notes** with a similar `## Related Issues` section",
11147
+ " listing all issues created from this meeting.",
11148
+ "",
11149
+ "3. **Comment on the extract issue** with a summary linking to all created",
11150
+ " artifacts.",
11151
+ "",
11152
+ "### Within-pipeline links",
11153
+ "",
11154
+ "- Phase issues link to predecessors via `Depends on: #N`",
11155
+ "- Phase issues reference the extraction file path in their body",
11156
+ "- Draft documents reference both the extraction and the meeting notes",
11157
+ "",
11158
+ "---",
11159
+ "",
11160
+ "## Phase 1: Extract (`meeting:extract`)",
11161
+ "",
11162
+ "**Goal:** Read the meeting transcript and categorize all substantive content.",
11163
+ "",
11164
+ "### Steps",
11165
+ "",
11166
+ "1. Read the transcript file specified in the issue body. Parse the",
11167
+ " meeting-note frontmatter first, capturing `meeting_type` and",
11168
+ " `areas`. Resolve `meeting_type` to a kind using the **Meeting type",
11169
+ " handling** table; note the resolved kind in the extraction",
11170
+ " frontmatter. If `meeting_type` is missing or not in the project's",
11171
+ " Recognized meeting types table, treat it as `other` and flag it",
11172
+ " in Open Questions.",
11173
+ "2. Identify and categorize content into these buckets:",
11174
+ "",
11175
+ " | Bucket | What to look for |",
11176
+ " |--------|-----------------|",
11177
+ ` | **Decisions** | "We decided...", "Let's go with...", explicit choices |`,
11178
+ ' | **Requirements** | Feature descriptions, acceptance criteria, "it should..." |',
11179
+ " | **Technology discussions** | Platform comparisons, architecture options, tool evaluations |",
11180
+ ' | **Action items** | "[Person] will...", "Next step is...", assigned tasks |',
11181
+ ' | **Open questions** | "We need to figure out...", unresolved debates |',
11182
+ " | **People of interest** | Industry contacts, domain experts mentioned |",
11183
+ " | **Companies of interest** | Competitors, vendors, partners discussed |",
11184
+ " | **Strategic direction** | Business model changes, market positioning |",
11185
+ " | **Product direction** | Roadmap changes, feature prioritization |",
11186
+ "",
11187
+ "3. Write the extraction to a markdown file with structured sections:",
11188
+ " - Attendees",
11189
+ " - Decisions Made (with category and confidence: Firm / Tentative / Needs confirmation)",
11190
+ " - Requirements Identified (with category and priority estimate)",
11191
+ " - Technology Discussions (with status: Decided / Leaning toward / Open)",
11192
+ " - Action Items (with assignee and due date if stated)",
11193
+ " - Open Questions",
11194
+ " - People of Interest (with context and whether a profile exists)",
11195
+ " - Companies of Interest (with type and context)",
11196
+ " - Strategic / Product Direction",
11197
+ "",
11198
+ " Carry `meeting_type`, the resolved `meeting_kind`, and `areas`",
11199
+ " into the extraction frontmatter so downstream phases can gate",
11200
+ " behaviour without re-parsing the meeting note.",
11201
+ "",
11202
+ "4. **Apply type-specific rules.** Consult the **Meeting type handling**",
11203
+ " table for the resolved kind and apply its phase-1 rules in",
11204
+ " addition to the default extraction above. Examples:",
11205
+ " - `standup`: skip requirement, ADR, and BDR extraction; focus on",
11206
+ " action items and status.",
11207
+ " - `brainstorm`: emphasize Open Questions and Future Features.",
11208
+ " Only mark decisions as Firm when the transcript is unambiguous.",
11209
+ " - `external`: default attendees outside the team into People of",
11210
+ " Interest; default organizations mentioned into Companies of",
11211
+ " Interest (signal threshold still applies); capture customer",
11212
+ " pain points as candidate BR (not FR).",
11213
+ "",
11214
+ "5. **Create downstream phase issues** using `gh issue create`:",
11215
+ " - Always create a `meeting:notes` issue (blocked on this extract issue)",
11216
+ " - If requirements OR decisions/ADRs identified, create a `meeting:draft` issue",
11217
+ " (blocked on the notes issue). For kind `standup`, skip the",
11218
+ " `meeting:draft` issue \u2014 standup meetings almost never produce",
11219
+ " formal drafts.",
11220
+ " - Always create a `meeting:link` issue \u2014 blocked on the draft issue if one",
11221
+ " was created, otherwise blocked on the notes issue",
11222
+ "",
11223
+ "6. Commit, push, and close the extract issue.",
11224
+ "",
11225
+ "---",
11226
+ "",
11227
+ "## Phase 2: Notes (`meeting:notes`)",
11228
+ "",
11229
+ "**Goal:** Transform the extraction into structured meeting notes.",
11230
+ "",
11231
+ "### Steps",
11232
+ "",
11233
+ "1. Read the extraction file referenced in the issue body (output of Phase 1).",
11234
+ "2. Write structured meeting notes with these sections:",
11235
+ " - Meeting metadata (title, date, attendees)",
11236
+ " - Agenda / topics covered",
11237
+ " - Key Discussion Points (organized by topic)",
11238
+ " - Decisions (numbered, with rationale)",
11239
+ " - Action Items (table: who, what, when)",
11240
+ " - Open Questions",
11241
+ " - Follow-up items",
11242
+ "3. Commit, push, and close the notes issue.",
11243
+ "",
11244
+ "---",
11245
+ "",
11246
+ "## Phase 3: Draft (`meeting:draft`)",
11247
+ "",
11248
+ "**Goal:** Draft proposals for requirements, ADRs, and product/strategy updates.",
11249
+ "",
11250
+ "This phase only exists if the extraction identified requirements, architectural",
11251
+ "decisions, or strategy changes. If this issue exists, execute it.",
11252
+ "",
11253
+ "### Steps",
11254
+ "",
11255
+ "1. Read the extraction file from Phase 1.",
11256
+ "2. Check existing requirement registries and ADR registries for duplicates.",
11257
+ "3. Draft requirement proposals with:",
11258
+ " - Category (FR, BR, NFR, etc.)",
11259
+ " - Summary (2-3 sentences)",
11260
+ " - Draft acceptance criteria",
11261
+ " - Related existing requirements",
11262
+ "4. Draft ADR proposals for technology decisions with:",
11263
+ " - Context and problem statement",
11264
+ " - Options discussed",
11265
+ " - Stated preferences or decisions",
11266
+ " - Status: Proposed or Decided",
11267
+ "5. Summarize product/strategy document updates needed.",
11268
+ "6. Commit, push, and close the draft issue.",
11269
+ "",
11270
+ "---",
11271
+ "",
11272
+ "## Phase 4: Link (`meeting:link`)",
11273
+ "",
11274
+ "**Goal:** Create GitHub issues for follow-up work, cross-reference the",
11275
+ "meeting into existing documentation, and complete bi-directional traceability.",
11276
+ "",
11277
+ "### Steps",
11278
+ "",
11279
+ "1. Read the drafts from Phase 3 (if they exist) and the extraction from Phase 1.",
11280
+ " Re-read the extraction frontmatter for `meeting_kind` and `areas`,",
11281
+ " then build the area-gate: the set of `docRoot` sub-folders that",
11282
+ " are in scope for direct edits on this meeting. Apply the rules in",
11283
+ " the **Areas filtering** section above.",
11284
+ "2. Create requirement issues using `gh issue create` with appropriate labels.",
11285
+ " Include a `## Traceability` section in each issue body linking back to",
11286
+ " the source meeting and extraction file. Issue creation is **not**",
11287
+ " gated by areas.",
11288
+ "3. Create ADR issues if technology decisions need formal records.",
11289
+ " Include a `## Traceability` section in each. Not gated by areas.",
11290
+ "4. Create action item issues for tasks that are not document-related.",
11291
+ " Include a `## Traceability` section in each. Not gated by areas.",
11292
+ "5. Cross-reference the meeting in any existing documents that were",
11293
+ " discussed **and that live under an in-scope `docRoot`**. For",
11294
+ " discussed documents under an out-of-scope `docRoot`, open a",
11295
+ " follow-up `type:docs` issue instead of editing the doc directly.",
11296
+ "6. Apply direct product/strategy doc updates for items flagged as",
11297
+ " **Firm** confidence. When the project declares `meetingAreas`,",
11298
+ " gate these edits: only apply them when the target path lies",
11299
+ " under an in-scope `docRoot`, and for Firm items targeting an",
11300
+ " out-of-scope `docRoot` open a follow-up `type:docs` issue",
11301
+ " noting the area and the target path. When the project has not",
11302
+ " declared any `meetingAreas`, apply Firm updates unrestricted",
11303
+ " (the degenerate case above). For the kind `planning`, also",
11304
+ " update the active sprint-plan doc directly (when the",
11305
+ " sprint-plan doc's area is in-scope, or always when no",
11306
+ " `meetingAreas` are declared); for the kind `review`, mark",
11307
+ " completed tasks in that same doc.",
11308
+ "7. **Update the extraction file** with a `## Downstream Artifacts` section",
11309
+ " listing every issue and document created from this meeting. Note",
11310
+ " any items that were deferred to follow-up issues because of the",
11311
+ " area gate.",
11312
+ "8. **Update the meeting notes** with a `## Related Issues` section listing",
11313
+ " all issues created from this meeting.",
11314
+ "9. Comment on the parent extract issue with a summary linking to all",
11315
+ " created artifacts.",
11316
+ "10. Commit and push (if any file changes were made), then close the link issue.",
11317
+ "",
11318
+ "---",
11319
+ "",
11320
+ "## GitHub Integration",
11321
+ "",
11322
+ "- Use `gh` CLI for all GitHub operations",
11323
+ "- Apply the appropriate `meeting:*` phase label to each phase issue",
11324
+ "- Use `type:docs` for documentation-producing issues, `type:chore` for",
11325
+ " maintenance/organizational tasks",
11326
+ "- Use `status:` labels to track progress (`status:ready`, `status:in-progress`, `status:done`)",
11327
+ "- Reference the source meeting transcript in all created issues",
11328
+ "- Link phase issues with `Depends on: #N` to enforce ordering",
11329
+ "",
11330
+ "## When to Shorten the Pipeline",
11331
+ "",
11332
+ "- **Short meetings** (<30 min, <2000 words): combine extract + notes into one session",
11333
+ "- **No requirements or decisions**: Phase 1 skips creating the `meeting:draft` issue",
11334
+ "- **Only action items**: extract + notes + link (3 phase issues)"
11335
+ ].join("\n")
11336
+ };
11337
+ }
11283
11338
  var processMeetingSkill = {
11284
11339
  name: "process-meeting",
11285
11340
  description: "Process a meeting transcript through the 4-phase meeting analysis pipeline",
@@ -11323,64 +11378,67 @@ var processMeetingSkill = {
11323
11378
  " to pick up (notes, draft, link)"
11324
11379
  ].join("\n")
11325
11380
  };
11326
- var meetingAnalysisBundle = {
11327
- name: "meeting-analysis",
11328
- description: "Meeting transcript processing workflow with 4-phase pipeline (extract, notes, draft, link)",
11329
- appliesWhen: () => true,
11330
- rules: [
11331
- {
11332
- name: "meeting-processing-workflow",
11333
- description: "Describes the 4-phase meeting processing pipeline, extraction taxonomy, and labeling conventions",
11334
- scope: AGENT_RULE_SCOPE.ALWAYS,
11335
- content: [
11336
- "# Meeting Processing Workflow",
11337
- "",
11338
- "Use `/process-meeting <path>` to process a meeting transcript through a",
11339
- "4-phase pipeline (extract \u2192 notes \u2192 draft \u2192 link). Each phase runs as a",
11340
- "separate agent session tracked by a GitHub issue with a `meeting:*` label.",
11341
- "See the `meeting-analyst` agent definition for full workflow details.",
11342
- "",
11343
- "Meeting notes may declare a `meeting_type` (one of the project's",
11344
- "recognized types) and an `areas: [...]` list. The `meeting_type`",
11345
- "resolves to a generic kind \u2014 `planning` / `review` / `brainstorm` /",
11346
- "`standup` / `external` / `other` \u2014 that drives type-specific",
11347
- "extraction rules in phases 1\u20132. The `areas` list scopes phase-4",
11348
- "direct edits to the doc-root sub-folders declared in the project's",
11349
- "**Area \u2192 doc-root mapping** table. When both fields are absent",
11350
- "the pipeline falls back to the default workflow (kind `other`,",
11351
- "no area gating)."
11352
- ].join("\n"),
11353
- platforms: {
11354
- cursor: { exclude: true }
11381
+ function buildMeetingAnalysisBundle(tier = AGENT_MODEL.BALANCED) {
11382
+ return {
11383
+ name: "meeting-analysis",
11384
+ description: "Meeting transcript processing workflow with 4-phase pipeline (extract, notes, draft, link)",
11385
+ appliesWhen: () => true,
11386
+ rules: [
11387
+ {
11388
+ name: "meeting-processing-workflow",
11389
+ description: "Describes the 4-phase meeting processing pipeline, extraction taxonomy, and labeling conventions",
11390
+ scope: AGENT_RULE_SCOPE.ALWAYS,
11391
+ content: [
11392
+ "# Meeting Processing Workflow",
11393
+ "",
11394
+ "Use `/process-meeting <path>` to process a meeting transcript through a",
11395
+ "4-phase pipeline (extract \u2192 notes \u2192 draft \u2192 link). Each phase runs as a",
11396
+ "separate agent session tracked by a GitHub issue with a `meeting:*` label.",
11397
+ "See the `meeting-analyst` agent definition for full workflow details.",
11398
+ "",
11399
+ "Meeting notes may declare a `meeting_type` (one of the project's",
11400
+ "recognized types) and an `areas: [...]` list. The `meeting_type`",
11401
+ "resolves to a generic kind \u2014 `planning` / `review` / `brainstorm` /",
11402
+ "`standup` / `external` / `other` \u2014 that drives type-specific",
11403
+ "extraction rules in phases 1\u20132. The `areas` list scopes phase-4",
11404
+ "direct edits to the doc-root sub-folders declared in the project's",
11405
+ "**Area \u2192 doc-root mapping** table. When both fields are absent",
11406
+ "the pipeline falls back to the default workflow (kind `other`,",
11407
+ "no area gating)."
11408
+ ].join("\n"),
11409
+ platforms: {
11410
+ cursor: { exclude: true }
11411
+ },
11412
+ tags: ["workflow"]
11413
+ }
11414
+ ],
11415
+ skills: [processMeetingSkill],
11416
+ subAgents: [buildMeetingAnalystSubAgent(tier)],
11417
+ labels: [
11418
+ {
11419
+ name: "meeting:extract",
11420
+ color: "C5DEF5",
11421
+ description: "Phase 1: raw extraction from a meeting transcript"
11355
11422
  },
11356
- tags: ["workflow"]
11357
- }
11358
- ],
11359
- skills: [processMeetingSkill],
11360
- subAgents: [meetingAnalystSubAgent],
11361
- labels: [
11362
- {
11363
- name: "meeting:extract",
11364
- color: "C5DEF5",
11365
- description: "Phase 1: raw extraction from a meeting transcript"
11366
- },
11367
- {
11368
- name: "meeting:notes",
11369
- color: "BFDADC",
11370
- description: "Phase 2: curated notes derived from an extraction"
11371
- },
11372
- {
11373
- name: "meeting:draft",
11374
- color: "D4C5F9",
11375
- description: "Phase 3: draft follow-up issues proposed from notes"
11376
- },
11377
- {
11378
- name: "meeting:link",
11379
- color: "FEF2C0",
11380
- description: "Phase 4: linking/reconciling drafted issues with existing work"
11381
- }
11382
- ]
11383
- };
11423
+ {
11424
+ name: "meeting:notes",
11425
+ color: "BFDADC",
11426
+ description: "Phase 2: curated notes derived from an extraction"
11427
+ },
11428
+ {
11429
+ name: "meeting:draft",
11430
+ color: "D4C5F9",
11431
+ description: "Phase 3: draft follow-up issues proposed from notes"
11432
+ },
11433
+ {
11434
+ name: "meeting:link",
11435
+ color: "FEF2C0",
11436
+ description: "Phase 4: linking/reconciling drafted issues with existing work"
11437
+ }
11438
+ ]
11439
+ };
11440
+ }
11441
+ var meetingAnalysisBundle = buildMeetingAnalysisBundle();
11384
11442
 
11385
11443
  // src/agent/bundles/run-ratio.ts
11386
11444
  var DEFAULT_DISPATCH_TO_HOUSEKEEPING_RATIO = 4;
@@ -13455,11 +13513,10 @@ function shellSingleQuote(value) {
13455
13513
  }
13456
13514
 
13457
13515
  // src/agent/bundles/orchestrator.ts
13458
- function buildCheckBlockedScript(tiers, scopeGate, runRatio) {
13516
+ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
13459
13517
  const tierCase = renderAgentTierCaseStatement(tiers);
13460
13518
  const scopeHelper = renderScopeGateShellHelpers(scopeGate);
13461
13519
  const scopeHelperIndented = scopeHelper.split("\n").map((line) => line.length > 0 ? line : "").join("\n");
13462
- const runRatioHelper = renderRunRatioShellHelpers(runRatio);
13463
13520
  return [
13464
13521
  "#!/usr/bin/env bash",
13465
13522
  "# check-blocked.sh \u2014 Token-efficient issue triage for agent loops.",
@@ -13471,9 +13528,9 @@ function buildCheckBlockedScript(tiers, scopeGate, runRatio) {
13471
13528
  "# .claude/procedures/check-blocked.sh eligible",
13472
13529
  "# .claude/procedures/check-blocked.sh stale",
13473
13530
  "# .claude/procedures/check-blocked.sh orphaned",
13531
+ "# .claude/procedures/check-blocked.sh maintenance",
13474
13532
  "# .claude/procedures/check-blocked.sh prs",
13475
13533
  "# .claude/procedures/check-blocked.sh scope <issue-number>",
13476
- "# .claude/procedures/check-blocked.sh tick",
13477
13534
  "",
13478
13535
  "set -uo pipefail",
13479
13536
  "",
@@ -13487,9 +13544,6 @@ function buildCheckBlockedScript(tiers, scopeGate, runRatio) {
13487
13544
  `SCOPE_AC_MEDIUM_MAX=${scopeGate.acceptanceCriteria.mediumMax}`,
13488
13545
  `SCOPE_SOURCES_SMALL_MAX=${scopeGate.sources.smallMax}`,
13489
13546
  `SCOPE_SOURCES_MEDIUM_MAX=${scopeGate.sources.mediumMax}`,
13490
- `RUN_RATIO_ENABLED=${runRatio.enabled ? "1" : "0"}`,
13491
- `RUN_RATIO_DISPATCH_PER_HOUSEKEEPING=${runRatio.ratio}`,
13492
- `ORCHESTRATOR_STATE_FILE="${runRatio.stateFilePath}"`,
13493
13547
  "",
13494
13548
  "# \u2500\u2500 helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
13495
13549
  "",
@@ -13520,8 +13574,6 @@ function buildCheckBlockedScript(tiers, scopeGate, runRatio) {
13520
13574
  "",
13521
13575
  scopeHelperIndented,
13522
13576
  "",
13523
- runRatioHelper,
13524
- "",
13525
13577
  "# \u2500\u2500 subcommands \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
13526
13578
  "",
13527
13579
  "cmd_unblock() {",
@@ -13532,7 +13584,7 @@ function buildCheckBlockedScript(tiers, scopeGate, runRatio) {
13532
13584
  " local count",
13533
13585
  ` count=$(echo "$issues" | jq 'length')`,
13534
13586
  ' if [[ "$count" -eq 0 ]]; then',
13535
- ' echo "NO_BLOCKED_ISSUES"',
13587
+ ' echo "TRIAGE_DONE unblocked=0 still_blocked=0"',
13536
13588
  " return 0",
13537
13589
  " fi",
13538
13590
  "",
@@ -13543,18 +13595,36 @@ function buildCheckBlockedScript(tiers, scopeGate, runRatio) {
13543
13595
  ' "\\(.number)\\t\\($dep_line)"',
13544
13596
  " ')",
13545
13597
  "",
13598
+ " # Counters for the summary line. The orchestrator reads only",
13599
+ " # TRIAGE_DONE; the per-issue lines below are for log visibility,",
13600
+ " # mirroring unblock-dependents.sh.",
13601
+ " local unblocked_count=0",
13602
+ " local still_blocked_count=0",
13603
+ "",
13546
13604
  " while IFS=$'\\t' read -r num dep_line; do",
13547
13605
  ' [[ -z "$num" ]] && continue',
13548
13606
  "",
13549
13607
  ' if [[ -z "$dep_line" ]]; then',
13550
13608
  ' echo "BLOCKED #${num} \u2014 no Depends on field found"',
13609
+ " still_blocked_count=$((still_blocked_count + 1))",
13551
13610
  " continue",
13552
13611
  " fi",
13553
13612
  "",
13554
13613
  " local deps",
13555
13614
  ' deps=$(parse_deps "$dep_line")',
13556
13615
  ' if [[ -z "${deps// /}" ]]; then',
13557
- ' echo "UNBLOCK #${num} \u2014 no dependencies"',
13616
+ " # Empty dependency list \u2014 treat as eligible for unblocking.",
13617
+ ' if gh issue edit "$num" \\',
13618
+ ' --remove-label "status:blocked" \\',
13619
+ ' --add-label "status:ready" >/dev/null 2>&1; then',
13620
+ ' gh issue comment "$num" \\',
13621
+ ' --body "Dependencies resolved \u2014 unblocking." >/dev/null 2>&1 || true',
13622
+ ' echo "UNBLOCKED #${num} \u2014 no dependencies"',
13623
+ " unblocked_count=$((unblocked_count + 1))",
13624
+ " else",
13625
+ ' echo "UNBLOCK_FAILED #${num} \u2014 label flip failed (no dependencies)"',
13626
+ " still_blocked_count=$((still_blocked_count + 1))",
13627
+ " fi",
13558
13628
  " continue",
13559
13629
  " fi",
13560
13630
  "",
@@ -13571,11 +13641,30 @@ function buildCheckBlockedScript(tiers, scopeGate, runRatio) {
13571
13641
  " done",
13572
13642
  "",
13573
13643
  " if $all_closed; then",
13574
- ' echo "UNBLOCK #${num} \u2014 all deps closed (${closed_deps% })"',
13644
+ " # The label flip is the load-bearing edit; if it fails we must",
13645
+ " # report the failure so the orchestrator does not double-count.",
13646
+ " # The comment is best-effort (its absence is recoverable on the",
13647
+ " # next sweep), so a failed `gh issue comment` does not block the",
13648
+ " # unblock from being recorded as successful.",
13649
+ ' if gh issue edit "$num" \\',
13650
+ ' --remove-label "status:blocked" \\',
13651
+ ' --add-label "status:ready" >/dev/null 2>&1; then',
13652
+ ' gh issue comment "$num" \\',
13653
+ ' --body "Dependencies resolved \u2014 unblocking." >/dev/null 2>&1 || true',
13654
+ ' echo "UNBLOCKED #${num} \u2014 all deps closed (${closed_deps% })"',
13655
+ " unblocked_count=$((unblocked_count + 1))",
13656
+ " else",
13657
+ ' echo "UNBLOCK_FAILED #${num} \u2014 label flip failed (all deps closed: ${closed_deps% })"',
13658
+ " still_blocked_count=$((still_blocked_count + 1))",
13659
+ " fi",
13575
13660
  " else",
13576
- ' echo "BLOCKED #${num} \u2014 waiting on ${open_deps% }"',
13661
+ ' echo "STILL_BLOCKED #${num} \u2014 waiting on ${open_deps% }"',
13662
+ " still_blocked_count=$((still_blocked_count + 1))",
13577
13663
  " fi",
13578
13664
  ' done <<< "$issue_data"',
13665
+ "",
13666
+ " # Single summary line consumed by the orchestrator.",
13667
+ ' echo "TRIAGE_DONE unblocked=${unblocked_count} still_blocked=${still_blocked_count}"',
13579
13668
  "}",
13580
13669
  "",
13581
13670
  "cmd_eligible() {",
@@ -13767,6 +13856,101 @@ function buildCheckBlockedScript(tiers, scopeGate, runRatio) {
13767
13856
  " fi",
13768
13857
  "}",
13769
13858
  "",
13859
+ "cmd_maintenance() {",
13860
+ " # Phase D maintenance sweep: flag stale issues with",
13861
+ " # `status:needs-attention`, count orphan branches/PRs (no",
13862
+ " # auto-deletion), and emit a single MAINTENANCE_DONE summary",
13863
+ " # line so the orchestrator never iterates raw stale/orphan",
13864
+ " # output. Mirrors the discipline of cmd_unblock \u2014 per-issue",
13865
+ " # informational lines (FLAGGED #N, ORPHAN_BRANCH \u2026) survive",
13866
+ " # for log visibility but are not load-bearing for the",
13867
+ " # orchestrator.",
13868
+ " #",
13869
+ " # SAFETY: this subcommand NEVER auto-resets stale issues to",
13870
+ " # `status:ready`. It only adds `status:needs-attention`; the",
13871
+ " # existing `status:in-progress` or `status:blocked` label",
13872
+ " # stays in place so partial implementation work on a branch",
13873
+ " # remains visible to humans.",
13874
+ "",
13875
+ " local flagged_stale_count=0",
13876
+ " local flagged_blocked_count=0",
13877
+ " local orphan_branches_count=0",
13878
+ " local orphan_prs_count=0",
13879
+ "",
13880
+ " # \u2500\u2500 stale detection + flagging \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
13881
+ " local stale_output",
13882
+ " stale_output=$(cmd_stale)",
13883
+ "",
13884
+ " while IFS= read -r line; do",
13885
+ ' [[ -z "$line" ]] && continue',
13886
+ ' case "$line" in',
13887
+ " 'STALE #'*)",
13888
+ " # Extract the issue number after the '#' and before the ' '.",
13889
+ " local num=${line#STALE #}",
13890
+ " num=${num%% *}",
13891
+ ' if gh issue edit "$num" --add-label "status:needs-attention" >/dev/null 2>&1; then',
13892
+ ' gh issue comment "$num" \\',
13893
+ ' --body "Flagged: in-progress for >3 days with no activity." >/dev/null 2>&1 || true',
13894
+ ' echo "FLAGGED_STALE #${num} \u2014 added status:needs-attention"',
13895
+ " flagged_stale_count=$((flagged_stale_count + 1))",
13896
+ " else",
13897
+ ' echo "FLAG_FAILED #${num} \u2014 could not add status:needs-attention (stale)"',
13898
+ " fi",
13899
+ " # Surface the original informational line for log visibility.",
13900
+ ' echo "$line"',
13901
+ " ;;",
13902
+ " 'STALE_BLOCKED #'*)",
13903
+ " local num=${line#STALE_BLOCKED #}",
13904
+ " num=${num%% *}",
13905
+ ' if gh issue edit "$num" --add-label "status:needs-attention" >/dev/null 2>&1; then',
13906
+ ' gh issue comment "$num" \\',
13907
+ ' --body "Flagged: blocked for >7 days \u2014 may need human intervention." >/dev/null 2>&1 || true',
13908
+ ' echo "FLAGGED_BLOCKED #${num} \u2014 added status:needs-attention"',
13909
+ " flagged_blocked_count=$((flagged_blocked_count + 1))",
13910
+ " else",
13911
+ ' echo "FLAG_FAILED #${num} \u2014 could not add status:needs-attention (blocked)"',
13912
+ " fi",
13913
+ ' echo "$line"',
13914
+ " ;;",
13915
+ " 'NO_STALE_ISSUES')",
13916
+ " # Surface the no-op marker for log visibility; counters",
13917
+ " # stay at zero.",
13918
+ ' echo "$line"',
13919
+ " ;;",
13920
+ " esac",
13921
+ ' done <<< "$stale_output"',
13922
+ "",
13923
+ " # \u2500\u2500 orphan detection (count only \u2014 no auto-deletion) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
13924
+ " local orphan_output",
13925
+ " orphan_output=$(cmd_orphaned)",
13926
+ "",
13927
+ " while IFS= read -r line; do",
13928
+ ' [[ -z "$line" ]] && continue',
13929
+ ' case "$line" in',
13930
+ " 'ORPHAN_BRANCH '*)",
13931
+ " orphan_branches_count=$((orphan_branches_count + 1))",
13932
+ ' echo "$line"',
13933
+ " ;;",
13934
+ " 'ORPHAN_PR '*)",
13935
+ " orphan_prs_count=$((orphan_prs_count + 1))",
13936
+ ' echo "$line"',
13937
+ " ;;",
13938
+ " 'NO_ORPHANED_RESOURCES')",
13939
+ ' echo "$line"',
13940
+ " ;;",
13941
+ " esac",
13942
+ ' done <<< "$orphan_output"',
13943
+ "",
13944
+ " # \u2500\u2500 needs-attention total \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
13945
+ " local needs_attention_total",
13946
+ ' needs_attention_total=$(gh issue list --label "status:needs-attention" --state open \\',
13947
+ " --json number --limit 100 2>/dev/null | jq 'length' 2>/dev/null || echo 0)",
13948
+ " needs_attention_total=${needs_attention_total:-0}",
13949
+ "",
13950
+ " # Single summary line consumed by the orchestrator.",
13951
+ ' echo "MAINTENANCE_DONE flagged_stale=${flagged_stale_count} flagged_blocked=${flagged_blocked_count} orphan_branches=${orphan_branches_count} orphan_prs=${orphan_prs_count} needs_attention_total=${needs_attention_total}"',
13952
+ "}",
13953
+ "",
13770
13954
  "cmd_prs() {",
13771
13955
  " local prs",
13772
13956
  " prs=$(gh pr list --state open --json number,title,isDraft,headRefName,labels,body \\",
@@ -13905,31 +14089,18 @@ function buildCheckBlockedScript(tiers, scopeGate, runRatio) {
13905
14089
  " fi",
13906
14090
  "}",
13907
14091
  "",
13908
- "cmd_tick() {",
13909
- " # DEPRECATED: the orchestrator no longer maintains a dispatch /",
13910
- " # housekeeping run-counter \u2014 every invocation runs the full",
13911
- " # end-to-end cycle. This subcommand is retained as a no-op for one",
13912
- " # release so any consumer mid-flight does not error out, then will",
13913
- " # be removed entirely. Existing state files at",
13914
- ' # "$ORCHESTRATOR_STATE_FILE" can be left alone (they will become',
13915
- " # orphaned).",
13916
- ' echo "tick: deprecated no-op (orchestrator runs a single linear cycle every invocation)" >&2',
13917
- ' echo "run=0 type=dispatch (deprecated)"',
13918
- " return 0",
13919
- "}",
13920
- "",
13921
14092
  "# \u2500\u2500 main \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
13922
14093
  "",
13923
14094
  'case "${1:-help}" in',
13924
- ' unblock) shift; cmd_unblock "$@" ;;',
13925
- ' eligible) shift; cmd_eligible "$@" ;;',
13926
- ' stale) shift; cmd_stale "$@" ;;',
13927
- ' orphaned) shift; cmd_orphaned "$@" ;;',
13928
- ' prs) shift; cmd_prs "$@" ;;',
13929
- ' scope) shift; cmd_scope "$@" ;;',
13930
- ' tick) shift; cmd_tick "$@" ;;',
14095
+ ' unblock) shift; cmd_unblock "$@" ;;',
14096
+ ' eligible) shift; cmd_eligible "$@" ;;',
14097
+ ' stale) shift; cmd_stale "$@" ;;',
14098
+ ' orphaned) shift; cmd_orphaned "$@" ;;',
14099
+ ' maintenance) shift; cmd_maintenance "$@" ;;',
14100
+ ' prs) shift; cmd_prs "$@" ;;',
14101
+ ' scope) shift; cmd_scope "$@" ;;',
13931
14102
  " help|*)",
13932
- ' echo "Usage: check-blocked.sh <unblock|eligible|stale|orphaned|prs|scope|tick>"',
14103
+ ' echo "Usage: check-blocked.sh <unblock|eligible|stale|orphaned|maintenance|prs|scope>"',
13933
14104
  " exit 1",
13934
14105
  " ;;",
13935
14106
  "esac"
@@ -13938,7 +14109,7 @@ function buildCheckBlockedScript(tiers, scopeGate, runRatio) {
13938
14109
  function buildCheckBlockedProcedure(tiers, scopeGate = resolveScopeGate(), runRatio = resolveRunRatio()) {
13939
14110
  return {
13940
14111
  name: "check-blocked.sh",
13941
- description: "Token-efficient issue triage script with subcommands: eligible, unblock, stale, orphaned, prs, scope, and (deprecated) tick. Sorts eligible issues by priority desc \u2192 funnel tier asc \u2192 issue number asc; the scope subcommand classifies a single issue against the scope-gate thresholds. The tick subcommand is a deprecation no-op retained for one release (the orchestrator no longer maintains a dispatch/housekeeping run counter).",
14112
+ description: "Token-efficient issue triage script with subcommands: eligible, unblock, stale, orphaned, maintenance, prs, scope. Sorts eligible issues by priority desc \u2192 funnel tier asc \u2192 issue number asc; the scope subcommand classifies a single issue against the scope-gate thresholds; the unblock subcommand applies the `status:blocked` \u2192 `status:ready` label flip itself, posts the canned unblock comment, and emits a single `TRIAGE_DONE unblocked=N still_blocked=M` summary line; the maintenance subcommand flags stale issues with `status:needs-attention` (never auto-resets to `status:ready`), counts orphan branches/PRs, and emits a single `MAINTENANCE_DONE flagged_stale=N flagged_blocked=M orphan_branches=A orphan_prs=B needs_attention_total=T` summary line.",
13942
14113
  content: buildCheckBlockedScript(tiers, scopeGate, runRatio)
13943
14114
  };
13944
14115
  }
@@ -13957,6 +14128,59 @@ function buildUnblockDependentsProcedure(unblockDependents = resolveUnblockDepen
13957
14128
  var unblockDependentsProcedure = buildUnblockDependentsProcedure(
13958
14129
  resolveUnblockDependents()
13959
14130
  );
14131
+ function buildPrSweepScript() {
14132
+ return [
14133
+ "#!/usr/bin/env bash",
14134
+ "# pr-sweep.sh \u2014 Token-efficient PR-eligibility filter for the",
14135
+ "# orchestrator's Phase B PR review sweep. Emits one summary line",
14136
+ "# per open PR; the orchestrator reads only the PR numbers and",
14137
+ "# dispatches `pr-reviewer` per eligible PR.",
14138
+ "#",
14139
+ "# Skip rules:",
14140
+ "# - isDraft = true \u2192 reason=draft",
14141
+ "# - review:human-required label \u2192 reason=human-required",
14142
+ "# - review:awaiting-human label \u2192 reason=awaiting-human",
14143
+ "#",
14144
+ "# Usage:",
14145
+ "# .claude/procedures/pr-sweep.sh",
14146
+ "",
14147
+ "set -uo pipefail",
14148
+ "",
14149
+ "prs=$(gh pr list --state open --json number,isDraft,labels \\",
14150
+ ' --limit 50 2>/dev/null || echo "[]")',
14151
+ "",
14152
+ `count=$(echo "$prs" | jq 'length')`,
14153
+ 'if [[ "$count" -eq 0 ]]; then',
14154
+ ' echo "NO_OPEN_PRS"',
14155
+ " exit 0",
14156
+ "fi",
14157
+ "",
14158
+ "# Emit one PR_ELIGIBLE / PR_SKIP line per open PR, sorted by PR",
14159
+ "# number ascending so the order is deterministic across runs.",
14160
+ `echo "$prs" | jq -r '`,
14161
+ " sort_by(.number) |",
14162
+ " .[] |",
14163
+ " (.labels | map(.name)) as $labels |",
14164
+ " if .isDraft then",
14165
+ ' "PR_SKIP #\\(.number) reason=draft"',
14166
+ ' elif ($labels | index("review:human-required")) then',
14167
+ ' "PR_SKIP #\\(.number) reason=human-required"',
14168
+ ' elif ($labels | index("review:awaiting-human")) then',
14169
+ ' "PR_SKIP #\\(.number) reason=awaiting-human"',
14170
+ " else",
14171
+ ' "PR_ELIGIBLE #\\(.number)"',
14172
+ " end",
14173
+ "'"
14174
+ ].join("\n");
14175
+ }
14176
+ function buildPrSweepProcedure() {
14177
+ return {
14178
+ name: "pr-sweep.sh",
14179
+ description: "Token-efficient PR-eligibility filter for the orchestrator's Phase B PR review sweep. Emits one `PR_ELIGIBLE #<n>` or `PR_SKIP #<n> reason=<draft|human-required|awaiting-human>` line per open PR; the orchestrator reads only the eligible numbers and dispatches `pr-reviewer` per eligible PR.",
14180
+ content: buildPrSweepScript()
14181
+ };
14182
+ }
14183
+ var prSweepProcedure = buildPrSweepProcedure();
13960
14184
  var orchestratorSubAgent = {
13961
14185
  name: "orchestrator",
13962
14186
  description: "End-to-end pipeline manager that runs one full cycle every invocation: triage \u2192 maintenance \u2192 queue scan \u2192 delegate the picked issue to the issue-worker \u2192 cleanup",
@@ -13967,13 +14191,16 @@ var orchestratorSubAgent = {
13967
14191
  "# Orchestrator Agent",
13968
14192
  "",
13969
14193
  "You are the pipeline orchestrator for the **{{repository.owner}}/{{repository.name}}** repository.",
13970
- "Each invocation runs **one full end-to-end cycle**. The cycle triages",
13971
- "blocked issues, runs maintenance scans, picks the next ready issue, and",
13972
- "**delegates implementation to the `issue-worker`** sub-agent in scheduled",
13973
- "mode. The orchestrator itself never implements code, creates branches, or",
13974
- "pushes commits \u2014 it routes work to other agents. Approved-PR merging is",
13975
- "owned by the `pr-reviewer` sub-agent (invoked via `/review-pr` /",
13976
- "`/review-prs`); the orchestrator does not run a merge sweep.",
14194
+ "Each invocation runs **one full end-to-end cycle**. The cycle pulls",
14195
+ "the default branch, sweeps eligible open PRs through the `pr-reviewer`",
14196
+ "sub-agent, triages blocked issues, runs maintenance scans, picks the",
14197
+ "next ready issue, and **delegates implementation to the `issue-worker`**",
14198
+ "sub-agent in scheduled mode. The orchestrator itself never implements",
14199
+ "code, creates branches, pushes commits, or merges PRs \u2014 it routes",
14200
+ "work to other agents. The merge decision is still owned by the",
14201
+ "`pr-reviewer` sub-agent; the orchestrator only chooses which PRs the",
14202
+ "reviewer should look at and dispatches one reviewer session per",
14203
+ "eligible PR.",
13977
14204
  "",
13978
14205
  "Run the phases below in order on every invocation. There is no",
13979
14206
  "dispatch/housekeeping fork \u2014 every phase runs every time.",
@@ -13981,16 +14208,20 @@ var orchestratorSubAgent = {
13981
14208
  "Phase ordering (each runs exactly once per invocation):",
13982
14209
  "",
13983
14210
  "1. **Phase A \u2014 Startup.** Pull the default branch.",
13984
- "2. **Phase C \u2014 Triage / Unblock.** Flip dependents that have all",
14211
+ "2. **Phase B \u2014 PR Review Sweep.** Read the procedure-script's",
14212
+ " eligibility summary and dispatch the `pr-reviewer` sub-agent",
14213
+ " in scheduled mode against each eligible open PR. Failures on",
14214
+ " one PR never stop the sweep.",
14215
+ "3. **Phase C \u2014 Triage / Unblock.** Flip dependents that have all",
13985
14216
  " their dependencies closed back to `status:ready`.",
13986
- "3. **Phase D \u2014 Maintenance.** Stale-issue and orphaned-resource scan",
14217
+ "4. **Phase D \u2014 Maintenance.** Stale-issue and orphaned-resource scan",
13987
14218
  " plus a needs-attention summary.",
13988
- "4. **Phase E \u2014 Queue Scan.** Pick the highest-priority `PICK` line",
14219
+ "5. **Phase E \u2014 Queue Scan.** Pick the highest-priority `PICK` line",
13989
14220
  " and run the scope gate. If the result is `large`, flag and stop.",
13990
- "5. **Phase G \u2014 Delegate Implementation.** Hand the picked issue off",
14221
+ "6. **Phase G \u2014 Delegate Implementation.** Hand the picked issue off",
13991
14222
  " to the `issue-worker` sub-agent in scheduled mode. The worker",
13992
14223
  " handles claim \u2192 branch \u2192 implement \u2192 commit \u2192 PR autonomously.",
13993
- "6. **Phase F \u2014 Cleanup.** Return to the default branch and log the",
14224
+ "7. **Phase F \u2014 Cleanup.** Return to the default branch and log the",
13994
14225
  " run summary.",
13995
14226
  "",
13996
14227
  "Phase letters are stable identifiers \u2014 letters skipped in the list",
@@ -14009,76 +14240,160 @@ var orchestratorSubAgent = {
14009
14240
  "git checkout main && git pull origin main",
14010
14241
  "```",
14011
14242
  "",
14012
- "## Phase C: Triage \u2014 Unblock",
14243
+ "## Phase B: PR Review Sweep",
14244
+ "",
14245
+ "Sweep every open PR through the `pr-reviewer` sub-agent before the",
14246
+ "queue scan so a single `/orchestrate` invocation merges ready PRs",
14247
+ "and dispatches new issue work in one pass.",
14013
14248
  "",
14014
- "Check for blocked issues whose dependencies have resolved:",
14249
+ "### Step 1 \u2014 read the eligibility summary",
14250
+ "",
14251
+ "Run the bundled `pr-sweep.sh` procedure and read **only** the",
14252
+ "summary lines it emits. Do **not** iterate raw `gh pr list` JSON",
14253
+ "yourself \u2014 the procedure is the single source of PR-eligibility",
14254
+ "metadata, mirroring the discipline of Phase E's bucketed queue scan.",
14015
14255
  "",
14016
14256
  "```bash",
14017
- ".claude/procedures/check-blocked.sh unblock",
14257
+ ".claude/procedures/pr-sweep.sh",
14018
14258
  "```",
14019
14259
  "",
14020
- "This phase is the **fallback safety net** for agent-driven",
14021
- "unblocking. Every agent that applies `status:done` already runs",
14022
- "`.claude/procedures/unblock-dependents.sh <n>` as part of that",
14023
- "transition, so in steady state Phase C should usually report",
14024
- "`NO_BLOCKED_ISSUES` or leave dependents untouched. Phase C still",
14025
- "runs on every cycle so issues that slipped through \u2014 human",
14026
- "closures, manual `status:done` edits, or crashes mid-sweep \u2014 get",
14027
- "picked up within one cycle. See the **Agent-driven",
14028
- "unblocking** section in `CLAUDE.md` for the per-agent contract.",
14260
+ "Each line follows one of these shapes:",
14029
14261
  "",
14030
- "For each `UNBLOCK #N` line:",
14031
- "```bash",
14032
- 'gh issue edit N --remove-label "status:blocked" --add-label "status:ready"',
14033
- 'gh issue comment N --body "Dependencies resolved \u2014 unblocking."',
14262
+ "```",
14263
+ "PR_ELIGIBLE #<n>",
14264
+ "PR_SKIP #<n> reason=draft",
14265
+ "PR_SKIP #<n> reason=human-required",
14266
+ "PR_SKIP #<n> reason=awaiting-human",
14034
14267
  "```",
14035
14268
  "",
14036
- "For `BLOCKED #N \u2014 no Depends on field found`: leave as-is (Phase D will",
14037
- "catch it if it's been blocked too long).",
14269
+ "Skip rules (PRs that never reach the reviewer in this sweep):",
14038
14270
  "",
14039
- "If output is `NO_BLOCKED_ISSUES`, continue to Phase D.",
14271
+ "- `isDraft: true` \u2192 `reason=draft` (the author has not yet asked for review)",
14272
+ "- Carries the `review:human-required` label \u2192 `reason=human-required`",
14273
+ " (operator override \u2014 the reviewer's auto-merge path is forbidden)",
14274
+ "- Carries the `review:awaiting-human` label \u2192 `reason=awaiting-human`",
14275
+ " (the reviewer already handed off; a human is the next actor)",
14040
14276
  "",
14041
- "## Phase D: Maintenance",
14277
+ "If the script emits `NO_OPEN_PRS`, log the empty result and skip",
14278
+ "directly to Phase C.",
14042
14279
  "",
14043
- "### D1: Stale Detection",
14280
+ "### Step 2 \u2014 dispatch the reviewer per eligible PR",
14044
14281
  "",
14045
- "```bash",
14046
- ".claude/procedures/check-blocked.sh stale",
14047
- "```",
14282
+ "For each `PR_ELIGIBLE #<n>` line in the order the procedure",
14283
+ "emitted them, invoke the `pr-reviewer` sub-agent **in scheduled",
14284
+ "mode** with the brief below. Substitute `<n>` with the PR number",
14285
+ "from the line.",
14286
+ "",
14287
+ "> `scheduled mode`: review PR #<n>. Run the full review pipeline",
14288
+ "> (eligibility, AC checklist, CI status, policy decision, sticky",
14289
+ "> reviewer-notes update) and either enable squash auto-merge or",
14290
+ "> apply `review:awaiting-human`. Do not pause for approval.",
14291
+ "> Return a one-line summary as the final line of your response in",
14292
+ "> the form `REVIEWER_DONE pr:#<n> outcome:<merged|awaiting-human|commented>`",
14293
+ "> on success or `REVIEWER_FAILED pr:#<n> reason:<short>` on failure.",
14294
+ "",
14295
+ "The orchestrator does **not** call `gh pr merge`, apply review",
14296
+ "labels, or post review comments directly \u2014 the reviewer's existing",
14297
+ "logic owns merge dispatch, AC verification, and the sticky",
14298
+ "`## Reviewer notes` comment. Phase B is a thin invoker, not an",
14299
+ "absorber.",
14300
+ "",
14301
+ "### Step 3 \u2014 failure semantics",
14302
+ "",
14303
+ "A single PR's reviewer failure (missing or malformed",
14304
+ "`REVIEWER_*` final line, sub-agent error, CI flake) **never stops",
14305
+ "the sweep**. Log the failure as",
14306
+ "`REVIEWER_FAILED pr:#<n> reason:<observed>` and continue to the",
14307
+ "next eligible PR. The next orchestrator invocation re-scans the",
14308
+ "PR list and re-dispatches the reviewer if the PR is still",
14309
+ "eligible.",
14310
+ "",
14311
+ "After every eligible PR has been processed (or zero eligible PRs",
14312
+ "were emitted), continue to Phase C regardless of outcomes.",
14313
+ "",
14314
+ "## Phase C: Triage \u2014 Unblock",
14315
+ "",
14316
+ "Run the bundled `check-blocked.sh unblock` procedure and read",
14317
+ "**only** the final `TRIAGE_DONE` summary line it emits. The",
14318
+ "procedure applies the label flips (`status:blocked` \u2192",
14319
+ "`status:ready`) and posts the canned",
14320
+ "`Dependencies resolved \u2014 unblocking.` comment itself, mirroring",
14321
+ "the discipline of Phase B (`pr-sweep.sh`) and Phase E",
14322
+ "(`check-blocked.sh eligible`) \u2014 the orchestrator never iterates",
14323
+ "raw `gh` responses, only the procedure's summary lines.",
14048
14324
  "",
14049
- "For each `STALE #N` line (in-progress >72h without activity):",
14050
14325
  "```bash",
14051
- 'gh issue edit N --add-label "status:needs-attention"',
14052
- 'gh issue comment N --body "Flagged: in-progress for >3 days with no activity."',
14326
+ ".claude/procedures/check-blocked.sh unblock",
14053
14327
  "```",
14054
14328
  "",
14055
- "For each `STALE_BLOCKED #N` line (blocked >168h):",
14056
- "```bash",
14057
- 'gh issue edit N --add-label "status:needs-attention"',
14058
- 'gh issue comment N --body "Flagged: blocked for >7 days \u2014 may need human intervention."',
14329
+ "The script emits one summary line in this shape:",
14330
+ "",
14331
+ "```",
14332
+ "TRIAGE_DONE unblocked=<N> still_blocked=<M>",
14059
14333
  "```",
14060
14334
  "",
14061
- "**Important:** Do NOT auto-reset stale issues to `status:ready` \u2014 partial",
14062
- "implementation work may exist on a branch.",
14335
+ "Per-issue informational lines (`UNBLOCKED #N`, `UNBLOCK_FAILED #N`,",
14336
+ "`STILL_BLOCKED #N`, `BLOCKED #N \u2014 no Depends on field found`) are",
14337
+ "emitted for log visibility but are **not** load-bearing for the",
14338
+ "orchestrator \u2014 partial failures (one bad `gh` call) do not abort",
14339
+ "the sweep, they are simply counted toward `still_blocked`.",
14340
+ "",
14341
+ "This phase is the **fallback safety net** for agent-driven",
14342
+ "unblocking. Every agent that applies `status:done` already runs",
14343
+ "`.claude/procedures/unblock-dependents.sh <n>` as part of that",
14344
+ "transition, so in steady state Phase C should usually report",
14345
+ "`TRIAGE_DONE unblocked=0 still_blocked=0` or leave dependents",
14346
+ "untouched. Phase C still runs on every cycle so issues that",
14347
+ "slipped through \u2014 human closures, manual `status:done` edits, or",
14348
+ "crashes mid-sweep \u2014 get picked up within one cycle. See the",
14349
+ "**Agent-driven unblocking** section in `CLAUDE.md` for the",
14350
+ "per-agent contract.",
14351
+ "",
14352
+ "Log the summary line and continue to Phase D regardless of",
14353
+ "outcomes \u2014 a non-zero `still_blocked` count is normal (issues",
14354
+ "with open dependencies stay blocked) and Phase D will surface",
14355
+ "any that have been blocked too long.",
14063
14356
  "",
14064
- "### D2: Orphaned Detection",
14357
+ "## Phase D: Maintenance",
14358
+ "",
14359
+ "Run the bundled `check-blocked.sh maintenance` procedure and read",
14360
+ "**only** the final `MAINTENANCE_DONE` summary line it emits. The",
14361
+ "procedure folds the stale-detection, orphan-detection, and",
14362
+ "needs-attention summary that earlier revisions split across D1,",
14363
+ "D2, and D3 into a single sweep \u2014 applying the",
14364
+ "`status:needs-attention` label and posting the canned flag comment",
14365
+ "for each stale / stale-blocked issue itself, mirroring the",
14366
+ "discipline of Phase B (`pr-sweep.sh`) and Phase C",
14367
+ "(`check-blocked.sh unblock`).",
14065
14368
  "",
14066
14369
  "```bash",
14067
- ".claude/procedures/check-blocked.sh orphaned",
14370
+ ".claude/procedures/check-blocked.sh maintenance",
14068
14371
  "```",
14069
14372
  "",
14070
- "Report any `ORPHAN_BRANCH` or `ORPHAN_PR` lines. These indicate branches",
14071
- "or PRs whose linked issues are closed or missing. Log them for visibility",
14072
- "but do not delete branches automatically.",
14073
- "",
14074
- "### D3: Needs-Attention Summary",
14373
+ "The script emits one summary line in this shape:",
14075
14374
  "",
14076
- "List all issues currently flagged:",
14077
- "```bash",
14078
- 'gh issue list --label "status:needs-attention" --state open --json number,title',
14375
+ "```",
14376
+ "MAINTENANCE_DONE flagged_stale=<N> flagged_blocked=<M> orphan_branches=<A> orphan_prs=<B> needs_attention_total=<T>",
14079
14377
  "```",
14080
14378
  "",
14081
- "Log the count and titles for operator visibility.",
14379
+ "Per-issue / per-orphan informational lines (`FLAGGED_STALE #N`,",
14380
+ "`FLAGGED_BLOCKED #N`, `STALE #N \u2014 \u2026`, `STALE_BLOCKED #N \u2014 \u2026`,",
14381
+ "`ORPHAN_BRANCH \u2026`, `ORPHAN_PR #N \u2014 \u2026`, `FLAG_FAILED #N \u2014 \u2026`) are",
14382
+ "emitted for log visibility but are **not** load-bearing for the",
14383
+ "orchestrator \u2014 partial failures (one bad `gh` call) do not abort",
14384
+ "the sweep, they are simply omitted from the `flagged_*` counters.",
14385
+ "",
14386
+ "**Important:** the script never auto-resets stale issues to",
14387
+ "`status:ready`. It only adds `status:needs-attention`; the",
14388
+ "existing `status:in-progress` or `status:blocked` label stays in",
14389
+ "place so partial implementation work on a branch remains visible",
14390
+ "to humans. Orphan branches and PRs are surfaced in the counters",
14391
+ "but are never deleted by the orchestrator \u2014 a human reviews the",
14392
+ "log lines and prunes orphans manually.",
14393
+ "",
14394
+ "Log the summary line and continue to Phase E regardless of",
14395
+ "outcomes \u2014 a non-zero `flagged_*` or `orphan_*` count is",
14396
+ "informational, not a failure.",
14082
14397
  "",
14083
14398
  "## Phase E: Queue Scan",
14084
14399
  "",
@@ -14238,27 +14553,30 @@ var orchestratorSubAgent = {
14238
14553
  "",
14239
14554
  "1. **Never implement code.** You triage, scan, and delegate \u2014 you do not code.",
14240
14555
  "2. **Never claim issues.** Do not add `status:in-progress` or create branches for issues. Phase G's `issue-worker` delegation is the only path that flips an issue's claim labels.",
14241
- "3. **Never merge PRs.** Approved-PR merging is owned by the `pr-reviewer` sub-agent (invoked via `/review-pr` / `/review-prs`). The orchestrator does not run a merge sweep, does not call `gh pr merge`, and does not enable auto-merge.",
14242
- "4. **Always use check-blocked.sh.** All triage queries go through the shell script for token efficiency.",
14243
- "5. **Follow CLAUDE.md conventions** for all git and gh operations.",
14244
- "6. **Priority order:** critical > high > medium > low > trivial, then **funnel tier asc** (lower tier wins ties), then FIFO by issue number.",
14245
- "7. **Never dispatch a `large` issue.** Always run the scope check",
14556
+ "3. **Always use procedure scripts.** Issue triage queries go through `.claude/procedures/check-blocked.sh`; PR-eligibility filtering goes through `.claude/procedures/pr-sweep.sh`. Never iterate raw `gh issue list` / `gh pr list` JSON in-context.",
14557
+ "4. **Follow CLAUDE.md conventions** for all git and gh operations.",
14558
+ "5. **Priority order:** critical > high > medium > low > trivial, then **funnel tier asc** (lower tier wins ties), then FIFO by issue number.",
14559
+ "6. **Never dispatch a `large` issue.** Always run the scope check",
14246
14560
  " on the top `PICK` line before reporting `NEXT_WORK_ITEM`. A",
14247
14561
  " `large` issue must be flagged with `status:needs-attention` and",
14248
14562
  " handed a decomposition proposal \u2014 never claimed, never branched,",
14249
14563
  " never delegated to the `issue-worker`.",
14250
- "8. **Sweep dependents whenever you apply `status:done`.** Every",
14564
+ "7. **Sweep dependents whenever you apply `status:done`.** Every",
14251
14565
  " `status:done` transition must be immediately followed by",
14252
14566
  " `.claude/procedures/unblock-dependents.sh <n>` so downstream",
14253
14567
  " `Depends on: #<n>` issues flip to `status:ready` without",
14254
14568
  " waiting for the next cycle. See the **Agent-driven unblocking**",
14255
14569
  " section in `CLAUDE.md` for the contract.",
14256
- "9. **Always invoke `issue-worker` in scheduled mode.** Phase G's",
14257
- " delegation prompt must contain the literal phrase",
14258
- " `scheduled mode` so the worker skips its interactive approval",
14259
- " pause and runs the implementation cycle autonomously. Without",
14260
- " the phrase the worker pauses for human approval and the",
14261
- " delegation stalls."
14570
+ "8. **Always invoke child sub-agents in scheduled mode.** Phase B's",
14571
+ " `pr-reviewer` dispatch and Phase G's `issue-worker` delegation",
14572
+ " prompts must each contain the literal phrase `scheduled mode`",
14573
+ " so the child sub-agent skips its interactive approval pause and",
14574
+ " runs autonomously. Without the phrase the child pauses for",
14575
+ " human approval and the delegation stalls.",
14576
+ "9. **A failed reviewer never stops the sweep.** Phase B continues",
14577
+ " to the next eligible PR after a `REVIEWER_FAILED` line and",
14578
+ " proceeds to Phase C regardless of how many PRs failed. Do not",
14579
+ " abort the cycle on a single PR's reviewer failure."
14262
14580
  ].join("\n")
14263
14581
  };
14264
14582
  var issueWorkerSubAgent = {
@@ -14733,9 +15051,9 @@ var ORCHESTRATOR_CONVENTIONS_PREAMBLE = [
14733
15051
  "",
14734
15052
  "When running the orchestrator agent (`.claude/agents/orchestrator.md`):",
14735
15053
  "",
14736
- "- The orchestrator runs **one full end-to-end cycle every invocation**: triage / unblock \u2192 maintenance \u2192 queue scan \u2192 delegate to `issue-worker` \u2192 cleanup",
14737
- "- The orchestrator **never** implements code, creates branches, pushes commits, **or merges PRs** \u2014 it triages issues, picks the next work item, and delegates implementation to other sub-agents. Approved-PR merging is owned by the `pr-reviewer` sub-agent (invoked via `/review-pr` / `/review-prs`).",
14738
- "- All triage queries use `.claude/procedures/check-blocked.sh` for token efficiency",
15054
+ "- The orchestrator runs **one full end-to-end cycle every invocation**: startup \u2192 PR review sweep \u2192 triage / unblock \u2192 maintenance \u2192 queue scan \u2192 delegate to `issue-worker` \u2192 cleanup",
15055
+ "- The orchestrator **never** implements code, creates branches, or pushes commits \u2014 it routes work to other sub-agents. The merge decision is still owned by the `pr-reviewer` sub-agent; Phase B only chooses which open PRs the reviewer should look at and dispatches one reviewer session per eligible PR (skipping drafts and any PR carrying `review:human-required` or `review:awaiting-human`).",
15056
+ "- All triage queries use `.claude/procedures/check-blocked.sh` for token efficiency. PR-eligibility filtering for Phase B uses `.claude/procedures/pr-sweep.sh`, which emits `PR_ELIGIBLE` / `PR_SKIP` lines so the orchestrator never iterates raw `gh pr list` JSON.",
14739
15057
  "- The queue scan reads only `priority:*` and `status:*` labels \u2014 type-routing (which typed agent handles a given `type:*` label) is the `issue-worker`'s concern, not the orchestrator's. The orchestrator's funnel-tier sort is a tie-breaker on `priority:*`, not a routing decision.",
14740
15058
  "- Priority order: critical > high > medium > low > trivial, then **funnel tier asc** (lower tier wins ties), then FIFO by issue number. Phase E's queue scan walks each priority bucket in turn (one `gh issue list` call per bucket, **capped at 50 issues per bucket**) and short-circuits on the first bucket whose survivors clear the `Depends on:` filter, so every higher-priority issue is visible even when the global ready backlog is much larger than 50.",
14741
15059
  "- Stale thresholds: 72h for in-progress, 168h for blocked",
@@ -14743,11 +15061,11 @@ var ORCHESTRATOR_CONVENTIONS_PREAMBLE = [
14743
15061
  "",
14744
15062
  "## Depth-0 invocation requirement",
14745
15063
  "",
14746
- 'The orchestrator agent **must run as the top-level (depth-0) session** when its Phase G needs to delegate work to the `issue-worker` sub-agent. The Claude Code harness forbids nested sub-agent spawning \u2014 *"Subagents cannot spawn other subagents. If your workflow requires nested delegation, use Skills or chain subagents from the main conversation."* (see <https://code.claude.com/docs/en/sub-agents>). If the orchestrator itself were spawned as a depth-1 sub-agent (e.g. via `Agent(subagent_type: "orchestrator")` from another session), the `Agent` tool needed to reach the `issue-worker` at depth-2 would not be available and Phase G would silently abort.',
15064
+ 'The orchestrator agent **must run as the top-level (depth-0) session** because both Phase B (PR review sweep) and Phase G (issue delegation) need to spawn child sub-agents. Phase B dispatches the `pr-reviewer` sub-agent once per eligible open PR; Phase G dispatches the `issue-worker` sub-agent against the picked issue. The Claude Code harness forbids nested sub-agent spawning \u2014 *"Subagents cannot spawn other subagents. If your workflow requires nested delegation, use Skills or chain subagents from the main conversation."* (see <https://code.claude.com/docs/en/sub-agents>). If the orchestrator itself were spawned as a depth-1 sub-agent (e.g. via `Agent(subagent_type: "orchestrator")` from another session), the `Agent` tool needed to reach `pr-reviewer` and `issue-worker` at depth-2 would not be available and both Phase B and Phase G would silently abort.',
14747
15065
  "",
14748
- 'Practical implication for scheduled-task wiring: the `worker-orchestrator` scheduled task\'s `SKILL.md` instructs the **scheduled-task session itself** to read `.claude/agents/orchestrator.md` and execute its phase pipeline in-session, then use the `Agent` tool to delegate the picked work item to `issue-worker` (depth-1). The scheduled task does **not** call `Agent(subagent_type: "orchestrator")` \u2014 that would put the orchestrator at depth-1 and break the chain.'
15066
+ 'Practical implication for scheduled-task wiring: the `worker-orchestrator` scheduled task\'s `SKILL.md` instructs the **scheduled-task session itself** to read `.claude/agents/orchestrator.md` and execute its phase pipeline in-session, then use the `Agent` tool to delegate to `pr-reviewer` (Phase B) and `issue-worker` (Phase G) at depth-1. The scheduled task does **not** call `Agent(subagent_type: "orchestrator")` \u2014 that would put the orchestrator at depth-1 and break both delegation chains.'
14749
15067
  ].join("\n");
14750
- function buildOrchestratorConventionsContent(tiers, scopeGate = resolveScopeGate(), runRatio = resolveRunRatio(), scheduledTasks = resolveScheduledTasks(), unblockDependents = resolveUnblockDependents(), excludeBundles = []) {
15068
+ function buildOrchestratorConventionsContent(tiers, scopeGate = resolveScopeGate(), _runRatio = resolveRunRatio(), scheduledTasks = resolveScheduledTasks(), unblockDependents = resolveUnblockDependents(), excludeBundles = []) {
14751
15069
  return [
14752
15070
  ORCHESTRATOR_CONVENTIONS_PREAMBLE,
14753
15071
  "",
@@ -14755,8 +15073,6 @@ function buildOrchestratorConventionsContent(tiers, scopeGate = resolveScopeGate
14755
15073
  "",
14756
15074
  renderScopeGateSection(scopeGate, excludeBundles),
14757
15075
  "",
14758
- renderRunRatioSection(runRatio),
14759
- "",
14760
15076
  renderScheduledTasksSection(scheduledTasks),
14761
15077
  "",
14762
15078
  renderUnblockDependentsSection(unblockDependents)
@@ -14793,6 +15109,74 @@ function resolveOrchestratorAssets(tierConfig, scopeGateConfig, runRatioConfig,
14793
15109
  )
14794
15110
  };
14795
15111
  }
15112
+ var orchestrateCommand = {
15113
+ name: "orchestrate",
15114
+ description: "Run one full orchestrator cycle: triage, queue scan, delegate to issue-worker.",
15115
+ content: [
15116
+ "# Orchestrate",
15117
+ "",
15118
+ "Invoke the `orchestrator` sub-agent and run **one full end-to-end",
15119
+ "cycle**: triage / unblock \u2192 maintenance \u2192 queue scan \u2192 delegate the",
15120
+ "picked issue to the `issue-worker` sub-agent \u2192 cleanup.",
15121
+ "",
15122
+ "Follow the contract in `.claude/agents/orchestrator.md`. Do **not**",
15123
+ "implement code, create branches, push commits, or merge PRs \u2014 those",
15124
+ "are the worker's and `pr-reviewer`'s jobs respectively.",
15125
+ "",
15126
+ "Run as a depth-0 (top-level) session so the orchestrator can spawn",
15127
+ "the `issue-worker` sub-agent at depth-1 \u2014 Claude Code forbids nested",
15128
+ "sub-agent dispatch.",
15129
+ ""
15130
+ ].join("\n")
15131
+ };
15132
+ var checkBlockedCommand = {
15133
+ name: "check-blocked",
15134
+ description: "Run .claude/procedures/check-blocked.sh and summarise the unblock candidates.",
15135
+ content: [
15136
+ "# Check Blocked",
15137
+ "",
15138
+ "Run the bundled triage helper and report what it found:",
15139
+ "",
15140
+ "```bash",
15141
+ 'bash .claude/procedures/check-blocked.sh "$@"',
15142
+ "```",
15143
+ "",
15144
+ "Pass any extra arguments through verbatim. The `unblock` subcommand",
15145
+ "walks every `status:blocked` issue, re-checks each `Depends on:`",
15146
+ "reference, applies the `status:blocked` \u2192 `status:ready` label flip",
15147
+ "itself, posts the canned `Dependencies resolved \u2014 unblocking.`",
15148
+ "comment, and emits a single",
15149
+ "`TRIAGE_DONE unblocked=<N> still_blocked=<M>` summary line. Per-issue",
15150
+ "informational lines (`UNBLOCKED`, `UNBLOCK_FAILED`, `STILL_BLOCKED`)",
15151
+ "are also emitted for log visibility.",
15152
+ "",
15153
+ "Summarise the output (the `TRIAGE_DONE` counts and the first few",
15154
+ "per-issue lines) \u2014 do **not** apply additional label flips yourself;",
15155
+ "the script already transitioned every eligible issue.",
15156
+ ""
15157
+ ].join("\n")
15158
+ };
15159
+ var scanCommand = {
15160
+ name: "scan",
15161
+ description: "Run the orchestrator queue scan only and report the next ready issue without dispatching.",
15162
+ content: [
15163
+ "# Scan",
15164
+ "",
15165
+ "Run the orchestrator's **queue scan only** (Phase E of the contract",
15166
+ "in `.claude/agents/orchestrator.md`) and print the next ready issue,",
15167
+ "the funnel-tier sort that put it on top, and the scope-gate result.",
15168
+ "",
15169
+ "Do **not** claim the issue, create a branch, or delegate to",
15170
+ "`issue-worker`. This command exists for proactive review of what",
15171
+ "the next `/orchestrate` invocation would pick up.",
15172
+ "",
15173
+ "Walk each `priority:*` bucket in turn (capped at 50 per bucket),",
15174
+ "filter `Depends on:` references, and short-circuit on the first",
15175
+ "bucket whose survivors clear the filter. Report the top candidate",
15176
+ "and the number of survivors per bucket.",
15177
+ ""
15178
+ ].join("\n")
15179
+ };
14796
15180
  var orchestratorBundle = {
14797
15181
  name: "orchestrator",
14798
15182
  description: "Pipeline orchestrator agent for issue triage, PR review, and queue management",
@@ -14818,7 +15202,12 @@ var orchestratorBundle = {
14818
15202
  }
14819
15203
  ],
14820
15204
  subAgents: [orchestratorSubAgent, issueWorkerSubAgent],
14821
- procedures: [checkBlockedProcedure, unblockDependentsProcedure],
15205
+ procedures: [
15206
+ checkBlockedProcedure,
15207
+ unblockDependentsProcedure,
15208
+ prSweepProcedure
15209
+ ],
15210
+ commands: [orchestrateCommand, checkBlockedCommand, scanCommand],
14822
15211
  claudePermissions: {
14823
15212
  allow: [
14824
15213
  // Allow executing the check-blocked.sh procedure
@@ -14828,11 +15217,11 @@ var orchestratorBundle = {
14828
15217
  };
14829
15218
 
14830
15219
  // src/agent/bundles/people-profile.ts
14831
- function buildPeopleProfileAnalystSubAgent(paths, issueDefaults) {
15220
+ function buildPeopleProfileAnalystSubAgent(paths, issueDefaults, tier) {
14832
15221
  return {
14833
15222
  name: "people-profile-analyst",
14834
15223
  description: "Researches an individual person (colleague, customer contact, vendor contact, partner contact, industry expert, or connector) from public sources and produces a structured markdown profile cross-linked to companies, software, and meeting notes, then enqueues downstream `company:research` and `software:research` issues for unprofiled companies and software products surfaced during profiling. Also handles maintenance refreshes on a configurable staleness cadence (`people:refresh`). One person per session, tracked by people:* GitHub issue labels.",
14835
- model: AGENT_MODEL.POWERFUL,
15224
+ model: tier,
14836
15225
  maxTurns: 80,
14837
15226
  platforms: { cursor: { exclude: true } },
14838
15227
  prompt: [
@@ -15662,7 +16051,7 @@ function buildRefreshPersonSkill(paths, issueDefaults) {
15662
16051
  ].join("\n")
15663
16052
  };
15664
16053
  }
15665
- function buildPeopleProfileBundle(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS) {
16054
+ function buildPeopleProfileBundle(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS, tier = AGENT_MODEL.BALANCED) {
15666
16055
  return {
15667
16056
  name: "people-profile",
15668
16057
  description: "People research and profiling pipeline: research, draft profile, followup, refresh. Enabled by default; domain-neutral; filesystem-durable between phases. Cross-references existing companies, software, and meeting notes, and Phase 3 (Followup) hands unprofiled, genuinely-relevant companies and software products off to the `company-profile` and `software-profile` bundles via `company:research` and `software:research` issues. Phase 4 (Refresh) re-verifies profiles on a configurable staleness cadence.",
@@ -15710,7 +16099,7 @@ function buildPeopleProfileBundle(paths = DEFAULT_AGENT_PATHS, issueDefaults = D
15710
16099
  buildProfilePersonSkill(paths, issueDefaults),
15711
16100
  buildRefreshPersonSkill(paths, issueDefaults)
15712
16101
  ],
15713
- subAgents: [buildPeopleProfileAnalystSubAgent(paths, issueDefaults)],
16102
+ subAgents: [buildPeopleProfileAnalystSubAgent(paths, issueDefaults, tier)],
15714
16103
  labels: [
15715
16104
  {
15716
16105
  name: "type:people-profile",
@@ -17285,7 +17674,19 @@ var prReviewBundle = {
17285
17674
  "to a PR's branch \u2014 it only reviews, decides, and orchestrates merge",
17286
17675
  "or comment. In loop mode, a failed review for one PR never stops",
17287
17676
  "the loop; the reviewer comments and moves on. See the `pr-reviewer`",
17288
- "agent definition for the full phase-by-phase contract.",
17677
+ "agent definition for the full phase-by-phase contract."
17678
+ ].join("\n"),
17679
+ platforms: {
17680
+ cursor: { exclude: true }
17681
+ },
17682
+ tags: ["workflow"]
17683
+ },
17684
+ {
17685
+ name: "pr-review-feedback-protocol",
17686
+ description: "Documents the human-in-the-loop feedback loop on PR review: reaction state machine, pushback resolution, fix-list comment format, sticky reviewer-notes comment, label glossary, and human-author opt-in flag.",
17687
+ scope: AGENT_RULE_SCOPE.ALWAYS,
17688
+ content: [
17689
+ "# PR Review Feedback Protocol",
17289
17690
  "",
17290
17691
  "## Human-in-the-Loop Feedback Protocol",
17291
17692
  "",
@@ -24069,11 +24470,11 @@ var slackBundle = {
24069
24470
  };
24070
24471
 
24071
24472
  // src/agent/bundles/software-profile.ts
24072
- function buildSoftwareProfileAnalystSubAgent(paths, issueDefaults) {
24473
+ function buildSoftwareProfileAnalystSubAgent(paths, issueDefaults, tier) {
24073
24474
  return {
24074
24475
  name: "software-profile-analyst",
24075
24476
  description: "Researches a software product (competitor, adjacent, incumbent, enabler, infrastructure, or ecosystem-tool) from public sources, produces a structured markdown profile, contributes rows to a shared feature matrix ranked against configurable segment-importance weights, maps features back to the BCM capability model and flags unmapped features, then enqueues downstream `bcm:outline`, `company:research`, and `people:research` issues for new sub-capabilities, the vendor company, and primary-attribution founders/leaders surfaced during profiling. One product per session, tracked by software:* GitHub issue labels.",
24076
- model: AGENT_MODEL.POWERFUL,
24477
+ model: tier,
24077
24478
  maxTurns: 80,
24078
24479
  platforms: { cursor: { exclude: true } },
24079
24480
  prompt: [
@@ -25008,7 +25409,7 @@ function buildMapSoftwareSkill(paths, issueDefaults) {
25008
25409
  ].join("\n")
25009
25410
  };
25010
25411
  }
25011
- function buildSoftwareProfileBundle(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS) {
25412
+ function buildSoftwareProfileBundle(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS, tier = AGENT_MODEL.BALANCED) {
25012
25413
  return {
25013
25414
  name: "software-profile",
25014
25415
  description: "Software research, profiling, feature-matrix, and capability-mapping pipeline: research, profile, matrix, map, followup. Enabled by default; domain-neutral; filesystem-durable between phases; ranks features against configurable segment-importance weights and maps features back to the BCM capability model. Phase 4 (Map) hands unmapped features off to the `bcm-writer` bundle via `bcm:outline` issues. Phase 5 (Followup) hands the vendor company and primary-attribution founders/leaders off to the `company-profile` and `people-profile` bundles via `company:research` and `people:research` issues.",
@@ -25065,7 +25466,9 @@ function buildSoftwareProfileBundle(paths = DEFAULT_AGENT_PATHS, issueDefaults =
25065
25466
  buildProfileSoftwareSkill(paths, issueDefaults),
25066
25467
  buildMapSoftwareSkill(paths, issueDefaults)
25067
25468
  ],
25068
- subAgents: [buildSoftwareProfileAnalystSubAgent(paths, issueDefaults)],
25469
+ subAgents: [
25470
+ buildSoftwareProfileAnalystSubAgent(paths, issueDefaults, tier)
25471
+ ],
25069
25472
  labels: [
25070
25473
  {
25071
25474
  name: "type:software-profile",
@@ -27299,6 +27702,211 @@ var vitestBundle = {
27299
27702
  }
27300
27703
  };
27301
27704
 
27705
+ // src/agent/bundles/agent-registry.ts
27706
+ var AGENT_REGISTRY_ENTRIES = [
27707
+ {
27708
+ bundle: "agenda",
27709
+ skill: "/draft-agenda",
27710
+ agent: "agenda-analyst",
27711
+ resolveOutputPath: (p) => `${p.meetingsRoot}/`,
27712
+ purpose: "Pre-meeting agenda authoring (2-phase: draft, finalize). Reuses the project's meeting-type taxonomy.",
27713
+ workflowRuleName: "meeting-agenda-workflow"
27714
+ },
27715
+ {
27716
+ bundle: "bcm-writer",
27717
+ skill: "/write-bcm",
27718
+ agent: "bcm-writer",
27719
+ resolveOutputPath: (p) => `${p.bcmRoot}/`,
27720
+ purpose: "BCM capability-model documents (4-phase: outline, scaffold, context, connect). BIZBOK-aligned.",
27721
+ workflowRuleName: "bcm-writer-workflow"
27722
+ },
27723
+ {
27724
+ bundle: "business-models",
27725
+ skill: "/scan-business-models",
27726
+ agent: "business-models-analyst",
27727
+ resolveOutputPath: (p) => `${p.docsRoot}/industry-research/`,
27728
+ purpose: "Osterwalder business-model canvases per industry segment (3-phase: scan, canvas, complete).",
27729
+ workflowRuleName: "business-models-workflow"
27730
+ },
27731
+ {
27732
+ bundle: "company-profile",
27733
+ skill: "/profile-company",
27734
+ agent: "company-profile-analyst",
27735
+ resolveOutputPath: (p) => `${p.companiesRoot}/`,
27736
+ purpose: "Company profiles, notes, and segment competitive analyses (up to 6 phases incl. refresh).",
27737
+ workflowRuleName: "company-profile-workflow"
27738
+ },
27739
+ {
27740
+ bundle: "customer-profile",
27741
+ skill: "/discover-customers",
27742
+ agent: "customer-profile-analyst",
27743
+ resolveOutputPath: (p) => `${p.profilesRoot}/customers/`,
27744
+ purpose: "Customer-archetype discovery, profiles, and competitor feature-mapping (3-phase).",
27745
+ workflowRuleName: "customer-profile-workflow"
27746
+ },
27747
+ {
27748
+ bundle: "docs-sync",
27749
+ skill: "/docs-sync-pr",
27750
+ agent: "docs-sync",
27751
+ resolveOutputPath: (p) => `${p.docsRoot}/`,
27752
+ purpose: "Pre-PR drift detection and mechanical doc fixes (2-phase: scan, fix). Skeleton release.",
27753
+ workflowRuleName: "docs-sync-workflow"
27754
+ },
27755
+ {
27756
+ bundle: "industry-discovery",
27757
+ skill: "/discover-industries",
27758
+ agent: "industry-discovery-analyst",
27759
+ resolveOutputPath: (p) => `${p.industriesRoot}/`,
27760
+ purpose: "Industry-vertical discovery, fit-evaluation, and research planning (3-phase).",
27761
+ workflowRuleName: "industry-discovery-workflow"
27762
+ },
27763
+ {
27764
+ bundle: "maintenance-audit",
27765
+ skill: "/audit-docs",
27766
+ agent: "maintenance-audit",
27767
+ resolveOutputPath: (p) => `${p.docsRoot}/`,
27768
+ purpose: "Doc-registry audits with idempotent fixes (3-phase: scan, fix, verify). No new content.",
27769
+ workflowRuleName: "maintenance-audit-workflow"
27770
+ },
27771
+ {
27772
+ bundle: "meeting-analysis",
27773
+ skill: "/process-meeting",
27774
+ agent: "meeting-analyst",
27775
+ resolveOutputPath: (p) => `${p.meetingsRoot}/`,
27776
+ purpose: "Post-meeting transcript processing (4-phase: extract, notes, draft, link).",
27777
+ workflowRuleName: "meeting-processing-workflow"
27778
+ },
27779
+ {
27780
+ bundle: "people-profile",
27781
+ skill: "/profile-person",
27782
+ agent: "people-profile-analyst",
27783
+ resolveOutputPath: (p) => `${p.peopleRoot}/`,
27784
+ purpose: "Person profiles only (up to 4 phases incl. refresh). Hands off to company/software bundles.",
27785
+ workflowRuleName: "people-profile-workflow"
27786
+ },
27787
+ {
27788
+ bundle: "pr-review",
27789
+ skill: "/review-pr",
27790
+ agent: "pr-reviewer",
27791
+ resolveOutputPath: () => "",
27792
+ purpose: "PR review, auto-merge gating, and human-in-the-loop feedback orchestration.",
27793
+ workflowRuleName: "pr-review-workflow"
27794
+ },
27795
+ {
27796
+ bundle: "regulatory-research",
27797
+ skill: "/scan-regulatory-landscape",
27798
+ agent: "regulatory-research-analyst",
27799
+ resolveOutputPath: (p) => `${p.docsRoot}/regulations/`,
27800
+ purpose: "Regulation research, version pages, and impact analysis (3-phase). Documents, never interprets.",
27801
+ workflowRuleName: "regulatory-research-workflow"
27802
+ },
27803
+ {
27804
+ bundle: "requirements-analyst",
27805
+ skill: "/scan-requirements",
27806
+ agent: "requirements-analyst",
27807
+ resolveOutputPath: (p) => `${p.researchRequirementsRoot}/`,
27808
+ purpose: "Requirements gap discovery + draft proposals (2-phase: scan, draft-trace). Hands off to writer.",
27809
+ workflowRuleName: "requirements-analyst-workflow"
27810
+ },
27811
+ {
27812
+ bundle: "requirements-reviewer",
27813
+ skill: "/review-requirements",
27814
+ agent: "requirements-reviewer",
27815
+ resolveOutputPath: (p) => `${p.requirementsRoot}/`,
27816
+ purpose: "Requirements audit (12 checks) and narrow-edit deprecation. Never authors new requirement docs.",
27817
+ workflowRuleName: "requirements-reviewer-workflow"
27818
+ },
27819
+ {
27820
+ bundle: "requirements-writer",
27821
+ skill: "/write-requirement",
27822
+ agent: "requirements-writer",
27823
+ resolveOutputPath: (p) => `${p.requirementsRoot}/`,
27824
+ purpose: "Formal requirement document authoring across the 11-category taxonomy + four-tier classification.",
27825
+ workflowRuleName: "requirements-writer-workflow"
27826
+ },
27827
+ {
27828
+ bundle: "research-pipeline",
27829
+ skill: "/research",
27830
+ agent: "research-analyst",
27831
+ resolveOutputPath: (p) => `${p.researchRoot}/`,
27832
+ purpose: "Generic research micro-tasks (3-phase: scope, slice, verify). Notes-only output.",
27833
+ workflowRuleName: "research-pipeline-workflow"
27834
+ },
27835
+ {
27836
+ bundle: "software-profile",
27837
+ skill: "/profile-software",
27838
+ agent: "software-profile-analyst",
27839
+ resolveOutputPath: (p) => `${p.softwareRoot}/`,
27840
+ purpose: "Software profiles, shared feature matrix, and BCM-tree capability mapping (up to 5 phases).",
27841
+ workflowRuleName: "software-profile-workflow"
27842
+ },
27843
+ {
27844
+ bundle: "standards-research",
27845
+ skill: "/scope-standards-research",
27846
+ agent: "standards-research-analyst",
27847
+ resolveOutputPath: (p) => `${p.docsRoot}/standards/`,
27848
+ purpose: "Interoperability standards research (5-phase: scope, research, compare, extension, organizations).",
27849
+ workflowRuleName: "standards-research-workflow"
27850
+ }
27851
+ ];
27852
+ var SUPPRESSED_WORKFLOW_RULE_NAMES = AGENT_REGISTRY_ENTRIES.map((e) => e.workflowRuleName);
27853
+ function isSuppressedWorkflowRule(name) {
27854
+ return SUPPRESSED_WORKFLOW_RULE_NAMES.includes(name);
27855
+ }
27856
+ function bundleNameForWorkflowRule(ruleName) {
27857
+ const entry = AGENT_REGISTRY_ENTRIES.find(
27858
+ (e) => e.workflowRuleName === ruleName
27859
+ );
27860
+ return entry?.bundle;
27861
+ }
27862
+ function buildAgentRegistryRule(bundles, paths) {
27863
+ const activeNames = new Set(bundles.map((b) => b.name));
27864
+ const rows = AGENT_REGISTRY_ENTRIES.filter(
27865
+ (e) => activeNames.has(e.bundle)
27866
+ ).slice();
27867
+ if (rows.length === 0) {
27868
+ return void 0;
27869
+ }
27870
+ rows.sort((a, b) => a.bundle.localeCompare(b.bundle));
27871
+ const lines = [
27872
+ "# Agent Registry",
27873
+ "",
27874
+ "Each row below is the routing summary for one phased-agent",
27875
+ "bundle: the user-invocable skill, the sub-agent that handles",
27876
+ "the work, the output path the agent writes to, and a one-line",
27877
+ "purpose. Use the table to answer *which agent handles X* \u2014",
27878
+ "full agent prompts live in `.claude/agents/<name>.md` and only",
27879
+ "load on delegation, so the table is the always-on routing",
27880
+ "signal.",
27881
+ "",
27882
+ "Cross-cutting policies that apply to every agent below \u2014",
27883
+ "Progress File Convention, Shared-Editing Safety, Issue",
27884
+ "Templates, Skill Evals \u2014 are documented as their own top-level",
27885
+ "sections elsewhere in this file. The registry only captures",
27886
+ "routing; it does not duplicate convention prose.",
27887
+ "",
27888
+ "| Skill | Agent | Bundle | Output path | Purpose |",
27889
+ "| --- | --- | --- | --- | --- |"
27890
+ ];
27891
+ for (const row of rows) {
27892
+ const outputPath = row.resolveOutputPath(paths);
27893
+ const pathCell = outputPath.length > 0 ? `\`${outputPath}\`` : "\u2014";
27894
+ lines.push(
27895
+ `| \`${row.skill}\` | \`${row.agent}\` | \`${row.bundle}\` | ${pathCell} | ${row.purpose} |`
27896
+ );
27897
+ }
27898
+ return {
27899
+ name: "agent-registry",
27900
+ description: "Routing table mapping each phased-agent bundle to its primary skill, sub-agent, output path, and one-line purpose. Replaces the per-bundle workflow rules whose prose summaries duplicated the routing signal.",
27901
+ scope: AGENT_RULE_SCOPE.ALWAYS,
27902
+ content: lines.join("\n"),
27903
+ platforms: {
27904
+ cursor: { exclude: true }
27905
+ },
27906
+ tags: ["workflow"]
27907
+ };
27908
+ }
27909
+
27302
27910
  // src/agent/bundles/features.ts
27303
27911
  var SOURCE_TIER_HEADINGS = [
27304
27912
  { key: "t1", heading: "### T1 \u2014 Primary Living" },
@@ -27666,7 +28274,8 @@ function renderPriorityRulesSection(rules) {
27666
28274
  }
27667
28275
 
27668
28276
  // src/agent/bundles/index.ts
27669
- function buildBuiltInBundles(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS) {
28277
+ function buildBuiltInBundles(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS, defaultAgentTier = AGENT_MODEL.BALANCED, bundleAgentTiers = /* @__PURE__ */ new Map()) {
28278
+ const tierFor = (bundle) => bundleAgentTiers.get(bundle) ?? defaultAgentTier;
27670
28279
  return [
27671
28280
  buildBaseBundle(paths),
27672
28281
  upstreamConfigulatorDocsBundle,
@@ -27679,7 +28288,7 @@ function buildBuiltInBundles(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAUL
27679
28288
  projenBundle,
27680
28289
  githubWorkflowBundle,
27681
28290
  slackBundle,
27682
- meetingAnalysisBundle,
28291
+ buildMeetingAnalysisBundle(tierFor("meeting-analysis")),
27683
28292
  agendaBundle,
27684
28293
  orchestratorBundle,
27685
28294
  prReviewBundle,
@@ -27687,16 +28296,28 @@ function buildBuiltInBundles(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAUL
27687
28296
  buildRequirementsWriterBundle(paths, issueDefaults),
27688
28297
  buildRequirementsReviewerBundle(paths, issueDefaults),
27689
28298
  buildResearchPipelineBundle(paths, issueDefaults),
27690
- buildCompanyProfileBundle(paths, issueDefaults),
27691
- buildCustomerProfileBundle(paths, issueDefaults),
27692
- buildPeopleProfileBundle(paths, issueDefaults),
27693
- buildSoftwareProfileBundle(paths, issueDefaults),
28299
+ buildCompanyProfileBundle(paths, issueDefaults, tierFor("company-profile")),
28300
+ buildCustomerProfileBundle(
28301
+ paths,
28302
+ issueDefaults,
28303
+ tierFor("customer-profile")
28304
+ ),
28305
+ buildPeopleProfileBundle(paths, issueDefaults, tierFor("people-profile")),
28306
+ buildSoftwareProfileBundle(
28307
+ paths,
28308
+ issueDefaults,
28309
+ tierFor("software-profile")
28310
+ ),
27694
28311
  buildIndustryDiscoveryBundle(paths, issueDefaults),
27695
28312
  buildBusinessModelsBundle(paths, issueDefaults),
27696
28313
  buildBcmWriterBundle(paths, issueDefaults),
27697
28314
  buildStandardsResearchBundle(paths, issueDefaults),
27698
28315
  buildRegulatoryResearchBundle(paths, issueDefaults),
27699
- buildMaintenanceAuditBundle(paths, issueDefaults),
28316
+ buildMaintenanceAuditBundle(
28317
+ paths,
28318
+ issueDefaults,
28319
+ tierFor("maintenance-audit")
28320
+ ),
27700
28321
  buildDocsSyncBundle(paths)
27701
28322
  ];
27702
28323
  }
@@ -27942,7 +28563,7 @@ var ClaudeRenderer = class _ClaudeRenderer {
27942
28563
  /**
27943
28564
  * Render all Claude Code configuration files.
27944
28565
  */
27945
- static render(component, rules, skills, subAgents, mcpServers, settings, procedures) {
28566
+ static render(component, rules, skills, subAgents, mcpServers, settings, procedures, commands) {
27946
28567
  _ClaudeRenderer.renderClaudeMd(component, rules);
27947
28568
  _ClaudeRenderer.renderScopedRules(component, rules);
27948
28569
  _ClaudeRenderer.renderSettings(component, mcpServers, settings);
@@ -27951,6 +28572,9 @@ var ClaudeRenderer = class _ClaudeRenderer {
27951
28572
  if (procedures && procedures.length > 0) {
27952
28573
  _ClaudeRenderer.renderProcedures(component, procedures);
27953
28574
  }
28575
+ if (commands && commands.length > 0) {
28576
+ _ClaudeRenderer.renderCommands(component, commands);
28577
+ }
27954
28578
  }
27955
28579
  static renderClaudeMd(component, rules) {
27956
28580
  const claudeMdRules = rules.filter((r) => {
@@ -28282,6 +28906,30 @@ var ClaudeRenderer = class _ClaudeRenderer {
28282
28906
  });
28283
28907
  }
28284
28908
  }
28909
+ /**
28910
+ * Renders user-invokable slash commands to `.claude/commands/<name>.md`.
28911
+ *
28912
+ * Each command file carries a YAML frontmatter block with the
28913
+ * `description` (always present) and an optional `model` alias. The
28914
+ * body is the {@link AgentCommand.content} written verbatim.
28915
+ *
28916
+ * @see https://docs.claude.com/en/docs/claude-code/slash-commands
28917
+ */
28918
+ static renderCommands(component, commands) {
28919
+ for (const command of commands) {
28920
+ const lines = [];
28921
+ lines.push("---");
28922
+ lines.push(`description: ${command.description}`);
28923
+ const resolvedModel = resolveModelAlias(command.model);
28924
+ if (resolvedModel) {
28925
+ lines.push(`model: ${resolvedModel}`);
28926
+ }
28927
+ lines.push("---");
28928
+ lines.push("");
28929
+ lines.push(...command.content.split("\n"));
28930
+ new import_textfile3.TextFile(component, `.claude/commands/${command.name}.md`, { lines });
28931
+ }
28932
+ }
28285
28933
  /**
28286
28934
  * Determine the default Claude rule target based on rule scope.
28287
28935
  * ALWAYS-scoped rules default to CLAUDE_MD; FILE_PATTERN rules default to SCOPED_FILE.
@@ -28304,6 +28952,45 @@ var CopilotRenderer = class {
28304
28952
  };
28305
28953
 
28306
28954
  // src/agent/agent-config.ts
28955
+ function mapAgentTier(value) {
28956
+ switch (value) {
28957
+ case "powerful":
28958
+ return AGENT_MODEL.POWERFUL;
28959
+ case "fast":
28960
+ return AGENT_MODEL.FAST;
28961
+ case "balanced":
28962
+ default:
28963
+ return AGENT_MODEL.BALANCED;
28964
+ }
28965
+ }
28966
+ function resolveDefaultAgentTier(options) {
28967
+ return mapAgentTier(options?.defaultAgentTier);
28968
+ }
28969
+ var TIER_AWARE_BUNDLE_NAMES = [
28970
+ "company-profile",
28971
+ "customer-profile",
28972
+ "maintenance-audit",
28973
+ "meeting-analysis",
28974
+ "people-profile",
28975
+ "software-profile"
28976
+ ];
28977
+ function resolveBundleAgentTiers(options) {
28978
+ const overrides = options?.bundleAgentTiers;
28979
+ if (!overrides) {
28980
+ return /* @__PURE__ */ new Map();
28981
+ }
28982
+ const validNames = new Set(TIER_AWARE_BUNDLE_NAMES);
28983
+ const out = /* @__PURE__ */ new Map();
28984
+ for (const [name, value] of Object.entries(overrides)) {
28985
+ if (!validNames.has(name)) {
28986
+ throw new Error(
28987
+ `bundleAgentTiers contains unknown bundle name "${name}". Valid names are: ${TIER_AWARE_BUNDLE_NAMES.join(", ")}.`
28988
+ );
28989
+ }
28990
+ out.set(name, mapAgentTier(value));
28991
+ }
28992
+ return out;
28993
+ }
28307
28994
  var DEFAULT_CLAUDE_ALLOW = [
28308
28995
  // ── Git ──────────────────────────────────────────────────────────────
28309
28996
  "Bash(git add *)",
@@ -28408,6 +29095,45 @@ var DEFAULT_CLAUDE_DENY = [
28408
29095
  "Bash(export *)",
28409
29096
  "Bash(env *)"
28410
29097
  ];
29098
+ var DEFAULT_CLAUDE_PATH_DENY = [
29099
+ "Read(./node_modules/**)",
29100
+ "Read(./dist/**)",
29101
+ "Read(./lib/**)",
29102
+ "Read(./.astro/**)",
29103
+ "Read(./.env)",
29104
+ "Read(./.env.*)",
29105
+ "Read(./*.lock)",
29106
+ "Read(./pnpm-lock.yaml)",
29107
+ "Read(./package-lock.json)"
29108
+ ];
29109
+ var DEFAULT_USAGE_LOG_PATH = ".claude/usage.log";
29110
+ var DEFAULT_CLAUDE_ENV = {
29111
+ ENABLE_TOOL_SEARCH: "1"
29112
+ };
29113
+ var DEFAULT_CLAUDE_HOOKS = {
29114
+ Stop: [
29115
+ {
29116
+ matcher: "*",
29117
+ hooks: [
29118
+ {
29119
+ type: "command",
29120
+ command: `echo "$(date -u +%FT%TZ),\${CLAUDE_SESSION_ID:-unknown},\${CLAUDE_INPUT_TOKENS:-0},\${CLAUDE_OUTPUT_TOKENS:-0}" >> ${DEFAULT_USAGE_LOG_PATH}`
29121
+ }
29122
+ ]
29123
+ }
29124
+ ],
29125
+ PostToolUse: [
29126
+ {
29127
+ matcher: "Edit|Write",
29128
+ hooks: [
29129
+ {
29130
+ type: "command",
29131
+ command: 'case "${CLAUDE_TOOL_INPUT_path:-}" in *docs/src/content/docs/*) if [ -x .claude/procedures/check-links.sh ]; then bash .claude/procedures/check-links.sh "$CLAUDE_TOOL_INPUT_path" 2>&1 | head -20; fi ;; esac'
29132
+ }
29133
+ ]
29134
+ }
29135
+ ]
29136
+ };
28411
29137
  var PROGRESS_FILE_BUNDLE_HOOKS = [
28412
29138
  ["bcm-writer-workflow", "bcm-writer"],
28413
29139
  ["business-models-workflow", "business-models"],
@@ -28482,10 +29208,30 @@ var AgentConfig = class _AgentConfig extends import_projen8.Component {
28482
29208
  return (examples.t1?.length ?? 0) > 0 || (examples.t2?.length ?? 0) > 0 || (examples.t3?.length ?? 0) > 0 || (examples.t4?.length ?? 0) > 0;
28483
29209
  }
28484
29210
  /**
28485
- * Merges default Claude permissions with bundle and user-supplied settings.
29211
+ * Merges default Claude permissions and hooks with bundle and
29212
+ * user-supplied settings.
28486
29213
  *
28487
- * Merge order: defaults → bundle permissions → user-supplied entries.
28488
- * `defaultMode` defaults to `"dontAsk"` unless overridden.
29214
+ * Permission merge order: defaults → bundle permissions → user-supplied
29215
+ * entries. Both `allow` and `deny` are deduped via
29216
+ * `Array.from(new Set(...))`; V8 `Set` iteration preserves insertion
29217
+ * order, so the final ordering is defaults first, then bundle, then
29218
+ * user, with duplicates removed (first-occurrence wins). `defaultMode`
29219
+ * defaults to `"dontAsk"` unless overridden — see the inline comment
29220
+ * on the literal below for the autonomous-worker rationale.
29221
+ *
29222
+ * Hooks merge: consumer-supplied entries first, then default entries
29223
+ * (Stop, PostToolUse), deduped by `(matcher, JSON-serialized hooks)`.
29224
+ * Defaults are skipped entirely when `disableAllHooks: true` is set —
29225
+ * the `disableAllHooks` flag passes through to the rendered file so
29226
+ * Claude Code suppresses every project-level hook at runtime, and the
29227
+ * defaults are dropped at synth time so the rendered file does not
29228
+ * carry orphan entries that would re-fire if the operator later flipped
29229
+ * the flag back off.
29230
+ *
29231
+ * Env merge: defaults from `DEFAULT_CLAUDE_ENV` (e.g.
29232
+ * `ENABLE_TOOL_SEARCH=1`) layer first, then consumer-supplied
29233
+ * `claudeSettings.env` entries override on key collision. Sibling keys
29234
+ * from both sources land in the rendered file.
28489
29235
  */
28490
29236
  static mergeClaudeDefaults(userSettings, bundlePermissions) {
28491
29237
  const bundleAllow = bundlePermissions?.allow ?? [];
@@ -28494,14 +29240,80 @@ var AgentConfig = class _AgentConfig extends import_projen8.Component {
28494
29240
  const userDeny = userSettings?.permissions?.deny ?? [];
28495
29241
  return {
28496
29242
  ...userSettings,
29243
+ // `defaultMode: "dontAsk"` is configulator's hardcoded default
29244
+ // because scheduled-task workers (issue-worker, orchestrator,
29245
+ // pr-reviewer, and the analyst/writer family) run autonomously
29246
+ // and would deadlock on confirmation prompts. Any other value
29247
+ // breaks the autonomous-worker contract — override only after
29248
+ // revisiting that contract end-to-end. The override path for
29249
+ // consumers is `claudeSettings.defaultMode` on
29250
+ // `AgentConfigOptions`.
28497
29251
  defaultMode: userSettings?.defaultMode ?? "dontAsk",
28498
29252
  permissions: {
28499
29253
  ...userSettings?.permissions,
28500
- allow: [...DEFAULT_CLAUDE_ALLOW, ...bundleAllow, ...userAllow],
28501
- deny: [...DEFAULT_CLAUDE_DENY, ...bundleDeny, ...userDeny]
28502
- }
29254
+ allow: Array.from(
29255
+ /* @__PURE__ */ new Set([...DEFAULT_CLAUDE_ALLOW, ...bundleAllow, ...userAllow])
29256
+ ),
29257
+ deny: Array.from(
29258
+ /* @__PURE__ */ new Set([
29259
+ ...DEFAULT_CLAUDE_DENY,
29260
+ ...DEFAULT_CLAUDE_PATH_DENY,
29261
+ ...bundleDeny,
29262
+ ...userDeny
29263
+ ])
29264
+ )
29265
+ },
29266
+ hooks: _AgentConfig.mergeClaudeHooks(userSettings),
29267
+ env: { ...DEFAULT_CLAUDE_ENV, ...userSettings?.env ?? {} }
28503
29268
  };
28504
29269
  }
29270
+ /**
29271
+ * Merge default lifecycle hooks (Stop, PostToolUse) with consumer-
29272
+ * supplied entries on `claudeSettings.hooks`. Consumer entries appear
29273
+ * first so a downstream override that wires a faster lint/format hook
29274
+ * runs ahead of the defaults; defaults are appended after.
29275
+ *
29276
+ * Returns `undefined` when neither defaults nor consumer entries
29277
+ * remain — the renderer skips the `hooks` key entirely in that case
29278
+ * so opt-out repos do not ship an empty `"hooks": {}` object.
29279
+ *
29280
+ * Defaults are gated on `disableAllHooks !== true`. When the flag is
29281
+ * set we still pass through any consumer-supplied entries (the
29282
+ * runtime-level `disableAllHooks: true` is what suppresses execution),
29283
+ * but we never inject the bundle defaults. That keeps the disable-all
29284
+ * escape hatch idempotent: flipping the flag drops the bundle's
29285
+ * default surface area instead of leaving the entries on disk for a
29286
+ * future re-enable.
29287
+ */
29288
+ static mergeClaudeHooks(userSettings) {
29289
+ const userHooks = userSettings?.hooks;
29290
+ const includeDefaults = userSettings?.disableAllHooks !== true;
29291
+ const defaults = includeDefaults ? DEFAULT_CLAUDE_HOOKS : {};
29292
+ const allEvents = /* @__PURE__ */ new Set([
29293
+ ...Object.keys(userHooks ?? {}),
29294
+ ...Object.keys(defaults)
29295
+ ]);
29296
+ const merged = {};
29297
+ for (const event of allEvents) {
29298
+ const userEntries = (userHooks ?? {})[event] ?? [];
29299
+ const defaultEntries = defaults[event] ?? [];
29300
+ const seen = /* @__PURE__ */ new Set();
29301
+ const combined = [];
29302
+ for (const entry of [...userEntries, ...defaultEntries]) {
29303
+ const key = JSON.stringify({
29304
+ matcher: entry.matcher,
29305
+ hooks: entry.hooks
29306
+ });
29307
+ if (seen.has(key)) continue;
29308
+ seen.add(key);
29309
+ combined.push(entry);
29310
+ }
29311
+ if (combined.length > 0) {
29312
+ merged[event] = combined;
29313
+ }
29314
+ }
29315
+ return Object.keys(merged).length > 0 ? merged : void 0;
29316
+ }
28505
29317
  constructor(project, options = {}) {
28506
29318
  super(project);
28507
29319
  this.options = options;
@@ -28530,7 +29342,9 @@ var AgentConfig = class _AgentConfig extends import_projen8.Component {
28530
29342
  if (!this.cachedBundles) {
28531
29343
  this.cachedBundles = buildBuiltInBundles(
28532
29344
  this.resolvedPaths,
28533
- resolveIssueDefaults(this.options.issueDefaults)
29345
+ resolveIssueDefaults(this.options.issueDefaults),
29346
+ resolveDefaultAgentTier(this.options),
29347
+ resolveBundleAgentTiers(this.options)
28534
29348
  );
28535
29349
  }
28536
29350
  return this.cachedBundles;
@@ -28576,6 +29390,9 @@ var AgentConfig = class _AgentConfig extends import_projen8.Component {
28576
29390
  if (resolvedRunRatio.enabled) {
28577
29391
  this.project.gitignore.addPatterns(`/${resolvedRunRatio.stateFilePath}`);
28578
29392
  }
29393
+ if (this.options.claudeSettings?.disableAllHooks !== true) {
29394
+ this.project.gitignore.addPatterns(`/${DEFAULT_USAGE_LOG_PATH}`);
29395
+ }
28579
29396
  validateUnblockDependentsConfig(this.options.unblockDependents);
28580
29397
  validateIssueDefaultsConfig(this.options.issueDefaults);
28581
29398
  const resolvedProgressFiles = validateProgressFilesConfig(
@@ -28640,6 +29457,7 @@ var AgentConfig = class _AgentConfig extends import_projen8.Component {
28640
29457
  const skills = this.resolveSkills();
28641
29458
  const subAgents = this.resolveSubAgents();
28642
29459
  const procedures = this.resolveProcedures();
29460
+ const commands = this.resolveCommands();
28643
29461
  const mcpServers = this.options.mcpServers ?? {};
28644
29462
  const projectMetadata = ProjectMetadata.of(this.project);
28645
29463
  const metadata = projectMetadata?.metadata;
@@ -28675,7 +29493,8 @@ var AgentConfig = class _AgentConfig extends import_projen8.Component {
28675
29493
  this.options.claudeSettings,
28676
29494
  bundlePermissions
28677
29495
  ),
28678
- resolvedProcedures
29496
+ resolvedProcedures,
29497
+ commands
28679
29498
  );
28680
29499
  }
28681
29500
  if (platforms.includes(AGENT_PLATFORM.CODEX)) {
@@ -28814,6 +29633,7 @@ ${section}`
28814
29633
  }
28815
29634
  }
28816
29635
  }
29636
+ const injectBundleHooks = this.options.claudeMd?.injectBundleHooks ?? true;
28817
29637
  const resolvedProgressFiles = resolveProgressFiles(
28818
29638
  this.options.progressFiles
28819
29639
  );
@@ -28831,7 +29651,7 @@ ${section}`
28831
29651
  }
28832
29652
  }
28833
29653
  }
28834
- if (resolvedProgressFiles.enabled) {
29654
+ if (injectBundleHooks && resolvedProgressFiles.enabled) {
28835
29655
  for (const [ruleName, label] of PROGRESS_FILE_BUNDLE_HOOKS) {
28836
29656
  const existing = ruleMap.get(ruleName);
28837
29657
  if (!existing) {
@@ -28871,7 +29691,7 @@ ${hook}`
28871
29691
  }
28872
29692
  }
28873
29693
  }
28874
- if (resolvedSharedEditingForRules.enabled) {
29694
+ if (injectBundleHooks && resolvedSharedEditingForRules.enabled) {
28875
29695
  for (const [ruleName, label] of SHARED_EDITING_BUNDLE_HOOKS) {
28876
29696
  const existing = ruleMap.get(ruleName);
28877
29697
  if (!existing) {
@@ -28911,7 +29731,7 @@ ${hook}`
28911
29731
  }
28912
29732
  }
28913
29733
  }
28914
- if (resolvedSkillEvalsForRules.enabled) {
29734
+ if (injectBundleHooks && resolvedSkillEvalsForRules.enabled) {
28915
29735
  for (const [ruleName, label] of SKILL_EVALS_BUNDLE_HOOKS) {
28916
29736
  const existing = ruleMap.get(ruleName);
28917
29737
  if (!existing) {
@@ -28956,7 +29776,7 @@ ${hook}`
28956
29776
  if (!hasAnyDocsEmittingBundle(excludedBundleNames)) {
28957
29777
  ruleMap.delete("stub-index-convention");
28958
29778
  }
28959
- if (resolvedIssueTemplatesForRules.enabled && hasDownstreamBundles) {
29779
+ if (injectBundleHooks && resolvedIssueTemplatesForRules.enabled && hasDownstreamBundles) {
28960
29780
  for (const [ruleName, label] of ISSUE_TEMPLATES_BUNDLE_HOOKS) {
28961
29781
  const existing = ruleMap.get(ruleName);
28962
29782
  if (!existing) {
@@ -29004,6 +29824,57 @@ ${hook}`
29004
29824
  }
29005
29825
  }
29006
29826
  }
29827
+ const registryRule = buildAgentRegistryRule(
29828
+ this.activeBundles,
29829
+ this.resolvedPaths
29830
+ );
29831
+ if (registryRule) {
29832
+ let registryContent = registryRule.content;
29833
+ const meetingAnalysisActive = this.activeBundles.some(
29834
+ (b) => b.name === "meeting-analysis"
29835
+ );
29836
+ if (this.options.meetings && meetingAnalysisActive) {
29837
+ const meetingsSection = renderMeetingTypesSection(
29838
+ this.options.meetings
29839
+ );
29840
+ if (meetingsSection.length > 0) {
29841
+ registryContent = `${registryContent}
29842
+
29843
+ ---
29844
+
29845
+ ${meetingsSection}`;
29846
+ }
29847
+ }
29848
+ ruleMap.set(registryRule.name, {
29849
+ ...registryRule,
29850
+ content: registryContent
29851
+ });
29852
+ const customSectionBundles = new Set(
29853
+ (this.options.features?.customDocSections ?? []).map(
29854
+ (s) => s.bundleName
29855
+ )
29856
+ );
29857
+ for (const [ruleName, rule] of ruleMap) {
29858
+ if (!isSuppressedWorkflowRule(ruleName)) {
29859
+ continue;
29860
+ }
29861
+ const owningBundleName = bundleNameForWorkflowRule(ruleName);
29862
+ if (owningBundleName !== void 0 && customSectionBundles.has(owningBundleName)) {
29863
+ continue;
29864
+ }
29865
+ const existingClaude = rule.platforms?.claude ?? {};
29866
+ if (existingClaude.exclude === true) {
29867
+ continue;
29868
+ }
29869
+ ruleMap.set(ruleName, {
29870
+ ...rule,
29871
+ platforms: {
29872
+ ...rule.platforms,
29873
+ claude: { ...existingClaude, exclude: true }
29874
+ }
29875
+ });
29876
+ }
29877
+ }
29007
29878
  return [...ruleMap.values()].sort((a, b) => {
29008
29879
  if (a.name === "project-overview") return -1;
29009
29880
  if (b.name === "project-overview") return 1;
@@ -29152,6 +30023,49 @@ ${hook}`
29152
30023
  }
29153
30024
  return [...procMap.values()];
29154
30025
  }
30026
+ /**
30027
+ * Resolves the final list of slash commands by merging bundle-shipped
30028
+ * defaults with consumer-supplied entries. Mirrors {@link resolveSkills}
30029
+ * and {@link resolveSubAgents}: auto-detected bundles contribute first,
30030
+ * force-included bundles overlay, and consumer commands override on
30031
+ * name collision. Names listed in `excludeCommands` are dropped after
30032
+ * the merge so consumers can opt out of a single default without
30033
+ * disabling the whole bundle.
30034
+ */
30035
+ resolveCommands() {
30036
+ const commandMap = /* @__PURE__ */ new Map();
30037
+ if (this.options.autoDetectBundles !== false) {
30038
+ for (const bundle of this.pathAwareBundles) {
30039
+ if (this.options.excludeBundles?.includes(bundle.name)) continue;
30040
+ if (bundle.appliesWhen(this.project) && bundle.commands) {
30041
+ for (const command of bundle.commands) {
30042
+ commandMap.set(command.name, command);
30043
+ }
30044
+ }
30045
+ }
30046
+ }
30047
+ if (this.options.includeBundles) {
30048
+ for (const bundleName of this.options.includeBundles) {
30049
+ const bundle = this.pathAwareBundles.find((b) => b.name === bundleName);
30050
+ if (bundle?.commands) {
30051
+ for (const command of bundle.commands) {
30052
+ commandMap.set(command.name, command);
30053
+ }
30054
+ }
30055
+ }
30056
+ }
30057
+ if (this.options.commands) {
30058
+ for (const command of this.options.commands) {
30059
+ commandMap.set(command.name, command);
30060
+ }
30061
+ }
30062
+ if (this.options.excludeCommands) {
30063
+ for (const name of this.options.excludeCommands) {
30064
+ commandMap.delete(name);
30065
+ }
30066
+ }
30067
+ return [...commandMap.values()];
30068
+ }
29155
30069
  /**
29156
30070
  * Resolves template variables in rule content using project metadata.
29157
30071
  * Emits synthesis warnings for rules with unresolved variables.
@@ -33584,6 +34498,7 @@ var TypeScriptConfig = class extends import_projen24.Component {
33584
34498
  0 && (module.exports = {
33585
34499
  AGENT_MODEL,
33586
34500
  AGENT_PLATFORM,
34501
+ AGENT_REGISTRY_ENTRIES,
33587
34502
  AGENT_RULE_SCOPE,
33588
34503
  AGENT_TIER_ROLES,
33589
34504
  AGENT_TIER_VALUES,
@@ -33680,8 +34595,10 @@ var TypeScriptConfig = class extends import_projen24.Component {
33680
34595
  SCOPE_CLASS_VALUES,
33681
34596
  SHARED_EDITING_CONFLICT_STRATEGY_VALUES,
33682
34597
  STARLIGHT_ROLE,
34598
+ SUPPRESSED_WORKFLOW_RULE_NAMES,
33683
34599
  SampleLang,
33684
34600
  StarlightProject,
34601
+ TIER_AWARE_BUNDLE_NAMES,
33685
34602
  TestRunner,
33686
34603
  TsDocCoverageKind,
33687
34604
  TurboRepo,
@@ -33705,6 +34622,7 @@ var TypeScriptConfig = class extends import_projen24.Component {
33705
34622
  awsCdkBundle,
33706
34623
  baseBundle,
33707
34624
  bcmWriterBundle,
34625
+ buildAgentRegistryRule,
33708
34626
  buildBaseBundle,
33709
34627
  buildBcmWriterBundle,
33710
34628
  buildBuiltInBundles,
@@ -33715,6 +34633,7 @@ var TypeScriptConfig = class extends import_projen24.Component {
33715
34633
  buildDocsSyncBundle,
33716
34634
  buildIndustryDiscoveryBundle,
33717
34635
  buildMaintenanceAuditBundle,
34636
+ buildMeetingAnalysisBundle,
33718
34637
  buildOrchestratorConventionsContent,
33719
34638
  buildPeopleProfileBundle,
33720
34639
  buildRegulatoryResearchBundle,
@@ -33726,6 +34645,7 @@ var TypeScriptConfig = class extends import_projen24.Component {
33726
34645
  buildSoftwareProfileBundle,
33727
34646
  buildStandardsResearchBundle,
33728
34647
  buildUnblockDependentsProcedure,
34648
+ bundleNameForWorkflowRule,
33729
34649
  businessModelsBundle,
33730
34650
  checkDocSamplesProcedure,
33731
34651
  checkLinksProcedure,
@@ -33752,6 +34672,7 @@ var TypeScriptConfig = class extends import_projen24.Component {
33752
34672
  industryDiscoveryBundle,
33753
34673
  isPhaseLabelOwnedByExcluded,
33754
34674
  isScheduledTaskOwnedByExcluded,
34675
+ isSuppressedWorkflowRule,
33755
34676
  isTypeLabelOwnedByExcluded,
33756
34677
  jestBundle,
33757
34678
  labelsForPhase,
@@ -33810,6 +34731,8 @@ var TypeScriptConfig = class extends import_projen24.Component {
33810
34731
  resolveAgentTiers,
33811
34732
  resolveAstroProjectOutdir,
33812
34733
  resolveAwsCdkProjectOutdir,
34734
+ resolveBundleAgentTiers,
34735
+ resolveDefaultAgentTier,
33813
34736
  resolveIssueDefaults,
33814
34737
  resolveIssueTemplates,
33815
34738
  resolveModelAlias,