devlensio 1.0.1 → 1.0.3
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/dist/pipeline/index.d.ts +1 -0
- package/dist/pipeline/index.js +14 -2
- package/dist/storage/fileStorage.d.ts +3 -0
- package/dist/storage/fileStorage.js +25 -1
- package/dist/storage/interface.d.ts +2 -0
- package/extractors/go/bin/darwin-amd64/devlens_go_extractor +0 -0
- package/extractors/go/bin/darwin-arm64/devlens_go_extractor +0 -0
- package/extractors/go/bin/linux-amd64/devlens_go_extractor +0 -0
- package/extractors/go/bin/linux-arm64/devlens_go_extractor +0 -0
- package/extractors/go/bin/windows-amd64/devlens_go_extractor.exe +0 -0
- package/extractors/java/devlens_java_extractor.jar +0 -0
- package/extractors/rust/bin/darwin-amd64/devlens_rust_extractor +0 -0
- package/extractors/rust/bin/darwin-arm64/devlens_rust_extractor +0 -0
- package/extractors/rust/bin/linux-amd64/devlens_rust_extractor +0 -0
- package/extractors/rust/bin/linux-arm64/devlens_rust_extractor +0 -0
- package/extractors/rust/bin/windows-amd64/devlens_rust_extractor.exe +0 -0
- package/extractors/rust/src/calls.rs +1 -1
- package/extractors/rust/src/enrich.rs +1 -1
- package/extractors/rust/src/imports.rs +1 -1
- package/extractors/rust/src/inheritance.rs +1 -1
- package/extractors/rust/src/nodes.rs +30 -10
- package/extractors/rust/src/orm_edges.rs +2 -2
- package/extractors/rust/src/parser.rs +8 -3
- package/extractors/rust/src/routes.rs +2 -2
- package/extractors/rust/src/tests.rs +1 -1
- package/package.json +1 -1
package/dist/pipeline/index.d.ts
CHANGED
|
@@ -46,6 +46,7 @@ export interface PipelineResult {
|
|
|
46
46
|
isGithubRepo: boolean;
|
|
47
47
|
gitInfo: GitInfo;
|
|
48
48
|
}
|
|
49
|
+
export declare function worktreeCommitHash(nodes: CodeNode[]): string;
|
|
49
50
|
export declare function routesToCodeNodes(routes: (RouteNode | BackendRouteNode)[], repoPath: string): CodeNode[];
|
|
50
51
|
export declare function analyzePipeline(repoPath: string, isGithubRepo: boolean, options?: PipelineOptions): Promise<PipelineResult>;
|
|
51
52
|
export declare function refilterPipeline(stored: PipelineResult, thresholds: FilterThresholds): Pick<PipelineResult, "nodes" | "edges" | "stats">;
|
package/dist/pipeline/index.js
CHANGED
|
@@ -15,6 +15,12 @@ function generateGraphId(repoPath, isGithubRepo) {
|
|
|
15
15
|
.digest("hex")
|
|
16
16
|
.slice(0, 16);
|
|
17
17
|
}
|
|
18
|
+
// Stable id for repos without git: derived from node ids + code hashes, so the
|
|
19
|
+
// same content overwrites one snapshot in place instead of accumulating timestamps
|
|
20
|
+
export function worktreeCommitHash(nodes) {
|
|
21
|
+
const parts = nodes.map((n) => `${n.id}:${n.codeHash ?? ""}`).sort();
|
|
22
|
+
return "worktree-" + createHash("sha256").update(parts.join("|")).digest("hex").slice(0, 12);
|
|
23
|
+
}
|
|
18
24
|
// Gets current git state of the repo
|
|
19
25
|
// Falls back gracefully if git is not initialized
|
|
20
26
|
function getGitInfo(repoPath) {
|
|
@@ -28,9 +34,11 @@ function getGitInfo(repoPath) {
|
|
|
28
34
|
return { commitHash, branch, message, hasGit: true };
|
|
29
35
|
}
|
|
30
36
|
catch {
|
|
31
|
-
// No git, or no commits yet
|
|
37
|
+
// No git, or no commits yet. Placeholder resolved to a content-derived
|
|
38
|
+
// id after extraction (see worktreeCommitHash) so unchanged repos reuse
|
|
39
|
+
// one snapshot instead of growing a new timestamped one per analyze
|
|
32
40
|
return {
|
|
33
|
-
commitHash:
|
|
41
|
+
commitHash: "worktree-pending",
|
|
34
42
|
branch: "unknown",
|
|
35
43
|
message: "no git history",
|
|
36
44
|
hasGit: false,
|
|
@@ -221,6 +229,10 @@ export async function analyzePipeline(repoPath, isGithubRepo, options) {
|
|
|
221
229
|
}
|
|
222
230
|
const allNodes = extractorResult.nodes;
|
|
223
231
|
const allEdges = extractorResult.edges;
|
|
232
|
+
if (!gitInfo.hasGit) {
|
|
233
|
+
gitInfo.commitHash = worktreeCommitHash(allNodes);
|
|
234
|
+
gitInfo.message = "worktree snapshot";
|
|
235
|
+
}
|
|
224
236
|
// Step 5: Score and filter
|
|
225
237
|
console.log("\n[5/5] Scoring and filtering...");
|
|
226
238
|
const scoringResult = scoreAndFilter(allNodes, allEdges, options?.thresholds);
|
|
@@ -21,6 +21,7 @@ export interface CommitSummary {
|
|
|
21
21
|
edgeCount: number;
|
|
22
22
|
hasGit: boolean;
|
|
23
23
|
isSummarized?: boolean;
|
|
24
|
+
isIndexed?: boolean;
|
|
24
25
|
}
|
|
25
26
|
export interface GraphMeta {
|
|
26
27
|
graphId: string;
|
|
@@ -101,6 +102,8 @@ export declare function deleteCommit(graphId: string, commitHash: string): boole
|
|
|
101
102
|
export declare function diffCommits(graphId: string, fromHash: string, toHash: string): NodeDiff | undefined;
|
|
102
103
|
export declare function markCommitSummarized(graphId: string, commitHash: string): void;
|
|
103
104
|
export declare function isCommitSummarized(graphId: string, commitHash: string): boolean;
|
|
105
|
+
export declare function markCommitIndexed(graphId: string, commitHash: string): void;
|
|
106
|
+
export declare function isCommitIndexed(graphId: string, commitHash: string): boolean;
|
|
104
107
|
export declare function findLastSummarizedAncestor(graphId: string, commitHash: string, repoPath: string): Promise<string | undefined>;
|
|
105
108
|
export declare function saveNodeSummaries(graphId: string, commitHash: string, nodeUpdates: Map<string, {
|
|
106
109
|
technicalSummary: string;
|
|
@@ -182,9 +182,14 @@ export function saveGraph(result, options) {
|
|
|
182
182
|
commits: [],
|
|
183
183
|
summarizedCommits: [],
|
|
184
184
|
};
|
|
185
|
-
// Replace if same commit already exists, else append
|
|
185
|
+
// Replace if same commit already exists, else append.
|
|
186
|
+
// Carry over flags the rebuilt summary does not know about (same hazard as
|
|
187
|
+
// isSummarized: buildCommitSummary starts from scratch)
|
|
186
188
|
const existingCommit = meta.commits.findIndex((c) => c.commitHash === commitHash);
|
|
187
189
|
if (existingCommit >= 0) {
|
|
190
|
+
const prev = meta.commits[existingCommit];
|
|
191
|
+
if (prev.isIndexed)
|
|
192
|
+
newSummary.isIndexed = true;
|
|
188
193
|
meta.commits[existingCommit] = newSummary;
|
|
189
194
|
}
|
|
190
195
|
else {
|
|
@@ -500,6 +505,23 @@ export function isCommitSummarized(graphId, commitHash) {
|
|
|
500
505
|
return false;
|
|
501
506
|
return meta.summarizedCommits?.includes(commitHash);
|
|
502
507
|
}
|
|
508
|
+
// Marks a commit as having a derived .search.json index
|
|
509
|
+
export function markCommitIndexed(graphId, commitHash) {
|
|
510
|
+
const meta = readMeta(graphId);
|
|
511
|
+
if (!meta)
|
|
512
|
+
return;
|
|
513
|
+
const commit = meta.commits.find((c) => c.commitHash === commitHash);
|
|
514
|
+
if (!commit || commit.isIndexed)
|
|
515
|
+
return;
|
|
516
|
+
commit.isIndexed = true;
|
|
517
|
+
writeMeta(meta);
|
|
518
|
+
}
|
|
519
|
+
export function isCommitIndexed(graphId, commitHash) {
|
|
520
|
+
const meta = readMeta(graphId);
|
|
521
|
+
if (!meta)
|
|
522
|
+
return false;
|
|
523
|
+
return meta.commits.find((c) => c.commitHash === commitHash)?.isIndexed === true;
|
|
524
|
+
}
|
|
503
525
|
// Finds the most recent ancestor commit that has been summarized.
|
|
504
526
|
// Uses simple-git to walk the commit history of the repo.
|
|
505
527
|
// Returns undefined if no summarized ancestor exists (= full summarization needed).
|
|
@@ -601,6 +623,8 @@ export const fileStorage = {
|
|
|
601
623
|
diffCommits,
|
|
602
624
|
markCommitSummarized,
|
|
603
625
|
isCommitSummarized,
|
|
626
|
+
markCommitIndexed,
|
|
627
|
+
isCommitIndexed,
|
|
604
628
|
findLastSummarizedAncestor,
|
|
605
629
|
saveNodeSummaries,
|
|
606
630
|
getCheckpointPath,
|
|
@@ -11,6 +11,8 @@ export interface GraphStorage {
|
|
|
11
11
|
diffCommits(graphId: string, fromHash: string, toHash: string): NodeDiff | undefined;
|
|
12
12
|
markCommitSummarized(graphId: string, commitHash: string): void;
|
|
13
13
|
isCommitSummarized(graphId: string, commitHash: string): boolean;
|
|
14
|
+
markCommitIndexed(graphId: string, commitHash: string): void;
|
|
15
|
+
isCommitIndexed(graphId: string, commitHash: string): boolean;
|
|
14
16
|
findLastSummarizedAncestor(graphId: string, commitHash: string, repoPath: string): Promise<string | undefined>;
|
|
15
17
|
saveNodeSummaries(graphId: string, commitHash: string, nodeUpdates: Map<string, {
|
|
16
18
|
technicalSummary: string;
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -34,7 +34,7 @@ pub fn detect_imports(
|
|
|
34
34
|
let mut seen: std::collections::HashSet<(String, String)> = std::collections::HashSet::new();
|
|
35
35
|
|
|
36
36
|
for pf in &repo.files {
|
|
37
|
-
if pf.
|
|
37
|
+
if pf.is_pure_test_file {
|
|
38
38
|
// test files ARE leaf nodes, but they still import — emit edges
|
|
39
39
|
// from the TEST node (same file id — parentFile covers it)
|
|
40
40
|
}
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Contract rules enforced here:
|
|
4
4
|
// - FILE id `file::rel/path.rs`; file nodes parent themselves
|
|
5
|
-
// -
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// - PURE test files (tests/ integration files, or files whose every item is
|
|
6
|
+
// a #[test] fn) = LEAF TEST nodes; Rust's idiomatic inline #[cfg(test)]
|
|
7
|
+
// mod tests must NOT collapse a mixed production file — its non-test items
|
|
8
|
+
// emit normally and #[test] fns emit as FUNCTION nodes flagged
|
|
9
|
+
// metadata.isTestFunction (test bodies stay out of prod call edges there)
|
|
8
10
|
// - rawCode + codeHash on every FUNCTION/METHOD/STRUCT/ENUM/TRAIT/IMPL_BLOCK
|
|
9
11
|
// - deterministic ids (file-scoped; trait-impl method suffix for the
|
|
10
12
|
// same-name-different-trait collision class)
|
|
@@ -17,7 +19,7 @@ use serde_json::{Map, Value};
|
|
|
17
19
|
pub fn collect_file_nodes(files: &[ParsedFile]) -> Vec<CodeNode> {
|
|
18
20
|
let mut out = vec![];
|
|
19
21
|
for pf in files {
|
|
20
|
-
if pf.
|
|
22
|
+
if pf.is_pure_test_file {
|
|
21
23
|
let mut n = file_node(&pf.rel_path, pf.line_count, NODE_TEST);
|
|
22
24
|
let meta = n
|
|
23
25
|
.metadata
|
|
@@ -30,25 +32,43 @@ pub fn collect_file_nodes(files: &[ParsedFile]) -> Vec<CodeNode> {
|
|
|
30
32
|
}
|
|
31
33
|
out.push(n);
|
|
32
34
|
} else {
|
|
33
|
-
|
|
35
|
+
let mut n = file_node(&pf.rel_path, pf.line_count, NODE_FILE);
|
|
36
|
+
// Mixed files keep a testCases index so consumers that never walk
|
|
37
|
+
// FUNCTION nodes still see the file's tests.
|
|
38
|
+
if !pf.test_fns.is_empty() {
|
|
39
|
+
let meta = n
|
|
40
|
+
.metadata
|
|
41
|
+
.entry("testCases".to_string())
|
|
42
|
+
.or_insert_with(|| Value::Array(vec![]));
|
|
43
|
+
if let Value::Array(arr) = meta {
|
|
44
|
+
for t in &pf.test_fns {
|
|
45
|
+
arr.push(Value::from(t.clone()));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
out.push(n);
|
|
34
50
|
}
|
|
35
51
|
}
|
|
36
52
|
out
|
|
37
53
|
}
|
|
38
54
|
|
|
39
|
-
/// Code nodes
|
|
40
|
-
///
|
|
55
|
+
/// Code nodes. PURE test files stay leaves (their fns ride in testCases).
|
|
56
|
+
/// Mixed files emit ALL items; #[test] fns are flagged metadata.isTestFunction
|
|
57
|
+
/// instead of being dropped, so a test function is addressable without a new
|
|
58
|
+
/// node type.
|
|
41
59
|
pub fn collect_code_nodes(files: &[ParsedFile]) -> Vec<CodeNode> {
|
|
42
60
|
let mut out = vec![];
|
|
43
61
|
for pf in files {
|
|
44
|
-
if pf.
|
|
62
|
+
if pf.is_pure_test_file {
|
|
45
63
|
continue;
|
|
46
64
|
}
|
|
47
65
|
for item in &pf.items {
|
|
66
|
+
let mut n = item_node(pf, item);
|
|
48
67
|
if item.is_test {
|
|
49
|
-
|
|
68
|
+
n.metadata
|
|
69
|
+
.insert("isTestFunction".to_string(), Value::from(true));
|
|
50
70
|
}
|
|
51
|
-
out.push(
|
|
71
|
+
out.push(n);
|
|
52
72
|
}
|
|
53
73
|
}
|
|
54
74
|
out
|
|
@@ -21,7 +21,7 @@ use std::collections::HashMap;
|
|
|
21
21
|
pub fn table_to_model_map(repo: &ParsedRepo) -> HashMap<String, String> {
|
|
22
22
|
let mut map = HashMap::new();
|
|
23
23
|
for pf in &repo.files {
|
|
24
|
-
if pf.
|
|
24
|
+
if pf.is_pure_test_file {
|
|
25
25
|
continue;
|
|
26
26
|
}
|
|
27
27
|
for item in &pf.items {
|
|
@@ -89,7 +89,7 @@ pub fn detect_orm_edges(
|
|
|
89
89
|
let table_to_schema_file = table_to_schema_file_map(repo);
|
|
90
90
|
|
|
91
91
|
for pf in &repo.files {
|
|
92
|
-
if pf.
|
|
92
|
+
if pf.is_pure_test_file {
|
|
93
93
|
continue;
|
|
94
94
|
}
|
|
95
95
|
for item in &pf.items {
|
|
@@ -162,7 +162,12 @@ pub struct ParsedFile {
|
|
|
162
162
|
pub items: Vec<ParsedItem>,
|
|
163
163
|
pub tables: Vec<TableDecl>,
|
|
164
164
|
pub test_fns: Vec<String>,
|
|
165
|
-
|
|
165
|
+
/// True only when the file is EXCLUSIVELY tests (every top-level item is a
|
|
166
|
+
/// #[test] fn, or the file only holds inline #[cfg(test)] mods). Rust's
|
|
167
|
+
/// idiomatic inline `#[cfg(test)] mod tests` must NOT collapse a mixed
|
|
168
|
+
/// production file into a TEST leaf — only pure test files get leaf
|
|
169
|
+
/// treatment (mirrors the intent of go's _test.go rule).
|
|
170
|
+
pub is_pure_test_file: bool,
|
|
166
171
|
pub line_count: i64,
|
|
167
172
|
}
|
|
168
173
|
|
|
@@ -326,7 +331,7 @@ pub fn parse_file(rel_path: &str, source: &str) -> Result<ParsedFile, String> {
|
|
|
326
331
|
items: vec![],
|
|
327
332
|
tables: vec![],
|
|
328
333
|
test_fns: vec![],
|
|
329
|
-
|
|
334
|
+
is_pure_test_file: false,
|
|
330
335
|
line_count: line_index.line_count() as i64,
|
|
331
336
|
};
|
|
332
337
|
|
|
@@ -342,7 +347,7 @@ pub fn parse_file(rel_path: &str, source: &str) -> Result<ParsedFile, String> {
|
|
|
342
347
|
}
|
|
343
348
|
pf.ast.items = ast_items;
|
|
344
349
|
|
|
345
|
-
pf.
|
|
350
|
+
pf.is_pure_test_file = !pf.test_fns.is_empty() && pf.items.iter().all(|i| i.is_test);
|
|
346
351
|
Ok(pf)
|
|
347
352
|
}
|
|
348
353
|
|
|
@@ -96,7 +96,7 @@ pub fn detect_routes(
|
|
|
96
96
|
let mut func_lit_vars: HashMap<(String, String), String> = HashMap::new();
|
|
97
97
|
|
|
98
98
|
for pf in &repo.files {
|
|
99
|
-
if pf.
|
|
99
|
+
if pf.is_pure_test_file {
|
|
100
100
|
continue;
|
|
101
101
|
}
|
|
102
102
|
for item in &pf.items {
|
|
@@ -195,7 +195,7 @@ pub fn detect_routes(
|
|
|
195
195
|
// ── actix-web / rocket: attribute macros ──
|
|
196
196
|
if framework == "actix-web" || framework == "rocket" {
|
|
197
197
|
for pf in &repo.files {
|
|
198
|
-
if pf.
|
|
198
|
+
if pf.is_pure_test_file {
|
|
199
199
|
continue;
|
|
200
200
|
}
|
|
201
201
|
for item in &pf.items {
|
package/package.json
CHANGED