@codedrifters/configulator 0.0.404 → 0.0.406

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
@@ -213,6 +213,7 @@ __export(index_exports, {
213
213
  CDK_WATCH_DEFAULTS_BY_STAGE: () => CDK_WATCH_DEFAULTS_BY_STAGE,
214
214
  CLAUDE_RULE_TARGET: () => CLAUDE_RULE_TARGET,
215
215
  COMPLETE_JOB_ID: () => COMPLETE_JOB_ID,
216
+ CONVENTIONAL_COMMIT_TYPE_LABELS: () => CONVENTIONAL_COMMIT_TYPE_LABELS,
216
217
  CdkCli: () => CdkCli,
217
218
  DEFAULT_AC_THRESHOLDS: () => DEFAULT_AC_THRESHOLDS,
218
219
  DEFAULT_AGENT_PATHS: () => DEFAULT_AGENT_PATHS,
@@ -227,6 +228,7 @@ __export(index_exports, {
227
228
  DEFAULT_DECOMPOSITION_TEMPLATE: () => DEFAULT_DECOMPOSITION_TEMPLATE,
228
229
  DEFAULT_DISPATCH_MODEL: () => DEFAULT_DISPATCH_MODEL,
229
230
  DEFAULT_DISPATCH_TO_HOUSEKEEPING_RATIO: () => DEFAULT_DISPATCH_TO_HOUSEKEEPING_RATIO,
231
+ DEFAULT_GITHUB_ISSUE_TYPE: () => DEFAULT_GITHUB_ISSUE_TYPE,
230
232
  DEFAULT_HOUSEKEEPING_MODEL: () => DEFAULT_HOUSEKEEPING_MODEL,
231
233
  DEFAULT_ISSUE_PRIORITY: () => DEFAULT_ISSUE_PRIORITY,
232
234
  DEFAULT_ISSUE_STATUS: () => DEFAULT_ISSUE_STATUS,
@@ -273,6 +275,8 @@ __export(index_exports, {
273
275
  DEFAULT_UNBLOCK_DEPENDENTS_ENABLED: () => DEFAULT_UNBLOCK_DEPENDENTS_ENABLED,
274
276
  DEFAULT_UPSTREAM_CONFIGULATOR_ENABLED: () => DEFAULT_UPSTREAM_CONFIGULATOR_ENABLED,
275
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,
276
280
  JsiiFaker: () => JsiiFaker,
277
281
  LAYOUT_ENFORCEMENT: () => LAYOUT_ENFORCEMENT,
278
282
  LAYOUT_ROOT_BY_PROJECT_TYPE: () => LAYOUT_ROOT_BY_PROJECT_TYPE,
@@ -284,6 +288,7 @@ __export(index_exports, {
284
288
  MONOREPO_LAYOUT: () => MONOREPO_LAYOUT,
285
289
  MonorepoProject: () => MonorepoProject,
286
290
  Nvmrc: () => Nvmrc,
291
+ PHASE_LABEL_TYPE_MAP: () => PHASE_LABEL_TYPE_MAP,
287
292
  PROD_DEPLOY_NAME: () => PROD_DEPLOY_NAME,
288
293
  PROGRESS_FILES_FORMAT_VALUES: () => PROGRESS_FILES_FORMAT_VALUES,
289
294
  PnpmWorkspace: () => PnpmWorkspace,
@@ -296,6 +301,7 @@ __export(index_exports, {
296
301
  ResetTask: () => ResetTask,
297
302
  SCHEDULED_TASK_MODEL_VALUES: () => SCHEDULED_TASK_MODEL_VALUES,
298
303
  SCOPE_CLASS_VALUES: () => SCOPE_CLASS_VALUES,
304
+ SET_ISSUE_TYPE_HELPER_PATH: () => SET_ISSUE_TYPE_HELPER_PATH,
299
305
  SHARED_EDITING_CONFLICT_STRATEGY_VALUES: () => SHARED_EDITING_CONFLICT_STRATEGY_VALUES,
300
306
  STARLIGHT_ROLE: () => STARLIGHT_ROLE,
301
307
  SUPPRESSED_WORKFLOW_RULE_NAMES: () => SUPPRESSED_WORKFLOW_RULE_NAMES,
@@ -381,6 +387,7 @@ __export(index_exports, {
381
387
  formatLayoutViolation: () => formatLayoutViolation,
382
388
  formatStarlightSingletonViolation: () => formatStarlightSingletonViolation,
383
389
  getLatestEligibleVersion: () => getLatestEligibleVersion,
390
+ githubIssueTypeForTitle: () => githubIssueTypeForTitle,
384
391
  githubWorkflowBundle: () => githubWorkflowBundle,
385
392
  hasAnyDocsEmittingBundle: () => hasAnyDocsEmittingBundle,
386
393
  hasAnyDownstreamIssueKindBundle: () => hasAnyDownstreamIssueKindBundle,
@@ -438,12 +445,18 @@ __export(index_exports, {
438
445
  renderCustomDocSections: () => renderCustomDocSections,
439
446
  renderExtractApiProcedure: () => renderExtractApiProcedure,
440
447
  renderFocusSection: () => renderFocusSection,
448
+ renderGithubIssueTypeSection: () => renderGithubIssueTypeSection,
449
+ renderGithubIssueTypeSectionLines: () => renderGithubIssueTypeSectionLines,
441
450
  renderIssueTemplatesBundleHook: () => renderIssueTemplatesBundleHook,
442
451
  renderIssueTemplatesCheckerScript: () => renderIssueTemplatesCheckerScript,
443
452
  renderIssueTemplatesRuleContent: () => renderIssueTemplatesRuleContent,
444
453
  renderIssueTemplatesStarterPage: () => renderIssueTemplatesStarterPage,
454
+ renderIssueTypeAssignmentBlanket: () => renderIssueTypeAssignmentBlanket,
455
+ renderIssueTypeAssignmentStep: () => renderIssueTypeAssignmentStep,
445
456
  renderMeetingTypesSection: () => renderMeetingTypesSection,
446
457
  renderNextRequirementIdProcedure: () => renderNextRequirementIdProcedure,
458
+ renderPhaseTypeInvariantSection: () => renderPhaseTypeInvariantSection,
459
+ renderPhaseTypeInvariantShellHelpers: () => renderPhaseTypeInvariantShellHelpers,
447
460
  renderPriorityRulesSection: () => renderPriorityRulesSection,
448
461
  renderProgressFileName: () => renderProgressFileName,
449
462
  renderProgressFilePath: () => renderProgressFilePath,
@@ -456,6 +469,7 @@ __export(index_exports, {
456
469
  renderScheduledTasksSection: () => renderScheduledTasksSection,
457
470
  renderScopeGateSection: () => renderScopeGateSection,
458
471
  renderScopeGateShellHelpers: () => renderScopeGateShellHelpers,
472
+ renderSetIssueTypeFallbackLines: () => renderSetIssueTypeFallbackLines,
459
473
  renderSharedEditingBundleHook: () => renderSharedEditingBundleHook,
460
474
  renderSharedEditingHelperScript: () => renderSharedEditingHelperScript,
461
475
  renderSharedEditingRuleContent: () => renderSharedEditingRuleContent,
@@ -466,6 +480,7 @@ __export(index_exports, {
466
480
  renderStripToolArtifactTagsProcedure: () => renderStripToolArtifactTagsProcedure,
467
481
  renderTemporalFramingCheckerScript: () => renderTemporalFramingCheckerScript,
468
482
  renderTemporalFramingRuleContent: () => renderTemporalFramingRuleContent,
483
+ renderTitlePrefixTypeBullets: () => renderTitlePrefixTypeBullets,
469
484
  renderUnblockDependentsScript: () => renderUnblockDependentsScript,
470
485
  renderUnblockDependentsSection: () => renderUnblockDependentsSection,
471
486
  requirementsAnalystBundle: () => requirementsAnalystBundle,
@@ -495,6 +510,7 @@ __export(index_exports, {
495
510
  resolveSkillEvals: () => resolveSkillEvals,
496
511
  resolveTemplateVariables: () => resolveTemplateVariables,
497
512
  resolveTemporalFraming: () => resolveTemporalFraming,
513
+ resolveTypeLabelForLabels: () => resolveTypeLabelForLabels,
498
514
  resolveTypeScriptProjectOutdir: () => resolveTypeScriptProjectOutdir,
499
515
  resolveUnblockDependents: () => resolveUnblockDependents,
500
516
  runScan: () => runScan,
@@ -504,6 +520,7 @@ __export(index_exports, {
504
520
  stripToolArtifactTagsProcedure: () => stripToolArtifactTagsProcedure,
505
521
  tsdocRecordToFindings: () => tsdocRecordToFindings,
506
522
  turborepoBundle: () => turborepoBundle,
523
+ typeLabelForPhaseLabel: () => typeLabelForPhaseLabel,
507
524
  typescriptBundle: () => typescriptBundle,
508
525
  upstreamConfigulatorDocsBundle: () => upstreamConfigulatorDocsBundle,
509
526
  validateAgentTierConfig: () => validateAgentTierConfig,
@@ -632,6 +649,603 @@ function renderClaimVerificationSection() {
632
649
  ];
633
650
  }
634
651
 
652
+ // src/agent/bundles/bundle-ownership.ts
653
+ var BUNDLE_OWNERSHIP = {
654
+ agenda: {
655
+ typeLabels: ["agenda"],
656
+ phaseLabelPrefixes: ["agenda:"],
657
+ scheduledTaskIds: ["worker-agenda"],
658
+ emitsDocs: true,
659
+ downstreamIssueKinds: true
660
+ },
661
+ "bcm-writer": {
662
+ typeLabels: ["bcm-document"],
663
+ phaseLabelPrefixes: ["bcm:"],
664
+ scheduledTaskIds: ["worker-bcm-writer"],
665
+ emitsDocs: true,
666
+ downstreamIssueKinds: true
667
+ },
668
+ "business-models": {
669
+ typeLabels: ["business-model"],
670
+ phaseLabelPrefixes: ["business-models:"],
671
+ scheduledTaskIds: ["worker-business-models"],
672
+ emitsDocs: true,
673
+ downstreamIssueKinds: true
674
+ },
675
+ "company-profile": {
676
+ typeLabels: ["company-profile"],
677
+ phaseLabelPrefixes: ["company:"],
678
+ scheduledTaskIds: ["worker-company-profile"],
679
+ emitsDocs: true,
680
+ downstreamIssueKinds: true
681
+ },
682
+ "customer-profile": {
683
+ typeLabels: ["customer-profile"],
684
+ phaseLabelPrefixes: ["customer:"],
685
+ scheduledTaskIds: ["worker-customer-profile"],
686
+ emitsDocs: true,
687
+ downstreamIssueKinds: true
688
+ },
689
+ "docs-sync": {
690
+ typeLabels: ["docs-sync"],
691
+ phaseLabelPrefixes: ["docs-sync:"],
692
+ scheduledTaskIds: ["worker-docs-sync"],
693
+ emitsDocs: true,
694
+ downstreamIssueKinds: true
695
+ },
696
+ "industry-discovery": {
697
+ typeLabels: ["industry-discovery"],
698
+ phaseLabelPrefixes: ["industry:"],
699
+ scheduledTaskIds: ["worker-industry-discovery"],
700
+ emitsDocs: true,
701
+ downstreamIssueKinds: true
702
+ },
703
+ "maintenance-audit": {
704
+ typeLabels: ["maintenance"],
705
+ phaseLabelPrefixes: ["maint:"],
706
+ scheduledTaskIds: ["worker-maintenance"],
707
+ emitsDocs: false,
708
+ downstreamIssueKinds: true
709
+ },
710
+ "meeting-analysis": {
711
+ typeLabels: ["meeting-processing"],
712
+ phaseLabelPrefixes: ["meeting:"],
713
+ scheduledTaskIds: ["worker-meeting-analysis"],
714
+ emitsDocs: true,
715
+ downstreamIssueKinds: true
716
+ },
717
+ orchestrator: {
718
+ // The orchestrator pipeline manager itself ships with the
719
+ // `worker-orchestrator` scheduled task. It owns no `type:*` label
720
+ // (it dispatches every type) and no phase-label prefix.
721
+ typeLabels: [],
722
+ phaseLabelPrefixes: [],
723
+ scheduledTaskIds: ["worker-orchestrator"],
724
+ emitsDocs: false,
725
+ downstreamIssueKinds: false
726
+ },
727
+ "people-profile": {
728
+ typeLabels: ["people-profile"],
729
+ phaseLabelPrefixes: ["people:"],
730
+ scheduledTaskIds: ["worker-people-profile"],
731
+ emitsDocs: true,
732
+ downstreamIssueKinds: true
733
+ },
734
+ "pr-review": {
735
+ typeLabels: ["pr-review"],
736
+ phaseLabelPrefixes: [],
737
+ scheduledTaskIds: ["worker-pr-review"],
738
+ emitsDocs: false,
739
+ downstreamIssueKinds: false
740
+ },
741
+ "regulatory-research": {
742
+ typeLabels: ["regulatory-research"],
743
+ phaseLabelPrefixes: ["regulatory:"],
744
+ scheduledTaskIds: ["worker-regulatory-research"],
745
+ emitsDocs: true,
746
+ downstreamIssueKinds: true
747
+ },
748
+ "requirements-analyst": {
749
+ typeLabels: ["requirement"],
750
+ phaseLabelPrefixes: ["req:"],
751
+ scheduledTaskIds: ["worker-requirements-analyst"],
752
+ emitsDocs: true,
753
+ downstreamIssueKinds: true
754
+ },
755
+ "requirements-reviewer": {
756
+ // Co-owns `type:requirement` with the analyst and writer; owns
757
+ // the `req:review` and `req:deprecate` phase labels exactly.
758
+ typeLabels: ["requirement"],
759
+ phaseLabelPrefixes: ["req:review", "req:deprecate"],
760
+ scheduledTaskIds: ["worker-requirements-reviewer"],
761
+ emitsDocs: true,
762
+ downstreamIssueKinds: true
763
+ },
764
+ "requirements-writer": {
765
+ // Co-owns `type:requirement` with the analyst and reviewer; owns
766
+ // the `req:write` phase label exactly.
767
+ typeLabels: ["requirement"],
768
+ phaseLabelPrefixes: ["req:write"],
769
+ scheduledTaskIds: ["worker-requirements-writer"],
770
+ emitsDocs: true,
771
+ downstreamIssueKinds: true
772
+ },
773
+ "research-pipeline": {
774
+ typeLabels: ["research"],
775
+ phaseLabelPrefixes: ["research:"],
776
+ scheduledTaskIds: ["worker-research"],
777
+ emitsDocs: true,
778
+ downstreamIssueKinds: true
779
+ },
780
+ "software-profile": {
781
+ typeLabels: ["software-profile"],
782
+ phaseLabelPrefixes: ["software:"],
783
+ scheduledTaskIds: ["worker-software-profile"],
784
+ emitsDocs: true,
785
+ downstreamIssueKinds: true
786
+ },
787
+ "standards-research": {
788
+ typeLabels: ["standards-research"],
789
+ phaseLabelPrefixes: ["standards:"],
790
+ scheduledTaskIds: ["worker-standards-research"],
791
+ emitsDocs: true,
792
+ downstreamIssueKinds: true
793
+ }
794
+ };
795
+ var CONVENTIONAL_COMMIT_TYPE_LABELS = [
796
+ "type:chore",
797
+ "type:docs",
798
+ "type:feat",
799
+ "type:fix",
800
+ "type:hotfix",
801
+ "type:refactor",
802
+ "type:release"
803
+ ];
804
+ var PHASE_LABEL_TYPE_MAP = buildPhaseLabelTypeMap();
805
+ function typeLabelForPhaseLabel(phaseLabel) {
806
+ const exact = PHASE_LABEL_TYPE_MAP[phaseLabel];
807
+ if (exact !== void 0 && !phaseLabel.endsWith(":")) {
808
+ return exact;
809
+ }
810
+ let bestMatcher;
811
+ for (const matcher of Object.keys(PHASE_LABEL_TYPE_MAP)) {
812
+ if (!matcher.endsWith(":")) {
813
+ continue;
814
+ }
815
+ if (!phaseLabel.startsWith(matcher)) {
816
+ continue;
817
+ }
818
+ if (bestMatcher === void 0 || matcher.length > bestMatcher.length) {
819
+ bestMatcher = matcher;
820
+ }
821
+ }
822
+ return bestMatcher === void 0 ? void 0 : PHASE_LABEL_TYPE_MAP[bestMatcher];
823
+ }
824
+ function resolveTypeLabelForLabels(labels) {
825
+ const phaseLabels = [];
826
+ const candidates = /* @__PURE__ */ new Set();
827
+ for (const label of labels) {
828
+ const typeLabel = typeLabelForPhaseLabel(label);
829
+ if (typeLabel === void 0) {
830
+ continue;
831
+ }
832
+ phaseLabels.push(label);
833
+ candidates.add(typeLabel);
834
+ }
835
+ const candidateTypeLabels = Array.from(candidates).sort();
836
+ if (candidateTypeLabels.length === 0) {
837
+ return { outcome: "none", candidateTypeLabels: [], phaseLabels: [] };
838
+ }
839
+ if (candidateTypeLabels.length === 1) {
840
+ return {
841
+ outcome: "match",
842
+ typeLabel: candidateTypeLabels[0],
843
+ candidateTypeLabels,
844
+ phaseLabels
845
+ };
846
+ }
847
+ return { outcome: "ambiguous", candidateTypeLabels, phaseLabels };
848
+ }
849
+ function renderPhaseTypeInvariantSection(excludeBundles = []) {
850
+ const rows = Object.keys(PHASE_LABEL_TYPE_MAP).filter(
851
+ (matcher) => !isPhaseLabelMatcherOwnedByExcluded(matcher, excludeBundles)
852
+ ).sort().map((matcher) => {
853
+ const display = matcher.endsWith(":") ? `${matcher}*` : matcher;
854
+ const kind = matcher.endsWith(":") ? "prefix" : "exact";
855
+ return `| \`${display}\` | ${kind} | \`${PHASE_LABEL_TYPE_MAP[matcher]}\` |`;
856
+ });
857
+ return [
858
+ "## Phase-label \u2192 `type:<bundle>` invariant",
859
+ "",
860
+ "Every phased-pipeline bundle pairs its `<bundle>:<phase>` labels",
861
+ "with exactly one `type:<bundle>` label. That type label is the",
862
+ "**dedup + dispatch signal**: agents de-duplicate downstream work",
863
+ 'with `gh issue list --label "type:<bundle>"`, and Phase E derives',
864
+ "its funnel-tier sort key from the issue's `type:*` label. An issue",
865
+ "that carries the phase label but a conventional-commit `type:*`",
866
+ "(`type:feat`, `type:docs`, \u2026, stamped from its title prefix by the",
867
+ "generic create-issue workflow) is invisible to that dedup query and",
868
+ "mis-tiers in dispatch.",
869
+ "",
870
+ "The pairing below is derived from the canonical bundle-ownership",
871
+ "map \u2014 the same source of truth that generates the label registry.",
872
+ "Matchers ending in `*` match by prefix; the rest match exactly, and",
873
+ "an exact match always beats a prefix match.",
874
+ "",
875
+ "| Phase label | Match | Required type label |",
876
+ "|-------------|-------|---------------------|",
877
+ ...rows,
878
+ "",
879
+ "### Enforcement",
880
+ "",
881
+ "The Phase D maintenance sweep runs the invariant in auto-correct",
882
+ "mode and emits one summary line:",
883
+ "",
884
+ "```",
885
+ "LABEL_INVARIANT_DONE mode=fix checked=<N> ok=<O> violations=<V> corrected=<C> flagged=<F>",
886
+ "```",
887
+ "",
888
+ "A **report-only** audit is available for repos that prefer",
889
+ "report-then-fix over auto-correct \u2014 same sweep, no mutations:",
890
+ "",
891
+ "```bash",
892
+ ".claude/procedures/check-blocked.sh label-invariant # report only",
893
+ ".claude/procedures/check-blocked.sh label-invariant --fix # apply",
894
+ "```",
895
+ "",
896
+ "### Correction policy",
897
+ "",
898
+ "An issue must carry **exactly one** `type:*` label. Merely *adding*",
899
+ "the required type label to an issue that already carries a",
900
+ "conventional-commit one would leave two, making the funnel-tier sort",
901
+ "key (which reads the **first** `type:*` label) non-deterministic and",
902
+ "violating the label conventions. The correction therefore",
903
+ "**replaces**, in a single atomic `gh issue edit`, so the issue is",
904
+ "never observable carrying two `type:*` labels:",
905
+ "",
906
+ "| Issue state | Action |",
907
+ "|-------------|--------|",
908
+ "| Required type label present, nothing else | none \u2014 compliant, left untouched |",
909
+ "| No `type:*` label at all | **add** the required label |",
910
+ "| Only conventional-commit `type:*` label(s) | **replace** \u2014 remove them, add the required label |",
911
+ "| Required label present **plus** a stray conventional-commit one | **remove** the stray, leaving exactly one |",
912
+ "| A `type:*` label owned by a **different** bundle | **flag** `status:needs-attention` \u2014 never guessed at |",
913
+ "| Phase labels imply **two or more** type labels | **flag** `status:needs-attention` \u2014 never auto-corrected |",
914
+ "",
915
+ "Only a conventional-commit `type:*` label is ever removed. A",
916
+ "`type:*` label owned by another bundle carries real routing",
917
+ "information, so the sweep refuses to guess and hands the issue to a",
918
+ "human instead. Ambiguity is narrower than it looks: all three",
919
+ "requirements bundles declare `type:requirement`, so every `req:*`",
920
+ "phase label resolves to the same type label \u2014 genuine ambiguity",
921
+ "needs phase labels from two bundles with *different* type labels.",
922
+ "",
923
+ "`status:needs-attention` is applied **additively** \u2014 the base",
924
+ "`status:*` label always stays, per the additive-flag rule. Flagging",
925
+ "is idempotent: an issue already carrying the flag is reported but",
926
+ "not re-flagged."
927
+ ].join("\n");
928
+ }
929
+ function isPhaseLabelMatcherOwnedByExcluded(matcher, excludeBundles) {
930
+ if (excludeBundles.length === 0) {
931
+ return false;
932
+ }
933
+ let owned = false;
934
+ for (const [bundleName, ownership] of Object.entries(BUNDLE_OWNERSHIP)) {
935
+ if (!ownership.phaseLabelPrefixes.includes(matcher)) {
936
+ continue;
937
+ }
938
+ owned = true;
939
+ if (!excludeBundles.includes(bundleName)) {
940
+ return false;
941
+ }
942
+ }
943
+ return owned;
944
+ }
945
+ function renderPhaseTypeInvariantShellHelpers() {
946
+ const matchers = Object.keys(PHASE_LABEL_TYPE_MAP);
947
+ const exactMatchers = matchers.filter((m) => !m.endsWith(":")).sort();
948
+ const prefixMatchers = matchers.filter((m) => m.endsWith(":")).sort((a, b) => b.length - a.length || a.localeCompare(b));
949
+ const lines = [
950
+ "# Resolve ONE phase label to the `type:<bundle>` label its owning",
951
+ "# bundle declares. Echoes the label (with the `type:` prefix) or",
952
+ "# nothing when no bundle owns it. Exact-match branches come first,",
953
+ "# so `req:write` (requirements-writer) beats the `req:` prefix",
954
+ "# (requirements-analyst). Generated from BUNDLE_OWNERSHIP \u2014 do not",
955
+ "# hand-edit.",
956
+ "phase_label_type_of() {",
957
+ ' case "${1:-}" in'
958
+ ];
959
+ for (const matcher of exactMatchers) {
960
+ lines.push(` ${matcher}) echo "${PHASE_LABEL_TYPE_MAP[matcher]}" ;;`);
961
+ }
962
+ for (const matcher of prefixMatchers) {
963
+ lines.push(` ${matcher}*) echo "${PHASE_LABEL_TYPE_MAP[matcher]}" ;;`);
964
+ }
965
+ lines.push(
966
+ " *) : ;;",
967
+ " esac",
968
+ "}",
969
+ "",
970
+ "# Return 0 when the argument is a conventional-commit `type:*`",
971
+ "# label \u2014 the ONLY type labels the invariant correction may remove.",
972
+ "# A `type:*` label owned by another bundle is never removed; the",
973
+ "# issue is flagged for human triage instead.",
974
+ "is_conventional_type_label() {",
975
+ ` case "\${1:-}" in`,
976
+ ` ${CONVENTIONAL_COMMIT_TYPE_LABELS.join("|")}) return 0 ;;`,
977
+ " *) return 1 ;;",
978
+ " esac",
979
+ "}",
980
+ "",
981
+ "# Resolve an issue's FULL label list (one label per line on stdin)",
982
+ "# to the type label the phase-label invariant requires. Emits",
983
+ "# KEY=VALUE assignments the caller parses:",
984
+ "# OUTCOME=none \u2014 no recognised phase label; invariant N/A",
985
+ "# OUTCOME=match \u2014 plus TYPE_LABEL=<type:bundle>",
986
+ "# OUTCOME=ambiguous \u2014 plus CANDIDATE_TYPE_LABELS=<space-separated>",
987
+ "# PHASE_LABELS=<space-separated recognised phase labels>",
988
+ "phase_type_of() {",
989
+ " local label resolved",
990
+ ' local matched_types=""',
991
+ ' local matched_phase=""',
992
+ " while IFS= read -r label; do",
993
+ ' [ -z "$label" ] && continue',
994
+ ' resolved=$(phase_label_type_of "$label")',
995
+ ' [ -z "$resolved" ] && continue',
996
+ ' matched_phase="${matched_phase}${label} "',
997
+ ' case " ${matched_types} " in',
998
+ ' *" ${resolved} "*) ;;',
999
+ ' *) matched_types="${matched_types}${resolved} " ;;',
1000
+ " esac",
1001
+ " done",
1002
+ " local count=0",
1003
+ " for resolved in $matched_types; do",
1004
+ " count=$((count + 1))",
1005
+ " done",
1006
+ ' if [ "$count" -eq 0 ]; then',
1007
+ ' echo "OUTCOME=none"',
1008
+ ' elif [ "$count" -eq 1 ]; then',
1009
+ ' echo "OUTCOME=match"',
1010
+ ' echo "TYPE_LABEL=${matched_types% }"',
1011
+ " else",
1012
+ ' echo "OUTCOME=ambiguous"',
1013
+ ' echo "CANDIDATE_TYPE_LABELS=${matched_types% }"',
1014
+ " fi",
1015
+ ' echo "PHASE_LABELS=${matched_phase% }"',
1016
+ "}"
1017
+ );
1018
+ return lines.join("\n");
1019
+ }
1020
+ function buildPhaseLabelTypeMap() {
1021
+ const map = {};
1022
+ for (const [bundleName, ownership] of Object.entries(BUNDLE_OWNERSHIP)) {
1023
+ if (ownership.phaseLabelPrefixes.length === 0) {
1024
+ continue;
1025
+ }
1026
+ if (ownership.typeLabels.length !== 1) {
1027
+ throw new Error(
1028
+ `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`
1029
+ );
1030
+ }
1031
+ const typeLabel = `type:${ownership.typeLabels[0]}`;
1032
+ for (const matcher of ownership.phaseLabelPrefixes) {
1033
+ const existing = map[matcher];
1034
+ if (existing !== void 0 && existing !== typeLabel) {
1035
+ throw new Error(
1036
+ `Phase-label matcher "${matcher}" resolves to both "${existing}" and "${typeLabel}"; a matcher must imply exactly one type label`
1037
+ );
1038
+ }
1039
+ map[matcher] = typeLabel;
1040
+ }
1041
+ }
1042
+ return map;
1043
+ }
1044
+ function isTypeLabelOwnedByExcluded(typeLabel, excludedBundles) {
1045
+ if (excludedBundles.length === 0) {
1046
+ return false;
1047
+ }
1048
+ for (const bundleName of excludedBundles) {
1049
+ const ownership = BUNDLE_OWNERSHIP[bundleName];
1050
+ if (!ownership) {
1051
+ continue;
1052
+ }
1053
+ if (ownership.typeLabels.includes(typeLabel)) {
1054
+ const owners = findOwnersOfTypeLabel(typeLabel);
1055
+ const allExcluded = owners.every(
1056
+ (owner) => excludedBundles.includes(owner)
1057
+ );
1058
+ if (allExcluded) {
1059
+ return true;
1060
+ }
1061
+ }
1062
+ }
1063
+ return false;
1064
+ }
1065
+ function isPhaseLabelOwnedByExcluded(phaseLabel, excludedBundles) {
1066
+ if (excludedBundles.length === 0) {
1067
+ return false;
1068
+ }
1069
+ for (const bundleName of excludedBundles) {
1070
+ const ownership = BUNDLE_OWNERSHIP[bundleName];
1071
+ if (!ownership) {
1072
+ continue;
1073
+ }
1074
+ for (const entry of ownership.phaseLabelPrefixes) {
1075
+ if (entry.endsWith(":")) {
1076
+ if (phaseLabel.startsWith(entry)) {
1077
+ return true;
1078
+ }
1079
+ } else if (phaseLabel === entry) {
1080
+ return true;
1081
+ }
1082
+ }
1083
+ }
1084
+ return false;
1085
+ }
1086
+ function isScheduledTaskOwnedByExcluded(taskId, excludedBundles) {
1087
+ if (excludedBundles.length === 0) {
1088
+ return false;
1089
+ }
1090
+ for (const bundleName of excludedBundles) {
1091
+ const ownership = BUNDLE_OWNERSHIP[bundleName];
1092
+ if (!ownership) {
1093
+ continue;
1094
+ }
1095
+ if (ownership.scheduledTaskIds.includes(taskId)) {
1096
+ return true;
1097
+ }
1098
+ }
1099
+ return false;
1100
+ }
1101
+ function hasAnyDocsEmittingBundle(excludedBundles) {
1102
+ for (const [bundleName, ownership] of Object.entries(BUNDLE_OWNERSHIP)) {
1103
+ if (!ownership.emitsDocs) {
1104
+ continue;
1105
+ }
1106
+ if (!excludedBundles.includes(bundleName)) {
1107
+ return true;
1108
+ }
1109
+ }
1110
+ return false;
1111
+ }
1112
+ function hasAnyDownstreamIssueKindBundle(excludedBundles) {
1113
+ for (const [bundleName, ownership] of Object.entries(BUNDLE_OWNERSHIP)) {
1114
+ if (!ownership.downstreamIssueKinds) {
1115
+ continue;
1116
+ }
1117
+ if (!excludedBundles.includes(bundleName)) {
1118
+ return true;
1119
+ }
1120
+ }
1121
+ return false;
1122
+ }
1123
+ function findOwnersOfTypeLabel(typeLabel) {
1124
+ const owners = [];
1125
+ for (const [bundleName, ownership] of Object.entries(BUNDLE_OWNERSHIP)) {
1126
+ if (ownership.typeLabels.includes(typeLabel)) {
1127
+ owners.push(bundleName);
1128
+ }
1129
+ }
1130
+ return owners;
1131
+ }
1132
+
1133
+ // src/agent/bundles/issue-type.ts
1134
+ var GITHUB_ISSUE_TYPES = ["Epic", "Feature", "Bug", "Task"];
1135
+ var DEFAULT_GITHUB_ISSUE_TYPE = "Task";
1136
+ var NON_DEFAULT_TITLE_PREFIX_TYPES = {
1137
+ "epic:": "Epic",
1138
+ "feat:": "Feature",
1139
+ "fix:": "Bug"
1140
+ };
1141
+ var GITHUB_ISSUE_TYPE_BY_TITLE_PREFIX = buildTitlePrefixTypeMap();
1142
+ function githubIssueTypeForTitle(title) {
1143
+ const colon = title.indexOf(":");
1144
+ if (colon === -1) {
1145
+ return DEFAULT_GITHUB_ISSUE_TYPE;
1146
+ }
1147
+ const prefix2 = title.slice(0, colon + 1).trim().toLowerCase();
1148
+ return GITHUB_ISSUE_TYPE_BY_TITLE_PREFIX[prefix2] ?? DEFAULT_GITHUB_ISSUE_TYPE;
1149
+ }
1150
+ var SET_ISSUE_TYPE_HELPER_PATH = ".claude/procedures/set-issue-type.sh";
1151
+ function renderSetIssueTypeFallbackLines() {
1152
+ return [
1153
+ "```bash",
1154
+ "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>",
1155
+ "gh issue view <issue-number> --json id --jq '.id'",
1156
+ "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>",
1157
+ "```"
1158
+ ];
1159
+ }
1160
+ function renderGithubIssueTypeSectionLines() {
1161
+ const defaultPrefixes = Object.keys(GITHUB_ISSUE_TYPE_BY_TITLE_PREFIX).filter(
1162
+ (prefix2) => GITHUB_ISSUE_TYPE_BY_TITLE_PREFIX[prefix2] === DEFAULT_GITHUB_ISSUE_TYPE
1163
+ ).sort().map((prefix2) => `\`${prefix2}\``).join(", ");
1164
+ return [
1165
+ "## GitHub Issue Type",
1166
+ "",
1167
+ "A GitHub **issue type** is a distinct field from the `type:*` **label**:",
1168
+ "the label routes and dedups, the issue type drives triage and Epic",
1169
+ "tracking. Setting one does not set the other. Derive it from the",
1170
+ "**title prefix**:",
1171
+ "",
1172
+ "| Prefix | GitHub Issue Type |",
1173
+ "|--------|------------------|",
1174
+ "| `epic:` | Epic |",
1175
+ "| `feat:` | Feature |",
1176
+ "| `fix:` | Bug |",
1177
+ `| ${defaultPrefixes} | ${DEFAULT_GITHUB_ISSUE_TYPE} |`,
1178
+ "| any bundle-phase prefix (`company:`, `req:`, \u2026), or no prefix | Task |",
1179
+ "",
1180
+ "### Assigning the GitHub issue type",
1181
+ "",
1182
+ "`gh issue create` **cannot** set the issue type \u2014 there is no `--type`",
1183
+ "flag \u2014 so assign it in a follow-up call immediately after creation, for",
1184
+ "every issue an agent files (auto-enqueued downstream issues included).",
1185
+ "An issue that skips that call stays untyped forever. The",
1186
+ "`github-workflow` bundle ships a one-step helper:",
1187
+ "",
1188
+ "```bash",
1189
+ `${SET_ISSUE_TYPE_HELPER_PATH} <issue-number> <${GITHUB_ISSUE_TYPES.join("|")}>`,
1190
+ "```",
1191
+ "",
1192
+ "When that helper is absent (the bundle is excluded), run the",
1193
+ "`updateIssueIssueType` GraphQL flow it wraps:",
1194
+ "",
1195
+ ...renderSetIssueTypeFallbackLines()
1196
+ ];
1197
+ }
1198
+ function renderTitlePrefixTypeBullets(indent = " ") {
1199
+ const nonDefault = Object.entries(NON_DEFAULT_TITLE_PREFIX_TYPES).map(
1200
+ ([prefix2, issueType]) => `${indent}- \`${prefix2}\` \u2192 ${issueType}`
1201
+ );
1202
+ const defaults = Object.keys(GITHUB_ISSUE_TYPE_BY_TITLE_PREFIX).filter(
1203
+ (prefix2) => GITHUB_ISSUE_TYPE_BY_TITLE_PREFIX[prefix2] === DEFAULT_GITHUB_ISSUE_TYPE
1204
+ ).sort().map((prefix2) => `\`${prefix2}\``).join(", ");
1205
+ return [
1206
+ ...nonDefault,
1207
+ `${indent}- ${defaults} \u2192 ${DEFAULT_GITHUB_ISSUE_TYPE}`,
1208
+ `${indent}- any bundle-phase prefix (\`company:\`, \`req:\`, \`bcm:\`, \u2026) \u2192 ${DEFAULT_GITHUB_ISSUE_TYPE}`
1209
+ ];
1210
+ }
1211
+ function renderGithubIssueTypeSection() {
1212
+ return renderGithubIssueTypeSectionLines().join("\n");
1213
+ }
1214
+ function renderIssueTypeAssignmentStep(options = {}) {
1215
+ const indent = options.indent ?? " ";
1216
+ const issueType = options.issueType ?? DEFAULT_GITHUB_ISSUE_TYPE;
1217
+ const lead = options.bullet ? `${indent}- ` : indent;
1218
+ const cont = options.bullet ? `${indent} ` : indent;
1219
+ return [
1220
+ `${lead}Then assign the new issue's **GitHub issue type** (\`${issueType}\`) \u2014`,
1221
+ `${cont}the \`type:*\` label does not set it:`,
1222
+ `${cont}\`${SET_ISSUE_TYPE_HELPER_PATH} <issue-number> ${issueType}\`. See`,
1223
+ `${cont}**Assigning the GitHub issue type** for the no-helper fallback.`
1224
+ ];
1225
+ }
1226
+ function renderIssueTypeAssignmentBlanket(indent = "", issueType = DEFAULT_GITHUB_ISSUE_TYPE) {
1227
+ return [
1228
+ `${indent}**Every** issue this phase files needs a **GitHub issue type**`,
1229
+ `${indent}assigned right after creation \u2014 the \`type:*\` label does not set`,
1230
+ `${indent}it, and \`gh issue create\` cannot:`,
1231
+ `${indent}\`${SET_ISSUE_TYPE_HELPER_PATH} <issue-number> ${issueType}\`. See`,
1232
+ `${indent}**Assigning the GitHub issue type** for the prefix table (nearly`,
1233
+ `${indent}every downstream issue is a \`${issueType}\`) and the no-helper fallback.`
1234
+ ];
1235
+ }
1236
+ function buildTitlePrefixTypeMap() {
1237
+ const map = {};
1238
+ for (const label of CONVENTIONAL_COMMIT_TYPE_LABELS) {
1239
+ map[`${label.slice("type:".length)}:`] = DEFAULT_GITHUB_ISSUE_TYPE;
1240
+ }
1241
+ for (const [prefix2, issueType] of Object.entries(
1242
+ NON_DEFAULT_TITLE_PREFIX_TYPES
1243
+ )) {
1244
+ map[prefix2] = issueType;
1245
+ }
1246
+ return map;
1247
+ }
1248
+
635
1249
  // src/agent/bundles/project-context.ts
636
1250
  var PROJECT_CONTEXT_PATH = "docs/src/content/docs/project-context.md";
637
1251
  var SUBPROJECT_ROLE_GUIDANCE = [
@@ -1224,6 +1838,9 @@ var draftAgendaSkill = {
1224
1838
  ' - `desired_outcomes` \u2014 1\u20133 bullets describing "done"',
1225
1839
  " - `pre_reads` \u2014 relative paths to existing docs (optional)",
1226
1840
  " - `related_docs` \u2014 relative paths for context (optional)",
1841
+ "",
1842
+ ...renderIssueTypeAssignmentStep(),
1843
+ "",
1227
1844
  "2. Execute Phase 1 (Draft) of the agenda-analyst agent.",
1228
1845
  "3. If the draft phase inline-finalizes (default path), no",
1229
1846
  " downstream issues are created.",
@@ -1288,6 +1905,9 @@ var finalizeAgendaSkill = {
1288
1905
  " - Path to the draft agenda",
1289
1906
  " (`<meetingsRoot>/<YYYY-MM-DD>-<slug>/agenda.md`)",
1290
1907
  " - Any late-added items to incorporate",
1908
+ "",
1909
+ ...renderIssueTypeAssignmentStep(),
1910
+ "",
1291
1911
  "2. Execute Phase 2 (Finalize) of the agenda-analyst agent.",
1292
1912
  "",
1293
1913
  "## Output",
@@ -1833,6 +2453,14 @@ function renderIssueTemplatesStarterPage(_it) {
1833
2453
  "`status:*` label from the conventions documented in the",
1834
2454
  "`issue-label-conventions` rule.",
1835
2455
  "",
2456
+ "Every recipe also ends with a **GitHub issue type** assignment.",
2457
+ "`gh issue create` cannot set the issue type, so it takes a second",
2458
+ "call \u2014 an issue that skips it stays untyped forever. Derive the type",
2459
+ "from the title prefix (`epic:` \u2192 Epic, `feat:` \u2192 Feature, `fix:` \u2192",
2460
+ "Bug, everything else \u2014 including every bundle-phase prefix \u2014 \u2192",
2461
+ "Task); see **Assigning the GitHub issue type** in the",
2462
+ "`issue-conventions` rule for the command and its fallback.",
2463
+ "",
1836
2464
  "Before creating any profile-style issue (people, companies,",
1837
2465
  "software), run the duplicate check documented in the owning",
1838
2466
  "bundle's rule \u2014 the issue-templates page is a recipe catalogue,",
@@ -1867,6 +2495,13 @@ function renderIssueTemplatesStarterPage(_it) {
1867
2495
  "- [ ] <criterion 1>",
1868
2496
  "- [ ] <criterion 2>",
1869
2497
  '"',
2498
+ "```",
2499
+ "",
2500
+ "Then assign the GitHub issue type (`Task` for a phase-labelled",
2501
+ "title):",
2502
+ "",
2503
+ "```bash",
2504
+ `${SET_ISSUE_TYPE_HELPER_PATH} <issue-number> ${DEFAULT_GITHUB_ISSUE_TYPE}`,
1870
2505
  "```"
1871
2506
  ].join("\n");
1872
2507
  }
@@ -4971,16 +5606,7 @@ function buildBaseBundle(paths = DEFAULT_AGENT_PATHS) {
4971
5606
  "| `release:` | Release preparation, version bumps |",
4972
5607
  "| `hotfix:` | Urgent production fixes |",
4973
5608
  "",
4974
- "## GitHub Issue Type",
4975
- "",
4976
- "When creating issues, always assign the appropriate **GitHub issue type** based on the title prefix:",
4977
- "",
4978
- "| Prefix | GitHub Issue Type |",
4979
- "|--------|------------------|",
4980
- "| `epic:` | Epic |",
4981
- "| `feat:` | Feature |",
4982
- "| `fix:` | Bug |",
4983
- "| `chore:`, `docs:`, `refactor:`, `release:`, `hotfix:` | Task |",
5609
+ ...renderGithubIssueTypeSectionLines(),
4984
5610
  "",
4985
5611
  "## Prerequisite Issues",
4986
5612
  "",
@@ -5670,6 +6296,8 @@ function buildBcmWriterSubAgent(paths, issueDefaults) {
5670
6296
  ` - \`priority:${labelsForPhase(issueDefaults, "people:research").priority}\``,
5671
6297
  ` - \`status:${labelsForPhase(issueDefaults, "people:research").status}\``,
5672
6298
  "",
6299
+ ...renderIssueTypeAssignmentStep(),
6300
+ "",
5673
6301
  " **Unfamiliar company (enabling vendor, partner, competitor).**",
5674
6302
  " Hand off to the `company-profile` bundle by creating a",
5675
6303
  " `company:research` issue. The issue must carry:",
@@ -5679,6 +6307,8 @@ function buildBcmWriterSubAgent(paths, issueDefaults) {
5679
6307
  ` - \`priority:${labelsForPhase(issueDefaults, "company:research").priority}\``,
5680
6308
  ` - \`status:${labelsForPhase(issueDefaults, "company:research").status}\``,
5681
6309
  "",
6310
+ ...renderIssueTypeAssignmentStep(),
6311
+ "",
5682
6312
  " **Missing research topic (value stream, market sizing, etc.).**",
5683
6313
  " Hand off to the `research-pipeline` bundle by creating a",
5684
6314
  " `research:scope` issue. The issue must carry:",
@@ -5688,6 +6318,8 @@ function buildBcmWriterSubAgent(paths, issueDefaults) {
5688
6318
  ` - \`priority:${labelsForPhase(issueDefaults, "research:scope").priority}\``,
5689
6319
  ` - \`status:${labelsForPhase(issueDefaults, "research:scope").status}\``,
5690
6320
  "",
6321
+ ...renderIssueTypeAssignmentStep(),
6322
+ "",
5691
6323
  " This phase assumes the `people-profile`, `company-profile`, and",
5692
6324
  " `research-pipeline` bundles are enabled in the consuming project.",
5693
6325
  " If a bundle is not enabled, flag the connect issue with",
@@ -5810,6 +6442,8 @@ function buildWriteBcmSkill(paths, issueDefaults) {
5810
6442
  ` \`priority:${labelsForPhase(issueDefaults, "bcm:outline").priority}\`, and \`status:${labelsForPhase(issueDefaults, "bcm:outline").status}\`. Body must include the`,
5811
6443
  " verbatim capability name and any overrides.",
5812
6444
  "",
6445
+ "",
6446
+ ...renderIssueTypeAssignmentStep(),
5813
6447
  "2. Execute Phase 1 (Outline) of the bcm-writer agent.",
5814
6448
  "",
5815
6449
  "3. Phase 1 creates a `bcm:scaffold` issue, which Phase 2 follows with",
@@ -7090,6 +7724,8 @@ function buildScanBusinessModelsSkill(paths, issueDefaults) {
7090
7724
  "1. Create a `business-models:scan` issue with `type:business-model`,",
7091
7725
  ` \`priority:${labelsForPhase(issueDefaults, "business-models:scan").priority}\`, and \`status:${labelsForPhase(issueDefaults, "business-models:scan").status}\`. The body must include the`,
7092
7726
  " industry slug, the source plan path (if any), and any overrides.",
7727
+ "",
7728
+ ...renderIssueTypeAssignmentStep(),
7093
7729
  "2. Execute Phase 1 (Scan) of the business-models-analyst agent.",
7094
7730
  "3. Phase 1 creates one `business-models:canvas` issue per in-scope",
7095
7731
  " segment. Each canvas issue declares its `Depends on:`",
@@ -7164,6 +7800,8 @@ function buildCanvasBusinessModelSkill(paths, issueDefaults) {
7164
7800
  ` \`type:business-model\`, \`priority:${labelsForPhase(issueDefaults, "business-models:canvas").priority}\`, and \`status:${labelsForPhase(issueDefaults, "business-models:canvas").status}\`.`,
7165
7801
  " The body must include the industry slug, segment slug, and any",
7166
7802
  " scan-report reference.",
7803
+ "",
7804
+ ...renderIssueTypeAssignmentStep(),
7167
7805
  "2. Execute Phase 2 (Canvas) of the business-models-analyst agent.",
7168
7806
  "3. Phase 2 writes the nine-block canvas and creates a",
7169
7807
  " `business-models:complete` issue for the downstream Phase 3.",
@@ -7223,6 +7861,8 @@ function buildCompleteBusinessModelSkill(paths, issueDefaults) {
7223
7861
  "1. Create a `business-models:complete` issue with",
7224
7862
  ` \`type:business-model\`, \`priority:${labelsForPhase(issueDefaults, "business-models:complete").priority}\`, and \`status:${labelsForPhase(issueDefaults, "business-models:complete").status}\`.`,
7225
7863
  " Body must include the canvas document path.",
7864
+ "",
7865
+ ...renderIssueTypeAssignmentStep(),
7226
7866
  "2. Execute Phase 3 (Complete) of the business-models-analyst agent.",
7227
7867
  "",
7228
7868
  "## Output",
@@ -7747,6 +8387,8 @@ function buildCompanyProfileAnalystSubAgent(paths, issueDefaults, tier) {
7747
8387
  ` - \`priority:${labelsForPhase(issueDefaults, "people:research").priority}\``,
7748
8388
  ` - \`status:${labelsForPhase(issueDefaults, "people:research").status}\``,
7749
8389
  "",
8390
+ ...renderIssueTypeAssignmentStep(),
8391
+ "",
7750
8392
  " The issue body must include:",
7751
8393
  " - A **discovery source** line naming this company profile",
7752
8394
  " (`Discovered while profiling: <company name>`)",
@@ -7764,6 +8406,8 @@ function buildCompanyProfileAnalystSubAgent(paths, issueDefaults, tier) {
7764
8406
  ` - \`priority:${labelsForPhase(issueDefaults, "software:research").priority}\``,
7765
8407
  ` - \`status:${labelsForPhase(issueDefaults, "software:research").status}\``,
7766
8408
  "",
8409
+ ...renderIssueTypeAssignmentStep(),
8410
+ "",
7767
8411
  " The issue body must include:",
7768
8412
  " - A **discovery source** line naming this company profile",
7769
8413
  " (`Discovered while profiling: <company name>`)",
@@ -8315,6 +8959,8 @@ function buildProfileCompanySkill(paths, issueDefaults) {
8315
8959
  "1. Create a `company:research` issue with `type:company-profile`,",
8316
8960
  ` \`priority:${labelsForPhase(issueDefaults, "company:research").priority}\`, and \`status:${labelsForPhase(issueDefaults, "company:research").status}\`. Body must include the`,
8317
8961
  " company name, selected type, framing, and any overrides.",
8962
+ "",
8963
+ ...renderIssueTypeAssignmentStep(),
8318
8964
  "2. Execute Phase 1 (Research) of the company-profile-analyst agent.",
8319
8965
  "3. Phase 1 creates the `company:draft` issue. Phase 2 may create a",
8320
8966
  " `company:followup` issue **and** a `company:match` issue. Each",
@@ -8389,6 +9035,8 @@ function buildMatchCompanySkill(paths, issueDefaults) {
8389
9035
  "1. Create a `company:match` issue with `type:company-profile`,",
8390
9036
  ` \`priority:${labelsForPhase(issueDefaults, "company:match").priority}\`, and \`status:${labelsForPhase(issueDefaults, "company:match").status}\`. Body must include the`,
8391
9037
  " profile path and any overrides.",
9038
+ "",
9039
+ ...renderIssueTypeAssignmentStep(),
8392
9040
  "2. Execute Phase 4 (Match) of the company-profile-analyst agent.",
8393
9041
  "",
8394
9042
  "## Output",
@@ -8462,6 +9110,8 @@ function buildRefreshCompanySkill(paths, issueDefaults) {
8462
9110
  "1. Create a `company:refresh` issue with `type:company-profile`,",
8463
9111
  ` \`priority:${labelsForPhase(issueDefaults, "company:refresh").priority}\`, and \`status:${labelsForPhase(issueDefaults, "company:refresh").status}\`. Body must include the`,
8464
9112
  " profile path and any overrides.",
9113
+ "",
9114
+ ...renderIssueTypeAssignmentStep(),
8465
9115
  "2. Execute Phase 5 (Refresh) of the company-profile-analyst agent.",
8466
9116
  "",
8467
9117
  "## Output",
@@ -8535,6 +9185,8 @@ function buildAnalyzeSegmentSkill(paths, issueDefaults) {
8535
9185
  "1. Create a `company:analyze` issue with `type:company-profile`,",
8536
9186
  ` \`priority:${labelsForPhase(issueDefaults, "company:analyze").priority}\`, and \`status:${labelsForPhase(issueDefaults, "company:analyze").status}\`. Body must include the`,
8537
9187
  " segment slug and any overrides.",
9188
+ "",
9189
+ ...renderIssueTypeAssignmentStep(),
8538
9190
  "2. Execute Phase 6 (Analyze) of the company-profile-analyst agent.",
8539
9191
  "",
8540
9192
  "## Output",
@@ -9123,6 +9775,8 @@ function buildCustomerProfileAnalystSubAgent(paths, issueDefaults, tier) {
9123
9775
  ` - \`priority:${labelsForPhase(issueDefaults, "company:research").priority}\``,
9124
9776
  ` - \`status:${labelsForPhase(issueDefaults, "company:research").status}\``,
9125
9777
  "",
9778
+ ...renderIssueTypeAssignmentStep(),
9779
+ "",
9126
9780
  "9. **Create `people:research` issues** for notable contacts",
9127
9781
  " (economic buyers, technical buyers, user champions) surfaced in",
9128
9782
  " sources. Keep the list small \u2014 at most three individuals per",
@@ -9134,6 +9788,8 @@ function buildCustomerProfileAnalystSubAgent(paths, issueDefaults, tier) {
9134
9788
  ` - \`priority:${labelsForPhase(issueDefaults, "people:research").priority}\``,
9135
9789
  ` - \`status:${labelsForPhase(issueDefaults, "people:research").status}\``,
9136
9790
  "",
9791
+ ...renderIssueTypeAssignmentStep(),
9792
+ "",
9137
9793
  "10. **Verify all handoffs were created** before committing.",
9138
9794
  "",
9139
9795
  "11. **Commit and push** the archetype page. Close the profile issue.",
@@ -9235,6 +9891,8 @@ function buildCustomerProfileAnalystSubAgent(paths, issueDefaults, tier) {
9235
9891
  ` - \`priority:${labelsForPhase(issueDefaults, "req:scan").priority}\``,
9236
9892
  ` - \`status:${labelsForPhase(issueDefaults, "req:scan").status}\``,
9237
9893
  "",
9894
+ ...renderIssueTypeAssignmentStep(),
9895
+ "",
9238
9896
  " The body must also:",
9239
9897
  " - Link back to the archetype page and the competitor page",
9240
9898
  " - Cite the specific unmet-need row and the originating job",
@@ -9251,6 +9909,8 @@ function buildCustomerProfileAnalystSubAgent(paths, issueDefaults, tier) {
9251
9909
  ` - \`priority:${labelsForPhase(issueDefaults, "company:research").priority}\``,
9252
9910
  ` - \`status:${labelsForPhase(issueDefaults, "company:research").status}\``,
9253
9911
  "",
9912
+ ...renderIssueTypeAssignmentStep(),
9913
+ "",
9254
9914
  "9. **Create `people:research` issues** for notable competitor",
9255
9915
  " leadership or customer-advocacy figures surfaced in the analysis.",
9256
9916
  " Keep this list small \u2014 at most three individuals per competitors",
@@ -9262,6 +9922,8 @@ function buildCustomerProfileAnalystSubAgent(paths, issueDefaults, tier) {
9262
9922
  ` - \`priority:${labelsForPhase(issueDefaults, "people:research").priority}\``,
9263
9923
  ` - \`status:${labelsForPhase(issueDefaults, "people:research").status}\``,
9264
9924
  "",
9925
+ ...renderIssueTypeAssignmentStep(),
9926
+ "",
9265
9927
  "10. **Commit and push** the competitor-analysis page and the",
9266
9928
  " updated archetype page. Close the competitors issue.",
9267
9929
  "",
@@ -9448,6 +10110,8 @@ function buildDiscoverCustomersSkill(paths, issueDefaults) {
9448
10110
  "1. Create a `customer:discover` issue with `type:customer-profile`,",
9449
10111
  ` \`priority:${labelsForPhase(issueDefaults, "customer:discover").priority}\`, and \`status:${labelsForPhase(issueDefaults, "customer:discover").status}\`. The body must include the`,
9450
10112
  " scope slug and any known archetypes to include or exclude.",
10113
+ "",
10114
+ ...renderIssueTypeAssignmentStep(),
9451
10115
  "2. Execute Phase 1 (Discover) of the customer-profile-analyst agent.",
9452
10116
  "3. Phase 1 writes the discovery report and creates one",
9453
10117
  " `customer:profile` issue per prioritized archetype.",
@@ -9523,6 +10187,8 @@ function buildProfileCustomerSkill(paths, issueDefaults) {
9523
10187
  "1. Create a `customer:profile` issue with `type:customer-profile`,",
9524
10188
  ` \`priority:${labelsForPhase(issueDefaults, "customer:profile").priority}\`, and \`status:${labelsForPhase(issueDefaults, "customer:profile").status}\`. The body must include the`,
9525
10189
  " archetype slug and (optionally) the segment slug.",
10190
+ "",
10191
+ ...renderIssueTypeAssignmentStep(),
9526
10192
  "2. Execute Phase 2 (Profile) of the customer-profile-analyst agent.",
9527
10193
  " The agent runs 4\u20138 targeted web searches and populates the",
9528
10194
  " archetype page.",
@@ -9599,6 +10265,8 @@ function buildAnalyzeCustomerCompetitorsSkill(paths, issueDefaults) {
9599
10265
  ` \`status:${labelsForPhase(issueDefaults, "customer:competitors").status}\` (or \`status:blocked\` when still dependent on a`,
9600
10266
  " Phase 2 profile issue). Body must include the archetype page",
9601
10267
  " path.",
10268
+ "",
10269
+ ...renderIssueTypeAssignmentStep(),
9602
10270
  "2. Execute Phase 3 (Competitors) of the customer-profile-analyst",
9603
10271
  " agent. The agent reads `<MATRIX_FILE>` and runs up to 4\u20138 web",
9604
10272
  " searches only to close competitor-coverage gaps the matrix does",
@@ -11308,10 +11976,7 @@ function buildGithubWorkflowBundle(buildPolicy = DEFAULT_BUILD_POLICY) {
11308
11976
  " - If unclear, ask the user which type applies",
11309
11977
  "2. **Compose the issue title** in the format: `<type>: <short description>`",
11310
11978
  "3. **Determine the GitHub issue type** based on the prefix:",
11311
- " - `epic:` \u2192 Epic",
11312
- " - `feat:` \u2192 Feature",
11313
- " - `fix:` \u2192 Bug",
11314
- " - `chore:`, `docs:`, `refactor:`, `release:`, `hotfix:` \u2192 Task",
11979
+ ...renderTitlePrefixTypeBullets(),
11315
11980
  "4. **Identify prerequisite issues** \u2014 if the user mentions dependencies or blockers, include a **Dependencies** section in the body with `Depends on: #<issue-number>`",
11316
11981
  "5. **Determine labels** \u2014 every issue must be created with the following labels:",
11317
11982
  " - **`type:*`** \u2014 derived from the issue title prefix:",
@@ -11797,6 +12462,7 @@ function buildIndustryDiscoveryAnalystSubAgent(paths, issueDefaults) {
11797
12462
  " downstream research needs to answer)",
11798
12463
  " - Cite the evaluation row that justified advancing the vertical",
11799
12464
  " - Reference the plan file path for traceability",
12465
+ ...renderIssueTypeAssignmentStep({ indent: " ", bullet: true }),
11800
12466
  "",
11801
12467
  "5. **Cross-link** \u2014 update the plan file's `## Cleared Verticals`",
11802
12468
  " table so each row references its newly-created `research:scope`",
@@ -11887,6 +12553,8 @@ function buildDiscoverIndustriesSkill(paths, issueDefaults) {
11887
12553
  "1. Create an `industry:discover` issue with `type:industry-discovery`,",
11888
12554
  ` \`priority:${labelsForPhase(issueDefaults, "industry:discover").priority}\`, and \`status:${labelsForPhase(issueDefaults, "industry:discover").status}\`. Body must include the`,
11889
12555
  " verbatim scope, authorized sources, and any overrides.",
12556
+ "",
12557
+ ...renderIssueTypeAssignmentStep(),
11890
12558
  "2. Execute Phase 1 (Discover) of the industry-discovery-analyst",
11891
12559
  " agent.",
11892
12560
  "3. Phase 1 creates an `industry:evaluate` issue, which Phase 2 follows",
@@ -12165,6 +12833,8 @@ function buildMaintenanceAuditSubAgent(paths, tier) {
12165
12833
  "4. Commit, push, open a PR (if applicable), and close the issue per",
12166
12834
  " your project's PR workflow.",
12167
12835
  "",
12836
+ ...renderIssueTypeAssignmentBlanket(),
12837
+ "",
12168
12838
  "---",
12169
12839
  "",
12170
12840
  ...renderClaimVerificationSection(),
@@ -12597,6 +13267,8 @@ function buildMaintenanceAuditSkill(paths, issueDefaults) {
12597
13267
  " - `<AUDIT_ROOT>` \u2014 where to write the audit report",
12598
13268
  " - `<AUDIT_SLUG>` \u2014 short identifier for this audit",
12599
13269
  " - The audit checks to run (defaults to the full check catalog)",
13270
+ "",
13271
+ ...renderIssueTypeAssignmentStep(),
12600
13272
  "2. Execute Phase 1 (Scan) of the maintenance-audit agent.",
12601
13273
  "3. If auto-fixable findings exist, a `maint:fix` issue is created",
12602
13274
  " automatically.",
@@ -12661,6 +13333,8 @@ function buildMaintenanceVerifySkill(issueDefaults) {
12661
13333
  " - The Phase 2 fix report path (if one exists)",
12662
13334
  " - The `Paths Touched` list (copied from the fix report)",
12663
13335
  " - The audit check catalog (copied from the scan issue)",
13336
+ "",
13337
+ ...renderIssueTypeAssignmentStep(),
12664
13338
  "2. Execute Phase 3 (Verify) of the maintenance-audit agent.",
12665
13339
  "",
12666
13340
  "## Output",
@@ -13170,6 +13844,8 @@ function buildMeetingAnalystSubAgent(tier) {
13170
13844
  " - Always create a `meeting:link` issue \u2014 blocked on the draft issue if one",
13171
13845
  " was created, otherwise blocked on the notes issue",
13172
13846
  "",
13847
+ ...renderIssueTypeAssignmentStep(),
13848
+ "",
13173
13849
  "7. **Commit the extraction content first.** Stage and commit",
13174
13850
  " the `insights/{type}/<basename>.md` file (and any newly",
13175
13851
  " created `insights/index.md` / `insights/{type}/index.md`",
@@ -13369,6 +14045,8 @@ function buildMeetingAnalystSubAgent(tier) {
13369
14045
  "",
13370
14046
  "### Steps",
13371
14047
  "",
14048
+ ...renderIssueTypeAssignmentBlanket(),
14049
+ "",
13372
14050
  "1. Read the drafts from Phase 3 (if they exist) and the extraction",
13373
14051
  " from Phase 1 at `<meetingsRoot>/insights/{type}/<basename>.md`.",
13374
14052
  " Re-read the extraction frontmatter for `meeting_kind` and `areas`,",
@@ -13531,6 +14209,8 @@ var processMeetingSkill = {
13531
14209
  " - `meeting:notes` issue (always)",
13532
14210
  " - `meeting:draft` issue (if requirements or decisions were found)",
13533
14211
  " - `meeting:link` issue (always)",
14212
+ "",
14213
+ ...renderIssueTypeAssignmentStep(),
13534
14214
  "5. Each downstream issue includes `Depends on:` linking to its predecessor",
13535
14215
  "6. Commit and push the extraction file",
13536
14216
  "",
@@ -13828,238 +14508,6 @@ function assertValidStateFilePath(stateFilePath) {
13828
14508
  }
13829
14509
  }
13830
14510
 
13831
- // src/agent/bundles/bundle-ownership.ts
13832
- var BUNDLE_OWNERSHIP = {
13833
- agenda: {
13834
- typeLabels: ["agenda"],
13835
- phaseLabelPrefixes: ["agenda:"],
13836
- scheduledTaskIds: ["worker-agenda"],
13837
- emitsDocs: true,
13838
- downstreamIssueKinds: true
13839
- },
13840
- "bcm-writer": {
13841
- typeLabels: ["bcm-document"],
13842
- phaseLabelPrefixes: ["bcm:"],
13843
- scheduledTaskIds: ["worker-bcm-writer"],
13844
- emitsDocs: true,
13845
- downstreamIssueKinds: true
13846
- },
13847
- "business-models": {
13848
- typeLabels: ["business-model"],
13849
- phaseLabelPrefixes: ["business-models:"],
13850
- scheduledTaskIds: ["worker-business-models"],
13851
- emitsDocs: true,
13852
- downstreamIssueKinds: true
13853
- },
13854
- "company-profile": {
13855
- typeLabels: ["company-profile"],
13856
- phaseLabelPrefixes: ["company:"],
13857
- scheduledTaskIds: ["worker-company-profile"],
13858
- emitsDocs: true,
13859
- downstreamIssueKinds: true
13860
- },
13861
- "customer-profile": {
13862
- typeLabels: ["customer-profile"],
13863
- phaseLabelPrefixes: ["customer:"],
13864
- scheduledTaskIds: ["worker-customer-profile"],
13865
- emitsDocs: true,
13866
- downstreamIssueKinds: true
13867
- },
13868
- "docs-sync": {
13869
- typeLabels: ["docs-sync"],
13870
- phaseLabelPrefixes: ["docs-sync:"],
13871
- scheduledTaskIds: ["worker-docs-sync"],
13872
- emitsDocs: true,
13873
- downstreamIssueKinds: true
13874
- },
13875
- "industry-discovery": {
13876
- typeLabels: ["industry-discovery"],
13877
- phaseLabelPrefixes: ["industry:"],
13878
- scheduledTaskIds: ["worker-industry-discovery"],
13879
- emitsDocs: true,
13880
- downstreamIssueKinds: true
13881
- },
13882
- "maintenance-audit": {
13883
- typeLabels: ["maintenance"],
13884
- phaseLabelPrefixes: ["maint:"],
13885
- scheduledTaskIds: ["worker-maintenance"],
13886
- emitsDocs: false,
13887
- downstreamIssueKinds: true
13888
- },
13889
- "meeting-analysis": {
13890
- typeLabels: ["meeting-processing"],
13891
- phaseLabelPrefixes: ["meeting:"],
13892
- scheduledTaskIds: ["worker-meeting-analysis"],
13893
- emitsDocs: true,
13894
- downstreamIssueKinds: true
13895
- },
13896
- orchestrator: {
13897
- // The orchestrator pipeline manager itself ships with the
13898
- // `worker-orchestrator` scheduled task. It owns no `type:*` label
13899
- // (it dispatches every type) and no phase-label prefix.
13900
- typeLabels: [],
13901
- phaseLabelPrefixes: [],
13902
- scheduledTaskIds: ["worker-orchestrator"],
13903
- emitsDocs: false,
13904
- downstreamIssueKinds: false
13905
- },
13906
- "people-profile": {
13907
- typeLabels: ["people-profile"],
13908
- phaseLabelPrefixes: ["people:"],
13909
- scheduledTaskIds: ["worker-people-profile"],
13910
- emitsDocs: true,
13911
- downstreamIssueKinds: true
13912
- },
13913
- "pr-review": {
13914
- typeLabels: ["pr-review"],
13915
- phaseLabelPrefixes: [],
13916
- scheduledTaskIds: ["worker-pr-review"],
13917
- emitsDocs: false,
13918
- downstreamIssueKinds: false
13919
- },
13920
- "regulatory-research": {
13921
- typeLabels: ["regulatory-research"],
13922
- phaseLabelPrefixes: ["regulatory:"],
13923
- scheduledTaskIds: ["worker-regulatory-research"],
13924
- emitsDocs: true,
13925
- downstreamIssueKinds: true
13926
- },
13927
- "requirements-analyst": {
13928
- typeLabels: ["requirement"],
13929
- phaseLabelPrefixes: ["req:"],
13930
- scheduledTaskIds: ["worker-requirements-analyst"],
13931
- emitsDocs: true,
13932
- downstreamIssueKinds: true
13933
- },
13934
- "requirements-reviewer": {
13935
- // Co-owns `type:requirement` with the analyst and writer; owns
13936
- // the `req:review` and `req:deprecate` phase labels exactly.
13937
- typeLabels: ["requirement"],
13938
- phaseLabelPrefixes: ["req:review", "req:deprecate"],
13939
- scheduledTaskIds: ["worker-requirements-reviewer"],
13940
- emitsDocs: true,
13941
- downstreamIssueKinds: true
13942
- },
13943
- "requirements-writer": {
13944
- // Co-owns `type:requirement` with the analyst and reviewer; owns
13945
- // the `req:write` phase label exactly.
13946
- typeLabels: ["requirement"],
13947
- phaseLabelPrefixes: ["req:write"],
13948
- scheduledTaskIds: ["worker-requirements-writer"],
13949
- emitsDocs: true,
13950
- downstreamIssueKinds: true
13951
- },
13952
- "research-pipeline": {
13953
- typeLabels: ["research"],
13954
- phaseLabelPrefixes: ["research:"],
13955
- scheduledTaskIds: ["worker-research"],
13956
- emitsDocs: true,
13957
- downstreamIssueKinds: true
13958
- },
13959
- "software-profile": {
13960
- typeLabels: ["software-profile"],
13961
- phaseLabelPrefixes: ["software:"],
13962
- scheduledTaskIds: ["worker-software-profile"],
13963
- emitsDocs: true,
13964
- downstreamIssueKinds: true
13965
- },
13966
- "standards-research": {
13967
- typeLabels: ["standards-research"],
13968
- phaseLabelPrefixes: ["standards:"],
13969
- scheduledTaskIds: ["worker-standards-research"],
13970
- emitsDocs: true,
13971
- downstreamIssueKinds: true
13972
- }
13973
- };
13974
- function isTypeLabelOwnedByExcluded(typeLabel, excludedBundles) {
13975
- if (excludedBundles.length === 0) {
13976
- return false;
13977
- }
13978
- for (const bundleName of excludedBundles) {
13979
- const ownership = BUNDLE_OWNERSHIP[bundleName];
13980
- if (!ownership) {
13981
- continue;
13982
- }
13983
- if (ownership.typeLabels.includes(typeLabel)) {
13984
- const owners = findOwnersOfTypeLabel(typeLabel);
13985
- const allExcluded = owners.every(
13986
- (owner) => excludedBundles.includes(owner)
13987
- );
13988
- if (allExcluded) {
13989
- return true;
13990
- }
13991
- }
13992
- }
13993
- return false;
13994
- }
13995
- function isPhaseLabelOwnedByExcluded(phaseLabel, excludedBundles) {
13996
- if (excludedBundles.length === 0) {
13997
- return false;
13998
- }
13999
- for (const bundleName of excludedBundles) {
14000
- const ownership = BUNDLE_OWNERSHIP[bundleName];
14001
- if (!ownership) {
14002
- continue;
14003
- }
14004
- for (const entry of ownership.phaseLabelPrefixes) {
14005
- if (entry.endsWith(":")) {
14006
- if (phaseLabel.startsWith(entry)) {
14007
- return true;
14008
- }
14009
- } else if (phaseLabel === entry) {
14010
- return true;
14011
- }
14012
- }
14013
- }
14014
- return false;
14015
- }
14016
- function isScheduledTaskOwnedByExcluded(taskId, excludedBundles) {
14017
- if (excludedBundles.length === 0) {
14018
- return false;
14019
- }
14020
- for (const bundleName of excludedBundles) {
14021
- const ownership = BUNDLE_OWNERSHIP[bundleName];
14022
- if (!ownership) {
14023
- continue;
14024
- }
14025
- if (ownership.scheduledTaskIds.includes(taskId)) {
14026
- return true;
14027
- }
14028
- }
14029
- return false;
14030
- }
14031
- function hasAnyDocsEmittingBundle(excludedBundles) {
14032
- for (const [bundleName, ownership] of Object.entries(BUNDLE_OWNERSHIP)) {
14033
- if (!ownership.emitsDocs) {
14034
- continue;
14035
- }
14036
- if (!excludedBundles.includes(bundleName)) {
14037
- return true;
14038
- }
14039
- }
14040
- return false;
14041
- }
14042
- function hasAnyDownstreamIssueKindBundle(excludedBundles) {
14043
- for (const [bundleName, ownership] of Object.entries(BUNDLE_OWNERSHIP)) {
14044
- if (!ownership.downstreamIssueKinds) {
14045
- continue;
14046
- }
14047
- if (!excludedBundles.includes(bundleName)) {
14048
- return true;
14049
- }
14050
- }
14051
- return false;
14052
- }
14053
- function findOwnersOfTypeLabel(typeLabel) {
14054
- const owners = [];
14055
- for (const [bundleName, ownership] of Object.entries(BUNDLE_OWNERSHIP)) {
14056
- if (ownership.typeLabels.includes(typeLabel)) {
14057
- owners.push(bundleName);
14058
- }
14059
- }
14060
- return owners;
14061
- }
14062
-
14063
14511
  // src/agent/bundles/scheduled-tasks.ts
14064
14512
  var SCHEDULED_TASK_MODEL_VALUES = ["opus", "sonnet", "haiku"];
14065
14513
  var SCHEDULED_TASK_KIND_VALUES = ["issue-worker", "pipeline"];
@@ -16015,6 +16463,7 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
16015
16463
  "# .claude/procedures/check-blocked.sh maintenance",
16016
16464
  "# .claude/procedures/check-blocked.sh prs",
16017
16465
  "# .claude/procedures/check-blocked.sh scope <issue-number>",
16466
+ "# .claude/procedures/check-blocked.sh label-invariant [--fix]",
16018
16467
  "",
16019
16468
  "set -uo pipefail",
16020
16469
  "",
@@ -16064,6 +16513,8 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
16064
16513
  "",
16065
16514
  scopeHelperIndented,
16066
16515
  "",
16516
+ renderPhaseTypeInvariantShellHelpers(),
16517
+ "",
16067
16518
  ...renderDelegationActiveSignalsHelper(),
16068
16519
  "",
16069
16520
  "# \u2500\u2500 subcommands \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
@@ -16699,16 +17150,43 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
16699
17150
  " esac",
16700
17151
  ' done <<< "$lease_output"',
16701
17152
  "",
17153
+ " # \u2500\u2500 phase-label \u2192 type:<bundle> invariant (MUTATING) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
17154
+ " # Run the label-invariant sweep in --fix mode and surface its",
17155
+ " # per-issue lines for log visibility. cmd_label_invariant applies",
17156
+ " # the label edits itself (a single atomic remove+add per issue, so",
17157
+ " # an issue is never observable carrying two type:* labels) and",
17158
+ " # flags the cases it must not guess at. Its terminal",
17159
+ " # LABEL_INVARIANT_DONE line is captured and re-emitted alongside",
17160
+ " # MAINTENANCE_DONE so the orchestrator reads every summary from",
17161
+ " # one maintenance invocation.",
17162
+ " local label_output",
17163
+ " label_output=$(cmd_label_invariant --fix)",
17164
+ ' local label_summary="LABEL_INVARIANT_DONE mode=fix checked=0 ok=0 violations=0 corrected=0 flagged=0"',
17165
+ "",
17166
+ " while IFS= read -r line; do",
17167
+ ' [[ -z "$line" ]] && continue',
17168
+ ' case "$line" in',
17169
+ " 'LABEL_INVARIANT_DONE '*)",
17170
+ ' label_summary="$line"',
17171
+ " ;;",
17172
+ " *)",
17173
+ ' echo "$line"',
17174
+ " ;;",
17175
+ " esac",
17176
+ ' done <<< "$label_output"',
17177
+ "",
16702
17178
  " # \u2500\u2500 needs-attention total \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
16703
17179
  " local needs_attention_total",
16704
17180
  ' needs_attention_total=$(gh issue list --label "status:needs-attention" --state open \\',
16705
17181
  " --json number --limit 100 2>/dev/null | jq 'length' 2>/dev/null || echo 0)",
16706
17182
  " needs_attention_total=${needs_attention_total:-0}",
16707
17183
  "",
16708
- " # Two summary lines consumed by the orchestrator: the issue/orphan",
16709
- " # MAINTENANCE_DONE line and the PR-lease LEASE_RECONCILE line.",
17184
+ " # Three summary lines consumed by the orchestrator: the",
17185
+ " # issue/orphan MAINTENANCE_DONE line, the PR-lease LEASE_RECONCILE",
17186
+ " # line, and the LABEL_INVARIANT_DONE line.",
16710
17187
  ' echo "MAINTENANCE_DONE flagged_stale=${flagged_stale_count} flagged_blocked=${flagged_blocked_count} orphan_branches=${orphan_branches_count} orphan_prs=${orphan_prs_count} needs_attention_total=${needs_attention_total}"',
16711
17188
  ' echo "$lease_summary"',
17189
+ ' echo "$label_summary"',
16712
17190
  "}",
16713
17191
  "",
16714
17192
  "cmd_prs() {",
@@ -16774,6 +17252,196 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
16774
17252
  ' done <<< "$eligible"',
16775
17253
  "}",
16776
17254
  "",
17255
+ "cmd_label_invariant() {",
17256
+ " # Phase-label \u2192 type:<bundle> invariant sweep.",
17257
+ " #",
17258
+ " # Every phased-pipeline bundle pairs its <bundle>:<phase> labels",
17259
+ " # with exactly one type:<bundle> label. An agent that reconstructs",
17260
+ " # a gh issue create call from prose can stamp a conventional-commit",
17261
+ " # type label derived from the title prefix instead, producing an",
17262
+ " # issue that carries the phase label but the WRONG type label \u2014",
17263
+ " # invisible to the --label type:<bundle> duplicate-check idiom and",
17264
+ " # mis-tiered by the funnel-tier sort in cmd_eligible.",
17265
+ " #",
17266
+ " # Default mode is REPORT-ONLY (the consumer-runnable audit); pass",
17267
+ " # --fix to apply corrections. cmd_maintenance runs the --fix mode",
17268
+ " # as part of the Phase D sweep.",
17269
+ " #",
17270
+ " # CORRECTION POLICY. An issue must carry EXACTLY ONE type:* label",
17271
+ " # (cmd_eligible derives its funnel-tier sort key from the FIRST",
17272
+ " # one, and the label conventions mandate exactly one), so the",
17273
+ " # correction REPLACES rather than merely adds:",
17274
+ " #",
17275
+ " # no type:* at all \u2192 add the implied type label",
17276
+ " # only conventional-commit \u2192 remove them, add the implied",
17277
+ " # type labels present label (single atomic edit)",
17278
+ " # implied label already there \u2192 remove the stray conventional",
17279
+ " # alongside a conventional one label, leaving exactly one",
17280
+ " # a type:* owned by ANOTHER \u2192 FLAG status:needs-attention;",
17281
+ " # bundle never guess which to drop",
17282
+ " # phase labels imply 2+ types \u2192 FLAG status:needs-attention",
17283
+ " #",
17284
+ " # Only a conventional-commit type label is ever removed \u2014 the set",
17285
+ " # is_conventional_type_label() recognises. A type:* label owned by",
17286
+ " # a different bundle is a genuine conflict a human must resolve.",
17287
+ " # status:needs-attention is applied ADDITIVELY; the base status:*",
17288
+ " # label always stays (see the additive-flag rule).",
17289
+ " local apply=0",
17290
+ ' if [[ "${1:-}" == "--fix" ]]; then',
17291
+ " apply=1",
17292
+ " fi",
17293
+ ' local mode="report"',
17294
+ ' [[ "$apply" -eq 1 ]] && mode="fix"',
17295
+ "",
17296
+ " local issues",
17297
+ " issues=$(gh issue list --state open --json number,labels \\",
17298
+ ' --limit 1000 2>/dev/null || echo "[]")',
17299
+ "",
17300
+ " # Filter jq-side and emit a TWO-field record. The label list is",
17301
+ " # last AND guaranteed non-empty by the select, so no field can",
17302
+ " # collapse under IFS=tab and shift the record left (#884). The",
17303
+ " # title is deliberately not threaded through \u2014 every output line",
17304
+ " # keys off the issue number.",
17305
+ " local issue_data",
17306
+ ` issue_data=$(echo "$issues" | jq -r '`,
17307
+ " .[] |",
17308
+ " (.labels | map(.name)) as $names |",
17309
+ " select($names | length > 0) |",
17310
+ ' "\\(.number)\\t\\($names | join(","))"',
17311
+ " ' 2>/dev/null)",
17312
+ "",
17313
+ " local checked_count=0",
17314
+ " local ok_count=0",
17315
+ " local corrected_count=0",
17316
+ " local flagged_count=0",
17317
+ "",
17318
+ " while IFS=$'\\t' read -r num labels_csv; do",
17319
+ ' [[ -z "$num" ]] && continue',
17320
+ "",
17321
+ " local labels_nl",
17322
+ ` labels_nl=$(printf '%s' "$labels_csv" | tr ',' '\\n')`,
17323
+ "",
17324
+ " local resolution assignment",
17325
+ " local outcome='' type_label='' phase_labels='' candidates=''",
17326
+ ` resolution=$(printf '%s\\n' "$labels_nl" | phase_type_of)`,
17327
+ " while IFS= read -r assignment; do",
17328
+ ' case "$assignment" in',
17329
+ " OUTCOME=*) outcome=${assignment#OUTCOME=} ;;",
17330
+ " TYPE_LABEL=*) type_label=${assignment#TYPE_LABEL=} ;;",
17331
+ " CANDIDATE_TYPE_LABELS=*) candidates=${assignment#CANDIDATE_TYPE_LABELS=} ;;",
17332
+ " PHASE_LABELS=*) phase_labels=${assignment#PHASE_LABELS=} ;;",
17333
+ " esac",
17334
+ ' done <<< "$resolution"',
17335
+ "",
17336
+ " # No recognised phase label \u2014 the invariant does not apply.",
17337
+ " # Consumer-specific `foo:bar` labels are deliberately not policed.",
17338
+ ' [[ "$outcome" == "none" ]] && continue',
17339
+ " checked_count=$((checked_count + 1))",
17340
+ "",
17341
+ " # Idempotent flagging: read the existing flag off the labels we",
17342
+ " # already fetched rather than spending another API call.",
17343
+ " local already_flagged=0",
17344
+ ' case ",${labels_csv}," in',
17345
+ ' *",status:needs-attention,"*) already_flagged=1 ;;',
17346
+ " esac",
17347
+ "",
17348
+ ' if [[ "$outcome" == "ambiguous" ]]; then',
17349
+ ' echo "LABEL_AMBIGUOUS #${num} phase=\\"${phase_labels}\\" candidates=\\"${candidates}\\""',
17350
+ " flagged_count=$((flagged_count + 1))",
17351
+ ' if [[ "$apply" -eq 1 && "$already_flagged" -eq 0 ]]; then',
17352
+ ' if gh issue edit "$num" --add-label "status:needs-attention" >/dev/null 2>&1; then',
17353
+ ' gh issue comment "$num" \\',
17354
+ ' --body "Label invariant: phase label(s) ${phase_labels} imply more than one bundle type label (${candidates}). Flagged for human triage \u2014 an ambiguous pairing is never auto-corrected." >/dev/null 2>&1 || true',
17355
+ ' echo "LABEL_FLAGGED #${num} \u2014 added status:needs-attention (ambiguous)"',
17356
+ " else",
17357
+ ' echo "LABEL_FLAG_FAILED #${num} \u2014 could not add status:needs-attention (ambiguous)"',
17358
+ " fi",
17359
+ " fi",
17360
+ " continue",
17361
+ " fi",
17362
+ "",
17363
+ " # Partition the issue's existing type:* labels against the",
17364
+ " # implied one.",
17365
+ " local label",
17366
+ " local has_expected=0",
17367
+ " local conv_types=''",
17368
+ " local foreign_types=''",
17369
+ " while IFS= read -r label; do",
17370
+ ' [ -z "$label" ] && continue',
17371
+ ' case "$label" in',
17372
+ " type:*) ;;",
17373
+ " *) continue ;;",
17374
+ " esac",
17375
+ ' if [ "$label" = "$type_label" ]; then',
17376
+ " has_expected=1",
17377
+ " continue",
17378
+ " fi",
17379
+ ' if is_conventional_type_label "$label"; then',
17380
+ ' conv_types="${conv_types}${label} "',
17381
+ " else",
17382
+ ' foreign_types="${foreign_types}${label} "',
17383
+ " fi",
17384
+ ' done <<< "$labels_nl"',
17385
+ "",
17386
+ ' if [[ "$has_expected" -eq 1 && -z "${conv_types}${foreign_types}" ]]; then',
17387
+ ' echo "LABEL_OK #${num} type=${type_label}"',
17388
+ " ok_count=$((ok_count + 1))",
17389
+ " continue",
17390
+ " fi",
17391
+ "",
17392
+ " # A type:* label owned by a DIFFERENT bundle is a conflict the",
17393
+ " # sweep must not guess at: removing it would destroy routing",
17394
+ " # information, and adding alongside it would leave two bundle",
17395
+ " # type labels and a non-deterministic funnel tier.",
17396
+ ' if [[ -n "$foreign_types" ]]; then',
17397
+ ' echo "LABEL_CONFLICT #${num} phase=\\"${phase_labels}\\" expected=${type_label} foreign=\\"${foreign_types% }\\""',
17398
+ " flagged_count=$((flagged_count + 1))",
17399
+ ' if [[ "$apply" -eq 1 && "$already_flagged" -eq 0 ]]; then',
17400
+ ' if gh issue edit "$num" --add-label "status:needs-attention" >/dev/null 2>&1; then',
17401
+ ' gh issue comment "$num" \\',
17402
+ ' --body "Label invariant: phase label(s) ${phase_labels} require ${type_label}, but this issue carries ${foreign_types% }, which is owned by another bundle. Flagged for human triage \u2014 the sweep only removes conventional-commit type labels." >/dev/null 2>&1 || true',
17403
+ ' echo "LABEL_FLAGGED #${num} \u2014 added status:needs-attention (conflicting bundle type)"',
17404
+ " else",
17405
+ ' echo "LABEL_FLAG_FAILED #${num} \u2014 could not add status:needs-attention (conflict)"',
17406
+ " fi",
17407
+ " fi",
17408
+ " continue",
17409
+ " fi",
17410
+ "",
17411
+ " # Correctable: add the implied label and/or drop the",
17412
+ " # conventional-commit label(s) shadowing it, in ONE edit so the",
17413
+ " # issue is never observable carrying two type:* labels.",
17414
+ ' local removing="${conv_types% }"',
17415
+ " local adding=''",
17416
+ ' [[ "$has_expected" -eq 0 ]] && adding="$type_label"',
17417
+ ' echo "LABEL_VIOLATION #${num} phase=\\"${phase_labels}\\" expected=${type_label} add=\\"${adding}\\" remove=\\"${removing}\\""',
17418
+ ' if [[ "$apply" -eq 0 ]]; then',
17419
+ " continue",
17420
+ " fi",
17421
+ "",
17422
+ " local -a edit_args=()",
17423
+ " local stray",
17424
+ " for stray in $removing; do",
17425
+ ' edit_args+=(--remove-label "$stray")',
17426
+ " done",
17427
+ ' [[ -n "$adding" ]] && edit_args+=(--add-label "$adding")',
17428
+ ' if [[ "${#edit_args[@]}" -eq 0 ]]; then',
17429
+ " continue",
17430
+ " fi",
17431
+ ' if gh issue edit "$num" "${edit_args[@]}" >/dev/null 2>&1; then',
17432
+ ' gh issue comment "$num" \\',
17433
+ ' --body "Label invariant: phase label(s) ${phase_labels} require ${type_label}. Corrected \u2014 added: ${adding:-(none)}; removed: ${removing:-(none)}." >/dev/null 2>&1 || true',
17434
+ ' echo "LABEL_CORRECTED #${num} added=\\"${adding}\\" removed=\\"${removing}\\""',
17435
+ " corrected_count=$((corrected_count + 1))",
17436
+ " else",
17437
+ ' echo "LABEL_CORRECT_FAILED #${num} \u2014 label edit failed"',
17438
+ " fi",
17439
+ ' done <<< "$issue_data"',
17440
+ "",
17441
+ " local violations_count=$((checked_count - ok_count))",
17442
+ ' echo "LABEL_INVARIANT_DONE mode=${mode} checked=${checked_count} ok=${ok_count} violations=${violations_count} corrected=${corrected_count} flagged=${flagged_count}"',
17443
+ "}",
17444
+ "",
16777
17445
  "cmd_scope() {",
16778
17446
  ' local issue_num="${1:-}"',
16779
17447
  ' if [[ -z "$issue_num" ]]; then',
@@ -16860,8 +17528,9 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
16860
17528
  ' maintenance) shift; cmd_maintenance "$@" ;;',
16861
17529
  ' prs) shift; cmd_prs "$@" ;;',
16862
17530
  ' scope) shift; cmd_scope "$@" ;;',
17531
+ ' label-invariant) shift; cmd_label_invariant "$@" ;;',
16863
17532
  " help|*)",
16864
- ' echo "Usage: check-blocked.sh <unblock|eligible|stale|orphaned|lease-reconcile|maintenance|prs|scope>"',
17533
+ ' echo "Usage: check-blocked.sh <unblock|eligible|stale|orphaned|lease-reconcile|maintenance|prs|scope|label-invariant>"',
16865
17534
  " exit 1",
16866
17535
  " ;;",
16867
17536
  "esac"
@@ -16870,7 +17539,7 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
16870
17539
  function buildCheckBlockedProcedure(tiers, scopeGate = resolveScopeGate(), runRatio = resolveRunRatio()) {
16871
17540
  return {
16872
17541
  name: "check-blocked.sh",
16873
- description: "Token-efficient issue triage script with subcommands: eligible, unblock, stale, orphaned, lease-reconcile, maintenance, prs, scope. Sorts eligible issues by priority desc \u2192 funnel tier asc \u2192 issue number asc, excluding issues carrying `status:needs-attention` or `status:deferred`; the scope subcommand classifies a single issue against the scope-gate thresholds; the unblock subcommand applies the `status:blocked` \u2192 `status:ready` label flip itself, posts the canned unblock comment, skips human-parked `status:deferred` candidates with a `SKIP_DEFERRED` line, and emits a single `TRIAGE_DONE unblocked=N still_blocked=M deferred_skipped=K` summary line; the lease-reconcile subcommand auto-reconciles stuck `review:fixing` PR leases \u2014 re-applying `review:needs-worker` on an orphaned lease (fix-list stale, no worker report) so the Phase B1 drain retries, and removing `review:needs-worker` on a consumed-but-uncleared wedge (branch HEAD advanced past the fix-list, marker still set, no worker report) so the reviewer confirm pass can merge, always posting an audit-trail note comment and emitting a single `LEASE_RECONCILE orphaned=N consumed_uncleared=M` summary line; the maintenance subcommand flags stale issues with `status:needs-attention` (never auto-resets to `status:ready`), counts orphan branches/PRs, folds in the lease-reconcile sweep, and emits a `MAINTENANCE_DONE flagged_stale=N flagged_blocked=M orphan_branches=A orphan_prs=B needs_attention_total=T` summary line followed by the `LEASE_RECONCILE` line.",
17542
+ description: "Token-efficient issue triage script with subcommands: eligible, unblock, stale, orphaned, lease-reconcile, maintenance, prs, scope, label-invariant. Sorts eligible issues by priority desc \u2192 funnel tier asc \u2192 issue number asc, excluding issues carrying `status:needs-attention` or `status:deferred`; the scope subcommand classifies a single issue against the scope-gate thresholds; the unblock subcommand applies the `status:blocked` \u2192 `status:ready` label flip itself, posts the canned unblock comment, skips human-parked `status:deferred` candidates with a `SKIP_DEFERRED` line, and emits a single `TRIAGE_DONE unblocked=N still_blocked=M deferred_skipped=K` summary line; the lease-reconcile subcommand auto-reconciles stuck `review:fixing` PR leases \u2014 re-applying `review:needs-worker` on an orphaned lease (fix-list stale, no worker report) so the Phase B1 drain retries, and removing `review:needs-worker` on a consumed-but-uncleared wedge (branch HEAD advanced past the fix-list, marker still set, no worker report) so the reviewer confirm pass can merge, always posting an audit-trail note comment and emitting a single `LEASE_RECONCILE orphaned=N consumed_uncleared=M` summary line; the maintenance subcommand flags stale issues with `status:needs-attention` (never auto-resets to `status:ready`), counts orphan branches/PRs, folds in the lease-reconcile sweep and the label-invariant auto-correction, and emits a `MAINTENANCE_DONE flagged_stale=N flagged_blocked=M orphan_branches=A orphan_prs=B needs_attention_total=T` summary line followed by the `LEASE_RECONCILE` and `LABEL_INVARIANT_DONE` lines; the label-invariant subcommand enforces the phase-label \u2192 `type:<bundle>` pairing derived from the canonical `BUNDLE_OWNERSHIP` map \u2014 report-only by default, auto-correcting with `--fix` (replacing a shadowing conventional-commit `type:*` rather than merely adding, so the issue keeps exactly one `type:*`) and flagging `status:needs-attention` when the pairing is ambiguous or collides with another bundle's type label, emitting a single `LABEL_INVARIANT_DONE mode=M checked=N ok=O violations=V corrected=C flagged=F` summary line.",
16874
17543
  content: buildCheckBlockedScript(tiers, scopeGate, runRatio)
16875
17544
  };
16876
17545
  }
@@ -17586,32 +18255,36 @@ var orchestratorSubAgent = {
17586
18255
  "## Phase D: Maintenance",
17587
18256
  "",
17588
18257
  "Run the bundled `check-blocked.sh maintenance` procedure and read",
17589
- "**only** the two summary lines it emits \u2014 `MAINTENANCE_DONE` and",
17590
- "`LEASE_RECONCILE`. The procedure folds the stale-detection,",
17591
- "orphan-detection, needs-attention summary, and PR-lease",
17592
- "auto-reconcile that earlier revisions split across D1, D2, and D3",
17593
- "into a single sweep \u2014 applying the `status:needs-attention` label",
17594
- "and posting the canned flag comment for each stale / stale-blocked",
17595
- "issue itself, and reconciling stuck `review:fixing` PR leases",
17596
- "itself, mirroring the discipline of Phase B (`pr-sweep.sh`) and",
17597
- "Phase C (`check-blocked.sh unblock`).",
18258
+ "**only** the three summary lines it emits \u2014 `MAINTENANCE_DONE`,",
18259
+ "`LEASE_RECONCILE`, and `LABEL_INVARIANT_DONE`. The procedure folds",
18260
+ "the stale-detection, orphan-detection, needs-attention summary,",
18261
+ "PR-lease auto-reconcile, and phase-label invariant sweep that",
18262
+ "earlier revisions split across D1, D2, and D3 into a single sweep \u2014",
18263
+ "applying the `status:needs-attention` label and posting the canned",
18264
+ "flag comment for each stale / stale-blocked issue itself,",
18265
+ "reconciling stuck `review:fixing` PR leases itself, and correcting",
18266
+ "mislabeled pipeline issues itself, mirroring the discipline of",
18267
+ "Phase B (`pr-sweep.sh`) and Phase C (`check-blocked.sh unblock`).",
17598
18268
  "",
17599
18269
  "```bash",
17600
18270
  ".claude/procedures/check-blocked.sh maintenance",
17601
18271
  "```",
17602
18272
  "",
17603
- "The script emits two summary lines in this shape:",
18273
+ "The script emits three summary lines in this shape:",
17604
18274
  "",
17605
18275
  "```",
17606
18276
  "MAINTENANCE_DONE flagged_stale=<N> flagged_blocked=<M> orphan_branches=<A> orphan_prs=<B> needs_attention_total=<T>",
17607
18277
  "LEASE_RECONCILE orphaned=<N> consumed_uncleared=<M>",
18278
+ "LABEL_INVARIANT_DONE mode=fix checked=<N> ok=<O> violations=<V> corrected=<C> flagged=<F>",
17608
18279
  "```",
17609
18280
  "",
17610
18281
  "Per-issue / per-orphan / per-PR informational lines (`FLAGGED_STALE",
17611
18282
  "#N`, `FLAGGED_BLOCKED #N`, `STALE #N \u2014 \u2026`, `STALE_BLOCKED #N \u2014 \u2026`,",
17612
18283
  "`ORPHAN_BRANCH \u2026`, `ORPHAN_PR #N \u2014 \u2026`, `FLAG_FAILED #N \u2014 \u2026`,",
17613
18284
  "`LEASE_ORPHANED PR #N \u2014 \u2026`, `LEASE_CONSUMED_UNCLEARED PR #N \u2014 \u2026`,",
17614
- "`LEASE_RECONCILE_FAILED PR #N \u2014 \u2026`) are emitted for log visibility",
18285
+ "`LEASE_RECONCILE_FAILED PR #N \u2014 \u2026`, `LABEL_VIOLATION #N \u2014 \u2026`,",
18286
+ "`LABEL_CORRECTED #N \u2014 \u2026`, `LABEL_AMBIGUOUS #N \u2014 \u2026`,",
18287
+ "`LABEL_CONFLICT #N \u2014 \u2026`) are emitted for log visibility",
17615
18288
  "but are **not** load-bearing for the orchestrator \u2014 partial failures",
17616
18289
  "(one bad `gh` call) do not abort the sweep, they are simply omitted",
17617
18290
  "from the counters.",
@@ -17651,9 +18324,24 @@ var orchestratorSubAgent = {
17651
18324
  "is never silently force-released. The `review:fixing` lease itself",
17652
18325
  "is always left for the reviewer confirm pass to release.",
17653
18326
  "",
17654
- "Log both summary lines and continue to Phase E regardless of",
17655
- "outcomes \u2014 a non-zero `flagged_*`, `orphan_*`, `orphaned`, or",
17656
- "`consumed_uncleared` count is informational, not a failure.",
18327
+ "**Phase-label invariant (`LABEL_INVARIANT_DONE`).** The sweep also",
18328
+ "enforces the phase-label \u2192 `type:<bundle>` pairing on every open",
18329
+ "issue, so a pipeline issue filed with a conventional-commit",
18330
+ "`type:*` (derived from its title prefix) cannot stay invisible to",
18331
+ 'the `--label "type:<bundle>"` duplicate-check idiom or mis-tier in',
18332
+ "Phase E's funnel-tier sort. The script mutates the labels itself \u2014",
18333
+ "the orchestrator only reads the summary line. See the **Phase-label",
18334
+ "\u2192 `type:<bundle>` invariant** section in `CLAUDE.md` for the full",
18335
+ "correction policy; in short, a shadowing conventional-commit type",
18336
+ "label is **replaced** (never merely supplemented, so the issue",
18337
+ "keeps exactly one `type:*`), and an ambiguous or cross-bundle",
18338
+ "collision is **flagged** `status:needs-attention` rather than",
18339
+ "guessed at.",
18340
+ "",
18341
+ "Log all three summary lines and continue to Phase E regardless of",
18342
+ "outcomes \u2014 a non-zero `flagged_*`, `orphan_*`, `orphaned`,",
18343
+ "`consumed_uncleared`, `corrected`, or `flagged` count is",
18344
+ "informational, not a failure.",
17657
18345
  "",
17658
18346
  "## Phase E: Queue Scan",
17659
18347
  "",
@@ -18655,6 +19343,7 @@ var ORCHESTRATOR_CONVENTIONS_PREAMBLE = [
18655
19343
  "- Stale thresholds: 72h for in-progress, 168h for blocked",
18656
19344
  "- Flagged issues get `status:needs-attention` \u2014 they are not auto-reset",
18657
19345
  "- **Phase D auto-reconciles stuck PR leases.** The maintenance sweep's `check-blocked.sh maintenance` invocation folds in a PR-lease reconcile that unwedges `review:fixing` PRs the Phase B1 drain and the worker hand-off left stuck, emitting a `LEASE_RECONCILE orphaned=<N> consumed_uncleared=<M>` line alongside `MAINTENANCE_DONE`. An **orphaned lease** (`review:fixing` without `review:needs-worker`, fix-list older than the 72h stale threshold, no worker-report newer than the fix-list) is **re-armed** \u2014 the sweep re-applies `review:needs-worker` so the Phase B1 drain retries the delegation, leaving the lease untouched. A **consumed-but-uncleared wedge** (both labels present, branch HEAD newer than the fix-list, no worker-report \u2014 the worker pushed but skipped the hand-off) is **auto-reconciled** \u2014 the sweep removes `review:needs-worker` so the reviewer confirm pass can merge. Both paths post an audit-trail note comment; a lease is never silently force-released, and the `review:fixing` lease itself is always left for the reviewer confirm pass to release.",
19346
+ "- **Phase D enforces the phase-label \u2192 `type:<bundle>` invariant.** The same maintenance sweep folds in a label-invariant pass that auto-corrects open issues carrying a pipeline phase label without the matching `type:<bundle>` label, emitting a `LABEL_INVARIANT_DONE mode=fix checked=<N> ok=<O> violations=<V> corrected=<C> flagged=<F>` line alongside `MAINTENANCE_DONE`. The correction **replaces** a shadowing conventional-commit `type:*` rather than merely adding the bundle type, so the issue keeps exactly one `type:*` and Phase E's funnel-tier sort key stays deterministic. Ambiguous pairings and collisions with another bundle's `type:*` label are **flagged** `status:needs-attention` (additively) instead of guessed at. See the **Phase-label \u2192 `type:<bundle>` invariant** section below for the matcher table and the full correction policy.",
18658
19347
  "",
18659
19348
  "## Depth-0 invocation requirement",
18660
19349
  "",
@@ -18670,6 +19359,8 @@ function buildOrchestratorConventionsContent(tiers, scopeGate = resolveScopeGate
18670
19359
  "",
18671
19360
  renderScopeGateSection(scopeGate, excludeBundles),
18672
19361
  "",
19362
+ renderPhaseTypeInvariantSection(excludeBundles),
19363
+ "",
18673
19364
  renderScheduledTasksSection(scheduledTasks),
18674
19365
  "",
18675
19366
  renderUnblockDependentsSection(unblockDependents)
@@ -19270,6 +19961,8 @@ function buildPeopleProfileAnalystSubAgent(paths, issueDefaults, tier) {
19270
19961
  ` - \`priority:${labelsForPhase(issueDefaults, "company:research").priority}\``,
19271
19962
  ` - \`status:${labelsForPhase(issueDefaults, "company:research").status}\``,
19272
19963
  "",
19964
+ ...renderIssueTypeAssignmentStep(),
19965
+ "",
19273
19966
  " The issue body must include:",
19274
19967
  " - A **discovery source** line naming this person profile",
19275
19968
  " (`Discovered while profiling: <person name>`)",
@@ -19290,6 +19983,8 @@ function buildPeopleProfileAnalystSubAgent(paths, issueDefaults, tier) {
19290
19983
  ` - \`priority:${labelsForPhase(issueDefaults, "software:research").priority}\``,
19291
19984
  ` - \`status:${labelsForPhase(issueDefaults, "software:research").status}\``,
19292
19985
  "",
19986
+ ...renderIssueTypeAssignmentStep(),
19987
+ "",
19293
19988
  " The issue body must include:",
19294
19989
  " - A **discovery source** line naming this person profile",
19295
19990
  " (`Discovered while profiling: <person name>`)",
@@ -19572,6 +20267,8 @@ function buildProfilePersonSkill(paths, issueDefaults) {
19572
20267
  ` \`priority:${labelsForPhase(issueDefaults, "people:research").priority}\`, and \`status:${labelsForPhase(issueDefaults, "people:research").status}\`. Body must include the`,
19573
20268
  " person's name, selected role, primary company, framing, and any",
19574
20269
  " overrides.",
20270
+ "",
20271
+ ...renderIssueTypeAssignmentStep(),
19575
20272
  "2. Execute Phase 1 (Research) of the people-profile-analyst agent.",
19576
20273
  "3. Phase 1 creates the `people:draft` issue. Phase 2 may create a",
19577
20274
  " `people:followup` issue. Each downstream issue declares its",
@@ -19663,6 +20360,8 @@ function buildRefreshPersonSkill(paths, issueDefaults) {
19663
20360
  "1. Create a `people:refresh` issue with `type:people-profile`,",
19664
20361
  ` \`priority:${labelsForPhase(issueDefaults, "people:refresh").priority}\`, and \`status:${labelsForPhase(issueDefaults, "people:refresh").status}\`. Body must include the`,
19665
20362
  " profile path and any overrides.",
20363
+ "",
20364
+ ...renderIssueTypeAssignmentStep(),
19666
20365
  "2. Execute Phase 4 (Refresh) of the people-profile-analyst agent.",
19667
20366
  "",
19668
20367
  "## Output",
@@ -23359,6 +24058,8 @@ function buildRegulatoryResearchAnalystSubAgent(paths, issueDefaults) {
23359
24058
  ` - \`priority:${labelsForPhase(issueDefaults, "company:research").priority}\``,
23360
24059
  ` - \`status:${labelsForPhase(issueDefaults, "company:research").status}\``,
23361
24060
  "",
24061
+ ...renderIssueTypeAssignmentStep(),
24062
+ "",
23362
24063
  "9. **Verify all handoffs were created** before committing.",
23363
24064
  "",
23364
24065
  "10. **Commit and push** the regulation page. Close the research",
@@ -23469,6 +24170,8 @@ function buildRegulatoryResearchAnalystSubAgent(paths, issueDefaults) {
23469
24170
  ` - \`priority:${labelsForPhase(issueDefaults, "req:scan").priority}\``,
23470
24171
  ` - \`status:${labelsForPhase(issueDefaults, "req:scan").status}\``,
23471
24172
  "",
24173
+ ...renderIssueTypeAssignmentStep(),
24174
+ "",
23472
24175
  " The body must also:",
23473
24176
  " - Identify the target category as **SEC** (security & compliance)",
23474
24177
  " in the body so the downstream writer phase picks up the right",
@@ -23488,6 +24191,8 @@ function buildRegulatoryResearchAnalystSubAgent(paths, issueDefaults) {
23488
24191
  ` - \`priority:${labelsForPhase(issueDefaults, "company:research").priority}\``,
23489
24192
  ` - \`status:${labelsForPhase(issueDefaults, "company:research").status}\``,
23490
24193
  "",
24194
+ ...renderIssueTypeAssignmentStep(),
24195
+ "",
23491
24196
  "6. **Create `people:research` issues** for notable regulatory",
23492
24197
  " leaders (commissioners, agency directors, lead policy authors)",
23493
24198
  " mentioned on the regulation page. Keep this list small \u2014 at most",
@@ -23499,6 +24204,8 @@ function buildRegulatoryResearchAnalystSubAgent(paths, issueDefaults) {
23499
24204
  ` - \`priority:${labelsForPhase(issueDefaults, "people:research").priority}\``,
23500
24205
  ` - \`status:${labelsForPhase(issueDefaults, "people:research").status}\``,
23501
24206
  "",
24207
+ ...renderIssueTypeAssignmentStep(),
24208
+ "",
23502
24209
  "7. **Commit and push** the updated regulation page. Close the",
23503
24210
  " impact issue.",
23504
24211
  "",
@@ -23666,6 +24373,8 @@ function buildScanRegulatoryLandscapeSkill(paths, issueDefaults) {
23666
24373
  ` \`priority:${labelsForPhase(issueDefaults, "regulatory:scan").priority}\`, and \`status:${labelsForPhase(issueDefaults, "regulatory:scan").status}\`. The body must include the`,
23667
24374
  " scope slug, the scope type, and any known regulations to include",
23668
24375
  " or exclude.",
24376
+ "",
24377
+ ...renderIssueTypeAssignmentStep(),
23669
24378
  "2. Execute Phase 1 (Scan) of the regulatory-research-analyst agent.",
23670
24379
  "3. Phase 1 writes the scan report and creates one",
23671
24380
  " `regulatory:research` issue per prioritized regulation.",
@@ -23742,6 +24451,8 @@ function buildResearchRegulationSkill(paths, issueDefaults) {
23742
24451
  ` \`type:regulatory-research\`, \`priority:${labelsForPhase(issueDefaults, "regulatory:research").priority}\`, and`,
23743
24452
  ` \`status:${labelsForPhase(issueDefaults, "regulatory:research").status}\`. The body must include the scope slug and the`,
23744
24453
  " regulation slug.",
24454
+ "",
24455
+ ...renderIssueTypeAssignmentStep(),
23745
24456
  "2. Execute Phase 2 (Research) of the regulatory-research-analyst",
23746
24457
  " agent. The agent runs 6\u201312 targeted web searches and populates",
23747
24458
  " the regulation page.",
@@ -23809,6 +24520,8 @@ function buildImpactRegulationSkill(paths, issueDefaults) {
23809
24520
  ` \`status:${labelsForPhase(issueDefaults, "regulatory:impact").status}\` (or \`status:blocked\` when still dependent on a`,
23810
24521
  " Phase 2 research issue). Body must include the regulation page",
23811
24522
  " path.",
24523
+ "",
24524
+ ...renderIssueTypeAssignmentStep(),
23812
24525
  "2. Execute Phase 3 (Impact) of the regulatory-research-analyst",
23813
24526
  " agent. No web searches \u2014 pure synthesis from the regulation",
23814
24527
  " page and project context.",
@@ -26644,6 +27357,8 @@ function buildWriteRequirementSkill(paths, issueDefaults) {
26644
27357
  " Body must include the category, tier, output path, and a pointer",
26645
27358
  " to the upstream proposal (or a direct user description if no",
26646
27359
  " proposals file exists).",
27360
+ "",
27361
+ ...renderIssueTypeAssignmentStep(),
26647
27362
  "2. Execute the write phase of the requirements-writer agent.",
26648
27363
  "3. The agent writes one requirement document, updates the category",
26649
27364
  " index, opens a PR, and closes the issue.",
@@ -27233,6 +27948,8 @@ function buildRequirementsAnalystSubAgent(paths, issueDefaults) {
27233
27948
  ` \`--label "tier:<tier-slug>"\`, \`--label "status:${labelsForPhase(issueDefaults, "req:write").status}"\`, and`,
27234
27949
  ` \`--label "priority:${labelsForPhase(issueDefaults, "req:write").priority}"\`.`,
27235
27950
  "",
27951
+ ...renderIssueTypeAssignmentStep(),
27952
+ "",
27236
27953
  " The body must carry the three writer-required fields in an",
27237
27954
  " `## Objective` block, written as bold-prefixed lines so the",
27238
27955
  " writer's intake parser can pull them out, plus the reserved-ID",
@@ -27379,6 +28096,8 @@ function buildScanRequirementsSkill(issueDefaults) {
27379
28096
  "",
27380
28097
  `1. Create a \`req:scan\` issue with \`type:requirement\`, \`priority:${labelsForPhase(issueDefaults, "req:scan").priority}\`,`,
27381
28098
  ` and \`status:${labelsForPhase(issueDefaults, "req:scan").status}\`. Body must list the files to read and the scan scope.`,
28099
+ "",
28100
+ ...renderIssueTypeAssignmentStep(),
27382
28101
  "2. Execute Phase 1 (Scan) of the requirements-analyst agent.",
27383
28102
  "3. If gaps are found, a `req:draft-trace` issue is created automatically.",
27384
28103
  "",
@@ -28427,6 +29146,7 @@ function buildRequirementsReviewerSubAgent(paths, issueDefaults) {
28427
29146
  "- Labels: `type:requirement`, `req:write`, the matching",
28428
29147
  " `tier:*` label derived from the referencing document's Tier,",
28429
29148
  ` \`priority:${labelsForPhase(issueDefaults, "req:write").priority}\`, \`status:${labelsForPhase(issueDefaults, "req:write").status}\`.`,
29149
+ ...renderIssueTypeAssignmentStep({ indent: "", bullet: true }),
28430
29150
  "- If any of Category / Tier / Output Path cannot be derived from",
28431
29151
  " the referencing document, open the follow-up with",
28432
29152
  " `status:needs-attention` and a `Missing:` line, per the same",
@@ -28582,6 +29302,8 @@ function buildReviewRequirementsSkill(paths, issueDefaults) {
28582
29302
  ` \`priority:${labelsForPhase(issueDefaults, "req:review").priority}\`, and \`status:${labelsForPhase(issueDefaults, "req:review").status}\`. Body must include the`,
28583
29303
  " scope, the requirements root (or accept the default), and the",
28584
29304
  " output path.",
29305
+ "",
29306
+ ...renderIssueTypeAssignmentStep(),
28585
29307
  "2. Execute the review phase of the requirements-reviewer agent.",
28586
29308
  "3. The agent runs the 12-check audit, writes a structured report",
28587
29309
  " grouped by Critical / Warning / Info (plus a separate",
@@ -28684,6 +29406,8 @@ function buildDeprecateRequirementSkill(_paths, issueDefaults) {
28684
29406
  ` \`priority:${labelsForPhase(issueDefaults, "req:deprecate").priority}\`, and \`status:${labelsForPhase(issueDefaults, "req:deprecate").status}\`. Body must include the`,
28685
29407
  " Targets, Transition, and Reason sections above (optionally",
28686
29408
  " `Pre-approved: yes`).",
29409
+ "",
29410
+ ...renderIssueTypeAssignmentStep(),
28687
29411
  "2. Execute the deprecation phase of the requirements-reviewer",
28688
29412
  " agent.",
28689
29413
  "3. The agent verifies inputs, pauses for human confirmation (or",
@@ -29258,6 +29982,8 @@ function buildResearchSkill(paths, issueDefaults) {
29258
29982
  "1. Create a `research:scope` issue with `type:research`,",
29259
29983
  ` \`priority:${labelsForPhase(issueDefaults, "research:scope").priority}\`, and \`status:${labelsForPhase(issueDefaults, "research:scope").status}\`. Body must include the`,
29260
29984
  " verbatim question, authorized sources, and any overrides.",
29985
+ "",
29986
+ ...renderIssueTypeAssignmentStep(),
29261
29987
  "2. Execute Phase 1 (Scope) of the research-analyst agent.",
29262
29988
  "3. Phase 1 creates `research:slice` issues (one per slice) and a",
29263
29989
  " single `research:verify` issue. Each downstream issue declares",
@@ -30073,6 +30799,8 @@ function buildSoftwareProfileAnalystSubAgent(paths, issueDefaults, tier) {
30073
30799
  ` - \`priority:${labelsForPhase(issueDefaults, "bcm:outline").priority}\``,
30074
30800
  ` - \`status:${labelsForPhase(issueDefaults, "bcm:outline").status}\``,
30075
30801
  "",
30802
+ ...renderIssueTypeAssignmentStep(),
30803
+ "",
30076
30804
  " The issue body must include:",
30077
30805
  " - A **discovery source** line naming this software profile",
30078
30806
  " (`Discovered while mapping: <product name>`)",
@@ -30141,6 +30869,8 @@ function buildSoftwareProfileAnalystSubAgent(paths, issueDefaults, tier) {
30141
30869
  " `software:research` phase with `type:software-profile`,",
30142
30870
  ` \`priority:${labelsForPhase(issueDefaults, "software:research").priority}\`, and \`status:${labelsForPhase(issueDefaults, "software:research").status}\`.`,
30143
30871
  "",
30872
+ ...renderIssueTypeAssignmentStep(),
30873
+ "",
30144
30874
  "3. **Enqueue a `company:research` issue for the vendor company** if",
30145
30875
  " no matching profile already exists under `<COMPANY_PROFILES_DIR>/`.",
30146
30876
  " The vendor (the company that builds or maintains the product)",
@@ -30153,6 +30883,8 @@ function buildSoftwareProfileAnalystSubAgent(paths, issueDefaults, tier) {
30153
30883
  ` - \`priority:${labelsForPhase(issueDefaults, "company:research").priority}\``,
30154
30884
  ` - \`status:${labelsForPhase(issueDefaults, "company:research").status}\``,
30155
30885
  "",
30886
+ ...renderIssueTypeAssignmentStep(),
30887
+ "",
30156
30888
  " The issue body must include:",
30157
30889
  " - A **discovery source** line naming this software profile",
30158
30890
  " (`Discovered while profiling: <product name>`)",
@@ -30172,6 +30904,8 @@ function buildSoftwareProfileAnalystSubAgent(paths, issueDefaults, tier) {
30172
30904
  ` - \`priority:${labelsForPhase(issueDefaults, "people:research").priority}\``,
30173
30905
  ` - \`status:${labelsForPhase(issueDefaults, "people:research").status}\``,
30174
30906
  "",
30907
+ ...renderIssueTypeAssignmentStep(),
30908
+ "",
30175
30909
  " The issue body must include:",
30176
30910
  " - A **discovery source** line naming this software profile",
30177
30911
  " (`Discovered while profiling: <product name>`)",
@@ -30362,6 +31096,8 @@ function buildProfileSoftwareSkill(paths, issueDefaults) {
30362
31096
  "1. Create a `software:research` issue with `type:software-profile`,",
30363
31097
  ` \`priority:${labelsForPhase(issueDefaults, "software:research").priority}\`, and \`status:${labelsForPhase(issueDefaults, "software:research").status}\`. Body must include the`,
30364
31098
  " product name, selected type, framing, and any overrides.",
31099
+ "",
31100
+ ...renderIssueTypeAssignmentStep(),
30365
31101
  "2. Execute Phase 1 (Research) of the software-profile-analyst",
30366
31102
  " agent.",
30367
31103
  "3. Phase 1 creates the `software:profile` issue. Phase 2 creates",
@@ -30450,6 +31186,8 @@ function buildMapSoftwareSkill(paths, issueDefaults) {
30450
31186
  "1. Create a `software:map` issue with `type:software-profile`,",
30451
31187
  ` \`priority:${labelsForPhase(issueDefaults, "software:map").priority}\`, and \`status:${labelsForPhase(issueDefaults, "software:map").status}\`. Body must include the`,
30452
31188
  " profile path and any overrides.",
31189
+ "",
31190
+ ...renderIssueTypeAssignmentStep(),
30453
31191
  "2. Execute Phase 4 (Map) of the software-profile-analyst agent.",
30454
31192
  "3. Phase 4 writes the `## Capability Mapping` section, enqueues",
30455
31193
  " `bcm:outline` issues for unmapped features above the threshold,",
@@ -31062,6 +31800,8 @@ function buildStandardsResearchAnalystSubAgent(paths, issueDefaults) {
31062
31800
  ` - \`priority:${labelsForPhase(issueDefaults, "company:research").priority}\``,
31063
31801
  ` - \`status:${labelsForPhase(issueDefaults, "company:research").status}\``,
31064
31802
  "",
31803
+ ...renderIssueTypeAssignmentStep(),
31804
+ "",
31065
31805
  "6. **Create `people:research` issues** for every entry in *People of",
31066
31806
  " Interest* that does not already have a canonical person profile",
31067
31807
  " or an open `people:research` issue. The `people-profile` bundle",
@@ -31073,6 +31813,8 @@ function buildStandardsResearchAnalystSubAgent(paths, issueDefaults) {
31073
31813
  ` - \`priority:${labelsForPhase(issueDefaults, "people:research").priority}\``,
31074
31814
  ` - \`status:${labelsForPhase(issueDefaults, "people:research").status}\``,
31075
31815
  "",
31816
+ ...renderIssueTypeAssignmentStep(),
31817
+ "",
31076
31818
  "7. **Verify all handoffs were created** before committing. For each",
31077
31819
  " entity in both tables, confirm an existing profile or a fresh",
31078
31820
  " handoff issue exists. Do not commit with missing handoffs.",
@@ -31366,6 +32108,8 @@ function buildStandardsResearchAnalystSubAgent(paths, issueDefaults) {
31366
32108
  ` - \`priority:${labelsForPhase(issueDefaults, "company:research").priority}\``,
31367
32109
  ` - \`status:${labelsForPhase(issueDefaults, "company:research").status}\``,
31368
32110
  "",
32111
+ ...renderIssueTypeAssignmentStep(),
32112
+ "",
31369
32113
  "6. **Hand off canonical people profiles** by creating one",
31370
32114
  " `people:research` issue per leadership entry that does not",
31371
32115
  " already have a canonical profile or an open `people:research`",
@@ -31377,6 +32121,8 @@ function buildStandardsResearchAnalystSubAgent(paths, issueDefaults) {
31377
32121
  ` - \`priority:${labelsForPhase(issueDefaults, "people:research").priority}\``,
31378
32122
  ` - \`status:${labelsForPhase(issueDefaults, "people:research").status}\``,
31379
32123
  "",
32124
+ ...renderIssueTypeAssignmentStep(),
32125
+ "",
31380
32126
  "7. **Commit and push** the organization page. Close the",
31381
32127
  " organizations issue.",
31382
32128
  "",
@@ -31551,6 +32297,8 @@ function buildScopeStandardsResearchSkill(paths, issueDefaults) {
31551
32297
  ` \`priority:${labelsForPhase(issueDefaults, "standards:scope").priority}\`, and \`status:${labelsForPhase(issueDefaults, "standards:scope").status}\`. The body must include the`,
31552
32298
  " standard slug, the list of target versions (or a request to",
31553
32299
  " enumerate), and any known governance bodies.",
32300
+ "",
32301
+ ...renderIssueTypeAssignmentStep(),
31554
32302
  "2. Execute Phase 1 (Scope) of the standards-research-analyst agent.",
31555
32303
  "3. Phase 1 writes the query plan and creates one `standards:research`",
31556
32304
  " issue per target version, one `standards:compare` issue (blocked",
@@ -31615,6 +32363,8 @@ function buildResearchStandardSkill(paths, issueDefaults) {
31615
32363
  "1. Create a `standards:research` issue with",
31616
32364
  ` \`type:standards-research\`, \`priority:${labelsForPhase(issueDefaults, "standards:research").priority}\`, and \`status:${labelsForPhase(issueDefaults, "standards:research").status}\`.`,
31617
32365
  " The body must include the standard slug and the version slug.",
32366
+ "",
32367
+ ...renderIssueTypeAssignmentStep(),
31618
32368
  "2. Execute Phase 2 (Research) of the standards-research-analyst",
31619
32369
  " agent. The agent runs 6\u201310 targeted web searches and populates",
31620
32370
  " the version page.",
@@ -31688,6 +32438,8 @@ function buildCompareStandardsSkill(paths, issueDefaults) {
31688
32438
  ` \`type:standards-research\`, \`priority:${labelsForPhase(issueDefaults, "standards:compare").priority}\`, and \`status:${labelsForPhase(issueDefaults, "standards:compare").status}\`.`,
31689
32439
  " Body must include the standard slug and the list of version",
31690
32440
  " pages under comparison.",
32441
+ "",
32442
+ ...renderIssueTypeAssignmentStep(),
31691
32443
  "2. Execute Phase 3 (Compare) of the standards-research-analyst",
31692
32444
  " agent. No web searches \u2014 pure synthesis from the version pages.",
31693
32445
  "",
@@ -31756,6 +32508,8 @@ function buildExtensionStandardSkill(paths, issueDefaults) {
31756
32508
  " The body must include the standard slug, the entity slug, the",
31757
32509
  " closest resource, and a link to the source BCM or canvas",
31758
32510
  " document.",
32511
+ "",
32512
+ ...renderIssueTypeAssignmentStep(),
31759
32513
  "2. Execute Phase 4 (Extension) of the standards-research-analyst",
31760
32514
  " agent. The agent runs 4\u20138 targeted web searches against",
31761
32515
  " community registries, implementation guides, and working-group",
@@ -31821,6 +32575,8 @@ function buildOrganizationsStandardSkill(paths, issueDefaults) {
31821
32575
  ` \`type:standards-research\`, \`priority:${labelsForPhase(issueDefaults, "standards:organizations").priority}\`, and \`status:${labelsForPhase(issueDefaults, "standards:organizations").status}\`.`,
31822
32576
  " Body must include the standard slug, the organization slug, and",
31823
32577
  " the organization's governance role.",
32578
+ "",
32579
+ ...renderIssueTypeAssignmentStep(),
31824
32580
  "2. Execute Phase 5 (Organizations) of the",
31825
32581
  " standards-research-analyst agent. The agent runs 4\u20138 targeted",
31826
32582
  " web searches.",
@@ -32130,6 +32886,14 @@ var UPSTREAM_CONFIGULATOR_DOCS_SUMMARY = [
32130
32886
  "",
32131
32887
  "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.",
32132
32888
  "",
32889
+ "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:",
32890
+ "",
32891
+ "```sh",
32892
+ "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",
32893
+ "gh issue view <issue-number> --repo codedrifters/packages --json id --jq '.id'",
32894
+ "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>",
32895
+ "```",
32896
+ "",
32133
32897
  "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."
32134
32898
  ].join("\n");
32135
32899
  var UPSTREAM_CONFIGULATOR_DOCS_DETAIL = [
@@ -32238,6 +33002,16 @@ var UPSTREAM_CONFIGULATOR_DOCS_DETAIL = [
32238
33002
  "- `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.",
32239
33003
  "- `status:*` \u2014 `status:ready` for self-contained issues, `status:blocked` when the issue declares `Depends on: #<n>` against another open issue.",
32240
33004
  "",
33005
+ "### GitHub issue type",
33006
+ "",
33007
+ "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:",
33008
+ "",
33009
+ "```sh",
33010
+ "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",
33011
+ "gh issue view <issue-number> --repo codedrifters/packages --json id --jq '.id'",
33012
+ "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>",
33013
+ "```",
33014
+ "",
32241
33015
  "### Worked example",
32242
33016
  "",
32243
33017
  "Suppose configulator's `software-profile-analyst` agent prompt is missing a step you need. Instead of forking the prompt locally:",
@@ -41022,6 +41796,7 @@ export const collections = {
41022
41796
  CDK_WATCH_DEFAULTS_BY_STAGE,
41023
41797
  CLAUDE_RULE_TARGET,
41024
41798
  COMPLETE_JOB_ID,
41799
+ CONVENTIONAL_COMMIT_TYPE_LABELS,
41025
41800
  CdkCli,
41026
41801
  DEFAULT_AC_THRESHOLDS,
41027
41802
  DEFAULT_AGENT_PATHS,
@@ -41036,6 +41811,7 @@ export const collections = {
41036
41811
  DEFAULT_DECOMPOSITION_TEMPLATE,
41037
41812
  DEFAULT_DISPATCH_MODEL,
41038
41813
  DEFAULT_DISPATCH_TO_HOUSEKEEPING_RATIO,
41814
+ DEFAULT_GITHUB_ISSUE_TYPE,
41039
41815
  DEFAULT_HOUSEKEEPING_MODEL,
41040
41816
  DEFAULT_ISSUE_PRIORITY,
41041
41817
  DEFAULT_ISSUE_STATUS,
@@ -41082,6 +41858,8 @@ export const collections = {
41082
41858
  DEFAULT_UNBLOCK_DEPENDENTS_ENABLED,
41083
41859
  DEFAULT_UPSTREAM_CONFIGULATOR_ENABLED,
41084
41860
  DOCS_SYNC_AUDIT_SCHEMA_VERSION,
41861
+ GITHUB_ISSUE_TYPES,
41862
+ GITHUB_ISSUE_TYPE_BY_TITLE_PREFIX,
41085
41863
  JsiiFaker,
41086
41864
  LAYOUT_ENFORCEMENT,
41087
41865
  LAYOUT_ROOT_BY_PROJECT_TYPE,
@@ -41093,6 +41871,7 @@ export const collections = {
41093
41871
  MONOREPO_LAYOUT,
41094
41872
  MonorepoProject,
41095
41873
  Nvmrc,
41874
+ PHASE_LABEL_TYPE_MAP,
41096
41875
  PROD_DEPLOY_NAME,
41097
41876
  PROGRESS_FILES_FORMAT_VALUES,
41098
41877
  PnpmWorkspace,
@@ -41105,6 +41884,7 @@ export const collections = {
41105
41884
  ResetTask,
41106
41885
  SCHEDULED_TASK_MODEL_VALUES,
41107
41886
  SCOPE_CLASS_VALUES,
41887
+ SET_ISSUE_TYPE_HELPER_PATH,
41108
41888
  SHARED_EDITING_CONFLICT_STRATEGY_VALUES,
41109
41889
  STARLIGHT_ROLE,
41110
41890
  SUPPRESSED_WORKFLOW_RULE_NAMES,
@@ -41190,6 +41970,7 @@ export const collections = {
41190
41970
  formatLayoutViolation,
41191
41971
  formatStarlightSingletonViolation,
41192
41972
  getLatestEligibleVersion,
41973
+ githubIssueTypeForTitle,
41193
41974
  githubWorkflowBundle,
41194
41975
  hasAnyDocsEmittingBundle,
41195
41976
  hasAnyDownstreamIssueKindBundle,
@@ -41247,12 +42028,18 @@ export const collections = {
41247
42028
  renderCustomDocSections,
41248
42029
  renderExtractApiProcedure,
41249
42030
  renderFocusSection,
42031
+ renderGithubIssueTypeSection,
42032
+ renderGithubIssueTypeSectionLines,
41250
42033
  renderIssueTemplatesBundleHook,
41251
42034
  renderIssueTemplatesCheckerScript,
41252
42035
  renderIssueTemplatesRuleContent,
41253
42036
  renderIssueTemplatesStarterPage,
42037
+ renderIssueTypeAssignmentBlanket,
42038
+ renderIssueTypeAssignmentStep,
41254
42039
  renderMeetingTypesSection,
41255
42040
  renderNextRequirementIdProcedure,
42041
+ renderPhaseTypeInvariantSection,
42042
+ renderPhaseTypeInvariantShellHelpers,
41256
42043
  renderPriorityRulesSection,
41257
42044
  renderProgressFileName,
41258
42045
  renderProgressFilePath,
@@ -41265,6 +42052,7 @@ export const collections = {
41265
42052
  renderScheduledTasksSection,
41266
42053
  renderScopeGateSection,
41267
42054
  renderScopeGateShellHelpers,
42055
+ renderSetIssueTypeFallbackLines,
41268
42056
  renderSharedEditingBundleHook,
41269
42057
  renderSharedEditingHelperScript,
41270
42058
  renderSharedEditingRuleContent,
@@ -41275,6 +42063,7 @@ export const collections = {
41275
42063
  renderStripToolArtifactTagsProcedure,
41276
42064
  renderTemporalFramingCheckerScript,
41277
42065
  renderTemporalFramingRuleContent,
42066
+ renderTitlePrefixTypeBullets,
41278
42067
  renderUnblockDependentsScript,
41279
42068
  renderUnblockDependentsSection,
41280
42069
  requirementsAnalystBundle,
@@ -41304,6 +42093,7 @@ export const collections = {
41304
42093
  resolveSkillEvals,
41305
42094
  resolveTemplateVariables,
41306
42095
  resolveTemporalFraming,
42096
+ resolveTypeLabelForLabels,
41307
42097
  resolveTypeScriptProjectOutdir,
41308
42098
  resolveUnblockDependents,
41309
42099
  runScan,
@@ -41313,6 +42103,7 @@ export const collections = {
41313
42103
  stripToolArtifactTagsProcedure,
41314
42104
  tsdocRecordToFindings,
41315
42105
  turborepoBundle,
42106
+ typeLabelForPhaseLabel,
41316
42107
  typescriptBundle,
41317
42108
  upstreamConfigulatorDocsBundle,
41318
42109
  validateAgentTierConfig,