vigiles 27.3.0 → 29.0.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 (59) hide show
  1. package/README.md +1 -1
  2. package/dist/adapter-registry.d.ts +39 -0
  3. package/dist/adapter-registry.js +45 -0
  4. package/dist/adapter.d.ts +8 -0
  5. package/dist/adapter.js +10 -1
  6. package/dist/adapters/claude-code/adapter.js +9 -2
  7. package/dist/adapters/claude-code/layout.d.ts +5 -0
  8. package/dist/adapters/claude-code/plugin-loader.d.ts +10 -1
  9. package/dist/adapters/claude-code/plugin-loader.js +10 -1
  10. package/dist/adapters/codex/adapter.js +7 -2
  11. package/dist/adapters/codex/layout.d.ts +51 -5
  12. package/dist/adapters/codex/layout.js +13 -4
  13. package/dist/adapters/opencode/adapter.js +6 -0
  14. package/dist/audit-report.template.html +1 -1
  15. package/dist/audit-score.d.ts +7 -0
  16. package/dist/audit-score.js +49 -2
  17. package/dist/cli-main.js +162 -39
  18. package/dist/core/adapter.d.ts +45 -1
  19. package/dist/core/compile.js +11 -1
  20. package/dist/core/config-schema.d.ts +244 -0
  21. package/dist/core/config-schema.js +452 -0
  22. package/dist/core/hook-program.d.ts +43 -0
  23. package/dist/core/hook-program.js +32 -0
  24. package/dist/core/refs.js +10 -1
  25. package/dist/core/surface-discovery.d.ts +270 -0
  26. package/dist/core/surface-discovery.js +425 -0
  27. package/dist/core/surface-scopes.d.ts +38 -1
  28. package/dist/core/surface-scopes.js +73 -1
  29. package/dist/core/symbols.d.ts +24 -2
  30. package/dist/core/symbols.js +66 -18
  31. package/dist/core/types.d.ts +36 -107
  32. package/dist/core/validate.d.ts +46 -18
  33. package/dist/core/validate.js +98 -172
  34. package/dist/exclude.d.ts +20 -0
  35. package/dist/exclude.js +11 -1
  36. package/dist/harness-test.js +3 -3
  37. package/dist/hook-install.d.ts +53 -0
  38. package/dist/hook-install.js +60 -0
  39. package/dist/hook-runtime.d.ts +2 -2
  40. package/dist/hook-runtime.js +82 -63
  41. package/dist/layout-registry.d.ts +14 -0
  42. package/dist/layout-registry.js +40 -0
  43. package/dist/load-hook.d.ts +1 -1
  44. package/dist/load-hook.js +2 -2
  45. package/dist/plugin-loader.d.ts +49 -1
  46. package/dist/plugin-loader.js +120 -14
  47. package/dist/run-hook.js +17 -1
  48. package/dist/scan-core.d.ts +19 -0
  49. package/dist/scan-core.js +30 -0
  50. package/dist/scan-files.js +15 -5
  51. package/dist/scan.d.ts +63 -0
  52. package/dist/scan.js +68 -12
  53. package/dist/score-core.js +8 -0
  54. package/dist/setup-plan.d.ts +2 -1
  55. package/dist/setup-plan.js +7 -2
  56. package/dist/surface-discovery-fs.d.ts +12 -0
  57. package/dist/surface-discovery-fs.js +108 -0
  58. package/dist/vigilesrc.schema.json +1689 -0
  59. package/package.json +10 -6
package/dist/scan.js CHANGED
@@ -35,10 +35,15 @@ exports.expandMarketplace = expandMarketplace;
35
35
  exports.formatScanReport = formatScanReport;
36
36
  const node_fs_1 = require("node:fs");
37
37
  const node_path_1 = require("node:path");
38
- const plugin_loader_js_1 = require("./adapters/claude-code/plugin-loader.js");
38
+ // The COMPOSITION-ROOT loader, not the `vigiles/claude-code` wrapper: this
39
+ // module always passes a layout explicitly (it never wanted the wrapper's
40
+ // Claude Code default), and only the generic one takes the `ExcludeSet` that
41
+ // makes `.vigilesrc.json#exclude` reach surface discovery.
42
+ const plugin_loader_js_1 = require("./plugin-loader.js");
39
43
  const layout_js_1 = require("./adapters/claude-code/layout.js");
40
44
  const dialect_js_1 = require("./adapters/claude-code/dialect.js");
41
45
  const plugin_loader_js_2 = require("./plugin-loader.js");
46
+ const score_core_js_1 = require("./score-core.js");
42
47
  const skill_refs_js_1 = require("./skill-refs.js");
43
48
  const hook_events_js_1 = require("./core/hook-events.js");
44
49
  const mcp_config_js_1 = require("./core/mcp-config.js");
@@ -49,6 +54,9 @@ const mcp_contract_message_js_1 = require("./core/mcp-contract-message.js");
49
54
  const mcp_hook_js_1 = require("./core/mcp-hook.js");
50
55
  const merge_conflict_js_1 = require("./core/merge-conflict.js");
51
56
  const plugin_dir_layout_js_1 = require("./core/plugin-dir-layout.js");
57
+ const surface_discovery_js_1 = require("./core/surface-discovery.js");
58
+ const surface_discovery_fs_js_1 = require("./surface-discovery-fs.js");
59
+ const layout_registry_js_1 = require("./layout-registry.js");
52
60
  const hook_block_ineffective_js_1 = require("./core/hook-block-ineffective.js");
53
61
  const hook_matcher_js_1 = require("./core/hook-matcher.js");
54
62
  const test_coverage_js_1 = require("./test-coverage.js");
@@ -139,8 +147,23 @@ function ownTestSignalOnDisk(dir) {
139
147
  /** Scan a plugin/repo directory and report its surfaces + structural issues. */
140
148
  function scanPlugin(dir, layout, dialect = dialect_js_1.claudeCodeDialect, opts = {}) {
141
149
  const lay = layout ?? layout_js_1.claudeCodeLayout;
142
- const cls = (0, scan_core_js_1.makeClassifier)(lay);
143
- const loaded = (0, plugin_loader_js_1.loadPlugin)(dir, lay);
150
+ // The declared harnesses, or the single positional one — so every path below
151
+ // is the multi-harness path and a one-entry list is not a second code path.
152
+ const declared = opts.harnesses && opts.harnesses.length > 0
153
+ ? opts.harnesses
154
+ : [{ layout: lay, dialect, roots: [] }];
155
+ // A path is a surface if ANY declared harness reads it as one. Classifying a
156
+ // merged file map with one layout would drop the other harness's surfaces
157
+ // silently — the same shape of miss as grading a scan that opened nothing.
158
+ const cls = (0, scan_core_js_1.makeUnionClassifier)(declared.map((h) => h.layout));
159
+ const loaded = (0, plugin_loader_js_1.loadPlugins)(dir, declared.map((h) => ({ layout: h.layout, roots: h.roots })), opts.excludes);
160
+ // WHICH harness's instruction file this repo actually has. The first declared
161
+ // one that is present, not the first one declared: a repo listing
162
+ // `claude-code` before `codex` and shipping only `AGENTS.md` has an
163
+ // instruction file, and saying it does not was half of #240's wrong grade.
164
+ // Its dialect carries the budget the weight below is measured against, so the
165
+ // file named and the limit it is judged by come from the same harness.
166
+ const instructionHarness = declared.find((h) => loaded.files[h.layout.instructionFile] !== undefined) ?? declared[0];
144
167
  // Parse the raw `settings.hooks` ONCE at the boundary (parse-don't-validate):
145
168
  // tolerant of the Claude Code nested shape AND the Codex flat shape, so every
146
169
  // hook detector below consumes typed `HookRegistration[]` instead of re-walking
@@ -157,10 +180,11 @@ function scanPlugin(dir, layout, dialect = dialect_js_1.claudeCodeDialect, opts
157
180
  const eventNames = (0, hook_normalize_js_1.hookEventNames)(loaded.settings.hooks);
158
181
  const allHookEventIssues = (0, hook_events_js_1.verifyHookEvents)(eventNames, dialect);
159
182
  const hookEventIssues = (0, hook_events_js_1.scoredIssues)(allHookEventIssues);
160
- const instructions = loaded.files[lay.instructionFile] !== undefined
183
+ const instructionFile = instructionHarness.layout.instructionFile;
184
+ const instructions = loaded.files[instructionFile] !== undefined
161
185
  ? {
162
- file: lay.instructionFile,
163
- hasSpec: (0, node_fs_1.existsSync)((0, node_path_1.join)((0, node_path_1.resolve)(dir), `${lay.instructionFile}.spec.ts`)),
186
+ file: instructionFile,
187
+ hasSpec: (0, node_fs_1.existsSync)((0, node_path_1.join)((0, node_path_1.resolve)(dir), `${instructionFile}.spec.ts`)),
164
188
  }
165
189
  : null;
166
190
  const mcpServers = collectMcpServers((0, node_path_1.resolve)(dir), lay);
@@ -188,7 +212,17 @@ function scanPlugin(dir, layout, dialect = dialect_js_1.claudeCodeDialect, opts
188
212
  // deterministic tier (`Tested`), and the real-model tier (`Evaluated`). The
189
213
  // tiers differ in cost, cadence AND in the question they answer, so collapsing
190
214
  // them here would make the difference unrecoverable downstream.
191
- const coverage = (0, test_coverage_js_1.findUntestedSurfaces)({ basePath: dir, layout: lay });
215
+ const coverage = (0, test_coverage_js_1.findUntestedSurfaces)({
216
+ basePath: dir,
217
+ layout: lay,
218
+ // The SAME `.vigilesrc.json#exclude`, in this walk's string face. Untested-
219
+ // surface discovery is a second walk over the same trees, so leaving it out
220
+ // would have excluded a skill from the GRADE while still naming it in
221
+ // "Untested surfaces: 1" — a report contradicting itself about whether the
222
+ // file exists. `exclude` here NARROWS (it unions with DEFAULT_IGNORE), which
223
+ // is the documented relationship between the repo floor and a rule's own list.
224
+ exclude: opts.excludes ? [...opts.excludes.ignore] : undefined,
225
+ });
192
226
  const caveats = (0, test_coverage_js_1.coverageCaveats)(coverage);
193
227
  return {
194
228
  dir,
@@ -229,6 +263,11 @@ function scanPlugin(dir, layout, dialect = dialect_js_1.claudeCodeDialect, opts
229
263
  trifectaFindings,
230
264
  skillResourceIssues: skillResourceFindings,
231
265
  skillFenceIssues: skillFenceFindings,
266
+ unclaimedSurfaces: (0, surface_discovery_js_1.unclaimedSurfaceFindings)((0, surface_discovery_fs_js_1.boundedSurfacePaths)((0, node_path_1.resolve)(dir), opts.excludes), layout_registry_js_1.REGISTERED_LAYOUTS, declared.map((h) => ({
267
+ harness: h.layout.name,
268
+ layout: h.layout,
269
+ roots: h.roots,
270
+ }))),
232
271
  pluginLayoutIssues: (0, plugin_dir_layout_js_1.pluginDirLayoutIssues)((0, node_path_1.resolve)(dir, (0, node_path_1.dirname)(lay.manifestPath)),
233
272
  // The hooks dir is a misplaceable functional surface too, but it lives in
234
273
  // the layout as a convention PATH (`hooks/hooks.json`), not in surfaceDirs
@@ -257,8 +296,13 @@ function scanPlugin(dir, layout, dialect = dialect_js_1.claudeCodeDialect, opts
257
296
  return (0, node_fs_1.existsSync)(p) ? nodeReadFile(p) : undefined;
258
297
  }).map(merge_conflict_js_1.mergeConflictWarning),
259
298
  ],
260
- instructionWeight: dialect.instructionBudget
261
- ? (0, instruction_weight_js_1.weighInstructions)(readAlwaysLoaded(dir, dialect.instructionBudget), dialect.instructionBudget)
299
+ // Measured under the dialect whose instruction file this repo HAS — see
300
+ // `instructionHarness`. A budget is a harness's own number in a harness's own
301
+ // unit (Claude Code counts 40 000 chars, Codex 32 768 bytes), so there is no
302
+ // meaningful sum across two; reporting the one that owns the file that
303
+ // exists is the only reading that is true of something.
304
+ instructionWeight: instructionHarness.dialect.instructionBudget
305
+ ? (0, instruction_weight_js_1.weighInstructions)(readAlwaysLoaded(dir, instructionHarness.dialect.instructionBudget), instructionHarness.dialect.instructionBudget)
262
306
  : null,
263
307
  untested: coverage.untested.length,
264
308
  untestedHarness: coverage.harness.untested.length,
@@ -588,6 +632,11 @@ function formatScanReport(r) {
588
632
  out.push(...section("Skill bundled resources", r.skillResourceIssues.map((s) => ` ✗ ${s.name}: ${s.finding.ref} (line ${String(s.finding.line)}) — bundled resource not found`)));
589
633
  out.push(...section("Invisible skills (missing frontmatter fence)", r.skillFenceIssues.map((s) => ` ✗ ${s.name} (${s.path}): opens with \`${s.finding.key}:\` but no \`---\` fence — loads as body, never fires`)));
590
634
  out.push(...section("Misplaced plugin directories", r.pluginLayoutIssues.map((p) => ` ✗ ${p.message}`)));
635
+ // A harness sitting where no adapter reads (#240). Printed as a ✗ section like
636
+ // any other structural defect, because that is what it is: the grade above it
637
+ // was computed without this directory in it, and the old behaviour was to say
638
+ // nothing at all while returning A (100/100).
639
+ out.push(...section("Surfaces no harness reads", r.unclaimedSurfaces.map((u) => ` ✗ ${u.message}`)));
591
640
  out.push(...section("Lethal trifecta across delegation (blast radius)", r.delegationTrifecta.map((d) => ` ⚠ ${d.finding.name} (${d.path}): ${d.finding.message}`)));
592
641
  out.push(...section("Ineffective hook guards (false confidence)", r.hookBlockFindings.map((h) => ` ✗ [${h.event}] ${h.scriptPath ?? "(inline)"}: ${h.message}`)));
593
642
  out.push(...section("Hook matchers that don't fire as written", r.hookMatcherFindings.map((m) => ` ✗ ${m.message}`)));
@@ -658,6 +707,7 @@ function formatScanReport(r) {
658
707
  r.skillResourceIssues.length +
659
708
  r.skillFenceIssues.length +
660
709
  r.pluginLayoutIssues.length +
710
+ r.unclaimedSurfaces.length +
661
711
  r.hookBlockFindings.length +
662
712
  r.hookMatcherFindings.length +
663
713
  // Only HARD trifectas (✗) count as STRUCTURAL defects here. Both severities are
@@ -667,9 +717,15 @@ function formatScanReport(r) {
667
717
  // "broken" in the CLI summary is a separate, louder call. The delegation-trifecta
668
718
  // ⚠ risk is ungraded and does NOT count either.
669
719
  r.trifectaFindings.filter((t) => t.finding.severity === "hard").length;
670
- out.push(broken === 0
671
- ? "✓ no structural issues found"
672
- : `⚠ ${String(broken)} structural issue(s) — see ✗/⚠ above`);
720
+ out.push(broken > 0
721
+ ? `⚠ ${String(broken)} structural issue(s) — see ✗/⚠ above`
722
+ : // 🔴 "NOTHING WAS FOUND" IS NOT "NOTHING IS WRONG" (#240). With zero
723
+ // surfaces read, `broken` is zero for want of anything to count, and this
724
+ // line was the sentence the reporter quoted under an A (100): a repo whose
725
+ // 37 skills sat in a directory this tool does not know by name.
726
+ (0, score_core_js_1.isEmptyMachine)(r)
727
+ ? "⚠ nothing to check — 0 skills, 0 agents, 0 commands were read"
728
+ : "✓ no structural issues found");
673
729
  return out.join("\n");
674
730
  }
675
731
  //# sourceMappingURL=scan.js.map
@@ -290,6 +290,14 @@ function reportDeductions(r) {
290
290
  weight: exports.W_NO_DESCRIPTION,
291
291
  label: "functional dir(s) misplaced inside `.claude-plugin/` (invisible)",
292
292
  },
293
+ // A surface dir NO registered harness reads (#240). Same weight and label as
294
+ // the Structure ring's row by construction — the two are kept identical on
295
+ // purpose, so the headline and the breakdown cannot disagree about it.
296
+ {
297
+ n: r.unclaimedSurfaces.length,
298
+ weight: exports.W_NO_DESCRIPTION,
299
+ label: "surface dir(s) no harness reads (not in this grade)",
300
+ },
293
301
  {
294
302
  n: r.hookBlockFindings.length,
295
303
  weight: exports.W_MISSING_HOOK,
@@ -133,7 +133,8 @@ export declare const WORKFLOW_RULES: readonly ["require-instructions-spec", "unt
133
133
  */
134
134
  export declare const NUDGE_RULES: readonly ["skill-description-budget", "frontmatter-valid", "skill-frontmatter", "prefer-compiled-hooks", "unmarked-refs", "lethal-trifecta", "skill-resource-resolves", "skill-missing-fence", "plugin-dir-layout", "delegation-trifecta", "hook-block-ineffective", "hook-matcher", "doc-refs"];
135
135
  export declare function mergeProjectConfig(existing: Record<string, unknown>, opts: {
136
- harness: string | string[];
136
+ /** Canonical harness names this repo targets, in the order to declare them. */
137
+ harnesses: readonly string[];
137
138
  strict: boolean;
138
139
  reportOnly?: boolean;
139
140
  /** Whether the LINT pillar is on (default true). The rule gate is a lint-layer
@@ -148,8 +148,13 @@ exports.NUDGE_RULES = [
148
148
  function mergeProjectConfig(existing, opts) {
149
149
  const config = { ...existing };
150
150
  let changed = false;
151
- if (config.harness === undefined) {
152
- config.harness = opts.harness;
151
+ // The NESTED key (#240). `init` writes the declaration with no roots — a repo
152
+ // whose surfaces sit where its harness reads them needs none, and a root is a
153
+ // fact only the owner knows. Writing the flat `harness`/`surfaceRoots` pair
154
+ // here would emit a config the loader now REFUSES, which is why this one line
155
+ // moved with the shape even though the rest of `init` did not.
156
+ if (config.harnesses === undefined) {
157
+ config.harnesses = Object.fromEntries(opts.harnesses.map((h) => [h, {}]));
153
158
  changed = true;
154
159
  }
155
160
  // The rule gate belongs to the LINT layer — a test-only setup records the
@@ -0,0 +1,12 @@
1
+ import { type DiscoveredSurface } from "./core/surface-discovery.js";
2
+ import { type ExcludeSet } from "./exclude.js";
3
+ /**
4
+ * Repo-relative paths of every file inside the bounded root set's surface dirs.
5
+ *
6
+ * Exported for the test that proves the bound holds — a caller that wants
7
+ * surfaces wants {@link discoverSurfacesOnDisk}.
8
+ */
9
+ export declare function boundedSurfacePaths(root: string, excludes?: ExcludeSet): readonly string[];
10
+ /** Every surface the bounded walk finds on disk under `root`, by shape. */
11
+ export declare function discoverSurfacesOnDisk(root: string, excludes?: ExcludeSet): readonly DiscoveredSurface[];
12
+ //# sourceMappingURL=surface-discovery-fs.d.ts.map
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.boundedSurfacePaths = boundedSurfacePaths;
4
+ exports.discoverSurfacesOnDisk = discoverSurfacesOnDisk;
5
+ /**
6
+ * The DISK half of surface discovery: enumerate the repo-relative paths inside
7
+ * the bounded root set, so the pure classifier in `src/core/surface-discovery.ts`
8
+ * can say which of them are surfaces.
9
+ *
10
+ * Paths only — never contents. Discovery answers "is there a surface here that
11
+ * nobody reads", which is a question about NAMES; reading a file nobody claims
12
+ * would be doing the very work the finding says is not being done.
13
+ *
14
+ * 🔴 THE WALK IS BOUNDED BY CONSTRUCTION, NOT BY A DEPTH COUNTER. One `readdir`
15
+ * of the repo root, one per DOT-DIRECTORY found there, and then descent ONLY
16
+ * into a directory named by `SURFACE_SHAPES` (`skills`/`agents`/`commands`).
17
+ * `src/`, `packages/`, `node_modules/` are never entered — there is no traversal
18
+ * that could reach them, so there is no budget to tune and no flag to get wrong.
19
+ * The module header of `core/surface-discovery.ts` holds the reason the bound is
20
+ * this shape and why an adapter may not widen it.
21
+ *
22
+ * `exclude` is applied PER ENTRY, not only at the entry point, for the reason
23
+ * b751471 measured: a vendored tree is normally excluded at its own root
24
+ * (`skills/vendored`), which is a DESCENDANT of a surface dir, so an
25
+ * entry-point-only check lets every one of its files through. Without this, a
26
+ * discovery pass that walks dot-directories would grade copied-in corpora as the
27
+ * repo's own work and the repo's only stated remedy would not reach it.
28
+ *
29
+ * Symlinks go through the ONE policy (`src/fs-walk.ts`): `walkableRoot` at each
30
+ * surface dir the layout-blind walk opens, `entryOf` for every entry below it.
31
+ */
32
+ const node_fs_1 = require("node:fs");
33
+ const node_path_1 = require("node:path");
34
+ const surface_discovery_js_1 = require("./core/surface-discovery.js");
35
+ const exclude_js_1 = require("./exclude.js");
36
+ const fs_walk_js_1 = require("./fs-walk.js");
37
+ /** The shape directory names, as a set — the only dirs the walk descends into. */
38
+ const SHAPE_DIRS = new Set(surface_discovery_js_1.SURFACE_SHAPES.map((s) => s.dir));
39
+ /** Directory entry names under `dir`, or `[]` when it cannot be read. */
40
+ function namesIn(dir) {
41
+ try {
42
+ return (0, node_fs_1.readdirSync)(dir);
43
+ }
44
+ catch {
45
+ return []; // unreadable: the same silence `entryOf` gives, never a throw
46
+ }
47
+ }
48
+ /** Every file path (repo-relative, POSIX) under an opened surface directory. */
49
+ function filesUnder(root, rel, excluded, out) {
50
+ const abs = (0, node_path_1.join)(root, rel);
51
+ for (const name of namesIn(abs)) {
52
+ const childRel = `${rel}/${name}`;
53
+ const childAbs = (0, node_path_1.join)(root, childRel);
54
+ if (excluded(childAbs))
55
+ continue;
56
+ const entry = (0, fs_walk_js_1.entryOf)(childAbs);
57
+ if (entry.kind === "dir")
58
+ filesUnder(root, childRel, excluded, out);
59
+ // `childRel` is built with `/` from `readdir` names (which never contain a
60
+ // separator), so it is already the POSIX form the classifier matches on.
61
+ else if (entry.kind === "file")
62
+ out.push(childRel);
63
+ }
64
+ }
65
+ /**
66
+ * Repo-relative paths of every file inside the bounded root set's surface dirs.
67
+ *
68
+ * Exported for the test that proves the bound holds — a caller that wants
69
+ * surfaces wants {@link discoverSurfacesOnDisk}.
70
+ */
71
+ function boundedSurfacePaths(root, excludes) {
72
+ const excluded = (0, exclude_js_1.excludedBy)(excludes);
73
+ const out = [];
74
+ // The repo root is a discovery root, and so is each of its dot-directories.
75
+ const roots = ["", ...namesIn(root).filter((n) => (0, surface_discovery_js_1.isDiscoveryRoot)(n))];
76
+ for (const base of roots.filter((b) => b === "" || !excluded((0, node_path_1.join)(root, b)))) {
77
+ for (const rel of surfaceDirsIn(root, base)) {
78
+ if (openableSurfaceDir(root, rel, excluded))
79
+ filesUnder(root, rel, excluded, out);
80
+ }
81
+ }
82
+ return out;
83
+ }
84
+ /** Repo-relative surface dirs directly under one discovery root. */
85
+ function surfaceDirsIn(root, base) {
86
+ const baseAbs = base === "" ? root : (0, node_path_1.join)(root, base);
87
+ return namesIn(baseAbs)
88
+ .filter((n) => SHAPE_DIRS.has(n))
89
+ .map((n) => (base === "" ? n : `${base}/${n}`));
90
+ }
91
+ /** May the walk open `<root>/<rel>` as a surface directory? */
92
+ function openableSurfaceDir(root, rel, excluded) {
93
+ const abs = (0, node_path_1.join)(root, rel);
94
+ if (excluded(abs))
95
+ return false;
96
+ // A FILE named `skills` is not a surface dir. A SYMLINKED one is (`entryOf`
97
+ // says "skip" for it, deliberately — that rule is for entries the walk finds
98
+ // INSIDE), so the entry point gets `walkableRoot`, which follows it unless
99
+ // the target contains the scanned root. See src/fs-walk.ts.
100
+ if ((0, fs_walk_js_1.entryOf)(abs).kind === "file")
101
+ return false;
102
+ return (0, fs_walk_js_1.walkableRoot)(abs, root);
103
+ }
104
+ /** Every surface the bounded walk finds on disk under `root`, by shape. */
105
+ function discoverSurfacesOnDisk(root, excludes) {
106
+ return (0, surface_discovery_js_1.discoverSurfaces)(boundedSurfacePaths(root, excludes));
107
+ }
108
+ //# sourceMappingURL=surface-discovery-fs.js.map