rcf-lite 0.28.4 → 0.29.0
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/CHANGELOG.md +107 -1
- package/bin/rcf.js +4 -0
- package/fixtures/canary-manifest.json +9 -9
- package/guidance/define-draft-shapes.md +89 -0
- package/guidance/define-intake.md +97 -0
- package/guidance/define-intent.md +107 -0
- package/guidance/discover-prototype.md +67 -0
- package/guidance/discovery-prototypes.md +69 -0
- package/guidance/harness-template.md +34 -0
- package/guidance/managed/agent-instructions-block.hash +1 -1
- package/guidance/managed/agent-instructions-block.md +34 -0
- package/guidance/manifest.json +9 -2
- package/package.json +1 -1
- package/rcf/adrs/adr-4124-define-freeze-verb.json +30 -0
- package/rcf/adrs/adr-4126-persona-tagged-blockers-two-levels.json +40 -0
- package/rcf/adrs/adr-4127-readiness-tab-order-and-register.json +40 -0
- package/rcf/adrs/adr-4129-questions-harness-driven-loop.json +25 -0
- package/rcf/adrs/adr-4130-intake-harness-split-tool-scanned.json +20 -0
- package/rcf/adrs/adr-4132-prototypes-in-discovery-as-evidence.json +20 -0
- package/rcf/adrs/adr-4133-dag-dependency-depth-layout.json +40 -0
- package/rcf/adrs/adr-4134-index-json-route.json +35 -0
- package/rcf/adrs/adr-4135-readiness-po-register-and-adapter.json +35 -0
- package/rcf/adrs/adr-4136-embedding-host-fixture.json +35 -0
- package/rcf/code-nodes/cn-725.json +18 -2
- package/rcf/code-nodes/cn-726.json +10 -2
- package/rcf/code-nodes/cn-727.json +13 -4
- package/rcf/code-nodes/cn-728.json +18 -2
- package/rcf/code-nodes/cn-729.json +10 -2
- package/rcf/code-nodes/cn-730.json +13 -3
- package/rcf/code-nodes/cn-731.json +20 -0
- package/rcf/code-nodes/cn-732.json +14 -0
- package/rcf/code-nodes/cn-733.json +14 -0
- package/rcf/code-nodes/cn-734.json +14 -0
- package/rcf/code-nodes/cn-735.json +20 -0
- package/rcf/code-nodes/cn-736.json +14 -0
- package/rcf/code-nodes/cn-737.json +14 -0
- package/rcf/code-nodes/cn-738.json +14 -0
- package/rcf/code-nodes/cn-739.json +18 -0
- package/rcf/code-nodes/cn-740.json +19 -0
- package/rcf/code-nodes/cn-741.json +16 -0
- package/rcf/code-nodes/cn-742.json +16 -0
- package/rcf/code-nodes/cn-743.json +15 -0
- package/rcf/code-nodes/cn-744.json +22 -0
- package/rcf/code-nodes/cn-745.json +16 -0
- package/rcf/code-nodes/cn-746.json +14 -0
- package/rcf/code-nodes/cn-747.json +18 -0
- package/rcf/code-nodes/cn-748.json +16 -0
- package/rcf/code-nodes/cn-749.json +16 -0
- package/rcf/code-nodes/cn-750.json +15 -0
- package/rcf/code-nodes/cn-751.json +18 -0
- package/rcf/code-nodes/cn-752.json +14 -0
- package/rcf/fbs/fbs-196.json +13 -5
- package/rcf/fbs/fbs-198.json +42 -0
- package/rcf/fbs/fbs-199.json +59 -0
- package/rcf/fbs/fbs-200.json +46 -0
- package/rcf/fbs/fbs-201.json +49 -0
- package/rcf/fbs/fbs-202.json +110 -0
- package/rcf/fbs/fbs-203.json +100 -0
- package/rcf/requirements/req-002.json +2 -2
- package/rcf/requirements/req-173.json +2 -2
- package/rcf/requirements/req-174.json +10 -4
- package/rcf/requirements/req-175.json +10 -4
- package/rcf/requirements/req-176.json +25 -0
- package/rcf/requirements/req-180.json +31 -0
- package/rcf/requirements/req-181.json +31 -0
- package/rcf/requirements/req-182.json +25 -0
- package/rcf/requirements/req-186.json +25 -0
- package/rcf/requirements/req-187.json +31 -0
- package/rcf/requirements/req-188.json +25 -0
- package/rcf/tacs/tac-4121-define-ledgers.json +15 -3
- package/rcf/tacs/tac-4122-define-gates.json +40 -4
- package/rcf/tacs/tac-4123-define-readiness.json +39 -3
- package/rcf/tacs/tac-4124-define-freeze.json +28 -0
- package/rcf/tacs/tac-4127-readiness-view.json +49 -0
- package/rcf/tacs/tac-4128-define-guidance.json +53 -0
- package/rcf/tacs/tac-4129-define-questions.json +45 -0
- package/rcf/tacs/tac-4130-define-intake-brief.json +35 -0
- package/rcf/tacs/tac-4131-build-dag-view.json +39 -0
- package/rcf/tacs/tac-4132-id-lookup.json +42 -0
- package/rcf/tacs/tac-4133-readiness-po-layer.json +37 -0
- package/rcf/tacs/tac-4134-wespa-host-fixture.json +54 -0
- package/rcf/test-suites/ts-016.json +3 -3
- package/rcf/test-suites/ts-215.json +148 -4
- package/rcf/test-suites/ts-216.json +19 -3
- package/rcf/test-suites/ts-218.json +78 -0
- package/rcf/test-suites/ts-219.json +70 -0
- package/rcf/test-suites/ts-220.json +78 -0
- package/rcf/test-suites/ts-221.json +78 -0
- package/rcf/test-suites/ts-222.json +100 -0
- package/rcf/test-suites/ts-223.json +94 -0
- package/rcf/test-suites/ts-224.json +70 -0
- package/rcf/test-suites/ts-225.json +78 -0
- package/rcf/test-suites/ts-226.json +158 -0
- package/rcf/test-suites/ts-227.json +38 -0
- package/rcf/test-suites/ts-228.json +86 -0
- package/rcf/test-suites/ts-229.json +83 -0
- package/rcf/user-stories/us-17302.json +50 -0
- package/rcf/user-stories/us-17401.json +14 -2
- package/rcf/user-stories/us-17402.json +55 -0
- package/rcf/user-stories/us-17403.json +60 -0
- package/rcf/user-stories/us-17502.json +60 -0
- package/rcf/user-stories/us-17503.json +65 -0
- package/rcf/user-stories/us-17601.json +58 -0
- package/rcf/user-stories/us-18001.json +55 -0
- package/rcf/user-stories/us-18002.json +35 -0
- package/rcf/user-stories/us-18101.json +35 -0
- package/rcf/user-stories/us-18202.json +40 -0
- package/rcf/user-stories/us-18601.json +55 -0
- package/rcf/user-stories/us-18602.json +50 -0
- package/rcf/user-stories/us-18603.json +45 -0
- package/rcf/user-stories/us-18701.json +50 -0
- package/rcf/user-stories/us-18702.json +50 -0
- package/rcf/user-stories/us-18801.json +45 -0
- package/rcf/user-stories/us-204.json +59 -0
- package/rcf/user-stories/us-205.json +64 -0
- package/rcf/user-stories/us-206.json +54 -0
- package/releases/releases.yaml +11 -1
- package/src/cli/freeze.js +730 -0
- package/src/cli/help.js +12 -0
- package/src/cli/ledger.js +228 -12
- package/src/cli/questions.js +288 -0
- package/src/cli/readiness.js +267 -49
- package/src/cli/view.js +29 -0
- package/src/define/freeze-record.js +30 -15
- package/src/define/ledgers.js +210 -11
- package/src/intake/index.js +1 -1
- package/src/intake/orchestrator.js +117 -0
- package/src/mcp/tools.js +354 -0
- package/src/query/delta.js +6 -0
- package/src/query/gates.js +314 -39
- package/src/query/index.js +16 -2
- package/src/query/questions.js +438 -0
- package/src/query/readiness.js +248 -7
- package/src/server/index.js +24 -2
- package/src/server/routes.js +68 -1
- package/src/setup/identity-seed.js +17 -0
- package/src/view/build-dag.js +563 -0
- package/src/view/components/badge.js +41 -0
- package/src/view/components/diff.js +72 -0
- package/src/view/components/doc-row.js +62 -0
- package/src/view/components/empty-state.js +35 -0
- package/src/view/components/entity-selector.js +137 -0
- package/src/view/components/filter-bar.js +97 -0
- package/src/view/components/findings-list.js +49 -0
- package/src/view/components/index.js +12 -0
- package/src/view/components/pill.js +46 -0
- package/src/view/components/sub-tab-strip.js +43 -0
- package/src/view/components/toast.js +25 -0
- package/src/view/components-fixture.js +265 -0
- package/src/view/doc-renderers/architecture.js +304 -0
- package/src/view/doc-renderers/build.js +160 -0
- package/src/view/doc-renderers/index.js +2 -0
- package/src/view/doc-renderers/prd.js +35 -6
- package/src/view/html-page.js +700 -602
- package/src/view/index.js +96 -3
- package/src/view/live-client.js +39 -6
- package/src/view/needs-work.js +79 -0
- package/src/view/page-init.js +2193 -0
- package/src/view/readiness/phrasebook.js +115 -0
- package/src/view/readiness/po-layer.js +288 -0
- package/src/view/readiness/question-adapter.js +329 -0
- package/src/view/readiness/stage-legend.js +71 -0
- package/src/view/readiness.js +505 -0
- package/src/view/style.css +2017 -382
- package/src/view/test-host/embed-client.js +121 -0
- package/src/view/test-host/fixture.js +183 -0
- package/src/view/test-host/lints.js +220 -0
- package/src/view/view-index.js +323 -0
package/src/query/gates.js
CHANGED
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
|
|
46
46
|
import { computeQueue } from '../build/queue.js';
|
|
47
47
|
import { isOptedOut } from '../req-baseline/opt-out.js';
|
|
48
|
-
import { BRIEF_KINDS } from '../define/ledgers.js';
|
|
48
|
+
import { BRIEF_KINDS, parseResolvedBy } from '../define/ledgers.js';
|
|
49
49
|
|
|
50
50
|
/** Canonical stage order (proposal §3.2). */
|
|
51
51
|
export const STAGE_ORDER = /** @type {const} */ (['D1', 'D2', 'D3', 'D4', 'D5', 'D6', 'D7', 'D8']);
|
|
@@ -91,6 +91,195 @@ export const INTERFACE_KINDS = /** @type {const} */ ([
|
|
|
91
91
|
'port', 'fileFormat', 'fixture', 'other',
|
|
92
92
|
]);
|
|
93
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Owning persona for every stage-gate check in 0.29.0 (ADR-4126).
|
|
96
|
+
*
|
|
97
|
+
* Two values: `productOwner` (the person who owns what the product
|
|
98
|
+
* must do) and `engineer` (the person who owns how it is built).
|
|
99
|
+
* There is no `either` / `shared` / `both`; a check that would need
|
|
100
|
+
* both is split, which is what happens to `skeleton:reqFields`
|
|
101
|
+
* (becomes `skeleton:reqIntent` + `skeleton:reqShape`) and
|
|
102
|
+
* `decisions:allAnswered` (becomes `decisions:wellFormed` +
|
|
103
|
+
* `decisions:allAnswered`). A failing item inherits its check's
|
|
104
|
+
* persona; there is no per-item override.
|
|
105
|
+
*
|
|
106
|
+
* The `notApplicable` placeholder (`stage:<D>:scope`) carries the
|
|
107
|
+
* per-stage fallback persona from `STAGE_FALLBACK_PERSONA` below.
|
|
108
|
+
* It is always ok so it never affects a level verdict.
|
|
109
|
+
*/
|
|
110
|
+
export const CHECK_PERSONA = /** @type {const} */ ({
|
|
111
|
+
// D1 Brief: every check is PO (the brief is the PO's statement of intent).
|
|
112
|
+
'brief:sinceFreeze': 'productOwner',
|
|
113
|
+
'brief:kinds': 'productOwner',
|
|
114
|
+
'brief:openQuestions': 'productOwner',
|
|
115
|
+
'brief:profile': 'productOwner',
|
|
116
|
+
// D2 Skeleton: resolvedBy + reqIntent are PO; reqShape, tadPersistence, deployAdr are engineer.
|
|
117
|
+
'skeleton:resolvedBy': 'productOwner',
|
|
118
|
+
'skeleton:reqIntent': 'productOwner',
|
|
119
|
+
'skeleton:reqShape': 'engineer',
|
|
120
|
+
'skeleton:tadPersistence': 'engineer',
|
|
121
|
+
'skeleton:deployAdr': 'engineer',
|
|
122
|
+
// D3 Shapes: every check is engineer (shapes are the engineer's first act after hand-over).
|
|
123
|
+
'shapes:tacHasInterface': 'engineer',
|
|
124
|
+
'shapes:kindVocabulary': 'engineer',
|
|
125
|
+
'shapes:draftSettled': 'engineer',
|
|
126
|
+
// D4 Stories: reqHasUs is PO ("what does someone do with this?"); the floors are engineer.
|
|
127
|
+
'stories:reqHasUs': 'productOwner',
|
|
128
|
+
'stories:usFloors': 'engineer',
|
|
129
|
+
// D5 Crosscut: TAD + concern-ledger content is engineering.
|
|
130
|
+
'crosscut:securityArchitecture': 'engineer',
|
|
131
|
+
'crosscut:operationalConcerns': 'engineer',
|
|
132
|
+
'crosscut:concernsResolved': 'engineer',
|
|
133
|
+
// D6 Consistency: schema validity and probes are engineer.
|
|
134
|
+
'consistency:validateClean': 'engineer',
|
|
135
|
+
'consistency:probeCount': 'engineer',
|
|
136
|
+
// D7 Decisions: enumeration is PO (the PO answers or lets the default stand);
|
|
137
|
+
// zero-open is the engineer's hand-over condition.
|
|
138
|
+
'decisions:wellFormed': 'productOwner',
|
|
139
|
+
'decisions:allAnswered': 'engineer',
|
|
140
|
+
// D8 Freeze: every check is mechanical / engineering.
|
|
141
|
+
'freeze:priorGates': 'engineer',
|
|
142
|
+
'freeze:acFbsOwnership': 'engineer',
|
|
143
|
+
'freeze:queueHead': 'engineer',
|
|
144
|
+
'freeze:validateClean': 'engineer',
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Per-check heading question in the owning persona's register
|
|
149
|
+
* (ADR-4126, proposal section 1.1). The text report and the
|
|
150
|
+
* Readiness tab print the question as the heading of each check's
|
|
151
|
+
* findings list. Static per check in 0.29.0; per-item plain-English
|
|
152
|
+
* wording is step 3's guided elicitation.
|
|
153
|
+
*/
|
|
154
|
+
export const CHECK_QUESTION = /** @type {const} */ ({
|
|
155
|
+
'brief:sinceFreeze': 'What has changed, or what is this project for?',
|
|
156
|
+
'brief:kinds': 'Is this a capability, a constraint, an actor, or something else?',
|
|
157
|
+
'brief:openQuestions': 'Any open questions in the brief, resolved or promoted to a decision?',
|
|
158
|
+
'brief:profile': 'How do you want to review, and who are you writing for?',
|
|
159
|
+
'skeleton:resolvedBy': 'Does this statement become a requirement, an entity, or an omission?',
|
|
160
|
+
'skeleton:reqIntent': 'What does this requirement mean and where does it sit?',
|
|
161
|
+
'skeleton:reqShape': 'Every requirement in scope carries a shape classification.',
|
|
162
|
+
'skeleton:tadPersistence': 'TAD data architecture lists dataStores and coreEntities for persistence REQs.',
|
|
163
|
+
'skeleton:deployAdr': 'Exactly one Deploy target or Deploy deferral ADR exists.',
|
|
164
|
+
'shapes:tacHasInterface': 'Every TAC in scope has at least one interface.',
|
|
165
|
+
'shapes:kindVocabulary': 'Every interface kind is in the closed vocabulary.',
|
|
166
|
+
'shapes:draftSettled': 'Every draft interface has been settled (the [draft] marker is removed).',
|
|
167
|
+
'stories:reqHasUs': 'What does someone do with this requirement?',
|
|
168
|
+
'stories:usFloors': 'Every story in scope meets the class, tacIds and testability floors.',
|
|
169
|
+
'crosscut:securityArchitecture': 'TAD.securityArchitecture is present when an auth or httpApi REQ exists.',
|
|
170
|
+
'crosscut:operationalConcerns': 'TAD.operationalConcerns is present when a deployed-scope AC exists.',
|
|
171
|
+
'crosscut:concernsResolved': 'Every concern-ledger entry on a REQ in scope is applied or waived.',
|
|
172
|
+
'consistency:validateClean': 'Validate is clean tree-wide.',
|
|
173
|
+
'consistency:probeCount': 'Zero open probe-ledger entries.',
|
|
174
|
+
'decisions:wellFormed': 'Every decision is enumerated (question, two or more options, a default).',
|
|
175
|
+
'decisions:allAnswered': 'Zero decisions are open.',
|
|
176
|
+
'freeze:priorGates': 'Every prior stage passed or is acknowledged at the current hash.',
|
|
177
|
+
'freeze:acFbsOwnership': 'Every AC is owned by exactly one FBS.',
|
|
178
|
+
'freeze:queueHead': 'Queue head is actionable and not a placeholder.',
|
|
179
|
+
'freeze:validateClean': 'Validate is clean tree-wide.',
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Per-stage fallback persona for the `stage:<D>:scope` placeholder
|
|
184
|
+
* check that `notApplicable` envelopes carry. The placeholder is
|
|
185
|
+
* always ok so this never affects a level, but keeps "every check
|
|
186
|
+
* has a persona" true for consumers and the table test (ADR-4126
|
|
187
|
+
* decision 10).
|
|
188
|
+
*/
|
|
189
|
+
export const STAGE_FALLBACK_PERSONA = /** @type {const} */ ({
|
|
190
|
+
D1: 'productOwner',
|
|
191
|
+
D2: 'productOwner',
|
|
192
|
+
D3: 'engineer',
|
|
193
|
+
D4: 'productOwner',
|
|
194
|
+
D5: 'engineer',
|
|
195
|
+
D6: 'engineer',
|
|
196
|
+
D7: 'productOwner',
|
|
197
|
+
D8: 'engineer',
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Look up the question heading for a check name.
|
|
202
|
+
* @param {string} name
|
|
203
|
+
* @returns {string}
|
|
204
|
+
*/
|
|
205
|
+
export function checkQuestion(name) {
|
|
206
|
+
return /** @type {any} */ (CHECK_QUESTION)[name] ?? '';
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Look up the owning persona for a check name.
|
|
211
|
+
* @param {string} name
|
|
212
|
+
* @returns {'productOwner' | 'engineer' | null}
|
|
213
|
+
*/
|
|
214
|
+
export function checkPersona(name) {
|
|
215
|
+
const v = /** @type {any} */ (CHECK_PERSONA)[name];
|
|
216
|
+
return v === 'productOwner' || v === 'engineer' ? v : null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Draft marker (ADR-4126 for `TAC.interfaces[].description` in 0.29.0;
|
|
221
|
+
* REQ-188 extends it to `TAC.purpose` and `coreEntities[].description`
|
|
222
|
+
* in 0.30.0). A description that starts with `[draft]` after optional
|
|
223
|
+
* leading whitespace is pre-populated engineer entry material; the
|
|
224
|
+
* engineer removes the marker when the shape is settled. Decision 6
|
|
225
|
+
* uses the same bracket-prefix convention for AC class.
|
|
226
|
+
*/
|
|
227
|
+
const INTERFACE_DRAFT_RE = /^\[draft\]/;
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* True when a string carries the leading `[draft]` marker
|
|
231
|
+
* (whitespace tolerated). Pure string test used by every draft-home
|
|
232
|
+
* reader below.
|
|
233
|
+
*
|
|
234
|
+
* @param {unknown} value
|
|
235
|
+
* @returns {boolean}
|
|
236
|
+
*/
|
|
237
|
+
export function hasDraftMarker(value) {
|
|
238
|
+
if (typeof value !== 'string') return false;
|
|
239
|
+
return INTERFACE_DRAFT_RE.test(value.trimStart());
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* True when an interface's `description` carries the leading
|
|
244
|
+
* `[draft]` marker (whitespace tolerated). Thin wrapper over
|
|
245
|
+
* `hasDraftMarker` kept for ADR-4126 call sites; REQ-188 adds
|
|
246
|
+
* `parseTacPurposeDraft` and `parseCoreEntityDraft` for the two new
|
|
247
|
+
* homes.
|
|
248
|
+
*
|
|
249
|
+
* @param {unknown} iface
|
|
250
|
+
* @returns {boolean}
|
|
251
|
+
*/
|
|
252
|
+
export function parseInterfaceDraft(iface) {
|
|
253
|
+
if (!iface || typeof iface !== 'object') return false;
|
|
254
|
+
return hasDraftMarker(/** @type {any} */ (iface).description);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* True when a TAC's `purpose` carries the leading `[draft]` marker
|
|
259
|
+
* (whitespace tolerated). REQ-188 draft home; the engineer removes
|
|
260
|
+
* the marker when the TAC's purpose is owned.
|
|
261
|
+
*
|
|
262
|
+
* @param {unknown} tac
|
|
263
|
+
* @returns {boolean}
|
|
264
|
+
*/
|
|
265
|
+
export function parseTacPurposeDraft(tac) {
|
|
266
|
+
if (!tac || typeof tac !== 'object') return false;
|
|
267
|
+
return hasDraftMarker(/** @type {any} */ (tac).purpose);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* True when a coreEntities entry's `description` carries the
|
|
272
|
+
* leading `[draft]` marker (whitespace tolerated). REQ-188 draft
|
|
273
|
+
* home; the engineer removes the marker when the entity is owned.
|
|
274
|
+
*
|
|
275
|
+
* @param {unknown} entity
|
|
276
|
+
* @returns {boolean}
|
|
277
|
+
*/
|
|
278
|
+
export function parseCoreEntityDraft(entity) {
|
|
279
|
+
if (!entity || typeof entity !== 'object') return false;
|
|
280
|
+
return hasDraftMarker(/** @type {any} */ (entity).description);
|
|
281
|
+
}
|
|
282
|
+
|
|
94
283
|
/** Warn-with-ack stages in 0.29.0 (ADR-4122). */
|
|
95
284
|
const WARN_WITH_ACK = new Set(['D3', 'D5', 'D6']);
|
|
96
285
|
|
|
@@ -156,12 +345,26 @@ export function parseAcClass(ac) {
|
|
|
156
345
|
* twentieth failure. `ok` is likewise derived from the untruncated
|
|
157
346
|
* count. The section 2.4 shape stays intact -- `failing` remains the
|
|
158
347
|
* display array; no new field is exported.
|
|
159
|
-
* @returns {{ name: string, ok: boolean, over: 'delta'|'tree', pass: number, total: number, failing: Array<{ id: string, why: string }
|
|
348
|
+
* @returns {{ name: string, ok: boolean, over: 'delta'|'tree', pass: number, total: number, failing: Array<{ id: string, why: string }>, persona: 'productOwner'|'engineer', question: string }}
|
|
160
349
|
*/
|
|
161
350
|
function makeCheck(name, over, total, failing, totalFailingCount) {
|
|
162
351
|
const nFailing = typeof totalFailingCount === 'number' ? totalFailingCount : failing.length;
|
|
163
352
|
const pass = Math.max(0, total - nFailing);
|
|
164
|
-
|
|
353
|
+
// Resolve persona + question for the check. For stage placeholders
|
|
354
|
+
// (`stage:<D>:scope`) the persona comes from STAGE_FALLBACK_PERSONA.
|
|
355
|
+
// Unknown names fall back to engineer with an empty question so
|
|
356
|
+
// consumers never see null; the table test catches any new check
|
|
357
|
+
// that forgets to register in CHECK_PERSONA / CHECK_QUESTION.
|
|
358
|
+
let persona = /** @type {any} */ (CHECK_PERSONA)[name];
|
|
359
|
+
if (!persona) {
|
|
360
|
+
const stageMatch = /^stage:(D[1-8]):scope$/.exec(name);
|
|
361
|
+
persona = stageMatch
|
|
362
|
+
? /** @type {any} */ (STAGE_FALLBACK_PERSONA)[stageMatch[1]]
|
|
363
|
+
: 'engineer';
|
|
364
|
+
}
|
|
365
|
+
const question = /** @type {any} */ (CHECK_QUESTION)[name]
|
|
366
|
+
?? (name.startsWith('stage:') ? 'No documents of this stage’s type are in scope.' : '');
|
|
367
|
+
return { name, ok: nFailing === 0, over, pass, total, failing: [...failing], persona, question };
|
|
165
368
|
}
|
|
166
369
|
|
|
167
370
|
/**
|
|
@@ -342,57 +545,68 @@ export function checkD2Skeleton(ctx) {
|
|
|
342
545
|
|
|
343
546
|
const checks = [];
|
|
344
547
|
|
|
345
|
-
// Check 1: every capability/constraint/entity/... statement
|
|
346
|
-
//
|
|
347
|
-
//
|
|
348
|
-
//
|
|
349
|
-
|
|
548
|
+
// Check 1 (0.30.0): every capability/constraint/entity/... statement
|
|
549
|
+
// resolves to a closed-grammar pointer in `resolvedBy`:
|
|
550
|
+
// REQ-nnn | TAD.entity:<name> | PRD.user:<name>
|
|
551
|
+
// | TAD.system:<name> | TAC-nnnn | omitted:<reason>
|
|
552
|
+
// The 0.29.0 REQ-title fallback is dropped (decision 3; a title hit
|
|
553
|
+
// becomes a suggestion the PR 2 computeQuestions surface returns).
|
|
554
|
+
// A statement with no `resolvedBy` fails; a statement with a
|
|
555
|
+
// `resolvedBy` outside the grammar fails with 'pointer does not
|
|
556
|
+
// resolve'.
|
|
557
|
+
/** @type {Array<{ id: string, why: string }>} */
|
|
558
|
+
const unresolvedFailing = [];
|
|
559
|
+
for (const s of resolvingStmts) {
|
|
350
560
|
const rb = s?.resolvedBy;
|
|
351
|
-
if (typeof rb
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
if (!text) return false;
|
|
355
|
-
for (const req of tree.requirements ?? []) {
|
|
356
|
-
const title = typeof req?.title === 'string' ? req.title.toLowerCase() : '';
|
|
357
|
-
if (title && text.includes(title)) return true;
|
|
561
|
+
if (typeof rb !== 'string' || rb.length === 0) {
|
|
562
|
+
unresolvedFailing.push({ id: `brief:${s.id}`, why: `${s.kind} statement has no resolvedBy` });
|
|
563
|
+
continue;
|
|
358
564
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
565
|
+
const parsed = parseResolvedBy(rb, tree);
|
|
566
|
+
if (!parsed.ok) {
|
|
567
|
+
unresolvedFailing.push({ id: `brief:${s.id}`, why: 'pointer does not resolve' });
|
|
568
|
+
}
|
|
569
|
+
}
|
|
362
570
|
checks.push(makeCheck(
|
|
363
571
|
'skeleton:resolvedBy',
|
|
364
572
|
'delta',
|
|
365
573
|
resolvingStmts.length,
|
|
366
|
-
|
|
574
|
+
unresolvedFailing,
|
|
367
575
|
));
|
|
368
576
|
|
|
369
|
-
//
|
|
577
|
+
// Checks 2a / 2b: the former `skeleton:reqFields` split into two
|
|
578
|
+
// homogeneous checks (ADR-4126, proposal section 1.3). Each
|
|
579
|
+
// evaluates every REQ in scope independently so a REQ with a TODO
|
|
580
|
+
// description still has its shapes reported, and vice versa.
|
|
581
|
+
const reqIntentFail = [];
|
|
370
582
|
const reqShapeFail = [];
|
|
371
583
|
for (const req of reqInScope) {
|
|
584
|
+
// reqIntent (productOwner): description without a TODO + domain
|
|
585
|
+
// (what the requirement means and where it sits).
|
|
372
586
|
if (typeof req.description !== 'string' || req.description.length === 0 || TODO_RE.test(req.description)) {
|
|
373
|
-
|
|
374
|
-
|
|
587
|
+
reqIntentFail.push({ id: req.reqId, why: 'description missing or contains TODO placeholder' });
|
|
588
|
+
} else if (typeof req.domain !== 'string' || req.domain.length === 0) {
|
|
589
|
+
reqIntentFail.push({ id: req.reqId, why: 'domain missing' });
|
|
375
590
|
}
|
|
591
|
+
// reqShape (engineer): shapeClassification.shapes classification.
|
|
376
592
|
if (!req.shapeClassification || !Array.isArray(req.shapeClassification.shapes)) {
|
|
377
593
|
reqShapeFail.push({ id: req.reqId, why: 'shapeClassification.shapes missing' });
|
|
378
|
-
continue;
|
|
379
|
-
}
|
|
380
|
-
if (typeof req.domain !== 'string' || req.domain.length === 0) {
|
|
381
|
-
reqShapeFail.push({ id: req.reqId, why: 'domain missing' });
|
|
382
594
|
}
|
|
383
595
|
}
|
|
384
|
-
checks.push(makeCheck('skeleton:
|
|
596
|
+
checks.push(makeCheck('skeleton:reqIntent', 'delta', reqInScope.length, reqIntentFail));
|
|
597
|
+
checks.push(makeCheck('skeleton:reqShape', 'delta', reqInScope.length, reqShapeFail));
|
|
385
598
|
|
|
386
|
-
// Check 3: TAD.dataStores + coreEntities present when any
|
|
599
|
+
// Check 3: TAD.dataArchitecture.dataStores + coreEntities present when any
|
|
387
600
|
// REQ exists tree-wide.
|
|
388
601
|
const persistenceReq = (tree.requirements ?? []).some((r) => Array.isArray(r?.shapeClassification?.shapes) && r.shapeClassification.shapes.includes('persistence'));
|
|
389
602
|
const tadFail = [];
|
|
390
603
|
if (persistenceReq) {
|
|
391
604
|
const tad = tree.tad ?? {};
|
|
392
|
-
const
|
|
393
|
-
const
|
|
394
|
-
|
|
395
|
-
if (
|
|
605
|
+
const dataArch = tad?.dataArchitecture ?? {};
|
|
606
|
+
const dataStores = Array.isArray(dataArch.dataStores) ? dataArch.dataStores : [];
|
|
607
|
+
const coreEntities = Array.isArray(dataArch.coreEntities) ? dataArch.coreEntities : [];
|
|
608
|
+
if (dataStores.length === 0) tadFail.push({ id: 'TAD.dataArchitecture.dataStores', why: 'TAD.dataArchitecture.dataStores empty while persistence REQ exists' });
|
|
609
|
+
if (coreEntities.length === 0) tadFail.push({ id: 'TAD.dataArchitecture.coreEntities', why: 'TAD.dataArchitecture.coreEntities empty while persistence REQ exists' });
|
|
396
610
|
}
|
|
397
611
|
checks.push(makeCheck('skeleton:tadPersistence', 'tree', persistenceReq ? 2 : 0, tadFail));
|
|
398
612
|
|
|
@@ -421,14 +635,20 @@ export function checkD2Skeleton(ctx) {
|
|
|
421
635
|
const INTERFACE_KINDS_SET = new Set(INTERFACE_KINDS);
|
|
422
636
|
|
|
423
637
|
/**
|
|
424
|
-
* D3 -- Interface contracts and shapes. 0.29.0 runs
|
|
425
|
-
* presence
|
|
638
|
+
* D3 -- Interface contracts and shapes. 0.29.0 runs the cheap
|
|
639
|
+
* presence, closed-vocabulary and draft-marker checks
|
|
640
|
+
* (`shapes:draftSettled` per ADR-4126 scans the `[draft]` prefix).
|
|
641
|
+
* REQ-188 (0.30.0): the draft marker extends to `TAC.purpose` and
|
|
642
|
+
* `TAD.dataArchitecture.coreEntities[].description`; `shapes:draftSettled`
|
|
643
|
+
* enumerates all three homes with ids `<tacId>:<name>`, `<tacId>`
|
|
644
|
+
* and `TAD.entity:<name>` respectively. `tacHasInterface` and
|
|
645
|
+
* `kindVocabulary` apply to drafts unchanged.
|
|
426
646
|
*
|
|
427
647
|
* SEAM 0.30: template markers per kind (recordShape.fields:, httpRoute
|
|
428
648
|
* method/path/request/response/errors, fixture.instances:); entity-name
|
|
429
|
-
* join across TAD.coreEntities and TAC.interfaces[];
|
|
430
|
-
* `authoredAt` path resolution. Those add checks to this
|
|
431
|
-
* fold shape stays the same.
|
|
649
|
+
* join across TAD.dataArchitecture.coreEntities and TAC.interfaces[];
|
|
650
|
+
* ownerRef and `authoredAt` path resolution. Those add checks to this
|
|
651
|
+
* stage; the fold shape stays the same.
|
|
432
652
|
*
|
|
433
653
|
* @param {StageContext} ctx
|
|
434
654
|
*/
|
|
@@ -443,8 +663,17 @@ export function checkD3Shapes(ctx) {
|
|
|
443
663
|
return shapes.includes('httpApi') || shapes.includes('persistence') || shapes.includes('auth');
|
|
444
664
|
});
|
|
445
665
|
|
|
446
|
-
|
|
447
|
-
|
|
666
|
+
// Core entities on TAD.dataArchitecture come from the whole tree,
|
|
667
|
+
// not the scope: an entity's draft marker is a tree-wide draft home
|
|
668
|
+
// (the engineer owns every entity in one place) and reporting it
|
|
669
|
+
// under D3 does not depend on which TAC a change touched.
|
|
670
|
+
const coreEntities = Array.isArray(tree.tad?.dataArchitecture?.coreEntities)
|
|
671
|
+
? /** @type {any[]} */ (tree.tad.dataArchitecture.coreEntities)
|
|
672
|
+
: [];
|
|
673
|
+
const draftEntities = coreEntities.filter(parseCoreEntityDraft);
|
|
674
|
+
|
|
675
|
+
if (tacsInScope.length === 0 && shapedReqInScope.length === 0 && draftEntities.length === 0) {
|
|
676
|
+
return notApplicable('D3', gate, 'no TAC, no shaped REQ (httpApi/persistence/auth) and no [draft] core entity in scope');
|
|
448
677
|
}
|
|
449
678
|
|
|
450
679
|
const checks = [];
|
|
@@ -459,18 +688,39 @@ export function checkD3Shapes(ctx) {
|
|
|
459
688
|
));
|
|
460
689
|
|
|
461
690
|
// Check 2: every interface kind is in the closed vocabulary.
|
|
691
|
+
// Check 3: no description across the three draft homes still
|
|
692
|
+
// carries the `[draft]` marker. A draft shape is entry material
|
|
693
|
+
// the agent pre-populates after L1; the engineer removes the
|
|
694
|
+
// marker when the shape is settled. `tacHasInterface` and
|
|
695
|
+
// `kindVocabulary` apply equally to drafts.
|
|
462
696
|
let totalInterfaces = 0;
|
|
463
697
|
const badKinds = [];
|
|
698
|
+
const draftFindings = [];
|
|
464
699
|
for (const tac of tacsInScope) {
|
|
700
|
+
if (parseTacPurposeDraft(tac)) {
|
|
701
|
+
draftFindings.push({ id: tac.tacId, why: 'draft TAC purpose pre-populated at L1, not yet settled' });
|
|
702
|
+
}
|
|
465
703
|
for (const iface of tac.interfaces ?? []) {
|
|
466
704
|
totalInterfaces += 1;
|
|
467
705
|
const kind = iface?.kind;
|
|
468
706
|
if (typeof kind !== 'string' || !INTERFACE_KINDS_SET.has(kind)) {
|
|
469
707
|
badKinds.push({ id: `${tac.tacId}:${iface?.name ?? '(unnamed)'}`, why: `unknown interface kind ${JSON.stringify(kind)}` });
|
|
470
708
|
}
|
|
709
|
+
if (parseInterfaceDraft(iface)) {
|
|
710
|
+
draftFindings.push({ id: `${tac.tacId}:${iface?.name ?? '(unnamed)'}`, why: 'draft interface pre-populated at L1, not yet settled' });
|
|
711
|
+
}
|
|
471
712
|
}
|
|
472
713
|
}
|
|
714
|
+
for (const entity of draftEntities) {
|
|
715
|
+
draftFindings.push({ id: `TAD.entity:${entity?.name ?? '(unnamed)'}`, why: 'draft core entity pre-populated at L1, not yet settled' });
|
|
716
|
+
}
|
|
717
|
+
// Total for the draft check is every draft home that could carry
|
|
718
|
+
// the marker on an in-scope TAC plus every core entity (tree-wide):
|
|
719
|
+
// the TAC itself (its purpose) + every interface on it, plus the
|
|
720
|
+
// coreEntities list.
|
|
721
|
+
const totalDraftHomes = tacsInScope.length + totalInterfaces + coreEntities.length;
|
|
473
722
|
checks.push(makeCheck('shapes:kindVocabulary', 'delta', totalInterfaces, badKinds));
|
|
723
|
+
checks.push(makeCheck('shapes:draftSettled', 'delta', totalDraftHomes, draftFindings));
|
|
474
724
|
|
|
475
725
|
return foldState('D3', gate, checks, {
|
|
476
726
|
currentTreeHash: ctx.currentTreeHash ?? null,
|
|
@@ -713,6 +963,31 @@ export function checkD7Decisions(ctx) {
|
|
|
713
963
|
}
|
|
714
964
|
|
|
715
965
|
const checks = [];
|
|
966
|
+
// Check 1: `decisions:wellFormed` (productOwner, over tree) --
|
|
967
|
+
// every decision is enumerated (ADR-4126, proposal section 1.3):
|
|
968
|
+
// a non-empty question, at least two options, a default that
|
|
969
|
+
// names one of the option letters.
|
|
970
|
+
const wellFormedFail = [];
|
|
971
|
+
for (const d of decisionEntries) {
|
|
972
|
+
const why = 'decision is not enumerated (question, two or more options, a default)';
|
|
973
|
+
const id = `decision:${d?.id ?? '?'}`;
|
|
974
|
+
if (typeof d?.question !== 'string' || d.question.length === 0) {
|
|
975
|
+
wellFormedFail.push({ id, why });
|
|
976
|
+
continue;
|
|
977
|
+
}
|
|
978
|
+
if (!Array.isArray(d.options) || d.options.length < 2) {
|
|
979
|
+
wellFormedFail.push({ id, why });
|
|
980
|
+
continue;
|
|
981
|
+
}
|
|
982
|
+
const letters = new Set(d.options.map((o) => (o && typeof o === 'object') ? /** @type {any} */ (o).letter : undefined));
|
|
983
|
+
if (typeof d.default !== 'string' || !letters.has(d.default)) {
|
|
984
|
+
wellFormedFail.push({ id, why });
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
checks.push(makeCheck('decisions:wellFormed', 'tree', decisionEntries.length, wellFormedFail));
|
|
988
|
+
|
|
989
|
+
// Check 2: `decisions:allAnswered` (engineer, over tree) -- zero
|
|
990
|
+
// open decisions. The hand-over condition for build.
|
|
716
991
|
checks.push(makeCheck(
|
|
717
992
|
'decisions:allAnswered',
|
|
718
993
|
'tree',
|
package/src/query/index.js
CHANGED
|
@@ -13,15 +13,25 @@ export {
|
|
|
13
13
|
computeDelta,
|
|
14
14
|
} from './delta.js';
|
|
15
15
|
// DEFINE stage gates (REQ-174; proposal §2.4, §3.2 v3). One check
|
|
16
|
-
// function per stage; foldState + parseAcClass helpers.
|
|
16
|
+
// function per stage; foldState + parseAcClass helpers. Persona /
|
|
17
|
+
// question / draft-marker helpers added for ADR-4126 (0.29.0).
|
|
17
18
|
export {
|
|
18
19
|
STAGE_ORDER,
|
|
19
20
|
STAGE_GATES,
|
|
20
21
|
STAGE_SHORT_NAMES,
|
|
21
22
|
STAGE_ALIASES,
|
|
22
23
|
INTERFACE_KINDS,
|
|
24
|
+
CHECK_PERSONA,
|
|
25
|
+
CHECK_QUESTION,
|
|
26
|
+
STAGE_FALLBACK_PERSONA,
|
|
23
27
|
stagePolicy,
|
|
24
28
|
parseAcClass,
|
|
29
|
+
parseInterfaceDraft,
|
|
30
|
+
parseTacPurposeDraft,
|
|
31
|
+
parseCoreEntityDraft,
|
|
32
|
+
hasDraftMarker,
|
|
33
|
+
checkPersona,
|
|
34
|
+
checkQuestion,
|
|
25
35
|
foldState,
|
|
26
36
|
runStage,
|
|
27
37
|
checkD1Brief,
|
|
@@ -33,10 +43,14 @@ export {
|
|
|
33
43
|
checkD7Decisions,
|
|
34
44
|
checkD8Freeze,
|
|
35
45
|
} from './gates.js';
|
|
36
|
-
// Readiness composer (REQ-175; proposal §2.4, §6 v3).
|
|
46
|
+
// Readiness composer (REQ-175; proposal §2.4, §6 v3). Levels /
|
|
47
|
+
// personas / verdict-line helpers added for ADR-4126 (0.29.0).
|
|
37
48
|
export {
|
|
38
49
|
computeReadiness,
|
|
39
50
|
deriveNextAction,
|
|
51
|
+
deriveLevels,
|
|
52
|
+
derivePersonas,
|
|
53
|
+
formatVerdictLines,
|
|
40
54
|
shortHash,
|
|
41
55
|
formatTreeLine,
|
|
42
56
|
} from './readiness.js';
|