@mstar-harness/cli 2.1.1 → 2.3.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 +482 -400
- package/package.json +4 -4
package/dist/mstar-harness.js
CHANGED
|
@@ -2344,13 +2344,24 @@ var require_commander = __commonJS((exports) => {
|
|
|
2344
2344
|
});
|
|
2345
2345
|
|
|
2346
2346
|
// src/index.ts
|
|
2347
|
-
import { execFileSync as
|
|
2347
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
2348
2348
|
import fs7 from "fs";
|
|
2349
2349
|
import path11 from "path";
|
|
2350
2350
|
|
|
2351
2351
|
// ../../node_modules/@inquirer/core/dist/lib/key.js
|
|
2352
|
-
var
|
|
2353
|
-
var
|
|
2352
|
+
var keybindings = ["emacs", "vim"];
|
|
2353
|
+
var keybindingLookup = new Set(keybindings);
|
|
2354
|
+
function isKeybinding(value) {
|
|
2355
|
+
return keybindingLookup.has(value);
|
|
2356
|
+
}
|
|
2357
|
+
function getDefaultKeybindings() {
|
|
2358
|
+
const env = process.env["INQUIRER_KEYBINDINGS"];
|
|
2359
|
+
if (!env)
|
|
2360
|
+
return [];
|
|
2361
|
+
return Array.from(new Set(env.toLowerCase().split(/[\s,]+/).filter(isKeybinding)));
|
|
2362
|
+
}
|
|
2363
|
+
var isUpKey = (key, keybindings2 = []) => key.name === "up" || keybindings2.includes("vim") && key.name === "k" || keybindings2.includes("emacs") && key.ctrl && key.name === "p";
|
|
2364
|
+
var isDownKey = (key, keybindings2 = []) => key.name === "down" || keybindings2.includes("vim") && key.name === "j" || keybindings2.includes("emacs") && key.ctrl && key.name === "n";
|
|
2354
2365
|
var isBackspaceKey = (key) => key.name === "backspace";
|
|
2355
2366
|
var isNumberKey = (key) => "1234567890".includes(key.name);
|
|
2356
2367
|
var isEnterKey = (key) => key.name === "enter" || key.name === "return";
|
|
@@ -2535,267 +2546,267 @@ function isUnicodeSupported() {
|
|
|
2535
2546
|
var common = {
|
|
2536
2547
|
circleQuestionMark: "(?)",
|
|
2537
2548
|
questionMarkPrefix: "(?)",
|
|
2538
|
-
square: "
|
|
2539
|
-
squareDarkShade: "
|
|
2540
|
-
squareMediumShade: "
|
|
2541
|
-
squareLightShade: "
|
|
2542
|
-
squareTop: "
|
|
2543
|
-
squareBottom: "
|
|
2544
|
-
squareLeft: "
|
|
2545
|
-
squareRight: "
|
|
2546
|
-
squareCenter: "
|
|
2547
|
-
bullet: "
|
|
2548
|
-
dot: "
|
|
2549
|
-
ellipsis: "
|
|
2550
|
-
pointerSmall: "
|
|
2551
|
-
triangleUp: "
|
|
2552
|
-
triangleUpSmall: "
|
|
2553
|
-
triangleDown: "
|
|
2554
|
-
triangleDownSmall: "
|
|
2555
|
-
triangleLeftSmall: "
|
|
2556
|
-
triangleRightSmall: "
|
|
2557
|
-
home: "
|
|
2558
|
-
heart: "
|
|
2559
|
-
musicNote: "
|
|
2560
|
-
musicNoteBeamed: "
|
|
2561
|
-
arrowUp: "
|
|
2562
|
-
arrowDown: "
|
|
2563
|
-
arrowLeft: "
|
|
2564
|
-
arrowRight: "
|
|
2565
|
-
arrowLeftRight: "
|
|
2566
|
-
arrowUpDown: "
|
|
2567
|
-
almostEqual: "
|
|
2568
|
-
notEqual: "
|
|
2569
|
-
lessOrEqual: "
|
|
2570
|
-
greaterOrEqual: "
|
|
2571
|
-
identical: "
|
|
2572
|
-
infinity: "
|
|
2573
|
-
subscriptZero: "
|
|
2574
|
-
subscriptOne: "
|
|
2575
|
-
subscriptTwo: "
|
|
2576
|
-
subscriptThree: "
|
|
2577
|
-
subscriptFour: "
|
|
2578
|
-
subscriptFive: "
|
|
2579
|
-
subscriptSix: "
|
|
2580
|
-
subscriptSeven: "
|
|
2581
|
-
subscriptEight: "
|
|
2582
|
-
subscriptNine: "
|
|
2583
|
-
oneHalf: "
|
|
2584
|
-
oneThird: "
|
|
2585
|
-
oneQuarter: "
|
|
2586
|
-
oneFifth: "
|
|
2587
|
-
oneSixth: "
|
|
2588
|
-
oneEighth: "
|
|
2589
|
-
twoThirds: "
|
|
2590
|
-
twoFifths: "
|
|
2591
|
-
threeQuarters: "
|
|
2592
|
-
threeFifths: "
|
|
2593
|
-
threeEighths: "
|
|
2594
|
-
fourFifths: "
|
|
2595
|
-
fiveSixths: "
|
|
2596
|
-
fiveEighths: "
|
|
2597
|
-
sevenEighths: "
|
|
2598
|
-
line: "
|
|
2599
|
-
lineBold: "
|
|
2600
|
-
lineDouble: "
|
|
2601
|
-
lineDashed0: "
|
|
2602
|
-
lineDashed1: "
|
|
2603
|
-
lineDashed2: "
|
|
2604
|
-
lineDashed3: "
|
|
2605
|
-
lineDashed4: "
|
|
2606
|
-
lineDashed5: "
|
|
2607
|
-
lineDashed6: "
|
|
2608
|
-
lineDashed7: "
|
|
2609
|
-
lineDashed8: "
|
|
2610
|
-
lineDashed9: "
|
|
2611
|
-
lineDashed10: "
|
|
2612
|
-
lineDashed11: "
|
|
2613
|
-
lineDashed12: "
|
|
2614
|
-
lineDashed13: "
|
|
2615
|
-
lineDashed14: "
|
|
2616
|
-
lineDashed15: "
|
|
2617
|
-
lineVertical: "
|
|
2618
|
-
lineVerticalBold: "
|
|
2619
|
-
lineVerticalDouble: "
|
|
2620
|
-
lineVerticalDashed0: "
|
|
2621
|
-
lineVerticalDashed1: "
|
|
2622
|
-
lineVerticalDashed2: "
|
|
2623
|
-
lineVerticalDashed3: "
|
|
2624
|
-
lineVerticalDashed4: "
|
|
2625
|
-
lineVerticalDashed5: "
|
|
2626
|
-
lineVerticalDashed6: "
|
|
2627
|
-
lineVerticalDashed7: "
|
|
2628
|
-
lineVerticalDashed8: "
|
|
2629
|
-
lineVerticalDashed9: "
|
|
2630
|
-
lineVerticalDashed10: "
|
|
2631
|
-
lineVerticalDashed11: "
|
|
2632
|
-
lineDownLeft: "
|
|
2633
|
-
lineDownLeftArc: "
|
|
2634
|
-
lineDownBoldLeftBold: "
|
|
2635
|
-
lineDownBoldLeft: "
|
|
2636
|
-
lineDownLeftBold: "
|
|
2637
|
-
lineDownDoubleLeftDouble: "
|
|
2638
|
-
lineDownDoubleLeft: "
|
|
2639
|
-
lineDownLeftDouble: "
|
|
2640
|
-
lineDownRight: "
|
|
2641
|
-
lineDownRightArc: "
|
|
2642
|
-
lineDownBoldRightBold: "
|
|
2643
|
-
lineDownBoldRight: "
|
|
2644
|
-
lineDownRightBold: "
|
|
2645
|
-
lineDownDoubleRightDouble: "
|
|
2646
|
-
lineDownDoubleRight: "
|
|
2647
|
-
lineDownRightDouble: "
|
|
2648
|
-
lineUpLeft: "
|
|
2649
|
-
lineUpLeftArc: "
|
|
2650
|
-
lineUpBoldLeftBold: "
|
|
2651
|
-
lineUpBoldLeft: "
|
|
2652
|
-
lineUpLeftBold: "
|
|
2653
|
-
lineUpDoubleLeftDouble: "
|
|
2654
|
-
lineUpDoubleLeft: "
|
|
2655
|
-
lineUpLeftDouble: "
|
|
2656
|
-
lineUpRight: "
|
|
2657
|
-
lineUpRightArc: "
|
|
2658
|
-
lineUpBoldRightBold: "
|
|
2659
|
-
lineUpBoldRight: "
|
|
2660
|
-
lineUpRightBold: "
|
|
2661
|
-
lineUpDoubleRightDouble: "
|
|
2662
|
-
lineUpDoubleRight: "
|
|
2663
|
-
lineUpRightDouble: "
|
|
2664
|
-
lineUpDownLeft: "
|
|
2665
|
-
lineUpBoldDownBoldLeftBold: "
|
|
2666
|
-
lineUpBoldDownBoldLeft: "
|
|
2667
|
-
lineUpDownLeftBold: "
|
|
2668
|
-
lineUpBoldDownLeftBold: "
|
|
2669
|
-
lineUpDownBoldLeftBold: "
|
|
2670
|
-
lineUpDownBoldLeft: "
|
|
2671
|
-
lineUpBoldDownLeft: "
|
|
2672
|
-
lineUpDoubleDownDoubleLeftDouble: "
|
|
2673
|
-
lineUpDoubleDownDoubleLeft: "
|
|
2674
|
-
lineUpDownLeftDouble: "
|
|
2675
|
-
lineUpDownRight: "
|
|
2676
|
-
lineUpBoldDownBoldRightBold: "
|
|
2677
|
-
lineUpBoldDownBoldRight: "
|
|
2678
|
-
lineUpDownRightBold: "
|
|
2679
|
-
lineUpBoldDownRightBold: "
|
|
2680
|
-
lineUpDownBoldRightBold: "
|
|
2681
|
-
lineUpDownBoldRight: "
|
|
2682
|
-
lineUpBoldDownRight: "
|
|
2683
|
-
lineUpDoubleDownDoubleRightDouble: "
|
|
2684
|
-
lineUpDoubleDownDoubleRight: "
|
|
2685
|
-
lineUpDownRightDouble: "
|
|
2686
|
-
lineDownLeftRight: "
|
|
2687
|
-
lineDownBoldLeftBoldRightBold: "
|
|
2688
|
-
lineDownLeftBoldRightBold: "
|
|
2689
|
-
lineDownBoldLeftRight: "
|
|
2690
|
-
lineDownBoldLeftBoldRight: "
|
|
2691
|
-
lineDownBoldLeftRightBold: "
|
|
2692
|
-
lineDownLeftRightBold: "
|
|
2693
|
-
lineDownLeftBoldRight: "
|
|
2694
|
-
lineDownDoubleLeftDoubleRightDouble: "
|
|
2695
|
-
lineDownDoubleLeftRight: "
|
|
2696
|
-
lineDownLeftDoubleRightDouble: "
|
|
2697
|
-
lineUpLeftRight: "
|
|
2698
|
-
lineUpBoldLeftBoldRightBold: "
|
|
2699
|
-
lineUpLeftBoldRightBold: "
|
|
2700
|
-
lineUpBoldLeftRight: "
|
|
2701
|
-
lineUpBoldLeftBoldRight: "
|
|
2702
|
-
lineUpBoldLeftRightBold: "
|
|
2703
|
-
lineUpLeftRightBold: "
|
|
2704
|
-
lineUpLeftBoldRight: "
|
|
2705
|
-
lineUpDoubleLeftDoubleRightDouble: "
|
|
2706
|
-
lineUpDoubleLeftRight: "
|
|
2707
|
-
lineUpLeftDoubleRightDouble: "
|
|
2708
|
-
lineUpDownLeftRight: "
|
|
2709
|
-
lineUpBoldDownBoldLeftBoldRightBold: "
|
|
2710
|
-
lineUpDownBoldLeftBoldRightBold: "
|
|
2711
|
-
lineUpBoldDownLeftBoldRightBold: "
|
|
2712
|
-
lineUpBoldDownBoldLeftRightBold: "
|
|
2713
|
-
lineUpBoldDownBoldLeftBoldRight: "
|
|
2714
|
-
lineUpBoldDownLeftRight: "
|
|
2715
|
-
lineUpDownBoldLeftRight: "
|
|
2716
|
-
lineUpDownLeftBoldRight: "
|
|
2717
|
-
lineUpDownLeftRightBold: "
|
|
2718
|
-
lineUpBoldDownBoldLeftRight: "
|
|
2719
|
-
lineUpDownLeftBoldRightBold: "
|
|
2720
|
-
lineUpBoldDownLeftBoldRight: "
|
|
2721
|
-
lineUpBoldDownLeftRightBold: "
|
|
2722
|
-
lineUpDownBoldLeftBoldRight: "
|
|
2723
|
-
lineUpDownBoldLeftRightBold: "
|
|
2724
|
-
lineUpDoubleDownDoubleLeftDoubleRightDouble: "
|
|
2725
|
-
lineUpDoubleDownDoubleLeftRight: "
|
|
2726
|
-
lineUpDownLeftDoubleRightDouble: "
|
|
2727
|
-
lineCross: "
|
|
2728
|
-
lineBackslash: "
|
|
2729
|
-
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"
|
|
2730
2741
|
};
|
|
2731
2742
|
var specialMainSymbols = {
|
|
2732
|
-
tick: "
|
|
2733
|
-
info: "
|
|
2734
|
-
warning: "
|
|
2735
|
-
cross: "
|
|
2736
|
-
squareSmall: "
|
|
2737
|
-
squareSmallFilled: "
|
|
2738
|
-
circle: "
|
|
2739
|
-
circleFilled: "
|
|
2740
|
-
circleDotted: "
|
|
2741
|
-
circleDouble: "
|
|
2742
|
-
circleCircle: "
|
|
2743
|
-
circleCross: "
|
|
2744
|
-
circlePipe: "
|
|
2745
|
-
radioOn: "
|
|
2746
|
-
radioOff: "
|
|
2747
|
-
checkboxOn: "
|
|
2748
|
-
checkboxOff: "
|
|
2749
|
-
checkboxCircleOn: "
|
|
2750
|
-
checkboxCircleOff: "
|
|
2751
|
-
pointer: "
|
|
2752
|
-
triangleUpOutline: "
|
|
2753
|
-
triangleLeft: "
|
|
2754
|
-
triangleRight: "
|
|
2755
|
-
lozenge: "
|
|
2756
|
-
lozengeOutline: "
|
|
2757
|
-
hamburger: "
|
|
2758
|
-
smiley: "
|
|
2759
|
-
mustache: "
|
|
2760
|
-
star: "
|
|
2761
|
-
play: "
|
|
2762
|
-
nodejs: "
|
|
2763
|
-
oneSeventh: "
|
|
2764
|
-
oneNinth: "
|
|
2765
|
-
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"
|
|
2766
2777
|
};
|
|
2767
2778
|
var specialFallbackSymbols = {
|
|
2768
|
-
tick: "
|
|
2779
|
+
tick: "\u221a",
|
|
2769
2780
|
info: "i",
|
|
2770
|
-
warning: "
|
|
2771
|
-
cross: "
|
|
2772
|
-
squareSmall: "
|
|
2773
|
-
squareSmallFilled: "
|
|
2781
|
+
warning: "\u203c",
|
|
2782
|
+
cross: "\u00d7",
|
|
2783
|
+
squareSmall: "\u25a1",
|
|
2784
|
+
squareSmallFilled: "\u25a0",
|
|
2774
2785
|
circle: "( )",
|
|
2775
2786
|
circleFilled: "(*)",
|
|
2776
2787
|
circleDotted: "( )",
|
|
2777
2788
|
circleDouble: "( )",
|
|
2778
|
-
circleCircle: "(
|
|
2779
|
-
circleCross: "(
|
|
2780
|
-
circlePipe: "(
|
|
2789
|
+
circleCircle: "(\u25cb)",
|
|
2790
|
+
circleCross: "(\u00d7)",
|
|
2791
|
+
circlePipe: "(\u2502)",
|
|
2781
2792
|
radioOn: "(*)",
|
|
2782
2793
|
radioOff: "( )",
|
|
2783
|
-
checkboxOn: "[
|
|
2794
|
+
checkboxOn: "[\u00d7]",
|
|
2784
2795
|
checkboxOff: "[ ]",
|
|
2785
|
-
checkboxCircleOn: "(
|
|
2796
|
+
checkboxCircleOn: "(\u00d7)",
|
|
2786
2797
|
checkboxCircleOff: "( )",
|
|
2787
2798
|
pointer: ">",
|
|
2788
|
-
triangleUpOutline: "
|
|
2789
|
-
triangleLeft: "
|
|
2790
|
-
triangleRight: "
|
|
2791
|
-
lozenge: "
|
|
2792
|
-
lozengeOutline: "
|
|
2793
|
-
hamburger: "
|
|
2794
|
-
smiley: "
|
|
2795
|
-
mustache: "
|
|
2796
|
-
star: "
|
|
2797
|
-
play: "
|
|
2798
|
-
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",
|
|
2799
2810
|
oneSeventh: "1/7",
|
|
2800
2811
|
oneNinth: "1/9",
|
|
2801
2812
|
oneTenth: "1/10"
|
|
@@ -2821,8 +2832,9 @@ var defaultTheme = {
|
|
|
2821
2832
|
},
|
|
2822
2833
|
spinner: {
|
|
2823
2834
|
interval: 80,
|
|
2824
|
-
frames: ["
|
|
2835
|
+
frames: ["\u280b", "\u2819", "\u2839", "\u2838", "\u283c", "\u2834", "\u2826", "\u2827", "\u2807", "\u280f"].map((frame) => styleText("yellow", frame))
|
|
2825
2836
|
},
|
|
2837
|
+
keybindings: [],
|
|
2826
2838
|
style: {
|
|
2827
2839
|
answer: (text) => styleText("cyan", text),
|
|
2828
2840
|
message: (text) => styleText("bold", text),
|
|
@@ -2833,6 +2845,12 @@ var defaultTheme = {
|
|
|
2833
2845
|
key: (text) => styleText("cyan", styleText("bold", `<${text}>`))
|
|
2834
2846
|
}
|
|
2835
2847
|
};
|
|
2848
|
+
function getDefaultTheme() {
|
|
2849
|
+
return {
|
|
2850
|
+
...defaultTheme,
|
|
2851
|
+
keybindings: getDefaultKeybindings()
|
|
2852
|
+
};
|
|
2853
|
+
}
|
|
2836
2854
|
|
|
2837
2855
|
// ../../node_modules/@inquirer/core/dist/lib/make-theme.js
|
|
2838
2856
|
function isPlainObject(value) {
|
|
@@ -2856,7 +2874,7 @@ function deepMerge(...objects) {
|
|
|
2856
2874
|
}
|
|
2857
2875
|
function makeTheme(...themes) {
|
|
2858
2876
|
const themesToMerge = [
|
|
2859
|
-
|
|
2877
|
+
getDefaultTheme(),
|
|
2860
2878
|
...themes.filter((theme) => theme != null)
|
|
2861
2879
|
];
|
|
2862
2880
|
return deepMerge(...themesToMerge);
|
|
@@ -3060,7 +3078,7 @@ var dist_default3 = fastStringWidth;
|
|
|
3060
3078
|
|
|
3061
3079
|
// ../../node_modules/fast-wrap-ansi/lib/main.js
|
|
3062
3080
|
var ESC = "\x1B";
|
|
3063
|
-
var CSI = "
|
|
3081
|
+
var CSI = "\u009b";
|
|
3064
3082
|
var END_CODE = 39;
|
|
3065
3083
|
var ANSI_ESCAPE_BELL = "\x07";
|
|
3066
3084
|
var ANSI_CSI = "[";
|
|
@@ -3273,7 +3291,7 @@ function wrapAnsi(string, columns, options) {
|
|
|
3273
3291
|
// ../../node_modules/@inquirer/core/dist/lib/utils.js
|
|
3274
3292
|
function breakLines(content, width) {
|
|
3275
3293
|
return content.split(`
|
|
3276
|
-
`).flatMap((line) => wrapAnsi(line, width, { trim: false,
|
|
3294
|
+
`).flatMap((line) => wrapAnsi(line, width, { trim: false, wordWrap: false }).split(`
|
|
3277
3295
|
`).map((str) => str.trimEnd())).join(`
|
|
3278
3296
|
`);
|
|
3279
3297
|
}
|
|
@@ -3638,6 +3656,7 @@ class ScreenManager {
|
|
|
3638
3656
|
let output = cursorDown(this.extraLinesUnderPrompt);
|
|
3639
3657
|
output += clearContent ? eraseLines(this.height) : `
|
|
3640
3658
|
`;
|
|
3659
|
+
output += cursorLeft;
|
|
3641
3660
|
output += cursorShow;
|
|
3642
3661
|
this.write(output);
|
|
3643
3662
|
this.rl.close();
|
|
@@ -3661,7 +3680,7 @@ class PromisePolyfill extends Promise {
|
|
|
3661
3680
|
import path from "node:path";
|
|
3662
3681
|
var nativeSetImmediate = globalThis.setImmediate;
|
|
3663
3682
|
function getCallSites() {
|
|
3664
|
-
const
|
|
3683
|
+
const savedPrepareStackTrace = Error.prepareStackTrace;
|
|
3665
3684
|
let result = [];
|
|
3666
3685
|
try {
|
|
3667
3686
|
Error.prepareStackTrace = (_, callSites) => {
|
|
@@ -3673,7 +3692,7 @@ function getCallSites() {
|
|
|
3673
3692
|
} catch {
|
|
3674
3693
|
return result;
|
|
3675
3694
|
}
|
|
3676
|
-
Error.prepareStackTrace =
|
|
3695
|
+
Error.prepareStackTrace = savedPrepareStackTrace;
|
|
3677
3696
|
return result;
|
|
3678
3697
|
}
|
|
3679
3698
|
function createPrompt(view) {
|
|
@@ -3789,11 +3808,10 @@ var selectTheme = {
|
|
|
3789
3808
|
style: {
|
|
3790
3809
|
disabled: (text) => styleText3("dim", text),
|
|
3791
3810
|
description: (text) => styleText3("cyan", text),
|
|
3792
|
-
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 "))
|
|
3793
3812
|
},
|
|
3794
3813
|
i18n: { disabledError: "This option is disabled and cannot be selected." },
|
|
3795
|
-
indexMode: "hidden"
|
|
3796
|
-
keybindings: []
|
|
3814
|
+
indexMode: "hidden"
|
|
3797
3815
|
};
|
|
3798
3816
|
function isSelectable(item) {
|
|
3799
3817
|
return !Separator.isSeparator(item) && !item.disabled;
|
|
@@ -3830,11 +3848,11 @@ function normalizeChoices(choices) {
|
|
|
3830
3848
|
var dist_default4 = createPrompt((config, done) => {
|
|
3831
3849
|
const { loop = true, pageSize = 7 } = config;
|
|
3832
3850
|
const theme = makeTheme(selectTheme, config.theme);
|
|
3833
|
-
const { keybindings } = theme;
|
|
3851
|
+
const { keybindings: keybindings2 } = theme;
|
|
3834
3852
|
const [status, setStatus] = useState("idle");
|
|
3835
3853
|
const prefix = usePrefix({ status, theme });
|
|
3836
3854
|
const searchTimeoutRef = useRef();
|
|
3837
|
-
const searchEnabled = !
|
|
3855
|
+
const searchEnabled = !keybindings2.includes("vim");
|
|
3838
3856
|
const items = useMemo(() => normalizeChoices(config.choices), [config.choices]);
|
|
3839
3857
|
const bounds = useMemo(() => {
|
|
3840
3858
|
const first = items.findIndex(isNavigable);
|
|
@@ -3867,10 +3885,10 @@ var dist_default4 = createPrompt((config, done) => {
|
|
|
3867
3885
|
setStatus("done");
|
|
3868
3886
|
done(selectedChoice.value);
|
|
3869
3887
|
}
|
|
3870
|
-
} else if (isUpKey(key,
|
|
3888
|
+
} else if (isUpKey(key, keybindings2) || isDownKey(key, keybindings2)) {
|
|
3871
3889
|
rl.clearLine(0);
|
|
3872
|
-
if (loop || isUpKey(key,
|
|
3873
|
-
const offset = isUpKey(key,
|
|
3890
|
+
if (loop || isUpKey(key, keybindings2) && active !== bounds.first || isDownKey(key, keybindings2) && active !== bounds.last) {
|
|
3891
|
+
const offset = isUpKey(key, keybindings2) ? -1 : 1;
|
|
3874
3892
|
let next = active;
|
|
3875
3893
|
do {
|
|
3876
3894
|
next = (next + offset + items.length) % items.length;
|
|
@@ -3915,8 +3933,8 @@ var dist_default4 = createPrompt((config, done) => {
|
|
|
3915
3933
|
}, []);
|
|
3916
3934
|
const message = theme.style.message(config.message, status);
|
|
3917
3935
|
const helpLine = theme.style.keysHelpTip([
|
|
3918
|
-
["
|
|
3919
|
-
["
|
|
3936
|
+
["\u2191\u2193", "navigate"],
|
|
3937
|
+
["\u23ce", "select"]
|
|
3920
3938
|
]);
|
|
3921
3939
|
let separatorCount = 0;
|
|
3922
3940
|
const page = usePagination({
|
|
@@ -3979,6 +3997,7 @@ import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileS
|
|
|
3979
3997
|
import { randomUUID } from "node:crypto";
|
|
3980
3998
|
import { basename, dirname, join, resolve } from "node:path";
|
|
3981
3999
|
import { mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
|
|
4000
|
+
import { execFileSync } from "node:child_process";
|
|
3982
4001
|
import { basename as basename2, dirname as dirname2, isAbsolute, join as join2, relative, resolve as resolve2 } from "node:path";
|
|
3983
4002
|
import { existsSync as existsSync2, readFileSync as readFileSync3, readdirSync as readdirSync2 } from "node:fs";
|
|
3984
4003
|
import { join as join4, resolve as resolve4 } from "node:path";
|
|
@@ -3986,10 +4005,10 @@ import { mkdirSync as mkdirSync3, rmdirSync, statSync as statSync2, unlinkSync a
|
|
|
3986
4005
|
import { dirname as dirname3, isAbsolute as isAbsolute2, join as join3, resolve as resolve3 } from "node:path";
|
|
3987
4006
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
3988
4007
|
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
3989
|
-
import { execFileSync } from "node:child_process";
|
|
4008
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
3990
4009
|
import { existsSync as existsSync3 } from "node:fs";
|
|
3991
4010
|
import { isAbsolute as isAbsolute3, resolve as resolve5 } from "node:path";
|
|
3992
|
-
import { execFileSync as
|
|
4011
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
3993
4012
|
import { mkdirSync as mkdirSync4, readFileSync as readFileSync4, realpathSync, statSync as statSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
3994
4013
|
import { basename as basename3, dirname as dirname4, isAbsolute as isAbsolute4, join as join5, resolve as resolve6 } from "node:path";
|
|
3995
4014
|
import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync5 } from "node:fs";
|
|
@@ -4030,18 +4049,45 @@ function resolveHarnessDir(startDir = process.cwd(), opts = {}) {
|
|
|
4030
4049
|
const explicit = opts.harnessDir ?? process.env.MSTAR_HARNESS_DIR;
|
|
4031
4050
|
if (explicit)
|
|
4032
4051
|
return resolve2(start, explicit);
|
|
4052
|
+
const boundary = resolve2(start, opts.workspaceRoot ?? defaultWorkspaceRoot(start));
|
|
4033
4053
|
let dir = start;
|
|
4034
4054
|
for (;; ) {
|
|
4055
|
+
if (!isAtOrBelow(dir, boundary))
|
|
4056
|
+
return null;
|
|
4035
4057
|
for (const candidate of [join2(dir, ".mstar"), join2(dir, ".agents"), join2(dir, ".plans"), join2(dir, "plans")]) {
|
|
4036
4058
|
if (isDirectory(candidate))
|
|
4037
4059
|
return candidate;
|
|
4038
4060
|
}
|
|
4061
|
+
if (dir === boundary)
|
|
4062
|
+
return null;
|
|
4039
4063
|
const parent = dirname2(dir);
|
|
4040
4064
|
if (parent === dir)
|
|
4041
4065
|
return null;
|
|
4042
4066
|
dir = parent;
|
|
4043
4067
|
}
|
|
4044
4068
|
}
|
|
4069
|
+
function defaultWorkspaceRoot(startDir) {
|
|
4070
|
+
try {
|
|
4071
|
+
const cdup = execFileSync("git", ["rev-parse", "--show-cdup"], {
|
|
4072
|
+
cwd: startDir,
|
|
4073
|
+
encoding: "utf8",
|
|
4074
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
4075
|
+
}).trim();
|
|
4076
|
+
if (!cdup)
|
|
4077
|
+
return startDir;
|
|
4078
|
+
let boundary = startDir;
|
|
4079
|
+
for (const segment of cdup.split(/[\\/]/)) {
|
|
4080
|
+
if (segment && segment !== ".")
|
|
4081
|
+
boundary = dirname2(boundary);
|
|
4082
|
+
}
|
|
4083
|
+
return resolve2(boundary);
|
|
4084
|
+
} catch {}
|
|
4085
|
+
return startDir;
|
|
4086
|
+
}
|
|
4087
|
+
function isAtOrBelow(dir, root) {
|
|
4088
|
+
const rel = relative(root, dir);
|
|
4089
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
4090
|
+
}
|
|
4045
4091
|
function resolveSpecsDir(harnessDir, opts = {}) {
|
|
4046
4092
|
const harness = resolve2(harnessDir);
|
|
4047
4093
|
const repoRoot = dirname2(harness);
|
|
@@ -4063,7 +4109,7 @@ function resolveSpecsDir(harnessDir, opts = {}) {
|
|
|
4063
4109
|
}
|
|
4064
4110
|
function assertSafePathComponent(value, what) {
|
|
4065
4111
|
if (value === "" || value === "." || value === ".." || !/^[A-Za-z0-9._-]+$/.test(value)) {
|
|
4066
|
-
throw new Error(`${what} must be a single safe path component ([A-Za-z0-9._-]+; not "", ".", "..", or containing "/" or "\\")
|
|
4112
|
+
throw new Error(`${what} must be a single safe path component ([A-Za-z0-9._-]+; not "", ".", "..", or containing "/" or "\\") \u2014 got ${JSON.stringify(value)}`);
|
|
4067
4113
|
}
|
|
4068
4114
|
}
|
|
4069
4115
|
function resolveSddDir(harnessDir, planId) {
|
|
@@ -4080,7 +4126,7 @@ var GITIGNORE_SNIPPET = `# Morning Star harness (.mstar/)
|
|
|
4080
4126
|
!.mstar/specs/
|
|
4081
4127
|
!.mstar/specs/**
|
|
4082
4128
|
`;
|
|
4083
|
-
var GITIGNORE_SNIPPET_AGENTS = `# Morning Star harness (.agents/)
|
|
4129
|
+
var GITIGNORE_SNIPPET_AGENTS = `# Morning Star harness (.agents/) \u2014 legacy
|
|
4084
4130
|
# Default-ignore everything under .agents/, then re-include the tracked results.
|
|
4085
4131
|
.agents/**
|
|
4086
4132
|
!.agents/AGENTS.md
|
|
@@ -4125,7 +4171,7 @@ async function withStatusWriteLock(statusPath, fn, opts = {}) {
|
|
|
4125
4171
|
const lockDir = join3(dirname3(resolve3(statusPath)), STATUS_WRITE_LOCKDIR);
|
|
4126
4172
|
const held = heldLockDirs.getStore();
|
|
4127
4173
|
if (held !== undefined && held.has(lockDir)) {
|
|
4128
|
-
throw new Error(`${lockDir} is already held by this process in this async context
|
|
4174
|
+
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`);
|
|
4129
4175
|
}
|
|
4130
4176
|
const timeoutMs = opts.timeoutMs ?? 30000;
|
|
4131
4177
|
const pollMs = opts.pollMs ?? 25;
|
|
@@ -4141,7 +4187,7 @@ async function withStatusWriteLock(statusPath, fn, opts = {}) {
|
|
|
4141
4187
|
if (error.code !== "EEXIST")
|
|
4142
4188
|
throw error;
|
|
4143
4189
|
if (Date.now() >= deadline) {
|
|
4144
|
-
throw new Error(`${lockDir} already exists
|
|
4190
|
+
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)`);
|
|
4145
4191
|
}
|
|
4146
4192
|
await sleep(pollMs);
|
|
4147
4193
|
}
|
|
@@ -4166,7 +4212,7 @@ async function withStatusWriteLock(statusPath, fn, opts = {}) {
|
|
|
4166
4212
|
} catch {}
|
|
4167
4213
|
}
|
|
4168
4214
|
}
|
|
4169
|
-
var BRANCH_FORMS_HINT = '"Working branch: <existing>" | "Working branch: create <new> from <base>" | "Branch policy: direct on <branch>
|
|
4215
|
+
var BRANCH_FORMS_HINT = '"Working branch: <existing>" | "Working branch: create <new> from <base>" | "Branch policy: direct on <branch> \u2014 <reason>"';
|
|
4170
4216
|
var REQUIRED_FIELDS = [
|
|
4171
4217
|
{ key: "executeAs", label: "Execute as", code: "execute-as" },
|
|
4172
4218
|
{ key: "delegation", label: "Delegation", code: "delegation" },
|
|
@@ -4233,7 +4279,7 @@ function parseAssignmentBranchForms(assignmentText) {
|
|
|
4233
4279
|
if (fields.branchPolicy !== undefined && fields.branchPolicy !== "") {
|
|
4234
4280
|
const direct = fields.branchPolicy.match(/^direct\s+on\s+(\S+)/i);
|
|
4235
4281
|
if (direct) {
|
|
4236
|
-
const strict = fields.branchPolicy.match(/^direct\s+on\s+(\S+)(?:\s*(?:[
|
|
4282
|
+
const strict = fields.branchPolicy.match(/^direct\s+on\s+(\S+)(?:\s*(?:[\u2014\u2013]|--|-)\s*(.+))?$/);
|
|
4237
4283
|
forms.directOn = { branch: direct[1].trim(), reason: strict ? (strict[2] ?? "").trim() : "" };
|
|
4238
4284
|
}
|
|
4239
4285
|
}
|
|
@@ -4262,7 +4308,7 @@ function validateAssignmentFields(assignmentText, opts = {}) {
|
|
|
4262
4308
|
if (formCount === 0) {
|
|
4263
4309
|
violations.push(violation2("high", "assignment.field.branch-missing", "writable assignment must contain exactly one branch form", `add exactly one of: ${BRANCH_FORMS_HINT}`));
|
|
4264
4310
|
} else if (formCount > 1) {
|
|
4265
|
-
violations.push(violation2("high", "assignment.field.branch-multiple", `writable assignment contains ${formCount} branch forms (Working branch + Branch policy)
|
|
4311
|
+
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}`));
|
|
4266
4312
|
} else if (workingPresent) {
|
|
4267
4313
|
const create = forms.createForm;
|
|
4268
4314
|
if (create !== undefined && (create.base === undefined || create.base.trim() === "" || create.name.trim() === "")) {
|
|
@@ -4271,9 +4317,9 @@ function validateAssignmentFields(assignmentText, opts = {}) {
|
|
|
4271
4317
|
} else if (policyPresent) {
|
|
4272
4318
|
const direct = forms.directOn;
|
|
4273
4319
|
if (direct === undefined) {
|
|
4274
|
-
violations.push(violation2("high", "assignment.field.branch-policy-missing-branch", `unparseable Branch policy: "${fields.branchPolicy}" (expected "direct on <branch>
|
|
4320
|
+
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>`"));
|
|
4275
4321
|
} else if (direct.reason === "") {
|
|
4276
|
-
violations.push(violation2("high", "assignment.field.branch-policy-missing-reason", `Branch policy "direct on ${direct.branch}" is missing the reason`, 'append "
|
|
4322
|
+
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'));
|
|
4277
4323
|
}
|
|
4278
4324
|
}
|
|
4279
4325
|
}
|
|
@@ -4284,7 +4330,7 @@ function assertDefaultBranchProtected(branch, opts = {}) {
|
|
|
4284
4330
|
const violations = [];
|
|
4285
4331
|
const normalized = branch.trim();
|
|
4286
4332
|
if (normalized !== "" && defaultBranches.includes(normalized) && opts.directOnException !== true) {
|
|
4287
|
-
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}
|
|
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} \u2014 <reason>" to the Assignment, or use a feature branch`));
|
|
4288
4334
|
}
|
|
4289
4335
|
return { ok: violations.length === 0, violations };
|
|
4290
4336
|
}
|
|
@@ -4301,9 +4347,9 @@ function executionModeToN(executionMode, opts = {}) {
|
|
|
4301
4347
|
} else if (mode === "targeted") {
|
|
4302
4348
|
const seats = [...new Set((opts.seats ?? []).map((s) => s.trim()).filter((s) => s !== ""))];
|
|
4303
4349
|
if (seats.length === 0) {
|
|
4304
|
-
violations.push(violation2("high", "dispatch.execution-mode.missing-seats", 'execution mode "targeted" requires listed reviewer seats', 'add "QC re-review: targeted
|
|
4350
|
+
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'));
|
|
4305
4351
|
} else if (seats.length > 3) {
|
|
4306
|
-
violations.push(violation2("high", "dispatch.execution-mode.too-many-seats", `execution mode "targeted" lists ${seats.length} reviewer seats
|
|
4352
|
+
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"));
|
|
4307
4353
|
} else {
|
|
4308
4354
|
n = seats.length;
|
|
4309
4355
|
}
|
|
@@ -4370,7 +4416,7 @@ function validatePlanRow(row) {
|
|
|
4370
4416
|
validateNonEmptyString2(violations, planId, "plan_id", "status.plan-row.missing-plan-id", "status.plan-row.invalid-plan-id");
|
|
4371
4417
|
}
|
|
4372
4418
|
if (id !== undefined && planId !== undefined && id !== planId) {
|
|
4373
|
-
violations.push(violation3("medium", "status.plan-row.dual-id", "row has both id and plan_id with different values
|
|
4419
|
+
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)"));
|
|
4374
4420
|
}
|
|
4375
4421
|
}
|
|
4376
4422
|
validateNonEmptyString2(violations, title, "title", "status.plan-row.missing-title", "status.plan-row.invalid-title");
|
|
@@ -4378,7 +4424,7 @@ function validatePlanRow(row) {
|
|
|
4378
4424
|
if (status === undefined) {
|
|
4379
4425
|
violations.push(violation3("high", "status.plan-row.missing-status", "missing required field: status"));
|
|
4380
4426
|
} else if (typeof status !== "string" || !PLAN_STATUSES.includes(status)) {
|
|
4381
|
-
violations.push(violation3("medium", "status.plan-row.invalid-status", `status must be one of ${PLAN_STATUSES.join(" | ")}
|
|
4427
|
+
violations.push(violation3("medium", "status.plan-row.invalid-status", `status must be one of ${PLAN_STATUSES.join(" | ")} \u2014 got ${JSON.stringify(status)}`));
|
|
4382
4428
|
}
|
|
4383
4429
|
if (metadata !== undefined && !isPlainObject2(metadata)) {
|
|
4384
4430
|
violations.push(violation3("medium", "status.plan-row.invalid-metadata", "metadata must be an object"));
|
|
@@ -4387,7 +4433,7 @@ function validatePlanRow(row) {
|
|
|
4387
4433
|
violations.push(violation3("medium", "status.plan-row.invalid-execution-lease", "execution_lease must be an object"));
|
|
4388
4434
|
}
|
|
4389
4435
|
if (status === "Done" && execution_lease !== undefined) {
|
|
4390
|
-
violations.push(violation3("medium", "status.plan-row.done-with-lease", 'plan status Done must not carry an execution_lease
|
|
4436
|
+
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"'));
|
|
4391
4437
|
}
|
|
4392
4438
|
return { ok: violations.length === 0, violations };
|
|
4393
4439
|
}
|
|
@@ -4405,14 +4451,14 @@ function validateResidual(entry) {
|
|
|
4405
4451
|
if (severity === undefined) {
|
|
4406
4452
|
violations.push(violation3("high", "status.residual.missing-severity", "missing required field: severity"));
|
|
4407
4453
|
} else if (typeof severity !== "string" || !SEVERITY_ORDER.includes(severity) && severity !== "warning") {
|
|
4408
|
-
violations.push(violation3("medium", "status.residual.invalid-severity", `severity must be one of ${SEVERITY_ORDER.join(" | ")}
|
|
4454
|
+
violations.push(violation3("medium", "status.residual.invalid-severity", `severity must be one of ${SEVERITY_ORDER.join(" | ")} \u2014 got ${JSON.stringify(severity)}`));
|
|
4409
4455
|
} else if (severity === "warning") {
|
|
4410
|
-
violations.push(violation3("low", "status.residual.legacy-warning", `severity "warning" is legacy
|
|
4456
|
+
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')`));
|
|
4411
4457
|
}
|
|
4412
4458
|
if (decision === undefined) {
|
|
4413
4459
|
violations.push(violation3("high", "status.residual.missing-decision", "missing required field: decision"));
|
|
4414
4460
|
} else if (typeof decision !== "string" || !RESIDUAL_DECISIONS.includes(decision)) {
|
|
4415
|
-
violations.push(violation3("medium", "status.residual.invalid-decision", `decision must be one of ${RESIDUAL_DECISIONS.join(" | ")}
|
|
4461
|
+
violations.push(violation3("medium", "status.residual.invalid-decision", `decision must be one of ${RESIDUAL_DECISIONS.join(" | ")} \u2014 got ${JSON.stringify(decision)}`));
|
|
4416
4462
|
}
|
|
4417
4463
|
if (target === undefined) {
|
|
4418
4464
|
violations.push(violation3("high", "status.residual.missing-target", "missing required field: target"));
|
|
@@ -4432,7 +4478,7 @@ function validateResidual(entry) {
|
|
|
4432
4478
|
}
|
|
4433
4479
|
if (lifecycle !== undefined) {
|
|
4434
4480
|
if (typeof lifecycle !== "string" || !RESIDUAL_LIFECYCLES.includes(lifecycle)) {
|
|
4435
|
-
violations.push(violation3("medium", "status.residual.invalid-lifecycle", `lifecycle must be one of ${RESIDUAL_LIFECYCLES.join(" | ")}
|
|
4481
|
+
violations.push(violation3("medium", "status.residual.invalid-lifecycle", `lifecycle must be one of ${RESIDUAL_LIFECYCLES.join(" | ")} \u2014 got ${JSON.stringify(lifecycle)}`));
|
|
4436
4482
|
} else if (lifecycle !== "open") {
|
|
4437
4483
|
if (closed_at === undefined) {
|
|
4438
4484
|
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")'));
|
|
@@ -4465,7 +4511,7 @@ function validateStatus(docOrPath) {
|
|
|
4465
4511
|
} else if (typeof version !== "number" || !Number.isInteger(version)) {
|
|
4466
4512
|
violations.push(violation3("high", "status.invalid-version", "version must be an integer"));
|
|
4467
4513
|
} else if (version !== 1) {
|
|
4468
|
-
violations.push(violation3("medium", "status.unsupported-version", `unsupported status.json schema version ${version}
|
|
4514
|
+
violations.push(violation3("medium", "status.unsupported-version", `unsupported status.json schema version ${version} \u2014 expected 1`));
|
|
4469
4515
|
}
|
|
4470
4516
|
if (updated_at === undefined) {
|
|
4471
4517
|
violations.push(violation3("high", "status.missing-updated-at", "missing required field: updated_at"));
|
|
@@ -4490,7 +4536,7 @@ function validateStatus(docOrPath) {
|
|
|
4490
4536
|
if (!Array.isArray(list)) {
|
|
4491
4537
|
violations.push(violation3("high", "status.residual.invalid-list", `residual_findings["${planId}"] must be an array`));
|
|
4492
4538
|
} else if (list.length === 0) {
|
|
4493
|
-
violations.push(violation3("low", "status.residual.empty-key", `residual_findings["${planId}"] is empty
|
|
4539
|
+
violations.push(violation3("low", "status.residual.empty-key", `residual_findings["${planId}"] is empty \u2014 delete the key (no "plan-id": [])`));
|
|
4494
4540
|
} else {
|
|
4495
4541
|
for (const entry of list) {
|
|
4496
4542
|
violations.push(...validateResidual(entry).violations);
|
|
@@ -4503,14 +4549,14 @@ function validateStatus(docOrPath) {
|
|
|
4503
4549
|
} else if (!isPlainObject2(metadata)) {
|
|
4504
4550
|
violations.push(violation3("high", "status.invalid-metadata", "metadata must be an object"));
|
|
4505
4551
|
} else if (Object.prototype.hasOwnProperty.call(metadata, "residual_findings")) {
|
|
4506
|
-
violations.push(violation3("medium", "status.dual-write-residuals", "residual_findings must be root-only
|
|
4552
|
+
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"));
|
|
4507
4553
|
}
|
|
4508
4554
|
return { ok: violations.length === 0, violations };
|
|
4509
4555
|
}
|
|
4510
4556
|
async function archiveResiduals(planId, harnessDir) {
|
|
4511
4557
|
const dir = harnessDir !== undefined ? resolve4(harnessDir) : resolveHarnessDir();
|
|
4512
4558
|
if (dir === null) {
|
|
4513
|
-
throw new Error(`harness dir not found from ${process.cwd()}
|
|
4559
|
+
throw new Error(`harness dir not found from ${process.cwd()} \u2014 pass harnessDir or set MSTAR_HARNESS_DIR`);
|
|
4514
4560
|
}
|
|
4515
4561
|
assertSafePathComponent(planId, "planId");
|
|
4516
4562
|
const statusPath = join4(dir, "status.json");
|
|
@@ -4565,7 +4611,7 @@ function probeBranch(worktreePath, opts) {
|
|
|
4565
4611
|
return { branch: precomputed };
|
|
4566
4612
|
const timeout = opts.timeoutMs ?? probeTimeoutMs();
|
|
4567
4613
|
try {
|
|
4568
|
-
const stdout =
|
|
4614
|
+
const stdout = execFileSync2(opts.gitPath ?? "git", ["-C", worktreePath, "branch", "--show-current"], {
|
|
4569
4615
|
encoding: "utf8",
|
|
4570
4616
|
stdio: ["ignore", "pipe", "pipe"],
|
|
4571
4617
|
timeout
|
|
@@ -4587,16 +4633,16 @@ function l1PreDispatchCheck(input, opts = {}) {
|
|
|
4587
4633
|
const violations = [];
|
|
4588
4634
|
const { controlWorktreePath, leaseWorktreePath, leaseWorkingBranch, planId } = input;
|
|
4589
4635
|
if (controlWorktreePath.trim() === "") {
|
|
4590
|
-
violations.push(violation4("high", "worktree.l1.control-missing", "metadata.control_worktree_path is not recorded
|
|
4636
|
+
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"));
|
|
4591
4637
|
}
|
|
4592
4638
|
if (leaseWorktreePath.trim() === "") {
|
|
4593
|
-
violations.push(violation4("high", "worktree.l1.lease-missing", `execution_lease.worktree_path is empty for plan "${planId}"
|
|
4639
|
+
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"));
|
|
4594
4640
|
}
|
|
4595
4641
|
if (leaseWorkingBranch.trim() === "") {
|
|
4596
4642
|
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"));
|
|
4597
4643
|
}
|
|
4598
4644
|
if (controlWorktreePath !== "" && leaseWorktreePath !== "" && resolve5(controlWorktreePath) === resolve5(leaseWorktreePath)) {
|
|
4599
|
-
violations.push(violation4("critical", "worktree.l1.lease-equals-control", `execution_lease.worktree_path "${leaseWorktreePath}" equals metadata.control_worktree_path
|
|
4645
|
+
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"));
|
|
4600
4646
|
}
|
|
4601
4647
|
if (leaseWorktreePath !== "" && !existsSync3(leaseWorktreePath)) {
|
|
4602
4648
|
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>`));
|
|
@@ -4615,7 +4661,7 @@ function l2PreDispatchCheck(input, opts = {}) {
|
|
|
4615
4661
|
const tracks = input.tracks ?? [];
|
|
4616
4662
|
const seenPaths = new Set;
|
|
4617
4663
|
if (tracks.length < 1) {
|
|
4618
|
-
violations.push(violation4("high", "worktree.l2.no-tracks", "no parallel writable tracks
|
|
4664
|
+
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"));
|
|
4619
4665
|
}
|
|
4620
4666
|
tracks.forEach((track, index) => {
|
|
4621
4667
|
if (track.worktreePath.trim() === "" || track.workingBranch.trim() === "") {
|
|
@@ -4623,12 +4669,12 @@ function l2PreDispatchCheck(input, opts = {}) {
|
|
|
4623
4669
|
return;
|
|
4624
4670
|
}
|
|
4625
4671
|
if (!isAbsolute3(track.worktreePath)) {
|
|
4626
|
-
violations.push(violation4("high", "worktree.l2.track-path-relative", `track ${index + 1} worktreePath "${track.worktreePath}" is not an absolute path
|
|
4672
|
+
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>)`));
|
|
4627
4673
|
return;
|
|
4628
4674
|
}
|
|
4629
4675
|
const normalized = resolve5(track.worktreePath);
|
|
4630
4676
|
if (seenPaths.has(normalized)) {
|
|
4631
|
-
violations.push(violation4("high", "worktree.l2.track-path-collision", `duplicate worktreePath "${track.worktreePath}" across parallel tracks
|
|
4677
|
+
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"));
|
|
4632
4678
|
return;
|
|
4633
4679
|
}
|
|
4634
4680
|
seenPaths.add(normalized);
|
|
@@ -4670,7 +4716,7 @@ function isFile(file) {
|
|
|
4670
4716
|
var GIT_CAPTURE_MAX_BYTES = 64 * 1024 * 1024;
|
|
4671
4717
|
function gitOut(cwd, args) {
|
|
4672
4718
|
try {
|
|
4673
|
-
return
|
|
4719
|
+
return execFileSync3("git", args, {
|
|
4674
4720
|
cwd,
|
|
4675
4721
|
encoding: "utf8",
|
|
4676
4722
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -4725,7 +4771,7 @@ function sddWorkspace(planId, opts = {}) {
|
|
|
4725
4771
|
throw new SddScriptError(`mstar sdd workspace: linked worktree at ${root} has no {HARNESS_DIR}/status.json (default gitignore).
|
|
4726
4772
|
` + ` Refusing to create a second SDD tree under the feature checkout.
|
|
4727
4773
|
` + ` Re-run with MSTAR_CONTROL_ROOT=<control_worktree_path> or: mstar sdd workspace ${planId} <control_worktree_path>
|
|
4728
|
-
` + ` See mstar-branch-worktree
|
|
4774
|
+
` + ` See mstar-branch-worktree \u00abHarness path SSOT under default gitignore\u00bb.`, 1);
|
|
4729
4775
|
}
|
|
4730
4776
|
const harnessOverride = opts.harnessDir ?? (process.env.MSTAR_HARNESS_DIR || undefined);
|
|
4731
4777
|
let harnessDir;
|
|
@@ -4803,7 +4849,7 @@ function reviewPackage(base, head, outFile, opts = {}) {
|
|
|
4803
4849
|
const cwd = opts.cwd ?? process.cwd();
|
|
4804
4850
|
const verifyRef = (ref, what) => {
|
|
4805
4851
|
try {
|
|
4806
|
-
|
|
4852
|
+
execFileSync3("git", ["rev-parse", "--verify", "--quiet", ref], { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
4807
4853
|
} catch {
|
|
4808
4854
|
throw new SddScriptError(`bad ${what}: ${ref}`, 2);
|
|
4809
4855
|
}
|
|
@@ -4823,7 +4869,7 @@ function reviewPackage(base, head, outFile, opts = {}) {
|
|
|
4823
4869
|
const shortHead = gitOut(cwd, ["rev-parse", "--short", head]) ?? head;
|
|
4824
4870
|
out = join5(sddDir, `review-${shortBase}..${shortHead}.diff`);
|
|
4825
4871
|
}
|
|
4826
|
-
const run = (args) =>
|
|
4872
|
+
const run = (args) => execFileSync3("git", args, { cwd, maxBuffer: GIT_CAPTURE_MAX_BYTES });
|
|
4827
4873
|
const parts = [
|
|
4828
4874
|
Buffer.from(`# Review package: ${base}..${head}
|
|
4829
4875
|
|
|
@@ -4927,7 +4973,7 @@ function validateCompassFrontmatter(doc) {
|
|
|
4927
4973
|
return {
|
|
4928
4974
|
ok: false,
|
|
4929
4975
|
violations: [
|
|
4930
|
-
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
|
|
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 \u00a71.3)", "Fix the frontmatter of {ITERATION_DIR}/<iteration-id>/delivery-compass.md")
|
|
4931
4977
|
]
|
|
4932
4978
|
};
|
|
4933
4979
|
}
|
|
@@ -4937,17 +4983,17 @@ function validateCompassFrontmatter(doc) {
|
|
|
4937
4983
|
ok: false,
|
|
4938
4984
|
violations: parsed.issues.map((issue) => {
|
|
4939
4985
|
const field = issue.path.join(".") || "(root)";
|
|
4940
|
-
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
|
|
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 \u00a71.3)`);
|
|
4941
4987
|
})
|
|
4942
4988
|
};
|
|
4943
4989
|
}
|
|
4944
4990
|
const violations = [];
|
|
4945
4991
|
const { status, end_date } = parsed.data;
|
|
4946
4992
|
if (status === "completed" && end_date === undefined) {
|
|
4947
|
-
violations.push(violation5("high", "COMPASS_END_DATE_REQUIRED", "Compass frontmatter status is 'completed' but end_date is missing
|
|
4993
|
+
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"));
|
|
4948
4994
|
}
|
|
4949
4995
|
if (status !== "completed" && end_date !== undefined) {
|
|
4950
|
-
violations.push(violation5("medium", "COMPASS_END_DATE_NOT_ALLOWED", `Compass frontmatter sets end_date while status is '${status}'
|
|
4996
|
+
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"));
|
|
4951
4997
|
}
|
|
4952
4998
|
return { ok: violations.length === 0, violations };
|
|
4953
4999
|
}
|
|
@@ -4971,17 +5017,17 @@ function findPlanRow(statusDoc, planId) {
|
|
|
4971
5017
|
function entryPlansAllDone(statusDoc, registered) {
|
|
4972
5018
|
const violations = [];
|
|
4973
5019
|
if (registered.length === 0) {
|
|
4974
|
-
violations.push(violation5("medium", "COMPASS_NO_PLANS", "Compass frontmatter registers no plans
|
|
5020
|
+
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`"));
|
|
4975
5021
|
return violations;
|
|
4976
5022
|
}
|
|
4977
5023
|
for (const planId of registered) {
|
|
4978
5024
|
const row = findPlanRow(statusDoc, planId);
|
|
4979
5025
|
if (row === null) {
|
|
4980
|
-
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
|
|
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 \u00a73.1 entry item 1)`, "Add the plan row to {HARNESS_DIR}/status.json"));
|
|
4981
5027
|
continue;
|
|
4982
5028
|
}
|
|
4983
5029
|
if (row.status !== PLAN_STATUS_DONE) {
|
|
4984
|
-
violations.push(violation5("high", "PLAN_NOT_DONE", `Plan '${planId}' status is ${JSON.stringify(row.status)} in status.json
|
|
5030
|
+
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)`));
|
|
4985
5031
|
}
|
|
4986
5032
|
}
|
|
4987
5033
|
return violations;
|
|
@@ -4992,14 +5038,14 @@ function entryResidualsOpen(statusDoc, planId) {
|
|
|
4992
5038
|
if (residualRoot === undefined || residualRoot === null)
|
|
4993
5039
|
return violations;
|
|
4994
5040
|
if (!isPlainObject3(residualRoot)) {
|
|
4995
|
-
violations.push(violation5("medium", "RESIDUAL_MALFORMED", "status.json residual_findings must be a plan-id
|
|
5041
|
+
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)"));
|
|
4996
5042
|
return violations;
|
|
4997
5043
|
}
|
|
4998
5044
|
const entries = residualRoot[planId];
|
|
4999
5045
|
if (entries === undefined)
|
|
5000
5046
|
return violations;
|
|
5001
5047
|
if (!Array.isArray(entries)) {
|
|
5002
|
-
violations.push(violation5("medium", "RESIDUAL_MALFORMED", `status.json residual_findings['${planId}'] must be an array of residual entries (mstar-iteration
|
|
5048
|
+
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)`));
|
|
5003
5049
|
return violations;
|
|
5004
5050
|
}
|
|
5005
5051
|
const openIds = [];
|
|
@@ -5012,7 +5058,7 @@ function entryResidualsOpen(statusDoc, planId) {
|
|
|
5012
5058
|
openIds.push(typeof entry.id === "string" ? entry.id : "<unnamed>");
|
|
5013
5059
|
}
|
|
5014
5060
|
if (openIds.length > 0) {
|
|
5015
|
-
violations.push(violation5("high", "OPEN_RESIDUALS", `Plan '${planId}' has ${openIds.length} open residual finding(s) not exempted as blocker-defers (${openIds.join(", ")})
|
|
5061
|
+
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"));
|
|
5016
5062
|
}
|
|
5017
5063
|
return violations;
|
|
5018
5064
|
}
|
|
@@ -5022,11 +5068,11 @@ function entryFrontmatterComplete(compassDoc) {
|
|
|
5022
5068
|
function exitFrontmatterClosed(compassDoc) {
|
|
5023
5069
|
const violations = [];
|
|
5024
5070
|
if (compassDoc.status !== "completed") {
|
|
5025
|
-
violations.push(violation5("high", "EXIT_STATUS_NOT_COMPLETED", `Compass frontmatter status must be 'completed' at close exit
|
|
5071
|
+
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)`));
|
|
5026
5072
|
}
|
|
5027
5073
|
const endDate = compassDoc.end_date;
|
|
5028
5074
|
if (typeof endDate !== "string" || !DATE_RE2.test(endDate)) {
|
|
5029
|
-
violations.push(violation5("high", "EXIT_END_DATE_REQUIRED", "Compass frontmatter end_date (YYYY-MM-DD) is required when closing (mstar-iteration
|
|
5075
|
+
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)"));
|
|
5030
5076
|
}
|
|
5031
5077
|
return violations;
|
|
5032
5078
|
}
|
|
@@ -5034,9 +5080,9 @@ function exitBranchCheck(opts) {
|
|
|
5034
5080
|
const violations = [];
|
|
5035
5081
|
const { currentBranch, specIntegrationBranch } = opts;
|
|
5036
5082
|
if (currentBranch === undefined || specIntegrationBranch === undefined) {
|
|
5037
|
-
violations.push(violation5("medium", "EXIT_BRANCH_UNVERIFIABLE", "Cannot verify the current branch is spec_integration_branch
|
|
5083
|
+
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)"));
|
|
5038
5084
|
} else if (currentBranch !== specIntegrationBranch) {
|
|
5039
|
-
violations.push(violation5("high", "EXIT_BRANCH_MISMATCH", `Current branch '${currentBranch}' is not the spec_integration_branch '${specIntegrationBranch}' (mstar-iteration
|
|
5085
|
+
violations.push(violation5("high", "EXIT_BRANCH_MISMATCH", `Current branch '${currentBranch}' is not the spec_integration_branch '${specIntegrationBranch}' (mstar-iteration \u00a73.5 exit item 5)`));
|
|
5040
5086
|
}
|
|
5041
5087
|
return violations;
|
|
5042
5088
|
}
|
|
@@ -5045,9 +5091,9 @@ function exitPrBaseCheck(compassDoc, opts) {
|
|
|
5045
5091
|
const target = compassDoc.target_branch;
|
|
5046
5092
|
const { prBaseBranch } = opts;
|
|
5047
5093
|
if (prBaseBranch === undefined) {
|
|
5048
|
-
violations.push(violation5("medium", "EXIT_PR_BASE_UNVERIFIABLE", "Cannot verify the PR base
|
|
5094
|
+
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)"));
|
|
5049
5095
|
} else if (typeof target !== "string" || prBaseBranch !== target) {
|
|
5050
|
-
violations.push(violation5("high", "EXIT_PR_BASE_MISMATCH", `PR base '${prBaseBranch}' must equal the compass target_branch '${String(target)}'
|
|
5096
|
+
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)`));
|
|
5051
5097
|
}
|
|
5052
5098
|
return violations;
|
|
5053
5099
|
}
|
|
@@ -5089,10 +5135,10 @@ function evaluatePhaseGate(statusDoc, compassDoc, opts = {}) {
|
|
|
5089
5135
|
function pushCadenceProbe(ciRunning, reviewWaveActive) {
|
|
5090
5136
|
const violations = [];
|
|
5091
5137
|
if (ciRunning) {
|
|
5092
|
-
violations.push(violation5("high", "PUSH_BLOCKED_CI", "CI checks are still queued/in_progress on the current head
|
|
5138
|
+
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"));
|
|
5093
5139
|
}
|
|
5094
5140
|
if (reviewWaveActive) {
|
|
5095
|
-
violations.push(violation5("high", "PUSH_BLOCKED_REVIEW_WAVE", "An AI/bot review wave is still running on the current head
|
|
5141
|
+
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"));
|
|
5096
5142
|
}
|
|
5097
5143
|
return { ok: violations.length === 0, violations };
|
|
5098
5144
|
}
|
|
@@ -5133,7 +5179,7 @@ function parseCompassFrontmatter(filePath) {
|
|
|
5133
5179
|
function parseFlowArray(raw, filePath) {
|
|
5134
5180
|
const inner = raw.slice(1, -1);
|
|
5135
5181
|
if (/[[\]]/.test(inner)) {
|
|
5136
|
-
throw new Error(`nested flow-style array in ${filePath}: ${JSON.stringify(raw)}
|
|
5182
|
+
throw new Error(`nested flow-style array in ${filePath}: ${JSON.stringify(raw)} \u2014 only flat scalar items are supported (e.g. [a, b])`);
|
|
5137
5183
|
}
|
|
5138
5184
|
let quote = null;
|
|
5139
5185
|
for (const ch of inner) {
|
|
@@ -5143,7 +5189,7 @@ function parseFlowArray(raw, filePath) {
|
|
|
5143
5189
|
else if (quote === ch)
|
|
5144
5190
|
quote = null;
|
|
5145
5191
|
} else if (ch === "," && quote !== null) {
|
|
5146
|
-
throw new Error(`ambiguous flow-style array in ${filePath}: ${JSON.stringify(raw)}
|
|
5192
|
+
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)`);
|
|
5147
5193
|
}
|
|
5148
5194
|
}
|
|
5149
5195
|
if (quote !== null) {
|
|
@@ -5271,7 +5317,7 @@ function validateDesignTokenFrontmatter(frontmatterText) {
|
|
|
5271
5317
|
const violations = [];
|
|
5272
5318
|
const fm = parseDesignFrontmatter(frontmatterText);
|
|
5273
5319
|
if (fm === null) {
|
|
5274
|
-
violations.push(violation6("medium", "design-md.tokens.missing-frontmatter", "no `---` YAML frontmatter block found
|
|
5320
|
+
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"));
|
|
5275
5321
|
return { ok: false, violations };
|
|
5276
5322
|
}
|
|
5277
5323
|
const groupEntries = (group) => {
|
|
@@ -5286,61 +5332,61 @@ function validateDesignTokenFrontmatter(frontmatterText) {
|
|
|
5286
5332
|
};
|
|
5287
5333
|
for (const group of ["colors", "typography", "spacing", "rounded"]) {
|
|
5288
5334
|
if (!isMap(fm[group])) {
|
|
5289
|
-
violations.push(violation6("medium", "design-md.tokens.group-not-map", `token group "${group}" must be a YAML map, not a scalar (design-md-spec
|
|
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 \u00a71.5)`, `rewrite \`${group}\` as a nested map`));
|
|
5290
5336
|
} else if (!groupIsMap(group)) {
|
|
5291
|
-
violations.push(violation6("medium", "design-md.tokens.group-not-map", `token group "${group}" must be a YAML map, not a scalar (design-md-spec
|
|
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 \u00a71.5)`, `rewrite \`${group}\` as a nested map`));
|
|
5292
5338
|
} else if (groupEntries(group).length === 0) {
|
|
5293
|
-
violations.push(violation6("medium", "design-md.tokens.missing-group", `missing required token group "${group}"
|
|
5339
|
+
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`));
|
|
5294
5340
|
}
|
|
5295
5341
|
}
|
|
5296
5342
|
if (!isMap(fm.components) || !groupIsMap("components")) {
|
|
5297
|
-
violations.push(violation6("medium", "design-md.tokens.group-not-map", `token group "components" must be a YAML map, not a scalar (design-md-spec
|
|
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 \u00a71.5)`, `rewrite \`components\` as a nested map`));
|
|
5298
5344
|
}
|
|
5299
|
-
const placeholder = (group, name, value) => violations.push(violation6("low", "design-md.tokens.placeholder", `token "${group}.${name}" uses a "[...]" template value
|
|
5345
|
+
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`));
|
|
5300
5346
|
for (const [name, value] of groupEntries("colors")) {
|
|
5301
5347
|
if (typeof value !== "string") {
|
|
5302
|
-
violations.push(violation6("medium", "design-md.tokens.color-format", `color "${name}" must be a string value (design-md-spec
|
|
5348
|
+
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"));
|
|
5303
5349
|
continue;
|
|
5304
5350
|
}
|
|
5305
5351
|
if (isPlaceholder(value)) {
|
|
5306
5352
|
placeholder("colors", name, value);
|
|
5307
5353
|
} else if (!HEX_RE.test(value) && !OKLCH_RE.test(value)) {
|
|
5308
|
-
violations.push(violation6("medium", "design-md.tokens.color-format", `color "${name}" = "${value}" is not a hex (\`#rrggbb\`/\`#rrggbbaa\`) or oklch() value (design-md-spec
|
|
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 \u00a72.2)`, "use an sRGB hex value, optionally with a `-p3` oklch() twin"));
|
|
5309
5355
|
}
|
|
5310
5356
|
}
|
|
5311
5357
|
for (const [name, value] of groupEntries("typography")) {
|
|
5312
5358
|
if (!isMap(value)) {
|
|
5313
|
-
violations.push(violation6("medium", "design-md.tokens.typography-shape", `typography token "${name}" must be a map of the five properties (design-md-spec
|
|
5359
|
+
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"));
|
|
5314
5360
|
continue;
|
|
5315
5361
|
}
|
|
5316
5362
|
const keys = Object.keys(value);
|
|
5317
5363
|
const missing = TYPOGRAPHY_PROPS.filter((p) => !keys.includes(p));
|
|
5318
5364
|
const extra = keys.filter((k) => !TYPOGRAPHY_PROPS.includes(k));
|
|
5319
5365
|
if (missing.length > 0 || extra.length > 0) {
|
|
5320
|
-
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
|
|
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 \u00a71.5)${missing.length > 0 ? ` \u2014 missing: ${missing.join(", ")}` : ""}${extra.length > 0 ? ` \u2014 extra: ${extra.join(", ")}` : ""}`, "align the token with the five-property shape"));
|
|
5321
5367
|
}
|
|
5322
5368
|
for (const prop of ["fontFamily", "fontSize"]) {
|
|
5323
5369
|
const v = value[prop];
|
|
5324
5370
|
if (typeof v === "string" && isPlaceholder(v))
|
|
5325
5371
|
placeholder("typography", name, v);
|
|
5326
5372
|
else if (typeof v !== "string" || v.trim() === "") {
|
|
5327
|
-
violations.push(violation6("medium", "design-md.tokens.typography-shape", `typography token "${name}" has an empty \`${prop}\` (design-md-spec
|
|
5373
|
+
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`));
|
|
5328
5374
|
}
|
|
5329
5375
|
}
|
|
5330
5376
|
}
|
|
5331
5377
|
if (groupIsMap("spacing")) {
|
|
5332
5378
|
const spacing = fm.spacing;
|
|
5333
5379
|
if (!Object.prototype.hasOwnProperty.call(spacing, "base")) {
|
|
5334
|
-
violations.push(violation6("medium", "design-md.tokens.spacing-base", "spacing must declare the base unit as `base` (design-md-spec
|
|
5380
|
+
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"));
|
|
5335
5381
|
}
|
|
5336
5382
|
for (const [name, value] of Object.entries(spacing)) {
|
|
5337
5383
|
if (name !== "base" && name !== RAW_GROUP && !/^\d+$/.test(name)) {
|
|
5338
|
-
violations.push(violation6("medium", "design-md.tokens.spacing-key", `spacing key "${name}" must be \`base\` or a numeric multiplier (design-md-spec
|
|
5384
|
+
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`"));
|
|
5339
5385
|
}
|
|
5340
5386
|
if (typeof value === "string" && isPlaceholder(value)) {
|
|
5341
5387
|
placeholder("spacing", name, value);
|
|
5342
5388
|
} else if (typeof value !== "string" || !PX_RE.test(value)) {
|
|
5343
|
-
violations.push(violation6("medium", "design-md.tokens.spacing-format", `spacing value "${name}" = "${String(value)}" is not a px length (design-md-spec
|
|
5389
|
+
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`"));
|
|
5344
5390
|
}
|
|
5345
5391
|
}
|
|
5346
5392
|
}
|
|
@@ -5348,12 +5394,12 @@ function validateDesignTokenFrontmatter(frontmatterText) {
|
|
|
5348
5394
|
if (typeof value === "string" && isPlaceholder(value)) {
|
|
5349
5395
|
placeholder("rounded", name, value);
|
|
5350
5396
|
} else if (typeof value !== "string" || !PX_RE.test(value)) {
|
|
5351
|
-
violations.push(violation6("medium", "design-md.tokens.rounded-format", `rounded value "${name}" = "${String(value)}" is not a px length (design-md-spec
|
|
5397
|
+
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`"));
|
|
5352
5398
|
}
|
|
5353
5399
|
}
|
|
5354
5400
|
for (const [name, value] of groupEntries("components")) {
|
|
5355
5401
|
if (!isMap(value)) {
|
|
5356
|
-
violations.push(violation6("medium", "design-md.tokens.components-shape", `component token "${name}" must be a map of properties (design-md-spec
|
|
5402
|
+
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"));
|
|
5357
5403
|
continue;
|
|
5358
5404
|
}
|
|
5359
5405
|
for (const [prop, v] of Object.entries(value)) {
|
|
@@ -5369,7 +5415,7 @@ function validateDesignTokenFrontmatter(frontmatterText) {
|
|
|
5369
5415
|
const [, refGroup, refKey] = ref;
|
|
5370
5416
|
const resolves = REF_GROUPS.includes(refGroup) && groupIsMap(refGroup) && Object.prototype.hasOwnProperty.call(fm[refGroup], refKey);
|
|
5371
5417
|
if (!resolves) {
|
|
5372
|
-
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
|
|
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 \u00a76 \u2014 {path} refs MUST trace back to a key)`, `add the referenced token or use a literal value`));
|
|
5373
5419
|
}
|
|
5374
5420
|
}
|
|
5375
5421
|
}
|
|
@@ -5381,7 +5427,7 @@ function assertLightDarkParity(lightFm, darkFm) {
|
|
|
5381
5427
|
const light = parseDesignFrontmatter(lightFm);
|
|
5382
5428
|
const dark = parseDesignFrontmatter(darkFm);
|
|
5383
5429
|
if (light === null || dark === null) {
|
|
5384
|
-
violations.push(violation6("medium", "design-md.parity.missing-frontmatter", `light/dark parity needs a YAML frontmatter in both files
|
|
5430
|
+
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"));
|
|
5385
5431
|
return { ok: false, violations };
|
|
5386
5432
|
}
|
|
5387
5433
|
const activeKeys = (fm) => {
|
|
@@ -5399,12 +5445,12 @@ function assertLightDarkParity(lightFm, darkFm) {
|
|
|
5399
5445
|
const darkKeys = activeKeys(dark);
|
|
5400
5446
|
for (const key of lightKeys) {
|
|
5401
5447
|
if (!darkKeys.has(key)) {
|
|
5402
|
-
violations.push(violation6("medium", "design-md.parity.missing-dark", `token "${key}" is active in DESIGN.md but missing from DESIGN.dark.md
|
|
5448
|
+
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"));
|
|
5403
5449
|
}
|
|
5404
5450
|
}
|
|
5405
5451
|
for (const key of darkKeys) {
|
|
5406
5452
|
if (!lightKeys.has(key)) {
|
|
5407
|
-
violations.push(violation6("medium", "design-md.parity.missing-light", `token "${key}" is active in DESIGN.dark.md but missing from DESIGN.md
|
|
5453
|
+
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"));
|
|
5408
5454
|
}
|
|
5409
5455
|
}
|
|
5410
5456
|
return { ok: violations.length === 0, violations };
|
|
@@ -5597,7 +5643,7 @@ function slugify(title) {
|
|
|
5597
5643
|
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
5598
5644
|
}
|
|
5599
5645
|
var escapeCell = (value) => value.replace(/\|/g, "\\|");
|
|
5600
|
-
var truncate = (value, max) => value.length > max ? `${value.slice(0, max)}
|
|
5646
|
+
var truncate = (value, max) => value.length > max ? `${value.slice(0, max)}\u2026` : value;
|
|
5601
5647
|
function renderPlanFile(finding, plannedAt) {
|
|
5602
5648
|
const sections = [
|
|
5603
5649
|
`# ${finding.title}`,
|
|
@@ -5636,14 +5682,14 @@ function renderIndex(params) {
|
|
|
5636
5682
|
const { date, repoName, repoShortSha, rows, rejected } = params;
|
|
5637
5683
|
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(`
|
|
5638
5684
|
`);
|
|
5639
|
-
const directionRows = rows.filter((r) => r.category === "direction").map((r) => `- ${escapeCell(r.title)}
|
|
5685
|
+
const directionRows = rows.filter((r) => r.category === "direction").map((r) => `- ${escapeCell(r.title)} \u2014 ${escapeCell(truncate(r.impact, 120))}`).join(`
|
|
5640
5686
|
`);
|
|
5641
5687
|
const executionRows = rows.map((r) => `| ${r.num} | ${escapeCell(r.title)} | ${r.priority} | ${r.effort} | ${r.dependsOn} | TODO |`).join(`
|
|
5642
5688
|
`);
|
|
5643
5689
|
const rejectedRows = rejected.map((r) => `- ${escapeCell(r.title)}: ${escapeCell(r.reason)}`).join(`
|
|
5644
5690
|
`);
|
|
5645
5691
|
const sections = [
|
|
5646
|
-
`# Audit Report
|
|
5692
|
+
`# Audit Report \u2014 ${repoName} @ ${repoShortSha} (${date})`,
|
|
5647
5693
|
"",
|
|
5648
5694
|
"## Findings",
|
|
5649
5695
|
"",
|
|
@@ -5658,6 +5704,7 @@ function renderIndex(params) {
|
|
|
5658
5704
|
if (rejectedRows !== "") {
|
|
5659
5705
|
sections.push("", "## Findings considered and rejected", "", rejectedRows);
|
|
5660
5706
|
}
|
|
5707
|
+
sections.push("", "## Red-team dispositions", "", "- <finding>: <survived / refuted / hallucination-dropped / uncovered-kept>, <one-line reason>");
|
|
5661
5708
|
return `${sections.join(`
|
|
5662
5709
|
`)}
|
|
5663
5710
|
`;
|
|
@@ -5692,14 +5739,14 @@ function scaffoldAuditPlan(outDir, findings, options = {}) {
|
|
|
5692
5739
|
return {
|
|
5693
5740
|
num: file.slice(0, 3),
|
|
5694
5741
|
title: summary.title,
|
|
5695
|
-
category: fields.get("Category") ?? "
|
|
5742
|
+
category: fields.get("Category") ?? "\u2014",
|
|
5696
5743
|
impact: "see plan file",
|
|
5697
|
-
effort: fields.get("Effort") ?? "
|
|
5698
|
-
risk: fields.get("Risk") ?? "
|
|
5699
|
-
confidence: "
|
|
5700
|
-
evidence: fields.get("Evidence") ?? "
|
|
5701
|
-
priority: fields.get("Priority") ?? "
|
|
5702
|
-
dependsOn: fields.get("Depends on") ?? "
|
|
5744
|
+
effort: fields.get("Effort") ?? "\u2014",
|
|
5745
|
+
risk: fields.get("Risk") ?? "\u2014",
|
|
5746
|
+
confidence: "\u2014",
|
|
5747
|
+
evidence: fields.get("Evidence") ?? "\u2014",
|
|
5748
|
+
priority: fields.get("Priority") ?? "\u2014",
|
|
5749
|
+
dependsOn: fields.get("Depends on") ?? "\u2014"
|
|
5703
5750
|
};
|
|
5704
5751
|
});
|
|
5705
5752
|
const byNum = new Map(rows.map((r) => [r.num, r]));
|
|
@@ -5893,7 +5940,7 @@ function validateSchemaYaml(frontmatterText) {
|
|
|
5893
5940
|
const violations = [];
|
|
5894
5941
|
const doc = parseYamlLite(frontmatterText);
|
|
5895
5942
|
if (doc === null) {
|
|
5896
|
-
violations.push(violation8("medium", "compound.schema.missing-frontmatter", "no `---` YAML frontmatter block found
|
|
5943
|
+
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"));
|
|
5897
5944
|
return { ok: false, violations };
|
|
5898
5945
|
}
|
|
5899
5946
|
const isStr = (v) => typeof v === "string";
|
|
@@ -5907,14 +5954,14 @@ function validateSchemaYaml(frontmatterText) {
|
|
|
5907
5954
|
}
|
|
5908
5955
|
const problemType = doc.problem_type;
|
|
5909
5956
|
if (problemType !== undefined && !isStr(problemType)) {
|
|
5910
|
-
violations.push(violation8("medium", "compound.schema.invalid-problem-type", `problem_type "${String(problemType)}" must be a string
|
|
5957
|
+
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"));
|
|
5911
5958
|
}
|
|
5912
5959
|
const problemTypeValid = isStr(problemType) && KNOWLEDGE_PROBLEM_TYPES.includes(problemType);
|
|
5913
5960
|
if (isStr(problemType) && !problemTypeValid) {
|
|
5914
|
-
violations.push(violation8("medium", "compound.schema.invalid-problem-type", `problem_type "${problemType}" is not one of the schema.yaml enum values (bug: build_error
|
|
5961
|
+
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"));
|
|
5915
5962
|
}
|
|
5916
5963
|
if (doc.severity !== undefined && !isStr(doc.severity)) {
|
|
5917
|
-
violations.push(violation8("medium", "compound.schema.invalid-severity", `severity "${String(doc.severity)}" must be a string
|
|
5964
|
+
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"));
|
|
5918
5965
|
}
|
|
5919
5966
|
if (isStr(doc.severity) && !KNOWLEDGE_SEVERITIES.includes(doc.severity)) {
|
|
5920
5967
|
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"));
|
|
@@ -5922,7 +5969,7 @@ function validateSchemaYaml(frontmatterText) {
|
|
|
5922
5969
|
if (problemTypeValid && isStr(doc.category)) {
|
|
5923
5970
|
const expected = KNOWLEDGE_CATEGORY_MAP[problemType];
|
|
5924
5971
|
if (doc.category !== expected) {
|
|
5925
|
-
violations.push(violation8("medium", "compound.schema.category-mismatch", `category "${doc.category}" does not match problem_type "${problemType}"
|
|
5972
|
+
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})`));
|
|
5926
5973
|
}
|
|
5927
5974
|
}
|
|
5928
5975
|
if (problemTypeValid) {
|
|
@@ -5953,7 +6000,7 @@ function validateSchemaYaml(frontmatterText) {
|
|
|
5953
6000
|
if (!Array.isArray(doc.tags)) {
|
|
5954
6001
|
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"));
|
|
5955
6002
|
} else if (doc.tags.length > 8) {
|
|
5956
|
-
violations.push(violation8("low", "compound.schema.tags-too-many", `tags has ${doc.tags.length} entries
|
|
6003
|
+
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"));
|
|
5957
6004
|
}
|
|
5958
6005
|
}
|
|
5959
6006
|
if (doc.last_updated !== undefined && (!isStr(doc.last_updated) || !DATE_RE3.test(doc.last_updated))) {
|
|
@@ -6001,7 +6048,7 @@ function assertIndexRows(knowledgeDir) {
|
|
|
6001
6048
|
const violations = [];
|
|
6002
6049
|
const readmePath = join8(knowledgeDir, "README.md");
|
|
6003
6050
|
if (!existsSync5(readmePath)) {
|
|
6004
|
-
violations.push(violation8("medium", "compound.index.missing-readme", `missing ${readmePath}
|
|
6051
|
+
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"));
|
|
6005
6052
|
return { ok: false, violations };
|
|
6006
6053
|
}
|
|
6007
6054
|
const docs = collectKnowledgeDocs(knowledgeDir);
|
|
@@ -6040,7 +6087,7 @@ function scopeGuard(path2, allowedRoots) {
|
|
|
6040
6087
|
return {
|
|
6041
6088
|
ok: false,
|
|
6042
6089
|
violations: [
|
|
6043
|
-
violation8("medium", "compound.scope.outside", `path "${path2}" is outside the compound-refresh scope (allowed: ${allowedRoots.join(", ")})
|
|
6090
|
+
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")
|
|
6044
6091
|
]
|
|
6045
6092
|
};
|
|
6046
6093
|
}
|
|
@@ -6087,16 +6134,36 @@ function findTemporaryMarkers(fileText) {
|
|
|
6087
6134
|
}
|
|
6088
6135
|
markers.push({ line: i + 1, text, removalPath });
|
|
6089
6136
|
if (removalPath === null) {
|
|
6090
|
-
violations.push(violation9("medium", "lint.temporary.no-removal-path", `temporary marker at line ${i + 1} records no removal path (plan/status artifact reference)
|
|
6137
|
+
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"'));
|
|
6091
6138
|
}
|
|
6092
6139
|
}
|
|
6093
6140
|
return { ok: violations.length === 0, violations, markers };
|
|
6094
6141
|
}
|
|
6142
|
+
var TASK_ARTIFACT_RE = /\btask-\d+(?:-(?:brief|report|fix-report|diff)|\.diff)\b/g;
|
|
6143
|
+
var SDD_DEEPLINK_RE = /\.(?:mstar|agents)\/sdd\/([^\s/<>{}\[\]"'\*\?]+)/g;
|
|
6144
|
+
function findEphemeralCitations(skillText) {
|
|
6145
|
+
const citations = [];
|
|
6146
|
+
const lines = skillText.split(/\r?\n/);
|
|
6147
|
+
for (let i = 0;i < lines.length; i++) {
|
|
6148
|
+
const found = [];
|
|
6149
|
+
for (const m of lines[i].matchAll(TASK_ARTIFACT_RE)) {
|
|
6150
|
+
found.push({ index: m.index, match: m[0], kind: "task-artifact" });
|
|
6151
|
+
}
|
|
6152
|
+
for (const m of lines[i].matchAll(SDD_DEEPLINK_RE)) {
|
|
6153
|
+
found.push({ index: m.index, match: m[0], kind: "sdd-deeplink" });
|
|
6154
|
+
}
|
|
6155
|
+
found.sort((a, b) => a.index - b.index);
|
|
6156
|
+
for (const f of found) {
|
|
6157
|
+
citations.push({ line: i + 1, match: f.match, kind: f.kind });
|
|
6158
|
+
}
|
|
6159
|
+
}
|
|
6160
|
+
return citations;
|
|
6161
|
+
}
|
|
6095
6162
|
var TEST_FILE_PATH_RE = /[\w./-]+\.(?:test|spec)\.[a-z0-9]+/i;
|
|
6096
6163
|
var TEST_FILE_PHRASE_RE = /\btest files?\b/i;
|
|
6097
6164
|
var COMMAND_PROMPT_RE = /^\s*[$>]\s*\S/;
|
|
6098
6165
|
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;
|
|
6099
|
-
var OUTPUT_TOKEN_RE = /[
|
|
6166
|
+
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;
|
|
6100
6167
|
function assertSddTddTriple(reportText) {
|
|
6101
6168
|
const violations = [];
|
|
6102
6169
|
const lines = reportText.split(/\r?\n/);
|
|
@@ -6114,13 +6181,13 @@ function assertSddTddTriple(reportText) {
|
|
|
6114
6181
|
break;
|
|
6115
6182
|
}
|
|
6116
6183
|
if (!hasTests) {
|
|
6117
|
-
violations.push(violation9("medium", "lint.sdd-tdd.missing-tests", "task report carries no test file reference
|
|
6184
|
+
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'));
|
|
6118
6185
|
}
|
|
6119
6186
|
if (!hasCommand) {
|
|
6120
|
-
violations.push(violation9("medium", "lint.sdd-tdd.missing-command", "task report carries no command
|
|
6187
|
+
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'));
|
|
6121
6188
|
}
|
|
6122
6189
|
if (!hasOutput) {
|
|
6123
|
-
violations.push(violation9("medium", "lint.sdd-tdd.missing-output", "task report carries no output evidence
|
|
6190
|
+
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'));
|
|
6124
6191
|
}
|
|
6125
6192
|
return { ok: violations.length === 0, violations };
|
|
6126
6193
|
}
|
|
@@ -6173,18 +6240,18 @@ function lintSkillFrontmatter(frontmatterText) {
|
|
|
6173
6240
|
const violations = [];
|
|
6174
6241
|
const fm = parseFrontmatter(frontmatterText);
|
|
6175
6242
|
if (fm === null) {
|
|
6176
|
-
violations.push(violation9("medium", "lint.frontmatter.missing", "no YAML frontmatter block found
|
|
6243
|
+
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"));
|
|
6177
6244
|
return { ok: false, violations };
|
|
6178
6245
|
}
|
|
6179
6246
|
const name = fm.name ?? "";
|
|
6180
6247
|
if (name === "") {
|
|
6181
|
-
violations.push(violation9("medium", "lint.frontmatter.name.missing", "frontmatter `name` is missing
|
|
6248
|
+
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"));
|
|
6182
6249
|
} else if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)) {
|
|
6183
|
-
violations.push(violation9("medium", "lint.frontmatter.name.format", `frontmatter \`name\` must be lowercase-hyphen ("${name}")
|
|
6250
|
+
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`"));
|
|
6184
6251
|
}
|
|
6185
6252
|
const description = fm.description ?? "";
|
|
6186
6253
|
if (description === "") {
|
|
6187
|
-
violations.push(violation9("medium", "lint.frontmatter.description.missing", "frontmatter `description` is missing
|
|
6254
|
+
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)"));
|
|
6188
6255
|
} else {
|
|
6189
6256
|
const stripped = description.replace(/`[^`]*`/g, " ").replace(/'[^']*'/g, " ").replace(/"[^"]*"/g, " ");
|
|
6190
6257
|
let pronoun = null;
|
|
@@ -6195,15 +6262,15 @@ function lintSkillFrontmatter(frontmatterText) {
|
|
|
6195
6262
|
break;
|
|
6196
6263
|
}
|
|
6197
6264
|
if (pronoun !== null) {
|
|
6198
|
-
violations.push(violation9("low", "lint.frontmatter.description.person", `description uses first/second-person pronoun "${pronoun[0]}"
|
|
6265
|
+
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"'));
|
|
6199
6266
|
}
|
|
6200
6267
|
const start = description.trim().replace(/^[*_#>]+/, "").replace(/^["'`]+/, "").trim();
|
|
6201
6268
|
if (WORKFLOW_VERB_START_RE.test(start)) {
|
|
6202
|
-
violations.push(violation9("low", "lint.frontmatter.description.workflow", 'description reads as a workflow summary ("Explains/Describes/Covers
|
|
6269
|
+
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"));
|
|
6203
6270
|
} else {
|
|
6204
6271
|
const words = description.trim().split(/\s+/).filter(Boolean).length;
|
|
6205
6272
|
if (words > DESCRIPTION_MAX_WORDS) {
|
|
6206
|
-
violations.push(violation9("low", "lint.frontmatter.description.workflow", `description is ${words} words
|
|
6273
|
+
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"));
|
|
6207
6274
|
}
|
|
6208
6275
|
}
|
|
6209
6276
|
}
|
|
@@ -6250,7 +6317,7 @@ function lintStrategySections(docText) {
|
|
|
6250
6317
|
}
|
|
6251
6318
|
for (const required of REQUIRED_STRATEGY_SECTIONS) {
|
|
6252
6319
|
if (!headings.has(required.toLowerCase())) {
|
|
6253
|
-
violations.push(violation9("medium", "lint.strategy.missing-section", `missing required section "${required}" (mstar-strategy
|
|
6320
|
+
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"));
|
|
6254
6321
|
}
|
|
6255
6322
|
}
|
|
6256
6323
|
return { ok: violations.length === 0, violations };
|
|
@@ -6263,6 +6330,8 @@ function detectHost(signals2) {
|
|
|
6263
6330
|
return "opencode";
|
|
6264
6331
|
if (s.has("task_agent_batch") || s.has("ask") || s.has("hub"))
|
|
6265
6332
|
return "omp";
|
|
6333
|
+
if (s.has("subagent"))
|
|
6334
|
+
return "dsh";
|
|
6266
6335
|
if (s.has("AgentSwarm"))
|
|
6267
6336
|
return "kimi";
|
|
6268
6337
|
if (s.has("Agent") || s.has("AskUserQuestion") || s.has("EnterPlanMode") || s.has("TodoWrite"))
|
|
@@ -6289,7 +6358,7 @@ function lintFiveQuestion(bodyText) {
|
|
|
6289
6358
|
const label = section.label.toLowerCase();
|
|
6290
6359
|
const covered = headings.some((heading) => heading.includes(label));
|
|
6291
6360
|
if (!covered) {
|
|
6292
|
-
violations.push(violation11("low", `skill-authoring.five-question.${section.key}`, `body does not answer "${section.question}"
|
|
6361
|
+
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}`));
|
|
6293
6362
|
}
|
|
6294
6363
|
}
|
|
6295
6364
|
return { ok: violations.length === 0, violations };
|
|
@@ -6386,7 +6455,7 @@ var GITIGNORE_SNIPPET2 = `# Morning Star harness (.mstar/)
|
|
|
6386
6455
|
!.mstar/specs/
|
|
6387
6456
|
!.mstar/specs/**
|
|
6388
6457
|
`;
|
|
6389
|
-
var GITIGNORE_SNIPPET_AGENTS2 = `# Morning Star harness (.agents/)
|
|
6458
|
+
var GITIGNORE_SNIPPET_AGENTS2 = `# Morning Star harness (.agents/) \u2014 legacy
|
|
6390
6459
|
# Default-ignore everything under .agents/, then re-include the tracked results.
|
|
6391
6460
|
.agents/**
|
|
6392
6461
|
!.agents/AGENTS.md
|
|
@@ -6424,7 +6493,7 @@ function validateExecutionLease(lease) {
|
|
|
6424
6493
|
return {
|
|
6425
6494
|
ok: false,
|
|
6426
6495
|
violations: [
|
|
6427
|
-
violation("high", "lease.execution-lease.invalid", "execution_lease must be an object
|
|
6496
|
+
violation("high", "lease.execution-lease.invalid", "execution_lease must be an object \u2014 null and tombstone objects are invalid; writers delete the key on release")
|
|
6428
6497
|
]
|
|
6429
6498
|
};
|
|
6430
6499
|
}
|
|
@@ -6439,11 +6508,11 @@ function validateExecutionLease(lease) {
|
|
|
6439
6508
|
} else if (typeof lease.worktree_path !== "string" || lease.worktree_path.trim() === "") {
|
|
6440
6509
|
violations.push(violation("medium", "lease.execution-lease.invalid-worktree-path", "worktree_path must be a non-empty string"));
|
|
6441
6510
|
} else if (!isAbsolute22(lease.worktree_path)) {
|
|
6442
|
-
violations.push(violation("medium", "lease.execution-lease.invalid-worktree-path", "worktree_path must be an absolute path
|
|
6511
|
+
violations.push(violation("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)"));
|
|
6443
6512
|
}
|
|
6444
6513
|
validateNonEmptyString(violations, lease.working_branch, "working_branch", "lease.execution-lease.missing-working-branch", "lease.execution-lease.invalid-working-branch");
|
|
6445
6514
|
if (lease.session_label !== undefined && typeof lease.session_label !== "string") {
|
|
6446
|
-
violations.push(violation("medium", "lease.execution-lease.invalid-session-label", "session_label must be a string (display only
|
|
6515
|
+
violations.push(violation("medium", "lease.execution-lease.invalid-session-label", "session_label must be a string (display only \u2014 never used for ownership comparison)"));
|
|
6447
6516
|
}
|
|
6448
6517
|
return { ok: violations.length === 0, violations };
|
|
6449
6518
|
}
|
|
@@ -6460,7 +6529,7 @@ function verifyPlanExecutionLease(row, planId) {
|
|
|
6460
6529
|
return {
|
|
6461
6530
|
ok: false,
|
|
6462
6531
|
violations: [
|
|
6463
|
-
violation("high", "lease.verify.orphan", "plan is InProgress without an execution_lease
|
|
6532
|
+
violation("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)")
|
|
6464
6533
|
]
|
|
6465
6534
|
};
|
|
6466
6535
|
}
|
|
@@ -6473,9 +6542,9 @@ function verifyPlanExecutionLease(row, planId) {
|
|
|
6473
6542
|
}
|
|
6474
6543
|
const violations = [];
|
|
6475
6544
|
if (rowLease !== undefined && metadataLease !== undefined) {
|
|
6476
|
-
violations.push(violation("high", "lease.verify.dual-write", "execution_lease present in BOTH plans[].execution_lease (SSOT) and plans[].metadata.execution_lease
|
|
6545
|
+
violations.push(violation("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"));
|
|
6477
6546
|
} else if (rowLease === undefined) {
|
|
6478
|
-
violations.push(violation("high", "lease.verify.non-ssot-location", "execution_lease found only under plans[].metadata.execution_lease
|
|
6547
|
+
violations.push(violation("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)"));
|
|
6479
6548
|
}
|
|
6480
6549
|
violations.push(...validateExecutionLease(lease).violations);
|
|
6481
6550
|
return { ok: violations.length === 0, violations, lease };
|
|
@@ -6653,11 +6722,11 @@ function validateManifest(manifest, errors2, warnings) {
|
|
|
6653
6722
|
}
|
|
6654
6723
|
if (doc.extensions !== undefined) {
|
|
6655
6724
|
if (!isPlainObject6(doc.extensions)) {
|
|
6656
|
-
warnings.push('plugin.json: "extensions" is not an object
|
|
6725
|
+
warnings.push('plugin.json: "extensions" is not an object \u2014 ignored');
|
|
6657
6726
|
} else {
|
|
6658
6727
|
for (const [namespace, value] of Object.entries(doc.extensions)) {
|
|
6659
6728
|
if (!isPlainObject6(value)) {
|
|
6660
|
-
warnings.push(`plugin.json: "extensions.${namespace}" is not an object
|
|
6729
|
+
warnings.push(`plugin.json: "extensions.${namespace}" is not an object \u2014 ignored`);
|
|
6661
6730
|
}
|
|
6662
6731
|
}
|
|
6663
6732
|
}
|
|
@@ -6718,7 +6787,7 @@ function validateMcpServer(name, entry, errors2) {
|
|
|
6718
6787
|
} else {
|
|
6719
6788
|
const remainder = stripMcpPathPrefix(server.cwd);
|
|
6720
6789
|
if (remainder === null) {
|
|
6721
|
-
errors2.push(`${prefix}: "cwd" must be "
|
|
6790
|
+
errors2.push(`${prefix}: "cwd" must be "./\u2026", "${"${PLUGIN_ROOT}"}\u2026", or "${"${PLUGIN_DATA}"}\u2026"`);
|
|
6722
6791
|
} else if (escapesPluginRoot(remainder)) {
|
|
6723
6792
|
errors2.push(`${prefix}: "cwd" must remain within the plugin root (got "${server.cwd}")`);
|
|
6724
6793
|
}
|
|
@@ -6947,7 +7016,7 @@ import path5 from "node:path";
|
|
|
6947
7016
|
import fs2 from "node:fs";
|
|
6948
7017
|
import os from "node:os";
|
|
6949
7018
|
import path4 from "node:path";
|
|
6950
|
-
import { execFileSync as
|
|
7019
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
6951
7020
|
var REPO_URL = "https://github.com/btspoony/mstar-harness.git";
|
|
6952
7021
|
var PLUGIN_NAME = "morning-star-harness";
|
|
6953
7022
|
var HARNESS_REPO_PATH = path4.join(os.homedir(), ".mstar", "harness");
|
|
@@ -6981,7 +7050,7 @@ function ensureDir(dirPath, dryRun) {
|
|
|
6981
7050
|
function runCommand(command, cwd, dryRun) {
|
|
6982
7051
|
if (dryRun)
|
|
6983
7052
|
return;
|
|
6984
|
-
|
|
7053
|
+
execFileSync4(command[0], command.slice(1), { cwd, stdio: "pipe", encoding: "utf8" });
|
|
6985
7054
|
}
|
|
6986
7055
|
function ensureLocalHarnessRepo(dryRun) {
|
|
6987
7056
|
const notes = [];
|
|
@@ -7293,7 +7362,7 @@ function ensureIterationSkillLinks(dryRun) {
|
|
|
7293
7362
|
}
|
|
7294
7363
|
const gitignoreEntries = CODEX_PROJECT_COMMAND_NAMES.map(iterationSkillGitignoreEntry);
|
|
7295
7364
|
notes.push(...appendGitignore(projectRoot, gitignoreEntries, dryRun));
|
|
7296
|
-
notes.push("Installed Codex project-scoped command skills under .agents/skills/ (iteration-start, iteration-drive, iteration-loop, codebase-audit)
|
|
7365
|
+
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.");
|
|
7297
7366
|
return notes;
|
|
7298
7367
|
}
|
|
7299
7368
|
function validateIterationSkillLinks() {
|
|
@@ -7467,7 +7536,7 @@ var cursorAdapter = {
|
|
|
7467
7536
|
// src/adapters/omp.ts
|
|
7468
7537
|
import fs5 from "node:fs";
|
|
7469
7538
|
import path7 from "node:path";
|
|
7470
|
-
import { execFileSync as
|
|
7539
|
+
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
7471
7540
|
var OMP_PLUGIN_MARKER = ".omp-plugin/plugin.json";
|
|
7472
7541
|
var CLAUDE_PLUGIN_MARKER = ".claude-plugin/plugin.json";
|
|
7473
7542
|
var PACKAGE_NAMES = new Set(["morning-star", PLUGIN_NAME, "github:btspoony/mstar-harness"]);
|
|
@@ -7475,7 +7544,7 @@ var SKILL_SMOKE = ["mstar-host", "mstar-harness-core", "pm"];
|
|
|
7475
7544
|
var COMMAND_SMOKE = ["iteration-start", "iteration-drive", "iteration-loop", "codebase-audit"];
|
|
7476
7545
|
function ompAvailable() {
|
|
7477
7546
|
try {
|
|
7478
|
-
|
|
7547
|
+
execFileSync6("omp", ["--version"], { stdio: "pipe", encoding: "utf8" });
|
|
7479
7548
|
return true;
|
|
7480
7549
|
} catch {
|
|
7481
7550
|
return false;
|
|
@@ -7484,11 +7553,11 @@ function ompAvailable() {
|
|
|
7484
7553
|
function runOmp(args, dryRun) {
|
|
7485
7554
|
if (dryRun)
|
|
7486
7555
|
return;
|
|
7487
|
-
|
|
7556
|
+
execFileSync6("omp", args, { stdio: "pipe", encoding: "utf8" });
|
|
7488
7557
|
}
|
|
7489
7558
|
function listInstalledPlugins() {
|
|
7490
7559
|
try {
|
|
7491
|
-
const raw =
|
|
7560
|
+
const raw = execFileSync6("omp", ["plugin", "list", "--json"], {
|
|
7492
7561
|
stdio: "pipe",
|
|
7493
7562
|
encoding: "utf8"
|
|
7494
7563
|
});
|
|
@@ -7564,7 +7633,7 @@ function runInit2(scope, dryRun) {
|
|
|
7564
7633
|
notes.push(`Would update local harness repo: git -C ${HARNESS_REPO_PATH} pull --ff-only`);
|
|
7565
7634
|
} else {
|
|
7566
7635
|
try {
|
|
7567
|
-
|
|
7636
|
+
execFileSync6("git", ["-C", HARNESS_REPO_PATH, "pull", "--ff-only"], {
|
|
7568
7637
|
stdio: "pipe",
|
|
7569
7638
|
encoding: "utf8"
|
|
7570
7639
|
});
|
|
@@ -7609,7 +7678,7 @@ function runInit2(scope, dryRun) {
|
|
|
7609
7678
|
}
|
|
7610
7679
|
notes.push("Verify with: omp plugin list");
|
|
7611
7680
|
notes.push("Enter PM with /skill:pm ; commands: /iteration-start /iteration-drive /iteration-loop /codebase-audit");
|
|
7612
|
-
notes.push(`Host adapter: mstar-host
|
|
7681
|
+
notes.push(`Host adapter: mstar-host \u2192 references/omp.md (skill://mstar-host/references/omp.md)`);
|
|
7613
7682
|
notes.push(`Alternate install without CLI link: omp plugin install ${REPO_URL.replace("https://github.com/", "github:").replace(/\.git$/, "")}`);
|
|
7614
7683
|
return {
|
|
7615
7684
|
location: HARNESS_REPO_PATH,
|
|
@@ -7734,10 +7803,10 @@ function getDoctorWarnings(config) {
|
|
|
7734
7803
|
const hasNpm = strings.some(isMstarHarnessOpencodePlugin);
|
|
7735
7804
|
const hasLegacy = strings.some(isLegacyMorningStarGitPlugin);
|
|
7736
7805
|
if (hasLegacy && !hasNpm) {
|
|
7737
|
-
warnings.push("Plugin list uses legacy `morning-star@git
|
|
7806
|
+
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`.");
|
|
7738
7807
|
}
|
|
7739
7808
|
if (hasLegacy && hasNpm) {
|
|
7740
|
-
warnings.push("Both legacy `morning-star@git
|
|
7809
|
+
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.");
|
|
7741
7810
|
}
|
|
7742
7811
|
const agent = ensureObject(config.agent);
|
|
7743
7812
|
const missingModels = ALL_ROLES.filter((roleId) => {
|
|
@@ -7745,7 +7814,7 @@ function getDoctorWarnings(config) {
|
|
|
7745
7814
|
return typeof role.model !== "string" || !role.model.trim();
|
|
7746
7815
|
});
|
|
7747
7816
|
if (missingModels.length) {
|
|
7748
|
-
warnings.push(`${missingModels.length} role(s) have no explicit agent.<role>.model
|
|
7817
|
+
warnings.push(`${missingModels.length} role(s) have no explicit agent.<role>.model \u2014 OpenCode default model will be used (recommended for fastest setup).`);
|
|
7749
7818
|
}
|
|
7750
7819
|
return warnings;
|
|
7751
7820
|
}
|
|
@@ -7933,7 +8002,7 @@ function runInit3(scope, dryRun) {
|
|
|
7933
8002
|
if (!dryRun)
|
|
7934
8003
|
writeJson2(KNOWN_MARKETPLACES_PATH, knownNext);
|
|
7935
8004
|
notes.push(`Registered ${MARKETPLACE_ID} marketplace in ${KNOWN_MARKETPLACES_PATH}`);
|
|
7936
|
-
notes.push(`Then in ZCode: Settings
|
|
8005
|
+
notes.push(`Then in ZCode: Settings \u2192 Plugin Management \u2192 Discover \u2192 install ${PLUGIN_NAME} from the ${MARKETPLACE_ID} marketplace.`);
|
|
7937
8006
|
return {
|
|
7938
8007
|
location: KNOWN_MARKETPLACES_PATH,
|
|
7939
8008
|
notes
|
|
@@ -8184,7 +8253,7 @@ pathCommand.command("resolve").description("Resolve {HARNESS_DIR} + {SPECS_DIR}
|
|
|
8184
8253
|
const startDir = pathArg ? path11.resolve(pathArg) : process.cwd();
|
|
8185
8254
|
const harnessDir = resolveHarnessDir(startDir);
|
|
8186
8255
|
if (!harnessDir) {
|
|
8187
|
-
const guidance = "no harness dir found (
|
|
8256
|
+
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`;
|
|
8188
8257
|
if (options.json) {
|
|
8189
8258
|
console.log(JSON.stringify({ ok: false, startDir, harnessDir: null, specsDir: null, guidance }));
|
|
8190
8259
|
} else {
|
|
@@ -8792,7 +8861,7 @@ function resolveAuditShortSha(cwd, override) {
|
|
|
8792
8861
|
if (override !== undefined && override !== "")
|
|
8793
8862
|
return override;
|
|
8794
8863
|
try {
|
|
8795
|
-
const out =
|
|
8864
|
+
const out = execFileSync7("git", ["rev-parse", "--short", "HEAD"], {
|
|
8796
8865
|
cwd,
|
|
8797
8866
|
encoding: "utf8",
|
|
8798
8867
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -8902,7 +8971,7 @@ hostCommand.command("detect").description("Detect the active host from --signals
|
|
|
8902
8971
|
}
|
|
8903
8972
|
});
|
|
8904
8973
|
var skillCommand = program2.command("skill").description("skill-authoring lints (engine-backed)");
|
|
8905
|
-
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) => {
|
|
8974
|
+
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) => {
|
|
8906
8975
|
try {
|
|
8907
8976
|
if (!skillDir)
|
|
8908
8977
|
throw new SddScriptError("usage: skill lint <skill-dir>", 2);
|
|
@@ -8917,6 +8986,19 @@ skillCommand.command("lint").description("Lint <skill-dir>/SKILL.md: frontmatter
|
|
|
8917
8986
|
const fiveQuestion = lintFiveQuestion(stripFrontmatter(text));
|
|
8918
8987
|
printChecklist("skill lint (five questions)", fiveQuestion);
|
|
8919
8988
|
violations.push(...fiveQuestion.violations);
|
|
8989
|
+
const ephemeral = findEphemeralCitations(text);
|
|
8990
|
+
const ephemeralGate = {
|
|
8991
|
+
ok: ephemeral.length === 0,
|
|
8992
|
+
violations: ephemeral.map((citation) => ({
|
|
8993
|
+
ok: false,
|
|
8994
|
+
severity: "medium",
|
|
8995
|
+
code: `skill.ephemeral.${citation.kind}`,
|
|
8996
|
+
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)`,
|
|
8997
|
+
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`
|
|
8998
|
+
}))
|
|
8999
|
+
};
|
|
9000
|
+
printChecklist("skill lint (ephemeral citations)", ephemeralGate);
|
|
9001
|
+
violations.push(...ephemeralGate.violations);
|
|
8920
9002
|
if (violations.length > 0)
|
|
8921
9003
|
process.exitCode = 1;
|
|
8922
9004
|
} catch (error) {
|