@orkestrel/probe 0.0.5 → 0.0.8
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/core/index.cjs +12 -4
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +31 -12
- package/dist/src/core/index.d.ts +31 -12
- package/dist/src/core/index.js +12 -4
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +155 -291
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +80 -24
- package/dist/src/server/index.d.ts +80 -24
- package/dist/src/server/index.js +157 -292
- package/dist/src/server/index.js.map +1 -1
- package/package.json +6 -5
package/dist/src/core/index.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
let _orkestrel_contract = require("@orkestrel/contract");
|
|
3
|
+
let _orkestrel_lsp = require("@orkestrel/lsp");
|
|
3
4
|
//#region src/core/constants.ts
|
|
4
5
|
/**
|
|
5
6
|
* Lists the stages a claim passes through, in the order a verdict reports them.
|
|
@@ -336,8 +337,8 @@ var isIssue = (0, _orkestrel_contract.recordOf)({
|
|
|
336
337
|
origin: isParty,
|
|
337
338
|
path: _orkestrel_contract.isString,
|
|
338
339
|
message: _orkestrel_contract.isString,
|
|
339
|
-
|
|
340
|
-
}, ["
|
|
340
|
+
range: _orkestrel_lsp.isLSPRange
|
|
341
|
+
}, ["range"]);
|
|
341
342
|
/**
|
|
342
343
|
* Checks whether a value carries one stage's outcome.
|
|
343
344
|
*
|
|
@@ -425,6 +426,12 @@ var isVerdict = (0, _orkestrel_contract.recordOf)({
|
|
|
425
426
|
/**
|
|
426
427
|
* Renders one tool message as a single line an agent can classify and locate.
|
|
427
428
|
*
|
|
429
|
+
* @remarks
|
|
430
|
+
* `Issue.range` is stored zero-based, and this is the only place it is rendered, so the one-based
|
|
431
|
+
* line an editor shows is derived here and nowhere else. The rendered location carries the line
|
|
432
|
+
* alone: a reader opens a file at a line, and the column would widen every issue line in every
|
|
433
|
+
* verdict for a precision that address does not use.
|
|
434
|
+
*
|
|
428
435
|
* @param issue - The message and location a stage reported
|
|
429
436
|
* @returns The bracketed origin, location, and message, separated by spaces
|
|
430
437
|
*
|
|
@@ -434,7 +441,7 @@ var isVerdict = (0, _orkestrel_contract.recordOf)({
|
|
|
434
441
|
* origin: 'claimant',
|
|
435
442
|
* path: 'src/core/greeting.ts',
|
|
436
443
|
* message: 'not assignable',
|
|
437
|
-
* line:
|
|
444
|
+
* range: { start: { line: 0, character: 6 }, end: { line: 0, character: 13 } },
|
|
438
445
|
* }
|
|
439
446
|
* const whole: Issue = {
|
|
440
447
|
* origin: 'claimant',
|
|
@@ -446,7 +453,8 @@ var isVerdict = (0, _orkestrel_contract.recordOf)({
|
|
|
446
453
|
* ```
|
|
447
454
|
*/
|
|
448
455
|
function formatIssue(issue) {
|
|
449
|
-
const
|
|
456
|
+
const range = issue.range;
|
|
457
|
+
const where = range === void 0 ? issue.path : `${issue.path}:${range.start.line + 1}`;
|
|
450
458
|
return `[${issue.origin}] ${where} ${issue.message}`;
|
|
451
459
|
}
|
|
452
460
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../../../src/core/constants.ts","../../../src/core/errors.ts","../../../src/core/validators.ts","../../../src/core/helpers.ts","../../../src/core/shapers.ts"],"sourcesContent":["/**\n * Lists the stages a claim passes through, in the order a verdict reports them.\n *\n * @remarks\n * One list feeds the `Stage` type, the wire shape, the stage guard, and the receipt computation, so\n * a stage cannot be admitted by one and refused by another. The type derives from this list rather\n * than standing beside it, so a stage the list omits is a stage no member of the union names.\n *\n * @example\n * ```ts\n * PROBE_STAGES // ['type', 'lint', 'runtime']\n * ```\n */\nexport const PROBE_STAGES = Object.freeze(['type', 'lint', 'runtime'] as const)\n\n/**\n * Lists the parties that can own action on an issue or probe failure.\n *\n * @remarks\n * One list feeds the `Party` type and its guard, so a party cannot be admitted by one and refused\n * by another.\n *\n * @example\n * ```ts\n * PROBE_PARTIES // ['claimant', 'workspace', 'instrument']\n * ```\n */\nexport const PROBE_PARTIES = Object.freeze(['claimant', 'workspace', 'instrument'] as const)\n\n/**\n * Lists the conditions that can end a probe operation.\n *\n * @remarks\n * One list feeds the code union and the error guard, so a condition cannot be constructed by one\n * and refused by the other. The guard reads this list rather than the class, which is what lets it\n * refuse a lookalike carrying a condition this package never declared.\n *\n * @example\n * ```ts\n * PROBE_ERROR_CODES // ['refused', 'missing', 'malformed', 'destroyed', 'deadline']\n * ```\n */\nexport const PROBE_ERROR_CODES = Object.freeze([\n\t'refused',\n\t'missing',\n\t'malformed',\n\t'destroyed',\n\t'deadline',\n] as const)\n\n/**\n * Names the leading token every receipt carries.\n *\n * @remarks\n * A receipt travels away from the verdict that minted it — an agent pastes it into the promotion\n * action — so it names itself rather than relying on where it was found.\n *\n * @example\n * ```ts\n * const receipt =\n * \t'probe:6ca20c3bff623031d3955b9d1a76d71d:type:typescript@6.0.3:oxlint@1.79.0:vitest@4.1.11:configs/src/tsconfig.core.json@3b674fdf121c85efb9ed1bab25ceeec8'\n * receipt.startsWith(RECEIPT_PREFIX) // true\n * ```\n */\nexport const RECEIPT_PREFIX = 'probe'\n\n/**\n * Names the character joining a receipt's tokens.\n *\n * @remarks\n * A project path can contain this character, so the project field goes last and a reader rejoins\n * every field from index 6 onward with this separator rather than expecting a fixed count.\n *\n * @example\n * ```ts\n * const receipt =\n * \t'probe:6ca20c3bff623031d3955b9d1a76d71d:type:typescript@6.0.3:oxlint@1.79.0:vitest@4.1.11:configs/src/tsconfig.core.json@3b674fdf121c85efb9ed1bab25ceeec8'\n * receipt.split(RECEIPT_SEPARATOR).length // 7\n * ```\n */\nexport const RECEIPT_SEPARATOR = ':'\n","import type { Party, ProbeErrorCode, ProbeErrorContext, ProbeErrorOptions } from './types.js'\nimport { holds, isError } from '@orkestrel/contract'\nimport { PROBE_ERROR_CODES, PROBE_PARTIES } from './constants.js'\n\n/**\n * Reports one probe failure under stable ownership and condition axes.\n *\n * @remarks\n * Every failure raised while serving a claim arrives as one of these, so a consumer catching from\n * `prove`, from a stage, or from a workspace leaf branches on {@link Party} and\n * {@link ProbeErrorCode} rather than on message text. Narrow a caught value with\n * {@link isProbeError} before reading either member. A pure leaf called directly with a value\n * `isClaim` would refuse raises the host's own fault unchanged, because those leaves are written to\n * be total over a validated claim rather than over every value.\n *\n * @example\n * ```ts\n * const error = new ProbeError('Path escapes the workspace: ../secrets.env', {\n * \torigin: 'claimant',\n * \tcode: 'refused',\n * \tcontext: { path: '../secrets.env' },\n * })\n * error.origin // 'claimant'\n * error.code // 'refused'\n * error.context?.path // '../secrets.env'\n * ```\n */\nexport class ProbeError extends Error {\n\toverride readonly name = 'ProbeError'\n\treadonly origin: Party\n\treadonly code: ProbeErrorCode\n\treadonly context?: ProbeErrorContext\n\n\t/**\n\t * Creates one classified probe failure.\n\t *\n\t * @param message - Human-readable description of what failed\n\t * @param options - The ownership, condition, optional structured context, and optional cause\n\t */\n\tconstructor(message: string, options: ProbeErrorOptions) {\n\t\tconst cause = options.cause\n\t\tsuper(message, cause === undefined ? undefined : { cause })\n\t\t// A global own-property brand rather than the constructor identity, because a consumer that\n\t\t// installs this package twice, or loads its ESM and CommonJS builds together, holds two\n\t\t// classes and `instanceof` refuses the other one's error.\n\t\tObject.defineProperty(this, Symbol.for('@orkestrel/probe.error'), { value: true })\n\t\tthis.origin = options.origin\n\t\tthis.code = options.code\n\t\tif (options.context !== undefined) this.context = options.context\n\t}\n}\n\n/**\n * Checks whether an unknown value is a {@link ProbeError}.\n *\n * @remarks\n * Recognition combines the global own-property brand with the native `Error` base, the subclass\n * prototype, the fixed name, a party {@link PROBE_PARTIES} declares, and a condition\n * {@link PROBE_ERROR_CODES} declares. The brand carries recognition across duplicate installations\n * and across an ESM and a CommonJS copy of this package, where the two classes are different values.\n * A plain `Error`, a property-only lookalike, and a branded value carrying an undeclared origin or\n * condition all stay outside the type.\n *\n * @param value - The value to inspect\n * @returns True only for a `ProbeError` instance; false otherwise\n *\n * @example\n * ```ts\n * isProbeError(createDestroyedError('probe')) // true\n * isProbeError(new Error('The probe has been destroyed')) // false\n * ```\n */\nexport function isProbeError(value: unknown): value is ProbeError {\n\tif (!isError(value)) return false\n\treturn holds(() => {\n\t\tif (Object.getPrototypeOf(value) === Error.prototype) return false\n\t\tif (value.name !== 'ProbeError' || !('origin' in value) || !('code' in value)) return false\n\t\tconst descriptor = Object.getOwnPropertyDescriptor(value, Symbol.for('@orkestrel/probe.error'))\n\t\tif (descriptor?.value !== true) return false\n\t\tconst origin: unknown = value.origin\n\t\tconst code: unknown = value.code\n\t\treturn (\n\t\t\tPROBE_PARTIES.some((declared) => declared === origin) &&\n\t\t\tPROBE_ERROR_CODES.some((declared) => declared === code)\n\t\t)\n\t})\n}\n\n/**\n * Creates the failure raised when an instrument is used after it was torn down.\n *\n * @remarks\n * Every resident entity in this package refuses this way, so the subject names which one in the\n * message and the condition is the same for all of them. A caller that meets this builds a\n * replacement rather than retrying: teardown is permanent.\n *\n * @param subject - The torn-down entity, named as the message names it, such as `type stage`\n * @returns A typed teardown failure\n *\n * @example\n * ```ts\n * createDestroyedError('type stage').message // 'The type stage has been destroyed'\n * createDestroyedError('probe').code // 'destroyed'\n * ```\n */\nexport function createDestroyedError(subject: string): ProbeError {\n\treturn new ProbeError(`The ${subject} has been destroyed`, {\n\t\torigin: 'claimant',\n\t\tcode: 'destroyed',\n\t})\n}\n","import type { Guard } from '@orkestrel/contract'\nimport type {\n\tCase,\n\tCheck,\n\tClaim,\n\tControl,\n\tDraft,\n\tIssue,\n\tParty,\n\tProject,\n\tStage,\n\tToolchain,\n\tVerdict,\n} from './types.js'\nimport {\n\tandOf,\n\tarrayOf,\n\tisNonEmptyString,\n\tisNumber,\n\tisString,\n\tliteralOf,\n\trecordOf,\n} from '@orkestrel/contract'\nimport { PROBE_PARTIES, PROBE_STAGES } from './constants.js'\n\n/**\n * Checks whether a value names a stage.\n *\n * @param value - The value to check\n * @returns True if the value is a stage name; false otherwise\n *\n * @example\n * ```ts\n * isStage('lint') // true\n * isStage('typecheck') // false\n * ```\n */\nexport const isStage: Guard<Stage> = literalOf(PROBE_STAGES)\n\n/**\n * Checks whether a value carries a proposed file's path and text.\n *\n * @remarks\n * Rejects an empty, absolute, or workspace-escaping path. A contained relative path may contain\n * `.` or may traverse to a parent that remains inside the workspace. Every stage resolves a file\n * by that path, so admitting an escape here would defer the refusal until a stage had already\n * accepted the draft. An `Issue` carries no such minimum because this package produces issues\n * rather than admits them.\n *\n * @param value - The value to check\n * @returns True if the value is a draft; false otherwise\n *\n * @example\n * ```ts\n * isDraft({ path: 'src/core/greeting.ts', text: 'export const GREETING = \"hi\"\\n' }) // true\n * isDraft({ path: '../../etc/hosts', text: '' }) // false\n * isDraft({ path: 'src/core/greeting.ts' }) // false\n * ```\n */\nexport const isDraft: Guard<Draft> = recordOf({\n\tpath: andOf<string>(isNonEmptyString, (path) => {\n\t\tif (/^(?:[\\\\/]|[A-Za-z]:)/.test(path)) return false\n\t\tlet depth = 0\n\t\tfor (const segment of path.split(/[\\\\/]+/)) {\n\t\t\tif (segment === '' || segment === '.') continue\n\t\t\tif (segment !== '..') {\n\t\t\t\tdepth += 1\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif (depth === 0) return false\n\t\t\tdepth -= 1\n\t\t}\n\t\treturn true\n\t}),\n\ttext: isString,\n})\n\n/**\n * Checks whether a value carries a claim's candidate drafts and its test.\n *\n * @param value - The value to check\n * @returns True if the value is a case; false otherwise\n *\n * @example\n * ```ts\n * const test = { path: 'tests/src/core/greeting.test.ts', text: 'test(\"greets\", () => {})\\n' }\n * isCase({ files: [], test }) // true\n * ```\n */\nexport const isCase: Guard<Case> = recordOf({ files: arrayOf(isDraft), test: isDraft })\n\n/**\n * Checks whether a value carries a case plus the stage and reason it must fail for.\n *\n * @param value - The value to check\n * @returns True if the value is a control; false otherwise\n *\n * @example\n * ```ts\n * const test = { path: 'tests/src/core/greeting.test.ts', text: 'test(\"greets\", () => {})\\n' }\n * isControl({ files: [], test, stage: 'type', reason: 'must not compile' }) // true\n * isControl({ files: [], test }) // false\n * ```\n */\nexport const isControl: Guard<Control> = recordOf({\n\tfiles: arrayOf(isDraft),\n\ttest: isDraft,\n\tstage: isStage,\n\treason: isNonEmptyString,\n})\n\n/**\n * Checks whether a value carries a project, a case, and a control.\n *\n * @remarks\n * Exact rather than open: a claim is this package's own record, so an unknown member is a caller\n * sending a contract this service does not implement rather than a wider caller it must tolerate.\n *\n * `CLAIM_SHAPE` is the wire contract's shape and this is the admission rule the tool enforces, and\n * the rule is strictly narrower than the shape. They agree on every member but `Draft.path`: the\n * shape constrains it to a non-empty string, while `isDraft` also refuses an absolute path\n * and one that traverses out of the workspace, so `../../etc/hosts` satisfies\n * `compileGuard(CLAIM_SHAPE)` and is refused here. `ProbeServer` advertises the shape and enforces\n * this rule, so it names the refused member rather than reporting only that the claim was invalid.\n *\n * @param value - The value to check\n * @returns True if the value is a claim; false otherwise\n *\n * @example\n * ```ts\n * isClaim({ project: 'tsconfig.json', case: subject, control }) // true\n * isClaim({ project: 'tsconfig.json', case: subject }) // false\n * ```\n */\nexport const isClaim: Guard<Claim> = recordOf({\n\tproject: isNonEmptyString,\n\tcase: isCase,\n\tcontrol: isControl,\n})\n\n/**\n * Checks whether a value names a party an issue carries.\n *\n * @param value - The value to check\n * @returns True if the value is a party; false otherwise\n *\n * @example\n * ```ts\n * isParty('claimant') // true\n * isParty('workspace') // true\n * isParty('instrument') // true\n * isParty('stage') // false\n * ```\n */\nexport const isParty: Guard<Party> = literalOf(PROBE_PARTIES)\n\n/**\n * Checks whether a value carries one message, its location, and the origin of the fault it names.\n *\n * @remarks\n * `origin` is required rather than optional. Every issue this package produces sets it, and a\n * receipt turns on it: a control's issue disproves the claim only when its origin names the\n * claimant.\n *\n * @param value - The value to check\n * @returns True if the value is an issue; false otherwise\n *\n * @example\n * ```ts\n * isIssue({ origin: 'claimant', path: 'src/core/greeting.ts', message: 'not assignable' }) // true\n * isIssue({ path: 'src/core/greeting.ts', message: 'not assignable' }) // false\n * ```\n */\nexport const isIssue: Guard<Issue> = recordOf(\n\t{ origin: isParty, path: isString, message: isString, line: isNumber },\n\t['line'],\n)\n\n/**\n * Checks whether a value carries one stage's outcome.\n *\n * @param value - The value to check\n * @returns True if the value is a check; false otherwise\n *\n * @example\n * ```ts\n * isCheck({ stage: 'lint', elapsed: 17, issues: [] }) // true\n * ```\n */\nexport const isCheck: Guard<Check> = recordOf({\n\tstage: isStage,\n\telapsed: isNumber,\n\tissues: arrayOf(isIssue),\n})\n\n/**\n * Checks whether a value names every resolved tool version.\n *\n * @param value - The value to check\n * @returns True if the value is a toolchain; false otherwise\n *\n * @example\n * ```ts\n * isToolchain({ typescript: '6.0.3', oxlint: '1.79.0', vitest: '4.1.11' }) // true\n * isToolchain({ typescript: '6.0.3' }) // false\n * ```\n */\nexport const isToolchain: Guard<Toolchain> = recordOf({\n\ttypescript: isString,\n\toxlint: isString,\n\tvitest: isString,\n})\n\n/**\n * Checks whether a value names one resolved TypeScript project and what it contained.\n *\n * @remarks\n * Rejects an empty path and an empty digest. A verdict names the project that judged it, so a\n * blank member here would report a project the type stage never applied.\n *\n * @param value - The value to check\n * @returns True if the value is a project; false otherwise\n *\n * @example\n * ```ts\n * isProject({ path: 'configs/src/tsconfig.core.json', digest: '3b674fdf121c85ef' }) // true\n * isProject({ path: 'configs/src/tsconfig.core.json', digest: '' }) // false\n * ```\n */\nexport const isProject: Guard<Project> = recordOf({\n\tpath: isNonEmptyString,\n\tdigest: isNonEmptyString,\n})\n\n/**\n * Checks whether a value carries a complete verdict.\n *\n * @remarks\n * Accepts any check count, because arity is the coordinator's obligation rather than this guard's:\n * a stage that could not start throws before a verdict is built, so a short list can only arrive\n * from a caller that assembled one by hand.\n *\n * @param value - The value to check\n * @returns True if the value is a verdict; false otherwise\n *\n * @example\n * ```ts\n * const basis = { id: '01J8Z0', digest: '6ca20c3b', toolchain, project, elapsed: 337 }\n * isVerdict({ ...basis, case: [], control: [] }) // true\n * isVerdict({ ...basis, case: [] }) // false\n * ```\n */\nexport const isVerdict: Guard<Verdict> = recordOf(\n\t{\n\t\tid: isString,\n\t\tdigest: isString,\n\t\ttoolchain: isToolchain,\n\t\tproject: isProject,\n\t\treason: isNonEmptyString,\n\t\tcase: arrayOf(isCheck),\n\t\tcontrol: arrayOf(isCheck),\n\t\telapsed: isNumber,\n\t\treceipt: isString,\n\t},\n\t['reason', 'receipt'],\n)\n","import type { Check, Issue, Stage, Verdict } from './types.js'\nimport { PROBE_STAGES, RECEIPT_PREFIX, RECEIPT_SEPARATOR } from './constants.js'\n\n/**\n * Renders one tool message as a single line an agent can classify and locate.\n *\n * @param issue - The message and location a stage reported\n * @returns The bracketed origin, location, and message, separated by spaces\n *\n * @example\n * ```ts\n * const located: Issue = {\n * \torigin: 'claimant',\n * \tpath: 'src/core/greeting.ts',\n * \tmessage: 'not assignable',\n * \tline: 1,\n * }\n * const whole: Issue = {\n * \torigin: 'claimant',\n * \tpath: 'src/core/greeting.ts',\n * \tmessage: 'not assignable',\n * }\n * formatIssue(located) // '[claimant] src/core/greeting.ts:1 not assignable'\n * formatIssue(whole) // '[claimant] src/core/greeting.ts not assignable'\n * ```\n */\nexport function formatIssue(issue: Issue): string {\n\tconst where = issue.line === undefined ? issue.path : `${issue.path}:${issue.line}`\n\treturn `[${issue.origin}] ${where} ${issue.message}`\n}\n\n/**\n * Renders one stage's outcome as its summary line followed by every message it reported.\n *\n * @remarks\n * A clean stage renders one line. The count appears even when it is zero, because that line is\n * what shows a reader the stage ran; a silent stage and a skipped stage read the same.\n *\n * @param check - The stage outcome to render\n * @returns The summary line, then one indented line per issue\n *\n * @example\n * ```ts\n * formatCheck({ stage: 'lint', elapsed: 17, issues: [] })\n * // 'lint: 0 issues (17 ms)'\n * ```\n */\nexport function formatCheck(check: Check): string {\n\tconst count = check.issues.length\n\tconst noun = count === 1 ? 'issue' : 'issues'\n\tconst summary = `${check.stage}: ${count} ${noun} (${check.elapsed} ms)`\n\tconst lines = check.issues.map((issue) => ` ${formatIssue(issue)}`)\n\treturn [summary, ...lines].join('\\n')\n}\n\n/**\n * Renders a whole verdict as the text an agent reads.\n *\n * @remarks\n * Every stage appears for both the case and the control, so no failing stage can be masked by an\n * earlier one. The closing line always states the receipt or its absence, so a reader never has to\n * infer from silence whether the claim was proven.\n *\n * The heading lines carry everything the receipt binds, each in the name-then-value form the\n * toolchain line established: the call's identity, the claim it answered, the tools that ran, and\n * the project that judged the candidates. A verdict carrying the control's reason renders it next,\n * before either phase's checks. A reader comparing two verdicts therefore reads what differed\n * without recomputing anything.\n *\n * @param verdict - The verdict to render\n * @returns The identity, claim, toolchain, project, and reason when present, then every case and\n * control stage, then the receipt line\n *\n * @example\n * ```ts\n * formatVerdict(verdict).split('\\n')[1] // 'claim 6ca20c3bff623031d3955b9d1a76d71d'\n * ```\n */\nexport function formatVerdict(verdict: Verdict): string {\n\tconst { typescript, oxlint, vitest } = verdict.toolchain\n\tconst tools = `toolchain typescript ${typescript}, oxlint ${oxlint}, vitest ${vitest}`\n\tconst project = `project ${verdict.project.path} ${verdict.project.digest}`\n\tconst reason = verdict.reason === undefined ? [] : [`reason ${verdict.reason}`]\n\tconst proof = verdict.receipt === undefined ? 'no receipt' : `receipt ${verdict.receipt}`\n\treturn [\n\t\t`probe ${verdict.id} (${verdict.elapsed} ms)`,\n\t\t`claim ${verdict.digest}`,\n\t\ttools,\n\t\tproject,\n\t\t...reason,\n\t\t...verdict.case.map((check) => `case ${formatCheck(check)}`),\n\t\t...verdict.control.map((check) => `control ${formatCheck(check)}`),\n\t\tproof,\n\t].join('\\n')\n}\n\n/**\n * Computes the proof token a verdict carries, or returns nothing when the claim was not proven.\n *\n * @remarks\n * A receipt is minted on these conditions together: both phases report one check per stage, the\n * case carries no issue, the control carries at least one `origin: 'claimant'` issue\n * at the stage it declared, and every other control stage carries no claimant issue. A control\n * that fails somewhere else has falsified the instrument rather than the claim, so no receipt is\n * minted for it.\n *\n * Both phases owe every stage, not the case alone. `stayed` reads the control entries a verdict\n * carries, so a control that omits a stage entirely would otherwise read as a stage that stayed\n * clean, and the receipt would certify an inspection that never ran. `prove` records every stage\n * for both phases, so this condition refuses only a verdict a caller assembled by hand.\n *\n * The token names every condition the verdict was reached under, so a receipt read away from its\n * verdict still says what was judged and what judged it: the claim's digest, the stage the control\n * broke at, the tool versions, and the project the candidate drafts were checked against.\n * The call's identity is deliberately absent. It carries no integrity and it is the only value\n * stopping two honest runs of one claim from producing one comparable string, so leaving it out is\n * what makes \"re-run `prove` and compare tokens\" a verification a reader can perform.\n *\n * The project goes last, and its own path and digest are joined by `@`. Split the token on\n * `RECEIPT_SEPARATOR`, take fields 0 through 5, and rejoin the remainder with that separator to\n * read the project field; inside it the digest is everything after the final `@`. That rule stays\n * total for a workspace-relative project path containing either character.\n *\n * An `origin: 'instrument'` issue in either phase means the inspection did not complete and\n * refuses the receipt. A workspace issue in the control does not decide whether the claimant's\n * candidate broke, while one in the case still means that the case did not run clean.\n *\n * @param verdict - The verdict whose case and control checks decide the outcome\n * @param stage - The stage the claim's control declared it must fail at\n * @returns The receipt token, or `undefined` when any condition fails\n *\n * @example\n * ```ts\n * computeReceipt(proven, 'type')\n * // 'probe:6ca20c3bff623031d3955b9d1a76d71d:type:typescript@6.0.3:oxlint@1.79.0:vitest@4.1.11:configs/src/tsconfig.core.json@3b674fdf121c85efb9ed1bab25ceeec8'\n * computeReceipt(proven, 'lint') // undefined\n * ```\n */\nexport function computeReceipt(verdict: Verdict, stage: Stage): string | undefined {\n\tconst ran = PROBE_STAGES.every(\n\t\t(name) =>\n\t\t\tverdict.case.filter((check) => check.stage === name).length === 1 &&\n\t\t\tverdict.control.filter((check) => check.stage === name).length === 1,\n\t)\n\tconst clean = verdict.case.every((check) => check.issues.length === 0)\n\tconst declared = verdict.control.find((check) => check.stage === stage)\n\tconst broke = declared?.issues.some((issue) => issue.origin === 'claimant') ?? false\n\tconst faulted =\n\t\tverdict.case.some((check) => check.issues.some((issue) => issue.origin === 'instrument')) ||\n\t\tverdict.control.some((check) => check.issues.some((issue) => issue.origin === 'instrument'))\n\tconst stayed = verdict.control.every(\n\t\t(check) => check.stage === stage || check.issues.every((issue) => issue.origin !== 'claimant'),\n\t)\n\tif (!ran || !clean || !broke || faulted || !stayed) return undefined\n\tconst { typescript, oxlint, vitest } = verdict.toolchain\n\treturn [\n\t\tRECEIPT_PREFIX,\n\t\tverdict.digest,\n\t\tstage,\n\t\t`typescript@${typescript}`,\n\t\t`oxlint@${oxlint}`,\n\t\t`vitest@${vitest}`,\n\t\t`${verdict.project.path}@${verdict.project.digest}`,\n\t].join(RECEIPT_SEPARATOR)\n}\n\n/**\n * Renders one generated specification: the caller's own test text, then the marker naming the\n * revision that wrote it.\n *\n * @remarks\n * The marker is what makes a file in a target workspace attributable to this package. A generated\n * specification carries text the caller supplied, so its name alone cannot separate it from a\n * developer's own file, and a sweep reading the name alone deletes that file. The marker goes last\n * so every line of the caller's text keeps the number a reported stack frame gives it, and the text\n * gains a terminating newline first so the marker is never appended to a line of the test.\n *\n * @param text - The caller's own test text\n * @param revision - The writing host's process id and a fresh UUID, joined by `-`\n * @returns The bytes the runtime stage writes for that revision\n *\n * @example\n * ```ts\n * formatSpecification(\"test('greets', () => {})\\n\", '4821-9f0c')\n * // \"test('greets', () => {})\\n// @orkestrel/probe generated specification 4821-9f0c\\n\"\n * ```\n */\nexport function formatSpecification(text: string, revision: string): string {\n\tconst body = text === '' || text.endsWith('\\n') ? text : `${text}\\n`\n\treturn `${body}// @orkestrel/probe generated specification ${revision}\\n`\n}\n\n/**\n * Checks whether one file's text is the generated specification written for one revision.\n *\n * @remarks\n * Reads the marker `formatSpecification` writes, taken from that function rather than restated, so\n * the writer and the reader cannot drift apart. An empty body renders the marker alone, which is\n * why the comparison is built from a call rather than from a second copy of the text.\n *\n * @param text - The file's full contents\n * @param revision - The revision the file's name declares\n * @returns True if the text ends with this package's marker for that revision; false otherwise\n *\n * @example\n * ```ts\n * matchesSpecification(formatSpecification('', '4821-9f0c'), '4821-9f0c') // true\n * matchesSpecification('export const NOTE = 1\\n', '4821-9f0c') // false\n * ```\n */\nexport function matchesSpecification(text: string, revision: string): boolean {\n\treturn text.endsWith(formatSpecification('', revision))\n}\n","import { arrayShape, literalShape, objectShape, stringShape } from '@orkestrel/contract'\nimport { PROBE_STAGES } from './constants.js'\n\n/**\n * Describes one proposed file a claim carries.\n *\n * @remarks\n * `path` is constrained to a non-empty string because every stage resolves a file by it, so an\n * empty path names no file. That minimum is the whole of the constraint here, and it is the one\n * place the wire contract is wider than what this package admits: `isDraft` enforces the same\n * minimum and also refuses an absolute path and one that traverses out of the workspace, which no\n * JSON Schema keyword can express. A caller that satisfies this shape can still be refused, and\n * `ProbeServer` names the member when it is.\n *\n * @example\n * ```ts\n * compileGuard(DRAFT_SHAPE)({ path: 'src/core/greeting.ts', text: '' }) // true\n * ```\n */\nexport const DRAFT_SHAPE = objectShape(\n\t{\n\t\tpath: stringShape({ min: 1, description: 'Workspace-relative path, which need not exist.' }),\n\t\ttext: stringShape({ description: \"The file's full contents.\" }),\n\t},\n\t{ description: 'One proposed file a claim carries.' },\n)\n\n/**\n * Describes the drafts a claim asserts about and the test that exercises them.\n *\n * @example\n * ```ts\n * const test = { path: 'tests/src/core/greeting.test.ts', text: '' }\n * compileGuard(CASE_SHAPE)({ files: [], test }) // true\n * ```\n */\nexport const CASE_SHAPE = objectShape(\n\t{\n\t\tfiles: arrayShape(DRAFT_SHAPE, { description: 'The candidate drafts the test imports.' }),\n\t\ttest: DRAFT_SHAPE,\n\t},\n\t{ description: 'The drafts a claim asserts about, and the test that exercises them.' },\n)\n\n/**\n * Describes the negative control, which is a case plus where and why it must break.\n *\n * @remarks\n * Built from `CASE_SHAPE`'s own properties rather than a second copy of them, so the control and\n * the case cannot describe a draft differently.\n *\n * @example\n * ```ts\n * const test = { path: 'tests/src/core/greeting.test.ts', text: '' }\n * compileGuard(CONTROL_SHAPE)({ files: [], test, stage: 'type', reason: 'must not compile' }) // true\n * ```\n */\nexport const CONTROL_SHAPE = objectShape(\n\t{\n\t\t...CASE_SHAPE.properties,\n\t\tstage: literalShape(PROBE_STAGES, { description: 'The stage this control must fail at.' }),\n\t\treason: stringShape({ min: 1, description: 'Why the control fails at that stage.' }),\n\t},\n\t{ description: 'A case that must fail, naming the stage where it must fail and why.' },\n)\n\n/**\n * Describes one claim and is the sole source of both the published tool schema and the guard\n * applied to an arriving claim.\n *\n * @remarks\n * The Model Context Protocol tool publishes `compileSchema(CLAIM_SHAPE)` and admits a call with\n * `isClaim`. Deriving the advertised schema from this one value is what stops the two from drifting\n * apart across a release on any member either can express. `isClaim` is narrower on the one member\n * neither a schema keyword nor this shape can carry — the containment rule on `Draft.path` — and\n * `validators.ts` names where.\n *\n * @example\n * ```ts\n * const schema = compileSchema(CLAIM_SHAPE)\n * const admits = compileGuard(CLAIM_SHAPE)\n * schema.type // 'object'\n * admits({ project: 'tsconfig.json' }) // false\n * ```\n */\nexport const CLAIM_SHAPE = objectShape(\n\t{\n\t\tproject: stringShape({\n\t\t\tmin: 1,\n\t\t\tdescription:\n\t\t\t\t'Workspace-relative TypeScript project the candidate drafts are checked against.',\n\t\t}),\n\t\tcase: CASE_SHAPE,\n\t\tcontrol: CONTROL_SHAPE,\n\t},\n\t{ description: 'One claim: the project, the case, and the control that must break.' },\n)\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../../../src/core/constants.ts","../../../src/core/errors.ts","../../../src/core/validators.ts","../../../src/core/helpers.ts","../../../src/core/shapers.ts"],"sourcesContent":["/**\n * Lists the stages a claim passes through, in the order a verdict reports them.\n *\n * @remarks\n * One list feeds the `Stage` type, the wire shape, the stage guard, and the receipt computation, so\n * a stage cannot be admitted by one and refused by another. The type derives from this list rather\n * than standing beside it, so a stage the list omits is a stage no member of the union names.\n *\n * @example\n * ```ts\n * PROBE_STAGES // ['type', 'lint', 'runtime']\n * ```\n */\nexport const PROBE_STAGES = Object.freeze(['type', 'lint', 'runtime'] as const)\n\n/**\n * Lists the parties that can own action on an issue or probe failure.\n *\n * @remarks\n * One list feeds the `Party` type and its guard, so a party cannot be admitted by one and refused\n * by another.\n *\n * @example\n * ```ts\n * PROBE_PARTIES // ['claimant', 'workspace', 'instrument']\n * ```\n */\nexport const PROBE_PARTIES = Object.freeze(['claimant', 'workspace', 'instrument'] as const)\n\n/**\n * Lists the conditions that can end a probe operation.\n *\n * @remarks\n * One list feeds the code union and the error guard, so a condition cannot be constructed by one\n * and refused by the other. The guard reads this list rather than the class, which is what lets it\n * refuse a lookalike carrying a condition this package never declared.\n *\n * @example\n * ```ts\n * PROBE_ERROR_CODES // ['refused', 'missing', 'malformed', 'destroyed', 'deadline']\n * ```\n */\nexport const PROBE_ERROR_CODES = Object.freeze([\n\t'refused',\n\t'missing',\n\t'malformed',\n\t'destroyed',\n\t'deadline',\n] as const)\n\n/**\n * Names the leading token every receipt carries.\n *\n * @remarks\n * A receipt travels away from the verdict that minted it — an agent pastes it into the promotion\n * action — so it names itself rather than relying on where it was found.\n *\n * @example\n * ```ts\n * const receipt =\n * \t'probe:6ca20c3bff623031d3955b9d1a76d71d:type:typescript@6.0.3:oxlint@1.79.0:vitest@4.1.11:configs/src/tsconfig.core.json@3b674fdf121c85efb9ed1bab25ceeec8'\n * receipt.startsWith(RECEIPT_PREFIX) // true\n * ```\n */\nexport const RECEIPT_PREFIX = 'probe'\n\n/**\n * Names the character joining a receipt's tokens.\n *\n * @remarks\n * A project path can contain this character, so the project field goes last and a reader rejoins\n * every field from index 6 onward with this separator rather than expecting a fixed count.\n *\n * @example\n * ```ts\n * const receipt =\n * \t'probe:6ca20c3bff623031d3955b9d1a76d71d:type:typescript@6.0.3:oxlint@1.79.0:vitest@4.1.11:configs/src/tsconfig.core.json@3b674fdf121c85efb9ed1bab25ceeec8'\n * receipt.split(RECEIPT_SEPARATOR).length // 7\n * ```\n */\nexport const RECEIPT_SEPARATOR = ':'\n","import type { Party, ProbeErrorCode, ProbeErrorContext, ProbeErrorOptions } from './types.js'\nimport { holds, isError } from '@orkestrel/contract'\nimport { PROBE_ERROR_CODES, PROBE_PARTIES } from './constants.js'\n\n/**\n * Reports one probe failure under stable ownership and condition axes.\n *\n * @remarks\n * Every failure raised while serving a claim arrives as one of these, so a consumer catching from\n * `prove`, from a stage, or from a workspace leaf branches on {@link Party} and\n * {@link ProbeErrorCode} rather than on message text. Narrow a caught value with\n * {@link isProbeError} before reading either member. A pure leaf called directly with a value\n * `isClaim` would refuse raises the host's own fault unchanged, because those leaves are written to\n * be total over a validated claim rather than over every value.\n *\n * @example\n * ```ts\n * const error = new ProbeError('Path escapes the workspace: ../secrets.env', {\n * \torigin: 'claimant',\n * \tcode: 'refused',\n * \tcontext: { path: '../secrets.env' },\n * })\n * error.origin // 'claimant'\n * error.code // 'refused'\n * error.context?.path // '../secrets.env'\n * ```\n */\nexport class ProbeError extends Error {\n\toverride readonly name = 'ProbeError'\n\treadonly origin: Party\n\treadonly code: ProbeErrorCode\n\treadonly context?: ProbeErrorContext\n\n\t/**\n\t * Creates one classified probe failure.\n\t *\n\t * @param message - Human-readable description of what failed\n\t * @param options - The ownership, condition, optional structured context, and optional cause\n\t */\n\tconstructor(message: string, options: ProbeErrorOptions) {\n\t\tconst cause = options.cause\n\t\tsuper(message, cause === undefined ? undefined : { cause })\n\t\t// A global own-property brand rather than the constructor identity, because a consumer that\n\t\t// installs this package twice, or loads its ESM and CommonJS builds together, holds two\n\t\t// classes and `instanceof` refuses the other one's error.\n\t\tObject.defineProperty(this, Symbol.for('@orkestrel/probe.error'), { value: true })\n\t\tthis.origin = options.origin\n\t\tthis.code = options.code\n\t\tif (options.context !== undefined) this.context = options.context\n\t}\n}\n\n/**\n * Checks whether an unknown value is a {@link ProbeError}.\n *\n * @remarks\n * Recognition combines the global own-property brand with the native `Error` base, the subclass\n * prototype, the fixed name, a party {@link PROBE_PARTIES} declares, and a condition\n * {@link PROBE_ERROR_CODES} declares. The brand carries recognition across duplicate installations\n * and across an ESM and a CommonJS copy of this package, where the two classes are different values.\n * A plain `Error`, a property-only lookalike, and a branded value carrying an undeclared origin or\n * condition all stay outside the type.\n *\n * @param value - The value to inspect\n * @returns True only for a `ProbeError` instance; false otherwise\n *\n * @example\n * ```ts\n * isProbeError(createDestroyedError('probe')) // true\n * isProbeError(new Error('The probe has been destroyed')) // false\n * ```\n */\nexport function isProbeError(value: unknown): value is ProbeError {\n\tif (!isError(value)) return false\n\treturn holds(() => {\n\t\tif (Object.getPrototypeOf(value) === Error.prototype) return false\n\t\tif (value.name !== 'ProbeError' || !('origin' in value) || !('code' in value)) return false\n\t\tconst descriptor = Object.getOwnPropertyDescriptor(value, Symbol.for('@orkestrel/probe.error'))\n\t\tif (descriptor?.value !== true) return false\n\t\tconst origin: unknown = value.origin\n\t\tconst code: unknown = value.code\n\t\treturn (\n\t\t\tPROBE_PARTIES.some((declared) => declared === origin) &&\n\t\t\tPROBE_ERROR_CODES.some((declared) => declared === code)\n\t\t)\n\t})\n}\n\n/**\n * Creates the failure raised when an instrument is used after it was torn down.\n *\n * @remarks\n * Every resident entity in this package refuses this way, so the subject names which one in the\n * message and the condition is the same for all of them. A caller that meets this builds a\n * replacement rather than retrying: teardown is permanent.\n *\n * @param subject - The torn-down entity, named as the message names it, such as `type stage`\n * @returns A typed teardown failure\n *\n * @example\n * ```ts\n * createDestroyedError('type stage').message // 'The type stage has been destroyed'\n * createDestroyedError('probe').code // 'destroyed'\n * ```\n */\nexport function createDestroyedError(subject: string): ProbeError {\n\treturn new ProbeError(`The ${subject} has been destroyed`, {\n\t\torigin: 'claimant',\n\t\tcode: 'destroyed',\n\t})\n}\n","import type { Guard } from '@orkestrel/contract'\nimport type {\n\tCase,\n\tCheck,\n\tClaim,\n\tControl,\n\tDraft,\n\tIssue,\n\tParty,\n\tProject,\n\tStage,\n\tToolchain,\n\tVerdict,\n} from './types.js'\nimport {\n\tandOf,\n\tarrayOf,\n\tisNonEmptyString,\n\tisNumber,\n\tisString,\n\tliteralOf,\n\trecordOf,\n} from '@orkestrel/contract'\nimport { isLSPRange } from '@orkestrel/lsp'\nimport { PROBE_PARTIES, PROBE_STAGES } from './constants.js'\n\n/**\n * Checks whether a value names a stage.\n *\n * @param value - The value to check\n * @returns True if the value is a stage name; false otherwise\n *\n * @example\n * ```ts\n * isStage('lint') // true\n * isStage('typecheck') // false\n * ```\n */\nexport const isStage: Guard<Stage> = literalOf(PROBE_STAGES)\n\n/**\n * Checks whether a value carries a proposed file's path and text.\n *\n * @remarks\n * Rejects an empty, absolute, or workspace-escaping path. A contained relative path may contain\n * `.` or may traverse to a parent that remains inside the workspace. Every stage resolves a file\n * by that path, so admitting an escape here would defer the refusal until a stage had already\n * accepted the draft. An `Issue` carries no such minimum because this package produces issues\n * rather than admits them.\n *\n * @param value - The value to check\n * @returns True if the value is a draft; false otherwise\n *\n * @example\n * ```ts\n * isDraft({ path: 'src/core/greeting.ts', text: 'export const GREETING = \"hi\"\\n' }) // true\n * isDraft({ path: '../../etc/hosts', text: '' }) // false\n * isDraft({ path: 'src/core/greeting.ts' }) // false\n * ```\n */\nexport const isDraft: Guard<Draft> = recordOf({\n\tpath: andOf<string>(isNonEmptyString, (path) => {\n\t\tif (/^(?:[\\\\/]|[A-Za-z]:)/.test(path)) return false\n\t\tlet depth = 0\n\t\tfor (const segment of path.split(/[\\\\/]+/)) {\n\t\t\tif (segment === '' || segment === '.') continue\n\t\t\tif (segment !== '..') {\n\t\t\t\tdepth += 1\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif (depth === 0) return false\n\t\t\tdepth -= 1\n\t\t}\n\t\treturn true\n\t}),\n\ttext: isString,\n})\n\n/**\n * Checks whether a value carries a claim's candidate drafts and its test.\n *\n * @param value - The value to check\n * @returns True if the value is a case; false otherwise\n *\n * @example\n * ```ts\n * const test = { path: 'tests/src/core/greeting.test.ts', text: 'test(\"greets\", () => {})\\n' }\n * isCase({ files: [], test }) // true\n * ```\n */\nexport const isCase: Guard<Case> = recordOf({ files: arrayOf(isDraft), test: isDraft })\n\n/**\n * Checks whether a value carries a case plus the stage and reason it must fail for.\n *\n * @param value - The value to check\n * @returns True if the value is a control; false otherwise\n *\n * @example\n * ```ts\n * const test = { path: 'tests/src/core/greeting.test.ts', text: 'test(\"greets\", () => {})\\n' }\n * isControl({ files: [], test, stage: 'type', reason: 'must not compile' }) // true\n * isControl({ files: [], test }) // false\n * ```\n */\nexport const isControl: Guard<Control> = recordOf({\n\tfiles: arrayOf(isDraft),\n\ttest: isDraft,\n\tstage: isStage,\n\treason: isNonEmptyString,\n})\n\n/**\n * Checks whether a value carries a project, a case, and a control.\n *\n * @remarks\n * Exact rather than open: a claim is this package's own record, so an unknown member is a caller\n * sending a contract this service does not implement rather than a wider caller it must tolerate.\n *\n * `CLAIM_SHAPE` is the wire contract's shape and this is the admission rule the tool enforces, and\n * the rule is strictly narrower than the shape. They agree on every member but `Draft.path`: the\n * shape constrains it to a non-empty string, while `isDraft` also refuses an absolute path\n * and one that traverses out of the workspace, so `../../etc/hosts` satisfies\n * `compileGuard(CLAIM_SHAPE)` and is refused here. `ProbeServer` advertises the shape and enforces\n * this rule, so it names the refused member rather than reporting only that the claim was invalid.\n *\n * @param value - The value to check\n * @returns True if the value is a claim; false otherwise\n *\n * @example\n * ```ts\n * isClaim({ project: 'tsconfig.json', case: subject, control }) // true\n * isClaim({ project: 'tsconfig.json', case: subject }) // false\n * ```\n */\nexport const isClaim: Guard<Claim> = recordOf({\n\tproject: isNonEmptyString,\n\tcase: isCase,\n\tcontrol: isControl,\n})\n\n/**\n * Checks whether a value names a party an issue carries.\n *\n * @param value - The value to check\n * @returns True if the value is a party; false otherwise\n *\n * @example\n * ```ts\n * isParty('claimant') // true\n * isParty('workspace') // true\n * isParty('instrument') // true\n * isParty('stage') // false\n * ```\n */\nexport const isParty: Guard<Party> = literalOf(PROBE_PARTIES)\n\n/**\n * Checks whether a value carries one message, its location, and the origin of the fault it names.\n *\n * @remarks\n * `origin` is required rather than optional. Every issue this package produces sets it, and a\n * receipt turns on it: a control's issue disproves the claim only when its origin names the\n * claimant.\n *\n * @param value - The value to check\n * @returns True if the value is an issue; false otherwise\n *\n * @example\n * ```ts\n * isIssue({ origin: 'claimant', path: 'src/core/greeting.ts', message: 'not assignable' }) // true\n * isIssue({ path: 'src/core/greeting.ts', message: 'not assignable' }) // false\n * ```\n */\nexport const isIssue: Guard<Issue> = recordOf(\n\t{ origin: isParty, path: isString, message: isString, range: isLSPRange },\n\t['range'],\n)\n\n/**\n * Checks whether a value carries one stage's outcome.\n *\n * @param value - The value to check\n * @returns True if the value is a check; false otherwise\n *\n * @example\n * ```ts\n * isCheck({ stage: 'lint', elapsed: 17, issues: [] }) // true\n * ```\n */\nexport const isCheck: Guard<Check> = recordOf({\n\tstage: isStage,\n\telapsed: isNumber,\n\tissues: arrayOf(isIssue),\n})\n\n/**\n * Checks whether a value names every resolved tool version.\n *\n * @param value - The value to check\n * @returns True if the value is a toolchain; false otherwise\n *\n * @example\n * ```ts\n * isToolchain({ typescript: '6.0.3', oxlint: '1.79.0', vitest: '4.1.11' }) // true\n * isToolchain({ typescript: '6.0.3' }) // false\n * ```\n */\nexport const isToolchain: Guard<Toolchain> = recordOf({\n\ttypescript: isString,\n\toxlint: isString,\n\tvitest: isString,\n})\n\n/**\n * Checks whether a value names one resolved TypeScript project and what it contained.\n *\n * @remarks\n * Rejects an empty path and an empty digest. A verdict names the project that judged it, so a\n * blank member here would report a project the type stage never applied.\n *\n * @param value - The value to check\n * @returns True if the value is a project; false otherwise\n *\n * @example\n * ```ts\n * isProject({ path: 'configs/src/tsconfig.core.json', digest: '3b674fdf121c85ef' }) // true\n * isProject({ path: 'configs/src/tsconfig.core.json', digest: '' }) // false\n * ```\n */\nexport const isProject: Guard<Project> = recordOf({\n\tpath: isNonEmptyString,\n\tdigest: isNonEmptyString,\n})\n\n/**\n * Checks whether a value carries a complete verdict.\n *\n * @remarks\n * Accepts any check count, because arity is the coordinator's obligation rather than this guard's:\n * a stage that could not start throws before a verdict is built, so a short list can only arrive\n * from a caller that assembled one by hand.\n *\n * @param value - The value to check\n * @returns True if the value is a verdict; false otherwise\n *\n * @example\n * ```ts\n * const basis = { id: '01J8Z0', digest: '6ca20c3b', toolchain, project, elapsed: 337 }\n * isVerdict({ ...basis, case: [], control: [] }) // true\n * isVerdict({ ...basis, case: [] }) // false\n * ```\n */\nexport const isVerdict: Guard<Verdict> = recordOf(\n\t{\n\t\tid: isString,\n\t\tdigest: isString,\n\t\ttoolchain: isToolchain,\n\t\tproject: isProject,\n\t\treason: isNonEmptyString,\n\t\tcase: arrayOf(isCheck),\n\t\tcontrol: arrayOf(isCheck),\n\t\telapsed: isNumber,\n\t\treceipt: isString,\n\t},\n\t['reason', 'receipt'],\n)\n","import type { Check, Issue, Stage, Verdict } from './types.js'\nimport { PROBE_STAGES, RECEIPT_PREFIX, RECEIPT_SEPARATOR } from './constants.js'\n\n/**\n * Renders one tool message as a single line an agent can classify and locate.\n *\n * @remarks\n * `Issue.range` is stored zero-based, and this is the only place it is rendered, so the one-based\n * line an editor shows is derived here and nowhere else. The rendered location carries the line\n * alone: a reader opens a file at a line, and the column would widen every issue line in every\n * verdict for a precision that address does not use.\n *\n * @param issue - The message and location a stage reported\n * @returns The bracketed origin, location, and message, separated by spaces\n *\n * @example\n * ```ts\n * const located: Issue = {\n * \torigin: 'claimant',\n * \tpath: 'src/core/greeting.ts',\n * \tmessage: 'not assignable',\n * \trange: { start: { line: 0, character: 6 }, end: { line: 0, character: 13 } },\n * }\n * const whole: Issue = {\n * \torigin: 'claimant',\n * \tpath: 'src/core/greeting.ts',\n * \tmessage: 'not assignable',\n * }\n * formatIssue(located) // '[claimant] src/core/greeting.ts:1 not assignable'\n * formatIssue(whole) // '[claimant] src/core/greeting.ts not assignable'\n * ```\n */\nexport function formatIssue(issue: Issue): string {\n\tconst range = issue.range\n\tconst where = range === undefined ? issue.path : `${issue.path}:${range.start.line + 1}`\n\treturn `[${issue.origin}] ${where} ${issue.message}`\n}\n\n/**\n * Renders one stage's outcome as its summary line followed by every message it reported.\n *\n * @remarks\n * A clean stage renders one line. The count appears even when it is zero, because that line is\n * what shows a reader the stage ran; a silent stage and a skipped stage read the same.\n *\n * @param check - The stage outcome to render\n * @returns The summary line, then one indented line per issue\n *\n * @example\n * ```ts\n * formatCheck({ stage: 'lint', elapsed: 17, issues: [] })\n * // 'lint: 0 issues (17 ms)'\n * ```\n */\nexport function formatCheck(check: Check): string {\n\tconst count = check.issues.length\n\tconst noun = count === 1 ? 'issue' : 'issues'\n\tconst summary = `${check.stage}: ${count} ${noun} (${check.elapsed} ms)`\n\tconst lines = check.issues.map((issue) => ` ${formatIssue(issue)}`)\n\treturn [summary, ...lines].join('\\n')\n}\n\n/**\n * Renders a whole verdict as the text an agent reads.\n *\n * @remarks\n * Every stage appears for both the case and the control, so no failing stage can be masked by an\n * earlier one. The closing line always states the receipt or its absence, so a reader never has to\n * infer from silence whether the claim was proven.\n *\n * The heading lines carry everything the receipt binds, each in the name-then-value form the\n * toolchain line established: the call's identity, the claim it answered, the tools that ran, and\n * the project that judged the candidates. A verdict carrying the control's reason renders it next,\n * before either phase's checks. A reader comparing two verdicts therefore reads what differed\n * without recomputing anything.\n *\n * @param verdict - The verdict to render\n * @returns The identity, claim, toolchain, project, and reason when present, then every case and\n * control stage, then the receipt line\n *\n * @example\n * ```ts\n * formatVerdict(verdict).split('\\n')[1] // 'claim 6ca20c3bff623031d3955b9d1a76d71d'\n * ```\n */\nexport function formatVerdict(verdict: Verdict): string {\n\tconst { typescript, oxlint, vitest } = verdict.toolchain\n\tconst tools = `toolchain typescript ${typescript}, oxlint ${oxlint}, vitest ${vitest}`\n\tconst project = `project ${verdict.project.path} ${verdict.project.digest}`\n\tconst reason = verdict.reason === undefined ? [] : [`reason ${verdict.reason}`]\n\tconst proof = verdict.receipt === undefined ? 'no receipt' : `receipt ${verdict.receipt}`\n\treturn [\n\t\t`probe ${verdict.id} (${verdict.elapsed} ms)`,\n\t\t`claim ${verdict.digest}`,\n\t\ttools,\n\t\tproject,\n\t\t...reason,\n\t\t...verdict.case.map((check) => `case ${formatCheck(check)}`),\n\t\t...verdict.control.map((check) => `control ${formatCheck(check)}`),\n\t\tproof,\n\t].join('\\n')\n}\n\n/**\n * Computes the proof token a verdict carries, or returns nothing when the claim was not proven.\n *\n * @remarks\n * A receipt is minted on these conditions together: both phases report one check per stage, the\n * case carries no issue, the control carries at least one `origin: 'claimant'` issue\n * at the stage it declared, and every other control stage carries no claimant issue. A control\n * that fails somewhere else has falsified the instrument rather than the claim, so no receipt is\n * minted for it.\n *\n * Both phases owe every stage, not the case alone. `stayed` reads the control entries a verdict\n * carries, so a control that omits a stage entirely would otherwise read as a stage that stayed\n * clean, and the receipt would certify an inspection that never ran. `prove` records every stage\n * for both phases, so this condition refuses only a verdict a caller assembled by hand.\n *\n * The token names every condition the verdict was reached under, so a receipt read away from its\n * verdict still says what was judged and what judged it: the claim's digest, the stage the control\n * broke at, the tool versions, and the project the candidate drafts were checked against.\n * The call's identity is deliberately absent. It carries no integrity and it is the only value\n * stopping two honest runs of one claim from producing one comparable string, so leaving it out is\n * what makes \"re-run `prove` and compare tokens\" a verification a reader can perform.\n *\n * The project goes last, and its own path and digest are joined by `@`. Split the token on\n * `RECEIPT_SEPARATOR`, take fields 0 through 5, and rejoin the remainder with that separator to\n * read the project field; inside it the digest is everything after the final `@`. That rule stays\n * total for a workspace-relative project path containing either character.\n *\n * An `origin: 'instrument'` issue in either phase means the inspection did not complete and\n * refuses the receipt. A workspace issue in the control does not decide whether the claimant's\n * candidate broke, while one in the case still means that the case did not run clean.\n *\n * @param verdict - The verdict whose case and control checks decide the outcome\n * @param stage - The stage the claim's control declared it must fail at\n * @returns The receipt token, or `undefined` when any condition fails\n *\n * @example\n * ```ts\n * computeReceipt(proven, 'type')\n * // 'probe:6ca20c3bff623031d3955b9d1a76d71d:type:typescript@6.0.3:oxlint@1.79.0:vitest@4.1.11:configs/src/tsconfig.core.json@3b674fdf121c85efb9ed1bab25ceeec8'\n * computeReceipt(proven, 'lint') // undefined\n * ```\n */\nexport function computeReceipt(verdict: Verdict, stage: Stage): string | undefined {\n\tconst ran = PROBE_STAGES.every(\n\t\t(name) =>\n\t\t\tverdict.case.filter((check) => check.stage === name).length === 1 &&\n\t\t\tverdict.control.filter((check) => check.stage === name).length === 1,\n\t)\n\tconst clean = verdict.case.every((check) => check.issues.length === 0)\n\tconst declared = verdict.control.find((check) => check.stage === stage)\n\tconst broke = declared?.issues.some((issue) => issue.origin === 'claimant') ?? false\n\tconst faulted =\n\t\tverdict.case.some((check) => check.issues.some((issue) => issue.origin === 'instrument')) ||\n\t\tverdict.control.some((check) => check.issues.some((issue) => issue.origin === 'instrument'))\n\tconst stayed = verdict.control.every(\n\t\t(check) => check.stage === stage || check.issues.every((issue) => issue.origin !== 'claimant'),\n\t)\n\tif (!ran || !clean || !broke || faulted || !stayed) return undefined\n\tconst { typescript, oxlint, vitest } = verdict.toolchain\n\treturn [\n\t\tRECEIPT_PREFIX,\n\t\tverdict.digest,\n\t\tstage,\n\t\t`typescript@${typescript}`,\n\t\t`oxlint@${oxlint}`,\n\t\t`vitest@${vitest}`,\n\t\t`${verdict.project.path}@${verdict.project.digest}`,\n\t].join(RECEIPT_SEPARATOR)\n}\n\n/**\n * Renders one generated specification: the caller's own test text, then the marker naming the\n * revision that wrote it.\n *\n * @remarks\n * The marker is what makes a file in a target workspace attributable to this package. A generated\n * specification carries text the caller supplied, so its name alone cannot separate it from a\n * developer's own file, and a sweep reading the name alone deletes that file. The marker goes last\n * so every line of the caller's text keeps the number a reported stack frame gives it, and the text\n * gains a terminating newline first so the marker is never appended to a line of the test.\n *\n * @param text - The caller's own test text\n * @param revision - The writing host's process id and a fresh UUID, joined by `-`\n * @returns The bytes the runtime stage writes for that revision\n *\n * @example\n * ```ts\n * formatSpecification(\"test('greets', () => {})\\n\", '4821-9f0c')\n * // \"test('greets', () => {})\\n// @orkestrel/probe generated specification 4821-9f0c\\n\"\n * ```\n */\nexport function formatSpecification(text: string, revision: string): string {\n\tconst body = text === '' || text.endsWith('\\n') ? text : `${text}\\n`\n\treturn `${body}// @orkestrel/probe generated specification ${revision}\\n`\n}\n\n/**\n * Checks whether one file's text is the generated specification written for one revision.\n *\n * @remarks\n * Reads the marker `formatSpecification` writes, taken from that function rather than restated, so\n * the writer and the reader cannot drift apart. An empty body renders the marker alone, which is\n * why the comparison is built from a call rather than from a second copy of the text.\n *\n * @param text - The file's full contents\n * @param revision - The revision the file's name declares\n * @returns True if the text ends with this package's marker for that revision; false otherwise\n *\n * @example\n * ```ts\n * matchesSpecification(formatSpecification('', '4821-9f0c'), '4821-9f0c') // true\n * matchesSpecification('export const NOTE = 1\\n', '4821-9f0c') // false\n * ```\n */\nexport function matchesSpecification(text: string, revision: string): boolean {\n\treturn text.endsWith(formatSpecification('', revision))\n}\n","import { arrayShape, literalShape, objectShape, stringShape } from '@orkestrel/contract'\nimport { PROBE_STAGES } from './constants.js'\n\n/**\n * Describes one proposed file a claim carries.\n *\n * @remarks\n * `path` is constrained to a non-empty string because every stage resolves a file by it, so an\n * empty path names no file. That minimum is the whole of the constraint here, and it is the one\n * place the wire contract is wider than what this package admits: `isDraft` enforces the same\n * minimum and also refuses an absolute path and one that traverses out of the workspace, which no\n * JSON Schema keyword can express. A caller that satisfies this shape can still be refused, and\n * `ProbeServer` names the member when it is.\n *\n * @example\n * ```ts\n * compileGuard(DRAFT_SHAPE)({ path: 'src/core/greeting.ts', text: '' }) // true\n * ```\n */\nexport const DRAFT_SHAPE = objectShape(\n\t{\n\t\tpath: stringShape({ min: 1, description: 'Workspace-relative path, which need not exist.' }),\n\t\ttext: stringShape({ description: \"The file's full contents.\" }),\n\t},\n\t{ description: 'One proposed file a claim carries.' },\n)\n\n/**\n * Describes the drafts a claim asserts about and the test that exercises them.\n *\n * @example\n * ```ts\n * const test = { path: 'tests/src/core/greeting.test.ts', text: '' }\n * compileGuard(CASE_SHAPE)({ files: [], test }) // true\n * ```\n */\nexport const CASE_SHAPE = objectShape(\n\t{\n\t\tfiles: arrayShape(DRAFT_SHAPE, { description: 'The candidate drafts the test imports.' }),\n\t\ttest: DRAFT_SHAPE,\n\t},\n\t{ description: 'The drafts a claim asserts about, and the test that exercises them.' },\n)\n\n/**\n * Describes the negative control, which is a case plus where and why it must break.\n *\n * @remarks\n * Built from `CASE_SHAPE`'s own properties rather than a second copy of them, so the control and\n * the case cannot describe a draft differently.\n *\n * @example\n * ```ts\n * const test = { path: 'tests/src/core/greeting.test.ts', text: '' }\n * compileGuard(CONTROL_SHAPE)({ files: [], test, stage: 'type', reason: 'must not compile' }) // true\n * ```\n */\nexport const CONTROL_SHAPE = objectShape(\n\t{\n\t\t...CASE_SHAPE.properties,\n\t\tstage: literalShape(PROBE_STAGES, { description: 'The stage this control must fail at.' }),\n\t\treason: stringShape({ min: 1, description: 'Why the control fails at that stage.' }),\n\t},\n\t{ description: 'A case that must fail, naming the stage where it must fail and why.' },\n)\n\n/**\n * Describes one claim and is the sole source of both the published tool schema and the guard\n * applied to an arriving claim.\n *\n * @remarks\n * The Model Context Protocol tool publishes `compileSchema(CLAIM_SHAPE)` and admits a call with\n * `isClaim`. Deriving the advertised schema from this one value is what stops the two from drifting\n * apart across a release on any member either can express. `isClaim` is narrower on the one member\n * neither a schema keyword nor this shape can carry — the containment rule on `Draft.path` — and\n * `validators.ts` names where.\n *\n * @example\n * ```ts\n * const schema = compileSchema(CLAIM_SHAPE)\n * const admits = compileGuard(CLAIM_SHAPE)\n * schema.type // 'object'\n * admits({ project: 'tsconfig.json' }) // false\n * ```\n */\nexport const CLAIM_SHAPE = objectShape(\n\t{\n\t\tproject: stringShape({\n\t\t\tmin: 1,\n\t\t\tdescription:\n\t\t\t\t'Workspace-relative TypeScript project the candidate drafts are checked against.',\n\t\t}),\n\t\tcase: CASE_SHAPE,\n\t\tcontrol: CONTROL_SHAPE,\n\t},\n\t{ description: 'One claim: the project, the case, and the control that must break.' },\n)\n"],"mappings":"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"}
|
|
@@ -4,6 +4,7 @@ import { EmitterHooks } from '@orkestrel/emitter';
|
|
|
4
4
|
import { EmitterInterface } from '@orkestrel/emitter';
|
|
5
5
|
import { Guard } from '@orkestrel/contract';
|
|
6
6
|
import { LiteralShape } from '@orkestrel/contract';
|
|
7
|
+
import { LSPRange } from '@orkestrel/lsp';
|
|
7
8
|
import { ObjectShape } from '@orkestrel/contract';
|
|
8
9
|
import { StringShape } from '@orkestrel/contract';
|
|
9
10
|
|
|
@@ -345,6 +346,12 @@ export declare function formatCheck(check: Check): string;
|
|
|
345
346
|
/**
|
|
346
347
|
* Renders one tool message as a single line an agent can classify and locate.
|
|
347
348
|
*
|
|
349
|
+
* @remarks
|
|
350
|
+
* `Issue.range` is stored zero-based, and this is the only place it is rendered, so the one-based
|
|
351
|
+
* line an editor shows is derived here and nowhere else. The rendered location carries the line
|
|
352
|
+
* alone: a reader opens a file at a line, and the column would widen every issue line in every
|
|
353
|
+
* verdict for a precision that address does not use.
|
|
354
|
+
*
|
|
348
355
|
* @param issue - The message and location a stage reported
|
|
349
356
|
* @returns The bracketed origin, location, and message, separated by spaces
|
|
350
357
|
*
|
|
@@ -354,7 +361,7 @@ export declare function formatCheck(check: Check): string;
|
|
|
354
361
|
* origin: 'claimant',
|
|
355
362
|
* path: 'src/core/greeting.ts',
|
|
356
363
|
* message: 'not assignable',
|
|
357
|
-
* line:
|
|
364
|
+
* range: { start: { line: 0, character: 6 }, end: { line: 0, character: 13 } },
|
|
358
365
|
* }
|
|
359
366
|
* const whole: Issue = {
|
|
360
367
|
* origin: 'claimant',
|
|
@@ -609,9 +616,18 @@ export declare const isStage: Guard<Stage>;
|
|
|
609
616
|
* path, the lint stage from the document URI it opened, and the runtime stage from the generated
|
|
610
617
|
* specification it wrote to the test path the case declared.
|
|
611
618
|
*
|
|
612
|
-
* `
|
|
613
|
-
*
|
|
614
|
-
*
|
|
619
|
+
* `range` is a half-open span in the zero-based UTF-16 coordinates the Language Server Protocol
|
|
620
|
+
* fixes, so one numbering carries every stage's location and each tool's own numbering is
|
|
621
|
+
* converted where it is read rather than where it is rendered. It is absent when the stage's tool
|
|
622
|
+
* reported no location, which happens for a whole-file diagnostic. A runtime failure is not one of
|
|
623
|
+
* those: a failure Vitest reported at a stack frame carries that frame's position.
|
|
624
|
+
*
|
|
625
|
+
* Each stage supplies the extent its own tool gives it. The type stage spans the diagnostic's
|
|
626
|
+
* reported length, and the lint stage carries the span the language server published. A tool that
|
|
627
|
+
* reports a point rather than a span, as a stack frame does, produces a zero-width range at that
|
|
628
|
+
* point, which is the same value a span of no width would carry.
|
|
629
|
+
*
|
|
630
|
+
* `formatIssue` renders `start.line` one-based, because that is the numbering an editor shows.
|
|
615
631
|
*
|
|
616
632
|
* @example
|
|
617
633
|
* ```ts
|
|
@@ -619,7 +635,7 @@ export declare const isStage: Guard<Stage>;
|
|
|
619
635
|
* origin: 'claimant',
|
|
620
636
|
* path: 'src/core/greeting.ts',
|
|
621
637
|
* message: "Type 'string' is not assignable to type 'number'.",
|
|
622
|
-
* line:
|
|
638
|
+
* range: { start: { line: 0, character: 6 }, end: { line: 0, character: 13 } },
|
|
623
639
|
* }
|
|
624
640
|
* ```
|
|
625
641
|
*/
|
|
@@ -630,8 +646,8 @@ export declare interface Issue {
|
|
|
630
646
|
readonly path: string;
|
|
631
647
|
/** The diagnostic or failure message. */
|
|
632
648
|
readonly message: string;
|
|
633
|
-
/**
|
|
634
|
-
readonly
|
|
649
|
+
/** Zero-based UTF-16 span the tool reported, or absent when it reported no location. */
|
|
650
|
+
readonly range?: LSPRange;
|
|
635
651
|
}
|
|
636
652
|
|
|
637
653
|
/**
|
|
@@ -863,9 +879,12 @@ export declare interface ProbeErrorOptions {
|
|
|
863
879
|
*
|
|
864
880
|
* @remarks
|
|
865
881
|
* `arm` fires when the boot control has reported red and the service will answer calls; a probe
|
|
866
|
-
* arriving before it awaits that step rather than starting a second one.
|
|
867
|
-
*
|
|
868
|
-
*
|
|
882
|
+
* arriving before it awaits that step rather than starting a second one. An arming attempt that
|
|
883
|
+
* rejects fires `error` instead, as it rejects, so a host waiting on `arm` reads the refusal rather
|
|
884
|
+
* than waiting on a state no event describes. The rejected attempt is still retained for retry, and
|
|
885
|
+
* the next `prove` runs the boot controls again, so a workspace that cannot arm surfaces one `error`
|
|
886
|
+
* per attempt. `expire` fires when the coordinator's own deadline destroyed a stage and a
|
|
887
|
+
* replacement took its place, which is the only way a synchronous infinite loop is ever reported.
|
|
869
888
|
*
|
|
870
889
|
* @example
|
|
871
890
|
* ```ts
|
|
@@ -879,7 +898,7 @@ export declare type ProbeEventMap = {
|
|
|
879
898
|
readonly prove: readonly [verdict: Verdict];
|
|
880
899
|
/** The coordinator's deadline fired at one stage and that stage was replaced before this event. */
|
|
881
900
|
readonly expire: readonly [claim: Claim];
|
|
882
|
-
/** A fault surfaced for observation. */
|
|
901
|
+
/** A fault surfaced for observation, once per fault, including a rejected arming attempt. */
|
|
883
902
|
readonly error: readonly [error: unknown];
|
|
884
903
|
};
|
|
885
904
|
|
|
@@ -1094,7 +1113,7 @@ export declare interface Toolchain {
|
|
|
1094
1113
|
* origin: 'claimant',
|
|
1095
1114
|
* path: 'src/core/farewell.ts',
|
|
1096
1115
|
* message: 'not assignable',
|
|
1097
|
-
* line:
|
|
1116
|
+
* range: { start: { line: 0, character: 6 }, end: { line: 0, character: 13 } },
|
|
1098
1117
|
* }
|
|
1099
1118
|
* const verdict: Verdict = {
|
|
1100
1119
|
* id: '88a5addc-7d33-40dc-9a5a-104b71f8787d',
|
package/dist/src/core/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { EmitterHooks } from '@orkestrel/emitter';
|
|
|
4
4
|
import { EmitterInterface } from '@orkestrel/emitter';
|
|
5
5
|
import { Guard } from '@orkestrel/contract';
|
|
6
6
|
import { LiteralShape } from '@orkestrel/contract';
|
|
7
|
+
import { LSPRange } from '@orkestrel/lsp';
|
|
7
8
|
import { ObjectShape } from '@orkestrel/contract';
|
|
8
9
|
import { StringShape } from '@orkestrel/contract';
|
|
9
10
|
|
|
@@ -345,6 +346,12 @@ export declare function formatCheck(check: Check): string;
|
|
|
345
346
|
/**
|
|
346
347
|
* Renders one tool message as a single line an agent can classify and locate.
|
|
347
348
|
*
|
|
349
|
+
* @remarks
|
|
350
|
+
* `Issue.range` is stored zero-based, and this is the only place it is rendered, so the one-based
|
|
351
|
+
* line an editor shows is derived here and nowhere else. The rendered location carries the line
|
|
352
|
+
* alone: a reader opens a file at a line, and the column would widen every issue line in every
|
|
353
|
+
* verdict for a precision that address does not use.
|
|
354
|
+
*
|
|
348
355
|
* @param issue - The message and location a stage reported
|
|
349
356
|
* @returns The bracketed origin, location, and message, separated by spaces
|
|
350
357
|
*
|
|
@@ -354,7 +361,7 @@ export declare function formatCheck(check: Check): string;
|
|
|
354
361
|
* origin: 'claimant',
|
|
355
362
|
* path: 'src/core/greeting.ts',
|
|
356
363
|
* message: 'not assignable',
|
|
357
|
-
* line:
|
|
364
|
+
* range: { start: { line: 0, character: 6 }, end: { line: 0, character: 13 } },
|
|
358
365
|
* }
|
|
359
366
|
* const whole: Issue = {
|
|
360
367
|
* origin: 'claimant',
|
|
@@ -609,9 +616,18 @@ export declare const isStage: Guard<Stage>;
|
|
|
609
616
|
* path, the lint stage from the document URI it opened, and the runtime stage from the generated
|
|
610
617
|
* specification it wrote to the test path the case declared.
|
|
611
618
|
*
|
|
612
|
-
* `
|
|
613
|
-
*
|
|
614
|
-
*
|
|
619
|
+
* `range` is a half-open span in the zero-based UTF-16 coordinates the Language Server Protocol
|
|
620
|
+
* fixes, so one numbering carries every stage's location and each tool's own numbering is
|
|
621
|
+
* converted where it is read rather than where it is rendered. It is absent when the stage's tool
|
|
622
|
+
* reported no location, which happens for a whole-file diagnostic. A runtime failure is not one of
|
|
623
|
+
* those: a failure Vitest reported at a stack frame carries that frame's position.
|
|
624
|
+
*
|
|
625
|
+
* Each stage supplies the extent its own tool gives it. The type stage spans the diagnostic's
|
|
626
|
+
* reported length, and the lint stage carries the span the language server published. A tool that
|
|
627
|
+
* reports a point rather than a span, as a stack frame does, produces a zero-width range at that
|
|
628
|
+
* point, which is the same value a span of no width would carry.
|
|
629
|
+
*
|
|
630
|
+
* `formatIssue` renders `start.line` one-based, because that is the numbering an editor shows.
|
|
615
631
|
*
|
|
616
632
|
* @example
|
|
617
633
|
* ```ts
|
|
@@ -619,7 +635,7 @@ export declare const isStage: Guard<Stage>;
|
|
|
619
635
|
* origin: 'claimant',
|
|
620
636
|
* path: 'src/core/greeting.ts',
|
|
621
637
|
* message: "Type 'string' is not assignable to type 'number'.",
|
|
622
|
-
* line:
|
|
638
|
+
* range: { start: { line: 0, character: 6 }, end: { line: 0, character: 13 } },
|
|
623
639
|
* }
|
|
624
640
|
* ```
|
|
625
641
|
*/
|
|
@@ -630,8 +646,8 @@ export declare interface Issue {
|
|
|
630
646
|
readonly path: string;
|
|
631
647
|
/** The diagnostic or failure message. */
|
|
632
648
|
readonly message: string;
|
|
633
|
-
/**
|
|
634
|
-
readonly
|
|
649
|
+
/** Zero-based UTF-16 span the tool reported, or absent when it reported no location. */
|
|
650
|
+
readonly range?: LSPRange;
|
|
635
651
|
}
|
|
636
652
|
|
|
637
653
|
/**
|
|
@@ -863,9 +879,12 @@ export declare interface ProbeErrorOptions {
|
|
|
863
879
|
*
|
|
864
880
|
* @remarks
|
|
865
881
|
* `arm` fires when the boot control has reported red and the service will answer calls; a probe
|
|
866
|
-
* arriving before it awaits that step rather than starting a second one.
|
|
867
|
-
*
|
|
868
|
-
*
|
|
882
|
+
* arriving before it awaits that step rather than starting a second one. An arming attempt that
|
|
883
|
+
* rejects fires `error` instead, as it rejects, so a host waiting on `arm` reads the refusal rather
|
|
884
|
+
* than waiting on a state no event describes. The rejected attempt is still retained for retry, and
|
|
885
|
+
* the next `prove` runs the boot controls again, so a workspace that cannot arm surfaces one `error`
|
|
886
|
+
* per attempt. `expire` fires when the coordinator's own deadline destroyed a stage and a
|
|
887
|
+
* replacement took its place, which is the only way a synchronous infinite loop is ever reported.
|
|
869
888
|
*
|
|
870
889
|
* @example
|
|
871
890
|
* ```ts
|
|
@@ -879,7 +898,7 @@ export declare type ProbeEventMap = {
|
|
|
879
898
|
readonly prove: readonly [verdict: Verdict];
|
|
880
899
|
/** The coordinator's deadline fired at one stage and that stage was replaced before this event. */
|
|
881
900
|
readonly expire: readonly [claim: Claim];
|
|
882
|
-
/** A fault surfaced for observation. */
|
|
901
|
+
/** A fault surfaced for observation, once per fault, including a rejected arming attempt. */
|
|
883
902
|
readonly error: readonly [error: unknown];
|
|
884
903
|
};
|
|
885
904
|
|
|
@@ -1094,7 +1113,7 @@ export declare interface Toolchain {
|
|
|
1094
1113
|
* origin: 'claimant',
|
|
1095
1114
|
* path: 'src/core/farewell.ts',
|
|
1096
1115
|
* message: 'not assignable',
|
|
1097
|
-
* line:
|
|
1116
|
+
* range: { start: { line: 0, character: 6 }, end: { line: 0, character: 13 } },
|
|
1098
1117
|
* }
|
|
1099
1118
|
* const verdict: Verdict = {
|
|
1100
1119
|
* id: '88a5addc-7d33-40dc-9a5a-104b71f8787d',
|
package/dist/src/core/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { andOf, arrayOf, arrayShape, holds, isError, isNonEmptyString, isNumber, isString, literalOf, literalShape, objectShape, recordOf, stringShape } from "@orkestrel/contract";
|
|
2
|
+
import { isLSPRange } from "@orkestrel/lsp";
|
|
2
3
|
//#region src/core/constants.ts
|
|
3
4
|
/**
|
|
4
5
|
* Lists the stages a claim passes through, in the order a verdict reports them.
|
|
@@ -335,8 +336,8 @@ var isIssue = recordOf({
|
|
|
335
336
|
origin: isParty,
|
|
336
337
|
path: isString,
|
|
337
338
|
message: isString,
|
|
338
|
-
|
|
339
|
-
}, ["
|
|
339
|
+
range: isLSPRange
|
|
340
|
+
}, ["range"]);
|
|
340
341
|
/**
|
|
341
342
|
* Checks whether a value carries one stage's outcome.
|
|
342
343
|
*
|
|
@@ -424,6 +425,12 @@ var isVerdict = recordOf({
|
|
|
424
425
|
/**
|
|
425
426
|
* Renders one tool message as a single line an agent can classify and locate.
|
|
426
427
|
*
|
|
428
|
+
* @remarks
|
|
429
|
+
* `Issue.range` is stored zero-based, and this is the only place it is rendered, so the one-based
|
|
430
|
+
* line an editor shows is derived here and nowhere else. The rendered location carries the line
|
|
431
|
+
* alone: a reader opens a file at a line, and the column would widen every issue line in every
|
|
432
|
+
* verdict for a precision that address does not use.
|
|
433
|
+
*
|
|
427
434
|
* @param issue - The message and location a stage reported
|
|
428
435
|
* @returns The bracketed origin, location, and message, separated by spaces
|
|
429
436
|
*
|
|
@@ -433,7 +440,7 @@ var isVerdict = recordOf({
|
|
|
433
440
|
* origin: 'claimant',
|
|
434
441
|
* path: 'src/core/greeting.ts',
|
|
435
442
|
* message: 'not assignable',
|
|
436
|
-
* line:
|
|
443
|
+
* range: { start: { line: 0, character: 6 }, end: { line: 0, character: 13 } },
|
|
437
444
|
* }
|
|
438
445
|
* const whole: Issue = {
|
|
439
446
|
* origin: 'claimant',
|
|
@@ -445,7 +452,8 @@ var isVerdict = recordOf({
|
|
|
445
452
|
* ```
|
|
446
453
|
*/
|
|
447
454
|
function formatIssue(issue) {
|
|
448
|
-
const
|
|
455
|
+
const range = issue.range;
|
|
456
|
+
const where = range === void 0 ? issue.path : `${issue.path}:${range.start.line + 1}`;
|
|
449
457
|
return `[${issue.origin}] ${where} ${issue.message}`;
|
|
450
458
|
}
|
|
451
459
|
/**
|