@mrsf/cli 0.6.0 → 0.7.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/README.md +10 -7
- package/dist/browser.cjs +1101 -24
- package/dist/browser.cjs.map +4 -4
- package/dist/browser.d.ts +11 -0
- package/dist/browser.js +8 -0
- package/dist/evaluation/generate-reanchor-cases.d.ts +10 -0
- package/dist/evaluation/generate-reanchor-cases.js +301 -0
- package/dist/evaluation/reanchor-eval.d.ts +111 -0
- package/dist/evaluation/reanchor-eval.js +266 -0
- package/dist/evaluation/reanchor-gates.d.ts +29 -0
- package/dist/evaluation/reanchor-gates.js +47 -0
- package/dist/evaluation/run-reanchor-eval.d.ts +3 -0
- package/dist/evaluation/run-reanchor-eval.js +103 -0
- package/dist/evaluation/run-reanchor-generator.d.ts +3 -0
- package/dist/evaluation/run-reanchor-generator.js +63 -0
- package/dist/evaluation/run-reanchor-profile.d.ts +3 -0
- package/dist/evaluation/run-reanchor-profile.js +129 -0
- package/dist/index.cjs +1209 -34
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +12 -0
- package/dist/index.js +9 -0
- package/dist/lib/anchor-context.d.ts +46 -0
- package/dist/lib/anchor-context.js +474 -0
- package/dist/lib/confidence-calibration.d.ts +16 -0
- package/dist/lib/confidence-calibration.js +120 -0
- package/dist/lib/fuzzy.d.ts +14 -1
- package/dist/lib/fuzzy.js +140 -19
- package/dist/lib/git.d.ts +2 -0
- package/dist/lib/git.js +12 -0
- package/dist/lib/global-reconciliation.d.ts +11 -0
- package/dist/lib/global-reconciliation.js +145 -0
- package/dist/lib/reanchor-core.d.ts +7 -0
- package/dist/lib/reanchor-core.js +46 -4
- package/dist/lib/reanchor.js +82 -11
- package/dist/lib/revision-projection.d.ts +21 -0
- package/dist/lib/revision-projection.js +203 -0
- package/package.json +5 -1
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type ReanchorGateMode = "correctness" | "performance";
|
|
2
|
+
export interface ReanchorProfileReport {
|
|
3
|
+
profile: string;
|
|
4
|
+
metrics: {
|
|
5
|
+
passRate: number;
|
|
6
|
+
incorrectConfidentRelocations: number;
|
|
7
|
+
};
|
|
8
|
+
timingMs: {
|
|
9
|
+
perComment: number;
|
|
10
|
+
p95: number;
|
|
11
|
+
max: number;
|
|
12
|
+
reconciliationMax: number;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export interface ReanchorGatePolicy {
|
|
16
|
+
version: 1;
|
|
17
|
+
correctness: Record<string, {
|
|
18
|
+
minimum_pass_rate: number;
|
|
19
|
+
maximum_incorrect_confident_relocations: number;
|
|
20
|
+
}>;
|
|
21
|
+
performance: Record<string, {
|
|
22
|
+
maximum_ms_per_comment: number;
|
|
23
|
+
maximum_p95_comment_ms: number;
|
|
24
|
+
maximum_single_comment_ms: number;
|
|
25
|
+
maximum_reconciliation_ms: number;
|
|
26
|
+
}>;
|
|
27
|
+
}
|
|
28
|
+
export declare function evaluateReanchorGate(report: ReanchorProfileReport, policy: ReanchorGatePolicy, mode: ReanchorGateMode): string[];
|
|
29
|
+
//# sourceMappingURL=reanchor-gates.d.ts.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export function evaluateReanchorGate(report, policy, mode) {
|
|
2
|
+
const failures = [];
|
|
3
|
+
const correctness = policy.correctness[report.profile];
|
|
4
|
+
if (!correctness) {
|
|
5
|
+
failures.push(`No correctness gate configured for profile "${report.profile}".`);
|
|
6
|
+
}
|
|
7
|
+
else {
|
|
8
|
+
if (report.metrics.passRate < correctness.minimum_pass_rate) {
|
|
9
|
+
failures.push(`Pass rate ${report.metrics.passRate.toFixed(4)} is below `
|
|
10
|
+
+ `${correctness.minimum_pass_rate.toFixed(4)}.`);
|
|
11
|
+
}
|
|
12
|
+
if (report.metrics.incorrectConfidentRelocations
|
|
13
|
+
> correctness.maximum_incorrect_confident_relocations) {
|
|
14
|
+
failures.push(`${report.metrics.incorrectConfidentRelocations} incorrect confident `
|
|
15
|
+
+ `relocations exceed the allowed `
|
|
16
|
+
+ `${correctness.maximum_incorrect_confident_relocations}.`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (mode === "performance") {
|
|
20
|
+
const performance = policy.performance[report.profile];
|
|
21
|
+
if (!performance) {
|
|
22
|
+
failures.push(`No performance gate configured for profile "${report.profile}".`);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
if (report.timingMs.perComment > performance.maximum_ms_per_comment) {
|
|
26
|
+
failures.push(`${report.timingMs.perComment.toFixed(3)} ms/comment exceeds `
|
|
27
|
+
+ `${performance.maximum_ms_per_comment.toFixed(3)} ms/comment.`);
|
|
28
|
+
}
|
|
29
|
+
if (report.timingMs.p95 > performance.maximum_p95_comment_ms) {
|
|
30
|
+
failures.push(`p95 ${report.timingMs.p95.toFixed(3)} ms exceeds `
|
|
31
|
+
+ `${performance.maximum_p95_comment_ms.toFixed(3)} ms.`);
|
|
32
|
+
}
|
|
33
|
+
if (report.timingMs.max > performance.maximum_single_comment_ms) {
|
|
34
|
+
failures.push(`maximum ${report.timingMs.max.toFixed(3)} ms exceeds `
|
|
35
|
+
+ `${performance.maximum_single_comment_ms.toFixed(3)} ms.`);
|
|
36
|
+
}
|
|
37
|
+
if (report.timingMs.reconciliationMax
|
|
38
|
+
> performance.maximum_reconciliation_ms) {
|
|
39
|
+
failures.push(`reconciliation maximum `
|
|
40
|
+
+ `${report.timingMs.reconciliationMax.toFixed(3)} ms exceeds `
|
|
41
|
+
+ `${performance.maximum_reconciliation_ms.toFixed(3)} ms.`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return failures;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=reanchor-gates.js.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
5
|
+
import { createEvaluationBaseline, evaluateCases, findBaselineDifferences, loadEvaluationCases, } from "./reanchor-eval.js";
|
|
6
|
+
async function main() {
|
|
7
|
+
const options = parseArguments(process.argv.slice(2));
|
|
8
|
+
const casesPath = path.resolve(options.casesPath);
|
|
9
|
+
const schemaPath = path.resolve(options.schemaPath);
|
|
10
|
+
const cases = await loadEvaluationCases(casesPath, schemaPath);
|
|
11
|
+
const summary = await evaluateCases(cases);
|
|
12
|
+
const baseline = createEvaluationBaseline(summary);
|
|
13
|
+
if (options.json) {
|
|
14
|
+
process.stdout.write(`${JSON.stringify(summary, null, 2)}\n`);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
printSummary(summary);
|
|
18
|
+
}
|
|
19
|
+
if (options.writeBaselinePath) {
|
|
20
|
+
await writeFile(path.resolve(options.writeBaselinePath), `${JSON.stringify(baseline, null, 2)}\n`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (options.baselinePath) {
|
|
24
|
+
const expected = JSON.parse(await readFile(path.resolve(options.baselinePath), "utf8"));
|
|
25
|
+
const differences = findBaselineDifferences(expected, baseline);
|
|
26
|
+
if (differences.length > 0) {
|
|
27
|
+
for (const difference of differences) {
|
|
28
|
+
console.error(`BASELINE ${difference}`);
|
|
29
|
+
}
|
|
30
|
+
process.exitCode = 1;
|
|
31
|
+
}
|
|
32
|
+
else if (!options.json) {
|
|
33
|
+
console.log("Baseline matches.");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else if (summary.failed > 0) {
|
|
37
|
+
process.exitCode = 1;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function parseArguments(args) {
|
|
41
|
+
let casesPath = "../evaluation/reanchor/cases";
|
|
42
|
+
let schemaPath = "../evaluation/reanchor/schema.json";
|
|
43
|
+
let json = false;
|
|
44
|
+
let baselinePath;
|
|
45
|
+
let writeBaselinePath;
|
|
46
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
47
|
+
const argument = args[index];
|
|
48
|
+
if (argument === "--json") {
|
|
49
|
+
json = true;
|
|
50
|
+
}
|
|
51
|
+
else if (argument === "--cases" && args[index + 1]) {
|
|
52
|
+
casesPath = args[index += 1];
|
|
53
|
+
}
|
|
54
|
+
else if (argument === "--schema" && args[index + 1]) {
|
|
55
|
+
schemaPath = args[index += 1];
|
|
56
|
+
}
|
|
57
|
+
else if (argument === "--baseline" && args[index + 1]) {
|
|
58
|
+
baselinePath = args[index += 1];
|
|
59
|
+
}
|
|
60
|
+
else if (argument === "--write-baseline" && args[index + 1]) {
|
|
61
|
+
writeBaselinePath = args[index += 1];
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
throw new Error(`Unknown or incomplete argument: ${argument}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (baselinePath && writeBaselinePath) {
|
|
68
|
+
throw new Error("--baseline and --write-baseline cannot be used together.");
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
casesPath,
|
|
72
|
+
schemaPath,
|
|
73
|
+
json,
|
|
74
|
+
baselinePath,
|
|
75
|
+
writeBaselinePath,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function printSummary(summary) {
|
|
79
|
+
const passRate = summary.comments === 0
|
|
80
|
+
? 0
|
|
81
|
+
: (summary.passed / summary.comments) * 100;
|
|
82
|
+
console.log(`Reanchoring evaluation (${summary.algorithm})`);
|
|
83
|
+
console.log(`Cases: ${summary.cases}`);
|
|
84
|
+
console.log(`Comments: ${summary.comments}`);
|
|
85
|
+
console.log(`Passed: ${summary.passed}/${summary.comments} (${passRate.toFixed(1)}%)`);
|
|
86
|
+
console.log(`Status matches: ${summary.statusMatches}/${summary.comments}`);
|
|
87
|
+
console.log(`Exact range matches: ${summary.exactRangeMatches}/${summary.comments}`);
|
|
88
|
+
console.log(`Incorrect confident relocations: ${summary.incorrectConfidentRelocations}`);
|
|
89
|
+
console.log(`Duration: ${summary.durationMs.toFixed(2)} ms`);
|
|
90
|
+
console.log(`Per-comment timing: median ${summary.timingMs.median.toFixed(3)} ms, `
|
|
91
|
+
+ `p95 ${summary.timingMs.p95.toFixed(3)} ms, `
|
|
92
|
+
+ `max ${summary.timingMs.max.toFixed(3)} ms`);
|
|
93
|
+
for (const result of summary.results.filter((item) => !item.passed)) {
|
|
94
|
+
console.log(`FAIL ${result.caseId}/${result.commentId}: expected ${result.expected.status}, `
|
|
95
|
+
+ `received ${result.actual.status} (${result.actual.reason})`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
main().catch((error) => {
|
|
99
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
100
|
+
console.error(message);
|
|
101
|
+
process.exitCode = 1;
|
|
102
|
+
});
|
|
103
|
+
//# sourceMappingURL=run-reanchor-eval.js.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { generateEvaluationCases } from "./generate-reanchor-cases.js";
|
|
6
|
+
async function main() {
|
|
7
|
+
const options = parseArguments(process.argv.slice(2));
|
|
8
|
+
const outputPath = path.resolve(options.output);
|
|
9
|
+
const cases = generateEvaluationCases(options);
|
|
10
|
+
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
11
|
+
await writeFile(outputPath, `${JSON.stringify(cases, null, 2)}\n`);
|
|
12
|
+
console.log(`Generated ${cases.length} reanchoring cases at ${outputPath} `
|
|
13
|
+
+ `(seeds ${options.seed}-${options.seed + cases.length - 1}).`);
|
|
14
|
+
}
|
|
15
|
+
function parseArguments(args) {
|
|
16
|
+
const values = new Map();
|
|
17
|
+
for (let index = 0; index < args.length; index += 2) {
|
|
18
|
+
const name = args[index];
|
|
19
|
+
const value = args[index + 1];
|
|
20
|
+
if (!name?.startsWith("--") || value == null) {
|
|
21
|
+
throw new Error(`Unknown or incomplete argument: ${name ?? ""}`);
|
|
22
|
+
}
|
|
23
|
+
values.set(name, value);
|
|
24
|
+
}
|
|
25
|
+
const supported = new Set([
|
|
26
|
+
"--seed",
|
|
27
|
+
"--cases",
|
|
28
|
+
"--blocks",
|
|
29
|
+
"--comments",
|
|
30
|
+
"--mutations",
|
|
31
|
+
"--output",
|
|
32
|
+
]);
|
|
33
|
+
for (const name of values.keys()) {
|
|
34
|
+
if (!supported.has(name))
|
|
35
|
+
throw new Error(`Unknown argument: ${name}`);
|
|
36
|
+
}
|
|
37
|
+
const seed = integerArgument(values, "--seed", 1);
|
|
38
|
+
return {
|
|
39
|
+
seed,
|
|
40
|
+
caseCount: integerArgument(values, "--cases", 10),
|
|
41
|
+
blocksPerCase: integerArgument(values, "--blocks", 12),
|
|
42
|
+
commentsPerCase: integerArgument(values, "--comments", 4),
|
|
43
|
+
mutationsPerCase: integerArgument(values, "--mutations", 5),
|
|
44
|
+
output: values.get("--output")
|
|
45
|
+
?? `../evaluation/reanchor/generated/seed-${seed}.json`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function integerArgument(values, name, fallback) {
|
|
49
|
+
const raw = values.get(name);
|
|
50
|
+
if (raw == null)
|
|
51
|
+
return fallback;
|
|
52
|
+
const value = Number(raw);
|
|
53
|
+
if (!Number.isSafeInteger(value)) {
|
|
54
|
+
throw new Error(`${name} must be an integer.`);
|
|
55
|
+
}
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
main().catch((error) => {
|
|
59
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
60
|
+
console.error(message);
|
|
61
|
+
process.exitCode = 1;
|
|
62
|
+
});
|
|
63
|
+
//# sourceMappingURL=run-reanchor-generator.js.map
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { generateEvaluationCases } from "./generate-reanchor-cases.js";
|
|
6
|
+
import { evaluateCases } from "./reanchor-eval.js";
|
|
7
|
+
import { evaluateReanchorGate, } from "./reanchor-gates.js";
|
|
8
|
+
async function main() {
|
|
9
|
+
const options = parseArguments(process.argv.slice(2));
|
|
10
|
+
const workloads = JSON.parse(await readFile(path.resolve(options.workloadsPath), "utf8"));
|
|
11
|
+
assertWorkloadFile(workloads);
|
|
12
|
+
const config = workloads.profiles[options.profile];
|
|
13
|
+
if (!config) {
|
|
14
|
+
throw new Error(`Unknown workload profile "${options.profile}". Available profiles: `
|
|
15
|
+
+ `${Object.keys(workloads.profiles).sort().join(", ")}.`);
|
|
16
|
+
}
|
|
17
|
+
const generated = generateEvaluationCases({
|
|
18
|
+
seed: config.seed,
|
|
19
|
+
caseCount: config.case_count,
|
|
20
|
+
blocksPerCase: config.blocks_per_case,
|
|
21
|
+
commentsPerCase: config.comments_per_case,
|
|
22
|
+
mutationsPerCase: config.mutations_per_case,
|
|
23
|
+
});
|
|
24
|
+
const cases = generated.map((value) => ({
|
|
25
|
+
casePath: `<generated:${value.id}>`,
|
|
26
|
+
value,
|
|
27
|
+
}));
|
|
28
|
+
const summary = await evaluateCases(cases);
|
|
29
|
+
const report = {
|
|
30
|
+
profile: options.profile,
|
|
31
|
+
config,
|
|
32
|
+
metrics: {
|
|
33
|
+
cases: summary.cases,
|
|
34
|
+
comments: summary.comments,
|
|
35
|
+
passed: summary.passed,
|
|
36
|
+
failed: summary.failed,
|
|
37
|
+
passRate: summary.comments > 0
|
|
38
|
+
? summary.passed / summary.comments
|
|
39
|
+
: 0,
|
|
40
|
+
incorrectConfidentRelocations: summary.incorrectConfidentRelocations,
|
|
41
|
+
},
|
|
42
|
+
timingMs: {
|
|
43
|
+
total: summary.durationMs,
|
|
44
|
+
...summary.timingMs,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
let gateFailures = [];
|
|
48
|
+
if (options.gatePath) {
|
|
49
|
+
const policy = JSON.parse(await readFile(path.resolve(options.gatePath), "utf8"));
|
|
50
|
+
gateFailures = evaluateReanchorGate(report, policy, options.gateMode);
|
|
51
|
+
}
|
|
52
|
+
if (options.json) {
|
|
53
|
+
console.log(JSON.stringify(report, null, 2));
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
console.log(`Reanchoring workload: ${options.profile}`);
|
|
57
|
+
console.log(`${report.metrics.cases} cases, ${report.metrics.comments} comments, `
|
|
58
|
+
+ `${report.metrics.passed} passed, ${report.metrics.failed} failed`);
|
|
59
|
+
console.log(`${report.metrics.incorrectConfidentRelocations} incorrect confident relocations`);
|
|
60
|
+
console.log(`Timing: ${report.timingMs.total.toFixed(2)} ms total, `
|
|
61
|
+
+ `${report.timingMs.perComment.toFixed(3)} ms/comment, `
|
|
62
|
+
+ `${report.timingMs.p95.toFixed(3)} ms p95`);
|
|
63
|
+
}
|
|
64
|
+
if (gateFailures.length > 0) {
|
|
65
|
+
throw new Error(`Reanchoring ${options.gateMode} gate failed for ${options.profile}:\n`
|
|
66
|
+
+ gateFailures.map((failure) => `- ${failure}`).join("\n"));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function parseArguments(args) {
|
|
70
|
+
let profile = "small";
|
|
71
|
+
let workloadsPath = "../evaluation/reanchor/workloads.json";
|
|
72
|
+
let json = false;
|
|
73
|
+
let gatePath;
|
|
74
|
+
let gateMode = "correctness";
|
|
75
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
76
|
+
const argument = args[index];
|
|
77
|
+
if (argument === "--json") {
|
|
78
|
+
json = true;
|
|
79
|
+
}
|
|
80
|
+
else if (argument === "--profile" && args[index + 1]) {
|
|
81
|
+
profile = args[index += 1];
|
|
82
|
+
}
|
|
83
|
+
else if (argument === "--workloads" && args[index + 1]) {
|
|
84
|
+
workloadsPath = args[index += 1];
|
|
85
|
+
}
|
|
86
|
+
else if (argument === "--gate" && args[index + 1]) {
|
|
87
|
+
gatePath = args[index += 1];
|
|
88
|
+
}
|
|
89
|
+
else if (argument === "--gate-mode"
|
|
90
|
+
&& (args[index + 1] === "correctness"
|
|
91
|
+
|| args[index + 1] === "performance")) {
|
|
92
|
+
gateMode = args[index += 1];
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
throw new Error(`Unknown or incomplete argument: ${argument}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return { profile, workloadsPath, json, gatePath, gateMode };
|
|
99
|
+
}
|
|
100
|
+
function assertWorkloadFile(value) {
|
|
101
|
+
if (!isRecord(value) || value.version !== 1 || !isRecord(value.profiles)) {
|
|
102
|
+
throw new Error("Workload file must contain version 1 and a profiles object.");
|
|
103
|
+
}
|
|
104
|
+
for (const [name, profile] of Object.entries(value.profiles)) {
|
|
105
|
+
if (!isRecord(profile)
|
|
106
|
+
|| !isNonNegativeInteger(profile.seed)
|
|
107
|
+
|| !isPositiveInteger(profile.case_count)
|
|
108
|
+
|| !isPositiveInteger(profile.blocks_per_case)
|
|
109
|
+
|| !isPositiveInteger(profile.comments_per_case)
|
|
110
|
+
|| !isPositiveInteger(profile.mutations_per_case)) {
|
|
111
|
+
throw new Error(`Workload profile "${name}" is invalid.`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function isRecord(value) {
|
|
116
|
+
return typeof value === "object" && value != null && !Array.isArray(value);
|
|
117
|
+
}
|
|
118
|
+
function isPositiveInteger(value) {
|
|
119
|
+
return Number.isSafeInteger(value) && Number(value) > 0;
|
|
120
|
+
}
|
|
121
|
+
function isNonNegativeInteger(value) {
|
|
122
|
+
return Number.isSafeInteger(value) && Number(value) >= 0;
|
|
123
|
+
}
|
|
124
|
+
main().catch((error) => {
|
|
125
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
126
|
+
console.error(message);
|
|
127
|
+
process.exitCode = 1;
|
|
128
|
+
});
|
|
129
|
+
//# sourceMappingURL=run-reanchor-profile.js.map
|