@compilr-dev/sdk 0.9.8 → 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.
- package/dist/detection/common.d.ts +24 -0
- package/dist/detection/common.js +247 -0
- package/dist/detection/index.d.ts +6 -0
- package/dist/detection/index.js +5 -0
- package/dist/detection/project-detector.d.ts +29 -0
- package/dist/detection/project-detector.js +106 -0
- package/dist/detection/strategies/book.d.ts +7 -0
- package/dist/detection/strategies/book.js +48 -0
- package/dist/detection/strategies/business.d.ts +7 -0
- package/dist/detection/strategies/business.js +22 -0
- package/dist/detection/strategies/content.d.ts +7 -0
- package/dist/detection/strategies/content.js +21 -0
- package/dist/detection/strategies/course.d.ts +7 -0
- package/dist/detection/strategies/course.js +30 -0
- package/dist/detection/strategies/general.d.ts +8 -0
- package/dist/detection/strategies/general.js +10 -0
- package/dist/detection/strategies/research.d.ts +7 -0
- package/dist/detection/strategies/research.js +44 -0
- package/dist/detection/strategies/software.d.ts +8 -0
- package/dist/detection/strategies/software.js +199 -0
- package/dist/detection/strategies/tech-docs.d.ts +7 -0
- package/dist/detection/strategies/tech-docs.js +51 -0
- package/dist/detection/types.d.ts +80 -0
- package/dist/detection/types.js +7 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/package.json +1 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common Detection Utilities
|
|
3
|
+
*
|
|
4
|
+
* Shared by all type-specific strategies: git detection, README parsing,
|
|
5
|
+
* file counting, folder name parsing.
|
|
6
|
+
*/
|
|
7
|
+
import type { DetectionResult } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Detect common project info (runs for ALL project types).
|
|
10
|
+
* Returns a base DetectionResult with name, git, README, file counts.
|
|
11
|
+
*/
|
|
12
|
+
export declare function detectCommon(projectPath: string): DetectionResult;
|
|
13
|
+
/**
|
|
14
|
+
* List files matching a pattern in a directory (non-recursive or single level).
|
|
15
|
+
*/
|
|
16
|
+
export declare function findFiles(dir: string, predicate: (name: string, ext: string) => boolean, maxDepth?: number): string[];
|
|
17
|
+
/**
|
|
18
|
+
* Check if a directory exists within the project.
|
|
19
|
+
*/
|
|
20
|
+
export declare function hasDirectory(projectPath: string, dirName: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Read a file's content if it exists, or return undefined.
|
|
23
|
+
*/
|
|
24
|
+
export declare function readFileIfExists(filePath: string): string | undefined;
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common Detection Utilities
|
|
3
|
+
*
|
|
4
|
+
* Shared by all type-specific strategies: git detection, README parsing,
|
|
5
|
+
* file counting, folder name parsing.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
8
|
+
import { join, basename, extname } from 'node:path';
|
|
9
|
+
/** Directories to skip when scanning */
|
|
10
|
+
const IGNORED_DIRS = new Set([
|
|
11
|
+
'node_modules',
|
|
12
|
+
'.git',
|
|
13
|
+
'__pycache__',
|
|
14
|
+
'.venv',
|
|
15
|
+
'venv',
|
|
16
|
+
'.tox',
|
|
17
|
+
'.mypy_cache',
|
|
18
|
+
'.pytest_cache',
|
|
19
|
+
'.next',
|
|
20
|
+
'.nuxt',
|
|
21
|
+
'dist',
|
|
22
|
+
'build',
|
|
23
|
+
'target',
|
|
24
|
+
'.gradle',
|
|
25
|
+
'.idea',
|
|
26
|
+
'.vscode',
|
|
27
|
+
'.DS_Store',
|
|
28
|
+
'vendor',
|
|
29
|
+
'.compilr',
|
|
30
|
+
'.compilr-dev',
|
|
31
|
+
]);
|
|
32
|
+
const IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg']);
|
|
33
|
+
const MARKDOWN_EXTENSIONS = new Set(['.md', '.markdown', '.mdx']);
|
|
34
|
+
/**
|
|
35
|
+
* Detect common project info (runs for ALL project types).
|
|
36
|
+
* Returns a base DetectionResult with name, git, README, file counts.
|
|
37
|
+
*/
|
|
38
|
+
export function detectCommon(projectPath) {
|
|
39
|
+
const folderName = basename(projectPath);
|
|
40
|
+
const name = slugify(folderName);
|
|
41
|
+
const displayName = prettify(folderName);
|
|
42
|
+
// Git detection
|
|
43
|
+
const hasGit = existsSync(join(projectPath, '.git'));
|
|
44
|
+
let gitRemote;
|
|
45
|
+
if (hasGit) {
|
|
46
|
+
gitRemote = readGitRemote(projectPath);
|
|
47
|
+
}
|
|
48
|
+
// COMPILR.md
|
|
49
|
+
const hasCompilrMd = existsSync(join(projectPath, 'COMPILR.md'));
|
|
50
|
+
// Description from README
|
|
51
|
+
const description = readDescription(projectPath);
|
|
52
|
+
// File counting
|
|
53
|
+
const content = countFiles(projectPath);
|
|
54
|
+
return {
|
|
55
|
+
name,
|
|
56
|
+
displayName,
|
|
57
|
+
description,
|
|
58
|
+
gitRemote,
|
|
59
|
+
hasGit,
|
|
60
|
+
hasCompilrMd,
|
|
61
|
+
content,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Read the git remote URL from .git/config.
|
|
66
|
+
*/
|
|
67
|
+
function readGitRemote(projectPath) {
|
|
68
|
+
const configPath = join(projectPath, '.git', 'config');
|
|
69
|
+
if (!existsSync(configPath))
|
|
70
|
+
return undefined;
|
|
71
|
+
try {
|
|
72
|
+
const config = readFileSync(configPath, 'utf-8');
|
|
73
|
+
const match = config.match(/\[remote "origin"\]\s*\n\s*url\s*=\s*(.+)/);
|
|
74
|
+
return match?.[1]?.trim();
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Read description from README.md or COMPILR.md (first non-heading paragraph).
|
|
82
|
+
*/
|
|
83
|
+
function readDescription(projectPath) {
|
|
84
|
+
const candidates = ['README.md', 'readme.md', 'COMPILR.md'];
|
|
85
|
+
for (const file of candidates) {
|
|
86
|
+
const filePath = join(projectPath, file);
|
|
87
|
+
if (!existsSync(filePath))
|
|
88
|
+
continue;
|
|
89
|
+
try {
|
|
90
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
91
|
+
const lines = content.split('\n');
|
|
92
|
+
// Find first non-empty, non-heading line
|
|
93
|
+
for (const line of lines) {
|
|
94
|
+
const trimmed = line.trim();
|
|
95
|
+
if (!trimmed)
|
|
96
|
+
continue;
|
|
97
|
+
if (trimmed.startsWith('#'))
|
|
98
|
+
continue;
|
|
99
|
+
if (trimmed.startsWith('!['))
|
|
100
|
+
continue; // badge images
|
|
101
|
+
if (trimmed.startsWith('<!--'))
|
|
102
|
+
continue;
|
|
103
|
+
// Found a content line — take up to 200 chars
|
|
104
|
+
return trimmed.length > 200 ? trimmed.slice(0, 200) + '...' : trimmed;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Count files in a project directory (non-recursive into ignored dirs).
|
|
115
|
+
*/
|
|
116
|
+
function countFiles(projectPath) {
|
|
117
|
+
let fileCount = 0;
|
|
118
|
+
let totalSizeBytes = 0;
|
|
119
|
+
let markdownFiles = 0;
|
|
120
|
+
let imageFiles = 0;
|
|
121
|
+
function walk(dir, depth) {
|
|
122
|
+
if (depth > 8)
|
|
123
|
+
return; // Don't go too deep
|
|
124
|
+
let entries;
|
|
125
|
+
try {
|
|
126
|
+
entries = readdirSync(dir);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
for (const entry of entries) {
|
|
132
|
+
if (IGNORED_DIRS.has(entry))
|
|
133
|
+
continue;
|
|
134
|
+
if (entry.startsWith('.') && entry !== '.git')
|
|
135
|
+
continue;
|
|
136
|
+
const fullPath = join(dir, entry);
|
|
137
|
+
let stat;
|
|
138
|
+
try {
|
|
139
|
+
stat = statSync(fullPath);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (stat.isDirectory()) {
|
|
145
|
+
walk(fullPath, depth + 1);
|
|
146
|
+
}
|
|
147
|
+
else if (stat.isFile()) {
|
|
148
|
+
fileCount++;
|
|
149
|
+
totalSizeBytes += stat.size;
|
|
150
|
+
const ext = extname(entry).toLowerCase();
|
|
151
|
+
if (MARKDOWN_EXTENSIONS.has(ext))
|
|
152
|
+
markdownFiles++;
|
|
153
|
+
if (IMAGE_EXTENSIONS.has(ext))
|
|
154
|
+
imageFiles++;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
walk(projectPath, 0);
|
|
159
|
+
return {
|
|
160
|
+
fileCount,
|
|
161
|
+
totalSizeKB: Math.round(totalSizeBytes / 1024),
|
|
162
|
+
markdownFiles,
|
|
163
|
+
imageFiles,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* List files matching a pattern in a directory (non-recursive or single level).
|
|
168
|
+
*/
|
|
169
|
+
export function findFiles(dir, predicate, maxDepth = 2) {
|
|
170
|
+
const results = [];
|
|
171
|
+
function walk(currentDir, depth) {
|
|
172
|
+
if (depth > maxDepth)
|
|
173
|
+
return;
|
|
174
|
+
let entries;
|
|
175
|
+
try {
|
|
176
|
+
entries = readdirSync(currentDir);
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
for (const entry of entries) {
|
|
182
|
+
if (IGNORED_DIRS.has(entry))
|
|
183
|
+
continue;
|
|
184
|
+
const fullPath = join(currentDir, entry);
|
|
185
|
+
let stat;
|
|
186
|
+
try {
|
|
187
|
+
stat = statSync(fullPath);
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (stat.isDirectory()) {
|
|
193
|
+
walk(fullPath, depth + 1);
|
|
194
|
+
}
|
|
195
|
+
else if (stat.isFile()) {
|
|
196
|
+
const ext = extname(entry).toLowerCase();
|
|
197
|
+
if (predicate(entry, ext)) {
|
|
198
|
+
// Return relative path from root dir
|
|
199
|
+
results.push(fullPath.slice(dir.length + 1));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
walk(dir, 0);
|
|
205
|
+
return results;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Check if a directory exists within the project.
|
|
209
|
+
*/
|
|
210
|
+
export function hasDirectory(projectPath, dirName) {
|
|
211
|
+
const dirPath = join(projectPath, dirName);
|
|
212
|
+
try {
|
|
213
|
+
return existsSync(dirPath) && statSync(dirPath).isDirectory();
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Read a file's content if it exists, or return undefined.
|
|
221
|
+
*/
|
|
222
|
+
export function readFileIfExists(filePath) {
|
|
223
|
+
try {
|
|
224
|
+
if (existsSync(filePath))
|
|
225
|
+
return readFileSync(filePath, 'utf-8');
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
/* ignore */
|
|
229
|
+
}
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Slugify a folder name for use as project name.
|
|
234
|
+
*/
|
|
235
|
+
function slugify(name) {
|
|
236
|
+
return name
|
|
237
|
+
.toLowerCase()
|
|
238
|
+
.replace(/[^a-z0-9-]/g, '-')
|
|
239
|
+
.replace(/-+/g, '-')
|
|
240
|
+
.replace(/^-|-$/g, '');
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Prettify a folder name for display.
|
|
244
|
+
*/
|
|
245
|
+
function prettify(name) {
|
|
246
|
+
return name.replace(/[-_]/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
|
247
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Detection — Universal project content detection.
|
|
3
|
+
*/
|
|
4
|
+
export { detectProject, suggestProjectType } from './project-detector.js';
|
|
5
|
+
export { detectCommon } from './common.js';
|
|
6
|
+
export type { DetectProjectOptions, DetectionResult, ContentSummary, DetectionStrategy, } from './types.js';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Detector — Universal project detection dispatcher.
|
|
3
|
+
*
|
|
4
|
+
* Runs common detection (git, README, file counts) for all types,
|
|
5
|
+
* then delegates to a type-specific strategy for additional scanning.
|
|
6
|
+
*/
|
|
7
|
+
import type { DetectProjectOptions, DetectionResult } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Detect project contents based on the specified type.
|
|
10
|
+
*
|
|
11
|
+
* 1. Runs common detection (git, README, file counts) for all types
|
|
12
|
+
* 2. Delegates to type-specific strategy for additional scanning
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* const result = await detectProject({
|
|
17
|
+
* path: '/Users/me/projects/my-app',
|
|
18
|
+
* type: 'web',
|
|
19
|
+
* });
|
|
20
|
+
* console.log(result.content.language); // 'typescript'
|
|
21
|
+
* console.log(result.content.framework); // 'react + express'
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare function detectProject(options: DetectProjectOptions): DetectionResult;
|
|
25
|
+
/**
|
|
26
|
+
* Quick check to suggest a project type based on folder contents.
|
|
27
|
+
* Returns the most likely type, or 'general' if uncertain.
|
|
28
|
+
*/
|
|
29
|
+
export declare function suggestProjectType(projectPath: string): string;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Detector — Universal project detection dispatcher.
|
|
3
|
+
*
|
|
4
|
+
* Runs common detection (git, README, file counts) for all types,
|
|
5
|
+
* then delegates to a type-specific strategy for additional scanning.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { detectCommon } from './common.js';
|
|
10
|
+
import { detectSoftware } from './strategies/software.js';
|
|
11
|
+
import { detectResearch } from './strategies/research.js';
|
|
12
|
+
import { detectBook } from './strategies/book.js';
|
|
13
|
+
import { detectBusiness } from './strategies/business.js';
|
|
14
|
+
import { detectContent } from './strategies/content.js';
|
|
15
|
+
import { detectTechDocs } from './strategies/tech-docs.js';
|
|
16
|
+
import { detectCourse } from './strategies/course.js';
|
|
17
|
+
import { detectGeneral } from './strategies/general.js';
|
|
18
|
+
/**
|
|
19
|
+
* Strategy map — maps project type IDs to detection functions.
|
|
20
|
+
* Software subtypes (web, cli, library, api) all use the software strategy.
|
|
21
|
+
*/
|
|
22
|
+
const STRATEGIES = {
|
|
23
|
+
// Software types
|
|
24
|
+
web: detectSoftware,
|
|
25
|
+
cli: detectSoftware,
|
|
26
|
+
library: detectSoftware,
|
|
27
|
+
api: detectSoftware,
|
|
28
|
+
// Non-software types
|
|
29
|
+
research: detectResearch,
|
|
30
|
+
book: detectBook,
|
|
31
|
+
'business-plan': detectBusiness,
|
|
32
|
+
content: detectContent,
|
|
33
|
+
'tech-docs': detectTechDocs,
|
|
34
|
+
course: detectCourse,
|
|
35
|
+
// Catch-all
|
|
36
|
+
general: detectGeneral,
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Detect project contents based on the specified type.
|
|
40
|
+
*
|
|
41
|
+
* 1. Runs common detection (git, README, file counts) for all types
|
|
42
|
+
* 2. Delegates to type-specific strategy for additional scanning
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* const result = await detectProject({
|
|
47
|
+
* path: '/Users/me/projects/my-app',
|
|
48
|
+
* type: 'web',
|
|
49
|
+
* });
|
|
50
|
+
* console.log(result.content.language); // 'typescript'
|
|
51
|
+
* console.log(result.content.framework); // 'react + express'
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export function detectProject(options) {
|
|
55
|
+
// Step 1: Common detection (runs for ALL types)
|
|
56
|
+
const base = detectCommon(options.path);
|
|
57
|
+
// Step 2: Type-specific strategy
|
|
58
|
+
const strategy = STRATEGIES[options.type] ?? detectGeneral;
|
|
59
|
+
const enrichedContent = strategy(options.path, base.content);
|
|
60
|
+
return {
|
|
61
|
+
...base,
|
|
62
|
+
content: enrichedContent,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Quick check to suggest a project type based on folder contents.
|
|
67
|
+
* Returns the most likely type, or 'general' if uncertain.
|
|
68
|
+
*/
|
|
69
|
+
export function suggestProjectType(projectPath) {
|
|
70
|
+
// Software indicators (strongest signals)
|
|
71
|
+
if (existsSync(join(projectPath, 'package.json')))
|
|
72
|
+
return 'web';
|
|
73
|
+
if (existsSync(join(projectPath, 'go.mod')))
|
|
74
|
+
return 'web';
|
|
75
|
+
if (existsSync(join(projectPath, 'Cargo.toml')))
|
|
76
|
+
return 'web';
|
|
77
|
+
if (existsSync(join(projectPath, 'pyproject.toml')) ||
|
|
78
|
+
existsSync(join(projectPath, 'requirements.txt'))) {
|
|
79
|
+
// Python could be software or research — check for research indicators
|
|
80
|
+
if (existsSync(join(projectPath, 'references.bib')) ||
|
|
81
|
+
existsSync(join(projectPath, 'main.tex'))) {
|
|
82
|
+
return 'research';
|
|
83
|
+
}
|
|
84
|
+
return 'web';
|
|
85
|
+
}
|
|
86
|
+
if (existsSync(join(projectPath, 'pom.xml')) || existsSync(join(projectPath, 'build.gradle')))
|
|
87
|
+
return 'web';
|
|
88
|
+
// Research indicators
|
|
89
|
+
try {
|
|
90
|
+
const entries = readdirSync(projectPath);
|
|
91
|
+
if (entries.some((f) => f.endsWith('.bib')))
|
|
92
|
+
return 'research';
|
|
93
|
+
if (entries.includes('main.tex'))
|
|
94
|
+
return 'research';
|
|
95
|
+
// Tech docs indicators
|
|
96
|
+
if (entries.includes('mkdocs.yml') || entries.includes('docusaurus.config.js'))
|
|
97
|
+
return 'tech-docs';
|
|
98
|
+
// Book indicators
|
|
99
|
+
if (entries.some((f) => /^(chapter|ch)[-_]?\d/i.test(f)))
|
|
100
|
+
return 'book';
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
/* unreadable directory */
|
|
104
|
+
}
|
|
105
|
+
return 'general';
|
|
106
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Book Detection Strategy
|
|
3
|
+
*
|
|
4
|
+
* Detects: chapter files, outline, manuscript/draft folders.
|
|
5
|
+
*/
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { findFiles, hasDirectory } from '../common.js';
|
|
8
|
+
export function detectBook(path, base) {
|
|
9
|
+
const result = { ...base };
|
|
10
|
+
// Chapter files (chapter-1.md, ch01.md, chapters/01-intro.md, etc.)
|
|
11
|
+
const chapterPattern = /^(chapter|ch)[-_]?\d/i;
|
|
12
|
+
const chapterFiles = findFiles(path, (name) => chapterPattern.test(name), 2);
|
|
13
|
+
// Also check chapters/ directory
|
|
14
|
+
if (hasDirectory(path, 'chapters')) {
|
|
15
|
+
const inDir = findFiles(join(path, 'chapters'), (_name, ext) => ext === '.md' || ext === '.txt', 1);
|
|
16
|
+
chapterFiles.push(...inDir.map((f) => `chapters/${f}`));
|
|
17
|
+
}
|
|
18
|
+
if (chapterFiles.length > 0) {
|
|
19
|
+
result.chapterCount = chapterFiles.length;
|
|
20
|
+
}
|
|
21
|
+
// Outline file
|
|
22
|
+
const outlineCandidates = [
|
|
23
|
+
'outline.md',
|
|
24
|
+
'outline.txt',
|
|
25
|
+
'OUTLINE.md',
|
|
26
|
+
'toc.md',
|
|
27
|
+
'table-of-contents.md',
|
|
28
|
+
];
|
|
29
|
+
for (const candidate of outlineCandidates) {
|
|
30
|
+
const found = findFiles(path, (name) => name.toLowerCase() === candidate.toLowerCase(), 0);
|
|
31
|
+
if (found.length > 0) {
|
|
32
|
+
result.outlineFile = found[0];
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Draft/manuscript files
|
|
37
|
+
const draftDirs = ['manuscript', 'drafts', 'draft', 'content'];
|
|
38
|
+
for (const dir of draftDirs) {
|
|
39
|
+
if (hasDirectory(path, dir)) {
|
|
40
|
+
const drafts = findFiles(join(path, dir), (_name, ext) => ext === '.md' || ext === '.txt', 1);
|
|
41
|
+
if (drafts.length > 0) {
|
|
42
|
+
result.draftFiles = drafts.map((f) => `${dir}/${f}`);
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Business Plan Detection Strategy
|
|
3
|
+
*
|
|
4
|
+
* Detects: spreadsheets, presentations, analysis folders.
|
|
5
|
+
*/
|
|
6
|
+
import { findFiles } from '../common.js';
|
|
7
|
+
export function detectBusiness(path, base) {
|
|
8
|
+
const result = { ...base };
|
|
9
|
+
// Spreadsheet files
|
|
10
|
+
const spreadsheetExts = new Set(['.xlsx', '.xls', '.csv', '.tsv', '.ods']);
|
|
11
|
+
const spreadsheets = findFiles(path, (_name, ext) => spreadsheetExts.has(ext), 2);
|
|
12
|
+
if (spreadsheets.length > 0) {
|
|
13
|
+
result.spreadsheetFiles = spreadsheets.length;
|
|
14
|
+
}
|
|
15
|
+
// Presentation files
|
|
16
|
+
const presentationExts = new Set(['.pptx', '.ppt', '.pdf', '.key']);
|
|
17
|
+
const presentations = findFiles(path, (_name, ext) => presentationExts.has(ext), 2);
|
|
18
|
+
if (presentations.length > 0) {
|
|
19
|
+
result.presentationFiles = presentations.length;
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content & Marketing Detection Strategy
|
|
3
|
+
*
|
|
4
|
+
* Detects: article folders, editorial calendar, media assets.
|
|
5
|
+
*/
|
|
6
|
+
import { findFiles, hasDirectory } from '../common.js';
|
|
7
|
+
export function detectContent(path, base) {
|
|
8
|
+
const result = { ...base };
|
|
9
|
+
// Draft files in common content directories
|
|
10
|
+
const contentDirs = ['articles', 'posts', 'content', 'blog', 'drafts'];
|
|
11
|
+
for (const dir of contentDirs) {
|
|
12
|
+
if (hasDirectory(path, dir)) {
|
|
13
|
+
const drafts = findFiles(`${path}/${dir}`, (_name, ext) => ext === '.md' || ext === '.txt', 1);
|
|
14
|
+
if (drafts.length > 0) {
|
|
15
|
+
result.draftFiles = drafts.map((f) => `${dir}/${f}`);
|
|
16
|
+
break;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Course / Training Detection Strategy
|
|
3
|
+
*
|
|
4
|
+
* Detects: lesson/module directories, exercise files, syllabus.
|
|
5
|
+
*/
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { findFiles, hasDirectory } from '../common.js';
|
|
8
|
+
export function detectCourse(path, base) {
|
|
9
|
+
const result = { ...base };
|
|
10
|
+
// Lesson/module directories
|
|
11
|
+
const lessonDirs = ['modules', 'lessons', 'units', 'weeks', 'sessions'];
|
|
12
|
+
for (const dir of lessonDirs) {
|
|
13
|
+
if (hasDirectory(path, dir)) {
|
|
14
|
+
// Count subdirectories as lessons
|
|
15
|
+
const items = findFiles(join(path, dir), () => true, 0);
|
|
16
|
+
result.lessonCount = items.length;
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
// Exercise files
|
|
21
|
+
const exerciseDirs = ['exercises', 'homework', 'labs', 'assignments'];
|
|
22
|
+
for (const dir of exerciseDirs) {
|
|
23
|
+
if (hasDirectory(path, dir)) {
|
|
24
|
+
const files = findFiles(join(path, dir), () => true, 1);
|
|
25
|
+
result.exerciseFiles = files.length;
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* General Detection Strategy
|
|
3
|
+
*
|
|
4
|
+
* Minimal scan — just the common detection (git, README, file counts).
|
|
5
|
+
* No type-specific additions.
|
|
6
|
+
*/
|
|
7
|
+
import type { ContentSummary } from '../types.js';
|
|
8
|
+
export declare function detectGeneral(_path: string, base: ContentSummary): ContentSummary;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* General Detection Strategy
|
|
3
|
+
*
|
|
4
|
+
* Minimal scan — just the common detection (git, README, file counts).
|
|
5
|
+
* No type-specific additions.
|
|
6
|
+
*/
|
|
7
|
+
export function detectGeneral(_path, base) {
|
|
8
|
+
// Common detection already covers everything for general projects
|
|
9
|
+
return { ...base };
|
|
10
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Research Detection Strategy
|
|
3
|
+
*
|
|
4
|
+
* Detects: bibliography files, LaTeX, data files, draft folders.
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { findFiles, hasDirectory } from '../common.js';
|
|
9
|
+
export function detectResearch(path, base) {
|
|
10
|
+
const result = { ...base };
|
|
11
|
+
// Bibliography (.bib files)
|
|
12
|
+
const bibFiles = findFiles(path, (_name, ext) => ext === '.bib', 1);
|
|
13
|
+
if (bibFiles.length > 0) {
|
|
14
|
+
result.bibliographyFile = bibFiles[0];
|
|
15
|
+
try {
|
|
16
|
+
const content = readFileSync(join(path, bibFiles[0]), 'utf-8');
|
|
17
|
+
result.bibliographyEntries = (content.match(/@\w+\{/g) ?? []).length;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
/* ignore */
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// LaTeX files
|
|
24
|
+
const texFiles = findFiles(path, (_name, ext) => ext === '.tex', 2);
|
|
25
|
+
if (texFiles.length > 0) {
|
|
26
|
+
result.latexFiles = texFiles;
|
|
27
|
+
}
|
|
28
|
+
// Data files (.csv, .xlsx, .json in data/ or root)
|
|
29
|
+
const dataExtensions = new Set(['.csv', '.xlsx', '.xls', '.json', '.tsv']);
|
|
30
|
+
const dataFiles = findFiles(path, (_name, ext) => dataExtensions.has(ext), 2);
|
|
31
|
+
if (dataFiles.length > 0) {
|
|
32
|
+
result.dataFiles = dataFiles.length;
|
|
33
|
+
}
|
|
34
|
+
// Check for common research folders
|
|
35
|
+
if (hasDirectory(path, 'drafts') || hasDirectory(path, 'papers')) {
|
|
36
|
+
// Count draft markdown files
|
|
37
|
+
const draftDir = hasDirectory(path, 'drafts') ? 'drafts' : 'papers';
|
|
38
|
+
const drafts = findFiles(join(path, draftDir), (_name, ext) => ext === '.md' || ext === '.tex', 1);
|
|
39
|
+
if (drafts.length > 0) {
|
|
40
|
+
result.draftFiles = drafts.map((f) => `${draftDir}/${f}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
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 type { ContentSummary } from '../types.js';
|
|
8
|
+
export declare function detectSoftware(path: string, base: ContentSummary): ContentSummary;
|
|
@@ -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;
|
package/dist/index.d.ts
CHANGED
|
@@ -58,6 +58,8 @@ export { createSQLiteRepositories, SQLiteProjectRepository, SQLiteWorkItemReposi
|
|
|
58
58
|
export type { SQLiteRepositories, CreateSQLiteRepositoriesOptions, ProjectDeleteHooks, ProjectRecord, WorkItemRecord, ProjectDocumentRecord, WorkItemCommentRecord, } from './platform/index.js';
|
|
59
59
|
export { createAskUserTool, createAskUserSimpleTool } from './tools/index.js';
|
|
60
60
|
export type { AskUserQuestion, AskUserInput, AskUserResult, AskUserHandler, AskUserSimpleInput, AskUserSimpleResult, AskUserSimpleHandler, } from './tools/index.js';
|
|
61
|
+
export { detectProject, suggestProjectType, detectCommon } from './detection/index.js';
|
|
62
|
+
export type { DetectProjectOptions, DetectionResult, ContentSummary } from './detection/index.js';
|
|
61
63
|
export { createGuideTool, SHARED_GUIDE_ENTRIES, searchGuideEntries, topicToGuideEntry, } from './guide/index.js';
|
|
62
64
|
export type { GuideEntry, ContentTopic, ContentSection, GuideToolConfig } from './guide/index.js';
|
|
63
65
|
export { createPlatformTools, createProjectTools, createWorkItemTools, createDocumentTools, createPlanTools, createBacklogTools, createAnchorTools, createArtifactTools, createEpisodeTools, createImageTools, ProjectAnchorStore, } from './platform/index.js';
|
package/dist/index.js
CHANGED
|
@@ -129,6 +129,10 @@ export { createSQLiteRepositories, SQLiteProjectRepository, SQLiteWorkItemReposi
|
|
|
129
129
|
// =============================================================================
|
|
130
130
|
export { createAskUserTool, createAskUserSimpleTool } from './tools/index.js';
|
|
131
131
|
// =============================================================================
|
|
132
|
+
// Project Detection (universal project content detection)
|
|
133
|
+
// =============================================================================
|
|
134
|
+
export { detectProject, suggestProjectType, detectCommon } from './detection/index.js';
|
|
135
|
+
// =============================================================================
|
|
132
136
|
// Guide Tool (environment-aware documentation)
|
|
133
137
|
// =============================================================================
|
|
134
138
|
export { createGuideTool, SHARED_GUIDE_ENTRIES, searchGuideEntries, topicToGuideEntry, } from './guide/index.js';
|