@compilr-dev/sdk 0.9.7 → 0.9.9

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 (37) hide show
  1. package/dist/detection/common.d.ts +24 -0
  2. package/dist/detection/common.js +247 -0
  3. package/dist/detection/index.d.ts +6 -0
  4. package/dist/detection/index.js +5 -0
  5. package/dist/detection/project-detector.d.ts +29 -0
  6. package/dist/detection/project-detector.js +106 -0
  7. package/dist/detection/strategies/book.d.ts +7 -0
  8. package/dist/detection/strategies/book.js +48 -0
  9. package/dist/detection/strategies/business.d.ts +7 -0
  10. package/dist/detection/strategies/business.js +22 -0
  11. package/dist/detection/strategies/content.d.ts +7 -0
  12. package/dist/detection/strategies/content.js +21 -0
  13. package/dist/detection/strategies/course.d.ts +7 -0
  14. package/dist/detection/strategies/course.js +30 -0
  15. package/dist/detection/strategies/general.d.ts +8 -0
  16. package/dist/detection/strategies/general.js +10 -0
  17. package/dist/detection/strategies/research.d.ts +7 -0
  18. package/dist/detection/strategies/research.js +44 -0
  19. package/dist/detection/strategies/software.d.ts +8 -0
  20. package/dist/detection/strategies/software.js +199 -0
  21. package/dist/detection/strategies/tech-docs.d.ts +7 -0
  22. package/dist/detection/strategies/tech-docs.js +51 -0
  23. package/dist/detection/types.d.ts +80 -0
  24. package/dist/detection/types.js +7 -0
  25. package/dist/guide/guide-tool.d.ts +31 -0
  26. package/dist/guide/guide-tool.js +90 -0
  27. package/dist/guide/index.d.ts +7 -0
  28. package/dist/guide/index.js +6 -0
  29. package/dist/guide/search.d.ts +21 -0
  30. package/dist/guide/search.js +62 -0
  31. package/dist/guide/shared-content.d.ts +8 -0
  32. package/dist/guide/shared-content.js +267 -0
  33. package/dist/guide/types.d.ts +34 -0
  34. package/dist/guide/types.js +4 -0
  35. package/dist/index.d.ts +4 -0
  36. package/dist/index.js +8 -0
  37. package/package.json +1 -1
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Software Detection Strategy
3
+ *
4
+ * Detects: language, framework, package manager, entry points, test framework.
5
+ * Uses file-based heuristics (package.json, go.mod, pyproject.toml, etc.).
6
+ */
7
+ import { existsSync, readFileSync } from 'node:fs';
8
+ import { join } from 'node:path';
9
+ import { findFiles, readFileIfExists } from '../common.js';
10
+ export function detectSoftware(path, base) {
11
+ const result = { ...base };
12
+ // ── Node.js / JavaScript / TypeScript ─────────────────────────────
13
+ const pkgJsonPath = join(path, 'package.json');
14
+ if (existsSync(pkgJsonPath)) {
15
+ try {
16
+ const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
17
+ const rawDeps = (pkg.dependencies ?? {});
18
+ const rawDevDeps = (pkg.devDependencies ?? {});
19
+ const deps = { ...rawDeps, ...rawDevDeps };
20
+ // Language
21
+ result.language =
22
+ deps.typescript || existsSync(join(path, 'tsconfig.json')) ? 'typescript' : 'javascript';
23
+ // Framework
24
+ if (deps.next)
25
+ result.framework = 'next';
26
+ else if (deps.react)
27
+ result.framework = deps.express ? 'react + express' : 'react';
28
+ else if (deps.vue)
29
+ result.framework = deps.express ? 'vue + express' : 'vue';
30
+ else if (deps.svelte || deps['@sveltejs/kit'])
31
+ result.framework = 'svelte';
32
+ else if (deps.express)
33
+ result.framework = 'express';
34
+ else if (deps.fastify)
35
+ result.framework = 'fastify';
36
+ else if (deps.hono)
37
+ result.framework = 'hono';
38
+ // Package manager
39
+ if (existsSync(join(path, 'pnpm-lock.yaml')))
40
+ result.packageManager = 'pnpm';
41
+ else if (existsSync(join(path, 'yarn.lock')))
42
+ result.packageManager = 'yarn';
43
+ else if (existsSync(join(path, 'bun.lockb')))
44
+ result.packageManager = 'bun';
45
+ else
46
+ result.packageManager = 'npm';
47
+ // Test framework
48
+ if (deps.vitest)
49
+ result.testFramework = 'vitest';
50
+ else if (deps.jest)
51
+ result.testFramework = 'jest';
52
+ else if (deps.mocha)
53
+ result.testFramework = 'mocha';
54
+ else if (deps['@playwright/test'])
55
+ result.testFramework = 'playwright';
56
+ // Entry points
57
+ const entries = [];
58
+ for (const candidate of [
59
+ 'src/index.ts',
60
+ 'src/index.js',
61
+ 'src/main.ts',
62
+ 'src/main.js',
63
+ 'src/app.ts',
64
+ 'src/app.js',
65
+ 'index.ts',
66
+ 'index.js',
67
+ ]) {
68
+ if (existsSync(join(path, candidate)))
69
+ entries.push(candidate);
70
+ }
71
+ if (entries.length > 0)
72
+ result.entryPoints = entries;
73
+ }
74
+ catch {
75
+ /* invalid package.json */
76
+ }
77
+ return result;
78
+ }
79
+ // ── Python ────────────────────────────────────────────────────────
80
+ const hasPyproject = existsSync(join(path, 'pyproject.toml'));
81
+ const hasRequirements = existsSync(join(path, 'requirements.txt'));
82
+ const hasSetupPy = existsSync(join(path, 'setup.py'));
83
+ if (hasPyproject || hasRequirements || hasSetupPy) {
84
+ result.language = 'python';
85
+ if (hasPyproject) {
86
+ const content = readFileIfExists(join(path, 'pyproject.toml'));
87
+ if (content) {
88
+ if (content.includes('poetry'))
89
+ result.packageManager = 'poetry';
90
+ else if (content.includes('pdm'))
91
+ result.packageManager = 'pdm';
92
+ else
93
+ result.packageManager = 'pip';
94
+ if (content.includes('fastapi'))
95
+ result.framework = 'fastapi';
96
+ else if (content.includes('django'))
97
+ result.framework = 'django';
98
+ else if (content.includes('flask'))
99
+ result.framework = 'flask';
100
+ if (content.includes('pytest'))
101
+ result.testFramework = 'pytest';
102
+ }
103
+ }
104
+ else {
105
+ result.packageManager = 'pip';
106
+ const reqs = readFileIfExists(join(path, 'requirements.txt'));
107
+ if (reqs) {
108
+ if (reqs.includes('fastapi'))
109
+ result.framework = 'fastapi';
110
+ else if (reqs.includes('django'))
111
+ result.framework = 'django';
112
+ else if (reqs.includes('flask'))
113
+ result.framework = 'flask';
114
+ if (reqs.includes('pytest'))
115
+ result.testFramework = 'pytest';
116
+ }
117
+ }
118
+ const entries = [];
119
+ for (const candidate of ['main.py', 'app.py', 'server.py', 'src/main.py', 'src/app.py']) {
120
+ if (existsSync(join(path, candidate)))
121
+ entries.push(candidate);
122
+ }
123
+ if (entries.length > 0)
124
+ result.entryPoints = entries;
125
+ return result;
126
+ }
127
+ // ── Go ────────────────────────────────────────────────────────────
128
+ if (existsSync(join(path, 'go.mod'))) {
129
+ result.language = 'go';
130
+ result.packageManager = 'go mod';
131
+ const goMod = readFileIfExists(join(path, 'go.mod'));
132
+ if (goMod) {
133
+ if (goMod.includes('github.com/gin-gonic/gin'))
134
+ result.framework = 'gin';
135
+ else if (goMod.includes('github.com/labstack/echo'))
136
+ result.framework = 'echo';
137
+ else if (goMod.includes('github.com/gofiber/fiber'))
138
+ result.framework = 'fiber';
139
+ }
140
+ const entries = [];
141
+ for (const candidate of ['main.go', 'cmd/main.go', 'cmd/server/main.go']) {
142
+ if (existsSync(join(path, candidate)))
143
+ entries.push(candidate);
144
+ }
145
+ if (entries.length > 0)
146
+ result.entryPoints = entries;
147
+ return result;
148
+ }
149
+ // ── Rust ──────────────────────────────────────────────────────────
150
+ if (existsSync(join(path, 'Cargo.toml'))) {
151
+ result.language = 'rust';
152
+ result.packageManager = 'cargo';
153
+ const cargo = readFileIfExists(join(path, 'Cargo.toml'));
154
+ if (cargo) {
155
+ if (cargo.includes('actix-web'))
156
+ result.framework = 'actix';
157
+ else if (cargo.includes('axum'))
158
+ result.framework = 'axum';
159
+ else if (cargo.includes('rocket'))
160
+ result.framework = 'rocket';
161
+ }
162
+ if (existsSync(join(path, 'src', 'main.rs')))
163
+ result.entryPoints = ['src/main.rs'];
164
+ else if (existsSync(join(path, 'src', 'lib.rs')))
165
+ result.entryPoints = ['src/lib.rs'];
166
+ return result;
167
+ }
168
+ // ── Java / Kotlin ─────────────────────────────────────────────────
169
+ if (existsSync(join(path, 'pom.xml'))) {
170
+ result.language = 'java';
171
+ result.packageManager = 'maven';
172
+ return result;
173
+ }
174
+ if (existsSync(join(path, 'build.gradle')) || existsSync(join(path, 'build.gradle.kts'))) {
175
+ result.language = existsSync(join(path, 'build.gradle.kts')) ? 'kotlin' : 'java';
176
+ result.packageManager = 'gradle';
177
+ return result;
178
+ }
179
+ // ── Fallback: check for common source files ───────────────────────
180
+ const sourceFiles = findFiles(path, (_name, ext) => ['.ts', '.js', '.py', '.go', '.rs', '.java', '.kt', '.rb', '.php', '.cs', '.swift'].includes(ext), 1);
181
+ if (sourceFiles.length > 0) {
182
+ const ext = sourceFiles[0].split('.').pop();
183
+ const langMap = {
184
+ ts: 'typescript',
185
+ js: 'javascript',
186
+ py: 'python',
187
+ go: 'go',
188
+ rs: 'rust',
189
+ java: 'java',
190
+ kt: 'kotlin',
191
+ rb: 'ruby',
192
+ php: 'php',
193
+ cs: 'csharp',
194
+ swift: 'swift',
195
+ };
196
+ result.language = ext ? langMap[ext] : undefined;
197
+ }
198
+ return result;
199
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Tech Docs Detection Strategy
3
+ *
4
+ * Detects: documentation frameworks (mkdocs, docusaurus, sphinx), docs folder.
5
+ */
6
+ import type { ContentSummary } from '../types.js';
7
+ export declare function detectTechDocs(path: string, base: ContentSummary): ContentSummary;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Tech Docs Detection Strategy
3
+ *
4
+ * Detects: documentation frameworks (mkdocs, docusaurus, sphinx), docs folder.
5
+ */
6
+ import { existsSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import { hasDirectory } from '../common.js';
9
+ export function detectTechDocs(path, base) {
10
+ const result = { ...base };
11
+ // MkDocs
12
+ if (existsSync(join(path, 'mkdocs.yml')) || existsSync(join(path, 'mkdocs.yaml'))) {
13
+ result.docsFramework = 'mkdocs';
14
+ result.docsConfigFile = existsSync(join(path, 'mkdocs.yml')) ? 'mkdocs.yml' : 'mkdocs.yaml';
15
+ return result;
16
+ }
17
+ // Docusaurus
18
+ if (existsSync(join(path, 'docusaurus.config.js')) ||
19
+ existsSync(join(path, 'docusaurus.config.ts'))) {
20
+ result.docsFramework = 'docusaurus';
21
+ result.docsConfigFile = existsSync(join(path, 'docusaurus.config.js'))
22
+ ? 'docusaurus.config.js'
23
+ : 'docusaurus.config.ts';
24
+ return result;
25
+ }
26
+ // Sphinx
27
+ if (existsSync(join(path, 'conf.py')) || existsSync(join(path, 'source', 'conf.py'))) {
28
+ result.docsFramework = 'sphinx';
29
+ result.docsConfigFile = existsSync(join(path, 'conf.py')) ? 'conf.py' : 'source/conf.py';
30
+ return result;
31
+ }
32
+ // VitePress
33
+ if (existsSync(join(path, '.vitepress', 'config.ts')) ||
34
+ existsSync(join(path, '.vitepress', 'config.js'))) {
35
+ result.docsFramework = 'vitepress';
36
+ result.docsConfigFile = '.vitepress/config.ts';
37
+ return result;
38
+ }
39
+ // GitBook
40
+ if (existsSync(join(path, 'SUMMARY.md'))) {
41
+ result.docsFramework = 'gitbook';
42
+ result.docsConfigFile = 'SUMMARY.md';
43
+ return result;
44
+ }
45
+ // Generic docs/ folder
46
+ if (hasDirectory(path, 'docs')) {
47
+ result.docsFramework = 'custom';
48
+ return result;
49
+ }
50
+ return result;
51
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Project Detection Types
3
+ *
4
+ * Universal detection result that works across all project types.
5
+ * Type-specific fields are optional — only populated when relevant.
6
+ */
7
+ export interface DetectProjectOptions {
8
+ /** Absolute path to the project folder */
9
+ path: string;
10
+ /** Project type — determines detection strategy */
11
+ type: string;
12
+ }
13
+ export interface DetectionResult {
14
+ /** Inferred project name (slug: lowercase, hyphens) */
15
+ name: string;
16
+ /** Display name (prettified from folder name or config) */
17
+ displayName: string;
18
+ /** Description (from README first paragraph, package.json, etc.) */
19
+ description?: string;
20
+ /** Git remote URL (if .git exists with a remote) */
21
+ gitRemote?: string;
22
+ /** Whether .git directory exists */
23
+ hasGit: boolean;
24
+ /** Whether COMPILR.md exists in the project root */
25
+ hasCompilrMd: boolean;
26
+ /** Type-specific detection results */
27
+ content: ContentSummary;
28
+ }
29
+ export interface ContentSummary {
30
+ /** Total files found (excluding ignored dirs) */
31
+ fileCount: number;
32
+ /** Total size in KB */
33
+ totalSizeKB: number;
34
+ /** Markdown files count */
35
+ markdownFiles: number;
36
+ /** Image files count */
37
+ imageFiles: number;
38
+ /** Detected language (typescript, python, go, rust, java, etc.) */
39
+ language?: string;
40
+ /** Detected framework (react, next, express, fastapi, gin, etc.) */
41
+ framework?: string;
42
+ /** Package manager (npm, yarn, pnpm, pip, poetry, go mod, cargo) */
43
+ packageManager?: string;
44
+ /** Main entry points found */
45
+ entryPoints?: string[];
46
+ /** Test framework detected */
47
+ testFramework?: string;
48
+ /** Path to .bib file */
49
+ bibliographyFile?: string;
50
+ /** Number of bib entries */
51
+ bibliographyEntries?: number;
52
+ /** LaTeX files found */
53
+ latexFiles?: string[];
54
+ /** Data files (.csv, .xlsx, .json in data/ or root) */
55
+ dataFiles?: number;
56
+ /** Number of chapter files detected */
57
+ chapterCount?: number;
58
+ /** Path to outline file */
59
+ outlineFile?: string;
60
+ /** Manuscript/draft files */
61
+ draftFiles?: string[];
62
+ /** Spreadsheet files (.xlsx, .csv) */
63
+ spreadsheetFiles?: number;
64
+ /** Presentation files (.pptx, .pdf) */
65
+ presentationFiles?: number;
66
+ /** Documentation framework (mkdocs, docusaurus, sphinx, etc.) */
67
+ docsFramework?: string;
68
+ /** Config file path */
69
+ docsConfigFile?: string;
70
+ /** Lesson/module directories */
71
+ lessonCount?: number;
72
+ /** Exercise files */
73
+ exerciseFiles?: number;
74
+ }
75
+ /**
76
+ * Detection strategy function signature.
77
+ * Each strategy receives the project path and a base summary,
78
+ * then adds type-specific fields.
79
+ */
80
+ export type DetectionStrategy = (path: string, base: ContentSummary) => ContentSummary;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Project Detection Types
3
+ *
4
+ * Universal detection result that works across all project types.
5
+ * Type-specific fields are optional — only populated when relevant.
6
+ */
7
+ export {};
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Guide Tool Factory — creates an environment-aware compilr_guide tool.
3
+ *
4
+ * The tool definition lives in the SDK. Each host app (CLI, Desktop)
5
+ * registers environment-specific content via additionalEntries.
6
+ */
7
+ import type { GuideToolConfig } from './types.js';
8
+ interface GuideInput {
9
+ query?: string;
10
+ list_topics?: boolean;
11
+ }
12
+ /**
13
+ * Create the compilr_guide tool for the given environment.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * // In CLI
18
+ * const guideTool = createGuideTool({
19
+ * environment: 'cli',
20
+ * additionalEntries: CLI_GUIDE_ENTRIES,
21
+ * });
22
+ *
23
+ * // In Desktop
24
+ * const guideTool = createGuideTool({
25
+ * environment: 'desktop',
26
+ * additionalEntries: DESKTOP_GUIDE_ENTRIES,
27
+ * });
28
+ * ```
29
+ */
30
+ export declare function createGuideTool(config: GuideToolConfig): import("@compilr-dev/agents").Tool<GuideInput>;
31
+ export {};
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Guide Tool Factory — creates an environment-aware compilr_guide tool.
3
+ *
4
+ * The tool definition lives in the SDK. Each host app (CLI, Desktop)
5
+ * registers environment-specific content via additionalEntries.
6
+ */
7
+ import { defineTool } from '@compilr-dev/agents';
8
+ import { SHARED_GUIDE_ENTRIES } from './shared-content.js';
9
+ import { searchGuideEntries } from './search.js';
10
+ /**
11
+ * Create the compilr_guide tool for the given environment.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * // In CLI
16
+ * const guideTool = createGuideTool({
17
+ * environment: 'cli',
18
+ * additionalEntries: CLI_GUIDE_ENTRIES,
19
+ * });
20
+ *
21
+ * // In Desktop
22
+ * const guideTool = createGuideTool({
23
+ * environment: 'desktop',
24
+ * additionalEntries: DESKTOP_GUIDE_ENTRIES,
25
+ * });
26
+ * ```
27
+ */
28
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
29
+ export function createGuideTool(config) {
30
+ // Merge shared + environment-specific content
31
+ const allEntries = [...SHARED_GUIDE_ENTRIES, ...(config.additionalEntries ?? [])];
32
+ const envLabel = config.environment === 'desktop' ? 'Desktop' : 'CLI';
33
+ return defineTool({
34
+ name: 'compilr_guide',
35
+ description: `Get documentation about Compilr Dev ${envLabel} features and capabilities. ` +
36
+ `Use this to answer user questions about how to use the ${envLabel.toLowerCase()}. ` +
37
+ 'Query by topic (e.g., "creating-project", "team-agents", "image-support") or ' +
38
+ 'set list_topics=true to see all available topics.',
39
+ inputSchema: {
40
+ type: 'object',
41
+ properties: {
42
+ query: {
43
+ type: 'string',
44
+ description: 'Topic or keyword to look up. Examples: "creating-project", "team-agents", "permissions", "image-support"',
45
+ },
46
+ list_topics: {
47
+ type: 'boolean',
48
+ description: 'Set to true to list all available guide topics',
49
+ },
50
+ },
51
+ },
52
+ execute: (input) => {
53
+ if (input.list_topics) {
54
+ const topics = allEntries.map((e) => `- ${e.id} — ${e.title}`).join('\n');
55
+ return Promise.resolve({
56
+ success: true,
57
+ result: {
58
+ output: `Available guide topics (${envLabel}):\n\n${topics}`,
59
+ },
60
+ });
61
+ }
62
+ if (!input.query) {
63
+ return Promise.resolve({
64
+ success: true,
65
+ result: {
66
+ output: 'Provide a query (topic or keyword) or set list_topics=true to see all topics.',
67
+ },
68
+ });
69
+ }
70
+ const results = searchGuideEntries(allEntries, input.query);
71
+ if (results.length === 0) {
72
+ const topics = allEntries.map((e) => e.id).join(', ');
73
+ return Promise.resolve({
74
+ success: true,
75
+ result: {
76
+ output: `No guide found for "${input.query}".\n\nAvailable topics: ${topics}`,
77
+ },
78
+ });
79
+ }
80
+ const formatted = results
81
+ .map((entry) => `## ${entry.title}\n\n${entry.content}`)
82
+ .join('\n\n---\n\n');
83
+ return Promise.resolve({
84
+ success: true,
85
+ result: { output: formatted },
86
+ });
87
+ },
88
+ silent: true,
89
+ });
90
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Guide module — environment-aware documentation tool.
3
+ */
4
+ export { createGuideTool } from './guide-tool.js';
5
+ export { SHARED_GUIDE_ENTRIES } from './shared-content.js';
6
+ export { searchGuideEntries, topicToGuideEntry } from './search.js';
7
+ export type { GuideEntry, ContentTopic, ContentSection, GuideToolConfig } from './types.js';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Guide module — environment-aware documentation tool.
3
+ */
4
+ export { createGuideTool } from './guide-tool.js';
5
+ export { SHARED_GUIDE_ENTRIES } from './shared-content.js';
6
+ export { searchGuideEntries, topicToGuideEntry } from './search.js';
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Guide search — finds relevant entries by query.
3
+ */
4
+ import type { GuideEntry } from './types.js';
5
+ /**
6
+ * Search guide entries by keyword, topic ID, or free text.
7
+ * Returns up to maxResults matches, ranked by relevance.
8
+ */
9
+ export declare function searchGuideEntries(entries: GuideEntry[], query: string, maxResults?: number): GuideEntry[];
10
+ /**
11
+ * Convert a ContentTopic to a GuideEntry (plain text).
12
+ */
13
+ export declare function topicToGuideEntry(topic: {
14
+ id: string;
15
+ title: string;
16
+ keywords: string[];
17
+ sections: Array<{
18
+ title?: string;
19
+ items: string[];
20
+ }>;
21
+ }): GuideEntry;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Guide search — finds relevant entries by query.
3
+ */
4
+ /**
5
+ * Search guide entries by keyword, topic ID, or free text.
6
+ * Returns up to maxResults matches, ranked by relevance.
7
+ */
8
+ export function searchGuideEntries(entries, query, maxResults = 3) {
9
+ const q = query.toLowerCase().trim();
10
+ if (!q)
11
+ return [];
12
+ // 1. Exact ID match
13
+ const exactMatch = entries.find((e) => e.id === q);
14
+ if (exactMatch)
15
+ return [exactMatch];
16
+ // 2. Command match (e.g., "/design" → "skill-design", or "design")
17
+ const cmdQuery = q.startsWith('/') ? q : `/${q}`;
18
+ const cmdMatch = entries.find((e) => e.keywords.includes(cmdQuery) || e.keywords.includes(q));
19
+ if (cmdMatch)
20
+ return [cmdMatch];
21
+ // 3. Keyword + title + content search with scoring
22
+ const scored = entries
23
+ .map((e) => {
24
+ let score = 0;
25
+ // Keyword match (strongest signal)
26
+ if (e.keywords.some((k) => k === q))
27
+ score += 10;
28
+ if (e.keywords.some((k) => k.includes(q)))
29
+ score += 5;
30
+ if (e.keywords.some((k) => q.includes(k) && k.length > 2))
31
+ score += 3;
32
+ // Title match
33
+ if (e.title.toLowerCase().includes(q))
34
+ score += 4;
35
+ // Content match (weakest signal)
36
+ if (e.content.toLowerCase().includes(q))
37
+ score += 1;
38
+ return { entry: e, score };
39
+ })
40
+ .filter((s) => s.score > 0)
41
+ .sort((a, b) => b.score - a.score);
42
+ return scored.slice(0, maxResults).map((s) => s.entry);
43
+ }
44
+ /**
45
+ * Convert a ContentTopic to a GuideEntry (plain text).
46
+ */
47
+ export function topicToGuideEntry(topic) {
48
+ const parts = [];
49
+ for (const section of topic.sections) {
50
+ if (section.title)
51
+ parts.push(section.title);
52
+ for (const item of section.items) {
53
+ parts.push(` ${item}`);
54
+ }
55
+ }
56
+ return {
57
+ id: topic.id,
58
+ title: topic.title,
59
+ keywords: topic.keywords,
60
+ content: parts.join('\n'),
61
+ };
62
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Shared Guide Content — environment-agnostic topics.
3
+ *
4
+ * These apply to both CLI and Desktop. Each entry describes a feature
5
+ * that works the same way regardless of the host application.
6
+ */
7
+ import type { GuideEntry } from './types.js';
8
+ export declare const SHARED_GUIDE_ENTRIES: GuideEntry[];