@orkestrel/scaffold 0.0.107 → 0.0.109
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +368 -27
- 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 +26 -20
- package/dist/host/agents/orchestration.md +98 -58
- package/dist/host/agents/platforms/posix.md +15 -0
- package/dist/host/agents/platforms/windows.md +37 -0
- 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 +12 -14
- package/dist/host/agents/skills/orkestrel-dispatch/SKILL.md +36 -24
- package/dist/host/agents/skills/orkestrel-dispatch/references/launch.md +14 -11
- 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 +428 -27
- 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 +91 -30
- package/dist/host/agents/skills/orkestrel-parallel/agents/openai.yaml +2 -2
- package/dist/host/agents/skills/orkestrel-parallel/references/claude.md +34 -0
- package/dist/host/agents/skills/orkestrel-parallel/references/codex.md +29 -0
- package/dist/host/agents/skills/orkestrel-parallel/references/cursor.md +29 -0
- package/dist/host/agents/skills/orkestrel-parallel/references/t3.md +31 -0
- 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 +12 -10
- 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 +3 -8
- package/dist/host/agents/transports/cursor.md +2 -3
- package/dist/host/claude/AGENTS.md +7 -10
- 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 +55 -0
- package/dist/host/claude/rules/documentation.md +26 -11
- 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 +2 -3
- 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 +8 -10
- package/dist/host/claude/rules/workspace.md +21 -19
- package/dist/host/claude/rules/writing.md +3 -3
- 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-parallel/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/codex/config.toml +3 -3
- package/dist/host/configs/helpers.ts +21 -1
- package/dist/host/configs/policy.ts +405 -16
- package/dist/host/cursor/rules/orchestration.mdc +1 -0
- package/dist/host/dotfiles/oxlintrc.json +141 -5
- 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 +152 -19
- package/dist/host/guides/study.md +534 -440
- package/dist/host/guides/test.md +111 -44
- package/dist/host/manifest.json +199 -242
- package/dist/host/tests/config.test.ts +1558 -219
- package/dist/host/tests/policy.test.ts +127 -1
- package/dist/host/tests/setupPolicy.ts +1389 -39
- package/dist/src/core/index.cjs +239 -87
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +123 -39
- package/dist/src/core/index.d.ts +123 -39
- package/dist/src/core/index.js +234 -86
- 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/agents/skills/orkestrel-parallel/references/harnesses.md +0 -19
- package/dist/host/guides/supervisor.md +0 -4890
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orkestrel/scaffold",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.109",
|
|
4
4
|
"description": "Scaffold workspaces with commands: new, audit, repair, catalog, and overwrite.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"audit",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"test:templates": "vitest run --config vite.config.ts --no-cache --reporter=dot --project templates",
|
|
85
85
|
"test:distribution": "vitest run --config vite.config.ts --no-cache --reporter=dot --project distribution",
|
|
86
86
|
"test:probe": "vitest run --config vite.config.ts --no-cache --reporter=verbose --project probe",
|
|
87
|
-
"test:bench": "vitest bench --config vite.config.ts --no-cache --project probe",
|
|
87
|
+
"test:bench": "vitest bench --config vite.config.ts --no-cache --project \"probe (bench)\" --mode benchmark",
|
|
88
88
|
"test:setup": "vitest run --config vite.config.ts --no-cache --reporter=dot --project setup",
|
|
89
89
|
"build": "npm run clean && npm run build:src && npm run build:host && npm run build:skills && npm run build:inventory",
|
|
90
90
|
"build:src": "npm run build:src:core && npm run build:src:server && npm run build:src:bin",
|
|
@@ -106,20 +106,20 @@
|
|
|
106
106
|
"@orkestrel/template": "^0.0.10"
|
|
107
107
|
},
|
|
108
108
|
"devDependencies": {
|
|
109
|
-
"@microsoft/api-extractor": "^7.
|
|
109
|
+
"@microsoft/api-extractor": "^7.60.0",
|
|
110
110
|
"@orkestrel/browser": "^0.0.29",
|
|
111
111
|
"@orkestrel/guide": "^0.0.25",
|
|
112
112
|
"@orkestrel/html": "^0.0.12",
|
|
113
|
-
"@orkestrel/probe": "^0.0.
|
|
114
|
-
"@orkestrel/test": "^0.0.
|
|
113
|
+
"@orkestrel/probe": "^0.0.25",
|
|
114
|
+
"@orkestrel/test": "^0.0.30",
|
|
115
115
|
"@types/node": "^24.19.2",
|
|
116
|
-
"@vitest/browser-playwright": "^
|
|
117
|
-
"oxfmt": "^0.
|
|
118
|
-
"oxlint": "^1.
|
|
119
|
-
"playwright": "^1.
|
|
120
|
-
"typescript": "^
|
|
116
|
+
"@vitest/browser-playwright": "^5.0.5",
|
|
117
|
+
"oxfmt": "^0.75.0",
|
|
118
|
+
"oxlint": "^1.90.0",
|
|
119
|
+
"playwright": "^1.64.1",
|
|
120
|
+
"typescript": "^7.0.2",
|
|
121
121
|
"vite": "^8.3.5",
|
|
122
|
-
"vitest": "^
|
|
122
|
+
"vitest": "^5.0.5"
|
|
123
123
|
},
|
|
124
124
|
"engines": {
|
|
125
125
|
"node": ">=22.18.0"
|
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { existsSync, readdirSync, rmSync, 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
|
-
/** Names the manifest fields that hold specs, including nested overrides. */
|
|
14
|
-
export const SPEC_FIELDS = Object.freeze([
|
|
15
|
-
'dependencies',
|
|
16
|
-
'devDependencies',
|
|
17
|
-
'peerDependencies',
|
|
18
|
-
'optionalDependencies',
|
|
19
|
-
'overrides',
|
|
20
|
-
]);
|
|
21
|
-
/** Matches npm local prefixes and path starts, including drive-relative paths. */
|
|
22
|
-
export const LOCAL_SPEC_PATTERN = /^(?:file:|link:|\.|~[\\/]|[\\/]|[A-Za-z]:)/iu;
|
|
23
|
-
/** Matches archive specs with no protocol and a tgz, tar.gz, or tar extension. */
|
|
24
|
-
export const ARCHIVE_SPEC_PATTERN = /^[^:]+\.(?:tgz|tar\.gz|tar)$/iu;
|
|
25
|
-
/** Matches hosted user/repository shorthand even when its name ends in an archive extension. */
|
|
26
|
-
export const SHORTHAND_SPEC_PATTERN = /^[^:@%/\s.-][^:@%/\s]*\/[^:@\s/%]+$/u;
|
|
27
|
-
/**
|
|
28
|
-
* Checks whether a value is a non-null, non-array object.
|
|
29
|
-
*
|
|
30
|
-
* @param value - The value to inspect.
|
|
31
|
-
* @returns True if the value is an object other than null or an array; false otherwise.
|
|
32
|
-
* @example
|
|
33
|
-
* isRecord({ dependencies: {} }) // true
|
|
34
|
-
* isRecord([]) // false
|
|
35
|
-
*/
|
|
36
|
-
export function isRecord(value) {
|
|
37
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Checks whether npm resolves a spec from the local host.
|
|
41
|
-
*
|
|
42
|
-
* @param spec - The package spec, with leading whitespace ignored.
|
|
43
|
-
* @returns True if the spec names a local path or archive; false otherwise.
|
|
44
|
-
* @example
|
|
45
|
-
* matchesLocalSpec('file:../other') // true
|
|
46
|
-
* matchesLocalSpec('owner/package.tgz') // false
|
|
47
|
-
*/
|
|
48
|
-
export function matchesLocalSpec(spec) {
|
|
49
|
-
const text = spec.trimStart();
|
|
50
|
-
return (LOCAL_SPEC_PATTERN.test(text) ||
|
|
51
|
-
(ARCHIVE_SPEC_PATTERN.test(text) && !SHORTHAND_SPEC_PATTERN.test(text)));
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Collects string specs with the dotted path of each nested record member.
|
|
55
|
-
*
|
|
56
|
-
* @param path - The field path that prefixes each finding.
|
|
57
|
-
* @param value - A string or finite, acyclic manifest record; other values contribute no specs.
|
|
58
|
-
* @returns The path and spec pairs in record order.
|
|
59
|
-
* @example
|
|
60
|
-
* collectSpecs('overrides', { a: { b: 'file:../other' } })
|
|
61
|
-
* // [['overrides.a.b', 'file:../other']]
|
|
62
|
-
*/
|
|
63
|
-
export function collectSpecs(path, value) {
|
|
64
|
-
if (typeof value === 'string')
|
|
65
|
-
return [[path, value]];
|
|
66
|
-
if (!isRecord(value))
|
|
67
|
-
return [];
|
|
68
|
-
return Object.entries(value).flatMap(([key, child]) => collectSpecs(`${path}.${key}`, child));
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Finds local specs in the manifest's spec fields, including nested overrides.
|
|
72
|
-
*
|
|
73
|
-
* @param manifest - A finite, acyclic package manifest record.
|
|
74
|
-
* @returns Each local spec's dotted field path and spec, in field and record order.
|
|
75
|
-
* @example
|
|
76
|
-
* scanLocalSpecs({ overrides: { a: { b: 'file:../other' } } })
|
|
77
|
-
* // [['overrides.a.b', 'file:../other']]
|
|
78
|
-
*/
|
|
79
|
-
export function scanLocalSpecs(manifest) {
|
|
80
|
-
return SPEC_FIELDS.flatMap((field) => collectSpecs(field, manifest[field]).filter(([, spec]) => matchesLocalSpec(spec)));
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Removes a tree with bounded retries for transient host removal errors.
|
|
84
|
-
*
|
|
85
|
-
* @param path - The directory to remove; a missing path succeeds.
|
|
86
|
-
* @returns Nothing after the removal succeeds.
|
|
87
|
-
* @throws Thrown when removal fails after the bounded retries or the host refuses the path.
|
|
88
|
-
* @remarks
|
|
89
|
-
* Uses Node's recursive removal with five retries and a 100 ms retry delay.
|
|
90
|
-
* @example
|
|
91
|
-
* removeTree(scratch.path)
|
|
92
|
-
*/
|
|
93
|
-
export function removeTree(path) {
|
|
94
|
-
rmSync(path, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Reads the value that follows a flag.
|
|
98
|
-
*
|
|
99
|
-
* @remarks
|
|
100
|
-
* Every skill script reads `--flag value` pairs: the first occurrence of a flag wins, a following
|
|
101
|
-
* value that opens with `--` is a missing value, and the `--flag=value` form is not read.
|
|
102
|
-
*
|
|
103
|
-
* @param argv - The arguments after the script path.
|
|
104
|
-
* @param name - The flag, such as `--journal`.
|
|
105
|
-
* @returns The value after the flag, or undefined when the flag is absent, last, or followed by
|
|
106
|
-
* another flag.
|
|
107
|
-
*/
|
|
108
|
-
export function readOption(argv, name) {
|
|
109
|
-
const index = argv.indexOf(name);
|
|
110
|
-
const value = index === -1 ? undefined : argv[index + 1];
|
|
111
|
-
return value === undefined || value.startsWith('--') ? undefined : value;
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Names the value flags a caller gave with no value.
|
|
115
|
-
*
|
|
116
|
-
* @param argv - The arguments after the script path.
|
|
117
|
-
* @param names - The flags that take a value.
|
|
118
|
-
* @returns Each named flag that appears last or is followed by another flag, in argument order.
|
|
119
|
-
*/
|
|
120
|
-
export function readMissingFlags(argv, names) {
|
|
121
|
-
return names.filter((name) => argv.includes(name) && readOption(argv, name) === undefined);
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Reads every value that follows a repeatable flag.
|
|
125
|
-
*
|
|
126
|
-
* @param argv - The arguments after the script path.
|
|
127
|
-
* @param name - The flag, such as `--range`.
|
|
128
|
-
* @returns The values in order, empty when the flag never appears.
|
|
129
|
-
*/
|
|
130
|
-
export function readOptions(argv, name) {
|
|
131
|
-
return argv.flatMap((argument, index) => {
|
|
132
|
-
const value = argv[index + 1];
|
|
133
|
-
return argument === name && value !== undefined && !value.startsWith('--') ? [value] : [];
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Reads the values that follow a flag up to the next flag.
|
|
138
|
-
*
|
|
139
|
-
* @param argv - The arguments after the script path.
|
|
140
|
-
* @param name - The flag, such as `--tree`.
|
|
141
|
-
* @returns The values between the flag and the next `--` argument, empty when the flag is absent.
|
|
142
|
-
*/
|
|
143
|
-
export function readList(argv, name) {
|
|
144
|
-
const index = argv.indexOf(name);
|
|
145
|
-
if (index === -1)
|
|
146
|
-
return [];
|
|
147
|
-
const rest = argv.slice(index + 1);
|
|
148
|
-
const end = rest.findIndex((argument) => argument.startsWith('--'));
|
|
149
|
-
return end === -1 ? rest : rest.slice(0, end);
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Normalizes a host path to forward slashes.
|
|
153
|
-
*
|
|
154
|
-
* @param path - A host path.
|
|
155
|
-
* @returns The same path with every backslash replaced by a slash.
|
|
156
|
-
*/
|
|
157
|
-
export function normalizeSlashes(path) {
|
|
158
|
-
return path.split('\\').join('/');
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* Lists every regular file beneath a directory, skipping the directories skill scripts never read.
|
|
162
|
-
*
|
|
163
|
-
* @remarks
|
|
164
|
-
* A skipped name matches a directory at any depth, exactly and case-sensitively, so a
|
|
165
|
-
* `src/dist/` directory is skipped with the build output and a `Node_Modules/` directory is not.
|
|
166
|
-
*
|
|
167
|
-
* @param directory - The directory to walk.
|
|
168
|
-
* @param skipped - The directory names to skip. Default: {@link SKIPPED_DIRECTORIES}.
|
|
169
|
-
* @returns Each file's forward-slash path and modification time, in walk order.
|
|
170
|
-
* @throws Thrown when `directory` does not exist or cannot be read; check `existsSync` first.
|
|
171
|
-
*/
|
|
172
|
-
export function listFiles(directory, skipped = SKIPPED_DIRECTORIES) {
|
|
173
|
-
const files = [];
|
|
174
|
-
const pending = [directory];
|
|
175
|
-
while (pending.length > 0) {
|
|
176
|
-
const current = pending.pop();
|
|
177
|
-
if (current === undefined)
|
|
178
|
-
break;
|
|
179
|
-
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
180
|
-
if (skipped.has(entry.name))
|
|
181
|
-
continue;
|
|
182
|
-
const path = join(current, entry.name);
|
|
183
|
-
if (entry.isDirectory())
|
|
184
|
-
pending.push(path);
|
|
185
|
-
else if (entry.isFile()) {
|
|
186
|
-
files.push({ path: normalizeSlashes(path), modified: statSync(path).mtimeMs });
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
return files;
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Reads the last non-empty line of a text as a JSON object.
|
|
194
|
-
*
|
|
195
|
-
* @param text - Text whose last line a script printed as JSON.
|
|
196
|
-
* @returns The parsed object, or undefined when the last line is not a JSON object.
|
|
197
|
-
*/
|
|
198
|
-
export function readJsonObject(text) {
|
|
199
|
-
const line = text
|
|
200
|
-
.split(/\r\n|\n/)
|
|
201
|
-
.filter((candidate) => candidate.trim() !== '')
|
|
202
|
-
.at(-1);
|
|
203
|
-
if (line === undefined)
|
|
204
|
-
return undefined;
|
|
205
|
-
try {
|
|
206
|
-
const parsed = JSON.parse(line);
|
|
207
|
-
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
208
|
-
return Object.fromEntries(Object.entries(parsed));
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
catch { }
|
|
212
|
-
return undefined;
|
|
213
|
-
}
|
|
214
|
-
/**
|
|
215
|
-
* Parses a whole text as one JSON object.
|
|
216
|
-
*
|
|
217
|
-
* @param text - A JSON document, formatted or not, such as a manifest or `npm view --json` output.
|
|
218
|
-
* @returns The parsed object, or undefined when the text is not a JSON object.
|
|
219
|
-
*/
|
|
220
|
-
export function parseJsonRecord(text) {
|
|
221
|
-
try {
|
|
222
|
-
const parsed = JSON.parse(text);
|
|
223
|
-
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
224
|
-
return Object.fromEntries(Object.entries(parsed));
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
catch { }
|
|
228
|
-
return undefined;
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
|
-
* Reads a string member of a record.
|
|
232
|
-
*
|
|
233
|
-
* @param record - The record, or undefined.
|
|
234
|
-
* @param key - The member name.
|
|
235
|
-
* @returns The string, or undefined when the member is absent or not a string.
|
|
236
|
-
*/
|
|
237
|
-
export function readString(record, key) {
|
|
238
|
-
const value = record?.[key];
|
|
239
|
-
return typeof value === 'string' ? value : undefined;
|
|
240
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* Reads a finite number member of a record.
|
|
243
|
-
*
|
|
244
|
-
* @param record - The record, or undefined.
|
|
245
|
-
* @param key - The member name.
|
|
246
|
-
* @returns The number, or undefined when the member is absent, not a number, or not finite.
|
|
247
|
-
*/
|
|
248
|
-
export function readNumber(record, key) {
|
|
249
|
-
const value = record?.[key];
|
|
250
|
-
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
251
|
-
}
|
|
252
|
-
/**
|
|
253
|
-
* Resolves the npm command for this host.
|
|
254
|
-
*
|
|
255
|
-
* @remarks
|
|
256
|
-
* The `npm-cli.js` entry beside the running Node binary spawns without a shell on every host; the
|
|
257
|
-
* `npm` shim on Windows is a `.cmd` file that needs one, so that path is the fallback alone.
|
|
258
|
-
*
|
|
259
|
-
* @returns The file to spawn, the arguments that precede npm's own, and whether a shell is needed.
|
|
260
|
-
*/
|
|
261
|
-
export function resolveNpm() {
|
|
262
|
-
const cli = join(dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npm-cli.js');
|
|
263
|
-
if (existsSync(cli))
|
|
264
|
-
return { file: process.execPath, prefix: [cli], shell: false };
|
|
265
|
-
return { file: 'npm', prefix: [], shell: process.platform === 'win32' };
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Runs npm with the given arguments and returns the finished child.
|
|
269
|
-
*
|
|
270
|
-
* @param args - The npm arguments, such as `['view', '@orkestrel/scaffold', 'version']`.
|
|
271
|
-
* @param cwd - The working directory. Default: the current directory.
|
|
272
|
-
* @returns The spawn result with `utf8` stdout and stderr.
|
|
273
|
-
*/
|
|
274
|
-
export function runNpm(args, cwd) {
|
|
275
|
-
const npm = resolveNpm();
|
|
276
|
-
return spawnSync(npm.file, [...npm.prefix, ...args], {
|
|
277
|
-
encoding: 'utf8',
|
|
278
|
-
windowsHide: true,
|
|
279
|
-
shell: npm.shell,
|
|
280
|
-
maxBuffer: 64 * 1024 * 1024,
|
|
281
|
-
...(cwd === undefined ? {} : { cwd }),
|
|
282
|
-
});
|
|
283
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# Bench lanes
|
|
2
|
-
|
|
3
|
-
An external engine widens capacity and inherits no authority. Treat every bench output as a proposal until the Orchestrator verifies it against the tree. `.agents/transports/<harness>.md` owns each bench's command, sandbox, and recovery; this file owns what is true of every bench.
|
|
4
|
-
|
|
5
|
-
## Before the lane
|
|
6
|
-
|
|
7
|
-
- Probe the bench once per session with `node .agents/skills/orkestrel-dispatch/scripts/bench.ts --cursor`, `--codex`, or `--claude` before its first lane, and again at any dispatch after a failure. `live: false` records the bench dark. A version string or a login status is not liveness.
|
|
8
|
-
- Write the brief with `scripts/brief.ts --unit <unit> --lane <bench>`; briefs never travel as shell arguments. Run `scripts/brief.ts --check` on it and fix every missing path.
|
|
9
|
-
- Name in the brief the host restrictions the transport contract records that this unit will meet. A proof that needs a restricted capability is taken on the host by the Orchestrator, and the unit records it as an observation naming the exact command.
|
|
10
|
-
- When the sandbox rejects a write, the unit stops and reports the rejection. It never tries another write mechanism, and the brief says so.
|
|
11
|
-
- A bench unit writes only its owned files and its checkout's `tmp/`. Name its report path there, or take the report from the last-message file.
|
|
12
|
-
- Launch one Grok lane at a time. An empty lane beside a live probe is starvation, so cut concurrency and re-run rather than recording the bench dark.
|
|
13
|
-
|
|
14
|
-
## During the lane
|
|
15
|
-
|
|
16
|
-
- The journal under `tmp/<bench>/` is the liveness signal and the recovery handle; `scripts/result.ts` reads both. Register the lane in the session task registry with subject, journal path, and session id.
|
|
17
|
-
- Never poll the bench from inside the driver. The Orchestrator owns the cap and the watch.
|
|
18
|
-
|
|
19
|
-
## After the lane
|
|
20
|
-
|
|
21
|
-
- Refuse a result whose journal path and session id are absent: it ran on the driver's engine.
|
|
22
|
-
- Read the answer with `scripts/result.ts`, never from stdout. Run `scripts/cite.ts` over it before reading it.
|
|
23
|
-
- Verify a writing lane with `git status --porcelain`, the diff, and scoped validation before integrating.
|
|
24
|
-
- A dispatch that fails on auth, quota, model access, or network is a fresh liveness result: record the bench dark and re-plan the lane on the substitute engine.
|
|
25
|
-
- Delete the brief, journal, `.err`, and last-message files when the unit is accepted.
|
|
@@ -1,333 +0,0 @@
|
|
|
1
|
-
// What the skill scripts share: argument reading, tree walking, JSON line reading, and the npm
|
|
2
|
-
// command. This module runs nothing. Import it from a sibling or another skill's script with the
|
|
3
|
-
// `.ts` extension, for example `import { readOption } from '../../orkestrel-dispatch/scripts/helpers.ts'`.
|
|
4
|
-
import type { SpawnSyncReturns } from 'node:child_process'
|
|
5
|
-
import { spawnSync } from 'node:child_process'
|
|
6
|
-
import { existsSync, readdirSync, rmSync, statSync } from 'node:fs'
|
|
7
|
-
import { dirname, join } from 'node:path'
|
|
8
|
-
|
|
9
|
-
/** Names the directories every tree walk in a skill script skips. */
|
|
10
|
-
export const SKIPPED_DIRECTORIES: ReadonlySet<string> = new Set([
|
|
11
|
-
'node_modules',
|
|
12
|
-
'.git',
|
|
13
|
-
'dist',
|
|
14
|
-
'tmp',
|
|
15
|
-
'.orkestrel',
|
|
16
|
-
'.idea',
|
|
17
|
-
])
|
|
18
|
-
|
|
19
|
-
/** Names the manifest fields that hold specs, including nested overrides. */
|
|
20
|
-
export const SPEC_FIELDS: readonly string[] = Object.freeze([
|
|
21
|
-
'dependencies',
|
|
22
|
-
'devDependencies',
|
|
23
|
-
'peerDependencies',
|
|
24
|
-
'optionalDependencies',
|
|
25
|
-
'overrides',
|
|
26
|
-
])
|
|
27
|
-
|
|
28
|
-
/** Matches npm local prefixes and path starts, including drive-relative paths. */
|
|
29
|
-
export const LOCAL_SPEC_PATTERN = /^(?:file:|link:|\.|~[\\/]|[\\/]|[A-Za-z]:)/iu
|
|
30
|
-
|
|
31
|
-
/** Matches archive specs with no protocol and a tgz, tar.gz, or tar extension. */
|
|
32
|
-
export const ARCHIVE_SPEC_PATTERN = /^[^:]+\.(?:tgz|tar\.gz|tar)$/iu
|
|
33
|
-
|
|
34
|
-
/** Matches hosted user/repository shorthand even when its name ends in an archive extension. */
|
|
35
|
-
export const SHORTHAND_SPEC_PATTERN = /^[^:@%/\s.-][^:@%/\s]*\/[^:@\s/%]+$/u
|
|
36
|
-
|
|
37
|
-
/** Describes one file a tree walk found, with the modification time the sweep reads. */
|
|
38
|
-
export interface WalkedFile {
|
|
39
|
-
readonly path: string
|
|
40
|
-
readonly modified: number
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** Describes how to spawn npm on this host without a shell where the CLI entry is present. */
|
|
44
|
-
export interface NpmCommand {
|
|
45
|
-
readonly file: string
|
|
46
|
-
readonly prefix: readonly string[]
|
|
47
|
-
readonly shell: boolean
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Checks whether a value is a non-null, non-array object.
|
|
52
|
-
*
|
|
53
|
-
* @param value - The value to inspect.
|
|
54
|
-
* @returns True if the value is an object other than null or an array; false otherwise.
|
|
55
|
-
* @example
|
|
56
|
-
* isRecord({ dependencies: {} }) // true
|
|
57
|
-
* isRecord([]) // false
|
|
58
|
-
*/
|
|
59
|
-
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
60
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Checks whether npm resolves a spec from the local host.
|
|
65
|
-
*
|
|
66
|
-
* @param spec - The package spec, with leading whitespace ignored.
|
|
67
|
-
* @returns True if the spec names a local path or archive; false otherwise.
|
|
68
|
-
* @example
|
|
69
|
-
* matchesLocalSpec('file:../other') // true
|
|
70
|
-
* matchesLocalSpec('owner/package.tgz') // false
|
|
71
|
-
*/
|
|
72
|
-
export function matchesLocalSpec(spec: string): boolean {
|
|
73
|
-
const text = spec.trimStart()
|
|
74
|
-
return (
|
|
75
|
-
LOCAL_SPEC_PATTERN.test(text) ||
|
|
76
|
-
(ARCHIVE_SPEC_PATTERN.test(text) && !SHORTHAND_SPEC_PATTERN.test(text))
|
|
77
|
-
)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Collects string specs with the dotted path of each nested record member.
|
|
82
|
-
*
|
|
83
|
-
* @param path - The field path that prefixes each finding.
|
|
84
|
-
* @param value - A string or finite, acyclic manifest record; other values contribute no specs.
|
|
85
|
-
* @returns The path and spec pairs in record order.
|
|
86
|
-
* @example
|
|
87
|
-
* collectSpecs('overrides', { a: { b: 'file:../other' } })
|
|
88
|
-
* // [['overrides.a.b', 'file:../other']]
|
|
89
|
-
*/
|
|
90
|
-
export function collectSpecs(
|
|
91
|
-
path: string,
|
|
92
|
-
value: unknown,
|
|
93
|
-
): ReadonlyArray<readonly [string, string]> {
|
|
94
|
-
if (typeof value === 'string') return [[path, value]]
|
|
95
|
-
if (!isRecord(value)) return []
|
|
96
|
-
return Object.entries(value).flatMap(([key, child]) => collectSpecs(`${path}.${key}`, child))
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Finds local specs in the manifest's spec fields, including nested overrides.
|
|
101
|
-
*
|
|
102
|
-
* @param manifest - A finite, acyclic package manifest record.
|
|
103
|
-
* @returns Each local spec's dotted field path and spec, in field and record order.
|
|
104
|
-
* @example
|
|
105
|
-
* scanLocalSpecs({ overrides: { a: { b: 'file:../other' } } })
|
|
106
|
-
* // [['overrides.a.b', 'file:../other']]
|
|
107
|
-
*/
|
|
108
|
-
export function scanLocalSpecs(
|
|
109
|
-
manifest: Readonly<Record<string, unknown>>,
|
|
110
|
-
): ReadonlyArray<readonly [string, string]> {
|
|
111
|
-
return SPEC_FIELDS.flatMap((field) =>
|
|
112
|
-
collectSpecs(field, manifest[field]).filter(([, spec]) => matchesLocalSpec(spec)),
|
|
113
|
-
)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Removes a tree with bounded retries for transient host removal errors.
|
|
118
|
-
*
|
|
119
|
-
* @param path - The directory to remove; a missing path succeeds.
|
|
120
|
-
* @returns Nothing after the removal succeeds.
|
|
121
|
-
* @throws Thrown when removal fails after the bounded retries or the host refuses the path.
|
|
122
|
-
* @remarks
|
|
123
|
-
* Uses Node's recursive removal with five retries and a 100 ms retry delay.
|
|
124
|
-
* @example
|
|
125
|
-
* removeTree(scratch.path)
|
|
126
|
-
*/
|
|
127
|
-
export function removeTree(path: string): void {
|
|
128
|
-
rmSync(path, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 })
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Reads the value that follows a flag.
|
|
133
|
-
*
|
|
134
|
-
* @remarks
|
|
135
|
-
* Every skill script reads `--flag value` pairs: the first occurrence of a flag wins, a following
|
|
136
|
-
* value that opens with `--` is a missing value, and the `--flag=value` form is not read.
|
|
137
|
-
*
|
|
138
|
-
* @param argv - The arguments after the script path.
|
|
139
|
-
* @param name - The flag, such as `--journal`.
|
|
140
|
-
* @returns The value after the flag, or undefined when the flag is absent, last, or followed by
|
|
141
|
-
* another flag.
|
|
142
|
-
*/
|
|
143
|
-
export function readOption(argv: readonly string[], name: string): string | undefined {
|
|
144
|
-
const index = argv.indexOf(name)
|
|
145
|
-
const value = index === -1 ? undefined : argv[index + 1]
|
|
146
|
-
return value === undefined || value.startsWith('--') ? undefined : value
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Names the value flags a caller gave with no value.
|
|
151
|
-
*
|
|
152
|
-
* @param argv - The arguments after the script path.
|
|
153
|
-
* @param names - The flags that take a value.
|
|
154
|
-
* @returns Each named flag that appears last or is followed by another flag, in argument order.
|
|
155
|
-
*/
|
|
156
|
-
export function readMissingFlags(
|
|
157
|
-
argv: readonly string[],
|
|
158
|
-
names: readonly string[],
|
|
159
|
-
): readonly string[] {
|
|
160
|
-
return names.filter((name) => argv.includes(name) && readOption(argv, name) === undefined)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Reads every value that follows a repeatable flag.
|
|
165
|
-
*
|
|
166
|
-
* @param argv - The arguments after the script path.
|
|
167
|
-
* @param name - The flag, such as `--range`.
|
|
168
|
-
* @returns The values in order, empty when the flag never appears.
|
|
169
|
-
*/
|
|
170
|
-
export function readOptions(argv: readonly string[], name: string): readonly string[] {
|
|
171
|
-
return argv.flatMap((argument, index) => {
|
|
172
|
-
const value = argv[index + 1]
|
|
173
|
-
return argument === name && value !== undefined && !value.startsWith('--') ? [value] : []
|
|
174
|
-
})
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Reads the values that follow a flag up to the next flag.
|
|
179
|
-
*
|
|
180
|
-
* @param argv - The arguments after the script path.
|
|
181
|
-
* @param name - The flag, such as `--tree`.
|
|
182
|
-
* @returns The values between the flag and the next `--` argument, empty when the flag is absent.
|
|
183
|
-
*/
|
|
184
|
-
export function readList(argv: readonly string[], name: string): readonly string[] {
|
|
185
|
-
const index = argv.indexOf(name)
|
|
186
|
-
if (index === -1) return []
|
|
187
|
-
const rest = argv.slice(index + 1)
|
|
188
|
-
const end = rest.findIndex((argument) => argument.startsWith('--'))
|
|
189
|
-
return end === -1 ? rest : rest.slice(0, end)
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Normalizes a host path to forward slashes.
|
|
194
|
-
*
|
|
195
|
-
* @param path - A host path.
|
|
196
|
-
* @returns The same path with every backslash replaced by a slash.
|
|
197
|
-
*/
|
|
198
|
-
export function normalizeSlashes(path: string): string {
|
|
199
|
-
return path.split('\\').join('/')
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Lists every regular file beneath a directory, skipping the directories skill scripts never read.
|
|
204
|
-
*
|
|
205
|
-
* @remarks
|
|
206
|
-
* A skipped name matches a directory at any depth, exactly and case-sensitively, so a
|
|
207
|
-
* `src/dist/` directory is skipped with the build output and a `Node_Modules/` directory is not.
|
|
208
|
-
*
|
|
209
|
-
* @param directory - The directory to walk.
|
|
210
|
-
* @param skipped - The directory names to skip. Default: {@link SKIPPED_DIRECTORIES}.
|
|
211
|
-
* @returns Each file's forward-slash path and modification time, in walk order.
|
|
212
|
-
* @throws Thrown when `directory` does not exist or cannot be read; check `existsSync` first.
|
|
213
|
-
*/
|
|
214
|
-
export function listFiles(
|
|
215
|
-
directory: string,
|
|
216
|
-
skipped: ReadonlySet<string> = SKIPPED_DIRECTORIES,
|
|
217
|
-
): readonly WalkedFile[] {
|
|
218
|
-
const files: WalkedFile[] = []
|
|
219
|
-
const pending = [directory]
|
|
220
|
-
while (pending.length > 0) {
|
|
221
|
-
const current = pending.pop()
|
|
222
|
-
if (current === undefined) break
|
|
223
|
-
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
224
|
-
if (skipped.has(entry.name)) continue
|
|
225
|
-
const path = join(current, entry.name)
|
|
226
|
-
if (entry.isDirectory()) pending.push(path)
|
|
227
|
-
else if (entry.isFile()) {
|
|
228
|
-
files.push({ path: normalizeSlashes(path), modified: statSync(path).mtimeMs })
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
return files
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Reads the last non-empty line of a text as a JSON object.
|
|
237
|
-
*
|
|
238
|
-
* @param text - Text whose last line a script printed as JSON.
|
|
239
|
-
* @returns The parsed object, or undefined when the last line is not a JSON object.
|
|
240
|
-
*/
|
|
241
|
-
export function readJsonObject(text: string): Record<string, unknown> | undefined {
|
|
242
|
-
const line = text
|
|
243
|
-
.split(/\r\n|\n/)
|
|
244
|
-
.filter((candidate) => candidate.trim() !== '')
|
|
245
|
-
.at(-1)
|
|
246
|
-
if (line === undefined) return undefined
|
|
247
|
-
try {
|
|
248
|
-
const parsed: unknown = JSON.parse(line)
|
|
249
|
-
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
250
|
-
return Object.fromEntries(Object.entries(parsed))
|
|
251
|
-
}
|
|
252
|
-
} catch {}
|
|
253
|
-
return undefined
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Parses a whole text as one JSON object.
|
|
258
|
-
*
|
|
259
|
-
* @param text - A JSON document, formatted or not, such as a manifest or `npm view --json` output.
|
|
260
|
-
* @returns The parsed object, or undefined when the text is not a JSON object.
|
|
261
|
-
*/
|
|
262
|
-
export function parseJsonRecord(text: string): Record<string, unknown> | undefined {
|
|
263
|
-
try {
|
|
264
|
-
const parsed: unknown = JSON.parse(text)
|
|
265
|
-
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
266
|
-
return Object.fromEntries(Object.entries(parsed))
|
|
267
|
-
}
|
|
268
|
-
} catch {}
|
|
269
|
-
return undefined
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Reads a string member of a record.
|
|
274
|
-
*
|
|
275
|
-
* @param record - The record, or undefined.
|
|
276
|
-
* @param key - The member name.
|
|
277
|
-
* @returns The string, or undefined when the member is absent or not a string.
|
|
278
|
-
*/
|
|
279
|
-
export function readString(
|
|
280
|
-
record: Record<string, unknown> | undefined,
|
|
281
|
-
key: string,
|
|
282
|
-
): string | undefined {
|
|
283
|
-
const value = record?.[key]
|
|
284
|
-
return typeof value === 'string' ? value : undefined
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Reads a finite number member of a record.
|
|
289
|
-
*
|
|
290
|
-
* @param record - The record, or undefined.
|
|
291
|
-
* @param key - The member name.
|
|
292
|
-
* @returns The number, or undefined when the member is absent, not a number, or not finite.
|
|
293
|
-
*/
|
|
294
|
-
export function readNumber(
|
|
295
|
-
record: Record<string, unknown> | undefined,
|
|
296
|
-
key: string,
|
|
297
|
-
): number | undefined {
|
|
298
|
-
const value = record?.[key]
|
|
299
|
-
return typeof value === 'number' && Number.isFinite(value) ? value : undefined
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Resolves the npm command for this host.
|
|
304
|
-
*
|
|
305
|
-
* @remarks
|
|
306
|
-
* The `npm-cli.js` entry beside the running Node binary spawns without a shell on every host; the
|
|
307
|
-
* `npm` shim on Windows is a `.cmd` file that needs one, so that path is the fallback alone.
|
|
308
|
-
*
|
|
309
|
-
* @returns The file to spawn, the arguments that precede npm's own, and whether a shell is needed.
|
|
310
|
-
*/
|
|
311
|
-
export function resolveNpm(): NpmCommand {
|
|
312
|
-
const cli = join(dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npm-cli.js')
|
|
313
|
-
if (existsSync(cli)) return { file: process.execPath, prefix: [cli], shell: false }
|
|
314
|
-
return { file: 'npm', prefix: [], shell: process.platform === 'win32' }
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Runs npm with the given arguments and returns the finished child.
|
|
319
|
-
*
|
|
320
|
-
* @param args - The npm arguments, such as `['view', '@orkestrel/scaffold', 'version']`.
|
|
321
|
-
* @param cwd - The working directory. Default: the current directory.
|
|
322
|
-
* @returns The spawn result with `utf8` stdout and stderr.
|
|
323
|
-
*/
|
|
324
|
-
export function runNpm(args: readonly string[], cwd?: string): SpawnSyncReturns<string> {
|
|
325
|
-
const npm = resolveNpm()
|
|
326
|
-
return spawnSync(npm.file, [...npm.prefix, ...args], {
|
|
327
|
-
encoding: 'utf8',
|
|
328
|
-
windowsHide: true,
|
|
329
|
-
shell: npm.shell,
|
|
330
|
-
maxBuffer: 64 * 1024 * 1024,
|
|
331
|
-
...(cwd === undefined ? {} : { cwd }),
|
|
332
|
-
})
|
|
333
|
-
}
|