/** * The test-first offer — making the TDD loop discoverable from the calls people * already make. * * The loop exists and is complete: `prepare(mode="test")` resolves a class or a * table method, `generate_object(pattern="systest")` emits a red-first scaffold, * `run_systest_class` executes it and reports per method. Across 1,603 real MCP * calls in 47 sessions it was used **zero times**. `prepare(mode="test")`: 0. * `run_systest_class`: 0. Meanwhile `prepare(mode="change")` ran 54 times and * `d365fo_file(modify)` 195, and the single most-asked knowledge question — about * thirty-five of roughly fifty real asks — was the table `validateWrite` Chain of * Command contract, which is precisely the thing the loop was built to test. * * A feature nobody finds is indistinguishable from one that does not exist. So * the offer goes where the developer already is, and it costs nothing to publish: * this is response TEXT, not schema, so the ListTools payload (44,951 of 45,000 * chars) is untouched. * * ── WHAT IT MUST NOT DO ─────────────────────────────────────────────────────── * Offer on everything. A suggestion that appears on every call is banner blindness * within a session, and it would land on changes a SysTest cannot express: adding * a field to a table, adding a control to a form, renaming an enum value. Those * are METADATA, and the eval loop already covers them with a golden — a golden is * the right oracle for structure, a SysTest for behaviour. * * So the trigger is behaviour, and structure is not a weaker signal for it — it is * no signal at all. `add-field` on a table-extension does not offer a test even * though a table-extension is testable; `add-method` on the same object does. */ import type { XppServerContext } from '../../types/context.js'; export interface TestFirstInput { /** Resolved objectType of the change, if known. */ objectType?: string; /** The method being written, if the change names one. */ methodName?: string; /** The caller's stated goal. */ goal?: string; /** Comma-separated or array `operation` argument, as `prepare` receives it. */ operation?: string | string[]; } export interface TestFirstOffer { /** Why the offer fired — surfaced in the text so the reader can judge it. */ reason: string; /** The method to focus the scaffold on, when the change named one. */ focusMethod?: string; } /** * Should this change be offered a red-first test, and why? * * Returns `undefined` for "no" rather than a boolean plus a separate reason, * so a caller cannot render an offer without the sentence that justifies it. */ export declare function testFirstOffer(input: TestFirstInput): TestFirstOffer | undefined; /** * Test classes that already mention the target. * * Shared with `prepare(mode="test")` rather than re-queried: two copies of this * lookup would drift, and the answer decides between "scaffold one" and "extend * the one you have", which is the difference between a useful offer and a * duplicate test class. */ export declare function findExistingTests(context: XppServerContext, target: string): string[]; /** * The object a test should target, given the object being WRITTEN. * * A CoC wrapper is transparent to a test: you exercise `CustTable`, not * `CustTableConExtension_Extension`, and the wrapper is what makes the assertion * change. So an extension name has to be reduced to its base — and that cannot be * done by string surgery alone, because the infix between base and `_Extension` * is a per-model convention (`FMVehicleDataContract` + `Con` + `_Extension`). * * Candidates are therefore VERIFIED against the index, and `undefined` is * returned when none resolves. Printing an unverified name would put a call in * front of the caller that resolves to nothing — worse than saying nothing, since * it costs a round trip to discover. */ export declare function resolveTestTarget(context: XppServerContext, objectName: string, objectType?: string): string | undefined; /** * One line for a write that just landed: is this behaviour covered by a test? * * Emitted only when the write SUCCEEDED, carried X++, and the index knows both * the target and that nothing tests it. Silence is the default — a note on every * write is noise, and this one is competing with the write's own report. */ export declare function testCoverageNote(context: XppServerContext, input: TestFirstInput & { objectName?: string; }): string | undefined; /** * The offer, as markdown lines. Empty when there is nothing to offer. * * Three lines at most. This section competes for a capped response with the * write contract and the grounding token, and it must never be the reason one of * those is cut — callers place it after both. */ export declare function renderTestFirst(offer: TestFirstOffer | undefined, target: string, existingTests: string[]): string[]; //# sourceMappingURL=testFirst.d.ts.map