@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.
- package/dist/agents/skills/claude-code/cleanup-dead-code/skill.yaml +3 -0
- package/dist/agents/skills/claude-code/detect-doc-drift/skill.yaml +5 -0
- package/dist/agents/skills/claude-code/enforce-architecture/skill.yaml +13 -0
- package/dist/agents/skills/claude-code/harness-accessibility/skill.yaml +20 -0
- package/dist/agents/skills/claude-code/harness-code-review/skill.yaml +5 -0
- package/dist/agents/skills/claude-code/harness-codebase-cleanup/skill.yaml +5 -0
- package/dist/agents/skills/claude-code/harness-debugging/skill.yaml +5 -0
- package/dist/agents/skills/claude-code/harness-dependency-health/skill.yaml +9 -0
- package/dist/agents/skills/claude-code/harness-design/skill.yaml +20 -0
- package/dist/agents/skills/claude-code/harness-design-mobile/skill.yaml +20 -0
- package/dist/agents/skills/claude-code/harness-design-system/skill.yaml +22 -0
- package/dist/agents/skills/claude-code/harness-design-web/skill.yaml +22 -0
- package/dist/agents/skills/claude-code/harness-diagnostics/skill.yaml +19 -0
- package/dist/agents/skills/claude-code/harness-git-workflow/skill.yaml +15 -0
- package/dist/agents/skills/claude-code/harness-hotspot-detector/skill.yaml +9 -0
- package/dist/agents/skills/claude-code/harness-i18n/skill.yaml +22 -0
- package/dist/agents/skills/claude-code/harness-i18n-process/skill.yaml +15 -0
- package/dist/agents/skills/claude-code/harness-i18n-workflow/skill.yaml +19 -0
- package/dist/agents/skills/claude-code/harness-integrity/skill.yaml +5 -0
- package/dist/agents/skills/claude-code/harness-perf/skill.yaml +3 -0
- package/dist/agents/skills/claude-code/harness-perf-tdd/skill.yaml +20 -0
- package/dist/agents/skills/claude-code/harness-pre-commit-review/skill.yaml +18 -0
- package/dist/agents/skills/claude-code/harness-refactoring/skill.yaml +9 -0
- package/dist/agents/skills/claude-code/harness-security-review/skill.yaml +23 -0
- package/dist/agents/skills/claude-code/harness-security-scan/skill.yaml +3 -0
- package/dist/agents/skills/claude-code/harness-soundness-review/skill.yaml +5 -0
- package/dist/agents/skills/claude-code/harness-supply-chain-audit/skill.yaml +3 -0
- package/dist/agents/skills/claude-code/harness-tdd/skill.yaml +3 -0
- package/dist/agents/skills/codex/cleanup-dead-code/skill.yaml +3 -0
- package/dist/agents/skills/codex/detect-doc-drift/skill.yaml +5 -0
- package/dist/agents/skills/codex/enforce-architecture/skill.yaml +13 -0
- package/dist/agents/skills/codex/harness-accessibility/skill.yaml +20 -0
- package/dist/agents/skills/codex/harness-code-review/skill.yaml +5 -0
- package/dist/agents/skills/codex/harness-codebase-cleanup/skill.yaml +5 -0
- package/dist/agents/skills/codex/harness-debugging/skill.yaml +5 -0
- package/dist/agents/skills/codex/harness-dependency-health/skill.yaml +9 -0
- package/dist/agents/skills/codex/harness-design/skill.yaml +20 -0
- package/dist/agents/skills/codex/harness-design-mobile/skill.yaml +20 -0
- package/dist/agents/skills/codex/harness-design-system/skill.yaml +22 -0
- package/dist/agents/skills/codex/harness-design-web/skill.yaml +22 -0
- package/dist/agents/skills/codex/harness-diagnostics/skill.yaml +19 -0
- package/dist/agents/skills/codex/harness-git-workflow/skill.yaml +15 -0
- package/dist/agents/skills/codex/harness-hotspot-detector/skill.yaml +9 -0
- package/dist/agents/skills/codex/harness-i18n/skill.yaml +22 -0
- package/dist/agents/skills/codex/harness-i18n-process/skill.yaml +15 -0
- package/dist/agents/skills/codex/harness-i18n-workflow/skill.yaml +19 -0
- package/dist/agents/skills/codex/harness-integrity/skill.yaml +5 -0
- package/dist/agents/skills/codex/harness-perf/skill.yaml +3 -0
- package/dist/agents/skills/codex/harness-perf-tdd/skill.yaml +20 -0
- package/dist/agents/skills/codex/harness-pre-commit-review/skill.yaml +18 -0
- package/dist/agents/skills/codex/harness-refactoring/skill.yaml +9 -0
- package/dist/agents/skills/codex/harness-security-review/skill.yaml +23 -0
- package/dist/agents/skills/codex/harness-security-scan/skill.yaml +3 -0
- package/dist/agents/skills/codex/harness-soundness-review/skill.yaml +5 -0
- package/dist/agents/skills/codex/harness-supply-chain-audit/skill.yaml +3 -0
- package/dist/agents/skills/codex/harness-tdd/skill.yaml +3 -0
- package/dist/agents/skills/cursor/cleanup-dead-code/skill.yaml +3 -0
- package/dist/agents/skills/cursor/detect-doc-drift/skill.yaml +5 -0
- package/dist/agents/skills/cursor/enforce-architecture/skill.yaml +13 -0
- package/dist/agents/skills/cursor/harness-accessibility/skill.yaml +20 -0
- package/dist/agents/skills/cursor/harness-code-review/skill.yaml +5 -0
- package/dist/agents/skills/cursor/harness-codebase-cleanup/skill.yaml +5 -0
- package/dist/agents/skills/cursor/harness-debugging/skill.yaml +5 -0
- package/dist/agents/skills/cursor/harness-dependency-health/skill.yaml +9 -0
- package/dist/agents/skills/cursor/harness-design/skill.yaml +20 -0
- package/dist/agents/skills/cursor/harness-design-mobile/skill.yaml +20 -0
- package/dist/agents/skills/cursor/harness-design-system/skill.yaml +22 -0
- package/dist/agents/skills/cursor/harness-design-web/skill.yaml +22 -0
- package/dist/agents/skills/cursor/harness-diagnostics/skill.yaml +19 -0
- package/dist/agents/skills/cursor/harness-git-workflow/skill.yaml +15 -0
- package/dist/agents/skills/cursor/harness-hotspot-detector/skill.yaml +9 -0
- package/dist/agents/skills/cursor/harness-i18n/skill.yaml +22 -0
- package/dist/agents/skills/cursor/harness-i18n-process/skill.yaml +15 -0
- package/dist/agents/skills/cursor/harness-i18n-workflow/skill.yaml +19 -0
- package/dist/agents/skills/cursor/harness-integrity/skill.yaml +5 -0
- package/dist/agents/skills/cursor/harness-perf/skill.yaml +3 -0
- package/dist/agents/skills/cursor/harness-perf-tdd/skill.yaml +20 -0
- package/dist/agents/skills/cursor/harness-pre-commit-review/skill.yaml +18 -0
- package/dist/agents/skills/cursor/harness-refactoring/skill.yaml +9 -0
- package/dist/agents/skills/cursor/harness-security-review/skill.yaml +23 -0
- package/dist/agents/skills/cursor/harness-security-scan/skill.yaml +3 -0
- package/dist/agents/skills/cursor/harness-soundness-review/skill.yaml +5 -0
- package/dist/agents/skills/cursor/harness-supply-chain-audit/skill.yaml +3 -0
- package/dist/agents/skills/cursor/harness-tdd/skill.yaml +3 -0
- package/dist/agents/skills/gemini-cli/cleanup-dead-code/skill.yaml +3 -0
- package/dist/agents/skills/gemini-cli/detect-doc-drift/skill.yaml +5 -0
- package/dist/agents/skills/gemini-cli/enforce-architecture/skill.yaml +13 -0
- package/dist/agents/skills/gemini-cli/harness-accessibility/skill.yaml +20 -0
- package/dist/agents/skills/gemini-cli/harness-code-review/skill.yaml +5 -0
- package/dist/agents/skills/gemini-cli/harness-codebase-cleanup/skill.yaml +5 -0
- package/dist/agents/skills/gemini-cli/harness-debugging/skill.yaml +5 -0
- package/dist/agents/skills/gemini-cli/harness-dependency-health/skill.yaml +9 -0
- package/dist/agents/skills/gemini-cli/harness-design/skill.yaml +20 -0
- package/dist/agents/skills/gemini-cli/harness-design-mobile/skill.yaml +20 -0
- package/dist/agents/skills/gemini-cli/harness-design-system/skill.yaml +22 -0
- package/dist/agents/skills/gemini-cli/harness-design-web/skill.yaml +22 -0
- package/dist/agents/skills/gemini-cli/harness-diagnostics/skill.yaml +19 -0
- package/dist/agents/skills/gemini-cli/harness-git-workflow/skill.yaml +15 -0
- package/dist/agents/skills/gemini-cli/harness-hotspot-detector/skill.yaml +9 -0
- package/dist/agents/skills/gemini-cli/harness-i18n/skill.yaml +22 -0
- package/dist/agents/skills/gemini-cli/harness-i18n-process/skill.yaml +15 -0
- package/dist/agents/skills/gemini-cli/harness-i18n-workflow/skill.yaml +19 -0
- package/dist/agents/skills/gemini-cli/harness-integrity/skill.yaml +5 -0
- package/dist/agents/skills/gemini-cli/harness-perf/skill.yaml +3 -0
- package/dist/agents/skills/gemini-cli/harness-perf-tdd/skill.yaml +20 -0
- package/dist/agents/skills/gemini-cli/harness-pre-commit-review/skill.yaml +18 -0
- package/dist/agents/skills/gemini-cli/harness-refactoring/skill.yaml +9 -0
- package/dist/agents/skills/gemini-cli/harness-security-review/skill.yaml +23 -0
- package/dist/agents/skills/gemini-cli/harness-security-scan/skill.yaml +3 -0
- package/dist/agents/skills/gemini-cli/harness-soundness-review/skill.yaml +5 -0
- package/dist/agents/skills/gemini-cli/harness-supply-chain-audit/skill.yaml +3 -0
- package/dist/agents/skills/gemini-cli/harness-tdd/skill.yaml +3 -0
- package/dist/{agents-md-WHXVPOK2.js → agents-md-PM7LO74M.js} +2 -1
- package/dist/{architecture-45YCLD26.js → architecture-OVOCDTI6.js} +3 -2
- package/dist/assess-project-R2OZIDDS.js +9 -0
- package/dist/bin/harness-mcp.js +15 -13
- package/dist/bin/harness.js +21 -19
- package/dist/{check-phase-gate-2VXVOUJ5.js → check-phase-gate-7JQ6EW5R.js} +4 -3
- package/dist/{chunk-M6TIO6NF.js → chunk-2PAPHA77.js} +1 -1
- package/dist/chunk-5FBWWMY2.js +293 -0
- package/dist/{chunk-H6KZAGHZ.js → chunk-5QTWFO24.js} +8 -8
- package/dist/{chunk-CZZXE6BL.js → chunk-ASS5TD2Y.js} +1 -1
- package/dist/{chunk-45ZJPG24.js → chunk-B4WHXHF7.js} +1 -1
- package/dist/{chunk-SQY4AAKP.js → chunk-DJEBBENF.js} +1005 -408
- package/dist/{chunk-LEWXD6PR.js → chunk-DXYOAQQC.js} +1 -1
- package/dist/{chunk-PDEEQJHH.js → chunk-FSLFBLYW.js} +7 -7
- package/dist/{chunk-YDOGGQSF.js → chunk-GRJ7A4WT.js} +17 -2
- package/dist/{chunk-4U4V7A6U.js → chunk-IK5GSLW6.js} +4 -4
- package/dist/{chunk-LVJ7SCD7.js → chunk-J7W4LTRK.js} +2 -2
- package/dist/{chunk-PDOSLTWP.js → chunk-PUOMFNRO.js} +26 -3
- package/dist/{chunk-HKUX2X7O.js → chunk-SE4YPMLH.js} +9 -1
- package/dist/{chunk-HAJD5LTI.js → chunk-SOTTK27D.js} +459 -37
- package/dist/{chunk-LRG3B43J.js → chunk-T5QWCVGK.js} +1 -1
- package/dist/{dist-U7EAO6T2.js → chunk-TEZI27SA.js} +401 -60
- package/dist/{chunk-IC5CZSHF.js → chunk-U44JNY3Y.js} +1549 -593
- package/dist/{chunk-A33LHIRD.js → chunk-W6MPLFXU.js} +3 -3
- package/dist/{chunk-V73TEHIF.js → chunk-ZEIEUCZL.js} +9 -9
- package/dist/{ci-workflow-HWX5OVLI.js → ci-workflow-OTTEERPF.js} +2 -1
- package/dist/{create-skill-NDXQSTIK.js → create-skill-U3XCFRZN.js} +2 -2
- package/dist/dist-IA6XYKNO.js +92 -0
- package/dist/{dist-WHL3NN5S.js → dist-LCR2IO7U.js} +56 -3
- package/dist/{docs-FJFY7GF2.js → docs-CHAYSGOP.js} +4 -3
- package/dist/{engine-R5BZHIZB.js → engine-4MY2U5RZ.js} +2 -1
- package/dist/{entropy-Y2GE4MYS.js → entropy-AKSZG7G5.js} +3 -2
- package/dist/{feedback-FKZ7GMPO.js → feedback-QGCSW7SB.js} +1 -1
- package/dist/{generate-agent-definitions-LN3A45OL.js → generate-agent-definitions-KU6X2UQN.js} +2 -1
- package/dist/{graph-loader-KMHDQYDT.js → graph-loader-FJN4H7Y4.js} +1 -1
- package/dist/index.d.ts +58 -2
- package/dist/index.js +27 -23
- package/dist/{loader-2TBQUFWX.js → loader-AV5XEMER.js} +2 -1
- package/dist/{mcp-KEY575NJ.js → mcp-LWHVQRG7.js} +15 -13
- package/dist/{performance-BSOMMWK5.js → performance-ETZVXXGQ.js} +4 -3
- package/dist/{review-pipeline-KUBHP3RV.js → review-pipeline-3ZS3GJSP.js} +1 -1
- package/dist/{runtime-BN7KGJAO.js → runtime-KQTJRK3H.js} +2 -1
- package/dist/{security-3T4JGDZP.js → security-LJCLZES6.js} +1 -1
- package/dist/{skill-executor-XEVDGXUM.js → skill-executor-2BZQLHYN.js} +2 -2
- package/dist/{validate-R5WGB2AV.js → validate-4IA5RPEX.js} +3 -2
- package/dist/{validate-cross-check-76Z5P6EX.js → validate-cross-check-VX2BAHQI.js} +2 -1
- package/package.json +4 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
detectEntropyDefinition,
|
|
3
3
|
handleDetectEntropy
|
|
4
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
22
|
+
} from "./chunk-ZEIEUCZL.js";
|
|
23
23
|
import {
|
|
24
24
|
handleRunSecurityScan,
|
|
25
25
|
runSecurityScanDefinition
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-DXYOAQQC.js";
|
|
27
27
|
import {
|
|
28
28
|
handleRunCodeReview,
|
|
29
29
|
runCodeReviewDefinition
|
|
30
|
-
} from "./chunk-
|
|
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-
|
|
47
|
+
} from "./chunk-J7W4LTRK.js";
|
|
44
48
|
import {
|
|
45
49
|
loadGraphStore
|
|
46
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-ASS5TD2Y.js";
|
|
47
51
|
import {
|
|
48
52
|
checkDependenciesDefinition,
|
|
49
53
|
handleCheckDependencies
|
|
50
|
-
} from "./chunk-
|
|
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-
|
|
61
|
+
} from "./chunk-5QTWFO24.js";
|
|
58
62
|
import {
|
|
59
63
|
TrackerConfigSchema,
|
|
60
64
|
resolveConfig
|
|
61
|
-
} from "./chunk-
|
|
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-
|
|
88
|
+
} from "./chunk-SE4YPMLH.js";
|
|
85
89
|
import {
|
|
86
90
|
DESTRUCTIVE_BASH,
|
|
87
91
|
checkTaint,
|
|
88
92
|
scanForInjection,
|
|
89
93
|
writeTaint
|
|
90
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
606
|
-
const { generateRuntime } = await import("./runtime-
|
|
607
|
-
const { generateAgentsMd } = await import("./agents-md-
|
|
608
|
-
const { generateCIWorkflow } = await import("./ci-workflow-
|
|
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-
|
|
667
|
+
const { loadPersona } = await import("./loader-AV5XEMER.js");
|
|
664
668
|
const { runPersona } = await import("./runner-VMYLHWOC.js");
|
|
665
|
-
const { executeSkill } = await import("./skill-executor-
|
|
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
|
|
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, "")
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
2907
|
-
const
|
|
2908
|
-
await
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
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:
|
|
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:
|
|
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-
|
|
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
|
|
3977
|
+
const metadata = { id, handoffWritten: true };
|
|
3910
3978
|
if (!transition.requiresConfirmation) {
|
|
3911
|
-
|
|
3912
|
-
|
|
3979
|
+
metadata.autoTransition = true;
|
|
3980
|
+
metadata.nextAction = `Invoke harness-${transition.suggestedNext} skill now`;
|
|
3913
3981
|
}
|
|
3914
|
-
return { content:
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|