@orkestrel/scaffold 0.0.67 → 0.0.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/main.js +67 -44
- package/dist/bin/main.js.map +1 -1
- package/dist/host/agents/templates/brief.md +9 -0
- package/dist/host/claude/agents/orkestrel.md +4 -4
- package/dist/host/claude/rules/names.md +15 -0
- package/dist/host/claude/rules/tests.md +33 -4
- package/dist/host/claude/rules/workspace.md +14 -2
- package/dist/host/dotfiles/prettierignore +3 -0
- package/dist/host/guides/README.md +65 -0
- package/dist/host/guides/abort.md +169 -0
- package/dist/host/guides/agent.md +1509 -0
- package/dist/host/guides/brief.md +1266 -0
- package/dist/host/guides/browser.md +2200 -0
- package/dist/host/guides/budget.md +196 -0
- package/dist/host/guides/codec.md +519 -0
- package/dist/host/guides/console.md +785 -0
- package/dist/host/guides/contract.md +1193 -0
- package/dist/host/guides/csv.md +541 -0
- package/dist/host/guides/database.md +2518 -0
- package/dist/host/guides/emitter.md +233 -0
- package/dist/host/guides/form.md +1791 -0
- package/dist/host/guides/html.md +717 -0
- package/dist/host/guides/indexeddb.md +505 -0
- package/dist/host/guides/interpret.md +1029 -0
- package/dist/host/guides/lsp.md +515 -0
- package/dist/host/guides/markdown.md +964 -0
- package/dist/host/guides/mcp.md +5554 -0
- package/dist/host/guides/middleware.md +927 -0
- package/dist/host/guides/msg.md +440 -0
- package/dist/host/guides/ndjson.md +120 -0
- package/dist/host/guides/ollama.md +380 -0
- package/dist/host/guides/pool.md +280 -0
- package/dist/host/guides/probe.md +1210 -0
- package/dist/host/guides/process.md +1620 -0
- package/dist/host/guides/program.md +1110 -0
- package/dist/host/guides/qualifier.md +854 -0
- package/dist/host/guides/queue.md +370 -0
- package/dist/host/guides/rater.md +330 -0
- package/dist/host/guides/reason.md +1122 -0
- package/dist/host/guides/relation.md +373 -0
- package/dist/host/guides/router.md +753 -0
- package/dist/host/guides/scaffold.md +192 -31
- package/dist/host/guides/sea.md +383 -0
- package/dist/host/guides/server.md +752 -0
- package/dist/host/guides/sqlite.md +330 -0
- package/dist/host/guides/sse.md +187 -0
- package/dist/host/guides/supervisor.md +4890 -0
- package/dist/host/guides/table.md +1556 -0
- package/dist/host/guides/template.md +280 -0
- package/dist/host/guides/terminal.md +1145 -0
- package/dist/host/guides/test.md +2969 -0
- package/dist/host/guides/timeout.md +252 -0
- package/dist/host/guides/tool.md +311 -0
- package/dist/host/guides/toolbox.md +1038 -0
- package/dist/host/guides/websocket.md +282 -0
- package/dist/host/guides/worker.md +615 -0
- package/dist/host/guides/workflow.md +1507 -0
- package/dist/host/guides/workspace.md +595 -0
- package/dist/host/manifest.json +1218 -10
- package/dist/host/tests/policy.test.ts +279 -2
- package/dist/host/tests/setupPolicy.ts +437 -6
- package/dist/src/core/index.cjs +38 -16
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +33 -9
- package/dist/src/core/index.d.ts +33 -9
- package/dist/src/core/index.js +37 -17
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +1750 -1567
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +106 -24
- package/dist/src/server/index.d.ts +106 -24
- package/dist/src/server/index.js +1751 -1570
- package/dist/src/server/index.js.map +1 -1
- 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.
|