@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.
- package/dist/src/server/index.cjs +164 -60
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +43 -5
- package/dist/src/server/index.d.ts +43 -5
- package/dist/src/server/index.js +164 -61
- package/dist/src/server/index.js.map +1 -1
- package/package.json +13 -11
|
@@ -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.
|
|
689
|
+
var version = "0.0.3";
|
|
643
690
|
var peerDependencies = {
|
|
644
|
-
"oxlint": "^1.
|
|
645
|
-
"typescript": "^6.0.
|
|
646
|
-
"vitest": "^4.1.
|
|
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
|
|
1031
|
-
* not run, evict, or delete, in each case for a
|
|
1032
|
-
*
|
|
1033
|
-
*
|
|
1034
|
-
*
|
|
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
|
-
|
|
1371
|
-
vitest.
|
|
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
|
|
1722
|
-
|
|
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)(
|
|
1731
|
-
if (parsed.errors.length > 0) throw new _src_core.ProbeError(typescript
|
|
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
|
|
1742
|
-
getCompilationSettings:
|
|
1743
|
-
getScriptFileNames:
|
|
1744
|
-
getScriptVersion:
|
|
1745
|
-
getScriptSnapshot:
|
|
1746
|
-
getCurrentDirectory: ()
|
|
1747
|
-
getDefaultLibFileName: (
|
|
1748
|
-
fileExists:
|
|
1749
|
-
readFile:
|
|
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:
|
|
1812
|
+
directoryExists: this.#directoryExists.bind(this, typescript),
|
|
1752
1813
|
getDirectories: typescript.sys.getDirectories,
|
|
1753
|
-
useCaseSensitiveFileNames: (
|
|
1754
|
-
getNewLine: (
|
|
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:
|
|
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:
|
|
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:
|
|
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: "
|
|
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:
|
|
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:
|
|
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;
|