@orkestrel/scaffold 0.0.103 → 0.0.105
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/agents/skills/orkestrel-dispatch/scripts/helpers.js +84 -1
- package/dist/agents/skills/orkestrel-dispatch/scripts/launch.js +21 -6
- package/dist/agents/skills/orkestrel-harden/scripts/titles.js +338 -0
- package/dist/agents/skills/orkestrel-publish/scripts/swap.js +3 -39
- package/dist/agents/skills/orkestrel-publish/scripts/wave.js +433 -47
- package/dist/agents/skills/orkestrel-publish/scripts/window.js +2 -1
- package/dist/bin/main.js +17 -10
- package/dist/bin/main.js.map +1 -1
- package/dist/host/agents/orchestration.md +6 -6
- package/dist/host/agents/skills/enterprise-bootstrap/references/components.md +2 -2
- package/dist/host/agents/skills/enterprise-bootstrap/references/inspection.md +1 -1
- package/dist/host/agents/skills/orkestrel-build/SKILL.md +2 -2
- package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +2 -2
- package/dist/host/agents/skills/orkestrel-debrief/references/field-testing.md +1 -1
- package/dist/host/agents/skills/orkestrel-debrief/references/instruction-audit.md +3 -3
- package/dist/host/agents/skills/orkestrel-dispatch/SKILL.md +10 -10
- package/dist/host/agents/skills/orkestrel-dispatch/references/launch.md +1 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/helpers.ts +100 -1
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/launch.ts +27 -6
- package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +4 -4
- package/dist/host/agents/skills/orkestrel-falsify/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/orkestrel-falsify/references/brief.md +8 -7
- package/dist/host/agents/skills/orkestrel-falsify/references/reconcile.md +6 -6
- package/dist/host/agents/skills/orkestrel-harden/SKILL.md +6 -1
- package/dist/host/agents/skills/orkestrel-harden/references/bindings.md +1700 -0
- package/dist/host/agents/skills/orkestrel-harden/references/contract.md +2 -2
- package/dist/host/agents/skills/orkestrel-harden/references/equivalence.md +48 -0
- package/dist/host/agents/skills/orkestrel-harden/references/hardening.md +4 -2
- package/dist/host/agents/skills/orkestrel-harden/scripts/titles.ts +425 -0
- package/dist/host/agents/skills/orkestrel-journey/SKILL.md +2 -2
- package/dist/host/agents/skills/orkestrel-journey/references/captures.md +9 -8
- package/dist/host/agents/skills/orkestrel-parallel/SKILL.md +72 -0
- package/dist/host/agents/skills/orkestrel-parallel/agents/openai.yaml +4 -0
- package/dist/host/agents/skills/orkestrel-parallel/references/harnesses.md +12 -0
- package/dist/host/agents/skills/orkestrel-polish/SKILL.md +3 -3
- package/dist/host/agents/skills/orkestrel-publish/SKILL.md +2 -2
- package/dist/host/agents/skills/orkestrel-publish/references/release.md +1 -0
- package/dist/host/agents/skills/orkestrel-publish/references/wave.md +56 -13
- package/dist/host/agents/skills/orkestrel-publish/references/window.md +6 -6
- package/dist/host/agents/skills/orkestrel-publish/scripts/swap.ts +5 -49
- package/dist/host/agents/skills/orkestrel-publish/scripts/wave.ts +646 -49
- package/dist/host/agents/skills/orkestrel-publish/scripts/window.ts +2 -1
- package/dist/host/agents/transports/codex.md +1 -1
- package/dist/host/claude/agents/orkestrel.md +54 -53
- package/dist/host/claude/agents/reviewer.md +4 -4
- package/dist/host/claude/agents/verifier.md +1 -1
- package/dist/host/claude/rules/documentation.md +2 -1
- package/dist/host/claude/rules/patterns.md +1 -1
- package/dist/host/claude/rules/quality.md +2 -2
- package/dist/host/claude/rules/styles.md +1 -1
- package/dist/host/claude/rules/tests.md +34 -8
- package/dist/host/claude/rules/typescript.md +29 -0
- package/dist/host/claude/rules/workspace.md +32 -22
- package/dist/host/claude/rules/writing.md +34 -26
- package/dist/host/claude/settings.json +2 -0
- package/dist/host/claude/skills/orkestrel-debrief/SKILL.md +1 -1
- package/dist/host/claude/skills/orkestrel-falsify/SKILL.md +1 -1
- package/dist/host/claude/skills/orkestrel-parallel/SKILL.md +11 -0
- package/dist/host/codex/agents/analyst.toml +3 -3
- package/dist/host/codex/agents/reviewer.toml +4 -4
- package/dist/host/codex/agents/verifier.toml +3 -1
- package/dist/host/configs/policy.ts +13 -2
- package/dist/host/dotfiles/editorconfig +0 -1
- package/dist/host/dotfiles/gitattributes +0 -1
- package/dist/host/dotfiles/gitignore +0 -1
- package/dist/host/dotfiles/oxlintrc.json +24 -0
- package/dist/host/guides/agent.md +1674 -598
- package/dist/host/guides/budget.md +89 -29
- package/dist/host/guides/form.md +485 -99
- package/dist/host/guides/mcp.md +14 -2
- package/dist/host/guides/ollama.md +367 -86
- package/dist/host/guides/probe.md +1 -1
- package/dist/host/guides/scaffold.md +158 -101
- package/dist/host/guides/study.md +3185 -0
- package/dist/host/guides/table.md +239 -29
- package/dist/host/guides/terminal.md +22 -20
- package/dist/host/guides/test.md +776 -315
- package/dist/host/manifest.json +117 -65
- package/dist/host/tests/config.test.ts +136 -82
- package/dist/host/tests/policy.test.ts +41 -3
- package/dist/host/tests/setupPolicy.ts +87 -29
- package/dist/src/core/index.cjs +1389 -612
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +35 -20
- package/dist/src/core/index.d.ts +35 -20
- package/dist/src/core/index.js +1387 -613
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +12 -12
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +12 -12
- package/dist/src/server/index.d.ts +12 -12
- package/dist/src/server/index.js +12 -12
- package/dist/src/server/index.js.map +1 -1
- package/package.json +5 -5
|
@@ -21,7 +21,7 @@ Inventory local code involving:
|
|
|
21
21
|
- JSON parsing and narrowing;
|
|
22
22
|
- `Result`, success/failure outcomes, and safe exception capture;
|
|
23
23
|
- contract shapes, compiled guards/parsers/schemas/generators/reporters;
|
|
24
|
-
- finite/integer checks, error narrowing, and
|
|
24
|
+
- finite/integer checks, error narrowing, and untrusted-boundary containment.
|
|
25
25
|
|
|
26
26
|
For each local symbol, record:
|
|
27
27
|
|
|
@@ -46,7 +46,7 @@ Do not force contract abstractions into plain control flow where they obscure be
|
|
|
46
46
|
|
|
47
47
|
Test every adopted boundary for relevant invariants:
|
|
48
48
|
|
|
49
|
-
- guards are total for
|
|
49
|
+
- guards are total for any value, including revoked proxies, throwing accessors, and exotic prototypes;
|
|
50
50
|
- parsed values satisfy the paired guard;
|
|
51
51
|
- guard-valid values retain identity when promised;
|
|
52
52
|
- invalid or ambiguous values fail in the documented form;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Equivalence of a rewrite
|
|
2
|
+
|
|
3
|
+
Judge a structural rewrite that claims unchanged behavior, such as a sweep under
|
|
4
|
+
`.claude/rules/typescript.md` § Bindings, a centralization move, or a nested-function extraction, by
|
|
5
|
+
this contract against its baseline revision. Prove it with a differential under
|
|
6
|
+
`.claude/rules/quality.md` § Instruments that runs the baseline and the rewrite on the same inputs,
|
|
7
|
+
with a row for each class this contract names that the changed code admits.
|
|
8
|
+
|
|
9
|
+
## Hold the contract
|
|
10
|
+
|
|
11
|
+
- Treat every value the declared parameter types admit as inside the contract: holes, entries read
|
|
12
|
+
through getters (their count and order), entries that throw, an array whose `Symbol.iterator` is
|
|
13
|
+
missing or replaced, and an iterator's `return()`.
|
|
14
|
+
- Match the outcome (the return value, or the thrown error's constructor), the element reads (which
|
|
15
|
+
indices, in what order, how often), and every effect before a throw. Let a message differ only
|
|
16
|
+
where the throwing expression differs, and name each such row.
|
|
17
|
+
- For an entry whose own getters are pure, returning the same value on every read with no side
|
|
18
|
+
effect, match the outcome. For an entry that throws, match the thrown constructor. Leave the count
|
|
19
|
+
and order of an entry's own property reads outside the contract.
|
|
20
|
+
- Leave outside the contract a value outside the declared types, such as a number or a plain
|
|
21
|
+
array-like where an array is declared; a `Proxy` trap other than element `get` and
|
|
22
|
+
`Symbol.iterator`; the order of a record's own property reads when each is read as often as before
|
|
23
|
+
and no outcome changes; and tampering with a global prototype.
|
|
24
|
+
|
|
25
|
+
## Keep the iterator protocol
|
|
26
|
+
|
|
27
|
+
When a rewrite replaces a `for…of` over a caller's array, refuse each form that changes what it
|
|
28
|
+
reads:
|
|
29
|
+
|
|
30
|
+
- `Array.from(source, mapper)` falls back to index access when `Symbol.iterator` is missing, so it
|
|
31
|
+
returns where `for…of` threw a `TypeError`.
|
|
32
|
+
- `source.values()` and the array methods read by index, so they accept a missing iterator and
|
|
33
|
+
bypass a replaced or closing one; `flatMap`, `filter`, `reduce`, `forEach`, `some`, and `every`
|
|
34
|
+
skip a hole that `for…of` reads.
|
|
35
|
+
- A spread copies every element first, so it reads past a hole or ahead of a throwing entry where
|
|
36
|
+
the baseline threw at the earlier element.
|
|
37
|
+
|
|
38
|
+
Consume the source through `Iterator.from(source)` and its helpers, or through a generator whose
|
|
39
|
+
body is the baseline `for…of`. To keep `Array.from`, hand it an iterable that forwards only
|
|
40
|
+
`[Symbol.iterator]()`. Never bound a hand-written stepper by `length`: a replaced iterator that
|
|
41
|
+
yields more entries than `length` is truncated.
|
|
42
|
+
|
|
43
|
+
## Keep every test
|
|
44
|
+
|
|
45
|
+
Before a claim that a rewrite lost no test, run
|
|
46
|
+
`node .agents/skills/orkestrel-harden/scripts/titles.ts --base REV`, where `REV` is the baseline
|
|
47
|
+
revision. Restore each missing title, or rule its removal with the behavior it proved, and confirm
|
|
48
|
+
each moved title by reading the registration that uses it.
|
|
@@ -10,7 +10,7 @@ List every boundary where assumptions can fail:
|
|
|
10
10
|
- partial work, abort, timeout, retry, pause/resume, stop/destroy, and cleanup;
|
|
11
11
|
- concurrency, reentrancy, ordering, idempotency, fairness, and backpressure;
|
|
12
12
|
- filesystem, process, socket, protocol, browser, model, and network boundaries;
|
|
13
|
-
-
|
|
13
|
+
- malformed or unexpected strings, paths, headers, frames, JSON, prototypes, getters, and proxies;
|
|
14
14
|
- resource ownership, leaks, teardown, and pressure;
|
|
15
15
|
- environment isolation and unsupported-host behavior;
|
|
16
16
|
- serialization, restore, public exports, and consumer ergonomics.
|
|
@@ -78,7 +78,7 @@ For every fetch, write, delete, extraction, path, protocol, or authentication bo
|
|
|
78
78
|
- fail closed when an allowlist or target cannot be established;
|
|
79
79
|
- avoid logging secrets or untrusted full payloads;
|
|
80
80
|
- make destructive scope explicit and recoverable where practical;
|
|
81
|
-
- prove malformed, partial, oversized, and
|
|
81
|
+
- prove malformed, partial, oversized, and unexpected input behavior.
|
|
82
82
|
|
|
83
83
|
## Audit the tests themselves
|
|
84
84
|
|
|
@@ -92,6 +92,8 @@ Run `node .agents/skills/orkestrel-harden/scripts/discovery.ts` for the discover
|
|
|
92
92
|
- helpers do not reimplement production logic;
|
|
93
93
|
- real cleanup runs even after assertion or setup failure.
|
|
94
94
|
|
|
95
|
+
Collection evaluates every collected project's `setupFiles`, so the census runs each setup module's load-time work, and `--projects` narrows the report, never that listing. When a setup module contacts a live service at load, such as a `service` project's readiness gate in `tests/setupService.ts`, run the census only while an instance the run may contact is up and the service's address points at it; with the address on a closed port, that project's listing fails and the census exits 2 naming its gate.
|
|
96
|
+
|
|
95
97
|
## Inspect generated outputs
|
|
96
98
|
|
|
97
99
|
- build all supported environments;
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
// Run from the checkout root:
|
|
2
|
+
// node .agents/skills/orkestrel-harden/scripts/titles.ts --base REV [--root DIR] [--json] [--out FILE]
|
|
3
|
+
// Compare exact decoded describe/it/test title counts in tests/**/*.test.ts against REV. Report
|
|
4
|
+
// deficits with their baseline files, or as moved when another tests/**/*.ts string literal holds
|
|
5
|
+
// the title. Confirm moved registrations by reading their holder. Decode quote and backslash
|
|
6
|
+
// escapes only. Balance each-table parentheses over code tokens, skipping lexical regions.
|
|
7
|
+
// Print text by default or one JSON line with --json; --out writes .json or .txt independently.
|
|
8
|
+
// Compare interpolated templates by full raw spelling. Refuse unclosed lexical tokens.
|
|
9
|
+
// Track lexical brace context without a syntax tree. Function-expression and arrow bodies cannot
|
|
10
|
+
// establish a following slash's grammar; refuse that detectable ambiguity instead of claiming a move.
|
|
11
|
+
// Exit 0 with no missing titles, 3 with missing titles, 2 on git, filesystem or token refusal, 64 on usage.
|
|
12
|
+
import { spawnSync } from 'node:child_process'
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
14
|
+
import { dirname, extname, join, relative, resolve } from 'node:path'
|
|
15
|
+
import {
|
|
16
|
+
listFiles,
|
|
17
|
+
normalizeSlashes,
|
|
18
|
+
readMissingFlags,
|
|
19
|
+
readOption,
|
|
20
|
+
} from '../../orkestrel-dispatch/scripts/helpers.ts'
|
|
21
|
+
|
|
22
|
+
interface Title {
|
|
23
|
+
readonly file: string
|
|
24
|
+
readonly title: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface Missing extends Title {
|
|
28
|
+
readonly deficit: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface Moved extends Title {
|
|
32
|
+
readonly holder: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface Report {
|
|
36
|
+
readonly base: string
|
|
37
|
+
readonly root: string
|
|
38
|
+
readonly count: number
|
|
39
|
+
readonly missing: readonly Missing[]
|
|
40
|
+
readonly moved: readonly Moved[]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface Token {
|
|
44
|
+
readonly category: 'code' | 'line' | 'block' | 'string' | 'template' | 'regex'
|
|
45
|
+
readonly start: number
|
|
46
|
+
readonly end: number
|
|
47
|
+
readonly children: readonly Token[]
|
|
48
|
+
readonly prefix?: boolean
|
|
49
|
+
readonly ambiguous?: boolean
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface Brace {
|
|
53
|
+
readonly category: 'block' | 'expression' | 'template'
|
|
54
|
+
readonly ambiguous: boolean
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface Parenthesis {
|
|
58
|
+
readonly prefix: boolean
|
|
59
|
+
readonly ambiguous: boolean
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const TITLE_PATTERN = /^\s*(?:it|test|describe)(?:\.\w+)*\(/gmu
|
|
63
|
+
const REGEX_PREFIX =
|
|
64
|
+
/^(?:return|throw|case|delete|void|typeof|instanceof|in|of|yield|await|else|do)$/u
|
|
65
|
+
|
|
66
|
+
function decodeTitle(text: string, token: Token): string {
|
|
67
|
+
const raw = text.slice(token.start + 1, token.end - 1)
|
|
68
|
+
return token.category === 'template' && token.children.length > 0
|
|
69
|
+
? raw
|
|
70
|
+
: raw.replace(/\\(['"`\\])/gu, '$1')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
class TokenScanner {
|
|
74
|
+
readonly #text: string
|
|
75
|
+
readonly #file: string
|
|
76
|
+
readonly #parentheses: Parenthesis[] = []
|
|
77
|
+
readonly #braces: Brace[] = []
|
|
78
|
+
#position = 0
|
|
79
|
+
|
|
80
|
+
constructor(text: string, file: string) {
|
|
81
|
+
this.#text = text
|
|
82
|
+
this.#file = file
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
scan(): readonly Token[] {
|
|
86
|
+
return this.#readCode(false, 0)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#readCode(expression: boolean, depth: number): readonly Token[] {
|
|
90
|
+
if (depth > 256) throw new Error(`${this.#file}: token nesting exceeds 256`)
|
|
91
|
+
const tokens: Token[] = []
|
|
92
|
+
while (this.#position < this.#text.length) {
|
|
93
|
+
const start = this.#position
|
|
94
|
+
const rest = this.#text.slice(start)
|
|
95
|
+
const character = this.#text[start]
|
|
96
|
+
const previous = tokens.findLast(
|
|
97
|
+
(token) =>
|
|
98
|
+
token.category !== 'line' &&
|
|
99
|
+
token.category !== 'block' &&
|
|
100
|
+
this.#text.slice(token.start, token.end).trim().length > 0,
|
|
101
|
+
)
|
|
102
|
+
const spelling = previous === undefined ? '' : this.#text.slice(previous.start, previous.end)
|
|
103
|
+
if (character === '}' && expression) {
|
|
104
|
+
this.#position += 1
|
|
105
|
+
return tokens
|
|
106
|
+
}
|
|
107
|
+
if (character === '{') {
|
|
108
|
+
const block =
|
|
109
|
+
previous === undefined
|
|
110
|
+
? this.#braces.at(-1)?.category !== 'template'
|
|
111
|
+
: previous.category === 'code' &&
|
|
112
|
+
/^(?:\)|=>|;|\{|\}|else|try|finally|do)$/u.test(spelling)
|
|
113
|
+
this.#braces.push({
|
|
114
|
+
category: block ? 'block' : 'expression',
|
|
115
|
+
ambiguous: block && (spelling === '=>' || previous?.ambiguous === true),
|
|
116
|
+
})
|
|
117
|
+
this.#position += 1
|
|
118
|
+
tokens.push({ category: 'code', start, end: this.#position, children: [] })
|
|
119
|
+
tokens.push(...this.#readCode(true, depth + 1))
|
|
120
|
+
const brace = this.#braces.pop()
|
|
121
|
+
tokens.push({
|
|
122
|
+
category: 'code',
|
|
123
|
+
start: this.#position - 1,
|
|
124
|
+
end: this.#position,
|
|
125
|
+
children: [],
|
|
126
|
+
prefix: brace?.category === 'block',
|
|
127
|
+
ambiguous: brace?.ambiguous === true,
|
|
128
|
+
})
|
|
129
|
+
continue
|
|
130
|
+
}
|
|
131
|
+
if (character === '`') {
|
|
132
|
+
tokens.push(this.#readTemplate(depth + 1))
|
|
133
|
+
continue
|
|
134
|
+
}
|
|
135
|
+
if (
|
|
136
|
+
character === '/' &&
|
|
137
|
+
!rest.startsWith('//') &&
|
|
138
|
+
!rest.startsWith('/*') &&
|
|
139
|
+
previous?.ambiguous === true
|
|
140
|
+
)
|
|
141
|
+
throw new Error(`${this.#file}:${start}: Ambiguous brace before slash`)
|
|
142
|
+
const regex =
|
|
143
|
+
character === '/' &&
|
|
144
|
+
!rest.startsWith('//') &&
|
|
145
|
+
!rest.startsWith('/*') &&
|
|
146
|
+
(previous === undefined ||
|
|
147
|
+
previous.prefix === true ||
|
|
148
|
+
(previous.category === 'code' &&
|
|
149
|
+
(REGEX_PREFIX.test(spelling) ||
|
|
150
|
+
!/^(?:[\p{ID_Continue}$]+|[\])}]|\+\+|--)$/u.test(spelling))))
|
|
151
|
+
const category = rest.startsWith('//')
|
|
152
|
+
? 'line'
|
|
153
|
+
: rest.startsWith('/*')
|
|
154
|
+
? 'block'
|
|
155
|
+
: character === "'" || character === '"'
|
|
156
|
+
? 'string'
|
|
157
|
+
: regex
|
|
158
|
+
? 'regex'
|
|
159
|
+
: 'code'
|
|
160
|
+
const match =
|
|
161
|
+
category === 'line'
|
|
162
|
+
? /^\/\/[^\r\n]*/u.exec(rest)
|
|
163
|
+
: category === 'block'
|
|
164
|
+
? /^\/\*[\s\S]*?\*\//u.exec(rest)
|
|
165
|
+
: category === 'string'
|
|
166
|
+
? (character === "'"
|
|
167
|
+
? /^'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n\u2028\u2029])*'/u
|
|
168
|
+
: /^"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n\u2028\u2029])*"/u
|
|
169
|
+
).exec(rest)
|
|
170
|
+
: category === 'regex'
|
|
171
|
+
? /^\/(?:\\[^\r\n]|\[(?:\\[^\r\n]|[^\]\\\r\n])*\]|[^/\\[\r\n])*\/[a-z]*/iu.exec(
|
|
172
|
+
rest,
|
|
173
|
+
)
|
|
174
|
+
: /^(?:[\p{ID_Start}_$][\p{ID_Continue}$]*|\d[\w.]*|\s+|\+\+|--|=>|.)/u.exec(rest)
|
|
175
|
+
if (match === null) throw new Error(`${this.#file}:${start}: Unclosed ${category} token`)
|
|
176
|
+
this.#position += match[0].length
|
|
177
|
+
if (category === 'code' && character === '(') {
|
|
178
|
+
const words = tokens
|
|
179
|
+
.filter((token) => token.category === 'code')
|
|
180
|
+
.map((token) => this.#text.slice(token.start, token.end))
|
|
181
|
+
.filter((word) => word.trim().length > 0)
|
|
182
|
+
const keyword = words.findLastIndex((word) => word === 'function')
|
|
183
|
+
const callable =
|
|
184
|
+
keyword >= 0 &&
|
|
185
|
+
words
|
|
186
|
+
.slice(keyword + 1)
|
|
187
|
+
.every((word) => word === '*' || /^[\p{ID_Start}_$][\p{ID_Continue}$]*$/u.test(word))
|
|
188
|
+
const before = words[words[keyword - 1] === 'async' ? keyword - 2 : keyword - 1]
|
|
189
|
+
this.#parentheses.push({
|
|
190
|
+
prefix: /^(?:if|while|for|with|switch|catch)$/u.test(spelling),
|
|
191
|
+
ambiguous:
|
|
192
|
+
callable &&
|
|
193
|
+
(before === undefined
|
|
194
|
+
? this.#braces.at(-1)?.category === 'template'
|
|
195
|
+
: /^(?:[=(,:[?+\-*/%&|^!~<>]|=>|return|throw|yield|await|case|new|void|typeof|delete|instanceof|in|of)$/u.test(
|
|
196
|
+
before,
|
|
197
|
+
)),
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
const parenthesis =
|
|
201
|
+
category === 'code' && character === ')' ? this.#parentheses.pop() : undefined
|
|
202
|
+
tokens.push({
|
|
203
|
+
category,
|
|
204
|
+
start,
|
|
205
|
+
end: this.#position,
|
|
206
|
+
children: [],
|
|
207
|
+
prefix: parenthesis?.prefix === true,
|
|
208
|
+
ambiguous: parenthesis?.ambiguous === true,
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
if (expression)
|
|
212
|
+
throw new Error(`${this.#file}:${this.#position}: Unclosed template expression or brace`)
|
|
213
|
+
return tokens
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
#readTemplate(depth: number): Token {
|
|
217
|
+
const start = this.#position
|
|
218
|
+
const children: Token[] = []
|
|
219
|
+
this.#position += 1
|
|
220
|
+
while (this.#position < this.#text.length) {
|
|
221
|
+
if (this.#text[this.#position] === '\\') {
|
|
222
|
+
this.#position += 2
|
|
223
|
+
} else if (this.#text[this.#position] === '`') {
|
|
224
|
+
this.#position += 1
|
|
225
|
+
return { category: 'template', start, end: this.#position, children }
|
|
226
|
+
} else if (this.#text.startsWith('${', this.#position)) {
|
|
227
|
+
const opening = this.#position
|
|
228
|
+
this.#position += 2
|
|
229
|
+
this.#braces.push({ category: 'template', ambiguous: false })
|
|
230
|
+
const expression = this.#readCode(true, depth)
|
|
231
|
+
this.#braces.pop()
|
|
232
|
+
children.push({
|
|
233
|
+
category: 'code',
|
|
234
|
+
start: opening,
|
|
235
|
+
end: this.#position,
|
|
236
|
+
children: expression,
|
|
237
|
+
})
|
|
238
|
+
} else {
|
|
239
|
+
this.#position += 1
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
throw new Error(`${this.#file}:${start}: Unclosed template token`)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function readArguments(
|
|
247
|
+
text: string,
|
|
248
|
+
tokens: IterableIterator<Token>,
|
|
249
|
+
location: string,
|
|
250
|
+
depth: number,
|
|
251
|
+
): number {
|
|
252
|
+
if (depth > 256) throw new Error(`${location}: each argument nesting exceeds 256`)
|
|
253
|
+
for (const token of tokens) {
|
|
254
|
+
if (token.category !== 'code') continue
|
|
255
|
+
const spelling = text.slice(token.start, token.end)
|
|
256
|
+
if (spelling === '(') readArguments(text, tokens, location, depth + 1)
|
|
257
|
+
else if (spelling === ')') return token.end
|
|
258
|
+
}
|
|
259
|
+
throw new Error(`${location}: Unbalanced each arguments`)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function readRegistrations(text: string, tokens: readonly Token[], file: string): readonly Token[] {
|
|
263
|
+
const code = tokens
|
|
264
|
+
.map((token) => {
|
|
265
|
+
const raw = text.slice(token.start, token.end)
|
|
266
|
+
if (token.category === 'code') return raw
|
|
267
|
+
const blank = raw
|
|
268
|
+
.split('')
|
|
269
|
+
.map((character) => (/[\r\n]/u.test(character) ? character : ' '))
|
|
270
|
+
.join('')
|
|
271
|
+
return token.category === 'string' || token.category === 'template'
|
|
272
|
+
? `\0${blank.slice(1)}`
|
|
273
|
+
: blank
|
|
274
|
+
})
|
|
275
|
+
.join('')
|
|
276
|
+
const offset = tokens[0]?.start ?? 0
|
|
277
|
+
const positions = new Map(tokens.map((token, index) => [token.start, index]))
|
|
278
|
+
const starts = new Set(
|
|
279
|
+
Array.from(code.matchAll(TITLE_PATTERN)).flatMap((match) => {
|
|
280
|
+
const opening = offset + match.index + match[0].length - 1
|
|
281
|
+
const direct = /^\s*\0/u.exec(code.slice(opening + 1 - offset))
|
|
282
|
+
if (!/\.each\(/u.test(match[0]) && direct !== null) return [opening + direct[0].length]
|
|
283
|
+
const index = positions.get(opening)
|
|
284
|
+
if (index === undefined) return []
|
|
285
|
+
const line = text.slice(0, opening).split(/\r\n|\n/u).length
|
|
286
|
+
const end = readArguments(text, tokens.slice(index + 1).values(), `${file}:${line}`, 0)
|
|
287
|
+
const title = /^\s*\(\s*\0/u.exec(code.slice(end - offset))
|
|
288
|
+
return title === null ? [] : [end + title[0].length - 1]
|
|
289
|
+
}),
|
|
290
|
+
)
|
|
291
|
+
return [
|
|
292
|
+
...tokens.filter((token) => starts.has(token.start)),
|
|
293
|
+
...tokens.flatMap((token) => readRegistrations(text, token.children, file)),
|
|
294
|
+
]
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function collectLiterals(tokens: readonly Token[]): readonly Token[] {
|
|
298
|
+
return tokens.flatMap((token) => [
|
|
299
|
+
...(token.category === 'string' || token.category === 'template' ? [token] : []),
|
|
300
|
+
...collectLiterals(token.children),
|
|
301
|
+
])
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function readGit(root: string, args: readonly string[]): string {
|
|
305
|
+
const result = spawnSync('git', ['-C', root, ...args], {
|
|
306
|
+
encoding: 'utf8',
|
|
307
|
+
windowsHide: true,
|
|
308
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
309
|
+
})
|
|
310
|
+
if (result.error !== undefined || result.status !== 0) {
|
|
311
|
+
throw new Error(`git: ${result.error?.message ?? result.stderr.trim()}`)
|
|
312
|
+
}
|
|
313
|
+
return result.stdout
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function readReport(base: string, root: string): Report {
|
|
317
|
+
const baseline = readGit(root, ['ls-tree', '-r', '-z', '--name-only', base, '--', 'tests'])
|
|
318
|
+
.split('\0')
|
|
319
|
+
.filter((file) => file.endsWith('.test.ts'))
|
|
320
|
+
.flatMap((file) => {
|
|
321
|
+
const text = readGit(root, ['show', `${base}:${file}`])
|
|
322
|
+
return readRegistrations(
|
|
323
|
+
text,
|
|
324
|
+
new TokenScanner(text, `${base}:${file}`).scan(),
|
|
325
|
+
`${base}:${file}`,
|
|
326
|
+
).map((token) => ({
|
|
327
|
+
file,
|
|
328
|
+
title: decodeTitle(text, token),
|
|
329
|
+
}))
|
|
330
|
+
})
|
|
331
|
+
const directory = join(root, 'tests')
|
|
332
|
+
const files = (existsSync(directory) ? listFiles(directory) : [])
|
|
333
|
+
.filter(({ path }) => path.endsWith('.ts'))
|
|
334
|
+
.map(({ path }) => {
|
|
335
|
+
const file = normalizeSlashes(relative(root, path))
|
|
336
|
+
const text = readFileSync(path, 'utf8')
|
|
337
|
+
const tokens = new TokenScanner(text, file).scan()
|
|
338
|
+
return {
|
|
339
|
+
file,
|
|
340
|
+
text,
|
|
341
|
+
tokens,
|
|
342
|
+
registrations: file.endsWith('.test.ts') ? readRegistrations(text, tokens, file) : [],
|
|
343
|
+
}
|
|
344
|
+
})
|
|
345
|
+
.sort((left, right) => left.file.localeCompare(right.file))
|
|
346
|
+
const titles = files
|
|
347
|
+
.filter(({ file }) => file.endsWith('.test.ts'))
|
|
348
|
+
.flatMap(({ file, text, registrations }) =>
|
|
349
|
+
registrations.map((token) => ({ file, title: decodeTitle(text, token) })),
|
|
350
|
+
)
|
|
351
|
+
// A surviving registration supplies its title count, never evidence that a lost duplicate moved.
|
|
352
|
+
const holders = files.flatMap(({ file, text, tokens, registrations }) =>
|
|
353
|
+
collectLiterals(tokens)
|
|
354
|
+
.filter((token) => !registrations.includes(token))
|
|
355
|
+
.map((token) => ({ file, title: decodeTitle(text, token) })),
|
|
356
|
+
)
|
|
357
|
+
const deficits = [...Map.groupBy(baseline, ({ title }) => title)].flatMap(([title, entries]) => {
|
|
358
|
+
const survivors = titles.filter((candidate) => candidate.title === title)
|
|
359
|
+
const unmatched = [...Map.groupBy(entries, ({ file }) => file)].flatMap(([file, local]) =>
|
|
360
|
+
local.slice(survivors.filter((candidate) => candidate.file === file).length),
|
|
361
|
+
)
|
|
362
|
+
const surplus = [...Map.groupBy(survivors, ({ file }) => file)].flatMap(([file, local]) =>
|
|
363
|
+
local.slice(entries.filter((candidate) => candidate.file === file).length),
|
|
364
|
+
)
|
|
365
|
+
return [...Map.groupBy(unmatched.slice(surplus.length), ({ file }) => file)].map(
|
|
366
|
+
([file, lost]) => ({
|
|
367
|
+
file,
|
|
368
|
+
title,
|
|
369
|
+
deficit: lost.length,
|
|
370
|
+
}),
|
|
371
|
+
)
|
|
372
|
+
})
|
|
373
|
+
const classified = deficits.map((entry) => ({
|
|
374
|
+
entry,
|
|
375
|
+
holder: holders.find(({ title }) => title === entry.title)?.file,
|
|
376
|
+
}))
|
|
377
|
+
return {
|
|
378
|
+
base,
|
|
379
|
+
root: normalizeSlashes(root),
|
|
380
|
+
count: baseline.length,
|
|
381
|
+
missing: classified.flatMap(({ entry, holder }) => (holder === undefined ? [entry] : [])),
|
|
382
|
+
moved: classified.flatMap(({ entry, holder }) =>
|
|
383
|
+
holder === undefined ? [] : [{ file: entry.file, title: entry.title, holder }],
|
|
384
|
+
),
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function renderReport(report: Report): string {
|
|
389
|
+
return [
|
|
390
|
+
`titles: ${report.count} baseline title(s) at ${report.base}, ${report.missing.length} missing, ${report.moved.length} moved`,
|
|
391
|
+
...report.missing.map(({ file, title, deficit }) => `missing\t${file}\t${title}\t${deficit}`),
|
|
392
|
+
...report.moved.map(({ file, title, holder }) => `moved\t${file}\t${title}\t${holder}`),
|
|
393
|
+
].join('\n')
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function main(argv: readonly string[]): number {
|
|
397
|
+
const missing = readMissingFlags(argv, ['--base', '--root', '--out'])
|
|
398
|
+
if (missing.length > 0) {
|
|
399
|
+
console.error(`titles: ${missing.join(', ')} given with no value`)
|
|
400
|
+
return 64
|
|
401
|
+
}
|
|
402
|
+
const base = readOption(argv, '--base')
|
|
403
|
+
const out = readOption(argv, '--out')
|
|
404
|
+
if (base === undefined || (out !== undefined && !['.json', '.txt'].includes(extname(out)))) {
|
|
405
|
+
console.error('usage: titles.ts --base REV [--root DIR] [--json] [--out FILE.json|FILE.txt]')
|
|
406
|
+
return 64
|
|
407
|
+
}
|
|
408
|
+
try {
|
|
409
|
+
const report = readReport(base, resolve(readOption(argv, '--root') ?? process.cwd()))
|
|
410
|
+
const json = JSON.stringify(report)
|
|
411
|
+
const text = renderReport(report)
|
|
412
|
+
if (out !== undefined) {
|
|
413
|
+
const target = resolve(out)
|
|
414
|
+
mkdirSync(dirname(target), { recursive: true })
|
|
415
|
+
writeFileSync(target, `${extname(target) === '.json' ? json : text}\n`)
|
|
416
|
+
}
|
|
417
|
+
console.log(argv.includes('--json') ? json : text)
|
|
418
|
+
return report.missing.length > 0 ? 3 : 0
|
|
419
|
+
} catch (error) {
|
|
420
|
+
console.error(`titles: ${error instanceof Error ? error.message : String(error)}`)
|
|
421
|
+
return 2
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
process.exitCode = main(process.argv.slice(2))
|
|
@@ -201,8 +201,8 @@ await PORTFOLIO.place('home')
|
|
|
201
201
|
control, a Tab away, or a named button. Report a screen that commits on a timer, on an
|
|
202
202
|
unpredictable event, or only after work the person cannot observe as a screen finding, and never
|
|
203
203
|
work around it in the layer.
|
|
204
|
-
7. **Type only what a person would.** Journeys carry trusted input;
|
|
205
|
-
the transport family and the parser suites.
|
|
204
|
+
7. **Type only what a person would.** Journeys carry trusted input; inputs no person would type
|
|
205
|
+
belong to the transport family and the parser suites.
|
|
206
206
|
8. **Perform every interaction step unconditionally.** Never gate a step on whether the control it
|
|
207
207
|
is about to drive exists or is reachable, and never branch a journey on `readRefusal`. Let the
|
|
208
208
|
resolver's failure voice name what the interface withheld. A guarded step passes whether or not
|
|
@@ -30,7 +30,8 @@ file is written to, and the flag as `enabled`.
|
|
|
30
30
|
picture is one element.
|
|
31
31
|
- Take `enabled` from `inject('capture')`, which the generated root configuration provides from the
|
|
32
32
|
`CAPTURE` environment variable set to `1` in the shell that runs the axis. An ordinary run leaves
|
|
33
|
-
it false, so `place` returns `undefined`, resizes nothing, writes nothing,
|
|
33
|
+
it false, so `place` returns `undefined`, resizes nothing, writes nothing, records no placement,
|
|
34
|
+
and still refuses an unregistered state and a second placement of one state.
|
|
34
35
|
- Read `files` for the registry expanded across every variant, `placements` for what this run placed,
|
|
35
36
|
and `paths` for what it wrote. Each hands back a snapshot.
|
|
36
37
|
- Reach for `readFrame` where the written image itself is the subject — its decoded size, or its
|
|
@@ -42,8 +43,8 @@ The package already refuses these, so assert none of them again:
|
|
|
42
43
|
| Refusal | Raised by |
|
|
43
44
|
| -------------------------------------------------------------------- | --------------------- |
|
|
44
45
|
| `Capture variant "<name>" is not registered` | `createPortfolio` |
|
|
45
|
-
| `Capture state "<state>" is not registered` | `place`,
|
|
46
|
-
| `Capture state "<state>" is already placed` | `place`,
|
|
46
|
+
| `Capture state "<state>" is not registered` | `place`, in every run |
|
|
47
|
+
| `Capture state "<state>" is already placed` | `place`, in every run |
|
|
47
48
|
| `Capture frame at <path> is not the one this run shot` | `captureFrame` |
|
|
48
49
|
| `Capture frame was written to <written> where <asked> was asked for` | `captureFrame` |
|
|
49
50
|
|
|
@@ -92,11 +93,11 @@ Write each of the following proofs into the suite. The package asserts none of t
|
|
|
92
93
|
- Keep the filename proof always-on, so a registry edit that introduces a duplicate or a collision
|
|
93
94
|
fails the ordinary run. Compare `expandCaptures(states, variants)` against `files` and against a
|
|
94
95
|
set built from it; a length equal to the set's size is what proves the expansion unique.
|
|
95
|
-
- Assert placement as set equality against the registry, in every run.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
- Assert placement as set equality against the registry, in every run.
|
|
97
|
+
- Read the placement proof from the suite's own set rather than from `placements`, which stays empty
|
|
98
|
+
in an ordinary run. `place` refuses an unregistered name and a second placement of one state in
|
|
99
|
+
every run, but a registered state that no journey placed reaches no refusal; the always-on
|
|
100
|
+
placement proof catches it.
|
|
100
101
|
- Under the flag, assert the written filenames equal the registry expanded for the run's variant.
|
|
101
102
|
`captureFrame` already reads each file back and compares its bytes against the frame it shot, so
|
|
102
103
|
the path `place` returns is proof the file exists and holds that frame.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: orkestrel-parallel
|
|
3
|
+
description: >-
|
|
4
|
+
Run several writer lanes in one checkout at the same time, coordinated by messages: writers edit owned regions in phases, one runner lane owns every test and check process, and the Orchestrator rules and mediates. Use when a campaign has three or more units whose files are mostly disjoint and whose shared files are a few kind files, guides, or entry tests. Do not use it when two units rewrite the same region, when a unit needs a quiet tree for a measurement, or when the harness gives lanes no shared file system or no message channel that wakes an idle lane.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Parallel lanes
|
|
8
|
+
|
|
9
|
+
## Lanes
|
|
10
|
+
|
|
11
|
+
| Lane | Role | Owns | Never |
|
|
12
|
+
| ------------------ | -------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
|
|
13
|
+
| Orchestrator | the harness's own engine | the protocol, the roster, launches, rulings, holds, acceptance, commits | delegates acceptance |
|
|
14
|
+
| `runner` | `verifier` on the native small engine | every test, typecheck, lint, format, build, and citation process; its log, run files, and result files under `coord/` | writes outside `coord/`; runs an unrequested command |
|
|
15
|
+
| writer (`w<unit>`) | `builder`, `astra`, or `opus` by `.agents/orchestration.md` § Routing | its owned files, its owned regions of shared files, and its log | runs a test, check, lint, format, or build process |
|
|
16
|
+
| reviewer | `analyst` or `reviewer`, independent of the lane's writer per `.agents/orchestration.md` § Engines | a read-only verdict on the work the size gate sends it | edits, writes, runs, or messages |
|
|
17
|
+
|
|
18
|
+
- Run at most three writers and one runner. Keep every live lane, reviewers included, inside the harness's concurrency limit (`references/harnesses.md`), and queue a lane that does not fit.
|
|
19
|
+
- Before launch, assign every file region to exactly one writer. A shared file is owned by region (a doc block, a table, a guide section, a list entry), never as a whole.
|
|
20
|
+
|
|
21
|
+
## Set up
|
|
22
|
+
|
|
23
|
+
1. Commit the accepted state; every lane starts from one commit.
|
|
24
|
+
2. Write `tmp/units/<round>/coord/protocol.md`: the path of this skill, the round's lanes, each writer's owned files and regions, and each writer's acceptance commands. Write `coord/roster.md` (lane, role, address, Orchestrator address). Create `coord/results/` and `coord/runs/`.
|
|
25
|
+
3. Launch the runner first. Its first turn logs `ready` with its address and ends.
|
|
26
|
+
4. Launch the writers one at a time. Fill each roster row from its launch result; after launch, send each lane that messages peers their addresses.
|
|
27
|
+
5. For a lane already running, send one in-flight message naming the protocol, its lane name, its log, and its next request number.
|
|
28
|
+
|
|
29
|
+
## Channels
|
|
30
|
+
|
|
31
|
+
- File channel: each writer and the runner append only to their own log, `coord/<lane>.md`, one line per event: `<lane> | <request or phase> | <kind> | <text>`. Never write another lane's log. A reviewer returns its verdict to the Orchestrator and writes nothing.
|
|
32
|
+
- Push channel (`references/harnesses.md`): requests queue to the runner one turn each, in arrival order; replies and rulings join the receiver's running turn or start one.
|
|
33
|
+
- Number requests from 1 per lane across the whole round, fix units included, and never reuse a number.
|
|
34
|
+
- Results: the runner writes each result under a temporary name in `coord/results/`, renames it to `coord/results/<lane>-<n>.md` when it is complete, then replies. A waiting writer polls for that file; a lost message does not stall it.
|
|
35
|
+
- Request format:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
REQUEST <lane>-<n>
|
|
39
|
+
from: <lane> <address>
|
|
40
|
+
commands:
|
|
41
|
+
1. <exact command>
|
|
42
|
+
expect: <red or green per command, with case names>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
- Escalation: send the Orchestrator `ESCALATE <lane>: expected, found, evidence, proposed action`. Continue the work the question does not block. The ruling arrives in your turn; record any scope extension in your report.
|
|
46
|
+
- A writer can message another writer a cross-lane fact (a line it added, a window it opens). Rulings come only from the Orchestrator.
|
|
47
|
+
|
|
48
|
+
## Writers
|
|
49
|
+
|
|
50
|
+
- Work in phases and log each: `types` (types and API docs), `red` (failing-first cases, then a red-run request), `source` (the change, then a green-run request), `docs` (guide sentences).
|
|
51
|
+
- In a shared file, edit small, complete hunks inside your owned regions. Re-read the region immediately before each patch, never rewrite a whole file, and leave every hunk syntactically complete. When a patch fails on changed context, re-read and re-apply.
|
|
52
|
+
- Request every test, check, lint, format, and build process from the runner. Read-only shell and search commands stay yours.
|
|
53
|
+
- Run no formatter. Fix a format finding by hand; when the finding names only a file, ask the Orchestrator for the exact hunk.
|
|
54
|
+
- Before a temporary mutation (a mutation proof), ask the runner to hold unrelated runs and each lane that owns a region of the mutated file to hold its edits, and mutate only after each has logged `held`. Restore the exact text, confirm with `git diff`, log `restored`, and tell them.
|
|
55
|
+
- When done and green, log `done`, send the runner `FINAL <lane>`, write your report, and end your task.
|
|
56
|
+
|
|
57
|
+
## Runner
|
|
58
|
+
|
|
59
|
+
- Before each command, wait until no test or typecheck process runs in this checkout, excluding editor language services. Bound the wait by the request's expected run time. When any conflicting process exceeds that budget, report its recorded ownership, process id, and command line to the Orchestrator and suspend the request for a ruling. Never kill a process yourself. For a capped run, have the Orchestrator apply the `orkestrel-dispatch` skill's launch reference § Kill and relaunch before releasing the affected files or starting another command.
|
|
60
|
+
- Launch each command through `node .agents/skills/orkestrel-dispatch/scripts/launch.ts` under a cap sized from prior runs, with `--journal coord/runs/<lane>-<n>-<k>.txt` and `--errors coord/runs/<lane>-<n>-<k>.err`, and log the process id it prints. When the shell returns before the launcher ends, resume its tracked shell session until the launcher exits, then read both files and require the terminal exit record. Track descendant process ids while their parent lives, and confirm that the command and its descendants have ended before publishing completion; otherwise escalate under the preceding wait rule.
|
|
61
|
+
- Report each finding only to the lane that owns its file.
|
|
62
|
+
- Serve the commands each writer's acceptance criteria name, plus the standing list: the root typecheck, the scoped typecheck, the guide parity check, the package entry test, and lint or format checks over paths the writer names. Run each read-only; none writes the tree.
|
|
63
|
+
- After every writer's current `FINAL` has arrived, wait for the Orchestrator's closing request naming a unique gate number and confirming acknowledged write holds. Run the root typecheck, scoped typecheck, every acceptance test file alone, the guide parity check, and lint and format checks over every path `git status --porcelain` lists. Publish the complete result atomically as `coord/results/closing-<k>.md`, reply with that gate number, and end your task.
|
|
64
|
+
|
|
65
|
+
## Orchestrator
|
|
66
|
+
|
|
67
|
+
- Before ruling on an escalation, reproduce its evidence at its cited lines. Bound every ruling: what to change, what to leave.
|
|
68
|
+
- When a format check names only a file, inspect the file and send the writer the exact formatting hunk. After the writer applies it, request a read-only format check from the runner.
|
|
69
|
+
- Run the review `.agents/orchestration.md` § Size gate names, and supply its diff and status. A unit's review can start read-only when its lane logs `done`, while the others write; a campaign's `orkestrel-falsify` round runs on the integrated result after the closing gate.
|
|
70
|
+
- Before resuming a writer for a fix unit, send the runner that lane's `FINAL` invalidation and wait for acknowledgment; every other lane's `FINAL` stands. Follow `references/harnesses.md` to resume the writer's lane or dispatch its successor. If a closing gate is active, keep the reopened writer read-only until that gate completes.
|
|
71
|
+
- Before requesting a closing gate, obtain write-hold acknowledgments from every live writer and hold your own source edits. Allocate a gate number never used earlier in the round. Keep those holds until the matching complete result is published. After a fix unit, request another gate covering the affected checks and acceptance tests.
|
|
72
|
+
- After the last green gate and the review the size gate names, commit. When shared files mix several units' hunks, commit the batch once and name each unit, its engine, and each fix unit in the message.
|