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.
Files changed (124) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +210 -0
  3. package/dist/benchproto/types.d.ts +32 -0
  4. package/dist/benchproto/types.js +28 -0
  5. package/dist/benchproto/types.js.map +1 -0
  6. package/dist/benchrun/child.d.ts +1 -0
  7. package/dist/benchrun/child.js +222 -0
  8. package/dist/benchrun/child.js.map +1 -0
  9. package/dist/benchrun/invoke.d.ts +61 -0
  10. package/dist/benchrun/invoke.js +152 -0
  11. package/dist/benchrun/invoke.js.map +1 -0
  12. package/dist/cli/cmd-baseline.d.ts +15 -0
  13. package/dist/cli/cmd-baseline.js +292 -0
  14. package/dist/cli/cmd-baseline.js.map +1 -0
  15. package/dist/cli/cmd-doctor.d.ts +11 -0
  16. package/dist/cli/cmd-doctor.js +26 -0
  17. package/dist/cli/cmd-doctor.js.map +1 -0
  18. package/dist/cli/cmd-eval.d.ts +30 -0
  19. package/dist/cli/cmd-eval.js +173 -0
  20. package/dist/cli/cmd-eval.js.map +1 -0
  21. package/dist/cli/cmd-init.d.ts +16 -0
  22. package/dist/cli/cmd-init.js +343 -0
  23. package/dist/cli/cmd-init.js.map +1 -0
  24. package/dist/cli/cmd-profile.d.ts +11 -0
  25. package/dist/cli/cmd-profile.js +140 -0
  26. package/dist/cli/cmd-profile.js.map +1 -0
  27. package/dist/cli/cmd-report.d.ts +15 -0
  28. package/dist/cli/cmd-report.js +70 -0
  29. package/dist/cli/cmd-report.js.map +1 -0
  30. package/dist/cli/cmd-status.d.ts +11 -0
  31. package/dist/cli/cmd-status.js +143 -0
  32. package/dist/cli/cmd-status.js.map +1 -0
  33. package/dist/cli/cmd-stop.d.ts +15 -0
  34. package/dist/cli/cmd-stop.js +143 -0
  35. package/dist/cli/cmd-stop.js.map +1 -0
  36. package/dist/cli/main.d.ts +31 -0
  37. package/dist/cli/main.js +133 -0
  38. package/dist/cli/main.js.map +1 -0
  39. package/dist/cli/runctx.d.ts +29 -0
  40. package/dist/cli/runctx.js +49 -0
  41. package/dist/cli/runctx.js.map +1 -0
  42. package/dist/cli/speedup.d.ts +14 -0
  43. package/dist/cli/speedup.js +25 -0
  44. package/dist/cli/speedup.js.map +1 -0
  45. package/dist/config/load.d.ts +6 -0
  46. package/dist/config/load.js +172 -0
  47. package/dist/config/load.js.map +1 -0
  48. package/dist/config/schema.d.ts +42 -0
  49. package/dist/config/schema.js +25 -0
  50. package/dist/config/schema.js.map +1 -0
  51. package/dist/discover/benchmarks.d.ts +26 -0
  52. package/dist/discover/benchmarks.js +88 -0
  53. package/dist/discover/benchmarks.js.map +1 -0
  54. package/dist/discover/files.d.ts +23 -0
  55. package/dist/discover/files.js +82 -0
  56. package/dist/discover/files.js.map +1 -0
  57. package/dist/doctor/doctor.d.ts +56 -0
  58. package/dist/doctor/doctor.js +255 -0
  59. package/dist/doctor/doctor.js.map +1 -0
  60. package/dist/freeze/freeze.d.ts +23 -0
  61. package/dist/freeze/freeze.js +107 -0
  62. package/dist/freeze/freeze.js.map +1 -0
  63. package/dist/freeze/manifest.d.ts +6 -0
  64. package/dist/freeze/manifest.js +9 -0
  65. package/dist/freeze/manifest.js.map +1 -0
  66. package/dist/freeze/safepath.d.ts +8 -0
  67. package/dist/freeze/safepath.js +22 -0
  68. package/dist/freeze/safepath.js.map +1 -0
  69. package/dist/gitx/git.d.ts +87 -0
  70. package/dist/gitx/git.js +235 -0
  71. package/dist/gitx/git.js.map +1 -0
  72. package/dist/measure/interleave.d.ts +32 -0
  73. package/dist/measure/interleave.js +42 -0
  74. package/dist/measure/interleave.js.map +1 -0
  75. package/dist/pipeline/eval.d.ts +67 -0
  76. package/dist/pipeline/eval.js +495 -0
  77. package/dist/pipeline/eval.js.map +1 -0
  78. package/dist/pm/detect.d.ts +17 -0
  79. package/dist/pm/detect.js +131 -0
  80. package/dist/pm/detect.js.map +1 -0
  81. package/dist/profile/profile.d.ts +53 -0
  82. package/dist/profile/profile.js +105 -0
  83. package/dist/profile/profile.js.map +1 -0
  84. package/dist/results/results.d.ts +65 -0
  85. package/dist/results/results.js +214 -0
  86. package/dist/results/results.js.map +1 -0
  87. package/dist/runner/exec.d.ts +69 -0
  88. package/dist/runner/exec.js +185 -0
  89. package/dist/runner/exec.js.map +1 -0
  90. package/dist/scope/scope.d.ts +20 -0
  91. package/dist/scope/scope.js +70 -0
  92. package/dist/scope/scope.js.map +1 -0
  93. package/dist/state/baseline.d.ts +35 -0
  94. package/dist/state/baseline.js +38 -0
  95. package/dist/state/baseline.js.map +1 -0
  96. package/dist/state/home.d.ts +9 -0
  97. package/dist/state/home.js +72 -0
  98. package/dist/state/home.js.map +1 -0
  99. package/dist/state/lock.d.ts +11 -0
  100. package/dist/state/lock.js +89 -0
  101. package/dist/state/lock.js.map +1 -0
  102. package/dist/state/runnaming.d.ts +27 -0
  103. package/dist/state/runnaming.js +47 -0
  104. package/dist/state/runnaming.js.map +1 -0
  105. package/dist/state/stop.d.ts +28 -0
  106. package/dist/state/stop.js +59 -0
  107. package/dist/state/stop.js.map +1 -0
  108. package/dist/stats/delta.d.ts +29 -0
  109. package/dist/stats/delta.js +41 -0
  110. package/dist/stats/delta.js.map +1 -0
  111. package/dist/stats/geomean.d.ts +4 -0
  112. package/dist/stats/geomean.js +21 -0
  113. package/dist/stats/geomean.js.map +1 -0
  114. package/dist/stats/mannwhitney.d.ts +27 -0
  115. package/dist/stats/mannwhitney.js +158 -0
  116. package/dist/stats/mannwhitney.js.map +1 -0
  117. package/dist/verdict/verdict.d.ts +46 -0
  118. package/dist/verdict/verdict.js +102 -0
  119. package/dist/verdict/verdict.js.map +1 -0
  120. package/dist/version.d.ts +2 -0
  121. package/dist/version.js +3 -0
  122. package/dist/version.js.map +1 -0
  123. package/package.json +35 -0
  124. package/templates/program.md +131 -0
@@ -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