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.
- package/README.md +1 -1
- package/dist/adapter-registry.d.ts +39 -0
- package/dist/adapter-registry.js +45 -0
- package/dist/adapter.d.ts +8 -0
- package/dist/adapter.js +10 -1
- package/dist/adapters/claude-code/adapter.js +9 -2
- package/dist/adapters/claude-code/layout.d.ts +5 -0
- package/dist/adapters/claude-code/plugin-loader.d.ts +10 -1
- package/dist/adapters/claude-code/plugin-loader.js +10 -1
- package/dist/adapters/codex/adapter.js +7 -2
- package/dist/adapters/codex/layout.d.ts +51 -5
- package/dist/adapters/codex/layout.js +13 -4
- package/dist/adapters/opencode/adapter.js +6 -0
- package/dist/audit-report.template.html +1 -1
- package/dist/audit-score.d.ts +7 -0
- package/dist/audit-score.js +49 -2
- package/dist/cli-main.js +162 -39
- package/dist/core/adapter.d.ts +45 -1
- package/dist/core/compile.js +11 -1
- package/dist/core/config-schema.d.ts +244 -0
- package/dist/core/config-schema.js +452 -0
- package/dist/core/hook-program.d.ts +43 -0
- package/dist/core/hook-program.js +32 -0
- package/dist/core/refs.js +10 -1
- package/dist/core/surface-discovery.d.ts +270 -0
- package/dist/core/surface-discovery.js +425 -0
- package/dist/core/surface-scopes.d.ts +38 -1
- package/dist/core/surface-scopes.js +73 -1
- package/dist/core/symbols.d.ts +24 -2
- package/dist/core/symbols.js +66 -18
- package/dist/core/types.d.ts +36 -107
- package/dist/core/validate.d.ts +46 -18
- package/dist/core/validate.js +98 -172
- package/dist/exclude.d.ts +20 -0
- package/dist/exclude.js +11 -1
- package/dist/harness-test.js +3 -3
- package/dist/hook-install.d.ts +53 -0
- package/dist/hook-install.js +60 -0
- package/dist/hook-runtime.d.ts +2 -2
- package/dist/hook-runtime.js +82 -63
- package/dist/layout-registry.d.ts +14 -0
- package/dist/layout-registry.js +40 -0
- package/dist/load-hook.d.ts +1 -1
- package/dist/load-hook.js +2 -2
- package/dist/plugin-loader.d.ts +49 -1
- package/dist/plugin-loader.js +120 -14
- package/dist/run-hook.js +17 -1
- package/dist/scan-core.d.ts +19 -0
- package/dist/scan-core.js +30 -0
- package/dist/scan-files.js +15 -5
- package/dist/scan.d.ts +63 -0
- package/dist/scan.js +68 -12
- package/dist/score-core.js +8 -0
- package/dist/setup-plan.d.ts +2 -1
- package/dist/setup-plan.js +7 -2
- package/dist/surface-discovery-fs.d.ts +12 -0
- package/dist/surface-discovery-fs.js +108 -0
- package/dist/vigilesrc.schema.json +1689 -0
- 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
|
-
|
|
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
|
-
|
|
143
|
-
|
|
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
|
|
183
|
+
const instructionFile = instructionHarness.layout.instructionFile;
|
|
184
|
+
const instructions = loaded.files[instructionFile] !== undefined
|
|
161
185
|
? {
|
|
162
|
-
file:
|
|
163
|
-
hasSpec: (0, node_fs_1.existsSync)((0, node_path_1.join)((0, node_path_1.resolve)(dir), `${
|
|
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)({
|
|
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
|
-
|
|
261
|
-
|
|
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
|
|
671
|
-
?
|
|
672
|
-
:
|
|
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
|
package/dist/score-core.js
CHANGED
|
@@ -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,
|
package/dist/setup-plan.d.ts
CHANGED
|
@@ -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
|
|
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
|
package/dist/setup-plan.js
CHANGED
|
@@ -148,8 +148,13 @@ exports.NUDGE_RULES = [
|
|
|
148
148
|
function mergeProjectConfig(existing, opts) {
|
|
149
149
|
const config = { ...existing };
|
|
150
150
|
let changed = false;
|
|
151
|
-
|
|
152
|
-
|
|
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
|