@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.
- 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/guide/guide-tool.d.ts +31 -0
- package/dist/guide/guide-tool.js +90 -0
- package/dist/guide/index.d.ts +7 -0
- package/dist/guide/index.js +6 -0
- package/dist/guide/search.d.ts +21 -0
- package/dist/guide/search.js +62 -0
- package/dist/guide/shared-content.d.ts +8 -0
- package/dist/guide/shared-content.js +267 -0
- package/dist/guide/types.d.ts +34 -0
- package/dist/guide/types.js +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +8 -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;
|