pmp-gywd 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +27 -0
- package/README.md +567 -0
- package/bin/install.js +348 -0
- package/commands/gywd/add-phase.md +207 -0
- package/commands/gywd/anticipate.md +271 -0
- package/commands/gywd/bootstrap.md +336 -0
- package/commands/gywd/challenge.md +344 -0
- package/commands/gywd/check-drift.md +144 -0
- package/commands/gywd/complete-milestone.md +106 -0
- package/commands/gywd/consider-issues.md +202 -0
- package/commands/gywd/context.md +93 -0
- package/commands/gywd/create-roadmap.md +115 -0
- package/commands/gywd/deps.md +169 -0
- package/commands/gywd/digest.md +138 -0
- package/commands/gywd/discuss-milestone.md +47 -0
- package/commands/gywd/discuss-phase.md +60 -0
- package/commands/gywd/execute-plan.md +161 -0
- package/commands/gywd/extract-decisions.md +325 -0
- package/commands/gywd/health.md +150 -0
- package/commands/gywd/help.md +556 -0
- package/commands/gywd/history.md +278 -0
- package/commands/gywd/impact.md +317 -0
- package/commands/gywd/init.md +95 -0
- package/commands/gywd/insert-phase.md +227 -0
- package/commands/gywd/list-phase-assumptions.md +50 -0
- package/commands/gywd/map-codebase.md +84 -0
- package/commands/gywd/memory.md +159 -0
- package/commands/gywd/new-milestone.md +59 -0
- package/commands/gywd/new-project.md +315 -0
- package/commands/gywd/pause-work.md +123 -0
- package/commands/gywd/plan-fix.md +205 -0
- package/commands/gywd/plan-phase.md +93 -0
- package/commands/gywd/preview-plan.md +139 -0
- package/commands/gywd/profile.md +363 -0
- package/commands/gywd/progress.md +317 -0
- package/commands/gywd/remove-phase.md +338 -0
- package/commands/gywd/research-phase.md +91 -0
- package/commands/gywd/resume-work.md +40 -0
- package/commands/gywd/rollback.md +179 -0
- package/commands/gywd/status.md +42 -0
- package/commands/gywd/sync-github.md +234 -0
- package/commands/gywd/verify-work.md +71 -0
- package/commands/gywd/why.md +251 -0
- package/docs/COMMANDS.md +722 -0
- package/docs/CONTRIBUTING.md +342 -0
- package/docs/EXAMPLES.md +535 -0
- package/docs/GETTING-STARTED.md +262 -0
- package/docs/README.md +55 -0
- package/docs/RELEASING.md +159 -0
- package/get-your-work-done/core/agent-patterns.md +331 -0
- package/get-your-work-done/core/architecture.md +334 -0
- package/get-your-work-done/core/context-model-schema.json +154 -0
- package/get-your-work-done/core/decisions-schema.json +193 -0
- package/get-your-work-done/core/learning-state-schema.json +133 -0
- package/get-your-work-done/core/profile-schema.json +257 -0
- package/get-your-work-done/references/adaptive-decomposition.md +175 -0
- package/get-your-work-done/references/checkpoints.md +287 -0
- package/get-your-work-done/references/confidence-scoring.md +169 -0
- package/get-your-work-done/references/continuation-format.md +255 -0
- package/get-your-work-done/references/git-integration.md +254 -0
- package/get-your-work-done/references/plan-format.md +428 -0
- package/get-your-work-done/references/principles.md +157 -0
- package/get-your-work-done/references/questioning.md +162 -0
- package/get-your-work-done/references/research-pitfalls.md +215 -0
- package/get-your-work-done/references/scope-estimation.md +172 -0
- package/get-your-work-done/references/tdd.md +263 -0
- package/get-your-work-done/templates/codebase/architecture.md +255 -0
- package/get-your-work-done/templates/codebase/concerns.md +310 -0
- package/get-your-work-done/templates/codebase/conventions.md +307 -0
- package/get-your-work-done/templates/codebase/integrations.md +280 -0
- package/get-your-work-done/templates/codebase/stack.md +186 -0
- package/get-your-work-done/templates/codebase/structure.md +285 -0
- package/get-your-work-done/templates/codebase/testing.md +480 -0
- package/get-your-work-done/templates/config.json +18 -0
- package/get-your-work-done/templates/context.md +161 -0
- package/get-your-work-done/templates/continue-here.md +78 -0
- package/get-your-work-done/templates/discovery.md +146 -0
- package/get-your-work-done/templates/issues.md +32 -0
- package/get-your-work-done/templates/milestone-archive.md +123 -0
- package/get-your-work-done/templates/milestone-context.md +93 -0
- package/get-your-work-done/templates/milestone.md +115 -0
- package/get-your-work-done/templates/phase-prompt.md +303 -0
- package/get-your-work-done/templates/project.md +184 -0
- package/get-your-work-done/templates/research.md +529 -0
- package/get-your-work-done/templates/roadmap.md +196 -0
- package/get-your-work-done/templates/state.md +210 -0
- package/get-your-work-done/templates/summary.md +273 -0
- package/get-your-work-done/templates/uat-issues.md +143 -0
- package/get-your-work-done/workflows/complete-milestone.md +643 -0
- package/get-your-work-done/workflows/create-milestone.md +416 -0
- package/get-your-work-done/workflows/create-roadmap.md +481 -0
- package/get-your-work-done/workflows/discovery-phase.md +293 -0
- package/get-your-work-done/workflows/discuss-milestone.md +236 -0
- package/get-your-work-done/workflows/discuss-phase.md +247 -0
- package/get-your-work-done/workflows/execute-phase.md +1625 -0
- package/get-your-work-done/workflows/list-phase-assumptions.md +178 -0
- package/get-your-work-done/workflows/map-codebase.md +434 -0
- package/get-your-work-done/workflows/plan-phase.md +488 -0
- package/get-your-work-done/workflows/research-phase.md +436 -0
- package/get-your-work-done/workflows/resume-project.md +287 -0
- package/get-your-work-done/workflows/transition.md +580 -0
- package/get-your-work-done/workflows/verify-work.md +202 -0
- package/lib/automation/dependency-analyzer.js +635 -0
- package/lib/automation/doc-generator.js +643 -0
- package/lib/automation/index.js +42 -0
- package/lib/automation/test-generator.js +628 -0
- package/lib/context/context-analyzer.js +554 -0
- package/lib/context/context-cache.js +426 -0
- package/lib/context/context-predictor.js +622 -0
- package/lib/context/index.js +44 -0
- package/lib/memory/confidence-calibrator.js +484 -0
- package/lib/memory/feedback-collector.js +551 -0
- package/lib/memory/global-memory.js +465 -0
- package/lib/memory/index.js +75 -0
- package/lib/memory/pattern-aggregator.js +487 -0
- package/lib/memory/team-sync.js +501 -0
- package/lib/profile/index.js +24 -0
- package/lib/profile/pattern-learner.js +303 -0
- package/lib/profile/profile-manager.js +445 -0
- package/lib/questioning/index.js +49 -0
- package/lib/questioning/question-engine.js +311 -0
- package/lib/questioning/question-templates.js +315 -0
- package/lib/validators/command-validator.js +188 -0
- package/lib/validators/index.js +29 -0
- package/lib/validators/schema-validator.js +183 -0
- package/package.json +61 -0
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Analyzer
|
|
3
|
+
*
|
|
4
|
+
* Analyzes codebase structure to understand file relationships.
|
|
5
|
+
* Builds a relationship graph for intelligent context prediction.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Relationship types between files
|
|
13
|
+
*/
|
|
14
|
+
const RELATIONSHIP_TYPES = {
|
|
15
|
+
IMPORTS: 'imports',
|
|
16
|
+
IMPORTED_BY: 'imported_by',
|
|
17
|
+
SAME_DIRECTORY: 'same_dir',
|
|
18
|
+
SIMILAR_NAME: 'similar_name',
|
|
19
|
+
TEST_FOR: 'test_for',
|
|
20
|
+
TESTED_BY: 'tested_by',
|
|
21
|
+
CO_MODIFIED: 'co_modified',
|
|
22
|
+
CO_ACCESSED: 'co_accessed',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Weight multipliers for relationship types
|
|
27
|
+
*/
|
|
28
|
+
const RELATIONSHIP_WEIGHTS = {
|
|
29
|
+
[RELATIONSHIP_TYPES.IMPORTS]: 1.0,
|
|
30
|
+
[RELATIONSHIP_TYPES.IMPORTED_BY]: 0.8,
|
|
31
|
+
[RELATIONSHIP_TYPES.SAME_DIRECTORY]: 0.3,
|
|
32
|
+
[RELATIONSHIP_TYPES.SIMILAR_NAME]: 0.5,
|
|
33
|
+
[RELATIONSHIP_TYPES.TEST_FOR]: 0.9,
|
|
34
|
+
[RELATIONSHIP_TYPES.TESTED_BY]: 0.9,
|
|
35
|
+
[RELATIONSHIP_TYPES.CO_MODIFIED]: 0.7,
|
|
36
|
+
[RELATIONSHIP_TYPES.CO_ACCESSED]: 0.6,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Regex patterns for import detection
|
|
41
|
+
*/
|
|
42
|
+
const IMPORT_PATTERNS = {
|
|
43
|
+
// ES6: import x from './file'
|
|
44
|
+
esm: /import\s+(?:(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)\s+from\s+)?['"]([^'"]+)['"]/g,
|
|
45
|
+
// CommonJS: require('./file')
|
|
46
|
+
cjs: /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
47
|
+
// Dynamic import: import('./file')
|
|
48
|
+
dynamic: /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Context Analyzer class
|
|
53
|
+
*/
|
|
54
|
+
class ContextAnalyzer {
|
|
55
|
+
constructor() {
|
|
56
|
+
this.fileGraph = new Map(); // file -> Map<relatedFile, relationships[]>
|
|
57
|
+
this.fileMetadata = new Map(); // file -> metadata
|
|
58
|
+
this.keywords = new Map(); // keyword -> Set<files>
|
|
59
|
+
this.directories = new Map(); // directory -> Set<files>
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Analyze a file and extract its relationships
|
|
64
|
+
* @param {string} filePath - Path to the file
|
|
65
|
+
* @param {string} content - File content
|
|
66
|
+
* @returns {object} Analysis result
|
|
67
|
+
*/
|
|
68
|
+
analyzeFile(filePath, content) {
|
|
69
|
+
const normalizedPath = this.normalizePath(filePath);
|
|
70
|
+
const ext = path.extname(filePath);
|
|
71
|
+
const baseName = path.basename(filePath, ext);
|
|
72
|
+
const dirName = path.dirname(filePath);
|
|
73
|
+
|
|
74
|
+
// Initialize file in graph if needed
|
|
75
|
+
if (!this.fileGraph.has(normalizedPath)) {
|
|
76
|
+
this.fileGraph.set(normalizedPath, new Map());
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Store metadata
|
|
80
|
+
const metadata = {
|
|
81
|
+
path: normalizedPath,
|
|
82
|
+
extension: ext,
|
|
83
|
+
baseName,
|
|
84
|
+
directory: dirName,
|
|
85
|
+
size: content.length,
|
|
86
|
+
lineCount: content.split('\n').length,
|
|
87
|
+
isTest: this.isTestFile(filePath),
|
|
88
|
+
lastAnalyzed: Date.now(),
|
|
89
|
+
};
|
|
90
|
+
this.fileMetadata.set(normalizedPath, metadata);
|
|
91
|
+
|
|
92
|
+
// Track directory membership
|
|
93
|
+
if (!this.directories.has(dirName)) {
|
|
94
|
+
this.directories.set(dirName, new Set());
|
|
95
|
+
}
|
|
96
|
+
this.directories.get(dirName).add(normalizedPath);
|
|
97
|
+
|
|
98
|
+
// Extract imports
|
|
99
|
+
const imports = this.extractImports(content, dirName);
|
|
100
|
+
|
|
101
|
+
// Extract keywords
|
|
102
|
+
const keywords = this.extractKeywords(content, baseName);
|
|
103
|
+
for (const keyword of keywords) {
|
|
104
|
+
if (!this.keywords.has(keyword)) {
|
|
105
|
+
this.keywords.set(keyword, new Set());
|
|
106
|
+
}
|
|
107
|
+
this.keywords.get(keyword).add(normalizedPath);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
path: normalizedPath,
|
|
112
|
+
metadata,
|
|
113
|
+
imports,
|
|
114
|
+
keywords,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Extract import statements from file content
|
|
120
|
+
* @param {string} content - File content
|
|
121
|
+
* @param {string} baseDir - Base directory for resolving relative paths
|
|
122
|
+
* @returns {string[]} Array of imported file paths
|
|
123
|
+
*/
|
|
124
|
+
extractImports(content, baseDir) {
|
|
125
|
+
const imports = new Set();
|
|
126
|
+
|
|
127
|
+
for (const [_type, regex] of Object.entries(IMPORT_PATTERNS)) {
|
|
128
|
+
// Reset regex state
|
|
129
|
+
regex.lastIndex = 0;
|
|
130
|
+
let match;
|
|
131
|
+
|
|
132
|
+
while ((match = regex.exec(content)) !== null) {
|
|
133
|
+
const importPath = match[1];
|
|
134
|
+
|
|
135
|
+
// Skip external packages (no ./ or ../)
|
|
136
|
+
if (!importPath.startsWith('.') && !importPath.startsWith('/')) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Resolve relative path
|
|
141
|
+
const resolved = this.resolveImportPath(importPath, baseDir);
|
|
142
|
+
if (resolved) {
|
|
143
|
+
imports.add(resolved);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return Array.from(imports);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Resolve an import path to a normalized file path
|
|
153
|
+
* @param {string} importPath - Import path from source
|
|
154
|
+
* @param {string} baseDir - Base directory
|
|
155
|
+
* @returns {string|null} Resolved path or null
|
|
156
|
+
*/
|
|
157
|
+
resolveImportPath(importPath, baseDir) {
|
|
158
|
+
// Handle relative paths
|
|
159
|
+
const resolved = path.resolve(baseDir, importPath);
|
|
160
|
+
|
|
161
|
+
// Try common extensions if no extension
|
|
162
|
+
if (!path.extname(resolved)) {
|
|
163
|
+
const extensions = ['.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs'];
|
|
164
|
+
for (const ext of extensions) {
|
|
165
|
+
if (fs.existsSync(resolved + ext)) {
|
|
166
|
+
return this.normalizePath(resolved + ext);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Try index files
|
|
170
|
+
for (const ext of extensions) {
|
|
171
|
+
const indexPath = path.join(resolved, `index${ext}`);
|
|
172
|
+
if (fs.existsSync(indexPath)) {
|
|
173
|
+
return this.normalizePath(indexPath);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (fs.existsSync(resolved)) {
|
|
179
|
+
return this.normalizePath(resolved);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Extract keywords from file content and name
|
|
187
|
+
* @param {string} content - File content
|
|
188
|
+
* @param {string} baseName - File base name
|
|
189
|
+
* @returns {string[]} Array of keywords
|
|
190
|
+
*/
|
|
191
|
+
extractKeywords(content, baseName) {
|
|
192
|
+
const keywords = new Set();
|
|
193
|
+
|
|
194
|
+
// Add words from file name (split on camelCase, PascalCase, kebab-case, snake_case)
|
|
195
|
+
const nameWords = baseName
|
|
196
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
197
|
+
.replace(/[-_]/g, ' ')
|
|
198
|
+
.toLowerCase()
|
|
199
|
+
.split(/\s+/)
|
|
200
|
+
.filter(w => w.length > 2);
|
|
201
|
+
|
|
202
|
+
nameWords.forEach(w => keywords.add(w));
|
|
203
|
+
|
|
204
|
+
// Extract function/class names
|
|
205
|
+
const functionMatches = content.matchAll(/(?:function|class|const|let|var)\s+(\w+)/g);
|
|
206
|
+
for (const match of functionMatches) {
|
|
207
|
+
const name = match[1].toLowerCase();
|
|
208
|
+
if (name.length > 2) {
|
|
209
|
+
keywords.add(name);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Extract common domain terms
|
|
214
|
+
const domainTerms = [
|
|
215
|
+
'auth', 'user', 'login', 'api', 'database', 'db', 'cache',
|
|
216
|
+
'config', 'util', 'helper', 'service', 'controller', 'model',
|
|
217
|
+
'view', 'component', 'route', 'middleware', 'test', 'spec',
|
|
218
|
+
'payment', 'order', 'product', 'customer', 'admin', 'dashboard',
|
|
219
|
+
];
|
|
220
|
+
|
|
221
|
+
const lowerContent = content.toLowerCase();
|
|
222
|
+
for (const term of domainTerms) {
|
|
223
|
+
if (lowerContent.includes(term)) {
|
|
224
|
+
keywords.add(term);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return Array.from(keywords);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Check if a file is a test file
|
|
233
|
+
* @param {string} filePath - File path
|
|
234
|
+
* @returns {boolean}
|
|
235
|
+
*/
|
|
236
|
+
isTestFile(filePath) {
|
|
237
|
+
const lower = filePath.toLowerCase();
|
|
238
|
+
return (
|
|
239
|
+
lower.includes('.test.') ||
|
|
240
|
+
lower.includes('.spec.') ||
|
|
241
|
+
lower.includes('__tests__') ||
|
|
242
|
+
lower.includes('/test/') ||
|
|
243
|
+
lower.includes('/tests/')
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Build relationship graph from analyzed files
|
|
249
|
+
*/
|
|
250
|
+
buildRelationshipGraph() {
|
|
251
|
+
// Build import relationships
|
|
252
|
+
for (const [filePath, _metadata] of this.fileMetadata) {
|
|
253
|
+
const fileData = this.fileGraph.get(filePath);
|
|
254
|
+
if (!fileData) continue;
|
|
255
|
+
|
|
256
|
+
// Find test relationships
|
|
257
|
+
this.findTestRelationships(filePath);
|
|
258
|
+
|
|
259
|
+
// Find similar name relationships
|
|
260
|
+
this.findSimilarNameRelationships(filePath);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Build same-directory relationships
|
|
264
|
+
for (const [_dir, files] of this.directories) {
|
|
265
|
+
const fileArray = Array.from(files);
|
|
266
|
+
for (let i = 0; i < fileArray.length; i++) {
|
|
267
|
+
for (let j = i + 1; j < fileArray.length; j++) {
|
|
268
|
+
this.addRelationship(fileArray[i], fileArray[j], RELATIONSHIP_TYPES.SAME_DIRECTORY);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Add import relationship between files
|
|
276
|
+
* @param {string} fromFile - Importing file
|
|
277
|
+
* @param {string} toFile - Imported file
|
|
278
|
+
*/
|
|
279
|
+
addImportRelationship(fromFile, toFile) {
|
|
280
|
+
this.addRelationship(fromFile, toFile, RELATIONSHIP_TYPES.IMPORTS);
|
|
281
|
+
this.addRelationship(toFile, fromFile, RELATIONSHIP_TYPES.IMPORTED_BY);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Find test-to-source relationships
|
|
286
|
+
* @param {string} filePath - File to analyze
|
|
287
|
+
*/
|
|
288
|
+
findTestRelationships(filePath) {
|
|
289
|
+
const metadata = this.fileMetadata.get(filePath);
|
|
290
|
+
if (!metadata) return;
|
|
291
|
+
|
|
292
|
+
if (metadata.isTest) {
|
|
293
|
+
// Find the source file this tests
|
|
294
|
+
const sourcePattern = metadata.baseName
|
|
295
|
+
.replace(/\.test$/, '')
|
|
296
|
+
.replace(/\.spec$/, '')
|
|
297
|
+
.replace(/Test$/, '')
|
|
298
|
+
.replace(/Spec$/, '');
|
|
299
|
+
|
|
300
|
+
for (const [otherPath, otherMeta] of this.fileMetadata) {
|
|
301
|
+
if (otherPath === filePath) continue;
|
|
302
|
+
if (otherMeta.isTest) continue;
|
|
303
|
+
|
|
304
|
+
if (otherMeta.baseName.toLowerCase() === sourcePattern.toLowerCase()) {
|
|
305
|
+
this.addRelationship(filePath, otherPath, RELATIONSHIP_TYPES.TEST_FOR);
|
|
306
|
+
this.addRelationship(otherPath, filePath, RELATIONSHIP_TYPES.TESTED_BY);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Find similar name relationships
|
|
314
|
+
* @param {string} filePath - File to analyze
|
|
315
|
+
*/
|
|
316
|
+
findSimilarNameRelationships(filePath) {
|
|
317
|
+
const metadata = this.fileMetadata.get(filePath);
|
|
318
|
+
if (!metadata) return;
|
|
319
|
+
|
|
320
|
+
const baseName = metadata.baseName.toLowerCase();
|
|
321
|
+
|
|
322
|
+
for (const [otherPath, otherMeta] of this.fileMetadata) {
|
|
323
|
+
if (otherPath === filePath) continue;
|
|
324
|
+
|
|
325
|
+
const otherBase = otherMeta.baseName.toLowerCase();
|
|
326
|
+
|
|
327
|
+
// Check if names share a common root (>50% overlap)
|
|
328
|
+
if (this.calculateNameSimilarity(baseName, otherBase) > 0.5) {
|
|
329
|
+
this.addRelationship(filePath, otherPath, RELATIONSHIP_TYPES.SIMILAR_NAME);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Calculate name similarity between two strings
|
|
336
|
+
* @param {string} a - First string
|
|
337
|
+
* @param {string} b - Second string
|
|
338
|
+
* @returns {number} Similarity score (0-1)
|
|
339
|
+
*/
|
|
340
|
+
calculateNameSimilarity(a, b) {
|
|
341
|
+
if (a === b) return 1;
|
|
342
|
+
if (a.length === 0 || b.length === 0) return 0;
|
|
343
|
+
|
|
344
|
+
// Check prefix match
|
|
345
|
+
const minLen = Math.min(a.length, b.length);
|
|
346
|
+
let prefixMatch = 0;
|
|
347
|
+
for (let i = 0; i < minLen; i++) {
|
|
348
|
+
if (a[i] === b[i]) prefixMatch++;
|
|
349
|
+
else break;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Check if one contains the other
|
|
353
|
+
if (a.includes(b) || b.includes(a)) {
|
|
354
|
+
return 0.7;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return prefixMatch / Math.max(a.length, b.length);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Add a relationship between two files
|
|
362
|
+
* @param {string} from - Source file
|
|
363
|
+
* @param {string} to - Target file
|
|
364
|
+
* @param {string} type - Relationship type
|
|
365
|
+
*/
|
|
366
|
+
addRelationship(from, to, type) {
|
|
367
|
+
if (!this.fileGraph.has(from)) {
|
|
368
|
+
this.fileGraph.set(from, new Map());
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const relations = this.fileGraph.get(from);
|
|
372
|
+
if (!relations.has(to)) {
|
|
373
|
+
relations.set(to, []);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const existing = relations.get(to);
|
|
377
|
+
if (!existing.includes(type)) {
|
|
378
|
+
existing.push(type);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Record co-access pattern
|
|
384
|
+
* @param {string[]} files - Files accessed together
|
|
385
|
+
*/
|
|
386
|
+
recordCoAccess(files) {
|
|
387
|
+
for (let i = 0; i < files.length; i++) {
|
|
388
|
+
for (let j = i + 1; j < files.length; j++) {
|
|
389
|
+
this.addRelationship(files[i], files[j], RELATIONSHIP_TYPES.CO_ACCESSED);
|
|
390
|
+
this.addRelationship(files[j], files[i], RELATIONSHIP_TYPES.CO_ACCESSED);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Get related files with scores
|
|
397
|
+
* @param {string} filePath - File to find relations for
|
|
398
|
+
* @param {number} limit - Maximum results
|
|
399
|
+
* @returns {Array<{file: string, score: number, relationships: string[]}>}
|
|
400
|
+
*/
|
|
401
|
+
getRelatedFiles(filePath, limit = 10) {
|
|
402
|
+
const normalizedPath = this.normalizePath(filePath);
|
|
403
|
+
const relations = this.fileGraph.get(normalizedPath);
|
|
404
|
+
|
|
405
|
+
if (!relations) return [];
|
|
406
|
+
|
|
407
|
+
const scored = [];
|
|
408
|
+
|
|
409
|
+
for (const [relatedFile, relationTypes] of relations) {
|
|
410
|
+
let score = 0;
|
|
411
|
+
for (const type of relationTypes) {
|
|
412
|
+
score += RELATIONSHIP_WEIGHTS[type] || 0.1;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
scored.push({
|
|
416
|
+
file: relatedFile,
|
|
417
|
+
score,
|
|
418
|
+
relationships: relationTypes,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Sort by score descending
|
|
423
|
+
scored.sort((a, b) => b.score - a.score);
|
|
424
|
+
|
|
425
|
+
return scored.slice(0, limit);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Find files by keyword
|
|
430
|
+
* @param {string} keyword - Keyword to search
|
|
431
|
+
* @returns {string[]} Matching file paths
|
|
432
|
+
*/
|
|
433
|
+
findByKeyword(keyword) {
|
|
434
|
+
const lowerKeyword = keyword.toLowerCase();
|
|
435
|
+
const files = this.keywords.get(lowerKeyword);
|
|
436
|
+
return files ? Array.from(files) : [];
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Find files by multiple keywords (intersection)
|
|
441
|
+
* @param {string[]} keywords - Keywords to search
|
|
442
|
+
* @returns {string[]} Matching file paths
|
|
443
|
+
*/
|
|
444
|
+
findByKeywords(keywords) {
|
|
445
|
+
if (keywords.length === 0) return [];
|
|
446
|
+
|
|
447
|
+
const sets = keywords
|
|
448
|
+
.map(k => this.keywords.get(k.toLowerCase()))
|
|
449
|
+
.filter(s => s !== undefined);
|
|
450
|
+
|
|
451
|
+
if (sets.length === 0) return [];
|
|
452
|
+
|
|
453
|
+
// Intersection of all sets
|
|
454
|
+
let result = new Set(sets[0]);
|
|
455
|
+
for (let i = 1; i < sets.length; i++) {
|
|
456
|
+
result = new Set([...result].filter(x => sets[i].has(x)));
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return Array.from(result);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Normalize file path for consistent comparison
|
|
464
|
+
* @param {string} filePath - File path
|
|
465
|
+
* @returns {string} Normalized path
|
|
466
|
+
*/
|
|
467
|
+
normalizePath(filePath) {
|
|
468
|
+
return path.normalize(filePath).replace(/\\/g, '/');
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Get analysis summary
|
|
473
|
+
* @returns {object} Summary statistics
|
|
474
|
+
*/
|
|
475
|
+
getSummary() {
|
|
476
|
+
let totalRelationships = 0;
|
|
477
|
+
for (const relations of this.fileGraph.values()) {
|
|
478
|
+
totalRelationships += relations.size;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
return {
|
|
482
|
+
filesAnalyzed: this.fileMetadata.size,
|
|
483
|
+
totalRelationships,
|
|
484
|
+
keywordsIndexed: this.keywords.size,
|
|
485
|
+
directoriesTracked: this.directories.size,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Export graph data for persistence
|
|
491
|
+
* @returns {object} Serializable graph data
|
|
492
|
+
*/
|
|
493
|
+
export() {
|
|
494
|
+
const graph = {};
|
|
495
|
+
for (const [file, relations] of this.fileGraph) {
|
|
496
|
+
graph[file] = {};
|
|
497
|
+
for (const [related, types] of relations) {
|
|
498
|
+
graph[file][related] = types;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const metadata = {};
|
|
503
|
+
for (const [file, meta] of this.fileMetadata) {
|
|
504
|
+
metadata[file] = meta;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const keywords = {};
|
|
508
|
+
for (const [keyword, files] of this.keywords) {
|
|
509
|
+
keywords[keyword] = Array.from(files);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return { graph, metadata, keywords };
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Import graph data from persistence
|
|
517
|
+
* @param {object} data - Previously exported data
|
|
518
|
+
*/
|
|
519
|
+
import(data) {
|
|
520
|
+
if (data.graph) {
|
|
521
|
+
for (const [file, relations] of Object.entries(data.graph)) {
|
|
522
|
+
this.fileGraph.set(file, new Map(Object.entries(relations)));
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (data.metadata) {
|
|
527
|
+
for (const [file, meta] of Object.entries(data.metadata)) {
|
|
528
|
+
this.fileMetadata.set(file, meta);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (data.keywords) {
|
|
533
|
+
for (const [keyword, files] of Object.entries(data.keywords)) {
|
|
534
|
+
this.keywords.set(keyword, new Set(files));
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Clear all data
|
|
541
|
+
*/
|
|
542
|
+
clear() {
|
|
543
|
+
this.fileGraph.clear();
|
|
544
|
+
this.fileMetadata.clear();
|
|
545
|
+
this.keywords.clear();
|
|
546
|
+
this.directories.clear();
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
module.exports = {
|
|
551
|
+
ContextAnalyzer,
|
|
552
|
+
RELATIONSHIP_TYPES,
|
|
553
|
+
RELATIONSHIP_WEIGHTS,
|
|
554
|
+
};
|