@mstar-harness/cli 1.8.8 → 1.8.9

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 +586 -153
  2. package/package.json +1 -1
@@ -2343,6 +2343,10 @@ var require_commander = __commonJS((exports) => {
2343
2343
  exports.InvalidOptionArgumentError = InvalidArgumentError;
2344
2344
  });
2345
2345
 
2346
+ // src/index.ts
2347
+ import fs9 from "fs";
2348
+ import path11 from "path";
2349
+
2346
2350
  // ../../node_modules/@inquirer/core/dist/lib/key.js
2347
2351
  var isUpKey = (key, keybindings = []) => key.name === "up" || keybindings.includes("vim") && key.name === "k" || keybindings.includes("emacs") && key.ctrl && key.name === "p";
2348
2352
  var isDownKey = (key, keybindings = []) => key.name === "down" || keybindings.includes("vim") && key.name === "j" || keybindings.includes("emacs") && key.ctrl && key.name === "n";
@@ -3969,6 +3973,442 @@ var {
3969
3973
  Help
3970
3974
  } = import__.default;
3971
3975
 
3976
+ // src/agent-plugins.ts
3977
+ import fs2 from "node:fs";
3978
+ import path3 from "node:path";
3979
+
3980
+ // src/utils.ts
3981
+ import fs from "node:fs";
3982
+ import path2 from "node:path";
3983
+ import { fileURLToPath } from "node:url";
3984
+ function ensureObject(value) {
3985
+ if (value && typeof value === "object" && !Array.isArray(value))
3986
+ return value;
3987
+ return {};
3988
+ }
3989
+ function readJson(filePath) {
3990
+ if (!fs.existsSync(filePath))
3991
+ return {};
3992
+ const content = fs.readFileSync(filePath, "utf8").trim();
3993
+ if (!content)
3994
+ return {};
3995
+ try {
3996
+ return JSON.parse(content);
3997
+ } catch (error) {
3998
+ throw new Error(`Invalid JSON in ${filePath}: ${error.message}`);
3999
+ }
4000
+ }
4001
+ function writeJson(filePath, value) {
4002
+ const parent = path2.dirname(filePath);
4003
+ if (!fs.existsSync(parent))
4004
+ fs.mkdirSync(parent, { recursive: true });
4005
+ fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}
4006
+ `, "utf8");
4007
+ }
4008
+ function resolveProjectRoot() {
4009
+ const candidate = process.env.MSTAR_CLI_PROJECT_ROOT || process.env.INIT_CWD || process.env.PWD;
4010
+ if (candidate && candidate.trim())
4011
+ return path2.resolve(candidate);
4012
+ return process.cwd();
4013
+ }
4014
+ function readHarnessVersion() {
4015
+ const packageJsonPath = path2.resolve(path2.dirname(fileURLToPath(import.meta.url)), "../package.json");
4016
+ try {
4017
+ const parsed = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
4018
+ return parsed.version || "0.0.0";
4019
+ } catch {
4020
+ return "0.0.0";
4021
+ }
4022
+ }
4023
+
4024
+ // src/agent-plugins.ts
4025
+ var PLUGIN_SCHEMA_URL = "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json";
4026
+ var MCP_SCHEMA_URL = "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json";
4027
+ var PLUGIN_TOP_LEVEL_FIELDS = {
4028
+ $schema: true,
4029
+ name: true,
4030
+ version: true,
4031
+ description: true,
4032
+ author: true,
4033
+ homepage: true,
4034
+ repository: true,
4035
+ license: true,
4036
+ keywords: true,
4037
+ extensions: true
4038
+ };
4039
+ var PLUGIN_NAME_PATTERN = /^(?!.*(?:--|\.\.))[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/;
4040
+ function stripMcpPathPrefix(raw) {
4041
+ if (raw.startsWith("./"))
4042
+ return raw.slice(2);
4043
+ if (raw.startsWith("${PLUGIN_ROOT}/"))
4044
+ return raw.slice("${PLUGIN_ROOT}/".length);
4045
+ if (raw.startsWith("${PLUGIN_DATA}/"))
4046
+ return raw.slice("${PLUGIN_DATA}/".length);
4047
+ if (raw === "${PLUGIN_ROOT}" || raw === "${PLUGIN_DATA}")
4048
+ return "";
4049
+ return null;
4050
+ }
4051
+ function escapesPluginRoot(remainder) {
4052
+ const normalized = path3.posix.normalize(remainder);
4053
+ return normalized.startsWith("..") || path3.posix.isAbsolute(normalized);
4054
+ }
4055
+ var SKILL_NAME_PATTERN = /^(?!.*--)[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
4056
+ var HTTP_HEADER_NAME_PATTERN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
4057
+ var MCP_SERVER_TYPES = {
4058
+ stdio: true,
4059
+ "streamable-http": true,
4060
+ sse: true
4061
+ };
4062
+ var STDIO_FIELDS = { type: true, command: true, args: true, env: true, cwd: true };
4063
+ var REMOTE_FIELDS = { type: true, url: true, headers: true };
4064
+ var AUTHOR_FIELDS = { name: true, email: true, url: true };
4065
+ function describeType(value) {
4066
+ if (value === null)
4067
+ return "null";
4068
+ if (Array.isArray(value))
4069
+ return "array";
4070
+ return typeof value;
4071
+ }
4072
+ function isPlainObject2(value) {
4073
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4074
+ }
4075
+ function parseScalar(raw) {
4076
+ const trimmed = raw.trim();
4077
+ if (trimmed.length >= 2 && (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'"))) {
4078
+ return trimmed.slice(1, -1);
4079
+ }
4080
+ return trimmed;
4081
+ }
4082
+ function parseFrontmatter(filePath) {
4083
+ const content = fs2.readFileSync(filePath, "utf8");
4084
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);
4085
+ if (!match)
4086
+ return null;
4087
+ const result = {};
4088
+ for (const line of match[1].split(/\r?\n/)) {
4089
+ const field = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
4090
+ if (!field)
4091
+ continue;
4092
+ result[field[1]] = parseScalar(field[2]);
4093
+ }
4094
+ return result;
4095
+ }
4096
+ function isValidMcpUrl(raw) {
4097
+ let parsed;
4098
+ try {
4099
+ parsed = new URL(raw);
4100
+ } catch {
4101
+ return false;
4102
+ }
4103
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
4104
+ return false;
4105
+ if (!parsed.hostname)
4106
+ return false;
4107
+ if (parsed.username || parsed.password || parsed.hash)
4108
+ return false;
4109
+ const host = parsed.hostname;
4110
+ const isLoopback = host === "localhost" || host === "::1" || host === "[::1]" || /^127\.\d+\.\d+\.\d+$/.test(host);
4111
+ if (!isLoopback && parsed.protocol !== "https:")
4112
+ return false;
4113
+ return true;
4114
+ }
4115
+ function validateManifest(manifest, errors2, warnings) {
4116
+ if (!isPlainObject2(manifest)) {
4117
+ errors2.push("plugin.json: manifest must be a JSON object");
4118
+ return;
4119
+ }
4120
+ const doc = manifest;
4121
+ for (const key of Object.keys(doc)) {
4122
+ if (!Object.hasOwn(PLUGIN_TOP_LEVEL_FIELDS, key)) {
4123
+ warnings.push(`plugin.json: unknown top-level field "${key}" (ignored; client-specific data belongs under "extensions")`);
4124
+ }
4125
+ }
4126
+ const schema = doc["$schema"];
4127
+ if (typeof schema !== "string") {
4128
+ errors2.push(`plugin.json: "$schema" is required and must be the string ${PLUGIN_SCHEMA_URL}`);
4129
+ } else if (schema !== PLUGIN_SCHEMA_URL) {
4130
+ errors2.push(`plugin.json: unsupported "$schema" ${JSON.stringify(schema)} (expected ${PLUGIN_SCHEMA_URL})`);
4131
+ }
4132
+ const name = doc.name;
4133
+ if (typeof name !== "string" || name.length === 0) {
4134
+ errors2.push('plugin.json: "name" is required and must be a non-empty string');
4135
+ } else {
4136
+ if (name.length > 64) {
4137
+ errors2.push(`plugin.json: "name" must be 1-64 characters (got ${name.length})`);
4138
+ }
4139
+ if (!PLUGIN_NAME_PATTERN.test(name)) {
4140
+ errors2.push(`plugin.json: "name" ${JSON.stringify(name)} violates Agent Plugins name rules ` + `(lowercase alphanumerics, hyphens, periods; no "--" or ".."; must start and end alphanumeric)`);
4141
+ }
4142
+ }
4143
+ for (const field of ["version", "description", "homepage", "repository", "license"]) {
4144
+ const value = doc[field];
4145
+ if (value === undefined)
4146
+ continue;
4147
+ if (typeof value !== "string") {
4148
+ errors2.push(`plugin.json: "${field}" must be a string (got ${describeType(value)})`);
4149
+ }
4150
+ }
4151
+ if (doc.author !== undefined) {
4152
+ if (!isPlainObject2(doc.author)) {
4153
+ errors2.push('plugin.json: "author" must be an object with optional string fields name/email/url');
4154
+ } else {
4155
+ const author = doc.author;
4156
+ for (const key of Object.keys(author)) {
4157
+ if (!Object.hasOwn(AUTHOR_FIELDS, key)) {
4158
+ errors2.push(`plugin.json: "author" has unknown field "${key}" (only name, email, url are allowed)`);
4159
+ }
4160
+ }
4161
+ for (const key of ["name", "email", "url"]) {
4162
+ const value = author[key];
4163
+ if (value !== undefined && typeof value !== "string") {
4164
+ errors2.push(`plugin.json: "author.${key}" must be a string (got ${describeType(value)})`);
4165
+ }
4166
+ }
4167
+ }
4168
+ }
4169
+ if (doc.keywords !== undefined) {
4170
+ if (!Array.isArray(doc.keywords) || doc.keywords.some((entry) => typeof entry !== "string")) {
4171
+ errors2.push('plugin.json: "keywords" must be an array of strings');
4172
+ }
4173
+ }
4174
+ if (doc.extensions !== undefined) {
4175
+ if (!isPlainObject2(doc.extensions)) {
4176
+ warnings.push('plugin.json: "extensions" is not an object — ignored');
4177
+ } else {
4178
+ for (const [namespace, value] of Object.entries(doc.extensions)) {
4179
+ if (!isPlainObject2(value)) {
4180
+ warnings.push(`plugin.json: "extensions.${namespace}" is not an object — ignored`);
4181
+ }
4182
+ }
4183
+ }
4184
+ }
4185
+ }
4186
+ function validateMcpServer(name, entry, errors2) {
4187
+ const prefix = `mcp.json: mcpServers.${name}`;
4188
+ if (!isPlainObject2(entry)) {
4189
+ errors2.push(`${prefix} must be an object`);
4190
+ return;
4191
+ }
4192
+ const server = entry;
4193
+ const type = server.type;
4194
+ if (typeof type !== "string" || !Object.hasOwn(MCP_SERVER_TYPES, type)) {
4195
+ errors2.push(`${prefix}: "type" must be one of "stdio" | "streamable-http" | "sse" (got ${JSON.stringify(type)})`);
4196
+ return;
4197
+ }
4198
+ if (type === "stdio") {
4199
+ for (const key of Object.keys(server)) {
4200
+ if (!Object.hasOwn(STDIO_FIELDS, key)) {
4201
+ errors2.push(`${prefix}: unknown field "${key}" for stdio server (allowed: type, command, args, env, cwd)`);
4202
+ }
4203
+ }
4204
+ const command = server.command;
4205
+ if (typeof command !== "string" || command.length === 0) {
4206
+ errors2.push(`${prefix}: "command" is required and must be a non-empty string`);
4207
+ } else {
4208
+ if (/\s/.test(command)) {
4209
+ errors2.push(`${prefix}: "command" must be a single executable token, not a shell command string`);
4210
+ } else if (command.includes("/") && !command.startsWith("./")) {
4211
+ errors2.push(`${prefix}: "command" must be a bare executable name or a plugin-relative path beginning with "./"`);
4212
+ } else if (command.startsWith("./") && escapesPluginRoot(command.slice(2))) {
4213
+ errors2.push(`${prefix}: "command" must remain within the plugin root (got "${command}")`);
4214
+ }
4215
+ }
4216
+ if (server.args !== undefined) {
4217
+ if (!Array.isArray(server.args) || server.args.some((arg) => typeof arg !== "string")) {
4218
+ errors2.push(`${prefix}: "args" must be an array of strings`);
4219
+ }
4220
+ }
4221
+ if (server.env !== undefined) {
4222
+ if (!isPlainObject2(server.env)) {
4223
+ errors2.push(`${prefix}: "env" must be an object of strings`);
4224
+ } else {
4225
+ for (const [key, value] of Object.entries(server.env)) {
4226
+ if (key === "PLUGIN_ROOT" || key === "PLUGIN_DATA") {
4227
+ errors2.push(`${prefix}: "env" must not set reserved variable "${key}" (clients supply it themselves)`);
4228
+ }
4229
+ if (typeof value !== "string") {
4230
+ errors2.push(`${prefix}: "env.${key}" must be a string`);
4231
+ }
4232
+ }
4233
+ }
4234
+ }
4235
+ if (server.cwd !== undefined) {
4236
+ if (typeof server.cwd !== "string") {
4237
+ errors2.push(`${prefix}: "cwd" must be a string`);
4238
+ } else {
4239
+ const remainder = stripMcpPathPrefix(server.cwd);
4240
+ if (remainder === null) {
4241
+ errors2.push(`${prefix}: "cwd" must be "./…", "${"${PLUGIN_ROOT}"}…", or "${"${PLUGIN_DATA}"}…"`);
4242
+ } else if (escapesPluginRoot(remainder)) {
4243
+ errors2.push(`${prefix}: "cwd" must remain within the plugin root (got "${server.cwd}")`);
4244
+ }
4245
+ }
4246
+ }
4247
+ return;
4248
+ }
4249
+ for (const key of Object.keys(server)) {
4250
+ if (!Object.hasOwn(REMOTE_FIELDS, key)) {
4251
+ errors2.push(`${prefix}: unknown field "${key}" for ${type} server (allowed: type, url, headers)`);
4252
+ }
4253
+ }
4254
+ const url = server.url;
4255
+ if (typeof url !== "string" || url.length === 0) {
4256
+ errors2.push(`${prefix}: "url" is required and must be a non-empty string`);
4257
+ } else if (!isValidMcpUrl(url)) {
4258
+ errors2.push(`${prefix}: "url" must be an absolute http(s) URL without user info or fragment; non-loopback endpoints must use https`);
4259
+ }
4260
+ if (server.headers !== undefined) {
4261
+ if (!isPlainObject2(server.headers)) {
4262
+ errors2.push(`${prefix}: "headers" must be an object of strings`);
4263
+ } else {
4264
+ const seen = new Set;
4265
+ for (const [key, value] of Object.entries(server.headers)) {
4266
+ if (typeof value !== "string") {
4267
+ errors2.push(`${prefix}: "headers.${key}" must be a string`);
4268
+ continue;
4269
+ }
4270
+ if (value.includes("\r") || value.includes(`
4271
+ `)) {
4272
+ errors2.push(`${prefix}: "headers.${key}" value must be a single HTTP header value`);
4273
+ }
4274
+ if (!HTTP_HEADER_NAME_PATTERN.test(key)) {
4275
+ errors2.push(`${prefix}: "headers.${key}" is not a valid HTTP header name`);
4276
+ } else {
4277
+ const lower = key.toLowerCase();
4278
+ if (seen.has(lower)) {
4279
+ errors2.push(`${prefix}: header "${key}" is duplicated (case-insensitive)`);
4280
+ }
4281
+ seen.add(lower);
4282
+ }
4283
+ }
4284
+ }
4285
+ }
4286
+ }
4287
+ function validateMcp(root, manifestSchema, errors2) {
4288
+ const mcpPath = path3.join(root, "mcp.json");
4289
+ if (!fs2.existsSync(mcpPath))
4290
+ return;
4291
+ let parsed;
4292
+ try {
4293
+ parsed = readJson(mcpPath);
4294
+ } catch (error) {
4295
+ errors2.push(`mcp.json: ${error.message}`);
4296
+ return;
4297
+ }
4298
+ if (!isPlainObject2(parsed)) {
4299
+ errors2.push("mcp.json: configuration must be a JSON object");
4300
+ return;
4301
+ }
4302
+ const doc = parsed;
4303
+ for (const key of Object.keys(doc)) {
4304
+ if (key !== "$schema" && key !== "mcpServers") {
4305
+ errors2.push(`mcp.json: unknown top-level field "${key}" (only "$schema" and "mcpServers" allowed)`);
4306
+ }
4307
+ }
4308
+ const schema = doc["$schema"];
4309
+ if (typeof schema !== "string") {
4310
+ errors2.push(`mcp.json: "$schema" is required and must be the string ${MCP_SCHEMA_URL}`);
4311
+ } else if (schema !== MCP_SCHEMA_URL) {
4312
+ errors2.push(`mcp.json: unsupported "$schema" ${JSON.stringify(schema)} (expected ${MCP_SCHEMA_URL})`);
4313
+ } else {
4314
+ const manifestVersion = typeof manifestSchema === "string" ? manifestSchema.match(/^https:\/\/agent-plugins\.org\/schemas\/([^/]+)\/plugin\.schema\.json$/)?.[1] : undefined;
4315
+ const mcpVersion = schema.match(/^https:\/\/agent-plugins\.org\/schemas\/([^/]+)\/mcp\.schema\.json$/)?.[1];
4316
+ if (manifestVersion && mcpVersion && manifestVersion !== mcpVersion) {
4317
+ errors2.push(`mcp.json: "$schema" targets Agent Plugins ${mcpVersion} but plugin.json targets ${manifestVersion} (versions must match)`);
4318
+ }
4319
+ }
4320
+ const servers = doc.mcpServers;
4321
+ if (!isPlainObject2(servers)) {
4322
+ errors2.push('mcp.json: "mcpServers" is required and must be an object');
4323
+ return;
4324
+ }
4325
+ for (const [serverName, entry] of Object.entries(servers)) {
4326
+ validateMcpServer(serverName, entry, errors2);
4327
+ }
4328
+ }
4329
+ function validateSkills(root, errors2, warnings) {
4330
+ const skillsPath = path3.join(root, "skills");
4331
+ try {
4332
+ if (!fs2.existsSync(skillsPath))
4333
+ return;
4334
+ if (!fs2.statSync(skillsPath).isDirectory()) {
4335
+ errors2.push("skills: skills/ is not a directory (component type invalid)");
4336
+ return;
4337
+ }
4338
+ const entries = fs2.readdirSync(skillsPath, { withFileTypes: true });
4339
+ const realRoot = fs2.realpathSync(root);
4340
+ for (const entry of entries) {
4341
+ if (!entry.isDirectory() && !entry.isSymbolicLink())
4342
+ continue;
4343
+ const skillDir = entry.name;
4344
+ let realSkillPath;
4345
+ try {
4346
+ realSkillPath = fs2.realpathSync(path3.join(skillsPath, skillDir));
4347
+ } catch (error) {
4348
+ warnings.push(`skills: ${skillDir}/ cannot be resolved (${error.message}; skill skipped)`);
4349
+ continue;
4350
+ }
4351
+ const relative = path3.relative(realRoot, realSkillPath);
4352
+ if (relative === ".." || relative.startsWith(`..${path3.sep}`) || path3.isAbsolute(relative)) {
4353
+ warnings.push(`skills: ${skillDir}/ resolves outside the plugin root (${realSkillPath}; skill skipped)`);
4354
+ continue;
4355
+ }
4356
+ const skillMdPath = path3.join(skillsPath, skillDir, "SKILL.md");
4357
+ if (!fs2.existsSync(skillMdPath) || !fs2.statSync(skillMdPath).isFile()) {
4358
+ warnings.push(`skills: ${skillDir}/ has no SKILL.md (directory is not a skill; ignored)`);
4359
+ continue;
4360
+ }
4361
+ const frontmatter = parseFrontmatter(skillMdPath);
4362
+ if (!frontmatter) {
4363
+ warnings.push(`skills: ${skillDir}/SKILL.md is missing YAML frontmatter (name and description are required; skill skipped)`);
4364
+ continue;
4365
+ }
4366
+ const skillName = frontmatter.name;
4367
+ const problems = [];
4368
+ if (skillName !== skillDir) {
4369
+ problems.push(`frontmatter "name" ${JSON.stringify(skillName)} must equal the directory name "${skillDir}"`);
4370
+ } else if (!SKILL_NAME_PATTERN.test(skillName)) {
4371
+ problems.push(`frontmatter "name" violates Agent Skills name rules ` + `(lowercase alphanumerics and hyphens, no "--", no leading or trailing hyphen)`);
4372
+ }
4373
+ if (typeof skillName === "string" && skillName.length > 64) {
4374
+ problems.push(`frontmatter "name" must be at most 64 characters (got ${skillName.length})`);
4375
+ }
4376
+ const description = frontmatter.description;
4377
+ if (typeof description !== "string" || description.trim().length === 0) {
4378
+ problems.push(`frontmatter "description" is required and must be non-empty`);
4379
+ } else if (description.length > 1024) {
4380
+ problems.push(`frontmatter "description" must be at most 1024 characters (got ${description.length})`);
4381
+ }
4382
+ for (const problem of problems) {
4383
+ warnings.push(`skills: ${skillDir}/SKILL.md ${problem} (skill skipped)`);
4384
+ }
4385
+ }
4386
+ } catch (error) {
4387
+ errors2.push(`skills: ${error.message}`);
4388
+ }
4389
+ }
4390
+ function validateAgentPlugin(root) {
4391
+ const errors2 = [];
4392
+ const warnings = [];
4393
+ const manifestPath = path3.join(root, "plugin.json");
4394
+ if (!fs2.existsSync(manifestPath)) {
4395
+ errors2.push(`plugin.json: manifest not found at ${manifestPath} (plugin root must contain plugin.json)`);
4396
+ return { ok: false, errors: errors2, warnings };
4397
+ }
4398
+ let manifest;
4399
+ try {
4400
+ manifest = readJson(manifestPath);
4401
+ } catch (error) {
4402
+ errors2.push(`plugin.json: ${error.message}`);
4403
+ return { ok: false, errors: errors2, warnings };
4404
+ }
4405
+ validateManifest(manifest, errors2, warnings);
4406
+ const manifestSchema = isPlainObject2(manifest) ? manifest["$schema"] : undefined;
4407
+ validateMcp(root, manifestSchema, errors2);
4408
+ validateSkills(root, errors2, warnings);
4409
+ return { ok: errors2.length === 0, errors: errors2, warnings };
4410
+ }
4411
+
3972
4412
  // src/constants.ts
3973
4413
  var ALL_ROLES = [
3974
4414
  "project-manager",
@@ -4019,62 +4459,18 @@ function buildModelAssignments(selections) {
4019
4459
  }
4020
4460
 
4021
4461
  // src/adapters/codex.ts
4022
- import fs3 from "node:fs";
4462
+ import fs4 from "node:fs";
4023
4463
  import os2 from "node:os";
4024
- import path4 from "node:path";
4025
-
4026
- // src/utils.ts
4027
- import fs from "node:fs";
4028
- import path2 from "node:path";
4029
- import { fileURLToPath } from "node:url";
4030
- function ensureObject(value) {
4031
- if (value && typeof value === "object" && !Array.isArray(value))
4032
- return value;
4033
- return {};
4034
- }
4035
- function readJson(filePath) {
4036
- if (!fs.existsSync(filePath))
4037
- return {};
4038
- const content = fs.readFileSync(filePath, "utf8").trim();
4039
- if (!content)
4040
- return {};
4041
- try {
4042
- return JSON.parse(content);
4043
- } catch (error) {
4044
- throw new Error(`Invalid JSON in ${filePath}: ${error.message}`);
4045
- }
4046
- }
4047
- function writeJson(filePath, value) {
4048
- const parent = path2.dirname(filePath);
4049
- if (!fs.existsSync(parent))
4050
- fs.mkdirSync(parent, { recursive: true });
4051
- fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}
4052
- `, "utf8");
4053
- }
4054
- function resolveProjectRoot() {
4055
- const candidate = process.env.MSTAR_CLI_PROJECT_ROOT || process.env.INIT_CWD || process.env.PWD;
4056
- if (candidate && candidate.trim())
4057
- return path2.resolve(candidate);
4058
- return process.cwd();
4059
- }
4060
- function readHarnessVersion() {
4061
- const packageJsonPath = path2.resolve(path2.dirname(fileURLToPath(import.meta.url)), "../package.json");
4062
- try {
4063
- const parsed = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
4064
- return parsed.version || "0.0.0";
4065
- } catch {
4066
- return "0.0.0";
4067
- }
4068
- }
4464
+ import path5 from "node:path";
4069
4465
 
4070
4466
  // src/adapters/shared-install.ts
4071
- import fs2 from "node:fs";
4467
+ import fs3 from "node:fs";
4072
4468
  import os from "node:os";
4073
- import path3 from "node:path";
4469
+ import path4 from "node:path";
4074
4470
  import { execFileSync } from "node:child_process";
4075
4471
  var REPO_URL = "https://github.com/btspoony/mstar-harness.git";
4076
4472
  var PLUGIN_NAME = "morning-star-harness";
4077
- var HARNESS_REPO_PATH = path3.join(os.homedir(), ".mstar", "harness");
4473
+ var HARNESS_REPO_PATH = path4.join(os.homedir(), ".mstar", "harness");
4078
4474
  var HARNESS_MARKERS = [
4079
4475
  ".codex-plugin/plugin.json",
4080
4476
  ".zcode-plugin/plugin.json",
@@ -4082,15 +4478,15 @@ var HARNESS_MARKERS = [
4082
4478
  ];
4083
4479
  function harnessMarkerPath() {
4084
4480
  for (const marker of HARNESS_MARKERS) {
4085
- const candidate = path3.join(HARNESS_REPO_PATH, marker);
4086
- if (fs2.existsSync(candidate))
4481
+ const candidate = path4.join(HARNESS_REPO_PATH, marker);
4482
+ if (fs3.existsSync(candidate))
4087
4483
  return candidate;
4088
4484
  }
4089
- return path3.join(HARNESS_REPO_PATH, HARNESS_MARKERS[0]);
4485
+ return path4.join(HARNESS_REPO_PATH, HARNESS_MARKERS[0]);
4090
4486
  }
4091
4487
  function pathOrSymlinkExists(filePath) {
4092
4488
  try {
4093
- fs2.lstatSync(filePath);
4489
+ fs3.lstatSync(filePath);
4094
4490
  return true;
4095
4491
  } catch {
4096
4492
  return false;
@@ -4099,8 +4495,8 @@ function pathOrSymlinkExists(filePath) {
4099
4495
  function ensureDir(dirPath, dryRun) {
4100
4496
  if (dryRun)
4101
4497
  return;
4102
- if (!fs2.existsSync(dirPath))
4103
- fs2.mkdirSync(dirPath, { recursive: true });
4498
+ if (!fs3.existsSync(dirPath))
4499
+ fs3.mkdirSync(dirPath, { recursive: true });
4104
4500
  }
4105
4501
  function runCommand(command, cwd, dryRun) {
4106
4502
  if (dryRun)
@@ -4109,7 +4505,7 @@ function runCommand(command, cwd, dryRun) {
4109
4505
  }
4110
4506
  function ensureLocalHarnessRepo(dryRun) {
4111
4507
  const notes = [];
4112
- if (fs2.existsSync(HARNESS_REPO_PATH)) {
4508
+ if (fs3.existsSync(HARNESS_REPO_PATH)) {
4113
4509
  const errors2 = validateLocalHarnessRepo();
4114
4510
  if (errors2.length) {
4115
4511
  throw new Error(errors2.join(`
@@ -4118,34 +4514,34 @@ function ensureLocalHarnessRepo(dryRun) {
4118
4514
  notes.push(`Using existing local harness repo at ${HARNESS_REPO_PATH}`);
4119
4515
  return notes;
4120
4516
  }
4121
- ensureDir(path3.dirname(HARNESS_REPO_PATH), dryRun);
4122
- runCommand(["git", "clone", REPO_URL, HARNESS_REPO_PATH], path3.dirname(HARNESS_REPO_PATH), dryRun);
4517
+ ensureDir(path4.dirname(HARNESS_REPO_PATH), dryRun);
4518
+ runCommand(["git", "clone", REPO_URL, HARNESS_REPO_PATH], path4.dirname(HARNESS_REPO_PATH), dryRun);
4123
4519
  notes.push(`Cloned ${REPO_URL} to ${HARNESS_REPO_PATH}`);
4124
4520
  return notes;
4125
4521
  }
4126
4522
  function validateLocalHarnessRepo() {
4127
4523
  const errors2 = [];
4128
- if (!fs2.existsSync(HARNESS_REPO_PATH)) {
4524
+ if (!fs3.existsSync(HARNESS_REPO_PATH)) {
4129
4525
  errors2.push(`Missing local harness repo: ${HARNESS_REPO_PATH}`);
4130
4526
  return errors2;
4131
4527
  }
4132
4528
  const marker = harnessMarkerPath();
4133
- if (!fs2.existsSync(marker)) {
4529
+ if (!fs3.existsSync(marker)) {
4134
4530
  errors2.push(`Local harness repo is missing a plugin marker (expected one of: ${HARNESS_MARKERS.join(", ")}).`);
4135
4531
  }
4136
4532
  return errors2;
4137
4533
  }
4138
4534
  function ensureGitCheckout(repoUrl, checkoutPath, dryRun) {
4139
4535
  const notes = [];
4140
- const parentDir = path3.dirname(checkoutPath);
4536
+ const parentDir = path4.dirname(checkoutPath);
4141
4537
  if (pathOrSymlinkExists(checkoutPath)) {
4142
- const stat = fs2.lstatSync(checkoutPath);
4538
+ const stat = fs3.lstatSync(checkoutPath);
4143
4539
  if (stat.isSymbolicLink()) {
4144
4540
  notes.push(dryRun ? `Would remove symlink at ${checkoutPath} and clone ${repoUrl}` : `Removed symlink at ${checkoutPath} (Cursor requires a real directory)`);
4145
4541
  if (dryRun)
4146
4542
  return notes;
4147
- fs2.unlinkSync(checkoutPath);
4148
- } else if (fs2.existsSync(path3.join(checkoutPath, ".git"))) {
4543
+ fs3.unlinkSync(checkoutPath);
4544
+ } else if (fs3.existsSync(path4.join(checkoutPath, ".git"))) {
4149
4545
  if (!dryRun) {
4150
4546
  runCommand(["git", "-C", checkoutPath, "pull", "--ff-only"], checkoutPath, dryRun);
4151
4547
  }
@@ -4168,39 +4564,39 @@ function validateGitCheckout(checkoutPath, markerRelativePath) {
4168
4564
  errors2.push(`Missing checkout directory: ${checkoutPath}`);
4169
4565
  return errors2;
4170
4566
  }
4171
- const stat = fs2.lstatSync(checkoutPath);
4567
+ const stat = fs3.lstatSync(checkoutPath);
4172
4568
  if (stat.isSymbolicLink()) {
4173
4569
  errors2.push(`Path must be a real directory, not a symlink: ${checkoutPath}. Run: mstar-harness init --target cursor`);
4174
4570
  return errors2;
4175
4571
  }
4176
- if (!fs2.existsSync(path3.join(checkoutPath, ".git"))) {
4572
+ if (!fs3.existsSync(path4.join(checkoutPath, ".git"))) {
4177
4573
  errors2.push(`Path is not a git checkout: ${checkoutPath}`);
4178
4574
  }
4179
- const marker = path3.join(checkoutPath, markerRelativePath);
4180
- if (!fs2.existsSync(marker)) {
4575
+ const marker = path4.join(checkoutPath, markerRelativePath);
4576
+ if (!fs3.existsSync(marker)) {
4181
4577
  errors2.push(`Missing marker file: ${marker}`);
4182
4578
  }
4183
4579
  return errors2;
4184
4580
  }
4185
4581
  function ensureSymlink(target, linkPath, dryRun) {
4186
4582
  if (pathOrSymlinkExists(linkPath)) {
4187
- const stat = fs2.lstatSync(linkPath);
4583
+ const stat = fs3.lstatSync(linkPath);
4188
4584
  if (!stat.isSymbolicLink()) {
4189
4585
  throw new Error(`Path exists and is not a symlink: ${linkPath}`);
4190
4586
  }
4191
- if (!fs2.existsSync(target)) {
4587
+ if (!fs3.existsSync(target)) {
4192
4588
  throw new Error(`Symlink target is missing: ${target}`);
4193
4589
  }
4194
- const actual = fs2.realpathSync(linkPath);
4195
- const expected = fs2.realpathSync(target);
4590
+ const actual = fs3.realpathSync(linkPath);
4591
+ const expected = fs3.realpathSync(target);
4196
4592
  if (actual !== expected) {
4197
4593
  throw new Error(`Symlink ${linkPath} points to ${actual}, expected ${expected}`);
4198
4594
  }
4199
4595
  return `Symlink already exists: ${linkPath} -> ${target}`;
4200
4596
  }
4201
- ensureDir(path3.dirname(linkPath), dryRun);
4597
+ ensureDir(path4.dirname(linkPath), dryRun);
4202
4598
  if (!dryRun)
4203
- fs2.symlinkSync(target, linkPath);
4599
+ fs3.symlinkSync(target, linkPath);
4204
4600
  return `Linked ${linkPath} -> ${target}`;
4205
4601
  }
4206
4602
  function validateSymlink(target, linkPath) {
@@ -4209,17 +4605,17 @@ function validateSymlink(target, linkPath) {
4209
4605
  errors2.push(`Missing symlink: ${linkPath}`);
4210
4606
  return errors2;
4211
4607
  }
4212
- const stat = fs2.lstatSync(linkPath);
4608
+ const stat = fs3.lstatSync(linkPath);
4213
4609
  if (!stat.isSymbolicLink()) {
4214
4610
  errors2.push(`Path exists but is not a symlink: ${linkPath}`);
4215
4611
  return errors2;
4216
4612
  }
4217
- if (!fs2.existsSync(target)) {
4613
+ if (!fs3.existsSync(target)) {
4218
4614
  errors2.push(`Symlink target is missing: ${target}`);
4219
4615
  return errors2;
4220
4616
  }
4221
- const actual = fs2.realpathSync(linkPath);
4222
- const expected = fs2.realpathSync(target);
4617
+ const actual = fs3.realpathSync(linkPath);
4618
+ const expected = fs3.realpathSync(target);
4223
4619
  if (actual !== expected) {
4224
4620
  errors2.push(`Symlink ${linkPath} points to ${actual}, expected ${expected}`);
4225
4621
  }
@@ -4244,8 +4640,8 @@ function missingHarnessProcessGitignoreEntries(gitignoreContent) {
4244
4640
  return HARNESS_PROCESS_GITIGNORE.filter((entry) => !lines.includes(entry));
4245
4641
  }
4246
4642
  function appendGitignore(projectRoot, entries, dryRun) {
4247
- const gitignorePath = path3.join(projectRoot, ".gitignore");
4248
- const current = fs2.existsSync(gitignorePath) ? fs2.readFileSync(gitignorePath, "utf8") : "";
4643
+ const gitignorePath = path4.join(projectRoot, ".gitignore");
4644
+ const current = fs3.existsSync(gitignorePath) ? fs3.readFileSync(gitignorePath, "utf8") : "";
4249
4645
  const lines = new Set(current.split(/\r?\n/).map((line) => line.trim()));
4250
4646
  const missing = entries.filter((entry) => !lines.has(entry));
4251
4647
  if (!missing.length)
@@ -4254,7 +4650,7 @@ function appendGitignore(projectRoot, entries, dryRun) {
4254
4650
  const prefix = current && !current.endsWith(`
4255
4651
  `) ? `
4256
4652
  ` : "";
4257
- fs2.appendFileSync(gitignorePath, `${prefix}${missing.join(`
4653
+ fs3.appendFileSync(gitignorePath, `${prefix}${missing.join(`
4258
4654
  `)}
4259
4655
  `, "utf8");
4260
4656
  }
@@ -4264,14 +4660,14 @@ function appendHarnessProjectGitignore(projectRoot, dryRun) {
4264
4660
  return appendGitignore(projectRoot, HARNESS_PROCESS_GITIGNORE, dryRun);
4265
4661
  }
4266
4662
  function homeRelativeSourcePath(targetPath) {
4267
- const rel = path3.relative(os.homedir(), targetPath).split(path3.sep).join("/");
4663
+ const rel = path4.relative(os.homedir(), targetPath).split(path4.sep).join("/");
4268
4664
  return rel.startsWith("..") ? targetPath : `./${rel}`;
4269
4665
  }
4270
4666
 
4271
4667
  // src/adapters/codex.ts
4272
4668
  var MARKETPLACE_NAME = "personal";
4273
4669
  var MARKETPLACE_DISPLAY_NAME = "Personal";
4274
- var GLOBAL_MARKETPLACE_PATH = path4.join(os2.homedir(), ".agents", "plugins", "marketplace.json");
4670
+ var GLOBAL_MARKETPLACE_PATH = path5.join(os2.homedir(), ".agents", "plugins", "marketplace.json");
4275
4671
  var PLUGIN_CATEGORY = "Productivity";
4276
4672
  var CODEX_PLUGIN_LINK = ".codex/plugins/mstar-harness";
4277
4673
  var CODEX_AGENT_NAMES = [
@@ -4299,16 +4695,16 @@ function globalMarketplacePath() {
4299
4695
  return GLOBAL_MARKETPLACE_PATH;
4300
4696
  }
4301
4697
  function projectMarketplacePath() {
4302
- return path4.join(resolveProjectRoot(), ".agents", "plugins", "marketplace.json");
4698
+ return path5.join(resolveProjectRoot(), ".agents", "plugins", "marketplace.json");
4303
4699
  }
4304
4700
  function agentSourcePath(agentName) {
4305
- return path4.join(HARNESS_REPO_PATH, "codex", "agents", `${agentName}.toml`);
4701
+ return path5.join(HARNESS_REPO_PATH, "codex", "agents", `${agentName}.toml`);
4306
4702
  }
4307
4703
  function globalAgentLinkPath(agentName) {
4308
- return path4.join(os2.homedir(), ".codex", "agents", `${agentName}.toml`);
4704
+ return path5.join(os2.homedir(), ".codex", "agents", `${agentName}.toml`);
4309
4705
  }
4310
4706
  function projectAgentLinkPath(agentName) {
4311
- return path4.join(resolveProjectRoot(), ".codex", "agents", `${agentName}.toml`);
4707
+ return path5.join(resolveProjectRoot(), ".codex", "agents", `${agentName}.toml`);
4312
4708
  }
4313
4709
  function mstarEntry(scope) {
4314
4710
  const sourcePath = scope === "global" ? homeRelativeSourcePath(HARNESS_REPO_PATH) : `./${CODEX_PLUGIN_LINK}`;
@@ -4399,10 +4795,10 @@ function validateAgentLinks(scope) {
4399
4795
  return errors2;
4400
4796
  }
4401
4797
  function iterationCommandSourcePath(skillName) {
4402
- return path4.join(HARNESS_REPO_PATH, "commands", `${skillName}.md`);
4798
+ return path5.join(HARNESS_REPO_PATH, "commands", `${skillName}.md`);
4403
4799
  }
4404
4800
  function projectIterationSkillLinkPath(skillName) {
4405
- return path4.join(resolveProjectRoot(), ".agents", "skills", skillName, "SKILL.md");
4801
+ return path5.join(resolveProjectRoot(), ".agents", "skills", skillName, "SKILL.md");
4406
4802
  }
4407
4803
  function iterationSkillGitignoreEntry(skillName) {
4408
4804
  return `.agents/skills/${skillName}`;
@@ -4423,8 +4819,8 @@ function ensureIterationSkillLinks(dryRun) {
4423
4819
  function validateIterationSkillLinks() {
4424
4820
  const errors2 = [];
4425
4821
  const projectRoot = resolveProjectRoot();
4426
- const gitignorePath = path4.join(projectRoot, ".gitignore");
4427
- const gitignore = fs3.existsSync(gitignorePath) ? fs3.readFileSync(gitignorePath, "utf8") : "";
4822
+ const gitignorePath = path5.join(projectRoot, ".gitignore");
4823
+ const gitignore = fs4.existsSync(gitignorePath) ? fs4.readFileSync(gitignorePath, "utf8") : "";
4428
4824
  const lines = gitignore.split(/\r?\n/);
4429
4825
  for (const skillName of CODEX_PROJECT_COMMAND_NAMES) {
4430
4826
  const source = iterationCommandSourcePath(skillName);
@@ -4444,7 +4840,7 @@ function runInit(scope, dryRun) {
4444
4840
  const notes = ensureLocalHarnessRepo(dryRun);
4445
4841
  if (scope === "project") {
4446
4842
  const projectRoot = resolveProjectRoot();
4447
- notes.push(ensureSymlink(HARNESS_REPO_PATH, path4.join(projectRoot, CODEX_PLUGIN_LINK), dryRun));
4843
+ notes.push(ensureSymlink(HARNESS_REPO_PATH, path5.join(projectRoot, CODEX_PLUGIN_LINK), dryRun));
4448
4844
  notes.push(...appendGitignore(projectRoot, [CODEX_PLUGIN_LINK, ".codex/agents/*.toml"], dryRun));
4449
4845
  notes.push(...appendHarnessProjectGitignore(projectRoot, dryRun));
4450
4846
  notes.push(...ensureIterationSkillLinks(dryRun));
@@ -4465,7 +4861,7 @@ function runInit(scope, dryRun) {
4465
4861
  function runDoctor(scope) {
4466
4862
  const pathToMarketplace = marketplacePath(scope);
4467
4863
  const errors2 = validateLocalHarnessRepo();
4468
- if (!fs3.existsSync(pathToMarketplace)) {
4864
+ if (!fs4.existsSync(pathToMarketplace)) {
4469
4865
  return { location: pathToMarketplace, errors: [...errors2, `Missing Codex marketplace: ${pathToMarketplace}`] };
4470
4866
  }
4471
4867
  const marketplace = readJson(pathToMarketplace);
@@ -4475,9 +4871,9 @@ function runDoctor(scope) {
4475
4871
  errors2.push(...validateEntryShape(findEntry(marketplace), scope, pathToMarketplace));
4476
4872
  if (scope === "project") {
4477
4873
  const projectRoot = resolveProjectRoot();
4478
- errors2.push(...validateSymlink(HARNESS_REPO_PATH, path4.join(projectRoot, CODEX_PLUGIN_LINK)));
4479
- const gitignorePath = path4.join(projectRoot, ".gitignore");
4480
- const gitignore = fs3.existsSync(gitignorePath) ? fs3.readFileSync(gitignorePath, "utf8") : "";
4874
+ errors2.push(...validateSymlink(HARNESS_REPO_PATH, path5.join(projectRoot, CODEX_PLUGIN_LINK)));
4875
+ const gitignorePath = path5.join(projectRoot, ".gitignore");
4876
+ const gitignore = fs4.existsSync(gitignorePath) ? fs4.readFileSync(gitignorePath, "utf8") : "";
4481
4877
  const lines = gitignore.split(/\r?\n/);
4482
4878
  if (!lines.includes(CODEX_PLUGIN_LINK))
4483
4879
  errors2.push(`Missing .gitignore entry: ${CODEX_PLUGIN_LINK}`);
@@ -4499,33 +4895,33 @@ var codexAdapter = {
4499
4895
  };
4500
4896
 
4501
4897
  // src/adapters/cursor.ts
4502
- import fs4 from "node:fs";
4898
+ import fs5 from "node:fs";
4503
4899
  import os3 from "node:os";
4504
- import path5 from "node:path";
4900
+ import path6 from "node:path";
4505
4901
  var CURSOR_PLUGIN_NAME = "morning-star-harness";
4506
4902
  var CURSOR_PLUGIN_MARKER = ".cursor-plugin/plugin.json";
4507
4903
  var CURSOR_PLUGIN_LINK = ".cursor/plugins/morning-star-harness";
4508
4904
  var CURSOR_AGENT_SMOKE_NAMES = ["fullstack-dev", "qc-specialist"];
4509
4905
  function globalInstallPath() {
4510
- return path5.join(os3.homedir(), ".cursor", "plugins", "local", CURSOR_PLUGIN_NAME);
4906
+ return path6.join(os3.homedir(), ".cursor", "plugins", "local", CURSOR_PLUGIN_NAME);
4511
4907
  }
4512
4908
  function projectInstallPath() {
4513
- return path5.join(resolveProjectRoot(), CURSOR_PLUGIN_LINK);
4909
+ return path6.join(resolveProjectRoot(), CURSOR_PLUGIN_LINK);
4514
4910
  }
4515
4911
  function validatePluginAgents(pluginRoot) {
4516
4912
  const errors2 = [];
4517
- const agentsDir = path5.join(pluginRoot, "agents");
4518
- if (!fs4.existsSync(agentsDir)) {
4913
+ const agentsDir = path6.join(pluginRoot, "agents");
4914
+ if (!fs5.existsSync(agentsDir)) {
4519
4915
  errors2.push(`Missing plugin agents directory: ${agentsDir}`);
4520
4916
  return errors2;
4521
4917
  }
4522
4918
  for (const agentName of CURSOR_AGENT_SMOKE_NAMES) {
4523
- const agentPath = path5.join(agentsDir, `${agentName}.md`);
4524
- if (!fs4.existsSync(agentPath)) {
4919
+ const agentPath = path6.join(agentsDir, `${agentName}.md`);
4920
+ if (!fs5.existsSync(agentPath)) {
4525
4921
  errors2.push(`Missing plugin agent file: ${agentPath}`);
4526
4922
  continue;
4527
4923
  }
4528
- const content = fs4.readFileSync(agentPath, "utf8");
4924
+ const content = fs5.readFileSync(agentPath, "utf8");
4529
4925
  if (!/^---\nname:\s/m.test(content)) {
4530
4926
  errors2.push(`Plugin agent ${agentName}.md must use Cursor-first frontmatter (name, description, model before OpenCode fields).`);
4531
4927
  }
@@ -4562,8 +4958,8 @@ function projectDoctor() {
4562
4958
  const location = projectInstallPath();
4563
4959
  const errors2 = validateLocalHarnessRepo();
4564
4960
  errors2.push(...validateGitCheckout(location, CURSOR_PLUGIN_MARKER));
4565
- const gitignorePath = path5.join(projectRoot, ".gitignore");
4566
- const gitignore = fs4.existsSync(gitignorePath) ? fs4.readFileSync(gitignorePath, "utf8") : "";
4961
+ const gitignorePath = path6.join(projectRoot, ".gitignore");
4962
+ const gitignore = fs5.existsSync(gitignorePath) ? fs5.readFileSync(gitignorePath, "utf8") : "";
4567
4963
  if (!gitignore.split(/\r?\n/).includes(CURSOR_PLUGIN_LINK)) {
4568
4964
  errors2.push(`Missing .gitignore entry: ${CURSOR_PLUGIN_LINK}`);
4569
4965
  }
@@ -4589,8 +4985,8 @@ var cursorAdapter = {
4589
4985
  };
4590
4986
 
4591
4987
  // src/adapters/omp.ts
4592
- import fs5 from "node:fs";
4593
- import path6 from "node:path";
4988
+ import fs6 from "node:fs";
4989
+ import path7 from "node:path";
4594
4990
  import { execFileSync as execFileSync2 } from "node:child_process";
4595
4991
  var OMP_PLUGIN_MARKER = ".omp-plugin/plugin.json";
4596
4992
  var CLAUDE_PLUGIN_MARKER = ".claude-plugin/plugin.json";
@@ -4652,7 +5048,7 @@ function findInstalledPlugin(plugins) {
4652
5048
  return true;
4653
5049
  if (name.includes("morning-star") || manifestName.includes("morning-star"))
4654
5050
  return true;
4655
- if (pathValue.includes("mstar-harness") || pathValue.includes(`${path6.sep}morning-star`))
5051
+ if (pathValue.includes("mstar-harness") || pathValue.includes(`${path7.sep}morning-star`))
4656
5052
  return true;
4657
5053
  return false;
4658
5054
  });
@@ -4660,30 +5056,30 @@ function findInstalledPlugin(plugins) {
4660
5056
  function validatePluginTree(pluginRoot) {
4661
5057
  const errors2 = [];
4662
5058
  for (const marker of [OMP_PLUGIN_MARKER, CLAUDE_PLUGIN_MARKER]) {
4663
- const markerPath = path6.join(pluginRoot, marker);
4664
- if (!fs5.existsSync(markerPath)) {
5059
+ const markerPath = path7.join(pluginRoot, marker);
5060
+ if (!fs6.existsSync(markerPath)) {
4665
5061
  errors2.push(`Missing omp plugin marker: ${markerPath}`);
4666
5062
  }
4667
5063
  }
4668
5064
  for (const skill of SKILL_SMOKE) {
4669
- const skillPath = path6.join(pluginRoot, "skills", skill, "SKILL.md");
4670
- if (!fs5.existsSync(skillPath))
5065
+ const skillPath = path7.join(pluginRoot, "skills", skill, "SKILL.md");
5066
+ if (!fs6.existsSync(skillPath))
4671
5067
  errors2.push(`Missing skill: ${skillPath}`);
4672
5068
  }
4673
5069
  for (const command of COMMAND_SMOKE) {
4674
- const commandPath = path6.join(pluginRoot, "commands", `${command}.md`);
4675
- if (!fs5.existsSync(commandPath))
5070
+ const commandPath = path7.join(pluginRoot, "commands", `${command}.md`);
5071
+ if (!fs6.existsSync(commandPath))
4676
5072
  errors2.push(`Missing command: ${commandPath}`);
4677
5073
  }
4678
- const hostRef = path6.join(pluginRoot, "skills", "mstar-host", "references", "omp.md");
4679
- if (!fs5.existsSync(hostRef))
5074
+ const hostRef = path7.join(pluginRoot, "skills", "mstar-host", "references", "omp.md");
5075
+ if (!fs6.existsSync(hostRef))
4680
5076
  errors2.push(`Missing omp host reference: ${hostRef}`);
4681
5077
  return errors2;
4682
5078
  }
4683
5079
  function runInit2(scope, dryRun) {
4684
5080
  const notes = ensureLocalHarnessRepo(dryRun);
4685
5081
  const projectRoot = resolveProjectRoot();
4686
- if (fs5.existsSync(path6.join(HARNESS_REPO_PATH, ".git"))) {
5082
+ if (fs6.existsSync(path7.join(HARNESS_REPO_PATH, ".git"))) {
4687
5083
  if (dryRun) {
4688
5084
  notes.push(`Would update local harness repo: git -C ${HARNESS_REPO_PATH} pull --ff-only`);
4689
5085
  } else {
@@ -4757,8 +5153,8 @@ function runDoctor2(scope) {
4757
5153
  }
4758
5154
  if (scope === "project") {
4759
5155
  const projectRoot = resolveProjectRoot();
4760
- const gitignorePath = path6.join(projectRoot, ".gitignore");
4761
- const gitignore = fs5.existsSync(gitignorePath) ? fs5.readFileSync(gitignorePath, "utf8") : "";
5156
+ const gitignorePath = path7.join(projectRoot, ".gitignore");
5157
+ const gitignore = fs6.existsSync(gitignorePath) ? fs6.readFileSync(gitignorePath, "utf8") : "";
4762
5158
  for (const entry of missingHarnessProcessGitignoreEntries(gitignore)) {
4763
5159
  errors2.push(`Missing .gitignore entry: ${entry}`);
4764
5160
  }
@@ -4774,7 +5170,7 @@ var ompAdapter = {
4774
5170
 
4775
5171
  // src/adapters/opencode.ts
4776
5172
  import os4 from "node:os";
4777
- import path7 from "node:path";
5173
+ import path8 from "node:path";
4778
5174
  var OPENCODE_CONFIG_SCHEMA = "https://opencode.ai/config.json";
4779
5175
  var MSTAR_OPENCODE_PLUGIN = "@mstar-harness/opencode@latest";
4780
5176
  function isLegacyMorningStarGitPlugin(plugin) {
@@ -4795,11 +5191,11 @@ function isAnyMstarHarnessOpencodeSlot(plugin) {
4795
5191
  function resolveOpencodeConfigPath(scope, outputPath) {
4796
5192
  if (outputPath && outputPath.trim()) {
4797
5193
  const raw = outputPath.trim();
4798
- return path7.isAbsolute(raw) ? raw : path7.join(resolveProjectRoot(), raw);
5194
+ return path8.isAbsolute(raw) ? raw : path8.join(resolveProjectRoot(), raw);
4799
5195
  }
4800
5196
  if (scope === "global")
4801
- return path7.join(os4.homedir(), ".config", "opencode", "opencode.json");
4802
- return path7.join(resolveProjectRoot(), "opencode.json");
5197
+ return path8.join(os4.homedir(), ".config", "opencode", "opencode.json");
5198
+ return path8.join(resolveProjectRoot(), "opencode.json");
4803
5199
  }
4804
5200
  function ensureConfigSchema(config) {
4805
5201
  const next = ensureObject(config);
@@ -4892,9 +5288,9 @@ var opencodeAdapter = {
4892
5288
  };
4893
5289
 
4894
5290
  // src/adapters/zcode.ts
4895
- import fs6 from "node:fs";
5291
+ import fs7 from "node:fs";
4896
5292
  import os5 from "node:os";
4897
- import path8 from "node:path";
5293
+ import path9 from "node:path";
4898
5294
  var MARKETPLACE_ID = "mstar-local";
4899
5295
  var MARKETPLACE_NAME2 = "mstar-local";
4900
5296
  var MARKETPLACE_DESCRIPTION = "Morning Star harness marketplace (GitHub source).";
@@ -4905,10 +5301,10 @@ var GITHUB_REF = "main";
4905
5301
  var ZCODE_PLUGIN_MARKER = ".zcode-plugin/plugin.json";
4906
5302
  var ZCODE_PLUGIN_CHECKOUT_PROJECT = ".zcode/plugin-checkout";
4907
5303
  var ZCODE_AGENT_SMOKE_NAMES = ["fullstack-dev", "qc-specialist"];
4908
- var ZCODE_PLUGINS_ROOT = path8.join(os5.homedir(), ".zcode", "cli", "plugins");
4909
- var KNOWN_MARKETPLACES_PATH = path8.join(ZCODE_PLUGINS_ROOT, "known_marketplaces.json");
4910
- var MARKETPLACE_DIR = path8.join(ZCODE_PLUGINS_ROOT, "marketplaces", MARKETPLACE_ID);
4911
- var MARKETPLACE_JSON_PATH = path8.join(MARKETPLACE_DIR, "marketplace.json");
5304
+ var ZCODE_PLUGINS_ROOT = path9.join(os5.homedir(), ".zcode", "cli", "plugins");
5305
+ var KNOWN_MARKETPLACES_PATH = path9.join(ZCODE_PLUGINS_ROOT, "known_marketplaces.json");
5306
+ var MARKETPLACE_DIR = path9.join(ZCODE_PLUGINS_ROOT, "marketplaces", MARKETPLACE_ID);
5307
+ var MARKETPLACE_JSON_PATH = path9.join(MARKETPLACE_DIR, "marketplace.json");
4912
5308
  var GITHUB_SOURCE = { source: "github", repo: GITHUB_REPO, ref: GITHUB_REF };
4913
5309
  function nowIso() {
4914
5310
  return new Date().toISOString();
@@ -4964,7 +5360,7 @@ function findMarketplacePlugin(raw) {
4964
5360
  }
4965
5361
  function validateMarketplaceJson() {
4966
5362
  const errors2 = [];
4967
- if (!fs6.existsSync(MARKETPLACE_JSON_PATH)) {
5363
+ if (!fs7.existsSync(MARKETPLACE_JSON_PATH)) {
4968
5364
  errors2.push(`Missing ZCode marketplace: ${MARKETPLACE_JSON_PATH}`);
4969
5365
  return errors2;
4970
5366
  }
@@ -4993,7 +5389,7 @@ function validateMarketplaceJson() {
4993
5389
  }
4994
5390
  function validateKnownMarketplaces() {
4995
5391
  const errors2 = [];
4996
- if (!fs6.existsSync(KNOWN_MARKETPLACES_PATH)) {
5392
+ if (!fs7.existsSync(KNOWN_MARKETPLACES_PATH)) {
4997
5393
  errors2.push(`Missing ZCode known_marketplaces.json: ${KNOWN_MARKETPLACES_PATH}`);
4998
5394
  return errors2;
4999
5395
  }
@@ -5016,14 +5412,14 @@ function validateKnownMarketplaces() {
5016
5412
  }
5017
5413
  function validatePluginAgents2(pluginRoot) {
5018
5414
  const errors2 = [];
5019
- const agentsDir = path8.join(pluginRoot, "agents");
5020
- if (!fs6.existsSync(agentsDir)) {
5415
+ const agentsDir = path9.join(pluginRoot, "agents");
5416
+ if (!fs7.existsSync(agentsDir)) {
5021
5417
  errors2.push(`Missing plugin agents directory: ${agentsDir}`);
5022
5418
  return errors2;
5023
5419
  }
5024
5420
  for (const agentName of ZCODE_AGENT_SMOKE_NAMES) {
5025
- const agentPath = path8.join(agentsDir, `${agentName}.md`);
5026
- if (!fs6.existsSync(agentPath)) {
5421
+ const agentPath = path9.join(agentsDir, `${agentName}.md`);
5422
+ if (!fs7.existsSync(agentPath)) {
5027
5423
  errors2.push(`Missing plugin agent file: ${agentPath}`);
5028
5424
  }
5029
5425
  }
@@ -5040,15 +5436,15 @@ function runInit3(scope, dryRun) {
5040
5436
  const notes = ensureLocalHarnessRepo(dryRun);
5041
5437
  const projectRoot = resolveProjectRoot();
5042
5438
  if (scope === "project") {
5043
- const checkoutPath = path8.join(projectRoot, ZCODE_PLUGIN_CHECKOUT_PROJECT);
5439
+ const checkoutPath = path9.join(projectRoot, ZCODE_PLUGIN_CHECKOUT_PROJECT);
5044
5440
  notes.push(...ensureGitCheckout(REPO_URL, checkoutPath, dryRun));
5045
5441
  notes.push(...appendGitignore(projectRoot, [ZCODE_PLUGIN_CHECKOUT_PROJECT], dryRun));
5046
5442
  notes.push(...appendHarnessProjectGitignore(projectRoot, dryRun));
5047
5443
  notes.push(`Materialized local ZCode plugin checkout at ${ZCODE_PLUGIN_CHECKOUT_PROJECT} for smoke checks (the registered marketplace still points at the github repo).`);
5048
5444
  }
5049
5445
  if (!dryRun) {
5050
- if (!fs6.existsSync(MARKETPLACE_DIR))
5051
- fs6.mkdirSync(MARKETPLACE_DIR, { recursive: true });
5446
+ if (!fs7.existsSync(MARKETPLACE_DIR))
5447
+ fs7.mkdirSync(MARKETPLACE_DIR, { recursive: true });
5052
5448
  writeJson(MARKETPLACE_JSON_PATH, buildMarketplaceJson());
5053
5449
  }
5054
5450
  notes.push(`Wrote ZCode marketplace: ${MARKETPLACE_JSON_PATH}`);
@@ -5068,10 +5464,10 @@ function runDoctor3(scope) {
5068
5464
  errors2.push(...validateLocalHarnessRepo());
5069
5465
  if (scope === "project") {
5070
5466
  const projectRoot = resolveProjectRoot();
5071
- const checkoutPath = path8.join(projectRoot, ZCODE_PLUGIN_CHECKOUT_PROJECT);
5467
+ const checkoutPath = path9.join(projectRoot, ZCODE_PLUGIN_CHECKOUT_PROJECT);
5072
5468
  errors2.push(...validateGitCheckout(checkoutPath, ZCODE_PLUGIN_MARKER));
5073
- const gitignorePath = path8.join(projectRoot, ".gitignore");
5074
- const gitignore = fs6.existsSync(gitignorePath) ? fs6.readFileSync(gitignorePath, "utf8") : "";
5469
+ const gitignorePath = path9.join(projectRoot, ".gitignore");
5470
+ const gitignore = fs7.existsSync(gitignorePath) ? fs7.readFileSync(gitignorePath, "utf8") : "";
5075
5471
  if (!gitignore.split(/\r?\n/).includes(ZCODE_PLUGIN_CHECKOUT_PROJECT)) {
5076
5472
  errors2.push(`Missing .gitignore entry: ${ZCODE_PLUGIN_CHECKOUT_PROJECT}`);
5077
5473
  }
@@ -5112,8 +5508,8 @@ function getAdapter(target) {
5112
5508
  var SUPPORTED_TARGETS = ["opencode", "cursor", "codex", "zcode", "omp"];
5113
5509
 
5114
5510
  // src/utils.ts
5115
- import fs7 from "node:fs";
5116
- import path9 from "node:path";
5511
+ import fs8 from "node:fs";
5512
+ import path10 from "node:path";
5117
5513
  import { fileURLToPath as fileURLToPath2 } from "node:url";
5118
5514
  function parseCsv(raw) {
5119
5515
  if (!raw)
@@ -5121,9 +5517,9 @@ function parseCsv(raw) {
5121
5517
  return raw.split(",").map((item) => item.trim()).filter(Boolean);
5122
5518
  }
5123
5519
  function readJson2(filePath) {
5124
- if (!fs7.existsSync(filePath))
5520
+ if (!fs8.existsSync(filePath))
5125
5521
  return {};
5126
- const content = fs7.readFileSync(filePath, "utf8").trim();
5522
+ const content = fs8.readFileSync(filePath, "utf8").trim();
5127
5523
  if (!content)
5128
5524
  return {};
5129
5525
  try {
@@ -5133,16 +5529,22 @@ function readJson2(filePath) {
5133
5529
  }
5134
5530
  }
5135
5531
  function writeJson2(filePath, value) {
5136
- const parent = path9.dirname(filePath);
5137
- if (!fs7.existsSync(parent))
5138
- fs7.mkdirSync(parent, { recursive: true });
5139
- fs7.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}
5532
+ const parent = path10.dirname(filePath);
5533
+ if (!fs8.existsSync(parent))
5534
+ fs8.mkdirSync(parent, { recursive: true });
5535
+ fs8.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}
5140
5536
  `, "utf8");
5141
5537
  }
5538
+ function resolveProjectRoot2() {
5539
+ const candidate = process.env.MSTAR_CLI_PROJECT_ROOT || process.env.INIT_CWD || process.env.PWD;
5540
+ if (candidate && candidate.trim())
5541
+ return path10.resolve(candidate);
5542
+ return process.cwd();
5543
+ }
5142
5544
  function readHarnessVersion2() {
5143
- const packageJsonPath = path9.resolve(path9.dirname(fileURLToPath2(import.meta.url)), "../package.json");
5545
+ const packageJsonPath = path10.resolve(path10.dirname(fileURLToPath2(import.meta.url)), "../package.json");
5144
5546
  try {
5145
- const parsed = JSON.parse(fs7.readFileSync(packageJsonPath, "utf8"));
5547
+ const parsed = JSON.parse(fs8.readFileSync(packageJsonPath, "utf8"));
5146
5548
  return parsed.version || "0.0.0";
5147
5549
  } catch {
5148
5550
  return "0.0.0";
@@ -5289,6 +5691,33 @@ function runDoctor4(options) {
5289
5691
  console.log(` - ${issue}`);
5290
5692
  process.exitCode = 1;
5291
5693
  }
5694
+ function resolvePluginRoot(options) {
5695
+ if (options.root)
5696
+ return path11.resolve(options.root);
5697
+ let candidate = resolveProjectRoot2();
5698
+ while (!fs9.existsSync(path11.join(candidate, "plugin.json"))) {
5699
+ const parent = path11.dirname(candidate);
5700
+ if (parent === candidate)
5701
+ break;
5702
+ candidate = parent;
5703
+ }
5704
+ return candidate;
5705
+ }
5706
+ function runPluginValidate(options) {
5707
+ const root = resolvePluginRoot(options);
5708
+ const result = validateAgentPlugin(root);
5709
+ for (const warning of result.warnings) {
5710
+ console.warn(import_picocolors.default.yellow(warning));
5711
+ }
5712
+ if (result.ok) {
5713
+ console.log(import_picocolors.default.green(`OK ${root}: Agent Plugins v1.0.0 conformant`));
5714
+ return;
5715
+ }
5716
+ for (const error of result.errors) {
5717
+ console.error(import_picocolors.default.red(error));
5718
+ }
5719
+ process.exitCode = 1;
5720
+ }
5292
5721
  program2.name("mstar-harness").description("Morning Star harness CLI for target-based agent bootstrap").version(packageVersion);
5293
5722
  program2.command("init").description("Interactive/non-interactive setup for target agent bootstrap").option("-y, --yes", "Non-interactive mode").option("--target <target>", "Install target", "opencode").option("--scope <scope>", "Config scope: global|project (default: project)").option("--output <path>", "Config file path override, relative to project root").option("--dry-run", "Preview result without writing config").option("--pm-model <model>", "Optional: model for project-manager (advanced override)").option("--strategic-models <a,b,c>", "Optional: models for architect/product-manager/prompt-engineer").option("--dev-models <a,b,c>", "Optional: models for fullstack-dev/fullstack-dev-2/frontend-dev").option("--qc-models <a,b,c>", "Optional: models for qc trio").option("--other-models <a,b,c>", "Optional: models for remaining roles").action(async (options) => {
5294
5723
  await runInit4(options);
@@ -5296,6 +5725,10 @@ program2.command("init").description("Interactive/non-interactive setup for targ
5296
5725
  program2.command("doctor").description("Validate Morning Star setup for a target agent config").option("--target <target>", "Target agent for doctor checks", "opencode").option("--scope <scope>", "Config scope: global|project", "project").option("--output <path>", "Config file path override, relative to project root").action((options) => {
5297
5726
  runDoctor4(options);
5298
5727
  });
5728
+ var pluginCommand = program2.command("plugin").description("Agent Plugins v1.0.0 portable package commands");
5729
+ pluginCommand.command("validate").description("Validate a plugin package against Agent Plugins v1.0.0").option("--root <path>", "Plugin root directory to validate (default: project root)").action((options) => {
5730
+ runPluginValidate(options);
5731
+ });
5299
5732
  program2.parseAsync(process.argv).catch((error) => {
5300
5733
  console.error(import_picocolors.default.red(`Setup failed: ${error.message}`));
5301
5734
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mstar-harness/cli",
3
- "version": "1.8.8",
3
+ "version": "1.8.9",
4
4
  "description": "Morning Star harness installer CLI (OpenCode, Cursor, Codex, ZCode, omp).",
5
5
  "license": "MIT",
6
6
  "repository": {