@codedrifters/configulator 0.0.406 → 0.0.408

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
@@ -223,6 +223,7 @@ __export(index_exports, {
223
223
  DEFAULT_API_EXTRACTOR_REPORT_FILENAME: () => DEFAULT_API_EXTRACTOR_REPORT_FILENAME,
224
224
  DEFAULT_API_EXTRACTOR_REPORT_FOLDER: () => DEFAULT_API_EXTRACTOR_REPORT_FOLDER,
225
225
  DEFAULT_AUDIT_REPORT_DIR: () => DEFAULT_AUDIT_REPORT_DIR,
226
+ DEFAULT_BASE_CONVENTIONS: () => DEFAULT_BASE_CONVENTIONS,
226
227
  DEFAULT_BUILD_POLICY: () => DEFAULT_BUILD_POLICY,
227
228
  DEFAULT_BUNDLE_OVERRIDES: () => DEFAULT_BUNDLE_OVERRIDES,
228
229
  DEFAULT_DECOMPOSITION_TEMPLATE: () => DEFAULT_DECOMPOSITION_TEMPLATE,
@@ -239,6 +240,7 @@ __export(index_exports, {
239
240
  DEFAULT_ISSUE_TEMPLATES_PATH: () => DEFAULT_ISSUE_TEMPLATES_PATH,
240
241
  DEFAULT_ISSUE_TEMPLATES_REQUIRE_REFERENCE: () => DEFAULT_ISSUE_TEMPLATES_REQUIRE_REFERENCE,
241
242
  DEFAULT_OFF_PEAK_CRON_EXAMPLE: () => DEFAULT_OFF_PEAK_CRON_EXAMPLE,
243
+ DEFAULT_ORCHESTRATOR_CONVENTIONS: () => DEFAULT_ORCHESTRATOR_CONVENTIONS,
242
244
  DEFAULT_PARTIAL_UNBLOCK_COMMENT_TEMPLATE: () => DEFAULT_PARTIAL_UNBLOCK_COMMENT_TEMPLATE,
243
245
  DEFAULT_PATHS_EXEMPT_FROM_SIZE: () => DEFAULT_PATHS_EXEMPT_FROM_SIZE,
244
246
  DEFAULT_PRIORITY_LABELS: () => DEFAULT_PRIORITY_LABELS,
@@ -251,6 +253,7 @@ __export(index_exports, {
251
253
  DEFAULT_REQUIREMENT_CATEGORY_DIRS: () => DEFAULT_REQUIREMENT_CATEGORY_DIRS,
252
254
  DEFAULT_REQUIRE_PRODUCT_CONTEXT: () => DEFAULT_REQUIRE_PRODUCT_CONTEXT,
253
255
  DEFAULT_RESOLVED_ISSUE_DEFAULTS: () => DEFAULT_RESOLVED_ISSUE_DEFAULTS,
256
+ DEFAULT_RULE_CONVENTIONS: () => DEFAULT_RULE_CONVENTIONS,
254
257
  DEFAULT_SAMPLE_COMPILER_OPTIONS: () => DEFAULT_SAMPLE_COMPILER_OPTIONS,
255
258
  DEFAULT_SCHEDULED_TASKS_ROOT: () => DEFAULT_SCHEDULED_TASKS_ROOT,
256
259
  DEFAULT_SCHEDULED_TASK_ENTRIES: () => DEFAULT_SCHEDULED_TASK_ENTRIES,
@@ -277,6 +280,7 @@ __export(index_exports, {
277
280
  DOCS_SYNC_AUDIT_SCHEMA_VERSION: () => DOCS_SYNC_AUDIT_SCHEMA_VERSION,
278
281
  GITHUB_ISSUE_TYPES: () => GITHUB_ISSUE_TYPES,
279
282
  GITHUB_ISSUE_TYPE_BY_TITLE_PREFIX: () => GITHUB_ISSUE_TYPE_BY_TITLE_PREFIX,
283
+ ISSUE_TEMPLATES_GENERATED_SUFFIX: () => ISSUE_TEMPLATES_GENERATED_SUFFIX,
280
284
  JsiiFaker: () => JsiiFaker,
281
285
  LAYOUT_ENFORCEMENT: () => LAYOUT_ENFORCEMENT,
282
286
  LAYOUT_ROOT_BY_PROJECT_TYPE: () => LAYOUT_ROOT_BY_PROJECT_TYPE,
@@ -353,6 +357,7 @@ __export(index_exports, {
353
357
  buildIndustryDiscoveryBundle: () => buildIndustryDiscoveryBundle,
354
358
  buildMaintenanceAuditBundle: () => buildMaintenanceAuditBundle,
355
359
  buildMeetingAnalysisBundle: () => buildMeetingAnalysisBundle,
360
+ buildOrchestratorBundle: () => buildOrchestratorBundle,
356
361
  buildOrchestratorConventionsContent: () => buildOrchestratorConventionsContent,
357
362
  buildPeopleProfileBundle: () => buildPeopleProfileBundle,
358
363
  buildPrReviewBundle: () => buildPrReviewBundle,
@@ -372,6 +377,7 @@ __export(index_exports, {
372
377
  checkLinksProcedure: () => checkLinksProcedure,
373
378
  classifyIssueScope: () => classifyIssueScope,
374
379
  classifyRun: () => classifyRun,
380
+ collectIssueTemplateRecipeStubs: () => collectIssueTemplateRecipeStubs,
375
381
  companyProfileBundle: () => companyProfileBundle,
376
382
  compileFencedSamples: () => compileFencedSamples,
377
383
  createApiDiffCheck: () => createApiDiffCheck,
@@ -397,6 +403,8 @@ __export(index_exports, {
397
403
  isScheduledTaskOwnedByExcluded: () => isScheduledTaskOwnedByExcluded,
398
404
  isSuppressedWorkflowRule: () => isSuppressedWorkflowRule,
399
405
  isTypeLabelOwnedByExcluded: () => isTypeLabelOwnedByExcluded,
406
+ issueTemplatesChildGlob: () => issueTemplatesChildGlob,
407
+ issueTemplatesGeneratedPath: () => issueTemplatesGeneratedPath,
400
408
  jestBundle: () => jestBundle,
401
409
  labelsForPhase: () => labelsForPhase,
402
410
  maintenanceAuditBundle: () => maintenanceAuditBundle,
@@ -447,8 +455,10 @@ __export(index_exports, {
447
455
  renderFocusSection: () => renderFocusSection,
448
456
  renderGithubIssueTypeSection: () => renderGithubIssueTypeSection,
449
457
  renderGithubIssueTypeSectionLines: () => renderGithubIssueTypeSectionLines,
458
+ renderIssueTemplateLabelsCheckerScript: () => renderIssueTemplateLabelsCheckerScript,
450
459
  renderIssueTemplatesBundleHook: () => renderIssueTemplatesBundleHook,
451
460
  renderIssueTemplatesCheckerScript: () => renderIssueTemplatesCheckerScript,
461
+ renderIssueTemplatesGeneratedPage: () => renderIssueTemplatesGeneratedPage,
452
462
  renderIssueTemplatesRuleContent: () => renderIssueTemplatesRuleContent,
453
463
  renderIssueTemplatesStarterPage: () => renderIssueTemplatesStarterPage,
454
464
  renderIssueTypeAssignmentBlanket: () => renderIssueTypeAssignmentBlanket,
@@ -2199,12 +2209,111 @@ var awsCdkBundle = {
2199
2209
  ]
2200
2210
  };
2201
2211
 
2212
+ // src/agent/bundles/issue-defaults.ts
2213
+ var VALID_STATUS_VALUES = [
2214
+ "ready",
2215
+ "blocked",
2216
+ "in-progress",
2217
+ "ready-for-review",
2218
+ "needs-attention",
2219
+ "done",
2220
+ "deferred"
2221
+ ];
2222
+ var VALID_PRIORITY_VALUES = [
2223
+ "critical",
2224
+ "high",
2225
+ "medium",
2226
+ "low",
2227
+ "trivial"
2228
+ ];
2229
+ var DEFAULT_ISSUE_STATUS = "ready";
2230
+ var DEFAULT_ISSUE_PRIORITY = "medium";
2231
+ var DEFAULT_RESOLVED_ISSUE_DEFAULTS = {
2232
+ defaults: {
2233
+ status: DEFAULT_ISSUE_STATUS,
2234
+ priority: DEFAULT_ISSUE_PRIORITY
2235
+ },
2236
+ overrides: {}
2237
+ };
2238
+ function resolveIssueDefaults(config) {
2239
+ if (config === void 0) {
2240
+ return DEFAULT_RESOLVED_ISSUE_DEFAULTS;
2241
+ }
2242
+ const overrides = {};
2243
+ for (const [phaseLabel, override] of Object.entries(config)) {
2244
+ assertValidPhaseLabel(phaseLabel);
2245
+ assertValidOverride(phaseLabel, override);
2246
+ overrides[phaseLabel] = {
2247
+ status: override.status ?? DEFAULT_ISSUE_STATUS,
2248
+ priority: override.priority ?? DEFAULT_ISSUE_PRIORITY
2249
+ };
2250
+ }
2251
+ return {
2252
+ defaults: {
2253
+ status: DEFAULT_ISSUE_STATUS,
2254
+ priority: DEFAULT_ISSUE_PRIORITY
2255
+ },
2256
+ overrides
2257
+ };
2258
+ }
2259
+ function validateIssueDefaultsConfig(config) {
2260
+ return resolveIssueDefaults(config);
2261
+ }
2262
+ function labelsForPhase(resolved, phaseLabel) {
2263
+ return resolved.overrides[phaseLabel] ?? resolved.defaults;
2264
+ }
2265
+ function assertValidPhaseLabel(phaseLabel) {
2266
+ if (typeof phaseLabel !== "string" || phaseLabel.trim() === "") {
2267
+ throw new Error(
2268
+ "AgentConfigOptions.issueDefaults: phase-label keys must be non-empty strings (e.g. `people:research`)."
2269
+ );
2270
+ }
2271
+ }
2272
+ function assertValidOverride(phaseLabel, override) {
2273
+ if (override === null || typeof override !== "object" || Array.isArray(override)) {
2274
+ throw new Error(
2275
+ `AgentConfigOptions.issueDefaults["${phaseLabel}"] must be an object with optional \`status\` and \`priority\` fields.`
2276
+ );
2277
+ }
2278
+ const { status, priority } = override;
2279
+ if (status === void 0 && priority === void 0) {
2280
+ throw new Error(
2281
+ `AgentConfigOptions.issueDefaults["${phaseLabel}"] must declare at least one of \`status\` or \`priority\`. Empty entries are rejected because they are almost always a typo on the field name.`
2282
+ );
2283
+ }
2284
+ if (status !== void 0 && !VALID_STATUS_VALUES.includes(status)) {
2285
+ throw new Error(
2286
+ `AgentConfigOptions.issueDefaults["${phaseLabel}"].status="${status}" is not a recognised status value. Allowed values: ${VALID_STATUS_VALUES.join(", ")}.`
2287
+ );
2288
+ }
2289
+ if (priority !== void 0 && !VALID_PRIORITY_VALUES.includes(priority)) {
2290
+ throw new Error(
2291
+ `AgentConfigOptions.issueDefaults["${phaseLabel}"].priority="${priority}" is not a recognised priority value. Allowed values: ${VALID_PRIORITY_VALUES.join(", ")}.`
2292
+ );
2293
+ }
2294
+ }
2295
+
2202
2296
  // src/agent/bundles/issue-templates.ts
2203
2297
  var DEFAULT_ISSUE_TEMPLATES_ENABLED = true;
2204
2298
  var DEFAULT_ISSUE_TEMPLATES_PATH = "docs/src/content/docs/agents/issue-templates.md";
2205
2299
  var DEFAULT_ISSUE_TEMPLATES_BUNDLE_PATH_PATTERNS = [".claude/agents/**/*.md", ".claude/skills/**/*.md"];
2206
2300
  var DEFAULT_ISSUE_TEMPLATES_EMIT_CHECKER = false;
2207
2301
  var DEFAULT_ISSUE_TEMPLATES_EMIT_STARTER = false;
2302
+ var ISSUE_TEMPLATES_GENERATED_SUFFIX = "-generated";
2303
+ function issueTemplatesGeneratedPath(templatesPath) {
2304
+ const slash = templatesPath.lastIndexOf("/");
2305
+ const dot = templatesPath.lastIndexOf(".");
2306
+ if (dot > slash) {
2307
+ return `${templatesPath.slice(0, dot)}${ISSUE_TEMPLATES_GENERATED_SUFFIX}${templatesPath.slice(dot)}`;
2308
+ }
2309
+ return `${templatesPath}${ISSUE_TEMPLATES_GENERATED_SUFFIX}`;
2310
+ }
2311
+ function issueTemplatesChildGlob(templatesPath) {
2312
+ const slash = templatesPath.lastIndexOf("/");
2313
+ const dot = templatesPath.lastIndexOf(".");
2314
+ const stem = dot > slash ? templatesPath.slice(0, dot) : templatesPath;
2315
+ return `${stem}/*.md`;
2316
+ }
2208
2317
  var DEFAULT_ISSUE_TEMPLATES_REQUIRE_REFERENCE = true;
2209
2318
  function resolveIssueTemplates(config) {
2210
2319
  const templatesPath = config?.templatesPath ?? DEFAULT_ISSUE_TEMPLATES_PATH;
@@ -2288,7 +2397,40 @@ function renderIssueTemplatesRuleContent(it, hasDownstreamBundles = true) {
2288
2397
  "issue kind appends one section; retiring a kind removes one. The",
2289
2398
  "label taxonomy, body template shape, and placeholder conventions",
2290
2399
  "are shared across every section.",
2291
- "",
2400
+ ""
2401
+ ];
2402
+ if (it.emitStarterDoc) {
2403
+ lines.push(
2404
+ "## Scaffolded Starting Point",
2405
+ "",
2406
+ "The page is hand-authored, but it does not start empty. Two files",
2407
+ "ship together:",
2408
+ "",
2409
+ `1. \`${it.templatesPath}\` \u2014 **written once**, then yours. Titles,`,
2410
+ " bodies, and the callers list are hand-authored here and never",
2411
+ " overwritten.",
2412
+ `2. \`${issueTemplatesGeneratedPath(it.templatesPath)}\` \u2014`,
2413
+ " **regenerated on every synth**. One",
2414
+ " `## Template: <phase-label>` stub per phase label the active",
2415
+ " bundles contribute to the label registry, with the phase label,",
2416
+ " the `type:<bundle>` label its owning bundle declares, and the",
2417
+ " configured `status:*` / `priority:*` defaults pre-filled.",
2418
+ "",
2419
+ "Only the **label set** is generated. Title and body stay",
2420
+ "angle-bracket placeholders, so the scaffold answers *which labels*",
2421
+ "without dictating *what the issue says*. Copy a stub into the",
2422
+ "hand-authored page, flesh out its body, and leave the label block",
2423
+ "as generated.",
2424
+ "",
2425
+ "The split is load-bearing. A write-once scaffold alone freezes",
2426
+ "every consumer on whatever skeleton shipped the day they adopted",
2427
+ "the convention \u2014 a new phase label minted upstream never reaches",
2428
+ "them. Regenerating the label half fixes that without ever",
2429
+ "clobbering an authored body.",
2430
+ ""
2431
+ );
2432
+ }
2433
+ lines.push(
2292
2434
  "## Reference-Don't-Inline Rule",
2293
2435
  "",
2294
2436
  `Every bundle rule, agent prompt, or skill instruction that files a`,
@@ -2359,8 +2501,12 @@ function renderIssueTemplatesRuleContent(it, hasDownstreamBundles = true) {
2359
2501
  " placeholders (`<Organization Name>`, `<slug>`) that match",
2360
2502
  " existing templates. Consistency across sections lets callers",
2361
2503
  " substitute without re-reading the body every time.",
2504
+ "5. **Does the recipe assign a GitHub issue type?** `gh issue",
2505
+ " create` cannot set it and the `type:*` label does not either, so",
2506
+ " every section ends with the follow-up call. An issue that skips",
2507
+ " it stays untyped forever.",
2362
2508
  ""
2363
- ];
2509
+ );
2364
2510
  if (it.emitChecker) {
2365
2511
  lines.push(
2366
2512
  "## Automated Lint",
@@ -2391,6 +2537,33 @@ function renderIssueTemplatesRuleContent(it, hasDownstreamBundles = true) {
2391
2537
  "(which documents the conventional `gh issue create` call shape",
2392
2538
  "as taxonomy, not a downstream template). All other bundle",
2393
2539
  "files in the pattern set are in scope.",
2540
+ "",
2541
+ "### Recipe label correctness",
2542
+ "",
2543
+ "A companion lint,",
2544
+ "`.claude/procedures/check-issue-template-labels.sh`, checks the",
2545
+ "recipes themselves rather than where they live. It walks the",
2546
+ "templates page, its router-style child pages, and the generated",
2547
+ "label-set companion, and fails non-zero when a",
2548
+ "`## Template: <phase-label>` section:",
2549
+ "",
2550
+ "- never passes `--label <phase-label>` \u2014 the heading and the",
2551
+ " command disagree;",
2552
+ "- carries no `type:*` label, more than one, or one that is not",
2553
+ " the `type:<bundle>` label the phase label's owning bundle",
2554
+ " declares;",
2555
+ "- has no GitHub issue-type assignment step.",
2556
+ "",
2557
+ "```bash",
2558
+ ".claude/procedures/check-issue-template-labels.sh",
2559
+ "```",
2560
+ "",
2561
+ "The phase-label \u2192 type-label pairing is generated from the same",
2562
+ "bundle-ownership map that drives `.github/labels.yml` and the",
2563
+ "orchestrator's invariant sweep, so a hand-edit can never",
2564
+ "reintroduce a mismatch the registry has already ruled out.",
2565
+ "Sections whose heading matches no bundle-owned phase label are",
2566
+ "skipped \u2014 consumer-specific labels are deliberately not policed.",
2394
2567
  ""
2395
2568
  );
2396
2569
  }
@@ -2399,8 +2572,10 @@ function renderIssueTemplatesRuleContent(it, hasDownstreamBundles = true) {
2399
2572
  "",
2400
2573
  "The convention intentionally does not:",
2401
2574
  "",
2402
- "- Auto-generate the templates page \u2014 repo-specific phase labels",
2403
- " and body conventions are too varied for a one-size template.",
2575
+ "- Auto-generate recipe **bodies**. Only the label set is",
2576
+ " scaffolded, because labels are derivable from the bundle",
2577
+ " registry and bodies are not \u2014 repo-specific objectives,",
2578
+ " acceptance criteria, and callers lists stay hand-authored.",
2404
2579
  "- Prescribe a fixed body shape across templates. Each section",
2405
2580
  " may carry its own `## Acceptance Criteria`, `## Scope Size`,",
2406
2581
  " or `## Output Path` fields as the downstream agent requires;",
@@ -2431,7 +2606,8 @@ function renderIssueTemplatesBundleHook(it, bundleLabel) {
2431
2606
  "as an inline template vs. a prose mention."
2432
2607
  ].join("\n");
2433
2608
  }
2434
- function renderIssueTemplatesStarterPage(_it) {
2609
+ function renderIssueTemplatesStarterPage(it) {
2610
+ const generatedLink = `./${basename(issueTemplatesGeneratedPath(it.templatesPath))}`;
2435
2611
  return [
2436
2612
  "---",
2437
2613
  "title: Issue Templates",
@@ -2466,6 +2642,20 @@ function renderIssueTemplatesStarterPage(_it) {
2466
2642
  "bundle's rule \u2014 the issue-templates page is a recipe catalogue,",
2467
2643
  "not a duplicate-check layer.",
2468
2644
  "",
2645
+ "## Generated label sets",
2646
+ "",
2647
+ `[Generated Issue-Template Label Sets](${generatedLink}) carries one`,
2648
+ "correct-by-construction stub per phase label the active bundles",
2649
+ "emit \u2014 the phase label paired with the `type:<bundle>` label its",
2650
+ "owning bundle declares, plus the configured `status:*` /",
2651
+ "`priority:*` defaults. That page regenerates on every `projen` run,",
2652
+ "so it never goes stale as phase labels come and go.",
2653
+ "",
2654
+ "This page is the opposite: written once, then owned by you. Copy a",
2655
+ "stub across, flesh out its title and body here, and leave the label",
2656
+ "block exactly as generated \u2014 the label-consistency lint holds both",
2657
+ "copies to the same pairing.",
2658
+ "",
2469
2659
  "## Template: example-phase",
2470
2660
  "",
2471
2661
  "Replace this example with the real templates for this repo. Each",
@@ -2505,6 +2695,115 @@ function renderIssueTemplatesStarterPage(_it) {
2505
2695
  "```"
2506
2696
  ].join("\n");
2507
2697
  }
2698
+ function collectIssueTemplateRecipeStubs(bundles, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS) {
2699
+ const stubs = /* @__PURE__ */ new Map();
2700
+ for (const bundle of bundles) {
2701
+ for (const label of bundle.labels ?? []) {
2702
+ const typeLabel = typeLabelForPhaseLabel(label.name);
2703
+ if (typeLabel === void 0) {
2704
+ continue;
2705
+ }
2706
+ if (stubs.has(label.name)) {
2707
+ continue;
2708
+ }
2709
+ const defaults = labelsForPhase(issueDefaults, label.name);
2710
+ stubs.set(label.name, {
2711
+ phaseLabel: label.name,
2712
+ typeLabel,
2713
+ bundleName: bundle.name,
2714
+ description: label.description ?? "",
2715
+ status: defaults.status,
2716
+ priority: defaults.priority,
2717
+ issueType: githubIssueTypeForTitle(`${label.name}: placeholder`)
2718
+ });
2719
+ }
2720
+ }
2721
+ return [...stubs.values()].sort(
2722
+ (a, b) => a.phaseLabel.localeCompare(b.phaseLabel)
2723
+ );
2724
+ }
2725
+ function renderIssueTemplatesGeneratedPage(it, stubs) {
2726
+ const lines = [
2727
+ "---",
2728
+ "title: Generated Issue-Template Label Sets",
2729
+ "description: Auto-generated label-correct gh issue create stubs \u2014 one per phase label.",
2730
+ "---",
2731
+ "",
2732
+ "**Generated file \u2014 do not edit.** Every section below is",
2733
+ "regenerated from the active bundle set on each `projen` run; hand",
2734
+ "edits are overwritten. The hand-authored recipes live in",
2735
+ `[the issue-templates page](./${basename(it.templatesPath)}) \u2014 this`,
2736
+ "page exists so the **label half** of every recipe stays",
2737
+ "correct-by-construction as phase labels come and go.",
2738
+ "",
2739
+ "## How to use",
2740
+ "",
2741
+ "Each section carries the exact label set the phase-label invariant",
2742
+ "requires: the phase label itself, the `type:<bundle>` label its",
2743
+ "owning bundle declares, and the configured `status:*` / `priority:*`",
2744
+ "defaults for that phase. Title and body are placeholders \u2014 copy the",
2745
+ "stub into the hand-authored templates page and flesh out the body",
2746
+ "there, or run it as-is when no repo-specific body exists yet.",
2747
+ "",
2748
+ "Every stub ends with the **GitHub issue type** assignment. `gh issue",
2749
+ "create` cannot set the issue type and the `type:*` label does not",
2750
+ "set it either, so an issue that skips the follow-up call stays",
2751
+ "untyped forever. See **Assigning the GitHub issue type** in the",
2752
+ "`issue-conventions` rule for the no-helper fallback.",
2753
+ ""
2754
+ ];
2755
+ if (stubs.length === 0) {
2756
+ lines.push(
2757
+ "## No phase labels",
2758
+ "",
2759
+ "No active bundle contributes a phase label, so there is nothing to",
2760
+ "scaffold. Enable a phased-pipeline bundle (or drop it from",
2761
+ "`excludeBundles`) and re-run `projen`."
2762
+ );
2763
+ return lines.join("\n");
2764
+ }
2765
+ for (const stub of stubs) {
2766
+ lines.push(
2767
+ `## Template: ${stub.phaseLabel}`,
2768
+ "",
2769
+ ...stub.description === "" ? [] : [stub.description, ""],
2770
+ `**Owning bundle:** \`${stub.bundleName}\` \u2014 **required type label:**`,
2771
+ `\`${stub.typeLabel}\` (paired by the bundle-ownership map).`,
2772
+ "",
2773
+ "**Callers:** <name the bundles and agents that file this issue>",
2774
+ "",
2775
+ "```bash",
2776
+ "gh issue create \\",
2777
+ ` --title "${stub.phaseLabel}: <short description>" \\`,
2778
+ ` --label "${stub.typeLabel}" \\`,
2779
+ ` --label "${stub.phaseLabel}" \\`,
2780
+ ` --label "priority:${stub.priority}" \\`,
2781
+ ` --label "status:${stub.status}" \\`,
2782
+ ' --body "## Objective',
2783
+ "",
2784
+ "<1-3 sentences describing the work>.",
2785
+ "",
2786
+ "## Context",
2787
+ "",
2788
+ "- **Discovered in:** <source description> (#<parent-issue>)",
2789
+ "",
2790
+ "## Acceptance Criteria",
2791
+ "",
2792
+ "- [ ] <criterion 1>",
2793
+ '"',
2794
+ "```",
2795
+ "",
2796
+ `Then assign the GitHub issue type (\`${stub.issueType}\`):`,
2797
+ "",
2798
+ "```bash",
2799
+ `${SET_ISSUE_TYPE_HELPER_PATH} <issue-number> ${stub.issueType}`,
2800
+ "```",
2801
+ ""
2802
+ );
2803
+ }
2804
+ lines.pop();
2805
+ return lines.join("\n");
2806
+ }
2508
2807
  function renderIssueTemplatesCheckerScript(it) {
2509
2808
  const patternsLiteral = it.bundlePathPatterns.map((p) => ` ${JSON.stringify(p)}`).join("\n");
2510
2809
  return [
@@ -2693,6 +2992,157 @@ function renderIssueTemplatesCheckerScript(it) {
2693
2992
  "exit 0"
2694
2993
  ].join("\n");
2695
2994
  }
2995
+ function renderIssueTemplateLabelsCheckerScript(it) {
2996
+ const generatedPath = issueTemplatesGeneratedPath(it.templatesPath);
2997
+ const childGlob = issueTemplatesChildGlob(it.templatesPath);
2998
+ return [
2999
+ "#!/usr/bin/env bash",
3000
+ "# check-issue-template-labels.sh \u2014 Enforce recipe label correctness.",
3001
+ "#",
3002
+ "# Usage:",
3003
+ "# .claude/procedures/check-issue-template-labels.sh [<file>...]",
3004
+ "#",
3005
+ "# With no arguments the lint walks the templates page, its",
3006
+ "# router-style child pages, and the generated label-set companion.",
3007
+ "# Positional arguments override that default set.",
3008
+ "#",
3009
+ "# For every `## Template: <phase-label>` section it asserts the",
3010
+ "# recipe passes the heading's phase label, carries exactly one",
3011
+ "# `type:*` label matching the phase-label invariant, and assigns a",
3012
+ "# GitHub issue type. Sections whose heading matches no bundle-owned",
3013
+ "# phase label are skipped \u2014 unrecognised labels are consumer-",
3014
+ "# specific and deliberately not policed.",
3015
+ "#",
3016
+ "# The resolver below is generated from the canonical bundle-",
3017
+ "# ownership map \u2014 do not edit by hand; regenerate via",
3018
+ "# `pnpm exec projen`.",
3019
+ "",
3020
+ "set -uo pipefail",
3021
+ "",
3022
+ "err() {",
3023
+ ' printf "check-issue-template-labels.sh: %s\\n" "$*" >&2',
3024
+ "}",
3025
+ "",
3026
+ renderPhaseTypeInvariantShellHelpers(),
3027
+ "",
3028
+ `templates_path=${JSON.stringify(it.templatesPath)}`,
3029
+ `generated_path=${JSON.stringify(generatedPath)}`,
3030
+ `child_glob=${JSON.stringify(childGlob)}`,
3031
+ "",
3032
+ "# Emit one TAB-separated record per `## Template:` section:",
3033
+ "# <file>\\t<line>\\t<phase-label>\\t<comma-joined labels>\\t<0|1 typed>",
3034
+ "# Quote characters are folded to spaces up front so a recipe written",
3035
+ "# with single quotes, double quotes, or none at all parses the same.",
3036
+ "# Fenced blocks are tracked so the `## Objective` heading inside a",
3037
+ "# recipe's --body string never reads as the end of the section.",
3038
+ "scan_file() {",
3039
+ ' local file="$1"',
3040
+ ` tr '\\042\\047' ' ' < "$file" | awk -v FNAME="$file" '`,
3041
+ " function flush() {",
3042
+ ' if (phase != "") {',
3043
+ ' printf "%s\\t%d\\t%s\\t%s\\t%d\\n", FNAME, start, phase, labels, hastype;',
3044
+ " }",
3045
+ ' phase = ""; labels = ""; hastype = 0;',
3046
+ " }",
3047
+ " {",
3048
+ " if ($0 ~ /^[ \\t]*```/) {",
3049
+ " fence = 1 - fence;",
3050
+ ' } else if (fence == 0 && index($0, "## ") == 1) {',
3051
+ " flush();",
3052
+ ' if (index($0, "## Template: ") == 1) {',
3053
+ " phase = substr($0, 14);",
3054
+ ' gsub(/[ \\t`]/, "", phase);',
3055
+ " start = NR;",
3056
+ " }",
3057
+ " next;",
3058
+ " }",
3059
+ ' if (phase == "") next;',
3060
+ " if ($0 ~ /set-issue-type\\.sh/ || $0 ~ /updateIssueIssueType/) {",
3061
+ " hastype = 1;",
3062
+ " }",
3063
+ " line = $0;",
3064
+ " while (match(line, /--label[ \\t]+[^ \\t]+/)) {",
3065
+ " tok = substr(line, RSTART, RLENGTH);",
3066
+ " line = substr(line, RSTART + RLENGTH);",
3067
+ ' sub(/^--label[ \\t]+/, "", tok);',
3068
+ ' gsub(/[\\\\`]/, "", tok);',
3069
+ ' labels = labels tok ",";',
3070
+ " }",
3071
+ " }",
3072
+ " END { flush(); }",
3073
+ " '",
3074
+ "}",
3075
+ "",
3076
+ "# Collect the file list: positional args override the default set.",
3077
+ "files=()",
3078
+ "if [[ $# -gt 0 ]]; then",
3079
+ ' files=("$@")',
3080
+ "else",
3081
+ ' [[ -f "$templates_path" ]] && files+=("$templates_path")',
3082
+ ' [[ -f "$generated_path" ]] && files+=("$generated_path")',
3083
+ " for child in $child_glob; do",
3084
+ ' [[ -f "$child" ]] && files+=("$child")',
3085
+ " done",
3086
+ "fi",
3087
+ "",
3088
+ "if [[ ${#files[@]} -eq 0 ]]; then",
3089
+ " exit 0",
3090
+ "fi",
3091
+ "",
3092
+ "violations=()",
3093
+ "checked=0",
3094
+ "",
3095
+ 'for file in "${files[@]}"; do',
3096
+ ' [[ -f "$file" ]] || continue',
3097
+ " while IFS=$'\\t' read -r rec_file rec_line phase labels hastype; do",
3098
+ ' [[ -z "$phase" ]] && continue',
3099
+ ' required=$(phase_label_type_of "$phase")',
3100
+ ' [[ -z "$required" ]] && continue',
3101
+ " checked=$(( checked + 1 ))",
3102
+ ' case ",${labels}" in',
3103
+ ' *",${phase},"*) ;;',
3104
+ ` *) violations+=("$rec_file:$rec_line: recipe for '$phase' never passes --label $phase") ;;`,
3105
+ " esac",
3106
+ ' type_labels=""',
3107
+ " type_count=0",
3108
+ ' for entry in $(printf "%s" "$labels" | tr , " "); do',
3109
+ ' case "$entry" in',
3110
+ " type:*)",
3111
+ ' type_labels="${type_labels}${entry} "',
3112
+ " type_count=$(( type_count + 1 ))",
3113
+ " ;;",
3114
+ " esac",
3115
+ " done",
3116
+ ' if [[ "$type_count" -eq 0 ]]; then',
3117
+ ` violations+=("$rec_file:$rec_line: recipe for '$phase' carries no type:* label (expected $required)")`,
3118
+ ' elif [[ "$type_count" -gt 1 ]]; then',
3119
+ " violations+=(\"$rec_file:$rec_line: recipe for '$phase' carries ${type_count} type:* labels (${type_labels% }); exactly one is allowed\")",
3120
+ ' elif [[ "${type_labels% }" != "$required" ]]; then',
3121
+ ` violations+=("$rec_file:$rec_line: recipe for '$phase' carries \${type_labels% } but the phase label requires $required")`,
3122
+ " fi",
3123
+ ' if [[ "$hastype" != "1" ]]; then',
3124
+ ` violations+=("$rec_file:$rec_line: recipe for '$phase' has no GitHub issue-type assignment step")`,
3125
+ " fi",
3126
+ ' done < <(scan_file "$file")',
3127
+ "done",
3128
+ "",
3129
+ "if [[ ${#violations[@]} -gt 0 ]]; then",
3130
+ ' err "issue-template recipes disagree with the phase-label invariant:"',
3131
+ ' for v in "${violations[@]}"; do',
3132
+ ' err " - $v"',
3133
+ " done",
3134
+ ` err "each '## Template: <phase-label>' recipe must pass its own phase label, exactly one matching type:<bundle> label, and a set-issue-type.sh (or updateIssueIssueType) step"`,
3135
+ " exit 1",
3136
+ "fi",
3137
+ "",
3138
+ 'printf "check-issue-template-labels.sh: %d recipe(s) OK\\n" "$checked"',
3139
+ "exit 0"
3140
+ ].join("\n");
3141
+ }
3142
+ function basename(path8) {
3143
+ const slash = path8.lastIndexOf("/");
3144
+ return slash === -1 ? path8 : path8.slice(slash + 1);
3145
+ }
2696
3146
  function assertValidTemplatesPath(value) {
2697
3147
  if (typeof value !== "string" || value.trim().length === 0) {
2698
3148
  throw new Error(
@@ -4956,7 +5406,15 @@ var createRuleSkill = {
4956
5406
  "- Use `ruleExtensions` to add project-specific content to existing bundle rules instead of replacing them"
4957
5407
  ].join("\n")
4958
5408
  };
4959
- function buildBaseBundle(paths = DEFAULT_AGENT_PATHS) {
5409
+ var DEFAULT_BASE_CONVENTIONS = {
5410
+ progressFiles: resolveProgressFiles(),
5411
+ sharedEditing: resolveSharedEditing(),
5412
+ temporalFraming: resolveTemporalFraming(),
5413
+ skillEvals: resolveSkillEvals(),
5414
+ issueTemplates: resolveIssueTemplates(),
5415
+ hasDownstreamIssueKindBundles: true
5416
+ };
5417
+ function buildBaseBundle(paths = DEFAULT_AGENT_PATHS, conventions = DEFAULT_BASE_CONVENTIONS) {
4960
5418
  return {
4961
5419
  name: "base",
4962
5420
  description: "Core rules: project overview, interaction style, and general coding conventions",
@@ -5625,7 +6083,7 @@ function buildBaseBundle(paths = DEFAULT_AGENT_PATHS) {
5625
6083
  // them via `agentConfig.additionalRulePaths`; other consumers
5626
6084
  // get a clean default.
5627
6085
  filePatterns: [".claude/agents/*.md", ".claude/procedures/**"],
5628
- content: renderProgressFilesRuleContent(resolveProgressFiles()),
6086
+ content: renderProgressFilesRuleContent(conventions.progressFiles),
5629
6087
  platforms: {
5630
6088
  cursor: { exclude: true }
5631
6089
  },
@@ -5636,7 +6094,7 @@ function buildBaseBundle(paths = DEFAULT_AGENT_PATHS) {
5636
6094
  description: "Shared-editing safety: single-entry deterministic-sort inserts on index files, commit-path verification, and the merge-conflict resolution recipe that keeps concurrent agent sessions from dropping each other's rows on shared registries and feature matrices.",
5637
6095
  scope: AGENT_RULE_SCOPE.FILE_PATTERN,
5638
6096
  filePatterns: DEFAULT_SHARED_INDEX_PATHS,
5639
- content: renderSharedEditingRuleContent(resolveSharedEditing()),
6097
+ content: renderSharedEditingRuleContent(conventions.sharedEditing),
5640
6098
  platforms: {
5641
6099
  cursor: { exclude: true }
5642
6100
  },
@@ -5654,7 +6112,7 @@ function buildBaseBundle(paths = DEFAULT_AGENT_PATHS) {
5654
6112
  "docs/src/content/docs/standards-research/**/*.md",
5655
6113
  "docs/src/content/docs/customer-research/**/*.md"
5656
6114
  ],
5657
- content: renderTemporalFramingRuleContent(resolveTemporalFraming()),
6115
+ content: renderTemporalFramingRuleContent(conventions.temporalFraming),
5658
6116
  platforms: {
5659
6117
  cursor: { exclude: true }
5660
6118
  },
@@ -5667,7 +6125,7 @@ function buildBaseBundle(paths = DEFAULT_AGENT_PATHS) {
5667
6125
  // Bundle defaults exclude paths into configulator's own
5668
6126
  // source — see comment on progress-file-convention above.
5669
6127
  filePatterns: [".claude/skills/**"],
5670
- content: renderSkillEvalsRuleContent(resolveSkillEvals()),
6128
+ content: renderSkillEvalsRuleContent(conventions.skillEvals),
5671
6129
  platforms: {
5672
6130
  cursor: { exclude: true }
5673
6131
  },
@@ -5684,7 +6142,10 @@ function buildBaseBundle(paths = DEFAULT_AGENT_PATHS) {
5684
6142
  ".claude/agents/*.md",
5685
6143
  `${paths.docsRoot}/agents/issue-templates.md`
5686
6144
  ],
5687
- content: renderIssueTemplatesRuleContent(resolveIssueTemplates()),
6145
+ content: renderIssueTemplatesRuleContent(
6146
+ conventions.issueTemplates,
6147
+ conventions.hasDownstreamIssueKindBundles
6148
+ ),
5688
6149
  platforms: {
5689
6150
  cursor: { exclude: true }
5690
6151
  },
@@ -5695,90 +6156,6 @@ function buildBaseBundle(paths = DEFAULT_AGENT_PATHS) {
5695
6156
  }
5696
6157
  var baseBundle = buildBaseBundle();
5697
6158
 
5698
- // src/agent/bundles/issue-defaults.ts
5699
- var VALID_STATUS_VALUES = [
5700
- "ready",
5701
- "blocked",
5702
- "in-progress",
5703
- "ready-for-review",
5704
- "needs-attention",
5705
- "done",
5706
- "deferred"
5707
- ];
5708
- var VALID_PRIORITY_VALUES = [
5709
- "critical",
5710
- "high",
5711
- "medium",
5712
- "low",
5713
- "trivial"
5714
- ];
5715
- var DEFAULT_ISSUE_STATUS = "ready";
5716
- var DEFAULT_ISSUE_PRIORITY = "medium";
5717
- var DEFAULT_RESOLVED_ISSUE_DEFAULTS = {
5718
- defaults: {
5719
- status: DEFAULT_ISSUE_STATUS,
5720
- priority: DEFAULT_ISSUE_PRIORITY
5721
- },
5722
- overrides: {}
5723
- };
5724
- function resolveIssueDefaults(config) {
5725
- if (config === void 0) {
5726
- return DEFAULT_RESOLVED_ISSUE_DEFAULTS;
5727
- }
5728
- const overrides = {};
5729
- for (const [phaseLabel, override] of Object.entries(config)) {
5730
- assertValidPhaseLabel(phaseLabel);
5731
- assertValidOverride(phaseLabel, override);
5732
- overrides[phaseLabel] = {
5733
- status: override.status ?? DEFAULT_ISSUE_STATUS,
5734
- priority: override.priority ?? DEFAULT_ISSUE_PRIORITY
5735
- };
5736
- }
5737
- return {
5738
- defaults: {
5739
- status: DEFAULT_ISSUE_STATUS,
5740
- priority: DEFAULT_ISSUE_PRIORITY
5741
- },
5742
- overrides
5743
- };
5744
- }
5745
- function validateIssueDefaultsConfig(config) {
5746
- return resolveIssueDefaults(config);
5747
- }
5748
- function labelsForPhase(resolved, phaseLabel) {
5749
- return resolved.overrides[phaseLabel] ?? resolved.defaults;
5750
- }
5751
- function assertValidPhaseLabel(phaseLabel) {
5752
- if (typeof phaseLabel !== "string" || phaseLabel.trim() === "") {
5753
- throw new Error(
5754
- "AgentConfigOptions.issueDefaults: phase-label keys must be non-empty strings (e.g. `people:research`)."
5755
- );
5756
- }
5757
- }
5758
- function assertValidOverride(phaseLabel, override) {
5759
- if (override === null || typeof override !== "object" || Array.isArray(override)) {
5760
- throw new Error(
5761
- `AgentConfigOptions.issueDefaults["${phaseLabel}"] must be an object with optional \`status\` and \`priority\` fields.`
5762
- );
5763
- }
5764
- const { status, priority } = override;
5765
- if (status === void 0 && priority === void 0) {
5766
- throw new Error(
5767
- `AgentConfigOptions.issueDefaults["${phaseLabel}"] must declare at least one of \`status\` or \`priority\`. Empty entries are rejected because they are almost always a typo on the field name.`
5768
- );
5769
- }
5770
- if (status !== void 0 && !VALID_STATUS_VALUES.includes(status)) {
5771
- throw new Error(
5772
- `AgentConfigOptions.issueDefaults["${phaseLabel}"].status="${status}" is not a recognised status value. Allowed values: ${VALID_STATUS_VALUES.join(", ")}.`
5773
- );
5774
- }
5775
- if (priority !== void 0 && !VALID_PRIORITY_VALUES.includes(priority)) {
5776
- throw new Error(
5777
- `AgentConfigOptions.issueDefaults["${phaseLabel}"].priority="${priority}" is not a recognised priority value. Allowed values: ${VALID_PRIORITY_VALUES.join(", ")}.`
5778
- );
5779
- }
5780
- }
5781
-
5782
6159
  // src/agent/bundles/bcm-writer.ts
5783
6160
  function buildBcmWriterSubAgent(paths, issueDefaults) {
5784
6161
  return {
@@ -19469,37 +19846,7 @@ var scanCommand = {
19469
19846
  ""
19470
19847
  ].join("\n")
19471
19848
  };
19472
- var orchestratorBundle = {
19473
- name: "orchestrator",
19474
- description: "Pipeline orchestrator agent for issue triage, PR review, and queue management",
19475
- // Always included by default
19476
- appliesWhen: () => true,
19477
- rules: [
19478
- {
19479
- name: "orchestrator-conventions",
19480
- description: "Guidelines for orchestrator agent behavior and pipeline management, including the funnel-tier dispatch sort, scope gate, and per-agent scheduled-task layout",
19481
- scope: AGENT_RULE_SCOPE.FILE_PATTERN,
19482
- // Bundle defaults exclude paths into configulator's own source
19483
- // (only meaningful when configulator is a workspace package).
19484
- // codedrifters/packages restores them via
19485
- // `agentConfig.additionalRulePaths`.
19486
- filePatterns: [
19487
- ".claude/agents/orchestrator.md",
19488
- ".claude/scheduled-tasks/**"
19489
- ],
19490
- content: buildOrchestratorConventionsContent(
19491
- DEFAULT_AGENT_TIERS,
19492
- resolveScopeGate(),
19493
- resolveRunRatio(),
19494
- resolveScheduledTasks(),
19495
- resolveUnblockDependents()
19496
- ),
19497
- platforms: {
19498
- cursor: { exclude: true }
19499
- },
19500
- tags: ["workflow"]
19501
- }
19502
- ],
19849
+ var ORCHESTRATOR_BUNDLE_STATIC = {
19503
19850
  subAgents: [orchestratorSubAgent, issueWorkerSubAgent],
19504
19851
  procedures: [
19505
19852
  checkBlockedProcedure,
@@ -19515,6 +19862,51 @@ var orchestratorBundle = {
19515
19862
  ]
19516
19863
  }
19517
19864
  };
19865
+ var DEFAULT_ORCHESTRATOR_CONVENTIONS = {
19866
+ tiers: DEFAULT_AGENT_TIERS,
19867
+ scopeGate: resolveScopeGate(),
19868
+ runRatio: resolveRunRatio(),
19869
+ scheduledTasks: resolveScheduledTasks(),
19870
+ unblockDependents: resolveUnblockDependents(),
19871
+ excludeBundles: []
19872
+ };
19873
+ function buildOrchestratorBundle(conventions = DEFAULT_ORCHESTRATOR_CONVENTIONS) {
19874
+ return {
19875
+ name: "orchestrator",
19876
+ description: "Pipeline orchestrator agent for issue triage, PR review, and queue management",
19877
+ // Always included by default
19878
+ appliesWhen: () => true,
19879
+ rules: [
19880
+ {
19881
+ name: "orchestrator-conventions",
19882
+ description: "Guidelines for orchestrator agent behavior and pipeline management, including the funnel-tier dispatch sort, scope gate, and per-agent scheduled-task layout",
19883
+ scope: AGENT_RULE_SCOPE.FILE_PATTERN,
19884
+ // Bundle defaults exclude paths into configulator's own source
19885
+ // (only meaningful when configulator is a workspace package).
19886
+ // codedrifters/packages restores them via
19887
+ // `agentConfig.additionalRulePaths`.
19888
+ filePatterns: [
19889
+ ".claude/agents/orchestrator.md",
19890
+ ".claude/scheduled-tasks/**"
19891
+ ],
19892
+ content: buildOrchestratorConventionsContent(
19893
+ conventions.tiers,
19894
+ conventions.scopeGate,
19895
+ conventions.runRatio,
19896
+ conventions.scheduledTasks,
19897
+ conventions.unblockDependents,
19898
+ conventions.excludeBundles
19899
+ ),
19900
+ platforms: {
19901
+ cursor: { exclude: true }
19902
+ },
19903
+ tags: ["workflow"]
19904
+ }
19905
+ ],
19906
+ ...ORCHESTRATOR_BUNDLE_STATIC
19907
+ };
19908
+ }
19909
+ var orchestratorBundle = buildOrchestratorBundle();
19518
19910
 
19519
19911
  // src/agent/bundles/people-profile.ts
19520
19912
  function buildPeopleProfileAnalystSubAgent(paths, issueDefaults, tier) {
@@ -33025,6 +33417,8 @@ var UPSTREAM_CONFIGULATOR_DOCS_DETAIL = [
33025
33417
  " --body $'## Summary\\n\\nThe `software-profile-analyst` Phase 2 prompt does not capture the vendor pricing tier (free / paid / enterprise), which downstream consumers need for segment matching.\\n\\n## Details\\n\\n- Add a `Pricing tier` field to the profile template at `docs/src/content/docs/agents/software-profile-analyst.md`\\n- Update the Phase 2 instructions to require the field before commit\\n- Add a regression test in `bundles.test.ts`'",
33026
33418
  "```",
33027
33419
  "",
33420
+ "The call above does **not** set the GitHub issue type \u2014 `gh issue create` cannot. Assign it immediately, in the same session, with the cross-repo GraphQL flow above (`feat:` title \u2192 **Feature**; every other prefix except `epic:` / `fix:` \u2192 **Task**). Skipping it leaves the upstream issue untyped, and nobody comes back for it later.",
33421
+ "",
33028
33422
  "After filing, reference the upstream issue from any local workaround so it can be reverted once the upstream fix lands:",
33029
33423
  "",
33030
33424
  "```typescript",
@@ -33986,10 +34380,14 @@ function renderPriorityRulesSection(rules) {
33986
34380
  }
33987
34381
 
33988
34382
  // src/agent/bundles/index.ts
33989
- function buildBuiltInBundles(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS, defaultAgentTier = AGENT_MODEL.BALANCED, bundleAgentTiers = /* @__PURE__ */ new Map(), prReviewPolicy = resolvePrReviewPolicy(), buildPolicy = DEFAULT_BUILD_POLICY) {
34383
+ var DEFAULT_RULE_CONVENTIONS = {
34384
+ base: DEFAULT_BASE_CONVENTIONS,
34385
+ orchestrator: DEFAULT_ORCHESTRATOR_CONVENTIONS
34386
+ };
34387
+ function buildBuiltInBundles(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS, defaultAgentTier = AGENT_MODEL.BALANCED, bundleAgentTiers = /* @__PURE__ */ new Map(), prReviewPolicy = resolvePrReviewPolicy(), buildPolicy = DEFAULT_BUILD_POLICY, conventions = DEFAULT_RULE_CONVENTIONS) {
33990
34388
  const tierFor = (bundle) => bundleAgentTiers.get(bundle) ?? defaultAgentTier;
33991
34389
  return [
33992
- buildBaseBundle(paths),
34390
+ buildBaseBundle(paths, conventions.base),
33993
34391
  upstreamConfigulatorDocsBundle,
33994
34392
  typescriptBundle,
33995
34393
  vitestBundle,
@@ -34002,7 +34400,7 @@ function buildBuiltInBundles(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAUL
34002
34400
  slackBundle,
34003
34401
  buildMeetingAnalysisBundle(tierFor("meeting-analysis")),
34004
34402
  agendaBundle,
34005
- orchestratorBundle,
34403
+ buildOrchestratorBundle(conventions.orchestrator),
34006
34404
  buildPrReviewBundle(prReviewPolicy),
34007
34405
  buildRequirementsAnalystBundle(paths, issueDefaults),
34008
34406
  buildRequirementsWriterBundle(paths, issueDefaults),
@@ -35090,6 +35488,13 @@ var AgentConfig = class _AgentConfig extends import_projen8.Component {
35090
35488
  * credential requirement when a remote cache actually exists. The
35091
35489
  * getter is lazy by design — `TurboRepo` must already be attached
35092
35490
  * to the project when the bundles are first read.
35491
+ *
35492
+ * Every **config-driven convention rule** is likewise resolved here
35493
+ * and seeded into its owning bundle, so the rule enters the rule map
35494
+ * already carrying the consumer's settings. Rewriting those rules
35495
+ * after the map was assembled — the previous approach — silently
35496
+ * discarded any `ruleExtensions` append or same-name `rules`
35497
+ * override that had already been merged in.
35093
35498
  */
35094
35499
  get pathAwareBundles() {
35095
35500
  if (!this.cachedBundles) {
@@ -35099,11 +35504,52 @@ var AgentConfig = class _AgentConfig extends import_projen8.Component {
35099
35504
  resolveDefaultAgentTier(this.options),
35100
35505
  resolveBundleAgentTiers(this.options),
35101
35506
  resolvePrReviewPolicy(this.options.prReviewPolicy),
35102
- resolveBuildPolicy(this.project)
35507
+ resolveBuildPolicy(this.project),
35508
+ this.resolvedRuleConventions
35103
35509
  );
35104
35510
  }
35105
35511
  return this.cachedBundles;
35106
35512
  }
35513
+ /**
35514
+ * Resolved settings for every config-driven convention rule, derived
35515
+ * from this project's options. Consumed by `buildBuiltInBundles` so
35516
+ * the `base` and `orchestrator` bundles seed final rule content.
35517
+ *
35518
+ * `excludeBundles` feeds two of these: the orchestrator's rendered
35519
+ * tier table / scope-gate overrides / scheduled-tasks registry drop
35520
+ * rows owned by excluded bundles, and the issue-templates rule falls
35521
+ * back to its disabled stub once every downstream-issue-kind bundle
35522
+ * has been excluded.
35523
+ */
35524
+ get resolvedRuleConventions() {
35525
+ const excludeBundles = this.options.excludeBundles ?? [];
35526
+ const orchestratorAssets = resolveOrchestratorAssets(
35527
+ this.options.tiers,
35528
+ this.options.scopeGate,
35529
+ this.options.runRatio,
35530
+ this.options.scheduledTasks,
35531
+ this.options.unblockDependents,
35532
+ excludeBundles
35533
+ );
35534
+ return {
35535
+ base: {
35536
+ progressFiles: resolveProgressFiles(this.options.progressFiles),
35537
+ sharedEditing: resolveSharedEditing(this.options.sharedEditing),
35538
+ temporalFraming: resolveTemporalFraming(this.options.temporalFraming),
35539
+ skillEvals: resolveSkillEvals(this.options.skillEvals),
35540
+ issueTemplates: resolveIssueTemplates(this.options.issueTemplates),
35541
+ hasDownstreamIssueKindBundles: hasAnyDownstreamIssueKindBundle(excludeBundles)
35542
+ },
35543
+ orchestrator: {
35544
+ tiers: orchestratorAssets.tiers,
35545
+ scopeGate: orchestratorAssets.scopeGate,
35546
+ runRatio: orchestratorAssets.runRatio,
35547
+ scheduledTasks: orchestratorAssets.scheduledTasks,
35548
+ unblockDependents: orchestratorAssets.unblockDependents,
35549
+ excludeBundles
35550
+ }
35551
+ };
35552
+ }
35107
35553
  /**
35108
35554
  * Returns the bundles that are active for this project: auto-detected
35109
35555
  * bundles (when `autoDetectBundles !== false`) plus force-included
@@ -35201,11 +35647,34 @@ var AgentConfig = class _AgentConfig extends import_projen8.Component {
35201
35647
  ).split("\n"),
35202
35648
  executable: true
35203
35649
  });
35650
+ new import_projen8.TextFile(
35651
+ this,
35652
+ ".claude/procedures/check-issue-template-labels.sh",
35653
+ {
35654
+ lines: renderIssueTemplateLabelsCheckerScript(
35655
+ resolvedIssueTemplates
35656
+ ).split("\n"),
35657
+ executable: true
35658
+ }
35659
+ );
35204
35660
  }
35205
35661
  if (resolvedIssueTemplates.emitStarterDoc) {
35206
35662
  new import_projen8.SampleFile(this.project, resolvedIssueTemplates.templatesPath, {
35207
35663
  contents: renderIssueTemplatesStarterPage(resolvedIssueTemplates)
35208
35664
  });
35665
+ new import_projen8.TextFile(
35666
+ this,
35667
+ issueTemplatesGeneratedPath(resolvedIssueTemplates.templatesPath),
35668
+ {
35669
+ lines: renderIssueTemplatesGeneratedPage(
35670
+ resolvedIssueTemplates,
35671
+ collectIssueTemplateRecipeStubs(
35672
+ this.activeBundles,
35673
+ resolveIssueDefaults(this.options.issueDefaults)
35674
+ )
35675
+ ).split("\n")
35676
+ }
35677
+ );
35209
35678
  }
35210
35679
  }
35211
35680
  const resolvedTemporalFraming = validateTemporalFramingConfig(
@@ -35395,43 +35864,10 @@ ${section}`
35395
35864
  }
35396
35865
  }
35397
35866
  const excludedBundleNames = this.options.excludeBundles ?? [];
35398
- if (this.options.tiers || this.options.scopeGate || this.options.runRatio || this.options.scheduledTasks || this.options.unblockDependents || excludedBundleNames.length > 0) {
35399
- const orchestratorRule = ruleMap.get("orchestrator-conventions");
35400
- if (orchestratorRule) {
35401
- const { conventionsContent } = resolveOrchestratorAssets(
35402
- this.options.tiers,
35403
- this.options.scopeGate,
35404
- this.options.runRatio,
35405
- this.options.scheduledTasks,
35406
- this.options.unblockDependents,
35407
- excludedBundleNames
35408
- );
35409
- if (conventionsContent !== orchestratorRule.content) {
35410
- ruleMap.set("orchestrator-conventions", {
35411
- ...orchestratorRule,
35412
- content: conventionsContent
35413
- });
35414
- }
35415
- }
35416
- }
35417
35867
  const injectBundleHooks = this.options.claudeMd?.injectBundleHooks ?? true;
35418
35868
  const resolvedProgressFiles = resolveProgressFiles(
35419
35869
  this.options.progressFiles
35420
35870
  );
35421
- if (this.options.progressFiles) {
35422
- const progressRule = ruleMap.get("progress-file-convention");
35423
- if (progressRule) {
35424
- const progressContent = renderProgressFilesRuleContent(
35425
- resolvedProgressFiles
35426
- );
35427
- if (progressContent !== progressRule.content) {
35428
- ruleMap.set("progress-file-convention", {
35429
- ...progressRule,
35430
- content: progressContent
35431
- });
35432
- }
35433
- }
35434
- }
35435
35871
  if (injectBundleHooks && resolvedProgressFiles.enabled) {
35436
35872
  for (const [ruleName, label] of PROGRESS_FILE_BUNDLE_HOOKS) {
35437
35873
  const existing = ruleMap.get(ruleName);
@@ -35458,20 +35894,6 @@ ${hook}`
35458
35894
  const resolvedSharedEditingForRules = resolveSharedEditing(
35459
35895
  this.options.sharedEditing
35460
35896
  );
35461
- if (this.options.sharedEditing) {
35462
- const sharedEditingRule = ruleMap.get("shared-editing-safety");
35463
- if (sharedEditingRule) {
35464
- const sharedEditingContent = renderSharedEditingRuleContent(
35465
- resolvedSharedEditingForRules
35466
- );
35467
- if (sharedEditingContent !== sharedEditingRule.content) {
35468
- ruleMap.set("shared-editing-safety", {
35469
- ...sharedEditingRule,
35470
- content: sharedEditingContent
35471
- });
35472
- }
35473
- }
35474
- }
35475
35897
  if (injectBundleHooks && resolvedSharedEditingForRules.enabled) {
35476
35898
  for (const [ruleName, label] of SHARED_EDITING_BUNDLE_HOOKS) {
35477
35899
  const existing = ruleMap.get(ruleName);
@@ -35498,20 +35920,6 @@ ${hook}`
35498
35920
  const resolvedSkillEvalsForRules = resolveSkillEvals(
35499
35921
  this.options.skillEvals
35500
35922
  );
35501
- if (this.options.skillEvals) {
35502
- const skillEvalsRule = ruleMap.get("skill-evals");
35503
- if (skillEvalsRule) {
35504
- const skillEvalsContent = renderSkillEvalsRuleContent(
35505
- resolvedSkillEvalsForRules
35506
- );
35507
- if (skillEvalsContent !== skillEvalsRule.content) {
35508
- ruleMap.set("skill-evals", {
35509
- ...skillEvalsRule,
35510
- content: skillEvalsContent
35511
- });
35512
- }
35513
- }
35514
- }
35515
35923
  if (injectBundleHooks && resolvedSkillEvalsForRules.enabled) {
35516
35924
  for (const [ruleName, label] of SKILL_EVALS_BUNDLE_HOOKS) {
35517
35925
  const existing = ruleMap.get(ruleName);
@@ -35539,21 +35947,6 @@ ${hook}`
35539
35947
  this.options.issueTemplates
35540
35948
  );
35541
35949
  const hasDownstreamBundles = hasAnyDownstreamIssueKindBundle(excludedBundleNames);
35542
- if (this.options.issueTemplates || !hasDownstreamBundles) {
35543
- const issueTemplatesRule = ruleMap.get("issue-templates-convention");
35544
- if (issueTemplatesRule) {
35545
- const issueTemplatesContent = renderIssueTemplatesRuleContent(
35546
- resolvedIssueTemplatesForRules,
35547
- hasDownstreamBundles
35548
- );
35549
- if (issueTemplatesContent !== issueTemplatesRule.content) {
35550
- ruleMap.set("issue-templates-convention", {
35551
- ...issueTemplatesRule,
35552
- content: issueTemplatesContent
35553
- });
35554
- }
35555
- }
35556
- }
35557
35950
  if (injectBundleHooks && resolvedIssueTemplatesForRules.enabled && hasDownstreamBundles) {
35558
35951
  for (const [ruleName, label] of ISSUE_TEMPLATES_BUNDLE_HOOKS) {
35559
35952
  const existing = ruleMap.get(ruleName);
@@ -35577,23 +35970,6 @@ ${hook}`
35577
35970
  });
35578
35971
  }
35579
35972
  }
35580
- const resolvedTemporalFramingForRules = resolveTemporalFraming(
35581
- this.options.temporalFraming
35582
- );
35583
- if (this.options.temporalFraming) {
35584
- const temporalFramingRule = ruleMap.get("temporal-framing-convention");
35585
- if (temporalFramingRule) {
35586
- const temporalFramingContent = renderTemporalFramingRuleContent(
35587
- resolvedTemporalFramingForRules
35588
- );
35589
- if (temporalFramingContent !== temporalFramingRule.content) {
35590
- ruleMap.set("temporal-framing-convention", {
35591
- ...temporalFramingRule,
35592
- content: temporalFramingContent
35593
- });
35594
- }
35595
- }
35596
- }
35597
35973
  const tierExamples = this.options.features?.sourceTierExamples;
35598
35974
  if (_AgentConfig.hasActiveTierExamples(tierExamples)) {
35599
35975
  const sourceRule = ruleMap.get("source-quality-verification");
@@ -41806,6 +42182,7 @@ export const collections = {
41806
42182
  DEFAULT_API_EXTRACTOR_REPORT_FILENAME,
41807
42183
  DEFAULT_API_EXTRACTOR_REPORT_FOLDER,
41808
42184
  DEFAULT_AUDIT_REPORT_DIR,
42185
+ DEFAULT_BASE_CONVENTIONS,
41809
42186
  DEFAULT_BUILD_POLICY,
41810
42187
  DEFAULT_BUNDLE_OVERRIDES,
41811
42188
  DEFAULT_DECOMPOSITION_TEMPLATE,
@@ -41822,6 +42199,7 @@ export const collections = {
41822
42199
  DEFAULT_ISSUE_TEMPLATES_PATH,
41823
42200
  DEFAULT_ISSUE_TEMPLATES_REQUIRE_REFERENCE,
41824
42201
  DEFAULT_OFF_PEAK_CRON_EXAMPLE,
42202
+ DEFAULT_ORCHESTRATOR_CONVENTIONS,
41825
42203
  DEFAULT_PARTIAL_UNBLOCK_COMMENT_TEMPLATE,
41826
42204
  DEFAULT_PATHS_EXEMPT_FROM_SIZE,
41827
42205
  DEFAULT_PRIORITY_LABELS,
@@ -41834,6 +42212,7 @@ export const collections = {
41834
42212
  DEFAULT_REQUIREMENT_CATEGORY_DIRS,
41835
42213
  DEFAULT_REQUIRE_PRODUCT_CONTEXT,
41836
42214
  DEFAULT_RESOLVED_ISSUE_DEFAULTS,
42215
+ DEFAULT_RULE_CONVENTIONS,
41837
42216
  DEFAULT_SAMPLE_COMPILER_OPTIONS,
41838
42217
  DEFAULT_SCHEDULED_TASKS_ROOT,
41839
42218
  DEFAULT_SCHEDULED_TASK_ENTRIES,
@@ -41860,6 +42239,7 @@ export const collections = {
41860
42239
  DOCS_SYNC_AUDIT_SCHEMA_VERSION,
41861
42240
  GITHUB_ISSUE_TYPES,
41862
42241
  GITHUB_ISSUE_TYPE_BY_TITLE_PREFIX,
42242
+ ISSUE_TEMPLATES_GENERATED_SUFFIX,
41863
42243
  JsiiFaker,
41864
42244
  LAYOUT_ENFORCEMENT,
41865
42245
  LAYOUT_ROOT_BY_PROJECT_TYPE,
@@ -41936,6 +42316,7 @@ export const collections = {
41936
42316
  buildIndustryDiscoveryBundle,
41937
42317
  buildMaintenanceAuditBundle,
41938
42318
  buildMeetingAnalysisBundle,
42319
+ buildOrchestratorBundle,
41939
42320
  buildOrchestratorConventionsContent,
41940
42321
  buildPeopleProfileBundle,
41941
42322
  buildPrReviewBundle,
@@ -41955,6 +42336,7 @@ export const collections = {
41955
42336
  checkLinksProcedure,
41956
42337
  classifyIssueScope,
41957
42338
  classifyRun,
42339
+ collectIssueTemplateRecipeStubs,
41958
42340
  companyProfileBundle,
41959
42341
  compileFencedSamples,
41960
42342
  createApiDiffCheck,
@@ -41980,6 +42362,8 @@ export const collections = {
41980
42362
  isScheduledTaskOwnedByExcluded,
41981
42363
  isSuppressedWorkflowRule,
41982
42364
  isTypeLabelOwnedByExcluded,
42365
+ issueTemplatesChildGlob,
42366
+ issueTemplatesGeneratedPath,
41983
42367
  jestBundle,
41984
42368
  labelsForPhase,
41985
42369
  maintenanceAuditBundle,
@@ -42030,8 +42414,10 @@ export const collections = {
42030
42414
  renderFocusSection,
42031
42415
  renderGithubIssueTypeSection,
42032
42416
  renderGithubIssueTypeSectionLines,
42417
+ renderIssueTemplateLabelsCheckerScript,
42033
42418
  renderIssueTemplatesBundleHook,
42034
42419
  renderIssueTemplatesCheckerScript,
42420
+ renderIssueTemplatesGeneratedPage,
42035
42421
  renderIssueTemplatesRuleContent,
42036
42422
  renderIssueTemplatesStarterPage,
42037
42423
  renderIssueTypeAssignmentBlanket,