@orkestrel/scaffold 0.0.67 → 0.0.69

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.
Files changed (74) hide show
  1. package/dist/bin/main.js +67 -44
  2. package/dist/bin/main.js.map +1 -1
  3. package/dist/host/agents/templates/brief.md +9 -0
  4. package/dist/host/claude/agents/orkestrel.md +4 -4
  5. package/dist/host/claude/rules/names.md +15 -0
  6. package/dist/host/claude/rules/tests.md +33 -4
  7. package/dist/host/claude/rules/workspace.md +14 -2
  8. package/dist/host/dotfiles/prettierignore +3 -0
  9. package/dist/host/guides/README.md +65 -0
  10. package/dist/host/guides/abort.md +169 -0
  11. package/dist/host/guides/agent.md +1567 -0
  12. package/dist/host/guides/brief.md +1266 -0
  13. package/dist/host/guides/browser.md +2200 -0
  14. package/dist/host/guides/budget.md +196 -0
  15. package/dist/host/guides/codec.md +519 -0
  16. package/dist/host/guides/console.md +785 -0
  17. package/dist/host/guides/contract.md +1193 -0
  18. package/dist/host/guides/csv.md +541 -0
  19. package/dist/host/guides/database.md +2518 -0
  20. package/dist/host/guides/emitter.md +233 -0
  21. package/dist/host/guides/form.md +1791 -0
  22. package/dist/host/guides/html.md +717 -0
  23. package/dist/host/guides/indexeddb.md +505 -0
  24. package/dist/host/guides/interpret.md +1029 -0
  25. package/dist/host/guides/lsp.md +515 -0
  26. package/dist/host/guides/markdown.md +964 -0
  27. package/dist/host/guides/mcp.md +5554 -0
  28. package/dist/host/guides/middleware.md +927 -0
  29. package/dist/host/guides/msg.md +440 -0
  30. package/dist/host/guides/ndjson.md +120 -0
  31. package/dist/host/guides/ollama.md +380 -0
  32. package/dist/host/guides/pool.md +280 -0
  33. package/dist/host/guides/probe.md +1210 -0
  34. package/dist/host/guides/process.md +1620 -0
  35. package/dist/host/guides/program.md +1110 -0
  36. package/dist/host/guides/qualifier.md +854 -0
  37. package/dist/host/guides/queue.md +370 -0
  38. package/dist/host/guides/rater.md +330 -0
  39. package/dist/host/guides/reason.md +1122 -0
  40. package/dist/host/guides/relation.md +373 -0
  41. package/dist/host/guides/router.md +753 -0
  42. package/dist/host/guides/scaffold.md +192 -31
  43. package/dist/host/guides/sea.md +383 -0
  44. package/dist/host/guides/server.md +752 -0
  45. package/dist/host/guides/sqlite.md +330 -0
  46. package/dist/host/guides/sse.md +187 -0
  47. package/dist/host/guides/supervisor.md +4890 -0
  48. package/dist/host/guides/table.md +1556 -0
  49. package/dist/host/guides/template.md +280 -0
  50. package/dist/host/guides/terminal.md +1145 -0
  51. package/dist/host/guides/test.md +2969 -0
  52. package/dist/host/guides/timeout.md +252 -0
  53. package/dist/host/guides/tool.md +507 -0
  54. package/dist/host/guides/toolbox.md +1038 -0
  55. package/dist/host/guides/websocket.md +282 -0
  56. package/dist/host/guides/worker.md +615 -0
  57. package/dist/host/guides/workflow.md +1507 -0
  58. package/dist/host/guides/workspace.md +595 -0
  59. package/dist/host/manifest.json +1218 -10
  60. package/dist/host/tests/policy.test.ts +279 -2
  61. package/dist/host/tests/setupPolicy.ts +445 -6
  62. package/dist/src/core/index.cjs +38 -16
  63. package/dist/src/core/index.cjs.map +1 -1
  64. package/dist/src/core/index.d.cts +33 -9
  65. package/dist/src/core/index.d.ts +33 -9
  66. package/dist/src/core/index.js +37 -17
  67. package/dist/src/core/index.js.map +1 -1
  68. package/dist/src/server/index.cjs +1750 -1567
  69. package/dist/src/server/index.cjs.map +1 -1
  70. package/dist/src/server/index.d.cts +106 -24
  71. package/dist/src/server/index.d.ts +106 -24
  72. package/dist/src/server/index.js +1751 -1570
  73. package/dist/src/server/index.js.map +1 -1
  74. package/package.json +3 -3
@@ -0,0 +1,1266 @@
1
+ # Brief
2
+
3
+ > The specification compiler: a synchronous, deterministic pipeline that resolves a rough
4
+ > request into a `Brief` — a closed, content-hashed execution contract another agent can run
5
+ > with no interpretation left to do — gated by a traceable reasoner and projected into every
6
+ > downstream artifact.
7
+
8
+ You cannot make a model's sampling deterministic from a prompt, but you can make the task
9
+ deterministic: resolve every implicit decision ahead of time and pin the result with
10
+ mechanical proofs, so any correct execution is equivalent under the contract. The `Brief`
11
+ is that resolution as plain data, and the module is deliberately mechanism, never policy.
12
+ The judgment calls — which files, which outcomes, which proofs — belong to the caller,
13
+ whether a human or an agent. This module supplies the closed vocabularies, the exact-record
14
+ validation, the fail-closed gate, the deterministic pinning, and the lossless projections.
15
+ Separating the _what_ from the _how_ is the whole design: `outcomes` and `proofs` pin the
16
+ result's shape and its transcript-provable evidence, while the method stays free unless the
17
+ method itself is the requirement.
18
+
19
+ The forward path: raw text runs through an injected `@orkestrel/interpret` pipeline, its
20
+ `Interpretation` is drafted into brief sections (intent to `task`, entities to `givens`,
21
+ ambiguities to `gaps`), caller-supplied sections merge over the draft, the fail-closed gate is
22
+ evaluated as a reasons `LogicalDefinition` — a traceable verdict, never an ad-hoc `if` — and a
23
+ passing brief is pinned, with `trace` and `hash` derived rather than authored. The reverse
24
+ path: `briefToMarkdown`, `briefToGoal`, and `briefToDispatch` project the pinned brief into
25
+ its downstream views, each derived from the one contract rather than written beside it.
26
+
27
+ Nothing here is an LLM, provider, or agent: the markdown a projection renders is for an
28
+ external model, never consumed internally. A brief with blocking gaps yields a visible
29
+ incomplete `Briefing` carrying the questions, because a half-specified brief is worse than a
30
+ question.
31
+
32
+ Every discriminant names its axis, never `kind` or `type`: `stage` splits the pipeline
33
+ phases, `severity` splits risks, and `code` splits coded errors. A record whose container
34
+ already fixes what it is, like a referenced path, or whose candidate vocabulary was neither
35
+ closed nor disjoint, like a cited source, carries no discriminant at all.
36
+
37
+ Source: [`src/core`](../src/core). Surfaced through the `@src/core` barrel.
38
+
39
+ ## Surface
40
+
41
+ ### Compile and project a brief
42
+
43
+ Compile a request into a `Briefing`, then project the brief it carries:
44
+
45
+ ```ts
46
+ import {
47
+ briefToGoal,
48
+ briefToMarkdown,
49
+ buildOutcome,
50
+ buildProof,
51
+ buildTask,
52
+ createBriefCompiler,
53
+ } from '@orkestrel/brief'
54
+
55
+ const compiler = createBriefCompiler()
56
+
57
+ const briefing = compiler.compile({
58
+ task: buildTask('refactor', 'code', 'Refactor useForm to native browser form APIs.'),
59
+ authority: [{ path: 'AGENTS.md', note: 'project law; wins every conflict' }],
60
+ manifest: {
61
+ read: [
62
+ { path: 'AGENTS.md', note: 'project law; wins every conflict' },
63
+ { path: 'guides/browser.md', note: 'the composable contract' },
64
+ ],
65
+ edit: [{ path: 'src/browser/composables/useForm.ts', note: 'the composable being refactored' }],
66
+ locked: [{ path: 'src/browser/types.ts', note: 'the published contract' }],
67
+ forbidden: [{ path: 'app/**', note: 'out of scope' }],
68
+ },
69
+ outcomes: [buildOutcome(1, 'useForm uses native FormData with no behavior change')],
70
+ proofs: [buildProof('type-check and lint pass', 'npm run check')],
71
+ })
72
+
73
+ briefing.brief !== undefined // true — the brief is present exactly when the gate passed
74
+ if (briefing.brief !== undefined) {
75
+ briefToMarkdown(briefing.brief) // the copy-ready agent prompt
76
+ briefToGoal(briefing.brief) // the /goal completion condition
77
+ }
78
+
79
+ compiler.emitter.on('block', (questions) => questions.length)
80
+ compiler.destroy()
81
+ ```
82
+
83
+ `compile()` is genuinely synchronous and runs the fixed pipeline
84
+ `[interpret, draft, gate, pin]`. Blocking gaps, a refused gate, and a thrown stage all
85
+ yield a visible incomplete `Briefing` — `brief` absent and the cause recorded on `failures` —
86
+ rather than a throw. `questions` carries the gaps when a blocking gap is the cause and is
87
+ empty otherwise, so `brief !== undefined` is the completeness test rather than
88
+ `questions.length`. The `interpret` stage is skipped entirely when the input carries no `text`,
89
+ so a fully caller-authored `BriefInput` drafts, gates, and pins without touching the language
90
+ pipeline at all.
91
+
92
+ ### Types
93
+
94
+ A `Shape` cell holds an interface's data members as bare names in braces, `?` marking an optional member and `plus` introducing its call-signature members, and a type alias's own type literal with a union's arms escaped as `\|`.
95
+
96
+ | Type | Kind | Shape | Summary |
97
+ | ------------------------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
98
+ | `TaskOperation` | type | `'create' \| 'refactor' \| 'debug' \| 'extract' \| 'migrate' \| 'explain' \| 'review' \| 'optimize' \| 'audit' \| 'test' \| 'document' \| 'plan'` | Names the closed vocabulary of what a brief asks for. |
99
+ | `TaskDomain` | type | `'code' \| 'writing' \| 'research' \| 'analysis' \| 'design' \| 'data' \| 'ops' \| 'other'` | Names the closed vocabulary of the subject matter a brief operates on. |
100
+ | `OutputFormat` | type | `'markdown' \| 'json' \| 'code' \| 'diff' \| 'prose'` | Names the closed vocabulary of deliverable shapes. |
101
+ | `RiskSeverity` | type | `'low' \| 'medium' \| 'high'` | Names the closed vocabulary of risk severities. |
102
+ | `BriefStage` | type | `'interpret' \| 'draft' \| 'gate' \| 'pin'` | Names the fixed compilation phases, in pipeline order. |
103
+ | `BriefErrorCode` | type | `'INTERPRET_FAILED' \| 'DRAFT_FAILED' \| 'GATE_FAILED' \| 'PIN_FAILED' \| 'BLOCKED' \| 'INVALID' \| 'DESTROYED'` | Names the machine-readable reasons a `BriefError` carries. |
104
+ | `Task` | interface | `{ operation, domain, statement }` | States what the brief asks for, in one imperative sentence. |
105
+ | `Reference` | interface | `{ path, note }` | Represents one referenced path and why it is listed. |
106
+ | `Manifest` | interface | `{ read, edit, locked, forbidden }` | Represents the disjoint file partitions of a brief. |
107
+ | `Outcome` | interface | `{ rank, text, required }` | Represents one ranked outcome — a result, never a step. |
108
+ | `Given` | interface | `{ category, name, value }` | Represents one context fact handed to the executor — a convention, a version, a constraint value. |
109
+ | `Example` | interface | `{ input, output, note? }` | Represents one input to output exemplar — the ambiguity remover that leaves the least to interpret. |
110
+ | `Citation` | interface | `{ name, url, note }` | Represents one external source — what it is called, where it lives, and why it is cited. |
111
+ | `Gap` | interface | `{ field, question, blocking, candidates? }` | Represents one unknown the brief has not resolved. |
112
+ | `Risk` | interface | `{ severity, text, mitigation }` | Represents one pre-empted risk and the mitigation that answers it. |
113
+ | `Output` | interface | `{ format, sections?, include?, exclude? }` | Represents the closed shape of the deliverable. |
114
+ | `Proof` | interface | `{ text, command }` | Represents one mechanical, transcript-provable check. |
115
+ | `Brief` | interface | `{ task, authority, manifest, outcomes, rules, invariants, givens, examples, assumptions, citations, gaps, risks, output, proofs, trace?, hash? }` | Represents the closed execution contract — a rough request with every implicit decision resolved. |
116
+ | `BriefInput` | interface | `{ text?, interpretation?, task?, authority?, manifest?, outcomes?, rules?, invariants?, givens?, examples?, assumptions?, citations?, gaps?, risks?, output?, proofs? }` | Represents one `compile()` input. |
117
+ | `Briefing` | interface | `{ interpretation?, brief?, questions, verdict?, stages, failures, digest }` | Represents the full, replayable outcome of one `compile()` call. |
118
+ | `Dispatch` | interface | `{ prompt, authority, read, edit, locked, forbidden }` | Represents the subagent projection of a brief. |
119
+ | `InterpretStageRecord` | interface | `{ stage, input, output?, error? }` | Records the `interpret` phase snapshot — raw text in, an `Interpretation` out. |
120
+ | `DraftStageRecord` | interface | `{ stage, input, output?, error? }` | Records the `draft` phase snapshot — the caller's input in, an unpinned `Brief` out. |
121
+ | `GateStageRecord` | interface | `{ stage, input, output?, error? }` | Records the `gate` phase snapshot — the readiness `Subject` in, the reasoner's verdict out. |
122
+ | `PinStageRecord` | interface | `{ stage, input, output?, error? }` | Records the `pin` phase snapshot — the drafted `Brief` in, the pinned `Brief` out. |
123
+ | `BriefStageRecord` | type | `InterpretStageRecord \| DraftStageRecord \| GateStageRecord \| PinStageRecord` | Represents one pipeline phase, discriminated by `stage`. |
124
+ | `BriefStageFailure` | interface | `{ stage, code, message }` | Represents a visible marker for a phase that failed. |
125
+ | `BriefRecord` | interface | `{ id, brief, version, hash }` | Represents a versioned, content-hashed `Brief` inside a `BriefManagerInterface`. |
126
+ | `BriefCompilerEventMap` | type | `{ compile, block, error, destroy }` | Declares the `BriefCompiler`'s push observation surface. |
127
+ | `BriefCompilerOptions` | interface | `{ interpret?, reason?, actions?, domains?, on?, error? }` | Represents the input to `createBriefCompiler`. |
128
+ | `BriefCompilerInterface` | interface | `{ emitter, interpret, reason } plus compile, gate, destroy` | Declares the compilation orchestrator contract. |
129
+ | `BriefManagerEventMap` | type | `{ add, remove, destroy }` | Declares the `BriefManager`'s push observation surface. |
130
+ | `BriefManagerOptions` | interface | `{ briefs?, on?, error? }` | Represents the input to `createBriefManager`. |
131
+ | `BriefManagerInterface` | interface | `{ emitter, count } plus has, brief, briefs, add, remove, destroy` | Declares the brief registry contract. |
132
+
133
+ The `Briefing` deliberately carries cross-package payloads by their originating types:
134
+ `interpretation` is an `@orkestrel/interpret` `Interpretation`, `verdict` is an
135
+ `@orkestrel/reason` `LogicalResult`, and `BriefManagerInterface.add` takes interprets
136
+ `RecordOptions`. Each is imported at the consumer site from the package that owns it,
137
+ never re-exported here. The verdict is the gate's own traceable account: every readiness
138
+ check, met or missed, narrated by the reasoner.
139
+
140
+ ### Constants
141
+
142
+ A `Shape` cell holds the constant's declared type.
143
+
144
+ | API | Kind | Shape | Summary |
145
+ | ------------------------ | ----- | ----------------------------------- | ------------------------------------------------------------------------------- |
146
+ | `TASK_OPERATIONS` | const | `readonly TaskOperation[]` | Lists the `TaskOperation` values, frozen. |
147
+ | `TASK_DOMAINS` | const | `readonly TaskDomain[]` | Lists the `TaskDomain` values, frozen. |
148
+ | `OUTPUT_FORMATS` | const | `readonly OutputFormat[]` | Lists the `OutputFormat` values, frozen. |
149
+ | `RISK_SEVERITIES` | const | `readonly RiskSeverity[]` | Lists the `RiskSeverity` values, frozen. |
150
+ | `INTERPRETATION_MEMBERS` | const | `readonly (keyof Interpretation)[]` | Lists every published `Interpretation` member name, frozen. |
151
+ | `DEFAULT_BRIEF_TURNS` | const | `number` | Holds `16` — the default turn cap `briefToGoal` renders. |
152
+ | `GATE_ID` | const | `string` | Holds `'gate'` — the id of the `buildGateDefinition()` logical definition. |
153
+ | `LINE_BREAK_PATTERN` | const | `RegExp` | Matches every line terminator a brief field refuses. |
154
+ | `SINGLE_LINE_PATTERN` | const | `RegExp` | Holds the positive form of `LINE_BREAK_PATTERN`, for a `stringShape` `pattern`. |
155
+ | `BLANK_PATTERN` | const | `RegExp` | Matches a string of one or more spaces and nothing else. |
156
+
157
+ An interpretation the compiler reads is foreign data, and a class instance carries its contract
158
+ on the prototype, so `BriefCompiler` materializes the members `INTERPRETATION_MEMBERS` names into
159
+ a frozen view of its own. The list is pinned to `keyof Interpretation`: a name
160
+ `@orkestrel/interpret` does not publish fails the compile, and a member it adds fails the equality
161
+ assertion in [`BriefCompiler.test.ts`](../tests/src/core/BriefCompiler.test.ts).
162
+
163
+ ```ts
164
+ import {
165
+ DEFAULT_BRIEF_TURNS,
166
+ GATE_ID,
167
+ INTERPRETATION_MEMBERS,
168
+ OUTPUT_FORMATS,
169
+ RISK_SEVERITIES,
170
+ TASK_DOMAINS,
171
+ TASK_OPERATIONS,
172
+ } from '@orkestrel/brief'
173
+
174
+ TASK_OPERATIONS.length // 12
175
+ TASK_DOMAINS // ['code', 'writing', 'research', 'analysis', 'design', 'data', 'ops', 'other']
176
+ OUTPUT_FORMATS // ['markdown', 'json', 'code', 'diff', 'prose']
177
+ RISK_SEVERITIES // ['low', 'medium', 'high']
178
+ DEFAULT_BRIEF_TURNS // 16
179
+ GATE_ID // 'gate'
180
+ INTERPRETATION_MEMBERS.includes('subject') // true — the optional members are captured too
181
+ ```
182
+
183
+ A closed-set field that does not fit a listed value is a signal the request is mis-scoped,
184
+ not licence to invent a value: the validators reject an off-vocabulary literal and the
185
+ shapers compile the same tuples into the JSON Schema `enum`s, so the vocabulary cannot
186
+ drift between the guard and the schema.
187
+
188
+ ### Errors
189
+
190
+ | API | Kind | Summary |
191
+ | -------------- | -------- | --------------------------------------------------- |
192
+ | `BriefError` | class | Represents the one error class this package throws. |
193
+ | `isBriefError` | function | Narrows a caught value to a `BriefError`. |
194
+
195
+ `BriefError` extends `Error` with a readonly `code` on the `BriefErrorCode` vocabulary and an
196
+ optional readonly `context` record.
197
+
198
+ ```ts
199
+ import { BriefError, isBriefError } from '@orkestrel/brief'
200
+
201
+ try {
202
+ throw new BriefError('INVALID', 'Brief failed the exact-record contract')
203
+ } catch (error) {
204
+ if (isBriefError(error)) error.code // 'INVALID'
205
+ }
206
+ ```
207
+
208
+ Throws are reserved for caller misuse: `assertBrief` and `snapshotBrief` throw `INVALID` on
209
+ data the contract refuses, and any method after `destroy()` throws `DESTROYED`. Blocking gaps
210
+ are not an error — the gate fails closed into an incomplete `Briefing` whose `failures` carry
211
+ a `BLOCKED` marker, mirroring interprets `NO_TEMPLATE` visible-incomplete outcome.
212
+
213
+ Each code carries the `context` it can actually supply, and they are not uniform: `DRAFT_FAILED`
214
+ carries `{ stage, field }`, `GATE_FAILED` carries `{ stage, field, reasoning }`, `INVALID`
215
+ carries `{ field }` plus the offending value where the code has one — the hash on a
216
+ content-hash collision — and `DESTROYED` carries none.
217
+
218
+ ### Validators
219
+
220
+ Total guards composed from the `@orkestrel/contract` combinators. Adversarial input — junk,
221
+ cycles, hostile prototypes — returns `false`, never throws. Every record guard is exact: an
222
+ extra key fails, which is why the following builders omit absent optional keys. A key that is
223
+ present but holds `undefined` also fails, matching this workspace's
224
+ `exactOptionalPropertyTypes` contract.
225
+
226
+ Exactness stops at this package's own records. The gate checks a borrowed engine's return with
227
+ `@orkestrel/reason`'s published result guards — open on unknown keys, class instances accepted —
228
+ imported rather than reimplemented here. reason deliberately accepts an empty rule id, because
229
+ `RuleResult.id` is `string` and a non-empty check would narrow past the published contract; a
230
+ consumer wanting that stricter reading asserts it at its own boundary. The interpret stage
231
+ guards the same way with `@orkestrel/interpret`'s published `isInterpretation`, at both of its
232
+ doors: the borrowed engine's return and a caller-supplied `interpretation`. A malformed value at
233
+ either door records `INTERPRET_FAILED` instead of throwing out of `compile`.
234
+
235
+ In a guard table a `Shape` cell holds the type the guard narrows to.
236
+
237
+ | API | Kind | Shape | Summary |
238
+ | ----------------- | ----- | --------------- | ------------------------------------------------------------------------------------------------ |
239
+ | `isText` | const | `string` | Checks whether the value is a string holding no line terminator, empty included. |
240
+ | `isLine` | const | `string` | Checks whether the value is a non-empty string holding no line terminator. |
241
+ | `isTaskOperation` | const | `TaskOperation` | Checks whether the value is one of the `TaskOperation` literals. |
242
+ | `isTaskDomain` | const | `TaskDomain` | Checks whether the value is one of the `TaskDomain` literals. |
243
+ | `isOutputFormat` | const | `OutputFormat` | Checks whether the value is one of the `OutputFormat` literals. |
244
+ | `isRiskSeverity` | const | `RiskSeverity` | Checks whether the value is one of the `RiskSeverity` literals. |
245
+ | `isTask` | const | `Task` | Checks whether the value is a well-formed `Task` — both vocabularies closed, statement one line. |
246
+ | `isReference` | const | `Reference` | Checks whether the value is a well-formed `Reference` — both members required, both single-line. |
247
+ | `isManifest` | const | `Manifest` | Checks whether the value is a well-formed `Manifest`. |
248
+ | `isOutcome` | const | `Outcome` | Checks whether the value is a well-formed `Outcome` — `rank` a positive integer. |
249
+ | `isGiven` | const | `Given` | Checks whether the value is a well-formed `Given` — its `value` may be empty but stays one line. |
250
+ | `isExample` | const | `Example` | Checks whether the value is a well-formed `Example`. |
251
+ | `isCitation` | const | `Citation` | Checks whether the value is a well-formed `Citation` — every member single-line. |
252
+ | `isGap` | const | `Gap` | Checks whether the value is a well-formed `Gap`. |
253
+ | `isRisk` | const | `Risk` | Checks whether the value is a well-formed `Risk` — `severity` on the closed vocabulary. |
254
+ | `isOutput` | const | `Output` | Checks whether the value is a well-formed `Output` — `format` on the closed vocabulary. |
255
+ | `isProof` | const | `Proof` | Checks whether the value is a well-formed `Proof`. |
256
+ | `isBrief` | const | `Brief` | Checks whether the value satisfies the whole exact-record `Brief` contract. |
257
+
258
+ ```ts
259
+ import {
260
+ isBrief,
261
+ isGap,
262
+ isProof,
263
+ isTask,
264
+ isTaskDomain,
265
+ isTaskOperation,
266
+ isCitation,
267
+ isExample,
268
+ isGiven,
269
+ isManifest,
270
+ isOutcome,
271
+ isOutput,
272
+ isOutputFormat,
273
+ isReference,
274
+ isRisk,
275
+ isRiskSeverity,
276
+ } from '@orkestrel/brief'
277
+
278
+ isTask({ operation: 'refactor', domain: 'code', statement: 'Refactor useForm.' }) // true
279
+ isTask({ operation: 'improve', domain: 'code', statement: 'x' }) // false — off-vocabulary
280
+ isTaskOperation('refactor') // true
281
+ isTaskDomain('frontend') // false
282
+ isReference({ path: 'AGENTS.md', note: 'project law' }) // true
283
+ isReference({ path: 'AGENTS.md' }) // false — `note` is required
284
+ isManifest({ read: [], edit: [], locked: [], forbidden: [] }) // true
285
+ isOutcome({ rank: 1, text: 'the tests pass', required: true }) // true
286
+ isGiven({ category: 'convention', name: 'indentation', value: 'tabs' }) // true
287
+ isExample({ input: '<input required>', output: 'el.validity' }) // true
288
+ isCitation({ name: 'MDN', url: 'https://developer.mozilla.org/', note: 'native validity' }) // true
289
+ isGap({ field: 'output', question: 'Diff or files?', blocking: true, candidates: ['diff'] }) // true
290
+ isRisk({ severity: 'medium', text: 'subtle drift', mitigation: 'assert in tests' }) // true
291
+ isRiskSeverity('medium') // true
292
+ isOutput({ format: 'diff' }) // true
293
+ isOutputFormat('diff') // true
294
+ isProof({ text: 'tests pass', command: 'npm run test:src:core' }) // true
295
+ isBrief({ task: { operation: 'plan', domain: 'ops', statement: 'x.' } }) // false — sections missing
296
+ ```
297
+
298
+ ### Shapers
299
+
300
+ The `Brief` contract declared a second time as a contracts `ContractShape`, because a shape
301
+ buys what a guard cannot: the JSON Schema a tool boundary needs, a seeded generator
302
+ for test data, and per-field diagnostics through `explain`. Each shaper is a plain shape
303
+ value; `briefShape` composes the section shapes, and `createBriefContract()` compiles it.
304
+
305
+ The guard family and the shape family are independent mechanisms over one vocabulary.
306
+ That is deliberate: a single source could not disagree with itself, so it could never catch
307
+ a mistake. [`tests/src/core/shapers.test.ts`](../tests/src/core/shapers.test.ts) drives both
308
+ over the same values and fails the moment they diverge, and `createBriefContract`'s declared
309
+ `ContractInterface<Brief>` return type makes the compiler prove `Infer<typeof briefShape>`
310
+ is exactly `Brief`.
311
+
312
+ A `Shape` cell holds the constant's declared type.
313
+
314
+ | API | Kind | Shape | Summary |
315
+ | ---------------- | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
316
+ | `textShape` | const | `StringShape` | Describes a single-line string of any length, including empty — the shape mirror of `isText`. |
317
+ | `lineShape` | const | `StringShape` | Describes a non-empty single-line string — the shape mirror of `isLine`. |
318
+ | `taskShape` | const | `ObjectShape<{ operation, domain, statement }>` | Describes the `Task` shape — closed operation and domain vocabularies plus a non-empty statement. |
319
+ | `referenceShape` | const | `ObjectShape<{ path, note }>` | Describes the `Reference` shape — a path and the note that justifies listing it. |
320
+ | `manifestShape` | const | `ObjectShape<{ read, edit, locked, forbidden }>` | Describes the `Manifest` shape — disjoint reference partitions. |
321
+ | `outcomeShape` | const | `ObjectShape<{ rank, text, required }>` | Describes the `Outcome` shape — a one-based rank, the result text, and whether it gates done. |
322
+ | `givenShape` | const | `ObjectShape<{ category, name, value }>` | Describes the `Given` shape — one categorized context fact. |
323
+ | `exampleShape` | const | `ObjectShape<{ input, output, note? }>` | Describes the `Example` shape — one input to output exemplar. |
324
+ | `citationShape` | const | `ObjectShape<{ name, url, note }>` | Describes the `Citation` shape — a name, a locator, and why the source is cited. |
325
+ | `gapShape` | const | `ObjectShape<{ field, question, blocking, candidates? }>` | Describes the `Gap` shape — an unknown, whether it blocks, and the candidates that would close it. |
326
+ | `riskShape` | const | `ObjectShape<{ severity, text, mitigation }>` | Describes the `Risk` shape — a closed severity, the risk, and its mitigation. |
327
+ | `outputShape` | const | `ObjectShape<{ format, sections?, include?, exclude? }>` | Describes the `Output` shape — a closed format plus its optional refinements. |
328
+ | `proofShape` | const | `ObjectShape<{ text, command }>` | Describes the `Proof` shape — the claim and the command that settles it. |
329
+ | `briefShape` | const | `ObjectShape<{ task, authority, manifest, outcomes, rules, invariants, givens, examples, assumptions, citations, gaps, risks, output, proofs, trace?, hash? }>` | Describes the whole `Brief` shape, section shapes composed. |
330
+
331
+ ```ts
332
+ import {
333
+ briefShape,
334
+ citationShape,
335
+ createBriefContract,
336
+ exampleShape,
337
+ gapShape,
338
+ givenShape,
339
+ manifestShape,
340
+ outcomeShape,
341
+ outputShape,
342
+ proofShape,
343
+ referenceShape,
344
+ riskShape,
345
+ taskShape,
346
+ } from '@orkestrel/brief'
347
+ import { createContract, schemaToParameters, seededRandom } from '@orkestrel/contract'
348
+
349
+ const contract = createBriefContract()
350
+ contract.schema // the full JSON Schema — hand it to a tool boundary
351
+ contract.generate(seededRandom(42)) // a reproducible, on-contract seed brief for tests
352
+ schemaToParameters(contract.schema) // the open tool-parameters record, no `as` anywhere
353
+
354
+ createContract(taskShape).is({ operation: 'plan', domain: 'ops', statement: 'Plan it.' }) // true
355
+ briefShape.category // 'object'
356
+ referenceShape.category // 'object'
357
+ manifestShape.category // 'object'
358
+ outcomeShape.category // 'object'
359
+ givenShape.category // 'object'
360
+ exampleShape.category // 'object'
361
+ citationShape.category // 'object'
362
+ gapShape.category // 'object'
363
+ riskShape.category // 'object'
364
+ outputShape.category // 'object'
365
+ proofShape.category // 'object'
366
+ ```
367
+
368
+ ### Builders
369
+
370
+ Value builders — every builder returns a fresh object and omits absent optional keys entirely,
371
+ so its shape round-trips the exact-record validators named earlier.
372
+
373
+ Builders are structural, not validating: they adopt whatever they are handed. `buildReference('a',
374
+ '')` and `buildCitation('MDN', 'https://x', 'a\nb')` both return a typed value the matching guard
375
+ rejects, because the single-line and non-empty contracts bind where a guard runs rather than
376
+ where a value is built. That is deliberate and uniform across every builder — validation lives
377
+ at the boundaries the data actually crosses: `parseBrief` on the way in, `snapshotBrief` inside
378
+ every projection, and the gate before emission. Pass on-contract arguments, or check the
379
+ result.
380
+
381
+ | API | Kind | Summary |
382
+ | --------------------- | -------- | -------------------------------------------------------------------------------------------- |
383
+ | `buildTask` | function | Assembles a `Task` from an operation, a domain, and a statement. |
384
+ | `buildReference` | function | Assembles a `Reference` from a path and the note that justifies listing it. |
385
+ | `buildManifest` | function | Assembles a `Manifest`, defaulting every absent partition to an empty list. |
386
+ | `buildOutcome` | function | Assembles an `Outcome` from a rank and its result text. |
387
+ | `buildGiven` | function | Assembles a `Given` from a category, a name, and a value. |
388
+ | `buildExample` | function | Assembles an `Example` from an exemplar input and its expected output. |
389
+ | `buildCitation` | function | Assembles a `Citation` from a name, a URL, and the note that justifies citing it. |
390
+ | `buildGap` | function | Assembles a `Gap` from the section it belongs to and the question that would close it. |
391
+ | `buildRisk` | function | Assembles a `Risk` from a severity, what could go wrong, and the mitigation that answers it. |
392
+ | `buildOutput` | function | Assembles an `Output` from a format plus its optional refinements. |
393
+ | `buildProof` | function | Assembles a `Proof` from what the check settles and the command that settles it. |
394
+ | `buildBrief` | function | Assembles a `Brief` from a `Task` plus section overrides. |
395
+ | `buildGateDefinition` | function | Assembles the fail-closed readiness gate as a reasons `LogicalDefinition`. |
396
+
397
+ ```ts
398
+ import {
399
+ buildBrief,
400
+ buildCitation,
401
+ buildExample,
402
+ buildGap,
403
+ buildGateDefinition,
404
+ buildGiven,
405
+ buildManifest,
406
+ buildOutcome,
407
+ buildOutput,
408
+ buildProof,
409
+ buildReference,
410
+ buildRisk,
411
+ buildTask,
412
+ } from '@orkestrel/brief'
413
+
414
+ const draft = buildBrief(
415
+ buildTask('refactor', 'code', 'Refactor useForm to native browser form APIs.'),
416
+ {
417
+ authority: [buildReference('AGENTS.md', 'project law; wins every conflict')],
418
+ manifest: buildManifest({
419
+ read: [
420
+ // Ranked authority must also be granted here — the `granted` rule refuses a brief
421
+ // that tells the executor to obey a file no partition lets it open.
422
+ buildReference('AGENTS.md', 'project law; wins every conflict'),
423
+ buildReference('guides/browser.md', 'the composable contract'),
424
+ ],
425
+ edit: [
426
+ buildReference('src/browser/composables/useForm.ts', 'the composable being refactored'),
427
+ ],
428
+ locked: [buildReference('src/browser/types.ts', 'the published contract')],
429
+ forbidden: [buildReference('app/**', 'out of scope')],
430
+ }),
431
+ outcomes: [
432
+ buildOutcome(1, 'useForm uses native FormData with no behavior change'),
433
+ buildOutcome(2, 'tests cover the changed code paths'),
434
+ ],
435
+ rules: ['Add no dependencies.'],
436
+ invariants: ['useForm public method names and signatures in types.ts.'],
437
+ givens: [buildGiven('convention', 'indentation', 'tabs')],
438
+ examples: [buildExample('<input required>', 'validity read from el.validity')],
439
+ assumptions: ['Validation message wording is preserved.'],
440
+ citations: [
441
+ buildCitation(
442
+ 'MDN Constraint Validation',
443
+ 'https://developer.mozilla.org/',
444
+ 'the native validity behavior being adopted',
445
+ ),
446
+ ],
447
+ gaps: [buildGap('rules', 'Does validation message wording need to change?')],
448
+ risks: [
449
+ buildRisk('medium', 'native validation differs subtly', 'assert message and state in tests'),
450
+ ],
451
+ output: buildOutput('diff', { include: ['updated useForm.ts'] }),
452
+ proofs: [buildProof('type-check and lint pass', 'npm run check')],
453
+ },
454
+ )
455
+ draft.output.format // 'diff'
456
+ draft.trace // undefined — the pin fills it, never the author
457
+ buildGateDefinition().rules.length // 7 — the readiness rules plus the conjunction
458
+ ```
459
+
460
+ ### Helpers
461
+
462
+ Pure, exported utility functions — the referentially-transparent leaves behind the
463
+ `BriefCompiler` and the projection surface. Projections use the `{noun}To{Noun}` idiom: each
464
+ consumes a whole and returns a derived view of it.
465
+
466
+ | API | Kind | Summary |
467
+ | ------------------------ | -------- | -------------------------------------------------------------------------------------- |
468
+ | `briefToMarkdown` | function | Projects a brief into the copy-ready agent prompt. |
469
+ | `briefToGoal` | function | Projects a brief into a `/goal` completion condition. |
470
+ | `briefToDispatch` | function | Projects a brief into a subagent `Dispatch`. |
471
+ | `briefToSubject` | function | Projects a brief into the reasons `Subject` of readiness measures the gate reads. |
472
+ | `briefToHash` | function | Computes the canonical structural digest of a brief's content. |
473
+ | `briefToTrace` | function | Renders the one-line census `pinBrief` stamps onto a brief. |
474
+ | `briefToContent` | function | Renders the canonical text of exactly what a brief's hash describes. |
475
+ | `findUnmetRules` | function | Lists the readiness rules a brief fails, computed directly from its own measures. |
476
+ | `pinBrief` | function | Returns a fresh brief with `trace` and `hash` derived from its own content. |
477
+ | `snapshotBrief` | function | Returns a deeply owned, deeply frozen copy of a brief, refusing anything off-contract. |
478
+ | `captureValue` | function | Captures one stable, frozen view of a foreign contract value. |
479
+ | `assertBrief` | function | Narrows unknown data to a `Brief`, throwing when it is off-contract. |
480
+ | `exampleToLines` | function | Renders one exemplar as markdown lines. |
481
+ | `validateBrief` | function | Runs the semantic pass over an already-shape-valid brief. |
482
+ | `countSentences` | function | Counts the sentences a statement holds. |
483
+ | `findBlockingGaps` | function | Lists the gaps that block emission. |
484
+ | `findManifestOverlaps` | function | Lists the paths appearing in more than one manifest partition. |
485
+ | `findUngrantedAuthority` | function | Lists the authority paths the manifest never grants access to. |
486
+ | `findUnpairedGaps` | function | Lists the open gaps with no assumption to stand on. |
487
+ | `deriveStatement` | function | Derives one imperative statement from free text. |
488
+ | `deriveTask` | function | Derives a `Task` from an interprets `Intent` through the caller's vocabularies. |
489
+ | `deriveGivens` | function | Derives `Given[]` from an interprets `Entity[]`. |
490
+ | `deriveGaps` | function | Derives `Gap[]` from an interprets `Ambiguity[]`. |
491
+ | `errorToMessage` | function | Renders a value thrown by a stage into a message. |
492
+ | `freezeDeep` | function | Freezes a value and everything reachable from it. |
493
+ | `freezeBranch` | function | Freezes one branch of a value graph, skipping what the visited set already holds. |
494
+
495
+ ```ts
496
+ import {
497
+ briefToDispatch,
498
+ briefToGoal,
499
+ briefToHash,
500
+ briefToTrace,
501
+ briefToMarkdown,
502
+ briefToSubject,
503
+ captureValue,
504
+ countSentences,
505
+ deriveGaps,
506
+ deriveGivens,
507
+ deriveStatement,
508
+ deriveTask,
509
+ errorToMessage,
510
+ freezeDeep,
511
+ findBlockingGaps,
512
+ findUngrantedAuthority,
513
+ findManifestOverlaps,
514
+ findUnpairedGaps,
515
+ pinBrief,
516
+ validateBrief,
517
+ } from '@orkestrel/brief'
518
+
519
+ const pinned = pinBrief(draft)
520
+ pinned.hash // an eight-hex-digit digest of the brief's content, stable across runs
521
+ pinned.trace // 'refactor/code · outcomes:2 · gaps:0/1 · proofs:1' — derived, never authored
522
+ briefToTrace(pinned) === pinned.trace // true — one implementation, and what reconciles an inbound trace
523
+ briefToHash(pinned) === briefToHash(draft) // true — pinning does not move the identity
524
+
525
+ briefToMarkdown(pinned) // '# Brief: Refactor useForm…\n\nrefactor · code\n…'
526
+ briefToGoal(pinned) // 'Done when every proof passes: npm run check exits 0. Cap: 16 turns.'
527
+ briefToGoal(pinned, 12) // the same condition with a 12-turn cap
528
+ briefToDispatch(pinned).edit // ['src/browser/composables/useForm.ts'] — the owned set
529
+ briefToSubject(pinned) // { operation: 'refactor', blocking: 0, outcomes: 2, proofs: 1, … }
530
+
531
+ countSentences('Refactor useForm. Then update the tests.') // 2
532
+ findBlockingGaps(pinned) // [] — safe to emit
533
+ findManifestOverlaps(pinned) // [] — the partitions are disjoint
534
+ findUngrantedAuthority(pinned) // [] — every ranked authority is opened by some partition
535
+ findUnpairedGaps(pinned) // [] — the one open gap has its assumption
536
+ validateBrief(pinned) // { valid: true, errors: [], warnings: [] }
537
+
538
+ deriveStatement(' clean up useForm ') // 'Clean up useForm.'
539
+ deriveTask(
540
+ { action: 'migrate', domain: 'code', confidence: 1 },
541
+ 'migrate the stores',
542
+ { migrate: 'migrate' },
543
+ { code: 'code' },
544
+ ) // { operation: 'migrate', domain: 'code', statement: 'Migrate the stores.' }
545
+ deriveGivens([{ name: 'count', value: 3, provenance: { category: 'extracted' }, confidence: 1 }]) // [{ category: 'extracted', name: 'count', value: '3' }]
546
+ deriveGaps([{ field: 'output', question: 'Diff or files?', candidates: [], required: true }])
547
+ errorToMessage(new Error('boom')) // 'boom'
548
+ errorToMessage(Object.create(null)) // 'an unreadable object was thrown' — never throws
549
+ freezeDeep({ outcomes: [{ rank: 1 }] }) // nested array frozen too, unlike Object.freeze
550
+
551
+ const leaf = () => 'ready'
552
+ const captured = captureValue({ leaf }, ['leaf'])
553
+ Object.isFrozen(captured) // true — the view a Briefing replays
554
+ Reflect.get(Object(captured), 'leaf') === leaf // true — an uncloneable leaf keeps its identity
555
+ ```
556
+
557
+ `validateBrief` errors on the structural violations no assumption can paper over — a
558
+ manifest overlap (`Path "<path>" appears in more than one manifest partition`), an authority
559
+ no partition grants access to (`Authority "<path>" is in no manifest partition that grants
560
+ access — the executor cannot obey what it cannot open`), an empty `proofs` list, and a
561
+ `task.statement` that is not one sentence (`Statement holds <n> sentences — a compound
562
+ statement is two briefs`). It warns on the runnable-but-suspicious: duplicate outcome ranks,
563
+ an open gap without its paired assumption, and an optional outcome ranked above every
564
+ required one.
565
+
566
+ The grant check reads `read`, `edit`, and `locked` — all of them open a file, and read-only is
567
+ exactly what obeying one requires. It subsumes the narrower question of an authority sitting
568
+ in `forbidden`, because the partitions are disjoint, and it also catches the case a
569
+ forbidden-only check cannot see: an authority the manifest never mentions at all, where the
570
+ brief never says the executor may open what it must obey.
571
+
572
+ Both path checks compare exact strings and never expand a glob. `read: 'guides/**'` does not
573
+ grant `authority: 'guides/brief.md'`, and `forbidden: 'app/**'` does not overlap
574
+ `edit: 'app/file.ts'`. Disjointness and grant are properties of the written paths, so state a
575
+ grant as the same literal path the authority carries.
576
+
577
+ ### Parsers
578
+
579
+ | API | Kind | Summary |
580
+ | ------------ | -------- | ------------------------------------ |
581
+ | `parseBrief` | function | Parses a JSON string into a `Brief`. |
582
+
583
+ ```ts
584
+ import { parseBrief } from '@orkestrel/brief'
585
+
586
+ parseBrief('not json') // undefined
587
+ parseBrief(JSON.stringify(pinned))?.hash === pinned.hash // true — briefs round-trip JSON
588
+ ```
589
+
590
+ Coerce a bare vocabulary value with `parseEnum` from `@orkestrel/contract` against the
591
+ exported tuple; this package ships no rename-wrapper around it.
592
+
593
+ `parseBrief` is the intake half that is owned by construction: its argument is text, so the
594
+ value it returns is a graph built inside the call, carrying no caller identity, no accessor,
595
+ and no alias back to anything the caller holds. `assertBrief` is the other half, and it narrows
596
+ by identity without taking ownership — the value that comes back is the caller's own object,
597
+ whose accessors can still answer a later reader differently. Pass `assertBrief` a value you
598
+ already own, and cross `snapshotBrief` when you want intake to own it for you.
599
+
600
+ ### Factories
601
+
602
+ | API | Kind | Summary |
603
+ | --------------------- | -------- | ------------------------------------------------------------------------------------- |
604
+ | `createBriefCompiler` | function | Creates a compilation orchestrator. |
605
+ | `createBriefManager` | function | Creates a brief registry. |
606
+ | `createBriefContract` | function | Compiles `briefShape` into a guard, parser, JSON Schema, and seeded generator bundle. |
607
+
608
+ Every entry here builds something. `assertBrief` constructs nothing — it returns its
609
+ argument after the guard passes — so it is a helper, not a factory, and lives beside
610
+ the other pure leaves.
611
+
612
+ ```ts
613
+ import {
614
+ assertBrief,
615
+ createBriefContract,
616
+ createBriefManager,
617
+ createBriefCompiler,
618
+ } from '@orkestrel/brief'
619
+
620
+ const compiler = createBriefCompiler() // owns a default interpret pipeline and a logical Reason
621
+ compiler.destroy()
622
+
623
+ const briefs = createBriefManager()
624
+ briefs.count // 0
625
+ briefs.destroy()
626
+
627
+ createBriefContract().is(pinned) // true
628
+ assertBrief(pinned) === pinned // true — narrowed by identity, never rebuilt
629
+ ```
630
+
631
+ `createBriefCompiler` wires its engines when the caller supplies none: a default
632
+ `createInterpret()` — empty vocabularies, so the caller's `actions` and `domains` drive
633
+ `deriveTask` — and a `createReason({ reasoners: [createLogicalReasoner()] })` dedicated to
634
+ the gate. Bring your own through `BriefCompilerOptions.interpret` / `BriefCompilerOptions.reason` to
635
+ share instances or observe their emitters; the compiler destroys only the instances it
636
+ created.
637
+
638
+ ### Classes
639
+
640
+ | API | Kind | Summary |
641
+ | --------------- | ----- | --------------------------------------------------------------------------------------- |
642
+ | `BriefCompiler` | class | Implements the compilation orchestrator — the `[interpret, draft, gate, pin]` pipeline. |
643
+ | `BriefManager` | class | Implements the self-owning, versioned and content-hashed brief registry. |
644
+
645
+ ## Methods
646
+
647
+ The public methods of each behavioral interface — one table per type, keyed by its
648
+ backticked name, every call-signature member listed. The `readonly` data members
649
+ (`emitter` / `interpret` / `reason` on `BriefCompiler`; `emitter` / `count` on `BriefManager`)
650
+ stay in the earlier Surface rows. Each implementing class exposes exactly its interface's
651
+ methods, so this doubles as the per-instance method surface.
652
+
653
+ #### `BriefCompilerInterface`
654
+
655
+ `compile` is genuinely synchronous. After `destroy()` every method except the getters and
656
+ `destroy` itself throws `BriefError('DESTROYED', …)`; `destroy()` is idempotent, cascades to
657
+ the owned engines first — never a borrowed one — and tears the emitter down last.
658
+
659
+ | Method | Returns | Summary |
660
+ | --------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------- |
661
+ | `compile` | `Briefing` | Runs the `interpret` → `draft` → `gate` → `pin` pipeline over a `BriefInput`, returning a complete or visible-incomplete result. |
662
+ | `gate` | `LogicalResult` | Evaluates one brief's readiness through the reasons gate — `briefToSubject` against `buildGateDefinition()`. |
663
+ | `destroy` | `void` | Tears the orchestrator down idempotently — owned engines first, the emitter last. |
664
+
665
+ ```ts
666
+ import {
667
+ BriefCompiler,
668
+ buildBrief,
669
+ buildOutcome,
670
+ buildProof,
671
+ buildTask,
672
+ createBriefCompiler,
673
+ } from '@orkestrel/brief'
674
+
675
+ const audit = buildBrief(buildTask('audit', 'code', 'Audit the barrel for undocumented exports.'), {
676
+ outcomes: [buildOutcome(1, 'every export appears in the guide')],
677
+ proofs: [buildProof('parity passes', 'npm run test:guides')],
678
+ })
679
+
680
+ const engine = createBriefCompiler()
681
+ const verdict = engine.gate(audit)
682
+ verdict.conclusion // true — no blocking gaps, outcomes and proofs present, manifest disjoint
683
+ verdict.trace // the step-by-step readiness account, straight from the LogicalReasoner
684
+
685
+ const briefing = engine.compile({
686
+ task: audit.task,
687
+ outcomes: audit.outcomes,
688
+ proofs: audit.proofs,
689
+ })
690
+ briefing.brief !== undefined // true
691
+ briefing.brief?.hash // pinned
692
+ briefing.stages.map((record) => record.stage) // ['draft', 'gate', 'pin'] — no text, no interpret
693
+ engine.destroy()
694
+
695
+ new BriefCompiler().destroy() // the class is public; the factory is the ordinary entry point
696
+ ```
697
+
698
+ #### `BriefManagerInterface`
699
+
700
+ The self-owning, ordered registry over briefs. `add` derives each record's `hash` from the
701
+ brief's content and bumps `version` only when that hash changes; an absent id defaults to
702
+ the content hash itself, so minting is deterministic with no randomness. The array overload
703
+ of `remove` is declared first so an id list resolves to the batch form, and it returns
704
+ `true` only when every listed id was present. A call after `destroy()` throws
705
+ `BriefError('DESTROYED', …)`.
706
+
707
+ | Method | Returns | Summary |
708
+ | --------- | -------------------------- | ------------------------------------------------------------------------------------------- |
709
+ | `has` | `boolean` | Reports whether a brief with the given id is registered. |
710
+ | `brief` | `BriefRecord \| undefined` | Looks up one registered brief record by id. |
711
+ | `briefs` | `readonly BriefRecord[]` | Lists every registered brief record. |
712
+ | `add` | `BriefRecord` | Registers one brief from its data, minting the id from its content hash when none is given. |
713
+ | `remove` | `boolean` (or `void`) | Removes the listed briefs by id, one brief by id, or every brief. |
714
+ | `destroy` | `void` | Tears the registry down idempotently — the collection first, the emitter last. |
715
+
716
+ ```ts
717
+ import { BriefManager, buildBrief, buildTask, createBriefManager } from '@orkestrel/brief'
718
+
719
+ const registry = createBriefManager()
720
+ const record = registry.add(buildBrief(buildTask('document', 'writing', 'Write the brief guide.')))
721
+ record.id === record.hash // true — id minted from content, deterministic
722
+ record.version // 1
723
+ registry.has(record.id) // true
724
+ registry.brief(record.id) // the BriefRecord, or undefined
725
+ registry.briefs() // every registered record
726
+ registry.remove([record.id]) // true — the batch form, declared first
727
+ registry.destroy()
728
+
729
+ new BriefManager().destroy() // the class is public; the factory is the ordinary entry point
730
+ ```
731
+
732
+ ## Contract
733
+
734
+ These invariants hold across `src/core` and this guide:
735
+
736
+ 1. **Doc and source bijection.** Every `function` / `class` / `const` / `interface` / `type`
737
+ row in the `## Surface` tables is a real export of the brief source tree, and every export
738
+ appears as a Surface row — exhaustive, both directions. Adding, renaming, or removing an
739
+ export breaks the parity gate until the doc is reconciled.
740
+ 2. **Deterministic, synchronous, immutable.** This module adds no nondeterminism: no clocks,
741
+ no randomness, no I/O, nothing async. The same `BriefInput` therefore produces the same
742
+ `Briefing` for as long as the engines do — which is unconditional for the engines the
743
+ compiler wires itself, and inherited for a borrowed `interpret` or `reason`. A stateful
744
+ supplied engine that answers differently on a later call makes `compile` answer differently
745
+ too, and that is the engine's determinism, not this module's.
746
+
747
+ Within one call the answer cannot drift, because how many times a foreign object is read is
748
+ this module's decision rather than the engine's: every value an engine returns is owned at
749
+ arrival — copied where a structured clone can carry it, captured into a frozen plain view
750
+ where it cannot — and every later read is of that copy or that view, never of the engine's
751
+ object. A verdict whose members answer differently on a second read produces the same
752
+ `Briefing` as one returning those first answers as plain data. The
753
+ earlier wording said "on its second call" while the module was in fact reading one returned
754
+ object twice, which made a sentence about the engine's determinism cover a defect in this
755
+ module's.
756
+ `pinBrief`'s `trace` and `hash` derive from content alone — the hash is the canonical
757
+ structural digest interprets `digestValue` computes — and the `BriefManager` mints record
758
+ ids from that hash, so re-adding unchanged content is a version no-op. No input is ever
759
+ mutated. A builder adopts the collections it is handed, so a draft still aliases the
760
+ caller's arrays; `snapshotBrief` is the boundary that ends that, and `pinBrief` and
761
+ `BriefManager.add` both cross it. Past that point the brief is a deeply frozen
762
+ null-prototype record sharing no reference with its source, which is what lets a hash keep
763
+ describing its brief for as long as the brief exists.
764
+
765
+ The digest is eight hex digits — 32 bits — so it is an identity for a working set, not a
766
+ cryptographic one. Read that as a birthday bound rather than a threshold: collisions become
767
+ likely in the tens of thousands of distinct briefs, around a 50% chance near 77,000, and
768
+ independent searches over this package's own builders hit their first pair at roughly
769
+ 42,000 and 53,000. Those are draws from one distribution, not a limit — quoting either
770
+ as the safe size is the mistake, and an earlier revision of this paragraph quoted a single
771
+ measured index that overstated the safe scale by more than an order of magnitude.
772
+
773
+ `BriefManager` therefore compares `briefToContent` before calling a re-add unchanged, and
774
+ refuses a collision with `BriefError('INVALID', …)` rather than silently replacing the
775
+ record already stored. Give a registry expecting more than a few thousand distinct briefs
776
+ its own ids through `RecordOptions.id` rather than letting the digest mint them.
777
+
778
+ 3. **Fail closed at the gate, and the gate is not delegable.** Readiness is decided by
779
+ `findUnmetRules` — in code, from the brief's own measures — before any verdict is consulted.
780
+ `buildGateDefinition()` states the same readiness rules as data so a reasoner can narrate them, and
781
+ a narration is not a decision: `BriefCompilerOptions.reason` lets a caller supply the engine,
782
+ and a supplied engine can add detail to a refusal but can never turn one into a pass. A
783
+ test drives the data and the code over one value set, which is what holds them together.
784
+
785
+ A non-empty `findBlockingGaps` yields an absent `brief`, the questions
786
+ on `Briefing.questions`, and a `BriefStageFailure` coded `BLOCKED` — never a throw, never a
787
+ half-specified brief. That holds even when the gate itself throws and leaves no verdict to
788
+ name rules from: the `BLOCKED` marker is keyed on the blocking gaps, not on the verdict's
789
+ presence. Alongside the decision, `buildGateDefinition()` is evaluated against
790
+ `briefToSubject(brief)` by a `LogicalReasoner`, so every verdict carries the reasoner's own
791
+ `trace` on `Briefing.verdict` and readiness is auditable, replayable data — and
792
+ `BriefStageRecord` is discriminated by `stage`, so that replay is readable without a single
793
+ type assertion. The measures and the verdict are conjoined in the refusing direction: a
794
+ brief must satisfy each, so a borrowed engine can withhold a pass it cannot grant.
795
+
796
+ 4. **The brief is the source of truth; projections never add.** `briefToMarkdown`,
797
+ `briefToGoal`, and `briefToDispatch` are pure views over the pinned brief: the prompt
798
+ references paths and never inlines file contents, the goal condition is the proofs'
799
+ commands verbatim plus the turn cap, the dispatch's owned set is exactly `manifest.edit`,
800
+ and its `authority` is exactly `brief.authority` in rank order. The dispatch therefore says
801
+ in data every path the prompt says in prose, so a machine consumer never parses the
802
+ rendering to decide what it may touch or what wins. Everything else the prompt carries —
803
+ outcomes, the proofs' commands, gaps, risks, the output shape, each path's `note` — stays
804
+ in the prompt, which is written for a model; a consumer wanting those reads the `Brief`.
805
+ The artifacts cannot disagree with the contract or each other, and that
806
+ is enforced in the data rather than in the renderer: every brief string is one line
807
+ (`isLine` / `isText`), so no field can carry the line break that would forge a heading or
808
+ a second manifest row. An `Example`'s two sides are the single exception, because they
809
+ carry code — `exampleToLines` fences them, which makes them content rather than structure.
810
+ 5. **Mechanism, never policy.** The module decides nothing about a task: `deriveTask` maps
811
+ intents only through caller vocabularies and refuses an inherited key, and the draft stage
812
+ merges caller sections over derived ones so the user is never overridden. An open gap is
813
+ meant to proceed on a recorded assumption, and `findUnpairedGaps` measures that as a
814
+ count rather than a relation — a brief carries no link from a gap to the assumption that
815
+ answers it. A count is not a contract, so it advises through `validateBrief` and does not
816
+ gate: one assumption answering two related gaps would otherwise be refused for arithmetic.
817
+ What ships is the closed vocabularies, the exact contract, the gate, the pin, and the
818
+ projections.
819
+ 6. **Guard totality and mechanism parity.** Every validator is a total guard: adversarial
820
+ input returns `false`, never throws. The hand-composed guards and the compiled shape
821
+ contract are independent mechanisms over one vocabulary, held in lockstep by
822
+ [`tests/src/core/shapers.test.ts`](../tests/src/core/shapers.test.ts), which drives both
823
+ over one value set. That test is the guarantee, and it is a real one: adding a property to
824
+ `briefShape` that `Brief` does not have turns its assertions red.
825
+ `createBriefContract`'s declared `ContractInterface<Brief>` return type is a weaker
826
+ companion, not a proof of equality — assignability runs one way over `T`'s output
827
+ positions, so it catches a shape that infers too little and would accept one that infers
828
+ too much.
829
+ 7. **Coded errors.** Every throw out of this module is a `BriefError` with a machine-readable
830
+ code, and `catch` blocks narrow with `isBriefError`, never `as`. The `context` is whatever
831
+ the code can actually supply rather than a fixed pair: `DRAFT_FAILED` carries
832
+ `{ stage, field }`, `GATE_FAILED` adds `reasoning`, `INVALID` carries `{ field }` plus the
833
+ offending value where there is one,
834
+ and `DESTROYED` carries none. Stage failures inside `compile` are contained as
835
+ `BriefStageFailure` entries on the `Briefing`, reserving throws for caller misuse.
836
+ 8. **Observation is a pure side-channel.** The `BriefCompiler` owns a typed emitter
837
+ (`compile` / `block` / `error` / `destroy`); the `BriefManager` owns its own
838
+ (`add` / `remove` / `destroy`). Every event is emitted directly and synchronously, after
839
+ the outcome it reports. A complete briefing emits `compile` and an incomplete one emits
840
+ `block` instead — exactly one of the two, every call. Listener isolation is the emitter's
841
+ own: a throwing listener routes to the `error` option handler, never onto the domain map.
842
+ `destroy()` is idempotent and tears the emitter down last.
843
+ 9. **Doc and source method bijection.** Every behavioral interface's `## Methods` table lists
844
+ exactly its public methods — exhaustive, both directions — and each implementing class
845
+ exposes the same public methods, no more.
846
+ 10. **A borrowed engine is the caller's code, not an attacker.** `BriefCompilerOptions.interpret`
847
+ and `.reason` are seams, and what crosses them is owned, validated, and read once: own at
848
+ arrival — copied where a structured clone can carry the value, captured into a frozen plain
849
+ view where it cannot; validate the owned view; never read a foreign object twice. How many
850
+ times a foreign object is read is this module's decision, so a briefing never depends on it.
851
+
852
+ `captureValue` is the capture arm, and what it produces is what the `Briefing` replays. It
853
+ rebuilds the root and every reachable plain container from own enumerable members, keeps
854
+ unknown own members, materializes each published member absent from that own set by reading
855
+ it once, and keeps an uncloneable leaf — a function, for one — by identity. A member the
856
+ caller's prototype carries and the published contract does not name drops out of the capture,
857
+ and it never survived a structured clone either, so the arms agree on what a `Briefing`
858
+ carries.
859
+
860
+ The law reaches values this module pulls across a seam it called, and stops there.
861
+ `assertBrief` and `parseBrief` are intake narrowing, outside it: `assertBrief` returns the
862
+ caller's own object by identity and takes no ownership of it, and `parseBrief` is owned by
863
+ construction because its argument is text. `snapshotBrief` is the ownership door on that
864
+ side, and a caller who wants intake to own its value takes it.
865
+
866
+ Bounding the law the other way, and equally load-bearing: never narrow past the published
867
+ contract. `Entity.value` is `unknown` and `LogicalResult` is an interface a class instance
868
+ satisfies, so a value JSON cannot express is on-contract and is captured rather than refused.
869
+ Every defect this seam produced came from narrowing it — an exact guard that failed the gate
870
+ closed on a valid engine, and a JSON clone that turned a correct refusal into an emitted
871
+ brief.
872
+
873
+ Both engines' returns are shape-checked with their packages' published guards: the verdict
874
+ with reasons' `isLogicalResult`, and the `Interpretation` with interprets'
875
+ `isInterpretation` at both of the stage's doors. A supplied interpretation whose snapshot
876
+ copy loses prototype-carried members is captured rather than refused, because the published
877
+ contract is wider than the copy mechanism.
878
+
879
+ Deliberately absent: a relation or graph layer over briefs (a brief's links are one ranked
880
+ list and the disjoint partitions — order and set membership, fully served by the guards,
881
+ `findManifestOverlaps`, and the gate), asynchronous compilation, brief persistence
882
+ (`JSON.stringify` out, `parseBrief` back in), a turn cap on `BriefCompilerOptions` (nothing in the
883
+ pipeline renders a goal, so the cap is `briefToGoal`'s argument), glob expansion (both path
884
+ checks compare exact strings, and a glob engine is a dependency this package will not take),
885
+ a guard or shape for any projection (`Briefing`, `BriefStageRecord`, `BriefRecord`, and
886
+ `Dispatch` have neither, because a guard narrows untrusted inbound data and nothing accepts
887
+ those — the published round trip is the `Brief`, through `JSON.stringify` and `parseBrief`,
888
+ and a consumer derives its own dispatch locally), URL validation on `Citation.url` (see its
889
+ TSDoc: a `pattern` there makes the seeded generator throw for the whole brief, and the
890
+ working mechanisms beat one stricter member), and any LLM invocation — the authoring
891
+ judgment is the caller's.
892
+
893
+ `Dispatch` carries the authority and permission axes, and reading it as one flat set of
894
+ partitions is the one way to get it wrong. Permission is `read` / `edit` / `locked` / `forbidden`, flattened 1:1 from `Manifest`
895
+ — they answer "may I touch this file", and they are mutually disjoint in a gated brief, which
896
+ is what `findManifestOverlaps` measures and the `disjoint` rule refuses on. `briefToDispatch`
897
+ runs no gate, so projecting an unvetted draft can produce arrays that intersect: gate before
898
+ you dispatch. Precedence is `authority`, in rank order, index 0 winning every conflict.
899
+
900
+ `authority` therefore overlaps the permission arrays by design, and by requirement: the
901
+ executor has to open what it obeys, so a ranked path always also sits in `read`, `edit`, or
902
+ `locked`, and the `granted` rule refuses a brief where one does not. Read the partitions to
903
+ decide what may be touched and `authority` to decide what wins. Never union the partitions
904
+ with `authority` — that was already wrong before `authority` existed, since `forbidden` is an exclusion rather than a
905
+ grant. It is projected as paths rather than left to `prompt` because a machine consumer must
906
+ not have to parse a document written for a model to discover mandatory authority.
907
+
908
+ ## Patterns
909
+
910
+ ### Compiling a rough request
911
+
912
+ The forward path end to end: text, interpret, draft, gate, pin. The interpret stage
913
+ classifies intent and mines entities; `deriveTask` / `deriveGivens` / `deriveGaps` draft the
914
+ brief; caller sections merge over the draft; the gate rules; the pin stamps.
915
+
916
+ Passing `text` means "interpret this". A pipeline that cannot resolve the text raises its
917
+ own required ambiguity, which drafts as a blocking gap and stops emission — so the text path
918
+ needs an interpret pipeline that can actually match the request:
919
+
920
+ ```ts
921
+ import { buildOutcome, buildOutput, buildProof, createBriefCompiler } from '@orkestrel/brief'
922
+ import { createInterpret } from '@orkestrel/interpret'
923
+ import { createQuantitativeDefinition } from '@orkestrel/reason'
924
+
925
+ const migrations = createBriefCompiler({
926
+ interpret: createInterpret({
927
+ extractor: {
928
+ extract: () => ({
929
+ intent: { action: 'migrate', domain: 'code', confidence: 1 },
930
+ numbers: [3],
931
+ }),
932
+ },
933
+ templates: [
934
+ {
935
+ id: 'migration',
936
+ name: 'Migration',
937
+ domain: 'code',
938
+ intents: ['migrate'],
939
+ mappings: [{ entity: 'count', aliases: [], field: 'count' }],
940
+ defaults: [],
941
+ computations: [],
942
+ definition: createQuantitativeDefinition('migration', 'Migration', []),
943
+ },
944
+ ],
945
+ }),
946
+ actions: { migrate: 'migrate' }, // an interprets action to the closed TaskOperation
947
+ domains: { code: 'code' }, // an interprets domain to the closed TaskDomain
948
+ })
949
+
950
+ const migration = migrations.compile({
951
+ text: 'migrate the 3 legacy stores to the replacement driver seam',
952
+ manifest: {
953
+ read: [{ path: 'guides/stores.md', note: 'the driver seam contract' }],
954
+ edit: [{ path: 'src/core/stores/**', note: 'the three legacy stores' }],
955
+ locked: [],
956
+ forbidden: [{ path: 'app/**', note: 'out of scope' }],
957
+ },
958
+ outcomes: [buildOutcome(1, 'all three stores implement the driver seam')],
959
+ output: buildOutput('diff'),
960
+ proofs: [buildProof('the core test project passes', 'npm run test:src:core')],
961
+ })
962
+
963
+ migration.stages.map((record) => record.stage) // ['interpret', 'draft', 'gate', 'pin']
964
+ migration.interpretation?.intent // { action: 'migrate', domain: 'code', confidence: 1 }
965
+ migration.brief?.task.operation // 'migrate' — derived through the caller vocabulary
966
+ migration.brief?.givens // [{ category: 'extracted', name: 'count', value: '3' }]
967
+ migration.verdict?.conclusion // true — the gate's traceable yes
968
+ migration.brief !== undefined // true
969
+ migrations.destroy()
970
+ ```
971
+
972
+ Drop the templates and the same call blocks instead, carrying interprets own question
973
+ (`Which domain and action did you mean?`) as a blocking gap. That is the design working, not
974
+ a defect: a contract built on a request the language pipeline could not resolve is exactly
975
+ what the gate exists to refuse. A caller who does not want interpretation omits `text` and
976
+ supplies `task` directly.
977
+
978
+ ### Failing closed — the blocking path
979
+
980
+ A required, unresolvable decision drafts a blocking gap. The gate stops emission and the
981
+ `Briefing` carries the questions; the caller answers, re-compiles, done. No half-specified
982
+ brief ever leaves the pipeline.
983
+
984
+ ```ts
985
+ import {
986
+ buildGap,
987
+ buildOutcome,
988
+ buildProof,
989
+ buildTask,
990
+ createBriefCompiler,
991
+ } from '@orkestrel/brief'
992
+
993
+ const blocked = createBriefCompiler()
994
+ const stopped = blocked.compile({
995
+ task: buildTask('refactor', 'code', 'Refactor the session store to the async seam.'),
996
+ outcomes: [buildOutcome(1, 'the store implements the async seam')],
997
+ gaps: [
998
+ buildGap('output', 'Does the result need to land as a diff or as full files?', {
999
+ blocking: true,
1000
+ candidates: ['diff', 'code'],
1001
+ }),
1002
+ ],
1003
+ proofs: [buildProof('checks pass', 'npm run check')],
1004
+ })
1005
+
1006
+ stopped.brief // undefined — the gate failed closed, and that absence is the signal
1007
+ stopped.questions // [{ field: 'output', question: 'Does the result need…', blocking: true, … }]
1008
+ stopped.questions.length // 1 — one question to answer, then re-compile
1009
+ stopped.failures // [{ stage: 'gate', code: 'BLOCKED', message: '1 blocking gap(s)' }]
1010
+ stopped.verdict?.rules.filter((entry) => !entry.applied) // exactly which rules missed
1011
+ stopped.verdict?.trace // the reasoner's narration of the rules that derived, not the misses
1012
+ blocked.destroy()
1013
+ ```
1014
+
1015
+ A gate that refuses for a reason other than a blocking gap — no proofs, a compound
1016
+ statement, an overlapping manifest, an authority no partition opens — reports the unmet
1017
+ rule ids instead, and the briefing is
1018
+ incomplete the same way. A brief with no proofs and a two-sentence statement reports
1019
+ `Gate refused: proven, single`.
1020
+
1021
+ An open gap takes the other path: assume narrowly, record the assumption, proceed. It does
1022
+ not gate. `findUnpairedGaps` counts the open gaps past the assumption count and
1023
+ `validateBrief` reports the surplus as a warning, because the pairing is arithmetic rather
1024
+ than a link the brief carries — one assumption answering two related gaps is good practice
1025
+ and a count cannot tell it from a missing one. Read the warning; the gate will not read it
1026
+ for you.
1027
+
1028
+ ### Gating through the reason engine
1029
+
1030
+ The gate is ordinary reasons machinery, which means you can inspect it, run it standalone, or
1031
+ build your own beside it. `briefToSubject` projects the brief into a flat measures record,
1032
+ `buildGateDefinition()` is a `LogicalDefinition` whose rules read those measures, and the
1033
+ `LogicalReasoner` narrates the verdict.
1034
+
1035
+ ```ts
1036
+ import { briefToSubject, buildGateDefinition } from '@orkestrel/brief'
1037
+ import {
1038
+ createAtom,
1039
+ createCompound,
1040
+ createLogicalDefinition,
1041
+ createLogicalReasoner,
1042
+ createReason,
1043
+ createRule,
1044
+ } from '@orkestrel/reason'
1045
+
1046
+ const engine = createReason({ reasoners: [createLogicalReasoner()] })
1047
+ const measures = briefToSubject(pinned)
1048
+ measures // { operation: 'refactor', blocking: 0, outcomes: 2, required: 2, proofs: 1, edits: 1, … }
1049
+
1050
+ const ruling = engine.reason(measures, buildGateDefinition())
1051
+ if (ruling.reasoning === 'logical') {
1052
+ ruling.conclusion // true — ready to emit
1053
+ ruling.rules.filter((entry) => !entry.applied) // the rules that missed
1054
+ }
1055
+ engine.destroy()
1056
+
1057
+ // A house gate: this package's readiness unchanged, plus one rule of your own, on your engine.
1058
+ const house = createLogicalDefinition('house', 'House readiness', [
1059
+ ...buildGateDefinition().rules,
1060
+ createRule(
1061
+ 'anchored',
1062
+ [createAtom('authority', 'above', 0)],
1063
+ createAtom('anchored', 'equals', true),
1064
+ ),
1065
+ createRule(
1066
+ 'fit',
1067
+ [
1068
+ createCompound('and', [
1069
+ createAtom('ready', 'equals', true),
1070
+ createAtom('anchored', 'equals', true),
1071
+ ]),
1072
+ ],
1073
+ createAtom('fit', 'equals', true),
1074
+ ),
1075
+ ])
1076
+ engine.reason(measures, house) // conclusion is `fit` — base readiness AND anchored
1077
+ ```
1078
+
1079
+ Keep `buildGateDefinition().rules` whole. `fit` reads the `ready` fact, so dropping the rule that
1080
+ derives it leaves `fit` conjoining something nothing proved, and a base-ready brief concludes
1081
+ `false`.
1082
+
1083
+ **`compile` does not decide readiness with this definition.** `findUnmetRules(brief)` measures
1084
+ the same readiness rules in code and refuses before any verdict is read; the preceding definition is
1085
+ what a reasoner narrates, so `Briefing.verdict` carries the trace and the rule-level detail.
1086
+ The code and the data are conjoined in the refusing direction, so a borrowed engine can
1087
+ withhold a pass and can never grant one. A test drives each over one value set, which is what
1088
+ keeps them saying the same thing.
1089
+
1090
+ **`buildGateDefinition()` takes no arguments, and there is no option that reaches it.** That is
1091
+ the design, not an omission. The reasoner overlays every derived fact into one flat
1092
+ namespace, so a caller rule named for a readiness fact — `specified`, `proven`, `single` —
1093
+ overwrites the fact `ready` conjoins, and a brief carrying a blocking gap concludes `true`.
1094
+ A gate whose whole job is to fail closed cannot take rules from the caller it is gating.
1095
+
1096
+ Readiness is this package's contract. A caller who needs different readiness composes their
1097
+ own `LogicalDefinition` over `briefToSubject` and evaluates it on their own reasoner, as
1098
+ in the preceding example; both are exported for exactly that, and neither can reach the definition `compile`
1099
+ uses. Independent safeguards back it up: `compile` refuses a blocking gap **before** it
1100
+ consults the verdict, so no supplied engine can emit a brief that still carries an open
1101
+ question, and `ready` is always the last rule so forward chaining reports it.
1102
+
1103
+ ### Projecting the downstream artifacts
1104
+
1105
+ One brief, projected views — authored zero times each.
1106
+
1107
+ ```ts
1108
+ import { briefToDispatch, briefToGoal, briefToMarkdown } from '@orkestrel/brief'
1109
+
1110
+ briefToMarkdown(pinned)
1111
+ // '# Brief: Refactor useForm to native browser form APIs.
1112
+ //
1113
+ // refactor · code
1114
+ //
1115
+ // ## Authority (ranked)
1116
+ // 1. AGENTS.md — project law; wins every conflict
1117
+ // …paths referenced, contents never inlined — the executor retrieves.'
1118
+
1119
+ briefToGoal(pinned, 12)
1120
+ // 'Done when every proof passes: npm run check exits 0. Cap: 12 turns.'
1121
+
1122
+ const handoff = briefToDispatch(pinned)
1123
+ handoff.edit // ['src/browser/composables/useForm.ts'] — the subagent's owned files
1124
+ handoff.locked // ['src/browser/types.ts'] — read, never write
1125
+ handoff.forbidden // ['app/**'] — never opened
1126
+ handoff.authority // ['AGENTS.md'] — precedence, not permission; index 0 wins every conflict
1127
+ handoff.prompt // the briefToMarkdown rendering, ready to hand off
1128
+ ```
1129
+
1130
+ Because `manifest.edit` is what parallel subagents partition on, keep it minimal and
1131
+ disjoint: two dispatches whose `edit` sets do not intersect can run concurrently under the
1132
+ same brief without conflict.
1133
+
1134
+ ### Narrowing an untrusted brief
1135
+
1136
+ Briefs round-trip JSON — a stored brief, a tool argument, an agent's emission — through the
1137
+ parse-then-trust boundary: shape through the compiled guard, semantics through
1138
+ `validateBrief`, readiness through the gate. Each check answers its own question.
1139
+
1140
+ ```ts
1141
+ import { createBriefCompiler, parseBrief, validateBrief } from '@orkestrel/brief'
1142
+
1143
+ const incoming = parseBrief(JSON.stringify(pinned)) // Brief | undefined — never throws
1144
+ if (incoming !== undefined) {
1145
+ const validation = validateBrief(incoming) // the semantic pass — returns, never throws
1146
+ if (validation.valid) {
1147
+ const checker = createBriefCompiler()
1148
+ checker.gate(incoming).conclusion // the readiness verdict, traced
1149
+ checker.destroy()
1150
+ } else {
1151
+ validation.errors.length // what no assumption can paper over
1152
+ validation.warnings.length // what is runnable but suspicious
1153
+ }
1154
+ }
1155
+ ```
1156
+
1157
+ ### Serving briefs at a tool boundary
1158
+
1159
+ The shape payoff: the same declaration that compiles the guard serves the tool schema and the
1160
+ test data, so an MCP tool accepting briefs cannot drift from the validator that checks them.
1161
+
1162
+ ```ts
1163
+ import { briefToMarkdown, createBriefContract, parseBrief } from '@orkestrel/brief'
1164
+ import { schemaToParameters, seededRandom } from '@orkestrel/contract'
1165
+
1166
+ const boundary = createBriefContract()
1167
+
1168
+ const tool = {
1169
+ name: 'execute_brief',
1170
+ description: 'Execute a compiled brief against this workspace.',
1171
+ parameters: schemaToParameters(boundary.schema), // the JSON Schema, no `as` anywhere
1172
+ }
1173
+ tool.name // 'execute_brief'
1174
+
1175
+ // In the handler: the string boundary is parseBrief; the payload is then trusted typed data.
1176
+ const handled = parseBrief('{}') === undefined ? 'Rejected: not a valid brief.' : 'accepted'
1177
+ handled // 'Rejected: not a valid brief.'
1178
+ briefToMarkdown(boundary.generate(seededRandom(7))) // a reproducible fixture, rendered
1179
+ ```
1180
+
1181
+ ### Storing briefs by their own identity
1182
+
1183
+ A record's id is its brief's own content hash, so re-adding identical content mints no second
1184
+ record and moves no version:
1185
+
1186
+ ```ts
1187
+ import {
1188
+ buildBrief,
1189
+ buildOutcome,
1190
+ buildProof,
1191
+ buildTask,
1192
+ createBriefManager,
1193
+ } from '@orkestrel/brief'
1194
+
1195
+ const store = createBriefManager()
1196
+ const first = store.add(
1197
+ buildBrief(buildTask('plan', 'ops', 'Plan the 0.1 release.'), {
1198
+ outcomes: [buildOutcome(1, 'the layer order is written down')],
1199
+ proofs: [buildProof('the catalog regenerates', 'npx @orkestrel/scaffold catalog')],
1200
+ }),
1201
+ )
1202
+ store.add(first.brief).version // 1 — unchanged content is a version no-op
1203
+ store.count // 1 — the same content minted the same id
1204
+ store.destroy()
1205
+ ```
1206
+
1207
+ ### Practices
1208
+
1209
+ - **One task per brief.** `validateBrief` errors on a multi-sentence statement; a compound
1210
+ request is two `compile` calls, not one longer statement.
1211
+ - **Ask only blocking gaps.** Everything else: assume narrowly, record the assumption beside
1212
+ its open gap, proceed. Never override the user — the draft stage merges caller sections
1213
+ over derived ones for the same reason.
1214
+ - **Reference, never duplicate.** `authority` and `manifest` point at paths; pasting file
1215
+ contents into `givens` buries the signal and duplicates what the executor can retrieve. The
1216
+ one exception is `examples`: an input-to-output exemplar removes more ambiguity than prose.
1217
+ - **Let the container say what a path is.** Both sections hold the same `Reference`, so a path
1218
+ gets its meaning from where it sits: rank in `authority`, permission in a manifest partition.
1219
+ Write `note` for why the path is listed, not for what kind of file it is.
1220
+ - **List every authority in the manifest too.** Ranking a path says obey it; a partition says
1221
+ the executor may open it, and it needs both. Put it in `read`, or in `locked` when it must
1222
+ not change. `findUngrantedAuthority` refuses the gap at the gate, so this is not advice.
1223
+ - **Pin the outcome, not a plan.** Specify `output` and `proofs` and leave the route free; a
1224
+ brittle step list in `rules` makes runs diverge. A rule belongs in `rules` only when the
1225
+ method is the requirement.
1226
+ - **Outcomes versus bounds, decided once.** If violating it makes the result wrong, it is a
1227
+ rule or an invariant; if achieving it defines "done", it is a required outcome. Never put a
1228
+ limit in `outcomes`.
1229
+ - **Prefer proofs with a clear exit signal.** `npm run check`, a scoped `npm run test:<scope>`,
1230
+ a `git diff` confined to the manifest. These become the `/goal` condition verbatim, so vague
1231
+ prose here is a vague finish line.
1232
+ - **Keep the manifest partitions disjoint and `edit` minimal.** `findManifestOverlaps` catches
1233
+ the overlap; a lean `edit` set is what makes parallel dispatch safe.
1234
+ - **Gate untrusted briefs twice.** `parseBrief` for shape at the boundary, `validateBrief` for
1235
+ semantics; reserve `assertBrief`'s throw for programmer-error contexts where invalidity is a
1236
+ bug.
1237
+ - **Store `pinBrief` output, not drafts.** The hash is the identity; `JSON.stringify` out,
1238
+ `parseBrief` back in, and the `BriefManager` recognizes unchanged content as the same version.
1239
+ - **Bring your own engines to observe them.** Pass `interpret` and `reason` through
1240
+ `BriefCompilerOptions` when you need their emitters or shared registries; the compiler destroys
1241
+ only what it created.
1242
+ - **Destroy when done.** `destroy()` releases the owned engines and the emitter; a destroyed
1243
+ instance throws `DESTROYED` on use — narrow with `isBriefError`.
1244
+
1245
+ ## Tests
1246
+
1247
+ - [`tests/guides.test.ts`](../tests/guides.test.ts) — the `## Surface` to `src/core` bijection (value and type exports), the `## Methods` to interface-method bijection, link integrity, fence languages, example presence, fence-import reality, options-member liveness, TSDoc identifier resolution, and the equality gate: every `Summary` cell against its declaration's description paragraph, the titled `Compile and project a brief` fence against the `@example` of that title (pinned so the titled pair cannot be retired silently), and the README pitch against this guide's tagline. It also runs the flagship fences — the `## Surface` compile and the `### Builders` draft — and asserts the values their `// value` comments claim.
1248
+ - [`tests/src/core/BriefCompiler.test.ts`](../tests/src/core/BriefCompiler.test.ts) — the `interpret` → `draft` → `gate` → `pin` pipeline, stage order and records, the interpret-skip path, caller-over-derived merging, fail-closed blocking (questions, the `BLOCKED` failure, the absent brief), `gate` verdict tracing, event sequences (`compile` versus `block`), owned-versus-borrowed engine teardown, idempotent `destroy`, and `DESTROYED` throws.
1249
+ - [`tests/src/core/BriefManager.test.ts`](../tests/src/core/BriefManager.test.ts) — content-hash id minting, version bump only on content change, the `remove` forms, per-event emissions, and destroy semantics.
1250
+ - [`tests/src/core/helpers.test.ts`](../tests/src/core/helpers.test.ts) — every builder's output shape, every projection, the derivations and their off-vocabulary `undefined`, `pinBrief` determinism and idempotence, `buildGateDefinition`'s rule list against `findUnmetRules` over one value set, `validateBrief` errors and warnings, and the `find*` leaves.
1251
+ - [`tests/src/core/validators.test.ts`](../tests/src/core/validators.test.ts) — each guard accepts valid and rejects invalid plus adversarial junk, exact-record semantics, and off-vocabulary rejection.
1252
+ - [`tests/src/core/shapers.test.ts`](../tests/src/core/shapers.test.ts) — the shape family against the guard family over one value set, JSON Schema essentials, and seeded generate round-trips.
1253
+ - [`tests/src/core/parsers.test.ts`](../tests/src/core/parsers.test.ts) — `parseBrief` guard soundness in both directions, its JSON round-trip, and the intake contrast: `assertBrief` returning its argument by identity where `parseBrief` returns a graph sharing no reference with the source.
1254
+ - [`tests/src/core/cloners.test.ts`](../tests/src/core/cloners.test.ts) — `captureValue` over a source carrying a prototype accessor, an unknown own accessor, an uncloneable leaf, a cycle, and a shared branch, plus the source whose own members cannot be read at all.
1255
+ - [`tests/src/core/factories.test.ts`](../tests/src/core/factories.test.ts) — `createBriefCompiler` / `createBriefManager` / `createBriefContract`.
1256
+ - [`tests/src/core/index.test.ts`](../tests/src/core/index.test.ts) — the barrel resolves and re-exports the whole surface.
1257
+ - [`tests/src/core/integration.test.ts`](../tests/src/core/integration.test.ts) — text to interpret to brief to gate to projections end to end, and a gated brief re-compiled with the answered gap passing.
1258
+
1259
+ ## See also
1260
+
1261
+ - [`reason.md`](reason.md) — the engine beneath the gate: `LogicalDefinition`, `Subject`, the traceable `LogicalResult`, and the capability layer `buildGateDefinition` extends.
1262
+ - [`interpret.md`](interpret.md) — the language pipeline the `interpret` stage delegates to: `Interpretation`, `Intent`, `Entity`, `Ambiguity`.
1263
+ - [`contract.md`](contract.md) — the guards, combinators, shapes, and `createContract` machinery this package composes.
1264
+ - [`emitter.md`](emitter.md) — the typed emitter behind the compiler's and the manager's observation surfaces.
1265
+ - [`AGENTS.md`](../AGENTS.md) — the rules this package is written to.
1266
+ - [`README.md`](README.md) — the guides index.