@orkestrel/brief 0.0.1
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 +47 -0
- package/dist/src/core/index.cjs +2219 -0
- package/dist/src/core/index.cjs.map +1 -0
- package/dist/src/core/index.d.cts +1911 -0
- package/dist/src/core/index.d.ts +1911 -0
- package/dist/src/core/index.js +2129 -0
- package/dist/src/core/index.js.map +1 -0
- package/package.json +92 -0
|
@@ -0,0 +1,1911 @@
|
|
|
1
|
+
import { Ambiguity } from '@orkestrel/interpret';
|
|
2
|
+
import { ArrayShape } from '@orkestrel/contract';
|
|
3
|
+
import { BooleanShape } from '@orkestrel/contract';
|
|
4
|
+
import { ContractInterface } from '@orkestrel/contract';
|
|
5
|
+
import { EmitterErrorHandler } from '@orkestrel/emitter';
|
|
6
|
+
import { EmitterHooks } from '@orkestrel/emitter';
|
|
7
|
+
import { EmitterInterface } from '@orkestrel/emitter';
|
|
8
|
+
import { Entity } from '@orkestrel/interpret';
|
|
9
|
+
import { Guard } from '@orkestrel/contract';
|
|
10
|
+
import { Intent } from '@orkestrel/interpret';
|
|
11
|
+
import { Interpretation } from '@orkestrel/interpret';
|
|
12
|
+
import { InterpretInterface } from '@orkestrel/interpret';
|
|
13
|
+
import { LiteralShape } from '@orkestrel/contract';
|
|
14
|
+
import { LogicalDefinition } from '@orkestrel/reason';
|
|
15
|
+
import { LogicalResult } from '@orkestrel/reason';
|
|
16
|
+
import { ManagerAddOptions } from '@orkestrel/interpret';
|
|
17
|
+
import { NumberShape } from '@orkestrel/contract';
|
|
18
|
+
import { ObjectShape } from '@orkestrel/contract';
|
|
19
|
+
import { OptionalShape } from '@orkestrel/contract';
|
|
20
|
+
import { ReasonInterface } from '@orkestrel/reason';
|
|
21
|
+
import { ReasonValidationResult } from '@orkestrel/reason';
|
|
22
|
+
import { RuleResult } from '@orkestrel/reason';
|
|
23
|
+
import { StringShape } from '@orkestrel/contract';
|
|
24
|
+
import { Subject } from '@orkestrel/reason';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Narrow unknown data to a `Brief`, throwing when it is off-contract.
|
|
28
|
+
*
|
|
29
|
+
* @remarks
|
|
30
|
+
* The throwing half of the intake pair: this returns its argument by IDENTITY once the
|
|
31
|
+
* guard passes, while `parseBrief` returns `undefined` for bad input. It constructs
|
|
32
|
+
* nothing, so it is an assertion rather than a factory. Reserve it for programmer-error
|
|
33
|
+
* contexts where invalidity is a bug.
|
|
34
|
+
*
|
|
35
|
+
* @param data - The candidate brief data.
|
|
36
|
+
* @returns The same value, now known to satisfy {@link Brief}.
|
|
37
|
+
* @throws {@link BriefError} `INVALID` when `data` fails `isBrief`.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* import { assertBrief, brief, proof, task } from '@orkestrel/brief'
|
|
42
|
+
*
|
|
43
|
+
* assertBrief(brief(task('plan', 'ops', 'Plan the release.'), { proofs: [proof('x', 'y')] }))
|
|
44
|
+
* assertBrief({ task: { operation: 'plan', domain: 'ops', statement: 'x.' } }) // throws INVALID
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function assertBrief(data: unknown): Brief;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A string of one or more spaces and nothing else.
|
|
51
|
+
*
|
|
52
|
+
* @remarks
|
|
53
|
+
* The one exemplar side `exampleToLines` must NOT pad. CommonMark strips a fully-blank code
|
|
54
|
+
* span to nothing rather than one space from each end, so padding inflates an all-space value
|
|
55
|
+
* while every other value needs the pad to keep its own boundary spaces.
|
|
56
|
+
*
|
|
57
|
+
* `+` rather than `*`, because the EMPTY string is not that case: it has no spaces to
|
|
58
|
+
* preserve, and withholding the pad emitted an empty backtick run that does not close.
|
|
59
|
+
*/
|
|
60
|
+
export declare const BLANK_PATTERN: RegExp;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The closed execution contract — a rough request with every implicit decision resolved.
|
|
64
|
+
*
|
|
65
|
+
* @remarks
|
|
66
|
+
* `trace` and `hash` are DERIVED by `pinBrief`. The `brief` builder cannot set them, so
|
|
67
|
+
* nothing this package produces authors them.
|
|
68
|
+
*
|
|
69
|
+
* They are still SHAPE-checked rather than verified on the way in: `isBrief` and `parseBrief`
|
|
70
|
+
* accept any single-line string, because that is what lets a pinned brief round-trip through
|
|
71
|
+
* JSON and back. So an inbound brief's `hash` is not proof of its content — re-derive it with
|
|
72
|
+
* `briefToHash` if the source is untrusted. `BriefManager` does exactly that, and refuses a
|
|
73
|
+
* record whose stored hash contradicts its content.
|
|
74
|
+
*/
|
|
75
|
+
export declare interface Brief {
|
|
76
|
+
readonly task: Task;
|
|
77
|
+
readonly authority: readonly Reference[];
|
|
78
|
+
readonly manifest: Manifest;
|
|
79
|
+
readonly outcomes: readonly Outcome[];
|
|
80
|
+
readonly rules: readonly string[];
|
|
81
|
+
readonly invariants: readonly string[];
|
|
82
|
+
readonly givens: readonly Given[];
|
|
83
|
+
readonly examples: readonly Example[];
|
|
84
|
+
readonly assumptions: readonly string[];
|
|
85
|
+
readonly citations: readonly Citation[];
|
|
86
|
+
readonly gaps: readonly Gap[];
|
|
87
|
+
readonly risks: readonly Risk[];
|
|
88
|
+
readonly output: Output;
|
|
89
|
+
readonly proofs: readonly Proof[];
|
|
90
|
+
readonly trace?: string;
|
|
91
|
+
readonly hash?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Build a `Brief` from a `Task` plus section overrides.
|
|
96
|
+
*
|
|
97
|
+
* @param subject - The task the brief is about.
|
|
98
|
+
* @param overrides - Any sections to fill; every absent collection defaults to `[]`,
|
|
99
|
+
* `output` defaults to `output('markdown')`, and `trace` / `hash` stay OMITTED so
|
|
100
|
+
* `pinBrief` can fill them.
|
|
101
|
+
* @returns A fresh, unpinned `Brief`.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* import { brief, outcome, proof, task } from '@orkestrel/brief'
|
|
106
|
+
*
|
|
107
|
+
* brief(task('audit', 'code', 'Audit the barrel for undocumented exports.'), {
|
|
108
|
+
* outcomes: [outcome(1, 'every export appears in the guide')],
|
|
109
|
+
* proofs: [proof('parity passes', 'npm run test:guides')],
|
|
110
|
+
* })
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export declare function brief(subject: Task, overrides?: Partial<Omit<Brief, 'task' | 'trace' | 'hash'>>): Brief;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The compilation orchestrator — the four-stage `[interpret, draft, gate, pin]` pipeline.
|
|
117
|
+
*
|
|
118
|
+
* @remarks
|
|
119
|
+
* `compile` is genuinely SYNCHRONOUS and never throws for a brief it cannot emit: a
|
|
120
|
+
* blocking gap, a refused gate, and a thrown stage all yield a visible INCOMPLETE
|
|
121
|
+
* `Briefing`. It owns the engines it created and BORROWS the ones passed in, so
|
|
122
|
+
* `destroy()` releases only what it made.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```ts
|
|
126
|
+
* import { BriefCompiler, proof, task } from '@orkestrel/brief'
|
|
127
|
+
*
|
|
128
|
+
* const compiler = new BriefCompiler()
|
|
129
|
+
* const briefing = compiler.compile({
|
|
130
|
+
* task: task('audit', 'code', 'Audit the barrel for undocumented exports.'),
|
|
131
|
+
* outcomes: [{ rank: 1, text: 'every export appears in the guide', required: true }],
|
|
132
|
+
* proofs: [proof('parity passes', 'npm run test:guides')],
|
|
133
|
+
* })
|
|
134
|
+
* briefing.brief !== undefined // true — the presence of the brief IS the completeness test
|
|
135
|
+
* compiler.destroy()
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
export declare class BriefCompiler implements BriefCompilerInterface {
|
|
139
|
+
#private;
|
|
140
|
+
constructor(options?: BriefCompilerOptions);
|
|
141
|
+
get emitter(): EmitterInterface<BriefCompilerEventMap>;
|
|
142
|
+
get interpret(): InterpretInterface;
|
|
143
|
+
get reason(): ReasonInterface;
|
|
144
|
+
compile(input: BriefInput): Briefing;
|
|
145
|
+
gate(source: Brief): LogicalResult;
|
|
146
|
+
destroy(): void;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** The `BriefCompiler`'s push observation surface. */
|
|
150
|
+
export declare type BriefCompilerEventMap = {
|
|
151
|
+
compile: readonly [briefing: Briefing];
|
|
152
|
+
block: readonly [questions: readonly Gap[]];
|
|
153
|
+
error: readonly [error: unknown];
|
|
154
|
+
destroy: readonly [];
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/** The compilation orchestrator contract. */
|
|
158
|
+
export declare interface BriefCompilerInterface {
|
|
159
|
+
readonly emitter: EmitterInterface<BriefCompilerEventMap>;
|
|
160
|
+
readonly interpret: InterpretInterface;
|
|
161
|
+
readonly reason: ReasonInterface;
|
|
162
|
+
compile(input: BriefInput): Briefing;
|
|
163
|
+
gate(brief: Brief): LogicalResult;
|
|
164
|
+
destroy(): void;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Input to `createBriefCompiler`.
|
|
169
|
+
*
|
|
170
|
+
* @remarks
|
|
171
|
+
* `interpret` and `reason` are BORROWED when supplied — the compiler destroys only
|
|
172
|
+
* engines it created itself. `actions` and `domains` map an interpret `Intent`'s free
|
|
173
|
+
* strings onto the closed task vocabularies; an unmapped value drafts no task. The gate
|
|
174
|
+
* is FIXED: readiness is this package's contract, not a caller setting.
|
|
175
|
+
*
|
|
176
|
+
* A borrowed engine is the caller's own code, not an attacker, and this package does not
|
|
177
|
+
* treat it as one. The line is OWNERSHIP, and it produces four obligations worth stating
|
|
178
|
+
* because none of them is enforced in code:
|
|
179
|
+
*
|
|
180
|
+
* - Every value a borrowed engine returns is OWNED AT ARRIVAL — copied where the value
|
|
181
|
+
* permits it, sealed in place where it does not — and then read exactly once. What the
|
|
182
|
+
* engine does with its own object afterwards cannot reach a `Briefing`.
|
|
183
|
+
* - Only the VERDICT is shape-checked, because `ReasonInterface.reason` publishes a union
|
|
184
|
+
* this package must narrow. The `Interpretation` is owned and not validated: it is a
|
|
185
|
+
* 14-member foreign interface with no published guard, and only four of its members are
|
|
186
|
+
* read, inside contained code. That asymmetry is deliberate — guarding it would trade a
|
|
187
|
+
* contained failure for a wrong refusal against a valid engine.
|
|
188
|
+
* - Neither engine is narrowed past its published contract. `Entity.value` is `unknown` and
|
|
189
|
+
* `LogicalResult` is an interface a class instance satisfies, so a value JSON cannot
|
|
190
|
+
* express is on-contract and is sealed rather than refused.
|
|
191
|
+
* - `actions` and `domains` are read LIVE on every `compile`, not snapshotted at
|
|
192
|
+
* construction. Mutating them between calls changes what the next call derives.
|
|
193
|
+
*
|
|
194
|
+
* Whether the engine is correct, and whether it answers the same way twice, remain the
|
|
195
|
+
* caller's own problem.
|
|
196
|
+
*/
|
|
197
|
+
export declare interface BriefCompilerOptions {
|
|
198
|
+
readonly interpret?: InterpretInterface;
|
|
199
|
+
readonly reason?: ReasonInterface;
|
|
200
|
+
readonly actions?: Readonly<Record<string, TaskOperation>>;
|
|
201
|
+
readonly domains?: Readonly<Record<string, TaskDomain>>;
|
|
202
|
+
readonly on?: EmitterHooks<BriefCompilerEventMap>;
|
|
203
|
+
readonly error?: EmitterErrorHandler;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* The one error class this package throws.
|
|
208
|
+
*
|
|
209
|
+
* @remarks
|
|
210
|
+
* Throws are reserved for caller misuse: `assertBrief`, `snapshotBrief`, and `pinBrief` on
|
|
211
|
+
* off-contract data throw `INVALID`; any method after `destroy()` throws `DESTROYED`; and `BriefCompiler.gate` throws
|
|
212
|
+
* `GATE_FAILED` when a borrowed reasoner returns a non-logical result. A stage that fails
|
|
213
|
+
* inside `compile` is CONTAINED as a `BriefStageFailure` on the `Briefing` instead.
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* ```ts
|
|
217
|
+
* import { BriefError } from '@orkestrel/brief'
|
|
218
|
+
*
|
|
219
|
+
* const error = new BriefError('INVALID', 'Brief failed the exact-record contract', {
|
|
220
|
+
* field: 'proofs',
|
|
221
|
+
* })
|
|
222
|
+
* error.code // 'INVALID'
|
|
223
|
+
* error.context // { field: 'proofs' }
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
export declare class BriefError extends Error {
|
|
227
|
+
readonly code: BriefErrorCode;
|
|
228
|
+
readonly context?: Readonly<Record<string, unknown>>;
|
|
229
|
+
constructor(code: BriefErrorCode, message: string, context?: Readonly<Record<string, unknown>>);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* The machine-readable reasons a {@link BriefError} carries.
|
|
234
|
+
*
|
|
235
|
+
* @remarks
|
|
236
|
+
* Inside `compile` every stage failure is CONTAINED: the four `*_FAILED` codes and
|
|
237
|
+
* `BLOCKED` mark it on the {@link Briefing} rather than throwing.
|
|
238
|
+
*
|
|
239
|
+
* `INTERPRET_FAILED` needs a FOREIGN `InterpretInterface`. `@orkestrel/interpret` contains
|
|
240
|
+
* its own stage failures and returns a degraded `Interpretation` rather than throwing, so
|
|
241
|
+
* the default engine never raises it; `BriefCompilerOptions.interpret` is the seam a caller
|
|
242
|
+
* reaches it through. Three codes also reach a
|
|
243
|
+
* throw, from methods outside that containment — `INVALID` from `assertBrief`,
|
|
244
|
+
* `snapshotBrief`, and `pinBrief`; `DESTROYED` from any method after
|
|
245
|
+
* `destroy()`; and `GATE_FAILED` from `BriefCompiler.gate` when a borrowed reasoner returns a
|
|
246
|
+
* non-logical result OR throws its own error, which is translated rather than leaked so that
|
|
247
|
+
* every throw out of this module stays a `BriefError` an `isBriefError` catch can narrow.
|
|
248
|
+
*/
|
|
249
|
+
export declare type BriefErrorCode = 'INTERPRET_FAILED' | 'DRAFT_FAILED' | 'GATE_FAILED' | 'PIN_FAILED' | 'BLOCKED' | 'INVALID' | 'DESTROYED';
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* The full, replayable outcome of one `compile()` call.
|
|
253
|
+
*
|
|
254
|
+
* @remarks
|
|
255
|
+
* `brief` is present exactly when the compile completed, so it is ALSO the completeness
|
|
256
|
+
* test — there is no `complete` flag, because a second stored fact is free to disagree
|
|
257
|
+
* with the first. `questions` carries what the caller must answer when it is absent.
|
|
258
|
+
*
|
|
259
|
+
* Nor is the caller's `text` echoed back. It is the caller's own value on the direct
|
|
260
|
+
* path, and `interpretation.text` on the interpret path, so storing it here would be one
|
|
261
|
+
* fact in two places. `interpretation` and `verdict` are the originating packages' own
|
|
262
|
+
* types — import them from `@orkestrel/interpret` and `@orkestrel/reason`.
|
|
263
|
+
*
|
|
264
|
+
* `digest` identifies this OUTCOME — the brief, the questions, and the failures together — so
|
|
265
|
+
* two identical compiles share it and a refused compile has one just as a complete one does.
|
|
266
|
+
* It is not `Brief.hash`, which identifies the brief's content alone and exists only on an
|
|
267
|
+
* emitted brief. Key a cache of compile results by `digest`; key a registry of briefs by
|
|
268
|
+
* `hash`.
|
|
269
|
+
*/
|
|
270
|
+
export declare interface Briefing {
|
|
271
|
+
readonly interpretation?: Interpretation;
|
|
272
|
+
readonly brief?: Brief;
|
|
273
|
+
readonly questions: readonly Gap[];
|
|
274
|
+
readonly verdict?: LogicalResult;
|
|
275
|
+
readonly stages: readonly BriefStageRecord[];
|
|
276
|
+
readonly failures: readonly BriefStageFailure[];
|
|
277
|
+
readonly digest: string;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* One `compile()` input.
|
|
282
|
+
*
|
|
283
|
+
* @remarks
|
|
284
|
+
* THREE classes of input, not two. `text` selects the interpret stage. `interpretation`
|
|
285
|
+
* supplies that stage's result directly: with no `text` it drives `deriveTask`,
|
|
286
|
+
* `deriveGivens`, and `deriveGaps` without running the language pipeline at all, and with
|
|
287
|
+
* `text` present it is also the FALLBACK the draft uses when the interpret engine throws.
|
|
288
|
+
* Every remaining key is a caller-authored section merged OVER whatever the draft derived.
|
|
289
|
+
*
|
|
290
|
+
* Supplying both `text` and `interpretation` is therefore meaningful: the engine's result
|
|
291
|
+
* wins when it succeeds, and the supplied one carries the compile when it does not.
|
|
292
|
+
*/
|
|
293
|
+
export declare interface BriefInput {
|
|
294
|
+
readonly text?: string;
|
|
295
|
+
readonly interpretation?: Interpretation;
|
|
296
|
+
readonly task?: Task;
|
|
297
|
+
readonly authority?: readonly Reference[];
|
|
298
|
+
readonly manifest?: Manifest;
|
|
299
|
+
readonly outcomes?: readonly Outcome[];
|
|
300
|
+
readonly rules?: readonly string[];
|
|
301
|
+
readonly invariants?: readonly string[];
|
|
302
|
+
readonly givens?: readonly Given[];
|
|
303
|
+
readonly examples?: readonly Example[];
|
|
304
|
+
readonly assumptions?: readonly string[];
|
|
305
|
+
readonly citations?: readonly Citation[];
|
|
306
|
+
readonly gaps?: readonly Gap[];
|
|
307
|
+
readonly risks?: readonly Risk[];
|
|
308
|
+
readonly output?: Output;
|
|
309
|
+
readonly proofs?: readonly Proof[];
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* The self-owning, versioned and content-hashed brief registry.
|
|
314
|
+
*
|
|
315
|
+
* @remarks
|
|
316
|
+
* Record ids are MINTED from each brief's own content hash unless the caller names one,
|
|
317
|
+
* so registering unchanged content twice is a version no-op and two callers who compiled
|
|
318
|
+
* the same request land on the same id with no coordination. A call after `destroy()`
|
|
319
|
+
* throws `BriefError('DESTROYED', …)`.
|
|
320
|
+
*
|
|
321
|
+
* @example
|
|
322
|
+
* ```ts
|
|
323
|
+
* import { BriefManager, brief, task } from '@orkestrel/brief'
|
|
324
|
+
*
|
|
325
|
+
* const briefs = new BriefManager()
|
|
326
|
+
* const record = briefs.add(brief(task('document', 'writing', 'Write the brief guide.')))
|
|
327
|
+
* record.id === record.hash // true
|
|
328
|
+
* briefs.destroy()
|
|
329
|
+
* ```
|
|
330
|
+
*/
|
|
331
|
+
export declare class BriefManager implements BriefManagerInterface {
|
|
332
|
+
#private;
|
|
333
|
+
constructor(options?: BriefManagerOptions);
|
|
334
|
+
get emitter(): EmitterInterface<BriefManagerEventMap>;
|
|
335
|
+
get size(): number;
|
|
336
|
+
has(id: string): boolean;
|
|
337
|
+
brief(id: string): BriefRecord | undefined;
|
|
338
|
+
briefs(): readonly BriefRecord[];
|
|
339
|
+
add(source: Brief, options?: ManagerAddOptions): BriefRecord;
|
|
340
|
+
remove(ids: readonly string[]): boolean;
|
|
341
|
+
remove(id: string): boolean;
|
|
342
|
+
remove(): void;
|
|
343
|
+
destroy(): void;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** The `BriefManager`'s push observation surface. */
|
|
347
|
+
export declare type BriefManagerEventMap = {
|
|
348
|
+
add: readonly [id: string];
|
|
349
|
+
remove: readonly [id: string];
|
|
350
|
+
destroy: readonly [];
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* The brief registry contract.
|
|
355
|
+
*
|
|
356
|
+
* @remarks
|
|
357
|
+
* The array overload of `remove` is declared FIRST so an id list resolves to the batch
|
|
358
|
+
* form. `add` takes the fleet's own `ManagerAddOptions` from `@orkestrel/interpret`;
|
|
359
|
+
* omit its `id` and the record is keyed by the brief's own content hash, so re-adding
|
|
360
|
+
* unchanged content is a version no-op.
|
|
361
|
+
*/
|
|
362
|
+
export declare interface BriefManagerInterface {
|
|
363
|
+
readonly emitter: EmitterInterface<BriefManagerEventMap>;
|
|
364
|
+
readonly size: number;
|
|
365
|
+
has(id: string): boolean;
|
|
366
|
+
brief(id: string): BriefRecord | undefined;
|
|
367
|
+
briefs(): readonly BriefRecord[];
|
|
368
|
+
add(brief: Brief, options?: ManagerAddOptions): BriefRecord;
|
|
369
|
+
remove(ids: readonly string[]): boolean;
|
|
370
|
+
remove(id: string): boolean;
|
|
371
|
+
remove(): void;
|
|
372
|
+
destroy(): void;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/** Input to `createBriefManager`. */
|
|
376
|
+
export declare interface BriefManagerOptions {
|
|
377
|
+
readonly briefs?: readonly Brief[];
|
|
378
|
+
readonly on?: EmitterHooks<BriefManagerEventMap>;
|
|
379
|
+
readonly error?: EmitterErrorHandler;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/** A versioned, content-hashed `Brief` inside a {@link BriefManagerInterface}. */
|
|
383
|
+
export declare interface BriefRecord {
|
|
384
|
+
readonly id: string;
|
|
385
|
+
readonly brief: Brief;
|
|
386
|
+
readonly version: number;
|
|
387
|
+
readonly hash: string;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* The whole `Brief` shape, section shapes composed.
|
|
392
|
+
*
|
|
393
|
+
* @remarks
|
|
394
|
+
* `trace` and `hash` are optional because `pinBrief` fills them; an unpinned draft is
|
|
395
|
+
* on-contract without them.
|
|
396
|
+
*/
|
|
397
|
+
export declare const briefShape: ObjectShape<{
|
|
398
|
+
task: ObjectShape<{
|
|
399
|
+
operation: LiteralShape<readonly TaskOperation[]>;
|
|
400
|
+
domain: LiteralShape<readonly TaskDomain[]>;
|
|
401
|
+
statement: StringShape;
|
|
402
|
+
}, false>;
|
|
403
|
+
authority: ArrayShape<ObjectShape<{
|
|
404
|
+
path: StringShape;
|
|
405
|
+
note: StringShape;
|
|
406
|
+
}, false>>;
|
|
407
|
+
manifest: ObjectShape<{
|
|
408
|
+
read: ArrayShape<ObjectShape<{
|
|
409
|
+
path: StringShape;
|
|
410
|
+
note: StringShape;
|
|
411
|
+
}, false>>;
|
|
412
|
+
edit: ArrayShape<ObjectShape<{
|
|
413
|
+
path: StringShape;
|
|
414
|
+
note: StringShape;
|
|
415
|
+
}, false>>;
|
|
416
|
+
locked: ArrayShape<ObjectShape<{
|
|
417
|
+
path: StringShape;
|
|
418
|
+
note: StringShape;
|
|
419
|
+
}, false>>;
|
|
420
|
+
forbidden: ArrayShape<ObjectShape<{
|
|
421
|
+
path: StringShape;
|
|
422
|
+
note: StringShape;
|
|
423
|
+
}, false>>;
|
|
424
|
+
}, false>;
|
|
425
|
+
outcomes: ArrayShape<ObjectShape<{
|
|
426
|
+
rank: NumberShape;
|
|
427
|
+
text: StringShape;
|
|
428
|
+
required: BooleanShape;
|
|
429
|
+
}, false>>;
|
|
430
|
+
rules: ArrayShape<StringShape>;
|
|
431
|
+
invariants: ArrayShape<StringShape>;
|
|
432
|
+
givens: ArrayShape<ObjectShape<{
|
|
433
|
+
category: StringShape;
|
|
434
|
+
name: StringShape;
|
|
435
|
+
value: StringShape;
|
|
436
|
+
}, false>>;
|
|
437
|
+
examples: ArrayShape<ObjectShape<{
|
|
438
|
+
input: StringShape;
|
|
439
|
+
output: StringShape;
|
|
440
|
+
note: OptionalShape<StringShape>;
|
|
441
|
+
}, false>>;
|
|
442
|
+
assumptions: ArrayShape<StringShape>;
|
|
443
|
+
citations: ArrayShape<ObjectShape<{
|
|
444
|
+
name: StringShape;
|
|
445
|
+
url: StringShape;
|
|
446
|
+
note: StringShape;
|
|
447
|
+
}, false>>;
|
|
448
|
+
gaps: ArrayShape<ObjectShape<{
|
|
449
|
+
field: StringShape;
|
|
450
|
+
question: StringShape;
|
|
451
|
+
blocking: BooleanShape;
|
|
452
|
+
candidates: OptionalShape<ArrayShape<StringShape>>;
|
|
453
|
+
}, false>>;
|
|
454
|
+
risks: ArrayShape<ObjectShape<{
|
|
455
|
+
severity: LiteralShape<readonly RiskSeverity[]>;
|
|
456
|
+
text: StringShape;
|
|
457
|
+
mitigation: StringShape;
|
|
458
|
+
}, false>>;
|
|
459
|
+
output: ObjectShape<{
|
|
460
|
+
format: LiteralShape<readonly OutputFormat[]>;
|
|
461
|
+
sections: OptionalShape<ArrayShape<StringShape>>;
|
|
462
|
+
include: OptionalShape<ArrayShape<StringShape>>;
|
|
463
|
+
exclude: OptionalShape<ArrayShape<StringShape>>;
|
|
464
|
+
}, false>;
|
|
465
|
+
proofs: ArrayShape<ObjectShape<{
|
|
466
|
+
text: StringShape;
|
|
467
|
+
command: StringShape;
|
|
468
|
+
}, false>>;
|
|
469
|
+
trace: OptionalShape<StringShape>;
|
|
470
|
+
hash: OptionalShape<StringShape>;
|
|
471
|
+
}, false>;
|
|
472
|
+
|
|
473
|
+
/** The four fixed compilation phases, in pipeline order. */
|
|
474
|
+
export declare type BriefStage = 'interpret' | 'draft' | 'gate' | 'pin';
|
|
475
|
+
|
|
476
|
+
/** A visible marker for a phase that failed. */
|
|
477
|
+
export declare interface BriefStageFailure {
|
|
478
|
+
readonly stage: BriefStage;
|
|
479
|
+
readonly code: BriefErrorCode;
|
|
480
|
+
readonly message: string;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* One pipeline phase, discriminated by `stage`.
|
|
485
|
+
*
|
|
486
|
+
* @remarks
|
|
487
|
+
* Narrowing on `stage` types both payloads exactly, so a consumer reads a replay without
|
|
488
|
+
* a type assertion. A phase failed exactly when `error` is present.
|
|
489
|
+
*/
|
|
490
|
+
export declare type BriefStageRecord = InterpretStageRecord | DraftStageRecord | GateStageRecord | PinStageRecord;
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* The canonical text of exactly what a brief's hash describes.
|
|
494
|
+
*
|
|
495
|
+
* @remarks
|
|
496
|
+
* `trace` and `hash` are stripped, then interprets `canonicalize` renders the rest in a
|
|
497
|
+
* key-order-stable form. Two briefs with the same hash are the same brief only when this
|
|
498
|
+
* text matches — the digest is eight hex digits, so hash equality alone is not identity.
|
|
499
|
+
*
|
|
500
|
+
* @param source - The brief to render.
|
|
501
|
+
* @returns The canonical content text.
|
|
502
|
+
*
|
|
503
|
+
* @example
|
|
504
|
+
* ```ts
|
|
505
|
+
* import { brief, briefToContent, pinBrief, task } from '@orkestrel/brief'
|
|
506
|
+
*
|
|
507
|
+
* const draft = brief(task('plan', 'ops', 'Plan the release.'))
|
|
508
|
+
* briefToContent(draft) === briefToContent(pinBrief(draft)) // true — pinning adds no content
|
|
509
|
+
* ```
|
|
510
|
+
*/
|
|
511
|
+
export declare function briefToContent(source: Brief): string;
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Project a brief into a subagent `Dispatch`.
|
|
515
|
+
*
|
|
516
|
+
* @remarks
|
|
517
|
+
* `edit` is exactly `manifest.edit`, so two dispatches whose `edit` sets do not intersect
|
|
518
|
+
* can run concurrently under the same brief without conflict.
|
|
519
|
+
*
|
|
520
|
+
* `authority` is exactly `brief.authority` in rank order, and it is a SEPARATE axis from the
|
|
521
|
+
* four permission sets rather than a fifth partition — a ranked path normally also appears in
|
|
522
|
+
* `read` or `locked`, because the executor has to open what it obeys. It is projected as
|
|
523
|
+
* paths so a machine consumer never has to parse `prompt`, which is written for a model.
|
|
524
|
+
*
|
|
525
|
+
* @param source - The brief to project.
|
|
526
|
+
* @returns The dispatch — the rendered prompt, the ranked authority, and the four path sets.
|
|
527
|
+
*
|
|
528
|
+
* @example
|
|
529
|
+
* ```ts
|
|
530
|
+
* import { brief, briefToDispatch, manifest, reference, task } from '@orkestrel/brief'
|
|
531
|
+
*
|
|
532
|
+
* const draft = brief(task('migrate', 'code', 'Migrate the stores.'), {
|
|
533
|
+
* authority: [reference('AGENTS.md', 'project law')],
|
|
534
|
+
* manifest: manifest({ edit: [reference('src/core/stores/**', 'the legacy stores')] }),
|
|
535
|
+
* })
|
|
536
|
+
* briefToDispatch(draft).edit // ['src/core/stores/**']
|
|
537
|
+
* briefToDispatch(draft).authority // ['AGENTS.md']
|
|
538
|
+
* ```
|
|
539
|
+
*/
|
|
540
|
+
export declare function briefToDispatch(input: Brief): Dispatch;
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Project a brief into a `/goal` completion condition.
|
|
544
|
+
*
|
|
545
|
+
* @remarks
|
|
546
|
+
* The proofs' commands VERBATIM plus a turn cap — the goal never adds a condition the
|
|
547
|
+
* brief does not carry.
|
|
548
|
+
*
|
|
549
|
+
* @param source - The brief to render.
|
|
550
|
+
* @param turns - The turn cap; defaults to `DEFAULT_BRIEF_TURNS`.
|
|
551
|
+
* @returns The one-line completion condition.
|
|
552
|
+
*
|
|
553
|
+
* @example
|
|
554
|
+
* ```ts
|
|
555
|
+
* import { brief, briefToGoal, proof, task } from '@orkestrel/brief'
|
|
556
|
+
*
|
|
557
|
+
* briefToGoal(brief(task('test', 'code', 'Cover the gate.'), { proofs: [proof('x', 'npm test')] }))
|
|
558
|
+
* // 'Done when every proof passes: npm test exits 0. Cap: 16 turns.'
|
|
559
|
+
* ```
|
|
560
|
+
*/
|
|
561
|
+
export declare function briefToGoal(input: Brief, turns?: number): string;
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* The canonical structural digest of a brief's content.
|
|
565
|
+
*
|
|
566
|
+
* @remarks
|
|
567
|
+
* `trace` and `hash` are stripped before digesting, so the value is the identity of what
|
|
568
|
+
* the brief SAYS rather than of a particular pinning. Deterministic across runs — the
|
|
569
|
+
* same interprets `digestValue` the fleet uses everywhere else.
|
|
570
|
+
*
|
|
571
|
+
* @param source - The brief to digest.
|
|
572
|
+
* @returns An eight-hex-digit digest.
|
|
573
|
+
*
|
|
574
|
+
* @example
|
|
575
|
+
* ```ts
|
|
576
|
+
* import { brief, briefToHash, pinBrief, task } from '@orkestrel/brief'
|
|
577
|
+
*
|
|
578
|
+
* const draft = brief(task('plan', 'ops', 'Plan the release.'))
|
|
579
|
+
* briefToHash(draft) === briefToHash(pinBrief(draft)) // true — pinning does not move it
|
|
580
|
+
* ```
|
|
581
|
+
*/
|
|
582
|
+
export declare function briefToHash(source: Brief): string;
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Project a brief into the copy-ready agent prompt.
|
|
586
|
+
*
|
|
587
|
+
* @remarks
|
|
588
|
+
* Paths are REFERENCED, never inlined — the executor retrieves them. An empty section is
|
|
589
|
+
* omitted entirely, so the rendering carries no filler an executor must read past.
|
|
590
|
+
*
|
|
591
|
+
* @param source - The brief to render.
|
|
592
|
+
* @returns The markdown prompt.
|
|
593
|
+
*
|
|
594
|
+
* @example
|
|
595
|
+
* ```ts
|
|
596
|
+
* import { brief, briefToMarkdown, task } from '@orkestrel/brief'
|
|
597
|
+
*
|
|
598
|
+
* briefToMarkdown(brief(task('review', 'code', 'Review the gate rules.')))
|
|
599
|
+
* // '# Brief: Review the gate rules.\n\nreview · code\n\n## Output\n\n- format: markdown\n'
|
|
600
|
+
* ```
|
|
601
|
+
*/
|
|
602
|
+
export declare function briefToMarkdown(input: Brief): string;
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Project a brief into the reasons `Subject` of readiness measures the gate reads.
|
|
606
|
+
*
|
|
607
|
+
* @param source - The brief to measure.
|
|
608
|
+
* @returns A flat record of counts plus the task's two vocabulary values.
|
|
609
|
+
*
|
|
610
|
+
* @example
|
|
611
|
+
* ```ts
|
|
612
|
+
* import { brief, briefToSubject, proof, task } from '@orkestrel/brief'
|
|
613
|
+
*
|
|
614
|
+
* briefToSubject(brief(task('test', 'code', 'Cover the gate.'), { proofs: [proof('x', 'y')] }))
|
|
615
|
+
* // { operation: 'test', domain: 'code', sentences: 1, proofs: 1, … }
|
|
616
|
+
* ```
|
|
617
|
+
*/
|
|
618
|
+
export declare function briefToSubject(source: Brief): Subject;
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* The one-line census `pinBrief` stamps onto a brief.
|
|
622
|
+
*
|
|
623
|
+
* @remarks
|
|
624
|
+
* Extracted so it has ONE implementation. `pinBrief` derives it and `BriefManager` re-derives
|
|
625
|
+
* it to reconcile an inbound brief's own `trace` against its content — an inbound `trace` is
|
|
626
|
+
* shape-checked rather than verified, and it is the line `briefToMarkdown` prints at the top
|
|
627
|
+
* of the executor's prompt, so a stale one misdescribes the brief where it is most read.
|
|
628
|
+
*
|
|
629
|
+
* @param source - The brief to describe.
|
|
630
|
+
* @returns The census line: operation/domain, outcomes, blocking-over-total gaps, proofs.
|
|
631
|
+
*
|
|
632
|
+
* @example
|
|
633
|
+
* ```ts
|
|
634
|
+
* import { brief, briefToTrace, task } from '@orkestrel/brief'
|
|
635
|
+
*
|
|
636
|
+
* briefToTrace(brief(task('document', 'writing', 'Write the guide.')))
|
|
637
|
+
* // 'document/writing · outcomes:0 · gaps:0/0 · proofs:0'
|
|
638
|
+
* ```
|
|
639
|
+
*/
|
|
640
|
+
export declare function briefToTrace(source: Brief): string;
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* One external source — what it is called, where it lives, and why it is cited.
|
|
644
|
+
*
|
|
645
|
+
* @remarks
|
|
646
|
+
* List ORDER is the trust order; there is no per-entry weight.
|
|
647
|
+
*
|
|
648
|
+
* The off-repository twin of {@link Reference}: `note` says why the entry is listed, in both.
|
|
649
|
+
* It carries `name` where a reference does not because a path is already readable and a URL
|
|
650
|
+
* is not, so the name is the citation's readable half rather than a classifier in disguise.
|
|
651
|
+
*
|
|
652
|
+
* It carries no classifier for a different reason than a reference does. A reference has none
|
|
653
|
+
* because its container already fixes what it is; a citation has none because the closed
|
|
654
|
+
* vocabulary this once held — `docs`, `spec`, `api`, `standard` — was a document taxonomy no
|
|
655
|
+
* mechanism read, and its members were not disjoint, so a page documenting an API or a
|
|
656
|
+
* specification that is also a standard forced an arbitrary choice.
|
|
657
|
+
*
|
|
658
|
+
* `url` is validated as a single line and NOT as a URL, so `url: 'docs'` is on-contract. That
|
|
659
|
+
* is a limit rather than an oversight: constraining it needs a `pattern` on `citationShape`,
|
|
660
|
+
* and the shape DSL's seeded generator builds a random alphanumeric string and throws when it
|
|
661
|
+
* fails the pattern — so any pattern requiring a scheme's colon makes `createBriefContract()`
|
|
662
|
+
* ungeneratable for the whole brief. Constraining only the guard would leave the guard and the
|
|
663
|
+
* compiled shape disagreeing, which is the parity this package holds in lockstep. Four working
|
|
664
|
+
* mechanisms beat one stricter member.
|
|
665
|
+
*
|
|
666
|
+
* The cost lands on one migration: `citation` takes `(name, url, note)` where it once took
|
|
667
|
+
* `(name, role, url)` — three strings either way, so a stale call still compiles and still
|
|
668
|
+
* passes the guard, and only renders wrong. Nothing is published, so a version bump carries it.
|
|
669
|
+
*/
|
|
670
|
+
export declare interface Citation {
|
|
671
|
+
readonly name: string;
|
|
672
|
+
readonly url: string;
|
|
673
|
+
readonly note: string;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Build a `Citation`.
|
|
678
|
+
*
|
|
679
|
+
* @param name - The source's display name.
|
|
680
|
+
* @param url - Where the source lives.
|
|
681
|
+
* @param note - Why the source is cited.
|
|
682
|
+
* @returns A fresh `Citation`.
|
|
683
|
+
*
|
|
684
|
+
* @example
|
|
685
|
+
* ```ts
|
|
686
|
+
* import { citation } from '@orkestrel/brief'
|
|
687
|
+
*
|
|
688
|
+
* citation(
|
|
689
|
+
* 'MDN Constraint Validation',
|
|
690
|
+
* 'https://developer.mozilla.org/',
|
|
691
|
+
* 'the native validity behavior being adopted',
|
|
692
|
+
* )
|
|
693
|
+
* ```
|
|
694
|
+
*/
|
|
695
|
+
export declare function citation(name: string, url: string, note: string): Citation;
|
|
696
|
+
|
|
697
|
+
/** The `Citation` shape — a name, a locator, and why the source is cited. */
|
|
698
|
+
export declare const citationShape: ObjectShape<{
|
|
699
|
+
name: StringShape;
|
|
700
|
+
url: StringShape;
|
|
701
|
+
note: StringShape;
|
|
702
|
+
}, false>;
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Count the sentences a statement holds.
|
|
706
|
+
*
|
|
707
|
+
* @remarks
|
|
708
|
+
* A terminator run (`.`, `!`, `?`) followed by whitespace or the end of the text closes one
|
|
709
|
+
* sentence, and a trailing run with no terminator closes one more.
|
|
710
|
+
*
|
|
711
|
+
* LIMIT, stated because it decides a gate: an embedded abbreviation reads as a boundary, so
|
|
712
|
+
* `'Ask Dr. Smith'` and `'Compare React vs. Vue'` count TWO and the `single` rule refuses
|
|
713
|
+
* them. Rewrite the statement without the abbreviation — a brief's statement is one
|
|
714
|
+
* imperative sentence naming the object of the work, and it rarely needs one.
|
|
715
|
+
*
|
|
716
|
+
* This is inherent rather than unfinished. Separating `'Dr.'` from a real boundary needs a
|
|
717
|
+
* lexicon or a heuristic over capitalisation and word length, and a heuristic gets a
|
|
718
|
+
* different set of statements wrong — quietly, in the direction of letting a genuinely
|
|
719
|
+
* compound statement through, which is the failure this rule exists to prevent. `validateBrief`
|
|
720
|
+
* therefore reports the count and lets the author judge, rather than guessing at intent.
|
|
721
|
+
*
|
|
722
|
+
* @param statement - The statement to measure.
|
|
723
|
+
* @returns The sentence count; `0` for empty or whitespace-only text.
|
|
724
|
+
*
|
|
725
|
+
* @example
|
|
726
|
+
* ```ts
|
|
727
|
+
* import { countSentences } from '@orkestrel/brief'
|
|
728
|
+
*
|
|
729
|
+
* countSentences('Refactor useForm to native APIs.') // 1
|
|
730
|
+
* countSentences('Refactor useForm. Then update the tests') // 2 — the tail counts
|
|
731
|
+
* countSentences('Ask Dr. Smith') // 2 — an abbreviation reads as a boundary
|
|
732
|
+
* countSentences('') // 0
|
|
733
|
+
* ```
|
|
734
|
+
*/
|
|
735
|
+
export declare function countSentences(statement: string): number;
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Create a compilation orchestrator.
|
|
739
|
+
*
|
|
740
|
+
* @remarks
|
|
741
|
+
* With no engines supplied the compiler wires its own: a default `createInterpret()`
|
|
742
|
+
* (empty vocabularies, so `options.actions` / `options.domains` drive `deriveTask`) and a
|
|
743
|
+
* `createReason` carrying one `LogicalReasoner` for the gate. Pass your own to share
|
|
744
|
+
* instances or observe their emitters — the compiler destroys ONLY what it created.
|
|
745
|
+
*
|
|
746
|
+
* @param options - Engines to borrow, the two intent vocabularies, and emitter hooks.
|
|
747
|
+
* @returns A working {@link BriefCompilerInterface}.
|
|
748
|
+
*
|
|
749
|
+
* @example
|
|
750
|
+
* ```ts
|
|
751
|
+
* import { createBriefCompiler } from '@orkestrel/brief'
|
|
752
|
+
*
|
|
753
|
+
* const compiler = createBriefCompiler({ actions: { refactor: 'refactor' }, domains: { code: 'code' } })
|
|
754
|
+
* compiler.destroy()
|
|
755
|
+
* ```
|
|
756
|
+
*/
|
|
757
|
+
export declare function createBriefCompiler(options?: BriefCompilerOptions): BriefCompilerInterface;
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Compile `briefShape` into a guard, parser, JSON Schema, and seeded generator bundle.
|
|
761
|
+
*
|
|
762
|
+
* @remarks
|
|
763
|
+
* The schema is what a tool boundary needs — hand it to `schemaToParameters` — and
|
|
764
|
+
* `generate(seededRandom(n))` yields a reproducible on-contract brief for tests. This
|
|
765
|
+
* bundle and the hand-composed `isBrief` are two independent mechanisms over one
|
|
766
|
+
* vocabulary; `tests/src/core/shapers.test.ts` is what holds them in lockstep.
|
|
767
|
+
*
|
|
768
|
+
* @returns A `ContractInterface` over `Brief`.
|
|
769
|
+
*
|
|
770
|
+
* @example
|
|
771
|
+
* ```ts
|
|
772
|
+
* import { createBriefContract } from '@orkestrel/brief'
|
|
773
|
+
* import { schemaToParameters, seededRandom } from '@orkestrel/contract'
|
|
774
|
+
*
|
|
775
|
+
* const contract = createBriefContract()
|
|
776
|
+
* schemaToParameters(contract.schema) // the open tool-parameters record, no `as` anywhere
|
|
777
|
+
* contract.generate(seededRandom(42)) // a reproducible on-contract brief
|
|
778
|
+
* ```
|
|
779
|
+
*/
|
|
780
|
+
export declare function createBriefContract(): ContractInterface<Brief>;
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* Create a brief registry.
|
|
784
|
+
*
|
|
785
|
+
* @param options - An optional seed collection plus emitter hooks.
|
|
786
|
+
* @returns A working {@link BriefManagerInterface}.
|
|
787
|
+
*
|
|
788
|
+
* @example
|
|
789
|
+
* ```ts
|
|
790
|
+
* import { createBriefManager } from '@orkestrel/brief'
|
|
791
|
+
*
|
|
792
|
+
* const briefs = createBriefManager()
|
|
793
|
+
* briefs.size // 0
|
|
794
|
+
* briefs.destroy()
|
|
795
|
+
* ```
|
|
796
|
+
*/
|
|
797
|
+
export declare function createBriefManager(options?: BriefManagerOptions): BriefManagerInterface;
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* `16` — the default turn cap `briefToGoal` renders.
|
|
801
|
+
*
|
|
802
|
+
* @remarks
|
|
803
|
+
* Domain-qualified so the barrel stays collision-free as sibling modules add their own
|
|
804
|
+
* turn defaults.
|
|
805
|
+
*/
|
|
806
|
+
export declare const DEFAULT_BRIEF_TURNS = 16;
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* Derive `Gap[]` from an interprets `Ambiguity[]`.
|
|
810
|
+
*
|
|
811
|
+
* @remarks
|
|
812
|
+
* A REQUIRED ambiguity becomes a BLOCKING gap — the gate must fail closed on it. The
|
|
813
|
+
* rest stay open, to be answered with a recorded assumption. An array field path flattens
|
|
814
|
+
* through reasons `formatField`.
|
|
815
|
+
*
|
|
816
|
+
* @param ambiguities - The ambiguities an interpret pipeline surfaced.
|
|
817
|
+
* @returns One `Gap` per ambiguity, in surfacing order.
|
|
818
|
+
*
|
|
819
|
+
* @example
|
|
820
|
+
* ```ts
|
|
821
|
+
* import { deriveGaps } from '@orkestrel/brief'
|
|
822
|
+
*
|
|
823
|
+
* deriveGaps([{ field: 'output', question: 'Diff or files?', candidates: [], required: true }])
|
|
824
|
+
* // [{ field: 'output', question: 'Diff or files?', blocking: true }]
|
|
825
|
+
* ```
|
|
826
|
+
*/
|
|
827
|
+
export declare function deriveGaps(ambiguities: readonly Ambiguity[]): readonly Gap[];
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Derive `Given[]` from an interprets `Entity[]`.
|
|
831
|
+
*
|
|
832
|
+
* @remarks
|
|
833
|
+
* Every extracted entity becomes one `extracted` fact. A nameless entity is dropped; an
|
|
834
|
+
* object value renders through interprets `canonicalize`, so the text is key-order stable.
|
|
835
|
+
*
|
|
836
|
+
* @param entities - The entities an interpret pipeline extracted.
|
|
837
|
+
* @returns One `Given` per named entity, in extraction order.
|
|
838
|
+
*
|
|
839
|
+
* @example
|
|
840
|
+
* ```ts
|
|
841
|
+
* import { deriveGivens } from '@orkestrel/brief'
|
|
842
|
+
*
|
|
843
|
+
* deriveGivens([
|
|
844
|
+
* { name: 'value', value: 3, provenance: { category: 'extracted' }, confidence: 1 },
|
|
845
|
+
* ]) // [{ category: 'extracted', name: 'value', value: '3' }]
|
|
846
|
+
* ```
|
|
847
|
+
*/
|
|
848
|
+
export declare function deriveGivens(entities: readonly Entity[]): readonly Given[];
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* Derive one imperative statement from free text.
|
|
852
|
+
*
|
|
853
|
+
* @remarks
|
|
854
|
+
* Whitespace collapses, the first character uppercases, and a terminator is appended
|
|
855
|
+
* when the text carries none. Nothing else is invented.
|
|
856
|
+
*
|
|
857
|
+
* @param text - The raw request text.
|
|
858
|
+
* @returns The statement, or `''` for empty or whitespace-only text.
|
|
859
|
+
*
|
|
860
|
+
* @example
|
|
861
|
+
* ```ts
|
|
862
|
+
* import { deriveStatement } from '@orkestrel/brief'
|
|
863
|
+
*
|
|
864
|
+
* deriveStatement(' clean up useForm ') // 'Clean up useForm.'
|
|
865
|
+
* deriveStatement('') // ''
|
|
866
|
+
* ```
|
|
867
|
+
*/
|
|
868
|
+
export declare function deriveStatement(text: string): string;
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* Derive a `Task` from an interprets `Intent` through the caller's vocabularies.
|
|
872
|
+
*
|
|
873
|
+
* @remarks
|
|
874
|
+
* The vocabularies are the CALLER's policy: this maps and never guesses. An action or
|
|
875
|
+
* domain the caller did not map — or mapped to an off-vocabulary value — yields
|
|
876
|
+
* `undefined` rather than an invented task. Inherited keys never resolve.
|
|
877
|
+
*
|
|
878
|
+
* @param intent - The classified intent from an interpret pipeline.
|
|
879
|
+
* @param text - The text the statement derives from.
|
|
880
|
+
* @param actions - Maps an intent action onto a closed `TaskOperation`.
|
|
881
|
+
* @param domains - Maps an intent domain onto a closed `TaskDomain`.
|
|
882
|
+
* @returns The derived `Task`, or `undefined` when either side is unmapped.
|
|
883
|
+
*
|
|
884
|
+
* @example
|
|
885
|
+
* ```ts
|
|
886
|
+
* import { deriveTask } from '@orkestrel/brief'
|
|
887
|
+
*
|
|
888
|
+
* const intent = { action: 'migrate', domain: 'code', confidence: 1 }
|
|
889
|
+
* deriveTask(intent, 'migrate the stores', { migrate: 'migrate' }, { code: 'code' })
|
|
890
|
+
* // { operation: 'migrate', domain: 'code', statement: 'Migrate the stores.' }
|
|
891
|
+
* deriveTask(intent, 'migrate the stores', {}, { code: 'code' }) // undefined
|
|
892
|
+
* ```
|
|
893
|
+
*/
|
|
894
|
+
export declare function deriveTask(intent: Intent, text: string, actions: Readonly<Record<string, TaskOperation>>, domains: Readonly<Record<string, TaskDomain>>): Task | undefined;
|
|
895
|
+
|
|
896
|
+
/**
|
|
897
|
+
* The subagent projection of a brief.
|
|
898
|
+
*
|
|
899
|
+
* @remarks
|
|
900
|
+
* Two orthogonal axes, not five partitions. PERMISSION is `read`, `edit`, `locked`, and
|
|
901
|
+
* `forbidden`, with `edit` the owned set two concurrent dispatches must not intersect on and
|
|
902
|
+
* `locked` and `forbidden` do-not-touch. PRECEDENCE is `authority`, in ranked order, index 0
|
|
903
|
+
* winning every conflict.
|
|
904
|
+
*
|
|
905
|
+
* `authority` therefore OVERLAPS the permission arrays by design: a ranked path ALWAYS also
|
|
906
|
+
* sits in `read`, `edit`, or `locked`, because the executor has to open what it obeys, and
|
|
907
|
+
* the `granted` gate rule refuses a brief where it does not. Read the four to decide what may
|
|
908
|
+
* be touched and `authority` to decide what wins. Never union all five — that was already
|
|
909
|
+
* wrong before `authority` existed, because `forbidden` is an exclusion rather than a grant.
|
|
910
|
+
* `authority` is a path list rather than a rendered section because a machine consumer must
|
|
911
|
+
* reach mandatory authority without parsing `prompt`, which is written for a model.
|
|
912
|
+
*
|
|
913
|
+
* The four permission arrays are mutually disjoint in a GATED brief — `findManifestOverlaps`
|
|
914
|
+
* measures it and the `disjoint` rule refuses on it. `briefToDispatch` is a pure projection
|
|
915
|
+
* and runs no gate, so projecting an unvetted draft can produce arrays that intersect. Gate
|
|
916
|
+
* before you dispatch, or treat disjointness as unproven.
|
|
917
|
+
*/
|
|
918
|
+
export declare interface Dispatch {
|
|
919
|
+
readonly prompt: string;
|
|
920
|
+
readonly authority: readonly string[];
|
|
921
|
+
readonly read: readonly string[];
|
|
922
|
+
readonly edit: readonly string[];
|
|
923
|
+
readonly locked: readonly string[];
|
|
924
|
+
readonly forbidden: readonly string[];
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/** The `draft` phase snapshot — the caller's input in, an unpinned `Brief` out. */
|
|
928
|
+
export declare interface DraftStageRecord {
|
|
929
|
+
readonly stage: 'draft';
|
|
930
|
+
readonly input: BriefInput;
|
|
931
|
+
readonly output?: Brief;
|
|
932
|
+
readonly error?: string;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/**
|
|
936
|
+
* Render a value thrown by a stage into a message.
|
|
937
|
+
*
|
|
938
|
+
* @remarks
|
|
939
|
+
* TOTAL: it never throws, for any input. That is load-bearing rather than tidy, because this
|
|
940
|
+
* is the containment code itself — `compile` calls it inside the `catch` that turns a thrown
|
|
941
|
+
* stage into a recorded `BriefStageFailure`. A throw here escapes `compile` uncontained and
|
|
942
|
+
* falsifies the package's central promise that a failing stage yields an incomplete
|
|
943
|
+
* `Briefing` rather than an exception.
|
|
944
|
+
*
|
|
945
|
+
* Three real inputs used to throw: an `Error` subclass whose `message` getter throws, a value
|
|
946
|
+
* whose string conversion throws, and a null-prototype object, which has no inherited
|
|
947
|
+
* conversion for String() to reach. Each is wrapped, and an unreadable value degrades to its
|
|
948
|
+
* type rather than propagating.
|
|
949
|
+
*
|
|
950
|
+
* @param error - The caught value, of any shape.
|
|
951
|
+
* @returns The `Error` message when there is one, otherwise the value stringified; a fixed
|
|
952
|
+
* description when the value cannot be read at all.
|
|
953
|
+
*
|
|
954
|
+
* @example
|
|
955
|
+
* ```ts
|
|
956
|
+
* import { errorToMessage } from '@orkestrel/brief'
|
|
957
|
+
*
|
|
958
|
+
* errorToMessage(new Error('boom')) // 'boom'
|
|
959
|
+
* errorToMessage('boom') // 'boom'
|
|
960
|
+
* errorToMessage(Object.create(null)) // 'an unreadable object was thrown'
|
|
961
|
+
* ```
|
|
962
|
+
*/
|
|
963
|
+
export declare function errorToMessage(error: unknown): string;
|
|
964
|
+
|
|
965
|
+
/** One input to output exemplar — the highest-leverage ambiguity remover. */
|
|
966
|
+
export declare interface Example {
|
|
967
|
+
readonly input: string;
|
|
968
|
+
readonly output: string;
|
|
969
|
+
readonly note?: string;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* Build an `Example`.
|
|
974
|
+
*
|
|
975
|
+
* @param input - The exemplar input.
|
|
976
|
+
* @param result - The expected output for that input.
|
|
977
|
+
* @param note - Optional detail; the key is OMITTED when absent.
|
|
978
|
+
* @returns A fresh `Example`.
|
|
979
|
+
*
|
|
980
|
+
* @example
|
|
981
|
+
* ```ts
|
|
982
|
+
* import { example } from '@orkestrel/brief'
|
|
983
|
+
*
|
|
984
|
+
* example('<input required>', 'validity read from el.validity')
|
|
985
|
+
* ```
|
|
986
|
+
*/
|
|
987
|
+
export declare function example(input: string, result: string, note?: string): Example;
|
|
988
|
+
|
|
989
|
+
/** The `Example` shape — one input to output exemplar. */
|
|
990
|
+
export declare const exampleShape: ObjectShape<{
|
|
991
|
+
input: StringShape;
|
|
992
|
+
output: StringShape;
|
|
993
|
+
note: OptionalShape<StringShape>;
|
|
994
|
+
}, false>;
|
|
995
|
+
|
|
996
|
+
/**
|
|
997
|
+
* Render one exemplar as markdown lines.
|
|
998
|
+
*
|
|
999
|
+
* @remarks
|
|
1000
|
+
* An `Example`'s two sides are the only brief members permitted to span lines, so a
|
|
1001
|
+
* single-line pair renders as one row and a multi-line pair renders as a fenced block.
|
|
1002
|
+
* Fencing is what stops the one permissive field from forging a heading.
|
|
1003
|
+
*
|
|
1004
|
+
* @param entry - The exemplar to render.
|
|
1005
|
+
* @returns The markdown lines, without a trailing blank.
|
|
1006
|
+
*
|
|
1007
|
+
* @example
|
|
1008
|
+
* ```ts
|
|
1009
|
+
* import { example, exampleToLines } from '@orkestrel/brief'
|
|
1010
|
+
*
|
|
1011
|
+
* exampleToLines(example('<input required>', 'el.validity')) // ['- ` <input required> ` → ` el.validity `']
|
|
1012
|
+
* ```
|
|
1013
|
+
*/
|
|
1014
|
+
export declare function exampleToLines(entry: Example): readonly string[];
|
|
1015
|
+
|
|
1016
|
+
/**
|
|
1017
|
+
* The gaps that block emission.
|
|
1018
|
+
*
|
|
1019
|
+
* @param source - The brief to inspect.
|
|
1020
|
+
* @returns Every gap carrying `blocking: true`, in declaration order.
|
|
1021
|
+
*
|
|
1022
|
+
* @example
|
|
1023
|
+
* ```ts
|
|
1024
|
+
* import { brief, findBlockingGaps, gap, task } from '@orkestrel/brief'
|
|
1025
|
+
*
|
|
1026
|
+
* const draft = brief(task('plan', 'ops', 'Plan the release.'), {
|
|
1027
|
+
* gaps: [gap('output', 'Diff or files?', { blocking: true })],
|
|
1028
|
+
* })
|
|
1029
|
+
* findBlockingGaps(draft).length // 1
|
|
1030
|
+
* ```
|
|
1031
|
+
*/
|
|
1032
|
+
export declare function findBlockingGaps(source: Brief): readonly Gap[];
|
|
1033
|
+
|
|
1034
|
+
/**
|
|
1035
|
+
* The paths appearing in more than one manifest partition.
|
|
1036
|
+
*
|
|
1037
|
+
* @remarks
|
|
1038
|
+
* Duplicates WITHIN one partition are not an overlap; the four partitions must be
|
|
1039
|
+
* mutually disjoint, which is what `validateBrief` errors on.
|
|
1040
|
+
*
|
|
1041
|
+
* Paths are compared as EXACT strings. A glob is never expanded, so `edit: 'app/file.ts'`
|
|
1042
|
+
* and `forbidden: 'app/**'` are not reported as an overlap even though a walker would place
|
|
1043
|
+
* one inside the other. Disjointness here is a property of the written paths.
|
|
1044
|
+
*
|
|
1045
|
+
* @param source - The brief to inspect.
|
|
1046
|
+
* @returns Each overlapping path once, in first-seen partition order.
|
|
1047
|
+
*
|
|
1048
|
+
* @example
|
|
1049
|
+
* ```ts
|
|
1050
|
+
* import { brief, findManifestOverlaps, manifest, reference, task } from '@orkestrel/brief'
|
|
1051
|
+
*
|
|
1052
|
+
* const draft = brief(task('debug', 'code', 'Fix the leak.'), {
|
|
1053
|
+
* manifest: manifest({
|
|
1054
|
+
* edit: [reference('src/core/BriefCompiler.ts', 'the leaking pipeline')],
|
|
1055
|
+
* locked: [reference('src/core/BriefCompiler.ts', 'the published contract')],
|
|
1056
|
+
* }),
|
|
1057
|
+
* })
|
|
1058
|
+
* findManifestOverlaps(draft) // ['src/core/BriefCompiler.ts']
|
|
1059
|
+
* ```
|
|
1060
|
+
*/
|
|
1061
|
+
export declare function findManifestOverlaps(source: Brief): readonly string[];
|
|
1062
|
+
|
|
1063
|
+
/**
|
|
1064
|
+
* The authority paths the manifest never grants access to.
|
|
1065
|
+
*
|
|
1066
|
+
* @remarks
|
|
1067
|
+
* An authority the executor cannot open is an instruction it cannot follow, so every ranked
|
|
1068
|
+
* path must appear in `read`, `edit`, or `locked`. Those three are the grants: `locked` is a
|
|
1069
|
+
* grant, because read-only is exactly what obeying a file requires.
|
|
1070
|
+
*
|
|
1071
|
+
* This subsumes the narrower question of an authority sitting in `forbidden`. The four
|
|
1072
|
+
* partitions are disjoint — `findManifestOverlaps` and the `disjoint` rule enforce it — so a
|
|
1073
|
+
* forbidden path is in none of the three grants and is reported here. An authority named in
|
|
1074
|
+
* NO partition at all is reported for the same reason, and that is the case a forbidden-only
|
|
1075
|
+
* check misses entirely: the brief simply never says the executor may open what it must obey.
|
|
1076
|
+
*
|
|
1077
|
+
* Paths are compared as EXACT strings, matching `findManifestOverlaps`. A glob is never
|
|
1078
|
+
* expanded, so `read: 'guides/**'` does not grant `authority: 'guides/brief.md'`. State a
|
|
1079
|
+
* grant as the same literal path the authority carries.
|
|
1080
|
+
*
|
|
1081
|
+
* @param source - The brief to inspect.
|
|
1082
|
+
* @returns Each ungranted authority path once, in authority order; empty when all are granted.
|
|
1083
|
+
*
|
|
1084
|
+
* @example
|
|
1085
|
+
* ```ts
|
|
1086
|
+
* import { brief, findUngrantedAuthority, manifest, reference, task } from '@orkestrel/brief'
|
|
1087
|
+
*
|
|
1088
|
+
* const draft = brief(task('debug', 'code', 'Fix the leak.'), {
|
|
1089
|
+
* authority: [reference('AGENTS.md', 'project law')],
|
|
1090
|
+
* manifest: manifest(),
|
|
1091
|
+
* })
|
|
1092
|
+
* findUngrantedAuthority(draft) // ['AGENTS.md'] — ranked, but no partition opens it
|
|
1093
|
+
* ```
|
|
1094
|
+
*/
|
|
1095
|
+
export declare function findUngrantedAuthority(source: Brief): readonly string[];
|
|
1096
|
+
|
|
1097
|
+
/**
|
|
1098
|
+
* The readiness rules a brief fails, computed directly from its own measures.
|
|
1099
|
+
*
|
|
1100
|
+
* @remarks
|
|
1101
|
+
* The gate's decision, in code. `gateDefinition()` states the same six rules as data for a
|
|
1102
|
+
* reasoner to narrate, and a narration is not a decision: `BriefCompilerOptions.reason` lets a
|
|
1103
|
+
* caller supply the engine, and an engine that answers "met" to everything would otherwise
|
|
1104
|
+
* emit a brief with no proofs. `compile` refuses on THIS and keeps the verdict for its
|
|
1105
|
+
* trace, so a supplied engine can add detail and never remove a refusal.
|
|
1106
|
+
*
|
|
1107
|
+
* The two must agree. `tests/src/core/helpers.test.ts` drives both over one value set, which
|
|
1108
|
+
* is what stops the data and the code from drifting apart.
|
|
1109
|
+
*
|
|
1110
|
+
* @param source - The brief to measure.
|
|
1111
|
+
* @returns The unmet rule ids, in gate order; empty when the brief is ready.
|
|
1112
|
+
*
|
|
1113
|
+
* @example
|
|
1114
|
+
* ```ts
|
|
1115
|
+
* import { brief, findUnmetRules, outcome, proof, task } from '@orkestrel/brief'
|
|
1116
|
+
*
|
|
1117
|
+
* findUnmetRules(brief(task('plan', 'ops', 'Plan the release.'))) // ['aimed', 'proven']
|
|
1118
|
+
* findUnmetRules(
|
|
1119
|
+
* brief(task('plan', 'ops', 'Plan the release.'), {
|
|
1120
|
+
* outcomes: [outcome(1, 'shipped')],
|
|
1121
|
+
* proofs: [proof('x', 'npm test')],
|
|
1122
|
+
* }),
|
|
1123
|
+
* ) // []
|
|
1124
|
+
* ```
|
|
1125
|
+
*/
|
|
1126
|
+
export declare function findUnmetRules(source: Brief): readonly string[];
|
|
1127
|
+
|
|
1128
|
+
/**
|
|
1129
|
+
* The open gaps with no assumption to stand on.
|
|
1130
|
+
*
|
|
1131
|
+
* @remarks
|
|
1132
|
+
* The discipline is exactly one recorded assumption per open gap, so the open gaps past
|
|
1133
|
+
* the assumption count are the unpaired ones. A blocking gap is never unpaired — it is
|
|
1134
|
+
* a question, not something to assume around.
|
|
1135
|
+
*
|
|
1136
|
+
* @param source - The brief to inspect.
|
|
1137
|
+
* @returns The surplus open gaps, in declaration order.
|
|
1138
|
+
*
|
|
1139
|
+
* @example
|
|
1140
|
+
* ```ts
|
|
1141
|
+
* import { brief, findUnpairedGaps, gap, task } from '@orkestrel/brief'
|
|
1142
|
+
*
|
|
1143
|
+
* const draft = brief(task('plan', 'ops', 'Plan the release.'), {
|
|
1144
|
+
* gaps: [gap('rules', 'Keep the wording?'), gap('output', 'Diff or files?')],
|
|
1145
|
+
* assumptions: ['Wording is preserved.'],
|
|
1146
|
+
* })
|
|
1147
|
+
* findUnpairedGaps(draft).length // 1
|
|
1148
|
+
* ```
|
|
1149
|
+
*/
|
|
1150
|
+
export declare function findUnpairedGaps(source: Brief): readonly Gap[];
|
|
1151
|
+
|
|
1152
|
+
/**
|
|
1153
|
+
* Freeze one branch of a value graph, skipping what the visited set already holds.
|
|
1154
|
+
*
|
|
1155
|
+
* @param value - The branch to freeze.
|
|
1156
|
+
* @param seen - The objects already frozen on this walk; what makes a cycle terminate.
|
|
1157
|
+
* @returns The same branch, now frozen.
|
|
1158
|
+
*
|
|
1159
|
+
* @example
|
|
1160
|
+
* ```ts
|
|
1161
|
+
* import { freezeBranch } from '@orkestrel/brief'
|
|
1162
|
+
*
|
|
1163
|
+
* freezeBranch({ a: [1] }, new WeakSet()) // frozen, one level of nesting included
|
|
1164
|
+
* ```
|
|
1165
|
+
*/
|
|
1166
|
+
export declare function freezeBranch<T>(value: T, seen: WeakSet<object>): T;
|
|
1167
|
+
|
|
1168
|
+
/**
|
|
1169
|
+
* Freeze a value and everything reachable from it.
|
|
1170
|
+
*
|
|
1171
|
+
* @remarks
|
|
1172
|
+
* `Object.freeze` is SHALLOW, so freezing a record leaves every nested array and object
|
|
1173
|
+
* writable. A `Briefing` is documented as a replayable record, and a shallow freeze let a
|
|
1174
|
+
* consumer rewrite the recorded stage input after the digest describing it was already
|
|
1175
|
+
* sealed — the replay and its hash could disagree.
|
|
1176
|
+
*
|
|
1177
|
+
* Cycles terminate: `structuredClone` preserves them, so a naive walk would not return.
|
|
1178
|
+
* Delegates each branch to `freezeBranch` with the shared visited set.
|
|
1179
|
+
*
|
|
1180
|
+
* Reaches PLAIN objects and arrays, which is the whole of a `Brief` — it is JSON-serializable
|
|
1181
|
+
* by contract. A `Map`, `Set`, or typed array is frozen as an object and its CONTENTS are left
|
|
1182
|
+
* writable, and `Object.isFrozen` reports `true` for it either way. Nothing this package
|
|
1183
|
+
* produces contains one; a caller freezing their own value should know the limit.
|
|
1184
|
+
*
|
|
1185
|
+
* @param value - The value to freeze in place; returned for convenience.
|
|
1186
|
+
* @returns The same value, now deeply frozen.
|
|
1187
|
+
*
|
|
1188
|
+
* @example
|
|
1189
|
+
* ```ts
|
|
1190
|
+
* import { freezeDeep } from '@orkestrel/brief'
|
|
1191
|
+
*
|
|
1192
|
+
* const owned = freezeDeep({ outcomes: [{ rank: 1 }] })
|
|
1193
|
+
* Object.isFrozen(owned.outcomes) // true — the nested array too
|
|
1194
|
+
* ```
|
|
1195
|
+
*/
|
|
1196
|
+
export declare function freezeDeep<T>(value: T): T;
|
|
1197
|
+
|
|
1198
|
+
/**
|
|
1199
|
+
* One unknown the brief has not resolved.
|
|
1200
|
+
*
|
|
1201
|
+
* @remarks
|
|
1202
|
+
* `blocking: true` means no safe default exists and the gate must fail closed. An
|
|
1203
|
+
* open gap proceeds on a narrow recorded assumption instead.
|
|
1204
|
+
*/
|
|
1205
|
+
export declare interface Gap {
|
|
1206
|
+
readonly field: string;
|
|
1207
|
+
readonly question: string;
|
|
1208
|
+
readonly blocking: boolean;
|
|
1209
|
+
readonly candidates?: readonly string[];
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
/**
|
|
1213
|
+
* Build a `Gap`.
|
|
1214
|
+
*
|
|
1215
|
+
* @param field - The brief section the unknown belongs to.
|
|
1216
|
+
* @param question - The question that would close it.
|
|
1217
|
+
* @param overrides - Optional `blocking` (defaults `false`) and `candidates`; an absent
|
|
1218
|
+
* `candidates` key is OMITTED entirely.
|
|
1219
|
+
* @returns A fresh `Gap`.
|
|
1220
|
+
*
|
|
1221
|
+
* @example
|
|
1222
|
+
* ```ts
|
|
1223
|
+
* import { gap } from '@orkestrel/brief'
|
|
1224
|
+
*
|
|
1225
|
+
* gap('rules', 'Should validation message wording change?') // blocking: false
|
|
1226
|
+
* gap('output', 'Diff or full files?', { blocking: true, candidates: ['diff', 'code'] })
|
|
1227
|
+
* ```
|
|
1228
|
+
*/
|
|
1229
|
+
export declare function gap(field: string, question: string, overrides?: Partial<Omit<Gap, 'field' | 'question'>>): Gap;
|
|
1230
|
+
|
|
1231
|
+
/** The `Gap` shape — an unknown, whether it blocks, and the candidates that would close it. */
|
|
1232
|
+
export declare const gapShape: ObjectShape<{
|
|
1233
|
+
field: StringShape;
|
|
1234
|
+
question: StringShape;
|
|
1235
|
+
blocking: BooleanShape;
|
|
1236
|
+
candidates: OptionalShape<ArrayShape<StringShape>>;
|
|
1237
|
+
}, false>;
|
|
1238
|
+
|
|
1239
|
+
/** `'gate'` — the id of the `gateDefinition()` logical definition. */
|
|
1240
|
+
export declare const GATE_ID = "gate";
|
|
1241
|
+
|
|
1242
|
+
/**
|
|
1243
|
+
* Build the fail-closed readiness gate as a reasons `LogicalDefinition`.
|
|
1244
|
+
*
|
|
1245
|
+
* @remarks
|
|
1246
|
+
* Six readiness rules each derive one named fact from `briefToSubject`'s measures, and a
|
|
1247
|
+
* final `ready` rule conjoins all six. Forward chaining reports the LAST rule's
|
|
1248
|
+
* conclusion, so `LogicalResult.conclusion` is exactly `ready`.
|
|
1249
|
+
*
|
|
1250
|
+
* The gate takes NO parameters, and that is deliberate rather than unfinished. The
|
|
1251
|
+
* reasoner overlays every derived fact into one flat namespace, so a caller rule named
|
|
1252
|
+
* for a readiness fact overwrites it and `ready` then conjoins a fact no base rule
|
|
1253
|
+
* proved — a refusal silently becomes a pass. Readiness is this package's contract, not
|
|
1254
|
+
* a caller setting. A caller who needs different readiness composes their own
|
|
1255
|
+
* `LogicalDefinition` over `briefToSubject` and evaluates it on their own reasoner; both
|
|
1256
|
+
* are exported for exactly that, and neither can reach this definition.
|
|
1257
|
+
*
|
|
1258
|
+
* @returns A fresh `LogicalDefinition` with id `GATE_ID`.
|
|
1259
|
+
*
|
|
1260
|
+
* @example
|
|
1261
|
+
* ```ts
|
|
1262
|
+
* import { briefToSubject, gateDefinition } from '@orkestrel/brief'
|
|
1263
|
+
* import { createLogicalReasoner, createReason } from '@orkestrel/reason'
|
|
1264
|
+
*
|
|
1265
|
+
* const reason = createReason({ reasoners: [createLogicalReasoner()] })
|
|
1266
|
+
* const verdict = reason.reason(briefToSubject(pinned), gateDefinition())
|
|
1267
|
+
* reason.destroy()
|
|
1268
|
+
* ```
|
|
1269
|
+
*/
|
|
1270
|
+
export declare function gateDefinition(): LogicalDefinition;
|
|
1271
|
+
|
|
1272
|
+
/** The `gate` phase snapshot — the readiness `Subject` in, the reasoner's verdict out. */
|
|
1273
|
+
export declare interface GateStageRecord {
|
|
1274
|
+
readonly stage: 'gate';
|
|
1275
|
+
readonly input: Subject;
|
|
1276
|
+
readonly output?: LogicalResult;
|
|
1277
|
+
readonly error?: string;
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
/** One context fact handed to the executor — a convention, a version, a constraint value. */
|
|
1281
|
+
export declare interface Given {
|
|
1282
|
+
readonly category: string;
|
|
1283
|
+
readonly name: string;
|
|
1284
|
+
readonly value: string;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
/**
|
|
1288
|
+
* Build a `Given`.
|
|
1289
|
+
*
|
|
1290
|
+
* @param category - The kind of fact — a convention, a version, a constraint.
|
|
1291
|
+
* @param name - The fact's name.
|
|
1292
|
+
* @param value - The fact's value, already rendered as text.
|
|
1293
|
+
* @returns A fresh `Given`.
|
|
1294
|
+
*
|
|
1295
|
+
* @example
|
|
1296
|
+
* ```ts
|
|
1297
|
+
* import { given } from '@orkestrel/brief'
|
|
1298
|
+
*
|
|
1299
|
+
* given('convention', 'indentation', 'tabs')
|
|
1300
|
+
* ```
|
|
1301
|
+
*/
|
|
1302
|
+
export declare function given(category: string, name: string, value: string): Given;
|
|
1303
|
+
|
|
1304
|
+
/** The `Given` shape — one categorized context fact. */
|
|
1305
|
+
export declare const givenShape: ObjectShape<{
|
|
1306
|
+
category: StringShape;
|
|
1307
|
+
name: StringShape;
|
|
1308
|
+
value: StringShape;
|
|
1309
|
+
}, false>;
|
|
1310
|
+
|
|
1311
|
+
/**
|
|
1312
|
+
* The `interpret` phase snapshot — raw text in, an `Interpretation` out.
|
|
1313
|
+
*
|
|
1314
|
+
* @remarks
|
|
1315
|
+
* `output` is absent exactly when `error` is present, which is what makes the phase
|
|
1316
|
+
* failure derivable rather than stored twice.
|
|
1317
|
+
*/
|
|
1318
|
+
export declare interface InterpretStageRecord {
|
|
1319
|
+
readonly stage: 'interpret';
|
|
1320
|
+
readonly input: string;
|
|
1321
|
+
readonly output?: Interpretation;
|
|
1322
|
+
readonly error?: string;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
/**
|
|
1326
|
+
* `true` when the value satisfies the whole exact-record `Brief` contract.
|
|
1327
|
+
*
|
|
1328
|
+
* @remarks
|
|
1329
|
+
* Every section must be present; an extra key fails. `trace` and `hash` are the only
|
|
1330
|
+
* optional members, because `pinBrief` rather than the author fills them.
|
|
1331
|
+
*/
|
|
1332
|
+
export declare const isBrief: Guard<Brief>;
|
|
1333
|
+
|
|
1334
|
+
/**
|
|
1335
|
+
* Narrow a caught value to a {@link BriefError}.
|
|
1336
|
+
*
|
|
1337
|
+
* @param value - The caught value to inspect.
|
|
1338
|
+
* @returns `true` when `value` is a `BriefError`.
|
|
1339
|
+
*
|
|
1340
|
+
* @example
|
|
1341
|
+
* ```ts
|
|
1342
|
+
* import { BriefError, isBriefError } from '@orkestrel/brief'
|
|
1343
|
+
*
|
|
1344
|
+
* try {
|
|
1345
|
+
* throw new BriefError('DESTROYED', 'BriefCompiler has been destroyed')
|
|
1346
|
+
* } catch (error) {
|
|
1347
|
+
* if (isBriefError(error)) error.code // 'DESTROYED'
|
|
1348
|
+
* }
|
|
1349
|
+
* ```
|
|
1350
|
+
*/
|
|
1351
|
+
export declare function isBriefError(value: unknown): value is BriefError;
|
|
1352
|
+
|
|
1353
|
+
/** `true` when the value is a well-formed `Citation` — all three members single-line. */
|
|
1354
|
+
export declare const isCitation: Guard<Citation>;
|
|
1355
|
+
|
|
1356
|
+
/**
|
|
1357
|
+
* `true` when the value is a well-formed `Example`.
|
|
1358
|
+
*
|
|
1359
|
+
* @remarks
|
|
1360
|
+
* An exemplar's two sides are the ONLY members a brief lets span lines, because they
|
|
1361
|
+
* carry code. `briefToMarkdown` fences them rather than rendering them as a row.
|
|
1362
|
+
*/
|
|
1363
|
+
export declare const isExample: Guard<Example>;
|
|
1364
|
+
|
|
1365
|
+
/** `true` when the value is a well-formed `Gap`. */
|
|
1366
|
+
export declare const isGap: Guard<Gap>;
|
|
1367
|
+
|
|
1368
|
+
/** `true` when the value is a well-formed `Given` — `value` may be empty but stays one line. */
|
|
1369
|
+
export declare const isGiven: Guard<Given>;
|
|
1370
|
+
|
|
1371
|
+
/** `true` when the value is a non-empty string holding no line terminator. */
|
|
1372
|
+
export declare const isLine: Guard<string>;
|
|
1373
|
+
|
|
1374
|
+
/**
|
|
1375
|
+
* `true` when the value is a well-formed reasons `LogicalResult`.
|
|
1376
|
+
*
|
|
1377
|
+
* @remarks
|
|
1378
|
+
* The gate's reasoner is supplied by the caller through `BriefCompilerOptions.reason`, so its
|
|
1379
|
+
* return value is FOREIGN data no matter how well-typed the interface is. `BriefCompiler`
|
|
1380
|
+
* dereferences `reasoning`, `conclusion`, and `rules`; checking one field left a malformed
|
|
1381
|
+
* result to throw a raw `TypeError` out of `compile`, from the very code that contains stage
|
|
1382
|
+
* failures. Total: returns `false` for `undefined`, `null`, and every off-shape value.
|
|
1383
|
+
*
|
|
1384
|
+
* Checks the WHOLE published shape rather than only the three members read today, because a
|
|
1385
|
+
* guard that narrows to `LogicalResult` while ignoring four of its members is unsound.
|
|
1386
|
+
*
|
|
1387
|
+
* OPEN on unknown keys, deliberately. The exact-record combinator this file uses elsewhere
|
|
1388
|
+
* refuses a value a FOREIGN interface permits: `LogicalResult` is a TypeScript interface,
|
|
1389
|
+
* so a conforming reasoner returning a richer result is still returning a `LogicalResult`. An
|
|
1390
|
+
* exact check refused it and failed the gate closed on a valid engine — trading a loud crash
|
|
1391
|
+
* for a wrong refusal, which is the worse of the two. Exactness belongs on records this
|
|
1392
|
+
* package OWNS, where an extra key means the caller misunderstood the contract.
|
|
1393
|
+
*
|
|
1394
|
+
* `count` is checked as a number rather than an integer for the same reason: the published
|
|
1395
|
+
* type says `number`, and narrowing past a foreign contract is the same mistake.
|
|
1396
|
+
*/
|
|
1397
|
+
export declare const isLogicalVerdict: Guard<LogicalResult>;
|
|
1398
|
+
|
|
1399
|
+
/**
|
|
1400
|
+
* `true` when the value is a well-formed `Manifest`.
|
|
1401
|
+
*
|
|
1402
|
+
* @remarks
|
|
1403
|
+
* Partition presence only — disjointness is `validateBrief`'s semantic pass.
|
|
1404
|
+
*/
|
|
1405
|
+
export declare const isManifest: Guard<Manifest>;
|
|
1406
|
+
|
|
1407
|
+
/**
|
|
1408
|
+
* `true` when the value is a non-null object whose named members can be read.
|
|
1409
|
+
*
|
|
1410
|
+
* @remarks
|
|
1411
|
+
* Wider than the contract package's plain-record guard, which refuses any object carrying its
|
|
1412
|
+
* own prototype — a class instance among them. The verdict guards below narrow FOREIGN
|
|
1413
|
+
* interfaces, and an
|
|
1414
|
+
* interface is satisfied by a class instance as readily as by a literal — refusing one is the
|
|
1415
|
+
* same narrowing-past-the-contract mistake that made an exact-record verdict guard fail the
|
|
1416
|
+
* gate closed on a valid engine.
|
|
1417
|
+
*
|
|
1418
|
+
* Arrays are excluded because no interface this narrows is an array, and admitting one would
|
|
1419
|
+
* let index access stand in for member access.
|
|
1420
|
+
*/
|
|
1421
|
+
export declare const isObject: Guard<Record<string, unknown>>;
|
|
1422
|
+
|
|
1423
|
+
/** `true` when the value is a well-formed `Outcome` — `rank` a positive integer. */
|
|
1424
|
+
export declare const isOutcome: Guard<Outcome>;
|
|
1425
|
+
|
|
1426
|
+
/** `true` when the value is a well-formed `Output` — `format` on the closed vocabulary. */
|
|
1427
|
+
export declare const isOutput: Guard<Output>;
|
|
1428
|
+
|
|
1429
|
+
/** `true` when the value is one of the five `OutputFormat` literals. */
|
|
1430
|
+
export declare const isOutputFormat: Guard<OutputFormat>;
|
|
1431
|
+
|
|
1432
|
+
/** `true` when the value is a well-formed `Proof`. */
|
|
1433
|
+
export declare const isProof: Guard<Proof>;
|
|
1434
|
+
|
|
1435
|
+
/** `true` when the value is a well-formed `Reference` — both members required, both single-line. */
|
|
1436
|
+
export declare const isReference: Guard<Reference>;
|
|
1437
|
+
|
|
1438
|
+
/** `true` when the value is a well-formed `Risk` — `severity` on the closed vocabulary. */
|
|
1439
|
+
export declare const isRisk: Guard<Risk>;
|
|
1440
|
+
|
|
1441
|
+
/** `true` when the value is one of the three `RiskSeverity` literals. */
|
|
1442
|
+
export declare const isRiskSeverity: Guard<RiskSeverity>;
|
|
1443
|
+
|
|
1444
|
+
/**
|
|
1445
|
+
* `true` when the value is a well-formed reasons `RuleResult`.
|
|
1446
|
+
*
|
|
1447
|
+
* @remarks
|
|
1448
|
+
* `@orkestrel/reason` publishes the type but no guard for it, and `BriefCompiler` reads these
|
|
1449
|
+
* off a BORROWED engine's return value, so the shape has to be checked rather than trusted.
|
|
1450
|
+
*/
|
|
1451
|
+
export declare const isRuleVerdict: Guard<RuleResult>;
|
|
1452
|
+
|
|
1453
|
+
/** `true` when the value is a well-formed `Task` — both vocabularies closed, statement one line. */
|
|
1454
|
+
export declare const isTask: Guard<Task>;
|
|
1455
|
+
|
|
1456
|
+
/** `true` when the value is one of the eight `TaskDomain` literals. */
|
|
1457
|
+
export declare const isTaskDomain: Guard<TaskDomain>;
|
|
1458
|
+
|
|
1459
|
+
/** `true` when the value is one of the twelve `TaskOperation` literals. */
|
|
1460
|
+
export declare const isTaskOperation: Guard<TaskOperation>;
|
|
1461
|
+
|
|
1462
|
+
/**
|
|
1463
|
+
* `true` when the value is a string holding no line terminator, empty included.
|
|
1464
|
+
*
|
|
1465
|
+
* @remarks
|
|
1466
|
+
* `briefToMarkdown` renders each brief field as ONE markdown row, so a field carrying a
|
|
1467
|
+
* line break would forge a heading or an extra manifest row — which is how a rendered
|
|
1468
|
+
* prompt and `briefToDispatch`'s path sets could disagree about the same brief.
|
|
1469
|
+
*/
|
|
1470
|
+
export declare const isText: Guard<string>;
|
|
1471
|
+
|
|
1472
|
+
/**
|
|
1473
|
+
* Every line terminator a brief field refuses.
|
|
1474
|
+
*
|
|
1475
|
+
* @remarks
|
|
1476
|
+
* The four ECMAScript line terminators, not just `\n`: a renderer that splits on any of
|
|
1477
|
+
* them would let the other three forge a markdown row. CRLF leads the alternation so a
|
|
1478
|
+
* Windows exemplar splits as ONE break rather than two, which would insert a blank line the
|
|
1479
|
+
* caller never wrote. Kept unanchored and stateless — no `g` flag — so `test` never carries
|
|
1480
|
+
* `lastIndex` between calls.
|
|
1481
|
+
*/
|
|
1482
|
+
export declare const LINE_BREAK_PATTERN: RegExp;
|
|
1483
|
+
|
|
1484
|
+
/** A non-empty single-line string — the shape mirror of `isLine`. */
|
|
1485
|
+
export declare const lineShape: StringShape;
|
|
1486
|
+
|
|
1487
|
+
/**
|
|
1488
|
+
* The four disjoint file partitions of a brief.
|
|
1489
|
+
*
|
|
1490
|
+
* @remarks
|
|
1491
|
+
* `read` order is the reading order. A path in more than one partition is a
|
|
1492
|
+
* `validateBrief` error, found by `findManifestOverlaps`.
|
|
1493
|
+
*/
|
|
1494
|
+
export declare interface Manifest {
|
|
1495
|
+
readonly read: readonly Reference[];
|
|
1496
|
+
readonly edit: readonly Reference[];
|
|
1497
|
+
readonly locked: readonly Reference[];
|
|
1498
|
+
readonly forbidden: readonly Reference[];
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
/**
|
|
1502
|
+
* Build a `Manifest`, defaulting every absent partition to an empty list.
|
|
1503
|
+
*
|
|
1504
|
+
* @param partitions - The partitions to fill; a partial literal is enough.
|
|
1505
|
+
* @returns A fresh `Manifest` with all four partitions present.
|
|
1506
|
+
*
|
|
1507
|
+
* @example
|
|
1508
|
+
* ```ts
|
|
1509
|
+
* import { manifest, reference } from '@orkestrel/brief'
|
|
1510
|
+
*
|
|
1511
|
+
* manifest({ edit: [reference('src/core/helpers.ts', 'implementation')] })
|
|
1512
|
+
* ```
|
|
1513
|
+
*/
|
|
1514
|
+
export declare function manifest(partitions?: Partial<Manifest>): Manifest;
|
|
1515
|
+
|
|
1516
|
+
/** The `Manifest` shape — four disjoint reference partitions. */
|
|
1517
|
+
export declare const manifestShape: ObjectShape<{
|
|
1518
|
+
read: ArrayShape<ObjectShape<{
|
|
1519
|
+
path: StringShape;
|
|
1520
|
+
note: StringShape;
|
|
1521
|
+
}, false>>;
|
|
1522
|
+
edit: ArrayShape<ObjectShape<{
|
|
1523
|
+
path: StringShape;
|
|
1524
|
+
note: StringShape;
|
|
1525
|
+
}, false>>;
|
|
1526
|
+
locked: ArrayShape<ObjectShape<{
|
|
1527
|
+
path: StringShape;
|
|
1528
|
+
note: StringShape;
|
|
1529
|
+
}, false>>;
|
|
1530
|
+
forbidden: ArrayShape<ObjectShape<{
|
|
1531
|
+
path: StringShape;
|
|
1532
|
+
note: StringShape;
|
|
1533
|
+
}, false>>;
|
|
1534
|
+
}, false>;
|
|
1535
|
+
|
|
1536
|
+
/**
|
|
1537
|
+
* One ranked outcome — a result, never a step.
|
|
1538
|
+
*
|
|
1539
|
+
* @remarks
|
|
1540
|
+
* `required: true` gates "done"; a demoted outcome is desirable but not blocking.
|
|
1541
|
+
*/
|
|
1542
|
+
export declare interface Outcome {
|
|
1543
|
+
readonly rank: number;
|
|
1544
|
+
readonly text: string;
|
|
1545
|
+
readonly required: boolean;
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
/**
|
|
1549
|
+
* Build an `Outcome`.
|
|
1550
|
+
*
|
|
1551
|
+
* @param rank - The one-based rank; lower ranks matter more.
|
|
1552
|
+
* @param text - The result, never a step.
|
|
1553
|
+
* @param required - Whether the outcome gates "done"; defaults to `true`.
|
|
1554
|
+
* @returns A fresh `Outcome`.
|
|
1555
|
+
*
|
|
1556
|
+
* @example
|
|
1557
|
+
* ```ts
|
|
1558
|
+
* import { outcome } from '@orkestrel/brief'
|
|
1559
|
+
*
|
|
1560
|
+
* outcome(1, 'useForm uses native FormData with no behavior change') // required: true
|
|
1561
|
+
* outcome(2, 'the diff stays under 200 lines', false)
|
|
1562
|
+
* ```
|
|
1563
|
+
*/
|
|
1564
|
+
export declare function outcome(rank: number, text: string, required?: boolean): Outcome;
|
|
1565
|
+
|
|
1566
|
+
/** The `Outcome` shape — a one-based rank, the result text, and whether it gates done. */
|
|
1567
|
+
export declare const outcomeShape: ObjectShape<{
|
|
1568
|
+
rank: NumberShape;
|
|
1569
|
+
text: StringShape;
|
|
1570
|
+
required: BooleanShape;
|
|
1571
|
+
}, false>;
|
|
1572
|
+
|
|
1573
|
+
/**
|
|
1574
|
+
* The closed shape of the deliverable.
|
|
1575
|
+
*
|
|
1576
|
+
* @remarks
|
|
1577
|
+
* `format` is required; `sections` / `include` / `exclude` refine it.
|
|
1578
|
+
*/
|
|
1579
|
+
export declare interface Output {
|
|
1580
|
+
readonly format: OutputFormat;
|
|
1581
|
+
readonly sections?: readonly string[];
|
|
1582
|
+
readonly include?: readonly string[];
|
|
1583
|
+
readonly exclude?: readonly string[];
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
/**
|
|
1587
|
+
* Build an `Output`.
|
|
1588
|
+
*
|
|
1589
|
+
* @param format - The closed deliverable format.
|
|
1590
|
+
* @param overrides - Optional `sections` / `include` / `exclude`; absent keys are OMITTED.
|
|
1591
|
+
* @returns A fresh `Output`.
|
|
1592
|
+
*
|
|
1593
|
+
* @example
|
|
1594
|
+
* ```ts
|
|
1595
|
+
* import { output } from '@orkestrel/brief'
|
|
1596
|
+
*
|
|
1597
|
+
* output('markdown') // { format: 'markdown' }
|
|
1598
|
+
* output('diff', { include: ['updated useForm.ts'] })
|
|
1599
|
+
* ```
|
|
1600
|
+
*/
|
|
1601
|
+
export declare function output(format: OutputFormat, overrides?: Partial<Omit<Output, 'format'>>): Output;
|
|
1602
|
+
|
|
1603
|
+
/** The five `OutputFormat` values, frozen. */
|
|
1604
|
+
export declare const OUTPUT_FORMATS: readonly OutputFormat[];
|
|
1605
|
+
|
|
1606
|
+
/** The closed vocabulary of deliverable shapes. */
|
|
1607
|
+
export declare type OutputFormat = 'markdown' | 'json' | 'code' | 'diff' | 'prose';
|
|
1608
|
+
|
|
1609
|
+
/** The `Output` shape — a closed format plus its optional refinements. */
|
|
1610
|
+
export declare const outputShape: ObjectShape<{
|
|
1611
|
+
format: LiteralShape<readonly OutputFormat[]>;
|
|
1612
|
+
sections: OptionalShape<ArrayShape<StringShape>>;
|
|
1613
|
+
include: OptionalShape<ArrayShape<StringShape>>;
|
|
1614
|
+
exclude: OptionalShape<ArrayShape<StringShape>>;
|
|
1615
|
+
}, false>;
|
|
1616
|
+
|
|
1617
|
+
/**
|
|
1618
|
+
* Parse a JSON string into a `Brief`.
|
|
1619
|
+
*
|
|
1620
|
+
* @remarks
|
|
1621
|
+
* The parse-then-trust boundary for a stored brief, a tool argument, or an agent's
|
|
1622
|
+
* emission. Invalid JSON, an extra key, an off-vocabulary literal, and a missing section
|
|
1623
|
+
* all fail the same way — `undefined`, never a throw. Coerce a bare vocabulary value with
|
|
1624
|
+
* `parseEnum` from `@orkestrel/contract` against the exported tuple instead.
|
|
1625
|
+
*
|
|
1626
|
+
* @param value - The JSON text to parse.
|
|
1627
|
+
* @returns The `Brief` when the parsed value satisfies `isBrief`, otherwise `undefined`.
|
|
1628
|
+
*
|
|
1629
|
+
* @example
|
|
1630
|
+
* ```ts
|
|
1631
|
+
* import { parseBrief } from '@orkestrel/brief'
|
|
1632
|
+
*
|
|
1633
|
+
* parseBrief('not json') // undefined
|
|
1634
|
+
* parseBrief('{"task":{"operation":"plan","domain":"ops","statement":"x."}}') // undefined
|
|
1635
|
+
* ```
|
|
1636
|
+
*/
|
|
1637
|
+
export declare function parseBrief(value: string): Brief | undefined;
|
|
1638
|
+
|
|
1639
|
+
/**
|
|
1640
|
+
* Return a fresh brief with `trace` and `hash` derived from its own content.
|
|
1641
|
+
*
|
|
1642
|
+
* @remarks
|
|
1643
|
+
* Deterministic: no clock, no randomness, no run-specific data. Any existing `trace` /
|
|
1644
|
+
* `hash` is stripped before the digest, so pinning is idempotent and a re-pin of unchanged
|
|
1645
|
+
* content produces the same hash.
|
|
1646
|
+
*
|
|
1647
|
+
* The snapshot is taken FIRST, before any member is read, so a hostile input whose getters
|
|
1648
|
+
* throw surfaces as this package's coded error rather than as whatever it threw.
|
|
1649
|
+
*
|
|
1650
|
+
* @param source - The brief to pin.
|
|
1651
|
+
* @returns A fresh, pinned, deeply frozen `Brief`.
|
|
1652
|
+
* @throws {@link BriefError} `INVALID` when the brief carries data JSON cannot express.
|
|
1653
|
+
*
|
|
1654
|
+
* @example
|
|
1655
|
+
* ```ts
|
|
1656
|
+
* import { brief, pinBrief, task } from '@orkestrel/brief'
|
|
1657
|
+
*
|
|
1658
|
+
* const pinned = pinBrief(brief(task('document', 'writing', 'Write the brief guide.')))
|
|
1659
|
+
* pinned.hash // an 8-hex-digit structural digest
|
|
1660
|
+
* pinned.trace // 'document/writing · outcomes:0 · gaps:0/0 · proofs:0'
|
|
1661
|
+
* ```
|
|
1662
|
+
*/
|
|
1663
|
+
export declare function pinBrief(source: Brief): Brief;
|
|
1664
|
+
|
|
1665
|
+
/** The `pin` phase snapshot — the drafted `Brief` in, the pinned `Brief` out. */
|
|
1666
|
+
export declare interface PinStageRecord {
|
|
1667
|
+
readonly stage: 'pin';
|
|
1668
|
+
readonly input: Brief;
|
|
1669
|
+
readonly output?: Brief;
|
|
1670
|
+
readonly error?: string;
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
/**
|
|
1674
|
+
* One mechanical, transcript-provable check.
|
|
1675
|
+
*
|
|
1676
|
+
* @remarks
|
|
1677
|
+
* `command` should carry a clear exit signal — it becomes the `/goal` condition verbatim.
|
|
1678
|
+
*/
|
|
1679
|
+
export declare interface Proof {
|
|
1680
|
+
readonly text: string;
|
|
1681
|
+
readonly command: string;
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
/**
|
|
1685
|
+
* Build a `Proof`.
|
|
1686
|
+
*
|
|
1687
|
+
* @param text - What the check settles.
|
|
1688
|
+
* @param command - The command whose exit signal settles it.
|
|
1689
|
+
* @returns A fresh `Proof`.
|
|
1690
|
+
*
|
|
1691
|
+
* @example
|
|
1692
|
+
* ```ts
|
|
1693
|
+
* import { proof } from '@orkestrel/brief'
|
|
1694
|
+
*
|
|
1695
|
+
* proof('type-check and lint pass', 'npm run check')
|
|
1696
|
+
* ```
|
|
1697
|
+
*/
|
|
1698
|
+
export declare function proof(text: string, command: string): Proof;
|
|
1699
|
+
|
|
1700
|
+
/** The `Proof` shape — the claim and the command that settles it. */
|
|
1701
|
+
export declare const proofShape: ObjectShape<{
|
|
1702
|
+
text: StringShape;
|
|
1703
|
+
command: StringShape;
|
|
1704
|
+
}, false>;
|
|
1705
|
+
|
|
1706
|
+
/**
|
|
1707
|
+
* One referenced path and why it is listed.
|
|
1708
|
+
*
|
|
1709
|
+
* @remarks
|
|
1710
|
+
* The ONE path record. A reference means different things in different containers, and the
|
|
1711
|
+
* container is what says which: `Brief.authority` ranks its entries so index 0 wins every
|
|
1712
|
+
* conflict, and each `Manifest` partition states a permission. The record itself carries no
|
|
1713
|
+
* classifier, because a second label on the row would restate what the container already
|
|
1714
|
+
* fixed — and the label this package used to carry was one repository's document taxonomy
|
|
1715
|
+
* rather than a domain.
|
|
1716
|
+
*
|
|
1717
|
+
* `note` is required. A path with no rationale is interpretation left to do, which is the
|
|
1718
|
+
* one thing a brief exists to remove.
|
|
1719
|
+
*/
|
|
1720
|
+
export declare interface Reference {
|
|
1721
|
+
readonly path: string;
|
|
1722
|
+
readonly note: string;
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
/**
|
|
1726
|
+
* Build a `Reference`.
|
|
1727
|
+
*
|
|
1728
|
+
* @param path - The referenced path or glob.
|
|
1729
|
+
* @param note - Why the path is listed.
|
|
1730
|
+
* @returns A fresh `Reference`.
|
|
1731
|
+
*
|
|
1732
|
+
* @example
|
|
1733
|
+
* ```ts
|
|
1734
|
+
* import { reference } from '@orkestrel/brief'
|
|
1735
|
+
*
|
|
1736
|
+
* reference('AGENTS.md', 'project law') // { path: 'AGENTS.md', note: 'project law' }
|
|
1737
|
+
* ```
|
|
1738
|
+
*/
|
|
1739
|
+
export declare function reference(path: string, note: string): Reference;
|
|
1740
|
+
|
|
1741
|
+
/** The `Reference` shape — a path and the note that justifies listing it. */
|
|
1742
|
+
export declare const referenceShape: ObjectShape<{
|
|
1743
|
+
path: StringShape;
|
|
1744
|
+
note: StringShape;
|
|
1745
|
+
}, false>;
|
|
1746
|
+
|
|
1747
|
+
/** One pre-empted risk and the mitigation that answers it. */
|
|
1748
|
+
export declare interface Risk {
|
|
1749
|
+
readonly severity: RiskSeverity;
|
|
1750
|
+
readonly text: string;
|
|
1751
|
+
readonly mitigation: string;
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
/**
|
|
1755
|
+
* Build a `Risk`.
|
|
1756
|
+
*
|
|
1757
|
+
* @param severity - The closed severity.
|
|
1758
|
+
* @param text - What could go wrong.
|
|
1759
|
+
* @param mitigation - What answers it.
|
|
1760
|
+
* @returns A fresh `Risk`.
|
|
1761
|
+
*
|
|
1762
|
+
* @example
|
|
1763
|
+
* ```ts
|
|
1764
|
+
* import { risk } from '@orkestrel/brief'
|
|
1765
|
+
*
|
|
1766
|
+
* risk('medium', 'native validation differs subtly', 'assert message and state in tests')
|
|
1767
|
+
* ```
|
|
1768
|
+
*/
|
|
1769
|
+
export declare function risk(severity: RiskSeverity, text: string, mitigation: string): Risk;
|
|
1770
|
+
|
|
1771
|
+
/** The three `RiskSeverity` values, frozen. */
|
|
1772
|
+
export declare const RISK_SEVERITIES: readonly RiskSeverity[];
|
|
1773
|
+
|
|
1774
|
+
/** The closed vocabulary of risk severities. */
|
|
1775
|
+
export declare type RiskSeverity = 'low' | 'medium' | 'high';
|
|
1776
|
+
|
|
1777
|
+
/** The `Risk` shape — a closed severity, the risk, and its mitigation. */
|
|
1778
|
+
export declare const riskShape: ObjectShape<{
|
|
1779
|
+
severity: LiteralShape<readonly RiskSeverity[]>;
|
|
1780
|
+
text: StringShape;
|
|
1781
|
+
mitigation: StringShape;
|
|
1782
|
+
}, false>;
|
|
1783
|
+
|
|
1784
|
+
/**
|
|
1785
|
+
* The positive form of {@link LINE_BREAK_PATTERN}, for the shape DSL.
|
|
1786
|
+
*
|
|
1787
|
+
* @remarks
|
|
1788
|
+
* `stringShape`'s `pattern` must MATCH an accepted value, so the guard's refusal regex
|
|
1789
|
+
* cannot be reused directly. Both are derived from one character class, which is what
|
|
1790
|
+
* keeps the hand-composed guards and the compiled shapes refusing the same strings.
|
|
1791
|
+
*/
|
|
1792
|
+
export declare const SINGLE_LINE_PATTERN: RegExp;
|
|
1793
|
+
|
|
1794
|
+
/**
|
|
1795
|
+
* Return a deeply owned, deeply frozen copy of a brief, refusing anything off-contract.
|
|
1796
|
+
*
|
|
1797
|
+
* @remarks
|
|
1798
|
+
* The one reading boundary this package has, used by the pin, the registry, and every
|
|
1799
|
+
* projection. It matters twice over. A brief built from caller collections ADOPTS those
|
|
1800
|
+
* arrays, so a later `outcomes.push` would change content a hash already described. And a
|
|
1801
|
+
* caller's object may answer differently on each read, so validating one reading and
|
|
1802
|
+
* rendering from a second let a brief that passed the contract render a row it does not
|
|
1803
|
+
* contain — this takes ONE reading, validates that, and freezes it.
|
|
1804
|
+
*
|
|
1805
|
+
* `cloneJSONRecord` is `@orkestrel/contract`'s primitive rather than the ambient
|
|
1806
|
+
* `structuredClone`: it deep-freezes, it refuses a value JSON cannot express, and it is a
|
|
1807
|
+
* captured import rather than a mutable global. The result is a null-prototype record, so
|
|
1808
|
+
* compare it structurally rather than by prototype.
|
|
1809
|
+
*
|
|
1810
|
+
* This file imports no sibling helper, which is what lets `helpers.ts` consume it without a
|
|
1811
|
+
* module cycle.
|
|
1812
|
+
*
|
|
1813
|
+
* @param source - The brief to snapshot.
|
|
1814
|
+
* @returns A deeply frozen `Brief` sharing no reference with `source`.
|
|
1815
|
+
* @throws {@link BriefError} `INVALID` when the value is off-contract or JSON cannot express it.
|
|
1816
|
+
*
|
|
1817
|
+
* @example
|
|
1818
|
+
* ```ts
|
|
1819
|
+
* import { brief, outcome, snapshotBrief, task } from '@orkestrel/brief'
|
|
1820
|
+
*
|
|
1821
|
+
* const outcomes = [outcome(1, 'shipped')]
|
|
1822
|
+
* const owned = snapshotBrief(brief(task('plan', 'ops', 'Plan the release.'), { outcomes }))
|
|
1823
|
+
* owned.outcomes === outcomes // false — the alias is broken
|
|
1824
|
+
* Object.isFrozen(owned.outcomes) // true
|
|
1825
|
+
* ```
|
|
1826
|
+
*/
|
|
1827
|
+
export declare function snapshotBrief(source: Brief): Brief;
|
|
1828
|
+
|
|
1829
|
+
/**
|
|
1830
|
+
* What the brief asks for, in one imperative sentence.
|
|
1831
|
+
*
|
|
1832
|
+
* @remarks
|
|
1833
|
+
* A compound `statement` is two briefs — `validateBrief` errors on more than one sentence.
|
|
1834
|
+
*/
|
|
1835
|
+
export declare interface Task {
|
|
1836
|
+
readonly operation: TaskOperation;
|
|
1837
|
+
readonly domain: TaskDomain;
|
|
1838
|
+
readonly statement: string;
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
/**
|
|
1842
|
+
* Build a `Task`.
|
|
1843
|
+
*
|
|
1844
|
+
* @param operation - What the brief asks for, from the closed operation vocabulary.
|
|
1845
|
+
* @param domain - The subject matter, from the closed domain vocabulary.
|
|
1846
|
+
* @param statement - One imperative sentence naming the object of the work.
|
|
1847
|
+
* @returns A fresh `Task`.
|
|
1848
|
+
*
|
|
1849
|
+
* @example
|
|
1850
|
+
* ```ts
|
|
1851
|
+
* import { task } from '@orkestrel/brief'
|
|
1852
|
+
*
|
|
1853
|
+
* task('refactor', 'code', 'Refactor useForm to native browser form APIs.')
|
|
1854
|
+
* ```
|
|
1855
|
+
*/
|
|
1856
|
+
export declare function task(operation: TaskOperation, domain: TaskDomain, statement: string): Task;
|
|
1857
|
+
|
|
1858
|
+
/** The eight `TaskDomain` values, frozen. */
|
|
1859
|
+
export declare const TASK_DOMAINS: readonly TaskDomain[];
|
|
1860
|
+
|
|
1861
|
+
/** The twelve `TaskOperation` values, frozen. */
|
|
1862
|
+
export declare const TASK_OPERATIONS: readonly TaskOperation[];
|
|
1863
|
+
|
|
1864
|
+
/** The closed vocabulary of the subject matter a brief operates on. */
|
|
1865
|
+
export declare type TaskDomain = 'code' | 'writing' | 'research' | 'analysis' | 'design' | 'data' | 'ops' | 'other';
|
|
1866
|
+
|
|
1867
|
+
/**
|
|
1868
|
+
* The closed vocabulary of what a brief asks for.
|
|
1869
|
+
*
|
|
1870
|
+
* @remarks
|
|
1871
|
+
* A request that fits none of these twelve is mis-scoped rather than a missing
|
|
1872
|
+
* literal. Compose with `literalOf(TASK_OPERATIONS)` or `parseEnum(value, TASK_OPERATIONS)`.
|
|
1873
|
+
*/
|
|
1874
|
+
export declare type TaskOperation = 'create' | 'refactor' | 'debug' | 'extract' | 'migrate' | 'explain' | 'review' | 'optimize' | 'audit' | 'test' | 'document' | 'plan';
|
|
1875
|
+
|
|
1876
|
+
/** The `Task` shape — closed operation and domain vocabularies plus a non-empty statement. */
|
|
1877
|
+
export declare const taskShape: ObjectShape<{
|
|
1878
|
+
operation: LiteralShape<readonly TaskOperation[]>;
|
|
1879
|
+
domain: LiteralShape<readonly TaskDomain[]>;
|
|
1880
|
+
statement: StringShape;
|
|
1881
|
+
}, false>;
|
|
1882
|
+
|
|
1883
|
+
/** A single-line string of any length, including empty. */
|
|
1884
|
+
export declare const textShape: StringShape;
|
|
1885
|
+
|
|
1886
|
+
/**
|
|
1887
|
+
* The semantic pass over an already-shape-valid brief.
|
|
1888
|
+
*
|
|
1889
|
+
* @remarks
|
|
1890
|
+
* ERRORS are the structural violations no assumption can paper over: a manifest
|
|
1891
|
+
* overlap, an authority no partition grants access to, an empty `proofs` list, and a
|
|
1892
|
+
* statement that is not exactly one sentence.
|
|
1893
|
+
* WARNINGS are runnable but suspicious: duplicate outcome ranks, an unpaired open gap,
|
|
1894
|
+
* and an optional outcome ranked above a required one. Never throws.
|
|
1895
|
+
*
|
|
1896
|
+
* @param source - The brief to inspect.
|
|
1897
|
+
* @returns A reasons `ReasonValidationResult`; `valid` exactly when `errors` is empty.
|
|
1898
|
+
*
|
|
1899
|
+
* @example
|
|
1900
|
+
* ```ts
|
|
1901
|
+
* import { brief, proof, task, validateBrief } from '@orkestrel/brief'
|
|
1902
|
+
*
|
|
1903
|
+
* validateBrief(brief(task('plan', 'ops', 'Plan the release.'))) // valid: false — no proofs
|
|
1904
|
+
* validateBrief(
|
|
1905
|
+
* brief(task('plan', 'ops', 'Plan the release.'), { proofs: [proof('ok', 'npm test')] }),
|
|
1906
|
+
* ) // valid: true
|
|
1907
|
+
* ```
|
|
1908
|
+
*/
|
|
1909
|
+
export declare function validateBrief(source: Brief): ReasonValidationResult;
|
|
1910
|
+
|
|
1911
|
+
export { }
|