@termwright/conformance 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +189 -0
- package/dist/index.d.ts +351 -0
- package/dist/index.js +1129 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -0
- package/src/fixtures/adversarial-peer.mjs +567 -0
- package/src/fixtures/generic-app.mjs +236 -0
- package/src/fixtures/ink-probe-app.mjs +23 -0
- package/src/fixtures/prompt-app.mjs +97 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapter-conformance.ts","../src/support/probe.ts","../src/support/pty.ts","../../probe-ink/src/annotations.ts","../../probe-ink/src/observe.ts","../../probe-ink/src/geometry.ts","../../probe-ink/src/version.ts","../../probe-ink/src/session.ts","../../recognizers/src/recognize.ts","../../recognizers/src/naming.ts","../../recognizers/src/ink.ts","../../recognizers/src/opentui.ts","../../probe-ink/src/shim.ts","../../probe-ink/src/runtime.ts","../../probe-ink/src/launch.ts"],"sourcesContent":["/**\n * The adapter contract suite — the part of this package that is meant to be\n * used from outside it.\n *\n * An adapter is conforming when five things hold, and they are the same five in\n * every language: it stays dormant without an endpoint, it completes the\n * handshake, every snapshot it publishes is valid, it orders each revision as\n * snapshot → commit → marker-after-the-frame, and it survives losing the\n * channel without taking the application with it.\n *\n * The suite drives the adapter as a subprocess and observes only bytes and\n * frames, so a Python, Go or Rust adapter self-certifies exactly like the Ink\n * one — nothing here imports an adapter.\n */\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { ADAPTER_CAPABILITIES, validateSnapshot, DEFAULT_LIMITS } from '@termwright/protocol';\nimport type { SemanticSnapshot } from '@termwright/protocol';\nimport { AdapterProbe, MARKER_TEXT_PREFIX, type AdapterCommand, type ProbeObservation } from './support/probe.js';\nimport { commandAvailable, ptyAvailable } from './support/pty.js';\n\n/** How to start, drive and stop the adapter under test. */\nexport interface AdapterConformanceOptions {\n /** Name of the adapter, used in the test titles. */\n readonly name: string;\n /** Command that starts the instrumented application. */\n spawn(): AdapterCommand;\n /**\n * Optional command rendering the same UI with the adapter compiled out. When\n * given, the dormant run is compared against it byte for byte — the strongest\n * form of the dormant rule. Without it, a dormant run is only checked for\n * silence on the wire.\n */\n baseline?(): AdapterCommand;\n /** Text that proves the first frame reached the terminal. */\n readonly ready: string | RegExp;\n /**\n * An input that changes the screen, and the text that proves it landed.\n *\n * The suite sends it **more than once** — a dormant run, a run that has to\n * produce a second revision, and a run after the channel was cut all need a\n * render. Pick something whose repetition is harmless.\n */\n readonly interaction: { readonly input: string; readonly expect: string | RegExp };\n /**\n * An input that makes the application exit, and the status it exits with.\n *\n * It must work from **any** state repeated `interaction` can reach. A key\n * that quits only while a particular widget has focus is not a quit input:\n * the tview example's documented `q` types into its text field once focus has\n * cycled that far, so its registration uses Ctrl+C instead.\n */\n readonly quit: { readonly input: string; readonly exitCode?: number };\n readonly columns?: number;\n readonly rows?: number;\n /** Assert that published bounds are viewport-absolute (an `absolute-bounds` claim). */\n readonly expectAbsoluteBounds?: boolean;\n /**\n * Opt out of the \"publishes a tree before any input\" obligation.\n *\n * By default an adapter must publish a non-empty tree once the handshake\n * completes, with no input sent — an app that is addressable only after the\n * first keystroke is not addressable at all to a driver that has just\n * launched it. Some apps legitimately render nothing until an event arrives;\n * pass a reason, which is printed in the test title so the exemption stays\n * visible rather than becoming folklore.\n */\n readonly treeBeforeInput?: { readonly required: false; readonly reason: string };\n /**\n * How to make the application log, for an adapter that announces the `logs`\n * capability. Without it the log obligations are skipped; with it they are\n * asserted, and an adapter that announces `logs` but never delivers one\n * fails here rather than in a user's test.\n */\n /**\n * How to check the normative adapter conventions (protocol README, \"Adapter\n * semantics conventions\"). Rules 1, 2 and 4 are largely judgement calls from\n * outside; what is listed here is what a subprocess can actually observe.\n *\n * A rule an adapter cannot follow is a *declared deviation*, not a failure:\n * name it in `deviations` and the matching check is skipped with the reason\n * in the test title, so the exemption stays visible instead of becoming\n * folklore.\n */\n readonly conventions?: {\n /** A test id the fixture sets by author annotation (rule 3). */\n readonly annotatedTestId?: string;\n /** A textbox whose field is empty, to prove `value: ''` (rule 5). */\n readonly emptyTextboxTestId?: string;\n /** A container with no name of its own, wrapping text (rule 2). */\n readonly unnamedContainerTestId?: string;\n /**\n * Test ids whose `value` is author-annotated. The role gate in rule 5\n * bounds *derived* values; an annotation may put one on any role, and only\n * the registration knows which is which.\n */\n readonly annotatedValues?: readonly string[];\n /**\n * The adapter's README. Its `## Deviations` section is the single source of\n * truth for what this adapter cannot do (rule 6), so a declared limitation\n * is read from there rather than repeated in the registration — two copies\n * of the same fact disagree eventually, and the README is the one a user\n * reads.\n */\n readonly readmePath?: string;\n };\n readonly logs?: {\n /**\n * Input that makes the application write a record. Omit it for an app that\n * logs on its own (at startup, say) — the obligation then waits for a\n * record rather than provoking one.\n */\n readonly input?: string;\n /** A substring of the logged message, used to prove it stayed off-screen. */\n readonly expect: string;\n };\n /** How long the handshake may take. Default 10 s. */\n readonly timeoutMs?: number;\n /**\n * A command that must succeed before this adapter can be certified here —\n * its interpreter, or a build step that produces the binary `spawn` runs.\n * When it fails the whole suite skips and the reason is in the block's name,\n * exactly as a missing pseudo-terminal does.\n */\n readonly requires?: {\n readonly probe: readonly string[];\n readonly label: string;\n readonly cwd?: string;\n readonly timeoutMs?: number;\n };\n}\n\n/**\n * Asserts that what the adapter's deltas say the tree is matches what the\n * adapter itself reports when asked. A producer that also composed would only\n * prove it agrees with itself, so the composition here is the protocol's own\n * `applyTreeDelta` and the comparison is against a `get-tree` answer.\n */\nasync function assertDeltasCompose(probe: AdapterProbe, timeoutMs: number): Promise<void> {\n const { expect } = await import('vitest');\n const observation = probe.observe();\n\n expect(observation.compositionError, 'the adapter produced a delta nobody could apply').toBeNull();\n expect(observation.composed).not.toBeNull();\n expect(observation.deltas.length).toBeGreaterThan(0);\n\n const authoritative = await probe.requestTree(timeoutMs);\n expect(authoritative, 'the adapter answered no get-tree').not.toBeNull();\n\n const composed = observation.composed as SemanticSnapshot;\n const truth = authoritative as SemanticSnapshot;\n const byId = (nodes: SemanticSnapshot['nodes']): SemanticSnapshot['nodes'] =>\n [...nodes].sort((left, right) => left.id.localeCompare(right.id));\n\n expect(byId(truth.nodes)).toEqual(byId(composed.nodes));\n expect([...truth.rootIds].sort()).toEqual([...composed.rootIds].sort());\n}\n\n/**\n * Directory the convention summaries are written to, and read back by\n * `scripts/conformance.mjs` when it prints the matrix.\n */\nexport const CONVENTION_SUMMARY_DIR = join(tmpdir(), 'termwright-conformance-conventions');\n\n/**\n * Records what each rule concluded, so the matrix can print a per-adapter\n * roll-up of declared deviations.\n *\n * This exists because a hand-maintained table of per-adapter gaps went stale\n * within one round of being written — a generated one cannot. Nothing here is\n * a gate: the file is a report, and the tests already decided pass or fail.\n */\nfunction writeConventionSummary(\n name: string,\n declared: Map<string, string[]>,\n outcomes: readonly ConventionOutcome[],\n): void {\n try {\n mkdirSync(CONVENTION_SUMMARY_DIR, { recursive: true });\n const file = join(CONVENTION_SUMMARY_DIR, `${name.replace(/[^\\w.-]+/gu, '_')}.json`);\n writeFileSync(\n file,\n JSON.stringify(\n {\n adapter: name,\n declared: Object.fromEntries(declared),\n outcomes,\n // A rule declared in the README that no check covers: the suite\n // cannot confirm or refute it, and saying so is more honest than\n // letting it read as verified.\n unverified: [...declared.keys()].filter(\n (rule) => !outcomes.some((outcome) => outcome.rule === rule),\n ),\n },\n null,\n 2,\n ),\n 'utf8',\n );\n } catch {\n // A report that cannot be written must not fail a conformance run.\n }\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms);\n timer.unref?.();\n });\n}\n\n/**\n * Waits until the child stops writing, so two captures end at comparable\n * points. A stabilisation, not a deadline: an app that never stops writing\n * makes the comparison fail loudly rather than pass by accident.\n */\nasync function settle(probe: AdapterProbe, quietMs = 250, budgetMs = 5_000): Promise<void> {\n const deadline = Date.now() + budgetMs;\n let seen = -1;\n for (;;) {\n const length = probe.observe().stdout.length;\n if (length === seen) return;\n seen = length;\n if (Date.now() >= deadline) return;\n await new Promise((resolve) => {\n setTimeout(resolve, quietMs);\n });\n }\n}\n\n/**\n * Rule numbers declared in an adapter's `## Deviations` section.\n *\n * Three shapes are in use and all are accepted, because the suite should not\n * dictate anyone's prose: `**Rule 2 — …**` (a heading per entry, Ink),\n * `- **…** (rule 3).` (the number at the end of a bullet, the language\n * clients), and a markdown table whose first column is `2 — …` (OpenTUI).\n *\n * Entries that name no rule are kept under `other`: they are still declared\n * limitations, and dropping them would make the roll-up quietly incomplete.\n */\nexport function parseDeclaredDeviations(readme: string): Map<string, string[]> {\n const declared = new Map<string, string[]>();\n const start = readme.indexOf('## Deviations');\n if (start < 0) return declared;\n const rest = readme.slice(start + '## Deviations'.length);\n const end = rest.indexOf('\\n## ');\n const section = end < 0 ? rest : rest.slice(0, end);\n\n for (const line of section.split('\\n')) {\n for (const match of line.matchAll(/\\*\\*Rule (\\d+)\\s*—\\s*([^*]+?)\\.?\\*\\*/gu)) {\n add(declared, match[1] as string, (match[2] as string).trim());\n }\n for (const match of line.matchAll(/\\*\\*(.+?)\\*\\*\\s*\\(rule (\\d+)\\)/gu)) {\n add(declared, match[2] as string, (match[1] as string).trim());\n }\n const trimmed = line.trim();\n if (trimmed.startsWith('|') && !/^\\|[\\s:|-]*\\|?$/u.test(trimmed) && !/^\\|\\s*rule\\s*\\|/iu.test(trimmed)) {\n // A table row. The rule number and its title share the first cell\n // (`2 — name sources`); a row that names no rule carries its text in the\n // second cell instead.\n const cells = trimmed.split('|').slice(1, -1).map((cell) => cell.trim());\n const numbered = /^(\\d+)\\s*[—-]\\s*(.+)$/u.exec(cells[0] ?? '');\n if (numbered !== null) {\n add(declared, numbered[1] as string, (numbered[2] as string).trim());\n } else if ((cells[1] ?? '').length > 0) {\n add(declared, 'other', cells[1] as string);\n }\n }\n }\n return declared;\n}\n\nfunction add(map: Map<string, string[]>, key: string, value: string): void {\n map.set(key, [...(map.get(key) ?? []), value]);\n}\n\n/** What a convention check concluded, for the run summary. */\ninterface ConventionOutcome {\n readonly rule: string;\n readonly what: string;\n readonly status: 'compliant' | 'documented' | 'checked-despite-declaration' | 'violation';\n readonly detail?: string;\n}\n\n/** Roles that are containers: never named from what they contain (rule 2). */\nconst CONTAINER_ROLES: ReadonlySet<string> = new Set([\n 'region',\n 'dialog',\n 'list',\n 'table',\n 'application',\n 'menu',\n]);\n\nconst snapshotsOf = (observation: ProbeObservation): SemanticSnapshot[] =>\n observation.messages\n .filter((entry) => entry.message.type === 'snapshot')\n .map((entry) => (entry.message as { snapshot: SemanticSnapshot }).snapshot);\n\n/**\n * Registers the adapter contract suite for one adapter.\n *\n * Call it at the top level of a test file; it declares its own `describe`.\n *\n * `vitest` is imported dynamically, so the package stays importable from a\n * plain script that only wants the fixture paths or the probe. That is why the\n * function is async: `await` it at the top level of the test file, which is\n * where vitest collects the suite from.\n *\n * @example\n * ```ts\n * await runAdapterConformance({\n * name: 'my-framework-probe',\n * spawn: () => ({ command: ['node', 'app.mjs'] }),\n * ready: 'Ready',\n * interaction: { input: '\\t', expect: '[Save]' },\n * quit: { input: 'q', exitCode: 0 },\n * });\n * ```\n */\nexport async function runAdapterConformance(options: AdapterConformanceOptions): Promise<void> {\n const { afterAll, beforeAll, describe, expect, it } = await import('vitest');\n const timeout = options.timeoutMs ?? 10_000;\n const toolchain =\n options.requires === undefined ||\n commandAvailable(options.requires.probe, {\n ...(options.requires.cwd === undefined ? {} : { cwd: options.requires.cwd }),\n ...(options.requires.timeoutMs === undefined ? {} : { timeoutMs: options.requires.timeoutMs }),\n });\n const probeOptions = {\n ...(options.columns === undefined ? {} : { columns: options.columns }),\n ...(options.rows === undefined ? {} : { rows: options.rows }),\n };\n\n const title = !ptyAvailable()\n ? `adapter conformance: ${options.name} (skipped: no pseudo-terminal here)`\n : toolchain\n ? `adapter conformance: ${options.name}`\n : `adapter conformance: ${options.name} (skipped: ${options.requires?.label ?? 'toolchain'} unavailable)`;\n\n describe.skipIf(!ptyAvailable() || !toolchain)(title, { timeout: timeout * 4 }, () => {\n describe('the dormant rule', () => {\n it('opens nothing and emits no marker without an endpoint', async () => {\n const probe = await AdapterProbe.start(options.spawn(), { ...probeOptions, instrument: false });\n try {\n await probe.waitForText(options.ready, timeout);\n await probe.write(options.interaction.input);\n await probe.waitForText(options.interaction.expect, timeout);\n\n const observation = probe.observe();\n expect(observation.connections).toBe(0);\n expect(observation.messages).toHaveLength(0);\n expect(observation.text).not.toContain(MARKER_TEXT_PREFIX);\n } finally {\n await probe.stop();\n }\n });\n\n it.skipIf(options.baseline === undefined)(\n 'produces the same bytes as a build without the adapter',\n async () => {\n // Nothing is written to the child during this comparison. A\n // pseudo-terminal echoes the suite's own keystrokes, and whether an\n // echoed byte lands between two frames depends on when the app took\n // raw mode — so a stream containing our input compares the tty's\n // timing, not the adapter's output. Measured on the Ink fixture: 3\n // mismatches in 30 pairs with input (always a stray 0x09, the tab the\n // suite itself sent), 0 in 40 pairs without.\n const startup = async (\n command: AdapterCommand,\n ): Promise<{ readonly stdout: Uint8Array; readonly screen: string }> => {\n const probe = await AdapterProbe.start(command, { ...probeOptions, instrument: false });\n try {\n await probe.waitForText(options.ready, timeout);\n await settle(probe);\n const observation = probe.observe();\n return { stdout: observation.stdout, screen: observation.screen };\n } finally {\n await probe.stop();\n }\n };\n\n const instrumented = await startup(options.spawn());\n const plain = await startup((options.baseline as () => AdapterCommand)());\n if (process.platform === 'win32') {\n // ConPTY itself emits process-lifecycle control sequences. Their\n // chunking/order can differ between two identical child binaries,\n // so the raw host stream is not an application-byte oracle on\n // Windows. Feed both streams through the same terminal emulator\n // and require the complete visible grids to be identical. The\n // adjacent dormant test separately proves zero connections,\n // messages and Termwright markers.\n expect(instrumented.screen).toBe(plain.screen);\n } else {\n // POSIX PTYs expose the child stream directly: one extra escape\n // sequence from instrumentation is a real dormant-rule failure.\n expect(Buffer.from(instrumented.stdout).toString('binary')).toBe(\n Buffer.from(plain.stdout).toString('binary'),\n );\n }\n },\n );\n });\n\n describe('an instrumented session', () => {\n let probe: AdapterProbe;\n /** Everything observed before a single byte was written to the child. */\n let beforeInput: ProbeObservation;\n\n beforeAll(\n async () => {\n probe = await AdapterProbe.start(options.spawn(), probeOptions);\n await probe.waitForText(options.ready, timeout);\n await probe.waitFor(\n (observation) => snapshotsOf(observation).length > 0,\n timeout,\n 'a first snapshot from the adapter',\n );\n beforeInput = probe.observe();\n },\n // Starting the process, waiting for its first frame, and completing the\n // adapter handshake are separate bounded operations. Vitest otherwise\n // applies its 10-second hook default even though the suite has a larger\n // timeout, which makes real adapters flaky under a parallel root run.\n timeout * 4,\n );\n\n afterAll(async () => {\n await probe?.stop();\n });\n\n it('completes the handshake before anything else', async () => {\n const { messages, connections } = probe.observe();\n const first = messages[0];\n\n expect(connections).toBe(1);\n expect(first?.message.type).toBe('hello');\n const hello = first?.message as { protocol: string; adapter: { name: string; version: string }; capabilities: readonly string[] };\n expect(hello.protocol).toBe('termwright/1');\n expect(hello.adapter.name.length).toBeGreaterThan(0);\n expect(hello.adapter.version.length).toBeGreaterThan(0);\n expect(hello.capabilities.every((entry) => (ADAPTER_CAPABILITIES as readonly string[]).includes(entry))).toBe(true);\n expect(hello.capabilities).toContain('tree');\n // A second hello, or a hello after other traffic, is a protocol fault.\n expect(messages.filter((entry) => entry.message.type === 'hello')).toHaveLength(1);\n\n // Nothing may precede the handshake, log records least of all: their\n // budget is granted *in* the reply to this message.\n const firstLog = messages.findIndex((entry) => entry.message.type === 'log');\n expect(firstLog === -1 || firstLog > 0).toBe(true);\n });\n\n it(\n options.treeBeforeInput === undefined\n ? 'publishes a usable tree before any input'\n : `publishes a usable tree before any input (exempt: ${options.treeBeforeInput.reason})`,\n { skip: options.treeBeforeInput !== undefined },\n () => {\n const latest = snapshotsOf(beforeInput).at(-1);\n\n // A driver launches an app and addresses it. An adapter that only\n // publishes once a key has been pressed is not addressable at that\n // moment, and a suite that sends input before looking would never\n // notice — which is exactly how this class of bug reached a shipped\n // adapter.\n expect(latest, 'no snapshot arrived before any input was sent').toBeDefined();\n expect(latest?.nodes.length ?? 0).toBeGreaterThan(0);\n expect(latest?.rootIds.length ?? 0).toBeGreaterThan(0);\n\n // A tree of one anonymous root is empty in every sense that matters:\n // nothing in it can be located by role and name.\n const addressable = (latest?.nodes ?? []).filter(\n (node) => node.name.length > 0 || node.testId !== undefined,\n );\n expect(addressable.length, 'the tree has no node that a locator could address').toBeGreaterThan(0);\n },\n );\n\n it('publishes only valid snapshots, bound to this session', async () => {\n const observation = probe.observe();\n const snapshots = snapshotsOf(observation);\n\n expect(observation.faults).toEqual([]);\n expect(snapshots.length).toBeGreaterThan(0);\n for (const snapshot of snapshots) {\n expect(validateSnapshot(snapshot, DEFAULT_LIMITS)).toMatchObject({ ok: true });\n expect(snapshot.sessionId).toBe(probe.sessionId);\n expect(snapshot.v).toBe(1);\n const ids = new Set(snapshot.nodes.map((node) => node.id));\n for (const node of snapshot.nodes) {\n if (node.parentId === undefined) expect(snapshot.rootIds).toContain(node.id);\n else expect(ids.has(node.parentId)).toBe(true);\n }\n }\n\n const revisions = snapshots.map((snapshot) => snapshot.revision);\n expect([...revisions]).toEqual([...new Set(revisions)].sort((left, right) => left - right));\n });\n\n it.skipIf(options.expectAbsoluteBounds !== true)('publishes viewport-absolute bounds', () => {\n const snapshots = snapshotsOf(probe.observe());\n const latest = snapshots[snapshots.length - 1];\n expect(latest).toBeDefined();\n const bounded = latest?.nodes.filter((node) => node.bounds !== undefined) ?? [];\n expect(bounded.length).toBeGreaterThan(0);\n for (const node of bounded) {\n const bounds = node.bounds as { row: number; column: number; width: number; height: number };\n expect(bounds.row).toBeGreaterThanOrEqual(0);\n expect(bounds.column).toBeGreaterThanOrEqual(0);\n expect(bounds.row).toBeLessThan(latest?.rows ?? 0);\n expect(bounds.column).toBeLessThan(latest?.columns ?? 0);\n }\n });\n\n it('orders every revision as snapshot, then commit, then marker', async () => {\n await probe.write(options.interaction.input);\n await probe.waitForText(options.interaction.expect, timeout);\n await probe.waitFor(\n (observation) => observation.markers.length >= 2,\n timeout,\n 'a second render marker',\n );\n\n // The socket and the terminal are independent streams, so a marker can\n // be read before the frame describing the same revision has been\n // parsed. The contract is that each revision *eventually* has all\n // three parts; waiting for that is not leniency, it is the difference\n // between asserting the contract and asserting arrival order.\n const complete = (observation: ProbeObservation): boolean =>\n observation.markers.every(\n (marker) =>\n observation.messages.some(\n (entry) =>\n entry.message.type === 'snapshot' &&\n (entry.message as { snapshot: SemanticSnapshot }).snapshot.revision === marker.revision,\n ) &&\n observation.messages.some(\n (entry) => entry.message.type === 'revision-commit' && entry.message.revision === marker.revision,\n ),\n );\n await probe.waitFor(complete, timeout, 'every marker paired with a snapshot and a commit');\n\n const observation = probe.observe();\n const markers = observation.markers;\n expect(markers.length).toBeGreaterThan(0);\n expect(markers.map((marker) => marker.revision)).toEqual(\n [...markers.map((marker) => marker.revision)].sort((left, right) => left - right),\n );\n\n for (const marker of markers) {\n const snapshot = observation.messages.find(\n (entry) =>\n entry.message.type === 'snapshot' &&\n (entry.message as { snapshot: SemanticSnapshot }).snapshot.revision === marker.revision,\n );\n const commit = observation.messages.find(\n (entry) => entry.message.type === 'revision-commit' && entry.message.revision === marker.revision,\n );\n expect(snapshot, `no snapshot for revision ${marker.revision}`).toBeDefined();\n expect(commit, `no commit for revision ${marker.revision}`).toBeDefined();\n\n const snapshotIndex = observation.messages.indexOf(snapshot!);\n expect(snapshotIndex).toBeLessThan(observation.messages.indexOf(commit!));\n }\n\n // Each marker commits a frame, so there must be output between one\n // marker and the next, and the first one cannot open the stream.\n //\n // The socket and the terminal are two independent streams, and the\n // event loop may deliver a later chunk of one before an earlier message\n // of the other. Comparing a marker's byte offset against the stdout\n // position recorded when its frame arrived therefore measures delivery\n // scheduling, not adapter ordering, and is deliberately not asserted.\n let previousEnd = 0;\n for (const marker of markers) {\n expect(marker.offset).toBeGreaterThan(previousEnd);\n previousEnd = marker.offset;\n }\n });\n\n const conventions = options.conventions ?? {};\n const readme =\n conventions.readmePath !== undefined && existsSync(conventions.readmePath)\n ? readFileSync(conventions.readmePath, 'utf8')\n : '';\n const declared = parseDeclaredDeviations(readme);\n const outcomes: ConventionOutcome[] = [];\n\n it('reads the deviations its README declares', () => {\n if (conventions.readmePath === undefined) return;\n if (!readme.includes('## Deviations')) return;\n\n // A section the parser cannot read is worse than a missing one: the\n // three-state logic silently collapses to two, and every documented\n // limitation starts reporting as an error against the adapter that\n // took the trouble to declare it.\n //\n // Detectable without understanding any shape: a section that *has\n // structure* — bullets, table rows, bold lead-ins — but yields no\n // entries is a parser gap. A section that is plain prose saying there\n // is nothing to declare is not, and must not be failed for it.\n const start = readme.indexOf('## Deviations');\n const rest = readme.slice(start + '## Deviations'.length);\n const end = rest.indexOf('\\n## ');\n const section = end < 0 ? rest : rest.slice(0, end);\n const structured = /^\\s*[-*|]/mu.test(section) || section.includes('**');\n if (!structured) return;\n\n expect(\n declared.size,\n `${options.name} has a \"## Deviations\" section with entries this suite could not ` +\n 'read; its declarations would be invisible and its documented limitations would ' +\n 'report as errors. Teach `parseDeclaredDeviations` the shape it uses.',\n ).toBeGreaterThan(0);\n });\n\n /**\n * Runs one rule and decides what its result means.\n *\n * Three states, not two. A rule an adapter cannot follow and *says* it\n * cannot follow is a documented limitation, not a failure — failing it\n * would give the first author who honestly describes their framework a\n * red run for doing exactly what rule 6 asks, which is the shortest path\n * to people hiding deviations instead of declaring them.\n *\n * A check that passes while a declaration exists is deliberately *not*\n * called stale. One rule has more aspects than a subprocess can observe:\n * Ink declares a rule 3 limitation about native identifiers while\n * satisfying the annotation half of the same rule, and both are true.\n * The summary records the coincidence so a reader can re-read the\n * README; calling it a defect would be a false signal, and a suite that\n * cries wolf gets ignored.\n */\n const convention = (rule: string, what: string, check: () => string | null): void => {\n const failure = check();\n const titles = declared.get(rule) ?? [];\n if (failure === null) {\n outcomes.push(\n titles.length === 0\n ? { rule, what, status: 'compliant' }\n : { rule, what, status: 'checked-despite-declaration', detail: titles.join('; ') },\n );\n return;\n }\n if (titles.length > 0) {\n outcomes.push({ rule, what, status: 'documented', detail: `${titles.join('; ')} — ${failure}` });\n return;\n }\n outcomes.push({ rule, what, status: 'violation', detail: failure });\n expect.fail(`convention ${rule} (${what}): ${failure}`);\n };\n\n afterAll(() => {\n writeConventionSummary(options.name, declared, outcomes);\n });\n\n it('convention 3: an author-annotated test id reaches the wire', () => {\n if (conventions.annotatedTestId === undefined) return;\n const wanted = conventions.annotatedTestId;\n convention('3', 'an annotated test id reaches the wire', () => {\n const latest = snapshotsOf(beforeInput).at(-1);\n const node = latest?.nodes.find((entry) => entry.testId === wanted);\n return node === undefined ? `no node carries the test id ${JSON.stringify(wanted)}` : null;\n });\n });\n\n it('convention 5: an empty textbox publishes an empty value', () => {\n if (conventions.emptyTextboxTestId === undefined) return;\n const wanted = conventions.emptyTextboxTestId;\n convention('5', 'an empty textbox publishes an empty value', () => {\n const latest = snapshotsOf(beforeInput).at(-1);\n const node = latest?.nodes.find((entry) => entry.testId === wanted);\n if (node === undefined) return `no node carries the test id ${JSON.stringify(wanted)}`;\n // `''` means the field is empty; absent means \"not a value-bearing\n // widget\". A wire format that drops empty strings turns the first\n // into the second and makes `toHaveValue('')` unassertable.\n return node.value === '' ? null : `the value is ${JSON.stringify(node.value)}, not an empty string`;\n });\n });\n\n it('convention 5: value is derived only for value-bearing roles', () => {\n convention('5', 'value is derived only for value-bearing roles', () => {\n const annotated = new Set(conventions.annotatedValues ?? []);\n const nodes = snapshotsOf(beforeInput).at(-1)?.nodes ?? [];\n const offenders = nodes.filter(\n (node) =>\n node.value !== undefined &&\n node.role !== 'textbox' &&\n node.role !== 'progressbar' &&\n !(node.testId !== undefined && annotated.has(node.testId)),\n );\n if (offenders.length > 0) {\n return `derived a value outside {textbox, progressbar}: ${offenders\n .map((node) => `${node.role} ${JSON.stringify(node.name)}`)\n .join(', ')}`;\n }\n // A boolean is a state, not contents: publishing `value: \"true\"`\n // makes a checkbox look like a textbox containing that word.\n const booleans = nodes.filter((node) => node.value === 'true' || node.value === 'false');\n return booleans.length === 0\n ? null\n : `published a boolean as a value on ${booleans.map((node) => node.role).join(', ')}`;\n });\n });\n\n it('convention 2: no container is named from the text it contains', () => {\n convention('2', 'no container is named from the text it contains', () => {\n const nodes = snapshotsOf(beforeInput).at(-1)?.nodes ?? [];\n const children = new Map<string, string[]>();\n for (const node of nodes) {\n if (node.parentId === undefined) continue;\n children.set(node.parentId, [...(children.get(node.parentId) ?? []), node.id]);\n }\n const descendantText = (id: string): string[] => {\n const out: string[] = [];\n const pending = [...(children.get(id) ?? [])];\n while (pending.length > 0) {\n const next = nodes.find((node) => node.id === pending.pop());\n if (next === undefined) continue;\n if (next.name.length > 0) out.push(next.name);\n pending.push(...(children.get(next.id) ?? []));\n }\n return out;\n };\n\n // Naming containers from content is what makes\n // getByRole('region', {name: 'Approve'}) match the dialog *around*\n // the button, so every ancestor of a label becomes a plausible match\n // for it. Both failure shapes are visible from the tree alone.\n const offenders = nodes\n .filter((node) => CONTAINER_ROLES.has(node.role) && node.name.length > 0)\n .filter((node) => {\n const texts = descendantText(node.id);\n const joined = texts.join(' ').replace(/\\s+/gu, ' ').trim();\n return texts.includes(node.name) || (joined.length > 0 && joined === node.name);\n });\n return offenders.length === 0\n ? null\n : `named from content: ${offenders.map((node) => `${node.role} ${JSON.stringify(node.name)}`).join(', ')}`;\n });\n });\n\n it('convention 2: a container with no label of its own has an empty name', () => {\n if (conventions.unnamedContainerTestId === undefined) return;\n const wanted = conventions.unnamedContainerTestId;\n convention('2', 'an unlabelled container has an empty name', () => {\n const node = snapshotsOf(beforeInput)\n .at(-1)\n ?.nodes.find((entry) => entry.testId === wanted);\n if (node === undefined) return `no node carries the test id ${JSON.stringify(wanted)}`;\n return node.name === '' ? null : `the container is named ${JSON.stringify(node.name)}`;\n });\n });\n\n it.skipIf(conventions.readmePath === undefined)(\n 'declares its deviations in its README (advisory)',\n () => {\n // Rules 1, 2 and 4 cannot be judged from outside a subprocess, so the\n // README is the only evidence that a difference was considered rather\n // than overlooked. Advisory on purpose: a missing heading is a\n // documentation gap, not a broken adapter, and failing here would\n // make a conformance run red for something no user can observe.\n const path = conventions.readmePath as string;\n const text = existsSync(path) ? readFileSync(path, 'utf8') : '';\n if (!text.includes('## Deviations')) {\n // Rule 6 follows the adapter, not the package: something that\n // publishes no tree has nothing to declare. Anything reaching this\n // suite does publish one, so the heading is expected here.\n process.stderr.write(\n `conformance: ${options.name} has no \"## Deviations\" section in ${path}; ` +\n 'rules 1, 2 and 4 are unverifiable from outside, so an undeclared difference is invisible\\n',\n );\n }\n expect(true).toBe(true);\n },\n );\n\n it('sends log records only if it negotiated the channel', async () => {\n const hello = beforeInput.messages[0]?.message as { capabilities: readonly string[] };\n if (hello.capabilities.includes('logs')) return;\n\n // An adapter is free not to support logs. What it is not free to do is\n // send records anyway: the budget granted in the handshake is what\n // bounds them, and one that was never granted cannot bound anything.\n // The driver closes the channel over this, so an adapter that does it\n // is broken in production rather than merely untidy.\n await probe.write(options.interaction.input);\n await probe.waitForText(options.interaction.expect, timeout);\n expect(\n probe.observe().logs,\n 'the adapter sent log records without announcing the logs capability',\n ).toEqual([]);\n });\n\n it.skipIf(options.logs === undefined)('carries a log record without printing it', async () => {\n const logs = options.logs as NonNullable<AdapterConformanceOptions['logs']>;\n const hello = probe.observe().messages[0]?.message as { capabilities: readonly string[] };\n expect(\n hello.capabilities.includes('logs'),\n 'the registration declares logs, but the adapter never announced the capability',\n ).toBe(true);\n\n const before = probe.observe().logs.length;\n if (logs.input !== undefined) await probe.write(logs.input);\n // An app that logs on its own may already have; one that logs on demand\n // has just been asked to. Either way the wait is for a record.\n await probe.waitFor(\n (observation) => observation.logs.length > (logs.input === undefined ? 0 : before),\n timeout,\n 'a log record over the negotiated channel',\n );\n\n const observation = probe.observe();\n const record = observation.logs.find((entry) => entry.message.includes(logs.expect));\n expect(record, `no log record matched ${JSON.stringify(logs.expect)}`).toBeDefined();\n // `seq` is a non-negative counter, so the first record of a session is\n // legitimately 0; what matters is the relation between records.\n expect(record?.seq).toBeGreaterThanOrEqual(0);\n\n // Strictly increasing within a session: a consumer counting errors must\n // not be able to count one twice, and a gap must mean a real loss.\n const seqs = observation.logs.map((entry) => entry.seq);\n expect(seqs).toEqual([...seqs].sort((left, right) => left - right));\n expect(new Set(seqs).size).toBe(seqs.length);\n\n // The whole point of the capability: the record reaches the driver and\n // never the terminal. A TUI that printed it would corrupt its render.\n expect(observation.screen).not.toContain(logs.expect);\n expect(observation.text).not.toContain(logs.expect);\n });\n\n it('produces deltas that compose to the tree it would have sent', async () => {\n const announced = (beforeInput.messages[0]?.message as { capabilities: readonly string[] })\n .capabilities;\n if (!announced.includes('tree-diffs')) return;\n\n // Its own session: deltas are only sent to a driver that subscribed to\n // them, and the shared session deliberately subscribes to whole trees\n // so the other obligations exercise that path.\n const diffs = await AdapterProbe.start(options.spawn(), { ...probeOptions, subscribe: 'diffs' });\n try {\n await diffs.waitForText(options.ready, timeout);\n await diffs.waitFor((observation) => observation.composed !== null, timeout);\n\n // Drive a few renders so there is something to diff.\n for (let press = 0; press < 3; press += 1) {\n await diffs.write(options.interaction.input);\n await delay(150);\n }\n await diffs.waitFor((observation) => observation.deltas.length > 0, timeout);\n await assertDeltasCompose(diffs, timeout);\n } finally {\n await diffs.stop();\n }\n });\n\n it('keeps the application alive when the channel is cut', async () => {\n const before = probe.observe();\n probe.cutChannel();\n\n await probe.write(options.interaction.input);\n // The observed text is cumulative, so `waitForText` would match output\n // the application produced before the cut. Growth is what proves it is\n // still rendering.\n await probe.waitFor(\n (observation) => observation.text.length > before.text.length,\n timeout,\n 'any further output from the child',\n );\n const after = probe.observe();\n\n // The application keeps rendering; the adapter goes quiet and does not\n // reconnect behind the driver's back.\n expect(after.text.length).toBeGreaterThan(before.text.length);\n expect(after.connections).toBe(1);\n expect(probe.exitStatus).toBeNull();\n\n await probe.write(options.quit.input);\n const status = await probe.waitForExit(timeout);\n if (options.quit.exitCode !== undefined) expect(status.code).toBe(options.quit.exitCode);\n });\n });\n });\n}\n","/**\n * A minimal driver, built for one job: watching what an adapter actually puts\n * on the wire.\n *\n * `@termwright/driver` deliberately hides frame ordering behind a settled tree,\n * which is the right API for tests of applications and the wrong one for tests\n * of adapters. The probe therefore speaks the protocol itself — endpoint,\n * handshake, framing, marker verification — and records every message together\n * with how many stdout bytes had been written when it arrived. That is what\n * makes the §4.3 ordering contract (snapshot → commit → marker-after-frame)\n * observable at all.\n *\n * It does emulate a terminal, because it has to: an adapter is free to draw by\n * positioning each run of cells (tview does), so the text a user sees exists\n * only on a rendered grid and never as contiguous bytes on the wire. Waiting\n * for text therefore reads the grid, while marker offsets read the byte stream.\n *\n * It is still not a second driver: it never locates and never acts.\n */\nimport { createServer, type Server, type Socket } from 'node:net';\nimport { readFileSync } from 'node:fs';\nimport { mkdtemp, rm } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { randomBytes, randomUUID } from 'node:crypto';\nimport {\n DEFAULT_LIMITS,\n ENV_ENDPOINT,\n ENV_PROTOCOL,\n ENV_TOKEN,\n MARKER_OSC_CODE,\n MARKER_OSC_PREFIX,\n parseAdapterMessage,\n PROTOCOL_ID,\n PROTOCOL_VERSION,\n applyTreeDelta,\n createFrameDecoder,\n encodeFrame,\n generateToken,\n verifyMarkerPayload,\n type AdapterToDriverMessage,\n type HelloAckMessage,\n type LogRecord,\n type SemanticSnapshot,\n type TreeDelta,\n} from '@termwright/protocol';\n// `@xterm/headless` is CommonJS: a named ESM import type-checks and passes\n// under vitest's transform, then fails at runtime for anyone importing the\n// built package from plain Node. The default import is the interop that works\n// in both, and is what the driver does.\nimport xh from '@xterm/headless';\nimport type { Terminal } from '@xterm/headless';\nimport { createNodePtyBackend, type PtyProcess } from '@termwright/driver';\nimport { environment } from './pty.js';\n\n/** How a fixture is started. Everything else about it is opaque to the probe. */\nexport interface AdapterCommand {\n readonly command: readonly string[];\n readonly env?: Readonly<Record<string, string>>;\n readonly cwd?: string;\n}\n\n/** One message the adapter sent, stamped with the stdout position at arrival. */\nexport interface RecordedMessage {\n readonly message: AdapterToDriverMessage;\n /** Bytes of stdout the probe had received when this frame was parsed. */\n readonly stdoutBytes: number;\n readonly atMs: number;\n}\n\n/** One verified render marker found in stdout. */\nexport interface RecordedMarker {\n readonly revision: number;\n /** Offset of the marker's first byte in the stdout stream. */\n readonly offset: number;\n readonly atMs: number;\n}\n\n/** Log budget the probe grants to an adapter that announces `logs`. */\nconst LOG_BUDGET = Object.freeze({ enabled: true, maxRecordsPerSecond: 200, burst: 400 });\n\n/** A frame the probe refused; a conforming adapter produces none. */\nexport interface RecordedFault {\n readonly code: string;\n readonly detail: string;\n}\n\nexport interface ProbeOptions {\n readonly columns?: number;\n readonly rows?: number;\n /** Set `false` to withhold the instrumentation env — the dormant-run case. */\n readonly instrument?: boolean;\n /**\n * What the probe asks the adapter to push. `'diffs'` is a preference, not a\n * prohibition: an adapter may still send a full tree when a delta would not\n * pay for itself, and the first publication always is one.\n */\n readonly subscribe?: 'snapshots' | 'diffs';\n}\n\n/** Everything the probe observed, readable while the child is still running. */\nexport interface ProbeObservation {\n readonly messages: readonly RecordedMessage[];\n readonly markers: readonly RecordedMarker[];\n readonly faults: readonly RecordedFault[];\n readonly connections: number;\n readonly stdout: Uint8Array;\n /** Raw bytes decoded as UTF-8: what was written, in order, escapes included. */\n readonly text: string;\n /** The visible grid, one row per line — what a user would actually see. */\n readonly screen: string;\n /** Application log records the adapter sent, in arrival order. */\n readonly logs: readonly LogRecord[];\n /** Deltas the adapter sent, in arrival order. */\n readonly deltas: readonly TreeDelta[];\n /**\n * The tree obtained by composing every snapshot and delta received, in order,\n * with the protocol's own `applyTreeDelta`. This is the oracle an adapter's\n * deltas are checked against: a producer that also composed would only prove\n * it agrees with itself.\n */\n readonly composed: SemanticSnapshot | null;\n /** Why composition stopped, when it did. A conforming adapter produces none. */\n readonly compositionError: string | null;\n}\n\n/**\n * A marker on the wire: `OSC 8487 ; twm;<rev>;<mac>` closed by BEL or ST.\n *\n * Both terminators are matched because both are legal — an implementation\n * emits BEL, but a receiver that only understood BEL would reject a\n * conforming adapter, and this probe stands in for a receiver.\n */\nconst MARKER_PATTERN = new RegExp(\n `\\\\x1b\\\\]${MARKER_OSC_CODE};(${MARKER_OSC_PREFIX}[0-9]+;[A-Za-z0-9_-]+)(?:\\\\x07|\\\\x1b\\\\\\\\)`,\n 'gu',\n);\n\n/**\n * Runs one fixture under a pseudo-terminal with a protocol endpoint attached.\n *\n * @example\n * ```ts\n * const probe = await AdapterProbe.start({ command: ['node', 'app.mjs'] }, {});\n * await probe.waitForText('Ready');\n * await probe.write('\\t');\n * const { messages, markers } = probe.observe();\n * await probe.stop();\n * ```\n */\nexport class AdapterProbe {\n readonly sessionId: string;\n readonly token: string;\n\n readonly #server: Server | null;\n readonly #directory: string | null;\n readonly #pty: PtyProcess;\n readonly #subscribe: 'snapshots' | 'diffs';\n readonly #terminal: Terminal;\n readonly #startedAt = performance.now();\n readonly #messages: RecordedMessage[] = [];\n readonly #markers: RecordedMarker[] = [];\n readonly #faults: RecordedFault[] = [];\n readonly #logs: LogRecord[] = [];\n readonly #deltas: TreeDelta[] = [];\n #composed: SemanticSnapshot | null = null;\n #compositionError: string | null = null;\n #requestId = 0;\n #chunks: Uint8Array[] = [];\n #bytes = 0;\n #text = '';\n #markerScanFrom = 0;\n #connections = 0;\n #socket: Socket | null = null;\n #exit: { code: number | null; signal: string | null } | null = null;\n /** Where the adapter writes its own account of attaching, if it writes one. */\n #debugFile: string | null = null;\n #stopped = false;\n\n private constructor(\n identity: { readonly sessionId: string; readonly token: string },\n server: Server | null,\n directory: string | null,\n pty: PtyProcess,\n size: { readonly columns: number; readonly rows: number },\n subscribe: 'snapshots' | 'diffs',\n ) {\n this.#subscribe = subscribe;\n this.sessionId = identity.sessionId;\n this.token = identity.token;\n this.#server = server;\n this.#directory = directory;\n this.#pty = pty;\n this.#terminal = new xh.Terminal({\n cols: size.columns,\n rows: size.rows,\n allowProposedApi: true,\n scrollback: 1_000,\n });\n }\n\n /** Creates the endpoint (unless dormant), then spawns the fixture. */\n static async start(command: AdapterCommand, options: ProbeOptions = {}): Promise<AdapterProbe> {\n const instrument = options.instrument ?? true;\n const sessionId = randomUUID();\n const token = generateToken();\n\n let server: Server | null = null;\n let directory: string | null = null;\n let endpoint: string | null = null;\n\n if (instrument) {\n server = createServer();\n if (process.platform === 'win32') {\n endpoint = `\\\\\\\\.\\\\pipe\\\\termwright-probe-${randomBytes(16).toString('hex')}`;\n } else {\n directory = await mkdtemp(join(tmpdir(), 'termwright-probe-'));\n endpoint = join(directory, 'semantic.sock');\n }\n const listening = server;\n const address = endpoint;\n await new Promise<void>((resolve, reject) => {\n listening.once('error', reject);\n listening.listen(address, () => {\n listening.removeListener('error', reject);\n resolve();\n });\n });\n }\n\n const env = environment(command.env);\n // A dormant run must not merely lack our endpoint — it must not inherit one\n // from whatever process is running the suite.\n delete env[ENV_ENDPOINT];\n delete env[ENV_TOKEN];\n delete env[ENV_PROTOCOL];\n if (endpoint !== null) {\n env[ENV_ENDPOINT] = endpoint;\n env[ENV_TOKEN] = token;\n env[ENV_PROTOCOL] = String(PROTOCOL_VERSION);\n }\n\n // The adapter's own account of why it did or did not attach. The probe can\n // only see the outside — no connection arrived — which leaves \"wrong\n // transport\", \"driver not listening\" and \"never started\" indistinguishable.\n // The clients write that distinction here (1bbe0f9), and a failure quotes\n // the file, so the attribution lands in the message rather than in an\n // artifact somebody has to go and find. A path, never `TERMWRIGHT_DEBUG=1`:\n // that means \"log to stderr\", which under a pty lands in the middle of the\n // frame this suite makes assertions about.\n const debugFile = join(tmpdir(), `termwright-adapter-debug-${randomBytes(8).toString('hex')}.log`);\n env['TERMWRIGHT_DEBUG_FILE'] = debugFile;\n\n const size = { columns: options.columns ?? 80, rows: options.rows ?? 24 };\n const pty = createNodePtyBackend().spawn({\n command: command.command,\n ...(command.cwd === undefined ? {} : { cwd: command.cwd }),\n env,\n columns: size.columns,\n rows: size.rows,\n });\n\n const probe = new AdapterProbe(\n { sessionId, token },\n server,\n directory,\n pty,\n size,\n options.subscribe ?? 'snapshots',\n );\n probe.#debugFile = debugFile;\n\n pty.onData((data) => probe.#onData(data));\n pty.onExit((status) => {\n probe.#exit = status;\n });\n server?.on('connection', (socket) => probe.#onConnection(socket));\n return probe;\n }\n\n /** Everything observed so far. Safe to call at any point. */\n observe(): ProbeObservation {\n return {\n messages: [...this.#messages],\n markers: [...this.#markers],\n faults: [...this.#faults],\n connections: this.#connections,\n stdout: this.#stdout(),\n text: this.#text,\n screen: this.screenText(),\n logs: this.#logs.map((entry) => entry),\n deltas: this.#deltas.map((entry) => entry),\n composed: this.#composed,\n compositionError: this.#compositionError,\n };\n }\n\n /** The visible grid as text, trailing whitespace trimmed per row. */\n screenText(): string {\n const buffer = this.#terminal.buffer.active;\n const rows: string[] = [];\n for (let row = 0; row < this.#terminal.rows; row += 1) {\n rows.push(buffer.getLine(buffer.viewportY + row)?.translateToString(true) ?? '');\n }\n return rows.join('\\n');\n }\n\n /** The child's exit status, or `null` while it is still running. */\n get exitStatus(): { code: number | null; signal: string | null } | null {\n return this.#exit;\n }\n\n /** Writes raw bytes to the child, exactly as a terminal would. */\n async write(input: string): Promise<void> {\n this.#pty.write(new TextEncoder().encode(input));\n await Promise.resolve();\n }\n\n /**\n * Resolves once `needle` appears on the rendered grid.\n *\n * Matching the byte stream instead would only work for adapters that happen\n * to write their text contiguously: a framework that positions each run of\n * cells never emits `focus: reject` as those twelve bytes in a row.\n */\n async waitForText(needle: string | RegExp, timeoutMs = 10_000): Promise<void> {\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n const screen = this.screenText();\n if (needle instanceof RegExp ? needle.test(screen) : screen.includes(needle)) return;\n if (Date.now() >= deadline) {\n throw new Error(\n `adapter conformance: ${String(needle)} never appeared on the fixture's screen\\n` +\n `screen was:\\n${screen}`,\n );\n }\n await delay(20);\n }\n }\n\n /**\n * Resolves once `predicate` holds over the current observation.\n *\n * `what` names the thing being waited for, and the failure carries what the\n * probe could see when it gave up. \"Condition never became true\" is not a\n * result anybody can act on: an adapter that never connected, one that\n * connected and published nothing, and one whose binary died all produce it,\n * and only the observation tells them apart.\n */\n async waitFor(\n predicate: (observation: ProbeObservation) => boolean,\n timeoutMs = 10_000,\n what = 'the condition',\n ): Promise<void> {\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n if (predicate(this.observe())) return;\n if (Date.now() >= deadline) {\n throw new Error(`adapter conformance: ${what} never happened — ${this.describe()}`);\n }\n await delay(20);\n }\n }\n\n /** What the probe has seen so far, for a failure that has to explain itself. */\n describe(): string {\n const { messages, connections } = this.observe();\n const kinds = new Map<string, number>();\n for (const recorded of messages) {\n const kind = recorded.message.type;\n kinds.set(kind, (kinds.get(kind) ?? 0) + 1);\n }\n const traffic =\n kinds.size === 0 ? 'no messages' : [...kinds].map(([kind, n]) => `${kind}×${n}`).join(', ');\n const screen = this.screenText().trimEnd().split('\\n').filter((line) => line.trim() !== '');\n const exit = this.#exit === null ? 'still running' : `exited ${JSON.stringify(this.#exit)}`;\n return (\n `${connections} connection(s) to the endpoint, ${traffic}; the child is ${exit}; ` +\n `last screen line: ${JSON.stringify(screen.at(-1) ?? '')}\\n${this.#adapterAccount()}`\n );\n }\n\n /**\n * What the adapter says about its own attach, if the client writes it.\n *\n * The outside view cannot tell \"dialled the wrong transport\" from \"the driver\n * was not listening\" from \"the process never started\" — all three look like\n * no connection. The clients write that distinction to `TERMWRIGHT_DEBUG_FILE`,\n * so it is quoted here rather than left in a file nobody opens. An adapter\n * that writes nothing is itself an answer, and says so.\n */\n #adapterAccount(): string {\n if (this.#debugFile === null) return 'adapter debug log: not requested';\n let contents: string;\n try {\n contents = readFileSync(this.#debugFile, 'utf8');\n } catch {\n return `adapter debug log: nothing written to ${this.#debugFile} — the client either predates the log or never ran`;\n }\n const lines = contents.trimEnd().split('\\n').slice(-12);\n return `adapter debug log (last ${lines.length} line(s)):\\n ${lines.join('\\n ')}`;\n }\n\n /** Waits for the child to exit and returns its status. */\n async waitForExit(timeoutMs = 10_000): Promise<{ code: number | null; signal: string | null }> {\n const deadline = Date.now() + timeoutMs;\n while (this.#exit === null) {\n if (Date.now() >= deadline) throw new Error('adapter conformance: the fixture never exited');\n await delay(20);\n }\n return this.#exit;\n }\n\n /** Cuts the semantic channel without touching the child: the disconnect case. */\n cutChannel(): void {\n this.#socket?.destroy();\n this.#socket = null;\n }\n\n /** Stops the child and releases the endpoint. Idempotent. */\n async stop(): Promise<void> {\n if (this.#stopped) return;\n this.#stopped = true;\n this.#socket?.destroy();\n this.#pty.dispose();\n this.#terminal.dispose();\n if (this.#server !== null) await new Promise<void>((resolve) => this.#server?.close(() => resolve()));\n if (this.#directory !== null) await rm(this.#directory, { recursive: true, force: true }).catch(() => {});\n if (this.#debugFile !== null) await rm(this.#debugFile, { force: true }).catch(() => {});\n }\n\n // -------------------------------------------------------------------------\n\n #stdout(): Uint8Array {\n const out = new Uint8Array(this.#bytes);\n let offset = 0;\n for (const chunk of this.#chunks) {\n out.set(chunk, offset);\n offset += chunk.length;\n }\n this.#chunks = [out];\n return out;\n }\n\n #onData(data: Uint8Array): void {\n this.#chunks.push(data);\n this.#bytes += data.length;\n this.#text += Buffer.from(data).toString('utf8');\n this.#terminal.write(data);\n this.#scanMarkers();\n }\n\n /** Finds render markers in the byte stream and verifies each against the token. */\n #scanMarkers(): void {\n MARKER_PATTERN.lastIndex = this.#markerScanFrom;\n for (;;) {\n const match = MARKER_PATTERN.exec(this.#text);\n if (match === null) break;\n const payload = match[1] ?? '';\n const verified = verifyMarkerPayload(payload, this.token, this.sessionId);\n if (verified === null) {\n this.#faults.push({ code: 'marker', detail: `marker did not verify: ${JSON.stringify(payload)}` });\n } else {\n this.#markers.push({ revision: verified.revision, offset: match.index, atMs: this.#now() });\n }\n this.#markerScanFrom = match.index + match[0].length;\n }\n MARKER_PATTERN.lastIndex = 0;\n }\n\n #onConnection(socket: Socket): void {\n this.#connections += 1;\n if (this.#socket !== null) {\n // One adapter per session; a second connection is a conformance failure.\n this.#faults.push({ code: 'second-connection', detail: 'the adapter opened a second channel' });\n socket.destroy();\n return;\n }\n this.#socket = socket;\n const decoder = createFrameDecoder(DEFAULT_LIMITS.maxFrameBytes);\n socket.on('data', (chunk: Buffer) => {\n let frames: readonly unknown[];\n try {\n frames = decoder.push(chunk);\n } catch (error) {\n this.#faults.push({ code: 'framing', detail: error instanceof Error ? error.message : String(error) });\n socket.destroy();\n return;\n }\n for (const frame of frames) this.#onFrame(socket, frame);\n });\n socket.on('error', () => socket.destroy());\n socket.on('close', () => {\n if (this.#socket === socket) this.#socket = null;\n });\n }\n\n #onFrame(socket: Socket, frame: unknown): void {\n // The probe parses with the real protocol parser: a hand-written check\n // would only prove that the fixture agrees with itself.\n const parsed = parseAdapterMessage(frame, DEFAULT_LIMITS);\n if (!parsed.ok) {\n this.#faults.push({ code: parsed.code, detail: parsed.detail });\n return;\n }\n this.#messages.push({ message: parsed.message, stdoutBytes: this.#bytes, atMs: this.#now() });\n if (parsed.message.type === 'log') this.#logs.push(parsed.message.record);\n if (parsed.message.type === 'snapshot') this.#composed = parsed.message.snapshot;\n if (parsed.message.type === 'tree-delta') this.#compose(parsed.message);\n if (parsed.message.type !== 'hello') return;\n\n const ack: HelloAckMessage = {\n type: 'hello-ack',\n protocol: PROTOCOL_ID,\n sessionId: this.sessionId,\n limits: DEFAULT_LIMITS,\n // Deltas are only ever sent to a driver that asked for them.\n subscribe:\n this.#subscribe === 'diffs' && parsed.message.capabilities.includes('tree-diffs')\n ? 'diffs'\n : 'snapshots',\n marker: { enabled: parsed.message.capabilities.includes('render-revisions') },\n // Granted only to an adapter that asked: an adapter that never announced\n // `logs` must not be handed a budget it can then claim it was given.\n ...(parsed.message.capabilities.includes('logs') ? { logs: LOG_BUDGET } : {}),\n };\n socket.write(encodeFrame(ack, DEFAULT_LIMITS.maxFrameBytes));\n }\n\n /** Applies one delta to the held tree, recording the first failure. */\n #compose(delta: TreeDelta & { readonly type: 'tree-delta' }): void {\n const { type: _type, ...body } = delta;\n this.#deltas.push(body);\n const base = this.#composed;\n if (base === null) {\n this.#compositionError ??= `delta ${body.baseRevision}→${body.revision} arrived before any full tree`;\n return;\n }\n const result = applyTreeDelta(base, body, DEFAULT_LIMITS);\n if (!result.ok) {\n this.#compositionError ??= `delta ${body.baseRevision}→${body.revision} did not compose (${result.code}): ${result.detail}`;\n return;\n }\n this.#composed = result.snapshot;\n }\n\n /**\n * Asks the adapter for a full tree and resolves with it.\n *\n * This is what turns composition into a check rather than a belief: the\n * locally composed tree is compared against one the adapter built itself.\n */\n async requestTree(timeoutMs = 10_000): Promise<SemanticSnapshot | null> {\n const socket = this.#socket;\n if (socket === null) return null;\n this.#requestId += 1;\n const requestId = this.#requestId;\n socket.write(encodeFrame({ type: 'get-tree', requestId }, DEFAULT_LIMITS.maxFrameBytes));\n\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n const reply = this.#messages.find(\n (entry) =>\n entry.message.type === 'get-tree-result' &&\n (entry.message as { requestId: number }).requestId === requestId,\n );\n if (reply !== undefined) {\n return (reply.message as { snapshot?: SemanticSnapshot }).snapshot ?? null;\n }\n if (Date.now() >= deadline) return null;\n await delay(20);\n }\n }\n\n #now(): number {\n return performance.now() - this.#startedAt;\n }\n}\n\n/** The marker prefix, re-exported so suites can assert on dormant output. */\nexport const MARKER_TEXT_PREFIX = `\\x1b]${MARKER_OSC_CODE};${MARKER_OSC_PREFIX}`;\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms);\n timer.unref?.();\n });\n}\n","/**\n * Shared harness plumbing for the conformance suites: fixture resolution,\n * pseudo-terminal availability and session bookkeeping.\n *\n * Every suite in this package drives real child processes. Where no PTY can be\n * opened — a sandboxed CI runner, a missing prebuild — the suites skip rather\n * than fail, because \"this machine cannot open a terminal\" is not a conformance\n * result. Set `TERMWRIGHT_SKIP_PTY=1` to skip them explicitly.\n */\nimport { spawnSync } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { launchTerminal, createNodePtyBackend, type LaunchOptions, type TerminalHarness } from '@termwright/driver';\nimport { withProbe } from '@termwright/probe-ink';\n\n/**\n * Absolute path of a fixture that ships with this package.\n *\n * The fixtures are shipped as sources rather than bundled, because they are\n * meant to be launched as `node <path>` by suites in other packages and in\n * other languages' CI. They are therefore located from the package root, which\n * is the one anchor that is the same whether this module was loaded from `src/`\n * during development or from the bundled `dist/`.\n */\nexport function fixturePath(name: string): string {\n return join(packageRoot(), 'src', 'fixtures', name);\n}\n\n/**\n * Absolute path inside the repository, for suites that reach outside this\n * package — the language clients live in `clients/`, not in `packages/`.\n */\nexport function repositoryPath(...segments: readonly string[]): string {\n return join(packageRoot(), '..', '..', ...segments);\n}\n\nlet cachedRoot: string | null = null;\n\nfunction packageRoot(): string {\n if (cachedRoot !== null) return cachedRoot;\n let directory = dirname(fileURLToPath(import.meta.url));\n for (;;) {\n if (existsSync(join(directory, 'package.json'))) {\n cachedRoot = directory;\n return directory;\n }\n const parent = dirname(directory);\n if (parent === directory) {\n throw new Error('@termwright/conformance: could not locate the package root from this module');\n }\n directory = parent;\n }\n}\n\n/** The fixtures published for adapter authors and other packages' suites. */\nexport const CONFORMANCE_FIXTURES = Object.freeze({\n /** Uninstrumented app: proves the generic fallback (§20.1). */\n generic: () => fixturePath('generic-app.mjs'),\n /** Shell-shaped app emitting OSC 133 marks; `--marks=off` suppresses them. */\n prompt: () => fixturePath('prompt-app.mjs'),\n /** Normal-render Ink app used to exercise launch-time probe attachment. */\n inkProbe: () => fixturePath('ink-probe-app.mjs'),\n /** Hostile wire peer; takes a scenario name as its first argument (§20.3). */\n adversarialPeer: () => fixturePath('adversarial-peer.mjs'),\n});\n\nlet cachedPty: boolean | null = null;\n\n/**\n * Whether this machine can open a pseudo-terminal at all.\n *\n * @returns `false` when `TERMWRIGHT_SKIP_PTY=1` or spawning a trivial child\n * through the PTY backend throws. Probed once and cached.\n */\nexport function ptyAvailable(): boolean {\n if (cachedPty !== null) return cachedPty;\n if (process.env['TERMWRIGHT_SKIP_PTY'] === '1') {\n cachedPty = false;\n return cachedPty;\n }\n try {\n const pty = createNodePtyBackend().spawn({\n command: [process.execPath, '-e', 'process.exit(0)'],\n env: environment(),\n columns: 20,\n rows: 4,\n });\n pty.dispose();\n cachedPty = true;\n } catch {\n cachedPty = false;\n }\n return cachedPty;\n}\n\n/** `process.env` with the `undefined` values dropped, as PTY spawning requires. */\nexport function environment(extra?: Readonly<Record<string, string>>): Record<string, string> {\n const env: Record<string, string> = {};\n for (const [key, value] of Object.entries(process.env)) {\n if (value !== undefined) env[key] = value;\n }\n return { ...env, ...extra };\n}\n\n/**\n * Absolute path of a Python interpreter that can import the given modules.\n *\n * Two problems at once. The executable is `python3` on POSIX and often only\n * `python` on Windows, and `node-pty` resolves neither reliably — the first\n * Windows run failed with `File not found:` while a `spawnSync` probe of the\n * same name had just succeeded. Asking the interpreter for `sys.executable`\n * turns whichever name works into an absolute path a pty can spawn.\n *\n * @returns the interpreter path, or `null` when no candidate can import them.\n */\nexport function pythonWith(modules: readonly string[]): string | null {\n const script = `import ${modules.join(', ')}, sys; print(sys.executable)`;\n for (const candidate of ['python3', 'python']) {\n if (!commandAvailable([candidate, '-c', `import ${modules.join(', ')}`], { quiet: true })) continue;\n const resolved = spawnSync(candidate, ['-c', script], { encoding: 'utf8', env: environment() });\n const path = (resolved.stdout ?? '').trim();\n if (resolved.status === 0 && path.length > 0) return path;\n }\n return null;\n}\n\n/** Options every conformance session shares; suites override what they need. */\nexport interface FixtureLaunchOptions extends Partial<Omit<LaunchOptions, 'command'>> {\n /** Extra arguments appended to `node <fixture>`. */\n readonly args?: readonly string[];\n /** Attach the zero-config framework probe to the otherwise normal command. */\n readonly probe?: 'ink';\n /**\n * Text that proves the fixture started drawing. Waiting for it here rather\n * than in each suite is what lets the failure be diagnosed: a fixture that\n * printed nothing at all failed to start, which is a very different problem\n * from one that started and drew the wrong thing.\n */\n readonly ready?: string | RegExp;\n}\n\n/**\n * A set of sessions closed together, so one wedged fixture cannot leak a child\n * process into the next test.\n *\n * @example\n * ```ts\n * const sessions = createSessionPool();\n * afterEach(sessions.closeAll);\n * const terminal = await sessions.launch(CONFORMANCE_FIXTURES.generic());\n * ```\n */\nexport interface SessionPool {\n launch(fixture: string, options?: FixtureLaunchOptions): Promise<TerminalHarness>;\n closeAll(): Promise<void>;\n}\n\nexport function createSessionPool(): SessionPool {\n const open: TerminalHarness[] = [];\n return {\n async launch(fixture, options = {}) {\n const { args = [], probe, ready: _ready, ...launchOptions } = options;\n const base = [process.execPath, fixture, ...args];\n const terminal = await launchTerminal({\n command: probe === 'ink' ? withProbe('node', base).command : base,\n columns: 80,\n rows: 24,\n // No `env` and no `envMode`: the suites run against the secret-safe\n // 'replace' default, which is what a user gets. Forwarding the runner's\n // whole environment here would quietly make every suite an 'inherit'\n // test and leave the default uncovered.\n // Conformance runs start a fresh Node process, a pseudo-terminal and a\n // socket per test, and several suites run beside other builds. The\n // driver's defaults are tight enough that machine load, rather than the\n // implementation, would decide the result — a genuine failure still\n // fails here, just later.\n timeouts: { text: 30_000, action: 30_000, exit: 30_000, idle: 10_000 },\n ...launchOptions,\n });\n open.push(terminal);\n if (options.ready !== undefined) await waitForStart(terminal, options.ready, fixture);\n return terminal;\n },\n async closeAll() {\n while (open.length > 0) {\n const terminal = open.pop();\n await terminal?.close();\n }\n },\n };\n}\n\n/**\n * Waits for a fixture's first output, and says which way it failed.\n *\n * `waitForText` can only report that text never appeared, which reads as a\n * rendering problem. Distinguishing \"the child wrote nothing at all\" from \"the\n * child wrote something else\" is the difference between hunting a fixture bug\n * and hunting a spawn or scheduling one — and on a heavily loaded machine it is\n * always the latter.\n */\nasync function waitForStart(\n terminal: TerminalHarness,\n ready: string | RegExp,\n fixture: string,\n): Promise<void> {\n let bytes = 0;\n const off = terminal.events.on('output', ({ data }) => {\n bytes += data.length;\n });\n try {\n await terminal.waitForText(ready);\n } catch (error) {\n const exit = await Promise.race([terminal.exit, Promise.resolve(null)]);\n const detail =\n bytes === 0\n ? `it produced no output at all${exit === null ? ' and is still running' : `; it exited ${JSON.stringify(exit)}`}`\n : `it produced ${bytes} bytes but never drew ${String(ready)}`;\n throw new Error(`conformance: ${fixture.split('/').pop() ?? fixture} did not start — ${detail}`, {\n cause: error,\n });\n } finally {\n off();\n }\n}\n\n/**\n * Whether a toolchain command succeeds here.\n *\n * Used to decide, at collection time, whether an adapter written in another\n * language can be certified on this machine at all. A missing interpreter is\n * not a conformance result, so the suite skips and says why — the same rule as\n * a missing pseudo-terminal.\n *\n * @param command - argv to run; exit status 0 counts as available.\n * @returns `false` when the command is missing, fails, or exceeds `timeoutMs`.\n *\n * @example\n * ```ts\n * commandAvailable(['python3', '-c', 'import textual']);\n * ```\n */\nexport function commandAvailable(\n command: readonly string[],\n options: { readonly cwd?: string; readonly timeoutMs?: number; readonly quiet?: boolean } = {},\n): boolean {\n const [binary, ...args] = command;\n if (binary === undefined) return false;\n const printable = command.join(' ');\n try {\n const result = spawnSync(binary, args, {\n ...(options.cwd === undefined ? {} : { cwd: options.cwd }),\n timeout: options.timeoutMs ?? 120_000,\n encoding: 'utf8',\n env: environment(),\n });\n if (result.status === 0) return true;\n // A skipped suite has to say *why* it skipped, or a probe that broke looks\n // exactly like a toolchain that was never installed.\n if (options.quiet === true) return false;\n const reason =\n result.error?.message ??\n (result.signal === null ? `exit ${String(result.status)}` : `signal ${result.signal}`);\n process.stderr.write(\n `conformance: probe \\`${printable}\\` failed (${reason})\\n` +\n `${(result.stderr ?? '').trim().split('\\n').slice(-3).join('\\n')}\\n`,\n );\n return false;\n } catch (error) {\n if (options.quiet !== true) {\n process.stderr.write(\n `conformance: probe \\`${printable}\\` could not run: ${error instanceof Error ? error.message : String(error)}\\n`,\n );\n }\n return false;\n }\n}\n\n/**\n * Turns on the child's mouse reporting and reports whether the emulator can\n * see that it happened.\n *\n * ConPTY consumes the child's DECSET, so on Windows the mode reads `'unknown'`\n * — the child did enable tracking, the terminal just cannot say so. Branching\n * on the *observed* mode rather than on `process.platform` keeps one code path:\n * a platform that starts reporting the mode tightens the assertions by itself,\n * and one that stops loosens them, without anyone editing a list of platforms.\n *\n * @returns `true` when the mode is observable, so mode-specific claims (an\n * exact tracking level, a refusal for the wrong level) can be asserted.\n */\nexport async function enableMouseReporting(\n terminal: TerminalHarness,\n mode: 'click' | 'drag',\n): Promise<boolean> {\n const expected = mode === 'click' ? 'vt200' : 'drag';\n await terminal.press(mode === 'click' ? 'm' : 'M');\n await pollUntil(() => {\n const tracking = terminal.screen().modes.mouseTracking;\n return tracking === expected || tracking === 'unknown';\n });\n return terminal.screen().modes.mouseTracking === expected;\n}\n\n/**\n * Asks the child to enable focus reporting and reports what the emulator made\n * of it.\n *\n * Same shape as the mouse, and the same three answers: `'on'` where the DECSET\n * was seen, `'unknown'` where the platform reports the host's state rather than\n * the child's — ConPTY does that — and `'off'` only while the request is still\n * in flight. Waiting for a settled answer is what keeps the caller off the\n * third: branching on `'off'` would mean branching on how loaded the machine\n * is, which is how this last flaked.\n */\nexport async function enableFocusReporting(\n terminal: TerminalHarness,\n): Promise<'on' | 'off' | 'unknown'> {\n await terminal.press('f');\n await pollUntil(() => terminal.screen().modes.focusReporting !== 'off', 3_000).catch(\n () => undefined,\n );\n return terminal.screen().modes.focusReporting;\n}\n\n/**\n * Runs a burst out and answers where the published revision came to rest.\n *\n * A wall-clock budget for \"two hundred revisions arrived\" is a bet on the\n * platform's throughput, and it is the wrong question: what a burst settles on\n * depends on how far the terminal falls behind the socket while it is running,\n * which is the platform's business. Measured rather than assumed — the caller\n * then asserts what is true of the answer it got. `target` only bounds the\n * wait; reaching it is not required here.\n */\nexport async function settledRevision(\n terminal: TerminalHarness,\n target: number,\n stallMs = 15_000,\n): Promise<number> {\n let seen = terminal.semanticTree()?.revision ?? 0;\n let progressed = Date.now();\n for (;;) {\n const current = terminal.semanticTree()?.revision ?? 0;\n if (current >= target) return current;\n if (current > seen) {\n seen = current;\n progressed = Date.now();\n }\n if (Date.now() - progressed > stallMs) return seen;\n await new Promise((resolve) => {\n const timer = setTimeout(resolve, 25);\n timer.unref?.();\n });\n }\n}\n\n/** How many times each diagnostic code was recorded — for failure messages. */\nexport function diagnosticTally(terminal: TerminalHarness): string {\n const counts = new Map<string, number>();\n for (const entry of terminal.diagnostics()) {\n counts.set(entry.code, (counts.get(entry.code) ?? 0) + 1);\n }\n return [...counts].map(([code, count]) => `${code}×${count}`).join(', ') || 'none';\n}\n\n/** What the emulator currently makes of the child's focus-reporting request. */\nexport function focusMode(terminal: TerminalHarness): 'on' | 'off' | 'unknown' {\n return terminal.screen().modes.focusReporting;\n}\n\n/** True while the emulator cannot see which mouse mode the child asked for. */\nexport function mouseModeHidden(terminal: TerminalHarness): boolean {\n return terminal.screen().modes.mouseTracking === 'unknown';\n}\n\n/** Polls a predicate until it holds, or throws once the budget is spent. */\nexport async function pollUntil(predicate: () => boolean, timeoutMs = 15_000): Promise<void> {\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n if (predicate()) return;\n if (Date.now() >= deadline) throw new Error('conformance: condition never became true');\n await new Promise((resolve) => {\n const timer = setTimeout(resolve, 25);\n timer.unref?.();\n });\n }\n}\n\n/** Catch helper: awaits a rejection and returns the error, typed. */\nexport async function rejection<T>(promise: Promise<T>): Promise<unknown> {\n try {\n await promise;\n return null;\n } catch (error) {\n return error;\n }\n}\n","import type {\n ProbeAnnotations,\n ProtocolLimits,\n} from '@termwright/protocol';\nimport { validateProbeAnnotations } from '@termwright/protocol';\n\nconst REGISTRY = Symbol.for('termwright.annotation.ink.v1');\n\ninterface StoredAnnotation {\n readonly role?: unknown;\n readonly name?: unknown;\n readonly description?: unknown;\n readonly testId?: unknown;\n readonly extended?: unknown;\n readonly actions?: unknown;\n readonly labelledBy?: readonly WeakRef<object>[];\n readonly describedBy?: readonly WeakRef<object>[];\n}\n\ninterface AnnotationSlot {\n readonly current?: StoredAnnotation;\n}\n\ninterface AnnotationChannel {\n readonly entries: WeakMap<object, AnnotationSlot>;\n readonly listeners: Set<() => void>;\n}\n\nfunction channel(): AnnotationChannel {\n const scope = globalThis as Record<PropertyKey, unknown>;\n const present = scope[REGISTRY] as Partial<AnnotationChannel> | undefined;\n if (present?.entries instanceof WeakMap && present.listeners instanceof Set) {\n return present as AnnotationChannel;\n }\n const created: AnnotationChannel = {\n entries: new WeakMap<object, AnnotationSlot>(),\n listeners: new Set<() => void>(),\n };\n Object.defineProperty(scope, REGISTRY, { configurable: true, value: created });\n return created;\n}\n\n/** Re-capture after an annotation attaches to a newly reconciled host. */\nexport function onInkAnnotationChange(handler: () => void): () => void {\n const listeners = channel().listeners;\n listeners.add(handler);\n return () => listeners.delete(handler);\n}\n\nfunction strings(\n refs: unknown,\n idFor: (node: object) => string,\n maxTargets: number,\n): string[] | null | undefined {\n if (refs === undefined) return undefined;\n if (!Array.isArray(refs)) return null;\n const length = Object.getOwnPropertyDescriptor(refs, 'length')?.value;\n if (!Number.isSafeInteger(length) || length < 0 || length > maxTargets) return null;\n const ids: string[] = [];\n for (let index = 0; index < length; index += 1) {\n try {\n const descriptor = Object.getOwnPropertyDescriptor(refs, String(index));\n if (descriptor === undefined || !('value' in descriptor)) return null;\n const ref = descriptor.value;\n if (!(ref instanceof WeakRef)) return null;\n const target = WeakRef.prototype.deref.call(ref) as object | undefined;\n if (target !== undefined) ids.push(idFor(target));\n } catch {\n return null;\n }\n }\n return ids.length === 0 ? undefined : ids;\n}\n\nfunction ownData(value: object, key: keyof StoredAnnotation): unknown {\n const descriptor = Object.getOwnPropertyDescriptor(value, key);\n return descriptor !== undefined && 'value' in descriptor ? descriptor.value : undefined;\n}\n\n/** Read author intent without taking a runtime dependency on the optional SDK. */\nexport function annotationForInkNode(\n node: object,\n idFor: (node: object) => string,\n limits: ProtocolLimits,\n): ProbeAnnotations | undefined {\n try {\n const slot = channel().entries.get(node);\n const value = slot?.current;\n if (value === undefined) return undefined;\n const role = ownData(value, 'role');\n const name = ownData(value, 'name');\n const description = ownData(value, 'description');\n const testId = ownData(value, 'testId');\n const extended = ownData(value, 'extended');\n const actions = ownData(value, 'actions');\n const labelledBy = strings(ownData(value, 'labelledBy'), idFor, limits.maxRelationTargets);\n const describedBy = strings(ownData(value, 'describedBy'), idFor, limits.maxRelationTargets);\n const candidate = {\n ...(role === undefined ? {} : { role }),\n ...(name === undefined ? {} : { name }),\n ...(description === undefined ? {} : { description }),\n ...(testId === undefined ? {} : { testId }),\n ...(extended === undefined ? {} : { extended }),\n ...(actions === undefined ? {} : { actions }),\n ...(labelledBy === undefined ? {} : { labelledBy }),\n ...(describedBy === undefined ? {} : { describedBy }),\n };\n if (Object.keys(candidate).length === 0) return undefined;\n const validated = validateProbeAnnotations(candidate, limits);\n return validated.ok ? validated.annotations : undefined;\n } catch {\n return undefined;\n }\n}\n","/** Ink's retained host tree to framework-neutral Probe IR. */\n\nimport type {\n ProbeAccessibilityHints,\n ProbeFrame,\n ProbeObject,\n ProbeObservedState,\n ProbeRect,\n ProbeUnobservableField,\n ProtocolLimits,\n} from '@termwright/protocol';\nimport { annotationForInkNode } from './annotations.js';\n\n/** Structural subset of Ink's DOM node. No runtime import from `ink`. */\nexport interface InkDomElement {\n readonly nodeName: 'ink-root' | 'ink-box' | 'ink-text' | 'ink-virtual-text';\n readonly childNodes: readonly InkDomNode[];\n readonly parentNode?: InkDomElement;\n readonly style?: { readonly display?: string };\n readonly internal_static?: boolean;\n readonly staticNode?: InkDomElement;\n readonly internal_accessibility?: {\n readonly role?: string;\n readonly state?: {\n readonly checked?: boolean;\n readonly disabled?: boolean;\n readonly expanded?: boolean;\n readonly readonly?: boolean;\n readonly selected?: boolean;\n readonly busy?: boolean;\n readonly multiline?: boolean;\n };\n };\n}\n\nexport interface InkTextNode {\n readonly nodeName: '#text';\n readonly nodeValue: string;\n readonly parentNode?: InkDomElement;\n}\n\nexport type InkDomNode = InkDomElement | InkTextNode;\n\n/** Public Ink measurement function, kept injectable for tests and isolation. */\nexport type MeasureElement = (\n node: InkDomElement,\n) => { readonly x: number; readonly y: number; readonly width: number; readonly height: number };\n\nexport interface ObserveInkOptions {\n readonly frame: number;\n readonly limits: ProtocolLimits;\n /** The probe's own hidden Box. It is the sole injected node and is omitted. */\n readonly excluded?: InkDomElement | null;\n readonly measureElement?: MeasureElement;\n /** Only true when live-region coordinates are proven viewport-absolute. */\n readonly includeGeometry?: boolean;\n}\n\nexport interface InkObservation {\n readonly frame: ProbeFrame;\n readonly truncated: boolean;\n}\n\nconst isElement = (node: InkDomNode): node is InkDomElement => node.nodeName !== '#text';\n\n/**\n * Observe every Ink host element, including plain unannotated layout boxes.\n *\n * Source component names do not survive Ink's reconciler. `frameworkType` is\n * therefore deliberately one of Ink's four host kinds; inventing `Button` or\n * a component stack here would be false provenance.\n */\nexport function observeInkTree(root: InkDomElement, options: ObserveInkOptions): InkObservation {\n const objects: ProbeObject[] = [];\n const ids = identityStore(root);\n let truncated = false;\n\n const visit = (\n node: InkDomElement,\n parent: InkDomElement | undefined,\n depth: number,\n ancestorHidden: boolean,\n ): void => {\n if (node === options.excluded) return;\n if (depth > options.limits.maxDepth || objects.length >= options.limits.maxNodes) {\n truncated = true;\n return;\n }\n\n const hidden = ancestorHidden || node.style?.display === 'none';\n const state = observedState(node, !hidden);\n const annotations = annotationForInkNode(\n node,\n (target) => ids.idFor(target as InkDomElement),\n options.limits,\n );\n const accessibility = observedAccessibility(node);\n const geometry = geometryOf(node, options);\n // Probe IR's `text` is the object's own text, never a descendant-derived\n // accessible name. The recognizer applies name-from-content over the tree.\n const text = isTextHost(node) ? textOf(node, options.limits.maxStringBytes) : undefined;\n const unobservable = unobservableFor(node, geometry !== undefined, text !== undefined);\n\n objects.push({\n identity: { kind: 'stable', value: ids.idFor(node) },\n frameworkType: node.nodeName,\n ...(parent === undefined ? {} : { parent: ids.idFor(parent) }),\n ...(geometry === undefined ? {} : { geometry: { intendedRect: geometry } }),\n ...(state === undefined ? {} : { state }),\n ...(text === undefined ? {} : { text }),\n ...(accessibility === undefined ? {} : { accessibility }),\n ...(annotations === undefined ? {} : { annotations }),\n unobservable,\n });\n\n for (const child of node.childNodes) {\n // Raw `#text` values are payload owned by their `ink-text` host, not a\n // fifth host kind. The text is retained on that host above.\n if (isElement(child)) visit(child, node, depth + 1, hidden);\n }\n };\n\n visit(root, undefined, 0, false);\n return { frame: { frame: options.frame, objects }, truncated };\n}\n\n/** Weak identity is stable for exactly the lifetime of Ink's host object. */\nconst stores = new WeakMap<InkDomElement, IdentityStore>();\n\ninterface IdentityStore {\n idFor(node: InkDomElement): string;\n}\n\nfunction identityStore(root: InkDomElement): IdentityStore {\n let store = stores.get(root);\n if (store !== undefined) return store;\n const ids = new WeakMap<InkDomElement, string>();\n let nextId = 0;\n store = {\n idFor(node) {\n const existing = ids.get(node);\n if (existing !== undefined) return existing;\n nextId += 1;\n const id = String(nextId);\n ids.set(node, id);\n return id;\n },\n };\n stores.set(root, store);\n return store;\n}\n\nfunction isTextHost(node: InkDomElement): boolean {\n return node.nodeName === 'ink-text' || node.nodeName === 'ink-virtual-text';\n}\n\nfunction observedAccessibility(node: InkDomElement): ProbeAccessibilityHints | undefined {\n const role = node.internal_accessibility?.role;\n return role === undefined ? undefined : { role };\n}\n\nfunction observedState(node: InkDomElement, displayed: boolean): ProbeObservedState | undefined {\n const accessibility = node.internal_accessibility?.state;\n const state: ProbeObservedState = {\n displayed,\n ...(accessibility?.checked === undefined ? {} : { checked: accessibility.checked }),\n ...(accessibility?.disabled === undefined ? {} : { disabled: accessibility.disabled }),\n ...(accessibility?.expanded === undefined ? {} : { expanded: accessibility.expanded }),\n ...(accessibility?.readonly === undefined ? {} : { readonly: accessibility.readonly }),\n ...(accessibility?.selected === undefined ? {} : { selected: accessibility.selected }),\n ...(accessibility?.busy === undefined ? {} : { busy: accessibility.busy }),\n ...(accessibility?.multiline === undefined ? {} : { multiline: accessibility.multiline }),\n };\n return state;\n}\n\nfunction geometryOf(\n node: InkDomElement,\n options: ObserveInkOptions,\n): ProbeRect | undefined {\n if (options.includeGeometry !== true || options.measureElement === undefined) return undefined;\n if (node.nodeName === 'ink-virtual-text') return undefined;\n try {\n const measured = options.measureElement(node);\n if (\n !Number.isFinite(measured.x)\n || !Number.isFinite(measured.y)\n || !Number.isFinite(measured.width)\n || !Number.isFinite(measured.height)\n || measured.width <= 0\n || measured.height <= 0\n ) return undefined;\n return {\n row: Math.trunc(measured.y),\n column: Math.trunc(measured.x),\n width: Math.trunc(measured.width),\n height: Math.trunc(measured.height),\n };\n } catch {\n return undefined;\n }\n}\n\nfunction textOf(node: InkDomElement, maxBytes: number): string | undefined {\n const parts: string[] = [];\n let bytes = 0;\n\n const append = (value: string): void => {\n for (const codePoint of value) {\n const size = Buffer.byteLength(codePoint, 'utf8');\n if (bytes + size > maxBytes) return;\n parts.push(codePoint);\n bytes += size;\n }\n };\n\n // Raw #text children are this host's payload. Nested host elements retain\n // their own ProbeObjects, so folding them in here would violate the IR's\n // own-text contract and duplicate them during name-from-content inference.\n for (const child of node.childNodes) {\n if (bytes >= maxBytes) break;\n if (!isElement(child)) append(child.nodeValue);\n }\n const text = parts.join('').replace(/\\s+/gu, ' ').trim();\n return text.length === 0 ? undefined : text;\n}\n\nfunction unobservableFor(\n node: InkDomElement,\n hasGeometry: boolean,\n hasText: boolean,\n): readonly ProbeUnobservableField[] {\n const result: ProbeUnobservableField[] = [\n 'focused',\n 'value',\n 'selectedIndex',\n 'textSelection',\n 'scroll',\n 'scrollExtent',\n 'visibleRect',\n 'paintOrder',\n ];\n const state = node.internal_accessibility?.state;\n if (state?.disabled === undefined) result.push('disabled');\n if (state?.checked === undefined) result.push('checked');\n if (state?.expanded === undefined) result.push('expanded');\n if (state?.readonly === undefined) result.push('readonly');\n if (state?.selected === undefined) result.push('selected');\n if (state?.busy === undefined) result.push('busy');\n if (state?.multiline === undefined) result.push('multiline');\n if (!hasGeometry) result.push('intendedRect');\n if (!hasText && isTextHost(node)) result.push('text');\n return result;\n}\n\n/** `<Static>` moves the live region down by an offset Ink does not expose. */\nexport function hasStaticContent(root: InkDomElement): boolean {\n const stack: InkDomElement[] = [root];\n while (stack.length > 0) {\n const node = stack.pop() as InkDomElement;\n if (node.internal_static === true || node.staticNode !== undefined) return true;\n for (const child of node.childNodes) if (isElement(child)) stack.push(child);\n }\n return false;\n}\n","/** Truthful gate for Ink's live-region coordinates. */\n\nimport isInCi from 'is-in-ci';\n\nexport interface GeometryGateOptions {\n readonly alternateScreen: boolean;\n readonly interactive?: boolean;\n readonly stdoutIsTTY: boolean;\n /** Injectable only so the default-interactivity branch is deterministic. */\n readonly inCi?: boolean;\n}\n\n/**\n * Reproduce Ink 7's `resolveInteractiveOption` and\n * `resolveAlternateScreenOption`. Layout coordinates are terminal-absolute\n * only if Ink actually entered the alternate screen on a TTY.\n */\nexport function canPublishInkGeometry(options: GeometryGateOptions): boolean {\n const interactive = options.interactive\n ?? (!(options.inCi ?? isInCi) && options.stdoutIsTTY);\n return options.alternateScreen && interactive && options.stdoutIsTTY;\n}\n","/** Synchronized from package.json by scripts/sync-protocol-version.mjs. */\nexport const PACKAGE_VERSION = '0.1.0';\n","/** A committed Ink host tree to snapshot/commit/marker publication. */\n\nimport type { ProbeInfo, ProtocolLimits, SemanticSnapshot } from '@termwright/protocol';\nimport { recognize } from '@termwright/recognizers';\nimport type { ProbeChannel } from '@termwright/probe-runtime';\nimport {\n hasStaticContent,\n observeInkTree,\n type InkDomElement,\n type MeasureElement,\n} from './observe.js';\nimport { PACKAGE_VERSION } from './version.js';\n\n/** What this probe truthfully offers at handshake time. */\nexport function probeInfo(): ProbeInfo {\n return {\n framework: 'ink',\n probeVersion: PACKAGE_VERSION,\n identityKind: 'stable',\n // The optional @termwright/ink SDK writes author intent to the shared weak\n // registry. Ink's own aria metadata travels separately as framework facts.\n capabilities: ['stable-identity', 'annotations'],\n };\n}\n\nexport interface InkSessionOptions {\n readonly channel: ProbeChannel;\n readonly resolveRoot: () => InkDomElement | null;\n readonly resolveExcluded?: () => InkDomElement | null;\n readonly measureElement: MeasureElement;\n readonly stdout: NodeJS.WriteStream;\n readonly includeGeometry: boolean;\n}\n\nexport interface InkProbeSession {\n readonly revision: number;\n readonly frames: number;\n notifyRender(): void;\n /** Settle all captures queued at the time of the call. Never rejects. */\n flush(): Promise<void>;\n stop(): void;\n}\n\n/**\n * Pair each observed commit with its output bytes.\n *\n * Ink invokes `onRender` after layout and before writing. The tree is frozen\n * synchronously in that callback; deferring observation would let a microtask\n * or a throttled commit mutate the host objects before they were read. Only\n * marker placement is deferred: after Ink returns and writes, stdout is\n * drained and the authenticated marker is appended.\n */\nexport function createInkSession(options: InkSessionOptions): InkProbeSession {\n let revision = 0;\n let frames = 0;\n let latestFrame = 0;\n let staticSeen = false;\n let stopped = false;\n let queue: Promise<void> = Promise.resolve();\n\n const fail = (): void => {\n if (stopped) return;\n stopped = true;\n options.channel.close();\n };\n\n const writeMarker = async (frame: number, marker: string): Promise<void> => {\n await nextMacrotask();\n if (stopped || !options.channel.isOpen) return;\n if (frame !== latestFrame) {\n options.channel.recordCoalescedEvent();\n return;\n }\n await drain(options.stdout);\n // A newer render may have written while this drain was pending. Marker N\n // after frame N+1 bytes is actively misleading, so drop it and let the\n // newer full snapshot establish the next pairing.\n if (stopped || !options.channel.isOpen) return;\n if (frame !== latestFrame) {\n options.channel.recordCoalescedEvent();\n return;\n }\n options.stdout.write(marker);\n };\n\n return {\n get revision() {\n return revision;\n },\n get frames() {\n return frames;\n },\n notifyRender() {\n if (stopped) return;\n frames += 1;\n const frame = frames;\n // Even an unobservable/failed frame supersedes a queued old marker.\n latestFrame = frame;\n\n try {\n const root = options.resolveRoot();\n if (root === null) return;\n // Static output scrolls the live region down. Removing <Static> later\n // does not erase bytes already written above it, so loss of absolute\n // coordinates is sticky for this session.\n staticSeen ||= hasStaticContent(root);\n const includeGeometry = options.includeGeometry && !staticSeen;\n const excluded = options.resolveExcluded?.();\n const observation = observeInkTree(root, {\n frame,\n limits: options.channel.session.limits as ProtocolLimits,\n ...(excluded === undefined ? {} : { excluded }),\n measureElement: options.measureElement,\n includeGeometry,\n });\n\n revision += 1;\n const snapshot: SemanticSnapshot = recognize(observation.frame, {\n sessionId: options.channel.session.sessionId,\n revision,\n columns: options.stdout.columns ?? 80,\n rows: options.stdout.rows ?? 24,\n framework: 'ink',\n paintOrderKnown: false,\n maxStringBytes: options.channel.session.limits.maxStringBytes,\n qualified: options.channel.session.protocol === 'termwright/2',\n });\n const marker = options.channel.publish(snapshot, {\n probeEvents: observation.frame.objects.length + (observation.frame.operations?.length ?? 0),\n });\n if (marker === undefined) return;\n queue = queue.then(() => writeMarker(frame, marker)).catch(fail);\n } catch {\n fail();\n }\n },\n async flush() {\n await queue.catch(() => undefined);\n },\n stop() {\n fail();\n },\n };\n}\n\nfunction nextMacrotask(): Promise<void> {\n return new Promise((resolve) => setImmediate(resolve));\n}\n\nfunction drain(stream: NodeJS.WriteStream): Promise<void> {\n return new Promise((resolve) => {\n if (stream.writableEnded || stream.destroyed) {\n resolve();\n return;\n }\n try {\n stream.write('', () => resolve());\n } catch {\n resolve();\n }\n });\n}\n","/**\n * Probe IR → semantic tree.\n *\n * The probe reports what it saw; this decides what it means. Keeping the two\n * apart is what lets six frameworks disagree about what is knowable without\n * that disagreement leaking into the tree — and it is why this module is a pure\n * function over data, testable without a process, a framework or a socket.\n *\n * Merge precedence, from decision D6: **annotation > recognizer > framework >\n * correlation > heuristic**, with one exception that matters more than the\n * order does — physical facts are never overridden by an annotation. An author\n * may name a thing; an author may not declare where it is on screen, whether it\n * has focus, or whether it is visible.\n */\n\nimport {\n DEFAULT_LIMITS,\n resolveNodeBounds,\n SEMANTIC_ROLES,\n type ProbeFrame,\n type ProbeObject,\n type Observation,\n type ProvenanceSource,\n type Rect,\n type SemanticNode,\n type SemanticRole,\n type SemanticSnapshot,\n type SemanticState,\n} from '@termwright/protocol';\nimport { namesFromContent, normalizeName } from './naming.js';\nimport { roleForInkAria, roleForInkHost } from './ink.js';\nimport { roleForOpenTuiClass } from './opentui.js';\n\n/** Everything the tree needs that a frame does not carry. */\nexport interface RecognizeContext {\n readonly sessionId: string;\n readonly revision: number;\n readonly columns: number;\n readonly rows: number;\n /** Which framework produced the frame; selects the level-2 role map. */\n readonly framework: string;\n /** Whether the probe reported paint order, which decides occlusion. */\n readonly paintOrderKnown?: boolean;\n /** Bound for a name correlated from descendant text. */\n readonly maxStringBytes?: number;\n /** Emit the explicit qualified geometry contract instead of legacy v1. */\n readonly qualified?: boolean;\n}\n\nconst ROLES: ReadonlySet<string> = new Set(SEMANTIC_ROLES);\nconst UTF8_ENCODER = new TextEncoder();\n\n/** Level-2 maps, by framework. Others land on `generic`, which is legitimate. */\nconst ROLE_MAPS: Readonly<Record<string, (frameworkType: string) => SemanticRole | undefined>> =\n Object.freeze({ ink: roleForInkHost, opentui: roleForOpenTuiClass });\n\n/**\n * Resolve a role, in the normative order.\n *\n * An annotation that names a role outside the closed set is **not** silently\n * dropped: it falls through to the framework map, and the bad value stays\n * visible in the annotations rather than becoming an invented role.\n */\nfunction resolveRole(object: ProbeObject, framework: string): {\n role: SemanticRole;\n source: ProvenanceSource;\n} {\n const annotated = object.annotations?.role;\n if (annotated !== undefined && ROLES.has(annotated)) {\n return { role: annotated as SemanticRole, source: 'annotation' };\n }\n if (framework === 'ink' && object.accessibility?.role !== undefined) {\n const mapped = roleForInkAria(object.accessibility.role);\n if (mapped !== undefined) return { role: mapped, source: 'framework' };\n }\n const mapped = ROLE_MAPS[framework]?.(object.frameworkType);\n if (mapped !== undefined) return { role: mapped, source: 'recognizer' };\n return { role: 'generic', source: 'recognizer' };\n}\n\n/**\n * Resolve a name.\n *\n * An annotation wins, including a deliberate empty string. Otherwise the text\n * the probe observed is used **only** for roles that take their name from\n * content; everything else keeps an empty name rather than absorbing the label\n * of something inside it.\n */\nfunction resolveName(\n object: ProbeObject,\n role: SemanticRole,\n subtreeText: ReadonlyMap<string, string>,\n): { name: string; source: ProvenanceSource } {\n const annotated = object.annotations?.name;\n if (annotated !== undefined) return { name: annotated, source: 'annotation' };\n if (object.accessibility?.name !== undefined) {\n return { name: object.accessibility.name, source: 'framework' };\n }\n if (namesFromContent(role) && object.text !== undefined) {\n return { name: normalizeName(object.text), source: 'framework' };\n }\n if (namesFromContent(role)) {\n const correlated = subtreeText.get(object.identity.value);\n if (correlated !== undefined && correlated.length > 0) {\n return { name: normalizeName(correlated), source: 'recognizer' };\n }\n }\n return { name: '', source: 'recognizer' };\n}\n\n/**\n * Text reachable below each object, bounded once at every subtree.\n *\n * Probe IR keeps own text and descendants separate. Name-from-content is a\n * recognizer inference over parent links, not a reason for a probe to claim a\n * container directly carried its children's string. A nested annotated host is\n * an accessible-content boundary: it can name itself, but its label must not\n * leak into an outer control.\n */\nfunction collectSubtreeText(\n frame: ProbeFrame,\n maxStringBytes: number,\n): ReadonlyMap<string, string> {\n const objectById = new Map(frame.objects.map((object) => [object.identity.value, object]));\n const children = new Map<string, string[]>();\n for (const object of frame.objects) {\n if (object.parent === undefined || !objectById.has(object.parent)) continue;\n const siblings = children.get(object.parent) ?? [];\n siblings.push(object.identity.value);\n children.set(object.parent, siblings);\n }\n\n const memo = new Map<string, string>();\n const visiting = new Set<string>();\n const collect = (id: string): string => {\n const held = memo.get(id);\n if (held !== undefined) return held;\n if (visiting.has(id)) return '';\n visiting.add(id);\n const object = objectById.get(id);\n let result = object?.text ?? '';\n for (const child of children.get(id) ?? []) {\n const childText = collect(child);\n const childObject = objectById.get(child);\n if (\n childObject?.annotations?.role !== undefined\n || childObject?.accessibility?.role !== undefined\n ) continue;\n if (childText.length === 0) continue;\n // Separate host elements are separate content runs. Raw text fragments\n // inside one host were already joined by the probe, but sibling hosts\n // need a word boundary (`<Text>Save</Text><Text>now</Text>`).\n result = clampUtf8(result.length === 0 ? childText : `${result} ${childText}`, maxStringBytes);\n if (UTF8_ENCODER.encode(result).byteLength >= maxStringBytes) break;\n }\n visiting.delete(id);\n result = clampUtf8(result, maxStringBytes);\n memo.set(id, result);\n return result;\n };\n\n for (const object of frame.objects) collect(object.identity.value);\n return memo;\n}\n\nfunction clampUtf8(value: string, maxBytes: number): string {\n if (UTF8_ENCODER.encode(value).byteLength <= maxBytes) return value;\n let result = '';\n let bytes = 0;\n for (const codePoint of value) {\n const size = UTF8_ENCODER.encode(codePoint).byteLength;\n if (bytes + size > maxBytes) break;\n result += codePoint;\n bytes += size;\n }\n return result;\n}\n\n/** Map observed state onto the protocol's closed state set. */\nfunction resolveState(\n object: ProbeObject,\n hiddenByGeometry: boolean,\n offscreen: boolean,\n): SemanticState | undefined {\n const observed = object.state;\n const state: Record<string, unknown> = {};\n\n if (observed?.focused !== undefined) state['focused'] = observed.focused;\n if (observed?.disabled !== undefined) state['disabled'] = observed.disabled;\n if (observed?.checked !== undefined) state['checked'] = observed.checked;\n if (observed?.expanded !== undefined) state['expanded'] = observed.expanded;\n if (observed?.readonly !== undefined) state['readonly'] = observed.readonly;\n if (observed?.selected !== undefined) state['selected'] = observed.selected;\n if (observed?.busy !== undefined) state['busy'] = observed.busy;\n if (observed?.multiline !== undefined) state['multiline'] = observed.multiline;\n if (observed?.displayed !== undefined) state['hidden'] = !observed.displayed;\n // Probe IR keeps item selection distinct from a text range. The semantic\n // tree represents the highlighted item's zero-based index as the matching\n // one-based collection position.\n if (observed?.selectedIndex !== undefined) state['positionInSet'] = observed.selectedIndex + 1;\n if (observed?.scroll !== undefined) state['scrollOffset'] = observed.scroll.row;\n if (observed?.scrollExtent !== undefined) state['scrollExtent'] = observed.scrollExtent.rows;\n\n // Clipped entirely away is a different fact from the framework's own display\n // flag, and both end up as `hidden` because the wire has one field for it.\n if (hiddenByGeometry) state['hidden'] = true;\n if (offscreen) {\n state['hidden'] = true;\n state['offscreen'] = true;\n }\n\n return Object.keys(state).length === 0 ? undefined : (state as SemanticState);\n}\n\n/**\n * Turn one observed frame into a semantic snapshot.\n *\n * Every object becomes a node: nothing is dropped for being unrecognised, which\n * is what `generic` plus `frameworkType` is for. Parent links are rewritten\n * from probe identities to node ids, and an object whose parent did not survive\n * is attached to the root rather than left dangling — a snapshot with a missing\n * parent is refused by validation, and losing the subtree would be worse than\n * reparenting it.\n */\nexport function recognize(frame: ProbeFrame, context: RecognizeContext): SemanticSnapshot {\n // Ink boxes do not own their rendered label; it survives only in descendant\n // text hosts. Other framework probes already define the meaning of their own\n // `text` field and must not silently gain Ink's host-specific correlation.\n const subtreeText: ReadonlyMap<string, string> = context.framework === 'ink'\n ? collectSubtreeText(frame, context.maxStringBytes ?? DEFAULT_LIMITS.maxStringBytes)\n : new Map();\n const idByIdentity = new Map<string, string>();\n for (const object of frame.objects) {\n idByIdentity.set(object.identity.value, `n${object.identity.value}`);\n }\n\n const nodes: SemanticNode[] = [];\n const rootIds: string[] = [];\n\n for (const object of frame.objects) {\n const id = idByIdentity.get(object.identity.value) as string;\n const { role, source: roleSource } = resolveRole(object, context.framework);\n const { name, source: nameSource } = resolveName(object, role, subtreeText);\n\n const bounds =\n object.geometry === undefined\n ? undefined\n : resolveNodeBounds(object.geometry, {\n // The terminal viewport is always a real, known clip even when the\n // framework cannot expose clips imposed by intermediate widgets.\n clip: { row: 0, column: 0, width: context.columns, height: context.rows },\n // Paint order is not a hit test. Until the normalized contract\n // carries the topmost recipient at a concrete point, v1 must not\n // promote either a frame-level flag or an object's order number to\n // `occlusion: known`.\n paintOrderKnown: false,\n });\n\n const hiddenByGeometry =\n bounds !== undefined && (bounds.rect.width === 0 || bounds.rect.height === 0);\n const state = resolveState(object, hiddenByGeometry, bounds?.clippedAway ?? false);\n const parentId = object.parent === undefined ? undefined : idByIdentity.get(object.parent);\n if (parentId === undefined) rootIds.push(id);\n\n // One source for the node, exceptions listed. Physical facts always come\n // from the framework: an annotation may not move a widget on screen.\n const px: Record<string, ProvenanceSource> = {};\n if (nameSource !== roleSource) px['name'] = nameSource;\n if (bounds !== undefined) px[context.qualified ? 'geometry' : 'bounds'] = 'framework';\n if (state !== undefined) px['state'] = 'framework';\n if (object.annotations?.description !== undefined && roleSource !== 'annotation') {\n px['description'] = 'annotation';\n } else if (object.accessibility?.description !== undefined && roleSource !== 'framework') {\n px['description'] = 'framework';\n }\n if (object.annotations?.testId !== undefined && roleSource !== 'annotation') {\n px['testId'] = 'annotation';\n }\n if (object.annotations?.extended !== undefined && roleSource !== 'annotation') {\n px['extended'] = 'annotation';\n }\n if (object.annotations?.actions !== undefined && roleSource !== 'annotation') {\n px['actions'] = 'annotation';\n }\n if (object.annotations?.labelledBy !== undefined && roleSource !== 'annotation') {\n px['labelledBy'] = 'annotation';\n }\n if (object.annotations?.describedBy !== undefined && roleSource !== 'annotation') {\n px['describedBy'] = 'annotation';\n }\n\n const labelledBy = object.annotations?.labelledBy\n ?.map((identity) => idByIdentity.get(identity))\n .filter((target): target is string => target !== undefined);\n const describedBy = object.annotations?.describedBy\n ?.map((identity) => idByIdentity.get(identity))\n .filter((target): target is string => target !== undefined);\n\n const displayed: Observation<boolean> = object.state?.displayed !== undefined\n ? { status: 'known', value: object.state.displayed, evidence: 'probe' }\n : object.unobservable?.includes('displayed') === true\n ? { status: 'unsupported', capability: 'displayed', reason: 'framework-unobservable' }\n : { status: 'unknown', reason: 'not-reported' };\n const intendedRect: Observation<Rect> = object.geometry?.intendedRect !== undefined\n ? { status: 'known', value: object.geometry.intendedRect, evidence: 'probe' }\n : object.unobservable?.includes('intendedRect') === true\n ? { status: 'unsupported', capability: 'intended-rect', reason: 'framework-unobservable' }\n : { status: 'unknown', reason: 'not-reported' };\n const visibleRect: Observation<Rect> = displayed.status === 'known' && displayed.value === false\n ? { status: 'absent', reason: 'not-displayed' }\n : object.geometry?.visibleRect !== undefined\n ? { status: 'known', value: object.geometry.visibleRect, evidence: 'viewport-clip' }\n : object.unobservable?.includes('visibleRect') === true\n ? { status: 'unsupported', capability: 'visible-rect', reason: 'framework-unobservable' }\n : { status: 'unknown', reason: 'not-reported' };\n\n nodes.push({\n id,\n ...(parentId === undefined ? {} : { parentId }),\n role,\n name,\n ...(object.annotations?.description === undefined\n ? object.accessibility?.description === undefined\n ? {}\n : { description: object.accessibility.description }\n : { description: object.annotations.description }),\n frameworkType: object.frameworkType,\n ...(context.qualified\n ? { geometry: { displayed, intendedRect, visibleRect } }\n : {\n ...(bounds === undefined ? {} : { bounds: bounds.rect }),\n ...(bounds === undefined ? {} : { occlusion: bounds.occlusion }),\n }),\n ...(state === undefined ? {} : { state }),\n ...(object.annotations?.testId === undefined\n ? {}\n : { testId: object.annotations.testId }),\n ...(object.annotations?.extended === undefined\n ? {}\n : { extended: object.annotations.extended }),\n ...(object.annotations?.actions === undefined\n ? {}\n : { actions: object.annotations.actions }),\n ...(labelledBy === undefined || labelledBy.length === 0 ? {} : { labelledBy }),\n ...(describedBy === undefined || describedBy.length === 0 ? {} : { describedBy }),\n ...(object.state?.value === undefined ? {} : { value: object.state.value }),\n p: roleSource,\n ...(Object.keys(px).length === 0 ? {} : { px }),\n });\n }\n\n return context.qualified ? {\n v: 2,\n sessionId: context.sessionId,\n revision: context.revision,\n columns: context.columns,\n rows: context.rows,\n rootIds,\n nodes,\n coordinateSpace: { status: 'known', value: 'viewport-cells', evidence: 'probe' },\n hitGrid: { status: 'unsupported', capability: 'pointer-hit-grid', reason: 'framework-unobservable' },\n } : {\n v: 1,\n sessionId: context.sessionId,\n revision: context.revision,\n columns: context.columns,\n rows: context.rows,\n rootIds,\n nodes,\n };\n}\n","/**\n * Where a name comes from, and — more often — where it must not.\n *\n * The protocol's adapter conventions gate descendant-text naming to nine roles.\n * The rule exists because naming containers from their content makes\n * `getByRole('region', {name: 'Approve'})` match the dialog *containing* the\n * Approve button, so every ancestor of a label becomes a plausible match and\n * locators stop being selective. That failure is quiet: the tree looks richer,\n * and the tests get worse.\n */\n\nimport type { SemanticRole } from '@termwright/protocol';\n\n/**\n * Roles whose accessible name comes from the text they contain.\n *\n * Normative list, from the protocol README. Anything outside it is a container\n * and keeps an empty name unless an annotation gives it one.\n */\nexport const NAME_FROM_CONTENT: ReadonlySet<SemanticRole> = new Set<SemanticRole>([\n 'button',\n 'listitem',\n 'menuitem',\n 'tab',\n 'checkbox',\n 'radio',\n 'cell',\n 'row',\n 'heading',\n]);\n\n/**\n * Whether a role takes its name from the text it contains.\n *\n * `text` is included beyond the normative list: a text node's string is its\n * *own* content — naming source 2, \"the widget's own label\" — rather than a\n * descendant widget's. Without it `getByText` would have nothing to match.\n */\nexport function namesFromContent(role: SemanticRole): boolean {\n return role === 'text' || NAME_FROM_CONTENT.has(role);\n}\n\n/** Collapse whitespace the way a terminal reader would see it. */\nexport function normalizeName(text: string): string {\n return text.replace(/\\s+/gu, ' ').trim();\n}\n","/** Conservative role map for the only host kinds Ink retains after reconcile. */\n\nimport type { SemanticRole } from '@termwright/protocol';\n\nconst HOST_ROLES: Readonly<Record<string, SemanticRole>> = Object.freeze({\n 'ink-root': 'application',\n 'ink-text': 'text',\n 'ink-virtual-text': 'text',\n 'ink-box': 'generic',\n});\n\n/**\n * Resolve only facts the host kind itself proves.\n *\n * Ink discards source component names before host creation, so a plain box is\n * never promoted to `button` from its children or styling. A retained\n * `aria-role` travels separately as framework-native accessibility metadata.\n */\nexport function roleForInkHost(host: string): SemanticRole | undefined {\n return HOST_ROLES[host];\n}\n\nconst ARIA_ROLES: Readonly<Record<string, SemanticRole>> = Object.freeze({\n button: 'button',\n checkbox: 'checkbox',\n combobox: 'generic',\n list: 'list',\n listbox: 'list',\n listitem: 'listitem',\n menu: 'menu',\n menuitem: 'menuitem',\n option: 'listitem',\n progressbar: 'progressbar',\n radio: 'radio',\n radiogroup: 'generic',\n tab: 'tab',\n tablist: 'generic',\n table: 'table',\n textbox: 'textbox',\n timer: 'status',\n toolbar: 'generic',\n});\n\n/** Map Ink's retained aria vocabulary without guessing ambiguous containers. */\nexport function roleForInkAria(role: string): SemanticRole | undefined {\n return ARIA_ROLES[role];\n}\n","/**\n * OpenTUI's widget vocabulary, mapped onto the protocol's closed role set.\n *\n * OpenTUI has **no accessibility layer at all** — the Phase 0 audit found no\n * `role`, no `aria`, no `checked` anywhere in its types — so the class name is\n * the only signal there is. That makes this map level 2 of role resolution: it\n * runs after an author annotation and before `generic`.\n *\n * The map is deliberately short. A widget with no unambiguous counterpart stays\n * `generic` and keeps its `frameworkType`, which is what the protocol's D1\n * decision exists for: an unrecognised widget survives with its bounds, text\n * and children instead of being dropped, and a test can still find it by what\n * the framework called it. Inventing `tab` for `TabSelectRenderable` would be\n * the opposite trade — a role that reads right and makes locators match the\n * wrong thing.\n */\n\nimport type { SemanticRole } from '@termwright/protocol';\n\nconst ROLE_BY_CLASS: Readonly<Record<string, SemanticRole>> = Object.freeze({\n RootRenderable: 'application',\n TextRenderable: 'text',\n TextNodeRenderable: 'text',\n RootTextNodeRenderable: 'text',\n CodeRenderable: 'text',\n MarkdownRenderable: 'text',\n ASCIIFontRenderable: 'text',\n InputRenderable: 'textbox',\n TextareaRenderable: 'textbox',\n EditBufferRenderable: 'textbox',\n SelectRenderable: 'list',\n TextTableRenderable: 'table',\n ScrollBarRenderable: 'scrollbar',\n});\n\n/**\n * The role OpenTUI's class name implies, if any.\n *\n * @returns a role, or `undefined` when the class has no unambiguous\n * counterpart — `BoxRenderable`, `ScrollBoxRenderable`, `TabSelectRenderable`,\n * `SliderRenderable` and every application subclass land there on purpose.\n */\nexport function roleForOpenTuiClass(frameworkType: string): SemanticRole | undefined {\n return Object.hasOwn(ROLE_BY_CLASS, frameworkType) ? ROLE_BY_CLASS[frameworkType] : undefined;\n}\n\n/**\n * Whether a class is one of OpenTUI's own.\n *\n * Used to decide whether an unmapped `frameworkType` is a widget we chose not\n * to classify or an application's own subclass — a distinction worth keeping,\n * because the second is the case `generic` was designed for.\n */\nexport function isOpenTuiClass(frameworkType: string): boolean {\n return frameworkType.endsWith('Renderable');\n}\n","/** Replacement source for Ink's public entry module. */\n\n/** Marker on the shim's re-import of the untouched module. */\nexport const ORIGINAL_MARKER = 'termwright-original=1';\n\n/**\n * Ink's ESM entry under ordinary node_modules and Bun's versioned cache.\n * Matching the resolved path, rather than the bare specifier, is required by\n * both loader APIs used here.\n */\nexport const INK_ENTRY_PATTERN = /[\\\\/](?:ink|ink@[^\\\\/]+)[\\\\/]build[\\\\/]index\\.js$/u;\n\n/** The separately-built runtime imported by replacement module source. */\nexport const INSTRUMENT_URL = new URL('./instrument.js', import.meta.url).href;\n\nexport function shouldShim(urlOrPath: string): boolean {\n if (urlOrPath.includes(ORIGINAL_MARKER)) return false;\n return INK_ENTRY_PATTERN.test(urlOrPath.split('?')[0] ?? '');\n}\n\nexport function originalUrl(urlOrPath: string): string {\n return `${urlOrPath}${urlOrPath.includes('?') ? '&' : '?'}${ORIGINAL_MARKER}`;\n}\n\n/**\n * Forward the complete Ink namespace and shadow only `render`.\n *\n * The wrapper receives the already-marked original namespace, so the runtime\n * never imports `ink` itself and cannot recurse through the loader hook.\n */\nexport function buildShimSource(target: string, instrumentUrl = INSTRUMENT_URL): string {\n const original = JSON.stringify(originalUrl(target));\n const instrument = JSON.stringify(instrumentUrl);\n return `import * as __termwright_original from ${original};\nimport {wrapInkRender as __termwright_wrap} from ${instrument};\nexport * from ${original};\n\nexport const render = __termwright_wrap(__termwright_original);\n`;\n}\n","/** Runtime activation shared by the two preload entry points. */\n\nimport { ENV_ENDPOINT, ENV_TOKEN } from '@termwright/protocol';\n\n/** Runtimes into which the Ink probe can be injected. */\nexport type ProbeRuntime = 'bun' | 'node';\n\n/** Read-only environment view, so activation is testable without mutation. */\nexport type EnvSource = Readonly<Record<string, string | undefined>>;\n\n/** Both secrets are required. A partial environment remains fully dormant. */\nexport function isInstrumented(env: EnvSource): boolean {\n const endpoint = env[ENV_ENDPOINT];\n const token = env[ENV_TOKEN];\n return typeof endpoint === 'string' && endpoint.length > 0\n && typeof token === 'string' && token.length > 0;\n}\n","/** Build an application command with the zero-config Ink preload attached. */\n\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport type { ProbeRuntime } from './runtime.js';\n\n/** Published preload paths; callers never have to guess package layout. */\nexport const PROBE_ENTRIES = {\n bun: fileURLToPath(new URL('./bun-preload.js', import.meta.url)),\n node: fileURLToPath(new URL('./node-hook.js', import.meta.url)),\n} as const;\n\nexport interface ProbeCommand {\n readonly command: readonly string[];\n readonly runtime: ProbeRuntime;\n}\n\n/** Prefix a normal Node or Bun command with the matching preload flag. */\nexport function withProbe(runtime: ProbeRuntime, argv: readonly string[]): ProbeCommand {\n if (argv.length === 0) throw new Error('withProbe needs an interpreter in argv');\n const [interpreter, ...rest] = argv as [string, ...string[]];\n const flag = runtime === 'bun' ? '--preload' : '--import';\n return {\n command: [\n interpreter,\n flag,\n pathToFileURL(PROBE_ENTRIES[runtime]).href,\n ...rest,\n ],\n runtime,\n };\n}\n"],"mappings":"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","names":["existsSync","readFileSync","tmpdir","join","DEFAULT_LIMITS","join","DEFAULT_LIMITS","ENV_ENDPOINT","ENV_TOKEN","createNodePtyBackend","fileURLToPath","fileURLToPath","join","ENV_ENDPOINT","ENV_TOKEN","createNodePtyBackend","DEFAULT_LIMITS","join","tmpdir","delay","DEFAULT_LIMITS","observation","existsSync","readFileSync"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@termwright/conformance",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "generic/semantic/adversarial fixtures + adapter contract tests",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/Gorce-AI/termwright.git",
|
|
9
|
+
"directory": "packages/conformance"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=22"
|
|
14
|
+
},
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"src/fixtures"
|
|
24
|
+
],
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@xterm/headless": "^6.0.0",
|
|
27
|
+
"@termwright/driver": "0.2.0",
|
|
28
|
+
"@termwright/protocol": "0.2.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
32
|
+
"@types/react": "^19.2.18",
|
|
33
|
+
"ink": "^7.1.1",
|
|
34
|
+
"react": "^19.2.8",
|
|
35
|
+
"@termwright/logs": "0.2.0",
|
|
36
|
+
"@termwright/ink": "0.2.0",
|
|
37
|
+
"@termwright/probe-tview": "0.2.0",
|
|
38
|
+
"@termwright/mcp": "0.2.0",
|
|
39
|
+
"@termwright/probe-ink": "0.2.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"vitest": ">=3.2.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependenciesMeta": {
|
|
45
|
+
"vitest": {
|
|
46
|
+
"optional": true
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "tsup src/index.ts --format esm --dts --sourcemap",
|
|
51
|
+
"typecheck": "tsc --noEmit",
|
|
52
|
+
"test": "vitest run",
|
|
53
|
+
"test:hostile": "vitest run --config vitest.hostile.config.ts src/suites/adversarial.test.ts src/suites/mcp-sessions.test.ts",
|
|
54
|
+
"conformance": "node scripts/conformance.mjs"
|
|
55
|
+
}
|
|
56
|
+
}
|