@harness-engineering/cli 1.14.0 → 1.15.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.
Files changed (44) hide show
  1. package/dist/agents/skills/claude-code/harness-autopilot/SKILL.md +240 -39
  2. package/dist/agents/skills/claude-code/harness-autopilot/skill.yaml +6 -0
  3. package/dist/agents/skills/claude-code/harness-product-spec/SKILL.md +5 -5
  4. package/dist/agents/skills/gemini-cli/harness-autopilot/SKILL.md +240 -39
  5. package/dist/agents/skills/gemini-cli/harness-autopilot/skill.yaml +6 -0
  6. package/dist/agents/skills/gemini-cli/harness-product-spec/SKILL.md +5 -5
  7. package/dist/agents/skills/package.json +1 -0
  8. package/dist/agents/skills/vitest.config.mts +5 -0
  9. package/dist/{agents-md-YTYQDA3P.js → agents-md-ZGNIDWAF.js} +1 -1
  10. package/dist/{architecture-JQZYM4US.js → architecture-ZLIH5533.js} +2 -2
  11. package/dist/bin/harness-mcp.js +11 -11
  12. package/dist/bin/harness.js +16 -14
  13. package/dist/{check-phase-gate-L3RADYWO.js → check-phase-gate-ZOXVBDCN.js} +3 -3
  14. package/dist/{chunk-7IP4JIFL.js → chunk-2BKLWLY6.js} +4 -4
  15. package/dist/{chunk-OSXBPAMK.js → chunk-3ZZKVN62.js} +1 -1
  16. package/dist/{chunk-O5OJVPL6.js → chunk-B2HKP423.js} +1 -1
  17. package/dist/{chunk-YPYGXRDR.js → chunk-EDXIVMAP.js} +4 -4
  18. package/dist/{chunk-XKECDXJS.js → chunk-J4RAX7YB.js} +738 -186
  19. package/dist/{chunk-6KTUUFRN.js → chunk-LGYBN7Y6.js} +1 -1
  20. package/dist/{chunk-3C2MLBPJ.js → chunk-N25INEIX.js} +1 -1
  21. package/dist/{chunk-NLVUVUGD.js → chunk-ND2ENWDM.js} +1 -1
  22. package/dist/{chunk-YZD2MRNQ.js → chunk-NNHDDXYT.js} +379 -119
  23. package/dist/{chunk-S2FXOWOR.js → chunk-OFXQSFOW.js} +2 -2
  24. package/dist/{chunk-OXLLOSSR.js → chunk-VEPAJXBW.js} +2 -2
  25. package/dist/{chunk-TPOTOBR7.js → chunk-YLXFKVJE.js} +3 -3
  26. package/dist/{chunk-ABQHQ6I5.js → chunk-Z2OOPXJO.js} +1238 -133
  27. package/dist/{ci-workflow-EQZFVX3P.js → ci-workflow-765LSHRD.js} +1 -1
  28. package/dist/{dist-HWXF2C3R.js → dist-ALQDD67R.js} +47 -1
  29. package/dist/{docs-7ECGYMAV.js → docs-NRMQCOJ6.js} +3 -3
  30. package/dist/{engine-EG4EH4IX.js → engine-3RB7MXPP.js} +1 -1
  31. package/dist/{entropy-5USWKLVS.js → entropy-6AGX2ZUN.js} +2 -2
  32. package/dist/{feedback-UTBXZZHF.js → feedback-MY4QZIFD.js} +1 -1
  33. package/dist/{generate-agent-definitions-3PM5EU7V.js → generate-agent-definitions-ZAE726AU.js} +1 -1
  34. package/dist/index.d.ts +8 -8
  35. package/dist/index.js +13 -13
  36. package/dist/{loader-ZPALXIVR.js → loader-UUTVMQCC.js} +1 -1
  37. package/dist/{mcp-362EZHF4.js → mcp-VU5FMO52.js} +11 -11
  38. package/dist/{performance-OQAFMJUD.js → performance-2D7G6NMJ.js} +2 -2
  39. package/dist/{review-pipeline-C4GCFVGP.js → review-pipeline-RAQ55ISU.js} +1 -1
  40. package/dist/{runtime-7YLVK453.js → runtime-BCK5RRZQ.js} +1 -1
  41. package/dist/{security-PZOX7AQS.js → security-2RPQEN62.js} +1 -1
  42. package/dist/{validate-FD3Z6VJD.js → validate-KBYQAEWE.js} +2 -2
  43. package/dist/{validate-cross-check-WNJM6H2D.js → validate-cross-check-OABMREW4.js} +1 -1
  44. package/package.json +5 -3
@@ -6,7 +6,7 @@ import {
6
6
  OutputMode,
7
7
  createCheckPhaseGateCommand,
8
8
  findFiles
9
- } from "./chunk-6KTUUFRN.js";
9
+ } from "./chunk-LGYBN7Y6.js";
10
10
  import {
11
11
  createGenerateAgentDefinitionsCommand,
12
12
  generateAgentDefinitions
@@ -50,14 +50,14 @@ import {
50
50
  handleGetImpact,
51
51
  handleOrphanDeletion,
52
52
  persistToolingConfig
53
- } from "./chunk-YZD2MRNQ.js";
53
+ } from "./chunk-NNHDDXYT.js";
54
54
  import {
55
55
  VALID_PLATFORMS
56
56
  } from "./chunk-ZOAWBDWU.js";
57
57
  import {
58
58
  findConfigFile,
59
59
  resolveConfig
60
- } from "./chunk-O5OJVPL6.js";
60
+ } from "./chunk-B2HKP423.js";
61
61
  import {
62
62
  resolveGlobalSkillsDir,
63
63
  resolvePersonasDir,
@@ -127,14 +127,14 @@ import {
127
127
  validateKnowledgeMap,
128
128
  writeConfig,
129
129
  writeLockfile
130
- } from "./chunk-ABQHQ6I5.js";
130
+ } from "./chunk-Z2OOPXJO.js";
131
131
  import {
132
132
  Err,
133
133
  Ok
134
134
  } from "./chunk-ERS5EVUZ.js";
135
135
 
136
136
  // src/index.ts
137
- import { Command as Command55 } from "commander";
137
+ import { Command as Command61 } from "commander";
138
138
 
139
139
  // src/commands/validate.ts
140
140
  import { Command } from "commander";
@@ -213,7 +213,7 @@ function createValidateCommand() {
213
213
  process.exit(result.error.exitCode);
214
214
  }
215
215
  if (opts.crossCheck) {
216
- const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-WNJM6H2D.js");
216
+ const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-OABMREW4.js");
217
217
  const cwd = process.cwd();
218
218
  const specsDir = path.join(cwd, "docs", "specs");
219
219
  const plansDir = path.join(cwd, "docs", "plans");
@@ -481,10 +481,10 @@ async function runCheckSecurity(cwd, options) {
481
481
  const projectRoot = path4.resolve(cwd);
482
482
  let configData = {};
483
483
  try {
484
- const fs23 = await import("fs");
484
+ const fs29 = await import("fs");
485
485
  const configPath = path4.join(projectRoot, "harness.config.json");
486
- if (fs23.existsSync(configPath)) {
487
- const raw = fs23.readFileSync(configPath, "utf-8");
486
+ if (fs29.existsSync(configPath)) {
487
+ const raw = fs29.readFileSync(configPath, "utf-8");
488
488
  const parsed = JSON.parse(raw);
489
489
  configData = parsed.security ?? {};
490
490
  }
@@ -570,7 +570,7 @@ function registerBenchCommand(perf) {
570
570
  perf.command("bench [glob]").description("Run benchmarks via vitest bench").action(async (glob, _opts, cmd) => {
571
571
  const globalOpts = cmd.optsWithGlobals();
572
572
  const cwd = process.cwd();
573
- const { BenchmarkRunner } = await import("./dist-HWXF2C3R.js");
573
+ const { BenchmarkRunner } = await import("./dist-ALQDD67R.js");
574
574
  const runner = new BenchmarkRunner();
575
575
  const benchFiles = runner.discover(cwd, glob);
576
576
  if (benchFiles.length === 0) {
@@ -638,7 +638,7 @@ function registerBaselinesCommands(perf) {
638
638
  baselines.command("update").description("Update baselines from latest benchmark run").action(async (_opts, cmd) => {
639
639
  const globalOpts = cmd.optsWithGlobals();
640
640
  const cwd = process.cwd();
641
- const { BenchmarkRunner } = await import("./dist-HWXF2C3R.js");
641
+ const { BenchmarkRunner } = await import("./dist-ALQDD67R.js");
642
642
  const runner = new BenchmarkRunner();
643
643
  const manager = new BaselineManager(cwd);
644
644
  logger.info("Running benchmarks to update baselines...");
@@ -671,7 +671,7 @@ function registerReportCommand(perf) {
671
671
  perf.command("report").description("Full performance report with metrics, trends, and hotspots").action(async (_opts, cmd) => {
672
672
  const globalOpts = cmd.optsWithGlobals();
673
673
  const cwd = process.cwd();
674
- const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-HWXF2C3R.js");
674
+ const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-ALQDD67R.js");
675
675
  const analyzer = new EntropyAnalyzer2({
676
676
  rootDir: path5.resolve(cwd),
677
677
  analyze: { complexity: true, coupling: true }
@@ -3125,8 +3125,8 @@ function createResetCommand() {
3125
3125
  }
3126
3126
  if (!opts.yes) {
3127
3127
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
3128
- const answer = await new Promise((resolve29) => {
3129
- rl.question("Reset project state? This cannot be undone. [y/N] ", resolve29);
3128
+ const answer = await new Promise((resolve30) => {
3129
+ rl.question("Reset project state? This cannot be undone. [y/N] ", resolve30);
3130
3130
  });
3131
3131
  rl.close();
3132
3132
  if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
@@ -3240,11 +3240,302 @@ function createStateCommand() {
3240
3240
  return command;
3241
3241
  }
3242
3242
 
3243
+ // src/commands/setup.ts
3244
+ import { Command as Command34 } from "commander";
3245
+ import * as fs17 from "fs";
3246
+ import * as os4 from "os";
3247
+ import * as path29 from "path";
3248
+ import chalk3 from "chalk";
3249
+
3250
+ // src/utils/first-run.ts
3251
+ import * as fs16 from "fs";
3252
+ import * as os3 from "os";
3253
+ import * as path28 from "path";
3254
+ var HARNESS_DIR = path28.join(os3.homedir(), ".harness");
3255
+ var MARKER_FILE = path28.join(HARNESS_DIR, ".setup-complete");
3256
+ function isFirstRun() {
3257
+ return !fs16.existsSync(MARKER_FILE);
3258
+ }
3259
+ function markSetupComplete() {
3260
+ fs16.mkdirSync(HARNESS_DIR, { recursive: true });
3261
+ fs16.writeFileSync(MARKER_FILE, "", "utf-8");
3262
+ }
3263
+ function printFirstRunWelcome() {
3264
+ try {
3265
+ if (!isFirstRun()) return;
3266
+ if (process.env.CI) return;
3267
+ if (process.argv.includes("--quiet")) return;
3268
+ process.stderr.write("Welcome to harness! Run `harness setup` to get started.\n");
3269
+ } catch {
3270
+ }
3271
+ }
3272
+
3273
+ // src/utils/node-version.ts
3274
+ import semver2 from "semver";
3275
+ var REQUIRED_NODE_VERSION = ">=22.0.0";
3276
+ function checkNodeVersion() {
3277
+ return {
3278
+ satisfies: semver2.satisfies(process.version, REQUIRED_NODE_VERSION),
3279
+ current: process.version,
3280
+ required: ">=22"
3281
+ };
3282
+ }
3283
+
3284
+ // src/commands/setup.ts
3285
+ function checkNodeVersion2() {
3286
+ const result = checkNodeVersion();
3287
+ if (result.satisfies) {
3288
+ return { status: "pass", message: `Node.js ${result.current} (requires ${result.required})` };
3289
+ }
3290
+ return { status: "fail", message: `Node.js ${result.current} \u2014 requires ${result.required}` };
3291
+ }
3292
+ function runSlashCommandGeneration() {
3293
+ try {
3294
+ const results = generateSlashCommands({
3295
+ global: true,
3296
+ platforms: ["claude-code", "gemini-cli"],
3297
+ yes: true,
3298
+ includeGlobal: false,
3299
+ skillsDir: "",
3300
+ dryRun: false
3301
+ });
3302
+ const outputDirs = results.map((r) => r.outputDir).join(", ");
3303
+ return { status: "pass", message: `Generated global slash commands -> ${outputDirs}` };
3304
+ } catch (error) {
3305
+ const msg = error instanceof Error ? error.message : String(error);
3306
+ return { status: "fail", message: `Slash command generation failed \u2014 ${msg}` };
3307
+ }
3308
+ }
3309
+ function detectClient(dirName) {
3310
+ return fs17.existsSync(path29.join(os4.homedir(), dirName));
3311
+ }
3312
+ function runMcpSetup(cwd) {
3313
+ const results = [];
3314
+ const clients = [
3315
+ { name: "Claude Code", dir: ".claude", client: "claude", configTarget: ".mcp.json" },
3316
+ {
3317
+ name: "Gemini CLI",
3318
+ dir: ".gemini",
3319
+ client: "gemini",
3320
+ configTarget: ".gemini/settings.json"
3321
+ }
3322
+ ];
3323
+ for (const { name, dir, client, configTarget } of clients) {
3324
+ if (!detectClient(dir)) {
3325
+ results.push({
3326
+ status: "warn",
3327
+ message: `${name} not detected \u2014 skipped MCP configuration`
3328
+ });
3329
+ continue;
3330
+ }
3331
+ try {
3332
+ setupMcp(cwd, client);
3333
+ results.push({ status: "pass", message: `Configured MCP for ${name} -> ${configTarget}` });
3334
+ } catch (error) {
3335
+ const msg = error instanceof Error ? error.message : String(error);
3336
+ results.push({
3337
+ status: "fail",
3338
+ message: `MCP configuration failed for ${name} \u2014 ${msg}`
3339
+ });
3340
+ }
3341
+ }
3342
+ return results;
3343
+ }
3344
+ function formatStep(result) {
3345
+ const icon = result.status === "pass" ? chalk3.green("\u2713") : result.status === "warn" ? chalk3.yellow("\u26A0") : chalk3.red("\u2717");
3346
+ return ` ${icon} ${result.message}`;
3347
+ }
3348
+ function runSetup(cwd) {
3349
+ const steps = [];
3350
+ const nodeResult = checkNodeVersion2();
3351
+ steps.push(nodeResult);
3352
+ if (nodeResult.status === "fail") {
3353
+ return { steps, success: false };
3354
+ }
3355
+ const slashResult = runSlashCommandGeneration();
3356
+ steps.push(slashResult);
3357
+ const mcpResults = runMcpSetup(cwd);
3358
+ steps.push(...mcpResults);
3359
+ const success = steps.every((s) => s.status !== "fail");
3360
+ if (success) {
3361
+ markSetupComplete();
3362
+ }
3363
+ return { steps, success };
3364
+ }
3365
+ function createSetupCommand() {
3366
+ return new Command34("setup").description("Configure harness environment: slash commands, MCP, and more").action(() => {
3367
+ const cwd = process.cwd();
3368
+ console.log("");
3369
+ console.log(` ${chalk3.bold("harness setup")}`);
3370
+ console.log("");
3371
+ const { steps, success } = runSetup(cwd);
3372
+ for (const step of steps) {
3373
+ console.log(formatStep(step));
3374
+ }
3375
+ console.log("");
3376
+ if (success) {
3377
+ console.log(" Setup complete. Next steps:");
3378
+ console.log(" - Open a project directory and run /harness:initialize-project");
3379
+ console.log(" - Or run harness init --name my-project to scaffold a new one");
3380
+ console.log(" - Run harness doctor anytime to check your environment");
3381
+ console.log("");
3382
+ }
3383
+ process.exit(success ? ExitCode.SUCCESS : ExitCode.VALIDATION_FAILED);
3384
+ });
3385
+ }
3386
+
3387
+ // src/commands/doctor.ts
3388
+ import { Command as Command35 } from "commander";
3389
+ import * as fs18 from "fs";
3390
+ import * as os5 from "os";
3391
+ import * as path30 from "path";
3392
+ import chalk4 from "chalk";
3393
+ function checkNodeVersion3() {
3394
+ const result = checkNodeVersion();
3395
+ if (result.satisfies) {
3396
+ return {
3397
+ name: "node",
3398
+ status: "pass",
3399
+ message: `Node.js ${result.current} (requires ${result.required})`
3400
+ };
3401
+ }
3402
+ return {
3403
+ name: "node",
3404
+ status: "fail",
3405
+ message: `Node.js ${result.current} (requires ${result.required})`,
3406
+ fix: "Install Node.js >= 22: https://nodejs.org/"
3407
+ };
3408
+ }
3409
+ function countCommandFiles(dir, ext) {
3410
+ try {
3411
+ return fs18.readdirSync(dir).filter((f) => f.endsWith(ext)).length;
3412
+ } catch {
3413
+ return 0;
3414
+ }
3415
+ }
3416
+ function checkSlashCommands() {
3417
+ const platforms = [
3418
+ {
3419
+ name: "Claude Code",
3420
+ dir: path30.join(os5.homedir(), ".claude", "commands", "harness"),
3421
+ ext: ".md",
3422
+ client: "claude-code"
3423
+ },
3424
+ {
3425
+ name: "Gemini CLI",
3426
+ dir: path30.join(os5.homedir(), ".gemini", "commands", "harness"),
3427
+ ext: ".toml",
3428
+ client: "gemini-cli"
3429
+ }
3430
+ ];
3431
+ return platforms.map(({ name, dir, ext, client }) => {
3432
+ const count = countCommandFiles(dir, ext);
3433
+ if (count > 0) {
3434
+ return {
3435
+ name: `slash-commands-${client}`,
3436
+ status: "pass",
3437
+ message: `Slash commands installed -> ${dir} (${count} commands)`
3438
+ };
3439
+ }
3440
+ return {
3441
+ name: `slash-commands-${client}`,
3442
+ status: "fail",
3443
+ message: `No slash commands found for ${name}`,
3444
+ fix: "Run: harness setup"
3445
+ };
3446
+ });
3447
+ }
3448
+ function readJsonSafe(filePath) {
3449
+ try {
3450
+ if (!fs18.existsSync(filePath)) return null;
3451
+ return JSON.parse(fs18.readFileSync(filePath, "utf-8"));
3452
+ } catch {
3453
+ return null;
3454
+ }
3455
+ }
3456
+ function checkMcpConfig(cwd) {
3457
+ const results = [];
3458
+ const claudeConfigPath = path30.join(cwd, ".mcp.json");
3459
+ const claudeConfig = readJsonSafe(claudeConfigPath);
3460
+ if (claudeConfig?.mcpServers?.["harness"]) {
3461
+ results.push({
3462
+ name: "mcp-claude",
3463
+ status: "pass",
3464
+ message: "MCP configured for Claude Code"
3465
+ });
3466
+ } else {
3467
+ results.push({
3468
+ name: "mcp-claude",
3469
+ status: "fail",
3470
+ message: "MCP not configured for Claude Code",
3471
+ fix: "Run: harness setup-mcp --client claude"
3472
+ });
3473
+ }
3474
+ const geminiConfigPath = path30.join(os5.homedir(), ".gemini", "settings.json");
3475
+ const geminiConfig = readJsonSafe(geminiConfigPath);
3476
+ if (geminiConfig?.mcpServers?.["harness"]) {
3477
+ results.push({
3478
+ name: "mcp-gemini",
3479
+ status: "pass",
3480
+ message: "MCP configured for Gemini CLI"
3481
+ });
3482
+ } else {
3483
+ results.push({
3484
+ name: "mcp-gemini",
3485
+ status: "fail",
3486
+ message: "MCP not configured for Gemini CLI",
3487
+ fix: "Run: harness setup-mcp --client gemini"
3488
+ });
3489
+ }
3490
+ return results;
3491
+ }
3492
+ function runDoctor(cwd) {
3493
+ const checks = [];
3494
+ checks.push(checkNodeVersion3());
3495
+ checks.push(...checkSlashCommands());
3496
+ checks.push(...checkMcpConfig(cwd));
3497
+ const allPassed = checks.every((c) => c.status === "pass");
3498
+ return { checks, allPassed };
3499
+ }
3500
+ function formatCheck(check) {
3501
+ const icon = check.status === "pass" ? chalk4.green("\u2713") : chalk4.red("\u2717");
3502
+ let line = ` ${icon} ${check.message}`;
3503
+ if (check.status === "fail" && check.fix) {
3504
+ line += `
3505
+ -> ${check.fix}`;
3506
+ }
3507
+ return line;
3508
+ }
3509
+ function createDoctorCommand() {
3510
+ return new Command35("doctor").description("Check environment health: Node version, slash commands, MCP configuration").action((_opts, cmd) => {
3511
+ const globalOpts = cmd.optsWithGlobals();
3512
+ const cwd = process.cwd();
3513
+ const useJson = globalOpts.json;
3514
+ const result = runDoctor(cwd);
3515
+ if (useJson) {
3516
+ console.log(JSON.stringify(result, null, 2));
3517
+ } else {
3518
+ console.log("");
3519
+ console.log(` ${chalk4.bold("harness doctor")}`);
3520
+ console.log("");
3521
+ for (const check of result.checks) {
3522
+ console.log(formatCheck(check));
3523
+ }
3524
+ console.log("");
3525
+ const passed = result.checks.filter((c) => c.status === "pass").length;
3526
+ const total = result.checks.length;
3527
+ console.log(` ${passed}/${total} checks passed`);
3528
+ console.log("");
3529
+ }
3530
+ process.exit(result.allPassed ? ExitCode.SUCCESS : ExitCode.VALIDATION_FAILED);
3531
+ });
3532
+ }
3533
+
3243
3534
  // src/commands/ci/index.ts
3244
- import { Command as Command36 } from "commander";
3535
+ import { Command as Command38 } from "commander";
3245
3536
 
3246
3537
  // src/commands/ci/check.ts
3247
- import { Command as Command34 } from "commander";
3538
+ import { Command as Command36 } from "commander";
3248
3539
  var VALID_CHECKS = [
3249
3540
  "validate",
3250
3541
  "deps",
@@ -3284,7 +3575,7 @@ function parseFailOn(failOn) {
3284
3575
  return "error";
3285
3576
  }
3286
3577
  function createCheckCommand() {
3287
- return new Command34("check").description("Run all harness checks for CI (validate, deps, docs, entropy, phase-gate, arch)").option("--skip <checks>", "Comma-separated checks to skip (e.g., entropy,docs)").option("--fail-on <severity>", "Fail on severity level: error (default) or warning", "error").action(async (opts, cmd) => {
3578
+ return new Command36("check").description("Run all harness checks for CI (validate, deps, docs, entropy, phase-gate, arch)").option("--skip <checks>", "Comma-separated checks to skip (e.g., entropy,docs)").option("--fail-on <severity>", "Fail on severity level: error (default) or warning", "error").action(async (opts, cmd) => {
3288
3579
  const globalOpts = cmd.optsWithGlobals();
3289
3580
  const mode = resolveOutputMode(globalOpts);
3290
3581
  const skip = parseSkip(opts.skip);
@@ -3328,9 +3619,9 @@ function createCheckCommand() {
3328
3619
  }
3329
3620
 
3330
3621
  // src/commands/ci/init.ts
3331
- import { Command as Command35 } from "commander";
3332
- import * as fs16 from "fs";
3333
- import * as path28 from "path";
3622
+ import { Command as Command37 } from "commander";
3623
+ import * as fs19 from "fs";
3624
+ import * as path31 from "path";
3334
3625
  var ALL_CHECKS = [
3335
3626
  "validate",
3336
3627
  "deps",
@@ -3431,12 +3722,12 @@ function generateCIConfig(options) {
3431
3722
  });
3432
3723
  }
3433
3724
  function detectPlatform() {
3434
- if (fs16.existsSync(".github")) return "github";
3435
- if (fs16.existsSync(".gitlab-ci.yml")) return "gitlab";
3725
+ if (fs19.existsSync(".github")) return "github";
3726
+ if (fs19.existsSync(".gitlab-ci.yml")) return "gitlab";
3436
3727
  return null;
3437
3728
  }
3438
3729
  function createInitCommand2() {
3439
- return new Command35("init").description("Generate CI configuration for harness checks").option("--platform <platform>", "CI platform: github, gitlab, or generic").option("--checks <list>", "Comma-separated list of checks to include").action(async (opts, cmd) => {
3730
+ return new Command37("init").description("Generate CI configuration for harness checks").option("--platform <platform>", "CI platform: github, gitlab, or generic").option("--checks <list>", "Comma-separated list of checks to include").action(async (opts, cmd) => {
3440
3731
  const globalOpts = cmd.optsWithGlobals();
3441
3732
  const platform = opts.platform ?? detectPlatform() ?? "generic";
3442
3733
  const checks = opts.checks ? opts.checks.split(",").map((s) => s.trim()) : void 0;
@@ -3448,12 +3739,12 @@ function createInitCommand2() {
3448
3739
  process.exit(result.error.exitCode);
3449
3740
  }
3450
3741
  const { filename, content } = result.value;
3451
- const targetPath = path28.resolve(filename);
3452
- const dir = path28.dirname(targetPath);
3453
- fs16.mkdirSync(dir, { recursive: true });
3454
- fs16.writeFileSync(targetPath, content);
3742
+ const targetPath = path31.resolve(filename);
3743
+ const dir = path31.dirname(targetPath);
3744
+ fs19.mkdirSync(dir, { recursive: true });
3745
+ fs19.writeFileSync(targetPath, content);
3455
3746
  if (platform === "generic" && process.platform !== "win32") {
3456
- fs16.chmodSync(targetPath, "755");
3747
+ fs19.chmodSync(targetPath, "755");
3457
3748
  }
3458
3749
  if (globalOpts.json) {
3459
3750
  console.log(JSON.stringify({ file: filename, platform }));
@@ -3466,18 +3757,275 @@ function createInitCommand2() {
3466
3757
 
3467
3758
  // src/commands/ci/index.ts
3468
3759
  function createCICommand() {
3469
- const command = new Command36("ci").description("CI/CD integration commands");
3760
+ const command = new Command38("ci").description("CI/CD integration commands");
3470
3761
  command.addCommand(createCheckCommand());
3471
3762
  command.addCommand(createInitCommand2());
3472
3763
  return command;
3473
3764
  }
3474
3765
 
3766
+ // src/commands/hooks/index.ts
3767
+ import { Command as Command42 } from "commander";
3768
+
3769
+ // src/commands/hooks/init.ts
3770
+ import { Command as Command39 } from "commander";
3771
+ import * as fs20 from "fs";
3772
+ import * as path32 from "path";
3773
+ import { fileURLToPath } from "url";
3774
+
3775
+ // src/hooks/profiles.ts
3776
+ var HOOK_SCRIPTS = [
3777
+ { name: "block-no-verify", event: "PreToolUse", matcher: "Bash", minProfile: "minimal" },
3778
+ { name: "protect-config", event: "PreToolUse", matcher: "Write|Edit", minProfile: "standard" },
3779
+ { name: "quality-gate", event: "PostToolUse", matcher: "Edit|Write", minProfile: "standard" },
3780
+ { name: "pre-compact-state", event: "PreCompact", matcher: "*", minProfile: "standard" },
3781
+ { name: "cost-tracker", event: "Stop", matcher: "*", minProfile: "strict" }
3782
+ ];
3783
+ var PROFILE_ORDER = ["minimal", "standard", "strict"];
3784
+ function hooksForProfile(profile) {
3785
+ const profileIndex = PROFILE_ORDER.indexOf(profile);
3786
+ return HOOK_SCRIPTS.filter((h) => PROFILE_ORDER.indexOf(h.minProfile) <= profileIndex).map(
3787
+ (h) => h.name
3788
+ );
3789
+ }
3790
+ var PROFILES = {
3791
+ minimal: hooksForProfile("minimal"),
3792
+ standard: hooksForProfile("standard"),
3793
+ strict: hooksForProfile("strict")
3794
+ };
3795
+
3796
+ // src/commands/hooks/init.ts
3797
+ var __filename = fileURLToPath(import.meta.url);
3798
+ var __dirname = path32.dirname(__filename);
3799
+ var VALID_PROFILES = ["minimal", "standard", "strict"];
3800
+ function resolveHookSourceDir() {
3801
+ const candidate = path32.resolve(__dirname, "..", "..", "hooks");
3802
+ if (fs20.existsSync(candidate)) {
3803
+ return candidate;
3804
+ }
3805
+ throw new Error(`Cannot locate hook scripts directory. Expected at: ${candidate}`);
3806
+ }
3807
+ function buildSettingsHooks(profile) {
3808
+ const activeHookNames = PROFILES[profile];
3809
+ const activeScripts = HOOK_SCRIPTS.filter((h) => activeHookNames.includes(h.name));
3810
+ const hooks = {};
3811
+ for (const script of activeScripts) {
3812
+ if (!hooks[script.event]) {
3813
+ hooks[script.event] = [];
3814
+ }
3815
+ hooks[script.event].push({
3816
+ matcher: script.matcher,
3817
+ hooks: [{ type: "command", command: `node .harness/hooks/${script.name}.js` }]
3818
+ });
3819
+ }
3820
+ return hooks;
3821
+ }
3822
+ function mergeSettings(existing, hooksConfig) {
3823
+ return {
3824
+ ...existing,
3825
+ hooks: hooksConfig
3826
+ };
3827
+ }
3828
+ function initHooks(options) {
3829
+ const { profile, projectDir } = options;
3830
+ const hooksDestDir = path32.join(projectDir, ".harness", "hooks");
3831
+ fs20.mkdirSync(hooksDestDir, { recursive: true });
3832
+ if (fs20.existsSync(hooksDestDir)) {
3833
+ for (const entry of fs20.readdirSync(hooksDestDir)) {
3834
+ if (entry.endsWith(".js")) {
3835
+ fs20.unlinkSync(path32.join(hooksDestDir, entry));
3836
+ }
3837
+ }
3838
+ }
3839
+ const sourceDir = resolveHookSourceDir();
3840
+ const copiedScripts = [];
3841
+ const activeNames = PROFILES[profile];
3842
+ const activeScripts = HOOK_SCRIPTS.filter((h) => activeNames.includes(h.name));
3843
+ for (const script of activeScripts) {
3844
+ const srcFile = path32.join(sourceDir, `${script.name}.js`);
3845
+ const destFile = path32.join(hooksDestDir, `${script.name}.js`);
3846
+ if (fs20.existsSync(srcFile)) {
3847
+ fs20.copyFileSync(srcFile, destFile);
3848
+ copiedScripts.push(script.name);
3849
+ }
3850
+ }
3851
+ const profilePath = path32.join(hooksDestDir, "profile.json");
3852
+ fs20.writeFileSync(profilePath, JSON.stringify({ profile }, null, 2) + "\n");
3853
+ const claudeDir = path32.join(projectDir, ".claude");
3854
+ fs20.mkdirSync(claudeDir, { recursive: true });
3855
+ const settingsPath = path32.join(claudeDir, "settings.json");
3856
+ let existing = {};
3857
+ if (fs20.existsSync(settingsPath)) {
3858
+ try {
3859
+ existing = JSON.parse(fs20.readFileSync(settingsPath, "utf-8"));
3860
+ } catch (e) {
3861
+ throw new Error(
3862
+ `Malformed .claude/settings.json \u2014 fix the JSON syntax before running hooks init. Parse error: ${e instanceof Error ? e.message : String(e)}`,
3863
+ { cause: e }
3864
+ );
3865
+ }
3866
+ }
3867
+ const hooksConfig = buildSettingsHooks(profile);
3868
+ const merged = mergeSettings(existing, hooksConfig);
3869
+ fs20.writeFileSync(settingsPath, JSON.stringify(merged, null, 2) + "\n");
3870
+ return { copiedScripts, settingsPath, profilePath };
3871
+ }
3872
+ function createInitCommand3() {
3873
+ return new Command39("init").description("Install Claude Code hook configurations into the current project").option("--profile <profile>", "Hook profile: minimal, standard, or strict", "standard").action(async (opts, cmd) => {
3874
+ const globalOpts = cmd.optsWithGlobals();
3875
+ const profile = opts.profile;
3876
+ if (!VALID_PROFILES.includes(profile)) {
3877
+ logger.error(`Invalid profile: ${profile}. Must be one of: ${VALID_PROFILES.join(", ")}`);
3878
+ process.exit(2);
3879
+ }
3880
+ const projectDir = process.cwd();
3881
+ try {
3882
+ const result = initHooks({ profile, projectDir });
3883
+ if (globalOpts.json) {
3884
+ console.log(
3885
+ JSON.stringify({
3886
+ profile,
3887
+ copiedScripts: result.copiedScripts,
3888
+ settingsPath: result.settingsPath,
3889
+ profilePath: result.profilePath
3890
+ })
3891
+ );
3892
+ } else {
3893
+ logger.success(
3894
+ `Installed ${result.copiedScripts.length} hook scripts to .harness/hooks/`
3895
+ );
3896
+ logger.info(`Profile: ${profile}`);
3897
+ logger.info(`Settings: ${path32.relative(projectDir, result.settingsPath)}`);
3898
+ logger.dim("Run 'harness hooks list' to see installed hooks");
3899
+ }
3900
+ } catch (err) {
3901
+ const message = err instanceof Error ? err.message : String(err);
3902
+ logger.error(`Failed to initialize hooks: ${message}`);
3903
+ process.exit(2);
3904
+ }
3905
+ });
3906
+ }
3907
+
3908
+ // src/commands/hooks/list.ts
3909
+ import { Command as Command40 } from "commander";
3910
+ import * as fs21 from "fs";
3911
+ import * as path33 from "path";
3912
+ function listHooks(projectDir) {
3913
+ const hooksDir = path33.join(projectDir, ".harness", "hooks");
3914
+ const profilePath = path33.join(hooksDir, "profile.json");
3915
+ if (!fs21.existsSync(profilePath)) {
3916
+ return { installed: false, profile: null, hooks: [] };
3917
+ }
3918
+ let profile = "standard";
3919
+ let warning;
3920
+ try {
3921
+ const data = JSON.parse(fs21.readFileSync(profilePath, "utf-8"));
3922
+ if (data.profile && ["minimal", "standard", "strict"].includes(data.profile)) {
3923
+ profile = data.profile;
3924
+ }
3925
+ } catch {
3926
+ warning = "Malformed profile.json \u2014 defaulting to standard profile";
3927
+ }
3928
+ const activeNames = PROFILES[profile];
3929
+ const hooks = HOOK_SCRIPTS.filter((h) => activeNames.includes(h.name)).map((h) => ({
3930
+ name: h.name,
3931
+ event: h.event,
3932
+ matcher: h.matcher,
3933
+ scriptPath: path33.join(".harness", "hooks", `${h.name}.js`)
3934
+ }));
3935
+ const result = { installed: true, profile, hooks };
3936
+ if (warning) {
3937
+ result.warning = warning;
3938
+ }
3939
+ return result;
3940
+ }
3941
+ function createListCommand3() {
3942
+ return new Command40("list").description("Show installed hooks and active profile").action(async (_opts, cmd) => {
3943
+ const globalOpts = cmd.optsWithGlobals();
3944
+ const projectDir = process.cwd();
3945
+ const result = listHooks(projectDir);
3946
+ if (globalOpts.json) {
3947
+ console.log(JSON.stringify(result, null, 2));
3948
+ return;
3949
+ }
3950
+ if (!result.installed) {
3951
+ logger.info("No harness hooks installed. Run 'harness hooks init' to set up hooks.");
3952
+ return;
3953
+ }
3954
+ logger.info(`Profile: ${result.profile}`);
3955
+ logger.info(`Hooks (${result.hooks.length}):`);
3956
+ for (const hook of result.hooks) {
3957
+ console.log(` ${hook.name} ${hook.event}:${hook.matcher} ${hook.scriptPath}`);
3958
+ }
3959
+ });
3960
+ }
3961
+
3962
+ // src/commands/hooks/remove.ts
3963
+ import { Command as Command41 } from "commander";
3964
+ import * as fs22 from "fs";
3965
+ import * as path34 from "path";
3966
+ function removeHooks(projectDir) {
3967
+ const hooksDir = path34.join(projectDir, ".harness", "hooks");
3968
+ const settingsPath = path34.join(projectDir, ".claude", "settings.json");
3969
+ let removed = false;
3970
+ let settingsCleaned = false;
3971
+ if (fs22.existsSync(hooksDir)) {
3972
+ fs22.rmSync(hooksDir, { recursive: true, force: true });
3973
+ removed = true;
3974
+ }
3975
+ if (fs22.existsSync(settingsPath)) {
3976
+ try {
3977
+ const settings = JSON.parse(fs22.readFileSync(settingsPath, "utf-8"));
3978
+ if (settings.hooks !== void 0) {
3979
+ delete settings.hooks;
3980
+ settingsCleaned = true;
3981
+ if (Object.keys(settings).length === 0) {
3982
+ fs22.unlinkSync(settingsPath);
3983
+ } else {
3984
+ fs22.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
3985
+ }
3986
+ }
3987
+ } catch {
3988
+ }
3989
+ }
3990
+ return { removed, hooksDir, settingsCleaned };
3991
+ }
3992
+ function createRemoveCommand() {
3993
+ return new Command41("remove").description("Remove harness-managed hooks from the current project").action(async (_opts, cmd) => {
3994
+ const globalOpts = cmd.optsWithGlobals();
3995
+ const projectDir = process.cwd();
3996
+ const result = removeHooks(projectDir);
3997
+ if (globalOpts.json) {
3998
+ console.log(JSON.stringify(result));
3999
+ return;
4000
+ }
4001
+ if (!result.removed && !result.settingsCleaned) {
4002
+ logger.info("No harness hooks found to remove.");
4003
+ return;
4004
+ }
4005
+ if (result.removed) {
4006
+ logger.success("Removed .harness/hooks/ directory");
4007
+ }
4008
+ if (result.settingsCleaned) {
4009
+ logger.success("Cleaned hook entries from .claude/settings.json");
4010
+ }
4011
+ });
4012
+ }
4013
+
4014
+ // src/commands/hooks/index.ts
4015
+ function createHooksCommand() {
4016
+ const command = new Command42("hooks").description("Manage Claude Code hook configurations");
4017
+ command.addCommand(createInitCommand3());
4018
+ command.addCommand(createListCommand3());
4019
+ command.addCommand(createRemoveCommand());
4020
+ return command;
4021
+ }
4022
+
3475
4023
  // src/commands/update.ts
3476
- import { Command as Command37 } from "commander";
4024
+ import { Command as Command43 } from "commander";
3477
4025
  import { execFileSync as execFileSync4 } from "child_process";
3478
4026
  import { realpathSync } from "fs";
3479
4027
  import readline2 from "readline";
3480
- import chalk3 from "chalk";
4028
+ import chalk5 from "chalk";
3481
4029
  function detectPackageManager() {
3482
4030
  try {
3483
4031
  const argv1 = process.argv[1];
@@ -3533,10 +4081,10 @@ function prompt(question) {
3533
4081
  input: process.stdin,
3534
4082
  output: process.stdout
3535
4083
  });
3536
- return new Promise((resolve29) => {
4084
+ return new Promise((resolve30) => {
3537
4085
  rl.question(question, (answer) => {
3538
4086
  rl.close();
3539
- resolve29(answer.trim().toLowerCase());
4087
+ resolve30(answer.trim().toLowerCase());
3540
4088
  });
3541
4089
  });
3542
4090
  }
@@ -3552,11 +4100,11 @@ async function offerRegeneration() {
3552
4100
  });
3553
4101
  } catch {
3554
4102
  logger.warn("Generation failed. Run manually:");
3555
- console.log(` ${chalk3.cyan(`harness generate${isGlobal ? " --global" : ""}`)}`);
4103
+ console.log(` ${chalk5.cyan(`harness generate${isGlobal ? " --global" : ""}`)}`);
3556
4104
  }
3557
4105
  }
3558
4106
  function createUpdateCommand() {
3559
- return new Command37("update").description("Update all @harness-engineering packages to the latest version").option("--version <semver>", "Pin @harness-engineering/cli to a specific version").action(async (opts, cmd) => {
4107
+ return new Command43("update").description("Update all @harness-engineering packages to the latest version").option("--version <semver>", "Pin @harness-engineering/cli to a specific version").action(async (opts, cmd) => {
3560
4108
  const globalOpts = cmd.optsWithGlobals();
3561
4109
  const pm = detectPackageManager();
3562
4110
  if (globalOpts.verbose) {
@@ -3578,8 +4126,8 @@ function createUpdateCommand() {
3578
4126
  }
3579
4127
  if (currentVersion) {
3580
4128
  console.log("");
3581
- logger.info(`Current CLI version: ${chalk3.dim(`v${currentVersion}`)}`);
3582
- logger.info(`Latest CLI version: ${chalk3.green(`v${latestCliVersion}`)}`);
4129
+ logger.info(`Current CLI version: ${chalk5.dim(`v${currentVersion}`)}`);
4130
+ logger.info(`Latest CLI version: ${chalk5.green(`v${latestCliVersion}`)}`);
3583
4131
  console.log("");
3584
4132
  }
3585
4133
  }
@@ -3605,7 +4153,7 @@ function createUpdateCommand() {
3605
4153
  } catch {
3606
4154
  console.log("");
3607
4155
  logger.error("Update failed. You can try manually:");
3608
- console.log(` ${chalk3.cyan(installCmd)}`);
4156
+ console.log(` ${chalk5.cyan(installCmd)}`);
3609
4157
  process.exit(ExitCode.ERROR);
3610
4158
  }
3611
4159
  await offerRegeneration();
@@ -3614,9 +4162,9 @@ function createUpdateCommand() {
3614
4162
  }
3615
4163
 
3616
4164
  // src/commands/generate.ts
3617
- import { Command as Command38 } from "commander";
4165
+ import { Command as Command44 } from "commander";
3618
4166
  function createGenerateCommand3() {
3619
- return new Command38("generate").description("Generate all platform integrations (slash commands + agent definitions)").option("--platforms <list>", "Target platforms (comma-separated)", "claude-code,gemini-cli").option("--global", "Write to global directories", false).option("--include-global", "Include built-in global skills", false).option("--output <dir>", "Custom output directory").option("--dry-run", "Show what would change without writing", false).option("--yes", "Skip deletion confirmation prompts", false).action(async (opts, cmd) => {
4167
+ return new Command44("generate").description("Generate all platform integrations (slash commands + agent definitions)").option("--platforms <list>", "Target platforms (comma-separated)", "claude-code,gemini-cli").option("--global", "Write to global directories", false).option("--include-global", "Include built-in global skills", false).option("--output <dir>", "Custom output directory").option("--dry-run", "Show what would change without writing", false).option("--yes", "Skip deletion confirmation prompts", false).action(async (opts, cmd) => {
3620
4168
  const globalOpts = cmd.optsWithGlobals();
3621
4169
  const platforms = opts.platforms.split(",").map((p) => p.trim());
3622
4170
  for (const p of platforms) {
@@ -3675,8 +4223,8 @@ function createGenerateCommand3() {
3675
4223
  }
3676
4224
 
3677
4225
  // src/commands/graph/scan.ts
3678
- import { Command as Command39 } from "commander";
3679
- import * as path29 from "path";
4226
+ import { Command as Command45 } from "commander";
4227
+ import * as path35 from "path";
3680
4228
  async function runScan(projectPath) {
3681
4229
  const { GraphStore, CodeIngestor, TopologicalLinker, KnowledgeIngestor, GitIngestor } = await import("./dist-B26DFXMP.js");
3682
4230
  const store = new GraphStore();
@@ -3689,13 +4237,13 @@ async function runScan(projectPath) {
3689
4237
  await new GitIngestor(store).ingest(projectPath);
3690
4238
  } catch {
3691
4239
  }
3692
- const graphDir = path29.join(projectPath, ".harness", "graph");
4240
+ const graphDir = path35.join(projectPath, ".harness", "graph");
3693
4241
  await store.save(graphDir);
3694
4242
  return { nodeCount: store.nodeCount, edgeCount: store.edgeCount, durationMs: Date.now() - start };
3695
4243
  }
3696
4244
  function createScanCommand() {
3697
- return new Command39("scan").description("Scan project and build knowledge graph").argument("[path]", "Project root path", ".").action(async (inputPath, _opts, cmd) => {
3698
- const projectPath = path29.resolve(inputPath);
4245
+ return new Command45("scan").description("Scan project and build knowledge graph").argument("[path]", "Project root path", ".").action(async (inputPath, _opts, cmd) => {
4246
+ const projectPath = path35.resolve(inputPath);
3699
4247
  const globalOpts = cmd.optsWithGlobals();
3700
4248
  try {
3701
4249
  const result = await runScan(projectPath);
@@ -3714,13 +4262,13 @@ function createScanCommand() {
3714
4262
  }
3715
4263
 
3716
4264
  // src/commands/graph/ingest.ts
3717
- import { Command as Command40 } from "commander";
3718
- import * as path30 from "path";
4265
+ import { Command as Command46 } from "commander";
4266
+ import * as path36 from "path";
3719
4267
  async function loadConnectorConfig(projectPath, source) {
3720
4268
  try {
3721
- const fs23 = await import("fs/promises");
3722
- const configPath = path30.join(projectPath, "harness.config.json");
3723
- const config = JSON.parse(await fs23.readFile(configPath, "utf-8"));
4269
+ const fs29 = await import("fs/promises");
4270
+ const configPath = path36.join(projectPath, "harness.config.json");
4271
+ const config = JSON.parse(await fs29.readFile(configPath, "utf-8"));
3724
4272
  const connector = config.graph?.connectors?.find(
3725
4273
  (c) => c.source === source
3726
4274
  );
@@ -3760,7 +4308,7 @@ async function runIngest(projectPath, source, opts) {
3760
4308
  JiraConnector,
3761
4309
  SlackConnector
3762
4310
  } = await import("./dist-B26DFXMP.js");
3763
- const graphDir = path30.join(projectPath, ".harness", "graph");
4311
+ const graphDir = path36.join(projectPath, ".harness", "graph");
3764
4312
  const store = new GraphStore();
3765
4313
  await store.load(graphDir);
3766
4314
  if (opts?.all) {
@@ -3821,13 +4369,13 @@ async function runIngest(projectPath, source, opts) {
3821
4369
  return result;
3822
4370
  }
3823
4371
  function createIngestCommand() {
3824
- return new Command40("ingest").description("Ingest data into the knowledge graph").option("--source <name>", "Source to ingest (code, knowledge, git, jira, slack)").option("--all", "Run all sources (code, knowledge, git, and configured connectors)").option("--full", "Force full re-ingestion").action(async (opts, cmd) => {
4372
+ return new Command46("ingest").description("Ingest data into the knowledge graph").option("--source <name>", "Source to ingest (code, knowledge, git, jira, slack)").option("--all", "Run all sources (code, knowledge, git, and configured connectors)").option("--full", "Force full re-ingestion").action(async (opts, cmd) => {
3825
4373
  if (!opts.source && !opts.all) {
3826
4374
  console.error("Error: --source or --all is required");
3827
4375
  process.exit(1);
3828
4376
  }
3829
4377
  const globalOpts = cmd.optsWithGlobals();
3830
- const projectPath = path30.resolve(globalOpts.config ? path30.dirname(globalOpts.config) : ".");
4378
+ const projectPath = path36.resolve(globalOpts.config ? path36.dirname(globalOpts.config) : ".");
3831
4379
  try {
3832
4380
  const result = await runIngest(projectPath, opts.source ?? "", {
3833
4381
  full: opts.full,
@@ -3849,12 +4397,12 @@ function createIngestCommand() {
3849
4397
  }
3850
4398
 
3851
4399
  // src/commands/graph/query.ts
3852
- import { Command as Command41 } from "commander";
3853
- import * as path31 from "path";
4400
+ import { Command as Command47 } from "commander";
4401
+ import * as path37 from "path";
3854
4402
  async function runQuery(projectPath, rootNodeId, opts) {
3855
4403
  const { GraphStore, ContextQL } = await import("./dist-B26DFXMP.js");
3856
4404
  const store = new GraphStore();
3857
- const graphDir = path31.join(projectPath, ".harness", "graph");
4405
+ const graphDir = path37.join(projectPath, ".harness", "graph");
3858
4406
  const loaded = await store.load(graphDir);
3859
4407
  if (!loaded) throw new Error("No graph found. Run `harness scan` first.");
3860
4408
  const params = {
@@ -3868,9 +4416,9 @@ async function runQuery(projectPath, rootNodeId, opts) {
3868
4416
  return cql.execute(params);
3869
4417
  }
3870
4418
  function createQueryCommand() {
3871
- return new Command41("query").description("Query the knowledge graph").argument("<rootNodeId>", "Starting node ID").option("--depth <n>", "Max traversal depth", "3").option("--types <types>", "Comma-separated node types to include").option("--edges <edges>", "Comma-separated edge types to include").option("--bidirectional", "Traverse both directions").action(async (rootNodeId, opts, cmd) => {
4419
+ return new Command47("query").description("Query the knowledge graph").argument("<rootNodeId>", "Starting node ID").option("--depth <n>", "Max traversal depth", "3").option("--types <types>", "Comma-separated node types to include").option("--edges <edges>", "Comma-separated edge types to include").option("--bidirectional", "Traverse both directions").action(async (rootNodeId, opts, cmd) => {
3872
4420
  const globalOpts = cmd.optsWithGlobals();
3873
- const projectPath = path31.resolve(globalOpts.config ? path31.dirname(globalOpts.config) : ".");
4421
+ const projectPath = path37.resolve(globalOpts.config ? path37.dirname(globalOpts.config) : ".");
3874
4422
  try {
3875
4423
  const result = await runQuery(projectPath, rootNodeId, {
3876
4424
  depth: parseInt(opts.depth),
@@ -3896,21 +4444,21 @@ function createQueryCommand() {
3896
4444
  }
3897
4445
 
3898
4446
  // src/commands/graph/index.ts
3899
- import { Command as Command42 } from "commander";
4447
+ import { Command as Command48 } from "commander";
3900
4448
 
3901
4449
  // src/commands/graph/status.ts
3902
- import * as path32 from "path";
4450
+ import * as path38 from "path";
3903
4451
  async function runGraphStatus(projectPath) {
3904
4452
  const { GraphStore } = await import("./dist-B26DFXMP.js");
3905
- const graphDir = path32.join(projectPath, ".harness", "graph");
4453
+ const graphDir = path38.join(projectPath, ".harness", "graph");
3906
4454
  const store = new GraphStore();
3907
4455
  const loaded = await store.load(graphDir);
3908
4456
  if (!loaded) return { status: "no_graph", message: "No graph found. Run `harness scan` first." };
3909
- const fs23 = await import("fs/promises");
3910
- const metaPath = path32.join(graphDir, "metadata.json");
4457
+ const fs29 = await import("fs/promises");
4458
+ const metaPath = path38.join(graphDir, "metadata.json");
3911
4459
  let lastScan = "unknown";
3912
4460
  try {
3913
- const meta = JSON.parse(await fs23.readFile(metaPath, "utf-8"));
4461
+ const meta = JSON.parse(await fs29.readFile(metaPath, "utf-8"));
3914
4462
  lastScan = meta.lastScanTimestamp;
3915
4463
  } catch {
3916
4464
  }
@@ -3921,8 +4469,8 @@ async function runGraphStatus(projectPath) {
3921
4469
  }
3922
4470
  let connectorSyncStatus = {};
3923
4471
  try {
3924
- const syncMetaPath = path32.join(graphDir, "sync-metadata.json");
3925
- const syncMeta = JSON.parse(await fs23.readFile(syncMetaPath, "utf-8"));
4472
+ const syncMetaPath = path38.join(graphDir, "sync-metadata.json");
4473
+ const syncMeta = JSON.parse(await fs29.readFile(syncMetaPath, "utf-8"));
3926
4474
  for (const [name, data] of Object.entries(syncMeta.connectors ?? {})) {
3927
4475
  connectorSyncStatus[name] = data.lastSyncTimestamp;
3928
4476
  }
@@ -3939,10 +4487,10 @@ async function runGraphStatus(projectPath) {
3939
4487
  }
3940
4488
 
3941
4489
  // src/commands/graph/export.ts
3942
- import * as path33 from "path";
4490
+ import * as path39 from "path";
3943
4491
  async function runGraphExport(projectPath, format) {
3944
4492
  const { GraphStore } = await import("./dist-B26DFXMP.js");
3945
- const graphDir = path33.join(projectPath, ".harness", "graph");
4493
+ const graphDir = path39.join(projectPath, ".harness", "graph");
3946
4494
  const store = new GraphStore();
3947
4495
  const loaded = await store.load(graphDir);
3948
4496
  if (!loaded) throw new Error("No graph found. Run `harness scan` first.");
@@ -3971,13 +4519,13 @@ async function runGraphExport(projectPath, format) {
3971
4519
  }
3972
4520
 
3973
4521
  // src/commands/graph/index.ts
3974
- import * as path34 from "path";
4522
+ import * as path40 from "path";
3975
4523
  function createGraphCommand() {
3976
- const graph = new Command42("graph").description("Knowledge graph management");
4524
+ const graph = new Command48("graph").description("Knowledge graph management");
3977
4525
  graph.command("status").description("Show graph statistics").action(async (_opts, cmd) => {
3978
4526
  try {
3979
4527
  const globalOpts = cmd.optsWithGlobals();
3980
- const projectPath = path34.resolve(globalOpts.config ? path34.dirname(globalOpts.config) : ".");
4528
+ const projectPath = path40.resolve(globalOpts.config ? path40.dirname(globalOpts.config) : ".");
3981
4529
  const result = await runGraphStatus(projectPath);
3982
4530
  if (globalOpts.json) {
3983
4531
  console.log(JSON.stringify(result, null, 2));
@@ -4004,7 +4552,7 @@ function createGraphCommand() {
4004
4552
  });
4005
4553
  graph.command("export").description("Export graph").requiredOption("--format <format>", "Output format (json, mermaid)").action(async (opts, cmd) => {
4006
4554
  const globalOpts = cmd.optsWithGlobals();
4007
- const projectPath = path34.resolve(globalOpts.config ? path34.dirname(globalOpts.config) : ".");
4555
+ const projectPath = path40.resolve(globalOpts.config ? path40.dirname(globalOpts.config) : ".");
4008
4556
  try {
4009
4557
  const output = await runGraphExport(projectPath, opts.format);
4010
4558
  console.log(output);
@@ -4017,19 +4565,19 @@ function createGraphCommand() {
4017
4565
  }
4018
4566
 
4019
4567
  // src/commands/mcp.ts
4020
- import { Command as Command43 } from "commander";
4568
+ import { Command as Command49 } from "commander";
4021
4569
  function createMcpCommand() {
4022
- return new Command43("mcp").description("Start the MCP (Model Context Protocol) server on stdio").action(async () => {
4023
- const { startServer: startServer2 } = await import("./mcp-362EZHF4.js");
4570
+ return new Command49("mcp").description("Start the MCP (Model Context Protocol) server on stdio").action(async () => {
4571
+ const { startServer: startServer2 } = await import("./mcp-VU5FMO52.js");
4024
4572
  await startServer2();
4025
4573
  });
4026
4574
  }
4027
4575
 
4028
4576
  // src/commands/impact-preview.ts
4029
- import { Command as Command44 } from "commander";
4577
+ import { Command as Command50 } from "commander";
4030
4578
  import { execSync as execSync3 } from "child_process";
4031
- import * as path35 from "path";
4032
- import * as fs17 from "fs";
4579
+ import * as path41 from "path";
4580
+ import * as fs23 from "fs";
4033
4581
  function getStagedFiles(cwd) {
4034
4582
  try {
4035
4583
  const output = execSync3("git diff --cached --name-only", {
@@ -4043,7 +4591,7 @@ function getStagedFiles(cwd) {
4043
4591
  }
4044
4592
  function graphExists(projectPath) {
4045
4593
  try {
4046
- return fs17.existsSync(path35.join(projectPath, ".harness", "graph", "graph.json"));
4594
+ return fs23.existsSync(path41.join(projectPath, ".harness", "graph", "graph.json"));
4047
4595
  } catch {
4048
4596
  return false;
4049
4597
  }
@@ -4052,7 +4600,7 @@ function extractNodeName(id) {
4052
4600
  const parts = id.split(":");
4053
4601
  if (parts.length > 1) {
4054
4602
  const fullPath = parts.slice(1).join(":");
4055
- return path35.basename(fullPath);
4603
+ return path41.basename(fullPath);
4056
4604
  }
4057
4605
  return id;
4058
4606
  }
@@ -4175,7 +4723,7 @@ function formatPerFile(perFileResults) {
4175
4723
  return lines.join("\n");
4176
4724
  }
4177
4725
  async function runImpactPreview(options) {
4178
- const projectPath = path35.resolve(options.path ?? process.cwd());
4726
+ const projectPath = path41.resolve(options.path ?? process.cwd());
4179
4727
  const stagedFiles = getStagedFiles(projectPath);
4180
4728
  if (stagedFiles.length === 0) {
4181
4729
  return "Impact Preview: no staged changes";
@@ -4222,7 +4770,7 @@ async function runImpactPreview(options) {
4222
4770
  return formatCompact(stagedFiles.length, merged, aggregateCounts);
4223
4771
  }
4224
4772
  function createImpactPreviewCommand() {
4225
- const command = new Command44("impact-preview").description("Show blast radius of staged changes using the knowledge graph").option("--detailed", "Show all affected files instead of top items").option("--per-file", "Show impact per staged file instead of aggregate").option("--path <dir>", "Project root (default: cwd)").action(async (opts) => {
4773
+ const command = new Command50("impact-preview").description("Show blast radius of staged changes using the knowledge graph").option("--detailed", "Show all affected files instead of top items").option("--per-file", "Show impact per staged file instead of aggregate").option("--path <dir>", "Project root (default: cwd)").action(async (opts) => {
4226
4774
  const output = await runImpactPreview({
4227
4775
  detailed: opts.detailed,
4228
4776
  perFile: opts.perFile,
@@ -4235,7 +4783,7 @@ function createImpactPreviewCommand() {
4235
4783
  }
4236
4784
 
4237
4785
  // src/commands/check-arch.ts
4238
- import { Command as Command45 } from "commander";
4786
+ import { Command as Command51 } from "commander";
4239
4787
  import { execSync as execSync4 } from "child_process";
4240
4788
  function getCommitHash2(cwd) {
4241
4789
  try {
@@ -4330,7 +4878,7 @@ async function runCheckArch(options) {
4330
4878
  });
4331
4879
  }
4332
4880
  function createCheckArchCommand() {
4333
- const command = new Command45("check-arch").description("Check architecture assertions against baseline and thresholds").option("--update-baseline", "Capture current state as new baseline").option("--module <path>", "Check a single module").action(async (opts, cmd) => {
4881
+ const command = new Command51("check-arch").description("Check architecture assertions against baseline and thresholds").option("--update-baseline", "Capture current state as new baseline").option("--module <path>", "Check a single module").action(async (opts, cmd) => {
4334
4882
  const globalOpts = cmd.optsWithGlobals();
4335
4883
  const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : globalOpts.verbose ? OutputMode.VERBOSE : OutputMode.TEXT;
4336
4884
  const formatter = new OutputFormatter(mode);
@@ -4396,20 +4944,20 @@ function createCheckArchCommand() {
4396
4944
  }
4397
4945
 
4398
4946
  // src/commands/blueprint.ts
4399
- import { Command as Command46 } from "commander";
4400
- import * as path36 from "path";
4947
+ import { Command as Command52 } from "commander";
4948
+ import * as path42 from "path";
4401
4949
  function createBlueprintCommand() {
4402
- return new Command46("blueprint").description("Generate a self-contained, interactive blueprint of the codebase").argument("[path]", "Path to the project root", ".").option("-o, --output <dir>", "Output directory", "docs/blueprint").action(async (projectPath, options) => {
4950
+ return new Command52("blueprint").description("Generate a self-contained, interactive blueprint of the codebase").argument("[path]", "Path to the project root", ".").option("-o, --output <dir>", "Output directory", "docs/blueprint").action(async (projectPath, options) => {
4403
4951
  try {
4404
- const rootDir = path36.resolve(projectPath);
4405
- const outputDir = path36.resolve(options.output);
4952
+ const rootDir = path42.resolve(projectPath);
4953
+ const outputDir = path42.resolve(options.output);
4406
4954
  logger.info(`Scanning project at ${rootDir}...`);
4407
4955
  const scanner = new ProjectScanner(rootDir);
4408
4956
  const data = await scanner.scan();
4409
4957
  logger.info(`Generating blueprint to ${outputDir}...`);
4410
4958
  const generator = new BlueprintGenerator();
4411
4959
  await generator.generate(data, { outputDir });
4412
- logger.success(`Blueprint generated successfully at ${path36.join(outputDir, "index.html")}`);
4960
+ logger.success(`Blueprint generated successfully at ${path42.join(outputDir, "index.html")}`);
4413
4961
  } catch (error) {
4414
4962
  logger.error(
4415
4963
  `Failed to generate blueprint: ${error instanceof Error ? error.message : String(error)}`
@@ -4420,16 +4968,16 @@ function createBlueprintCommand() {
4420
4968
  }
4421
4969
 
4422
4970
  // src/commands/share.ts
4423
- import { Command as Command47 } from "commander";
4424
- import * as fs18 from "fs";
4425
- import * as path37 from "path";
4971
+ import { Command as Command53 } from "commander";
4972
+ import * as fs24 from "fs";
4973
+ import * as path43 from "path";
4426
4974
  import { parse as parseYaml } from "yaml";
4427
4975
  var MANIFEST_FILENAME = "constraints.yaml";
4428
4976
  function createShareCommand() {
4429
- return new Command47("share").description("Extract and publish a constraints bundle from constraints.yaml").argument("[path]", "Path to the project root", ".").option("-o, --output <dir>", "Output directory for the bundle", ".").action(async (projectPath, options) => {
4430
- const rootDir = path37.resolve(projectPath);
4431
- const manifestPath = path37.join(rootDir, MANIFEST_FILENAME);
4432
- if (!fs18.existsSync(manifestPath)) {
4977
+ return new Command53("share").description("Extract and publish a constraints bundle from constraints.yaml").argument("[path]", "Path to the project root", ".").option("-o, --output <dir>", "Output directory for the bundle", ".").action(async (projectPath, options) => {
4978
+ const rootDir = path43.resolve(projectPath);
4979
+ const manifestPath = path43.join(rootDir, MANIFEST_FILENAME);
4980
+ if (!fs24.existsSync(manifestPath)) {
4433
4981
  logger.error(
4434
4982
  `No ${MANIFEST_FILENAME} found at ${manifestPath}.
4435
4983
  Create a constraints.yaml in your project root to define what to share.`
@@ -4438,7 +4986,7 @@ Create a constraints.yaml in your project root to define what to share.`
4438
4986
  }
4439
4987
  let parsed;
4440
4988
  try {
4441
- const raw = fs18.readFileSync(manifestPath, "utf-8");
4989
+ const raw = fs24.readFileSync(manifestPath, "utf-8");
4442
4990
  parsed = parseYaml(raw);
4443
4991
  } catch (err) {
4444
4992
  logger.error(
@@ -4452,7 +5000,7 @@ Create a constraints.yaml in your project root to define what to share.`
4452
5000
  process.exit(1);
4453
5001
  }
4454
5002
  const manifest = manifestResult.value;
4455
- const configResult = resolveConfig(path37.join(rootDir, "harness.config.json"));
5003
+ const configResult = resolveConfig(path43.join(rootDir, "harness.config.json"));
4456
5004
  if (!configResult.ok) {
4457
5005
  logger.error(configResult.error.message);
4458
5006
  process.exit(1);
@@ -4470,8 +5018,8 @@ Create a constraints.yaml in your project root to define what to share.`
4470
5018
  );
4471
5019
  process.exit(1);
4472
5020
  }
4473
- const outputDir = path37.resolve(options.output);
4474
- const outputPath = path37.join(outputDir, `${manifest.name}.harness-constraints.json`);
5021
+ const outputDir = path43.resolve(options.output);
5022
+ const outputPath = path43.join(outputDir, `${manifest.name}.harness-constraints.json`);
4475
5023
  const writeResult = await writeConfig(outputPath, bundle);
4476
5024
  if (!writeResult.ok) {
4477
5025
  logger.error(`Failed to write bundle: ${writeResult.error.message}`);
@@ -4482,25 +5030,25 @@ Create a constraints.yaml in your project root to define what to share.`
4482
5030
  }
4483
5031
 
4484
5032
  // src/commands/install.ts
4485
- import * as fs20 from "fs";
4486
- import * as path39 from "path";
4487
- import { Command as Command48 } from "commander";
5033
+ import * as fs26 from "fs";
5034
+ import * as path45 from "path";
5035
+ import { Command as Command54 } from "commander";
4488
5036
  import { parse as yamlParse } from "yaml";
4489
5037
 
4490
5038
  // src/registry/tarball.ts
4491
- import * as fs19 from "fs";
4492
- import * as path38 from "path";
4493
- import * as os3 from "os";
5039
+ import * as fs25 from "fs";
5040
+ import * as path44 from "path";
5041
+ import * as os6 from "os";
4494
5042
  import { execFileSync as execFileSync5 } from "child_process";
4495
5043
  function extractTarball(tarballBuffer) {
4496
- const tmpDir = fs19.mkdtempSync(path38.join(os3.tmpdir(), "harness-skill-install-"));
4497
- const tarballPath = path38.join(tmpDir, "package.tgz");
5044
+ const tmpDir = fs25.mkdtempSync(path44.join(os6.tmpdir(), "harness-skill-install-"));
5045
+ const tarballPath = path44.join(tmpDir, "package.tgz");
4498
5046
  try {
4499
- fs19.writeFileSync(tarballPath, tarballBuffer);
5047
+ fs25.writeFileSync(tarballPath, tarballBuffer);
4500
5048
  execFileSync5("tar", ["-xzf", tarballPath, "-C", tmpDir], {
4501
5049
  timeout: 3e4
4502
5050
  });
4503
- fs19.unlinkSync(tarballPath);
5051
+ fs25.unlinkSync(tarballPath);
4504
5052
  } catch (err) {
4505
5053
  cleanupTempDir(tmpDir);
4506
5054
  throw new Error(
@@ -4511,43 +5059,43 @@ function extractTarball(tarballBuffer) {
4511
5059
  return tmpDir;
4512
5060
  }
4513
5061
  function placeSkillContent(extractedPkgDir, communityBaseDir, skillName, platforms) {
4514
- const files = fs19.readdirSync(extractedPkgDir);
5062
+ const files = fs25.readdirSync(extractedPkgDir);
4515
5063
  for (const platform of platforms) {
4516
- const targetDir = path38.join(communityBaseDir, platform, skillName);
4517
- if (fs19.existsSync(targetDir)) {
4518
- fs19.rmSync(targetDir, { recursive: true, force: true });
5064
+ const targetDir = path44.join(communityBaseDir, platform, skillName);
5065
+ if (fs25.existsSync(targetDir)) {
5066
+ fs25.rmSync(targetDir, { recursive: true, force: true });
4519
5067
  }
4520
- fs19.mkdirSync(targetDir, { recursive: true });
5068
+ fs25.mkdirSync(targetDir, { recursive: true });
4521
5069
  for (const file of files) {
4522
5070
  if (file === "package.json" || file === "node_modules") continue;
4523
- const srcPath = path38.join(extractedPkgDir, file);
4524
- const destPath = path38.join(targetDir, file);
4525
- const stat = fs19.statSync(srcPath);
5071
+ const srcPath = path44.join(extractedPkgDir, file);
5072
+ const destPath = path44.join(targetDir, file);
5073
+ const stat = fs25.statSync(srcPath);
4526
5074
  if (stat.isDirectory()) {
4527
- fs19.cpSync(srcPath, destPath, { recursive: true });
5075
+ fs25.cpSync(srcPath, destPath, { recursive: true });
4528
5076
  } else {
4529
- fs19.copyFileSync(srcPath, destPath);
5077
+ fs25.copyFileSync(srcPath, destPath);
4530
5078
  }
4531
5079
  }
4532
5080
  }
4533
5081
  }
4534
5082
  function removeSkillContent(communityBaseDir, skillName, platforms) {
4535
5083
  for (const platform of platforms) {
4536
- const targetDir = path38.join(communityBaseDir, platform, skillName);
4537
- if (fs19.existsSync(targetDir)) {
4538
- fs19.rmSync(targetDir, { recursive: true, force: true });
5084
+ const targetDir = path44.join(communityBaseDir, platform, skillName);
5085
+ if (fs25.existsSync(targetDir)) {
5086
+ fs25.rmSync(targetDir, { recursive: true, force: true });
4539
5087
  }
4540
5088
  }
4541
5089
  }
4542
5090
  function cleanupTempDir(dirPath) {
4543
5091
  try {
4544
- fs19.rmSync(dirPath, { recursive: true, force: true });
5092
+ fs25.rmSync(dirPath, { recursive: true, force: true });
4545
5093
  } catch {
4546
5094
  }
4547
5095
  }
4548
5096
 
4549
5097
  // src/registry/resolver.ts
4550
- import semver2 from "semver";
5098
+ import semver3 from "semver";
4551
5099
  function resolveVersion(metadata, versionRange) {
4552
5100
  const versions = Object.keys(metadata.versions);
4553
5101
  if (versions.length === 0) {
@@ -4559,13 +5107,13 @@ function resolveVersion(metadata, versionRange) {
4559
5107
  const latestInfo = metadata.versions[latestTag];
4560
5108
  if (latestInfo) return latestInfo;
4561
5109
  }
4562
- const highest = semver2.maxSatisfying(versions, "*");
5110
+ const highest = semver3.maxSatisfying(versions, "*");
4563
5111
  if (!highest || !metadata.versions[highest]) {
4564
5112
  throw new Error(`No versions available for ${metadata.name}.`);
4565
5113
  }
4566
5114
  return metadata.versions[highest];
4567
5115
  }
4568
- const matched = semver2.maxSatisfying(versions, versionRange);
5116
+ const matched = semver3.maxSatisfying(versions, versionRange);
4569
5117
  if (!matched || !metadata.versions[matched]) {
4570
5118
  throw new Error(
4571
5119
  `No version of ${metadata.name} matches range ${versionRange}. Available: ${versions.join(", ")}`
@@ -4597,35 +5145,35 @@ function validateSkillYaml(parsed) {
4597
5145
  };
4598
5146
  }
4599
5147
  async function runLocalInstall(fromPath, options) {
4600
- const resolvedPath = path39.resolve(fromPath);
4601
- if (!fs20.existsSync(resolvedPath)) {
5148
+ const resolvedPath = path45.resolve(fromPath);
5149
+ if (!fs26.existsSync(resolvedPath)) {
4602
5150
  throw new Error(`--from path does not exist: ${resolvedPath}`);
4603
5151
  }
4604
- const stat = fs20.statSync(resolvedPath);
5152
+ const stat = fs26.statSync(resolvedPath);
4605
5153
  let extractDir = null;
4606
5154
  let pkgDir;
4607
5155
  if (stat.isDirectory()) {
4608
5156
  pkgDir = resolvedPath;
4609
5157
  } else if (resolvedPath.endsWith(".tgz") || resolvedPath.endsWith(".tar.gz")) {
4610
- const tarballBuffer = fs20.readFileSync(resolvedPath);
5158
+ const tarballBuffer = fs26.readFileSync(resolvedPath);
4611
5159
  extractDir = extractTarball(tarballBuffer);
4612
- pkgDir = path39.join(extractDir, "package");
5160
+ pkgDir = path45.join(extractDir, "package");
4613
5161
  } else {
4614
5162
  throw new Error(`--from path must be a directory or .tgz file. Got: ${resolvedPath}`);
4615
5163
  }
4616
5164
  try {
4617
- const skillYamlPath = path39.join(pkgDir, "skill.yaml");
4618
- if (!fs20.existsSync(skillYamlPath)) {
5165
+ const skillYamlPath = path45.join(pkgDir, "skill.yaml");
5166
+ if (!fs26.existsSync(skillYamlPath)) {
4619
5167
  throw new Error(`No skill.yaml found at ${skillYamlPath}`);
4620
5168
  }
4621
- const rawYaml = fs20.readFileSync(skillYamlPath, "utf-8");
5169
+ const rawYaml = fs26.readFileSync(skillYamlPath, "utf-8");
4622
5170
  const parsed = yamlParse(rawYaml);
4623
5171
  const skillYaml = validateSkillYaml(parsed);
4624
5172
  const shortName = skillYaml.name;
4625
5173
  const globalDir = resolveGlobalSkillsDir();
4626
- const skillsDir = path39.dirname(globalDir);
4627
- const communityBase = path39.join(skillsDir, "community");
4628
- const lockfilePath = path39.join(communityBase, "skills-lock.json");
5174
+ const skillsDir = path45.dirname(globalDir);
5175
+ const communityBase = path45.join(skillsDir, "community");
5176
+ const lockfilePath = path45.join(communityBase, "skills-lock.json");
4629
5177
  const bundledNames = getBundledSkillNames(globalDir);
4630
5178
  if (bundledNames.has(shortName)) {
4631
5179
  throw new Error(
@@ -4666,9 +5214,9 @@ async function runInstall(skillName, options) {
4666
5214
  const packageName = resolvePackageName(skillName);
4667
5215
  const shortName = extractSkillName(packageName);
4668
5216
  const globalDir = resolveGlobalSkillsDir();
4669
- const skillsDir = path39.dirname(globalDir);
4670
- const communityBase = path39.join(skillsDir, "community");
4671
- const lockfilePath = path39.join(communityBase, "skills-lock.json");
5217
+ const skillsDir = path45.dirname(globalDir);
5218
+ const communityBase = path45.join(skillsDir, "community");
5219
+ const lockfilePath = path45.join(communityBase, "skills-lock.json");
4672
5220
  const bundledNames = getBundledSkillNames(globalDir);
4673
5221
  if (bundledNames.has(shortName)) {
4674
5222
  throw new Error(
@@ -4694,12 +5242,12 @@ async function runInstall(skillName, options) {
4694
5242
  const extractDir = extractTarball(tarballBuffer);
4695
5243
  let skillYaml;
4696
5244
  try {
4697
- const extractedPkgDir = path39.join(extractDir, "package");
4698
- const skillYamlPath = path39.join(extractedPkgDir, "skill.yaml");
4699
- if (!fs20.existsSync(skillYamlPath)) {
5245
+ const extractedPkgDir = path45.join(extractDir, "package");
5246
+ const skillYamlPath = path45.join(extractedPkgDir, "skill.yaml");
5247
+ if (!fs26.existsSync(skillYamlPath)) {
4700
5248
  throw new Error(`contains invalid skill.yaml: file not found in package`);
4701
5249
  }
4702
- const rawYaml = fs20.readFileSync(skillYamlPath, "utf-8");
5250
+ const rawYaml = fs26.readFileSync(skillYamlPath, "utf-8");
4703
5251
  const parsed = yamlParse(rawYaml);
4704
5252
  skillYaml = validateSkillYaml(parsed);
4705
5253
  placeSkillContent(extractedPkgDir, communityBase, shortName, skillYaml.platforms);
@@ -4738,7 +5286,7 @@ async function runInstall(skillName, options) {
4738
5286
  return result;
4739
5287
  }
4740
5288
  function createInstallCommand() {
4741
- const cmd = new Command48("install");
5289
+ const cmd = new Command54("install");
4742
5290
  cmd.description("Install a community skill from the @harness-skills registry").argument("<skill>", "Skill name or @harness-skills/scoped package name").option("--version <range>", "Semver range or exact version to install").option("--force", "Force reinstall even if same version is already installed").option("--from <path>", "Install from a local directory or .tgz file").option("--registry <url>", "Use a custom npm registry URL").action(async (skill, opts) => {
4743
5291
  try {
4744
5292
  const result = await runInstall(skill, opts);
@@ -4762,15 +5310,15 @@ function createInstallCommand() {
4762
5310
  }
4763
5311
 
4764
5312
  // src/commands/install-constraints.ts
4765
- import * as fs21 from "fs/promises";
4766
- import * as path40 from "path";
4767
- import { Command as Command49 } from "commander";
4768
- import semver3 from "semver";
5313
+ import * as fs27 from "fs/promises";
5314
+ import * as path46 from "path";
5315
+ import { Command as Command55 } from "commander";
5316
+ import semver4 from "semver";
4769
5317
  async function runInstallConstraints(options) {
4770
5318
  const { source, configPath, lockfilePath } = options;
4771
5319
  let rawBundle;
4772
5320
  try {
4773
- rawBundle = await fs21.readFile(source, "utf-8");
5321
+ rawBundle = await fs27.readFile(source, "utf-8");
4774
5322
  } catch (err) {
4775
5323
  if (isNodeError(err) && err.code === "ENOENT") {
4776
5324
  return { ok: false, error: `Bundle file not found: ${source}` };
@@ -4793,9 +5341,9 @@ async function runInstallConstraints(options) {
4793
5341
  }
4794
5342
  const bundle = bundleResult.data;
4795
5343
  if (bundle.minHarnessVersion) {
4796
- const installed = semver3.valid(semver3.coerce(CLI_VERSION));
4797
- const required = semver3.valid(semver3.coerce(bundle.minHarnessVersion));
4798
- if (installed && required && semver3.lt(installed, required)) {
5344
+ const installed = semver4.valid(semver4.coerce(CLI_VERSION));
5345
+ const required = semver4.valid(semver4.coerce(bundle.minHarnessVersion));
5346
+ if (installed && required && semver4.lt(installed, required)) {
4799
5347
  return {
4800
5348
  ok: false,
4801
5349
  error: `Bundle requires harness version >= ${bundle.minHarnessVersion}, but installed version is ${CLI_VERSION}. Please upgrade.`
@@ -4813,7 +5361,7 @@ async function runInstallConstraints(options) {
4813
5361
  }
4814
5362
  let localConfig;
4815
5363
  try {
4816
- const raw = await fs21.readFile(configPath, "utf-8");
5364
+ const raw = await fs27.readFile(configPath, "utf-8");
4817
5365
  localConfig = JSON.parse(raw);
4818
5366
  } catch (err) {
4819
5367
  return {
@@ -4961,7 +5509,7 @@ function isNodeError(err) {
4961
5509
  return err instanceof Error && "code" in err;
4962
5510
  }
4963
5511
  function resolveConfigPath(opts) {
4964
- if (opts.config) return path40.resolve(opts.config);
5512
+ if (opts.config) return path46.resolve(opts.config);
4965
5513
  const found = findConfigFile();
4966
5514
  if (!found.ok) {
4967
5515
  logger.error(found.error.message);
@@ -4996,9 +5544,9 @@ function logInstallResult(val, opts) {
4996
5544
  }
4997
5545
  async function handleInstallConstraints(source, opts) {
4998
5546
  const configPath = resolveConfigPath(opts);
4999
- const projectRoot = path40.dirname(configPath);
5000
- const lockfilePath = path40.join(projectRoot, ".harness", "constraints.lock.json");
5001
- const resolvedSource = path40.resolve(source);
5547
+ const projectRoot = path46.dirname(configPath);
5548
+ const lockfilePath = path46.join(projectRoot, ".harness", "constraints.lock.json");
5549
+ const resolvedSource = path46.resolve(source);
5002
5550
  if (opts.forceLocal && opts.forcePackage) {
5003
5551
  logger.error("Cannot use both --force-local and --force-package.");
5004
5552
  process.exit(1);
@@ -5018,15 +5566,15 @@ async function handleInstallConstraints(source, opts) {
5018
5566
  logInstallResult(result.value, opts);
5019
5567
  }
5020
5568
  function createInstallConstraintsCommand() {
5021
- const cmd = new Command49("install-constraints");
5569
+ const cmd = new Command55("install-constraints");
5022
5570
  cmd.description("Install a constraints bundle into the local harness config").argument("<source>", "Path to a .harness-constraints.json bundle file").option("--force-local", "Resolve all conflicts by keeping local values").option("--force-package", "Resolve all conflicts by using package values").option("--dry-run", "Show what would change without writing files").option("-c, --config <path>", "Path to harness.config.json").action(handleInstallConstraints);
5023
5571
  return cmd;
5024
5572
  }
5025
5573
 
5026
5574
  // src/commands/uninstall-constraints.ts
5027
- import * as fs22 from "fs/promises";
5028
- import * as path41 from "path";
5029
- import { Command as Command50 } from "commander";
5575
+ import * as fs28 from "fs/promises";
5576
+ import * as path47 from "path";
5577
+ import { Command as Command56 } from "commander";
5030
5578
  async function runUninstallConstraints(options) {
5031
5579
  const { packageName, configPath, lockfilePath } = options;
5032
5580
  const lockfileResult = await readLockfile(lockfilePath);
@@ -5046,7 +5594,7 @@ async function runUninstallConstraints(options) {
5046
5594
  }
5047
5595
  let localConfig;
5048
5596
  try {
5049
- const raw = await fs22.readFile(configPath, "utf-8");
5597
+ const raw = await fs28.readFile(configPath, "utf-8");
5050
5598
  localConfig = JSON.parse(raw);
5051
5599
  } catch (err) {
5052
5600
  return {
@@ -5083,11 +5631,11 @@ async function runUninstallConstraints(options) {
5083
5631
  };
5084
5632
  }
5085
5633
  function createUninstallConstraintsCommand() {
5086
- const cmd = new Command50("uninstall-constraints");
5634
+ const cmd = new Command56("uninstall-constraints");
5087
5635
  cmd.description("Remove a previously installed constraints package").argument("<name>", "Name of the constraint package to uninstall").option("-c, --config <path>", "Path to harness.config.json").action(async (name, opts) => {
5088
5636
  let configPath;
5089
5637
  if (opts.config) {
5090
- configPath = path41.resolve(opts.config);
5638
+ configPath = path47.resolve(opts.config);
5091
5639
  } else {
5092
5640
  const found = findConfigFile();
5093
5641
  if (!found.ok) {
@@ -5096,8 +5644,8 @@ function createUninstallConstraintsCommand() {
5096
5644
  }
5097
5645
  configPath = found.value;
5098
5646
  }
5099
- const projectRoot = path41.dirname(configPath);
5100
- const lockfilePath = path41.join(projectRoot, ".harness", "constraints.lock.json");
5647
+ const projectRoot = path47.dirname(configPath);
5648
+ const lockfilePath = path47.join(projectRoot, ".harness", "constraints.lock.json");
5101
5649
  const result = await runUninstallConstraints({
5102
5650
  packageName: name,
5103
5651
  configPath,
@@ -5122,15 +5670,15 @@ function createUninstallConstraintsCommand() {
5122
5670
  }
5123
5671
 
5124
5672
  // src/commands/uninstall.ts
5125
- import * as path42 from "path";
5126
- import { Command as Command51 } from "commander";
5673
+ import * as path48 from "path";
5674
+ import { Command as Command57 } from "commander";
5127
5675
  async function runUninstall(skillName, options) {
5128
5676
  const packageName = resolvePackageName(skillName);
5129
5677
  const shortName = extractSkillName(packageName);
5130
5678
  const globalDir = resolveGlobalSkillsDir();
5131
- const skillsDir = path42.dirname(globalDir);
5132
- const communityBase = path42.join(skillsDir, "community");
5133
- const lockfilePath = path42.join(communityBase, "skills-lock.json");
5679
+ const skillsDir = path48.dirname(globalDir);
5680
+ const communityBase = path48.join(skillsDir, "community");
5681
+ const lockfilePath = path48.join(communityBase, "skills-lock.json");
5134
5682
  const lockfile = readLockfile2(lockfilePath);
5135
5683
  const entry = lockfile.skills[packageName];
5136
5684
  if (!entry) {
@@ -5160,7 +5708,7 @@ async function runUninstall(skillName, options) {
5160
5708
  return result;
5161
5709
  }
5162
5710
  function createUninstallCommand() {
5163
- const cmd = new Command51("uninstall");
5711
+ const cmd = new Command57("uninstall");
5164
5712
  cmd.description("Uninstall a community skill").argument("<skill>", "Skill name or @harness-skills/scoped package name").option("--force", "Remove even if other skills depend on this one").action(async (skill, opts) => {
5165
5713
  try {
5166
5714
  const result = await runUninstall(skill, opts);
@@ -5179,13 +5727,13 @@ function createUninstallCommand() {
5179
5727
  }
5180
5728
 
5181
5729
  // src/commands/orchestrator.ts
5182
- import { Command as Command52 } from "commander";
5183
- import * as path43 from "path";
5730
+ import { Command as Command58 } from "commander";
5731
+ import * as path49 from "path";
5184
5732
  import { Orchestrator, WorkflowLoader, launchTUI } from "@harness-engineering/orchestrator";
5185
5733
  function createOrchestratorCommand() {
5186
- const orchestrator = new Command52("orchestrator");
5734
+ const orchestrator = new Command58("orchestrator");
5187
5735
  orchestrator.command("run").description("Run the orchestrator daemon").option("-w, --workflow <path>", "Path to WORKFLOW.md", "WORKFLOW.md").action(async (opts) => {
5188
- const workflowPath = path43.resolve(process.cwd(), opts.workflow);
5736
+ const workflowPath = path49.resolve(process.cwd(), opts.workflow);
5189
5737
  const loader = new WorkflowLoader();
5190
5738
  const result = await loader.loadWorkflow(workflowPath);
5191
5739
  if (!result.ok) {
@@ -5209,13 +5757,13 @@ function createOrchestratorCommand() {
5209
5757
  }
5210
5758
 
5211
5759
  // src/commands/learnings/index.ts
5212
- import { Command as Command54 } from "commander";
5760
+ import { Command as Command60 } from "commander";
5213
5761
 
5214
5762
  // src/commands/learnings/prune.ts
5215
- import { Command as Command53 } from "commander";
5216
- import * as path44 from "path";
5763
+ import { Command as Command59 } from "commander";
5764
+ import * as path50 from "path";
5217
5765
  async function handlePrune(opts) {
5218
- const projectPath = path44.resolve(opts.path);
5766
+ const projectPath = path50.resolve(opts.path);
5219
5767
  const result = await pruneLearnings(projectPath, opts.stream);
5220
5768
  if (!result.ok) {
5221
5769
  logger.error(result.error.message);
@@ -5254,21 +5802,21 @@ function printPatternProposals(patterns) {
5254
5802
  );
5255
5803
  }
5256
5804
  function createPruneCommand() {
5257
- return new Command53("prune").description(
5805
+ return new Command59("prune").description(
5258
5806
  "Analyze global learnings for patterns, present improvement proposals, and archive old entries"
5259
5807
  ).option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").action(handlePrune);
5260
5808
  }
5261
5809
 
5262
5810
  // src/commands/learnings/index.ts
5263
5811
  function createLearningsCommand() {
5264
- const command = new Command54("learnings").description("Learnings management commands");
5812
+ const command = new Command60("learnings").description("Learnings management commands");
5265
5813
  command.addCommand(createPruneCommand());
5266
5814
  return command;
5267
5815
  }
5268
5816
 
5269
5817
  // src/index.ts
5270
5818
  function createProgram() {
5271
- const program = new Command55();
5819
+ const program = new Command61();
5272
5820
  program.name("harness").description("CLI for Harness Engineering toolkit").version(CLI_VERSION).option("-c, --config <path>", "Path to config file").option("--json", "Output as JSON").option("--verbose", "Verbose output").option("--quiet", "Minimal output");
5273
5821
  program.addCommand(createValidateCommand());
5274
5822
  program.addCommand(createCheckDepsCommand());
@@ -5289,10 +5837,13 @@ function createProgram() {
5289
5837
  program.addCommand(createCheckPhaseGateCommand());
5290
5838
  program.addCommand(createCreateSkillCommand());
5291
5839
  program.addCommand(createSetupMcpCommand());
5840
+ program.addCommand(createSetupCommand());
5841
+ program.addCommand(createDoctorCommand());
5292
5842
  program.addCommand(createGenerateSlashCommandsCommand());
5293
5843
  program.addCommand(createGenerateAgentDefinitionsCommand());
5294
5844
  program.addCommand(createGenerateCommand3());
5295
5845
  program.addCommand(createCICommand());
5846
+ program.addCommand(createHooksCommand());
5296
5847
  program.addCommand(createUpdateCommand());
5297
5848
  program.addCommand(createScanCommand());
5298
5849
  program.addCommand(createIngestCommand());
@@ -5313,6 +5864,7 @@ function createProgram() {
5313
5864
 
5314
5865
  export {
5315
5866
  buildPreamble,
5867
+ printFirstRunWelcome,
5316
5868
  runScan,
5317
5869
  runIngest,
5318
5870
  runQuery,