@mstar-harness/cli 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/mstar-harness.js +295 -110
  2. package/package.json +1 -1
@@ -2344,8 +2344,8 @@ var require_commander = __commonJS((exports) => {
2344
2344
  });
2345
2345
 
2346
2346
  // src/index.ts
2347
- import fs5 from "fs";
2348
- import path7 from "path";
2347
+ import fs6 from "fs";
2348
+ import path8 from "path";
2349
2349
  import { fileURLToPath } from "url";
2350
2350
 
2351
2351
  // ../../node_modules/@inquirer/core/dist/lib/key.js
@@ -4241,9 +4241,9 @@ function buildModelAssignments(selections) {
4241
4241
  }
4242
4242
 
4243
4243
  // src/adapters/codex.ts
4244
- import fs2 from "node:fs";
4245
- import os from "node:os";
4246
- import path3 from "node:path";
4244
+ import fs3 from "node:fs";
4245
+ import os2 from "node:os";
4246
+ import path4 from "node:path";
4247
4247
 
4248
4248
  // src/utils.ts
4249
4249
  import fs from "node:fs";
@@ -4283,21 +4283,169 @@ function resolveProjectRoot() {
4283
4283
  return process.cwd();
4284
4284
  }
4285
4285
 
4286
+ // src/adapters/shared-install.ts
4287
+ import fs2 from "node:fs";
4288
+ import os from "node:os";
4289
+ import path3 from "node:path";
4290
+ import { execFileSync } from "node:child_process";
4291
+ var REPO_URL = "https://github.com/btspoony/mstar-harness.git";
4292
+ var PLUGIN_NAME = "morning-star-harness";
4293
+ var HARNESS_REPO_PATH = path3.join(os.homedir(), ".mstar", "harness");
4294
+ var HARNESS_MARKER = ".codex-plugin/plugin.json";
4295
+ function pathOrSymlinkExists(filePath) {
4296
+ try {
4297
+ fs2.lstatSync(filePath);
4298
+ return true;
4299
+ } catch {
4300
+ return false;
4301
+ }
4302
+ }
4303
+ function ensureDir(dirPath, dryRun) {
4304
+ if (dryRun)
4305
+ return;
4306
+ if (!fs2.existsSync(dirPath))
4307
+ fs2.mkdirSync(dirPath, { recursive: true });
4308
+ }
4309
+ function runCommand(command, cwd, dryRun) {
4310
+ if (dryRun)
4311
+ return;
4312
+ execFileSync(command[0], command.slice(1), { cwd, stdio: "pipe", encoding: "utf8" });
4313
+ }
4314
+ function ensureLocalHarnessRepo(dryRun) {
4315
+ const notes = [];
4316
+ if (fs2.existsSync(HARNESS_REPO_PATH)) {
4317
+ const errors2 = validateLocalHarnessRepo();
4318
+ if (errors2.length) {
4319
+ throw new Error(errors2.join(`
4320
+ `));
4321
+ }
4322
+ notes.push(`Using existing local harness repo at ${HARNESS_REPO_PATH}`);
4323
+ return notes;
4324
+ }
4325
+ ensureDir(path3.dirname(HARNESS_REPO_PATH), dryRun);
4326
+ runCommand(["git", "clone", REPO_URL, HARNESS_REPO_PATH], path3.dirname(HARNESS_REPO_PATH), dryRun);
4327
+ notes.push(`Cloned ${REPO_URL} to ${HARNESS_REPO_PATH}`);
4328
+ return notes;
4329
+ }
4330
+ function validateLocalHarnessRepo() {
4331
+ const errors2 = [];
4332
+ if (!fs2.existsSync(HARNESS_REPO_PATH)) {
4333
+ errors2.push(`Missing local harness repo: ${HARNESS_REPO_PATH}`);
4334
+ return errors2;
4335
+ }
4336
+ const marker = path3.join(HARNESS_REPO_PATH, HARNESS_MARKER);
4337
+ if (!fs2.existsSync(marker)) {
4338
+ errors2.push(`Local harness repo is missing marker file: ${marker}`);
4339
+ }
4340
+ return errors2;
4341
+ }
4342
+ function ensureSymlink(target, linkPath, dryRun) {
4343
+ if (pathOrSymlinkExists(linkPath)) {
4344
+ const stat = fs2.lstatSync(linkPath);
4345
+ if (!stat.isSymbolicLink()) {
4346
+ throw new Error(`Path exists and is not a symlink: ${linkPath}`);
4347
+ }
4348
+ if (!fs2.existsSync(target)) {
4349
+ throw new Error(`Symlink target is missing: ${target}`);
4350
+ }
4351
+ const actual = fs2.realpathSync(linkPath);
4352
+ const expected = fs2.realpathSync(target);
4353
+ if (actual !== expected) {
4354
+ throw new Error(`Symlink ${linkPath} points to ${actual}, expected ${expected}`);
4355
+ }
4356
+ return `Symlink already exists: ${linkPath} -> ${target}`;
4357
+ }
4358
+ ensureDir(path3.dirname(linkPath), dryRun);
4359
+ if (!dryRun)
4360
+ fs2.symlinkSync(target, linkPath);
4361
+ return `Linked ${linkPath} -> ${target}`;
4362
+ }
4363
+ function validateSymlink(target, linkPath) {
4364
+ const errors2 = [];
4365
+ if (!pathOrSymlinkExists(linkPath)) {
4366
+ errors2.push(`Missing symlink: ${linkPath}`);
4367
+ return errors2;
4368
+ }
4369
+ const stat = fs2.lstatSync(linkPath);
4370
+ if (!stat.isSymbolicLink()) {
4371
+ errors2.push(`Path exists but is not a symlink: ${linkPath}`);
4372
+ return errors2;
4373
+ }
4374
+ if (!fs2.existsSync(target)) {
4375
+ errors2.push(`Symlink target is missing: ${target}`);
4376
+ return errors2;
4377
+ }
4378
+ const actual = fs2.realpathSync(linkPath);
4379
+ const expected = fs2.realpathSync(target);
4380
+ if (actual !== expected) {
4381
+ errors2.push(`Symlink ${linkPath} points to ${actual}, expected ${expected}`);
4382
+ }
4383
+ return errors2;
4384
+ }
4385
+ function appendGitignore(projectRoot, entries, dryRun) {
4386
+ const gitignorePath = path3.join(projectRoot, ".gitignore");
4387
+ const current = fs2.existsSync(gitignorePath) ? fs2.readFileSync(gitignorePath, "utf8") : "";
4388
+ const lines = new Set(current.split(/\r?\n/).map((line) => line.trim()));
4389
+ const missing = entries.filter((entry) => !lines.has(entry));
4390
+ if (!missing.length)
4391
+ return [];
4392
+ if (!dryRun) {
4393
+ const prefix = current && !current.endsWith(`
4394
+ `) ? `
4395
+ ` : "";
4396
+ fs2.appendFileSync(gitignorePath, `${prefix}${missing.join(`
4397
+ `)}
4398
+ `, "utf8");
4399
+ }
4400
+ return missing.map((entry) => `Added ${entry} to .gitignore`);
4401
+ }
4402
+ function homeRelativeSourcePath(targetPath) {
4403
+ const rel = path3.relative(os.homedir(), targetPath).split(path3.sep).join("/");
4404
+ return rel.startsWith("..") ? targetPath : `./${rel}`;
4405
+ }
4406
+
4286
4407
  // src/adapters/codex.ts
4287
4408
  var MARKETPLACE_NAME = "personal";
4288
4409
  var MARKETPLACE_DISPLAY_NAME = "Personal";
4289
- var MARKETPLACE_PATH = path3.join(os.homedir(), ".agents", "plugins", "marketplace.json");
4290
- var PLUGIN_NAME = "morning-star-harness";
4291
- var PLUGIN_URL = "https://github.com/btspoony/mstar-harness.git";
4292
- var PLUGIN_REF = "main";
4410
+ var GLOBAL_MARKETPLACE_PATH = path4.join(os2.homedir(), ".agents", "plugins", "marketplace.json");
4293
4411
  var PLUGIN_CATEGORY = "Productivity";
4294
- function mstarEntry() {
4412
+ var CODEX_PLUGIN_LINK = ".codex/plugins/mstar-harness";
4413
+ var CODEX_AGENT_NAMES = [
4414
+ "product-manager",
4415
+ "architect",
4416
+ "fullstack-dev",
4417
+ "fullstack-dev-2",
4418
+ "frontend-dev",
4419
+ "qa-engineer",
4420
+ "qc-specialist",
4421
+ "qc-specialist-2",
4422
+ "qc-specialist-3",
4423
+ "ops-engineer",
4424
+ "writing-specialist",
4425
+ "prompt-engineer"
4426
+ ];
4427
+ function globalMarketplacePath() {
4428
+ return GLOBAL_MARKETPLACE_PATH;
4429
+ }
4430
+ function projectMarketplacePath() {
4431
+ return path4.join(resolveProjectRoot(), ".agents", "plugins", "marketplace.json");
4432
+ }
4433
+ function agentSourcePath(agentName) {
4434
+ return path4.join(HARNESS_REPO_PATH, "codex", "agents", `${agentName}.toml`);
4435
+ }
4436
+ function globalAgentLinkPath(agentName) {
4437
+ return path4.join(os2.homedir(), ".codex", "agents", `${agentName}.toml`);
4438
+ }
4439
+ function projectAgentLinkPath(agentName) {
4440
+ return path4.join(resolveProjectRoot(), ".codex", "agents", `${agentName}.toml`);
4441
+ }
4442
+ function mstarEntry(scope) {
4443
+ const sourcePath = scope === "global" ? homeRelativeSourcePath(HARNESS_REPO_PATH) : `./${CODEX_PLUGIN_LINK}`;
4295
4444
  return {
4296
4445
  name: PLUGIN_NAME,
4297
4446
  source: {
4298
- source: "url",
4299
- url: PLUGIN_URL,
4300
- ref: PLUGIN_REF
4447
+ source: "local",
4448
+ path: sourcePath
4301
4449
  },
4302
4450
  policy: {
4303
4451
  installation: "AVAILABLE",
@@ -4319,12 +4467,12 @@ function normalizeMarketplace(raw) {
4319
4467
  }
4320
4468
  return next;
4321
4469
  }
4322
- function upsertEntry(raw) {
4470
+ function upsertEntry(raw, scope) {
4323
4471
  const next = normalizeMarketplace(raw);
4324
4472
  const plugins = next.plugins.filter((entry) => {
4325
4473
  return !(entry && typeof entry === "object" && !Array.isArray(entry) && entry.name === PLUGIN_NAME);
4326
4474
  });
4327
- plugins.push(mstarEntry());
4475
+ plugins.push(mstarEntry(scope));
4328
4476
  next.plugins = plugins;
4329
4477
  return next;
4330
4478
  }
@@ -4334,19 +4482,19 @@ function findEntry(raw) {
4334
4482
  return entry && typeof entry === "object" && !Array.isArray(entry) && entry.name === PLUGIN_NAME;
4335
4483
  });
4336
4484
  }
4337
- function validateEntryShape(entry) {
4485
+ function validateEntryShape(entry, scope, marketplacePath) {
4338
4486
  const errors2 = [];
4339
4487
  if (!entry) {
4340
- errors2.push(`Missing ${PLUGIN_NAME} entry in ${MARKETPLACE_PATH}.`);
4488
+ errors2.push(`Missing ${PLUGIN_NAME} entry in ${marketplacePath}.`);
4341
4489
  return errors2;
4342
4490
  }
4343
4491
  const source = ensureObject(entry.source);
4344
- if (source.source !== "url")
4345
- errors2.push("Codex marketplace entry source.source must be `url`.");
4346
- if (source.url !== PLUGIN_URL)
4347
- errors2.push(`Codex marketplace entry source.url must be ${PLUGIN_URL}.`);
4348
- if (source.ref !== PLUGIN_REF)
4349
- errors2.push(`Codex marketplace entry source.ref must be ${PLUGIN_REF}.`);
4492
+ const expectedSourcePath = mstarEntry(scope).source.path;
4493
+ if (source.source !== "local")
4494
+ errors2.push("Codex marketplace entry source.source must be `local`.");
4495
+ if (source.path !== expectedSourcePath) {
4496
+ errors2.push(`Codex marketplace entry source.path must be ${expectedSourcePath}.`);
4497
+ }
4350
4498
  const policy = ensureObject(entry.policy);
4351
4499
  if (policy.installation !== "AVAILABLE")
4352
4500
  errors2.push("Codex marketplace entry policy.installation must be AVAILABLE.");
@@ -4356,120 +4504,157 @@ function validateEntryShape(entry) {
4356
4504
  errors2.push(`Codex marketplace entry category must be ${PLUGIN_CATEGORY}.`);
4357
4505
  return errors2;
4358
4506
  }
4359
- function runInit(dryRun) {
4360
- const current = readJson(MARKETPLACE_PATH);
4361
- const next = upsertEntry(current);
4507
+ function marketplacePath(scope) {
4508
+ if (scope === "global")
4509
+ return globalMarketplacePath();
4510
+ return projectMarketplacePath();
4511
+ }
4512
+ function ensureAgentLinks(scope, dryRun) {
4513
+ const notes = [];
4514
+ for (const agentName of CODEX_AGENT_NAMES) {
4515
+ const source = agentSourcePath(agentName);
4516
+ const linkPath = scope === "global" ? globalAgentLinkPath(agentName) : projectAgentLinkPath(agentName);
4517
+ notes.push(ensureSymlink(source, linkPath, dryRun));
4518
+ }
4519
+ return notes;
4520
+ }
4521
+ function validateAgentLinks(scope) {
4522
+ const errors2 = [];
4523
+ for (const agentName of CODEX_AGENT_NAMES) {
4524
+ const source = agentSourcePath(agentName);
4525
+ const linkPath = scope === "global" ? globalAgentLinkPath(agentName) : projectAgentLinkPath(agentName);
4526
+ errors2.push(...validateSymlink(source, linkPath));
4527
+ }
4528
+ return errors2;
4529
+ }
4530
+ function runInit(scope, dryRun) {
4531
+ const pathToMarketplace = marketplacePath(scope);
4532
+ const current = readJson(pathToMarketplace);
4533
+ const next = upsertEntry(current, scope);
4362
4534
  const existingEntry = findEntry(current);
4535
+ const notes = ensureLocalHarnessRepo(dryRun);
4536
+ if (scope === "project") {
4537
+ const projectRoot = resolveProjectRoot();
4538
+ notes.push(ensureSymlink(HARNESS_REPO_PATH, path4.join(projectRoot, CODEX_PLUGIN_LINK), dryRun));
4539
+ notes.push(...appendGitignore(projectRoot, [CODEX_PLUGIN_LINK, ".codex/agents/*.toml"], dryRun));
4540
+ }
4541
+ notes.push(...ensureAgentLinks(scope, dryRun));
4363
4542
  if (!dryRun)
4364
- writeJson(MARKETPLACE_PATH, next);
4543
+ writeJson(pathToMarketplace, next);
4544
+ notes.push(existingEntry ? `Updated ${PLUGIN_NAME} local marketplace entry.` : `Added ${PLUGIN_NAME} local marketplace entry.`);
4545
+ notes.push(`Source path: ${mstarEntry(scope).source.path}`);
4546
+ notes.push(`Install after init: codex plugin add ${PLUGIN_NAME} --marketplace ${MARKETPLACE_NAME}`);
4365
4547
  return {
4366
- location: MARKETPLACE_PATH,
4367
- notes: [
4368
- existingEntry ? `Updated ${PLUGIN_NAME} entry in personal Codex marketplace.` : `Added ${PLUGIN_NAME} entry to personal Codex marketplace.`,
4369
- `Source URL: ${PLUGIN_URL}#${PLUGIN_REF}`,
4370
- `Install after init: codex plugin add ${PLUGIN_NAME} --marketplace ${String(next.name)}`
4371
- ]
4548
+ location: pathToMarketplace,
4549
+ notes
4372
4550
  };
4373
4551
  }
4374
- function runDoctor() {
4375
- const errors2 = [];
4376
- if (!fs2.existsSync(MARKETPLACE_PATH)) {
4377
- return { location: MARKETPLACE_PATH, errors: [`Missing Codex personal marketplace: ${MARKETPLACE_PATH}`] };
4552
+ function runDoctor(scope) {
4553
+ const pathToMarketplace = marketplacePath(scope);
4554
+ const errors2 = validateLocalHarnessRepo();
4555
+ if (!fs3.existsSync(pathToMarketplace)) {
4556
+ return { location: pathToMarketplace, errors: [...errors2, `Missing Codex marketplace: ${pathToMarketplace}`] };
4378
4557
  }
4379
- const marketplace = readJson(MARKETPLACE_PATH);
4558
+ const marketplace = readJson(pathToMarketplace);
4380
4559
  if (marketplace.name !== MARKETPLACE_NAME) {
4381
4560
  errors2.push(`Codex personal marketplace name must be ${MARKETPLACE_NAME}.`);
4382
4561
  }
4383
- errors2.push(...validateEntryShape(findEntry(marketplace)));
4384
- return { location: MARKETPLACE_PATH, errors: errors2 };
4562
+ errors2.push(...validateEntryShape(findEntry(marketplace), scope, pathToMarketplace));
4563
+ if (scope === "project") {
4564
+ const projectRoot = resolveProjectRoot();
4565
+ errors2.push(...validateSymlink(HARNESS_REPO_PATH, path4.join(projectRoot, CODEX_PLUGIN_LINK)));
4566
+ const gitignorePath = path4.join(projectRoot, ".gitignore");
4567
+ const gitignore = fs3.existsSync(gitignorePath) ? fs3.readFileSync(gitignorePath, "utf8") : "";
4568
+ const lines = gitignore.split(/\r?\n/);
4569
+ if (!lines.includes(CODEX_PLUGIN_LINK))
4570
+ errors2.push(`Missing .gitignore entry: ${CODEX_PLUGIN_LINK}`);
4571
+ if (!lines.includes(".codex/agents/*.toml"))
4572
+ errors2.push("Missing .gitignore entry: .codex/agents/*.toml");
4573
+ }
4574
+ errors2.push(...validateAgentLinks(scope));
4575
+ return { location: pathToMarketplace, errors: errors2 };
4385
4576
  }
4386
4577
  var codexAdapter = {
4387
4578
  target: "codex",
4388
4579
  mode: "install",
4389
- runInstallInit: (_scope, dryRun) => runInit(dryRun),
4390
- runInstallDoctor: () => runDoctor()
4580
+ runInstallInit: (scope, dryRun) => runInit(scope, dryRun),
4581
+ runInstallDoctor: (scope) => runDoctor(scope)
4391
4582
  };
4392
4583
 
4393
4584
  // src/adapters/cursor.ts
4394
- import fs3 from "node:fs";
4395
- import os2 from "node:os";
4396
- import path4 from "node:path";
4397
- import { execFileSync } from "node:child_process";
4398
- var REPO_URL = "https://github.com/btspoony/mstar-harness.git";
4399
- var CURSOR_PLUGIN_NAME = "mstar-harness";
4585
+ import fs4 from "node:fs";
4586
+ import os3 from "node:os";
4587
+ import path5 from "node:path";
4588
+ var CURSOR_PLUGIN_NAME = "morning-star-harness";
4400
4589
  var CURSOR_PLUGIN_MARKER = ".cursor-plugin/plugin.json";
4590
+ var CURSOR_PLUGIN_LINK = ".cursor/plugins/morning-star-harness";
4591
+ var CURSOR_AGENT_SMOKE_NAMES = ["fullstack-dev", "qc-specialist"];
4401
4592
  function globalInstallPath() {
4402
- return path4.join(os2.homedir(), ".cursor", "plugins", "local", CURSOR_PLUGIN_NAME);
4593
+ return path5.join(os3.homedir(), ".cursor", "plugins", "local", CURSOR_PLUGIN_NAME);
4403
4594
  }
4404
4595
  function projectInstallPath() {
4405
- return path4.join(resolveProjectRoot(), ".cursor", "plugins", CURSOR_PLUGIN_NAME);
4406
- }
4407
- function ensureDir(dirPath, dryRun) {
4408
- if (dryRun)
4409
- return;
4410
- if (!fs3.existsSync(dirPath))
4411
- fs3.mkdirSync(dirPath, { recursive: true });
4596
+ return path5.join(resolveProjectRoot(), CURSOR_PLUGIN_LINK);
4412
4597
  }
4413
- function runCommand(command, cwd, dryRun) {
4414
- if (dryRun)
4415
- return;
4416
- execFileSync(command[0], command.slice(1), { cwd, stdio: "pipe", encoding: "utf8" });
4598
+ function validatePluginAgents() {
4599
+ const errors2 = [];
4600
+ const agentsDir = path5.join(HARNESS_REPO_PATH, "agents");
4601
+ if (!fs4.existsSync(agentsDir)) {
4602
+ errors2.push(`Missing plugin agents directory: ${agentsDir}`);
4603
+ return errors2;
4604
+ }
4605
+ for (const agentName of CURSOR_AGENT_SMOKE_NAMES) {
4606
+ const agentPath = path5.join(agentsDir, `${agentName}.md`);
4607
+ if (!fs4.existsSync(agentPath)) {
4608
+ errors2.push(`Missing plugin agent file: ${agentPath}`);
4609
+ continue;
4610
+ }
4611
+ const content = fs4.readFileSync(agentPath, "utf8");
4612
+ if (!/^---\nname:\s/m.test(content)) {
4613
+ errors2.push(`Plugin agent ${agentName}.md must use Cursor-first frontmatter (name, description, model before OpenCode fields).`);
4614
+ }
4615
+ }
4616
+ return errors2;
4417
4617
  }
4418
4618
  function globalInit(dryRun) {
4419
4619
  const location = globalInstallPath();
4420
- const notes = [];
4421
- if (fs3.existsSync(location)) {
4422
- notes.push(`Plugin already exists at ${location}`);
4423
- return { location, notes };
4424
- }
4425
- ensureDir(path4.dirname(location), dryRun);
4426
- runCommand(["git", "clone", REPO_URL, location], path4.dirname(location), dryRun);
4427
- notes.push(`Cloned ${REPO_URL} to ${location}`);
4620
+ const notes = ensureLocalHarnessRepo(dryRun);
4621
+ notes.push(ensureSymlink(HARNESS_REPO_PATH, location, dryRun));
4428
4622
  return { location, notes };
4429
4623
  }
4430
4624
  function projectInit(dryRun) {
4431
4625
  const projectRoot = resolveProjectRoot();
4432
4626
  const location = projectInstallPath();
4433
- const notes = [];
4434
- if (fs3.existsSync(location)) {
4435
- notes.push(`Submodule path already exists at ${location}`);
4436
- return { location, notes };
4437
- }
4438
- runCommand(["git", "rev-parse", "--is-inside-work-tree"], projectRoot, dryRun);
4439
- ensureDir(path4.join(projectRoot, ".cursor", "plugins"), dryRun);
4440
- runCommand(["git", "submodule", "add", REPO_URL, ".cursor/plugins/mstar-harness"], projectRoot, dryRun);
4441
- notes.push("Added mstar-harness as git submodule at .cursor/plugins/mstar-harness");
4627
+ const notes = ensureLocalHarnessRepo(dryRun);
4628
+ notes.push(ensureSymlink(HARNESS_REPO_PATH, location, dryRun));
4629
+ notes.push(...appendGitignore(projectRoot, [CURSOR_PLUGIN_LINK], dryRun));
4442
4630
  return { location, notes };
4443
4631
  }
4444
4632
  function globalDoctor() {
4445
4633
  const location = globalInstallPath();
4446
- const errors2 = [];
4447
- if (!fs3.existsSync(location)) {
4448
- errors2.push(`Missing plugin directory: ${location}`);
4449
- return { location, errors: errors2 };
4450
- }
4451
- const marker = path4.join(location, CURSOR_PLUGIN_MARKER);
4452
- if (!fs3.existsSync(marker)) {
4634
+ const errors2 = validateLocalHarnessRepo();
4635
+ errors2.push(...validateSymlink(HARNESS_REPO_PATH, location));
4636
+ const marker = path5.join(location, CURSOR_PLUGIN_MARKER);
4637
+ if (!fs4.existsSync(marker)) {
4453
4638
  errors2.push(`Missing Cursor plugin marker file: ${marker}`);
4454
4639
  }
4640
+ errors2.push(...validatePluginAgents());
4455
4641
  return { location, errors: errors2 };
4456
4642
  }
4457
4643
  function projectDoctor() {
4458
4644
  const projectRoot = resolveProjectRoot();
4459
4645
  const location = projectInstallPath();
4460
- const errors2 = [];
4461
- if (!fs3.existsSync(location)) {
4462
- errors2.push(`Missing submodule directory: ${location}`);
4463
- }
4464
- const gitmodulesPath = path4.join(projectRoot, ".gitmodules");
4465
- if (!fs3.existsSync(gitmodulesPath)) {
4466
- errors2.push("Missing .gitmodules (expected cursor plugin submodule entry).");
4467
- return { location, errors: errors2 };
4646
+ const errors2 = validateLocalHarnessRepo();
4647
+ errors2.push(...validateSymlink(HARNESS_REPO_PATH, location));
4648
+ const marker = path5.join(location, CURSOR_PLUGIN_MARKER);
4649
+ if (!fs4.existsSync(marker)) {
4650
+ errors2.push(`Missing Cursor plugin marker file: ${marker}`);
4468
4651
  }
4469
- const gitmodules = fs3.readFileSync(gitmodulesPath, "utf8");
4470
- if (!gitmodules.includes("path = .cursor/plugins/mstar-harness")) {
4471
- errors2.push("Missing .cursor/plugins/mstar-harness entry in .gitmodules.");
4652
+ const gitignorePath = path5.join(projectRoot, ".gitignore");
4653
+ const gitignore = fs4.existsSync(gitignorePath) ? fs4.readFileSync(gitignorePath, "utf8") : "";
4654
+ if (!gitignore.split(/\r?\n/).includes(CURSOR_PLUGIN_LINK)) {
4655
+ errors2.push(`Missing .gitignore entry: ${CURSOR_PLUGIN_LINK}`);
4472
4656
  }
4657
+ errors2.push(...validatePluginAgents());
4473
4658
  return { location, errors: errors2 };
4474
4659
  }
4475
4660
  var cursorAdapter = {
@@ -4488,8 +4673,8 @@ var cursorAdapter = {
4488
4673
  };
4489
4674
 
4490
4675
  // src/adapters/opencode.ts
4491
- import os3 from "node:os";
4492
- import path5 from "node:path";
4676
+ import os4 from "node:os";
4677
+ import path6 from "node:path";
4493
4678
  import { execFileSync as execFileSync2 } from "node:child_process";
4494
4679
  var OPENCODE_CONFIG_SCHEMA = "https://opencode.ai/config.json";
4495
4680
  var MSTAR_OPENCODE_PLUGIN = "@mstar-harness/opencode@latest";
@@ -4518,11 +4703,11 @@ function getOpencodeModels() {
4518
4703
  function resolveOpencodeConfigPath(scope, outputPath) {
4519
4704
  if (outputPath && outputPath.trim()) {
4520
4705
  const raw = outputPath.trim();
4521
- return path5.isAbsolute(raw) ? raw : path5.join(resolveProjectRoot(), raw);
4706
+ return path6.isAbsolute(raw) ? raw : path6.join(resolveProjectRoot(), raw);
4522
4707
  }
4523
4708
  if (scope === "global")
4524
- return path5.join(os3.homedir(), ".config", "opencode", "opencode.json");
4525
- return path5.join(resolveProjectRoot(), "opencode.json");
4709
+ return path6.join(os4.homedir(), ".config", "opencode", "opencode.json");
4710
+ return path6.join(resolveProjectRoot(), "opencode.json");
4526
4711
  }
4527
4712
  function ensureConfigSchema(config) {
4528
4713
  const next = ensureObject(config);
@@ -4630,17 +4815,17 @@ function getAdapter(target) {
4630
4815
  var SUPPORTED_TARGETS = ["opencode", "cursor", "codex"];
4631
4816
 
4632
4817
  // src/utils.ts
4633
- import fs4 from "node:fs";
4634
- import path6 from "node:path";
4818
+ import fs5 from "node:fs";
4819
+ import path7 from "node:path";
4635
4820
  function parseCsv(raw) {
4636
4821
  if (!raw)
4637
4822
  return;
4638
4823
  return raw.split(",").map((item) => item.trim()).filter(Boolean);
4639
4824
  }
4640
4825
  function readJson2(filePath) {
4641
- if (!fs4.existsSync(filePath))
4826
+ if (!fs5.existsSync(filePath))
4642
4827
  return {};
4643
- const content = fs4.readFileSync(filePath, "utf8").trim();
4828
+ const content = fs5.readFileSync(filePath, "utf8").trim();
4644
4829
  if (!content)
4645
4830
  return {};
4646
4831
  try {
@@ -4650,18 +4835,18 @@ function readJson2(filePath) {
4650
4835
  }
4651
4836
  }
4652
4837
  function writeJson2(filePath, value) {
4653
- const parent = path6.dirname(filePath);
4654
- if (!fs4.existsSync(parent))
4655
- fs4.mkdirSync(parent, { recursive: true });
4656
- fs4.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}
4838
+ const parent = path7.dirname(filePath);
4839
+ if (!fs5.existsSync(parent))
4840
+ fs5.mkdirSync(parent, { recursive: true });
4841
+ fs5.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}
4657
4842
  `, "utf8");
4658
4843
  }
4659
4844
 
4660
4845
  // src/index.ts
4661
- var packageJsonPath = path7.resolve(path7.dirname(fileURLToPath(import.meta.url)), "../package.json");
4846
+ var packageJsonPath = path8.resolve(path8.dirname(fileURLToPath(import.meta.url)), "../package.json");
4662
4847
  var packageVersion = (() => {
4663
4848
  try {
4664
- const parsed = JSON.parse(fs5.readFileSync(packageJsonPath, "utf8"));
4849
+ const parsed = JSON.parse(fs6.readFileSync(packageJsonPath, "utf8"));
4665
4850
  return parsed.version || "0.0.0";
4666
4851
  } catch {
4667
4852
  return "0.0.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mstar-harness/cli",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "Morning Star harness installer CLI (OpenCode, Cursor, Codex).",
5
5
  "license": "MIT",
6
6
  "repository": {