@mstar-harness/cli 2.2.0 → 2.4.0

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.
Files changed (2) hide show
  1. package/dist/mstar-harness.js +1336 -541
  2. package/package.json +6 -5
@@ -2344,9 +2344,9 @@ var require_commander = __commonJS((exports) => {
2344
2344
  });
2345
2345
 
2346
2346
  // src/index.ts
2347
- import { execFileSync as execFileSync7 } from "child_process";
2348
- import fs7 from "fs";
2349
- import path11 from "path";
2347
+ import { execFileSync as execFileSync8 } from "child_process";
2348
+ import fs8 from "fs";
2349
+ import path12 from "path";
2350
2350
 
2351
2351
  // ../../node_modules/@inquirer/core/dist/lib/key.js
2352
2352
  var keybindings = ["emacs", "vim"];
@@ -2546,267 +2546,267 @@ function isUnicodeSupported() {
2546
2546
  var common = {
2547
2547
  circleQuestionMark: "(?)",
2548
2548
  questionMarkPrefix: "(?)",
2549
- square: "",
2550
- squareDarkShade: "",
2551
- squareMediumShade: "",
2552
- squareLightShade: "",
2553
- squareTop: "",
2554
- squareBottom: "",
2555
- squareLeft: "",
2556
- squareRight: "",
2557
- squareCenter: "",
2558
- bullet: "",
2559
- dot: "",
2560
- ellipsis: "",
2561
- pointerSmall: "",
2562
- triangleUp: "",
2563
- triangleUpSmall: "",
2564
- triangleDown: "",
2565
- triangleDownSmall: "",
2566
- triangleLeftSmall: "",
2567
- triangleRightSmall: "",
2568
- home: "",
2569
- heart: "",
2570
- musicNote: "",
2571
- musicNoteBeamed: "",
2572
- arrowUp: "",
2573
- arrowDown: "",
2574
- arrowLeft: "",
2575
- arrowRight: "",
2576
- arrowLeftRight: "",
2577
- arrowUpDown: "",
2578
- almostEqual: "",
2579
- notEqual: "",
2580
- lessOrEqual: "",
2581
- greaterOrEqual: "",
2582
- identical: "",
2583
- infinity: "",
2584
- subscriptZero: "",
2585
- subscriptOne: "",
2586
- subscriptTwo: "",
2587
- subscriptThree: "",
2588
- subscriptFour: "",
2589
- subscriptFive: "",
2590
- subscriptSix: "",
2591
- subscriptSeven: "",
2592
- subscriptEight: "",
2593
- subscriptNine: "",
2594
- oneHalf: "½",
2595
- oneThird: "",
2596
- oneQuarter: "¼",
2597
- oneFifth: "",
2598
- oneSixth: "",
2599
- oneEighth: "",
2600
- twoThirds: "",
2601
- twoFifths: "",
2602
- threeQuarters: "¾",
2603
- threeFifths: "",
2604
- threeEighths: "",
2605
- fourFifths: "",
2606
- fiveSixths: "",
2607
- fiveEighths: "",
2608
- sevenEighths: "",
2609
- line: "",
2610
- lineBold: "",
2611
- lineDouble: "",
2612
- lineDashed0: "",
2613
- lineDashed1: "",
2614
- lineDashed2: "",
2615
- lineDashed3: "",
2616
- lineDashed4: "",
2617
- lineDashed5: "",
2618
- lineDashed6: "",
2619
- lineDashed7: "",
2620
- lineDashed8: "",
2621
- lineDashed9: "",
2622
- lineDashed10: "",
2623
- lineDashed11: "",
2624
- lineDashed12: "",
2625
- lineDashed13: "",
2626
- lineDashed14: "",
2627
- lineDashed15: "",
2628
- lineVertical: "",
2629
- lineVerticalBold: "",
2630
- lineVerticalDouble: "",
2631
- lineVerticalDashed0: "",
2632
- lineVerticalDashed1: "",
2633
- lineVerticalDashed2: "",
2634
- lineVerticalDashed3: "",
2635
- lineVerticalDashed4: "",
2636
- lineVerticalDashed5: "",
2637
- lineVerticalDashed6: "",
2638
- lineVerticalDashed7: "",
2639
- lineVerticalDashed8: "",
2640
- lineVerticalDashed9: "",
2641
- lineVerticalDashed10: "",
2642
- lineVerticalDashed11: "",
2643
- lineDownLeft: "",
2644
- lineDownLeftArc: "",
2645
- lineDownBoldLeftBold: "",
2646
- lineDownBoldLeft: "",
2647
- lineDownLeftBold: "",
2648
- lineDownDoubleLeftDouble: "",
2649
- lineDownDoubleLeft: "",
2650
- lineDownLeftDouble: "",
2651
- lineDownRight: "",
2652
- lineDownRightArc: "",
2653
- lineDownBoldRightBold: "",
2654
- lineDownBoldRight: "",
2655
- lineDownRightBold: "",
2656
- lineDownDoubleRightDouble: "",
2657
- lineDownDoubleRight: "",
2658
- lineDownRightDouble: "",
2659
- lineUpLeft: "",
2660
- lineUpLeftArc: "",
2661
- lineUpBoldLeftBold: "",
2662
- lineUpBoldLeft: "",
2663
- lineUpLeftBold: "",
2664
- lineUpDoubleLeftDouble: "",
2665
- lineUpDoubleLeft: "",
2666
- lineUpLeftDouble: "",
2667
- lineUpRight: "",
2668
- lineUpRightArc: "",
2669
- lineUpBoldRightBold: "",
2670
- lineUpBoldRight: "",
2671
- lineUpRightBold: "",
2672
- lineUpDoubleRightDouble: "",
2673
- lineUpDoubleRight: "",
2674
- lineUpRightDouble: "",
2675
- lineUpDownLeft: "",
2676
- lineUpBoldDownBoldLeftBold: "",
2677
- lineUpBoldDownBoldLeft: "",
2678
- lineUpDownLeftBold: "",
2679
- lineUpBoldDownLeftBold: "",
2680
- lineUpDownBoldLeftBold: "",
2681
- lineUpDownBoldLeft: "",
2682
- lineUpBoldDownLeft: "",
2683
- lineUpDoubleDownDoubleLeftDouble: "",
2684
- lineUpDoubleDownDoubleLeft: "",
2685
- lineUpDownLeftDouble: "",
2686
- lineUpDownRight: "",
2687
- lineUpBoldDownBoldRightBold: "",
2688
- lineUpBoldDownBoldRight: "",
2689
- lineUpDownRightBold: "",
2690
- lineUpBoldDownRightBold: "",
2691
- lineUpDownBoldRightBold: "",
2692
- lineUpDownBoldRight: "",
2693
- lineUpBoldDownRight: "",
2694
- lineUpDoubleDownDoubleRightDouble: "",
2695
- lineUpDoubleDownDoubleRight: "",
2696
- lineUpDownRightDouble: "",
2697
- lineDownLeftRight: "",
2698
- lineDownBoldLeftBoldRightBold: "",
2699
- lineDownLeftBoldRightBold: "",
2700
- lineDownBoldLeftRight: "",
2701
- lineDownBoldLeftBoldRight: "",
2702
- lineDownBoldLeftRightBold: "",
2703
- lineDownLeftRightBold: "",
2704
- lineDownLeftBoldRight: "",
2705
- lineDownDoubleLeftDoubleRightDouble: "",
2706
- lineDownDoubleLeftRight: "",
2707
- lineDownLeftDoubleRightDouble: "",
2708
- lineUpLeftRight: "",
2709
- lineUpBoldLeftBoldRightBold: "",
2710
- lineUpLeftBoldRightBold: "",
2711
- lineUpBoldLeftRight: "",
2712
- lineUpBoldLeftBoldRight: "",
2713
- lineUpBoldLeftRightBold: "",
2714
- lineUpLeftRightBold: "",
2715
- lineUpLeftBoldRight: "",
2716
- lineUpDoubleLeftDoubleRightDouble: "",
2717
- lineUpDoubleLeftRight: "",
2718
- lineUpLeftDoubleRightDouble: "",
2719
- lineUpDownLeftRight: "",
2720
- lineUpBoldDownBoldLeftBoldRightBold: "",
2721
- lineUpDownBoldLeftBoldRightBold: "",
2722
- lineUpBoldDownLeftBoldRightBold: "",
2723
- lineUpBoldDownBoldLeftRightBold: "",
2724
- lineUpBoldDownBoldLeftBoldRight: "",
2725
- lineUpBoldDownLeftRight: "",
2726
- lineUpDownBoldLeftRight: "",
2727
- lineUpDownLeftBoldRight: "",
2728
- lineUpDownLeftRightBold: "",
2729
- lineUpBoldDownBoldLeftRight: "",
2730
- lineUpDownLeftBoldRightBold: "",
2731
- lineUpBoldDownLeftBoldRight: "",
2732
- lineUpBoldDownLeftRightBold: "",
2733
- lineUpDownBoldLeftBoldRight: "",
2734
- lineUpDownBoldLeftRightBold: "",
2735
- lineUpDoubleDownDoubleLeftDoubleRightDouble: "",
2736
- lineUpDoubleDownDoubleLeftRight: "",
2737
- lineUpDownLeftDoubleRightDouble: "",
2738
- lineCross: "",
2739
- lineBackslash: "",
2740
- lineSlash: ""
2549
+ square: "\u2588",
2550
+ squareDarkShade: "\u2593",
2551
+ squareMediumShade: "\u2592",
2552
+ squareLightShade: "\u2591",
2553
+ squareTop: "\u2580",
2554
+ squareBottom: "\u2584",
2555
+ squareLeft: "\u258c",
2556
+ squareRight: "\u2590",
2557
+ squareCenter: "\u25a0",
2558
+ bullet: "\u25cf",
2559
+ dot: "\u2024",
2560
+ ellipsis: "\u2026",
2561
+ pointerSmall: "\u203a",
2562
+ triangleUp: "\u25b2",
2563
+ triangleUpSmall: "\u25b4",
2564
+ triangleDown: "\u25bc",
2565
+ triangleDownSmall: "\u25be",
2566
+ triangleLeftSmall: "\u25c2",
2567
+ triangleRightSmall: "\u25b8",
2568
+ home: "\u2302",
2569
+ heart: "\u2665",
2570
+ musicNote: "\u266a",
2571
+ musicNoteBeamed: "\u266b",
2572
+ arrowUp: "\u2191",
2573
+ arrowDown: "\u2193",
2574
+ arrowLeft: "\u2190",
2575
+ arrowRight: "\u2192",
2576
+ arrowLeftRight: "\u2194",
2577
+ arrowUpDown: "\u2195",
2578
+ almostEqual: "\u2248",
2579
+ notEqual: "\u2260",
2580
+ lessOrEqual: "\u2264",
2581
+ greaterOrEqual: "\u2265",
2582
+ identical: "\u2261",
2583
+ infinity: "\u221e",
2584
+ subscriptZero: "\u2080",
2585
+ subscriptOne: "\u2081",
2586
+ subscriptTwo: "\u2082",
2587
+ subscriptThree: "\u2083",
2588
+ subscriptFour: "\u2084",
2589
+ subscriptFive: "\u2085",
2590
+ subscriptSix: "\u2086",
2591
+ subscriptSeven: "\u2087",
2592
+ subscriptEight: "\u2088",
2593
+ subscriptNine: "\u2089",
2594
+ oneHalf: "\u00bd",
2595
+ oneThird: "\u2153",
2596
+ oneQuarter: "\u00bc",
2597
+ oneFifth: "\u2155",
2598
+ oneSixth: "\u2159",
2599
+ oneEighth: "\u215b",
2600
+ twoThirds: "\u2154",
2601
+ twoFifths: "\u2156",
2602
+ threeQuarters: "\u00be",
2603
+ threeFifths: "\u2157",
2604
+ threeEighths: "\u215c",
2605
+ fourFifths: "\u2158",
2606
+ fiveSixths: "\u215a",
2607
+ fiveEighths: "\u215d",
2608
+ sevenEighths: "\u215e",
2609
+ line: "\u2500",
2610
+ lineBold: "\u2501",
2611
+ lineDouble: "\u2550",
2612
+ lineDashed0: "\u2504",
2613
+ lineDashed1: "\u2505",
2614
+ lineDashed2: "\u2508",
2615
+ lineDashed3: "\u2509",
2616
+ lineDashed4: "\u254c",
2617
+ lineDashed5: "\u254d",
2618
+ lineDashed6: "\u2574",
2619
+ lineDashed7: "\u2576",
2620
+ lineDashed8: "\u2578",
2621
+ lineDashed9: "\u257a",
2622
+ lineDashed10: "\u257c",
2623
+ lineDashed11: "\u257e",
2624
+ lineDashed12: "\u2212",
2625
+ lineDashed13: "\u2013",
2626
+ lineDashed14: "\u2010",
2627
+ lineDashed15: "\u2043",
2628
+ lineVertical: "\u2502",
2629
+ lineVerticalBold: "\u2503",
2630
+ lineVerticalDouble: "\u2551",
2631
+ lineVerticalDashed0: "\u2506",
2632
+ lineVerticalDashed1: "\u2507",
2633
+ lineVerticalDashed2: "\u250a",
2634
+ lineVerticalDashed3: "\u250b",
2635
+ lineVerticalDashed4: "\u254e",
2636
+ lineVerticalDashed5: "\u254f",
2637
+ lineVerticalDashed6: "\u2575",
2638
+ lineVerticalDashed7: "\u2577",
2639
+ lineVerticalDashed8: "\u2579",
2640
+ lineVerticalDashed9: "\u257b",
2641
+ lineVerticalDashed10: "\u257d",
2642
+ lineVerticalDashed11: "\u257f",
2643
+ lineDownLeft: "\u2510",
2644
+ lineDownLeftArc: "\u256e",
2645
+ lineDownBoldLeftBold: "\u2513",
2646
+ lineDownBoldLeft: "\u2512",
2647
+ lineDownLeftBold: "\u2511",
2648
+ lineDownDoubleLeftDouble: "\u2557",
2649
+ lineDownDoubleLeft: "\u2556",
2650
+ lineDownLeftDouble: "\u2555",
2651
+ lineDownRight: "\u250c",
2652
+ lineDownRightArc: "\u256d",
2653
+ lineDownBoldRightBold: "\u250f",
2654
+ lineDownBoldRight: "\u250e",
2655
+ lineDownRightBold: "\u250d",
2656
+ lineDownDoubleRightDouble: "\u2554",
2657
+ lineDownDoubleRight: "\u2553",
2658
+ lineDownRightDouble: "\u2552",
2659
+ lineUpLeft: "\u2518",
2660
+ lineUpLeftArc: "\u256f",
2661
+ lineUpBoldLeftBold: "\u251b",
2662
+ lineUpBoldLeft: "\u251a",
2663
+ lineUpLeftBold: "\u2519",
2664
+ lineUpDoubleLeftDouble: "\u255d",
2665
+ lineUpDoubleLeft: "\u255c",
2666
+ lineUpLeftDouble: "\u255b",
2667
+ lineUpRight: "\u2514",
2668
+ lineUpRightArc: "\u2570",
2669
+ lineUpBoldRightBold: "\u2517",
2670
+ lineUpBoldRight: "\u2516",
2671
+ lineUpRightBold: "\u2515",
2672
+ lineUpDoubleRightDouble: "\u255a",
2673
+ lineUpDoubleRight: "\u2559",
2674
+ lineUpRightDouble: "\u2558",
2675
+ lineUpDownLeft: "\u2524",
2676
+ lineUpBoldDownBoldLeftBold: "\u252b",
2677
+ lineUpBoldDownBoldLeft: "\u2528",
2678
+ lineUpDownLeftBold: "\u2525",
2679
+ lineUpBoldDownLeftBold: "\u2529",
2680
+ lineUpDownBoldLeftBold: "\u252a",
2681
+ lineUpDownBoldLeft: "\u2527",
2682
+ lineUpBoldDownLeft: "\u2526",
2683
+ lineUpDoubleDownDoubleLeftDouble: "\u2563",
2684
+ lineUpDoubleDownDoubleLeft: "\u2562",
2685
+ lineUpDownLeftDouble: "\u2561",
2686
+ lineUpDownRight: "\u251c",
2687
+ lineUpBoldDownBoldRightBold: "\u2523",
2688
+ lineUpBoldDownBoldRight: "\u2520",
2689
+ lineUpDownRightBold: "\u251d",
2690
+ lineUpBoldDownRightBold: "\u2521",
2691
+ lineUpDownBoldRightBold: "\u2522",
2692
+ lineUpDownBoldRight: "\u251f",
2693
+ lineUpBoldDownRight: "\u251e",
2694
+ lineUpDoubleDownDoubleRightDouble: "\u2560",
2695
+ lineUpDoubleDownDoubleRight: "\u255f",
2696
+ lineUpDownRightDouble: "\u255e",
2697
+ lineDownLeftRight: "\u252c",
2698
+ lineDownBoldLeftBoldRightBold: "\u2533",
2699
+ lineDownLeftBoldRightBold: "\u252f",
2700
+ lineDownBoldLeftRight: "\u2530",
2701
+ lineDownBoldLeftBoldRight: "\u2531",
2702
+ lineDownBoldLeftRightBold: "\u2532",
2703
+ lineDownLeftRightBold: "\u252e",
2704
+ lineDownLeftBoldRight: "\u252d",
2705
+ lineDownDoubleLeftDoubleRightDouble: "\u2566",
2706
+ lineDownDoubleLeftRight: "\u2565",
2707
+ lineDownLeftDoubleRightDouble: "\u2564",
2708
+ lineUpLeftRight: "\u2534",
2709
+ lineUpBoldLeftBoldRightBold: "\u253b",
2710
+ lineUpLeftBoldRightBold: "\u2537",
2711
+ lineUpBoldLeftRight: "\u2538",
2712
+ lineUpBoldLeftBoldRight: "\u2539",
2713
+ lineUpBoldLeftRightBold: "\u253a",
2714
+ lineUpLeftRightBold: "\u2536",
2715
+ lineUpLeftBoldRight: "\u2535",
2716
+ lineUpDoubleLeftDoubleRightDouble: "\u2569",
2717
+ lineUpDoubleLeftRight: "\u2568",
2718
+ lineUpLeftDoubleRightDouble: "\u2567",
2719
+ lineUpDownLeftRight: "\u253c",
2720
+ lineUpBoldDownBoldLeftBoldRightBold: "\u254b",
2721
+ lineUpDownBoldLeftBoldRightBold: "\u2548",
2722
+ lineUpBoldDownLeftBoldRightBold: "\u2547",
2723
+ lineUpBoldDownBoldLeftRightBold: "\u254a",
2724
+ lineUpBoldDownBoldLeftBoldRight: "\u2549",
2725
+ lineUpBoldDownLeftRight: "\u2540",
2726
+ lineUpDownBoldLeftRight: "\u2541",
2727
+ lineUpDownLeftBoldRight: "\u253d",
2728
+ lineUpDownLeftRightBold: "\u253e",
2729
+ lineUpBoldDownBoldLeftRight: "\u2542",
2730
+ lineUpDownLeftBoldRightBold: "\u253f",
2731
+ lineUpBoldDownLeftBoldRight: "\u2543",
2732
+ lineUpBoldDownLeftRightBold: "\u2544",
2733
+ lineUpDownBoldLeftBoldRight: "\u2545",
2734
+ lineUpDownBoldLeftRightBold: "\u2546",
2735
+ lineUpDoubleDownDoubleLeftDoubleRightDouble: "\u256c",
2736
+ lineUpDoubleDownDoubleLeftRight: "\u256b",
2737
+ lineUpDownLeftDoubleRightDouble: "\u256a",
2738
+ lineCross: "\u2573",
2739
+ lineBackslash: "\u2572",
2740
+ lineSlash: "\u2571"
2741
2741
  };
2742
2742
  var specialMainSymbols = {
2743
- tick: "",
2744
- info: "",
2745
- warning: "",
2746
- cross: "",
2747
- squareSmall: "",
2748
- squareSmallFilled: "",
2749
- circle: "",
2750
- circleFilled: "",
2751
- circleDotted: "",
2752
- circleDouble: "",
2753
- circleCircle: "",
2754
- circleCross: "",
2755
- circlePipe: "",
2756
- radioOn: "",
2757
- radioOff: "",
2758
- checkboxOn: "",
2759
- checkboxOff: "",
2760
- checkboxCircleOn: "",
2761
- checkboxCircleOff: "",
2762
- pointer: "",
2763
- triangleUpOutline: "",
2764
- triangleLeft: "",
2765
- triangleRight: "",
2766
- lozenge: "",
2767
- lozengeOutline: "",
2768
- hamburger: "",
2769
- smiley: "",
2770
- mustache: "",
2771
- star: "",
2772
- play: "",
2773
- nodejs: "",
2774
- oneSeventh: "",
2775
- oneNinth: "",
2776
- oneTenth: ""
2743
+ tick: "\u2714",
2744
+ info: "\u2139",
2745
+ warning: "\u26a0",
2746
+ cross: "\u2718",
2747
+ squareSmall: "\u25fb",
2748
+ squareSmallFilled: "\u25fc",
2749
+ circle: "\u25ef",
2750
+ circleFilled: "\u25c9",
2751
+ circleDotted: "\u25cc",
2752
+ circleDouble: "\u25ce",
2753
+ circleCircle: "\u24de",
2754
+ circleCross: "\u24e7",
2755
+ circlePipe: "\u24be",
2756
+ radioOn: "\u25c9",
2757
+ radioOff: "\u25ef",
2758
+ checkboxOn: "\u2612",
2759
+ checkboxOff: "\u2610",
2760
+ checkboxCircleOn: "\u24e7",
2761
+ checkboxCircleOff: "\u24be",
2762
+ pointer: "\u276f",
2763
+ triangleUpOutline: "\u25b3",
2764
+ triangleLeft: "\u25c0",
2765
+ triangleRight: "\u25b6",
2766
+ lozenge: "\u25c6",
2767
+ lozengeOutline: "\u25c7",
2768
+ hamburger: "\u2630",
2769
+ smiley: "\u32e1",
2770
+ mustache: "\u0df4",
2771
+ star: "\u2605",
2772
+ play: "\u25b6",
2773
+ nodejs: "\u2b22",
2774
+ oneSeventh: "\u2150",
2775
+ oneNinth: "\u2151",
2776
+ oneTenth: "\u2152"
2777
2777
  };
2778
2778
  var specialFallbackSymbols = {
2779
- tick: "",
2779
+ tick: "\u221a",
2780
2780
  info: "i",
2781
- warning: "",
2782
- cross: "×",
2783
- squareSmall: "",
2784
- squareSmallFilled: "",
2781
+ warning: "\u203c",
2782
+ cross: "\u00d7",
2783
+ squareSmall: "\u25a1",
2784
+ squareSmallFilled: "\u25a0",
2785
2785
  circle: "( )",
2786
2786
  circleFilled: "(*)",
2787
2787
  circleDotted: "( )",
2788
2788
  circleDouble: "( )",
2789
- circleCircle: "()",
2790
- circleCross: "(×)",
2791
- circlePipe: "()",
2789
+ circleCircle: "(\u25cb)",
2790
+ circleCross: "(\u00d7)",
2791
+ circlePipe: "(\u2502)",
2792
2792
  radioOn: "(*)",
2793
2793
  radioOff: "( )",
2794
- checkboxOn: "[×]",
2794
+ checkboxOn: "[\u00d7]",
2795
2795
  checkboxOff: "[ ]",
2796
- checkboxCircleOn: "(×)",
2796
+ checkboxCircleOn: "(\u00d7)",
2797
2797
  checkboxCircleOff: "( )",
2798
2798
  pointer: ">",
2799
- triangleUpOutline: "",
2800
- triangleLeft: "",
2801
- triangleRight: "",
2802
- lozenge: "",
2803
- lozengeOutline: "",
2804
- hamburger: "",
2805
- smiley: "",
2806
- mustache: "┌─┐",
2807
- star: "",
2808
- play: "",
2809
- nodejs: "",
2799
+ triangleUpOutline: "\u2206",
2800
+ triangleLeft: "\u25c4",
2801
+ triangleRight: "\u25ba",
2802
+ lozenge: "\u2666",
2803
+ lozengeOutline: "\u25ca",
2804
+ hamburger: "\u2261",
2805
+ smiley: "\u263a",
2806
+ mustache: "\u250c\u2500\u2510",
2807
+ star: "\u2736",
2808
+ play: "\u25ba",
2809
+ nodejs: "\u2666",
2810
2810
  oneSeventh: "1/7",
2811
2811
  oneNinth: "1/9",
2812
2812
  oneTenth: "1/10"
@@ -2832,7 +2832,7 @@ var defaultTheme = {
2832
2832
  },
2833
2833
  spinner: {
2834
2834
  interval: 80,
2835
- frames: ["", "", "", "", "", "", "", "", "", ""].map((frame) => styleText("yellow", frame))
2835
+ frames: ["\u280b", "\u2819", "\u2839", "\u2838", "\u283c", "\u2834", "\u2826", "\u2827", "\u2807", "\u280f"].map((frame) => styleText("yellow", frame))
2836
2836
  },
2837
2837
  keybindings: [],
2838
2838
  style: {
@@ -3078,7 +3078,7 @@ var dist_default3 = fastStringWidth;
3078
3078
 
3079
3079
  // ../../node_modules/fast-wrap-ansi/lib/main.js
3080
3080
  var ESC = "\x1B";
3081
- var CSI = "›";
3081
+ var CSI = "\u009b";
3082
3082
  var END_CODE = 39;
3083
3083
  var ANSI_ESCAPE_BELL = "\x07";
3084
3084
  var ANSI_CSI = "[";
@@ -3808,7 +3808,7 @@ var selectTheme = {
3808
3808
  style: {
3809
3809
  disabled: (text) => styleText3("dim", text),
3810
3810
  description: (text) => styleText3("cyan", text),
3811
- keysHelpTip: (keys) => keys.map(([key, action]) => `${styleText3("bold", key)} ${styleText3("dim", action)}`).join(styleText3("dim", " "))
3811
+ keysHelpTip: (keys) => keys.map(([key, action]) => `${styleText3("bold", key)} ${styleText3("dim", action)}`).join(styleText3("dim", " \u2022 "))
3812
3812
  },
3813
3813
  i18n: { disabledError: "This option is disabled and cannot be selected." },
3814
3814
  indexMode: "hidden"
@@ -3933,8 +3933,8 @@ var dist_default4 = createPrompt((config, done) => {
3933
3933
  }, []);
3934
3934
  const message = theme.style.message(config.message, status);
3935
3935
  const helpLine = theme.style.keysHelpTip([
3936
- ["↑↓", "navigate"],
3937
- ["", "select"]
3936
+ ["\u2191\u2193", "navigate"],
3937
+ ["\u23ce", "select"]
3938
3938
  ]);
3939
3939
  let separatorCount = 0;
3940
3940
  const page = usePagination({
@@ -4016,6 +4016,8 @@ import { mkdirSync as mkdirSync5, readdirSync as readdirSync4, readFileSync as r
4016
4016
  import { join as join7, resolve as resolve7 } from "node:path";
4017
4017
  import { existsSync as existsSync5, readdirSync as readdirSync5, readFileSync as readFileSync7 } from "node:fs";
4018
4018
  import { basename as basename4, isAbsolute as isAbsolute5, join as join8, relative as relative2, resolve as resolve8, sep } from "node:path";
4019
+ import { existsSync as existsSync6 } from "node:fs";
4020
+ import { join as join9 } from "node:path";
4019
4021
  var SEVERITY_ORDER = ["critical", "high", "medium", "low", "nit"];
4020
4022
  function readJson(filePath) {
4021
4023
  if (!existsSync(filePath))
@@ -4109,7 +4111,7 @@ function resolveSpecsDir(harnessDir, opts = {}) {
4109
4111
  }
4110
4112
  function assertSafePathComponent(value, what) {
4111
4113
  if (value === "" || value === "." || value === ".." || !/^[A-Za-z0-9._-]+$/.test(value)) {
4112
- throw new Error(`${what} must be a single safe path component ([A-Za-z0-9._-]+; not "", ".", "..", or containing "/" or "\\") got ${JSON.stringify(value)}`);
4114
+ throw new Error(`${what} must be a single safe path component ([A-Za-z0-9._-]+; not "", ".", "..", or containing "/" or "\\") \u2014 got ${JSON.stringify(value)}`);
4113
4115
  }
4114
4116
  }
4115
4117
  function resolveSddDir(harnessDir, planId) {
@@ -4126,7 +4128,7 @@ var GITIGNORE_SNIPPET = `# Morning Star harness (.mstar/)
4126
4128
  !.mstar/specs/
4127
4129
  !.mstar/specs/**
4128
4130
  `;
4129
- var GITIGNORE_SNIPPET_AGENTS = `# Morning Star harness (.agents/) legacy
4131
+ var GITIGNORE_SNIPPET_AGENTS = `# Morning Star harness (.agents/) \u2014 legacy
4130
4132
  # Default-ignore everything under .agents/, then re-include the tracked results.
4131
4133
  .agents/**
4132
4134
  !.agents/AGENTS.md
@@ -4161,9 +4163,49 @@ function hasFiles(dir) {
4161
4163
  return false;
4162
4164
  }
4163
4165
  }
4166
+ function isPlainObject2(value) {
4167
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4168
+ }
4169
+ function violation(severity, code, message, fix) {
4170
+ return { ok: false, severity, code, message, fix };
4171
+ }
4172
+ function validateNonEmptyString(violations, value, field, missingCode, invalidCode) {
4173
+ if (value === undefined) {
4174
+ violations.push(violation("high", missingCode, `missing required field: ${field}`));
4175
+ } else if (typeof value !== "string" || value.trim() === "") {
4176
+ violations.push(violation("medium", invalidCode, `${field} must be a non-empty string`));
4177
+ }
4178
+ }
4164
4179
  var DATE_PART = String.raw`\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])`;
4165
4180
  var RFC3339_Z_RE = new RegExp(String.raw`^${DATE_PART}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$`);
4166
4181
  var DATE_ONLY_RE = new RegExp(String.raw`^${DATE_PART}$`);
4182
+ function isValidClaimedAt(value) {
4183
+ return typeof value === "string" && (RFC3339_Z_RE.test(value) || DATE_ONLY_RE.test(value));
4184
+ }
4185
+ function validateIntegrationMergeLease(lease) {
4186
+ const violations = [];
4187
+ if (!isPlainObject2(lease)) {
4188
+ return {
4189
+ ok: false,
4190
+ violations: [
4191
+ violation("high", "lease.merge-lease.invalid", "integration_merge_lease must be an object \u2014 absent means unclaimed; null and tombstone objects are invalid; writers delete the key on release")
4192
+ ]
4193
+ };
4194
+ }
4195
+ validateNonEmptyString(violations, lease.holder, "holder", "lease.merge-lease.missing-holder", "lease.merge-lease.invalid-holder");
4196
+ if (lease.claimed_at === undefined) {
4197
+ violations.push(violation("high", "lease.merge-lease.missing-claimed-at", "missing required field: claimed_at"));
4198
+ } else if (!isValidClaimedAt(lease.claimed_at)) {
4199
+ violations.push(violation("medium", "lease.merge-lease.invalid-claimed-at", "claimed_at must be an RFC 3339 UTC timestamp with explicit Z (e.g. 2026-07-22T04:00:00Z) or a YYYY-MM-DD date"));
4200
+ }
4201
+ validateNonEmptyString(violations, lease.plan_id, "plan_id", "lease.merge-lease.missing-plan-id", "lease.merge-lease.invalid-plan-id");
4202
+ validateNonEmptyString(violations, lease.source_branch, "source_branch", "lease.merge-lease.missing-source-branch", "lease.merge-lease.invalid-source-branch");
4203
+ validateNonEmptyString(violations, lease.target_branch, "target_branch", "lease.merge-lease.missing-target-branch", "lease.merge-lease.invalid-target-branch");
4204
+ if (lease.session_label !== undefined && typeof lease.session_label !== "string") {
4205
+ violations.push(violation("medium", "lease.merge-lease.invalid-session-label", "session_label must be a string (display only \u2014 never used for ownership comparison)"));
4206
+ }
4207
+ return { ok: violations.length === 0, violations };
4208
+ }
4167
4209
  var STATUS_WRITE_LOCKDIR = ".status-write.lockdir";
4168
4210
  var LOCKDIR_HOLDER_PID = "holder.pid";
4169
4211
  var heldLockDirs = new AsyncLocalStorage2;
@@ -4171,7 +4213,7 @@ async function withStatusWriteLock(statusPath, fn, opts = {}) {
4171
4213
  const lockDir = join3(dirname3(resolve3(statusPath)), STATUS_WRITE_LOCKDIR);
4172
4214
  const held = heldLockDirs.getStore();
4173
4215
  if (held !== undefined && held.has(lockDir)) {
4174
- throw new Error(`${lockDir} is already held by this process in this async context withStatusWriteLock is not reentrant; a nested acquisition on the same status.json is a bug`);
4216
+ throw new Error(`${lockDir} is already held by this process in this async context \u2014 withStatusWriteLock is not reentrant; a nested acquisition on the same status.json is a bug`);
4175
4217
  }
4176
4218
  const timeoutMs = opts.timeoutMs ?? 30000;
4177
4219
  const pollMs = opts.pollMs ?? 25;
@@ -4187,7 +4229,7 @@ async function withStatusWriteLock(statusPath, fn, opts = {}) {
4187
4229
  if (error.code !== "EEXIST")
4188
4230
  throw error;
4189
4231
  if (Date.now() >= deadline) {
4190
- throw new Error(`${lockDir} already exists another writer holds the status write lock; Blocked (same-host exclusive lock; status-and-residuals.md § Same-host exclusive write lock). ` + `Recovery: remove ${lockDir} if no writer is alive (holder.pid inside names the acquiring process)`);
4232
+ throw new Error(`${lockDir} already exists \u2014 another writer holds the status write lock; Blocked (same-host exclusive lock; status-and-residuals.md \u00a7 Same-host exclusive write lock). ` + `Recovery: remove ${lockDir} if no writer is alive (holder.pid inside names the acquiring process)`);
4191
4233
  }
4192
4234
  await sleep(pollMs);
4193
4235
  }
@@ -4212,7 +4254,7 @@ async function withStatusWriteLock(statusPath, fn, opts = {}) {
4212
4254
  } catch {}
4213
4255
  }
4214
4256
  }
4215
- var BRANCH_FORMS_HINT = '"Working branch: <existing>" | "Working branch: create <new> from <base>" | "Branch policy: direct on <branch> <reason>"';
4257
+ var BRANCH_FORMS_HINT = '"Working branch: <existing>" | "Working branch: create <new> from <base>" | "Branch policy: direct on <branch> \u2014 <reason>"';
4216
4258
  var REQUIRED_FIELDS = [
4217
4259
  { key: "executeAs", label: "Execute as", code: "execute-as" },
4218
4260
  { key: "delegation", label: "Delegation", code: "delegation" },
@@ -4279,7 +4321,7 @@ function parseAssignmentBranchForms(assignmentText) {
4279
4321
  if (fields.branchPolicy !== undefined && fields.branchPolicy !== "") {
4280
4322
  const direct = fields.branchPolicy.match(/^direct\s+on\s+(\S+)/i);
4281
4323
  if (direct) {
4282
- const strict = fields.branchPolicy.match(/^direct\s+on\s+(\S+)(?:\s*(?:[—–]|--|-)\s*(.+))?$/);
4324
+ const strict = fields.branchPolicy.match(/^direct\s+on\s+(\S+)(?:\s*(?:[\u2014\u2013]|--|-)\s*(.+))?$/);
4283
4325
  forms.directOn = { branch: direct[1].trim(), reason: strict ? (strict[2] ?? "").trim() : "" };
4284
4326
  }
4285
4327
  }
@@ -4308,7 +4350,7 @@ function validateAssignmentFields(assignmentText, opts = {}) {
4308
4350
  if (formCount === 0) {
4309
4351
  violations.push(violation2("high", "assignment.field.branch-missing", "writable assignment must contain exactly one branch form", `add exactly one of: ${BRANCH_FORMS_HINT}`));
4310
4352
  } else if (formCount > 1) {
4311
- violations.push(violation2("high", "assignment.field.branch-multiple", `writable assignment contains ${formCount} branch forms (Working branch + Branch policy) exactly one required`, `keep exactly one of: ${BRANCH_FORMS_HINT}`));
4353
+ violations.push(violation2("high", "assignment.field.branch-multiple", `writable assignment contains ${formCount} branch forms (Working branch + Branch policy) \u2014 exactly one required`, `keep exactly one of: ${BRANCH_FORMS_HINT}`));
4312
4354
  } else if (workingPresent) {
4313
4355
  const create = forms.createForm;
4314
4356
  if (create !== undefined && (create.base === undefined || create.base.trim() === "" || create.name.trim() === "")) {
@@ -4317,9 +4359,9 @@ function validateAssignmentFields(assignmentText, opts = {}) {
4317
4359
  } else if (policyPresent) {
4318
4360
  const direct = forms.directOn;
4319
4361
  if (direct === undefined) {
4320
- violations.push(violation2("high", "assignment.field.branch-policy-missing-branch", `unparseable Branch policy: "${fields.branchPolicy}" (expected "direct on <branch> <reason>")`, "start the field with `direct on <branch>`"));
4362
+ violations.push(violation2("high", "assignment.field.branch-policy-missing-branch", `unparseable Branch policy: "${fields.branchPolicy}" (expected "direct on <branch> \u2014 <reason>")`, "start the field with `direct on <branch>`"));
4321
4363
  } else if (direct.reason === "") {
4322
- violations.push(violation2("high", "assignment.field.branch-policy-missing-reason", `Branch policy "direct on ${direct.branch}" is missing the reason`, 'append " <reason>" after the branch name'));
4364
+ violations.push(violation2("high", "assignment.field.branch-policy-missing-reason", `Branch policy "direct on ${direct.branch}" is missing the reason`, 'append "\u2014 <reason>" after the branch name'));
4323
4365
  }
4324
4366
  }
4325
4367
  }
@@ -4330,7 +4372,7 @@ function assertDefaultBranchProtected(branch, opts = {}) {
4330
4372
  const violations = [];
4331
4373
  const normalized = branch.trim();
4332
4374
  if (normalized !== "" && defaultBranches.includes(normalized) && opts.directOnException !== true) {
4333
- violations.push(violation2("high", "dispatch.default-branch.protected", `writable work on default protected branch "${normalized}" requires an explicit direct-on exception`, `add "Branch policy: direct on ${normalized} <reason>" to the Assignment, or use a feature branch`));
4375
+ violations.push(violation2("high", "dispatch.default-branch.protected", `writable work on default protected branch "${normalized}" requires an explicit direct-on exception`, `add "Branch policy: direct on ${normalized} \u2014 <reason>" to the Assignment, or use a feature branch`));
4334
4376
  }
4335
4377
  return { ok: violations.length === 0, violations };
4336
4378
  }
@@ -4347,9 +4389,9 @@ function executionModeToN(executionMode, opts = {}) {
4347
4389
  } else if (mode === "targeted") {
4348
4390
  const seats = [...new Set((opts.seats ?? []).map((s) => s.trim()).filter((s) => s !== ""))];
4349
4391
  if (seats.length === 0) {
4350
- violations.push(violation2("high", "dispatch.execution-mode.missing-seats", 'execution mode "targeted" requires listed reviewer seats', 'add "QC re-review: targeted reviewers: <role-id>, " to the Assignment and pass the seats'));
4392
+ violations.push(violation2("high", "dispatch.execution-mode.missing-seats", 'execution mode "targeted" requires listed reviewer seats', 'add "QC re-review: targeted \u2014 reviewers: <role-id>, \u2026" to the Assignment and pass the seats'));
4351
4393
  } else if (seats.length > 3) {
4352
- violations.push(violation2("high", "dispatch.execution-mode.too-many-seats", `execution mode "targeted" lists ${seats.length} reviewer seats at most 3 (targeted re-review seats are the tri seats, N = 1–3)`, "list at most three reviewer seats for the targeted re-review"));
4394
+ violations.push(violation2("high", "dispatch.execution-mode.too-many-seats", `execution mode "targeted" lists ${seats.length} reviewer seats \u2014 at most 3 (targeted re-review seats are the tri seats, N = 1\u20133)`, "list at most three reviewer seats for the targeted re-review"));
4353
4395
  } else {
4354
4396
  n = seats.length;
4355
4397
  }
@@ -4376,7 +4418,8 @@ var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
4376
4418
  var PLAN_STATUSES = ["Todo", "InProgress", "InReview", "Blocked", "Done"];
4377
4419
  var RESIDUAL_DECISIONS = ["defer", "accept", "risk-accepted"];
4378
4420
  var RESIDUAL_LIFECYCLES = ["open", "resolved", "waived", "superseded", "duplicate"];
4379
- function isPlainObject2(value) {
4421
+ var ROLLUP_FIELDS = ["total_open", "by_severity", "by_target", "by_plan"];
4422
+ function isPlainObject22(value) {
4380
4423
  return typeof value === "object" && value !== null && !Array.isArray(value);
4381
4424
  }
4382
4425
  function violation3(severity, code, message, fix) {
@@ -4388,6 +4431,13 @@ function todayString() {
4388
4431
  const day = String(now.getDate()).padStart(2, "0");
4389
4432
  return `${now.getFullYear()}-${month}-${day}`;
4390
4433
  }
4434
+ function normalizeSeverity(value) {
4435
+ if (value === "warning")
4436
+ return "low";
4437
+ if (value === null || value === "")
4438
+ return "medium";
4439
+ return value;
4440
+ }
4391
4441
  function isOpenResidual(entry) {
4392
4442
  const lifecycle = entry.lifecycle;
4393
4443
  const effective = lifecycle === false || lifecycle === null || lifecycle === undefined ? "open" : lifecycle;
@@ -4402,7 +4452,7 @@ function validateNonEmptyString2(violations, value, field, missingCode, invalidC
4402
4452
  }
4403
4453
  function validatePlanRow(row) {
4404
4454
  const violations = [];
4405
- if (!isPlainObject2(row)) {
4455
+ if (!isPlainObject22(row)) {
4406
4456
  return { ok: false, violations: [violation3("high", "status.plan-row.invalid", "plan row must be an object")] };
4407
4457
  }
4408
4458
  const { id, plan_id: planId, title, file, status, metadata, execution_lease } = row;
@@ -4416,7 +4466,7 @@ function validatePlanRow(row) {
4416
4466
  validateNonEmptyString2(violations, planId, "plan_id", "status.plan-row.missing-plan-id", "status.plan-row.invalid-plan-id");
4417
4467
  }
4418
4468
  if (id !== undefined && planId !== undefined && id !== planId) {
4419
- violations.push(violation3("medium", "status.plan-row.dual-id", "row has both id and plan_id with different values write one canonical key (prefer id)"));
4469
+ violations.push(violation3("medium", "status.plan-row.dual-id", "row has both id and plan_id with different values \u2014 write one canonical key (prefer id)"));
4420
4470
  }
4421
4471
  }
4422
4472
  validateNonEmptyString2(violations, title, "title", "status.plan-row.missing-title", "status.plan-row.invalid-title");
@@ -4424,22 +4474,22 @@ function validatePlanRow(row) {
4424
4474
  if (status === undefined) {
4425
4475
  violations.push(violation3("high", "status.plan-row.missing-status", "missing required field: status"));
4426
4476
  } else if (typeof status !== "string" || !PLAN_STATUSES.includes(status)) {
4427
- violations.push(violation3("medium", "status.plan-row.invalid-status", `status must be one of ${PLAN_STATUSES.join(" | ")} got ${JSON.stringify(status)}`));
4477
+ violations.push(violation3("medium", "status.plan-row.invalid-status", `status must be one of ${PLAN_STATUSES.join(" | ")} \u2014 got ${JSON.stringify(status)}`));
4428
4478
  }
4429
- if (metadata !== undefined && !isPlainObject2(metadata)) {
4479
+ if (metadata !== undefined && !isPlainObject22(metadata)) {
4430
4480
  violations.push(violation3("medium", "status.plan-row.invalid-metadata", "metadata must be an object"));
4431
4481
  }
4432
- if (execution_lease !== undefined && !isPlainObject2(execution_lease)) {
4482
+ if (execution_lease !== undefined && !isPlainObject22(execution_lease)) {
4433
4483
  violations.push(violation3("medium", "status.plan-row.invalid-execution-lease", "execution_lease must be an object"));
4434
4484
  }
4435
4485
  if (status === "Done" && execution_lease !== undefined) {
4436
- violations.push(violation3("medium", "status.plan-row.done-with-lease", 'plan status Done must not carry an execution_lease the Done authority deletes the lease in the same complete-file update as status: "Done" (status-and-residuals.md § Hold, release, and override)', 'delete plans[].execution_lease in the same update that sets status: "Done"'));
4486
+ violations.push(violation3("medium", "status.plan-row.done-with-lease", 'plan status Done must not carry an execution_lease \u2014 the Done authority deletes the lease in the same complete-file update as status: "Done" (status-and-residuals.md \u00a7 Hold, release, and override)', 'delete plans[].execution_lease in the same update that sets status: "Done"'));
4437
4487
  }
4438
4488
  return { ok: violations.length === 0, violations };
4439
4489
  }
4440
4490
  function validateResidual(entry) {
4441
4491
  const violations = [];
4442
- if (!isPlainObject2(entry)) {
4492
+ if (!isPlainObject22(entry)) {
4443
4493
  return { ok: false, violations: [violation3("high", "status.residual.invalid", "residual entry must be an object")] };
4444
4494
  }
4445
4495
  const { id, title, severity, source, scope, decision, owner, target, tracking, detail_doc, lifecycle, closed_at } = entry;
@@ -4451,14 +4501,14 @@ function validateResidual(entry) {
4451
4501
  if (severity === undefined) {
4452
4502
  violations.push(violation3("high", "status.residual.missing-severity", "missing required field: severity"));
4453
4503
  } else if (typeof severity !== "string" || !SEVERITY_ORDER.includes(severity) && severity !== "warning") {
4454
- violations.push(violation3("medium", "status.residual.invalid-severity", `severity must be one of ${SEVERITY_ORDER.join(" | ")} got ${JSON.stringify(severity)}`));
4504
+ violations.push(violation3("medium", "status.residual.invalid-severity", `severity must be one of ${SEVERITY_ORDER.join(" | ")} \u2014 got ${JSON.stringify(severity)}`));
4455
4505
  } else if (severity === "warning") {
4456
- violations.push(violation3("low", "status.residual.legacy-warning", `severity "warning" is legacy forbidden on new entries; read paths normalize it to "low"`, `use "low" (normalizeSeverity maps 'warning' 'low')`));
4506
+ violations.push(violation3("low", "status.residual.legacy-warning", `severity "warning" is legacy \u2014 forbidden on new entries; read paths normalize it to "low"`, `use "low" (normalizeSeverity maps 'warning' \u2192 'low')`));
4457
4507
  }
4458
4508
  if (decision === undefined) {
4459
4509
  violations.push(violation3("high", "status.residual.missing-decision", "missing required field: decision"));
4460
4510
  } else if (typeof decision !== "string" || !RESIDUAL_DECISIONS.includes(decision)) {
4461
- violations.push(violation3("medium", "status.residual.invalid-decision", `decision must be one of ${RESIDUAL_DECISIONS.join(" | ")} got ${JSON.stringify(decision)}`));
4511
+ violations.push(violation3("medium", "status.residual.invalid-decision", `decision must be one of ${RESIDUAL_DECISIONS.join(" | ")} \u2014 got ${JSON.stringify(decision)}`));
4462
4512
  }
4463
4513
  if (target === undefined) {
4464
4514
  violations.push(violation3("high", "status.residual.missing-target", "missing required field: target"));
@@ -4478,7 +4528,7 @@ function validateResidual(entry) {
4478
4528
  }
4479
4529
  if (lifecycle !== undefined) {
4480
4530
  if (typeof lifecycle !== "string" || !RESIDUAL_LIFECYCLES.includes(lifecycle)) {
4481
- violations.push(violation3("medium", "status.residual.invalid-lifecycle", `lifecycle must be one of ${RESIDUAL_LIFECYCLES.join(" | ")} got ${JSON.stringify(lifecycle)}`));
4531
+ violations.push(violation3("medium", "status.residual.invalid-lifecycle", `lifecycle must be one of ${RESIDUAL_LIFECYCLES.join(" | ")} \u2014 got ${JSON.stringify(lifecycle)}`));
4482
4532
  } else if (lifecycle !== "open") {
4483
4533
  if (closed_at === undefined) {
4484
4534
  violations.push(violation3("high", "status.residual.closed-missing-closed-at", `lifecycle "${lifecycle}" requires closed_at (YYYY-MM-DD)`, 'set closed_at (e.g. "2026-08-08")'));
@@ -4511,7 +4561,7 @@ function validateStatus(docOrPath) {
4511
4561
  } else if (typeof version !== "number" || !Number.isInteger(version)) {
4512
4562
  violations.push(violation3("high", "status.invalid-version", "version must be an integer"));
4513
4563
  } else if (version !== 1) {
4514
- violations.push(violation3("medium", "status.unsupported-version", `unsupported status.json schema version ${version} expected 1`));
4564
+ violations.push(violation3("medium", "status.unsupported-version", `unsupported status.json schema version ${version} \u2014 expected 1`));
4515
4565
  }
4516
4566
  if (updated_at === undefined) {
4517
4567
  violations.push(violation3("high", "status.missing-updated-at", "missing required field: updated_at"));
@@ -4529,14 +4579,14 @@ function validateStatus(docOrPath) {
4529
4579
  }
4530
4580
  if (residual_findings === undefined) {
4531
4581
  violations.push(violation3("high", "status.missing-residual-findings", "missing required field: residual_findings (root-only canonical)"));
4532
- } else if (!isPlainObject2(residual_findings)) {
4582
+ } else if (!isPlainObject22(residual_findings)) {
4533
4583
  violations.push(violation3("high", "status.invalid-residual-findings", "residual_findings must be an object at root"));
4534
4584
  } else {
4535
4585
  for (const [planId, list] of Object.entries(residual_findings)) {
4536
4586
  if (!Array.isArray(list)) {
4537
4587
  violations.push(violation3("high", "status.residual.invalid-list", `residual_findings["${planId}"] must be an array`));
4538
4588
  } else if (list.length === 0) {
4539
- violations.push(violation3("low", "status.residual.empty-key", `residual_findings["${planId}"] is empty delete the key (no "plan-id": [])`));
4589
+ violations.push(violation3("low", "status.residual.empty-key", `residual_findings["${planId}"] is empty \u2014 delete the key (no "plan-id": [])`));
4540
4590
  } else {
4541
4591
  for (const entry of list) {
4542
4592
  violations.push(...validateResidual(entry).violations);
@@ -4546,17 +4596,17 @@ function validateStatus(docOrPath) {
4546
4596
  }
4547
4597
  if (metadata === undefined) {
4548
4598
  violations.push(violation3("high", "status.missing-metadata", "missing required field: metadata"));
4549
- } else if (!isPlainObject2(metadata)) {
4599
+ } else if (!isPlainObject22(metadata)) {
4550
4600
  violations.push(violation3("high", "status.invalid-metadata", "metadata must be an object"));
4551
4601
  } else if (Object.prototype.hasOwnProperty.call(metadata, "residual_findings")) {
4552
- violations.push(violation3("medium", "status.dual-write-residuals", "residual_findings must be root-only metadata.residual_findings is legacy read-only; remove it (no dual-write)", "move entries to root residual_findings and delete metadata.residual_findings"));
4602
+ violations.push(violation3("medium", "status.dual-write-residuals", "residual_findings must be root-only \u2014 metadata.residual_findings is legacy read-only; remove it (no dual-write)", "move entries to root residual_findings and delete metadata.residual_findings"));
4553
4603
  }
4554
4604
  return { ok: violations.length === 0, violations };
4555
4605
  }
4556
4606
  async function archiveResiduals(planId, harnessDir) {
4557
4607
  const dir = harnessDir !== undefined ? resolve4(harnessDir) : resolveHarnessDir();
4558
4608
  if (dir === null) {
4559
- throw new Error(`harness dir not found from ${process.cwd()} pass harnessDir or set MSTAR_HARNESS_DIR`);
4609
+ throw new Error(`harness dir not found from ${process.cwd()} \u2014 pass harnessDir or set MSTAR_HARNESS_DIR`);
4560
4610
  }
4561
4611
  assertSafePathComponent(planId, "planId");
4562
4612
  const statusPath = join4(dir, "status.json");
@@ -4565,7 +4615,7 @@ async function archiveResiduals(planId, harnessDir) {
4565
4615
  }
4566
4616
  return withStatusWriteLock(statusPath, () => {
4567
4617
  const doc = readJson(statusPath);
4568
- if (!isPlainObject2(doc.residual_findings)) {
4618
+ if (!isPlainObject22(doc.residual_findings)) {
4569
4619
  throw new Error(`status.json residual_findings must be an object: ${statusPath}`);
4570
4620
  }
4571
4621
  const open = doc.residual_findings[planId];
@@ -4575,10 +4625,10 @@ async function archiveResiduals(planId, harnessDir) {
4575
4625
  }
4576
4626
  const archive = readJson(archivePath);
4577
4627
  const existing = Array.isArray(archive.entries) ? archive.entries : [];
4578
- const existingIds = new Set(existing.map((e) => isPlainObject2(e) && typeof e.id === "string" ? e.id : undefined).filter((id) => id !== undefined));
4628
+ const existingIds = new Set(existing.map((e) => isPlainObject22(e) && typeof e.id === "string" ? e.id : undefined).filter((id) => id !== undefined));
4579
4629
  const today = todayString();
4580
4630
  const moved = open.filter((entry) => {
4581
- if (!isPlainObject2(entry) || typeof entry.id !== "string")
4631
+ if (!isPlainObject22(entry) || typeof entry.id !== "string")
4582
4632
  return true;
4583
4633
  return !existingIds.has(entry.id);
4584
4634
  }).map((entry) => ({ ...entry, archived_at: today }));
@@ -4591,6 +4641,105 @@ async function archiveResiduals(planId, harnessDir) {
4591
4641
  return { planId, archived: moved.length, archivePath };
4592
4642
  });
4593
4643
  }
4644
+ function planFindingsCleanup(doc, planId) {
4645
+ if (!Array.isArray(doc.plans))
4646
+ return;
4647
+ for (const row of doc.plans) {
4648
+ if (!isPlainObject22(row))
4649
+ continue;
4650
+ const rowId = row.id ?? row.plan_id;
4651
+ if (rowId !== planId)
4652
+ continue;
4653
+ if (!isPlainObject22(row.metadata))
4654
+ return;
4655
+ const mode = row.metadata.findings_cleanup;
4656
+ if (mode === "zero-residual" || mode === "allow-residual")
4657
+ return mode;
4658
+ return;
4659
+ }
4660
+ return;
4661
+ }
4662
+ function openResidualsOf(doc, planId) {
4663
+ if (!isPlainObject22(doc.residual_findings))
4664
+ return [];
4665
+ const list = doc.residual_findings[planId];
4666
+ if (!Array.isArray(list))
4667
+ return [];
4668
+ return list.filter((entry) => isPlainObject22(entry) && isOpenResidual(entry));
4669
+ }
4670
+ function findingsCleanupGate(doc, planId, opts) {
4671
+ const mode = opts?.mode ?? planFindingsCleanup(doc, planId) ?? "allow-residual";
4672
+ const violations = [];
4673
+ const residuals = openResidualsOf(doc, planId);
4674
+ for (const entry of residuals) {
4675
+ const id = typeof entry.id === "string" ? entry.id : "<unnamed>";
4676
+ const label = `R#${id}`;
4677
+ if (mode === "zero-residual") {
4678
+ if (entry.severity === "nit") {
4679
+ violations.push(violation3("medium", "findings.zero-residual-nit", `${label}: style-only nits must be fixed in-session or dropped \u2014 never left open under zero-residual`));
4680
+ } else if (entry.decision === "risk-accepted" || entry.lifecycle === "waived") {
4681
+ violations.push(violation3("medium", "findings.zero-residual-risk-accepted", `${label}: waived/risk-accepted findings must be closed/archived, not left open under zero-residual`));
4682
+ } else if (entry.decision === "defer") {
4683
+ if (typeof entry.target !== "string" || entry.target.trim() === "") {
4684
+ violations.push(violation3("medium", "findings.zero-residual-defer-no-target", `${label}: blocker-defer requires a target (next iteration/milestone) under zero-residual`));
4685
+ }
4686
+ } else {
4687
+ violations.push(violation3("medium", "findings.zero-residual-open-fixable", `${label}: fixable finding must not remain open under zero-residual \u2014 fix now or convert to a blocker-defer`));
4688
+ }
4689
+ } else if (normalizeSeverity(entry.severity) === "critical") {
4690
+ violations.push(violation3("high", "findings.allow-residual-critical", `${label}: unresolved critical blocks Approve with residuals`));
4691
+ }
4692
+ }
4693
+ return { ok: violations.length === 0, violations };
4694
+ }
4695
+ function groupCount(values) {
4696
+ const counts = new Map;
4697
+ for (const value of values) {
4698
+ const key = typeof value === "string" ? value : String(value);
4699
+ counts.set(key, (counts.get(key) ?? 0) + 1);
4700
+ }
4701
+ return Object.fromEntries([...counts.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0));
4702
+ }
4703
+ function techDebtRollup(docOrPath) {
4704
+ const doc = typeof docOrPath === "string" ? readJson(docOrPath) : docOrPath;
4705
+ const canonical = isPlainObject22(doc.residual_findings) ? doc.residual_findings : {};
4706
+ const metadata = isPlainObject22(doc.metadata) ? doc.metadata : {};
4707
+ const legacy = isPlainObject22(metadata.residual_findings) ? metadata.residual_findings : {};
4708
+ const merged = { ...canonical, ...legacy };
4709
+ const items = [];
4710
+ for (const [plan, list] of Object.entries(merged)) {
4711
+ if (!Array.isArray(list))
4712
+ continue;
4713
+ for (const value of list) {
4714
+ if (!isPlainObject22(value) || !isOpenResidual(value))
4715
+ continue;
4716
+ items.push({ plan, entry: value });
4717
+ }
4718
+ }
4719
+ const bySeverity = {};
4720
+ for (const severity of SEVERITY_ORDER) {
4721
+ bySeverity[severity] = items.filter(({ entry }) => normalizeSeverity(entry.severity) === severity).length;
4722
+ }
4723
+ const computed = {
4724
+ total_open: items.length,
4725
+ by_severity: bySeverity,
4726
+ by_target: groupCount(items.map(({ entry }) => entry.target ?? "unspecified")),
4727
+ by_plan: groupCount(items.map(({ plan }) => plan))
4728
+ };
4729
+ const storedRaw = metadata.tech_debt_summary ?? null;
4730
+ const stored = storedRaw === null ? null : storedRaw;
4731
+ const checks = ROLLUP_FIELDS.map((field) => {
4732
+ const computedField = computed[field];
4733
+ if (stored === null)
4734
+ return { field, status: "DRIFT" };
4735
+ const storedField = stored[field];
4736
+ const storedCompared = storedField === false ? null : storedField ?? null;
4737
+ const status = JSON.stringify(computedField) === JSON.stringify(storedCompared) ? "PASS" : "DRIFT";
4738
+ return { field, status };
4739
+ });
4740
+ const overall = checks.every((check) => check.status === "PASS") ? "PASS" : "DRIFT";
4741
+ return { computed, stored, checks, overall };
4742
+ }
4594
4743
  var DEFAULT_PROBE_TIMEOUT_MS = 1e4;
4595
4744
  function probeTimeoutMs() {
4596
4745
  const raw = process.env.MSTAR_GIT_PROBE_TIMEOUT_MS;
@@ -4633,16 +4782,16 @@ function l1PreDispatchCheck(input, opts = {}) {
4633
4782
  const violations = [];
4634
4783
  const { controlWorktreePath, leaseWorktreePath, leaseWorkingBranch, planId } = input;
4635
4784
  if (controlWorktreePath.trim() === "") {
4636
- violations.push(violation4("high", "worktree.l1.control-missing", "metadata.control_worktree_path is not recorded the L1 control worktree (integration-branch checkout) must be recorded in status.json before writable dispatch", "record the control worktree path in status.json metadata.control_worktree_path"));
4785
+ violations.push(violation4("high", "worktree.l1.control-missing", "metadata.control_worktree_path is not recorded \u2014 the L1 control worktree (integration-branch checkout) must be recorded in status.json before writable dispatch", "record the control worktree path in status.json metadata.control_worktree_path"));
4637
4786
  }
4638
4787
  if (leaseWorktreePath.trim() === "") {
4639
- violations.push(violation4("high", "worktree.l1.lease-missing", `execution_lease.worktree_path is empty for plan "${planId}" no verified execution_lease to dispatch against`, "claim the execution_lease with an absolute feature worktree path before dispatch"));
4788
+ violations.push(violation4("high", "worktree.l1.lease-missing", `execution_lease.worktree_path is empty for plan "${planId}" \u2014 no verified execution_lease to dispatch against`, "claim the execution_lease with an absolute feature worktree path before dispatch"));
4640
4789
  }
4641
4790
  if (leaseWorkingBranch.trim() === "") {
4642
4791
  violations.push(violation4("high", "worktree.l1.lease-branch-missing", `execution_lease.working_branch is empty for plan "${planId}"`, "record the lease working_branch before dispatch"));
4643
4792
  }
4644
4793
  if (controlWorktreePath !== "" && leaseWorktreePath !== "" && resolve5(controlWorktreePath) === resolve5(leaseWorktreePath)) {
4645
- violations.push(violation4("critical", "worktree.l1.lease-equals-control", `execution_lease.worktree_path "${leaseWorktreePath}" equals metadata.control_worktree_path the feature worktree MUST differ from the control worktree (L1 isolation; product edits never land in the control checkout)`, "use a distinct feature worktree for the plan (git worktree add <path> <branch>) and update the lease"));
4794
+ violations.push(violation4("critical", "worktree.l1.lease-equals-control", `execution_lease.worktree_path "${leaseWorktreePath}" equals metadata.control_worktree_path \u2014 the feature worktree MUST differ from the control worktree (L1 isolation; product edits never land in the control checkout)`, "use a distinct feature worktree for the plan (git worktree add <path> <branch>) and update the lease"));
4646
4795
  }
4647
4796
  if (leaseWorktreePath !== "" && !existsSync3(leaseWorktreePath)) {
4648
4797
  violations.push(violation4("high", "worktree.l1.feature-missing", `feature worktree directory "${leaseWorktreePath}" does not exist for plan "${planId}"`, `create it before dispatch: git worktree add ${leaseWorktreePath} <working-branch>`));
@@ -4661,7 +4810,7 @@ function l2PreDispatchCheck(input, opts = {}) {
4661
4810
  const tracks = input.tracks ?? [];
4662
4811
  const seenPaths = new Set;
4663
4812
  if (tracks.length < 1) {
4664
- violations.push(violation4("high", "worktree.l2.no-tracks", "no parallel writable tracks the L2 pre-dispatch checklist requires at least one track with an absolute worktreePath and Working branch", "pass each track's absolute Worktree path and PM-approved Working branch"));
4813
+ violations.push(violation4("high", "worktree.l2.no-tracks", "no parallel writable tracks \u2014 the L2 pre-dispatch checklist requires at least one track with an absolute worktreePath and Working branch", "pass each track's absolute Worktree path and PM-approved Working branch"));
4665
4814
  }
4666
4815
  tracks.forEach((track, index) => {
4667
4816
  if (track.worktreePath.trim() === "" || track.workingBranch.trim() === "") {
@@ -4669,12 +4818,12 @@ function l2PreDispatchCheck(input, opts = {}) {
4669
4818
  return;
4670
4819
  }
4671
4820
  if (!isAbsolute3(track.worktreePath)) {
4672
- violations.push(violation4("high", "worktree.l2.track-path-relative", `track ${index + 1} worktreePath "${track.worktreePath}" is not an absolute path L2 tracks MUST use absolute worktree checkout paths (consistent with the lease validator's absolute worktree_path enforcement)`, `use an absolute path for track ${index + 1} (e.g. /Users/<you>/worktrees/<branch>)`));
4821
+ violations.push(violation4("high", "worktree.l2.track-path-relative", `track ${index + 1} worktreePath "${track.worktreePath}" is not an absolute path \u2014 L2 tracks MUST use absolute worktree checkout paths (consistent with the lease validator's absolute worktree_path enforcement)`, `use an absolute path for track ${index + 1} (e.g. /Users/<you>/worktrees/<branch>)`));
4673
4822
  return;
4674
4823
  }
4675
4824
  const normalized = resolve5(track.worktreePath);
4676
4825
  if (seenPaths.has(normalized)) {
4677
- violations.push(violation4("high", "worktree.l2.track-path-collision", `duplicate worktreePath "${track.worktreePath}" across parallel tracks L2 parallel-writable isolation requires a distinct absolute Worktree path per track (N parallel invokes isolation)`, "give every parallel track its own git worktree checkout"));
4826
+ violations.push(violation4("high", "worktree.l2.track-path-collision", `duplicate worktreePath "${track.worktreePath}" across parallel tracks \u2014 L2 parallel-writable isolation requires a distinct absolute Worktree path per track (N parallel invokes \u2260 isolation)`, "give every parallel track its own git worktree checkout"));
4678
4827
  return;
4679
4828
  }
4680
4829
  seenPaths.add(normalized);
@@ -4691,6 +4840,22 @@ function l2PreDispatchCheck(input, opts = {}) {
4691
4840
  });
4692
4841
  return gate(violations);
4693
4842
  }
4843
+ var QC_ALIGNMENT_FIELDS = [
4844
+ { key: "planId", label: "plan_id" },
4845
+ { key: "reviewRange", label: "Review range" },
4846
+ { key: "diffBasis", label: "Diff basis" }
4847
+ ];
4848
+ function assertQcAlignment(assignments) {
4849
+ const violations = [];
4850
+ const list = assignments ?? [];
4851
+ for (const { key, label } of QC_ALIGNMENT_FIELDS) {
4852
+ const distinct = [...new Set(list.map((a) => a[key]))];
4853
+ if (distinct.length > 1) {
4854
+ violations.push(violation4("high", "qc.alignment.mismatch", `QC/QA alignment field "${label}" is not byte-identical across ${list.length} assignments: ${distinct.map((v) => `"${v}"`).join(" vs ")}`, `copy the same ${label} value verbatim into every QC tri and QA Assignment`));
4855
+ }
4856
+ }
4857
+ return gate(violations);
4858
+ }
4694
4859
  class SddScriptError extends Error {
4695
4860
  exitCode;
4696
4861
  constructor(message, exitCode) {
@@ -4771,7 +4936,7 @@ function sddWorkspace(planId, opts = {}) {
4771
4936
  throw new SddScriptError(`mstar sdd workspace: linked worktree at ${root} has no {HARNESS_DIR}/status.json (default gitignore).
4772
4937
  ` + ` Refusing to create a second SDD tree under the feature checkout.
4773
4938
  ` + ` Re-run with MSTAR_CONTROL_ROOT=<control_worktree_path> or: mstar sdd workspace ${planId} <control_worktree_path>
4774
- ` + ` See mstar-branch-worktree «Harness path SSOT under default gitignore».`, 1);
4939
+ ` + ` See mstar-branch-worktree \u00abHarness path SSOT under default gitignore\u00bb.`, 1);
4775
4940
  }
4776
4941
  const harnessOverride = opts.harnessDir ?? (process.env.MSTAR_HARNESS_DIR || undefined);
4777
4942
  let harnessDir;
@@ -4973,7 +5138,7 @@ function validateCompassFrontmatter(doc) {
4973
5138
  return {
4974
5139
  ok: false,
4975
5140
  violations: [
4976
- violation5("medium", "COMPASS_INVALID_FIELD", "Compass frontmatter must be a YAML object with iteration_id / start_date / status / iteration_base_branch / target_branch (template: mstar-iteration §1.3)", "Fix the frontmatter of {ITERATION_DIR}/<iteration-id>/delivery-compass.md")
5141
+ violation5("medium", "COMPASS_INVALID_FIELD", "Compass frontmatter must be a YAML object with iteration_id / start_date / status / iteration_base_branch / target_branch (template: mstar-iteration \u00a71.3)", "Fix the frontmatter of {ITERATION_DIR}/<iteration-id>/delivery-compass.md")
4977
5142
  ]
4978
5143
  };
4979
5144
  }
@@ -4983,17 +5148,17 @@ function validateCompassFrontmatter(doc) {
4983
5148
  ok: false,
4984
5149
  violations: parsed.issues.map((issue) => {
4985
5150
  const field = issue.path.join(".") || "(root)";
4986
- return violation5("medium", "COMPASS_INVALID_FIELD", `Compass frontmatter field '${field}' is invalid: ${issue.message}`, `Fix '${field}' in {ITERATION_DIR}/<iteration-id>/delivery-compass.md frontmatter (template: mstar-iteration §1.3)`);
5151
+ return violation5("medium", "COMPASS_INVALID_FIELD", `Compass frontmatter field '${field}' is invalid: ${issue.message}`, `Fix '${field}' in {ITERATION_DIR}/<iteration-id>/delivery-compass.md frontmatter (template: mstar-iteration \u00a71.3)`);
4987
5152
  })
4988
5153
  };
4989
5154
  }
4990
5155
  const violations = [];
4991
5156
  const { status, end_date } = parsed.data;
4992
5157
  if (status === "completed" && end_date === undefined) {
4993
- violations.push(violation5("high", "COMPASS_END_DATE_REQUIRED", "Compass frontmatter status is 'completed' but end_date is missing end_date is required at iteration-close (mstar-iteration §3.4, template Fields guide)", "Add `end_date: YYYY-MM-DD` to the frontmatter"));
5158
+ violations.push(violation5("high", "COMPASS_END_DATE_REQUIRED", "Compass frontmatter status is 'completed' but end_date is missing \u2014 end_date is required at iteration-close (mstar-iteration \u00a73.4, template Fields guide)", "Add `end_date: YYYY-MM-DD` to the frontmatter"));
4994
5159
  }
4995
5160
  if (status !== "completed" && end_date !== undefined) {
4996
- violations.push(violation5("medium", "COMPASS_END_DATE_NOT_ALLOWED", `Compass frontmatter sets end_date while status is '${status}' end_date is only written at iteration-close (mstar-iteration §3.4)`, "Remove end_date until iteration-close"));
5161
+ violations.push(violation5("medium", "COMPASS_END_DATE_NOT_ALLOWED", `Compass frontmatter sets end_date while status is '${status}' \u2014 end_date is only written at iteration-close (mstar-iteration \u00a73.4)`, "Remove end_date until iteration-close"));
4997
5162
  }
4998
5163
  return { ok: violations.length === 0, violations };
4999
5164
  }
@@ -5017,17 +5182,17 @@ function findPlanRow(statusDoc, planId) {
5017
5182
  function entryPlansAllDone(statusDoc, registered) {
5018
5183
  const violations = [];
5019
5184
  if (registered.length === 0) {
5020
- violations.push(violation5("medium", "COMPASS_NO_PLANS", "Compass frontmatter registers no plans the all-plans-Done transition cannot be verified (mstar-iteration §1.3 / Phase transition gates)", "List the iteration's plan ids in the compass frontmatter `plans`"));
5185
+ violations.push(violation5("medium", "COMPASS_NO_PLANS", "Compass frontmatter registers no plans \u2014 the all-plans-Done transition cannot be verified (mstar-iteration \u00a71.3 / Phase transition gates)", "List the iteration's plan ids in the compass frontmatter `plans`"));
5021
5186
  return violations;
5022
5187
  }
5023
5188
  for (const planId of registered) {
5024
5189
  const row = findPlanRow(statusDoc, planId);
5025
5190
  if (row === null) {
5026
- violations.push(violation5("high", "PLAN_NOT_IN_STATUS", `Plan '${planId}' is registered in the compass frontmatter but has no row in status.json plans[] (mstar-iteration §3.1 entry item 1)`, "Add the plan row to {HARNESS_DIR}/status.json"));
5191
+ violations.push(violation5("high", "PLAN_NOT_IN_STATUS", `Plan '${planId}' is registered in the compass frontmatter but has no row in status.json plans[] (mstar-iteration \u00a73.1 entry item 1)`, "Add the plan row to {HARNESS_DIR}/status.json"));
5027
5192
  continue;
5028
5193
  }
5029
5194
  if (row.status !== PLAN_STATUS_DONE) {
5030
- violations.push(violation5("high", "PLAN_NOT_DONE", `Plan '${planId}' status is ${JSON.stringify(row.status)} in status.json all compass-registered plans must be 'Done' before iteration-close (mstar-iteration §3.1 entry item 1)`));
5195
+ violations.push(violation5("high", "PLAN_NOT_DONE", `Plan '${planId}' status is ${JSON.stringify(row.status)} in status.json \u2014 all compass-registered plans must be 'Done' before iteration-close (mstar-iteration \u00a73.1 entry item 1)`));
5031
5196
  }
5032
5197
  }
5033
5198
  return violations;
@@ -5038,14 +5203,14 @@ function entryResidualsOpen(statusDoc, planId) {
5038
5203
  if (residualRoot === undefined || residualRoot === null)
5039
5204
  return violations;
5040
5205
  if (!isPlainObject3(residualRoot)) {
5041
- violations.push(violation5("medium", "RESIDUAL_MALFORMED", "status.json residual_findings must be a plan-id entries object (mstar-iteration §3.1 entry item 2)"));
5206
+ violations.push(violation5("medium", "RESIDUAL_MALFORMED", "status.json residual_findings must be a plan-id \u2192 entries object (mstar-iteration \u00a73.1 entry item 2)"));
5042
5207
  return violations;
5043
5208
  }
5044
5209
  const entries = residualRoot[planId];
5045
5210
  if (entries === undefined)
5046
5211
  return violations;
5047
5212
  if (!Array.isArray(entries)) {
5048
- violations.push(violation5("medium", "RESIDUAL_MALFORMED", `status.json residual_findings['${planId}'] must be an array of residual entries (mstar-iteration §3.1 entry item 2)`));
5213
+ violations.push(violation5("medium", "RESIDUAL_MALFORMED", `status.json residual_findings['${planId}'] must be an array of residual entries (mstar-iteration \u00a73.1 entry item 2)`));
5049
5214
  return violations;
5050
5215
  }
5051
5216
  const openIds = [];
@@ -5058,7 +5223,7 @@ function entryResidualsOpen(statusDoc, planId) {
5058
5223
  openIds.push(typeof entry.id === "string" ? entry.id : "<unnamed>");
5059
5224
  }
5060
5225
  if (openIds.length > 0) {
5061
- violations.push(violation5("high", "OPEN_RESIDUALS", `Plan '${planId}' has ${openIds.length} open residual finding(s) not exempted as blocker-defers (${openIds.join(", ")}) residuals must be closed/archived before iteration-close; only zero-residual blocker-defers (decision: defer + target) may stay open (mstar-iteration §3.1 entry item 2)`, "Close or archive the open residuals, or convert them into blocker-defers (decision: defer + non-empty target) per mstar-plan-artifacts Findings cleanup modes"));
5226
+ violations.push(violation5("high", "OPEN_RESIDUALS", `Plan '${planId}' has ${openIds.length} open residual finding(s) not exempted as blocker-defers (${openIds.join(", ")}) \u2014 residuals must be closed/archived before iteration-close; only zero-residual blocker-defers (decision: defer + target) may stay open (mstar-iteration \u00a73.1 entry item 2)`, "Close or archive the open residuals, or convert them into blocker-defers (decision: defer + non-empty target) per mstar-plan-artifacts Findings cleanup modes"));
5062
5227
  }
5063
5228
  return violations;
5064
5229
  }
@@ -5068,11 +5233,11 @@ function entryFrontmatterComplete(compassDoc) {
5068
5233
  function exitFrontmatterClosed(compassDoc) {
5069
5234
  const violations = [];
5070
5235
  if (compassDoc.status !== "completed") {
5071
- violations.push(violation5("high", "EXIT_STATUS_NOT_COMPLETED", `Compass frontmatter status must be 'completed' at close exit current: ${JSON.stringify(compassDoc.status)} (mstar-iteration §3.4 / §3.5 exit item 4)`));
5236
+ violations.push(violation5("high", "EXIT_STATUS_NOT_COMPLETED", `Compass frontmatter status must be 'completed' at close exit \u2014 current: ${JSON.stringify(compassDoc.status)} (mstar-iteration \u00a73.4 / \u00a73.5 exit item 4)`));
5072
5237
  }
5073
5238
  const endDate = compassDoc.end_date;
5074
5239
  if (typeof endDate !== "string" || !DATE_RE2.test(endDate)) {
5075
- violations.push(violation5("high", "EXIT_END_DATE_REQUIRED", "Compass frontmatter end_date (YYYY-MM-DD) is required when closing (mstar-iteration §3.4 / §3.5 exit item 4)"));
5240
+ violations.push(violation5("high", "EXIT_END_DATE_REQUIRED", "Compass frontmatter end_date (YYYY-MM-DD) is required when closing (mstar-iteration \u00a73.4 / \u00a73.5 exit item 4)"));
5076
5241
  }
5077
5242
  return violations;
5078
5243
  }
@@ -5080,9 +5245,9 @@ function exitBranchCheck(opts) {
5080
5245
  const violations = [];
5081
5246
  const { currentBranch, specIntegrationBranch } = opts;
5082
5247
  if (currentBranch === undefined || specIntegrationBranch === undefined) {
5083
- violations.push(violation5("medium", "EXIT_BRANCH_UNVERIFIABLE", "Cannot verify the current branch is spec_integration_branch missing currentBranch / specIntegrationBranch probe inputs (mstar-iteration §3.5 exit item 5)"));
5248
+ violations.push(violation5("medium", "EXIT_BRANCH_UNVERIFIABLE", "Cannot verify the current branch is spec_integration_branch \u2014 missing currentBranch / specIntegrationBranch probe inputs (mstar-iteration \u00a73.5 exit item 5)"));
5084
5249
  } else if (currentBranch !== specIntegrationBranch) {
5085
- violations.push(violation5("high", "EXIT_BRANCH_MISMATCH", `Current branch '${currentBranch}' is not the spec_integration_branch '${specIntegrationBranch}' (mstar-iteration §3.5 exit item 5)`));
5250
+ violations.push(violation5("high", "EXIT_BRANCH_MISMATCH", `Current branch '${currentBranch}' is not the spec_integration_branch '${specIntegrationBranch}' (mstar-iteration \u00a73.5 exit item 5)`));
5086
5251
  }
5087
5252
  return violations;
5088
5253
  }
@@ -5091,9 +5256,9 @@ function exitPrBaseCheck(compassDoc, opts) {
5091
5256
  const target = compassDoc.target_branch;
5092
5257
  const { prBaseBranch } = opts;
5093
5258
  if (prBaseBranch === undefined) {
5094
- violations.push(violation5("medium", "EXIT_PR_BASE_UNVERIFIABLE", "Cannot verify the PR base missing prBaseBranch probe input (mstar-iteration §3.5 exit item 6)"));
5259
+ violations.push(violation5("medium", "EXIT_PR_BASE_UNVERIFIABLE", "Cannot verify the PR base \u2014 missing prBaseBranch probe input (mstar-iteration \u00a73.5 exit item 6)"));
5095
5260
  } else if (typeof target !== "string" || prBaseBranch !== target) {
5096
- violations.push(violation5("high", "EXIT_PR_BASE_MISMATCH", `PR base '${prBaseBranch}' must equal the compass target_branch '${String(target)}' not an undocumented branch (mstar-iteration §3.5 exit item 6)`));
5261
+ violations.push(violation5("high", "EXIT_PR_BASE_MISMATCH", `PR base '${prBaseBranch}' must equal the compass target_branch '${String(target)}' \u2014 not an undocumented branch (mstar-iteration \u00a73.5 exit item 6)`));
5097
5262
  }
5098
5263
  return violations;
5099
5264
  }
@@ -5135,10 +5300,10 @@ function evaluatePhaseGate(statusDoc, compassDoc, opts = {}) {
5135
5300
  function pushCadenceProbe(ciRunning, reviewWaveActive) {
5136
5301
  const violations = [];
5137
5302
  if (ciRunning) {
5138
- violations.push(violation5("high", "PUSH_BLOCKED_CI", "CI checks are still queued/in_progress on the current head do not push until the wave completes (mstar-iteration §5.1a push gate 1)", "Wait for CI to settle, then push once with the whole local batch"));
5303
+ violations.push(violation5("high", "PUSH_BLOCKED_CI", "CI checks are still queued/in_progress on the current head \u2014 do not push until the wave completes (mstar-iteration \u00a75.1a push gate 1)", "Wait for CI to settle, then push once with the whole local batch"));
5139
5304
  }
5140
5305
  if (reviewWaveActive) {
5141
- violations.push(violation5("high", "PUSH_BLOCKED_REVIEW_WAVE", "An AI/bot review wave is still running on the current head do not push until it settles (mstar-iteration §5.1a push gate 2)", "Wait for the review wave, then push once"));
5306
+ violations.push(violation5("high", "PUSH_BLOCKED_REVIEW_WAVE", "An AI/bot review wave is still running on the current head \u2014 do not push until it settles (mstar-iteration \u00a75.1a push gate 2)", "Wait for the review wave, then push once"));
5142
5307
  }
5143
5308
  return { ok: violations.length === 0, violations };
5144
5309
  }
@@ -5179,7 +5344,7 @@ function parseCompassFrontmatter(filePath) {
5179
5344
  function parseFlowArray(raw, filePath) {
5180
5345
  const inner = raw.slice(1, -1);
5181
5346
  if (/[[\]]/.test(inner)) {
5182
- throw new Error(`nested flow-style array in ${filePath}: ${JSON.stringify(raw)} only flat scalar items are supported (e.g. [a, b])`);
5347
+ throw new Error(`nested flow-style array in ${filePath}: ${JSON.stringify(raw)} \u2014 only flat scalar items are supported (e.g. [a, b])`);
5183
5348
  }
5184
5349
  let quote = null;
5185
5350
  for (const ch of inner) {
@@ -5189,7 +5354,7 @@ function parseFlowArray(raw, filePath) {
5189
5354
  else if (quote === ch)
5190
5355
  quote = null;
5191
5356
  } else if (ch === "," && quote !== null) {
5192
- throw new Error(`ambiguous flow-style array in ${filePath}: ${JSON.stringify(raw)} quoted item containing comma cannot be split unambiguously (flat scalar items only)`);
5357
+ throw new Error(`ambiguous flow-style array in ${filePath}: ${JSON.stringify(raw)} \u2014 quoted item containing comma cannot be split unambiguously (flat scalar items only)`);
5193
5358
  }
5194
5359
  }
5195
5360
  if (quote !== null) {
@@ -5317,7 +5482,7 @@ function validateDesignTokenFrontmatter(frontmatterText) {
5317
5482
  const violations = [];
5318
5483
  const fm = parseDesignFrontmatter(frontmatterText);
5319
5484
  if (fm === null) {
5320
- violations.push(violation6("medium", "design-md.tokens.missing-frontmatter", "no `---` YAML frontmatter block found DESIGN.md must open with a fenced frontmatter holding the token SSOT (design-md-spec §1.5)", "add a `---` fenced frontmatter with version, name, description, and the colors/typography/spacing/rounded groups"));
5485
+ violations.push(violation6("medium", "design-md.tokens.missing-frontmatter", "no `---` YAML frontmatter block found \u2014 DESIGN.md must open with a fenced frontmatter holding the token SSOT (design-md-spec \u00a71.5)", "add a `---` fenced frontmatter with version, name, description, and the colors/typography/spacing/rounded groups"));
5321
5486
  return { ok: false, violations };
5322
5487
  }
5323
5488
  const groupEntries = (group) => {
@@ -5332,61 +5497,61 @@ function validateDesignTokenFrontmatter(frontmatterText) {
5332
5497
  };
5333
5498
  for (const group of ["colors", "typography", "spacing", "rounded"]) {
5334
5499
  if (!isMap(fm[group])) {
5335
- violations.push(violation6("medium", "design-md.tokens.group-not-map", `token group "${group}" must be a YAML map, not a scalar (design-md-spec §1.5)`, `rewrite \`${group}\` as a nested map`));
5500
+ violations.push(violation6("medium", "design-md.tokens.group-not-map", `token group "${group}" must be a YAML map, not a scalar (design-md-spec \u00a71.5)`, `rewrite \`${group}\` as a nested map`));
5336
5501
  } else if (!groupIsMap(group)) {
5337
- violations.push(violation6("medium", "design-md.tokens.group-not-map", `token group "${group}" must be a YAML map, not a scalar (design-md-spec §1.5)`, `rewrite \`${group}\` as a nested map`));
5502
+ violations.push(violation6("medium", "design-md.tokens.group-not-map", `token group "${group}" must be a YAML map, not a scalar (design-md-spec \u00a71.5)`, `rewrite \`${group}\` as a nested map`));
5338
5503
  } else if (groupEntries(group).length === 0) {
5339
- violations.push(violation6("medium", "design-md.tokens.missing-group", `missing required token group "${group}" colors/typography/spacing/rounded are required by the frontmatter SSOT (design-md-spec §1.5)`, `add an active \`${group}:\` block with concrete token values`));
5504
+ violations.push(violation6("medium", "design-md.tokens.missing-group", `missing required token group "${group}" \u2014 colors/typography/spacing/rounded are required by the frontmatter SSOT (design-md-spec \u00a71.5)`, `add an active \`${group}:\` block with concrete token values`));
5340
5505
  }
5341
5506
  }
5342
5507
  if (!isMap(fm.components) || !groupIsMap("components")) {
5343
- violations.push(violation6("medium", "design-md.tokens.group-not-map", `token group "components" must be a YAML map, not a scalar (design-md-spec §1.5)`, `rewrite \`components\` as a nested map`));
5508
+ violations.push(violation6("medium", "design-md.tokens.group-not-map", `token group "components" must be a YAML map, not a scalar (design-md-spec \u00a71.5)`, `rewrite \`components\` as a nested map`));
5344
5509
  }
5345
- const placeholder = (group, name, value) => violations.push(violation6("low", "design-md.tokens.placeholder", `token "${group}.${name}" uses a "[...]" template value placeholders never count as concrete tokens (completeness-checklist § How to use item 5)`, `replace \`${value}\` with a concrete value`));
5510
+ const placeholder = (group, name, value) => violations.push(violation6("low", "design-md.tokens.placeholder", `token "${group}.${name}" uses a "[...]" template value \u2014 placeholders never count as concrete tokens (completeness-checklist \u00a7 How to use item 5)`, `replace \`${value}\` with a concrete value`));
5346
5511
  for (const [name, value] of groupEntries("colors")) {
5347
5512
  if (typeof value !== "string") {
5348
- violations.push(violation6("medium", "design-md.tokens.color-format", `color "${name}" must be a string value (design-md-spec §2.2)`, "quote the color value"));
5513
+ violations.push(violation6("medium", "design-md.tokens.color-format", `color "${name}" must be a string value (design-md-spec \u00a72.2)`, "quote the color value"));
5349
5514
  continue;
5350
5515
  }
5351
5516
  if (isPlaceholder(value)) {
5352
5517
  placeholder("colors", name, value);
5353
5518
  } else if (!HEX_RE.test(value) && !OKLCH_RE.test(value)) {
5354
- violations.push(violation6("medium", "design-md.tokens.color-format", `color "${name}" = "${value}" is not a hex (\`#rrggbb\`/\`#rrggbbaa\`) or oklch() value (design-md-spec §2.2)`, "use an sRGB hex value, optionally with a `-p3` oklch() twin"));
5519
+ violations.push(violation6("medium", "design-md.tokens.color-format", `color "${name}" = "${value}" is not a hex (\`#rrggbb\`/\`#rrggbbaa\`) or oklch() value (design-md-spec \u00a72.2)`, "use an sRGB hex value, optionally with a `-p3` oklch() twin"));
5355
5520
  }
5356
5521
  }
5357
5522
  for (const [name, value] of groupEntries("typography")) {
5358
5523
  if (!isMap(value)) {
5359
- violations.push(violation6("medium", "design-md.tokens.typography-shape", `typography token "${name}" must be a map of the five properties (design-md-spec §1.5)`, "give it fontFamily/fontSize/fontWeight/lineHeight/letterSpacing"));
5524
+ violations.push(violation6("medium", "design-md.tokens.typography-shape", `typography token "${name}" must be a map of the five properties (design-md-spec \u00a71.5)`, "give it fontFamily/fontSize/fontWeight/lineHeight/letterSpacing"));
5360
5525
  continue;
5361
5526
  }
5362
5527
  const keys = Object.keys(value);
5363
5528
  const missing = TYPOGRAPHY_PROPS.filter((p) => !keys.includes(p));
5364
5529
  const extra = keys.filter((k) => !TYPOGRAPHY_PROPS.includes(k));
5365
5530
  if (missing.length > 0 || extra.length > 0) {
5366
- violations.push(violation6("medium", "design-md.tokens.typography-shape", `typography token "${name}" must have exactly the five properties fontFamily/fontSize/fontWeight/lineHeight/letterSpacing (design-md-spec §1.5)${missing.length > 0 ? ` missing: ${missing.join(", ")}` : ""}${extra.length > 0 ? ` extra: ${extra.join(", ")}` : ""}`, "align the token with the five-property shape"));
5531
+ violations.push(violation6("medium", "design-md.tokens.typography-shape", `typography token "${name}" must have exactly the five properties fontFamily/fontSize/fontWeight/lineHeight/letterSpacing (design-md-spec \u00a71.5)${missing.length > 0 ? ` \u2014 missing: ${missing.join(", ")}` : ""}${extra.length > 0 ? ` \u2014 extra: ${extra.join(", ")}` : ""}`, "align the token with the five-property shape"));
5367
5532
  }
5368
5533
  for (const prop of ["fontFamily", "fontSize"]) {
5369
5534
  const v = value[prop];
5370
5535
  if (typeof v === "string" && isPlaceholder(v))
5371
5536
  placeholder("typography", name, v);
5372
5537
  else if (typeof v !== "string" || v.trim() === "") {
5373
- violations.push(violation6("medium", "design-md.tokens.typography-shape", `typography token "${name}" has an empty \`${prop}\` (design-md-spec §1.5)`, `fill \`${prop}\` with a concrete value`));
5538
+ violations.push(violation6("medium", "design-md.tokens.typography-shape", `typography token "${name}" has an empty \`${prop}\` (design-md-spec \u00a71.5)`, `fill \`${prop}\` with a concrete value`));
5374
5539
  }
5375
5540
  }
5376
5541
  }
5377
5542
  if (groupIsMap("spacing")) {
5378
5543
  const spacing = fm.spacing;
5379
5544
  if (!Object.prototype.hasOwnProperty.call(spacing, "base")) {
5380
- violations.push(violation6("medium", "design-md.tokens.spacing-base", "spacing must declare the base unit as `base` (design-md-spec §2.4)", "add `base: 4px` (or 8px) to the spacing group"));
5545
+ violations.push(violation6("medium", "design-md.tokens.spacing-base", "spacing must declare the base unit as `base` (design-md-spec \u00a72.4)", "add `base: 4px` (or 8px) to the spacing group"));
5381
5546
  }
5382
5547
  for (const [name, value] of Object.entries(spacing)) {
5383
5548
  if (name !== "base" && name !== RAW_GROUP && !/^\d+$/.test(name)) {
5384
- violations.push(violation6("medium", "design-md.tokens.spacing-key", `spacing key "${name}" must be \`base\` or a numeric multiplier (design-md-spec §1.5)`, "use numeric scale-step keys or `base`"));
5549
+ violations.push(violation6("medium", "design-md.tokens.spacing-key", `spacing key "${name}" must be \`base\` or a numeric multiplier (design-md-spec \u00a71.5)`, "use numeric scale-step keys or `base`"));
5385
5550
  }
5386
5551
  if (typeof value === "string" && isPlaceholder(value)) {
5387
5552
  placeholder("spacing", name, value);
5388
5553
  } else if (typeof value !== "string" || !PX_RE.test(value)) {
5389
- violations.push(violation6("medium", "design-md.tokens.spacing-format", `spacing value "${name}" = "${String(value)}" is not a px length (design-md-spec §1.5)`, "use a pixel value like `4px`"));
5554
+ violations.push(violation6("medium", "design-md.tokens.spacing-format", `spacing value "${name}" = "${String(value)}" is not a px length (design-md-spec \u00a71.5)`, "use a pixel value like `4px`"));
5390
5555
  }
5391
5556
  }
5392
5557
  }
@@ -5394,12 +5559,12 @@ function validateDesignTokenFrontmatter(frontmatterText) {
5394
5559
  if (typeof value === "string" && isPlaceholder(value)) {
5395
5560
  placeholder("rounded", name, value);
5396
5561
  } else if (typeof value !== "string" || !PX_RE.test(value)) {
5397
- violations.push(violation6("medium", "design-md.tokens.rounded-format", `rounded value "${name}" = "${String(value)}" is not a px length (design-md-spec §1.5)`, "use a pixel value like `6px`"));
5562
+ violations.push(violation6("medium", "design-md.tokens.rounded-format", `rounded value "${name}" = "${String(value)}" is not a px length (design-md-spec \u00a71.5)`, "use a pixel value like `6px`"));
5398
5563
  }
5399
5564
  }
5400
5565
  for (const [name, value] of groupEntries("components")) {
5401
5566
  if (!isMap(value)) {
5402
- violations.push(violation6("medium", "design-md.tokens.components-shape", `component token "${name}" must be a map of properties (design-md-spec §2.8)`, "give it backgroundColor/textColor/typography/rounded/padding/height"));
5567
+ violations.push(violation6("medium", "design-md.tokens.components-shape", `component token "${name}" must be a map of properties (design-md-spec \u00a72.8)`, "give it backgroundColor/textColor/typography/rounded/padding/height"));
5403
5568
  continue;
5404
5569
  }
5405
5570
  for (const [prop, v] of Object.entries(value)) {
@@ -5415,7 +5580,7 @@ function validateDesignTokenFrontmatter(frontmatterText) {
5415
5580
  const [, refGroup, refKey] = ref;
5416
5581
  const resolves = REF_GROUPS.includes(refGroup) && groupIsMap(refGroup) && Object.prototype.hasOwnProperty.call(fm[refGroup], refKey);
5417
5582
  if (!resolves) {
5418
- violations.push(violation6("medium", "design-md.tokens.ref-unresolved", `component "${name}" references "${v}" which does not resolve to an active token in this frontmatter (design-md-spec §6 {path} refs MUST trace back to a key)`, `add the referenced token or use a literal value`));
5583
+ violations.push(violation6("medium", "design-md.tokens.ref-unresolved", `component "${name}" references "${v}" which does not resolve to an active token in this frontmatter (design-md-spec \u00a76 \u2014 {path} refs MUST trace back to a key)`, `add the referenced token or use a literal value`));
5419
5584
  }
5420
5585
  }
5421
5586
  }
@@ -5427,7 +5592,7 @@ function assertLightDarkParity(lightFm, darkFm) {
5427
5592
  const light = parseDesignFrontmatter(lightFm);
5428
5593
  const dark = parseDesignFrontmatter(darkFm);
5429
5594
  if (light === null || dark === null) {
5430
- violations.push(violation6("medium", "design-md.parity.missing-frontmatter", `light/dark parity needs a YAML frontmatter in both files ${light === null ? "DESIGN.md" : "DESIGN.dark.md"} has none (design-md-spec §4 rules 1–2)`, "add the fenced frontmatter to both theme files"));
5595
+ violations.push(violation6("medium", "design-md.parity.missing-frontmatter", `light/dark parity needs a YAML frontmatter in both files \u2014 ${light === null ? "DESIGN.md" : "DESIGN.dark.md"} has none (design-md-spec \u00a74 rules 1\u20132)`, "add the fenced frontmatter to both theme files"));
5431
5596
  return { ok: false, violations };
5432
5597
  }
5433
5598
  const activeKeys = (fm) => {
@@ -5445,12 +5610,12 @@ function assertLightDarkParity(lightFm, darkFm) {
5445
5610
  const darkKeys = activeKeys(dark);
5446
5611
  for (const key of lightKeys) {
5447
5612
  if (!darkKeys.has(key)) {
5448
- violations.push(violation6("medium", "design-md.parity.missing-dark", `token "${key}" is active in DESIGN.md but missing from DESIGN.dark.md both files must define the same token set (design-md-spec §4 rule 3)`, "add the token to DESIGN.dark.md with a dark-appropriate value"));
5613
+ violations.push(violation6("medium", "design-md.parity.missing-dark", `token "${key}" is active in DESIGN.md but missing from DESIGN.dark.md \u2014 both files must define the same token set (design-md-spec \u00a74 rule 3)`, "add the token to DESIGN.dark.md with a dark-appropriate value"));
5449
5614
  }
5450
5615
  }
5451
5616
  for (const key of darkKeys) {
5452
5617
  if (!lightKeys.has(key)) {
5453
- violations.push(violation6("medium", "design-md.parity.missing-light", `token "${key}" is active in DESIGN.dark.md but missing from DESIGN.md DESIGN.md is the SSOT for token names (design-md-spec §4 rules 3–4)`, "add the token to DESIGN.md, or remove it from the dark file"));
5618
+ violations.push(violation6("medium", "design-md.parity.missing-light", `token "${key}" is active in DESIGN.dark.md but missing from DESIGN.md \u2014 DESIGN.md is the SSOT for token names (design-md-spec \u00a74 rules 3\u20134)`, "add the token to DESIGN.md, or remove it from the dark file"));
5454
5619
  }
5455
5620
  }
5456
5621
  return { ok: violations.length === 0, violations };
@@ -5643,7 +5808,7 @@ function slugify(title) {
5643
5808
  return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
5644
5809
  }
5645
5810
  var escapeCell = (value) => value.replace(/\|/g, "\\|");
5646
- var truncate = (value, max) => value.length > max ? `${value.slice(0, max)}…` : value;
5811
+ var truncate = (value, max) => value.length > max ? `${value.slice(0, max)}\u2026` : value;
5647
5812
  function renderPlanFile(finding, plannedAt) {
5648
5813
  const sections = [
5649
5814
  `# ${finding.title}`,
@@ -5682,14 +5847,14 @@ function renderIndex(params) {
5682
5847
  const { date, repoName, repoShortSha, rows, rejected } = params;
5683
5848
  const findingsRows = rows.map((r) => `| ${r.num} | ${escapeCell(r.title)} | ${r.category} | ${escapeCell(truncate(r.impact, 80))} | ${r.effort} | ${r.risk} | ${r.confidence} | ${escapeCell(truncate(r.evidence, 80))} |`).join(`
5684
5849
  `);
5685
- const directionRows = rows.filter((r) => r.category === "direction").map((r) => `- ${escapeCell(r.title)} ${escapeCell(truncate(r.impact, 120))}`).join(`
5850
+ const directionRows = rows.filter((r) => r.category === "direction").map((r) => `- ${escapeCell(r.title)} \u2014 ${escapeCell(truncate(r.impact, 120))}`).join(`
5686
5851
  `);
5687
5852
  const executionRows = rows.map((r) => `| ${r.num} | ${escapeCell(r.title)} | ${r.priority} | ${r.effort} | ${r.dependsOn} | TODO |`).join(`
5688
5853
  `);
5689
5854
  const rejectedRows = rejected.map((r) => `- ${escapeCell(r.title)}: ${escapeCell(r.reason)}`).join(`
5690
5855
  `);
5691
5856
  const sections = [
5692
- `# Audit Report ${repoName} @ ${repoShortSha} (${date})`,
5857
+ `# Audit Report \u2014 ${repoName} @ ${repoShortSha} (${date})`,
5693
5858
  "",
5694
5859
  "## Findings",
5695
5860
  "",
@@ -5704,6 +5869,7 @@ function renderIndex(params) {
5704
5869
  if (rejectedRows !== "") {
5705
5870
  sections.push("", "## Findings considered and rejected", "", rejectedRows);
5706
5871
  }
5872
+ sections.push("", "## Red-team dispositions", "", "- <finding>: <survived / refuted / hallucination-dropped / uncovered-kept>, <one-line reason>");
5707
5873
  return `${sections.join(`
5708
5874
  `)}
5709
5875
  `;
@@ -5738,14 +5904,14 @@ function scaffoldAuditPlan(outDir, findings, options = {}) {
5738
5904
  return {
5739
5905
  num: file.slice(0, 3),
5740
5906
  title: summary.title,
5741
- category: fields.get("Category") ?? "",
5907
+ category: fields.get("Category") ?? "\u2014",
5742
5908
  impact: "see plan file",
5743
- effort: fields.get("Effort") ?? "",
5744
- risk: fields.get("Risk") ?? "",
5745
- confidence: "",
5746
- evidence: fields.get("Evidence") ?? "",
5747
- priority: fields.get("Priority") ?? "",
5748
- dependsOn: fields.get("Depends on") ?? ""
5909
+ effort: fields.get("Effort") ?? "\u2014",
5910
+ risk: fields.get("Risk") ?? "\u2014",
5911
+ confidence: "\u2014",
5912
+ evidence: fields.get("Evidence") ?? "\u2014",
5913
+ priority: fields.get("Priority") ?? "\u2014",
5914
+ dependsOn: fields.get("Depends on") ?? "\u2014"
5749
5915
  };
5750
5916
  });
5751
5917
  const byNum = new Map(rows.map((r) => [r.num, r]));
@@ -5939,7 +6105,7 @@ function validateSchemaYaml(frontmatterText) {
5939
6105
  const violations = [];
5940
6106
  const doc = parseYamlLite(frontmatterText);
5941
6107
  if (doc === null) {
5942
- violations.push(violation8("medium", "compound.schema.missing-frontmatter", "no `---` YAML frontmatter block found knowledge docs must open with the schema.yaml contract (mstar-compound/references/schema.yaml)", "add the fenced frontmatter with module, date, problem_type, category, severity"));
6108
+ violations.push(violation8("medium", "compound.schema.missing-frontmatter", "no `---` YAML frontmatter block found \u2014 knowledge docs must open with the schema.yaml contract (mstar-compound/references/schema.yaml)", "add the fenced frontmatter with module, date, problem_type, category, severity"));
5943
6109
  return { ok: false, violations };
5944
6110
  }
5945
6111
  const isStr = (v) => typeof v === "string";
@@ -5953,14 +6119,14 @@ function validateSchemaYaml(frontmatterText) {
5953
6119
  }
5954
6120
  const problemType = doc.problem_type;
5955
6121
  if (problemType !== undefined && !isStr(problemType)) {
5956
- violations.push(violation8("medium", "compound.schema.invalid-problem-type", `problem_type "${String(problemType)}" must be a string one of the schema.yaml enum values (bug: build_error…config_error; knowledge: developer_experience…testing_pattern)`, "pick the narrowest applicable problem_type from schema.yaml"));
6122
+ violations.push(violation8("medium", "compound.schema.invalid-problem-type", `problem_type "${String(problemType)}" must be a string \u2014 one of the schema.yaml enum values (bug: build_error\u2026config_error; knowledge: developer_experience\u2026testing_pattern)`, "pick the narrowest applicable problem_type from schema.yaml"));
5957
6123
  }
5958
6124
  const problemTypeValid = isStr(problemType) && KNOWLEDGE_PROBLEM_TYPES.includes(problemType);
5959
6125
  if (isStr(problemType) && !problemTypeValid) {
5960
- violations.push(violation8("medium", "compound.schema.invalid-problem-type", `problem_type "${problemType}" is not one of the schema.yaml enum values (bug: build_error…config_error; knowledge: developer_experience…testing_pattern)`, "pick the narrowest applicable problem_type from schema.yaml"));
6126
+ violations.push(violation8("medium", "compound.schema.invalid-problem-type", `problem_type "${problemType}" is not one of the schema.yaml enum values (bug: build_error\u2026config_error; knowledge: developer_experience\u2026testing_pattern)`, "pick the narrowest applicable problem_type from schema.yaml"));
5961
6127
  }
5962
6128
  if (doc.severity !== undefined && !isStr(doc.severity)) {
5963
- violations.push(violation8("medium", "compound.schema.invalid-severity", `severity "${String(doc.severity)}" must be a string critical | high | medium | low (schema.yaml required_fields.severity)`, "use one of the four severity values"));
6129
+ violations.push(violation8("medium", "compound.schema.invalid-severity", `severity "${String(doc.severity)}" must be a string \u2014 critical | high | medium | low (schema.yaml required_fields.severity)`, "use one of the four severity values"));
5964
6130
  }
5965
6131
  if (isStr(doc.severity) && !KNOWLEDGE_SEVERITIES.includes(doc.severity)) {
5966
6132
  violations.push(violation8("medium", "compound.schema.invalid-severity", `severity "${doc.severity}" must be critical | high | medium | low (schema.yaml required_fields.severity)`, "use one of the four severity values"));
@@ -5968,7 +6134,7 @@ function validateSchemaYaml(frontmatterText) {
5968
6134
  if (problemTypeValid && isStr(doc.category)) {
5969
6135
  const expected = KNOWLEDGE_CATEGORY_MAP[problemType];
5970
6136
  if (doc.category !== expected) {
5971
- violations.push(violation8("medium", "compound.schema.category-mismatch", `category "${doc.category}" does not match problem_type "${problemType}" category-mapping.md rule 1 maps it to "${expected}"`, `set \`category: ${expected}\` (the directory name under {KNOWLEDGE_DIR})`));
6137
+ violations.push(violation8("medium", "compound.schema.category-mismatch", `category "${doc.category}" does not match problem_type "${problemType}" \u2014 category-mapping.md rule 1 maps it to "${expected}"`, `set \`category: ${expected}\` (the directory name under {KNOWLEDGE_DIR})`));
5972
6138
  }
5973
6139
  }
5974
6140
  if (problemTypeValid) {
@@ -5999,7 +6165,7 @@ function validateSchemaYaml(frontmatterText) {
5999
6165
  if (!Array.isArray(doc.tags)) {
6000
6166
  violations.push(violation8("low", "compound.schema.invalid-tags", "optional `tags` must be a YAML list (schema.yaml optional_fields.tags)", "list lowercase, hyphen-separated keywords"));
6001
6167
  } else if (doc.tags.length > 8) {
6002
- violations.push(violation8("low", "compound.schema.tags-too-many", `tags has ${doc.tags.length} entries max 8 (schema.yaml optional_fields.tags.max_items)`, "trim the tag list to at most 8 keywords"));
6168
+ violations.push(violation8("low", "compound.schema.tags-too-many", `tags has ${doc.tags.length} entries \u2014 max 8 (schema.yaml optional_fields.tags.max_items)`, "trim the tag list to at most 8 keywords"));
6003
6169
  }
6004
6170
  }
6005
6171
  if (doc.last_updated !== undefined && (!isStr(doc.last_updated) || !DATE_RE3.test(doc.last_updated))) {
@@ -6047,7 +6213,7 @@ function assertIndexRows(knowledgeDir) {
6047
6213
  const violations = [];
6048
6214
  const readmePath = join8(knowledgeDir, "README.md");
6049
6215
  if (!existsSync5(readmePath)) {
6050
- violations.push(violation8("medium", "compound.index.missing-readme", `missing ${readmePath} the knowledge index is required (mstar-compound Phase 6: every doc gets a README.md row)`, "create knowledge/README.md with a Document / Source Plan / Description / Status table"));
6216
+ violations.push(violation8("medium", "compound.index.missing-readme", `missing ${readmePath} \u2014 the knowledge index is required (mstar-compound Phase 6: every doc gets a README.md row)`, "create knowledge/README.md with a Document / Source Plan / Description / Status table"));
6051
6217
  return { ok: false, violations };
6052
6218
  }
6053
6219
  const docs = collectKnowledgeDocs(knowledgeDir);
@@ -6086,7 +6252,7 @@ function scopeGuard(path2, allowedRoots) {
6086
6252
  return {
6087
6253
  ok: false,
6088
6254
  violations: [
6089
- violation8("medium", "compound.scope.outside", `path "${path2}" is outside the compound-refresh scope (allowed: ${allowedRoots.join(", ")}) compound-refresh operates only on {HARNESS_DIR}/knowledge/**, {HARNESS_DIR}/knowledge/README.md, <repo-root>/CONCEPTS.md, {HARNESS_DIR}/status.json (mstar-compound-refresh SKILL.md § 产物与操作路径)`, "point the operation at one of the allowed paths")
6255
+ violation8("medium", "compound.scope.outside", `path "${path2}" is outside the compound-refresh scope (allowed: ${allowedRoots.join(", ")}) \u2014 compound-refresh operates only on {HARNESS_DIR}/knowledge/**, {HARNESS_DIR}/knowledge/README.md, <repo-root>/CONCEPTS.md, {HARNESS_DIR}/status.json (mstar-compound-refresh SKILL.md \u00a7 \u4ea7\u7269\u4e0e\u64cd\u4f5c\u8def\u5f84)`, "point the operation at one of the allowed paths")
6090
6256
  ]
6091
6257
  };
6092
6258
  }
@@ -6133,16 +6299,36 @@ function findTemporaryMarkers(fileText) {
6133
6299
  }
6134
6300
  markers.push({ line: i + 1, text, removalPath });
6135
6301
  if (removalPath === null) {
6136
- violations.push(violation9("medium", "lint.temporary.no-removal-path", `temporary marker at line ${i + 1} records no removal path (plan/status artifact reference) record one before claiming the task complete (mstar-coding-behavior § Simplification markers)`, 'add a plan/status reference to the marker, e.g. "removal tracked in status.json" or "plan 20260808-slice2 removes this"'));
6302
+ violations.push(violation9("medium", "lint.temporary.no-removal-path", `temporary marker at line ${i + 1} records no removal path (plan/status artifact reference) \u2014 record one before claiming the task complete (mstar-coding-behavior \u00a7 Simplification markers)`, 'add a plan/status reference to the marker, e.g. "removal tracked in status.json" or "plan 20260808-slice2 removes this"'));
6137
6303
  }
6138
6304
  }
6139
6305
  return { ok: violations.length === 0, violations, markers };
6140
6306
  }
6307
+ var TASK_ARTIFACT_RE = /\btask-\d+(?:-(?:brief|report|fix-report|diff)|\.diff)\b/g;
6308
+ var SDD_DEEPLINK_RE = /\.(?:mstar|agents)\/sdd\/([^\s/<>{}\[\]"'\*\?]+)/g;
6309
+ function findEphemeralCitations(skillText) {
6310
+ const citations = [];
6311
+ const lines = skillText.split(/\r?\n/);
6312
+ for (let i = 0;i < lines.length; i++) {
6313
+ const found = [];
6314
+ for (const m of lines[i].matchAll(TASK_ARTIFACT_RE)) {
6315
+ found.push({ index: m.index, match: m[0], kind: "task-artifact" });
6316
+ }
6317
+ for (const m of lines[i].matchAll(SDD_DEEPLINK_RE)) {
6318
+ found.push({ index: m.index, match: m[0], kind: "sdd-deeplink" });
6319
+ }
6320
+ found.sort((a, b) => a.index - b.index);
6321
+ for (const f of found) {
6322
+ citations.push({ line: i + 1, match: f.match, kind: f.kind });
6323
+ }
6324
+ }
6325
+ return citations;
6326
+ }
6141
6327
  var TEST_FILE_PATH_RE = /[\w./-]+\.(?:test|spec)\.[a-z0-9]+/i;
6142
6328
  var TEST_FILE_PHRASE_RE = /\btest files?\b/i;
6143
6329
  var COMMAND_PROMPT_RE = /^\s*[$>]\s*\S/;
6144
6330
  var RUNNER_RE = /\b(?:bun|pnpm|npm|yarn|npx|bunx)\s+(?:test|run|exec)\b|\b(?:npx|bunx)\s+[\w./-]+\b|\b(?:tsc|vitest|jest|mocha|pytest)\b|\bgo\s+test\b|\bcargo\s+test\b/i;
6145
- var OUTPUT_TOKEN_RE = /[✓✔✗✘]|\b(?:PASS|FAIL)\b|\b\d+\s+(?:pass(?:es|ed)?|fail(?:s|ed|ing)?|skipped|tests?|ok)\b|\bok\s+\d+\b|\ball\s+ok\b|exit(?:ed)?\s+(?:with\s+)?(?:code\s+)?\d+/i;
6331
+ var OUTPUT_TOKEN_RE = /[\u2713\u2714\u2717\u2718]|\b(?:PASS|FAIL)\b|\b\d+\s+(?:pass(?:es|ed)?|fail(?:s|ed|ing)?|skipped|tests?|ok)\b|\bok\s+\d+\b|\ball\s+ok\b|exit(?:ed)?\s+(?:with\s+)?(?:code\s+)?\d+/i;
6146
6332
  function assertSddTddTriple(reportText) {
6147
6333
  const violations = [];
6148
6334
  const lines = reportText.split(/\r?\n/);
@@ -6160,13 +6346,13 @@ function assertSddTddTriple(reportText) {
6160
6346
  break;
6161
6347
  }
6162
6348
  if (!hasTests) {
6163
- violations.push(violation9("medium", "lint.sdd-tdd.missing-tests", "task report carries no test file reference the TDD triple needs covering test file(s) (mstar-coding-behavior § Integration Notes; mstar-sdd/references/file-handoffs.md)", 'add a "Covering test file(s): <path>.test.ts" line or a `.test.<ext>` path to the report'));
6349
+ violations.push(violation9("medium", "lint.sdd-tdd.missing-tests", "task report carries no test file reference \u2014 the TDD triple needs covering test file(s) (mstar-coding-behavior \u00a7 Integration Notes; mstar-sdd/references/file-handoffs.md)", 'add a "Covering test file(s): <path>.test.ts" line or a `.test.<ext>` path to the report'));
6164
6350
  }
6165
6351
  if (!hasCommand) {
6166
- violations.push(violation9("medium", "lint.sdd-tdd.missing-command", "task report carries no command the TDD triple needs the exact command run (mstar-coding-behavior § Integration Notes; mstar-sdd/references/file-handoffs.md)", 'add a "Command run: `bun test <file>`" line to the report'));
6352
+ violations.push(violation9("medium", "lint.sdd-tdd.missing-command", "task report carries no command \u2014 the TDD triple needs the exact command run (mstar-coding-behavior \u00a7 Integration Notes; mstar-sdd/references/file-handoffs.md)", 'add a "Command run: `bun test <file>`" line to the report'));
6167
6353
  }
6168
6354
  if (!hasOutput) {
6169
- violations.push(violation9("medium", "lint.sdd-tdd.missing-output", "task report carries no output evidence the TDD triple needs the run output (pass/fail counts or exit code) (mstar-coding-behavior § Integration Notes; mstar-sdd/references/file-handoffs.md)", 'paste the test-run output (e.g. "12 pass / 0 fail") into the report'));
6355
+ violations.push(violation9("medium", "lint.sdd-tdd.missing-output", "task report carries no output evidence \u2014 the TDD triple needs the run output (pass/fail counts or exit code) (mstar-coding-behavior \u00a7 Integration Notes; mstar-sdd/references/file-handoffs.md)", 'paste the test-run output (e.g. "12 pass / 0 fail") into the report'));
6170
6356
  }
6171
6357
  return { ok: violations.length === 0, violations };
6172
6358
  }
@@ -6219,18 +6405,18 @@ function lintSkillFrontmatter(frontmatterText) {
6219
6405
  const violations = [];
6220
6406
  const fm = parseFrontmatter(frontmatterText);
6221
6407
  if (fm === null) {
6222
- violations.push(violation9("medium", "lint.frontmatter.missing", "no YAML frontmatter block found a skill file must open with a `---` fenced frontmatter (mstar-skill-authoring § Frontmatter Contract)", "add a frontmatter block with `name` and `description` at the top of the file"));
6408
+ violations.push(violation9("medium", "lint.frontmatter.missing", "no YAML frontmatter block found \u2014 a skill file must open with a `---` fenced frontmatter (mstar-skill-authoring \u00a7 Frontmatter Contract)", "add a frontmatter block with `name` and `description` at the top of the file"));
6223
6409
  return { ok: false, violations };
6224
6410
  }
6225
6411
  const name = fm.name ?? "";
6226
6412
  if (name === "") {
6227
- violations.push(violation9("medium", "lint.frontmatter.name.missing", "frontmatter `name` is missing required (mstar-skill-authoring § Frontmatter Contract)", "add `name: <lowercase-hyphen-id>` to the frontmatter"));
6413
+ violations.push(violation9("medium", "lint.frontmatter.name.missing", "frontmatter `name` is missing \u2014 required (mstar-skill-authoring \u00a7 Frontmatter Contract)", "add `name: <lowercase-hyphen-id>` to the frontmatter"));
6228
6414
  } else if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)) {
6229
- violations.push(violation9("medium", "lint.frontmatter.name.format", `frontmatter \`name\` must be lowercase-hyphen ("${name}") e.g. example-skill (mstar-skill-authoring § Frontmatter Contract)`, "rename to a stable lowercase-hyphen id, e.g. `name: example-skill`"));
6415
+ violations.push(violation9("medium", "lint.frontmatter.name.format", `frontmatter \`name\` must be lowercase-hyphen ("${name}") \u2014 e.g. example-skill (mstar-skill-authoring \u00a7 Frontmatter Contract)`, "rename to a stable lowercase-hyphen id, e.g. `name: example-skill`"));
6230
6416
  }
6231
6417
  const description = fm.description ?? "";
6232
6418
  if (description === "") {
6233
- violations.push(violation9("medium", "lint.frontmatter.description.missing", "frontmatter `description` is missing the trigger contract is required (mstar-skill-authoring § Frontmatter Contract)", "add a `description:` that states when the skill loads (symptoms, context, roles, exclusions)"));
6419
+ violations.push(violation9("medium", "lint.frontmatter.description.missing", "frontmatter `description` is missing \u2014 the trigger contract is required (mstar-skill-authoring \u00a7 Frontmatter Contract)", "add a `description:` that states when the skill loads (symptoms, context, roles, exclusions)"));
6234
6420
  } else {
6235
6421
  const stripped = description.replace(/`[^`]*`/g, " ").replace(/'[^']*'/g, " ").replace(/"[^"]*"/g, " ");
6236
6422
  let pronoun = null;
@@ -6241,15 +6427,15 @@ function lintSkillFrontmatter(frontmatterText) {
6241
6427
  break;
6242
6428
  }
6243
6429
  if (pronoun !== null) {
6244
- violations.push(violation9("low", "lint.frontmatter.description.person", `description uses first/second-person pronoun "${pronoun[0]}" keep the trigger contract third person (mstar-skill-authoring § Frontmatter Contract)`, 'rewrite without I/we/you/my/our/your/us, e.g. "Use when the user asks "'));
6430
+ violations.push(violation9("low", "lint.frontmatter.description.person", `description uses first/second-person pronoun "${pronoun[0]}" \u2014 keep the trigger contract third person (mstar-skill-authoring \u00a7 Frontmatter Contract)`, 'rewrite without I/we/you/my/our/your/us, e.g. "Use when the user asks \u2026"'));
6245
6431
  }
6246
6432
  const start = description.trim().replace(/^[*_#>]+/, "").replace(/^["'`]+/, "").trim();
6247
6433
  if (WORKFLOW_VERB_START_RE.test(start)) {
6248
- violations.push(violation9("low", "lint.frontmatter.description.workflow", 'description reads as a workflow summary ("Explains/Describes/Covers ") the description is the trigger contract, not a summary of steps (mstar-skill-authoring § Frontmatter Contract)', "describe when to load the skill (symptoms, context, roles, exclusions) instead of summarizing its steps"));
6434
+ violations.push(violation9("low", "lint.frontmatter.description.workflow", 'description reads as a workflow summary ("Explains/Describes/Covers \u2026") \u2014 the description is the trigger contract, not a summary of steps (mstar-skill-authoring \u00a7 Frontmatter Contract)', "describe when to load the skill (symptoms, context, roles, exclusions) instead of summarizing its steps"));
6249
6435
  } else {
6250
6436
  const words = description.trim().split(/\s+/).filter(Boolean).length;
6251
6437
  if (words > DESCRIPTION_MAX_WORDS) {
6252
- violations.push(violation9("low", "lint.frontmatter.description.workflow", `description is ${words} words paragraph-length summaries bury the trigger contract (threshold ${DESCRIPTION_MAX_WORDS}, above the longest corpus description at 114 words, mstar-design-md; mstar-skill-authoring § Frontmatter Contract)`, "trim the description to a scannable trigger contract and move detail into the body"));
6438
+ violations.push(violation9("low", "lint.frontmatter.description.workflow", `description is ${words} words \u2014 paragraph-length summaries bury the trigger contract (threshold ${DESCRIPTION_MAX_WORDS}, above the longest corpus description at 114 words, mstar-design-md; mstar-skill-authoring \u00a7 Frontmatter Contract)`, "trim the description to a scannable trigger contract and move detail into the body"));
6253
6439
  }
6254
6440
  }
6255
6441
  }
@@ -6296,7 +6482,157 @@ function lintStrategySections(docText) {
6296
6482
  }
6297
6483
  for (const required of REQUIRED_STRATEGY_SECTIONS) {
6298
6484
  if (!headings.has(required.toLowerCase())) {
6299
- violations.push(violation9("medium", "lint.strategy.missing-section", `missing required section "${required}" (mstar-strategy § STRATEGY.md structure)`, "add a `## <Section>` heading; required: Vision, What we build, What we don't build, Guiding Principles, Technology Direction, Decision Log"));
6485
+ violations.push(violation9("medium", "lint.strategy.missing-section", `missing required section "${required}" (mstar-strategy \u00a7 STRATEGY.md structure)`, "add a `## <Section>` heading; required: Vision, What we build, What we don't build, Guiding Principles, Technology Direction, Decision Log"));
6486
+ }
6487
+ }
6488
+ return { ok: violations.length === 0, violations };
6489
+ }
6490
+ function violation10(severity, code, message, fix) {
6491
+ return { ok: false, severity, code, message, fix };
6492
+ }
6493
+ var ROLE_MAPPING = [
6494
+ { agentId: "project-manager", reference: "references/project-manager.md" },
6495
+ { agentId: "product-manager", reference: "references/product-manager.md" },
6496
+ { agentId: "architect", reference: "references/architect.md" },
6497
+ { agentId: "code-reviewer", reference: "references/code-reviewer.md" },
6498
+ { agentId: "fullstack-dev", reference: "references/fullstack-dev-shared.md" },
6499
+ { agentId: "fullstack-dev-2", reference: "references/fullstack-dev-shared.md" },
6500
+ { agentId: "frontend-dev", reference: "references/frontend-dev.md" },
6501
+ { agentId: "qa-engineer", reference: "references/qa-engineer.md" },
6502
+ { agentId: "qc-specialist", reference: "references/qc-specialist-shared.md" },
6503
+ { agentId: "qc-specialist-2", reference: "references/qc-specialist-shared.md" },
6504
+ { agentId: "qc-specialist-3", reference: "references/qc-specialist-shared.md" },
6505
+ { agentId: "ops-engineer", reference: "references/ops-engineer.md" },
6506
+ { agentId: "writing-specialist", reference: "references/writing-specialist.md" },
6507
+ { agentId: "prompt-engineer", reference: "references/prompt-engineer.md" }
6508
+ ];
6509
+ var SHARED_FAMILIES = [
6510
+ { family: "fullstack-dev", memberIds: ["fullstack-dev", "fullstack-dev-2"] },
6511
+ { family: "qc-specialist", memberIds: ["qc-specialist", "qc-specialist-2", "qc-specialist-3"] }
6512
+ ];
6513
+ var DEV_TRACK_PARAMS = [
6514
+ { roleId: "fullstack-dev", track: "primary" },
6515
+ { roleId: "fullstack-dev-2", track: "parallel_secondary" }
6516
+ ];
6517
+ var QC_REVIEWER_PARAMS = [
6518
+ {
6519
+ roleId: "qc-specialist",
6520
+ reviewerIndex: 1,
6521
+ focus: "Architecture coherence and maintainability risk",
6522
+ reportSuffix: "qc1"
6523
+ },
6524
+ {
6525
+ roleId: "qc-specialist-2",
6526
+ reviewerIndex: 2,
6527
+ focus: "Security and correctness risk",
6528
+ reportSuffix: "qc2"
6529
+ },
6530
+ {
6531
+ roleId: "qc-specialist-3",
6532
+ reviewerIndex: 3,
6533
+ focus: "Performance and reliability risk",
6534
+ reportSuffix: "qc3"
6535
+ }
6536
+ ];
6537
+ function validateRoleMapping(rolesDir, options = {}) {
6538
+ const mapping = options.mapping ?? ROLE_MAPPING;
6539
+ const families = options.families ?? SHARED_FAMILIES;
6540
+ const devTrack = options.devTrack ?? DEV_TRACK_PARAMS;
6541
+ const qcReviewers = options.qcReviewers ?? QC_REVIEWER_PARAMS;
6542
+ const violations = [];
6543
+ const referenceById = new Map(mapping.map((m) => [m.agentId, m.reference]));
6544
+ for (const { agentId, reference } of mapping) {
6545
+ if (!existsSync6(join9(rolesDir, reference))) {
6546
+ violations.push(violation10("medium", "roles.mapping.reference.missing", `role "${agentId}" maps to ${reference} which does not exist under ${rolesDir} (mstar-roles \u00a7 Role Reference Mapping)`, `create ${join9(rolesDir, reference)} or fix the mapping row`));
6547
+ }
6548
+ }
6549
+ for (const { family, memberIds } of families) {
6550
+ const absent = memberIds.filter((id) => !referenceById.has(id));
6551
+ for (const id of absent) {
6552
+ violations.push(violation10("medium", "roles.mapping.family.member.missing", `shared family "${family}" member "${id}" is absent from the role mapping (mstar-roles \u00a7 Role Reference Mapping)`, `add "${id}" to the mapping`));
6553
+ }
6554
+ if (absent.length === 0) {
6555
+ const refs = new Set(memberIds.map((id) => referenceById.get(id)));
6556
+ if (refs.size !== 1) {
6557
+ violations.push(violation10("medium", "roles.mapping.family.shared", `shared family "${family}" (${memberIds.join(", ")}) must resolve to ONE shared reference file \u2014 got ${[...refs].join(", ")} (mstar-roles \u00a7 Maintenance Rules: "Keep shared-family roles on one shared reference file")`, `point every "${family}" member at the same references/<role>-shared.md`));
6558
+ }
6559
+ }
6560
+ }
6561
+ const tableByRole = new Map;
6562
+ const checkParamRoles = (rows, table) => {
6563
+ for (const row of rows) {
6564
+ const existing = tableByRole.get(row.roleId);
6565
+ if (existing !== undefined) {
6566
+ violations.push(violation10("medium", "roles.param.role.duplicate", `role "${row.roleId}" appears in both the ${existing} and ${table} parameter rows (mstar-roles \u00a7 Parameter Table (SSOT))`, "remove the duplicate row"));
6567
+ } else {
6568
+ tableByRole.set(row.roleId, table);
6569
+ }
6570
+ if (!referenceById.has(row.roleId)) {
6571
+ violations.push(violation10("medium", "roles.param.role.missing", `${table} parameter row references unknown role "${row.roleId}" (mstar-roles \u00a7 Parameter Table (SSOT))`, `add "${row.roleId}" to the role mapping or drop the row`));
6572
+ }
6573
+ }
6574
+ };
6575
+ checkParamRoles(devTrack, "dev track");
6576
+ checkParamRoles(qcReviewers, "QC reviewer");
6577
+ for (const row of devTrack) {
6578
+ if (row.track !== "primary" && row.track !== "parallel_secondary") {
6579
+ violations.push(violation10("medium", "roles.param.track", `dev track for "${row.roleId}" is "${String(row.track)}" \u2014 must be primary or parallel_secondary (mstar-roles \u00a7 Parameter Table (SSOT))`, 'set track to "primary" or "parallel_secondary"'));
6580
+ }
6581
+ }
6582
+ const indices = qcReviewers.map((r) => r.reviewerIndex).sort((a, b) => a - b);
6583
+ const unique = new Set(indices);
6584
+ if (indices.length !== 3 || unique.size !== 3 || indices[0] !== 1 || indices[1] !== 2 || indices[2] !== 3) {
6585
+ violations.push(violation10("high", "roles.param.qc.index.set", `QC reviewer_index must be exactly {1, 2, 3} across the three qc-specialist* seats \u2014 got [${indices.join(", ")}] (mstar-roles \u00a7 Parameter Table (SSOT))`, "assign reviewer_index 1/2/3 to qc-specialist / qc-specialist-2 / qc-specialist-3"));
6586
+ }
6587
+ for (const row of qcReviewers) {
6588
+ if (row.focus.trim() === "") {
6589
+ violations.push(violation10("medium", "roles.param.qc.focus.missing", `QC seat "${row.roleId}" (reviewer_index ${row.reviewerIndex}) has an empty focus (mstar-roles \u00a7 Parameter Table (SSOT))`, "add the review focus"));
6590
+ }
6591
+ if (row.reportSuffix !== `qc${row.reviewerIndex}`) {
6592
+ violations.push(violation10("medium", "roles.param.qc.suffix", `QC seat "${row.roleId}" report_suffix "${row.reportSuffix}" must equal qc${row.reviewerIndex} \u2014 tri reports land at {SDD_DIR}/review/qc1.md\u2026qc3.md (mstar-roles \u00a7 Parameter Table (SSOT))`, `set report_suffix to qc${row.reviewerIndex}`));
6593
+ }
6594
+ }
6595
+ return { ok: violations.length === 0, violations };
6596
+ }
6597
+ var LOAD_ORDER_HEADING_RE = /^#{1,6}\s+[^\r\n]*\b(?:load[\s-]*order|first\s+action)\b[^\r\n]*$/i;
6598
+ function extractLoadOrderSection(text) {
6599
+ const lines = text.split(/\r?\n/);
6600
+ let start = -1;
6601
+ let level = 0;
6602
+ for (let i = 0;i < lines.length; i++) {
6603
+ const m = /^(#{1,6})\s+/.exec(lines[i]);
6604
+ if (m === null)
6605
+ continue;
6606
+ if (LOAD_ORDER_HEADING_RE.test(lines[i])) {
6607
+ start = i;
6608
+ level = m[1].length;
6609
+ break;
6610
+ }
6611
+ }
6612
+ if (start === -1)
6613
+ return null;
6614
+ const section = [lines[start]];
6615
+ for (let i = start + 1;i < lines.length; i++) {
6616
+ const m = /^(#{1,6})\s+/.exec(lines[i]);
6617
+ if (m !== null && m[1].length <= level)
6618
+ break;
6619
+ section.push(lines[i]);
6620
+ }
6621
+ return section.join(`
6622
+ `);
6623
+ }
6624
+ function lintLoadOrder(skillTexts) {
6625
+ const violations = [];
6626
+ for (const [name, text] of Object.entries(skillTexts)) {
6627
+ if (!name.startsWith("mstar-") || name === "mstar-harness-core")
6628
+ continue;
6629
+ const section = extractLoadOrderSection(text);
6630
+ if (section === null) {
6631
+ violations.push(violation10("medium", "roles.loadorder.section.missing", `skill "${name}" has no Load Order / First action section \u2014 every mstar-* topic skill must declare its first read (mstar-harness-core \u00a7 \u52a0\u8f7d\u7ea6\u5b9a; mstar-roles \u00a7 Load Order (Required))`, `add a "## Load Order" section naming mstar-harness-core as the first read`));
6632
+ continue;
6633
+ }
6634
+ if (!section.includes("mstar-harness-core")) {
6635
+ violations.push(violation10("medium", "roles.loadorder.core.missing", `skill "${name}" Load Order section does not declare mstar-harness-core as its first dependency (mstar-harness-core \u00a7 \u52a0\u8f7d\u7ea6\u5b9a: \u51e1 mstar-*\uff08name \u2260 mstar-harness-core\uff09\u5047\u5b9a\u8bfb\u8005\u5df2 Read \u672c skill)`, "name mstar-harness-core first in the Load Order section"));
6300
6636
  }
6301
6637
  }
6302
6638
  return { ok: violations.length === 0, violations };
@@ -6319,6 +6655,27 @@ function detectHost(signals2) {
6319
6655
  return "codex";
6320
6656
  return "ambiguous";
6321
6657
  }
6658
+ function resolveSkillRoot(host, paths) {
6659
+ const { skill, rel } = paths;
6660
+ const suffix = rel === undefined || rel === "" ? "" : `/${rel}`;
6661
+ switch (host) {
6662
+ case "omp":
6663
+ return `skill://${skill}${suffix}`;
6664
+ case "cursor":
6665
+ return `~/.cursor/plugins/local/morning-star-harness/skills/${skill}${suffix}`;
6666
+ case "codex":
6667
+ return `skills/${skill}${suffix}`;
6668
+ case "opencode":
6669
+ return `harness-skills/${skill}${suffix}`;
6670
+ case "kimi":
6671
+ case "zcode":
6672
+ return `./skills/${skill}${suffix}`;
6673
+ case "dsh":
6674
+ return `$DSH_BUNDLED_SKILL_DIR/${skill}${suffix}`;
6675
+ case "pi":
6676
+ return `deferred: pi has no plugin API in v1 \u2014 skill-root resolution lands with its adapter (roadmap \u00a78.4)`;
6677
+ }
6678
+ }
6322
6679
  function violation11(severity, code, message, fix) {
6323
6680
  return { ok: false, severity, code, message, fix };
6324
6681
  }
@@ -6329,29 +6686,72 @@ var FIVE_QUESTION_SECTIONS = [
6329
6686
  { key: "evidence", label: "Evidence", question: "what a correct result looks like (success criteria / evidence)" },
6330
6687
  { key: "references", label: "References", question: "additional resources to open when the main path is not enough" }
6331
6688
  ];
6689
+ function stripFrontmatter(text) {
6690
+ const lines = text.replace(/^\uFEFF/, "").split(/\r?\n/);
6691
+ if (lines.length === 0 || lines[0].trim() !== "---")
6692
+ return text;
6693
+ for (let i = 1;i < lines.length; i++) {
6694
+ if (lines[i].trim() === "---")
6695
+ return lines.slice(i + 1).join(`
6696
+ `);
6697
+ }
6698
+ return text;
6699
+ }
6332
6700
  var HEADING_RE = /^#{1,6}\s+[^\r\n]+$/;
6333
- function lintFiveQuestion(bodyText) {
6334
- const headings = bodyText.split(/\r?\n/).filter((line) => HEADING_RE.test(line)).map((line) => line.replace(/^#{1,6}\s+/, "").trim().toLowerCase());
6701
+ function collectHeadings(bodyText) {
6702
+ const headings = [];
6703
+ let inFence = false;
6704
+ for (const line of bodyText.split(/\r?\n/)) {
6705
+ if (line.startsWith("```") || line.startsWith("~~~")) {
6706
+ inFence = !inFence;
6707
+ continue;
6708
+ }
6709
+ if (!inFence && HEADING_RE.test(line)) {
6710
+ headings.push(line.replace(/^#{1,6}\s+/, "").trim().toLowerCase());
6711
+ }
6712
+ }
6713
+ return headings;
6714
+ }
6715
+ var RUNTIME_HEADING_ALIASES = {
6716
+ workflow: ["process", "playbook"],
6717
+ "decision-rules": [
6718
+ "hard rules",
6719
+ "core rules",
6720
+ "rule",
6721
+ "gate",
6722
+ "not to do",
6723
+ "red flags",
6724
+ "\u53cd\u6a21\u5f0f",
6725
+ "\u7ea2\u7ebf",
6726
+ "\u89c4\u5219",
6727
+ "\u95e8\u7981"
6728
+ ],
6729
+ evidence: ["output format", "\u8bc1\u636e"],
6730
+ references: ["dependencies", "\u5173\u7cfb"]
6731
+ };
6732
+ function lintFiveQuestion(bodyText, mode = "authoring") {
6733
+ const headings = collectHeadings(bodyText);
6335
6734
  const violations = [];
6336
6735
  for (const section of FIVE_QUESTION_SECTIONS) {
6337
6736
  const label = section.label.toLowerCase();
6338
- const covered = headings.some((heading) => heading.includes(label));
6737
+ const aliases = mode === "runtime" ? RUNTIME_HEADING_ALIASES[section.key] ?? [] : [];
6738
+ const covered = headings.some((heading) => heading.includes(label) || aliases.some((alias) => heading.includes(alias)));
6339
6739
  if (!covered) {
6340
- violations.push(violation11("low", `skill-authoring.five-question.${section.key}`, `body does not answer "${section.question}" no "${section.label}" section (mstar-skill-authoring § Body 必须回答的 5 / § 默认 Body 结构)`, `add a "## ${section.label}" section covering ${section.question}`));
6740
+ violations.push(violation11("low", `skill-authoring.five-question.${section.key}`, `body does not answer "${section.question}" \u2014 no "${section.label}" section (mstar-skill-authoring \u00a7 Body \u5fc5\u987b\u56de\u7b54\u7684 5 \u95ee / \u00a7 \u9ed8\u8ba4 Body \u7ed3\u6784)`, `add a "## ${section.label}" section covering ${section.question}`));
6341
6741
  }
6342
6742
  }
6343
6743
  return { ok: violations.length === 0, violations };
6344
6744
  }
6345
6745
 
6346
6746
  // ../engine/dist/engine.js
6347
- import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync8, renameSync as renameSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync5 } from "node:fs";
6747
+ import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync8, renameSync as renameSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync5 } from "node:fs";
6348
6748
  import { randomUUID as randomUUID2 } from "node:crypto";
6349
6749
  import { basename as basename5, dirname as dirname5, join as join6, resolve as resolve9 } from "node:path";
6350
6750
  import { fileURLToPath } from "node:url";
6351
6751
  import { dirname as dirname32, isAbsolute as isAbsolute22, join as join32, resolve as resolve32 } from "node:path";
6352
6752
  import { AsyncLocalStorage as AsyncLocalStorage3 } from "node:async_hooks";
6353
6753
  function readJson2(filePath) {
6354
- if (!existsSync6(filePath))
6754
+ if (!existsSync7(filePath))
6355
6755
  return {};
6356
6756
  const content = readFileSync8(filePath, "utf8").trim();
6357
6757
  if (!content)
@@ -6381,7 +6781,7 @@ function resolveProjectRoot(startDir = process.cwd()) {
6381
6781
  const start = resolve9(startDir);
6382
6782
  let dir = start;
6383
6783
  for (;; ) {
6384
- if (existsSync6(join6(dir, "package.json")) || existsSync6(join6(dir, "bun.lock")))
6784
+ if (existsSync7(join6(dir, "package.json")) || existsSync7(join6(dir, "bun.lock")))
6385
6785
  return dir;
6386
6786
  const parent = dirname5(dir);
6387
6787
  if (parent === dir)
@@ -6434,7 +6834,7 @@ var GITIGNORE_SNIPPET2 = `# Morning Star harness (.mstar/)
6434
6834
  !.mstar/specs/
6435
6835
  !.mstar/specs/**
6436
6836
  `;
6437
- var GITIGNORE_SNIPPET_AGENTS2 = `# Morning Star harness (.agents/) legacy
6837
+ var GITIGNORE_SNIPPET_AGENTS2 = `# Morning Star harness (.agents/) \u2014 legacy
6438
6838
  # Default-ignore everything under .agents/, then re-include the tracked results.
6439
6839
  .agents/**
6440
6840
  !.agents/AGENTS.md
@@ -6450,20 +6850,20 @@ var GITIGNORE_PROCESS_ENTRIES_AGENTS2 = GITIGNORE_SNIPPET_AGENTS2.split(`
6450
6850
  function isPlainObject4(value) {
6451
6851
  return typeof value === "object" && value !== null && !Array.isArray(value);
6452
6852
  }
6453
- function violation(severity, code, message, fix) {
6853
+ function violation7(severity, code, message, fix) {
6454
6854
  return { ok: false, severity, code, message, fix };
6455
6855
  }
6456
- function validateNonEmptyString(violations, value, field, missingCode, invalidCode) {
6856
+ function validateNonEmptyString3(violations, value, field, missingCode, invalidCode) {
6457
6857
  if (value === undefined) {
6458
- violations.push(violation("high", missingCode, `missing required field: ${field}`));
6858
+ violations.push(violation7("high", missingCode, `missing required field: ${field}`));
6459
6859
  } else if (typeof value !== "string" || value.trim() === "") {
6460
- violations.push(violation("medium", invalidCode, `${field} must be a non-empty string`));
6860
+ violations.push(violation7("medium", invalidCode, `${field} must be a non-empty string`));
6461
6861
  }
6462
6862
  }
6463
6863
  var DATE_PART2 = String.raw`\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])`;
6464
6864
  var RFC3339_Z_RE2 = new RegExp(String.raw`^${DATE_PART2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$`);
6465
6865
  var DATE_ONLY_RE2 = new RegExp(String.raw`^${DATE_PART2}$`);
6466
- function isValidClaimedAt(value) {
6866
+ function isValidClaimedAt2(value) {
6467
6867
  return typeof value === "string" && (RFC3339_Z_RE2.test(value) || DATE_ONLY_RE2.test(value));
6468
6868
  }
6469
6869
  function validateExecutionLease(lease) {
@@ -6472,26 +6872,26 @@ function validateExecutionLease(lease) {
6472
6872
  return {
6473
6873
  ok: false,
6474
6874
  violations: [
6475
- violation("high", "lease.execution-lease.invalid", "execution_lease must be an object null and tombstone objects are invalid; writers delete the key on release")
6875
+ violation7("high", "lease.execution-lease.invalid", "execution_lease must be an object \u2014 null and tombstone objects are invalid; writers delete the key on release")
6476
6876
  ]
6477
6877
  };
6478
6878
  }
6479
- validateNonEmptyString(violations, lease.holder, "holder", "lease.execution-lease.missing-holder", "lease.execution-lease.invalid-holder");
6879
+ validateNonEmptyString3(violations, lease.holder, "holder", "lease.execution-lease.missing-holder", "lease.execution-lease.invalid-holder");
6480
6880
  if (lease.claimed_at === undefined) {
6481
- violations.push(violation("high", "lease.execution-lease.missing-claimed-at", "missing required field: claimed_at"));
6482
- } else if (!isValidClaimedAt(lease.claimed_at)) {
6483
- violations.push(violation("medium", "lease.execution-lease.invalid-claimed-at", "claimed_at must be an RFC 3339 UTC timestamp with explicit Z (e.g. 2026-07-22T02:30:00Z) or a YYYY-MM-DD date"));
6881
+ violations.push(violation7("high", "lease.execution-lease.missing-claimed-at", "missing required field: claimed_at"));
6882
+ } else if (!isValidClaimedAt2(lease.claimed_at)) {
6883
+ violations.push(violation7("medium", "lease.execution-lease.invalid-claimed-at", "claimed_at must be an RFC 3339 UTC timestamp with explicit Z (e.g. 2026-07-22T02:30:00Z) or a YYYY-MM-DD date"));
6484
6884
  }
6485
6885
  if (lease.worktree_path === undefined) {
6486
- violations.push(violation("high", "lease.execution-lease.missing-worktree-path", "missing required field: worktree_path"));
6886
+ violations.push(violation7("high", "lease.execution-lease.missing-worktree-path", "missing required field: worktree_path"));
6487
6887
  } else if (typeof lease.worktree_path !== "string" || lease.worktree_path.trim() === "") {
6488
- violations.push(violation("medium", "lease.execution-lease.invalid-worktree-path", "worktree_path must be a non-empty string"));
6888
+ violations.push(violation7("medium", "lease.execution-lease.invalid-worktree-path", "worktree_path must be a non-empty string"));
6489
6889
  } else if (!isAbsolute22(lease.worktree_path)) {
6490
- violations.push(violation("medium", "lease.execution-lease.invalid-worktree-path", "worktree_path must be an absolute path it identifies the dedicated feature-worktree root (and MUST differ from metadata.control_worktree_path)"));
6890
+ violations.push(violation7("medium", "lease.execution-lease.invalid-worktree-path", "worktree_path must be an absolute path \u2014 it identifies the dedicated feature-worktree root (and MUST differ from metadata.control_worktree_path)"));
6491
6891
  }
6492
- validateNonEmptyString(violations, lease.working_branch, "working_branch", "lease.execution-lease.missing-working-branch", "lease.execution-lease.invalid-working-branch");
6892
+ validateNonEmptyString3(violations, lease.working_branch, "working_branch", "lease.execution-lease.missing-working-branch", "lease.execution-lease.invalid-working-branch");
6493
6893
  if (lease.session_label !== undefined && typeof lease.session_label !== "string") {
6494
- violations.push(violation("medium", "lease.execution-lease.invalid-session-label", "session_label must be a string (display only never used for ownership comparison)"));
6894
+ violations.push(violation7("medium", "lease.execution-lease.invalid-session-label", "session_label must be a string (display only \u2014 never used for ownership comparison)"));
6495
6895
  }
6496
6896
  return { ok: violations.length === 0, violations };
6497
6897
  }
@@ -6508,22 +6908,22 @@ function verifyPlanExecutionLease(row, planId) {
6508
6908
  return {
6509
6909
  ok: false,
6510
6910
  violations: [
6511
- violation("high", "lease.verify.orphan", "plan is InProgress without an execution_lease orphan: STOP, no writable dispatch until recovery (status-and-residuals.md § Orphan recovery)")
6911
+ violation7("high", "lease.verify.orphan", "plan is InProgress without an execution_lease \u2014 orphan: STOP, no writable dispatch until recovery (status-and-residuals.md \u00a7 Orphan recovery)")
6512
6912
  ]
6513
6913
  };
6514
6914
  }
6515
6915
  return {
6516
6916
  ok: false,
6517
6917
  violations: [
6518
- violation("high", "lease.verify.missing", `plan ${planId} has no execution_lease (neither plans[].execution_lease nor legacy plans[].metadata.execution_lease)`)
6918
+ violation7("high", "lease.verify.missing", `plan ${planId} has no execution_lease (neither plans[].execution_lease nor legacy plans[].metadata.execution_lease)`)
6519
6919
  ]
6520
6920
  };
6521
6921
  }
6522
6922
  const violations = [];
6523
6923
  if (rowLease !== undefined && metadataLease !== undefined) {
6524
- violations.push(violation("high", "lease.verify.dual-write", "execution_lease present in BOTH plans[].execution_lease (SSOT) and plans[].metadata.execution_lease the row-level lease wins; delete the metadata copy to remove the dual write"));
6924
+ violations.push(violation7("high", "lease.verify.dual-write", "execution_lease present in BOTH plans[].execution_lease (SSOT) and plans[].metadata.execution_lease \u2014 the row-level lease wins; delete the metadata copy to remove the dual write"));
6525
6925
  } else if (rowLease === undefined) {
6526
- violations.push(violation("high", "lease.verify.non-ssot-location", "execution_lease found only under plans[].metadata.execution_lease the SSOT location is plans[].execution_lease; the metadata location is a legacy/hand-written read-compat fallback, not equivalent to SSOT success (migrate the lease to the plan row)"));
6926
+ violations.push(violation7("high", "lease.verify.non-ssot-location", "execution_lease found only under plans[].metadata.execution_lease \u2014 the SSOT location is plans[].execution_lease; the metadata location is a legacy/hand-written read-compat fallback, not equivalent to SSOT success (migrate the lease to the plan row)"));
6527
6927
  }
6528
6928
  violations.push(...validateExecutionLease(lease).violations);
6529
6929
  return { ok: violations.length === 0, violations, lease };
@@ -6701,11 +7101,11 @@ function validateManifest(manifest, errors2, warnings) {
6701
7101
  }
6702
7102
  if (doc.extensions !== undefined) {
6703
7103
  if (!isPlainObject6(doc.extensions)) {
6704
- warnings.push('plugin.json: "extensions" is not an object ignored');
7104
+ warnings.push('plugin.json: "extensions" is not an object \u2014 ignored');
6705
7105
  } else {
6706
7106
  for (const [namespace, value] of Object.entries(doc.extensions)) {
6707
7107
  if (!isPlainObject6(value)) {
6708
- warnings.push(`plugin.json: "extensions.${namespace}" is not an object ignored`);
7108
+ warnings.push(`plugin.json: "extensions.${namespace}" is not an object \u2014 ignored`);
6709
7109
  }
6710
7110
  }
6711
7111
  }
@@ -6766,7 +7166,7 @@ function validateMcpServer(name, entry, errors2) {
6766
7166
  } else {
6767
7167
  const remainder = stripMcpPathPrefix(server.cwd);
6768
7168
  if (remainder === null) {
6769
- errors2.push(`${prefix}: "cwd" must be "./…", "${"${PLUGIN_ROOT}"}", or "${"${PLUGIN_DATA}"}"`);
7169
+ errors2.push(`${prefix}: "cwd" must be "./\u2026", "${"${PLUGIN_ROOT}"}\u2026", or "${"${PLUGIN_DATA}"}\u2026"`);
6770
7170
  } else if (escapesPluginRoot(remainder)) {
6771
7171
  errors2.push(`${prefix}: "cwd" must remain within the plugin root (got "${server.cwd}")`);
6772
7172
  }
@@ -7341,7 +7741,7 @@ function ensureIterationSkillLinks(dryRun) {
7341
7741
  }
7342
7742
  const gitignoreEntries = CODEX_PROJECT_COMMAND_NAMES.map(iterationSkillGitignoreEntry);
7343
7743
  notes.push(...appendGitignore(projectRoot, gitignoreEntries, dryRun));
7344
- notes.push("Installed Codex project-scoped command skills under .agents/skills/ (iteration-start, iteration-drive, iteration-loop, codebase-audit) symlinked to harness commands/*.md.");
7744
+ notes.push("Installed Codex project-scoped command skills under .agents/skills/ (iteration-start, iteration-drive, iteration-loop, codebase-audit) \u2014 symlinked to harness commands/*.md.");
7345
7745
  return notes;
7346
7746
  }
7347
7747
  function validateIterationSkillLinks() {
@@ -7512,10 +7912,178 @@ var cursorAdapter = {
7512
7912
  }
7513
7913
  };
7514
7914
 
7915
+ // src/adapters/dsh.ts
7916
+ import { execFileSync as execFileSync6 } from "node:child_process";
7917
+ import os4 from "node:os";
7918
+ import path7 from "node:path";
7919
+ var DSH_BIN = "dsh";
7920
+ var DSH_PROFILE = "web";
7921
+ var DSH_PROFILE_FLAG = "--profile";
7922
+ var DSH_DUMP_FLAG = "--dump-config";
7923
+ var DSH_HOME_ENV = "DSH_HOME";
7924
+ var DSH_HOME_SUBDIR = ".dsh";
7925
+ var DSH_PROFILES_DIR = "profiles";
7926
+ var DSH_LOCAL_TIMEOUT_MS = 1e4;
7927
+ var DSH_ADD_TIMEOUT_MS = 300000;
7928
+ var DSH_ADD_TIMEOUT_ENV = "MSTAR_DSH_SUBPROCESS_TIMEOUT_MS";
7929
+ function addTimeoutMs() {
7930
+ const raw = process.env[DSH_ADD_TIMEOUT_ENV];
7931
+ if (raw === undefined || raw.trim() === "")
7932
+ return DSH_ADD_TIMEOUT_MS;
7933
+ const parsed = Number(raw);
7934
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DSH_ADD_TIMEOUT_MS;
7935
+ }
7936
+ var DSH_PLUGIN_SPECS = ["@mstar-harness/dsh", "dsh-llm-fallbacks"];
7937
+ var DSH_FALLBACKS_SPEC = DSH_PLUGIN_SPECS[1];
7938
+ var DSH_INSTALL_HINT = "Install the DeepSeek Harness CLI (@deepseek-ai/dsh), e.g. `pnpm add -g @deepseek-ai/dsh` or `npm install -g @deepseek-ai/dsh`, then re-run init.";
7939
+ function runDsh(args, dryRun, timeoutMs) {
7940
+ if (dryRun)
7941
+ return "";
7942
+ return execFileSync6(DSH_BIN, args, {
7943
+ stdio: "pipe",
7944
+ encoding: "utf8",
7945
+ env: process.env,
7946
+ timeout: timeoutMs
7947
+ });
7948
+ }
7949
+ function dshAvailable() {
7950
+ try {
7951
+ runDsh(["--version"], false, DSH_LOCAL_TIMEOUT_MS);
7952
+ return true;
7953
+ } catch {
7954
+ return false;
7955
+ }
7956
+ }
7957
+ function resolveProfileDir() {
7958
+ const dshHome = process.env[DSH_HOME_ENV] ?? path7.join(os4.homedir(), DSH_HOME_SUBDIR);
7959
+ return path7.join(dshHome, DSH_PROFILES_DIR, DSH_PROFILE);
7960
+ }
7961
+ var DISABLED_MARKERS = /\b(?:disabled: true|enabled: false)\b/;
7962
+ function parseLoaderEntries(dump) {
7963
+ const entries = [];
7964
+ let current = null;
7965
+ for (const line of dump.split(`
7966
+ `)) {
7967
+ if (/^- id: /.test(line)) {
7968
+ if (current)
7969
+ entries.push(current);
7970
+ current = { name: "", enabled: !DISABLED_MARKERS.test(line) };
7971
+ } else if (current) {
7972
+ const nameMatch = /^ name: (.+)$/.exec(line);
7973
+ if (nameMatch) {
7974
+ let name = nameMatch[1].trim();
7975
+ if (DISABLED_MARKERS.test(line)) {
7976
+ current.enabled = false;
7977
+ name = name.replace(/\s*,?\s*(?:disabled: true|enabled: false)\s*$/, "");
7978
+ }
7979
+ current.name = name.replace(/^['"]|['"]$/g, "");
7980
+ } else if (/^ disabled: true$/.test(line) || /^ enabled: false$/.test(line)) {
7981
+ current.enabled = false;
7982
+ } else if (line.trim() !== "" && !line.startsWith(" ")) {
7983
+ entries.push(current);
7984
+ current = null;
7985
+ }
7986
+ }
7987
+ }
7988
+ if (current)
7989
+ entries.push(current);
7990
+ if (dump.trim() !== "" && (entries.length === 0 || entries.some((entry) => !entry.name))) {
7991
+ return null;
7992
+ }
7993
+ return entries;
7994
+ }
7995
+ function runInit2(scope, dryRun, initFlags) {
7996
+ const notes = [];
7997
+ const profileDir = resolveProfileDir();
7998
+ if (!dryRun && !dshAvailable()) {
7999
+ throw new Error(`${DSH_BIN} CLI not found on PATH. ${DSH_INSTALL_HINT}`);
8000
+ }
8001
+ const installed = new Set;
8002
+ if (!dryRun) {
8003
+ try {
8004
+ const entries = parseLoaderEntries(runDsh([DSH_PROFILE_FLAG, DSH_PROFILE, DSH_DUMP_FLAG], dryRun, DSH_LOCAL_TIMEOUT_MS));
8005
+ if (entries === null) {
8006
+ notes.push("Warning: could not parse installed plugins from dump (unexpected format); proceeding with add (idempotent).");
8007
+ } else {
8008
+ for (const entry of entries)
8009
+ installed.add(entry.name);
8010
+ }
8011
+ } catch (error) {
8012
+ const message = error instanceof Error ? error.message : String(error);
8013
+ notes.push(`Warning: could not probe installed plugins (${message}); proceeding with add (idempotent).`);
8014
+ }
8015
+ }
8016
+ for (const spec of DSH_PLUGIN_SPECS) {
8017
+ if (spec === DSH_FALLBACKS_SPEC && initFlags?.noFallbacks) {
8018
+ notes.push(`skipped-by-flag: ${spec} (--no-fallbacks)`);
8019
+ continue;
8020
+ }
8021
+ if (!dryRun && installed.has(spec)) {
8022
+ notes.push(`skipped-existing: ${spec} (already installed in profile ${DSH_PROFILE})`);
8023
+ continue;
8024
+ }
8025
+ const addArgs = ["plugin", DSH_PROFILE_FLAG, DSH_PROFILE, "add", spec];
8026
+ if (dryRun) {
8027
+ notes.push(`Would run: ${DSH_BIN} ${addArgs.join(" ")}`);
8028
+ continue;
8029
+ }
8030
+ try {
8031
+ runDsh(addArgs, dryRun, addTimeoutMs());
8032
+ notes.push(`installed: ${spec} (${DSH_BIN} ${addArgs.join(" ")})`);
8033
+ } catch (error) {
8034
+ const message = error instanceof Error ? error.message : String(error);
8035
+ throw new Error(`Failed to install ${spec} via ${DSH_BIN}: ${message}`);
8036
+ }
8037
+ }
8038
+ notes.push(`Profile: ${DSH_PROFILE} at ${profileDir}`);
8039
+ notes.push("Verify with: mstar-harness doctor --target dsh");
8040
+ notes.push(`Alternate manual install: ${DSH_BIN} plugin ${DSH_PROFILE_FLAG} ${DSH_PROFILE} add ${DSH_PLUGIN_SPECS.join(` && ${DSH_BIN} plugin ${DSH_PROFILE_FLAG} ${DSH_PROFILE} add `)}`);
8041
+ return { location: profileDir, notes };
8042
+ }
8043
+ function runDoctor2(scope) {
8044
+ const errors2 = [];
8045
+ const notes = [];
8046
+ const profileDir = resolveProfileDir();
8047
+ if (!dshAvailable()) {
8048
+ errors2.push(`${DSH_BIN} CLI not found on PATH. ${DSH_INSTALL_HINT}`);
8049
+ return { location: profileDir, errors: errors2, notes };
8050
+ }
8051
+ let dump;
8052
+ try {
8053
+ dump = runDsh([DSH_PROFILE_FLAG, DSH_PROFILE, DSH_DUMP_FLAG], false, DSH_LOCAL_TIMEOUT_MS);
8054
+ } catch (error) {
8055
+ const message = error instanceof Error ? error.message : String(error);
8056
+ errors2.push(`Warning: could not probe installed plugins (${message}); cannot verify install state.`);
8057
+ return { location: profileDir, errors: errors2, notes };
8058
+ }
8059
+ const entries = parseLoaderEntries(dump);
8060
+ if (entries === null) {
8061
+ errors2.push("Warning: could not parse installed plugins from dump (unexpected format); cannot verify install state.");
8062
+ return { location: profileDir, errors: errors2, notes };
8063
+ }
8064
+ const byName = new Map(entries.map((entry) => [entry.name, entry]));
8065
+ for (const spec of DSH_PLUGIN_SPECS) {
8066
+ const entry = byName.get(spec);
8067
+ const state = !entry ? "uninstalled" : entry.enabled ? "mounted" : "disabled";
8068
+ notes.push(`${spec}: ${state}`);
8069
+ if (state === "mounted")
8070
+ continue;
8071
+ const hint = state === "uninstalled" ? "Run: mstar-harness init --target dsh" : "Enable it (e.g. remove the disable entry from cordis.patch.yml) and re-run doctor.";
8072
+ errors2.push(`${spec} is ${state}. ${hint}`);
8073
+ }
8074
+ return { location: profileDir, errors: errors2, notes };
8075
+ }
8076
+ var dshAdapter = {
8077
+ target: "dsh",
8078
+ mode: "install",
8079
+ runInstallInit: (scope, dryRun, initFlags) => runInit2(scope, dryRun, initFlags),
8080
+ runInstallDoctor: (scope) => runDoctor2(scope)
8081
+ };
8082
+
7515
8083
  // src/adapters/omp.ts
7516
8084
  import fs5 from "node:fs";
7517
- import path7 from "node:path";
7518
- import { execFileSync as execFileSync6 } from "node:child_process";
8085
+ import path8 from "node:path";
8086
+ import { execFileSync as execFileSync7 } from "node:child_process";
7519
8087
  var OMP_PLUGIN_MARKER = ".omp-plugin/plugin.json";
7520
8088
  var CLAUDE_PLUGIN_MARKER = ".claude-plugin/plugin.json";
7521
8089
  var PACKAGE_NAMES = new Set(["morning-star", PLUGIN_NAME, "github:btspoony/mstar-harness"]);
@@ -7523,7 +8091,7 @@ var SKILL_SMOKE = ["mstar-host", "mstar-harness-core", "pm"];
7523
8091
  var COMMAND_SMOKE = ["iteration-start", "iteration-drive", "iteration-loop", "codebase-audit"];
7524
8092
  function ompAvailable() {
7525
8093
  try {
7526
- execFileSync6("omp", ["--version"], { stdio: "pipe", encoding: "utf8" });
8094
+ execFileSync7("omp", ["--version"], { stdio: "pipe", encoding: "utf8" });
7527
8095
  return true;
7528
8096
  } catch {
7529
8097
  return false;
@@ -7532,11 +8100,11 @@ function ompAvailable() {
7532
8100
  function runOmp(args, dryRun) {
7533
8101
  if (dryRun)
7534
8102
  return;
7535
- execFileSync6("omp", args, { stdio: "pipe", encoding: "utf8" });
8103
+ execFileSync7("omp", args, { stdio: "pipe", encoding: "utf8" });
7536
8104
  }
7537
8105
  function listInstalledPlugins() {
7538
8106
  try {
7539
- const raw = execFileSync6("omp", ["plugin", "list", "--json"], {
8107
+ const raw = execFileSync7("omp", ["plugin", "list", "--json"], {
7540
8108
  stdio: "pipe",
7541
8109
  encoding: "utf8"
7542
8110
  });
@@ -7576,7 +8144,7 @@ function findInstalledPlugin(plugins) {
7576
8144
  return true;
7577
8145
  if (name.includes("morning-star") || manifestName.includes("morning-star"))
7578
8146
  return true;
7579
- if (pathValue.includes("mstar-harness") || pathValue.includes(`${path7.sep}morning-star`))
8147
+ if (pathValue.includes("mstar-harness") || pathValue.includes(`${path8.sep}morning-star`))
7580
8148
  return true;
7581
8149
  return false;
7582
8150
  });
@@ -7584,35 +8152,35 @@ function findInstalledPlugin(plugins) {
7584
8152
  function validatePluginTree(pluginRoot) {
7585
8153
  const errors2 = [];
7586
8154
  for (const marker of [OMP_PLUGIN_MARKER, CLAUDE_PLUGIN_MARKER]) {
7587
- const markerPath = path7.join(pluginRoot, marker);
8155
+ const markerPath = path8.join(pluginRoot, marker);
7588
8156
  if (!fs5.existsSync(markerPath)) {
7589
8157
  errors2.push(`Missing omp plugin marker: ${markerPath}`);
7590
8158
  }
7591
8159
  }
7592
8160
  for (const skill of SKILL_SMOKE) {
7593
- const skillPath = path7.join(pluginRoot, "skills", skill, "SKILL.md");
8161
+ const skillPath = path8.join(pluginRoot, "skills", skill, "SKILL.md");
7594
8162
  if (!fs5.existsSync(skillPath))
7595
8163
  errors2.push(`Missing skill: ${skillPath}`);
7596
8164
  }
7597
8165
  for (const command of COMMAND_SMOKE) {
7598
- const commandPath = path7.join(pluginRoot, "commands", `${command}.md`);
8166
+ const commandPath = path8.join(pluginRoot, "commands", `${command}.md`);
7599
8167
  if (!fs5.existsSync(commandPath))
7600
8168
  errors2.push(`Missing command: ${commandPath}`);
7601
8169
  }
7602
- const hostRef = path7.join(pluginRoot, "skills", "mstar-host", "references", "omp.md");
8170
+ const hostRef = path8.join(pluginRoot, "skills", "mstar-host", "references", "omp.md");
7603
8171
  if (!fs5.existsSync(hostRef))
7604
8172
  errors2.push(`Missing omp host reference: ${hostRef}`);
7605
8173
  return errors2;
7606
8174
  }
7607
- function runInit2(scope, dryRun) {
8175
+ function runInit3(scope, dryRun) {
7608
8176
  const notes = ensureLocalHarnessRepo(dryRun);
7609
8177
  const projectRoot = resolveProjectRoot2();
7610
- if (fs5.existsSync(path7.join(HARNESS_REPO_PATH, ".git"))) {
8178
+ if (fs5.existsSync(path8.join(HARNESS_REPO_PATH, ".git"))) {
7611
8179
  if (dryRun) {
7612
8180
  notes.push(`Would update local harness repo: git -C ${HARNESS_REPO_PATH} pull --ff-only`);
7613
8181
  } else {
7614
8182
  try {
7615
- execFileSync6("git", ["-C", HARNESS_REPO_PATH, "pull", "--ff-only"], {
8183
+ execFileSync7("git", ["-C", HARNESS_REPO_PATH, "pull", "--ff-only"], {
7616
8184
  stdio: "pipe",
7617
8185
  encoding: "utf8"
7618
8186
  });
@@ -7657,14 +8225,14 @@ function runInit2(scope, dryRun) {
7657
8225
  }
7658
8226
  notes.push("Verify with: omp plugin list");
7659
8227
  notes.push("Enter PM with /skill:pm ; commands: /iteration-start /iteration-drive /iteration-loop /codebase-audit");
7660
- notes.push(`Host adapter: mstar-host references/omp.md (skill://mstar-host/references/omp.md)`);
8228
+ notes.push(`Host adapter: mstar-host \u2192 references/omp.md (skill://mstar-host/references/omp.md)`);
7661
8229
  notes.push(`Alternate install without CLI link: omp plugin install ${REPO_URL.replace("https://github.com/", "github:").replace(/\.git$/, "")}`);
7662
8230
  return {
7663
8231
  location: HARNESS_REPO_PATH,
7664
8232
  notes
7665
8233
  };
7666
8234
  }
7667
- function runDoctor2(scope) {
8235
+ function runDoctor3(scope) {
7668
8236
  const errors2 = [];
7669
8237
  errors2.push(...validateLocalHarnessRepo());
7670
8238
  errors2.push(...validatePluginTree(HARNESS_REPO_PATH));
@@ -7681,7 +8249,7 @@ function runDoctor2(scope) {
7681
8249
  }
7682
8250
  if (scope === "project") {
7683
8251
  const projectRoot = resolveProjectRoot2();
7684
- const gitignorePath = path7.join(projectRoot, ".gitignore");
8252
+ const gitignorePath = path8.join(projectRoot, ".gitignore");
7685
8253
  const gitignore = fs5.existsSync(gitignorePath) ? fs5.readFileSync(gitignorePath, "utf8") : "";
7686
8254
  for (const entry of missingHarnessProcessGitignoreEntries(gitignore)) {
7687
8255
  errors2.push(`Missing .gitignore entry: ${entry}`);
@@ -7692,13 +8260,13 @@ function runDoctor2(scope) {
7692
8260
  var ompAdapter = {
7693
8261
  target: "omp",
7694
8262
  mode: "install",
7695
- runInstallInit: (scope, dryRun) => runInit2(scope, dryRun),
7696
- runInstallDoctor: (scope) => runDoctor2(scope)
8263
+ runInstallInit: (scope, dryRun) => runInit3(scope, dryRun),
8264
+ runInstallDoctor: (scope) => runDoctor3(scope)
7697
8265
  };
7698
8266
 
7699
8267
  // src/adapters/opencode.ts
7700
- import os4 from "node:os";
7701
- import path8 from "node:path";
8268
+ import os5 from "node:os";
8269
+ import path9 from "node:path";
7702
8270
  var OPENCODE_CONFIG_SCHEMA = "https://opencode.ai/config.json";
7703
8271
  var MSTAR_OPENCODE_PLUGIN = "@mstar-harness/opencode@latest";
7704
8272
  function isLegacyMorningStarGitPlugin(plugin) {
@@ -7719,11 +8287,11 @@ function isAnyMstarHarnessOpencodeSlot(plugin) {
7719
8287
  function resolveOpencodeConfigPath(scope, outputPath) {
7720
8288
  if (outputPath && outputPath.trim()) {
7721
8289
  const raw = outputPath.trim();
7722
- return path8.isAbsolute(raw) ? raw : path8.join(resolveProjectRoot2(), raw);
8290
+ return path9.isAbsolute(raw) ? raw : path9.join(resolveProjectRoot2(), raw);
7723
8291
  }
7724
8292
  if (scope === "global")
7725
- return path8.join(os4.homedir(), ".config", "opencode", "opencode.json");
7726
- return path8.join(resolveProjectRoot2(), "opencode.json");
8293
+ return path9.join(os5.homedir(), ".config", "opencode", "opencode.json");
8294
+ return path9.join(resolveProjectRoot2(), "opencode.json");
7727
8295
  }
7728
8296
  function ensureConfigSchema(config) {
7729
8297
  const next = ensureObject(config);
@@ -7782,10 +8350,10 @@ function getDoctorWarnings(config) {
7782
8350
  const hasNpm = strings.some(isMstarHarnessOpencodePlugin);
7783
8351
  const hasLegacy = strings.some(isLegacyMorningStarGitPlugin);
7784
8352
  if (hasLegacy && !hasNpm) {
7785
- warnings.push("Plugin list uses legacy `morning-star@git+…` for this harness; run `mstar-harness init --target opencode` to rewrite to `@mstar-harness/opencode@latest`.");
8353
+ warnings.push("Plugin list uses legacy `morning-star@git+\u2026` for this harness; run `mstar-harness init --target opencode` to rewrite to `@mstar-harness/opencode@latest`.");
7786
8354
  }
7787
8355
  if (hasLegacy && hasNpm) {
7788
- warnings.push("Both legacy `morning-star@git+…` and `@mstar-harness/opencode` appear in `plugin`; run `init` again to dedupe and keep a single npm plugin line.");
8356
+ warnings.push("Both legacy `morning-star@git+\u2026` and `@mstar-harness/opencode` appear in `plugin`; run `init` again to dedupe and keep a single npm plugin line.");
7789
8357
  }
7790
8358
  const agent = ensureObject(config.agent);
7791
8359
  const missingModels = ALL_ROLES.filter((roleId) => {
@@ -7793,7 +8361,7 @@ function getDoctorWarnings(config) {
7793
8361
  return typeof role.model !== "string" || !role.model.trim();
7794
8362
  });
7795
8363
  if (missingModels.length) {
7796
- warnings.push(`${missingModels.length} role(s) have no explicit agent.<role>.model OpenCode default model will be used (recommended for fastest setup).`);
8364
+ warnings.push(`${missingModels.length} role(s) have no explicit agent.<role>.model \u2014 OpenCode default model will be used (recommended for fastest setup).`);
7797
8365
  }
7798
8366
  return warnings;
7799
8367
  }
@@ -7817,8 +8385,8 @@ var opencodeAdapter = {
7817
8385
 
7818
8386
  // src/adapters/zcode.ts
7819
8387
  import fs6 from "node:fs";
7820
- import os5 from "node:os";
7821
- import path9 from "node:path";
8388
+ import os6 from "node:os";
8389
+ import path10 from "node:path";
7822
8390
  var MARKETPLACE_ID = "mstar-local";
7823
8391
  var MARKETPLACE_NAME2 = "mstar-local";
7824
8392
  var MARKETPLACE_DESCRIPTION = "Morning Star harness marketplace (GitHub source).";
@@ -7829,10 +8397,10 @@ var GITHUB_REF = "main";
7829
8397
  var ZCODE_PLUGIN_MARKER = ".zcode-plugin/plugin.json";
7830
8398
  var ZCODE_PLUGIN_CHECKOUT_PROJECT = ".zcode/plugin-checkout";
7831
8399
  var ZCODE_AGENT_SMOKE_NAMES = ["fullstack-dev", "qc-specialist"];
7832
- var ZCODE_PLUGINS_ROOT = path9.join(os5.homedir(), ".zcode", "cli", "plugins");
7833
- var KNOWN_MARKETPLACES_PATH = path9.join(ZCODE_PLUGINS_ROOT, "known_marketplaces.json");
7834
- var MARKETPLACE_DIR = path9.join(ZCODE_PLUGINS_ROOT, "marketplaces", MARKETPLACE_ID);
7835
- var MARKETPLACE_JSON_PATH = path9.join(MARKETPLACE_DIR, "marketplace.json");
8400
+ var ZCODE_PLUGINS_ROOT = path10.join(os6.homedir(), ".zcode", "cli", "plugins");
8401
+ var KNOWN_MARKETPLACES_PATH = path10.join(ZCODE_PLUGINS_ROOT, "known_marketplaces.json");
8402
+ var MARKETPLACE_DIR = path10.join(ZCODE_PLUGINS_ROOT, "marketplaces", MARKETPLACE_ID);
8403
+ var MARKETPLACE_JSON_PATH = path10.join(MARKETPLACE_DIR, "marketplace.json");
7836
8404
  var GITHUB_SOURCE = { source: "github", repo: GITHUB_REPO, ref: GITHUB_REF };
7837
8405
  function nowIso() {
7838
8406
  return new Date().toISOString();
@@ -7940,13 +8508,13 @@ function validateKnownMarketplaces() {
7940
8508
  }
7941
8509
  function validatePluginAgents2(pluginRoot) {
7942
8510
  const errors2 = [];
7943
- const agentsDir = path9.join(pluginRoot, "agents");
8511
+ const agentsDir = path10.join(pluginRoot, "agents");
7944
8512
  if (!fs6.existsSync(agentsDir)) {
7945
8513
  errors2.push(`Missing plugin agents directory: ${agentsDir}`);
7946
8514
  return errors2;
7947
8515
  }
7948
8516
  for (const agentName of ZCODE_AGENT_SMOKE_NAMES) {
7949
- const agentPath = path9.join(agentsDir, `${agentName}.md`);
8517
+ const agentPath = path10.join(agentsDir, `${agentName}.md`);
7950
8518
  if (!fs6.existsSync(agentPath)) {
7951
8519
  errors2.push(`Missing plugin agent file: ${agentPath}`);
7952
8520
  }
@@ -7960,11 +8528,11 @@ function buildMarketplaceJson() {
7960
8528
  plugins: [marketplacePluginEntry()]
7961
8529
  };
7962
8530
  }
7963
- function runInit3(scope, dryRun) {
8531
+ function runInit4(scope, dryRun) {
7964
8532
  const notes = ensureLocalHarnessRepo(dryRun);
7965
8533
  const projectRoot = resolveProjectRoot2();
7966
8534
  if (scope === "project") {
7967
- const checkoutPath = path9.join(projectRoot, ZCODE_PLUGIN_CHECKOUT_PROJECT);
8535
+ const checkoutPath = path10.join(projectRoot, ZCODE_PLUGIN_CHECKOUT_PROJECT);
7968
8536
  notes.push(...ensureGitCheckout(REPO_URL, checkoutPath, dryRun));
7969
8537
  notes.push(...appendGitignore(projectRoot, [ZCODE_PLUGIN_CHECKOUT_PROJECT], dryRun));
7970
8538
  notes.push(...appendHarnessProjectGitignore(projectRoot, dryRun));
@@ -7981,20 +8549,20 @@ function runInit3(scope, dryRun) {
7981
8549
  if (!dryRun)
7982
8550
  writeJson2(KNOWN_MARKETPLACES_PATH, knownNext);
7983
8551
  notes.push(`Registered ${MARKETPLACE_ID} marketplace in ${KNOWN_MARKETPLACES_PATH}`);
7984
- notes.push(`Then in ZCode: Settings Plugin Management Discover install ${PLUGIN_NAME} from the ${MARKETPLACE_ID} marketplace.`);
8552
+ notes.push(`Then in ZCode: Settings \u2192 Plugin Management \u2192 Discover \u2192 install ${PLUGIN_NAME} from the ${MARKETPLACE_ID} marketplace.`);
7985
8553
  return {
7986
8554
  location: KNOWN_MARKETPLACES_PATH,
7987
8555
  notes
7988
8556
  };
7989
8557
  }
7990
- function runDoctor3(scope) {
8558
+ function runDoctor4(scope) {
7991
8559
  const errors2 = [];
7992
8560
  errors2.push(...validateLocalHarnessRepo());
7993
8561
  if (scope === "project") {
7994
8562
  const projectRoot = resolveProjectRoot2();
7995
- const checkoutPath = path9.join(projectRoot, ZCODE_PLUGIN_CHECKOUT_PROJECT);
8563
+ const checkoutPath = path10.join(projectRoot, ZCODE_PLUGIN_CHECKOUT_PROJECT);
7996
8564
  errors2.push(...validateGitCheckout(checkoutPath, ZCODE_PLUGIN_MARKER));
7997
- const gitignorePath = path9.join(projectRoot, ".gitignore");
8565
+ const gitignorePath = path10.join(projectRoot, ".gitignore");
7998
8566
  const gitignore = fs6.existsSync(gitignorePath) ? fs6.readFileSync(gitignorePath, "utf8") : "";
7999
8567
  if (!gitignore.split(/\r?\n/).includes(ZCODE_PLUGIN_CHECKOUT_PROJECT)) {
8000
8568
  errors2.push(`Missing .gitignore entry: ${ZCODE_PLUGIN_CHECKOUT_PROJECT}`);
@@ -8013,8 +8581,8 @@ function runDoctor3(scope) {
8013
8581
  var zcodeAdapter = {
8014
8582
  target: "zcode",
8015
8583
  mode: "install",
8016
- runInstallInit: (scope, dryRun) => runInit3(scope, dryRun),
8017
- runInstallDoctor: (scope) => runDoctor3(scope)
8584
+ runInstallInit: (scope, dryRun) => runInit4(scope, dryRun),
8585
+ runInstallDoctor: (scope) => runDoctor4(scope)
8018
8586
  };
8019
8587
 
8020
8588
  // src/adapters/index.ts
@@ -8023,7 +8591,8 @@ var adapters = {
8023
8591
  cursor: cursorAdapter,
8024
8592
  codex: codexAdapter,
8025
8593
  zcode: zcodeAdapter,
8026
- omp: ompAdapter
8594
+ omp: ompAdapter,
8595
+ dsh: dshAdapter
8027
8596
  };
8028
8597
  function getAdapter(target) {
8029
8598
  const adapter = adapters[target];
@@ -8033,10 +8602,11 @@ function getAdapter(target) {
8033
8602
  }
8034
8603
 
8035
8604
  // src/types.ts
8036
- var SUPPORTED_TARGETS = ["opencode", "cursor", "codex", "zcode", "omp"];
8605
+ var SUPPORTED_TARGETS = ["opencode", "cursor", "codex", "zcode", "omp", "dsh"];
8037
8606
 
8038
8607
  // src/utils.ts
8039
- import path10 from "node:path";
8608
+ import fs7 from "node:fs";
8609
+ import path11 from "node:path";
8040
8610
  function parseCsv(raw) {
8041
8611
  if (!raw)
8042
8612
  return;
@@ -8045,9 +8615,43 @@ function parseCsv(raw) {
8045
8615
  function resolveProjectRoot3() {
8046
8616
  const candidate = process.env.MSTAR_CLI_PROJECT_ROOT || process.env.INIT_CWD || process.env.PWD;
8047
8617
  if (candidate && candidate.trim())
8048
- return path10.resolve(candidate);
8618
+ return path11.resolve(candidate);
8049
8619
  return resolveProjectRoot();
8050
8620
  }
8621
+ function findUpPackageRoot(startDir, predicate) {
8622
+ let dir = path11.resolve(startDir);
8623
+ for (;; ) {
8624
+ try {
8625
+ const manifest = JSON.parse(fs7.readFileSync(path11.join(dir, "package.json"), "utf8"));
8626
+ if (predicate(manifest))
8627
+ return dir;
8628
+ } catch {}
8629
+ const parent = path11.dirname(dir);
8630
+ if (parent === dir)
8631
+ return null;
8632
+ dir = parent;
8633
+ }
8634
+ }
8635
+ function declaresWorkspaces(manifest) {
8636
+ return Array.isArray(manifest.workspaces) || typeof manifest.workspaces === "string" || manifest.workspaces !== undefined && manifest.workspaces !== null && typeof manifest.workspaces === "object";
8637
+ }
8638
+ function resolveCliProjectRoot() {
8639
+ const override = process.env.MSTAR_CLI_PROJECT_ROOT;
8640
+ if (override && override.trim())
8641
+ return path11.resolve(override);
8642
+ const monorepoRoot = findUpPackageRoot(process.cwd(), declaresWorkspaces);
8643
+ if (monorepoRoot)
8644
+ return monorepoRoot;
8645
+ const packageRoot = findUpPackageRoot(process.cwd(), () => true);
8646
+ if (packageRoot)
8647
+ return packageRoot;
8648
+ return process.cwd();
8649
+ }
8650
+ function resolveCliPath(userPath) {
8651
+ if (path11.isAbsolute(userPath))
8652
+ return userPath;
8653
+ return path11.resolve(resolveCliProjectRoot(), userPath);
8654
+ }
8051
8655
 
8052
8656
  // src/index.ts
8053
8657
  var packageVersion = readHarnessVersion();
@@ -8083,7 +8687,7 @@ function resolveExplicitModelAssignments(options) {
8083
8687
  others: allow("other-models", parseCsv(options.otherModels), 3, true)
8084
8688
  });
8085
8689
  }
8086
- async function runInit4(options) {
8690
+ async function runInit5(options) {
8087
8691
  const target = options.target || (options.yes ? "opencode" : await pickTargetInteractive());
8088
8692
  const scope = options.scope || "project";
8089
8693
  const adapter = getAdapter(target);
@@ -8092,7 +8696,7 @@ async function runInit4(options) {
8092
8696
  }
8093
8697
  if (adapter.mode === "install") {
8094
8698
  logStep("Step 2/2 - Run target install flow");
8095
- const installResult = adapter.runInstallInit?.(scope, !!options.dryRun);
8699
+ const installResult = adapter.runInstallInit?.(scope, !!options.dryRun, { noFallbacks: options.noFallbacks });
8096
8700
  if (!installResult) {
8097
8701
  throw new Error(`Adapter ${target} does not implement install init flow.`);
8098
8702
  }
@@ -8146,7 +8750,7 @@ async function runInit4(options) {
8146
8750
  }
8147
8751
  }
8148
8752
  }
8149
- function runDoctor4(options) {
8753
+ function runDoctor5(options) {
8150
8754
  const target = options.target || "opencode";
8151
8755
  const adapter = getAdapter(target);
8152
8756
  const scope = options.scope || "project";
@@ -8157,6 +8761,9 @@ function runDoctor4(options) {
8157
8761
  throw new Error(`Adapter ${target} does not implement install doctor flow.`);
8158
8762
  }
8159
8763
  console.log(`Install location: ${result.location}`);
8764
+ for (const note of result.notes ?? []) {
8765
+ console.log(` - ${note}`);
8766
+ }
8160
8767
  if (!result.errors.length) {
8161
8768
  console.log(import_picocolors.default.green("Doctor result: healthy"));
8162
8769
  return;
@@ -8191,10 +8798,10 @@ function runDoctor4(options) {
8191
8798
  }
8192
8799
  function resolvePluginRoot(options) {
8193
8800
  if (options.root)
8194
- return path11.resolve(options.root);
8801
+ return path12.resolve(options.root);
8195
8802
  let candidate = resolveProjectRoot3();
8196
- while (!fs7.existsSync(path11.join(candidate, "plugin.json"))) {
8197
- const parent = path11.dirname(candidate);
8803
+ while (!fs8.existsSync(path12.join(candidate, "plugin.json"))) {
8804
+ const parent = path12.dirname(candidate);
8198
8805
  if (parent === candidate)
8199
8806
  break;
8200
8807
  candidate = parent;
@@ -8217,11 +8824,11 @@ function runPluginValidate(options) {
8217
8824
  process.exitCode = 1;
8218
8825
  }
8219
8826
  program2.name("mstar-harness").description("Morning Star harness CLI for target-based agent bootstrap").version(packageVersion);
8220
- program2.command("init").description("Interactive/non-interactive setup for target agent bootstrap").option("-y, --yes", "Non-interactive mode").option("--target <target>", "Install target", "opencode").option("--scope <scope>", "Config scope: global|project (default: project)").option("--output <path>", "Config file path override, relative to project root").option("--dry-run", "Preview result without writing config").option("--pm-model <model>", "Optional: model for project-manager (advanced override)").option("--strategic-models <a,b,c>", "Optional: models for architect/product-manager/prompt-engineer").option("--dev-models <a,b,c>", "Optional: models for fullstack-dev/fullstack-dev-2/frontend-dev").option("--qc-models <a,b,c>", "Optional: models for qc trio").option("--other-models <a,b,c>", "Optional: models for remaining roles").action(async (options) => {
8221
- await runInit4(options);
8827
+ program2.command("init").description("Interactive/non-interactive setup for target agent bootstrap").option("-y, --yes", "Non-interactive mode").option("--target <target>", "Install target", "opencode").option("--scope <scope>", "Config scope: global|project (default: project)").option("--output <path>", "Config file path override, relative to project root").option("--dry-run", "Preview result without writing config").option("--no-fallbacks", "Skip installing the dsh-llm-fallbacks plugin (dsh target only)").option("--pm-model <model>", "Optional: model for project-manager (advanced override)").option("--strategic-models <a,b,c>", "Optional: models for architect/product-manager/prompt-engineer").option("--dev-models <a,b,c>", "Optional: models for fullstack-dev/fullstack-dev-2/frontend-dev").option("--qc-models <a,b,c>", "Optional: models for qc trio").option("--other-models <a,b,c>", "Optional: models for remaining roles").action(async (options) => {
8828
+ await runInit5({ ...options, noFallbacks: options.fallbacks === false });
8222
8829
  });
8223
8830
  program2.command("doctor").description("Validate Morning Star setup for a target agent config").option("--target <target>", "Target agent for doctor checks", "opencode").option("--scope <scope>", "Config scope: global|project", "project").option("--output <path>", "Config file path override, relative to project root").action((options) => {
8224
- runDoctor4(options);
8831
+ runDoctor5(options);
8225
8832
  });
8226
8833
  var pluginCommand = program2.command("plugin").description("Agent Plugins v1.0.0 portable package commands");
8227
8834
  pluginCommand.command("validate").description("Validate a plugin package against Agent Plugins v1.0.0").option("--root <path>", "Plugin root directory to validate (default: project root)").action((options) => {
@@ -8229,7 +8836,7 @@ pluginCommand.command("validate").description("Validate a plugin package against
8229
8836
  });
8230
8837
  var pathCommand = program2.command("path").description("harness/specs dir resolution checks (engine-backed)");
8231
8838
  pathCommand.command("resolve").description("Resolve {HARNESS_DIR} + {SPECS_DIR} from a start dir (exit 1 when no harness dir resolves)").argument("[path]", "Start dir to resolve from (default: cwd)").option("--json", "Machine-readable JSON output (ok, harnessDir, specsDir, guidance on failure)").action((pathArg, options) => {
8232
- const startDir = pathArg ? path11.resolve(pathArg) : process.cwd();
8839
+ const startDir = pathArg ? path12.resolve(pathArg) : process.cwd();
8233
8840
  const harnessDir = resolveHarnessDir(startDir);
8234
8841
  if (!harnessDir) {
8235
8842
  const guidance = "no harness dir found \u2014 the bounded probe (.mstar/, .agents/, .plans/, plans/) walked up from " + `${startDir} only within the workspace root (git top-level of the start dir; non-git start probes only itself) \u2014 run \`mstar init\` to bootstrap, or pass a start dir inside a harness-enabled project`;
@@ -8253,18 +8860,18 @@ pathCommand.command("resolve").description("Resolve {HARNESS_DIR} + {SPECS_DIR}
8253
8860
  var statusCommand = program2.command("status").description("status.json schema + residual lifecycle checks (engine-backed)");
8254
8861
  function resolveStatusFilePath(pathArg) {
8255
8862
  if (pathArg)
8256
- return path11.resolve(pathArg);
8863
+ return path12.resolve(pathArg);
8257
8864
  const harnessDir = resolveHarnessDir();
8258
8865
  if (!harnessDir) {
8259
8866
  throw new Error(`harness dir not found from ${process.cwd()} \u2014 pass a status.json path or set MSTAR_HARNESS_DIR`);
8260
8867
  }
8261
- return path11.join(harnessDir, "status.json");
8868
+ return path12.join(harnessDir, "status.json");
8262
8869
  }
8263
8870
  statusCommand.command("validate").description("Validate status.json (schema, severity enum, root-only residual_findings)").argument("[path]", "status.json path (default: {HARNESS_DIR}/status.json)").action((pathArg) => {
8264
8871
  let statusPath;
8265
8872
  try {
8266
8873
  statusPath = resolveStatusFilePath(pathArg);
8267
- if (!fs7.existsSync(statusPath)) {
8874
+ if (!fs8.existsSync(statusPath)) {
8268
8875
  throw new Error(`status file not found: ${statusPath}`);
8269
8876
  }
8270
8877
  const gate2 = validateStatus(statusPath);
@@ -8274,10 +8881,10 @@ statusCommand.command("validate").description("Validate status.json (schema, sev
8274
8881
  }
8275
8882
  const count = gate2.violations.length;
8276
8883
  console.error(import_picocolors.default.red(`${statusPath}: FAIL (${count} violation${count === 1 ? "" : "s"})`));
8277
- for (const violation7 of gate2.violations) {
8278
- console.error(` - [${violation7.severity}] ${violation7.code}: ${violation7.message}`);
8279
- if (violation7.fix)
8280
- console.error(` fix: ${violation7.fix}`);
8884
+ for (const violation12 of gate2.violations) {
8885
+ console.error(` - [${violation12.severity}] ${violation12.code}: ${violation12.message}`);
8886
+ if (violation12.fix)
8887
+ console.error(` fix: ${violation12.fix}`);
8281
8888
  }
8282
8889
  process.exitCode = 1;
8283
8890
  } catch (error) {
@@ -8302,10 +8909,54 @@ statusCommand.command("archive-residuals").description("Archive a plan's open re
8302
8909
  process.exitCode = 1;
8303
8910
  }
8304
8911
  });
8305
- var leaseCommand = program2.command("lease").description("execution_lease checks (engine-backed) \u2014 integration_merge_lease validation stays import-only via @mstar-harness/engine until a dedicated subcommand exists");
8912
+ statusCommand.command("tech-debt").description("Print the residual tech-debt rollup (total_open / by_severity / by_target / by_plan) and PASS/DRIFT vs stored " + "metadata.tech_debt_summary (exit 1 on DRIFT \u2014 refresh the stored summary to clear)").argument("[path]", "status.json path (default: {HARNESS_DIR}/status.json)").action((pathArg) => {
8913
+ try {
8914
+ const statusPath = resolveStatusFilePath(pathArg);
8915
+ if (!fs8.existsSync(statusPath)) {
8916
+ throw new Error(`status file not found: ${statusPath}`);
8917
+ }
8918
+ const rollup = techDebtRollup(statusPath);
8919
+ console.log(`status tech-debt: ${statusPath}`);
8920
+ console.log(`total_open: ${rollup.computed.total_open}`);
8921
+ console.log(`by_severity: ${JSON.stringify(rollup.computed.by_severity)}`);
8922
+ console.log(`by_target: ${JSON.stringify(rollup.computed.by_target)}`);
8923
+ console.log(`by_plan: ${JSON.stringify(rollup.computed.by_plan)}`);
8924
+ if (rollup.overall === "PASS") {
8925
+ console.log(import_picocolors.default.green("tech_debt_summary: PASS (all 4 fields match stored)"));
8926
+ return;
8927
+ }
8928
+ const drifts = rollup.checks.filter((check) => check.status === "DRIFT").map((check) => check.field);
8929
+ const note = rollup.stored === null ? " (no stored metadata.tech_debt_summary)" : "";
8930
+ console.error(import_picocolors.default.red(`tech_debt_summary: DRIFT (${drifts.length}/4 fields: ${drifts.join(", ")})${note}`));
8931
+ process.exitCode = 1;
8932
+ } catch (error) {
8933
+ console.error(import_picocolors.default.red(`status tech-debt failed: ${error.message}`));
8934
+ process.exitCode = 1;
8935
+ }
8936
+ });
8937
+ statusCommand.command("findings-cleanup").description("Enforce a plan's findings-cleanup mode on its open residuals (zero-residual via Assignment/metadata, else " + "allow-residual; exit 1 on violations)").argument("<plan-id>", "Plan id whose open residuals are checked against the cleanup mode").option("--harness <path>", "Harness dir override (default: resolved {HARNESS_DIR})").action((planId, options) => {
8938
+ try {
8939
+ const statusPath = options.harness ? path12.join(path12.resolve(options.harness), "status.json") : resolveStatusFilePath();
8940
+ if (!fs8.existsSync(statusPath)) {
8941
+ throw new Error(`status file not found: ${statusPath}`);
8942
+ }
8943
+ const doc = readJson2(statusPath);
8944
+ const gate2 = findingsCleanupGate(doc, planId);
8945
+ if (gate2.ok) {
8946
+ console.log(import_picocolors.default.green(`findings-cleanup ${planId}: OK`));
8947
+ return;
8948
+ }
8949
+ printChecklist(`findings-cleanup ${planId}`, gate2);
8950
+ process.exitCode = 1;
8951
+ } catch (error) {
8952
+ console.error(import_picocolors.default.red(`status findings-cleanup failed: ${error.message}`));
8953
+ process.exitCode = 1;
8954
+ }
8955
+ });
8956
+ var leaseCommand = program2.command("lease").description("execution_lease / integration_merge_lease checks (engine-backed)");
8306
8957
  function resolveLeaseHarnessDir(harnessArg) {
8307
8958
  if (harnessArg)
8308
- return path11.resolve(harnessArg);
8959
+ return path12.resolve(harnessArg);
8309
8960
  const harnessDir = resolveHarnessDir();
8310
8961
  if (!harnessDir) {
8311
8962
  throw new Error(`harness dir not found from ${process.cwd()} \u2014 pass --harness or set MSTAR_HARNESS_DIR`);
@@ -8315,8 +8966,8 @@ function resolveLeaseHarnessDir(harnessArg) {
8315
8966
  leaseCommand.command("verify").description("Verify a plan's execution_lease (missing/invalid/non-SSOT location \u2192 exit 1 with violations)").argument("<plan-id>", "Plan id whose execution_lease is verified").option("--harness <path>", "Harness dir override (default: resolved {HARNESS_DIR})").action((planId, options) => {
8316
8967
  try {
8317
8968
  const harnessDir = resolveLeaseHarnessDir(options.harness);
8318
- const statusPath = path11.join(harnessDir, "status.json");
8319
- if (!fs7.existsSync(statusPath)) {
8969
+ const statusPath = path12.join(harnessDir, "status.json");
8970
+ if (!fs8.existsSync(statusPath)) {
8320
8971
  throw new Error(`status file not found: ${statusPath}`);
8321
8972
  }
8322
8973
  const doc = readJson2(statusPath);
@@ -8342,10 +8993,10 @@ leaseCommand.command("verify").description("Verify a plan's execution_lease (mis
8342
8993
  }
8343
8994
  const count = result.violations.length;
8344
8995
  console.error(import_picocolors.default.red(`${statusPath}: FAIL plan ${planId} (${count} violation${count === 1 ? "" : "s"})`));
8345
- for (const violation7 of result.violations) {
8346
- console.error(` - [${violation7.severity}] ${violation7.code}: ${violation7.message}`);
8347
- if (violation7.fix)
8348
- console.error(` fix: ${violation7.fix}`);
8996
+ for (const violation12 of result.violations) {
8997
+ console.error(` - [${violation12.severity}] ${violation12.code}: ${violation12.message}`);
8998
+ if (violation12.fix)
8999
+ console.error(` fix: ${violation12.fix}`);
8349
9000
  }
8350
9001
  process.exitCode = 1;
8351
9002
  } catch (error) {
@@ -8353,6 +9004,32 @@ leaseCommand.command("verify").description("Verify a plan's execution_lease (mis
8353
9004
  process.exitCode = 1;
8354
9005
  }
8355
9006
  });
9007
+ leaseCommand.command("verify-integration").description("Verify the root metadata.integration_merge_lease when present (absent/unclaimed \u2192 OK; invalid lease \u2192 exit 1)").option("--harness <path>", "Harness dir override (default: resolved {HARNESS_DIR})").action((options) => {
9008
+ try {
9009
+ const harnessDir = resolveLeaseHarnessDir(options.harness);
9010
+ const statusPath = path12.join(harnessDir, "status.json");
9011
+ if (!fs8.existsSync(statusPath)) {
9012
+ throw new Error(`status file not found: ${statusPath}`);
9013
+ }
9014
+ const doc = readJson2(statusPath);
9015
+ const metadata = doc.metadata ?? {};
9016
+ if (metadata.integration_merge_lease === undefined) {
9017
+ console.log(import_picocolors.default.green(`${statusPath}: OK \u2014 no integration_merge_lease (unclaimed)`));
9018
+ return;
9019
+ }
9020
+ const gate2 = validateIntegrationMergeLease(metadata.integration_merge_lease);
9021
+ if (gate2.ok) {
9022
+ const lease = metadata.integration_merge_lease;
9023
+ console.log(import_picocolors.default.green(`${statusPath}: OK \u2014 integration_merge_lease valid (holder ${String(lease.holder ?? "")})`));
9024
+ return;
9025
+ }
9026
+ printChecklist("lease verify-integration", gate2);
9027
+ process.exitCode = 1;
9028
+ } catch (error) {
9029
+ console.error(import_picocolors.default.red(`lease verify-integration failed: ${error.message}`));
9030
+ process.exitCode = 1;
9031
+ }
9032
+ });
8356
9033
  function failScript(error, context) {
8357
9034
  if (error instanceof SddScriptError) {
8358
9035
  console.error(import_picocolors.default.red(`${context} failed: ${error.message}`));
@@ -8405,19 +9082,19 @@ function printChecklist(label, gate2) {
8405
9082
  }
8406
9083
  const count = gate2.violations.length;
8407
9084
  console.error(import_picocolors.default.red(`${label}: FAIL (${count} violation${count === 1 ? "" : "s"})`));
8408
- for (const violation7 of gate2.violations) {
8409
- console.error(` - [${violation7.severity}] ${violation7.code}: ${violation7.message}`);
8410
- if (violation7.fix)
8411
- console.error(` fix: ${violation7.fix}`);
9085
+ for (const violation12 of gate2.violations) {
9086
+ console.error(` - [${violation12.severity}] ${violation12.code}: ${violation12.message}`);
9087
+ if (violation12.fix)
9088
+ console.error(` fix: ${violation12.fix}`);
8412
9089
  }
8413
9090
  }
8414
9091
  iterationCommand.command("gate").description("Evaluate the phase-transition gate: prints the transition (phase-2-execute / phase-3-close / phase-4-pr-delivery) " + "plus the \xA73.1 entry and \xA73.5 exit checklists. Exit 1 when the gate verdict fails \u2014 during the Phase-3 window " + "(transition: phase-3-close) exit 1 is EXPECTED until the \xA73.4 close items (status: completed + end_date) are " + "written: the exit checklist gates Phase 4, not the Phase-3 entry (qc2 F-003)").requiredOption("--status <path>", "status.json path").requiredOption("--compass <path>", "delivery-compass.md path").option("--branch <branch>", "Current branch probe (exit \xA73.5 item 5)").option("--integration <branch>", "Spec integration branch probe (exit \xA73.5 item 5)").option("--target <branch>", "PR base branch probe (exit \xA73.5 item 6)").action((options) => {
8415
9092
  try {
8416
- const statusPath = path11.resolve(options.status);
8417
- const compassPath = path11.resolve(options.compass);
8418
- if (!fs7.existsSync(statusPath))
9093
+ const statusPath = path12.resolve(options.status);
9094
+ const compassPath = path12.resolve(options.compass);
9095
+ if (!fs8.existsSync(statusPath))
8419
9096
  throw new Error(`status file not found: ${statusPath}`);
8420
- if (!fs7.existsSync(compassPath))
9097
+ if (!fs8.existsSync(compassPath))
8421
9098
  throw new Error(`compass file not found: ${compassPath}`);
8422
9099
  const result = evaluatePhaseGate(readJson2(statusPath), parseCompassFrontmatter(compassPath), {
8423
9100
  currentBranch: options.branch,
@@ -8442,10 +9119,10 @@ iterationCommand.command("push-cadence").description("\xA75.1a push-cadence prob
8442
9119
  }
8443
9120
  const count = result.violations.length;
8444
9121
  console.error(import_picocolors.default.red(`push blocked (${count} violation${count === 1 ? "" : "s"})`));
8445
- for (const violation7 of result.violations) {
8446
- console.error(` - [${violation7.severity}] ${violation7.code}: ${violation7.message}`);
8447
- if (violation7.fix)
8448
- console.error(` fix: ${violation7.fix}`);
9122
+ for (const violation12 of result.violations) {
9123
+ console.error(` - [${violation12.severity}] ${violation12.code}: ${violation12.message}`);
9124
+ if (violation12.fix)
9125
+ console.error(` fix: ${violation12.fix}`);
8449
9126
  }
8450
9127
  process.exitCode = 1;
8451
9128
  });
@@ -8455,11 +9132,11 @@ dispatchCommand.command("validate").description("Validate an Assignment markdown
8455
9132
  if (!assignmentFile) {
8456
9133
  throw new SddScriptError("usage: dispatch validate <assignment-file> [--branch <branch>]", 2);
8457
9134
  }
8458
- const file = path11.resolve(assignmentFile);
8459
- if (!fs7.existsSync(file)) {
9135
+ const file = resolveCliPath(assignmentFile);
9136
+ if (!fs8.existsSync(file)) {
8460
9137
  throw new Error(`assignment file not found: ${file}`);
8461
9138
  }
8462
- const text = fs7.readFileSync(file, "utf8");
9139
+ const text = fs8.readFileSync(file, "utf8");
8463
9140
  const readOnly = isReadOnlyAssignmentRole(parseAssignmentFields(text).executeAs ?? "");
8464
9141
  const violations = [...validateAssignmentFields(text, { writable: readOnly ? false : undefined }).violations];
8465
9142
  if (!readOnly) {
@@ -8514,8 +9191,8 @@ worktreeCommand.command("check").description("L1: verify the plan's execution_le
8514
9191
  if (!plan) {
8515
9192
  throw new SddScriptError("usage: worktree check <plan-id> [--status <path>] [--control <path>] (or --plan <plan-id>)", 2);
8516
9193
  }
8517
- const statusPath = options.status ? path11.resolve(options.status) : resolveStatusFilePath();
8518
- if (!fs7.existsSync(statusPath)) {
9194
+ const statusPath = options.status ? path12.resolve(options.status) : resolveStatusFilePath();
9195
+ if (!fs8.existsSync(statusPath)) {
8519
9196
  throw new Error(`status file not found: ${statusPath}`);
8520
9197
  }
8521
9198
  const doc = readJson2(statusPath);
@@ -8537,7 +9214,7 @@ worktreeCommand.command("check").description("L1: verify the plan's execution_le
8537
9214
  const lease = row.execution_lease ?? {};
8538
9215
  const metadata = doc.metadata ?? {};
8539
9216
  const input = {
8540
- controlWorktreePath: options.control ? path11.resolve(options.control) : String(metadata.control_worktree_path ?? ""),
9217
+ controlWorktreePath: options.control ? path12.resolve(options.control) : String(metadata.control_worktree_path ?? ""),
8541
9218
  leaseWorktreePath: String(lease.worktree_path ?? ""),
8542
9219
  leaseWorkingBranch: String(lease.working_branch ?? ""),
8543
9220
  planId: plan
@@ -8550,6 +9227,61 @@ worktreeCommand.command("check").description("L1: verify the plan's execution_le
8550
9227
  failScript(error, "worktree check");
8551
9228
  }
8552
9229
  });
9230
+ function parseAssignmentHeaderField(assignmentText, label) {
9231
+ const escaped = label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9232
+ const boldRe = new RegExp(`^[ \\t]*(?:[-*][ \\t]+)?\\*\\*\\s*${escaped}\\s*\\*\\*\\s*:\\s*(.*)$`);
9233
+ const plainRe = new RegExp(`^[ \\t]*(?:[-*][ \\t]+)?${escaped}\\s*:\\s*(.*)$`);
9234
+ for (const line of assignmentText.split(/\r?\n/)) {
9235
+ const match = line.match(boldRe) ?? line.match(plainRe);
9236
+ if (match)
9237
+ return match[1].trim();
9238
+ }
9239
+ return "";
9240
+ }
9241
+ var QC_ALIGNMENT_FIELDS2 = [
9242
+ { key: "planId", label: "plan_id" },
9243
+ { key: "reviewRange", label: "Review range" },
9244
+ { key: "diffBasis", label: "Diff basis" }
9245
+ ];
9246
+ worktreeCommand.command("qc-alignment").description("Assert the QC/QA alignment fields (plan_id / Review range / Diff basis) are byte-identical across the given " + "Assignment files (separate or combined `Review range / Diff basis` labels; exit 1 on mismatch or missing field, 2 on usage)").argument("[assignment-files...]", "QC tri + QA Assignment markdown files (at least one required)").action((files) => {
9247
+ try {
9248
+ if (files.length === 0) {
9249
+ throw new SddScriptError("usage: worktree qc-alignment <assignment-file>...", 2);
9250
+ }
9251
+ const assignments = [];
9252
+ for (const fileArg of files) {
9253
+ const file = path12.resolve(fileArg);
9254
+ if (!fs8.existsSync(file)) {
9255
+ throw new Error(`assignment file not found: ${file}`);
9256
+ }
9257
+ const text = fs8.readFileSync(file, "utf8");
9258
+ const combinedRange = parseAssignmentHeaderField(text, "Review range / Diff basis");
9259
+ const planId = parseAssignmentHeaderField(text, "plan_id");
9260
+ const reviewRange = parseAssignmentHeaderField(text, "Review range") || combinedRange;
9261
+ const diffBasis = parseAssignmentHeaderField(text, "Diff basis") || combinedRange;
9262
+ const values = { planId, reviewRange, diffBasis };
9263
+ const missing = QC_ALIGNMENT_FIELDS2.filter((field) => values[field.key] === "");
9264
+ if (missing.length > 0) {
9265
+ console.error(import_picocolors.default.red(`worktree qc-alignment: FAIL ${file}`));
9266
+ for (const field of missing) {
9267
+ console.error(` - [high] qc.alignment.field.missing: missing "${field.label}" header field`);
9268
+ }
9269
+ process.exitCode = 1;
9270
+ return;
9271
+ }
9272
+ assignments.push({ planId, reviewRange, diffBasis });
9273
+ }
9274
+ const gate2 = assertQcAlignment(assignments);
9275
+ if (gate2.ok) {
9276
+ console.log(import_picocolors.default.green(`worktree qc-alignment: OK (${assignments.length} assignment${assignments.length === 1 ? "" : "s"}, ` + `${QC_ALIGNMENT_FIELDS2.length} fields byte-identical)`));
9277
+ return;
9278
+ }
9279
+ printChecklist("worktree qc-alignment", gate2);
9280
+ process.exitCode = 1;
9281
+ } catch (error) {
9282
+ failScript(error, "worktree qc-alignment");
9283
+ }
9284
+ });
8553
9285
  var reviewCommand = program2.command("review").description("QC seat-mapping checks (engine-backed)");
8554
9286
  function parseAssignmentExecutionMode(assignmentText) {
8555
9287
  for (const line of assignmentText.split(/\r?\n/)) {
@@ -8564,11 +9296,11 @@ reviewCommand.command("seats").description("Map an Assignment's execution mode t
8564
9296
  if (!assignmentFile) {
8565
9297
  throw new SddScriptError("usage: review seats <assignment-file> [--mode sdd|inline|targeted] [--reviewers <role1,role2,...>]", 2);
8566
9298
  }
8567
- const file = path11.resolve(assignmentFile);
8568
- if (!fs7.existsSync(file)) {
9299
+ const file = path12.resolve(assignmentFile);
9300
+ if (!fs8.existsSync(file)) {
8569
9301
  throw new Error(`assignment file not found: ${file}`);
8570
9302
  }
8571
- const text = fs7.readFileSync(file, "utf8");
9303
+ const text = fs8.readFileSync(file, "utf8");
8572
9304
  const mode = options.mode ?? parseAssignmentExecutionMode(text);
8573
9305
  const reviewers = (options.reviewers ?? "").split(",").map((role) => role.trim()).filter((role) => role !== "");
8574
9306
  const result = executionModeToN(mode, { seats: reviewers });
@@ -8625,31 +9357,31 @@ var LINT_CODE_EXTENSIONS = {
8625
9357
  ".swift": true
8626
9358
  };
8627
9359
  function lintTargetType(filePath) {
8628
- const base = path11.basename(filePath);
9360
+ const base = path12.basename(filePath);
8629
9361
  if (base === "STRATEGY.md")
8630
9362
  return "strategy";
8631
9363
  if (base === "SKILL.md")
8632
9364
  return "skill";
8633
9365
  if (/^task-\d+-report\.md$/i.test(base))
8634
9366
  return "report";
8635
- const dir = path11.dirname(filePath);
8636
- if (dir.includes(`${path11.sep}plans${path11.sep}`) || dir.endsWith(`${path11.sep}plans`))
9367
+ const dir = path12.dirname(filePath);
9368
+ if (dir.includes(`${path12.sep}plans${path12.sep}`) || dir.endsWith(`${path12.sep}plans`))
8637
9369
  return "plan";
8638
9370
  if (/^\d{8}-[a-z0-9.-]+\.md$/i.test(base))
8639
9371
  return "plan";
8640
- if (LINT_CODE_EXTENSIONS[path11.extname(base).toLowerCase()] === true)
9372
+ if (LINT_CODE_EXTENSIONS[path12.extname(base).toLowerCase()] === true)
8641
9373
  return "code";
8642
9374
  return null;
8643
9375
  }
8644
9376
  function collectLintTargets(dir) {
8645
9377
  const targets = [];
8646
9378
  const walk = (current) => {
8647
- for (const entry of fs7.readdirSync(current, { withFileTypes: true })) {
9379
+ for (const entry of fs8.readdirSync(current, { withFileTypes: true })) {
8648
9380
  if (entry.isDirectory()) {
8649
9381
  if (LINT_SKIP_DIRS[entry.name] !== true)
8650
- walk(path11.join(current, entry.name));
8651
- } else if (entry.isFile() && lintTargetType(path11.join(current, entry.name)) !== null) {
8652
- targets.push(path11.join(current, entry.name));
9382
+ walk(path12.join(current, entry.name));
9383
+ } else if (entry.isFile() && lintTargetType(path12.join(current, entry.name)) !== null) {
9384
+ targets.push(path12.join(current, entry.name));
8653
9385
  }
8654
9386
  }
8655
9387
  };
@@ -8657,8 +9389,8 @@ function collectLintTargets(dir) {
8657
9389
  return targets;
8658
9390
  }
8659
9391
  function lintOneFile(filePath) {
8660
- const abs = path11.resolve(filePath);
8661
- const text = fs7.readFileSync(abs, "utf8");
9392
+ const abs = path12.resolve(filePath);
9393
+ const text = fs8.readFileSync(abs, "utf8");
8662
9394
  const violations = [];
8663
9395
  const markers = [];
8664
9396
  switch (lintTargetType(abs)) {
@@ -8687,7 +9419,7 @@ function lintOneFile(filePath) {
8687
9419
  break;
8688
9420
  }
8689
9421
  default:
8690
- throw new SddScriptError(`usage: lint <target> \u2014 unsupported file type "${path11.basename(abs)}" (lintable: plan files, SKILL.md, STRATEGY.md, task-N-report.md, code files)`, 2);
9422
+ throw new SddScriptError(`usage: lint <target> \u2014 unsupported file type "${path12.basename(abs)}" (lintable: plan files, SKILL.md, STRATEGY.md, task-N-report.md, code files)`, 2);
8691
9423
  }
8692
9424
  return { violations, markers };
8693
9425
  }
@@ -8696,10 +9428,10 @@ lintCommand.description("Lint <target> (file or dir) \u2014 exit 1 on violations
8696
9428
  try {
8697
9429
  if (!target)
8698
9430
  throw new SddScriptError("usage: lint <target> (file or dir)", 2);
8699
- const abs = path11.resolve(target);
8700
- if (!fs7.existsSync(abs))
9431
+ const abs = resolveCliPath(target);
9432
+ if (!fs8.existsSync(abs))
8701
9433
  throw new Error(`lint target not found: ${abs}`);
8702
- const targets = fs7.statSync(abs).isDirectory() ? collectLintTargets(abs) : [abs];
9434
+ const targets = fs8.statSync(abs).isDirectory() ? collectLintTargets(abs) : [abs];
8703
9435
  if (targets.length === 0) {
8704
9436
  console.log(import_picocolors.default.yellow(`lint: no lintable files under ${target}`));
8705
9437
  return;
@@ -8726,10 +9458,10 @@ lintCommand.description("Lint <target> (file or dir) \u2014 exit 1 on violations
8726
9458
  violations += result.violations.length;
8727
9459
  const count = result.violations.length;
8728
9460
  console.error(import_picocolors.default.red(`${label}: FAIL (${count} violation${count === 1 ? "" : "s"})`));
8729
- for (const violation7 of result.violations) {
8730
- console.error(` - [${violation7.severity}] ${violation7.code}: ${violation7.message}`);
8731
- if (violation7.fix)
8732
- console.error(` fix: ${violation7.fix}`);
9461
+ for (const violation12 of result.violations) {
9462
+ console.error(` - [${violation12.severity}] ${violation12.code}: ${violation12.message}`);
9463
+ if (violation12.fix)
9464
+ console.error(` fix: ${violation12.fix}`);
8733
9465
  }
8734
9466
  }
8735
9467
  if (violations > 0)
@@ -8738,34 +9470,23 @@ lintCommand.description("Lint <target> (file or dir) \u2014 exit 1 on violations
8738
9470
  failScript(error, "lint");
8739
9471
  }
8740
9472
  });
8741
- function stripFrontmatter(text) {
8742
- const lines = text.replace(/^\uFEFF/, "").split(/\r?\n/);
8743
- if (lines.length === 0 || lines[0].trim() !== "---")
8744
- return text;
8745
- for (let i = 1;i < lines.length; i++) {
8746
- if (lines[i].trim() === "---")
8747
- return lines.slice(i + 1).join(`
8748
- `);
8749
- }
8750
- return text;
8751
- }
8752
9473
  var designMdCommand = program2.command("design-md").description("DESIGN.md token frontmatter / light-dark parity / completeness checks (engine-backed)");
8753
9474
  designMdCommand.command("validate").description("Validate DESIGN.md in <dir>: token frontmatter, light/dark parity when DESIGN.dark.md exists, " + "and the completeness level (exit 1 on violations, 2 on usage)").argument("[dir]", "Directory containing DESIGN.md").action((dir) => {
8754
9475
  try {
8755
9476
  if (!dir)
8756
9477
  throw new SddScriptError("usage: design-md validate <dir>", 2);
8757
- const abs = path11.resolve(dir);
8758
- const lightPath = path11.join(abs, "DESIGN.md");
8759
- if (!fs7.existsSync(lightPath))
9478
+ const abs = resolveCliPath(dir);
9479
+ const lightPath = path12.join(abs, "DESIGN.md");
9480
+ if (!fs8.existsSync(lightPath))
8760
9481
  throw new Error(`design file not found: ${lightPath}`);
8761
- const light = fs7.readFileSync(lightPath, "utf8");
9482
+ const light = fs8.readFileSync(lightPath, "utf8");
8762
9483
  const violations = [];
8763
9484
  const tokens = validateDesignTokenFrontmatter(light);
8764
9485
  printChecklist("design-md validate (tokens)", tokens);
8765
9486
  violations.push(...tokens.violations);
8766
- const darkPath = path11.join(abs, "DESIGN.dark.md");
8767
- if (fs7.existsSync(darkPath)) {
8768
- const parity = assertLightDarkParity(light, fs7.readFileSync(darkPath, "utf8"));
9487
+ const darkPath = path12.join(abs, "DESIGN.dark.md");
9488
+ if (fs8.existsSync(darkPath)) {
9489
+ const parity = assertLightDarkParity(light, fs8.readFileSync(darkPath, "utf8"));
8769
9490
  printChecklist("design-md validate (light/dark parity)", parity);
8770
9491
  violations.push(...parity.violations);
8771
9492
  }
@@ -8840,7 +9561,7 @@ function resolveAuditShortSha(cwd, override) {
8840
9561
  if (override !== undefined && override !== "")
8841
9562
  return override;
8842
9563
  try {
8843
- const out = execFileSync7("git", ["rev-parse", "--short", "HEAD"], {
9564
+ const out = execFileSync8("git", ["rev-parse", "--short", "HEAD"], {
8844
9565
  cwd,
8845
9566
  encoding: "utf8",
8846
9567
  stdio: ["ignore", "pipe", "ignore"]
@@ -8854,8 +9575,8 @@ auditCommand.command("scaffold").description("Scaffold an audit-<date>/ plan dir
8854
9575
  try {
8855
9576
  if (!findingsFile)
8856
9577
  throw new SddScriptError("usage: audit scaffold <findings-file> [--dir <out-dir>]", 2);
8857
- const abs = path11.resolve(findingsFile);
8858
- if (!fs7.existsSync(abs))
9578
+ const abs = resolveCliPath(findingsFile);
9579
+ if (!fs8.existsSync(abs))
8859
9580
  throw new Error(`findings file not found: ${abs}`);
8860
9581
  if (options.date !== undefined && !/^\d{4}-\d{2}-\d{2}$/.test(options.date)) {
8861
9582
  throw new SddScriptError("usage: audit scaffold \u2014 --date must be YYYY-MM-DD", 2);
@@ -8864,8 +9585,8 @@ auditCommand.command("scaffold").description("Scaffold an audit-<date>/ plan dir
8864
9585
  throw new SddScriptError("usage: audit scaffold \u2014 --sha must be a 7-40 char hex commit SHA", 2);
8865
9586
  }
8866
9587
  const date = options.date ?? new Date().toISOString().slice(0, 10);
8867
- const outDir = options.dir !== undefined ? path11.resolve(options.dir) : path11.resolve(`audit-${date}`);
8868
- const findings = parseAuditFindings(fs7.readFileSync(abs, "utf8"));
9588
+ const outDir = options.dir !== undefined ? resolveCliPath(options.dir) : resolveCliPath(`audit-${date}`);
9589
+ const findings = parseAuditFindings(fs8.readFileSync(abs, "utf8"));
8869
9590
  const sha = resolveAuditShortSha(process.cwd(), options.sha);
8870
9591
  const result = scaffoldAuditPlan(outDir, findings, { date, repoName: options.repo, repoShortSha: sha });
8871
9592
  const count = result.files.length;
@@ -8881,16 +9602,16 @@ compoundCommand.command("validate").description("Validate a knowledge doc frontm
8881
9602
  try {
8882
9603
  if (!docPath)
8883
9604
  throw new SddScriptError("usage: compound validate <doc-path> [--knowledge-dir <dir>]", 2);
8884
- const abs = path11.resolve(docPath);
8885
- if (!fs7.existsSync(abs))
9605
+ const abs = resolveCliPath(docPath);
9606
+ if (!fs8.existsSync(abs))
8886
9607
  throw new Error(`knowledge doc not found: ${abs}`);
8887
- const text = fs7.readFileSync(abs, "utf8");
9608
+ const text = fs8.readFileSync(abs, "utf8");
8888
9609
  const violations = [];
8889
9610
  const schema = validateSchemaYaml(text);
8890
9611
  printChecklist("compound validate (schema)", schema);
8891
9612
  violations.push(...schema.violations);
8892
9613
  if (options.knowledgeDir !== undefined) {
8893
- const knowledgeDir = path11.resolve(options.knowledgeDir);
9614
+ const knowledgeDir = resolveCliPath(options.knowledgeDir);
8894
9615
  const index = assertIndexRows(knowledgeDir);
8895
9616
  printChecklist("compound validate (index rows)", index);
8896
9617
  violations.push(...index.violations);
@@ -8926,6 +9647,8 @@ var HOST_SIGNALS = [
8926
9647
  "tool_search"
8927
9648
  ];
8928
9649
  var HOST_SIGNAL_LOOKUP = lookupTable(HOST_SIGNALS);
9650
+ var HOST_IDS = ["opencode", "omp", "pi", "dsh", "cursor", "codex", "kimi", "zcode"];
9651
+ var HOST_ID_LOOKUP = lookupTable(HOST_IDS);
8929
9652
  var hostCommand = program2.command("host").description("host detection from session tool shapes (engine-backed)");
8930
9653
  hostCommand.command("detect").description("Detect the active host from --signals (comma-separated tool-shape tokens): prints the host id or " + "'ambiguous' (exit 2 on usage)").option("--signals <list>", "Comma-separated tool-shape signals (e.g. question,ask,hub)").action((options) => {
8931
9654
  try {
@@ -8949,28 +9672,100 @@ hostCommand.command("detect").description("Detect the active host from --signals
8949
9672
  failScript(error, "host detect");
8950
9673
  }
8951
9674
  });
9675
+ hostCommand.command("skill-root").description("Resolve the loaded skill root for a host (mstar-host \xA7 Resolve loaded skill root): prints the canonical " + "skill-root string for --host / --skill (exit 1 on missing required options, 2 on usage errors)").requiredOption("--host <id>", "Host id (opencode | omp | pi | dsh | cursor | codex | kimi | zcode)").requiredOption("--skill <name>", "Skill name to resolve").option("--rel <path>", "Optional skill-relative path suffix").action((options) => {
9676
+ try {
9677
+ if (HOST_ID_LOOKUP[options.host] !== true) {
9678
+ throw new SddScriptError(`usage: host skill-root \u2014 unknown host "${options.host}" (valid: ${HOST_IDS.join(", ")})`, 2);
9679
+ }
9680
+ if (options.skill.trim() === "") {
9681
+ throw new SddScriptError("usage: host skill-root \u2014 --skill must be a non-empty skill name", 2);
9682
+ }
9683
+ const root = resolveSkillRoot(options.host, { skill: options.skill, rel: options.rel });
9684
+ if (options.host === "pi") {
9685
+ console.log(import_picocolors.default.yellow(root));
9686
+ } else {
9687
+ console.log(import_picocolors.default.green(root));
9688
+ }
9689
+ } catch (error) {
9690
+ failScript(error, "host skill-root");
9691
+ }
9692
+ });
8952
9693
  var skillCommand = program2.command("skill").description("skill-authoring lints (engine-backed)");
8953
- skillCommand.command("lint").description("Lint <skill-dir>/SKILL.md: frontmatter contract (name lowercase-hyphen, description trigger contract) " + "+ the five-question body (exit 1 on violations, 2 on usage)").argument("[skill-dir]", "Skill directory containing SKILL.md").action((skillDir) => {
9694
+ skillCommand.command("lint").description("Lint <skill-dir>/SKILL.md: frontmatter contract (name lowercase-hyphen, description trigger contract) " + "+ the five-question body + ephemeral-citation scan (task-<digits>-* artifacts and .mstar/sdd/\u2026 deeplinks \u2014 " + "exit 1 on violations, 2 on usage)").argument("[skill-dir]", "Skill directory containing SKILL.md").action((skillDir) => {
8954
9695
  try {
8955
9696
  if (!skillDir)
8956
9697
  throw new SddScriptError("usage: skill lint <skill-dir>", 2);
8957
- const skillFile = path11.join(path11.resolve(skillDir), "SKILL.md");
8958
- if (!fs7.existsSync(skillFile))
9698
+ const skillFile = path12.join(resolveCliPath(skillDir), "SKILL.md");
9699
+ if (!fs8.existsSync(skillFile))
8959
9700
  throw new Error(`SKILL.md not found: ${skillFile}`);
8960
- const text = fs7.readFileSync(skillFile, "utf8");
9701
+ const text = fs8.readFileSync(skillFile, "utf8");
8961
9702
  const violations = [];
8962
9703
  const frontmatter = lintSkillFrontmatter(text);
8963
9704
  printChecklist("skill lint (frontmatter)", frontmatter);
8964
9705
  violations.push(...frontmatter.violations);
8965
- const fiveQuestion = lintFiveQuestion(stripFrontmatter(text));
8966
- printChecklist("skill lint (five questions)", fiveQuestion);
8967
- violations.push(...fiveQuestion.violations);
9706
+ const skillBase = path12.basename(path12.dirname(skillFile));
9707
+ const isCore = skillBase === "mstar-harness-core";
9708
+ const fiveQuestionMode = skillBase.startsWith("mstar-") && !isCore && skillBase !== "mstar-skill-authoring" ? "runtime" : "authoring";
9709
+ if (isCore) {
9710
+ console.log(import_picocolors.default.yellow("skill lint (five questions): EXEMPT \u2014 mstar-harness-core is exempt by design (hub headings)"));
9711
+ } else {
9712
+ const fiveQuestion = lintFiveQuestion(stripFrontmatter(text), fiveQuestionMode);
9713
+ printChecklist("skill lint (five questions)", fiveQuestion);
9714
+ violations.push(...fiveQuestion.violations);
9715
+ }
9716
+ const ephemeral = findEphemeralCitations(text);
9717
+ const ephemeralGate = {
9718
+ ok: ephemeral.length === 0,
9719
+ violations: ephemeral.map((citation) => ({
9720
+ ok: false,
9721
+ severity: "medium",
9722
+ code: `skill.ephemeral.${citation.kind}`,
9723
+ message: `ephemeral ${citation.kind} citation at line ${citation.line}: "${citation.match}" \u2014 task artifacts and SDD deeplinks survive nothing; durable skill text cites in-repo artifacts only (knowledge conventions/skill-content-porting-discipline.md \xA73)`,
9724
+ fix: `rewrite "${citation.match}" as a placeholder form (e.g. task-N-report, <plan-id>, {SDD_DIR}/task-N-report.md) or cite a stable in-repo artifact instead`
9725
+ }))
9726
+ };
9727
+ printChecklist("skill lint (ephemeral citations)", ephemeralGate);
9728
+ violations.push(...ephemeralGate.violations);
8968
9729
  if (violations.length > 0)
8969
9730
  process.exitCode = 1;
8970
9731
  } catch (error) {
8971
9732
  failScript(error, "skill lint");
8972
9733
  }
8973
9734
  });
9735
+ var rolesCommand = program2.command("roles").description("mstar-roles mapping / load-order checks (engine-backed)");
9736
+ rolesCommand.command("validate").description("Validate the mstar-roles skill-dir state: role mapping / parameter tables against the on-disk " + "references layout plus load-order declarations across sibling mstar-* skills " + "(exit 1 on violations)").option("--roles-dir <dir>", "mstar-roles skill directory (default: skills/mstar-roles, resolved against the project root)").option("--skills-dir <dir>", "Skills root scanned for sibling mstar-* skills (default: parent of the roles dir)").action((options) => {
9737
+ try {
9738
+ const rolesDir = resolveCliPath(options.rolesDir ?? "skills/mstar-roles");
9739
+ const skillsRoot = options.skillsDir ? resolveCliPath(options.skillsDir) : path12.dirname(rolesDir);
9740
+ const violations = [];
9741
+ const mapping = validateRoleMapping(rolesDir);
9742
+ printChecklist("roles validate (mapping)", mapping);
9743
+ violations.push(...mapping.violations);
9744
+ const skillTexts = {};
9745
+ for (const entry of fs8.readdirSync(skillsRoot, { withFileTypes: true })) {
9746
+ if (!entry.isDirectory() || !entry.name.startsWith("mstar-"))
9747
+ continue;
9748
+ const skillFile = path12.join(skillsRoot, entry.name, "SKILL.md");
9749
+ if (!fs8.existsSync(skillFile))
9750
+ continue;
9751
+ try {
9752
+ skillTexts[entry.name] = fs8.readFileSync(skillFile, "utf8");
9753
+ } catch {}
9754
+ }
9755
+ const loadOrder = lintLoadOrder(skillTexts);
9756
+ printChecklist("roles validate (load order)", loadOrder);
9757
+ violations.push(...loadOrder.violations);
9758
+ const total = violations.length;
9759
+ const siblingCount = Object.keys(skillTexts).length;
9760
+ const loadOrderChecked = Object.keys(skillTexts).filter((name) => name !== "mstar-harness-core").length;
9761
+ const coreExempt = loadOrderChecked !== siblingCount;
9762
+ console.log(`roles validate: ${total === 0 ? "OK" : "FAIL"} (${total} violation${total === 1 ? "" : "s"}, ` + `${siblingCount} sibling skill${siblingCount === 1 ? "" : "s"} scanned; ` + `load-order over ${loadOrderChecked}${coreExempt ? ", core exempt" : ""})`);
9763
+ if (total > 0)
9764
+ process.exitCode = 1;
9765
+ } catch (error) {
9766
+ failScript(error, "roles validate");
9767
+ }
9768
+ });
8974
9769
  program2.parseAsync(process.argv).catch((error) => {
8975
9770
  console.error(import_picocolors.default.red(`Setup failed: ${error.message}`));
8976
9771
  process.exitCode = 1;