@skillsmith/mcp-server 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/README.md +11 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/src/__tests__/LocalIndexer.test.js +158 -0
  4. package/dist/src/__tests__/LocalIndexer.test.js.map +1 -1
  5. package/dist/src/__tests__/compare.test.d.ts +8 -0
  6. package/dist/src/__tests__/compare.test.d.ts.map +1 -0
  7. package/dist/src/__tests__/compare.test.js +162 -0
  8. package/dist/src/__tests__/compare.test.js.map +1 -0
  9. package/dist/src/__tests__/context.async.test.d.ts +8 -0
  10. package/dist/src/__tests__/context.async.test.d.ts.map +1 -0
  11. package/dist/src/__tests__/context.async.test.js +223 -0
  12. package/dist/src/__tests__/context.async.test.js.map +1 -0
  13. package/dist/src/__tests__/middleware/errorFormatter.builders.test.d.ts +10 -0
  14. package/dist/src/__tests__/middleware/errorFormatter.builders.test.d.ts.map +1 -0
  15. package/dist/src/__tests__/middleware/errorFormatter.builders.test.js +93 -0
  16. package/dist/src/__tests__/middleware/errorFormatter.builders.test.js.map +1 -0
  17. package/dist/src/__tests__/middleware/license-renewal.test.d.ts +10 -0
  18. package/dist/src/__tests__/middleware/license-renewal.test.d.ts.map +1 -0
  19. package/dist/src/__tests__/middleware/license-renewal.test.js +152 -0
  20. package/dist/src/__tests__/middleware/license-renewal.test.js.map +1 -0
  21. package/dist/src/__tests__/middleware/quota-helpers.test.d.ts +9 -0
  22. package/dist/src/__tests__/middleware/quota-helpers.test.d.ts.map +1 -0
  23. package/dist/src/__tests__/middleware/quota-helpers.test.js +105 -0
  24. package/dist/src/__tests__/middleware/quota-helpers.test.js.map +1 -0
  25. package/dist/src/__tests__/middleware/quota.test.d.ts +12 -0
  26. package/dist/src/__tests__/middleware/quota.test.d.ts.map +1 -0
  27. package/dist/src/__tests__/middleware/quota.test.js +189 -0
  28. package/dist/src/__tests__/middleware/quota.test.js.map +1 -0
  29. package/dist/src/__tests__/recommend-online-path.test.d.ts +10 -0
  30. package/dist/src/__tests__/recommend-online-path.test.d.ts.map +1 -0
  31. package/dist/src/__tests__/recommend-online-path.test.js +225 -0
  32. package/dist/src/__tests__/recommend-online-path.test.js.map +1 -0
  33. package/dist/src/__tests__/recommend.test.d.ts +2 -0
  34. package/dist/src/__tests__/recommend.test.d.ts.map +1 -1
  35. package/dist/src/__tests__/recommend.test.js +14 -2
  36. package/dist/src/__tests__/recommend.test.js.map +1 -1
  37. package/dist/src/__tests__/search-online-path.test.d.ts +10 -0
  38. package/dist/src/__tests__/search-online-path.test.d.ts.map +1 -0
  39. package/dist/src/__tests__/search-online-path.test.js +140 -0
  40. package/dist/src/__tests__/search-online-path.test.js.map +1 -0
  41. package/dist/src/__tests__/search.test.js +213 -5
  42. package/dist/src/__tests__/search.test.js.map +1 -1
  43. package/dist/src/context/project-detector.d.ts.map +1 -1
  44. package/dist/src/context/project-detector.js +1 -0
  45. package/dist/src/context/project-detector.js.map +1 -1
  46. package/dist/src/context.async.d.ts +48 -0
  47. package/dist/src/context.async.d.ts.map +1 -0
  48. package/dist/src/context.async.js +215 -0
  49. package/dist/src/context.async.js.map +1 -0
  50. package/dist/src/context.d.ts +5 -145
  51. package/dist/src/context.d.ts.map +1 -1
  52. package/dist/src/context.helpers.d.ts +25 -0
  53. package/dist/src/context.helpers.d.ts.map +1 -0
  54. package/dist/src/context.helpers.js +49 -0
  55. package/dist/src/context.helpers.js.map +1 -0
  56. package/dist/src/context.js +11 -224
  57. package/dist/src/context.js.map +1 -1
  58. package/dist/src/context.types.d.ts +110 -0
  59. package/dist/src/context.types.d.ts.map +1 -0
  60. package/dist/src/context.types.js +10 -0
  61. package/dist/src/context.types.js.map +1 -0
  62. package/dist/src/health/readinessCheck.d.ts +1 -1
  63. package/dist/src/health/readinessCheck.d.ts.map +1 -1
  64. package/dist/src/index.js +21 -152
  65. package/dist/src/index.js.map +1 -1
  66. package/dist/src/indexer/FrontmatterParser.d.ts +6 -0
  67. package/dist/src/indexer/FrontmatterParser.d.ts.map +1 -1
  68. package/dist/src/indexer/FrontmatterParser.js +15 -0
  69. package/dist/src/indexer/FrontmatterParser.js.map +1 -1
  70. package/dist/src/indexer/LocalIndexer.d.ts +4 -0
  71. package/dist/src/indexer/LocalIndexer.d.ts.map +1 -1
  72. package/dist/src/indexer/LocalIndexer.js +3 -0
  73. package/dist/src/indexer/LocalIndexer.js.map +1 -1
  74. package/dist/src/middleware/degradation.d.ts.map +1 -1
  75. package/dist/src/middleware/degradation.js +8 -0
  76. package/dist/src/middleware/degradation.js.map +1 -1
  77. package/dist/src/middleware/errorFormatter.builders.d.ts +49 -0
  78. package/dist/src/middleware/errorFormatter.builders.d.ts.map +1 -0
  79. package/dist/src/middleware/errorFormatter.builders.js +237 -0
  80. package/dist/src/middleware/errorFormatter.builders.js.map +1 -0
  81. package/dist/src/middleware/errorFormatter.d.ts +5 -100
  82. package/dist/src/middleware/errorFormatter.d.ts.map +1 -1
  83. package/dist/src/middleware/errorFormatter.js +16 -238
  84. package/dist/src/middleware/errorFormatter.js.map +1 -1
  85. package/dist/src/middleware/errorFormatter.types.d.ts +81 -0
  86. package/dist/src/middleware/errorFormatter.types.d.ts.map +1 -0
  87. package/dist/src/middleware/errorFormatter.types.js +34 -0
  88. package/dist/src/middleware/errorFormatter.types.js.map +1 -0
  89. package/dist/src/middleware/toolFeatureMapping.d.ts +1 -1
  90. package/dist/src/middleware/toolFeatureMapping.d.ts.map +1 -1
  91. package/dist/src/middleware/toolFeatureMapping.js +8 -0
  92. package/dist/src/middleware/toolFeatureMapping.js.map +1 -1
  93. package/dist/src/tool-dispatch.d.ts +27 -0
  94. package/dist/src/tool-dispatch.d.ts.map +1 -0
  95. package/dist/src/tool-dispatch.js +127 -0
  96. package/dist/src/tool-dispatch.js.map +1 -0
  97. package/dist/src/tools/LocalSkillSearch.d.ts.map +1 -1
  98. package/dist/src/tools/LocalSkillSearch.js +4 -0
  99. package/dist/src/tools/LocalSkillSearch.js.map +1 -1
  100. package/dist/src/tools/get-skill.d.ts.map +1 -1
  101. package/dist/src/tools/get-skill.js +14 -0
  102. package/dist/src/tools/get-skill.js.map +1 -1
  103. package/dist/src/tools/index.d.ts +6 -0
  104. package/dist/src/tools/index.d.ts.map +1 -1
  105. package/dist/src/tools/index.js +6 -0
  106. package/dist/src/tools/index.js.map +1 -1
  107. package/dist/src/tools/install.d.ts +3 -35
  108. package/dist/src/tools/install.d.ts.map +1 -1
  109. package/dist/src/tools/install.helpers.d.ts.map +1 -1
  110. package/dist/src/tools/install.helpers.js +12 -0
  111. package/dist/src/tools/install.helpers.js.map +1 -1
  112. package/dist/src/tools/install.js +57 -86
  113. package/dist/src/tools/install.js.map +1 -1
  114. package/dist/src/tools/install.optimize.d.ts +46 -0
  115. package/dist/src/tools/install.optimize.d.ts.map +1 -0
  116. package/dist/src/tools/install.optimize.js +67 -0
  117. package/dist/src/tools/install.optimize.js.map +1 -0
  118. package/dist/src/tools/install.tool.d.ts +44 -0
  119. package/dist/src/tools/install.tool.d.ts.map +1 -0
  120. package/dist/src/tools/install.tool.js +44 -0
  121. package/dist/src/tools/install.tool.js.map +1 -0
  122. package/dist/src/tools/install.types.d.ts +7 -1
  123. package/dist/src/tools/install.types.d.ts.map +1 -1
  124. package/dist/src/tools/recommend.d.ts +2 -4
  125. package/dist/src/tools/recommend.d.ts.map +1 -1
  126. package/dist/src/tools/recommend.format.d.ts +28 -0
  127. package/dist/src/tools/recommend.format.d.ts.map +1 -0
  128. package/dist/src/tools/recommend.format.js +111 -0
  129. package/dist/src/tools/recommend.format.js.map +1 -0
  130. package/dist/src/tools/recommend.js +6 -97
  131. package/dist/src/tools/recommend.js.map +1 -1
  132. package/dist/src/tools/recommend.types.d.ts +1 -1
  133. package/dist/src/tools/search.d.ts +24 -21
  134. package/dist/src/tools/search.d.ts.map +1 -1
  135. package/dist/src/tools/search.formatter.d.ts +30 -0
  136. package/dist/src/tools/search.formatter.d.ts.map +1 -0
  137. package/dist/src/tools/search.formatter.js +64 -0
  138. package/dist/src/tools/search.formatter.js.map +1 -0
  139. package/dist/src/tools/search.js +65 -50
  140. package/dist/src/tools/search.js.map +1 -1
  141. package/dist/src/tools/skill-audit.d.ts +98 -0
  142. package/dist/src/tools/skill-audit.d.ts.map +1 -0
  143. package/dist/src/tools/skill-audit.js +105 -0
  144. package/dist/src/tools/skill-audit.js.map +1 -0
  145. package/dist/src/tools/skill-audit.test.d.ts +6 -0
  146. package/dist/src/tools/skill-audit.test.d.ts.map +1 -0
  147. package/dist/src/tools/skill-audit.test.js +121 -0
  148. package/dist/src/tools/skill-audit.test.js.map +1 -0
  149. package/dist/src/tools/skill-diff.d.ts +107 -0
  150. package/dist/src/tools/skill-diff.d.ts.map +1 -0
  151. package/dist/src/tools/skill-diff.js +268 -0
  152. package/dist/src/tools/skill-diff.js.map +1 -0
  153. package/dist/src/tools/skill-diff.test.d.ts +6 -0
  154. package/dist/src/tools/skill-diff.test.d.ts.map +1 -0
  155. package/dist/src/tools/skill-diff.test.js +260 -0
  156. package/dist/src/tools/skill-diff.test.js.map +1 -0
  157. package/dist/src/tools/skill-updates.d.ts +89 -0
  158. package/dist/src/tools/skill-updates.d.ts.map +1 -0
  159. package/dist/src/tools/skill-updates.js +111 -0
  160. package/dist/src/tools/skill-updates.js.map +1 -0
  161. package/dist/src/tools/suggest.d.ts +4 -4
  162. package/dist/src/tools/uninstall.d.ts +1 -1
  163. package/dist/src/tools/validate.helpers.d.ts.map +1 -1
  164. package/dist/src/tools/validate.helpers.js +31 -0
  165. package/dist/src/tools/validate.helpers.js.map +1 -1
  166. package/dist/src/utils/validation.d.ts +13 -0
  167. package/dist/src/utils/validation.d.ts.map +1 -1
  168. package/dist/src/utils/validation.js +27 -0
  169. package/dist/src/utils/validation.js.map +1 -1
  170. package/dist/tests/health.test.js +4 -4
  171. package/dist/tests/health.test.js.map +1 -1
  172. package/dist/tests/integration/install.integration.test.js +110 -0
  173. package/dist/tests/integration/install.integration.test.js.map +1 -1
  174. package/dist/tests/integration/recommend.integration.test.js +2 -0
  175. package/dist/tests/integration/recommend.integration.test.js.map +1 -1
  176. package/dist/tests/integration/setup.d.ts +3 -1
  177. package/dist/tests/integration/setup.d.ts.map +1 -1
  178. package/dist/tests/integration/setup.js +4 -1
  179. package/dist/tests/integration/setup.js.map +1 -1
  180. package/dist/tests/performance/search-performance.test.js +10 -6
  181. package/dist/tests/performance/search-performance.test.js.map +1 -1
  182. package/dist/tests/recommend.test.js +2 -0
  183. package/dist/tests/recommend.test.js.map +1 -1
  184. package/dist/tests/unit/install-helpers.test.js +24 -0
  185. package/dist/tests/unit/install-helpers.test.js.map +1 -1
  186. package/dist/tests/unit/validate-helpers.test.js +54 -0
  187. package/dist/tests/unit/validate-helpers.test.js.map +1 -1
  188. package/package.json +2 -2
  189. package/server.json +2 -2
@@ -0,0 +1,107 @@
1
+ /**
2
+ * @fileoverview skill_diff MCP tool — section-level diff between skill versions
3
+ * @module @skillsmith/mcp-server/tools/skill-diff
4
+ * @see SMI-skill-version-tracking Wave 2
5
+ *
6
+ * Returns a structured JSON diff of heading-level (H2/H3) sections between
7
+ * the locally-installed SKILL.md and the latest version recorded in the
8
+ * skill_versions table. Avoids raw unified diffs — human language is used
9
+ * for section names instead.
10
+ *
11
+ * Tier gate: Individual (version_tracking feature flag).
12
+ */
13
+ import { z } from 'zod';
14
+ import type { ToolContext } from '../context.js';
15
+ /** Input schema for skill_diff tool */
16
+ export declare const skillDiffInputSchema: z.ZodObject<{
17
+ skillId: z.ZodString;
18
+ oldContent: z.ZodString;
19
+ newContent: z.ZodString;
20
+ oldRiskScore: z.ZodOptional<z.ZodNumber>;
21
+ newRiskScore: z.ZodOptional<z.ZodNumber>;
22
+ hasLocalModifications: z.ZodDefault<z.ZodBoolean>;
23
+ trustTier: z.ZodDefault<z.ZodEnum<["verified", "community", "experimental"]>>;
24
+ }, "strip", z.ZodTypeAny, {
25
+ trustTier: "verified" | "community" | "experimental";
26
+ skillId: string;
27
+ oldContent: string;
28
+ newContent: string;
29
+ hasLocalModifications: boolean;
30
+ oldRiskScore?: number | undefined;
31
+ newRiskScore?: number | undefined;
32
+ }, {
33
+ skillId: string;
34
+ oldContent: string;
35
+ newContent: string;
36
+ trustTier?: "verified" | "community" | "experimental" | undefined;
37
+ oldRiskScore?: number | undefined;
38
+ newRiskScore?: number | undefined;
39
+ hasLocalModifications?: boolean | undefined;
40
+ }>;
41
+ export type SkillDiffInput = z.infer<typeof skillDiffInputSchema>;
42
+ /** Structured section-level diff response */
43
+ export interface SkillDiffResponse {
44
+ skill: string;
45
+ changeType: 'major' | 'minor' | 'patch' | 'unknown';
46
+ sectionsAdded: string[];
47
+ sectionsRemoved: string[];
48
+ sectionsModified: string[];
49
+ riskScoreDelta: number | null;
50
+ changelog: string | null;
51
+ recommendation: 'auto-update' | 'review-then-update' | 'manual-review-required';
52
+ }
53
+ export declare const skillDiffToolSchema: {
54
+ name: "skill_diff";
55
+ description: string;
56
+ inputSchema: {
57
+ type: "object";
58
+ properties: {
59
+ skillId: {
60
+ type: string;
61
+ description: string;
62
+ };
63
+ oldContent: {
64
+ type: string;
65
+ description: string;
66
+ };
67
+ newContent: {
68
+ type: string;
69
+ description: string;
70
+ };
71
+ oldRiskScore: {
72
+ type: string;
73
+ description: string;
74
+ };
75
+ newRiskScore: {
76
+ type: string;
77
+ description: string;
78
+ };
79
+ hasLocalModifications: {
80
+ type: string;
81
+ description: string;
82
+ };
83
+ trustTier: {
84
+ type: string;
85
+ enum: string[];
86
+ description: string;
87
+ };
88
+ };
89
+ required: string[];
90
+ };
91
+ };
92
+ /**
93
+ * Execute the skill_diff tool.
94
+ *
95
+ * Computes a section-level diff using heading analysis and delegates change
96
+ * classification and risk scoring to core utilities.
97
+ *
98
+ * @param input Validated tool input
99
+ * @param _context Tool context (unused — diff is purely content-based)
100
+ * @returns SkillDiffResponse with section diff and risk recommendation
101
+ */
102
+ export declare function executeSkillDiff(input: SkillDiffInput, _context: ToolContext): Promise<SkillDiffResponse>;
103
+ /**
104
+ * Format a SkillDiffResponse as human-readable text
105
+ */
106
+ export declare function formatSkillDiffResults(response: SkillDiffResponse): string;
107
+ //# sourceMappingURL=skill-diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-diff.d.ts","sourceRoot":"","sources":["../../../src/tools/skill-diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAMhD,uCAAuC;AACvC,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;EA0B/B,CAAA;AAEF,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAA;AAEjE,6CAA6C;AAC7C,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,SAAS,CAAA;IACnD,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,cAAc,EAAE,aAAa,GAAG,oBAAoB,GAAG,wBAAwB,CAAA;CAChF;AAMD,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0C/B,CAAA;AA8GD;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,cAAc,EACrB,QAAQ,EAAE,WAAW,GACpB,OAAO,CAAC,iBAAiB,CAAC,CAiD5B;AAMD;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,MAAM,CAgC1E"}
@@ -0,0 +1,268 @@
1
+ /**
2
+ * @fileoverview skill_diff MCP tool — section-level diff between skill versions
3
+ * @module @skillsmith/mcp-server/tools/skill-diff
4
+ * @see SMI-skill-version-tracking Wave 2
5
+ *
6
+ * Returns a structured JSON diff of heading-level (H2/H3) sections between
7
+ * the locally-installed SKILL.md and the latest version recorded in the
8
+ * skill_versions table. Avoids raw unified diffs — human language is used
9
+ * for section names instead.
10
+ *
11
+ * Tier gate: Individual (version_tracking feature flag).
12
+ */
13
+ import { z } from 'zod';
14
+ import { classifyChange, computeUpdateRisk } from '@skillsmith/core';
15
+ // ============================================================================
16
+ // Input / Output types
17
+ // ============================================================================
18
+ /** Input schema for skill_diff tool */
19
+ export const skillDiffInputSchema = z.object({
20
+ skillId: z.string().min(1).describe('Registry skill identifier (e.g. "author/skill-name")'),
21
+ oldContent: z.string().min(1).describe('Previous SKILL.md content'),
22
+ newContent: z.string().min(1).describe('Updated SKILL.md content'),
23
+ oldRiskScore: z
24
+ .number()
25
+ .int()
26
+ .min(0)
27
+ .max(100)
28
+ .optional()
29
+ .describe('Risk score of the old version (0–100)'),
30
+ newRiskScore: z
31
+ .number()
32
+ .int()
33
+ .min(0)
34
+ .max(100)
35
+ .optional()
36
+ .describe('Risk score of the new version (0–100)'),
37
+ hasLocalModifications: z
38
+ .boolean()
39
+ .default(false)
40
+ .describe('Whether the installed skill has local edits'),
41
+ trustTier: z
42
+ .enum(['verified', 'community', 'experimental'])
43
+ .default('community')
44
+ .describe('Registry trust tier'),
45
+ });
46
+ // ============================================================================
47
+ // Tool schema (MCP tool definition)
48
+ // ============================================================================
49
+ export const skillDiffToolSchema = {
50
+ name: 'skill_diff',
51
+ description: 'Show a section-level diff between two versions of an installed skill. ' +
52
+ 'Returns added, removed, and modified headings along with a change type ' +
53
+ '(major/minor/patch) and update recommendation. ' +
54
+ 'Requires Individual tier or higher (version_tracking feature).',
55
+ inputSchema: {
56
+ type: 'object',
57
+ properties: {
58
+ skillId: {
59
+ type: 'string',
60
+ description: 'Registry skill identifier (e.g. "author/skill-name")',
61
+ },
62
+ oldContent: {
63
+ type: 'string',
64
+ description: 'Previous SKILL.md content',
65
+ },
66
+ newContent: {
67
+ type: 'string',
68
+ description: 'Updated SKILL.md content',
69
+ },
70
+ oldRiskScore: {
71
+ type: 'number',
72
+ description: 'Risk score of the old version (0–100)',
73
+ },
74
+ newRiskScore: {
75
+ type: 'number',
76
+ description: 'Risk score of the new version (0–100)',
77
+ },
78
+ hasLocalModifications: {
79
+ type: 'boolean',
80
+ description: 'Whether the installed skill has local edits',
81
+ },
82
+ trustTier: {
83
+ type: 'string',
84
+ enum: ['verified', 'community', 'experimental'],
85
+ description: 'Registry trust tier',
86
+ },
87
+ },
88
+ required: ['skillId', 'oldContent', 'newContent'],
89
+ },
90
+ };
91
+ // ============================================================================
92
+ // Heading extraction (local — same algorithm as change-classifier)
93
+ // ============================================================================
94
+ function extractHeadings(content) {
95
+ const headings = new Map();
96
+ for (const line of content.split('\n')) {
97
+ const m = /^#{2,3}\s+(.+)/.exec(line);
98
+ if (m) {
99
+ const title = m[1].trim();
100
+ headings.set(title.toLowerCase(), title);
101
+ }
102
+ }
103
+ return headings;
104
+ }
105
+ // ============================================================================
106
+ // Changelog extraction
107
+ // ============================================================================
108
+ /**
109
+ * Extract changelog text from frontmatter or from a ## Changelog section.
110
+ * Returns null if none found.
111
+ */
112
+ function extractChangelog(content) {
113
+ // Frontmatter: changelog: "some text"
114
+ const fmMatch = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
115
+ if (fmMatch) {
116
+ for (const line of fmMatch[1].split('\n')) {
117
+ const colonIdx = line.indexOf(':');
118
+ if (colonIdx !== -1) {
119
+ const key = line.slice(0, colonIdx).trim().toLowerCase();
120
+ const value = line.slice(colonIdx + 1).trim();
121
+ if (key === 'changelog' && value)
122
+ return value.replace(/^["']|["']$/g, '');
123
+ }
124
+ }
125
+ }
126
+ // Body: lines under ## Changelog / ## Change Log
127
+ const lines = content.split('\n');
128
+ let inSection = false;
129
+ const sectionLines = [];
130
+ for (const line of lines) {
131
+ if (/^#{1,3}\s+change[\s-]?log/i.test(line)) {
132
+ inSection = true;
133
+ continue;
134
+ }
135
+ if (inSection && /^#{1,3}\s+/.test(line))
136
+ break;
137
+ if (inSection && line.trim())
138
+ sectionLines.push(line.trim());
139
+ }
140
+ return sectionLines.length > 0 ? sectionLines.slice(0, 5).join(' ') : null;
141
+ }
142
+ // ============================================================================
143
+ // Section-body comparison
144
+ // ============================================================================
145
+ /**
146
+ * Determine which sections (present in both old and new) have changed body
147
+ * content. Returns section titles (canonical form) that were modified.
148
+ */
149
+ function detectModifiedSections(oldContent, newContent) {
150
+ const oldSectionBodies = extractSectionBodies(oldContent);
151
+ const newSectionBodies = extractSectionBodies(newContent);
152
+ const modified = [];
153
+ for (const [key, oldBody] of oldSectionBodies) {
154
+ const newBody = newSectionBodies.get(key);
155
+ if (newBody !== undefined && newBody !== oldBody) {
156
+ modified.push(key);
157
+ }
158
+ }
159
+ return modified;
160
+ }
161
+ /** Build a map of heading (lowercase) → body text */
162
+ function extractSectionBodies(content) {
163
+ const result = new Map();
164
+ const lines = content.split('\n');
165
+ let currentHeading = null;
166
+ const bodyLines = [];
167
+ const flush = () => {
168
+ if (currentHeading !== null) {
169
+ result.set(currentHeading, bodyLines.join('\n').trim());
170
+ bodyLines.length = 0;
171
+ }
172
+ };
173
+ for (const line of lines) {
174
+ const m = /^#{2,3}\s+(.+)/.exec(line);
175
+ if (m) {
176
+ flush();
177
+ currentHeading = m[1].trim().toLowerCase();
178
+ }
179
+ else if (currentHeading !== null) {
180
+ bodyLines.push(line);
181
+ }
182
+ }
183
+ flush();
184
+ return result;
185
+ }
186
+ // ============================================================================
187
+ // Execution
188
+ // ============================================================================
189
+ /**
190
+ * Execute the skill_diff tool.
191
+ *
192
+ * Computes a section-level diff using heading analysis and delegates change
193
+ * classification and risk scoring to core utilities.
194
+ *
195
+ * @param input Validated tool input
196
+ * @param _context Tool context (unused — diff is purely content-based)
197
+ * @returns SkillDiffResponse with section diff and risk recommendation
198
+ */
199
+ export async function executeSkillDiff(input, _context) {
200
+ const validated = skillDiffInputSchema.parse(input);
201
+ const { skillId, oldContent, newContent, oldRiskScore, newRiskScore, hasLocalModifications, trustTier, } = validated;
202
+ // Heading analysis
203
+ const oldHeadings = extractHeadings(oldContent);
204
+ const newHeadings = extractHeadings(newContent);
205
+ const sectionsRemoved = [...oldHeadings.values()].filter((t) => !newHeadings.has(t.toLowerCase()));
206
+ const sectionsAdded = [...newHeadings.values()].filter((t) => !oldHeadings.has(t.toLowerCase()));
207
+ const sectionsModified = detectModifiedSections(oldContent, newContent);
208
+ // Change classification
209
+ const changeType = classifyChange(oldContent, newContent, oldRiskScore, newRiskScore);
210
+ // Risk scoring
211
+ const riskScoreDelta = typeof oldRiskScore === 'number' && typeof newRiskScore === 'number'
212
+ ? newRiskScore - oldRiskScore
213
+ : null;
214
+ const { recommendation } = computeUpdateRisk({
215
+ changeType,
216
+ riskScoreDelta: riskScoreDelta ?? undefined,
217
+ hasLocalModifications,
218
+ trustTier,
219
+ hasChangelog: extractChangelog(newContent) !== null,
220
+ });
221
+ const changelog = extractChangelog(newContent);
222
+ return {
223
+ skill: skillId,
224
+ changeType,
225
+ sectionsAdded,
226
+ sectionsRemoved,
227
+ sectionsModified,
228
+ riskScoreDelta,
229
+ changelog,
230
+ recommendation,
231
+ };
232
+ }
233
+ // ============================================================================
234
+ // Format (for CLI / text output)
235
+ // ============================================================================
236
+ /**
237
+ * Format a SkillDiffResponse as human-readable text
238
+ */
239
+ export function formatSkillDiffResults(response) {
240
+ const lines = [];
241
+ lines.push(`\n=== Skill Diff: ${response.skill} ===\n`);
242
+ lines.push(`Change type: ${response.changeType.toUpperCase()}`);
243
+ lines.push(`Recommendation: ${response.recommendation}`);
244
+ if (response.riskScoreDelta !== null) {
245
+ const prefix = response.riskScoreDelta > 0 ? '+' : '';
246
+ lines.push(`Risk score delta: ${prefix}${response.riskScoreDelta}`);
247
+ }
248
+ if (response.sectionsAdded.length > 0) {
249
+ lines.push('\nSections added:');
250
+ for (const s of response.sectionsAdded)
251
+ lines.push(` + ${s}`);
252
+ }
253
+ if (response.sectionsRemoved.length > 0) {
254
+ lines.push('\nSections removed:');
255
+ for (const s of response.sectionsRemoved)
256
+ lines.push(` - ${s}`);
257
+ }
258
+ if (response.sectionsModified.length > 0) {
259
+ lines.push('\nSections modified:');
260
+ for (const s of response.sectionsModified)
261
+ lines.push(` ~ ${s}`);
262
+ }
263
+ if (response.changelog) {
264
+ lines.push(`\nChangelog: ${response.changelog}`);
265
+ }
266
+ return lines.join('\n');
267
+ }
268
+ //# sourceMappingURL=skill-diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-diff.js","sourceRoot":"","sources":["../../../src/tools/skill-diff.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @fileoverview Tests for skill-diff.ts MCP tool
3
+ * @see SMI-skill-version-tracking Wave 2
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=skill-diff.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-diff.test.d.ts","sourceRoot":"","sources":["../../../src/tools/skill-diff.test.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
@@ -0,0 +1,260 @@
1
+ /**
2
+ * @fileoverview Tests for skill-diff.ts MCP tool
3
+ * @see SMI-skill-version-tracking Wave 2
4
+ */
5
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
6
+ import { executeSkillDiff, skillDiffInputSchema } from './skill-diff.js';
7
+ // ============================================================================
8
+ // Mock context
9
+ // ============================================================================
10
+ const mockContext = {};
11
+ // ============================================================================
12
+ // Fixtures
13
+ // ============================================================================
14
+ const OLD_CONTENT = `---
15
+ name: test-skill
16
+ version: 1.0.0
17
+ ---
18
+
19
+ ## Overview
20
+
21
+ A test skill.
22
+
23
+ ## Usage
24
+
25
+ Run with /test.
26
+
27
+ ## Configuration
28
+
29
+ Set env vars.
30
+ `;
31
+ const NEW_CONTENT_MINOR = `---
32
+ name: test-skill
33
+ version: 1.1.0
34
+ ---
35
+
36
+ ## Overview
37
+
38
+ A test skill.
39
+
40
+ ## Usage
41
+
42
+ Run with /test.
43
+
44
+ ## Configuration
45
+
46
+ Set env vars.
47
+
48
+ ## Examples
49
+
50
+ Here are some examples.
51
+ `;
52
+ const NEW_CONTENT_MAJOR = `---
53
+ name: test-skill
54
+ version: 2.0.0
55
+ ---
56
+
57
+ ## Overview
58
+
59
+ A test skill.
60
+
61
+ ## Usage
62
+
63
+ Run with /test.
64
+ `;
65
+ const NEW_CONTENT_PATCH = `---
66
+ name: test-skill
67
+ version: 1.0.1
68
+ ---
69
+
70
+ ## Overview
71
+
72
+ An updated test skill.
73
+
74
+ ## Usage
75
+
76
+ Run with /test now.
77
+
78
+ ## Configuration
79
+
80
+ Updated env vars.
81
+ `;
82
+ const NEW_CONTENT_WITH_CHANGELOG = `---
83
+ name: test-skill
84
+ version: 1.0.1
85
+ changelog: "Fixed a bug in configuration handling"
86
+ ---
87
+
88
+ ## Overview
89
+
90
+ An updated test skill.
91
+
92
+ ## Usage
93
+
94
+ Run with /test.
95
+
96
+ ## Configuration
97
+
98
+ Updated env vars.
99
+ `;
100
+ // ============================================================================
101
+ // Tests
102
+ // ============================================================================
103
+ describe('executeSkillDiff', () => {
104
+ beforeEach(() => {
105
+ vi.clearAllMocks();
106
+ });
107
+ describe('SkillDiffResponse schema', () => {
108
+ it('returns a valid SkillDiffResponse for a minor update', async () => {
109
+ const result = await executeSkillDiff({
110
+ skillId: 'anthropic/test-skill',
111
+ oldContent: OLD_CONTENT,
112
+ newContent: NEW_CONTENT_MINOR,
113
+ hasLocalModifications: false,
114
+ trustTier: 'community',
115
+ }, mockContext);
116
+ // Verify all required fields are present
117
+ expect(result).toHaveProperty('skill', 'anthropic/test-skill');
118
+ expect(result).toHaveProperty('changeType');
119
+ expect(result).toHaveProperty('sectionsAdded');
120
+ expect(result).toHaveProperty('sectionsRemoved');
121
+ expect(result).toHaveProperty('sectionsModified');
122
+ expect(result).toHaveProperty('riskScoreDelta');
123
+ expect(result).toHaveProperty('changelog');
124
+ expect(result).toHaveProperty('recommendation');
125
+ // Type assertions
126
+ expect(['major', 'minor', 'patch', 'unknown']).toContain(result.changeType);
127
+ expect(Array.isArray(result.sectionsAdded)).toBe(true);
128
+ expect(Array.isArray(result.sectionsRemoved)).toBe(true);
129
+ expect(Array.isArray(result.sectionsModified)).toBe(true);
130
+ expect(['auto-update', 'review-then-update', 'manual-review-required']).toContain(result.recommendation);
131
+ });
132
+ it('detects added sections correctly', async () => {
133
+ const result = await executeSkillDiff({
134
+ skillId: 'test/skill',
135
+ oldContent: OLD_CONTENT,
136
+ newContent: NEW_CONTENT_MINOR,
137
+ hasLocalModifications: false,
138
+ trustTier: 'community',
139
+ }, mockContext);
140
+ expect(result.sectionsAdded).toContain('Examples');
141
+ expect(result.sectionsRemoved).toHaveLength(0);
142
+ });
143
+ it('detects removed sections correctly', async () => {
144
+ const result = await executeSkillDiff({
145
+ skillId: 'test/skill',
146
+ oldContent: OLD_CONTENT,
147
+ newContent: NEW_CONTENT_MAJOR,
148
+ hasLocalModifications: false,
149
+ trustTier: 'community',
150
+ }, mockContext);
151
+ expect(result.sectionsRemoved).toContain('Configuration');
152
+ expect(result.sectionsAdded).toHaveLength(0);
153
+ });
154
+ it('detects modified sections correctly', async () => {
155
+ const result = await executeSkillDiff({
156
+ skillId: 'test/skill',
157
+ oldContent: OLD_CONTENT,
158
+ newContent: NEW_CONTENT_PATCH,
159
+ hasLocalModifications: false,
160
+ trustTier: 'community',
161
+ }, mockContext);
162
+ // Body text changed but no headings added/removed
163
+ expect(result.sectionsRemoved).toHaveLength(0);
164
+ expect(result.sectionsAdded).toHaveLength(0);
165
+ expect(result.sectionsModified.length).toBeGreaterThan(0);
166
+ });
167
+ it('returns riskScoreDelta when scores are provided', async () => {
168
+ const result = await executeSkillDiff({
169
+ skillId: 'test/skill',
170
+ oldContent: OLD_CONTENT,
171
+ newContent: NEW_CONTENT_PATCH,
172
+ oldRiskScore: 10,
173
+ newRiskScore: 30,
174
+ hasLocalModifications: false,
175
+ trustTier: 'community',
176
+ }, mockContext);
177
+ expect(result.riskScoreDelta).toBe(20);
178
+ });
179
+ it('returns null riskScoreDelta when scores are not provided', async () => {
180
+ const result = await executeSkillDiff({
181
+ skillId: 'test/skill',
182
+ oldContent: OLD_CONTENT,
183
+ newContent: NEW_CONTENT_PATCH,
184
+ hasLocalModifications: false,
185
+ trustTier: 'community',
186
+ }, mockContext);
187
+ expect(result.riskScoreDelta).toBeNull();
188
+ });
189
+ it('extracts changelog from frontmatter', async () => {
190
+ const result = await executeSkillDiff({
191
+ skillId: 'test/skill',
192
+ oldContent: OLD_CONTENT,
193
+ newContent: NEW_CONTENT_WITH_CHANGELOG,
194
+ hasLocalModifications: false,
195
+ trustTier: 'community',
196
+ }, mockContext);
197
+ expect(result.changelog).toContain('Fixed a bug');
198
+ });
199
+ it('returns null changelog when none present', async () => {
200
+ const result = await executeSkillDiff({
201
+ skillId: 'test/skill',
202
+ oldContent: OLD_CONTENT,
203
+ newContent: NEW_CONTENT_PATCH,
204
+ hasLocalModifications: false,
205
+ trustTier: 'community',
206
+ }, mockContext);
207
+ expect(result.changelog).toBeNull();
208
+ });
209
+ });
210
+ describe('recommendation based on changeType', () => {
211
+ it('returns auto-update for patch + verified + changelog', async () => {
212
+ const result = await executeSkillDiff({
213
+ skillId: 'test/skill',
214
+ oldContent: OLD_CONTENT,
215
+ newContent: NEW_CONTENT_WITH_CHANGELOG,
216
+ hasLocalModifications: false,
217
+ trustTier: 'verified',
218
+ }, mockContext);
219
+ expect(result.recommendation).toBe('auto-update');
220
+ });
221
+ it('returns manual-review-required for major + local mods + risk increase', async () => {
222
+ const result = await executeSkillDiff({
223
+ skillId: 'test/skill',
224
+ oldContent: OLD_CONTENT,
225
+ newContent: NEW_CONTENT_MAJOR,
226
+ oldRiskScore: 10,
227
+ newRiskScore: 60,
228
+ hasLocalModifications: true,
229
+ trustTier: 'community',
230
+ }, mockContext);
231
+ expect(result.recommendation).toBe('manual-review-required');
232
+ });
233
+ });
234
+ describe('input validation', () => {
235
+ it('rejects empty skillId', () => {
236
+ expect(() => skillDiffInputSchema.parse({
237
+ skillId: '',
238
+ oldContent: 'old',
239
+ newContent: 'new',
240
+ })).toThrow();
241
+ });
242
+ it('rejects empty oldContent', () => {
243
+ expect(() => skillDiffInputSchema.parse({
244
+ skillId: 'test/skill',
245
+ oldContent: '',
246
+ newContent: 'new',
247
+ })).toThrow();
248
+ });
249
+ it('accepts optional fields', () => {
250
+ const parsed = skillDiffInputSchema.parse({
251
+ skillId: 'test/skill',
252
+ oldContent: 'old content',
253
+ newContent: 'new content',
254
+ });
255
+ expect(parsed.hasLocalModifications).toBe(false);
256
+ expect(parsed.trustTier).toBe('community');
257
+ });
258
+ });
259
+ });
260
+ //# sourceMappingURL=skill-diff.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-diff.test.js","sourceRoot":"","sources":["../../../src/tools/skill-diff.test.ts"],"names":[],"mappings":"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"}