@orkestrel/probe 0.0.1 → 0.0.3

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.
@@ -107,6 +107,53 @@ function resolveWorkspaceFile(workspace, target, mutate = false) {
107
107
  return file;
108
108
  }
109
109
  /**
110
+ * Reports whether a fault means the host refuses the name a caller supplied for a file to create.
111
+ *
112
+ * @remarks
113
+ * A host reports one refusal with a code of its own and several unrelated conditions with one
114
+ * shared code, so the code alone does not separate a name the host will not accept from a file
115
+ * that is merely absent. `ENAMETOOLONG` names the refusal outright. `ERR_INVALID_ARG_VALUE` names
116
+ * every argument Node rejects, so it names the refusal only for a path carrying the NUL byte Node
117
+ * refuses; the same code arrives for a bad `flag` and a bad encoding, which say nothing about the
118
+ * name. An `ENOENT` raised while the parent stats as a directory names the refusal too, because an
119
+ * ordinary absent file beneath an existing directory would have been created instead; a Windows
120
+ * host reports an overlong component and a character its filesystem reserves that way. Measured on
121
+ * 2026-08-21 on Windows 11 with Node v24.18.1 over NTFS, `writeFileSync` reported `ENOENT` for a
122
+ * 300-character final component and never reported `ENAMETOOLONG`.
123
+ *
124
+ * This reads the fault the host reported rather than applying a length or character policy of its
125
+ * own, so a host that accepts a name another host refuses is not second-guessed and no platform
126
+ * branch decides the answer. Every read of the fault is guarded, so it is total over every value a
127
+ * published caller can supply: a proxy whose `has` trap throws, a value whose `code` getter throws,
128
+ * a fault carrying no code, and a parent this host cannot stat each report false.
129
+ *
130
+ * @param file - The path whose creation failed
131
+ * @param error - The fault the failed operation raised
132
+ * @returns True when the fault means the host refuses this name; false otherwise
133
+ *
134
+ * @example
135
+ * ```ts
136
+ * try {
137
+ * writeFileSync('tmp/probe/greeting\0.test.ts', '')
138
+ * } catch (error) {
139
+ * isRefusedName('tmp/probe/greeting\0.test.ts', error) // true
140
+ * }
141
+ * isRefusedName('tmp/probe/greeting.test.ts', new Error('the runtime stage was destroyed')) // false
142
+ * ```
143
+ */
144
+ function isRefusedName(file, error) {
145
+ if (typeof error !== "object" || error === null) return false;
146
+ const fault = error;
147
+ const reading = (0, _orkestrel_contract.attempt)(() => "code" in fault ? fault.code : void 0);
148
+ if (!reading.success) return false;
149
+ const code = reading.value;
150
+ if (code === "ENAMETOOLONG") return true;
151
+ if (code === "ERR_INVALID_ARG_VALUE") return file.includes("\0");
152
+ if (code !== "ENOENT") return false;
153
+ const parent = (0, _orkestrel_contract.attempt)(() => (0, node_fs.statSync)((0, node_path.dirname)(file), { throwIfNoEntry: false }));
154
+ return parent.success && parent.value !== void 0 && parent.value.isDirectory();
155
+ }
156
+ /**
110
157
  * Projects an absolute tool path into the workspace-relative form issues expose.
111
158
  *
112
159
  * @param workspace - The target workspace root
@@ -639,11 +686,11 @@ var Overlay = class {
639
686
  };
640
687
  //#endregion
641
688
  //#region package.json
642
- var version = "0.0.1";
689
+ var version = "0.0.3";
643
690
  var peerDependencies = {
644
- "oxlint": "^1.77.0",
645
- "typescript": "^6.0.0",
646
- "vitest": "^4.1.0"
691
+ "oxlint": "^1.79.0",
692
+ "typescript": "^6.0.3",
693
+ "vitest": "^4.1.11"
647
694
  };
648
695
  //#endregion
649
696
  //#region src/server/stages/LintStage.ts
@@ -1027,11 +1074,12 @@ var LintStage = class {
1027
1074
  * as a path string, into which the stage can install no overlay, and a specification path that
1028
1075
  * crosses a symbolic link, whose existing components this host cannot inspect, or whose directory
1029
1076
  * the target tree blocks probe from creating. An `origin: 'instrument'` issue names this package's
1030
- * own machinery: a specification it could not write after its directory exists, or one it could
1031
- * not run, evict, or delete, in each case for a reason the target tree does not own, and a run
1032
- * that reported no test. An unmapped caller test and a test whose named project is absent are claimant failures with
1033
- * `code: 'missing'`, thrown rather than reported, because a test that never ran is no evidence
1034
- * about the candidate.
1077
+ * own machinery: a specification it could not write after its directory exists and the host did not
1078
+ * refuse the caller's own name, or one it could not run, evict, or delete, in each case for a
1079
+ * reason the target tree does not own, and a run that reported no test. An unmapped caller test and
1080
+ * a test whose named project is absent are claimant failures with `code: 'missing'`, and a declared
1081
+ * test path the host refuses to create is a claimant failure with `code: 'refused'`. Each is thrown
1082
+ * rather than reported, because a test that never ran is no evidence about the candidate.
1035
1083
  *
1036
1084
  * The write path's physical-containment guarantee covers the claim inputs and the target tree as
1037
1085
  * inspected. A concurrent process that mutates a path component between the final inspection and
@@ -1094,7 +1142,7 @@ var RuntimeStage = class {
1094
1142
  elapsed: Math.round(performance.now() - started),
1095
1143
  issues: [specification]
1096
1144
  };
1097
- const file = specification;
1145
+ const file = node_path.sep === "\\" ? normalizePath(specification) : specification;
1098
1146
  this.#specifications += 1;
1099
1147
  this.#revisions.add(file);
1100
1148
  let issues = [];
@@ -1244,9 +1292,11 @@ var RuntimeStage = class {
1244
1292
  }
1245
1293
  #specification(test) {
1246
1294
  let creating = false;
1295
+ let generated;
1247
1296
  const outcome = (0, _orkestrel_contract.attempt)(() => {
1248
1297
  const revision = `${process.pid}-${(0, node_crypto.randomUUID)()}`;
1249
1298
  const file = createRevisionFile(this.#workspace, test.path, revision);
1299
+ generated = file;
1250
1300
  const target = (0, node_path.relative)(this.#workspace, file);
1251
1301
  resolveWorkspaceFile(this.#workspace, target, true);
1252
1302
  creating = true;
@@ -1269,6 +1319,15 @@ var RuntimeStage = class {
1269
1319
  },
1270
1320
  cause: outcome.error.cause
1271
1321
  });
1322
+ if (!creating && generated !== void 0 && isRefusedName(generated, outcome.error)) throw new _src_core.ProbeError(`The runtime stage cannot write a specification beside a test path the host refuses (${describeUnknown(outcome.error)})`, {
1323
+ origin: "claimant",
1324
+ code: "refused",
1325
+ context: {
1326
+ stage: this.stage,
1327
+ path: test.path
1328
+ },
1329
+ cause: outcome.error
1330
+ });
1272
1331
  return {
1273
1332
  origin: outcome.error instanceof _src_core.ProbeError && outcome.error.origin === "workspace" || creating ? "workspace" : "instrument",
1274
1333
  path: test.path,
@@ -1367,8 +1426,9 @@ var RuntimeStage = class {
1367
1426
  for (const [path, digest] of modules) this.#modules.set(path, digest);
1368
1427
  }
1369
1428
  #invalidate(vitest, path) {
1370
- vitest.invalidateFile(path);
1371
- vitest.watcher.invalidates.add(path);
1429
+ const id = node_path.sep === "\\" ? normalizePath(path) : path;
1430
+ vitest.invalidateFile(id);
1431
+ vitest.watcher.invalidates.add(id);
1372
1432
  }
1373
1433
  #snapshot() {
1374
1434
  const modules = /* @__PURE__ */ new Map();
@@ -1718,8 +1778,9 @@ var TypeStage = class {
1718
1778
  const path = resolveWorkspaceFile(this.#workspace, project);
1719
1779
  const existing = this.#services.get(path);
1720
1780
  if (existing !== void 0) return existing;
1721
- const config = typescript.readConfigFile(path, typescript.sys.readFile);
1722
- if (config.error !== void 0) throw new _src_core.ProbeError(typescript.flattenDiagnosticMessageText(config.error.messageText, "\n"), {
1781
+ const spelling = normalizePath(path);
1782
+ const config = typescript.readConfigFile(spelling, typescript.sys.readFile);
1783
+ if (config.error !== void 0) throw new _src_core.ProbeError(this.#translate(typescript, config.error.messageText, path), {
1723
1784
  origin: "workspace",
1724
1785
  code: "malformed",
1725
1786
  context: {
@@ -1727,8 +1788,8 @@ var TypeStage = class {
1727
1788
  project
1728
1789
  }
1729
1790
  });
1730
- const parsed = typescript.parseJsonConfigFileContent(config.config, typescript.sys, (0, node_path.dirname)(path), void 0, path);
1731
- if (parsed.errors.length > 0) throw new _src_core.ProbeError(typescript.flattenDiagnosticMessageText(parsed.errors[0]?.messageText ?? "", "\n"), {
1791
+ const parsed = typescript.parseJsonConfigFileContent(config.config, typescript.sys, (0, node_path.dirname)(spelling), void 0, spelling);
1792
+ if (parsed.errors.length > 0) throw new _src_core.ProbeError(this.#translate(typescript, parsed.errors[0]?.messageText, path), {
1732
1793
  origin: "workspace",
1733
1794
  code: "malformed",
1734
1795
  context: {
@@ -1738,25 +1799,56 @@ var TypeStage = class {
1738
1799
  });
1739
1800
  this.#options.set(path, parsed.options);
1740
1801
  this.#files.set(path, parsed.fileNames);
1741
- const service = typescript.createLanguageService({
1742
- getCompilationSettings: () => this.#options.get(path) ?? {},
1743
- getScriptFileNames: () => [...this.#files.get(path) ?? [], ...this.#overlay.paths],
1744
- getScriptVersion: (file) => this.#version(file),
1745
- getScriptSnapshot: (file) => this.#snapshot(typescript, file),
1746
- getCurrentDirectory: () => this.#workspace,
1747
- getDefaultLibFileName: (options) => typescript.getDefaultLibFilePath(options),
1748
- fileExists: (file) => this.#overlay.text(file) !== void 0 || typescript.sys.fileExists(file),
1749
- readFile: (file) => this.#overlay.text(file) ?? typescript.sys.readFile(file),
1802
+ const host = {
1803
+ getCompilationSettings: this.#compilationSettings.bind(this, path),
1804
+ getScriptFileNames: this.#scriptFiles.bind(this, path),
1805
+ getScriptVersion: this.#version.bind(this),
1806
+ getScriptSnapshot: this.#snapshot.bind(this, typescript),
1807
+ getCurrentDirectory: this.#directory.bind(this),
1808
+ getDefaultLibFileName: this.#defaultLibrary.bind(this, typescript),
1809
+ fileExists: this.#fileExists.bind(this, typescript),
1810
+ readFile: this.#readFile.bind(this, typescript),
1750
1811
  readDirectory: typescript.sys.readDirectory,
1751
- directoryExists: (directory) => typescript.sys.directoryExists(directory) || this.#overlay.covers(directory),
1812
+ directoryExists: this.#directoryExists.bind(this, typescript),
1752
1813
  getDirectories: typescript.sys.getDirectories,
1753
- useCaseSensitiveFileNames: () => typescript.sys.useCaseSensitiveFileNames,
1754
- getNewLine: () => typescript.sys.newLine
1755
- });
1814
+ useCaseSensitiveFileNames: this.#caseSensitive.bind(this, typescript),
1815
+ getNewLine: this.#newline.bind(this, typescript)
1816
+ };
1817
+ const service = typescript.createLanguageService(host);
1756
1818
  this.#services.set(path, service);
1757
1819
  if (!this.#resident.has(path)) this.#recycle(path);
1758
1820
  return service;
1759
1821
  }
1822
+ #compilationSettings(path) {
1823
+ return this.#options.get(path) ?? {};
1824
+ }
1825
+ #scriptFiles(path) {
1826
+ return [...this.#files.get(path) ?? [], ...this.#overlay.paths];
1827
+ }
1828
+ #directory() {
1829
+ return this.#workspace;
1830
+ }
1831
+ #defaultLibrary(typescript, options) {
1832
+ return typescript.getDefaultLibFilePath(options);
1833
+ }
1834
+ #fileExists(typescript, file) {
1835
+ return this.#overlay.text(file) !== void 0 || typescript.sys.fileExists(file);
1836
+ }
1837
+ #readFile(typescript, file) {
1838
+ return this.#overlay.text(file) ?? typescript.sys.readFile(file);
1839
+ }
1840
+ #directoryExists(typescript, directory) {
1841
+ return typescript.sys.directoryExists(directory) || this.#overlay.covers(directory);
1842
+ }
1843
+ #caseSensitive(typescript) {
1844
+ return typescript.sys.useCaseSensitiveFileNames;
1845
+ }
1846
+ #newline(typescript) {
1847
+ return typescript.sys.newLine;
1848
+ }
1849
+ #translate(typescript, message, path) {
1850
+ return normalizePath(typescript.flattenDiagnosticMessageText(message, "\n")).replaceAll(normalizePath(path), relativeWorkspaceFile(this.#workspace, path));
1851
+ }
1760
1852
  #configure(service, project) {
1761
1853
  const path = resolveWorkspaceFile(this.#workspace, project);
1762
1854
  if (this.#diagnostics.has(path)) return;
@@ -1883,17 +1975,17 @@ var Probe = class {
1883
1975
  this.#typeQueue = (0, _orkestrel_queue.createQueue)({
1884
1976
  concurrency: 1,
1885
1977
  retries: 0,
1886
- handler: (inspection) => this.#inspectStage(this.#type, this.#type.progress, this.#type.inspect(inspection.subject, inspection.claim.project), inspection.claim)
1978
+ handler: this.#inspectType.bind(this)
1887
1979
  });
1888
1980
  this.#lintQueue = (0, _orkestrel_queue.createQueue)({
1889
1981
  concurrency: 1,
1890
1982
  retries: 0,
1891
- handler: (inspection) => this.#inspectStage(this.#lint, this.#lint.progress, this.#lint.inspect(inspection.subject), inspection.claim)
1983
+ handler: this.#inspectLint.bind(this)
1892
1984
  });
1893
1985
  this.#runtimeQueue = (0, _orkestrel_queue.createQueue)({
1894
1986
  concurrency: 1,
1895
1987
  retries: 0,
1896
- handler: (inspection) => this.#inspectStage(this.#runtime, this.#runtime.progress, this.#runtime.inspect(inspection.subject), inspection.claim)
1988
+ handler: this.#inspectRuntime.bind(this)
1897
1989
  });
1898
1990
  this.#arming = this.#arm();
1899
1991
  this.#arming.catch(() => {});
@@ -2093,6 +2185,15 @@ var Probe = class {
2093
2185
  for (const pending of admitted) pending.catch(() => {});
2094
2186
  return Promise.all(admitted);
2095
2187
  }
2188
+ #inspectType(inspection) {
2189
+ return this.#inspectStage(this.#type, this.#type.progress, this.#type.inspect(inspection.subject, inspection.claim.project), inspection.claim);
2190
+ }
2191
+ #inspectLint(inspection) {
2192
+ return this.#inspectStage(this.#lint, this.#lint.progress, this.#lint.inspect(inspection.subject), inspection.claim);
2193
+ }
2194
+ #inspectRuntime(inspection) {
2195
+ return this.#inspectStage(this.#runtime, this.#runtime.progress, this.#runtime.inspect(inspection.subject), inspection.claim);
2196
+ }
2096
2197
  async #inspectStage(stage, progress, operation, claim) {
2097
2198
  const timeout = (0, _orkestrel_timeout.createTimeout)({ ms: this.#deadline });
2098
2199
  timeout.start();
@@ -2277,19 +2378,9 @@ var ProbeServer = class {
2277
2378
  const tools = (0, _orkestrel_tool.createToolManager)();
2278
2379
  tools.add((0, _orkestrel_tool.createTool)({
2279
2380
  name: "prove",
2280
- description: "Proves a claim with type, lint, and runtime evidence.",
2381
+ 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.",
2281
2382
  parameters,
2282
- execute: async (input) => {
2283
- if (!(0, _src_core.isClaim)(input)) {
2284
- const refused = findRefusedPaths(input);
2285
- throw new _src_core.ProbeError(refused.length === 0 ? "The prove tool requires a claim matching the advertised schema" : `The prove tool refuses ${refused.join(", ")}: a draft path must stay inside the workspace, which the advertised schema does not constrain`, {
2286
- origin: "claimant",
2287
- code: "refused",
2288
- context: { value: input }
2289
- });
2290
- }
2291
- return this.#probe.prove(input);
2292
- }
2383
+ execute: this.#prove.bind(this)
2293
2384
  }));
2294
2385
  return (0, _orkestrel_mcp.createMCPServer)({
2295
2386
  identity: {
@@ -2297,24 +2388,36 @@ var ProbeServer = class {
2297
2388
  version
2298
2389
  },
2299
2390
  tools,
2300
- execution: async ({ call }) => {
2301
- const result = await tools.execute(call);
2302
- if (!result.success) return result;
2303
- if (!(0, _src_core.isVerdict)(result.value)) throw new _src_core.ProbeError("The prove tool returned an invalid verdict", {
2304
- origin: "instrument",
2305
- code: "malformed",
2306
- context: { value: result.value }
2307
- });
2308
- return {
2309
- resultType: "complete",
2310
- content: [{
2311
- type: "text",
2312
- text: (0, _src_core.formatVerdict)(result.value)
2313
- }]
2314
- };
2315
- }
2391
+ execution: this.#execute.bind(this)
2316
2392
  });
2317
2393
  }
2394
+ async #prove(input) {
2395
+ if (!(0, _src_core.isClaim)(input)) {
2396
+ const refused = findRefusedPaths(input);
2397
+ throw new _src_core.ProbeError(refused.length === 0 ? "The prove tool requires a claim matching the advertised schema" : `The prove tool refuses ${refused.join(", ")}: a draft path must stay inside the workspace, which the advertised schema does not constrain`, {
2398
+ origin: "claimant",
2399
+ code: "refused",
2400
+ context: { value: input }
2401
+ });
2402
+ }
2403
+ return this.#probe.prove(input);
2404
+ }
2405
+ async #execute(context) {
2406
+ const result = await context.tools.execute(context.call);
2407
+ if (!result.success) return result;
2408
+ if (!(0, _src_core.isVerdict)(result.value)) throw new _src_core.ProbeError("The prove tool returned an invalid verdict", {
2409
+ origin: "instrument",
2410
+ code: "malformed",
2411
+ context: { value: result.value }
2412
+ });
2413
+ return {
2414
+ resultType: "complete",
2415
+ content: [{
2416
+ type: "text",
2417
+ text: (0, _src_core.formatVerdict)(result.value)
2418
+ }]
2419
+ };
2420
+ }
2318
2421
  };
2319
2422
  //#endregion
2320
2423
  exports.LintStage = LintStage;
@@ -2331,6 +2434,7 @@ exports.findRefusedPaths = findRefusedPaths;
2331
2434
  exports.inferDocumentLanguage = inferDocumentLanguage;
2332
2435
  exports.inferTestProject = inferTestProject;
2333
2436
  exports.inferTypeProject = inferTypeProject;
2437
+ exports.isRefusedName = isRefusedName;
2334
2438
  exports.loadWorkspaceModule = loadWorkspaceModule;
2335
2439
  exports.matchesWorkspaceModule = matchesWorkspaceModule;
2336
2440
  exports.normalizePath = normalizePath;