@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.
- package/dist/mstar-harness.js +1336 -541
- package/package.json +6 -5
package/dist/mstar-harness.js
CHANGED
|
@@ -2344,9 +2344,9 @@ var require_commander = __commonJS((exports) => {
|
|
|
2344
2344
|
});
|
|
2345
2345
|
|
|
2346
2346
|
// src/index.ts
|
|
2347
|
-
import { execFileSync as
|
|
2348
|
-
import
|
|
2349
|
-
import
|
|
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: ["
|
|
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
|
-
["
|
|
3937
|
-
["
|
|
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 "\\")
|
|
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/)
|
|
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
|
|
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
|
|
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>
|
|
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*(?:[
|
|
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)
|
|
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>
|
|
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 "
|
|
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}
|
|
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
|
|
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
|
|
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
|
-
|
|
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 (!
|
|
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
|
|
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(" | ")}
|
|
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 && !
|
|
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 && !
|
|
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
|
|
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 (!
|
|
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(" | ")}
|
|
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
|
|
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(" | ")}
|
|
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(" | ")}
|
|
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}
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
|
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()}
|
|
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 (!
|
|
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) =>
|
|
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 (!
|
|
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
|
|
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}"
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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}'
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(", ")})
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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)}'
|
|
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
|
|
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
|
|
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)}
|
|
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)}
|
|
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
|
|
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
|
|
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
|
|
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}"
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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)}
|
|
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)}
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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}"
|
|
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
|
|
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}
|
|
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(", ")})
|
|
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)
|
|
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 = /[
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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}")
|
|
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
|
|
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]}"
|
|
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
|
|
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
|
|
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
|
|
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
|
|
6334
|
-
const headings =
|
|
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
|
|
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}"
|
|
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
|
|
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 (!
|
|
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 (
|
|
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/)
|
|
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
|
|
6853
|
+
function violation7(severity, code, message, fix) {
|
|
6454
6854
|
return { ok: false, severity, code, message, fix };
|
|
6455
6855
|
}
|
|
6456
|
-
function
|
|
6856
|
+
function validateNonEmptyString3(violations, value, field, missingCode, invalidCode) {
|
|
6457
6857
|
if (value === undefined) {
|
|
6458
|
-
violations.push(
|
|
6858
|
+
violations.push(violation7("high", missingCode, `missing required field: ${field}`));
|
|
6459
6859
|
} else if (typeof value !== "string" || value.trim() === "") {
|
|
6460
|
-
violations.push(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
6482
|
-
} else if (!
|
|
6483
|
-
violations.push(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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 "
|
|
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)
|
|
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
|
|
7518
|
-
import { execFileSync as
|
|
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
|
-
|
|
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
|
-
|
|
8103
|
+
execFileSync7("omp", args, { stdio: "pipe", encoding: "utf8" });
|
|
7536
8104
|
}
|
|
7537
8105
|
function listInstalledPlugins() {
|
|
7538
8106
|
try {
|
|
7539
|
-
const raw =
|
|
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(`${
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
8175
|
+
function runInit3(scope, dryRun) {
|
|
7608
8176
|
const notes = ensureLocalHarnessRepo(dryRun);
|
|
7609
8177
|
const projectRoot = resolveProjectRoot2();
|
|
7610
|
-
if (fs5.existsSync(
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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) =>
|
|
7696
|
-
runInstallDoctor: (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
|
|
7701
|
-
import
|
|
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
|
|
8290
|
+
return path9.isAbsolute(raw) ? raw : path9.join(resolveProjectRoot2(), raw);
|
|
7723
8291
|
}
|
|
7724
8292
|
if (scope === "global")
|
|
7725
|
-
return
|
|
7726
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
7821
|
-
import
|
|
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 =
|
|
7833
|
-
var KNOWN_MARKETPLACES_PATH =
|
|
7834
|
-
var MARKETPLACE_DIR =
|
|
7835
|
-
var MARKETPLACE_JSON_PATH =
|
|
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 =
|
|
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 =
|
|
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
|
|
8531
|
+
function runInit4(scope, dryRun) {
|
|
7964
8532
|
const notes = ensureLocalHarnessRepo(dryRun);
|
|
7965
8533
|
const projectRoot = resolveProjectRoot2();
|
|
7966
8534
|
if (scope === "project") {
|
|
7967
|
-
const checkoutPath =
|
|
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
|
|
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
|
|
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 =
|
|
8563
|
+
const checkoutPath = path10.join(projectRoot, ZCODE_PLUGIN_CHECKOUT_PROJECT);
|
|
7996
8564
|
errors2.push(...validateGitCheckout(checkoutPath, ZCODE_PLUGIN_MARKER));
|
|
7997
|
-
const gitignorePath =
|
|
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) =>
|
|
8017
|
-
runInstallDoctor: (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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
8801
|
+
return path12.resolve(options.root);
|
|
8195
8802
|
let candidate = resolveProjectRoot3();
|
|
8196
|
-
while (!
|
|
8197
|
-
const parent =
|
|
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
|
|
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
|
-
|
|
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 ?
|
|
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
|
|
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
|
|
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 (!
|
|
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
|
|
8278
|
-
console.error(` - [${
|
|
8279
|
-
if (
|
|
8280
|
-
console.error(` 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
|
-
|
|
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
|
|
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 =
|
|
8319
|
-
if (!
|
|
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
|
|
8346
|
-
console.error(` - [${
|
|
8347
|
-
if (
|
|
8348
|
-
console.error(` 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
|
|
8409
|
-
console.error(` - [${
|
|
8410
|
-
if (
|
|
8411
|
-
console.error(` 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 =
|
|
8417
|
-
const compassPath =
|
|
8418
|
-
if (!
|
|
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 (!
|
|
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
|
|
8446
|
-
console.error(` - [${
|
|
8447
|
-
if (
|
|
8448
|
-
console.error(` 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 =
|
|
8459
|
-
if (!
|
|
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 =
|
|
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 ?
|
|
8518
|
-
if (!
|
|
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 ?
|
|
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 =
|
|
8568
|
-
if (!
|
|
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 =
|
|
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 =
|
|
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 =
|
|
8636
|
-
if (dir.includes(`${
|
|
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[
|
|
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
|
|
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(
|
|
8651
|
-
} else if (entry.isFile() && lintTargetType(
|
|
8652
|
-
targets.push(
|
|
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 =
|
|
8661
|
-
const text =
|
|
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 "${
|
|
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 =
|
|
8700
|
-
if (!
|
|
9431
|
+
const abs = resolveCliPath(target);
|
|
9432
|
+
if (!fs8.existsSync(abs))
|
|
8701
9433
|
throw new Error(`lint target not found: ${abs}`);
|
|
8702
|
-
const targets =
|
|
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
|
|
8730
|
-
console.error(` - [${
|
|
8731
|
-
if (
|
|
8732
|
-
console.error(` 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 =
|
|
8758
|
-
const lightPath =
|
|
8759
|
-
if (!
|
|
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 =
|
|
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 =
|
|
8767
|
-
if (
|
|
8768
|
-
const parity = assertLightDarkParity(light,
|
|
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 =
|
|
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 =
|
|
8858
|
-
if (!
|
|
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 ?
|
|
8868
|
-
const findings = parseAuditFindings(
|
|
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 =
|
|
8885
|
-
if (!
|
|
9605
|
+
const abs = resolveCliPath(docPath);
|
|
9606
|
+
if (!fs8.existsSync(abs))
|
|
8886
9607
|
throw new Error(`knowledge doc not found: ${abs}`);
|
|
8887
|
-
const text =
|
|
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 =
|
|
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 =
|
|
8958
|
-
if (!
|
|
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 =
|
|
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
|
|
8966
|
-
|
|
8967
|
-
|
|
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;
|