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