sigmap 3.4.0 → 3.5.1
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/AGENTS.md +610 -17
- package/CHANGELOG.md +35 -0
- package/README.md +18 -14
- package/gen-context.js +561 -8
- package/package.json +2 -1
- package/packages/adapters/llm-full.js +25 -0
- package/packages/cli/package.json +1 -1
- package/packages/core/index.js +10 -0
- package/packages/core/package.json +1 -1
- package/src/eval/analyzer.js +3 -0
- package/src/extractors/generic.js +26 -0
- package/src/extractors/patterns.js +135 -0
- package/src/extractors/python_dataclass.js +77 -0
- package/src/extractors/typescript_react.js +60 -0
- package/src/extractors/vue_sfc.js +99 -0
- package/src/format/llm-txt.js +28 -0
- package/src/format/llms-txt.js +70 -0
- package/src/mcp/server.js +1 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
module.exports = { name: 'llm-full', format, outputPath, write };
|
|
5
|
+
|
|
6
|
+
function outputPath(cwd) { return path.join(cwd, 'llm-full.txt'); }
|
|
7
|
+
|
|
8
|
+
function format(context, opts) {
|
|
9
|
+
opts = opts || {};
|
|
10
|
+
const lines = [
|
|
11
|
+
`# ${context.projectName || 'Project'} — SigMap Context`,
|
|
12
|
+
`Generated: ${new Date().toISOString()} | SigMap v${opts.version || ''}`,
|
|
13
|
+
'',
|
|
14
|
+
];
|
|
15
|
+
for (const entry of (context.fileEntries || [])) {
|
|
16
|
+
const rel = path.relative(opts.cwd || '', entry.filePath);
|
|
17
|
+
lines.push(`## ${rel}`, '```', ...(entry.sigs || []), '```', '');
|
|
18
|
+
}
|
|
19
|
+
return lines.join('\n');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function write(context, cwd, opts) {
|
|
23
|
+
opts = opts || {};
|
|
24
|
+
fs.writeFileSync(outputPath(cwd), format(context, { ...opts, cwd }));
|
|
25
|
+
}
|
package/packages/core/index.js
CHANGED
|
@@ -45,6 +45,16 @@ const EXT_MAP = {
|
|
|
45
45
|
'.properties': 'properties',
|
|
46
46
|
'.xml': 'xml',
|
|
47
47
|
'.md': 'markdown',
|
|
48
|
+
// Phase C specialized extractors
|
|
49
|
+
'.tsx': 'typescript_react',
|
|
50
|
+
'.vue': 'vue_sfc',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Phase C fallback: also try specialized extractors for base extensions
|
|
54
|
+
const PHASE_C_EXTRACTORS = {
|
|
55
|
+
'typescript_react': '.tsx',
|
|
56
|
+
'vue_sfc': '.vue',
|
|
57
|
+
'python_dataclass': '.py',
|
|
48
58
|
};
|
|
49
59
|
|
|
50
60
|
const SRC_ROOT = path.resolve(__dirname, '..', '..', 'src');
|
package/src/eval/analyzer.js
CHANGED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
module.exports = { extract };
|
|
3
|
+
|
|
4
|
+
const PATTERNS = [
|
|
5
|
+
/^(?:pub\s+)?(?:async\s+)?function\s+\w+\s*\(/,
|
|
6
|
+
/^(?:pub\s+)?(?:async\s+)?fn\s+\w+[\s(<]/,
|
|
7
|
+
/^def\s+\w+[\s(|:]/,
|
|
8
|
+
/^(?:pub\s+)?func\s+\w+\s*\(/,
|
|
9
|
+
/^(?:let|let\s+rec)\s+\w+\s*[=(]/,
|
|
10
|
+
/^class\s+\w+/,
|
|
11
|
+
/^(?:proc|sub|method)\s+\w+\s*\(/,
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
function extract(src) {
|
|
15
|
+
if (!src || typeof src !== 'string') return [];
|
|
16
|
+
const results = [];
|
|
17
|
+
for (const raw of src.split('\n')) {
|
|
18
|
+
const line = raw.trim();
|
|
19
|
+
if (!line || /^[#\-]/.test(line) || /^\/\//.test(line) || line.includes('\0')) continue;
|
|
20
|
+
for (const pat of PATTERNS) {
|
|
21
|
+
if (pat.test(line)) { results.push(line.slice(0, 120)); break; }
|
|
22
|
+
}
|
|
23
|
+
if (results.length >= 15) break;
|
|
24
|
+
}
|
|
25
|
+
return results;
|
|
26
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Phase D: Cross-module pattern inference and architectural detection.
|
|
5
|
+
* Identifies DI patterns, service/repo layers, circular deps, type linkage, and unsafe patterns.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} src - Raw source code
|
|
8
|
+
* @returns {string[]} Array of pattern signatures
|
|
9
|
+
*/
|
|
10
|
+
function extract(src) {
|
|
11
|
+
if (!src || typeof src !== 'string') return [];
|
|
12
|
+
const sigs = [];
|
|
13
|
+
|
|
14
|
+
// ────────────────────────────────────────────────────────────────
|
|
15
|
+
// Dependency Injection Pattern Detection
|
|
16
|
+
// ────────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
// Service container / factory patterns
|
|
19
|
+
const containerRe = /(?:class|function|const)\s+([A-Z]\w*(?:Container|Factory|Registry|Provider|Injector))\b/g;
|
|
20
|
+
for (const m of src.matchAll(containerRe)) {
|
|
21
|
+
sigs.push(`di-container ${m[1]}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Service decorators: @Injectable, @Service, @Singleton, @Provide
|
|
25
|
+
const decoratorRe = /@(?:Injectable|Service|Singleton|Provide|Module|Component)\s*(?:\([^)]*\))?\s*(?:class|export\s+class|const\s+)\s+([A-Z]\w*)/g;
|
|
26
|
+
for (const m of src.matchAll(decoratorRe)) {
|
|
27
|
+
sigs.push(`service-decorated ${m[1]}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Dependency injection via constructor: constructor(private readonly ...: Service)
|
|
31
|
+
const ctorDiRe = /constructor\s*\([^)]*(?:private|protected)?\s+(?:readonly\s+)?([a-z_]\w*)\s*:\s*([A-Z]\w*)/g;
|
|
32
|
+
const diServices = new Set();
|
|
33
|
+
for (const m of src.matchAll(ctorDiRe)) {
|
|
34
|
+
diServices.add(m[1]);
|
|
35
|
+
}
|
|
36
|
+
if (diServices.size > 0) {
|
|
37
|
+
sigs.push(`di-injection ${diServices.size} params`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ────────────────────────────────────────────────────────────────
|
|
41
|
+
// Service/Repository/Middleware Layer Detection
|
|
42
|
+
// ────────────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
// Repository pattern: extends Repository, implements IRepository
|
|
45
|
+
const repoRe = /class\s+([A-Z]\w*(?:Repository|Repo|DataAccess))\b/g;
|
|
46
|
+
for (const m of src.matchAll(repoRe)) {
|
|
47
|
+
sigs.push(`repo ${m[1]}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Service layer: @Service or ServiceImpl pattern
|
|
51
|
+
const serviceRe = /(?:export\s+)?class\s+([A-Z]\w*Service\b)/g;
|
|
52
|
+
for (const m of src.matchAll(serviceRe)) {
|
|
53
|
+
sigs.push(`service ${m[1]}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Middleware detection: app.use(), router.use(), middleware function
|
|
57
|
+
if (/app\.use\s*\(|router\.use\s*\(|\.use\s*\(\s*function|middleware|app\.get\s*\(\s*['"`]\/[^'"`]*['"`]/.test(src)) {
|
|
58
|
+
sigs.push('middleware-present');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ────────────────────────────────────────────────────────────────
|
|
62
|
+
// Type Linkage: Exported types → Implementations
|
|
63
|
+
// ────────────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
// Export type/interface followed by class implementing it
|
|
66
|
+
const exportTypeRe = /export\s+(?:type|interface)\s+([A-Z]\w*)/g;
|
|
67
|
+
const exportedTypes = new Set();
|
|
68
|
+
for (const m of src.matchAll(exportTypeRe)) {
|
|
69
|
+
exportedTypes.add(m[1]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Check if exported types have implementations
|
|
73
|
+
for (const type of exportedTypes) {
|
|
74
|
+
const implRe = new RegExp(`class\\s+([A-Z]\\w*)\\s+(?:extends|implements)\\s+(?:.*\\s+)?${type}\\b`, 'i');
|
|
75
|
+
if (implRe.test(src)) {
|
|
76
|
+
sigs.push(`type-impl ${type}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ────────────────────────────────────────────────────────────────
|
|
81
|
+
// Unsafe Pattern Detection
|
|
82
|
+
// ────────────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
// Unchecked nulls / optional without validation
|
|
85
|
+
if (/\?\s*{|Optional\s*<|\?\s*\.get\(\)|\?\s*\[|\?\s*\.length|\.split\(\)\.filter\(Boolean\)|if\s*\(\s*!.*\).*throw/.test(src)) {
|
|
86
|
+
sigs.push('unsafe-null-check');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Missing validation (direct use of user input)
|
|
90
|
+
const userInputRe = /(?:request|input|params|body|query|args)\s*\[\s*['"][^'"]*['"]\s*\]|req(?:uest)?\..*\s*==|params\.split|String\(.*\)\.toLowerCase/;
|
|
91
|
+
if (userInputRe.test(src)) {
|
|
92
|
+
sigs.push('unsafe-input-validation');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Weak error handling (catch {} or empty catch)
|
|
96
|
+
if (/catch\s*\(\s*\)\s*\{|\}\s*catch\s*\{(\s*\/\/|\s*\})/.test(src)) {
|
|
97
|
+
sigs.push('weak-error-handling');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Direct error exposure
|
|
101
|
+
if (/throw\s+new\s+Error\(|console\s*\.\s*error|res\.status\(500\)\.send\(err\)/.test(src)) {
|
|
102
|
+
sigs.push('unsafe-error-exposure');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ────────────────────────────────────────────────────────────────
|
|
106
|
+
// Circular Dependency Hints
|
|
107
|
+
// ────────────────────────────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
// Mutual imports detected (A imports B, B imports A) — can't directly detect in single file,
|
|
110
|
+
// but we can flag suspicious patterns
|
|
111
|
+
const importRe = /(?:import|require)\s+(?:{[^}]*}|[a-zA-Z_]\w*)\s+from\s+['"]\.?\.?\/[^'"]+['"]/g;
|
|
112
|
+
const importCount = (src.match(importRe) || []).length;
|
|
113
|
+
if (importCount > 5) {
|
|
114
|
+
sigs.push(`heavy-imports ${importCount}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ────────────────────────────────────────────────────────────────
|
|
118
|
+
// Layer/Module Organization Hints
|
|
119
|
+
// ────────────────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
// Controller/Handler layer
|
|
122
|
+
const controllerRe = /(?:class|export)\s+([A-Z]\w*(?:Controller|Handler|Route))\b/g;
|
|
123
|
+
for (const m of src.matchAll(controllerRe)) {
|
|
124
|
+
sigs.push(`controller ${m[1]}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Use case / Domain logic
|
|
128
|
+
if (/UseCase|Command|Query|UseCase\b|Interactor/.test(src)) {
|
|
129
|
+
sigs.push('domain-usecase');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return Array.from(new Set(sigs)).slice(0, 60);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
module.exports = { extract };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extract Python dataclass, Pydantic model, and SQLAlchemy ORM metadata.
|
|
5
|
+
* Focuses on model fields, validation, and relationships.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} src - Raw Python content
|
|
8
|
+
* @returns {string[]} Array of signature strings
|
|
9
|
+
*/
|
|
10
|
+
function extract(src) {
|
|
11
|
+
if (!src || typeof src !== 'string') return [];
|
|
12
|
+
const sigs = [];
|
|
13
|
+
|
|
14
|
+
// Dataclass definitions
|
|
15
|
+
const dataclassRe = /@dataclass(?:\([^)]*\))?[\s\n]+class\s+([A-Z]\w*)/g;
|
|
16
|
+
for (const m of src.matchAll(dataclassRe)) {
|
|
17
|
+
sigs.push(`dataclass ${m[1]}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Pydantic BaseModel classes
|
|
21
|
+
const pydanticRe = /class\s+([A-Z]\w*)\s*\([^)]*BaseModel[^)]*\)/g;
|
|
22
|
+
for (const m of src.matchAll(pydanticRe)) {
|
|
23
|
+
sigs.push(`model ${m[1]}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Pydantic v2 model_validate / field definitions
|
|
27
|
+
if (/model_validate|field_validator|computed_field/.test(src)) {
|
|
28
|
+
sigs.push('pydantic v2+');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// SQLAlchemy model classes
|
|
32
|
+
const sqlalchemyRe = /class\s+([A-Z]\w*)\s*\([^)]*(?:Base|declarative_base)[^)]*\)/g;
|
|
33
|
+
for (const m of src.matchAll(sqlalchemyRe)) {
|
|
34
|
+
sigs.push(`orm ${m[1]}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Model fields with type hints (for dataclass/Pydantic)
|
|
38
|
+
const fieldRe = /^\s+([a-z_]\w*)\s*:\s*([A-Z]\w*|List|Dict|Optional|Union)[^=]*/gm;
|
|
39
|
+
const fields = new Set();
|
|
40
|
+
for (const m of src.matchAll(fieldRe)) {
|
|
41
|
+
if (fields.size < 15) fields.add(m[1]);
|
|
42
|
+
}
|
|
43
|
+
for (const f of fields) {
|
|
44
|
+
sigs.push(`field ${f}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// SQLAlchemy Column definitions
|
|
48
|
+
const columnRe = /([a-z_]\w*)\s*=\s*Column\s*\([^)]*\)/g;
|
|
49
|
+
for (const m of src.matchAll(columnRe)) {
|
|
50
|
+
sigs.push(`column ${m[1]}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Relationships (SQLAlchemy ForeignKey, relationship)
|
|
54
|
+
const relRe = /(?:ForeignKey|relationship)\s*\(\s*['"]([a-zA-Z_]\w*)['"]/g;
|
|
55
|
+
for (const m of src.matchAll(relRe)) {
|
|
56
|
+
sigs.push(`relation ${m[1]}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Validators (@validator, @field_validator)
|
|
60
|
+
const validatorRe = /@(?:validator|field_validator)\s*\(\s*['"]?([a-z_]\w*)(?:['"]|,|\s|\))/g;
|
|
61
|
+
const validators = new Set();
|
|
62
|
+
for (const m of src.matchAll(validatorRe)) {
|
|
63
|
+
validators.add(m[1]);
|
|
64
|
+
}
|
|
65
|
+
for (const v of validators) {
|
|
66
|
+
sigs.push(`validator ${v}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Config class (Pydantic v1 / SQLAlchemy)
|
|
70
|
+
if (/class\s+Config\s*:/.test(src)) {
|
|
71
|
+
sigs.push('config-class');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return Array.from(new Set(sigs)).slice(0, 50);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = { extract };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extract React component signatures from .tsx files.
|
|
5
|
+
* Captures component props interfaces, hooks usage, and exports.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} src - Raw TypeScript/TSX content
|
|
8
|
+
* @returns {string[]} Array of signature strings
|
|
9
|
+
*/
|
|
10
|
+
function extract(src) {
|
|
11
|
+
if (!src || typeof src !== 'string') return [];
|
|
12
|
+
const sigs = [];
|
|
13
|
+
|
|
14
|
+
// Remove comments to simplify matching
|
|
15
|
+
const stripped = src
|
|
16
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
17
|
+
.replace(/\/\/.*$/gm, '');
|
|
18
|
+
|
|
19
|
+
// Component function declarations
|
|
20
|
+
const compRe = /(?:export\s+)?(?:const|function)\s+([A-Z]\w*)\s*(?:<[^>]*>)?\s*\(\s*(?:props|{\s*[^}]*})?/g;
|
|
21
|
+
for (const m of stripped.matchAll(compRe)) {
|
|
22
|
+
sigs.push(`component ${m[1]}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Props interfaces: interface SomeProps { ... }
|
|
26
|
+
const propsRe = /interface\s+(\w*Props)\s*(?:<[^>]*>)?\s*{/g;
|
|
27
|
+
for (const m of stripped.matchAll(propsRe)) {
|
|
28
|
+
sigs.push(`props ${m[1]}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// React hooks (useState, useEffect, useContext, useCallback, useMemo, useReducer)
|
|
32
|
+
const hookRe = /use([A-Z]\w*)\s*(?:<[^>]*>)?\s*\(/g;
|
|
33
|
+
const hooks = new Set();
|
|
34
|
+
for (const m of stripped.matchAll(hookRe)) {
|
|
35
|
+
hooks.add(m[1]);
|
|
36
|
+
}
|
|
37
|
+
for (const h of hooks) {
|
|
38
|
+
sigs.push(`hook use${h}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Import/export statements for components
|
|
42
|
+
const exportRe = /export\s+(?:const|function|default|interface|type)\s+([A-Z]\w*)/g;
|
|
43
|
+
for (const m of stripped.matchAll(exportRe)) {
|
|
44
|
+
sigs.push(`export ${m[1]}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Event handler patterns: onClick, onChange, onSubmit, etc
|
|
48
|
+
const handlerRe = /on([A-Z]\w+)\s*=\s*{?\s*\(?[a-zA-Z_$]/g;
|
|
49
|
+
const handlers = new Set();
|
|
50
|
+
for (const m of stripped.matchAll(handlerRe)) {
|
|
51
|
+
handlers.add(m[1]);
|
|
52
|
+
}
|
|
53
|
+
for (const h of handlers) {
|
|
54
|
+
sigs.push(`handler on${h}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return Array.from(new Set(sigs)).slice(0, 50);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = { extract };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extract Vue Single-File Component (SFC) signatures from .vue files.
|
|
5
|
+
* Captures component metadata: name, props, emits, slots, composables, lifecycle.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} src - Raw Vue SFC content
|
|
8
|
+
* @returns {string[]} Array of signature strings
|
|
9
|
+
*/
|
|
10
|
+
function extract(src) {
|
|
11
|
+
if (!src || typeof src !== 'string') return [];
|
|
12
|
+
const sigs = [];
|
|
13
|
+
|
|
14
|
+
// Extract script block (both <script> and <script setup>)
|
|
15
|
+
const scriptMatch = src.match(/<script[^>]*>([\s\S]*?)<\/script>/);
|
|
16
|
+
if (!scriptMatch) return sigs;
|
|
17
|
+
const script = scriptMatch[1];
|
|
18
|
+
|
|
19
|
+
// Component name (from export default { name: '...' })
|
|
20
|
+
const nameRe = /(?:name\s*:\s*['"`]([a-zA-Z0-9]+)['"`]|export\s+default\s+defineComponent\s*\(\s*{\s*name\s*:\s*['"`]([a-zA-Z0-9]+)['"`])/;
|
|
21
|
+
const nameMatch = script.match(nameRe);
|
|
22
|
+
if (nameMatch) {
|
|
23
|
+
sigs.push(`component ${nameMatch[1] || nameMatch[2]}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Props definition
|
|
27
|
+
const propsRe = /props\s*:\s*{([^}]*)}/;
|
|
28
|
+
const propsMatch = script.match(propsRe);
|
|
29
|
+
if (propsMatch) {
|
|
30
|
+
const propLines = propsMatch[1].split(',');
|
|
31
|
+
for (const line of propLines) {
|
|
32
|
+
const propName = line.trim().match(/([a-zA-Z_$]\w*)/);
|
|
33
|
+
if (propName) sigs.push(`prop ${propName[1]}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Props in composition API (defineProps)
|
|
38
|
+
const definePropsRe = /defineProps\s*(?:<([^>]+)>)?\s*\(/;
|
|
39
|
+
if (definePropsRe.test(script)) {
|
|
40
|
+
sigs.push('props composition-api');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Emits definition
|
|
44
|
+
const emitsRe = /emits\s*:\s*\[([^\]]+)\]/;
|
|
45
|
+
const emitsMatch = script.match(emitsRe);
|
|
46
|
+
if (emitsMatch) {
|
|
47
|
+
const emitNames = emitsMatch[1].split(',').map(e => e.trim().replace(/['"`]/g, ''));
|
|
48
|
+
for (const e of emitNames) {
|
|
49
|
+
if (e) sigs.push(`emit ${e}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Emits in composition API (defineEmits)
|
|
54
|
+
const defineEmitsRe = /defineEmits\s*(?:<([^>]+)>)?\s*\(/;
|
|
55
|
+
if (defineEmitsRe.test(script)) {
|
|
56
|
+
sigs.push('emits composition-api');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Lifecycle hooks
|
|
60
|
+
const lifecycleHooks = ['setup', 'created', 'mounted', 'updated', 'unmounted'];
|
|
61
|
+
for (const hook of lifecycleHooks) {
|
|
62
|
+
if (new RegExp(`\\b${hook}\\s*\\(`).test(script)) {
|
|
63
|
+
sigs.push(`lifecycle ${hook}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Composable/hooks usage (useXxx)
|
|
68
|
+
const composableRe = /(?:import|const)\s+(?:{[^}]*}|[a-zA-Z_$]\w*)\s+from\s+['"]/g;
|
|
69
|
+
if (composableRe.test(script)) {
|
|
70
|
+
const useRe = /use([A-Z]\w*)/g;
|
|
71
|
+
for (const m of script.matchAll(useRe)) {
|
|
72
|
+
sigs.push(`composable use${m[1]}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Slots definition — capture from <slot name="..."> and template slots
|
|
77
|
+
const namedSlotRe = /<slot\s+name=['"]([a-zA-Z_$]\w*)['"][^>]*>/g;
|
|
78
|
+
const templateSlotRe = /<template\s+#([a-zA-Z_$]\w*)|v-slot:([a-zA-Z_$]\w*)/g;
|
|
79
|
+
const slots = new Set();
|
|
80
|
+
|
|
81
|
+
// Named slots in template: <slot name="header">
|
|
82
|
+
for (const m of src.matchAll(namedSlotRe)) {
|
|
83
|
+
if (m[1]) slots.add(m[1]);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Template slots with # or v-slot
|
|
87
|
+
for (const m of src.matchAll(templateSlotRe)) {
|
|
88
|
+
const slotName = m[1] || m[2];
|
|
89
|
+
if (slotName) slots.add(slotName);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
for (const s of slots) {
|
|
93
|
+
sigs.push(`slot ${s}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return Array.from(new Set(sigs)).slice(0, 50);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = { extract };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const path = require('path');
|
|
3
|
+
module.exports = { format, outputPath };
|
|
4
|
+
|
|
5
|
+
function outputPath(cwd) { return path.join(cwd, 'llm.txt'); }
|
|
6
|
+
|
|
7
|
+
function format(context, cwd, version) {
|
|
8
|
+
const name = context.projectName || path.basename(cwd);
|
|
9
|
+
const langs = [...new Set((context.fileEntries || []).map(f => f.language).filter(Boolean))];
|
|
10
|
+
const mods = context.srcDirs || [];
|
|
11
|
+
|
|
12
|
+
return [
|
|
13
|
+
`# Project: ${name}`,
|
|
14
|
+
`Languages: ${langs.join(', ') || 'unknown'}`,
|
|
15
|
+
`Root: ${mods[0] || 'src/'}`,
|
|
16
|
+
'',
|
|
17
|
+
'## Modules',
|
|
18
|
+
...mods.map(m => `- ${m}/`),
|
|
19
|
+
'',
|
|
20
|
+
'## Key flows',
|
|
21
|
+
'- <!-- describe your main user flows here -->',
|
|
22
|
+
'',
|
|
23
|
+
'## Rules',
|
|
24
|
+
'- <!-- describe your team conventions here -->',
|
|
25
|
+
'',
|
|
26
|
+
`Generated: ${new Date().toISOString()} | SigMap v${version}`,
|
|
27
|
+
].join('\n');
|
|
28
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const { execSync } = require('child_process');
|
|
5
|
+
module.exports = { format, outputPath };
|
|
6
|
+
|
|
7
|
+
function outputPath(cwd) { return path.join(cwd, 'llms.txt'); }
|
|
8
|
+
|
|
9
|
+
function getShortCommit(cwd) {
|
|
10
|
+
try { return execSync('git rev-parse --short HEAD', { cwd, timeout: 2000 }).toString().trim(); }
|
|
11
|
+
catch (_) { return ''; }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function detectVersion(cwd) {
|
|
15
|
+
try {
|
|
16
|
+
const pkg = path.join(cwd, 'package.json');
|
|
17
|
+
if (fs.existsSync(pkg)) return JSON.parse(fs.readFileSync(pkg, 'utf8')).version || '';
|
|
18
|
+
} catch (_) {}
|
|
19
|
+
return '';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function format(context, cwd, writtenFiles, sigmapVersion) {
|
|
23
|
+
writtenFiles = writtenFiles || [];
|
|
24
|
+
sigmapVersion = sigmapVersion || '';
|
|
25
|
+
|
|
26
|
+
const name = context.projectName || path.basename(cwd);
|
|
27
|
+
const ver = detectVersion(cwd);
|
|
28
|
+
const commit = getShortCommit(cwd);
|
|
29
|
+
const langs = [...new Set((context.fileEntries || []).map(f => f.language).filter(Boolean))];
|
|
30
|
+
|
|
31
|
+
const lines = [
|
|
32
|
+
'# SigMap Context Index',
|
|
33
|
+
`> Generated by SigMap v${sigmapVersion} — zero-dependency AI context engine`,
|
|
34
|
+
'',
|
|
35
|
+
'## Project',
|
|
36
|
+
`- Name: ${name}`,
|
|
37
|
+
];
|
|
38
|
+
if (ver) lines.push(`- Version: ${ver}`);
|
|
39
|
+
if (langs.length) lines.push(`- Language: ${langs.join(', ')}`);
|
|
40
|
+
if (commit) lines.push(`- Commit: ${commit}`);
|
|
41
|
+
|
|
42
|
+
if (writtenFiles.length) {
|
|
43
|
+
lines.push('', '## Context Files');
|
|
44
|
+
for (const f of writtenFiles) {
|
|
45
|
+
const rel = path.relative(cwd, f.path);
|
|
46
|
+
const extra = f.tokens ? `: ${f.tokens} tokens` : '';
|
|
47
|
+
lines.push(`- [${f.label || rel}](${rel})${extra}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const mods = context.srcDirs || [];
|
|
52
|
+
if (mods.length) {
|
|
53
|
+
lines.push('', '## Source Modules');
|
|
54
|
+
for (const m of mods) lines.push(`- [${m}/](${m}/)`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const top5 = (context.fileEntries || [])
|
|
58
|
+
.sort((a, b) => (b.sigs || []).length - (a.sigs || []).length)
|
|
59
|
+
.slice(0, 5);
|
|
60
|
+
if (top5.length) {
|
|
61
|
+
lines.push('', '## Key Files');
|
|
62
|
+
for (const f of top5) {
|
|
63
|
+
const rel = path.relative(cwd, f.filePath);
|
|
64
|
+
const preview = (f.sigs || []).slice(0, 3).join(', ');
|
|
65
|
+
lines.push(`- ${rel}: ${preview}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return lines.join('\n');
|
|
70
|
+
}
|
package/src/mcp/server.js
CHANGED