@codeledger/repo 0.1.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/LICENSE +27 -0
- package/dist/churn.d.ts +3 -0
- package/dist/churn.d.ts.map +1 -0
- package/dist/churn.js +66 -0
- package/dist/churn.js.map +1 -0
- package/dist/dep-graph.d.ts +3 -0
- package/dist/dep-graph.d.ts.map +1 -0
- package/dist/dep-graph.js +94 -0
- package/dist/dep-graph.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/indexer.d.ts +12 -0
- package/dist/indexer.d.ts.map +1 -0
- package/dist/indexer.js +57 -0
- package/dist/indexer.js.map +1 -0
- package/dist/scanner.d.ts +3 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +109 -0
- package/dist/scanner.js.map +1 -0
- package/dist/test-map.d.ts +3 -0
- package/dist/test-map.d.ts.map +1 -0
- package/dist/test-map.js +103 -0
- package/dist/test-map.js.map +1 -0
- package/dist/walk.d.ts +2 -0
- package/dist/walk.d.ts.map +1 -0
- package/dist/walk.js +132 -0
- package/dist/walk.js.map +1 -0
- package/package.json +37 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Intelligent Context AI, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
Note: This license applies to the CLI wrapper, types, repository scanning,
|
|
26
|
+
instrumentation, harness, and report packages (the "Plugin"). The scoring
|
|
27
|
+
engine (packages/core-engine/bin/) is licensed separately under LICENSE-CORE.
|
package/dist/churn.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"churn.d.ts","sourceRoot":"","sources":["../src/churn.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE,MAAW,GAAG,SAAS,EAAE,CA+E9E"}
|
package/dist/churn.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
export function analyzeChurn(root, sinceDays = 90) {
|
|
3
|
+
// Validate sinceDays is a safe positive integer to prevent shell injection
|
|
4
|
+
const safeDays = Math.floor(Math.abs(sinceDays));
|
|
5
|
+
if (!Number.isFinite(safeDays) || safeDays <= 0 || safeDays > 3650) {
|
|
6
|
+
throw new Error(`Invalid sinceDays value: ${sinceDays} (must be 1–3650)`);
|
|
7
|
+
}
|
|
8
|
+
const churnMap = new Map();
|
|
9
|
+
let gitLog;
|
|
10
|
+
try {
|
|
11
|
+
gitLog = execSync(`git log --numstat --since=${String(safeDays)}.days --pretty=format:"COMMIT %aI"`, { cwd: root, encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024 });
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
// Not a git repo or git not available
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
let currentDate = null;
|
|
18
|
+
for (const line of gitLog.split('\n')) {
|
|
19
|
+
const trimmed = line.trim();
|
|
20
|
+
if (trimmed.startsWith('COMMIT ')) {
|
|
21
|
+
const dateStr = trimmed.slice(7);
|
|
22
|
+
currentDate = new Date(dateStr);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (!trimmed || !currentDate)
|
|
26
|
+
continue;
|
|
27
|
+
// numstat format: added\tremoved\tfilepath
|
|
28
|
+
const parts = trimmed.split('\t');
|
|
29
|
+
if (parts.length < 3)
|
|
30
|
+
continue;
|
|
31
|
+
const filePath = parts[2];
|
|
32
|
+
// Skip binary files (shown as -)
|
|
33
|
+
if (parts[0] === '-')
|
|
34
|
+
continue;
|
|
35
|
+
const existing = churnMap.get(filePath);
|
|
36
|
+
if (!existing) {
|
|
37
|
+
churnMap.set(filePath, { commits: 1, lastDate: currentDate });
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
existing.commits++;
|
|
41
|
+
if (currentDate > existing.lastDate) {
|
|
42
|
+
existing.lastDate = currentDate;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const now = Date.now();
|
|
47
|
+
const results = [];
|
|
48
|
+
for (const [path, info] of churnMap) {
|
|
49
|
+
const daysSinceTouch = Math.max(0, (now - info.lastDate.getTime()) / (1000 * 60 * 60 * 24));
|
|
50
|
+
// Time-decayed score: more recent changes score higher
|
|
51
|
+
// Decay factor: e^(-daysSinceTouch / halfLifeDays)
|
|
52
|
+
const halfLife = 30;
|
|
53
|
+
const recencyFactor = Math.exp(-daysSinceTouch / halfLife);
|
|
54
|
+
const timeDecayedScore = Math.min(1, (info.commits / 20) * recencyFactor);
|
|
55
|
+
results.push({
|
|
56
|
+
path,
|
|
57
|
+
commit_count: info.commits,
|
|
58
|
+
days_since_last_touch: Math.round(daysSinceTouch),
|
|
59
|
+
time_decayed_score: Math.round(timeDecayedScore * 1000) / 1000,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
// Sort by time_decayed_score descending
|
|
63
|
+
results.sort((a, b) => b.time_decayed_score - a.time_decayed_score);
|
|
64
|
+
return results;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=churn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"churn.js","sourceRoot":"","sources":["../src/churn.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dep-graph.d.ts","sourceRoot":"","sources":["../src/dep-graph.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAQ5D,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,QAAQ,CAiC3G"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
// Matches: import ... from '...' / import '...' / require('...')
|
|
4
|
+
// Also: export ... from '...'
|
|
5
|
+
const IMPORT_RE = /(?:import|export)\s+(?:[\s\S]*?\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
6
|
+
const REQUIRE_RE = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
7
|
+
export function buildDepGraph(root, files, contentCache) {
|
|
8
|
+
const imports = {};
|
|
9
|
+
const dependents = {};
|
|
10
|
+
// Build path lookup set for fast resolution
|
|
11
|
+
const fileSet = new Set(files.map((f) => f.path));
|
|
12
|
+
for (const file of files) {
|
|
13
|
+
let content;
|
|
14
|
+
const cached = contentCache?.get(file.path);
|
|
15
|
+
if (cached !== undefined) {
|
|
16
|
+
content = cached;
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
const absPath = join(root, file.path);
|
|
20
|
+
try {
|
|
21
|
+
content = readFileSync(absPath, 'utf-8');
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const resolved = extractImports(content, file.path, root, fileSet);
|
|
28
|
+
imports[file.path] = resolved;
|
|
29
|
+
for (const dep of resolved) {
|
|
30
|
+
if (!dependents[dep]) {
|
|
31
|
+
dependents[dep] = [];
|
|
32
|
+
}
|
|
33
|
+
dependents[dep].push(file.path);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return { imports, dependents };
|
|
37
|
+
}
|
|
38
|
+
function extractImports(content, filePath, root, fileSet) {
|
|
39
|
+
const specifiers = new Set();
|
|
40
|
+
for (const regex of [IMPORT_RE, REQUIRE_RE]) {
|
|
41
|
+
regex.lastIndex = 0;
|
|
42
|
+
let match;
|
|
43
|
+
while ((match = regex.exec(content)) !== null) {
|
|
44
|
+
const spec = match[1];
|
|
45
|
+
if (spec && isRelative(spec)) {
|
|
46
|
+
specifiers.add(spec);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const resolved = [];
|
|
51
|
+
const dir = dirname(filePath);
|
|
52
|
+
for (const spec of specifiers) {
|
|
53
|
+
const target = resolveImport(dir, spec, root, fileSet);
|
|
54
|
+
if (target) {
|
|
55
|
+
resolved.push(target);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return resolved;
|
|
59
|
+
}
|
|
60
|
+
function isRelative(spec) {
|
|
61
|
+
return spec.startsWith('./') || spec.startsWith('../');
|
|
62
|
+
}
|
|
63
|
+
function resolveImport(fromDir, spec, _root, fileSet) {
|
|
64
|
+
// Resolve relative path
|
|
65
|
+
const base = join(fromDir, spec).replace(/\\/g, '/');
|
|
66
|
+
// Try exact match first
|
|
67
|
+
if (fileSet.has(base))
|
|
68
|
+
return base;
|
|
69
|
+
// Try common extensions
|
|
70
|
+
const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
|
|
71
|
+
// Strip .js extension if present (ESM convention)
|
|
72
|
+
const withoutJs = base.endsWith('.js') ? base.slice(0, -3) : null;
|
|
73
|
+
if (withoutJs) {
|
|
74
|
+
for (const ext of extensions) {
|
|
75
|
+
const candidate = withoutJs + ext;
|
|
76
|
+
if (fileSet.has(candidate))
|
|
77
|
+
return candidate;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Try adding extensions
|
|
81
|
+
for (const ext of extensions) {
|
|
82
|
+
const candidate = base + ext;
|
|
83
|
+
if (fileSet.has(candidate))
|
|
84
|
+
return candidate;
|
|
85
|
+
}
|
|
86
|
+
// Try index files
|
|
87
|
+
for (const ext of extensions) {
|
|
88
|
+
const candidate = base + '/index' + ext;
|
|
89
|
+
if (fileSet.has(candidate))
|
|
90
|
+
return candidate;
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=dep-graph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dep-graph.js","sourceRoot":"","sources":["../src/dep-graph.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { scanFiles } from './scanner.js';
|
|
2
|
+
export { buildDepGraph } from './dep-graph.js';
|
|
3
|
+
export { analyzeChurn } from './churn.js';
|
|
4
|
+
export { buildTestMap } from './test-map.js';
|
|
5
|
+
export { buildRepoIndex } from './indexer.js';
|
|
6
|
+
export type { IndexerOptions } from './indexer.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RepoConfig, RepoIndex } from '@codeledger/types';
|
|
2
|
+
export interface IndexerOptions {
|
|
3
|
+
root: string;
|
|
4
|
+
repoConfig: RepoConfig;
|
|
5
|
+
outputPath: string;
|
|
6
|
+
churnDays?: number;
|
|
7
|
+
/** Optional logger — defaults to console.log. Pass a stderr-based logger
|
|
8
|
+
* (or a no-op) to keep stdout clean for --json / --quiet modes. */
|
|
9
|
+
log?: (...args: unknown[]) => void;
|
|
10
|
+
}
|
|
11
|
+
export declare function buildRepoIndex(opts: IndexerOptions): RepoIndex;
|
|
12
|
+
//# sourceMappingURL=indexer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexer.d.ts","sourceRoot":"","sources":["../src/indexer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAM/D,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,UAAU,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;wEACoE;IACpE,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;CACpC;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,cAAc,GAAG,SAAS,CA6D9D"}
|
package/dist/indexer.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { scanFiles } from './scanner.js';
|
|
4
|
+
import { buildDepGraph } from './dep-graph.js';
|
|
5
|
+
import { analyzeChurn } from './churn.js';
|
|
6
|
+
import { buildTestMap } from './test-map.js';
|
|
7
|
+
export function buildRepoIndex(opts) {
|
|
8
|
+
const { root, repoConfig, outputPath, churnDays = 90, log = console.log.bind(console) } = opts;
|
|
9
|
+
// Content cache: scanner reads each file once, dep-graph and test-map
|
|
10
|
+
// reuse the cached content instead of re-reading from disk.
|
|
11
|
+
const contentCache = new Map();
|
|
12
|
+
// Step 1: Scan files
|
|
13
|
+
log(' Scanning files...');
|
|
14
|
+
const files = scanFiles(root, repoConfig, contentCache);
|
|
15
|
+
log(` Found ${files.length} files`);
|
|
16
|
+
// Step 2: Build dependency graph
|
|
17
|
+
log(' Building dependency graph...');
|
|
18
|
+
const depGraph = buildDepGraph(root, files, contentCache);
|
|
19
|
+
const totalImports = Object.values(depGraph.imports).reduce((sum, deps) => sum + deps.length, 0);
|
|
20
|
+
log(` Resolved ${totalImports} import relationships`);
|
|
21
|
+
// Step 3: Analyze git churn
|
|
22
|
+
log(' Analyzing git history...');
|
|
23
|
+
const churn = analyzeChurn(root, churnDays);
|
|
24
|
+
log(` Found churn data for ${churn.length} files`);
|
|
25
|
+
// Step 4: Build test map
|
|
26
|
+
log(' Mapping tests to source...');
|
|
27
|
+
const testMap = buildTestMap(root, files, contentCache);
|
|
28
|
+
const testCount = files.filter((f) => f.is_test).length;
|
|
29
|
+
log(` Mapped ${testMap.length} test↔source pairs (${testCount} test files)`);
|
|
30
|
+
// Step 5: Detect languages
|
|
31
|
+
const langCounts = new Map();
|
|
32
|
+
for (const f of files) {
|
|
33
|
+
langCounts.set(f.language, (langCounts.get(f.language) ?? 0) + 1);
|
|
34
|
+
}
|
|
35
|
+
const langSummary = Array.from(langCounts.entries())
|
|
36
|
+
.sort((a, b) => b[1] - a[1])
|
|
37
|
+
.map(([lang, count]) => `${lang}(${count})`)
|
|
38
|
+
.join(', ');
|
|
39
|
+
log(` Languages: ${langSummary}`);
|
|
40
|
+
const index = {
|
|
41
|
+
generated_at: new Date().toISOString(),
|
|
42
|
+
root,
|
|
43
|
+
files,
|
|
44
|
+
dep_graph: depGraph,
|
|
45
|
+
churn,
|
|
46
|
+
test_map: testMap,
|
|
47
|
+
};
|
|
48
|
+
// Write to disk
|
|
49
|
+
const outputDir = dirname(outputPath);
|
|
50
|
+
if (!existsSync(outputDir)) {
|
|
51
|
+
mkdirSync(outputDir, { recursive: true });
|
|
52
|
+
}
|
|
53
|
+
writeFileSync(outputPath, JSON.stringify(index, null, 2) + '\n');
|
|
54
|
+
log(` ✅ Repo index written to ${outputPath}`);
|
|
55
|
+
return index;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=indexer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexer.js","sourceRoot":"","sources":["../src/indexer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAG9D,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,UAAU,EAClB,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACjC,QAAQ,EAAE,CAuCZ"}
|
package/dist/scanner.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { extname, relative } from 'node:path';
|
|
3
|
+
import { walkFiles } from './walk.js';
|
|
4
|
+
export function scanFiles(root, config, contentCache) {
|
|
5
|
+
const allPaths = walkFiles(root, config.include, config.exclude);
|
|
6
|
+
const files = [];
|
|
7
|
+
for (const absPath of allPaths) {
|
|
8
|
+
const rel = relative(root, absPath);
|
|
9
|
+
const ext = extname(rel);
|
|
10
|
+
const language = config.language_extensions[ext] ?? 'unknown';
|
|
11
|
+
const isTest = config.test_patterns.some((pat) => matchGlob(rel, pat));
|
|
12
|
+
let lines = 0;
|
|
13
|
+
let size = 0;
|
|
14
|
+
let content = '';
|
|
15
|
+
try {
|
|
16
|
+
size = statSync(absPath).size;
|
|
17
|
+
// Skip files larger than 10MB to prevent OOM
|
|
18
|
+
if (size > 10 * 1024 * 1024)
|
|
19
|
+
continue;
|
|
20
|
+
content = readFileSync(absPath, 'utf-8');
|
|
21
|
+
// Skip binary files: null bytes in the first 8KB indicate non-text content
|
|
22
|
+
if (content.length > 0 && content.slice(0, 8192).includes('\0'))
|
|
23
|
+
continue;
|
|
24
|
+
lines = content.split('\n').length;
|
|
25
|
+
contentCache?.set(rel, content);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// Skip unreadable files
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
files.push({
|
|
32
|
+
path: rel,
|
|
33
|
+
language,
|
|
34
|
+
extension: ext,
|
|
35
|
+
lines,
|
|
36
|
+
size,
|
|
37
|
+
is_test: isTest,
|
|
38
|
+
content_keywords: extractContentKeywords(content),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return files;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Extract meaningful identifiers from file content for content-aware matching.
|
|
45
|
+
*
|
|
46
|
+
* Extracts from:
|
|
47
|
+
* - Export signatures (exported functions, classes, interfaces, types, consts)
|
|
48
|
+
* - The first ~50 lines (imports, config keys, top-level declarations)
|
|
49
|
+
* - JSON keys (for package.json, config files)
|
|
50
|
+
*
|
|
51
|
+
* Returns deduplicated lowercase tokens (length > 2) capped at 50 entries.
|
|
52
|
+
*/
|
|
53
|
+
function extractContentKeywords(content) {
|
|
54
|
+
const tokens = new Set();
|
|
55
|
+
const lines = content.split('\n');
|
|
56
|
+
// Regex for identifiers in export/function/class/interface/const declarations
|
|
57
|
+
const DECL_RE = /(?:export\s+)?(?:async\s+)?(?:function|class|interface|type|const|enum|let|var)\s+([A-Za-z_$][A-Za-z0-9_$]*)/g;
|
|
58
|
+
// Regex for JSON-like keys: "key": or key:
|
|
59
|
+
const JSON_KEY_RE = /["']?([a-zA-Z_][a-zA-Z0-9_]*)["']?\s*:/g;
|
|
60
|
+
// Scan the first 50 lines for identifiers + all export lines
|
|
61
|
+
for (let i = 0; i < lines.length; i++) {
|
|
62
|
+
const line = lines[i];
|
|
63
|
+
const isHead = i < 50;
|
|
64
|
+
const isExport = line.trimStart().startsWith('export ');
|
|
65
|
+
if (!isHead && !isExport)
|
|
66
|
+
continue;
|
|
67
|
+
// Extract declaration names
|
|
68
|
+
let m;
|
|
69
|
+
DECL_RE.lastIndex = 0;
|
|
70
|
+
while ((m = DECL_RE.exec(line)) !== null) {
|
|
71
|
+
addIdentifierTokens(m[1], tokens);
|
|
72
|
+
}
|
|
73
|
+
// Extract JSON/object keys from head section
|
|
74
|
+
if (isHead) {
|
|
75
|
+
JSON_KEY_RE.lastIndex = 0;
|
|
76
|
+
while ((m = JSON_KEY_RE.exec(line)) !== null) {
|
|
77
|
+
addIdentifierTokens(m[1], tokens);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Cap at 50 to keep index size reasonable
|
|
82
|
+
return [...tokens].slice(0, 50);
|
|
83
|
+
}
|
|
84
|
+
/** Split camelCase/PascalCase/snake_case into lowercase tokens and add those > 2 chars. */
|
|
85
|
+
function addIdentifierTokens(name, tokens) {
|
|
86
|
+
// Split camelCase/PascalCase: "feedbackService" -> ["feedback", "service"]
|
|
87
|
+
const parts = name
|
|
88
|
+
.replace(/([a-z])([A-Z])/g, '$1_$2')
|
|
89
|
+
.toLowerCase()
|
|
90
|
+
.split(/[_\-]+/)
|
|
91
|
+
.filter((p) => p.length > 2);
|
|
92
|
+
for (const part of parts) {
|
|
93
|
+
tokens.add(part);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function matchGlob(filePath, pattern) {
|
|
97
|
+
// Convert glob to regex — escape special regex chars first, then replace glob tokens
|
|
98
|
+
const regexStr = pattern
|
|
99
|
+
.replace(/\*\*/g, '<<<GLOBSTAR>>>')
|
|
100
|
+
.replace(/\*/g, '<<<STAR>>>')
|
|
101
|
+
.replace(/\?/g, '<<<QUESTION>>>')
|
|
102
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
103
|
+
.replace(/<<<GLOBSTAR>>>/g, '.*')
|
|
104
|
+
.replace(/<<<STAR>>>/g, '[^/]*')
|
|
105
|
+
.replace(/<<<QUESTION>>>/g, '.');
|
|
106
|
+
const regex = new RegExp(`^${regexStr}$`);
|
|
107
|
+
return regex.test(filePath);
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.js","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-map.d.ts","sourceRoot":"","sources":["../src/test-map.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE/D,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,WAAW,EAAE,CAqB/G"}
|
package/dist/test-map.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { basename, dirname, join } from 'node:path';
|
|
3
|
+
export function buildTestMap(root, files, contentCache) {
|
|
4
|
+
const mappings = [];
|
|
5
|
+
const sourceSet = new Set(files.filter((f) => !f.is_test).map((f) => f.path));
|
|
6
|
+
const testFiles = files.filter((f) => f.is_test);
|
|
7
|
+
for (const testFile of testFiles) {
|
|
8
|
+
// Strategy 1: Convention-based mapping
|
|
9
|
+
const conventionSource = conventionMatch(testFile.path, sourceSet);
|
|
10
|
+
if (conventionSource) {
|
|
11
|
+
mappings.push({ test_file: testFile.path, source_file: conventionSource });
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
// Strategy 2: Import-based mapping
|
|
15
|
+
const importSources = importMatch(root, testFile.path, sourceSet, contentCache);
|
|
16
|
+
for (const source of importSources) {
|
|
17
|
+
mappings.push({ test_file: testFile.path, source_file: source });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return mappings;
|
|
21
|
+
}
|
|
22
|
+
function conventionMatch(testPath, sourceSet) {
|
|
23
|
+
const base = basename(testPath);
|
|
24
|
+
const dir = dirname(testPath);
|
|
25
|
+
// foo.test.ts → foo.ts, foo.spec.ts → foo.ts
|
|
26
|
+
const sourceBase = base
|
|
27
|
+
.replace(/\.test\./, '.')
|
|
28
|
+
.replace(/\.spec\./, '.');
|
|
29
|
+
// Check same directory
|
|
30
|
+
const sameDirCandidate = join(dir, sourceBase);
|
|
31
|
+
if (sourceSet.has(sameDirCandidate)) {
|
|
32
|
+
return sameDirCandidate;
|
|
33
|
+
}
|
|
34
|
+
// Check __tests__ convention: __tests__/foo.test.ts → ../foo.ts
|
|
35
|
+
if (dir.includes('__tests__')) {
|
|
36
|
+
const parentDir = dir.replace(/__tests__\/?/, '');
|
|
37
|
+
const parentCandidate = join(parentDir, sourceBase);
|
|
38
|
+
if (sourceSet.has(parentCandidate)) {
|
|
39
|
+
return parentCandidate;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Check test/ convention: test/foo.test.ts → src/foo.ts
|
|
43
|
+
if (dir.startsWith('test/') || dir.startsWith('test\\')) {
|
|
44
|
+
const srcDir = 'src/' + dir.slice(5);
|
|
45
|
+
const srcCandidate = join(srcDir, sourceBase);
|
|
46
|
+
if (sourceSet.has(srcCandidate)) {
|
|
47
|
+
return srcCandidate;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
function importMatch(root, testPath, sourceSet, contentCache) {
|
|
53
|
+
let content;
|
|
54
|
+
const cached = contentCache?.get(testPath);
|
|
55
|
+
if (cached !== undefined) {
|
|
56
|
+
content = cached;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
const absPath = join(root, testPath);
|
|
60
|
+
try {
|
|
61
|
+
content = readFileSync(absPath, 'utf-8');
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const importRe = /(?:import|export)\s+(?:[\s\S]*?\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
68
|
+
const sources = [];
|
|
69
|
+
let match;
|
|
70
|
+
while ((match = importRe.exec(content)) !== null) {
|
|
71
|
+
const spec = match[1];
|
|
72
|
+
if (!spec || !spec.startsWith('.'))
|
|
73
|
+
continue;
|
|
74
|
+
const resolved = resolveTestImport(dirname(testPath), spec, sourceSet);
|
|
75
|
+
if (resolved) {
|
|
76
|
+
sources.push(resolved);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return sources;
|
|
80
|
+
}
|
|
81
|
+
function resolveTestImport(fromDir, spec, sourceSet) {
|
|
82
|
+
const base = join(fromDir, spec).replace(/\\/g, '/');
|
|
83
|
+
if (sourceSet.has(base))
|
|
84
|
+
return base;
|
|
85
|
+
const extensions = ['.ts', '.tsx', '.js', '.jsx'];
|
|
86
|
+
const withoutJs = base.endsWith('.js') ? base.slice(0, -3) : null;
|
|
87
|
+
if (withoutJs) {
|
|
88
|
+
for (const ext of extensions) {
|
|
89
|
+
if (sourceSet.has(withoutJs + ext))
|
|
90
|
+
return withoutJs + ext;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
for (const ext of extensions) {
|
|
94
|
+
if (sourceSet.has(base + ext))
|
|
95
|
+
return base + ext;
|
|
96
|
+
}
|
|
97
|
+
for (const ext of extensions) {
|
|
98
|
+
if (sourceSet.has(base + '/index' + ext))
|
|
99
|
+
return base + '/index' + ext;
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=test-map.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-map.js","sourceRoot":"","sources":["../src/test-map.ts"],"names":[],"mappings":"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"}
|
package/dist/walk.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"walk.d.ts","sourceRoot":"","sources":["../src/walk.ts"],"names":[],"mappings":"AAQA,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EAAE,EACtB,YAAY,EAAE,MAAM,EAAE,GACrB,MAAM,EAAE,CAQV"}
|
package/dist/walk.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { readdirSync, readFileSync, statSync, existsSync } from 'node:fs';
|
|
2
|
+
import { join, relative } from 'node:path';
|
|
3
|
+
/** Directories that are always skipped regardless of config. */
|
|
4
|
+
const ALWAYS_SKIP = new Set([
|
|
5
|
+
'node_modules', '.git', 'dist', 'build', '.codeledger',
|
|
6
|
+
]);
|
|
7
|
+
export function walkFiles(root, includeGlobs, excludeGlobs) {
|
|
8
|
+
// Parse root .gitignore patterns and merge with config excludes
|
|
9
|
+
const { excludes: gitExcludes, negations } = parseGitignore(root);
|
|
10
|
+
const allExcludes = [...excludeGlobs, ...gitExcludes];
|
|
11
|
+
const results = [];
|
|
12
|
+
walk(root, root, includeGlobs, allExcludes, negations, results);
|
|
13
|
+
return results;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Parse a .gitignore file and return glob patterns compatible with our
|
|
17
|
+
* exclude matching. Only handles the most common patterns -- enough to
|
|
18
|
+
* prevent scanning vendored/generated directories that users have
|
|
19
|
+
* already told git to ignore.
|
|
20
|
+
*
|
|
21
|
+
* Negation patterns (lines starting with !) are returned separately so
|
|
22
|
+
* the walker can un-ignore files that match them.
|
|
23
|
+
*/
|
|
24
|
+
function parseGitignore(root) {
|
|
25
|
+
const gitignorePath = join(root, '.gitignore');
|
|
26
|
+
if (!existsSync(gitignorePath))
|
|
27
|
+
return { excludes: [], negations: [] };
|
|
28
|
+
try {
|
|
29
|
+
const content = readFileSync(gitignorePath, 'utf-8');
|
|
30
|
+
const excludes = [];
|
|
31
|
+
const negations = [];
|
|
32
|
+
for (const rawLine of content.split('\n')) {
|
|
33
|
+
const line = rawLine.trim();
|
|
34
|
+
// Skip empty lines and comments
|
|
35
|
+
if (!line || line.startsWith('#'))
|
|
36
|
+
continue;
|
|
37
|
+
// Detect negation patterns: strip the `!` and normalize like
|
|
38
|
+
// any other pattern, but collect into the negations list.
|
|
39
|
+
const isNegation = line.startsWith('!');
|
|
40
|
+
const target = isNegation ? negations : excludes;
|
|
41
|
+
let pat = isNegation ? line.slice(1) : line;
|
|
42
|
+
// Skip bare negation operator with no pattern (e.g. a lone "!")
|
|
43
|
+
if (!pat)
|
|
44
|
+
continue;
|
|
45
|
+
// Normalize patterns to match our full-relative-path glob matcher.
|
|
46
|
+
// Git behavior: patterns without '/' match against the filename at any depth.
|
|
47
|
+
if (pat.endsWith('/')) {
|
|
48
|
+
// Explicit directory: "dirname/" -> "dirname/**"
|
|
49
|
+
pat = pat.slice(0, -1) + '/**';
|
|
50
|
+
}
|
|
51
|
+
else if (!pat.includes('/')) {
|
|
52
|
+
// No slash: git matches against filename at any depth
|
|
53
|
+
if (!pat.includes('.') && !pat.includes('*')) {
|
|
54
|
+
// Bare name (e.g. Makefile, LICENSE, Dockerfile): could be file OR dir
|
|
55
|
+
target.push(pat); // root file match
|
|
56
|
+
target.push('**/' + pat); // file at any depth
|
|
57
|
+
target.push(pat + '/**'); // root dir contents
|
|
58
|
+
pat = '**/' + pat + '/**'; // dir contents at any depth
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// Has extension or glob (e.g. .env, *.log, .DS_Store)
|
|
62
|
+
target.push(pat); // root match
|
|
63
|
+
pat = '**/' + pat; // match at any depth
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Patterns with a path separator are path-anchored, used as-is
|
|
67
|
+
target.push(pat);
|
|
68
|
+
}
|
|
69
|
+
return { excludes, negations };
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return { excludes: [], negations: [] };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function walk(base, dir, includeGlobs, excludeGlobs, negationGlobs, results) {
|
|
76
|
+
let entries;
|
|
77
|
+
try {
|
|
78
|
+
entries = readdirSync(dir);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
for (const entry of entries) {
|
|
84
|
+
const fullPath = join(dir, entry);
|
|
85
|
+
const rel = relative(base, fullPath);
|
|
86
|
+
// Quick skip for always-excluded directories
|
|
87
|
+
if (ALWAYS_SKIP.has(entry)) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
let stat;
|
|
91
|
+
try {
|
|
92
|
+
stat = statSync(fullPath);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (stat.isDirectory()) {
|
|
98
|
+
// Skip nested repositories -- a directory with its own .git is a
|
|
99
|
+
// separate project and should never be indexed as part of this repo.
|
|
100
|
+
if (existsSync(join(fullPath, '.git'))) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
// Check if directory matches an exclude pattern (unless negated)
|
|
104
|
+
const dirRel = rel + '/';
|
|
105
|
+
const excluded = excludeGlobs.some((g) => matchSimple(dirRel, g))
|
|
106
|
+
&& !negationGlobs.some((g) => matchSimple(dirRel, g));
|
|
107
|
+
if (!excluded) {
|
|
108
|
+
walk(base, fullPath, includeGlobs, excludeGlobs, negationGlobs, results);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else if (stat.isFile()) {
|
|
112
|
+
const excluded = excludeGlobs.some((g) => matchSimple(rel, g))
|
|
113
|
+
&& !negationGlobs.some((g) => matchSimple(rel, g));
|
|
114
|
+
if (excluded)
|
|
115
|
+
continue;
|
|
116
|
+
const included = includeGlobs.some((g) => matchSimple(rel, g));
|
|
117
|
+
if (included) {
|
|
118
|
+
results.push(fullPath);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function matchSimple(path, pattern) {
|
|
124
|
+
const regexStr = pattern
|
|
125
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
126
|
+
.replace(/\*\*/g, '<<<GLOBSTAR>>>')
|
|
127
|
+
.replace(/\*/g, '[^/]*')
|
|
128
|
+
.replace(/<<<GLOBSTAR>>>/g, '.*')
|
|
129
|
+
.replace(/\?/g, '.');
|
|
130
|
+
return new RegExp(`^${regexStr}$`).test(path);
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=walk.js.map
|
package/dist/walk.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"walk.js","sourceRoot":"","sources":["../src/walk.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@codeledger/repo",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Repository scanning and indexing for CodeLedger",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/codeledgerECF/codeledger-blackbox.git",
|
|
10
|
+
"directory": "packages/repo"
|
|
11
|
+
},
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"main": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@codeledger/types": "0.1.1"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"typescript": "^5.4.0"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc",
|
|
34
|
+
"typecheck": "tsc --noEmit",
|
|
35
|
+
"clean": "rm -rf dist"
|
|
36
|
+
}
|
|
37
|
+
}
|