vigiles 29.0.0 → 30.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/dist/adapter-conformance.d.ts +1 -1
- package/dist/adapter-conformance.js +106 -25
- package/dist/adapter-registry.d.ts +61 -14
- package/dist/adapter-registry.js +78 -10
- package/dist/adapter.d.ts +23 -2
- package/dist/adapter.js +13 -1
- package/dist/adapters/claude-code/adapter.d.ts +32 -2
- package/dist/adapters/claude-code/adapter.js +44 -23
- package/dist/adapters/claude-code/dialect.js +87 -21
- package/dist/adapters/claude-code/hook-protocol.js +16 -0
- package/dist/adapters/claude-code/instruction-chain.d.ts +25 -0
- package/dist/adapters/claude-code/instruction-chain.js +626 -0
- package/dist/adapters/claude-code/layout.d.ts +2 -2
- package/dist/adapters/claude-code/layout.js +42 -8
- package/dist/adapters/claude-code/model-access.d.ts +41 -0
- package/dist/adapters/claude-code/model-access.js +46 -0
- package/dist/adapters/claude-code/skill-reachability.d.ts +125 -0
- package/dist/adapters/claude-code/skill-reachability.js +111 -0
- package/dist/adapters/codex/adapter.d.ts +39 -2
- package/dist/adapters/codex/adapter.js +29 -29
- package/dist/adapters/codex/dialect.js +11 -6
- package/dist/adapters/codex/eval.d.ts +10 -0
- package/dist/adapters/codex/eval.js +48 -1
- package/dist/adapters/codex/hook-protocol.d.ts +2 -1
- package/dist/adapters/codex/hook-protocol.js +10 -0
- package/dist/adapters/codex/instruction-chain.d.ts +40 -0
- package/dist/adapters/codex/instruction-chain.js +105 -0
- package/dist/adapters/codex/layout.d.ts +1 -1
- package/dist/adapters/codex/layout.js +41 -14
- package/dist/adapters/opencode/adapter.d.ts +33 -2
- package/dist/adapters/opencode/adapter.js +36 -36
- package/dist/adapters/opencode/dialect.js +2 -2
- package/dist/adapters/opencode/instruction-chain.d.ts +37 -0
- package/dist/adapters/opencode/instruction-chain.js +70 -0
- package/dist/adapters/opencode/layout.d.ts +19 -0
- package/dist/adapters/opencode/layout.js +34 -15
- package/dist/adoptability.d.ts +31 -1
- package/dist/adoptability.js +57 -0
- package/dist/cli-main.js +180 -102
- package/dist/core/adapter.d.ts +213 -61
- package/dist/core/compile.d.ts +2 -2
- package/dist/core/compile.js +57 -38
- package/dist/core/compose.d.ts +5 -3
- package/dist/core/compose.js +5 -3
- package/dist/core/config-schema.d.ts +14 -2
- package/dist/core/config-schema.js +24 -3
- package/dist/core/dialect.d.ts +54 -12
- package/dist/core/dialect.js +56 -0
- package/dist/core/eval-driver.d.ts +194 -0
- package/dist/core/eval-driver.js +3 -0
- package/dist/core/frontmatter-read.d.ts +10 -0
- package/dist/core/frontmatter-read.js +30 -3
- package/dist/core/hook-program.d.ts +27 -2
- package/dist/core/hook-program.js +29 -24
- package/dist/core/hook-protocol.d.ts +54 -0
- package/dist/core/install-reader.d.ts +18 -0
- package/dist/core/install-reader.js +88 -0
- package/dist/core/instruction-chain.d.ts +444 -0
- package/dist/core/instruction-chain.js +292 -0
- package/dist/core/instruction-weight.d.ts +96 -14
- package/dist/core/instruction-weight.js +65 -30
- package/dist/core/layout.d.ts +220 -33
- package/dist/core/layout.js +115 -1
- package/dist/core/lethal-trifecta.d.ts +12 -7
- package/dist/core/lethal-trifecta.js +13 -8
- package/dist/core/live-driver.d.ts +137 -0
- package/dist/core/live-driver.js +14 -0
- package/dist/core/markdown.d.ts +23 -0
- package/dist/core/markdown.js +77 -28
- package/dist/core/orphans.js +9 -7
- package/dist/core/settings-codec.d.ts +17 -0
- package/dist/core/settings-codec.js +56 -0
- package/dist/core/surface-discovery.d.ts +2 -2
- package/dist/core/surface-discovery.js +24 -8
- package/dist/core/surface-scopes.d.ts +26 -6
- package/dist/core/surface-scopes.js +52 -11
- package/dist/core/validate.js +16 -3
- package/dist/eval.d.ts +16 -108
- package/dist/eval.js +34 -1
- package/dist/harness-test.d.ts +3 -63
- package/dist/hook-install.d.ts +12 -1
- package/dist/hook-install.js +12 -1
- package/dist/plugin-loader.d.ts +1 -1
- package/dist/plugin-loader.js +43 -36
- package/dist/scan-behavioral.d.ts +34 -25
- package/dist/scan-behavioral.js +122 -58
- package/dist/scan-core.js +37 -18
- package/dist/scan-files.d.ts +1 -1
- package/dist/scan-files.js +53 -33
- package/dist/scan-trigger-suggest.d.ts +0 -21
- package/dist/scan-trigger-suggest.js +0 -23
- package/dist/scan.d.ts +4 -4
- package/dist/scan.js +120 -73
- package/dist/skill-harness.d.ts +21 -5
- package/dist/skill-harness.js +29 -11
- package/dist/surface-discovery-fs.d.ts +2 -0
- package/dist/surface-discovery-fs.js +108 -6
- package/dist/test-coverage-files.js +24 -17
- package/dist/test-coverage.d.ts +9 -3
- package/dist/test-coverage.js +32 -17
- package/dist/verify-plugin-guards.js +1 -1
- package/package.json +1 -1
- package/dist/skill-reachability.d.ts +0 -68
- package/dist/skill-reachability.js +0 -205
- /package/dist/{dialect-drift.d.ts → adapters/claude-code/dialect-drift.d.ts} +0 -0
- /package/dist/{dialect-drift.js → adapters/claude-code/dialect-drift.js} +0 -0
|
@@ -11,27 +11,6 @@
|
|
|
11
11
|
* not through the report verb. The IO (prompt / run / remember) lives in the CLI;
|
|
12
12
|
* this is the pure decision + helpers.
|
|
13
13
|
*/
|
|
14
|
-
/** Only the env vars that signal a reachable model (parse, don't validate). */
|
|
15
|
-
export interface ModelEnv {
|
|
16
|
-
readonly ANTHROPIC_API_KEY?: string;
|
|
17
|
-
readonly CLAUDECODE?: string;
|
|
18
|
-
readonly CLAUDE_CODE_ENTRYPOINT?: string;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Is a real model reachable for the trigger tier? Either a metered API key
|
|
22
|
-
* (`ANTHROPIC_API_KEY`), OR an authenticated Claude Code session (`CLAUDECODE=1`
|
|
23
|
-
* / `CLAUDE_CODE_ENTRYPOINT`, web/desktop/CLI) — the latter drives the `claude`
|
|
24
|
-
* CLI on the user's subscription, no key needed and $0 metered. A tiny env-only
|
|
25
|
-
* predicate (not a live probe), so it never spends a token just to decide.
|
|
26
|
-
*/
|
|
27
|
-
export declare function hasModelAccess(env: ModelEnv): boolean;
|
|
28
|
-
/**
|
|
29
|
-
* Is the reachable model METERED (a paid API key) rather than a subscription?
|
|
30
|
-
* Only affects the consent DISCLOSURE wording (a metered key bills per token; a
|
|
31
|
-
* subscription is $0 metered) — the run/skip decision itself is consent-driven,
|
|
32
|
-
* not metered-driven.
|
|
33
|
-
*/
|
|
34
|
-
export declare function isMeteredAccess(env: ModelEnv): boolean;
|
|
35
14
|
/** Why the executing checks were skipped (drives the "not run" nudge). */
|
|
36
15
|
export type ExecuteSkipReason = "nothing" | "headless" | "remembered-no";
|
|
37
16
|
/**
|
|
@@ -13,32 +13,9 @@
|
|
|
13
13
|
* this is the pure decision + helpers.
|
|
14
14
|
*/
|
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
-
exports.hasModelAccess = hasModelAccess;
|
|
17
|
-
exports.isMeteredAccess = isMeteredAccess;
|
|
18
16
|
exports.decideExecute = decideExecute;
|
|
19
17
|
exports.formatExecuteSkip = formatExecuteSkip;
|
|
20
18
|
exports.scaffoldTriggerPrompts = scaffoldTriggerPrompts;
|
|
21
|
-
/**
|
|
22
|
-
* Is a real model reachable for the trigger tier? Either a metered API key
|
|
23
|
-
* (`ANTHROPIC_API_KEY`), OR an authenticated Claude Code session (`CLAUDECODE=1`
|
|
24
|
-
* / `CLAUDE_CODE_ENTRYPOINT`, web/desktop/CLI) — the latter drives the `claude`
|
|
25
|
-
* CLI on the user's subscription, no key needed and $0 metered. A tiny env-only
|
|
26
|
-
* predicate (not a live probe), so it never spends a token just to decide.
|
|
27
|
-
*/
|
|
28
|
-
function hasModelAccess(env) {
|
|
29
|
-
return Boolean(env.ANTHROPIC_API_KEY ||
|
|
30
|
-
env.CLAUDECODE === "1" ||
|
|
31
|
-
env.CLAUDE_CODE_ENTRYPOINT);
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Is the reachable model METERED (a paid API key) rather than a subscription?
|
|
35
|
-
* Only affects the consent DISCLOSURE wording (a metered key bills per token; a
|
|
36
|
-
* subscription is $0 metered) — the run/skip decision itself is consent-driven,
|
|
37
|
-
* not metered-driven.
|
|
38
|
-
*/
|
|
39
|
-
function isMeteredAccess(env) {
|
|
40
|
-
return Boolean(env.ANTHROPIC_API_KEY);
|
|
41
|
-
}
|
|
42
19
|
/**
|
|
43
20
|
* Decide what `audit` does with the executing checks. Total + pure; the first
|
|
44
21
|
* matching rule wins. There is NO execution flag — `audit` is a local report, so
|
package/dist/scan.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* need to RUN the plugin (observed egress under the sandbox, real trigger-rate)
|
|
12
12
|
* stack on top later; this core stays pure so it runs anywhere in CI for free.
|
|
13
13
|
*/
|
|
14
|
-
import type
|
|
14
|
+
import { type PluginLayout } from "./core/layout.js";
|
|
15
15
|
import type { ExcludeSet } from "./exclude.js";
|
|
16
16
|
import type { HarnessDialect } from "./core/dialect.js";
|
|
17
17
|
import type { ToolIssue } from "./core/tool-contract.js";
|
|
@@ -412,7 +412,7 @@ export interface ScanHarness {
|
|
|
412
412
|
readonly roots: readonly string[];
|
|
413
413
|
}
|
|
414
414
|
/** Scan a plugin/repo directory and report its surfaces + structural issues. */
|
|
415
|
-
export declare function scanPlugin(dir: string, layout
|
|
415
|
+
export declare function scanPlugin(dir: string, layout: PluginLayout, dialect: HarnessDialect, opts?: {
|
|
416
416
|
sharedDirs?: readonly string[];
|
|
417
417
|
sharedDirsRoot?: string;
|
|
418
418
|
/**
|
|
@@ -492,7 +492,7 @@ export interface MarketplaceInfo {
|
|
|
492
492
|
* marketplace. The source of truth behind {@link expandMarketplace} and the
|
|
493
493
|
* curated-marketplace report in `vigiles audit`.
|
|
494
494
|
*/
|
|
495
|
-
export declare function inspectMarketplace(dir: string, layout
|
|
495
|
+
export declare function inspectMarketplace(dir: string, layout: PluginLayout): MarketplaceInfo | null;
|
|
496
496
|
/**
|
|
497
497
|
* If `dir` is a plugin MARKETPLACE (a `marketplace.json` beside the layout's
|
|
498
498
|
* plugin manifest, e.g. `.claude-plugin/marketplace.json`), expand it into the
|
|
@@ -501,6 +501,6 @@ export declare function inspectMarketplace(dir: string, layout?: PluginLayout):
|
|
|
501
501
|
* on disk). Used by `vigiles audit` to rank a whole marketplace — wshobson/agents
|
|
502
502
|
* alone ships 80+ plugins under one `marketplace.json`. See {@link inspectMarketplace}.
|
|
503
503
|
*/
|
|
504
|
-
export declare function expandMarketplace(dir: string, layout
|
|
504
|
+
export declare function expandMarketplace(dir: string, layout: PluginLayout): string[] | null;
|
|
505
505
|
export declare function formatScanReport(r: ScanReport): string;
|
|
506
506
|
//# sourceMappingURL=scan.d.ts.map
|
package/dist/scan.js
CHANGED
|
@@ -40,11 +40,20 @@ const node_path_1 = require("node:path");
|
|
|
40
40
|
// Claude Code default), and only the generic one takes the `ExcludeSet` that
|
|
41
41
|
// makes `.vigilesrc.json#exclude` reach surface discovery.
|
|
42
42
|
const plugin_loader_js_1 = require("./plugin-loader.js");
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
// 🔴 THIS MODULE NO LONGER IMPORTS AN ADAPTER, and that is the point of the
|
|
44
|
+
// change rather than tidiness. It is listed as a harness-agnostic detector and
|
|
45
|
+
// it used to import `claudeCodeLayout` + `claudeCodeDialect` to DEFAULT five
|
|
46
|
+
// parameters with. Neither of the other two fences could see it —
|
|
47
|
+
// `boundaries/dependencies` deliberately leaves this file unclassified, and the
|
|
48
|
+
// CC-literal rule's `\.claude` needs the dot while this path spells
|
|
49
|
+
// `/claude-code/` — so it took a third rule to find, and two per-line disables
|
|
50
|
+
// to live with. The default belongs to the CALLER: the CLI already resolves an
|
|
51
|
+
// adapter before every one of these calls, and passing it is what removes the
|
|
52
|
+
// import rather than hiding it.
|
|
45
53
|
const plugin_loader_js_2 = require("./plugin-loader.js");
|
|
46
54
|
const score_core_js_1 = require("./score-core.js");
|
|
47
55
|
const skill_refs_js_1 = require("./skill-refs.js");
|
|
56
|
+
const layout_js_1 = require("./core/layout.js");
|
|
48
57
|
const hook_events_js_1 = require("./core/hook-events.js");
|
|
49
58
|
const mcp_config_js_1 = require("./core/mcp-config.js");
|
|
50
59
|
const agent_plugins_js_1 = require("./core/agent-plugins.js");
|
|
@@ -145,8 +154,8 @@ function ownTestSignalOnDisk(dir) {
|
|
|
145
154
|
});
|
|
146
155
|
}
|
|
147
156
|
/** Scan a plugin/repo directory and report its surfaces + structural issues. */
|
|
148
|
-
function scanPlugin(dir, layout, dialect
|
|
149
|
-
const lay = layout
|
|
157
|
+
function scanPlugin(dir, layout, dialect, opts = {}) {
|
|
158
|
+
const lay = layout;
|
|
150
159
|
// The declared harnesses, or the single positional one — so every path below
|
|
151
160
|
// is the multi-harness path and a one-entry list is not a second code path.
|
|
152
161
|
const declared = opts.harnesses && opts.harnesses.length > 0
|
|
@@ -181,6 +190,12 @@ function scanPlugin(dir, layout, dialect = dialect_js_1.claudeCodeDialect, opts
|
|
|
181
190
|
const allHookEventIssues = (0, hook_events_js_1.verifyHookEvents)(eventNames, dialect);
|
|
182
191
|
const hookEventIssues = (0, hook_events_js_1.scoredIssues)(allHookEventIssues);
|
|
183
192
|
const instructionFile = instructionHarness.layout.instructionFile;
|
|
193
|
+
// The BOUNDED candidate set, read once and handed to the harness that owns
|
|
194
|
+
// the instruction file this repo has. It replaced `readAlwaysLoaded`, which
|
|
195
|
+
// expanded an ADAPTER's globs by walking the whole tree; the bound and the
|
|
196
|
+
// classification are now separate jobs held by separate modules, and only the
|
|
197
|
+
// second is the adapter's. See `core/instruction-chain.ts`.
|
|
198
|
+
const instructionFiles = (0, surface_discovery_fs_js_1.boundedInstructionFiles)((0, node_path_1.resolve)(dir), instructionHarness.layout, opts.excludes);
|
|
184
199
|
const instructions = loaded.files[instructionFile] !== undefined
|
|
185
200
|
? {
|
|
186
201
|
file: instructionFile,
|
|
@@ -195,7 +210,7 @@ function scanPlugin(dir, layout, dialect = dialect_js_1.claudeCodeDialect, opts
|
|
|
195
210
|
});
|
|
196
211
|
const skills = (0, scan_core_js_1.scanSkills)(loaded.files, cls, {
|
|
197
212
|
root: (0, node_path_1.resolve)(dir),
|
|
198
|
-
materializeRoot: lay
|
|
213
|
+
materializeRoot: (0, layout_js_1.materializePrefix)(lay),
|
|
199
214
|
dialect,
|
|
200
215
|
sources: loaded.sources,
|
|
201
216
|
sharedDirs: opts.sharedDirs,
|
|
@@ -269,10 +284,14 @@ function scanPlugin(dir, layout, dialect = dialect_js_1.claudeCodeDialect, opts
|
|
|
269
284
|
roots: h.roots,
|
|
270
285
|
}))),
|
|
271
286
|
pluginLayoutIssues: (0, plugin_dir_layout_js_1.pluginDirLayoutIssues)((0, node_path_1.resolve)(dir, (0, node_path_1.dirname)(lay.manifestPath)),
|
|
272
|
-
//
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
|
|
287
|
+
// 🔴 THE HOOK SCRIPTS DIR IS NAMED, NOT DERIVED FROM A FILE PATH. This
|
|
288
|
+
// used to be `lay.hooksConventionPath.split("/")[0]`, copied here and into
|
|
289
|
+
// the twin: it reads `hooks` from `hooks/hooks.json`, but `.codex` from
|
|
290
|
+
// `.codex/hooks.json` — a REGISTRATION directory, not a scripts one, so
|
|
291
|
+
// for Codex the detector was watching the wrong directory entirely. The
|
|
292
|
+
// layout names it (`hookScriptsDir`) and `executableSourceDirs` joins it
|
|
293
|
+
// to the surfaces.
|
|
294
|
+
(0, layout_js_1.executableSourceDirs)(lay), { existsSync: node_fs_1.existsSync, isDirectory: nodeIsDirectory }),
|
|
276
295
|
delegationTrifecta: (0, scan_core_js_1.collectDelegationTrifecta)(agents, dialect),
|
|
277
296
|
hookBlockFindings: (0, event_capability_js_1.blockIneffectiveEventsOf)(dialect).length > 0
|
|
278
297
|
? (0, hook_block_ineffective_js_1.hookBlockIssues)((0, scan_core_js_1.collectHookBlockEntries)(hookRegs, (0, node_path_1.resolve)(dir), lay.pluginRootToken, node_fs_1.existsSync), {
|
|
@@ -302,7 +321,7 @@ function scanPlugin(dir, layout, dialect = dialect_js_1.claudeCodeDialect, opts
|
|
|
302
321
|
// meaningful sum across two; reporting the one that owns the file that
|
|
303
322
|
// exists is the only reading that is true of something.
|
|
304
323
|
instructionWeight: instructionHarness.dialect.instructionBudget
|
|
305
|
-
? (0, instruction_weight_js_1.weighInstructions)(
|
|
324
|
+
? (0, instruction_weight_js_1.weighInstructions)(instructionHarness.layout.instructionChain(instructionFiles), instructionFiles, instructionHarness.dialect.instructionBudget)
|
|
306
325
|
: null,
|
|
307
326
|
untested: coverage.untested.length,
|
|
308
327
|
untestedHarness: coverage.harness.untested.length,
|
|
@@ -351,7 +370,7 @@ function formatMcpContractReport(errors) {
|
|
|
351
370
|
* marketplace. The source of truth behind {@link expandMarketplace} and the
|
|
352
371
|
* curated-marketplace report in `vigiles audit`.
|
|
353
372
|
*/
|
|
354
|
-
function inspectMarketplace(dir, layout
|
|
373
|
+
function inspectMarketplace(dir, layout) {
|
|
355
374
|
const mpPath = (0, node_path_1.join)(dir, (0, node_path_1.dirname)(layout.manifestPath), "marketplace.json");
|
|
356
375
|
if (!(0, node_fs_1.existsSync)(mpPath))
|
|
357
376
|
return null;
|
|
@@ -404,7 +423,7 @@ function inspectMarketplace(dir, layout = layout_js_1.claudeCodeLayout) {
|
|
|
404
423
|
* on disk). Used by `vigiles audit` to rank a whole marketplace — wshobson/agents
|
|
405
424
|
* alone ships 80+ plugins under one `marketplace.json`. See {@link inspectMarketplace}.
|
|
406
425
|
*/
|
|
407
|
-
function expandMarketplace(dir, layout
|
|
426
|
+
function expandMarketplace(dir, layout) {
|
|
408
427
|
const mp = inspectMarketplace(dir, layout);
|
|
409
428
|
return mp ? [...mp.onDisk] : null;
|
|
410
429
|
}
|
|
@@ -497,63 +516,6 @@ function agentLines(a) {
|
|
|
497
516
|
return lines;
|
|
498
517
|
}
|
|
499
518
|
/** Format a scan report as human-readable text. */
|
|
500
|
-
/**
|
|
501
|
-
* Read every unconditionally-loaded instruction file off disk.
|
|
502
|
-
*
|
|
503
|
-
* Separate from `loadPlugin` on purpose: that materializes the harness's
|
|
504
|
-
* SURFACES (skills, agents, hooks), while this reads what the harness loads
|
|
505
|
-
* before any surface is involved — including `.claude/rules/**`, which is
|
|
506
|
-
* exactly the directory a repo relocates into when it wants the root file to
|
|
507
|
-
* look smaller.
|
|
508
|
-
*/
|
|
509
|
-
function readAlwaysLoaded(dir, budget) {
|
|
510
|
-
const out = {};
|
|
511
|
-
const walk = (rel) => {
|
|
512
|
-
const abs = (0, node_path_1.join)(dir, rel);
|
|
513
|
-
if (!(0, node_fs_1.existsSync)(abs) || !(0, node_fs_1.statSync)(abs).isDirectory())
|
|
514
|
-
return;
|
|
515
|
-
for (const entry of (0, node_fs_1.readdirSync)(abs)) {
|
|
516
|
-
const child = `${rel}/${entry}`;
|
|
517
|
-
if ((0, node_fs_1.statSync)((0, node_path_1.join)(dir, child)).isDirectory())
|
|
518
|
-
walk(child);
|
|
519
|
-
else
|
|
520
|
-
out[child] = (0, node_fs_1.readFileSync)((0, node_path_1.join)(dir, child), "utf-8");
|
|
521
|
-
}
|
|
522
|
-
};
|
|
523
|
-
// `**/NAME` — Codex reads nested AGENTS.md root-to-leaf and they all pay into
|
|
524
|
-
// the SAME budget, so leaving them out under-reports in the one direction that
|
|
525
|
-
// matters: the harness truncates silently, and an under-report reads as "you
|
|
526
|
-
// are fine". Skipped dirs are the ones that are never the user's instructions
|
|
527
|
-
// and would dominate the walk.
|
|
528
|
-
const SKIP = new Set(["node_modules", ".git", "dist", "build", "vendor"]);
|
|
529
|
-
const findNested = (name, rel = "") => {
|
|
530
|
-
const abs = rel === "" ? dir : (0, node_path_1.join)(dir, rel);
|
|
531
|
-
if (!(0, node_fs_1.existsSync)(abs))
|
|
532
|
-
return;
|
|
533
|
-
for (const entry of (0, node_fs_1.readdirSync)(abs)) {
|
|
534
|
-
if (SKIP.has(entry) || entry.startsWith("."))
|
|
535
|
-
continue;
|
|
536
|
-
const child = rel === "" ? entry : `${rel}/${entry}`;
|
|
537
|
-
const childAbs = (0, node_path_1.join)(dir, child);
|
|
538
|
-
if ((0, node_fs_1.statSync)(childAbs).isDirectory())
|
|
539
|
-
findNested(name, child);
|
|
540
|
-
else if (entry === name && out[child] === undefined)
|
|
541
|
-
out[child] = (0, node_fs_1.readFileSync)(childAbs, "utf-8");
|
|
542
|
-
}
|
|
543
|
-
};
|
|
544
|
-
for (const glob of budget.alwaysLoaded) {
|
|
545
|
-
if (!glob.includes("*")) {
|
|
546
|
-
const abs = (0, node_path_1.join)(dir, glob);
|
|
547
|
-
if ((0, node_fs_1.existsSync)(abs) && (0, node_fs_1.statSync)(abs).isFile())
|
|
548
|
-
out[glob] = (0, node_fs_1.readFileSync)(abs, "utf-8");
|
|
549
|
-
}
|
|
550
|
-
else if (glob.endsWith("/**"))
|
|
551
|
-
walk(glob.slice(0, -3));
|
|
552
|
-
else if (glob.startsWith("**/"))
|
|
553
|
-
findNested(glob.slice(3));
|
|
554
|
-
}
|
|
555
|
-
return out;
|
|
556
|
-
}
|
|
557
519
|
/**
|
|
558
520
|
* The weight report. States the SUM first and the per-file breakdown second,
|
|
559
521
|
* because the sum is the number a reader can act on and the breakdown is only
|
|
@@ -565,20 +527,105 @@ function readAlwaysLoaded(dir, budget) {
|
|
|
565
527
|
*/
|
|
566
528
|
function instructionWeightLines(w) {
|
|
567
529
|
const g = (n) => String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
568
|
-
|
|
530
|
+
/** One breakdown row, with its provenance when it did not get in by location. */
|
|
531
|
+
const fileLine = (f) => ` ${g(f.size).padStart(9)} ${f.path}` +
|
|
532
|
+
(f.via === undefined ? "" : ` via ${f.via.token} in ${f.via.from}`);
|
|
533
|
+
const head = `Always-loaded instructions: ${g(w.committedTotal)} ${w.unit} (budget ${g(w.limit)})`;
|
|
534
|
+
// 🔴 TWO NUMBERS, AND ONLY THE FIRST IS JUDGED. The committed total is what a
|
|
535
|
+
// teammate or CI loads from this commit; the effective total is what THIS
|
|
536
|
+
// working copy loads, per-machine files included. Scoring the second would
|
|
537
|
+
// make a published grade depend on a gitignored file and put the CLI and the
|
|
538
|
+
// browser engine — which reads a GitHub tree and can never see one —
|
|
539
|
+
// permanently out of agreement. Printing only the first would hide bytes the
|
|
540
|
+
// author is really paying for. See `core/instruction-chain.ts`.
|
|
541
|
+
const local = w.effectiveTotal - w.committedTotal;
|
|
542
|
+
// 🔴 A REDIRECT IS A FINDING, NOT A SIZE. A `CLAUDE.md` holding nothing but
|
|
543
|
+
// `@AGENTS.md` is fourteen bytes, and the repository it describes loads tens
|
|
544
|
+
// of kilobytes; printing the fourteen would be a confident wrong answer. The
|
|
545
|
+
// line says what the file IS and what it points at, above the number.
|
|
546
|
+
const redirectLines = w.redirects.map((r) => ` ↪ ${r.path} is a REDIRECT — its entire content is import(s): ${r.to.join(", ")}`);
|
|
547
|
+
// 🔴 AN IMPORTED FILE IS NAMED WHETHER OR NOT WE ARE OVER BUDGET. The
|
|
548
|
+
// breakdown below only prints when over, and an import is exactly the entry a
|
|
549
|
+
// reader cannot account for — `AGENTS.md` in a Claude Code weight reads as a
|
|
550
|
+
// bug until the line says which file asked for it.
|
|
551
|
+
const importedLines = w.overBy === null
|
|
552
|
+
? w.files.filter((f) => f.via !== undefined).map(fileLine)
|
|
553
|
+
: [];
|
|
554
|
+
// 🔴 THE DELTA HAS A SIGN NOW, AND SUPPRESSING THE NEGATIVE ONE WAS THE WORST
|
|
555
|
+
// OF THE THREE OPTIONS. `local > 0` was written when a per-machine file could
|
|
556
|
+
// only ADD bytes; the supersede rule makes it subtract, and the model already
|
|
557
|
+
// reports that (`supersededLocallyBy`, `effectiveTotal` below `committedTotal`
|
|
558
|
+
// — see `core/instruction-weight.ts`). The printer was the last reader still
|
|
559
|
+
// assuming one direction, so the header announced the larger COMMITTED number
|
|
560
|
+
// as what is always loaded and nothing said this working copy loads a
|
|
561
|
+
// different, smaller chain. That is a confident over-report — the same
|
|
562
|
+
// undecomposable total the breakdown exists to prevent, pointing the other
|
|
563
|
+
// way. Both signs print; only exactly zero stays silent, because there is
|
|
564
|
+
// nothing to decompose.
|
|
565
|
+
const removedHere = w.files.filter((f) => f.notLoadedHere !== undefined);
|
|
566
|
+
const deltaLine = () => {
|
|
567
|
+
const here = `${g(w.effectiveTotal)} in this working copy, not scored`;
|
|
568
|
+
if (local > 0) {
|
|
569
|
+
return ` + ${g(local)} ${w.unit} from per-machine file(s) — ${here}`;
|
|
570
|
+
}
|
|
571
|
+
// NAMED, not just signed. A reader meeting "−93" has to be told which
|
|
572
|
+
// committed file stopped being loaded and what silenced it, or the number
|
|
573
|
+
// is an accusation with no defendant.
|
|
574
|
+
// The VERB comes off the entry, because the two doors to this state read
|
|
575
|
+
// very differently to someone deciding what to do about it: a superseding
|
|
576
|
+
// file is one they wrote, an exclusion is a pattern they set.
|
|
577
|
+
const by = removedHere
|
|
578
|
+
.map((f) => `${f.path} (${f.notLoadedHere?.why === "excluded" ? "excluded" : "silenced"} by ${String(f.notLoadedHere?.by)})`)
|
|
579
|
+
.join(", ");
|
|
580
|
+
return (` − ${g(-local)} ${w.unit}: a per-machine file REMOVES committed instruction(s) — ${here}` +
|
|
581
|
+
(by === "" ? "" : `\n not loaded here: ${by}`));
|
|
582
|
+
};
|
|
583
|
+
const tail = [
|
|
584
|
+
...(local === 0 ? [] : [deltaLine()]),
|
|
585
|
+
// Named rather than silently dropped: a number that omits a file it knows
|
|
586
|
+
// about is the under-report this whole report exists to prevent.
|
|
587
|
+
...(w.unreadImports.length > 0
|
|
588
|
+
? [
|
|
589
|
+
` + ${String(w.unreadImports.length)} named import(s) not read: ${w.unreadImports.join(", ")}`,
|
|
590
|
+
]
|
|
591
|
+
: []),
|
|
592
|
+
...(w.unweighedPatterns.length > 0
|
|
593
|
+
? [
|
|
594
|
+
` + ${String(w.unweighedPatterns.length)} pattern(s) not weighed: ${w.unweighedPatterns.join(", ")}`,
|
|
595
|
+
]
|
|
596
|
+
: []),
|
|
597
|
+
// 🔴 THE NUMBER IS A FLOOR, AND SAYING SO IS THE WHOLE FIX. Both vendors
|
|
598
|
+
// load instruction files from OUTSIDE the repository ALONGSIDE the ones
|
|
599
|
+
// counted here — a home-directory file and an organization's managed one.
|
|
600
|
+
// The Claude Code page is explicit that those "don't count, and keep
|
|
601
|
+
// loading alongside `AGENTS.md`": they are ADDED to this sum in a real
|
|
602
|
+
// session, they are never subtracted from it.
|
|
603
|
+
//
|
|
604
|
+
// READING THEM WOULD BE THE WRONG FIX, not a better one. A grade that
|
|
605
|
+
// reached into `~` would depend on whose machine ran it, and the browser
|
|
606
|
+
// twin — which reads a GitHub tree — could never reproduce it; that is the
|
|
607
|
+
// same argument `scope: "local"` rests on, one directory further out. So
|
|
608
|
+
// the fix is this line: a total that silently omits files it KNOWS exist is
|
|
609
|
+
// the undecomposable number this whole report exists to prevent, and one
|
|
610
|
+
// line costs nothing and cannot be wrong. Unconditional, because the
|
|
611
|
+
// omission does not depend on anything in the repository.
|
|
612
|
+
` ⌊ a FLOOR: a home-directory or organization-managed instruction file loads on top of this and is outside a repository audit`,
|
|
613
|
+
];
|
|
569
614
|
if (w.overBy === null)
|
|
570
|
-
return [head];
|
|
571
|
-
const factor = (w.
|
|
615
|
+
return [head, ...redirectLines, ...importedLines, ...tail];
|
|
616
|
+
const factor = (w.committedTotal / w.limit).toFixed(1);
|
|
572
617
|
const consequence = w.onExceed === "truncates"
|
|
573
618
|
? "past the budget is SILENTLY TRUNCATED — those rules never reach the model"
|
|
574
619
|
: "the harness warns; the rules still reach the model, you pay for them every request";
|
|
575
620
|
return [
|
|
576
621
|
`${head} — ${factor}x OVER by ${g(w.overBy)} ${w.unit}`,
|
|
577
622
|
` ${consequence}`,
|
|
578
|
-
...
|
|
623
|
+
...redirectLines,
|
|
624
|
+
...w.files.slice(0, 5).map(fileLine),
|
|
579
625
|
...(w.files.length > 5
|
|
580
626
|
? [` …and ${String(w.files.length - 5)} more`]
|
|
581
627
|
: []),
|
|
628
|
+
...tail,
|
|
582
629
|
];
|
|
583
630
|
}
|
|
584
631
|
function formatScanReport(r) {
|
package/dist/skill-harness.d.ts
CHANGED
|
@@ -14,12 +14,28 @@
|
|
|
14
14
|
* about another tool's parser tolerance.
|
|
15
15
|
*/
|
|
16
16
|
import type { SkillSpec } from "./core/spec.js";
|
|
17
|
-
/** The Claude-Code-only frontmatter keys a skill spec would emit. */
|
|
18
|
-
export declare function claudeOnlyFrontmatterKeys(spec: SkillSpec): string[];
|
|
19
17
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
18
|
+
* The optional frontmatter keys a skill spec would emit that this check ranges
|
|
19
|
+
* over. `name` and `description` are excluded because conformance requires
|
|
20
|
+
* every dialect to read them, so they can never be dropped.
|
|
21
|
+
*
|
|
22
|
+
* ⚠️ NOT the whole optional set: `context`, `allowed-tools` and
|
|
23
|
+
* `disallowed-tools` are also droppable and are not listed here. That is the
|
|
24
|
+
* pre-existing scope of this warning, carried over unchanged when the profile
|
|
25
|
+
* enum became a key set — widening it is a behaviour change to argue
|
|
26
|
+
* separately, not a side effect of the rename.
|
|
27
|
+
*/
|
|
28
|
+
export declare function optionalFrontmatterKeys(spec: SkillSpec): string[];
|
|
29
|
+
/**
|
|
30
|
+
* Warn for each declared harness that would DROP frontmatter keys this skill
|
|
31
|
+
* sets, because its dialect does not read them. Empty when the skill uses no
|
|
32
|
+
* optional keys, or when every declared harness reads the ones it uses.
|
|
33
|
+
*
|
|
34
|
+
* 🔴 THE KEYS THE WARNING NAMES ARE NOW THE KEYS ACTUALLY DROPPED. It used to
|
|
35
|
+
* test `dialect.skillFrontmatter === "minimal"` and then print every
|
|
36
|
+
* Claude-Code-only key the spec set — one bucket for every non-CC harness, and
|
|
37
|
+
* a message that could name a key the harness in fact reads. The set difference
|
|
38
|
+
* is both the condition and the message, so the two cannot disagree.
|
|
23
39
|
*/
|
|
24
40
|
export declare function skillFrontmatterDropWarnings(spec: SkillSpec, harnessNames: readonly string[]): string[];
|
|
25
41
|
//# sourceMappingURL=skill-harness.d.ts.map
|
package/dist/skill-harness.js
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.optionalFrontmatterKeys = optionalFrontmatterKeys;
|
|
4
4
|
exports.skillFrontmatterDropWarnings = skillFrontmatterDropWarnings;
|
|
5
5
|
const adapter_registry_js_1 = require("./adapter-registry.js");
|
|
6
|
-
/**
|
|
7
|
-
|
|
6
|
+
/**
|
|
7
|
+
* The optional frontmatter keys a skill spec would emit that this check ranges
|
|
8
|
+
* over. `name` and `description` are excluded because conformance requires
|
|
9
|
+
* every dialect to read them, so they can never be dropped.
|
|
10
|
+
*
|
|
11
|
+
* ⚠️ NOT the whole optional set: `context`, `allowed-tools` and
|
|
12
|
+
* `disallowed-tools` are also droppable and are not listed here. That is the
|
|
13
|
+
* pre-existing scope of this warning, carried over unchanged when the profile
|
|
14
|
+
* enum became a key set — widening it is a behaviour change to argue
|
|
15
|
+
* separately, not a side effect of the rename.
|
|
16
|
+
*/
|
|
17
|
+
function optionalFrontmatterKeys(spec) {
|
|
8
18
|
const keys = [];
|
|
9
19
|
if (spec.disableModelInvocation !== undefined) {
|
|
10
20
|
keys.push("disable-model-invocation");
|
|
@@ -15,13 +25,19 @@ function claudeOnlyFrontmatterKeys(spec) {
|
|
|
15
25
|
return keys;
|
|
16
26
|
}
|
|
17
27
|
/**
|
|
18
|
-
* Warn for each declared harness
|
|
19
|
-
*
|
|
20
|
-
*
|
|
28
|
+
* Warn for each declared harness that would DROP frontmatter keys this skill
|
|
29
|
+
* sets, because its dialect does not read them. Empty when the skill uses no
|
|
30
|
+
* optional keys, or when every declared harness reads the ones it uses.
|
|
31
|
+
*
|
|
32
|
+
* 🔴 THE KEYS THE WARNING NAMES ARE NOW THE KEYS ACTUALLY DROPPED. It used to
|
|
33
|
+
* test `dialect.skillFrontmatter === "minimal"` and then print every
|
|
34
|
+
* Claude-Code-only key the spec set — one bucket for every non-CC harness, and
|
|
35
|
+
* a message that could name a key the harness in fact reads. The set difference
|
|
36
|
+
* is both the condition and the message, so the two cannot disagree.
|
|
21
37
|
*/
|
|
22
38
|
function skillFrontmatterDropWarnings(spec, harnessNames) {
|
|
23
|
-
const
|
|
24
|
-
if (
|
|
39
|
+
const optionalKeys = optionalFrontmatterKeys(spec);
|
|
40
|
+
if (optionalKeys.length === 0)
|
|
25
41
|
return [];
|
|
26
42
|
const warnings = [];
|
|
27
43
|
const seen = new Set();
|
|
@@ -30,9 +46,11 @@ function skillFrontmatterDropWarnings(spec, harnessNames) {
|
|
|
30
46
|
if (!adapter || seen.has(adapter.name))
|
|
31
47
|
continue;
|
|
32
48
|
seen.add(adapter.name);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
49
|
+
const read = adapter.dialect.skillFrontmatterKeys;
|
|
50
|
+
const dropped = optionalKeys.filter((key) => !read.includes(key));
|
|
51
|
+
if (dropped.length > 0) {
|
|
52
|
+
const one = dropped.length === 1;
|
|
53
|
+
warnings.push(`skill "${spec.name}": ${dropped.join(", ")} ${one ? "is" : "are"} not read by declared harness "${adapter.name}" — it drops ${one ? "it" : "them"}.`);
|
|
36
54
|
}
|
|
37
55
|
}
|
|
38
56
|
return warnings;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PluginLayout } from "./core/layout.js";
|
|
1
2
|
import { type DiscoveredSurface } from "./core/surface-discovery.js";
|
|
2
3
|
import { type ExcludeSet } from "./exclude.js";
|
|
3
4
|
/**
|
|
@@ -9,4 +10,5 @@ import { type ExcludeSet } from "./exclude.js";
|
|
|
9
10
|
export declare function boundedSurfacePaths(root: string, excludes?: ExcludeSet): readonly string[];
|
|
10
11
|
/** Every surface the bounded walk finds on disk under `root`, by shape. */
|
|
11
12
|
export declare function discoverSurfacesOnDisk(root: string, excludes?: ExcludeSet): readonly DiscoveredSurface[];
|
|
13
|
+
export declare function boundedInstructionFiles(root: string, layout: PluginLayout, excludes?: ExcludeSet): Record<string, string>;
|
|
12
14
|
//# sourceMappingURL=surface-discovery-fs.d.ts.map
|
|
@@ -2,14 +2,21 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.boundedSurfacePaths = boundedSurfacePaths;
|
|
4
4
|
exports.discoverSurfacesOnDisk = discoverSurfacesOnDisk;
|
|
5
|
+
exports.boundedInstructionFiles = boundedInstructionFiles;
|
|
5
6
|
/**
|
|
6
|
-
* The DISK half of
|
|
7
|
-
* the bounded root set, so the pure
|
|
8
|
-
* can say
|
|
7
|
+
* The DISK half of BOUNDED DISCOVERY: enumerate the repo-relative paths inside
|
|
8
|
+
* the bounded root set, so the pure classifiers in `src/core/surface-discovery.ts`
|
|
9
|
+
* and `src/core/instruction-chain.ts` can say what each of them is.
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
* nobody reads"
|
|
12
|
-
* would be doing the very work the
|
|
11
|
+
* Two enumerations, one bound. {@link boundedSurfacePaths} answers "is there a
|
|
12
|
+
* surface here that nobody reads" and returns PATHS ONLY — that is a question
|
|
13
|
+
* about NAMES, and reading a file nobody claims would be doing the very work the
|
|
14
|
+
* finding says is not being done. {@link boundedInstructionFiles} answers "what
|
|
15
|
+
* does this harness load without being asked" and must return CONTENTS, because
|
|
16
|
+
* the answer depends on them: a rule's own frontmatter decides whether it loads
|
|
17
|
+
* at launch, and the repository's settings decide which files are candidates at
|
|
18
|
+
* all. The header of `core/instruction-chain.ts` holds the reason that is a port
|
|
19
|
+
* method rather than a glob.
|
|
13
20
|
*
|
|
14
21
|
* 🔴 THE WALK IS BOUNDED BY CONSTRUCTION, NOT BY A DEPTH COUNTER. One `readdir`
|
|
15
22
|
* of the repo root, one per DOT-DIRECTORY found there, and then descent ONLY
|
|
@@ -31,6 +38,8 @@ exports.discoverSurfacesOnDisk = discoverSurfacesOnDisk;
|
|
|
31
38
|
*/
|
|
32
39
|
const node_fs_1 = require("node:fs");
|
|
33
40
|
const node_path_1 = require("node:path");
|
|
41
|
+
const instruction_chain_js_1 = require("./core/instruction-chain.js");
|
|
42
|
+
const layout_js_1 = require("./core/layout.js");
|
|
34
43
|
const surface_discovery_js_1 = require("./core/surface-discovery.js");
|
|
35
44
|
const exclude_js_1 = require("./exclude.js");
|
|
36
45
|
const fs_walk_js_1 = require("./fs-walk.js");
|
|
@@ -105,4 +114,97 @@ function openableSurfaceDir(root, rel, excluded) {
|
|
|
105
114
|
function discoverSurfacesOnDisk(root, excludes) {
|
|
106
115
|
return (0, surface_discovery_js_1.discoverSurfaces)(boundedSurfacePaths(root, excludes));
|
|
107
116
|
}
|
|
117
|
+
/** Read one repo-relative file, or `undefined` for anything that is not one. */
|
|
118
|
+
function readIfFile(root, rel) {
|
|
119
|
+
const abs = (0, node_path_1.join)(root, rel);
|
|
120
|
+
if ((0, fs_walk_js_1.entryOf)(abs).kind !== "file")
|
|
121
|
+
return undefined;
|
|
122
|
+
try {
|
|
123
|
+
return (0, node_fs_1.readFileSync)(abs, "utf-8");
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return undefined; // unreadable: the same silence the walk gives elsewhere
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** Repo-relative paths of the files directly inside one discovery root. */
|
|
130
|
+
function filesDirectlyIn(root, base) {
|
|
131
|
+
const baseAbs = base === "" ? root : (0, node_path_1.join)(root, base);
|
|
132
|
+
return namesIn(baseAbs).map((n) => (base === "" ? n : `${base}/${n}`));
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Every instruction CANDIDATE on disk, with its contents — the input a harness's
|
|
136
|
+
* `instructionChain` is allowed to classify.
|
|
137
|
+
*
|
|
138
|
+
* 🔴 BOUNDED BY THE SAME CONSTRUCTION AS THE SURFACE WALK, and for the same
|
|
139
|
+
* reason. One `readdir` of the repo root, one per dot-directory found there, and
|
|
140
|
+
* a recursive descent ONLY into a `rules` directory inside one of those. `src/`,
|
|
141
|
+
* `packages/` and `node_modules/` are never entered — which is exactly what the
|
|
142
|
+
* thing this replaced did do: `scan.ts:readAlwaysLoaded` expanded an ADAPTER's
|
|
143
|
+
* `"**\/AGENTS.md"` by recursing through the whole tree, so registering an
|
|
144
|
+
* adapter widened what vigiles read in everyone's repository.
|
|
145
|
+
*
|
|
146
|
+
* The one read outside that bound is the IMPORT PASS below, and the difference
|
|
147
|
+
* is who chose the path.
|
|
148
|
+
*/
|
|
149
|
+
/**
|
|
150
|
+
* The `rules` tree under one dot-directory, read RECURSIVELY.
|
|
151
|
+
*
|
|
152
|
+
* Recursive because the vendor documents it that way — and because the scan
|
|
153
|
+
* classifier now says the same through `RULE_FILE_LEAF_RE`; a rule the loader
|
|
154
|
+
* reads and the classifier ignores is a file that is never checked, counted or
|
|
155
|
+
* weighed. The entry point goes through the same symlink and exclude policy as
|
|
156
|
+
* a surface dir, `filesUnder` applies `exclude` per entry below it.
|
|
157
|
+
*/
|
|
158
|
+
function addRulesTree(root, rulesRel, excluded, out) {
|
|
159
|
+
if (rulesRel === null)
|
|
160
|
+
return;
|
|
161
|
+
// The same entry question `openableSurfaceDir` answers for a surface dir, so
|
|
162
|
+
// it is asked through that function rather than spelled a second time. The
|
|
163
|
+
// second spelling was `kind !== "dir"`, and `entryOf` says "skip" for a
|
|
164
|
+
// symlinked directory on purpose — so a `.claude/rules` linked to a shared
|
|
165
|
+
// policy directory was dropped whole, while the same link as `.claude/skills`
|
|
166
|
+
// was walked.
|
|
167
|
+
if (!openableSurfaceDir(root, rulesRel, excluded))
|
|
168
|
+
return;
|
|
169
|
+
filesUnder(root, rulesRel, excluded, out);
|
|
170
|
+
}
|
|
171
|
+
function boundedInstructionFiles(root, layout, excludes) {
|
|
172
|
+
const excluded = (0, exclude_js_1.excludedBy)(excludes);
|
|
173
|
+
const roots = ["", ...namesIn(root).filter((n) => (0, surface_discovery_js_1.isDiscoveryRoot)(n))].filter((b) => b === "" || !excluded((0, node_path_1.join)(root, b)));
|
|
174
|
+
// Every file directly inside a discovery root is a candidate PATH; the pure
|
|
175
|
+
// filter keeps the instruction-shaped ones plus the layout's own named files
|
|
176
|
+
// (`.codex/config.toml` is neither markdown nor an instruction — it is the
|
|
177
|
+
// settings source that decides WHICH files load, and is never weighed).
|
|
178
|
+
const candidates = [];
|
|
179
|
+
for (const base of roots) {
|
|
180
|
+
candidates.push(...filesDirectlyIn(root, base));
|
|
181
|
+
}
|
|
182
|
+
// 🔴 THE RULES TREE HAS ONE HOME, AND THE LAYOUT NAMES IT (#271).
|
|
183
|
+
//
|
|
184
|
+
// This used to run INSIDE the loop above, joining `<base>/<rulesDir>` for
|
|
185
|
+
// every discovery root and guarding the repository root out with
|
|
186
|
+
// `if (base !== "")`. Two wrong answers fell out of that, measured on layouts
|
|
187
|
+
// built from the type: a layout declaring `rulesDir` with no
|
|
188
|
+
// `userSurfaceRoot` had its tree skipped entirely, while `.github/rules` was
|
|
189
|
+
// walked for every layout, declared or not. `rulesHome` is the same answer
|
|
190
|
+
// `ruleFileRe` gives the classifier, so the walk and the filter can no longer
|
|
191
|
+
// disagree about which tree belongs to this harness.
|
|
192
|
+
addRulesTree(root, (0, layout_js_1.rulesHome)(layout), excluded, candidates);
|
|
193
|
+
const candidateFiles = {};
|
|
194
|
+
for (const rel of (0, instruction_chain_js_1.instructionCandidatePaths)(candidates, layout)) {
|
|
195
|
+
if (excluded((0, node_path_1.join)(root, rel)))
|
|
196
|
+
continue;
|
|
197
|
+
const text = readIfFile(root, rel);
|
|
198
|
+
if (text !== undefined)
|
|
199
|
+
candidateFiles[rel] = text;
|
|
200
|
+
}
|
|
201
|
+
// The import pass is the ONE read outside the bound, and the pure half of it
|
|
202
|
+
// lives in the core so the browser twin runs the identical loop over its file
|
|
203
|
+
// map. It is ONE concrete path per token, one level deep — the corpus
|
|
204
|
+
// measurement behind that is in `resolveImports`. `exclude` is deliberately
|
|
205
|
+
// NOT applied to it: the path was written by the repository owner in their own
|
|
206
|
+
// instruction file, and the harness really does load it, so hiding its size
|
|
207
|
+
// would under-report the one number this report exists to give.
|
|
208
|
+
return (0, instruction_chain_js_1.resolveImports)(layout, candidateFiles, (rel) => readIfFile(root, rel));
|
|
209
|
+
}
|
|
108
210
|
//# sourceMappingURL=surface-discovery-fs.js.map
|