@orkestrel/probe 0.0.1 → 0.0.2

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.
@@ -187,6 +187,43 @@ export declare interface Inspection {
187
187
  readonly claim: Claim;
188
188
  }
189
189
 
190
+ /**
191
+ * Reports whether a fault means the host refuses the name a caller supplied for a file to create.
192
+ *
193
+ * @remarks
194
+ * A host reports one refusal with a code of its own and several unrelated conditions with one
195
+ * shared code, so the code alone does not separate a name the host will not accept from a file
196
+ * that is merely absent. `ENAMETOOLONG` names the refusal outright. `ERR_INVALID_ARG_VALUE` names
197
+ * every argument Node rejects, so it names the refusal only for a path carrying the NUL byte Node
198
+ * refuses; the same code arrives for a bad `flag` and a bad encoding, which say nothing about the
199
+ * name. An `ENOENT` raised while the parent stats as a directory names the refusal too, because an
200
+ * ordinary absent file beneath an existing directory would have been created instead; a Windows
201
+ * host reports an overlong component and a character its filesystem reserves that way. Measured on
202
+ * 2026-08-21 on Windows 11 with Node v24.18.1 over NTFS, `writeFileSync` reported `ENOENT` for a
203
+ * 300-character final component and never reported `ENAMETOOLONG`.
204
+ *
205
+ * This reads the fault the host reported rather than applying a length or character policy of its
206
+ * own, so a host that accepts a name another host refuses is not second-guessed and no platform
207
+ * branch decides the answer. Every read of the fault is guarded, so it is total over every value a
208
+ * published caller can supply: a proxy whose `has` trap throws, a value whose `code` getter throws,
209
+ * a fault carrying no code, and a parent this host cannot stat each report false.
210
+ *
211
+ * @param file - The path whose creation failed
212
+ * @param error - The fault the failed operation raised
213
+ * @returns True when the fault means the host refuses this name; false otherwise
214
+ *
215
+ * @example
216
+ * ```ts
217
+ * try {
218
+ * writeFileSync('tmp/probe/greeting\0.test.ts', '')
219
+ * } catch (error) {
220
+ * isRefusedName('tmp/probe/greeting\0.test.ts', error) // true
221
+ * }
222
+ * isRefusedName('tmp/probe/greeting.test.ts', new Error('the runtime stage was destroyed')) // false
223
+ * ```
224
+ */
225
+ export declare function isRefusedName(file: string, error: unknown): boolean;
226
+
190
227
  /**
191
228
  * Inspects virtual documents through one resident Oxlint language server.
192
229
  *
@@ -728,11 +765,12 @@ export declare function resolveWorkspaceModule(workspace: string, specifier: str
728
765
  * as a path string, into which the stage can install no overlay, and a specification path that
729
766
  * crosses a symbolic link, whose existing components this host cannot inspect, or whose directory
730
767
  * the target tree blocks probe from creating. An `origin: 'instrument'` issue names this package's
731
- * own machinery: a specification it could not write after its directory exists, or one it could
732
- * not run, evict, or delete, in each case for a reason the target tree does not own, and a run
733
- * that reported no test. An unmapped caller test and a test whose named project is absent are claimant failures with
734
- * `code: 'missing'`, thrown rather than reported, because a test that never ran is no evidence
735
- * about the candidate.
768
+ * own machinery: a specification it could not write after its directory exists and the host did not
769
+ * refuse the caller's own name, or one it could not run, evict, or delete, in each case for a
770
+ * reason the target tree does not own, and a run that reported no test. An unmapped caller test and
771
+ * a test whose named project is absent are claimant failures with `code: 'missing'`, and a declared
772
+ * test path the host refuses to create is a claimant failure with `code: 'refused'`. Each is thrown
773
+ * rather than reported, because a test that never ran is no evidence about the candidate.
736
774
  *
737
775
  * The write path's physical-containment guarantee covers the claim inputs and the target tree as
738
776
  * inspected. A concurrent process that mutates a path component between the final inspection and
@@ -187,6 +187,43 @@ export declare interface Inspection {
187
187
  readonly claim: Claim;
188
188
  }
189
189
 
190
+ /**
191
+ * Reports whether a fault means the host refuses the name a caller supplied for a file to create.
192
+ *
193
+ * @remarks
194
+ * A host reports one refusal with a code of its own and several unrelated conditions with one
195
+ * shared code, so the code alone does not separate a name the host will not accept from a file
196
+ * that is merely absent. `ENAMETOOLONG` names the refusal outright. `ERR_INVALID_ARG_VALUE` names
197
+ * every argument Node rejects, so it names the refusal only for a path carrying the NUL byte Node
198
+ * refuses; the same code arrives for a bad `flag` and a bad encoding, which say nothing about the
199
+ * name. An `ENOENT` raised while the parent stats as a directory names the refusal too, because an
200
+ * ordinary absent file beneath an existing directory would have been created instead; a Windows
201
+ * host reports an overlong component and a character its filesystem reserves that way. Measured on
202
+ * 2026-08-21 on Windows 11 with Node v24.18.1 over NTFS, `writeFileSync` reported `ENOENT` for a
203
+ * 300-character final component and never reported `ENAMETOOLONG`.
204
+ *
205
+ * This reads the fault the host reported rather than applying a length or character policy of its
206
+ * own, so a host that accepts a name another host refuses is not second-guessed and no platform
207
+ * branch decides the answer. Every read of the fault is guarded, so it is total over every value a
208
+ * published caller can supply: a proxy whose `has` trap throws, a value whose `code` getter throws,
209
+ * a fault carrying no code, and a parent this host cannot stat each report false.
210
+ *
211
+ * @param file - The path whose creation failed
212
+ * @param error - The fault the failed operation raised
213
+ * @returns True when the fault means the host refuses this name; false otherwise
214
+ *
215
+ * @example
216
+ * ```ts
217
+ * try {
218
+ * writeFileSync('tmp/probe/greeting\0.test.ts', '')
219
+ * } catch (error) {
220
+ * isRefusedName('tmp/probe/greeting\0.test.ts', error) // true
221
+ * }
222
+ * isRefusedName('tmp/probe/greeting.test.ts', new Error('the runtime stage was destroyed')) // false
223
+ * ```
224
+ */
225
+ export declare function isRefusedName(file: string, error: unknown): boolean;
226
+
190
227
  /**
191
228
  * Inspects virtual documents through one resident Oxlint language server.
192
229
  *
@@ -728,11 +765,12 @@ export declare function resolveWorkspaceModule(workspace: string, specifier: str
728
765
  * as a path string, into which the stage can install no overlay, and a specification path that
729
766
  * crosses a symbolic link, whose existing components this host cannot inspect, or whose directory
730
767
  * the target tree blocks probe from creating. An `origin: 'instrument'` issue names this package's
731
- * own machinery: a specification it could not write after its directory exists, or one it could
732
- * not run, evict, or delete, in each case for a reason the target tree does not own, and a run
733
- * that reported no test. An unmapped caller test and a test whose named project is absent are claimant failures with
734
- * `code: 'missing'`, thrown rather than reported, because a test that never ran is no evidence
735
- * about the candidate.
768
+ * own machinery: a specification it could not write after its directory exists and the host did not
769
+ * refuse the caller's own name, or one it could not run, evict, or delete, in each case for a
770
+ * reason the target tree does not own, and a run that reported no test. An unmapped caller test and
771
+ * a test whose named project is absent are claimant failures with `code: 'missing'`, and a declared
772
+ * test path the host refuses to create is a claimant failure with `code: 'refused'`. Each is thrown
773
+ * rather than reported, because a test that never ran is no evidence about the candidate.
736
774
  *
737
775
  * The write path's physical-containment guarantee covers the claim inputs and the target tree as
738
776
  * inspected. A concurrent process that mutates a path component between the final inspection and
@@ -106,6 +106,53 @@ function resolveWorkspaceFile(workspace, target, mutate = false) {
106
106
  return file;
107
107
  }
108
108
  /**
109
+ * Reports whether a fault means the host refuses the name a caller supplied for a file to create.
110
+ *
111
+ * @remarks
112
+ * A host reports one refusal with a code of its own and several unrelated conditions with one
113
+ * shared code, so the code alone does not separate a name the host will not accept from a file
114
+ * that is merely absent. `ENAMETOOLONG` names the refusal outright. `ERR_INVALID_ARG_VALUE` names
115
+ * every argument Node rejects, so it names the refusal only for a path carrying the NUL byte Node
116
+ * refuses; the same code arrives for a bad `flag` and a bad encoding, which say nothing about the
117
+ * name. An `ENOENT` raised while the parent stats as a directory names the refusal too, because an
118
+ * ordinary absent file beneath an existing directory would have been created instead; a Windows
119
+ * host reports an overlong component and a character its filesystem reserves that way. Measured on
120
+ * 2026-08-21 on Windows 11 with Node v24.18.1 over NTFS, `writeFileSync` reported `ENOENT` for a
121
+ * 300-character final component and never reported `ENAMETOOLONG`.
122
+ *
123
+ * This reads the fault the host reported rather than applying a length or character policy of its
124
+ * own, so a host that accepts a name another host refuses is not second-guessed and no platform
125
+ * branch decides the answer. Every read of the fault is guarded, so it is total over every value a
126
+ * published caller can supply: a proxy whose `has` trap throws, a value whose `code` getter throws,
127
+ * a fault carrying no code, and a parent this host cannot stat each report false.
128
+ *
129
+ * @param file - The path whose creation failed
130
+ * @param error - The fault the failed operation raised
131
+ * @returns True when the fault means the host refuses this name; false otherwise
132
+ *
133
+ * @example
134
+ * ```ts
135
+ * try {
136
+ * writeFileSync('tmp/probe/greeting\0.test.ts', '')
137
+ * } catch (error) {
138
+ * isRefusedName('tmp/probe/greeting\0.test.ts', error) // true
139
+ * }
140
+ * isRefusedName('tmp/probe/greeting.test.ts', new Error('the runtime stage was destroyed')) // false
141
+ * ```
142
+ */
143
+ function isRefusedName(file, error) {
144
+ if (typeof error !== "object" || error === null) return false;
145
+ const fault = error;
146
+ const reading = attempt(() => "code" in fault ? fault.code : void 0);
147
+ if (!reading.success) return false;
148
+ const code = reading.value;
149
+ if (code === "ENAMETOOLONG") return true;
150
+ if (code === "ERR_INVALID_ARG_VALUE") return file.includes("\0");
151
+ if (code !== "ENOENT") return false;
152
+ const parent = attempt(() => statSync(dirname(file), { throwIfNoEntry: false }));
153
+ return parent.success && parent.value !== void 0 && parent.value.isDirectory();
154
+ }
155
+ /**
109
156
  * Projects an absolute tool path into the workspace-relative form issues expose.
110
157
  *
111
158
  * @param workspace - The target workspace root
@@ -638,7 +685,7 @@ var Overlay = class {
638
685
  };
639
686
  //#endregion
640
687
  //#region package.json
641
- var version = "0.0.1";
688
+ var version = "0.0.2";
642
689
  var peerDependencies = {
643
690
  "oxlint": "^1.77.0",
644
691
  "typescript": "^6.0.0",
@@ -1026,11 +1073,12 @@ var LintStage = class {
1026
1073
  * as a path string, into which the stage can install no overlay, and a specification path that
1027
1074
  * crosses a symbolic link, whose existing components this host cannot inspect, or whose directory
1028
1075
  * the target tree blocks probe from creating. An `origin: 'instrument'` issue names this package's
1029
- * own machinery: a specification it could not write after its directory exists, or one it could
1030
- * not run, evict, or delete, in each case for a reason the target tree does not own, and a run
1031
- * that reported no test. An unmapped caller test and a test whose named project is absent are claimant failures with
1032
- * `code: 'missing'`, thrown rather than reported, because a test that never ran is no evidence
1033
- * about the candidate.
1076
+ * own machinery: a specification it could not write after its directory exists and the host did not
1077
+ * refuse the caller's own name, or one it could not run, evict, or delete, in each case for a
1078
+ * reason the target tree does not own, and a run that reported no test. An unmapped caller test and
1079
+ * a test whose named project is absent are claimant failures with `code: 'missing'`, and a declared
1080
+ * test path the host refuses to create is a claimant failure with `code: 'refused'`. Each is thrown
1081
+ * rather than reported, because a test that never ran is no evidence about the candidate.
1034
1082
  *
1035
1083
  * The write path's physical-containment guarantee covers the claim inputs and the target tree as
1036
1084
  * inspected. A concurrent process that mutates a path component between the final inspection and
@@ -1093,7 +1141,7 @@ var RuntimeStage = class {
1093
1141
  elapsed: Math.round(performance.now() - started),
1094
1142
  issues: [specification]
1095
1143
  };
1096
- const file = specification;
1144
+ const file = sep === "\\" ? normalizePath(specification) : specification;
1097
1145
  this.#specifications += 1;
1098
1146
  this.#revisions.add(file);
1099
1147
  let issues = [];
@@ -1243,9 +1291,11 @@ var RuntimeStage = class {
1243
1291
  }
1244
1292
  #specification(test) {
1245
1293
  let creating = false;
1294
+ let generated;
1246
1295
  const outcome = attempt(() => {
1247
1296
  const revision = `${process.pid}-${randomUUID()}`;
1248
1297
  const file = createRevisionFile(this.#workspace, test.path, revision);
1298
+ generated = file;
1249
1299
  const target = relative(this.#workspace, file);
1250
1300
  resolveWorkspaceFile(this.#workspace, target, true);
1251
1301
  creating = true;
@@ -1268,6 +1318,15 @@ var RuntimeStage = class {
1268
1318
  },
1269
1319
  cause: outcome.error.cause
1270
1320
  });
1321
+ if (!creating && generated !== void 0 && isRefusedName(generated, outcome.error)) throw new ProbeError(`The runtime stage cannot write a specification beside a test path the host refuses (${describeUnknown(outcome.error)})`, {
1322
+ origin: "claimant",
1323
+ code: "refused",
1324
+ context: {
1325
+ stage: this.stage,
1326
+ path: test.path
1327
+ },
1328
+ cause: outcome.error
1329
+ });
1271
1330
  return {
1272
1331
  origin: outcome.error instanceof ProbeError && outcome.error.origin === "workspace" || creating ? "workspace" : "instrument",
1273
1332
  path: test.path,
@@ -1366,8 +1425,9 @@ var RuntimeStage = class {
1366
1425
  for (const [path, digest] of modules) this.#modules.set(path, digest);
1367
1426
  }
1368
1427
  #invalidate(vitest, path) {
1369
- vitest.invalidateFile(path);
1370
- vitest.watcher.invalidates.add(path);
1428
+ const id = sep === "\\" ? normalizePath(path) : path;
1429
+ vitest.invalidateFile(id);
1430
+ vitest.watcher.invalidates.add(id);
1371
1431
  }
1372
1432
  #snapshot() {
1373
1433
  const modules = /* @__PURE__ */ new Map();
@@ -1717,8 +1777,9 @@ var TypeStage = class {
1717
1777
  const path = resolveWorkspaceFile(this.#workspace, project);
1718
1778
  const existing = this.#services.get(path);
1719
1779
  if (existing !== void 0) return existing;
1720
- const config = typescript.readConfigFile(path, typescript.sys.readFile);
1721
- if (config.error !== void 0) throw new ProbeError(typescript.flattenDiagnosticMessageText(config.error.messageText, "\n"), {
1780
+ const spelling = normalizePath(path);
1781
+ const config = typescript.readConfigFile(spelling, typescript.sys.readFile);
1782
+ if (config.error !== void 0) throw new ProbeError(this.#translate(typescript, config.error.messageText, path), {
1722
1783
  origin: "workspace",
1723
1784
  code: "malformed",
1724
1785
  context: {
@@ -1726,8 +1787,8 @@ var TypeStage = class {
1726
1787
  project
1727
1788
  }
1728
1789
  });
1729
- const parsed = typescript.parseJsonConfigFileContent(config.config, typescript.sys, dirname(path), void 0, path);
1730
- if (parsed.errors.length > 0) throw new ProbeError(typescript.flattenDiagnosticMessageText(parsed.errors[0]?.messageText ?? "", "\n"), {
1790
+ const parsed = typescript.parseJsonConfigFileContent(config.config, typescript.sys, dirname(spelling), void 0, spelling);
1791
+ if (parsed.errors.length > 0) throw new ProbeError(this.#translate(typescript, parsed.errors[0]?.messageText, path), {
1731
1792
  origin: "workspace",
1732
1793
  code: "malformed",
1733
1794
  context: {
@@ -1756,6 +1817,9 @@ var TypeStage = class {
1756
1817
  if (!this.#resident.has(path)) this.#recycle(path);
1757
1818
  return service;
1758
1819
  }
1820
+ #translate(typescript, message, path) {
1821
+ return normalizePath(typescript.flattenDiagnosticMessageText(message, "\n")).replaceAll(normalizePath(path), relativeWorkspaceFile(this.#workspace, path));
1822
+ }
1759
1823
  #configure(service, project) {
1760
1824
  const path = resolveWorkspaceFile(this.#workspace, project);
1761
1825
  if (this.#diagnostics.has(path)) return;
@@ -2276,7 +2340,7 @@ var ProbeServer = class {
2276
2340
  const tools = createToolManager();
2277
2341
  tools.add(createTool({
2278
2342
  name: "prove",
2279
- description: "Proves a claim with type, lint, and runtime evidence.",
2343
+ description: "Answers whether a TypeScript edit compiles, lints, and passes its test in this workspace — call it before relying on such a claim, instead of reasoning or writing a throwaway probe. Supply the edit you believe is correct and an edit that must break with the stage it breaks at; the closing line is a receipt only when the case ran clean and the control broke exactly at its declared stage, and no receipt otherwise. For a performance claim, measure before proving: write a guarded bench block — if (import.meta.env.MODE === 'benchmark') — beside the probe test, run the workspace bench script, and record only magnitudes; a settled ratio then proves here as an ordinary runtime claim.",
2280
2344
  parameters,
2281
2345
  execute: async (input) => {
2282
2346
  if (!isClaim(input)) {
@@ -2316,6 +2380,6 @@ var ProbeServer = class {
2316
2380
  }
2317
2381
  };
2318
2382
  //#endregion
2319
- export { LintStage, Overlay, Probe, ProbeServer, RuntimeStage, TypeStage, captureListeners, computeDigest, createRevisionFile, describeUnknown, findRefusedPaths, inferDocumentLanguage, inferTestProject, inferTypeProject, loadWorkspaceModule, matchesWorkspaceModule, normalizePath, normalizeValue, parseContentLength, readWorkspaceManifest, relativeWorkspaceFile, releaseListeners, resolveWorkspaceBinary, resolveWorkspaceFile, resolveWorkspaceModule };
2383
+ export { LintStage, Overlay, Probe, ProbeServer, RuntimeStage, TypeStage, captureListeners, computeDigest, createRevisionFile, describeUnknown, findRefusedPaths, inferDocumentLanguage, inferTestProject, inferTypeProject, isRefusedName, loadWorkspaceModule, matchesWorkspaceModule, normalizePath, normalizeValue, parseContentLength, readWorkspaceManifest, relativeWorkspaceFile, releaseListeners, resolveWorkspaceBinary, resolveWorkspaceFile, resolveWorkspaceModule };
2320
2384
 
2321
2385
  //# sourceMappingURL=index.js.map