@functional-systems/srcx 0.1.0-dev.3 → 0.1.0-dev.31

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 (61) hide show
  1. package/CHANGELOG.md +137 -1
  2. package/DESIGN.md +193 -58
  3. package/README.md +553 -38
  4. package/RELEASING.md +30 -9
  5. package/VALIDATION.md +855 -25
  6. package/dist/build.d.ts +81 -1
  7. package/dist/build.js +187 -37
  8. package/dist/build.js.map +1 -1
  9. package/dist/chunk.d.ts +9 -3
  10. package/dist/chunk.js +341 -14
  11. package/dist/chunk.js.map +1 -1
  12. package/dist/cli.js +103 -19
  13. package/dist/cli.js.map +1 -1
  14. package/dist/file-policy.d.ts +20 -0
  15. package/dist/file-policy.js +96 -0
  16. package/dist/file-policy.js.map +1 -0
  17. package/dist/implementation.d.ts +11 -0
  18. package/dist/implementation.js +191 -0
  19. package/dist/implementation.js.map +1 -0
  20. package/dist/publish.d.ts +18 -0
  21. package/dist/publish.js +158 -34
  22. package/dist/publish.js.map +1 -1
  23. package/dist/releases.js +21 -3
  24. package/dist/releases.js.map +1 -1
  25. package/dist/remote.d.ts +1 -0
  26. package/dist/remote.js +50 -9
  27. package/dist/remote.js.map +1 -1
  28. package/dist/repository.d.ts +4 -1
  29. package/dist/repository.js +26 -11
  30. package/dist/repository.js.map +1 -1
  31. package/dist/rerank.d.ts +16 -0
  32. package/dist/rerank.js +87 -0
  33. package/dist/rerank.js.map +1 -0
  34. package/dist/search.d.ts +14 -4
  35. package/dist/search.js +119 -23
  36. package/dist/search.js.map +1 -1
  37. package/dist/skills.d.ts +24 -0
  38. package/dist/skills.js +155 -0
  39. package/dist/skills.js.map +1 -0
  40. package/package.json +16 -5
  41. package/runtime/grammars/README.md +50 -0
  42. package/runtime/grammars/tree-sitter-bash.LICENSE +21 -0
  43. package/runtime/grammars/tree-sitter-bash.wasm +0 -0
  44. package/runtime/grammars/tree-sitter-sql.LICENSE +21 -0
  45. package/runtime/grammars/tree-sitter-sql.wasm +0 -0
  46. package/runtime/grammars/tree-sitter-wasm.LICENSE +21 -0
  47. package/runtime/qwen.py +132 -0
  48. package/runtime/requirements.txt +34 -0
  49. package/skills/srcx-search/SKILL.md +85 -0
  50. package/src/build.ts +258 -47
  51. package/src/chunk.ts +360 -12
  52. package/src/cli.ts +212 -24
  53. package/src/file-policy.ts +157 -0
  54. package/src/implementation.ts +253 -0
  55. package/src/publish.ts +226 -34
  56. package/src/releases.ts +36 -3
  57. package/src/remote.ts +70 -13
  58. package/src/repository.ts +40 -11
  59. package/src/rerank.ts +131 -0
  60. package/src/search.ts +154 -28
  61. package/src/skills.ts +205 -0
package/src/cli.ts CHANGED
@@ -1,12 +1,23 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFileSync } from "node:fs";
3
- import { Command, InvalidArgumentError } from "commander";
3
+ import { Command, InvalidArgumentError, Option } from "commander";
4
4
  import { mkdir } from "node:fs/promises";
5
5
  import { join, resolve } from "node:path";
6
6
  import { randomUUID } from "node:crypto";
7
7
  import { configure, loadSettings, stateRoot } from "./settings.js";
8
- import { identity } from "./git.js";
9
- import { loadBuild, materialize, validateBuild, type Build } from "./build.js";
8
+ import { identity, resolveCommit } from "./git.js";
9
+ import {
10
+ loadBuild,
11
+ materialize,
12
+ validateBuild,
13
+ type Build,
14
+ type Preset,
15
+ PRESET,
16
+ presetFor,
17
+ normalizeAnalyzers,
18
+ ANALYZERS,
19
+ } from "./build.js";
20
+ import { normalizeFilePolicy } from "./file-policy.js";
10
21
  import { invariant, optionalJson } from "./common.js";
11
22
  import { LambdaRemote } from "./remote.js";
12
23
  import {
@@ -16,9 +27,17 @@ import {
16
27
  register,
17
28
  selectRepository,
18
29
  } from "./repository.js";
19
- import { exclusive, publish, published, type Published } from "./publish.js";
30
+ import {
31
+ exclusive,
32
+ lastBuildPath,
33
+ publish,
34
+ published,
35
+ type Published,
36
+ } from "./publish.js";
20
37
  import { resolveVersion, syncTags } from "./releases.js";
21
38
  import { directHandle, loadHandle, readHandle, search } from "./search.js";
39
+ import { candidateLimit } from "./rerank.js";
40
+ import { manageSkill, SKILL_AGENTS, SKILL_SCOPES } from "./skills.js";
22
41
  const cli = new Command()
23
42
  .name("srcx")
24
43
  .description("Version-aware code search on LambdaDB")
@@ -39,10 +58,48 @@ const integer = (value: string) => {
39
58
  throw new InvalidArgumentError("Integer is too large.");
40
59
  return n;
41
60
  };
61
+ const analyzerOption = (description: string) =>
62
+ new Option(
63
+ "--analyzers <names>",
64
+ description + ` (${ANALYZERS.join(", ")}; default: standard)`,
65
+ ).argParser((value) =>
66
+ normalizeAnalyzers(value.split(",").map((name) => name.trim())),
67
+ );
68
+ const filePolicyOption = () =>
69
+ new Option(
70
+ "--file-policy <json>",
71
+ "Path to file policy JSON; pinned at registration or local dry run",
72
+ ).argParser((path) =>
73
+ normalizeFilePolicy(JSON.parse(readFileSync(path, "utf8"))),
74
+ );
42
75
  async function connected() {
43
76
  const settings = await loadSettings();
44
77
  return { settings, remote: new LambdaRemote(settings) };
45
78
  }
79
+ const skills = cli
80
+ .command("skills")
81
+ .description(
82
+ "Install and manage the bundled agent skill (no credentials required)",
83
+ );
84
+ for (const operation of ["install", "update", "remove", "status"] as const) {
85
+ skills
86
+ .command(operation)
87
+ .addOption(
88
+ new Option("--agent <name>", "Agent to configure")
89
+ .choices(SKILL_AGENTS)
90
+ .makeOptionMandatory(),
91
+ )
92
+ .addOption(
93
+ new Option("--scope <scope>", "Installation scope")
94
+ .choices(SKILL_SCOPES)
95
+ .default("user"),
96
+ )
97
+ .option(
98
+ "--path <directory>",
99
+ "Project directory (project scope only; default: current directory)",
100
+ )
101
+ .action(async (options) => output(await manageSkill(operation, options)));
102
+ }
46
103
  cli
47
104
  .command("configure")
48
105
  .requiredOption("--endpoint <origin>")
@@ -81,6 +138,16 @@ repo
81
138
  .option("--remote <name>")
82
139
  .option("--description <text>")
83
140
  .option("--tag <key=value>", "One optional Collection metadata tag")
141
+ .addOption(analyzerOption("Comma-separated text analyzers"))
142
+ .addOption(filePolicyOption())
143
+ .addOption(
144
+ new Option(
145
+ "--embedding <model>",
146
+ "Managed model (source text is sent to the provider on import)",
147
+ )
148
+ .choices(["none", "text-embedding-3-small", "text-embedding-3-large"])
149
+ .default("none"),
150
+ )
84
151
  .action(async (o) => {
85
152
  const { remote } = await connected();
86
153
  let labels: Record<string, string> | undefined;
@@ -95,6 +162,7 @@ repo
95
162
  remote: o.remote,
96
163
  description: o.description,
97
164
  labels,
165
+ preset: presetFor(o.embedding, o.analyzers, o.filePolicy),
98
166
  }),
99
167
  );
100
168
  });
@@ -122,6 +190,18 @@ cli
122
190
  .option("--repo <name>", "Registered remote repository")
123
191
  .option("--ref <ref>", "Local branch, Git tag, or commit OID")
124
192
  .option("--dry-run", "Only materialize and validate a local build")
193
+ .addOption(filePolicyOption())
194
+ .addOption(
195
+ new Option(
196
+ "--embedding <model>",
197
+ "Preset for --dry-run --path; connected imports use the repository preset",
198
+ ).choices(["none", "text-embedding-3-small", "text-embedding-3-large"]),
199
+ )
200
+ .addOption(
201
+ analyzerOption(
202
+ "Text analyzers for --dry-run --path; connected imports use the repository preset",
203
+ ),
204
+ )
125
205
  .option("--output <directory>", "New artifact directory")
126
206
  .option(
127
207
  "--previous <directory>",
@@ -134,6 +214,18 @@ cli
134
214
  )
135
215
  .option("--timeout <seconds>", "Validation wait deadline", integer, 120)
136
216
  .action(async (o) => {
217
+ invariant(
218
+ !o.embedding || (o.dryRun && o.path),
219
+ "--embedding is only accepted with --dry-run --path; connected imports use the repository preset.",
220
+ );
221
+ invariant(
222
+ !o.analyzers || (o.dryRun && o.path),
223
+ "--analyzers is only accepted with --dry-run --path; connected imports use the repository preset.",
224
+ );
225
+ invariant(
226
+ !o.filePolicy || (o.dryRun && o.path),
227
+ "--file-policy is only accepted with --dry-run --path; connected imports use the repository preset.",
228
+ );
137
229
  invariant(!(o.path && o.repo), "Choose --path or --repo.");
138
230
  invariant(
139
231
  !o.previous || o.dryRun,
@@ -147,16 +239,33 @@ cli
147
239
  const buildLocal = async (
148
240
  source: Awaited<ReturnType<typeof identity>>,
149
241
  previous?: Build,
242
+ state?: string,
243
+ preset: Preset = PRESET,
150
244
  ) => {
151
245
  invariant(o.ref, "--ref is required to build a commit.");
246
+ const commit = await resolveCommit(source.path, o.ref);
247
+ if (state) {
248
+ const last = await optionalJson<{ artifact: string }>(
249
+ lastBuildPath(state, commit.branch),
250
+ );
251
+ if (last) {
252
+ try {
253
+ previous = await loadBuild(last.artifact);
254
+ } catch (error) {
255
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
256
+ }
257
+ }
258
+ }
152
259
  const parent = join(stateRoot(), "builds");
153
260
  await mkdir(parent, { recursive: true, mode: 0o700 });
154
261
  const path = o.output ? resolve(o.output) : join(parent, randomUUID());
155
262
  return materialize({
156
263
  identity: source,
157
264
  ref: o.ref,
265
+ resolvedCommit: commit,
158
266
  output: path,
159
267
  previous,
268
+ preset,
160
269
  });
161
270
  };
162
271
  if (o.dryRun) {
@@ -166,18 +275,20 @@ cli
166
275
  await validateBuild(b);
167
276
  } else {
168
277
  let source;
278
+ let preset = presetFor(o.embedding, o.analyzers, o.filePolicy);
169
279
  if (o.path) source = await identity(o.path, o.remote);
170
280
  else {
171
281
  invariant(o.repo, "Use --path for a credential-free dry run.");
172
282
  const { remote, settings } = await connected();
173
- source = await attachment(
174
- settings,
175
- await selectRepository(remote, o.repo),
176
- );
283
+ const selected = await selectRepository(remote, o.repo);
284
+ preset = selected.preset ?? PRESET;
285
+ source = await attachment(settings, selected);
177
286
  }
178
287
  b = await buildLocal(
179
288
  source,
180
289
  o.previous ? await loadBuild(o.previous) : undefined,
290
+ undefined,
291
+ preset,
181
292
  );
182
293
  }
183
294
  output({
@@ -192,6 +303,8 @@ cli
192
303
  path: e.path,
193
304
  status: e.status,
194
305
  reason: e.reason,
306
+ retrieval: e.retrieval,
307
+ policyReason: e.policyReason,
195
308
  parseStatus: e.parseStatus,
196
309
  })),
197
310
  uploaded: false,
@@ -212,20 +325,34 @@ cli
212
325
  // Pending attempts reconcile against the journal's immutable remote
213
326
  // baseline; they do not need the previous build's local files.
214
327
  const pending = await optionalJson(join(state, "pending.json"));
328
+ const b = o.artifact
329
+ ? await loadBuild(o.artifact)
330
+ : await buildLocal(
331
+ await attachment(settings, r),
332
+ undefined,
333
+ pending ? undefined : state,
334
+ r.preset ?? PRESET,
335
+ );
215
336
  let baseline: { version: Published; build: Build } | undefined;
216
- if (!pending) {
337
+ // Tracked branches discover their own immutable baseline remotely.
338
+ // Manual SHA/tag imports retain the previous frozen workspace optimization.
339
+ if (!pending && !b.branchRef) {
217
340
  const last = await optionalJson<{
218
341
  artifact: string;
219
342
  version: Published;
220
- }>(join(state, "last-build.json"));
343
+ }>(lastBuildPath(state));
221
344
  if (last) {
222
- const b = await loadBuild(last.artifact);
223
- baseline = { version: last.version, build: b };
345
+ try {
346
+ baseline = {
347
+ version: last.version,
348
+ build: await loadBuild(last.artifact),
349
+ };
350
+ } catch (error) {
351
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT")
352
+ throw error;
353
+ }
224
354
  }
225
355
  }
226
- const b = o.artifact
227
- ? await loadBuild(o.artifact)
228
- : await buildLocal(await attachment(settings, r), baseline?.build);
229
356
  return publish({
230
357
  store,
231
358
  binding: r,
@@ -249,7 +376,7 @@ cli
249
376
  cli
250
377
  .command("resolve")
251
378
  .requiredOption("--repo <name>")
252
- .requiredOption("--ref <commit-or-release>")
379
+ .requiredOption("--ref <commit-branch-or-release>")
253
380
  .action(async (o) => {
254
381
  const { remote } = await connected();
255
382
  const r = await selectRepository(remote, o.repo);
@@ -258,33 +385,93 @@ cli
258
385
  cli
259
386
  .command("search")
260
387
  .requiredOption("--repo <name>")
261
- .requiredOption("--version <commit-or-release>")
262
- .requiredOption("--query <text>")
388
+ .requiredOption("--version <commit-branch-or-release>")
389
+ .requiredOption(
390
+ "--query <text>",
391
+ "Search text; use the target source language",
392
+ )
393
+ .addHelpText(
394
+ "after",
395
+ "\nQuery language: any language is accepted; queries are not automatically translated.\n" +
396
+ "For English code/comments, prefer English terms and preserve identifiers, API names and error messages.\n" +
397
+ "For Korean documentation, use Korean queries with the korean analyzer.\n" +
398
+ "Agents can translate the search query while answering in the user's language.\n",
399
+ )
263
400
  .option("--limit <count>", "Maximum hits", integer, 10)
401
+ .addOption(
402
+ new Option(
403
+ "--mode <mode>",
404
+ "Retrieval method; semantic/hybrid incur managed query embedding usage",
405
+ )
406
+ .choices(["lexical", "semantic", "hybrid"])
407
+ .default("lexical"),
408
+ )
409
+ .addOption(
410
+ new Option(
411
+ "--rerank <model>",
412
+ "Opt-in local reranker (requires separate Python/model setup)",
413
+ ).choices(["qwen"]),
414
+ )
415
+ .option(
416
+ "--candidates <count>",
417
+ "Rerank pool size (default: max(50, limit); max: 100)",
418
+ integer,
419
+ )
264
420
  .option("--path <path>", "Exact path filter")
265
- .option("--language <name>")
421
+ .option("--language <name>", "Exact programming-language filter")
266
422
  .action(async (o) => {
423
+ const started = performance.now();
424
+ candidateLimit(o.limit, o);
267
425
  const { remote, settings } = await connected();
268
426
  const r = await selectRepository(remote, o.repo),
269
427
  store = remote.store(r.collection),
270
428
  v = await resolveVersion(store, r, o.version);
271
429
  output(
272
- await search(store, settings, r, v, o.query, o.limit, {
273
- path: o.path,
274
- language: o.language,
275
- }),
430
+ await search(
431
+ store,
432
+ settings,
433
+ r,
434
+ v,
435
+ o.query,
436
+ o.limit,
437
+ {
438
+ path: o.path,
439
+ language: o.language,
440
+ },
441
+ o.mode,
442
+ {
443
+ rerank: o.rerank,
444
+ candidates: o.candidates,
445
+ onTiming: (timing) =>
446
+ process.stderr.write(
447
+ JSON.stringify({
448
+ event: "rerank-timing",
449
+ ...timing,
450
+ commandMs: performance.now() - started,
451
+ }) + "\n",
452
+ ),
453
+ },
454
+ ),
276
455
  );
277
456
  });
278
457
  cli
279
458
  .command("read")
280
459
  .option("--result <id>")
281
460
  .option("--repo <name>")
282
- .option("--version <commit-or-release>")
461
+ .option("--version <commit-branch-or-release>")
283
462
  .option("--path <path>")
284
463
  .option("--lines <start:end>")
285
464
  .option("--context <lines>", "Expand an evidence span", integer, 0)
286
465
  .option("--full-file")
466
+ .option(
467
+ "--implementation",
468
+ "Read a linked Python/TypeScript overload implementation",
469
+ )
287
470
  .action(async (o) => {
471
+ invariant(
472
+ !o.implementation || (o.result && !o.fullFile && !o.lines && !o.context),
473
+ "--implementation requires --result and cannot be combined with range options.",
474
+ );
288
475
  invariant(
289
476
  !(o.fullFile && (o.lines || o.context)),
290
477
  "--full-file cannot be combined with --lines or --context.",
@@ -331,6 +518,7 @@ cli
331
518
  context: o.context,
332
519
  fullFile: o.fullFile || (!o.result && !o.lines),
333
520
  lines,
521
+ implementation: o.implementation,
334
522
  },
335
523
  ),
336
524
  );
@@ -0,0 +1,157 @@
1
+ import { invariant } from "./common.js";
2
+
3
+ export type FileAction = "exclude" | "lexical" | "semantic";
4
+ export type FileRule = { pattern: string; action: FileAction };
5
+ export type FilePolicy = { version: 1; rules: FileRule[] };
6
+ export const FILE_POLICY: FilePolicy = { version: 1, rules: [] };
7
+ export type FileDecision = { action: FileAction; reason: string };
8
+
9
+ /** Deliberately small glob syntax: repo-relative paths, *, ?, and whole-segment **. */
10
+ function glob(pattern: string): RegExp {
11
+ let expression = "^";
12
+ const parts = pattern.split("/");
13
+ for (const [index, part] of parts.entries()) {
14
+ if (part === "**")
15
+ expression += index === parts.length - 1 ? "[\\s\\S]*" : "(?:[^/]+/)*";
16
+ else {
17
+ for (const char of part)
18
+ expression +=
19
+ char === "*"
20
+ ? "[^/]*"
21
+ : char === "?"
22
+ ? "[^/]"
23
+ : char.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
24
+ if (index !== parts.length - 1) expression += "/";
25
+ }
26
+ }
27
+ return new RegExp(expression + "$", "u");
28
+ }
29
+
30
+ export function normalizeFilePolicy(value: unknown): FilePolicy {
31
+ invariant(
32
+ value !== null && typeof value === "object" && !Array.isArray(value),
33
+ "File policy must be an object.",
34
+ );
35
+ const policy = value as FilePolicy;
36
+ invariant(
37
+ Object.keys(policy).sort().join(",") === "rules,version" &&
38
+ policy.version === 1 &&
39
+ Array.isArray(policy.rules) &&
40
+ policy.rules.length <= 100,
41
+ "File policy requires version: 1 and at most 100 rules.",
42
+ );
43
+ const rules = policy.rules.map((rule) => {
44
+ invariant(
45
+ rule &&
46
+ typeof rule === "object" &&
47
+ Object.keys(rule).sort().join(",") === "action,pattern" &&
48
+ ["exclude", "lexical", "semantic"].includes(rule.action),
49
+ "Each file rule requires pattern and action (exclude, lexical, semantic).",
50
+ );
51
+ invariant(
52
+ typeof rule.pattern === "string" &&
53
+ rule.pattern.length > 0 &&
54
+ rule.pattern.length <= 512 &&
55
+ !/[\\\x00-\x1f\x7f\[\]{}!]/.test(rule.pattern) &&
56
+ rule.pattern
57
+ .split("/")
58
+ .every(
59
+ (part) =>
60
+ part &&
61
+ part !== "." &&
62
+ part !== ".." &&
63
+ (!part.includes("**") || part === "**"),
64
+ ),
65
+ "File policy patterns must be relative paths using only *, ?, and whole-segment ** wildcards.",
66
+ );
67
+ glob(rule.pattern);
68
+ return { pattern: rule.pattern, action: rule.action };
69
+ });
70
+ return { version: 1, rules };
71
+ }
72
+
73
+ /** These bodies cannot be re-enabled by repository rules. This is not a secret scanner. */
74
+ export function hardExclusion(path: string): string | undefined {
75
+ const name = path.split("/").at(-1)!;
76
+ if (
77
+ (/^\.env(?:\.|$)/i.test(name) &&
78
+ !/\.(example|sample|template)$/i.test(name)) ||
79
+ /^(\.npmrc|\.pypirc|\.netrc|_netrc|id_rsa|id_dsa|id_ecdsa|id_ed25519)$/i.test(
80
+ name,
81
+ ) ||
82
+ /(^|\/)\.(aws|ssh)(\/|$)/i.test(path)
83
+ )
84
+ return "credential-file";
85
+ if (
86
+ /\.(pem|key|crt|cer|csr|der|p12|pfx|p7b|p7c|jks|keystore|srl)$/i.test(path)
87
+ )
88
+ return "crypto-material";
89
+ if (
90
+ /\.(svgz?|png|apng|jpe?g|jfif|gif|webp|avif|bmp|dib|tiff?|ico|icns|heic|heif|jxl|psd|ai|eps|pnm|pbm|pgm|ppm|xbm|xpm)$/i.test(
91
+ path,
92
+ )
93
+ )
94
+ return "image-extension";
95
+ if (
96
+ /\.(pdf|zip|gz|bz2|xz|7z|tar|rar|jar|class|woff2?|ttf|otf|mp[34]|wav|ogg|mov|exe|dll|so|dylib|wasm|pyc|pyo|o|a|sqlite3?|db)$/i.test(
97
+ path,
98
+ )
99
+ )
100
+ return "binary-extension";
101
+ return undefined;
102
+ }
103
+
104
+ export function privateKeyContent(source: string): boolean {
105
+ return /(?:^|\r?\n)-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY-----\r?\n/.test(
106
+ source,
107
+ );
108
+ }
109
+
110
+ /** Explicit rules override defaults, in order; the final matching rule wins. */
111
+ export function fileDecision(
112
+ path: string,
113
+ policy: FilePolicy,
114
+ source?: string,
115
+ ): FileDecision {
116
+ let decision: FileDecision = { action: "semantic", reason: "source" };
117
+ if (
118
+ /(^|\/)(node_modules|vendor|vendored|third_party|third-party|dist|build|target|coverage|\.git|\.next|\.nuxt|\.venv|venv|__pycache__|\.cache|\.pytest_cache|\.mypy_cache|\.ruff_cache|\.tox|\.nox|\.srcx)(\/|$)/i.test(
119
+ path,
120
+ )
121
+ )
122
+ decision = { action: "exclude", reason: "dependency-or-build-output" };
123
+ else if (/\.(min\.(js|css)|map)$/i.test(path))
124
+ decision = { action: "exclude", reason: "generated-output" };
125
+ else if (/\.(log|log\.\d+)$/i.test(path))
126
+ decision = { action: "exclude", reason: "log-output" };
127
+ else if (
128
+ /(^|\/)(package-lock\.json|npm-shrinkwrap\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb?|go\.sum|[^/]+\.lock|packages\.lock\.json|Package\.resolved)$/i.test(
129
+ path,
130
+ )
131
+ )
132
+ decision = { action: "lexical", reason: "lockfile" };
133
+ else if (
134
+ /(^|\/)(licen[sc]e|copying|copyright|notice|authors|contributors)([.-][^/]*)?$/i.test(
135
+ path,
136
+ )
137
+ )
138
+ decision = { action: "lexical", reason: "legal-or-attribution" };
139
+ else if (
140
+ /\.(csv|tsv|jsonl|ndjson|snap)$/i.test(path) ||
141
+ /(^|\/)__snapshots__\//i.test(path)
142
+ )
143
+ decision = { action: "lexical", reason: "data-or-snapshot" };
144
+ else if (
145
+ /\.(pb\.go|generated\.[^/]+|g\.[^/]+)$/i.test(path) ||
146
+ /_pb2(?:_grpc)?\.py$/i.test(path) ||
147
+ (source !== undefined &&
148
+ /(?:^|\n)[ \t]*(?:\/\/|#|\/\*|\*|<!--)[^\r\n]*(?:code generated[^\r\n]*do not edit|(?:auto[- ]?generated|generated by)[^\r\n]*do not edit|do not edit[^\r\n]*generated|@generated(?:[ \t]|$))/i.test(
149
+ Buffer.from(source, "utf8").subarray(0, 8192).toString("utf8"),
150
+ ))
151
+ )
152
+ decision = { action: "lexical", reason: "generated-source" };
153
+ for (const rule of policy.rules)
154
+ if (glob(rule.pattern).test(path))
155
+ decision = { action: rule.action, reason: `file-rule:${rule.action}` };
156
+ return decision;
157
+ }