vigiles 32.1.1 → 33.1.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.
@@ -1,3 +1,16 @@
1
+ /**
2
+ * vigiles — run harness-test / eval script files via the CLI.
3
+ *
4
+ * `vigiles test` and `vigiles eval` discover `*.harness.*` / `*.eval.*`
5
+ * scripts and run each as a child `node` process, so the two-tier
6
+ * harness-testing API (`src/harness-test.ts`, `src/eval.ts`) works as a CI
7
+ * command, not just `node x.mjs`. Scripts may be authored in **JavaScript**
8
+ * (`.mjs` / `.cjs` / `.js`) **or TypeScript** (`.ts` / `.mts` / `.cts`) — a TS
9
+ * script is run through `tsx` when installed, else Node's built-in type
10
+ * stripping (Node >= 22.6). The scripts import from the built `dist/`, so they
11
+ * also run standalone — the CLI just discovers, runs, and aggregates exit codes.
12
+ */
13
+ import { type TestTier } from "../../source-kinds.js";
1
14
  import { type SurfaceProbe } from "../../check-count.js";
2
15
  /**
3
16
  * The outcome of running one script.
@@ -102,10 +115,13 @@ export declare function statusFor(code: number, checks: number | undefined, load
102
115
  * retracting silently with no type error.
103
116
  */
104
117
  export declare function loadFailed(r: ScriptRunResult): boolean;
105
- /** Filename extensions accepted for harness/eval scripts (JS and TS). */
118
+ /**
119
+ * Filename extensions accepted for harness/eval scripts (JS and TS). The table
120
+ * lives in `source-kinds.ts`; this re-export keeps the runner's name for it.
121
+ */
106
122
  export declare const SCRIPT_EXTS: readonly ["mjs", "cjs", "js", "mts", "cts", "ts"];
107
- /** Glob suffix matching every accepted script extension, e.g. `harness`. */
108
- export declare function scriptGlob(kind: "harness" | "eval"): string;
123
+ /** The glob that discovers one tier's scripts, derived from the same table as everything else that names a test. */
124
+ export declare function scriptGlob(kind: TestTier): string;
109
125
  export { detectNodeCaps, canRunTypeScript, type NodeCaps, } from "../../ts-runner-caps.js";
110
126
  import type { NodeCaps } from "../../ts-runner-caps.js";
111
127
  import { type GlobIgnore } from "../../core/glob-ignore.js";
@@ -22,6 +22,7 @@ exports.formatScriptSummary = formatScriptSummary;
22
22
  * stripping (Node >= 22.6). The scripts import from the built `dist/`, so they
23
23
  * also run standalone — the CLI just discovers, runs, and aggregates exit codes.
24
24
  */
25
+ const source_kinds_js_1 = require("../../source-kinds.js");
25
26
  const node_child_process_1 = require("node:child_process");
26
27
  const node_os_1 = require("node:os");
27
28
  const node_path_1 = require("node:path");
@@ -103,11 +104,14 @@ function statusFor(code, checks, load) {
103
104
  function loadFailed(r) {
104
105
  return r.status === "skip" && r.code !== exports.SKIP_EXIT_CODE;
105
106
  }
106
- /** Filename extensions accepted for harness/eval scripts (JS and TS). */
107
- exports.SCRIPT_EXTS = ["mjs", "cjs", "js", "mts", "cts", "ts"];
108
- /** Glob suffix matching every accepted script extension, e.g. `harness`. */
107
+ /**
108
+ * Filename extensions accepted for harness/eval scripts (JS and TS). The table
109
+ * lives in `source-kinds.ts`; this re-export keeps the runner's name for it.
110
+ */
111
+ exports.SCRIPT_EXTS = source_kinds_js_1.RUNNABLE_EXTS;
112
+ /** The glob that discovers one tier's scripts, derived from the same table as everything else that names a test. */
109
113
  function scriptGlob(kind) {
110
- return `**/*.${kind}.{${exports.SCRIPT_EXTS.join(",")}}`;
114
+ return (0, source_kinds_js_1.testGlob)(kind);
111
115
  }
112
116
  const TS_EXT = /\.(?:m|c)?ts$/;
113
117
  // The capability probe lives in `src/ts-runner-caps.ts` so the SUGGESTER
@@ -194,6 +198,10 @@ function discoverScripts(patterns, defaultGlob, cwd, ignore) {
194
198
  ignore: (0, glob_ignore_js_1.withIgnored)([], ignore),
195
199
  dot: true,
196
200
  nodir: true,
201
+ // `glob` defaults this to TRUE on macOS and Windows, which made
202
+ // `a.hook.HARNESS.mjs` a test there and nothing on Linux. The kind of a
203
+ // name is decided case-sensitively (`source-kinds.ts`); the glob agrees.
204
+ nocase: false,
197
205
  })) {
198
206
  found.add(m);
199
207
  }
package/dist/cli-main.js CHANGED
@@ -5981,7 +5981,16 @@ async function refsHookCommand() {
5981
5981
  */
5982
5982
  async function compileProviders() {
5983
5983
  const names = [];
5984
- for (const file of (0, hook_install_js_1.discoverProviderFiles)(process.cwd())) {
5984
+ const { claimed, unclaimed } = (0, hook_install_js_1.discoverProviderFiles)(process.cwd());
5985
+ // A provider that lost (or never had) its marker is not silently skipped: a
5986
+ // hook's `provider()` ref to it would then fail to resolve with a message
5987
+ // about the REF, far from the cause.
5988
+ if (unclaimed.length > 0) {
5989
+ // Every one gets the ✗ (the catch adds the first): the others used to read
5990
+ // as a continuation of the first message.
5991
+ throw new hook_program_js_1.HookCompileError(unclaimed.map((f) => (0, hook_install_js_1.unclaimedReport)(f, "provider")).join("\n✗ "));
5992
+ }
5993
+ for (const file of claimed) {
5985
5994
  const def = await (0, hook_runtime_js_1.loadProvider)(file);
5986
5995
  if ((0, hook_providers_js_1.unsafeProvider)(def)) {
5987
5996
  throw new hook_program_js_1.HookCompileError(`provider ${file} ("${def.run}") is not provably read-only — ` +
@@ -6117,8 +6126,8 @@ async function installHookFile(file, adapter, registeredProviders = []) {
6117
6126
  * resolved from the `--harness=` flag, else `.vigilesrc.json#harnesses`, else auto-detect.
6118
6127
  * Returns false if any hook failed to compile for any harness.
6119
6128
  */
6120
- async function installHooks(hookFiles, harnessFlag, configHarness) {
6121
- if (hookFiles.length === 0)
6129
+ async function installHooks(hookFiles, harnessFlag, configHarness, validateProvidersAnyway = false) {
6130
+ if (hookFiles.length === 0 && !validateProvidersAnyway)
6122
6131
  return true;
6123
6132
  const adapters = (0, adapter_registry_js_1.resolveHarnessAdapters)({
6124
6133
  root: process.cwd(),
@@ -6138,6 +6147,8 @@ async function installHooks(hookFiles, harnessFlag, configHarness) {
6138
6147
  }
6139
6148
  throw e;
6140
6149
  }
6150
+ if (hookFiles.length === 0)
6151
+ return true; // providers validated; nothing to wire
6141
6152
  let ok = true;
6142
6153
  for (const file of hookFiles) {
6143
6154
  try {
@@ -6645,15 +6656,40 @@ async function main() {
6645
6656
  .filter((f) => f.endsWith(".spec.ts"))
6646
6657
  .map((f) => noteExplicitOverride(excludes, f, "compiling"))
6647
6658
  : findSpecs(excludes);
6648
- const hooks = restArgs.length > 0
6649
- ? restArgs.filter((f) => !f.endsWith(".spec.ts"))
6650
- : (0, hook_install_js_1.discoverHookFiles)(process.cwd());
6651
- if (specs.length === 0 && hooks.length === 0) {
6659
+ // WHAT A HOOK IS is decided once, in `source-kinds.ts`: a bare `compile`
6660
+ // and `compile <file>` ask the same classifier, so they cannot disagree.
6661
+ // Only `.hook.` files are compiled; a runnable file that carries no marker
6662
+ // is refused below, out loud, with the command that fixes it.
6663
+ const found = restArgs.length > 0
6664
+ ? (0, hook_install_js_1.partitionHookArgs)(restArgs.filter((f) => !f.endsWith(".spec.ts")))
6665
+ : (0, hook_install_js_1.discoveredHookArgs)(process.cwd());
6666
+ const hooks = [...found.hooks];
6667
+ const unclaimed = found.unclaimed;
6668
+ if (specs.length === 0 &&
6669
+ hooks.length === 0 &&
6670
+ unclaimed.length === 0 &&
6671
+ found.skipped.length === 0 &&
6672
+ found.invalid.length === 0) {
6652
6673
  console.log("No .spec.ts or .vigiles/hooks/ hook files found.");
6653
6674
  console.log("Run `vigiles init` to create one.");
6654
6675
  process.exit(0);
6655
6676
  }
6656
6677
  let valid = true;
6678
+ // Refused files FIRST and on stderr: they are the news in this run, and a
6679
+ // line that scrolled past under forty `✓` lines is a silent skip with
6680
+ // extra steps. Compiling the rest still proceeds — one stray file must
6681
+ // not stop the hooks that are fine.
6682
+ for (const f of unclaimed) {
6683
+ console.error(`✗ ${(0, hook_install_js_1.unclaimedReport)(f, "hook")}`);
6684
+ valid = false;
6685
+ }
6686
+ for (const bad of found.invalid) {
6687
+ console.error(`✗ ${(0, hook_install_js_1.invalidArgMessage)(bad.path, bad.reason)}`);
6688
+ valid = false;
6689
+ }
6690
+ for (const s of found.skipped) {
6691
+ console.log(`- ${s.path} skipped: a ${s.kind}, not a hook.`);
6692
+ }
6657
6693
  if (specs.length > 0)
6658
6694
  valid =
6659
6695
  (await compile(specs, config, excludes, { harnessFlag })) && valid;
@@ -6670,13 +6706,21 @@ async function main() {
6670
6706
  console.log(`\n${weightLine}`);
6671
6707
  }
6672
6708
  valid =
6673
- (await installHooks(hooks, harnessFlag, (0, adapter_registry_js_1.declaredHarnessNames)(config.harnesses))) && valid;
6709
+ (await installHooks(hooks, harnessFlag, (0, adapter_registry_js_1.declaredHarnessNames)(config.harnesses),
6710
+ // A hook-side compile is under way even when every hook was refused:
6711
+ // validate the providers too, so ONE run shows every problem.
6712
+ unclaimed.length > 0)) && valid;
6674
6713
  // Keep an existing whole-harness registry in sync (cheap, opt-in) so the
6675
6714
  // user never hand-runs `generate-harness`. Skipped when no harness.gen.ts.
6676
6715
  if (specs.length > 0)
6677
6716
  valid = (await refreshHarnessGenIfPresent(harnessFlag)) && valid;
6678
6717
  console.log("");
6679
- if (valid) {
6718
+ if (valid && specs.length === 0 && hooks.length === 0) {
6719
+ // Skipped paths are not errors, but a run that compiled nothing must not
6720
+ // read like one that did.
6721
+ console.log("Nothing was compiled: every path given was skipped.");
6722
+ }
6723
+ else if (valid) {
6680
6724
  console.log("Compilation complete.");
6681
6725
  }
6682
6726
  else {
@@ -28,7 +28,7 @@
28
28
  * 🔴 THE ONE CONVERSION POINT. A `RepoPath` is minted here and nowhere else:
29
29
  * {@link Frame.repo} from an absolute path, {@link Bundle.path} from a
30
30
  * `BundlePath`. A cast to `RepoPath` outside this file is refused by ESLint
31
- * (`no-restricted-syntax` in `eslint.config.mjs`), so "where did this path get its
31
+ * (`local/frame-mint` in `eslint.config.mjs`), so "where did this path get its
32
32
  * frame" always has the answer "in frame.ts".
33
33
  *
34
34
  * WHAT IS UNREPRESENTABLE AND WHAT IS ONLY DETECTABLE. The CLI's output boundary
@@ -515,7 +515,7 @@ function commandView(raw, root) {
515
515
  // uses, and for the same reason. This matcher read only `leaves` (raw), so
516
516
  // three ordinary rewrites walked past a guard that blocks the plain form.
517
517
  // MEASURED 2026-09-02 on the shipped dogfood artifact behind the public
518
- // 7/7 claim (`examples/harness/safe-bash-guard.mjs`):
518
+ // 7/7 claim (`examples/harness/safe-bash-guard.hook.mjs`):
519
519
  // git push --force origin main exit 2 blocked
520
520
  // git push "--force" origin main exit 0 ALLOWED
521
521
  // sudo git push --force origin main exit 0 ALLOWED
@@ -54,7 +54,7 @@ export declare const provide: <const Name extends string>(name: Name, run: strin
54
54
  export declare const dangerously: <const Name extends string>(name: Name, run: string) => InlineProvider<Name>;
55
55
  /**
56
56
  * A REGISTERED provider — a reusable, named fact authored once in
57
- * `.vigiles/providers/<name>.{mjs,ts}` (default-exported via {@link defineProvider})
57
+ * `.vigiles/providers/<name>.provider.{mjs,ts}` (default-exported via {@link defineProvider})
58
58
  * and referenced from many hooks by {@link provider} name. The reusable sibling of
59
59
  * the one-off inline {@link provide}; same read-only-by-default rule.
60
60
  */
@@ -64,9 +64,7 @@ export interface PreparedTest {
64
64
  export declare function isColocatedTest(surface: Pick<CoverableSurface, "name" | "path">, testPath: string): boolean;
65
65
  /** Wrap a discovered path. Kept as a function so both twins share one shape. */
66
66
  export declare function prepareTest(path: string): PreparedTest;
67
- /** @param filename a BASENAME — callers strip the directory with their own path
68
- * helper (`node:path` on disk, `posix-path` in the browser twin). */
69
- export declare function isEvalScript(filename: string): boolean;
67
+ export { isEvalScript } from "./source-kinds.js";
70
68
  /**
71
69
  * The root variables a hook command may be written against — the structural
72
70
  * slice of `PluginLayout` this needs, taken by SHAPE so the browser twin does
@@ -1,10 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SURFACE_TOKEN = void 0;
3
+ exports.SURFACE_TOKEN = exports.isEvalScript = void 0;
4
4
  exports.strongerEvidence = strongerEvidence;
5
5
  exports.isColocatedTest = isColocatedTest;
6
6
  exports.prepareTest = prepareTest;
7
- exports.isEvalScript = isEvalScript;
8
7
  exports.hookScriptRefs = hookScriptRefs;
9
8
  exports.evidenceFor = evidenceFor;
10
9
  exports.hasSurfaceToken = hasSurfaceToken;
@@ -185,39 +184,12 @@ function prepareTest(path) {
185
184
  * mode here is under-counting, not accusation.
186
185
  */
187
186
  const SCRIPT_RE = (0, source_refs_js_1.scriptRefPattern)();
188
- /**
189
- * Is this filename one the PAID real-model runner would actually run?
190
- *
191
- * The tier split decides which of two runners owns a test file, and it is the
192
- * only thing standing between a scheduled paid run and a per-push one. It has
193
- * now been wrong in both directions:
194
- *
195
- * - As the full suffix `.eval.mjs` it was a MONEY HAZARD — `foo.eval.ts` fell
196
- * into the free branch and would have spent real model calls on every push.
197
- * - As the bare INFIX `.eval.` it made a FALSE GRANT — `parser.eval.test.ts`,
198
- * an ordinary deterministic test discovered by an `include` of
199
- * `**\/*.test.ts`, was credited to the paid tier and dropped from the free
200
- * one. `vigiles eval` globs `**\/*.eval.{mjs,cjs,js,mts,cts,ts}`, so that name
201
- * is not discoverable by the eval runner at all: the surface was reported
202
- * covered by the tier that cannot run it, and uncovered by the tier that does.
203
- *
204
- * The rule that is wrong in neither direction is the one the runner itself uses:
205
- * `.eval.` followed by a runnable extension AT THE END of the name. Anything
206
- * else — including a name that merely contains `.eval.` — is deterministic, or
207
- * is not a script at all.
208
- *
209
- * ⚠️ The extension list is RE-DECLARED, not imported: the authority is
210
- * `SCRIPT_EXTS` in adapters/claude-code/run-scripts.ts, which pulls in
211
- * `node:child_process` and `glob` and so cannot be imported by this
212
- * browser-safe module. A test in test-coverage.test.ts asserts the two agree,
213
- * so an extension added to the runner cannot silently fail to be billed here.
214
- */
215
- const EVAL_SCRIPT_RE = /\.eval\.(?:mjs|cjs|js|mts|cts|ts)$/;
216
- /** @param filename a BASENAME — callers strip the directory with their own path
217
- * helper (`node:path` on disk, `posix-path` in the browser twin). */
218
- function isEvalScript(filename) {
219
- return EVAL_SCRIPT_RE.test(filename);
220
- }
187
+ // Which runner owns a test file — the paid real-model one or the free one — is
188
+ // answered by the shared source-kind table (`source-kinds.ts`), which this
189
+ // browser-safe module can import because it imports nothing. See `isEvalScript`
190
+ // there for the two ways this question has been answered wrongly before.
191
+ var source_kinds_js_1 = require("./source-kinds.js");
192
+ Object.defineProperty(exports, "isEvalScript", { enumerable: true, get: function () { return source_kinds_js_1.isEvalScript; } });
221
193
  /**
222
194
  * The hook-script paths a manifest's `hooks` block names, repo-relative.
223
195
  *
@@ -60,6 +60,7 @@ exports.defineEval = defineEval;
60
60
  * never executes, so it is untouched.
61
61
  */
62
62
  const node_path_1 = require("node:path");
63
+ const source_kinds_js_1 = require("./source-kinds.js");
63
64
  /**
64
65
  * Brand marking a value as built by {@link defineEval}. A registered symbol, so
65
66
  * a descriptor still reads as one across two copies of the package on disk —
@@ -128,8 +129,6 @@ function moduleDefault(mod) {
128
129
  const inner = outer?.default;
129
130
  return isEvalDefinition(inner) ? inner : outer;
130
131
  }
131
- /** Filenames vigiles runs as evals — the runner's own glob, as a pattern. */
132
- const EVAL_FILE = /\.eval\.(?:m|c)?[jt]s$/;
133
132
  /**
134
133
  * Was node pointed STRAIGHT at an eval file? `argv1` is `process.argv[1]`: the
135
134
  * path node was started with, which no stray configuration can forge.
@@ -142,7 +141,8 @@ const EVAL_FILE = /\.eval\.(?:m|c)?[jt]s$/;
142
141
  * Pure: a fact in, a boolean out.
143
142
  */
144
143
  function ranAsEntry(argv1) {
145
- return argv1 !== undefined && EVAL_FILE.test(argv1.replaceAll("\\", "/"));
144
+ return (argv1 !== undefined &&
145
+ (0, source_kinds_js_1.isEvalScript)(argv1.replaceAll("\\", "/").split("/").pop() ?? ""));
146
146
  }
147
147
  /** The words shown when someone runs an eval file directly. Asserted by a test:
148
148
  * a refusal that stops a run without saying what to do instead is a ticket. */
package/dist/eval-lock.js CHANGED
@@ -58,6 +58,7 @@ const node_fs_1 = require("node:fs");
58
58
  const node_path_1 = require("node:path");
59
59
  const hash_js_1 = require("./core/hash.js");
60
60
  const eval_cache_js_1 = require("./eval-cache.js");
61
+ const source_kinds_js_1 = require("./source-kinds.js");
61
62
  /**
62
63
  * On-disk lock-format version, salted into nothing (the lock is keyed by name,
63
64
  * not by hash) but VALIDATED on read so an incompatible shape fails loud rather
@@ -162,7 +163,7 @@ function isEvalInputFile(path) {
162
163
  const p = path.replace(/\\/g, "/");
163
164
  if (/(^|\/)SKILL\.md$/.test(p))
164
165
  return true;
165
- return /\.eval\.(mjs|cjs|js|mts|cts|ts)$/.test(p);
166
+ return (0, source_kinds_js_1.isEvalScript)(p.split("/").pop() ?? "");
166
167
  }
167
168
  /**
168
169
  * The NON-BLOCKING nudge to emit after an eval-input edit when committed locks
@@ -1,11 +1,100 @@
1
+ import { type CompilableMarker, type SourceKind } from "./source-kinds.js";
1
2
  /** The agnostic, committed home for hook SOURCE — one dir, cross-adapter. */
2
3
  export declare const HOOKS_DIR = ".vigiles/hooks";
3
4
  /** The committed home for registered context-provider SOURCE (v2). */
4
5
  export declare const PROVIDERS_DIR = ".vigiles/providers";
5
- /** Discover hook source files under {@link HOOKS_DIR} (stamps excluded). */
6
- export declare function discoverHookFiles(cwd: string): string[];
7
- /** Discover registered-provider source files under {@link PROVIDERS_DIR}. */
8
- export declare function discoverProviderFiles(cwd: string): string[];
6
+ /** What discovery found in one source directory, split by what `compile` owes each part. */
7
+ export interface Discovered {
8
+ /** Files this directory's role compiles: `.hook.` files in `.vigiles/hooks/`, `.provider.` files in `.vigiles/providers/`. */
9
+ readonly claimed: readonly string[];
10
+ /**
11
+ * Runnable files nothing claims here. `compile` REFUSES these out loud
12
+ * ({@link unclaimedMessage}); dropping them would make a hook that lost its
13
+ * marker vanish from the build without a word.
14
+ */
15
+ readonly unclaimed: readonly string[];
16
+ }
17
+ /** Discover hook sources under {@link HOOKS_DIR}: `.hook.` files, and the runnable files that are nobody's. */
18
+ export declare function discoverHookFiles(cwd: string): Discovered;
19
+ /** Discover registered-provider sources under {@link PROVIDERS_DIR}. */
20
+ export declare function discoverProviderFiles(cwd: string): Discovered;
21
+ /** Why an explicit `compile` argument is an error rather than a hook, a refusal or a skip. */
22
+ export type InvalidArgReason = "missing" | "directory" | "not-a-source";
23
+ /** What `vigiles compile <files…>` makes of the paths it was handed. */
24
+ export interface HookArgs {
25
+ readonly hooks: readonly string[];
26
+ /** Runnable files nothing claims (or a marked file in the wrong directory): refused, with the fix. */
27
+ readonly unclaimed: readonly string[];
28
+ /** Named on purpose, not compilable (a test, a stamp…): said out loud, not an error. */
29
+ readonly skipped: readonly {
30
+ readonly path: string;
31
+ readonly kind: SourceKind["kind"];
32
+ }[];
33
+ /** Not a file at all, or not a source: an error, the way it always was. */
34
+ readonly invalid: readonly {
35
+ readonly path: string;
36
+ readonly reason: InvalidArgReason;
37
+ }[];
38
+ }
39
+ /** What the file system says a path is. Injected so the rules below stay pure. */
40
+ export type PathProbe = (path: string) => "file" | "directory" | "missing" | "other";
41
+ /**
42
+ * Sort explicit `compile` arguments by the same classifier discovery uses, so
43
+ * `compile guard.mjs` and a bare `compile` give the same answer about it — for a
44
+ * path ANYWHERE, not just under `.vigiles/hooks/`: `compile .claude/hooks/x.mjs`
45
+ * or a plugin's `hooks/guard.mjs` is refused unless it carries `.hook.`.
46
+ *
47
+ * A path that is missing, a directory or not a regular file is an error (it used
48
+ * to fail, and the first version of the marker rule turned it into a green run).
49
+ * The one lenient case exists for a shell glob (`compile .vigiles/hooks/*`): a
50
+ * test, declaration or stamp is reported as skipped, and so is a README or
51
+ * `.gitkeep` — but only inside the two vigiles source directories, where a glob
52
+ * would sweep one up. A `/etc/passwd` is not a README.
53
+ */
54
+ export declare function partitionHookArgs(args: readonly string[], probe?: PathProbe): HookArgs;
55
+ /** The line `compile` prints for an argument it cannot treat as a hook source at all. */
56
+ export declare function invalidArgMessage(path: string, reason: InvalidArgReason): string;
57
+ /**
58
+ * Discovery, shaped like {@link partitionHookArgs}'s answer, so `compile` handles
59
+ * "no arguments" and "these paths" through one code path afterwards.
60
+ */
61
+ export declare function discoveredHookArgs(cwd: string): HookArgs;
62
+ /**
63
+ * The ONE shell command that renames an unclaimed file to its marked name and
64
+ * (for a hook) recompiles it. Every path is single-quoted: this is printed by a
65
+ * tool and pasted by a person, and a repository can plant a file name that is a
66
+ * command.
67
+ *
68
+ * 🔴 THE HOOK COMMAND COPIES FIRST AND DELETES LAST, and that order is the
69
+ * point. Already-wired hooks keep running after an upgrade: the runtime loads
70
+ * whatever path `settings.json` names, and never asks what a file is called. So
71
+ * the break is the NEXT compile, and a plain `mv` + `compile` has one bad
72
+ * moment — the old path is gone and the wiring still names it. For a gate
73
+ * wired `|| exit 2` that is a hook that cannot load, which blocks every Bash
74
+ * call (the repair is a file write, not a command). Compiling the renamed copy
75
+ * while the old file still exists, and deleting the old one only after that
76
+ * compile succeeded, has no such moment: if anything fails the old hook is
77
+ * still on disk and still wired. `compile` replaces the old wiring itself
78
+ * ({@link ownedRefs}), so nothing is left naming the deleted path.
79
+ *
80
+ * 🔴 `cp -n`, because the marked name may already exist: a plain `cp` replaced
81
+ * an edited `guard.hook.mjs` with the stale `guard.mjs`, and the weaker hook
82
+ * became the wired gate. {@link unclaimedMessage} checks first and prints no
83
+ * command at all in that case; `-n` is for the file that appears in between.
84
+ */
85
+ export declare function renameCommand(path: string, role: CompilableMarker): string;
86
+ /**
87
+ * {@link unclaimedMessage} with the one fact it cannot know, read off the disk:
88
+ * whether the marked name is already there.
89
+ */
90
+ export declare function unclaimedReport(path: string, role: CompilableMarker, cwd?: string): string;
91
+ /**
92
+ * The text `compile` prints for one file it will not compile, with the exact
93
+ * command to fix it. `targetExists` is whether the marked name is already on
94
+ * disk: then there is nothing safe to copy, and the message says to merge by
95
+ * hand instead.
96
+ */
97
+ export declare function unclaimedMessage(path: string, role: CompilableMarker, targetExists?: boolean): string;
9
98
  interface CommandHook {
10
99
  readonly type: "command";
11
100
  readonly command: string;