@harness-engineering/cli 1.20.1 → 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 (159) 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-accessibility/skill.yaml +20 -0
  5. package/dist/agents/skills/claude-code/harness-code-review/skill.yaml +5 -0
  6. package/dist/agents/skills/claude-code/harness-codebase-cleanup/skill.yaml +5 -0
  7. package/dist/agents/skills/claude-code/harness-debugging/skill.yaml +5 -0
  8. package/dist/agents/skills/claude-code/harness-dependency-health/skill.yaml +9 -0
  9. package/dist/agents/skills/claude-code/harness-design/skill.yaml +20 -0
  10. package/dist/agents/skills/claude-code/harness-design-mobile/skill.yaml +20 -0
  11. package/dist/agents/skills/claude-code/harness-design-system/skill.yaml +22 -0
  12. package/dist/agents/skills/claude-code/harness-design-web/skill.yaml +22 -0
  13. package/dist/agents/skills/claude-code/harness-diagnostics/skill.yaml +19 -0
  14. package/dist/agents/skills/claude-code/harness-git-workflow/skill.yaml +15 -0
  15. package/dist/agents/skills/claude-code/harness-hotspot-detector/skill.yaml +9 -0
  16. package/dist/agents/skills/claude-code/harness-i18n/skill.yaml +22 -0
  17. package/dist/agents/skills/claude-code/harness-i18n-process/skill.yaml +15 -0
  18. package/dist/agents/skills/claude-code/harness-i18n-workflow/skill.yaml +19 -0
  19. package/dist/agents/skills/claude-code/harness-integrity/skill.yaml +5 -0
  20. package/dist/agents/skills/claude-code/harness-perf/skill.yaml +3 -0
  21. package/dist/agents/skills/claude-code/harness-perf-tdd/skill.yaml +20 -0
  22. package/dist/agents/skills/claude-code/harness-pre-commit-review/skill.yaml +18 -0
  23. package/dist/agents/skills/claude-code/harness-refactoring/skill.yaml +9 -0
  24. package/dist/agents/skills/claude-code/harness-security-review/skill.yaml +23 -0
  25. package/dist/agents/skills/claude-code/harness-security-scan/skill.yaml +3 -0
  26. package/dist/agents/skills/claude-code/harness-soundness-review/skill.yaml +5 -0
  27. package/dist/agents/skills/claude-code/harness-supply-chain-audit/skill.yaml +3 -0
  28. package/dist/agents/skills/claude-code/harness-tdd/skill.yaml +3 -0
  29. package/dist/agents/skills/codex/cleanup-dead-code/skill.yaml +3 -0
  30. package/dist/agents/skills/codex/detect-doc-drift/skill.yaml +5 -0
  31. package/dist/agents/skills/codex/enforce-architecture/skill.yaml +13 -0
  32. package/dist/agents/skills/codex/harness-accessibility/skill.yaml +20 -0
  33. package/dist/agents/skills/codex/harness-code-review/skill.yaml +5 -0
  34. package/dist/agents/skills/codex/harness-codebase-cleanup/skill.yaml +5 -0
  35. package/dist/agents/skills/codex/harness-debugging/skill.yaml +5 -0
  36. package/dist/agents/skills/codex/harness-dependency-health/skill.yaml +9 -0
  37. package/dist/agents/skills/codex/harness-design/skill.yaml +20 -0
  38. package/dist/agents/skills/codex/harness-design-mobile/skill.yaml +20 -0
  39. package/dist/agents/skills/codex/harness-design-system/skill.yaml +22 -0
  40. package/dist/agents/skills/codex/harness-design-web/skill.yaml +22 -0
  41. package/dist/agents/skills/codex/harness-diagnostics/skill.yaml +19 -0
  42. package/dist/agents/skills/codex/harness-git-workflow/skill.yaml +15 -0
  43. package/dist/agents/skills/codex/harness-hotspot-detector/skill.yaml +9 -0
  44. package/dist/agents/skills/codex/harness-i18n/skill.yaml +22 -0
  45. package/dist/agents/skills/codex/harness-i18n-process/skill.yaml +15 -0
  46. package/dist/agents/skills/codex/harness-i18n-workflow/skill.yaml +19 -0
  47. package/dist/agents/skills/codex/harness-integrity/skill.yaml +5 -0
  48. package/dist/agents/skills/codex/harness-perf/skill.yaml +3 -0
  49. package/dist/agents/skills/codex/harness-perf-tdd/skill.yaml +20 -0
  50. package/dist/agents/skills/codex/harness-pre-commit-review/skill.yaml +18 -0
  51. package/dist/agents/skills/codex/harness-refactoring/skill.yaml +9 -0
  52. package/dist/agents/skills/codex/harness-security-review/skill.yaml +23 -0
  53. package/dist/agents/skills/codex/harness-security-scan/skill.yaml +3 -0
  54. package/dist/agents/skills/codex/harness-soundness-review/skill.yaml +5 -0
  55. package/dist/agents/skills/codex/harness-supply-chain-audit/skill.yaml +3 -0
  56. package/dist/agents/skills/codex/harness-tdd/skill.yaml +3 -0
  57. package/dist/agents/skills/cursor/cleanup-dead-code/skill.yaml +3 -0
  58. package/dist/agents/skills/cursor/detect-doc-drift/skill.yaml +5 -0
  59. package/dist/agents/skills/cursor/enforce-architecture/skill.yaml +13 -0
  60. package/dist/agents/skills/cursor/harness-accessibility/skill.yaml +20 -0
  61. package/dist/agents/skills/cursor/harness-code-review/skill.yaml +5 -0
  62. package/dist/agents/skills/cursor/harness-codebase-cleanup/skill.yaml +5 -0
  63. package/dist/agents/skills/cursor/harness-debugging/skill.yaml +5 -0
  64. package/dist/agents/skills/cursor/harness-dependency-health/skill.yaml +9 -0
  65. package/dist/agents/skills/cursor/harness-design/skill.yaml +20 -0
  66. package/dist/agents/skills/cursor/harness-design-mobile/skill.yaml +20 -0
  67. package/dist/agents/skills/cursor/harness-design-system/skill.yaml +22 -0
  68. package/dist/agents/skills/cursor/harness-design-web/skill.yaml +22 -0
  69. package/dist/agents/skills/cursor/harness-diagnostics/skill.yaml +19 -0
  70. package/dist/agents/skills/cursor/harness-git-workflow/skill.yaml +15 -0
  71. package/dist/agents/skills/cursor/harness-hotspot-detector/skill.yaml +9 -0
  72. package/dist/agents/skills/cursor/harness-i18n/skill.yaml +22 -0
  73. package/dist/agents/skills/cursor/harness-i18n-process/skill.yaml +15 -0
  74. package/dist/agents/skills/cursor/harness-i18n-workflow/skill.yaml +19 -0
  75. package/dist/agents/skills/cursor/harness-integrity/skill.yaml +5 -0
  76. package/dist/agents/skills/cursor/harness-perf/skill.yaml +3 -0
  77. package/dist/agents/skills/cursor/harness-perf-tdd/skill.yaml +20 -0
  78. package/dist/agents/skills/cursor/harness-pre-commit-review/skill.yaml +18 -0
  79. package/dist/agents/skills/cursor/harness-refactoring/skill.yaml +9 -0
  80. package/dist/agents/skills/cursor/harness-security-review/skill.yaml +23 -0
  81. package/dist/agents/skills/cursor/harness-security-scan/skill.yaml +3 -0
  82. package/dist/agents/skills/cursor/harness-soundness-review/skill.yaml +5 -0
  83. package/dist/agents/skills/cursor/harness-supply-chain-audit/skill.yaml +3 -0
  84. package/dist/agents/skills/cursor/harness-tdd/skill.yaml +3 -0
  85. package/dist/agents/skills/gemini-cli/cleanup-dead-code/skill.yaml +3 -0
  86. package/dist/agents/skills/gemini-cli/detect-doc-drift/skill.yaml +5 -0
  87. package/dist/agents/skills/gemini-cli/enforce-architecture/skill.yaml +13 -0
  88. package/dist/agents/skills/gemini-cli/harness-accessibility/skill.yaml +20 -0
  89. package/dist/agents/skills/gemini-cli/harness-code-review/skill.yaml +5 -0
  90. package/dist/agents/skills/gemini-cli/harness-codebase-cleanup/skill.yaml +5 -0
  91. package/dist/agents/skills/gemini-cli/harness-debugging/skill.yaml +5 -0
  92. package/dist/agents/skills/gemini-cli/harness-dependency-health/skill.yaml +9 -0
  93. package/dist/agents/skills/gemini-cli/harness-design/skill.yaml +20 -0
  94. package/dist/agents/skills/gemini-cli/harness-design-mobile/skill.yaml +20 -0
  95. package/dist/agents/skills/gemini-cli/harness-design-system/skill.yaml +22 -0
  96. package/dist/agents/skills/gemini-cli/harness-design-web/skill.yaml +22 -0
  97. package/dist/agents/skills/gemini-cli/harness-diagnostics/skill.yaml +19 -0
  98. package/dist/agents/skills/gemini-cli/harness-git-workflow/skill.yaml +15 -0
  99. package/dist/agents/skills/gemini-cli/harness-hotspot-detector/skill.yaml +9 -0
  100. package/dist/agents/skills/gemini-cli/harness-i18n/skill.yaml +22 -0
  101. package/dist/agents/skills/gemini-cli/harness-i18n-process/skill.yaml +15 -0
  102. package/dist/agents/skills/gemini-cli/harness-i18n-workflow/skill.yaml +19 -0
  103. package/dist/agents/skills/gemini-cli/harness-integrity/skill.yaml +5 -0
  104. package/dist/agents/skills/gemini-cli/harness-perf/skill.yaml +3 -0
  105. package/dist/agents/skills/gemini-cli/harness-perf-tdd/skill.yaml +20 -0
  106. package/dist/agents/skills/gemini-cli/harness-pre-commit-review/skill.yaml +18 -0
  107. package/dist/agents/skills/gemini-cli/harness-refactoring/skill.yaml +9 -0
  108. package/dist/agents/skills/gemini-cli/harness-security-review/skill.yaml +23 -0
  109. package/dist/agents/skills/gemini-cli/harness-security-scan/skill.yaml +3 -0
  110. package/dist/agents/skills/gemini-cli/harness-soundness-review/skill.yaml +5 -0
  111. package/dist/agents/skills/gemini-cli/harness-supply-chain-audit/skill.yaml +3 -0
  112. package/dist/agents/skills/gemini-cli/harness-tdd/skill.yaml +3 -0
  113. package/dist/{agents-md-WHXVPOK2.js → agents-md-PM7LO74M.js} +2 -1
  114. package/dist/{architecture-45YCLD26.js → architecture-OVOCDTI6.js} +3 -2
  115. package/dist/assess-project-R2OZIDDS.js +9 -0
  116. package/dist/bin/harness-mcp.js +15 -13
  117. package/dist/bin/harness.js +21 -19
  118. package/dist/{check-phase-gate-2VXVOUJ5.js → check-phase-gate-7JQ6EW5R.js} +4 -3
  119. package/dist/{chunk-M6TIO6NF.js → chunk-2PAPHA77.js} +1 -1
  120. package/dist/chunk-5FBWWMY2.js +293 -0
  121. package/dist/{chunk-H6KZAGHZ.js → chunk-5QTWFO24.js} +8 -8
  122. package/dist/{chunk-CZZXE6BL.js → chunk-ASS5TD2Y.js} +1 -1
  123. package/dist/{chunk-45ZJPG24.js → chunk-B4WHXHF7.js} +1 -1
  124. package/dist/{chunk-SQY4AAKP.js → chunk-DJEBBENF.js} +1005 -408
  125. package/dist/{chunk-LEWXD6PR.js → chunk-DXYOAQQC.js} +1 -1
  126. package/dist/{chunk-PDEEQJHH.js → chunk-FSLFBLYW.js} +7 -7
  127. package/dist/{chunk-YDOGGQSF.js → chunk-GRJ7A4WT.js} +17 -2
  128. package/dist/{chunk-4U4V7A6U.js → chunk-IK5GSLW6.js} +4 -4
  129. package/dist/{chunk-LVJ7SCD7.js → chunk-J7W4LTRK.js} +2 -2
  130. package/dist/{chunk-PDOSLTWP.js → chunk-PUOMFNRO.js} +26 -3
  131. package/dist/{chunk-HKUX2X7O.js → chunk-SE4YPMLH.js} +9 -1
  132. package/dist/{chunk-HAJD5LTI.js → chunk-SOTTK27D.js} +459 -37
  133. package/dist/{chunk-LRG3B43J.js → chunk-T5QWCVGK.js} +1 -1
  134. package/dist/{dist-U7EAO6T2.js → chunk-TEZI27SA.js} +401 -60
  135. package/dist/{chunk-IC5CZSHF.js → chunk-U44JNY3Y.js} +1549 -593
  136. package/dist/{chunk-A33LHIRD.js → chunk-W6MPLFXU.js} +3 -3
  137. package/dist/{chunk-V73TEHIF.js → chunk-ZEIEUCZL.js} +9 -9
  138. package/dist/{ci-workflow-HWX5OVLI.js → ci-workflow-OTTEERPF.js} +2 -1
  139. package/dist/{create-skill-NDXQSTIK.js → create-skill-U3XCFRZN.js} +2 -2
  140. package/dist/dist-IA6XYKNO.js +92 -0
  141. package/dist/{dist-WHL3NN5S.js → dist-LCR2IO7U.js} +56 -3
  142. package/dist/{docs-FJFY7GF2.js → docs-CHAYSGOP.js} +4 -3
  143. package/dist/{engine-R5BZHIZB.js → engine-4MY2U5RZ.js} +2 -1
  144. package/dist/{entropy-Y2GE4MYS.js → entropy-AKSZG7G5.js} +3 -2
  145. package/dist/{feedback-FKZ7GMPO.js → feedback-QGCSW7SB.js} +1 -1
  146. package/dist/{generate-agent-definitions-LN3A45OL.js → generate-agent-definitions-KU6X2UQN.js} +2 -1
  147. package/dist/{graph-loader-KMHDQYDT.js → graph-loader-FJN4H7Y4.js} +1 -1
  148. package/dist/index.d.ts +58 -2
  149. package/dist/index.js +27 -23
  150. package/dist/{loader-2TBQUFWX.js → loader-AV5XEMER.js} +2 -1
  151. package/dist/{mcp-KEY575NJ.js → mcp-LWHVQRG7.js} +15 -13
  152. package/dist/{performance-BSOMMWK5.js → performance-ETZVXXGQ.js} +4 -3
  153. package/dist/{review-pipeline-KUBHP3RV.js → review-pipeline-3ZS3GJSP.js} +1 -1
  154. package/dist/{runtime-BN7KGJAO.js → runtime-KQTJRK3H.js} +2 -1
  155. package/dist/{security-3T4JGDZP.js → security-LJCLZES6.js} +1 -1
  156. package/dist/{skill-executor-XEVDGXUM.js → skill-executor-2BZQLHYN.js} +2 -2
  157. package/dist/{validate-R5WGB2AV.js → validate-4IA5RPEX.js} +3 -2
  158. package/dist/{validate-cross-check-76Z5P6EX.js → validate-cross-check-VX2BAHQI.js} +2 -1
  159. package/package.json +4 -4
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  detectEntropyDefinition,
3
3
  handleDetectEntropy
4
- } from "./chunk-4U4V7A6U.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-PDEEQJHH.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-V73TEHIF.js";
22
+ } from "./chunk-ZEIEUCZL.js";
23
23
  import {
24
24
  handleRunSecurityScan,
25
25
  runSecurityScanDefinition
26
- } from "./chunk-LEWXD6PR.js";
26
+ } from "./chunk-DXYOAQQC.js";
27
27
  import {
28
28
  handleRunCodeReview,
29
29
  runCodeReviewDefinition
30
- } from "./chunk-45ZJPG24.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-LVJ7SCD7.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-A33LHIRD.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-H6KZAGHZ.js";
61
+ } from "./chunk-5QTWFO24.js";
58
62
  import {
59
63
  TrackerConfigSchema,
60
64
  resolveConfig
61
- } from "./chunk-YDOGGQSF.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-IC5CZSHF.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-R5BZHIZB.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-2TBQUFWX.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-2TBQUFWX.js");
606
- const { generateRuntime } = await import("./runtime-BN7KGJAO.js");
607
- const { generateAgentsMd } = await import("./agents-md-WHXVPOK2.js");
608
- const { generateCIWorkflow } = await import("./ci-workflow-HWX5OVLI.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-2TBQUFWX.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,13 +846,46 @@ 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) {
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())
849
872
  )
850
873
  );
851
874
  const keywordScore = queryTerms.length > 0 ? matchedKeywords.length / queryTerms.length : 0;
875
+ let nameScore = 0;
876
+ if (skillName.length > 0 && queryTerms.length > 0) {
877
+ const nameSegments = skillName.toLowerCase().split("-").filter((s) => s.length > 2);
878
+ const matchedNameSegments = queryTerms.filter(
879
+ (term) => nameSegments.some((seg) => seg.includes(term) || term.includes(seg))
880
+ );
881
+ nameScore = matchedNameSegments.length / queryTerms.length;
882
+ }
883
+ let descScore = 0;
884
+ if (queryTerms.length > 0) {
885
+ const descLower = entry.description.toLowerCase();
886
+ const matchedDescTerms = queryTerms.filter((term) => descLower.includes(term));
887
+ descScore = matchedDescTerms.length / queryTerms.length;
888
+ }
852
889
  let stackScore = 0;
853
890
  if (profile && entry.stackSignals.length > 0) {
854
891
  const matchedSignals = entry.stackSignals.filter((signal) => {
@@ -862,19 +899,24 @@ function scoreSkill(entry, queryTerms, profile, recentFiles) {
862
899
  let recencyBoost = 0;
863
900
  if (recentFiles.length > 0) {
864
901
  const hasRecentMatch = entry.stackSignals.some((signal) => {
865
- const cleanSignal = signal.replace(/\*/g, "").replace(/\*\*/g, "");
902
+ const cleanSignal = signal.replace(/\*/g, "");
866
903
  return recentFiles.some((f) => f.includes(cleanSignal));
867
904
  });
868
905
  recencyBoost = hasRecentMatch ? 1 : 0;
869
906
  }
870
- return 0.5 * keywordScore + 0.3 * stackScore + 0.2 * 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;
871
913
  }
872
914
  function suggest(index, taskDescription, profile, recentFiles, config) {
873
915
  const queryTerms = taskDescription.toLowerCase().split(/\s+/).filter((t) => t.length > 2);
874
916
  const scored = [];
875
917
  for (const [name, entry] of Object.entries(index.skills)) {
876
918
  if (config?.neverSuggest?.includes(name)) continue;
877
- const score = scoreSkill(entry, queryTerms, profile, recentFiles);
919
+ const score = scoreSkill(entry, queryTerms, profile, recentFiles, name);
878
920
  const isForced = config?.alwaysSuggest?.includes(name);
879
921
  if (score >= 0.4 || isForced) {
880
922
  scored.push({
@@ -936,7 +978,9 @@ function parseSkillEntry(yamlPath, source, tierOverrides) {
936
978
  stackSignals: meta.stack_signals ?? [],
937
979
  cognitiveMode: meta.cognitive_mode,
938
980
  phases: (meta.phases ?? []).map((p) => p.name),
939
- source
981
+ source,
982
+ addresses: meta.addresses ?? [],
983
+ dependsOn: meta.depends_on ?? []
940
984
  };
941
985
  }
942
986
  function scanDirectory(dir, source, index, tierOverrides) {
@@ -1160,7 +1204,7 @@ var createSkillDefinition = {
1160
1204
  };
1161
1205
  async function handleCreateSkill(input) {
1162
1206
  try {
1163
- const { generateSkillFiles } = await import("./create-skill-NDXQSTIK.js");
1207
+ const { generateSkillFiles } = await import("./create-skill-U3XCFRZN.js");
1164
1208
  const result = generateSkillFiles({
1165
1209
  name: input.name,
1166
1210
  description: input.description,
@@ -1278,7 +1322,7 @@ async function autoSyncRoadmap(projectPath) {
1278
1322
  try {
1279
1323
  const roadmapFile = path11.join(projectPath, "docs", "roadmap.md");
1280
1324
  if (!fs10.existsSync(roadmapFile)) return;
1281
- const { parseRoadmap, serializeRoadmap, syncRoadmap, applySyncChanges } = await import("./dist-WHL3NN5S.js");
1325
+ const { parseRoadmap, serializeRoadmap, syncRoadmap, applySyncChanges } = await import("./dist-LCR2IO7U.js");
1282
1326
  const raw = fs10.readFileSync(roadmapFile, "utf-8");
1283
1327
  const parseResult = parseRoadmap(raw);
1284
1328
  if (!parseResult.ok) return;
@@ -1305,7 +1349,7 @@ async function triggerExternalSync(projectPath, roadmapFile) {
1305
1349
  }
1306
1350
  const token = process.env.GITHUB_TOKEN;
1307
1351
  if (!token) return;
1308
- const { fullSync, GitHubIssuesSyncAdapter } = await import("./dist-WHL3NN5S.js");
1352
+ const { fullSync, GitHubIssuesSyncAdapter } = await import("./dist-LCR2IO7U.js");
1309
1353
  const adapter = new GitHubIssuesSyncAdapter({
1310
1354
  token,
1311
1355
  config: trackerConfig
@@ -1417,12 +1461,12 @@ var manageStateDefinition = {
1417
1461
  }
1418
1462
  };
1419
1463
  async function handleShow(projectPath, input) {
1420
- const { loadState } = await import("./dist-WHL3NN5S.js");
1464
+ const { loadState } = await import("./dist-LCR2IO7U.js");
1421
1465
  return resultToMcpResponse(await loadState(projectPath, input.stream, input.session));
1422
1466
  }
1423
1467
  async function handleLearn(projectPath, input) {
1424
1468
  if (!input.learning) return mcpError("Error: learning is required for learn action");
1425
- const { appendLearning } = await import("./dist-WHL3NN5S.js");
1469
+ const { appendLearning } = await import("./dist-LCR2IO7U.js");
1426
1470
  const result = await appendLearning(
1427
1471
  projectPath,
1428
1472
  input.learning,
@@ -1437,7 +1481,7 @@ async function handleLearn(projectPath, input) {
1437
1481
  async function handleFailure(projectPath, input) {
1438
1482
  if (!input.description) return mcpError("Error: description is required for failure action");
1439
1483
  if (!input.failureType) return mcpError("Error: failureType is required for failure action");
1440
- const { appendFailure } = await import("./dist-WHL3NN5S.js");
1484
+ const { appendFailure } = await import("./dist-LCR2IO7U.js");
1441
1485
  const result = await appendFailure(
1442
1486
  projectPath,
1443
1487
  input.description,
@@ -1450,24 +1494,24 @@ async function handleFailure(projectPath, input) {
1450
1494
  return resultToMcpResponse(Ok({ recorded: true }));
1451
1495
  }
1452
1496
  async function handleArchive(projectPath, input) {
1453
- const { archiveFailures } = await import("./dist-WHL3NN5S.js");
1497
+ const { archiveFailures } = await import("./dist-LCR2IO7U.js");
1454
1498
  const result = await archiveFailures(projectPath, input.stream, input.session);
1455
1499
  if (!result.ok) return resultToMcpResponse(result);
1456
1500
  return resultToMcpResponse(Ok({ archived: true }));
1457
1501
  }
1458
1502
  async function handleReset(projectPath, input) {
1459
- const { saveState, DEFAULT_STATE } = await import("./dist-WHL3NN5S.js");
1503
+ const { saveState, DEFAULT_STATE } = await import("./dist-LCR2IO7U.js");
1460
1504
  const result = await saveState(projectPath, { ...DEFAULT_STATE }, input.stream, input.session);
1461
1505
  if (!result.ok) return resultToMcpResponse(result);
1462
1506
  return resultToMcpResponse(Ok({ reset: true }));
1463
1507
  }
1464
1508
  async function handleGate(projectPath, _input) {
1465
- const { runMechanicalGate } = await import("./dist-WHL3NN5S.js");
1509
+ const { runMechanicalGate } = await import("./dist-LCR2IO7U.js");
1466
1510
  return resultToMcpResponse(await runMechanicalGate(projectPath));
1467
1511
  }
1468
1512
  async function handleSaveHandoff(projectPath, input) {
1469
1513
  if (!input.handoff) return mcpError("Error: handoff is required for save-handoff action");
1470
- const { saveHandoff } = await import("./dist-WHL3NN5S.js");
1514
+ const { saveHandoff } = await import("./dist-LCR2IO7U.js");
1471
1515
  const result = await saveHandoff(
1472
1516
  projectPath,
1473
1517
  input.handoff,
@@ -1479,7 +1523,7 @@ async function handleSaveHandoff(projectPath, input) {
1479
1523
  return resultToMcpResponse(Ok({ saved: true }));
1480
1524
  }
1481
1525
  async function handleLoadHandoff(projectPath, input) {
1482
- const { loadHandoff } = await import("./dist-WHL3NN5S.js");
1526
+ const { loadHandoff } = await import("./dist-LCR2IO7U.js");
1483
1527
  return resultToMcpResponse(await loadHandoff(projectPath, input.stream, input.session));
1484
1528
  }
1485
1529
  async function handleAppendEntry(projectPath, input) {
@@ -1487,7 +1531,7 @@ async function handleAppendEntry(projectPath, input) {
1487
1531
  if (!input.section) return mcpError("Error: section is required for append_entry action");
1488
1532
  if (!input.authorSkill) return mcpError("Error: authorSkill is required for append_entry action");
1489
1533
  if (!input.content) return mcpError("Error: content is required for append_entry action");
1490
- const { appendSessionEntry } = await import("./dist-WHL3NN5S.js");
1534
+ const { appendSessionEntry } = await import("./dist-LCR2IO7U.js");
1491
1535
  const result = await appendSessionEntry(
1492
1536
  projectPath,
1493
1537
  input.session,
@@ -1503,7 +1547,7 @@ async function handleUpdateEntryStatus(projectPath, input) {
1503
1547
  if (!input.entryId) return mcpError("Error: entryId is required for update_entry_status action");
1504
1548
  if (!input.newStatus)
1505
1549
  return mcpError("Error: newStatus is required for update_entry_status action");
1506
- const { updateSessionEntryStatus } = await import("./dist-WHL3NN5S.js");
1550
+ const { updateSessionEntryStatus } = await import("./dist-LCR2IO7U.js");
1507
1551
  const result = await updateSessionEntryStatus(
1508
1552
  projectPath,
1509
1553
  input.session,
@@ -1516,7 +1560,7 @@ async function handleUpdateEntryStatus(projectPath, input) {
1516
1560
  async function handleReadSection(projectPath, input) {
1517
1561
  if (!input.session) return mcpError("Error: session is required for read_section action");
1518
1562
  if (!input.section) return mcpError("Error: section is required for read_section action");
1519
- const { readSessionSection } = await import("./dist-WHL3NN5S.js");
1563
+ const { readSessionSection } = await import("./dist-LCR2IO7U.js");
1520
1564
  const result = await readSessionSection(
1521
1565
  projectPath,
1522
1566
  input.session,
@@ -1526,13 +1570,13 @@ async function handleReadSection(projectPath, input) {
1526
1570
  }
1527
1571
  async function handleReadSections(projectPath, input) {
1528
1572
  if (!input.session) return mcpError("Error: session is required for read_sections action");
1529
- const { readSessionSections } = await import("./dist-WHL3NN5S.js");
1573
+ const { readSessionSections } = await import("./dist-LCR2IO7U.js");
1530
1574
  const result = await readSessionSections(projectPath, input.session);
1531
1575
  return resultToMcpResponse(result);
1532
1576
  }
1533
1577
  async function handleArchiveSession(projectPath, input) {
1534
1578
  if (!input.session) return mcpError("Error: session is required for archive_session action");
1535
- const { archiveSession } = await import("./dist-WHL3NN5S.js");
1579
+ const { archiveSession } = await import("./dist-LCR2IO7U.js");
1536
1580
  const result = await archiveSession(projectPath, input.session);
1537
1581
  if (!result.ok) return resultToMcpResponse(result);
1538
1582
  await autoSyncRoadmap(projectPath);
@@ -1596,7 +1640,7 @@ var listStreamsDefinition = {
1596
1640
  };
1597
1641
  async function handleListStreams(input) {
1598
1642
  try {
1599
- const { listStreams, loadStreamIndex } = await import("./dist-WHL3NN5S.js");
1643
+ const { listStreams, loadStreamIndex } = await import("./dist-LCR2IO7U.js");
1600
1644
  const projectPath = sanitizePath(input.path);
1601
1645
  const indexResult = await loadStreamIndex(projectPath);
1602
1646
  const streamsResult = await listStreams(projectPath);
@@ -1634,7 +1678,7 @@ var checkPhaseGateDefinition = {
1634
1678
  };
1635
1679
  async function handleCheckPhaseGate(input) {
1636
1680
  try {
1637
- const { runCheckPhaseGate } = await import("./check-phase-gate-2VXVOUJ5.js");
1681
+ const { runCheckPhaseGate } = await import("./check-phase-gate-7JQ6EW5R.js");
1638
1682
  const result = await runCheckPhaseGate({ cwd: sanitizePath(input.path) });
1639
1683
  if (result.ok) {
1640
1684
  return { content: [{ type: "text", text: JSON.stringify(result.value) }] };
@@ -1690,7 +1734,7 @@ async function handleValidateCrossCheck(input) {
1690
1734
  };
1691
1735
  }
1692
1736
  try {
1693
- const { runCrossCheck } = await import("./validate-cross-check-76Z5P6EX.js");
1737
+ const { runCrossCheck } = await import("./validate-cross-check-VX2BAHQI.js");
1694
1738
  const specsDir = path12.resolve(projectPath, input.specsDir ?? "docs/specs");
1695
1739
  if (!specsDir.startsWith(projectPath)) {
1696
1740
  return {
@@ -2335,7 +2379,7 @@ async function handleGenerateSlashCommands(input) {
2335
2379
  // src/mcp/resources/state.ts
2336
2380
  async function getStateResource(projectRoot) {
2337
2381
  try {
2338
- const { loadState, migrateToStreams } = await import("./dist-WHL3NN5S.js");
2382
+ const { loadState, migrateToStreams } = await import("./dist-LCR2IO7U.js");
2339
2383
  await migrateToStreams(projectRoot);
2340
2384
  const result = await loadState(projectRoot);
2341
2385
  if (result.ok) {
@@ -2423,7 +2467,7 @@ async function handleQueryGraph(input) {
2423
2467
  const projectPath = sanitizePath(input.path);
2424
2468
  const store = await loadGraphStore(projectPath);
2425
2469
  if (!store) return graphNotFoundError();
2426
- const { ContextQL } = await import("./dist-U7EAO6T2.js");
2470
+ const { ContextQL } = await import("./dist-IA6XYKNO.js");
2427
2471
  const cql = new ContextQL(store);
2428
2472
  const result = cql.execute({
2429
2473
  rootNodeIds: input.rootNodeIds,
@@ -2514,7 +2558,7 @@ async function handleSearchSimilar(input) {
2514
2558
  const projectPath = sanitizePath(input.path);
2515
2559
  const store = await loadGraphStore(projectPath);
2516
2560
  if (!store) return graphNotFoundError();
2517
- const { FusionLayer } = await import("./dist-U7EAO6T2.js");
2561
+ const { FusionLayer } = await import("./dist-IA6XYKNO.js");
2518
2562
  const fusion = new FusionLayer(store);
2519
2563
  const results = fusion.search(input.query, input.topK ?? 10);
2520
2564
  if (input.mode === "summary") {
@@ -2569,7 +2613,7 @@ async function handleFindContextFor(input) {
2569
2613
  const projectPath = sanitizePath(input.path);
2570
2614
  const store = await loadGraphStore(projectPath);
2571
2615
  if (!store) return graphNotFoundError();
2572
- const { FusionLayer, ContextQL } = await import("./dist-U7EAO6T2.js");
2616
+ const { FusionLayer, ContextQL } = await import("./dist-IA6XYKNO.js");
2573
2617
  const fusion = new FusionLayer(store);
2574
2618
  const cql = new ContextQL(store);
2575
2619
  const tokenBudget = input.tokenBudget ?? 4e3;
@@ -2665,7 +2709,7 @@ async function handleGetRelationships(input) {
2665
2709
  const projectPath = sanitizePath(input.path);
2666
2710
  const store = await loadGraphStore(projectPath);
2667
2711
  if (!store) return graphNotFoundError();
2668
- const { ContextQL } = await import("./dist-U7EAO6T2.js");
2712
+ const { ContextQL } = await import("./dist-IA6XYKNO.js");
2669
2713
  const cql = new ContextQL(store);
2670
2714
  const direction = input.direction ?? "both";
2671
2715
  const bidirectional = direction === "both" || direction === "inbound";
@@ -2771,7 +2815,7 @@ async function handleGetImpact(input) {
2771
2815
  const projectPath = sanitizePath(input.path);
2772
2816
  const store = await loadGraphStore(projectPath);
2773
2817
  if (!store) return graphNotFoundError();
2774
- const { ContextQL } = await import("./dist-U7EAO6T2.js");
2818
+ const { ContextQL } = await import("./dist-IA6XYKNO.js");
2775
2819
  let targetNodeId = input.nodeId;
2776
2820
  if (!targetNodeId && input.filePath) {
2777
2821
  const fileNodes = store.findNodes({ type: "file" });
@@ -2903,9 +2947,9 @@ async function handleIngestSource(input) {
2903
2947
  try {
2904
2948
  const projectPath = sanitizePath(input.path);
2905
2949
  const graphDir = path16.join(projectPath, ".harness", "graph");
2906
- const { GraphStore, CodeIngestor, TopologicalLinker, KnowledgeIngestor, GitIngestor } = await import("./dist-U7EAO6T2.js");
2907
- const fs16 = await import("fs/promises");
2908
- 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 });
2909
2953
  const store = new GraphStore();
2910
2954
  await store.load(graphDir);
2911
2955
  const results = [];
@@ -2978,7 +3022,7 @@ async function handleDetectAnomalies(input) {
2978
3022
  const projectPath = sanitizePath(input.path);
2979
3023
  const store = await loadGraphStore(projectPath);
2980
3024
  if (!store) return graphNotFoundError();
2981
- const { GraphAnomalyAdapter } = await import("./dist-U7EAO6T2.js");
3025
+ const { GraphAnomalyAdapter } = await import("./dist-IA6XYKNO.js");
2982
3026
  const adapter = new GraphAnomalyAdapter(store);
2983
3027
  const report = adapter.detect({
2984
3028
  ...input.threshold !== void 0 && { threshold: input.threshold },
@@ -3018,7 +3062,7 @@ async function handleAskGraph(input) {
3018
3062
  const projectPath = sanitizePath(input.path);
3019
3063
  const store = await loadGraphStore(projectPath);
3020
3064
  if (!store) return graphNotFoundError();
3021
- const { askGraph } = await import("./dist-U7EAO6T2.js");
3065
+ const { askGraph } = await import("./dist-IA6XYKNO.js");
3022
3066
  const result = await askGraph(store, input.question);
3023
3067
  return {
3024
3068
  content: [{ type: "text", text: JSON.stringify(result) }]
@@ -3154,7 +3198,7 @@ var generateAgentDefinitionsDefinition = {
3154
3198
  }
3155
3199
  };
3156
3200
  async function handleGenerateAgentDefinitions(input) {
3157
- const { generateAgentDefinitions } = await import("./generate-agent-definitions-LN3A45OL.js");
3201
+ const { generateAgentDefinitions } = await import("./generate-agent-definitions-KU6X2UQN.js");
3158
3202
  const platforms = input.platform === "all" || !input.platform ? ["claude-code", "gemini-cli"] : [input.platform];
3159
3203
  const results = generateAgentDefinitions({
3160
3204
  platforms: [...platforms],
@@ -3449,32 +3493,42 @@ function handleSync(projectPath, input, deps) {
3449
3493
  }
3450
3494
  return resultToMcpResponse(Ok2({ changes, applied: false }));
3451
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
+ }
3452
3520
  async function handleManageRoadmap(input) {
3453
3521
  try {
3454
- const { parseRoadmap, serializeRoadmap, syncRoadmap, applySyncChanges } = await import("./dist-WHL3NN5S.js");
3522
+ const { parseRoadmap, serializeRoadmap, syncRoadmap, applySyncChanges } = await import("./dist-LCR2IO7U.js");
3455
3523
  const { Ok: Ok2 } = await import("./dist-USY2C5JL.js");
3456
3524
  const projectPath = sanitizePath(input.path);
3457
3525
  const deps = { parseRoadmap, serializeRoadmap, syncRoadmap, applySyncChanges, Ok: Ok2 };
3458
- switch (input.action) {
3459
- case "show":
3460
- return handleShow2(projectPath, input, deps);
3461
- case "add":
3462
- return handleAdd(projectPath, input, deps);
3463
- case "update":
3464
- return handleUpdate(projectPath, input, deps);
3465
- case "remove":
3466
- return handleRemove(projectPath, input, deps);
3467
- case "query":
3468
- return handleQuery(projectPath, input, deps);
3469
- case "sync":
3470
- return handleSync(projectPath, input, deps);
3471
- default: {
3472
- return {
3473
- content: [{ type: "text", text: `Error: unknown action` }],
3474
- isError: true
3475
- };
3476
- }
3526
+ const response = dispatchAction(input.action, projectPath, input, deps);
3527
+ if (shouldTriggerExternalSync(input, response)) {
3528
+ await triggerExternalSync(projectPath, roadmapPath(projectPath)).catch(() => {
3529
+ });
3477
3530
  }
3531
+ return response;
3478
3532
  } catch (error) {
3479
3533
  return {
3480
3534
  content: [
@@ -3837,6 +3891,20 @@ var emitInteractionDefinition = {
3837
3891
  required: ["path", "type"]
3838
3892
  }
3839
3893
  };
3894
+ function dualContent(prompt, metadata) {
3895
+ return [
3896
+ {
3897
+ type: "text",
3898
+ text: prompt,
3899
+ annotations: { audience: ["user", "assistant"], priority: 1 }
3900
+ },
3901
+ {
3902
+ type: "text",
3903
+ text: JSON.stringify(metadata),
3904
+ annotations: { audience: ["assistant"], priority: 0.2 }
3905
+ }
3906
+ ];
3907
+ }
3840
3908
  function formatZodErrors(issues) {
3841
3909
  return issues.map((i) => i.path.length > 0 ? `${i.path.join(".")}: ${i.message}` : i.message).join("; ");
3842
3910
  }
@@ -3848,7 +3916,7 @@ async function handleQuestion(validInput, projectPath, id) {
3848
3916
  return mcpError(`Error: ${formatZodErrors(questionResult.error.issues)}`);
3849
3917
  const prompt = renderQuestion(questionResult.data);
3850
3918
  await recordInteraction(projectPath, id, "question", questionResult.data.text, validInput.stream);
3851
- return { content: [{ type: "text", text: JSON.stringify({ id, prompt }) }] };
3919
+ return { content: dualContent(prompt, { id }) };
3852
3920
  }
3853
3921
  async function handleConfirmation(validInput, projectPath, id) {
3854
3922
  if (!validInput.confirmation)
@@ -3866,7 +3934,7 @@ async function handleConfirmation(validInput, projectPath, id) {
3866
3934
  confirmResult.data.text,
3867
3935
  validInput.stream
3868
3936
  );
3869
- return { content: [{ type: "text", text: JSON.stringify({ id, prompt }) }] };
3937
+ return { content: dualContent(prompt, { id }) };
3870
3938
  }
3871
3939
  async function handleTransition(validInput, projectPath, id) {
3872
3940
  if (!validInput.transition)
@@ -3879,7 +3947,7 @@ async function handleTransition(validInput, projectPath, id) {
3879
3947
  const transition = transitionResult.data;
3880
3948
  const prompt = renderTransition(transition);
3881
3949
  try {
3882
- const { saveHandoff } = await import("./dist-WHL3NN5S.js");
3950
+ const { saveHandoff } = await import("./dist-LCR2IO7U.js");
3883
3951
  await saveHandoff(
3884
3952
  projectPath,
3885
3953
  {
@@ -3906,12 +3974,12 @@ async function handleTransition(validInput, projectPath, id) {
3906
3974
  `${transition.completedPhase} -> ${transition.suggestedNext}`,
3907
3975
  validInput.stream
3908
3976
  );
3909
- const responsePayload = { id, prompt, handoffWritten: true };
3977
+ const metadata = { id, handoffWritten: true };
3910
3978
  if (!transition.requiresConfirmation) {
3911
- responsePayload.autoTransition = true;
3912
- responsePayload.nextAction = `Invoke harness-${transition.suggestedNext} skill now`;
3979
+ metadata.autoTransition = true;
3980
+ metadata.nextAction = `Invoke harness-${transition.suggestedNext} skill now`;
3913
3981
  }
3914
- return { content: [{ type: "text", text: JSON.stringify(responsePayload) }] };
3982
+ return { content: dualContent(prompt, metadata) };
3915
3983
  }
3916
3984
  async function handleBatch(validInput, projectPath, id) {
3917
3985
  if (!validInput.batch) return mcpError("Error: batch payload is required when type is batch");
@@ -3922,9 +3990,7 @@ async function handleBatch(validInput, projectPath, id) {
3922
3990
  );
3923
3991
  const prompt = renderBatch(batchResult.data);
3924
3992
  await recordInteraction(projectPath, id, "batch", batchResult.data.text, validInput.stream);
3925
- return {
3926
- content: [{ type: "text", text: JSON.stringify({ id, prompt, batchMode: true }) }]
3927
- };
3993
+ return { content: dualContent(prompt, { id, batchMode: true }) };
3928
3994
  }
3929
3995
  var INTERACTION_HANDLERS = {
3930
3996
  question: handleQuestion,
@@ -3949,7 +4015,7 @@ async function handleEmitInteraction(input) {
3949
4015
  }
3950
4016
  async function recordInteraction(projectPath, id, type, decision, stream) {
3951
4017
  try {
3952
- const { loadState, saveState } = await import("./dist-WHL3NN5S.js");
4018
+ const { loadState, saveState } = await import("./dist-LCR2IO7U.js");
3953
4019
  const stateResult = await loadState(projectPath, stream);
3954
4020
  if (stateResult.ok) {
3955
4021
  const state = stateResult.value;
@@ -4038,10 +4104,10 @@ async function handleGatherContext(input) {
4038
4104
  input.include ?? ["state", "learnings", "handoff", "graph", "validation"]
4039
4105
  );
4040
4106
  const errors = [];
4041
- const statePromise = includeSet.has("state") ? import("./dist-WHL3NN5S.js").then(
4107
+ const statePromise = includeSet.has("state") ? import("./dist-LCR2IO7U.js").then(
4042
4108
  (core) => core.loadState(projectPath, void 0, input.session)
4043
4109
  ) : Promise.resolve(null);
4044
- const learningsPromise = includeSet.has("learnings") ? import("./dist-WHL3NN5S.js").then(
4110
+ const learningsPromise = includeSet.has("learnings") ? import("./dist-LCR2IO7U.js").then(
4045
4111
  (core) => core.loadBudgetedLearnings(projectPath, {
4046
4112
  intent: input.intent,
4047
4113
  tokenBudget: input.learningsBudget ?? 1e3,
@@ -4050,14 +4116,14 @@ async function handleGatherContext(input) {
4050
4116
  ...input.depth !== void 0 && { depth: input.depth }
4051
4117
  })
4052
4118
  ) : Promise.resolve(null);
4053
- const handoffPromise = includeSet.has("handoff") ? import("./dist-WHL3NN5S.js").then(
4119
+ const handoffPromise = includeSet.has("handoff") ? import("./dist-LCR2IO7U.js").then(
4054
4120
  (core) => core.loadHandoff(projectPath, void 0, input.session)
4055
4121
  ) : Promise.resolve(null);
4056
4122
  const graphPromise = includeSet.has("graph") ? (async () => {
4057
- const { loadGraphStore: loadGraphStore2 } = await import("./graph-loader-KMHDQYDT.js");
4123
+ const { loadGraphStore: loadGraphStore2 } = await import("./graph-loader-FJN4H7Y4.js");
4058
4124
  const store = await loadGraphStore2(projectPath);
4059
4125
  if (!store) return null;
4060
- const { FusionLayer, ContextQL } = await import("./dist-U7EAO6T2.js");
4126
+ const { FusionLayer, ContextQL } = await import("./dist-IA6XYKNO.js");
4061
4127
  const fusion = new FusionLayer(store);
4062
4128
  const cql = new ContextQL(store);
4063
4129
  const tokenBudget = input.tokenBudget ?? 4e3;
@@ -4094,11 +4160,11 @@ async function handleGatherContext(input) {
4094
4160
  context: contextBlocks
4095
4161
  };
4096
4162
  })() : Promise.resolve(null);
4097
- const sessionsPromise = includeSet.has("sessions") && input.session ? import("./dist-WHL3NN5S.js").then(
4163
+ const sessionsPromise = includeSet.has("sessions") && input.session ? import("./dist-LCR2IO7U.js").then(
4098
4164
  (core) => core.readSessionSections(projectPath, input.session)
4099
4165
  ) : Promise.resolve(null);
4100
4166
  const shouldIncludeEvents = input.includeEvents !== void 0 ? input.includeEvents : includeSet.has("events") || !!input.session && !input.include;
4101
- const eventsPromise = shouldIncludeEvents ? import("./dist-WHL3NN5S.js").then(async (core) => {
4167
+ const eventsPromise = shouldIncludeEvents ? import("./dist-LCR2IO7U.js").then(async (core) => {
4102
4168
  const result = await core.loadEvents(projectPath, {
4103
4169
  session: input.session
4104
4170
  });
@@ -4106,7 +4172,7 @@ async function handleGatherContext(input) {
4106
4172
  return core.formatEventTimeline(result.value);
4107
4173
  }) : Promise.resolve(null);
4108
4174
  const validationPromise = includeSet.has("validation") ? (async () => {
4109
- const { handleValidateProject: handleValidateProject2 } = await import("./validate-R5WGB2AV.js");
4175
+ const { handleValidateProject: handleValidateProject2 } = await import("./validate-4IA5RPEX.js");
4110
4176
  const result = await handleValidateProject2({ path: projectPath });
4111
4177
  const first = result.content[0];
4112
4178
  return first ? JSON.parse(first.text) : null;
@@ -4200,7 +4266,7 @@ async function handleGatherContext(input) {
4200
4266
  };
4201
4267
  if (input.session) {
4202
4268
  try {
4203
- const core = await import("./dist-WHL3NN5S.js");
4269
+ const core = await import("./dist-LCR2IO7U.js");
4204
4270
  core.updateSessionIndex(
4205
4271
  projectPath,
4206
4272
  input.session,
@@ -4222,291 +4288,6 @@ async function handleGatherContext(input) {
4222
4288
  };
4223
4289
  }
4224
4290
 
4225
- // src/mcp/tools/assess-project.ts
4226
- var assessProjectDefinition = {
4227
- name: "assess_project",
4228
- description: "Run all project health checks in parallel and return a unified report. Checks: validate, dependencies, docs, entropy, security, performance, lint.",
4229
- inputSchema: {
4230
- type: "object",
4231
- properties: {
4232
- path: { type: "string", description: "Path to project root" },
4233
- checks: {
4234
- type: "array",
4235
- items: {
4236
- type: "string",
4237
- enum: ["validate", "deps", "docs", "entropy", "security", "perf", "lint"]
4238
- },
4239
- description: "Which checks to run (default: all)"
4240
- },
4241
- mode: {
4242
- type: "string",
4243
- enum: ["summary", "detailed"],
4244
- description: "Response density. Default: summary"
4245
- }
4246
- },
4247
- required: ["path"]
4248
- }
4249
- };
4250
- async function handleAssessProject(input) {
4251
- const start = Date.now();
4252
- let projectPath;
4253
- try {
4254
- projectPath = sanitizePath(input.path);
4255
- } catch (error) {
4256
- return {
4257
- content: [
4258
- {
4259
- type: "text",
4260
- text: `Error: ${error instanceof Error ? error.message : String(error)}`
4261
- }
4262
- ],
4263
- isError: true
4264
- };
4265
- }
4266
- const checksToRun = new Set(
4267
- input.checks ?? ["validate", "deps", "docs", "entropy", "security", "perf", "lint"]
4268
- );
4269
- const mode = input.mode ?? "summary";
4270
- let validateResult = null;
4271
- if (checksToRun.has("validate")) {
4272
- try {
4273
- const { handleValidateProject: handleValidateProject2 } = await import("./validate-R5WGB2AV.js");
4274
- const result = await handleValidateProject2({ path: projectPath });
4275
- const first = result.content[0];
4276
- const parsed = first ? JSON.parse(first.text) : {};
4277
- validateResult = {
4278
- name: "validate",
4279
- passed: parsed.valid === true,
4280
- issueCount: parsed.errors?.length ?? 0,
4281
- ...parsed.errors?.length > 0 ? { topIssue: parsed.errors[0] } : {},
4282
- ...mode === "detailed" ? { detailed: parsed } : {}
4283
- };
4284
- } catch (error) {
4285
- validateResult = {
4286
- name: "validate",
4287
- passed: false,
4288
- issueCount: 1,
4289
- topIssue: error instanceof Error ? error.message : String(error)
4290
- };
4291
- }
4292
- }
4293
- const parallelChecks = [];
4294
- if (checksToRun.has("deps")) {
4295
- parallelChecks.push(
4296
- (async () => {
4297
- try {
4298
- const { handleCheckDependencies: handleCheckDependencies2 } = await import("./architecture-45YCLD26.js");
4299
- const result = await handleCheckDependencies2({ path: projectPath });
4300
- const first = result.content[0];
4301
- const parsed = first ? JSON.parse(first.text) : {};
4302
- const violations = parsed.violations ?? [];
4303
- return {
4304
- name: "deps",
4305
- passed: !result.isError && violations.length === 0,
4306
- issueCount: violations.length,
4307
- ...violations.length > 0 ? { topIssue: violations[0]?.message ?? String(violations[0]) } : {},
4308
- ...mode === "detailed" ? { detailed: parsed } : {}
4309
- };
4310
- } catch (error) {
4311
- return {
4312
- name: "deps",
4313
- passed: false,
4314
- issueCount: 1,
4315
- topIssue: error instanceof Error ? error.message : String(error)
4316
- };
4317
- }
4318
- })()
4319
- );
4320
- }
4321
- if (checksToRun.has("docs")) {
4322
- parallelChecks.push(
4323
- (async () => {
4324
- try {
4325
- const { handleCheckDocs: handleCheckDocs2 } = await import("./docs-FJFY7GF2.js");
4326
- const result = await handleCheckDocs2({ path: projectPath, scope: "coverage" });
4327
- const first = result.content[0];
4328
- const parsed = first ? JSON.parse(first.text) : {};
4329
- const undocumented = parsed.undocumented ?? parsed.files?.undocumented ?? [];
4330
- return {
4331
- name: "docs",
4332
- passed: !result.isError,
4333
- issueCount: Array.isArray(undocumented) ? undocumented.length : 0,
4334
- ...Array.isArray(undocumented) && undocumented.length > 0 ? { topIssue: `Undocumented: ${undocumented[0]}` } : {},
4335
- ...mode === "detailed" ? { detailed: parsed } : {}
4336
- };
4337
- } catch (error) {
4338
- return {
4339
- name: "docs",
4340
- passed: false,
4341
- issueCount: 1,
4342
- topIssue: error instanceof Error ? error.message : String(error)
4343
- };
4344
- }
4345
- })()
4346
- );
4347
- }
4348
- if (checksToRun.has("entropy")) {
4349
- parallelChecks.push(
4350
- (async () => {
4351
- try {
4352
- const { handleDetectEntropy: handleDetectEntropy2 } = await import("./entropy-Y2GE4MYS.js");
4353
- const result = await handleDetectEntropy2({ path: projectPath, type: "all" });
4354
- const first = result.content[0];
4355
- const parsed = first ? JSON.parse(first.text) : {};
4356
- 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);
4357
- return {
4358
- name: "entropy",
4359
- passed: !("isError" in result && result.isError) && issues === 0,
4360
- issueCount: issues,
4361
- ...issues > 0 ? { topIssue: "Entropy detected -- run detect_entropy for details" } : {},
4362
- ...mode === "detailed" ? { detailed: parsed } : {}
4363
- };
4364
- } catch (error) {
4365
- return {
4366
- name: "entropy",
4367
- passed: false,
4368
- issueCount: 1,
4369
- topIssue: error instanceof Error ? error.message : String(error)
4370
- };
4371
- }
4372
- })()
4373
- );
4374
- }
4375
- if (checksToRun.has("security")) {
4376
- parallelChecks.push(
4377
- (async () => {
4378
- try {
4379
- const { handleRunSecurityScan: handleRunSecurityScan2 } = await import("./security-3T4JGDZP.js");
4380
- const result = await handleRunSecurityScan2({ path: projectPath });
4381
- const first = result.content[0];
4382
- const parsed = first ? JSON.parse(first.text) : {};
4383
- const findings = parsed.findings ?? [];
4384
- const errorCount = findings.filter(
4385
- (f) => f.severity === "error"
4386
- ).length;
4387
- return {
4388
- name: "security",
4389
- passed: !result.isError && errorCount === 0,
4390
- issueCount: findings.length,
4391
- ...findings.length > 0 ? {
4392
- topIssue: `${findings[0]?.rule ?? findings[0]?.type ?? "finding"}: ${findings[0]?.message ?? ""}`
4393
- } : {},
4394
- ...mode === "detailed" ? { detailed: parsed } : {}
4395
- };
4396
- } catch (error) {
4397
- return {
4398
- name: "security",
4399
- passed: false,
4400
- issueCount: 1,
4401
- topIssue: error instanceof Error ? error.message : String(error)
4402
- };
4403
- }
4404
- })()
4405
- );
4406
- }
4407
- if (checksToRun.has("perf")) {
4408
- parallelChecks.push(
4409
- (async () => {
4410
- try {
4411
- const { handleCheckPerformance: handleCheckPerformance2 } = await import("./performance-BSOMMWK5.js");
4412
- const result = await handleCheckPerformance2({ path: projectPath });
4413
- if ("isError" in result && result.isError) {
4414
- const msg = result.content[0]?.text ?? "Performance check failed";
4415
- return { name: "perf", passed: false, issueCount: 1, topIssue: msg };
4416
- }
4417
- const first = result.content[0];
4418
- let parsed = {};
4419
- try {
4420
- parsed = first ? JSON.parse(first.text) : {};
4421
- } catch {
4422
- return {
4423
- name: "perf",
4424
- passed: false,
4425
- issueCount: 1,
4426
- topIssue: first?.text ?? "Invalid perf output"
4427
- };
4428
- }
4429
- const issues = parsed.violations?.length ?? parsed.issues?.length ?? 0;
4430
- return {
4431
- name: "perf",
4432
- passed: issues === 0,
4433
- issueCount: issues,
4434
- ...issues > 0 ? { topIssue: "Performance issues detected" } : {},
4435
- ...mode === "detailed" ? { detailed: parsed } : {}
4436
- };
4437
- } catch (error) {
4438
- return {
4439
- name: "perf",
4440
- passed: false,
4441
- issueCount: 1,
4442
- topIssue: error instanceof Error ? error.message : String(error)
4443
- };
4444
- }
4445
- })()
4446
- );
4447
- }
4448
- if (checksToRun.has("lint")) {
4449
- parallelChecks.push(
4450
- (async () => {
4451
- try {
4452
- const { execFileSync } = await import("child_process");
4453
- const output = execFileSync("npx", ["turbo", "run", "lint", "--force"], {
4454
- cwd: projectPath,
4455
- encoding: "utf-8",
4456
- timeout: 6e4,
4457
- stdio: ["pipe", "pipe", "pipe"]
4458
- });
4459
- return {
4460
- name: "lint",
4461
- passed: true,
4462
- issueCount: 0,
4463
- ...mode === "detailed" ? { detailed: output } : {}
4464
- };
4465
- } catch (error) {
4466
- const stderr = error && typeof error === "object" && "stderr" in error ? String(error.stderr) : "";
4467
- const stdout = error && typeof error === "object" && "stdout" in error ? String(error.stdout) : "";
4468
- const combined = (stderr + "\n" + stdout).trim();
4469
- const errorMatch = combined.match(/(\d+) error/);
4470
- const issueCount = errorMatch?.[1] ? parseInt(errorMatch[1], 10) : 1;
4471
- const firstError = combined.split("\n").find((line) => line.includes("error"));
4472
- return {
4473
- name: "lint",
4474
- passed: false,
4475
- issueCount,
4476
- topIssue: firstError?.trim() ?? (error instanceof Error ? error.message : String(error)),
4477
- ...mode === "detailed" ? { detailed: combined } : {}
4478
- };
4479
- }
4480
- })()
4481
- );
4482
- }
4483
- const parallelResults = await Promise.all(parallelChecks);
4484
- const allChecks = [];
4485
- if (validateResult) allChecks.push(validateResult);
4486
- allChecks.push(...parallelResults);
4487
- const healthy = allChecks.every((c) => c.passed);
4488
- const assessedIn = Date.now() - start;
4489
- if (mode === "summary") {
4490
- const summaryChecks = allChecks.map(({ detailed: _d, ...rest }) => rest);
4491
- return {
4492
- content: [
4493
- {
4494
- type: "text",
4495
- text: JSON.stringify({ healthy, checks: summaryChecks, assessedIn })
4496
- }
4497
- ]
4498
- };
4499
- }
4500
- return {
4501
- content: [
4502
- {
4503
- type: "text",
4504
- text: JSON.stringify({ healthy, checks: allChecks, assessedIn })
4505
- }
4506
- ]
4507
- };
4508
- }
4509
-
4510
4291
  // src/mcp/tools/review-changes.ts
4511
4292
  var SIZE_GATE_LINES = 1e4;
4512
4293
  var reviewChangesDefinition = {
@@ -4611,7 +4392,7 @@ async function handleReviewChanges(input) {
4611
4392
  }
4612
4393
  }
4613
4394
  async function runQuickReview(projectPath, diff, diffLines, downgraded) {
4614
- const { handleAnalyzeDiff: handleAnalyzeDiff2 } = await import("./feedback-FKZ7GMPO.js");
4395
+ const { handleAnalyzeDiff: handleAnalyzeDiff2 } = await import("./feedback-QGCSW7SB.js");
4615
4396
  const result = await handleAnalyzeDiff2({ diff, path: projectPath });
4616
4397
  const firstContent = result.content[0];
4617
4398
  if (!firstContent) throw new Error("Empty analyze_diff response");
@@ -4642,7 +4423,7 @@ function extractFileCount(diffParsed) {
4642
4423
  return files?.length ?? 0;
4643
4424
  }
4644
4425
  async function runStandardReview(projectPath, diff, diffLines, downgraded) {
4645
- const { handleAnalyzeDiff: handleAnalyzeDiff2, handleCreateSelfReview: handleCreateSelfReview2 } = await import("./feedback-FKZ7GMPO.js");
4426
+ const { handleAnalyzeDiff: handleAnalyzeDiff2, handleCreateSelfReview: handleCreateSelfReview2 } = await import("./feedback-QGCSW7SB.js");
4646
4427
  const [diffResult, reviewResult] = await Promise.all([
4647
4428
  handleAnalyzeDiff2({ diff, path: projectPath }),
4648
4429
  handleCreateSelfReview2({ path: projectPath, diff })
@@ -4674,7 +4455,7 @@ async function runStandardReview(projectPath, diff, diffLines, downgraded) {
4674
4455
  };
4675
4456
  }
4676
4457
  async function runDeepReview(projectPath, diff, diffLines, _downgraded) {
4677
- const { handleRunCodeReview: handleRunCodeReview2 } = await import("./review-pipeline-KUBHP3RV.js");
4458
+ const { handleRunCodeReview: handleRunCodeReview2 } = await import("./review-pipeline-3ZS3GJSP.js");
4678
4459
  const result = await handleRunCodeReview2({ path: projectPath, diff });
4679
4460
  const deepContent = result.content[0];
4680
4461
  if (!deepContent) throw new Error("Empty code review response");
@@ -4745,7 +4526,7 @@ async function handleCheckTaskIndependence(input) {
4745
4526
  try {
4746
4527
  const projectPath = sanitizePath(input.path);
4747
4528
  const store = await loadGraphStore(projectPath);
4748
- const { TaskIndependenceAnalyzer } = await import("./dist-U7EAO6T2.js");
4529
+ const { TaskIndependenceAnalyzer } = await import("./dist-IA6XYKNO.js");
4749
4530
  const analyzer = new TaskIndependenceAnalyzer(store ?? void 0);
4750
4531
  const result = analyzer.analyze({
4751
4532
  tasks: input.tasks,
@@ -4833,7 +4614,7 @@ async function handlePredictConflicts(input) {
4833
4614
  try {
4834
4615
  const projectPath = sanitizePath(input.path);
4835
4616
  const store = await loadGraphStore(projectPath);
4836
- const { ConflictPredictor } = await import("./dist-U7EAO6T2.js");
4617
+ const { ConflictPredictor } = await import("./dist-IA6XYKNO.js");
4837
4618
  const predictor = new ConflictPredictor(store ?? void 0);
4838
4619
  const result = predictor.predict({
4839
4620
  tasks: input.tasks,
@@ -4939,7 +4720,7 @@ async function handleDetectStaleConstraints(input) {
4939
4720
  isError: true
4940
4721
  };
4941
4722
  }
4942
- const { loadGraphStore: loadGraphStore2 } = await import("./graph-loader-KMHDQYDT.js");
4723
+ const { loadGraphStore: loadGraphStore2 } = await import("./graph-loader-FJN4H7Y4.js");
4943
4724
  const store = await loadGraphStore2(projectPath);
4944
4725
  if (!store) {
4945
4726
  return {
@@ -4960,7 +4741,7 @@ async function handleDetectStaleConstraints(input) {
4960
4741
  ]
4961
4742
  };
4962
4743
  }
4963
- const { detectStaleConstraints } = await import("./dist-WHL3NN5S.js");
4744
+ const { detectStaleConstraints } = await import("./dist-LCR2IO7U.js");
4964
4745
  const result = detectStaleConstraints(
4965
4746
  store,
4966
4747
  windowDays,
@@ -4987,10 +4768,208 @@ async function handleDetectStaleConstraints(input) {
4987
4768
  }
4988
4769
  }
4989
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
+
4990
4969
  // src/mcp/tools/search-skills.ts
4991
4970
  var searchSkillsDefinition = {
4992
4971
  name: "search_skills",
4993
- description: "Search the skill catalog for domain-specific skills. Returns ranked results based on keyword and stack-signal matching. Use this to discover catalog skills that are not loaded as slash commands.",
4972
+ description: "Search the skill catalog for domain-specific skills. Returns ranked results based on keyword, name, description, and stack-signal matching. Use this to discover catalog skills that are not loaded as slash commands.",
4994
4973
  inputSchema: {
4995
4974
  type: "object",
4996
4975
  properties: {
@@ -5019,21 +4998,12 @@ async function handleSearchSkills(input) {
5019
4998
  const tierOverrides = configResult.ok ? configResult.value.skills?.tierOverrides : void 0;
5020
4999
  const index = loadOrRebuildIndex(platform, projectRoot, tierOverrides);
5021
5000
  const profile = loadOrGenerateProfile(projectRoot);
5001
+ const snapshot = loadCachedSnapshot(projectRoot);
5002
+ const freshSnapshot = snapshot && isSnapshotFresh(snapshot, projectRoot) ? snapshot : void 0;
5022
5003
  const queryTerms = query.toLowerCase().split(/\s+/).filter((t) => t.length > 2);
5023
5004
  const results = [];
5024
5005
  for (const [name, entry] of Object.entries(index.skills)) {
5025
- const matchedKeywords = entry.keywords.filter(
5026
- (kw) => queryTerms.some(
5027
- (term) => kw.toLowerCase().includes(term.toLowerCase()) || term.toLowerCase().includes(kw.toLowerCase())
5028
- )
5029
- );
5030
- const keywordScore = queryTerms.length > 0 ? matchedKeywords.length / queryTerms.length : 0;
5031
- let stackScore = 0;
5032
- if (entry.stackSignals.length > 0) {
5033
- const matchedSignals = entry.stackSignals.filter((signal) => profile.signals[signal]);
5034
- stackScore = matchedSignals.length / entry.stackSignals.length;
5035
- }
5036
- const score = 0.6 * keywordScore + 0.4 * stackScore;
5006
+ const score = scoreSkill(entry, queryTerms, profile, [], name, freshSnapshot);
5037
5007
  if (score > 0 || queryTerms.length === 0) {
5038
5008
  results.push({
5039
5009
  name,
@@ -5057,6 +5027,125 @@ async function handleSearchSkills(input) {
5057
5027
  };
5058
5028
  }
5059
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
+
5060
5149
  // src/mcp/tools/code-nav.ts
5061
5150
  var codeOutlineDefinition = {
5062
5151
  name: "code_outline",
@@ -5092,7 +5181,7 @@ async function handleCodeOutline(input) {
5092
5181
  };
5093
5182
  }
5094
5183
  try {
5095
- const { getOutline, formatOutline, EXTENSION_MAP } = await import("./dist-WHL3NN5S.js");
5184
+ const { getOutline, formatOutline, EXTENSION_MAP } = await import("./dist-LCR2IO7U.js");
5096
5185
  const { stat } = await import("fs/promises");
5097
5186
  const stats = await stat(targetPath).catch(() => null);
5098
5187
  if (stats?.isFile()) {
@@ -5172,7 +5261,7 @@ async function handleCodeSearch(input) {
5172
5261
  };
5173
5262
  }
5174
5263
  try {
5175
- const { searchSymbols } = await import("./dist-WHL3NN5S.js");
5264
+ const { searchSymbols } = await import("./dist-LCR2IO7U.js");
5176
5265
  const result = await searchSymbols(input.query, directory, input.glob);
5177
5266
  const lines = [`Search: "${result.query}" \u2014 ${result.matches.length} matches`];
5178
5267
  for (const match of result.matches) {
@@ -5241,7 +5330,7 @@ async function handleCodeUnfold(input) {
5241
5330
  }
5242
5331
  try {
5243
5332
  if (input.symbol) {
5244
- const { unfoldSymbol } = await import("./dist-WHL3NN5S.js");
5333
+ const { unfoldSymbol } = await import("./dist-LCR2IO7U.js");
5245
5334
  const result = await unfoldSymbol(filePath, input.symbol);
5246
5335
  const header = result.warning ? `${result.file}:${result.startLine}-${result.endLine} ${result.warning}
5247
5336
  ` : `${result.file}:${result.startLine}-${result.endLine}
@@ -5249,7 +5338,7 @@ async function handleCodeUnfold(input) {
5249
5338
  return { content: [{ type: "text", text: header + result.content }] };
5250
5339
  }
5251
5340
  if (input.startLine != null && input.endLine != null) {
5252
- const { unfoldRange } = await import("./dist-WHL3NN5S.js");
5341
+ const { unfoldRange } = await import("./dist-LCR2IO7U.js");
5253
5342
  const result = await unfoldRange(filePath, input.startLine, input.endLine);
5254
5343
  const header = `${result.file}:${result.startLine}-${result.endLine}
5255
5344
  `;
@@ -5277,6 +5366,501 @@ async function handleCodeUnfold(input) {
5277
5366
  }
5278
5367
  }
5279
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
+
5280
5864
  // src/mcp/server.ts
5281
5865
  var TOOL_DEFINITIONS = [
5282
5866
  validateToolDefinition,
@@ -5327,7 +5911,11 @@ var TOOL_DEFINITIONS = [
5327
5911
  searchSkillsDefinition,
5328
5912
  codeOutlineDefinition,
5329
5913
  codeSearchDefinition,
5330
- codeUnfoldDefinition
5914
+ codeUnfoldDefinition,
5915
+ getDecayTrendsDefinition,
5916
+ checkTraceabilityDefinition,
5917
+ predictFailuresDefinition,
5918
+ recommendSkillsDefinition
5331
5919
  ].map((def) => ({ ...def, trustedOutput: true }));
5332
5920
  var TOOL_HANDLERS = {
5333
5921
  validate_project: handleValidateProject,
@@ -5378,7 +5966,11 @@ var TOOL_HANDLERS = {
5378
5966
  search_skills: handleSearchSkills,
5379
5967
  code_outline: handleCodeOutline,
5380
5968
  code_search: handleCodeSearch,
5381
- code_unfold: handleCodeUnfold
5969
+ code_unfold: handleCodeUnfold,
5970
+ get_decay_trends: handleGetDecayTrends,
5971
+ check_traceability: handleCheckTraceability,
5972
+ predict_failures: handlePredictFailures,
5973
+ recommend_skills: handleRecommendSkills
5382
5974
  };
5383
5975
  var RESOURCE_DEFINITIONS = [
5384
5976
  {
@@ -5464,7 +6056,7 @@ async function appendUpdateNotification(result, resolvedRoot) {
5464
6056
  shouldRunCheck,
5465
6057
  readCheckState,
5466
6058
  spawnBackgroundCheck
5467
- } = await import("./dist-WHL3NN5S.js");
6059
+ } = await import("./dist-LCR2IO7U.js");
5468
6060
  const { CLI_VERSION } = await import("./version-KFFPOQAX.js");
5469
6061
  const configInterval = readConfigInterval(resolvedRoot);
5470
6062
  const DEFAULT_INTERVAL = 864e5;
@@ -5546,7 +6138,12 @@ export {
5546
6138
  generateSlashCommands,
5547
6139
  handleOrphanDeletion,
5548
6140
  createGenerateSlashCommandsCommand,
6141
+ loadOrRebuildIndex,
5549
6142
  handleGetImpact,
6143
+ isSnapshotFresh,
6144
+ loadCachedSnapshot,
6145
+ captureHealthSnapshot,
6146
+ recommend,
5550
6147
  getToolDefinitions,
5551
6148
  createHarnessServer,
5552
6149
  startServer