@orkestrel/scaffold 0.0.108 → 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.
- package/dist/agents/skills/orkestrel-dispatch/scripts/bench.js +326 -91
- package/dist/agents/skills/orkestrel-dispatch/scripts/brief.js +110 -14
- package/dist/agents/skills/orkestrel-dispatch/scripts/chain.js +280 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/cite.js +188 -26
- package/dist/agents/skills/orkestrel-dispatch/scripts/launch.js +142 -43
- package/dist/agents/skills/orkestrel-dispatch/scripts/login.js +240 -62
- package/dist/agents/skills/orkestrel-dispatch/scripts/patch.js +323 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/result.js +169 -32
- package/dist/agents/skills/orkestrel-dispatch/scripts/sweep.js +290 -69
- package/dist/agents/skills/orkestrel-falsify/scripts/mutate.js +560 -0
- package/dist/agents/skills/orkestrel-harden/scripts/discovery.js +1052 -205
- package/dist/agents/skills/orkestrel-harden/scripts/measure.js +361 -0
- package/dist/agents/skills/orkestrel-harden/scripts/titles.js +455 -80
- package/dist/agents/skills/orkestrel-parallel/scripts/cases.js +327 -0
- package/dist/agents/skills/orkestrel-publish/scripts/compare.js +298 -51
- package/dist/agents/skills/orkestrel-publish/scripts/pins.js +170 -13
- package/dist/agents/skills/orkestrel-publish/scripts/swap.js +956 -191
- package/dist/agents/skills/orkestrel-publish/scripts/wave.js +707 -112
- package/dist/agents/skills/orkestrel-publish/scripts/window.js +235 -41
- package/dist/agents/skills/orkestrel-scout/scripts/map.js +341 -54
- package/dist/agents/templates/brief.md +1 -1
- package/dist/bin/main.js +51 -46
- package/dist/bin/main.js.map +1 -1
- package/dist/host/AGENTS.md +23 -21
- package/dist/host/agents/orchestration.md +43 -18
- package/dist/host/agents/platforms/windows.md +3 -3
- package/dist/host/agents/skills/orkestrel-align/SKILL.md +10 -13
- package/dist/host/agents/skills/orkestrel-align/references/fleet.md +4 -10
- package/dist/host/agents/skills/orkestrel-align/references/integration.md +1 -1
- package/dist/host/agents/skills/orkestrel-build/SKILL.md +16 -48
- package/dist/host/agents/skills/orkestrel-build/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +19 -16
- package/dist/host/agents/skills/orkestrel-debrief/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/orkestrel-debrief/references/field-testing.md +15 -28
- package/dist/host/agents/skills/orkestrel-debrief/references/instruction-audit.md +5 -6
- package/dist/host/agents/skills/orkestrel-debrief/references/retention.md +4 -6
- package/dist/host/agents/skills/orkestrel-dispatch/SKILL.md +31 -20
- package/dist/host/agents/skills/orkestrel-dispatch/references/launch.md +3 -4
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/bench.ts +366 -75
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/brief.ts +126 -16
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/chain.ts +323 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/cite.ts +220 -31
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/launch.ts +176 -33
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/login.ts +294 -59
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/patch.ts +401 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/result.ts +199 -34
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/sweep.ts +330 -69
- package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +10 -8
- package/dist/host/agents/skills/orkestrel-falsify/references/brief.md +1 -5
- package/dist/host/agents/skills/orkestrel-falsify/references/reconcile.md +1 -1
- package/dist/host/agents/skills/orkestrel-falsify/scripts/mutate.ts +701 -0
- package/dist/host/agents/skills/orkestrel-harden/SKILL.md +16 -12
- package/dist/host/agents/skills/orkestrel-harden/references/bindings.md +23 -81
- package/dist/host/agents/skills/orkestrel-harden/references/centralization.md +4 -5
- package/dist/host/agents/skills/orkestrel-harden/references/contract.md +2 -0
- package/dist/host/agents/skills/orkestrel-harden/references/document.md +33 -0
- package/dist/host/agents/skills/orkestrel-harden/references/equivalence.md +6 -3
- package/dist/host/agents/skills/orkestrel-harden/references/hardening.md +8 -4
- package/dist/host/agents/skills/orkestrel-harden/references/research.md +2 -0
- package/dist/host/agents/skills/orkestrel-harden/scripts/discovery.ts +1272 -279
- package/dist/host/agents/skills/orkestrel-harden/scripts/measure.ts +425 -0
- package/dist/host/agents/skills/orkestrel-harden/scripts/titles.ts +603 -98
- package/dist/host/agents/skills/orkestrel-journey/SKILL.md +75 -98
- package/dist/host/agents/skills/orkestrel-journey/references/captures.md +54 -17
- package/dist/host/agents/skills/orkestrel-journey/references/decide.md +1 -20
- package/dist/host/agents/skills/orkestrel-journey/references/layer.md +21 -31
- package/dist/host/agents/skills/orkestrel-journey/references/styles.md +1 -1
- package/dist/host/agents/skills/orkestrel-parallel/SKILL.md +28 -24
- package/dist/host/agents/skills/orkestrel-parallel/references/claude.md +15 -13
- package/dist/host/agents/skills/orkestrel-parallel/references/codex.md +1 -0
- package/dist/host/agents/skills/orkestrel-parallel/references/cursor.md +13 -13
- package/dist/host/agents/skills/orkestrel-parallel/scripts/cases.ts +389 -0
- package/dist/host/agents/skills/orkestrel-polish/SKILL.md +16 -20
- package/dist/host/agents/skills/orkestrel-polish/references/capture-harness.md +16 -26
- package/dist/host/agents/skills/orkestrel-publish/SKILL.md +12 -26
- package/dist/host/agents/skills/orkestrel-publish/agents/openai.yaml +2 -2
- package/dist/host/agents/skills/orkestrel-publish/references/adopt.md +23 -0
- package/dist/host/agents/skills/orkestrel-publish/references/release.md +4 -4
- package/dist/host/agents/skills/orkestrel-publish/references/wave.md +10 -9
- package/dist/host/agents/skills/orkestrel-publish/references/window.md +20 -25
- package/dist/host/agents/skills/orkestrel-publish/scripts/compare.ts +348 -59
- package/dist/host/agents/skills/orkestrel-publish/scripts/pins.ts +202 -22
- package/dist/host/agents/skills/orkestrel-publish/scripts/swap.ts +1061 -191
- package/dist/host/agents/skills/orkestrel-publish/scripts/wave.ts +812 -140
- package/dist/host/agents/skills/orkestrel-publish/scripts/window.ts +280 -38
- package/dist/host/agents/skills/orkestrel-scout/SKILL.md +3 -3
- package/dist/host/agents/skills/orkestrel-scout/scripts/map.ts +428 -70
- package/dist/host/agents/templates/brief.md +1 -1
- package/dist/host/agents/transports/claude-cli.md +3 -3
- package/dist/host/agents/transports/codex.md +2 -7
- package/dist/host/agents/transports/cursor.md +2 -3
- package/dist/host/claude/AGENTS.md +2 -2
- package/dist/host/claude/agents/analyst.md +3 -3
- package/dist/host/claude/agents/astra.md +3 -3
- package/dist/host/claude/agents/builder.md +1 -1
- package/dist/host/claude/agents/checker.md +1 -1
- package/dist/host/claude/agents/distiller.md +1 -1
- package/dist/host/claude/agents/grok.md +1 -1
- package/dist/host/claude/agents/opus.md +2 -2
- package/dist/host/claude/agents/orkestrel.md +57 -90
- package/dist/host/claude/agents/researcher.md +1 -1
- package/dist/host/claude/agents/reviewer.md +4 -0
- package/dist/host/claude/agents/scout.md +1 -1
- package/dist/host/claude/agents/verifier.md +1 -1
- package/dist/host/claude/rules/application.md +14 -21
- package/dist/host/claude/rules/architecture.md +34 -44
- package/dist/host/claude/rules/browser.md +3 -4
- package/dist/host/claude/rules/campaign.md +2 -2
- package/dist/host/claude/rules/documentation.md +25 -12
- package/dist/host/claude/rules/names.md +9 -4
- package/dist/host/claude/rules/patterns.md +6 -7
- package/dist/host/claude/rules/portability.md +1 -1
- package/dist/host/claude/rules/quality.md +4 -5
- package/dist/host/claude/rules/styles.md +2 -2
- package/dist/host/claude/rules/tests.md +57 -114
- package/dist/host/claude/rules/typescript.md +7 -9
- package/dist/host/claude/rules/workspace.md +21 -19
- package/dist/host/claude/rules/writing.md +2 -5
- package/dist/host/claude/skills/orkestrel-dispatch/SKILL.md +1 -1
- package/dist/host/claude/skills/orkestrel-harden/SKILL.md +1 -1
- package/dist/host/claude/skills/orkestrel-publish/SKILL.md +1 -1
- package/dist/host/claude/skills/orkestrel-scout/SKILL.md +1 -1
- package/dist/host/codex/agents/analyst.toml +9 -3
- package/dist/host/codex/agents/astra.toml +3 -4
- package/dist/host/codex/agents/builder.toml +1 -1
- package/dist/host/codex/agents/opus.toml +3 -4
- package/dist/host/codex/agents/orkestrel.toml +1 -1
- package/dist/host/codex/agents/researcher.toml +2 -2
- package/dist/host/codex/agents/verifier.toml +1 -1
- package/dist/host/configs/helpers.ts +21 -1
- package/dist/host/configs/policy.ts +299 -15
- package/dist/host/dotfiles/oxlintrc.json +137 -3
- package/dist/host/guides/agent.md +111 -134
- package/dist/host/guides/capture.md +801 -0
- package/dist/host/guides/probe.md +27 -8
- package/dist/host/guides/scaffold.md +96 -18
- package/dist/host/guides/study.md +534 -440
- package/dist/host/guides/test.md +111 -44
- package/dist/host/manifest.json +160 -240
- package/dist/host/tests/config.test.ts +971 -187
- package/dist/host/tests/policy.test.ts +127 -1
- package/dist/host/tests/setupPolicy.ts +893 -33
- package/dist/src/core/index.cjs +214 -86
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +122 -38
- package/dist/src/core/index.d.ts +122 -38
- package/dist/src/core/index.js +209 -85
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +20 -27
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.js +21 -28
- package/dist/src/server/index.js.map +1 -1
- package/package.json +11 -11
- package/dist/agents/skills/orkestrel-dispatch/scripts/helpers.js +0 -283
- package/dist/host/agents/skills/orkestrel-dispatch/references/bench.md +0 -25
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/helpers.ts +0 -333
- package/dist/host/guides/supervisor.md +0 -4890
|
@@ -5,34 +5,78 @@
|
|
|
5
5
|
// --cursor resolves the Cursor CLI entry (the newest versioned install under LOCALAPPDATA on
|
|
6
6
|
// Windows, `agent` on PATH elsewhere), reads its version (an entry that cannot answer `--version` is
|
|
7
7
|
// dark, with or without --resolve), and runs one bounded prompt through
|
|
8
|
-
// launch.ts into tmp/cursor/bench.jsonl; --resolve
|
|
8
|
+
// launch.ts into tmp/cursor/bench.jsonl; --resolve, which only --cursor takes, stops after printing
|
|
9
|
+
// the entry. --codex reads
|
|
9
10
|
// `codex --version` and `codex login status`, then runs one bounded exec into tmp/codex/bench.jsonl
|
|
10
|
-
// under the sandbox the host allows (danger-full-access on Windows, read-only elsewhere)
|
|
11
|
+
// under the sandbox the host allows (danger-full-access on Windows, read-only elsewhere) unless
|
|
12
|
+
// --sandbox names read-only, workspace-write, or danger-full-access. --claude
|
|
11
13
|
// reads `claude --version` and runs one bounded print-mode prompt into tmp/claude/bench.jsonl. The
|
|
12
|
-
//
|
|
13
|
-
// the
|
|
14
|
+
// script runs launch.ts and result.ts as child processes beside it and imports neither. The JSON
|
|
15
|
+
// summary carries the command, the version, the session id, the answer, and `live`. --cap is a
|
|
16
|
+
// number of seconds above 0 and at most 2147483, checked before any probe runs. Name one bench per
|
|
17
|
+
// run; the script refuses a flag it does not read, the `--flag=value` form included. Exit 0 when the
|
|
18
|
+
// bench answered READY, 3 when it is dark, 64 on usage.
|
|
14
19
|
import { spawnSync } from 'node:child_process'
|
|
15
|
-
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'
|
|
20
|
+
import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from 'node:fs'
|
|
16
21
|
import { dirname, extname, join } from 'node:path'
|
|
17
22
|
import { fileURLToPath } from 'node:url'
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const SCRIPTS = dirname(fileURLToPath(import.meta.url))
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
|
|
24
|
+
/** Names the directory this script runs from, which holds the launch and result siblings. */
|
|
25
|
+
export const SCRIPTS = dirname(fileURLToPath(import.meta.url))
|
|
26
|
+
|
|
27
|
+
/** Names the extension of the running file: the checkout runs `.ts` sources and a built twin runs `.js` siblings. */
|
|
28
|
+
export const EXTENSION = extname(fileURLToPath(import.meta.url))
|
|
29
|
+
|
|
30
|
+
/** Holds the one-word request every bench round trip sends. */
|
|
31
|
+
export const PROMPT = 'Reply with the single word READY and nothing else.'
|
|
32
|
+
|
|
33
|
+
/** Names the Cursor model a round trip uses unless the call names another. */
|
|
34
|
+
export const CURSOR_MODEL = process.env.CURSOR_GROK_MODEL ?? 'grok-4.7-xhigh'
|
|
35
|
+
|
|
36
|
+
/** Names the Codex model a round trip uses unless the call names another. */
|
|
37
|
+
export const CODEX_MODEL = process.env.CODEX_ASTRA_MODEL ?? 'gpt-6-astra'
|
|
38
|
+
|
|
39
|
+
/** Names the Claude model alias a round trip uses unless the call names another. */
|
|
40
|
+
export const CLAUDE_MODEL = 'opus'
|
|
41
|
+
|
|
42
|
+
/** Holds the seconds a round trip may run when the call gives no cap. */
|
|
43
|
+
export const DEFAULT_CAP = 180
|
|
44
|
+
|
|
45
|
+
/** Holds the longest cap in whole seconds that a Node timer carries through the launcher. */
|
|
46
|
+
export const MAX_CAP_SECONDS = 2_147_483
|
|
47
|
+
|
|
48
|
+
/** Lists the sandbox modes `--sandbox` may name, which the Codex CLI reads. */
|
|
49
|
+
export const SANDBOX_MODES: readonly string[] = [
|
|
50
|
+
'read-only',
|
|
51
|
+
'workspace-write',
|
|
52
|
+
'danger-full-access',
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
/** Lists the flags that each select one bench. */
|
|
56
|
+
export const BENCH_FLAGS: readonly string[] = ['--cursor', '--codex', '--claude']
|
|
57
|
+
|
|
58
|
+
/** Lists the flags that take a value. */
|
|
59
|
+
export const VALUE_FLAGS: readonly string[] = ['--model', '--cap', '--sandbox']
|
|
60
|
+
|
|
61
|
+
/** Lists every flag the script reads, value flags and switches alike. */
|
|
62
|
+
export const FLAGS: readonly string[] = [...BENCH_FLAGS, '--resolve', ...VALUE_FLAGS]
|
|
63
|
+
|
|
64
|
+
/** Holds the usage line printed when a call names no bench or more than one. */
|
|
65
|
+
export const USAGE =
|
|
66
|
+
'usage: bench.ts --cursor [--resolve] [--model ID] [--cap SECONDS] | --codex [--model ID] [--sandbox MODE] [--cap SECONDS] | --claude [--model ALIAS] [--cap SECONDS]; name one bench'
|
|
67
|
+
|
|
68
|
+
/** Names one bench a probe can run against. */
|
|
69
|
+
export type Bench = 'cursor' | 'codex' | 'claude'
|
|
70
|
+
|
|
71
|
+
/** Describes a resolved bench entry: the command that starts it, its version, and why it did not resolve. */
|
|
72
|
+
export interface Entry {
|
|
30
73
|
readonly command: readonly string[]
|
|
31
74
|
readonly version: string | undefined
|
|
32
75
|
readonly error: string | undefined
|
|
33
76
|
}
|
|
34
77
|
|
|
35
|
-
|
|
78
|
+
/** Describes what one round trip observed. */
|
|
79
|
+
export interface Summary {
|
|
36
80
|
readonly session: string | undefined
|
|
37
81
|
readonly answer: string | undefined
|
|
38
82
|
readonly exit: number | undefined
|
|
@@ -40,14 +84,158 @@ interface Summary {
|
|
|
40
84
|
readonly durationMs: number | undefined
|
|
41
85
|
}
|
|
42
86
|
|
|
43
|
-
|
|
87
|
+
/** Describes what a probe decided: the JSON report to print and the exit code to return. */
|
|
88
|
+
export interface Probe {
|
|
89
|
+
readonly report: Record<string, unknown>
|
|
90
|
+
readonly exit: number
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Reads the value that follows a flag.
|
|
95
|
+
*
|
|
96
|
+
* @remarks
|
|
97
|
+
* The first occurrence of a flag wins, a following value that opens with `--` is a missing value,
|
|
98
|
+
* and the `--flag=value` form is not read.
|
|
99
|
+
*
|
|
100
|
+
* @param argv - The arguments after the script path.
|
|
101
|
+
* @param name - The flag, such as `--cap`.
|
|
102
|
+
* @returns The value after the flag, or undefined when the flag is absent, last, or followed by
|
|
103
|
+
* another flag.
|
|
104
|
+
*/
|
|
105
|
+
export function readOption(argv: readonly string[], name: string): string | undefined {
|
|
106
|
+
const index = argv.indexOf(name)
|
|
107
|
+
const value = index === -1 ? undefined : argv[index + 1]
|
|
108
|
+
return value === undefined || value.startsWith('--') ? undefined : value
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Names the value flags a caller gave with no value.
|
|
113
|
+
*
|
|
114
|
+
* @param argv - The arguments after the script path.
|
|
115
|
+
* @param names - The flags that take a value.
|
|
116
|
+
* @returns Each named flag that appears last or is followed by another flag, in argument order.
|
|
117
|
+
*/
|
|
118
|
+
export function readMissingFlags(
|
|
119
|
+
argv: readonly string[],
|
|
120
|
+
names: readonly string[],
|
|
121
|
+
): readonly string[] {
|
|
122
|
+
return names.filter((name) => argv.includes(name) && readOption(argv, name) === undefined)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Names the flags a caller gave that the script does not read.
|
|
127
|
+
*
|
|
128
|
+
* @remarks
|
|
129
|
+
* A `--flag=value` argument is unknown, because the script reads no such form. A bare `--` ends the
|
|
130
|
+
* scan.
|
|
131
|
+
*
|
|
132
|
+
* @param argv - The arguments after the script path.
|
|
133
|
+
* @param flags - Every flag the script reads, value flags and switches alike.
|
|
134
|
+
* @returns Each argument that opens with `--` and is not a read flag, in argument order.
|
|
135
|
+
*/
|
|
136
|
+
export function readUnknownFlags(
|
|
137
|
+
argv: readonly string[],
|
|
138
|
+
flags: readonly string[],
|
|
139
|
+
): readonly string[] {
|
|
140
|
+
const end = argv.indexOf('--')
|
|
141
|
+
return (end === -1 ? argv : argv.slice(0, end)).filter(
|
|
142
|
+
(argument) => argument.startsWith('--') && !flags.includes(argument),
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Quotes an untrusted text for a diagnostic, JSON-encoded and cut to a bounded length.
|
|
148
|
+
*
|
|
149
|
+
* @param text - The text to quote.
|
|
150
|
+
* @returns The JSON string literal of the text, ending in an ellipsis inside the quotes when cut.
|
|
151
|
+
*/
|
|
152
|
+
export function quote(text: string): string {
|
|
153
|
+
return JSON.stringify(text.length > 80 ? `${text.slice(0, 80)}…` : text)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Reads the last non-empty line of a text as a JSON object.
|
|
158
|
+
*
|
|
159
|
+
* @param text - Text whose last line a script printed as JSON.
|
|
160
|
+
* @returns The parsed object, or undefined when the last line is not a JSON object.
|
|
161
|
+
*/
|
|
162
|
+
export function parseJsonLine(text: string): Record<string, unknown> | undefined {
|
|
163
|
+
const line = text
|
|
164
|
+
.split(/\r\n|\n/)
|
|
165
|
+
.filter((candidate) => candidate.trim() !== '')
|
|
166
|
+
.at(-1)
|
|
167
|
+
if (line === undefined) return undefined
|
|
168
|
+
try {
|
|
169
|
+
const parsed: unknown = JSON.parse(line)
|
|
170
|
+
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
171
|
+
return Object.fromEntries(Object.entries(parsed))
|
|
172
|
+
}
|
|
173
|
+
} catch {}
|
|
174
|
+
return undefined
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Reads a string member of a record.
|
|
179
|
+
*
|
|
180
|
+
* @param record - The record, or undefined.
|
|
181
|
+
* @param key - The member name.
|
|
182
|
+
* @returns The string, or undefined when the member is absent or not a string.
|
|
183
|
+
*/
|
|
184
|
+
export function readString(
|
|
185
|
+
record: Record<string, unknown> | undefined,
|
|
186
|
+
key: string,
|
|
187
|
+
): string | undefined {
|
|
188
|
+
const value = record?.[key]
|
|
189
|
+
return typeof value === 'string' ? value : undefined
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Reads a finite number member of a record.
|
|
194
|
+
*
|
|
195
|
+
* @param record - The record, or undefined.
|
|
196
|
+
* @param key - The member name.
|
|
197
|
+
* @returns The number, or undefined when the member is absent, not a number, or not finite.
|
|
198
|
+
*/
|
|
199
|
+
export function readNumber(
|
|
200
|
+
record: Record<string, unknown> | undefined,
|
|
201
|
+
key: string,
|
|
202
|
+
): number | undefined {
|
|
203
|
+
const value = record?.[key]
|
|
204
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Parses the `--cap` value into a number of seconds.
|
|
209
|
+
*
|
|
210
|
+
* @param text - The value after `--cap`, or undefined when the flag is absent.
|
|
211
|
+
* @returns The default cap when the flag is absent, the number when it is finite, above 0, and at
|
|
212
|
+
* most the longest cap, and undefined otherwise.
|
|
213
|
+
*/
|
|
214
|
+
export function parseCap(text: string | undefined): number | undefined {
|
|
215
|
+
if (text === undefined) return DEFAULT_CAP
|
|
216
|
+
const cap = Number(text)
|
|
217
|
+
return Number.isFinite(cap) && cap > 0 && cap <= MAX_CAP_SECONDS ? cap : undefined
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Reads the version a command prints for `--version`.
|
|
222
|
+
*
|
|
223
|
+
* @param command - The executable and the arguments that precede the flag.
|
|
224
|
+
* @returns The trimmed output, or undefined when the command is empty, cannot start, or exits non-zero.
|
|
225
|
+
*/
|
|
226
|
+
export function readVersion(command: readonly string[]): string | undefined {
|
|
44
227
|
const [file, ...args] = command
|
|
45
228
|
if (file === undefined) return undefined
|
|
46
229
|
const result = spawnSync(file, [...args, '--version'], { encoding: 'utf8', windowsHide: true })
|
|
47
230
|
return result.status === 0 ? result.stdout.trim() : undefined
|
|
48
231
|
}
|
|
49
232
|
|
|
50
|
-
|
|
233
|
+
/**
|
|
234
|
+
* Resolves the Cursor CLI entry for this host.
|
|
235
|
+
*
|
|
236
|
+
* @returns The command and its version, or the reason the entry did not resolve.
|
|
237
|
+
*/
|
|
238
|
+
export function resolveCursor(): Entry {
|
|
51
239
|
if (process.platform !== 'win32') {
|
|
52
240
|
return { command: ['agent'], version: readVersion(['agent']), error: undefined }
|
|
53
241
|
}
|
|
@@ -74,10 +262,20 @@ function resolveCursor(): Entry {
|
|
|
74
262
|
return { command: [node, index], version: readVersion([node, index]), error: undefined }
|
|
75
263
|
}
|
|
76
264
|
|
|
77
|
-
|
|
78
|
-
|
|
265
|
+
/**
|
|
266
|
+
* Runs one bounded round trip through the launch and result scripts beside this one.
|
|
267
|
+
*
|
|
268
|
+
* @param bench - The bench whose journal, errors, and answer files receive the run.
|
|
269
|
+
* @param command - The bench command with its arguments and prompt.
|
|
270
|
+
* @param cap - The seconds the launcher allows the command.
|
|
271
|
+
* @param root - The directory the children run in, which holds the `tmp/<bench>` files.
|
|
272
|
+
* @returns What the launcher and the result reader reported.
|
|
273
|
+
*/
|
|
274
|
+
export function runRoundTrip(
|
|
275
|
+
bench: Bench,
|
|
79
276
|
command: readonly string[],
|
|
80
277
|
cap: number,
|
|
278
|
+
root: string,
|
|
81
279
|
): Summary {
|
|
82
280
|
const journal = `tmp/${bench}/bench.jsonl`
|
|
83
281
|
const errors = `tmp/${bench}/bench.err`
|
|
@@ -95,25 +293,34 @@ function runRoundTrip(
|
|
|
95
293
|
'--',
|
|
96
294
|
...command,
|
|
97
295
|
],
|
|
98
|
-
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'], windowsHide: true },
|
|
296
|
+
{ cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'], windowsHide: true },
|
|
99
297
|
)
|
|
100
|
-
const launched =
|
|
298
|
+
const launched = parseJsonLine(launch.stdout)
|
|
101
299
|
const result = spawnSync(
|
|
102
300
|
process.execPath,
|
|
103
301
|
[join(SCRIPTS, `result${EXTENSION}`), `--${bench}`, journal, '--out', answer],
|
|
104
|
-
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'], windowsHide: true },
|
|
302
|
+
{ cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'], windowsHide: true },
|
|
105
303
|
)
|
|
106
|
-
const read =
|
|
304
|
+
const read = parseJsonLine(result.stdout)
|
|
305
|
+
const answerPath = join(root, answer)
|
|
107
306
|
return {
|
|
108
307
|
session: readString(read, 'session'),
|
|
109
|
-
answer: existsSync(
|
|
308
|
+
answer: existsSync(answerPath)
|
|
309
|
+
? readFileSync(answerPath, 'utf8').trim().slice(0, 200)
|
|
310
|
+
: undefined,
|
|
110
311
|
exit: readNumber(launched, 'exit'),
|
|
111
312
|
capped: launched?.capped === true,
|
|
112
313
|
durationMs: readNumber(launched, 'durationMs'),
|
|
113
314
|
}
|
|
114
315
|
}
|
|
115
316
|
|
|
116
|
-
|
|
317
|
+
/**
|
|
318
|
+
* Checks whether a round trip shows a live bench.
|
|
319
|
+
*
|
|
320
|
+
* @param summary - What the round trip observed.
|
|
321
|
+
* @returns True if the run has a session, exited 0 uncapped, and answered READY; false otherwise.
|
|
322
|
+
*/
|
|
323
|
+
export function isLive(summary: Summary): boolean {
|
|
117
324
|
return (
|
|
118
325
|
summary.session !== undefined &&
|
|
119
326
|
summary.exit === 0 &&
|
|
@@ -123,18 +330,26 @@ function isLive(summary: Summary): boolean {
|
|
|
123
330
|
)
|
|
124
331
|
}
|
|
125
332
|
|
|
126
|
-
|
|
333
|
+
/**
|
|
334
|
+
* Probes the Cursor bench, or prints its entry when the call names `--resolve`.
|
|
335
|
+
*
|
|
336
|
+
* @param argv - The arguments after the script path.
|
|
337
|
+
* @param cap - The validated cap in seconds.
|
|
338
|
+
* @param root - The directory the round trip runs in.
|
|
339
|
+
* @returns Exit 0 when live or resolved, 3 when dark, with the report to print.
|
|
340
|
+
*/
|
|
341
|
+
export function probeCursor(argv: readonly string[], cap: number, root: string): Probe {
|
|
127
342
|
const entry = resolveCursor()
|
|
128
343
|
const model = readOption(argv, '--model') ?? CURSOR_MODEL
|
|
129
344
|
if (entry.error !== undefined || entry.version === undefined) {
|
|
130
|
-
|
|
131
|
-
return 3
|
|
345
|
+
return { report: { bench: 'cursor', ...entry, live: false }, exit: 3 }
|
|
132
346
|
}
|
|
133
347
|
if (argv.includes('--resolve')) {
|
|
134
|
-
|
|
135
|
-
|
|
348
|
+
return {
|
|
349
|
+
report: { bench: 'cursor', command: entry.command, version: entry.version },
|
|
350
|
+
exit: 0,
|
|
351
|
+
}
|
|
136
352
|
}
|
|
137
|
-
const cap = Number(readOption(argv, '--cap') ?? DEFAULT_CAP)
|
|
138
353
|
const summary = runRoundTrip(
|
|
139
354
|
'cursor',
|
|
140
355
|
[
|
|
@@ -149,10 +364,11 @@ function probeCursor(argv: readonly string[]): number {
|
|
|
149
364
|
PROMPT,
|
|
150
365
|
],
|
|
151
366
|
cap,
|
|
367
|
+
root,
|
|
152
368
|
)
|
|
153
369
|
const live = isLive(summary)
|
|
154
|
-
|
|
155
|
-
|
|
370
|
+
return {
|
|
371
|
+
report: {
|
|
156
372
|
bench: 'cursor',
|
|
157
373
|
command: entry.command,
|
|
158
374
|
version: entry.version,
|
|
@@ -160,32 +376,48 @@ function probeCursor(argv: readonly string[]): number {
|
|
|
160
376
|
...summary,
|
|
161
377
|
live,
|
|
162
378
|
journal: 'tmp/cursor/bench.jsonl',
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
|
|
379
|
+
},
|
|
380
|
+
exit: live ? 0 : 3,
|
|
381
|
+
}
|
|
166
382
|
}
|
|
167
383
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
384
|
+
/**
|
|
385
|
+
* Probes the Codex bench: its version, its login, and one bounded exec.
|
|
386
|
+
*
|
|
387
|
+
* @param command - The executable and the arguments that precede the Codex arguments, such as `['codex']`.
|
|
388
|
+
* @param argv - The arguments after the script path.
|
|
389
|
+
* @param cap - The validated cap in seconds.
|
|
390
|
+
* @param root - The directory the round trip runs in.
|
|
391
|
+
* @returns Exit 0 when live, 3 when dark, with the report to print.
|
|
392
|
+
*/
|
|
393
|
+
export function probeCodex(
|
|
394
|
+
command: readonly string[],
|
|
395
|
+
argv: readonly string[],
|
|
396
|
+
cap: number,
|
|
397
|
+
root: string,
|
|
398
|
+
): Probe {
|
|
399
|
+
const [file, ...prefix] = command
|
|
400
|
+
const version = readVersion(command)
|
|
401
|
+
const login =
|
|
402
|
+
file === undefined
|
|
403
|
+
? undefined
|
|
404
|
+
: spawnSync(file, [...prefix, 'login', 'status'], { encoding: 'utf8', windowsHide: true })
|
|
171
405
|
const model = readOption(argv, '--model') ?? CODEX_MODEL
|
|
172
406
|
const sandbox =
|
|
173
407
|
readOption(argv, '--sandbox') ??
|
|
174
408
|
(process.platform === 'win32' ? 'danger-full-access' : 'read-only')
|
|
175
|
-
const status = `${login.stdout}${login.stderr}`.trim()
|
|
176
|
-
if (version === undefined || login
|
|
177
|
-
|
|
178
|
-
return 3
|
|
409
|
+
const status = login === undefined ? '' : `${login.stdout ?? ''}${login.stderr ?? ''}`.trim()
|
|
410
|
+
if (version === undefined || login?.status !== 0) {
|
|
411
|
+
return { report: { bench: 'codex', version, login: status, live: false }, exit: 3 }
|
|
179
412
|
}
|
|
180
|
-
const cap = Number(readOption(argv, '--cap') ?? DEFAULT_CAP)
|
|
181
413
|
const summary = runRoundTrip(
|
|
182
414
|
'codex',
|
|
183
415
|
[
|
|
184
|
-
|
|
416
|
+
...command,
|
|
185
417
|
'exec',
|
|
186
418
|
'--json',
|
|
187
419
|
'-C',
|
|
188
|
-
|
|
420
|
+
root,
|
|
189
421
|
'--sandbox',
|
|
190
422
|
sandbox,
|
|
191
423
|
'--model',
|
|
@@ -197,10 +429,11 @@ function probeCodex(argv: readonly string[]): number {
|
|
|
197
429
|
PROMPT,
|
|
198
430
|
],
|
|
199
431
|
cap,
|
|
432
|
+
root,
|
|
200
433
|
)
|
|
201
434
|
const live = isLive(summary)
|
|
202
|
-
|
|
203
|
-
|
|
435
|
+
return {
|
|
436
|
+
report: {
|
|
204
437
|
bench: 'codex',
|
|
205
438
|
version,
|
|
206
439
|
login: status,
|
|
@@ -209,51 +442,109 @@ function probeCodex(argv: readonly string[]): number {
|
|
|
209
442
|
...summary,
|
|
210
443
|
live,
|
|
211
444
|
journal: 'tmp/codex/bench.jsonl',
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
445
|
+
},
|
|
446
|
+
exit: live ? 0 : 3,
|
|
447
|
+
}
|
|
215
448
|
}
|
|
216
449
|
|
|
217
|
-
|
|
218
|
-
|
|
450
|
+
/**
|
|
451
|
+
* Probes the Claude bench: its version and one bounded print-mode prompt.
|
|
452
|
+
*
|
|
453
|
+
* @param command - The executable and the arguments that precede the Claude arguments, such as `['claude']`.
|
|
454
|
+
* @param argv - The arguments after the script path.
|
|
455
|
+
* @param cap - The validated cap in seconds.
|
|
456
|
+
* @param root - The directory the round trip runs in.
|
|
457
|
+
* @returns Exit 0 when live, 3 when dark, with the report to print.
|
|
458
|
+
*/
|
|
459
|
+
export function probeClaude(
|
|
460
|
+
command: readonly string[],
|
|
461
|
+
argv: readonly string[],
|
|
462
|
+
cap: number,
|
|
463
|
+
root: string,
|
|
464
|
+
): Probe {
|
|
465
|
+
const version = readVersion(command)
|
|
219
466
|
const model = readOption(argv, '--model') ?? CLAUDE_MODEL
|
|
220
467
|
if (version === undefined) {
|
|
221
|
-
|
|
222
|
-
return 3
|
|
468
|
+
return { report: { bench: 'claude', version, live: false }, exit: 3 }
|
|
223
469
|
}
|
|
224
|
-
const cap = Number(readOption(argv, '--cap') ?? DEFAULT_CAP)
|
|
225
470
|
const summary = runRoundTrip(
|
|
226
471
|
'claude',
|
|
227
|
-
[
|
|
472
|
+
[...command, '-p', PROMPT, '--output-format', 'stream-json', '--verbose', '--model', model],
|
|
228
473
|
cap,
|
|
474
|
+
root,
|
|
229
475
|
)
|
|
230
476
|
const live = isLive(summary)
|
|
231
|
-
|
|
232
|
-
|
|
477
|
+
return {
|
|
478
|
+
report: {
|
|
233
479
|
bench: 'claude',
|
|
234
480
|
version,
|
|
235
481
|
model,
|
|
236
482
|
...summary,
|
|
237
483
|
live,
|
|
238
484
|
journal: 'tmp/claude/bench.jsonl',
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
|
|
485
|
+
},
|
|
486
|
+
exit: live ? 0 : 3,
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Runs the probe the call names, against the real `codex` or `claude` command.
|
|
492
|
+
*
|
|
493
|
+
* @param argv - The arguments after the script path, which name one bench.
|
|
494
|
+
* @param cap - The validated cap in seconds.
|
|
495
|
+
* @param root - The directory the round trip runs in.
|
|
496
|
+
* @returns What the probe decided.
|
|
497
|
+
*/
|
|
498
|
+
export function selectProbe(argv: readonly string[], cap: number, root: string): Probe {
|
|
499
|
+
if (argv.includes('--cursor')) return probeCursor(argv, cap, root)
|
|
500
|
+
if (argv.includes('--codex')) return probeCodex(['codex'], argv, cap, root)
|
|
501
|
+
return probeClaude(['claude'], argv, cap, root)
|
|
242
502
|
}
|
|
243
503
|
|
|
244
|
-
|
|
245
|
-
|
|
504
|
+
/**
|
|
505
|
+
* Runs the script on its arguments.
|
|
506
|
+
*
|
|
507
|
+
* @param argv - The arguments after the script path.
|
|
508
|
+
* @returns The exit code: 0 live, 3 dark, 64 on usage.
|
|
509
|
+
*/
|
|
510
|
+
export function main(argv: readonly string[]): number {
|
|
511
|
+
const unknown = readUnknownFlags(argv, FLAGS)
|
|
512
|
+
if (unknown.length > 0) {
|
|
513
|
+
console.error(`bench: ${unknown.join(', ')} is not a flag this script reads; ${USAGE}`)
|
|
514
|
+
return 64
|
|
515
|
+
}
|
|
516
|
+
const missing = readMissingFlags(argv, VALUE_FLAGS)
|
|
246
517
|
if (missing.length > 0) {
|
|
247
518
|
console.error(`bench: ${missing.join(', ')} given with no value`)
|
|
248
519
|
return 64
|
|
249
520
|
}
|
|
250
|
-
if (argv.includes(
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
521
|
+
if (BENCH_FLAGS.filter((flag) => argv.includes(flag)).length !== 1) {
|
|
522
|
+
console.error(USAGE)
|
|
523
|
+
return 64
|
|
524
|
+
}
|
|
525
|
+
if (argv.includes('--resolve') && !argv.includes('--cursor')) {
|
|
526
|
+
console.error('bench: --resolve belongs to --cursor and reads nothing for another bench')
|
|
527
|
+
return 64
|
|
528
|
+
}
|
|
529
|
+
const sandbox = readOption(argv, '--sandbox')
|
|
530
|
+
if (sandbox !== undefined && !SANDBOX_MODES.includes(sandbox)) {
|
|
531
|
+
console.error(
|
|
532
|
+
`bench: --sandbox takes one of ${SANDBOX_MODES.join(', ')}; got ${quote(sandbox)}`,
|
|
533
|
+
)
|
|
534
|
+
return 64
|
|
535
|
+
}
|
|
536
|
+
const cap = parseCap(readOption(argv, '--cap'))
|
|
537
|
+
if (cap === undefined) {
|
|
538
|
+
console.error(
|
|
539
|
+
`bench: --cap takes a number of seconds above 0 and at most ${String(MAX_CAP_SECONDS)}`,
|
|
540
|
+
)
|
|
541
|
+
return 64
|
|
542
|
+
}
|
|
543
|
+
const probe = selectProbe(argv, cap, process.cwd())
|
|
544
|
+
console.log(JSON.stringify(probe.report))
|
|
545
|
+
return probe.exit
|
|
257
546
|
}
|
|
258
547
|
|
|
259
|
-
process.
|
|
548
|
+
if (process.argv[1] !== undefined && realpathSync(process.argv[1]) === import.meta.filename) {
|
|
549
|
+
process.exitCode = main(process.argv.slice(2))
|
|
550
|
+
}
|