@harness-engineering/cli 1.13.1 → 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 (147) 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-brainstorming/SKILL.md +39 -0
  4. package/dist/agents/skills/claude-code/harness-code-review/SKILL.md +44 -0
  5. package/dist/agents/skills/claude-code/harness-execution/SKILL.md +44 -0
  6. package/dist/agents/skills/claude-code/harness-planning/SKILL.md +39 -0
  7. package/dist/agents/skills/claude-code/harness-product-spec/SKILL.md +5 -5
  8. package/dist/agents/skills/claude-code/harness-release-readiness/SKILL.md +3 -3
  9. package/dist/agents/skills/claude-code/harness-verification/SKILL.md +35 -0
  10. package/dist/agents/skills/claude-code/initialize-harness-project/SKILL.md +11 -3
  11. package/dist/agents/skills/gemini-cli/harness-autopilot/SKILL.md +240 -39
  12. package/dist/agents/skills/gemini-cli/harness-autopilot/skill.yaml +6 -0
  13. package/dist/agents/skills/gemini-cli/harness-brainstorming/SKILL.md +39 -0
  14. package/dist/agents/skills/gemini-cli/harness-code-review/SKILL.md +44 -0
  15. package/dist/agents/skills/gemini-cli/harness-execution/SKILL.md +44 -0
  16. package/dist/agents/skills/gemini-cli/harness-planning/SKILL.md +39 -0
  17. package/dist/agents/skills/gemini-cli/harness-product-spec/SKILL.md +5 -5
  18. package/dist/agents/skills/gemini-cli/harness-release-readiness/SKILL.md +3 -3
  19. package/dist/agents/skills/gemini-cli/harness-verification/SKILL.md +35 -0
  20. package/dist/agents/skills/gemini-cli/initialize-harness-project/SKILL.md +11 -3
  21. package/dist/agents/skills/package.json +1 -0
  22. package/dist/agents/skills/vitest.config.mts +5 -0
  23. package/dist/agents-md-ZGNIDWAF.js +8 -0
  24. package/dist/{architecture-2R5Z4ZAF.js → architecture-ZLIH5533.js} +4 -4
  25. package/dist/bin/harness-mcp.js +14 -14
  26. package/dist/bin/harness.js +27 -25
  27. package/dist/{check-phase-gate-2OFZ7OWW.js → check-phase-gate-ZOXVBDCN.js} +4 -4
  28. package/dist/{chunk-ND6PNADU.js → chunk-2BKLWLY6.js} +9 -9
  29. package/dist/{chunk-65FRIL4D.js → chunk-3ZZKVN62.js} +1 -1
  30. package/dist/{chunk-C2ERUR3L.js → chunk-7MJAPE3Z.js} +165 -49
  31. package/dist/{chunk-Z77YQRQT.js → chunk-B2HKP423.js} +16 -5
  32. package/dist/{chunk-QPEH2QPG.js → chunk-DBSOCI3G.js} +53 -54
  33. package/dist/{chunk-TKJZKICB.js → chunk-EDXIVMAP.js} +7 -7
  34. package/dist/{chunk-MHBMTPW7.js → chunk-ERS5EVUZ.js} +9 -0
  35. package/dist/{chunk-JSTQ3AWB.js → chunk-FIAPHX37.js} +1 -1
  36. package/dist/{chunk-IMFVFNJE.js → chunk-FTMXDOR6.js} +1 -1
  37. package/dist/{chunk-72GHBOL2.js → chunk-GZKSBLQL.js} +1 -1
  38. package/dist/{chunk-K6XAPGML.js → chunk-H7Y5CKTM.js} +1 -1
  39. package/dist/{chunk-SSKDAOX5.js → chunk-J4RAX7YB.js} +1164 -516
  40. package/dist/{chunk-UAX4I5ZE.js → chunk-LGYBN7Y6.js} +2 -2
  41. package/dist/{chunk-QY4T6YAZ.js → chunk-N25INEIX.js} +4 -4
  42. package/dist/{chunk-4ZMOCPYO.js → chunk-ND2ENWDM.js} +1 -1
  43. package/dist/{chunk-NERR4TAO.js → chunk-NNHDDXYT.js} +1250 -765
  44. package/dist/{chunk-NKDM3FMH.js → chunk-OD3S2NHN.js} +1 -1
  45. package/dist/{chunk-NOPU4RZ4.js → chunk-OFXQSFOW.js} +3 -3
  46. package/dist/{chunk-TS3XWPW5.js → chunk-RCWZBSK5.js} +1 -1
  47. package/dist/{chunk-VUCPTQ6G.js → chunk-SD3SQOZ2.js} +1 -1
  48. package/dist/{chunk-DZS7CJKL.js → chunk-VEPAJXBW.js} +45 -47
  49. package/dist/{chunk-IM32EEDM.js → chunk-YLXFKVJE.js} +9 -9
  50. package/dist/{chunk-Q6AB7W5Z.js → chunk-YQ6KC6TE.js} +1 -1
  51. package/dist/{chunk-PQ5YK4AY.js → chunk-Z2OOPXJO.js} +2740 -1221
  52. package/dist/ci-workflow-765LSHRD.js +8 -0
  53. package/dist/{dist-2B363XUH.js → dist-ALQDD67R.js} +64 -2
  54. package/dist/{dist-HXHWB7SV.js → dist-B26DFXMP.js} +571 -478
  55. package/dist/{dist-L7LAAQAS.js → dist-DZ63LLUD.js} +1 -1
  56. package/dist/{dist-D4RYGUZE.js → dist-USY2C5JL.js} +3 -1
  57. package/dist/{docs-FZOPM4GK.js → docs-NRMQCOJ6.js} +4 -4
  58. package/dist/engine-3RB7MXPP.js +8 -0
  59. package/dist/{entropy-LVHJMFGH.js → entropy-6AGX2ZUN.js} +3 -3
  60. package/dist/{feedback-IHLVLMRD.js → feedback-MY4QZIFD.js} +1 -1
  61. package/dist/{generate-agent-definitions-64S3CLEZ.js → generate-agent-definitions-ZAE726AU.js} +4 -4
  62. package/dist/{graph-loader-GJZ4FN4Y.js → graph-loader-2M2HXDQI.js} +1 -1
  63. package/dist/index.d.ts +156 -17
  64. package/dist/index.js +24 -24
  65. package/dist/loader-UUTVMQCC.js +10 -0
  66. package/dist/{mcp-JQUI7BVZ.js → mcp-VU5FMO52.js} +14 -14
  67. package/dist/{performance-ZTVSUANN.js → performance-2D7G6NMJ.js} +3 -3
  68. package/dist/{review-pipeline-76JHKGSV.js → review-pipeline-RAQ55ISU.js} +1 -1
  69. package/dist/runtime-BCK5RRZQ.js +9 -0
  70. package/dist/{security-FWQZF2IZ.js → security-2RPQEN62.js} +1 -1
  71. package/dist/templates/axum/Cargo.toml.hbs +8 -0
  72. package/dist/templates/axum/src/main.rs +12 -0
  73. package/dist/templates/axum/template.json +16 -0
  74. package/dist/templates/django/manage.py.hbs +19 -0
  75. package/dist/templates/django/requirements.txt.hbs +1 -0
  76. package/dist/templates/django/src/settings.py.hbs +44 -0
  77. package/dist/templates/django/src/urls.py +6 -0
  78. package/dist/templates/django/src/wsgi.py.hbs +9 -0
  79. package/dist/templates/django/template.json +21 -0
  80. package/dist/templates/express/package.json.hbs +15 -0
  81. package/dist/templates/express/src/app.ts +12 -0
  82. package/dist/templates/express/src/lib/.gitkeep +0 -0
  83. package/dist/templates/express/template.json +16 -0
  84. package/dist/templates/fastapi/requirements.txt.hbs +2 -0
  85. package/dist/templates/fastapi/src/main.py +8 -0
  86. package/dist/templates/fastapi/template.json +20 -0
  87. package/dist/templates/gin/go.mod.hbs +5 -0
  88. package/dist/templates/gin/main.go +15 -0
  89. package/dist/templates/gin/template.json +19 -0
  90. package/dist/templates/go-base/.golangci.yml +16 -0
  91. package/dist/templates/go-base/AGENTS.md.hbs +35 -0
  92. package/dist/templates/go-base/go.mod.hbs +3 -0
  93. package/dist/templates/go-base/harness.config.json.hbs +17 -0
  94. package/dist/templates/go-base/main.go +7 -0
  95. package/dist/templates/go-base/template.json +14 -0
  96. package/dist/templates/java-base/AGENTS.md.hbs +35 -0
  97. package/dist/templates/java-base/checkstyle.xml +20 -0
  98. package/dist/templates/java-base/harness.config.json.hbs +16 -0
  99. package/dist/templates/java-base/pom.xml.hbs +39 -0
  100. package/dist/templates/java-base/src/main/java/App.java.hbs +5 -0
  101. package/dist/templates/java-base/template.json +13 -0
  102. package/dist/templates/nestjs/nest-cli.json +5 -0
  103. package/dist/templates/nestjs/package.json.hbs +18 -0
  104. package/dist/templates/nestjs/src/app.module.ts +8 -0
  105. package/dist/templates/nestjs/src/lib/.gitkeep +0 -0
  106. package/dist/templates/nestjs/src/main.ts +11 -0
  107. package/dist/templates/nestjs/template.json +16 -0
  108. package/dist/templates/nextjs/template.json +15 -1
  109. package/dist/templates/python-base/.python-version +1 -0
  110. package/dist/templates/python-base/AGENTS.md.hbs +32 -0
  111. package/dist/templates/python-base/harness.config.json.hbs +16 -0
  112. package/dist/templates/python-base/pyproject.toml.hbs +18 -0
  113. package/dist/templates/python-base/ruff.toml +5 -0
  114. package/dist/templates/python-base/src/__init__.py +0 -0
  115. package/dist/templates/python-base/template.json +13 -0
  116. package/dist/templates/react-vite/index.html +12 -0
  117. package/dist/templates/react-vite/package.json.hbs +18 -0
  118. package/dist/templates/react-vite/src/App.tsx +7 -0
  119. package/dist/templates/react-vite/src/lib/.gitkeep +0 -0
  120. package/dist/templates/react-vite/src/main.tsx +9 -0
  121. package/dist/templates/react-vite/template.json +19 -0
  122. package/dist/templates/react-vite/vite.config.ts +6 -0
  123. package/dist/templates/rust-base/AGENTS.md.hbs +35 -0
  124. package/dist/templates/rust-base/Cargo.toml.hbs +6 -0
  125. package/dist/templates/rust-base/clippy.toml +2 -0
  126. package/dist/templates/rust-base/harness.config.json.hbs +17 -0
  127. package/dist/templates/rust-base/src/main.rs +3 -0
  128. package/dist/templates/rust-base/template.json +14 -0
  129. package/dist/templates/spring-boot/pom.xml.hbs +50 -0
  130. package/dist/templates/spring-boot/src/main/java/Application.java.hbs +19 -0
  131. package/dist/templates/spring-boot/template.json +15 -0
  132. package/dist/templates/vue/index.html +12 -0
  133. package/dist/templates/vue/package.json.hbs +16 -0
  134. package/dist/templates/vue/src/App.vue +7 -0
  135. package/dist/templates/vue/src/lib/.gitkeep +0 -0
  136. package/dist/templates/vue/src/main.ts +4 -0
  137. package/dist/templates/vue/template.json +19 -0
  138. package/dist/templates/vue/vite.config.ts +6 -0
  139. package/dist/{validate-GCHZJIL7.js → validate-KBYQAEWE.js} +4 -4
  140. package/dist/validate-cross-check-OABMREW4.js +8 -0
  141. package/package.json +7 -5
  142. package/dist/agents-md-XU3BHE22.js +0 -8
  143. package/dist/ci-workflow-EHV65NQB.js +0 -8
  144. package/dist/engine-OL4T6NZS.js +0 -8
  145. package/dist/loader-DPYFB6R6.js +0 -10
  146. package/dist/runtime-X7U6SC7K.js +0 -9
  147. package/dist/validate-cross-check-STFHYMAZ.js +0 -8
@@ -1,20 +1,20 @@
1
1
  import {
2
2
  generateCIWorkflow
3
- } from "./chunk-VUCPTQ6G.js";
3
+ } from "./chunk-SD3SQOZ2.js";
4
4
  import {
5
5
  OutputFormatter,
6
6
  OutputMode,
7
7
  createCheckPhaseGateCommand,
8
8
  findFiles
9
- } from "./chunk-UAX4I5ZE.js";
9
+ } from "./chunk-LGYBN7Y6.js";
10
10
  import {
11
11
  createGenerateAgentDefinitionsCommand,
12
12
  generateAgentDefinitions
13
- } from "./chunk-TS3XWPW5.js";
13
+ } from "./chunk-RCWZBSK5.js";
14
14
  import {
15
15
  listPersonas,
16
16
  loadPersona
17
- } from "./chunk-Q6AB7W5Z.js";
17
+ } from "./chunk-YQ6KC6TE.js";
18
18
  import {
19
19
  runPersona
20
20
  } from "./chunk-TRAPF4IX.js";
@@ -33,29 +33,31 @@ import {
33
33
  import {
34
34
  generate,
35
35
  validate
36
- } from "./chunk-QPEH2QPG.js";
36
+ } from "./chunk-DBSOCI3G.js";
37
37
  import {
38
38
  generateRuntime
39
- } from "./chunk-JSTQ3AWB.js";
39
+ } from "./chunk-FIAPHX37.js";
40
40
  import {
41
41
  toKebabCase
42
42
  } from "./chunk-KET4QQZB.js";
43
43
  import {
44
44
  generateAgentsMd
45
- } from "./chunk-NKDM3FMH.js";
45
+ } from "./chunk-OD3S2NHN.js";
46
46
  import {
47
+ appendFrameworkAgents,
47
48
  createGenerateSlashCommandsCommand,
48
49
  generateSlashCommands,
49
50
  handleGetImpact,
50
- handleOrphanDeletion
51
- } from "./chunk-NERR4TAO.js";
51
+ handleOrphanDeletion,
52
+ persistToolingConfig
53
+ } from "./chunk-NNHDDXYT.js";
52
54
  import {
53
55
  VALID_PLATFORMS
54
56
  } from "./chunk-ZOAWBDWU.js";
55
57
  import {
56
58
  findConfigFile,
57
59
  resolveConfig
58
- } from "./chunk-Z77YQRQT.js";
60
+ } from "./chunk-B2HKP423.js";
59
61
  import {
60
62
  resolveGlobalSkillsDir,
61
63
  resolvePersonasDir,
@@ -76,7 +78,7 @@ import {
76
78
  } from "./chunk-BM3PWGXQ.js";
77
79
  import {
78
80
  TemplateEngine
79
- } from "./chunk-C2ERUR3L.js";
81
+ } from "./chunk-7MJAPE3Z.js";
80
82
  import {
81
83
  ArchBaselineManager,
82
84
  ArchConfigSchema,
@@ -125,14 +127,14 @@ import {
125
127
  validateKnowledgeMap,
126
128
  writeConfig,
127
129
  writeLockfile
128
- } from "./chunk-PQ5YK4AY.js";
130
+ } from "./chunk-Z2OOPXJO.js";
129
131
  import {
130
132
  Err,
131
133
  Ok
132
- } from "./chunk-MHBMTPW7.js";
134
+ } from "./chunk-ERS5EVUZ.js";
133
135
 
134
136
  // src/index.ts
135
- import { Command as Command55 } from "commander";
137
+ import { Command as Command61 } from "commander";
136
138
 
137
139
  // src/commands/validate.ts
138
140
  import { Command } from "commander";
@@ -211,7 +213,7 @@ function createValidateCommand() {
211
213
  process.exit(result.error.exitCode);
212
214
  }
213
215
  if (opts.crossCheck) {
214
- const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-STFHYMAZ.js");
216
+ const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-OABMREW4.js");
215
217
  const cwd = process.cwd();
216
218
  const specsDir = path.join(cwd, "docs", "specs");
217
219
  const plansDir = path.join(cwd, "docs", "plans");
@@ -479,10 +481,10 @@ async function runCheckSecurity(cwd, options) {
479
481
  const projectRoot = path4.resolve(cwd);
480
482
  let configData = {};
481
483
  try {
482
- const fs23 = await import("fs");
484
+ const fs29 = await import("fs");
483
485
  const configPath = path4.join(projectRoot, "harness.config.json");
484
- if (fs23.existsSync(configPath)) {
485
- const raw = fs23.readFileSync(configPath, "utf-8");
486
+ if (fs29.existsSync(configPath)) {
487
+ const raw = fs29.readFileSync(configPath, "utf-8");
486
488
  const parsed = JSON.parse(raw);
487
489
  configData = parsed.security ?? {};
488
490
  }
@@ -564,12 +566,11 @@ function createCheckSecurityCommand() {
564
566
  // src/commands/perf.ts
565
567
  import { Command as Command5 } from "commander";
566
568
  import * as path5 from "path";
567
- function createPerfCommand() {
568
- const perf = new Command5("perf").description("Performance benchmark and baseline management");
569
+ function registerBenchCommand(perf) {
569
570
  perf.command("bench [glob]").description("Run benchmarks via vitest bench").action(async (glob, _opts, cmd) => {
570
571
  const globalOpts = cmd.optsWithGlobals();
571
572
  const cwd = process.cwd();
572
- const { BenchmarkRunner } = await import("./dist-2B363XUH.js");
573
+ const { BenchmarkRunner } = await import("./dist-ALQDD67R.js");
573
574
  const runner = new BenchmarkRunner();
574
575
  const benchFiles = runner.discover(cwd, glob);
575
576
  if (benchFiles.length === 0) {
@@ -580,48 +581,47 @@ function createPerfCommand() {
580
581
  }
581
582
  return;
582
583
  }
583
- if (globalOpts.json) {
584
- logger.info(`Found ${benchFiles.length} benchmark file(s). Running...`);
585
- } else {
586
- logger.info(`Found ${benchFiles.length} benchmark file(s):`);
587
- for (const f of benchFiles) {
588
- logger.info(` ${f}`);
589
- }
590
- logger.info("Running benchmarks...");
591
- }
584
+ logBenchDiscovery(globalOpts.json, benchFiles);
592
585
  const result = await runner.run(glob ? { cwd, glob } : { cwd });
593
- if (globalOpts.json) {
594
- console.log(JSON.stringify({ results: result.results, success: result.success }));
595
- } else {
596
- if (result.success && result.results.length > 0) {
597
- logger.info(`
586
+ outputBenchResults(globalOpts.json, result);
587
+ });
588
+ }
589
+ function logBenchDiscovery(json, benchFiles) {
590
+ if (json) {
591
+ logger.info(`Found ${benchFiles.length} benchmark file(s). Running...`);
592
+ } else {
593
+ logger.info(`Found ${benchFiles.length} benchmark file(s):`);
594
+ for (const f of benchFiles) logger.info(` ${f}`);
595
+ logger.info("Running benchmarks...");
596
+ }
597
+ }
598
+ function outputBenchResults(json, result) {
599
+ if (json) {
600
+ console.log(JSON.stringify({ results: result.results, success: result.success }));
601
+ return;
602
+ }
603
+ if (result.success && result.results.length > 0) {
604
+ logger.info(`
598
605
  Results (${result.results.length} benchmarks):`);
599
- for (const r of result.results) {
600
- logger.info(
601
- ` ${r.file}::${r.name}: ${r.opsPerSec} ops/s (mean: ${r.meanMs.toFixed(2)}ms)`
602
- );
603
- }
604
- logger.info("\nTo save as baselines: harness perf baselines update");
605
- } else {
606
- logger.info("Benchmark run completed. Check output above for details.");
607
- if (result.rawOutput) {
608
- console.log(result.rawOutput);
609
- }
610
- }
606
+ for (const r of result.results) {
607
+ logger.info(` ${r.file}::${r.name}: ${r.opsPerSec} ops/s (mean: ${r.meanMs.toFixed(2)}ms)`);
611
608
  }
612
- });
609
+ logger.info("\nTo save as baselines: harness perf baselines update");
610
+ } else {
611
+ logger.info("Benchmark run completed. Check output above for details.");
612
+ if (result.rawOutput) console.log(result.rawOutput);
613
+ }
614
+ }
615
+ function registerBaselinesCommands(perf) {
613
616
  const baselines = perf.command("baselines").description("Manage performance baselines");
614
617
  baselines.command("show").description("Display current baselines").action(async (_opts, cmd) => {
615
618
  const globalOpts = cmd.optsWithGlobals();
616
- const cwd = process.cwd();
617
- const manager = new BaselineManager(cwd);
619
+ const manager = new BaselineManager(process.cwd());
618
620
  const data = manager.load();
619
621
  if (!data) {
620
- if (globalOpts.json) {
621
- console.log(JSON.stringify({ baselines: null, message: "No baselines file found" }));
622
- } else {
623
- logger.info("No baselines file found at .harness/perf/baselines.json");
624
- }
622
+ console.log(
623
+ globalOpts.json ? JSON.stringify({ baselines: null, message: "No baselines file found" }) : "No baselines file found at .harness/perf/baselines.json"
624
+ );
625
625
  return;
626
626
  }
627
627
  if (globalOpts.json) {
@@ -638,7 +638,7 @@ Results (${result.results.length} benchmarks):`);
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-2B363XUH.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...");
@@ -649,12 +649,7 @@ Results (${result.results.length} benchmarks):`);
649
649
  );
650
650
  return;
651
651
  }
652
- let commitHash = "unknown";
653
- try {
654
- const { execSync: execSync5 } = await import("child_process");
655
- commitHash = execSync5("git rev-parse --short HEAD", { cwd, encoding: "utf-8" }).trim();
656
- } catch {
657
- }
652
+ const commitHash = await getCommitHash(cwd);
658
653
  manager.save(benchResult.results, commitHash);
659
654
  if (globalOpts.json) {
660
655
  console.log(JSON.stringify({ updated: benchResult.results.length, commitHash }));
@@ -663,10 +658,20 @@ Results (${result.results.length} benchmarks):`);
663
658
  logger.info("Baselines saved to .harness/perf/baselines.json");
664
659
  }
665
660
  });
661
+ }
662
+ async function getCommitHash(cwd) {
663
+ try {
664
+ const { execSync: execSync5 } = await import("child_process");
665
+ return execSync5("git rev-parse --short HEAD", { cwd, encoding: "utf-8" }).trim();
666
+ } catch {
667
+ return "unknown";
668
+ }
669
+ }
670
+ function registerReportCommand(perf) {
666
671
  perf.command("report").description("Full performance report with metrics, trends, and hotspots").action(async (_opts, cmd) => {
667
672
  const globalOpts = cmd.optsWithGlobals();
668
673
  const cwd = process.cwd();
669
- const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-2B363XUH.js");
674
+ const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-ALQDD67R.js");
670
675
  const analyzer = new EntropyAnalyzer2({
671
676
  rootDir: path5.resolve(cwd),
672
677
  analyze: { complexity: true, coupling: true }
@@ -703,10 +708,11 @@ Results (${result.results.length} benchmarks):`);
703
708
  }
704
709
  }
705
710
  });
711
+ }
712
+ function registerCriticalPathsCommand(perf) {
706
713
  perf.command("critical-paths").description("Show resolved critical path set (annotations + graph inference)").action(async (_opts, cmd) => {
707
714
  const globalOpts = cmd.optsWithGlobals();
708
- const cwd = process.cwd();
709
- const resolver = new CriticalPathResolver(cwd);
715
+ const resolver = new CriticalPathResolver(process.cwd());
710
716
  const result = await resolver.resolve();
711
717
  if (globalOpts.json) {
712
718
  console.log(JSON.stringify(result, null, 2));
@@ -721,12 +727,29 @@ Results (${result.results.length} benchmarks):`);
721
727
  }
722
728
  }
723
729
  });
730
+ }
731
+ function createPerfCommand() {
732
+ const perf = new Command5("perf").description("Performance benchmark and baseline management");
733
+ registerBenchCommand(perf);
734
+ registerBaselinesCommands(perf);
735
+ registerReportCommand(perf);
736
+ registerCriticalPathsCommand(perf);
724
737
  return perf;
725
738
  }
726
739
 
727
740
  // src/commands/check-docs.ts
728
741
  import { Command as Command6 } from "commander";
729
742
  import * as path6 from "path";
743
+
744
+ // src/utils/output.ts
745
+ function resolveOutputMode(globalOpts) {
746
+ if (globalOpts.json) return OutputMode.JSON;
747
+ if (globalOpts.quiet) return OutputMode.QUIET;
748
+ if (globalOpts.verbose) return OutputMode.VERBOSE;
749
+ return OutputMode.TEXT;
750
+ }
751
+
752
+ // src/commands/check-docs.ts
730
753
  async function runCheckDocs(options) {
731
754
  const cwd = options.cwd ?? process.cwd();
732
755
  const minCoverage = options.minCoverage ?? 80;
@@ -777,7 +800,7 @@ async function runCheckDocs(options) {
777
800
  function createCheckDocsCommand() {
778
801
  const command = new Command6("check-docs").description("Check documentation coverage").option("--min-coverage <percent>", "Minimum coverage percentage", "80").action(async (opts, cmd) => {
779
802
  const globalOpts = cmd.optsWithGlobals();
780
- const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : globalOpts.verbose ? OutputMode.VERBOSE : OutputMode.TEXT;
803
+ const mode = resolveOutputMode(globalOpts);
781
804
  const formatter = new OutputFormatter(mode);
782
805
  const result = await runCheckDocs({
783
806
  configPath: globalOpts.config,
@@ -947,7 +970,6 @@ function createSetupMcpCommand() {
947
970
  async function runInit(options) {
948
971
  const cwd = options.cwd ?? process.cwd();
949
972
  const name = options.name ?? path8.basename(cwd);
950
- const level = options.level ?? "basic";
951
973
  const force = options.force ?? false;
952
974
  const configPath = path8.join(cwd, "harness.config.json");
953
975
  if (!force && fs2.existsSync(configPath)) {
@@ -955,33 +977,82 @@ async function runInit(options) {
955
977
  new CLIError("Project already initialized. Use --force to overwrite.", ExitCode.ERROR)
956
978
  );
957
979
  }
958
- const templatesDir = resolveTemplatesDir();
959
- const engine = new TemplateEngine(templatesDir);
960
- const resolveResult = engine.resolveTemplate(level, options.framework);
961
- if (!resolveResult.ok) {
962
- return Err(new CLIError(resolveResult.error.message, ExitCode.ERROR));
980
+ const engine = new TemplateEngine(resolveTemplatesDir());
981
+ const templates = engine.listTemplates();
982
+ const templateList = templates.ok ? templates.value : [];
983
+ const validationError = validateFrameworkLanguage(options, templateList);
984
+ if (validationError) return Err(validationError);
985
+ const detected = tryAutoDetect(engine, cwd, options);
986
+ if (detected) return Ok(detected);
987
+ const language = resolveLanguage(options, templateList);
988
+ return scaffoldProject(engine, { cwd, name, force, language, options });
989
+ }
990
+ function validateFrameworkLanguage(options, templateList) {
991
+ if (!options.framework || !options.language) return null;
992
+ const fwTemplate = templateList.find((t) => t.framework === options.framework);
993
+ if (fwTemplate?.language && fwTemplate.language !== options.language) {
994
+ return new CLIError(
995
+ `Framework "${options.framework}" is a ${fwTemplate.language} framework, but --language ${options.language} was specified. Remove --language or use --language ${fwTemplate.language}.`,
996
+ ExitCode.ERROR
997
+ );
998
+ }
999
+ return null;
1000
+ }
1001
+ function tryAutoDetect(engine, cwd, options) {
1002
+ if (options.framework || options.language) return null;
1003
+ const detectResult = engine.detectFramework(cwd);
1004
+ if (detectResult.ok && detectResult.value.length > 0) {
1005
+ return { filesCreated: [], skippedConfigs: [], detectedFrameworks: detectResult.value };
1006
+ }
1007
+ return null;
1008
+ }
1009
+ function resolveLanguage(options, templateList) {
1010
+ if (options.language) return options.language;
1011
+ if (options.framework) {
1012
+ const fwTemplate = templateList.find((t) => t.framework === options.framework);
1013
+ if (fwTemplate?.language) return fwTemplate.language;
963
1014
  }
1015
+ return void 0;
1016
+ }
1017
+ function scaffoldProject(engine, ctx) {
1018
+ const { cwd, name, force, language, options } = ctx;
1019
+ const isNonJs = language && language !== "typescript";
1020
+ const level = isNonJs ? void 0 : options.level ?? "basic";
1021
+ const resolveResult = engine.resolveTemplate(level, options.framework, language);
1022
+ if (!resolveResult.ok) return Err(new CLIError(resolveResult.error.message, ExitCode.ERROR));
964
1023
  const renderResult = engine.render(resolveResult.value, {
965
1024
  projectName: name,
966
- level,
967
- ...options.framework !== void 0 && { framework: options.framework }
1025
+ level: level ?? "",
1026
+ ...options.framework !== void 0 && { framework: options.framework },
1027
+ ...language !== void 0 && { language }
968
1028
  });
969
- if (!renderResult.ok) {
970
- return Err(new CLIError(renderResult.error.message, ExitCode.ERROR));
971
- }
972
- const writeResult = engine.write(renderResult.value, cwd, { overwrite: force });
973
- if (!writeResult.ok) {
974
- return Err(new CLIError(writeResult.error.message, ExitCode.ERROR));
1029
+ if (!renderResult.ok) return Err(new CLIError(renderResult.error.message, ExitCode.ERROR));
1030
+ const writeResult = engine.write(renderResult.value, cwd, {
1031
+ overwrite: force,
1032
+ ...language !== void 0 && { language }
1033
+ });
1034
+ if (!writeResult.ok) return Err(new CLIError(writeResult.error.message, ExitCode.ERROR));
1035
+ if (writeResult.value.skippedConfigs.length > 0) {
1036
+ logger.warn("Skipped existing package config files:");
1037
+ for (const file of writeResult.value.skippedConfigs) {
1038
+ logger.info(` - ${file} (add harness dependencies manually)`);
1039
+ }
975
1040
  }
976
- return Ok({ filesCreated: writeResult.value });
1041
+ persistToolingConfig(cwd, resolveResult.value, options.framework);
1042
+ appendFrameworkAgents(cwd, options.framework, language);
1043
+ return Ok({
1044
+ filesCreated: writeResult.value.written,
1045
+ skippedConfigs: writeResult.value.skippedConfigs
1046
+ });
977
1047
  }
978
1048
  function createInitCommand() {
979
- const command = new Command8("init").description("Initialize a new harness-engineering project").option("-n, --name <name>", "Project name").option("-l, --level <level>", "Adoption level (basic, intermediate, advanced)", "basic").option("--framework <framework>", "Framework overlay (nextjs)").option("-f, --force", "Overwrite existing files").option("-y, --yes", "Use defaults without prompting").action(async (opts, cmd) => {
1049
+ const command = new Command8("init").description("Initialize a new harness-engineering project").option("-n, --name <name>", "Project name").option("-l, --level <level>", "Adoption level (basic, intermediate, advanced)", "basic").option("--framework <framework>", "Framework overlay (nextjs)").option("--language <language>", "Target language (typescript, python, go, rust, java)").option("-f, --force", "Overwrite existing files").option("-y, --yes", "Use defaults without prompting").action(async (opts, cmd) => {
980
1050
  const globalOpts = cmd.optsWithGlobals();
981
1051
  const result = await runInit({
982
1052
  name: opts.name,
983
1053
  level: opts.level,
984
1054
  framework: opts.framework,
1055
+ language: opts.language,
985
1056
  force: opts.force
986
1057
  });
987
1058
  if (!result.ok) {
@@ -1077,10 +1148,39 @@ async function runCleanup(options) {
1077
1148
  result.totalIssues = result.driftIssues.length + result.deadCode.length + result.patternViolations.length;
1078
1149
  return Ok(result);
1079
1150
  }
1151
+ function printCleanupResult(value, formatter) {
1152
+ console.log(
1153
+ formatter.formatSummary("Entropy issues", value.totalIssues.toString(), value.totalIssues === 0)
1154
+ );
1155
+ if (value.driftIssues.length > 0) {
1156
+ console.log("\nDocumentation drift:");
1157
+ for (const issue of value.driftIssues) {
1158
+ console.log(` - ${issue.file}: ${issue.issue}`);
1159
+ }
1160
+ }
1161
+ if (value.deadCode.length > 0) {
1162
+ console.log("\nDead code:");
1163
+ for (const item of value.deadCode.slice(0, 10)) {
1164
+ console.log(` - ${item.file}${item.symbol ? `: ${item.symbol}` : ""}`);
1165
+ }
1166
+ if (value.deadCode.length > 10) {
1167
+ console.log(` ... and ${value.deadCode.length - 10} more`);
1168
+ }
1169
+ }
1170
+ if (value.patternViolations.length > 0) {
1171
+ console.log("\nPattern violations:");
1172
+ for (const violation of value.patternViolations.slice(0, 10)) {
1173
+ console.log(` - ${violation.file} [${violation.pattern}]: ${violation.message}`);
1174
+ }
1175
+ if (value.patternViolations.length > 10) {
1176
+ console.log(` ... and ${value.patternViolations.length - 10} more`);
1177
+ }
1178
+ }
1179
+ }
1080
1180
  function createCleanupCommand() {
1081
1181
  const command = new Command9("cleanup").description("Detect entropy issues (doc drift, dead code, patterns)").option("-t, --type <type>", "Issue type: drift, dead-code, patterns, all", "all").action(async (opts, cmd) => {
1082
1182
  const globalOpts = cmd.optsWithGlobals();
1083
- const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : globalOpts.verbose ? OutputMode.VERBOSE : OutputMode.TEXT;
1183
+ const mode = resolveOutputMode(globalOpts);
1084
1184
  const formatter = new OutputFormatter(mode);
1085
1185
  const result = await runCleanup({
1086
1186
  configPath: globalOpts.config,
@@ -1100,37 +1200,7 @@ function createCleanupCommand() {
1100
1200
  if (mode === OutputMode.JSON) {
1101
1201
  console.log(JSON.stringify(result.value, null, 2));
1102
1202
  } else if (mode !== OutputMode.QUIET || result.value.totalIssues > 0) {
1103
- console.log(
1104
- formatter.formatSummary(
1105
- "Entropy issues",
1106
- result.value.totalIssues.toString(),
1107
- result.value.totalIssues === 0
1108
- )
1109
- );
1110
- if (result.value.driftIssues.length > 0) {
1111
- console.log("\nDocumentation drift:");
1112
- for (const issue of result.value.driftIssues) {
1113
- console.log(` - ${issue.file}: ${issue.issue}`);
1114
- }
1115
- }
1116
- if (result.value.deadCode.length > 0) {
1117
- console.log("\nDead code:");
1118
- for (const item of result.value.deadCode.slice(0, 10)) {
1119
- console.log(` - ${item.file}${item.symbol ? `: ${item.symbol}` : ""}`);
1120
- }
1121
- if (result.value.deadCode.length > 10) {
1122
- console.log(` ... and ${result.value.deadCode.length - 10} more`);
1123
- }
1124
- }
1125
- if (result.value.patternViolations.length > 0) {
1126
- console.log("\nPattern violations:");
1127
- for (const violation of result.value.patternViolations.slice(0, 10)) {
1128
- console.log(` - ${violation.file} [${violation.pattern}]: ${violation.message}`);
1129
- }
1130
- if (result.value.patternViolations.length > 10) {
1131
- console.log(` ... and ${result.value.patternViolations.length - 10} more`);
1132
- }
1133
- }
1203
+ printCleanupResult(result.value, formatter);
1134
1204
  }
1135
1205
  process.exit(result.value.totalIssues === 0 ? ExitCode.SUCCESS : ExitCode.VALIDATION_FAILED);
1136
1206
  });
@@ -1238,10 +1308,42 @@ async function runFixDrift(options) {
1238
1308
  };
1239
1309
  return Ok(result);
1240
1310
  }
1311
+ function printFixDriftResult(value, mode, formatter) {
1312
+ const statusMessage = value.dryRun ? "(dry-run)" : "";
1313
+ console.log(
1314
+ formatter.formatSummary(
1315
+ `Fix drift ${statusMessage}`,
1316
+ `${value.fixes.length} fixes, ${value.suggestions.length} suggestions`,
1317
+ value.fixes.length === 0 && value.suggestions.length === 0
1318
+ )
1319
+ );
1320
+ if (value.fixes.length > 0) {
1321
+ console.log("\nFixes:");
1322
+ for (const fix of value.fixes.slice(0, 10)) {
1323
+ const status = fix.applied ? "[applied]" : "[pending]";
1324
+ console.log(` ${status} ${fix.action}: ${fix.file}`);
1325
+ }
1326
+ if (value.fixes.length > 10) {
1327
+ console.log(` ... and ${value.fixes.length - 10} more`);
1328
+ }
1329
+ }
1330
+ if (value.suggestions.length > 0 && (mode === OutputMode.VERBOSE || value.fixes.length === 0)) {
1331
+ console.log("\nSuggestions:");
1332
+ for (const suggestion of value.suggestions.slice(0, 10)) {
1333
+ console.log(` - ${suggestion.file}: ${suggestion.suggestion}`);
1334
+ }
1335
+ if (value.suggestions.length > 10) {
1336
+ console.log(` ... and ${value.suggestions.length - 10} more`);
1337
+ }
1338
+ }
1339
+ if (value.dryRun && value.fixes.length > 0) {
1340
+ console.log("\nRun with --no-dry-run to apply fixes.");
1341
+ }
1342
+ }
1241
1343
  function createFixDriftCommand() {
1242
1344
  const command = new Command10("fix-drift").description("Auto-fix entropy issues (doc drift, dead code)").option("--no-dry-run", "Actually apply fixes (default is dry-run mode)").action(async (opts, cmd) => {
1243
1345
  const globalOpts = cmd.optsWithGlobals();
1244
- const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : globalOpts.verbose ? OutputMode.VERBOSE : OutputMode.TEXT;
1346
+ const mode = resolveOutputMode(globalOpts);
1245
1347
  const formatter = new OutputFormatter(mode);
1246
1348
  const result = await runFixDrift({
1247
1349
  configPath: globalOpts.config,
@@ -1261,37 +1363,7 @@ function createFixDriftCommand() {
1261
1363
  if (mode === OutputMode.JSON) {
1262
1364
  console.log(JSON.stringify(result.value, null, 2));
1263
1365
  } else if (mode !== OutputMode.QUIET || result.value.fixes.length > 0 || result.value.suggestions.length > 0) {
1264
- const { value } = result;
1265
- const statusMessage = value.dryRun ? "(dry-run)" : "";
1266
- console.log(
1267
- formatter.formatSummary(
1268
- `Fix drift ${statusMessage}`,
1269
- `${value.fixes.length} fixes, ${value.suggestions.length} suggestions`,
1270
- value.fixes.length === 0 && value.suggestions.length === 0
1271
- )
1272
- );
1273
- if (value.fixes.length > 0) {
1274
- console.log("\nFixes:");
1275
- for (const fix of value.fixes.slice(0, 10)) {
1276
- const status = fix.applied ? "[applied]" : "[pending]";
1277
- console.log(` ${status} ${fix.action}: ${fix.file}`);
1278
- }
1279
- if (value.fixes.length > 10) {
1280
- console.log(` ... and ${value.fixes.length - 10} more`);
1281
- }
1282
- }
1283
- if (value.suggestions.length > 0 && (mode === OutputMode.VERBOSE || value.fixes.length === 0)) {
1284
- console.log("\nSuggestions:");
1285
- for (const suggestion of value.suggestions.slice(0, 10)) {
1286
- console.log(` - ${suggestion.file}: ${suggestion.suggestion}`);
1287
- }
1288
- if (value.suggestions.length > 10) {
1289
- console.log(` ... and ${value.suggestions.length - 10} more`);
1290
- }
1291
- }
1292
- if (value.dryRun && value.fixes.length > 0) {
1293
- console.log("\nRun with --no-dry-run to apply fixes.");
1294
- }
1366
+ printFixDriftResult(result.value, mode, formatter);
1295
1367
  }
1296
1368
  process.exit(ExitCode.SUCCESS);
1297
1369
  });
@@ -1356,49 +1428,54 @@ var VALID_TRIGGERS = /* @__PURE__ */ new Set([
1356
1428
  "on_plan_approved",
1357
1429
  "auto"
1358
1430
  ]);
1431
+ function resolveTrigger(triggerOpt) {
1432
+ if (triggerOpt === "auto") return "auto";
1433
+ return VALID_TRIGGERS.has(triggerOpt) ? triggerOpt : "manual";
1434
+ }
1435
+ function createCommandExecutor() {
1436
+ return async (command) => {
1437
+ if (!ALLOWED_PERSONA_COMMANDS.has(command)) {
1438
+ return Err(new Error(`Unknown harness command: ${command}`));
1439
+ }
1440
+ try {
1441
+ childProcess.execFileSync("npx", ["harness", command], { stdio: "inherit" });
1442
+ return Ok(null);
1443
+ } catch (error) {
1444
+ return Err(new Error(error instanceof Error ? error.message : String(error)));
1445
+ }
1446
+ };
1447
+ }
1448
+ async function runPersonaMode(opts, quiet) {
1449
+ const personasDir = resolvePersonasDir();
1450
+ const filePath = path11.join(personasDir, `${opts.persona}.yaml`);
1451
+ const personaResult = loadPersona(filePath);
1452
+ if (!personaResult.ok) {
1453
+ logger.error(personaResult.error.message);
1454
+ process.exit(ExitCode.ERROR);
1455
+ }
1456
+ const report = await runPersona(personaResult.value, {
1457
+ trigger: resolveTrigger(opts.trigger),
1458
+ commandExecutor: createCommandExecutor(),
1459
+ skillExecutor: executeSkill,
1460
+ projectPath: process.cwd()
1461
+ });
1462
+ if (!quiet) {
1463
+ logger.info(`Persona '${report.persona}' status: ${report.status}`);
1464
+ for (const s of report.steps) {
1465
+ const icon = s.status === "pass" ? "v" : s.status === "fail" ? "x" : "-";
1466
+ const typeTag = s.type === "skill" ? " [skill]" : "";
1467
+ console.log(` [${icon}] ${s.name}${typeTag} (${s.durationMs}ms)`);
1468
+ if (s.artifactPath) console.log(` artifact: ${s.artifactPath}`);
1469
+ }
1470
+ }
1471
+ process.exit(report.status === "fail" ? ExitCode.ERROR : ExitCode.SUCCESS);
1472
+ }
1359
1473
  function createRunCommand() {
1360
1474
  return new Command11("run").description("Run an agent task").argument("[task]", "Task to run (review, doc-review, test-review)").option("--timeout <ms>", "Timeout in milliseconds", "300000").option("--persona <name>", "Run a persona by name").option("--trigger <context>", "Trigger context (auto, on_pr, on_commit, manual)", "auto").action(async (task, opts, cmd) => {
1361
1475
  const globalOpts = cmd.optsWithGlobals();
1362
1476
  if (opts.persona) {
1363
- const personasDir = resolvePersonasDir();
1364
- const filePath = path11.join(personasDir, `${opts.persona}.yaml`);
1365
- const personaResult = loadPersona(filePath);
1366
- if (!personaResult.ok) {
1367
- logger.error(personaResult.error.message);
1368
- process.exit(ExitCode.ERROR);
1369
- }
1370
- const persona = personaResult.value;
1371
- const projectPath = process.cwd();
1372
- const trigger = opts.trigger === "auto" ? "auto" : VALID_TRIGGERS.has(opts.trigger) ? opts.trigger : "manual";
1373
- const commandExecutor = async (command) => {
1374
- if (!ALLOWED_PERSONA_COMMANDS.has(command)) {
1375
- return Err(new Error(`Unknown harness command: ${command}`));
1376
- }
1377
- try {
1378
- childProcess.execFileSync("npx", ["harness", command], { stdio: "inherit" });
1379
- return Ok(null);
1380
- } catch (error) {
1381
- return Err(new Error(error instanceof Error ? error.message : String(error)));
1382
- }
1383
- };
1384
- const report = await runPersona(persona, {
1385
- trigger,
1386
- commandExecutor,
1387
- skillExecutor: executeSkill,
1388
- projectPath
1389
- });
1390
- if (!globalOpts.quiet) {
1391
- logger.info(`Persona '${report.persona}' status: ${report.status}`);
1392
- for (const s of report.steps) {
1393
- const icon = s.status === "pass" ? "v" : s.status === "fail" ? "x" : "-";
1394
- const typeTag = s.type === "skill" ? " [skill]" : "";
1395
- console.log(` [${icon}] ${s.name}${typeTag} (${s.durationMs}ms)`);
1396
- if (s.artifactPath) {
1397
- console.log(` artifact: ${s.artifactPath}`);
1398
- }
1399
- }
1400
- }
1401
- process.exit(report.status === "fail" ? ExitCode.ERROR : ExitCode.SUCCESS);
1477
+ await runPersonaMode(opts, globalOpts.quiet);
1478
+ return;
1402
1479
  }
1403
1480
  if (!task) {
1404
1481
  logger.error("Either a task argument or --persona flag is required.");
@@ -1987,6 +2064,33 @@ function scanDirectory(dirPath, source) {
1987
2064
  }
1988
2065
  return skills;
1989
2066
  }
2067
+ function collectCommunitySkills(seen, allSkills) {
2068
+ const globalDir = resolveGlobalSkillsDir();
2069
+ const skillsDir = path15.dirname(globalDir);
2070
+ const communityBase = path15.join(skillsDir, "community");
2071
+ const communityPlatformDir = path15.join(communityBase, "claude-code");
2072
+ const lockfilePath = path15.join(communityBase, "skills-lock.json");
2073
+ const lockfile = readLockfile2(lockfilePath);
2074
+ const communitySkills = scanDirectory(communityPlatformDir, "community");
2075
+ for (const skill of communitySkills) {
2076
+ const lockEntry = lockfile.skills[`@harness-skills/${skill.name}`];
2077
+ if (lockEntry) skill.version = lockEntry.version;
2078
+ }
2079
+ for (const [pkgName, entry] of Object.entries(lockfile.skills)) {
2080
+ const shortName = pkgName.replace("@harness-skills/", "");
2081
+ if (!seen.has(shortName)) {
2082
+ seen.add(shortName);
2083
+ allSkills.push({
2084
+ name: shortName,
2085
+ description: "",
2086
+ type: "",
2087
+ source: "community",
2088
+ version: entry.version
2089
+ });
2090
+ }
2091
+ }
2092
+ return communitySkills;
2093
+ }
1990
2094
  function collectSkills(opts) {
1991
2095
  const seen = /* @__PURE__ */ new Set();
1992
2096
  const allSkills = [];
@@ -2005,34 +2109,7 @@ function collectSkills(opts) {
2005
2109
  }
2006
2110
  }
2007
2111
  if (opts.filter === "all" || opts.filter === "installed") {
2008
- const globalDir = resolveGlobalSkillsDir();
2009
- const skillsDir = path15.dirname(globalDir);
2010
- const communityBase = path15.join(skillsDir, "community");
2011
- const communityPlatformDir = path15.join(communityBase, "claude-code");
2012
- const lockfilePath = path15.join(communityBase, "skills-lock.json");
2013
- const lockfile = readLockfile2(lockfilePath);
2014
- const communitySkills = scanDirectory(communityPlatformDir, "community");
2015
- for (const skill of communitySkills) {
2016
- const pkgName = `@harness-skills/${skill.name}`;
2017
- const lockEntry = lockfile.skills[pkgName];
2018
- if (lockEntry) {
2019
- skill.version = lockEntry.version;
2020
- }
2021
- }
2022
- addUnique(communitySkills);
2023
- for (const [pkgName, entry] of Object.entries(lockfile.skills)) {
2024
- const shortName = pkgName.replace("@harness-skills/", "");
2025
- if (!seen.has(shortName)) {
2026
- seen.add(shortName);
2027
- allSkills.push({
2028
- name: shortName,
2029
- description: "",
2030
- type: "",
2031
- source: "community",
2032
- version: entry.version
2033
- });
2034
- }
2035
- }
2112
+ addUnique(collectCommunitySkills(seen, allSkills));
2036
2113
  }
2037
2114
  if (opts.filter === "all") {
2038
2115
  const globalDir = resolveGlobalSkillsDir();
@@ -2166,6 +2243,69 @@ ${options.priorState}`);
2166
2243
  }
2167
2244
 
2168
2245
  // src/commands/skill/run.ts
2246
+ function loadSkillMetadata(skillDir) {
2247
+ const yamlPath = path16.join(skillDir, "skill.yaml");
2248
+ if (!fs7.existsSync(yamlPath)) return null;
2249
+ try {
2250
+ const result = SkillMetadataSchema.safeParse(parse2(fs7.readFileSync(yamlPath, "utf-8")));
2251
+ return result.success ? result.data : null;
2252
+ } catch {
2253
+ return null;
2254
+ }
2255
+ }
2256
+ function resolveComplexity(metadata, requested, projectPath) {
2257
+ if (!metadata?.phases || metadata.phases.length === 0) return void 0;
2258
+ if (requested === "auto") return detectComplexity(projectPath);
2259
+ return requested;
2260
+ }
2261
+ function loadPrinciples(projectPath) {
2262
+ const principlesPath = path16.join(projectPath, "docs", "principles.md");
2263
+ return fs7.existsSync(principlesPath) ? fs7.readFileSync(principlesPath, "utf-8") : void 0;
2264
+ }
2265
+ function readMostRecentFileInDir(dirPath) {
2266
+ const files = fs7.readdirSync(dirPath).map((f) => ({ name: f, mtime: fs7.statSync(path16.join(dirPath, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
2267
+ if (files.length > 0) return fs7.readFileSync(path16.join(dirPath, files[0].name), "utf-8");
2268
+ return void 0;
2269
+ }
2270
+ function loadPriorState(metadata, projectPath) {
2271
+ if (!metadata?.state.persistent || metadata.state.files.length === 0) return void 0;
2272
+ for (const stateFilePath of metadata.state.files) {
2273
+ const fullPath = path16.join(projectPath, stateFilePath);
2274
+ if (!fs7.existsSync(fullPath)) continue;
2275
+ const stat = fs7.statSync(fullPath);
2276
+ if (stat.isDirectory()) return readMostRecentFileInDir(fullPath);
2277
+ return fs7.readFileSync(fullPath, "utf-8");
2278
+ }
2279
+ return void 0;
2280
+ }
2281
+ function validatePhaseName(metadata, phase) {
2282
+ if (!metadata?.phases) return true;
2283
+ return metadata.phases.map((p) => p.name).includes(phase);
2284
+ }
2285
+ function resolvePhaseState(metadata, projectPath, phase) {
2286
+ if (!validatePhaseName(metadata, phase)) {
2287
+ const validPhases = metadata.phases.map((p) => p.name);
2288
+ logger.error(`Unknown phase: ${phase}. Valid phases: ${validPhases.join(", ")}`);
2289
+ return null;
2290
+ }
2291
+ const priorState = loadPriorState(metadata, projectPath);
2292
+ const stateWarning = !priorState && metadata?.state.persistent ? "No prior phase data found. Earlier phases have not been completed. Proceed with caution." : void 0;
2293
+ return { priorState, stateWarning };
2294
+ }
2295
+ function appendProjectState(content, metadata, projectPath, hasPathOpt) {
2296
+ if (!metadata?.state.persistent || !hasPathOpt) return content;
2297
+ const stateFile = path16.join(projectPath, ".harness", "state.json");
2298
+ if (!fs7.existsSync(stateFile)) return content;
2299
+ const stateContent = fs7.readFileSync(stateFile, "utf-8");
2300
+ return content + `
2301
+
2302
+ ---
2303
+ ## Project State
2304
+ \`\`\`json
2305
+ ${stateContent}
2306
+ \`\`\`
2307
+ `;
2308
+ }
2169
2309
  function createRunCommand2() {
2170
2310
  return new Command22("run").description("Run a skill (outputs SKILL.md content with context preamble)").argument("<name>", "Skill name (e.g., harness-tdd)").option("--path <path>", "Project root path for context injection").option("--complexity <level>", "Complexity: auto, light, full", "auto").option("--phase <name>", "Start at a specific phase (for re-entry)").option("--party", "Enable multi-perspective evaluation").action(async (name, opts, _cmd) => {
2171
2311
  const skillsDir = resolveSkillsDir();
@@ -2175,64 +2315,24 @@ function createRunCommand2() {
2175
2315
  process.exit(ExitCode.ERROR);
2176
2316
  return;
2177
2317
  }
2178
- const yamlPath = path16.join(skillDir, "skill.yaml");
2179
- let metadata = null;
2180
- if (fs7.existsSync(yamlPath)) {
2181
- try {
2182
- const raw = fs7.readFileSync(yamlPath, "utf-8");
2183
- const parsed = parse2(raw);
2184
- const result = SkillMetadataSchema.safeParse(parsed);
2185
- if (result.success) metadata = result.data;
2186
- } catch {
2187
- }
2188
- }
2189
- let complexity;
2190
- if (metadata?.phases && metadata.phases.length > 0) {
2191
- const requested = opts.complexity ?? "auto";
2192
- if (requested === "auto") {
2193
- const projectPath2 = opts.path ? path16.resolve(opts.path) : process.cwd();
2194
- complexity = detectComplexity(projectPath2);
2195
- } else {
2196
- complexity = requested;
2197
- }
2198
- }
2199
- let principles;
2318
+ const metadata = loadSkillMetadata(skillDir);
2200
2319
  const projectPath = opts.path ? path16.resolve(opts.path) : process.cwd();
2201
- const principlesPath = path16.join(projectPath, "docs", "principles.md");
2202
- if (fs7.existsSync(principlesPath)) {
2203
- principles = fs7.readFileSync(principlesPath, "utf-8");
2204
- }
2320
+ const complexity = resolveComplexity(
2321
+ metadata,
2322
+ opts.complexity ?? "auto",
2323
+ projectPath
2324
+ );
2325
+ const principles = loadPrinciples(projectPath);
2205
2326
  let priorState;
2206
2327
  let stateWarning;
2207
2328
  if (opts.phase) {
2208
- if (metadata?.phases) {
2209
- const validPhases = metadata.phases.map((p) => p.name);
2210
- if (!validPhases.includes(opts.phase)) {
2211
- logger.error(`Unknown phase: ${opts.phase}. Valid phases: ${validPhases.join(", ")}`);
2212
- process.exit(ExitCode.ERROR);
2213
- return;
2214
- }
2215
- }
2216
- if (metadata?.state.persistent && metadata.state.files.length > 0) {
2217
- for (const stateFilePath of metadata.state.files) {
2218
- const fullPath = path16.join(projectPath, stateFilePath);
2219
- if (fs7.existsSync(fullPath)) {
2220
- const stat = fs7.statSync(fullPath);
2221
- if (stat.isDirectory()) {
2222
- const files = fs7.readdirSync(fullPath).map((f) => ({ name: f, mtime: fs7.statSync(path16.join(fullPath, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
2223
- if (files.length > 0) {
2224
- priorState = fs7.readFileSync(path16.join(fullPath, files[0].name), "utf-8");
2225
- }
2226
- } else {
2227
- priorState = fs7.readFileSync(fullPath, "utf-8");
2228
- }
2229
- break;
2230
- }
2231
- }
2232
- if (!priorState) {
2233
- stateWarning = "No prior phase data found. Earlier phases have not been completed. Proceed with caution.";
2234
- }
2329
+ const phaseResult = resolvePhaseState(metadata, projectPath, opts.phase);
2330
+ if (!phaseResult) {
2331
+ process.exit(ExitCode.ERROR);
2332
+ return;
2235
2333
  }
2334
+ priorState = phaseResult.priorState;
2335
+ stateWarning = phaseResult.stateWarning;
2236
2336
  }
2237
2337
  const preamble = buildPreamble({
2238
2338
  ...complexity !== void 0 && { complexity },
@@ -2249,21 +2349,12 @@ function createRunCommand2() {
2249
2349
  process.exit(ExitCode.ERROR);
2250
2350
  return;
2251
2351
  }
2252
- let content = fs7.readFileSync(skillMdPath, "utf-8");
2253
- if (metadata?.state.persistent && opts.path) {
2254
- const stateFile = path16.join(projectPath, ".harness", "state.json");
2255
- if (fs7.existsSync(stateFile)) {
2256
- const stateContent = fs7.readFileSync(stateFile, "utf-8");
2257
- content += `
2258
-
2259
- ---
2260
- ## Project State
2261
- \`\`\`json
2262
- ${stateContent}
2263
- \`\`\`
2264
- `;
2265
- }
2266
- }
2352
+ const content = appendProjectState(
2353
+ fs7.readFileSync(skillMdPath, "utf-8"),
2354
+ metadata,
2355
+ projectPath,
2356
+ !!opts.path
2357
+ );
2267
2358
  process.stdout.write(preamble + content);
2268
2359
  process.exit(ExitCode.SUCCESS);
2269
2360
  });
@@ -2281,6 +2372,48 @@ var REQUIRED_SECTIONS = [
2281
2372
  "## Success Criteria",
2282
2373
  "## Examples"
2283
2374
  ];
2375
+ function validateSkillMd(name, skillMdPath, skillType, errors) {
2376
+ if (!fs8.existsSync(skillMdPath)) {
2377
+ errors.push(`${name}: missing SKILL.md`);
2378
+ return;
2379
+ }
2380
+ const mdContent = fs8.readFileSync(skillMdPath, "utf-8");
2381
+ for (const section of REQUIRED_SECTIONS) {
2382
+ if (!mdContent.includes(section)) {
2383
+ errors.push(`${name}/SKILL.md: missing section "${section}"`);
2384
+ }
2385
+ }
2386
+ if (!mdContent.trim().startsWith("# ")) {
2387
+ errors.push(`${name}/SKILL.md: must start with an h1 heading`);
2388
+ }
2389
+ if (skillType === "rigid") {
2390
+ if (!mdContent.includes("## Gates"))
2391
+ errors.push(`${name}/SKILL.md: rigid skill missing "## Gates" section`);
2392
+ if (!mdContent.includes("## Escalation"))
2393
+ errors.push(`${name}/SKILL.md: rigid skill missing "## Escalation" section`);
2394
+ }
2395
+ }
2396
+ function validateSkillEntry(name, skillsDir, errors) {
2397
+ const skillDir = path17.join(skillsDir, name);
2398
+ const yamlPath = path17.join(skillDir, "skill.yaml");
2399
+ if (!fs8.existsSync(yamlPath)) {
2400
+ errors.push(`${name}: missing skill.yaml`);
2401
+ return false;
2402
+ }
2403
+ try {
2404
+ const raw = fs8.readFileSync(yamlPath, "utf-8");
2405
+ const result = SkillMetadataSchema.safeParse(parse3(raw));
2406
+ if (!result.success) {
2407
+ errors.push(`${name}/skill.yaml: ${result.error.message}`);
2408
+ return false;
2409
+ }
2410
+ validateSkillMd(name, path17.join(skillDir, "SKILL.md"), result.data.type, errors);
2411
+ return true;
2412
+ } catch (e) {
2413
+ errors.push(`${name}: parse error \u2014 ${e instanceof Error ? e.message : String(e)}`);
2414
+ return false;
2415
+ }
2416
+ }
2284
2417
  function createValidateCommand3() {
2285
2418
  return new Command23("validate").description("Validate all skill.yaml files and SKILL.md structure").action(async (_opts, cmd) => {
2286
2419
  const globalOpts = cmd.optsWithGlobals();
@@ -2294,46 +2427,7 @@ function createValidateCommand3() {
2294
2427
  const errors = [];
2295
2428
  let validated = 0;
2296
2429
  for (const name of entries) {
2297
- const skillDir = path17.join(skillsDir, name);
2298
- const yamlPath = path17.join(skillDir, "skill.yaml");
2299
- const skillMdPath = path17.join(skillDir, "SKILL.md");
2300
- if (!fs8.existsSync(yamlPath)) {
2301
- errors.push(`${name}: missing skill.yaml`);
2302
- continue;
2303
- }
2304
- try {
2305
- const raw = fs8.readFileSync(yamlPath, "utf-8");
2306
- const parsed = parse3(raw);
2307
- const result = SkillMetadataSchema.safeParse(parsed);
2308
- if (!result.success) {
2309
- errors.push(`${name}/skill.yaml: ${result.error.message}`);
2310
- continue;
2311
- }
2312
- if (fs8.existsSync(skillMdPath)) {
2313
- const mdContent = fs8.readFileSync(skillMdPath, "utf-8");
2314
- for (const section of REQUIRED_SECTIONS) {
2315
- if (!mdContent.includes(section)) {
2316
- errors.push(`${name}/SKILL.md: missing section "${section}"`);
2317
- }
2318
- }
2319
- if (!mdContent.trim().startsWith("# ")) {
2320
- errors.push(`${name}/SKILL.md: must start with an h1 heading`);
2321
- }
2322
- if (result.data.type === "rigid") {
2323
- if (!mdContent.includes("## Gates")) {
2324
- errors.push(`${name}/SKILL.md: rigid skill missing "## Gates" section`);
2325
- }
2326
- if (!mdContent.includes("## Escalation")) {
2327
- errors.push(`${name}/SKILL.md: rigid skill missing "## Escalation" section`);
2328
- }
2329
- }
2330
- } else {
2331
- errors.push(`${name}: missing SKILL.md`);
2332
- }
2333
- validated++;
2334
- } catch (e) {
2335
- errors.push(`${name}: parse error \u2014 ${e instanceof Error ? e.message : String(e)}`);
2336
- }
2430
+ if (validateSkillEntry(name, skillsDir, errors)) validated++;
2337
2431
  }
2338
2432
  if (globalOpts.json) {
2339
2433
  logger.raw({ validated, errors });
@@ -3031,8 +3125,8 @@ function createResetCommand() {
3031
3125
  }
3032
3126
  if (!opts.yes) {
3033
3127
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
3034
- const answer = await new Promise((resolve29) => {
3035
- 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);
3036
3130
  });
3037
3131
  rl.close();
3038
3132
  if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
@@ -3146,11 +3240,302 @@ function createStateCommand() {
3146
3240
  return command;
3147
3241
  }
3148
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
+
3149
3534
  // src/commands/ci/index.ts
3150
- import { Command as Command36 } from "commander";
3535
+ import { Command as Command38 } from "commander";
3151
3536
 
3152
3537
  // src/commands/ci/check.ts
3153
- import { Command as Command34 } from "commander";
3538
+ import { Command as Command36 } from "commander";
3154
3539
  var VALID_CHECKS = [
3155
3540
  "validate",
3156
3541
  "deps",
@@ -3190,9 +3575,9 @@ function parseFailOn(failOn) {
3190
3575
  return "error";
3191
3576
  }
3192
3577
  function createCheckCommand() {
3193
- 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) => {
3194
3579
  const globalOpts = cmd.optsWithGlobals();
3195
- const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : globalOpts.verbose ? OutputMode.VERBOSE : OutputMode.TEXT;
3580
+ const mode = resolveOutputMode(globalOpts);
3196
3581
  const skip = parseSkip(opts.skip);
3197
3582
  const failOn = parseFailOn(opts.failOn);
3198
3583
  const result = await runCICheck({
@@ -3234,9 +3619,9 @@ function createCheckCommand() {
3234
3619
  }
3235
3620
 
3236
3621
  // src/commands/ci/init.ts
3237
- import { Command as Command35 } from "commander";
3238
- import * as fs16 from "fs";
3239
- import * as path28 from "path";
3622
+ import { Command as Command37 } from "commander";
3623
+ import * as fs19 from "fs";
3624
+ import * as path31 from "path";
3240
3625
  var ALL_CHECKS = [
3241
3626
  "validate",
3242
3627
  "deps",
@@ -3337,12 +3722,12 @@ function generateCIConfig(options) {
3337
3722
  });
3338
3723
  }
3339
3724
  function detectPlatform() {
3340
- if (fs16.existsSync(".github")) return "github";
3341
- if (fs16.existsSync(".gitlab-ci.yml")) return "gitlab";
3725
+ if (fs19.existsSync(".github")) return "github";
3726
+ if (fs19.existsSync(".gitlab-ci.yml")) return "gitlab";
3342
3727
  return null;
3343
3728
  }
3344
3729
  function createInitCommand2() {
3345
- 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) => {
3346
3731
  const globalOpts = cmd.optsWithGlobals();
3347
3732
  const platform = opts.platform ?? detectPlatform() ?? "generic";
3348
3733
  const checks = opts.checks ? opts.checks.split(",").map((s) => s.trim()) : void 0;
@@ -3354,12 +3739,12 @@ function createInitCommand2() {
3354
3739
  process.exit(result.error.exitCode);
3355
3740
  }
3356
3741
  const { filename, content } = result.value;
3357
- const targetPath = path28.resolve(filename);
3358
- const dir = path28.dirname(targetPath);
3359
- fs16.mkdirSync(dir, { recursive: true });
3360
- 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);
3361
3746
  if (platform === "generic" && process.platform !== "win32") {
3362
- fs16.chmodSync(targetPath, "755");
3747
+ fs19.chmodSync(targetPath, "755");
3363
3748
  }
3364
3749
  if (globalOpts.json) {
3365
3750
  console.log(JSON.stringify({ file: filename, platform }));
@@ -3372,18 +3757,275 @@ function createInitCommand2() {
3372
3757
 
3373
3758
  // src/commands/ci/index.ts
3374
3759
  function createCICommand() {
3375
- const command = new Command36("ci").description("CI/CD integration commands");
3760
+ const command = new Command38("ci").description("CI/CD integration commands");
3376
3761
  command.addCommand(createCheckCommand());
3377
3762
  command.addCommand(createInitCommand2());
3378
3763
  return command;
3379
3764
  }
3380
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
+
3381
4023
  // src/commands/update.ts
3382
- import { Command as Command37 } from "commander";
4024
+ import { Command as Command43 } from "commander";
3383
4025
  import { execFileSync as execFileSync4 } from "child_process";
3384
4026
  import { realpathSync } from "fs";
3385
4027
  import readline2 from "readline";
3386
- import chalk3 from "chalk";
4028
+ import chalk5 from "chalk";
3387
4029
  function detectPackageManager() {
3388
4030
  try {
3389
4031
  const argv1 = process.argv[1];
@@ -3439,15 +4081,30 @@ function prompt(question) {
3439
4081
  input: process.stdin,
3440
4082
  output: process.stdout
3441
4083
  });
3442
- return new Promise((resolve29) => {
4084
+ return new Promise((resolve30) => {
3443
4085
  rl.question(question, (answer) => {
3444
4086
  rl.close();
3445
- resolve29(answer.trim().toLowerCase());
4087
+ resolve30(answer.trim().toLowerCase());
3446
4088
  });
3447
4089
  });
3448
4090
  }
4091
+ async function offerRegeneration() {
4092
+ console.log("");
4093
+ const regenAnswer = await prompt("Regenerate slash commands and agent definitions? (Y/n) ");
4094
+ if (regenAnswer === "n" || regenAnswer === "no") return;
4095
+ const scopeAnswer = await prompt("Generate for (G)lobal or (l)ocal project? (G/l) ");
4096
+ const isGlobal = scopeAnswer !== "l" && scopeAnswer !== "local";
4097
+ try {
4098
+ execFileSync4("harness", ["generate", ...isGlobal ? ["--global"] : []], {
4099
+ stdio: "inherit"
4100
+ });
4101
+ } catch {
4102
+ logger.warn("Generation failed. Run manually:");
4103
+ console.log(` ${chalk5.cyan(`harness generate${isGlobal ? " --global" : ""}`)}`);
4104
+ }
4105
+ }
3449
4106
  function createUpdateCommand() {
3450
- 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) => {
3451
4108
  const globalOpts = cmd.optsWithGlobals();
3452
4109
  const pm = detectPackageManager();
3453
4110
  if (globalOpts.verbose) {
@@ -3469,8 +4126,8 @@ function createUpdateCommand() {
3469
4126
  }
3470
4127
  if (currentVersion) {
3471
4128
  console.log("");
3472
- logger.info(`Current CLI version: ${chalk3.dim(`v${currentVersion}`)}`);
3473
- 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}`)}`);
3474
4131
  console.log("");
3475
4132
  }
3476
4133
  }
@@ -3496,31 +4153,18 @@ function createUpdateCommand() {
3496
4153
  } catch {
3497
4154
  console.log("");
3498
4155
  logger.error("Update failed. You can try manually:");
3499
- console.log(` ${chalk3.cyan(installCmd)}`);
4156
+ console.log(` ${chalk5.cyan(installCmd)}`);
3500
4157
  process.exit(ExitCode.ERROR);
3501
4158
  }
3502
- console.log("");
3503
- const regenAnswer = await prompt("Regenerate slash commands and agent definitions? (Y/n) ");
3504
- if (regenAnswer !== "n" && regenAnswer !== "no") {
3505
- const scopeAnswer = await prompt("Generate for (G)lobal or (l)ocal project? (G/l) ");
3506
- const isGlobal = scopeAnswer !== "l" && scopeAnswer !== "local";
3507
- try {
3508
- execFileSync4("harness", ["generate", ...isGlobal ? ["--global"] : []], {
3509
- stdio: "inherit"
3510
- });
3511
- } catch {
3512
- logger.warn("Generation failed. Run manually:");
3513
- console.log(` ${chalk3.cyan(`harness generate${isGlobal ? " --global" : ""}`)}`);
3514
- }
3515
- }
4159
+ await offerRegeneration();
3516
4160
  process.exit(ExitCode.SUCCESS);
3517
4161
  });
3518
4162
  }
3519
4163
 
3520
4164
  // src/commands/generate.ts
3521
- import { Command as Command38 } from "commander";
4165
+ import { Command as Command44 } from "commander";
3522
4166
  function createGenerateCommand3() {
3523
- 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) => {
3524
4168
  const globalOpts = cmd.optsWithGlobals();
3525
4169
  const platforms = opts.platforms.split(",").map((p) => p.trim());
3526
4170
  for (const p of platforms) {
@@ -3579,10 +4223,10 @@ function createGenerateCommand3() {
3579
4223
  }
3580
4224
 
3581
4225
  // src/commands/graph/scan.ts
3582
- import { Command as Command39 } from "commander";
3583
- import * as path29 from "path";
4226
+ import { Command as Command45 } from "commander";
4227
+ import * as path35 from "path";
3584
4228
  async function runScan(projectPath) {
3585
- const { GraphStore, CodeIngestor, TopologicalLinker, KnowledgeIngestor, GitIngestor } = await import("./dist-HXHWB7SV.js");
4229
+ const { GraphStore, CodeIngestor, TopologicalLinker, KnowledgeIngestor, GitIngestor } = await import("./dist-B26DFXMP.js");
3586
4230
  const store = new GraphStore();
3587
4231
  const start = Date.now();
3588
4232
  await new CodeIngestor(store).ingest(projectPath);
@@ -3593,13 +4237,13 @@ async function runScan(projectPath) {
3593
4237
  await new GitIngestor(store).ingest(projectPath);
3594
4238
  } catch {
3595
4239
  }
3596
- const graphDir = path29.join(projectPath, ".harness", "graph");
4240
+ const graphDir = path35.join(projectPath, ".harness", "graph");
3597
4241
  await store.save(graphDir);
3598
4242
  return { nodeCount: store.nodeCount, edgeCount: store.edgeCount, durationMs: Date.now() - start };
3599
4243
  }
3600
4244
  function createScanCommand() {
3601
- return new Command39("scan").description("Scan project and build knowledge graph").argument("[path]", "Project root path", ".").action(async (inputPath, _opts, cmd) => {
3602
- 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);
3603
4247
  const globalOpts = cmd.optsWithGlobals();
3604
4248
  try {
3605
4249
  const result = await runScan(projectPath);
@@ -3618,13 +4262,13 @@ function createScanCommand() {
3618
4262
  }
3619
4263
 
3620
4264
  // src/commands/graph/ingest.ts
3621
- import { Command as Command40 } from "commander";
3622
- import * as path30 from "path";
4265
+ import { Command as Command46 } from "commander";
4266
+ import * as path36 from "path";
3623
4267
  async function loadConnectorConfig(projectPath, source) {
3624
4268
  try {
3625
- const fs23 = await import("fs/promises");
3626
- const configPath = path30.join(projectPath, "harness.config.json");
3627
- 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"));
3628
4272
  const connector = config.graph?.connectors?.find(
3629
4273
  (c) => c.source === source
3630
4274
  );
@@ -3663,8 +4307,8 @@ async function runIngest(projectPath, source, opts) {
3663
4307
  SyncManager,
3664
4308
  JiraConnector,
3665
4309
  SlackConnector
3666
- } = await import("./dist-HXHWB7SV.js");
3667
- const graphDir = path30.join(projectPath, ".harness", "graph");
4310
+ } = await import("./dist-B26DFXMP.js");
4311
+ const graphDir = path36.join(projectPath, ".harness", "graph");
3668
4312
  const store = new GraphStore();
3669
4313
  await store.load(graphDir);
3670
4314
  if (opts?.all) {
@@ -3725,13 +4369,13 @@ async function runIngest(projectPath, source, opts) {
3725
4369
  return result;
3726
4370
  }
3727
4371
  function createIngestCommand() {
3728
- 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) => {
3729
4373
  if (!opts.source && !opts.all) {
3730
4374
  console.error("Error: --source or --all is required");
3731
4375
  process.exit(1);
3732
4376
  }
3733
4377
  const globalOpts = cmd.optsWithGlobals();
3734
- const projectPath = path30.resolve(globalOpts.config ? path30.dirname(globalOpts.config) : ".");
4378
+ const projectPath = path36.resolve(globalOpts.config ? path36.dirname(globalOpts.config) : ".");
3735
4379
  try {
3736
4380
  const result = await runIngest(projectPath, opts.source ?? "", {
3737
4381
  full: opts.full,
@@ -3753,12 +4397,12 @@ function createIngestCommand() {
3753
4397
  }
3754
4398
 
3755
4399
  // src/commands/graph/query.ts
3756
- import { Command as Command41 } from "commander";
3757
- import * as path31 from "path";
4400
+ import { Command as Command47 } from "commander";
4401
+ import * as path37 from "path";
3758
4402
  async function runQuery(projectPath, rootNodeId, opts) {
3759
- const { GraphStore, ContextQL } = await import("./dist-HXHWB7SV.js");
4403
+ const { GraphStore, ContextQL } = await import("./dist-B26DFXMP.js");
3760
4404
  const store = new GraphStore();
3761
- const graphDir = path31.join(projectPath, ".harness", "graph");
4405
+ const graphDir = path37.join(projectPath, ".harness", "graph");
3762
4406
  const loaded = await store.load(graphDir);
3763
4407
  if (!loaded) throw new Error("No graph found. Run `harness scan` first.");
3764
4408
  const params = {
@@ -3772,9 +4416,9 @@ async function runQuery(projectPath, rootNodeId, opts) {
3772
4416
  return cql.execute(params);
3773
4417
  }
3774
4418
  function createQueryCommand() {
3775
- 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) => {
3776
4420
  const globalOpts = cmd.optsWithGlobals();
3777
- const projectPath = path31.resolve(globalOpts.config ? path31.dirname(globalOpts.config) : ".");
4421
+ const projectPath = path37.resolve(globalOpts.config ? path37.dirname(globalOpts.config) : ".");
3778
4422
  try {
3779
4423
  const result = await runQuery(projectPath, rootNodeId, {
3780
4424
  depth: parseInt(opts.depth),
@@ -3800,21 +4444,21 @@ function createQueryCommand() {
3800
4444
  }
3801
4445
 
3802
4446
  // src/commands/graph/index.ts
3803
- import { Command as Command42 } from "commander";
4447
+ import { Command as Command48 } from "commander";
3804
4448
 
3805
4449
  // src/commands/graph/status.ts
3806
- import * as path32 from "path";
4450
+ import * as path38 from "path";
3807
4451
  async function runGraphStatus(projectPath) {
3808
- const { GraphStore } = await import("./dist-HXHWB7SV.js");
3809
- const graphDir = path32.join(projectPath, ".harness", "graph");
4452
+ const { GraphStore } = await import("./dist-B26DFXMP.js");
4453
+ const graphDir = path38.join(projectPath, ".harness", "graph");
3810
4454
  const store = new GraphStore();
3811
4455
  const loaded = await store.load(graphDir);
3812
4456
  if (!loaded) return { status: "no_graph", message: "No graph found. Run `harness scan` first." };
3813
- const fs23 = await import("fs/promises");
3814
- const metaPath = path32.join(graphDir, "metadata.json");
4457
+ const fs29 = await import("fs/promises");
4458
+ const metaPath = path38.join(graphDir, "metadata.json");
3815
4459
  let lastScan = "unknown";
3816
4460
  try {
3817
- const meta = JSON.parse(await fs23.readFile(metaPath, "utf-8"));
4461
+ const meta = JSON.parse(await fs29.readFile(metaPath, "utf-8"));
3818
4462
  lastScan = meta.lastScanTimestamp;
3819
4463
  } catch {
3820
4464
  }
@@ -3825,8 +4469,8 @@ async function runGraphStatus(projectPath) {
3825
4469
  }
3826
4470
  let connectorSyncStatus = {};
3827
4471
  try {
3828
- const syncMetaPath = path32.join(graphDir, "sync-metadata.json");
3829
- 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"));
3830
4474
  for (const [name, data] of Object.entries(syncMeta.connectors ?? {})) {
3831
4475
  connectorSyncStatus[name] = data.lastSyncTimestamp;
3832
4476
  }
@@ -3843,10 +4487,10 @@ async function runGraphStatus(projectPath) {
3843
4487
  }
3844
4488
 
3845
4489
  // src/commands/graph/export.ts
3846
- import * as path33 from "path";
4490
+ import * as path39 from "path";
3847
4491
  async function runGraphExport(projectPath, format) {
3848
- const { GraphStore } = await import("./dist-HXHWB7SV.js");
3849
- const graphDir = path33.join(projectPath, ".harness", "graph");
4492
+ const { GraphStore } = await import("./dist-B26DFXMP.js");
4493
+ const graphDir = path39.join(projectPath, ".harness", "graph");
3850
4494
  const store = new GraphStore();
3851
4495
  const loaded = await store.load(graphDir);
3852
4496
  if (!loaded) throw new Error("No graph found. Run `harness scan` first.");
@@ -3875,13 +4519,13 @@ async function runGraphExport(projectPath, format) {
3875
4519
  }
3876
4520
 
3877
4521
  // src/commands/graph/index.ts
3878
- import * as path34 from "path";
4522
+ import * as path40 from "path";
3879
4523
  function createGraphCommand() {
3880
- const graph = new Command42("graph").description("Knowledge graph management");
4524
+ const graph = new Command48("graph").description("Knowledge graph management");
3881
4525
  graph.command("status").description("Show graph statistics").action(async (_opts, cmd) => {
3882
4526
  try {
3883
4527
  const globalOpts = cmd.optsWithGlobals();
3884
- const projectPath = path34.resolve(globalOpts.config ? path34.dirname(globalOpts.config) : ".");
4528
+ const projectPath = path40.resolve(globalOpts.config ? path40.dirname(globalOpts.config) : ".");
3885
4529
  const result = await runGraphStatus(projectPath);
3886
4530
  if (globalOpts.json) {
3887
4531
  console.log(JSON.stringify(result, null, 2));
@@ -3908,7 +4552,7 @@ function createGraphCommand() {
3908
4552
  });
3909
4553
  graph.command("export").description("Export graph").requiredOption("--format <format>", "Output format (json, mermaid)").action(async (opts, cmd) => {
3910
4554
  const globalOpts = cmd.optsWithGlobals();
3911
- const projectPath = path34.resolve(globalOpts.config ? path34.dirname(globalOpts.config) : ".");
4555
+ const projectPath = path40.resolve(globalOpts.config ? path40.dirname(globalOpts.config) : ".");
3912
4556
  try {
3913
4557
  const output = await runGraphExport(projectPath, opts.format);
3914
4558
  console.log(output);
@@ -3921,19 +4565,19 @@ function createGraphCommand() {
3921
4565
  }
3922
4566
 
3923
4567
  // src/commands/mcp.ts
3924
- import { Command as Command43 } from "commander";
4568
+ import { Command as Command49 } from "commander";
3925
4569
  function createMcpCommand() {
3926
- return new Command43("mcp").description("Start the MCP (Model Context Protocol) server on stdio").action(async () => {
3927
- const { startServer: startServer2 } = await import("./mcp-JQUI7BVZ.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");
3928
4572
  await startServer2();
3929
4573
  });
3930
4574
  }
3931
4575
 
3932
4576
  // src/commands/impact-preview.ts
3933
- import { Command as Command44 } from "commander";
4577
+ import { Command as Command50 } from "commander";
3934
4578
  import { execSync as execSync3 } from "child_process";
3935
- import * as path35 from "path";
3936
- import * as fs17 from "fs";
4579
+ import * as path41 from "path";
4580
+ import * as fs23 from "fs";
3937
4581
  function getStagedFiles(cwd) {
3938
4582
  try {
3939
4583
  const output = execSync3("git diff --cached --name-only", {
@@ -3947,7 +4591,7 @@ function getStagedFiles(cwd) {
3947
4591
  }
3948
4592
  function graphExists(projectPath) {
3949
4593
  try {
3950
- return fs17.existsSync(path35.join(projectPath, ".harness", "graph", "graph.json"));
4594
+ return fs23.existsSync(path41.join(projectPath, ".harness", "graph", "graph.json"));
3951
4595
  } catch {
3952
4596
  return false;
3953
4597
  }
@@ -3956,7 +4600,7 @@ function extractNodeName(id) {
3956
4600
  const parts = id.split(":");
3957
4601
  if (parts.length > 1) {
3958
4602
  const fullPath = parts.slice(1).join(":");
3959
- return path35.basename(fullPath);
4603
+ return path41.basename(fullPath);
3960
4604
  }
3961
4605
  return id;
3962
4606
  }
@@ -4079,7 +4723,7 @@ function formatPerFile(perFileResults) {
4079
4723
  return lines.join("\n");
4080
4724
  }
4081
4725
  async function runImpactPreview(options) {
4082
- const projectPath = path35.resolve(options.path ?? process.cwd());
4726
+ const projectPath = path41.resolve(options.path ?? process.cwd());
4083
4727
  const stagedFiles = getStagedFiles(projectPath);
4084
4728
  if (stagedFiles.length === 0) {
4085
4729
  return "Impact Preview: no staged changes";
@@ -4126,7 +4770,7 @@ async function runImpactPreview(options) {
4126
4770
  return formatCompact(stagedFiles.length, merged, aggregateCounts);
4127
4771
  }
4128
4772
  function createImpactPreviewCommand() {
4129
- 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) => {
4130
4774
  const output = await runImpactPreview({
4131
4775
  detailed: opts.detailed,
4132
4776
  perFile: opts.perFile,
@@ -4139,9 +4783,9 @@ function createImpactPreviewCommand() {
4139
4783
  }
4140
4784
 
4141
4785
  // src/commands/check-arch.ts
4142
- import { Command as Command45 } from "commander";
4786
+ import { Command as Command51 } from "commander";
4143
4787
  import { execSync as execSync4 } from "child_process";
4144
- function getCommitHash(cwd) {
4788
+ function getCommitHash2(cwd) {
4145
4789
  try {
4146
4790
  return execSync4("git rev-parse --short HEAD", { cwd, encoding: "utf-8" }).toString().trim();
4147
4791
  } catch {
@@ -4189,7 +4833,7 @@ async function runCheckArch(options) {
4189
4833
  }
4190
4834
  const manager = new ArchBaselineManager(cwd, archConfig.baselinePath);
4191
4835
  if (options.updateBaseline) {
4192
- const commitHash = getCommitHash(cwd);
4836
+ const commitHash = getCommitHash2(cwd);
4193
4837
  const baseline2 = manager.capture(results, commitHash);
4194
4838
  manager.save(baseline2);
4195
4839
  return Ok({
@@ -4234,7 +4878,7 @@ async function runCheckArch(options) {
4234
4878
  });
4235
4879
  }
4236
4880
  function createCheckArchCommand() {
4237
- 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) => {
4238
4882
  const globalOpts = cmd.optsWithGlobals();
4239
4883
  const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : globalOpts.verbose ? OutputMode.VERBOSE : OutputMode.TEXT;
4240
4884
  const formatter = new OutputFormatter(mode);
@@ -4300,20 +4944,20 @@ function createCheckArchCommand() {
4300
4944
  }
4301
4945
 
4302
4946
  // src/commands/blueprint.ts
4303
- import { Command as Command46 } from "commander";
4304
- import * as path36 from "path";
4947
+ import { Command as Command52 } from "commander";
4948
+ import * as path42 from "path";
4305
4949
  function createBlueprintCommand() {
4306
- 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) => {
4307
4951
  try {
4308
- const rootDir = path36.resolve(projectPath);
4309
- const outputDir = path36.resolve(options.output);
4952
+ const rootDir = path42.resolve(projectPath);
4953
+ const outputDir = path42.resolve(options.output);
4310
4954
  logger.info(`Scanning project at ${rootDir}...`);
4311
4955
  const scanner = new ProjectScanner(rootDir);
4312
4956
  const data = await scanner.scan();
4313
4957
  logger.info(`Generating blueprint to ${outputDir}...`);
4314
4958
  const generator = new BlueprintGenerator();
4315
4959
  await generator.generate(data, { outputDir });
4316
- logger.success(`Blueprint generated successfully at ${path36.join(outputDir, "index.html")}`);
4960
+ logger.success(`Blueprint generated successfully at ${path42.join(outputDir, "index.html")}`);
4317
4961
  } catch (error) {
4318
4962
  logger.error(
4319
4963
  `Failed to generate blueprint: ${error instanceof Error ? error.message : String(error)}`
@@ -4324,16 +4968,16 @@ function createBlueprintCommand() {
4324
4968
  }
4325
4969
 
4326
4970
  // src/commands/share.ts
4327
- import { Command as Command47 } from "commander";
4328
- import * as fs18 from "fs";
4329
- import * as path37 from "path";
4971
+ import { Command as Command53 } from "commander";
4972
+ import * as fs24 from "fs";
4973
+ import * as path43 from "path";
4330
4974
  import { parse as parseYaml } from "yaml";
4331
4975
  var MANIFEST_FILENAME = "constraints.yaml";
4332
4976
  function createShareCommand() {
4333
- 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) => {
4334
- const rootDir = path37.resolve(projectPath);
4335
- const manifestPath = path37.join(rootDir, MANIFEST_FILENAME);
4336
- 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)) {
4337
4981
  logger.error(
4338
4982
  `No ${MANIFEST_FILENAME} found at ${manifestPath}.
4339
4983
  Create a constraints.yaml in your project root to define what to share.`
@@ -4342,7 +4986,7 @@ Create a constraints.yaml in your project root to define what to share.`
4342
4986
  }
4343
4987
  let parsed;
4344
4988
  try {
4345
- const raw = fs18.readFileSync(manifestPath, "utf-8");
4989
+ const raw = fs24.readFileSync(manifestPath, "utf-8");
4346
4990
  parsed = parseYaml(raw);
4347
4991
  } catch (err) {
4348
4992
  logger.error(
@@ -4356,7 +5000,7 @@ Create a constraints.yaml in your project root to define what to share.`
4356
5000
  process.exit(1);
4357
5001
  }
4358
5002
  const manifest = manifestResult.value;
4359
- const configResult = resolveConfig(path37.join(rootDir, "harness.config.json"));
5003
+ const configResult = resolveConfig(path43.join(rootDir, "harness.config.json"));
4360
5004
  if (!configResult.ok) {
4361
5005
  logger.error(configResult.error.message);
4362
5006
  process.exit(1);
@@ -4374,8 +5018,8 @@ Create a constraints.yaml in your project root to define what to share.`
4374
5018
  );
4375
5019
  process.exit(1);
4376
5020
  }
4377
- const outputDir = path37.resolve(options.output);
4378
- 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`);
4379
5023
  const writeResult = await writeConfig(outputPath, bundle);
4380
5024
  if (!writeResult.ok) {
4381
5025
  logger.error(`Failed to write bundle: ${writeResult.error.message}`);
@@ -4386,25 +5030,25 @@ Create a constraints.yaml in your project root to define what to share.`
4386
5030
  }
4387
5031
 
4388
5032
  // src/commands/install.ts
4389
- import * as fs20 from "fs";
4390
- import * as path39 from "path";
4391
- 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";
4392
5036
  import { parse as yamlParse } from "yaml";
4393
5037
 
4394
5038
  // src/registry/tarball.ts
4395
- import * as fs19 from "fs";
4396
- import * as path38 from "path";
4397
- import * as os3 from "os";
5039
+ import * as fs25 from "fs";
5040
+ import * as path44 from "path";
5041
+ import * as os6 from "os";
4398
5042
  import { execFileSync as execFileSync5 } from "child_process";
4399
5043
  function extractTarball(tarballBuffer) {
4400
- const tmpDir = fs19.mkdtempSync(path38.join(os3.tmpdir(), "harness-skill-install-"));
4401
- 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");
4402
5046
  try {
4403
- fs19.writeFileSync(tarballPath, tarballBuffer);
5047
+ fs25.writeFileSync(tarballPath, tarballBuffer);
4404
5048
  execFileSync5("tar", ["-xzf", tarballPath, "-C", tmpDir], {
4405
5049
  timeout: 3e4
4406
5050
  });
4407
- fs19.unlinkSync(tarballPath);
5051
+ fs25.unlinkSync(tarballPath);
4408
5052
  } catch (err) {
4409
5053
  cleanupTempDir(tmpDir);
4410
5054
  throw new Error(
@@ -4415,43 +5059,43 @@ function extractTarball(tarballBuffer) {
4415
5059
  return tmpDir;
4416
5060
  }
4417
5061
  function placeSkillContent(extractedPkgDir, communityBaseDir, skillName, platforms) {
4418
- const files = fs19.readdirSync(extractedPkgDir);
5062
+ const files = fs25.readdirSync(extractedPkgDir);
4419
5063
  for (const platform of platforms) {
4420
- const targetDir = path38.join(communityBaseDir, platform, skillName);
4421
- if (fs19.existsSync(targetDir)) {
4422
- 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 });
4423
5067
  }
4424
- fs19.mkdirSync(targetDir, { recursive: true });
5068
+ fs25.mkdirSync(targetDir, { recursive: true });
4425
5069
  for (const file of files) {
4426
5070
  if (file === "package.json" || file === "node_modules") continue;
4427
- const srcPath = path38.join(extractedPkgDir, file);
4428
- const destPath = path38.join(targetDir, file);
4429
- 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);
4430
5074
  if (stat.isDirectory()) {
4431
- fs19.cpSync(srcPath, destPath, { recursive: true });
5075
+ fs25.cpSync(srcPath, destPath, { recursive: true });
4432
5076
  } else {
4433
- fs19.copyFileSync(srcPath, destPath);
5077
+ fs25.copyFileSync(srcPath, destPath);
4434
5078
  }
4435
5079
  }
4436
5080
  }
4437
5081
  }
4438
5082
  function removeSkillContent(communityBaseDir, skillName, platforms) {
4439
5083
  for (const platform of platforms) {
4440
- const targetDir = path38.join(communityBaseDir, platform, skillName);
4441
- if (fs19.existsSync(targetDir)) {
4442
- 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 });
4443
5087
  }
4444
5088
  }
4445
5089
  }
4446
5090
  function cleanupTempDir(dirPath) {
4447
5091
  try {
4448
- fs19.rmSync(dirPath, { recursive: true, force: true });
5092
+ fs25.rmSync(dirPath, { recursive: true, force: true });
4449
5093
  } catch {
4450
5094
  }
4451
5095
  }
4452
5096
 
4453
5097
  // src/registry/resolver.ts
4454
- import semver2 from "semver";
5098
+ import semver3 from "semver";
4455
5099
  function resolveVersion(metadata, versionRange) {
4456
5100
  const versions = Object.keys(metadata.versions);
4457
5101
  if (versions.length === 0) {
@@ -4463,13 +5107,13 @@ function resolveVersion(metadata, versionRange) {
4463
5107
  const latestInfo = metadata.versions[latestTag];
4464
5108
  if (latestInfo) return latestInfo;
4465
5109
  }
4466
- const highest = semver2.maxSatisfying(versions, "*");
5110
+ const highest = semver3.maxSatisfying(versions, "*");
4467
5111
  if (!highest || !metadata.versions[highest]) {
4468
5112
  throw new Error(`No versions available for ${metadata.name}.`);
4469
5113
  }
4470
5114
  return metadata.versions[highest];
4471
5115
  }
4472
- const matched = semver2.maxSatisfying(versions, versionRange);
5116
+ const matched = semver3.maxSatisfying(versions, versionRange);
4473
5117
  if (!matched || !metadata.versions[matched]) {
4474
5118
  throw new Error(
4475
5119
  `No version of ${metadata.name} matches range ${versionRange}. Available: ${versions.join(", ")}`
@@ -4501,35 +5145,35 @@ function validateSkillYaml(parsed) {
4501
5145
  };
4502
5146
  }
4503
5147
  async function runLocalInstall(fromPath, options) {
4504
- const resolvedPath = path39.resolve(fromPath);
4505
- if (!fs20.existsSync(resolvedPath)) {
5148
+ const resolvedPath = path45.resolve(fromPath);
5149
+ if (!fs26.existsSync(resolvedPath)) {
4506
5150
  throw new Error(`--from path does not exist: ${resolvedPath}`);
4507
5151
  }
4508
- const stat = fs20.statSync(resolvedPath);
5152
+ const stat = fs26.statSync(resolvedPath);
4509
5153
  let extractDir = null;
4510
5154
  let pkgDir;
4511
5155
  if (stat.isDirectory()) {
4512
5156
  pkgDir = resolvedPath;
4513
5157
  } else if (resolvedPath.endsWith(".tgz") || resolvedPath.endsWith(".tar.gz")) {
4514
- const tarballBuffer = fs20.readFileSync(resolvedPath);
5158
+ const tarballBuffer = fs26.readFileSync(resolvedPath);
4515
5159
  extractDir = extractTarball(tarballBuffer);
4516
- pkgDir = path39.join(extractDir, "package");
5160
+ pkgDir = path45.join(extractDir, "package");
4517
5161
  } else {
4518
5162
  throw new Error(`--from path must be a directory or .tgz file. Got: ${resolvedPath}`);
4519
5163
  }
4520
5164
  try {
4521
- const skillYamlPath = path39.join(pkgDir, "skill.yaml");
4522
- if (!fs20.existsSync(skillYamlPath)) {
5165
+ const skillYamlPath = path45.join(pkgDir, "skill.yaml");
5166
+ if (!fs26.existsSync(skillYamlPath)) {
4523
5167
  throw new Error(`No skill.yaml found at ${skillYamlPath}`);
4524
5168
  }
4525
- const rawYaml = fs20.readFileSync(skillYamlPath, "utf-8");
5169
+ const rawYaml = fs26.readFileSync(skillYamlPath, "utf-8");
4526
5170
  const parsed = yamlParse(rawYaml);
4527
5171
  const skillYaml = validateSkillYaml(parsed);
4528
5172
  const shortName = skillYaml.name;
4529
5173
  const globalDir = resolveGlobalSkillsDir();
4530
- const skillsDir = path39.dirname(globalDir);
4531
- const communityBase = path39.join(skillsDir, "community");
4532
- 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");
4533
5177
  const bundledNames = getBundledSkillNames(globalDir);
4534
5178
  if (bundledNames.has(shortName)) {
4535
5179
  throw new Error(
@@ -4570,9 +5214,9 @@ async function runInstall(skillName, options) {
4570
5214
  const packageName = resolvePackageName(skillName);
4571
5215
  const shortName = extractSkillName(packageName);
4572
5216
  const globalDir = resolveGlobalSkillsDir();
4573
- const skillsDir = path39.dirname(globalDir);
4574
- const communityBase = path39.join(skillsDir, "community");
4575
- 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");
4576
5220
  const bundledNames = getBundledSkillNames(globalDir);
4577
5221
  if (bundledNames.has(shortName)) {
4578
5222
  throw new Error(
@@ -4598,12 +5242,12 @@ async function runInstall(skillName, options) {
4598
5242
  const extractDir = extractTarball(tarballBuffer);
4599
5243
  let skillYaml;
4600
5244
  try {
4601
- const extractedPkgDir = path39.join(extractDir, "package");
4602
- const skillYamlPath = path39.join(extractedPkgDir, "skill.yaml");
4603
- if (!fs20.existsSync(skillYamlPath)) {
5245
+ const extractedPkgDir = path45.join(extractDir, "package");
5246
+ const skillYamlPath = path45.join(extractedPkgDir, "skill.yaml");
5247
+ if (!fs26.existsSync(skillYamlPath)) {
4604
5248
  throw new Error(`contains invalid skill.yaml: file not found in package`);
4605
5249
  }
4606
- const rawYaml = fs20.readFileSync(skillYamlPath, "utf-8");
5250
+ const rawYaml = fs26.readFileSync(skillYamlPath, "utf-8");
4607
5251
  const parsed = yamlParse(rawYaml);
4608
5252
  skillYaml = validateSkillYaml(parsed);
4609
5253
  placeSkillContent(extractedPkgDir, communityBase, shortName, skillYaml.platforms);
@@ -4642,7 +5286,7 @@ async function runInstall(skillName, options) {
4642
5286
  return result;
4643
5287
  }
4644
5288
  function createInstallCommand() {
4645
- const cmd = new Command48("install");
5289
+ const cmd = new Command54("install");
4646
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) => {
4647
5291
  try {
4648
5292
  const result = await runInstall(skill, opts);
@@ -4666,15 +5310,15 @@ function createInstallCommand() {
4666
5310
  }
4667
5311
 
4668
5312
  // src/commands/install-constraints.ts
4669
- import * as fs21 from "fs/promises";
4670
- import * as path40 from "path";
4671
- import { Command as Command49 } from "commander";
4672
- 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";
4673
5317
  async function runInstallConstraints(options) {
4674
5318
  const { source, configPath, lockfilePath } = options;
4675
5319
  let rawBundle;
4676
5320
  try {
4677
- rawBundle = await fs21.readFile(source, "utf-8");
5321
+ rawBundle = await fs27.readFile(source, "utf-8");
4678
5322
  } catch (err) {
4679
5323
  if (isNodeError(err) && err.code === "ENOENT") {
4680
5324
  return { ok: false, error: `Bundle file not found: ${source}` };
@@ -4697,9 +5341,9 @@ async function runInstallConstraints(options) {
4697
5341
  }
4698
5342
  const bundle = bundleResult.data;
4699
5343
  if (bundle.minHarnessVersion) {
4700
- const installed = semver3.valid(semver3.coerce(CLI_VERSION));
4701
- const required = semver3.valid(semver3.coerce(bundle.minHarnessVersion));
4702
- 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)) {
4703
5347
  return {
4704
5348
  ok: false,
4705
5349
  error: `Bundle requires harness version >= ${bundle.minHarnessVersion}, but installed version is ${CLI_VERSION}. Please upgrade.`
@@ -4717,7 +5361,7 @@ async function runInstallConstraints(options) {
4717
5361
  }
4718
5362
  let localConfig;
4719
5363
  try {
4720
- const raw = await fs21.readFile(configPath, "utf-8");
5364
+ const raw = await fs27.readFile(configPath, "utf-8");
4721
5365
  localConfig = JSON.parse(raw);
4722
5366
  } catch (err) {
4723
5367
  return {
@@ -4865,7 +5509,7 @@ function isNodeError(err) {
4865
5509
  return err instanceof Error && "code" in err;
4866
5510
  }
4867
5511
  function resolveConfigPath(opts) {
4868
- if (opts.config) return path40.resolve(opts.config);
5512
+ if (opts.config) return path46.resolve(opts.config);
4869
5513
  const found = findConfigFile();
4870
5514
  if (!found.ok) {
4871
5515
  logger.error(found.error.message);
@@ -4900,9 +5544,9 @@ function logInstallResult(val, opts) {
4900
5544
  }
4901
5545
  async function handleInstallConstraints(source, opts) {
4902
5546
  const configPath = resolveConfigPath(opts);
4903
- const projectRoot = path40.dirname(configPath);
4904
- const lockfilePath = path40.join(projectRoot, ".harness", "constraints.lock.json");
4905
- 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);
4906
5550
  if (opts.forceLocal && opts.forcePackage) {
4907
5551
  logger.error("Cannot use both --force-local and --force-package.");
4908
5552
  process.exit(1);
@@ -4922,15 +5566,15 @@ async function handleInstallConstraints(source, opts) {
4922
5566
  logInstallResult(result.value, opts);
4923
5567
  }
4924
5568
  function createInstallConstraintsCommand() {
4925
- const cmd = new Command49("install-constraints");
5569
+ const cmd = new Command55("install-constraints");
4926
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);
4927
5571
  return cmd;
4928
5572
  }
4929
5573
 
4930
5574
  // src/commands/uninstall-constraints.ts
4931
- import * as fs22 from "fs/promises";
4932
- import * as path41 from "path";
4933
- 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";
4934
5578
  async function runUninstallConstraints(options) {
4935
5579
  const { packageName, configPath, lockfilePath } = options;
4936
5580
  const lockfileResult = await readLockfile(lockfilePath);
@@ -4950,7 +5594,7 @@ async function runUninstallConstraints(options) {
4950
5594
  }
4951
5595
  let localConfig;
4952
5596
  try {
4953
- const raw = await fs22.readFile(configPath, "utf-8");
5597
+ const raw = await fs28.readFile(configPath, "utf-8");
4954
5598
  localConfig = JSON.parse(raw);
4955
5599
  } catch (err) {
4956
5600
  return {
@@ -4987,11 +5631,11 @@ async function runUninstallConstraints(options) {
4987
5631
  };
4988
5632
  }
4989
5633
  function createUninstallConstraintsCommand() {
4990
- const cmd = new Command50("uninstall-constraints");
5634
+ const cmd = new Command56("uninstall-constraints");
4991
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) => {
4992
5636
  let configPath;
4993
5637
  if (opts.config) {
4994
- configPath = path41.resolve(opts.config);
5638
+ configPath = path47.resolve(opts.config);
4995
5639
  } else {
4996
5640
  const found = findConfigFile();
4997
5641
  if (!found.ok) {
@@ -5000,8 +5644,8 @@ function createUninstallConstraintsCommand() {
5000
5644
  }
5001
5645
  configPath = found.value;
5002
5646
  }
5003
- const projectRoot = path41.dirname(configPath);
5004
- 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");
5005
5649
  const result = await runUninstallConstraints({
5006
5650
  packageName: name,
5007
5651
  configPath,
@@ -5026,15 +5670,15 @@ function createUninstallConstraintsCommand() {
5026
5670
  }
5027
5671
 
5028
5672
  // src/commands/uninstall.ts
5029
- import * as path42 from "path";
5030
- import { Command as Command51 } from "commander";
5673
+ import * as path48 from "path";
5674
+ import { Command as Command57 } from "commander";
5031
5675
  async function runUninstall(skillName, options) {
5032
5676
  const packageName = resolvePackageName(skillName);
5033
5677
  const shortName = extractSkillName(packageName);
5034
5678
  const globalDir = resolveGlobalSkillsDir();
5035
- const skillsDir = path42.dirname(globalDir);
5036
- const communityBase = path42.join(skillsDir, "community");
5037
- 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");
5038
5682
  const lockfile = readLockfile2(lockfilePath);
5039
5683
  const entry = lockfile.skills[packageName];
5040
5684
  if (!entry) {
@@ -5064,7 +5708,7 @@ async function runUninstall(skillName, options) {
5064
5708
  return result;
5065
5709
  }
5066
5710
  function createUninstallCommand() {
5067
- const cmd = new Command51("uninstall");
5711
+ const cmd = new Command57("uninstall");
5068
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) => {
5069
5713
  try {
5070
5714
  const result = await runUninstall(skill, opts);
@@ -5083,13 +5727,13 @@ function createUninstallCommand() {
5083
5727
  }
5084
5728
 
5085
5729
  // src/commands/orchestrator.ts
5086
- import { Command as Command52 } from "commander";
5087
- import * as path43 from "path";
5730
+ import { Command as Command58 } from "commander";
5731
+ import * as path49 from "path";
5088
5732
  import { Orchestrator, WorkflowLoader, launchTUI } from "@harness-engineering/orchestrator";
5089
5733
  function createOrchestratorCommand() {
5090
- const orchestrator = new Command52("orchestrator");
5734
+ const orchestrator = new Command58("orchestrator");
5091
5735
  orchestrator.command("run").description("Run the orchestrator daemon").option("-w, --workflow <path>", "Path to WORKFLOW.md", "WORKFLOW.md").action(async (opts) => {
5092
- const workflowPath = path43.resolve(process.cwd(), opts.workflow);
5736
+ const workflowPath = path49.resolve(process.cwd(), opts.workflow);
5093
5737
  const loader = new WorkflowLoader();
5094
5738
  const result = await loader.loadWorkflow(workflowPath);
5095
5739
  if (!result.ok) {
@@ -5113,13 +5757,13 @@ function createOrchestratorCommand() {
5113
5757
  }
5114
5758
 
5115
5759
  // src/commands/learnings/index.ts
5116
- import { Command as Command54 } from "commander";
5760
+ import { Command as Command60 } from "commander";
5117
5761
 
5118
5762
  // src/commands/learnings/prune.ts
5119
- import { Command as Command53 } from "commander";
5120
- import * as path44 from "path";
5763
+ import { Command as Command59 } from "commander";
5764
+ import * as path50 from "path";
5121
5765
  async function handlePrune(opts) {
5122
- const projectPath = path44.resolve(opts.path);
5766
+ const projectPath = path50.resolve(opts.path);
5123
5767
  const result = await pruneLearnings(projectPath, opts.stream);
5124
5768
  if (!result.ok) {
5125
5769
  logger.error(result.error.message);
@@ -5158,21 +5802,21 @@ function printPatternProposals(patterns) {
5158
5802
  );
5159
5803
  }
5160
5804
  function createPruneCommand() {
5161
- return new Command53("prune").description(
5805
+ return new Command59("prune").description(
5162
5806
  "Analyze global learnings for patterns, present improvement proposals, and archive old entries"
5163
5807
  ).option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").action(handlePrune);
5164
5808
  }
5165
5809
 
5166
5810
  // src/commands/learnings/index.ts
5167
5811
  function createLearningsCommand() {
5168
- const command = new Command54("learnings").description("Learnings management commands");
5812
+ const command = new Command60("learnings").description("Learnings management commands");
5169
5813
  command.addCommand(createPruneCommand());
5170
5814
  return command;
5171
5815
  }
5172
5816
 
5173
5817
  // src/index.ts
5174
5818
  function createProgram() {
5175
- const program = new Command55();
5819
+ const program = new Command61();
5176
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");
5177
5821
  program.addCommand(createValidateCommand());
5178
5822
  program.addCommand(createCheckDepsCommand());
@@ -5193,10 +5837,13 @@ function createProgram() {
5193
5837
  program.addCommand(createCheckPhaseGateCommand());
5194
5838
  program.addCommand(createCreateSkillCommand());
5195
5839
  program.addCommand(createSetupMcpCommand());
5840
+ program.addCommand(createSetupCommand());
5841
+ program.addCommand(createDoctorCommand());
5196
5842
  program.addCommand(createGenerateSlashCommandsCommand());
5197
5843
  program.addCommand(createGenerateAgentDefinitionsCommand());
5198
5844
  program.addCommand(createGenerateCommand3());
5199
5845
  program.addCommand(createCICommand());
5846
+ program.addCommand(createHooksCommand());
5200
5847
  program.addCommand(createUpdateCommand());
5201
5848
  program.addCommand(createScanCommand());
5202
5849
  program.addCommand(createIngestCommand());
@@ -5217,6 +5864,7 @@ function createProgram() {
5217
5864
 
5218
5865
  export {
5219
5866
  buildPreamble,
5867
+ printFirstRunWelcome,
5220
5868
  runScan,
5221
5869
  runIngest,
5222
5870
  runQuery,