agent-doc-system 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +269 -0
  2. package/README.md +97 -112
  3. package/adapters/ansible.mjs +306 -0
  4. package/adapters/cache.mjs +8 -2
  5. package/adapters/contract-consumers.mjs +1 -1
  6. package/adapters/database.mjs +18 -4
  7. package/adapters/events.mjs +1 -1
  8. package/adapters/github-actions.mjs +244 -85
  9. package/adapters/go.mjs +1 -1
  10. package/adapters/index.mjs +5 -2
  11. package/adapters/object-store.mjs +2 -2
  12. package/adapters/python.mjs +1142 -0
  13. package/adapters/resource-client.mjs +1 -1
  14. package/adapters/rust.mjs +987 -0
  15. package/adapters/typescript.mjs +1 -1
  16. package/bin/agentdoc.mjs +60 -44
  17. package/bin/commands.mjs +169 -0
  18. package/bin/usage.txt +5 -3
  19. package/core/audit.mjs +55 -4
  20. package/core/authority/engine.mjs +27 -4
  21. package/core/codes.mjs +11 -0
  22. package/core/compile.mjs +263 -13
  23. package/core/dependents.mjs +42 -0
  24. package/core/descriptors.mjs +5 -2
  25. package/core/discover.mjs +60 -9
  26. package/core/fsx.mjs +27 -4
  27. package/core/graph.mjs +1 -1
  28. package/core/impact.mjs +55 -44
  29. package/core/normative.mjs +171 -0
  30. package/core/query.mjs +18 -8
  31. package/core/scaffold.mjs +87 -22
  32. package/core/sourcescan.mjs +11 -2
  33. package/core/toml.mjs +463 -140
  34. package/core/yaml.mjs +5 -3
  35. package/docs/ADAPTERS.md +232 -7
  36. package/docs/AUTHORITY.md +10 -1
  37. package/docs/BENCHMARK.md +68 -0
  38. package/docs/CI.md +3 -1
  39. package/docs/EVALS.md +33 -2
  40. package/docs/MIGRATION.md +76 -6
  41. package/docs/QUERY.md +53 -4
  42. package/docs/SPEC.md +22 -0
  43. package/docs/assets/readme/agentdoc-hero.webp +0 -0
  44. package/docs/assets/readme/agentdoc-in-action.webp +0 -0
  45. package/docs/assets/readme/how-agentdoc-works.webp +0 -0
  46. package/docs/assets/readme/why-agentdoc.webp +0 -0
  47. package/docs/ci/github-actions.yml +1 -1
  48. package/evals/harness.mjs +32 -6
  49. package/evals/scenarios/fixture-b-03-change-shared-lib.json +8 -1
  50. package/evals/scenarios/fixture-c-03-shared-vocabulary.json +7 -1
  51. package/evals/scenarios/fixture-e-01-change-shared-lib.json +14 -0
  52. package/evals/scenarios/fixture-e-02-change-manifest.json +9 -0
  53. package/evals/scenarios/fixture-e-03-local-module.json +9 -0
  54. package/evals/scenarios/fixture-e-04-ambiguous-crate.json +14 -0
  55. package/evals/scenarios/fixture-f-01-change-shared-lib.json +9 -0
  56. package/evals/scenarios/fixture-f-02-change-manifest.json +9 -0
  57. package/evals/scenarios/fixture-f-03-shadowed-import.json +9 -0
  58. package/evals/scenarios/fixture-f-04-nested-workspace.json +9 -0
  59. package/evals/scenarios/fixture-f-05-alembic-database.json +9 -0
  60. package/evals/scenarios/fixture-f-06-unittest-job.json +9 -0
  61. package/package.json +2 -1
  62. package/schemas/common.schema.json +40 -0
  63. package/schemas/component.schema.json +5 -1
  64. package/schemas/config.schema.json +117 -1
  65. package/skills/agent-doc-system/SKILL.md +5 -1
  66. package/skills/agent-doc-system/references/cli.md +12 -4
  67. package/skills/agent-doc-system/references/create-migrate.md +28 -6
  68. package/templates/ci.yml +1 -1
@@ -1,106 +1,265 @@
1
- // GitHub Actions adapter.
2
- //
3
- // CI is a verification authority: a workflow that names a component's paths is
4
- // the check that must be run for that component. Recording the mapping means
5
- // an agent changing a component is told which gate covers it, instead of
6
- // guessing or running everything.
1
+ // GitHub Actions is verification authority, not evidence of an artifact.
2
+ // Scope each check from its own invocation; a mention elsewhere in a workflow
3
+ // says nothing about which component that command checks.
4
+ import path from "node:path";
7
5
  import { Adapter } from "./registry.mjs";
8
6
  import { parseYamlDocuments } from "../core/yaml.mjs";
9
7
 
10
8
  export class GitHubActionsAdapter extends Adapter {
11
9
  static adapterName = "github-actions";
12
10
  constructor() {
13
- super({ name: "github-actions", version: "1.0.0", kind: "ci" });
14
- }
15
- detect(ctx, root) {
16
- const p = root + "/.github/workflows";
17
- if (!ctx.repo.isDir(p)) return null;
18
- return { path: p, shape: "importable" };
11
+ super({ name: "github-actions", version: "1.2.0", kind: "ci" });
19
12
  }
13
+ // GitHub runs only repository-root workflows. A workflow directory is not a
14
+ // unit marker, and nested workflows (vendored code, subtrees) are inert.
20
15
  contracts(ctx) {
21
16
  const dir = ".github/workflows";
22
17
  if (!ctx.repo.isDir(dir)) return [];
23
- const out = [];
24
- const files = ctx.repo.walk(dir).filter((f) => /\.ya?ml$/.test(f)).sort();
25
18
  const units = ctx.discovery.eligible.filter((u) => u.component);
26
- for (const f of files) {
27
- let text = "";
28
- try {
29
- text = ctx.repo.readText(f);
30
- } catch {
31
- continue;
32
- }
33
- let doc = null;
34
- try {
35
- doc = parseYamlDocuments(text, f)[0]?.doc;
36
- } catch {
37
- doc = null;
38
- }
39
- const steps = [];
40
- if (doc && doc.jobs) {
41
- for (const [jobName, job] of Object.entries(doc.jobs)) {
42
- for (const step of (job && job.steps) || []) {
43
- const run = [step.run, step.uses].filter(Boolean).join(" ");
44
- if (!run) continue;
45
- steps.push({ job: jobName, run: String(run) });
19
+ for (const f of ctx.repo.walk(dir).filter((f) => /\.ya?ml$/.test(f)).sort()) {
20
+ let doc;
21
+ try { doc = parseYamlDocuments(ctx.repo.readText(f), f)[0]?.doc; } catch { continue; }
22
+ const pr = ctx.prov.add("observed", f, "ci-extractor", []);
23
+ const filters = Object.values(doc?.on || {}).flatMap((trigger) => Array.isArray(trigger?.paths) ? trigger.paths : []);
24
+ for (const [jobName, job] of Object.entries(doc?.jobs || {}).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))) {
25
+ for (const [i, step] of (job?.steps || []).entries()) {
26
+ const action = typeof step?.uses === "string";
27
+ let cwd = action
28
+ ? step.with?.["working-directory"] ?? step.with?.working_directory ?? step.with?.path
29
+ : step?.["working-directory"] ?? job.defaults?.run?.["working-directory"] ?? doc.defaults?.run?.["working-directory"];
30
+ const commands = action ? [{ command: step.uses }] : typeof step?.run === "string" ? simpleCommands(step.run) : [];
31
+ for (const [n, { command, words }] of commands.entries()) {
32
+ if (!action && words[0] === "cd") {
33
+ const target = words[1] === "--" ? words.slice(2) : words.slice(1);
34
+ // Only a single literal directory can establish the context of
35
+ // following commands. Resolve successive changes from that cwd.
36
+ const directory = target.length === 1 ? resolveDirectory(cwd, target[0]) : null;
37
+ if (!directory) break;
38
+ cwd = directory;
39
+ continue;
40
+ }
41
+ if (!action && ["pushd", "popd", "source", ".", "eval", "export"].includes(words[0])) break;
42
+ const invocations = action ? actionInvocations(command) : ciInvocations(words, cwd, ctx.repo);
43
+ if (command.length > 600) continue;
44
+ for (const [task, invocation] of invocations.entries()) {
45
+ const { tier, args, directory: invocationDirectory } = invocation;
46
+ const scopeCwd = invocationDirectory === undefined ? cwd : resolveDirectory(cwd, invocationDirectory);
47
+ if (invocationDirectory !== undefined && !scopeCwd) continue;
48
+ const covered = units.filter((u) => scopedTo(u, args, scopeCwd, filters, units, ctx.repo)).map((u) => u.component.ref).sort();
49
+ const suffix = f.replace(/[^A-Za-z0-9]+/g, "-") + ":" + jobName + ":" + i + (commands.length > 1 ? ":" + n : "") + (invocations.length > 1 ? ":task:" + task : "");
50
+ const id = (covered.length ? "ci:" : "repo-gate:") + suffix;
51
+ const directory = literalPath(cwd);
52
+ const record = {
53
+ id, tier,
54
+ command: !action && directory && directory !== "." ? "cd " + shellQuote(directory) + " && " + command : command,
55
+ configPaths: [f], provRecs: [pr],
56
+ };
57
+ if (covered.length) ctx.addVerification({ ...record, componentRefs: covered });
58
+ else ctx.addGate(record);
59
+ pr.subjects.add((covered.length ? "ver:" : "gate:") + id);
60
+ }
46
61
  }
47
62
  }
48
63
  }
49
- const covered = new Set();
50
- for (const u of units) {
51
- if (text.includes(u.root + "/") || (u.pkgName && text.includes(u.pkgName))) covered.add(u.component.ref);
52
- }
53
- const pr = ctx.prov.add("observed", f, "ci-extractor", []);
54
- const subjects = [];
55
- steps.forEach((s, i) => {
56
- if (!/[a-z0-9]/i.test(s.run)) return;
57
- const command = s.run.trim().slice(0, 600);
58
- // A step that installs dependencies or checks out code is not a check.
59
- // Recording it as one would bury the commands that actually gate.
60
- const tier = ciTier(command);
61
- if (!tier) return;
62
- const id = "ci:" + f.replace(/[^A-Za-z0-9]+/g, "-") + ":" + s.job + ":" + i;
63
- if (covered.size) {
64
- ctx.addVerification({
65
- id,
66
- componentRefs: [...covered].sort(),
67
- tier,
68
- command,
69
- configPaths: [f],
70
- provRecs: [pr],
71
- });
72
- subjects.push("ver:" + id);
73
- } else {
74
- // A gate that names no component is repository-wide: it applies to
75
- // everything, so it is recorded once as a gate rather than
76
- // duplicated onto every component.
77
- // The graph id must not itself start with the provenance prefix, or
78
- // the subject key becomes ambiguous. "repo-gate:" is unambiguous.
79
- const gid = "repo-gate:" + f.replace(/[^A-Za-z0-9]+/g, "-") + ":" + s.job + ":" + i;
80
- ctx.addGate({ id: gid, tier: ciTier(command), command, configPaths: [f], provRecs: [pr] });
81
- subjects.push("gate:" + gid);
82
- }
83
- });
84
- for (const s of subjects) pr.subjects.add(s);
85
- void steps;
86
- void covered;
87
64
  }
88
- return out;
65
+ return [];
89
66
  }
90
67
  }
91
68
 
92
- // Returns null for a step that is not a check. Install, checkout, cache and
93
- // upload steps gate nothing and would only add noise to a routing surface.
94
- const NOT_A_CHECK = /^\s*(?:-\s*)?(?:uses:\s*actions\/(?:checkout|cache|upload|download|setup-|artifact)|run:\s*(?:\w*\s*)?(?:pnpm|npm|yarn|bun)\s+(?:i|install|ci)\b)/i;
69
+ // Read only simple shell invocations, splitting newline, `;` and `&&` outside
70
+ // quotes. Control flow, pipelines, substitutions and heredocs require shell
71
+ // interpretation, so refuse the step rather than extract text inside them.
72
+ function simpleCommands(run) {
73
+ run = run.replace(/\\\r?\n/g, " ");
74
+ const out = [];
75
+ let words = [], word = "", quote = null, start = 0;
76
+ const endWord = () => { if (word) words.push(word); word = ""; };
77
+ const endCommand = (end) => {
78
+ endWord();
79
+ if (words.length) out.push({ command: run.slice(start, end).trim(), words });
80
+ words = [];
81
+ };
82
+ for (let i = 0; i < run.length; i++) {
83
+ const c = run[i];
84
+ if (c === "`" || (c === "$" && run[i + 1] === "(")) return [];
85
+ if (c === "\\" && quote !== "'") {
86
+ const next = run[++i];
87
+ if (next !== "\n" && next !== undefined) word += next;
88
+ } else if (quote) {
89
+ if (c === quote) quote = null;
90
+ else if (c === "\n") return [];
91
+ else word += c;
92
+ } else if (c === '"' || c === "'") quote = c;
93
+ else if (c === "#" && !word) {
94
+ endCommand(i);
95
+ while (i < run.length && run[i] !== "\n") i++;
96
+ start = i + 1;
97
+ } else if (c === "\n" || c === ";" || (c === "&" && run[i + 1] === "&")) {
98
+ endCommand(i);
99
+ if (c === "&") i++;
100
+ start = i + 1;
101
+ } else if (/[|&<>(){}]/.test(c)) return [];
102
+ else if (/\s/.test(c)) endWord();
103
+ else word += c;
104
+ }
105
+ if (quote) return [];
106
+ endCommand(run.length);
107
+ if (out.some(({ words: [first] }) => /^(?:if|then|else|elif|fi|for|while|until|do|done|case|esac|function)$/.test(first))) return [];
108
+ return out;
109
+ }
95
110
 
96
- function ciTier(run) {
97
- if (NOT_A_CHECK.test(run.trim())) return null;
98
- if (/^uses:/.test(run.trim()) && !/check|lint|test|build|audit|scan/i.test(run)) return null;
99
- if (/e2e|playwright|cypress/.test(run)) return "e2e";
100
- if (/contract|openapi|api:check|drift|catalog/.test(run)) return "contract";
101
- if (/integration/.test(run)) return "integration";
102
- if (/lint|typecheck|type-check|golangci|vet/.test(run)) return "static";
103
- if (/\btest\b|pytest|vitest|jest|go test/.test(run)) return "unit";
104
- if (/\bbuild\b|tsc|compile/.test(run)) return "build";
111
+ function taskTier(task) {
112
+ if (!/^(?:test|tests|e2e|integration|lint|typecheck|type-check|build|compile|check|api:check|contract|catalog|drift)(?:$|[:_-])/.test(task || "")) return null;
113
+ if (/(?:^|[:_-])(?:e2e|playwright|cypress)(?:$|[:_-])/.test(task)) return "e2e";
114
+ if (/(?:contract|openapi|api:check|drift|catalog)/.test(task)) return "contract";
115
+ if (/(?:^|[:_-])integration(?:$|[:_-])/.test(task)) return "integration";
116
+ if (/^(?:lint|typecheck|type-check)/.test(task)) return "static";
117
+ if (/^(?:tests?|check)(?:$|[:_-])/.test(task)) return "unit";
118
+ if (/^(?:build|compile)(?:$|[:_-])/.test(task)) return "build";
105
119
  return null;
106
120
  }
121
+
122
+ const VALUE_OPTIONS = new Set(["--filter", "-F", "--prefix", "--dir", "--cwd", "-C", "--workspace", "-w"]);
123
+ function ciInvocations(words, cwd, repo) {
124
+ let [exe, ...args] = words;
125
+ if (exe === "env") [exe, ...args] = args;
126
+ while (/^[A-Za-z_][A-Za-z0-9_]*=/.test(exe || "")) [exe, ...args] = args;
127
+ if (["uv", "poetry"].includes(exe) && args[0] === "run") [exe, ...args] = args.slice(1);
128
+ if (["npx", "bunx"].includes(exe)) [exe, ...args] = args;
129
+ const result = (tier, scopeArgs = args) => tier ? [{ tier, args: scopeArgs }] : [];
130
+ if (["npm", "pnpm", "yarn", "bun"].includes(exe)) {
131
+ const selectors = [];
132
+ while (args[0]?.startsWith("-")) {
133
+ const option = args.shift();
134
+ selectors.push(option);
135
+ if (VALUE_OPTIONS.has(option) && args.length) selectors.push(args.shift());
136
+ }
137
+ if (["exec", "dlx"].includes(args[0])) {
138
+ return ciInvocations(args.slice(1), cwd, repo).map((inner) => ({ ...inner, args: [...selectors, ...inner.args] }));
139
+ }
140
+ if (["turbo", "nx"].includes(args[0])) {
141
+ return ciInvocations(args, cwd, repo).map((inner) => ({ ...inner, args: [...selectors, ...inner.args] }));
142
+ }
143
+ if (args[0] === "run") args.shift();
144
+ return result(taskTier(args[0]), [...selectors, ...args.slice(1)]);
145
+ }
146
+ if (exe === "node" && args.includes("--test")) return result("unit");
147
+ if (/^python(?:3(?:\.\d+)?)?$/.test(exe) && args[0] === "-m") return ciInvocations(args.slice(1), cwd, repo);
148
+ if (["bash", "sh", "node"].includes(exe) || /^python(?:3(?:\.\d+)?)?$/.test(exe)) {
149
+ return scriptInvocation(args[0], args.slice(1), cwd, repo);
150
+ }
151
+ if (["pytest", "unittest", "vitest", "jest", "mocha", "ava"].includes(exe)) return result("unit");
152
+ if (["playwright", "cypress"].includes(exe) && ["test", "run"].includes(args[0])) return result("e2e", args.slice(1));
153
+ if (["eslint", "mypy", "golangci-lint"].includes(exe)) return result("static");
154
+ if (["ruff", "biome"].includes(exe) && ["check", "lint"].includes(args[0])) return result("static", args.slice(1));
155
+ if (exe === "tsc") return result(args.includes("--noEmit") ? "static" : "build");
156
+ if (["go", "cargo", "dotnet"].includes(exe)) return result(({ test: "unit", vet: "static", clippy: "static", build: "build" })[args[0]], args.slice(1));
157
+ if (["turbo", "nx", "make", "just"].includes(exe)) return taskInvocations(exe, args);
158
+ if (exe === "agentdoc" && args[0] === "compile") return result("build", args.slice(1));
159
+ if (exe?.includes("/")) return scriptInvocation(exe, args, cwd, repo);
160
+ return [];
161
+ }
162
+
163
+ function scriptInvocation(script, args, cwd, repo) {
164
+ if (typeof script !== "string") return [];
165
+ const tier = taskTier(path.posix.basename(script, path.posix.extname(script)));
166
+ const file = tier && resolveDirectory(cwd, script);
167
+ // A check-like basename alone does not prove a repository script. Resolve
168
+ // from the step directory and keep only literal files inside the checkout.
169
+ if (!file || !repo.exists(file) || repo.isDir(file)) return [];
170
+ return [{ tier, args: [script, ...args] }];
171
+ }
172
+
173
+ // Task names determine tiers; option values are never mistaken for tasks or
174
+ // free-form path scope. Multi-task runs contribute one record per tier.
175
+ function taskInvocations(exe, args) {
176
+ const tasks = [], selectors = [];
177
+ let directory;
178
+ const valueOptions = new Set(["--filter", "-F", "-C", "--directory", "--cwd", "-t", "--target", "--targets", "--projects", "--configuration", "-c", "--parallel", "--concurrency", "--cache-dir", "--output-logs"]);
179
+ for (let i = 0; i < args.length; i++) {
180
+ const token = args[i];
181
+ if (token === "--") break;
182
+ const [option, inline] = token.split(/=(.*)/s);
183
+ if (token.startsWith("-")) {
184
+ const value = inline ?? (valueOptions.has(option) ? args[++i] : undefined);
185
+ if (["--filter", "-F"].includes(option)) selectors.push(option, value || "");
186
+ if (exe === "nx" && option === "--projects") {
187
+ for (const project of (value || "").split(",")) selectors.push("--project", project);
188
+ }
189
+ if (exe === "nx" && ["-t", "--target", "--targets"].includes(option)) tasks.push(...(value || "").split(","));
190
+ if (exe === "make" && ["-C", "--directory"].includes(option)) directory = directory === undefined ? value ?? null : resolveDirectory(directory, value);
191
+ if (exe === "turbo" && option === "--cwd") directory = value ?? null;
192
+ continue;
193
+ }
194
+ if (exe === "nx") {
195
+ if (args[0] === "run" && i === 1) {
196
+ const [project, task] = token.split(":");
197
+ selectors.push("--project", project);
198
+ tasks.push(task);
199
+ } else if (["run-many", "affected"].includes(args[0]) && i > 0) tasks.push(...token.split(","));
200
+ } else if (!(exe === "turbo" && token === "run") && !token.includes("=")) tasks.push(token);
201
+ }
202
+ return [...new Set(tasks.map(taskTier).filter(Boolean))].map((tier) => ({ tier, args: selectors, directory }));
203
+ }
204
+
205
+ function actionInvocations(action) {
206
+ const name = action.split("@")[0].toLowerCase();
207
+ if (/setup|checkout|cache|upload|download|artifact/.test(name) || !/lint|test|check|scan|audit/.test(name)) return [];
208
+ const tier = /e2e|playwright|cypress/.test(name) ? "e2e"
209
+ : /contract|openapi|drift|catalog/.test(name) ? "contract"
210
+ : /integration/.test(name) ? "integration"
211
+ : /lint|typecheck|type-check|scan|audit/.test(name) ? "static"
212
+ : /test/.test(name) ? "unit" : "static";
213
+ return [{ tier, args: [] }];
214
+ }
215
+
216
+ function resolveDirectory(cwd, target) {
217
+ if (typeof target !== "string" || !target || target.startsWith("-") || /[$`*?!{}\[\]~]/.test(target) || path.posix.isAbsolute(target)) return null;
218
+ const base = cwd === undefined ? "." : literalPath(cwd);
219
+ return base ? literalPath(path.posix.join(base, target)) : null;
220
+ }
221
+
222
+ function literalPath(value) {
223
+ if (typeof value !== "string" || !value || /[$`*?!{}]/.test(value) || path.posix.isAbsolute(value)) return null;
224
+ const p = path.posix.normalize(value);
225
+ return p === ".." || p.startsWith("../") ? null : p.replace(/\/$/, "");
226
+ }
227
+ function within(p, root) { return root === "." || p === root || p.startsWith(root + "/"); }
228
+ function shellQuote(p) { return /^[A-Za-z0-9_./-]+$/.test(p) ? p : "'" + p.replace(/'/g, "'\\''") + "'"; }
229
+
230
+ function scopedTo(unit, args, cwd, filters, units, repo) {
231
+ const root = path.posix.normalize(unit.root);
232
+ const directory = literalPath(cwd);
233
+ // A directory belongs to its deepest enclosing component, not every parent.
234
+ if (directory && within(directory, root) && !units.some((u) => u.root !== unit.root && within(directory, u.root) && within(u.root, root))) return true;
235
+ if (cwd !== undefined && !directory) return false;
236
+ for (let i = 0; i < args.length; i++) {
237
+ const token = args[i];
238
+ const [option, inline] = token.split(/=(.*)/s);
239
+ const value = inline ?? args[i + 1];
240
+ if (option === "--project") {
241
+ let name = unit.pkgName;
242
+ const config = path.posix.join(unit.root, "project.json");
243
+ try { if (repo.exists(config)) name = repo.readJson(config).name || name; } catch { /* an unreadable project config cannot name a scope */ }
244
+ if (value === name || value === unit.root) return true;
245
+ i++;
246
+ continue;
247
+ }
248
+ // Package names are scope only in an explicit package selector.
249
+ if (unit.pkgName && ["--filter", "-F", "--workspace", "-w"].includes(option) && value === unit.pkgName) return true;
250
+ const arg = token.startsWith("-") ? inline : token;
251
+ if (!arg || /[$`]/.test(arg)) continue;
252
+ if (path.posix.isAbsolute(arg)) continue;
253
+ const p = literalPath(path.posix.join(directory || ".", arg.replace(/\/[*?].*$/, "")));
254
+ if (p && root !== "." && within(p, root)) return true;
255
+ }
256
+ // A literal prefix in a workflow trigger is evidence of component scope.
257
+ // Negative filters cannot create scope and can exclude an entire root.
258
+ const matches = (filter) => {
259
+ if (typeof filter !== "string") return false;
260
+ const prefix = literalPath(filter.replace(/\/[*?].*$/, ""));
261
+ return prefix && prefix !== "." && (within(prefix, root) || within(root, prefix));
262
+ };
263
+ return filters.some((p) => typeof p === "string" && !p.startsWith("!") && matches(p)) &&
264
+ !filters.some((p) => typeof p === "string" && p.startsWith("!") && within(root, literalPath(p.slice(1).replace(/\/\*\*$/, "")) || "\0"));
265
+ }
package/adapters/go.mjs CHANGED
@@ -61,7 +61,7 @@ export class GoAdapter extends Adapter {
61
61
  }
62
62
  }
63
63
 
64
- const hasTests = repo.walk(unit.root).some((f) => /_test\.go$/.test(f));
64
+ const hasTests = repo.walkUnit(unit.root).some((f) => /_test\.go$/.test(f));
65
65
  ctx.addVerification({
66
66
  id: compRef + ":go-test",
67
67
  componentRefs: [compRef],
@@ -8,6 +8,8 @@ import { PnpmAdapter } from "./pnpm.mjs";
8
8
  import { TurborepoAdapter } from "./turborepo.mjs";
9
9
  import { TypeScriptAdapter } from "./typescript.mjs";
10
10
  import { GoAdapter } from "./go.mjs";
11
+ import { RustAdapter } from "./rust.mjs";
12
+ import { PythonAdapter } from "./python.mjs";
11
13
  import { OpenApiAdapter } from "./openapi.mjs";
12
14
  import { ProtobufAdapter } from "./protobuf.mjs";
13
15
  import { GraphQLAdapter } from "./graphql.mjs";
@@ -20,13 +22,14 @@ import { EventsAdapter } from "./events.mjs";
20
22
  import { DeployConfigAdapter } from "./deploy-config.mjs";
21
23
  import { ObjectStoreAdapter } from "./object-store.mjs";
22
24
  import { CacheAdapter } from "./cache.mjs";
25
+ import { AnsibleAdapter } from "./ansible.mjs";
23
26
 
24
27
  const REGISTRY = new Map();
25
28
  for (const C of [
26
29
  GenericAdapter, NodeAdapter, PnpmAdapter, TurborepoAdapter, TypeScriptAdapter,
27
- GoAdapter, OpenApiAdapter, ProtobufAdapter, GraphQLAdapter, OidcAdapter,
30
+ GoAdapter, RustAdapter, PythonAdapter, OpenApiAdapter, ProtobufAdapter, GraphQLAdapter, OidcAdapter,
28
31
  GitHubActionsAdapter, DockerfileAdapter, WranglerAdapter, DatabaseAdapter,
29
- EventsAdapter, DeployConfigAdapter, ObjectStoreAdapter, CacheAdapter,
32
+ EventsAdapter, DeployConfigAdapter, ObjectStoreAdapter, CacheAdapter, AnsibleAdapter,
30
33
  ]) {
31
34
  REGISTRY.set(C.prototype.constructor.adapterName, new C());
32
35
  }
@@ -10,7 +10,7 @@ const SRC_EXT = /\.(ts|tsx|js|jsx|mts|cts|go|py|rb|java|kt|rs)$/;
10
10
  const TEST_PATH = /(^|\/)(tests?|__tests__|spec)\//;
11
11
  const TEST_FILE = /\.(test|spec)\./;
12
12
  const BUCKET_LITERAL = /["'`]([a-z][a-z0-9-]{4,60})["'`]/g;
13
- const STORE_HINT = /(@aws-sdk\/client-s3|minio|google\.cloud\.storage|\bS3\b|bucket|Bucket|BlobService|createBucket)/i;
13
+ const STORE_HINT = /(@aws-sdk\/client-s3|aws-sdk-s3|aws_sdk_s3|minio|google\.cloud\.storage|boto3|\bS3\b|bucket|Bucket|BlobService|createBucket)/i;
14
14
 
15
15
  export class ObjectStoreAdapter extends Adapter {
16
16
  static adapterName = "object-store";
@@ -24,7 +24,7 @@ export class ObjectStoreAdapter extends Adapter {
24
24
  const usesStore = ctx.unitImports && [...ctx.unitImports.keys()].some((k) => /s3|minio|storage|blob/i.test(k));
25
25
  const bindings = [];
26
26
  const claimed = new Set();
27
- for (const f of repo.walk(unit.root)) {
27
+ for (const f of repo.walkUnit(unit.root)) {
28
28
  if (!SRC_EXT.test(f)) continue;
29
29
  if (TEST_PATH.test(f) || TEST_FILE.test(f)) continue;
30
30
  const text = repo.readText(f);