@codedrifters/configulator 0.0.405 → 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.js CHANGED
@@ -228,6 +228,7 @@ __export(index_exports, {
228
228
  DEFAULT_DECOMPOSITION_TEMPLATE: () => DEFAULT_DECOMPOSITION_TEMPLATE,
229
229
  DEFAULT_DISPATCH_MODEL: () => DEFAULT_DISPATCH_MODEL,
230
230
  DEFAULT_DISPATCH_TO_HOUSEKEEPING_RATIO: () => DEFAULT_DISPATCH_TO_HOUSEKEEPING_RATIO,
231
+ DEFAULT_GITHUB_ISSUE_TYPE: () => DEFAULT_GITHUB_ISSUE_TYPE,
231
232
  DEFAULT_HOUSEKEEPING_MODEL: () => DEFAULT_HOUSEKEEPING_MODEL,
232
233
  DEFAULT_ISSUE_PRIORITY: () => DEFAULT_ISSUE_PRIORITY,
233
234
  DEFAULT_ISSUE_STATUS: () => DEFAULT_ISSUE_STATUS,
@@ -274,6 +275,9 @@ __export(index_exports, {
274
275
  DEFAULT_UNBLOCK_DEPENDENTS_ENABLED: () => DEFAULT_UNBLOCK_DEPENDENTS_ENABLED,
275
276
  DEFAULT_UPSTREAM_CONFIGULATOR_ENABLED: () => DEFAULT_UPSTREAM_CONFIGULATOR_ENABLED,
276
277
  DOCS_SYNC_AUDIT_SCHEMA_VERSION: () => DOCS_SYNC_AUDIT_SCHEMA_VERSION,
278
+ GITHUB_ISSUE_TYPES: () => GITHUB_ISSUE_TYPES,
279
+ GITHUB_ISSUE_TYPE_BY_TITLE_PREFIX: () => GITHUB_ISSUE_TYPE_BY_TITLE_PREFIX,
280
+ ISSUE_TEMPLATES_GENERATED_SUFFIX: () => ISSUE_TEMPLATES_GENERATED_SUFFIX,
277
281
  JsiiFaker: () => JsiiFaker,
278
282
  LAYOUT_ENFORCEMENT: () => LAYOUT_ENFORCEMENT,
279
283
  LAYOUT_ROOT_BY_PROJECT_TYPE: () => LAYOUT_ROOT_BY_PROJECT_TYPE,
@@ -298,6 +302,7 @@ __export(index_exports, {
298
302
  ResetTask: () => ResetTask,
299
303
  SCHEDULED_TASK_MODEL_VALUES: () => SCHEDULED_TASK_MODEL_VALUES,
300
304
  SCOPE_CLASS_VALUES: () => SCOPE_CLASS_VALUES,
305
+ SET_ISSUE_TYPE_HELPER_PATH: () => SET_ISSUE_TYPE_HELPER_PATH,
301
306
  SHARED_EDITING_CONFLICT_STRATEGY_VALUES: () => SHARED_EDITING_CONFLICT_STRATEGY_VALUES,
302
307
  STARLIGHT_ROLE: () => STARLIGHT_ROLE,
303
308
  SUPPRESSED_WORKFLOW_RULE_NAMES: () => SUPPRESSED_WORKFLOW_RULE_NAMES,
@@ -368,6 +373,7 @@ __export(index_exports, {
368
373
  checkLinksProcedure: () => checkLinksProcedure,
369
374
  classifyIssueScope: () => classifyIssueScope,
370
375
  classifyRun: () => classifyRun,
376
+ collectIssueTemplateRecipeStubs: () => collectIssueTemplateRecipeStubs,
371
377
  companyProfileBundle: () => companyProfileBundle,
372
378
  compileFencedSamples: () => compileFencedSamples,
373
379
  createApiDiffCheck: () => createApiDiffCheck,
@@ -383,6 +389,7 @@ __export(index_exports, {
383
389
  formatLayoutViolation: () => formatLayoutViolation,
384
390
  formatStarlightSingletonViolation: () => formatStarlightSingletonViolation,
385
391
  getLatestEligibleVersion: () => getLatestEligibleVersion,
392
+ githubIssueTypeForTitle: () => githubIssueTypeForTitle,
386
393
  githubWorkflowBundle: () => githubWorkflowBundle,
387
394
  hasAnyDocsEmittingBundle: () => hasAnyDocsEmittingBundle,
388
395
  hasAnyDownstreamIssueKindBundle: () => hasAnyDownstreamIssueKindBundle,
@@ -392,6 +399,8 @@ __export(index_exports, {
392
399
  isScheduledTaskOwnedByExcluded: () => isScheduledTaskOwnedByExcluded,
393
400
  isSuppressedWorkflowRule: () => isSuppressedWorkflowRule,
394
401
  isTypeLabelOwnedByExcluded: () => isTypeLabelOwnedByExcluded,
402
+ issueTemplatesChildGlob: () => issueTemplatesChildGlob,
403
+ issueTemplatesGeneratedPath: () => issueTemplatesGeneratedPath,
395
404
  jestBundle: () => jestBundle,
396
405
  labelsForPhase: () => labelsForPhase,
397
406
  maintenanceAuditBundle: () => maintenanceAuditBundle,
@@ -440,10 +449,16 @@ __export(index_exports, {
440
449
  renderCustomDocSections: () => renderCustomDocSections,
441
450
  renderExtractApiProcedure: () => renderExtractApiProcedure,
442
451
  renderFocusSection: () => renderFocusSection,
452
+ renderGithubIssueTypeSection: () => renderGithubIssueTypeSection,
453
+ renderGithubIssueTypeSectionLines: () => renderGithubIssueTypeSectionLines,
454
+ renderIssueTemplateLabelsCheckerScript: () => renderIssueTemplateLabelsCheckerScript,
443
455
  renderIssueTemplatesBundleHook: () => renderIssueTemplatesBundleHook,
444
456
  renderIssueTemplatesCheckerScript: () => renderIssueTemplatesCheckerScript,
457
+ renderIssueTemplatesGeneratedPage: () => renderIssueTemplatesGeneratedPage,
445
458
  renderIssueTemplatesRuleContent: () => renderIssueTemplatesRuleContent,
446
459
  renderIssueTemplatesStarterPage: () => renderIssueTemplatesStarterPage,
460
+ renderIssueTypeAssignmentBlanket: () => renderIssueTypeAssignmentBlanket,
461
+ renderIssueTypeAssignmentStep: () => renderIssueTypeAssignmentStep,
447
462
  renderMeetingTypesSection: () => renderMeetingTypesSection,
448
463
  renderNextRequirementIdProcedure: () => renderNextRequirementIdProcedure,
449
464
  renderPhaseTypeInvariantSection: () => renderPhaseTypeInvariantSection,
@@ -460,6 +475,7 @@ __export(index_exports, {
460
475
  renderScheduledTasksSection: () => renderScheduledTasksSection,
461
476
  renderScopeGateSection: () => renderScopeGateSection,
462
477
  renderScopeGateShellHelpers: () => renderScopeGateShellHelpers,
478
+ renderSetIssueTypeFallbackLines: () => renderSetIssueTypeFallbackLines,
463
479
  renderSharedEditingBundleHook: () => renderSharedEditingBundleHook,
464
480
  renderSharedEditingHelperScript: () => renderSharedEditingHelperScript,
465
481
  renderSharedEditingRuleContent: () => renderSharedEditingRuleContent,
@@ -470,6 +486,7 @@ __export(index_exports, {
470
486
  renderStripToolArtifactTagsProcedure: () => renderStripToolArtifactTagsProcedure,
471
487
  renderTemporalFramingCheckerScript: () => renderTemporalFramingCheckerScript,
472
488
  renderTemporalFramingRuleContent: () => renderTemporalFramingRuleContent,
489
+ renderTitlePrefixTypeBullets: () => renderTitlePrefixTypeBullets,
473
490
  renderUnblockDependentsScript: () => renderUnblockDependentsScript,
474
491
  renderUnblockDependentsSection: () => renderUnblockDependentsSection,
475
492
  requirementsAnalystBundle: () => requirementsAnalystBundle,
@@ -638,6 +655,603 @@ function renderClaimVerificationSection() {
638
655
  ];
639
656
  }
640
657
 
658
+ // src/agent/bundles/bundle-ownership.ts
659
+ var BUNDLE_OWNERSHIP = {
660
+ agenda: {
661
+ typeLabels: ["agenda"],
662
+ phaseLabelPrefixes: ["agenda:"],
663
+ scheduledTaskIds: ["worker-agenda"],
664
+ emitsDocs: true,
665
+ downstreamIssueKinds: true
666
+ },
667
+ "bcm-writer": {
668
+ typeLabels: ["bcm-document"],
669
+ phaseLabelPrefixes: ["bcm:"],
670
+ scheduledTaskIds: ["worker-bcm-writer"],
671
+ emitsDocs: true,
672
+ downstreamIssueKinds: true
673
+ },
674
+ "business-models": {
675
+ typeLabels: ["business-model"],
676
+ phaseLabelPrefixes: ["business-models:"],
677
+ scheduledTaskIds: ["worker-business-models"],
678
+ emitsDocs: true,
679
+ downstreamIssueKinds: true
680
+ },
681
+ "company-profile": {
682
+ typeLabels: ["company-profile"],
683
+ phaseLabelPrefixes: ["company:"],
684
+ scheduledTaskIds: ["worker-company-profile"],
685
+ emitsDocs: true,
686
+ downstreamIssueKinds: true
687
+ },
688
+ "customer-profile": {
689
+ typeLabels: ["customer-profile"],
690
+ phaseLabelPrefixes: ["customer:"],
691
+ scheduledTaskIds: ["worker-customer-profile"],
692
+ emitsDocs: true,
693
+ downstreamIssueKinds: true
694
+ },
695
+ "docs-sync": {
696
+ typeLabels: ["docs-sync"],
697
+ phaseLabelPrefixes: ["docs-sync:"],
698
+ scheduledTaskIds: ["worker-docs-sync"],
699
+ emitsDocs: true,
700
+ downstreamIssueKinds: true
701
+ },
702
+ "industry-discovery": {
703
+ typeLabels: ["industry-discovery"],
704
+ phaseLabelPrefixes: ["industry:"],
705
+ scheduledTaskIds: ["worker-industry-discovery"],
706
+ emitsDocs: true,
707
+ downstreamIssueKinds: true
708
+ },
709
+ "maintenance-audit": {
710
+ typeLabels: ["maintenance"],
711
+ phaseLabelPrefixes: ["maint:"],
712
+ scheduledTaskIds: ["worker-maintenance"],
713
+ emitsDocs: false,
714
+ downstreamIssueKinds: true
715
+ },
716
+ "meeting-analysis": {
717
+ typeLabels: ["meeting-processing"],
718
+ phaseLabelPrefixes: ["meeting:"],
719
+ scheduledTaskIds: ["worker-meeting-analysis"],
720
+ emitsDocs: true,
721
+ downstreamIssueKinds: true
722
+ },
723
+ orchestrator: {
724
+ // The orchestrator pipeline manager itself ships with the
725
+ // `worker-orchestrator` scheduled task. It owns no `type:*` label
726
+ // (it dispatches every type) and no phase-label prefix.
727
+ typeLabels: [],
728
+ phaseLabelPrefixes: [],
729
+ scheduledTaskIds: ["worker-orchestrator"],
730
+ emitsDocs: false,
731
+ downstreamIssueKinds: false
732
+ },
733
+ "people-profile": {
734
+ typeLabels: ["people-profile"],
735
+ phaseLabelPrefixes: ["people:"],
736
+ scheduledTaskIds: ["worker-people-profile"],
737
+ emitsDocs: true,
738
+ downstreamIssueKinds: true
739
+ },
740
+ "pr-review": {
741
+ typeLabels: ["pr-review"],
742
+ phaseLabelPrefixes: [],
743
+ scheduledTaskIds: ["worker-pr-review"],
744
+ emitsDocs: false,
745
+ downstreamIssueKinds: false
746
+ },
747
+ "regulatory-research": {
748
+ typeLabels: ["regulatory-research"],
749
+ phaseLabelPrefixes: ["regulatory:"],
750
+ scheduledTaskIds: ["worker-regulatory-research"],
751
+ emitsDocs: true,
752
+ downstreamIssueKinds: true
753
+ },
754
+ "requirements-analyst": {
755
+ typeLabels: ["requirement"],
756
+ phaseLabelPrefixes: ["req:"],
757
+ scheduledTaskIds: ["worker-requirements-analyst"],
758
+ emitsDocs: true,
759
+ downstreamIssueKinds: true
760
+ },
761
+ "requirements-reviewer": {
762
+ // Co-owns `type:requirement` with the analyst and writer; owns
763
+ // the `req:review` and `req:deprecate` phase labels exactly.
764
+ typeLabels: ["requirement"],
765
+ phaseLabelPrefixes: ["req:review", "req:deprecate"],
766
+ scheduledTaskIds: ["worker-requirements-reviewer"],
767
+ emitsDocs: true,
768
+ downstreamIssueKinds: true
769
+ },
770
+ "requirements-writer": {
771
+ // Co-owns `type:requirement` with the analyst and reviewer; owns
772
+ // the `req:write` phase label exactly.
773
+ typeLabels: ["requirement"],
774
+ phaseLabelPrefixes: ["req:write"],
775
+ scheduledTaskIds: ["worker-requirements-writer"],
776
+ emitsDocs: true,
777
+ downstreamIssueKinds: true
778
+ },
779
+ "research-pipeline": {
780
+ typeLabels: ["research"],
781
+ phaseLabelPrefixes: ["research:"],
782
+ scheduledTaskIds: ["worker-research"],
783
+ emitsDocs: true,
784
+ downstreamIssueKinds: true
785
+ },
786
+ "software-profile": {
787
+ typeLabels: ["software-profile"],
788
+ phaseLabelPrefixes: ["software:"],
789
+ scheduledTaskIds: ["worker-software-profile"],
790
+ emitsDocs: true,
791
+ downstreamIssueKinds: true
792
+ },
793
+ "standards-research": {
794
+ typeLabels: ["standards-research"],
795
+ phaseLabelPrefixes: ["standards:"],
796
+ scheduledTaskIds: ["worker-standards-research"],
797
+ emitsDocs: true,
798
+ downstreamIssueKinds: true
799
+ }
800
+ };
801
+ var CONVENTIONAL_COMMIT_TYPE_LABELS = [
802
+ "type:chore",
803
+ "type:docs",
804
+ "type:feat",
805
+ "type:fix",
806
+ "type:hotfix",
807
+ "type:refactor",
808
+ "type:release"
809
+ ];
810
+ var PHASE_LABEL_TYPE_MAP = buildPhaseLabelTypeMap();
811
+ function typeLabelForPhaseLabel(phaseLabel) {
812
+ const exact = PHASE_LABEL_TYPE_MAP[phaseLabel];
813
+ if (exact !== void 0 && !phaseLabel.endsWith(":")) {
814
+ return exact;
815
+ }
816
+ let bestMatcher;
817
+ for (const matcher of Object.keys(PHASE_LABEL_TYPE_MAP)) {
818
+ if (!matcher.endsWith(":")) {
819
+ continue;
820
+ }
821
+ if (!phaseLabel.startsWith(matcher)) {
822
+ continue;
823
+ }
824
+ if (bestMatcher === void 0 || matcher.length > bestMatcher.length) {
825
+ bestMatcher = matcher;
826
+ }
827
+ }
828
+ return bestMatcher === void 0 ? void 0 : PHASE_LABEL_TYPE_MAP[bestMatcher];
829
+ }
830
+ function resolveTypeLabelForLabels(labels) {
831
+ const phaseLabels = [];
832
+ const candidates = /* @__PURE__ */ new Set();
833
+ for (const label of labels) {
834
+ const typeLabel = typeLabelForPhaseLabel(label);
835
+ if (typeLabel === void 0) {
836
+ continue;
837
+ }
838
+ phaseLabels.push(label);
839
+ candidates.add(typeLabel);
840
+ }
841
+ const candidateTypeLabels = Array.from(candidates).sort();
842
+ if (candidateTypeLabels.length === 0) {
843
+ return { outcome: "none", candidateTypeLabels: [], phaseLabels: [] };
844
+ }
845
+ if (candidateTypeLabels.length === 1) {
846
+ return {
847
+ outcome: "match",
848
+ typeLabel: candidateTypeLabels[0],
849
+ candidateTypeLabels,
850
+ phaseLabels
851
+ };
852
+ }
853
+ return { outcome: "ambiguous", candidateTypeLabels, phaseLabels };
854
+ }
855
+ function renderPhaseTypeInvariantSection(excludeBundles = []) {
856
+ const rows = Object.keys(PHASE_LABEL_TYPE_MAP).filter(
857
+ (matcher) => !isPhaseLabelMatcherOwnedByExcluded(matcher, excludeBundles)
858
+ ).sort().map((matcher) => {
859
+ const display = matcher.endsWith(":") ? `${matcher}*` : matcher;
860
+ const kind = matcher.endsWith(":") ? "prefix" : "exact";
861
+ return `| \`${display}\` | ${kind} | \`${PHASE_LABEL_TYPE_MAP[matcher]}\` |`;
862
+ });
863
+ return [
864
+ "## Phase-label \u2192 `type:<bundle>` invariant",
865
+ "",
866
+ "Every phased-pipeline bundle pairs its `<bundle>:<phase>` labels",
867
+ "with exactly one `type:<bundle>` label. That type label is the",
868
+ "**dedup + dispatch signal**: agents de-duplicate downstream work",
869
+ 'with `gh issue list --label "type:<bundle>"`, and Phase E derives',
870
+ "its funnel-tier sort key from the issue's `type:*` label. An issue",
871
+ "that carries the phase label but a conventional-commit `type:*`",
872
+ "(`type:feat`, `type:docs`, \u2026, stamped from its title prefix by the",
873
+ "generic create-issue workflow) is invisible to that dedup query and",
874
+ "mis-tiers in dispatch.",
875
+ "",
876
+ "The pairing below is derived from the canonical bundle-ownership",
877
+ "map \u2014 the same source of truth that generates the label registry.",
878
+ "Matchers ending in `*` match by prefix; the rest match exactly, and",
879
+ "an exact match always beats a prefix match.",
880
+ "",
881
+ "| Phase label | Match | Required type label |",
882
+ "|-------------|-------|---------------------|",
883
+ ...rows,
884
+ "",
885
+ "### Enforcement",
886
+ "",
887
+ "The Phase D maintenance sweep runs the invariant in auto-correct",
888
+ "mode and emits one summary line:",
889
+ "",
890
+ "```",
891
+ "LABEL_INVARIANT_DONE mode=fix checked=<N> ok=<O> violations=<V> corrected=<C> flagged=<F>",
892
+ "```",
893
+ "",
894
+ "A **report-only** audit is available for repos that prefer",
895
+ "report-then-fix over auto-correct \u2014 same sweep, no mutations:",
896
+ "",
897
+ "```bash",
898
+ ".claude/procedures/check-blocked.sh label-invariant # report only",
899
+ ".claude/procedures/check-blocked.sh label-invariant --fix # apply",
900
+ "```",
901
+ "",
902
+ "### Correction policy",
903
+ "",
904
+ "An issue must carry **exactly one** `type:*` label. Merely *adding*",
905
+ "the required type label to an issue that already carries a",
906
+ "conventional-commit one would leave two, making the funnel-tier sort",
907
+ "key (which reads the **first** `type:*` label) non-deterministic and",
908
+ "violating the label conventions. The correction therefore",
909
+ "**replaces**, in a single atomic `gh issue edit`, so the issue is",
910
+ "never observable carrying two `type:*` labels:",
911
+ "",
912
+ "| Issue state | Action |",
913
+ "|-------------|--------|",
914
+ "| Required type label present, nothing else | none \u2014 compliant, left untouched |",
915
+ "| No `type:*` label at all | **add** the required label |",
916
+ "| Only conventional-commit `type:*` label(s) | **replace** \u2014 remove them, add the required label |",
917
+ "| Required label present **plus** a stray conventional-commit one | **remove** the stray, leaving exactly one |",
918
+ "| A `type:*` label owned by a **different** bundle | **flag** `status:needs-attention` \u2014 never guessed at |",
919
+ "| Phase labels imply **two or more** type labels | **flag** `status:needs-attention` \u2014 never auto-corrected |",
920
+ "",
921
+ "Only a conventional-commit `type:*` label is ever removed. A",
922
+ "`type:*` label owned by another bundle carries real routing",
923
+ "information, so the sweep refuses to guess and hands the issue to a",
924
+ "human instead. Ambiguity is narrower than it looks: all three",
925
+ "requirements bundles declare `type:requirement`, so every `req:*`",
926
+ "phase label resolves to the same type label \u2014 genuine ambiguity",
927
+ "needs phase labels from two bundles with *different* type labels.",
928
+ "",
929
+ "`status:needs-attention` is applied **additively** \u2014 the base",
930
+ "`status:*` label always stays, per the additive-flag rule. Flagging",
931
+ "is idempotent: an issue already carrying the flag is reported but",
932
+ "not re-flagged."
933
+ ].join("\n");
934
+ }
935
+ function isPhaseLabelMatcherOwnedByExcluded(matcher, excludeBundles) {
936
+ if (excludeBundles.length === 0) {
937
+ return false;
938
+ }
939
+ let owned = false;
940
+ for (const [bundleName, ownership] of Object.entries(BUNDLE_OWNERSHIP)) {
941
+ if (!ownership.phaseLabelPrefixes.includes(matcher)) {
942
+ continue;
943
+ }
944
+ owned = true;
945
+ if (!excludeBundles.includes(bundleName)) {
946
+ return false;
947
+ }
948
+ }
949
+ return owned;
950
+ }
951
+ function renderPhaseTypeInvariantShellHelpers() {
952
+ const matchers = Object.keys(PHASE_LABEL_TYPE_MAP);
953
+ const exactMatchers = matchers.filter((m) => !m.endsWith(":")).sort();
954
+ const prefixMatchers = matchers.filter((m) => m.endsWith(":")).sort((a, b) => b.length - a.length || a.localeCompare(b));
955
+ const lines = [
956
+ "# Resolve ONE phase label to the `type:<bundle>` label its owning",
957
+ "# bundle declares. Echoes the label (with the `type:` prefix) or",
958
+ "# nothing when no bundle owns it. Exact-match branches come first,",
959
+ "# so `req:write` (requirements-writer) beats the `req:` prefix",
960
+ "# (requirements-analyst). Generated from BUNDLE_OWNERSHIP \u2014 do not",
961
+ "# hand-edit.",
962
+ "phase_label_type_of() {",
963
+ ' case "${1:-}" in'
964
+ ];
965
+ for (const matcher of exactMatchers) {
966
+ lines.push(` ${matcher}) echo "${PHASE_LABEL_TYPE_MAP[matcher]}" ;;`);
967
+ }
968
+ for (const matcher of prefixMatchers) {
969
+ lines.push(` ${matcher}*) echo "${PHASE_LABEL_TYPE_MAP[matcher]}" ;;`);
970
+ }
971
+ lines.push(
972
+ " *) : ;;",
973
+ " esac",
974
+ "}",
975
+ "",
976
+ "# Return 0 when the argument is a conventional-commit `type:*`",
977
+ "# label \u2014 the ONLY type labels the invariant correction may remove.",
978
+ "# A `type:*` label owned by another bundle is never removed; the",
979
+ "# issue is flagged for human triage instead.",
980
+ "is_conventional_type_label() {",
981
+ ` case "\${1:-}" in`,
982
+ ` ${CONVENTIONAL_COMMIT_TYPE_LABELS.join("|")}) return 0 ;;`,
983
+ " *) return 1 ;;",
984
+ " esac",
985
+ "}",
986
+ "",
987
+ "# Resolve an issue's FULL label list (one label per line on stdin)",
988
+ "# to the type label the phase-label invariant requires. Emits",
989
+ "# KEY=VALUE assignments the caller parses:",
990
+ "# OUTCOME=none \u2014 no recognised phase label; invariant N/A",
991
+ "# OUTCOME=match \u2014 plus TYPE_LABEL=<type:bundle>",
992
+ "# OUTCOME=ambiguous \u2014 plus CANDIDATE_TYPE_LABELS=<space-separated>",
993
+ "# PHASE_LABELS=<space-separated recognised phase labels>",
994
+ "phase_type_of() {",
995
+ " local label resolved",
996
+ ' local matched_types=""',
997
+ ' local matched_phase=""',
998
+ " while IFS= read -r label; do",
999
+ ' [ -z "$label" ] && continue',
1000
+ ' resolved=$(phase_label_type_of "$label")',
1001
+ ' [ -z "$resolved" ] && continue',
1002
+ ' matched_phase="${matched_phase}${label} "',
1003
+ ' case " ${matched_types} " in',
1004
+ ' *" ${resolved} "*) ;;',
1005
+ ' *) matched_types="${matched_types}${resolved} " ;;',
1006
+ " esac",
1007
+ " done",
1008
+ " local count=0",
1009
+ " for resolved in $matched_types; do",
1010
+ " count=$((count + 1))",
1011
+ " done",
1012
+ ' if [ "$count" -eq 0 ]; then',
1013
+ ' echo "OUTCOME=none"',
1014
+ ' elif [ "$count" -eq 1 ]; then',
1015
+ ' echo "OUTCOME=match"',
1016
+ ' echo "TYPE_LABEL=${matched_types% }"',
1017
+ " else",
1018
+ ' echo "OUTCOME=ambiguous"',
1019
+ ' echo "CANDIDATE_TYPE_LABELS=${matched_types% }"',
1020
+ " fi",
1021
+ ' echo "PHASE_LABELS=${matched_phase% }"',
1022
+ "}"
1023
+ );
1024
+ return lines.join("\n");
1025
+ }
1026
+ function buildPhaseLabelTypeMap() {
1027
+ const map = {};
1028
+ for (const [bundleName, ownership] of Object.entries(BUNDLE_OWNERSHIP)) {
1029
+ if (ownership.phaseLabelPrefixes.length === 0) {
1030
+ continue;
1031
+ }
1032
+ if (ownership.typeLabels.length !== 1) {
1033
+ throw new Error(
1034
+ `BUNDLE_OWNERSHIP["${bundleName}"] declares ${ownership.phaseLabelPrefixes.length} phase-label matcher(s) but ${ownership.typeLabels.length} type label(s); a phase label must pair with exactly one type label`
1035
+ );
1036
+ }
1037
+ const typeLabel = `type:${ownership.typeLabels[0]}`;
1038
+ for (const matcher of ownership.phaseLabelPrefixes) {
1039
+ const existing = map[matcher];
1040
+ if (existing !== void 0 && existing !== typeLabel) {
1041
+ throw new Error(
1042
+ `Phase-label matcher "${matcher}" resolves to both "${existing}" and "${typeLabel}"; a matcher must imply exactly one type label`
1043
+ );
1044
+ }
1045
+ map[matcher] = typeLabel;
1046
+ }
1047
+ }
1048
+ return map;
1049
+ }
1050
+ function isTypeLabelOwnedByExcluded(typeLabel, excludedBundles) {
1051
+ if (excludedBundles.length === 0) {
1052
+ return false;
1053
+ }
1054
+ for (const bundleName of excludedBundles) {
1055
+ const ownership = BUNDLE_OWNERSHIP[bundleName];
1056
+ if (!ownership) {
1057
+ continue;
1058
+ }
1059
+ if (ownership.typeLabels.includes(typeLabel)) {
1060
+ const owners = findOwnersOfTypeLabel(typeLabel);
1061
+ const allExcluded = owners.every(
1062
+ (owner) => excludedBundles.includes(owner)
1063
+ );
1064
+ if (allExcluded) {
1065
+ return true;
1066
+ }
1067
+ }
1068
+ }
1069
+ return false;
1070
+ }
1071
+ function isPhaseLabelOwnedByExcluded(phaseLabel, excludedBundles) {
1072
+ if (excludedBundles.length === 0) {
1073
+ return false;
1074
+ }
1075
+ for (const bundleName of excludedBundles) {
1076
+ const ownership = BUNDLE_OWNERSHIP[bundleName];
1077
+ if (!ownership) {
1078
+ continue;
1079
+ }
1080
+ for (const entry of ownership.phaseLabelPrefixes) {
1081
+ if (entry.endsWith(":")) {
1082
+ if (phaseLabel.startsWith(entry)) {
1083
+ return true;
1084
+ }
1085
+ } else if (phaseLabel === entry) {
1086
+ return true;
1087
+ }
1088
+ }
1089
+ }
1090
+ return false;
1091
+ }
1092
+ function isScheduledTaskOwnedByExcluded(taskId, excludedBundles) {
1093
+ if (excludedBundles.length === 0) {
1094
+ return false;
1095
+ }
1096
+ for (const bundleName of excludedBundles) {
1097
+ const ownership = BUNDLE_OWNERSHIP[bundleName];
1098
+ if (!ownership) {
1099
+ continue;
1100
+ }
1101
+ if (ownership.scheduledTaskIds.includes(taskId)) {
1102
+ return true;
1103
+ }
1104
+ }
1105
+ return false;
1106
+ }
1107
+ function hasAnyDocsEmittingBundle(excludedBundles) {
1108
+ for (const [bundleName, ownership] of Object.entries(BUNDLE_OWNERSHIP)) {
1109
+ if (!ownership.emitsDocs) {
1110
+ continue;
1111
+ }
1112
+ if (!excludedBundles.includes(bundleName)) {
1113
+ return true;
1114
+ }
1115
+ }
1116
+ return false;
1117
+ }
1118
+ function hasAnyDownstreamIssueKindBundle(excludedBundles) {
1119
+ for (const [bundleName, ownership] of Object.entries(BUNDLE_OWNERSHIP)) {
1120
+ if (!ownership.downstreamIssueKinds) {
1121
+ continue;
1122
+ }
1123
+ if (!excludedBundles.includes(bundleName)) {
1124
+ return true;
1125
+ }
1126
+ }
1127
+ return false;
1128
+ }
1129
+ function findOwnersOfTypeLabel(typeLabel) {
1130
+ const owners = [];
1131
+ for (const [bundleName, ownership] of Object.entries(BUNDLE_OWNERSHIP)) {
1132
+ if (ownership.typeLabels.includes(typeLabel)) {
1133
+ owners.push(bundleName);
1134
+ }
1135
+ }
1136
+ return owners;
1137
+ }
1138
+
1139
+ // src/agent/bundles/issue-type.ts
1140
+ var GITHUB_ISSUE_TYPES = ["Epic", "Feature", "Bug", "Task"];
1141
+ var DEFAULT_GITHUB_ISSUE_TYPE = "Task";
1142
+ var NON_DEFAULT_TITLE_PREFIX_TYPES = {
1143
+ "epic:": "Epic",
1144
+ "feat:": "Feature",
1145
+ "fix:": "Bug"
1146
+ };
1147
+ var GITHUB_ISSUE_TYPE_BY_TITLE_PREFIX = buildTitlePrefixTypeMap();
1148
+ function githubIssueTypeForTitle(title) {
1149
+ const colon = title.indexOf(":");
1150
+ if (colon === -1) {
1151
+ return DEFAULT_GITHUB_ISSUE_TYPE;
1152
+ }
1153
+ const prefix2 = title.slice(0, colon + 1).trim().toLowerCase();
1154
+ return GITHUB_ISSUE_TYPE_BY_TITLE_PREFIX[prefix2] ?? DEFAULT_GITHUB_ISSUE_TYPE;
1155
+ }
1156
+ var SET_ISSUE_TYPE_HELPER_PATH = ".claude/procedures/set-issue-type.sh";
1157
+ function renderSetIssueTypeFallbackLines() {
1158
+ return [
1159
+ "```bash",
1160
+ "gh api graphql -f query='query($owner:String!,$repo:String!){repository(owner:$owner,name:$repo){issueTypes(first:50){nodes{id name}}}}' -f owner=<owner> -f repo=<repo>",
1161
+ "gh issue view <issue-number> --json id --jq '.id'",
1162
+ "gh api graphql -f query='mutation($issueId:ID!,$typeId:ID!){updateIssueIssueType(input:{issueId:$issueId,issueTypeId:$typeId}){issue{number issueType{name}}}}' -f issueId=<issue-node-id> -f typeId=<issue-type-node-id>",
1163
+ "```"
1164
+ ];
1165
+ }
1166
+ function renderGithubIssueTypeSectionLines() {
1167
+ const defaultPrefixes = Object.keys(GITHUB_ISSUE_TYPE_BY_TITLE_PREFIX).filter(
1168
+ (prefix2) => GITHUB_ISSUE_TYPE_BY_TITLE_PREFIX[prefix2] === DEFAULT_GITHUB_ISSUE_TYPE
1169
+ ).sort().map((prefix2) => `\`${prefix2}\``).join(", ");
1170
+ return [
1171
+ "## GitHub Issue Type",
1172
+ "",
1173
+ "A GitHub **issue type** is a distinct field from the `type:*` **label**:",
1174
+ "the label routes and dedups, the issue type drives triage and Epic",
1175
+ "tracking. Setting one does not set the other. Derive it from the",
1176
+ "**title prefix**:",
1177
+ "",
1178
+ "| Prefix | GitHub Issue Type |",
1179
+ "|--------|------------------|",
1180
+ "| `epic:` | Epic |",
1181
+ "| `feat:` | Feature |",
1182
+ "| `fix:` | Bug |",
1183
+ `| ${defaultPrefixes} | ${DEFAULT_GITHUB_ISSUE_TYPE} |`,
1184
+ "| any bundle-phase prefix (`company:`, `req:`, \u2026), or no prefix | Task |",
1185
+ "",
1186
+ "### Assigning the GitHub issue type",
1187
+ "",
1188
+ "`gh issue create` **cannot** set the issue type \u2014 there is no `--type`",
1189
+ "flag \u2014 so assign it in a follow-up call immediately after creation, for",
1190
+ "every issue an agent files (auto-enqueued downstream issues included).",
1191
+ "An issue that skips that call stays untyped forever. The",
1192
+ "`github-workflow` bundle ships a one-step helper:",
1193
+ "",
1194
+ "```bash",
1195
+ `${SET_ISSUE_TYPE_HELPER_PATH} <issue-number> <${GITHUB_ISSUE_TYPES.join("|")}>`,
1196
+ "```",
1197
+ "",
1198
+ "When that helper is absent (the bundle is excluded), run the",
1199
+ "`updateIssueIssueType` GraphQL flow it wraps:",
1200
+ "",
1201
+ ...renderSetIssueTypeFallbackLines()
1202
+ ];
1203
+ }
1204
+ function renderTitlePrefixTypeBullets(indent = " ") {
1205
+ const nonDefault = Object.entries(NON_DEFAULT_TITLE_PREFIX_TYPES).map(
1206
+ ([prefix2, issueType]) => `${indent}- \`${prefix2}\` \u2192 ${issueType}`
1207
+ );
1208
+ const defaults = Object.keys(GITHUB_ISSUE_TYPE_BY_TITLE_PREFIX).filter(
1209
+ (prefix2) => GITHUB_ISSUE_TYPE_BY_TITLE_PREFIX[prefix2] === DEFAULT_GITHUB_ISSUE_TYPE
1210
+ ).sort().map((prefix2) => `\`${prefix2}\``).join(", ");
1211
+ return [
1212
+ ...nonDefault,
1213
+ `${indent}- ${defaults} \u2192 ${DEFAULT_GITHUB_ISSUE_TYPE}`,
1214
+ `${indent}- any bundle-phase prefix (\`company:\`, \`req:\`, \`bcm:\`, \u2026) \u2192 ${DEFAULT_GITHUB_ISSUE_TYPE}`
1215
+ ];
1216
+ }
1217
+ function renderGithubIssueTypeSection() {
1218
+ return renderGithubIssueTypeSectionLines().join("\n");
1219
+ }
1220
+ function renderIssueTypeAssignmentStep(options = {}) {
1221
+ const indent = options.indent ?? " ";
1222
+ const issueType = options.issueType ?? DEFAULT_GITHUB_ISSUE_TYPE;
1223
+ const lead = options.bullet ? `${indent}- ` : indent;
1224
+ const cont = options.bullet ? `${indent} ` : indent;
1225
+ return [
1226
+ `${lead}Then assign the new issue's **GitHub issue type** (\`${issueType}\`) \u2014`,
1227
+ `${cont}the \`type:*\` label does not set it:`,
1228
+ `${cont}\`${SET_ISSUE_TYPE_HELPER_PATH} <issue-number> ${issueType}\`. See`,
1229
+ `${cont}**Assigning the GitHub issue type** for the no-helper fallback.`
1230
+ ];
1231
+ }
1232
+ function renderIssueTypeAssignmentBlanket(indent = "", issueType = DEFAULT_GITHUB_ISSUE_TYPE) {
1233
+ return [
1234
+ `${indent}**Every** issue this phase files needs a **GitHub issue type**`,
1235
+ `${indent}assigned right after creation \u2014 the \`type:*\` label does not set`,
1236
+ `${indent}it, and \`gh issue create\` cannot:`,
1237
+ `${indent}\`${SET_ISSUE_TYPE_HELPER_PATH} <issue-number> ${issueType}\`. See`,
1238
+ `${indent}**Assigning the GitHub issue type** for the prefix table (nearly`,
1239
+ `${indent}every downstream issue is a \`${issueType}\`) and the no-helper fallback.`
1240
+ ];
1241
+ }
1242
+ function buildTitlePrefixTypeMap() {
1243
+ const map = {};
1244
+ for (const label of CONVENTIONAL_COMMIT_TYPE_LABELS) {
1245
+ map[`${label.slice("type:".length)}:`] = DEFAULT_GITHUB_ISSUE_TYPE;
1246
+ }
1247
+ for (const [prefix2, issueType] of Object.entries(
1248
+ NON_DEFAULT_TITLE_PREFIX_TYPES
1249
+ )) {
1250
+ map[prefix2] = issueType;
1251
+ }
1252
+ return map;
1253
+ }
1254
+
641
1255
  // src/agent/bundles/project-context.ts
642
1256
  var PROJECT_CONTEXT_PATH = "docs/src/content/docs/project-context.md";
643
1257
  var SUBPROJECT_ROLE_GUIDANCE = [
@@ -1230,6 +1844,9 @@ var draftAgendaSkill = {
1230
1844
  ' - `desired_outcomes` \u2014 1\u20133 bullets describing "done"',
1231
1845
  " - `pre_reads` \u2014 relative paths to existing docs (optional)",
1232
1846
  " - `related_docs` \u2014 relative paths for context (optional)",
1847
+ "",
1848
+ ...renderIssueTypeAssignmentStep(),
1849
+ "",
1233
1850
  "2. Execute Phase 1 (Draft) of the agenda-analyst agent.",
1234
1851
  "3. If the draft phase inline-finalizes (default path), no",
1235
1852
  " downstream issues are created.",
@@ -1294,6 +1911,9 @@ var finalizeAgendaSkill = {
1294
1911
  " - Path to the draft agenda",
1295
1912
  " (`<meetingsRoot>/<YYYY-MM-DD>-<slug>/agenda.md`)",
1296
1913
  " - Any late-added items to incorporate",
1914
+ "",
1915
+ ...renderIssueTypeAssignmentStep(),
1916
+ "",
1297
1917
  "2. Execute Phase 2 (Finalize) of the agenda-analyst agent.",
1298
1918
  "",
1299
1919
  "## Output",
@@ -1585,12 +2205,111 @@ var awsCdkBundle = {
1585
2205
  ]
1586
2206
  };
1587
2207
 
2208
+ // src/agent/bundles/issue-defaults.ts
2209
+ var VALID_STATUS_VALUES = [
2210
+ "ready",
2211
+ "blocked",
2212
+ "in-progress",
2213
+ "ready-for-review",
2214
+ "needs-attention",
2215
+ "done",
2216
+ "deferred"
2217
+ ];
2218
+ var VALID_PRIORITY_VALUES = [
2219
+ "critical",
2220
+ "high",
2221
+ "medium",
2222
+ "low",
2223
+ "trivial"
2224
+ ];
2225
+ var DEFAULT_ISSUE_STATUS = "ready";
2226
+ var DEFAULT_ISSUE_PRIORITY = "medium";
2227
+ var DEFAULT_RESOLVED_ISSUE_DEFAULTS = {
2228
+ defaults: {
2229
+ status: DEFAULT_ISSUE_STATUS,
2230
+ priority: DEFAULT_ISSUE_PRIORITY
2231
+ },
2232
+ overrides: {}
2233
+ };
2234
+ function resolveIssueDefaults(config) {
2235
+ if (config === void 0) {
2236
+ return DEFAULT_RESOLVED_ISSUE_DEFAULTS;
2237
+ }
2238
+ const overrides = {};
2239
+ for (const [phaseLabel, override] of Object.entries(config)) {
2240
+ assertValidPhaseLabel(phaseLabel);
2241
+ assertValidOverride(phaseLabel, override);
2242
+ overrides[phaseLabel] = {
2243
+ status: override.status ?? DEFAULT_ISSUE_STATUS,
2244
+ priority: override.priority ?? DEFAULT_ISSUE_PRIORITY
2245
+ };
2246
+ }
2247
+ return {
2248
+ defaults: {
2249
+ status: DEFAULT_ISSUE_STATUS,
2250
+ priority: DEFAULT_ISSUE_PRIORITY
2251
+ },
2252
+ overrides
2253
+ };
2254
+ }
2255
+ function validateIssueDefaultsConfig(config) {
2256
+ return resolveIssueDefaults(config);
2257
+ }
2258
+ function labelsForPhase(resolved, phaseLabel) {
2259
+ return resolved.overrides[phaseLabel] ?? resolved.defaults;
2260
+ }
2261
+ function assertValidPhaseLabel(phaseLabel) {
2262
+ if (typeof phaseLabel !== "string" || phaseLabel.trim() === "") {
2263
+ throw new Error(
2264
+ "AgentConfigOptions.issueDefaults: phase-label keys must be non-empty strings (e.g. `people:research`)."
2265
+ );
2266
+ }
2267
+ }
2268
+ function assertValidOverride(phaseLabel, override) {
2269
+ if (override === null || typeof override !== "object" || Array.isArray(override)) {
2270
+ throw new Error(
2271
+ `AgentConfigOptions.issueDefaults["${phaseLabel}"] must be an object with optional \`status\` and \`priority\` fields.`
2272
+ );
2273
+ }
2274
+ const { status, priority } = override;
2275
+ if (status === void 0 && priority === void 0) {
2276
+ throw new Error(
2277
+ `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.`
2278
+ );
2279
+ }
2280
+ if (status !== void 0 && !VALID_STATUS_VALUES.includes(status)) {
2281
+ throw new Error(
2282
+ `AgentConfigOptions.issueDefaults["${phaseLabel}"].status="${status}" is not a recognised status value. Allowed values: ${VALID_STATUS_VALUES.join(", ")}.`
2283
+ );
2284
+ }
2285
+ if (priority !== void 0 && !VALID_PRIORITY_VALUES.includes(priority)) {
2286
+ throw new Error(
2287
+ `AgentConfigOptions.issueDefaults["${phaseLabel}"].priority="${priority}" is not a recognised priority value. Allowed values: ${VALID_PRIORITY_VALUES.join(", ")}.`
2288
+ );
2289
+ }
2290
+ }
2291
+
1588
2292
  // src/agent/bundles/issue-templates.ts
1589
2293
  var DEFAULT_ISSUE_TEMPLATES_ENABLED = true;
1590
2294
  var DEFAULT_ISSUE_TEMPLATES_PATH = "docs/src/content/docs/agents/issue-templates.md";
1591
2295
  var DEFAULT_ISSUE_TEMPLATES_BUNDLE_PATH_PATTERNS = [".claude/agents/**/*.md", ".claude/skills/**/*.md"];
1592
2296
  var DEFAULT_ISSUE_TEMPLATES_EMIT_CHECKER = false;
1593
2297
  var DEFAULT_ISSUE_TEMPLATES_EMIT_STARTER = false;
2298
+ var ISSUE_TEMPLATES_GENERATED_SUFFIX = "-generated";
2299
+ function issueTemplatesGeneratedPath(templatesPath) {
2300
+ const slash = templatesPath.lastIndexOf("/");
2301
+ const dot = templatesPath.lastIndexOf(".");
2302
+ if (dot > slash) {
2303
+ return `${templatesPath.slice(0, dot)}${ISSUE_TEMPLATES_GENERATED_SUFFIX}${templatesPath.slice(dot)}`;
2304
+ }
2305
+ return `${templatesPath}${ISSUE_TEMPLATES_GENERATED_SUFFIX}`;
2306
+ }
2307
+ function issueTemplatesChildGlob(templatesPath) {
2308
+ const slash = templatesPath.lastIndexOf("/");
2309
+ const dot = templatesPath.lastIndexOf(".");
2310
+ const stem = dot > slash ? templatesPath.slice(0, dot) : templatesPath;
2311
+ return `${stem}/*.md`;
2312
+ }
1594
2313
  var DEFAULT_ISSUE_TEMPLATES_REQUIRE_REFERENCE = true;
1595
2314
  function resolveIssueTemplates(config) {
1596
2315
  const templatesPath = config?.templatesPath ?? DEFAULT_ISSUE_TEMPLATES_PATH;
@@ -1674,7 +2393,40 @@ function renderIssueTemplatesRuleContent(it, hasDownstreamBundles = true) {
1674
2393
  "issue kind appends one section; retiring a kind removes one. The",
1675
2394
  "label taxonomy, body template shape, and placeholder conventions",
1676
2395
  "are shared across every section.",
1677
- "",
2396
+ ""
2397
+ ];
2398
+ if (it.emitStarterDoc) {
2399
+ lines.push(
2400
+ "## Scaffolded Starting Point",
2401
+ "",
2402
+ "The page is hand-authored, but it does not start empty. Two files",
2403
+ "ship together:",
2404
+ "",
2405
+ `1. \`${it.templatesPath}\` \u2014 **written once**, then yours. Titles,`,
2406
+ " bodies, and the callers list are hand-authored here and never",
2407
+ " overwritten.",
2408
+ `2. \`${issueTemplatesGeneratedPath(it.templatesPath)}\` \u2014`,
2409
+ " **regenerated on every synth**. One",
2410
+ " `## Template: <phase-label>` stub per phase label the active",
2411
+ " bundles contribute to the label registry, with the phase label,",
2412
+ " the `type:<bundle>` label its owning bundle declares, and the",
2413
+ " configured `status:*` / `priority:*` defaults pre-filled.",
2414
+ "",
2415
+ "Only the **label set** is generated. Title and body stay",
2416
+ "angle-bracket placeholders, so the scaffold answers *which labels*",
2417
+ "without dictating *what the issue says*. Copy a stub into the",
2418
+ "hand-authored page, flesh out its body, and leave the label block",
2419
+ "as generated.",
2420
+ "",
2421
+ "The split is load-bearing. A write-once scaffold alone freezes",
2422
+ "every consumer on whatever skeleton shipped the day they adopted",
2423
+ "the convention \u2014 a new phase label minted upstream never reaches",
2424
+ "them. Regenerating the label half fixes that without ever",
2425
+ "clobbering an authored body.",
2426
+ ""
2427
+ );
2428
+ }
2429
+ lines.push(
1678
2430
  "## Reference-Don't-Inline Rule",
1679
2431
  "",
1680
2432
  `Every bundle rule, agent prompt, or skill instruction that files a`,
@@ -1745,8 +2497,12 @@ function renderIssueTemplatesRuleContent(it, hasDownstreamBundles = true) {
1745
2497
  " placeholders (`<Organization Name>`, `<slug>`) that match",
1746
2498
  " existing templates. Consistency across sections lets callers",
1747
2499
  " substitute without re-reading the body every time.",
2500
+ "5. **Does the recipe assign a GitHub issue type?** `gh issue",
2501
+ " create` cannot set it and the `type:*` label does not either, so",
2502
+ " every section ends with the follow-up call. An issue that skips",
2503
+ " it stays untyped forever.",
1748
2504
  ""
1749
- ];
2505
+ );
1750
2506
  if (it.emitChecker) {
1751
2507
  lines.push(
1752
2508
  "## Automated Lint",
@@ -1777,6 +2533,33 @@ function renderIssueTemplatesRuleContent(it, hasDownstreamBundles = true) {
1777
2533
  "(which documents the conventional `gh issue create` call shape",
1778
2534
  "as taxonomy, not a downstream template). All other bundle",
1779
2535
  "files in the pattern set are in scope.",
2536
+ "",
2537
+ "### Recipe label correctness",
2538
+ "",
2539
+ "A companion lint,",
2540
+ "`.claude/procedures/check-issue-template-labels.sh`, checks the",
2541
+ "recipes themselves rather than where they live. It walks the",
2542
+ "templates page, its router-style child pages, and the generated",
2543
+ "label-set companion, and fails non-zero when a",
2544
+ "`## Template: <phase-label>` section:",
2545
+ "",
2546
+ "- never passes `--label <phase-label>` \u2014 the heading and the",
2547
+ " command disagree;",
2548
+ "- carries no `type:*` label, more than one, or one that is not",
2549
+ " the `type:<bundle>` label the phase label's owning bundle",
2550
+ " declares;",
2551
+ "- has no GitHub issue-type assignment step.",
2552
+ "",
2553
+ "```bash",
2554
+ ".claude/procedures/check-issue-template-labels.sh",
2555
+ "```",
2556
+ "",
2557
+ "The phase-label \u2192 type-label pairing is generated from the same",
2558
+ "bundle-ownership map that drives `.github/labels.yml` and the",
2559
+ "orchestrator's invariant sweep, so a hand-edit can never",
2560
+ "reintroduce a mismatch the registry has already ruled out.",
2561
+ "Sections whose heading matches no bundle-owned phase label are",
2562
+ "skipped \u2014 consumer-specific labels are deliberately not policed.",
1780
2563
  ""
1781
2564
  );
1782
2565
  }
@@ -1785,8 +2568,10 @@ function renderIssueTemplatesRuleContent(it, hasDownstreamBundles = true) {
1785
2568
  "",
1786
2569
  "The convention intentionally does not:",
1787
2570
  "",
1788
- "- Auto-generate the templates page \u2014 repo-specific phase labels",
1789
- " and body conventions are too varied for a one-size template.",
2571
+ "- Auto-generate recipe **bodies**. Only the label set is",
2572
+ " scaffolded, because labels are derivable from the bundle",
2573
+ " registry and bodies are not \u2014 repo-specific objectives,",
2574
+ " acceptance criteria, and callers lists stay hand-authored.",
1790
2575
  "- Prescribe a fixed body shape across templates. Each section",
1791
2576
  " may carry its own `## Acceptance Criteria`, `## Scope Size`,",
1792
2577
  " or `## Output Path` fields as the downstream agent requires;",
@@ -1817,7 +2602,8 @@ function renderIssueTemplatesBundleHook(it, bundleLabel) {
1817
2602
  "as an inline template vs. a prose mention."
1818
2603
  ].join("\n");
1819
2604
  }
1820
- function renderIssueTemplatesStarterPage(_it) {
2605
+ function renderIssueTemplatesStarterPage(it) {
2606
+ const generatedLink = `./${basename(issueTemplatesGeneratedPath(it.templatesPath))}`;
1821
2607
  return [
1822
2608
  "---",
1823
2609
  "title: Issue Templates",
@@ -1839,11 +2625,33 @@ function renderIssueTemplatesStarterPage(_it) {
1839
2625
  "`status:*` label from the conventions documented in the",
1840
2626
  "`issue-label-conventions` rule.",
1841
2627
  "",
2628
+ "Every recipe also ends with a **GitHub issue type** assignment.",
2629
+ "`gh issue create` cannot set the issue type, so it takes a second",
2630
+ "call \u2014 an issue that skips it stays untyped forever. Derive the type",
2631
+ "from the title prefix (`epic:` \u2192 Epic, `feat:` \u2192 Feature, `fix:` \u2192",
2632
+ "Bug, everything else \u2014 including every bundle-phase prefix \u2014 \u2192",
2633
+ "Task); see **Assigning the GitHub issue type** in the",
2634
+ "`issue-conventions` rule for the command and its fallback.",
2635
+ "",
1842
2636
  "Before creating any profile-style issue (people, companies,",
1843
2637
  "software), run the duplicate check documented in the owning",
1844
2638
  "bundle's rule \u2014 the issue-templates page is a recipe catalogue,",
1845
2639
  "not a duplicate-check layer.",
1846
2640
  "",
2641
+ "## Generated label sets",
2642
+ "",
2643
+ `[Generated Issue-Template Label Sets](${generatedLink}) carries one`,
2644
+ "correct-by-construction stub per phase label the active bundles",
2645
+ "emit \u2014 the phase label paired with the `type:<bundle>` label its",
2646
+ "owning bundle declares, plus the configured `status:*` /",
2647
+ "`priority:*` defaults. That page regenerates on every `projen` run,",
2648
+ "so it never goes stale as phase labels come and go.",
2649
+ "",
2650
+ "This page is the opposite: written once, then owned by you. Copy a",
2651
+ "stub across, flesh out its title and body here, and leave the label",
2652
+ "block exactly as generated \u2014 the label-consistency lint holds both",
2653
+ "copies to the same pairing.",
2654
+ "",
1847
2655
  "## Template: example-phase",
1848
2656
  "",
1849
2657
  "Replace this example with the real templates for this repo. Each",
@@ -1873,9 +2681,125 @@ function renderIssueTemplatesStarterPage(_it) {
1873
2681
  "- [ ] <criterion 1>",
1874
2682
  "- [ ] <criterion 2>",
1875
2683
  '"',
2684
+ "```",
2685
+ "",
2686
+ "Then assign the GitHub issue type (`Task` for a phase-labelled",
2687
+ "title):",
2688
+ "",
2689
+ "```bash",
2690
+ `${SET_ISSUE_TYPE_HELPER_PATH} <issue-number> ${DEFAULT_GITHUB_ISSUE_TYPE}`,
1876
2691
  "```"
1877
2692
  ].join("\n");
1878
2693
  }
2694
+ function collectIssueTemplateRecipeStubs(bundles, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS) {
2695
+ const stubs = /* @__PURE__ */ new Map();
2696
+ for (const bundle of bundles) {
2697
+ for (const label of bundle.labels ?? []) {
2698
+ const typeLabel = typeLabelForPhaseLabel(label.name);
2699
+ if (typeLabel === void 0) {
2700
+ continue;
2701
+ }
2702
+ if (stubs.has(label.name)) {
2703
+ continue;
2704
+ }
2705
+ const defaults = labelsForPhase(issueDefaults, label.name);
2706
+ stubs.set(label.name, {
2707
+ phaseLabel: label.name,
2708
+ typeLabel,
2709
+ bundleName: bundle.name,
2710
+ description: label.description ?? "",
2711
+ status: defaults.status,
2712
+ priority: defaults.priority,
2713
+ issueType: githubIssueTypeForTitle(`${label.name}: placeholder`)
2714
+ });
2715
+ }
2716
+ }
2717
+ return [...stubs.values()].sort(
2718
+ (a, b) => a.phaseLabel.localeCompare(b.phaseLabel)
2719
+ );
2720
+ }
2721
+ function renderIssueTemplatesGeneratedPage(it, stubs) {
2722
+ const lines = [
2723
+ "---",
2724
+ "title: Generated Issue-Template Label Sets",
2725
+ "description: Auto-generated label-correct gh issue create stubs \u2014 one per phase label.",
2726
+ "---",
2727
+ "",
2728
+ "**Generated file \u2014 do not edit.** Every section below is",
2729
+ "regenerated from the active bundle set on each `projen` run; hand",
2730
+ "edits are overwritten. The hand-authored recipes live in",
2731
+ `[the issue-templates page](./${basename(it.templatesPath)}) \u2014 this`,
2732
+ "page exists so the **label half** of every recipe stays",
2733
+ "correct-by-construction as phase labels come and go.",
2734
+ "",
2735
+ "## How to use",
2736
+ "",
2737
+ "Each section carries the exact label set the phase-label invariant",
2738
+ "requires: the phase label itself, the `type:<bundle>` label its",
2739
+ "owning bundle declares, and the configured `status:*` / `priority:*`",
2740
+ "defaults for that phase. Title and body are placeholders \u2014 copy the",
2741
+ "stub into the hand-authored templates page and flesh out the body",
2742
+ "there, or run it as-is when no repo-specific body exists yet.",
2743
+ "",
2744
+ "Every stub ends with the **GitHub issue type** assignment. `gh issue",
2745
+ "create` cannot set the issue type and the `type:*` label does not",
2746
+ "set it either, so an issue that skips the follow-up call stays",
2747
+ "untyped forever. See **Assigning the GitHub issue type** in the",
2748
+ "`issue-conventions` rule for the no-helper fallback.",
2749
+ ""
2750
+ ];
2751
+ if (stubs.length === 0) {
2752
+ lines.push(
2753
+ "## No phase labels",
2754
+ "",
2755
+ "No active bundle contributes a phase label, so there is nothing to",
2756
+ "scaffold. Enable a phased-pipeline bundle (or drop it from",
2757
+ "`excludeBundles`) and re-run `projen`."
2758
+ );
2759
+ return lines.join("\n");
2760
+ }
2761
+ for (const stub of stubs) {
2762
+ lines.push(
2763
+ `## Template: ${stub.phaseLabel}`,
2764
+ "",
2765
+ ...stub.description === "" ? [] : [stub.description, ""],
2766
+ `**Owning bundle:** \`${stub.bundleName}\` \u2014 **required type label:**`,
2767
+ `\`${stub.typeLabel}\` (paired by the bundle-ownership map).`,
2768
+ "",
2769
+ "**Callers:** <name the bundles and agents that file this issue>",
2770
+ "",
2771
+ "```bash",
2772
+ "gh issue create \\",
2773
+ ` --title "${stub.phaseLabel}: <short description>" \\`,
2774
+ ` --label "${stub.typeLabel}" \\`,
2775
+ ` --label "${stub.phaseLabel}" \\`,
2776
+ ` --label "priority:${stub.priority}" \\`,
2777
+ ` --label "status:${stub.status}" \\`,
2778
+ ' --body "## Objective',
2779
+ "",
2780
+ "<1-3 sentences describing the work>.",
2781
+ "",
2782
+ "## Context",
2783
+ "",
2784
+ "- **Discovered in:** <source description> (#<parent-issue>)",
2785
+ "",
2786
+ "## Acceptance Criteria",
2787
+ "",
2788
+ "- [ ] <criterion 1>",
2789
+ '"',
2790
+ "```",
2791
+ "",
2792
+ `Then assign the GitHub issue type (\`${stub.issueType}\`):`,
2793
+ "",
2794
+ "```bash",
2795
+ `${SET_ISSUE_TYPE_HELPER_PATH} <issue-number> ${stub.issueType}`,
2796
+ "```",
2797
+ ""
2798
+ );
2799
+ }
2800
+ lines.pop();
2801
+ return lines.join("\n");
2802
+ }
1879
2803
  function renderIssueTemplatesCheckerScript(it) {
1880
2804
  const patternsLiteral = it.bundlePathPatterns.map((p) => ` ${JSON.stringify(p)}`).join("\n");
1881
2805
  return [
@@ -2064,6 +2988,157 @@ function renderIssueTemplatesCheckerScript(it) {
2064
2988
  "exit 0"
2065
2989
  ].join("\n");
2066
2990
  }
2991
+ function renderIssueTemplateLabelsCheckerScript(it) {
2992
+ const generatedPath = issueTemplatesGeneratedPath(it.templatesPath);
2993
+ const childGlob = issueTemplatesChildGlob(it.templatesPath);
2994
+ return [
2995
+ "#!/usr/bin/env bash",
2996
+ "# check-issue-template-labels.sh \u2014 Enforce recipe label correctness.",
2997
+ "#",
2998
+ "# Usage:",
2999
+ "# .claude/procedures/check-issue-template-labels.sh [<file>...]",
3000
+ "#",
3001
+ "# With no arguments the lint walks the templates page, its",
3002
+ "# router-style child pages, and the generated label-set companion.",
3003
+ "# Positional arguments override that default set.",
3004
+ "#",
3005
+ "# For every `## Template: <phase-label>` section it asserts the",
3006
+ "# recipe passes the heading's phase label, carries exactly one",
3007
+ "# `type:*` label matching the phase-label invariant, and assigns a",
3008
+ "# GitHub issue type. Sections whose heading matches no bundle-owned",
3009
+ "# phase label are skipped \u2014 unrecognised labels are consumer-",
3010
+ "# specific and deliberately not policed.",
3011
+ "#",
3012
+ "# The resolver below is generated from the canonical bundle-",
3013
+ "# ownership map \u2014 do not edit by hand; regenerate via",
3014
+ "# `pnpm exec projen`.",
3015
+ "",
3016
+ "set -uo pipefail",
3017
+ "",
3018
+ "err() {",
3019
+ ' printf "check-issue-template-labels.sh: %s\\n" "$*" >&2',
3020
+ "}",
3021
+ "",
3022
+ renderPhaseTypeInvariantShellHelpers(),
3023
+ "",
3024
+ `templates_path=${JSON.stringify(it.templatesPath)}`,
3025
+ `generated_path=${JSON.stringify(generatedPath)}`,
3026
+ `child_glob=${JSON.stringify(childGlob)}`,
3027
+ "",
3028
+ "# Emit one TAB-separated record per `## Template:` section:",
3029
+ "# <file>\\t<line>\\t<phase-label>\\t<comma-joined labels>\\t<0|1 typed>",
3030
+ "# Quote characters are folded to spaces up front so a recipe written",
3031
+ "# with single quotes, double quotes, or none at all parses the same.",
3032
+ "# Fenced blocks are tracked so the `## Objective` heading inside a",
3033
+ "# recipe's --body string never reads as the end of the section.",
3034
+ "scan_file() {",
3035
+ ' local file="$1"',
3036
+ ` tr '\\042\\047' ' ' < "$file" | awk -v FNAME="$file" '`,
3037
+ " function flush() {",
3038
+ ' if (phase != "") {',
3039
+ ' printf "%s\\t%d\\t%s\\t%s\\t%d\\n", FNAME, start, phase, labels, hastype;',
3040
+ " }",
3041
+ ' phase = ""; labels = ""; hastype = 0;',
3042
+ " }",
3043
+ " {",
3044
+ " if ($0 ~ /^[ \\t]*```/) {",
3045
+ " fence = 1 - fence;",
3046
+ ' } else if (fence == 0 && index($0, "## ") == 1) {',
3047
+ " flush();",
3048
+ ' if (index($0, "## Template: ") == 1) {',
3049
+ " phase = substr($0, 14);",
3050
+ ' gsub(/[ \\t`]/, "", phase);',
3051
+ " start = NR;",
3052
+ " }",
3053
+ " next;",
3054
+ " }",
3055
+ ' if (phase == "") next;',
3056
+ " if ($0 ~ /set-issue-type\\.sh/ || $0 ~ /updateIssueIssueType/) {",
3057
+ " hastype = 1;",
3058
+ " }",
3059
+ " line = $0;",
3060
+ " while (match(line, /--label[ \\t]+[^ \\t]+/)) {",
3061
+ " tok = substr(line, RSTART, RLENGTH);",
3062
+ " line = substr(line, RSTART + RLENGTH);",
3063
+ ' sub(/^--label[ \\t]+/, "", tok);',
3064
+ ' gsub(/[\\\\`]/, "", tok);',
3065
+ ' labels = labels tok ",";',
3066
+ " }",
3067
+ " }",
3068
+ " END { flush(); }",
3069
+ " '",
3070
+ "}",
3071
+ "",
3072
+ "# Collect the file list: positional args override the default set.",
3073
+ "files=()",
3074
+ "if [[ $# -gt 0 ]]; then",
3075
+ ' files=("$@")',
3076
+ "else",
3077
+ ' [[ -f "$templates_path" ]] && files+=("$templates_path")',
3078
+ ' [[ -f "$generated_path" ]] && files+=("$generated_path")',
3079
+ " for child in $child_glob; do",
3080
+ ' [[ -f "$child" ]] && files+=("$child")',
3081
+ " done",
3082
+ "fi",
3083
+ "",
3084
+ "if [[ ${#files[@]} -eq 0 ]]; then",
3085
+ " exit 0",
3086
+ "fi",
3087
+ "",
3088
+ "violations=()",
3089
+ "checked=0",
3090
+ "",
3091
+ 'for file in "${files[@]}"; do',
3092
+ ' [[ -f "$file" ]] || continue',
3093
+ " while IFS=$'\\t' read -r rec_file rec_line phase labels hastype; do",
3094
+ ' [[ -z "$phase" ]] && continue',
3095
+ ' required=$(phase_label_type_of "$phase")',
3096
+ ' [[ -z "$required" ]] && continue',
3097
+ " checked=$(( checked + 1 ))",
3098
+ ' case ",${labels}" in',
3099
+ ' *",${phase},"*) ;;',
3100
+ ` *) violations+=("$rec_file:$rec_line: recipe for '$phase' never passes --label $phase") ;;`,
3101
+ " esac",
3102
+ ' type_labels=""',
3103
+ " type_count=0",
3104
+ ' for entry in $(printf "%s" "$labels" | tr , " "); do',
3105
+ ' case "$entry" in',
3106
+ " type:*)",
3107
+ ' type_labels="${type_labels}${entry} "',
3108
+ " type_count=$(( type_count + 1 ))",
3109
+ " ;;",
3110
+ " esac",
3111
+ " done",
3112
+ ' if [[ "$type_count" -eq 0 ]]; then',
3113
+ ` violations+=("$rec_file:$rec_line: recipe for '$phase' carries no type:* label (expected $required)")`,
3114
+ ' elif [[ "$type_count" -gt 1 ]]; then',
3115
+ " violations+=(\"$rec_file:$rec_line: recipe for '$phase' carries ${type_count} type:* labels (${type_labels% }); exactly one is allowed\")",
3116
+ ' elif [[ "${type_labels% }" != "$required" ]]; then',
3117
+ ` violations+=("$rec_file:$rec_line: recipe for '$phase' carries \${type_labels% } but the phase label requires $required")`,
3118
+ " fi",
3119
+ ' if [[ "$hastype" != "1" ]]; then',
3120
+ ` violations+=("$rec_file:$rec_line: recipe for '$phase' has no GitHub issue-type assignment step")`,
3121
+ " fi",
3122
+ ' done < <(scan_file "$file")',
3123
+ "done",
3124
+ "",
3125
+ "if [[ ${#violations[@]} -gt 0 ]]; then",
3126
+ ' err "issue-template recipes disagree with the phase-label invariant:"',
3127
+ ' for v in "${violations[@]}"; do',
3128
+ ' err " - $v"',
3129
+ " done",
3130
+ ` 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"`,
3131
+ " exit 1",
3132
+ "fi",
3133
+ "",
3134
+ 'printf "check-issue-template-labels.sh: %d recipe(s) OK\\n" "$checked"',
3135
+ "exit 0"
3136
+ ].join("\n");
3137
+ }
3138
+ function basename(path8) {
3139
+ const slash = path8.lastIndexOf("/");
3140
+ return slash === -1 ? path8 : path8.slice(slash + 1);
3141
+ }
2067
3142
  function assertValidTemplatesPath(value) {
2068
3143
  if (typeof value !== "string" || value.trim().length === 0) {
2069
3144
  throw new Error(
@@ -4977,16 +6052,7 @@ function buildBaseBundle(paths = DEFAULT_AGENT_PATHS) {
4977
6052
  "| `release:` | Release preparation, version bumps |",
4978
6053
  "| `hotfix:` | Urgent production fixes |",
4979
6054
  "",
4980
- "## GitHub Issue Type",
4981
- "",
4982
- "When creating issues, always assign the appropriate **GitHub issue type** based on the title prefix:",
4983
- "",
4984
- "| Prefix | GitHub Issue Type |",
4985
- "|--------|------------------|",
4986
- "| `epic:` | Epic |",
4987
- "| `feat:` | Feature |",
4988
- "| `fix:` | Bug |",
4989
- "| `chore:`, `docs:`, `refactor:`, `release:`, `hotfix:` | Task |",
6055
+ ...renderGithubIssueTypeSectionLines(),
4990
6056
  "",
4991
6057
  "## Prerequisite Issues",
4992
6058
  "",
@@ -5075,90 +6141,6 @@ function buildBaseBundle(paths = DEFAULT_AGENT_PATHS) {
5075
6141
  }
5076
6142
  var baseBundle = buildBaseBundle();
5077
6143
 
5078
- // src/agent/bundles/issue-defaults.ts
5079
- var VALID_STATUS_VALUES = [
5080
- "ready",
5081
- "blocked",
5082
- "in-progress",
5083
- "ready-for-review",
5084
- "needs-attention",
5085
- "done",
5086
- "deferred"
5087
- ];
5088
- var VALID_PRIORITY_VALUES = [
5089
- "critical",
5090
- "high",
5091
- "medium",
5092
- "low",
5093
- "trivial"
5094
- ];
5095
- var DEFAULT_ISSUE_STATUS = "ready";
5096
- var DEFAULT_ISSUE_PRIORITY = "medium";
5097
- var DEFAULT_RESOLVED_ISSUE_DEFAULTS = {
5098
- defaults: {
5099
- status: DEFAULT_ISSUE_STATUS,
5100
- priority: DEFAULT_ISSUE_PRIORITY
5101
- },
5102
- overrides: {}
5103
- };
5104
- function resolveIssueDefaults(config) {
5105
- if (config === void 0) {
5106
- return DEFAULT_RESOLVED_ISSUE_DEFAULTS;
5107
- }
5108
- const overrides = {};
5109
- for (const [phaseLabel, override] of Object.entries(config)) {
5110
- assertValidPhaseLabel(phaseLabel);
5111
- assertValidOverride(phaseLabel, override);
5112
- overrides[phaseLabel] = {
5113
- status: override.status ?? DEFAULT_ISSUE_STATUS,
5114
- priority: override.priority ?? DEFAULT_ISSUE_PRIORITY
5115
- };
5116
- }
5117
- return {
5118
- defaults: {
5119
- status: DEFAULT_ISSUE_STATUS,
5120
- priority: DEFAULT_ISSUE_PRIORITY
5121
- },
5122
- overrides
5123
- };
5124
- }
5125
- function validateIssueDefaultsConfig(config) {
5126
- return resolveIssueDefaults(config);
5127
- }
5128
- function labelsForPhase(resolved, phaseLabel) {
5129
- return resolved.overrides[phaseLabel] ?? resolved.defaults;
5130
- }
5131
- function assertValidPhaseLabel(phaseLabel) {
5132
- if (typeof phaseLabel !== "string" || phaseLabel.trim() === "") {
5133
- throw new Error(
5134
- "AgentConfigOptions.issueDefaults: phase-label keys must be non-empty strings (e.g. `people:research`)."
5135
- );
5136
- }
5137
- }
5138
- function assertValidOverride(phaseLabel, override) {
5139
- if (override === null || typeof override !== "object" || Array.isArray(override)) {
5140
- throw new Error(
5141
- `AgentConfigOptions.issueDefaults["${phaseLabel}"] must be an object with optional \`status\` and \`priority\` fields.`
5142
- );
5143
- }
5144
- const { status, priority } = override;
5145
- if (status === void 0 && priority === void 0) {
5146
- throw new Error(
5147
- `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.`
5148
- );
5149
- }
5150
- if (status !== void 0 && !VALID_STATUS_VALUES.includes(status)) {
5151
- throw new Error(
5152
- `AgentConfigOptions.issueDefaults["${phaseLabel}"].status="${status}" is not a recognised status value. Allowed values: ${VALID_STATUS_VALUES.join(", ")}.`
5153
- );
5154
- }
5155
- if (priority !== void 0 && !VALID_PRIORITY_VALUES.includes(priority)) {
5156
- throw new Error(
5157
- `AgentConfigOptions.issueDefaults["${phaseLabel}"].priority="${priority}" is not a recognised priority value. Allowed values: ${VALID_PRIORITY_VALUES.join(", ")}.`
5158
- );
5159
- }
5160
- }
5161
-
5162
6144
  // src/agent/bundles/bcm-writer.ts
5163
6145
  function buildBcmWriterSubAgent(paths, issueDefaults) {
5164
6146
  return {
@@ -5676,6 +6658,8 @@ function buildBcmWriterSubAgent(paths, issueDefaults) {
5676
6658
  ` - \`priority:${labelsForPhase(issueDefaults, "people:research").priority}\``,
5677
6659
  ` - \`status:${labelsForPhase(issueDefaults, "people:research").status}\``,
5678
6660
  "",
6661
+ ...renderIssueTypeAssignmentStep(),
6662
+ "",
5679
6663
  " **Unfamiliar company (enabling vendor, partner, competitor).**",
5680
6664
  " Hand off to the `company-profile` bundle by creating a",
5681
6665
  " `company:research` issue. The issue must carry:",
@@ -5685,6 +6669,8 @@ function buildBcmWriterSubAgent(paths, issueDefaults) {
5685
6669
  ` - \`priority:${labelsForPhase(issueDefaults, "company:research").priority}\``,
5686
6670
  ` - \`status:${labelsForPhase(issueDefaults, "company:research").status}\``,
5687
6671
  "",
6672
+ ...renderIssueTypeAssignmentStep(),
6673
+ "",
5688
6674
  " **Missing research topic (value stream, market sizing, etc.).**",
5689
6675
  " Hand off to the `research-pipeline` bundle by creating a",
5690
6676
  " `research:scope` issue. The issue must carry:",
@@ -5694,6 +6680,8 @@ function buildBcmWriterSubAgent(paths, issueDefaults) {
5694
6680
  ` - \`priority:${labelsForPhase(issueDefaults, "research:scope").priority}\``,
5695
6681
  ` - \`status:${labelsForPhase(issueDefaults, "research:scope").status}\``,
5696
6682
  "",
6683
+ ...renderIssueTypeAssignmentStep(),
6684
+ "",
5697
6685
  " This phase assumes the `people-profile`, `company-profile`, and",
5698
6686
  " `research-pipeline` bundles are enabled in the consuming project.",
5699
6687
  " If a bundle is not enabled, flag the connect issue with",
@@ -5816,6 +6804,8 @@ function buildWriteBcmSkill(paths, issueDefaults) {
5816
6804
  ` \`priority:${labelsForPhase(issueDefaults, "bcm:outline").priority}\`, and \`status:${labelsForPhase(issueDefaults, "bcm:outline").status}\`. Body must include the`,
5817
6805
  " verbatim capability name and any overrides.",
5818
6806
  "",
6807
+ "",
6808
+ ...renderIssueTypeAssignmentStep(),
5819
6809
  "2. Execute Phase 1 (Outline) of the bcm-writer agent.",
5820
6810
  "",
5821
6811
  "3. Phase 1 creates a `bcm:scaffold` issue, which Phase 2 follows with",
@@ -7096,6 +8086,8 @@ function buildScanBusinessModelsSkill(paths, issueDefaults) {
7096
8086
  "1. Create a `business-models:scan` issue with `type:business-model`,",
7097
8087
  ` \`priority:${labelsForPhase(issueDefaults, "business-models:scan").priority}\`, and \`status:${labelsForPhase(issueDefaults, "business-models:scan").status}\`. The body must include the`,
7098
8088
  " industry slug, the source plan path (if any), and any overrides.",
8089
+ "",
8090
+ ...renderIssueTypeAssignmentStep(),
7099
8091
  "2. Execute Phase 1 (Scan) of the business-models-analyst agent.",
7100
8092
  "3. Phase 1 creates one `business-models:canvas` issue per in-scope",
7101
8093
  " segment. Each canvas issue declares its `Depends on:`",
@@ -7170,6 +8162,8 @@ function buildCanvasBusinessModelSkill(paths, issueDefaults) {
7170
8162
  ` \`type:business-model\`, \`priority:${labelsForPhase(issueDefaults, "business-models:canvas").priority}\`, and \`status:${labelsForPhase(issueDefaults, "business-models:canvas").status}\`.`,
7171
8163
  " The body must include the industry slug, segment slug, and any",
7172
8164
  " scan-report reference.",
8165
+ "",
8166
+ ...renderIssueTypeAssignmentStep(),
7173
8167
  "2. Execute Phase 2 (Canvas) of the business-models-analyst agent.",
7174
8168
  "3. Phase 2 writes the nine-block canvas and creates a",
7175
8169
  " `business-models:complete` issue for the downstream Phase 3.",
@@ -7229,6 +8223,8 @@ function buildCompleteBusinessModelSkill(paths, issueDefaults) {
7229
8223
  "1. Create a `business-models:complete` issue with",
7230
8224
  ` \`type:business-model\`, \`priority:${labelsForPhase(issueDefaults, "business-models:complete").priority}\`, and \`status:${labelsForPhase(issueDefaults, "business-models:complete").status}\`.`,
7231
8225
  " Body must include the canvas document path.",
8226
+ "",
8227
+ ...renderIssueTypeAssignmentStep(),
7232
8228
  "2. Execute Phase 3 (Complete) of the business-models-analyst agent.",
7233
8229
  "",
7234
8230
  "## Output",
@@ -7753,6 +8749,8 @@ function buildCompanyProfileAnalystSubAgent(paths, issueDefaults, tier) {
7753
8749
  ` - \`priority:${labelsForPhase(issueDefaults, "people:research").priority}\``,
7754
8750
  ` - \`status:${labelsForPhase(issueDefaults, "people:research").status}\``,
7755
8751
  "",
8752
+ ...renderIssueTypeAssignmentStep(),
8753
+ "",
7756
8754
  " The issue body must include:",
7757
8755
  " - A **discovery source** line naming this company profile",
7758
8756
  " (`Discovered while profiling: <company name>`)",
@@ -7770,6 +8768,8 @@ function buildCompanyProfileAnalystSubAgent(paths, issueDefaults, tier) {
7770
8768
  ` - \`priority:${labelsForPhase(issueDefaults, "software:research").priority}\``,
7771
8769
  ` - \`status:${labelsForPhase(issueDefaults, "software:research").status}\``,
7772
8770
  "",
8771
+ ...renderIssueTypeAssignmentStep(),
8772
+ "",
7773
8773
  " The issue body must include:",
7774
8774
  " - A **discovery source** line naming this company profile",
7775
8775
  " (`Discovered while profiling: <company name>`)",
@@ -8321,6 +9321,8 @@ function buildProfileCompanySkill(paths, issueDefaults) {
8321
9321
  "1. Create a `company:research` issue with `type:company-profile`,",
8322
9322
  ` \`priority:${labelsForPhase(issueDefaults, "company:research").priority}\`, and \`status:${labelsForPhase(issueDefaults, "company:research").status}\`. Body must include the`,
8323
9323
  " company name, selected type, framing, and any overrides.",
9324
+ "",
9325
+ ...renderIssueTypeAssignmentStep(),
8324
9326
  "2. Execute Phase 1 (Research) of the company-profile-analyst agent.",
8325
9327
  "3. Phase 1 creates the `company:draft` issue. Phase 2 may create a",
8326
9328
  " `company:followup` issue **and** a `company:match` issue. Each",
@@ -8395,6 +9397,8 @@ function buildMatchCompanySkill(paths, issueDefaults) {
8395
9397
  "1. Create a `company:match` issue with `type:company-profile`,",
8396
9398
  ` \`priority:${labelsForPhase(issueDefaults, "company:match").priority}\`, and \`status:${labelsForPhase(issueDefaults, "company:match").status}\`. Body must include the`,
8397
9399
  " profile path and any overrides.",
9400
+ "",
9401
+ ...renderIssueTypeAssignmentStep(),
8398
9402
  "2. Execute Phase 4 (Match) of the company-profile-analyst agent.",
8399
9403
  "",
8400
9404
  "## Output",
@@ -8468,6 +9472,8 @@ function buildRefreshCompanySkill(paths, issueDefaults) {
8468
9472
  "1. Create a `company:refresh` issue with `type:company-profile`,",
8469
9473
  ` \`priority:${labelsForPhase(issueDefaults, "company:refresh").priority}\`, and \`status:${labelsForPhase(issueDefaults, "company:refresh").status}\`. Body must include the`,
8470
9474
  " profile path and any overrides.",
9475
+ "",
9476
+ ...renderIssueTypeAssignmentStep(),
8471
9477
  "2. Execute Phase 5 (Refresh) of the company-profile-analyst agent.",
8472
9478
  "",
8473
9479
  "## Output",
@@ -8541,6 +9547,8 @@ function buildAnalyzeSegmentSkill(paths, issueDefaults) {
8541
9547
  "1. Create a `company:analyze` issue with `type:company-profile`,",
8542
9548
  ` \`priority:${labelsForPhase(issueDefaults, "company:analyze").priority}\`, and \`status:${labelsForPhase(issueDefaults, "company:analyze").status}\`. Body must include the`,
8543
9549
  " segment slug and any overrides.",
9550
+ "",
9551
+ ...renderIssueTypeAssignmentStep(),
8544
9552
  "2. Execute Phase 6 (Analyze) of the company-profile-analyst agent.",
8545
9553
  "",
8546
9554
  "## Output",
@@ -9129,6 +10137,8 @@ function buildCustomerProfileAnalystSubAgent(paths, issueDefaults, tier) {
9129
10137
  ` - \`priority:${labelsForPhase(issueDefaults, "company:research").priority}\``,
9130
10138
  ` - \`status:${labelsForPhase(issueDefaults, "company:research").status}\``,
9131
10139
  "",
10140
+ ...renderIssueTypeAssignmentStep(),
10141
+ "",
9132
10142
  "9. **Create `people:research` issues** for notable contacts",
9133
10143
  " (economic buyers, technical buyers, user champions) surfaced in",
9134
10144
  " sources. Keep the list small \u2014 at most three individuals per",
@@ -9140,6 +10150,8 @@ function buildCustomerProfileAnalystSubAgent(paths, issueDefaults, tier) {
9140
10150
  ` - \`priority:${labelsForPhase(issueDefaults, "people:research").priority}\``,
9141
10151
  ` - \`status:${labelsForPhase(issueDefaults, "people:research").status}\``,
9142
10152
  "",
10153
+ ...renderIssueTypeAssignmentStep(),
10154
+ "",
9143
10155
  "10. **Verify all handoffs were created** before committing.",
9144
10156
  "",
9145
10157
  "11. **Commit and push** the archetype page. Close the profile issue.",
@@ -9241,6 +10253,8 @@ function buildCustomerProfileAnalystSubAgent(paths, issueDefaults, tier) {
9241
10253
  ` - \`priority:${labelsForPhase(issueDefaults, "req:scan").priority}\``,
9242
10254
  ` - \`status:${labelsForPhase(issueDefaults, "req:scan").status}\``,
9243
10255
  "",
10256
+ ...renderIssueTypeAssignmentStep(),
10257
+ "",
9244
10258
  " The body must also:",
9245
10259
  " - Link back to the archetype page and the competitor page",
9246
10260
  " - Cite the specific unmet-need row and the originating job",
@@ -9257,6 +10271,8 @@ function buildCustomerProfileAnalystSubAgent(paths, issueDefaults, tier) {
9257
10271
  ` - \`priority:${labelsForPhase(issueDefaults, "company:research").priority}\``,
9258
10272
  ` - \`status:${labelsForPhase(issueDefaults, "company:research").status}\``,
9259
10273
  "",
10274
+ ...renderIssueTypeAssignmentStep(),
10275
+ "",
9260
10276
  "9. **Create `people:research` issues** for notable competitor",
9261
10277
  " leadership or customer-advocacy figures surfaced in the analysis.",
9262
10278
  " Keep this list small \u2014 at most three individuals per competitors",
@@ -9268,6 +10284,8 @@ function buildCustomerProfileAnalystSubAgent(paths, issueDefaults, tier) {
9268
10284
  ` - \`priority:${labelsForPhase(issueDefaults, "people:research").priority}\``,
9269
10285
  ` - \`status:${labelsForPhase(issueDefaults, "people:research").status}\``,
9270
10286
  "",
10287
+ ...renderIssueTypeAssignmentStep(),
10288
+ "",
9271
10289
  "10. **Commit and push** the competitor-analysis page and the",
9272
10290
  " updated archetype page. Close the competitors issue.",
9273
10291
  "",
@@ -9454,6 +10472,8 @@ function buildDiscoverCustomersSkill(paths, issueDefaults) {
9454
10472
  "1. Create a `customer:discover` issue with `type:customer-profile`,",
9455
10473
  ` \`priority:${labelsForPhase(issueDefaults, "customer:discover").priority}\`, and \`status:${labelsForPhase(issueDefaults, "customer:discover").status}\`. The body must include the`,
9456
10474
  " scope slug and any known archetypes to include or exclude.",
10475
+ "",
10476
+ ...renderIssueTypeAssignmentStep(),
9457
10477
  "2. Execute Phase 1 (Discover) of the customer-profile-analyst agent.",
9458
10478
  "3. Phase 1 writes the discovery report and creates one",
9459
10479
  " `customer:profile` issue per prioritized archetype.",
@@ -9529,6 +10549,8 @@ function buildProfileCustomerSkill(paths, issueDefaults) {
9529
10549
  "1. Create a `customer:profile` issue with `type:customer-profile`,",
9530
10550
  ` \`priority:${labelsForPhase(issueDefaults, "customer:profile").priority}\`, and \`status:${labelsForPhase(issueDefaults, "customer:profile").status}\`. The body must include the`,
9531
10551
  " archetype slug and (optionally) the segment slug.",
10552
+ "",
10553
+ ...renderIssueTypeAssignmentStep(),
9532
10554
  "2. Execute Phase 2 (Profile) of the customer-profile-analyst agent.",
9533
10555
  " The agent runs 4\u20138 targeted web searches and populates the",
9534
10556
  " archetype page.",
@@ -9605,6 +10627,8 @@ function buildAnalyzeCustomerCompetitorsSkill(paths, issueDefaults) {
9605
10627
  ` \`status:${labelsForPhase(issueDefaults, "customer:competitors").status}\` (or \`status:blocked\` when still dependent on a`,
9606
10628
  " Phase 2 profile issue). Body must include the archetype page",
9607
10629
  " path.",
10630
+ "",
10631
+ ...renderIssueTypeAssignmentStep(),
9608
10632
  "2. Execute Phase 3 (Competitors) of the customer-profile-analyst",
9609
10633
  " agent. The agent reads `<MATRIX_FILE>` and runs up to 4\u20138 web",
9610
10634
  " searches only to close competitor-coverage gaps the matrix does",
@@ -11314,10 +12338,7 @@ function buildGithubWorkflowBundle(buildPolicy = DEFAULT_BUILD_POLICY) {
11314
12338
  " - If unclear, ask the user which type applies",
11315
12339
  "2. **Compose the issue title** in the format: `<type>: <short description>`",
11316
12340
  "3. **Determine the GitHub issue type** based on the prefix:",
11317
- " - `epic:` \u2192 Epic",
11318
- " - `feat:` \u2192 Feature",
11319
- " - `fix:` \u2192 Bug",
11320
- " - `chore:`, `docs:`, `refactor:`, `release:`, `hotfix:` \u2192 Task",
12341
+ ...renderTitlePrefixTypeBullets(),
11321
12342
  "4. **Identify prerequisite issues** \u2014 if the user mentions dependencies or blockers, include a **Dependencies** section in the body with `Depends on: #<issue-number>`",
11322
12343
  "5. **Determine labels** \u2014 every issue must be created with the following labels:",
11323
12344
  " - **`type:*`** \u2014 derived from the issue title prefix:",
@@ -11803,6 +12824,7 @@ function buildIndustryDiscoveryAnalystSubAgent(paths, issueDefaults) {
11803
12824
  " downstream research needs to answer)",
11804
12825
  " - Cite the evaluation row that justified advancing the vertical",
11805
12826
  " - Reference the plan file path for traceability",
12827
+ ...renderIssueTypeAssignmentStep({ indent: " ", bullet: true }),
11806
12828
  "",
11807
12829
  "5. **Cross-link** \u2014 update the plan file's `## Cleared Verticals`",
11808
12830
  " table so each row references its newly-created `research:scope`",
@@ -11893,6 +12915,8 @@ function buildDiscoverIndustriesSkill(paths, issueDefaults) {
11893
12915
  "1. Create an `industry:discover` issue with `type:industry-discovery`,",
11894
12916
  ` \`priority:${labelsForPhase(issueDefaults, "industry:discover").priority}\`, and \`status:${labelsForPhase(issueDefaults, "industry:discover").status}\`. Body must include the`,
11895
12917
  " verbatim scope, authorized sources, and any overrides.",
12918
+ "",
12919
+ ...renderIssueTypeAssignmentStep(),
11896
12920
  "2. Execute Phase 1 (Discover) of the industry-discovery-analyst",
11897
12921
  " agent.",
11898
12922
  "3. Phase 1 creates an `industry:evaluate` issue, which Phase 2 follows",
@@ -12171,6 +13195,8 @@ function buildMaintenanceAuditSubAgent(paths, tier) {
12171
13195
  "4. Commit, push, open a PR (if applicable), and close the issue per",
12172
13196
  " your project's PR workflow.",
12173
13197
  "",
13198
+ ...renderIssueTypeAssignmentBlanket(),
13199
+ "",
12174
13200
  "---",
12175
13201
  "",
12176
13202
  ...renderClaimVerificationSection(),
@@ -12603,6 +13629,8 @@ function buildMaintenanceAuditSkill(paths, issueDefaults) {
12603
13629
  " - `<AUDIT_ROOT>` \u2014 where to write the audit report",
12604
13630
  " - `<AUDIT_SLUG>` \u2014 short identifier for this audit",
12605
13631
  " - The audit checks to run (defaults to the full check catalog)",
13632
+ "",
13633
+ ...renderIssueTypeAssignmentStep(),
12606
13634
  "2. Execute Phase 1 (Scan) of the maintenance-audit agent.",
12607
13635
  "3. If auto-fixable findings exist, a `maint:fix` issue is created",
12608
13636
  " automatically.",
@@ -12667,6 +13695,8 @@ function buildMaintenanceVerifySkill(issueDefaults) {
12667
13695
  " - The Phase 2 fix report path (if one exists)",
12668
13696
  " - The `Paths Touched` list (copied from the fix report)",
12669
13697
  " - The audit check catalog (copied from the scan issue)",
13698
+ "",
13699
+ ...renderIssueTypeAssignmentStep(),
12670
13700
  "2. Execute Phase 3 (Verify) of the maintenance-audit agent.",
12671
13701
  "",
12672
13702
  "## Output",
@@ -13176,6 +14206,8 @@ function buildMeetingAnalystSubAgent(tier) {
13176
14206
  " - Always create a `meeting:link` issue \u2014 blocked on the draft issue if one",
13177
14207
  " was created, otherwise blocked on the notes issue",
13178
14208
  "",
14209
+ ...renderIssueTypeAssignmentStep(),
14210
+ "",
13179
14211
  "7. **Commit the extraction content first.** Stage and commit",
13180
14212
  " the `insights/{type}/<basename>.md` file (and any newly",
13181
14213
  " created `insights/index.md` / `insights/{type}/index.md`",
@@ -13375,6 +14407,8 @@ function buildMeetingAnalystSubAgent(tier) {
13375
14407
  "",
13376
14408
  "### Steps",
13377
14409
  "",
14410
+ ...renderIssueTypeAssignmentBlanket(),
14411
+ "",
13378
14412
  "1. Read the drafts from Phase 3 (if they exist) and the extraction",
13379
14413
  " from Phase 1 at `<meetingsRoot>/insights/{type}/<basename>.md`.",
13380
14414
  " Re-read the extraction frontmatter for `meeting_kind` and `areas`,",
@@ -13438,700 +14472,221 @@ function buildMeetingAnalystSubAgent(tier) {
13438
14472
  " `## Downstream Artifacts` section listing every issue and",
13439
14473
  " document created from this meeting. Note any items that were",
13440
14474
  " deferred to follow-up issues because of the area gate.",
13441
- "8. **Update the meeting notes** at",
13442
- " `<meetingsRoot>/notes/{type}/<basename>.md` with a",
13443
- " `## Related Issues` section listing all issues created from this",
13444
- " meeting.",
13445
- "9. Comment on the parent extract issue with a summary linking to all",
13446
- " created artifacts.",
13447
- "10. Commit and push (if any file changes were made), then close the link issue.",
13448
- "",
13449
- "---",
13450
- "",
13451
- "## GitHub Integration",
13452
- "",
13453
- "- Use `gh` CLI for all GitHub operations",
13454
- "- Apply the appropriate `meeting:*` phase label to each phase issue",
13455
- "- Use `type:docs` for documentation-producing issues, `type:chore` for",
13456
- " maintenance/organizational tasks the pipeline can action",
13457
- "- Do **not** file issues for human-owned action items \u2014 per the",
13458
- " **Action-item filing policy** they live only in the notes",
13459
- " `## Action Items` table",
13460
- "- Use `status:` labels to track progress (`status:ready`, `status:in-progress`, `status:done`)",
13461
- "- Reference the source meeting transcript in all created issues",
13462
- "- Link phase issues with `Depends on: #N` to enforce ordering",
13463
- "",
13464
- "## When to Shorten the Pipeline",
13465
- "",
13466
- "- **Short meetings** (<30 min, <2000 words): combine extract + notes into one session",
13467
- "- **No requirements or decisions**: Phase 1 skips creating the `meeting:draft` issue",
13468
- "- **Only action items**: extract + notes + link (3 phase issues)"
13469
- ].join("\n")
13470
- };
13471
- }
13472
- var processMeetingSkill = {
13473
- name: "process-meeting",
13474
- description: "Process a meeting transcript through the 4-phase meeting analysis pipeline",
13475
- disableModelInvocation: true,
13476
- userInvocable: true,
13477
- context: "fork",
13478
- agent: "meeting-analyst",
13479
- platforms: { cursor: { exclude: true } },
13480
- instructions: [
13481
- "# Process Meeting",
13482
- "",
13483
- "Kick off meeting processing by executing Phase 1 (Extract) and",
13484
- "creating downstream phase issues for the remaining phases. Inputs",
13485
- "live in the **sibling-tree layout**: pass any file from",
13486
- "`<meetingsRoot>/notes/{type}/` or `<meetingsRoot>/transcripts/{type}/`",
13487
- "(both, notes-only, and transcript-only are all valid input cases).",
13488
- "",
13489
- "## Usage",
13490
- "",
13491
- "/process-meeting <path-to-notes-or-transcript>",
13492
- "",
13493
- "## Basename-pairing algorithm",
13494
- "",
13495
- "The user may pass either a notes-tree path or a transcripts-tree",
13496
- "path. Resolve the sibling implicitly via filesystem lookup \u2014",
13497
- "do **not** prompt the user or fail-loud when the sibling is",
13498
- "absent:",
13499
- "",
13500
- "1. Infer the **basename** (filename without the `.md` extension)",
13501
- " and the `{internal,external}` partition from the provided path.",
13502
- " Both segments are usually adjacent in the path \u2014",
13503
- " `\u2026/notes/internal/2026-03-14-sprint-kickoff.md` yields basename",
13504
- " `2026-03-14-sprint-kickoff` and type `internal`.",
13505
- "2. Determine the **input tree** from the path:",
13506
- " - If the path contains `/notes/`, treat it as the notes input",
13507
- " and look for the sibling at",
13508
- " `<meetingsRoot>/transcripts/<type>/<basename>.md`.",
13509
- " - If the path contains `/transcripts/`, treat it as the",
13510
- " transcript input and look for the sibling at",
13511
- " `<meetingsRoot>/notes/<type>/<basename>.md`.",
13512
- " - If the path matches neither (e.g. an ad-hoc location), infer",
13513
- " basename + type as best you can from the filename and",
13514
- " surrounding folder segments, then proceed with whichever",
13515
- " sibling-tree lookups succeed. Best-effort, never fail-loud.",
13516
- "3. Read **every** sibling that exists on disk. The combination",
13517
- " (notes present, transcript present, both present) selects the",
13518
- " Phase-1 input case from the **input-case matrix** in the",
13519
- " `meeting-analyst` agent prompt.",
13520
- "4. A missing sibling is a normal outcome \u2014 proceed with whatever",
13521
- " was found. Notes-only and transcript-only are both valid",
13522
- " Phase-1 input cases; the agent never warns or prompts the",
13523
- " user about the absence.",
13524
- "",
13525
- "## Steps",
13526
- "",
13527
- "1. Resolve the basename, partition, and any sibling files using",
13528
- " the **Basename-pairing algorithm** above",
13529
- "2. Execute Phase 1 (Extract) \u2014 categorize the available content into",
13530
- " decisions, requirements, action items, open questions, and more.",
13531
- " Tag each action item's disposition (`agent-workable` vs",
13532
- " `human-owned`) per the agent's **Action-item filing policy** so",
13533
- " Phase 4 keeps human-owned tasks out of the issue queue",
13534
- "3. Write the extraction to",
13535
- " `<meetingsRoot>/insights/{type}/<basename>.md`",
13536
- "4. Create downstream phase issues using `gh issue create`:",
13537
- " - `meeting:notes` issue (always)",
13538
- " - `meeting:draft` issue (if requirements or decisions were found)",
13539
- " - `meeting:link` issue (always)",
13540
- "5. Each downstream issue includes `Depends on:` linking to its predecessor",
13541
- "6. Commit and push the extraction file",
13542
- "",
13543
- "## Input",
13544
- "",
13545
- "Provide a path to a file in either `notes/` or `transcripts/`. The",
13546
- "agent will look up the sibling automatically via basename match. A",
13547
- "missing sibling is a normal case (notes-only or transcript-only) \u2014",
13548
- "the agent handles all three input cases.",
13549
- "",
13550
- "## Output",
13551
- "",
13552
- "- A structured extraction at",
13553
- " `<meetingsRoot>/insights/{type}/<basename>.md`",
13554
- "- Phase issues with `meeting:*` labels for downstream agent sessions",
13555
- " to pick up (notes, draft, link)"
13556
- ].join("\n")
13557
- };
13558
- function buildMeetingAnalysisBundle(tier = AGENT_MODEL.BALANCED) {
13559
- return {
13560
- name: "meeting-analysis",
13561
- description: "Meeting transcript processing workflow with 4-phase pipeline (extract, notes, draft, link)",
13562
- appliesWhen: () => true,
13563
- rules: [
13564
- {
13565
- name: "meeting-processing-workflow",
13566
- description: "Describes the 4-phase meeting processing pipeline, extraction taxonomy, and labeling conventions",
13567
- scope: AGENT_RULE_SCOPE.ALWAYS,
13568
- content: [
13569
- "# Meeting Processing Workflow",
13570
- "",
13571
- "Use `/process-meeting <path>` to process a meeting through a",
13572
- "4-phase pipeline (extract \u2192 notes \u2192 draft \u2192 link). Each phase runs as a",
13573
- "separate agent session tracked by a GitHub issue with a `meeting:*` label.",
13574
- "See the `meeting-analyst` agent definition for full workflow details.",
13575
- "",
13576
- "Meeting artifacts live in **three parallel trees** rooted at the",
13577
- "project's meetings root:",
13578
- "",
13579
- "- `transcripts/{internal,external}/` \u2014 verbatim transcripts (evidence)",
13580
- "- `notes/{internal,external}/` \u2014 curated narrative summary",
13581
- " (input + Phase 2 output)",
13582
- "- `insights/{internal,external}/` \u2014 structured extraction (Phase 1 output)",
13583
- "",
13584
- "The three trees share identical basenames so a meeting's siblings",
13585
- "are looked up by filename, not by frontmatter cross-references.",
13586
- "Phase 1 accepts every input case (notes + transcript, notes only,",
13587
- "or transcript only) and Phase 2 enhances the notes file in place",
13588
- "rather than authoring it from scratch.",
13589
- "",
13590
- "Notes and insights files may carry optional frontmatter fields",
13591
- "`slug`, `source`, `transcript_available`, and `confidence`",
13592
- "alongside the existing `meeting_type` and `areas` fields. Missing",
13593
- "frontmatter is treated as a normal input case \u2014 never fail-loud.",
13594
- "",
13595
- "Meeting notes may declare a `meeting_type` (one of the project's",
13596
- "recognized types) and an `areas: [...]` list. The `meeting_type`",
13597
- "resolves to a generic kind \u2014 `planning` / `review` / `brainstorm` /",
13598
- "`standup` / `external` / `other` \u2014 that drives type-specific",
13599
- "extraction rules in phases 1\u20132. The `areas` list scopes phase-4",
13600
- "direct edits to the doc-root sub-folders declared in the project's",
13601
- "**Area \u2192 doc-root mapping** table. When both fields are absent",
13602
- "the pipeline falls back to the default workflow (kind `other`,",
13603
- "no area gating).",
13604
- "",
13605
- "Phase 4 applies an **agent-workability split** to action items:",
13606
- "items that yield a documentation deliverable an agent can author",
13607
- "are filed through their dedicated downstream channel",
13608
- "(`req:write`, `docs:write`, `bcm:*`, `research:scope`, roadmap",
13609
- "follow-up); human-owned tasks (send / schedule / install /",
13610
- "decide / communicate / get-access / build-elsewhere) are",
13611
- "recorded **only** in the notes `## Action Items` table, which is",
13612
- "the **system of record for human follow-ups** \u2014 no GitHub issue",
13613
- "is filed for them. Consumers tune or disable the split via",
13614
- "`AgentConfigOptions.meetings.actionItemFiling`."
13615
- ].join("\n"),
13616
- platforms: {
13617
- cursor: { exclude: true }
13618
- },
13619
- tags: ["workflow"]
13620
- }
13621
- ],
13622
- skills: [processMeetingSkill],
13623
- subAgents: [buildMeetingAnalystSubAgent(tier)],
13624
- labels: [
13625
- {
13626
- name: "type:meeting-processing",
13627
- color: "5319E7",
13628
- description: "Meeting transcript processing pipeline"
13629
- },
13630
- {
13631
- name: "meeting:extract",
13632
- color: "C5DEF5",
13633
- description: "Phase 1: raw extraction from a meeting transcript"
13634
- },
13635
- {
13636
- name: "meeting:notes",
13637
- color: "BFDADC",
13638
- description: "Phase 2: curated notes derived from an extraction"
13639
- },
13640
- {
13641
- name: "meeting:draft",
13642
- color: "D4C5F9",
13643
- description: "Phase 3: draft follow-up issues proposed from notes"
13644
- },
13645
- {
13646
- name: "meeting:link",
13647
- color: "FEF2C0",
13648
- description: "Phase 4: linking/reconciling drafted issues with existing work"
13649
- }
13650
- ]
13651
- };
13652
- }
13653
- var meetingAnalysisBundle = buildMeetingAnalysisBundle();
13654
-
13655
- // src/agent/bundles/bundle-ownership.ts
13656
- var BUNDLE_OWNERSHIP = {
13657
- agenda: {
13658
- typeLabels: ["agenda"],
13659
- phaseLabelPrefixes: ["agenda:"],
13660
- scheduledTaskIds: ["worker-agenda"],
13661
- emitsDocs: true,
13662
- downstreamIssueKinds: true
13663
- },
13664
- "bcm-writer": {
13665
- typeLabels: ["bcm-document"],
13666
- phaseLabelPrefixes: ["bcm:"],
13667
- scheduledTaskIds: ["worker-bcm-writer"],
13668
- emitsDocs: true,
13669
- downstreamIssueKinds: true
13670
- },
13671
- "business-models": {
13672
- typeLabels: ["business-model"],
13673
- phaseLabelPrefixes: ["business-models:"],
13674
- scheduledTaskIds: ["worker-business-models"],
13675
- emitsDocs: true,
13676
- downstreamIssueKinds: true
13677
- },
13678
- "company-profile": {
13679
- typeLabels: ["company-profile"],
13680
- phaseLabelPrefixes: ["company:"],
13681
- scheduledTaskIds: ["worker-company-profile"],
13682
- emitsDocs: true,
13683
- downstreamIssueKinds: true
13684
- },
13685
- "customer-profile": {
13686
- typeLabels: ["customer-profile"],
13687
- phaseLabelPrefixes: ["customer:"],
13688
- scheduledTaskIds: ["worker-customer-profile"],
13689
- emitsDocs: true,
13690
- downstreamIssueKinds: true
13691
- },
13692
- "docs-sync": {
13693
- typeLabels: ["docs-sync"],
13694
- phaseLabelPrefixes: ["docs-sync:"],
13695
- scheduledTaskIds: ["worker-docs-sync"],
13696
- emitsDocs: true,
13697
- downstreamIssueKinds: true
13698
- },
13699
- "industry-discovery": {
13700
- typeLabels: ["industry-discovery"],
13701
- phaseLabelPrefixes: ["industry:"],
13702
- scheduledTaskIds: ["worker-industry-discovery"],
13703
- emitsDocs: true,
13704
- downstreamIssueKinds: true
13705
- },
13706
- "maintenance-audit": {
13707
- typeLabels: ["maintenance"],
13708
- phaseLabelPrefixes: ["maint:"],
13709
- scheduledTaskIds: ["worker-maintenance"],
13710
- emitsDocs: false,
13711
- downstreamIssueKinds: true
13712
- },
13713
- "meeting-analysis": {
13714
- typeLabels: ["meeting-processing"],
13715
- phaseLabelPrefixes: ["meeting:"],
13716
- scheduledTaskIds: ["worker-meeting-analysis"],
13717
- emitsDocs: true,
13718
- downstreamIssueKinds: true
13719
- },
13720
- orchestrator: {
13721
- // The orchestrator pipeline manager itself ships with the
13722
- // `worker-orchestrator` scheduled task. It owns no `type:*` label
13723
- // (it dispatches every type) and no phase-label prefix.
13724
- typeLabels: [],
13725
- phaseLabelPrefixes: [],
13726
- scheduledTaskIds: ["worker-orchestrator"],
13727
- emitsDocs: false,
13728
- downstreamIssueKinds: false
13729
- },
13730
- "people-profile": {
13731
- typeLabels: ["people-profile"],
13732
- phaseLabelPrefixes: ["people:"],
13733
- scheduledTaskIds: ["worker-people-profile"],
13734
- emitsDocs: true,
13735
- downstreamIssueKinds: true
13736
- },
13737
- "pr-review": {
13738
- typeLabels: ["pr-review"],
13739
- phaseLabelPrefixes: [],
13740
- scheduledTaskIds: ["worker-pr-review"],
13741
- emitsDocs: false,
13742
- downstreamIssueKinds: false
13743
- },
13744
- "regulatory-research": {
13745
- typeLabels: ["regulatory-research"],
13746
- phaseLabelPrefixes: ["regulatory:"],
13747
- scheduledTaskIds: ["worker-regulatory-research"],
13748
- emitsDocs: true,
13749
- downstreamIssueKinds: true
13750
- },
13751
- "requirements-analyst": {
13752
- typeLabels: ["requirement"],
13753
- phaseLabelPrefixes: ["req:"],
13754
- scheduledTaskIds: ["worker-requirements-analyst"],
13755
- emitsDocs: true,
13756
- downstreamIssueKinds: true
13757
- },
13758
- "requirements-reviewer": {
13759
- // Co-owns `type:requirement` with the analyst and writer; owns
13760
- // the `req:review` and `req:deprecate` phase labels exactly.
13761
- typeLabels: ["requirement"],
13762
- phaseLabelPrefixes: ["req:review", "req:deprecate"],
13763
- scheduledTaskIds: ["worker-requirements-reviewer"],
13764
- emitsDocs: true,
13765
- downstreamIssueKinds: true
13766
- },
13767
- "requirements-writer": {
13768
- // Co-owns `type:requirement` with the analyst and reviewer; owns
13769
- // the `req:write` phase label exactly.
13770
- typeLabels: ["requirement"],
13771
- phaseLabelPrefixes: ["req:write"],
13772
- scheduledTaskIds: ["worker-requirements-writer"],
13773
- emitsDocs: true,
13774
- downstreamIssueKinds: true
13775
- },
13776
- "research-pipeline": {
13777
- typeLabels: ["research"],
13778
- phaseLabelPrefixes: ["research:"],
13779
- scheduledTaskIds: ["worker-research"],
13780
- emitsDocs: true,
13781
- downstreamIssueKinds: true
13782
- },
13783
- "software-profile": {
13784
- typeLabels: ["software-profile"],
13785
- phaseLabelPrefixes: ["software:"],
13786
- scheduledTaskIds: ["worker-software-profile"],
13787
- emitsDocs: true,
13788
- downstreamIssueKinds: true
13789
- },
13790
- "standards-research": {
13791
- typeLabels: ["standards-research"],
13792
- phaseLabelPrefixes: ["standards:"],
13793
- scheduledTaskIds: ["worker-standards-research"],
13794
- emitsDocs: true,
13795
- downstreamIssueKinds: true
13796
- }
13797
- };
13798
- var CONVENTIONAL_COMMIT_TYPE_LABELS = [
13799
- "type:chore",
13800
- "type:docs",
13801
- "type:feat",
13802
- "type:fix",
13803
- "type:hotfix",
13804
- "type:refactor",
13805
- "type:release"
13806
- ];
13807
- var PHASE_LABEL_TYPE_MAP = buildPhaseLabelTypeMap();
13808
- function typeLabelForPhaseLabel(phaseLabel) {
13809
- const exact = PHASE_LABEL_TYPE_MAP[phaseLabel];
13810
- if (exact !== void 0 && !phaseLabel.endsWith(":")) {
13811
- return exact;
13812
- }
13813
- let bestMatcher;
13814
- for (const matcher of Object.keys(PHASE_LABEL_TYPE_MAP)) {
13815
- if (!matcher.endsWith(":")) {
13816
- continue;
13817
- }
13818
- if (!phaseLabel.startsWith(matcher)) {
13819
- continue;
13820
- }
13821
- if (bestMatcher === void 0 || matcher.length > bestMatcher.length) {
13822
- bestMatcher = matcher;
13823
- }
13824
- }
13825
- return bestMatcher === void 0 ? void 0 : PHASE_LABEL_TYPE_MAP[bestMatcher];
13826
- }
13827
- function resolveTypeLabelForLabels(labels) {
13828
- const phaseLabels = [];
13829
- const candidates = /* @__PURE__ */ new Set();
13830
- for (const label of labels) {
13831
- const typeLabel = typeLabelForPhaseLabel(label);
13832
- if (typeLabel === void 0) {
13833
- continue;
13834
- }
13835
- phaseLabels.push(label);
13836
- candidates.add(typeLabel);
13837
- }
13838
- const candidateTypeLabels = Array.from(candidates).sort();
13839
- if (candidateTypeLabels.length === 0) {
13840
- return { outcome: "none", candidateTypeLabels: [], phaseLabels: [] };
13841
- }
13842
- if (candidateTypeLabels.length === 1) {
13843
- return {
13844
- outcome: "match",
13845
- typeLabel: candidateTypeLabels[0],
13846
- candidateTypeLabels,
13847
- phaseLabels
13848
- };
13849
- }
13850
- return { outcome: "ambiguous", candidateTypeLabels, phaseLabels };
14475
+ "8. **Update the meeting notes** at",
14476
+ " `<meetingsRoot>/notes/{type}/<basename>.md` with a",
14477
+ " `## Related Issues` section listing all issues created from this",
14478
+ " meeting.",
14479
+ "9. Comment on the parent extract issue with a summary linking to all",
14480
+ " created artifacts.",
14481
+ "10. Commit and push (if any file changes were made), then close the link issue.",
14482
+ "",
14483
+ "---",
14484
+ "",
14485
+ "## GitHub Integration",
14486
+ "",
14487
+ "- Use `gh` CLI for all GitHub operations",
14488
+ "- Apply the appropriate `meeting:*` phase label to each phase issue",
14489
+ "- Use `type:docs` for documentation-producing issues, `type:chore` for",
14490
+ " maintenance/organizational tasks the pipeline can action",
14491
+ "- Do **not** file issues for human-owned action items \u2014 per the",
14492
+ " **Action-item filing policy** they live only in the notes",
14493
+ " `## Action Items` table",
14494
+ "- Use `status:` labels to track progress (`status:ready`, `status:in-progress`, `status:done`)",
14495
+ "- Reference the source meeting transcript in all created issues",
14496
+ "- Link phase issues with `Depends on: #N` to enforce ordering",
14497
+ "",
14498
+ "## When to Shorten the Pipeline",
14499
+ "",
14500
+ "- **Short meetings** (<30 min, <2000 words): combine extract + notes into one session",
14501
+ "- **No requirements or decisions**: Phase 1 skips creating the `meeting:draft` issue",
14502
+ "- **Only action items**: extract + notes + link (3 phase issues)"
14503
+ ].join("\n")
14504
+ };
13851
14505
  }
13852
- function renderPhaseTypeInvariantSection(excludeBundles = []) {
13853
- const rows = Object.keys(PHASE_LABEL_TYPE_MAP).filter(
13854
- (matcher) => !isPhaseLabelMatcherOwnedByExcluded(matcher, excludeBundles)
13855
- ).sort().map((matcher) => {
13856
- const display = matcher.endsWith(":") ? `${matcher}*` : matcher;
13857
- const kind = matcher.endsWith(":") ? "prefix" : "exact";
13858
- return `| \`${display}\` | ${kind} | \`${PHASE_LABEL_TYPE_MAP[matcher]}\` |`;
13859
- });
13860
- return [
13861
- "## Phase-label \u2192 `type:<bundle>` invariant",
13862
- "",
13863
- "Every phased-pipeline bundle pairs its `<bundle>:<phase>` labels",
13864
- "with exactly one `type:<bundle>` label. That type label is the",
13865
- "**dedup + dispatch signal**: agents de-duplicate downstream work",
13866
- 'with `gh issue list --label "type:<bundle>"`, and Phase E derives',
13867
- "its funnel-tier sort key from the issue's `type:*` label. An issue",
13868
- "that carries the phase label but a conventional-commit `type:*`",
13869
- "(`type:feat`, `type:docs`, \u2026, stamped from its title prefix by the",
13870
- "generic create-issue workflow) is invisible to that dedup query and",
13871
- "mis-tiers in dispatch.",
13872
- "",
13873
- "The pairing below is derived from the canonical bundle-ownership",
13874
- "map \u2014 the same source of truth that generates the label registry.",
13875
- "Matchers ending in `*` match by prefix; the rest match exactly, and",
13876
- "an exact match always beats a prefix match.",
14506
+ var processMeetingSkill = {
14507
+ name: "process-meeting",
14508
+ description: "Process a meeting transcript through the 4-phase meeting analysis pipeline",
14509
+ disableModelInvocation: true,
14510
+ userInvocable: true,
14511
+ context: "fork",
14512
+ agent: "meeting-analyst",
14513
+ platforms: { cursor: { exclude: true } },
14514
+ instructions: [
14515
+ "# Process Meeting",
13877
14516
  "",
13878
- "| Phase label | Match | Required type label |",
13879
- "|-------------|-------|---------------------|",
13880
- ...rows,
14517
+ "Kick off meeting processing by executing Phase 1 (Extract) and",
14518
+ "creating downstream phase issues for the remaining phases. Inputs",
14519
+ "live in the **sibling-tree layout**: pass any file from",
14520
+ "`<meetingsRoot>/notes/{type}/` or `<meetingsRoot>/transcripts/{type}/`",
14521
+ "(both, notes-only, and transcript-only are all valid input cases).",
13881
14522
  "",
13882
- "### Enforcement",
14523
+ "## Usage",
13883
14524
  "",
13884
- "The Phase D maintenance sweep runs the invariant in auto-correct",
13885
- "mode and emits one summary line:",
14525
+ "/process-meeting <path-to-notes-or-transcript>",
13886
14526
  "",
13887
- "```",
13888
- "LABEL_INVARIANT_DONE mode=fix checked=<N> ok=<O> violations=<V> corrected=<C> flagged=<F>",
13889
- "```",
14527
+ "## Basename-pairing algorithm",
13890
14528
  "",
13891
- "A **report-only** audit is available for repos that prefer",
13892
- "report-then-fix over auto-correct \u2014 same sweep, no mutations:",
14529
+ "The user may pass either a notes-tree path or a transcripts-tree",
14530
+ "path. Resolve the sibling implicitly via filesystem lookup \u2014",
14531
+ "do **not** prompt the user or fail-loud when the sibling is",
14532
+ "absent:",
13893
14533
  "",
13894
- "```bash",
13895
- ".claude/procedures/check-blocked.sh label-invariant # report only",
13896
- ".claude/procedures/check-blocked.sh label-invariant --fix # apply",
13897
- "```",
14534
+ "1. Infer the **basename** (filename without the `.md` extension)",
14535
+ " and the `{internal,external}` partition from the provided path.",
14536
+ " Both segments are usually adjacent in the path \u2014",
14537
+ " `\u2026/notes/internal/2026-03-14-sprint-kickoff.md` yields basename",
14538
+ " `2026-03-14-sprint-kickoff` and type `internal`.",
14539
+ "2. Determine the **input tree** from the path:",
14540
+ " - If the path contains `/notes/`, treat it as the notes input",
14541
+ " and look for the sibling at",
14542
+ " `<meetingsRoot>/transcripts/<type>/<basename>.md`.",
14543
+ " - If the path contains `/transcripts/`, treat it as the",
14544
+ " transcript input and look for the sibling at",
14545
+ " `<meetingsRoot>/notes/<type>/<basename>.md`.",
14546
+ " - If the path matches neither (e.g. an ad-hoc location), infer",
14547
+ " basename + type as best you can from the filename and",
14548
+ " surrounding folder segments, then proceed with whichever",
14549
+ " sibling-tree lookups succeed. Best-effort, never fail-loud.",
14550
+ "3. Read **every** sibling that exists on disk. The combination",
14551
+ " (notes present, transcript present, both present) selects the",
14552
+ " Phase-1 input case from the **input-case matrix** in the",
14553
+ " `meeting-analyst` agent prompt.",
14554
+ "4. A missing sibling is a normal outcome \u2014 proceed with whatever",
14555
+ " was found. Notes-only and transcript-only are both valid",
14556
+ " Phase-1 input cases; the agent never warns or prompts the",
14557
+ " user about the absence.",
13898
14558
  "",
13899
- "### Correction policy",
14559
+ "## Steps",
13900
14560
  "",
13901
- "An issue must carry **exactly one** `type:*` label. Merely *adding*",
13902
- "the required type label to an issue that already carries a",
13903
- "conventional-commit one would leave two, making the funnel-tier sort",
13904
- "key (which reads the **first** `type:*` label) non-deterministic and",
13905
- "violating the label conventions. The correction therefore",
13906
- "**replaces**, in a single atomic `gh issue edit`, so the issue is",
13907
- "never observable carrying two `type:*` labels:",
14561
+ "1. Resolve the basename, partition, and any sibling files using",
14562
+ " the **Basename-pairing algorithm** above",
14563
+ "2. Execute Phase 1 (Extract) \u2014 categorize the available content into",
14564
+ " decisions, requirements, action items, open questions, and more.",
14565
+ " Tag each action item's disposition (`agent-workable` vs",
14566
+ " `human-owned`) per the agent's **Action-item filing policy** so",
14567
+ " Phase 4 keeps human-owned tasks out of the issue queue",
14568
+ "3. Write the extraction to",
14569
+ " `<meetingsRoot>/insights/{type}/<basename>.md`",
14570
+ "4. Create downstream phase issues using `gh issue create`:",
14571
+ " - `meeting:notes` issue (always)",
14572
+ " - `meeting:draft` issue (if requirements or decisions were found)",
14573
+ " - `meeting:link` issue (always)",
13908
14574
  "",
13909
- "| Issue state | Action |",
13910
- "|-------------|--------|",
13911
- "| Required type label present, nothing else | none \u2014 compliant, left untouched |",
13912
- "| No `type:*` label at all | **add** the required label |",
13913
- "| Only conventional-commit `type:*` label(s) | **replace** \u2014 remove them, add the required label |",
13914
- "| Required label present **plus** a stray conventional-commit one | **remove** the stray, leaving exactly one |",
13915
- "| A `type:*` label owned by a **different** bundle | **flag** `status:needs-attention` \u2014 never guessed at |",
13916
- "| Phase labels imply **two or more** type labels | **flag** `status:needs-attention` \u2014 never auto-corrected |",
14575
+ ...renderIssueTypeAssignmentStep(),
14576
+ "5. Each downstream issue includes `Depends on:` linking to its predecessor",
14577
+ "6. Commit and push the extraction file",
13917
14578
  "",
13918
- "Only a conventional-commit `type:*` label is ever removed. A",
13919
- "`type:*` label owned by another bundle carries real routing",
13920
- "information, so the sweep refuses to guess and hands the issue to a",
13921
- "human instead. Ambiguity is narrower than it looks: all three",
13922
- "requirements bundles declare `type:requirement`, so every `req:*`",
13923
- "phase label resolves to the same type label \u2014 genuine ambiguity",
13924
- "needs phase labels from two bundles with *different* type labels.",
14579
+ "## Input",
13925
14580
  "",
13926
- "`status:needs-attention` is applied **additively** \u2014 the base",
13927
- "`status:*` label always stays, per the additive-flag rule. Flagging",
13928
- "is idempotent: an issue already carrying the flag is reported but",
13929
- "not re-flagged."
13930
- ].join("\n");
13931
- }
13932
- function isPhaseLabelMatcherOwnedByExcluded(matcher, excludeBundles) {
13933
- if (excludeBundles.length === 0) {
13934
- return false;
13935
- }
13936
- let owned = false;
13937
- for (const [bundleName, ownership] of Object.entries(BUNDLE_OWNERSHIP)) {
13938
- if (!ownership.phaseLabelPrefixes.includes(matcher)) {
13939
- continue;
13940
- }
13941
- owned = true;
13942
- if (!excludeBundles.includes(bundleName)) {
13943
- return false;
13944
- }
13945
- }
13946
- return owned;
13947
- }
13948
- function renderPhaseTypeInvariantShellHelpers() {
13949
- const matchers = Object.keys(PHASE_LABEL_TYPE_MAP);
13950
- const exactMatchers = matchers.filter((m) => !m.endsWith(":")).sort();
13951
- const prefixMatchers = matchers.filter((m) => m.endsWith(":")).sort((a, b) => b.length - a.length || a.localeCompare(b));
13952
- const lines = [
13953
- "# Resolve ONE phase label to the `type:<bundle>` label its owning",
13954
- "# bundle declares. Echoes the label (with the `type:` prefix) or",
13955
- "# nothing when no bundle owns it. Exact-match branches come first,",
13956
- "# so `req:write` (requirements-writer) beats the `req:` prefix",
13957
- "# (requirements-analyst). Generated from BUNDLE_OWNERSHIP \u2014 do not",
13958
- "# hand-edit.",
13959
- "phase_label_type_of() {",
13960
- ' case "${1:-}" in'
13961
- ];
13962
- for (const matcher of exactMatchers) {
13963
- lines.push(` ${matcher}) echo "${PHASE_LABEL_TYPE_MAP[matcher]}" ;;`);
13964
- }
13965
- for (const matcher of prefixMatchers) {
13966
- lines.push(` ${matcher}*) echo "${PHASE_LABEL_TYPE_MAP[matcher]}" ;;`);
13967
- }
13968
- lines.push(
13969
- " *) : ;;",
13970
- " esac",
13971
- "}",
14581
+ "Provide a path to a file in either `notes/` or `transcripts/`. The",
14582
+ "agent will look up the sibling automatically via basename match. A",
14583
+ "missing sibling is a normal case (notes-only or transcript-only) \u2014",
14584
+ "the agent handles all three input cases.",
13972
14585
  "",
13973
- "# Return 0 when the argument is a conventional-commit `type:*`",
13974
- "# label \u2014 the ONLY type labels the invariant correction may remove.",
13975
- "# A `type:*` label owned by another bundle is never removed; the",
13976
- "# issue is flagged for human triage instead.",
13977
- "is_conventional_type_label() {",
13978
- ` case "\${1:-}" in`,
13979
- ` ${CONVENTIONAL_COMMIT_TYPE_LABELS.join("|")}) return 0 ;;`,
13980
- " *) return 1 ;;",
13981
- " esac",
13982
- "}",
14586
+ "## Output",
13983
14587
  "",
13984
- "# Resolve an issue's FULL label list (one label per line on stdin)",
13985
- "# to the type label the phase-label invariant requires. Emits",
13986
- "# KEY=VALUE assignments the caller parses:",
13987
- "# OUTCOME=none \u2014 no recognised phase label; invariant N/A",
13988
- "# OUTCOME=match \u2014 plus TYPE_LABEL=<type:bundle>",
13989
- "# OUTCOME=ambiguous \u2014 plus CANDIDATE_TYPE_LABELS=<space-separated>",
13990
- "# PHASE_LABELS=<space-separated recognised phase labels>",
13991
- "phase_type_of() {",
13992
- " local label resolved",
13993
- ' local matched_types=""',
13994
- ' local matched_phase=""',
13995
- " while IFS= read -r label; do",
13996
- ' [ -z "$label" ] && continue',
13997
- ' resolved=$(phase_label_type_of "$label")',
13998
- ' [ -z "$resolved" ] && continue',
13999
- ' matched_phase="${matched_phase}${label} "',
14000
- ' case " ${matched_types} " in',
14001
- ' *" ${resolved} "*) ;;',
14002
- ' *) matched_types="${matched_types}${resolved} " ;;',
14003
- " esac",
14004
- " done",
14005
- " local count=0",
14006
- " for resolved in $matched_types; do",
14007
- " count=$((count + 1))",
14008
- " done",
14009
- ' if [ "$count" -eq 0 ]; then',
14010
- ' echo "OUTCOME=none"',
14011
- ' elif [ "$count" -eq 1 ]; then',
14012
- ' echo "OUTCOME=match"',
14013
- ' echo "TYPE_LABEL=${matched_types% }"',
14014
- " else",
14015
- ' echo "OUTCOME=ambiguous"',
14016
- ' echo "CANDIDATE_TYPE_LABELS=${matched_types% }"',
14017
- " fi",
14018
- ' echo "PHASE_LABELS=${matched_phase% }"',
14019
- "}"
14020
- );
14021
- return lines.join("\n");
14022
- }
14023
- function buildPhaseLabelTypeMap() {
14024
- const map = {};
14025
- for (const [bundleName, ownership] of Object.entries(BUNDLE_OWNERSHIP)) {
14026
- if (ownership.phaseLabelPrefixes.length === 0) {
14027
- continue;
14028
- }
14029
- if (ownership.typeLabels.length !== 1) {
14030
- throw new Error(
14031
- `BUNDLE_OWNERSHIP["${bundleName}"] declares ${ownership.phaseLabelPrefixes.length} phase-label matcher(s) but ${ownership.typeLabels.length} type label(s); a phase label must pair with exactly one type label`
14032
- );
14033
- }
14034
- const typeLabel = `type:${ownership.typeLabels[0]}`;
14035
- for (const matcher of ownership.phaseLabelPrefixes) {
14036
- const existing = map[matcher];
14037
- if (existing !== void 0 && existing !== typeLabel) {
14038
- throw new Error(
14039
- `Phase-label matcher "${matcher}" resolves to both "${existing}" and "${typeLabel}"; a matcher must imply exactly one type label`
14040
- );
14041
- }
14042
- map[matcher] = typeLabel;
14043
- }
14044
- }
14045
- return map;
14046
- }
14047
- function isTypeLabelOwnedByExcluded(typeLabel, excludedBundles) {
14048
- if (excludedBundles.length === 0) {
14049
- return false;
14050
- }
14051
- for (const bundleName of excludedBundles) {
14052
- const ownership = BUNDLE_OWNERSHIP[bundleName];
14053
- if (!ownership) {
14054
- continue;
14055
- }
14056
- if (ownership.typeLabels.includes(typeLabel)) {
14057
- const owners = findOwnersOfTypeLabel(typeLabel);
14058
- const allExcluded = owners.every(
14059
- (owner) => excludedBundles.includes(owner)
14060
- );
14061
- if (allExcluded) {
14062
- return true;
14588
+ "- A structured extraction at",
14589
+ " `<meetingsRoot>/insights/{type}/<basename>.md`",
14590
+ "- Phase issues with `meeting:*` labels for downstream agent sessions",
14591
+ " to pick up (notes, draft, link)"
14592
+ ].join("\n")
14593
+ };
14594
+ function buildMeetingAnalysisBundle(tier = AGENT_MODEL.BALANCED) {
14595
+ return {
14596
+ name: "meeting-analysis",
14597
+ description: "Meeting transcript processing workflow with 4-phase pipeline (extract, notes, draft, link)",
14598
+ appliesWhen: () => true,
14599
+ rules: [
14600
+ {
14601
+ name: "meeting-processing-workflow",
14602
+ description: "Describes the 4-phase meeting processing pipeline, extraction taxonomy, and labeling conventions",
14603
+ scope: AGENT_RULE_SCOPE.ALWAYS,
14604
+ content: [
14605
+ "# Meeting Processing Workflow",
14606
+ "",
14607
+ "Use `/process-meeting <path>` to process a meeting through a",
14608
+ "4-phase pipeline (extract \u2192 notes \u2192 draft \u2192 link). Each phase runs as a",
14609
+ "separate agent session tracked by a GitHub issue with a `meeting:*` label.",
14610
+ "See the `meeting-analyst` agent definition for full workflow details.",
14611
+ "",
14612
+ "Meeting artifacts live in **three parallel trees** rooted at the",
14613
+ "project's meetings root:",
14614
+ "",
14615
+ "- `transcripts/{internal,external}/` \u2014 verbatim transcripts (evidence)",
14616
+ "- `notes/{internal,external}/` \u2014 curated narrative summary",
14617
+ " (input + Phase 2 output)",
14618
+ "- `insights/{internal,external}/` \u2014 structured extraction (Phase 1 output)",
14619
+ "",
14620
+ "The three trees share identical basenames so a meeting's siblings",
14621
+ "are looked up by filename, not by frontmatter cross-references.",
14622
+ "Phase 1 accepts every input case (notes + transcript, notes only,",
14623
+ "or transcript only) and Phase 2 enhances the notes file in place",
14624
+ "rather than authoring it from scratch.",
14625
+ "",
14626
+ "Notes and insights files may carry optional frontmatter fields",
14627
+ "`slug`, `source`, `transcript_available`, and `confidence`",
14628
+ "alongside the existing `meeting_type` and `areas` fields. Missing",
14629
+ "frontmatter is treated as a normal input case \u2014 never fail-loud.",
14630
+ "",
14631
+ "Meeting notes may declare a `meeting_type` (one of the project's",
14632
+ "recognized types) and an `areas: [...]` list. The `meeting_type`",
14633
+ "resolves to a generic kind \u2014 `planning` / `review` / `brainstorm` /",
14634
+ "`standup` / `external` / `other` \u2014 that drives type-specific",
14635
+ "extraction rules in phases 1\u20132. The `areas` list scopes phase-4",
14636
+ "direct edits to the doc-root sub-folders declared in the project's",
14637
+ "**Area \u2192 doc-root mapping** table. When both fields are absent",
14638
+ "the pipeline falls back to the default workflow (kind `other`,",
14639
+ "no area gating).",
14640
+ "",
14641
+ "Phase 4 applies an **agent-workability split** to action items:",
14642
+ "items that yield a documentation deliverable an agent can author",
14643
+ "are filed through their dedicated downstream channel",
14644
+ "(`req:write`, `docs:write`, `bcm:*`, `research:scope`, roadmap",
14645
+ "follow-up); human-owned tasks (send / schedule / install /",
14646
+ "decide / communicate / get-access / build-elsewhere) are",
14647
+ "recorded **only** in the notes `## Action Items` table, which is",
14648
+ "the **system of record for human follow-ups** \u2014 no GitHub issue",
14649
+ "is filed for them. Consumers tune or disable the split via",
14650
+ "`AgentConfigOptions.meetings.actionItemFiling`."
14651
+ ].join("\n"),
14652
+ platforms: {
14653
+ cursor: { exclude: true }
14654
+ },
14655
+ tags: ["workflow"]
14063
14656
  }
14064
- }
14065
- }
14066
- return false;
14067
- }
14068
- function isPhaseLabelOwnedByExcluded(phaseLabel, excludedBundles) {
14069
- if (excludedBundles.length === 0) {
14070
- return false;
14071
- }
14072
- for (const bundleName of excludedBundles) {
14073
- const ownership = BUNDLE_OWNERSHIP[bundleName];
14074
- if (!ownership) {
14075
- continue;
14076
- }
14077
- for (const entry of ownership.phaseLabelPrefixes) {
14078
- if (entry.endsWith(":")) {
14079
- if (phaseLabel.startsWith(entry)) {
14080
- return true;
14081
- }
14082
- } else if (phaseLabel === entry) {
14083
- return true;
14657
+ ],
14658
+ skills: [processMeetingSkill],
14659
+ subAgents: [buildMeetingAnalystSubAgent(tier)],
14660
+ labels: [
14661
+ {
14662
+ name: "type:meeting-processing",
14663
+ color: "5319E7",
14664
+ description: "Meeting transcript processing pipeline"
14665
+ },
14666
+ {
14667
+ name: "meeting:extract",
14668
+ color: "C5DEF5",
14669
+ description: "Phase 1: raw extraction from a meeting transcript"
14670
+ },
14671
+ {
14672
+ name: "meeting:notes",
14673
+ color: "BFDADC",
14674
+ description: "Phase 2: curated notes derived from an extraction"
14675
+ },
14676
+ {
14677
+ name: "meeting:draft",
14678
+ color: "D4C5F9",
14679
+ description: "Phase 3: draft follow-up issues proposed from notes"
14680
+ },
14681
+ {
14682
+ name: "meeting:link",
14683
+ color: "FEF2C0",
14684
+ description: "Phase 4: linking/reconciling drafted issues with existing work"
14084
14685
  }
14085
- }
14086
- }
14087
- return false;
14088
- }
14089
- function isScheduledTaskOwnedByExcluded(taskId, excludedBundles) {
14090
- if (excludedBundles.length === 0) {
14091
- return false;
14092
- }
14093
- for (const bundleName of excludedBundles) {
14094
- const ownership = BUNDLE_OWNERSHIP[bundleName];
14095
- if (!ownership) {
14096
- continue;
14097
- }
14098
- if (ownership.scheduledTaskIds.includes(taskId)) {
14099
- return true;
14100
- }
14101
- }
14102
- return false;
14103
- }
14104
- function hasAnyDocsEmittingBundle(excludedBundles) {
14105
- for (const [bundleName, ownership] of Object.entries(BUNDLE_OWNERSHIP)) {
14106
- if (!ownership.emitsDocs) {
14107
- continue;
14108
- }
14109
- if (!excludedBundles.includes(bundleName)) {
14110
- return true;
14111
- }
14112
- }
14113
- return false;
14114
- }
14115
- function hasAnyDownstreamIssueKindBundle(excludedBundles) {
14116
- for (const [bundleName, ownership] of Object.entries(BUNDLE_OWNERSHIP)) {
14117
- if (!ownership.downstreamIssueKinds) {
14118
- continue;
14119
- }
14120
- if (!excludedBundles.includes(bundleName)) {
14121
- return true;
14122
- }
14123
- }
14124
- return false;
14125
- }
14126
- function findOwnersOfTypeLabel(typeLabel) {
14127
- const owners = [];
14128
- for (const [bundleName, ownership] of Object.entries(BUNDLE_OWNERSHIP)) {
14129
- if (ownership.typeLabels.includes(typeLabel)) {
14130
- owners.push(bundleName);
14131
- }
14132
- }
14133
- return owners;
14686
+ ]
14687
+ };
14134
14688
  }
14689
+ var meetingAnalysisBundle = buildMeetingAnalysisBundle();
14135
14690
 
14136
14691
  // src/agent/bundles/run-ratio.ts
14137
14692
  var DEFAULT_DISPATCH_TO_HOUSEKEEPING_RATIO = 4;
@@ -19768,6 +20323,8 @@ function buildPeopleProfileAnalystSubAgent(paths, issueDefaults, tier) {
19768
20323
  ` - \`priority:${labelsForPhase(issueDefaults, "company:research").priority}\``,
19769
20324
  ` - \`status:${labelsForPhase(issueDefaults, "company:research").status}\``,
19770
20325
  "",
20326
+ ...renderIssueTypeAssignmentStep(),
20327
+ "",
19771
20328
  " The issue body must include:",
19772
20329
  " - A **discovery source** line naming this person profile",
19773
20330
  " (`Discovered while profiling: <person name>`)",
@@ -19788,6 +20345,8 @@ function buildPeopleProfileAnalystSubAgent(paths, issueDefaults, tier) {
19788
20345
  ` - \`priority:${labelsForPhase(issueDefaults, "software:research").priority}\``,
19789
20346
  ` - \`status:${labelsForPhase(issueDefaults, "software:research").status}\``,
19790
20347
  "",
20348
+ ...renderIssueTypeAssignmentStep(),
20349
+ "",
19791
20350
  " The issue body must include:",
19792
20351
  " - A **discovery source** line naming this person profile",
19793
20352
  " (`Discovered while profiling: <person name>`)",
@@ -20070,6 +20629,8 @@ function buildProfilePersonSkill(paths, issueDefaults) {
20070
20629
  ` \`priority:${labelsForPhase(issueDefaults, "people:research").priority}\`, and \`status:${labelsForPhase(issueDefaults, "people:research").status}\`. Body must include the`,
20071
20630
  " person's name, selected role, primary company, framing, and any",
20072
20631
  " overrides.",
20632
+ "",
20633
+ ...renderIssueTypeAssignmentStep(),
20073
20634
  "2. Execute Phase 1 (Research) of the people-profile-analyst agent.",
20074
20635
  "3. Phase 1 creates the `people:draft` issue. Phase 2 may create a",
20075
20636
  " `people:followup` issue. Each downstream issue declares its",
@@ -20161,6 +20722,8 @@ function buildRefreshPersonSkill(paths, issueDefaults) {
20161
20722
  "1. Create a `people:refresh` issue with `type:people-profile`,",
20162
20723
  ` \`priority:${labelsForPhase(issueDefaults, "people:refresh").priority}\`, and \`status:${labelsForPhase(issueDefaults, "people:refresh").status}\`. Body must include the`,
20163
20724
  " profile path and any overrides.",
20725
+ "",
20726
+ ...renderIssueTypeAssignmentStep(),
20164
20727
  "2. Execute Phase 4 (Refresh) of the people-profile-analyst agent.",
20165
20728
  "",
20166
20729
  "## Output",
@@ -23857,6 +24420,8 @@ function buildRegulatoryResearchAnalystSubAgent(paths, issueDefaults) {
23857
24420
  ` - \`priority:${labelsForPhase(issueDefaults, "company:research").priority}\``,
23858
24421
  ` - \`status:${labelsForPhase(issueDefaults, "company:research").status}\``,
23859
24422
  "",
24423
+ ...renderIssueTypeAssignmentStep(),
24424
+ "",
23860
24425
  "9. **Verify all handoffs were created** before committing.",
23861
24426
  "",
23862
24427
  "10. **Commit and push** the regulation page. Close the research",
@@ -23967,6 +24532,8 @@ function buildRegulatoryResearchAnalystSubAgent(paths, issueDefaults) {
23967
24532
  ` - \`priority:${labelsForPhase(issueDefaults, "req:scan").priority}\``,
23968
24533
  ` - \`status:${labelsForPhase(issueDefaults, "req:scan").status}\``,
23969
24534
  "",
24535
+ ...renderIssueTypeAssignmentStep(),
24536
+ "",
23970
24537
  " The body must also:",
23971
24538
  " - Identify the target category as **SEC** (security & compliance)",
23972
24539
  " in the body so the downstream writer phase picks up the right",
@@ -23986,6 +24553,8 @@ function buildRegulatoryResearchAnalystSubAgent(paths, issueDefaults) {
23986
24553
  ` - \`priority:${labelsForPhase(issueDefaults, "company:research").priority}\``,
23987
24554
  ` - \`status:${labelsForPhase(issueDefaults, "company:research").status}\``,
23988
24555
  "",
24556
+ ...renderIssueTypeAssignmentStep(),
24557
+ "",
23989
24558
  "6. **Create `people:research` issues** for notable regulatory",
23990
24559
  " leaders (commissioners, agency directors, lead policy authors)",
23991
24560
  " mentioned on the regulation page. Keep this list small \u2014 at most",
@@ -23997,6 +24566,8 @@ function buildRegulatoryResearchAnalystSubAgent(paths, issueDefaults) {
23997
24566
  ` - \`priority:${labelsForPhase(issueDefaults, "people:research").priority}\``,
23998
24567
  ` - \`status:${labelsForPhase(issueDefaults, "people:research").status}\``,
23999
24568
  "",
24569
+ ...renderIssueTypeAssignmentStep(),
24570
+ "",
24000
24571
  "7. **Commit and push** the updated regulation page. Close the",
24001
24572
  " impact issue.",
24002
24573
  "",
@@ -24164,6 +24735,8 @@ function buildScanRegulatoryLandscapeSkill(paths, issueDefaults) {
24164
24735
  ` \`priority:${labelsForPhase(issueDefaults, "regulatory:scan").priority}\`, and \`status:${labelsForPhase(issueDefaults, "regulatory:scan").status}\`. The body must include the`,
24165
24736
  " scope slug, the scope type, and any known regulations to include",
24166
24737
  " or exclude.",
24738
+ "",
24739
+ ...renderIssueTypeAssignmentStep(),
24167
24740
  "2. Execute Phase 1 (Scan) of the regulatory-research-analyst agent.",
24168
24741
  "3. Phase 1 writes the scan report and creates one",
24169
24742
  " `regulatory:research` issue per prioritized regulation.",
@@ -24240,6 +24813,8 @@ function buildResearchRegulationSkill(paths, issueDefaults) {
24240
24813
  ` \`type:regulatory-research\`, \`priority:${labelsForPhase(issueDefaults, "regulatory:research").priority}\`, and`,
24241
24814
  ` \`status:${labelsForPhase(issueDefaults, "regulatory:research").status}\`. The body must include the scope slug and the`,
24242
24815
  " regulation slug.",
24816
+ "",
24817
+ ...renderIssueTypeAssignmentStep(),
24243
24818
  "2. Execute Phase 2 (Research) of the regulatory-research-analyst",
24244
24819
  " agent. The agent runs 6\u201312 targeted web searches and populates",
24245
24820
  " the regulation page.",
@@ -24307,6 +24882,8 @@ function buildImpactRegulationSkill(paths, issueDefaults) {
24307
24882
  ` \`status:${labelsForPhase(issueDefaults, "regulatory:impact").status}\` (or \`status:blocked\` when still dependent on a`,
24308
24883
  " Phase 2 research issue). Body must include the regulation page",
24309
24884
  " path.",
24885
+ "",
24886
+ ...renderIssueTypeAssignmentStep(),
24310
24887
  "2. Execute Phase 3 (Impact) of the regulatory-research-analyst",
24311
24888
  " agent. No web searches \u2014 pure synthesis from the regulation",
24312
24889
  " page and project context.",
@@ -27142,6 +27719,8 @@ function buildWriteRequirementSkill(paths, issueDefaults) {
27142
27719
  " Body must include the category, tier, output path, and a pointer",
27143
27720
  " to the upstream proposal (or a direct user description if no",
27144
27721
  " proposals file exists).",
27722
+ "",
27723
+ ...renderIssueTypeAssignmentStep(),
27145
27724
  "2. Execute the write phase of the requirements-writer agent.",
27146
27725
  "3. The agent writes one requirement document, updates the category",
27147
27726
  " index, opens a PR, and closes the issue.",
@@ -27731,6 +28310,8 @@ function buildRequirementsAnalystSubAgent(paths, issueDefaults) {
27731
28310
  ` \`--label "tier:<tier-slug>"\`, \`--label "status:${labelsForPhase(issueDefaults, "req:write").status}"\`, and`,
27732
28311
  ` \`--label "priority:${labelsForPhase(issueDefaults, "req:write").priority}"\`.`,
27733
28312
  "",
28313
+ ...renderIssueTypeAssignmentStep(),
28314
+ "",
27734
28315
  " The body must carry the three writer-required fields in an",
27735
28316
  " `## Objective` block, written as bold-prefixed lines so the",
27736
28317
  " writer's intake parser can pull them out, plus the reserved-ID",
@@ -27877,6 +28458,8 @@ function buildScanRequirementsSkill(issueDefaults) {
27877
28458
  "",
27878
28459
  `1. Create a \`req:scan\` issue with \`type:requirement\`, \`priority:${labelsForPhase(issueDefaults, "req:scan").priority}\`,`,
27879
28460
  ` and \`status:${labelsForPhase(issueDefaults, "req:scan").status}\`. Body must list the files to read and the scan scope.`,
28461
+ "",
28462
+ ...renderIssueTypeAssignmentStep(),
27880
28463
  "2. Execute Phase 1 (Scan) of the requirements-analyst agent.",
27881
28464
  "3. If gaps are found, a `req:draft-trace` issue is created automatically.",
27882
28465
  "",
@@ -28925,6 +29508,7 @@ function buildRequirementsReviewerSubAgent(paths, issueDefaults) {
28925
29508
  "- Labels: `type:requirement`, `req:write`, the matching",
28926
29509
  " `tier:*` label derived from the referencing document's Tier,",
28927
29510
  ` \`priority:${labelsForPhase(issueDefaults, "req:write").priority}\`, \`status:${labelsForPhase(issueDefaults, "req:write").status}\`.`,
29511
+ ...renderIssueTypeAssignmentStep({ indent: "", bullet: true }),
28928
29512
  "- If any of Category / Tier / Output Path cannot be derived from",
28929
29513
  " the referencing document, open the follow-up with",
28930
29514
  " `status:needs-attention` and a `Missing:` line, per the same",
@@ -29080,6 +29664,8 @@ function buildReviewRequirementsSkill(paths, issueDefaults) {
29080
29664
  ` \`priority:${labelsForPhase(issueDefaults, "req:review").priority}\`, and \`status:${labelsForPhase(issueDefaults, "req:review").status}\`. Body must include the`,
29081
29665
  " scope, the requirements root (or accept the default), and the",
29082
29666
  " output path.",
29667
+ "",
29668
+ ...renderIssueTypeAssignmentStep(),
29083
29669
  "2. Execute the review phase of the requirements-reviewer agent.",
29084
29670
  "3. The agent runs the 12-check audit, writes a structured report",
29085
29671
  " grouped by Critical / Warning / Info (plus a separate",
@@ -29182,6 +29768,8 @@ function buildDeprecateRequirementSkill(_paths, issueDefaults) {
29182
29768
  ` \`priority:${labelsForPhase(issueDefaults, "req:deprecate").priority}\`, and \`status:${labelsForPhase(issueDefaults, "req:deprecate").status}\`. Body must include the`,
29183
29769
  " Targets, Transition, and Reason sections above (optionally",
29184
29770
  " `Pre-approved: yes`).",
29771
+ "",
29772
+ ...renderIssueTypeAssignmentStep(),
29185
29773
  "2. Execute the deprecation phase of the requirements-reviewer",
29186
29774
  " agent.",
29187
29775
  "3. The agent verifies inputs, pauses for human confirmation (or",
@@ -29756,6 +30344,8 @@ function buildResearchSkill(paths, issueDefaults) {
29756
30344
  "1. Create a `research:scope` issue with `type:research`,",
29757
30345
  ` \`priority:${labelsForPhase(issueDefaults, "research:scope").priority}\`, and \`status:${labelsForPhase(issueDefaults, "research:scope").status}\`. Body must include the`,
29758
30346
  " verbatim question, authorized sources, and any overrides.",
30347
+ "",
30348
+ ...renderIssueTypeAssignmentStep(),
29759
30349
  "2. Execute Phase 1 (Scope) of the research-analyst agent.",
29760
30350
  "3. Phase 1 creates `research:slice` issues (one per slice) and a",
29761
30351
  " single `research:verify` issue. Each downstream issue declares",
@@ -30571,6 +31161,8 @@ function buildSoftwareProfileAnalystSubAgent(paths, issueDefaults, tier) {
30571
31161
  ` - \`priority:${labelsForPhase(issueDefaults, "bcm:outline").priority}\``,
30572
31162
  ` - \`status:${labelsForPhase(issueDefaults, "bcm:outline").status}\``,
30573
31163
  "",
31164
+ ...renderIssueTypeAssignmentStep(),
31165
+ "",
30574
31166
  " The issue body must include:",
30575
31167
  " - A **discovery source** line naming this software profile",
30576
31168
  " (`Discovered while mapping: <product name>`)",
@@ -30639,6 +31231,8 @@ function buildSoftwareProfileAnalystSubAgent(paths, issueDefaults, tier) {
30639
31231
  " `software:research` phase with `type:software-profile`,",
30640
31232
  ` \`priority:${labelsForPhase(issueDefaults, "software:research").priority}\`, and \`status:${labelsForPhase(issueDefaults, "software:research").status}\`.`,
30641
31233
  "",
31234
+ ...renderIssueTypeAssignmentStep(),
31235
+ "",
30642
31236
  "3. **Enqueue a `company:research` issue for the vendor company** if",
30643
31237
  " no matching profile already exists under `<COMPANY_PROFILES_DIR>/`.",
30644
31238
  " The vendor (the company that builds or maintains the product)",
@@ -30651,6 +31245,8 @@ function buildSoftwareProfileAnalystSubAgent(paths, issueDefaults, tier) {
30651
31245
  ` - \`priority:${labelsForPhase(issueDefaults, "company:research").priority}\``,
30652
31246
  ` - \`status:${labelsForPhase(issueDefaults, "company:research").status}\``,
30653
31247
  "",
31248
+ ...renderIssueTypeAssignmentStep(),
31249
+ "",
30654
31250
  " The issue body must include:",
30655
31251
  " - A **discovery source** line naming this software profile",
30656
31252
  " (`Discovered while profiling: <product name>`)",
@@ -30670,6 +31266,8 @@ function buildSoftwareProfileAnalystSubAgent(paths, issueDefaults, tier) {
30670
31266
  ` - \`priority:${labelsForPhase(issueDefaults, "people:research").priority}\``,
30671
31267
  ` - \`status:${labelsForPhase(issueDefaults, "people:research").status}\``,
30672
31268
  "",
31269
+ ...renderIssueTypeAssignmentStep(),
31270
+ "",
30673
31271
  " The issue body must include:",
30674
31272
  " - A **discovery source** line naming this software profile",
30675
31273
  " (`Discovered while profiling: <product name>`)",
@@ -30860,6 +31458,8 @@ function buildProfileSoftwareSkill(paths, issueDefaults) {
30860
31458
  "1. Create a `software:research` issue with `type:software-profile`,",
30861
31459
  ` \`priority:${labelsForPhase(issueDefaults, "software:research").priority}\`, and \`status:${labelsForPhase(issueDefaults, "software:research").status}\`. Body must include the`,
30862
31460
  " product name, selected type, framing, and any overrides.",
31461
+ "",
31462
+ ...renderIssueTypeAssignmentStep(),
30863
31463
  "2. Execute Phase 1 (Research) of the software-profile-analyst",
30864
31464
  " agent.",
30865
31465
  "3. Phase 1 creates the `software:profile` issue. Phase 2 creates",
@@ -30948,6 +31548,8 @@ function buildMapSoftwareSkill(paths, issueDefaults) {
30948
31548
  "1. Create a `software:map` issue with `type:software-profile`,",
30949
31549
  ` \`priority:${labelsForPhase(issueDefaults, "software:map").priority}\`, and \`status:${labelsForPhase(issueDefaults, "software:map").status}\`. Body must include the`,
30950
31550
  " profile path and any overrides.",
31551
+ "",
31552
+ ...renderIssueTypeAssignmentStep(),
30951
31553
  "2. Execute Phase 4 (Map) of the software-profile-analyst agent.",
30952
31554
  "3. Phase 4 writes the `## Capability Mapping` section, enqueues",
30953
31555
  " `bcm:outline` issues for unmapped features above the threshold,",
@@ -31560,6 +32162,8 @@ function buildStandardsResearchAnalystSubAgent(paths, issueDefaults) {
31560
32162
  ` - \`priority:${labelsForPhase(issueDefaults, "company:research").priority}\``,
31561
32163
  ` - \`status:${labelsForPhase(issueDefaults, "company:research").status}\``,
31562
32164
  "",
32165
+ ...renderIssueTypeAssignmentStep(),
32166
+ "",
31563
32167
  "6. **Create `people:research` issues** for every entry in *People of",
31564
32168
  " Interest* that does not already have a canonical person profile",
31565
32169
  " or an open `people:research` issue. The `people-profile` bundle",
@@ -31571,6 +32175,8 @@ function buildStandardsResearchAnalystSubAgent(paths, issueDefaults) {
31571
32175
  ` - \`priority:${labelsForPhase(issueDefaults, "people:research").priority}\``,
31572
32176
  ` - \`status:${labelsForPhase(issueDefaults, "people:research").status}\``,
31573
32177
  "",
32178
+ ...renderIssueTypeAssignmentStep(),
32179
+ "",
31574
32180
  "7. **Verify all handoffs were created** before committing. For each",
31575
32181
  " entity in both tables, confirm an existing profile or a fresh",
31576
32182
  " handoff issue exists. Do not commit with missing handoffs.",
@@ -31864,6 +32470,8 @@ function buildStandardsResearchAnalystSubAgent(paths, issueDefaults) {
31864
32470
  ` - \`priority:${labelsForPhase(issueDefaults, "company:research").priority}\``,
31865
32471
  ` - \`status:${labelsForPhase(issueDefaults, "company:research").status}\``,
31866
32472
  "",
32473
+ ...renderIssueTypeAssignmentStep(),
32474
+ "",
31867
32475
  "6. **Hand off canonical people profiles** by creating one",
31868
32476
  " `people:research` issue per leadership entry that does not",
31869
32477
  " already have a canonical profile or an open `people:research`",
@@ -31875,6 +32483,8 @@ function buildStandardsResearchAnalystSubAgent(paths, issueDefaults) {
31875
32483
  ` - \`priority:${labelsForPhase(issueDefaults, "people:research").priority}\``,
31876
32484
  ` - \`status:${labelsForPhase(issueDefaults, "people:research").status}\``,
31877
32485
  "",
32486
+ ...renderIssueTypeAssignmentStep(),
32487
+ "",
31878
32488
  "7. **Commit and push** the organization page. Close the",
31879
32489
  " organizations issue.",
31880
32490
  "",
@@ -32049,6 +32659,8 @@ function buildScopeStandardsResearchSkill(paths, issueDefaults) {
32049
32659
  ` \`priority:${labelsForPhase(issueDefaults, "standards:scope").priority}\`, and \`status:${labelsForPhase(issueDefaults, "standards:scope").status}\`. The body must include the`,
32050
32660
  " standard slug, the list of target versions (or a request to",
32051
32661
  " enumerate), and any known governance bodies.",
32662
+ "",
32663
+ ...renderIssueTypeAssignmentStep(),
32052
32664
  "2. Execute Phase 1 (Scope) of the standards-research-analyst agent.",
32053
32665
  "3. Phase 1 writes the query plan and creates one `standards:research`",
32054
32666
  " issue per target version, one `standards:compare` issue (blocked",
@@ -32113,6 +32725,8 @@ function buildResearchStandardSkill(paths, issueDefaults) {
32113
32725
  "1. Create a `standards:research` issue with",
32114
32726
  ` \`type:standards-research\`, \`priority:${labelsForPhase(issueDefaults, "standards:research").priority}\`, and \`status:${labelsForPhase(issueDefaults, "standards:research").status}\`.`,
32115
32727
  " The body must include the standard slug and the version slug.",
32728
+ "",
32729
+ ...renderIssueTypeAssignmentStep(),
32116
32730
  "2. Execute Phase 2 (Research) of the standards-research-analyst",
32117
32731
  " agent. The agent runs 6\u201310 targeted web searches and populates",
32118
32732
  " the version page.",
@@ -32186,6 +32800,8 @@ function buildCompareStandardsSkill(paths, issueDefaults) {
32186
32800
  ` \`type:standards-research\`, \`priority:${labelsForPhase(issueDefaults, "standards:compare").priority}\`, and \`status:${labelsForPhase(issueDefaults, "standards:compare").status}\`.`,
32187
32801
  " Body must include the standard slug and the list of version",
32188
32802
  " pages under comparison.",
32803
+ "",
32804
+ ...renderIssueTypeAssignmentStep(),
32189
32805
  "2. Execute Phase 3 (Compare) of the standards-research-analyst",
32190
32806
  " agent. No web searches \u2014 pure synthesis from the version pages.",
32191
32807
  "",
@@ -32254,6 +32870,8 @@ function buildExtensionStandardSkill(paths, issueDefaults) {
32254
32870
  " The body must include the standard slug, the entity slug, the",
32255
32871
  " closest resource, and a link to the source BCM or canvas",
32256
32872
  " document.",
32873
+ "",
32874
+ ...renderIssueTypeAssignmentStep(),
32257
32875
  "2. Execute Phase 4 (Extension) of the standards-research-analyst",
32258
32876
  " agent. The agent runs 4\u20138 targeted web searches against",
32259
32877
  " community registries, implementation guides, and working-group",
@@ -32319,6 +32937,8 @@ function buildOrganizationsStandardSkill(paths, issueDefaults) {
32319
32937
  ` \`type:standards-research\`, \`priority:${labelsForPhase(issueDefaults, "standards:organizations").priority}\`, and \`status:${labelsForPhase(issueDefaults, "standards:organizations").status}\`.`,
32320
32938
  " Body must include the standard slug, the organization slug, and",
32321
32939
  " the organization's governance role.",
32940
+ "",
32941
+ ...renderIssueTypeAssignmentStep(),
32322
32942
  "2. Execute Phase 5 (Organizations) of the",
32323
32943
  " standards-research-analyst agent. The agent runs 4\u20138 targeted",
32324
32944
  " web searches.",
@@ -32628,6 +33248,14 @@ var UPSTREAM_CONFIGULATOR_DOCS_SUMMARY = [
32628
33248
  "",
32629
33249
  "Pick the `type:*` and matching prefix that describes the work (`feat:`, `fix:`, `chore:`, `docs:`, `refactor:`). Set `priority:*` from the urgency and use `status:blocked` instead of `status:ready` if the issue depends on another open issue.",
32630
33250
  "",
33251
+ "Then set the new issue's **GitHub issue type** \u2014 a separate field from the `type:*` label, which `gh issue create` cannot set. Derive it from the title prefix (`epic:` \u2192 Epic, `feat:` \u2192 Feature, `fix:` \u2192 Bug, everything else \u2192 Task). The local `.claude/procedures/set-issue-type.sh` helper resolves owner/repo from the **current** checkout, so a cross-repo filing needs the `updateIssueIssueType` GraphQL flow with an explicit owner/repo:",
33252
+ "",
33253
+ "```sh",
33254
+ "gh api graphql -f query='query($owner:String!,$repo:String!){repository(owner:$owner,name:$repo){issueTypes(first:50){nodes{id name}}}}' -f owner=codedrifters -f repo=packages",
33255
+ "gh issue view <issue-number> --repo codedrifters/packages --json id --jq '.id'",
33256
+ "gh api graphql -f query='mutation($issueId:ID!,$typeId:ID!){updateIssueIssueType(input:{issueId:$issueId,issueTypeId:$typeId}){issue{number issueType{name}}}}' -f issueId=<issue-node-id> -f typeId=<issue-type-node-id>",
33257
+ "```",
33258
+ "",
32631
33259
  "See `.claude/rules/upstream-configulator-docs-detail.md` for the full reference \u2014 extended `gh` recipes, common pitfalls (rate limits, base64 decoding, search vs. browse), and a complete issue-creation walkthrough."
32632
33260
  ].join("\n");
32633
33261
  var UPSTREAM_CONFIGULATOR_DOCS_DETAIL = [
@@ -32736,6 +33364,16 @@ var UPSTREAM_CONFIGULATOR_DOCS_DETAIL = [
32736
33364
  "- `priority:*` \u2014 pick exactly one of `priority:critical`, `priority:high`, `priority:medium`, `priority:low`, `priority:trivial`. Default to `priority:medium` when nothing in the request signals urgency. Use `priority:high` for blockers and `priority:critical` only for outages or release-blocking work.",
32737
33365
  "- `status:*` \u2014 `status:ready` for self-contained issues, `status:blocked` when the issue declares `Depends on: #<n>` against another open issue.",
32738
33366
  "",
33367
+ "### GitHub issue type",
33368
+ "",
33369
+ "The GitHub **issue type** (Epic / Feature / Bug / Task) is a separate field from the `type:*` label, and `gh issue create` cannot set it. Derive it from the title prefix \u2014 `epic:` \u2192 Epic, `feat:` \u2192 Feature, `fix:` \u2192 Bug, everything else \u2192 Task \u2014 and apply it in a follow-up call. The bundled `.claude/procedures/set-issue-type.sh` helper resolves owner/repo from the **current** checkout, so a cross-repo filing against `codedrifters/packages` uses the `updateIssueIssueType` GraphQL flow with an explicit owner/repo instead:",
33370
+ "",
33371
+ "```sh",
33372
+ "gh api graphql -f query='query($owner:String!,$repo:String!){repository(owner:$owner,name:$repo){issueTypes(first:50){nodes{id name}}}}' -f owner=codedrifters -f repo=packages",
33373
+ "gh issue view <issue-number> --repo codedrifters/packages --json id --jq '.id'",
33374
+ "gh api graphql -f query='mutation($issueId:ID!,$typeId:ID!){updateIssueIssueType(input:{issueId:$issueId,issueTypeId:$typeId}){issue{number issueType{name}}}}' -f issueId=<issue-node-id> -f typeId=<issue-type-node-id>",
33375
+ "```",
33376
+ "",
32739
33377
  "### Worked example",
32740
33378
  "",
32741
33379
  "Suppose configulator's `software-profile-analyst` agent prompt is missing a step you need. Instead of forking the prompt locally:",
@@ -32749,6 +33387,8 @@ var UPSTREAM_CONFIGULATOR_DOCS_DETAIL = [
32749
33387
  " --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`'",
32750
33388
  "```",
32751
33389
  "",
33390
+ "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.",
33391
+ "",
32752
33392
  "After filing, reference the upstream issue from any local workaround so it can be reverted once the upstream fix lands:",
32753
33393
  "",
32754
33394
  "```typescript",
@@ -34925,11 +35565,34 @@ var AgentConfig = class _AgentConfig extends import_projen8.Component {
34925
35565
  ).split("\n"),
34926
35566
  executable: true
34927
35567
  });
35568
+ new import_projen8.TextFile(
35569
+ this,
35570
+ ".claude/procedures/check-issue-template-labels.sh",
35571
+ {
35572
+ lines: renderIssueTemplateLabelsCheckerScript(
35573
+ resolvedIssueTemplates
35574
+ ).split("\n"),
35575
+ executable: true
35576
+ }
35577
+ );
34928
35578
  }
34929
35579
  if (resolvedIssueTemplates.emitStarterDoc) {
34930
35580
  new import_projen8.SampleFile(this.project, resolvedIssueTemplates.templatesPath, {
34931
35581
  contents: renderIssueTemplatesStarterPage(resolvedIssueTemplates)
34932
35582
  });
35583
+ new import_projen8.TextFile(
35584
+ this,
35585
+ issueTemplatesGeneratedPath(resolvedIssueTemplates.templatesPath),
35586
+ {
35587
+ lines: renderIssueTemplatesGeneratedPage(
35588
+ resolvedIssueTemplates,
35589
+ collectIssueTemplateRecipeStubs(
35590
+ this.activeBundles,
35591
+ resolveIssueDefaults(this.options.issueDefaults)
35592
+ )
35593
+ ).split("\n")
35594
+ }
35595
+ );
34933
35596
  }
34934
35597
  }
34935
35598
  const resolvedTemporalFraming = validateTemporalFramingConfig(
@@ -41535,6 +42198,7 @@ export const collections = {
41535
42198
  DEFAULT_DECOMPOSITION_TEMPLATE,
41536
42199
  DEFAULT_DISPATCH_MODEL,
41537
42200
  DEFAULT_DISPATCH_TO_HOUSEKEEPING_RATIO,
42201
+ DEFAULT_GITHUB_ISSUE_TYPE,
41538
42202
  DEFAULT_HOUSEKEEPING_MODEL,
41539
42203
  DEFAULT_ISSUE_PRIORITY,
41540
42204
  DEFAULT_ISSUE_STATUS,
@@ -41581,6 +42245,9 @@ export const collections = {
41581
42245
  DEFAULT_UNBLOCK_DEPENDENTS_ENABLED,
41582
42246
  DEFAULT_UPSTREAM_CONFIGULATOR_ENABLED,
41583
42247
  DOCS_SYNC_AUDIT_SCHEMA_VERSION,
42248
+ GITHUB_ISSUE_TYPES,
42249
+ GITHUB_ISSUE_TYPE_BY_TITLE_PREFIX,
42250
+ ISSUE_TEMPLATES_GENERATED_SUFFIX,
41584
42251
  JsiiFaker,
41585
42252
  LAYOUT_ENFORCEMENT,
41586
42253
  LAYOUT_ROOT_BY_PROJECT_TYPE,
@@ -41605,6 +42272,7 @@ export const collections = {
41605
42272
  ResetTask,
41606
42273
  SCHEDULED_TASK_MODEL_VALUES,
41607
42274
  SCOPE_CLASS_VALUES,
42275
+ SET_ISSUE_TYPE_HELPER_PATH,
41608
42276
  SHARED_EDITING_CONFLICT_STRATEGY_VALUES,
41609
42277
  STARLIGHT_ROLE,
41610
42278
  SUPPRESSED_WORKFLOW_RULE_NAMES,
@@ -41675,6 +42343,7 @@ export const collections = {
41675
42343
  checkLinksProcedure,
41676
42344
  classifyIssueScope,
41677
42345
  classifyRun,
42346
+ collectIssueTemplateRecipeStubs,
41678
42347
  companyProfileBundle,
41679
42348
  compileFencedSamples,
41680
42349
  createApiDiffCheck,
@@ -41690,6 +42359,7 @@ export const collections = {
41690
42359
  formatLayoutViolation,
41691
42360
  formatStarlightSingletonViolation,
41692
42361
  getLatestEligibleVersion,
42362
+ githubIssueTypeForTitle,
41693
42363
  githubWorkflowBundle,
41694
42364
  hasAnyDocsEmittingBundle,
41695
42365
  hasAnyDownstreamIssueKindBundle,
@@ -41699,6 +42369,8 @@ export const collections = {
41699
42369
  isScheduledTaskOwnedByExcluded,
41700
42370
  isSuppressedWorkflowRule,
41701
42371
  isTypeLabelOwnedByExcluded,
42372
+ issueTemplatesChildGlob,
42373
+ issueTemplatesGeneratedPath,
41702
42374
  jestBundle,
41703
42375
  labelsForPhase,
41704
42376
  maintenanceAuditBundle,
@@ -41747,10 +42419,16 @@ export const collections = {
41747
42419
  renderCustomDocSections,
41748
42420
  renderExtractApiProcedure,
41749
42421
  renderFocusSection,
42422
+ renderGithubIssueTypeSection,
42423
+ renderGithubIssueTypeSectionLines,
42424
+ renderIssueTemplateLabelsCheckerScript,
41750
42425
  renderIssueTemplatesBundleHook,
41751
42426
  renderIssueTemplatesCheckerScript,
42427
+ renderIssueTemplatesGeneratedPage,
41752
42428
  renderIssueTemplatesRuleContent,
41753
42429
  renderIssueTemplatesStarterPage,
42430
+ renderIssueTypeAssignmentBlanket,
42431
+ renderIssueTypeAssignmentStep,
41754
42432
  renderMeetingTypesSection,
41755
42433
  renderNextRequirementIdProcedure,
41756
42434
  renderPhaseTypeInvariantSection,
@@ -41767,6 +42445,7 @@ export const collections = {
41767
42445
  renderScheduledTasksSection,
41768
42446
  renderScopeGateSection,
41769
42447
  renderScopeGateShellHelpers,
42448
+ renderSetIssueTypeFallbackLines,
41770
42449
  renderSharedEditingBundleHook,
41771
42450
  renderSharedEditingHelperScript,
41772
42451
  renderSharedEditingRuleContent,
@@ -41777,6 +42456,7 @@ export const collections = {
41777
42456
  renderStripToolArtifactTagsProcedure,
41778
42457
  renderTemporalFramingCheckerScript,
41779
42458
  renderTemporalFramingRuleContent,
42459
+ renderTitlePrefixTypeBullets,
41780
42460
  renderUnblockDependentsScript,
41781
42461
  renderUnblockDependentsSection,
41782
42462
  requirementsAnalystBundle,