@orkestrel/scaffold 0.0.107 → 0.0.109

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.
Files changed (164) hide show
  1. package/dist/agents/skills/orkestrel-dispatch/scripts/bench.js +326 -91
  2. package/dist/agents/skills/orkestrel-dispatch/scripts/brief.js +110 -14
  3. package/dist/agents/skills/orkestrel-dispatch/scripts/chain.js +280 -0
  4. package/dist/agents/skills/orkestrel-dispatch/scripts/cite.js +188 -26
  5. package/dist/agents/skills/orkestrel-dispatch/scripts/launch.js +142 -43
  6. package/dist/agents/skills/orkestrel-dispatch/scripts/login.js +240 -62
  7. package/dist/agents/skills/orkestrel-dispatch/scripts/patch.js +323 -0
  8. package/dist/agents/skills/orkestrel-dispatch/scripts/result.js +169 -32
  9. package/dist/agents/skills/orkestrel-dispatch/scripts/sweep.js +368 -27
  10. package/dist/agents/skills/orkestrel-falsify/scripts/mutate.js +560 -0
  11. package/dist/agents/skills/orkestrel-harden/scripts/discovery.js +1052 -205
  12. package/dist/agents/skills/orkestrel-harden/scripts/measure.js +361 -0
  13. package/dist/agents/skills/orkestrel-harden/scripts/titles.js +455 -80
  14. package/dist/agents/skills/orkestrel-parallel/scripts/cases.js +327 -0
  15. package/dist/agents/skills/orkestrel-publish/scripts/compare.js +298 -51
  16. package/dist/agents/skills/orkestrel-publish/scripts/pins.js +170 -13
  17. package/dist/agents/skills/orkestrel-publish/scripts/swap.js +956 -191
  18. package/dist/agents/skills/orkestrel-publish/scripts/wave.js +707 -112
  19. package/dist/agents/skills/orkestrel-publish/scripts/window.js +235 -41
  20. package/dist/agents/skills/orkestrel-scout/scripts/map.js +341 -54
  21. package/dist/agents/templates/brief.md +1 -1
  22. package/dist/bin/main.js +51 -46
  23. package/dist/bin/main.js.map +1 -1
  24. package/dist/host/AGENTS.md +26 -20
  25. package/dist/host/agents/orchestration.md +98 -58
  26. package/dist/host/agents/platforms/posix.md +15 -0
  27. package/dist/host/agents/platforms/windows.md +37 -0
  28. package/dist/host/agents/skills/orkestrel-align/SKILL.md +10 -13
  29. package/dist/host/agents/skills/orkestrel-align/references/fleet.md +4 -10
  30. package/dist/host/agents/skills/orkestrel-align/references/integration.md +1 -1
  31. package/dist/host/agents/skills/orkestrel-build/SKILL.md +16 -48
  32. package/dist/host/agents/skills/orkestrel-build/agents/openai.yaml +1 -1
  33. package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +19 -16
  34. package/dist/host/agents/skills/orkestrel-debrief/agents/openai.yaml +1 -1
  35. package/dist/host/agents/skills/orkestrel-debrief/references/field-testing.md +15 -28
  36. package/dist/host/agents/skills/orkestrel-debrief/references/instruction-audit.md +5 -6
  37. package/dist/host/agents/skills/orkestrel-debrief/references/retention.md +12 -14
  38. package/dist/host/agents/skills/orkestrel-dispatch/SKILL.md +36 -24
  39. package/dist/host/agents/skills/orkestrel-dispatch/references/launch.md +14 -11
  40. package/dist/host/agents/skills/orkestrel-dispatch/scripts/bench.ts +366 -75
  41. package/dist/host/agents/skills/orkestrel-dispatch/scripts/brief.ts +126 -16
  42. package/dist/host/agents/skills/orkestrel-dispatch/scripts/chain.ts +323 -0
  43. package/dist/host/agents/skills/orkestrel-dispatch/scripts/cite.ts +220 -31
  44. package/dist/host/agents/skills/orkestrel-dispatch/scripts/launch.ts +176 -33
  45. package/dist/host/agents/skills/orkestrel-dispatch/scripts/login.ts +294 -59
  46. package/dist/host/agents/skills/orkestrel-dispatch/scripts/patch.ts +401 -0
  47. package/dist/host/agents/skills/orkestrel-dispatch/scripts/result.ts +199 -34
  48. package/dist/host/agents/skills/orkestrel-dispatch/scripts/sweep.ts +428 -27
  49. package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +10 -8
  50. package/dist/host/agents/skills/orkestrel-falsify/references/brief.md +1 -5
  51. package/dist/host/agents/skills/orkestrel-falsify/references/reconcile.md +1 -1
  52. package/dist/host/agents/skills/orkestrel-falsify/scripts/mutate.ts +701 -0
  53. package/dist/host/agents/skills/orkestrel-harden/SKILL.md +16 -12
  54. package/dist/host/agents/skills/orkestrel-harden/references/bindings.md +23 -81
  55. package/dist/host/agents/skills/orkestrel-harden/references/centralization.md +4 -5
  56. package/dist/host/agents/skills/orkestrel-harden/references/contract.md +2 -0
  57. package/dist/host/agents/skills/orkestrel-harden/references/document.md +33 -0
  58. package/dist/host/agents/skills/orkestrel-harden/references/equivalence.md +6 -3
  59. package/dist/host/agents/skills/orkestrel-harden/references/hardening.md +8 -4
  60. package/dist/host/agents/skills/orkestrel-harden/references/research.md +2 -0
  61. package/dist/host/agents/skills/orkestrel-harden/scripts/discovery.ts +1272 -279
  62. package/dist/host/agents/skills/orkestrel-harden/scripts/measure.ts +425 -0
  63. package/dist/host/agents/skills/orkestrel-harden/scripts/titles.ts +603 -98
  64. package/dist/host/agents/skills/orkestrel-journey/SKILL.md +75 -98
  65. package/dist/host/agents/skills/orkestrel-journey/references/captures.md +54 -17
  66. package/dist/host/agents/skills/orkestrel-journey/references/decide.md +1 -20
  67. package/dist/host/agents/skills/orkestrel-journey/references/layer.md +21 -31
  68. package/dist/host/agents/skills/orkestrel-journey/references/styles.md +1 -1
  69. package/dist/host/agents/skills/orkestrel-parallel/SKILL.md +91 -30
  70. package/dist/host/agents/skills/orkestrel-parallel/agents/openai.yaml +2 -2
  71. package/dist/host/agents/skills/orkestrel-parallel/references/claude.md +34 -0
  72. package/dist/host/agents/skills/orkestrel-parallel/references/codex.md +29 -0
  73. package/dist/host/agents/skills/orkestrel-parallel/references/cursor.md +29 -0
  74. package/dist/host/agents/skills/orkestrel-parallel/references/t3.md +31 -0
  75. package/dist/host/agents/skills/orkestrel-parallel/scripts/cases.ts +389 -0
  76. package/dist/host/agents/skills/orkestrel-polish/SKILL.md +16 -20
  77. package/dist/host/agents/skills/orkestrel-polish/references/capture-harness.md +16 -26
  78. package/dist/host/agents/skills/orkestrel-publish/SKILL.md +12 -26
  79. package/dist/host/agents/skills/orkestrel-publish/agents/openai.yaml +2 -2
  80. package/dist/host/agents/skills/orkestrel-publish/references/adopt.md +23 -0
  81. package/dist/host/agents/skills/orkestrel-publish/references/release.md +4 -4
  82. package/dist/host/agents/skills/orkestrel-publish/references/wave.md +12 -10
  83. package/dist/host/agents/skills/orkestrel-publish/references/window.md +20 -25
  84. package/dist/host/agents/skills/orkestrel-publish/scripts/compare.ts +348 -59
  85. package/dist/host/agents/skills/orkestrel-publish/scripts/pins.ts +202 -22
  86. package/dist/host/agents/skills/orkestrel-publish/scripts/swap.ts +1061 -191
  87. package/dist/host/agents/skills/orkestrel-publish/scripts/wave.ts +812 -140
  88. package/dist/host/agents/skills/orkestrel-publish/scripts/window.ts +280 -38
  89. package/dist/host/agents/skills/orkestrel-scout/SKILL.md +3 -3
  90. package/dist/host/agents/skills/orkestrel-scout/scripts/map.ts +428 -70
  91. package/dist/host/agents/templates/brief.md +1 -1
  92. package/dist/host/agents/transports/claude-cli.md +3 -3
  93. package/dist/host/agents/transports/codex.md +3 -8
  94. package/dist/host/agents/transports/cursor.md +2 -3
  95. package/dist/host/claude/AGENTS.md +7 -10
  96. package/dist/host/claude/agents/analyst.md +3 -3
  97. package/dist/host/claude/agents/astra.md +3 -3
  98. package/dist/host/claude/agents/builder.md +1 -1
  99. package/dist/host/claude/agents/checker.md +1 -1
  100. package/dist/host/claude/agents/distiller.md +1 -1
  101. package/dist/host/claude/agents/grok.md +1 -1
  102. package/dist/host/claude/agents/opus.md +2 -2
  103. package/dist/host/claude/agents/orkestrel.md +57 -90
  104. package/dist/host/claude/agents/researcher.md +1 -1
  105. package/dist/host/claude/agents/reviewer.md +4 -0
  106. package/dist/host/claude/agents/scout.md +1 -1
  107. package/dist/host/claude/agents/verifier.md +1 -1
  108. package/dist/host/claude/rules/application.md +14 -21
  109. package/dist/host/claude/rules/architecture.md +34 -44
  110. package/dist/host/claude/rules/browser.md +3 -4
  111. package/dist/host/claude/rules/campaign.md +55 -0
  112. package/dist/host/claude/rules/documentation.md +26 -11
  113. package/dist/host/claude/rules/names.md +9 -4
  114. package/dist/host/claude/rules/patterns.md +6 -7
  115. package/dist/host/claude/rules/portability.md +2 -3
  116. package/dist/host/claude/rules/quality.md +4 -5
  117. package/dist/host/claude/rules/styles.md +2 -2
  118. package/dist/host/claude/rules/tests.md +57 -114
  119. package/dist/host/claude/rules/typescript.md +8 -10
  120. package/dist/host/claude/rules/workspace.md +21 -19
  121. package/dist/host/claude/rules/writing.md +3 -3
  122. package/dist/host/claude/skills/orkestrel-dispatch/SKILL.md +1 -1
  123. package/dist/host/claude/skills/orkestrel-harden/SKILL.md +1 -1
  124. package/dist/host/claude/skills/orkestrel-parallel/SKILL.md +1 -1
  125. package/dist/host/claude/skills/orkestrel-publish/SKILL.md +1 -1
  126. package/dist/host/claude/skills/orkestrel-scout/SKILL.md +1 -1
  127. package/dist/host/codex/agents/analyst.toml +9 -3
  128. package/dist/host/codex/agents/astra.toml +3 -4
  129. package/dist/host/codex/agents/builder.toml +1 -1
  130. package/dist/host/codex/agents/opus.toml +3 -4
  131. package/dist/host/codex/agents/orkestrel.toml +1 -1
  132. package/dist/host/codex/agents/researcher.toml +2 -2
  133. package/dist/host/codex/agents/verifier.toml +1 -1
  134. package/dist/host/codex/config.toml +3 -3
  135. package/dist/host/configs/helpers.ts +21 -1
  136. package/dist/host/configs/policy.ts +405 -16
  137. package/dist/host/cursor/rules/orchestration.mdc +1 -0
  138. package/dist/host/dotfiles/oxlintrc.json +141 -5
  139. package/dist/host/guides/agent.md +111 -134
  140. package/dist/host/guides/capture.md +801 -0
  141. package/dist/host/guides/probe.md +27 -8
  142. package/dist/host/guides/scaffold.md +152 -19
  143. package/dist/host/guides/study.md +534 -440
  144. package/dist/host/guides/test.md +111 -44
  145. package/dist/host/manifest.json +199 -242
  146. package/dist/host/tests/config.test.ts +1558 -219
  147. package/dist/host/tests/policy.test.ts +127 -1
  148. package/dist/host/tests/setupPolicy.ts +1389 -39
  149. package/dist/src/core/index.cjs +239 -87
  150. package/dist/src/core/index.cjs.map +1 -1
  151. package/dist/src/core/index.d.cts +123 -39
  152. package/dist/src/core/index.d.ts +123 -39
  153. package/dist/src/core/index.js +234 -86
  154. package/dist/src/core/index.js.map +1 -1
  155. package/dist/src/server/index.cjs +20 -27
  156. package/dist/src/server/index.cjs.map +1 -1
  157. package/dist/src/server/index.js +21 -28
  158. package/dist/src/server/index.js.map +1 -1
  159. package/package.json +11 -11
  160. package/dist/agents/skills/orkestrel-dispatch/scripts/helpers.js +0 -283
  161. package/dist/host/agents/skills/orkestrel-dispatch/references/bench.md +0 -25
  162. package/dist/host/agents/skills/orkestrel-dispatch/scripts/helpers.ts +0 -333
  163. package/dist/host/agents/skills/orkestrel-parallel/references/harnesses.md +0 -19
  164. package/dist/host/guides/supervisor.md +0 -4890
@@ -0,0 +1,389 @@
1
+ // Check a Vitest JSON report against named red and green cases. Run from the checkout root:
2
+ // node .agents/skills/orkestrel-parallel/scripts/cases.ts --report FILE.json [--passed NAME ...] [--failed NAME ...]
3
+ // The report is Vitest's `--reporter=json` output. A name matches every case whose full name (its
4
+ // `describe` titles and its own title, joined by spaces) or whose own title equals it exactly; a name
5
+ // that opens with `--` cannot be given. Each `--passed NAME` requires every case it matches to have
6
+ // passed and each `--failed NAME` requires every one to have failed; a name that matches no case is
7
+ // missing. A failed case that no name matches is refused, so a run that fails more than the request
8
+ // expected reads as a difference. A test file that failed with no failed case (it did not collect,
9
+ // or a hook threw) is an unnamed failure named by its file, and a report whose `success` is false
10
+ // while no case and no file failed is a difference too. Name no case to require a report with no
11
+ // failure. Both flags repeat and a name may not be given to both. The script prints one summary
12
+ // line, one line per difference, and `ok` or `differences=N`. The script refuses a flag it does not
13
+ // read, the `--flag=value` form included, and names an unknown flag before a flag given with no
14
+ // value. Exit 0 when the report matches the names, 3 when it does not, 2 when the report cannot be
15
+ // read as a Vitest report, 64 on usage.
16
+ import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs'
17
+ import { relative } from 'node:path'
18
+
19
+ /** Lists the flags that take a value. */
20
+ export const VALUE_FLAGS: readonly string[] = ['--report', '--passed', '--failed']
21
+
22
+ /** Lists every flag the script reads, value flags and switches alike. */
23
+ export const FLAGS: readonly string[] = VALUE_FLAGS
24
+
25
+ /** Holds the usage line printed when a call names no report. */
26
+ export const USAGE =
27
+ 'usage: cases.ts --report FILE.json [--passed NAME ...] [--failed NAME ...]; --passed and --failed repeat'
28
+
29
+ /** Describes one test case a report holds. */
30
+ export interface Case {
31
+ readonly file: string
32
+ readonly fullName: string
33
+ readonly title: string
34
+ readonly status: string
35
+ }
36
+
37
+ /**
38
+ * Names the value flags a caller gave with no value, at any occurrence.
39
+ *
40
+ * @param argv - The arguments after the script path.
41
+ * @param names - The flags that take a value.
42
+ * @returns Each named flag that appears last or is followed by another flag, in argument order.
43
+ */
44
+ export function readMissingFlags(
45
+ argv: readonly string[],
46
+ names: readonly string[],
47
+ ): readonly string[] {
48
+ return names.filter((name) =>
49
+ argv.some((argument, index) => argument === name && isMissingValue(argv, index)),
50
+ )
51
+ }
52
+
53
+ /**
54
+ * Checks whether the argument after a flag is absent or is another flag.
55
+ *
56
+ * @param argv - The arguments after the script path.
57
+ * @param index - The position of the flag.
58
+ * @returns True when no value follows the flag; false otherwise.
59
+ */
60
+ export function isMissingValue(argv: readonly string[], index: number): boolean {
61
+ const value = argv[index + 1]
62
+ return value === undefined || value.startsWith('--')
63
+ }
64
+
65
+ /**
66
+ * Names the flags a caller gave that the script does not read.
67
+ *
68
+ * @remarks
69
+ * A `--flag=value` argument is unknown, because the script reads no such form. A bare `--` ends the
70
+ * scan.
71
+ *
72
+ * @param argv - The arguments after the script path.
73
+ * @param flags - Every flag the script reads, value flags and switches alike.
74
+ * @returns Each argument that opens with `--` and is not a read flag, in argument order.
75
+ */
76
+ export function readUnknownFlags(
77
+ argv: readonly string[],
78
+ flags: readonly string[],
79
+ ): readonly string[] {
80
+ const end = argv.indexOf('--')
81
+ return (end === -1 ? argv : argv.slice(0, end)).filter(
82
+ (argument) => argument.startsWith('--') && !flags.includes(argument),
83
+ )
84
+ }
85
+
86
+ /**
87
+ * Reads every value that follows a repeatable flag.
88
+ *
89
+ * @param argv - The arguments after the script path.
90
+ * @param name - The flag, such as `--passed`.
91
+ * @returns The values in order, empty when the flag never appears.
92
+ */
93
+ export function readOptions(argv: readonly string[], name: string): readonly string[] {
94
+ return argv.flatMap((argument, index) => {
95
+ const value = argv[index + 1]
96
+ return argument === name && value !== undefined && !value.startsWith('--') ? [value] : []
97
+ })
98
+ }
99
+
100
+ /**
101
+ * Reads the arguments that are neither a flag nor the value of a value flag.
102
+ *
103
+ * @param argv - The arguments after the script path.
104
+ * @param valueFlags - The flags that take a value.
105
+ * @returns The positional arguments in order.
106
+ */
107
+ export function readPositionals(
108
+ argv: readonly string[],
109
+ valueFlags: readonly string[],
110
+ ): readonly string[] {
111
+ return argv.filter(
112
+ (argument, index) => !argument.startsWith('--') && !valueFlags.includes(argv[index - 1] ?? ''),
113
+ )
114
+ }
115
+
116
+ /**
117
+ * Checks whether a value is a non-null, non-array object.
118
+ *
119
+ * @param value - The value to inspect.
120
+ * @returns True if the value is an object other than null or an array; false otherwise.
121
+ */
122
+ export function isRecord(value: unknown): value is Record<string, unknown> {
123
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
124
+ }
125
+
126
+ /**
127
+ * Quotes an untrusted text for a diagnostic, JSON-encoded and cut to a bounded length.
128
+ *
129
+ * @param text - The text to quote.
130
+ * @returns The JSON string literal of the text, ending in an ellipsis inside the quotes when cut.
131
+ */
132
+ export function quote(text: string): string {
133
+ return JSON.stringify(text.length > 120 ? `${text.slice(0, 120)}…` : text)
134
+ }
135
+
136
+ /**
137
+ * Reads one assertion result of a report as a case.
138
+ *
139
+ * @param file - The test file the result belongs to.
140
+ * @param result - The parsed JSON value of one assertion result.
141
+ * @returns The case as a one-member list, or an empty list when the result lacks a title or status.
142
+ */
143
+ export function readCase(file: string, result: unknown): readonly Case[] {
144
+ if (!isRecord(result)) return []
145
+ const { title, status, fullName, ancestorTitles } = result
146
+ if (typeof title !== 'string' || typeof status !== 'string') return []
147
+ const ancestors = Array.isArray(ancestorTitles)
148
+ ? ancestorTitles.filter((member) => typeof member === 'string')
149
+ : []
150
+ return [
151
+ {
152
+ file,
153
+ title,
154
+ status,
155
+ fullName: typeof fullName === 'string' ? fullName : [...ancestors, title].join(' '),
156
+ },
157
+ ]
158
+ }
159
+
160
+ /**
161
+ * Reads the cases of a Vitest JSON report.
162
+ *
163
+ * @param report - The parsed JSON value of the report.
164
+ * @returns Every case in file order, or undefined when the value is not a Vitest report: an object
165
+ * whose `testResults` is an array.
166
+ */
167
+ export function readCases(report: unknown): readonly Case[] | undefined {
168
+ if (!isRecord(report) || !Array.isArray(report.testResults)) return undefined
169
+ return report.testResults.flatMap((suite): readonly Case[] => {
170
+ if (!isRecord(suite) || !Array.isArray(suite.assertionResults)) return []
171
+ const file = typeof suite.name === 'string' ? suite.name : ''
172
+ return suite.assertionResults.flatMap((result) => readCase(file, result))
173
+ })
174
+ }
175
+
176
+ /**
177
+ * Reads the test files of a report that failed with no failed case.
178
+ *
179
+ * @param report - The parsed JSON value of the report.
180
+ * @returns The `name` of each `testResults` entry whose `status` is `failed` and whose assertion
181
+ * results hold no failed case, in file order; an empty text stands for an entry with no name.
182
+ */
183
+ export function readFailedFiles(report: unknown): readonly string[] {
184
+ if (!isRecord(report) || !Array.isArray(report.testResults)) return []
185
+ return report.testResults.flatMap((suite): readonly string[] => {
186
+ if (!isRecord(suite) || suite.status !== 'failed') return []
187
+ const results = Array.isArray(suite.assertionResults) ? suite.assertionResults : []
188
+ if (results.some((result) => isRecord(result) && result.status === 'failed')) return []
189
+ return [typeof suite.name === 'string' ? suite.name : '']
190
+ })
191
+ }
192
+
193
+ /**
194
+ * Checks whether a report states that the run did not succeed.
195
+ *
196
+ * @param report - The parsed JSON value of the report.
197
+ * @returns True when the report is an object whose `success` is `false`; false otherwise.
198
+ */
199
+ export function isUnsuccessful(report: unknown): boolean {
200
+ return isRecord(report) && report.success === false
201
+ }
202
+
203
+ /**
204
+ * Checks whether a name matches a case.
205
+ *
206
+ * @param entry - The case.
207
+ * @param name - The name from `--passed` or `--failed`.
208
+ * @returns True when the name equals the case's full name or its own title; false otherwise.
209
+ */
210
+ export function matchesCase(entry: Case, name: string): boolean {
211
+ return entry.fullName === name || entry.title === name
212
+ }
213
+
214
+ /**
215
+ * Judges the cases of a report against the names a caller gave.
216
+ *
217
+ * @param cases - The report's cases.
218
+ * @param passed - The names that must have passed.
219
+ * @param failed - The names that must have failed.
220
+ * @param base - The directory the file names in each difference are written relative to.
221
+ * @returns Each difference in order: missing names, wrong statuses, then failed cases no name
222
+ * matches; empty when the report matches.
223
+ */
224
+ export function judgeCases(
225
+ cases: readonly Case[],
226
+ passed: readonly string[],
227
+ failed: readonly string[],
228
+ base: string,
229
+ ): readonly string[] {
230
+ const expectations = [
231
+ ...passed.map((name) => ({ name, status: 'passed' })),
232
+ ...failed.map((name) => ({ name, status: 'failed' })),
233
+ ]
234
+ const named = expectations.flatMap((expectation) => {
235
+ const matches = cases.filter((entry) => matchesCase(entry, expectation.name))
236
+ if (matches.length === 0) return [`missing: ${quote(expectation.name)} matches no case`]
237
+ return matches
238
+ .filter((entry) => entry.status !== expectation.status)
239
+ .map(
240
+ (entry) =>
241
+ `status: ${quote(entry.fullName)} is ${entry.status}, expected ${expectation.status}${describeFile(base, entry.file)}`,
242
+ )
243
+ })
244
+ const unnamed = cases
245
+ .filter(
246
+ (entry) =>
247
+ entry.status === 'failed' &&
248
+ !expectations.some((expectation) => matchesCase(entry, expectation.name)),
249
+ )
250
+ .map((entry) => `unnamed failure: ${quote(entry.fullName)}${describeFile(base, entry.file)}`)
251
+ return [...named, ...unnamed]
252
+ }
253
+
254
+ /**
255
+ * Judges the failures a report holds outside its cases.
256
+ *
257
+ * @param cases - The report's cases.
258
+ * @param failedFiles - The test files that failed with no failed case, from {@link readFailedFiles}.
259
+ * @param unsuccessful - Whether the report states that the run did not succeed.
260
+ * @param base - The directory the file names in each difference are written relative to.
261
+ * @returns One unnamed failure per failed file; or, when no file and no case failed yet the report
262
+ * is not successful, one unnamed failure for the report; empty otherwise.
263
+ */
264
+ export function judgeFailures(
265
+ cases: readonly Case[],
266
+ failedFiles: readonly string[],
267
+ unsuccessful: boolean,
268
+ base: string,
269
+ ): readonly string[] {
270
+ if (failedFiles.length > 0) {
271
+ return failedFiles.map(
272
+ (file) =>
273
+ `unnamed failure: ${file === '' ? 'a test file' : quote(relativeFile(base, file))} failed with no failed case`,
274
+ )
275
+ }
276
+ return unsuccessful && cases.every((entry) => entry.status !== 'failed')
277
+ ? ['unnamed failure: the report is not successful, yet no case and no file in it failed']
278
+ : []
279
+ }
280
+
281
+ /**
282
+ * Writes a test file path relative to a directory.
283
+ *
284
+ * @param base - The directory the path is written relative to.
285
+ * @param file - The test file path from the report.
286
+ * @returns The relative path with forward slashes.
287
+ */
288
+ export function relativeFile(base: string, file: string): string {
289
+ return relative(base, file).split('\\').join('/')
290
+ }
291
+
292
+ /**
293
+ * Describes the file a case belongs to, written relative to a directory.
294
+ *
295
+ * @param base - The directory the path is written relative to.
296
+ * @param file - The test file path from the report.
297
+ * @returns ` in <path>` with forward slashes, or an empty text when the report names no file.
298
+ */
299
+ export function describeFile(base: string, file: string): string {
300
+ return file === '' ? '' : ` in ${relativeFile(base, file)}`
301
+ }
302
+
303
+ /**
304
+ * Counts the cases that carry a status.
305
+ *
306
+ * @param cases - The report's cases.
307
+ * @param status - The status, such as `passed`.
308
+ * @returns How many cases have exactly that status.
309
+ */
310
+ export function countStatus(cases: readonly Case[], status: string): number {
311
+ return cases.filter((entry) => entry.status === status).length
312
+ }
313
+
314
+ /**
315
+ * Reads a report file as JSON.
316
+ *
317
+ * @param path - The report file.
318
+ * @returns The parsed value, or undefined when the file is not readable JSON.
319
+ */
320
+ export function readReport(path: string): unknown {
321
+ try {
322
+ return JSON.parse(readFileSync(path, 'utf8'))
323
+ } catch {
324
+ return undefined
325
+ }
326
+ }
327
+
328
+ /**
329
+ * Runs the script on its arguments.
330
+ *
331
+ * @param argv - The arguments after the script path.
332
+ * @returns The exit code: 0 when the report matches the names, 3 when it does not, 2 when the report
333
+ * cannot be read as a Vitest report, 64 on usage.
334
+ */
335
+ export function main(argv: readonly string[]): number {
336
+ const unknown = readUnknownFlags(argv, FLAGS)
337
+ if (unknown.length > 0) {
338
+ console.error(`cases: ${unknown.join(', ')} is not a flag this script reads; ${USAGE}`)
339
+ return 64
340
+ }
341
+ const missing = readMissingFlags(argv, VALUE_FLAGS)
342
+ if (missing.length > 0) {
343
+ console.error(`cases: ${missing.join(', ')} given with no value`)
344
+ return 64
345
+ }
346
+ const reports = readOptions(argv, '--report')
347
+ const path = reports[0]
348
+ const passed = readOptions(argv, '--passed')
349
+ const failed = readOptions(argv, '--failed')
350
+ if (path === undefined || readPositionals(argv, VALUE_FLAGS).length > 0) {
351
+ console.error(USAGE)
352
+ return 64
353
+ }
354
+ const both = passed.filter((name) => failed.includes(name))
355
+ if (both.length > 0) {
356
+ console.error(
357
+ `cases: ${both.map(quote).join(', ')} given to both --passed and --failed; ${USAGE}`,
358
+ )
359
+ return 64
360
+ }
361
+ if (!existsSync(path) || !statSync(path).isFile()) {
362
+ console.error(`cases: ${quote(path)} is not a readable file`)
363
+ return 2
364
+ }
365
+ const report = readReport(path)
366
+ const cases = readCases(report)
367
+ if (cases === undefined) {
368
+ console.error(
369
+ `cases: ${quote(path)} is not a Vitest JSON report; it needs a "testResults" array (run vitest with --reporter=json)`,
370
+ )
371
+ return 2
372
+ }
373
+ const differences = [
374
+ ...judgeCases(cases, passed, failed, process.cwd()),
375
+ ...judgeFailures(cases, readFailedFiles(report), isUnsuccessful(report), process.cwd()),
376
+ ]
377
+ const green = countStatus(cases, 'passed')
378
+ const red = countStatus(cases, 'failed')
379
+ console.log(
380
+ `cases total=${String(cases.length)} passed=${String(green)} failed=${String(red)} other=${String(cases.length - green - red)}`,
381
+ )
382
+ for (const difference of differences) console.log(difference)
383
+ console.log(differences.length === 0 ? 'ok' : `differences=${String(differences.length)}`)
384
+ return differences.length === 0 ? 0 : 3
385
+ }
386
+
387
+ if (process.argv[1] !== undefined && realpathSync(process.argv[1]) === import.meta.filename) {
388
+ process.exitCode = main(process.argv.slice(2))
389
+ }
@@ -24,8 +24,8 @@ supposed to produce it. Source-reading review passes a component that renders no
24
24
  accessibility snapshot, and an interaction log.
25
25
  - Source is corroboration for a mechanism, never the proof that the surface shows it.
26
26
  - A claim the portfolio cannot show is unproven, not passed. Say so.
27
- - The same law governs an externally driven surface: a compatibility claim is proven by
28
- driving it with a representative real client, not by its own tests.
27
+ - Prove an externally driven surface's compatibility claim per the `orkestrel-harden` skill's
28
+ Production hardening → Use real implementations.
29
29
 
30
30
  ## Select the scope
31
31
 
@@ -36,29 +36,25 @@ Choose the smallest scope that satisfies the request:
36
36
  re-verdict on the repaired items → independent gates.
37
37
 
38
38
  A narrow verdict request does not authorize fixes. A polish request authorizes fixing every
39
- confirmed finding in scope and rebuilding the harness gaps the verdicts expose. A second round
40
- over the same surface needs an instruction from the user.
39
+ confirmed finding in scope and rebuilding the harness gaps the verdicts expose.
41
40
 
42
41
  ## Execute the round
43
42
 
44
43
  1. **Build the portfolio.** Produce the full evidence set with the harness reference, then
45
- eyeball every artifact yourself before spending a verdict round on it. An unexamined
46
- portfolio buys harness bugs at verdict prices.
44
+ eyeball every artifact yourself before spending a verdict round on it.
47
45
  2. **Seed candidates.** Turn your own mid-integration observations into numbered
48
- confirm-or-refute candidates inside the verdict brief. Observations that stay in your
49
- head are neither evidence nor findings.
46
+ confirm-or-refute candidates inside the verdict brief.
50
47
  3. **Take independent verdicts** on the SAME portfolio, in the shape
51
- § "Return the fixed verdict shape" fixes. The
52
- lanes are subjective design fit; objective state truth; and mechanical inventory of
53
- copy, classes, icons, and accessibility attributes. This is the surface variant of the
54
- review in `.agents/orchestration.md` § Size gate, so its rules bind: each lane is a fresh
55
- subagent with a clean context, blind to the others until reconciliation, run once, and no
56
- lane is dropped because an engine is dark. A narrow verdict request runs the selected lane once
57
- and stops.
48
+ § "Return the fixed verdict shape" fixes. Judgment runs as the two lanes
49
+ `.agents/orchestration.md` § Parallelism names: subjective design fit and objective state
50
+ truth. The mechanical inventory of copy, classes, icons, and accessibility attributes runs as a
51
+ `checker` lane. This is the surface variant of the review in `.agents/orchestration.md`
52
+ § Size gate, so its rules bind: each judgment lane is a fresh subagent with a clean context,
53
+ blind to the other until reconciliation, run once, and no lane is dropped because an engine is
54
+ dark. A narrow verdict request runs the selected lane once and stops.
58
55
  4. **Reconcile.** Confirmed findings get carriers; refuted findings close on the record with
59
56
  the evidence that refuted them; conflicts get a dated ruling. Then walk the reconciled
60
- list once and name the fix-brief item carrying each finding — a finding with no carrier
61
- is a dropped finding, and it will cost the next round.
57
+ list once and name the fix-brief item carrying each finding.
62
58
  5. **Dispatch serialized fix units,** objective lane first because it changes the ground
63
59
  truth the subjective lane reads. Each unit records the failing proof before the
64
60
  repair — the exact command and its red count — and the same command's green count
@@ -87,7 +83,7 @@ over the same surface needs an instruction from the user.
87
83
 
88
84
  ## Return the fixed verdict shape
89
85
 
90
- Free-form verdicts cost one reconciliation pass each. Every verdict returns:
86
+ Every verdict returns:
91
87
 
92
88
  - one line per item, in the brief's numbering:
93
89
  - `RENDERED-PROVEN(<capture>)` — the artifact shows it;
@@ -101,8 +97,8 @@ Triage it against the capture reference before it becomes a finding.
101
97
 
102
98
  ## Accept the result
103
99
 
104
- Do not accept a source-only verdict, a self-audited fix unit, a round without a terminal
105
- line, or a green claim from the engine that wrote the code. Completion requires:
100
+ Accept per `.agents/orchestration.md` § Acceptance. Refuse a source-only verdict, a self-audited
101
+ fix unit, and a round without a terminal line. Completion requires:
106
102
 
107
103
  - every confirmed finding fixed or explicitly ruled out of scope on the record;
108
104
  - a final portfolio whose captures show the accepted state;
@@ -17,9 +17,7 @@ journey-generated and part spawned.
17
17
  Apply this section to a spawned harness only.
18
18
 
19
19
  - Write the harness as one self-contained script that spawns its own children, waits for readiness,
20
- does the capture, and kills them before returning.
21
- - Never leave a child running across calls or expect one to survive its parent. A process started
22
- inside one tool call dies with that call's process group.
20
+ does the capture, and kills them before returning. Spawn, capture, and tear down in one call.
23
21
  - Give every child a pinned working directory. A process that resolves assets, configuration, or
24
22
  fixtures relative to the current directory dies silently when launched from elsewhere.
25
23
  - Pipe child standard error somewhere readable and print it on failure.
@@ -43,33 +41,26 @@ and `orkestrel-journey` fixes how it enters and what it may reach past.
43
41
 
44
42
  ## Capture the full portfolio
45
43
 
46
- Produce every artifact in the following table each round, for every scenario in scope. The Source
47
- column names what produces the artifact in the journey run; a spawned harness produces each one
48
- itself.
49
-
50
- | Artifact | Source in the journey run | What the artifact must show |
51
- | ---------------------- | ------------------------------------ | ------------------------------------------------------------------------------------- |
52
- | Viewport captures | `place` across the declared variants | Every breakpoint the surface declares, never one convenient size |
53
- | Theme captures | `place` across the declared variants | Every theme the surface ships, at every declared viewport |
54
- | Accessibility snapshot | `describeTree` and `describeFocus` | The rendered roles, names, and states, and the focus order the walk took |
55
- | Interaction log | The journal's `steps` | Each interaction, its trigger, and the result observed after it |
56
- | Console and error log | The journal's `output` | Everything the page emitted, including an uncaught error |
57
- | Statechart outcome | The harness after a play-all run | The terminal status, the passed, failed, and total tallies, and each row's own result |
58
-
59
- - Require the statechart outcome of every surface that declares the statechart family, and omit the
60
- row only where the surface declares none.
61
- - Read the journey run's per-variant written artifact for the accessibility snapshot and the logs,
62
- and cite the statechart harness by its deep link where a lane must watch the widget move rather
63
- than read a still of it. `orkestrel-journey` fixes what each holds and what each is named
64
- for.
44
+ Produce each of the following every round, for every scenario in scope:
45
+
46
+ - Viewport captures at every breakpoint the surface declares, and theme captures for every theme it ships at every declared viewport. In the journey run, `place` across the declared variants produces them.
47
+ - The rendered artifact per variant, composed as the `orkestrel-journey` skill's Routing a question → The rendered artifact fixes: the accessibility snapshot, the focus order, the interaction log, and the console and error log.
48
+ - The statechart outcome of every surface that declares the statechart family: the terminal status, the passed, failed, and total tallies, and each row's own result. The harness produces it after a play-all run; cite its deep link where a lane must watch the widget move rather than read a still of it.
49
+
50
+ A spawned harness produces each artifact itself.
51
+
65
52
  - Shoot the whole surface before selecting or focusing anything inside it. A capture taken after a
66
53
  selection reports a duplicate or highlighted artifact that does not exist.
67
54
  - Start a keyboard walk from a neutral state, never from an already-focused control, or the log will
68
55
  report a broken order no person meets.
69
56
  - Name a spawned harness's artifacts so a verdict cites one exactly: scenario, viewport, theme,
70
57
  step. The journey run's filename law already does this.
71
- - Keep the artifacts of each round beside its verdicts. A round judged against the previous round's
72
- captures is not a round.
58
+ - Keep the artifacts of each round beside its verdicts.
59
+ - For a journey-generated portfolio, record the capture manifest snapshot the round judges beside
60
+ its verdicts. Require every judged frame to be current, with its recorded source digests equal
61
+ to the tree's; recapture every stale judged frame before dispatch. Apply this rule to sittings
62
+ and checkpoints alike. Admit
63
+ frames assembled from several runs only when every judged frame is current.
73
64
 
74
65
  ## Preflight before spending a round
75
66
 
@@ -84,8 +75,7 @@ Confirm each of the following:
84
75
  to the total;
85
76
  - nothing in the console log indicates the harness, rather than the surface, failed.
86
77
 
87
- Repair a portfolio that fails preflight before dispatching it. Never spend a verdict round
88
- discovering a harness defect.
78
+ Repair a portfolio that fails preflight before dispatching it.
89
79
 
90
80
  ## Triage missing evidence to the harness first
91
81
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: orkestrel-publish
3
- description: Run an Orkestrel release from layer order to registry confirmation. Use when the user asks to publish a package, to run a fleet-wide release wave, or to recover a release that stalled at the npm approval, and follow it for the per-repository visit, the bump ruling, the layer preparation, the login approval, and the five-minute upload window.
3
+ description: Run an Orkestrel release from layer order to registry confirmation, or adopt a published scaffold release in one consumer. Use when the user asks to publish a package, to run a fleet-wide release wave, to recover a release that stalled at the npm approval, or to adopt a published scaffold release in one repository, and follow it for the per-repository visit, the bump ruling, the layer preparation, the login approval, the upload approval, and the adoption.
4
4
  ---
5
5
 
6
6
  # Publish an Orkestrel release
@@ -14,9 +14,10 @@ description: Run an Orkestrel release from layer order to registry confirmation.
14
14
  here.
15
15
  2. The reference the moment needs: [wave.md](references/wave.md) before visiting a repository,
16
16
  ruling on a bump, or preparing a layer; [window.md](references/window.md) before running
17
- `npm login` or any upload.
17
+ `npm login` or any upload; [adopt.md](references/adopt.md) before one consumer adopts a
18
+ published scaffold release outside a wave.
18
19
  3. The live evidence: the registry's packument for every package in the round, each target's
19
- manifest, and the catalog table the contract names as the layer order.
20
+ manifest, and the catalog table `references/release.md` names as the layer order.
20
21
 
21
22
  ## Scripts
22
23
 
@@ -26,35 +27,20 @@ description: Run an Orkestrel release from layer order to registry confirmation.
26
27
  | `scripts/pins.ts` | `--version PRIOR [--range PRIOR_RANGE ...] [--paths src,tests] [--json]`, run from the package root: lists every line under the paths carrying the prior version literal or a prior range literal, once per line, sweeping a one-comparator range such as `^8.3.0` for its bare version in any form. Exit 0 with no hits, 3 with hits, 64 on usage (no literal, or a flag with no value). |
27
28
  | `scripts/swap.ts` | One mode per run, from any directory: `--pin --consumer DIR --source DIR [--source DIR ...] [--override NAME ...] [--no-build] [--dry-run] [--json]` refuses a source whose packed manifest names a local dependency spec in any dependency field (a `file:` or `link:` prefix, a path start of `.`, `~/`, `/`, `\`, or a drive letter, or a protocol-free archive name ending in `.tgz`, `.tar.gz`, or `.tar` that is not a `user/repo` shorthand), builds and packs each source into `<consumer>/tmp/tarballs/` under a name carrying its SHA-256, records each replaced range in the ledger `pins.json` there, the record of which build the consumer runs, writes each tarball's absolute `file:` spec and each `--override` (repeatable, like `--source`) where oxfmt sorts it, installs, and proves that every pinned source, an override-only one included, is installed with the source's bytes; `--restore --consumer DIR [--package NAME ...] [--json]` puts back each recorded range and override, keeps an `overrides` block that existed before the pin, installs, proves the restored copy, and lists the re-pin arguments, and refuses with 64, before it writes anything, a ledger whose package or override keys are not package names or whose removal would leave `node_modules`; `--status --consumer DIR [--json]` writes nothing and reports drift between the ledger, the manifest, the tarballs, the installed bytes, and the source commits, a manifest spec holding its tarball only when it names the ledger archive's location. `--pin` and `--restore` write a receipt under `tmp/units/`. Exit 0 on success, 1 when a command or a proof failed, 3 when `--status` reports drift, 64 on usage. |
28
29
  | `scripts/wave.ts` | `--visit [--target DIR] [--offline] [--regenerate] [--from STEP] [--to STEP] [--prior VERSION] [--dry-run] [--json] [--out FILE]` runs one repository's visit in the order [wave.md](references/wave.md) § Visit a repository fixes (pin, commit, overwrite, verify, install, pins, format, record, gates, compare), stops at the first failing step, journals each executed step's full output under the target's `tmp/units/wave/` when the work tree ignores `tmp/` and names the journal in its row, and prints each step's exit (a failed step names the `code: message` refusal or the `note` a JSON verb writes on stdout, else the last stderr lines), the bump ruling (the rebuilt dist against the published tarball, and the final runtime dependency set against the published manifest), and a row for `.orkestrel/release.md`; `--prior` names the version a pre-ruled bump replaced so the self-pin sweep finds it; `--plan` prints the packages by layer from the catalog table. An install that fails `ERESOLVE` names the rerun command, and `--regenerate` replaces the lockfile and `node_modules` from the manifest under the refusals and proof [wave.md](references/wave.md) § Regenerate a lock the install cannot move states; `record` commits the overwrite's writes as `Overwrite with scaffold VERSION`. Exit 0 when every step passed, 1 when one failed, 2 when a reading failed, 64 on usage, including `--regenerate` with `--plan`. |
29
- | `scripts/window.ts` | `--whoami [--wait SECONDS]` reads or polls `npm whoami`; `--login` prints the operator's login command and polls `whoami` until it answers; `--publish DIR... --otp CODE` refuses a batch whose nearest `.git` entry is a file, including ancestor entries, with exit 3 before contacting npm; it names a linked worktree's primary clone or identifies a submodule, then uploads back to back with `npm publish --ignore-scripts --otp`, journals each upload under `tmp/units/`, stops at the first refusal naming where to resume, and confirms each accepted version against the registry; `--confirm NAME@VERSION...` re-reads the registry until it serves the version. Exit 0 on success, 3 when not. |
30
+ | `scripts/window.ts` | `--whoami [--wait SECONDS]` reads or polls `npm whoami`; `--login` prints the operator's login command and polls `whoami` until it answers; `--publish DIR... --otp CODE` refuses a batch whose nearest `.git` entry is a file, including ancestor entries, with exit 3 before contacting npm; it names a linked worktree's primary clone or identifies a submodule, then uploads back to back with `npm publish --ignore-scripts --otp`, journals each upload under `tmp/units/`, stops at the first refusal naming where to resume, and confirms each accepted version against the registry; `--confirm NAME@VERSION...` re-reads the registry until it serves the version. Exit 0 on success, 3 when not, 64 on usage. |
30
31
 
31
- The user's current instruction wins. `references/release.md` owns the credential
32
- and authorization law; nothing here weakens it.
33
-
34
- ## The boundary with the contract
35
-
36
- `references/release.md` binds every release, and this skill does not
37
- repeat it. Read that section for the credential and approval law, the long-running-command
38
- binding, the serialization of uploads, the tarball swap that serves a consumer whose dependency
39
- has not published, what a bump obliges downstream, and where the layer order comes from.
40
-
41
- This skill carries what an operator needs while a release is running: the per-repository visit,
42
- the bump ruling, the preparation order, the login and approval mechanics, and the window.
43
-
44
- Where the skill and the contract disagree, the contract wins. Report the drift instead of
45
- following the skill.
32
+ The user's current instruction wins. `references/release.md` binds every release: the credential and authorization law, the upload serialization, the tarball swap, what a bump obliges downstream, and the layer order. Nothing here weakens it. Where this skill disagrees with it, report the drift instead of following the skill.
46
33
 
47
34
  ## Run the release
48
35
 
49
36
  1. **Name the round.** List the packages the release covers, and group them into layers with
50
- `node .agents/skills/orkestrel-publish/scripts/wave.ts --plan`, which reads the contract's
51
- layer order from the catalog table.
37
+ `node .agents/skills/orkestrel-publish/scripts/wave.ts --plan`, which reads the layer order
38
+ from the catalog table.
52
39
  2. **Take the registry evidence.** Read what the registry serves for every package in the round.
53
40
  Derive each pin from that reading, never from a local manifest.
54
41
  3. **Visit each repository.** Run `node .agents/skills/orkestrel-publish/scripts/wave.ts --visit --target <repository>`,
55
42
  which runs the visit in [wave.md](references/wave.md) in its stated order and stops at the first
56
- failing step; repair that step by hand and re-run from it with `--from`; for an `ERESOLVE`, follow [wave.md](references/wave.md) § Regenerate a lock the install cannot move. Run visits in parallel
57
- slices of disjoint repositories, each slice serial inside itself.
43
+ failing step; repair that step by hand and re-run from it with `--from`; for an `ERESOLVE`, follow [wave.md](references/wave.md) § Regenerate a lock the install cannot move.
58
44
  4. **Rule on each package's bump.** Take the visit's comparison and range reading; repeat `compare.ts` only after the artifact changes or an unanswered result. Apply the triggers in [wave.md](references/wave.md) § Rule on
59
45
  the bump; `references/release.md` § What a bump obliges owns the blast radius.
60
46
  5. **Prepare the whole layer before authenticating**, in the order [wave.md](references/wave.md)
@@ -69,8 +55,8 @@ following the skill.
69
55
  8. **Close the layer from the registry, then prepare the next**, per [wave.md](references/wave.md)
70
56
  § Prepare a layer.
71
57
 
72
- Run that sequence for every layer, from the registry reading to the registry close. Refresh the
73
- registry evidence between layers rather than carrying the previous round's reading forward.
58
+ Run that sequence for every layer, from the registry reading to the registry close;
59
+ [wave.md](references/wave.md) § Refresh the registry between layers owns the refresh.
74
60
 
75
61
  ## Accept the release
76
62
 
@@ -81,7 +67,7 @@ Completion requires:
81
67
  - every obligation `references/release.md` § What a bump obliges places on a published package's dependents has closed as
82
68
  that section requires;
83
69
  - every tarball swap is restored per `references/release.md` § Fix a dependency before it publishes, and no target
84
- repository is left holding an uncommitted bump or an unpushed commit;
70
+ repository is left holding an uncommitted bump or an unpushed commit the user has not held;
85
71
  - every gate that proved a package ran outside the window and against the artifact that shipped;
86
72
  - every gate red at a package's baseline for a cause `ROADMAP.md` already carries is recorded as a
87
73
  standing reading beside the package's row with its carrier, and the release report names it. A
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: 'Publish Orkestrel Release'
3
- short_description: 'Run a release in layer order through the npm approval window'
4
- default_prompt: 'Use $orkestrel-publish to run the release - layer order, per-repository visit, bump ruling, layer preparation, login approval, upload window, and registry confirmation.'
3
+ short_description: 'Run a release in layer order through the npm approval, or adopt a scaffold release in one consumer'
4
+ default_prompt: 'Use $orkestrel-publish to run the release - layer order, per-repository visit, bump ruling, layer preparation, login approval, upload approval, and registry confirmation - or to adopt a published scaffold release in one consumer.'