@codedrifters/configulator 0.0.406 → 0.0.407

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.mjs CHANGED
@@ -1830,12 +1830,111 @@ var awsCdkBundle = {
1830
1830
  ]
1831
1831
  };
1832
1832
 
1833
+ // src/agent/bundles/issue-defaults.ts
1834
+ var VALID_STATUS_VALUES = [
1835
+ "ready",
1836
+ "blocked",
1837
+ "in-progress",
1838
+ "ready-for-review",
1839
+ "needs-attention",
1840
+ "done",
1841
+ "deferred"
1842
+ ];
1843
+ var VALID_PRIORITY_VALUES = [
1844
+ "critical",
1845
+ "high",
1846
+ "medium",
1847
+ "low",
1848
+ "trivial"
1849
+ ];
1850
+ var DEFAULT_ISSUE_STATUS = "ready";
1851
+ var DEFAULT_ISSUE_PRIORITY = "medium";
1852
+ var DEFAULT_RESOLVED_ISSUE_DEFAULTS = {
1853
+ defaults: {
1854
+ status: DEFAULT_ISSUE_STATUS,
1855
+ priority: DEFAULT_ISSUE_PRIORITY
1856
+ },
1857
+ overrides: {}
1858
+ };
1859
+ function resolveIssueDefaults(config) {
1860
+ if (config === void 0) {
1861
+ return DEFAULT_RESOLVED_ISSUE_DEFAULTS;
1862
+ }
1863
+ const overrides = {};
1864
+ for (const [phaseLabel, override] of Object.entries(config)) {
1865
+ assertValidPhaseLabel(phaseLabel);
1866
+ assertValidOverride(phaseLabel, override);
1867
+ overrides[phaseLabel] = {
1868
+ status: override.status ?? DEFAULT_ISSUE_STATUS,
1869
+ priority: override.priority ?? DEFAULT_ISSUE_PRIORITY
1870
+ };
1871
+ }
1872
+ return {
1873
+ defaults: {
1874
+ status: DEFAULT_ISSUE_STATUS,
1875
+ priority: DEFAULT_ISSUE_PRIORITY
1876
+ },
1877
+ overrides
1878
+ };
1879
+ }
1880
+ function validateIssueDefaultsConfig(config) {
1881
+ return resolveIssueDefaults(config);
1882
+ }
1883
+ function labelsForPhase(resolved, phaseLabel) {
1884
+ return resolved.overrides[phaseLabel] ?? resolved.defaults;
1885
+ }
1886
+ function assertValidPhaseLabel(phaseLabel) {
1887
+ if (typeof phaseLabel !== "string" || phaseLabel.trim() === "") {
1888
+ throw new Error(
1889
+ "AgentConfigOptions.issueDefaults: phase-label keys must be non-empty strings (e.g. `people:research`)."
1890
+ );
1891
+ }
1892
+ }
1893
+ function assertValidOverride(phaseLabel, override) {
1894
+ if (override === null || typeof override !== "object" || Array.isArray(override)) {
1895
+ throw new Error(
1896
+ `AgentConfigOptions.issueDefaults["${phaseLabel}"] must be an object with optional \`status\` and \`priority\` fields.`
1897
+ );
1898
+ }
1899
+ const { status, priority } = override;
1900
+ if (status === void 0 && priority === void 0) {
1901
+ throw new Error(
1902
+ `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.`
1903
+ );
1904
+ }
1905
+ if (status !== void 0 && !VALID_STATUS_VALUES.includes(status)) {
1906
+ throw new Error(
1907
+ `AgentConfigOptions.issueDefaults["${phaseLabel}"].status="${status}" is not a recognised status value. Allowed values: ${VALID_STATUS_VALUES.join(", ")}.`
1908
+ );
1909
+ }
1910
+ if (priority !== void 0 && !VALID_PRIORITY_VALUES.includes(priority)) {
1911
+ throw new Error(
1912
+ `AgentConfigOptions.issueDefaults["${phaseLabel}"].priority="${priority}" is not a recognised priority value. Allowed values: ${VALID_PRIORITY_VALUES.join(", ")}.`
1913
+ );
1914
+ }
1915
+ }
1916
+
1833
1917
  // src/agent/bundles/issue-templates.ts
1834
1918
  var DEFAULT_ISSUE_TEMPLATES_ENABLED = true;
1835
1919
  var DEFAULT_ISSUE_TEMPLATES_PATH = "docs/src/content/docs/agents/issue-templates.md";
1836
1920
  var DEFAULT_ISSUE_TEMPLATES_BUNDLE_PATH_PATTERNS = [".claude/agents/**/*.md", ".claude/skills/**/*.md"];
1837
1921
  var DEFAULT_ISSUE_TEMPLATES_EMIT_CHECKER = false;
1838
1922
  var DEFAULT_ISSUE_TEMPLATES_EMIT_STARTER = false;
1923
+ var ISSUE_TEMPLATES_GENERATED_SUFFIX = "-generated";
1924
+ function issueTemplatesGeneratedPath(templatesPath) {
1925
+ const slash = templatesPath.lastIndexOf("/");
1926
+ const dot = templatesPath.lastIndexOf(".");
1927
+ if (dot > slash) {
1928
+ return `${templatesPath.slice(0, dot)}${ISSUE_TEMPLATES_GENERATED_SUFFIX}${templatesPath.slice(dot)}`;
1929
+ }
1930
+ return `${templatesPath}${ISSUE_TEMPLATES_GENERATED_SUFFIX}`;
1931
+ }
1932
+ function issueTemplatesChildGlob(templatesPath) {
1933
+ const slash = templatesPath.lastIndexOf("/");
1934
+ const dot = templatesPath.lastIndexOf(".");
1935
+ const stem = dot > slash ? templatesPath.slice(0, dot) : templatesPath;
1936
+ return `${stem}/*.md`;
1937
+ }
1839
1938
  var DEFAULT_ISSUE_TEMPLATES_REQUIRE_REFERENCE = true;
1840
1939
  function resolveIssueTemplates(config) {
1841
1940
  const templatesPath = config?.templatesPath ?? DEFAULT_ISSUE_TEMPLATES_PATH;
@@ -1919,7 +2018,40 @@ function renderIssueTemplatesRuleContent(it, hasDownstreamBundles = true) {
1919
2018
  "issue kind appends one section; retiring a kind removes one. The",
1920
2019
  "label taxonomy, body template shape, and placeholder conventions",
1921
2020
  "are shared across every section.",
1922
- "",
2021
+ ""
2022
+ ];
2023
+ if (it.emitStarterDoc) {
2024
+ lines.push(
2025
+ "## Scaffolded Starting Point",
2026
+ "",
2027
+ "The page is hand-authored, but it does not start empty. Two files",
2028
+ "ship together:",
2029
+ "",
2030
+ `1. \`${it.templatesPath}\` \u2014 **written once**, then yours. Titles,`,
2031
+ " bodies, and the callers list are hand-authored here and never",
2032
+ " overwritten.",
2033
+ `2. \`${issueTemplatesGeneratedPath(it.templatesPath)}\` \u2014`,
2034
+ " **regenerated on every synth**. One",
2035
+ " `## Template: <phase-label>` stub per phase label the active",
2036
+ " bundles contribute to the label registry, with the phase label,",
2037
+ " the `type:<bundle>` label its owning bundle declares, and the",
2038
+ " configured `status:*` / `priority:*` defaults pre-filled.",
2039
+ "",
2040
+ "Only the **label set** is generated. Title and body stay",
2041
+ "angle-bracket placeholders, so the scaffold answers *which labels*",
2042
+ "without dictating *what the issue says*. Copy a stub into the",
2043
+ "hand-authored page, flesh out its body, and leave the label block",
2044
+ "as generated.",
2045
+ "",
2046
+ "The split is load-bearing. A write-once scaffold alone freezes",
2047
+ "every consumer on whatever skeleton shipped the day they adopted",
2048
+ "the convention \u2014 a new phase label minted upstream never reaches",
2049
+ "them. Regenerating the label half fixes that without ever",
2050
+ "clobbering an authored body.",
2051
+ ""
2052
+ );
2053
+ }
2054
+ lines.push(
1923
2055
  "## Reference-Don't-Inline Rule",
1924
2056
  "",
1925
2057
  `Every bundle rule, agent prompt, or skill instruction that files a`,
@@ -1990,8 +2122,12 @@ function renderIssueTemplatesRuleContent(it, hasDownstreamBundles = true) {
1990
2122
  " placeholders (`<Organization Name>`, `<slug>`) that match",
1991
2123
  " existing templates. Consistency across sections lets callers",
1992
2124
  " substitute without re-reading the body every time.",
2125
+ "5. **Does the recipe assign a GitHub issue type?** `gh issue",
2126
+ " create` cannot set it and the `type:*` label does not either, so",
2127
+ " every section ends with the follow-up call. An issue that skips",
2128
+ " it stays untyped forever.",
1993
2129
  ""
1994
- ];
2130
+ );
1995
2131
  if (it.emitChecker) {
1996
2132
  lines.push(
1997
2133
  "## Automated Lint",
@@ -2022,6 +2158,33 @@ function renderIssueTemplatesRuleContent(it, hasDownstreamBundles = true) {
2022
2158
  "(which documents the conventional `gh issue create` call shape",
2023
2159
  "as taxonomy, not a downstream template). All other bundle",
2024
2160
  "files in the pattern set are in scope.",
2161
+ "",
2162
+ "### Recipe label correctness",
2163
+ "",
2164
+ "A companion lint,",
2165
+ "`.claude/procedures/check-issue-template-labels.sh`, checks the",
2166
+ "recipes themselves rather than where they live. It walks the",
2167
+ "templates page, its router-style child pages, and the generated",
2168
+ "label-set companion, and fails non-zero when a",
2169
+ "`## Template: <phase-label>` section:",
2170
+ "",
2171
+ "- never passes `--label <phase-label>` \u2014 the heading and the",
2172
+ " command disagree;",
2173
+ "- carries no `type:*` label, more than one, or one that is not",
2174
+ " the `type:<bundle>` label the phase label's owning bundle",
2175
+ " declares;",
2176
+ "- has no GitHub issue-type assignment step.",
2177
+ "",
2178
+ "```bash",
2179
+ ".claude/procedures/check-issue-template-labels.sh",
2180
+ "```",
2181
+ "",
2182
+ "The phase-label \u2192 type-label pairing is generated from the same",
2183
+ "bundle-ownership map that drives `.github/labels.yml` and the",
2184
+ "orchestrator's invariant sweep, so a hand-edit can never",
2185
+ "reintroduce a mismatch the registry has already ruled out.",
2186
+ "Sections whose heading matches no bundle-owned phase label are",
2187
+ "skipped \u2014 consumer-specific labels are deliberately not policed.",
2025
2188
  ""
2026
2189
  );
2027
2190
  }
@@ -2030,8 +2193,10 @@ function renderIssueTemplatesRuleContent(it, hasDownstreamBundles = true) {
2030
2193
  "",
2031
2194
  "The convention intentionally does not:",
2032
2195
  "",
2033
- "- Auto-generate the templates page \u2014 repo-specific phase labels",
2034
- " and body conventions are too varied for a one-size template.",
2196
+ "- Auto-generate recipe **bodies**. Only the label set is",
2197
+ " scaffolded, because labels are derivable from the bundle",
2198
+ " registry and bodies are not \u2014 repo-specific objectives,",
2199
+ " acceptance criteria, and callers lists stay hand-authored.",
2035
2200
  "- Prescribe a fixed body shape across templates. Each section",
2036
2201
  " may carry its own `## Acceptance Criteria`, `## Scope Size`,",
2037
2202
  " or `## Output Path` fields as the downstream agent requires;",
@@ -2062,7 +2227,8 @@ function renderIssueTemplatesBundleHook(it, bundleLabel) {
2062
2227
  "as an inline template vs. a prose mention."
2063
2228
  ].join("\n");
2064
2229
  }
2065
- function renderIssueTemplatesStarterPage(_it) {
2230
+ function renderIssueTemplatesStarterPage(it) {
2231
+ const generatedLink = `./${basename(issueTemplatesGeneratedPath(it.templatesPath))}`;
2066
2232
  return [
2067
2233
  "---",
2068
2234
  "title: Issue Templates",
@@ -2097,6 +2263,20 @@ function renderIssueTemplatesStarterPage(_it) {
2097
2263
  "bundle's rule \u2014 the issue-templates page is a recipe catalogue,",
2098
2264
  "not a duplicate-check layer.",
2099
2265
  "",
2266
+ "## Generated label sets",
2267
+ "",
2268
+ `[Generated Issue-Template Label Sets](${generatedLink}) carries one`,
2269
+ "correct-by-construction stub per phase label the active bundles",
2270
+ "emit \u2014 the phase label paired with the `type:<bundle>` label its",
2271
+ "owning bundle declares, plus the configured `status:*` /",
2272
+ "`priority:*` defaults. That page regenerates on every `projen` run,",
2273
+ "so it never goes stale as phase labels come and go.",
2274
+ "",
2275
+ "This page is the opposite: written once, then owned by you. Copy a",
2276
+ "stub across, flesh out its title and body here, and leave the label",
2277
+ "block exactly as generated \u2014 the label-consistency lint holds both",
2278
+ "copies to the same pairing.",
2279
+ "",
2100
2280
  "## Template: example-phase",
2101
2281
  "",
2102
2282
  "Replace this example with the real templates for this repo. Each",
@@ -2136,6 +2316,115 @@ function renderIssueTemplatesStarterPage(_it) {
2136
2316
  "```"
2137
2317
  ].join("\n");
2138
2318
  }
2319
+ function collectIssueTemplateRecipeStubs(bundles, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS) {
2320
+ const stubs = /* @__PURE__ */ new Map();
2321
+ for (const bundle of bundles) {
2322
+ for (const label of bundle.labels ?? []) {
2323
+ const typeLabel = typeLabelForPhaseLabel(label.name);
2324
+ if (typeLabel === void 0) {
2325
+ continue;
2326
+ }
2327
+ if (stubs.has(label.name)) {
2328
+ continue;
2329
+ }
2330
+ const defaults = labelsForPhase(issueDefaults, label.name);
2331
+ stubs.set(label.name, {
2332
+ phaseLabel: label.name,
2333
+ typeLabel,
2334
+ bundleName: bundle.name,
2335
+ description: label.description ?? "",
2336
+ status: defaults.status,
2337
+ priority: defaults.priority,
2338
+ issueType: githubIssueTypeForTitle(`${label.name}: placeholder`)
2339
+ });
2340
+ }
2341
+ }
2342
+ return [...stubs.values()].sort(
2343
+ (a, b) => a.phaseLabel.localeCompare(b.phaseLabel)
2344
+ );
2345
+ }
2346
+ function renderIssueTemplatesGeneratedPage(it, stubs) {
2347
+ const lines = [
2348
+ "---",
2349
+ "title: Generated Issue-Template Label Sets",
2350
+ "description: Auto-generated label-correct gh issue create stubs \u2014 one per phase label.",
2351
+ "---",
2352
+ "",
2353
+ "**Generated file \u2014 do not edit.** Every section below is",
2354
+ "regenerated from the active bundle set on each `projen` run; hand",
2355
+ "edits are overwritten. The hand-authored recipes live in",
2356
+ `[the issue-templates page](./${basename(it.templatesPath)}) \u2014 this`,
2357
+ "page exists so the **label half** of every recipe stays",
2358
+ "correct-by-construction as phase labels come and go.",
2359
+ "",
2360
+ "## How to use",
2361
+ "",
2362
+ "Each section carries the exact label set the phase-label invariant",
2363
+ "requires: the phase label itself, the `type:<bundle>` label its",
2364
+ "owning bundle declares, and the configured `status:*` / `priority:*`",
2365
+ "defaults for that phase. Title and body are placeholders \u2014 copy the",
2366
+ "stub into the hand-authored templates page and flesh out the body",
2367
+ "there, or run it as-is when no repo-specific body exists yet.",
2368
+ "",
2369
+ "Every stub ends with the **GitHub issue type** assignment. `gh issue",
2370
+ "create` cannot set the issue type and the `type:*` label does not",
2371
+ "set it either, so an issue that skips the follow-up call stays",
2372
+ "untyped forever. See **Assigning the GitHub issue type** in the",
2373
+ "`issue-conventions` rule for the no-helper fallback.",
2374
+ ""
2375
+ ];
2376
+ if (stubs.length === 0) {
2377
+ lines.push(
2378
+ "## No phase labels",
2379
+ "",
2380
+ "No active bundle contributes a phase label, so there is nothing to",
2381
+ "scaffold. Enable a phased-pipeline bundle (or drop it from",
2382
+ "`excludeBundles`) and re-run `projen`."
2383
+ );
2384
+ return lines.join("\n");
2385
+ }
2386
+ for (const stub of stubs) {
2387
+ lines.push(
2388
+ `## Template: ${stub.phaseLabel}`,
2389
+ "",
2390
+ ...stub.description === "" ? [] : [stub.description, ""],
2391
+ `**Owning bundle:** \`${stub.bundleName}\` \u2014 **required type label:**`,
2392
+ `\`${stub.typeLabel}\` (paired by the bundle-ownership map).`,
2393
+ "",
2394
+ "**Callers:** <name the bundles and agents that file this issue>",
2395
+ "",
2396
+ "```bash",
2397
+ "gh issue create \\",
2398
+ ` --title "${stub.phaseLabel}: <short description>" \\`,
2399
+ ` --label "${stub.typeLabel}" \\`,
2400
+ ` --label "${stub.phaseLabel}" \\`,
2401
+ ` --label "priority:${stub.priority}" \\`,
2402
+ ` --label "status:${stub.status}" \\`,
2403
+ ' --body "## Objective',
2404
+ "",
2405
+ "<1-3 sentences describing the work>.",
2406
+ "",
2407
+ "## Context",
2408
+ "",
2409
+ "- **Discovered in:** <source description> (#<parent-issue>)",
2410
+ "",
2411
+ "## Acceptance Criteria",
2412
+ "",
2413
+ "- [ ] <criterion 1>",
2414
+ '"',
2415
+ "```",
2416
+ "",
2417
+ `Then assign the GitHub issue type (\`${stub.issueType}\`):`,
2418
+ "",
2419
+ "```bash",
2420
+ `${SET_ISSUE_TYPE_HELPER_PATH} <issue-number> ${stub.issueType}`,
2421
+ "```",
2422
+ ""
2423
+ );
2424
+ }
2425
+ lines.pop();
2426
+ return lines.join("\n");
2427
+ }
2139
2428
  function renderIssueTemplatesCheckerScript(it) {
2140
2429
  const patternsLiteral = it.bundlePathPatterns.map((p) => ` ${JSON.stringify(p)}`).join("\n");
2141
2430
  return [
@@ -2324,6 +2613,157 @@ function renderIssueTemplatesCheckerScript(it) {
2324
2613
  "exit 0"
2325
2614
  ].join("\n");
2326
2615
  }
2616
+ function renderIssueTemplateLabelsCheckerScript(it) {
2617
+ const generatedPath = issueTemplatesGeneratedPath(it.templatesPath);
2618
+ const childGlob = issueTemplatesChildGlob(it.templatesPath);
2619
+ return [
2620
+ "#!/usr/bin/env bash",
2621
+ "# check-issue-template-labels.sh \u2014 Enforce recipe label correctness.",
2622
+ "#",
2623
+ "# Usage:",
2624
+ "# .claude/procedures/check-issue-template-labels.sh [<file>...]",
2625
+ "#",
2626
+ "# With no arguments the lint walks the templates page, its",
2627
+ "# router-style child pages, and the generated label-set companion.",
2628
+ "# Positional arguments override that default set.",
2629
+ "#",
2630
+ "# For every `## Template: <phase-label>` section it asserts the",
2631
+ "# recipe passes the heading's phase label, carries exactly one",
2632
+ "# `type:*` label matching the phase-label invariant, and assigns a",
2633
+ "# GitHub issue type. Sections whose heading matches no bundle-owned",
2634
+ "# phase label are skipped \u2014 unrecognised labels are consumer-",
2635
+ "# specific and deliberately not policed.",
2636
+ "#",
2637
+ "# The resolver below is generated from the canonical bundle-",
2638
+ "# ownership map \u2014 do not edit by hand; regenerate via",
2639
+ "# `pnpm exec projen`.",
2640
+ "",
2641
+ "set -uo pipefail",
2642
+ "",
2643
+ "err() {",
2644
+ ' printf "check-issue-template-labels.sh: %s\\n" "$*" >&2',
2645
+ "}",
2646
+ "",
2647
+ renderPhaseTypeInvariantShellHelpers(),
2648
+ "",
2649
+ `templates_path=${JSON.stringify(it.templatesPath)}`,
2650
+ `generated_path=${JSON.stringify(generatedPath)}`,
2651
+ `child_glob=${JSON.stringify(childGlob)}`,
2652
+ "",
2653
+ "# Emit one TAB-separated record per `## Template:` section:",
2654
+ "# <file>\\t<line>\\t<phase-label>\\t<comma-joined labels>\\t<0|1 typed>",
2655
+ "# Quote characters are folded to spaces up front so a recipe written",
2656
+ "# with single quotes, double quotes, or none at all parses the same.",
2657
+ "# Fenced blocks are tracked so the `## Objective` heading inside a",
2658
+ "# recipe's --body string never reads as the end of the section.",
2659
+ "scan_file() {",
2660
+ ' local file="$1"',
2661
+ ` tr '\\042\\047' ' ' < "$file" | awk -v FNAME="$file" '`,
2662
+ " function flush() {",
2663
+ ' if (phase != "") {',
2664
+ ' printf "%s\\t%d\\t%s\\t%s\\t%d\\n", FNAME, start, phase, labels, hastype;',
2665
+ " }",
2666
+ ' phase = ""; labels = ""; hastype = 0;',
2667
+ " }",
2668
+ " {",
2669
+ " if ($0 ~ /^[ \\t]*```/) {",
2670
+ " fence = 1 - fence;",
2671
+ ' } else if (fence == 0 && index($0, "## ") == 1) {',
2672
+ " flush();",
2673
+ ' if (index($0, "## Template: ") == 1) {',
2674
+ " phase = substr($0, 14);",
2675
+ ' gsub(/[ \\t`]/, "", phase);',
2676
+ " start = NR;",
2677
+ " }",
2678
+ " next;",
2679
+ " }",
2680
+ ' if (phase == "") next;',
2681
+ " if ($0 ~ /set-issue-type\\.sh/ || $0 ~ /updateIssueIssueType/) {",
2682
+ " hastype = 1;",
2683
+ " }",
2684
+ " line = $0;",
2685
+ " while (match(line, /--label[ \\t]+[^ \\t]+/)) {",
2686
+ " tok = substr(line, RSTART, RLENGTH);",
2687
+ " line = substr(line, RSTART + RLENGTH);",
2688
+ ' sub(/^--label[ \\t]+/, "", tok);',
2689
+ ' gsub(/[\\\\`]/, "", tok);',
2690
+ ' labels = labels tok ",";',
2691
+ " }",
2692
+ " }",
2693
+ " END { flush(); }",
2694
+ " '",
2695
+ "}",
2696
+ "",
2697
+ "# Collect the file list: positional args override the default set.",
2698
+ "files=()",
2699
+ "if [[ $# -gt 0 ]]; then",
2700
+ ' files=("$@")',
2701
+ "else",
2702
+ ' [[ -f "$templates_path" ]] && files+=("$templates_path")',
2703
+ ' [[ -f "$generated_path" ]] && files+=("$generated_path")',
2704
+ " for child in $child_glob; do",
2705
+ ' [[ -f "$child" ]] && files+=("$child")',
2706
+ " done",
2707
+ "fi",
2708
+ "",
2709
+ "if [[ ${#files[@]} -eq 0 ]]; then",
2710
+ " exit 0",
2711
+ "fi",
2712
+ "",
2713
+ "violations=()",
2714
+ "checked=0",
2715
+ "",
2716
+ 'for file in "${files[@]}"; do',
2717
+ ' [[ -f "$file" ]] || continue',
2718
+ " while IFS=$'\\t' read -r rec_file rec_line phase labels hastype; do",
2719
+ ' [[ -z "$phase" ]] && continue',
2720
+ ' required=$(phase_label_type_of "$phase")',
2721
+ ' [[ -z "$required" ]] && continue',
2722
+ " checked=$(( checked + 1 ))",
2723
+ ' case ",${labels}" in',
2724
+ ' *",${phase},"*) ;;',
2725
+ ` *) violations+=("$rec_file:$rec_line: recipe for '$phase' never passes --label $phase") ;;`,
2726
+ " esac",
2727
+ ' type_labels=""',
2728
+ " type_count=0",
2729
+ ' for entry in $(printf "%s" "$labels" | tr , " "); do',
2730
+ ' case "$entry" in',
2731
+ " type:*)",
2732
+ ' type_labels="${type_labels}${entry} "',
2733
+ " type_count=$(( type_count + 1 ))",
2734
+ " ;;",
2735
+ " esac",
2736
+ " done",
2737
+ ' if [[ "$type_count" -eq 0 ]]; then',
2738
+ ` violations+=("$rec_file:$rec_line: recipe for '$phase' carries no type:* label (expected $required)")`,
2739
+ ' elif [[ "$type_count" -gt 1 ]]; then',
2740
+ " violations+=(\"$rec_file:$rec_line: recipe for '$phase' carries ${type_count} type:* labels (${type_labels% }); exactly one is allowed\")",
2741
+ ' elif [[ "${type_labels% }" != "$required" ]]; then',
2742
+ ` violations+=("$rec_file:$rec_line: recipe for '$phase' carries \${type_labels% } but the phase label requires $required")`,
2743
+ " fi",
2744
+ ' if [[ "$hastype" != "1" ]]; then',
2745
+ ` violations+=("$rec_file:$rec_line: recipe for '$phase' has no GitHub issue-type assignment step")`,
2746
+ " fi",
2747
+ ' done < <(scan_file "$file")',
2748
+ "done",
2749
+ "",
2750
+ "if [[ ${#violations[@]} -gt 0 ]]; then",
2751
+ ' err "issue-template recipes disagree with the phase-label invariant:"',
2752
+ ' for v in "${violations[@]}"; do',
2753
+ ' err " - $v"',
2754
+ " done",
2755
+ ` 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"`,
2756
+ " exit 1",
2757
+ "fi",
2758
+ "",
2759
+ 'printf "check-issue-template-labels.sh: %d recipe(s) OK\\n" "$checked"',
2760
+ "exit 0"
2761
+ ].join("\n");
2762
+ }
2763
+ function basename(path8) {
2764
+ const slash = path8.lastIndexOf("/");
2765
+ return slash === -1 ? path8 : path8.slice(slash + 1);
2766
+ }
2327
2767
  function assertValidTemplatesPath(value) {
2328
2768
  if (typeof value !== "string" || value.trim().length === 0) {
2329
2769
  throw new Error(
@@ -5326,90 +5766,6 @@ function buildBaseBundle(paths = DEFAULT_AGENT_PATHS) {
5326
5766
  }
5327
5767
  var baseBundle = buildBaseBundle();
5328
5768
 
5329
- // src/agent/bundles/issue-defaults.ts
5330
- var VALID_STATUS_VALUES = [
5331
- "ready",
5332
- "blocked",
5333
- "in-progress",
5334
- "ready-for-review",
5335
- "needs-attention",
5336
- "done",
5337
- "deferred"
5338
- ];
5339
- var VALID_PRIORITY_VALUES = [
5340
- "critical",
5341
- "high",
5342
- "medium",
5343
- "low",
5344
- "trivial"
5345
- ];
5346
- var DEFAULT_ISSUE_STATUS = "ready";
5347
- var DEFAULT_ISSUE_PRIORITY = "medium";
5348
- var DEFAULT_RESOLVED_ISSUE_DEFAULTS = {
5349
- defaults: {
5350
- status: DEFAULT_ISSUE_STATUS,
5351
- priority: DEFAULT_ISSUE_PRIORITY
5352
- },
5353
- overrides: {}
5354
- };
5355
- function resolveIssueDefaults(config) {
5356
- if (config === void 0) {
5357
- return DEFAULT_RESOLVED_ISSUE_DEFAULTS;
5358
- }
5359
- const overrides = {};
5360
- for (const [phaseLabel, override] of Object.entries(config)) {
5361
- assertValidPhaseLabel(phaseLabel);
5362
- assertValidOverride(phaseLabel, override);
5363
- overrides[phaseLabel] = {
5364
- status: override.status ?? DEFAULT_ISSUE_STATUS,
5365
- priority: override.priority ?? DEFAULT_ISSUE_PRIORITY
5366
- };
5367
- }
5368
- return {
5369
- defaults: {
5370
- status: DEFAULT_ISSUE_STATUS,
5371
- priority: DEFAULT_ISSUE_PRIORITY
5372
- },
5373
- overrides
5374
- };
5375
- }
5376
- function validateIssueDefaultsConfig(config) {
5377
- return resolveIssueDefaults(config);
5378
- }
5379
- function labelsForPhase(resolved, phaseLabel) {
5380
- return resolved.overrides[phaseLabel] ?? resolved.defaults;
5381
- }
5382
- function assertValidPhaseLabel(phaseLabel) {
5383
- if (typeof phaseLabel !== "string" || phaseLabel.trim() === "") {
5384
- throw new Error(
5385
- "AgentConfigOptions.issueDefaults: phase-label keys must be non-empty strings (e.g. `people:research`)."
5386
- );
5387
- }
5388
- }
5389
- function assertValidOverride(phaseLabel, override) {
5390
- if (override === null || typeof override !== "object" || Array.isArray(override)) {
5391
- throw new Error(
5392
- `AgentConfigOptions.issueDefaults["${phaseLabel}"] must be an object with optional \`status\` and \`priority\` fields.`
5393
- );
5394
- }
5395
- const { status, priority } = override;
5396
- if (status === void 0 && priority === void 0) {
5397
- throw new Error(
5398
- `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.`
5399
- );
5400
- }
5401
- if (status !== void 0 && !VALID_STATUS_VALUES.includes(status)) {
5402
- throw new Error(
5403
- `AgentConfigOptions.issueDefaults["${phaseLabel}"].status="${status}" is not a recognised status value. Allowed values: ${VALID_STATUS_VALUES.join(", ")}.`
5404
- );
5405
- }
5406
- if (priority !== void 0 && !VALID_PRIORITY_VALUES.includes(priority)) {
5407
- throw new Error(
5408
- `AgentConfigOptions.issueDefaults["${phaseLabel}"].priority="${priority}" is not a recognised priority value. Allowed values: ${VALID_PRIORITY_VALUES.join(", ")}.`
5409
- );
5410
- }
5411
- }
5412
-
5413
5769
  // src/agent/bundles/bcm-writer.ts
5414
5770
  function buildBcmWriterSubAgent(paths, issueDefaults) {
5415
5771
  return {
@@ -32662,6 +33018,8 @@ var UPSTREAM_CONFIGULATOR_DOCS_DETAIL = [
32662
33018
  " --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`'",
32663
33019
  "```",
32664
33020
  "",
33021
+ "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.",
33022
+ "",
32665
33023
  "After filing, reference the upstream issue from any local workaround so it can be reverted once the upstream fix lands:",
32666
33024
  "",
32667
33025
  "```typescript",
@@ -34838,11 +35196,34 @@ var AgentConfig = class _AgentConfig extends Component8 {
34838
35196
  ).split("\n"),
34839
35197
  executable: true
34840
35198
  });
35199
+ new TextFile4(
35200
+ this,
35201
+ ".claude/procedures/check-issue-template-labels.sh",
35202
+ {
35203
+ lines: renderIssueTemplateLabelsCheckerScript(
35204
+ resolvedIssueTemplates
35205
+ ).split("\n"),
35206
+ executable: true
35207
+ }
35208
+ );
34841
35209
  }
34842
35210
  if (resolvedIssueTemplates.emitStarterDoc) {
34843
35211
  new SampleFile2(this.project, resolvedIssueTemplates.templatesPath, {
34844
35212
  contents: renderIssueTemplatesStarterPage(resolvedIssueTemplates)
34845
35213
  });
35214
+ new TextFile4(
35215
+ this,
35216
+ issueTemplatesGeneratedPath(resolvedIssueTemplates.templatesPath),
35217
+ {
35218
+ lines: renderIssueTemplatesGeneratedPage(
35219
+ resolvedIssueTemplates,
35220
+ collectIssueTemplateRecipeStubs(
35221
+ this.activeBundles,
35222
+ resolveIssueDefaults(this.options.issueDefaults)
35223
+ )
35224
+ ).split("\n")
35225
+ }
35226
+ );
34846
35227
  }
34847
35228
  }
34848
35229
  const resolvedTemporalFraming = validateTemporalFramingConfig(
@@ -41509,6 +41890,7 @@ export {
41509
41890
  DOCS_SYNC_AUDIT_SCHEMA_VERSION,
41510
41891
  GITHUB_ISSUE_TYPES,
41511
41892
  GITHUB_ISSUE_TYPE_BY_TITLE_PREFIX,
41893
+ ISSUE_TEMPLATES_GENERATED_SUFFIX,
41512
41894
  JsiiFaker,
41513
41895
  LAYOUT_ENFORCEMENT,
41514
41896
  LAYOUT_ROOT_BY_PROJECT_TYPE,
@@ -41604,6 +41986,7 @@ export {
41604
41986
  checkLinksProcedure,
41605
41987
  classifyIssueScope,
41606
41988
  classifyRun,
41989
+ collectIssueTemplateRecipeStubs,
41607
41990
  companyProfileBundle,
41608
41991
  compileFencedSamples,
41609
41992
  createApiDiffCheck,
@@ -41629,6 +42012,8 @@ export {
41629
42012
  isScheduledTaskOwnedByExcluded,
41630
42013
  isSuppressedWorkflowRule,
41631
42014
  isTypeLabelOwnedByExcluded,
42015
+ issueTemplatesChildGlob,
42016
+ issueTemplatesGeneratedPath,
41632
42017
  jestBundle,
41633
42018
  labelsForPhase,
41634
42019
  maintenanceAuditBundle,
@@ -41679,8 +42064,10 @@ export {
41679
42064
  renderFocusSection,
41680
42065
  renderGithubIssueTypeSection,
41681
42066
  renderGithubIssueTypeSectionLines,
42067
+ renderIssueTemplateLabelsCheckerScript,
41682
42068
  renderIssueTemplatesBundleHook,
41683
42069
  renderIssueTemplatesCheckerScript,
42070
+ renderIssueTemplatesGeneratedPage,
41684
42071
  renderIssueTemplatesRuleContent,
41685
42072
  renderIssueTemplatesStarterPage,
41686
42073
  renderIssueTypeAssignmentBlanket,