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.
Files changed (117) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +118 -0
  3. package/dist/bench/metrics.d.ts +31 -0
  4. package/dist/bench/metrics.js +122 -0
  5. package/dist/bench/metrics.js.map +1 -0
  6. package/dist/bench/runner.d.ts +27 -0
  7. package/dist/bench/runner.js +184 -0
  8. package/dist/bench/runner.js.map +1 -0
  9. package/dist/cli.d.ts +2 -0
  10. package/dist/cli.js +42 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/commands/bench.d.ts +11 -0
  13. package/dist/commands/bench.js +23 -0
  14. package/dist/commands/bench.js.map +1 -0
  15. package/dist/commands/doctor.d.ts +5 -0
  16. package/dist/commands/doctor.js +246 -0
  17. package/dist/commands/doctor.js.map +1 -0
  18. package/dist/commands/init.d.ts +8 -0
  19. package/dist/commands/init.js +319 -0
  20. package/dist/commands/init.js.map +1 -0
  21. package/dist/commands/stats.d.ts +5 -0
  22. package/dist/commands/stats.js +87 -0
  23. package/dist/commands/stats.js.map +1 -0
  24. package/dist/deliver/ci.d.ts +4 -0
  25. package/dist/deliver/ci.js +62 -0
  26. package/dist/deliver/ci.js.map +1 -0
  27. package/dist/deliver/claudemd.d.ts +9 -0
  28. package/dist/deliver/claudemd.js +51 -0
  29. package/dist/deliver/claudemd.js.map +1 -0
  30. package/dist/deliver/cross-tool.d.ts +10 -0
  31. package/dist/deliver/cross-tool.js +49 -0
  32. package/dist/deliver/cross-tool.js.map +1 -0
  33. package/dist/deliver/git-hook.d.ts +10 -0
  34. package/dist/deliver/git-hook.js +104 -0
  35. package/dist/deliver/git-hook.js.map +1 -0
  36. package/dist/deliver/hooks.d.ts +9 -0
  37. package/dist/deliver/hooks.js +315 -0
  38. package/dist/deliver/hooks.js.map +1 -0
  39. package/dist/extract/complexity.d.ts +16 -0
  40. package/dist/extract/complexity.js +46 -0
  41. package/dist/extract/complexity.js.map +1 -0
  42. package/dist/extract/conventions.d.ts +18 -0
  43. package/dist/extract/conventions.js +143 -0
  44. package/dist/extract/conventions.js.map +1 -0
  45. package/dist/extract/depgraph.d.ts +12 -0
  46. package/dist/extract/depgraph.js +70 -0
  47. package/dist/extract/depgraph.js.map +1 -0
  48. package/dist/extract/env.d.ts +17 -0
  49. package/dist/extract/env.js +142 -0
  50. package/dist/extract/env.js.map +1 -0
  51. package/dist/extract/error-patterns.d.ts +15 -0
  52. package/dist/extract/error-patterns.js +107 -0
  53. package/dist/extract/error-patterns.js.map +1 -0
  54. package/dist/extract/frontend.d.ts +30 -0
  55. package/dist/extract/frontend.js +244 -0
  56. package/dist/extract/frontend.js.map +1 -0
  57. package/dist/extract/gotchas.d.ts +12 -0
  58. package/dist/extract/gotchas.js +145 -0
  59. package/dist/extract/gotchas.js.map +1 -0
  60. package/dist/extract/history.d.ts +29 -0
  61. package/dist/extract/history.js +91 -0
  62. package/dist/extract/history.js.map +1 -0
  63. package/dist/extract/infra.d.ts +27 -0
  64. package/dist/extract/infra.js +226 -0
  65. package/dist/extract/infra.js.map +1 -0
  66. package/dist/extract/monorepo.d.ts +16 -0
  67. package/dist/extract/monorepo.js +135 -0
  68. package/dist/extract/monorepo.js.map +1 -0
  69. package/dist/extract/routes.d.ts +16 -0
  70. package/dist/extract/routes.js +156 -0
  71. package/dist/extract/routes.js.map +1 -0
  72. package/dist/extract/scanner.d.ts +18 -0
  73. package/dist/extract/scanner.js +109 -0
  74. package/dist/extract/scanner.js.map +1 -0
  75. package/dist/extract/schema.d.ts +28 -0
  76. package/dist/extract/schema.js +192 -0
  77. package/dist/extract/schema.js.map +1 -0
  78. package/dist/extract/scripts.d.ts +18 -0
  79. package/dist/extract/scripts.js +104 -0
  80. package/dist/extract/scripts.js.map +1 -0
  81. package/dist/extract/security.d.ts +20 -0
  82. package/dist/extract/security.js +95 -0
  83. package/dist/extract/security.js.map +1 -0
  84. package/dist/extract/signatures.d.ts +33 -0
  85. package/dist/extract/signatures.js +608 -0
  86. package/dist/extract/signatures.js.map +1 -0
  87. package/dist/extract/staleness.d.ts +16 -0
  88. package/dist/extract/staleness.js +108 -0
  89. package/dist/extract/staleness.js.map +1 -0
  90. package/dist/extract/tests.d.ts +16 -0
  91. package/dist/extract/tests.js +175 -0
  92. package/dist/extract/tests.js.map +1 -0
  93. package/dist/extract/usage-examples.d.ts +17 -0
  94. package/dist/extract/usage-examples.js +115 -0
  95. package/dist/extract/usage-examples.js.map +1 -0
  96. package/dist/generate/index-file.d.ts +29 -0
  97. package/dist/generate/index-file.js +168 -0
  98. package/dist/generate/index-file.js.map +1 -0
  99. package/dist/generate/rules.d.ts +6 -0
  100. package/dist/generate/rules.js +94 -0
  101. package/dist/generate/rules.js.map +1 -0
  102. package/dist/generate/skeleton.d.ts +14 -0
  103. package/dist/generate/skeleton.js +145 -0
  104. package/dist/generate/skeleton.js.map +1 -0
  105. package/dist/learn/tracker.d.ts +54 -0
  106. package/dist/learn/tracker.js +129 -0
  107. package/dist/learn/tracker.js.map +1 -0
  108. package/dist/utils/detect.d.ts +16 -0
  109. package/dist/utils/detect.js +188 -0
  110. package/dist/utils/detect.js.map +1 -0
  111. package/dist/utils/pagerank.d.ts +19 -0
  112. package/dist/utils/pagerank.js +52 -0
  113. package/dist/utils/pagerank.js.map +1 -0
  114. package/dist/utils/tokens.d.ts +11 -0
  115. package/dist/utils/tokens.js +27 -0
  116. package/dist/utils/tokens.js.map +1 -0
  117. package/package.json +43 -0
@@ -0,0 +1,315 @@
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
2
+ import { join } from "path";
3
+ /**
4
+ * Install briefed hooks into .claude/settings.json.
5
+ * Adds SessionStart (compact) and UserPromptSubmit hooks.
6
+ */
7
+ export function installHooks(root) {
8
+ const claudeDir = join(root, ".claude");
9
+ const settingsPath = join(claudeDir, "settings.json");
10
+ // Ensure .claude directory exists
11
+ if (!existsSync(claudeDir)) {
12
+ mkdirSync(claudeDir, { recursive: true });
13
+ }
14
+ // Load existing settings or create new
15
+ // Security: backup existing settings before modifying
16
+ let settings = {};
17
+ if (existsSync(settingsPath)) {
18
+ try {
19
+ const raw = readFileSync(settingsPath, "utf-8");
20
+ settings = JSON.parse(raw);
21
+ // Backup before modifying
22
+ writeFileSync(settingsPath + ".briefed-backup", raw);
23
+ }
24
+ catch {
25
+ settings = {};
26
+ }
27
+ }
28
+ // Initialize hooks object
29
+ if (!settings.hooks)
30
+ settings.hooks = {};
31
+ // Remove any existing briefed hooks (for idempotency)
32
+ for (const eventName of Object.keys(settings.hooks)) {
33
+ settings.hooks[eventName] = settings.hooks[eventName].filter((entry) => !entry.hooks.some((h) => h.command.includes("briefed")));
34
+ if (settings.hooks[eventName].length === 0) {
35
+ delete settings.hooks[eventName];
36
+ }
37
+ }
38
+ // Add SessionStart hook (compact matcher) — re-inject skeleton after compaction
39
+ if (!settings.hooks.SessionStart)
40
+ settings.hooks.SessionStart = [];
41
+ settings.hooks.SessionStart.push({
42
+ matcher: "compact",
43
+ hooks: [
44
+ {
45
+ type: "command",
46
+ command: `node "${join(root, ".briefed", "hooks", "session-start.js")}"`,
47
+ timeout: 5,
48
+ },
49
+ ],
50
+ });
51
+ // Add UserPromptSubmit hook — dynamic per-prompt context injection
52
+ if (!settings.hooks.UserPromptSubmit)
53
+ settings.hooks.UserPromptSubmit = [];
54
+ settings.hooks.UserPromptSubmit.push({
55
+ hooks: [
56
+ {
57
+ type: "command",
58
+ command: `node "${join(root, ".briefed", "hooks", "prompt-submit.js")}"`,
59
+ timeout: 5,
60
+ },
61
+ ],
62
+ });
63
+ // Add PostToolUse hook — learning loop (tracks file reads)
64
+ if (!settings.hooks.PostToolUse)
65
+ settings.hooks.PostToolUse = [];
66
+ settings.hooks.PostToolUse.push({
67
+ matcher: "Read",
68
+ hooks: [
69
+ {
70
+ type: "command",
71
+ command: `node "${join(root, ".briefed", "hooks", "post-read.js")}"`,
72
+ timeout: 2,
73
+ },
74
+ ],
75
+ });
76
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
77
+ }
78
+ /**
79
+ * Generate the hook scripts that get executed by Claude Code.
80
+ */
81
+ export function generateHookScripts(root) {
82
+ const hooksDir = join(root, ".briefed", "hooks");
83
+ mkdirSync(hooksDir, { recursive: true });
84
+ // SessionStart hook — re-inject skeleton after compaction
85
+ const sessionStartScript = `#!/usr/bin/env node
86
+ // briefed: SessionStart hook — re-inject skeleton after compaction
87
+ // Security: only reads from .briefed/ directory, never writes, never persists input
88
+ const { readFileSync, realpathSync } = require("fs");
89
+ const { join, resolve } = require("path");
90
+
91
+ const briefedDir = resolve(join(__dirname, ".."));
92
+ const skeletonPath = join(briefedDir, "skeleton.md");
93
+
94
+ // Verify the skeleton file is inside .briefed/ (prevent path traversal)
95
+ try {
96
+ const realPath = realpathSync(skeletonPath);
97
+ if (!realPath.startsWith(realpathSync(briefedDir))) process.exit(0);
98
+ process.stdout.write(readFileSync(realPath, "utf-8"));
99
+ } catch {}
100
+ `.trim();
101
+ writeFileSync(join(hooksDir, "session-start.js"), sessionStartScript);
102
+ // UserPromptSubmit hook — analyze prompt, inject relevant contracts + dependencies
103
+ const promptSubmitScript = `
104
+ #!/usr/bin/env node
105
+ // briefed: UserPromptSubmit hook — adaptive context injection
106
+ // Security: read-only, never persists prompt data, all file reads scoped to .briefed/
107
+ const { readFileSync, existsSync, realpathSync } = require("fs");
108
+ const { join, resolve } = require("path");
109
+
110
+ const briefedDir = resolve(join(__dirname, ".."));
111
+ const contractsDir = join(briefedDir, "contracts");
112
+ const indexPath = join(briefedDir, "index.json");
113
+ const testMapPath = join(briefedDir, "test-map.json");
114
+ const historyPath = join(briefedDir, "history.json");
115
+
116
+ // Security: verify a path is inside .briefed/ before reading
117
+ function safeRead(filePath) {
118
+ try {
119
+ const real = realpathSync(filePath);
120
+ if (!real.startsWith(realpathSync(briefedDir))) return null;
121
+ return readFileSync(real, "utf-8");
122
+ } catch { return null; }
123
+ }
124
+ function safeExists(filePath) {
125
+ try {
126
+ if (!existsSync(filePath)) return false;
127
+ return realpathSync(filePath).startsWith(realpathSync(briefedDir));
128
+ } catch { return false; }
129
+ }
130
+
131
+ // Complexity scoring — determines how much context to inject
132
+ function scorePrompt(prompt) {
133
+ let score = 3; // baseline: moderate
134
+
135
+ // High complexity signals (+2 each)
136
+ const highSignals = [/refactor/i, /restructur/i, /redesign/i, /migrat/i, /debug/i, /investigate/i, /\\bwhy\\b/i, /architect/i, /\\bsystem\\b/i, /security/i, /vulnerab/i, /performance/i, /optimiz/i];
137
+ for (const s of highSignals) { if (s.test(prompt)) score += 2; }
138
+
139
+ // Medium signals (+1 each)
140
+ const medSignals = [/\\badd\\b/i, /implement/i, /create/i, /build/i, /integrat/i, /\\btest/i, /endpoint/i, /feature/i, /module/i, /service/i];
141
+ for (const s of medSignals) { if (s.test(prompt)) score += 1; }
142
+
143
+ // Low complexity signals (-2 each)
144
+ const lowSignals = [/typo/i, /rename/i, /comment/i, /\\blog\\b/i, /print/i, /format/i, /lint/i, /spell/i];
145
+ for (const s of lowSignals) { if (s.test(prompt)) score -= 2; }
146
+
147
+ // Long prompts = more complex
148
+ if (prompt.length > 200) score += 1;
149
+ if (prompt.length > 500) score += 1;
150
+ if (prompt.length < 30) score -= 1;
151
+
152
+ // Multiple file/module mentions = complex
153
+ const fileRefs = (prompt.match(/\\w+\\.\\w{2,4}/g) || []).length;
154
+ if (fileRefs >= 2) score += 2;
155
+
156
+ return Math.max(1, Math.min(10, score));
157
+ }
158
+
159
+ let input = "";
160
+ process.stdin.setEncoding("utf-8");
161
+ process.stdin.on("data", (chunk) => { input += chunk; });
162
+ process.stdin.on("end", () => {
163
+ try {
164
+ const data = JSON.parse(input);
165
+ const prompt = (data.prompt || data.message || "").toLowerCase();
166
+
167
+ if (!prompt || !safeExists(indexPath)) {
168
+ process.exit(0);
169
+ return;
170
+ }
171
+
172
+ // Security: truncate prompt analysis to prevent ReDoS on long inputs
173
+ const safePrompt = prompt.slice(0, 2000);
174
+ const index = JSON.parse(safeRead(indexPath) || "{}");
175
+ if (!index.modules) { process.exit(0); return; }
176
+ const complexity = scorePrompt(safePrompt);
177
+
178
+ // Adaptive budget based on prompt complexity
179
+ // Simple (1-3): 1500 chars (~400 tokens) — contracts only
180
+ // Moderate (4-6): 5000 chars (~1350 tokens) — contracts + deps
181
+ // Complex (7-10): 9000 chars (~2400 tokens) — contracts + deps + tests + history
182
+ const budget = complexity <= 3 ? 1500 : complexity <= 6 ? 5000 : 9000;
183
+ const includeDeps = complexity >= 4;
184
+ const includeTests = complexity >= 7;
185
+ const includeHistory = complexity >= 7;
186
+
187
+ let used = 0;
188
+ const loaded = new Set();
189
+ const output = [];
190
+
191
+ // Score each module by keyword hits
192
+ const scored = index.modules.map((mod) => {
193
+ const keywords = mod.keywords || [];
194
+ const hits = keywords.filter((k) => safePrompt.includes(k.toLowerCase()));
195
+ return { mod, hits: hits.length, complexity: mod.complexity || 0 };
196
+ })
197
+ .filter((s) => s.hits > 0)
198
+ .sort((a, b) => b.hits - a.hits || b.complexity - a.complexity);
199
+
200
+ // Load matching modules + their dependencies
201
+ for (const { mod } of scored) {
202
+ if (used >= budget) break;
203
+
204
+ // Load the module's contract
205
+ if (!loaded.has(mod.file)) {
206
+ const contractPath = join(contractsDir, mod.file);
207
+ const contract = safeRead(contractPath);
208
+ if (contract) {
209
+ if (used + contract.length <= budget) {
210
+ output.push("# Module: " + mod.dir + "\\n" + contract);
211
+ used += contract.length;
212
+ loaded.add(mod.file);
213
+ }
214
+ }
215
+ }
216
+
217
+ // Load dependency modules (moderate+ complexity)
218
+ if (includeDeps) {
219
+ const contractPath2 = join(contractsDir, mod.file);
220
+ const contractText = safeRead(contractPath2);
221
+ if (contractText) {
222
+ const depMatch = contractText.match(/dependencies:\\n([\\s\\S]*?)(?:\\n\\w|$)/);
223
+ if (depMatch) {
224
+ const deps = depMatch[1].match(/- (.+)/g) || [];
225
+ for (const dep of deps) {
226
+ const depName = dep.replace("- ", "").trim();
227
+ const depMod = index.modules.find((m) => m.name === depName);
228
+ if (depMod && !loaded.has(depMod.file) && used < budget) {
229
+ const depPath = join(contractsDir, depMod.file);
230
+ const depContract = safeRead(depPath);
231
+ if (depContract) {
232
+ if (used + depContract.length <= budget) {
233
+ output.push("# Dependency: " + depMod.dir + "\\n" + depContract);
234
+ used += depContract.length;
235
+ loaded.add(depMod.file);
236
+ }
237
+ }
238
+ }
239
+ }
240
+ }
241
+ }
242
+ }
243
+
244
+ // Inject test info for matched modules (complex tasks only)
245
+ if (includeTests && safeExists(testMapPath) && used < budget) {
246
+ try {
247
+ const testMap = JSON.parse(safeRead(testMapPath) || "{}");
248
+ for (const file of mod.files || []) {
249
+ const testInfo = testMap[file];
250
+ if (testInfo && used < budget) {
251
+ const testLine = "# Tests for " + file + ": " + testInfo.test + " (" + testInfo.count + " tests)\\n" +
252
+ (testInfo.names || []).slice(0, 5).map((n) => " - " + n).join("\\n");
253
+ if (used + testLine.length <= budget) {
254
+ output.push(testLine);
255
+ used += testLine.length;
256
+ }
257
+ }
258
+ }
259
+ } catch {}
260
+ }
261
+
262
+ // Inject history for matched modules (complex tasks only)
263
+ if (includeHistory && safeExists(historyPath) && used < budget) {
264
+ try {
265
+ const history = JSON.parse(safeRead(historyPath) || "{}");
266
+ for (const file of mod.files || []) {
267
+ const hist = history[file];
268
+ if (hist && hist.recent && hist.recent.length > 0 && used < budget) {
269
+ const histLine = "# History for " + file + " (" + hist.frequency + " recent commits):\\n" +
270
+ hist.recent.slice(0, 3).map((m) => " - " + m).join("\\n");
271
+ if (used + histLine.length <= budget) {
272
+ output.push(histLine);
273
+ used += histLine.length;
274
+ }
275
+ }
276
+ }
277
+ } catch {}
278
+ }
279
+ }
280
+
281
+ if (output.length > 0) {
282
+ process.stdout.write(output.join("\\n---\\n"));
283
+ }
284
+ } catch {
285
+ // Fail silently
286
+ }
287
+ process.exit(0);
288
+ });
289
+ `.trim();
290
+ writeFileSync(join(hooksDir, "prompt-submit.js"), promptSubmitScript);
291
+ // PostToolUse hook — learning loop (tracks file reads for future improvement)
292
+ writeFileSync(join(hooksDir, "post-read.js"), `#!/usr/bin/env node
293
+ // briefed: PostToolUse hook — tracks which files Claude reads for the learning loop
294
+ // Security: only appends to .briefed/session-reads.log, never reads prompt content
295
+ const { appendFileSync, mkdirSync, existsSync } = require("fs");
296
+ const { join } = require("path");
297
+
298
+ let input = "";
299
+ process.stdin.setEncoding("utf-8");
300
+ process.stdin.on("data", (chunk) => { input += chunk; });
301
+ process.stdin.on("end", () => {
302
+ try {
303
+ const data = JSON.parse(input);
304
+ if (data.tool_name !== "Read") { process.exit(0); return; }
305
+ const filePath = data.tool_input && data.tool_input.file_path;
306
+ if (!filePath) { process.exit(0); return; }
307
+ const briefedDir = join(process.cwd(), ".briefed");
308
+ if (!existsSync(briefedDir)) mkdirSync(briefedDir, { recursive: true });
309
+ appendFileSync(join(briefedDir, "session-reads.log"), filePath + "\\n");
310
+ } catch {}
311
+ process.exit(0);
312
+ });
313
+ `);
314
+ }
315
+ //# sourceMappingURL=hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/deliver/hooks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { FileExtraction } from "./signatures.js";
2
+ import type { DepGraph } from "./depgraph.js";
3
+ export interface ComplexityScore {
4
+ file: string;
5
+ score: number;
6
+ fanOut: number;
7
+ fanIn: number;
8
+ branchCount: number;
9
+ lineCount: number;
10
+ symbolCount: number;
11
+ }
12
+ /**
13
+ * Compute complexity score for a file.
14
+ * Higher score = more context needed when working with this file.
15
+ */
16
+ export declare function computeComplexity(extraction: FileExtraction, depGraph: DepGraph): ComplexityScore;
@@ -0,0 +1,46 @@
1
+ import { readFileSync } from "fs";
2
+ /**
3
+ * Compute complexity score for a file.
4
+ * Higher score = more context needed when working with this file.
5
+ */
6
+ export function computeComplexity(extraction, depGraph) {
7
+ const content = readFileSync(extraction.path, "utf-8");
8
+ const fanOut = extraction.imports.filter((i) => i.isRelative).length;
9
+ const node = depGraph.nodes.get(extraction.path.replace(/\\/g, "/"));
10
+ const fanIn = node?.inEdges.length || 0;
11
+ // Count branching constructs
12
+ const branchPatterns = [
13
+ /\bif\s*\(/g,
14
+ /\belse\s+if\s*\(/g,
15
+ /\bswitch\s*\(/g,
16
+ /\bcase\s+/g,
17
+ /\bcatch\s*\(/g,
18
+ /\?\s*[^:]/g, // ternary
19
+ ];
20
+ let branchCount = 0;
21
+ for (const pattern of branchPatterns) {
22
+ const matches = content.match(pattern);
23
+ if (matches)
24
+ branchCount += matches.length;
25
+ }
26
+ // Compute weighted score (0-10)
27
+ const score = Math.min(10, (clamp(fanOut / 3, 0, 3) + // 0-3 points for imports
28
+ clamp(fanIn / 3, 0, 3) + // 0-3 points for dependents
29
+ clamp(branchCount / 15, 0, 2) + // 0-2 points for branching
30
+ clamp(extraction.lineCount / 200, 0, 1) + // 0-1 points for size
31
+ clamp(extraction.symbols.length / 10, 0, 1) // 0-1 points for symbol density
32
+ ));
33
+ return {
34
+ file: extraction.path,
35
+ score: Math.round(score * 10) / 10,
36
+ fanOut,
37
+ fanIn,
38
+ branchCount,
39
+ lineCount: extraction.lineCount,
40
+ symbolCount: extraction.symbols.length,
41
+ };
42
+ }
43
+ function clamp(val, min, max) {
44
+ return Math.max(min, Math.min(max, val));
45
+ }
46
+ //# sourceMappingURL=complexity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"complexity.js","sourceRoot":"","sources":["../../src/extract/complexity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAclC;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,UAA0B,EAC1B,QAAkB;IAElB,MAAM,OAAO,GAAG,YAAY,CAAC,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAEvD,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC;IACrE,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAC7B,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CACpC,CAAC;IACF,MAAM,KAAK,GAAG,IAAI,EAAE,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC;IAExC,6BAA6B;IAC7B,MAAM,cAAc,GAAG;QACrB,YAAY;QACZ,mBAAmB;QACnB,gBAAgB;QAChB,YAAY;QACZ,eAAe;QACf,YAAY,EAAG,UAAU;KAC1B,CAAC;IACF,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,KAAK,MAAM,OAAO,IAAI,cAAc,EAAE,CAAC;QACrC,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACvC,IAAI,OAAO;YAAE,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC;IAC7C,CAAC;IAED,gCAAgC;IAChC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CACzB,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAS,yBAAyB;QACzD,KAAK,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAU,4BAA4B;QAC5D,KAAK,CAAC,WAAW,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,2BAA2B;QAC3D,KAAK,CAAC,UAAU,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,sBAAsB;QAChE,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,gCAAgC;KAC7E,CAAC,CAAC;IAEH,OAAO;QACL,IAAI,EAAE,UAAU,CAAC,IAAI;QACrB,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,GAAG,EAAE;QAClC,MAAM;QACN,KAAK;QACL,WAAW;QACX,SAAS,EAAE,UAAU,CAAC,SAAS;QAC/B,WAAW,EAAE,UAAU,CAAC,OAAO,CAAC,MAAM;KACvC,CAAC;AACJ,CAAC;AAED,SAAS,KAAK,CAAC,GAAW,EAAE,GAAW,EAAE,GAAW;IAClD,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;AAC3C,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { FileExtraction } from "./signatures.js";
2
+ export interface ProjectConventions {
3
+ naming: string[];
4
+ errorHandling: string[];
5
+ patterns: string[];
6
+ testing: string[];
7
+ imports: string[];
8
+ other: string[];
9
+ }
10
+ /**
11
+ * Auto-detect project conventions from code patterns.
12
+ * Prevents the "inconsistent code style" problem that compounds across sessions.
13
+ */
14
+ export declare function detectConventions(extractions: FileExtraction[], root: string): ProjectConventions;
15
+ /**
16
+ * Format conventions for inclusion in CLAUDE.md or rules.
17
+ */
18
+ export declare function formatConventions(conv: ProjectConventions): string;
@@ -0,0 +1,143 @@
1
+ import { readFileSync } from "fs";
2
+ /**
3
+ * Auto-detect project conventions from code patterns.
4
+ * Prevents the "inconsistent code style" problem that compounds across sessions.
5
+ */
6
+ export function detectConventions(extractions, root) {
7
+ const conventions = {
8
+ naming: [],
9
+ errorHandling: [],
10
+ patterns: [],
11
+ testing: [],
12
+ imports: [],
13
+ other: [],
14
+ };
15
+ // Sample files for analysis (up to 20 of the most important)
16
+ const sample = extractions
17
+ .filter((e) => e.symbols.length > 0)
18
+ .slice(0, 20);
19
+ if (sample.length === 0)
20
+ return conventions;
21
+ // Analyze naming conventions
22
+ const allSymbols = sample.flatMap((e) => e.symbols);
23
+ const exportedFunctions = allSymbols.filter((s) => s.exported && (s.kind === "function" || s.kind === "method"));
24
+ const exportedTypes = allSymbols.filter((s) => s.exported && (s.kind === "class" || s.kind === "interface" || s.kind === "type"));
25
+ if (exportedFunctions.length > 3) {
26
+ const camelCount = exportedFunctions.filter((s) => /^[a-z][a-zA-Z]+$/.test(s.name.split(".").pop())).length;
27
+ const snakeCount = exportedFunctions.filter((s) => /^[a-z][a-z_]+$/.test(s.name.split(".").pop())).length;
28
+ if (camelCount > snakeCount * 2) {
29
+ conventions.naming.push("camelCase for functions and methods");
30
+ }
31
+ else if (snakeCount > camelCount * 2) {
32
+ conventions.naming.push("snake_case for functions and methods");
33
+ }
34
+ }
35
+ if (exportedTypes.length > 2) {
36
+ const pascalCount = exportedTypes.filter((s) => /^[A-Z][a-zA-Z]+$/.test(s.name)).length;
37
+ if (pascalCount > exportedTypes.length * 0.7) {
38
+ conventions.naming.push("PascalCase for types, classes, and interfaces");
39
+ }
40
+ }
41
+ // Analyze error handling patterns
42
+ const fileContents = sample.map((e) => {
43
+ try {
44
+ return readFileSync(e.path, "utf-8");
45
+ }
46
+ catch {
47
+ return "";
48
+ }
49
+ });
50
+ const tryCatchCount = fileContents.filter((c) => c.includes("try {") || c.includes("try:")).length;
51
+ const resultTypeCount = fileContents.filter((c) => c.includes("Result<") || c.includes("Either<") || c.includes("Result[")).length;
52
+ const throwCount = fileContents.filter((c) => c.includes("throw new") || c.includes("raise ")).length;
53
+ if (tryCatchCount > sample.length * 0.3) {
54
+ conventions.errorHandling.push("uses try/catch for error handling");
55
+ }
56
+ if (resultTypeCount > 2) {
57
+ conventions.errorHandling.push("uses Result/Either types for error propagation");
58
+ }
59
+ if (throwCount > sample.length * 0.3) {
60
+ // Check if custom error classes are used
61
+ const customErrors = fileContents.filter((c) => c.match(/class \w+Error extends/)).length;
62
+ if (customErrors > 0) {
63
+ conventions.errorHandling.push("throws custom error classes (not generic Error)");
64
+ }
65
+ }
66
+ // Analyze import patterns
67
+ const allImports = sample.flatMap((e) => e.imports);
68
+ const aliasImports = allImports.filter((i) => i.source.startsWith("@/") || i.source.startsWith("~/") || i.source.startsWith("#"));
69
+ if (aliasImports.length > allImports.length * 0.1) {
70
+ const prefix = aliasImports[0]?.source.split("/")[0];
71
+ conventions.imports.push(`uses ${prefix} path alias for absolute imports`);
72
+ }
73
+ const relativeImports = allImports.filter((i) => i.isRelative);
74
+ const absoluteImports = allImports.filter((i) => !i.isRelative);
75
+ if (relativeImports.length > absoluteImports.length * 2) {
76
+ conventions.imports.push("prefers relative imports");
77
+ }
78
+ // Detect testing patterns
79
+ const testFiles = extractions.filter((e) => e.path.includes(".test.") || e.path.includes(".spec.") ||
80
+ e.path.includes("_test.") || e.path.startsWith("test_"));
81
+ if (testFiles.length > 0) {
82
+ const colocated = testFiles.filter((t) => {
83
+ const dir = t.path.split("/").slice(0, -1).join("/");
84
+ return sample.some((s) => s.path.split("/").slice(0, -1).join("/") === dir);
85
+ });
86
+ if (colocated.length > testFiles.length * 0.5) {
87
+ conventions.testing.push("test files are colocated with source files");
88
+ }
89
+ else {
90
+ conventions.testing.push("test files are in separate test/ directory");
91
+ }
92
+ if (testFiles.some((t) => t.path.includes(".test."))) {
93
+ conventions.testing.push("uses .test.{ext} naming convention");
94
+ }
95
+ else if (testFiles.some((t) => t.path.includes(".spec."))) {
96
+ conventions.testing.push("uses .spec.{ext} naming convention");
97
+ }
98
+ }
99
+ // Detect common architectural patterns
100
+ const hasServices = extractions.some((e) => e.path.includes("/services/") || e.path.includes("/service/"));
101
+ const hasControllers = extractions.some((e) => e.path.includes("/controllers/") || e.path.includes("/routes/") ||
102
+ e.path.includes("/api/"));
103
+ if (hasServices && hasControllers) {
104
+ conventions.patterns.push("layered architecture: routes/controllers → services");
105
+ }
106
+ // Detect async patterns
107
+ const asyncCount = fileContents.filter((c) => c.includes("async ") || c.includes("await ")).length;
108
+ if (asyncCount > sample.length * 0.5) {
109
+ conventions.patterns.push("predominantly async/await (not callbacks)");
110
+ }
111
+ // Detect export style
112
+ const defaultExports = fileContents.filter((c) => c.includes("export default") || c.includes("module.exports =")).length;
113
+ const namedExports = fileContents.filter((c) => c.includes("export function") || c.includes("export const") || c.includes("export class")).length;
114
+ if (namedExports > defaultExports * 3) {
115
+ conventions.patterns.push("prefers named exports over default exports");
116
+ }
117
+ else if (defaultExports > namedExports * 2) {
118
+ conventions.patterns.push("uses default exports");
119
+ }
120
+ return conventions;
121
+ }
122
+ /**
123
+ * Format conventions for inclusion in CLAUDE.md or rules.
124
+ */
125
+ export function formatConventions(conv) {
126
+ const lines = [];
127
+ const all = [
128
+ ...conv.naming,
129
+ ...conv.errorHandling,
130
+ ...conv.patterns,
131
+ ...conv.testing,
132
+ ...conv.imports,
133
+ ...conv.other,
134
+ ];
135
+ if (all.length === 0)
136
+ return "";
137
+ lines.push("Conventions:");
138
+ for (const c of all) {
139
+ lines.push(` - ${c}`);
140
+ }
141
+ return lines.join("\n");
142
+ }
143
+ //# sourceMappingURL=conventions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conventions.js","sourceRoot":"","sources":["../../src/extract/conventions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { FileExtraction } from "./signatures.js";
2
+ import { GraphNode } from "../utils/pagerank.js";
3
+ export interface DepGraph {
4
+ nodes: Map<string, GraphNode>;
5
+ pageRank: Map<string, number>;
6
+ refCounts: Map<string, number>;
7
+ }
8
+ /**
9
+ * Build a dependency graph from file extractions.
10
+ * Resolves relative imports to actual file paths.
11
+ */
12
+ export declare function buildDepGraph(extractions: FileExtraction[], root: string): DepGraph;
@@ -0,0 +1,70 @@
1
+ import { dirname, join, relative } from "path";
2
+ import { computePageRank, computeRefCounts } from "../utils/pagerank.js";
3
+ /**
4
+ * Build a dependency graph from file extractions.
5
+ * Resolves relative imports to actual file paths.
6
+ */
7
+ export function buildDepGraph(extractions, root) {
8
+ const fileSet = new Set(extractions.map((e) => e.path));
9
+ const nodes = new Map();
10
+ // Initialize all nodes
11
+ for (const ext of extractions) {
12
+ nodes.set(ext.path, {
13
+ id: ext.path,
14
+ outEdges: [],
15
+ inEdges: [],
16
+ });
17
+ }
18
+ // Resolve imports and build edges
19
+ for (const ext of extractions) {
20
+ const node = nodes.get(ext.path);
21
+ for (const imp of ext.imports) {
22
+ if (!imp.isRelative)
23
+ continue; // skip external packages
24
+ const resolved = resolveImport(ext.path, imp.source, root, fileSet);
25
+ if (resolved && nodes.has(resolved)) {
26
+ if (!node.outEdges.includes(resolved)) {
27
+ node.outEdges.push(resolved);
28
+ }
29
+ const targetNode = nodes.get(resolved);
30
+ if (!targetNode.inEdges.includes(ext.path)) {
31
+ targetNode.inEdges.push(ext.path);
32
+ }
33
+ }
34
+ }
35
+ }
36
+ // Compute rankings
37
+ const pageRank = computePageRank(nodes);
38
+ const refCounts = computeRefCounts(nodes);
39
+ return { nodes, pageRank, refCounts };
40
+ }
41
+ /**
42
+ * Resolve a relative import to a file path.
43
+ * Handles TypeScript/JS resolution with extension inference.
44
+ */
45
+ function resolveImport(fromFile, importPath, root, fileSet) {
46
+ const fromDir = dirname(join(root, fromFile));
47
+ const basePath = relative(root, join(fromDir, importPath)).replace(/\\/g, "/");
48
+ // Security: prevent path traversal outside project root
49
+ if (basePath.startsWith("..") || basePath.startsWith("/"))
50
+ return null;
51
+ // Try exact match first
52
+ if (fileSet.has(basePath))
53
+ return basePath;
54
+ // Try with extensions
55
+ const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".py", ".go", ".rs"];
56
+ for (const ext of extensions) {
57
+ const withExt = basePath + ext;
58
+ if (fileSet.has(withExt))
59
+ return withExt;
60
+ }
61
+ // Try index files
62
+ const indexFiles = ["index.ts", "index.tsx", "index.js", "index.jsx", "mod.rs", "__init__.py"];
63
+ for (const idx of indexFiles) {
64
+ const indexPath = basePath + "/" + idx;
65
+ if (fileSet.has(indexPath))
66
+ return indexPath;
67
+ }
68
+ return null;
69
+ }
70
+ //# sourceMappingURL=depgraph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"depgraph.js","sourceRoot":"","sources":["../../src/extract/depgraph.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AAG/C,OAAO,EAAa,eAAe,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAQpF;;;GAGG;AACH,MAAM,UAAU,aAAa,CAC3B,WAA6B,EAC7B,IAAY;IAEZ,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACxD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAqB,CAAC;IAE3C,uBAAuB;IACvB,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;QAC9B,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE;YAClB,EAAE,EAAE,GAAG,CAAC,IAAI;YACZ,QAAQ,EAAE,EAAE;YACZ,OAAO,EAAE,EAAE;SACZ,CAAC,CAAC;IACL,CAAC;IAED,kCAAkC;IAClC,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAE,CAAC;QAElC,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;YAC9B,IAAI,CAAC,GAAG,CAAC,UAAU;gBAAE,SAAS,CAAC,yBAAyB;YAExD,MAAM,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;YACpE,IAAI,QAAQ,IAAI,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACpC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;oBACtC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;gBAC/B,CAAC;gBACD,MAAM,UAAU,GAAG,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAE,CAAC;gBACxC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC3C,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;gBACpC,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,mBAAmB;IACnB,MAAM,QAAQ,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,SAAS,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAE1C,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;AACxC,CAAC;AAED;;;GAGG;AACH,SAAS,aAAa,CACpB,QAAgB,EAChB,UAAkB,EAClB,IAAY,EACZ,OAAoB;IAEpB,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAE/E,wDAAwD;IACxD,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAEvE,wBAAwB;IACxB,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAC;IAE3C,sBAAsB;IACtB,MAAM,UAAU,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;IAC/E,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,MAAM,OAAO,GAAG,QAAQ,GAAG,GAAG,CAAC;QAC/B,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,OAAO,OAAO,CAAC;IAC3C,CAAC;IAED,kBAAkB;IAClB,MAAM,UAAU,GAAG,CAAC,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;IAC/F,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,MAAM,SAAS,GAAG,QAAQ,GAAG,GAAG,GAAG,GAAG,CAAC;QACvC,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC;YAAE,OAAO,SAAS,CAAC;IAC/C,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}