@orkestrel/brief 0.0.6 → 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 +380 -274
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +594 -485
- package/dist/src/core/index.d.ts +594 -485
- package/dist/src/core/index.js +369 -263
- package/dist/src/core/index.js.map +1 -1
- package/package.json +16 -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,14 +37,14 @@ 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
48
|
*
|
|
49
49
|
* @remarks
|
|
50
50
|
* The capture list `BriefCompiler` hands `captureValue` at each interpret door — the borrowed
|
|
@@ -69,33 +69,32 @@ var INTERPRETATION_MEMBERS = Object.freeze([
|
|
|
69
69
|
"prompt",
|
|
70
70
|
"stages",
|
|
71
71
|
"failures",
|
|
72
|
-
"complete",
|
|
73
72
|
"confidence",
|
|
74
73
|
"digest"
|
|
75
74
|
]);
|
|
76
75
|
/**
|
|
77
|
-
* `16` — the default turn cap `briefToGoal` renders.
|
|
76
|
+
* Holds `16` — the default turn cap `briefToGoal` renders.
|
|
78
77
|
*
|
|
79
78
|
* @remarks
|
|
80
79
|
* Domain-qualified so the barrel stays collision-free as sibling modules add their own
|
|
81
80
|
* turn defaults.
|
|
82
81
|
*/
|
|
83
82
|
var DEFAULT_BRIEF_TURNS = 16;
|
|
84
|
-
/** `'gate'` — the id of the `
|
|
83
|
+
/** Holds `'gate'` — the id of the `buildGateDefinition()` logical definition. */
|
|
85
84
|
var GATE_ID = "gate";
|
|
86
85
|
/**
|
|
87
|
-
*
|
|
86
|
+
* Matches every line terminator a brief field refuses.
|
|
88
87
|
*
|
|
89
88
|
* @remarks
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
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
|
|
94
93
|
* `lastIndex` between calls.
|
|
95
94
|
*/
|
|
96
95
|
var LINE_BREAK_PATTERN = /\r\n|[\n\r\u2028\u2029]/;
|
|
97
96
|
/**
|
|
98
|
-
*
|
|
97
|
+
* Holds the positive form of {@link LINE_BREAK_PATTERN}, for the shape DSL.
|
|
99
98
|
*
|
|
100
99
|
* @remarks
|
|
101
100
|
* `stringShape`'s `pattern` must MATCH an accepted value, so the guard's refusal regex
|
|
@@ -104,7 +103,7 @@ var LINE_BREAK_PATTERN = /\r\n|[\n\r\u2028\u2029]/;
|
|
|
104
103
|
*/
|
|
105
104
|
var SINGLE_LINE_PATTERN = /^[^\n\r\u2028\u2029]*$/;
|
|
106
105
|
/**
|
|
107
|
-
*
|
|
106
|
+
* Matches a string of one or more spaces and nothing else.
|
|
108
107
|
*
|
|
109
108
|
* @remarks
|
|
110
109
|
* The one exemplar side `exampleToLines` must NOT pad. CommonMark strips a fully-blank code
|
|
@@ -118,7 +117,7 @@ var BLANK_PATTERN = /^ +$/;
|
|
|
118
117
|
//#endregion
|
|
119
118
|
//#region src/core/errors.ts
|
|
120
119
|
/**
|
|
121
|
-
*
|
|
120
|
+
* Represents the one error class this package throws.
|
|
122
121
|
*
|
|
123
122
|
* @remarks
|
|
124
123
|
* Throws are reserved for caller misuse: `assertBrief`, `snapshotBrief`, and `pinBrief` on
|
|
@@ -148,10 +147,10 @@ var BriefError = class extends Error {
|
|
|
148
147
|
}
|
|
149
148
|
};
|
|
150
149
|
/**
|
|
151
|
-
*
|
|
150
|
+
* Narrows a caught value to a {@link BriefError}.
|
|
152
151
|
*
|
|
153
152
|
* @param value - The caught value to inspect.
|
|
154
|
-
* @returns
|
|
153
|
+
* @returns True if `value` is a `BriefError`; false otherwise.
|
|
155
154
|
*
|
|
156
155
|
* @example
|
|
157
156
|
* ```ts
|
|
@@ -169,82 +168,82 @@ function isBriefError(value) {
|
|
|
169
168
|
}
|
|
170
169
|
//#endregion
|
|
171
170
|
//#region src/core/shapers.ts
|
|
172
|
-
/**
|
|
171
|
+
/** Describes a single-line string of any length, including empty. */
|
|
173
172
|
var textShape = stringShape({ pattern: SINGLE_LINE_PATTERN });
|
|
174
|
-
/**
|
|
173
|
+
/** Describes a non-empty single-line string — the shape mirror of `isLine`. */
|
|
175
174
|
var lineShape = stringShape({
|
|
176
175
|
min: 1,
|
|
177
176
|
pattern: SINGLE_LINE_PATTERN
|
|
178
177
|
});
|
|
179
|
-
/**
|
|
178
|
+
/** Describes the `Task` shape — closed operation and domain vocabularies plus a non-empty statement. */
|
|
180
179
|
var taskShape = objectShape({
|
|
181
180
|
operation: literalShape(TASK_OPERATIONS),
|
|
182
181
|
domain: literalShape(TASK_DOMAINS),
|
|
183
182
|
statement: lineShape
|
|
184
183
|
}, { description: "What the brief asks for, in one imperative sentence." });
|
|
185
|
-
/**
|
|
184
|
+
/** Describes the `Reference` shape — a path and the note that justifies listing it. */
|
|
186
185
|
var referenceShape = objectShape({
|
|
187
186
|
path: lineShape,
|
|
188
187
|
note: lineShape
|
|
189
188
|
}, { description: "One referenced path and why it is listed." });
|
|
190
|
-
/**
|
|
189
|
+
/** Describes the `Manifest` shape — disjoint reference partitions. */
|
|
191
190
|
var manifestShape = objectShape({
|
|
192
191
|
read: arrayShape(referenceShape),
|
|
193
192
|
edit: arrayShape(referenceShape),
|
|
194
193
|
locked: arrayShape(referenceShape),
|
|
195
194
|
forbidden: arrayShape(referenceShape)
|
|
196
|
-
}, { description: "The
|
|
197
|
-
/**
|
|
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. */
|
|
198
197
|
var outcomeShape = objectShape({
|
|
199
198
|
rank: integerShape({ min: 1 }),
|
|
200
199
|
text: lineShape,
|
|
201
200
|
required: booleanShape()
|
|
202
201
|
}, { description: "One ranked outcome — a result, never a step." });
|
|
203
|
-
/**
|
|
202
|
+
/** Describes the `Given` shape — one categorized context fact. */
|
|
204
203
|
var givenShape = objectShape({
|
|
205
204
|
category: lineShape,
|
|
206
205
|
name: lineShape,
|
|
207
206
|
value: textShape
|
|
208
207
|
}, { description: "One context fact handed to the executor." });
|
|
209
|
-
/**
|
|
208
|
+
/** Describes the `Example` shape — one input to output exemplar. */
|
|
210
209
|
var exampleShape = objectShape({
|
|
211
210
|
input: stringShape({ min: 1 }),
|
|
212
211
|
output: stringShape({ min: 1 }),
|
|
213
212
|
note: optionalShape(lineShape)
|
|
214
213
|
}, { description: "One input to output exemplar." });
|
|
215
|
-
/**
|
|
214
|
+
/** Describes the `Citation` shape — a name, a locator, and why the source is cited. */
|
|
216
215
|
var citationShape = objectShape({
|
|
217
216
|
name: lineShape,
|
|
218
217
|
url: lineShape,
|
|
219
218
|
note: lineShape
|
|
220
219
|
}, { description: "One external source; list order is the trust order." });
|
|
221
|
-
/**
|
|
220
|
+
/** Describes the `Gap` shape — an unknown, whether it blocks, and the candidates that would close it. */
|
|
222
221
|
var gapShape = objectShape({
|
|
223
222
|
field: lineShape,
|
|
224
223
|
question: lineShape,
|
|
225
224
|
blocking: booleanShape(),
|
|
226
225
|
candidates: optionalShape(arrayShape(lineShape))
|
|
227
226
|
}, { description: "One unresolved decision; blocking means the gate fails closed." });
|
|
228
|
-
/**
|
|
227
|
+
/** Describes the `Risk` shape — a closed severity, the risk, and its mitigation. */
|
|
229
228
|
var riskShape = objectShape({
|
|
230
229
|
severity: literalShape(RISK_SEVERITIES),
|
|
231
230
|
text: lineShape,
|
|
232
231
|
mitigation: lineShape
|
|
233
232
|
}, { description: "One pre-empted risk and the mitigation that answers it." });
|
|
234
|
-
/**
|
|
233
|
+
/** Describes the `Output` shape — a closed format plus its optional refinements. */
|
|
235
234
|
var outputShape = objectShape({
|
|
236
235
|
format: literalShape(OUTPUT_FORMATS),
|
|
237
236
|
sections: optionalShape(arrayShape(lineShape)),
|
|
238
237
|
include: optionalShape(arrayShape(lineShape)),
|
|
239
238
|
exclude: optionalShape(arrayShape(lineShape))
|
|
240
239
|
}, { description: "The closed shape of the deliverable." });
|
|
241
|
-
/**
|
|
240
|
+
/** Describes the `Proof` shape — the claim and the command that settles it. */
|
|
242
241
|
var proofShape = objectShape({
|
|
243
242
|
text: lineShape,
|
|
244
243
|
command: lineShape
|
|
245
244
|
}, { description: "One mechanical, transcript-provable check." });
|
|
246
245
|
/**
|
|
247
|
-
*
|
|
246
|
+
* Describes the whole `Brief` shape, section shapes composed.
|
|
248
247
|
*
|
|
249
248
|
* @remarks
|
|
250
249
|
* `trace` and `hash` are optional because `pinBrief` fills them; an unpinned draft is
|
|
@@ -271,40 +270,82 @@ var briefShape = objectShape({
|
|
|
271
270
|
//#endregion
|
|
272
271
|
//#region src/core/validators.ts
|
|
273
272
|
/**
|
|
274
|
-
*
|
|
273
|
+
* Checks whether the value is a string holding no line terminator, empty included.
|
|
275
274
|
*
|
|
276
275
|
* @remarks
|
|
277
276
|
* `briefToMarkdown` renders each brief field as ONE markdown row, so a field carrying a
|
|
278
277
|
* line break would forge a heading or an extra manifest row — which is how a rendered
|
|
279
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.
|
|
280
283
|
*/
|
|
281
284
|
var isText = (value) => isString(value) && !LINE_BREAK_PATTERN.test(value);
|
|
282
|
-
/**
|
|
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
|
+
*/
|
|
283
291
|
var isLine = andOf(isNonEmptyString, isText);
|
|
284
|
-
/**
|
|
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
|
+
*/
|
|
285
298
|
var isTaskOperation = literalOf(TASK_OPERATIONS);
|
|
286
|
-
/**
|
|
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
|
+
*/
|
|
287
305
|
var isTaskDomain = literalOf(TASK_DOMAINS);
|
|
288
|
-
/**
|
|
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
|
+
*/
|
|
289
312
|
var isOutputFormat = literalOf(OUTPUT_FORMATS);
|
|
290
|
-
/**
|
|
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
|
+
*/
|
|
291
319
|
var isRiskSeverity = literalOf(RISK_SEVERITIES);
|
|
292
|
-
/**
|
|
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
|
+
*/
|
|
293
326
|
var isTask = recordOf({
|
|
294
327
|
operation: isTaskOperation,
|
|
295
328
|
domain: isTaskDomain,
|
|
296
329
|
statement: isLine
|
|
297
330
|
});
|
|
298
|
-
/**
|
|
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
|
+
*/
|
|
299
337
|
var isReference = recordOf({
|
|
300
338
|
path: isLine,
|
|
301
339
|
note: isLine
|
|
302
340
|
});
|
|
303
341
|
/**
|
|
304
|
-
*
|
|
342
|
+
* Checks whether the value is a well-formed `Manifest`.
|
|
305
343
|
*
|
|
306
344
|
* @remarks
|
|
307
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.
|
|
308
349
|
*/
|
|
309
350
|
var isManifest = recordOf({
|
|
310
351
|
read: arrayOf(isReference),
|
|
@@ -312,50 +353,83 @@ var isManifest = recordOf({
|
|
|
312
353
|
locked: arrayOf(isReference),
|
|
313
354
|
forbidden: arrayOf(isReference)
|
|
314
355
|
});
|
|
315
|
-
/**
|
|
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
|
+
*/
|
|
316
362
|
var isOutcome = recordOf({
|
|
317
363
|
rank: andOf(isInteger, boundsOf(1)),
|
|
318
364
|
text: isLine,
|
|
319
365
|
required: isBoolean
|
|
320
366
|
});
|
|
321
|
-
/**
|
|
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
|
+
*/
|
|
322
373
|
var isGiven = recordOf({
|
|
323
374
|
category: isLine,
|
|
324
375
|
name: isLine,
|
|
325
376
|
value: isText
|
|
326
377
|
});
|
|
327
378
|
/**
|
|
328
|
-
*
|
|
379
|
+
* Checks whether the value is a well-formed `Example`.
|
|
329
380
|
*
|
|
330
381
|
* @remarks
|
|
331
382
|
* An exemplar's two sides are the ONLY members a brief lets span lines, because they
|
|
332
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.
|
|
333
387
|
*/
|
|
334
388
|
var isExample = recordOf({
|
|
335
389
|
input: isNonEmptyString,
|
|
336
390
|
output: isNonEmptyString,
|
|
337
391
|
note: isLine
|
|
338
392
|
}, ["note"]);
|
|
339
|
-
/**
|
|
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
|
+
*/
|
|
340
399
|
var isCitation = recordOf({
|
|
341
400
|
name: isLine,
|
|
342
401
|
url: isLine,
|
|
343
402
|
note: isLine
|
|
344
403
|
});
|
|
345
|
-
/**
|
|
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
|
+
*/
|
|
346
410
|
var isGap = recordOf({
|
|
347
411
|
field: isLine,
|
|
348
412
|
question: isLine,
|
|
349
413
|
blocking: isBoolean,
|
|
350
414
|
candidates: arrayOf(isLine)
|
|
351
415
|
}, ["candidates"]);
|
|
352
|
-
/**
|
|
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
|
+
*/
|
|
353
422
|
var isRisk = recordOf({
|
|
354
423
|
severity: isRiskSeverity,
|
|
355
424
|
text: isLine,
|
|
356
425
|
mitigation: isLine
|
|
357
426
|
});
|
|
358
|
-
/**
|
|
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
|
+
*/
|
|
359
433
|
var isOutput = recordOf({
|
|
360
434
|
format: isOutputFormat,
|
|
361
435
|
sections: arrayOf(isLine),
|
|
@@ -366,17 +440,25 @@ var isOutput = recordOf({
|
|
|
366
440
|
"include",
|
|
367
441
|
"exclude"
|
|
368
442
|
]);
|
|
369
|
-
/**
|
|
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
|
+
*/
|
|
370
449
|
var isProof = recordOf({
|
|
371
450
|
text: isLine,
|
|
372
451
|
command: isLine
|
|
373
452
|
});
|
|
374
453
|
/**
|
|
375
|
-
*
|
|
454
|
+
* Checks whether the value satisfies the whole exact-record `Brief` contract.
|
|
376
455
|
*
|
|
377
456
|
* @remarks
|
|
378
457
|
* Every section must be present; an extra key fails. `trace` and `hash` are the only
|
|
379
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.
|
|
380
462
|
*/
|
|
381
463
|
var isBrief = recordOf({
|
|
382
464
|
task: isTask,
|
|
@@ -477,7 +559,7 @@ function captureValue(source, members) {
|
|
|
477
559
|
return target;
|
|
478
560
|
}
|
|
479
561
|
/**
|
|
480
|
-
*
|
|
562
|
+
* Returns a deeply owned, deeply frozen copy of a brief, refusing anything off-contract.
|
|
481
563
|
*
|
|
482
564
|
* @remarks
|
|
483
565
|
* The one reading boundary this package has, used by the pin, the registry, and every
|
|
@@ -501,10 +583,10 @@ function captureValue(source, members) {
|
|
|
501
583
|
*
|
|
502
584
|
* @example
|
|
503
585
|
* ```ts
|
|
504
|
-
* import {
|
|
586
|
+
* import { buildBrief, buildOutcome, buildTask, snapshotBrief } from '@orkestrel/brief'
|
|
505
587
|
*
|
|
506
|
-
* const outcomes = [
|
|
507
|
-
* const owned = snapshotBrief(
|
|
588
|
+
* const outcomes = [buildOutcome(1, 'shipped')]
|
|
589
|
+
* const owned = snapshotBrief(buildBrief(buildTask('plan', 'ops', 'Plan the release.'), { outcomes }))
|
|
508
590
|
* owned.outcomes === outcomes // false — the alias is broken
|
|
509
591
|
* Object.isFrozen(owned.outcomes) // true
|
|
510
592
|
* ```
|
|
@@ -517,7 +599,7 @@ function snapshotBrief(source) {
|
|
|
517
599
|
//#endregion
|
|
518
600
|
//#region src/core/helpers.ts
|
|
519
601
|
/**
|
|
520
|
-
*
|
|
602
|
+
* Assembles a `Task` from an operation, a domain, and a statement.
|
|
521
603
|
*
|
|
522
604
|
* @param operation - What the brief asks for, from the closed operation vocabulary.
|
|
523
605
|
* @param domain - The subject matter, from the closed domain vocabulary.
|
|
@@ -526,12 +608,12 @@ function snapshotBrief(source) {
|
|
|
526
608
|
*
|
|
527
609
|
* @example
|
|
528
610
|
* ```ts
|
|
529
|
-
* import {
|
|
611
|
+
* import { buildTask } from '@orkestrel/brief'
|
|
530
612
|
*
|
|
531
|
-
*
|
|
613
|
+
* buildTask('refactor', 'code', 'Refactor useForm to native browser form APIs.')
|
|
532
614
|
* ```
|
|
533
615
|
*/
|
|
534
|
-
function
|
|
616
|
+
function buildTask(operation, domain, statement) {
|
|
535
617
|
return {
|
|
536
618
|
operation,
|
|
537
619
|
domain,
|
|
@@ -539,7 +621,7 @@ function task(operation, domain, statement) {
|
|
|
539
621
|
};
|
|
540
622
|
}
|
|
541
623
|
/**
|
|
542
|
-
*
|
|
624
|
+
* Assembles a `Reference` from a path and the note that justifies listing it.
|
|
543
625
|
*
|
|
544
626
|
* @param path - The referenced path or glob.
|
|
545
627
|
* @param note - Why the path is listed.
|
|
@@ -547,31 +629,31 @@ function task(operation, domain, statement) {
|
|
|
547
629
|
*
|
|
548
630
|
* @example
|
|
549
631
|
* ```ts
|
|
550
|
-
* import {
|
|
632
|
+
* import { buildReference } from '@orkestrel/brief'
|
|
551
633
|
*
|
|
552
|
-
*
|
|
634
|
+
* buildReference('AGENTS.md', 'project law') // { path: 'AGENTS.md', note: 'project law' }
|
|
553
635
|
* ```
|
|
554
636
|
*/
|
|
555
|
-
function
|
|
637
|
+
function buildReference(path, note) {
|
|
556
638
|
return {
|
|
557
639
|
path,
|
|
558
640
|
note
|
|
559
641
|
};
|
|
560
642
|
}
|
|
561
643
|
/**
|
|
562
|
-
*
|
|
644
|
+
* Assembles a `Manifest`, defaulting every absent partition to an empty list.
|
|
563
645
|
*
|
|
564
646
|
* @param partitions - The partitions to fill; a partial literal is enough.
|
|
565
|
-
* @returns A fresh `Manifest` with
|
|
647
|
+
* @returns A fresh `Manifest` with every partition present.
|
|
566
648
|
*
|
|
567
649
|
* @example
|
|
568
650
|
* ```ts
|
|
569
|
-
* import {
|
|
651
|
+
* import { buildManifest, buildReference } from '@orkestrel/brief'
|
|
570
652
|
*
|
|
571
|
-
*
|
|
653
|
+
* buildManifest({ edit: [buildReference('src/core/helpers.ts', 'implementation')] })
|
|
572
654
|
* ```
|
|
573
655
|
*/
|
|
574
|
-
function
|
|
656
|
+
function buildManifest(partitions) {
|
|
575
657
|
return {
|
|
576
658
|
read: partitions?.read ?? [],
|
|
577
659
|
edit: partitions?.edit ?? [],
|
|
@@ -580,22 +662,23 @@ function manifest(partitions) {
|
|
|
580
662
|
};
|
|
581
663
|
}
|
|
582
664
|
/**
|
|
583
|
-
*
|
|
665
|
+
* Assembles an `Outcome` from a rank and its result text.
|
|
584
666
|
*
|
|
585
667
|
* @param rank - The one-based rank; lower ranks matter more.
|
|
586
668
|
* @param text - The result, never a step.
|
|
587
|
-
* @param required -
|
|
669
|
+
* @param required - If `true`, the outcome gates "done"; if `false`, it is desirable but not
|
|
670
|
+
* blocking. Default: `true`.
|
|
588
671
|
* @returns A fresh `Outcome`.
|
|
589
672
|
*
|
|
590
673
|
* @example
|
|
591
674
|
* ```ts
|
|
592
|
-
* import {
|
|
675
|
+
* import { buildOutcome } from '@orkestrel/brief'
|
|
593
676
|
*
|
|
594
|
-
*
|
|
595
|
-
*
|
|
677
|
+
* buildOutcome(1, 'useForm uses native FormData with no behavior change') // required: true
|
|
678
|
+
* buildOutcome(2, 'the diff stays under 200 lines', false)
|
|
596
679
|
* ```
|
|
597
680
|
*/
|
|
598
|
-
function
|
|
681
|
+
function buildOutcome(rank, text, required = true) {
|
|
599
682
|
return {
|
|
600
683
|
rank,
|
|
601
684
|
text,
|
|
@@ -603,7 +686,7 @@ function outcome(rank, text, required = true) {
|
|
|
603
686
|
};
|
|
604
687
|
}
|
|
605
688
|
/**
|
|
606
|
-
*
|
|
689
|
+
* Assembles a `Given` from a category, a name, and a value.
|
|
607
690
|
*
|
|
608
691
|
* @param category - The kind of fact — a convention, a version, a constraint.
|
|
609
692
|
* @param name - The fact's name.
|
|
@@ -612,12 +695,12 @@ function outcome(rank, text, required = true) {
|
|
|
612
695
|
*
|
|
613
696
|
* @example
|
|
614
697
|
* ```ts
|
|
615
|
-
* import {
|
|
698
|
+
* import { buildGiven } from '@orkestrel/brief'
|
|
616
699
|
*
|
|
617
|
-
*
|
|
700
|
+
* buildGiven('convention', 'indentation', 'tabs')
|
|
618
701
|
* ```
|
|
619
702
|
*/
|
|
620
|
-
function
|
|
703
|
+
function buildGiven(category, name, value) {
|
|
621
704
|
return {
|
|
622
705
|
category,
|
|
623
706
|
name,
|
|
@@ -625,32 +708,32 @@ function given(category, name, value) {
|
|
|
625
708
|
};
|
|
626
709
|
}
|
|
627
710
|
/**
|
|
628
|
-
*
|
|
711
|
+
* Assembles an `Example` from an exemplar input and its expected output.
|
|
629
712
|
*
|
|
630
713
|
* @param input - The exemplar input.
|
|
631
|
-
* @param
|
|
714
|
+
* @param output - The expected output for that input.
|
|
632
715
|
* @param note - Optional detail; the key is OMITTED when absent.
|
|
633
716
|
* @returns A fresh `Example`.
|
|
634
717
|
*
|
|
635
718
|
* @example
|
|
636
719
|
* ```ts
|
|
637
|
-
* import {
|
|
720
|
+
* import { buildExample } from '@orkestrel/brief'
|
|
638
721
|
*
|
|
639
|
-
*
|
|
722
|
+
* buildExample('<input required>', 'validity read from el.validity')
|
|
640
723
|
* ```
|
|
641
724
|
*/
|
|
642
|
-
function
|
|
725
|
+
function buildExample(input, output, note) {
|
|
643
726
|
return note === void 0 ? {
|
|
644
727
|
input,
|
|
645
|
-
output
|
|
728
|
+
output
|
|
646
729
|
} : {
|
|
647
730
|
input,
|
|
648
|
-
output
|
|
731
|
+
output,
|
|
649
732
|
note
|
|
650
733
|
};
|
|
651
734
|
}
|
|
652
735
|
/**
|
|
653
|
-
*
|
|
736
|
+
* Assembles a `Citation` from a name, a URL, and the note that justifies citing it.
|
|
654
737
|
*
|
|
655
738
|
* @param name - The source's display name.
|
|
656
739
|
* @param url - Where the source lives.
|
|
@@ -659,16 +742,16 @@ function example(input, result, note) {
|
|
|
659
742
|
*
|
|
660
743
|
* @example
|
|
661
744
|
* ```ts
|
|
662
|
-
* import {
|
|
745
|
+
* import { buildCitation } from '@orkestrel/brief'
|
|
663
746
|
*
|
|
664
|
-
*
|
|
747
|
+
* buildCitation(
|
|
665
748
|
* 'MDN Constraint Validation',
|
|
666
749
|
* 'https://developer.mozilla.org/',
|
|
667
750
|
* 'the native validity behavior being adopted',
|
|
668
751
|
* )
|
|
669
752
|
* ```
|
|
670
753
|
*/
|
|
671
|
-
function
|
|
754
|
+
function buildCitation(name, url, note) {
|
|
672
755
|
return {
|
|
673
756
|
name,
|
|
674
757
|
url,
|
|
@@ -676,23 +759,23 @@ function citation(name, url, note) {
|
|
|
676
759
|
};
|
|
677
760
|
}
|
|
678
761
|
/**
|
|
679
|
-
*
|
|
762
|
+
* Assembles a `Gap` from the section it belongs to and the question that would close it.
|
|
680
763
|
*
|
|
681
764
|
* @param field - The brief section the unknown belongs to.
|
|
682
765
|
* @param question - The question that would close it.
|
|
683
|
-
* @param overrides - Optional `blocking`
|
|
684
|
-
*
|
|
766
|
+
* @param overrides - Optional `blocking` and `candidates`; an absent `candidates` key is
|
|
767
|
+
* OMITTED entirely. Default: `blocking: false`.
|
|
685
768
|
* @returns A fresh `Gap`.
|
|
686
769
|
*
|
|
687
770
|
* @example
|
|
688
771
|
* ```ts
|
|
689
|
-
* import {
|
|
772
|
+
* import { buildGap } from '@orkestrel/brief'
|
|
690
773
|
*
|
|
691
|
-
*
|
|
692
|
-
*
|
|
774
|
+
* buildGap('rules', 'Does validation message wording need to change?') // blocking: false
|
|
775
|
+
* buildGap('output', 'Diff or full files?', { blocking: true, candidates: ['diff', 'code'] })
|
|
693
776
|
* ```
|
|
694
777
|
*/
|
|
695
|
-
function
|
|
778
|
+
function buildGap(field, question, overrides) {
|
|
696
779
|
const blocking = overrides?.blocking ?? false;
|
|
697
780
|
return overrides?.candidates === void 0 ? {
|
|
698
781
|
field,
|
|
@@ -706,7 +789,7 @@ function gap(field, question, overrides) {
|
|
|
706
789
|
};
|
|
707
790
|
}
|
|
708
791
|
/**
|
|
709
|
-
*
|
|
792
|
+
* Assembles a `Risk` from a severity, what could go wrong, and the mitigation that answers it.
|
|
710
793
|
*
|
|
711
794
|
* @param severity - The closed severity.
|
|
712
795
|
* @param text - What could go wrong.
|
|
@@ -715,12 +798,12 @@ function gap(field, question, overrides) {
|
|
|
715
798
|
*
|
|
716
799
|
* @example
|
|
717
800
|
* ```ts
|
|
718
|
-
* import {
|
|
801
|
+
* import { buildRisk } from '@orkestrel/brief'
|
|
719
802
|
*
|
|
720
|
-
*
|
|
803
|
+
* buildRisk('medium', 'native validation differs subtly', 'assert message and state in tests')
|
|
721
804
|
* ```
|
|
722
805
|
*/
|
|
723
|
-
function
|
|
806
|
+
function buildRisk(severity, text, mitigation) {
|
|
724
807
|
return {
|
|
725
808
|
severity,
|
|
726
809
|
text,
|
|
@@ -728,7 +811,7 @@ function risk(severity, text, mitigation) {
|
|
|
728
811
|
};
|
|
729
812
|
}
|
|
730
813
|
/**
|
|
731
|
-
*
|
|
814
|
+
* Assembles an `Output` from a format plus its optional refinements.
|
|
732
815
|
*
|
|
733
816
|
* @param format - The closed deliverable format.
|
|
734
817
|
* @param overrides - Optional `sections` / `include` / `exclude`; absent keys are OMITTED.
|
|
@@ -736,13 +819,13 @@ function risk(severity, text, mitigation) {
|
|
|
736
819
|
*
|
|
737
820
|
* @example
|
|
738
821
|
* ```ts
|
|
739
|
-
* import {
|
|
822
|
+
* import { buildOutput } from '@orkestrel/brief'
|
|
740
823
|
*
|
|
741
|
-
*
|
|
742
|
-
*
|
|
824
|
+
* buildOutput('markdown') // { format: 'markdown' }
|
|
825
|
+
* buildOutput('diff', { include: ['updated useForm.ts'] })
|
|
743
826
|
* ```
|
|
744
827
|
*/
|
|
745
|
-
function
|
|
828
|
+
function buildOutput(format, overrides) {
|
|
746
829
|
return {
|
|
747
830
|
format,
|
|
748
831
|
...overrides?.sections === void 0 ? {} : { sections: overrides.sections },
|
|
@@ -751,7 +834,7 @@ function output(format, overrides) {
|
|
|
751
834
|
};
|
|
752
835
|
}
|
|
753
836
|
/**
|
|
754
|
-
*
|
|
837
|
+
* Assembles a `Proof` from what the check settles and the command that settles it.
|
|
755
838
|
*
|
|
756
839
|
* @param text - What the check settles.
|
|
757
840
|
* @param command - The command whose exit signal settles it.
|
|
@@ -759,41 +842,41 @@ function output(format, overrides) {
|
|
|
759
842
|
*
|
|
760
843
|
* @example
|
|
761
844
|
* ```ts
|
|
762
|
-
* import {
|
|
845
|
+
* import { buildProof } from '@orkestrel/brief'
|
|
763
846
|
*
|
|
764
|
-
*
|
|
847
|
+
* buildProof('type-check and lint pass', 'npm run check')
|
|
765
848
|
* ```
|
|
766
849
|
*/
|
|
767
|
-
function
|
|
850
|
+
function buildProof(text, command) {
|
|
768
851
|
return {
|
|
769
852
|
text,
|
|
770
853
|
command
|
|
771
854
|
};
|
|
772
855
|
}
|
|
773
856
|
/**
|
|
774
|
-
*
|
|
857
|
+
* Assembles a `Brief` from a `Task` plus section overrides.
|
|
775
858
|
*
|
|
776
859
|
* @param subject - The task the brief is about.
|
|
777
|
-
* @param overrides - Any sections to fill;
|
|
778
|
-
* `
|
|
779
|
-
* `
|
|
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`.
|
|
780
863
|
* @returns A fresh, unpinned `Brief`.
|
|
781
864
|
*
|
|
782
865
|
* @example
|
|
783
866
|
* ```ts
|
|
784
|
-
* import {
|
|
867
|
+
* import { buildBrief, buildOutcome, buildProof, buildTask } from '@orkestrel/brief'
|
|
785
868
|
*
|
|
786
|
-
*
|
|
787
|
-
* outcomes: [
|
|
788
|
-
* 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')],
|
|
789
872
|
* })
|
|
790
873
|
* ```
|
|
791
874
|
*/
|
|
792
|
-
function
|
|
875
|
+
function buildBrief(subject, overrides) {
|
|
793
876
|
return {
|
|
794
877
|
task: subject,
|
|
795
878
|
authority: overrides?.authority ?? [],
|
|
796
|
-
manifest: overrides?.manifest ??
|
|
879
|
+
manifest: overrides?.manifest ?? buildManifest(),
|
|
797
880
|
outcomes: overrides?.outcomes ?? [],
|
|
798
881
|
rules: overrides?.rules ?? [],
|
|
799
882
|
invariants: overrides?.invariants ?? [],
|
|
@@ -803,17 +886,17 @@ function brief(subject, overrides) {
|
|
|
803
886
|
citations: overrides?.citations ?? [],
|
|
804
887
|
gaps: overrides?.gaps ?? [],
|
|
805
888
|
risks: overrides?.risks ?? [],
|
|
806
|
-
output: overrides?.output ??
|
|
889
|
+
output: overrides?.output ?? buildOutput("markdown"),
|
|
807
890
|
proofs: overrides?.proofs ?? []
|
|
808
891
|
};
|
|
809
892
|
}
|
|
810
893
|
/**
|
|
811
|
-
*
|
|
894
|
+
* Assembles the fail-closed readiness gate as a reasons `LogicalDefinition`.
|
|
812
895
|
*
|
|
813
896
|
* @remarks
|
|
814
|
-
*
|
|
815
|
-
*
|
|
816
|
-
*
|
|
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`.
|
|
817
900
|
*
|
|
818
901
|
* The gate takes NO parameters, and that is deliberate rather than unfinished. The
|
|
819
902
|
* reasoner overlays every derived fact into one flat namespace, so a caller rule named
|
|
@@ -827,50 +910,50 @@ function brief(subject, overrides) {
|
|
|
827
910
|
*
|
|
828
911
|
* @example
|
|
829
912
|
* ```ts
|
|
830
|
-
* import { briefToSubject,
|
|
913
|
+
* import { briefToSubject, buildGateDefinition } from '@orkestrel/brief'
|
|
831
914
|
* import { createLogicalReasoner, createReason } from '@orkestrel/reason'
|
|
832
915
|
*
|
|
833
916
|
* const reason = createReason({ reasoners: [createLogicalReasoner()] })
|
|
834
|
-
* const verdict = reason.reason(briefToSubject(pinned),
|
|
917
|
+
* const verdict = reason.reason(briefToSubject(pinned), buildGateDefinition())
|
|
835
918
|
* reason.destroy()
|
|
836
919
|
* ```
|
|
837
920
|
*/
|
|
838
|
-
function
|
|
921
|
+
function buildGateDefinition() {
|
|
839
922
|
const readiness = [
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
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))
|
|
846
929
|
];
|
|
847
|
-
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))]);
|
|
848
931
|
}
|
|
849
932
|
/**
|
|
850
|
-
*
|
|
933
|
+
* Lists the readiness rules a brief fails, computed directly from its own measures.
|
|
851
934
|
*
|
|
852
935
|
* @remarks
|
|
853
|
-
* The gate's decision, in code. `
|
|
936
|
+
* The gate's decision, in code. `buildGateDefinition()` states the same rules as data for a
|
|
854
937
|
* reasoner to narrate, and a narration is not a decision: `BriefCompilerOptions.reason` lets a
|
|
855
938
|
* caller supply the engine, and an engine that answers "met" to everything would otherwise
|
|
856
939
|
* emit a brief with no proofs. `compile` refuses on THIS and keeps the verdict for its
|
|
857
940
|
* trace, so a supplied engine can add detail and never remove a refusal.
|
|
858
941
|
*
|
|
859
|
-
* The
|
|
860
|
-
* 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.
|
|
861
944
|
*
|
|
862
945
|
* @param source - The brief to measure.
|
|
863
946
|
* @returns The unmet rule ids, in gate order; empty when the brief is ready.
|
|
864
947
|
*
|
|
865
948
|
* @example
|
|
866
949
|
* ```ts
|
|
867
|
-
* import {
|
|
950
|
+
* import { buildBrief, buildOutcome, buildProof, buildTask, findUnmetRules } from '@orkestrel/brief'
|
|
868
951
|
*
|
|
869
|
-
* findUnmetRules(
|
|
952
|
+
* findUnmetRules(buildBrief(buildTask('plan', 'ops', 'Plan the release.'))) // ['aimed', 'proven']
|
|
870
953
|
* findUnmetRules(
|
|
871
|
-
*
|
|
872
|
-
* outcomes: [
|
|
873
|
-
* proofs: [
|
|
954
|
+
* buildBrief(buildTask('plan', 'ops', 'Plan the release.'), {
|
|
955
|
+
* outcomes: [buildOutcome(1, 'shipped')],
|
|
956
|
+
* proofs: [buildProof('x', 'npm test')],
|
|
874
957
|
* }),
|
|
875
958
|
* ) // []
|
|
876
959
|
* ```
|
|
@@ -886,7 +969,7 @@ function findUnmetRules(source) {
|
|
|
886
969
|
return unready;
|
|
887
970
|
}
|
|
888
971
|
/**
|
|
889
|
-
*
|
|
972
|
+
* Counts the sentences a statement holds.
|
|
890
973
|
*
|
|
891
974
|
* @remarks
|
|
892
975
|
* A terminator run (`.`, `!`, `?`) followed by whitespace or the end of the text closes one
|
|
@@ -924,17 +1007,17 @@ function countSentences(statement) {
|
|
|
924
1007
|
return /[.!?]$/u.test(text) ? matches.length : matches.length + 1;
|
|
925
1008
|
}
|
|
926
1009
|
/**
|
|
927
|
-
*
|
|
1010
|
+
* Lists the gaps that block emission.
|
|
928
1011
|
*
|
|
929
1012
|
* @param source - The brief to inspect.
|
|
930
1013
|
* @returns Every gap carrying `blocking: true`, in declaration order.
|
|
931
1014
|
*
|
|
932
1015
|
* @example
|
|
933
1016
|
* ```ts
|
|
934
|
-
* import {
|
|
1017
|
+
* import { buildBrief, buildGap, buildTask, findBlockingGaps } from '@orkestrel/brief'
|
|
935
1018
|
*
|
|
936
|
-
* const draft =
|
|
937
|
-
* gaps: [
|
|
1019
|
+
* const draft = buildBrief(buildTask('plan', 'ops', 'Plan the release.'), {
|
|
1020
|
+
* gaps: [buildGap('output', 'Diff or files?', { blocking: true })],
|
|
938
1021
|
* })
|
|
939
1022
|
* findBlockingGaps(draft).length // 1
|
|
940
1023
|
* ```
|
|
@@ -943,18 +1026,18 @@ function findBlockingGaps(source) {
|
|
|
943
1026
|
return source.gaps.filter((entry) => entry.blocking);
|
|
944
1027
|
}
|
|
945
1028
|
/**
|
|
946
|
-
*
|
|
1029
|
+
* Lists the authority paths the manifest never grants access to.
|
|
947
1030
|
*
|
|
948
1031
|
* @remarks
|
|
949
1032
|
* An authority the executor cannot open is an instruction it cannot follow, so every ranked
|
|
950
|
-
* 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
|
|
951
1034
|
* grant, because read-only is exactly what obeying a file requires.
|
|
952
1035
|
*
|
|
953
|
-
* This subsumes the narrower question of an authority sitting in `forbidden`. The
|
|
954
|
-
*
|
|
955
|
-
*
|
|
956
|
-
*
|
|
957
|
-
*
|
|
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.
|
|
958
1041
|
*
|
|
959
1042
|
* Paths are compared as EXACT strings, matching `findManifestOverlaps`. A glob is never
|
|
960
1043
|
* expanded, so `read: 'guides/**'` does not grant `authority: 'guides/brief.md'`. State a
|
|
@@ -965,11 +1048,17 @@ function findBlockingGaps(source) {
|
|
|
965
1048
|
*
|
|
966
1049
|
* @example
|
|
967
1050
|
* ```ts
|
|
968
|
-
* import {
|
|
969
|
-
*
|
|
970
|
-
*
|
|
971
|
-
*
|
|
972
|
-
*
|
|
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(),
|
|
973
1062
|
* })
|
|
974
1063
|
* findUngrantedAuthority(draft) // ['AGENTS.md'] — ranked, but no partition opens it
|
|
975
1064
|
* ```
|
|
@@ -985,10 +1074,10 @@ function findUngrantedAuthority(source) {
|
|
|
985
1074
|
return ungranted;
|
|
986
1075
|
}
|
|
987
1076
|
/**
|
|
988
|
-
*
|
|
1077
|
+
* Lists the paths appearing in more than one manifest partition.
|
|
989
1078
|
*
|
|
990
1079
|
* @remarks
|
|
991
|
-
* Duplicates WITHIN one partition are not an overlap; the
|
|
1080
|
+
* Duplicates WITHIN one partition are not an overlap; the partitions must be
|
|
992
1081
|
* mutually disjoint, which is what `validateBrief` errors on.
|
|
993
1082
|
*
|
|
994
1083
|
* Paths are compared as EXACT strings. A glob is never expanded, so `edit: 'app/file.ts'`
|
|
@@ -1000,12 +1089,18 @@ function findUngrantedAuthority(source) {
|
|
|
1000
1089
|
*
|
|
1001
1090
|
* @example
|
|
1002
1091
|
* ```ts
|
|
1003
|
-
* import {
|
|
1004
|
-
*
|
|
1005
|
-
*
|
|
1006
|
-
*
|
|
1007
|
-
*
|
|
1008
|
-
*
|
|
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')],
|
|
1009
1104
|
* }),
|
|
1010
1105
|
* })
|
|
1011
1106
|
* findManifestOverlaps(draft) // ['src/core/BriefCompiler.ts']
|
|
@@ -1025,7 +1120,7 @@ function findManifestOverlaps(source) {
|
|
|
1025
1120
|
return overlaps;
|
|
1026
1121
|
}
|
|
1027
1122
|
/**
|
|
1028
|
-
*
|
|
1123
|
+
* Lists the open gaps with no assumption to stand on.
|
|
1029
1124
|
*
|
|
1030
1125
|
* @remarks
|
|
1031
1126
|
* The discipline is exactly one recorded assumption per open gap, so the open gaps past
|
|
@@ -1037,10 +1132,10 @@ function findManifestOverlaps(source) {
|
|
|
1037
1132
|
*
|
|
1038
1133
|
* @example
|
|
1039
1134
|
* ```ts
|
|
1040
|
-
* import {
|
|
1135
|
+
* import { buildBrief, buildGap, buildTask, findUnpairedGaps } from '@orkestrel/brief'
|
|
1041
1136
|
*
|
|
1042
|
-
* const draft =
|
|
1043
|
-
* gaps: [
|
|
1137
|
+
* const draft = buildBrief(buildTask('plan', 'ops', 'Plan the release.'), {
|
|
1138
|
+
* gaps: [buildGap('rules', 'Keep the wording?'), buildGap('output', 'Diff or files?')],
|
|
1044
1139
|
* assumptions: ['Wording is preserved.'],
|
|
1045
1140
|
* })
|
|
1046
1141
|
* findUnpairedGaps(draft).length // 1
|
|
@@ -1050,16 +1145,16 @@ function findUnpairedGaps(source) {
|
|
|
1050
1145
|
return source.gaps.filter((entry) => !entry.blocking).slice(source.assumptions.length);
|
|
1051
1146
|
}
|
|
1052
1147
|
/**
|
|
1053
|
-
*
|
|
1148
|
+
* Projects a brief into the reasons `Subject` of readiness measures the gate reads.
|
|
1054
1149
|
*
|
|
1055
1150
|
* @param source - The brief to measure.
|
|
1056
|
-
* @returns A flat record of counts plus the task's
|
|
1151
|
+
* @returns A flat record of counts plus the task's vocabulary values.
|
|
1057
1152
|
*
|
|
1058
1153
|
* @example
|
|
1059
1154
|
* ```ts
|
|
1060
|
-
* import {
|
|
1155
|
+
* import { briefToSubject, buildBrief, buildProof, buildTask } from '@orkestrel/brief'
|
|
1061
1156
|
*
|
|
1062
|
-
* briefToSubject(
|
|
1157
|
+
* briefToSubject(buildBrief(buildTask('test', 'code', 'Cover the gate.'), { proofs: [buildProof('x', 'y')] }))
|
|
1063
1158
|
* // { operation: 'test', domain: 'code', sentences: 1, proofs: 1, … }
|
|
1064
1159
|
* ```
|
|
1065
1160
|
*/
|
|
@@ -1086,7 +1181,7 @@ function briefToSubject(source) {
|
|
|
1086
1181
|
};
|
|
1087
1182
|
}
|
|
1088
1183
|
/**
|
|
1089
|
-
*
|
|
1184
|
+
* Runs the semantic pass over an already-shape-valid brief.
|
|
1090
1185
|
*
|
|
1091
1186
|
* @remarks
|
|
1092
1187
|
* ERRORS are the structural violations no assumption can paper over: a manifest
|
|
@@ -1100,11 +1195,11 @@ function briefToSubject(source) {
|
|
|
1100
1195
|
*
|
|
1101
1196
|
* @example
|
|
1102
1197
|
* ```ts
|
|
1103
|
-
* import {
|
|
1198
|
+
* import { buildBrief, buildProof, buildTask, validateBrief } from '@orkestrel/brief'
|
|
1104
1199
|
*
|
|
1105
|
-
* validateBrief(
|
|
1200
|
+
* validateBrief(buildBrief(buildTask('plan', 'ops', 'Plan the release.'))) // valid: false — no proofs
|
|
1106
1201
|
* validateBrief(
|
|
1107
|
-
*
|
|
1202
|
+
* buildBrief(buildTask('plan', 'ops', 'Plan the release.'), { proofs: [buildProof('ok', 'npm test')] }),
|
|
1108
1203
|
* ) // valid: true
|
|
1109
1204
|
* ```
|
|
1110
1205
|
*/
|
|
@@ -1132,7 +1227,7 @@ function validateBrief(source) {
|
|
|
1132
1227
|
};
|
|
1133
1228
|
}
|
|
1134
1229
|
/**
|
|
1135
|
-
*
|
|
1230
|
+
* Computes the canonical structural digest of a brief's content.
|
|
1136
1231
|
*
|
|
1137
1232
|
* @remarks
|
|
1138
1233
|
* `trace` and `hash` are stripped before digesting, so the value is the identity of what
|
|
@@ -1144,9 +1239,9 @@ function validateBrief(source) {
|
|
|
1144
1239
|
*
|
|
1145
1240
|
* @example
|
|
1146
1241
|
* ```ts
|
|
1147
|
-
* import {
|
|
1242
|
+
* import { briefToHash, buildBrief, buildTask, pinBrief } from '@orkestrel/brief'
|
|
1148
1243
|
*
|
|
1149
|
-
* const draft =
|
|
1244
|
+
* const draft = buildBrief(buildTask('plan', 'ops', 'Plan the release.'))
|
|
1150
1245
|
* briefToHash(draft) === briefToHash(pinBrief(draft)) // true — pinning does not move it
|
|
1151
1246
|
* ```
|
|
1152
1247
|
*/
|
|
@@ -1154,7 +1249,7 @@ function briefToHash(source) {
|
|
|
1154
1249
|
return digestValue(briefToContent(source));
|
|
1155
1250
|
}
|
|
1156
1251
|
/**
|
|
1157
|
-
*
|
|
1252
|
+
* Renders the canonical text of exactly what a brief's hash describes.
|
|
1158
1253
|
*
|
|
1159
1254
|
* @remarks
|
|
1160
1255
|
* `trace` and `hash` are stripped, then interprets `canonicalize` renders the rest in a
|
|
@@ -1166,9 +1261,9 @@ function briefToHash(source) {
|
|
|
1166
1261
|
*
|
|
1167
1262
|
* @example
|
|
1168
1263
|
* ```ts
|
|
1169
|
-
* import {
|
|
1264
|
+
* import { briefToContent, buildBrief, buildTask, pinBrief } from '@orkestrel/brief'
|
|
1170
1265
|
*
|
|
1171
|
-
* const draft =
|
|
1266
|
+
* const draft = buildBrief(buildTask('plan', 'ops', 'Plan the release.'))
|
|
1172
1267
|
* briefToContent(draft) === briefToContent(pinBrief(draft)) // true — pinning adds no content
|
|
1173
1268
|
* ```
|
|
1174
1269
|
*/
|
|
@@ -1177,7 +1272,7 @@ function briefToContent(source) {
|
|
|
1177
1272
|
return canonicalize(content);
|
|
1178
1273
|
}
|
|
1179
1274
|
/**
|
|
1180
|
-
*
|
|
1275
|
+
* Freezes a value and everything reachable from it.
|
|
1181
1276
|
*
|
|
1182
1277
|
* @remarks
|
|
1183
1278
|
* `Object.freeze` is SHALLOW, so freezing a record leaves every nested array and object
|
|
@@ -1191,7 +1286,7 @@ function briefToContent(source) {
|
|
|
1191
1286
|
* Reaches PLAIN objects and arrays, which is the whole of a `Brief` — it is JSON-serializable
|
|
1192
1287
|
* by contract. A `Map`, `Set`, or typed array is frozen as an object and its CONTENTS are left
|
|
1193
1288
|
* writable, and `Object.isFrozen` reports `true` for it either way. Nothing this package
|
|
1194
|
-
* produces contains one; a caller freezing their own value
|
|
1289
|
+
* produces contains one; the limit lands on a caller freezing their own value.
|
|
1195
1290
|
*
|
|
1196
1291
|
* @param value - The value to freeze in place; returned for convenience.
|
|
1197
1292
|
* @returns The same value, now deeply frozen.
|
|
@@ -1208,7 +1303,7 @@ function freezeDeep(value) {
|
|
|
1208
1303
|
return freezeBranch(value, /* @__PURE__ */ new WeakSet());
|
|
1209
1304
|
}
|
|
1210
1305
|
/**
|
|
1211
|
-
*
|
|
1306
|
+
* Freezes one branch of a value graph, skipping what the visited set already holds.
|
|
1212
1307
|
*
|
|
1213
1308
|
* @param value - The branch to freeze.
|
|
1214
1309
|
* @param seen - The objects already frozen on this walk; what makes a cycle terminate.
|
|
@@ -1230,7 +1325,7 @@ function freezeBranch(value, seen) {
|
|
|
1230
1325
|
return value;
|
|
1231
1326
|
}
|
|
1232
1327
|
/**
|
|
1233
|
-
*
|
|
1328
|
+
* Renders a value thrown by a stage into a message.
|
|
1234
1329
|
*
|
|
1235
1330
|
* @remarks
|
|
1236
1331
|
* TOTAL: it never throws, for any input. That is load-bearing rather than tidy, because this
|
|
@@ -1239,7 +1334,7 @@ function freezeBranch(value, seen) {
|
|
|
1239
1334
|
* falsifies the package's central promise that a failing stage yields an incomplete
|
|
1240
1335
|
* `Briefing` rather than an exception.
|
|
1241
1336
|
*
|
|
1242
|
-
*
|
|
1337
|
+
* Real inputs used to throw: an `Error` subclass whose `message` getter throws, a value
|
|
1243
1338
|
* whose string conversion throws, and a null-prototype object, which has no inherited
|
|
1244
1339
|
* conversion for String() to reach. Each is wrapped, and an unreadable value degrades to its
|
|
1245
1340
|
* type rather than propagating.
|
|
@@ -1263,10 +1358,10 @@ function errorToMessage(error) {
|
|
|
1263
1358
|
return `an unreadable ${typeof error} was thrown`;
|
|
1264
1359
|
}
|
|
1265
1360
|
/**
|
|
1266
|
-
*
|
|
1361
|
+
* Narrows unknown data to a `Brief`, throwing when it is off-contract.
|
|
1267
1362
|
*
|
|
1268
1363
|
* @remarks
|
|
1269
|
-
* 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
|
|
1270
1365
|
* guard passes, while `parseBrief` returns `undefined` for bad input. It constructs
|
|
1271
1366
|
* nothing, so it is an assertion rather than a factory. Reserve it for programmer-error
|
|
1272
1367
|
* contexts where invalidity is a bug.
|
|
@@ -1280,24 +1375,24 @@ function errorToMessage(error) {
|
|
|
1280
1375
|
* `briefToTrace` read the value they are handed instead, so a caller reaching one of those
|
|
1281
1376
|
* directly owns that reading. Pass `assertBrief` a value you already own.
|
|
1282
1377
|
*
|
|
1283
|
-
* @param
|
|
1378
|
+
* @param value - The candidate brief value.
|
|
1284
1379
|
* @returns The same value, now known to satisfy {@link Brief}.
|
|
1285
|
-
* @throws {@link BriefError} `INVALID` when `
|
|
1380
|
+
* @throws {@link BriefError} `INVALID` when `value` fails `isBrief`.
|
|
1286
1381
|
*
|
|
1287
1382
|
* @example
|
|
1288
1383
|
* ```ts
|
|
1289
|
-
* import { assertBrief,
|
|
1384
|
+
* import { assertBrief, buildBrief, buildProof, buildTask } from '@orkestrel/brief'
|
|
1290
1385
|
*
|
|
1291
|
-
* assertBrief(
|
|
1386
|
+
* assertBrief(buildBrief(buildTask('plan', 'ops', 'Plan the release.'), { proofs: [buildProof('x', 'y')] }))
|
|
1292
1387
|
* assertBrief({ task: { operation: 'plan', domain: 'ops', statement: 'x.' } }) // throws INVALID
|
|
1293
1388
|
* ```
|
|
1294
1389
|
*/
|
|
1295
|
-
function assertBrief(
|
|
1296
|
-
if (!isBrief(
|
|
1297
|
-
return
|
|
1390
|
+
function assertBrief(value) {
|
|
1391
|
+
if (!isBrief(value)) throw new BriefError("INVALID", "Brief failed the exact-record contract", { field: "brief" });
|
|
1392
|
+
return value;
|
|
1298
1393
|
}
|
|
1299
1394
|
/**
|
|
1300
|
-
*
|
|
1395
|
+
* Returns a fresh brief with `trace` and `hash` derived from its own content.
|
|
1301
1396
|
*
|
|
1302
1397
|
* @remarks
|
|
1303
1398
|
* Deterministic: no clock, no randomness, no run-specific data. Any existing `trace` /
|
|
@@ -1313,9 +1408,9 @@ function assertBrief(data) {
|
|
|
1313
1408
|
*
|
|
1314
1409
|
* @example
|
|
1315
1410
|
* ```ts
|
|
1316
|
-
* import {
|
|
1411
|
+
* import { buildBrief, buildTask, pinBrief } from '@orkestrel/brief'
|
|
1317
1412
|
*
|
|
1318
|
-
* const pinned = pinBrief(
|
|
1413
|
+
* const pinned = pinBrief(buildBrief(buildTask('document', 'writing', 'Write the brief guide.')))
|
|
1319
1414
|
* pinned.hash // an 8-hex-digit structural digest
|
|
1320
1415
|
* pinned.trace // 'document/writing · outcomes:0 · gaps:0/0 · proofs:0'
|
|
1321
1416
|
* ```
|
|
@@ -1330,7 +1425,7 @@ function pinBrief(source) {
|
|
|
1330
1425
|
});
|
|
1331
1426
|
}
|
|
1332
1427
|
/**
|
|
1333
|
-
*
|
|
1428
|
+
* Renders the one-line census `pinBrief` stamps onto a brief.
|
|
1334
1429
|
*
|
|
1335
1430
|
* @remarks
|
|
1336
1431
|
* Extracted so it has ONE implementation. `pinBrief` derives it and `BriefManager` re-derives
|
|
@@ -1343,9 +1438,9 @@ function pinBrief(source) {
|
|
|
1343
1438
|
*
|
|
1344
1439
|
* @example
|
|
1345
1440
|
* ```ts
|
|
1346
|
-
* import {
|
|
1441
|
+
* import { briefToTrace, buildBrief, buildTask } from '@orkestrel/brief'
|
|
1347
1442
|
*
|
|
1348
|
-
* briefToTrace(
|
|
1443
|
+
* briefToTrace(buildBrief(buildTask('document', 'writing', 'Write the guide.')))
|
|
1349
1444
|
* // 'document/writing · outcomes:0 · gaps:0/0 · proofs:0'
|
|
1350
1445
|
* ```
|
|
1351
1446
|
*/
|
|
@@ -1358,7 +1453,7 @@ function briefToTrace(source) {
|
|
|
1358
1453
|
].join(" · ");
|
|
1359
1454
|
}
|
|
1360
1455
|
/**
|
|
1361
|
-
*
|
|
1456
|
+
* Renders one exemplar as markdown lines.
|
|
1362
1457
|
*
|
|
1363
1458
|
* @remarks
|
|
1364
1459
|
* An `Example`'s two sides are the only brief members permitted to span lines, so a
|
|
@@ -1370,9 +1465,9 @@ function briefToTrace(source) {
|
|
|
1370
1465
|
*
|
|
1371
1466
|
* @example
|
|
1372
1467
|
* ```ts
|
|
1373
|
-
* import {
|
|
1468
|
+
* import { buildExample, exampleToLines } from '@orkestrel/brief'
|
|
1374
1469
|
*
|
|
1375
|
-
* exampleToLines(
|
|
1470
|
+
* exampleToLines(buildExample('<input required>', 'el.validity')) // ['- ` <input required> ` → ` el.validity `']
|
|
1376
1471
|
* ```
|
|
1377
1472
|
*/
|
|
1378
1473
|
function exampleToLines(entry) {
|
|
@@ -1403,20 +1498,20 @@ function exampleToLines(entry) {
|
|
|
1403
1498
|
];
|
|
1404
1499
|
}
|
|
1405
1500
|
/**
|
|
1406
|
-
*
|
|
1501
|
+
* Projects a brief into the copy-ready agent prompt.
|
|
1407
1502
|
*
|
|
1408
1503
|
* @remarks
|
|
1409
1504
|
* Paths are REFERENCED, never inlined — the executor retrieves them. An empty section is
|
|
1410
1505
|
* omitted entirely, so the rendering carries no filler an executor must read past.
|
|
1411
1506
|
*
|
|
1412
|
-
* @param
|
|
1507
|
+
* @param input - The brief to render.
|
|
1413
1508
|
* @returns The markdown prompt.
|
|
1414
1509
|
*
|
|
1415
1510
|
* @example
|
|
1416
1511
|
* ```ts
|
|
1417
|
-
* import {
|
|
1512
|
+
* import { briefToMarkdown, buildBrief, buildTask } from '@orkestrel/brief'
|
|
1418
1513
|
*
|
|
1419
|
-
* briefToMarkdown(
|
|
1514
|
+
* briefToMarkdown(buildBrief(buildTask('review', 'code', 'Review the gate rules.')))
|
|
1420
1515
|
* // '# Brief: Review the gate rules.\n\nreview · code\n\n## Output\n\n- format: markdown\n'
|
|
1421
1516
|
* ```
|
|
1422
1517
|
*/
|
|
@@ -1510,21 +1605,21 @@ function briefToMarkdown(input) {
|
|
|
1510
1605
|
return lines.join("\n");
|
|
1511
1606
|
}
|
|
1512
1607
|
/**
|
|
1513
|
-
*
|
|
1608
|
+
* Projects a brief into a `/goal` completion condition.
|
|
1514
1609
|
*
|
|
1515
1610
|
* @remarks
|
|
1516
1611
|
* The proofs' commands VERBATIM plus a turn cap — the goal never adds a condition the
|
|
1517
1612
|
* brief does not carry.
|
|
1518
1613
|
*
|
|
1519
|
-
* @param
|
|
1520
|
-
* @param turns - The turn cap
|
|
1614
|
+
* @param input - The brief to render.
|
|
1615
|
+
* @param turns - The turn cap. Default: `DEFAULT_BRIEF_TURNS`.
|
|
1521
1616
|
* @returns The one-line completion condition.
|
|
1522
1617
|
*
|
|
1523
1618
|
* @example
|
|
1524
1619
|
* ```ts
|
|
1525
|
-
* import {
|
|
1620
|
+
* import { briefToGoal, buildBrief, buildProof, buildTask } from '@orkestrel/brief'
|
|
1526
1621
|
*
|
|
1527
|
-
* briefToGoal(
|
|
1622
|
+
* briefToGoal(buildBrief(buildTask('test', 'code', 'Cover the gate.'), { proofs: [buildProof('x', 'npm test')] }))
|
|
1528
1623
|
* // 'Done when every proof passes: npm test exits 0. Cap: 16 turns.'
|
|
1529
1624
|
* ```
|
|
1530
1625
|
*/
|
|
@@ -1533,27 +1628,33 @@ function briefToGoal(input, turns = 16) {
|
|
|
1533
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.`;
|
|
1534
1629
|
}
|
|
1535
1630
|
/**
|
|
1536
|
-
*
|
|
1631
|
+
* Projects a brief into a subagent `Dispatch`.
|
|
1537
1632
|
*
|
|
1538
1633
|
* @remarks
|
|
1539
1634
|
* `edit` is exactly `manifest.edit`, so two dispatches whose `edit` sets do not intersect
|
|
1540
1635
|
* can run concurrently under the same brief without conflict.
|
|
1541
1636
|
*
|
|
1542
1637
|
* `authority` is exactly `brief.authority` in rank order, and it is a SEPARATE axis from the
|
|
1543
|
-
*
|
|
1638
|
+
* permission sets rather than a further partition — a ranked path normally also appears in
|
|
1544
1639
|
* `read` or `locked`, because the executor has to open what it obeys. It is projected as
|
|
1545
1640
|
* paths so a machine consumer never has to parse `prompt`, which is written for a model.
|
|
1546
1641
|
*
|
|
1547
|
-
* @param
|
|
1548
|
-
* @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.
|
|
1549
1644
|
*
|
|
1550
1645
|
* @example
|
|
1551
1646
|
* ```ts
|
|
1552
|
-
* import {
|
|
1553
|
-
*
|
|
1554
|
-
*
|
|
1555
|
-
*
|
|
1556
|
-
*
|
|
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')] }),
|
|
1557
1658
|
* })
|
|
1558
1659
|
* briefToDispatch(draft).edit // ['src/core/stores/**']
|
|
1559
1660
|
* briefToDispatch(draft).authority // ['AGENTS.md']
|
|
@@ -1571,36 +1672,39 @@ function briefToDispatch(input) {
|
|
|
1571
1672
|
};
|
|
1572
1673
|
}
|
|
1573
1674
|
/**
|
|
1574
|
-
*
|
|
1675
|
+
* Derives one imperative statement from free text.
|
|
1575
1676
|
*
|
|
1576
1677
|
* @remarks
|
|
1577
1678
|
* Whitespace collapses, the first character uppercases, and a terminator is appended
|
|
1578
1679
|
* when the text carries none. Nothing else is invented.
|
|
1579
1680
|
*
|
|
1580
1681
|
* @param text - The raw request text.
|
|
1581
|
-
* @returns The statement, or `
|
|
1682
|
+
* @returns The statement, or `undefined` for empty or whitespace-only text.
|
|
1582
1683
|
*
|
|
1583
1684
|
* @example
|
|
1584
1685
|
* ```ts
|
|
1585
1686
|
* import { deriveStatement } from '@orkestrel/brief'
|
|
1586
1687
|
*
|
|
1587
1688
|
* deriveStatement(' clean up useForm ') // 'Clean up useForm.'
|
|
1588
|
-
* deriveStatement('') //
|
|
1689
|
+
* deriveStatement('') // undefined
|
|
1589
1690
|
* ```
|
|
1590
1691
|
*/
|
|
1591
1692
|
function deriveStatement(text) {
|
|
1592
1693
|
const collapsed = collapseWhitespace(text);
|
|
1593
|
-
if (collapsed.length === 0) return
|
|
1694
|
+
if (collapsed.length === 0) return void 0;
|
|
1594
1695
|
const capitalized = collapsed.charAt(0).toUpperCase() + collapsed.slice(1);
|
|
1595
1696
|
return /[.!?]$/u.test(capitalized) ? capitalized : `${capitalized}.`;
|
|
1596
1697
|
}
|
|
1597
1698
|
/**
|
|
1598
|
-
*
|
|
1699
|
+
* Derives a `Task` from an interprets `Intent` through the caller's vocabularies.
|
|
1599
1700
|
*
|
|
1600
1701
|
* @remarks
|
|
1601
1702
|
* The vocabularies are the CALLER's policy: this maps and never guesses. An action or
|
|
1602
1703
|
* domain the caller did not map — or mapped to an off-vocabulary value — yields
|
|
1603
|
-
* `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.
|
|
1604
1708
|
*
|
|
1605
1709
|
* @param intent - The classified intent from an interpret pipeline.
|
|
1606
1710
|
* @param text - The text the statement derives from.
|
|
@@ -1619,16 +1723,17 @@ function deriveStatement(text) {
|
|
|
1619
1723
|
* ```
|
|
1620
1724
|
*/
|
|
1621
1725
|
function deriveTask(intent, text, actions, domains) {
|
|
1726
|
+
if (intent.action === void 0 || intent.domain === void 0) return void 0;
|
|
1622
1727
|
const operationDescriptor = Object.getOwnPropertyDescriptor(actions, intent.action);
|
|
1623
1728
|
const domainDescriptor = Object.getOwnPropertyDescriptor(domains, intent.domain);
|
|
1624
1729
|
const operation = operationDescriptor === void 0 ? void 0 : "value" in operationDescriptor ? operationDescriptor.value : operationDescriptor.get === void 0 ? void 0 : Reflect.apply(operationDescriptor.get, actions, []);
|
|
1625
1730
|
const domain = domainDescriptor === void 0 ? void 0 : "value" in domainDescriptor ? domainDescriptor.value : domainDescriptor.get === void 0 ? void 0 : Reflect.apply(domainDescriptor.get, domains, []);
|
|
1626
1731
|
if (!isTaskOperation(operation) || !isTaskDomain(domain)) return void 0;
|
|
1627
1732
|
const statement = deriveStatement(text);
|
|
1628
|
-
return statement
|
|
1733
|
+
return statement === void 0 ? void 0 : buildTask(operation, domain, statement);
|
|
1629
1734
|
}
|
|
1630
1735
|
/**
|
|
1631
|
-
*
|
|
1736
|
+
* Derives `Given[]` from an interprets `Entity[]`.
|
|
1632
1737
|
*
|
|
1633
1738
|
* @remarks
|
|
1634
1739
|
* Every extracted entity becomes one `extracted` fact. A nameless entity is dropped; an
|
|
@@ -1647,10 +1752,10 @@ function deriveTask(intent, text, actions, domains) {
|
|
|
1647
1752
|
* ```
|
|
1648
1753
|
*/
|
|
1649
1754
|
function deriveGivens(entities) {
|
|
1650
|
-
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)));
|
|
1651
1756
|
}
|
|
1652
1757
|
/**
|
|
1653
|
-
*
|
|
1758
|
+
* Derives `Gap[]` from an interprets `Ambiguity[]`.
|
|
1654
1759
|
*
|
|
1655
1760
|
* @remarks
|
|
1656
1761
|
* A REQUIRED ambiguity becomes a BLOCKING gap — the gate must fail closed on it. The
|
|
@@ -1671,7 +1776,7 @@ function deriveGivens(entities) {
|
|
|
1671
1776
|
function deriveGaps(ambiguities) {
|
|
1672
1777
|
return ambiguities.map((ambiguity) => {
|
|
1673
1778
|
const candidates = ambiguity.candidates.filter((candidate) => candidate.length > 0);
|
|
1674
|
-
return
|
|
1779
|
+
return buildGap(formatField(ambiguity.field), ambiguity.question, {
|
|
1675
1780
|
blocking: ambiguity.required,
|
|
1676
1781
|
...candidates.length === 0 ? {} : { candidates }
|
|
1677
1782
|
});
|
|
@@ -1680,7 +1785,7 @@ function deriveGaps(ambiguities) {
|
|
|
1680
1785
|
//#endregion
|
|
1681
1786
|
//#region src/core/parsers.ts
|
|
1682
1787
|
/**
|
|
1683
|
-
*
|
|
1788
|
+
* Parses a JSON string into a `Brief`.
|
|
1684
1789
|
*
|
|
1685
1790
|
* @remarks
|
|
1686
1791
|
* The parse-then-trust boundary for a stored brief, a tool argument, or an agent's
|
|
@@ -1714,7 +1819,7 @@ function parseBrief(value) {
|
|
|
1714
1819
|
//#endregion
|
|
1715
1820
|
//#region src/core/BriefManager.ts
|
|
1716
1821
|
/**
|
|
1717
|
-
*
|
|
1822
|
+
* Implements the self-owning, versioned and content-hashed brief registry.
|
|
1718
1823
|
*
|
|
1719
1824
|
* @remarks
|
|
1720
1825
|
* Record ids are MINTED from each brief's own content hash unless the caller names one,
|
|
@@ -1724,10 +1829,10 @@ function parseBrief(value) {
|
|
|
1724
1829
|
*
|
|
1725
1830
|
* @example
|
|
1726
1831
|
* ```ts
|
|
1727
|
-
* import { BriefManager,
|
|
1832
|
+
* import { BriefManager, buildBrief, buildTask } from '@orkestrel/brief'
|
|
1728
1833
|
*
|
|
1729
1834
|
* const briefs = new BriefManager()
|
|
1730
|
-
* const record = briefs.add(
|
|
1835
|
+
* const record = briefs.add(buildBrief(buildTask('document', 'writing', 'Write the brief guide.')))
|
|
1731
1836
|
* record.id === record.hash // true
|
|
1732
1837
|
* briefs.destroy()
|
|
1733
1838
|
* ```
|
|
@@ -1754,7 +1859,7 @@ var BriefManager = class {
|
|
|
1754
1859
|
get emitter() {
|
|
1755
1860
|
return this.#emitter;
|
|
1756
1861
|
}
|
|
1757
|
-
get
|
|
1862
|
+
get count() {
|
|
1758
1863
|
return this.#records.size;
|
|
1759
1864
|
}
|
|
1760
1865
|
has(id) {
|
|
@@ -1769,9 +1874,9 @@ var BriefManager = class {
|
|
|
1769
1874
|
this.#refuseDestroyed();
|
|
1770
1875
|
return [...this.#records.values()];
|
|
1771
1876
|
}
|
|
1772
|
-
add(
|
|
1877
|
+
add(brief, options) {
|
|
1773
1878
|
this.#refuseDestroyed();
|
|
1774
|
-
const record = this.#stage(
|
|
1879
|
+
const record = this.#stage(brief, this.#records, options);
|
|
1775
1880
|
this.#commit(record);
|
|
1776
1881
|
return record;
|
|
1777
1882
|
}
|
|
@@ -1838,7 +1943,7 @@ var BriefManager = class {
|
|
|
1838
1943
|
//#endregion
|
|
1839
1944
|
//#region src/core/BriefCompiler.ts
|
|
1840
1945
|
/**
|
|
1841
|
-
*
|
|
1946
|
+
* Implements the compilation orchestrator — the `[interpret, draft, gate, pin]` pipeline.
|
|
1842
1947
|
*
|
|
1843
1948
|
* @remarks
|
|
1844
1949
|
* `compile` is genuinely SYNCHRONOUS and never throws for a brief it cannot emit: a
|
|
@@ -1848,13 +1953,13 @@ var BriefManager = class {
|
|
|
1848
1953
|
*
|
|
1849
1954
|
* @example
|
|
1850
1955
|
* ```ts
|
|
1851
|
-
* import { BriefCompiler,
|
|
1956
|
+
* import { BriefCompiler, buildProof, buildTask } from '@orkestrel/brief'
|
|
1852
1957
|
*
|
|
1853
1958
|
* const compiler = new BriefCompiler()
|
|
1854
1959
|
* const briefing = compiler.compile({
|
|
1855
|
-
* task:
|
|
1960
|
+
* task: buildTask('audit', 'code', 'Audit the barrel for undocumented exports.'),
|
|
1856
1961
|
* outcomes: [{ rank: 1, text: 'every export appears in the guide', required: true }],
|
|
1857
|
-
* proofs: [
|
|
1962
|
+
* proofs: [buildProof('parity passes', 'npm run test:guides')],
|
|
1858
1963
|
* })
|
|
1859
1964
|
* briefing.brief !== undefined // true — the presence of the brief IS the completeness test
|
|
1860
1965
|
* compiler.destroy()
|
|
@@ -2005,9 +2110,9 @@ var BriefCompiler = class {
|
|
|
2005
2110
|
this.#emitter.emit("compile", briefing);
|
|
2006
2111
|
return briefing;
|
|
2007
2112
|
}
|
|
2008
|
-
gate(
|
|
2113
|
+
gate(brief) {
|
|
2009
2114
|
this.#refuseDestroyed();
|
|
2010
|
-
const ruled = attempt(() => this.#own(this.#reason.reason(briefToSubject(
|
|
2115
|
+
const ruled = attempt(() => this.#own(this.#reason.reason(briefToSubject(brief), buildGateDefinition()), [
|
|
2011
2116
|
"reasoning",
|
|
2012
2117
|
"conclusion",
|
|
2013
2118
|
"rules",
|
|
@@ -2096,7 +2201,7 @@ var BriefCompiler = class {
|
|
|
2096
2201
|
message: `Gate refused: ${unready.join(", ")}`
|
|
2097
2202
|
};
|
|
2098
2203
|
if (verdict === void 0) return void 0;
|
|
2099
|
-
const refused = verdict.rules.filter((entry) => !entry.
|
|
2204
|
+
const refused = verdict.rules.filter((entry) => !entry.applied).map((entry) => entry.id).join(", ");
|
|
2100
2205
|
if (refused.length === 0) return {
|
|
2101
2206
|
stage: "gate",
|
|
2102
2207
|
code: "BLOCKED",
|
|
@@ -2111,7 +2216,7 @@ var BriefCompiler = class {
|
|
|
2111
2216
|
#unresolved(interpretation, failures) {
|
|
2112
2217
|
if (interpretation !== void 0) return [];
|
|
2113
2218
|
if (!failures.some((entry) => entry.stage === "interpret")) return [];
|
|
2114
|
-
return [
|
|
2219
|
+
return [buildGap("gaps", "The interpret stage failed, so the request is unread and its unknowns are unknown", { blocking: true })];
|
|
2115
2220
|
}
|
|
2116
2221
|
#draft(input, interpretation, unresolved) {
|
|
2117
2222
|
const derived = interpretation === void 0 ? void 0 : deriveTask(interpretation.intent, interpretation.text, this.#actions, this.#domains);
|
|
@@ -2120,9 +2225,9 @@ var BriefCompiler = class {
|
|
|
2120
2225
|
stage: "draft",
|
|
2121
2226
|
field: "task"
|
|
2122
2227
|
});
|
|
2123
|
-
return snapshotBrief(
|
|
2228
|
+
return snapshotBrief(buildBrief(subject, {
|
|
2124
2229
|
authority: input.authority ?? [],
|
|
2125
|
-
manifest: input.manifest ??
|
|
2230
|
+
manifest: input.manifest ?? buildManifest(),
|
|
2126
2231
|
outcomes: input.outcomes ?? [],
|
|
2127
2232
|
rules: input.rules ?? [],
|
|
2128
2233
|
invariants: input.invariants ?? [],
|
|
@@ -2136,7 +2241,7 @@ var BriefCompiler = class {
|
|
|
2136
2241
|
...input.gaps ?? []
|
|
2137
2242
|
],
|
|
2138
2243
|
risks: input.risks ?? [],
|
|
2139
|
-
output: input.output ??
|
|
2244
|
+
output: input.output ?? buildOutput("markdown"),
|
|
2140
2245
|
proofs: input.proofs ?? []
|
|
2141
2246
|
}));
|
|
2142
2247
|
}
|
|
@@ -2164,7 +2269,7 @@ var BriefCompiler = class {
|
|
|
2164
2269
|
//#endregion
|
|
2165
2270
|
//#region src/core/factories.ts
|
|
2166
2271
|
/**
|
|
2167
|
-
*
|
|
2272
|
+
* Creates a compilation orchestrator.
|
|
2168
2273
|
*
|
|
2169
2274
|
* @remarks
|
|
2170
2275
|
* With no engines supplied the compiler wires its own: a default `createInterpret()`
|
|
@@ -2172,7 +2277,8 @@ var BriefCompiler = class {
|
|
|
2172
2277
|
* `createReason` carrying one `LogicalReasoner` for the gate. Pass your own to share
|
|
2173
2278
|
* instances or observe their emitters — the compiler destroys ONLY what it created.
|
|
2174
2279
|
*
|
|
2175
|
-
* @param options - Engines to borrow, the
|
|
2280
|
+
* @param options - Engines to borrow, the `actions` and `domains` intent vocabularies, and
|
|
2281
|
+
* emitter hooks.
|
|
2176
2282
|
* @returns A working {@link BriefCompilerInterface}.
|
|
2177
2283
|
*
|
|
2178
2284
|
* @example
|
|
@@ -2187,7 +2293,7 @@ function createBriefCompiler(options) {
|
|
|
2187
2293
|
return new BriefCompiler(options);
|
|
2188
2294
|
}
|
|
2189
2295
|
/**
|
|
2190
|
-
*
|
|
2296
|
+
* Creates a brief registry.
|
|
2191
2297
|
*
|
|
2192
2298
|
* @param options - An optional seed collection plus emitter hooks.
|
|
2193
2299
|
* @returns A working {@link BriefManagerInterface}.
|
|
@@ -2197,7 +2303,7 @@ function createBriefCompiler(options) {
|
|
|
2197
2303
|
* import { createBriefManager } from '@orkestrel/brief'
|
|
2198
2304
|
*
|
|
2199
2305
|
* const briefs = createBriefManager()
|
|
2200
|
-
* briefs.
|
|
2306
|
+
* briefs.count // 0
|
|
2201
2307
|
* briefs.destroy()
|
|
2202
2308
|
* ```
|
|
2203
2309
|
*/
|
|
@@ -2205,7 +2311,7 @@ function createBriefManager(options) {
|
|
|
2205
2311
|
return new BriefManager(options);
|
|
2206
2312
|
}
|
|
2207
2313
|
/**
|
|
2208
|
-
*
|
|
2314
|
+
* Compiles `briefShape` into a guard, parser, JSON Schema, and seeded generator bundle.
|
|
2209
2315
|
*
|
|
2210
2316
|
* @remarks
|
|
2211
2317
|
* The schema is what a tool boundary needs — hand it to `schemaToParameters` — and
|
|
@@ -2229,6 +2335,6 @@ function createBriefContract() {
|
|
|
2229
2335
|
return createContract(briefShape);
|
|
2230
2336
|
}
|
|
2231
2337
|
//#endregion
|
|
2232
|
-
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,
|
|
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 };
|
|
2233
2339
|
|
|
2234
2340
|
//# sourceMappingURL=index.js.map
|