@orkestrel/brief 0.0.5 → 0.0.7
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/README.md +6 -6
- package/dist/src/core/index.cjs +411 -290
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +608 -483
- package/dist/src/core/index.d.ts +608 -483
- package/dist/src/core/index.js +399 -279
- package/dist/src/core/index.js.map +1 -1
- package/package.json +17 -15
package/dist/src/core/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { andOf, arrayOf, arrayShape, attempt, booleanShape, boundsOf, cloneJSONRecord, createContract, integerShape, isBoolean, isInteger, isNonEmptyString, isString, literalOf, literalShape, objectShape, optionalShape, parseJSONAs, recordOf, stringShape } from "@orkestrel/contract";
|
|
2
2
|
import { canonicalize, collapseWhitespace, createInterpret, digestValue, isInterpretation } from "@orkestrel/interpret";
|
|
3
|
-
import {
|
|
3
|
+
import { createAtom, createCompound, createLogicalDefinition, createLogicalReasoner, createReason, createRule, formatField, isLogicalResult } from "@orkestrel/reason";
|
|
4
4
|
import { Emitter } from "@orkestrel/emitter";
|
|
5
5
|
//#region src/core/constants.ts
|
|
6
|
-
/**
|
|
6
|
+
/** Lists the `TaskOperation` values, frozen. */
|
|
7
7
|
var TASK_OPERATIONS = Object.freeze([
|
|
8
8
|
"create",
|
|
9
9
|
"refactor",
|
|
@@ -18,7 +18,7 @@ var TASK_OPERATIONS = Object.freeze([
|
|
|
18
18
|
"document",
|
|
19
19
|
"plan"
|
|
20
20
|
]);
|
|
21
|
-
/**
|
|
21
|
+
/** Lists the `TaskDomain` values, frozen. */
|
|
22
22
|
var TASK_DOMAINS = Object.freeze([
|
|
23
23
|
"code",
|
|
24
24
|
"writing",
|
|
@@ -29,7 +29,7 @@ var TASK_DOMAINS = Object.freeze([
|
|
|
29
29
|
"ops",
|
|
30
30
|
"other"
|
|
31
31
|
]);
|
|
32
|
-
/**
|
|
32
|
+
/** Lists the `OutputFormat` values, frozen. */
|
|
33
33
|
var OUTPUT_FORMATS = Object.freeze([
|
|
34
34
|
"markdown",
|
|
35
35
|
"json",
|
|
@@ -37,35 +37,64 @@ var OUTPUT_FORMATS = Object.freeze([
|
|
|
37
37
|
"diff",
|
|
38
38
|
"prose"
|
|
39
39
|
]);
|
|
40
|
-
/**
|
|
40
|
+
/** Lists the `RiskSeverity` values, frozen. */
|
|
41
41
|
var RISK_SEVERITIES = Object.freeze([
|
|
42
42
|
"low",
|
|
43
43
|
"medium",
|
|
44
44
|
"high"
|
|
45
45
|
]);
|
|
46
46
|
/**
|
|
47
|
-
*
|
|
47
|
+
* Lists every published `Interpretation` member name, frozen.
|
|
48
|
+
*
|
|
49
|
+
* @remarks
|
|
50
|
+
* The capture list `BriefCompiler` hands `captureValue` at each interpret door — the borrowed
|
|
51
|
+
* engine's return, and the caller's supplied interpretation. A class instance carries its
|
|
52
|
+
* contract on the prototype, so the captured view materializes exactly the members named here,
|
|
53
|
+
* and a name missing from the list is a member the view drops.
|
|
54
|
+
*
|
|
55
|
+
* The `satisfies` clause refuses a name `Interpretation` does not declare, and it holds the
|
|
56
|
+
* element type at the listed names rather than widening it to `string`. That is what lets the
|
|
57
|
+
* equality assertion beside the capture cases refuse a list that has fallen short of the
|
|
58
|
+
* published shape.
|
|
59
|
+
*/
|
|
60
|
+
var INTERPRETATION_MEMBERS = Object.freeze([
|
|
61
|
+
"text",
|
|
62
|
+
"normalized",
|
|
63
|
+
"intent",
|
|
64
|
+
"entities",
|
|
65
|
+
"subject",
|
|
66
|
+
"definition",
|
|
67
|
+
"mappings",
|
|
68
|
+
"ambiguities",
|
|
69
|
+
"prompt",
|
|
70
|
+
"stages",
|
|
71
|
+
"failures",
|
|
72
|
+
"confidence",
|
|
73
|
+
"digest"
|
|
74
|
+
]);
|
|
75
|
+
/**
|
|
76
|
+
* Holds `16` — the default turn cap `briefToGoal` renders.
|
|
48
77
|
*
|
|
49
78
|
* @remarks
|
|
50
79
|
* Domain-qualified so the barrel stays collision-free as sibling modules add their own
|
|
51
80
|
* turn defaults.
|
|
52
81
|
*/
|
|
53
82
|
var DEFAULT_BRIEF_TURNS = 16;
|
|
54
|
-
/** `'gate'` — the id of the `
|
|
83
|
+
/** Holds `'gate'` — the id of the `buildGateDefinition()` logical definition. */
|
|
55
84
|
var GATE_ID = "gate";
|
|
56
85
|
/**
|
|
57
|
-
*
|
|
86
|
+
* Matches every line terminator a brief field refuses.
|
|
58
87
|
*
|
|
59
88
|
* @remarks
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
89
|
+
* Every ECMAScript line terminator, not just `\n`: a renderer that splits on any of them
|
|
90
|
+
* would let the others forge a markdown row. CRLF leads the alternation so a Windows
|
|
91
|
+
* exemplar splits as ONE break rather than two, which would insert a blank line the caller
|
|
92
|
+
* never wrote. Kept unanchored and stateless — no `g` flag — so `test` never carries
|
|
64
93
|
* `lastIndex` between calls.
|
|
65
94
|
*/
|
|
66
95
|
var LINE_BREAK_PATTERN = /\r\n|[\n\r\u2028\u2029]/;
|
|
67
96
|
/**
|
|
68
|
-
*
|
|
97
|
+
* Holds the positive form of {@link LINE_BREAK_PATTERN}, for the shape DSL.
|
|
69
98
|
*
|
|
70
99
|
* @remarks
|
|
71
100
|
* `stringShape`'s `pattern` must MATCH an accepted value, so the guard's refusal regex
|
|
@@ -74,7 +103,7 @@ var LINE_BREAK_PATTERN = /\r\n|[\n\r\u2028\u2029]/;
|
|
|
74
103
|
*/
|
|
75
104
|
var SINGLE_LINE_PATTERN = /^[^\n\r\u2028\u2029]*$/;
|
|
76
105
|
/**
|
|
77
|
-
*
|
|
106
|
+
* Matches a string of one or more spaces and nothing else.
|
|
78
107
|
*
|
|
79
108
|
* @remarks
|
|
80
109
|
* The one exemplar side `exampleToLines` must NOT pad. CommonMark strips a fully-blank code
|
|
@@ -88,7 +117,7 @@ var BLANK_PATTERN = /^ +$/;
|
|
|
88
117
|
//#endregion
|
|
89
118
|
//#region src/core/errors.ts
|
|
90
119
|
/**
|
|
91
|
-
*
|
|
120
|
+
* Represents the one error class this package throws.
|
|
92
121
|
*
|
|
93
122
|
* @remarks
|
|
94
123
|
* Throws are reserved for caller misuse: `assertBrief`, `snapshotBrief`, and `pinBrief` on
|
|
@@ -118,10 +147,10 @@ var BriefError = class extends Error {
|
|
|
118
147
|
}
|
|
119
148
|
};
|
|
120
149
|
/**
|
|
121
|
-
*
|
|
150
|
+
* Narrows a caught value to a {@link BriefError}.
|
|
122
151
|
*
|
|
123
152
|
* @param value - The caught value to inspect.
|
|
124
|
-
* @returns
|
|
153
|
+
* @returns True if `value` is a `BriefError`; false otherwise.
|
|
125
154
|
*
|
|
126
155
|
* @example
|
|
127
156
|
* ```ts
|
|
@@ -139,82 +168,82 @@ function isBriefError(value) {
|
|
|
139
168
|
}
|
|
140
169
|
//#endregion
|
|
141
170
|
//#region src/core/shapers.ts
|
|
142
|
-
/**
|
|
171
|
+
/** Describes a single-line string of any length, including empty. */
|
|
143
172
|
var textShape = stringShape({ pattern: SINGLE_LINE_PATTERN });
|
|
144
|
-
/**
|
|
173
|
+
/** Describes a non-empty single-line string — the shape mirror of `isLine`. */
|
|
145
174
|
var lineShape = stringShape({
|
|
146
175
|
min: 1,
|
|
147
176
|
pattern: SINGLE_LINE_PATTERN
|
|
148
177
|
});
|
|
149
|
-
/**
|
|
178
|
+
/** Describes the `Task` shape — closed operation and domain vocabularies plus a non-empty statement. */
|
|
150
179
|
var taskShape = objectShape({
|
|
151
180
|
operation: literalShape(TASK_OPERATIONS),
|
|
152
181
|
domain: literalShape(TASK_DOMAINS),
|
|
153
182
|
statement: lineShape
|
|
154
183
|
}, { description: "What the brief asks for, in one imperative sentence." });
|
|
155
|
-
/**
|
|
184
|
+
/** Describes the `Reference` shape — a path and the note that justifies listing it. */
|
|
156
185
|
var referenceShape = objectShape({
|
|
157
186
|
path: lineShape,
|
|
158
187
|
note: lineShape
|
|
159
188
|
}, { description: "One referenced path and why it is listed." });
|
|
160
|
-
/**
|
|
189
|
+
/** Describes the `Manifest` shape — disjoint reference partitions. */
|
|
161
190
|
var manifestShape = objectShape({
|
|
162
191
|
read: arrayShape(referenceShape),
|
|
163
192
|
edit: arrayShape(referenceShape),
|
|
164
193
|
locked: arrayShape(referenceShape),
|
|
165
194
|
forbidden: arrayShape(referenceShape)
|
|
166
|
-
}, { description: "The
|
|
167
|
-
/**
|
|
195
|
+
}, { description: "The disjoint file partitions of a brief." });
|
|
196
|
+
/** Describes the `Outcome` shape — a one-based rank, the result text, and whether it gates done. */
|
|
168
197
|
var outcomeShape = objectShape({
|
|
169
198
|
rank: integerShape({ min: 1 }),
|
|
170
199
|
text: lineShape,
|
|
171
200
|
required: booleanShape()
|
|
172
201
|
}, { description: "One ranked outcome — a result, never a step." });
|
|
173
|
-
/**
|
|
202
|
+
/** Describes the `Given` shape — one categorized context fact. */
|
|
174
203
|
var givenShape = objectShape({
|
|
175
204
|
category: lineShape,
|
|
176
205
|
name: lineShape,
|
|
177
206
|
value: textShape
|
|
178
207
|
}, { description: "One context fact handed to the executor." });
|
|
179
|
-
/**
|
|
208
|
+
/** Describes the `Example` shape — one input to output exemplar. */
|
|
180
209
|
var exampleShape = objectShape({
|
|
181
210
|
input: stringShape({ min: 1 }),
|
|
182
211
|
output: stringShape({ min: 1 }),
|
|
183
212
|
note: optionalShape(lineShape)
|
|
184
213
|
}, { description: "One input to output exemplar." });
|
|
185
|
-
/**
|
|
214
|
+
/** Describes the `Citation` shape — a name, a locator, and why the source is cited. */
|
|
186
215
|
var citationShape = objectShape({
|
|
187
216
|
name: lineShape,
|
|
188
217
|
url: lineShape,
|
|
189
218
|
note: lineShape
|
|
190
219
|
}, { description: "One external source; list order is the trust order." });
|
|
191
|
-
/**
|
|
220
|
+
/** Describes the `Gap` shape — an unknown, whether it blocks, and the candidates that would close it. */
|
|
192
221
|
var gapShape = objectShape({
|
|
193
222
|
field: lineShape,
|
|
194
223
|
question: lineShape,
|
|
195
224
|
blocking: booleanShape(),
|
|
196
225
|
candidates: optionalShape(arrayShape(lineShape))
|
|
197
226
|
}, { description: "One unresolved decision; blocking means the gate fails closed." });
|
|
198
|
-
/**
|
|
227
|
+
/** Describes the `Risk` shape — a closed severity, the risk, and its mitigation. */
|
|
199
228
|
var riskShape = objectShape({
|
|
200
229
|
severity: literalShape(RISK_SEVERITIES),
|
|
201
230
|
text: lineShape,
|
|
202
231
|
mitigation: lineShape
|
|
203
232
|
}, { description: "One pre-empted risk and the mitigation that answers it." });
|
|
204
|
-
/**
|
|
233
|
+
/** Describes the `Output` shape — a closed format plus its optional refinements. */
|
|
205
234
|
var outputShape = objectShape({
|
|
206
235
|
format: literalShape(OUTPUT_FORMATS),
|
|
207
236
|
sections: optionalShape(arrayShape(lineShape)),
|
|
208
237
|
include: optionalShape(arrayShape(lineShape)),
|
|
209
238
|
exclude: optionalShape(arrayShape(lineShape))
|
|
210
239
|
}, { description: "The closed shape of the deliverable." });
|
|
211
|
-
/**
|
|
240
|
+
/** Describes the `Proof` shape — the claim and the command that settles it. */
|
|
212
241
|
var proofShape = objectShape({
|
|
213
242
|
text: lineShape,
|
|
214
243
|
command: lineShape
|
|
215
244
|
}, { description: "One mechanical, transcript-provable check." });
|
|
216
245
|
/**
|
|
217
|
-
*
|
|
246
|
+
* Describes the whole `Brief` shape, section shapes composed.
|
|
218
247
|
*
|
|
219
248
|
* @remarks
|
|
220
249
|
* `trace` and `hash` are optional because `pinBrief` fills them; an unpinned draft is
|
|
@@ -241,40 +270,82 @@ var briefShape = objectShape({
|
|
|
241
270
|
//#endregion
|
|
242
271
|
//#region src/core/validators.ts
|
|
243
272
|
/**
|
|
244
|
-
*
|
|
273
|
+
* Checks whether the value is a string holding no line terminator, empty included.
|
|
245
274
|
*
|
|
246
275
|
* @remarks
|
|
247
276
|
* `briefToMarkdown` renders each brief field as ONE markdown row, so a field carrying a
|
|
248
277
|
* line break would forge a heading or an extra manifest row — which is how a rendered
|
|
249
278
|
* prompt and `briefToDispatch`'s path sets could disagree about the same brief.
|
|
279
|
+
*
|
|
280
|
+
* @param value - The value to inspect.
|
|
281
|
+
* @returns True if `value` is a string holding no line terminator, empty included; false
|
|
282
|
+
* otherwise.
|
|
250
283
|
*/
|
|
251
284
|
var isText = (value) => isString(value) && !LINE_BREAK_PATTERN.test(value);
|
|
252
|
-
/**
|
|
285
|
+
/**
|
|
286
|
+
* Checks whether the value is a non-empty string holding no line terminator.
|
|
287
|
+
*
|
|
288
|
+
* @param value - The value to inspect.
|
|
289
|
+
* @returns True if `value` is a non-empty string holding no line terminator; false otherwise.
|
|
290
|
+
*/
|
|
253
291
|
var isLine = andOf(isNonEmptyString, isText);
|
|
254
|
-
/**
|
|
292
|
+
/**
|
|
293
|
+
* Checks whether the value is one of the `TaskOperation` literals.
|
|
294
|
+
*
|
|
295
|
+
* @param value - The value to inspect.
|
|
296
|
+
* @returns True if `value` is one of the `TaskOperation` literals; false otherwise.
|
|
297
|
+
*/
|
|
255
298
|
var isTaskOperation = literalOf(TASK_OPERATIONS);
|
|
256
|
-
/**
|
|
299
|
+
/**
|
|
300
|
+
* Checks whether the value is one of the `TaskDomain` literals.
|
|
301
|
+
*
|
|
302
|
+
* @param value - The value to inspect.
|
|
303
|
+
* @returns True if `value` is one of the `TaskDomain` literals; false otherwise.
|
|
304
|
+
*/
|
|
257
305
|
var isTaskDomain = literalOf(TASK_DOMAINS);
|
|
258
|
-
/**
|
|
306
|
+
/**
|
|
307
|
+
* Checks whether the value is one of the `OutputFormat` literals.
|
|
308
|
+
*
|
|
309
|
+
* @param value - The value to inspect.
|
|
310
|
+
* @returns True if `value` is one of the `OutputFormat` literals; false otherwise.
|
|
311
|
+
*/
|
|
259
312
|
var isOutputFormat = literalOf(OUTPUT_FORMATS);
|
|
260
|
-
/**
|
|
313
|
+
/**
|
|
314
|
+
* Checks whether the value is one of the `RiskSeverity` literals.
|
|
315
|
+
*
|
|
316
|
+
* @param value - The value to inspect.
|
|
317
|
+
* @returns True if `value` is one of the `RiskSeverity` literals; false otherwise.
|
|
318
|
+
*/
|
|
261
319
|
var isRiskSeverity = literalOf(RISK_SEVERITIES);
|
|
262
|
-
/**
|
|
320
|
+
/**
|
|
321
|
+
* Checks whether the value is a well-formed `Task` — both vocabularies closed, statement one line.
|
|
322
|
+
*
|
|
323
|
+
* @param value - The value to inspect.
|
|
324
|
+
* @returns True if `value` is a well-formed `Task`; false otherwise.
|
|
325
|
+
*/
|
|
263
326
|
var isTask = recordOf({
|
|
264
327
|
operation: isTaskOperation,
|
|
265
328
|
domain: isTaskDomain,
|
|
266
329
|
statement: isLine
|
|
267
330
|
});
|
|
268
|
-
/**
|
|
331
|
+
/**
|
|
332
|
+
* Checks whether the value is a well-formed `Reference` — both members required, both single-line.
|
|
333
|
+
*
|
|
334
|
+
* @param value - The value to inspect.
|
|
335
|
+
* @returns True if `value` is a well-formed `Reference`; false otherwise.
|
|
336
|
+
*/
|
|
269
337
|
var isReference = recordOf({
|
|
270
338
|
path: isLine,
|
|
271
339
|
note: isLine
|
|
272
340
|
});
|
|
273
341
|
/**
|
|
274
|
-
*
|
|
342
|
+
* Checks whether the value is a well-formed `Manifest`.
|
|
275
343
|
*
|
|
276
344
|
* @remarks
|
|
277
345
|
* Partition presence only — disjointness is `validateBrief`'s semantic pass.
|
|
346
|
+
*
|
|
347
|
+
* @param value - The value to inspect.
|
|
348
|
+
* @returns True if `value` is a well-formed `Manifest`; false otherwise.
|
|
278
349
|
*/
|
|
279
350
|
var isManifest = recordOf({
|
|
280
351
|
read: arrayOf(isReference),
|
|
@@ -282,50 +353,83 @@ var isManifest = recordOf({
|
|
|
282
353
|
locked: arrayOf(isReference),
|
|
283
354
|
forbidden: arrayOf(isReference)
|
|
284
355
|
});
|
|
285
|
-
/**
|
|
356
|
+
/**
|
|
357
|
+
* Checks whether the value is a well-formed `Outcome` — `rank` a positive integer.
|
|
358
|
+
*
|
|
359
|
+
* @param value - The value to inspect.
|
|
360
|
+
* @returns True if `value` is a well-formed `Outcome`; false otherwise.
|
|
361
|
+
*/
|
|
286
362
|
var isOutcome = recordOf({
|
|
287
363
|
rank: andOf(isInteger, boundsOf(1)),
|
|
288
364
|
text: isLine,
|
|
289
365
|
required: isBoolean
|
|
290
366
|
});
|
|
291
|
-
/**
|
|
367
|
+
/**
|
|
368
|
+
* Checks whether the value is a well-formed `Given` — its `value` may be empty but stays one line.
|
|
369
|
+
*
|
|
370
|
+
* @param value - The value to inspect.
|
|
371
|
+
* @returns True if `value` is a well-formed `Given`; false otherwise.
|
|
372
|
+
*/
|
|
292
373
|
var isGiven = recordOf({
|
|
293
374
|
category: isLine,
|
|
294
375
|
name: isLine,
|
|
295
376
|
value: isText
|
|
296
377
|
});
|
|
297
378
|
/**
|
|
298
|
-
*
|
|
379
|
+
* Checks whether the value is a well-formed `Example`.
|
|
299
380
|
*
|
|
300
381
|
* @remarks
|
|
301
382
|
* An exemplar's two sides are the ONLY members a brief lets span lines, because they
|
|
302
383
|
* carry code. `briefToMarkdown` fences them rather than rendering them as a row.
|
|
384
|
+
*
|
|
385
|
+
* @param value - The value to inspect.
|
|
386
|
+
* @returns True if `value` is a well-formed `Example`; false otherwise.
|
|
303
387
|
*/
|
|
304
388
|
var isExample = recordOf({
|
|
305
389
|
input: isNonEmptyString,
|
|
306
390
|
output: isNonEmptyString,
|
|
307
391
|
note: isLine
|
|
308
392
|
}, ["note"]);
|
|
309
|
-
/**
|
|
393
|
+
/**
|
|
394
|
+
* Checks whether the value is a well-formed `Citation` — every member single-line.
|
|
395
|
+
*
|
|
396
|
+
* @param value - The value to inspect.
|
|
397
|
+
* @returns True if `value` is a well-formed `Citation`; false otherwise.
|
|
398
|
+
*/
|
|
310
399
|
var isCitation = recordOf({
|
|
311
400
|
name: isLine,
|
|
312
401
|
url: isLine,
|
|
313
402
|
note: isLine
|
|
314
403
|
});
|
|
315
|
-
/**
|
|
404
|
+
/**
|
|
405
|
+
* Checks whether the value is a well-formed `Gap`.
|
|
406
|
+
*
|
|
407
|
+
* @param value - The value to inspect.
|
|
408
|
+
* @returns True if `value` is a well-formed `Gap`; false otherwise.
|
|
409
|
+
*/
|
|
316
410
|
var isGap = recordOf({
|
|
317
411
|
field: isLine,
|
|
318
412
|
question: isLine,
|
|
319
413
|
blocking: isBoolean,
|
|
320
414
|
candidates: arrayOf(isLine)
|
|
321
415
|
}, ["candidates"]);
|
|
322
|
-
/**
|
|
416
|
+
/**
|
|
417
|
+
* Checks whether the value is a well-formed `Risk` — `severity` on the closed vocabulary.
|
|
418
|
+
*
|
|
419
|
+
* @param value - The value to inspect.
|
|
420
|
+
* @returns True if `value` is a well-formed `Risk`; false otherwise.
|
|
421
|
+
*/
|
|
323
422
|
var isRisk = recordOf({
|
|
324
423
|
severity: isRiskSeverity,
|
|
325
424
|
text: isLine,
|
|
326
425
|
mitigation: isLine
|
|
327
426
|
});
|
|
328
|
-
/**
|
|
427
|
+
/**
|
|
428
|
+
* Checks whether the value is a well-formed `Output` — `format` on the closed vocabulary.
|
|
429
|
+
*
|
|
430
|
+
* @param value - The value to inspect.
|
|
431
|
+
* @returns True if `value` is a well-formed `Output`; false otherwise.
|
|
432
|
+
*/
|
|
329
433
|
var isOutput = recordOf({
|
|
330
434
|
format: isOutputFormat,
|
|
331
435
|
sections: arrayOf(isLine),
|
|
@@ -336,17 +440,25 @@ var isOutput = recordOf({
|
|
|
336
440
|
"include",
|
|
337
441
|
"exclude"
|
|
338
442
|
]);
|
|
339
|
-
/**
|
|
443
|
+
/**
|
|
444
|
+
* Checks whether the value is a well-formed `Proof`.
|
|
445
|
+
*
|
|
446
|
+
* @param value - The value to inspect.
|
|
447
|
+
* @returns True if `value` is a well-formed `Proof`; false otherwise.
|
|
448
|
+
*/
|
|
340
449
|
var isProof = recordOf({
|
|
341
450
|
text: isLine,
|
|
342
451
|
command: isLine
|
|
343
452
|
});
|
|
344
453
|
/**
|
|
345
|
-
*
|
|
454
|
+
* Checks whether the value satisfies the whole exact-record `Brief` contract.
|
|
346
455
|
*
|
|
347
456
|
* @remarks
|
|
348
457
|
* Every section must be present; an extra key fails. `trace` and `hash` are the only
|
|
349
458
|
* optional members, because `pinBrief` rather than the author fills them.
|
|
459
|
+
*
|
|
460
|
+
* @param value - The value to inspect.
|
|
461
|
+
* @returns True if `value` satisfies the whole exact-record `Brief` contract; false otherwise.
|
|
350
462
|
*/
|
|
351
463
|
var isBrief = recordOf({
|
|
352
464
|
task: isTask,
|
|
@@ -447,7 +559,7 @@ function captureValue(source, members) {
|
|
|
447
559
|
return target;
|
|
448
560
|
}
|
|
449
561
|
/**
|
|
450
|
-
*
|
|
562
|
+
* Returns a deeply owned, deeply frozen copy of a brief, refusing anything off-contract.
|
|
451
563
|
*
|
|
452
564
|
* @remarks
|
|
453
565
|
* The one reading boundary this package has, used by the pin, the registry, and every
|
|
@@ -471,10 +583,10 @@ function captureValue(source, members) {
|
|
|
471
583
|
*
|
|
472
584
|
* @example
|
|
473
585
|
* ```ts
|
|
474
|
-
* import {
|
|
586
|
+
* import { buildBrief, buildOutcome, buildTask, snapshotBrief } from '@orkestrel/brief'
|
|
475
587
|
*
|
|
476
|
-
* const outcomes = [
|
|
477
|
-
* const owned = snapshotBrief(
|
|
588
|
+
* const outcomes = [buildOutcome(1, 'shipped')]
|
|
589
|
+
* const owned = snapshotBrief(buildBrief(buildTask('plan', 'ops', 'Plan the release.'), { outcomes }))
|
|
478
590
|
* owned.outcomes === outcomes // false — the alias is broken
|
|
479
591
|
* Object.isFrozen(owned.outcomes) // true
|
|
480
592
|
* ```
|
|
@@ -487,7 +599,7 @@ function snapshotBrief(source) {
|
|
|
487
599
|
//#endregion
|
|
488
600
|
//#region src/core/helpers.ts
|
|
489
601
|
/**
|
|
490
|
-
*
|
|
602
|
+
* Assembles a `Task` from an operation, a domain, and a statement.
|
|
491
603
|
*
|
|
492
604
|
* @param operation - What the brief asks for, from the closed operation vocabulary.
|
|
493
605
|
* @param domain - The subject matter, from the closed domain vocabulary.
|
|
@@ -496,12 +608,12 @@ function snapshotBrief(source) {
|
|
|
496
608
|
*
|
|
497
609
|
* @example
|
|
498
610
|
* ```ts
|
|
499
|
-
* import {
|
|
611
|
+
* import { buildTask } from '@orkestrel/brief'
|
|
500
612
|
*
|
|
501
|
-
*
|
|
613
|
+
* buildTask('refactor', 'code', 'Refactor useForm to native browser form APIs.')
|
|
502
614
|
* ```
|
|
503
615
|
*/
|
|
504
|
-
function
|
|
616
|
+
function buildTask(operation, domain, statement) {
|
|
505
617
|
return {
|
|
506
618
|
operation,
|
|
507
619
|
domain,
|
|
@@ -509,7 +621,7 @@ function task(operation, domain, statement) {
|
|
|
509
621
|
};
|
|
510
622
|
}
|
|
511
623
|
/**
|
|
512
|
-
*
|
|
624
|
+
* Assembles a `Reference` from a path and the note that justifies listing it.
|
|
513
625
|
*
|
|
514
626
|
* @param path - The referenced path or glob.
|
|
515
627
|
* @param note - Why the path is listed.
|
|
@@ -517,31 +629,31 @@ function task(operation, domain, statement) {
|
|
|
517
629
|
*
|
|
518
630
|
* @example
|
|
519
631
|
* ```ts
|
|
520
|
-
* import {
|
|
632
|
+
* import { buildReference } from '@orkestrel/brief'
|
|
521
633
|
*
|
|
522
|
-
*
|
|
634
|
+
* buildReference('AGENTS.md', 'project law') // { path: 'AGENTS.md', note: 'project law' }
|
|
523
635
|
* ```
|
|
524
636
|
*/
|
|
525
|
-
function
|
|
637
|
+
function buildReference(path, note) {
|
|
526
638
|
return {
|
|
527
639
|
path,
|
|
528
640
|
note
|
|
529
641
|
};
|
|
530
642
|
}
|
|
531
643
|
/**
|
|
532
|
-
*
|
|
644
|
+
* Assembles a `Manifest`, defaulting every absent partition to an empty list.
|
|
533
645
|
*
|
|
534
646
|
* @param partitions - The partitions to fill; a partial literal is enough.
|
|
535
|
-
* @returns A fresh `Manifest` with
|
|
647
|
+
* @returns A fresh `Manifest` with every partition present.
|
|
536
648
|
*
|
|
537
649
|
* @example
|
|
538
650
|
* ```ts
|
|
539
|
-
* import {
|
|
651
|
+
* import { buildManifest, buildReference } from '@orkestrel/brief'
|
|
540
652
|
*
|
|
541
|
-
*
|
|
653
|
+
* buildManifest({ edit: [buildReference('src/core/helpers.ts', 'implementation')] })
|
|
542
654
|
* ```
|
|
543
655
|
*/
|
|
544
|
-
function
|
|
656
|
+
function buildManifest(partitions) {
|
|
545
657
|
return {
|
|
546
658
|
read: partitions?.read ?? [],
|
|
547
659
|
edit: partitions?.edit ?? [],
|
|
@@ -550,22 +662,23 @@ function manifest(partitions) {
|
|
|
550
662
|
};
|
|
551
663
|
}
|
|
552
664
|
/**
|
|
553
|
-
*
|
|
665
|
+
* Assembles an `Outcome` from a rank and its result text.
|
|
554
666
|
*
|
|
555
667
|
* @param rank - The one-based rank; lower ranks matter more.
|
|
556
668
|
* @param text - The result, never a step.
|
|
557
|
-
* @param required -
|
|
669
|
+
* @param required - If `true`, the outcome gates "done"; if `false`, it is desirable but not
|
|
670
|
+
* blocking. Default: `true`.
|
|
558
671
|
* @returns A fresh `Outcome`.
|
|
559
672
|
*
|
|
560
673
|
* @example
|
|
561
674
|
* ```ts
|
|
562
|
-
* import {
|
|
675
|
+
* import { buildOutcome } from '@orkestrel/brief'
|
|
563
676
|
*
|
|
564
|
-
*
|
|
565
|
-
*
|
|
677
|
+
* buildOutcome(1, 'useForm uses native FormData with no behavior change') // required: true
|
|
678
|
+
* buildOutcome(2, 'the diff stays under 200 lines', false)
|
|
566
679
|
* ```
|
|
567
680
|
*/
|
|
568
|
-
function
|
|
681
|
+
function buildOutcome(rank, text, required = true) {
|
|
569
682
|
return {
|
|
570
683
|
rank,
|
|
571
684
|
text,
|
|
@@ -573,7 +686,7 @@ function outcome(rank, text, required = true) {
|
|
|
573
686
|
};
|
|
574
687
|
}
|
|
575
688
|
/**
|
|
576
|
-
*
|
|
689
|
+
* Assembles a `Given` from a category, a name, and a value.
|
|
577
690
|
*
|
|
578
691
|
* @param category - The kind of fact — a convention, a version, a constraint.
|
|
579
692
|
* @param name - The fact's name.
|
|
@@ -582,12 +695,12 @@ function outcome(rank, text, required = true) {
|
|
|
582
695
|
*
|
|
583
696
|
* @example
|
|
584
697
|
* ```ts
|
|
585
|
-
* import {
|
|
698
|
+
* import { buildGiven } from '@orkestrel/brief'
|
|
586
699
|
*
|
|
587
|
-
*
|
|
700
|
+
* buildGiven('convention', 'indentation', 'tabs')
|
|
588
701
|
* ```
|
|
589
702
|
*/
|
|
590
|
-
function
|
|
703
|
+
function buildGiven(category, name, value) {
|
|
591
704
|
return {
|
|
592
705
|
category,
|
|
593
706
|
name,
|
|
@@ -595,32 +708,32 @@ function given(category, name, value) {
|
|
|
595
708
|
};
|
|
596
709
|
}
|
|
597
710
|
/**
|
|
598
|
-
*
|
|
711
|
+
* Assembles an `Example` from an exemplar input and its expected output.
|
|
599
712
|
*
|
|
600
713
|
* @param input - The exemplar input.
|
|
601
|
-
* @param
|
|
714
|
+
* @param output - The expected output for that input.
|
|
602
715
|
* @param note - Optional detail; the key is OMITTED when absent.
|
|
603
716
|
* @returns A fresh `Example`.
|
|
604
717
|
*
|
|
605
718
|
* @example
|
|
606
719
|
* ```ts
|
|
607
|
-
* import {
|
|
720
|
+
* import { buildExample } from '@orkestrel/brief'
|
|
608
721
|
*
|
|
609
|
-
*
|
|
722
|
+
* buildExample('<input required>', 'validity read from el.validity')
|
|
610
723
|
* ```
|
|
611
724
|
*/
|
|
612
|
-
function
|
|
725
|
+
function buildExample(input, output, note) {
|
|
613
726
|
return note === void 0 ? {
|
|
614
727
|
input,
|
|
615
|
-
output
|
|
728
|
+
output
|
|
616
729
|
} : {
|
|
617
730
|
input,
|
|
618
|
-
output
|
|
731
|
+
output,
|
|
619
732
|
note
|
|
620
733
|
};
|
|
621
734
|
}
|
|
622
735
|
/**
|
|
623
|
-
*
|
|
736
|
+
* Assembles a `Citation` from a name, a URL, and the note that justifies citing it.
|
|
624
737
|
*
|
|
625
738
|
* @param name - The source's display name.
|
|
626
739
|
* @param url - Where the source lives.
|
|
@@ -629,16 +742,16 @@ function example(input, result, note) {
|
|
|
629
742
|
*
|
|
630
743
|
* @example
|
|
631
744
|
* ```ts
|
|
632
|
-
* import {
|
|
745
|
+
* import { buildCitation } from '@orkestrel/brief'
|
|
633
746
|
*
|
|
634
|
-
*
|
|
747
|
+
* buildCitation(
|
|
635
748
|
* 'MDN Constraint Validation',
|
|
636
749
|
* 'https://developer.mozilla.org/',
|
|
637
750
|
* 'the native validity behavior being adopted',
|
|
638
751
|
* )
|
|
639
752
|
* ```
|
|
640
753
|
*/
|
|
641
|
-
function
|
|
754
|
+
function buildCitation(name, url, note) {
|
|
642
755
|
return {
|
|
643
756
|
name,
|
|
644
757
|
url,
|
|
@@ -646,23 +759,23 @@ function citation(name, url, note) {
|
|
|
646
759
|
};
|
|
647
760
|
}
|
|
648
761
|
/**
|
|
649
|
-
*
|
|
762
|
+
* Assembles a `Gap` from the section it belongs to and the question that would close it.
|
|
650
763
|
*
|
|
651
764
|
* @param field - The brief section the unknown belongs to.
|
|
652
765
|
* @param question - The question that would close it.
|
|
653
|
-
* @param overrides - Optional `blocking`
|
|
654
|
-
*
|
|
766
|
+
* @param overrides - Optional `blocking` and `candidates`; an absent `candidates` key is
|
|
767
|
+
* OMITTED entirely. Default: `blocking: false`.
|
|
655
768
|
* @returns A fresh `Gap`.
|
|
656
769
|
*
|
|
657
770
|
* @example
|
|
658
771
|
* ```ts
|
|
659
|
-
* import {
|
|
772
|
+
* import { buildGap } from '@orkestrel/brief'
|
|
660
773
|
*
|
|
661
|
-
*
|
|
662
|
-
*
|
|
774
|
+
* buildGap('rules', 'Does validation message wording need to change?') // blocking: false
|
|
775
|
+
* buildGap('output', 'Diff or full files?', { blocking: true, candidates: ['diff', 'code'] })
|
|
663
776
|
* ```
|
|
664
777
|
*/
|
|
665
|
-
function
|
|
778
|
+
function buildGap(field, question, overrides) {
|
|
666
779
|
const blocking = overrides?.blocking ?? false;
|
|
667
780
|
return overrides?.candidates === void 0 ? {
|
|
668
781
|
field,
|
|
@@ -676,7 +789,7 @@ function gap(field, question, overrides) {
|
|
|
676
789
|
};
|
|
677
790
|
}
|
|
678
791
|
/**
|
|
679
|
-
*
|
|
792
|
+
* Assembles a `Risk` from a severity, what could go wrong, and the mitigation that answers it.
|
|
680
793
|
*
|
|
681
794
|
* @param severity - The closed severity.
|
|
682
795
|
* @param text - What could go wrong.
|
|
@@ -685,12 +798,12 @@ function gap(field, question, overrides) {
|
|
|
685
798
|
*
|
|
686
799
|
* @example
|
|
687
800
|
* ```ts
|
|
688
|
-
* import {
|
|
801
|
+
* import { buildRisk } from '@orkestrel/brief'
|
|
689
802
|
*
|
|
690
|
-
*
|
|
803
|
+
* buildRisk('medium', 'native validation differs subtly', 'assert message and state in tests')
|
|
691
804
|
* ```
|
|
692
805
|
*/
|
|
693
|
-
function
|
|
806
|
+
function buildRisk(severity, text, mitigation) {
|
|
694
807
|
return {
|
|
695
808
|
severity,
|
|
696
809
|
text,
|
|
@@ -698,7 +811,7 @@ function risk(severity, text, mitigation) {
|
|
|
698
811
|
};
|
|
699
812
|
}
|
|
700
813
|
/**
|
|
701
|
-
*
|
|
814
|
+
* Assembles an `Output` from a format plus its optional refinements.
|
|
702
815
|
*
|
|
703
816
|
* @param format - The closed deliverable format.
|
|
704
817
|
* @param overrides - Optional `sections` / `include` / `exclude`; absent keys are OMITTED.
|
|
@@ -706,13 +819,13 @@ function risk(severity, text, mitigation) {
|
|
|
706
819
|
*
|
|
707
820
|
* @example
|
|
708
821
|
* ```ts
|
|
709
|
-
* import {
|
|
822
|
+
* import { buildOutput } from '@orkestrel/brief'
|
|
710
823
|
*
|
|
711
|
-
*
|
|
712
|
-
*
|
|
824
|
+
* buildOutput('markdown') // { format: 'markdown' }
|
|
825
|
+
* buildOutput('diff', { include: ['updated useForm.ts'] })
|
|
713
826
|
* ```
|
|
714
827
|
*/
|
|
715
|
-
function
|
|
828
|
+
function buildOutput(format, overrides) {
|
|
716
829
|
return {
|
|
717
830
|
format,
|
|
718
831
|
...overrides?.sections === void 0 ? {} : { sections: overrides.sections },
|
|
@@ -721,7 +834,7 @@ function output(format, overrides) {
|
|
|
721
834
|
};
|
|
722
835
|
}
|
|
723
836
|
/**
|
|
724
|
-
*
|
|
837
|
+
* Assembles a `Proof` from what the check settles and the command that settles it.
|
|
725
838
|
*
|
|
726
839
|
* @param text - What the check settles.
|
|
727
840
|
* @param command - The command whose exit signal settles it.
|
|
@@ -729,41 +842,41 @@ function output(format, overrides) {
|
|
|
729
842
|
*
|
|
730
843
|
* @example
|
|
731
844
|
* ```ts
|
|
732
|
-
* import {
|
|
845
|
+
* import { buildProof } from '@orkestrel/brief'
|
|
733
846
|
*
|
|
734
|
-
*
|
|
847
|
+
* buildProof('type-check and lint pass', 'npm run check')
|
|
735
848
|
* ```
|
|
736
849
|
*/
|
|
737
|
-
function
|
|
850
|
+
function buildProof(text, command) {
|
|
738
851
|
return {
|
|
739
852
|
text,
|
|
740
853
|
command
|
|
741
854
|
};
|
|
742
855
|
}
|
|
743
856
|
/**
|
|
744
|
-
*
|
|
857
|
+
* Assembles a `Brief` from a `Task` plus section overrides.
|
|
745
858
|
*
|
|
746
859
|
* @param subject - The task the brief is about.
|
|
747
|
-
* @param overrides - Any sections to fill;
|
|
748
|
-
* `
|
|
749
|
-
* `
|
|
860
|
+
* @param overrides - Any sections to fill; `trace` / `hash` stay OMITTED so `pinBrief` can
|
|
861
|
+
* fill them. Default: `[]` for every absent collection and `buildOutput('markdown')` for
|
|
862
|
+
* `output`.
|
|
750
863
|
* @returns A fresh, unpinned `Brief`.
|
|
751
864
|
*
|
|
752
865
|
* @example
|
|
753
866
|
* ```ts
|
|
754
|
-
* import {
|
|
867
|
+
* import { buildBrief, buildOutcome, buildProof, buildTask } from '@orkestrel/brief'
|
|
755
868
|
*
|
|
756
|
-
*
|
|
757
|
-
* outcomes: [
|
|
758
|
-
* proofs: [
|
|
869
|
+
* buildBrief(buildTask('audit', 'code', 'Audit the barrel for undocumented exports.'), {
|
|
870
|
+
* outcomes: [buildOutcome(1, 'every export appears in the guide')],
|
|
871
|
+
* proofs: [buildProof('parity passes', 'npm run test:guides')],
|
|
759
872
|
* })
|
|
760
873
|
* ```
|
|
761
874
|
*/
|
|
762
|
-
function
|
|
875
|
+
function buildBrief(subject, overrides) {
|
|
763
876
|
return {
|
|
764
877
|
task: subject,
|
|
765
878
|
authority: overrides?.authority ?? [],
|
|
766
|
-
manifest: overrides?.manifest ??
|
|
879
|
+
manifest: overrides?.manifest ?? buildManifest(),
|
|
767
880
|
outcomes: overrides?.outcomes ?? [],
|
|
768
881
|
rules: overrides?.rules ?? [],
|
|
769
882
|
invariants: overrides?.invariants ?? [],
|
|
@@ -773,17 +886,17 @@ function brief(subject, overrides) {
|
|
|
773
886
|
citations: overrides?.citations ?? [],
|
|
774
887
|
gaps: overrides?.gaps ?? [],
|
|
775
888
|
risks: overrides?.risks ?? [],
|
|
776
|
-
output: overrides?.output ??
|
|
889
|
+
output: overrides?.output ?? buildOutput("markdown"),
|
|
777
890
|
proofs: overrides?.proofs ?? []
|
|
778
891
|
};
|
|
779
892
|
}
|
|
780
893
|
/**
|
|
781
|
-
*
|
|
894
|
+
* Assembles the fail-closed readiness gate as a reasons `LogicalDefinition`.
|
|
782
895
|
*
|
|
783
896
|
* @remarks
|
|
784
|
-
*
|
|
785
|
-
*
|
|
786
|
-
*
|
|
897
|
+
* Each readiness rule derives one named fact from `briefToSubject`'s measures, and a final
|
|
898
|
+
* `ready` rule conjoins them all. Forward chaining reports the LAST rule's conclusion, so
|
|
899
|
+
* `LogicalResult.conclusion` is exactly `ready`.
|
|
787
900
|
*
|
|
788
901
|
* The gate takes NO parameters, and that is deliberate rather than unfinished. The
|
|
789
902
|
* reasoner overlays every derived fact into one flat namespace, so a caller rule named
|
|
@@ -797,50 +910,50 @@ function brief(subject, overrides) {
|
|
|
797
910
|
*
|
|
798
911
|
* @example
|
|
799
912
|
* ```ts
|
|
800
|
-
* import { briefToSubject,
|
|
913
|
+
* import { briefToSubject, buildGateDefinition } from '@orkestrel/brief'
|
|
801
914
|
* import { createLogicalReasoner, createReason } from '@orkestrel/reason'
|
|
802
915
|
*
|
|
803
916
|
* const reason = createReason({ reasoners: [createLogicalReasoner()] })
|
|
804
|
-
* const verdict = reason.reason(briefToSubject(pinned),
|
|
917
|
+
* const verdict = reason.reason(briefToSubject(pinned), buildGateDefinition())
|
|
805
918
|
* reason.destroy()
|
|
806
919
|
* ```
|
|
807
920
|
*/
|
|
808
|
-
function
|
|
921
|
+
function buildGateDefinition() {
|
|
809
922
|
const readiness = [
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
923
|
+
createRule("specified", [createAtom("blocking", "equals", 0)], createAtom("specified", "equals", true)),
|
|
924
|
+
createRule("aimed", [createCompound("and", [createAtom("outcomes", "above", 0), createAtom("required", "above", 0)])], createAtom("aimed", "equals", true)),
|
|
925
|
+
createRule("proven", [createAtom("proofs", "above", 0)], createAtom("proven", "equals", true)),
|
|
926
|
+
createRule("disjoint", [createAtom("overlaps", "equals", 0)], createAtom("disjoint", "equals", true)),
|
|
927
|
+
createRule("granted", [createAtom("ungranted", "equals", 0)], createAtom("granted", "equals", true)),
|
|
928
|
+
createRule("single", [createAtom("sentences", "equals", 1)], createAtom("single", "equals", true))
|
|
816
929
|
];
|
|
817
|
-
return
|
|
930
|
+
return createLogicalDefinition(GATE_ID, "Brief readiness", [...readiness, createRule("ready", [createCompound("and", readiness.map((entry) => createAtom(entry.id, "equals", true)))], createAtom("ready", "equals", true))]);
|
|
818
931
|
}
|
|
819
932
|
/**
|
|
820
|
-
*
|
|
933
|
+
* Lists the readiness rules a brief fails, computed directly from its own measures.
|
|
821
934
|
*
|
|
822
935
|
* @remarks
|
|
823
|
-
* The gate's decision, in code. `
|
|
936
|
+
* The gate's decision, in code. `buildGateDefinition()` states the same rules as data for a
|
|
824
937
|
* reasoner to narrate, and a narration is not a decision: `BriefCompilerOptions.reason` lets a
|
|
825
938
|
* caller supply the engine, and an engine that answers "met" to everything would otherwise
|
|
826
939
|
* emit a brief with no proofs. `compile` refuses on THIS and keeps the verdict for its
|
|
827
940
|
* trace, so a supplied engine can add detail and never remove a refusal.
|
|
828
941
|
*
|
|
829
|
-
* The
|
|
830
|
-
* is what stops
|
|
942
|
+
* The data and the code must agree. `tests/src/core/helpers.test.ts` drives both over one
|
|
943
|
+
* value set, which is what stops them from drifting apart.
|
|
831
944
|
*
|
|
832
945
|
* @param source - The brief to measure.
|
|
833
946
|
* @returns The unmet rule ids, in gate order; empty when the brief is ready.
|
|
834
947
|
*
|
|
835
948
|
* @example
|
|
836
949
|
* ```ts
|
|
837
|
-
* import {
|
|
950
|
+
* import { buildBrief, buildOutcome, buildProof, buildTask, findUnmetRules } from '@orkestrel/brief'
|
|
838
951
|
*
|
|
839
|
-
* findUnmetRules(
|
|
952
|
+
* findUnmetRules(buildBrief(buildTask('plan', 'ops', 'Plan the release.'))) // ['aimed', 'proven']
|
|
840
953
|
* findUnmetRules(
|
|
841
|
-
*
|
|
842
|
-
* outcomes: [
|
|
843
|
-
* proofs: [
|
|
954
|
+
* buildBrief(buildTask('plan', 'ops', 'Plan the release.'), {
|
|
955
|
+
* outcomes: [buildOutcome(1, 'shipped')],
|
|
956
|
+
* proofs: [buildProof('x', 'npm test')],
|
|
844
957
|
* }),
|
|
845
958
|
* ) // []
|
|
846
959
|
* ```
|
|
@@ -856,7 +969,7 @@ function findUnmetRules(source) {
|
|
|
856
969
|
return unready;
|
|
857
970
|
}
|
|
858
971
|
/**
|
|
859
|
-
*
|
|
972
|
+
* Counts the sentences a statement holds.
|
|
860
973
|
*
|
|
861
974
|
* @remarks
|
|
862
975
|
* A terminator run (`.`, `!`, `?`) followed by whitespace or the end of the text closes one
|
|
@@ -894,17 +1007,17 @@ function countSentences(statement) {
|
|
|
894
1007
|
return /[.!?]$/u.test(text) ? matches.length : matches.length + 1;
|
|
895
1008
|
}
|
|
896
1009
|
/**
|
|
897
|
-
*
|
|
1010
|
+
* Lists the gaps that block emission.
|
|
898
1011
|
*
|
|
899
1012
|
* @param source - The brief to inspect.
|
|
900
1013
|
* @returns Every gap carrying `blocking: true`, in declaration order.
|
|
901
1014
|
*
|
|
902
1015
|
* @example
|
|
903
1016
|
* ```ts
|
|
904
|
-
* import {
|
|
1017
|
+
* import { buildBrief, buildGap, buildTask, findBlockingGaps } from '@orkestrel/brief'
|
|
905
1018
|
*
|
|
906
|
-
* const draft =
|
|
907
|
-
* gaps: [
|
|
1019
|
+
* const draft = buildBrief(buildTask('plan', 'ops', 'Plan the release.'), {
|
|
1020
|
+
* gaps: [buildGap('output', 'Diff or files?', { blocking: true })],
|
|
908
1021
|
* })
|
|
909
1022
|
* findBlockingGaps(draft).length // 1
|
|
910
1023
|
* ```
|
|
@@ -913,18 +1026,18 @@ function findBlockingGaps(source) {
|
|
|
913
1026
|
return source.gaps.filter((entry) => entry.blocking);
|
|
914
1027
|
}
|
|
915
1028
|
/**
|
|
916
|
-
*
|
|
1029
|
+
* Lists the authority paths the manifest never grants access to.
|
|
917
1030
|
*
|
|
918
1031
|
* @remarks
|
|
919
1032
|
* An authority the executor cannot open is an instruction it cannot follow, so every ranked
|
|
920
|
-
* path must appear in `read`, `edit`, or `locked`. Those
|
|
1033
|
+
* path must appear in `read`, `edit`, or `locked`. Those are the grants: `locked` is a
|
|
921
1034
|
* grant, because read-only is exactly what obeying a file requires.
|
|
922
1035
|
*
|
|
923
|
-
* This subsumes the narrower question of an authority sitting in `forbidden`. The
|
|
924
|
-
*
|
|
925
|
-
*
|
|
926
|
-
*
|
|
927
|
-
*
|
|
1036
|
+
* This subsumes the narrower question of an authority sitting in `forbidden`. The partitions
|
|
1037
|
+
* are disjoint — `findManifestOverlaps` and the `disjoint` rule enforce it — so a forbidden
|
|
1038
|
+
* path is in none of the grants and is reported here. An authority named in NO partition at
|
|
1039
|
+
* all is reported for the same reason, and that is the case a forbidden-only check misses
|
|
1040
|
+
* entirely: the brief simply never says the executor may open what it must obey.
|
|
928
1041
|
*
|
|
929
1042
|
* Paths are compared as EXACT strings, matching `findManifestOverlaps`. A glob is never
|
|
930
1043
|
* expanded, so `read: 'guides/**'` does not grant `authority: 'guides/brief.md'`. State a
|
|
@@ -935,11 +1048,17 @@ function findBlockingGaps(source) {
|
|
|
935
1048
|
*
|
|
936
1049
|
* @example
|
|
937
1050
|
* ```ts
|
|
938
|
-
* import {
|
|
939
|
-
*
|
|
940
|
-
*
|
|
941
|
-
*
|
|
942
|
-
*
|
|
1051
|
+
* import {
|
|
1052
|
+
* buildBrief,
|
|
1053
|
+
* buildManifest,
|
|
1054
|
+
* buildReference,
|
|
1055
|
+
* buildTask,
|
|
1056
|
+
* findUngrantedAuthority,
|
|
1057
|
+
* } from '@orkestrel/brief'
|
|
1058
|
+
*
|
|
1059
|
+
* const draft = buildBrief(buildTask('debug', 'code', 'Fix the leak.'), {
|
|
1060
|
+
* authority: [buildReference('AGENTS.md', 'project law')],
|
|
1061
|
+
* manifest: buildManifest(),
|
|
943
1062
|
* })
|
|
944
1063
|
* findUngrantedAuthority(draft) // ['AGENTS.md'] — ranked, but no partition opens it
|
|
945
1064
|
* ```
|
|
@@ -955,10 +1074,10 @@ function findUngrantedAuthority(source) {
|
|
|
955
1074
|
return ungranted;
|
|
956
1075
|
}
|
|
957
1076
|
/**
|
|
958
|
-
*
|
|
1077
|
+
* Lists the paths appearing in more than one manifest partition.
|
|
959
1078
|
*
|
|
960
1079
|
* @remarks
|
|
961
|
-
* Duplicates WITHIN one partition are not an overlap; the
|
|
1080
|
+
* Duplicates WITHIN one partition are not an overlap; the partitions must be
|
|
962
1081
|
* mutually disjoint, which is what `validateBrief` errors on.
|
|
963
1082
|
*
|
|
964
1083
|
* Paths are compared as EXACT strings. A glob is never expanded, so `edit: 'app/file.ts'`
|
|
@@ -970,12 +1089,18 @@ function findUngrantedAuthority(source) {
|
|
|
970
1089
|
*
|
|
971
1090
|
* @example
|
|
972
1091
|
* ```ts
|
|
973
|
-
* import {
|
|
974
|
-
*
|
|
975
|
-
*
|
|
976
|
-
*
|
|
977
|
-
*
|
|
978
|
-
*
|
|
1092
|
+
* import {
|
|
1093
|
+
* buildBrief,
|
|
1094
|
+
* buildManifest,
|
|
1095
|
+
* buildReference,
|
|
1096
|
+
* buildTask,
|
|
1097
|
+
* findManifestOverlaps,
|
|
1098
|
+
* } from '@orkestrel/brief'
|
|
1099
|
+
*
|
|
1100
|
+
* const draft = buildBrief(buildTask('debug', 'code', 'Fix the leak.'), {
|
|
1101
|
+
* manifest: buildManifest({
|
|
1102
|
+
* edit: [buildReference('src/core/BriefCompiler.ts', 'the leaking pipeline')],
|
|
1103
|
+
* locked: [buildReference('src/core/BriefCompiler.ts', 'the published contract')],
|
|
979
1104
|
* }),
|
|
980
1105
|
* })
|
|
981
1106
|
* findManifestOverlaps(draft) // ['src/core/BriefCompiler.ts']
|
|
@@ -995,7 +1120,7 @@ function findManifestOverlaps(source) {
|
|
|
995
1120
|
return overlaps;
|
|
996
1121
|
}
|
|
997
1122
|
/**
|
|
998
|
-
*
|
|
1123
|
+
* Lists the open gaps with no assumption to stand on.
|
|
999
1124
|
*
|
|
1000
1125
|
* @remarks
|
|
1001
1126
|
* The discipline is exactly one recorded assumption per open gap, so the open gaps past
|
|
@@ -1007,10 +1132,10 @@ function findManifestOverlaps(source) {
|
|
|
1007
1132
|
*
|
|
1008
1133
|
* @example
|
|
1009
1134
|
* ```ts
|
|
1010
|
-
* import {
|
|
1135
|
+
* import { buildBrief, buildGap, buildTask, findUnpairedGaps } from '@orkestrel/brief'
|
|
1011
1136
|
*
|
|
1012
|
-
* const draft =
|
|
1013
|
-
* gaps: [
|
|
1137
|
+
* const draft = buildBrief(buildTask('plan', 'ops', 'Plan the release.'), {
|
|
1138
|
+
* gaps: [buildGap('rules', 'Keep the wording?'), buildGap('output', 'Diff or files?')],
|
|
1014
1139
|
* assumptions: ['Wording is preserved.'],
|
|
1015
1140
|
* })
|
|
1016
1141
|
* findUnpairedGaps(draft).length // 1
|
|
@@ -1020,16 +1145,16 @@ function findUnpairedGaps(source) {
|
|
|
1020
1145
|
return source.gaps.filter((entry) => !entry.blocking).slice(source.assumptions.length);
|
|
1021
1146
|
}
|
|
1022
1147
|
/**
|
|
1023
|
-
*
|
|
1148
|
+
* Projects a brief into the reasons `Subject` of readiness measures the gate reads.
|
|
1024
1149
|
*
|
|
1025
1150
|
* @param source - The brief to measure.
|
|
1026
|
-
* @returns A flat record of counts plus the task's
|
|
1151
|
+
* @returns A flat record of counts plus the task's vocabulary values.
|
|
1027
1152
|
*
|
|
1028
1153
|
* @example
|
|
1029
1154
|
* ```ts
|
|
1030
|
-
* import {
|
|
1155
|
+
* import { briefToSubject, buildBrief, buildProof, buildTask } from '@orkestrel/brief'
|
|
1031
1156
|
*
|
|
1032
|
-
* briefToSubject(
|
|
1157
|
+
* briefToSubject(buildBrief(buildTask('test', 'code', 'Cover the gate.'), { proofs: [buildProof('x', 'y')] }))
|
|
1033
1158
|
* // { operation: 'test', domain: 'code', sentences: 1, proofs: 1, … }
|
|
1034
1159
|
* ```
|
|
1035
1160
|
*/
|
|
@@ -1056,7 +1181,7 @@ function briefToSubject(source) {
|
|
|
1056
1181
|
};
|
|
1057
1182
|
}
|
|
1058
1183
|
/**
|
|
1059
|
-
*
|
|
1184
|
+
* Runs the semantic pass over an already-shape-valid brief.
|
|
1060
1185
|
*
|
|
1061
1186
|
* @remarks
|
|
1062
1187
|
* ERRORS are the structural violations no assumption can paper over: a manifest
|
|
@@ -1070,11 +1195,11 @@ function briefToSubject(source) {
|
|
|
1070
1195
|
*
|
|
1071
1196
|
* @example
|
|
1072
1197
|
* ```ts
|
|
1073
|
-
* import {
|
|
1198
|
+
* import { buildBrief, buildProof, buildTask, validateBrief } from '@orkestrel/brief'
|
|
1074
1199
|
*
|
|
1075
|
-
* validateBrief(
|
|
1200
|
+
* validateBrief(buildBrief(buildTask('plan', 'ops', 'Plan the release.'))) // valid: false — no proofs
|
|
1076
1201
|
* validateBrief(
|
|
1077
|
-
*
|
|
1202
|
+
* buildBrief(buildTask('plan', 'ops', 'Plan the release.'), { proofs: [buildProof('ok', 'npm test')] }),
|
|
1078
1203
|
* ) // valid: true
|
|
1079
1204
|
* ```
|
|
1080
1205
|
*/
|
|
@@ -1102,7 +1227,7 @@ function validateBrief(source) {
|
|
|
1102
1227
|
};
|
|
1103
1228
|
}
|
|
1104
1229
|
/**
|
|
1105
|
-
*
|
|
1230
|
+
* Computes the canonical structural digest of a brief's content.
|
|
1106
1231
|
*
|
|
1107
1232
|
* @remarks
|
|
1108
1233
|
* `trace` and `hash` are stripped before digesting, so the value is the identity of what
|
|
@@ -1114,9 +1239,9 @@ function validateBrief(source) {
|
|
|
1114
1239
|
*
|
|
1115
1240
|
* @example
|
|
1116
1241
|
* ```ts
|
|
1117
|
-
* import {
|
|
1242
|
+
* import { briefToHash, buildBrief, buildTask, pinBrief } from '@orkestrel/brief'
|
|
1118
1243
|
*
|
|
1119
|
-
* const draft =
|
|
1244
|
+
* const draft = buildBrief(buildTask('plan', 'ops', 'Plan the release.'))
|
|
1120
1245
|
* briefToHash(draft) === briefToHash(pinBrief(draft)) // true — pinning does not move it
|
|
1121
1246
|
* ```
|
|
1122
1247
|
*/
|
|
@@ -1124,7 +1249,7 @@ function briefToHash(source) {
|
|
|
1124
1249
|
return digestValue(briefToContent(source));
|
|
1125
1250
|
}
|
|
1126
1251
|
/**
|
|
1127
|
-
*
|
|
1252
|
+
* Renders the canonical text of exactly what a brief's hash describes.
|
|
1128
1253
|
*
|
|
1129
1254
|
* @remarks
|
|
1130
1255
|
* `trace` and `hash` are stripped, then interprets `canonicalize` renders the rest in a
|
|
@@ -1136,9 +1261,9 @@ function briefToHash(source) {
|
|
|
1136
1261
|
*
|
|
1137
1262
|
* @example
|
|
1138
1263
|
* ```ts
|
|
1139
|
-
* import {
|
|
1264
|
+
* import { briefToContent, buildBrief, buildTask, pinBrief } from '@orkestrel/brief'
|
|
1140
1265
|
*
|
|
1141
|
-
* const draft =
|
|
1266
|
+
* const draft = buildBrief(buildTask('plan', 'ops', 'Plan the release.'))
|
|
1142
1267
|
* briefToContent(draft) === briefToContent(pinBrief(draft)) // true — pinning adds no content
|
|
1143
1268
|
* ```
|
|
1144
1269
|
*/
|
|
@@ -1147,7 +1272,7 @@ function briefToContent(source) {
|
|
|
1147
1272
|
return canonicalize(content);
|
|
1148
1273
|
}
|
|
1149
1274
|
/**
|
|
1150
|
-
*
|
|
1275
|
+
* Freezes a value and everything reachable from it.
|
|
1151
1276
|
*
|
|
1152
1277
|
* @remarks
|
|
1153
1278
|
* `Object.freeze` is SHALLOW, so freezing a record leaves every nested array and object
|
|
@@ -1161,7 +1286,7 @@ function briefToContent(source) {
|
|
|
1161
1286
|
* Reaches PLAIN objects and arrays, which is the whole of a `Brief` — it is JSON-serializable
|
|
1162
1287
|
* by contract. A `Map`, `Set`, or typed array is frozen as an object and its CONTENTS are left
|
|
1163
1288
|
* writable, and `Object.isFrozen` reports `true` for it either way. Nothing this package
|
|
1164
|
-
* produces contains one; a caller freezing their own value
|
|
1289
|
+
* produces contains one; the limit lands on a caller freezing their own value.
|
|
1165
1290
|
*
|
|
1166
1291
|
* @param value - The value to freeze in place; returned for convenience.
|
|
1167
1292
|
* @returns The same value, now deeply frozen.
|
|
@@ -1178,7 +1303,7 @@ function freezeDeep(value) {
|
|
|
1178
1303
|
return freezeBranch(value, /* @__PURE__ */ new WeakSet());
|
|
1179
1304
|
}
|
|
1180
1305
|
/**
|
|
1181
|
-
*
|
|
1306
|
+
* Freezes one branch of a value graph, skipping what the visited set already holds.
|
|
1182
1307
|
*
|
|
1183
1308
|
* @param value - The branch to freeze.
|
|
1184
1309
|
* @param seen - The objects already frozen on this walk; what makes a cycle terminate.
|
|
@@ -1200,7 +1325,7 @@ function freezeBranch(value, seen) {
|
|
|
1200
1325
|
return value;
|
|
1201
1326
|
}
|
|
1202
1327
|
/**
|
|
1203
|
-
*
|
|
1328
|
+
* Renders a value thrown by a stage into a message.
|
|
1204
1329
|
*
|
|
1205
1330
|
* @remarks
|
|
1206
1331
|
* TOTAL: it never throws, for any input. That is load-bearing rather than tidy, because this
|
|
@@ -1209,7 +1334,7 @@ function freezeBranch(value, seen) {
|
|
|
1209
1334
|
* falsifies the package's central promise that a failing stage yields an incomplete
|
|
1210
1335
|
* `Briefing` rather than an exception.
|
|
1211
1336
|
*
|
|
1212
|
-
*
|
|
1337
|
+
* Real inputs used to throw: an `Error` subclass whose `message` getter throws, a value
|
|
1213
1338
|
* whose string conversion throws, and a null-prototype object, which has no inherited
|
|
1214
1339
|
* conversion for String() to reach. Each is wrapped, and an unreadable value degrades to its
|
|
1215
1340
|
* type rather than propagating.
|
|
@@ -1233,10 +1358,10 @@ function errorToMessage(error) {
|
|
|
1233
1358
|
return `an unreadable ${typeof error} was thrown`;
|
|
1234
1359
|
}
|
|
1235
1360
|
/**
|
|
1236
|
-
*
|
|
1361
|
+
* Narrows unknown data to a `Brief`, throwing when it is off-contract.
|
|
1237
1362
|
*
|
|
1238
1363
|
* @remarks
|
|
1239
|
-
* The throwing half of the intake pair: this returns its argument by IDENTITY
|
|
1364
|
+
* The throwing half of the intake pair: this returns its argument by IDENTITY after the
|
|
1240
1365
|
* guard passes, while `parseBrief` returns `undefined` for bad input. It constructs
|
|
1241
1366
|
* nothing, so it is an assertion rather than a factory. Reserve it for programmer-error
|
|
1242
1367
|
* contexts where invalidity is a bug.
|
|
@@ -1250,24 +1375,24 @@ function errorToMessage(error) {
|
|
|
1250
1375
|
* `briefToTrace` read the value they are handed instead, so a caller reaching one of those
|
|
1251
1376
|
* directly owns that reading. Pass `assertBrief` a value you already own.
|
|
1252
1377
|
*
|
|
1253
|
-
* @param
|
|
1378
|
+
* @param value - The candidate brief value.
|
|
1254
1379
|
* @returns The same value, now known to satisfy {@link Brief}.
|
|
1255
|
-
* @throws {@link BriefError} `INVALID` when `
|
|
1380
|
+
* @throws {@link BriefError} `INVALID` when `value` fails `isBrief`.
|
|
1256
1381
|
*
|
|
1257
1382
|
* @example
|
|
1258
1383
|
* ```ts
|
|
1259
|
-
* import { assertBrief,
|
|
1384
|
+
* import { assertBrief, buildBrief, buildProof, buildTask } from '@orkestrel/brief'
|
|
1260
1385
|
*
|
|
1261
|
-
* assertBrief(
|
|
1386
|
+
* assertBrief(buildBrief(buildTask('plan', 'ops', 'Plan the release.'), { proofs: [buildProof('x', 'y')] }))
|
|
1262
1387
|
* assertBrief({ task: { operation: 'plan', domain: 'ops', statement: 'x.' } }) // throws INVALID
|
|
1263
1388
|
* ```
|
|
1264
1389
|
*/
|
|
1265
|
-
function assertBrief(
|
|
1266
|
-
if (!isBrief(
|
|
1267
|
-
return
|
|
1390
|
+
function assertBrief(value) {
|
|
1391
|
+
if (!isBrief(value)) throw new BriefError("INVALID", "Brief failed the exact-record contract", { field: "brief" });
|
|
1392
|
+
return value;
|
|
1268
1393
|
}
|
|
1269
1394
|
/**
|
|
1270
|
-
*
|
|
1395
|
+
* Returns a fresh brief with `trace` and `hash` derived from its own content.
|
|
1271
1396
|
*
|
|
1272
1397
|
* @remarks
|
|
1273
1398
|
* Deterministic: no clock, no randomness, no run-specific data. Any existing `trace` /
|
|
@@ -1283,9 +1408,9 @@ function assertBrief(data) {
|
|
|
1283
1408
|
*
|
|
1284
1409
|
* @example
|
|
1285
1410
|
* ```ts
|
|
1286
|
-
* import {
|
|
1411
|
+
* import { buildBrief, buildTask, pinBrief } from '@orkestrel/brief'
|
|
1287
1412
|
*
|
|
1288
|
-
* const pinned = pinBrief(
|
|
1413
|
+
* const pinned = pinBrief(buildBrief(buildTask('document', 'writing', 'Write the brief guide.')))
|
|
1289
1414
|
* pinned.hash // an 8-hex-digit structural digest
|
|
1290
1415
|
* pinned.trace // 'document/writing · outcomes:0 · gaps:0/0 · proofs:0'
|
|
1291
1416
|
* ```
|
|
@@ -1300,7 +1425,7 @@ function pinBrief(source) {
|
|
|
1300
1425
|
});
|
|
1301
1426
|
}
|
|
1302
1427
|
/**
|
|
1303
|
-
*
|
|
1428
|
+
* Renders the one-line census `pinBrief` stamps onto a brief.
|
|
1304
1429
|
*
|
|
1305
1430
|
* @remarks
|
|
1306
1431
|
* Extracted so it has ONE implementation. `pinBrief` derives it and `BriefManager` re-derives
|
|
@@ -1313,9 +1438,9 @@ function pinBrief(source) {
|
|
|
1313
1438
|
*
|
|
1314
1439
|
* @example
|
|
1315
1440
|
* ```ts
|
|
1316
|
-
* import {
|
|
1441
|
+
* import { briefToTrace, buildBrief, buildTask } from '@orkestrel/brief'
|
|
1317
1442
|
*
|
|
1318
|
-
* briefToTrace(
|
|
1443
|
+
* briefToTrace(buildBrief(buildTask('document', 'writing', 'Write the guide.')))
|
|
1319
1444
|
* // 'document/writing · outcomes:0 · gaps:0/0 · proofs:0'
|
|
1320
1445
|
* ```
|
|
1321
1446
|
*/
|
|
@@ -1328,7 +1453,7 @@ function briefToTrace(source) {
|
|
|
1328
1453
|
].join(" · ");
|
|
1329
1454
|
}
|
|
1330
1455
|
/**
|
|
1331
|
-
*
|
|
1456
|
+
* Renders one exemplar as markdown lines.
|
|
1332
1457
|
*
|
|
1333
1458
|
* @remarks
|
|
1334
1459
|
* An `Example`'s two sides are the only brief members permitted to span lines, so a
|
|
@@ -1340,9 +1465,9 @@ function briefToTrace(source) {
|
|
|
1340
1465
|
*
|
|
1341
1466
|
* @example
|
|
1342
1467
|
* ```ts
|
|
1343
|
-
* import {
|
|
1468
|
+
* import { buildExample, exampleToLines } from '@orkestrel/brief'
|
|
1344
1469
|
*
|
|
1345
|
-
* exampleToLines(
|
|
1470
|
+
* exampleToLines(buildExample('<input required>', 'el.validity')) // ['- ` <input required> ` → ` el.validity `']
|
|
1346
1471
|
* ```
|
|
1347
1472
|
*/
|
|
1348
1473
|
function exampleToLines(entry) {
|
|
@@ -1373,20 +1498,20 @@ function exampleToLines(entry) {
|
|
|
1373
1498
|
];
|
|
1374
1499
|
}
|
|
1375
1500
|
/**
|
|
1376
|
-
*
|
|
1501
|
+
* Projects a brief into the copy-ready agent prompt.
|
|
1377
1502
|
*
|
|
1378
1503
|
* @remarks
|
|
1379
1504
|
* Paths are REFERENCED, never inlined — the executor retrieves them. An empty section is
|
|
1380
1505
|
* omitted entirely, so the rendering carries no filler an executor must read past.
|
|
1381
1506
|
*
|
|
1382
|
-
* @param
|
|
1507
|
+
* @param input - The brief to render.
|
|
1383
1508
|
* @returns The markdown prompt.
|
|
1384
1509
|
*
|
|
1385
1510
|
* @example
|
|
1386
1511
|
* ```ts
|
|
1387
|
-
* import {
|
|
1512
|
+
* import { briefToMarkdown, buildBrief, buildTask } from '@orkestrel/brief'
|
|
1388
1513
|
*
|
|
1389
|
-
* briefToMarkdown(
|
|
1514
|
+
* briefToMarkdown(buildBrief(buildTask('review', 'code', 'Review the gate rules.')))
|
|
1390
1515
|
* // '# Brief: Review the gate rules.\n\nreview · code\n\n## Output\n\n- format: markdown\n'
|
|
1391
1516
|
* ```
|
|
1392
1517
|
*/
|
|
@@ -1480,21 +1605,21 @@ function briefToMarkdown(input) {
|
|
|
1480
1605
|
return lines.join("\n");
|
|
1481
1606
|
}
|
|
1482
1607
|
/**
|
|
1483
|
-
*
|
|
1608
|
+
* Projects a brief into a `/goal` completion condition.
|
|
1484
1609
|
*
|
|
1485
1610
|
* @remarks
|
|
1486
1611
|
* The proofs' commands VERBATIM plus a turn cap — the goal never adds a condition the
|
|
1487
1612
|
* brief does not carry.
|
|
1488
1613
|
*
|
|
1489
|
-
* @param
|
|
1490
|
-
* @param turns - The turn cap
|
|
1614
|
+
* @param input - The brief to render.
|
|
1615
|
+
* @param turns - The turn cap. Default: `DEFAULT_BRIEF_TURNS`.
|
|
1491
1616
|
* @returns The one-line completion condition.
|
|
1492
1617
|
*
|
|
1493
1618
|
* @example
|
|
1494
1619
|
* ```ts
|
|
1495
|
-
* import {
|
|
1620
|
+
* import { briefToGoal, buildBrief, buildProof, buildTask } from '@orkestrel/brief'
|
|
1496
1621
|
*
|
|
1497
|
-
* briefToGoal(
|
|
1622
|
+
* briefToGoal(buildBrief(buildTask('test', 'code', 'Cover the gate.'), { proofs: [buildProof('x', 'npm test')] }))
|
|
1498
1623
|
* // 'Done when every proof passes: npm test exits 0. Cap: 16 turns.'
|
|
1499
1624
|
* ```
|
|
1500
1625
|
*/
|
|
@@ -1503,27 +1628,33 @@ function briefToGoal(input, turns = 16) {
|
|
|
1503
1628
|
return `Done when every proof passes: ${source.proofs.length === 0 ? "no proofs recorded" : source.proofs.map((entry) => `${entry.command} exits 0`).join("; ")}. Cap: ${String(turns)} turns.`;
|
|
1504
1629
|
}
|
|
1505
1630
|
/**
|
|
1506
|
-
*
|
|
1631
|
+
* Projects a brief into a subagent `Dispatch`.
|
|
1507
1632
|
*
|
|
1508
1633
|
* @remarks
|
|
1509
1634
|
* `edit` is exactly `manifest.edit`, so two dispatches whose `edit` sets do not intersect
|
|
1510
1635
|
* can run concurrently under the same brief without conflict.
|
|
1511
1636
|
*
|
|
1512
1637
|
* `authority` is exactly `brief.authority` in rank order, and it is a SEPARATE axis from the
|
|
1513
|
-
*
|
|
1638
|
+
* permission sets rather than a further partition — a ranked path normally also appears in
|
|
1514
1639
|
* `read` or `locked`, because the executor has to open what it obeys. It is projected as
|
|
1515
1640
|
* paths so a machine consumer never has to parse `prompt`, which is written for a model.
|
|
1516
1641
|
*
|
|
1517
|
-
* @param
|
|
1518
|
-
* @returns The dispatch — the rendered prompt, the ranked authority, and the
|
|
1642
|
+
* @param input - The brief to project.
|
|
1643
|
+
* @returns The dispatch — the rendered prompt, the ranked authority, and the path sets.
|
|
1519
1644
|
*
|
|
1520
1645
|
* @example
|
|
1521
1646
|
* ```ts
|
|
1522
|
-
* import {
|
|
1523
|
-
*
|
|
1524
|
-
*
|
|
1525
|
-
*
|
|
1526
|
-
*
|
|
1647
|
+
* import {
|
|
1648
|
+
* briefToDispatch,
|
|
1649
|
+
* buildBrief,
|
|
1650
|
+
* buildManifest,
|
|
1651
|
+
* buildReference,
|
|
1652
|
+
* buildTask,
|
|
1653
|
+
* } from '@orkestrel/brief'
|
|
1654
|
+
*
|
|
1655
|
+
* const draft = buildBrief(buildTask('migrate', 'code', 'Migrate the stores.'), {
|
|
1656
|
+
* authority: [buildReference('AGENTS.md', 'project law')],
|
|
1657
|
+
* manifest: buildManifest({ edit: [buildReference('src/core/stores/**', 'the legacy stores')] }),
|
|
1527
1658
|
* })
|
|
1528
1659
|
* briefToDispatch(draft).edit // ['src/core/stores/**']
|
|
1529
1660
|
* briefToDispatch(draft).authority // ['AGENTS.md']
|
|
@@ -1541,36 +1672,39 @@ function briefToDispatch(input) {
|
|
|
1541
1672
|
};
|
|
1542
1673
|
}
|
|
1543
1674
|
/**
|
|
1544
|
-
*
|
|
1675
|
+
* Derives one imperative statement from free text.
|
|
1545
1676
|
*
|
|
1546
1677
|
* @remarks
|
|
1547
1678
|
* Whitespace collapses, the first character uppercases, and a terminator is appended
|
|
1548
1679
|
* when the text carries none. Nothing else is invented.
|
|
1549
1680
|
*
|
|
1550
1681
|
* @param text - The raw request text.
|
|
1551
|
-
* @returns The statement, or `
|
|
1682
|
+
* @returns The statement, or `undefined` for empty or whitespace-only text.
|
|
1552
1683
|
*
|
|
1553
1684
|
* @example
|
|
1554
1685
|
* ```ts
|
|
1555
1686
|
* import { deriveStatement } from '@orkestrel/brief'
|
|
1556
1687
|
*
|
|
1557
1688
|
* deriveStatement(' clean up useForm ') // 'Clean up useForm.'
|
|
1558
|
-
* deriveStatement('') //
|
|
1689
|
+
* deriveStatement('') // undefined
|
|
1559
1690
|
* ```
|
|
1560
1691
|
*/
|
|
1561
1692
|
function deriveStatement(text) {
|
|
1562
1693
|
const collapsed = collapseWhitespace(text);
|
|
1563
|
-
if (collapsed.length === 0) return
|
|
1694
|
+
if (collapsed.length === 0) return void 0;
|
|
1564
1695
|
const capitalized = collapsed.charAt(0).toUpperCase() + collapsed.slice(1);
|
|
1565
1696
|
return /[.!?]$/u.test(capitalized) ? capitalized : `${capitalized}.`;
|
|
1566
1697
|
}
|
|
1567
1698
|
/**
|
|
1568
|
-
*
|
|
1699
|
+
* Derives a `Task` from an interprets `Intent` through the caller's vocabularies.
|
|
1569
1700
|
*
|
|
1570
1701
|
* @remarks
|
|
1571
1702
|
* The vocabularies are the CALLER's policy: this maps and never guesses. An action or
|
|
1572
1703
|
* domain the caller did not map — or mapped to an off-vocabulary value — yields
|
|
1573
|
-
* `undefined` rather than an invented task. Inherited keys never resolve.
|
|
1704
|
+
* `undefined` rather than an invented task. Inherited keys never resolve. `Intent.action`
|
|
1705
|
+
* and `Intent.domain` are optional, because `classifyIntent` leaves an unmatched axis
|
|
1706
|
+
* absent, and an absent axis is unmapped by definition: it yields `undefined` before
|
|
1707
|
+
* either vocabulary is read.
|
|
1574
1708
|
*
|
|
1575
1709
|
* @param intent - The classified intent from an interpret pipeline.
|
|
1576
1710
|
* @param text - The text the statement derives from.
|
|
@@ -1589,16 +1723,17 @@ function deriveStatement(text) {
|
|
|
1589
1723
|
* ```
|
|
1590
1724
|
*/
|
|
1591
1725
|
function deriveTask(intent, text, actions, domains) {
|
|
1726
|
+
if (intent.action === void 0 || intent.domain === void 0) return void 0;
|
|
1592
1727
|
const operationDescriptor = Object.getOwnPropertyDescriptor(actions, intent.action);
|
|
1593
1728
|
const domainDescriptor = Object.getOwnPropertyDescriptor(domains, intent.domain);
|
|
1594
1729
|
const operation = operationDescriptor === void 0 ? void 0 : "value" in operationDescriptor ? operationDescriptor.value : operationDescriptor.get === void 0 ? void 0 : Reflect.apply(operationDescriptor.get, actions, []);
|
|
1595
1730
|
const domain = domainDescriptor === void 0 ? void 0 : "value" in domainDescriptor ? domainDescriptor.value : domainDescriptor.get === void 0 ? void 0 : Reflect.apply(domainDescriptor.get, domains, []);
|
|
1596
1731
|
if (!isTaskOperation(operation) || !isTaskDomain(domain)) return void 0;
|
|
1597
1732
|
const statement = deriveStatement(text);
|
|
1598
|
-
return statement
|
|
1733
|
+
return statement === void 0 ? void 0 : buildTask(operation, domain, statement);
|
|
1599
1734
|
}
|
|
1600
1735
|
/**
|
|
1601
|
-
*
|
|
1736
|
+
* Derives `Given[]` from an interprets `Entity[]`.
|
|
1602
1737
|
*
|
|
1603
1738
|
* @remarks
|
|
1604
1739
|
* Every extracted entity becomes one `extracted` fact. A nameless entity is dropped; an
|
|
@@ -1617,10 +1752,10 @@ function deriveTask(intent, text, actions, domains) {
|
|
|
1617
1752
|
* ```
|
|
1618
1753
|
*/
|
|
1619
1754
|
function deriveGivens(entities) {
|
|
1620
|
-
return entities.filter((entity) => entity.name.length > 0).map((entity) =>
|
|
1755
|
+
return entities.filter((entity) => entity.name.length > 0).map((entity) => buildGiven("extracted", entity.name, typeof entity.value === "string" ? entity.value : typeof entity.value === "object" && entity.value !== null ? canonicalize(entity.value) : String(entity.value)));
|
|
1621
1756
|
}
|
|
1622
1757
|
/**
|
|
1623
|
-
*
|
|
1758
|
+
* Derives `Gap[]` from an interprets `Ambiguity[]`.
|
|
1624
1759
|
*
|
|
1625
1760
|
* @remarks
|
|
1626
1761
|
* A REQUIRED ambiguity becomes a BLOCKING gap — the gate must fail closed on it. The
|
|
@@ -1641,7 +1776,7 @@ function deriveGivens(entities) {
|
|
|
1641
1776
|
function deriveGaps(ambiguities) {
|
|
1642
1777
|
return ambiguities.map((ambiguity) => {
|
|
1643
1778
|
const candidates = ambiguity.candidates.filter((candidate) => candidate.length > 0);
|
|
1644
|
-
return
|
|
1779
|
+
return buildGap(formatField(ambiguity.field), ambiguity.question, {
|
|
1645
1780
|
blocking: ambiguity.required,
|
|
1646
1781
|
...candidates.length === 0 ? {} : { candidates }
|
|
1647
1782
|
});
|
|
@@ -1650,7 +1785,7 @@ function deriveGaps(ambiguities) {
|
|
|
1650
1785
|
//#endregion
|
|
1651
1786
|
//#region src/core/parsers.ts
|
|
1652
1787
|
/**
|
|
1653
|
-
*
|
|
1788
|
+
* Parses a JSON string into a `Brief`.
|
|
1654
1789
|
*
|
|
1655
1790
|
* @remarks
|
|
1656
1791
|
* The parse-then-trust boundary for a stored brief, a tool argument, or an agent's
|
|
@@ -1684,7 +1819,7 @@ function parseBrief(value) {
|
|
|
1684
1819
|
//#endregion
|
|
1685
1820
|
//#region src/core/BriefManager.ts
|
|
1686
1821
|
/**
|
|
1687
|
-
*
|
|
1822
|
+
* Implements the self-owning, versioned and content-hashed brief registry.
|
|
1688
1823
|
*
|
|
1689
1824
|
* @remarks
|
|
1690
1825
|
* Record ids are MINTED from each brief's own content hash unless the caller names one,
|
|
@@ -1694,10 +1829,10 @@ function parseBrief(value) {
|
|
|
1694
1829
|
*
|
|
1695
1830
|
* @example
|
|
1696
1831
|
* ```ts
|
|
1697
|
-
* import { BriefManager,
|
|
1832
|
+
* import { BriefManager, buildBrief, buildTask } from '@orkestrel/brief'
|
|
1698
1833
|
*
|
|
1699
1834
|
* const briefs = new BriefManager()
|
|
1700
|
-
* const record = briefs.add(
|
|
1835
|
+
* const record = briefs.add(buildBrief(buildTask('document', 'writing', 'Write the brief guide.')))
|
|
1701
1836
|
* record.id === record.hash // true
|
|
1702
1837
|
* briefs.destroy()
|
|
1703
1838
|
* ```
|
|
@@ -1724,7 +1859,7 @@ var BriefManager = class {
|
|
|
1724
1859
|
get emitter() {
|
|
1725
1860
|
return this.#emitter;
|
|
1726
1861
|
}
|
|
1727
|
-
get
|
|
1862
|
+
get count() {
|
|
1728
1863
|
return this.#records.size;
|
|
1729
1864
|
}
|
|
1730
1865
|
has(id) {
|
|
@@ -1739,9 +1874,9 @@ var BriefManager = class {
|
|
|
1739
1874
|
this.#refuseDestroyed();
|
|
1740
1875
|
return [...this.#records.values()];
|
|
1741
1876
|
}
|
|
1742
|
-
add(
|
|
1877
|
+
add(brief, options) {
|
|
1743
1878
|
this.#refuseDestroyed();
|
|
1744
|
-
const record = this.#stage(
|
|
1879
|
+
const record = this.#stage(brief, this.#records, options);
|
|
1745
1880
|
this.#commit(record);
|
|
1746
1881
|
return record;
|
|
1747
1882
|
}
|
|
@@ -1808,7 +1943,7 @@ var BriefManager = class {
|
|
|
1808
1943
|
//#endregion
|
|
1809
1944
|
//#region src/core/BriefCompiler.ts
|
|
1810
1945
|
/**
|
|
1811
|
-
*
|
|
1946
|
+
* Implements the compilation orchestrator — the `[interpret, draft, gate, pin]` pipeline.
|
|
1812
1947
|
*
|
|
1813
1948
|
* @remarks
|
|
1814
1949
|
* `compile` is genuinely SYNCHRONOUS and never throws for a brief it cannot emit: a
|
|
@@ -1818,13 +1953,13 @@ var BriefManager = class {
|
|
|
1818
1953
|
*
|
|
1819
1954
|
* @example
|
|
1820
1955
|
* ```ts
|
|
1821
|
-
* import { BriefCompiler,
|
|
1956
|
+
* import { BriefCompiler, buildProof, buildTask } from '@orkestrel/brief'
|
|
1822
1957
|
*
|
|
1823
1958
|
* const compiler = new BriefCompiler()
|
|
1824
1959
|
* const briefing = compiler.compile({
|
|
1825
|
-
* task:
|
|
1960
|
+
* task: buildTask('audit', 'code', 'Audit the barrel for undocumented exports.'),
|
|
1826
1961
|
* outcomes: [{ rank: 1, text: 'every export appears in the guide', required: true }],
|
|
1827
|
-
* proofs: [
|
|
1962
|
+
* proofs: [buildProof('parity passes', 'npm run test:guides')],
|
|
1828
1963
|
* })
|
|
1829
1964
|
* briefing.brief !== undefined // true — the presence of the brief IS the completeness test
|
|
1830
1965
|
* compiler.destroy()
|
|
@@ -1975,9 +2110,9 @@ var BriefCompiler = class {
|
|
|
1975
2110
|
this.#emitter.emit("compile", briefing);
|
|
1976
2111
|
return briefing;
|
|
1977
2112
|
}
|
|
1978
|
-
gate(
|
|
2113
|
+
gate(brief) {
|
|
1979
2114
|
this.#refuseDestroyed();
|
|
1980
|
-
const ruled = attempt(() => this.#own(this.#reason.reason(briefToSubject(
|
|
2115
|
+
const ruled = attempt(() => this.#own(this.#reason.reason(briefToSubject(brief), buildGateDefinition()), [
|
|
1981
2116
|
"reasoning",
|
|
1982
2117
|
"conclusion",
|
|
1983
2118
|
"rules",
|
|
@@ -2013,25 +2148,9 @@ var BriefCompiler = class {
|
|
|
2013
2148
|
return cloned.success ? freezeDeep(cloned.value) : captureValue(value, members);
|
|
2014
2149
|
}
|
|
2015
2150
|
#read(input, raw, stages, failures) {
|
|
2016
|
-
const members = [
|
|
2017
|
-
"text",
|
|
2018
|
-
"normalized",
|
|
2019
|
-
"intent",
|
|
2020
|
-
"entities",
|
|
2021
|
-
"subject",
|
|
2022
|
-
"definition",
|
|
2023
|
-
"mappings",
|
|
2024
|
-
"ambiguities",
|
|
2025
|
-
"prompt",
|
|
2026
|
-
"stages",
|
|
2027
|
-
"failures",
|
|
2028
|
-
"complete",
|
|
2029
|
-
"confidence",
|
|
2030
|
-
"digest"
|
|
2031
|
-
];
|
|
2032
2151
|
const text = input.text;
|
|
2033
2152
|
if (text !== void 0) {
|
|
2034
|
-
const read = attempt(() => this.#own(this.#interpret.interpret(text),
|
|
2153
|
+
const read = attempt(() => this.#own(this.#interpret.interpret(text), INTERPRETATION_MEMBERS));
|
|
2035
2154
|
if (read.success && isInterpretation(read.value)) {
|
|
2036
2155
|
stages.push(Object.freeze({
|
|
2037
2156
|
stage: "interpret",
|
|
@@ -2056,7 +2175,7 @@ var BriefCompiler = class {
|
|
|
2056
2175
|
const supplied = input.interpretation;
|
|
2057
2176
|
if (supplied === void 0 || isInterpretation(supplied)) return supplied;
|
|
2058
2177
|
const live = raw.interpretation;
|
|
2059
|
-
const captured = attempt(() => captureValue(live,
|
|
2178
|
+
const captured = attempt(() => captureValue(live, INTERPRETATION_MEMBERS));
|
|
2060
2179
|
if (captured.success && isInterpretation(captured.value)) return captured.value;
|
|
2061
2180
|
const message = "The supplied interpretation does not satisfy the published shape";
|
|
2062
2181
|
stages.push(Object.freeze({
|
|
@@ -2082,7 +2201,7 @@ var BriefCompiler = class {
|
|
|
2082
2201
|
message: `Gate refused: ${unready.join(", ")}`
|
|
2083
2202
|
};
|
|
2084
2203
|
if (verdict === void 0) return void 0;
|
|
2085
|
-
const refused = verdict.rules.filter((entry) => !entry.
|
|
2204
|
+
const refused = verdict.rules.filter((entry) => !entry.applied).map((entry) => entry.id).join(", ");
|
|
2086
2205
|
if (refused.length === 0) return {
|
|
2087
2206
|
stage: "gate",
|
|
2088
2207
|
code: "BLOCKED",
|
|
@@ -2097,7 +2216,7 @@ var BriefCompiler = class {
|
|
|
2097
2216
|
#unresolved(interpretation, failures) {
|
|
2098
2217
|
if (interpretation !== void 0) return [];
|
|
2099
2218
|
if (!failures.some((entry) => entry.stage === "interpret")) return [];
|
|
2100
|
-
return [
|
|
2219
|
+
return [buildGap("gaps", "The interpret stage failed, so the request is unread and its unknowns are unknown", { blocking: true })];
|
|
2101
2220
|
}
|
|
2102
2221
|
#draft(input, interpretation, unresolved) {
|
|
2103
2222
|
const derived = interpretation === void 0 ? void 0 : deriveTask(interpretation.intent, interpretation.text, this.#actions, this.#domains);
|
|
@@ -2106,9 +2225,9 @@ var BriefCompiler = class {
|
|
|
2106
2225
|
stage: "draft",
|
|
2107
2226
|
field: "task"
|
|
2108
2227
|
});
|
|
2109
|
-
return snapshotBrief(
|
|
2228
|
+
return snapshotBrief(buildBrief(subject, {
|
|
2110
2229
|
authority: input.authority ?? [],
|
|
2111
|
-
manifest: input.manifest ??
|
|
2230
|
+
manifest: input.manifest ?? buildManifest(),
|
|
2112
2231
|
outcomes: input.outcomes ?? [],
|
|
2113
2232
|
rules: input.rules ?? [],
|
|
2114
2233
|
invariants: input.invariants ?? [],
|
|
@@ -2122,7 +2241,7 @@ var BriefCompiler = class {
|
|
|
2122
2241
|
...input.gaps ?? []
|
|
2123
2242
|
],
|
|
2124
2243
|
risks: input.risks ?? [],
|
|
2125
|
-
output: input.output ??
|
|
2244
|
+
output: input.output ?? buildOutput("markdown"),
|
|
2126
2245
|
proofs: input.proofs ?? []
|
|
2127
2246
|
}));
|
|
2128
2247
|
}
|
|
@@ -2150,7 +2269,7 @@ var BriefCompiler = class {
|
|
|
2150
2269
|
//#endregion
|
|
2151
2270
|
//#region src/core/factories.ts
|
|
2152
2271
|
/**
|
|
2153
|
-
*
|
|
2272
|
+
* Creates a compilation orchestrator.
|
|
2154
2273
|
*
|
|
2155
2274
|
* @remarks
|
|
2156
2275
|
* With no engines supplied the compiler wires its own: a default `createInterpret()`
|
|
@@ -2158,7 +2277,8 @@ var BriefCompiler = class {
|
|
|
2158
2277
|
* `createReason` carrying one `LogicalReasoner` for the gate. Pass your own to share
|
|
2159
2278
|
* instances or observe their emitters — the compiler destroys ONLY what it created.
|
|
2160
2279
|
*
|
|
2161
|
-
* @param options - Engines to borrow, the
|
|
2280
|
+
* @param options - Engines to borrow, the `actions` and `domains` intent vocabularies, and
|
|
2281
|
+
* emitter hooks.
|
|
2162
2282
|
* @returns A working {@link BriefCompilerInterface}.
|
|
2163
2283
|
*
|
|
2164
2284
|
* @example
|
|
@@ -2173,7 +2293,7 @@ function createBriefCompiler(options) {
|
|
|
2173
2293
|
return new BriefCompiler(options);
|
|
2174
2294
|
}
|
|
2175
2295
|
/**
|
|
2176
|
-
*
|
|
2296
|
+
* Creates a brief registry.
|
|
2177
2297
|
*
|
|
2178
2298
|
* @param options - An optional seed collection plus emitter hooks.
|
|
2179
2299
|
* @returns A working {@link BriefManagerInterface}.
|
|
@@ -2183,7 +2303,7 @@ function createBriefCompiler(options) {
|
|
|
2183
2303
|
* import { createBriefManager } from '@orkestrel/brief'
|
|
2184
2304
|
*
|
|
2185
2305
|
* const briefs = createBriefManager()
|
|
2186
|
-
* briefs.
|
|
2306
|
+
* briefs.count // 0
|
|
2187
2307
|
* briefs.destroy()
|
|
2188
2308
|
* ```
|
|
2189
2309
|
*/
|
|
@@ -2191,7 +2311,7 @@ function createBriefManager(options) {
|
|
|
2191
2311
|
return new BriefManager(options);
|
|
2192
2312
|
}
|
|
2193
2313
|
/**
|
|
2194
|
-
*
|
|
2314
|
+
* Compiles `briefShape` into a guard, parser, JSON Schema, and seeded generator bundle.
|
|
2195
2315
|
*
|
|
2196
2316
|
* @remarks
|
|
2197
2317
|
* The schema is what a tool boundary needs — hand it to `schemaToParameters` — and
|
|
@@ -2215,6 +2335,6 @@ function createBriefContract() {
|
|
|
2215
2335
|
return createContract(briefShape);
|
|
2216
2336
|
}
|
|
2217
2337
|
//#endregion
|
|
2218
|
-
export { BLANK_PATTERN, BriefCompiler, BriefError, BriefManager, DEFAULT_BRIEF_TURNS, GATE_ID, LINE_BREAK_PATTERN, OUTPUT_FORMATS, RISK_SEVERITIES, SINGLE_LINE_PATTERN, TASK_DOMAINS, TASK_OPERATIONS, assertBrief,
|
|
2338
|
+
export { BLANK_PATTERN, BriefCompiler, BriefError, BriefManager, DEFAULT_BRIEF_TURNS, GATE_ID, INTERPRETATION_MEMBERS, LINE_BREAK_PATTERN, OUTPUT_FORMATS, RISK_SEVERITIES, SINGLE_LINE_PATTERN, TASK_DOMAINS, TASK_OPERATIONS, assertBrief, briefShape, briefToContent, briefToDispatch, briefToGoal, briefToHash, briefToMarkdown, briefToSubject, briefToTrace, buildBrief, buildCitation, buildExample, buildGap, buildGateDefinition, buildGiven, buildManifest, buildOutcome, buildOutput, buildProof, buildReference, buildRisk, buildTask, captureValue, citationShape, countSentences, createBriefCompiler, createBriefContract, createBriefManager, deriveGaps, deriveGivens, deriveStatement, deriveTask, errorToMessage, exampleShape, exampleToLines, findBlockingGaps, findManifestOverlaps, findUngrantedAuthority, findUnmetRules, findUnpairedGaps, freezeBranch, freezeDeep, gapShape, givenShape, isBrief, isBriefError, isCitation, isExample, isGap, isGiven, isLine, isManifest, isOutcome, isOutput, isOutputFormat, isProof, isReference, isRisk, isRiskSeverity, isTask, isTaskDomain, isTaskOperation, isText, lineShape, manifestShape, outcomeShape, outputShape, parseBrief, pinBrief, proofShape, referenceShape, riskShape, snapshotBrief, taskShape, textShape, validateBrief };
|
|
2219
2339
|
|
|
2220
2340
|
//# sourceMappingURL=index.js.map
|