@jawache/flow 0.1.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.
@@ -0,0 +1,641 @@
1
+ declare const PHASES: {
2
+ readonly write: readonly ["edit"];
3
+ readonly command: readonly ["edit", "commit"];
4
+ readonly commit: readonly ["commit"];
5
+ readonly delete: readonly ["delete"];
6
+ readonly "turn-end": readonly ["turn"];
7
+ };
8
+ /** The moments a guardrail may fire at — the keys of the one table above. */
9
+ export type GuardrailMoment = keyof typeof PHASES;
10
+ /** The moments a breadcrumb may show at. A breadcrumb is a note; it has no rail to block. */
11
+ export type BreadcrumbMoment = (typeof BREADCRUMB_MOMENTS)[number];
12
+ /** Either vocabulary. `turn-end` is the one word in both. */
13
+ export type Moment = GuardrailMoment | BreadcrumbMoment;
14
+ export declare const GUARDRAIL_MOMENTS: readonly GuardrailMoment[];
15
+ export declare const BREADCRUMB_MOMENTS: readonly ["session", "touch", "turn-end"];
16
+ export declare const write: "write";
17
+ export declare const command: "command";
18
+ export declare const commit: "commit";
19
+ export declare const deletion: "delete";
20
+ export declare const turnEnd: "turn-end";
21
+ export declare const session: "session";
22
+ export declare const touch: "touch";
23
+ /** The engine phases a moment list fires at — deduped, in declaration order. */
24
+ export declare function phasesOf(at: readonly GuardrailMoment[]): string[];
25
+ /** Is this string one of the guardrail words? The load's backstop, not the editor's job. */
26
+ export declare function isGuardrailMoment(m: string): m is GuardrailMoment;
27
+ /** Is this string one of the breadcrumb words? */
28
+ export declare function isBreadcrumbMoment(m: string): m is BreadcrumbMoment;
29
+ /**
30
+ * The moments whose events NAME A PATH — and therefore the only ones `.on()` / `.ignore()` narrow.
31
+ *
32
+ * A write and a delete carry the would-be file; the commit gate carries the staged set; a touch
33
+ * carries the file the tool call was about. The other three carry no path at all: a command guard's
34
+ * patterns ARE its scope, a turn-end is about what the actor did, and a session start is about the
35
+ * session. The engine expresses the same fact at run time — `subjectsOf` consults `inScope` for
36
+ * exactly these moments, and `brief` narrows a note only when the event names a path — and stating
37
+ * it here is what lets a scope that would narrow NOTHING be refused rather than quietly ignored.
38
+ */
39
+ export declare const PATH_MOMENTS: readonly ["write", "delete", "commit", "touch"];
40
+ export type PathMoment = (typeof PATH_MOMENTS)[number];
41
+ /** Does this moment carry a path for `on` / `ignore` to narrow? */
42
+ export declare function isPathMoment(m: string): m is PathMoment;
43
+ /**
44
+ * The host-written record of one session × agent, as a classifier sees it.
45
+ *
46
+ * Every field is something a HARNESS wrote, not something the session said. `agentType` is the
47
+ * strongest of them — the sidecar the host writes beside every subagent transcript — and it is
48
+ * what a classifier should key on when it exists.
49
+ */
50
+ export interface SessionFacts {
51
+ /** The head of this session's transcript: the brief it was started with, verbatim. */
52
+ head: string;
53
+ /** True when this is a spawned subagent rather than the parent session. */
54
+ subagent: boolean;
55
+ /** The host's own agent type for this spawn, from the sidecar it wrote. Absent on a parent. */
56
+ agentType?: string | undefined;
57
+ /** The human-readable label the spawning agent gave this subagent. Absent on a parent. */
58
+ description?: string | undefined;
59
+ }
60
+ /** A category's recognizer: host-written facts in, a yes or no out. */
61
+ export type Classifier = (facts: SessionFacts) => boolean;
62
+ /**
63
+ * A declared category. Only `defineCategory` makes one — the brand is what lets the load refuse
64
+ * an entry that scoped itself to something that merely looks like a category.
65
+ */
66
+ export interface Category {
67
+ readonly [CATEGORY]: true;
68
+ readonly name: string;
69
+ readonly classify: Classifier;
70
+ }
71
+ declare const CATEGORY: unique symbol;
72
+ /** Declare a category: a name, and the recognizer that travels with it. */
73
+ export declare function defineCategory(name: string, classify: Classifier): Category;
74
+ /** Was this made by `defineCategory`? The load's backstop behind `.for()`'s typing. */
75
+ export declare function isCategory(value: unknown): value is Category;
76
+ /** What `ctx.exec` answers. A diagnostic command's whole result, as data. */
77
+ export interface ExecResult {
78
+ stdout: string;
79
+ stderr: string;
80
+ code: number;
81
+ }
82
+ /** A check's answer. Never an action — the engine turns a fail into an effect. */
83
+ export type Verdict = {
84
+ readonly ok: true;
85
+ } | {
86
+ readonly ok: false;
87
+ readonly detail?: string;
88
+ };
89
+ /**
90
+ * One thing the actor did during a turn: it changed a file, or it ran a command.
91
+ *
92
+ * TWO KINDS AND NO MORE, and the narrowness is the contract. A harness records a turn in its own
93
+ * vocabulary — Claude Code writes JSONL naming tools like `Edit`, `Write` and `Bash` — and which
94
+ * of those names counts as an edit is exactly the kind of dialect the adapter exists to absorb.
95
+ * What the engine and a check are handed is the answer, not the transcript: an ordered list of
96
+ * edits and runs that any harness can produce and a `.test()` case can simply write out.
97
+ *
98
+ * The rule this replaces read `$WORK_TURN_TRANSCRIPT` from the environment, opened the file and
99
+ * parsed it, inside the check — three reads of the world in the one place the design forbids
100
+ * them, and it made that rule the only core rule no case could drive.
101
+ */
102
+ export type TurnAction = {
103
+ readonly did: "edit";
104
+ readonly path: string;
105
+ } | {
106
+ readonly did: "run";
107
+ readonly command: string;
108
+ };
109
+ /**
110
+ * Everything a check may know and everything it may do.
111
+ *
112
+ * The facts are already in memory when the check runs — a write rail sees the file as it WOULD be
113
+ * written, disk untouched; a command rail sees the line about to run; the commit gate sees the
114
+ * staged paths. That is what lets a repo's own check name the file at fault instead of
115
+ * re-deriving the tree, and it makes the work scale with the change rather than with the repo.
116
+ */
117
+ export interface Ctx {
118
+ readonly moment: Moment;
119
+ /** write/touch: the would-be file. */
120
+ readonly file?: {
121
+ readonly path: string;
122
+ readonly content: string;
123
+ } | undefined;
124
+ /** command: the line about to run. */
125
+ readonly command?: string | undefined;
126
+ /** commit: the staged paths. */
127
+ readonly staged?: readonly string[] | undefined;
128
+ /** turn-end: what the actor did this turn, in order. */
129
+ readonly turn?: readonly TurnAction[] | undefined;
130
+ exec(cmd: string): Promise<ExecResult>;
131
+ readonly fs: {
132
+ read(path: string): Promise<string>;
133
+ exists(path: string): Promise<boolean>;
134
+ };
135
+ readonly git: {
136
+ diff(path?: string): Promise<string>;
137
+ stagedFiles(): Promise<string[]>;
138
+ };
139
+ ok(): Verdict;
140
+ /** `detail` rides under the sentence's message, saying which thing broke. */
141
+ fail(detail?: string): Verdict;
142
+ }
143
+ /** A check: ctx in, a verdict out. Sync or async — the engine awaits either. */
144
+ export type Check = (ctx: Ctx) => Verdict | Promise<Verdict>;
145
+ /**
146
+ * The two verdict constructors — INTERNAL to flow, and deliberately not part of its public surface.
147
+ *
148
+ * A check has exactly one way to answer and it is `ctx.ok()` / `ctx.fail(detail)`. That is the
149
+ * spelling the fourteen core checks are written in, the spelling a repo's own check is written in,
150
+ * and the only one flow/index.ts exports. These exist because something has to BUILD the ctx that
151
+ * carries them — the canned one a `.test()` case runs through, and the live one the adapter makes
152
+ * — and that is flow's own job, not a config author's. Exporting both would put two spellings of
153
+ * one answer in front of every check writer, and half of them would pick the wrong one.
154
+ */
155
+ export declare const verdict: {
156
+ ok: () => Verdict;
157
+ fail: (detail?: string) => Verdict;
158
+ };
159
+ /**
160
+ * Everything a check may reach for beyond the event's own facts — the three capabilities, alone.
161
+ *
162
+ * Derived from `Ctx` rather than restated, so there is one description of what flow can reach and
163
+ * no way to grow a fourth capability that a `.test()` case has no means of recording.
164
+ */
165
+ export type World = Pick<Ctx, "exec" | "fs" | "git">;
166
+ /**
167
+ * THE ctx builder — the one place a check's world is assembled, and there must only ever be one.
168
+ *
169
+ * Three callers, and the whole promise of this package rests on their agreeing: the engine builds
170
+ * one from a live event and a real world; `flow test` builds one from a case and a recorded world;
171
+ * replay builds one from a transcript. If those three assembled a ctx even slightly differently,
172
+ * a green case would stop being evidence about the live rail — which is the entire value of cases
173
+ * existing. They were two assemblers for one commit, and that was one commit too many.
174
+ *
175
+ * `exec` is wrapped while `fs` and `git` are passed whole, which looks inconsistent and is not: a
176
+ * method called off `ctx.fs` still has `world.fs` as its receiver, whereas a bare `exec: world.exec`
177
+ * would be called with `ctx` as its receiver and break any world that closes over itself.
178
+ */
179
+ export declare function makeCtx(moment: Moment, facts: Partial<Ctx>, world: World): Ctx;
180
+ /**
181
+ * Declare a CONFIGURED check: a function that takes options and returns a check.
182
+ *
183
+ * Its whole job is to give `ctx` its type inside the closure, so the body is written against the
184
+ * contract with no annotation. The other two forms need nothing at all: an inline check is a
185
+ * lambda in the sentence, and a named one is `export const x: Check = …`.
186
+ *
187
+ * IT DOES ONE THING BESIDES, and it was a plain identity function until it did: the options a
188
+ * check was configured with are kept ON the returned check, under a symbol nothing enumerates. A
189
+ * check is a closure, so once it is built the settings inside it are unreachable — and a reader
190
+ * that cannot see them cannot say what a rule really watches. The generated pack pages printed the
191
+ * `.check(…)` SOURCE instead, which is how `noGitDiscard({})`, a bare `TESTS` constant and a
192
+ * helper call reached a page as the whole description of a rule. Read back through `settings` on
193
+ * the loaded entry they are RESOLVED values: the globs a parameter supplied, the recipe a repo
194
+ * named. Nothing in the run reads them — the engine calls the check and asks it nothing else — so
195
+ * this cannot change what any rule decides.
196
+ */
197
+ export declare function defineCheck<Options, C extends Check>(factory: (options: Options) => C): (options: Options) => C;
198
+ /**
199
+ * The options a check was configured with, or `undefined` for one that never came through
200
+ * `defineCheck` — an inline lambda in a sentence, or a bespoke `const x: Check = …`.
201
+ */
202
+ export declare function checkSettings(check: Check | undefined): unknown;
203
+ /**
204
+ * The answers a case records for whatever its check reaches for beyond the event's own facts.
205
+ *
206
+ * Everything is optional and an absent answer is not a silent zero: the runner reports a reach it
207
+ * cannot answer as a FAILED case, naming the command or path to add here. A case that passed
208
+ * because the world happened to answer "" is the thing this design exists to prevent.
209
+ */
210
+ export interface CaseWorld {
211
+ /**
212
+ * Recorded `ctx.exec` answers, by command. Matched exactly first, then by CONTAINMENT — a key
213
+ * of `"depcruise"` answers the multi-line invocation the depcruise check builds, which is not a
214
+ * string anybody would want to retype into a case.
215
+ */
216
+ readonly exec?: Readonly<Record<string, {
217
+ stdout?: string;
218
+ stderr?: string;
219
+ code?: number;
220
+ }>>;
221
+ /** Files `ctx.fs` can see, by path. A path not here does not exist. */
222
+ readonly fs?: Readonly<Record<string, string>>;
223
+ /** What `ctx.git.diff()` answers. */
224
+ readonly gitDiff?: string;
225
+ /** What `ctx.git.stagedFiles()` answers when the moment carried no staged set of its own. */
226
+ readonly staged?: readonly string[];
227
+ }
228
+ /** One canned event, in the dialect its moment speaks. */
229
+ export type Case =
230
+ /** A command rail, shorthand — by far the commonest case, and it should read as one line. */
231
+ string | {
232
+ readonly command: string;
233
+ readonly world?: CaseWorld;
234
+ } | {
235
+ readonly path: string;
236
+ readonly content: string;
237
+ readonly world?: CaseWorld;
238
+ } | {
239
+ readonly staged: readonly string[];
240
+ readonly world?: CaseWorld;
241
+ } | {
242
+ readonly actions: readonly TurnAction[];
243
+ readonly world?: CaseWorld;
244
+ };
245
+ /** The cases an entry carries: what must pass it, and what must be blocked by it. */
246
+ export interface Cases {
247
+ readonly pass?: readonly Case[];
248
+ readonly block?: readonly Case[];
249
+ }
250
+ /** Does this cases block prove anything? An entry whose answer is no does not load. */
251
+ export declare function hasCases(cases: Cases | undefined): boolean;
252
+ /** What a recorded world was reached for and could not answer. */
253
+ export interface Unanswered {
254
+ readonly kind: "exec" | "read";
255
+ readonly asked: string;
256
+ }
257
+ /**
258
+ * THE recorded world — a `CaseWorld` as the three capabilities, and there must only ever be one.
259
+ *
260
+ * It sits beside `makeCtx` for the same reason and it is the same sentence: `flow test` drives a
261
+ * check through this, the engine's own suite drives a whole rail through this, and F5's replay will
262
+ * drive a recorded session through this. The moment there are two of them they disagree about
263
+ * silence — which is precisely what happened for one commit, where a second copy answered an
264
+ * unrecorded file read with `""` while this one recorded the reach. One of those makes a case pass
265
+ * on a fiction, and telling which is which afterwards is impossible.
266
+ *
267
+ * Recorded exec answers are matched by exact command first and then by CONTAINMENT, which is not
268
+ * laziness: the commands a check builds are not always things a person would want to retype — the
269
+ * depcruise runner's is a multi-line heredoc carrying a compiled config — so a case says
270
+ * `{ "depcruise": … }` and means "when it shells out to that". Exact wins when both could match, so
271
+ * a case can still pin one specific invocation out of three.
272
+ *
273
+ * An unanswered reach is RECORDED rather than guessed at or thrown on. Guessing would let a case
274
+ * pass on a fiction; throwing from here would be a throw in a pure home. The caller turns the
275
+ * record into a failure naming exactly what was asked for.
276
+ */
277
+ export declare function cannedWorld(recorded: CaseWorld, staged: readonly string[] | undefined, unanswered: Unanswered[]): World;
278
+ /** An entry turned off, and why. The reason is the sentence `flow status` prints beside it. */
279
+ export interface DisabledMark {
280
+ readonly reason?: string | undefined;
281
+ }
282
+ interface SpecCommon {
283
+ readonly for?: readonly Category[];
284
+ readonly on?: readonly string[];
285
+ readonly ignore?: readonly string[];
286
+ readonly disabled?: DisabledMark;
287
+ readonly description?: string;
288
+ }
289
+ /** A guardrail, as its sentence stated it. Every key explicit; nothing derived. */
290
+ export interface GuardrailSpec extends SpecCommon {
291
+ readonly kind: "guardrail";
292
+ readonly at?: readonly GuardrailMoment[];
293
+ readonly check?: Check;
294
+ readonly message?: string;
295
+ readonly test?: Cases;
296
+ }
297
+ /** A breadcrumb: a note, and the moments it shows at — all of them, not just the first. */
298
+ export interface BreadcrumbSpec extends SpecCommon {
299
+ readonly kind: "breadcrumb";
300
+ readonly at?: readonly BreadcrumbMoment[];
301
+ readonly text?: string;
302
+ readonly file?: string;
303
+ }
304
+ export type EntrySpec = GuardrailSpec | BreadcrumbSpec;
305
+ /**
306
+ * A path scope, as anything that has one states it — an entry's spec, or the universe row the
307
+ * facts layer reads. Two readers of `on` / `ignore` is how the guard and the report come to
308
+ * disagree about what a rule watches, so there is one and everything that has a scope wears it.
309
+ */
310
+ export interface Scope {
311
+ readonly on?: readonly string[] | undefined;
312
+ readonly ignore?: readonly string[] | undefined;
313
+ }
314
+ /**
315
+ * Is this path inside the entry's `on` / `ignore` scope? No `on` at all is every path.
316
+ *
317
+ * It lives HERE, with the two keys it reads, rather than with the engine that was its first
318
+ * caller. Three things now ask it and they must agree exactly: the engine, deciding which staged
319
+ * files an entry is run against; the case runner, driving that same fan-out from a recorded world;
320
+ * and the facts layer, reporting what a rule watches. A commit rule whose case exercised a
321
+ * different scope test than its live rail would be a green case that is not evidence.
322
+ */
323
+ export declare function inScope(scope: Scope, path: string): boolean;
324
+ /**
325
+ * The grammar's keys, per entry type — the closed sets the load re-checks.
326
+ *
327
+ * They are listed here rather than derived from the interfaces because a type is gone at run time
328
+ * and the load happens at run time. Adding a verb below without adding it here would build an
329
+ * entry the load then refuses, which is a loud failure and the right one.
330
+ */
331
+ export declare const GUARDRAIL_KEYS: readonly ["at", "for", "on", "ignore", "check", "message", "disabled", "test", "description"];
332
+ export declare const BREADCRUMB_KEYS: readonly ["at", "for", "on", "ignore", "text", "file", "disabled", "description"];
333
+ export type GuardrailKey = (typeof GUARDRAIL_KEYS)[number];
334
+ export type BreadcrumbKey = (typeof BREADCRUMB_KEYS)[number];
335
+ /**
336
+ * A verb, until it is spoken.
337
+ *
338
+ * Exported because the override chain in packs.ts is the same trick over the same grammar, and a
339
+ * second copy of these fifty characters is a second place for "may be said once" to stop being
340
+ * true.
341
+ */
342
+ export type Once<Spoken extends string, Key extends string, Verb> = Key extends Spoken ? never : Verb;
343
+ /** What `.on()` becomes once `.at(…)` has ruled every path out. The TEXT is the diagnostic. */
344
+ type DeadScope = "flow: every moment this entry fires at carries no path, so .on()/.ignore() would narrow nothing";
345
+ /**
346
+ * `.on()` / `.ignore()`, or the refusal — the compile-time half of the dead-scope rule.
347
+ *
348
+ * A path scope is consulted only when the event names a path (`PATH_MOMENTS`), so an entry whose
349
+ * every moment is `command`, `turn-end` or `session` and which nonetheless speaks `.on(…)` reads as
350
+ * armed and narrows nothing — config that looks like a fence and is one line of decoration. The
351
+ * engine's silence about it was the old engine's behaviour, kept deliberately until now.
352
+ *
353
+ * It is a WRONG-ARGUMENT refusal rather than a `never`, and that is the whole design: `never` gives
354
+ * "this expression is not callable", which says nothing about why. A parameter typed as the message
355
+ * puts the sentence itself in the diagnostic, where a reader is already looking.
356
+ *
357
+ * It can only fire when `.at(…)` was spoken FIRST — before that, `At` is the whole vocabulary and
358
+ * every scope is live. Saying `.on(…)` above `.at(command)` is the same mistake with the words in
359
+ * the other order, and the load's `dead-scope` refusal is what catches it. Both halves, always.
360
+ */
361
+ type Scoping<At extends Moment, Verb> = [Extract<At, PathMoment>] extends [never] ? (dead: DeadScope) => never : Verb;
362
+ /** Anything a pack may file under an entry name. The one shape `definePack` walks. */
363
+ export interface Sentence<S extends EntrySpec = EntrySpec> {
364
+ readonly spec: S;
365
+ }
366
+ /** A group of entries, or of groups — a pack may nest, and `git.node.versionIsSemver` is why. */
367
+ export interface EntryGroup {
368
+ readonly [key: string]: Sentence | EntryGroup;
369
+ }
370
+ export interface GuardrailSentence<Spoken extends GuardrailKey = never, At extends GuardrailMoment = GuardrailMoment> extends Sentence<GuardrailSpec> {
371
+ readonly at: Once<Spoken, "at", <M extends GuardrailMoment[]>(...moments: M) => GuardrailSentence<Spoken | "at", M[number]>>;
372
+ readonly for: Once<Spoken, "for", (...categories: Category[]) => GuardrailSentence<Spoken | "for", At>>;
373
+ readonly on: Once<Spoken, "on", Scoping<At, (...globs: string[]) => GuardrailSentence<Spoken | "on", At>>>;
374
+ readonly ignore: Once<Spoken, "ignore", Scoping<At, (...globs: string[]) => GuardrailSentence<Spoken | "ignore", At>>>;
375
+ readonly check: Once<Spoken, "check", (check: Check) => GuardrailSentence<Spoken | "check", At>>;
376
+ readonly message: Once<Spoken, "message", (message: string) => GuardrailSentence<Spoken | "message", At>>;
377
+ readonly disabled: Once<Spoken, "disabled", (reason?: string) => GuardrailSentence<Spoken | "disabled", At>>;
378
+ readonly test: Once<Spoken, "test", (cases: Cases) => GuardrailSentence<Spoken | "test", At>>;
379
+ readonly description: Once<Spoken, "description", (description: string) => GuardrailSentence<Spoken | "description", At>>;
380
+ }
381
+ export interface BreadcrumbSentence<Spoken extends BreadcrumbKey = never, At extends BreadcrumbMoment = BreadcrumbMoment> extends Sentence<BreadcrumbSpec> {
382
+ readonly at: Once<Spoken, "at", <M extends BreadcrumbMoment[]>(...moments: M) => BreadcrumbSentence<Spoken | "at", M[number]>>;
383
+ readonly for: Once<Spoken, "for", (...categories: Category[]) => BreadcrumbSentence<Spoken | "for", At>>;
384
+ readonly on: Once<Spoken, "on", Scoping<At, (...globs: string[]) => BreadcrumbSentence<Spoken | "on", At>>>;
385
+ readonly ignore: Once<Spoken, "ignore", Scoping<At, (...globs: string[]) => BreadcrumbSentence<Spoken | "ignore", At>>>;
386
+ readonly text: Once<Spoken, "text", (text: string) => BreadcrumbSentence<Spoken | "text", At>>;
387
+ readonly file: Once<Spoken, "file", (path: string) => BreadcrumbSentence<Spoken | "file", At>>;
388
+ readonly disabled: Once<Spoken, "disabled", (reason?: string) => BreadcrumbSentence<Spoken | "disabled", At>>;
389
+ readonly description: Once<Spoken, "description", (description: string) => BreadcrumbSentence<Spoken | "description", At>>;
390
+ }
391
+ type Missing<Spoken extends string, Required extends string> = Exclude<Required, Spoken>;
392
+ type NeverSaid<What extends string, Verb extends string> = Verb extends string ? `flow: this ${What} never said .${Verb}() — nothing defaults, so it is not an entry` : never;
393
+ /** A guardrail must say when it fires, what it asks, what it tells you, and what proves it. */
394
+ type GuardrailRequired = "at" | "check" | "message" | "test";
395
+ /** A breadcrumb must say when it shows, and carry its prose one of the two ways. */
396
+ type BreadcrumbProse = "flow: this breadcrumb has no prose — say .text(…) or .file(…)";
397
+ export type CompleteEntry<B> = B extends GuardrailSentence<infer S, infer _GuardrailAt> ? "disabled" extends S ? B : [Missing<S, GuardrailRequired>] extends [never] ? B : NeverSaid<"guardrail", Missing<S, GuardrailRequired>> : B extends BreadcrumbSentence<infer S, infer _BreadcrumbAt> ? "disabled" extends S ? B : [Missing<S, "at">] extends [never] ? "text" extends S ? B : "file" extends S ? B : BreadcrumbProse : NeverSaid<"breadcrumb", Missing<S, "at">> : B extends EntryGroup ? {
398
+ readonly [K in keyof B]: CompleteEntry<B[K]>;
399
+ } : never;
400
+ /**
401
+ * THE chain builder — every chain in this grammar, and there are three of them: a guardrail, a
402
+ * breadcrumb, and an override.
403
+ *
404
+ * The three differ in only two ways, and both are arguments here: which keys they START from, and
405
+ * how the accumulated keys are EXPOSED (a sentence shows them as `.spec`, an override wraps them
406
+ * in its binding). What they must never differ in is the verbs themselves — that a second `.on()`
407
+ * replaces nothing, that `.disabled()` records an empty reason rather than dropping it, that every
408
+ * step returns a NEW object so a pack can build a family off a shared prefix. Each of those was
409
+ * written twice for one commit, which is one commit longer than "one of the two copies is now
410
+ * subtly different" needs.
411
+ *
412
+ * Which verbs a given caller may reach is a TYPE question and is answered entirely by the
413
+ * interfaces above and in packs.ts — an override has no `.check` because its chain type does not
414
+ * declare one, not because a different builder withheld it. That is the same reason this works
415
+ * over an untyped record: the chain types are a PROJECTION of this one object, a shrinking subset
416
+ * of eleven verbs, and no signature can describe that from the inside. The casts at each entry
417
+ * point are the seam.
418
+ */
419
+ export declare function chain<Exposed extends object>(spoken: Readonly<Record<string, unknown>>, expose: (spoken: Readonly<Record<string, unknown>>) => Exposed): Exposed;
420
+ /** Open a guardrail sentence. It blocks; it must therefore say what it asks and what it proves. */
421
+ export declare function guardrail(): GuardrailSentence;
422
+ /** Open a breadcrumb sentence. Breadcrumbs are data — no check, ever, and no rail to block. */
423
+ export declare function breadcrumb(): BreadcrumbSentence;
424
+ /** Is this an entry rather than a group of them? What `definePack` walks a pack's tree with. */
425
+ export declare function isSentence(value: unknown): value is Sentence;
426
+ declare const PACK: unique symbol;
427
+ declare const REF: unique symbol;
428
+ /** Phantoms: typed, never present at run time. They exist so a call site can be refused. */
429
+ declare const PARAMS: unique symbol;
430
+ declare const KIND: unique symbol;
431
+ /** A pack's definition — its name, and how to build its entries. */
432
+ export interface PackDefinition {
433
+ readonly name: string;
434
+ /** Build the entry tree. A pack that takes no parameters ignores the argument. */
435
+ readonly build: (params: unknown) => EntryGroup;
436
+ /**
437
+ * Does this pack REQUIRE parameters? Measured from the factory's arity, so a factory whose
438
+ * parameter has a default — `(repo: Doors = {}) => …`, every field optional — is a pack
439
+ * `pack(docs)` may bind, exactly as the compiler already allows.
440
+ */
441
+ readonly takesParams: boolean;
442
+ }
443
+ /** Where a reference points. Flat: the pack's name, and the entry's dotted path within it. */
444
+ export interface RefTarget {
445
+ readonly pack: string;
446
+ readonly id: string;
447
+ }
448
+ /**
449
+ * A typed reference to one entry.
450
+ *
451
+ * `Kind` is a phantom — nothing carries it at run time, because a parameterised pack's tree does
452
+ * not exist until it is bound and a reference has to be writable before that. What it buys is the
453
+ * whole point of references being typed: `override()` knows whether it is looking at a guardrail
454
+ * or a breadcrumb, so `.message()` on a breadcrumb and `.text()` on a guardrail are refused where
455
+ * you type them rather than at a load nobody watches.
456
+ */
457
+ export interface EntryRef<Kind extends EntrySpec["kind"] = EntrySpec["kind"]> {
458
+ readonly [REF]: RefTarget;
459
+ readonly [KIND]?: Kind;
460
+ }
461
+ /** The reference tree a pack presents — the same shape as the entries, refs at the leaves. */
462
+ export type Refs<T> = {
463
+ readonly [K in keyof T]: T[K] extends Sentence<GuardrailSpec> ? EntryRef<"guardrail"> : T[K] extends Sentence<BreadcrumbSpec> ? EntryRef<"breadcrumb"> : T[K] extends EntryGroup ? Refs<T[K]> : never;
464
+ };
465
+ interface PackHandle<Params> {
466
+ readonly [PACK]: PackDefinition;
467
+ /** Phantom. It exists so `pack(tdd)` without parameters is not a call the compiler will make. */
468
+ readonly [PARAMS]?: Params;
469
+ }
470
+ /** What `definePack` hands back: the reference tree, with the definition riding underneath it. */
471
+ export type Pack<T extends EntryGroup, Params = undefined> = Refs<T> & PackHandle<Params>;
472
+ export interface PackBinding {
473
+ readonly kind: "pack";
474
+ /** `undefined` when the bound value was never a pack — what the load's `not-a-pack` reads. */
475
+ readonly pack: PackDefinition | undefined;
476
+ readonly params: unknown;
477
+ /** Were parameters supplied at all? The load's coarse backstop behind the compiler's fine one. */
478
+ readonly hasParams: boolean;
479
+ }
480
+ export interface OverrideBinding {
481
+ readonly kind: "override";
482
+ readonly ref: RefTarget;
483
+ /** ONLY the keys the chain spoke. An absent key here is one that flows through from the pack. */
484
+ readonly spoken: Readonly<Record<string, unknown>>;
485
+ }
486
+ export type Binding = PackBinding | OverrideBinding;
487
+ /**
488
+ * The engine's dials — the few numbers that are about the RUN rather than about any one entry.
489
+ *
490
+ * It is deliberately not part of the sentence grammar, and `defineConfig`'s second argument is
491
+ * where it goes. `driftTokens` is the clearest example of why: how far a session may drift before
492
+ * a breadcrumb shows again is one answer for the whole repo, and putting it on entries would make
493
+ * every breadcrumb restate it or inherit it silently. The grammar stays closed; this is a knob.
494
+ */
495
+ export interface Settings {
496
+ /** Context tokens between re-showings of a breadcrumb. Absent = the engine's own default. */
497
+ readonly driftTokens?: number;
498
+ }
499
+ /** A whole guard, as its config file states it: bindings, in the order they were spoken. */
500
+ export interface FlowConfig {
501
+ readonly bindings: readonly Binding[];
502
+ readonly settings: Settings;
503
+ }
504
+ /**
505
+ * Declare a pack: a name, and its entries.
506
+ *
507
+ * The entries argument is `T & CompleteEntry<T>` — the first half infers the tree, the second
508
+ * checks every sentence in it. An incomplete one lands on a string literal whose text names the
509
+ * verb it never said, so the editor's error is the instruction.
510
+ */
511
+ export declare function definePack<T extends EntryGroup>(name: string, entries: T & CompleteEntry<T>): Pack<T>;
512
+ export declare function definePack<Params, T extends EntryGroup>(name: string, factory: (params: Params) => T & CompleteEntry<T>): Pack<T, Params>;
513
+ /** The definition riding under a pack value, or undefined if this was never made by definePack. */
514
+ export declare function packDefinition(value: unknown): PackDefinition | undefined;
515
+ /** Where a reference points, or undefined if this was never a reference. */
516
+ export declare function refTarget(value: unknown): RefTarget | undefined;
517
+ /**
518
+ * Bind a pack — all its entries, opt-out.
519
+ *
520
+ * ONE signature, not two overloads, and the reason is the error message. A pack that declares no
521
+ * parameters carries `Params = undefined`, and the rest-tuple below then resolves to `[]` — so
522
+ * `pack(node)` is a complete call. A pack that DOES declare them infers its own parameter type,
523
+ * the tuple gains a required member, and `pack(tdd)` is short an argument: "Expected 2 arguments,
524
+ * but got 1", at the line you typed. Two overloads produced the same refusal wearing a diagnostic
525
+ * about a phantom property, which was true and unreadable.
526
+ */
527
+ export declare function pack<T extends EntryGroup, Params>(definition: Pack<T, Params>, ...params: undefined extends Params ? [params?: Params] : [params: Params]): PackBinding;
528
+ /**
529
+ * What an override may speak: BOTH entry grammars, minus the two verbs it must never reach.
530
+ *
531
+ * Derived rather than listed, and the subtraction is the whole statement — "an override moves an
532
+ * entry, rewords it or switches it off, and can never touch what the entry ASKS" is said once,
533
+ * here, in a form that cannot drift from the key sets it is subtracting from. A third hand-typed
534
+ * listing of the grammar would be a third place to forget a verb.
535
+ */
536
+ type OverrideKey = Exclude<GuardrailKey | BreadcrumbKey, "check" | "test">;
537
+ interface OverrideChain<Kind extends EntrySpec["kind"], Spoken extends OverrideKey> {
538
+ readonly binding: OverrideBinding;
539
+ readonly at: Once<Spoken, "at", (...moments: (Kind extends "guardrail" ? GuardrailMoment : BreadcrumbMoment)[]) => OverrideChain<Kind, Spoken | "at">>;
540
+ readonly for: Once<Spoken, "for", (...categories: Category[]) => OverrideChain<Kind, Spoken | "for">>;
541
+ readonly on: Once<Spoken, "on", (...globs: string[]) => OverrideChain<Kind, Spoken | "on">>;
542
+ readonly ignore: Once<Spoken, "ignore", (...globs: string[]) => OverrideChain<Kind, Spoken | "ignore">>;
543
+ readonly message: Kind extends "guardrail" ? Once<Spoken, "message", (message: string) => OverrideChain<Kind, Spoken | "message">> : never;
544
+ readonly text: Kind extends "breadcrumb" ? Once<Spoken, "text", (text: string) => OverrideChain<Kind, Spoken | "text">> : never;
545
+ readonly file: Kind extends "breadcrumb" ? Once<Spoken, "file", (path: string) => OverrideChain<Kind, Spoken | "file">> : never;
546
+ readonly disabled: Once<Spoken, "disabled", (reason?: string) => OverrideChain<Kind, Spoken | "disabled">>;
547
+ readonly description: Once<Spoken, "description", (description: string) => OverrideChain<Kind, Spoken | "description">>;
548
+ }
549
+ /**
550
+ * Override one entry, speaking only what changes.
551
+ *
552
+ * There is no `.check()` and no `.test()` here, and their absence is the design: an override can
553
+ * move an entry, reword it or switch it off, but it can never reach inside what the entry ASKS.
554
+ */
555
+ export declare function override<Kind extends EntrySpec["kind"]>(ref: EntryRef<Kind>): OverrideChain<Kind, never>;
556
+ /** Anything a config line may be: a pack binding, or an override chain at any point in its chain. */
557
+ export type ConfigSentence = PackBinding | {
558
+ readonly binding: OverrideBinding;
559
+ };
560
+ /**
561
+ * The config: the sentences, in order, and the engine's dials.
562
+ *
563
+ * It records and does not judge. Every refusal lives in `loadConfig` instead, for one reason —
564
+ * a config with three mistakes should report three, and a function that threw on the first would
565
+ * report one and hide the rest behind a fix.
566
+ *
567
+ * The settings argument is OPTIONAL and trailing, which is the shape F1 promised it: `driftTokens`
568
+ * is an engine number and the engine did not exist yet, so the grammar was closed without it and
569
+ * left room for exactly this. A config that sets nothing writes `defineConfig([…])` as before.
570
+ */
571
+ export declare function defineConfig(sentences: readonly ConfigSentence[], settings?: Settings): FlowConfig;
572
+ /** An effective entry, and where each of its keys came from. */
573
+ export interface Overlaid {
574
+ readonly spec: EntrySpec;
575
+ readonly source: Readonly<Record<string, "pack" | "override">>;
576
+ }
577
+ /**
578
+ * Lay an override's spoken keys over a definition, and record where every key came from.
579
+ *
580
+ * A named key replaces the WHOLE key — lists never merge, so an override that narrows one glob
581
+ * restates the others. That is deliberate: two merge semantics is two bugs, and it is the same
582
+ * replacement rule the YAML shadows had, kept.
583
+ *
584
+ * THE ONLY place a source map is built, including the first one: an entry with no override at all
585
+ * is `overlay(spec, {})`, not a second loop somewhere else that agrees with this one until it
586
+ * doesn't. `prior` is what makes that true for the second override onto one entry as well —
587
+ * without it, laying anything over an already-overridden entry would relabel the earlier
588
+ * override's keys as the pack's.
589
+ */
590
+ export declare function overlay(base: EntrySpec, spoken: Readonly<Record<string, unknown>>, prior?: Readonly<Record<string, "pack" | "override">>): Overlaid;
591
+ /** The closed vocabulary of refusals. Every load failure is exactly one of these. */
592
+ export declare const REFUSAL_CODES: readonly ["not-a-pack", "missing-parameter", "unknown-entry", "unknown-key", "unknown-moment", "missing-mandatory", "undeclared-category", "no-cases", "duplicate-id", "dead-scope"];
593
+ export type RefusalCode = (typeof REFUSAL_CODES)[number];
594
+ /** One reason the config will not load. */
595
+ export interface Refusal {
596
+ readonly code: RefusalCode;
597
+ /** What it is about: `pack.entry`, or the pack alone when no entry is named. */
598
+ readonly entry: string;
599
+ /** The sentence a reader acts on. It always names the entry, because a hook shows one line. */
600
+ readonly detail: string;
601
+ }
602
+ /** One entry as the engine will run it: the pack's sentence, with every override laid over it. */
603
+ export interface LoadedEntry {
604
+ /** `pack.id` — one spelling from definition to log line. */
605
+ readonly id: string;
606
+ readonly pack: string;
607
+ /** The entry's dotted path within its pack. */
608
+ readonly key: string;
609
+ readonly spec: EntrySpec;
610
+ /** Where each key came from — what `flow status` prints when it explains an entry. */
611
+ readonly source: Readonly<Record<string, "pack" | "override">>;
612
+ /** The categories this entry is scoped to. Empty means every category. */
613
+ readonly categories: readonly string[];
614
+ /** The engine phases this entry fires at. Empty for a breadcrumb, which has no rail. */
615
+ readonly phases: readonly string[];
616
+ /**
617
+ * The options this entry's check was configured with, RESOLVED — what a parameter supplied, not
618
+ * what the source typed. `undefined` for a breadcrumb, and for a check that never came through
619
+ * `defineCheck`. Nothing in the run reads it; it is how a reader is told what a rule watches.
620
+ */
621
+ readonly settings?: unknown;
622
+ }
623
+ export type LoadResult = {
624
+ readonly ok: true;
625
+ readonly entries: readonly LoadedEntry[];
626
+ } | {
627
+ readonly ok: false;
628
+ readonly refusals: readonly Refusal[];
629
+ };
630
+ /**
631
+ * Every refusal as ONE block of text — the sentence a person reads when the config will not load.
632
+ *
633
+ * There is exactly one wording, here, because there are three places it surfaces and they must not
634
+ * drift: the `flow test` exception (errors.ts), the block a gated moment refuses with, and the
635
+ * notice a breadcrumb moment degrades to. A guard that describes its own breakage differently
636
+ * depending on which rail you hit is a guard nobody learns to read.
637
+ */
638
+ export declare function refusalText(refusals: readonly Refusal[]): string;
639
+ /** Turn a config's bindings into the effective regime, or into every reason it cannot be one. */
640
+ export declare function loadConfig(config: FlowConfig): LoadResult;
641
+ export {};