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