@orkestrel/scaffold 0.0.73 → 0.0.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/main.js +75 -34
- package/dist/bin/main.js.map +1 -1
- package/dist/host/agents/skills/orkestrel-prove-journey/SKILL.md +212 -29
- package/dist/host/agents/skills/orkestrel-prove-journey/references/captures.md +40 -15
- package/dist/host/agents/skills/orkestrel-prove-journey/references/decide.md +34 -15
- package/dist/host/agents/skills/orkestrel-prove-journey/references/layer.md +248 -52
- package/dist/host/agents/skills/orkestrel-prove-journey/references/statechart.md +255 -49
- package/dist/host/agents/skills/orkestrel-prove-journey/references/styles.md +111 -23
- package/dist/host/agents/transports/codex.md +7 -0
- package/dist/host/claude/agents/orkestrel.md +2 -2
- package/dist/host/claude/rules/documentation.md +2 -0
- package/dist/host/claude/rules/tests.md +5 -3
- package/dist/host/claude/rules/workspace.md +26 -15
- package/dist/host/codex/config.toml +4 -7
- package/dist/host/guides/README.md +5 -0
- package/dist/host/guides/scaffold.md +104 -14
- package/dist/host/guides/test.md +744 -184
- package/dist/host/manifest.json +19 -19
- package/dist/host/tests/config.test.ts +159 -64
- package/dist/host/tests/policy.test.ts +11 -1
- package/dist/host/tests/setupPolicy.ts +571 -7
- package/dist/src/core/index.cjs +112 -18
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +19 -6
- package/dist/src/core/index.d.ts +19 -6
- package/dist/src/core/index.js +112 -19
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +6 -4
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +9 -7
- package/dist/src/server/index.d.ts +9 -7
- package/dist/src/server/index.js +6 -4
- package/dist/src/server/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,7 +1,29 @@
|
|
|
1
1
|
# The statechart family
|
|
2
2
|
|
|
3
|
-
Declare one transition table, and give it to the
|
|
4
|
-
|
|
3
|
+
Declare one transition table, and give it to the run that asserts it and to the harness a person
|
|
4
|
+
watches. Never write a second table for the harness.
|
|
5
|
+
|
|
6
|
+
## The vocabulary
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import type { StateScenario, StateTransition, StatechartStatus } from '@orkestrel/test'
|
|
10
|
+
import {
|
|
11
|
+
STATECHART_ATTRIBUTES,
|
|
12
|
+
STATECHART_STATUSES,
|
|
13
|
+
buildRefusal,
|
|
14
|
+
executeScenario,
|
|
15
|
+
executeScenarios,
|
|
16
|
+
requireValue,
|
|
17
|
+
} from '@orkestrel/test'
|
|
18
|
+
import type { HarnessInterface, HarnessOptions } from '@orkestrel/test/browser'
|
|
19
|
+
import {
|
|
20
|
+
clickAccessible,
|
|
21
|
+
clickDisclosure,
|
|
22
|
+
createHarness,
|
|
23
|
+
readStates,
|
|
24
|
+
render,
|
|
25
|
+
} from '@orkestrel/test/browser'
|
|
26
|
+
```
|
|
5
27
|
|
|
6
28
|
## Declare the table
|
|
7
29
|
|
|
@@ -12,13 +34,134 @@ harness a person watches. Never write a second table for the harness.
|
|
|
12
34
|
Design laws bars a literal union that names no real domain state.
|
|
13
35
|
- Write one `StateScenario` per transition, carrying that `transition` plus `arrange`, `act`, and
|
|
14
36
|
`assert`. Each phase receives the context and the part of the transition it owns.
|
|
15
|
-
- Put the scenarios in the workspace's browser test setup module
|
|
16
|
-
|
|
17
|
-
entity's unions, and import it from the page and from the setup alike, because a page cannot
|
|
18
|
-
import from `tests/` and a second table is what this reference forbids.
|
|
37
|
+
- Put the scenarios and the table in the workspace's browser test setup module, and import them from
|
|
38
|
+
there.
|
|
19
39
|
- Declare a transition for every event the surface accepts in every state it accepts it, including
|
|
20
40
|
the event that leaves the state unchanged. A table that lists only the moves the happy path takes
|
|
21
41
|
proves the happy path.
|
|
42
|
+
- Write the phases as module functions the whole table shares. Each phase reads its subject from its
|
|
43
|
+
own parameters rather than from the row it belongs to, so one set serves a table of any size.
|
|
44
|
+
|
|
45
|
+
## The worked table
|
|
46
|
+
|
|
47
|
+
Copy the shape of the following table, which is the table this layer's own browser suite runs. Its
|
|
48
|
+
entity is a native disclosure with a second door: the summary toggles it, and a `Dismiss` button
|
|
49
|
+
closes it and does nothing when it is already closed. Give your own table the same row that second
|
|
50
|
+
door produces here — the row whose event leaves the state where it found it.
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import type { StateScenario } from '@orkestrel/test'
|
|
54
|
+
import { executeScenarios, requireValue } from '@orkestrel/test'
|
|
55
|
+
import { clickAccessible, clickDisclosure, readStates, render } from '@orkestrel/test/browser'
|
|
56
|
+
import { expect, it } from 'vitest'
|
|
57
|
+
|
|
58
|
+
type DisclosureState = 'closed' | 'open'
|
|
59
|
+
type DisclosureEvent = 'toggle' | 'dismiss'
|
|
60
|
+
|
|
61
|
+
interface DisclosureContext {
|
|
62
|
+
readonly summary: HTMLElement
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// A journey verb resolves its own target by accessible name, so a second mounted disclosure called
|
|
66
|
+
// "Advanced" is an ambiguity rather than a second fixture. Each build takes the previous one out.
|
|
67
|
+
let mounted: HTMLElement | undefined
|
|
68
|
+
|
|
69
|
+
function buildDisclosure(): DisclosureContext {
|
|
70
|
+
mounted?.remove()
|
|
71
|
+
const container = render(
|
|
72
|
+
'<details><summary>Advanced</summary><p>Every setting.</p></details><button type="button">Dismiss</button>',
|
|
73
|
+
)
|
|
74
|
+
const details = requireValue(container.querySelector('details'))
|
|
75
|
+
requireValue(container.querySelector('button')).addEventListener('click', () => {
|
|
76
|
+
details.open = false
|
|
77
|
+
})
|
|
78
|
+
mounted = container
|
|
79
|
+
return { summary: requireValue(container.querySelector('summary')) }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function readDisclosure(context: DisclosureContext): DisclosureState {
|
|
83
|
+
return readStates(context.summary).includes('expanded') ? 'open' : 'closed'
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function arrangeDisclosure(
|
|
87
|
+
context: DisclosureContext,
|
|
88
|
+
state: DisclosureState,
|
|
89
|
+
): Promise<void> {
|
|
90
|
+
if (readDisclosure(context) !== state) await clickDisclosure('Advanced')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// The context is unused because a journey verb finds what a person reads rather than a node this
|
|
94
|
+
// row was handed.
|
|
95
|
+
async function actOnDisclosure(_context: DisclosureContext, event: DisclosureEvent): Promise<void> {
|
|
96
|
+
if (event === 'toggle') await clickDisclosure('Advanced')
|
|
97
|
+
else await clickAccessible('Dismiss')
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function assertDisclosure(context: DisclosureContext, state: DisclosureState): void {
|
|
101
|
+
expect(readDisclosure(context)).toBe(state)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const SCENARIOS: ReadonlyArray<StateScenario<DisclosureState, DisclosureEvent, DisclosureContext>> =
|
|
105
|
+
[
|
|
106
|
+
{
|
|
107
|
+
transition: {
|
|
108
|
+
name: 'closed opens through the summary',
|
|
109
|
+
from: 'closed',
|
|
110
|
+
event: 'toggle',
|
|
111
|
+
to: 'open',
|
|
112
|
+
},
|
|
113
|
+
arrange: arrangeDisclosure,
|
|
114
|
+
act: actOnDisclosure,
|
|
115
|
+
assert: assertDisclosure,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
transition: {
|
|
119
|
+
name: 'open closes through the summary',
|
|
120
|
+
from: 'open',
|
|
121
|
+
event: 'toggle',
|
|
122
|
+
to: 'closed',
|
|
123
|
+
},
|
|
124
|
+
arrange: arrangeDisclosure,
|
|
125
|
+
act: actOnDisclosure,
|
|
126
|
+
assert: assertDisclosure,
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
transition: {
|
|
130
|
+
name: 'open closes through the button',
|
|
131
|
+
from: 'open',
|
|
132
|
+
event: 'dismiss',
|
|
133
|
+
to: 'closed',
|
|
134
|
+
},
|
|
135
|
+
arrange: arrangeDisclosure,
|
|
136
|
+
act: actOnDisclosure,
|
|
137
|
+
assert: assertDisclosure,
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
// The row whose event leaves the state where it found it.
|
|
141
|
+
transition: {
|
|
142
|
+
name: 'closed stays closed through the button',
|
|
143
|
+
from: 'closed',
|
|
144
|
+
event: 'dismiss',
|
|
145
|
+
to: 'closed',
|
|
146
|
+
},
|
|
147
|
+
arrange: arrangeDisclosure,
|
|
148
|
+
act: actOnDisclosure,
|
|
149
|
+
assert: assertDisclosure,
|
|
150
|
+
},
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
it('walks the disclosure statechart', async () => {
|
|
154
|
+
await executeScenarios(SCENARIOS, buildDisclosure)
|
|
155
|
+
})
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
- Name each row for the door it drove. A table that names only the states reads as if one mechanism
|
|
159
|
+
moved the entity, and the row where the button leaves the disclosure exactly as it found it is the
|
|
160
|
+
one a name has to separate from the toggle rows beside it.
|
|
161
|
+
- Remove the previous fixture inside the builder. Without that removal the next row meets the
|
|
162
|
+
previous disclosure beside its own under one name, and the verb refuses them as ambiguous.
|
|
163
|
+
- Drive a native `<summary>` with `clickDisclosure` and an ARIA disclosure with `clickAccessible`
|
|
164
|
+
settled by `waitForState` ([layer.md](layer.md) → Disclosures).
|
|
22
165
|
|
|
23
166
|
## Run the table
|
|
24
167
|
|
|
@@ -28,60 +171,123 @@ harness a person watches. Never write a second table for the harness.
|
|
|
28
171
|
- Build the context in `build`, which receives the row it is building for. That is what lets one
|
|
29
172
|
table mix fixtures.
|
|
30
173
|
- Let the runner name the failure. It prepends the transition's `name` to whatever the row threw and
|
|
31
|
-
carries the original as the `cause`, so a bare assertion message still says which row failed.
|
|
174
|
+
carries the original as the `cause`, so a bare assertion message still says which row failed. A
|
|
175
|
+
phase that throws something other than an `Error` is named by its type, and a builder that refuses
|
|
176
|
+
raises `<name>: build refused` with its own refusal as the `cause`.
|
|
177
|
+
`buildRefusal(name, cause)` from `@orkestrel/test` builds that same error, so an assertion on
|
|
178
|
+
a refused build compares against what it returns rather than against a spelled string.
|
|
32
179
|
- Never assert the entity's internal state in `assert` where the transition is one a person drives.
|
|
33
180
|
Assert what the interface renders, through `readPerception`, `readValue`, or `readStates`.
|
|
34
181
|
|
|
35
182
|
## Drive the act the way the transition happens
|
|
36
183
|
|
|
37
184
|
- Drive `act` through the journey verbs — `clickAccessible`, `clickDisclosure`, `typeAccessible`,
|
|
38
|
-
`traverseAccessible` —
|
|
39
|
-
is a key on an already-focused control, for every transition a person can cause.
|
|
185
|
+
`traverseAccessible`, `pressKeys` — for every transition a person can cause.
|
|
40
186
|
- Drive `act` through the entity's own API only where the transition is the entity's rather than the
|
|
41
187
|
person's: a lifecycle event, a transport reply, a timer the surface owns.
|
|
42
188
|
- Say which door each row used, in the row's `name`. A table that mixes the doors silently reads as
|
|
43
189
|
a set of user transitions and proves something else.
|
|
44
190
|
|
|
45
|
-
##
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
191
|
+
## Mount the harness
|
|
192
|
+
|
|
193
|
+
`createHarness(options)` renders the table in the browser and drives it row by row. It takes the
|
|
194
|
+
table as `scenarios`, the fixture builder as `build`, the reader that reports the entity's state as
|
|
195
|
+
`state`, and an optional `pause` between rows for a table worth watching.
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
import { STATECHART_ATTRIBUTES } from '@orkestrel/test'
|
|
199
|
+
import { createHarness } from '@orkestrel/test/browser'
|
|
200
|
+
|
|
201
|
+
const harness = createHarness({
|
|
202
|
+
scenarios: SCENARIOS,
|
|
203
|
+
build: buildDisclosure,
|
|
204
|
+
state: readDisclosure,
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
harness.status // 'idle' — mounted, nothing run yet
|
|
208
|
+
harness.total // 4
|
|
209
|
+
|
|
210
|
+
await harness.execute()
|
|
211
|
+
|
|
212
|
+
harness.status // 'passed'
|
|
213
|
+
harness.passed // 4
|
|
214
|
+
harness.failed // 0
|
|
215
|
+
harness.failures // []
|
|
216
|
+
|
|
217
|
+
// The object reads its own markup, so a gate polling the page and a test asserting on the object
|
|
218
|
+
// cannot disagree.
|
|
219
|
+
harness.root.getAttribute(STATECHART_ATTRIBUTES.status) // 'passed'
|
|
220
|
+
harness.root.getAttribute(STATECHART_ATTRIBUTES.total) // '4'
|
|
221
|
+
|
|
222
|
+
harness.destroy()
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
- Hand it the same table the run asserts. A second table for the harness is what this reference
|
|
226
|
+
forbids.
|
|
227
|
+
- Let it render its own markup. It writes `status`, `passed`, `failed`, and `total` on its root,
|
|
228
|
+
`scenario` and `result` on each row, and `state` on the element rendering the entity's current
|
|
229
|
+
state, every one of them from `STATECHART_ATTRIBUTES` rather than from a literal. A workspace that
|
|
230
|
+
spells a `data-statechart-*` string of its own has left the contract.
|
|
231
|
+
- Read the announcer beside the attributes. A `role="status"` element narrates each step in a
|
|
232
|
+
sentence, so a screen reader and a vision model both read the run without visual chrome.
|
|
233
|
+
- Take `execute` as reporting on the whole table. It carries on past a failing row, where
|
|
234
|
+
`executeScenarios` stops at the first, and a builder that refuses fails its own row under the
|
|
235
|
+
sentence `buildRefusal` builds rather than ending the run.
|
|
236
|
+
- Call `execute` again to re-run the same table from a fresh tally and a cleared rendered state.
|
|
237
|
+
- Call `destroy` in the test's own cleanup. It removes the mounted root and does nothing when the
|
|
238
|
+
root is already gone.
|
|
239
|
+
- Pace a table a person watches with `pause`, and budget the gate from the row count and that pause
|
|
240
|
+
rather than from a fixed timeout.
|
|
241
|
+
|
|
242
|
+
## The observable statuses
|
|
243
|
+
|
|
244
|
+
`STATECHART_STATUSES` publishes the run states in the order a run passes through them, and
|
|
245
|
+
`StatechartStatus` is the same set as a named union.
|
|
246
|
+
|
|
247
|
+
| Status | The reading it names |
|
|
248
|
+
| --------- | ---------------------------------------------------------------------------------------- |
|
|
249
|
+
| `pending` | Construction, until every declared row has rendered and `total` carries the row count |
|
|
250
|
+
| `idle` | Mounted and standing ready, with `passed` and `failed` at zero and nothing in flight |
|
|
251
|
+
| `running` | A run in flight |
|
|
252
|
+
| `passed` | Terminal: the run finished and no row's result reads failed |
|
|
253
|
+
| `failed` | Terminal: the run finished with a failing row, or it ended on the `state` reader's throw |
|
|
254
|
+
|
|
255
|
+
- Read a `pending` status as a harness whose rows never mounted. A gate that finds it has found a
|
|
256
|
+
defect rather than a run to wait for.
|
|
257
|
+
- Read `total` as what the table declares and `passed` plus `failed` as what the last run finished.
|
|
258
|
+
- Take the terminal pair as the pair a gate waits for. Every exit writes one of them: a run the
|
|
259
|
+
`state` reader ends writes `failed` and then rejects with that reader's value by identity, and the
|
|
260
|
+
row that reader was called for is not counted as failed.
|
|
261
|
+
- Read the state element as carrying no reading before the first row produces a context. A state is
|
|
262
|
+
read from an entity, and no entity exists until a row builds one.
|
|
72
263
|
|
|
73
264
|
## Gate the harness
|
|
74
265
|
|
|
75
|
-
Prove the harness from the browser project, through the
|
|
76
|
-
|
|
77
|
-
- Mount the harness
|
|
78
|
-
|
|
79
|
-
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
-
|
|
87
|
-
|
|
266
|
+
Prove the harness from the browser project, through the object and the markup together:
|
|
267
|
+
|
|
268
|
+
- Mount the harness, `execute` it, and assert the status reads `passed`, the failed tally reads
|
|
269
|
+
zero, and the passed tally equals the total.
|
|
270
|
+
- Assert the inventory before the tally. A harness that mounted no row would pass every tally
|
|
271
|
+
assertion, and `createHarness` refuses an empty table with `Statechart harness mounted no
|
|
272
|
+
transition`.
|
|
273
|
+
- Name the failing rows from `failures`, which lists each row whose rendered `result` reads failed,
|
|
274
|
+
in table order. A red gate says which transition broke.
|
|
275
|
+
- Read the tally off `harness.root` as well as off the object, so the attribute contract a gate
|
|
276
|
+
outside the page depends on is asserted rather than assumed.
|
|
277
|
+
- Poll the root's `status` attribute until it reads a terminal value where the gate runs outside
|
|
278
|
+
this layer's environment, under a budget derived from the row count and the declared pause. Never
|
|
279
|
+
assert it from one read after the start.
|
|
280
|
+
|
|
281
|
+
## A harness page is product
|
|
282
|
+
|
|
283
|
+
A deep-linked page hosting a harness is optional product the workspace ships on its own account.
|
|
284
|
+
This layer's browser entry imports `vitest/browser` at module scope, so an application page cannot
|
|
285
|
+
import it.
|
|
286
|
+
|
|
287
|
+
- Name only the attribute contract such a page must honour: the names in `STATECHART_ATTRIBUTES`,
|
|
288
|
+
the readings in `STATECHART_STATUSES`, and the tally on one root node.
|
|
289
|
+
- Report the page itself as the repository owner's decision — which transitions a surface owes,
|
|
290
|
+
where the page is linked, and whether it ships at all.
|
|
291
|
+
- Route a person who must watch the widget move to the harness run's own frames and its written
|
|
292
|
+
artifact ([decide.md](decide.md) → The harness run), and name a deep link only where the workspace
|
|
293
|
+
already ships such a page.
|
|
@@ -2,37 +2,93 @@
|
|
|
2
2
|
|
|
3
3
|
Prove a style from what the browser resolved on the mounted surface. The `enterprise-bootstrap`
|
|
4
4
|
skill's [instruments reference](../../enterprise-bootstrap/references/inspection.md) names each
|
|
5
|
-
instrument's property, its population, and
|
|
6
|
-
|
|
5
|
+
instrument's property, its population, and its coverage. Take those from there, the reading from
|
|
6
|
+
here, and the control from the builder this layer publishes for it.
|
|
7
|
+
|
|
8
|
+
## The vocabulary
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import type {
|
|
12
|
+
CaptureVariant,
|
|
13
|
+
CensusFixture,
|
|
14
|
+
CensusReading,
|
|
15
|
+
Color,
|
|
16
|
+
ContrastFixture,
|
|
17
|
+
EscapeFixture,
|
|
18
|
+
} from '@orkestrel/test/browser'
|
|
19
|
+
import {
|
|
20
|
+
CANVAS_COLOR,
|
|
21
|
+
blendColor,
|
|
22
|
+
buildCensus,
|
|
23
|
+
buildContrast,
|
|
24
|
+
buildEscapes,
|
|
25
|
+
extractOrphans,
|
|
26
|
+
extractStyles,
|
|
27
|
+
findKeyframes,
|
|
28
|
+
findRule,
|
|
29
|
+
matchesColor,
|
|
30
|
+
measureContrast,
|
|
31
|
+
measureLuminance,
|
|
32
|
+
parseCSSColor,
|
|
33
|
+
pressKeys,
|
|
34
|
+
readBackdrop,
|
|
35
|
+
readCascade,
|
|
36
|
+
readCensus,
|
|
37
|
+
readClasses,
|
|
38
|
+
readContrast,
|
|
39
|
+
readLayers,
|
|
40
|
+
readPixels,
|
|
41
|
+
readRing,
|
|
42
|
+
readRootToken,
|
|
43
|
+
readRows,
|
|
44
|
+
readStyle,
|
|
45
|
+
readToken,
|
|
46
|
+
traverseAccessible,
|
|
47
|
+
waitForAnimations,
|
|
48
|
+
} from '@orkestrel/test/browser'
|
|
49
|
+
import { inject } from 'vitest'
|
|
50
|
+
```
|
|
7
51
|
|
|
8
52
|
## Assert the resolved value
|
|
9
53
|
|
|
10
54
|
- Read one property with `readStyle(element, property)` and a length with `readPixels(element, property)`. A
|
|
11
55
|
class present in the markup and absent from the cascade resolves to nothing, and an assertion on
|
|
12
56
|
the class list passes on it.
|
|
57
|
+
- Read `readPixels` as a measured contribution rather than a parsed length. A resolved value
|
|
58
|
+
carrying no leading number reads as `0`, so read the text with `readStyle` where an unparsable
|
|
59
|
+
value and a genuine zero are different findings.
|
|
13
60
|
- Never substitute `findRule` for a resolved read. It proves a declaration exists, and another rule
|
|
14
|
-
may still win; reach for it where the stylesheet itself is the subject
|
|
61
|
+
may still win; reach for it where the stylesheet itself is the subject, and for `findKeyframes`
|
|
62
|
+
where the animation's declaration is.
|
|
15
63
|
- Compare a color through `matchesColor` or `parseCSSColor` rather than by string. A browser normalizes a color
|
|
16
64
|
expression, so a literal comparison fails on a value that resolved correctly.
|
|
65
|
+
- Take every reading after the paint settles. Await `waitForAnimations` on the element whose
|
|
66
|
+
transition was running ([layer.md](layer.md) → The waits); a reading taken mid-transition reports
|
|
67
|
+
an interpolated frame no state of the interface paints.
|
|
17
68
|
|
|
18
69
|
## Run per variant
|
|
19
70
|
|
|
20
71
|
The run axis is fixed in [SKILL.md](../SKILL.md) → Read the variant once. The matrix family's own
|
|
21
72
|
readings follow.
|
|
22
73
|
|
|
23
|
-
-
|
|
24
|
-
|
|
74
|
+
- Read `inject('variants')` for the declared list, and walk every entry inside one run. This family
|
|
75
|
+
reads the whole matrix, where the capture family renders one variant per run.
|
|
76
|
+
- Compose each variant's `apply` in the test, and apply it with the variant's `width` and `height`
|
|
77
|
+
before the readings. Take every reading for that variant before moving to the next.
|
|
25
78
|
- Name the attribute the surface actually reads in `apply`; a Bootstrap surface switches on
|
|
26
79
|
`data-bs-theme`. An `apply` that sets another attribute leaves the run in the default theme, where
|
|
27
80
|
every reading passes.
|
|
81
|
+
- Reach for the application's own theme control where the surface ships one, and assert the state it
|
|
82
|
+
announces. Setting the attribute directly proves the stylesheet; driving the control proves the
|
|
83
|
+
surface.
|
|
28
84
|
- Assert that the run read every declared variant. A matrix that silently walked one variant reports
|
|
29
85
|
a pass for the theme nobody exercised.
|
|
30
86
|
- Report which variants a result covers beside it. A pairing that appears only in a state the run
|
|
31
87
|
never entered is unmeasured.
|
|
32
88
|
|
|
33
|
-
Vitest `provide` carries serializable values only
|
|
34
|
-
|
|
35
|
-
|
|
89
|
+
Vitest `provide` carries serializable values only, so the provided variant carries `name`, `width`,
|
|
90
|
+
and `height`; `apply` does not cross that channel. Compose it in the test from the name the project
|
|
91
|
+
provided.
|
|
36
92
|
|
|
37
93
|
## Contrast and focus chrome
|
|
38
94
|
|
|
@@ -43,39 +99,62 @@ cross that channel. Run `apply` inside the test from the variant the project pro
|
|
|
43
99
|
are all translucent; take that refusal as the reading, because an assumed white canvas turns "this
|
|
44
100
|
surface declares no background" into a number that reads like a measurement.
|
|
45
101
|
- Read focus chrome with `readRing(control)`, after focus arrived through `traverseAccessible`,
|
|
46
|
-
`
|
|
102
|
+
`pressKeys`, or a real click. Pass `worn` where the chrome is painted onto a second element such
|
|
47
103
|
as a label. It reports `undefined` for a control not matching `:focus-visible`, for the browser's
|
|
48
104
|
own automatic ring, and for a focus style that only repaints the fill — treat each as a finding
|
|
49
105
|
about the surface rather than as a pass.
|
|
50
|
-
- Carry the negative controls the composited-contrast instrument names, in the same run and composed
|
|
51
|
-
in the harness rather than taken from the surface. An instrument whose negative control passes is
|
|
52
|
-
broken, and its readings are not evidence.
|
|
53
106
|
- Reach for `measureContrast`, `measureLuminance`, `blendColor`, `readLayers`, and `readBackdrop`
|
|
54
107
|
only where the composite itself is the subject. Never re-derive `readContrast` from them.
|
|
55
108
|
|
|
109
|
+
## The published controls
|
|
110
|
+
|
|
111
|
+
Take each reading's control from the builder this layer publishes for it.
|
|
112
|
+
`.claude/rules/quality.md` § Instruments owns the law that control satisfies.
|
|
113
|
+
|
|
114
|
+
| Reading | Control | What it carries |
|
|
115
|
+
| --------------- | ------------------------- | -------------------------------------------------------------------------------------- |
|
|
116
|
+
| `readContrast` | `buildContrast(bar)` | A translucent tint over an opaque floor, with a `refused` and an `accepted` foreground |
|
|
117
|
+
| `extractStyles` | `buildEscapes(permitted)` | An inline declaration, an embedded `<style>` element, and the sheet the id exempts |
|
|
118
|
+
| `readCensus` | `buildCensus()` | An HTML token and an SVG token, neither declared by any loaded stylesheet |
|
|
119
|
+
|
|
120
|
+
- Append each control's `root` to the same surface root the reading walks, take the reading, and
|
|
121
|
+
remove it afterwards. Every builder returns detached nodes and mounts nothing, so where the
|
|
122
|
+
control is read is the caller's decision.
|
|
123
|
+
- Assert on the fields the builder returns rather than on a token or a selector written down in the
|
|
124
|
+
test. `buildCensus` hands back its own tokens, and `buildContrast` hands back `refused` and
|
|
125
|
+
`accepted` by name.
|
|
126
|
+
- Require the `refused` foreground to read under the bar and the `accepted` one to reach it, in the
|
|
127
|
+
same run as the production readings. `buildContrast` refuses a bar its own stack cannot straddle,
|
|
128
|
+
and that refusal is the reading: no pair it can compose settles that bar.
|
|
129
|
+
- Pass the exempt id the policy owns to `buildEscapes`, so the reader must leave the permitted sheet
|
|
130
|
+
alone rather than passing by rejecting every `<style>` element.
|
|
131
|
+
|
|
56
132
|
## The authored-class census
|
|
57
133
|
|
|
58
|
-
Take the property, the population, and the
|
|
59
|
-
|
|
134
|
+
Take the property, the population, and the coverage from the instruments reference → Authored class
|
|
135
|
+
in the shipped cascade. This is the reading.
|
|
60
136
|
|
|
61
|
-
- Read the census
|
|
62
|
-
|
|
63
|
-
declares.
|
|
137
|
+
- Read the census with `readCensus(root)`, which walks the mounted surface, reports `elements` as
|
|
138
|
+
the population it read, lists every `tokens` value the markup carries, and lists as `undeclared`
|
|
139
|
+
the tokens no loaded stylesheet declares. It refuses a walk that read no element.
|
|
140
|
+
- Assert on `elements` as well as on `undeclared`. An empty walk reports no undeclared token, and so
|
|
141
|
+
does markup whose every class the cascade declares.
|
|
64
142
|
- Take `root` from the mounted surface, so the census covers what rendered rather than what a
|
|
65
143
|
template file spells.
|
|
66
|
-
-
|
|
67
|
-
|
|
68
|
-
|
|
144
|
+
- Read `readClasses` and `readCascade` directly only where one side of the difference is the
|
|
145
|
+
subject. `readCensus` is the reading, and re-deriving it drops the population it reports.
|
|
146
|
+
- Append `buildCensus().root` to that same `root`, so the control reaches the difference through the
|
|
147
|
+
same walk rather than beside it.
|
|
69
148
|
|
|
70
149
|
## Style escapes
|
|
71
150
|
|
|
72
|
-
Take the property, the population, the named exemptions, and the
|
|
73
|
-
|
|
151
|
+
Take the property, the population, the named exemptions, and the coverage from the instruments
|
|
152
|
+
reference → Style escapes. This is the reading.
|
|
74
153
|
|
|
75
154
|
- Read escapes with `extractStyles(root)`, which returns the markup of every hit it found.
|
|
76
155
|
- Take the reading before any journey drives the surface, because the population is the undriven
|
|
77
156
|
tree.
|
|
78
|
-
- Append
|
|
157
|
+
- Append `buildEscapes(permitted).root` to that same `root`, so the control reaches the reading
|
|
79
158
|
through `extractStyles`.
|
|
80
159
|
- Reach for `extractOrphans` where the finding is a child element rendered outside its required
|
|
81
160
|
parent, and `readRows` where the subject is a repeated row's rendered text.
|
|
@@ -89,3 +168,12 @@ instruments reference → Style escapes. This is the reading.
|
|
|
89
168
|
assertion on presence passes on a token nobody declared.
|
|
90
169
|
- Read each token once per variant and assert the values differ where the design says the variants
|
|
91
170
|
differ. A pair of variants that resolves a token identically is a theme that did not switch.
|
|
171
|
+
|
|
172
|
+
## The engine bound
|
|
173
|
+
|
|
174
|
+
Every reading here comes from the one engine the gate renders.
|
|
175
|
+
|
|
176
|
+
- State that bound with the result. A claim about another engine's resolved value is unproven until
|
|
177
|
+
a reading taken on that engine records it.
|
|
178
|
+
- Read the limit and the condition that reopens it from the emitted `configs/browsers.ts` doc block,
|
|
179
|
+
which the generated workspace ships, rather than from a copy in the suite.
|
|
@@ -128,6 +128,13 @@ The Orchestrator verifies the finished exec with direct evidence — git status,
|
|
|
128
128
|
scoped validation — and carries touched files, diffstat, and deviation state into
|
|
129
129
|
integration and review.
|
|
130
130
|
|
|
131
|
+
On a Windows host a shell write that decodes and re-encodes text can replace a code point the active
|
|
132
|
+
code page cannot represent, so when a bench unit must edit a line carrying a code point above
|
|
133
|
+
`0x7F`, the brief tells it to make that edit through the exec's own patch tool, never through
|
|
134
|
+
`Get-Content`, `Set-Content`, `Out-File`, or a `>` redirection, and to report every such line it
|
|
135
|
+
touched. The Orchestrator's review sweep compares the set of code points above `0x7F` on each
|
|
136
|
+
touched line before and after the edit, and flags a line that lost any of them.
|
|
137
|
+
|
|
131
138
|
## Routing exclusion — defensive negative-test units
|
|
132
139
|
|
|
133
140
|
The provider applies a content-safety filter that terminates a turn mid-run when the work
|
|
@@ -78,7 +78,7 @@ so network-controlled descriptions never enter agent instruction context.
|
|
|
78
78
|
| `@orkestrel/reason` | `0.0.11` | L2 | `@orkestrel/contract` `^0.0.17`, `@orkestrel/emitter` `^0.0.10` | |
|
|
79
79
|
| `@orkestrel/relation` | `0.0.13` | L3 | `@orkestrel/emitter` `^0.0.10`, `@orkestrel/contract` `^0.0.17`, `@orkestrel/database` `^0.0.15` | |
|
|
80
80
|
| `@orkestrel/router` | `0.0.15` | L2 | `@orkestrel/abort` `^0.0.11`, `@orkestrel/emitter` `^0.0.10`, `@orkestrel/contract` `^0.0.17` | |
|
|
81
|
-
| `@orkestrel/scaffold` | `0.0.
|
|
81
|
+
| `@orkestrel/scaffold` | `0.0.73` | L3 | `@orkestrel/console` `^0.0.14`, `@orkestrel/contract` `^0.0.17`, `@orkestrel/emitter` `^0.0.10`, `@orkestrel/markdown` `^0.0.15`, `@orkestrel/process` `^0.0.13`, `@orkestrel/template` `^0.0.8` | |
|
|
82
82
|
| `@orkestrel/sea` | `0.0.17` | L3 | `@orkestrel/contract` `^0.0.17`, `@orkestrel/emitter` `^0.0.10`, `@orkestrel/process` `^0.0.13` | |
|
|
83
83
|
| `@orkestrel/server` | `0.0.20` | L3 | `@orkestrel/abort` `^0.0.11`, `@orkestrel/codec` `^0.0.4`, `@orkestrel/router` `^0.0.15`, `@orkestrel/emitter` `^0.0.10`, `@orkestrel/timeout` `^0.0.11`, `@orkestrel/contract` `^0.0.17` | |
|
|
84
84
|
| `@orkestrel/sqlite` | `0.0.12` | L1 | `@orkestrel/contract` `^0.0.17` | |
|
|
@@ -87,7 +87,7 @@ so network-controlled descriptions never enter agent instruction context.
|
|
|
87
87
|
| `@orkestrel/table` | `0.0.6` | L2 | `@orkestrel/emitter` `^0.0.10`, `@orkestrel/contract` `^0.0.17` | |
|
|
88
88
|
| `@orkestrel/template` | `0.0.8` | L2 | `@orkestrel/emitter` `^0.0.10`, `@orkestrel/contract` `^0.0.17` | |
|
|
89
89
|
| `@orkestrel/terminal` | `0.0.16` | L3 | `@orkestrel/sse` `^0.0.8`, `@orkestrel/form` `^0.0.7`, `@orkestrel/console` `^0.0.14`, `@orkestrel/emitter` `^0.0.10`, `@orkestrel/contract` `^0.0.17`, `@orkestrel/database` `^0.0.15` | |
|
|
90
|
-
| `@orkestrel/test` | `0.0.
|
|
90
|
+
| `@orkestrel/test` | `0.0.17` | L1 | `@orkestrel/contract` `^0.0.17` | `vitest` `^4.1.11` |
|
|
91
91
|
| `@orkestrel/timeout` | `0.0.11` | L1 | `@orkestrel/contract` `^0.0.17` | |
|
|
92
92
|
| `@orkestrel/tool` | `0.0.16` | L2 | `@orkestrel/emitter` `^0.0.10`, `@orkestrel/contract` `^0.0.17` | |
|
|
93
93
|
| `@orkestrel/toolbox` | `0.0.15` | L6 | `@orkestrel/form` `^0.0.7`, `@orkestrel/tool` `^0.0.16`, `@orkestrel/agent` `^0.0.24`, `@orkestrel/server` `^0.0.20`, `@orkestrel/contract` `^0.0.17`, `@orkestrel/database` `^0.0.15`, `@orkestrel/relation` `^0.0.13`, `@orkestrel/terminal` `^0.0.16`, `@orkestrel/workflow` `^0.0.19`, `@orkestrel/workspace` `^0.0.9` | |
|
|
@@ -91,6 +91,8 @@ Never use in-repository `@src/*` aliases in public guide examples; reserve them
|
|
|
91
91
|
- Do not put model routing or package version catalogs in a skill.
|
|
92
92
|
- Validate every referenced resource, leave no template TODOs, and limit each skill directory to `SKILL.md`, `agents/openai.yaml`, and the `references/*.md` files its `SKILL.md` names; add no other file or directory.
|
|
93
93
|
- Verify each API a skill instructs an executor to call against the installed package's public entry before landing the instruction, and name the entry you read. A skill that names a symbol its package does not export teaches an executor to write a dangling import.
|
|
94
|
+
- Put every symbol a skill teaches in a named import inside a Markdown fence in `SKILL.md` or a named reference. The policy sweep reads fenced value and type imports from `@orkestrel/*` declaration entries; it does not read identifiers in prose or table cells, or validate call signatures and runtime behavior.
|
|
95
|
+
- Import only packages in `BASE_DEV_DEPENDENCIES` in those fences. The sweep refuses a package outside that set because a generated workspace need not install it.
|
|
94
96
|
- Write `agents/openai.yaml` as one root `interface:` mapping over exactly `display_name`, `short_description`, and `default_prompt`, in that order, each on its own two-space-indented line.
|
|
95
97
|
- Research the external schema only when a consumer needs a key outside `display_name`, `short_description`, and `default_prompt`, and add no key before then.
|
|
96
98
|
- Give every one of those keys a non-empty single-quoted scalar, and write an apostrophe inside it as `''`.
|
|
@@ -59,9 +59,11 @@ its own:
|
|
|
59
59
|
| `tests/setup*.test.ts` | Reusable behavior exported from sibling `tests/setup*.ts` modules works as the workspace's suites require |
|
|
60
60
|
| `tests/service/**/*.test.ts` | The live external services this package drives, driven for real |
|
|
61
61
|
|
|
62
|
-
- Put
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
- Put `tests/setupBrowser.test.ts` in the browser-enabled `setup:browser` project. Put every
|
|
63
|
+
other root `tests/setup*.test.ts` proof in the Node `setup` project, and exclude the browser
|
|
64
|
+
proof from that project. Keep each proof's assertions on exported test-infrastructure behavior:
|
|
65
|
+
do not duplicate production behavior there, and do not move setup-helper assertions into another
|
|
66
|
+
cross-cutting proof.
|
|
65
67
|
- `.claude/rules/workspace.md` names the Vitest project each location belongs to.
|
|
66
68
|
- The `guides` project runs in Node with the browser disabled. Its subject is what the guide
|
|
67
69
|
claims: that every documented name resolves, and that every fence asserting a value returns
|