@orkestrel/scaffold 0.0.76 → 0.0.78

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 (154) hide show
  1. package/dist/agents/skills/orkestrel-dispatch/scripts/bench.js +204 -0
  2. package/dist/agents/skills/orkestrel-dispatch/scripts/brief.js +102 -0
  3. package/dist/agents/skills/orkestrel-dispatch/scripts/cite.js +95 -0
  4. package/dist/agents/skills/orkestrel-dispatch/scripts/helpers.js +207 -0
  5. package/dist/agents/skills/orkestrel-dispatch/scripts/launch.js +108 -0
  6. package/dist/agents/skills/orkestrel-dispatch/scripts/login.js +114 -0
  7. package/dist/agents/skills/orkestrel-dispatch/scripts/result.js +108 -0
  8. package/dist/agents/skills/orkestrel-dispatch/scripts/sweep.js +156 -0
  9. package/dist/agents/skills/orkestrel-harden/scripts/discovery.js +196 -0
  10. package/dist/agents/skills/orkestrel-publish/scripts/compare.js +206 -0
  11. package/dist/agents/skills/orkestrel-publish/scripts/pins.js +93 -0
  12. package/dist/agents/skills/orkestrel-publish/scripts/wave.js +458 -0
  13. package/dist/agents/skills/orkestrel-publish/scripts/window.js +188 -0
  14. package/dist/agents/skills/orkestrel-scout/scripts/map.js +300 -0
  15. package/dist/agents/templates/brief.md +55 -0
  16. package/dist/bin/main.js +4 -2
  17. package/dist/bin/main.js.map +1 -1
  18. package/dist/host/AGENTS.md +77 -135
  19. package/dist/host/agents/orchestration.md +147 -927
  20. package/dist/host/agents/skills/enterprise-bootstrap/SKILL.md +2 -2
  21. package/dist/host/agents/skills/enterprise-bootstrap/references/inspection.md +1 -1
  22. package/dist/host/agents/skills/{orkestrel-align-packages → orkestrel-align}/SKILL.md +6 -13
  23. package/dist/host/agents/skills/{orkestrel-align-packages → orkestrel-align}/agents/openai.yaml +1 -1
  24. package/dist/host/agents/skills/{orkestrel-align-packages → orkestrel-align}/references/fleet.md +5 -7
  25. package/dist/host/agents/skills/{orkestrel-build-application → orkestrel-build}/SKILL.md +11 -22
  26. package/dist/host/agents/skills/{orkestrel-build-application → orkestrel-build}/agents/openai.yaml +1 -1
  27. package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +8 -16
  28. package/dist/host/agents/skills/orkestrel-debrief/references/instruction-audit.md +3 -3
  29. package/dist/host/agents/skills/orkestrel-debrief/references/retention.md +13 -13
  30. package/dist/host/agents/skills/orkestrel-dispatch/SKILL.md +61 -0
  31. package/dist/host/agents/skills/orkestrel-dispatch/agents/openai.yaml +4 -0
  32. package/dist/host/agents/skills/orkestrel-dispatch/references/bench.md +25 -0
  33. package/dist/host/agents/skills/orkestrel-dispatch/references/launch.md +32 -0
  34. package/dist/host/agents/skills/orkestrel-dispatch/scripts/bench.ts +259 -0
  35. package/dist/host/agents/skills/orkestrel-dispatch/scripts/brief.ts +110 -0
  36. package/dist/host/agents/skills/orkestrel-dispatch/scripts/cite.ts +115 -0
  37. package/dist/host/agents/skills/orkestrel-dispatch/scripts/helpers.ts +239 -0
  38. package/dist/host/agents/skills/orkestrel-dispatch/scripts/launch.ts +124 -0
  39. package/dist/host/agents/skills/orkestrel-dispatch/scripts/login.ts +123 -0
  40. package/dist/host/agents/skills/orkestrel-dispatch/scripts/result.ts +129 -0
  41. package/dist/host/agents/skills/orkestrel-dispatch/scripts/sweep.ts +157 -0
  42. package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +42 -193
  43. package/dist/host/agents/skills/orkestrel-falsify/references/brief.md +38 -108
  44. package/dist/host/agents/skills/orkestrel-falsify/references/reconcile.md +35 -134
  45. package/dist/host/agents/skills/{orkestrel-harden-package → orkestrel-harden}/SKILL.md +10 -14
  46. package/dist/host/agents/skills/{orkestrel-harden-package → orkestrel-harden}/agents/openai.yaml +1 -1
  47. package/dist/host/agents/skills/{orkestrel-harden-package → orkestrel-harden}/references/hardening.md +3 -4
  48. package/dist/host/agents/skills/orkestrel-harden/scripts/discovery.ts +228 -0
  49. package/dist/host/agents/skills/{orkestrel-prove-journey → orkestrel-journey}/SKILL.md +15 -23
  50. package/dist/host/agents/skills/orkestrel-journey/agents/openai.yaml +4 -0
  51. package/dist/host/agents/skills/{orkestrel-prove-journey → orkestrel-journey}/references/captures.md +1 -1
  52. package/dist/host/agents/skills/{orkestrel-polish-surface → orkestrel-polish}/SKILL.md +25 -33
  53. package/dist/host/agents/skills/{orkestrel-polish-surface → orkestrel-polish}/agents/openai.yaml +1 -1
  54. package/dist/host/agents/skills/{orkestrel-polish-surface → orkestrel-polish}/references/capture-harness.md +3 -3
  55. package/dist/host/agents/skills/orkestrel-publish/SKILL.md +33 -20
  56. package/dist/host/agents/skills/orkestrel-publish/references/release.md +39 -0
  57. package/dist/host/agents/skills/orkestrel-publish/references/wave.md +22 -21
  58. package/dist/host/agents/skills/orkestrel-publish/references/window.md +27 -14
  59. package/dist/host/agents/skills/orkestrel-publish/scripts/compare.ts +220 -0
  60. package/dist/host/agents/skills/orkestrel-publish/scripts/pins.ts +114 -0
  61. package/dist/host/agents/skills/orkestrel-publish/scripts/wave.ts +629 -0
  62. package/dist/host/agents/skills/orkestrel-publish/scripts/window.ts +242 -0
  63. package/dist/host/agents/skills/orkestrel-scout/SKILL.md +28 -0
  64. package/dist/host/agents/skills/orkestrel-scout/agents/openai.yaml +4 -0
  65. package/dist/host/agents/skills/orkestrel-scout/scripts/map.ts +352 -0
  66. package/dist/host/agents/templates/brief.md +21 -142
  67. package/dist/host/agents/transports/claude-cli.md +21 -0
  68. package/dist/host/agents/transports/codex.md +38 -159
  69. package/dist/host/agents/transports/cursor.md +16 -65
  70. package/dist/host/claude/AGENTS.md +38 -0
  71. package/dist/host/claude/agents/analyst.md +14 -53
  72. package/dist/host/claude/agents/astra.md +26 -0
  73. package/dist/host/claude/agents/builder.md +14 -30
  74. package/dist/host/claude/agents/checker.md +13 -57
  75. package/dist/host/claude/agents/distiller.md +11 -26
  76. package/dist/host/claude/agents/grok.md +12 -35
  77. package/dist/host/claude/agents/opus.md +14 -30
  78. package/dist/host/claude/agents/planner.md +10 -44
  79. package/dist/host/claude/agents/researcher.md +11 -30
  80. package/dist/host/claude/agents/reviewer.md +11 -95
  81. package/dist/host/claude/agents/scout.md +9 -23
  82. package/dist/host/claude/agents/verifier.md +15 -33
  83. package/dist/host/claude/rules/documentation.md +8 -2
  84. package/dist/host/claude/rules/portability.md +7 -1
  85. package/dist/host/claude/rules/quality.md +36 -96
  86. package/dist/host/claude/rules/styles.md +3 -0
  87. package/dist/host/claude/rules/tests.md +6 -3
  88. package/dist/host/claude/rules/workspace.md +19 -15
  89. package/dist/host/claude/rules/writing.md +57 -108
  90. package/dist/host/claude/settings.json +5 -3
  91. package/dist/host/claude/skills/enterprise-bootstrap/SKILL.md +1 -1
  92. package/dist/host/claude/skills/{orkestrel-align-packages → orkestrel-align}/SKILL.md +2 -2
  93. package/dist/host/claude/skills/{orkestrel-build-application → orkestrel-build}/SKILL.md +2 -2
  94. package/dist/host/claude/skills/orkestrel-dispatch/SKILL.md +11 -0
  95. package/dist/host/claude/skills/orkestrel-falsify/SKILL.md +2 -1
  96. package/dist/host/claude/skills/{orkestrel-harden-package → orkestrel-harden}/SKILL.md +2 -2
  97. package/dist/host/claude/skills/{orkestrel-prove-journey → orkestrel-journey}/SKILL.md +2 -2
  98. package/dist/host/claude/skills/orkestrel-polish/SKILL.md +12 -0
  99. package/dist/host/claude/skills/orkestrel-scout/SKILL.md +11 -0
  100. package/dist/host/codex/agents/analyst.toml +15 -32
  101. package/dist/host/codex/agents/astra.toml +25 -0
  102. package/dist/host/codex/agents/builder.toml +13 -20
  103. package/dist/host/codex/agents/checker.toml +13 -27
  104. package/dist/host/codex/agents/distiller.toml +9 -22
  105. package/dist/host/codex/agents/grok.toml +11 -30
  106. package/dist/host/codex/agents/opus.toml +14 -22
  107. package/dist/host/codex/agents/orkestrel.toml +1 -1
  108. package/dist/host/codex/agents/planner.toml +11 -28
  109. package/dist/host/codex/agents/researcher.toml +10 -22
  110. package/dist/host/codex/agents/reviewer.toml +11 -27
  111. package/dist/host/codex/agents/scout.toml +11 -17
  112. package/dist/host/codex/agents/verifier.toml +16 -12
  113. package/dist/host/codex/config.toml +20 -23
  114. package/dist/host/cursor/mcp.json +0 -4
  115. package/dist/host/cursor/rules/orchestration.mdc +12 -20
  116. package/dist/host/dotfiles/mcp.json +0 -4
  117. package/dist/host/dotfiles/oxlintrc.json +7 -0
  118. package/dist/host/guides/probe.md +9 -9
  119. package/dist/host/guides/scaffold.md +117 -71
  120. package/dist/host/guides/test.md +1 -1
  121. package/dist/host/manifest.json +322 -185
  122. package/dist/host/scripts/codex.sh +2 -2
  123. package/dist/host/tests/config.test.ts +68 -46
  124. package/dist/host/tests/policy.test.ts +1 -5
  125. package/dist/host/tests/setupPolicy.ts +179 -4
  126. package/dist/src/core/index.cjs +255 -84
  127. package/dist/src/core/index.cjs.map +1 -1
  128. package/dist/src/core/index.d.cts +94 -29
  129. package/dist/src/core/index.d.ts +94 -29
  130. package/dist/src/core/index.js +253 -85
  131. package/dist/src/core/index.js.map +1 -1
  132. package/dist/src/server/index.cjs +55 -9
  133. package/dist/src/server/index.cjs.map +1 -1
  134. package/dist/src/server/index.d.cts +29 -4
  135. package/dist/src/server/index.d.ts +29 -4
  136. package/dist/src/server/index.js +56 -11
  137. package/dist/src/server/index.js.map +1 -1
  138. package/package.json +15 -11
  139. package/dist/host/CLAUDE.md +0 -61
  140. package/dist/host/agents/skills/orkestrel-prove-journey/agents/openai.yaml +0 -4
  141. package/dist/host/agents/transports/claude.md +0 -49
  142. package/dist/host/claude/agents/application.md +0 -36
  143. package/dist/host/claude/agents/sol.md +0 -61
  144. package/dist/host/claude/skills/orkestrel-polish-surface/SKILL.md +0 -12
  145. package/dist/host/codex/agents/application.toml +0 -25
  146. package/dist/host/codex/agents/sol.toml +0 -19
  147. /package/dist/host/agents/skills/{orkestrel-align-packages → orkestrel-align}/references/integration.md +0 -0
  148. /package/dist/host/agents/skills/{orkestrel-harden-package → orkestrel-harden}/references/centralization.md +0 -0
  149. /package/dist/host/agents/skills/{orkestrel-harden-package → orkestrel-harden}/references/contract.md +0 -0
  150. /package/dist/host/agents/skills/{orkestrel-harden-package → orkestrel-harden}/references/research.md +0 -0
  151. /package/dist/host/agents/skills/{orkestrel-prove-journey → orkestrel-journey}/references/decide.md +0 -0
  152. /package/dist/host/agents/skills/{orkestrel-prove-journey → orkestrel-journey}/references/layer.md +0 -0
  153. /package/dist/host/agents/skills/{orkestrel-prove-journey → orkestrel-journey}/references/statechart.md +0 -0
  154. /package/dist/host/agents/skills/{orkestrel-prove-journey → orkestrel-journey}/references/styles.md +0 -0
@@ -0,0 +1,458 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
3
+ import { basename, dirname, extname, join } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { parseJsonRecord, readJsonObject, readMissingFlags, readOption, readString, runNpm, } from "../../orkestrel-dispatch/scripts/helpers.js";
6
+ const STEPS = [
7
+ 'pin',
8
+ 'commit',
9
+ 'overwrite',
10
+ 'verify',
11
+ 'install',
12
+ 'pins',
13
+ 'format',
14
+ 'gates',
15
+ 'compare',
16
+ ];
17
+ const GATES = ['format:check', 'lint:check', 'check', 'build', 'test'];
18
+ const RUNTIME_FIELDS = ['dependencies', 'peerDependencies'];
19
+ const RANGE_FIELDS = [...RUNTIME_FIELDS, 'devDependencies'];
20
+ const CATALOG = '.claude/agents/orkestrel.md';
21
+ const OFFLINE_REFUSAL = "'catalog' does not take --offline";
22
+ const VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u;
23
+ // The checkout runs the `.ts` sources and a built twin under `dist/agents` runs `.js` siblings.
24
+ const EXTENSION = extname(fileURLToPath(import.meta.url));
25
+ const COMPARE = fileURLToPath(new URL(`./compare${EXTENSION}`, import.meta.url));
26
+ const PINS = fileURLToPath(new URL(`./pins${EXTENSION}`, import.meta.url));
27
+ function readManifest(target) {
28
+ try {
29
+ return parseJsonRecord(readFileSync(join(target, 'package.json'), 'utf8'));
30
+ }
31
+ catch {
32
+ return undefined;
33
+ }
34
+ }
35
+ function readRanges(manifest, fields) {
36
+ const ranges = new Map();
37
+ for (const field of fields) {
38
+ const block = manifest[field];
39
+ if (typeof block !== 'object' || block === null)
40
+ continue;
41
+ for (const [name, range] of Object.entries(block)) {
42
+ if (typeof range === 'string')
43
+ ranges.set(`${field} ${name}`, range);
44
+ }
45
+ }
46
+ return ranges;
47
+ }
48
+ function compareRanges(published, final) {
49
+ const names = [...new Set([...published.keys(), ...final.keys()])].sort();
50
+ return names.flatMap((name) => {
51
+ const before = published.get(name);
52
+ const after = final.get(name);
53
+ if (before === after)
54
+ return [];
55
+ if (before === undefined)
56
+ return [`${name} ${String(after)} added`];
57
+ if (after === undefined)
58
+ return [`${name} ${before} removed`];
59
+ return [`${name} ${before} -> ${after}`];
60
+ });
61
+ }
62
+ function resolveScaffold(target) {
63
+ const installed = join(target, 'node_modules', '@orkestrel', 'scaffold', 'dist', 'bin', 'main.js');
64
+ if (existsSync(installed))
65
+ return [process.execPath, installed];
66
+ const own = join(target, 'dist', 'bin', 'main.js');
67
+ if (existsSync(own))
68
+ return [process.execPath, own];
69
+ return [];
70
+ }
71
+ function readReleases(text) {
72
+ const releases = readJsonObject(text)?.releases;
73
+ if (!Array.isArray(releases))
74
+ return undefined;
75
+ return releases.flatMap((entry) => {
76
+ if (typeof entry !== 'object' || entry === null)
77
+ return [];
78
+ const record = Object.fromEntries(Object.entries(entry));
79
+ const name = readString(record, 'name');
80
+ const range = readString(record, 'range');
81
+ if (name === undefined || range === undefined)
82
+ return [];
83
+ return [{ name, range, latest: readString(record, 'latest') }];
84
+ });
85
+ }
86
+ function describeCommand(file, args) {
87
+ return [file === process.execPath ? 'node' : file, ...args].join(' ');
88
+ }
89
+ function describeFailure(result) {
90
+ if (result.status === 0)
91
+ return undefined;
92
+ return result.stderr
93
+ .trim()
94
+ .split(/\r\n|\n/)
95
+ .slice(-3)
96
+ .join(' | ');
97
+ }
98
+ function runStep(runner, step, file, args, cwd) {
99
+ const command = describeCommand(file, args);
100
+ if (runner.dry) {
101
+ runner.steps.push({ step, command, exit: undefined, durationMs: 0, note: 'dry run' });
102
+ return undefined;
103
+ }
104
+ const started = Date.now();
105
+ const result = spawnSync(file, args, {
106
+ cwd,
107
+ encoding: 'utf8',
108
+ windowsHide: true,
109
+ maxBuffer: 64 * 1024 * 1024,
110
+ });
111
+ runner.steps.push({
112
+ step,
113
+ command,
114
+ exit: result.status ?? undefined,
115
+ durationMs: Date.now() - started,
116
+ note: describeFailure(result),
117
+ });
118
+ return result;
119
+ }
120
+ function runNpmStep(runner, step, args) {
121
+ const command = `npm ${args.join(' ')}`;
122
+ if (runner.dry) {
123
+ runner.steps.push({ step, command, exit: undefined, durationMs: 0, note: 'dry run' });
124
+ return undefined;
125
+ }
126
+ const started = Date.now();
127
+ const result = runNpm(args, runner.target);
128
+ runner.steps.push({
129
+ step,
130
+ command,
131
+ exit: result.status ?? undefined,
132
+ durationMs: Date.now() - started,
133
+ note: describeFailure(result),
134
+ });
135
+ return result;
136
+ }
137
+ function recordStep(runner, step, command, note) {
138
+ runner.steps.push({ step, command, exit: runner.dry ? undefined : 0, durationMs: 0, note });
139
+ }
140
+ function replaceLastStep(runner, exit, note) {
141
+ const last = runner.steps.pop();
142
+ if (last !== undefined)
143
+ runner.steps.push({ ...last, exit, note });
144
+ }
145
+ function failed(runner) {
146
+ return runner.steps.some((entry) => entry.exit !== undefined &&
147
+ entry.exit !== 0 &&
148
+ entry.step !== 'pins' &&
149
+ entry.step !== 'compare');
150
+ }
151
+ function writeRanges(target, releases) {
152
+ const manifest = readManifest(target);
153
+ if (manifest === undefined)
154
+ return [];
155
+ const moved = [];
156
+ for (const field of RANGE_FIELDS) {
157
+ const block = manifest[field];
158
+ if (typeof block !== 'object' || block === null)
159
+ continue;
160
+ const ranges = Object.fromEntries(Object.entries(block));
161
+ for (const release of releases) {
162
+ if (release.latest === undefined || !(release.name in ranges))
163
+ continue;
164
+ const caret = `^${release.latest}`;
165
+ if (ranges[release.name] !== caret) {
166
+ ranges[release.name] = caret;
167
+ moved.push(`${release.name} ${String(release.range)} -> ${caret}`);
168
+ }
169
+ }
170
+ manifest[field] = ranges;
171
+ }
172
+ if (moved.length > 0)
173
+ writeFileSync(join(target, 'package.json'), `${JSON.stringify(manifest, null, '\t')}\n`);
174
+ return moved;
175
+ }
176
+ function runPin(runner, scaffoldFile, scaffoldPrefix, offline) {
177
+ const audit = runStep(runner, 'pin', scaffoldFile, [...scaffoldPrefix, 'audit', '--json', ...offline], runner.target);
178
+ if (audit !== undefined) {
179
+ const releases = readReleases(audit.stdout);
180
+ if (releases === undefined || (audit.status !== 0 && audit.status !== 1)) {
181
+ replaceLastStep(runner, audit.status === null || audit.status === 0 ? 2 : audit.status, releases === undefined ? 'the audit printed no releases envelope' : describeFailure(audit));
182
+ return;
183
+ }
184
+ // The audit exits 1 on the drift this step exists to close, so its exit is a reading here.
185
+ replaceLastStep(runner, 0, `${releases.length} release reading(s)`);
186
+ const moved = writeRanges(runner.target, releases);
187
+ recordStep(runner, 'pin', 'rewrite @orkestrel ranges to the registry caret', moved.length === 0 ? 'no range moved' : moved.join(', '));
188
+ }
189
+ else {
190
+ recordStep(runner, 'pin', 'rewrite @orkestrel ranges to the registry caret', 'dry run');
191
+ }
192
+ runNpmStep(runner, 'pin', ['install']);
193
+ }
194
+ function runCommit(runner) {
195
+ const status = runStep(runner, 'commit', 'git', ['status', '--porcelain', '--', 'package.json', 'package-lock.json'], runner.target);
196
+ if (status === undefined || status.stdout.trim() !== '') {
197
+ runStep(runner, 'commit', 'git', [
198
+ 'commit',
199
+ '--only',
200
+ '-m',
201
+ 'Re-pin the @orkestrel ranges for the release visit',
202
+ '--',
203
+ 'package.json',
204
+ 'package-lock.json',
205
+ ], runner.target);
206
+ }
207
+ else {
208
+ recordStep(runner, 'commit', 'git commit', 'manifest and lockfile already committed');
209
+ }
210
+ }
211
+ function runOverwrite(runner, scaffoldFile, scaffoldPrefix, offline) {
212
+ const written = runStep(runner, 'overwrite', scaffoldFile, [...scaffoldPrefix, 'overwrite', '--json', ...offline], runner.target);
213
+ if (runner.offline && written !== undefined && written.status === 1) {
214
+ const note = readString(readJsonObject(written.stdout) ?? {}, 'note');
215
+ if (note !== undefined && note.includes(OFFLINE_REFUSAL)) {
216
+ replaceLastStep(runner, 0, 'offline overwrite skipped the catalog step by design');
217
+ }
218
+ else {
219
+ replaceLastStep(runner, 1, note ?? describeFailure(written));
220
+ }
221
+ }
222
+ if (!failed(runner)) {
223
+ runStep(runner, 'overwrite', scaffoldFile, [...scaffoldPrefix, 'audit', ...offline], runner.target);
224
+ }
225
+ }
226
+ function runVerify(runner, scaffoldFile, scaffoldPrefix, offline) {
227
+ const audit = runStep(runner, 'verify', scaffoldFile, [...scaffoldPrefix, 'audit', '--json', ...offline], runner.target);
228
+ if (audit === undefined)
229
+ return;
230
+ const releases = readReleases(audit.stdout);
231
+ if (releases === undefined) {
232
+ replaceLastStep(runner, audit.status === null || audit.status === 0 ? 2 : audit.status, 'the audit printed no releases envelope');
233
+ return;
234
+ }
235
+ const drift = releases.filter((release) => release.latest !== undefined && release.range !== `^${release.latest}`);
236
+ runner.steps.push({
237
+ step: 'verify',
238
+ command: 'compare every declared range with the registry',
239
+ exit: drift.length === 0 ? 0 : 1,
240
+ durationMs: 0,
241
+ note: drift.length === 0
242
+ ? undefined
243
+ : drift
244
+ .map((release) => `${release.name} ${release.range} vs ${String(release.latest)}`)
245
+ .join(', '),
246
+ });
247
+ }
248
+ function runPins(runner, before, version) {
249
+ const after = readRanges(readManifest(runner.target) ?? {}, RANGE_FIELDS);
250
+ const prior = [...before]
251
+ .filter(([name, range]) => after.get(name) !== range)
252
+ .flatMap(([, range]) => ['--range', range]);
253
+ if (version === undefined) {
254
+ recordStep(runner, 'pins', basename(PINS), 'no prior version to sweep for; pass --prior');
255
+ return undefined;
256
+ }
257
+ const swept = runStep(runner, 'pins', process.execPath, [PINS, '--version', version, ...prior], runner.target);
258
+ return swept === undefined
259
+ ? undefined
260
+ : swept.stdout.split(/\r\n|\n/).filter((line) => /^\S+:\d+: /u.test(line)).length;
261
+ }
262
+ function runCompare(runner, name) {
263
+ const compared = runStep(runner, 'compare', process.execPath, [COMPARE, '--json'], runner.target);
264
+ const distMoved = compared === undefined
265
+ ? undefined
266
+ : compared.status === 3
267
+ ? true
268
+ : compared.status === 0
269
+ ? false
270
+ : undefined;
271
+ if (name === undefined) {
272
+ recordStep(runner, 'compare', 'npm view', 'the manifest names no package to read');
273
+ return { distMoved, rangesMoved: undefined, ranges: undefined, hits: undefined };
274
+ }
275
+ const view = runNpmStep(runner, 'compare', ['view', name, '--json']);
276
+ const published = view === undefined || view.status !== 0 ? undefined : parseJsonRecord(view.stdout);
277
+ if (published === undefined) {
278
+ return { distMoved, rangesMoved: undefined, ranges: undefined, hits: undefined };
279
+ }
280
+ const ranges = compareRanges(readRanges(published, RUNTIME_FIELDS), readRanges(readManifest(runner.target) ?? {}, RUNTIME_FIELDS));
281
+ return { distMoved, rangesMoved: ranges.length > 0, ranges, hits: undefined };
282
+ }
283
+ function describeReading(moved, movedWord, sameWord) {
284
+ return moved === undefined ? 'unanswered' : moved ? movedWord : sameWord;
285
+ }
286
+ function runVisit(argv) {
287
+ const target = readOption(argv, '--target') ?? process.cwd();
288
+ if (!existsSync(join(target, 'package.json'))) {
289
+ console.error(`wave: ${target} carries no package.json`);
290
+ return undefined;
291
+ }
292
+ const scaffold = resolveScaffold(target);
293
+ if (scaffold.length === 0) {
294
+ console.error(`wave: no scaffold executable under ${target}; run npm install first`);
295
+ return undefined;
296
+ }
297
+ const [scaffoldFile, ...scaffoldPrefix] = scaffold;
298
+ if (scaffoldFile === undefined)
299
+ return undefined;
300
+ const runner = {
301
+ target,
302
+ dry: argv.includes('--dry-run'),
303
+ offline: argv.includes('--offline'),
304
+ steps: [],
305
+ };
306
+ const from = STEPS.indexOf(readOption(argv, '--from') ?? 'pin');
307
+ const to = STEPS.indexOf(readOption(argv, '--to') ?? 'compare');
308
+ const selected = new Set(STEPS.slice(from, to + 1));
309
+ const manifest = readManifest(target);
310
+ if (manifest === undefined) {
311
+ console.error(`wave: ${target} carries an unreadable package.json`);
312
+ return undefined;
313
+ }
314
+ const name = readString(manifest, 'name');
315
+ const version = readString(manifest, 'version');
316
+ const before = readRanges(manifest, RANGE_FIELDS);
317
+ const offline = runner.offline ? ['--offline'] : [];
318
+ let hits;
319
+ let ruling = {
320
+ distMoved: undefined,
321
+ rangesMoved: undefined,
322
+ ranges: undefined,
323
+ hits: undefined,
324
+ };
325
+ if (selected.has('pin'))
326
+ runPin(runner, scaffoldFile, scaffoldPrefix, offline);
327
+ if (selected.has('commit') && !failed(runner))
328
+ runCommit(runner);
329
+ if (selected.has('overwrite') && !failed(runner)) {
330
+ runOverwrite(runner, scaffoldFile, scaffoldPrefix, offline);
331
+ }
332
+ if (selected.has('verify') && !failed(runner)) {
333
+ runVerify(runner, scaffoldFile, scaffoldPrefix, offline);
334
+ }
335
+ if (selected.has('install') && !failed(runner))
336
+ runNpmStep(runner, 'install', ['install']);
337
+ if (selected.has('pins') && !failed(runner)) {
338
+ hits = runPins(runner, before, readOption(argv, '--prior') ?? version);
339
+ }
340
+ if (selected.has('format') && !failed(runner))
341
+ runNpmStep(runner, 'format', ['run', 'format']);
342
+ if (selected.has('gates') && !failed(runner)) {
343
+ for (const gate of GATES) {
344
+ if (failed(runner))
345
+ break;
346
+ runNpmStep(runner, 'gates', ['run', gate]);
347
+ }
348
+ }
349
+ if (selected.has('compare') && !failed(runner))
350
+ ruling = runCompare(runner, name);
351
+ ruling = { ...ruling, hits };
352
+ const bump = ruling.distMoved === true || ruling.rangesMoved === true
353
+ ? 'bump'
354
+ : ruling.distMoved === undefined || ruling.rangesMoved === undefined
355
+ ? 'unanswered'
356
+ : 'no bump';
357
+ const reading = `dist ${describeReading(ruling.distMoved, 'moved', 'same')}, ranges ${describeReading(ruling.rangesMoved, 'moved', 'same')}${hits === undefined ? '' : `, ${hits} self-pin hit(s)`}`;
358
+ return {
359
+ target,
360
+ name,
361
+ version,
362
+ steps: runner.steps,
363
+ ruling,
364
+ row: `| ${name ?? target} | ${version ?? '?'} | ${bump} | ${reading} | round |`,
365
+ };
366
+ }
367
+ function readLayers(catalogPath) {
368
+ if (!existsSync(catalogPath))
369
+ return undefined;
370
+ const text = readFileSync(catalogPath, 'utf8');
371
+ const start = text.indexOf('<!-- orkestrel:catalog -->');
372
+ const end = text.indexOf('<!-- /orkestrel:catalog -->');
373
+ if (start === -1 || end === -1)
374
+ return undefined;
375
+ const layers = new Map();
376
+ for (const line of text.slice(start, end).split(/\r\n|\n/)) {
377
+ const match = line.match(/^\|\s*`(@orkestrel\/[a-z0-9-]+)`\s*\|\s*`([^`]+)`\s*\|\s*(L\d+)\s*\|/u);
378
+ if (match?.[1] === undefined || match[3] === undefined)
379
+ continue;
380
+ const members = layers.get(match[3]) ?? [];
381
+ members.push(`${match[1]} ${match[2] ?? ''}`.trim());
382
+ layers.set(match[3], members);
383
+ }
384
+ return new Map([...layers.entries()].sort(([left], [right]) => Number(left.slice(1)) - Number(right.slice(1))));
385
+ }
386
+ function renderVisit(visit) {
387
+ const lines = visit.steps.map((entry) => `wave: ${entry.step.padEnd(9)} exit=${entry.exit === undefined ? 'dry' : String(entry.exit)} ${String(entry.durationMs).padStart(7)} ms ${entry.command}${entry.note === undefined ? '' : ` (${entry.note})`}`);
388
+ lines.push(`wave: ruling dist ${describeReading(visit.ruling.distMoved, 'moved', 'same')}, ranges ${describeReading(visit.ruling.rangesMoved, 'moved', 'same')}${visit.ruling.hits === undefined ? '' : `, ${visit.ruling.hits} self-pin hit(s)`}`);
389
+ for (const range of visit.ruling.ranges ?? [])
390
+ lines.push(`wave: range ${range}`);
391
+ lines.push(visit.row);
392
+ return `${lines.join('\n')}\n`;
393
+ }
394
+ function main(argv) {
395
+ const missing = readMissingFlags(argv, [
396
+ '--target',
397
+ '--from',
398
+ '--to',
399
+ '--prior',
400
+ '--catalog',
401
+ '--out',
402
+ ]);
403
+ if (missing.length > 0) {
404
+ console.error(`wave: ${missing.join(', ')} given with no value`);
405
+ return 64;
406
+ }
407
+ if (argv.includes('--plan') && argv.includes('--visit')) {
408
+ console.error('wave: --plan and --visit are two modes; name one');
409
+ return 64;
410
+ }
411
+ if (argv.includes('--plan')) {
412
+ const layers = readLayers(readOption(argv, '--catalog') ?? CATALOG);
413
+ if (layers === undefined) {
414
+ console.error('wave: no marker-bounded catalog table; run scaffold catalog first');
415
+ return 2;
416
+ }
417
+ if (argv.includes('--json'))
418
+ console.log(JSON.stringify(Object.fromEntries(layers)));
419
+ else
420
+ for (const [layer, members] of layers)
421
+ console.log(`${layer}\t${members.join(', ')}`);
422
+ return 0;
423
+ }
424
+ if (!argv.includes('--visit')) {
425
+ console.error('usage: wave.ts --visit [--target DIR] [--offline] [--from STEP] [--to STEP] [--prior VERSION] [--dry-run] [--json] [--out FILE] | --plan [--catalog PATH] [--json]');
426
+ return 64;
427
+ }
428
+ const from = STEPS.indexOf(readOption(argv, '--from') ?? 'pin');
429
+ const to = STEPS.indexOf(readOption(argv, '--to') ?? 'compare');
430
+ if (from === -1 || to === -1 || to < from) {
431
+ console.error(`wave: --from and --to name steps in order from: ${STEPS.join(', ')}`);
432
+ return 64;
433
+ }
434
+ const prior = readOption(argv, '--prior');
435
+ if (prior !== undefined && !VERSION.test(prior)) {
436
+ console.error('wave: --prior takes the version the package carried before its bump, MAJOR.MINOR.PATCH');
437
+ return 64;
438
+ }
439
+ const visit = runVisit(argv);
440
+ if (visit === undefined)
441
+ return 2;
442
+ const output = argv.includes('--json') ? `${JSON.stringify(visit)}\n` : renderVisit(visit);
443
+ const out = readOption(argv, '--out');
444
+ if (out === undefined)
445
+ process.stdout.write(output);
446
+ else {
447
+ mkdirSync(dirname(out), { recursive: true });
448
+ writeFileSync(out, output);
449
+ console.log(`wave: wrote ${out}`);
450
+ }
451
+ return visit.steps.some((entry) => entry.exit !== undefined &&
452
+ entry.exit !== 0 &&
453
+ entry.step !== 'pins' &&
454
+ entry.step !== 'compare')
455
+ ? 1
456
+ : 0;
457
+ }
458
+ process.exitCode = main(process.argv.slice(2));
@@ -0,0 +1,188 @@
1
+ // Work the npm authentication window. Run from the checkout root:
2
+ // node .agents/skills/orkestrel-publish/scripts/window.ts --whoami [--wait SECONDS]
3
+ // node .agents/skills/orkestrel-publish/scripts/window.ts --login [--wait SECONDS]
4
+ // node .agents/skills/orkestrel-publish/scripts/window.ts --publish DIR... --otp CODE [--json]
5
+ // node .agents/skills/orkestrel-publish/scripts/window.ts --confirm NAME@VERSION... [--wait SECONDS]
6
+ // --whoami reads `npm whoami` once, or polls it every 5 seconds until it answers or the wait ends.
7
+ // --login prints the command the operator runs in a real terminal (npm offers its approval only to a
8
+ // TTY, so no child of this script can hold it), then polls `npm whoami` until it answers or the wait
9
+ // ends. --publish re-reads `npm whoami` immediately before the first upload and refuses when it
10
+ // answers nothing, then runs `npm publish --ignore-scripts --browser=false --otp=CODE` in each
11
+ // directory back to back under captured pipes (npm's non-TTY guard turns a refused code into
12
+ // EOTP with no prompt, so the path needs no TTY), journals each upload to tmp/units/publish-<name>.log, reads the `+ name@version`
13
+ // acceptance line, stops at the first refusal and names the package to resume from, then confirms
14
+ // each accepted version against the registry. --confirm re-reads the registry for each name@version every 5
15
+ // seconds until it serves the version or the wait (default 120 seconds) ends. Exit 0 when the mode
16
+ // succeeded, 3 when it did not, 64 on usage.
17
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
18
+ import { join } from 'node:path';
19
+ import { readList, readMissingFlags, readOption, readString, runNpm, } from "../../orkestrel-dispatch/scripts/helpers.js";
20
+ const POLL_MS = 5_000;
21
+ const ACCEPTED = /^\+ (\S+)@(\S+)\s*$/mu;
22
+ function waitFor(ms) {
23
+ return new Promise((resolve) => {
24
+ setTimeout(resolve, ms);
25
+ });
26
+ }
27
+ function readWhoami() {
28
+ const result = runNpm(['whoami']);
29
+ const user = result.stdout.trim();
30
+ return result.status === 0 && user !== '' ? user : undefined;
31
+ }
32
+ async function pollWhoami(waitSeconds) {
33
+ const started = Date.now();
34
+ let user = readWhoami();
35
+ while (user === undefined && Date.now() - started < waitSeconds * 1000) {
36
+ await waitFor(POLL_MS);
37
+ user = readWhoami();
38
+ }
39
+ return user;
40
+ }
41
+ function readServed(name, version) {
42
+ const result = runNpm(['view', `${name}@${version}`, 'version', '--json']);
43
+ return result.status === 0 && result.stdout.replace(/["\s]/gu, '') === version;
44
+ }
45
+ async function confirmServed(name, version, waitSeconds) {
46
+ const started = Date.now();
47
+ let served = readServed(name, version);
48
+ while (!served && Date.now() - started < waitSeconds * 1000) {
49
+ await waitFor(POLL_MS);
50
+ served = readServed(name, version);
51
+ }
52
+ return served;
53
+ }
54
+ function readErrorCode(text) {
55
+ return text.match(/\b(E[A-Z0-9]{3,}|EOTP|E401|E403|E404)\b/u)?.[1];
56
+ }
57
+ function readManifestName(directory) {
58
+ const path = join(directory, 'package.json');
59
+ if (!existsSync(path))
60
+ return { name: undefined, version: undefined };
61
+ try {
62
+ const parsed = JSON.parse(readFileSync(path, 'utf8'));
63
+ if (typeof parsed !== 'object' || parsed === null)
64
+ return { name: undefined, version: undefined };
65
+ const record = Object.fromEntries(Object.entries(parsed));
66
+ return { name: readString(record, 'name'), version: readString(record, 'version') };
67
+ }
68
+ catch {
69
+ return { name: undefined, version: undefined };
70
+ }
71
+ }
72
+ async function publishAll(directories, otp, waitSeconds) {
73
+ mkdirSync('tmp/units', { recursive: true });
74
+ const uploads = [];
75
+ for (const directory of directories) {
76
+ const manifest = readManifestName(directory);
77
+ const journal = `tmp/units/publish-${(manifest.name ?? directory).replace(/[^A-Za-z0-9.-]+/gu, '-')}.log`;
78
+ const result = runNpm(['publish', '--ignore-scripts', '--browser=false', `--otp=${otp}`], directory);
79
+ writeFileSync(journal, `${result.stdout}\n${result.stderr}\nexit=${result.status ?? 'null'}\n`);
80
+ const match = `${result.stdout}\n${result.stderr}`.match(ACCEPTED);
81
+ const accepted = result.status === 0 && match !== null;
82
+ const name = match?.[1] ?? manifest.name;
83
+ const version = match?.[2] ?? manifest.version;
84
+ const upload = {
85
+ directory,
86
+ name,
87
+ version,
88
+ exit: result.status ?? undefined,
89
+ accepted,
90
+ confirmed: false,
91
+ code: accepted ? undefined : readErrorCode(result.stderr),
92
+ journal,
93
+ };
94
+ if (!accepted) {
95
+ uploads.push(upload);
96
+ console.error(`window: ${name ?? directory} refused (${upload.code ?? `exit ${String(result.status)}`}); resume from it on a fresh code`);
97
+ break;
98
+ }
99
+ console.error(`window: accepted ${name ?? '?'}@${version ?? '?'}`);
100
+ uploads.push(upload);
101
+ }
102
+ const confirmed = [];
103
+ for (const upload of uploads) {
104
+ if (!upload.accepted || upload.name === undefined || upload.version === undefined) {
105
+ confirmed.push(upload);
106
+ continue;
107
+ }
108
+ confirmed.push({
109
+ ...upload,
110
+ confirmed: await confirmServed(upload.name, upload.version, waitSeconds),
111
+ });
112
+ }
113
+ return confirmed;
114
+ }
115
+ async function main(argv) {
116
+ const missing = readMissingFlags(argv, ['--wait', '--otp']);
117
+ if (missing.length > 0) {
118
+ console.error(`window: ${missing.join(', ')} given with no value`);
119
+ return 64;
120
+ }
121
+ const wait = Number(readOption(argv, '--wait') ?? 120);
122
+ const modes = ['--whoami', '--login', '--publish', '--confirm'].filter((flag) => argv.includes(flag));
123
+ if (modes.length > 1 || (argv.includes('--wait') && (!Number.isFinite(wait) || wait < 0))) {
124
+ console.error('usage: window.ts names one mode (--whoami | --login | --publish | --confirm), and --wait takes a number of seconds');
125
+ return 64;
126
+ }
127
+ if (argv.includes('--whoami')) {
128
+ const user = await pollWhoami(argv.includes('--wait') ? wait : 0);
129
+ console.log(JSON.stringify({ mode: 'whoami', user, live: user !== undefined }));
130
+ return user === undefined ? 3 : 0;
131
+ }
132
+ if (argv.includes('--login')) {
133
+ const command = process.platform === 'win32'
134
+ ? 'npm login --browser=false'
135
+ : "script -qfc 'npm login --browser=false' tmp/units/npm-login.log";
136
+ console.error(`window: run this in a real terminal, then approve the URL it prints on its first line: ${command}`);
137
+ const user = await pollWhoami(argv.includes('--wait') ? wait : 300);
138
+ console.log(JSON.stringify({ mode: 'login', command, user, live: user !== undefined }));
139
+ return user === undefined ? 3 : 0;
140
+ }
141
+ if (argv.includes('--publish')) {
142
+ const directories = readList(argv, '--publish');
143
+ const otp = readOption(argv, '--otp');
144
+ if (directories.length === 0 ||
145
+ otp === undefined ||
146
+ !/^\d{6,8}$/u.test(otp) ||
147
+ directories.some((directory) => !existsSync(join(directory, 'package.json')))) {
148
+ console.error("usage: window.ts --publish DIR... --otp CODE [--wait SECONDS] [--json]; every DIR carries a package.json and CODE is the account's one-time code");
149
+ return 64;
150
+ }
151
+ // A stored credential expires mid-session, so the session-start answer does not hold here.
152
+ if (readWhoami() === undefined) {
153
+ console.error('window: npm whoami answers nothing immediately before the upload; run --login first');
154
+ return 3;
155
+ }
156
+ const uploads = await publishAll(directories, otp, wait);
157
+ if (argv.includes('--json'))
158
+ console.log(JSON.stringify({ mode: 'publish', uploads }));
159
+ else {
160
+ for (const upload of uploads) {
161
+ console.log(`window: ${upload.directory}\t${upload.name ?? '?'}@${upload.version ?? '?'}\t${upload.accepted ? 'accepted' : `refused ${upload.code ?? String(upload.exit)}`}\t${upload.confirmed ? 'confirmed' : 'unconfirmed'}\t${upload.journal}`);
162
+ }
163
+ }
164
+ return uploads.every((upload) => upload.accepted && upload.confirmed) ? 0 : 3;
165
+ }
166
+ if (argv.includes('--confirm')) {
167
+ const targets = readList(argv, '--confirm');
168
+ if (targets.length === 0 || targets.some((target) => !/^@?[^@]+@\S+$/u.test(target))) {
169
+ console.error('usage: window.ts --confirm NAME@VERSION... [--wait SECONDS]');
170
+ return 64;
171
+ }
172
+ const readings = [];
173
+ for (const target of targets) {
174
+ const at = target.lastIndexOf('@');
175
+ readings.push({
176
+ target,
177
+ served: await confirmServed(target.slice(0, at), target.slice(at + 1), wait),
178
+ });
179
+ }
180
+ console.log(JSON.stringify({ mode: 'confirm', readings }));
181
+ return readings.every((reading) => reading.served) ? 0 : 3;
182
+ }
183
+ console.error('usage: window.ts --whoami [--wait SECONDS] | --login [--wait SECONDS] | --publish DIR... --otp CODE [--json] | --confirm NAME@VERSION... [--wait SECONDS]');
184
+ return 64;
185
+ }
186
+ main(process.argv.slice(2)).then((code) => {
187
+ process.exitCode = code;
188
+ });