briefed 0.1.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 +21 -0
- package/README.md +118 -0
- package/dist/bench/metrics.d.ts +31 -0
- package/dist/bench/metrics.js +122 -0
- package/dist/bench/metrics.js.map +1 -0
- package/dist/bench/runner.d.ts +27 -0
- package/dist/bench/runner.js +184 -0
- package/dist/bench/runner.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +42 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/bench.d.ts +11 -0
- package/dist/commands/bench.js +23 -0
- package/dist/commands/bench.js.map +1 -0
- package/dist/commands/doctor.d.ts +5 -0
- package/dist/commands/doctor.js +246 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/init.d.ts +8 -0
- package/dist/commands/init.js +319 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/stats.d.ts +5 -0
- package/dist/commands/stats.js +87 -0
- package/dist/commands/stats.js.map +1 -0
- package/dist/deliver/ci.d.ts +4 -0
- package/dist/deliver/ci.js +62 -0
- package/dist/deliver/ci.js.map +1 -0
- package/dist/deliver/claudemd.d.ts +9 -0
- package/dist/deliver/claudemd.js +51 -0
- package/dist/deliver/claudemd.js.map +1 -0
- package/dist/deliver/cross-tool.d.ts +10 -0
- package/dist/deliver/cross-tool.js +49 -0
- package/dist/deliver/cross-tool.js.map +1 -0
- package/dist/deliver/git-hook.d.ts +10 -0
- package/dist/deliver/git-hook.js +104 -0
- package/dist/deliver/git-hook.js.map +1 -0
- package/dist/deliver/hooks.d.ts +9 -0
- package/dist/deliver/hooks.js +315 -0
- package/dist/deliver/hooks.js.map +1 -0
- package/dist/extract/complexity.d.ts +16 -0
- package/dist/extract/complexity.js +46 -0
- package/dist/extract/complexity.js.map +1 -0
- package/dist/extract/conventions.d.ts +18 -0
- package/dist/extract/conventions.js +143 -0
- package/dist/extract/conventions.js.map +1 -0
- package/dist/extract/depgraph.d.ts +12 -0
- package/dist/extract/depgraph.js +70 -0
- package/dist/extract/depgraph.js.map +1 -0
- package/dist/extract/env.d.ts +17 -0
- package/dist/extract/env.js +142 -0
- package/dist/extract/env.js.map +1 -0
- package/dist/extract/error-patterns.d.ts +15 -0
- package/dist/extract/error-patterns.js +107 -0
- package/dist/extract/error-patterns.js.map +1 -0
- package/dist/extract/frontend.d.ts +30 -0
- package/dist/extract/frontend.js +244 -0
- package/dist/extract/frontend.js.map +1 -0
- package/dist/extract/gotchas.d.ts +12 -0
- package/dist/extract/gotchas.js +145 -0
- package/dist/extract/gotchas.js.map +1 -0
- package/dist/extract/history.d.ts +29 -0
- package/dist/extract/history.js +91 -0
- package/dist/extract/history.js.map +1 -0
- package/dist/extract/infra.d.ts +27 -0
- package/dist/extract/infra.js +226 -0
- package/dist/extract/infra.js.map +1 -0
- package/dist/extract/monorepo.d.ts +16 -0
- package/dist/extract/monorepo.js +135 -0
- package/dist/extract/monorepo.js.map +1 -0
- package/dist/extract/routes.d.ts +16 -0
- package/dist/extract/routes.js +156 -0
- package/dist/extract/routes.js.map +1 -0
- package/dist/extract/scanner.d.ts +18 -0
- package/dist/extract/scanner.js +109 -0
- package/dist/extract/scanner.js.map +1 -0
- package/dist/extract/schema.d.ts +28 -0
- package/dist/extract/schema.js +192 -0
- package/dist/extract/schema.js.map +1 -0
- package/dist/extract/scripts.d.ts +18 -0
- package/dist/extract/scripts.js +104 -0
- package/dist/extract/scripts.js.map +1 -0
- package/dist/extract/security.d.ts +20 -0
- package/dist/extract/security.js +95 -0
- package/dist/extract/security.js.map +1 -0
- package/dist/extract/signatures.d.ts +33 -0
- package/dist/extract/signatures.js +608 -0
- package/dist/extract/signatures.js.map +1 -0
- package/dist/extract/staleness.d.ts +16 -0
- package/dist/extract/staleness.js +108 -0
- package/dist/extract/staleness.js.map +1 -0
- package/dist/extract/tests.d.ts +16 -0
- package/dist/extract/tests.js +175 -0
- package/dist/extract/tests.js.map +1 -0
- package/dist/extract/usage-examples.d.ts +17 -0
- package/dist/extract/usage-examples.js +115 -0
- package/dist/extract/usage-examples.js.map +1 -0
- package/dist/generate/index-file.d.ts +29 -0
- package/dist/generate/index-file.js +168 -0
- package/dist/generate/index-file.js.map +1 -0
- package/dist/generate/rules.d.ts +6 -0
- package/dist/generate/rules.js +94 -0
- package/dist/generate/rules.js.map +1 -0
- package/dist/generate/skeleton.d.ts +14 -0
- package/dist/generate/skeleton.js +145 -0
- package/dist/generate/skeleton.js.map +1 -0
- package/dist/learn/tracker.d.ts +54 -0
- package/dist/learn/tracker.js +129 -0
- package/dist/learn/tracker.js.map +1 -0
- package/dist/utils/detect.d.ts +16 -0
- package/dist/utils/detect.js +188 -0
- package/dist/utils/detect.js.map +1 -0
- package/dist/utils/pagerank.d.ts +19 -0
- package/dist/utils/pagerank.js +52 -0
- package/dist/utils/pagerank.js.map +1 -0
- package/dist/utils/tokens.d.ts +11 -0
- package/dist/utils/tokens.js +27 -0
- package/dist/utils/tokens.js.map +1 -0
- package/package.json +43 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { execSync } from "child_process";
|
|
4
|
+
/**
|
|
5
|
+
* Check if the briefed context is stale (source files changed since last index).
|
|
6
|
+
*/
|
|
7
|
+
export function checkStaleness(root) {
|
|
8
|
+
const report = {
|
|
9
|
+
isStale: false,
|
|
10
|
+
lastIndexed: null,
|
|
11
|
+
changedFiles: 0,
|
|
12
|
+
totalFiles: 0,
|
|
13
|
+
stalePct: 0,
|
|
14
|
+
details: [],
|
|
15
|
+
};
|
|
16
|
+
const indexPath = join(root, ".briefed", "index.json");
|
|
17
|
+
if (!existsSync(indexPath)) {
|
|
18
|
+
report.isStale = true;
|
|
19
|
+
report.details.push("No index found — run briefed init");
|
|
20
|
+
return report;
|
|
21
|
+
}
|
|
22
|
+
// Get last indexed time
|
|
23
|
+
const indexStat = statSync(indexPath);
|
|
24
|
+
report.lastIndexed = indexStat.mtime;
|
|
25
|
+
// Use git to find files changed since last index
|
|
26
|
+
try {
|
|
27
|
+
const sinceDate = indexStat.mtime.toISOString();
|
|
28
|
+
const changedOutput = execSync(`git diff --name-only --diff-filter=ACMR HEAD -- "*.ts" "*.tsx" "*.js" "*.jsx" "*.py" "*.go" "*.rs" "*.java"`, { cwd: root, encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
29
|
+
// Also check for uncommitted changes
|
|
30
|
+
const uncommitted = execSync(`git status --porcelain -- "*.ts" "*.tsx" "*.js" "*.jsx" "*.py" "*.go" "*.rs" "*.java"`, { cwd: root, encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
31
|
+
const changed = new Set();
|
|
32
|
+
if (changedOutput) {
|
|
33
|
+
for (const f of changedOutput.split("\n"))
|
|
34
|
+
changed.add(f.trim());
|
|
35
|
+
}
|
|
36
|
+
if (uncommitted) {
|
|
37
|
+
for (const line of uncommitted.split("\n")) {
|
|
38
|
+
const file = line.trim().slice(3); // strip status chars
|
|
39
|
+
if (file)
|
|
40
|
+
changed.add(file);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Check which changed files are newer than the index
|
|
44
|
+
let staleCount = 0;
|
|
45
|
+
for (const file of changed) {
|
|
46
|
+
const fullPath = join(root, file);
|
|
47
|
+
if (existsSync(fullPath)) {
|
|
48
|
+
const fileStat = statSync(fullPath);
|
|
49
|
+
if (fileStat.mtime > indexStat.mtime) {
|
|
50
|
+
staleCount++;
|
|
51
|
+
if (staleCount <= 5) {
|
|
52
|
+
report.details.push(` changed: ${file}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (staleCount > 5) {
|
|
58
|
+
report.details.push(` ... and ${staleCount - 5} more`);
|
|
59
|
+
}
|
|
60
|
+
report.changedFiles = staleCount;
|
|
61
|
+
// Load index to get total file count
|
|
62
|
+
try {
|
|
63
|
+
const index = JSON.parse(readFileSync(indexPath, "utf-8"));
|
|
64
|
+
report.totalFiles = index.modules?.reduce((sum, m) => sum + (m.files?.length || 0), 0) || 0;
|
|
65
|
+
}
|
|
66
|
+
catch { /* skip */ }
|
|
67
|
+
report.stalePct = report.totalFiles > 0
|
|
68
|
+
? Math.round((staleCount / report.totalFiles) * 100)
|
|
69
|
+
: 0;
|
|
70
|
+
// Consider stale if >10% of files changed or >5 files changed
|
|
71
|
+
report.isStale = staleCount > 5 || report.stalePct > 10;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// Not a git repo or git not available — check file timestamps
|
|
75
|
+
report.details.push("No git available — using timestamp comparison");
|
|
76
|
+
report.isStale = true;
|
|
77
|
+
}
|
|
78
|
+
return report;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Format staleness report for display.
|
|
82
|
+
*/
|
|
83
|
+
export function formatStaleness(report) {
|
|
84
|
+
if (!report.isStale) {
|
|
85
|
+
const age = report.lastIndexed
|
|
86
|
+
? timeSince(report.lastIndexed)
|
|
87
|
+
: "unknown";
|
|
88
|
+
return ` Context is fresh (indexed ${age} ago)`;
|
|
89
|
+
}
|
|
90
|
+
const lines = [];
|
|
91
|
+
lines.push(` Context is STALE — ${report.changedFiles} files changed since last index`);
|
|
92
|
+
for (const d of report.details) {
|
|
93
|
+
lines.push(d);
|
|
94
|
+
}
|
|
95
|
+
lines.push(` Run: npx briefed init`);
|
|
96
|
+
return lines.join("\n");
|
|
97
|
+
}
|
|
98
|
+
function timeSince(date) {
|
|
99
|
+
const secs = Math.floor((Date.now() - date.getTime()) / 1000);
|
|
100
|
+
if (secs < 60)
|
|
101
|
+
return `${secs}s`;
|
|
102
|
+
if (secs < 3600)
|
|
103
|
+
return `${Math.floor(secs / 60)}m`;
|
|
104
|
+
if (secs < 86400)
|
|
105
|
+
return `${Math.floor(secs / 3600)}h`;
|
|
106
|
+
return `${Math.floor(secs / 86400)}d`;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=staleness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"staleness.js","sourceRoot":"","sources":["../../src/extract/staleness.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface TestMapping {
|
|
2
|
+
sourceFile: string;
|
|
3
|
+
testFile: string;
|
|
4
|
+
testNames: string[];
|
|
5
|
+
testCount: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Find test files that correspond to source files.
|
|
9
|
+
* Uses naming conventions: foo.test.ts, foo.spec.ts, test_foo.py, etc.
|
|
10
|
+
* This is the #1 research-backed improvement: +45.97% pass@1 (TiCoder, IEEE TSE).
|
|
11
|
+
*/
|
|
12
|
+
export declare function findTestMappings(sourceFiles: string[], root: string): TestMapping[];
|
|
13
|
+
/**
|
|
14
|
+
* Format test mappings for inclusion in the skeleton or contracts.
|
|
15
|
+
*/
|
|
16
|
+
export declare function formatTestContext(mapping: TestMapping): string;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { join, dirname, basename, extname } from "path";
|
|
3
|
+
import { glob } from "glob";
|
|
4
|
+
/**
|
|
5
|
+
* Find test files that correspond to source files.
|
|
6
|
+
* Uses naming conventions: foo.test.ts, foo.spec.ts, test_foo.py, etc.
|
|
7
|
+
* This is the #1 research-backed improvement: +45.97% pass@1 (TiCoder, IEEE TSE).
|
|
8
|
+
*/
|
|
9
|
+
export function findTestMappings(sourceFiles, root) {
|
|
10
|
+
const mappings = [];
|
|
11
|
+
// Build a map of all test files for fast lookup
|
|
12
|
+
const testFileSet = new Set();
|
|
13
|
+
const allFiles = sourceFiles;
|
|
14
|
+
for (const f of allFiles) {
|
|
15
|
+
if (isTestFile(f))
|
|
16
|
+
testFileSet.add(f);
|
|
17
|
+
}
|
|
18
|
+
// Also scan for test files not in the source list (test/ directories)
|
|
19
|
+
try {
|
|
20
|
+
const testGlobs = [
|
|
21
|
+
"test/**/*.{ts,tsx,js,jsx,py,go,rs}",
|
|
22
|
+
"tests/**/*.{ts,tsx,js,jsx,py,go,rs}",
|
|
23
|
+
"**/*.test.{ts,tsx,js,jsx}",
|
|
24
|
+
"**/*.spec.{ts,tsx,js,jsx}",
|
|
25
|
+
"**/test_*.py",
|
|
26
|
+
"**/*_test.go",
|
|
27
|
+
"**/*_test.rs",
|
|
28
|
+
];
|
|
29
|
+
for (const pattern of testGlobs) {
|
|
30
|
+
const found = glob.sync(pattern, {
|
|
31
|
+
cwd: root,
|
|
32
|
+
ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
|
|
33
|
+
});
|
|
34
|
+
for (const f of found)
|
|
35
|
+
testFileSet.add(f.replace(/\\/g, "/"));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// glob errors are non-fatal
|
|
40
|
+
}
|
|
41
|
+
// Match source files to test files
|
|
42
|
+
for (const sourceFile of sourceFiles) {
|
|
43
|
+
if (isTestFile(sourceFile))
|
|
44
|
+
continue;
|
|
45
|
+
const matched = findMatchingTest(sourceFile, testFileSet);
|
|
46
|
+
if (matched) {
|
|
47
|
+
const testPath = join(root, matched);
|
|
48
|
+
let testNames = [];
|
|
49
|
+
let testCount = 0;
|
|
50
|
+
try {
|
|
51
|
+
const content = readFileSync(testPath, "utf-8");
|
|
52
|
+
const extracted = extractTestNames(content, extname(matched));
|
|
53
|
+
testNames = extracted.names;
|
|
54
|
+
testCount = extracted.count;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Can't read test file — still map it
|
|
58
|
+
}
|
|
59
|
+
mappings.push({
|
|
60
|
+
sourceFile,
|
|
61
|
+
testFile: matched,
|
|
62
|
+
testNames,
|
|
63
|
+
testCount,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return mappings;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Check if a file path looks like a test file.
|
|
71
|
+
*/
|
|
72
|
+
function isTestFile(filePath) {
|
|
73
|
+
const name = basename(filePath);
|
|
74
|
+
return (name.includes(".test.") ||
|
|
75
|
+
name.includes(".spec.") ||
|
|
76
|
+
name.includes("_test.") ||
|
|
77
|
+
name.startsWith("test_") ||
|
|
78
|
+
filePath.includes("/test/") ||
|
|
79
|
+
filePath.includes("/tests/") ||
|
|
80
|
+
filePath.includes("/__tests__/"));
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Find the test file that matches a given source file.
|
|
84
|
+
*/
|
|
85
|
+
function findMatchingTest(sourceFile, testFiles) {
|
|
86
|
+
const dir = dirname(sourceFile);
|
|
87
|
+
const ext = extname(sourceFile);
|
|
88
|
+
const name = basename(sourceFile, ext);
|
|
89
|
+
// Try common test file naming patterns
|
|
90
|
+
const candidates = [
|
|
91
|
+
// Colocated: foo.test.ts next to foo.ts
|
|
92
|
+
`${dir}/${name}.test${ext}`,
|
|
93
|
+
`${dir}/${name}.spec${ext}`,
|
|
94
|
+
// __tests__ directory
|
|
95
|
+
`${dir}/__tests__/${name}.test${ext}`,
|
|
96
|
+
`${dir}/__tests__/${name}${ext}`,
|
|
97
|
+
// test/ directory at same level
|
|
98
|
+
`${dir.replace(/\/src\//, "/test/")}/${name}.test${ext}`,
|
|
99
|
+
`${dir.replace(/\/src\//, "/test/")}/${name}${ext}`,
|
|
100
|
+
`${dir.replace(/\/src\//, "/tests/")}/${name}.test${ext}`,
|
|
101
|
+
// test/ at root
|
|
102
|
+
`test/${name}.test${ext}`,
|
|
103
|
+
`test/${name}${ext}`,
|
|
104
|
+
`tests/${name}.test${ext}`,
|
|
105
|
+
// Python conventions
|
|
106
|
+
`${dir}/test_${name}${ext}`,
|
|
107
|
+
`test/test_${name}${ext}`,
|
|
108
|
+
`tests/test_${name}${ext}`,
|
|
109
|
+
// Go conventions
|
|
110
|
+
`${dir}/${name}_test${ext}`,
|
|
111
|
+
// Rust conventions
|
|
112
|
+
`tests/${name}${ext}`,
|
|
113
|
+
];
|
|
114
|
+
for (const candidate of candidates) {
|
|
115
|
+
const normalized = candidate.replace(/\\/g, "/");
|
|
116
|
+
if (testFiles.has(normalized))
|
|
117
|
+
return normalized;
|
|
118
|
+
}
|
|
119
|
+
// Fuzzy match: look for any test file containing the source name
|
|
120
|
+
for (const testFile of testFiles) {
|
|
121
|
+
const testName = basename(testFile, extname(testFile))
|
|
122
|
+
.replace(/\.test$|\.spec$|^test_|_test$/, "");
|
|
123
|
+
if (testName === name)
|
|
124
|
+
return testFile;
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Extract test names (describe/it/test blocks) from a test file.
|
|
130
|
+
*/
|
|
131
|
+
function extractTestNames(content, ext) {
|
|
132
|
+
const names = [];
|
|
133
|
+
if ([".ts", ".tsx", ".js", ".jsx", ".mjs"].includes(ext)) {
|
|
134
|
+
// JS/TS: describe("name"), it("name"), test("name")
|
|
135
|
+
const regex = /(?:describe|it|test)\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
136
|
+
for (const match of content.matchAll(regex)) {
|
|
137
|
+
names.push(match[1]);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else if (ext === ".py") {
|
|
141
|
+
// Python: def test_name, class TestName
|
|
142
|
+
const fnRegex = /def\s+(test_\w+)/g;
|
|
143
|
+
const classRegex = /class\s+(Test\w+)/g;
|
|
144
|
+
for (const match of content.matchAll(fnRegex))
|
|
145
|
+
names.push(match[1]);
|
|
146
|
+
for (const match of content.matchAll(classRegex))
|
|
147
|
+
names.push(match[1]);
|
|
148
|
+
}
|
|
149
|
+
else if (ext === ".go") {
|
|
150
|
+
// Go: func TestName(t *testing.T)
|
|
151
|
+
const regex = /func\s+(Test\w+)\s*\(/g;
|
|
152
|
+
for (const match of content.matchAll(regex))
|
|
153
|
+
names.push(match[1]);
|
|
154
|
+
}
|
|
155
|
+
else if (ext === ".rs") {
|
|
156
|
+
// Rust: #[test] fn test_name()
|
|
157
|
+
const regex = /fn\s+(test_\w+)\s*\(/g;
|
|
158
|
+
for (const match of content.matchAll(regex))
|
|
159
|
+
names.push(match[1]);
|
|
160
|
+
}
|
|
161
|
+
return { names, count: names.length };
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Format test mappings for inclusion in the skeleton or contracts.
|
|
165
|
+
*/
|
|
166
|
+
export function formatTestContext(mapping) {
|
|
167
|
+
const lines = [];
|
|
168
|
+
lines.push(`tests: ${mapping.testFile} (${mapping.testCount} tests)`);
|
|
169
|
+
if (mapping.testNames.length > 0) {
|
|
170
|
+
const preview = mapping.testNames.slice(0, 8);
|
|
171
|
+
lines.push(` covers: ${preview.join(", ")}${mapping.testNames.length > 8 ? ` +${mapping.testNames.length - 8} more` : ""}`);
|
|
172
|
+
}
|
|
173
|
+
return lines.join("\n");
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=tests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tests.js","sourceRoot":"","sources":["../../src/extract/tests.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { FileExtraction } from "./signatures.js";
|
|
2
|
+
export interface UsageExample {
|
|
3
|
+
symbol: string;
|
|
4
|
+
file: string;
|
|
5
|
+
line: number;
|
|
6
|
+
snippet: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Find how functions/classes are actually USED in the codebase.
|
|
10
|
+
* Research shows API usage examples improve LLM output by 3x vs descriptions alone.
|
|
11
|
+
* Instead of telling Claude what a function does, show it how the project calls it.
|
|
12
|
+
*/
|
|
13
|
+
export declare function findUsageExamples(extractions: FileExtraction[], maxExamplesPerSymbol?: number): Map<string, UsageExample[]>;
|
|
14
|
+
/**
|
|
15
|
+
* Format usage examples for context injection.
|
|
16
|
+
*/
|
|
17
|
+
export declare function formatUsageExamples(examples: Map<string, UsageExample[]>, maxTotal?: number): string;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { basename } from "path";
|
|
3
|
+
/**
|
|
4
|
+
* Find how functions/classes are actually USED in the codebase.
|
|
5
|
+
* Research shows API usage examples improve LLM output by 3x vs descriptions alone.
|
|
6
|
+
* Instead of telling Claude what a function does, show it how the project calls it.
|
|
7
|
+
*/
|
|
8
|
+
export function findUsageExamples(extractions, maxExamplesPerSymbol = 3) {
|
|
9
|
+
const examples = new Map();
|
|
10
|
+
// Build a set of all exported symbol names worth tracking
|
|
11
|
+
const exportedSymbols = new Map(); // name → defining file
|
|
12
|
+
for (const ext of extractions) {
|
|
13
|
+
for (const sym of ext.symbols) {
|
|
14
|
+
if (sym.exported && (sym.kind === "function" || sym.kind === "class" || sym.kind === "method")) {
|
|
15
|
+
const shortName = sym.name.split(".").pop();
|
|
16
|
+
if (shortName.length > 2 && !isCommonName(shortName)) {
|
|
17
|
+
exportedSymbols.set(shortName, ext.path);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (exportedSymbols.size === 0)
|
|
23
|
+
return examples;
|
|
24
|
+
// Scan files for usages of these symbols
|
|
25
|
+
for (const ext of extractions) {
|
|
26
|
+
// Only look in files that import something
|
|
27
|
+
if (ext.imports.length === 0)
|
|
28
|
+
continue;
|
|
29
|
+
let content;
|
|
30
|
+
try {
|
|
31
|
+
content = readFileSync(ext.path, "utf-8");
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const lines = content.split("\n");
|
|
37
|
+
// What does this file import?
|
|
38
|
+
const importedNames = new Set();
|
|
39
|
+
for (const imp of ext.imports) {
|
|
40
|
+
for (const name of imp.names) {
|
|
41
|
+
importedNames.add(name);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Find lines where imported symbols are called/used
|
|
45
|
+
for (let i = 0; i < lines.length; i++) {
|
|
46
|
+
const line = lines[i].trim();
|
|
47
|
+
// Skip imports, comments, empty lines
|
|
48
|
+
if (!line || line.startsWith("import ") || line.startsWith("//") ||
|
|
49
|
+
line.startsWith("/*") || line.startsWith("*") || line.startsWith("from ") ||
|
|
50
|
+
line.startsWith("require("))
|
|
51
|
+
continue;
|
|
52
|
+
for (const symName of importedNames) {
|
|
53
|
+
if (!exportedSymbols.has(symName))
|
|
54
|
+
continue;
|
|
55
|
+
// Check if this line calls/uses the symbol
|
|
56
|
+
// Match: symName( or new SymName( or symName. or await symName(
|
|
57
|
+
const callPattern = new RegExp(`\\b${escapeRegex(symName)}\\s*[.(]`);
|
|
58
|
+
if (callPattern.test(line)) {
|
|
59
|
+
if (!examples.has(symName))
|
|
60
|
+
examples.set(symName, []);
|
|
61
|
+
const existing = examples.get(symName);
|
|
62
|
+
if (existing.length < maxExamplesPerSymbol) {
|
|
63
|
+
// Don't duplicate from same file
|
|
64
|
+
if (!existing.some((e) => e.file === ext.path)) {
|
|
65
|
+
existing.push({
|
|
66
|
+
symbol: symName,
|
|
67
|
+
file: ext.path,
|
|
68
|
+
line: i + 1,
|
|
69
|
+
snippet: line.slice(0, 120), // truncate long lines
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return examples;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Format usage examples for context injection.
|
|
81
|
+
*/
|
|
82
|
+
export function formatUsageExamples(examples, maxTotal = 20) {
|
|
83
|
+
if (examples.size === 0)
|
|
84
|
+
return "";
|
|
85
|
+
const lines = [];
|
|
86
|
+
let count = 0;
|
|
87
|
+
// Sort by number of usages (most-used first)
|
|
88
|
+
const sorted = [...examples.entries()]
|
|
89
|
+
.sort((a, b) => b[1].length - a[1].length);
|
|
90
|
+
for (const [symbol, usages] of sorted) {
|
|
91
|
+
if (count >= maxTotal)
|
|
92
|
+
break;
|
|
93
|
+
for (const usage of usages) {
|
|
94
|
+
if (count >= maxTotal)
|
|
95
|
+
break;
|
|
96
|
+
lines.push(` ${symbol}: ${usage.snippet} (${basename(usage.file)}:${usage.line})`);
|
|
97
|
+
count++;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (lines.length === 0)
|
|
101
|
+
return "";
|
|
102
|
+
return "Usage examples:\n" + lines.join("\n");
|
|
103
|
+
}
|
|
104
|
+
function isCommonName(name) {
|
|
105
|
+
const common = new Set([
|
|
106
|
+
"get", "set", "map", "filter", "reduce", "find", "push", "pop",
|
|
107
|
+
"log", "error", "warn", "info", "debug", "toString", "valueOf",
|
|
108
|
+
"then", "catch", "finally", "resolve", "reject", "use", "run",
|
|
109
|
+
]);
|
|
110
|
+
return common.has(name);
|
|
111
|
+
}
|
|
112
|
+
function escapeRegex(str) {
|
|
113
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=usage-examples.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage-examples.js","sourceRoot":"","sources":["../../src/extract/usage-examples.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { FileExtraction } from "../extract/signatures.js";
|
|
2
|
+
import type { DepGraph } from "../extract/depgraph.js";
|
|
3
|
+
import type { ComplexityScore } from "../extract/complexity.js";
|
|
4
|
+
export interface ModuleEntry {
|
|
5
|
+
name: string;
|
|
6
|
+
dir: string;
|
|
7
|
+
files: string[];
|
|
8
|
+
keywords: string[];
|
|
9
|
+
complexity: number;
|
|
10
|
+
file: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ModuleIndex {
|
|
13
|
+
modules: ModuleEntry[];
|
|
14
|
+
generated: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Group files into logical modules and generate an index for prompt matching.
|
|
18
|
+
* Modules are primarily directory-based with keyword extraction.
|
|
19
|
+
*/
|
|
20
|
+
export declare function generateModuleIndex(extractions: FileExtraction[], depGraph: DepGraph, complexity: ComplexityScore[], root: string): ModuleIndex;
|
|
21
|
+
/**
|
|
22
|
+
* Write the module index to .briefed/index.json
|
|
23
|
+
*/
|
|
24
|
+
export declare function writeModuleIndex(root: string, index: ModuleIndex): void;
|
|
25
|
+
/**
|
|
26
|
+
* Generate simple contract files for each module (non-LLM version).
|
|
27
|
+
* Extracts structural info: purpose, exports, deps, complexity.
|
|
28
|
+
*/
|
|
29
|
+
export declare function generateSimpleContracts(index: ModuleIndex, extractions: FileExtraction[], depGraph: DepGraph, root: string): void;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { dirname, basename } from "path";
|
|
2
|
+
import { writeFileSync, mkdirSync, existsSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import yaml from "js-yaml";
|
|
5
|
+
/**
|
|
6
|
+
* Group files into logical modules and generate an index for prompt matching.
|
|
7
|
+
* Modules are primarily directory-based with keyword extraction.
|
|
8
|
+
*/
|
|
9
|
+
export function generateModuleIndex(extractions, depGraph, complexity, root) {
|
|
10
|
+
// Group files by directory
|
|
11
|
+
const dirGroups = new Map();
|
|
12
|
+
for (const ext of extractions) {
|
|
13
|
+
const dir = dirname(ext.path);
|
|
14
|
+
if (!dirGroups.has(dir))
|
|
15
|
+
dirGroups.set(dir, []);
|
|
16
|
+
dirGroups.get(dir).push(ext);
|
|
17
|
+
}
|
|
18
|
+
const modules = [];
|
|
19
|
+
for (const [dir, files] of dirGroups) {
|
|
20
|
+
if (files.length === 0)
|
|
21
|
+
continue;
|
|
22
|
+
// Extract keywords from:
|
|
23
|
+
// 1. Directory name parts
|
|
24
|
+
// 2. File names
|
|
25
|
+
// 3. Exported symbol names
|
|
26
|
+
const keywords = new Set();
|
|
27
|
+
// Directory parts
|
|
28
|
+
for (const part of dir.split("/")) {
|
|
29
|
+
if (part && part.length > 2) {
|
|
30
|
+
keywords.add(part.toLowerCase());
|
|
31
|
+
// Split camelCase/PascalCase
|
|
32
|
+
for (const word of splitIdentifier(part)) {
|
|
33
|
+
if (word.length > 2)
|
|
34
|
+
keywords.add(word.toLowerCase());
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// File names (without extension)
|
|
39
|
+
for (const f of files) {
|
|
40
|
+
const name = basename(f.path).replace(/\.[^.]+$/, "");
|
|
41
|
+
keywords.add(name.toLowerCase());
|
|
42
|
+
for (const word of splitIdentifier(name)) {
|
|
43
|
+
if (word.length > 2)
|
|
44
|
+
keywords.add(word.toLowerCase());
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Exported symbol names
|
|
48
|
+
for (const f of files) {
|
|
49
|
+
for (const sym of f.symbols.filter((s) => s.exported)) {
|
|
50
|
+
const name = sym.name.split(".").pop();
|
|
51
|
+
keywords.add(name.toLowerCase());
|
|
52
|
+
for (const word of splitIdentifier(name)) {
|
|
53
|
+
if (word.length > 2)
|
|
54
|
+
keywords.add(word.toLowerCase());
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Remove very common/generic keywords
|
|
59
|
+
const genericWords = new Set([
|
|
60
|
+
"src", "lib", "app", "index", "utils", "helpers", "types",
|
|
61
|
+
"const", "config", "common", "shared", "core", "main",
|
|
62
|
+
]);
|
|
63
|
+
for (const gw of genericWords)
|
|
64
|
+
keywords.delete(gw);
|
|
65
|
+
// Compute average complexity
|
|
66
|
+
const fileComplexities = files
|
|
67
|
+
.map((f) => complexity.find((c) => c.file === f.path)?.score || 0);
|
|
68
|
+
const avgComplexity = fileComplexities.length > 0
|
|
69
|
+
? fileComplexities.reduce((s, v) => s + v, 0) / fileComplexities.length
|
|
70
|
+
: 0;
|
|
71
|
+
const safeName = dir.replace(/[\/\\]/g, "-").replace(/^-/, "") || "root";
|
|
72
|
+
modules.push({
|
|
73
|
+
name: safeName,
|
|
74
|
+
dir,
|
|
75
|
+
files: files.map((f) => f.path),
|
|
76
|
+
keywords: [...keywords],
|
|
77
|
+
complexity: Math.round(avgComplexity * 10) / 10,
|
|
78
|
+
file: `${safeName}.yaml`,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
// Sort by complexity (most complex first — they need context most)
|
|
82
|
+
modules.sort((a, b) => b.complexity - a.complexity);
|
|
83
|
+
return {
|
|
84
|
+
modules,
|
|
85
|
+
generated: new Date().toISOString(),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Write the module index to .briefed/index.json
|
|
90
|
+
*/
|
|
91
|
+
export function writeModuleIndex(root, index) {
|
|
92
|
+
const briefedDir = join(root, ".briefed");
|
|
93
|
+
if (!existsSync(briefedDir))
|
|
94
|
+
mkdirSync(briefedDir, { recursive: true });
|
|
95
|
+
writeFileSync(join(briefedDir, "index.json"), JSON.stringify(index, null, 2));
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Generate simple contract files for each module (non-LLM version).
|
|
99
|
+
* Extracts structural info: purpose, exports, deps, complexity.
|
|
100
|
+
*/
|
|
101
|
+
export function generateSimpleContracts(index, extractions, depGraph, root) {
|
|
102
|
+
const contractsDir = join(root, ".briefed", "contracts");
|
|
103
|
+
if (!existsSync(contractsDir))
|
|
104
|
+
mkdirSync(contractsDir, { recursive: true });
|
|
105
|
+
for (const mod of index.modules) {
|
|
106
|
+
const modExtractions = extractions.filter((e) => mod.files.includes(e.path));
|
|
107
|
+
const exports = modExtractions.flatMap((e) => e.symbols.filter((s) => s.exported));
|
|
108
|
+
// Get dependencies (other modules this one imports from)
|
|
109
|
+
const deps = new Set();
|
|
110
|
+
for (const ext of modExtractions) {
|
|
111
|
+
for (const imp of ext.imports) {
|
|
112
|
+
if (imp.isRelative) {
|
|
113
|
+
// Find which module this import belongs to
|
|
114
|
+
for (const otherMod of index.modules) {
|
|
115
|
+
if (otherMod.name === mod.name)
|
|
116
|
+
continue;
|
|
117
|
+
if (otherMod.files.some((f) => imp.source.includes(basename(f).replace(/\.[^.]+$/, "")))) {
|
|
118
|
+
deps.add(otherMod.name);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Get dependents (other modules that import from this one)
|
|
125
|
+
const dependents = new Set();
|
|
126
|
+
for (const otherMod of index.modules) {
|
|
127
|
+
if (otherMod.name === mod.name)
|
|
128
|
+
continue;
|
|
129
|
+
const otherExtractions = extractions.filter((e) => otherMod.files.includes(e.path));
|
|
130
|
+
for (const ext of otherExtractions) {
|
|
131
|
+
for (const imp of ext.imports) {
|
|
132
|
+
if (imp.isRelative && mod.files.some((f) => imp.source.includes(basename(f).replace(/\.[^.]+$/, "")))) {
|
|
133
|
+
dependents.add(otherMod.name);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const contract = {
|
|
139
|
+
module: mod.dir,
|
|
140
|
+
files: mod.files.length,
|
|
141
|
+
complexity: mod.complexity,
|
|
142
|
+
};
|
|
143
|
+
if (exports.length > 0) {
|
|
144
|
+
contract.exports = exports.slice(0, 15).map((s) => s.signature);
|
|
145
|
+
}
|
|
146
|
+
if (deps.size > 0) {
|
|
147
|
+
contract.dependencies = [...deps];
|
|
148
|
+
}
|
|
149
|
+
if (dependents.size > 0) {
|
|
150
|
+
contract.dependents = [...dependents];
|
|
151
|
+
}
|
|
152
|
+
const yamlContent = yaml.dump(contract, {
|
|
153
|
+
indent: 2,
|
|
154
|
+
lineWidth: 120,
|
|
155
|
+
noRefs: true,
|
|
156
|
+
});
|
|
157
|
+
writeFileSync(join(contractsDir, mod.file), yamlContent);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/** Split camelCase/PascalCase/snake_case identifiers into words */
|
|
161
|
+
function splitIdentifier(name) {
|
|
162
|
+
return name
|
|
163
|
+
.replace(/([a-z])([A-Z])/g, "$1 $2")
|
|
164
|
+
.replace(/[_-]/g, " ")
|
|
165
|
+
.split(/\s+/)
|
|
166
|
+
.filter((w) => w.length > 0);
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=index-file.js.map
|