@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,204 @@
|
|
|
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.js";
|
|
19
|
+
const SCRIPTS = dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
// The checkout runs the `.ts` sources and a built twin under `dist/agents` runs `.js` siblings.
|
|
21
|
+
const EXTENSION = extname(fileURLToPath(import.meta.url));
|
|
22
|
+
const PROMPT = 'Reply with the single word READY and nothing else.';
|
|
23
|
+
const CURSOR_MODEL = process.env.CURSOR_GROK_MODEL ?? 'grok-4.7-high';
|
|
24
|
+
const CODEX_MODEL = process.env.CODEX_ASTRA_MODEL ?? 'gpt-6-astra';
|
|
25
|
+
const CLAUDE_MODEL = 'opus';
|
|
26
|
+
const DEFAULT_CAP = 180;
|
|
27
|
+
function readVersion(command) {
|
|
28
|
+
const [file, ...args] = command;
|
|
29
|
+
if (file === undefined)
|
|
30
|
+
return undefined;
|
|
31
|
+
const result = spawnSync(file, [...args, '--version'], { encoding: 'utf8', windowsHide: true });
|
|
32
|
+
return result.status === 0 ? result.stdout.trim() : undefined;
|
|
33
|
+
}
|
|
34
|
+
function resolveCursor() {
|
|
35
|
+
if (process.platform !== 'win32') {
|
|
36
|
+
return { command: ['agent'], version: readVersion(['agent']), error: undefined };
|
|
37
|
+
}
|
|
38
|
+
const local = process.env.LOCALAPPDATA;
|
|
39
|
+
if (local === undefined)
|
|
40
|
+
return { command: [], version: undefined, error: 'LOCALAPPDATA is not set' };
|
|
41
|
+
const versions = join(local, 'cursor-agent', 'versions');
|
|
42
|
+
if (!existsSync(versions) || !statSync(versions).isDirectory()) {
|
|
43
|
+
return { command: [], version: undefined, error: `${versions} is not a directory` };
|
|
44
|
+
}
|
|
45
|
+
const newest = readdirSync(versions, { withFileTypes: true })
|
|
46
|
+
.filter((entry) => entry.isDirectory() && /^\d{4}\.\d{2}\.\d{2}-[0-9a-f]+$/u.test(entry.name))
|
|
47
|
+
.map((entry) => entry.name)
|
|
48
|
+
.sort()
|
|
49
|
+
.at(-1);
|
|
50
|
+
if (newest === undefined) {
|
|
51
|
+
return { command: [], version: undefined, error: `no versioned install under ${versions}` };
|
|
52
|
+
}
|
|
53
|
+
const node = join(versions, newest, 'node.exe');
|
|
54
|
+
const index = join(versions, newest, 'index.js');
|
|
55
|
+
if (!existsSync(node) || !existsSync(index)) {
|
|
56
|
+
return { command: [], version: undefined, error: `${newest} lacks node.exe or index.js` };
|
|
57
|
+
}
|
|
58
|
+
return { command: [node, index], version: readVersion([node, index]), error: undefined };
|
|
59
|
+
}
|
|
60
|
+
function runRoundTrip(bench, command, cap) {
|
|
61
|
+
const journal = `tmp/${bench}/bench.jsonl`;
|
|
62
|
+
const errors = `tmp/${bench}/bench.err`;
|
|
63
|
+
const answer = `tmp/${bench}/bench-answer.md`;
|
|
64
|
+
const launch = spawnSync(process.execPath, [
|
|
65
|
+
join(SCRIPTS, `launch${EXTENSION}`),
|
|
66
|
+
'--journal',
|
|
67
|
+
journal,
|
|
68
|
+
'--errors',
|
|
69
|
+
errors,
|
|
70
|
+
'--cap',
|
|
71
|
+
String(cap),
|
|
72
|
+
'--',
|
|
73
|
+
...command,
|
|
74
|
+
], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'], windowsHide: true });
|
|
75
|
+
const launched = readJsonObject(launch.stdout);
|
|
76
|
+
const result = spawnSync(process.execPath, [join(SCRIPTS, `result${EXTENSION}`), `--${bench}`, journal, '--out', answer], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'], windowsHide: true });
|
|
77
|
+
const read = readJsonObject(result.stdout);
|
|
78
|
+
return {
|
|
79
|
+
session: readString(read, 'session'),
|
|
80
|
+
answer: existsSync(answer) ? readFileSync(answer, 'utf8').trim().slice(0, 200) : undefined,
|
|
81
|
+
exit: readNumber(launched, 'exit'),
|
|
82
|
+
capped: launched?.capped === true,
|
|
83
|
+
durationMs: readNumber(launched, 'durationMs'),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function isLive(summary) {
|
|
87
|
+
return (summary.session !== undefined &&
|
|
88
|
+
summary.exit === 0 &&
|
|
89
|
+
!summary.capped &&
|
|
90
|
+
summary.answer !== undefined &&
|
|
91
|
+
/^READY\.?$/u.test(summary.answer.trim()));
|
|
92
|
+
}
|
|
93
|
+
function probeCursor(argv) {
|
|
94
|
+
const entry = resolveCursor();
|
|
95
|
+
const model = readOption(argv, '--model') ?? CURSOR_MODEL;
|
|
96
|
+
if (entry.error !== undefined || entry.version === undefined) {
|
|
97
|
+
console.log(JSON.stringify({ bench: 'cursor', ...entry, live: false }));
|
|
98
|
+
return 3;
|
|
99
|
+
}
|
|
100
|
+
if (argv.includes('--resolve')) {
|
|
101
|
+
console.log(JSON.stringify({ bench: 'cursor', command: entry.command, version: entry.version }));
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
const cap = Number(readOption(argv, '--cap') ?? DEFAULT_CAP);
|
|
105
|
+
const summary = runRoundTrip('cursor', [
|
|
106
|
+
...entry.command,
|
|
107
|
+
'-p',
|
|
108
|
+
'--trust',
|
|
109
|
+
'--mode=ask',
|
|
110
|
+
'--model',
|
|
111
|
+
model,
|
|
112
|
+
'--output-format',
|
|
113
|
+
'stream-json',
|
|
114
|
+
PROMPT,
|
|
115
|
+
], cap);
|
|
116
|
+
const live = isLive(summary);
|
|
117
|
+
console.log(JSON.stringify({
|
|
118
|
+
bench: 'cursor',
|
|
119
|
+
command: entry.command,
|
|
120
|
+
version: entry.version,
|
|
121
|
+
model,
|
|
122
|
+
...summary,
|
|
123
|
+
live,
|
|
124
|
+
journal: 'tmp/cursor/bench.jsonl',
|
|
125
|
+
}));
|
|
126
|
+
return live ? 0 : 3;
|
|
127
|
+
}
|
|
128
|
+
function probeCodex(argv) {
|
|
129
|
+
const version = readVersion(['codex']);
|
|
130
|
+
const login = spawnSync('codex', ['login', 'status'], { encoding: 'utf8', windowsHide: true });
|
|
131
|
+
const model = readOption(argv, '--model') ?? CODEX_MODEL;
|
|
132
|
+
const sandbox = readOption(argv, '--sandbox') ??
|
|
133
|
+
(process.platform === 'win32' ? 'danger-full-access' : 'read-only');
|
|
134
|
+
const status = `${login.stdout}${login.stderr}`.trim();
|
|
135
|
+
if (version === undefined || login.status !== 0) {
|
|
136
|
+
console.log(JSON.stringify({ bench: 'codex', version, login: status, live: false }));
|
|
137
|
+
return 3;
|
|
138
|
+
}
|
|
139
|
+
const cap = Number(readOption(argv, '--cap') ?? DEFAULT_CAP);
|
|
140
|
+
const summary = runRoundTrip('codex', [
|
|
141
|
+
'codex',
|
|
142
|
+
'exec',
|
|
143
|
+
'--json',
|
|
144
|
+
'-C',
|
|
145
|
+
process.cwd(),
|
|
146
|
+
'--sandbox',
|
|
147
|
+
sandbox,
|
|
148
|
+
'--model',
|
|
149
|
+
model,
|
|
150
|
+
'-c',
|
|
151
|
+
'model_reasoning_effort="low"',
|
|
152
|
+
'--output-last-message',
|
|
153
|
+
'tmp/codex/bench-last.md',
|
|
154
|
+
PROMPT,
|
|
155
|
+
], cap);
|
|
156
|
+
const live = isLive(summary);
|
|
157
|
+
console.log(JSON.stringify({
|
|
158
|
+
bench: 'codex',
|
|
159
|
+
version,
|
|
160
|
+
login: status,
|
|
161
|
+
model,
|
|
162
|
+
sandbox,
|
|
163
|
+
...summary,
|
|
164
|
+
live,
|
|
165
|
+
journal: 'tmp/codex/bench.jsonl',
|
|
166
|
+
}));
|
|
167
|
+
return live ? 0 : 3;
|
|
168
|
+
}
|
|
169
|
+
function probeClaude(argv) {
|
|
170
|
+
const version = readVersion(['claude']);
|
|
171
|
+
const model = readOption(argv, '--model') ?? CLAUDE_MODEL;
|
|
172
|
+
if (version === undefined) {
|
|
173
|
+
console.log(JSON.stringify({ bench: 'claude', version, live: false }));
|
|
174
|
+
return 3;
|
|
175
|
+
}
|
|
176
|
+
const cap = Number(readOption(argv, '--cap') ?? DEFAULT_CAP);
|
|
177
|
+
const summary = runRoundTrip('claude', ['claude', '-p', PROMPT, '--output-format', 'stream-json', '--verbose', '--model', model], cap);
|
|
178
|
+
const live = isLive(summary);
|
|
179
|
+
console.log(JSON.stringify({
|
|
180
|
+
bench: 'claude',
|
|
181
|
+
version,
|
|
182
|
+
model,
|
|
183
|
+
...summary,
|
|
184
|
+
live,
|
|
185
|
+
journal: 'tmp/claude/bench.jsonl',
|
|
186
|
+
}));
|
|
187
|
+
return live ? 0 : 3;
|
|
188
|
+
}
|
|
189
|
+
function main(argv) {
|
|
190
|
+
const missing = readMissingFlags(argv, ['--model', '--cap', '--sandbox']);
|
|
191
|
+
if (missing.length > 0) {
|
|
192
|
+
console.error(`bench: ${missing.join(', ')} given with no value`);
|
|
193
|
+
return 64;
|
|
194
|
+
}
|
|
195
|
+
if (argv.includes('--cursor'))
|
|
196
|
+
return probeCursor(argv);
|
|
197
|
+
if (argv.includes('--codex'))
|
|
198
|
+
return probeCodex(argv);
|
|
199
|
+
if (argv.includes('--claude'))
|
|
200
|
+
return probeClaude(argv);
|
|
201
|
+
console.error('usage: bench.ts --cursor [--resolve] [--model ID] [--cap SECONDS] | --codex [--model ID] [--sandbox MODE] [--cap SECONDS] | --claude [--model ALIAS] [--cap SECONDS]');
|
|
202
|
+
return 64;
|
|
203
|
+
}
|
|
204
|
+
process.exitCode = main(process.argv.slice(2));
|
|
@@ -0,0 +1,102 @@
|
|
|
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.js";
|
|
16
|
+
const TEMPLATE = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', 'templates', 'brief.md');
|
|
17
|
+
const LANES = Object.freeze({
|
|
18
|
+
units: 'tmp/units',
|
|
19
|
+
cursor: 'tmp/cursor',
|
|
20
|
+
codex: 'tmp/codex',
|
|
21
|
+
claude: 'tmp/claude',
|
|
22
|
+
});
|
|
23
|
+
function readsAsPath(token) {
|
|
24
|
+
if (token.includes('://') || /[<>*{}\s]/u.test(token))
|
|
25
|
+
return false;
|
|
26
|
+
if (/[A-Z]{2,}_[A-Z]/u.test(token))
|
|
27
|
+
return false;
|
|
28
|
+
if (token.startsWith('@') || token.startsWith('-') || token.startsWith('$'))
|
|
29
|
+
return false;
|
|
30
|
+
if (!/^[A-Za-z0-9_.~/-]+$/u.test(token))
|
|
31
|
+
return false;
|
|
32
|
+
return token.includes('/');
|
|
33
|
+
}
|
|
34
|
+
function extractPaths(content) {
|
|
35
|
+
const found = new Set();
|
|
36
|
+
for (const match of content.matchAll(/`([^`\n]+)`/gu)) {
|
|
37
|
+
const token = (match[1] ?? '').replace(/\\/gu, '/').replace(/:\d+(?:[-–]\d+)?$/u, '');
|
|
38
|
+
if (readsAsPath(token))
|
|
39
|
+
found.add(token);
|
|
40
|
+
}
|
|
41
|
+
return [...found].sort();
|
|
42
|
+
}
|
|
43
|
+
function resolveBriefPath(directory, unit) {
|
|
44
|
+
const base = join(directory, `${unit}-brief.md`);
|
|
45
|
+
if (!existsSync(base))
|
|
46
|
+
return base;
|
|
47
|
+
let n = 2;
|
|
48
|
+
while (existsSync(join(directory, `${unit}-brief-${n}.md`)))
|
|
49
|
+
n += 1;
|
|
50
|
+
return join(directory, `${unit}-brief-${n}.md`);
|
|
51
|
+
}
|
|
52
|
+
function writeBrief(argv) {
|
|
53
|
+
const unit = readOption(argv, '--unit');
|
|
54
|
+
const lane = readOption(argv, '--lane');
|
|
55
|
+
const directory = lane === undefined ? undefined : LANES[lane];
|
|
56
|
+
if (unit === undefined || directory === undefined || !/^[A-Za-z0-9._-]+$/u.test(unit)) {
|
|
57
|
+
console.error('usage: brief.ts --unit UNIT --lane units|cursor|codex|claude [--subject TEXT]');
|
|
58
|
+
return 64;
|
|
59
|
+
}
|
|
60
|
+
if (!existsSync(TEMPLATE)) {
|
|
61
|
+
console.error(`brief: the template is missing at ${TEMPLATE}`);
|
|
62
|
+
return 64;
|
|
63
|
+
}
|
|
64
|
+
mkdirSync(directory, { recursive: true });
|
|
65
|
+
const path = resolveBriefPath(directory, unit);
|
|
66
|
+
const subject = readOption(argv, '--subject') ?? 'SHORT_SUBJECT';
|
|
67
|
+
const content = readFileSync(TEMPLATE, 'utf8')
|
|
68
|
+
.replace(/UNIT_ID/gu, unit)
|
|
69
|
+
.replace(/SHORT_SUBJECT/gu, subject);
|
|
70
|
+
writeFileSync(path, content);
|
|
71
|
+
console.log(JSON.stringify({ brief: path, template: TEMPLATE }));
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
function checkBrief(path) {
|
|
75
|
+
if (!existsSync(path) || !statSync(path).isFile()) {
|
|
76
|
+
console.error(`brief: ${path} is not a file`);
|
|
77
|
+
return 64;
|
|
78
|
+
}
|
|
79
|
+
const paths = extractPaths(readFileSync(path, 'utf8'));
|
|
80
|
+
const missing = paths.filter((candidate) => !existsSync(resolve(candidate)));
|
|
81
|
+
console.log(JSON.stringify({ brief: path, checked: paths.length, missing }));
|
|
82
|
+
return missing.length === 0 ? 0 : 3;
|
|
83
|
+
}
|
|
84
|
+
function main(argv) {
|
|
85
|
+
const missing = readMissingFlags(argv, ['--unit', '--lane', '--subject', '--check']);
|
|
86
|
+
if (missing.length > 0) {
|
|
87
|
+
console.error(`brief: ${missing.join(', ')} given with no value`);
|
|
88
|
+
return 64;
|
|
89
|
+
}
|
|
90
|
+
const check = readOption(argv, '--check');
|
|
91
|
+
if (check !== undefined && argv.includes('--unit')) {
|
|
92
|
+
console.error('brief: --check and --unit are two modes; name one');
|
|
93
|
+
return 64;
|
|
94
|
+
}
|
|
95
|
+
if (check !== undefined)
|
|
96
|
+
return checkBrief(check);
|
|
97
|
+
if (argv.includes('--unit'))
|
|
98
|
+
return writeBrief(argv);
|
|
99
|
+
console.error('usage: brief.ts --unit UNIT --lane units|cursor|codex|claude [--subject TEXT] | --check PATH');
|
|
100
|
+
return 64;
|
|
101
|
+
}
|
|
102
|
+
process.exitCode = main(process.argv.slice(2));
|
|
@@ -0,0 +1,95 @@
|
|
|
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.js";
|
|
13
|
+
const CITATION = /(?<![\w/\\.-])((?:\.{1,2}[/\\])?\.?[A-Za-z0-9_@][A-Za-z0-9_./\\@-]*\.[a-z]{1,5}):(\d+)(?:[-–](\d+))?(?![\w:])/gu;
|
|
14
|
+
function indexBasenames(root) {
|
|
15
|
+
const index = new Map();
|
|
16
|
+
for (const file of listFiles(root)) {
|
|
17
|
+
const name = basename(file.path);
|
|
18
|
+
const paths = index.get(name) ?? [];
|
|
19
|
+
paths.push(normalizeSlashes(relative(root, file.path)));
|
|
20
|
+
index.set(name, paths);
|
|
21
|
+
}
|
|
22
|
+
return index;
|
|
23
|
+
}
|
|
24
|
+
function countLines(path) {
|
|
25
|
+
const text = readFileSync(path, 'utf8');
|
|
26
|
+
if (text === '')
|
|
27
|
+
return 0;
|
|
28
|
+
const lines = text.split(/\r\n|\n/);
|
|
29
|
+
// A trailing line break ends the last line rather than opening a phantom one.
|
|
30
|
+
return lines.at(-1) === '' ? lines.length - 1 : lines.length;
|
|
31
|
+
}
|
|
32
|
+
function resolveFile(root, file, index) {
|
|
33
|
+
const direct = resolve(root, normalizeSlashes(file));
|
|
34
|
+
if (existsSync(direct) && statSync(direct).isFile())
|
|
35
|
+
return { path: direct, reason: undefined };
|
|
36
|
+
if (/[/\\]/u.test(file))
|
|
37
|
+
return { path: undefined, reason: 'file does not exist' };
|
|
38
|
+
const candidates = index().get(basename(file)) ?? [];
|
|
39
|
+
const only = candidates.length === 1 ? candidates[0] : undefined;
|
|
40
|
+
if (only !== undefined)
|
|
41
|
+
return { path: resolve(root, only), reason: undefined };
|
|
42
|
+
if (candidates.length === 0)
|
|
43
|
+
return { path: undefined, reason: 'file does not exist' };
|
|
44
|
+
return { path: undefined, reason: `ambiguous basename: ${candidates.join(', ')}` };
|
|
45
|
+
}
|
|
46
|
+
function judgeLines(path, line, end, counts) {
|
|
47
|
+
const total = counts.get(path) ?? countLines(path);
|
|
48
|
+
counts.set(path, total);
|
|
49
|
+
if (line < 1 || line > total)
|
|
50
|
+
return `line ${line} is outside 1-${total}`;
|
|
51
|
+
if (end !== undefined && (end < line || end > total))
|
|
52
|
+
return `range end ${end} is outside ${line}-${total}`;
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
function main(argv) {
|
|
56
|
+
const missing = readMissingFlags(argv, ['--root']);
|
|
57
|
+
if (missing.length > 0) {
|
|
58
|
+
console.error(`cite: ${missing.join(', ')} given with no value`);
|
|
59
|
+
return 64;
|
|
60
|
+
}
|
|
61
|
+
const root = readOption(argv, '--root') ?? process.cwd();
|
|
62
|
+
const path = argv.find((argument, position) => !argument.startsWith('--') && argv[position - 1] !== '--root');
|
|
63
|
+
if (path === undefined || !existsSync(path)) {
|
|
64
|
+
console.error('usage: cite.ts PATH [--root DIR] [--json]');
|
|
65
|
+
return 64;
|
|
66
|
+
}
|
|
67
|
+
let index;
|
|
68
|
+
const counts = new Map();
|
|
69
|
+
const unresolved = [];
|
|
70
|
+
let citations = 0;
|
|
71
|
+
for (const match of readFileSync(path, 'utf8').matchAll(CITATION)) {
|
|
72
|
+
const file = match[1];
|
|
73
|
+
const line = Number(match[2]);
|
|
74
|
+
const end = match[3] === undefined ? undefined : Number(match[3]);
|
|
75
|
+
if (file === undefined)
|
|
76
|
+
continue;
|
|
77
|
+
citations += 1;
|
|
78
|
+
const resolved = resolveFile(root, file, () => (index ??= indexBasenames(root)));
|
|
79
|
+
const reason = resolved.path === undefined
|
|
80
|
+
? (resolved.reason ?? 'file does not exist')
|
|
81
|
+
: judgeLines(resolved.path, line, end, counts);
|
|
82
|
+
if (reason !== undefined)
|
|
83
|
+
unresolved.push({ citation: match[0], reason });
|
|
84
|
+
}
|
|
85
|
+
if (argv.includes('--json')) {
|
|
86
|
+
console.log(JSON.stringify({ path, root, citations, unresolved }));
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
console.log(`cite: ${citations} citation(s) in ${path}, ${unresolved.length} unresolved`);
|
|
90
|
+
for (const entry of unresolved)
|
|
91
|
+
console.log(`cite: ${entry.citation} — ${entry.reason}`);
|
|
92
|
+
}
|
|
93
|
+
return unresolved.length === 0 ? 0 : 3;
|
|
94
|
+
}
|
|
95
|
+
process.exitCode = main(process.argv.slice(2));
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
/** Names the directories every tree walk in a skill script skips. */
|
|
5
|
+
export const SKIPPED_DIRECTORIES = new Set([
|
|
6
|
+
'node_modules',
|
|
7
|
+
'.git',
|
|
8
|
+
'dist',
|
|
9
|
+
'tmp',
|
|
10
|
+
'.orkestrel',
|
|
11
|
+
'.idea',
|
|
12
|
+
]);
|
|
13
|
+
/**
|
|
14
|
+
* Reads the value that follows a flag.
|
|
15
|
+
*
|
|
16
|
+
* @remarks
|
|
17
|
+
* Every skill script reads `--flag value` pairs: the first occurrence of a flag wins, a following
|
|
18
|
+
* value that opens with `--` is a missing value, and the `--flag=value` form is not read.
|
|
19
|
+
*
|
|
20
|
+
* @param argv - The arguments after the script path.
|
|
21
|
+
* @param name - The flag, such as `--journal`.
|
|
22
|
+
* @returns The value after the flag, or undefined when the flag is absent, last, or followed by
|
|
23
|
+
* another flag.
|
|
24
|
+
*/
|
|
25
|
+
export function readOption(argv, name) {
|
|
26
|
+
const index = argv.indexOf(name);
|
|
27
|
+
const value = index === -1 ? undefined : argv[index + 1];
|
|
28
|
+
return value === undefined || value.startsWith('--') ? undefined : value;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Names the value flags a caller gave with no value.
|
|
32
|
+
*
|
|
33
|
+
* @param argv - The arguments after the script path.
|
|
34
|
+
* @param names - The flags that take a value.
|
|
35
|
+
* @returns Each named flag that appears last or is followed by another flag, in argument order.
|
|
36
|
+
*/
|
|
37
|
+
export function readMissingFlags(argv, names) {
|
|
38
|
+
return names.filter((name) => argv.includes(name) && readOption(argv, name) === undefined);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Reads every value that follows a repeatable flag.
|
|
42
|
+
*
|
|
43
|
+
* @param argv - The arguments after the script path.
|
|
44
|
+
* @param name - The flag, such as `--range`.
|
|
45
|
+
* @returns The values in order, empty when the flag never appears.
|
|
46
|
+
*/
|
|
47
|
+
export function readOptions(argv, name) {
|
|
48
|
+
const values = [];
|
|
49
|
+
argv.forEach((argument, index) => {
|
|
50
|
+
const value = argv[index + 1];
|
|
51
|
+
if (argument === name && value !== undefined && !value.startsWith('--'))
|
|
52
|
+
values.push(value);
|
|
53
|
+
});
|
|
54
|
+
return values;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Reads the values that follow a flag up to the next flag.
|
|
58
|
+
*
|
|
59
|
+
* @param argv - The arguments after the script path.
|
|
60
|
+
* @param name - The flag, such as `--tree`.
|
|
61
|
+
* @returns The values between the flag and the next `--` argument, empty when the flag is absent.
|
|
62
|
+
*/
|
|
63
|
+
export function readList(argv, name) {
|
|
64
|
+
const index = argv.indexOf(name);
|
|
65
|
+
if (index === -1)
|
|
66
|
+
return [];
|
|
67
|
+
const values = [];
|
|
68
|
+
for (const argument of argv.slice(index + 1)) {
|
|
69
|
+
if (argument.startsWith('--'))
|
|
70
|
+
break;
|
|
71
|
+
values.push(argument);
|
|
72
|
+
}
|
|
73
|
+
return values;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Normalizes a host path to forward slashes.
|
|
77
|
+
*
|
|
78
|
+
* @param path - A host path.
|
|
79
|
+
* @returns The same path with every backslash replaced by a slash.
|
|
80
|
+
*/
|
|
81
|
+
export function normalizeSlashes(path) {
|
|
82
|
+
return path.split('\\').join('/');
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Lists every regular file beneath a directory, skipping the directories skill scripts never read.
|
|
86
|
+
*
|
|
87
|
+
* @remarks
|
|
88
|
+
* A skipped name matches a directory at any depth, exactly and case-sensitively, so a
|
|
89
|
+
* `src/dist/` directory is skipped with the build output and a `Node_Modules/` directory is not.
|
|
90
|
+
*
|
|
91
|
+
* @param directory - The directory to walk.
|
|
92
|
+
* @param skipped - The directory names to skip. Default: {@link SKIPPED_DIRECTORIES}.
|
|
93
|
+
* @returns Each file's forward-slash path and modification time, in walk order.
|
|
94
|
+
* @throws Thrown when `directory` does not exist or cannot be read; check `existsSync` first.
|
|
95
|
+
*/
|
|
96
|
+
export function listFiles(directory, skipped = SKIPPED_DIRECTORIES) {
|
|
97
|
+
const files = [];
|
|
98
|
+
const pending = [directory];
|
|
99
|
+
while (pending.length > 0) {
|
|
100
|
+
const current = pending.pop();
|
|
101
|
+
if (current === undefined)
|
|
102
|
+
break;
|
|
103
|
+
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
104
|
+
if (skipped.has(entry.name))
|
|
105
|
+
continue;
|
|
106
|
+
const path = join(current, entry.name);
|
|
107
|
+
if (entry.isDirectory())
|
|
108
|
+
pending.push(path);
|
|
109
|
+
else if (entry.isFile()) {
|
|
110
|
+
files.push({ path: normalizeSlashes(path), modified: statSync(path).mtimeMs });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return files;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Reads the last non-empty line of a text as a JSON object.
|
|
118
|
+
*
|
|
119
|
+
* @param text - Text whose last line a script printed as JSON.
|
|
120
|
+
* @returns The parsed object, or undefined when the last line is not a JSON object.
|
|
121
|
+
*/
|
|
122
|
+
export function readJsonObject(text) {
|
|
123
|
+
const line = text
|
|
124
|
+
.split(/\r\n|\n/)
|
|
125
|
+
.filter((candidate) => candidate.trim() !== '')
|
|
126
|
+
.at(-1);
|
|
127
|
+
if (line === undefined)
|
|
128
|
+
return undefined;
|
|
129
|
+
try {
|
|
130
|
+
const parsed = JSON.parse(line);
|
|
131
|
+
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
132
|
+
return Object.fromEntries(Object.entries(parsed));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch { }
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Parses a whole text as one JSON object.
|
|
140
|
+
*
|
|
141
|
+
* @param text - A JSON document, formatted or not, such as a manifest or `npm view --json` output.
|
|
142
|
+
* @returns The parsed object, or undefined when the text is not a JSON object.
|
|
143
|
+
*/
|
|
144
|
+
export function parseJsonRecord(text) {
|
|
145
|
+
try {
|
|
146
|
+
const parsed = JSON.parse(text);
|
|
147
|
+
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
148
|
+
return Object.fromEntries(Object.entries(parsed));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch { }
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Reads a string member of a record.
|
|
156
|
+
*
|
|
157
|
+
* @param record - The record, or undefined.
|
|
158
|
+
* @param key - The member name.
|
|
159
|
+
* @returns The string, or undefined when the member is absent or not a string.
|
|
160
|
+
*/
|
|
161
|
+
export function readString(record, key) {
|
|
162
|
+
const value = record?.[key];
|
|
163
|
+
return typeof value === 'string' ? value : undefined;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Reads a finite number member of a record.
|
|
167
|
+
*
|
|
168
|
+
* @param record - The record, or undefined.
|
|
169
|
+
* @param key - The member name.
|
|
170
|
+
* @returns The number, or undefined when the member is absent, not a number, or not finite.
|
|
171
|
+
*/
|
|
172
|
+
export function readNumber(record, key) {
|
|
173
|
+
const value = record?.[key];
|
|
174
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Resolves the npm command for this host.
|
|
178
|
+
*
|
|
179
|
+
* @remarks
|
|
180
|
+
* The `npm-cli.js` entry beside the running Node binary spawns without a shell on every host; the
|
|
181
|
+
* `npm` shim on Windows is a `.cmd` file that needs one, so that path is the fallback alone.
|
|
182
|
+
*
|
|
183
|
+
* @returns The file to spawn, the arguments that precede npm's own, and whether a shell is needed.
|
|
184
|
+
*/
|
|
185
|
+
export function resolveNpm() {
|
|
186
|
+
const cli = join(dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npm-cli.js');
|
|
187
|
+
if (existsSync(cli))
|
|
188
|
+
return { file: process.execPath, prefix: [cli], shell: false };
|
|
189
|
+
return { file: 'npm', prefix: [], shell: process.platform === 'win32' };
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Runs npm with the given arguments and returns the finished child.
|
|
193
|
+
*
|
|
194
|
+
* @param args - The npm arguments, such as `['view', '@orkestrel/scaffold', 'version']`.
|
|
195
|
+
* @param cwd - The working directory. Default: the current directory.
|
|
196
|
+
* @returns The spawn result with `utf8` stdout and stderr.
|
|
197
|
+
*/
|
|
198
|
+
export function runNpm(args, cwd) {
|
|
199
|
+
const npm = resolveNpm();
|
|
200
|
+
return spawnSync(npm.file, [...npm.prefix, ...args], {
|
|
201
|
+
encoding: 'utf8',
|
|
202
|
+
windowsHide: true,
|
|
203
|
+
shell: npm.shell,
|
|
204
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
205
|
+
...(cwd === undefined ? {} : { cwd }),
|
|
206
|
+
});
|
|
207
|
+
}
|