@harness-engineering/cli 1.21.0 → 1.22.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 (107) hide show
  1. package/dist/agents/skills/claude-code/cleanup-dead-code/skill.yaml +3 -0
  2. package/dist/agents/skills/claude-code/detect-doc-drift/skill.yaml +5 -0
  3. package/dist/agents/skills/claude-code/enforce-architecture/skill.yaml +13 -0
  4. package/dist/agents/skills/claude-code/harness-code-review/skill.yaml +5 -0
  5. package/dist/agents/skills/claude-code/harness-codebase-cleanup/skill.yaml +5 -0
  6. package/dist/agents/skills/claude-code/harness-debugging/skill.yaml +5 -0
  7. package/dist/agents/skills/claude-code/harness-dependency-health/skill.yaml +9 -0
  8. package/dist/agents/skills/claude-code/harness-hotspot-detector/skill.yaml +9 -0
  9. package/dist/agents/skills/claude-code/harness-integrity/skill.yaml +5 -0
  10. package/dist/agents/skills/claude-code/harness-perf/skill.yaml +3 -0
  11. package/dist/agents/skills/claude-code/harness-refactoring/skill.yaml +9 -0
  12. package/dist/agents/skills/claude-code/harness-security-scan/skill.yaml +3 -0
  13. package/dist/agents/skills/claude-code/harness-soundness-review/skill.yaml +5 -0
  14. package/dist/agents/skills/claude-code/harness-supply-chain-audit/skill.yaml +3 -0
  15. package/dist/agents/skills/claude-code/harness-tdd/skill.yaml +3 -0
  16. package/dist/agents/skills/codex/cleanup-dead-code/skill.yaml +3 -0
  17. package/dist/agents/skills/codex/detect-doc-drift/skill.yaml +5 -0
  18. package/dist/agents/skills/codex/enforce-architecture/skill.yaml +13 -0
  19. package/dist/agents/skills/codex/harness-code-review/skill.yaml +5 -0
  20. package/dist/agents/skills/codex/harness-codebase-cleanup/skill.yaml +5 -0
  21. package/dist/agents/skills/codex/harness-debugging/skill.yaml +5 -0
  22. package/dist/agents/skills/codex/harness-dependency-health/skill.yaml +9 -0
  23. package/dist/agents/skills/codex/harness-hotspot-detector/skill.yaml +9 -0
  24. package/dist/agents/skills/codex/harness-integrity/skill.yaml +5 -0
  25. package/dist/agents/skills/codex/harness-perf/skill.yaml +3 -0
  26. package/dist/agents/skills/codex/harness-refactoring/skill.yaml +9 -0
  27. package/dist/agents/skills/codex/harness-security-scan/skill.yaml +3 -0
  28. package/dist/agents/skills/codex/harness-soundness-review/skill.yaml +5 -0
  29. package/dist/agents/skills/codex/harness-supply-chain-audit/skill.yaml +3 -0
  30. package/dist/agents/skills/codex/harness-tdd/skill.yaml +3 -0
  31. package/dist/agents/skills/cursor/cleanup-dead-code/skill.yaml +3 -0
  32. package/dist/agents/skills/cursor/detect-doc-drift/skill.yaml +5 -0
  33. package/dist/agents/skills/cursor/enforce-architecture/skill.yaml +13 -0
  34. package/dist/agents/skills/cursor/harness-code-review/skill.yaml +5 -0
  35. package/dist/agents/skills/cursor/harness-codebase-cleanup/skill.yaml +5 -0
  36. package/dist/agents/skills/cursor/harness-debugging/skill.yaml +5 -0
  37. package/dist/agents/skills/cursor/harness-dependency-health/skill.yaml +9 -0
  38. package/dist/agents/skills/cursor/harness-hotspot-detector/skill.yaml +9 -0
  39. package/dist/agents/skills/cursor/harness-integrity/skill.yaml +5 -0
  40. package/dist/agents/skills/cursor/harness-perf/skill.yaml +3 -0
  41. package/dist/agents/skills/cursor/harness-refactoring/skill.yaml +9 -0
  42. package/dist/agents/skills/cursor/harness-security-scan/skill.yaml +3 -0
  43. package/dist/agents/skills/cursor/harness-soundness-review/skill.yaml +5 -0
  44. package/dist/agents/skills/cursor/harness-supply-chain-audit/skill.yaml +3 -0
  45. package/dist/agents/skills/cursor/harness-tdd/skill.yaml +3 -0
  46. package/dist/agents/skills/gemini-cli/cleanup-dead-code/skill.yaml +3 -0
  47. package/dist/agents/skills/gemini-cli/detect-doc-drift/skill.yaml +5 -0
  48. package/dist/agents/skills/gemini-cli/enforce-architecture/skill.yaml +13 -0
  49. package/dist/agents/skills/gemini-cli/harness-code-review/skill.yaml +5 -0
  50. package/dist/agents/skills/gemini-cli/harness-codebase-cleanup/skill.yaml +5 -0
  51. package/dist/agents/skills/gemini-cli/harness-debugging/skill.yaml +5 -0
  52. package/dist/agents/skills/gemini-cli/harness-dependency-health/skill.yaml +9 -0
  53. package/dist/agents/skills/gemini-cli/harness-hotspot-detector/skill.yaml +9 -0
  54. package/dist/agents/skills/gemini-cli/harness-integrity/skill.yaml +5 -0
  55. package/dist/agents/skills/gemini-cli/harness-perf/skill.yaml +3 -0
  56. package/dist/agents/skills/gemini-cli/harness-refactoring/skill.yaml +9 -0
  57. package/dist/agents/skills/gemini-cli/harness-security-scan/skill.yaml +3 -0
  58. package/dist/agents/skills/gemini-cli/harness-soundness-review/skill.yaml +5 -0
  59. package/dist/agents/skills/gemini-cli/harness-supply-chain-audit/skill.yaml +3 -0
  60. package/dist/agents/skills/gemini-cli/harness-tdd/skill.yaml +3 -0
  61. package/dist/{agents-md-TDTLYAQU.js → agents-md-PM7LO74M.js} +2 -1
  62. package/dist/{architecture-NANP4XPE.js → architecture-OVOCDTI6.js} +3 -2
  63. package/dist/assess-project-R2OZIDDS.js +9 -0
  64. package/dist/bin/harness-mcp.js +15 -13
  65. package/dist/bin/harness.js +21 -19
  66. package/dist/{check-phase-gate-I4NQOCSU.js → check-phase-gate-7JQ6EW5R.js} +4 -3
  67. package/dist/{chunk-M6TIO6NF.js → chunk-2PAPHA77.js} +1 -1
  68. package/dist/chunk-5FBWWMY2.js +293 -0
  69. package/dist/{chunk-YF5ROTWR.js → chunk-5QTWFO24.js} +8 -8
  70. package/dist/{chunk-CZZXE6BL.js → chunk-ASS5TD2Y.js} +1 -1
  71. package/dist/{chunk-L6LTNZQZ.js → chunk-B4WHXHF7.js} +1 -1
  72. package/dist/{chunk-TMSGI27F.js → chunk-DJEBBENF.js} +967 -385
  73. package/dist/{chunk-H6LXAH66.js → chunk-DXYOAQQC.js} +1 -1
  74. package/dist/{chunk-UVJFBKCX.js → chunk-FSLFBLYW.js} +7 -7
  75. package/dist/{chunk-SZ5TGZMI.js → chunk-GRJ7A4WT.js} +17 -2
  76. package/dist/{chunk-WXI5ONCU.js → chunk-IK5GSLW6.js} +4 -4
  77. package/dist/{chunk-SPUK5W4W.js → chunk-J7W4LTRK.js} +2 -2
  78. package/dist/{chunk-7G2ZUTZA.js → chunk-PUOMFNRO.js} +26 -3
  79. package/dist/{chunk-HKUX2X7O.js → chunk-SE4YPMLH.js} +9 -1
  80. package/dist/{chunk-UEKQ5G3V.js → chunk-SOTTK27D.js} +459 -37
  81. package/dist/{chunk-LRG3B43J.js → chunk-T5QWCVGK.js} +1 -1
  82. package/dist/{dist-U7EAO6T2.js → chunk-TEZI27SA.js} +401 -60
  83. package/dist/{chunk-YZYBQZVL.js → chunk-U44JNY3Y.js} +1539 -587
  84. package/dist/{chunk-HUDEBSR2.js → chunk-W6MPLFXU.js} +3 -3
  85. package/dist/{chunk-6GEYPBDU.js → chunk-ZEIEUCZL.js} +9 -9
  86. package/dist/{ci-workflow-Z4IUJBZL.js → ci-workflow-OTTEERPF.js} +2 -1
  87. package/dist/{create-skill-NDXQSTIK.js → create-skill-U3XCFRZN.js} +2 -2
  88. package/dist/dist-IA6XYKNO.js +92 -0
  89. package/dist/{dist-KV2ICL5X.js → dist-LCR2IO7U.js} +56 -3
  90. package/dist/{docs-2PCZVSGB.js → docs-CHAYSGOP.js} +4 -3
  91. package/dist/{engine-EOXMI5MD.js → engine-4MY2U5RZ.js} +2 -1
  92. package/dist/{entropy-VGXXBIGX.js → entropy-AKSZG7G5.js} +3 -2
  93. package/dist/{feedback-VTSPL3O7.js → feedback-QGCSW7SB.js} +1 -1
  94. package/dist/{generate-agent-definitions-QICSCGXB.js → generate-agent-definitions-KU6X2UQN.js} +2 -1
  95. package/dist/{graph-loader-KMHDQYDT.js → graph-loader-FJN4H7Y4.js} +1 -1
  96. package/dist/index.d.ts +58 -2
  97. package/dist/index.js +27 -23
  98. package/dist/{loader-7S4FYAPP.js → loader-AV5XEMER.js} +2 -1
  99. package/dist/{mcp-DF25USTE.js → mcp-LWHVQRG7.js} +15 -13
  100. package/dist/{performance-RV4DUMFI.js → performance-ETZVXXGQ.js} +4 -3
  101. package/dist/{review-pipeline-7KQJB4SI.js → review-pipeline-3ZS3GJSP.js} +1 -1
  102. package/dist/{runtime-XKOHGGRC.js → runtime-KQTJRK3H.js} +2 -1
  103. package/dist/{security-NLWTMK3G.js → security-LJCLZES6.js} +1 -1
  104. package/dist/{skill-executor-XEVDGXUM.js → skill-executor-2BZQLHYN.js} +2 -2
  105. package/dist/{validate-VHFE6J6O.js → validate-4IA5RPEX.js} +3 -2
  106. package/dist/{validate-cross-check-PFRKABCS.js → validate-cross-check-VX2BAHQI.js} +2 -1
  107. package/package.json +4 -4
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  detectEntropyDefinition,
3
3
  handleDetectEntropy
4
- } from "./chunk-WXI5ONCU.js";
4
+ } from "./chunk-IK5GSLW6.js";
5
5
  import {
6
6
  checkPerformanceDefinition,
7
7
  getCriticalPathsDefinition,
@@ -11,7 +11,7 @@ import {
11
11
  handleGetPerfBaselines,
12
12
  handleUpdatePerfBaselines,
13
13
  updatePerfBaselinesDefinition
14
- } from "./chunk-UVJFBKCX.js";
14
+ } from "./chunk-FSLFBLYW.js";
15
15
  import {
16
16
  analyzeDiffDefinition,
17
17
  createSelfReviewDefinition,
@@ -19,15 +19,19 @@ import {
19
19
  handleCreateSelfReview,
20
20
  handleRequestPeerReview,
21
21
  requestPeerReviewDefinition
22
- } from "./chunk-6GEYPBDU.js";
22
+ } from "./chunk-ZEIEUCZL.js";
23
23
  import {
24
24
  handleRunSecurityScan,
25
25
  runSecurityScanDefinition
26
- } from "./chunk-H6LXAH66.js";
26
+ } from "./chunk-DXYOAQQC.js";
27
27
  import {
28
28
  handleRunCodeReview,
29
29
  runCodeReviewDefinition
30
- } from "./chunk-L6LTNZQZ.js";
30
+ } from "./chunk-B4WHXHF7.js";
31
+ import {
32
+ assessProjectDefinition,
33
+ handleAssessProject
34
+ } from "./chunk-5FBWWMY2.js";
31
35
  import {
32
36
  GENERATED_HEADER_CLAUDE,
33
37
  GENERATED_HEADER_CODEX,
@@ -40,25 +44,25 @@ import {
40
44
  import {
41
45
  handleValidateProject,
42
46
  validateToolDefinition
43
- } from "./chunk-SPUK5W4W.js";
47
+ } from "./chunk-J7W4LTRK.js";
44
48
  import {
45
49
  loadGraphStore
46
- } from "./chunk-CZZXE6BL.js";
50
+ } from "./chunk-ASS5TD2Y.js";
47
51
  import {
48
52
  checkDependenciesDefinition,
49
53
  handleCheckDependencies
50
- } from "./chunk-HUDEBSR2.js";
54
+ } from "./chunk-W6MPLFXU.js";
51
55
  import {
52
56
  resolveProjectConfig
53
57
  } from "./chunk-H7Y5CKTM.js";
54
58
  import {
55
59
  checkDocsDefinition,
56
60
  handleCheckDocs
57
- } from "./chunk-YF5ROTWR.js";
61
+ } from "./chunk-5QTWFO24.js";
58
62
  import {
59
63
  TrackerConfigSchema,
60
64
  resolveConfig
61
- } from "./chunk-SZ5TGZMI.js";
65
+ } from "./chunk-GRJ7A4WT.js";
62
66
  import {
63
67
  resultToMcpResponse
64
68
  } from "./chunk-IDZNPTYD.js";
@@ -81,13 +85,13 @@ import {
81
85
  } from "./chunk-3WGJMBKH.js";
82
86
  import {
83
87
  SkillMetadataSchema
84
- } from "./chunk-HKUX2X7O.js";
88
+ } from "./chunk-SE4YPMLH.js";
85
89
  import {
86
90
  DESTRUCTIVE_BASH,
87
91
  checkTaint,
88
92
  scanForInjection,
89
93
  writeTaint
90
- } from "./chunk-YZYBQZVL.js";
94
+ } from "./chunk-U44JNY3Y.js";
91
95
  import {
92
96
  Err,
93
97
  Ok
@@ -557,7 +561,7 @@ ${skippedMsg}`
557
561
  async function handleInitProject(input) {
558
562
  const i = input;
559
563
  try {
560
- const { TemplateEngine } = await import("./engine-EOXMI5MD.js");
564
+ const { TemplateEngine } = await import("./engine-4MY2U5RZ.js");
561
565
  const engine = new TemplateEngine(resolveTemplatesDir());
562
566
  const safePath = sanitizePath(i.path);
563
567
  const detected = tryDetectFramework(engine, safePath, i);
@@ -578,7 +582,7 @@ var listPersonasDefinition = {
578
582
  inputSchema: { type: "object", properties: {} }
579
583
  };
580
584
  async function handleListPersonas() {
581
- const { listPersonas } = await import("./loader-7S4FYAPP.js");
585
+ const { listPersonas } = await import("./loader-AV5XEMER.js");
582
586
  const result = listPersonas(resolvePersonasDir());
583
587
  return resultToMcpResponse(result);
584
588
  }
@@ -602,10 +606,10 @@ async function handleGeneratePersonaArtifacts(input) {
602
606
  if (!/^[a-z0-9][a-z0-9._-]*$/i.test(input.name)) {
603
607
  return resultToMcpResponse(Err(new Error(`Invalid persona name: ${input.name}`)));
604
608
  }
605
- const { loadPersona } = await import("./loader-7S4FYAPP.js");
606
- const { generateRuntime } = await import("./runtime-XKOHGGRC.js");
607
- const { generateAgentsMd } = await import("./agents-md-TDTLYAQU.js");
608
- const { generateCIWorkflow } = await import("./ci-workflow-Z4IUJBZL.js");
609
+ const { loadPersona } = await import("./loader-AV5XEMER.js");
610
+ const { generateRuntime } = await import("./runtime-KQTJRK3H.js");
611
+ const { generateAgentsMd } = await import("./agents-md-PM7LO74M.js");
612
+ const { generateCIWorkflow } = await import("./ci-workflow-OTTEERPF.js");
609
613
  const personasDir = resolvePersonasDir();
610
614
  const filePath = path3.join(personasDir, `${input.name}.yaml`);
611
615
  if (!filePath.startsWith(personasDir)) {
@@ -660,9 +664,9 @@ async function handleRunPersona(input) {
660
664
  if (!/^[a-z0-9][a-z0-9._-]*$/i.test(input.persona)) {
661
665
  return resultToMcpResponse(Err(new Error(`Invalid persona name: ${input.persona}`)));
662
666
  }
663
- const { loadPersona } = await import("./loader-7S4FYAPP.js");
667
+ const { loadPersona } = await import("./loader-AV5XEMER.js");
664
668
  const { runPersona } = await import("./runner-VMYLHWOC.js");
665
- const { executeSkill } = await import("./skill-executor-XEVDGXUM.js");
669
+ const { executeSkill } = await import("./skill-executor-2BZQLHYN.js");
666
670
  const personasDir = resolvePersonasDir();
667
671
  const filePath = path3.join(personasDir, `${input.persona}.yaml`);
668
672
  if (!filePath.startsWith(personasDir)) {
@@ -842,7 +846,26 @@ var TIER_1_SKILLS = /* @__PURE__ */ new Set([
842
846
  function isTier1Skill(skillName) {
843
847
  return TIER_1_SKILLS.has(skillName);
844
848
  }
845
- function scoreSkill(entry, queryTerms, profile, recentFiles, skillName) {
849
+ function computeHealthScore(entry, snapshot) {
850
+ if (entry.addresses.length === 0) return 0;
851
+ const activeSignals = new Set(snapshot.signals);
852
+ const hasWeights = entry.addresses.some((a) => a.weight !== void 0);
853
+ if (hasWeights) {
854
+ let totalWeight = 0;
855
+ let matchedWeight = 0;
856
+ for (const addr of entry.addresses) {
857
+ const w = addr.weight ?? 0.5;
858
+ totalWeight += w;
859
+ if (activeSignals.has(addr.signal)) {
860
+ matchedWeight += w;
861
+ }
862
+ }
863
+ return totalWeight > 0 ? matchedWeight / totalWeight : 0;
864
+ }
865
+ const matched = entry.addresses.filter((a) => activeSignals.has(a.signal)).length;
866
+ return matched / entry.addresses.length;
867
+ }
868
+ function scoreSkill(entry, queryTerms, profile, recentFiles, skillName, healthSnapshot) {
846
869
  const matchedKeywords = entry.keywords.filter(
847
870
  (kw) => queryTerms.some(
848
871
  (term) => kw.toLowerCase().includes(term.toLowerCase()) || term.toLowerCase().includes(kw.toLowerCase())
@@ -881,7 +904,12 @@ function scoreSkill(entry, queryTerms, profile, recentFiles, skillName) {
881
904
  });
882
905
  recencyBoost = hasRecentMatch ? 1 : 0;
883
906
  }
884
- return 0.35 * keywordScore + 0.2 * nameScore + 0.1 * descScore + 0.2 * stackScore + 0.15 * recencyBoost;
907
+ let score = 0.35 * keywordScore + 0.2 * nameScore + 0.1 * descScore + 0.2 * stackScore + 0.15 * recencyBoost;
908
+ if (healthSnapshot) {
909
+ const healthScore = computeHealthScore(entry, healthSnapshot);
910
+ score = 0.7 * score + 0.3 * healthScore;
911
+ }
912
+ return score;
885
913
  }
886
914
  function suggest(index, taskDescription, profile, recentFiles, config) {
887
915
  const queryTerms = taskDescription.toLowerCase().split(/\s+/).filter((t) => t.length > 2);
@@ -950,7 +978,9 @@ function parseSkillEntry(yamlPath, source, tierOverrides) {
950
978
  stackSignals: meta.stack_signals ?? [],
951
979
  cognitiveMode: meta.cognitive_mode,
952
980
  phases: (meta.phases ?? []).map((p) => p.name),
953
- source
981
+ source,
982
+ addresses: meta.addresses ?? [],
983
+ dependsOn: meta.depends_on ?? []
954
984
  };
955
985
  }
956
986
  function scanDirectory(dir, source, index, tierOverrides) {
@@ -1174,7 +1204,7 @@ var createSkillDefinition = {
1174
1204
  };
1175
1205
  async function handleCreateSkill(input) {
1176
1206
  try {
1177
- const { generateSkillFiles } = await import("./create-skill-NDXQSTIK.js");
1207
+ const { generateSkillFiles } = await import("./create-skill-U3XCFRZN.js");
1178
1208
  const result = generateSkillFiles({
1179
1209
  name: input.name,
1180
1210
  description: input.description,
@@ -1292,7 +1322,7 @@ async function autoSyncRoadmap(projectPath) {
1292
1322
  try {
1293
1323
  const roadmapFile = path11.join(projectPath, "docs", "roadmap.md");
1294
1324
  if (!fs10.existsSync(roadmapFile)) return;
1295
- const { parseRoadmap, serializeRoadmap, syncRoadmap, applySyncChanges } = await import("./dist-KV2ICL5X.js");
1325
+ const { parseRoadmap, serializeRoadmap, syncRoadmap, applySyncChanges } = await import("./dist-LCR2IO7U.js");
1296
1326
  const raw = fs10.readFileSync(roadmapFile, "utf-8");
1297
1327
  const parseResult = parseRoadmap(raw);
1298
1328
  if (!parseResult.ok) return;
@@ -1319,7 +1349,7 @@ async function triggerExternalSync(projectPath, roadmapFile) {
1319
1349
  }
1320
1350
  const token = process.env.GITHUB_TOKEN;
1321
1351
  if (!token) return;
1322
- const { fullSync, GitHubIssuesSyncAdapter } = await import("./dist-KV2ICL5X.js");
1352
+ const { fullSync, GitHubIssuesSyncAdapter } = await import("./dist-LCR2IO7U.js");
1323
1353
  const adapter = new GitHubIssuesSyncAdapter({
1324
1354
  token,
1325
1355
  config: trackerConfig
@@ -1431,12 +1461,12 @@ var manageStateDefinition = {
1431
1461
  }
1432
1462
  };
1433
1463
  async function handleShow(projectPath, input) {
1434
- const { loadState } = await import("./dist-KV2ICL5X.js");
1464
+ const { loadState } = await import("./dist-LCR2IO7U.js");
1435
1465
  return resultToMcpResponse(await loadState(projectPath, input.stream, input.session));
1436
1466
  }
1437
1467
  async function handleLearn(projectPath, input) {
1438
1468
  if (!input.learning) return mcpError("Error: learning is required for learn action");
1439
- const { appendLearning } = await import("./dist-KV2ICL5X.js");
1469
+ const { appendLearning } = await import("./dist-LCR2IO7U.js");
1440
1470
  const result = await appendLearning(
1441
1471
  projectPath,
1442
1472
  input.learning,
@@ -1451,7 +1481,7 @@ async function handleLearn(projectPath, input) {
1451
1481
  async function handleFailure(projectPath, input) {
1452
1482
  if (!input.description) return mcpError("Error: description is required for failure action");
1453
1483
  if (!input.failureType) return mcpError("Error: failureType is required for failure action");
1454
- const { appendFailure } = await import("./dist-KV2ICL5X.js");
1484
+ const { appendFailure } = await import("./dist-LCR2IO7U.js");
1455
1485
  const result = await appendFailure(
1456
1486
  projectPath,
1457
1487
  input.description,
@@ -1464,24 +1494,24 @@ async function handleFailure(projectPath, input) {
1464
1494
  return resultToMcpResponse(Ok({ recorded: true }));
1465
1495
  }
1466
1496
  async function handleArchive(projectPath, input) {
1467
- const { archiveFailures } = await import("./dist-KV2ICL5X.js");
1497
+ const { archiveFailures } = await import("./dist-LCR2IO7U.js");
1468
1498
  const result = await archiveFailures(projectPath, input.stream, input.session);
1469
1499
  if (!result.ok) return resultToMcpResponse(result);
1470
1500
  return resultToMcpResponse(Ok({ archived: true }));
1471
1501
  }
1472
1502
  async function handleReset(projectPath, input) {
1473
- const { saveState, DEFAULT_STATE } = await import("./dist-KV2ICL5X.js");
1503
+ const { saveState, DEFAULT_STATE } = await import("./dist-LCR2IO7U.js");
1474
1504
  const result = await saveState(projectPath, { ...DEFAULT_STATE }, input.stream, input.session);
1475
1505
  if (!result.ok) return resultToMcpResponse(result);
1476
1506
  return resultToMcpResponse(Ok({ reset: true }));
1477
1507
  }
1478
1508
  async function handleGate(projectPath, _input) {
1479
- const { runMechanicalGate } = await import("./dist-KV2ICL5X.js");
1509
+ const { runMechanicalGate } = await import("./dist-LCR2IO7U.js");
1480
1510
  return resultToMcpResponse(await runMechanicalGate(projectPath));
1481
1511
  }
1482
1512
  async function handleSaveHandoff(projectPath, input) {
1483
1513
  if (!input.handoff) return mcpError("Error: handoff is required for save-handoff action");
1484
- const { saveHandoff } = await import("./dist-KV2ICL5X.js");
1514
+ const { saveHandoff } = await import("./dist-LCR2IO7U.js");
1485
1515
  const result = await saveHandoff(
1486
1516
  projectPath,
1487
1517
  input.handoff,
@@ -1493,7 +1523,7 @@ async function handleSaveHandoff(projectPath, input) {
1493
1523
  return resultToMcpResponse(Ok({ saved: true }));
1494
1524
  }
1495
1525
  async function handleLoadHandoff(projectPath, input) {
1496
- const { loadHandoff } = await import("./dist-KV2ICL5X.js");
1526
+ const { loadHandoff } = await import("./dist-LCR2IO7U.js");
1497
1527
  return resultToMcpResponse(await loadHandoff(projectPath, input.stream, input.session));
1498
1528
  }
1499
1529
  async function handleAppendEntry(projectPath, input) {
@@ -1501,7 +1531,7 @@ async function handleAppendEntry(projectPath, input) {
1501
1531
  if (!input.section) return mcpError("Error: section is required for append_entry action");
1502
1532
  if (!input.authorSkill) return mcpError("Error: authorSkill is required for append_entry action");
1503
1533
  if (!input.content) return mcpError("Error: content is required for append_entry action");
1504
- const { appendSessionEntry } = await import("./dist-KV2ICL5X.js");
1534
+ const { appendSessionEntry } = await import("./dist-LCR2IO7U.js");
1505
1535
  const result = await appendSessionEntry(
1506
1536
  projectPath,
1507
1537
  input.session,
@@ -1517,7 +1547,7 @@ async function handleUpdateEntryStatus(projectPath, input) {
1517
1547
  if (!input.entryId) return mcpError("Error: entryId is required for update_entry_status action");
1518
1548
  if (!input.newStatus)
1519
1549
  return mcpError("Error: newStatus is required for update_entry_status action");
1520
- const { updateSessionEntryStatus } = await import("./dist-KV2ICL5X.js");
1550
+ const { updateSessionEntryStatus } = await import("./dist-LCR2IO7U.js");
1521
1551
  const result = await updateSessionEntryStatus(
1522
1552
  projectPath,
1523
1553
  input.session,
@@ -1530,7 +1560,7 @@ async function handleUpdateEntryStatus(projectPath, input) {
1530
1560
  async function handleReadSection(projectPath, input) {
1531
1561
  if (!input.session) return mcpError("Error: session is required for read_section action");
1532
1562
  if (!input.section) return mcpError("Error: section is required for read_section action");
1533
- const { readSessionSection } = await import("./dist-KV2ICL5X.js");
1563
+ const { readSessionSection } = await import("./dist-LCR2IO7U.js");
1534
1564
  const result = await readSessionSection(
1535
1565
  projectPath,
1536
1566
  input.session,
@@ -1540,13 +1570,13 @@ async function handleReadSection(projectPath, input) {
1540
1570
  }
1541
1571
  async function handleReadSections(projectPath, input) {
1542
1572
  if (!input.session) return mcpError("Error: session is required for read_sections action");
1543
- const { readSessionSections } = await import("./dist-KV2ICL5X.js");
1573
+ const { readSessionSections } = await import("./dist-LCR2IO7U.js");
1544
1574
  const result = await readSessionSections(projectPath, input.session);
1545
1575
  return resultToMcpResponse(result);
1546
1576
  }
1547
1577
  async function handleArchiveSession(projectPath, input) {
1548
1578
  if (!input.session) return mcpError("Error: session is required for archive_session action");
1549
- const { archiveSession } = await import("./dist-KV2ICL5X.js");
1579
+ const { archiveSession } = await import("./dist-LCR2IO7U.js");
1550
1580
  const result = await archiveSession(projectPath, input.session);
1551
1581
  if (!result.ok) return resultToMcpResponse(result);
1552
1582
  await autoSyncRoadmap(projectPath);
@@ -1610,7 +1640,7 @@ var listStreamsDefinition = {
1610
1640
  };
1611
1641
  async function handleListStreams(input) {
1612
1642
  try {
1613
- const { listStreams, loadStreamIndex } = await import("./dist-KV2ICL5X.js");
1643
+ const { listStreams, loadStreamIndex } = await import("./dist-LCR2IO7U.js");
1614
1644
  const projectPath = sanitizePath(input.path);
1615
1645
  const indexResult = await loadStreamIndex(projectPath);
1616
1646
  const streamsResult = await listStreams(projectPath);
@@ -1648,7 +1678,7 @@ var checkPhaseGateDefinition = {
1648
1678
  };
1649
1679
  async function handleCheckPhaseGate(input) {
1650
1680
  try {
1651
- const { runCheckPhaseGate } = await import("./check-phase-gate-I4NQOCSU.js");
1681
+ const { runCheckPhaseGate } = await import("./check-phase-gate-7JQ6EW5R.js");
1652
1682
  const result = await runCheckPhaseGate({ cwd: sanitizePath(input.path) });
1653
1683
  if (result.ok) {
1654
1684
  return { content: [{ type: "text", text: JSON.stringify(result.value) }] };
@@ -1704,7 +1734,7 @@ async function handleValidateCrossCheck(input) {
1704
1734
  };
1705
1735
  }
1706
1736
  try {
1707
- const { runCrossCheck } = await import("./validate-cross-check-PFRKABCS.js");
1737
+ const { runCrossCheck } = await import("./validate-cross-check-VX2BAHQI.js");
1708
1738
  const specsDir = path12.resolve(projectPath, input.specsDir ?? "docs/specs");
1709
1739
  if (!specsDir.startsWith(projectPath)) {
1710
1740
  return {
@@ -2349,7 +2379,7 @@ async function handleGenerateSlashCommands(input) {
2349
2379
  // src/mcp/resources/state.ts
2350
2380
  async function getStateResource(projectRoot) {
2351
2381
  try {
2352
- const { loadState, migrateToStreams } = await import("./dist-KV2ICL5X.js");
2382
+ const { loadState, migrateToStreams } = await import("./dist-LCR2IO7U.js");
2353
2383
  await migrateToStreams(projectRoot);
2354
2384
  const result = await loadState(projectRoot);
2355
2385
  if (result.ok) {
@@ -2437,7 +2467,7 @@ async function handleQueryGraph(input) {
2437
2467
  const projectPath = sanitizePath(input.path);
2438
2468
  const store = await loadGraphStore(projectPath);
2439
2469
  if (!store) return graphNotFoundError();
2440
- const { ContextQL } = await import("./dist-U7EAO6T2.js");
2470
+ const { ContextQL } = await import("./dist-IA6XYKNO.js");
2441
2471
  const cql = new ContextQL(store);
2442
2472
  const result = cql.execute({
2443
2473
  rootNodeIds: input.rootNodeIds,
@@ -2528,7 +2558,7 @@ async function handleSearchSimilar(input) {
2528
2558
  const projectPath = sanitizePath(input.path);
2529
2559
  const store = await loadGraphStore(projectPath);
2530
2560
  if (!store) return graphNotFoundError();
2531
- const { FusionLayer } = await import("./dist-U7EAO6T2.js");
2561
+ const { FusionLayer } = await import("./dist-IA6XYKNO.js");
2532
2562
  const fusion = new FusionLayer(store);
2533
2563
  const results = fusion.search(input.query, input.topK ?? 10);
2534
2564
  if (input.mode === "summary") {
@@ -2583,7 +2613,7 @@ async function handleFindContextFor(input) {
2583
2613
  const projectPath = sanitizePath(input.path);
2584
2614
  const store = await loadGraphStore(projectPath);
2585
2615
  if (!store) return graphNotFoundError();
2586
- const { FusionLayer, ContextQL } = await import("./dist-U7EAO6T2.js");
2616
+ const { FusionLayer, ContextQL } = await import("./dist-IA6XYKNO.js");
2587
2617
  const fusion = new FusionLayer(store);
2588
2618
  const cql = new ContextQL(store);
2589
2619
  const tokenBudget = input.tokenBudget ?? 4e3;
@@ -2679,7 +2709,7 @@ async function handleGetRelationships(input) {
2679
2709
  const projectPath = sanitizePath(input.path);
2680
2710
  const store = await loadGraphStore(projectPath);
2681
2711
  if (!store) return graphNotFoundError();
2682
- const { ContextQL } = await import("./dist-U7EAO6T2.js");
2712
+ const { ContextQL } = await import("./dist-IA6XYKNO.js");
2683
2713
  const cql = new ContextQL(store);
2684
2714
  const direction = input.direction ?? "both";
2685
2715
  const bidirectional = direction === "both" || direction === "inbound";
@@ -2785,7 +2815,7 @@ async function handleGetImpact(input) {
2785
2815
  const projectPath = sanitizePath(input.path);
2786
2816
  const store = await loadGraphStore(projectPath);
2787
2817
  if (!store) return graphNotFoundError();
2788
- const { ContextQL } = await import("./dist-U7EAO6T2.js");
2818
+ const { ContextQL } = await import("./dist-IA6XYKNO.js");
2789
2819
  let targetNodeId = input.nodeId;
2790
2820
  if (!targetNodeId && input.filePath) {
2791
2821
  const fileNodes = store.findNodes({ type: "file" });
@@ -2917,9 +2947,9 @@ async function handleIngestSource(input) {
2917
2947
  try {
2918
2948
  const projectPath = sanitizePath(input.path);
2919
2949
  const graphDir = path16.join(projectPath, ".harness", "graph");
2920
- const { GraphStore, CodeIngestor, TopologicalLinker, KnowledgeIngestor, GitIngestor } = await import("./dist-U7EAO6T2.js");
2921
- const fs16 = await import("fs/promises");
2922
- await fs16.mkdir(graphDir, { recursive: true });
2950
+ const { GraphStore, CodeIngestor, TopologicalLinker, KnowledgeIngestor, GitIngestor } = await import("./dist-IA6XYKNO.js");
2951
+ const fs17 = await import("fs/promises");
2952
+ await fs17.mkdir(graphDir, { recursive: true });
2923
2953
  const store = new GraphStore();
2924
2954
  await store.load(graphDir);
2925
2955
  const results = [];
@@ -2992,7 +3022,7 @@ async function handleDetectAnomalies(input) {
2992
3022
  const projectPath = sanitizePath(input.path);
2993
3023
  const store = await loadGraphStore(projectPath);
2994
3024
  if (!store) return graphNotFoundError();
2995
- const { GraphAnomalyAdapter } = await import("./dist-U7EAO6T2.js");
3025
+ const { GraphAnomalyAdapter } = await import("./dist-IA6XYKNO.js");
2996
3026
  const adapter = new GraphAnomalyAdapter(store);
2997
3027
  const report = adapter.detect({
2998
3028
  ...input.threshold !== void 0 && { threshold: input.threshold },
@@ -3032,7 +3062,7 @@ async function handleAskGraph(input) {
3032
3062
  const projectPath = sanitizePath(input.path);
3033
3063
  const store = await loadGraphStore(projectPath);
3034
3064
  if (!store) return graphNotFoundError();
3035
- const { askGraph } = await import("./dist-U7EAO6T2.js");
3065
+ const { askGraph } = await import("./dist-IA6XYKNO.js");
3036
3066
  const result = await askGraph(store, input.question);
3037
3067
  return {
3038
3068
  content: [{ type: "text", text: JSON.stringify(result) }]
@@ -3168,7 +3198,7 @@ var generateAgentDefinitionsDefinition = {
3168
3198
  }
3169
3199
  };
3170
3200
  async function handleGenerateAgentDefinitions(input) {
3171
- const { generateAgentDefinitions } = await import("./generate-agent-definitions-QICSCGXB.js");
3201
+ const { generateAgentDefinitions } = await import("./generate-agent-definitions-KU6X2UQN.js");
3172
3202
  const platforms = input.platform === "all" || !input.platform ? ["claude-code", "gemini-cli"] : [input.platform];
3173
3203
  const results = generateAgentDefinitions({
3174
3204
  platforms: [...platforms],
@@ -3463,32 +3493,42 @@ function handleSync(projectPath, input, deps) {
3463
3493
  }
3464
3494
  return resultToMcpResponse(Ok2({ changes, applied: false }));
3465
3495
  }
3496
+ var readOnlyActions = /* @__PURE__ */ new Set(["show", "query"]);
3497
+ function dispatchAction(action, projectPath, input, deps) {
3498
+ switch (action) {
3499
+ case "show":
3500
+ return handleShow2(projectPath, input, deps);
3501
+ case "add":
3502
+ return handleAdd(projectPath, input, deps);
3503
+ case "update":
3504
+ return handleUpdate(projectPath, input, deps);
3505
+ case "remove":
3506
+ return handleRemove(projectPath, input, deps);
3507
+ case "query":
3508
+ return handleQuery(projectPath, input, deps);
3509
+ case "sync":
3510
+ return handleSync(projectPath, input, deps);
3511
+ default:
3512
+ return { content: [{ type: "text", text: `Error: unknown action` }], isError: true };
3513
+ }
3514
+ }
3515
+ function shouldTriggerExternalSync(input, response) {
3516
+ if (response.isError || readOnlyActions.has(input.action)) return false;
3517
+ if (input.action === "sync") return input.apply === true;
3518
+ return true;
3519
+ }
3466
3520
  async function handleManageRoadmap(input) {
3467
3521
  try {
3468
- const { parseRoadmap, serializeRoadmap, syncRoadmap, applySyncChanges } = await import("./dist-KV2ICL5X.js");
3522
+ const { parseRoadmap, serializeRoadmap, syncRoadmap, applySyncChanges } = await import("./dist-LCR2IO7U.js");
3469
3523
  const { Ok: Ok2 } = await import("./dist-USY2C5JL.js");
3470
3524
  const projectPath = sanitizePath(input.path);
3471
3525
  const deps = { parseRoadmap, serializeRoadmap, syncRoadmap, applySyncChanges, Ok: Ok2 };
3472
- switch (input.action) {
3473
- case "show":
3474
- return handleShow2(projectPath, input, deps);
3475
- case "add":
3476
- return handleAdd(projectPath, input, deps);
3477
- case "update":
3478
- return handleUpdate(projectPath, input, deps);
3479
- case "remove":
3480
- return handleRemove(projectPath, input, deps);
3481
- case "query":
3482
- return handleQuery(projectPath, input, deps);
3483
- case "sync":
3484
- return handleSync(projectPath, input, deps);
3485
- default: {
3486
- return {
3487
- content: [{ type: "text", text: `Error: unknown action` }],
3488
- isError: true
3489
- };
3490
- }
3526
+ const response = dispatchAction(input.action, projectPath, input, deps);
3527
+ if (shouldTriggerExternalSync(input, response)) {
3528
+ await triggerExternalSync(projectPath, roadmapPath(projectPath)).catch(() => {
3529
+ });
3491
3530
  }
3531
+ return response;
3492
3532
  } catch (error) {
3493
3533
  return {
3494
3534
  content: [
@@ -3907,7 +3947,7 @@ async function handleTransition(validInput, projectPath, id) {
3907
3947
  const transition = transitionResult.data;
3908
3948
  const prompt = renderTransition(transition);
3909
3949
  try {
3910
- const { saveHandoff } = await import("./dist-KV2ICL5X.js");
3950
+ const { saveHandoff } = await import("./dist-LCR2IO7U.js");
3911
3951
  await saveHandoff(
3912
3952
  projectPath,
3913
3953
  {
@@ -3975,7 +4015,7 @@ async function handleEmitInteraction(input) {
3975
4015
  }
3976
4016
  async function recordInteraction(projectPath, id, type, decision, stream) {
3977
4017
  try {
3978
- const { loadState, saveState } = await import("./dist-KV2ICL5X.js");
4018
+ const { loadState, saveState } = await import("./dist-LCR2IO7U.js");
3979
4019
  const stateResult = await loadState(projectPath, stream);
3980
4020
  if (stateResult.ok) {
3981
4021
  const state = stateResult.value;
@@ -4064,10 +4104,10 @@ async function handleGatherContext(input) {
4064
4104
  input.include ?? ["state", "learnings", "handoff", "graph", "validation"]
4065
4105
  );
4066
4106
  const errors = [];
4067
- const statePromise = includeSet.has("state") ? import("./dist-KV2ICL5X.js").then(
4107
+ const statePromise = includeSet.has("state") ? import("./dist-LCR2IO7U.js").then(
4068
4108
  (core) => core.loadState(projectPath, void 0, input.session)
4069
4109
  ) : Promise.resolve(null);
4070
- const learningsPromise = includeSet.has("learnings") ? import("./dist-KV2ICL5X.js").then(
4110
+ const learningsPromise = includeSet.has("learnings") ? import("./dist-LCR2IO7U.js").then(
4071
4111
  (core) => core.loadBudgetedLearnings(projectPath, {
4072
4112
  intent: input.intent,
4073
4113
  tokenBudget: input.learningsBudget ?? 1e3,
@@ -4076,14 +4116,14 @@ async function handleGatherContext(input) {
4076
4116
  ...input.depth !== void 0 && { depth: input.depth }
4077
4117
  })
4078
4118
  ) : Promise.resolve(null);
4079
- const handoffPromise = includeSet.has("handoff") ? import("./dist-KV2ICL5X.js").then(
4119
+ const handoffPromise = includeSet.has("handoff") ? import("./dist-LCR2IO7U.js").then(
4080
4120
  (core) => core.loadHandoff(projectPath, void 0, input.session)
4081
4121
  ) : Promise.resolve(null);
4082
4122
  const graphPromise = includeSet.has("graph") ? (async () => {
4083
- const { loadGraphStore: loadGraphStore2 } = await import("./graph-loader-KMHDQYDT.js");
4123
+ const { loadGraphStore: loadGraphStore2 } = await import("./graph-loader-FJN4H7Y4.js");
4084
4124
  const store = await loadGraphStore2(projectPath);
4085
4125
  if (!store) return null;
4086
- const { FusionLayer, ContextQL } = await import("./dist-U7EAO6T2.js");
4126
+ const { FusionLayer, ContextQL } = await import("./dist-IA6XYKNO.js");
4087
4127
  const fusion = new FusionLayer(store);
4088
4128
  const cql = new ContextQL(store);
4089
4129
  const tokenBudget = input.tokenBudget ?? 4e3;
@@ -4120,11 +4160,11 @@ async function handleGatherContext(input) {
4120
4160
  context: contextBlocks
4121
4161
  };
4122
4162
  })() : Promise.resolve(null);
4123
- const sessionsPromise = includeSet.has("sessions") && input.session ? import("./dist-KV2ICL5X.js").then(
4163
+ const sessionsPromise = includeSet.has("sessions") && input.session ? import("./dist-LCR2IO7U.js").then(
4124
4164
  (core) => core.readSessionSections(projectPath, input.session)
4125
4165
  ) : Promise.resolve(null);
4126
4166
  const shouldIncludeEvents = input.includeEvents !== void 0 ? input.includeEvents : includeSet.has("events") || !!input.session && !input.include;
4127
- const eventsPromise = shouldIncludeEvents ? import("./dist-KV2ICL5X.js").then(async (core) => {
4167
+ const eventsPromise = shouldIncludeEvents ? import("./dist-LCR2IO7U.js").then(async (core) => {
4128
4168
  const result = await core.loadEvents(projectPath, {
4129
4169
  session: input.session
4130
4170
  });
@@ -4132,7 +4172,7 @@ async function handleGatherContext(input) {
4132
4172
  return core.formatEventTimeline(result.value);
4133
4173
  }) : Promise.resolve(null);
4134
4174
  const validationPromise = includeSet.has("validation") ? (async () => {
4135
- const { handleValidateProject: handleValidateProject2 } = await import("./validate-VHFE6J6O.js");
4175
+ const { handleValidateProject: handleValidateProject2 } = await import("./validate-4IA5RPEX.js");
4136
4176
  const result = await handleValidateProject2({ path: projectPath });
4137
4177
  const first = result.content[0];
4138
4178
  return first ? JSON.parse(first.text) : null;
@@ -4226,7 +4266,7 @@ async function handleGatherContext(input) {
4226
4266
  };
4227
4267
  if (input.session) {
4228
4268
  try {
4229
- const core = await import("./dist-KV2ICL5X.js");
4269
+ const core = await import("./dist-LCR2IO7U.js");
4230
4270
  core.updateSessionIndex(
4231
4271
  projectPath,
4232
4272
  input.session,
@@ -4248,291 +4288,6 @@ async function handleGatherContext(input) {
4248
4288
  };
4249
4289
  }
4250
4290
 
4251
- // src/mcp/tools/assess-project.ts
4252
- var assessProjectDefinition = {
4253
- name: "assess_project",
4254
- description: "Run all project health checks in parallel and return a unified report. Checks: validate, dependencies, docs, entropy, security, performance, lint.",
4255
- inputSchema: {
4256
- type: "object",
4257
- properties: {
4258
- path: { type: "string", description: "Path to project root" },
4259
- checks: {
4260
- type: "array",
4261
- items: {
4262
- type: "string",
4263
- enum: ["validate", "deps", "docs", "entropy", "security", "perf", "lint"]
4264
- },
4265
- description: "Which checks to run (default: all)"
4266
- },
4267
- mode: {
4268
- type: "string",
4269
- enum: ["summary", "detailed"],
4270
- description: "Response density. Default: summary"
4271
- }
4272
- },
4273
- required: ["path"]
4274
- }
4275
- };
4276
- async function handleAssessProject(input) {
4277
- const start = Date.now();
4278
- let projectPath;
4279
- try {
4280
- projectPath = sanitizePath(input.path);
4281
- } catch (error) {
4282
- return {
4283
- content: [
4284
- {
4285
- type: "text",
4286
- text: `Error: ${error instanceof Error ? error.message : String(error)}`
4287
- }
4288
- ],
4289
- isError: true
4290
- };
4291
- }
4292
- const checksToRun = new Set(
4293
- input.checks ?? ["validate", "deps", "docs", "entropy", "security", "perf", "lint"]
4294
- );
4295
- const mode = input.mode ?? "summary";
4296
- let validateResult = null;
4297
- if (checksToRun.has("validate")) {
4298
- try {
4299
- const { handleValidateProject: handleValidateProject2 } = await import("./validate-VHFE6J6O.js");
4300
- const result = await handleValidateProject2({ path: projectPath });
4301
- const first = result.content[0];
4302
- const parsed = first ? JSON.parse(first.text) : {};
4303
- validateResult = {
4304
- name: "validate",
4305
- passed: parsed.valid === true,
4306
- issueCount: parsed.errors?.length ?? 0,
4307
- ...parsed.errors?.length > 0 ? { topIssue: parsed.errors[0] } : {},
4308
- ...mode === "detailed" ? { detailed: parsed } : {}
4309
- };
4310
- } catch (error) {
4311
- validateResult = {
4312
- name: "validate",
4313
- passed: false,
4314
- issueCount: 1,
4315
- topIssue: error instanceof Error ? error.message : String(error)
4316
- };
4317
- }
4318
- }
4319
- const parallelChecks = [];
4320
- if (checksToRun.has("deps")) {
4321
- parallelChecks.push(
4322
- (async () => {
4323
- try {
4324
- const { handleCheckDependencies: handleCheckDependencies2 } = await import("./architecture-NANP4XPE.js");
4325
- const result = await handleCheckDependencies2({ path: projectPath });
4326
- const first = result.content[0];
4327
- const parsed = first ? JSON.parse(first.text) : {};
4328
- const violations = parsed.violations ?? [];
4329
- return {
4330
- name: "deps",
4331
- passed: !result.isError && violations.length === 0,
4332
- issueCount: violations.length,
4333
- ...violations.length > 0 ? { topIssue: violations[0]?.message ?? String(violations[0]) } : {},
4334
- ...mode === "detailed" ? { detailed: parsed } : {}
4335
- };
4336
- } catch (error) {
4337
- return {
4338
- name: "deps",
4339
- passed: false,
4340
- issueCount: 1,
4341
- topIssue: error instanceof Error ? error.message : String(error)
4342
- };
4343
- }
4344
- })()
4345
- );
4346
- }
4347
- if (checksToRun.has("docs")) {
4348
- parallelChecks.push(
4349
- (async () => {
4350
- try {
4351
- const { handleCheckDocs: handleCheckDocs2 } = await import("./docs-2PCZVSGB.js");
4352
- const result = await handleCheckDocs2({ path: projectPath, scope: "coverage" });
4353
- const first = result.content[0];
4354
- const parsed = first ? JSON.parse(first.text) : {};
4355
- const undocumented = parsed.undocumented ?? parsed.files?.undocumented ?? [];
4356
- return {
4357
- name: "docs",
4358
- passed: !result.isError,
4359
- issueCount: Array.isArray(undocumented) ? undocumented.length : 0,
4360
- ...Array.isArray(undocumented) && undocumented.length > 0 ? { topIssue: `Undocumented: ${undocumented[0]}` } : {},
4361
- ...mode === "detailed" ? { detailed: parsed } : {}
4362
- };
4363
- } catch (error) {
4364
- return {
4365
- name: "docs",
4366
- passed: false,
4367
- issueCount: 1,
4368
- topIssue: error instanceof Error ? error.message : String(error)
4369
- };
4370
- }
4371
- })()
4372
- );
4373
- }
4374
- if (checksToRun.has("entropy")) {
4375
- parallelChecks.push(
4376
- (async () => {
4377
- try {
4378
- const { handleDetectEntropy: handleDetectEntropy2 } = await import("./entropy-VGXXBIGX.js");
4379
- const result = await handleDetectEntropy2({ path: projectPath, type: "all" });
4380
- const first = result.content[0];
4381
- const parsed = first ? JSON.parse(first.text) : {};
4382
- const issues = (parsed.drift?.staleReferences?.length ?? 0) + (parsed.drift?.missingTargets?.length ?? 0) + (parsed.deadCode?.unusedImports?.length ?? 0) + (parsed.deadCode?.unusedExports?.length ?? 0) + (parsed.patterns?.violations?.length ?? 0);
4383
- return {
4384
- name: "entropy",
4385
- passed: !("isError" in result && result.isError) && issues === 0,
4386
- issueCount: issues,
4387
- ...issues > 0 ? { topIssue: "Entropy detected -- run detect_entropy for details" } : {},
4388
- ...mode === "detailed" ? { detailed: parsed } : {}
4389
- };
4390
- } catch (error) {
4391
- return {
4392
- name: "entropy",
4393
- passed: false,
4394
- issueCount: 1,
4395
- topIssue: error instanceof Error ? error.message : String(error)
4396
- };
4397
- }
4398
- })()
4399
- );
4400
- }
4401
- if (checksToRun.has("security")) {
4402
- parallelChecks.push(
4403
- (async () => {
4404
- try {
4405
- const { handleRunSecurityScan: handleRunSecurityScan2 } = await import("./security-NLWTMK3G.js");
4406
- const result = await handleRunSecurityScan2({ path: projectPath });
4407
- const first = result.content[0];
4408
- const parsed = first ? JSON.parse(first.text) : {};
4409
- const findings = parsed.findings ?? [];
4410
- const errorCount = findings.filter(
4411
- (f) => f.severity === "error"
4412
- ).length;
4413
- return {
4414
- name: "security",
4415
- passed: !result.isError && errorCount === 0,
4416
- issueCount: findings.length,
4417
- ...findings.length > 0 ? {
4418
- topIssue: `${findings[0]?.rule ?? findings[0]?.type ?? "finding"}: ${findings[0]?.message ?? ""}`
4419
- } : {},
4420
- ...mode === "detailed" ? { detailed: parsed } : {}
4421
- };
4422
- } catch (error) {
4423
- return {
4424
- name: "security",
4425
- passed: false,
4426
- issueCount: 1,
4427
- topIssue: error instanceof Error ? error.message : String(error)
4428
- };
4429
- }
4430
- })()
4431
- );
4432
- }
4433
- if (checksToRun.has("perf")) {
4434
- parallelChecks.push(
4435
- (async () => {
4436
- try {
4437
- const { handleCheckPerformance: handleCheckPerformance2 } = await import("./performance-RV4DUMFI.js");
4438
- const result = await handleCheckPerformance2({ path: projectPath });
4439
- if ("isError" in result && result.isError) {
4440
- const msg = result.content[0]?.text ?? "Performance check failed";
4441
- return { name: "perf", passed: false, issueCount: 1, topIssue: msg };
4442
- }
4443
- const first = result.content[0];
4444
- let parsed = {};
4445
- try {
4446
- parsed = first ? JSON.parse(first.text) : {};
4447
- } catch {
4448
- return {
4449
- name: "perf",
4450
- passed: false,
4451
- issueCount: 1,
4452
- topIssue: first?.text ?? "Invalid perf output"
4453
- };
4454
- }
4455
- const issues = parsed.violations?.length ?? parsed.issues?.length ?? 0;
4456
- return {
4457
- name: "perf",
4458
- passed: issues === 0,
4459
- issueCount: issues,
4460
- ...issues > 0 ? { topIssue: "Performance issues detected" } : {},
4461
- ...mode === "detailed" ? { detailed: parsed } : {}
4462
- };
4463
- } catch (error) {
4464
- return {
4465
- name: "perf",
4466
- passed: false,
4467
- issueCount: 1,
4468
- topIssue: error instanceof Error ? error.message : String(error)
4469
- };
4470
- }
4471
- })()
4472
- );
4473
- }
4474
- if (checksToRun.has("lint")) {
4475
- parallelChecks.push(
4476
- (async () => {
4477
- try {
4478
- const { execFileSync } = await import("child_process");
4479
- const output = execFileSync("npx", ["turbo", "run", "lint", "--force"], {
4480
- cwd: projectPath,
4481
- encoding: "utf-8",
4482
- timeout: 6e4,
4483
- stdio: ["pipe", "pipe", "pipe"]
4484
- });
4485
- return {
4486
- name: "lint",
4487
- passed: true,
4488
- issueCount: 0,
4489
- ...mode === "detailed" ? { detailed: output } : {}
4490
- };
4491
- } catch (error) {
4492
- const stderr = error && typeof error === "object" && "stderr" in error ? String(error.stderr) : "";
4493
- const stdout = error && typeof error === "object" && "stdout" in error ? String(error.stdout) : "";
4494
- const combined = (stderr + "\n" + stdout).trim();
4495
- const errorMatch = combined.match(/(\d+) error/);
4496
- const issueCount = errorMatch?.[1] ? parseInt(errorMatch[1], 10) : 1;
4497
- const firstError = combined.split("\n").find((line) => line.includes("error"));
4498
- return {
4499
- name: "lint",
4500
- passed: false,
4501
- issueCount,
4502
- topIssue: firstError?.trim() ?? (error instanceof Error ? error.message : String(error)),
4503
- ...mode === "detailed" ? { detailed: combined } : {}
4504
- };
4505
- }
4506
- })()
4507
- );
4508
- }
4509
- const parallelResults = await Promise.all(parallelChecks);
4510
- const allChecks = [];
4511
- if (validateResult) allChecks.push(validateResult);
4512
- allChecks.push(...parallelResults);
4513
- const healthy = allChecks.every((c) => c.passed);
4514
- const assessedIn = Date.now() - start;
4515
- if (mode === "summary") {
4516
- const summaryChecks = allChecks.map(({ detailed: _d, ...rest }) => rest);
4517
- return {
4518
- content: [
4519
- {
4520
- type: "text",
4521
- text: JSON.stringify({ healthy, checks: summaryChecks, assessedIn })
4522
- }
4523
- ]
4524
- };
4525
- }
4526
- return {
4527
- content: [
4528
- {
4529
- type: "text",
4530
- text: JSON.stringify({ healthy, checks: allChecks, assessedIn })
4531
- }
4532
- ]
4533
- };
4534
- }
4535
-
4536
4291
  // src/mcp/tools/review-changes.ts
4537
4292
  var SIZE_GATE_LINES = 1e4;
4538
4293
  var reviewChangesDefinition = {
@@ -4637,7 +4392,7 @@ async function handleReviewChanges(input) {
4637
4392
  }
4638
4393
  }
4639
4394
  async function runQuickReview(projectPath, diff, diffLines, downgraded) {
4640
- const { handleAnalyzeDiff: handleAnalyzeDiff2 } = await import("./feedback-VTSPL3O7.js");
4395
+ const { handleAnalyzeDiff: handleAnalyzeDiff2 } = await import("./feedback-QGCSW7SB.js");
4641
4396
  const result = await handleAnalyzeDiff2({ diff, path: projectPath });
4642
4397
  const firstContent = result.content[0];
4643
4398
  if (!firstContent) throw new Error("Empty analyze_diff response");
@@ -4668,7 +4423,7 @@ function extractFileCount(diffParsed) {
4668
4423
  return files?.length ?? 0;
4669
4424
  }
4670
4425
  async function runStandardReview(projectPath, diff, diffLines, downgraded) {
4671
- const { handleAnalyzeDiff: handleAnalyzeDiff2, handleCreateSelfReview: handleCreateSelfReview2 } = await import("./feedback-VTSPL3O7.js");
4426
+ const { handleAnalyzeDiff: handleAnalyzeDiff2, handleCreateSelfReview: handleCreateSelfReview2 } = await import("./feedback-QGCSW7SB.js");
4672
4427
  const [diffResult, reviewResult] = await Promise.all([
4673
4428
  handleAnalyzeDiff2({ diff, path: projectPath }),
4674
4429
  handleCreateSelfReview2({ path: projectPath, diff })
@@ -4700,7 +4455,7 @@ async function runStandardReview(projectPath, diff, diffLines, downgraded) {
4700
4455
  };
4701
4456
  }
4702
4457
  async function runDeepReview(projectPath, diff, diffLines, _downgraded) {
4703
- const { handleRunCodeReview: handleRunCodeReview2 } = await import("./review-pipeline-7KQJB4SI.js");
4458
+ const { handleRunCodeReview: handleRunCodeReview2 } = await import("./review-pipeline-3ZS3GJSP.js");
4704
4459
  const result = await handleRunCodeReview2({ path: projectPath, diff });
4705
4460
  const deepContent = result.content[0];
4706
4461
  if (!deepContent) throw new Error("Empty code review response");
@@ -4771,7 +4526,7 @@ async function handleCheckTaskIndependence(input) {
4771
4526
  try {
4772
4527
  const projectPath = sanitizePath(input.path);
4773
4528
  const store = await loadGraphStore(projectPath);
4774
- const { TaskIndependenceAnalyzer } = await import("./dist-U7EAO6T2.js");
4529
+ const { TaskIndependenceAnalyzer } = await import("./dist-IA6XYKNO.js");
4775
4530
  const analyzer = new TaskIndependenceAnalyzer(store ?? void 0);
4776
4531
  const result = analyzer.analyze({
4777
4532
  tasks: input.tasks,
@@ -4859,7 +4614,7 @@ async function handlePredictConflicts(input) {
4859
4614
  try {
4860
4615
  const projectPath = sanitizePath(input.path);
4861
4616
  const store = await loadGraphStore(projectPath);
4862
- const { ConflictPredictor } = await import("./dist-U7EAO6T2.js");
4617
+ const { ConflictPredictor } = await import("./dist-IA6XYKNO.js");
4863
4618
  const predictor = new ConflictPredictor(store ?? void 0);
4864
4619
  const result = predictor.predict({
4865
4620
  tasks: input.tasks,
@@ -4965,7 +4720,7 @@ async function handleDetectStaleConstraints(input) {
4965
4720
  isError: true
4966
4721
  };
4967
4722
  }
4968
- const { loadGraphStore: loadGraphStore2 } = await import("./graph-loader-KMHDQYDT.js");
4723
+ const { loadGraphStore: loadGraphStore2 } = await import("./graph-loader-FJN4H7Y4.js");
4969
4724
  const store = await loadGraphStore2(projectPath);
4970
4725
  if (!store) {
4971
4726
  return {
@@ -4986,7 +4741,7 @@ async function handleDetectStaleConstraints(input) {
4986
4741
  ]
4987
4742
  };
4988
4743
  }
4989
- const { detectStaleConstraints } = await import("./dist-KV2ICL5X.js");
4744
+ const { detectStaleConstraints } = await import("./dist-LCR2IO7U.js");
4990
4745
  const result = detectStaleConstraints(
4991
4746
  store,
4992
4747
  windowDays,
@@ -5013,6 +4768,204 @@ async function handleDetectStaleConstraints(input) {
5013
4768
  }
5014
4769
  }
5015
4770
 
4771
+ // src/skill/health-snapshot.ts
4772
+ import { execSync } from "child_process";
4773
+ import * as fs16 from "fs";
4774
+ import * as path19 from "path";
4775
+ var CACHE_FILE = "health-snapshot.json";
4776
+ var STALENESS_MS = 36e5;
4777
+ function isSnapshotFresh(snapshot, projectPath) {
4778
+ try {
4779
+ const currentHead = execSync("git rev-parse HEAD", {
4780
+ cwd: projectPath,
4781
+ encoding: "utf-8",
4782
+ stdio: ["pipe", "pipe", "pipe"]
4783
+ }).trim();
4784
+ if (snapshot.gitHead === currentHead) return true;
4785
+ } catch {
4786
+ }
4787
+ const age = Date.now() - new Date(snapshot.capturedAt).getTime();
4788
+ return age < STALENESS_MS;
4789
+ }
4790
+ function loadCachedSnapshot(projectPath) {
4791
+ const filePath = path19.join(projectPath, ".harness", CACHE_FILE);
4792
+ try {
4793
+ const raw = fs16.readFileSync(filePath, "utf-8");
4794
+ return JSON.parse(raw);
4795
+ } catch {
4796
+ return null;
4797
+ }
4798
+ }
4799
+ function saveCachedSnapshot(snapshot, projectPath) {
4800
+ const dir = path19.join(projectPath, ".harness");
4801
+ fs16.mkdirSync(dir, { recursive: true });
4802
+ const filePath = path19.join(dir, CACHE_FILE);
4803
+ fs16.writeFileSync(filePath, JSON.stringify(snapshot, null, 2));
4804
+ }
4805
+ var SIGNAL_RULES = [
4806
+ ["circular-deps", (c) => c.deps.circularDeps > 0],
4807
+ ["layer-violations", (c) => c.deps.layerViolations > 0],
4808
+ ["dead-code", (c) => c.entropy.deadExports > 0 || c.entropy.deadFiles > 0],
4809
+ ["drift", (c) => c.entropy.driftCount > 0],
4810
+ ["security-findings", (c) => c.security.findingCount > 0],
4811
+ ["doc-gaps", (c) => c.docs.undocumentedCount > 0],
4812
+ ["perf-regression", (c) => c.perf.violationCount > 0],
4813
+ ["anomaly-outlier", (_c, m) => m.anomalyOutlierCount > 0],
4814
+ ["articulation-point", (_c, m) => m.articulationPointCount > 0],
4815
+ ["high-coupling", (_c, m) => m.avgCouplingRatio > 0.5 || m.maxFanOut > 20],
4816
+ ["high-complexity", (_c, m) => m.maxCyclomaticComplexity > 20 || m.avgCyclomaticComplexity > 10],
4817
+ ["low-coverage", (_c, m) => m.testCoverage !== null && m.testCoverage < 60]
4818
+ ];
4819
+ function deriveSignals(checks, metrics) {
4820
+ const signals = /* @__PURE__ */ new Set();
4821
+ for (const [name, predicate] of SIGNAL_RULES) {
4822
+ if (predicate(checks, metrics)) signals.add(name);
4823
+ }
4824
+ return [...signals];
4825
+ }
4826
+ var DEFAULT_CHECK = { passed: true, issueCount: 0 };
4827
+ function parseToolResult(result) {
4828
+ return JSON.parse(result.content[0]?.text ?? "{}");
4829
+ }
4830
+ function buildCheckMap(assessData) {
4831
+ const map = /* @__PURE__ */ new Map();
4832
+ const checks = assessData.checks ?? [];
4833
+ for (const c of checks) {
4834
+ map.set(c.name, { passed: c.passed, issueCount: c.issueCount });
4835
+ }
4836
+ return map;
4837
+ }
4838
+ function countViolations(depsData) {
4839
+ const violations = depsData.violations ?? [];
4840
+ return {
4841
+ circularDeps: violations.filter((v) => v.reason === "CIRCULAR_DEP").length,
4842
+ layerViolations: violations.filter(
4843
+ (v) => v.reason === "WRONG_LAYER" || v.reason === "FORBIDDEN_IMPORT"
4844
+ ).length
4845
+ };
4846
+ }
4847
+ function parseEntropyGranular(entropyData) {
4848
+ const dc = entropyData.deadCode ?? {};
4849
+ const dr = entropyData.drift ?? {};
4850
+ return {
4851
+ deadExports: dc.unusedExports?.length ?? 0,
4852
+ deadFiles: dc.deadFiles?.length ?? 0,
4853
+ driftCount: (dr.staleReferences?.length ?? 0) + (dr.missingTargets?.length ?? 0)
4854
+ };
4855
+ }
4856
+ function countCriticalFindings(securityData) {
4857
+ const findings = securityData.findings ?? [];
4858
+ return findings.filter((f) => f.severity === "error").length;
4859
+ }
4860
+ async function runHealthChecks(projectPath) {
4861
+ const { handleAssessProject: handleAssessProject2 } = await import("./assess-project-R2OZIDDS.js");
4862
+ const { handleCheckDependencies: handleCheckDependencies2 } = await import("./architecture-OVOCDTI6.js");
4863
+ const { handleDetectEntropy: handleDetectEntropy2 } = await import("./entropy-AKSZG7G5.js");
4864
+ const { handleRunSecurityScan: handleRunSecurityScan2 } = await import("./security-LJCLZES6.js");
4865
+ const [assessResult, depsResult, entropyResult, securityResult] = await Promise.all([
4866
+ handleAssessProject2({
4867
+ path: projectPath,
4868
+ checks: ["deps", "entropy", "security", "perf", "docs", "lint"]
4869
+ }),
4870
+ handleCheckDependencies2({ path: projectPath }),
4871
+ handleDetectEntropy2({ path: projectPath, type: "all" }),
4872
+ handleRunSecurityScan2({ path: projectPath })
4873
+ ]);
4874
+ const assessData = parseToolResult(assessResult);
4875
+ const checkMap = buildCheckMap(assessData);
4876
+ const { circularDeps, layerViolations } = countViolations(parseToolResult(depsResult));
4877
+ const entropyGranular = parseEntropyGranular(parseToolResult(entropyResult));
4878
+ const criticalCount = countCriticalFindings(parseToolResult(securityResult));
4879
+ const deps = checkMap.get("deps") ?? DEFAULT_CHECK;
4880
+ const entropy = checkMap.get("entropy") ?? DEFAULT_CHECK;
4881
+ const security = checkMap.get("security") ?? DEFAULT_CHECK;
4882
+ const perf = checkMap.get("perf") ?? DEFAULT_CHECK;
4883
+ const docs = checkMap.get("docs") ?? DEFAULT_CHECK;
4884
+ const lint = checkMap.get("lint") ?? DEFAULT_CHECK;
4885
+ return {
4886
+ deps: { passed: deps.passed, issueCount: deps.issueCount, circularDeps, layerViolations },
4887
+ entropy: { passed: entropy.passed, ...entropyGranular },
4888
+ security: { passed: security.passed, findingCount: security.issueCount, criticalCount },
4889
+ perf: { passed: perf.passed, violationCount: perf.issueCount },
4890
+ docs: { passed: docs.passed, undocumentedCount: docs.issueCount },
4891
+ lint: { passed: lint.passed, issueCount: lint.issueCount }
4892
+ };
4893
+ }
4894
+ var ZERO_METRICS = {
4895
+ avgFanOut: 0,
4896
+ maxFanOut: 0,
4897
+ avgCyclomaticComplexity: 0,
4898
+ maxCyclomaticComplexity: 0,
4899
+ avgCouplingRatio: 0,
4900
+ testCoverage: null,
4901
+ anomalyOutlierCount: 0,
4902
+ articulationPointCount: 0
4903
+ };
4904
+ function avg(values) {
4905
+ if (values.length === 0) return 0;
4906
+ return Math.round(values.reduce((s, v) => s + v, 0) / values.length * 1e3) / 1e3;
4907
+ }
4908
+ async function runGraphMetrics(projectPath) {
4909
+ try {
4910
+ const { loadGraphStore: loadGraphStore2 } = await import("./graph-loader-FJN4H7Y4.js");
4911
+ const store = await loadGraphStore2(projectPath);
4912
+ if (!store) return ZERO_METRICS;
4913
+ const { GraphCouplingAdapter, GraphComplexityAdapter, GraphAnomalyAdapter } = await import("./dist-IA6XYKNO.js");
4914
+ const couplingAdapter = new GraphCouplingAdapter(store);
4915
+ const couplingData = couplingAdapter.computeCouplingData();
4916
+ const files = couplingData.files;
4917
+ const avgFanOut = avg(files.map((f) => f.fanOut));
4918
+ const maxFanOut = files.length > 0 ? Math.max(...files.map((f) => f.fanOut)) : 0;
4919
+ const avgCouplingRatio = avg(files.map((f) => f.couplingRatio));
4920
+ const complexityAdapter = new GraphComplexityAdapter(store);
4921
+ const complexityData = complexityAdapter.computeComplexityHotspots();
4922
+ const hotspots = complexityData.hotspots;
4923
+ const avgCyclomaticComplexity = avg(hotspots.map((h) => h.complexity));
4924
+ const maxCyclomaticComplexity = hotspots.length > 0 ? Math.max(...hotspots.map((h) => h.complexity)) : 0;
4925
+ const anomalyAdapter = new GraphAnomalyAdapter(store);
4926
+ const anomalyReport = anomalyAdapter.detect();
4927
+ return {
4928
+ avgFanOut,
4929
+ maxFanOut,
4930
+ avgCyclomaticComplexity,
4931
+ maxCyclomaticComplexity,
4932
+ avgCouplingRatio,
4933
+ testCoverage: null,
4934
+ // Coverage integration deferred -- not available from graph
4935
+ anomalyOutlierCount: anomalyReport.summary.outlierCount,
4936
+ articulationPointCount: anomalyReport.summary.articulationPointCount
4937
+ };
4938
+ } catch {
4939
+ return ZERO_METRICS;
4940
+ }
4941
+ }
4942
+ async function captureHealthSnapshot(projectPath) {
4943
+ let gitHead = "";
4944
+ try {
4945
+ gitHead = execSync("git rev-parse HEAD", {
4946
+ cwd: projectPath,
4947
+ encoding: "utf-8",
4948
+ stdio: ["pipe", "pipe", "pipe"]
4949
+ }).trim();
4950
+ } catch {
4951
+ }
4952
+ const [checks, metrics] = await Promise.all([
4953
+ runHealthChecks(projectPath),
4954
+ runGraphMetrics(projectPath)
4955
+ ]);
4956
+ const signals = deriveSignals(checks, metrics);
4957
+ const snapshot = {
4958
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
4959
+ gitHead,
4960
+ projectPath,
4961
+ checks,
4962
+ metrics,
4963
+ signals
4964
+ };
4965
+ saveCachedSnapshot(snapshot, projectPath);
4966
+ return snapshot;
4967
+ }
4968
+
5016
4969
  // src/mcp/tools/search-skills.ts
5017
4970
  var searchSkillsDefinition = {
5018
4971
  name: "search_skills",
@@ -5045,10 +4998,12 @@ async function handleSearchSkills(input) {
5045
4998
  const tierOverrides = configResult.ok ? configResult.value.skills?.tierOverrides : void 0;
5046
4999
  const index = loadOrRebuildIndex(platform, projectRoot, tierOverrides);
5047
5000
  const profile = loadOrGenerateProfile(projectRoot);
5001
+ const snapshot = loadCachedSnapshot(projectRoot);
5002
+ const freshSnapshot = snapshot && isSnapshotFresh(snapshot, projectRoot) ? snapshot : void 0;
5048
5003
  const queryTerms = query.toLowerCase().split(/\s+/).filter((t) => t.length > 2);
5049
5004
  const results = [];
5050
5005
  for (const [name, entry] of Object.entries(index.skills)) {
5051
- const score = scoreSkill(entry, queryTerms, profile, [], name);
5006
+ const score = scoreSkill(entry, queryTerms, profile, [], name, freshSnapshot);
5052
5007
  if (score > 0 || queryTerms.length === 0) {
5053
5008
  results.push({
5054
5009
  name,
@@ -5072,6 +5027,125 @@ async function handleSearchSkills(input) {
5072
5027
  };
5073
5028
  }
5074
5029
 
5030
+ // src/mcp/tools/decay-trends.ts
5031
+ var getDecayTrendsDefinition = {
5032
+ name: "get_decay_trends",
5033
+ description: 'Get architecture decay trends over time. Returns stability score history and per-category trend analysis from timeline snapshots. Use to answer questions like "is the architecture decaying?" or "which metrics are getting worse?"',
5034
+ inputSchema: {
5035
+ type: "object",
5036
+ properties: {
5037
+ path: { type: "string", description: "Path to project root" },
5038
+ last: {
5039
+ type: "number",
5040
+ description: "Number of recent snapshots to analyze (default: 10)"
5041
+ },
5042
+ since: {
5043
+ type: "string",
5044
+ description: "Show trends since this ISO date (e.g., 2026-01-01)"
5045
+ },
5046
+ category: {
5047
+ type: "string",
5048
+ description: "Filter to a single metric category",
5049
+ enum: [
5050
+ "circular-deps",
5051
+ "layer-violations",
5052
+ "complexity",
5053
+ "coupling",
5054
+ "forbidden-imports",
5055
+ "module-size",
5056
+ "dependency-depth"
5057
+ ]
5058
+ }
5059
+ },
5060
+ required: ["path"]
5061
+ }
5062
+ };
5063
+ async function handleGetDecayTrends(input) {
5064
+ let projectPath;
5065
+ try {
5066
+ projectPath = sanitizePath(input.path);
5067
+ } catch (error) {
5068
+ return {
5069
+ content: [
5070
+ {
5071
+ type: "text",
5072
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
5073
+ }
5074
+ ],
5075
+ isError: true
5076
+ };
5077
+ }
5078
+ try {
5079
+ const core = await import("./dist-LCR2IO7U.js");
5080
+ const { TimelineManager } = core;
5081
+ const manager = new TimelineManager(projectPath);
5082
+ const timeline = manager.load();
5083
+ if (timeline.snapshots.length === 0) {
5084
+ return {
5085
+ content: [
5086
+ {
5087
+ type: "text",
5088
+ text: "No architecture snapshots found. Run `harness snapshot capture` to create the first snapshot."
5089
+ }
5090
+ ]
5091
+ };
5092
+ }
5093
+ const trendOptions = {};
5094
+ if (input.last !== void 0) trendOptions.last = input.last;
5095
+ if (input.since !== void 0) trendOptions.since = input.since;
5096
+ const trends = manager.trends(trendOptions);
5097
+ if (input.category) {
5098
+ const categoryTrend = trends.categories[input.category];
5099
+ if (!categoryTrend) {
5100
+ return {
5101
+ content: [
5102
+ {
5103
+ type: "text",
5104
+ text: `No trend data for category "${input.category}".`
5105
+ }
5106
+ ]
5107
+ };
5108
+ }
5109
+ return {
5110
+ content: [
5111
+ {
5112
+ type: "text",
5113
+ text: JSON.stringify(
5114
+ {
5115
+ category: input.category,
5116
+ trend: categoryTrend,
5117
+ snapshotCount: trends.snapshotCount,
5118
+ from: trends.from,
5119
+ to: trends.to
5120
+ },
5121
+ null,
5122
+ 2
5123
+ )
5124
+ }
5125
+ ]
5126
+ };
5127
+ }
5128
+ return {
5129
+ content: [
5130
+ {
5131
+ type: "text",
5132
+ text: JSON.stringify(trends, null, 2)
5133
+ }
5134
+ ]
5135
+ };
5136
+ } catch (error) {
5137
+ return {
5138
+ content: [
5139
+ {
5140
+ type: "text",
5141
+ text: `Error computing decay trends: ${error instanceof Error ? error.message : String(error)}`
5142
+ }
5143
+ ],
5144
+ isError: true
5145
+ };
5146
+ }
5147
+ }
5148
+
5075
5149
  // src/mcp/tools/code-nav.ts
5076
5150
  var codeOutlineDefinition = {
5077
5151
  name: "code_outline",
@@ -5107,7 +5181,7 @@ async function handleCodeOutline(input) {
5107
5181
  };
5108
5182
  }
5109
5183
  try {
5110
- const { getOutline, formatOutline, EXTENSION_MAP } = await import("./dist-KV2ICL5X.js");
5184
+ const { getOutline, formatOutline, EXTENSION_MAP } = await import("./dist-LCR2IO7U.js");
5111
5185
  const { stat } = await import("fs/promises");
5112
5186
  const stats = await stat(targetPath).catch(() => null);
5113
5187
  if (stats?.isFile()) {
@@ -5187,7 +5261,7 @@ async function handleCodeSearch(input) {
5187
5261
  };
5188
5262
  }
5189
5263
  try {
5190
- const { searchSymbols } = await import("./dist-KV2ICL5X.js");
5264
+ const { searchSymbols } = await import("./dist-LCR2IO7U.js");
5191
5265
  const result = await searchSymbols(input.query, directory, input.glob);
5192
5266
  const lines = [`Search: "${result.query}" \u2014 ${result.matches.length} matches`];
5193
5267
  for (const match of result.matches) {
@@ -5256,7 +5330,7 @@ async function handleCodeUnfold(input) {
5256
5330
  }
5257
5331
  try {
5258
5332
  if (input.symbol) {
5259
- const { unfoldSymbol } = await import("./dist-KV2ICL5X.js");
5333
+ const { unfoldSymbol } = await import("./dist-LCR2IO7U.js");
5260
5334
  const result = await unfoldSymbol(filePath, input.symbol);
5261
5335
  const header = result.warning ? `${result.file}:${result.startLine}-${result.endLine} ${result.warning}
5262
5336
  ` : `${result.file}:${result.startLine}-${result.endLine}
@@ -5264,7 +5338,7 @@ async function handleCodeUnfold(input) {
5264
5338
  return { content: [{ type: "text", text: header + result.content }] };
5265
5339
  }
5266
5340
  if (input.startLine != null && input.endLine != null) {
5267
- const { unfoldRange } = await import("./dist-KV2ICL5X.js");
5341
+ const { unfoldRange } = await import("./dist-LCR2IO7U.js");
5268
5342
  const result = await unfoldRange(filePath, input.startLine, input.endLine);
5269
5343
  const header = `${result.file}:${result.startLine}-${result.endLine}
5270
5344
  `;
@@ -5292,6 +5366,501 @@ async function handleCodeUnfold(input) {
5292
5366
  }
5293
5367
  }
5294
5368
 
5369
+ // src/mcp/tools/traceability.ts
5370
+ var checkTraceabilityDefinition = {
5371
+ name: "check_traceability",
5372
+ description: "Check requirement-to-code-to-test traceability for a spec or all specs",
5373
+ inputSchema: {
5374
+ type: "object",
5375
+ properties: {
5376
+ path: { type: "string", description: "Path to project root" },
5377
+ spec: { type: "string", description: "Specific spec file path to check" },
5378
+ feature: { type: "string", description: "Feature name filter" },
5379
+ mode: {
5380
+ type: "string",
5381
+ enum: ["summary", "detailed"],
5382
+ description: "Response density: summary returns coverage stats only, detailed returns full requirement list. Default: summary"
5383
+ }
5384
+ },
5385
+ required: ["path"]
5386
+ }
5387
+ };
5388
+ async function handleCheckTraceability(input) {
5389
+ try {
5390
+ const projectPath = sanitizePath(input.path);
5391
+ const store = await loadGraphStore(projectPath);
5392
+ if (!store) {
5393
+ return {
5394
+ content: [
5395
+ {
5396
+ type: "text",
5397
+ text: "No graph found. Run `harness scan` or use `ingest_source` tool first."
5398
+ }
5399
+ ],
5400
+ isError: true
5401
+ };
5402
+ }
5403
+ const { queryTraceability } = await import("./dist-IA6XYKNO.js");
5404
+ const options = {};
5405
+ if (input.spec) options.specPath = input.spec;
5406
+ if (input.feature) options.featureName = input.feature;
5407
+ const results = queryTraceability(store, options);
5408
+ if (results.length === 0) {
5409
+ return {
5410
+ content: [
5411
+ {
5412
+ type: "text",
5413
+ text: JSON.stringify({
5414
+ status: "no-requirements",
5415
+ message: "No requirement nodes found in graph. Ingest specs with RequirementIngestor first."
5416
+ })
5417
+ }
5418
+ ]
5419
+ };
5420
+ }
5421
+ const mode = input.mode ?? "summary";
5422
+ if (mode === "summary") {
5423
+ const summaries = results.map((r) => ({
5424
+ specPath: r.specPath,
5425
+ featureName: r.featureName,
5426
+ ...r.summary
5427
+ }));
5428
+ const totals = summaries.reduce(
5429
+ (acc, s) => ({
5430
+ total: acc.total + s.total,
5431
+ withCode: acc.withCode + s.withCode,
5432
+ withTests: acc.withTests + s.withTests,
5433
+ fullyTraced: acc.fullyTraced + s.fullyTraced,
5434
+ untraceable: acc.untraceable + s.untraceable
5435
+ }),
5436
+ { total: 0, withCode: 0, withTests: 0, fullyTraced: 0, untraceable: 0 }
5437
+ );
5438
+ const overallCoverage = totals.total > 0 ? Math.round(totals.fullyTraced / totals.total * 100) : 0;
5439
+ return {
5440
+ content: [
5441
+ {
5442
+ type: "text",
5443
+ text: JSON.stringify({
5444
+ mode: "summary",
5445
+ overallCoverage,
5446
+ totals,
5447
+ specs: summaries
5448
+ })
5449
+ }
5450
+ ]
5451
+ };
5452
+ }
5453
+ return {
5454
+ content: [
5455
+ {
5456
+ type: "text",
5457
+ text: JSON.stringify({
5458
+ mode: "detailed",
5459
+ results
5460
+ })
5461
+ }
5462
+ ]
5463
+ };
5464
+ } catch (error) {
5465
+ return {
5466
+ content: [
5467
+ {
5468
+ type: "text",
5469
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
5470
+ }
5471
+ ],
5472
+ isError: true
5473
+ };
5474
+ }
5475
+ }
5476
+
5477
+ // src/mcp/tools/predict-failures.ts
5478
+ var predictFailuresDefinition = {
5479
+ name: "predict_failures",
5480
+ description: "Predict which architectural constraints will break and when, based on decay trends and planned roadmap features. Requires at least 3 timeline snapshots.",
5481
+ inputSchema: {
5482
+ type: "object",
5483
+ properties: {
5484
+ path: { type: "string", description: "Path to project root" },
5485
+ horizon: {
5486
+ type: "number",
5487
+ description: "Forecast horizon in weeks (default: 12)"
5488
+ },
5489
+ category: {
5490
+ type: "string",
5491
+ description: "Filter to a single metric category",
5492
+ enum: [
5493
+ "circular-deps",
5494
+ "layer-violations",
5495
+ "complexity",
5496
+ "coupling",
5497
+ "forbidden-imports",
5498
+ "module-size",
5499
+ "dependency-depth"
5500
+ ]
5501
+ },
5502
+ includeRoadmap: {
5503
+ type: "boolean",
5504
+ description: "Include roadmap spec impact in forecasts (default: true)"
5505
+ }
5506
+ },
5507
+ required: ["path"]
5508
+ }
5509
+ };
5510
+ async function handlePredictFailures(input) {
5511
+ let projectPath;
5512
+ try {
5513
+ projectPath = sanitizePath(input.path);
5514
+ } catch (error) {
5515
+ return {
5516
+ content: [
5517
+ {
5518
+ type: "text",
5519
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
5520
+ }
5521
+ ],
5522
+ isError: true
5523
+ };
5524
+ }
5525
+ try {
5526
+ const core = await import("./dist-LCR2IO7U.js");
5527
+ const { TimelineManager, PredictionEngine, SpecImpactEstimator } = core;
5528
+ const manager = new TimelineManager(projectPath);
5529
+ const includeRoadmap = input.includeRoadmap !== false;
5530
+ const estimator = includeRoadmap ? new SpecImpactEstimator(projectPath) : null;
5531
+ const engine = new PredictionEngine(projectPath, manager, estimator);
5532
+ const opts = {
5533
+ includeRoadmap
5534
+ };
5535
+ if (input.horizon !== void 0) opts.horizon = input.horizon;
5536
+ if (input.category) opts.categories = [input.category];
5537
+ const result = engine.predict(opts);
5538
+ return {
5539
+ content: [
5540
+ {
5541
+ type: "text",
5542
+ text: JSON.stringify(result, null, 2)
5543
+ }
5544
+ ]
5545
+ };
5546
+ } catch (error) {
5547
+ return {
5548
+ content: [
5549
+ {
5550
+ type: "text",
5551
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
5552
+ }
5553
+ ],
5554
+ isError: true
5555
+ };
5556
+ }
5557
+ }
5558
+
5559
+ // src/skill/recommendation-rules.ts
5560
+ var FALLBACK_RULES = {
5561
+ "enforce-architecture": [
5562
+ { signal: "circular-deps", hard: true },
5563
+ { signal: "layer-violations", hard: true },
5564
+ { signal: "high-coupling", metric: "fanOut", threshold: 20, weight: 0.8 },
5565
+ { signal: "high-coupling", metric: "couplingRatio", threshold: 0.7, weight: 0.6 }
5566
+ ],
5567
+ "dependency-health": [
5568
+ { signal: "high-coupling", metric: "fanOut", threshold: 15, weight: 0.7 },
5569
+ { signal: "anomaly-outlier", weight: 0.6 },
5570
+ { signal: "articulation-point", weight: 0.5 }
5571
+ ],
5572
+ tdd: [{ signal: "low-coverage", weight: 0.9 }],
5573
+ "codebase-cleanup": [
5574
+ { signal: "dead-code", weight: 0.8 },
5575
+ { signal: "drift", weight: 0.6 }
5576
+ ],
5577
+ "security-scan": [{ signal: "security-findings", hard: true }],
5578
+ refactoring: [
5579
+ { signal: "high-complexity", metric: "cyclomaticComplexity", threshold: 15, weight: 0.8 },
5580
+ { signal: "high-coupling", metric: "couplingRatio", threshold: 0.5, weight: 0.6 }
5581
+ ],
5582
+ "detect-doc-drift": [
5583
+ { signal: "doc-gaps", weight: 0.7 },
5584
+ { signal: "drift", weight: 0.5 }
5585
+ ],
5586
+ perf: [{ signal: "perf-regression", weight: 0.8 }],
5587
+ "supply-chain-audit": [{ signal: "security-findings", weight: 0.6 }],
5588
+ "code-review": [
5589
+ { signal: "high-complexity", weight: 0.5 },
5590
+ { signal: "high-coupling", weight: 0.4 }
5591
+ ],
5592
+ integrity: [
5593
+ { signal: "drift", weight: 0.7 },
5594
+ { signal: "dead-code", weight: 0.5 }
5595
+ ],
5596
+ "soundness-review": [
5597
+ { signal: "layer-violations", weight: 0.6 },
5598
+ { signal: "circular-deps", weight: 0.5 }
5599
+ ],
5600
+ debugging: [
5601
+ { signal: "perf-regression", weight: 0.5 },
5602
+ { signal: "anomaly-outlier", weight: 0.6 }
5603
+ ],
5604
+ "hotspot-detector": [
5605
+ { signal: "high-complexity", metric: "cyclomaticComplexity", threshold: 20, weight: 0.9 },
5606
+ { signal: "anomaly-outlier", weight: 0.7 },
5607
+ { signal: "articulation-point", weight: 0.8 }
5608
+ ],
5609
+ "cleanup-dead-code": [{ signal: "dead-code", hard: true }]
5610
+ };
5611
+
5612
+ // src/skill/recommendation-engine.ts
5613
+ function resolveMetricValue(metrics, metricName) {
5614
+ switch (metricName) {
5615
+ case "fanOut":
5616
+ return metrics.maxFanOut;
5617
+ case "couplingRatio":
5618
+ return metrics.avgCouplingRatio;
5619
+ case "cyclomaticComplexity":
5620
+ return metrics.maxCyclomaticComplexity;
5621
+ case "coverage":
5622
+ return metrics.testCoverage !== null ? 100 - metrics.testCoverage : null;
5623
+ default:
5624
+ return null;
5625
+ }
5626
+ }
5627
+ function buildSkillAddressIndex(skills) {
5628
+ const index = /* @__PURE__ */ new Map();
5629
+ for (const [name, entry] of Object.entries(skills)) {
5630
+ const addresses = entry.addresses.length > 0 ? entry.addresses : FALLBACK_RULES[name] ?? [];
5631
+ index.set(name, { addresses, dependsOn: entry.dependsOn });
5632
+ }
5633
+ for (const [name, addresses] of Object.entries(FALLBACK_RULES)) {
5634
+ if (!index.has(name)) {
5635
+ index.set(name, { addresses, dependsOn: [] });
5636
+ }
5637
+ }
5638
+ return index;
5639
+ }
5640
+ function matchHardRules(snapshot, skillIndex) {
5641
+ const activeSignals = new Set(snapshot.signals);
5642
+ const results = [];
5643
+ for (const [skillName, entry] of skillIndex) {
5644
+ const hardAddresses = entry.addresses.filter((a) => a.hard === true);
5645
+ const matchedSignals = [];
5646
+ const reasons = [];
5647
+ for (const addr of hardAddresses) {
5648
+ if (activeSignals.has(addr.signal)) {
5649
+ matchedSignals.push(addr.signal);
5650
+ reasons.push(`[CRITICAL] Signal '${addr.signal}' is active`);
5651
+ }
5652
+ }
5653
+ if (matchedSignals.length > 0) {
5654
+ results.push({
5655
+ skillName,
5656
+ score: 1,
5657
+ urgency: "critical",
5658
+ reasons,
5659
+ sequence: 0,
5660
+ // assigned later by sequencer
5661
+ triggeredBy: matchedSignals
5662
+ });
5663
+ }
5664
+ }
5665
+ return results;
5666
+ }
5667
+ function clamp(value, min, max) {
5668
+ return Math.min(Math.max(value, min), max);
5669
+ }
5670
+ var DEFAULT_WEIGHT = 0.5;
5671
+ function scoreByHealth(snapshot, skillIndex) {
5672
+ const activeSignals = new Set(snapshot.signals);
5673
+ const results = [];
5674
+ for (const [skillName, entry] of skillIndex) {
5675
+ const softAddresses = entry.addresses.filter((a) => !a.hard);
5676
+ const contributions = [];
5677
+ const triggeredBy = [];
5678
+ const reasons = [];
5679
+ for (const addr of softAddresses) {
5680
+ if (!activeSignals.has(addr.signal)) continue;
5681
+ const weight = addr.weight ?? DEFAULT_WEIGHT;
5682
+ if (addr.metric && addr.threshold !== void 0) {
5683
+ const actual = resolveMetricValue(snapshot.metrics, addr.metric);
5684
+ if (actual === null) continue;
5685
+ const distance = clamp((actual - addr.threshold) / addr.threshold, 0, 1);
5686
+ const contribution = weight * distance;
5687
+ contributions.push(contribution);
5688
+ triggeredBy.push(addr.signal);
5689
+ reasons.push(
5690
+ `${addr.metric} = ${actual} (threshold ${addr.threshold}, distance ${distance.toFixed(2)})`
5691
+ );
5692
+ } else {
5693
+ contributions.push(weight);
5694
+ triggeredBy.push(addr.signal);
5695
+ reasons.push(`Signal '${addr.signal}' is active (weight ${weight})`);
5696
+ }
5697
+ }
5698
+ if (contributions.length === 0) continue;
5699
+ const score = clamp(contributions.reduce((sum, c) => sum + c, 0) / contributions.length, 0, 1);
5700
+ const urgency = score >= 0.7 ? "recommended" : "nice-to-have";
5701
+ results.push({
5702
+ skillName,
5703
+ score: Math.round(score * 1e3) / 1e3,
5704
+ // round to 3 decimal places
5705
+ urgency,
5706
+ reasons,
5707
+ sequence: 0,
5708
+ triggeredBy: [...new Set(triggeredBy)]
5709
+ });
5710
+ }
5711
+ return results;
5712
+ }
5713
+ var DIAGNOSTIC_KEYWORDS = ["health", "detect", "analyze", "audit", "hotspot", "debugging"];
5714
+ var FIX_KEYWORDS = ["enforce", "cleanup", "fix", "refactor", "codebase"];
5715
+ var VALIDATION_KEYWORDS = ["verify", "test", "tdd", "review", "soundness", "integrity"];
5716
+ function classifyPhase(skillName) {
5717
+ const lower = skillName.toLowerCase();
5718
+ if (DIAGNOSTIC_KEYWORDS.some((kw) => lower.includes(kw))) return 0;
5719
+ if (FIX_KEYWORDS.some((kw) => lower.includes(kw))) return 1;
5720
+ if (VALIDATION_KEYWORDS.some((kw) => lower.includes(kw))) return 2;
5721
+ return 3;
5722
+ }
5723
+ function heuristicComparator(recMap) {
5724
+ return (a, b) => {
5725
+ const phaseA = classifyPhase(a);
5726
+ const phaseB = classifyPhase(b);
5727
+ if (phaseA !== phaseB) return phaseA - phaseB;
5728
+ return (recMap.get(b)?.score ?? 0) - (recMap.get(a)?.score ?? 0);
5729
+ };
5730
+ }
5731
+ function buildDepGraph(nameSet, skillDeps) {
5732
+ const inDegree = /* @__PURE__ */ new Map();
5733
+ const adjacency = /* @__PURE__ */ new Map();
5734
+ for (const name of nameSet) {
5735
+ inDegree.set(name, 0);
5736
+ adjacency.set(name, []);
5737
+ }
5738
+ for (const name of nameSet) {
5739
+ for (const dep of skillDeps.get(name) ?? []) {
5740
+ if (!nameSet.has(dep)) continue;
5741
+ adjacency.get(dep).push(name);
5742
+ inDegree.set(name, (inDegree.get(name) ?? 0) + 1);
5743
+ }
5744
+ }
5745
+ return { inDegree, adjacency };
5746
+ }
5747
+ function sequenceRecommendations(recommendations, skillDeps) {
5748
+ if (recommendations.length === 0) return [];
5749
+ const nameSet = new Set(recommendations.map((r) => r.skillName));
5750
+ const recMap = new Map(recommendations.map((r) => [r.skillName, r]));
5751
+ const compare = heuristicComparator(recMap);
5752
+ const { inDegree, adjacency } = buildDepGraph(nameSet, skillDeps);
5753
+ const sorted = [];
5754
+ let sequence = 1;
5755
+ let queue = [...nameSet].filter((n) => (inDegree.get(n) ?? 0) === 0).sort(compare);
5756
+ while (queue.length > 0) {
5757
+ const nextQueue = [];
5758
+ for (const name of queue) {
5759
+ const rec = recMap.get(name);
5760
+ rec.sequence = sequence++;
5761
+ sorted.push(rec);
5762
+ for (const dependent of adjacency.get(name) ?? []) {
5763
+ const newDeg = (inDegree.get(dependent) ?? 1) - 1;
5764
+ inDegree.set(dependent, newDeg);
5765
+ if (newDeg === 0) nextQueue.push(dependent);
5766
+ }
5767
+ }
5768
+ queue = nextQueue.sort(compare);
5769
+ }
5770
+ return sorted;
5771
+ }
5772
+ function recommend(snapshot, skills, options = {}) {
5773
+ const top = options.top ?? 5;
5774
+ if (snapshot.signals.length === 0) {
5775
+ return {
5776
+ recommendations: [],
5777
+ snapshotAge: "none",
5778
+ sequenceReasoning: "No active signals detected in health snapshot."
5779
+ };
5780
+ }
5781
+ const addressIndex = buildSkillAddressIndex(skills);
5782
+ const hardRecs = matchHardRules(snapshot, addressIndex);
5783
+ const softRecs = scoreByHealth(snapshot, addressIndex);
5784
+ const hardSkills = new Set(hardRecs.map((r) => r.skillName));
5785
+ const merged = [...hardRecs, ...softRecs.filter((r) => !hardSkills.has(r.skillName))];
5786
+ merged.sort((a, b) => b.score - a.score);
5787
+ const limited = merged.slice(0, top);
5788
+ const depMap = /* @__PURE__ */ new Map();
5789
+ for (const [name, entry] of addressIndex) {
5790
+ depMap.set(name, entry.dependsOn);
5791
+ }
5792
+ const sequenced = sequenceRecommendations(limited, depMap);
5793
+ const criticalCount = sequenced.filter((r) => r.urgency === "critical").length;
5794
+ const phases = sequenced.map((r) => `${r.sequence}. ${r.skillName}`).join(" -> ");
5795
+ const reasoning = criticalCount > 0 ? `${criticalCount} critical issue(s) detected. Sequence: ${phases}. Critical items first, then diagnostic -> fix -> validate heuristic.` : `Sequence: ${phases}. Ordered by dependencies and diagnostic -> fix -> validate heuristic.`;
5796
+ return {
5797
+ recommendations: sequenced,
5798
+ snapshotAge: "fresh",
5799
+ sequenceReasoning: reasoning
5800
+ };
5801
+ }
5802
+
5803
+ // src/mcp/tools/recommend-skills.ts
5804
+ var recommendSkillsDefinition = {
5805
+ name: "recommend_skills",
5806
+ description: "Recommend skills based on codebase health. Returns sequenced workflow with urgency markers.",
5807
+ inputSchema: {
5808
+ type: "object",
5809
+ properties: {
5810
+ path: {
5811
+ type: "string",
5812
+ description: "Project root path (defaults to cwd)"
5813
+ },
5814
+ noCache: {
5815
+ type: "boolean",
5816
+ description: "Force fresh health snapshot even if cache is fresh"
5817
+ },
5818
+ top: {
5819
+ type: "number",
5820
+ description: "Max recommendations to return (default 5)"
5821
+ }
5822
+ },
5823
+ required: []
5824
+ }
5825
+ };
5826
+ async function handleRecommendSkills(input) {
5827
+ const projectRoot = input.path || process.cwd();
5828
+ const noCache = input.noCache || false;
5829
+ const top = input.top || 5;
5830
+ let snapshot = null;
5831
+ let usedCache = false;
5832
+ if (!noCache) {
5833
+ const cached = loadCachedSnapshot(projectRoot);
5834
+ if (cached && isSnapshotFresh(cached, projectRoot)) {
5835
+ snapshot = cached;
5836
+ usedCache = true;
5837
+ }
5838
+ }
5839
+ if (!snapshot) {
5840
+ snapshot = await captureHealthSnapshot(projectRoot);
5841
+ }
5842
+ const configResult = resolveConfig();
5843
+ const tierOverrides = configResult.ok ? configResult.value.skills?.tierOverrides : void 0;
5844
+ const index = loadOrRebuildIndex("claude-code", projectRoot, tierOverrides);
5845
+ const skills = {};
5846
+ for (const [name, entry] of Object.entries(index.skills)) {
5847
+ skills[name] = { addresses: entry.addresses, dependsOn: entry.dependsOn };
5848
+ }
5849
+ const result = recommend(snapshot, skills, { top });
5850
+ const output = {
5851
+ ...result,
5852
+ snapshotAge: usedCache ? "cached" : "fresh"
5853
+ };
5854
+ return {
5855
+ content: [
5856
+ {
5857
+ type: "text",
5858
+ text: JSON.stringify(output, null, 2)
5859
+ }
5860
+ ]
5861
+ };
5862
+ }
5863
+
5295
5864
  // src/mcp/server.ts
5296
5865
  var TOOL_DEFINITIONS = [
5297
5866
  validateToolDefinition,
@@ -5342,7 +5911,11 @@ var TOOL_DEFINITIONS = [
5342
5911
  searchSkillsDefinition,
5343
5912
  codeOutlineDefinition,
5344
5913
  codeSearchDefinition,
5345
- codeUnfoldDefinition
5914
+ codeUnfoldDefinition,
5915
+ getDecayTrendsDefinition,
5916
+ checkTraceabilityDefinition,
5917
+ predictFailuresDefinition,
5918
+ recommendSkillsDefinition
5346
5919
  ].map((def) => ({ ...def, trustedOutput: true }));
5347
5920
  var TOOL_HANDLERS = {
5348
5921
  validate_project: handleValidateProject,
@@ -5393,7 +5966,11 @@ var TOOL_HANDLERS = {
5393
5966
  search_skills: handleSearchSkills,
5394
5967
  code_outline: handleCodeOutline,
5395
5968
  code_search: handleCodeSearch,
5396
- code_unfold: handleCodeUnfold
5969
+ code_unfold: handleCodeUnfold,
5970
+ get_decay_trends: handleGetDecayTrends,
5971
+ check_traceability: handleCheckTraceability,
5972
+ predict_failures: handlePredictFailures,
5973
+ recommend_skills: handleRecommendSkills
5397
5974
  };
5398
5975
  var RESOURCE_DEFINITIONS = [
5399
5976
  {
@@ -5479,7 +6056,7 @@ async function appendUpdateNotification(result, resolvedRoot) {
5479
6056
  shouldRunCheck,
5480
6057
  readCheckState,
5481
6058
  spawnBackgroundCheck
5482
- } = await import("./dist-KV2ICL5X.js");
6059
+ } = await import("./dist-LCR2IO7U.js");
5483
6060
  const { CLI_VERSION } = await import("./version-KFFPOQAX.js");
5484
6061
  const configInterval = readConfigInterval(resolvedRoot);
5485
6062
  const DEFAULT_INTERVAL = 864e5;
@@ -5561,7 +6138,12 @@ export {
5561
6138
  generateSlashCommands,
5562
6139
  handleOrphanDeletion,
5563
6140
  createGenerateSlashCommandsCommand,
6141
+ loadOrRebuildIndex,
5564
6142
  handleGetImpact,
6143
+ isSnapshotFresh,
6144
+ loadCachedSnapshot,
6145
+ captureHealthSnapshot,
6146
+ recommend,
5565
6147
  getToolDefinitions,
5566
6148
  createHarnessServer,
5567
6149
  startServer