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
package/dist/cli/main.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { cmdBaseline } from './cmd-baseline.js';
|
|
5
|
+
import { cmdDoctor } from './cmd-doctor.js';
|
|
6
|
+
import { cmdEval } from './cmd-eval.js';
|
|
7
|
+
import { cmdInit } from './cmd-init.js';
|
|
8
|
+
import { cmdProfile } from './cmd-profile.js';
|
|
9
|
+
import { cmdReport } from './cmd-report.js';
|
|
10
|
+
import { cmdStatus } from './cmd-status.js';
|
|
11
|
+
import { cmdStop } from './cmd-stop.js';
|
|
12
|
+
import { resolveCtx, splitDashC } from './runctx.js';
|
|
13
|
+
/**
|
|
14
|
+
* The command table.
|
|
15
|
+
*
|
|
16
|
+
* Ruling 34: a command implemented and tested but never added here is
|
|
17
|
+
* unreachable, yet looks completely healthy from inside its own task's
|
|
18
|
+
* suite -- the missing piece lives in a file that task never touches, and
|
|
19
|
+
* `--help` would still print successfully, just without it. Every command
|
|
20
|
+
* task must register itself here, and prove it with a test that dispatches
|
|
21
|
+
* through `main`, not one that only calls the command function directly.
|
|
22
|
+
* Exported (only) so the COMMANDS/HELP invariant test below can compare
|
|
23
|
+
* this table's keys against what `--help` actually lists.
|
|
24
|
+
*/
|
|
25
|
+
export const COMMANDS = {
|
|
26
|
+
init: cmdInit,
|
|
27
|
+
doctor: cmdDoctor,
|
|
28
|
+
baseline: cmdBaseline,
|
|
29
|
+
eval: cmdEval,
|
|
30
|
+
status: cmdStatus,
|
|
31
|
+
stop: cmdStop,
|
|
32
|
+
report: cmdReport,
|
|
33
|
+
profile: cmdProfile,
|
|
34
|
+
};
|
|
35
|
+
const HELP = `autor3search-typescript -- autonomous performance optimization for a TypeScript repository
|
|
36
|
+
|
|
37
|
+
Usage: autor3search-typescript [-C <dir>] <command> [flags]
|
|
38
|
+
|
|
39
|
+
Commands:
|
|
40
|
+
init Discover benchmarks and write .autor3search/config.yaml and program.md
|
|
41
|
+
doctor Report whether this machine can measure reliably (informational, always exits 0)
|
|
42
|
+
baseline Freeze tests/benchmarks, pin a worktree at HEAD, install and prove it can measure
|
|
43
|
+
eval Run one experiment through the gate chain and report a verdict (0 KEEP, 1 DISCARD, 2 FAIL, 3 CRASH)
|
|
44
|
+
status Report where a run is: branch, commits, worktree, experiment counts, in-flight eval, pending stop (read-only)
|
|
45
|
+
stop Ask the agent to stop after its current experiment; -clear cancels, -force also signals the running eval
|
|
46
|
+
report Summarize results.tsv: counts by status, cumulative speedup, largest individual wins
|
|
47
|
+
profile Run the declared benchmarks under Node's CPU profiler and print the hottest functions
|
|
48
|
+
|
|
49
|
+
Global flags:
|
|
50
|
+
-C <dir> Run as if invoked from <dir> (resolves that directory's git repository root)
|
|
51
|
+
--help Print this message
|
|
52
|
+
`;
|
|
53
|
+
function printHelp() {
|
|
54
|
+
process.stdout.write(HELP);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Dispatch only: parse the leading `-C <dir>`, take the subcommand, and
|
|
58
|
+
* delegate. Every command is `(ctx, argv) => Promise<number>`, and its
|
|
59
|
+
* return value becomes the process exit code directly -- exit codes are
|
|
60
|
+
* reserved project-wide (0 KEEP, 1 DISCARD, 2 FAIL, 3 CRASH), and a usage
|
|
61
|
+
* error or an `init` failure is a FAIL (2).
|
|
62
|
+
*
|
|
63
|
+
* This function never rejects: everything below -- including `resolveCtx`
|
|
64
|
+
* failing on a `-C` outside any git repository -- is caught here and
|
|
65
|
+
* turned into `error: <message>` on stderr plus a `2` return, never a raw
|
|
66
|
+
* stack trace. That makes it directly testable (call it, read the
|
|
67
|
+
* returned number) without a test having to unwrap a promise rejection.
|
|
68
|
+
*/
|
|
69
|
+
export async function main(argv) {
|
|
70
|
+
try {
|
|
71
|
+
// The same `splitDashC` that `resolveCtx` uses below: one implementation
|
|
72
|
+
// of "-C is only recognized as the leading token," so this and
|
|
73
|
+
// `resolveCtx` cannot silently drift apart into two different parsings
|
|
74
|
+
// of the same flag.
|
|
75
|
+
const { rest } = splitDashC(argv);
|
|
76
|
+
const [name, ...commandArgv] = rest;
|
|
77
|
+
if (name === undefined || name === '--help' || name === '-h') {
|
|
78
|
+
printHelp();
|
|
79
|
+
return 2;
|
|
80
|
+
}
|
|
81
|
+
const command = COMMANDS[name];
|
|
82
|
+
if (!command) {
|
|
83
|
+
process.stderr.write(`error: unknown command "${name}"\n\n`);
|
|
84
|
+
printHelp();
|
|
85
|
+
return 2;
|
|
86
|
+
}
|
|
87
|
+
const ctx = await resolveCtx(argv);
|
|
88
|
+
return await command(ctx, commandArgv);
|
|
89
|
+
}
|
|
90
|
+
catch (e) {
|
|
91
|
+
process.stderr.write(`error: ${e.message}\n`);
|
|
92
|
+
return 2;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* True only when THIS file is the process's actual entry point, not when it
|
|
97
|
+
* is merely imported (by a test, or as a dependency).
|
|
98
|
+
*
|
|
99
|
+
* `npm` installs a package's `bin` as a symlink under `node_modules/.bin/`,
|
|
100
|
+
* so a real installed invocation runs this file through that symlink.
|
|
101
|
+
* `import.meta.url` for the entry module is Node's own resolved (symlink-
|
|
102
|
+
* followed) URL, while `process.argv[1]` is left exactly as invoked -- the
|
|
103
|
+
* symlink path itself. Comparing the two directly, as a naive
|
|
104
|
+
* `import.meta.url === \`file://${process.argv[1]}\`` check does, therefore
|
|
105
|
+
* NEVER matches for an installed package: this guard would silently never
|
|
106
|
+
* fire, `main()` would never run, and the process would exit 0 -- the KEEP
|
|
107
|
+
* exit code -- having done nothing at all, for every command including
|
|
108
|
+
* `--help`. `realpathSync` resolves both the symlink and any other
|
|
109
|
+
* indirection (a relative path, `.` segments) before comparing, so a
|
|
110
|
+
* symlinked, a relative, and a directly-invoked entry point all still match.
|
|
111
|
+
*
|
|
112
|
+
* `realpathSync` requires the path to exist; the fallback below only ever
|
|
113
|
+
* matters for a degenerate invocation (no `argv[1]` at all, e.g. a REPL) and
|
|
114
|
+
* intentionally reports "not the entry point" rather than throwing, since a
|
|
115
|
+
* thrown error here would crash an import that merely wants `COMMANDS` or
|
|
116
|
+
* `main` for testing.
|
|
117
|
+
*/
|
|
118
|
+
function isEntryPoint() {
|
|
119
|
+
if (process.argv[1] === undefined)
|
|
120
|
+
return false;
|
|
121
|
+
try {
|
|
122
|
+
return import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href;
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (isEntryPoint()) {
|
|
129
|
+
main(process.argv.slice(2)).then((code) => {
|
|
130
|
+
process.exitCode = code;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=main.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Repo-root-relative default location of the unattended run's transcript. */
|
|
2
|
+
export declare const LOG_PATH = "run.log";
|
|
3
|
+
export interface RunCtx {
|
|
4
|
+
repoRoot: string;
|
|
5
|
+
configPath: string;
|
|
6
|
+
resultsPath: string;
|
|
7
|
+
logPath: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Splits a leading `-C <dir>` off argv, recognized only as the very first
|
|
11
|
+
* token (the same convention `git -C` uses). Never touches `process.cwd()`
|
|
12
|
+
* or `process.chdir` -- the directory is only ever handed to a spawned
|
|
13
|
+
* `git` as its `cwd` option, so this is safe to call concurrently from
|
|
14
|
+
* multiple invocations and to unit test without disturbing the test
|
|
15
|
+
* process's own working directory.
|
|
16
|
+
*/
|
|
17
|
+
export declare function splitDashC(argv: readonly string[], cwd?: string): {
|
|
18
|
+
dir: string;
|
|
19
|
+
rest: string[];
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Resolves the run context for one invocation: the `-C <dir>` (or the
|
|
23
|
+
* current working directory) is walked up to its git repository root via a
|
|
24
|
+
* spawned `git rev-parse --show-toplevel`, and every path the harness
|
|
25
|
+
* writes -- the config, `results.tsv`, `run.log` -- is anchored to that
|
|
26
|
+
* root. Since resolution goes through a subprocess `cwd`, not
|
|
27
|
+
* `process.chdir`, `process.cwd()` in this process is never touched.
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveCtx(argv: readonly string[]): Promise<RunCtx>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { CONFIG_PATH } from '../config/schema.js';
|
|
3
|
+
import { repoRoot as gitRepoRoot } from '../gitx/git.js';
|
|
4
|
+
import { RESULTS_PATH } from '../results/results.js';
|
|
5
|
+
/** Repo-root-relative default location of the unattended run's transcript. */
|
|
6
|
+
export const LOG_PATH = 'run.log';
|
|
7
|
+
/**
|
|
8
|
+
* Splits a leading `-C <dir>` off argv, recognized only as the very first
|
|
9
|
+
* token (the same convention `git -C` uses). Never touches `process.cwd()`
|
|
10
|
+
* or `process.chdir` -- the directory is only ever handed to a spawned
|
|
11
|
+
* `git` as its `cwd` option, so this is safe to call concurrently from
|
|
12
|
+
* multiple invocations and to unit test without disturbing the test
|
|
13
|
+
* process's own working directory.
|
|
14
|
+
*/
|
|
15
|
+
export function splitDashC(argv, cwd = process.cwd()) {
|
|
16
|
+
if (argv[0] !== '-C') {
|
|
17
|
+
return { dir: cwd, rest: [...argv] };
|
|
18
|
+
}
|
|
19
|
+
const dir = argv[1];
|
|
20
|
+
if (dir === undefined) {
|
|
21
|
+
throw new Error('-C requires a directory argument');
|
|
22
|
+
}
|
|
23
|
+
return { dir, rest: argv.slice(2) };
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Resolves the run context for one invocation: the `-C <dir>` (or the
|
|
27
|
+
* current working directory) is walked up to its git repository root via a
|
|
28
|
+
* spawned `git rev-parse --show-toplevel`, and every path the harness
|
|
29
|
+
* writes -- the config, `results.tsv`, `run.log` -- is anchored to that
|
|
30
|
+
* root. Since resolution goes through a subprocess `cwd`, not
|
|
31
|
+
* `process.chdir`, `process.cwd()` in this process is never touched.
|
|
32
|
+
*/
|
|
33
|
+
export async function resolveCtx(argv) {
|
|
34
|
+
const { dir } = splitDashC(argv);
|
|
35
|
+
let root;
|
|
36
|
+
try {
|
|
37
|
+
root = await gitRepoRoot(dir);
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
throw new Error(`${dir} is not inside a git repository (or a subdirectory of one): ${e.message}`);
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
repoRoot: root,
|
|
44
|
+
configPath: path.join(root, CONFIG_PATH),
|
|
45
|
+
resultsPath: path.join(root, RESULTS_PATH),
|
|
46
|
+
logPath: path.join(root, LOG_PATH),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=runctx.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runctx.js","sourceRoot":"","sources":["../../src/cli/runctx.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACjD,OAAO,EAAE,QAAQ,IAAI,WAAW,EAAE,MAAM,gBAAgB,CAAA;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAEpD,8EAA8E;AAC9E,MAAM,CAAC,MAAM,QAAQ,GAAG,SAAS,CAAA;AASjC;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CAAC,IAAuB,EAAE,MAAc,OAAO,CAAC,GAAG,EAAE;IAC7E,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACrB,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,CAAA;IACtC,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;IACnB,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAA;IACrD,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;AACrC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAuB;IACtD,MAAM,EAAE,GAAG,EAAE,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;IAChC,IAAI,IAAY,CAAA;IAChB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,WAAW,CAAC,GAAG,CAAC,CAAA;IAC/B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,GAAG,GAAG,+DAAgE,CAAW,CAAC,OAAO,EAAE,CAC5F,CAAA;IACH,CAAC;IACD,OAAO;QACL,QAAQ,EAAE,IAAI;QACd,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC;QACxC,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC;QAC1C,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC;KACnC,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `summarize()`'s `cumulativeSpeedup` is a product of candidate/baseline
|
|
3
|
+
* TIME RATIOS -- below 1 is faster -- not a speedup factor itself. Printing
|
|
4
|
+
* it directly, suffixed `x`, reads as its own inverse: a genuine 12x win
|
|
5
|
+
* (a ratio of ~0.0833) prints as "0.0833x," which a human reads as "twelve
|
|
6
|
+
* times slower." This is the one number most likely to be read without the
|
|
7
|
+
* surrounding context of `summarize`'s own doc comment, so it must read
|
|
8
|
+
* correctly on its own.
|
|
9
|
+
*
|
|
10
|
+
* `1 / cumulativeSpeedup` is the actual "N times faster" figure a human
|
|
11
|
+
* expects; the raw ratio is kept alongside it, labeled explicitly, for
|
|
12
|
+
* anyone who wants the underlying number `summarize` actually computed.
|
|
13
|
+
*/
|
|
14
|
+
export declare function formatCumulativeSpeedup(cumulativeSpeedup: number): string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `summarize()`'s `cumulativeSpeedup` is a product of candidate/baseline
|
|
3
|
+
* TIME RATIOS -- below 1 is faster -- not a speedup factor itself. Printing
|
|
4
|
+
* it directly, suffixed `x`, reads as its own inverse: a genuine 12x win
|
|
5
|
+
* (a ratio of ~0.0833) prints as "0.0833x," which a human reads as "twelve
|
|
6
|
+
* times slower." This is the one number most likely to be read without the
|
|
7
|
+
* surrounding context of `summarize`'s own doc comment, so it must read
|
|
8
|
+
* correctly on its own.
|
|
9
|
+
*
|
|
10
|
+
* `1 / cumulativeSpeedup` is the actual "N times faster" figure a human
|
|
11
|
+
* expects; the raw ratio is kept alongside it, labeled explicitly, for
|
|
12
|
+
* anyone who wants the underlying number `summarize` actually computed.
|
|
13
|
+
*/
|
|
14
|
+
export function formatCumulativeSpeedup(cumulativeSpeedup) {
|
|
15
|
+
if (!(cumulativeSpeedup > 0) || !Number.isFinite(cumulativeSpeedup)) {
|
|
16
|
+
// Never reachable through `summarize`'s own product-of-scores (score is
|
|
17
|
+
// validated positive and finite before it ever reaches decide(), let
|
|
18
|
+
// alone this), but this function must still be total for whatever
|
|
19
|
+
// corrupted or synthetic input it is ever handed.
|
|
20
|
+
return `${cumulativeSpeedup.toFixed(4)}x (cumulative time ratio)`;
|
|
21
|
+
}
|
|
22
|
+
const factor = 1 / cumulativeSpeedup;
|
|
23
|
+
return `${factor.toFixed(2)}x faster (cumulative time ratio ${cumulativeSpeedup.toFixed(4)})`;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=speedup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"speedup.js","sourceRoot":"","sources":["../../src/cli/speedup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,uBAAuB,CAAC,iBAAyB;IAC/D,IAAI,CAAC,CAAC,iBAAiB,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC;QACpE,wEAAwE;QACxE,qEAAqE;QACrE,kEAAkE;QAClE,kDAAkD;QAClD,OAAO,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC,CAAC,2BAA2B,CAAA;IACnE,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,GAAG,iBAAiB,CAAA;IACpC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,mCAAmC,iBAAiB,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAA;AAC/F,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Config } from './schema.js';
|
|
2
|
+
export declare function defaultConfig(): Config;
|
|
3
|
+
/** Parses a duration into milliseconds. A bare number is refused as ambiguous. */
|
|
4
|
+
export declare function parseDuration(s: string): number;
|
|
5
|
+
export declare function parseConfig(text: string): Config;
|
|
6
|
+
export declare function loadConfig(path: string): Promise<Config>;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { parse as parseYaml } from 'yaml';
|
|
3
|
+
import { MIN_COUNT } from './schema.js';
|
|
4
|
+
/** yaml key -> Config field. The single source of truth for both directions. */
|
|
5
|
+
const KEYS = {
|
|
6
|
+
benchmarks: 'benchmarks',
|
|
7
|
+
scope: 'scope',
|
|
8
|
+
count: 'count',
|
|
9
|
+
benchtime: 'benchtime',
|
|
10
|
+
warmup: 'warmup',
|
|
11
|
+
max_regress_pct: 'maxRegressPct',
|
|
12
|
+
min_effect_pct: 'minEffectPct',
|
|
13
|
+
timeout: 'timeout',
|
|
14
|
+
typecheck_command: 'typecheckCommand',
|
|
15
|
+
build_command: 'buildCommand',
|
|
16
|
+
test_command: 'testCommand',
|
|
17
|
+
node_args: 'nodeArgs',
|
|
18
|
+
unfreeze: 'unfreeze',
|
|
19
|
+
};
|
|
20
|
+
export function defaultConfig() {
|
|
21
|
+
return {
|
|
22
|
+
benchmarks: [],
|
|
23
|
+
scope: ['src/**'],
|
|
24
|
+
count: 10,
|
|
25
|
+
benchtime: '500ms',
|
|
26
|
+
warmup: '100ms',
|
|
27
|
+
maxRegressPct: 5,
|
|
28
|
+
minEffectPct: 1,
|
|
29
|
+
timeout: '15m',
|
|
30
|
+
typecheckCommand: 'npx tsc --noEmit',
|
|
31
|
+
buildCommand: '',
|
|
32
|
+
testCommand: 'npm test',
|
|
33
|
+
nodeArgs: [],
|
|
34
|
+
unfreeze: [],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const DURATION = /^(\d+(?:\.\d+)?)(ms|s|m|h)$/;
|
|
38
|
+
/** Parses a duration into milliseconds. A bare number is refused as ambiguous. */
|
|
39
|
+
export function parseDuration(s) {
|
|
40
|
+
const m = DURATION.exec(String(s).trim());
|
|
41
|
+
if (!m) {
|
|
42
|
+
throw new Error(`invalid duration ${JSON.stringify(s)}: use a number with a unit, e.g. 500ms, 2s, 15m`);
|
|
43
|
+
}
|
|
44
|
+
const n = Number(m[1]);
|
|
45
|
+
const unit = m[2];
|
|
46
|
+
const mult = unit === 'ms' ? 1 : unit === 's' ? 1000 : unit === 'm' ? 60_000 : 3_600_000;
|
|
47
|
+
return n * mult;
|
|
48
|
+
}
|
|
49
|
+
/** A short, friendly noun phrase describing the runtime type of `v`, for error messages. */
|
|
50
|
+
function describeType(v) {
|
|
51
|
+
if (Array.isArray(v))
|
|
52
|
+
return 'an array';
|
|
53
|
+
if (v === null)
|
|
54
|
+
return 'null';
|
|
55
|
+
const t = typeof v;
|
|
56
|
+
return t === 'object' ? 'an object' : `a ${t}`;
|
|
57
|
+
}
|
|
58
|
+
function requireString(v, yamlKey) {
|
|
59
|
+
if (typeof v !== 'string') {
|
|
60
|
+
throw new Error(`${yamlKey} must be a string, got ${describeType(v)}`);
|
|
61
|
+
}
|
|
62
|
+
return v;
|
|
63
|
+
}
|
|
64
|
+
function requireNumber(v, yamlKey) {
|
|
65
|
+
if (typeof v !== 'number' || Number.isNaN(v)) {
|
|
66
|
+
throw new Error(`${yamlKey} must be a number, got ${describeType(v)}`);
|
|
67
|
+
}
|
|
68
|
+
return v;
|
|
69
|
+
}
|
|
70
|
+
function requireStringArray(v, yamlKey) {
|
|
71
|
+
if (!Array.isArray(v) || !v.every((x) => typeof x === 'string')) {
|
|
72
|
+
throw new Error(`${yamlKey} must be an array of strings, got ${describeType(v)}`);
|
|
73
|
+
}
|
|
74
|
+
return v;
|
|
75
|
+
}
|
|
76
|
+
/** Parses a duration field, prefixing the error with the offending yaml key. */
|
|
77
|
+
function requireDuration(v, yamlKey) {
|
|
78
|
+
try {
|
|
79
|
+
parseDuration(v);
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
throw new Error(`${yamlKey}: ${e.message}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function validate(c) {
|
|
86
|
+
// Type checks first: a wrong-typed value (e.g. count: "ten", or scope given
|
|
87
|
+
// as a bare string instead of an array) must be refused with a clear
|
|
88
|
+
// explanation rather than silently accepted by a numeric comparison that
|
|
89
|
+
// coerces it, or crashing later with an unrelated TypeError.
|
|
90
|
+
requireStringArray(c.benchmarks, 'benchmarks');
|
|
91
|
+
requireStringArray(c.scope, 'scope');
|
|
92
|
+
requireNumber(c.count, 'count');
|
|
93
|
+
requireString(c.benchtime, 'benchtime');
|
|
94
|
+
requireString(c.warmup, 'warmup');
|
|
95
|
+
requireNumber(c.maxRegressPct, 'max_regress_pct');
|
|
96
|
+
requireNumber(c.minEffectPct, 'min_effect_pct');
|
|
97
|
+
requireString(c.timeout, 'timeout');
|
|
98
|
+
requireString(c.typecheckCommand, 'typecheck_command');
|
|
99
|
+
requireString(c.buildCommand, 'build_command');
|
|
100
|
+
requireString(c.testCommand, 'test_command');
|
|
101
|
+
requireStringArray(c.nodeArgs, 'node_args');
|
|
102
|
+
requireStringArray(c.unfreeze, 'unfreeze');
|
|
103
|
+
if (c.count < MIN_COUNT) {
|
|
104
|
+
throw new Error(`count must be at least ${MIN_COUNT}: the significance test cannot report p < 0.05 with ` +
|
|
105
|
+
`fewer than ${MIN_COUNT} measured rounds per side no matter how large the improvement is, ` +
|
|
106
|
+
`so every experiment would be discarded regardless of what changed (the default is 10)`);
|
|
107
|
+
}
|
|
108
|
+
if (c.maxRegressPct < 0)
|
|
109
|
+
throw new Error('max_regress_pct must not be negative');
|
|
110
|
+
if (c.minEffectPct < 0)
|
|
111
|
+
throw new Error('min_effect_pct must be at least 0');
|
|
112
|
+
if (c.minEffectPct >= 100)
|
|
113
|
+
throw new Error('min_effect_pct must be less than 100');
|
|
114
|
+
if (c.scope.length === 0)
|
|
115
|
+
throw new Error('scope must list at least one path pattern');
|
|
116
|
+
for (const s of c.scope) {
|
|
117
|
+
if (s.trim() === '')
|
|
118
|
+
throw new Error('scope must not contain an empty or whitespace-only entry');
|
|
119
|
+
}
|
|
120
|
+
// Throws on anything unparseable, naming the offending key.
|
|
121
|
+
requireDuration(c.benchtime, 'benchtime');
|
|
122
|
+
requireDuration(c.warmup, 'warmup');
|
|
123
|
+
requireDuration(c.timeout, 'timeout');
|
|
124
|
+
if (c.testCommand.trim() === '')
|
|
125
|
+
throw new Error('test_command must not be empty');
|
|
126
|
+
// typecheck_command, unlike test_command, may legitimately be empty: a
|
|
127
|
+
// repository with no tsconfig.json has nothing for `tsc --noEmit` to check
|
|
128
|
+
// against, and `cmd-init` writes an empty typecheck_command plus a printed
|
|
129
|
+
// warning for exactly that case (see task 16 / Ruling 25). The eval gate
|
|
130
|
+
// chain (task 19) already treats an empty typecheck_command as "skip this
|
|
131
|
+
// gate," mirroring build_command's existing empty-means-skip semantics
|
|
132
|
+
// above -- so rejecting empty here would make a config that `init` itself
|
|
133
|
+
// legitimately writes fail to load, surfacing only at the user's first
|
|
134
|
+
// `eval`, long after `init` reported success.
|
|
135
|
+
}
|
|
136
|
+
export function parseConfig(text) {
|
|
137
|
+
const raw = parseYaml(text) ?? {};
|
|
138
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
|
|
139
|
+
throw new Error('config must be a YAML mapping');
|
|
140
|
+
}
|
|
141
|
+
const c = defaultConfig();
|
|
142
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
143
|
+
const field = KEYS[k];
|
|
144
|
+
if (!field) {
|
|
145
|
+
throw new Error(`unknown config key ${JSON.stringify(k)}: a typo here would silently leave the ` +
|
|
146
|
+
`default in force, so it is refused rather than ignored`);
|
|
147
|
+
}
|
|
148
|
+
// The cast is safe: validate() below rejects anything of the wrong shape
|
|
149
|
+
// once the value is in place. It goes through `unknown` first because
|
|
150
|
+
// `Config` has no index signature for TS to widen from directly.
|
|
151
|
+
;
|
|
152
|
+
c[field] = v;
|
|
153
|
+
}
|
|
154
|
+
validate(c);
|
|
155
|
+
return c;
|
|
156
|
+
}
|
|
157
|
+
export async function loadConfig(path) {
|
|
158
|
+
let text;
|
|
159
|
+
try {
|
|
160
|
+
text = await readFile(path, 'utf8');
|
|
161
|
+
}
|
|
162
|
+
catch (e) {
|
|
163
|
+
throw new Error(`read ${path}: ${e.message}`);
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
return parseConfig(text);
|
|
167
|
+
}
|
|
168
|
+
catch (e) {
|
|
169
|
+
throw new Error(`invalid ${path}: ${e.message}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=load.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load.js","sourceRoot":"","sources":["../../src/config/load.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** The config location, relative to the repository root. */
|
|
2
|
+
export declare const CONFIG_PATH = ".autor3search/config.yaml";
|
|
3
|
+
/** Lockfiles we recognise, in package-manager detection order. */
|
|
4
|
+
export declare const LOCKFILES: readonly ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lockb", "bun.lock"];
|
|
5
|
+
/**
|
|
6
|
+
* Files the agent may never modify, regardless of `scope`.
|
|
7
|
+
*
|
|
8
|
+
* A dependency change is a human decision, not an autonomous one, and a
|
|
9
|
+
* tsconfig change alters what is being measured rather than how fast it runs.
|
|
10
|
+
* Both would invalidate the comparison rather than win it.
|
|
11
|
+
*/
|
|
12
|
+
export declare const IMMUTABLE_FILES: readonly string[];
|
|
13
|
+
/** The smallest `count` at which the exact rank test can ever reach ALPHA. */
|
|
14
|
+
export declare const MIN_COUNT = 4;
|
|
15
|
+
export interface Config {
|
|
16
|
+
/** Declared benchmark ids ("<file>:<function>"). Empty means all discovered. */
|
|
17
|
+
benchmarks: string[];
|
|
18
|
+
/** Glob patterns the agent may modify. */
|
|
19
|
+
scope: string[];
|
|
20
|
+
/** Measured rounds per side. */
|
|
21
|
+
count: number;
|
|
22
|
+
/** Measured window per benchmark per round. */
|
|
23
|
+
benchtime: string;
|
|
24
|
+
/** Discarded window before each measurement. */
|
|
25
|
+
warmup: string;
|
|
26
|
+
/** Largest tolerated significant regression, percent. */
|
|
27
|
+
maxRegressPct: number;
|
|
28
|
+
/** Smallest geomean improvement a KEEP will accept, percent. */
|
|
29
|
+
minEffectPct: number;
|
|
30
|
+
/** Bounds each subprocess phase. */
|
|
31
|
+
timeout: string;
|
|
32
|
+
/** Typecheck gate command. Empty means skip (e.g. no tsconfig.json at init time). */
|
|
33
|
+
typecheckCommand: string;
|
|
34
|
+
/** Optional build gate command. Empty means skip. */
|
|
35
|
+
buildCommand: string;
|
|
36
|
+
/** Correctness gate command. */
|
|
37
|
+
testCommand: string;
|
|
38
|
+
/** Extra flags for the benchmark child process, e.g. ["--expose-gc"]. */
|
|
39
|
+
nodeArgs: string[];
|
|
40
|
+
/** Test/bench files deliberately exempted from freezing. */
|
|
41
|
+
unfreeze: string[];
|
|
42
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** The config location, relative to the repository root. */
|
|
2
|
+
export const CONFIG_PATH = '.autor3search/config.yaml';
|
|
3
|
+
/** Lockfiles we recognise, in package-manager detection order. */
|
|
4
|
+
export const LOCKFILES = [
|
|
5
|
+
'package-lock.json',
|
|
6
|
+
'pnpm-lock.yaml',
|
|
7
|
+
'yarn.lock',
|
|
8
|
+
'bun.lockb',
|
|
9
|
+
'bun.lock',
|
|
10
|
+
];
|
|
11
|
+
/**
|
|
12
|
+
* Files the agent may never modify, regardless of `scope`.
|
|
13
|
+
*
|
|
14
|
+
* A dependency change is a human decision, not an autonomous one, and a
|
|
15
|
+
* tsconfig change alters what is being measured rather than how fast it runs.
|
|
16
|
+
* Both would invalidate the comparison rather than win it.
|
|
17
|
+
*/
|
|
18
|
+
export const IMMUTABLE_FILES = [
|
|
19
|
+
'package.json',
|
|
20
|
+
'tsconfig.json',
|
|
21
|
+
...LOCKFILES,
|
|
22
|
+
];
|
|
23
|
+
/** The smallest `count` at which the exact rank test can ever reach ALPHA. */
|
|
24
|
+
export const MIN_COUNT = 4;
|
|
25
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,MAAM,CAAC,MAAM,WAAW,GAAG,2BAA2B,CAAA;AAEtD,kEAAkE;AAClE,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,mBAAmB;IACnB,gBAAgB;IAChB,WAAW;IACX,WAAW;IACX,UAAU;CACF,CAAA;AAEV;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAAsB;IAChD,cAAc;IACd,eAAe;IACf,GAAG,SAAS;CACb,CAAA;AAED,8EAA8E;AAC9E,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAA"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface Benchmark {
|
|
2
|
+
/** "<repo-relative file>:<function name>". */
|
|
3
|
+
id: string;
|
|
4
|
+
file: string;
|
|
5
|
+
fn: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Exported `bench*` names declared in one source text, keyed by the name a
|
|
9
|
+
* consumer sees on the imported module — not necessarily the local
|
|
10
|
+
* declaration name.
|
|
11
|
+
*
|
|
12
|
+
* Deduplicated: the same exported name can be reachable twice in one file —
|
|
13
|
+
* once via a declaration's own `export` modifier and again via a same-file
|
|
14
|
+
* `export { name }` clause naming it — and a caller measuring one id twice
|
|
15
|
+
* per round would double the sample rate for that benchmark without anyone
|
|
16
|
+
* asking for it, corrupting the statistics silently.
|
|
17
|
+
*/
|
|
18
|
+
export declare function benchNamesInSource(text: string, fileName: string): string[];
|
|
19
|
+
/**
|
|
20
|
+
* Finds every benchmark in the repository by parsing, never executing.
|
|
21
|
+
*
|
|
22
|
+
* `init` must work on a tree that does not build, and executing a benchmark
|
|
23
|
+
* module at discovery time would run arbitrary repository code before the user
|
|
24
|
+
* has agreed to anything at all.
|
|
25
|
+
*/
|
|
26
|
+
export declare function discoverBenchmarks(root: string): Promise<Benchmark[]>;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import ts from 'typescript';
|
|
4
|
+
import { classify, walkRepo } from './files.js';
|
|
5
|
+
const PREFIX = 'bench';
|
|
6
|
+
function isBenchName(name) {
|
|
7
|
+
// "bench" alone is not a benchmark; there must be a suffix.
|
|
8
|
+
return name.startsWith(PREFIX) && name.length > PREFIX.length;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Exported `bench*` names declared in one source text, keyed by the name a
|
|
12
|
+
* consumer sees on the imported module — not necessarily the local
|
|
13
|
+
* declaration name.
|
|
14
|
+
*
|
|
15
|
+
* Deduplicated: the same exported name can be reachable twice in one file —
|
|
16
|
+
* once via a declaration's own `export` modifier and again via a same-file
|
|
17
|
+
* `export { name }` clause naming it — and a caller measuring one id twice
|
|
18
|
+
* per round would double the sample rate for that benchmark without anyone
|
|
19
|
+
* asking for it, corrupting the statistics silently.
|
|
20
|
+
*/
|
|
21
|
+
export function benchNamesInSource(text, fileName) {
|
|
22
|
+
const sf = ts.createSourceFile(fileName, text, ts.ScriptTarget.Latest, true);
|
|
23
|
+
const names = new Set();
|
|
24
|
+
for (const stmt of sf.statements) {
|
|
25
|
+
const modifiers = ts.canHaveModifiers(stmt) ? ts.getModifiers(stmt) : undefined;
|
|
26
|
+
const isExported = modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) === true;
|
|
27
|
+
// A default export is reached as `mod.default`, not `mod[name]` — the measurement
|
|
28
|
+
// child imports by name, so a default-exported bench* would fail at measurement
|
|
29
|
+
// time with "does not export a function named X". Excluding it here, at
|
|
30
|
+
// discovery, is what keeps that failure from ever happening.
|
|
31
|
+
const isDefault = modifiers?.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword) === true;
|
|
32
|
+
if (isExported && !isDefault) {
|
|
33
|
+
if (ts.isFunctionDeclaration(stmt) && stmt.name && isBenchName(stmt.name.text)) {
|
|
34
|
+
names.add(stmt.name.text);
|
|
35
|
+
}
|
|
36
|
+
else if (ts.isVariableStatement(stmt)) {
|
|
37
|
+
for (const decl of stmt.declarationList.declarations) {
|
|
38
|
+
if (!ts.isIdentifier(decl.name) || !isBenchName(decl.name.text))
|
|
39
|
+
continue;
|
|
40
|
+
const init = decl.initializer;
|
|
41
|
+
if (init && (ts.isArrowFunction(init) || ts.isFunctionExpression(init))) {
|
|
42
|
+
names.add(decl.name.text);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// `export { local as exported }` / `export { local }`: what a consumer sees is the
|
|
48
|
+
// *exported* name (`import(file).then((m) => m[fn])`), so that is what must be
|
|
49
|
+
// bench-named, regardless of what the local declaration is called or even is.
|
|
50
|
+
// Idiomatic TypeScript often declares a function and exports it later this way, and
|
|
51
|
+
// missing it would be a silent false negative — the worst failure mode for a tool
|
|
52
|
+
// whose entire output is a measurement. `export { x } from './other'` and
|
|
53
|
+
// `export * from` are deliberately excluded: resolving those would mean walking a
|
|
54
|
+
// module graph, not parsing one file.
|
|
55
|
+
if (ts.isExportDeclaration(stmt) &&
|
|
56
|
+
!stmt.isTypeOnly &&
|
|
57
|
+
!stmt.moduleSpecifier &&
|
|
58
|
+
stmt.exportClause &&
|
|
59
|
+
ts.isNamedExports(stmt.exportClause)) {
|
|
60
|
+
for (const spec of stmt.exportClause.elements) {
|
|
61
|
+
if (spec.isTypeOnly)
|
|
62
|
+
continue;
|
|
63
|
+
if (isBenchName(spec.name.text))
|
|
64
|
+
names.add(spec.name.text);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return [...names];
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Finds every benchmark in the repository by parsing, never executing.
|
|
72
|
+
*
|
|
73
|
+
* `init` must work on a tree that does not build, and executing a benchmark
|
|
74
|
+
* module at discovery time would run arbitrary repository code before the user
|
|
75
|
+
* has agreed to anything at all.
|
|
76
|
+
*/
|
|
77
|
+
export async function discoverBenchmarks(root) {
|
|
78
|
+
const files = (await walkRepo(root)).filter((f) => classify(f) === 'bench');
|
|
79
|
+
const out = [];
|
|
80
|
+
for (const file of files) {
|
|
81
|
+
const text = await readFile(path.join(root, file), 'utf8');
|
|
82
|
+
for (const fn of benchNamesInSource(text, file)) {
|
|
83
|
+
out.push({ id: `${file}:${fn}`, file, fn });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return out.sort((a, b) => a.id.localeCompare(b.id));
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=benchmarks.js.map
|