@adversarylabs/sdk 0.1.15 → 0.1.16

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.
@@ -0,0 +1,54 @@
1
+ /** Environment variable set by the CLI pointing at an on-disk repo index directory. */
2
+ export declare const ADVERSARY_REPO_INDEX_ENV = "ADVERSARY_REPO_INDEX";
3
+ export declare const REPO_INDEX_SCHEMA_VERSION = "v1";
4
+ export interface RepoIndexMeta {
5
+ schemaVersion: string;
6
+ fingerprint: string;
7
+ repoPath: string;
8
+ builtAt?: string;
9
+ fileCount: number;
10
+ edgeCount: number;
11
+ }
12
+ export interface RepoIndexFile {
13
+ path: string;
14
+ language: string;
15
+ size: number;
16
+ hash: string;
17
+ }
18
+ export interface RepoIndexEdge {
19
+ from: string;
20
+ to: string;
21
+ kind: string;
22
+ }
23
+ export interface ListFilesOptions {
24
+ /** Exact language match: go | typescript | javascript */
25
+ language?: string;
26
+ /** Maximum files to return (default 5000). */
27
+ limit?: number;
28
+ }
29
+ /**
30
+ * Read-only navigation over a CLI-built repository index (files + import edges).
31
+ */
32
+ export interface RepoIndex {
33
+ readonly dir: string;
34
+ readonly meta: RepoIndexMeta;
35
+ listFiles(options?: ListFilesOptions): Promise<RepoIndexFile[]>;
36
+ file(path: string): Promise<RepoIndexFile | undefined>;
37
+ importsOf(path: string): Promise<RepoIndexEdge[]>;
38
+ importersOf(path: string): Promise<RepoIndexEdge[]>;
39
+ }
40
+ export declare class RepoIndexUnavailableError extends Error {
41
+ readonly code = "repo_index_unavailable";
42
+ constructor(message: string);
43
+ }
44
+ /** Open an index directory written by the adversary CLI (JSONL schema v1). */
45
+ export declare function openRepoIndex(dir: string): Promise<RepoIndex>;
46
+ /**
47
+ * Load index from ADVERSARY_REPO_INDEX when set; otherwise return null.
48
+ *
49
+ * A missing, deleted, or malformed index is treated as unavailable (null), not a
50
+ * hard run failure — rules already null-check `ctx.repoIndex`. Explicit opens via
51
+ * `openRepoIndex` still throw `RepoIndexUnavailableError`.
52
+ */
53
+ export declare function repoIndexFromEnvironment(env?: Record<string, string | undefined>): Promise<RepoIndex | null>;
54
+ //# sourceMappingURL=repo-index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo-index.d.ts","sourceRoot":"","sources":["../src/repo-index.ts"],"names":[],"mappings":"AAIA,uFAAuF;AACvF,eAAO,MAAM,wBAAwB,yBAAyB,CAAC;AAE/D,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAE9C,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,SAAS,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IAChE,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAAC;IACvD,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IAClD,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;CACrD;AAED,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,QAAQ,CAAC,IAAI,4BAA4B;gBAE7B,OAAO,EAAE,MAAM;CAI5B;AAED,8EAA8E;AAC9E,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAWnE;AAED;;;;;;GAMG;AACH,wBAAsB,wBAAwB,CAC5C,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GACpD,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAU3B"}
@@ -0,0 +1,120 @@
1
+ import { open, readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { createInterface } from "node:readline";
4
+ /** Environment variable set by the CLI pointing at an on-disk repo index directory. */
5
+ export const ADVERSARY_REPO_INDEX_ENV = "ADVERSARY_REPO_INDEX";
6
+ export const REPO_INDEX_SCHEMA_VERSION = "v1";
7
+ export class RepoIndexUnavailableError extends Error {
8
+ code = "repo_index_unavailable";
9
+ constructor(message) {
10
+ super(message);
11
+ this.name = "RepoIndexUnavailableError";
12
+ }
13
+ }
14
+ /** Open an index directory written by the adversary CLI (JSONL schema v1). */
15
+ export async function openRepoIndex(dir) {
16
+ const metaRaw = await readFile(join(dir, "meta.json"), "utf8");
17
+ const meta = JSON.parse(metaRaw);
18
+ if (meta.schemaVersion !== REPO_INDEX_SCHEMA_VERSION) {
19
+ throw new RepoIndexUnavailableError(`unsupported repo-index schema ${meta.schemaVersion} (want ${REPO_INDEX_SCHEMA_VERSION})`);
20
+ }
21
+ const files = await readJsonl(join(dir, "files.jsonl"));
22
+ const edges = await readJsonl(join(dir, "edges.jsonl"));
23
+ return new MemoryRepoIndex(dir, meta, files, edges);
24
+ }
25
+ /**
26
+ * Load index from ADVERSARY_REPO_INDEX when set; otherwise return null.
27
+ *
28
+ * A missing, deleted, or malformed index is treated as unavailable (null), not a
29
+ * hard run failure — rules already null-check `ctx.repoIndex`. Explicit opens via
30
+ * `openRepoIndex` still throw `RepoIndexUnavailableError`.
31
+ */
32
+ export async function repoIndexFromEnvironment(env = process.env) {
33
+ const dir = env[ADVERSARY_REPO_INDEX_ENV]?.trim();
34
+ if (!dir) {
35
+ return null;
36
+ }
37
+ try {
38
+ return await openRepoIndex(dir);
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ }
44
+ class MemoryRepoIndex {
45
+ dir;
46
+ meta;
47
+ files;
48
+ edges;
49
+ constructor(dir, meta, files, edges) {
50
+ this.dir = dir;
51
+ this.meta = meta;
52
+ this.files = files;
53
+ this.edges = edges;
54
+ }
55
+ async listFiles(options = {}) {
56
+ const limit = options.limit && options.limit > 0 ? options.limit : 5000;
57
+ const language = options.language?.trim();
58
+ const out = [];
59
+ for (const file of this.files) {
60
+ if (language && file.language !== language) {
61
+ continue;
62
+ }
63
+ out.push(file);
64
+ if (out.length >= limit) {
65
+ break;
66
+ }
67
+ }
68
+ return out;
69
+ }
70
+ async file(path) {
71
+ const normalized = normalizePath(path);
72
+ return this.files.find((file) => file.path === normalized);
73
+ }
74
+ async importsOf(path) {
75
+ const normalized = normalizePath(path);
76
+ return this.edges.filter((edge) => edge.from === normalized && edge.kind === "import");
77
+ }
78
+ async importersOf(path) {
79
+ const normalized = normalizePath(path);
80
+ const dir = dirOf(normalized);
81
+ return this.edges.filter((edge) => {
82
+ if (edge.kind !== "import") {
83
+ return false;
84
+ }
85
+ return edge.to === normalized || edge.to === dir;
86
+ });
87
+ }
88
+ }
89
+ function normalizePath(path) {
90
+ return path.replaceAll("\\", "/").replace(/^\.\//, "");
91
+ }
92
+ function dirOf(path) {
93
+ const idx = path.lastIndexOf("/");
94
+ if (idx <= 0) {
95
+ return "";
96
+ }
97
+ return path.slice(0, idx);
98
+ }
99
+ async function readJsonl(path) {
100
+ const handle = await open(path, "r");
101
+ try {
102
+ const rl = createInterface({
103
+ input: handle.createReadStream(),
104
+ crlfDelay: Number.POSITIVE_INFINITY,
105
+ });
106
+ const out = [];
107
+ for await (const line of rl) {
108
+ const trimmed = line.trim();
109
+ if (!trimmed) {
110
+ continue;
111
+ }
112
+ out.push(JSON.parse(trimmed));
113
+ }
114
+ return out;
115
+ }
116
+ finally {
117
+ await handle.close();
118
+ }
119
+ }
120
+ //# sourceMappingURL=repo-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo-index.js","sourceRoot":"","sources":["../src/repo-index.ts"],"names":[],"mappings":"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","sourcesContent":["import { open, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { createInterface } from \"node:readline\";\n\n/** Environment variable set by the CLI pointing at an on-disk repo index directory. */\nexport const ADVERSARY_REPO_INDEX_ENV = \"ADVERSARY_REPO_INDEX\";\n\nexport const REPO_INDEX_SCHEMA_VERSION = \"v1\";\n\nexport interface RepoIndexMeta {\n schemaVersion: string;\n fingerprint: string;\n repoPath: string;\n builtAt?: string;\n fileCount: number;\n edgeCount: number;\n}\n\nexport interface RepoIndexFile {\n path: string;\n language: string;\n size: number;\n hash: string;\n}\n\nexport interface RepoIndexEdge {\n from: string;\n to: string;\n kind: string;\n}\n\nexport interface ListFilesOptions {\n /** Exact language match: go | typescript | javascript */\n language?: string;\n /** Maximum files to return (default 5000). */\n limit?: number;\n}\n\n/**\n * Read-only navigation over a CLI-built repository index (files + import edges).\n */\nexport interface RepoIndex {\n readonly dir: string;\n readonly meta: RepoIndexMeta;\n listFiles(options?: ListFilesOptions): Promise<RepoIndexFile[]>;\n file(path: string): Promise<RepoIndexFile | undefined>;\n importsOf(path: string): Promise<RepoIndexEdge[]>;\n importersOf(path: string): Promise<RepoIndexEdge[]>;\n}\n\nexport class RepoIndexUnavailableError extends Error {\n readonly code = \"repo_index_unavailable\";\n\n constructor(message: string) {\n super(message);\n this.name = \"RepoIndexUnavailableError\";\n }\n}\n\n/** Open an index directory written by the adversary CLI (JSONL schema v1). */\nexport async function openRepoIndex(dir: string): Promise<RepoIndex> {\n const metaRaw = await readFile(join(dir, \"meta.json\"), \"utf8\");\n const meta = JSON.parse(metaRaw) as RepoIndexMeta;\n if (meta.schemaVersion !== REPO_INDEX_SCHEMA_VERSION) {\n throw new RepoIndexUnavailableError(\n `unsupported repo-index schema ${meta.schemaVersion} (want ${REPO_INDEX_SCHEMA_VERSION})`,\n );\n }\n const files = await readJsonl<RepoIndexFile>(join(dir, \"files.jsonl\"));\n const edges = await readJsonl<RepoIndexEdge>(join(dir, \"edges.jsonl\"));\n return new MemoryRepoIndex(dir, meta, files, edges);\n}\n\n/**\n * Load index from ADVERSARY_REPO_INDEX when set; otherwise return null.\n *\n * A missing, deleted, or malformed index is treated as unavailable (null), not a\n * hard run failure — rules already null-check `ctx.repoIndex`. Explicit opens via\n * `openRepoIndex` still throw `RepoIndexUnavailableError`.\n */\nexport async function repoIndexFromEnvironment(\n env: Record<string, string | undefined> = process.env,\n): Promise<RepoIndex | null> {\n const dir = env[ADVERSARY_REPO_INDEX_ENV]?.trim();\n if (!dir) {\n return null;\n }\n try {\n return await openRepoIndex(dir);\n } catch {\n return null;\n }\n}\n\nclass MemoryRepoIndex implements RepoIndex {\n constructor(\n readonly dir: string,\n readonly meta: RepoIndexMeta,\n private readonly files: RepoIndexFile[],\n private readonly edges: RepoIndexEdge[],\n ) {}\n\n async listFiles(options: ListFilesOptions = {}): Promise<RepoIndexFile[]> {\n const limit = options.limit && options.limit > 0 ? options.limit : 5000;\n const language = options.language?.trim();\n const out: RepoIndexFile[] = [];\n for (const file of this.files) {\n if (language && file.language !== language) {\n continue;\n }\n out.push(file);\n if (out.length >= limit) {\n break;\n }\n }\n return out;\n }\n\n async file(path: string): Promise<RepoIndexFile | undefined> {\n const normalized = normalizePath(path);\n return this.files.find((file) => file.path === normalized);\n }\n\n async importsOf(path: string): Promise<RepoIndexEdge[]> {\n const normalized = normalizePath(path);\n return this.edges.filter((edge) => edge.from === normalized && edge.kind === \"import\");\n }\n\n async importersOf(path: string): Promise<RepoIndexEdge[]> {\n const normalized = normalizePath(path);\n const dir = dirOf(normalized);\n return this.edges.filter((edge) => {\n if (edge.kind !== \"import\") {\n return false;\n }\n return edge.to === normalized || edge.to === dir;\n });\n }\n}\n\nfunction normalizePath(path: string): string {\n return path.replaceAll(\"\\\\\", \"/\").replace(/^\\.\\//, \"\");\n}\n\nfunction dirOf(path: string): string {\n const idx = path.lastIndexOf(\"/\");\n if (idx <= 0) {\n return \"\";\n }\n return path.slice(0, idx);\n}\n\nasync function readJsonl<T>(path: string): Promise<T[]> {\n const handle = await open(path, \"r\");\n try {\n const rl = createInterface({\n input: handle.createReadStream(),\n crlfDelay: Number.POSITIVE_INFINITY,\n });\n const out: T[] = [];\n for await (const line of rl) {\n const trimmed = line.trim();\n if (!trimmed) {\n continue;\n }\n out.push(JSON.parse(trimmed) as T);\n }\n return out;\n } finally {\n await handle.close();\n }\n}\n"]}
@@ -0,0 +1,52 @@
1
+ /** Minimal change fields needed to resolve review scope (matches ChangeContext). */
2
+ export interface SourceChangeScope {
3
+ readonly scanMode: "changed" | "all";
4
+ readonly changedFiles: readonly string[];
5
+ }
6
+ /** Default directories skipped when enumerating an entire target. */
7
+ export declare const DEFAULT_IGNORE_DIRECTORIES: readonly string[];
8
+ export type InScopeSourceStatus = "changed" | "repository";
9
+ /**
10
+ * One source file in the runner's requested review scope.
11
+ *
12
+ * The CLI owns dirty-worktree / PR / all-files resolution. Adversaries must not
13
+ * re-run git to invent a second change list — use these helpers instead.
14
+ */
15
+ export interface InScopeSource {
16
+ /** Repository-relative POSIX path. */
17
+ path: string;
18
+ content: string;
19
+ /**
20
+ * "changed" when the path is in the runner's change list (includes untracked).
21
+ * "repository" when scanning the whole target (scanMode all / no change).
22
+ */
23
+ status: InScopeSourceStatus;
24
+ }
25
+ export interface ListInScopePathsOptions {
26
+ /** Keep only matching paths (after ignore rules). Defaults to all files. */
27
+ include?: (path: string) => boolean;
28
+ /** Maximum paths to return (default unlimited for list, 750 for load). */
29
+ limit?: number;
30
+ /** Directory names to skip during whole-target walks. */
31
+ ignoreDirectories?: readonly string[];
32
+ }
33
+ export interface LoadInScopeSourcesOptions extends ListInScopePathsOptions {
34
+ /** Skip files larger than this many bytes (default 750_000). */
35
+ maxBytes?: number;
36
+ }
37
+ /**
38
+ * Paths in scope for this review.
39
+ *
40
+ * - `scanMode: "changed"` (or change list present with changed mode): use
41
+ * `change.changedFiles` only — the CLI already included untracked worktree
42
+ * paths. No git.
43
+ * - `scanMode: "all"` or `change === null`: walk the target filesystem.
44
+ */
45
+ export declare function listInScopePaths(repoPath: string, change: SourceChangeScope | null, options?: ListInScopePathsOptions): Promise<string[]>;
46
+ /**
47
+ * Read file contents for paths in scope.
48
+ *
49
+ * Deleted / unreadable paths are skipped. Binary / oversized files are skipped.
50
+ */
51
+ export declare function loadInScopeSources(repoPath: string, change: SourceChangeScope | null, options?: LoadInScopeSourcesOptions): Promise<InScopeSource[]>;
52
+ //# sourceMappingURL=sources.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sources.d.ts","sourceRoot":"","sources":["../src/sources.ts"],"names":[],"mappings":"AAIA,oFAAoF;AACpF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,EAAE,SAAS,GAAG,KAAK,CAAC;IACrC,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAED,qEAAqE;AACrE,eAAO,MAAM,0BAA0B,EAAE,SAAS,MAAM,EAUtD,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,YAAY,CAAC;AAE3D;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,MAAM,EAAE,mBAAmB,CAAC;CAC7B;AAED,MAAM,WAAW,uBAAuB;IACtC,4EAA4E;IAC5E,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IACpC,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yDAAyD;IACzD,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AAED,MAAM,WAAW,yBAA0B,SAAQ,uBAAuB;IACxE,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,iBAAiB,GAAG,IAAI,EAChC,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,MAAM,EAAE,CAAC,CAuBnB;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,iBAAiB,GAAG,IAAI,EAChC,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,aAAa,EAAE,CAAC,CAsB1B"}
@@ -0,0 +1,121 @@
1
+ import { readFile, readdir } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ /** Default directories skipped when enumerating an entire target. */
4
+ export const DEFAULT_IGNORE_DIRECTORIES = Object.freeze([
5
+ ".git",
6
+ ".next",
7
+ "build",
8
+ "coverage",
9
+ "dist",
10
+ "generated",
11
+ "node_modules",
12
+ "third_party",
13
+ "vendor",
14
+ ]);
15
+ /**
16
+ * Paths in scope for this review.
17
+ *
18
+ * - `scanMode: "changed"` (or change list present with changed mode): use
19
+ * `change.changedFiles` only — the CLI already included untracked worktree
20
+ * paths. No git.
21
+ * - `scanMode: "all"` or `change === null`: walk the target filesystem.
22
+ */
23
+ export async function listInScopePaths(repoPath, change, options = {}) {
24
+ const include = options.include ?? (() => true);
25
+ const limit = options.limit !== undefined && options.limit > 0 ? options.limit : undefined;
26
+ const ignore = new Set(options.ignoreDirectories ?? DEFAULT_IGNORE_DIRECTORIES);
27
+ let candidates;
28
+ if (change !== null && change.scanMode === "changed") {
29
+ candidates = change.changedFiles.map(normalizePath);
30
+ }
31
+ else {
32
+ candidates = await walkRelative(repoPath, ignore);
33
+ }
34
+ const out = [];
35
+ const seen = new Set();
36
+ for (const path of candidates) {
37
+ if (!path || seen.has(path))
38
+ continue;
39
+ if (path.split("/").some((segment) => ignore.has(segment)))
40
+ continue;
41
+ if (!include(path))
42
+ continue;
43
+ seen.add(path);
44
+ out.push(path);
45
+ if (limit !== undefined && out.length >= limit)
46
+ break;
47
+ }
48
+ return out;
49
+ }
50
+ /**
51
+ * Read file contents for paths in scope.
52
+ *
53
+ * Deleted / unreadable paths are skipped. Binary / oversized files are skipped.
54
+ */
55
+ export async function loadInScopeSources(repoPath, change, options = {}) {
56
+ const maxBytes = options.maxBytes ?? 750_000;
57
+ const paths = await listInScopePaths(repoPath, change, {
58
+ include: options.include,
59
+ limit: options.limit ?? 750,
60
+ ignoreDirectories: options.ignoreDirectories,
61
+ });
62
+ const wholeTarget = change === null || change.scanMode === "all";
63
+ const changedSet = new Set((change?.changedFiles ?? []).map(normalizePath));
64
+ const sources = [];
65
+ for (const path of paths) {
66
+ const content = await safeReadText(join(repoPath, path), maxBytes);
67
+ if (content === undefined)
68
+ continue;
69
+ sources.push({
70
+ path,
71
+ content,
72
+ status: wholeTarget && !changedSet.has(path) ? "repository" : "changed",
73
+ });
74
+ }
75
+ return sources;
76
+ }
77
+ function normalizePath(path) {
78
+ return path.replaceAll("\\", "/").replace(/^\.\//, "");
79
+ }
80
+ async function walkRelative(repoPath, ignore) {
81
+ const out = [];
82
+ async function visit(relativeDir) {
83
+ const abs = relativeDir === "" ? repoPath : join(repoPath, relativeDir);
84
+ let entries;
85
+ try {
86
+ entries = await readdir(abs, { withFileTypes: true });
87
+ }
88
+ catch {
89
+ return;
90
+ }
91
+ entries.sort((left, right) => left.name.localeCompare(right.name));
92
+ for (const entry of entries) {
93
+ const rel = relativeDir === "" ? entry.name : `${relativeDir}/${entry.name}`.replaceAll("\\", "/");
94
+ if (entry.isDirectory()) {
95
+ if (ignore.has(entry.name))
96
+ continue;
97
+ await visit(rel);
98
+ continue;
99
+ }
100
+ if (entry.isFile()) {
101
+ out.push(rel.replaceAll("\\", "/"));
102
+ }
103
+ }
104
+ }
105
+ await visit("");
106
+ return out;
107
+ }
108
+ async function safeReadText(absPath, maxBytes) {
109
+ try {
110
+ const buffer = await readFile(absPath);
111
+ if (buffer.byteLength > maxBytes)
112
+ return undefined;
113
+ if (buffer.includes(0))
114
+ return undefined;
115
+ return buffer.toString("utf8");
116
+ }
117
+ catch {
118
+ return undefined;
119
+ }
120
+ }
121
+ //# sourceMappingURL=sources.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sources.js","sourceRoot":"","sources":["../src/sources.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Dirent } from \"node:fs\";\nimport { readFile, readdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\n/** Minimal change fields needed to resolve review scope (matches ChangeContext). */\nexport interface SourceChangeScope {\n readonly scanMode: \"changed\" | \"all\";\n readonly changedFiles: readonly string[];\n}\n\n/** Default directories skipped when enumerating an entire target. */\nexport const DEFAULT_IGNORE_DIRECTORIES: readonly string[] = Object.freeze([\n \".git\",\n \".next\",\n \"build\",\n \"coverage\",\n \"dist\",\n \"generated\",\n \"node_modules\",\n \"third_party\",\n \"vendor\",\n]);\n\nexport type InScopeSourceStatus = \"changed\" | \"repository\";\n\n/**\n * One source file in the runner's requested review scope.\n *\n * The CLI owns dirty-worktree / PR / all-files resolution. Adversaries must not\n * re-run git to invent a second change list — use these helpers instead.\n */\nexport interface InScopeSource {\n /** Repository-relative POSIX path. */\n path: string;\n content: string;\n /**\n * \"changed\" when the path is in the runner's change list (includes untracked).\n * \"repository\" when scanning the whole target (scanMode all / no change).\n */\n status: InScopeSourceStatus;\n}\n\nexport interface ListInScopePathsOptions {\n /** Keep only matching paths (after ignore rules). Defaults to all files. */\n include?: (path: string) => boolean;\n /** Maximum paths to return (default unlimited for list, 750 for load). */\n limit?: number;\n /** Directory names to skip during whole-target walks. */\n ignoreDirectories?: readonly string[];\n}\n\nexport interface LoadInScopeSourcesOptions extends ListInScopePathsOptions {\n /** Skip files larger than this many bytes (default 750_000). */\n maxBytes?: number;\n}\n\n/**\n * Paths in scope for this review.\n *\n * - `scanMode: \"changed\"` (or change list present with changed mode): use\n * `change.changedFiles` only — the CLI already included untracked worktree\n * paths. No git.\n * - `scanMode: \"all\"` or `change === null`: walk the target filesystem.\n */\nexport async function listInScopePaths(\n repoPath: string,\n change: SourceChangeScope | null,\n options: ListInScopePathsOptions = {},\n): Promise<string[]> {\n const include = options.include ?? (() => true);\n const limit = options.limit !== undefined && options.limit > 0 ? options.limit : undefined;\n const ignore = new Set(options.ignoreDirectories ?? DEFAULT_IGNORE_DIRECTORIES);\n\n let candidates: string[];\n if (change !== null && change.scanMode === \"changed\") {\n candidates = change.changedFiles.map(normalizePath);\n } else {\n candidates = await walkRelative(repoPath, ignore);\n }\n\n const out: string[] = [];\n const seen = new Set<string>();\n for (const path of candidates) {\n if (!path || seen.has(path)) continue;\n if (path.split(\"/\").some((segment) => ignore.has(segment))) continue;\n if (!include(path)) continue;\n seen.add(path);\n out.push(path);\n if (limit !== undefined && out.length >= limit) break;\n }\n return out;\n}\n\n/**\n * Read file contents for paths in scope.\n *\n * Deleted / unreadable paths are skipped. Binary / oversized files are skipped.\n */\nexport async function loadInScopeSources(\n repoPath: string,\n change: SourceChangeScope | null,\n options: LoadInScopeSourcesOptions = {},\n): Promise<InScopeSource[]> {\n const maxBytes = options.maxBytes ?? 750_000;\n const paths = await listInScopePaths(repoPath, change, {\n include: options.include,\n limit: options.limit ?? 750,\n ignoreDirectories: options.ignoreDirectories,\n });\n\n const wholeTarget = change === null || change.scanMode === \"all\";\n const changedSet = new Set((change?.changedFiles ?? []).map(normalizePath));\n const sources: InScopeSource[] = [];\n\n for (const path of paths) {\n const content = await safeReadText(join(repoPath, path), maxBytes);\n if (content === undefined) continue;\n sources.push({\n path,\n content,\n status: wholeTarget && !changedSet.has(path) ? \"repository\" : \"changed\",\n });\n }\n return sources;\n}\n\nfunction normalizePath(path: string): string {\n return path.replaceAll(\"\\\\\", \"/\").replace(/^\\.\\//, \"\");\n}\n\nasync function walkRelative(repoPath: string, ignore: ReadonlySet<string>): Promise<string[]> {\n const out: string[] = [];\n\n async function visit(relativeDir: string): Promise<void> {\n const abs = relativeDir === \"\" ? repoPath : join(repoPath, relativeDir);\n let entries: Dirent[];\n try {\n entries = await readdir(abs, { withFileTypes: true });\n } catch {\n return;\n }\n entries.sort((left, right) => left.name.localeCompare(right.name));\n for (const entry of entries) {\n const rel =\n relativeDir === \"\" ? entry.name : `${relativeDir}/${entry.name}`.replaceAll(\"\\\\\", \"/\");\n if (entry.isDirectory()) {\n if (ignore.has(entry.name)) continue;\n await visit(rel);\n continue;\n }\n if (entry.isFile()) {\n out.push(rel.replaceAll(\"\\\\\", \"/\"));\n }\n }\n }\n\n await visit(\"\");\n return out;\n}\n\nasync function safeReadText(absPath: string, maxBytes: number): Promise<string | undefined> {\n try {\n const buffer = await readFile(absPath);\n if (buffer.byteLength > maxBytes) return undefined;\n if (buffer.includes(0)) return undefined;\n return buffer.toString(\"utf8\");\n } catch {\n return undefined;\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adversarylabs/sdk",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "description": "Small TypeScript SDK for authoring Adversaries.",
5
5
  "type": "module",
6
6
  "license": "MIT",