autor3search-typescript 0.1.0
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/LICENSE +21 -0
- package/README.md +210 -0
- package/dist/benchproto/types.d.ts +32 -0
- package/dist/benchproto/types.js +28 -0
- package/dist/benchproto/types.js.map +1 -0
- package/dist/benchrun/child.d.ts +1 -0
- package/dist/benchrun/child.js +222 -0
- package/dist/benchrun/child.js.map +1 -0
- package/dist/benchrun/invoke.d.ts +61 -0
- package/dist/benchrun/invoke.js +152 -0
- package/dist/benchrun/invoke.js.map +1 -0
- package/dist/cli/cmd-baseline.d.ts +15 -0
- package/dist/cli/cmd-baseline.js +292 -0
- package/dist/cli/cmd-baseline.js.map +1 -0
- package/dist/cli/cmd-doctor.d.ts +11 -0
- package/dist/cli/cmd-doctor.js +26 -0
- package/dist/cli/cmd-doctor.js.map +1 -0
- package/dist/cli/cmd-eval.d.ts +30 -0
- package/dist/cli/cmd-eval.js +173 -0
- package/dist/cli/cmd-eval.js.map +1 -0
- package/dist/cli/cmd-init.d.ts +16 -0
- package/dist/cli/cmd-init.js +343 -0
- package/dist/cli/cmd-init.js.map +1 -0
- package/dist/cli/cmd-profile.d.ts +11 -0
- package/dist/cli/cmd-profile.js +140 -0
- package/dist/cli/cmd-profile.js.map +1 -0
- package/dist/cli/cmd-report.d.ts +15 -0
- package/dist/cli/cmd-report.js +70 -0
- package/dist/cli/cmd-report.js.map +1 -0
- package/dist/cli/cmd-status.d.ts +11 -0
- package/dist/cli/cmd-status.js +143 -0
- package/dist/cli/cmd-status.js.map +1 -0
- package/dist/cli/cmd-stop.d.ts +15 -0
- package/dist/cli/cmd-stop.js +143 -0
- package/dist/cli/cmd-stop.js.map +1 -0
- package/dist/cli/main.d.ts +31 -0
- package/dist/cli/main.js +133 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/runctx.d.ts +29 -0
- package/dist/cli/runctx.js +49 -0
- package/dist/cli/runctx.js.map +1 -0
- package/dist/cli/speedup.d.ts +14 -0
- package/dist/cli/speedup.js +25 -0
- package/dist/cli/speedup.js.map +1 -0
- package/dist/config/load.d.ts +6 -0
- package/dist/config/load.js +172 -0
- package/dist/config/load.js.map +1 -0
- package/dist/config/schema.d.ts +42 -0
- package/dist/config/schema.js +25 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/discover/benchmarks.d.ts +26 -0
- package/dist/discover/benchmarks.js +88 -0
- package/dist/discover/benchmarks.js.map +1 -0
- package/dist/discover/files.d.ts +23 -0
- package/dist/discover/files.js +82 -0
- package/dist/discover/files.js.map +1 -0
- package/dist/doctor/doctor.d.ts +56 -0
- package/dist/doctor/doctor.js +255 -0
- package/dist/doctor/doctor.js.map +1 -0
- package/dist/freeze/freeze.d.ts +23 -0
- package/dist/freeze/freeze.js +107 -0
- package/dist/freeze/freeze.js.map +1 -0
- package/dist/freeze/manifest.d.ts +6 -0
- package/dist/freeze/manifest.js +9 -0
- package/dist/freeze/manifest.js.map +1 -0
- package/dist/freeze/safepath.d.ts +8 -0
- package/dist/freeze/safepath.js +22 -0
- package/dist/freeze/safepath.js.map +1 -0
- package/dist/gitx/git.d.ts +87 -0
- package/dist/gitx/git.js +235 -0
- package/dist/gitx/git.js.map +1 -0
- package/dist/measure/interleave.d.ts +32 -0
- package/dist/measure/interleave.js +42 -0
- package/dist/measure/interleave.js.map +1 -0
- package/dist/pipeline/eval.d.ts +67 -0
- package/dist/pipeline/eval.js +495 -0
- package/dist/pipeline/eval.js.map +1 -0
- package/dist/pm/detect.d.ts +17 -0
- package/dist/pm/detect.js +131 -0
- package/dist/pm/detect.js.map +1 -0
- package/dist/profile/profile.d.ts +53 -0
- package/dist/profile/profile.js +105 -0
- package/dist/profile/profile.js.map +1 -0
- package/dist/results/results.d.ts +65 -0
- package/dist/results/results.js +214 -0
- package/dist/results/results.js.map +1 -0
- package/dist/runner/exec.d.ts +69 -0
- package/dist/runner/exec.js +185 -0
- package/dist/runner/exec.js.map +1 -0
- package/dist/scope/scope.d.ts +20 -0
- package/dist/scope/scope.js +70 -0
- package/dist/scope/scope.js.map +1 -0
- package/dist/state/baseline.d.ts +35 -0
- package/dist/state/baseline.js +38 -0
- package/dist/state/baseline.js.map +1 -0
- package/dist/state/home.d.ts +9 -0
- package/dist/state/home.js +72 -0
- package/dist/state/home.js.map +1 -0
- package/dist/state/lock.d.ts +11 -0
- package/dist/state/lock.js +89 -0
- package/dist/state/lock.js.map +1 -0
- package/dist/state/runnaming.d.ts +27 -0
- package/dist/state/runnaming.js +47 -0
- package/dist/state/runnaming.js.map +1 -0
- package/dist/state/stop.d.ts +28 -0
- package/dist/state/stop.js +59 -0
- package/dist/state/stop.js.map +1 -0
- package/dist/stats/delta.d.ts +29 -0
- package/dist/stats/delta.js +41 -0
- package/dist/stats/delta.js.map +1 -0
- package/dist/stats/geomean.d.ts +4 -0
- package/dist/stats/geomean.js +21 -0
- package/dist/stats/geomean.js.map +1 -0
- package/dist/stats/mannwhitney.d.ts +27 -0
- package/dist/stats/mannwhitney.js +158 -0
- package/dist/stats/mannwhitney.js.map +1 -0
- package/dist/verdict/verdict.d.ts +46 -0
- package/dist/verdict/verdict.js +102 -0
- package/dist/verdict/verdict.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/package.json +35 -0
- package/templates/program.md +131 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"benchmarks.js","sourceRoot":"","sources":["../../src/discover/benchmarks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type FileKind = 'test' | 'bench' | 'runner-config' | 'source';
|
|
2
|
+
/**
|
|
3
|
+
* Classifies a repo-relative path.
|
|
4
|
+
*
|
|
5
|
+
* Matching is on dot-delimited segments of the basename, not substrings:
|
|
6
|
+
* `benchmarks.ts` and `testing.ts` are ordinary source, and treating them as
|
|
7
|
+
* frozen would quietly make real source un-editable.
|
|
8
|
+
*/
|
|
9
|
+
export declare function classify(rel: string): FileKind;
|
|
10
|
+
/** Every regular file in the repository, repo-relative with POSIX separators. */
|
|
11
|
+
export declare function walkRepo(root: string): Promise<string[]>;
|
|
12
|
+
/**
|
|
13
|
+
* Every symlink in the repository (whatever it points to), repo-relative
|
|
14
|
+
* with POSIX separators, not followed.
|
|
15
|
+
*
|
|
16
|
+
* Exists for `gitx/git.ts`'s `changedFiles`: an untracked symlink is
|
|
17
|
+
* invisible to `walkRepo` by design (see above) but is exactly what Node
|
|
18
|
+
* and `tsc` resolve at measure time, so the scope/clean-tree gates must see
|
|
19
|
+
* it as a change even though the ordinary file walk never will.
|
|
20
|
+
*/
|
|
21
|
+
export declare function listSymlinks(root: string): Promise<string[]>;
|
|
22
|
+
/** Every file that must be frozen at baseline. */
|
|
23
|
+
export declare function freezableFiles(root: string): Promise<string[]>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { readdir } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
/** Directories never walked: build output, dependencies, VCS and tool caches. */
|
|
4
|
+
const SKIP_DIRS = new Set(['node_modules', 'dist', 'build', 'coverage', '.git', 'out']);
|
|
5
|
+
const SCRIPT_EXT = /\.(?:[cm]?[jt]s|[jt]sx)$/;
|
|
6
|
+
const RUNNER_CONFIG = /^(?:vitest|jest|vite)\.config\.(?:[cm]?[jt]s)$/;
|
|
7
|
+
/**
|
|
8
|
+
* Classifies a repo-relative path.
|
|
9
|
+
*
|
|
10
|
+
* Matching is on dot-delimited segments of the basename, not substrings:
|
|
11
|
+
* `benchmarks.ts` and `testing.ts` are ordinary source, and treating them as
|
|
12
|
+
* frozen would quietly make real source un-editable.
|
|
13
|
+
*/
|
|
14
|
+
export function classify(rel) {
|
|
15
|
+
const base = path.posix.basename(rel);
|
|
16
|
+
if (RUNNER_CONFIG.test(base))
|
|
17
|
+
return 'runner-config';
|
|
18
|
+
if (!SCRIPT_EXT.test(base))
|
|
19
|
+
return 'source';
|
|
20
|
+
const parts = base.split('.');
|
|
21
|
+
// parts: name, [qualifier], ext
|
|
22
|
+
const qualifier = parts.length >= 3 ? parts[parts.length - 2] : undefined;
|
|
23
|
+
if (qualifier === 'test' || qualifier === 'spec')
|
|
24
|
+
return 'test';
|
|
25
|
+
if (qualifier === 'bench')
|
|
26
|
+
return 'bench';
|
|
27
|
+
return 'source';
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Walks the repository once, collecting regular files and symlinks
|
|
31
|
+
* separately. Shared by `walkRepo` and `listSymlinks` so the two never
|
|
32
|
+
* silently diverge on which directories are skipped.
|
|
33
|
+
*/
|
|
34
|
+
async function walkAll(root) {
|
|
35
|
+
const files = [];
|
|
36
|
+
const symlinks = [];
|
|
37
|
+
async function walk(dir) {
|
|
38
|
+
const entries = await readdir(path.join(root, dir), { withFileTypes: true });
|
|
39
|
+
for (const e of entries) {
|
|
40
|
+
const rel = dir === '' ? e.name : `${dir}/${e.name}`;
|
|
41
|
+
if (e.isSymbolicLink()) {
|
|
42
|
+
// Recorded, never followed: a symlink is never walked INTO
|
|
43
|
+
// (whether it points to a file or a directory), so its target's
|
|
44
|
+
// contents never appear in `files` regardless of where they live.
|
|
45
|
+
symlinks.push(rel);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (e.isDirectory()) {
|
|
49
|
+
if (SKIP_DIRS.has(e.name) || e.name.startsWith('.'))
|
|
50
|
+
continue;
|
|
51
|
+
await walk(rel);
|
|
52
|
+
}
|
|
53
|
+
else if (e.isFile()) {
|
|
54
|
+
files.push(rel);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
await walk('');
|
|
59
|
+
return { files: files.sort(), symlinks: symlinks.sort() };
|
|
60
|
+
}
|
|
61
|
+
/** Every regular file in the repository, repo-relative with POSIX separators. */
|
|
62
|
+
export async function walkRepo(root) {
|
|
63
|
+
return (await walkAll(root)).files;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Every symlink in the repository (whatever it points to), repo-relative
|
|
67
|
+
* with POSIX separators, not followed.
|
|
68
|
+
*
|
|
69
|
+
* Exists for `gitx/git.ts`'s `changedFiles`: an untracked symlink is
|
|
70
|
+
* invisible to `walkRepo` by design (see above) but is exactly what Node
|
|
71
|
+
* and `tsc` resolve at measure time, so the scope/clean-tree gates must see
|
|
72
|
+
* it as a change even though the ordinary file walk never will.
|
|
73
|
+
*/
|
|
74
|
+
export async function listSymlinks(root) {
|
|
75
|
+
return (await walkAll(root)).symlinks;
|
|
76
|
+
}
|
|
77
|
+
/** Every file that must be frozen at baseline. */
|
|
78
|
+
export async function freezableFiles(root) {
|
|
79
|
+
const all = await walkRepo(root);
|
|
80
|
+
return all.filter((f) => classify(f) !== 'source');
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.js","sourceRoot":"","sources":["../../src/discover/files.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One diagnostic result. `doctor` never fails a run on any of these -- it
|
|
3
|
+
* only ever reports, so `ok` is advice, not a gate.
|
|
4
|
+
*/
|
|
5
|
+
export interface Check {
|
|
6
|
+
name: string;
|
|
7
|
+
ok: boolean;
|
|
8
|
+
detail: string;
|
|
9
|
+
}
|
|
10
|
+
/** Pure so the boundary (21 vs. 22 vs. 23) is testable without spawning Node. */
|
|
11
|
+
export declare function nodeVersionVerdict(version: string): Check;
|
|
12
|
+
/** Pure threshold logic, so the 2 GB floor is testable without a real tiny filesystem. */
|
|
13
|
+
export declare function diskSpaceVerdict(freeBytes: number, dir: string): Check;
|
|
14
|
+
/**
|
|
15
|
+
* Exported (and parameterized on `dir`) so a test can point it at a path
|
|
16
|
+
* that certainly does not exist and assert it degrades instead of
|
|
17
|
+
* throwing, per the brief's own suggested test.
|
|
18
|
+
*/
|
|
19
|
+
export declare function checkDiskSpace(dir: string): Promise<Check>;
|
|
20
|
+
/**
|
|
21
|
+
* Parses `pmset -g batt` output. Pure and exported so the parsing logic is
|
|
22
|
+
* testable with fixture strings, independent of this machine's actual
|
|
23
|
+
* power state.
|
|
24
|
+
*/
|
|
25
|
+
export declare function pmsetBatteryVerdict(stdout: string): Check;
|
|
26
|
+
/**
|
|
27
|
+
* Reads the documented Linux sysfs shape: under `dir`, each `BAT<n>`
|
|
28
|
+
* entry has a `status` file containing `Charging`, `Discharging`,
|
|
29
|
+
* `Full`, etc. Exported and parameterized on `dir` so the shape can be
|
|
30
|
+
* exercised with a fixture directory on any platform -- this project's
|
|
31
|
+
* dev machine is macOS, so this has been verified only against a
|
|
32
|
+
* fabricated fixture, never a real Linux kernel.
|
|
33
|
+
*/
|
|
34
|
+
export declare function checkOnBatteryLinux(dir: string): Promise<Check>;
|
|
35
|
+
/** Pure so "performance" vs. anything else is testable without a real sysfs file. */
|
|
36
|
+
export declare function cpuGovernorVerdict(governor: string): Check;
|
|
37
|
+
/**
|
|
38
|
+
* Reads one sysfs file at a caller-supplied path, so both the "missing
|
|
39
|
+
* file" fallback and the parsing of a real value can be tested with a
|
|
40
|
+
* fixture on any platform. This project's dev machine is macOS, so the
|
|
41
|
+
* fixture-based tests are the only verification this has had -- it has
|
|
42
|
+
* never run against a real Linux kernel's cpufreq sysfs tree.
|
|
43
|
+
*/
|
|
44
|
+
export declare function checkCpuGovernorAt(governorPath: string): Promise<Check>;
|
|
45
|
+
/**
|
|
46
|
+
* Pure so the core-count-relative comparison is testable directly: a raw
|
|
47
|
+
* load of 4 means opposite things on a 4-core and a 64-core machine, so
|
|
48
|
+
* the check is expressed as a fraction of capacity, not a bare number.
|
|
49
|
+
*/
|
|
50
|
+
export declare function loadAverageVerdict(load1: number, cores: number): Check;
|
|
51
|
+
/**
|
|
52
|
+
* Runs every diagnostic. Total by construction: each probe already
|
|
53
|
+
* degrades a missing file or absent command to `ok: true`, and `safe`
|
|
54
|
+
* catches anything that still escapes. Never rejects, on any platform.
|
|
55
|
+
*/
|
|
56
|
+
export declare function runChecks(): Promise<Check[]>;
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { readFile, readdir, statfs } from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { ok as execOk, run } from '../runner/exec.js';
|
|
5
|
+
import { stateHome } from '../state/home.js';
|
|
6
|
+
const MIN_NODE_MAJOR = 22;
|
|
7
|
+
const GIB = 1024 ** 3;
|
|
8
|
+
const MIN_FREE_GIB = 2;
|
|
9
|
+
/** 1-minute load average at or above this fraction of core count counts as "busy". */
|
|
10
|
+
const BUSY_LOAD_RATIO = 0.8;
|
|
11
|
+
const PROBE_TIMEOUT_MS = 5_000;
|
|
12
|
+
/**
|
|
13
|
+
* The shared shape every probe falls back to when its platform-specific
|
|
14
|
+
* mechanism is missing or unreadable: `ok: true` (this is advice, not a
|
|
15
|
+
* failure of the machine) with a detail that says plainly why no real
|
|
16
|
+
* measurement was possible, so the user can tell "checked and fine" apart
|
|
17
|
+
* from "could not check."
|
|
18
|
+
*/
|
|
19
|
+
function notAvailable(name, reason) {
|
|
20
|
+
return { name, ok: true, detail: `not available on this platform: ${reason}` };
|
|
21
|
+
}
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Node version
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
/** Pure so the boundary (21 vs. 22 vs. 23) is testable without spawning Node. */
|
|
26
|
+
export function nodeVersionVerdict(version) {
|
|
27
|
+
const major = Number(version.replace(/^v/, '').split('.')[0]);
|
|
28
|
+
const okVal = Number.isFinite(major) && major >= MIN_NODE_MAJOR;
|
|
29
|
+
return {
|
|
30
|
+
name: 'node-version',
|
|
31
|
+
ok: okVal,
|
|
32
|
+
detail: okVal
|
|
33
|
+
? `Node ${version} meets the minimum (>=${MIN_NODE_MAJOR}).`
|
|
34
|
+
: `Node ${version} is below the minimum (>=${MIN_NODE_MAJOR}); upgrade before trusting timing comparisons -- older runtimes differ in JIT and GC behavior in ways that show up as noise between baseline and candidate.`,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function checkNodeVersion() {
|
|
38
|
+
return nodeVersionVerdict(process.version);
|
|
39
|
+
}
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Free disk space
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
/** Pure threshold logic, so the 2 GB floor is testable without a real tiny filesystem. */
|
|
44
|
+
export function diskSpaceVerdict(freeBytes, dir) {
|
|
45
|
+
const freeGb = freeBytes / GIB;
|
|
46
|
+
const okVal = freeGb >= MIN_FREE_GIB;
|
|
47
|
+
return {
|
|
48
|
+
name: 'disk-space',
|
|
49
|
+
ok: okVal,
|
|
50
|
+
detail: okVal
|
|
51
|
+
? `${freeGb.toFixed(1)} GB free at ${dir}.`
|
|
52
|
+
: `${freeGb.toFixed(1)} GB free at ${dir}, below the ${MIN_FREE_GIB} GB floor; a worktree plus its own node_modules will not fit comfortably. Free up space before an unattended run.`,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Exported (and parameterized on `dir`) so a test can point it at a path
|
|
57
|
+
* that certainly does not exist and assert it degrades instead of
|
|
58
|
+
* throwing, per the brief's own suggested test.
|
|
59
|
+
*/
|
|
60
|
+
export async function checkDiskSpace(dir) {
|
|
61
|
+
try {
|
|
62
|
+
const stats = await statfs(dir);
|
|
63
|
+
return diskSpaceVerdict(stats.bavail * stats.bsize, dir);
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
return notAvailable('disk-space', `could not read free space at ${dir}: ${e.message}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// On battery
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
/**
|
|
73
|
+
* Parses `pmset -g batt` output. Pure and exported so the parsing logic is
|
|
74
|
+
* testable with fixture strings, independent of this machine's actual
|
|
75
|
+
* power state.
|
|
76
|
+
*/
|
|
77
|
+
export function pmsetBatteryVerdict(stdout) {
|
|
78
|
+
const onBattery = /Battery Power/.test(stdout);
|
|
79
|
+
return {
|
|
80
|
+
name: 'on-battery',
|
|
81
|
+
ok: !onBattery,
|
|
82
|
+
detail: onBattery
|
|
83
|
+
? 'Running on battery power; macOS scales CPU frequency down aggressively off AC. Plug in before an unattended measurement run.'
|
|
84
|
+
: 'Running on AC power.',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
async function checkOnBatteryMac() {
|
|
88
|
+
const r = await run('pmset', ['-g', 'batt'], { cwd: os.tmpdir(), timeoutMs: PROBE_TIMEOUT_MS });
|
|
89
|
+
if (!execOk(r))
|
|
90
|
+
return notAvailable('on-battery', 'pmset did not run on this machine');
|
|
91
|
+
return pmsetBatteryVerdict(r.stdout);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Reads the documented Linux sysfs shape: under `dir`, each `BAT<n>`
|
|
95
|
+
* entry has a `status` file containing `Charging`, `Discharging`,
|
|
96
|
+
* `Full`, etc. Exported and parameterized on `dir` so the shape can be
|
|
97
|
+
* exercised with a fixture directory on any platform -- this project's
|
|
98
|
+
* dev machine is macOS, so this has been verified only against a
|
|
99
|
+
* fabricated fixture, never a real Linux kernel.
|
|
100
|
+
*/
|
|
101
|
+
export async function checkOnBatteryLinux(dir) {
|
|
102
|
+
let entries;
|
|
103
|
+
try {
|
|
104
|
+
entries = await readdir(dir);
|
|
105
|
+
}
|
|
106
|
+
catch (e) {
|
|
107
|
+
return notAvailable('on-battery', `${dir} unreadable: ${e.message}`);
|
|
108
|
+
}
|
|
109
|
+
const batteries = entries.filter((e) => e.startsWith('BAT'));
|
|
110
|
+
if (batteries.length === 0) {
|
|
111
|
+
return { name: 'on-battery', ok: true, detail: 'No battery present (desktop or VM); nothing to check.' };
|
|
112
|
+
}
|
|
113
|
+
const statuses = await Promise.all(batteries.map(async (b) => {
|
|
114
|
+
try {
|
|
115
|
+
return (await readFile(path.join(dir, b, 'status'), 'utf8')).trim();
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return 'unknown';
|
|
119
|
+
}
|
|
120
|
+
}));
|
|
121
|
+
const discharging = statuses.some((s) => s.toLowerCase() === 'discharging');
|
|
122
|
+
return {
|
|
123
|
+
name: 'on-battery',
|
|
124
|
+
ok: !discharging,
|
|
125
|
+
detail: discharging
|
|
126
|
+
? `On battery (${statuses.join(', ')}); frequency scaling on battery invites noise. Plug in before an unattended measurement run.`
|
|
127
|
+
: `On AC power (${statuses.join(', ')}).`,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
async function checkOnBattery() {
|
|
131
|
+
if (process.platform === 'darwin')
|
|
132
|
+
return checkOnBatteryMac();
|
|
133
|
+
if (process.platform === 'linux')
|
|
134
|
+
return checkOnBatteryLinux('/sys/class/power_supply');
|
|
135
|
+
return notAvailable('on-battery', `no battery probe for platform "${process.platform}"`);
|
|
136
|
+
}
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
// CPU frequency scaling governor (Linux)
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
/** Pure so "performance" vs. anything else is testable without a real sysfs file. */
|
|
141
|
+
export function cpuGovernorVerdict(governor) {
|
|
142
|
+
const okVal = governor === 'performance';
|
|
143
|
+
return {
|
|
144
|
+
name: 'cpu-governor',
|
|
145
|
+
ok: okVal,
|
|
146
|
+
detail: okVal
|
|
147
|
+
? 'cpu0 scaling governor is "performance".'
|
|
148
|
+
: `cpu0 scaling governor is "${governor}", not "performance"; frequency ramps mid-benchmark add noise between rounds. Consider "cpupower frequency-set -g performance" before measuring.`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Reads one sysfs file at a caller-supplied path, so both the "missing
|
|
153
|
+
* file" fallback and the parsing of a real value can be tested with a
|
|
154
|
+
* fixture on any platform. This project's dev machine is macOS, so the
|
|
155
|
+
* fixture-based tests are the only verification this has had -- it has
|
|
156
|
+
* never run against a real Linux kernel's cpufreq sysfs tree.
|
|
157
|
+
*/
|
|
158
|
+
export async function checkCpuGovernorAt(governorPath) {
|
|
159
|
+
try {
|
|
160
|
+
const governor = (await readFile(governorPath, 'utf8')).trim();
|
|
161
|
+
return cpuGovernorVerdict(governor);
|
|
162
|
+
}
|
|
163
|
+
catch (e) {
|
|
164
|
+
return notAvailable('cpu-governor', `scaling_governor unreadable: ${e.message}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async function checkCpuGovernor() {
|
|
168
|
+
if (process.platform !== 'linux') {
|
|
169
|
+
return notAvailable('cpu-governor', `scaling_governor is Linux-only (platform "${process.platform}")`);
|
|
170
|
+
}
|
|
171
|
+
return checkCpuGovernorAt('/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor');
|
|
172
|
+
}
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
// Thermal / chip-family risk -- informational only, never a pass/fail
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
/**
|
|
177
|
+
* There is no portable, reliable API to read "currently throttled" --
|
|
178
|
+
* macOS's own `pmset -g therm` reports nothing useful on Apple Silicon
|
|
179
|
+
* (confirmed empirically on this machine: "No thermal warning level has
|
|
180
|
+
* been recorded" even under load), which is exactly the kind of probe
|
|
181
|
+
* that always returns "ok" for lack of a real measurement the brief warns
|
|
182
|
+
* against shipping. Instead this reports the chip and, on Apple Silicon,
|
|
183
|
+
* the one thing that is both true and actionable: heterogeneous P/E cores
|
|
184
|
+
* make single-run timings noisy regardless of thermal state. Always
|
|
185
|
+
* `ok: true` -- explicitly informational, not a verdict.
|
|
186
|
+
*/
|
|
187
|
+
function checkThermalRisk() {
|
|
188
|
+
const cpus = os.cpus();
|
|
189
|
+
const model = cpus[0]?.model.trim() ?? 'unknown CPU';
|
|
190
|
+
const isAppleSilicon = process.platform === 'darwin' && process.arch === 'arm64';
|
|
191
|
+
const detail = isAppleSilicon
|
|
192
|
+
? `${model}, ${cpus.length} logical cores. Apple Silicon's P/E-core scheduler can place the same benchmark on a fast performance core in one run and a slow efficiency core in the next, adding variance unrelated to any code change -- expect to need more rounds than on a uniform-core machine. Informational only, not a pass/fail check.`
|
|
193
|
+
: `${model}, ${cpus.length} logical cores. Sustained back-to-back runs can trigger thermal throttling on any laptop chip; watch for timings drifting worse over a long session. Informational only, not a pass/fail check.`;
|
|
194
|
+
return { name: 'thermal-risk', ok: true, detail };
|
|
195
|
+
}
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
// Load average relative to core count
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
/**
|
|
200
|
+
* Pure so the core-count-relative comparison is testable directly: a raw
|
|
201
|
+
* load of 4 means opposite things on a 4-core and a 64-core machine, so
|
|
202
|
+
* the check is expressed as a fraction of capacity, not a bare number.
|
|
203
|
+
*/
|
|
204
|
+
export function loadAverageVerdict(load1, cores) {
|
|
205
|
+
const ratio = load1 / cores;
|
|
206
|
+
const pct = (ratio * 100).toFixed(0);
|
|
207
|
+
const okVal = ratio < BUSY_LOAD_RATIO;
|
|
208
|
+
return {
|
|
209
|
+
name: 'load-average',
|
|
210
|
+
ok: okVal,
|
|
211
|
+
detail: okVal
|
|
212
|
+
? `1-minute load average ${load1.toFixed(2)} across ${cores} core(s) (~${pct}% of capacity).`
|
|
213
|
+
: `1-minute load average ${load1.toFixed(2)} across ${cores} core(s) (~${pct}% of capacity) -- this machine is already busy. A competing process is exactly what turns a no-op change into a false KEEP or DISCARD; consider waiting or closing other work.`,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function checkLoadAverage() {
|
|
217
|
+
if (process.platform === 'win32') {
|
|
218
|
+
// Node documents os.loadavg() as always [0, 0, 0] on Windows.
|
|
219
|
+
return notAvailable('load-average', 'Windows does not report a load average (Node always returns 0)');
|
|
220
|
+
}
|
|
221
|
+
const [load1] = os.loadavg();
|
|
222
|
+
const cores = os.cpus().length || 1;
|
|
223
|
+
return loadAverageVerdict(load1 ?? 0, cores);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Wraps every probe individually so a bug in one check's own logic (not
|
|
227
|
+
* just a missing file the probe already anticipates) still cannot take
|
|
228
|
+
* down `runChecks` as a whole. Each probe already degrades known failure
|
|
229
|
+
* modes to `notAvailable`; this is the outer safety net for the unknown
|
|
230
|
+
* ones, which is why `runChecks` can promise it never throws.
|
|
231
|
+
*/
|
|
232
|
+
async function safe(name, probe) {
|
|
233
|
+
try {
|
|
234
|
+
return await probe();
|
|
235
|
+
}
|
|
236
|
+
catch (e) {
|
|
237
|
+
return notAvailable(name, `probe threw unexpectedly: ${e.message}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Runs every diagnostic. Total by construction: each probe already
|
|
242
|
+
* degrades a missing file or absent command to `ok: true`, and `safe`
|
|
243
|
+
* catches anything that still escapes. Never rejects, on any platform.
|
|
244
|
+
*/
|
|
245
|
+
export async function runChecks() {
|
|
246
|
+
return Promise.all([
|
|
247
|
+
safe('node-version', () => checkNodeVersion()),
|
|
248
|
+
safe('disk-space', () => checkDiskSpace(stateHome())),
|
|
249
|
+
safe('on-battery', () => checkOnBattery()),
|
|
250
|
+
safe('cpu-governor', () => checkCpuGovernor()),
|
|
251
|
+
safe('thermal-risk', () => checkThermalRisk()),
|
|
252
|
+
safe('load-average', () => checkLoadAverage()),
|
|
253
|
+
]);
|
|
254
|
+
}
|
|
255
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/doctor/doctor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type Manifest } from './manifest.js';
|
|
2
|
+
export declare function buildManifest(root: string, rels: string[]): Promise<Manifest>;
|
|
3
|
+
/** Copies every listed file into destDir, preserving relative layout. */
|
|
4
|
+
export declare function snapshot(root: string, rels: string[], destDir: string): Promise<Manifest>;
|
|
5
|
+
/**
|
|
6
|
+
* Restores every manifest entry into the repository, returning the paths that
|
|
7
|
+
* actually differed. Edits are erased rather than argued about.
|
|
8
|
+
*/
|
|
9
|
+
export declare function restore(root: string, srcDir: string, manifest: Manifest): Promise<string[]>;
|
|
10
|
+
/**
|
|
11
|
+
* Candidate test or bench files absent from the manifest.
|
|
12
|
+
*
|
|
13
|
+
* This is what closes "add an easier benchmark": a file that looks like a
|
|
14
|
+
* benchmark but was not frozen at baseline is rejected, not measured.
|
|
15
|
+
*
|
|
16
|
+
* The task brief's draft signature carried a leading `repoRoot` parameter.
|
|
17
|
+
* It was genuinely dead: every comparison here is against manifest keys and
|
|
18
|
+
* `unfreeze` entries, which are already repo-relative, so no filesystem
|
|
19
|
+
* lookup was ever performed against it. Removed rather than kept as an
|
|
20
|
+
* underscore-prefixed parameter, after confirming no other task's call site
|
|
21
|
+
* depends on the four-argument form.
|
|
22
|
+
*/
|
|
23
|
+
export declare function findUnmanifested(candidates: string[], manifest: Manifest, unfreeze: string[]): string[];
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { copyFile, lstat, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { hashString } from './manifest.js';
|
|
4
|
+
import { assertSafeRelPath } from './safepath.js';
|
|
5
|
+
/**
|
|
6
|
+
* Throws if `rel` inside `root` is a symlink.
|
|
7
|
+
*
|
|
8
|
+
* Both directions matter, for different reasons. `buildManifest`/`snapshot`
|
|
9
|
+
* `readFile` the path directly, so an unguarded symlink there would freeze
|
|
10
|
+
* (and copy into the snapshot dir) whatever it points at, leaking content
|
|
11
|
+
* from outside the repository. `restore`'s write path unlinks before
|
|
12
|
+
* writing (see below), which happens to stop a naive "symlink to a
|
|
13
|
+
* differing file" attack — but not the more patient one: point the link at
|
|
14
|
+
* a file whose bytes already match the frozen content, so `restore` sees no
|
|
15
|
+
* difference and leaves the live symlink in place, then edit the link's
|
|
16
|
+
* target *after* restore has already run and "approved" the path. Without
|
|
17
|
+
* this check that swap is invisible: a later test run reads straight
|
|
18
|
+
* through the symlink to whatever the agent put there last. This check
|
|
19
|
+
* refuses the path outright, so no symlink survives a restore regardless of
|
|
20
|
+
* whether its current content happens to match.
|
|
21
|
+
*
|
|
22
|
+
* A missing path is not an error here: `buildManifest` immediately follows
|
|
23
|
+
* this with a `readFile` that throws its own (loud) ENOENT for a baseline
|
|
24
|
+
* file that vanished, and `restore` legitimately expects the agent to have
|
|
25
|
+
* deleted a frozen file — that is one of the two things it must repair.
|
|
26
|
+
*/
|
|
27
|
+
async function assertNotSymlink(root, rel) {
|
|
28
|
+
const abs = path.join(root, rel);
|
|
29
|
+
try {
|
|
30
|
+
const st = await lstat(abs);
|
|
31
|
+
if (st.isSymbolicLink()) {
|
|
32
|
+
throw new Error(`refusing to touch ${rel}: it is a symlink, and following it would read or write outside the repository`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
if (e.code === 'ENOENT')
|
|
37
|
+
return;
|
|
38
|
+
throw e;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export async function buildManifest(root, rels) {
|
|
42
|
+
const files = {};
|
|
43
|
+
for (const rel of rels.slice().sort()) {
|
|
44
|
+
assertSafeRelPath(rel);
|
|
45
|
+
await assertNotSymlink(root, rel);
|
|
46
|
+
files[rel] = hashString(await readFile(path.join(root, rel), 'utf8'));
|
|
47
|
+
}
|
|
48
|
+
return { files };
|
|
49
|
+
}
|
|
50
|
+
/** Copies every listed file into destDir, preserving relative layout. */
|
|
51
|
+
export async function snapshot(root, rels, destDir) {
|
|
52
|
+
const manifest = await buildManifest(root, rels);
|
|
53
|
+
for (const rel of Object.keys(manifest.files)) {
|
|
54
|
+
const dest = path.join(destDir, rel);
|
|
55
|
+
await mkdir(path.dirname(dest), { recursive: true });
|
|
56
|
+
await copyFile(path.join(root, rel), dest);
|
|
57
|
+
}
|
|
58
|
+
return manifest;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Restores every manifest entry into the repository, returning the paths that
|
|
62
|
+
* actually differed. Edits are erased rather than argued about.
|
|
63
|
+
*/
|
|
64
|
+
export async function restore(root, srcDir, manifest) {
|
|
65
|
+
const changed = [];
|
|
66
|
+
for (const rel of Object.keys(manifest.files).sort()) {
|
|
67
|
+
assertSafeRelPath(rel);
|
|
68
|
+
await assertNotSymlink(root, rel);
|
|
69
|
+
const abs = path.join(root, rel);
|
|
70
|
+
const want = await readFile(path.join(srcDir, rel), 'utf8');
|
|
71
|
+
let have = null;
|
|
72
|
+
try {
|
|
73
|
+
have = await readFile(abs, 'utf8');
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
have = null;
|
|
77
|
+
}
|
|
78
|
+
if (have === want)
|
|
79
|
+
continue;
|
|
80
|
+
await mkdir(path.dirname(abs), { recursive: true });
|
|
81
|
+
// rm first so a read-only leftover cannot deflect the write, and so a
|
|
82
|
+
// symlink that slipped past assertNotSymlink's lstat by a race is
|
|
83
|
+
// unlinked (not followed) rather than written through.
|
|
84
|
+
await rm(abs, { force: true });
|
|
85
|
+
await writeFile(abs, want);
|
|
86
|
+
changed.push(rel);
|
|
87
|
+
}
|
|
88
|
+
return changed;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Candidate test or bench files absent from the manifest.
|
|
92
|
+
*
|
|
93
|
+
* This is what closes "add an easier benchmark": a file that looks like a
|
|
94
|
+
* benchmark but was not frozen at baseline is rejected, not measured.
|
|
95
|
+
*
|
|
96
|
+
* The task brief's draft signature carried a leading `repoRoot` parameter.
|
|
97
|
+
* It was genuinely dead: every comparison here is against manifest keys and
|
|
98
|
+
* `unfreeze` entries, which are already repo-relative, so no filesystem
|
|
99
|
+
* lookup was ever performed against it. Removed rather than kept as an
|
|
100
|
+
* underscore-prefixed parameter, after confirming no other task's call site
|
|
101
|
+
* depends on the four-argument form.
|
|
102
|
+
*/
|
|
103
|
+
export function findUnmanifested(candidates, manifest, unfreeze) {
|
|
104
|
+
const allowed = new Set([...Object.keys(manifest.files), ...unfreeze]);
|
|
105
|
+
return candidates.filter((c) => !allowed.has(c)).sort();
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=freeze.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"freeze.js","sourceRoot":"","sources":["../../src/freeze/freeze.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAClF,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,UAAU,EAAiB,MAAM,eAAe,CAAA;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAEjD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,KAAK,UAAU,gBAAgB,CAAC,IAAY,EAAE,GAAW;IACvD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;IAChC,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAA;QAC3B,IAAI,EAAE,CAAC,cAAc,EAAE,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CACb,qBAAqB,GAAG,gFAAgF,CACzG,CAAA;QACH,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAK,CAA2B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAM;QAC1D,MAAM,CAAC,CAAA;IACT,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAY,EAAE,IAAc;IAC9D,MAAM,KAAK,GAA2B,EAAE,CAAA;IACxC,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;QACtC,iBAAiB,CAAC,GAAG,CAAC,CAAA;QACtB,MAAM,gBAAgB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;QACjC,KAAK,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC,CAAA;IACvE,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,CAAA;AAClB,CAAC;AAED,yEAAyE;AACzE,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,IAAY,EACZ,IAAc,EACd,OAAe;IAEf,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IAChD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;QACpC,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QACpD,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,IAAI,CAAC,CAAA;IAC5C,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,IAAY,EACZ,MAAc,EACd,QAAkB;IAElB,MAAM,OAAO,GAAa,EAAE,CAAA;IAC5B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QACrD,iBAAiB,CAAC,GAAG,CAAC,CAAA;QACtB,MAAM,gBAAgB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;QACjC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;QAChC,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,CAAA;QAC3D,IAAI,IAAI,GAAkB,IAAI,CAAA;QAC9B,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;QACpC,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,GAAG,IAAI,CAAA;QACb,CAAC;QACD,IAAI,IAAI,KAAK,IAAI;YAAE,SAAQ;QAC3B,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QACnD,sEAAsE;QACtE,kEAAkE;QAClE,uDAAuD;QACvD,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;QAC9B,MAAM,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;QAC1B,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACnB,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,gBAAgB,CAC9B,UAAoB,EACpB,QAAkB,EAClB,QAAkB;IAElB,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAA;IACtE,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;AACzD,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
export function hashString(s) {
|
|
4
|
+
return createHash('sha256').update(s).digest('hex');
|
|
5
|
+
}
|
|
6
|
+
export async function hashFile(abs) {
|
|
7
|
+
return hashString(await readFile(abs, 'utf8'));
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/freeze/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAO3C,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AACrD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,GAAW;IACxC,OAAO,UAAU,CAAC,MAAM,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAA;AAChD,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Refuses any path that could escape the repository root.
|
|
3
|
+
*
|
|
4
|
+
* The agent controls the repository, so a manifest entry or a discovered path
|
|
5
|
+
* is untrusted input. Normalising first defeats `src/../../x`, which looks
|
|
6
|
+
* innocent until it is resolved.
|
|
7
|
+
*/
|
|
8
|
+
export declare function assertSafeRelPath(p: string): void;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
/**
|
|
3
|
+
* Refuses any path that could escape the repository root.
|
|
4
|
+
*
|
|
5
|
+
* The agent controls the repository, so a manifest entry or a discovered path
|
|
6
|
+
* is untrusted input. Normalising first defeats `src/../../x`, which looks
|
|
7
|
+
* innocent until it is resolved.
|
|
8
|
+
*/
|
|
9
|
+
export function assertSafeRelPath(p) {
|
|
10
|
+
if (p === '' || p === '.')
|
|
11
|
+
throw new Error('unsafe path: empty');
|
|
12
|
+
if (p.includes('\\'))
|
|
13
|
+
throw new Error(`unsafe path: backslash in ${JSON.stringify(p)}`);
|
|
14
|
+
if (path.posix.isAbsolute(p) || /^[A-Za-z]:/.test(p)) {
|
|
15
|
+
throw new Error(`unsafe path: absolute ${JSON.stringify(p)}`);
|
|
16
|
+
}
|
|
17
|
+
const normalized = path.posix.normalize(p);
|
|
18
|
+
if (normalized === '..' || normalized.startsWith('../')) {
|
|
19
|
+
throw new Error(`unsafe path: parent traversal in ${JSON.stringify(p)}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=safepath.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safepath.js","sourceRoot":"","sources":["../../src/freeze/safepath.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,CAAS;IACzC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAA;IAChE,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IACvF,IAAI,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,KAAK,CAAC,yBAAyB,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IAC/D,CAAC;IACD,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;IAC1C,IAAI,UAAU,KAAK,IAAI,IAAI,UAAU,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QACxD,MAAM,IAAI,KAAK,CAAC,oCAAoC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IAC1E,CAAC;AACH,CAAC"}
|