tailwindcss-docs-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +159 -0
  3. package/dist/auto-index.d.ts +21 -0
  4. package/dist/auto-index.d.ts.map +1 -0
  5. package/dist/auto-index.js +27 -0
  6. package/dist/auto-index.js.map +1 -0
  7. package/dist/index.d.ts +16 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +67 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/pipeline/chunker.d.ts +61 -0
  12. package/dist/pipeline/chunker.d.ts.map +1 -0
  13. package/dist/pipeline/chunker.js +162 -0
  14. package/dist/pipeline/chunker.js.map +1 -0
  15. package/dist/pipeline/embedder.d.ts +57 -0
  16. package/dist/pipeline/embedder.d.ts.map +1 -0
  17. package/dist/pipeline/embedder.js +108 -0
  18. package/dist/pipeline/embedder.js.map +1 -0
  19. package/dist/pipeline/fetcher.d.ts +63 -0
  20. package/dist/pipeline/fetcher.d.ts.map +1 -0
  21. package/dist/pipeline/fetcher.js +128 -0
  22. package/dist/pipeline/fetcher.js.map +1 -0
  23. package/dist/pipeline/parser.d.ts +73 -0
  24. package/dist/pipeline/parser.d.ts.map +1 -0
  25. package/dist/pipeline/parser.js +127 -0
  26. package/dist/pipeline/parser.js.map +1 -0
  27. package/dist/server.d.ts +47 -0
  28. package/dist/server.d.ts.map +1 -0
  29. package/dist/server.js +141 -0
  30. package/dist/server.js.map +1 -0
  31. package/dist/storage/database.d.ts +90 -0
  32. package/dist/storage/database.d.ts.map +1 -0
  33. package/dist/storage/database.js +342 -0
  34. package/dist/storage/database.js.map +1 -0
  35. package/dist/storage/search.d.ts +84 -0
  36. package/dist/storage/search.d.ts.map +1 -0
  37. package/dist/storage/search.js +165 -0
  38. package/dist/storage/search.js.map +1 -0
  39. package/dist/tools/check-status.d.ts +35 -0
  40. package/dist/tools/check-status.d.ts.map +1 -0
  41. package/dist/tools/check-status.js +40 -0
  42. package/dist/tools/check-status.js.map +1 -0
  43. package/dist/tools/fetch-docs.d.ts +42 -0
  44. package/dist/tools/fetch-docs.d.ts.map +1 -0
  45. package/dist/tools/fetch-docs.js +98 -0
  46. package/dist/tools/fetch-docs.js.map +1 -0
  47. package/dist/tools/list-utilities.d.ts +49 -0
  48. package/dist/tools/list-utilities.d.ts.map +1 -0
  49. package/dist/tools/list-utilities.js +63 -0
  50. package/dist/tools/list-utilities.js.map +1 -0
  51. package/dist/tools/search-docs.d.ts +44 -0
  52. package/dist/tools/search-docs.d.ts.map +1 -0
  53. package/dist/tools/search-docs.js +53 -0
  54. package/dist/tools/search-docs.js.map +1 -0
  55. package/dist/utils/categories.d.ts +25 -0
  56. package/dist/utils/categories.d.ts.map +1 -0
  57. package/dist/utils/categories.js +240 -0
  58. package/dist/utils/categories.js.map +1 -0
  59. package/dist/utils/config.d.ts +39 -0
  60. package/dist/utils/config.d.ts.map +1 -0
  61. package/dist/utils/config.js +37 -0
  62. package/dist/utils/config.js.map +1 -0
  63. package/dist/utils/query-expansion.d.ts +28 -0
  64. package/dist/utils/query-expansion.d.ts.map +1 -0
  65. package/dist/utils/query-expansion.js +147 -0
  66. package/dist/utils/query-expansion.js.map +1 -0
  67. package/dist/utils/similarity.d.ts +12 -0
  68. package/dist/utils/similarity.d.ts.map +1 -0
  69. package/dist/utils/similarity.js +28 -0
  70. package/dist/utils/similarity.js.map +1 -0
  71. package/package.json +59 -0
@@ -0,0 +1,84 @@
1
+ import type { Embedder } from "../pipeline/embedder.js";
2
+ import type { TailwindVersion } from "../utils/config.js";
3
+ import { type ChunkRow, type Database } from "./database.js";
4
+ /**
5
+ * Invalidate the in-process chunk embedding cache for a specific version,
6
+ * or all versions if none specified.
7
+ */
8
+ export declare function invalidateChunkCache(version?: TailwindVersion): void;
9
+ /**
10
+ * A search result with score and metadata.
11
+ */
12
+ export interface SearchResult {
13
+ /** Combined relevance score, normalized so the top result = 1.0 */
14
+ score: number;
15
+ /** Chunk heading breadcrumb */
16
+ heading: string;
17
+ /** Chunk content (clean markdown) */
18
+ content: string;
19
+ /** Deep link to tailwindcss.com */
20
+ url: string;
21
+ /** Parent document title */
22
+ docTitle: string;
23
+ }
24
+ /**
25
+ * Options for search operations.
26
+ */
27
+ export interface SearchOptions {
28
+ /** Search query string */
29
+ query: string;
30
+ /** Tailwind CSS version to search */
31
+ version: TailwindVersion;
32
+ /** Maximum number of results (default: 5) */
33
+ limit?: number;
34
+ }
35
+ /**
36
+ * Internal representation of a scored chunk during search.
37
+ */
38
+ export interface ScoredChunk {
39
+ chunk: ChunkRow;
40
+ semanticScore: number;
41
+ keywordScore: number;
42
+ fusedScore: number;
43
+ }
44
+ /**
45
+ * Weights for reciprocal rank fusion.
46
+ * Default 1.0/1.0 produces standard unweighted RRF.
47
+ */
48
+ export interface FusionWeights {
49
+ semantic: number;
50
+ keyword: number;
51
+ }
52
+ /**
53
+ * Perform hybrid semantic + keyword search across indexed documentation.
54
+ *
55
+ * Strategy:
56
+ * 1. Semantic search: embed query -> cosine similarity against all chunk embeddings
57
+ * 2. Keyword search: FTS5 query for exact class name matches
58
+ * 3. Merge and rank: combine results, dedup by chunk ID, sort by fused score
59
+ */
60
+ export declare function hybridSearch(db: Database, embedder: Embedder, options: SearchOptions): Promise<SearchResult[]>;
61
+ /**
62
+ * Perform semantic-only search using cosine similarity.
63
+ *
64
+ * Embeds the query with the snowflake-arctic-embed-xs query prefix,
65
+ * then computes cosine similarity against all stored chunk embeddings.
66
+ */
67
+ export declare function semanticSearch(embedder: Embedder, chunks: ChunkRow[], query: string, limit: number): Promise<ScoredChunk[]>;
68
+ /**
69
+ * Perform keyword-only search using SQLite FTS5.
70
+ *
71
+ * Useful for exact class name lookups (e.g., "px-4", "grid-cols-3").
72
+ */
73
+ export declare function keywordSearch(db: Database, query: string, version: TailwindVersion, limit: number): ScoredChunk[];
74
+ /**
75
+ * Fuse semantic and keyword scores into a single ranking.
76
+ *
77
+ * Uses reciprocal rank fusion (RRF) to combine results from
78
+ * both search strategies, deduplicating by chunk ID.
79
+ *
80
+ * Formula: score = w_s * 1/(k + rank_semantic) + w_k * 1/(k + rank_keyword)
81
+ * where k = 60 (standard RRF constant), w_s/w_k = strategy weights
82
+ */
83
+ export declare function fuseResults(semanticResults: ScoredChunk[], keywordResults: ScoredChunk[], limit: number, weights?: FusionWeights): ScoredChunk[];
84
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/storage/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAG1D,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,QAAQ,EAAmB,MAAM,eAAe,CAAC;AAY9E;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,IAAI,CAMpE;AASD;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAC;IACd,+BAA+B;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,mCAAmC;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,OAAO,EAAE,eAAe,CAAC;IACzB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,QAAQ,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAChC,EAAE,EAAE,QAAQ,EACZ,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,YAAY,EAAE,CAAC,CAkDzB;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,QAAQ,EAAE,EAClB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,WAAW,EAAE,CAAC,CAmBxB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,EAAE,EAAE,QAAQ,EACZ,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,eAAe,EACxB,KAAK,EAAE,MAAM,GACZ,WAAW,EAAE,CAWf;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CACzB,eAAe,EAAE,WAAW,EAAE,EAC9B,cAAc,EAAE,WAAW,EAAE,EAC7B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,aAA+C,GACvD,WAAW,EAAE,CAoCf"}
@@ -0,0 +1,165 @@
1
+ import { expandQuery } from "../utils/query-expansion.js";
2
+ import { cosineSimilarity } from "../utils/similarity.js";
3
+ import { blobToEmbedding } from "./database.js";
4
+ /** Log a performance warning when brute-force search exceeds this many chunks. */
5
+ const BRUTE_FORCE_WARN_THRESHOLD = 5000;
6
+ /**
7
+ * In-process cache for chunk embeddings, keyed by version.
8
+ * Avoids reloading all BLOB data from SQLite on every search request.
9
+ * Call `invalidateChunkCache()` after indexing to pick up new data.
10
+ */
11
+ const chunkCache = new Map();
12
+ /**
13
+ * Invalidate the in-process chunk embedding cache for a specific version,
14
+ * or all versions if none specified.
15
+ */
16
+ export function invalidateChunkCache(version) {
17
+ if (version) {
18
+ chunkCache.delete(version);
19
+ }
20
+ else {
21
+ chunkCache.clear();
22
+ }
23
+ }
24
+ /** Multiplier applied to the requested limit for each search strategy.
25
+ * Fetching more candidates from each strategy improves fusion quality. */
26
+ const CANDIDATE_MULTIPLIER = 3;
27
+ /** Standard RRF constant. Higher values reduce the impact of rank differences. */
28
+ const RRF_K = 60;
29
+ /**
30
+ * Perform hybrid semantic + keyword search across indexed documentation.
31
+ *
32
+ * Strategy:
33
+ * 1. Semantic search: embed query -> cosine similarity against all chunk embeddings
34
+ * 2. Keyword search: FTS5 query for exact class name matches
35
+ * 3. Merge and rank: combine results, dedup by chunk ID, sort by fused score
36
+ */
37
+ export async function hybridSearch(db, embedder, options) {
38
+ const { query, version, limit = 5 } = options;
39
+ if (!query.trim())
40
+ return [];
41
+ // Expand query: map class prefixes to CSS property names (e.g., text-lg → font size)
42
+ const expandedQuery = expandQuery(query);
43
+ let chunks = chunkCache.get(version);
44
+ if (!chunks) {
45
+ chunks = db.getAllChunksWithEmbeddings(version);
46
+ chunkCache.set(version, chunks);
47
+ }
48
+ if (chunks.length > BRUTE_FORCE_WARN_THRESHOLD) {
49
+ console.warn(`[tailwindcss-docs-mcp] ${chunks.length} chunks loaded for brute-force semantic search. Consider optimizing for large corpora.`);
50
+ }
51
+ if (chunks.length === 0)
52
+ return [];
53
+ const fetchLimit = limit * CANDIDATE_MULTIPLIER;
54
+ // keywordSearch is synchronous but wrapped in Promise.all alongside the async
55
+ // semanticSearch for uniform destructuring. No performance impact — sync
56
+ // functions in Promise.all resolve in the same microtask.
57
+ const [semantic, keyword] = await Promise.all([
58
+ semanticSearch(embedder, chunks, expandedQuery, fetchLimit),
59
+ keywordSearch(db, expandedQuery, version, fetchLimit),
60
+ ]);
61
+ // Boost keyword weight when query contains Tailwind class names (e.g., text-lg, pt-6)
62
+ const hasClassNames = /\b[a-z]+(?:-[a-z0-9]+)+\b/.test(query);
63
+ const weights = hasClassNames
64
+ ? { semantic: 1.0, keyword: 1.5 }
65
+ : { semantic: 1.0, keyword: 1.0 };
66
+ const fused = fuseResults(semantic, keyword, limit, weights);
67
+ // Normalize fused scores to 0-1 range (top result = 1.0)
68
+ const maxScore = fused[0]?.fusedScore ?? 1;
69
+ return fused.map((scored) => {
70
+ const doc = db.getDocById(scored.chunk.doc_id);
71
+ return {
72
+ score: maxScore > 0 ? scored.fusedScore / maxScore : 0,
73
+ heading: scored.chunk.heading,
74
+ content: scored.chunk.content,
75
+ url: scored.chunk.url,
76
+ docTitle: doc?.title ?? "",
77
+ };
78
+ });
79
+ }
80
+ /**
81
+ * Perform semantic-only search using cosine similarity.
82
+ *
83
+ * Embeds the query with the snowflake-arctic-embed-xs query prefix,
84
+ * then computes cosine similarity against all stored chunk embeddings.
85
+ */
86
+ export async function semanticSearch(embedder, chunks, query, limit) {
87
+ const queryEmbedding = await embedder.embed(query, { isQuery: true });
88
+ return chunks
89
+ .flatMap((chunk) => {
90
+ if (chunk.embedding === null)
91
+ return [];
92
+ const chunkEmbedding = blobToEmbedding(chunk.embedding);
93
+ const score = cosineSimilarity(queryEmbedding, chunkEmbedding);
94
+ return [
95
+ {
96
+ chunk,
97
+ semanticScore: score,
98
+ keywordScore: 0,
99
+ fusedScore: score,
100
+ },
101
+ ];
102
+ })
103
+ .sort((a, b) => b.semanticScore - a.semanticScore)
104
+ .slice(0, limit);
105
+ }
106
+ /**
107
+ * Perform keyword-only search using SQLite FTS5.
108
+ *
109
+ * Useful for exact class name lookups (e.g., "px-4", "grid-cols-3").
110
+ */
111
+ export function keywordSearch(db, query, version, limit) {
112
+ const ftsChunks = db.searchFts(query, version, limit);
113
+ return ftsChunks.map((chunk, index) => ({
114
+ chunk,
115
+ semanticScore: 0,
116
+ // keywordScore reflects FTS5 rank order for inspection/debugging.
117
+ // It is NOT used in fusion — RRF uses position-based ranks instead.
118
+ keywordScore: 1 / (index + 1),
119
+ fusedScore: 0,
120
+ }));
121
+ }
122
+ /**
123
+ * Fuse semantic and keyword scores into a single ranking.
124
+ *
125
+ * Uses reciprocal rank fusion (RRF) to combine results from
126
+ * both search strategies, deduplicating by chunk ID.
127
+ *
128
+ * Formula: score = w_s * 1/(k + rank_semantic) + w_k * 1/(k + rank_keyword)
129
+ * where k = 60 (standard RRF constant), w_s/w_k = strategy weights
130
+ */
131
+ export function fuseResults(semanticResults, keywordResults, limit, weights = { semantic: 1.0, keyword: 1.0 }) {
132
+ const chunkMap = new Map();
133
+ // Assign semantic RRF scores (1-indexed ranks)
134
+ for (let i = 0; i < semanticResults.length; i++) {
135
+ const rank = i + 1;
136
+ const rrf = weights.semantic * (1 / (RRF_K + rank));
137
+ const { chunk } = semanticResults[i];
138
+ const entry = chunkMap.get(chunk.id) ?? {
139
+ chunk,
140
+ semanticScore: 0,
141
+ keywordScore: 0,
142
+ fusedScore: 0,
143
+ };
144
+ entry.fusedScore += rrf;
145
+ entry.semanticScore = semanticResults[i].semanticScore;
146
+ chunkMap.set(chunk.id, entry);
147
+ }
148
+ // Assign keyword RRF scores
149
+ for (let i = 0; i < keywordResults.length; i++) {
150
+ const rank = i + 1;
151
+ const rrf = weights.keyword * (1 / (RRF_K + rank));
152
+ const { chunk } = keywordResults[i];
153
+ const entry = chunkMap.get(chunk.id) ?? {
154
+ chunk,
155
+ semanticScore: 0,
156
+ keywordScore: 0,
157
+ fusedScore: 0,
158
+ };
159
+ entry.fusedScore += rrf;
160
+ entry.keywordScore = keywordResults[i].keywordScore;
161
+ chunkMap.set(chunk.id, entry);
162
+ }
163
+ return [...chunkMap.values()].sort((a, b) => b.fusedScore - a.fusedScore).slice(0, limit);
164
+ }
165
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/storage/search.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import type { EmbedderStatus } from "../server.js";
2
+ import type { Database, IndexStatus } from "../storage/database.js";
3
+ import type { TailwindVersion } from "../utils/config.js";
4
+ /**
5
+ * Input parameters for the check_status MCP tool.
6
+ */
7
+ export interface CheckStatusInput {
8
+ /** Check specific version. Omit to check all. */
9
+ version?: TailwindVersion;
10
+ }
11
+ /**
12
+ * Result of the check_status operation.
13
+ */
14
+ export interface CheckStatusResult {
15
+ /** Whether any version is indexed */
16
+ indexed: boolean;
17
+ /** Status for each requested version */
18
+ versions: IndexStatus[];
19
+ /** Current embedder loading status */
20
+ embedderStatus: EmbedderStatus;
21
+ /** Human-readable status message */
22
+ message: string;
23
+ }
24
+ /**
25
+ * Handle the `check_status` MCP tool call.
26
+ *
27
+ * Reports the current index state without triggering any work.
28
+ * Returns doc/chunk counts, embedding model used, and last indexed timestamp.
29
+ */
30
+ export declare function handleCheckStatus(input: CheckStatusInput, db: Database, embedderStatus: EmbedderStatus): CheckStatusResult;
31
+ /**
32
+ * Format the status as markdown for LLM consumption.
33
+ */
34
+ export declare function formatStatus(indexed: boolean, versions: IndexStatus[], embedderStatus: EmbedderStatus): string;
35
+ //# sourceMappingURL=check-status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-status.d.ts","sourceRoot":"","sources":["../../src/tools/check-status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE1D;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,iDAAiD;IACjD,OAAO,CAAC,EAAE,eAAe,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,qCAAqC;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,wCAAwC;IACxC,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,sCAAsC;IACtC,cAAc,EAAE,cAAc,CAAC;IAC/B,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,gBAAgB,EACvB,EAAE,EAAE,QAAQ,EACZ,cAAc,EAAE,cAAc,GAC7B,iBAAiB,CAOnB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,WAAW,EAAE,EACvB,cAAc,EAAE,cAAc,GAC7B,MAAM,CA2BR"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Handle the `check_status` MCP tool call.
3
+ *
4
+ * Reports the current index state without triggering any work.
5
+ * Returns doc/chunk counts, embedding model used, and last indexed timestamp.
6
+ */
7
+ export function handleCheckStatus(input, db, embedderStatus) {
8
+ const versions = db.getIndexStatus(input.version);
9
+ const indexed = versions.length > 0;
10
+ const message = formatStatus(indexed, versions, embedderStatus);
11
+ return { indexed, versions, embedderStatus, message };
12
+ }
13
+ /**
14
+ * Format the status as markdown for LLM consumption.
15
+ */
16
+ export function formatStatus(indexed, versions, embedderStatus) {
17
+ const lines = ["# Tailwind CSS Documentation Index Status\n"];
18
+ // Embedding model status
19
+ const statusLabels = {
20
+ pending: "initializing",
21
+ downloading: "downloading (~27 MB)",
22
+ ready: "ready",
23
+ failed: "failed to load",
24
+ };
25
+ lines.push(`**Embedding model**: ${statusLabels[embedderStatus]}\n`);
26
+ if (!indexed) {
27
+ lines.push("Not indexed. Run fetch_docs to index Tailwind CSS documentation.");
28
+ return lines.join("\n");
29
+ }
30
+ for (const v of versions) {
31
+ lines.push(`## ${v.version}`);
32
+ lines.push(`- **Documents**: ${v.doc_count}`);
33
+ lines.push(`- **Chunks**: ${v.chunk_count}`);
34
+ lines.push(`- **Model**: ${v.embedding_model} (${v.embedding_dimensions} dimensions)`);
35
+ lines.push(`- **Last indexed**: ${v.indexed_at}`);
36
+ lines.push("");
37
+ }
38
+ return lines.join("\n");
39
+ }
40
+ //# sourceMappingURL=check-status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-status.js","sourceRoot":"","sources":["../../src/tools/check-status.ts"],"names":[],"mappings":"AA0BA;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAAuB,EACvB,EAAY,EACZ,cAA8B;IAE9B,MAAM,QAAQ,GAAG,EAAE,CAAC,cAAc,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAClD,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;IAEpC,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC;IAEhE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC;AACxD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAC1B,OAAgB,EAChB,QAAuB,EACvB,cAA8B;IAE9B,MAAM,KAAK,GAAa,CAAC,6CAA6C,CAAC,CAAC;IAExE,yBAAyB;IACzB,MAAM,YAAY,GAAmC;QACnD,OAAO,EAAE,cAAc;QACvB,WAAW,EAAE,sBAAsB;QACnC,KAAK,EAAE,OAAO;QACd,MAAM,EAAE,gBAAgB;KACzB,CAAC;IACF,KAAK,CAAC,IAAI,CAAC,wBAAwB,YAAY,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;IAErE,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,KAAK,CAAC,IAAI,CAAC,kEAAkE,CAAC,CAAC;QAC/E,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9B,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC;QAC9C,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;QAC7C,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,eAAe,KAAK,CAAC,CAAC,oBAAoB,cAAc,CAAC,CAAC;QACvF,KAAK,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC;QAClD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,42 @@
1
+ import type { Embedder } from "../pipeline/embedder.js";
2
+ import type { Database } from "../storage/database.js";
3
+ import type { Config, TailwindVersion } from "../utils/config.js";
4
+ /**
5
+ * Input parameters for the fetch_docs MCP tool.
6
+ */
7
+ export interface FetchDocsInput {
8
+ /** Tailwind CSS major version (default: "v4") */
9
+ version?: TailwindVersion;
10
+ /** Force re-download and re-index even if already cached (default: false) */
11
+ force?: boolean;
12
+ }
13
+ /**
14
+ * Result of the fetch_docs operation.
15
+ */
16
+ export interface FetchDocsResult {
17
+ /** Status message describing what happened */
18
+ message: string;
19
+ /** Number of documents processed */
20
+ docCount: number;
21
+ /** Number of chunks created */
22
+ chunkCount: number;
23
+ /** Time taken in milliseconds */
24
+ durationMs: number;
25
+ }
26
+ /**
27
+ * Handle the `fetch_docs` MCP tool call.
28
+ *
29
+ * Orchestrates the full pipeline:
30
+ * 1. Fetch MDX files from GitHub (or use cache)
31
+ * 2. Parse MDX into clean markdown
32
+ * 3. Chunk documents by headings
33
+ * 4. Generate embeddings via ONNX
34
+ * 5. Store chunks and embeddings in SQLite
35
+ *
36
+ * Uses content hashing (SHA-256) for incremental re-indexing:
37
+ * unchanged chunks are not re-embedded.
38
+ *
39
+ * Serialized: concurrent calls are queued to prevent data corruption.
40
+ */
41
+ export declare function handleFetchDocs(input: FetchDocsInput, config: Config, db: Database, embedder: Embedder): Promise<FetchDocsResult>;
42
+ //# sourceMappingURL=fetch-docs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch-docs.d.ts","sourceRoot":"","sources":["../../src/tools/fetch-docs.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAGxD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAEvD,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAElE;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,iDAAiD;IACjD,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,6EAA6E;IAC7E,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,8CAA8C;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,+BAA+B;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAC;CACpB;AAQD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,cAAc,EACrB,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,QAAQ,EACZ,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,eAAe,CAAC,CAa1B"}
@@ -0,0 +1,98 @@
1
+ import { chunkDocument } from "../pipeline/chunker.js";
2
+ import { buildEmbeddingInput } from "../pipeline/embedder.js";
3
+ import { fetchDocs, readCachedDocs } from "../pipeline/fetcher.js";
4
+ import { parseMdx } from "../pipeline/parser.js";
5
+ import { invalidateChunkCache } from "../storage/search.js";
6
+ /**
7
+ * Mutex to serialize concurrent fetch_docs calls.
8
+ * Prevents deleteOrphanedChunks from racing with inserts from another call.
9
+ */
10
+ let fetchLock = Promise.resolve();
11
+ /**
12
+ * Handle the `fetch_docs` MCP tool call.
13
+ *
14
+ * Orchestrates the full pipeline:
15
+ * 1. Fetch MDX files from GitHub (or use cache)
16
+ * 2. Parse MDX into clean markdown
17
+ * 3. Chunk documents by headings
18
+ * 4. Generate embeddings via ONNX
19
+ * 5. Store chunks and embeddings in SQLite
20
+ *
21
+ * Uses content hashing (SHA-256) for incremental re-indexing:
22
+ * unchanged chunks are not re-embedded.
23
+ *
24
+ * Serialized: concurrent calls are queued to prevent data corruption.
25
+ */
26
+ export async function handleFetchDocs(input, config, db, embedder) {
27
+ let releaseLock;
28
+ const acquired = new Promise((resolve) => {
29
+ releaseLock = resolve;
30
+ });
31
+ const prev = fetchLock;
32
+ fetchLock = acquired;
33
+ await prev;
34
+ try {
35
+ return await doFetchDocs(input, config, db, embedder);
36
+ }
37
+ finally {
38
+ releaseLock();
39
+ }
40
+ }
41
+ async function doFetchDocs(input, config, db, embedder) {
42
+ const start = Date.now();
43
+ const version = input.version ?? config.defaultVersion;
44
+ const force = input.force ?? false;
45
+ // Step 1: Fetch from GitHub (or use cache)
46
+ const fetchResult = await fetchDocs(config, { version, force });
47
+ // Step 1b: Read cached files
48
+ const rawFiles = readCachedDocs(config, version);
49
+ if (rawFiles.length === 0) {
50
+ return {
51
+ message: "No documentation files found.",
52
+ docCount: 0,
53
+ chunkCount: 0,
54
+ durationMs: Date.now() - start,
55
+ };
56
+ }
57
+ let totalChunks = 0;
58
+ // Process each document
59
+ for (const file of rawFiles) {
60
+ // Step 2: Parse MDX → clean markdown
61
+ const doc = parseMdx(file.content, file.slug, version);
62
+ // Step 3: Upsert document
63
+ const docId = db.upsertDoc(doc);
64
+ // Step 4: Chunk
65
+ const chunks = chunkDocument(doc);
66
+ const validHashes = new Set();
67
+ for (const chunk of chunks) {
68
+ const hash = chunk.id; // chunk.id is the content hash
69
+ validHashes.add(hash);
70
+ // Check if chunk already exists with same hash (skip re-embedding)
71
+ const existing = db.getChunkByHash(hash, docId);
72
+ if (existing?.embedding && !force) {
73
+ continue;
74
+ }
75
+ // Step 5: Embed
76
+ const embeddingInput = buildEmbeddingInput(doc.title, chunk.heading, chunk.content);
77
+ const embedding = await embedder.embed(embeddingInput);
78
+ // Step 6: Store
79
+ db.upsertChunk(chunk, docId, embedding);
80
+ }
81
+ // Clean up orphaned chunks
82
+ db.deleteOrphanedChunks(docId, validHashes);
83
+ totalChunks += chunks.length;
84
+ }
85
+ // Update index status
86
+ db.updateIndexStatus(version, config.embeddingModel, config.embeddingDimensions);
87
+ // Invalidate search cache so the next query picks up new embeddings
88
+ invalidateChunkCache(version);
89
+ const durationMs = Date.now() - start;
90
+ const partial = fetchResult.skipped > 0 ? ` (${fetchResult.skipped} files failed to fetch)` : "";
91
+ return {
92
+ message: `Indexed ${rawFiles.length} documents with ${totalChunks} chunks in ${durationMs}ms.${partial}`,
93
+ docCount: rawFiles.length,
94
+ chunkCount: totalChunks,
95
+ durationMs,
96
+ };
97
+ }
98
+ //# sourceMappingURL=fetch-docs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch-docs.js","sourceRoot":"","sources":["../../src/tools/fetch-docs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import type { Database } from "../storage/database.js";
2
+ import type { TailwindVersion } from "../utils/config.js";
3
+ /**
4
+ * Input parameters for the list_utilities MCP tool.
5
+ */
6
+ export interface ListUtilitiesInput {
7
+ /** Filter by category name (e.g., "layout", "spacing", "typography"). Omit to list all. */
8
+ category?: string;
9
+ /** Tailwind CSS major version (default: "v4") */
10
+ version?: TailwindVersion;
11
+ }
12
+ /**
13
+ * A utility entry in the list response.
14
+ */
15
+ export interface UtilityEntry {
16
+ /** Document title */
17
+ title: string;
18
+ /** Document description */
19
+ description: string;
20
+ /** URL to the documentation page */
21
+ url: string;
22
+ /** Category this utility belongs to */
23
+ category: string;
24
+ }
25
+ /**
26
+ * Result of the list_utilities operation.
27
+ */
28
+ export interface ListUtilitiesResult {
29
+ /** Grouped utility entries */
30
+ categories: Array<{
31
+ name: string;
32
+ description: string;
33
+ utilities: UtilityEntry[];
34
+ }>;
35
+ }
36
+ /**
37
+ * Handle the `list_utilities` MCP tool call.
38
+ *
39
+ * Lists all Tailwind CSS utility categories and their documentation pages.
40
+ * Optionally filters by category name (case-insensitive).
41
+ * Returns category metadata from the static mapping, enriched with
42
+ * indexed document titles and descriptions when available.
43
+ */
44
+ export declare function handleListUtilities(input: ListUtilitiesInput, db: Database, defaultVersion: TailwindVersion): ListUtilitiesResult;
45
+ /**
46
+ * Format the utility list as markdown for LLM consumption.
47
+ */
48
+ export declare function formatUtilitiesList(result: ListUtilitiesResult): string;
49
+ //# sourceMappingURL=list-utilities.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-utilities.d.ts","sourceRoot":"","sources":["../../src/tools/list-utilities.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAEvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE1D;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,OAAO,CAAC,EAAE,eAAe,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,oCAAoC;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,uCAAuC;IACvC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,8BAA8B;IAC9B,UAAU,EAAE,KAAK,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,YAAY,EAAE,CAAC;KAC3B,CAAC,CAAC;CACJ;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,kBAAkB,EACzB,EAAE,EAAE,QAAQ,EACZ,cAAc,EAAE,eAAe,GAC9B,mBAAmB,CA0BrB;AAYD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,MAAM,CAoBvE"}
@@ -0,0 +1,63 @@
1
+ import { UTILITY_CATEGORIES, findCategory } from "../utils/categories.js";
2
+ /**
3
+ * Handle the `list_utilities` MCP tool call.
4
+ *
5
+ * Lists all Tailwind CSS utility categories and their documentation pages.
6
+ * Optionally filters by category name (case-insensitive).
7
+ * Returns category metadata from the static mapping, enriched with
8
+ * indexed document titles and descriptions when available.
9
+ */
10
+ export function handleListUtilities(input, db, defaultVersion) {
11
+ const version = input.version ?? defaultVersion;
12
+ let categories;
13
+ if (input.category) {
14
+ const match = findCategory(input.category);
15
+ categories = match ? [match] : [];
16
+ }
17
+ else {
18
+ categories = UTILITY_CATEGORIES;
19
+ }
20
+ return {
21
+ categories: categories.map((cat) => ({
22
+ name: cat.name,
23
+ description: cat.description,
24
+ utilities: cat.slugs.map((slug) => {
25
+ const doc = db.getDoc(slug, version);
26
+ return {
27
+ title: doc?.title ?? slugToTitle(slug),
28
+ description: doc?.description ?? "",
29
+ url: doc?.url ?? `https://tailwindcss.com/docs/${slug}`,
30
+ category: cat.name,
31
+ };
32
+ }),
33
+ })),
34
+ };
35
+ }
36
+ /**
37
+ * Convert a slug to a human-readable title as fallback.
38
+ */
39
+ function slugToTitle(slug) {
40
+ return slug
41
+ .split("-")
42
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
43
+ .join(" ");
44
+ }
45
+ /**
46
+ * Format the utility list as markdown for LLM consumption.
47
+ */
48
+ export function formatUtilitiesList(result) {
49
+ if (result.categories.length === 0) {
50
+ return "No matching utility categories found.";
51
+ }
52
+ const lines = ["# Tailwind CSS Utility Categories\n"];
53
+ for (const cat of result.categories) {
54
+ lines.push(`## ${cat.name}`);
55
+ lines.push(`${cat.description}\n`);
56
+ for (const util of cat.utilities) {
57
+ lines.push(`- [${util.title}](${util.url})${util.description ? ` — ${util.description}` : ""}`);
58
+ }
59
+ lines.push("");
60
+ }
61
+ return lines.join("\n");
62
+ }
63
+ //# sourceMappingURL=list-utilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-utilities.js","sourceRoot":"","sources":["../../src/tools/list-utilities.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAwB,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAuChG;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CACjC,KAAyB,EACzB,EAAY,EACZ,cAA+B;IAE/B,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,IAAI,cAAc,CAAC;IAEhD,IAAI,UAA6B,CAAC;IAClC,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QACnB,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC3C,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACpC,CAAC;SAAM,CAAC;QACN,UAAU,GAAG,kBAAkB,CAAC;IAClC,CAAC;IAED,OAAO;QACL,UAAU,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YACnC,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,WAAW,EAAE,GAAG,CAAC,WAAW;YAC5B,SAAS,EAAE,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;gBAChC,MAAM,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;gBACrC,OAAO;oBACL,KAAK,EAAE,GAAG,EAAE,KAAK,IAAI,WAAW,CAAC,IAAI,CAAC;oBACtC,WAAW,EAAE,GAAG,EAAE,WAAW,IAAI,EAAE;oBACnC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,gCAAgC,IAAI,EAAE;oBACvD,QAAQ,EAAE,GAAG,CAAC,IAAI;iBACnB,CAAC;YACJ,CAAC,CAAC;SACH,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,SAAS,WAAW,CAAC,IAAY;IAC/B,OAAO,IAAI;SACR,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;SAClD,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAA2B;IAC7D,IAAI,MAAM,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnC,OAAO,uCAAuC,CAAC;IACjD,CAAC;IAED,MAAM,KAAK,GAAa,CAAC,qCAAqC,CAAC,CAAC;IAEhE,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QACpC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QAC7B,KAAK,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,WAAW,IAAI,CAAC,CAAC;QAEnC,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;YACjC,KAAK,CAAC,IAAI,CACR,MAAM,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CACpF,CAAC;QACJ,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}