@orkestrel/scaffold 0.0.77 → 0.0.79
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 +204 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/brief.js +102 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/cite.js +95 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/helpers.js +207 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/launch.js +108 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/login.js +114 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/result.js +108 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/sweep.js +156 -0
- package/dist/agents/skills/orkestrel-harden/scripts/discovery.js +196 -0
- package/dist/agents/skills/orkestrel-publish/scripts/compare.js +206 -0
- package/dist/agents/skills/orkestrel-publish/scripts/pins.js +93 -0
- package/dist/agents/skills/orkestrel-publish/scripts/wave.js +458 -0
- package/dist/agents/skills/orkestrel-publish/scripts/window.js +188 -0
- package/dist/agents/skills/orkestrel-scout/scripts/map.js +300 -0
- package/dist/agents/templates/brief.md +55 -0
- package/dist/bin/main.js +58 -6
- package/dist/bin/main.js.map +1 -1
- package/dist/host/AGENTS.md +77 -135
- package/dist/host/agents/orchestration.md +147 -998
- package/dist/host/agents/skills/enterprise-bootstrap/SKILL.md +2 -2
- package/dist/host/agents/skills/enterprise-bootstrap/references/inspection.md +1 -1
- package/dist/host/agents/skills/{orkestrel-align-packages → orkestrel-align}/SKILL.md +6 -13
- package/dist/host/agents/skills/{orkestrel-align-packages → orkestrel-align}/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/{orkestrel-align-packages → orkestrel-align}/references/fleet.md +5 -7
- package/dist/host/agents/skills/{orkestrel-build-application → orkestrel-build}/SKILL.md +11 -22
- package/dist/host/agents/skills/{orkestrel-build-application → orkestrel-build}/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +8 -16
- package/dist/host/agents/skills/orkestrel-debrief/references/instruction-audit.md +3 -3
- package/dist/host/agents/skills/orkestrel-debrief/references/retention.md +13 -13
- package/dist/host/agents/skills/orkestrel-dispatch/SKILL.md +61 -0
- package/dist/host/agents/skills/orkestrel-dispatch/agents/openai.yaml +4 -0
- package/dist/host/agents/skills/orkestrel-dispatch/references/bench.md +25 -0
- package/dist/host/agents/skills/orkestrel-dispatch/references/launch.md +32 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/bench.ts +259 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/brief.ts +110 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/cite.ts +115 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/helpers.ts +239 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/launch.ts +124 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/login.ts +123 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/result.ts +129 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/sweep.ts +157 -0
- package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +42 -193
- package/dist/host/agents/skills/orkestrel-falsify/references/brief.md +38 -108
- package/dist/host/agents/skills/orkestrel-falsify/references/reconcile.md +35 -134
- package/dist/host/agents/skills/{orkestrel-harden-package → orkestrel-harden}/SKILL.md +10 -14
- package/dist/host/agents/skills/{orkestrel-harden-package → orkestrel-harden}/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/{orkestrel-harden-package → orkestrel-harden}/references/hardening.md +3 -4
- package/dist/host/agents/skills/orkestrel-harden/scripts/discovery.ts +228 -0
- package/dist/host/agents/skills/{orkestrel-prove-journey → orkestrel-journey}/SKILL.md +15 -23
- package/dist/host/agents/skills/orkestrel-journey/agents/openai.yaml +4 -0
- package/dist/host/agents/skills/{orkestrel-prove-journey → orkestrel-journey}/references/captures.md +1 -1
- package/dist/host/agents/skills/{orkestrel-polish-surface → orkestrel-polish}/SKILL.md +25 -33
- package/dist/host/agents/skills/{orkestrel-polish-surface → orkestrel-polish}/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/{orkestrel-polish-surface → orkestrel-polish}/references/capture-harness.md +3 -3
- package/dist/host/agents/skills/orkestrel-publish/SKILL.md +33 -20
- package/dist/host/agents/skills/orkestrel-publish/references/release.md +39 -0
- package/dist/host/agents/skills/orkestrel-publish/references/wave.md +22 -21
- package/dist/host/agents/skills/orkestrel-publish/references/window.md +27 -14
- package/dist/host/agents/skills/orkestrel-publish/scripts/compare.ts +220 -0
- package/dist/host/agents/skills/orkestrel-publish/scripts/pins.ts +114 -0
- package/dist/host/agents/skills/orkestrel-publish/scripts/wave.ts +629 -0
- package/dist/host/agents/skills/orkestrel-publish/scripts/window.ts +242 -0
- package/dist/host/agents/skills/orkestrel-scout/SKILL.md +28 -0
- package/dist/host/agents/skills/orkestrel-scout/agents/openai.yaml +4 -0
- package/dist/host/agents/skills/orkestrel-scout/scripts/map.ts +352 -0
- package/dist/host/agents/templates/brief.md +21 -142
- package/dist/host/agents/transports/claude-cli.md +21 -0
- package/dist/host/agents/transports/codex.md +38 -159
- package/dist/host/agents/transports/cursor.md +16 -65
- package/dist/host/claude/AGENTS.md +38 -0
- package/dist/host/claude/agents/analyst.md +14 -53
- package/dist/host/claude/agents/astra.md +26 -0
- package/dist/host/claude/agents/builder.md +14 -30
- package/dist/host/claude/agents/checker.md +13 -57
- package/dist/host/claude/agents/distiller.md +11 -26
- package/dist/host/claude/agents/grok.md +12 -35
- package/dist/host/claude/agents/opus.md +14 -30
- package/dist/host/claude/agents/orkestrel.md +4 -4
- package/dist/host/claude/agents/planner.md +10 -44
- package/dist/host/claude/agents/researcher.md +11 -30
- package/dist/host/claude/agents/reviewer.md +11 -95
- package/dist/host/claude/agents/scout.md +9 -23
- package/dist/host/claude/agents/verifier.md +15 -33
- package/dist/host/claude/rules/documentation.md +8 -2
- package/dist/host/claude/rules/portability.md +7 -1
- package/dist/host/claude/rules/quality.md +36 -96
- package/dist/host/claude/rules/styles.md +3 -0
- package/dist/host/claude/rules/tests.md +6 -3
- package/dist/host/claude/rules/workspace.md +21 -15
- package/dist/host/claude/rules/writing.md +57 -108
- package/dist/host/claude/settings.json +5 -3
- package/dist/host/claude/skills/enterprise-bootstrap/SKILL.md +1 -1
- package/dist/host/claude/skills/{orkestrel-align-packages → orkestrel-align}/SKILL.md +2 -2
- package/dist/host/claude/skills/{orkestrel-build-application → orkestrel-build}/SKILL.md +2 -2
- package/dist/host/claude/skills/orkestrel-dispatch/SKILL.md +11 -0
- package/dist/host/claude/skills/orkestrel-falsify/SKILL.md +2 -1
- package/dist/host/claude/skills/{orkestrel-harden-package → orkestrel-harden}/SKILL.md +2 -2
- package/dist/host/claude/skills/{orkestrel-prove-journey → orkestrel-journey}/SKILL.md +2 -2
- package/dist/host/claude/skills/orkestrel-polish/SKILL.md +12 -0
- package/dist/host/claude/skills/orkestrel-scout/SKILL.md +11 -0
- package/dist/host/codex/agents/analyst.toml +14 -31
- package/dist/host/codex/agents/astra.toml +25 -0
- package/dist/host/codex/agents/builder.toml +13 -20
- package/dist/host/codex/agents/checker.toml +13 -27
- package/dist/host/codex/agents/distiller.toml +9 -22
- package/dist/host/codex/agents/grok.toml +11 -30
- package/dist/host/codex/agents/opus.toml +14 -22
- package/dist/host/codex/agents/orkestrel.toml +1 -1
- package/dist/host/codex/agents/planner.toml +11 -28
- package/dist/host/codex/agents/researcher.toml +10 -22
- package/dist/host/codex/agents/reviewer.toml +11 -27
- package/dist/host/codex/agents/scout.toml +11 -17
- package/dist/host/codex/agents/verifier.toml +16 -12
- package/dist/host/codex/config.toml +18 -21
- package/dist/host/cursor/mcp.json +0 -4
- package/dist/host/cursor/rules/orchestration.mdc +12 -20
- package/dist/host/dotfiles/mcp.json +0 -4
- package/dist/host/dotfiles/oxlintrc.json +7 -0
- package/dist/host/guides/probe.md +18 -14
- package/dist/host/guides/scaffold.md +147 -83
- package/dist/host/guides/test.md +442 -148
- package/dist/host/manifest.json +322 -185
- package/dist/host/scripts/codex.sh +0 -0
- package/dist/host/scripts/cursor.sh +0 -0
- package/dist/host/scripts/deps.sh +0 -0
- package/dist/host/scripts/ollama.sh +0 -0
- package/dist/host/tests/config.test.ts +86 -55
- package/dist/host/tests/policy.test.ts +1 -5
- package/dist/host/tests/setupPolicy.ts +179 -4
- package/dist/src/core/index.cjs +264 -89
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +95 -30
- package/dist/src/core/index.d.ts +95 -30
- package/dist/src/core/index.js +262 -90
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +55 -9
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +29 -4
- package/dist/src/server/index.d.ts +29 -4
- package/dist/src/server/index.js +56 -11
- package/dist/src/server/index.js.map +1 -1
- package/package.json +16 -12
- package/dist/host/CLAUDE.md +0 -61
- package/dist/host/agents/skills/orkestrel-prove-journey/agents/openai.yaml +0 -4
- package/dist/host/agents/transports/claude.md +0 -49
- package/dist/host/claude/agents/application.md +0 -36
- package/dist/host/claude/agents/sol.md +0 -61
- package/dist/host/claude/skills/orkestrel-polish-surface/SKILL.md +0 -12
- package/dist/host/codex/agents/application.toml +0 -25
- package/dist/host/codex/agents/sol.toml +0 -19
- /package/dist/host/agents/skills/{orkestrel-align-packages → orkestrel-align}/references/integration.md +0 -0
- /package/dist/host/agents/skills/{orkestrel-harden-package → orkestrel-harden}/references/centralization.md +0 -0
- /package/dist/host/agents/skills/{orkestrel-harden-package → orkestrel-harden}/references/contract.md +0 -0
- /package/dist/host/agents/skills/{orkestrel-harden-package → orkestrel-harden}/references/research.md +0 -0
- /package/dist/host/agents/skills/{orkestrel-prove-journey → orkestrel-journey}/references/decide.md +0 -0
- /package/dist/host/agents/skills/{orkestrel-prove-journey → orkestrel-journey}/references/layer.md +0 -0
- /package/dist/host/agents/skills/{orkestrel-prove-journey → orkestrel-journey}/references/statechart.md +0 -0
- /package/dist/host/agents/skills/{orkestrel-prove-journey → orkestrel-journey}/references/styles.md +0 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
// Probe a bench with one bounded round trip and resolve its entry. Run from the checkout root:
|
|
2
|
+
// node .agents/skills/orkestrel-dispatch/scripts/bench.ts --cursor [--resolve] [--model ID] [--cap SECONDS]
|
|
3
|
+
// node .agents/skills/orkestrel-dispatch/scripts/bench.ts --codex [--model ID] [--sandbox MODE] [--cap SECONDS]
|
|
4
|
+
// node .agents/skills/orkestrel-dispatch/scripts/bench.ts --claude [--model ALIAS] [--cap SECONDS]
|
|
5
|
+
// --cursor resolves the Cursor CLI entry (the newest versioned install under LOCALAPPDATA on
|
|
6
|
+
// Windows, `agent` on PATH elsewhere), reads its version (an entry that cannot answer `--version` is
|
|
7
|
+
// dark, with or without --resolve), and runs one bounded prompt through
|
|
8
|
+
// launch.ts into tmp/cursor/bench.jsonl; --resolve stops after printing the entry. --codex reads
|
|
9
|
+
// `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). --claude
|
|
11
|
+
// reads `claude --version` and runs one bounded print-mode prompt into tmp/claude/bench.jsonl. The
|
|
12
|
+
// JSON summary carries the command, the version, the session id, the answer, and `live`. Exit 0 when
|
|
13
|
+
// the bench answered READY, 3 when it is dark, 64 on usage.
|
|
14
|
+
import { spawnSync } from 'node:child_process'
|
|
15
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'
|
|
16
|
+
import { dirname, extname, join } from 'node:path'
|
|
17
|
+
import { fileURLToPath } from 'node:url'
|
|
18
|
+
import { readJsonObject, readMissingFlags, readNumber, readOption, readString } from './helpers.ts'
|
|
19
|
+
|
|
20
|
+
const SCRIPTS = dirname(fileURLToPath(import.meta.url))
|
|
21
|
+
// The checkout runs the `.ts` sources and a built twin under `dist/agents` runs `.js` siblings.
|
|
22
|
+
const EXTENSION = extname(fileURLToPath(import.meta.url))
|
|
23
|
+
const PROMPT = 'Reply with the single word READY and nothing else.'
|
|
24
|
+
const CURSOR_MODEL = process.env.CURSOR_GROK_MODEL ?? 'grok-4.7-high'
|
|
25
|
+
const CODEX_MODEL = process.env.CODEX_ASTRA_MODEL ?? 'gpt-6-astra'
|
|
26
|
+
const CLAUDE_MODEL = 'opus'
|
|
27
|
+
const DEFAULT_CAP = 180
|
|
28
|
+
|
|
29
|
+
interface Entry {
|
|
30
|
+
readonly command: readonly string[]
|
|
31
|
+
readonly version: string | undefined
|
|
32
|
+
readonly error: string | undefined
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface Summary {
|
|
36
|
+
readonly session: string | undefined
|
|
37
|
+
readonly answer: string | undefined
|
|
38
|
+
readonly exit: number | undefined
|
|
39
|
+
readonly capped: boolean
|
|
40
|
+
readonly durationMs: number | undefined
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readVersion(command: readonly string[]): string | undefined {
|
|
44
|
+
const [file, ...args] = command
|
|
45
|
+
if (file === undefined) return undefined
|
|
46
|
+
const result = spawnSync(file, [...args, '--version'], { encoding: 'utf8', windowsHide: true })
|
|
47
|
+
return result.status === 0 ? result.stdout.trim() : undefined
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function resolveCursor(): Entry {
|
|
51
|
+
if (process.platform !== 'win32') {
|
|
52
|
+
return { command: ['agent'], version: readVersion(['agent']), error: undefined }
|
|
53
|
+
}
|
|
54
|
+
const local = process.env.LOCALAPPDATA
|
|
55
|
+
if (local === undefined)
|
|
56
|
+
return { command: [], version: undefined, error: 'LOCALAPPDATA is not set' }
|
|
57
|
+
const versions = join(local, 'cursor-agent', 'versions')
|
|
58
|
+
if (!existsSync(versions) || !statSync(versions).isDirectory()) {
|
|
59
|
+
return { command: [], version: undefined, error: `${versions} is not a directory` }
|
|
60
|
+
}
|
|
61
|
+
const newest = readdirSync(versions, { withFileTypes: true })
|
|
62
|
+
.filter((entry) => entry.isDirectory() && /^\d{4}\.\d{2}\.\d{2}-[0-9a-f]+$/u.test(entry.name))
|
|
63
|
+
.map((entry) => entry.name)
|
|
64
|
+
.sort()
|
|
65
|
+
.at(-1)
|
|
66
|
+
if (newest === undefined) {
|
|
67
|
+
return { command: [], version: undefined, error: `no versioned install under ${versions}` }
|
|
68
|
+
}
|
|
69
|
+
const node = join(versions, newest, 'node.exe')
|
|
70
|
+
const index = join(versions, newest, 'index.js')
|
|
71
|
+
if (!existsSync(node) || !existsSync(index)) {
|
|
72
|
+
return { command: [], version: undefined, error: `${newest} lacks node.exe or index.js` }
|
|
73
|
+
}
|
|
74
|
+
return { command: [node, index], version: readVersion([node, index]), error: undefined }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function runRoundTrip(
|
|
78
|
+
bench: 'cursor' | 'codex' | 'claude',
|
|
79
|
+
command: readonly string[],
|
|
80
|
+
cap: number,
|
|
81
|
+
): Summary {
|
|
82
|
+
const journal = `tmp/${bench}/bench.jsonl`
|
|
83
|
+
const errors = `tmp/${bench}/bench.err`
|
|
84
|
+
const answer = `tmp/${bench}/bench-answer.md`
|
|
85
|
+
const launch = spawnSync(
|
|
86
|
+
process.execPath,
|
|
87
|
+
[
|
|
88
|
+
join(SCRIPTS, `launch${EXTENSION}`),
|
|
89
|
+
'--journal',
|
|
90
|
+
journal,
|
|
91
|
+
'--errors',
|
|
92
|
+
errors,
|
|
93
|
+
'--cap',
|
|
94
|
+
String(cap),
|
|
95
|
+
'--',
|
|
96
|
+
...command,
|
|
97
|
+
],
|
|
98
|
+
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'], windowsHide: true },
|
|
99
|
+
)
|
|
100
|
+
const launched = readJsonObject(launch.stdout)
|
|
101
|
+
const result = spawnSync(
|
|
102
|
+
process.execPath,
|
|
103
|
+
[join(SCRIPTS, `result${EXTENSION}`), `--${bench}`, journal, '--out', answer],
|
|
104
|
+
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'], windowsHide: true },
|
|
105
|
+
)
|
|
106
|
+
const read = readJsonObject(result.stdout)
|
|
107
|
+
return {
|
|
108
|
+
session: readString(read, 'session'),
|
|
109
|
+
answer: existsSync(answer) ? readFileSync(answer, 'utf8').trim().slice(0, 200) : undefined,
|
|
110
|
+
exit: readNumber(launched, 'exit'),
|
|
111
|
+
capped: launched?.capped === true,
|
|
112
|
+
durationMs: readNumber(launched, 'durationMs'),
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function isLive(summary: Summary): boolean {
|
|
117
|
+
return (
|
|
118
|
+
summary.session !== undefined &&
|
|
119
|
+
summary.exit === 0 &&
|
|
120
|
+
!summary.capped &&
|
|
121
|
+
summary.answer !== undefined &&
|
|
122
|
+
/^READY\.?$/u.test(summary.answer.trim())
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function probeCursor(argv: readonly string[]): number {
|
|
127
|
+
const entry = resolveCursor()
|
|
128
|
+
const model = readOption(argv, '--model') ?? CURSOR_MODEL
|
|
129
|
+
if (entry.error !== undefined || entry.version === undefined) {
|
|
130
|
+
console.log(JSON.stringify({ bench: 'cursor', ...entry, live: false }))
|
|
131
|
+
return 3
|
|
132
|
+
}
|
|
133
|
+
if (argv.includes('--resolve')) {
|
|
134
|
+
console.log(JSON.stringify({ bench: 'cursor', command: entry.command, version: entry.version }))
|
|
135
|
+
return 0
|
|
136
|
+
}
|
|
137
|
+
const cap = Number(readOption(argv, '--cap') ?? DEFAULT_CAP)
|
|
138
|
+
const summary = runRoundTrip(
|
|
139
|
+
'cursor',
|
|
140
|
+
[
|
|
141
|
+
...entry.command,
|
|
142
|
+
'-p',
|
|
143
|
+
'--trust',
|
|
144
|
+
'--mode=ask',
|
|
145
|
+
'--model',
|
|
146
|
+
model,
|
|
147
|
+
'--output-format',
|
|
148
|
+
'stream-json',
|
|
149
|
+
PROMPT,
|
|
150
|
+
],
|
|
151
|
+
cap,
|
|
152
|
+
)
|
|
153
|
+
const live = isLive(summary)
|
|
154
|
+
console.log(
|
|
155
|
+
JSON.stringify({
|
|
156
|
+
bench: 'cursor',
|
|
157
|
+
command: entry.command,
|
|
158
|
+
version: entry.version,
|
|
159
|
+
model,
|
|
160
|
+
...summary,
|
|
161
|
+
live,
|
|
162
|
+
journal: 'tmp/cursor/bench.jsonl',
|
|
163
|
+
}),
|
|
164
|
+
)
|
|
165
|
+
return live ? 0 : 3
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function probeCodex(argv: readonly string[]): number {
|
|
169
|
+
const version = readVersion(['codex'])
|
|
170
|
+
const login = spawnSync('codex', ['login', 'status'], { encoding: 'utf8', windowsHide: true })
|
|
171
|
+
const model = readOption(argv, '--model') ?? CODEX_MODEL
|
|
172
|
+
const sandbox =
|
|
173
|
+
readOption(argv, '--sandbox') ??
|
|
174
|
+
(process.platform === 'win32' ? 'danger-full-access' : 'read-only')
|
|
175
|
+
const status = `${login.stdout}${login.stderr}`.trim()
|
|
176
|
+
if (version === undefined || login.status !== 0) {
|
|
177
|
+
console.log(JSON.stringify({ bench: 'codex', version, login: status, live: false }))
|
|
178
|
+
return 3
|
|
179
|
+
}
|
|
180
|
+
const cap = Number(readOption(argv, '--cap') ?? DEFAULT_CAP)
|
|
181
|
+
const summary = runRoundTrip(
|
|
182
|
+
'codex',
|
|
183
|
+
[
|
|
184
|
+
'codex',
|
|
185
|
+
'exec',
|
|
186
|
+
'--json',
|
|
187
|
+
'-C',
|
|
188
|
+
process.cwd(),
|
|
189
|
+
'--sandbox',
|
|
190
|
+
sandbox,
|
|
191
|
+
'--model',
|
|
192
|
+
model,
|
|
193
|
+
'-c',
|
|
194
|
+
'model_reasoning_effort="low"',
|
|
195
|
+
'--output-last-message',
|
|
196
|
+
'tmp/codex/bench-last.md',
|
|
197
|
+
PROMPT,
|
|
198
|
+
],
|
|
199
|
+
cap,
|
|
200
|
+
)
|
|
201
|
+
const live = isLive(summary)
|
|
202
|
+
console.log(
|
|
203
|
+
JSON.stringify({
|
|
204
|
+
bench: 'codex',
|
|
205
|
+
version,
|
|
206
|
+
login: status,
|
|
207
|
+
model,
|
|
208
|
+
sandbox,
|
|
209
|
+
...summary,
|
|
210
|
+
live,
|
|
211
|
+
journal: 'tmp/codex/bench.jsonl',
|
|
212
|
+
}),
|
|
213
|
+
)
|
|
214
|
+
return live ? 0 : 3
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function probeClaude(argv: readonly string[]): number {
|
|
218
|
+
const version = readVersion(['claude'])
|
|
219
|
+
const model = readOption(argv, '--model') ?? CLAUDE_MODEL
|
|
220
|
+
if (version === undefined) {
|
|
221
|
+
console.log(JSON.stringify({ bench: 'claude', version, live: false }))
|
|
222
|
+
return 3
|
|
223
|
+
}
|
|
224
|
+
const cap = Number(readOption(argv, '--cap') ?? DEFAULT_CAP)
|
|
225
|
+
const summary = runRoundTrip(
|
|
226
|
+
'claude',
|
|
227
|
+
['claude', '-p', PROMPT, '--output-format', 'stream-json', '--verbose', '--model', model],
|
|
228
|
+
cap,
|
|
229
|
+
)
|
|
230
|
+
const live = isLive(summary)
|
|
231
|
+
console.log(
|
|
232
|
+
JSON.stringify({
|
|
233
|
+
bench: 'claude',
|
|
234
|
+
version,
|
|
235
|
+
model,
|
|
236
|
+
...summary,
|
|
237
|
+
live,
|
|
238
|
+
journal: 'tmp/claude/bench.jsonl',
|
|
239
|
+
}),
|
|
240
|
+
)
|
|
241
|
+
return live ? 0 : 3
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function main(argv: readonly string[]): number {
|
|
245
|
+
const missing = readMissingFlags(argv, ['--model', '--cap', '--sandbox'])
|
|
246
|
+
if (missing.length > 0) {
|
|
247
|
+
console.error(`bench: ${missing.join(', ')} given with no value`)
|
|
248
|
+
return 64
|
|
249
|
+
}
|
|
250
|
+
if (argv.includes('--cursor')) return probeCursor(argv)
|
|
251
|
+
if (argv.includes('--codex')) return probeCodex(argv)
|
|
252
|
+
if (argv.includes('--claude')) return probeClaude(argv)
|
|
253
|
+
console.error(
|
|
254
|
+
'usage: bench.ts --cursor [--resolve] [--model ID] [--cap SECONDS] | --codex [--model ID] [--sandbox MODE] [--cap SECONDS] | --claude [--model ALIAS] [--cap SECONDS]',
|
|
255
|
+
)
|
|
256
|
+
return 64
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
process.exitCode = main(process.argv.slice(2))
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Write a unit brief from the template, or check that every path a brief names resolves. Run from
|
|
2
|
+
// the checkout root:
|
|
3
|
+
// node .agents/skills/orkestrel-dispatch/scripts/brief.ts --unit UNIT --lane units|cursor|codex|claude [--subject TEXT]
|
|
4
|
+
// node .agents/skills/orkestrel-dispatch/scripts/brief.ts --check tmp/codex/UNIT-brief.md
|
|
5
|
+
// The first form copies the brief template beside this skill (`../../../templates/brief.md`, so a
|
|
6
|
+
// target's installed copy finds it too) to tmp/<lane>/<unit>-brief.md with UNIT_ID and SHORT_SUBJECT
|
|
7
|
+
// filled, and writes <unit>-brief-<n>.md when a brief already exists, so a launched brief is never
|
|
8
|
+
// edited. The second form reads every backticked token carrying a slash (a backslash counts and is
|
|
9
|
+
// read as a slash), strips a `:line` or
|
|
10
|
+
// `:line-line` suffix, resolves it from the working directory, and prints the ones that do not
|
|
11
|
+
// exist; a bare filename is outside the check. Exit 3 when any is missing, 64 on usage.
|
|
12
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'
|
|
13
|
+
import { dirname, join, resolve } from 'node:path'
|
|
14
|
+
import { fileURLToPath } from 'node:url'
|
|
15
|
+
import { readMissingFlags, readOption } from './helpers.ts'
|
|
16
|
+
|
|
17
|
+
const TEMPLATE = resolve(
|
|
18
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
19
|
+
'..',
|
|
20
|
+
'..',
|
|
21
|
+
'..',
|
|
22
|
+
'templates',
|
|
23
|
+
'brief.md',
|
|
24
|
+
)
|
|
25
|
+
const LANES: Readonly<Record<string, string>> = Object.freeze({
|
|
26
|
+
units: 'tmp/units',
|
|
27
|
+
cursor: 'tmp/cursor',
|
|
28
|
+
codex: 'tmp/codex',
|
|
29
|
+
claude: 'tmp/claude',
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
function readsAsPath(token: string): boolean {
|
|
33
|
+
if (token.includes('://') || /[<>*{}\s]/u.test(token)) return false
|
|
34
|
+
if (/[A-Z]{2,}_[A-Z]/u.test(token)) return false
|
|
35
|
+
if (token.startsWith('@') || token.startsWith('-') || token.startsWith('$')) return false
|
|
36
|
+
if (!/^[A-Za-z0-9_.~/-]+$/u.test(token)) return false
|
|
37
|
+
return token.includes('/')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function extractPaths(content: string): readonly string[] {
|
|
41
|
+
const found = new Set<string>()
|
|
42
|
+
for (const match of content.matchAll(/`([^`\n]+)`/gu)) {
|
|
43
|
+
const token = (match[1] ?? '').replace(/\\/gu, '/').replace(/:\d+(?:[-–]\d+)?$/u, '')
|
|
44
|
+
if (readsAsPath(token)) found.add(token)
|
|
45
|
+
}
|
|
46
|
+
return [...found].sort()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function resolveBriefPath(directory: string, unit: string): string {
|
|
50
|
+
const base = join(directory, `${unit}-brief.md`)
|
|
51
|
+
if (!existsSync(base)) return base
|
|
52
|
+
let n = 2
|
|
53
|
+
while (existsSync(join(directory, `${unit}-brief-${n}.md`))) n += 1
|
|
54
|
+
return join(directory, `${unit}-brief-${n}.md`)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function writeBrief(argv: readonly string[]): number {
|
|
58
|
+
const unit = readOption(argv, '--unit')
|
|
59
|
+
const lane = readOption(argv, '--lane')
|
|
60
|
+
const directory = lane === undefined ? undefined : LANES[lane]
|
|
61
|
+
if (unit === undefined || directory === undefined || !/^[A-Za-z0-9._-]+$/u.test(unit)) {
|
|
62
|
+
console.error('usage: brief.ts --unit UNIT --lane units|cursor|codex|claude [--subject TEXT]')
|
|
63
|
+
return 64
|
|
64
|
+
}
|
|
65
|
+
if (!existsSync(TEMPLATE)) {
|
|
66
|
+
console.error(`brief: the template is missing at ${TEMPLATE}`)
|
|
67
|
+
return 64
|
|
68
|
+
}
|
|
69
|
+
mkdirSync(directory, { recursive: true })
|
|
70
|
+
const path = resolveBriefPath(directory, unit)
|
|
71
|
+
const subject = readOption(argv, '--subject') ?? 'SHORT_SUBJECT'
|
|
72
|
+
const content = readFileSync(TEMPLATE, 'utf8')
|
|
73
|
+
.replace(/UNIT_ID/gu, unit)
|
|
74
|
+
.replace(/SHORT_SUBJECT/gu, subject)
|
|
75
|
+
writeFileSync(path, content)
|
|
76
|
+
console.log(JSON.stringify({ brief: path, template: TEMPLATE }))
|
|
77
|
+
return 0
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function checkBrief(path: string): number {
|
|
81
|
+
if (!existsSync(path) || !statSync(path).isFile()) {
|
|
82
|
+
console.error(`brief: ${path} is not a file`)
|
|
83
|
+
return 64
|
|
84
|
+
}
|
|
85
|
+
const paths = extractPaths(readFileSync(path, 'utf8'))
|
|
86
|
+
const missing = paths.filter((candidate) => !existsSync(resolve(candidate)))
|
|
87
|
+
console.log(JSON.stringify({ brief: path, checked: paths.length, missing }))
|
|
88
|
+
return missing.length === 0 ? 0 : 3
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function main(argv: readonly string[]): number {
|
|
92
|
+
const missing = readMissingFlags(argv, ['--unit', '--lane', '--subject', '--check'])
|
|
93
|
+
if (missing.length > 0) {
|
|
94
|
+
console.error(`brief: ${missing.join(', ')} given with no value`)
|
|
95
|
+
return 64
|
|
96
|
+
}
|
|
97
|
+
const check = readOption(argv, '--check')
|
|
98
|
+
if (check !== undefined && argv.includes('--unit')) {
|
|
99
|
+
console.error('brief: --check and --unit are two modes; name one')
|
|
100
|
+
return 64
|
|
101
|
+
}
|
|
102
|
+
if (check !== undefined) return checkBrief(check)
|
|
103
|
+
if (argv.includes('--unit')) return writeBrief(argv)
|
|
104
|
+
console.error(
|
|
105
|
+
'usage: brief.ts --unit UNIT --lane units|cursor|codex|claude [--subject TEXT] | --check PATH',
|
|
106
|
+
)
|
|
107
|
+
return 64
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
process.exitCode = main(process.argv.slice(2))
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// Check that every `file:line` citation in a report, verdict, or distillate resolves. Run from the
|
|
2
|
+
// checkout root:
|
|
3
|
+
// node .agents/skills/orkestrel-dispatch/scripts/cite.ts PATH [--root DIR] [--json]
|
|
4
|
+
// A citation resolves when its path exists under the root (a path relative to the root resolves
|
|
5
|
+
// first, and a backslash reads as a slash) and the line is within the file, where a trailing line
|
|
6
|
+
// break ends the last line. A citation naming a bare filename that no root-relative
|
|
7
|
+
// file matches resolves when exactly one file under the root carries that name, outside
|
|
8
|
+
// node_modules, dist, tmp, .git, .orkestrel, and .idea. The summary lists every citation that does
|
|
9
|
+
// not resolve, with the reason. Exit 0 when all resolve, 3 when any does not, 64 on usage.
|
|
10
|
+
import { existsSync, readFileSync, statSync } from 'node:fs'
|
|
11
|
+
import { basename, relative, resolve } from 'node:path'
|
|
12
|
+
import { listFiles, normalizeSlashes, readMissingFlags, readOption } from './helpers.ts'
|
|
13
|
+
|
|
14
|
+
const CITATION =
|
|
15
|
+
/(?<![\w/\\.-])((?:\.{1,2}[/\\])?\.?[A-Za-z0-9_@][A-Za-z0-9_./\\@-]*\.[a-z]{1,5}):(\d+)(?:[-–](\d+))?(?![\w:])/gu
|
|
16
|
+
|
|
17
|
+
interface Unresolved {
|
|
18
|
+
readonly citation: string
|
|
19
|
+
readonly reason: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface Resolution {
|
|
23
|
+
readonly path: string | undefined
|
|
24
|
+
readonly reason: string | undefined
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function indexBasenames(root: string): ReadonlyMap<string, readonly string[]> {
|
|
28
|
+
const index = new Map<string, string[]>()
|
|
29
|
+
for (const file of listFiles(root)) {
|
|
30
|
+
const name = basename(file.path)
|
|
31
|
+
const paths = index.get(name) ?? []
|
|
32
|
+
paths.push(normalizeSlashes(relative(root, file.path)))
|
|
33
|
+
index.set(name, paths)
|
|
34
|
+
}
|
|
35
|
+
return index
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function countLines(path: string): number {
|
|
39
|
+
const text = readFileSync(path, 'utf8')
|
|
40
|
+
if (text === '') return 0
|
|
41
|
+
const lines = text.split(/\r\n|\n/)
|
|
42
|
+
// A trailing line break ends the last line rather than opening a phantom one.
|
|
43
|
+
return lines.at(-1) === '' ? lines.length - 1 : lines.length
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function resolveFile(
|
|
47
|
+
root: string,
|
|
48
|
+
file: string,
|
|
49
|
+
index: () => ReadonlyMap<string, readonly string[]>,
|
|
50
|
+
): Resolution {
|
|
51
|
+
const direct = resolve(root, normalizeSlashes(file))
|
|
52
|
+
if (existsSync(direct) && statSync(direct).isFile()) return { path: direct, reason: undefined }
|
|
53
|
+
if (/[/\\]/u.test(file)) return { path: undefined, reason: 'file does not exist' }
|
|
54
|
+
const candidates = index().get(basename(file)) ?? []
|
|
55
|
+
const only = candidates.length === 1 ? candidates[0] : undefined
|
|
56
|
+
if (only !== undefined) return { path: resolve(root, only), reason: undefined }
|
|
57
|
+
if (candidates.length === 0) return { path: undefined, reason: 'file does not exist' }
|
|
58
|
+
return { path: undefined, reason: `ambiguous basename: ${candidates.join(', ')}` }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function judgeLines(
|
|
62
|
+
path: string,
|
|
63
|
+
line: number,
|
|
64
|
+
end: number | undefined,
|
|
65
|
+
counts: Map<string, number>,
|
|
66
|
+
): string | undefined {
|
|
67
|
+
const total = counts.get(path) ?? countLines(path)
|
|
68
|
+
counts.set(path, total)
|
|
69
|
+
if (line < 1 || line > total) return `line ${line} is outside 1-${total}`
|
|
70
|
+
if (end !== undefined && (end < line || end > total))
|
|
71
|
+
return `range end ${end} is outside ${line}-${total}`
|
|
72
|
+
return undefined
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function main(argv: readonly string[]): number {
|
|
76
|
+
const missing = readMissingFlags(argv, ['--root'])
|
|
77
|
+
if (missing.length > 0) {
|
|
78
|
+
console.error(`cite: ${missing.join(', ')} given with no value`)
|
|
79
|
+
return 64
|
|
80
|
+
}
|
|
81
|
+
const root = readOption(argv, '--root') ?? process.cwd()
|
|
82
|
+
const path = argv.find(
|
|
83
|
+
(argument, position) => !argument.startsWith('--') && argv[position - 1] !== '--root',
|
|
84
|
+
)
|
|
85
|
+
if (path === undefined || !existsSync(path)) {
|
|
86
|
+
console.error('usage: cite.ts PATH [--root DIR] [--json]')
|
|
87
|
+
return 64
|
|
88
|
+
}
|
|
89
|
+
let index: ReadonlyMap<string, readonly string[]> | undefined
|
|
90
|
+
const counts = new Map<string, number>()
|
|
91
|
+
const unresolved: Unresolved[] = []
|
|
92
|
+
let citations = 0
|
|
93
|
+
for (const match of readFileSync(path, 'utf8').matchAll(CITATION)) {
|
|
94
|
+
const file = match[1]
|
|
95
|
+
const line = Number(match[2])
|
|
96
|
+
const end = match[3] === undefined ? undefined : Number(match[3])
|
|
97
|
+
if (file === undefined) continue
|
|
98
|
+
citations += 1
|
|
99
|
+
const resolved = resolveFile(root, file, () => (index ??= indexBasenames(root)))
|
|
100
|
+
const reason =
|
|
101
|
+
resolved.path === undefined
|
|
102
|
+
? (resolved.reason ?? 'file does not exist')
|
|
103
|
+
: judgeLines(resolved.path, line, end, counts)
|
|
104
|
+
if (reason !== undefined) unresolved.push({ citation: match[0], reason })
|
|
105
|
+
}
|
|
106
|
+
if (argv.includes('--json')) {
|
|
107
|
+
console.log(JSON.stringify({ path, root, citations, unresolved }))
|
|
108
|
+
} else {
|
|
109
|
+
console.log(`cite: ${citations} citation(s) in ${path}, ${unresolved.length} unresolved`)
|
|
110
|
+
for (const entry of unresolved) console.log(`cite: ${entry.citation} — ${entry.reason}`)
|
|
111
|
+
}
|
|
112
|
+
return unresolved.length === 0 ? 0 : 3
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
process.exitCode = main(process.argv.slice(2))
|