@adversarylabs/sdk 0.1.16 → 0.1.17
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/README.md +107 -1
- package/dist/index.d.ts +12 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -5
- package/dist/index.js.map +1 -1
- package/dist/manifest.d.ts +19 -0
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js.map +1 -1
- package/dist/model.d.ts +6 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +109 -58
- package/dist/model.js.map +1 -1
- package/dist/repo-graph.d.ts +114 -0
- package/dist/repo-graph.d.ts.map +1 -0
- package/dist/repo-graph.js +272 -0
- package/dist/repo-graph.js.map +1 -0
- package/dist/repository-model.d.ts +7 -1
- package/dist/repository-model.d.ts.map +1 -1
- package/dist/repository-model.js +69 -4
- package/dist/repository-model.js.map +1 -1
- package/package.json +1 -1
- package/schemas/adversary.manifest.v1.schema.json +42 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { DatabaseSync } from "node:sqlite";
|
|
4
|
+
export const ADVERSARY_REPO_GRAPH_ENV = "ADVERSARY_REPO_GRAPH";
|
|
5
|
+
export const REPO_GRAPH_SCHEMA_VERSION = "v2";
|
|
6
|
+
export const REPO_GRAPH_ADAPTER_REVISION = "go-ast-v1+ts-syntax-v1";
|
|
7
|
+
export class RepoGraphUnavailableError extends Error {
|
|
8
|
+
code = "repo_graph_unavailable";
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "RepoGraphUnavailableError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export async function openRepoGraph(dir) {
|
|
15
|
+
const raw = await readFile(join(dir, "meta.json"), "utf8");
|
|
16
|
+
const meta = JSON.parse(raw);
|
|
17
|
+
if (meta.schemaVersion !== REPO_GRAPH_SCHEMA_VERSION ||
|
|
18
|
+
meta.adapterRevision !== REPO_GRAPH_ADAPTER_REVISION) {
|
|
19
|
+
throw new RepoGraphUnavailableError(`unsupported repo-graph schema ${meta.schemaVersion}/${meta.adapterRevision}`);
|
|
20
|
+
}
|
|
21
|
+
const database = new DatabaseSync(join(dir, "graph.sqlite"), { readOnly: true });
|
|
22
|
+
return new SQLiteRepoGraph(dir, meta, database);
|
|
23
|
+
}
|
|
24
|
+
export async function repoGraphFromEnvironment(env = process.env) {
|
|
25
|
+
const dir = env[ADVERSARY_REPO_GRAPH_ENV]?.trim();
|
|
26
|
+
if (!dir)
|
|
27
|
+
return null;
|
|
28
|
+
try {
|
|
29
|
+
return await openRepoGraph(dir);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
class SQLiteRepoGraph {
|
|
36
|
+
dir;
|
|
37
|
+
meta;
|
|
38
|
+
database;
|
|
39
|
+
constructor(dir, meta, database) {
|
|
40
|
+
this.dir = dir;
|
|
41
|
+
this.meta = meta;
|
|
42
|
+
this.database = database;
|
|
43
|
+
}
|
|
44
|
+
files(query = {}) {
|
|
45
|
+
const { limit, cursor } = bounds(query.limit, query.cursor);
|
|
46
|
+
const glob = query.glob === undefined ? "" : globToLike(query.glob);
|
|
47
|
+
const rows = this.database
|
|
48
|
+
.prepare(`SELECT id,path,language,size,hash,module FROM files
|
|
49
|
+
WHERE id > ? AND (? = '' OR language = ?) AND (? = '' OR path LIKE ? ESCAPE '\\')
|
|
50
|
+
ORDER BY id LIMIT ?`)
|
|
51
|
+
.all(cursor, query.language ?? "", query.language ?? "", glob, glob, limit + 1);
|
|
52
|
+
return page(rows.map(fileRow), limit, (item) => item.id);
|
|
53
|
+
}
|
|
54
|
+
symbolAt(path, line, column = 0) {
|
|
55
|
+
validPath(path);
|
|
56
|
+
if (!Number.isInteger(line) || line < 1 || !Number.isInteger(column) || column < 0) {
|
|
57
|
+
throw new Error("line must be positive and column non-negative");
|
|
58
|
+
}
|
|
59
|
+
const row = this.database
|
|
60
|
+
.prepare(`${symbolSelect}
|
|
61
|
+
WHERE f.path=? AND (s.start_line < ? OR (s.start_line=? AND s.start_col<=?))
|
|
62
|
+
AND (s.end_line > ? OR (s.end_line=? AND s.end_col>=?))
|
|
63
|
+
ORDER BY (s.end_line-s.start_line) ASC, s.id ASC LIMIT 1`)
|
|
64
|
+
.get(normalizePath(path), line, line, column, line, line, column);
|
|
65
|
+
return row === undefined ? undefined : symbolRow(row);
|
|
66
|
+
}
|
|
67
|
+
symbols(query = {}) {
|
|
68
|
+
if (query.path !== undefined)
|
|
69
|
+
validPath(query.path);
|
|
70
|
+
const { limit, cursor } = bounds(query.limit, query.cursor);
|
|
71
|
+
const rows = this.database
|
|
72
|
+
.prepare(`${symbolSelect}
|
|
73
|
+
WHERE s.id>? AND (?='' OR f.path=?) AND (?='' OR s.name=?) AND (?='' OR s.kind=?)
|
|
74
|
+
ORDER BY s.id LIMIT ?`)
|
|
75
|
+
.all(cursor, query.path ?? "", normalizePath(query.path ?? ""), query.name ?? "", query.name ?? "", query.kind ?? "", query.kind ?? "", limit + 1);
|
|
76
|
+
return page(rows.map(symbolRow), limit, (item) => item.id);
|
|
77
|
+
}
|
|
78
|
+
definitions(query) {
|
|
79
|
+
return this.symbols(query);
|
|
80
|
+
}
|
|
81
|
+
references(query) {
|
|
82
|
+
return this.relations(query, "references", false);
|
|
83
|
+
}
|
|
84
|
+
callers(query) {
|
|
85
|
+
return this.relations(query, "calls", false);
|
|
86
|
+
}
|
|
87
|
+
callees(query) {
|
|
88
|
+
return this.relations(query, "calls", true);
|
|
89
|
+
}
|
|
90
|
+
implementations(query) {
|
|
91
|
+
return this.relations(query, "implements", false);
|
|
92
|
+
}
|
|
93
|
+
importsOf(path, cursor, limit) {
|
|
94
|
+
return this.fileRelations(path, cursor, limit, true);
|
|
95
|
+
}
|
|
96
|
+
importersOf(path, cursor, limit) {
|
|
97
|
+
return this.fileRelations(path, cursor, limit, false);
|
|
98
|
+
}
|
|
99
|
+
relatedTests(options) {
|
|
100
|
+
if (options.path !== undefined)
|
|
101
|
+
validPath(options.path);
|
|
102
|
+
const symbolId = options.symbolId ?? 0;
|
|
103
|
+
if (!Number.isInteger(symbolId) || symbolId < 0)
|
|
104
|
+
throw new Error("symbolId must be non-negative");
|
|
105
|
+
const { limit, cursor } = bounds(options.limit, options.cursor);
|
|
106
|
+
const rows = this.database
|
|
107
|
+
.prepare(`SELECT tl.id,sf.path AS source_path,
|
|
108
|
+
tl.source_symbol_id,tf.path AS test_path,tl.test_symbol_id,tl.confidence,tl.reason
|
|
109
|
+
FROM test_links tl JOIN files sf ON sf.id=tl.source_file_id
|
|
110
|
+
JOIN files tf ON tf.id=tl.test_file_id
|
|
111
|
+
WHERE tl.id>? AND (?='' OR sf.path=?)
|
|
112
|
+
AND (?=0 OR tl.source_symbol_id=? OR sf.id=(SELECT file_id FROM symbols WHERE id=?))
|
|
113
|
+
ORDER BY tl.id LIMIT ?`)
|
|
114
|
+
.all(cursor, options.path ?? "", normalizePath(options.path ?? ""), symbolId, symbolId, symbolId, limit + 1);
|
|
115
|
+
return testLinkPage(rows.map(testLinkRow), limit);
|
|
116
|
+
}
|
|
117
|
+
close() {
|
|
118
|
+
this.database.close();
|
|
119
|
+
}
|
|
120
|
+
relations(query, kind, outgoing) {
|
|
121
|
+
if (!Number.isInteger(query.symbolId) || query.symbolId < 1) {
|
|
122
|
+
throw new Error("symbolId must be positive");
|
|
123
|
+
}
|
|
124
|
+
const { limit, cursor } = bounds(query.limit, query.cursor);
|
|
125
|
+
const column = outgoing ? "from_symbol_id" : "to_symbol_id";
|
|
126
|
+
const rows = this.database
|
|
127
|
+
.prepare(`${edgeSelect}
|
|
128
|
+
WHERE e.id>? AND e.kind=? AND e.${column}=? ORDER BY e.id LIMIT ?`)
|
|
129
|
+
.all(cursor, kind, query.symbolId, limit + 1);
|
|
130
|
+
return page(rows.map(edgeRow), limit, (item) => item.id);
|
|
131
|
+
}
|
|
132
|
+
fileRelations(path, cursorValue, limitValue, outgoing) {
|
|
133
|
+
validPath(path);
|
|
134
|
+
const { limit, cursor } = bounds(limitValue, cursorValue);
|
|
135
|
+
const condition = outgoing ? "ff.path=?" : "tf.module=(SELECT module FROM files WHERE path=?)";
|
|
136
|
+
const rows = this.database
|
|
137
|
+
.prepare(`${edgeSelect}
|
|
138
|
+
WHERE e.id>? AND e.kind='imports' AND ${condition} ORDER BY e.id LIMIT ?`)
|
|
139
|
+
.all(cursor, normalizePath(path), limit + 1);
|
|
140
|
+
return page(rows.map(edgeRow), limit, (item) => item.id);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const symbolSelect = `SELECT s.id,s.name,s.kind,f.path,s.start_line,s.start_col,
|
|
144
|
+
s.end_line,s.end_col,s.container_id,s.exported,f.language,s.adapter_data
|
|
145
|
+
FROM symbols s JOIN files f ON f.id=s.file_id`;
|
|
146
|
+
const edgeSelect = `SELECT e.id,ff.path AS from_path,e.from_symbol_id,
|
|
147
|
+
COALESCE(tf.path,'') AS to_path,e.to_symbol_id,
|
|
148
|
+
COALESCE(e.unresolved_target,'') AS unresolved_target,e.kind,e.line,e.column,
|
|
149
|
+
e.confidence,e.adapter FROM edges e JOIN files ff ON ff.id=e.from_file_id
|
|
150
|
+
LEFT JOIN files tf ON tf.id=e.to_file_id`;
|
|
151
|
+
function fileRow(row) {
|
|
152
|
+
return {
|
|
153
|
+
id: number(row.id),
|
|
154
|
+
path: text(row.path),
|
|
155
|
+
language: text(row.language),
|
|
156
|
+
size: number(row.size),
|
|
157
|
+
hash: text(row.hash),
|
|
158
|
+
...(text(row.module) === "" ? {} : { module: text(row.module) }),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function symbolRow(row) {
|
|
162
|
+
return {
|
|
163
|
+
id: number(row.id),
|
|
164
|
+
name: text(row.name),
|
|
165
|
+
kind: text(row.kind),
|
|
166
|
+
path: text(row.path),
|
|
167
|
+
startLine: number(row.start_line),
|
|
168
|
+
startColumn: number(row.start_col),
|
|
169
|
+
endLine: number(row.end_line),
|
|
170
|
+
endColumn: number(row.end_col),
|
|
171
|
+
...(row.container_id === null ? {} : { containerId: number(row.container_id) }),
|
|
172
|
+
exported: number(row.exported) !== 0,
|
|
173
|
+
language: text(row.language),
|
|
174
|
+
...(text(row.adapter_data) === "" ? {} : { metadata: text(row.adapter_data) }),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function edgeRow(row) {
|
|
178
|
+
return {
|
|
179
|
+
id: number(row.id),
|
|
180
|
+
fromPath: text(row.from_path),
|
|
181
|
+
...(row.from_symbol_id === null ? {} : { fromSymbolId: number(row.from_symbol_id) }),
|
|
182
|
+
...(text(row.to_path) === "" ? {} : { toPath: text(row.to_path) }),
|
|
183
|
+
...(row.to_symbol_id === null ? {} : { toSymbolId: number(row.to_symbol_id) }),
|
|
184
|
+
...(text(row.unresolved_target) === ""
|
|
185
|
+
? {}
|
|
186
|
+
: { unresolvedTarget: text(row.unresolved_target) }),
|
|
187
|
+
kind: text(row.kind),
|
|
188
|
+
line: number(row.line),
|
|
189
|
+
column: number(row.column),
|
|
190
|
+
confidence: number(row.confidence),
|
|
191
|
+
adapter: text(row.adapter),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function testLinkRow(row) {
|
|
195
|
+
return {
|
|
196
|
+
id: number(row.id),
|
|
197
|
+
sourcePath: text(row.source_path),
|
|
198
|
+
...(row.source_symbol_id === null ? {} : { sourceSymbolId: number(row.source_symbol_id) }),
|
|
199
|
+
testPath: text(row.test_path),
|
|
200
|
+
...(row.test_symbol_id === null ? {} : { testSymbolId: number(row.test_symbol_id) }),
|
|
201
|
+
confidence: number(row.confidence),
|
|
202
|
+
reason: text(row.reason),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function page(items, limit, id) {
|
|
206
|
+
const hasMore = items.length > limit;
|
|
207
|
+
const bounded = hasMore ? items.slice(0, limit) : items;
|
|
208
|
+
const nextCursor = hasMore ? String(id(bounded[bounded.length - 1])) : undefined;
|
|
209
|
+
return {
|
|
210
|
+
items: bounded,
|
|
211
|
+
...(nextCursor === undefined ? {} : { nextCursor }),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function testLinkPage(items, limit) {
|
|
215
|
+
const bounded = page(items, limit, (item) => item.id);
|
|
216
|
+
return {
|
|
217
|
+
items: bounded.items.map(({ id: _id, ...item }) => item),
|
|
218
|
+
...(bounded.nextCursor === undefined ? {} : { nextCursor: bounded.nextCursor }),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function bounds(limitValue, cursorValue) {
|
|
222
|
+
const limit = limitValue ?? 100;
|
|
223
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 500) {
|
|
224
|
+
throw new Error("limit must be an integer from 1 through 500");
|
|
225
|
+
}
|
|
226
|
+
const cursor = cursorValue === undefined || cursorValue === "" ? 0 : Number(cursorValue);
|
|
227
|
+
if (!Number.isSafeInteger(cursor) || cursor < 0) {
|
|
228
|
+
throw new Error("cursor must be a non-negative integer");
|
|
229
|
+
}
|
|
230
|
+
return { limit, cursor };
|
|
231
|
+
}
|
|
232
|
+
function validPath(path) {
|
|
233
|
+
const normalized = normalizePath(path);
|
|
234
|
+
if (normalized === "" ||
|
|
235
|
+
normalized.startsWith("/") ||
|
|
236
|
+
normalized === ".." ||
|
|
237
|
+
normalized.startsWith("../") ||
|
|
238
|
+
normalized.includes("/../") ||
|
|
239
|
+
normalized.includes("\0") ||
|
|
240
|
+
normalized.includes("//")) {
|
|
241
|
+
throw new Error("path must be normalized and repository-relative");
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function normalizePath(path) {
|
|
245
|
+
return path.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
246
|
+
}
|
|
247
|
+
function globToLike(glob) {
|
|
248
|
+
if (glob.includes("..") || glob.startsWith("/") || glob.includes("\0")) {
|
|
249
|
+
throw new Error("glob must be repository-relative");
|
|
250
|
+
}
|
|
251
|
+
return normalizePath(glob)
|
|
252
|
+
.replaceAll("\\", "\\\\")
|
|
253
|
+
.replaceAll("%", "\\%")
|
|
254
|
+
.replaceAll("_", "\\_")
|
|
255
|
+
.replaceAll("*", "%")
|
|
256
|
+
.replaceAll("?", "_");
|
|
257
|
+
}
|
|
258
|
+
function text(value) {
|
|
259
|
+
if (typeof value === "string")
|
|
260
|
+
return value;
|
|
261
|
+
if (value === null || value === undefined)
|
|
262
|
+
return "";
|
|
263
|
+
throw new Error("repo graph returned a non-string value");
|
|
264
|
+
}
|
|
265
|
+
function number(value) {
|
|
266
|
+
if (typeof value === "number")
|
|
267
|
+
return value;
|
|
268
|
+
if (typeof value === "bigint")
|
|
269
|
+
return Number(value);
|
|
270
|
+
throw new Error("repo graph returned a non-number value");
|
|
271
|
+
}
|
|
272
|
+
//# sourceMappingURL=repo-graph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repo-graph.js","sourceRoot":"","sources":["../src/repo-graph.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { DatabaseSync } from \"node:sqlite\";\n\nexport const ADVERSARY_REPO_GRAPH_ENV = \"ADVERSARY_REPO_GRAPH\";\nexport const REPO_GRAPH_SCHEMA_VERSION = \"v2\";\nexport const REPO_GRAPH_ADAPTER_REVISION = \"go-ast-v1+ts-syntax-v1\";\n\nexport interface RepoGraphMeta {\n schemaVersion: string;\n adapterRevision: string;\n fingerprint: string;\n repoPath: string;\n builtAt: string;\n durationMs: number;\n fileCount: number;\n symbolCount: number;\n edgeCount: number;\n testLinkCount: number;\n parseFailures?: readonly RepoGraphDiagnostic[];\n}\n\nexport interface RepoGraphDiagnostic {\n path: string;\n adapter: string;\n message: string;\n}\n\nexport interface RepoGraphFile {\n id: number;\n path: string;\n language: string;\n size: number;\n hash: string;\n module?: string;\n}\n\nexport interface RepoGraphSymbol {\n id: number;\n name: string;\n kind: string;\n path: string;\n startLine: number;\n startColumn: number;\n endLine: number;\n endColumn: number;\n containerId?: number;\n exported: boolean;\n language: string;\n metadata?: string;\n}\n\nexport interface RepoGraphEdge {\n id: number;\n fromPath: string;\n fromSymbolId?: number;\n toPath?: string;\n toSymbolId?: number;\n unresolvedTarget?: string;\n kind: string;\n line: number;\n column: number;\n confidence: number;\n adapter: string;\n}\n\nexport interface RepoGraphTestLink {\n sourcePath: string;\n sourceSymbolId?: number;\n testPath: string;\n testSymbolId?: number;\n confidence: number;\n reason: string;\n}\n\nexport interface RepoGraphPage<T> {\n items: readonly T[];\n nextCursor?: string;\n}\n\nexport interface RepoGraphFileQuery {\n language?: string;\n glob?: string;\n cursor?: string;\n limit?: number;\n}\n\nexport interface RepoGraphSymbolQuery {\n path?: string;\n name?: string;\n kind?: string;\n cursor?: string;\n limit?: number;\n}\n\nexport interface RepoGraphRelationQuery {\n symbolId: number;\n cursor?: string;\n limit?: number;\n}\n\nexport interface RepoGraph {\n readonly dir: string;\n readonly meta: RepoGraphMeta;\n files(query?: RepoGraphFileQuery): RepoGraphPage<RepoGraphFile>;\n symbolAt(path: string, line: number, column?: number): RepoGraphSymbol | undefined;\n symbols(query?: RepoGraphSymbolQuery): RepoGraphPage<RepoGraphSymbol>;\n definitions(query: RepoGraphSymbolQuery): RepoGraphPage<RepoGraphSymbol>;\n references(query: RepoGraphRelationQuery): RepoGraphPage<RepoGraphEdge>;\n callers(query: RepoGraphRelationQuery): RepoGraphPage<RepoGraphEdge>;\n callees(query: RepoGraphRelationQuery): RepoGraphPage<RepoGraphEdge>;\n implementations(query: RepoGraphRelationQuery): RepoGraphPage<RepoGraphEdge>;\n importsOf(path: string, cursor?: string, limit?: number): RepoGraphPage<RepoGraphEdge>;\n importersOf(path: string, cursor?: string, limit?: number): RepoGraphPage<RepoGraphEdge>;\n relatedTests(options: {\n path?: string;\n symbolId?: number;\n cursor?: string;\n limit?: number;\n }): RepoGraphPage<RepoGraphTestLink>;\n close(): void;\n}\n\nexport class RepoGraphUnavailableError extends Error {\n readonly code = \"repo_graph_unavailable\";\n\n constructor(message: string) {\n super(message);\n this.name = \"RepoGraphUnavailableError\";\n }\n}\n\nexport async function openRepoGraph(dir: string): Promise<RepoGraph> {\n const raw = await readFile(join(dir, \"meta.json\"), \"utf8\");\n const meta = JSON.parse(raw) as RepoGraphMeta;\n if (\n meta.schemaVersion !== REPO_GRAPH_SCHEMA_VERSION ||\n meta.adapterRevision !== REPO_GRAPH_ADAPTER_REVISION\n ) {\n throw new RepoGraphUnavailableError(\n `unsupported repo-graph schema ${meta.schemaVersion}/${meta.adapterRevision}`,\n );\n }\n const database = new DatabaseSync(join(dir, \"graph.sqlite\"), { readOnly: true });\n return new SQLiteRepoGraph(dir, meta, database);\n}\n\nexport async function repoGraphFromEnvironment(\n env: Record<string, string | undefined> = process.env,\n): Promise<RepoGraph | null> {\n const dir = env[ADVERSARY_REPO_GRAPH_ENV]?.trim();\n if (!dir) return null;\n try {\n return await openRepoGraph(dir);\n } catch {\n return null;\n }\n}\n\nclass SQLiteRepoGraph implements RepoGraph {\n constructor(\n readonly dir: string,\n readonly meta: RepoGraphMeta,\n private readonly database: DatabaseSync,\n ) {}\n\n files(query: RepoGraphFileQuery = {}): RepoGraphPage<RepoGraphFile> {\n const { limit, cursor } = bounds(query.limit, query.cursor);\n const glob = query.glob === undefined ? \"\" : globToLike(query.glob);\n const rows = this.database\n .prepare(`SELECT id,path,language,size,hash,module FROM files\n WHERE id > ? AND (? = '' OR language = ?) AND (? = '' OR path LIKE ? ESCAPE '\\\\')\n ORDER BY id LIMIT ?`)\n .all(cursor, query.language ?? \"\", query.language ?? \"\", glob, glob, limit + 1);\n return page(rows.map(fileRow), limit, (item) => item.id);\n }\n\n symbolAt(path: string, line: number, column = 0): RepoGraphSymbol | undefined {\n validPath(path);\n if (!Number.isInteger(line) || line < 1 || !Number.isInteger(column) || column < 0) {\n throw new Error(\"line must be positive and column non-negative\");\n }\n const row = this.database\n .prepare(`${symbolSelect}\n WHERE f.path=? AND (s.start_line < ? OR (s.start_line=? AND s.start_col<=?))\n AND (s.end_line > ? OR (s.end_line=? AND s.end_col>=?))\n ORDER BY (s.end_line-s.start_line) ASC, s.id ASC LIMIT 1`)\n .get(normalizePath(path), line, line, column, line, line, column);\n return row === undefined ? undefined : symbolRow(row);\n }\n\n symbols(query: RepoGraphSymbolQuery = {}): RepoGraphPage<RepoGraphSymbol> {\n if (query.path !== undefined) validPath(query.path);\n const { limit, cursor } = bounds(query.limit, query.cursor);\n const rows = this.database\n .prepare(`${symbolSelect}\n WHERE s.id>? AND (?='' OR f.path=?) AND (?='' OR s.name=?) AND (?='' OR s.kind=?)\n ORDER BY s.id LIMIT ?`)\n .all(\n cursor,\n query.path ?? \"\",\n normalizePath(query.path ?? \"\"),\n query.name ?? \"\",\n query.name ?? \"\",\n query.kind ?? \"\",\n query.kind ?? \"\",\n limit + 1,\n );\n return page(rows.map(symbolRow), limit, (item) => item.id);\n }\n\n definitions(query: RepoGraphSymbolQuery): RepoGraphPage<RepoGraphSymbol> {\n return this.symbols(query);\n }\n\n references(query: RepoGraphRelationQuery): RepoGraphPage<RepoGraphEdge> {\n return this.relations(query, \"references\", false);\n }\n\n callers(query: RepoGraphRelationQuery): RepoGraphPage<RepoGraphEdge> {\n return this.relations(query, \"calls\", false);\n }\n\n callees(query: RepoGraphRelationQuery): RepoGraphPage<RepoGraphEdge> {\n return this.relations(query, \"calls\", true);\n }\n\n implementations(query: RepoGraphRelationQuery): RepoGraphPage<RepoGraphEdge> {\n return this.relations(query, \"implements\", false);\n }\n\n importsOf(path: string, cursor?: string, limit?: number): RepoGraphPage<RepoGraphEdge> {\n return this.fileRelations(path, cursor, limit, true);\n }\n\n importersOf(path: string, cursor?: string, limit?: number): RepoGraphPage<RepoGraphEdge> {\n return this.fileRelations(path, cursor, limit, false);\n }\n\n relatedTests(options: {\n path?: string;\n symbolId?: number;\n cursor?: string;\n limit?: number;\n }): RepoGraphPage<RepoGraphTestLink> {\n if (options.path !== undefined) validPath(options.path);\n const symbolId = options.symbolId ?? 0;\n if (!Number.isInteger(symbolId) || symbolId < 0)\n throw new Error(\"symbolId must be non-negative\");\n const { limit, cursor } = bounds(options.limit, options.cursor);\n const rows = this.database\n .prepare(`SELECT tl.id,sf.path AS source_path,\n tl.source_symbol_id,tf.path AS test_path,tl.test_symbol_id,tl.confidence,tl.reason\n FROM test_links tl JOIN files sf ON sf.id=tl.source_file_id\n JOIN files tf ON tf.id=tl.test_file_id\n WHERE tl.id>? AND (?='' OR sf.path=?)\n AND (?=0 OR tl.source_symbol_id=? OR sf.id=(SELECT file_id FROM symbols WHERE id=?))\n ORDER BY tl.id LIMIT ?`)\n .all(\n cursor,\n options.path ?? \"\",\n normalizePath(options.path ?? \"\"),\n symbolId,\n symbolId,\n symbolId,\n limit + 1,\n );\n return testLinkPage(rows.map(testLinkRow), limit);\n }\n\n close(): void {\n this.database.close();\n }\n\n private relations(\n query: RepoGraphRelationQuery,\n kind: string,\n outgoing: boolean,\n ): RepoGraphPage<RepoGraphEdge> {\n if (!Number.isInteger(query.symbolId) || query.symbolId < 1) {\n throw new Error(\"symbolId must be positive\");\n }\n const { limit, cursor } = bounds(query.limit, query.cursor);\n const column = outgoing ? \"from_symbol_id\" : \"to_symbol_id\";\n const rows = this.database\n .prepare(`${edgeSelect}\n WHERE e.id>? AND e.kind=? AND e.${column}=? ORDER BY e.id LIMIT ?`)\n .all(cursor, kind, query.symbolId, limit + 1);\n return page(rows.map(edgeRow), limit, (item) => item.id);\n }\n\n private fileRelations(\n path: string,\n cursorValue: string | undefined,\n limitValue: number | undefined,\n outgoing: boolean,\n ): RepoGraphPage<RepoGraphEdge> {\n validPath(path);\n const { limit, cursor } = bounds(limitValue, cursorValue);\n const condition = outgoing ? \"ff.path=?\" : \"tf.module=(SELECT module FROM files WHERE path=?)\";\n const rows = this.database\n .prepare(`${edgeSelect}\n WHERE e.id>? AND e.kind='imports' AND ${condition} ORDER BY e.id LIMIT ?`)\n .all(cursor, normalizePath(path), limit + 1);\n return page(rows.map(edgeRow), limit, (item) => item.id);\n }\n}\n\nconst symbolSelect = `SELECT s.id,s.name,s.kind,f.path,s.start_line,s.start_col,\n s.end_line,s.end_col,s.container_id,s.exported,f.language,s.adapter_data\n FROM symbols s JOIN files f ON f.id=s.file_id`;\n\nconst edgeSelect = `SELECT e.id,ff.path AS from_path,e.from_symbol_id,\n COALESCE(tf.path,'') AS to_path,e.to_symbol_id,\n COALESCE(e.unresolved_target,'') AS unresolved_target,e.kind,e.line,e.column,\n e.confidence,e.adapter FROM edges e JOIN files ff ON ff.id=e.from_file_id\n LEFT JOIN files tf ON tf.id=e.to_file_id`;\n\ninterface RowRecord {\n [key: string]: unknown;\n}\n\nfunction fileRow(row: RowRecord): RepoGraphFile {\n return {\n id: number(row.id),\n path: text(row.path),\n language: text(row.language),\n size: number(row.size),\n hash: text(row.hash),\n ...(text(row.module) === \"\" ? {} : { module: text(row.module) }),\n };\n}\n\nfunction symbolRow(row: RowRecord): RepoGraphSymbol {\n return {\n id: number(row.id),\n name: text(row.name),\n kind: text(row.kind),\n path: text(row.path),\n startLine: number(row.start_line),\n startColumn: number(row.start_col),\n endLine: number(row.end_line),\n endColumn: number(row.end_col),\n ...(row.container_id === null ? {} : { containerId: number(row.container_id) }),\n exported: number(row.exported) !== 0,\n language: text(row.language),\n ...(text(row.adapter_data) === \"\" ? {} : { metadata: text(row.adapter_data) }),\n };\n}\n\nfunction edgeRow(row: RowRecord): RepoGraphEdge {\n return {\n id: number(row.id),\n fromPath: text(row.from_path),\n ...(row.from_symbol_id === null ? {} : { fromSymbolId: number(row.from_symbol_id) }),\n ...(text(row.to_path) === \"\" ? {} : { toPath: text(row.to_path) }),\n ...(row.to_symbol_id === null ? {} : { toSymbolId: number(row.to_symbol_id) }),\n ...(text(row.unresolved_target) === \"\"\n ? {}\n : { unresolvedTarget: text(row.unresolved_target) }),\n kind: text(row.kind),\n line: number(row.line),\n column: number(row.column),\n confidence: number(row.confidence),\n adapter: text(row.adapter),\n };\n}\n\ninterface TestLinkWithID extends RepoGraphTestLink {\n id: number;\n}\n\nfunction testLinkRow(row: RowRecord): TestLinkWithID {\n return {\n id: number(row.id),\n sourcePath: text(row.source_path),\n ...(row.source_symbol_id === null ? {} : { sourceSymbolId: number(row.source_symbol_id) }),\n testPath: text(row.test_path),\n ...(row.test_symbol_id === null ? {} : { testSymbolId: number(row.test_symbol_id) }),\n confidence: number(row.confidence),\n reason: text(row.reason),\n };\n}\n\nfunction page<T>(items: T[], limit: number, id: (item: T) => number): RepoGraphPage<T> {\n const hasMore = items.length > limit;\n const bounded = hasMore ? items.slice(0, limit) : items;\n const nextCursor = hasMore ? String(id(bounded[bounded.length - 1] as T)) : undefined;\n return {\n items: bounded,\n ...(nextCursor === undefined ? {} : { nextCursor }),\n };\n}\n\nfunction testLinkPage(items: TestLinkWithID[], limit: number): RepoGraphPage<RepoGraphTestLink> {\n const bounded = page(items, limit, (item) => item.id);\n return {\n items: bounded.items.map(({ id: _id, ...item }) => item),\n ...(bounded.nextCursor === undefined ? {} : { nextCursor: bounded.nextCursor }),\n };\n}\n\nfunction bounds(limitValue?: number, cursorValue?: string): { limit: number; cursor: number } {\n const limit = limitValue ?? 100;\n if (!Number.isInteger(limit) || limit < 1 || limit > 500) {\n throw new Error(\"limit must be an integer from 1 through 500\");\n }\n const cursor = cursorValue === undefined || cursorValue === \"\" ? 0 : Number(cursorValue);\n if (!Number.isSafeInteger(cursor) || cursor < 0) {\n throw new Error(\"cursor must be a non-negative integer\");\n }\n return { limit, cursor };\n}\n\nfunction validPath(path: string): void {\n const normalized = normalizePath(path);\n if (\n normalized === \"\" ||\n normalized.startsWith(\"/\") ||\n normalized === \"..\" ||\n normalized.startsWith(\"../\") ||\n normalized.includes(\"/../\") ||\n normalized.includes(\"\\0\") ||\n normalized.includes(\"//\")\n ) {\n throw new Error(\"path must be normalized and repository-relative\");\n }\n}\n\nfunction normalizePath(path: string): string {\n return path.replaceAll(\"\\\\\", \"/\").replace(/^\\.\\//, \"\");\n}\n\nfunction globToLike(glob: string): string {\n if (glob.includes(\"..\") || glob.startsWith(\"/\") || glob.includes(\"\\0\")) {\n throw new Error(\"glob must be repository-relative\");\n }\n return normalizePath(glob)\n .replaceAll(\"\\\\\", \"\\\\\\\\\")\n .replaceAll(\"%\", \"\\\\%\")\n .replaceAll(\"_\", \"\\\\_\")\n .replaceAll(\"*\", \"%\")\n .replaceAll(\"?\", \"_\");\n}\n\nfunction text(value: unknown): string {\n if (typeof value === \"string\") return value;\n if (value === null || value === undefined) return \"\";\n throw new Error(\"repo graph returned a non-string value\");\n}\n\nfunction number(value: unknown): number {\n if (typeof value === \"number\") return value;\n if (typeof value === \"bigint\") return Number(value);\n throw new Error(\"repo graph returned a non-number value\");\n}\n"]}
|
|
@@ -27,6 +27,12 @@ export interface ModelRepositoryRetrieval {
|
|
|
27
27
|
directoriesListed: number;
|
|
28
28
|
exhausted: boolean;
|
|
29
29
|
}
|
|
30
|
+
export interface ModelRepositoryChange {
|
|
31
|
+
baseRef?: string;
|
|
32
|
+
headRef?: string;
|
|
33
|
+
changedFiles: readonly string[];
|
|
34
|
+
worktree: boolean;
|
|
35
|
+
}
|
|
30
36
|
export declare function resolveModelCitation(citations: readonly ModelRepositoryCitation[] | undefined, citationId: string, line: number): ModelRepositoryCitation | undefined;
|
|
31
|
-
export declare function reviewWithRepositoryTools<T>(model: ReviewModel, repositoryRoot: string | undefined, request: ModelReviewRequest): Promise<ModelReviewResult<T>>;
|
|
37
|
+
export declare function reviewWithRepositoryTools<T>(model: ReviewModel, repositoryRoot: string | undefined, request: ModelReviewRequest, change?: ModelRepositoryChange | null): Promise<ModelReviewResult<T>>;
|
|
32
38
|
//# sourceMappingURL=repository-model.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repository-model.d.ts","sourceRoot":"","sources":["../src/repository-model.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"repository-model.d.ts","sourceRoot":"","sources":["../src/repository-model.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EAEtB,KAAK,WAAW,EACjB,MAAM,YAAY,CAAC;AAmCpB,MAAM,WAAW,0BAA0B;IACzC,kFAAkF;IAClF,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,uEAAuE;IACvE,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,SAAS,uBAAuB,EAAE,GAAG,SAAS,EACzD,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,GACX,uBAAuB,GAAG,SAAS,CAOrC;AAgHD,wBAAsB,yBAAyB,CAAC,CAAC,EAC/C,KAAK,EAAE,WAAW,EAClB,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,OAAO,EAAE,kBAAkB,EAC3B,MAAM,CAAC,EAAE,qBAAqB,GAAG,IAAI,GACpC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAkL/B"}
|
package/dist/repository-model.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
1
2
|
import { createReadStream } from "node:fs";
|
|
2
3
|
import { lstat, readdir, realpath } from "node:fs/promises";
|
|
3
4
|
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
4
5
|
import { createInterface } from "node:readline";
|
|
6
|
+
import { promisify } from "node:util";
|
|
5
7
|
import { ModelReviewError, } from "./model.js";
|
|
6
8
|
const DEFAULT_MAX_ROUNDS = 6;
|
|
7
9
|
const MAX_MAX_ROUNDS = 12;
|
|
@@ -21,6 +23,7 @@ const MAX_OPERATION_PATH_LENGTH = 4_096;
|
|
|
21
23
|
const MAX_OPERATIONS_PER_ROUND = 8;
|
|
22
24
|
const PLANNING_OUTPUT_TOKENS = 1_500;
|
|
23
25
|
const DEFAULT_PLANNING_TIMEOUT_MS = 120_000;
|
|
26
|
+
const execFileAsync = promisify(execFile);
|
|
24
27
|
const defaultExcludedSegments = new Set([
|
|
25
28
|
".git",
|
|
26
29
|
".hg",
|
|
@@ -60,7 +63,7 @@ const repositoryPlanSchema = {
|
|
|
60
63
|
additionalProperties: false,
|
|
61
64
|
required: ["tool", "path", "cursor", "startLine", "endLine"],
|
|
62
65
|
properties: {
|
|
63
|
-
tool: { type: "string", enum: ["list_directory", "read_file"] },
|
|
66
|
+
tool: { type: "string", enum: ["list_directory", "read_file", "read_change"] },
|
|
64
67
|
path: { type: "string" },
|
|
65
68
|
cursor: {
|
|
66
69
|
type: "integer",
|
|
@@ -79,7 +82,7 @@ const repositoryPlanSchema = {
|
|
|
79
82
|
},
|
|
80
83
|
},
|
|
81
84
|
};
|
|
82
|
-
export async function reviewWithRepositoryTools(model, repositoryRoot, request) {
|
|
85
|
+
export async function reviewWithRepositoryTools(model, repositoryRoot, request, change) {
|
|
83
86
|
if (repositoryRoot === undefined || repositoryRoot.trim() === "") {
|
|
84
87
|
throw new ModelReviewError("Repository model tools require a rule-context repository root.", {
|
|
85
88
|
code: "invalid_model_request",
|
|
@@ -103,6 +106,17 @@ export async function reviewWithRepositoryTools(model, repositoryRoot, request)
|
|
|
103
106
|
let exhausted = false;
|
|
104
107
|
let ready = false;
|
|
105
108
|
let usage = {};
|
|
109
|
+
if (change !== undefined && change !== null) {
|
|
110
|
+
const summary = {
|
|
111
|
+
tool: "change_summary",
|
|
112
|
+
...(change.baseRef === undefined ? {} : { baseRef: change.baseRef }),
|
|
113
|
+
...(change.headRef === undefined ? {} : { headRef: change.headRef }),
|
|
114
|
+
changedFiles: change.changedFiles.slice(0, 500),
|
|
115
|
+
worktree: change.worktree,
|
|
116
|
+
};
|
|
117
|
+
toolResults.push(summary);
|
|
118
|
+
totalBytes += encodedBytes(summary);
|
|
119
|
+
}
|
|
106
120
|
const initial = fitDirectoryResult(await executeListDirectory(root, ".", 0, budget.directoryPageSize, include, exclude), budget.maxTotalBytes);
|
|
107
121
|
toolResults.push(initial);
|
|
108
122
|
totalBytes += encodedBytes(initial);
|
|
@@ -155,7 +169,7 @@ export async function reviewWithRepositoryTools(model, repositoryRoot, request)
|
|
|
155
169
|
result = await executeListDirectory(root, operation.path, operation.cursor, budget.directoryPageSize, include, exclude);
|
|
156
170
|
directoriesListed += 1;
|
|
157
171
|
}
|
|
158
|
-
else {
|
|
172
|
+
else if (operation.tool === "read_file") {
|
|
159
173
|
result = await executeReadFile(root, operation, budget, include, exclude, `repo:read:${citations.length + 1}`);
|
|
160
174
|
pendingCitation = {
|
|
161
175
|
citationId: result.citationId,
|
|
@@ -165,6 +179,9 @@ export async function reviewWithRepositoryTools(model, repositoryRoot, request)
|
|
|
165
179
|
content: result.content,
|
|
166
180
|
};
|
|
167
181
|
}
|
|
182
|
+
else {
|
|
183
|
+
result = await executeReadChange(root, operation, budget, include, exclude, change);
|
|
184
|
+
}
|
|
168
185
|
}
|
|
169
186
|
catch (error) {
|
|
170
187
|
result = {
|
|
@@ -242,6 +259,7 @@ ${prompt}
|
|
|
242
259
|
RETRIEVAL RULES:
|
|
243
260
|
- list_directory reveals one deterministic, paginated directory page. Use cursor=0 initially and nextCursor from a prior result for another page. Set startLine=0 and endLine=0.
|
|
244
261
|
- read_file retrieves an inclusive 1-based line range and creates an immutable citation. Set cursor=0.
|
|
262
|
+
- read_change retrieves the patch for one path in change_summary. Set cursor=0, startLine=0, and endLine=0. Use it before judging changed behavior. It is navigation evidence, not a source citation; cite exact lines from a subsequent read_file.
|
|
245
263
|
- Inspect implementation and relevant tests before setting ready=true.
|
|
246
264
|
- Traverse only directories relevant to the requested review; do not inventory the entire repository.
|
|
247
265
|
- Prefer focused line ranges around important behavior over whole files.
|
|
@@ -413,6 +431,51 @@ async function executeReadFile(root, operation, budget, include, exclude, citati
|
|
|
413
431
|
truncated,
|
|
414
432
|
};
|
|
415
433
|
}
|
|
434
|
+
async function executeReadChange(root, operation, budget, include, exclude, change) {
|
|
435
|
+
if (change === undefined || change === null || change.baseRef === undefined) {
|
|
436
|
+
throw new Error("read_change requires a runner-provided change context");
|
|
437
|
+
}
|
|
438
|
+
const { relativePath } = await secureRepositoryPath(root, operation.path, "file");
|
|
439
|
+
if (!change.changedFiles.includes(relativePath)) {
|
|
440
|
+
throw new Error("read_change path is not in the runner-provided change set");
|
|
441
|
+
}
|
|
442
|
+
if (!isIncluded(relativePath, include) || isExcluded(relativePath, exclude)) {
|
|
443
|
+
throw new Error("read_change path is outside the configured repository file set");
|
|
444
|
+
}
|
|
445
|
+
const baseRef = validRevision(change.baseRef);
|
|
446
|
+
const headRef = change.worktree ? "WORKTREE" : validRevision(change.headRef ?? "");
|
|
447
|
+
const revisions = change.worktree ? [baseRef] : [baseRef, headRef];
|
|
448
|
+
const { stdout } = await execFileAsync("git", [
|
|
449
|
+
"-C",
|
|
450
|
+
root,
|
|
451
|
+
"--no-pager",
|
|
452
|
+
"diff",
|
|
453
|
+
"--no-ext-diff",
|
|
454
|
+
"--unified=40",
|
|
455
|
+
"--find-renames",
|
|
456
|
+
...revisions,
|
|
457
|
+
"--",
|
|
458
|
+
relativePath,
|
|
459
|
+
], { encoding: "utf8", maxBuffer: Math.max(budget.maxBytesPerRead * 4, 1 << 20) });
|
|
460
|
+
const encoded = Buffer.from(stdout, "utf8");
|
|
461
|
+
const truncated = encoded.byteLength > budget.maxBytesPerRead;
|
|
462
|
+
const content = truncated
|
|
463
|
+
? new TextDecoder().decode(encoded.subarray(0, budget.maxBytesPerRead))
|
|
464
|
+
: stdout;
|
|
465
|
+
return { tool: "read_change", path: relativePath, baseRef, headRef, content, truncated };
|
|
466
|
+
}
|
|
467
|
+
function validRevision(value) {
|
|
468
|
+
const revision = value.trim();
|
|
469
|
+
if (revision === "" ||
|
|
470
|
+
revision.length > 512 ||
|
|
471
|
+
revision.startsWith("-") ||
|
|
472
|
+
revision.includes("\0") ||
|
|
473
|
+
revision.includes("\n") ||
|
|
474
|
+
revision.includes("\r")) {
|
|
475
|
+
throw new Error("change revision is invalid");
|
|
476
|
+
}
|
|
477
|
+
return revision;
|
|
478
|
+
}
|
|
416
479
|
async function secureRepositoryPath(root, requestedPath, kind) {
|
|
417
480
|
const normalized = requestedPath
|
|
418
481
|
.trim()
|
|
@@ -470,7 +533,9 @@ function requireRepositoryPlan(value) {
|
|
|
470
533
|
function operationKey(operation) {
|
|
471
534
|
return operation.tool === "list_directory"
|
|
472
535
|
? `${operation.tool}:${operation.path}:${operation.cursor}`
|
|
473
|
-
:
|
|
536
|
+
: operation.tool === "read_change"
|
|
537
|
+
? `${operation.tool}:${operation.path}`
|
|
538
|
+
: `${operation.tool}:${operation.path}:${operation.startLine}:${operation.endLine}`;
|
|
474
539
|
}
|
|
475
540
|
function encodedBytes(value) {
|
|
476
541
|
return Buffer.byteLength(JSON.stringify(value), "utf8");
|