@mstar-harness/cli 2.1.1 → 2.2.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.
@@ -2344,13 +2344,24 @@ var require_commander = __commonJS((exports) => {
2344
2344
  });
2345
2345
 
2346
2346
  // src/index.ts
2347
- import { execFileSync as execFileSync6 } from "child_process";
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 isUpKey = (key, keybindings = []) => key.name === "up" || keybindings.includes("vim") && key.name === "k" || keybindings.includes("emacs") && key.ctrl && key.name === "p";
2353
- var isDownKey = (key, keybindings = []) => key.name === "down" || keybindings.includes("vim") && key.name === "j" || keybindings.includes("emacs") && key.ctrl && key.name === "n";
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";
@@ -2823,6 +2834,7 @@ var defaultTheme = {
2823
2834
  interval: 80,
2824
2835
  frames: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"].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
- defaultTheme,
2877
+ getDefaultTheme(),
2860
2878
  ...themes.filter((theme) => theme != null)
2861
2879
  ];
2862
2880
  return deepMerge(...themesToMerge);
@@ -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, hard: true }).split(`
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 _prepareStackTrace = Error.prepareStackTrace;
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 = _prepareStackTrace;
3695
+ Error.prepareStackTrace = savedPrepareStackTrace;
3677
3696
  return result;
3678
3697
  }
3679
3698
  function createPrompt(view) {
@@ -3792,8 +3811,7 @@ var selectTheme = {
3792
3811
  keysHelpTip: (keys) => keys.map(([key, action]) => `${styleText3("bold", key)} ${styleText3("dim", action)}`).join(styleText3("dim", " • "))
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 = !keybindings.includes("vim");
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, keybindings) || isDownKey(key, keybindings)) {
3888
+ } else if (isUpKey(key, keybindings2) || isDownKey(key, keybindings2)) {
3871
3889
  rl.clearLine(0);
3872
- if (loop || isUpKey(key, keybindings) && active !== bounds.first || isDownKey(key, keybindings) && active !== bounds.last) {
3873
- const offset = isUpKey(key, keybindings) ? -1 : 1;
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;
@@ -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 execFileSync2 } from "node:child_process";
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);
@@ -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 = execFileSync(opts.gitPath ?? "git", ["-C", worktreePath, "branch", "--show-current"], {
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
@@ -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 execFileSync2("git", args, {
4719
+ return execFileSync3("git", args, {
4674
4720
  cwd,
4675
4721
  encoding: "utf8",
4676
4722
  stdio: ["ignore", "pipe", "pipe"],
@@ -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
- execFileSync2("git", ["rev-parse", "--verify", "--quiet", ref], { cwd, stdio: ["ignore", "pipe", "pipe"] });
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) => execFileSync2("git", args, { cwd, maxBuffer: GIT_CAPTURE_MAX_BYTES });
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
 
@@ -6263,6 +6309,8 @@ function detectHost(signals2) {
6263
6309
  return "opencode";
6264
6310
  if (s.has("task_agent_batch") || s.has("ask") || s.has("hub"))
6265
6311
  return "omp";
6312
+ if (s.has("subagent"))
6313
+ return "dsh";
6266
6314
  if (s.has("AgentSwarm"))
6267
6315
  return "kimi";
6268
6316
  if (s.has("Agent") || s.has("AskUserQuestion") || s.has("EnterPlanMode") || s.has("TodoWrite"))
@@ -6947,7 +6995,7 @@ import path5 from "node:path";
6947
6995
  import fs2 from "node:fs";
6948
6996
  import os from "node:os";
6949
6997
  import path4 from "node:path";
6950
- import { execFileSync as execFileSync3 } from "node:child_process";
6998
+ import { execFileSync as execFileSync4 } from "node:child_process";
6951
6999
  var REPO_URL = "https://github.com/btspoony/mstar-harness.git";
6952
7000
  var PLUGIN_NAME = "morning-star-harness";
6953
7001
  var HARNESS_REPO_PATH = path4.join(os.homedir(), ".mstar", "harness");
@@ -6981,7 +7029,7 @@ function ensureDir(dirPath, dryRun) {
6981
7029
  function runCommand(command, cwd, dryRun) {
6982
7030
  if (dryRun)
6983
7031
  return;
6984
- execFileSync3(command[0], command.slice(1), { cwd, stdio: "pipe", encoding: "utf8" });
7032
+ execFileSync4(command[0], command.slice(1), { cwd, stdio: "pipe", encoding: "utf8" });
6985
7033
  }
6986
7034
  function ensureLocalHarnessRepo(dryRun) {
6987
7035
  const notes = [];
@@ -7467,7 +7515,7 @@ var cursorAdapter = {
7467
7515
  // src/adapters/omp.ts
7468
7516
  import fs5 from "node:fs";
7469
7517
  import path7 from "node:path";
7470
- import { execFileSync as execFileSync5 } from "node:child_process";
7518
+ import { execFileSync as execFileSync6 } from "node:child_process";
7471
7519
  var OMP_PLUGIN_MARKER = ".omp-plugin/plugin.json";
7472
7520
  var CLAUDE_PLUGIN_MARKER = ".claude-plugin/plugin.json";
7473
7521
  var PACKAGE_NAMES = new Set(["morning-star", PLUGIN_NAME, "github:btspoony/mstar-harness"]);
@@ -7475,7 +7523,7 @@ var SKILL_SMOKE = ["mstar-host", "mstar-harness-core", "pm"];
7475
7523
  var COMMAND_SMOKE = ["iteration-start", "iteration-drive", "iteration-loop", "codebase-audit"];
7476
7524
  function ompAvailable() {
7477
7525
  try {
7478
- execFileSync5("omp", ["--version"], { stdio: "pipe", encoding: "utf8" });
7526
+ execFileSync6("omp", ["--version"], { stdio: "pipe", encoding: "utf8" });
7479
7527
  return true;
7480
7528
  } catch {
7481
7529
  return false;
@@ -7484,11 +7532,11 @@ function ompAvailable() {
7484
7532
  function runOmp(args, dryRun) {
7485
7533
  if (dryRun)
7486
7534
  return;
7487
- execFileSync5("omp", args, { stdio: "pipe", encoding: "utf8" });
7535
+ execFileSync6("omp", args, { stdio: "pipe", encoding: "utf8" });
7488
7536
  }
7489
7537
  function listInstalledPlugins() {
7490
7538
  try {
7491
- const raw = execFileSync5("omp", ["plugin", "list", "--json"], {
7539
+ const raw = execFileSync6("omp", ["plugin", "list", "--json"], {
7492
7540
  stdio: "pipe",
7493
7541
  encoding: "utf8"
7494
7542
  });
@@ -7564,7 +7612,7 @@ function runInit2(scope, dryRun) {
7564
7612
  notes.push(`Would update local harness repo: git -C ${HARNESS_REPO_PATH} pull --ff-only`);
7565
7613
  } else {
7566
7614
  try {
7567
- execFileSync5("git", ["-C", HARNESS_REPO_PATH, "pull", "--ff-only"], {
7615
+ execFileSync6("git", ["-C", HARNESS_REPO_PATH, "pull", "--ff-only"], {
7568
7616
  stdio: "pipe",
7569
7617
  encoding: "utf8"
7570
7618
  });
@@ -8184,7 +8232,7 @@ pathCommand.command("resolve").description("Resolve {HARNESS_DIR} + {SPECS_DIR}
8184
8232
  const startDir = pathArg ? path11.resolve(pathArg) : process.cwd();
8185
8233
  const harnessDir = resolveHarnessDir(startDir);
8186
8234
  if (!harnessDir) {
8187
- const guidance = "no harness dir found (probed .mstar/, .agents/, .plans/, plans/ walking up from " + `${startDir}) \u2014 run \`mstar init\` to bootstrap, or pass a start dir inside a harness-enabled project`;
8235
+ 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
8236
  if (options.json) {
8189
8237
  console.log(JSON.stringify({ ok: false, startDir, harnessDir: null, specsDir: null, guidance }));
8190
8238
  } else {
@@ -8792,7 +8840,7 @@ function resolveAuditShortSha(cwd, override) {
8792
8840
  if (override !== undefined && override !== "")
8793
8841
  return override;
8794
8842
  try {
8795
- const out = execFileSync6("git", ["rev-parse", "--short", "HEAD"], {
8843
+ const out = execFileSync7("git", ["rev-parse", "--short", "HEAD"], {
8796
8844
  cwd,
8797
8845
  encoding: "utf8",
8798
8846
  stdio: ["ignore", "pipe", "ignore"]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mstar-harness/cli",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "description": "Morning Star harness CLI — installer bootstrap + mstar workflow verbs (path/status/lease/sdd/iteration/dispatch/worktree/lint/design-md/audit/compound/host/skill).",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -34,6 +34,6 @@
34
34
  "access": "public"
35
35
  },
36
36
  "devDependencies": {
37
- "@mstar-harness/engine": "2.1.1"
37
+ "@mstar-harness/engine": "2.2.0"
38
38
  }
39
39
  }