@harness-engineering/cli 1.10.0 → 1.11.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 (59) hide show
  1. package/dist/agents/skills/claude-code/enforce-architecture/SKILL.md +4 -0
  2. package/dist/agents/skills/claude-code/harness-parallel-agents/SKILL.md +105 -20
  3. package/dist/agents/skills/claude-code/harness-pre-commit-review/SKILL.md +37 -0
  4. package/dist/agents/skills/gemini-cli/enforce-architecture/SKILL.md +4 -0
  5. package/dist/agents/skills/gemini-cli/harness-parallel-agents/SKILL.md +105 -20
  6. package/dist/agents/skills/gemini-cli/harness-pre-commit-review/SKILL.md +37 -0
  7. package/dist/{agents-md-EMRFLNBC.js → agents-md-ZFV6RR5J.js} +1 -1
  8. package/dist/{architecture-5JNN5L3M.js → architecture-EXNUMH5R.js} +2 -2
  9. package/dist/bin/harness-mcp.js +14 -14
  10. package/dist/bin/harness.js +20 -20
  11. package/dist/{check-phase-gate-WOKIYGAM.js → check-phase-gate-VZFOY2PO.js} +4 -4
  12. package/dist/{chunk-7X7ZAYMY.js → chunk-2NCIKJES.js} +102 -5
  13. package/dist/{chunk-OPXH4CQN.js → chunk-2YPZKGAG.js} +1 -1
  14. package/dist/{chunk-NX6DSZSM.js → chunk-2YSQOUHO.js} +4483 -2668
  15. package/dist/{chunk-B7HFEHWP.js → chunk-3WGJMBKH.js} +10 -0
  16. package/dist/{chunk-ECUJQS3B.js → chunk-6N4R6FVX.js} +3 -3
  17. package/dist/chunk-EBJQ6N4M.js +39 -0
  18. package/dist/{chunk-46YA6FI3.js → chunk-GNGELAXY.js} +2 -2
  19. package/dist/{chunk-FPIPT36X.js → chunk-GSIVNYVJ.js} +6 -6
  20. package/dist/{chunk-EOLRW32Q.js → chunk-HD4IBGLA.js} +9 -1
  21. package/dist/{chunk-LXU5M77O.js → chunk-I6JZYEGT.js} +390 -57
  22. package/dist/{chunk-F3YDAJFQ.js → chunk-L2KLU56K.js} +2 -2
  23. package/dist/{chunk-F4PTVZWA.js → chunk-NC6PXVWT.js} +7 -7
  24. package/dist/{chunk-4PFMY3H7.js → chunk-PA2XHK75.js} +9 -9
  25. package/dist/{chunk-MO4YQOMB.js → chunk-TI4TGEX6.js} +3 -3
  26. package/dist/{chunk-MDUK2J2O.js → chunk-VRFZWGMS.js} +2 -1
  27. package/dist/{chunk-FX7SQHGD.js → chunk-WJZDO6OY.js} +2 -2
  28. package/dist/{chunk-PMTFPOCT.js → chunk-WUJTCNOU.js} +1 -1
  29. package/dist/{chunk-PSXF277V.js → chunk-X3MN5UQJ.js} +1 -1
  30. package/dist/{chunk-CWZ4Y2PO.js → chunk-Z75JC6I2.js} +4 -4
  31. package/dist/{chunk-PAHHT2IK.js → chunk-ZWC3MN5E.js} +1707 -294
  32. package/dist/{ci-workflow-ZBBUNTHQ.js → ci-workflow-K5RCRNYR.js} +1 -1
  33. package/dist/create-skill-WPXHSLX2.js +11 -0
  34. package/dist/{dist-PBTNVK6K.js → dist-JVZ2MKBC.js} +101 -1
  35. package/dist/{dist-I7DB5VKB.js → dist-M6BQODWC.js} +1145 -0
  36. package/dist/{docs-PTJGD6XI.js → docs-PWCUVYWU.js} +2 -2
  37. package/dist/{engine-SCMZ3G3E.js → engine-6XUP6GAK.js} +1 -1
  38. package/dist/{entropy-YIUBGKY7.js → entropy-4I6JEYAC.js} +2 -2
  39. package/dist/{feedback-WEVQSLAA.js → feedback-TNIW534S.js} +1 -1
  40. package/dist/{generate-agent-definitions-BU5LOJTI.js → generate-agent-definitions-MWKEA5NU.js} +4 -4
  41. package/dist/{graph-loader-RLO3KRIX.js → graph-loader-KO4GJ5N2.js} +1 -1
  42. package/dist/index.d.ts +318 -12
  43. package/dist/index.js +29 -21
  44. package/dist/{loader-6S6PVGSF.js → loader-4FIPIFII.js} +1 -1
  45. package/dist/mcp-MOKLYNZL.js +34 -0
  46. package/dist/{performance-5TVW6SA6.js → performance-BTOJCPXU.js} +2 -2
  47. package/dist/{review-pipeline-4JTQAWKW.js → review-pipeline-3YTW3463.js} +1 -1
  48. package/dist/{runtime-PXIM7UV6.js → runtime-GO7K2PJE.js} +1 -1
  49. package/dist/{security-URYTKLGK.js → security-4P2GGFF6.js} +1 -1
  50. package/dist/skill-executor-RG45LUO5.js +8 -0
  51. package/dist/templates/orchestrator/WORKFLOW.md +48 -0
  52. package/dist/templates/orchestrator/template.json +6 -0
  53. package/dist/{validate-KSDUUK2M.js → validate-JN44D2Q7.js} +2 -2
  54. package/dist/{validate-cross-check-WZAX357V.js → validate-cross-check-DB7RIFFF.js} +1 -1
  55. package/package.json +10 -6
  56. package/dist/chunk-HIOXKZYF.js +0 -15
  57. package/dist/create-skill-LUWO46WF.js +0 -11
  58. package/dist/mcp-BNLBTCXZ.js +0 -34
  59. package/dist/skill-executor-KVS47DAU.js +0 -8
@@ -7,11 +7,11 @@ import {
7
7
  createCheckPhaseGateCommand,
8
8
  findFiles,
9
9
  resolveConfig
10
- } from "./chunk-7X7ZAYMY.js";
10
+ } from "./chunk-2NCIKJES.js";
11
11
  import {
12
12
  createGenerateAgentDefinitionsCommand,
13
13
  generateAgentDefinitions
14
- } from "./chunk-46YA6FI3.js";
14
+ } from "./chunk-GNGELAXY.js";
15
15
  import {
16
16
  listPersonas,
17
17
  loadPersona
@@ -21,16 +21,16 @@ import {
21
21
  } from "./chunk-TRAPF4IX.js";
22
22
  import {
23
23
  executeSkill
24
- } from "./chunk-F3YDAJFQ.js";
24
+ } from "./chunk-L2KLU56K.js";
25
25
  import {
26
26
  ALLOWED_PERSONA_COMMANDS
27
27
  } from "./chunk-TEFCFC4H.js";
28
28
  import {
29
29
  createCreateSkillCommand
30
- } from "./chunk-ECUJQS3B.js";
30
+ } from "./chunk-6N4R6FVX.js";
31
31
  import {
32
32
  logger
33
- } from "./chunk-HIOXKZYF.js";
33
+ } from "./chunk-EBJQ6N4M.js";
34
34
  import {
35
35
  generate,
36
36
  validate
@@ -47,24 +47,27 @@ import {
47
47
  import {
48
48
  createGenerateSlashCommandsCommand,
49
49
  generateSlashCommands,
50
+ handleGetImpact,
50
51
  handleOrphanDeletion
51
- } from "./chunk-LXU5M77O.js";
52
+ } from "./chunk-I6JZYEGT.js";
52
53
  import {
53
54
  VALID_PLATFORMS
54
55
  } from "./chunk-ZOAWBDWU.js";
55
56
  import {
57
+ resolveGlobalSkillsDir,
56
58
  resolvePersonasDir,
59
+ resolveProjectSkillsDir,
57
60
  resolveSkillsDir,
58
61
  resolveTemplatesDir
59
- } from "./chunk-EOLRW32Q.js";
62
+ } from "./chunk-HD4IBGLA.js";
60
63
  import {
61
64
  CLIError,
62
65
  ExitCode,
63
66
  handleError
64
- } from "./chunk-B7HFEHWP.js";
67
+ } from "./chunk-3WGJMBKH.js";
65
68
  import {
66
69
  SkillMetadataSchema
67
- } from "./chunk-MDUK2J2O.js";
70
+ } from "./chunk-VRFZWGMS.js";
68
71
  import {
69
72
  CLI_VERSION
70
73
  } from "./chunk-BM3PWGXQ.js";
@@ -72,9 +75,13 @@ import {
72
75
  TemplateEngine
73
76
  } from "./chunk-C2ERUR3L.js";
74
77
  import {
78
+ ArchBaselineManager,
79
+ ArchConfigSchema,
75
80
  BaselineManager,
81
+ BlueprintGenerator,
76
82
  CriticalPathResolver,
77
83
  EntropyAnalyzer,
84
+ ProjectScanner,
78
85
  SecurityScanner,
79
86
  TypeScriptParser,
80
87
  appendLearning,
@@ -88,28 +95,33 @@ import {
88
95
  detectCircularDepsInFiles,
89
96
  detectDeadCode,
90
97
  detectDocDrift,
98
+ diff,
99
+ extractBundle,
91
100
  generateSuggestions,
92
101
  listStreams,
93
102
  loadState,
94
103
  loadStreamIndex,
95
104
  parseDiff,
105
+ parseManifest,
96
106
  parseSecurityConfig,
97
107
  requestPeerReview,
98
108
  resolveStreamPath,
109
+ runAll,
99
110
  runCIChecks,
100
111
  runReviewPipeline,
101
112
  setActiveStream,
102
113
  validateAgentsMap,
103
114
  validateDependencies,
104
- validateKnowledgeMap
105
- } from "./chunk-NX6DSZSM.js";
115
+ validateKnowledgeMap,
116
+ writeConfig
117
+ } from "./chunk-2YSQOUHO.js";
106
118
  import {
107
119
  Err,
108
120
  Ok
109
121
  } from "./chunk-MHBMTPW7.js";
110
122
 
111
123
  // src/index.ts
112
- import { Command as Command41 } from "commander";
124
+ import { Command as Command51 } from "commander";
113
125
 
114
126
  // src/commands/validate.ts
115
127
  import { Command } from "commander";
@@ -188,7 +200,7 @@ function createValidateCommand() {
188
200
  process.exit(result.error.exitCode);
189
201
  }
190
202
  if (opts.crossCheck) {
191
- const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-WZAX357V.js");
203
+ const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-DB7RIFFF.js");
192
204
  const cwd = process.cwd();
193
205
  const specsDir = path.join(cwd, "docs", "specs");
194
206
  const plansDir = path.join(cwd, "docs", "plans");
@@ -314,13 +326,13 @@ function createCheckDepsCommand() {
314
326
  import { Command as Command3 } from "commander";
315
327
  import * as path3 from "path";
316
328
  async function runCheckPerf(cwd, options) {
317
- const runAll = !options.structural && !options.size && !options.coupling;
329
+ const runAll2 = !options.structural && !options.size && !options.coupling;
318
330
  const analyzer = new EntropyAnalyzer({
319
331
  rootDir: path3.resolve(cwd),
320
332
  analyze: {
321
- complexity: runAll || !!options.structural,
322
- coupling: runAll || !!options.coupling,
323
- sizeBudget: runAll || !!options.size
333
+ complexity: runAll2 || !!options.structural,
334
+ coupling: runAll2 || !!options.coupling,
335
+ sizeBudget: runAll2 || !!options.size
324
336
  }
325
337
  });
326
338
  const analysisResult = await analyzer.analyze();
@@ -456,10 +468,10 @@ async function runCheckSecurity(cwd, options) {
456
468
  const projectRoot = path4.resolve(cwd);
457
469
  let configData = {};
458
470
  try {
459
- const fs11 = await import("fs");
471
+ const fs20 = await import("fs");
460
472
  const configPath = path4.join(projectRoot, "harness.config.json");
461
- if (fs11.existsSync(configPath)) {
462
- const raw = fs11.readFileSync(configPath, "utf-8");
473
+ if (fs20.existsSync(configPath)) {
474
+ const raw = fs20.readFileSync(configPath, "utf-8");
463
475
  const parsed = JSON.parse(raw);
464
476
  configData = parsed.security ?? {};
465
477
  }
@@ -546,7 +558,7 @@ function createPerfCommand() {
546
558
  perf.command("bench [glob]").description("Run benchmarks via vitest bench").action(async (glob, _opts, cmd) => {
547
559
  const globalOpts = cmd.optsWithGlobals();
548
560
  const cwd = process.cwd();
549
- const { BenchmarkRunner } = await import("./dist-PBTNVK6K.js");
561
+ const { BenchmarkRunner } = await import("./dist-JVZ2MKBC.js");
550
562
  const runner = new BenchmarkRunner();
551
563
  const benchFiles = runner.discover(cwd, glob);
552
564
  if (benchFiles.length === 0) {
@@ -615,7 +627,7 @@ Results (${result.results.length} benchmarks):`);
615
627
  baselines.command("update").description("Update baselines from latest benchmark run").action(async (_opts, cmd) => {
616
628
  const globalOpts = cmd.optsWithGlobals();
617
629
  const cwd = process.cwd();
618
- const { BenchmarkRunner } = await import("./dist-PBTNVK6K.js");
630
+ const { BenchmarkRunner } = await import("./dist-JVZ2MKBC.js");
619
631
  const runner = new BenchmarkRunner();
620
632
  const manager = new BaselineManager(cwd);
621
633
  logger.info("Running benchmarks to update baselines...");
@@ -628,8 +640,8 @@ Results (${result.results.length} benchmarks):`);
628
640
  }
629
641
  let commitHash = "unknown";
630
642
  try {
631
- const { execSync: execSync3 } = await import("child_process");
632
- commitHash = execSync3("git rev-parse --short HEAD", { cwd, encoding: "utf-8" }).trim();
643
+ const { execSync: execSync5 } = await import("child_process");
644
+ commitHash = execSync5("git rev-parse --short HEAD", { cwd, encoding: "utf-8" }).trim();
633
645
  } catch {
634
646
  }
635
647
  manager.save(benchResult.results, commitHash);
@@ -643,7 +655,7 @@ Results (${result.results.length} benchmarks):`);
643
655
  perf.command("report").description("Full performance report with metrics, trends, and hotspots").action(async (_opts, cmd) => {
644
656
  const globalOpts = cmd.optsWithGlobals();
645
657
  const cwd = process.cwd();
646
- const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-PBTNVK6K.js");
658
+ const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-JVZ2MKBC.js");
647
659
  const analyzer = new EntropyAnalyzer2({
648
660
  rootDir: path5.resolve(cwd),
649
661
  analyze: { complexity: true, coupling: true }
@@ -717,7 +729,14 @@ async function runCheckDocs(options) {
717
729
  const coverageResult = await checkDocCoverage("project", {
718
730
  docsDir,
719
731
  sourceDir,
720
- excludePatterns: ["**/*.test.ts", "**/*.spec.ts", "**/node_modules/**"]
732
+ excludePatterns: [
733
+ "**/*.test.ts",
734
+ "**/*.spec.ts",
735
+ "**/node_modules/**",
736
+ "**/dist/**",
737
+ "**/coverage/**",
738
+ "**/.turbo/**"
739
+ ]
721
740
  });
722
741
  if (!coverageResult.ok) {
723
742
  return Err(
@@ -1396,22 +1415,22 @@ async function runAgentReview(options) {
1396
1415
  return configResult;
1397
1416
  }
1398
1417
  const config = configResult.value;
1399
- let diff;
1418
+ let diff2;
1400
1419
  try {
1401
- diff = execSync2("git diff --cached", { encoding: "utf-8" });
1402
- if (!diff) {
1403
- diff = execSync2("git diff", { encoding: "utf-8" });
1420
+ diff2 = execSync2("git diff --cached", { encoding: "utf-8" });
1421
+ if (!diff2) {
1422
+ diff2 = execSync2("git diff", { encoding: "utf-8" });
1404
1423
  }
1405
1424
  } catch {
1406
1425
  return Err(new CLIError("Failed to get git diff", ExitCode.ERROR));
1407
1426
  }
1408
- if (!diff) {
1427
+ if (!diff2) {
1409
1428
  return Ok({
1410
1429
  passed: true,
1411
1430
  checklist: [{ check: "No changes to review", passed: true }]
1412
1431
  });
1413
1432
  }
1414
- const parsedDiffResult = parseDiff(diff);
1433
+ const parsedDiffResult = parseDiff(diff2);
1415
1434
  if (!parsedDiffResult.ok) {
1416
1435
  return Err(new CLIError(parsedDiffResult.error.message, ExitCode.ERROR));
1417
1436
  }
@@ -1425,7 +1444,7 @@ async function runAgentReview(options) {
1425
1444
  changedFiles: codeChanges.files.map((f) => f.path),
1426
1445
  newFiles: codeChanges.files.filter((f) => f.status === "added").map((f) => f.path),
1427
1446
  deletedFiles: codeChanges.files.filter((f) => f.status === "deleted").map((f) => f.path),
1428
- totalDiffLines: diff.split("\n").length,
1447
+ totalDiffLines: diff2.split("\n").length,
1429
1448
  fileDiffs: new Map(codeChanges.files.map((f) => [f.path, ""]))
1430
1449
  };
1431
1450
  const pipelineResult = await runReviewPipeline({
@@ -1592,7 +1611,7 @@ async function runAdd(componentType, name, options) {
1592
1611
  break;
1593
1612
  }
1594
1613
  case "skill": {
1595
- const { generateSkillFiles: generateSkillFiles2 } = await import("./create-skill-LUWO46WF.js");
1614
+ const { generateSkillFiles: generateSkillFiles2 } = await import("./create-skill-WPXHSLX2.js");
1596
1615
  generateSkillFiles2({
1597
1616
  name,
1598
1617
  description: `${name} skill`,
@@ -1857,37 +1876,161 @@ function createPersonaCommand() {
1857
1876
  }
1858
1877
 
1859
1878
  // src/commands/skill/index.ts
1860
- import { Command as Command25 } from "commander";
1879
+ import { Command as Command28 } from "commander";
1861
1880
 
1862
1881
  // src/commands/skill/list.ts
1863
1882
  import { Command as Command21 } from "commander";
1883
+ import * as fs6 from "fs";
1884
+ import * as path15 from "path";
1885
+ import { parse } from "yaml";
1886
+
1887
+ // src/registry/lockfile.ts
1864
1888
  import * as fs5 from "fs";
1865
1889
  import * as path14 from "path";
1866
- import { parse } from "yaml";
1867
- function createListCommand2() {
1868
- return new Command21("list").description("List available skills").action(async (_opts, cmd) => {
1869
- const globalOpts = cmd.optsWithGlobals();
1870
- const skillsDir = resolveSkillsDir();
1871
- if (!fs5.existsSync(skillsDir)) {
1872
- logger.info("No skills directory found.");
1873
- process.exit(ExitCode.SUCCESS);
1874
- return;
1890
+ function createEmptyLockfile() {
1891
+ return { version: 1, skills: {} };
1892
+ }
1893
+ function sortedStringify(obj) {
1894
+ return JSON.stringify(
1895
+ obj,
1896
+ (_key, value) => {
1897
+ if (value && typeof value === "object" && !Array.isArray(value)) {
1898
+ return Object.keys(value).sort().reduce((sorted, k) => {
1899
+ sorted[k] = value[k];
1900
+ return sorted;
1901
+ }, {});
1902
+ }
1903
+ return value;
1904
+ },
1905
+ 2
1906
+ );
1907
+ }
1908
+ function readLockfile(filePath) {
1909
+ if (!fs5.existsSync(filePath)) {
1910
+ return createEmptyLockfile();
1911
+ }
1912
+ const raw = fs5.readFileSync(filePath, "utf-8");
1913
+ let parsed;
1914
+ try {
1915
+ parsed = JSON.parse(raw);
1916
+ } catch {
1917
+ throw new Error(
1918
+ `Failed to parse lockfile at ${filePath}. The file may be corrupted. Delete it and re-run harness install to regenerate.`
1919
+ );
1920
+ }
1921
+ if (!parsed || typeof parsed !== "object" || !("version" in parsed) || parsed.version !== 1 || !("skills" in parsed) || typeof parsed.skills !== "object") {
1922
+ throw new Error(
1923
+ `Invalid lockfile format at ${filePath}. Expected version 1 with a skills object. Delete it and re-run harness install to regenerate.`
1924
+ );
1925
+ }
1926
+ return parsed;
1927
+ }
1928
+ function writeLockfile(filePath, lockfile) {
1929
+ const dir = path14.dirname(filePath);
1930
+ fs5.mkdirSync(dir, { recursive: true });
1931
+ fs5.writeFileSync(filePath, sortedStringify(lockfile) + "\n", "utf-8");
1932
+ }
1933
+ function updateLockfileEntry(lockfile, name, entry) {
1934
+ return {
1935
+ ...lockfile,
1936
+ skills: {
1937
+ ...lockfile.skills,
1938
+ [name]: entry
1875
1939
  }
1876
- const entries = fs5.readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
1877
- const skills = [];
1878
- for (const name of entries) {
1879
- const yamlPath = path14.join(skillsDir, name, "skill.yaml");
1880
- if (!fs5.existsSync(yamlPath)) continue;
1881
- try {
1882
- const raw = fs5.readFileSync(yamlPath, "utf-8");
1883
- const parsed = parse(raw);
1884
- const result = SkillMetadataSchema.safeParse(parsed);
1885
- if (result.success) {
1886
- skills.push(result.data);
1887
- }
1888
- } catch {
1940
+ };
1941
+ }
1942
+ function removeLockfileEntry(lockfile, name) {
1943
+ if (!(name in lockfile.skills)) {
1944
+ return lockfile;
1945
+ }
1946
+ const { [name]: _removed, ...rest } = lockfile.skills;
1947
+ return {
1948
+ ...lockfile,
1949
+ skills: rest
1950
+ };
1951
+ }
1952
+
1953
+ // src/commands/skill/list.ts
1954
+ function scanDirectory(dirPath, source) {
1955
+ if (!fs6.existsSync(dirPath)) return [];
1956
+ const entries = fs6.readdirSync(dirPath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
1957
+ const skills = [];
1958
+ for (const name of entries) {
1959
+ const yamlPath = path15.join(dirPath, name, "skill.yaml");
1960
+ if (!fs6.existsSync(yamlPath)) continue;
1961
+ try {
1962
+ const raw = fs6.readFileSync(yamlPath, "utf-8");
1963
+ const parsed = parse(raw);
1964
+ const result = SkillMetadataSchema.safeParse(parsed);
1965
+ if (result.success) {
1966
+ skills.push({
1967
+ name: result.data.name,
1968
+ description: result.data.description,
1969
+ type: result.data.type,
1970
+ source
1971
+ });
1972
+ }
1973
+ } catch {
1974
+ }
1975
+ }
1976
+ return skills;
1977
+ }
1978
+ function collectSkills(opts) {
1979
+ const seen = /* @__PURE__ */ new Set();
1980
+ const allSkills = [];
1981
+ const addUnique = (entries) => {
1982
+ for (const entry of entries) {
1983
+ if (!seen.has(entry.name)) {
1984
+ seen.add(entry.name);
1985
+ allSkills.push(entry);
1986
+ }
1987
+ }
1988
+ };
1989
+ if (opts.filter === "all" || opts.filter === "local") {
1990
+ const projectDir = resolveProjectSkillsDir();
1991
+ if (projectDir) {
1992
+ addUnique(scanDirectory(projectDir, "local"));
1993
+ }
1994
+ }
1995
+ if (opts.filter === "all" || opts.filter === "installed") {
1996
+ const globalDir = resolveGlobalSkillsDir();
1997
+ const skillsDir = path15.dirname(globalDir);
1998
+ const communityBase = path15.join(skillsDir, "community");
1999
+ const lockfilePath = path15.join(communityBase, "skills-lock.json");
2000
+ const lockfile = readLockfile(lockfilePath);
2001
+ for (const [pkgName, entry] of Object.entries(lockfile.skills)) {
2002
+ const shortName = pkgName.replace("@harness-skills/", "");
2003
+ if (!seen.has(shortName)) {
2004
+ seen.add(shortName);
2005
+ allSkills.push({
2006
+ name: shortName,
2007
+ description: "",
2008
+ type: "",
2009
+ source: "community",
2010
+ version: entry.version
2011
+ });
1889
2012
  }
1890
2013
  }
2014
+ }
2015
+ if (opts.filter === "all") {
2016
+ const globalDir = resolveGlobalSkillsDir();
2017
+ addUnique(scanDirectory(globalDir, "bundled"));
2018
+ }
2019
+ if (opts.filter === "installed") {
2020
+ return allSkills.filter((s) => s.source === "community");
2021
+ }
2022
+ if (opts.filter === "local") {
2023
+ return allSkills.filter((s) => s.source === "local");
2024
+ }
2025
+ return allSkills;
2026
+ }
2027
+ function createListCommand2() {
2028
+ return new Command21("list").description("List available skills").option("--installed", "Show only community-installed skills").option("--local", "Show only project-local skills").option("--all", "Show all skills (default)").action(async (opts, cmd) => {
2029
+ const globalOpts = cmd.optsWithGlobals();
2030
+ let filter = "all";
2031
+ if (opts.installed) filter = "installed";
2032
+ else if (opts.local) filter = "local";
2033
+ const skills = collectSkills({ filter });
1891
2034
  if (globalOpts.json) {
1892
2035
  logger.raw(skills);
1893
2036
  } else if (globalOpts.quiet) {
@@ -1898,9 +2041,12 @@ function createListCommand2() {
1898
2041
  } else {
1899
2042
  console.log("Available skills:\n");
1900
2043
  for (const s of skills) {
1901
- console.log(` ${s.name} (${s.type})`);
1902
- console.log(` ${s.description}
1903
- `);
2044
+ const version = s.version ? `@${s.version}` : "";
2045
+ console.log(` ${s.name}${version} [${s.source}] (${s.type || "unknown"})`);
2046
+ if (s.description) {
2047
+ console.log(` ${s.description}`);
2048
+ }
2049
+ console.log();
1904
2050
  }
1905
2051
  }
1906
2052
  }
@@ -1910,8 +2056,8 @@ function createListCommand2() {
1910
2056
 
1911
2057
  // src/commands/skill/run.ts
1912
2058
  import { Command as Command22 } from "commander";
1913
- import * as fs6 from "fs";
1914
- import * as path15 from "path";
2059
+ import * as fs7 from "fs";
2060
+ import * as path16 from "path";
1915
2061
  import { parse as parse2 } from "yaml";
1916
2062
 
1917
2063
  // src/skill/complexity.ts
@@ -2001,17 +2147,17 @@ ${options.priorState}`);
2001
2147
  function createRunCommand2() {
2002
2148
  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) => {
2003
2149
  const skillsDir = resolveSkillsDir();
2004
- const skillDir = path15.join(skillsDir, name);
2005
- if (!fs6.existsSync(skillDir)) {
2150
+ const skillDir = path16.join(skillsDir, name);
2151
+ if (!fs7.existsSync(skillDir)) {
2006
2152
  logger.error(`Skill not found: ${name}`);
2007
2153
  process.exit(ExitCode.ERROR);
2008
2154
  return;
2009
2155
  }
2010
- const yamlPath = path15.join(skillDir, "skill.yaml");
2156
+ const yamlPath = path16.join(skillDir, "skill.yaml");
2011
2157
  let metadata = null;
2012
- if (fs6.existsSync(yamlPath)) {
2158
+ if (fs7.existsSync(yamlPath)) {
2013
2159
  try {
2014
- const raw = fs6.readFileSync(yamlPath, "utf-8");
2160
+ const raw = fs7.readFileSync(yamlPath, "utf-8");
2015
2161
  const parsed = parse2(raw);
2016
2162
  const result = SkillMetadataSchema.safeParse(parsed);
2017
2163
  if (result.success) metadata = result.data;
@@ -2022,17 +2168,17 @@ function createRunCommand2() {
2022
2168
  if (metadata?.phases && metadata.phases.length > 0) {
2023
2169
  const requested = opts.complexity ?? "auto";
2024
2170
  if (requested === "auto") {
2025
- const projectPath2 = opts.path ? path15.resolve(opts.path) : process.cwd();
2171
+ const projectPath2 = opts.path ? path16.resolve(opts.path) : process.cwd();
2026
2172
  complexity = detectComplexity(projectPath2);
2027
2173
  } else {
2028
2174
  complexity = requested;
2029
2175
  }
2030
2176
  }
2031
2177
  let principles;
2032
- const projectPath = opts.path ? path15.resolve(opts.path) : process.cwd();
2033
- const principlesPath = path15.join(projectPath, "docs", "principles.md");
2034
- if (fs6.existsSync(principlesPath)) {
2035
- principles = fs6.readFileSync(principlesPath, "utf-8");
2178
+ const projectPath = opts.path ? path16.resolve(opts.path) : process.cwd();
2179
+ const principlesPath = path16.join(projectPath, "docs", "principles.md");
2180
+ if (fs7.existsSync(principlesPath)) {
2181
+ principles = fs7.readFileSync(principlesPath, "utf-8");
2036
2182
  }
2037
2183
  let priorState;
2038
2184
  let stateWarning;
@@ -2047,16 +2193,16 @@ function createRunCommand2() {
2047
2193
  }
2048
2194
  if (metadata?.state.persistent && metadata.state.files.length > 0) {
2049
2195
  for (const stateFilePath of metadata.state.files) {
2050
- const fullPath = path15.join(projectPath, stateFilePath);
2051
- if (fs6.existsSync(fullPath)) {
2052
- const stat = fs6.statSync(fullPath);
2196
+ const fullPath = path16.join(projectPath, stateFilePath);
2197
+ if (fs7.existsSync(fullPath)) {
2198
+ const stat = fs7.statSync(fullPath);
2053
2199
  if (stat.isDirectory()) {
2054
- const files = fs6.readdirSync(fullPath).map((f) => ({ name: f, mtime: fs6.statSync(path15.join(fullPath, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
2200
+ const files = fs7.readdirSync(fullPath).map((f) => ({ name: f, mtime: fs7.statSync(path16.join(fullPath, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
2055
2201
  if (files.length > 0) {
2056
- priorState = fs6.readFileSync(path15.join(fullPath, files[0].name), "utf-8");
2202
+ priorState = fs7.readFileSync(path16.join(fullPath, files[0].name), "utf-8");
2057
2203
  }
2058
2204
  } else {
2059
- priorState = fs6.readFileSync(fullPath, "utf-8");
2205
+ priorState = fs7.readFileSync(fullPath, "utf-8");
2060
2206
  }
2061
2207
  break;
2062
2208
  }
@@ -2075,17 +2221,17 @@ function createRunCommand2() {
2075
2221
  ...stateWarning !== void 0 && { stateWarning },
2076
2222
  party: opts.party
2077
2223
  });
2078
- const skillMdPath = path15.join(skillDir, "SKILL.md");
2079
- if (!fs6.existsSync(skillMdPath)) {
2224
+ const skillMdPath = path16.join(skillDir, "SKILL.md");
2225
+ if (!fs7.existsSync(skillMdPath)) {
2080
2226
  logger.error(`SKILL.md not found for skill: ${name}`);
2081
2227
  process.exit(ExitCode.ERROR);
2082
2228
  return;
2083
2229
  }
2084
- let content = fs6.readFileSync(skillMdPath, "utf-8");
2230
+ let content = fs7.readFileSync(skillMdPath, "utf-8");
2085
2231
  if (metadata?.state.persistent && opts.path) {
2086
- const stateFile = path15.join(projectPath, ".harness", "state.json");
2087
- if (fs6.existsSync(stateFile)) {
2088
- const stateContent = fs6.readFileSync(stateFile, "utf-8");
2232
+ const stateFile = path16.join(projectPath, ".harness", "state.json");
2233
+ if (fs7.existsSync(stateFile)) {
2234
+ const stateContent = fs7.readFileSync(stateFile, "utf-8");
2089
2235
  content += `
2090
2236
 
2091
2237
  ---
@@ -2103,8 +2249,8 @@ ${stateContent}
2103
2249
 
2104
2250
  // src/commands/skill/validate.ts
2105
2251
  import { Command as Command23 } from "commander";
2106
- import * as fs7 from "fs";
2107
- import * as path16 from "path";
2252
+ import * as fs8 from "fs";
2253
+ import * as path17 from "path";
2108
2254
  import { parse as parse3 } from "yaml";
2109
2255
  var REQUIRED_SECTIONS = [
2110
2256
  "## When to Use",
@@ -2117,32 +2263,32 @@ function createValidateCommand3() {
2117
2263
  return new Command23("validate").description("Validate all skill.yaml files and SKILL.md structure").action(async (_opts, cmd) => {
2118
2264
  const globalOpts = cmd.optsWithGlobals();
2119
2265
  const skillsDir = resolveSkillsDir();
2120
- if (!fs7.existsSync(skillsDir)) {
2266
+ if (!fs8.existsSync(skillsDir)) {
2121
2267
  logger.info("No skills directory found.");
2122
2268
  process.exit(ExitCode.SUCCESS);
2123
2269
  return;
2124
2270
  }
2125
- const entries = fs7.readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
2271
+ const entries = fs8.readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
2126
2272
  const errors = [];
2127
2273
  let validated = 0;
2128
2274
  for (const name of entries) {
2129
- const skillDir = path16.join(skillsDir, name);
2130
- const yamlPath = path16.join(skillDir, "skill.yaml");
2131
- const skillMdPath = path16.join(skillDir, "SKILL.md");
2132
- if (!fs7.existsSync(yamlPath)) {
2275
+ const skillDir = path17.join(skillsDir, name);
2276
+ const yamlPath = path17.join(skillDir, "skill.yaml");
2277
+ const skillMdPath = path17.join(skillDir, "SKILL.md");
2278
+ if (!fs8.existsSync(yamlPath)) {
2133
2279
  errors.push(`${name}: missing skill.yaml`);
2134
2280
  continue;
2135
2281
  }
2136
2282
  try {
2137
- const raw = fs7.readFileSync(yamlPath, "utf-8");
2283
+ const raw = fs8.readFileSync(yamlPath, "utf-8");
2138
2284
  const parsed = parse3(raw);
2139
2285
  const result = SkillMetadataSchema.safeParse(parsed);
2140
2286
  if (!result.success) {
2141
2287
  errors.push(`${name}/skill.yaml: ${result.error.message}`);
2142
2288
  continue;
2143
2289
  }
2144
- if (fs7.existsSync(skillMdPath)) {
2145
- const mdContent = fs7.readFileSync(skillMdPath, "utf-8");
2290
+ if (fs8.existsSync(skillMdPath)) {
2291
+ const mdContent = fs8.readFileSync(skillMdPath, "utf-8");
2146
2292
  for (const section of REQUIRED_SECTIONS) {
2147
2293
  if (!mdContent.includes(section)) {
2148
2294
  errors.push(`${name}/SKILL.md: missing section "${section}"`);
@@ -2184,27 +2330,27 @@ function createValidateCommand3() {
2184
2330
 
2185
2331
  // src/commands/skill/info.ts
2186
2332
  import { Command as Command24 } from "commander";
2187
- import * as fs8 from "fs";
2188
- import * as path17 from "path";
2333
+ import * as fs9 from "fs";
2334
+ import * as path18 from "path";
2189
2335
  import { parse as parse4 } from "yaml";
2190
2336
  function createInfoCommand() {
2191
2337
  return new Command24("info").description("Show metadata for a skill").argument("<name>", "Skill name (e.g., harness-tdd)").action(async (name, _opts, cmd) => {
2192
2338
  const globalOpts = cmd.optsWithGlobals();
2193
2339
  const skillsDir = resolveSkillsDir();
2194
- const skillDir = path17.join(skillsDir, name);
2195
- if (!fs8.existsSync(skillDir)) {
2340
+ const skillDir = path18.join(skillsDir, name);
2341
+ if (!fs9.existsSync(skillDir)) {
2196
2342
  logger.error(`Skill not found: ${name}`);
2197
2343
  process.exit(ExitCode.ERROR);
2198
2344
  return;
2199
2345
  }
2200
- const yamlPath = path17.join(skillDir, "skill.yaml");
2201
- if (!fs8.existsSync(yamlPath)) {
2346
+ const yamlPath = path18.join(skillDir, "skill.yaml");
2347
+ if (!fs9.existsSync(yamlPath)) {
2202
2348
  logger.error(`skill.yaml not found for skill: ${name}`);
2203
2349
  process.exit(ExitCode.ERROR);
2204
2350
  return;
2205
2351
  }
2206
2352
  try {
2207
- const raw = fs8.readFileSync(yamlPath, "utf-8");
2353
+ const raw = fs9.readFileSync(yamlPath, "utf-8");
2208
2354
  const parsed = parse4(raw);
2209
2355
  const result = SkillMetadataSchema.safeParse(parsed);
2210
2356
  if (!result.success) {
@@ -2243,138 +2389,623 @@ function createInfoCommand() {
2243
2389
  });
2244
2390
  }
2245
2391
 
2246
- // src/commands/skill/index.ts
2247
- function createSkillCommand() {
2248
- const command = new Command25("skill").description("Skill management commands");
2249
- command.addCommand(createListCommand2());
2250
- command.addCommand(createRunCommand2());
2251
- command.addCommand(createValidateCommand3());
2252
- command.addCommand(createInfoCommand());
2253
- return command;
2254
- }
2255
-
2256
- // src/commands/state/index.ts
2257
- import { Command as Command30 } from "commander";
2392
+ // src/commands/skill/search.ts
2393
+ import { Command as Command25 } from "commander";
2258
2394
 
2259
- // src/commands/state/show.ts
2260
- import { Command as Command26 } from "commander";
2261
- import * as path18 from "path";
2262
- function createShowCommand() {
2263
- return new Command26("show").description("Show current project state").option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").action(async (opts, cmd) => {
2264
- const globalOpts = cmd.optsWithGlobals();
2265
- const projectPath = path18.resolve(opts.path);
2266
- const result = await loadState(projectPath, opts.stream);
2267
- if (!result.ok) {
2268
- logger.error(result.error.message);
2269
- process.exit(ExitCode.ERROR);
2270
- return;
2395
+ // src/registry/npm-client.ts
2396
+ var NPM_REGISTRY = "https://registry.npmjs.org";
2397
+ var FETCH_TIMEOUT_MS = 3e4;
2398
+ var HARNESS_SKILLS_SCOPE = "@harness-skills/";
2399
+ function resolvePackageName(name) {
2400
+ if (name.startsWith(HARNESS_SKILLS_SCOPE)) {
2401
+ return name;
2402
+ }
2403
+ if (name.startsWith("@")) {
2404
+ throw new Error(`Only @harness-skills/ scoped packages are supported. Got: ${name}`);
2405
+ }
2406
+ return `${HARNESS_SKILLS_SCOPE}${name}`;
2407
+ }
2408
+ function extractSkillName(packageName) {
2409
+ if (packageName.startsWith(HARNESS_SKILLS_SCOPE)) {
2410
+ return packageName.slice(HARNESS_SKILLS_SCOPE.length);
2411
+ }
2412
+ return packageName;
2413
+ }
2414
+ async function fetchPackageMetadata(packageName) {
2415
+ const encodedName = encodeURIComponent(packageName);
2416
+ const url = `${NPM_REGISTRY}/${encodedName}`;
2417
+ let response;
2418
+ try {
2419
+ response = await fetch(url, {
2420
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
2421
+ });
2422
+ } catch {
2423
+ throw new Error("Cannot reach npm registry. Check your network connection.");
2424
+ }
2425
+ if (!response.ok) {
2426
+ if (response.status === 404) {
2427
+ throw new Error(`Package ${packageName} not found on npm registry.`);
2271
2428
  }
2272
- const state = result.value;
2273
- if (globalOpts.json) {
2274
- logger.raw(state);
2275
- } else if (globalOpts.quiet) {
2276
- console.log(JSON.stringify(state));
2277
- } else {
2278
- if (opts.stream) console.log(`Stream: ${opts.stream}`);
2279
- console.log(`Schema Version: ${state.schemaVersion}`);
2280
- if (state.position.phase) console.log(`Phase: ${state.position.phase}`);
2281
- if (state.position.task) console.log(`Task: ${state.position.task}`);
2282
- if (state.lastSession) {
2283
- console.log(`Last Session: ${state.lastSession.date} \u2014 ${state.lastSession.summary}`);
2284
- }
2285
- if (Object.keys(state.progress).length > 0) {
2286
- console.log("\nProgress:");
2287
- for (const [task, status] of Object.entries(state.progress)) {
2288
- console.log(` ${task}: ${status}`);
2289
- }
2290
- }
2291
- if (state.decisions.length > 0) {
2292
- console.log(`
2293
- Decisions: ${state.decisions.length}`);
2294
- }
2295
- if (state.blockers.length > 0) {
2296
- const open = state.blockers.filter((b) => b.status === "open").length;
2297
- console.log(`Blockers: ${open} open / ${state.blockers.length} total`);
2429
+ throw new Error(
2430
+ `npm registry returned ${response.status} ${response.statusText} for ${packageName}.`
2431
+ );
2432
+ }
2433
+ return await response.json();
2434
+ }
2435
+ async function downloadTarball(tarballUrl) {
2436
+ let lastError;
2437
+ for (let attempt = 0; attempt < 2; attempt++) {
2438
+ try {
2439
+ const response = await fetch(tarballUrl, {
2440
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
2441
+ });
2442
+ if (!response.ok) {
2443
+ throw new Error(`HTTP ${response.status} ${response.statusText}`);
2298
2444
  }
2445
+ const arrayBuffer = await response.arrayBuffer();
2446
+ return Buffer.from(arrayBuffer);
2447
+ } catch (err) {
2448
+ lastError = err instanceof Error ? err : new Error(String(err));
2299
2449
  }
2300
- process.exit(ExitCode.SUCCESS);
2301
- });
2450
+ }
2451
+ throw new Error(`Download failed for ${tarballUrl}. Try again. (${lastError?.message})`);
2452
+ }
2453
+ async function searchNpmRegistry(query) {
2454
+ const searchText = encodeURIComponent(`scope:harness-skills ${query}`);
2455
+ const url = `${NPM_REGISTRY}/-/v1/search?text=${searchText}&size=20`;
2456
+ let response;
2457
+ try {
2458
+ response = await fetch(url, {
2459
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
2460
+ });
2461
+ } catch {
2462
+ throw new Error("Cannot reach npm registry. Check your network connection.");
2463
+ }
2464
+ if (!response.ok) {
2465
+ throw new Error(`npm registry search returned ${response.status} ${response.statusText}.`);
2466
+ }
2467
+ const data = await response.json();
2468
+ return data.objects.map((obj) => ({
2469
+ name: obj.package.name,
2470
+ version: obj.package.version,
2471
+ description: obj.package.description,
2472
+ keywords: obj.package.keywords || [],
2473
+ date: obj.package.date
2474
+ }));
2302
2475
  }
2303
2476
 
2304
- // src/commands/state/reset.ts
2305
- import { Command as Command27 } from "commander";
2306
- import * as fs9 from "fs";
2307
- import * as path19 from "path";
2308
- import * as readline from "readline";
2309
- function createResetCommand() {
2310
- return new Command27("reset").description("Reset project state (deletes .harness/state.json)").option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").option("--yes", "Skip confirmation prompt").action(async (opts, _cmd) => {
2311
- const projectPath = path19.resolve(opts.path);
2312
- let statePath;
2313
- if (opts.stream) {
2314
- const streamResult = await resolveStreamPath(projectPath, { stream: opts.stream });
2315
- if (!streamResult.ok) {
2316
- logger.error(streamResult.error.message);
2317
- process.exit(ExitCode.ERROR);
2318
- return;
2319
- }
2320
- statePath = path19.join(streamResult.value, "state.json");
2321
- } else {
2322
- statePath = path19.join(projectPath, ".harness", "state.json");
2477
+ // src/commands/skill/search.ts
2478
+ async function runSearch(query, opts) {
2479
+ const results = await searchNpmRegistry(query);
2480
+ return results.filter((r) => {
2481
+ if (opts.platform && !r.keywords.includes(opts.platform)) {
2482
+ return false;
2323
2483
  }
2324
- if (!fs9.existsSync(statePath)) {
2325
- logger.info("No state file found. Nothing to reset.");
2326
- process.exit(ExitCode.SUCCESS);
2327
- return;
2484
+ if (opts.trigger && !r.keywords.includes(opts.trigger)) {
2485
+ return false;
2328
2486
  }
2329
- if (!opts.yes) {
2330
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
2331
- const answer = await new Promise((resolve20) => {
2332
- rl.question("Reset project state? This cannot be undone. [y/N] ", resolve20);
2333
- });
2334
- rl.close();
2335
- if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
2336
- logger.info("Reset cancelled.");
2487
+ return true;
2488
+ });
2489
+ }
2490
+ function createSearchCommand() {
2491
+ return new Command25("search").description("Search for community skills on the @harness-skills registry").argument("<query>", "Search query").option("--platform <platform>", "Filter by platform (e.g., claude-code)").option("--trigger <trigger>", "Filter by trigger type (e.g., manual, automatic)").action(async (query, opts, cmd) => {
2492
+ const globalOpts = cmd.optsWithGlobals();
2493
+ try {
2494
+ const results = await runSearch(query, opts);
2495
+ if (globalOpts.json) {
2496
+ logger.raw(results);
2337
2497
  process.exit(ExitCode.SUCCESS);
2338
2498
  return;
2339
2499
  }
2500
+ if (results.length === 0) {
2501
+ logger.info(`No skills found matching "${query}".`);
2502
+ process.exit(ExitCode.SUCCESS);
2503
+ return;
2504
+ }
2505
+ console.log(`
2506
+ Found ${results.length} skill(s):
2507
+ `);
2508
+ for (const r of results) {
2509
+ const shortName = extractSkillName(r.name);
2510
+ console.log(` ${shortName}@${r.version}`);
2511
+ console.log(` ${r.description}`);
2512
+ if (r.keywords.length > 0) {
2513
+ console.log(` keywords: ${r.keywords.join(", ")}`);
2514
+ }
2515
+ console.log();
2516
+ }
2517
+ logger.info(`Install with: harness install <skill-name>`);
2518
+ process.exit(ExitCode.SUCCESS);
2519
+ } catch (err) {
2520
+ logger.error(err instanceof Error ? err.message : String(err));
2521
+ process.exit(ExitCode.VALIDATION_FAILED);
2340
2522
  }
2341
- try {
2342
- fs9.unlinkSync(statePath);
2343
- logger.success("Project state reset.");
2344
- } catch (e) {
2345
- logger.error(`Failed to reset state: ${e instanceof Error ? e.message : String(e)}`);
2346
- process.exit(ExitCode.ERROR);
2347
- return;
2348
- }
2349
- process.exit(ExitCode.SUCCESS);
2350
2523
  });
2351
2524
  }
2352
2525
 
2353
- // src/commands/state/learn.ts
2354
- import { Command as Command28 } from "commander";
2355
- import * as path20 from "path";
2356
- function createLearnCommand() {
2357
- return new Command28("learn").description("Append a learning to .harness/learnings.md").argument("<message>", "The learning to record").option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").action(async (message, opts, _cmd) => {
2358
- const projectPath = path20.resolve(opts.path);
2359
- const result = await appendLearning(projectPath, message, void 0, void 0, opts.stream);
2360
- if (!result.ok) {
2361
- logger.error(result.error.message);
2362
- process.exit(ExitCode.ERROR);
2363
- return;
2364
- }
2365
- logger.success(`Learning recorded.`);
2366
- process.exit(ExitCode.SUCCESS);
2367
- });
2526
+ // src/commands/skill/create.ts
2527
+ import { Command as Command26 } from "commander";
2528
+ import * as path19 from "path";
2529
+ import * as fs10 from "fs";
2530
+ import YAML from "yaml";
2531
+ var KEBAB_CASE_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
2532
+ function buildReadme(name, description) {
2533
+ return `# @harness-skills/${name}
2534
+
2535
+ ${description}
2536
+
2537
+ ## Installation
2538
+
2539
+ \`\`\`bash
2540
+ harness install ${name}
2541
+ \`\`\`
2542
+
2543
+ ## Usage
2544
+
2545
+ This skill is automatically available after installation. Invoke it via:
2546
+
2547
+ \`\`\`bash
2548
+ harness skill run ${name}
2549
+ \`\`\`
2550
+
2551
+ Or use the slash command \`/${name}\` in your AI coding assistant.
2552
+
2553
+ ## Development
2554
+
2555
+ Edit \`skill.yaml\` to configure the skill metadata and \`SKILL.md\` to define the skill's behavior.
2556
+
2557
+ ### Validate
2558
+
2559
+ \`\`\`bash
2560
+ harness skill validate ${name}
2561
+ \`\`\`
2562
+
2563
+ ### Publish
2564
+
2565
+ \`\`\`bash
2566
+ harness skills publish
2567
+ \`\`\`
2568
+ `;
2569
+ }
2570
+ function buildSkillYaml(name, opts) {
2571
+ const platforms = opts.platforms ? opts.platforms.split(",").map((p) => p.trim()) : ["claude-code"];
2572
+ const triggers = opts.triggers ? opts.triggers.split(",").map((t) => t.trim()) : ["manual"];
2573
+ return {
2574
+ name,
2575
+ version: "0.1.0",
2576
+ description: opts.description || `A community skill: ${name}`,
2577
+ triggers,
2578
+ platforms,
2579
+ tools: ["Read", "Grep", "Glob", "Edit", "Write", "Bash"],
2580
+ type: opts.type || "flexible",
2581
+ state: {
2582
+ persistent: false,
2583
+ files: []
2584
+ },
2585
+ depends_on: []
2586
+ };
2368
2587
  }
2588
+ function buildSkillMd(name, description) {
2589
+ return `# ${name}
2369
2590
 
2370
- // src/commands/state/streams.ts
2371
- import { Command as Command29 } from "commander";
2372
- import * as path21 from "path";
2373
- function createStreamsCommand() {
2374
- const command = new Command29("streams").description("Manage state streams");
2375
- command.command("list").description("List all known streams").option("--path <path>", "Project root path", ".").action(async (opts, cmd) => {
2591
+ ${description}
2592
+
2593
+ ## When to Use
2594
+
2595
+ - [Describe when this skill should be invoked]
2596
+ - [Describe the trigger conditions]
2597
+
2598
+ ## Process
2599
+
2600
+ 1. [Describe the step-by-step process]
2601
+ 2. [Add additional steps as needed]
2602
+
2603
+ ## Success Criteria
2604
+
2605
+ - [Define what a successful execution looks like]
2606
+ - [Add measurable criteria]
2607
+ `;
2608
+ }
2609
+ function runCreate(name, opts) {
2610
+ if (!KEBAB_CASE_RE.test(name)) {
2611
+ throw new Error(`Invalid skill name "${name}". Must be kebab-case (e.g., my-skill).`);
2612
+ }
2613
+ const baseDir = opts.outputDir ?? path19.join(process.cwd(), "agents", "skills", "claude-code");
2614
+ const skillDir = path19.join(baseDir, name);
2615
+ if (fs10.existsSync(skillDir)) {
2616
+ throw new Error(`Skill directory already exists: ${skillDir}`);
2617
+ }
2618
+ fs10.mkdirSync(skillDir, { recursive: true });
2619
+ const description = opts.description || `A community skill: ${name}`;
2620
+ const skillYaml = buildSkillYaml(name, opts);
2621
+ const skillYamlPath = path19.join(skillDir, "skill.yaml");
2622
+ fs10.writeFileSync(skillYamlPath, YAML.stringify(skillYaml));
2623
+ const skillMd = buildSkillMd(name, description);
2624
+ const skillMdPath = path19.join(skillDir, "SKILL.md");
2625
+ fs10.writeFileSync(skillMdPath, skillMd);
2626
+ const readme = buildReadme(name, description);
2627
+ const readmePath = path19.join(skillDir, "README.md");
2628
+ fs10.writeFileSync(readmePath, readme);
2629
+ return {
2630
+ name,
2631
+ directory: skillDir,
2632
+ files: [skillYamlPath, skillMdPath, readmePath]
2633
+ };
2634
+ }
2635
+ function createCreateCommand() {
2636
+ return new Command26("create").description("Scaffold a new community skill").argument("<name>", "Skill name (kebab-case)").option("--description <desc>", "Skill description").option("--type <type>", "Skill type: rigid or flexible", "flexible").option("--platforms <platforms>", "Comma-separated platforms (default: claude-code)").option("--triggers <triggers>", "Comma-separated triggers (default: manual)").option("--output-dir <dir>", "Output directory (default: agents/skills/claude-code/)").action(async (name, opts, cmd) => {
2376
2637
  const globalOpts = cmd.optsWithGlobals();
2377
- const projectPath = path21.resolve(opts.path);
2638
+ try {
2639
+ const result = runCreate(name, {
2640
+ description: opts.description,
2641
+ type: opts.type,
2642
+ platforms: opts.platforms,
2643
+ triggers: opts.triggers,
2644
+ outputDir: opts.outputDir
2645
+ });
2646
+ if (globalOpts.json) {
2647
+ logger.raw(result);
2648
+ } else {
2649
+ logger.success(`Created skill "${name}"`);
2650
+ for (const f of result.files) {
2651
+ logger.info(` ${f}`);
2652
+ }
2653
+ logger.info(`
2654
+ Next steps:`);
2655
+ logger.info(
2656
+ ` 1. Edit ${path19.join(result.directory, "SKILL.md")} with your skill content`
2657
+ );
2658
+ logger.info(` 2. Run: harness skill validate ${name}`);
2659
+ logger.info(` 3. Run: harness skills publish`);
2660
+ }
2661
+ } catch (err) {
2662
+ logger.error(err instanceof Error ? err.message : String(err));
2663
+ process.exit(ExitCode.VALIDATION_FAILED);
2664
+ }
2665
+ });
2666
+ }
2667
+
2668
+ // src/commands/skill/publish.ts
2669
+ import { Command as Command27 } from "commander";
2670
+ import * as fs13 from "fs";
2671
+ import * as path21 from "path";
2672
+ import { execFileSync as execFileSync3 } from "child_process";
2673
+
2674
+ // src/registry/validator.ts
2675
+ import * as fs12 from "fs";
2676
+ import * as path20 from "path";
2677
+ import { parse as parse5 } from "yaml";
2678
+ import semver from "semver";
2679
+
2680
+ // src/registry/bundled-skills.ts
2681
+ import * as fs11 from "fs";
2682
+ function getBundledSkillNames(bundledSkillsDir) {
2683
+ if (!fs11.existsSync(bundledSkillsDir)) {
2684
+ return /* @__PURE__ */ new Set();
2685
+ }
2686
+ const entries = fs11.readdirSync(bundledSkillsDir);
2687
+ const names = /* @__PURE__ */ new Set();
2688
+ for (const entry of entries) {
2689
+ try {
2690
+ const stat = fs11.statSync(`${bundledSkillsDir}/${entry}`);
2691
+ if (stat.isDirectory()) {
2692
+ names.add(String(entry));
2693
+ }
2694
+ } catch {
2695
+ }
2696
+ }
2697
+ return names;
2698
+ }
2699
+
2700
+ // src/registry/validator.ts
2701
+ async function validateForPublish(skillDir) {
2702
+ const errors = [];
2703
+ const skillYamlPath = path20.join(skillDir, "skill.yaml");
2704
+ if (!fs12.existsSync(skillYamlPath)) {
2705
+ errors.push("skill.yaml not found. Create one with: harness skill create <name>");
2706
+ return { valid: false, errors };
2707
+ }
2708
+ let skillMeta;
2709
+ try {
2710
+ const raw = fs12.readFileSync(skillYamlPath, "utf-8");
2711
+ const parsed = parse5(raw);
2712
+ const result = SkillMetadataSchema.safeParse(parsed);
2713
+ if (!result.success) {
2714
+ const issues = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
2715
+ errors.push(`skill.yaml validation failed: ${issues}`);
2716
+ return { valid: false, errors };
2717
+ }
2718
+ skillMeta = result.data;
2719
+ } catch (err) {
2720
+ errors.push(`Failed to parse skill.yaml: ${err instanceof Error ? err.message : String(err)}`);
2721
+ return { valid: false, errors };
2722
+ }
2723
+ if (!skillMeta.description || skillMeta.description.trim().length === 0) {
2724
+ errors.push("description must not be empty. Add a meaningful description to skill.yaml.");
2725
+ }
2726
+ if (!skillMeta.platforms || skillMeta.platforms.length === 0) {
2727
+ errors.push("At least one platform is required. Add platforms to skill.yaml.");
2728
+ }
2729
+ if (!skillMeta.triggers || skillMeta.triggers.length === 0) {
2730
+ errors.push("At least one trigger is required. Add triggers to skill.yaml.");
2731
+ }
2732
+ const skillMdPath = path20.join(skillDir, "SKILL.md");
2733
+ if (!fs12.existsSync(skillMdPath)) {
2734
+ errors.push("SKILL.md not found. Create it with content describing your skill.");
2735
+ } else {
2736
+ const content = fs12.readFileSync(skillMdPath, "utf-8");
2737
+ if (!content.includes("## When to Use")) {
2738
+ errors.push('SKILL.md must contain a "## When to Use" section.');
2739
+ }
2740
+ if (!content.includes("## Process")) {
2741
+ errors.push('SKILL.md must contain a "## Process" section.');
2742
+ }
2743
+ }
2744
+ const globalSkillsDir = resolveGlobalSkillsDir();
2745
+ const bundledNames = getBundledSkillNames(globalSkillsDir);
2746
+ if (bundledNames.has(skillMeta.name)) {
2747
+ errors.push(
2748
+ `Skill name "${skillMeta.name}" conflicts with a bundled skill. Choose a different name.`
2749
+ );
2750
+ }
2751
+ try {
2752
+ const packageName = resolvePackageName(skillMeta.name);
2753
+ const metadata = await fetchPackageMetadata(packageName);
2754
+ const publishedVersion = metadata["dist-tags"]?.latest;
2755
+ if (publishedVersion && !semver.gt(skillMeta.version, publishedVersion)) {
2756
+ errors.push(
2757
+ `Version ${skillMeta.version} must be greater than published version ${publishedVersion}. Bump the version in skill.yaml.`
2758
+ );
2759
+ }
2760
+ } catch {
2761
+ }
2762
+ if (skillMeta.depends_on && skillMeta.depends_on.length > 0) {
2763
+ for (const dep of skillMeta.depends_on) {
2764
+ if (bundledNames.has(dep)) continue;
2765
+ try {
2766
+ const depPkg = resolvePackageName(dep);
2767
+ await fetchPackageMetadata(depPkg);
2768
+ } catch {
2769
+ errors.push(
2770
+ `Dependency "${dep}" not found on npm or as a bundled skill. Publish it first or remove from depends_on.`
2771
+ );
2772
+ }
2773
+ }
2774
+ }
2775
+ return {
2776
+ valid: errors.length === 0,
2777
+ errors,
2778
+ ...skillMeta ? { skillMeta } : {}
2779
+ };
2780
+ }
2781
+
2782
+ // src/skill/package-json.ts
2783
+ function derivePackageJson(skill) {
2784
+ const keywords = ["harness-skill", ...skill.platforms, ...skill.triggers];
2785
+ const pkg = {
2786
+ name: `@harness-skills/${skill.name}`,
2787
+ version: skill.version,
2788
+ description: skill.description,
2789
+ keywords,
2790
+ files: ["skill.yaml", "SKILL.md", "README.md"],
2791
+ license: "MIT"
2792
+ };
2793
+ if (skill.repository) {
2794
+ pkg.repository = {
2795
+ type: "git",
2796
+ url: skill.repository
2797
+ };
2798
+ }
2799
+ return pkg;
2800
+ }
2801
+
2802
+ // src/commands/skill/publish.ts
2803
+ async function runPublish(skillDir, opts) {
2804
+ const validation = await validateForPublish(skillDir);
2805
+ if (!validation.valid) {
2806
+ const errorList = validation.errors.map((e) => ` - ${e}`).join("\n");
2807
+ throw new Error(`Pre-publish validation failed:
2808
+ ${errorList}`);
2809
+ }
2810
+ const meta = validation.skillMeta;
2811
+ const pkg = derivePackageJson(meta);
2812
+ const pkgPath = path21.join(skillDir, "package.json");
2813
+ fs13.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
2814
+ const readmePath = path21.join(skillDir, "README.md");
2815
+ if (!fs13.existsSync(readmePath)) {
2816
+ const skillMdContent = fs13.readFileSync(path21.join(skillDir, "SKILL.md"), "utf-8");
2817
+ const readme = `# ${pkg.name}
2818
+
2819
+ ${meta.description}
2820
+
2821
+ ## Installation
2822
+
2823
+ \`\`\`bash
2824
+ harness install ${meta.name}
2825
+ \`\`\`
2826
+
2827
+ ---
2828
+
2829
+ ${skillMdContent}`;
2830
+ fs13.writeFileSync(readmePath, readme);
2831
+ }
2832
+ if (opts.dryRun) {
2833
+ return {
2834
+ name: pkg.name,
2835
+ version: pkg.version,
2836
+ published: false,
2837
+ dryRun: true
2838
+ };
2839
+ }
2840
+ execFileSync3("npm", ["publish", "--access", "public"], {
2841
+ cwd: skillDir,
2842
+ stdio: "pipe",
2843
+ timeout: 6e4
2844
+ });
2845
+ return {
2846
+ name: pkg.name,
2847
+ version: pkg.version,
2848
+ published: true
2849
+ };
2850
+ }
2851
+ function createPublishCommand() {
2852
+ return new Command27("publish").description("Validate and publish a skill to @harness-skills on npm").option("--dry-run", "Run validation and generate package.json without publishing").option("--dir <dir>", "Skill directory (default: current directory)").action(async (opts, cmd) => {
2853
+ const globalOpts = cmd.optsWithGlobals();
2854
+ const skillDir = opts.dir || process.cwd();
2855
+ try {
2856
+ const result = await runPublish(skillDir, {
2857
+ dryRun: opts.dryRun
2858
+ });
2859
+ if (globalOpts.json) {
2860
+ logger.raw(result);
2861
+ } else if (result.dryRun) {
2862
+ logger.success(`Validation passed. Would publish: ${result.name}@${result.version}`);
2863
+ logger.info("Run without --dry-run to publish.");
2864
+ } else {
2865
+ logger.success(`Published ${result.name}@${result.version}`);
2866
+ }
2867
+ } catch (err) {
2868
+ logger.error(err instanceof Error ? err.message : String(err));
2869
+ process.exit(ExitCode.VALIDATION_FAILED);
2870
+ }
2871
+ });
2872
+ }
2873
+
2874
+ // src/commands/skill/index.ts
2875
+ function createSkillCommand() {
2876
+ const command = new Command28("skill").description("Skill management commands");
2877
+ command.addCommand(createListCommand2());
2878
+ command.addCommand(createRunCommand2());
2879
+ command.addCommand(createValidateCommand3());
2880
+ command.addCommand(createInfoCommand());
2881
+ command.addCommand(createSearchCommand());
2882
+ command.addCommand(createCreateCommand());
2883
+ command.addCommand(createPublishCommand());
2884
+ return command;
2885
+ }
2886
+
2887
+ // src/commands/state/index.ts
2888
+ import { Command as Command33 } from "commander";
2889
+
2890
+ // src/commands/state/show.ts
2891
+ import { Command as Command29 } from "commander";
2892
+ import * as path22 from "path";
2893
+ function createShowCommand() {
2894
+ return new Command29("show").description("Show current project state").option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").action(async (opts, cmd) => {
2895
+ const globalOpts = cmd.optsWithGlobals();
2896
+ const projectPath = path22.resolve(opts.path);
2897
+ const result = await loadState(projectPath, opts.stream);
2898
+ if (!result.ok) {
2899
+ logger.error(result.error.message);
2900
+ process.exit(ExitCode.ERROR);
2901
+ return;
2902
+ }
2903
+ const state = result.value;
2904
+ if (globalOpts.json) {
2905
+ logger.raw(state);
2906
+ } else if (globalOpts.quiet) {
2907
+ console.log(JSON.stringify(state));
2908
+ } else {
2909
+ if (opts.stream) console.log(`Stream: ${opts.stream}`);
2910
+ console.log(`Schema Version: ${state.schemaVersion}`);
2911
+ if (state.position.phase) console.log(`Phase: ${state.position.phase}`);
2912
+ if (state.position.task) console.log(`Task: ${state.position.task}`);
2913
+ if (state.lastSession) {
2914
+ console.log(`Last Session: ${state.lastSession.date} \u2014 ${state.lastSession.summary}`);
2915
+ }
2916
+ if (Object.keys(state.progress).length > 0) {
2917
+ console.log("\nProgress:");
2918
+ for (const [task, status] of Object.entries(state.progress)) {
2919
+ console.log(` ${task}: ${status}`);
2920
+ }
2921
+ }
2922
+ if (state.decisions.length > 0) {
2923
+ console.log(`
2924
+ Decisions: ${state.decisions.length}`);
2925
+ }
2926
+ if (state.blockers.length > 0) {
2927
+ const open = state.blockers.filter((b) => b.status === "open").length;
2928
+ console.log(`Blockers: ${open} open / ${state.blockers.length} total`);
2929
+ }
2930
+ }
2931
+ process.exit(ExitCode.SUCCESS);
2932
+ });
2933
+ }
2934
+
2935
+ // src/commands/state/reset.ts
2936
+ import { Command as Command30 } from "commander";
2937
+ import * as fs14 from "fs";
2938
+ import * as path23 from "path";
2939
+ import * as readline from "readline";
2940
+ function createResetCommand() {
2941
+ return new Command30("reset").description("Reset project state (deletes .harness/state.json)").option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").option("--yes", "Skip confirmation prompt").action(async (opts, _cmd) => {
2942
+ const projectPath = path23.resolve(opts.path);
2943
+ let statePath;
2944
+ if (opts.stream) {
2945
+ const streamResult = await resolveStreamPath(projectPath, { stream: opts.stream });
2946
+ if (!streamResult.ok) {
2947
+ logger.error(streamResult.error.message);
2948
+ process.exit(ExitCode.ERROR);
2949
+ return;
2950
+ }
2951
+ statePath = path23.join(streamResult.value, "state.json");
2952
+ } else {
2953
+ statePath = path23.join(projectPath, ".harness", "state.json");
2954
+ }
2955
+ if (!fs14.existsSync(statePath)) {
2956
+ logger.info("No state file found. Nothing to reset.");
2957
+ process.exit(ExitCode.SUCCESS);
2958
+ return;
2959
+ }
2960
+ if (!opts.yes) {
2961
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
2962
+ const answer = await new Promise((resolve24) => {
2963
+ rl.question("Reset project state? This cannot be undone. [y/N] ", resolve24);
2964
+ });
2965
+ rl.close();
2966
+ if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
2967
+ logger.info("Reset cancelled.");
2968
+ process.exit(ExitCode.SUCCESS);
2969
+ return;
2970
+ }
2971
+ }
2972
+ try {
2973
+ fs14.unlinkSync(statePath);
2974
+ logger.success("Project state reset.");
2975
+ } catch (e) {
2976
+ logger.error(`Failed to reset state: ${e instanceof Error ? e.message : String(e)}`);
2977
+ process.exit(ExitCode.ERROR);
2978
+ return;
2979
+ }
2980
+ process.exit(ExitCode.SUCCESS);
2981
+ });
2982
+ }
2983
+
2984
+ // src/commands/state/learn.ts
2985
+ import { Command as Command31 } from "commander";
2986
+ import * as path24 from "path";
2987
+ function createLearnCommand() {
2988
+ return new Command31("learn").description("Append a learning to .harness/learnings.md").argument("<message>", "The learning to record").option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").action(async (message, opts, _cmd) => {
2989
+ const projectPath = path24.resolve(opts.path);
2990
+ const result = await appendLearning(projectPath, message, void 0, void 0, opts.stream);
2991
+ if (!result.ok) {
2992
+ logger.error(result.error.message);
2993
+ process.exit(ExitCode.ERROR);
2994
+ return;
2995
+ }
2996
+ logger.success(`Learning recorded.`);
2997
+ process.exit(ExitCode.SUCCESS);
2998
+ });
2999
+ }
3000
+
3001
+ // src/commands/state/streams.ts
3002
+ import { Command as Command32 } from "commander";
3003
+ import * as path25 from "path";
3004
+ function createStreamsCommand() {
3005
+ const command = new Command32("streams").description("Manage state streams");
3006
+ command.command("list").description("List all known streams").option("--path <path>", "Project root path", ".").action(async (opts, cmd) => {
3007
+ const globalOpts = cmd.optsWithGlobals();
3008
+ const projectPath = path25.resolve(opts.path);
2378
3009
  const indexResult = await loadStreamIndex(projectPath);
2379
3010
  const result = await listStreams(projectPath);
2380
3011
  if (!result.ok) {
@@ -2398,7 +3029,7 @@ function createStreamsCommand() {
2398
3029
  process.exit(ExitCode.SUCCESS);
2399
3030
  });
2400
3031
  command.command("create <name>").description("Create a new stream").option("--path <path>", "Project root path", ".").option("--branch <branch>", "Associate with a git branch").action(async (name, opts) => {
2401
- const projectPath = path21.resolve(opts.path);
3032
+ const projectPath = path25.resolve(opts.path);
2402
3033
  const result = await createStream(projectPath, name, opts.branch);
2403
3034
  if (!result.ok) {
2404
3035
  logger.error(result.error.message);
@@ -2409,7 +3040,7 @@ function createStreamsCommand() {
2409
3040
  process.exit(ExitCode.SUCCESS);
2410
3041
  });
2411
3042
  command.command("archive <name>").description("Archive a stream").option("--path <path>", "Project root path", ".").action(async (name, opts) => {
2412
- const projectPath = path21.resolve(opts.path);
3043
+ const projectPath = path25.resolve(opts.path);
2413
3044
  const result = await archiveStream(projectPath, name);
2414
3045
  if (!result.ok) {
2415
3046
  logger.error(result.error.message);
@@ -2420,7 +3051,7 @@ function createStreamsCommand() {
2420
3051
  process.exit(ExitCode.SUCCESS);
2421
3052
  });
2422
3053
  command.command("activate <name>").description("Set the active stream").option("--path <path>", "Project root path", ".").action(async (name, opts) => {
2423
- const projectPath = path21.resolve(opts.path);
3054
+ const projectPath = path25.resolve(opts.path);
2424
3055
  const result = await setActiveStream(projectPath, name);
2425
3056
  if (!result.ok) {
2426
3057
  logger.error(result.error.message);
@@ -2435,7 +3066,7 @@ function createStreamsCommand() {
2435
3066
 
2436
3067
  // src/commands/state/index.ts
2437
3068
  function createStateCommand() {
2438
- const command = new Command30("state").description("Project state management commands");
3069
+ const command = new Command33("state").description("Project state management commands");
2439
3070
  command.addCommand(createShowCommand());
2440
3071
  command.addCommand(createResetCommand());
2441
3072
  command.addCommand(createLearnCommand());
@@ -2444,11 +3075,20 @@ function createStateCommand() {
2444
3075
  }
2445
3076
 
2446
3077
  // src/commands/ci/index.ts
2447
- import { Command as Command33 } from "commander";
3078
+ import { Command as Command36 } from "commander";
2448
3079
 
2449
3080
  // src/commands/ci/check.ts
2450
- import { Command as Command31 } from "commander";
2451
- var VALID_CHECKS = ["validate", "deps", "docs", "entropy", "phase-gate"];
3081
+ import { Command as Command34 } from "commander";
3082
+ var VALID_CHECKS = [
3083
+ "validate",
3084
+ "deps",
3085
+ "docs",
3086
+ "entropy",
3087
+ "security",
3088
+ "perf",
3089
+ "phase-gate",
3090
+ "arch"
3091
+ ];
2452
3092
  async function runCICheck(options) {
2453
3093
  const configResult = resolveConfig(options.configPath);
2454
3094
  if (!configResult.ok) {
@@ -2478,7 +3118,7 @@ function parseFailOn(failOn) {
2478
3118
  return "error";
2479
3119
  }
2480
3120
  function createCheckCommand() {
2481
- return new Command31("check").description("Run all harness checks for CI (validate, deps, docs, entropy, phase-gate)").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) => {
3121
+ 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) => {
2482
3122
  const globalOpts = cmd.optsWithGlobals();
2483
3123
  const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : globalOpts.verbose ? OutputMode.VERBOSE : OutputMode.TEXT;
2484
3124
  const skip = parseSkip(opts.skip);
@@ -2522,10 +3162,19 @@ function createCheckCommand() {
2522
3162
  }
2523
3163
 
2524
3164
  // src/commands/ci/init.ts
2525
- import { Command as Command32 } from "commander";
2526
- import * as fs10 from "fs";
2527
- import * as path22 from "path";
2528
- var ALL_CHECKS = ["validate", "deps", "docs", "entropy", "phase-gate"];
3165
+ import { Command as Command35 } from "commander";
3166
+ import * as fs15 from "fs";
3167
+ import * as path26 from "path";
3168
+ var ALL_CHECKS = [
3169
+ "validate",
3170
+ "deps",
3171
+ "docs",
3172
+ "entropy",
3173
+ "security",
3174
+ "perf",
3175
+ "phase-gate",
3176
+ "arch"
3177
+ ];
2529
3178
  function buildSkipFlag(checks) {
2530
3179
  if (!checks) return "";
2531
3180
  const skipChecks = ALL_CHECKS.filter((c) => !checks.includes(c));
@@ -2616,12 +3265,12 @@ function generateCIConfig(options) {
2616
3265
  });
2617
3266
  }
2618
3267
  function detectPlatform() {
2619
- if (fs10.existsSync(".github")) return "github";
2620
- if (fs10.existsSync(".gitlab-ci.yml")) return "gitlab";
3268
+ if (fs15.existsSync(".github")) return "github";
3269
+ if (fs15.existsSync(".gitlab-ci.yml")) return "gitlab";
2621
3270
  return null;
2622
3271
  }
2623
3272
  function createInitCommand2() {
2624
- return new Command32("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) => {
3273
+ 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) => {
2625
3274
  const globalOpts = cmd.optsWithGlobals();
2626
3275
  const platform = opts.platform ?? detectPlatform() ?? "generic";
2627
3276
  const checks = opts.checks ? opts.checks.split(",").map((s) => s.trim()) : void 0;
@@ -2633,12 +3282,12 @@ function createInitCommand2() {
2633
3282
  process.exit(result.error.exitCode);
2634
3283
  }
2635
3284
  const { filename, content } = result.value;
2636
- const targetPath = path22.resolve(filename);
2637
- const dir = path22.dirname(targetPath);
2638
- fs10.mkdirSync(dir, { recursive: true });
2639
- fs10.writeFileSync(targetPath, content);
3285
+ const targetPath = path26.resolve(filename);
3286
+ const dir = path26.dirname(targetPath);
3287
+ fs15.mkdirSync(dir, { recursive: true });
3288
+ fs15.writeFileSync(targetPath, content);
2640
3289
  if (platform === "generic" && process.platform !== "win32") {
2641
- fs10.chmodSync(targetPath, "755");
3290
+ fs15.chmodSync(targetPath, "755");
2642
3291
  }
2643
3292
  if (globalOpts.json) {
2644
3293
  console.log(JSON.stringify({ file: filename, platform }));
@@ -2651,15 +3300,15 @@ function createInitCommand2() {
2651
3300
 
2652
3301
  // src/commands/ci/index.ts
2653
3302
  function createCICommand() {
2654
- const command = new Command33("ci").description("CI/CD integration commands");
3303
+ const command = new Command36("ci").description("CI/CD integration commands");
2655
3304
  command.addCommand(createCheckCommand());
2656
3305
  command.addCommand(createInitCommand2());
2657
3306
  return command;
2658
3307
  }
2659
3308
 
2660
3309
  // src/commands/update.ts
2661
- import { Command as Command34 } from "commander";
2662
- import { execFileSync as execFileSync3 } from "child_process";
3310
+ import { Command as Command37 } from "commander";
3311
+ import { execFileSync as execFileSync4 } from "child_process";
2663
3312
  import { realpathSync } from "fs";
2664
3313
  import readline2 from "readline";
2665
3314
  import chalk3 from "chalk";
@@ -2681,7 +3330,7 @@ function detectPackageManager() {
2681
3330
  return "npm";
2682
3331
  }
2683
3332
  function getLatestVersion(pkg = "@harness-engineering/cli") {
2684
- const output = execFileSync3("npm", ["view", pkg, "dist-tags.latest"], {
3333
+ const output = execFileSync4("npm", ["view", pkg, "dist-tags.latest"], {
2685
3334
  encoding: "utf-8",
2686
3335
  timeout: 15e3
2687
3336
  });
@@ -2689,7 +3338,7 @@ function getLatestVersion(pkg = "@harness-engineering/cli") {
2689
3338
  }
2690
3339
  function getInstalledVersion(pm) {
2691
3340
  try {
2692
- const output = execFileSync3(pm, ["list", "-g", "@harness-engineering/cli", "--json"], {
3341
+ const output = execFileSync4(pm, ["list", "-g", "@harness-engineering/cli", "--json"], {
2693
3342
  encoding: "utf-8",
2694
3343
  timeout: 15e3
2695
3344
  });
@@ -2702,7 +3351,7 @@ function getInstalledVersion(pm) {
2702
3351
  }
2703
3352
  function getInstalledPackages(pm) {
2704
3353
  try {
2705
- const output = execFileSync3(pm, ["list", "-g", "--json"], {
3354
+ const output = execFileSync4(pm, ["list", "-g", "--json"], {
2706
3355
  encoding: "utf-8",
2707
3356
  timeout: 15e3
2708
3357
  });
@@ -2718,15 +3367,15 @@ function prompt(question) {
2718
3367
  input: process.stdin,
2719
3368
  output: process.stdout
2720
3369
  });
2721
- return new Promise((resolve20) => {
3370
+ return new Promise((resolve24) => {
2722
3371
  rl.question(question, (answer) => {
2723
3372
  rl.close();
2724
- resolve20(answer.trim().toLowerCase());
3373
+ resolve24(answer.trim().toLowerCase());
2725
3374
  });
2726
3375
  });
2727
3376
  }
2728
3377
  function createUpdateCommand() {
2729
- return new Command34("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) => {
3378
+ 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) => {
2730
3379
  const globalOpts = cmd.optsWithGlobals();
2731
3380
  const pm = detectPackageManager();
2732
3381
  if (globalOpts.verbose) {
@@ -2769,7 +3418,7 @@ function createUpdateCommand() {
2769
3418
  }
2770
3419
  try {
2771
3420
  logger.info("Updating packages...");
2772
- execFileSync3(pm, ["install", "-g", ...installPkgs], { stdio: "inherit", timeout: 12e4 });
3421
+ execFileSync4(pm, ["install", "-g", ...installPkgs], { stdio: "inherit", timeout: 12e4 });
2773
3422
  console.log("");
2774
3423
  logger.success("Update complete");
2775
3424
  } catch {
@@ -2779,12 +3428,12 @@ function createUpdateCommand() {
2779
3428
  process.exit(ExitCode.ERROR);
2780
3429
  }
2781
3430
  console.log("");
2782
- const regenAnswer = await prompt("Regenerate slash commands and agent definitions? (y/N) ");
2783
- if (regenAnswer === "y" || regenAnswer === "yes") {
2784
- const scopeAnswer = await prompt("Generate for (g)lobal or (l)ocal project? (g/l) ");
2785
- const isGlobal = scopeAnswer === "g" || scopeAnswer === "global";
3431
+ const regenAnswer = await prompt("Regenerate slash commands and agent definitions? (Y/n) ");
3432
+ if (regenAnswer !== "n" && regenAnswer !== "no") {
3433
+ const scopeAnswer = await prompt("Generate for (G)lobal or (l)ocal project? (G/l) ");
3434
+ const isGlobal = scopeAnswer !== "l" && scopeAnswer !== "local";
2786
3435
  try {
2787
- execFileSync3("harness", ["generate", ...isGlobal ? ["--global"] : []], {
3436
+ execFileSync4("harness", ["generate", ...isGlobal ? ["--global"] : []], {
2788
3437
  stdio: "inherit"
2789
3438
  });
2790
3439
  } catch {
@@ -2797,9 +3446,9 @@ function createUpdateCommand() {
2797
3446
  }
2798
3447
 
2799
3448
  // src/commands/generate.ts
2800
- import { Command as Command35 } from "commander";
3449
+ import { Command as Command38 } from "commander";
2801
3450
  function createGenerateCommand3() {
2802
- return new Command35("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) => {
3451
+ 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) => {
2803
3452
  const globalOpts = cmd.optsWithGlobals();
2804
3453
  const platforms = opts.platforms.split(",").map((p) => p.trim());
2805
3454
  for (const p of platforms) {
@@ -2858,10 +3507,10 @@ function createGenerateCommand3() {
2858
3507
  }
2859
3508
 
2860
3509
  // src/commands/graph/scan.ts
2861
- import { Command as Command36 } from "commander";
2862
- import * as path23 from "path";
3510
+ import { Command as Command39 } from "commander";
3511
+ import * as path27 from "path";
2863
3512
  async function runScan(projectPath) {
2864
- const { GraphStore, CodeIngestor, TopologicalLinker, KnowledgeIngestor, GitIngestor } = await import("./dist-I7DB5VKB.js");
3513
+ const { GraphStore, CodeIngestor, TopologicalLinker, KnowledgeIngestor, GitIngestor } = await import("./dist-M6BQODWC.js");
2865
3514
  const store = new GraphStore();
2866
3515
  const start = Date.now();
2867
3516
  await new CodeIngestor(store).ingest(projectPath);
@@ -2872,13 +3521,13 @@ async function runScan(projectPath) {
2872
3521
  await new GitIngestor(store).ingest(projectPath);
2873
3522
  } catch {
2874
3523
  }
2875
- const graphDir = path23.join(projectPath, ".harness", "graph");
3524
+ const graphDir = path27.join(projectPath, ".harness", "graph");
2876
3525
  await store.save(graphDir);
2877
3526
  return { nodeCount: store.nodeCount, edgeCount: store.edgeCount, durationMs: Date.now() - start };
2878
3527
  }
2879
3528
  function createScanCommand() {
2880
- return new Command36("scan").description("Scan project and build knowledge graph").argument("[path]", "Project root path", ".").action(async (inputPath, _opts, cmd) => {
2881
- const projectPath = path23.resolve(inputPath);
3529
+ return new Command39("scan").description("Scan project and build knowledge graph").argument("[path]", "Project root path", ".").action(async (inputPath, _opts, cmd) => {
3530
+ const projectPath = path27.resolve(inputPath);
2882
3531
  const globalOpts = cmd.optsWithGlobals();
2883
3532
  try {
2884
3533
  const result = await runScan(projectPath);
@@ -2897,13 +3546,13 @@ function createScanCommand() {
2897
3546
  }
2898
3547
 
2899
3548
  // src/commands/graph/ingest.ts
2900
- import { Command as Command37 } from "commander";
2901
- import * as path24 from "path";
3549
+ import { Command as Command40 } from "commander";
3550
+ import * as path28 from "path";
2902
3551
  async function loadConnectorConfig(projectPath, source) {
2903
3552
  try {
2904
- const fs11 = await import("fs/promises");
2905
- const configPath = path24.join(projectPath, "harness.config.json");
2906
- const config = JSON.parse(await fs11.readFile(configPath, "utf-8"));
3553
+ const fs20 = await import("fs/promises");
3554
+ const configPath = path28.join(projectPath, "harness.config.json");
3555
+ const config = JSON.parse(await fs20.readFile(configPath, "utf-8"));
2907
3556
  const connector = config.graph?.connectors?.find(
2908
3557
  (c) => c.source === source
2909
3558
  );
@@ -2942,8 +3591,8 @@ async function runIngest(projectPath, source, opts) {
2942
3591
  SyncManager,
2943
3592
  JiraConnector,
2944
3593
  SlackConnector
2945
- } = await import("./dist-I7DB5VKB.js");
2946
- const graphDir = path24.join(projectPath, ".harness", "graph");
3594
+ } = await import("./dist-M6BQODWC.js");
3595
+ const graphDir = path28.join(projectPath, ".harness", "graph");
2947
3596
  const store = new GraphStore();
2948
3597
  await store.load(graphDir);
2949
3598
  if (opts?.all) {
@@ -3004,13 +3653,13 @@ async function runIngest(projectPath, source, opts) {
3004
3653
  return result;
3005
3654
  }
3006
3655
  function createIngestCommand() {
3007
- return new Command37("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) => {
3656
+ 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) => {
3008
3657
  if (!opts.source && !opts.all) {
3009
3658
  console.error("Error: --source or --all is required");
3010
3659
  process.exit(1);
3011
3660
  }
3012
3661
  const globalOpts = cmd.optsWithGlobals();
3013
- const projectPath = path24.resolve(globalOpts.config ? path24.dirname(globalOpts.config) : ".");
3662
+ const projectPath = path28.resolve(globalOpts.config ? path28.dirname(globalOpts.config) : ".");
3014
3663
  try {
3015
3664
  const result = await runIngest(projectPath, opts.source ?? "", {
3016
3665
  full: opts.full,
@@ -3032,12 +3681,12 @@ function createIngestCommand() {
3032
3681
  }
3033
3682
 
3034
3683
  // src/commands/graph/query.ts
3035
- import { Command as Command38 } from "commander";
3036
- import * as path25 from "path";
3684
+ import { Command as Command41 } from "commander";
3685
+ import * as path29 from "path";
3037
3686
  async function runQuery(projectPath, rootNodeId, opts) {
3038
- const { GraphStore, ContextQL } = await import("./dist-I7DB5VKB.js");
3687
+ const { GraphStore, ContextQL } = await import("./dist-M6BQODWC.js");
3039
3688
  const store = new GraphStore();
3040
- const graphDir = path25.join(projectPath, ".harness", "graph");
3689
+ const graphDir = path29.join(projectPath, ".harness", "graph");
3041
3690
  const loaded = await store.load(graphDir);
3042
3691
  if (!loaded) throw new Error("No graph found. Run `harness scan` first.");
3043
3692
  const params = {
@@ -3051,9 +3700,9 @@ async function runQuery(projectPath, rootNodeId, opts) {
3051
3700
  return cql.execute(params);
3052
3701
  }
3053
3702
  function createQueryCommand() {
3054
- return new Command38("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) => {
3703
+ 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) => {
3055
3704
  const globalOpts = cmd.optsWithGlobals();
3056
- const projectPath = path25.resolve(globalOpts.config ? path25.dirname(globalOpts.config) : ".");
3705
+ const projectPath = path29.resolve(globalOpts.config ? path29.dirname(globalOpts.config) : ".");
3057
3706
  try {
3058
3707
  const result = await runQuery(projectPath, rootNodeId, {
3059
3708
  depth: parseInt(opts.depth),
@@ -3079,21 +3728,21 @@ function createQueryCommand() {
3079
3728
  }
3080
3729
 
3081
3730
  // src/commands/graph/index.ts
3082
- import { Command as Command39 } from "commander";
3731
+ import { Command as Command42 } from "commander";
3083
3732
 
3084
3733
  // src/commands/graph/status.ts
3085
- import * as path26 from "path";
3734
+ import * as path30 from "path";
3086
3735
  async function runGraphStatus(projectPath) {
3087
- const { GraphStore } = await import("./dist-I7DB5VKB.js");
3088
- const graphDir = path26.join(projectPath, ".harness", "graph");
3736
+ const { GraphStore } = await import("./dist-M6BQODWC.js");
3737
+ const graphDir = path30.join(projectPath, ".harness", "graph");
3089
3738
  const store = new GraphStore();
3090
3739
  const loaded = await store.load(graphDir);
3091
3740
  if (!loaded) return { status: "no_graph", message: "No graph found. Run `harness scan` first." };
3092
- const fs11 = await import("fs/promises");
3093
- const metaPath = path26.join(graphDir, "metadata.json");
3741
+ const fs20 = await import("fs/promises");
3742
+ const metaPath = path30.join(graphDir, "metadata.json");
3094
3743
  let lastScan = "unknown";
3095
3744
  try {
3096
- const meta = JSON.parse(await fs11.readFile(metaPath, "utf-8"));
3745
+ const meta = JSON.parse(await fs20.readFile(metaPath, "utf-8"));
3097
3746
  lastScan = meta.lastScanTimestamp;
3098
3747
  } catch {
3099
3748
  }
@@ -3104,8 +3753,8 @@ async function runGraphStatus(projectPath) {
3104
3753
  }
3105
3754
  let connectorSyncStatus = {};
3106
3755
  try {
3107
- const syncMetaPath = path26.join(graphDir, "sync-metadata.json");
3108
- const syncMeta = JSON.parse(await fs11.readFile(syncMetaPath, "utf-8"));
3756
+ const syncMetaPath = path30.join(graphDir, "sync-metadata.json");
3757
+ const syncMeta = JSON.parse(await fs20.readFile(syncMetaPath, "utf-8"));
3109
3758
  for (const [name, data] of Object.entries(syncMeta.connectors ?? {})) {
3110
3759
  connectorSyncStatus[name] = data.lastSyncTimestamp;
3111
3760
  }
@@ -3122,10 +3771,10 @@ async function runGraphStatus(projectPath) {
3122
3771
  }
3123
3772
 
3124
3773
  // src/commands/graph/export.ts
3125
- import * as path27 from "path";
3774
+ import * as path31 from "path";
3126
3775
  async function runGraphExport(projectPath, format) {
3127
- const { GraphStore } = await import("./dist-I7DB5VKB.js");
3128
- const graphDir = path27.join(projectPath, ".harness", "graph");
3776
+ const { GraphStore } = await import("./dist-M6BQODWC.js");
3777
+ const graphDir = path31.join(projectPath, ".harness", "graph");
3129
3778
  const store = new GraphStore();
3130
3779
  const loaded = await store.load(graphDir);
3131
3780
  if (!loaded) throw new Error("No graph found. Run `harness scan` first.");
@@ -3154,13 +3803,13 @@ async function runGraphExport(projectPath, format) {
3154
3803
  }
3155
3804
 
3156
3805
  // src/commands/graph/index.ts
3157
- import * as path28 from "path";
3806
+ import * as path32 from "path";
3158
3807
  function createGraphCommand() {
3159
- const graph = new Command39("graph").description("Knowledge graph management");
3808
+ const graph = new Command42("graph").description("Knowledge graph management");
3160
3809
  graph.command("status").description("Show graph statistics").action(async (_opts, cmd) => {
3161
3810
  try {
3162
3811
  const globalOpts = cmd.optsWithGlobals();
3163
- const projectPath = path28.resolve(globalOpts.config ? path28.dirname(globalOpts.config) : ".");
3812
+ const projectPath = path32.resolve(globalOpts.config ? path32.dirname(globalOpts.config) : ".");
3164
3813
  const result = await runGraphStatus(projectPath);
3165
3814
  if (globalOpts.json) {
3166
3815
  console.log(JSON.stringify(result, null, 2));
@@ -3187,7 +3836,7 @@ function createGraphCommand() {
3187
3836
  });
3188
3837
  graph.command("export").description("Export graph").requiredOption("--format <format>", "Output format (json, mermaid)").action(async (opts, cmd) => {
3189
3838
  const globalOpts = cmd.optsWithGlobals();
3190
- const projectPath = path28.resolve(globalOpts.config ? path28.dirname(globalOpts.config) : ".");
3839
+ const projectPath = path32.resolve(globalOpts.config ? path32.dirname(globalOpts.config) : ".");
3191
3840
  try {
3192
3841
  const output = await runGraphExport(projectPath, opts.format);
3193
3842
  console.log(output);
@@ -3200,17 +3849,770 @@ function createGraphCommand() {
3200
3849
  }
3201
3850
 
3202
3851
  // src/commands/mcp.ts
3203
- import { Command as Command40 } from "commander";
3852
+ import { Command as Command43 } from "commander";
3204
3853
  function createMcpCommand() {
3205
- return new Command40("mcp").description("Start the MCP (Model Context Protocol) server on stdio").action(async () => {
3206
- const { startServer: startServer2 } = await import("./mcp-BNLBTCXZ.js");
3854
+ return new Command43("mcp").description("Start the MCP (Model Context Protocol) server on stdio").action(async () => {
3855
+ const { startServer: startServer2 } = await import("./mcp-MOKLYNZL.js");
3207
3856
  await startServer2();
3208
3857
  });
3209
3858
  }
3210
3859
 
3860
+ // src/commands/impact-preview.ts
3861
+ import { Command as Command44 } from "commander";
3862
+ import { execSync as execSync3 } from "child_process";
3863
+ import * as path33 from "path";
3864
+ import * as fs16 from "fs";
3865
+ function getStagedFiles(cwd) {
3866
+ try {
3867
+ const output = execSync3("git diff --cached --name-only", {
3868
+ cwd,
3869
+ encoding: "utf-8"
3870
+ });
3871
+ return output.trim().split("\n").filter((f) => f.length > 0);
3872
+ } catch {
3873
+ return [];
3874
+ }
3875
+ }
3876
+ function graphExists(projectPath) {
3877
+ try {
3878
+ return fs16.existsSync(path33.join(projectPath, ".harness", "graph", "graph.json"));
3879
+ } catch {
3880
+ return false;
3881
+ }
3882
+ }
3883
+ function extractNodeName(id) {
3884
+ const parts = id.split(":");
3885
+ if (parts.length > 1) {
3886
+ const fullPath = parts.slice(1).join(":");
3887
+ return path33.basename(fullPath);
3888
+ }
3889
+ return id;
3890
+ }
3891
+ var TEST_NODE_TYPES = /* @__PURE__ */ new Set(["test_result"]);
3892
+ var DOC_NODE_TYPES = /* @__PURE__ */ new Set(["adr", "decision", "document", "learning"]);
3893
+ function parseImpactResponse(response) {
3894
+ if (response.isError) return null;
3895
+ const text = response.content[0]?.text;
3896
+ if (!text) return null;
3897
+ try {
3898
+ const data = JSON.parse(text);
3899
+ if (data.mode === "summary") {
3900
+ const items = { code: [], tests: [], docs: [], other: [] };
3901
+ for (const item of data.highestRiskItems ?? []) {
3902
+ if (TEST_NODE_TYPES.has(item.type)) items.tests.push(item);
3903
+ else if (DOC_NODE_TYPES.has(item.type)) items.docs.push(item);
3904
+ else items.code.push(item);
3905
+ }
3906
+ return { counts: data.impactCounts, items };
3907
+ } else {
3908
+ const impact = data.impact ?? {};
3909
+ const items = {
3910
+ code: (impact.code ?? []).map((n) => ({
3911
+ id: n.id,
3912
+ type: n.type
3913
+ })),
3914
+ tests: (impact.tests ?? []).map((n) => ({
3915
+ id: n.id,
3916
+ type: n.type
3917
+ })),
3918
+ docs: (impact.docs ?? []).map((n) => ({
3919
+ id: n.id,
3920
+ type: n.type
3921
+ })),
3922
+ other: (impact.other ?? []).map((n) => ({
3923
+ id: n.id,
3924
+ type: n.type
3925
+ }))
3926
+ };
3927
+ return {
3928
+ counts: {
3929
+ code: items.code.length,
3930
+ tests: items.tests.length,
3931
+ docs: items.docs.length,
3932
+ other: items.other.length
3933
+ },
3934
+ items
3935
+ };
3936
+ }
3937
+ } catch {
3938
+ return null;
3939
+ }
3940
+ }
3941
+ function mergeImpactGroups(groups) {
3942
+ const seen = /* @__PURE__ */ new Set();
3943
+ const merged = { code: [], tests: [], docs: [], other: [] };
3944
+ for (const group of groups) {
3945
+ for (const category of ["code", "tests", "docs", "other"]) {
3946
+ for (const item of group[category]) {
3947
+ if (!seen.has(item.id)) {
3948
+ seen.add(item.id);
3949
+ merged[category].push(item);
3950
+ }
3951
+ }
3952
+ }
3953
+ }
3954
+ return merged;
3955
+ }
3956
+ function formatCompactLine(label, count, unit, items, maxItems) {
3957
+ if (count === 0) return "";
3958
+ const labelPad = label.padEnd(6);
3959
+ const countStr = String(count).padStart(3);
3960
+ const topNames = items.slice(0, maxItems).map((i) => extractNodeName(i.id));
3961
+ const remaining = count - topNames.length;
3962
+ const namePart = remaining > 0 ? `(${topNames.join(", ")}, +${remaining})` : topNames.length > 0 ? `(${topNames.join(", ")})` : "";
3963
+ return ` ${labelPad}${countStr} ${unit.padEnd(7)} ${namePart}`;
3964
+ }
3965
+ function formatCompact(stagedCount, merged, counts) {
3966
+ const lines = [];
3967
+ lines.push(`Impact Preview (${stagedCount} staged file${stagedCount === 1 ? "" : "s"})`);
3968
+ const codeLine = formatCompactLine("Code:", counts.code, "files", merged.code, 2);
3969
+ const testsLine = formatCompactLine("Tests:", counts.tests, "tests", merged.tests, 2);
3970
+ const docsLine = formatCompactLine("Docs:", counts.docs, "docs", merged.docs, 2);
3971
+ if (codeLine) lines.push(codeLine);
3972
+ if (testsLine) lines.push(testsLine);
3973
+ if (docsLine) lines.push(docsLine);
3974
+ const total = counts.code + counts.tests + counts.docs + counts.other;
3975
+ lines.push(` Total: ${total} affected`);
3976
+ return lines.join("\n");
3977
+ }
3978
+ function formatDetailed(stagedCount, merged, counts) {
3979
+ const lines = [];
3980
+ lines.push(`Impact Preview (${stagedCount} staged file${stagedCount === 1 ? "" : "s"})`);
3981
+ const sections = [
3982
+ { label: "Code", count: counts.code, unit: "files", items: merged.code },
3983
+ { label: "Tests", count: counts.tests, unit: "tests", items: merged.tests },
3984
+ { label: "Docs", count: counts.docs, unit: "docs", items: merged.docs }
3985
+ ];
3986
+ for (const section of sections) {
3987
+ if (section.count === 0 && section.items.length === 0) continue;
3988
+ lines.push(` ${section.label}: ${section.count} ${section.unit}`);
3989
+ for (const item of section.items) {
3990
+ lines.push(` ${extractNodeName(item.id)}`);
3991
+ }
3992
+ }
3993
+ const total = counts.code + counts.tests + counts.docs + counts.other;
3994
+ lines.push(` Total: ${total} affected`);
3995
+ return lines.join("\n");
3996
+ }
3997
+ function formatPerFile(perFileResults) {
3998
+ const lines = [];
3999
+ lines.push(
4000
+ `Impact Preview (${perFileResults.length} staged file${perFileResults.length === 1 ? "" : "s"})`
4001
+ );
4002
+ const maxLen = Math.max(...perFileResults.map((r) => r.file.length));
4003
+ for (const result of perFileResults) {
4004
+ const padded = result.file.padEnd(maxLen);
4005
+ lines.push(` ${padded} -> ${result.code} files, ${result.tests} tests, ${result.docs} docs`);
4006
+ }
4007
+ return lines.join("\n");
4008
+ }
4009
+ async function runImpactPreview(options) {
4010
+ const projectPath = path33.resolve(options.path ?? process.cwd());
4011
+ const stagedFiles = getStagedFiles(projectPath);
4012
+ if (stagedFiles.length === 0) {
4013
+ return "Impact Preview: no staged changes";
4014
+ }
4015
+ if (!graphExists(projectPath)) {
4016
+ return "Impact Preview: skipped (no graph \u2014 run `harness scan` to enable)";
4017
+ }
4018
+ const mode = options.detailed ? "detailed" : "summary";
4019
+ const perFileResults = [];
4020
+ const allGroups = [];
4021
+ const aggregateCounts = { code: 0, tests: 0, docs: 0, other: 0 };
4022
+ for (const file of stagedFiles) {
4023
+ const response = await handleGetImpact({
4024
+ path: projectPath,
4025
+ filePath: file,
4026
+ mode: options.perFile ? "summary" : mode
4027
+ });
4028
+ const parsed = parseImpactResponse(response);
4029
+ if (!parsed) continue;
4030
+ aggregateCounts.code += parsed.counts.code;
4031
+ aggregateCounts.tests += parsed.counts.tests;
4032
+ aggregateCounts.docs += parsed.counts.docs;
4033
+ aggregateCounts.other += parsed.counts.other;
4034
+ if (options.perFile) {
4035
+ perFileResults.push({
4036
+ file,
4037
+ code: parsed.counts.code,
4038
+ tests: parsed.counts.tests,
4039
+ docs: parsed.counts.docs
4040
+ });
4041
+ }
4042
+ allGroups.push(parsed.items);
4043
+ }
4044
+ if (options.perFile) {
4045
+ if (perFileResults.length === 0) {
4046
+ return `Impact Preview (${stagedFiles.length} staged file${stagedFiles.length === 1 ? "" : "s"}): no impact data`;
4047
+ }
4048
+ return formatPerFile(perFileResults);
4049
+ }
4050
+ const merged = mergeImpactGroups(allGroups);
4051
+ if (options.detailed) {
4052
+ return formatDetailed(stagedFiles.length, merged, aggregateCounts);
4053
+ }
4054
+ return formatCompact(stagedFiles.length, merged, aggregateCounts);
4055
+ }
4056
+ function createImpactPreviewCommand() {
4057
+ 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) => {
4058
+ const output = await runImpactPreview({
4059
+ detailed: opts.detailed,
4060
+ perFile: opts.perFile,
4061
+ path: opts.path
4062
+ });
4063
+ console.log(output);
4064
+ process.exit(0);
4065
+ });
4066
+ return command;
4067
+ }
4068
+
4069
+ // src/commands/check-arch.ts
4070
+ import { Command as Command45 } from "commander";
4071
+ import { execSync as execSync4 } from "child_process";
4072
+ function getCommitHash(cwd) {
4073
+ try {
4074
+ return execSync4("git rev-parse --short HEAD", { cwd, encoding: "utf-8" }).toString().trim();
4075
+ } catch {
4076
+ return "unknown";
4077
+ }
4078
+ }
4079
+ function filterByModule(results, modulePath) {
4080
+ const normalized = modulePath.replace(/\/+$/, "");
4081
+ return results.filter((r) => r.scope === normalized || r.scope.startsWith(normalized + "/"));
4082
+ }
4083
+ function findThresholdViolations(results) {
4084
+ const violations = [];
4085
+ for (const result of results) {
4086
+ for (const v of result.violations) {
4087
+ if (v.severity === "error") {
4088
+ violations.push(v);
4089
+ }
4090
+ }
4091
+ }
4092
+ return violations;
4093
+ }
4094
+ async function runCheckArch(options) {
4095
+ const cwd = options.cwd ?? process.cwd();
4096
+ const configResult = resolveConfig(options.configPath);
4097
+ if (!configResult.ok) {
4098
+ return configResult;
4099
+ }
4100
+ const config = configResult.value;
4101
+ const archConfig = config.architecture ?? ArchConfigSchema.parse({});
4102
+ if (!archConfig.enabled) {
4103
+ return Ok({
4104
+ passed: true,
4105
+ mode: "threshold-only",
4106
+ totalViolations: 0,
4107
+ newViolations: [],
4108
+ resolvedViolations: [],
4109
+ preExisting: [],
4110
+ regressions: [],
4111
+ thresholdViolations: []
4112
+ });
4113
+ }
4114
+ let results = await runAll(archConfig, cwd);
4115
+ if (options.module) {
4116
+ results = filterByModule(results, options.module);
4117
+ }
4118
+ const manager = new ArchBaselineManager(cwd, archConfig.baselinePath);
4119
+ if (options.updateBaseline) {
4120
+ const commitHash = getCommitHash(cwd);
4121
+ const baseline2 = manager.capture(results, commitHash);
4122
+ manager.save(baseline2);
4123
+ return Ok({
4124
+ passed: true,
4125
+ mode: "baseline",
4126
+ totalViolations: 0,
4127
+ newViolations: [],
4128
+ resolvedViolations: [],
4129
+ preExisting: [],
4130
+ regressions: [],
4131
+ thresholdViolations: [],
4132
+ baselineUpdated: true
4133
+ });
4134
+ }
4135
+ const thresholdViolations = findThresholdViolations(results);
4136
+ const baseline = manager.load();
4137
+ if (!baseline) {
4138
+ const passed2 = thresholdViolations.length === 0;
4139
+ return Ok({
4140
+ passed: passed2,
4141
+ mode: "threshold-only",
4142
+ totalViolations: thresholdViolations.length,
4143
+ newViolations: [],
4144
+ resolvedViolations: [],
4145
+ preExisting: [],
4146
+ regressions: [],
4147
+ thresholdViolations,
4148
+ warning: "No baseline found. Running in threshold-only mode. Run with --update-baseline to capture current state."
4149
+ });
4150
+ }
4151
+ const diffResult = diff(results, baseline);
4152
+ const passed = diffResult.passed && thresholdViolations.length === 0;
4153
+ return Ok({
4154
+ passed,
4155
+ mode: "baseline",
4156
+ totalViolations: diffResult.newViolations.length + thresholdViolations.length,
4157
+ newViolations: diffResult.newViolations,
4158
+ resolvedViolations: diffResult.resolvedViolations,
4159
+ preExisting: diffResult.preExisting,
4160
+ regressions: diffResult.regressions,
4161
+ thresholdViolations
4162
+ });
4163
+ }
4164
+ function createCheckArchCommand() {
4165
+ 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) => {
4166
+ const globalOpts = cmd.optsWithGlobals();
4167
+ const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : globalOpts.verbose ? OutputMode.VERBOSE : OutputMode.TEXT;
4168
+ const formatter = new OutputFormatter(mode);
4169
+ const result = await runCheckArch({
4170
+ configPath: globalOpts.config,
4171
+ updateBaseline: opts.updateBaseline,
4172
+ json: globalOpts.json,
4173
+ module: opts.module
4174
+ });
4175
+ if (!result.ok) {
4176
+ if (mode === OutputMode.JSON) {
4177
+ console.log(JSON.stringify({ error: result.error.message }));
4178
+ } else {
4179
+ logger.error(result.error.message);
4180
+ }
4181
+ process.exit(result.error.exitCode);
4182
+ }
4183
+ const value = result.value;
4184
+ if (value.warning && mode !== OutputMode.JSON) {
4185
+ logger.warn(value.warning);
4186
+ }
4187
+ if (value.baselineUpdated) {
4188
+ if (mode === OutputMode.JSON) {
4189
+ console.log(JSON.stringify({ baselineUpdated: true }));
4190
+ } else {
4191
+ logger.success("Baseline updated successfully.");
4192
+ }
4193
+ process.exit(ExitCode.SUCCESS);
4194
+ return;
4195
+ }
4196
+ const issues = [
4197
+ ...value.newViolations.map((v) => ({
4198
+ file: v.file,
4199
+ message: `New violation [${v.severity}]: ${v.detail}`
4200
+ })),
4201
+ ...value.thresholdViolations.map((v) => ({
4202
+ file: v.file,
4203
+ message: `Threshold exceeded: ${v.detail}`
4204
+ })),
4205
+ ...value.regressions.map((r) => ({
4206
+ message: `Regression in ${r.category}: ${r.baselineValue} -> ${r.currentValue} (+${r.delta})`
4207
+ }))
4208
+ ];
4209
+ if (mode === OutputMode.JSON) {
4210
+ console.log(JSON.stringify(value, null, 2));
4211
+ } else {
4212
+ if (value.resolvedViolations.length > 0 && mode !== OutputMode.QUIET) {
4213
+ logger.success(
4214
+ `${value.resolvedViolations.length} violation(s) resolved since baseline.`
4215
+ );
4216
+ }
4217
+ const output = formatter.formatValidation({
4218
+ valid: value.passed,
4219
+ issues
4220
+ });
4221
+ if (output) {
4222
+ console.log(output);
4223
+ }
4224
+ }
4225
+ process.exit(value.passed ? ExitCode.SUCCESS : ExitCode.VALIDATION_FAILED);
4226
+ });
4227
+ return command;
4228
+ }
4229
+
4230
+ // src/commands/blueprint.ts
4231
+ import { Command as Command46 } from "commander";
4232
+ import * as path34 from "path";
4233
+ function createBlueprintCommand() {
4234
+ 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) => {
4235
+ try {
4236
+ const rootDir = path34.resolve(projectPath);
4237
+ const outputDir = path34.resolve(options.output);
4238
+ logger.info(`Scanning project at ${rootDir}...`);
4239
+ const scanner = new ProjectScanner(rootDir);
4240
+ const data = await scanner.scan();
4241
+ logger.info(`Generating blueprint to ${outputDir}...`);
4242
+ const generator = new BlueprintGenerator();
4243
+ await generator.generate(data, { outputDir });
4244
+ logger.success(`Blueprint generated successfully at ${path34.join(outputDir, "index.html")}`);
4245
+ } catch (error) {
4246
+ logger.error(
4247
+ `Failed to generate blueprint: ${error instanceof Error ? error.message : String(error)}`
4248
+ );
4249
+ process.exit(1);
4250
+ }
4251
+ });
4252
+ }
4253
+
4254
+ // src/commands/share.ts
4255
+ import { Command as Command47 } from "commander";
4256
+ import * as fs17 from "fs";
4257
+ import * as path35 from "path";
4258
+ import { parse as parseYaml } from "yaml";
4259
+ var MANIFEST_FILENAME = "constraints.yaml";
4260
+ function createShareCommand() {
4261
+ 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) => {
4262
+ const rootDir = path35.resolve(projectPath);
4263
+ const manifestPath = path35.join(rootDir, MANIFEST_FILENAME);
4264
+ if (!fs17.existsSync(manifestPath)) {
4265
+ logger.error(
4266
+ `No ${MANIFEST_FILENAME} found at ${manifestPath}.
4267
+ Create a constraints.yaml in your project root to define what to share.`
4268
+ );
4269
+ process.exit(1);
4270
+ }
4271
+ let parsed;
4272
+ try {
4273
+ const raw = fs17.readFileSync(manifestPath, "utf-8");
4274
+ parsed = parseYaml(raw);
4275
+ } catch (err) {
4276
+ logger.error(
4277
+ `Failed to read ${MANIFEST_FILENAME}: ${err instanceof Error ? err.message : String(err)}`
4278
+ );
4279
+ process.exit(1);
4280
+ }
4281
+ const manifestResult = parseManifest(parsed);
4282
+ if (!manifestResult.ok) {
4283
+ logger.error(`Invalid ${MANIFEST_FILENAME}: ${manifestResult.error}`);
4284
+ process.exit(1);
4285
+ }
4286
+ const manifest = manifestResult.value;
4287
+ const configResult = resolveConfig(path35.join(rootDir, "harness.config.json"));
4288
+ if (!configResult.ok) {
4289
+ logger.error(configResult.error.message);
4290
+ process.exit(1);
4291
+ }
4292
+ const config = configResult.value;
4293
+ const bundleResult = extractBundle(manifest, config);
4294
+ if (!bundleResult.ok) {
4295
+ logger.error(`Failed to extract bundle: ${bundleResult.error}`);
4296
+ process.exit(1);
4297
+ }
4298
+ const bundle = bundleResult.value;
4299
+ if (Object.keys(bundle.constraints).length === 0) {
4300
+ logger.error(
4301
+ "No constraints found for the include paths in constraints.yaml.\nCheck that your harness config contains the declared sections."
4302
+ );
4303
+ process.exit(1);
4304
+ }
4305
+ const outputDir = path35.resolve(options.output);
4306
+ const outputPath = path35.join(outputDir, `${manifest.name}.harness-constraints.json`);
4307
+ try {
4308
+ await writeConfig(outputPath, bundle);
4309
+ } catch (err) {
4310
+ logger.error(`Failed to write bundle: ${err instanceof Error ? err.message : String(err)}`);
4311
+ process.exit(1);
4312
+ }
4313
+ logger.success(`Bundle written to ${outputPath}`);
4314
+ });
4315
+ }
4316
+
4317
+ // src/commands/install.ts
4318
+ import * as fs19 from "fs";
4319
+ import * as path37 from "path";
4320
+ import { Command as Command48 } from "commander";
4321
+ import { parse as yamlParse } from "yaml";
4322
+
4323
+ // src/registry/tarball.ts
4324
+ import * as fs18 from "fs";
4325
+ import * as path36 from "path";
4326
+ import * as os2 from "os";
4327
+ import { execFileSync as execFileSync5 } from "child_process";
4328
+ function extractTarball(tarballBuffer) {
4329
+ const tmpDir = fs18.mkdtempSync(path36.join(os2.tmpdir(), "harness-skill-install-"));
4330
+ const tarballPath = path36.join(tmpDir, "package.tgz");
4331
+ try {
4332
+ fs18.writeFileSync(tarballPath, tarballBuffer);
4333
+ execFileSync5("tar", ["-xzf", tarballPath, "-C", tmpDir], {
4334
+ timeout: 3e4
4335
+ });
4336
+ fs18.unlinkSync(tarballPath);
4337
+ } catch (err) {
4338
+ cleanupTempDir(tmpDir);
4339
+ throw new Error(
4340
+ `Failed to extract tarball: ${err instanceof Error ? err.message : String(err)}`,
4341
+ { cause: err }
4342
+ );
4343
+ }
4344
+ return tmpDir;
4345
+ }
4346
+ function placeSkillContent(extractedPkgDir, communityBaseDir, skillName, platforms) {
4347
+ const files = fs18.readdirSync(extractedPkgDir);
4348
+ for (const platform of platforms) {
4349
+ const targetDir = path36.join(communityBaseDir, platform, skillName);
4350
+ if (fs18.existsSync(targetDir)) {
4351
+ fs18.rmSync(targetDir, { recursive: true, force: true });
4352
+ }
4353
+ fs18.mkdirSync(targetDir, { recursive: true });
4354
+ for (const file of files) {
4355
+ if (file === "package.json" || file === "node_modules") continue;
4356
+ const srcPath = path36.join(extractedPkgDir, file);
4357
+ const destPath = path36.join(targetDir, file);
4358
+ const stat = fs18.statSync(srcPath);
4359
+ if (stat.isDirectory()) {
4360
+ fs18.cpSync(srcPath, destPath, { recursive: true });
4361
+ } else {
4362
+ fs18.copyFileSync(srcPath, destPath);
4363
+ }
4364
+ }
4365
+ }
4366
+ }
4367
+ function removeSkillContent(communityBaseDir, skillName, platforms) {
4368
+ for (const platform of platforms) {
4369
+ const targetDir = path36.join(communityBaseDir, platform, skillName);
4370
+ if (fs18.existsSync(targetDir)) {
4371
+ fs18.rmSync(targetDir, { recursive: true, force: true });
4372
+ }
4373
+ }
4374
+ }
4375
+ function cleanupTempDir(dirPath) {
4376
+ try {
4377
+ fs18.rmSync(dirPath, { recursive: true, force: true });
4378
+ } catch {
4379
+ }
4380
+ }
4381
+
4382
+ // src/registry/resolver.ts
4383
+ import semver2 from "semver";
4384
+ function resolveVersion(metadata, versionRange) {
4385
+ const versions = Object.keys(metadata.versions);
4386
+ if (versions.length === 0) {
4387
+ throw new Error(`No versions available for ${metadata.name}.`);
4388
+ }
4389
+ if (!versionRange) {
4390
+ const latestTag = metadata["dist-tags"].latest;
4391
+ if (latestTag) {
4392
+ const latestInfo = metadata.versions[latestTag];
4393
+ if (latestInfo) return latestInfo;
4394
+ }
4395
+ const highest = semver2.maxSatisfying(versions, "*");
4396
+ if (!highest || !metadata.versions[highest]) {
4397
+ throw new Error(`No versions available for ${metadata.name}.`);
4398
+ }
4399
+ return metadata.versions[highest];
4400
+ }
4401
+ const matched = semver2.maxSatisfying(versions, versionRange);
4402
+ if (!matched || !metadata.versions[matched]) {
4403
+ throw new Error(
4404
+ `No version of ${metadata.name} matches range ${versionRange}. Available: ${versions.join(", ")}`
4405
+ );
4406
+ }
4407
+ return metadata.versions[matched];
4408
+ }
4409
+ function findDependentsOf(lockfile, targetPackageName) {
4410
+ const entry = lockfile.skills[targetPackageName];
4411
+ if (!entry?.dependencyOf) return [];
4412
+ return [entry.dependencyOf];
4413
+ }
4414
+
4415
+ // src/commands/install.ts
4416
+ function validateSkillYaml(parsed) {
4417
+ if (!parsed || typeof parsed !== "object" || !("name" in parsed) || !("version" in parsed) || !("platforms" in parsed)) {
4418
+ throw new Error("contains invalid skill.yaml");
4419
+ }
4420
+ const obj = parsed;
4421
+ if (typeof obj["name"] !== "string" || typeof obj["version"] !== "string" || !Array.isArray(obj["platforms"])) {
4422
+ throw new Error("contains invalid skill.yaml");
4423
+ }
4424
+ return {
4425
+ name: obj["name"],
4426
+ version: obj["version"],
4427
+ platforms: obj["platforms"],
4428
+ depends_on: Array.isArray(obj["depends_on"]) ? obj["depends_on"] : []
4429
+ };
4430
+ }
4431
+ async function runInstall(skillName, options) {
4432
+ const packageName = resolvePackageName(skillName);
4433
+ const shortName = extractSkillName(packageName);
4434
+ const globalDir = resolveGlobalSkillsDir();
4435
+ const skillsDir = path37.dirname(globalDir);
4436
+ const communityBase = path37.join(skillsDir, "community");
4437
+ const lockfilePath = path37.join(communityBase, "skills-lock.json");
4438
+ const bundledNames = getBundledSkillNames(globalDir);
4439
+ if (bundledNames.has(shortName)) {
4440
+ throw new Error(
4441
+ `'${shortName}' is a bundled skill and cannot be overridden by community installs.`
4442
+ );
4443
+ }
4444
+ const metadata = await fetchPackageMetadata(packageName);
4445
+ const versionInfo = resolveVersion(metadata, options.version);
4446
+ const resolvedVersion = versionInfo.version;
4447
+ const lockfile = readLockfile(lockfilePath);
4448
+ const existingEntry = lockfile.skills[packageName];
4449
+ const previousVersion = existingEntry?.version;
4450
+ if (existingEntry && existingEntry.version === resolvedVersion && !options.force) {
4451
+ return {
4452
+ installed: false,
4453
+ skipped: true,
4454
+ name: packageName,
4455
+ version: resolvedVersion
4456
+ };
4457
+ }
4458
+ const tarballBuffer = await downloadTarball(versionInfo.dist.tarball);
4459
+ const extractDir = extractTarball(tarballBuffer);
4460
+ let skillYaml;
4461
+ try {
4462
+ const extractedPkgDir = path37.join(extractDir, "package");
4463
+ const skillYamlPath = path37.join(extractedPkgDir, "skill.yaml");
4464
+ if (!fs19.existsSync(skillYamlPath)) {
4465
+ throw new Error(`contains invalid skill.yaml: file not found in package`);
4466
+ }
4467
+ const rawYaml = fs19.readFileSync(skillYamlPath, "utf-8");
4468
+ const parsed = yamlParse(rawYaml);
4469
+ skillYaml = validateSkillYaml(parsed);
4470
+ placeSkillContent(extractedPkgDir, communityBase, shortName, skillYaml.platforms);
4471
+ } catch (err) {
4472
+ cleanupTempDir(extractDir);
4473
+ throw err;
4474
+ }
4475
+ cleanupTempDir(extractDir);
4476
+ const entry = {
4477
+ version: resolvedVersion,
4478
+ resolved: versionInfo.dist.tarball,
4479
+ integrity: versionInfo.dist.integrity,
4480
+ platforms: skillYaml.platforms,
4481
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
4482
+ dependencyOf: null
4483
+ };
4484
+ let updatedLockfile = updateLockfileEntry(lockfile, packageName, entry);
4485
+ writeLockfile(lockfilePath, updatedLockfile);
4486
+ const result = {
4487
+ installed: true,
4488
+ name: packageName,
4489
+ version: resolvedVersion
4490
+ };
4491
+ if (previousVersion && previousVersion !== resolvedVersion) {
4492
+ result.upgraded = true;
4493
+ result.previousVersion = previousVersion;
4494
+ }
4495
+ const deps = skillYaml.depends_on ?? [];
4496
+ for (const dep of deps) {
4497
+ logger.info(`Installing dependency: ${dep}`);
4498
+ await runInstall(dep, {});
4499
+ }
4500
+ return result;
4501
+ }
4502
+ function createInstallCommand() {
4503
+ const cmd = new Command48("install");
4504
+ 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").action(async (skill, opts) => {
4505
+ try {
4506
+ const result = await runInstall(skill, opts);
4507
+ if (result.skipped) {
4508
+ logger.info(
4509
+ `${result.name}@${result.version} is already installed. Use --force to reinstall.`
4510
+ );
4511
+ } else if (result.upgraded) {
4512
+ logger.success(
4513
+ `Upgraded ${result.name} from ${result.previousVersion} to ${result.version}`
4514
+ );
4515
+ } else {
4516
+ logger.success(`Installed ${result.name}@${result.version}`);
4517
+ }
4518
+ } catch (err) {
4519
+ logger.error(err instanceof Error ? err.message : String(err));
4520
+ process.exit(1);
4521
+ }
4522
+ });
4523
+ return cmd;
4524
+ }
4525
+
4526
+ // src/commands/uninstall.ts
4527
+ import * as path38 from "path";
4528
+ import { Command as Command49 } from "commander";
4529
+ async function runUninstall(skillName, options) {
4530
+ const packageName = resolvePackageName(skillName);
4531
+ const shortName = extractSkillName(packageName);
4532
+ const globalDir = resolveGlobalSkillsDir();
4533
+ const skillsDir = path38.dirname(globalDir);
4534
+ const communityBase = path38.join(skillsDir, "community");
4535
+ const lockfilePath = path38.join(communityBase, "skills-lock.json");
4536
+ const lockfile = readLockfile(lockfilePath);
4537
+ const entry = lockfile.skills[packageName];
4538
+ if (!entry) {
4539
+ throw new Error(`Skill '${shortName}' is not installed.`);
4540
+ }
4541
+ const dependents = findDependentsOf(lockfile, packageName);
4542
+ const warnings = [];
4543
+ if (dependents.length > 0) {
4544
+ if (!options.force) {
4545
+ throw new Error(
4546
+ `Cannot uninstall '${shortName}' because it is required by: ${dependents.join(", ")}. Use --force to remove anyway.`
4547
+ );
4548
+ }
4549
+ warnings.push(`Forced removal despite dependents: ${dependents.join(", ")}`);
4550
+ }
4551
+ removeSkillContent(communityBase, shortName, entry.platforms);
4552
+ const updatedLockfile = removeLockfileEntry(lockfile, packageName);
4553
+ writeLockfile(lockfilePath, updatedLockfile);
4554
+ const result = {
4555
+ removed: true,
4556
+ name: packageName,
4557
+ version: entry.version
4558
+ };
4559
+ if (warnings.length > 0) {
4560
+ result.warnings = warnings;
4561
+ }
4562
+ return result;
4563
+ }
4564
+ function createUninstallCommand() {
4565
+ const cmd = new Command49("uninstall");
4566
+ 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) => {
4567
+ try {
4568
+ const result = await runUninstall(skill, opts);
4569
+ if (result.warnings) {
4570
+ for (const warning of result.warnings) {
4571
+ logger.warn(warning);
4572
+ }
4573
+ }
4574
+ logger.success(`Uninstalled ${result.name}@${result.version}`);
4575
+ } catch (err) {
4576
+ logger.error(err instanceof Error ? err.message : String(err));
4577
+ process.exit(1);
4578
+ }
4579
+ });
4580
+ return cmd;
4581
+ }
4582
+
4583
+ // src/commands/orchestrator.ts
4584
+ import { Command as Command50 } from "commander";
4585
+ import * as path39 from "path";
4586
+ import { Orchestrator, WorkflowLoader, launchTUI } from "@harness-engineering/orchestrator";
4587
+ function createOrchestratorCommand() {
4588
+ const orchestrator = new Command50("orchestrator");
4589
+ orchestrator.command("run").description("Run the orchestrator daemon").option("-w, --workflow <path>", "Path to WORKFLOW.md", "WORKFLOW.md").action(async (opts) => {
4590
+ const workflowPath = path39.resolve(process.cwd(), opts.workflow);
4591
+ const loader = new WorkflowLoader();
4592
+ const result = await loader.loadWorkflow(workflowPath);
4593
+ if (!result.ok) {
4594
+ logger.error(`Failed to load workflow: ${result.error.message}`);
4595
+ process.exit(ExitCode.ERROR);
4596
+ }
4597
+ const { config, promptTemplate } = result.value;
4598
+ const daemon = new Orchestrator(config, promptTemplate);
4599
+ const shutdown = () => {
4600
+ daemon.stop();
4601
+ process.exit(ExitCode.SUCCESS);
4602
+ };
4603
+ process.on("SIGINT", shutdown);
4604
+ process.on("SIGTERM", shutdown);
4605
+ daemon.start();
4606
+ const { waitUntilExit } = launchTUI(daemon);
4607
+ await waitUntilExit();
4608
+ process.exit(ExitCode.SUCCESS);
4609
+ });
4610
+ return orchestrator;
4611
+ }
4612
+
3211
4613
  // src/index.ts
3212
4614
  function createProgram() {
3213
- const program = new Command41();
4615
+ const program = new Command51();
3214
4616
  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");
3215
4617
  program.addCommand(createValidateCommand());
3216
4618
  program.addCommand(createCheckDepsCommand());
@@ -3240,6 +4642,13 @@ function createProgram() {
3240
4642
  program.addCommand(createQueryCommand());
3241
4643
  program.addCommand(createGraphCommand());
3242
4644
  program.addCommand(createMcpCommand());
4645
+ program.addCommand(createImpactPreviewCommand());
4646
+ program.addCommand(createCheckArchCommand());
4647
+ program.addCommand(createBlueprintCommand());
4648
+ program.addCommand(createShareCommand());
4649
+ program.addCommand(createInstallCommand());
4650
+ program.addCommand(createUninstallCommand());
4651
+ program.addCommand(createOrchestratorCommand());
3243
4652
  return program;
3244
4653
  }
3245
4654
 
@@ -3250,5 +4659,9 @@ export {
3250
4659
  runQuery,
3251
4660
  runGraphStatus,
3252
4661
  runGraphExport,
4662
+ runImpactPreview,
4663
+ runCheckArch,
4664
+ runInstall,
4665
+ runUninstall,
3253
4666
  createProgram
3254
4667
  };