assertloom 0.0.0-stage → 0.2.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 +393 -2
- package/dist/ai/anthropic.d.ts +11 -0
- package/dist/ai/anthropic.js +68 -0
- package/dist/ai/anthropic.js.map +1 -0
- package/dist/ai/index.d.ts +12 -0
- package/dist/ai/index.js +23 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/openai.d.ts +9 -0
- package/dist/ai/openai.js +57 -0
- package/dist/ai/openai.js.map +1 -0
- package/dist/ai/openaiCompatible.d.ts +13 -0
- package/dist/ai/openaiCompatible.js +62 -0
- package/dist/ai/openaiCompatible.js.map +1 -0
- package/dist/ai/pricing.d.ts +38 -0
- package/dist/ai/pricing.js +105 -0
- package/dist/ai/pricing.js.map +1 -0
- package/dist/ai/prompts.d.ts +27 -0
- package/dist/ai/prompts.js +89 -0
- package/dist/ai/prompts.js.map +1 -0
- package/dist/ai/provider.d.ts +26 -0
- package/dist/ai/provider.js +4 -0
- package/dist/ai/provider.js.map +1 -0
- package/dist/ai/redact.d.ts +5 -0
- package/dist/ai/redact.js +31 -0
- package/dist/ai/redact.js.map +1 -0
- package/dist/analyzer/dependencies.d.ts +16 -0
- package/dist/analyzer/dependencies.js +168 -0
- package/dist/analyzer/dependencies.js.map +1 -0
- package/dist/analyzer/format.d.ts +3 -0
- package/dist/analyzer/format.js +14 -0
- package/dist/analyzer/format.js.map +1 -0
- package/dist/analyzer/functions.d.ts +4 -0
- package/dist/analyzer/functions.js +324 -0
- package/dist/analyzer/functions.js.map +1 -0
- package/dist/analyzer/index.d.ts +5 -0
- package/dist/analyzer/index.js +29 -0
- package/dist/analyzer/index.js.map +1 -0
- package/dist/analyzer/parser.d.ts +11 -0
- package/dist/analyzer/parser.js +38 -0
- package/dist/analyzer/parser.js.map +1 -0
- package/dist/analyzer/usage.d.ts +16 -0
- package/dist/analyzer/usage.js +159 -0
- package/dist/analyzer/usage.js.map +1 -0
- package/dist/ci/report.d.ts +88 -0
- package/dist/ci/report.js +139 -0
- package/dist/ci/report.js.map +1 -0
- package/dist/ci/run.d.ts +70 -0
- package/dist/ci/run.js +198 -0
- package/dist/ci/run.js.map +1 -0
- package/dist/ci/select.d.ts +58 -0
- package/dist/ci/select.js +238 -0
- package/dist/ci/select.js.map +1 -0
- package/dist/cli/commands/analyze.d.ts +2 -0
- package/dist/cli/commands/analyze.js +49 -0
- package/dist/cli/commands/analyze.js.map +1 -0
- package/dist/cli/commands/ci.d.ts +37 -0
- package/dist/cli/commands/ci.js +258 -0
- package/dist/cli/commands/ci.js.map +1 -0
- package/dist/cli/commands/generate.d.ts +3 -0
- package/dist/cli/commands/generate.js +201 -0
- package/dist/cli/commands/generate.js.map +1 -0
- package/dist/cli/commands/run.d.ts +4 -0
- package/dist/cli/commands/run.js +53 -0
- package/dist/cli/commands/run.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +16 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/program.d.ts +2 -0
- package/dist/cli/program.js +20 -0
- package/dist/cli/program.js.map +1 -0
- package/dist/cli/report.d.ts +3 -0
- package/dist/cli/report.js +36 -0
- package/dist/cli/report.js.map +1 -0
- package/dist/cli/validateFile.d.ts +5 -0
- package/dist/cli/validateFile.js +43 -0
- package/dist/cli/validateFile.js.map +1 -0
- package/dist/errors.d.ts +4 -0
- package/dist/errors.js +8 -0
- package/dist/errors.js.map +1 -0
- package/dist/generator/diff.d.ts +5 -0
- package/dist/generator/diff.js +56 -0
- package/dist/generator/diff.js.map +1 -0
- package/dist/generator/importPlan.d.ts +15 -0
- package/dist/generator/importPlan.js +62 -0
- package/dist/generator/importPlan.js.map +1 -0
- package/dist/generator/index.d.ts +5 -0
- package/dist/generator/index.js +6 -0
- package/dist/generator/index.js.map +1 -0
- package/dist/generator/mocks.d.ts +48 -0
- package/dist/generator/mocks.js +356 -0
- package/dist/generator/mocks.js.map +1 -0
- package/dist/generator/schema.d.ts +40 -0
- package/dist/generator/schema.js +15 -0
- package/dist/generator/schema.js.map +1 -0
- package/dist/generator/testGenerator.d.ts +36 -0
- package/dist/generator/testGenerator.js +85 -0
- package/dist/generator/testGenerator.js.map +1 -0
- package/dist/generator/testWriter.d.ts +22 -0
- package/dist/generator/testWriter.js +93 -0
- package/dist/generator/testWriter.js.map +1 -0
- package/dist/generator/validate.d.ts +31 -0
- package/dist/generator/validate.js +180 -0
- package/dist/generator/validate.js.map +1 -0
- package/dist/repair/classifyFailure.d.ts +11 -0
- package/dist/repair/classifyFailure.js +26 -0
- package/dist/repair/classifyFailure.js.map +1 -0
- package/dist/repair/index.d.ts +2 -0
- package/dist/repair/index.js +3 -0
- package/dist/repair/index.js.map +1 -0
- package/dist/repair/repairLoop.d.ts +74 -0
- package/dist/repair/repairLoop.js +219 -0
- package/dist/repair/repairLoop.js.map +1 -0
- package/dist/repair/schema.d.ts +17 -0
- package/dist/repair/schema.js +14 -0
- package/dist/repair/schema.js.map +1 -0
- package/dist/runner/coverage.d.ts +5 -0
- package/dist/runner/coverage.js +42 -0
- package/dist/runner/coverage.js.map +1 -0
- package/dist/runner/index.d.ts +5 -0
- package/dist/runner/index.js +5 -0
- package/dist/runner/index.js.map +1 -0
- package/dist/runner/jestConfig.d.ts +11 -0
- package/dist/runner/jestConfig.js +103 -0
- package/dist/runner/jestConfig.js.map +1 -0
- package/dist/runner/jestRunner.d.ts +17 -0
- package/dist/runner/jestRunner.js +154 -0
- package/dist/runner/jestRunner.js.map +1 -0
- package/dist/runner/networkGuard.d.ts +4 -0
- package/dist/runner/networkGuard.js +55 -0
- package/dist/runner/networkGuard.js.map +1 -0
- package/dist/runner/project.d.ts +30 -0
- package/dist/runner/project.js +86 -0
- package/dist/runner/project.js.map +1 -0
- package/dist/runner/results.d.ts +32 -0
- package/dist/runner/results.js +58 -0
- package/dist/runner/results.js.map +1 -0
- package/dist/runner/types.d.ts +39 -0
- package/dist/runner/types.js +2 -0
- package/dist/runner/types.js.map +1 -0
- package/dist/runner/vitest.d.ts +24 -0
- package/dist/runner/vitest.js +105 -0
- package/dist/runner/vitest.js.map +1 -0
- package/dist/types/index.d.ts +96 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +67 -4
package/dist/ci/run.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { access, copyFile, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { estimateCost } from "../ai/pricing.js";
|
|
4
|
+
import { analyzeSource } from "../analyzer/index.js";
|
|
5
|
+
import { validateSourceFile } from "../cli/validateFile.js";
|
|
6
|
+
import { BudgetExceededError, generateTests, previewPathFor, writeTestFile } from "../generator/index.js";
|
|
7
|
+
import { repairTests, summarize } from "../repair/index.js";
|
|
8
|
+
import { runJest } from "../runner/index.js";
|
|
9
|
+
const zeroUsage = () => ({ inputTokens: 0, outputTokens: 0 });
|
|
10
|
+
function emptyReport(candidate) {
|
|
11
|
+
return {
|
|
12
|
+
path: candidate.path,
|
|
13
|
+
...(candidate.from ? { from: candidate.from } : {}),
|
|
14
|
+
change: candidate.status,
|
|
15
|
+
status: "processed",
|
|
16
|
+
generated: 0,
|
|
17
|
+
accepted: 0,
|
|
18
|
+
rejected: 0,
|
|
19
|
+
repaired: 0,
|
|
20
|
+
flaggedAsSourceBugs: 0,
|
|
21
|
+
removed: 0,
|
|
22
|
+
gaveUp: 0,
|
|
23
|
+
repairRounds: 0,
|
|
24
|
+
regressions: 0,
|
|
25
|
+
durationMs: 0,
|
|
26
|
+
usage: zeroUsage(),
|
|
27
|
+
costUsd: null,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const key = (suite, name) => `${suite}\u0000${name}`;
|
|
31
|
+
/** Tests that failed in the first run and pass in the final run. */
|
|
32
|
+
export function countRepaired(initial, final) {
|
|
33
|
+
const failedAtFirst = new Set(initial.tests.filter((t) => t.status === "failed").map((t) => key(t.suite, t.name)));
|
|
34
|
+
return final.tests.filter((t) => t.status === "passed" && failedAtFirst.has(key(t.suite, t.name))).length;
|
|
35
|
+
}
|
|
36
|
+
function firstLine(text) {
|
|
37
|
+
return text.split("\n").find((l) => l.trim())?.trim().slice(0, 300) ?? "";
|
|
38
|
+
}
|
|
39
|
+
async function exists(file) {
|
|
40
|
+
return access(file).then(() => true, () => false);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Generates, runs, and repairs tests for each eligible file in turn, with one shared budget.
|
|
44
|
+
* Never writes outside generated preview files, which are removed from the tree once copied to `testsDir`.
|
|
45
|
+
*/
|
|
46
|
+
export async function runFiles(options) {
|
|
47
|
+
const log = options.log ?? (() => { });
|
|
48
|
+
const files = [];
|
|
49
|
+
let budgetExhausted = false;
|
|
50
|
+
let started = 0;
|
|
51
|
+
for (const candidate of options.candidates) {
|
|
52
|
+
const report = emptyReport(candidate);
|
|
53
|
+
files.push(report);
|
|
54
|
+
if (candidate.skip) {
|
|
55
|
+
Object.assign(report, { status: "skipped", skipReason: candidate.skip.reason, detail: candidate.skip.detail });
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (budgetExhausted) {
|
|
59
|
+
Object.assign(report, { status: "skipped", skipReason: "budget", detail: "the shared spending limit was reached" });
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (started >= options.maxFiles) {
|
|
63
|
+
Object.assign(report, { status: "skipped", skipReason: "file-limit", detail: `max-files (${options.maxFiles}) reached` });
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
started++;
|
|
67
|
+
log(`${candidate.path} ...`);
|
|
68
|
+
const outcome = await processFile(candidate, report, options);
|
|
69
|
+
if (outcome === "budget")
|
|
70
|
+
budgetExhausted = true;
|
|
71
|
+
log(` ${describe(report)}`);
|
|
72
|
+
await options.onProgress?.(files);
|
|
73
|
+
}
|
|
74
|
+
await options.onProgress?.(files);
|
|
75
|
+
return { files, budgetExhausted };
|
|
76
|
+
}
|
|
77
|
+
function describe(report) {
|
|
78
|
+
if (report.status === "skipped")
|
|
79
|
+
return `skipped: ${report.detail}`;
|
|
80
|
+
if (report.status === "failed")
|
|
81
|
+
return `failed: ${report.detail}`;
|
|
82
|
+
const f = report.final;
|
|
83
|
+
return `${f.passed} passed, ${f.failed} failed, ${f.skipped} skipped`;
|
|
84
|
+
}
|
|
85
|
+
async function processFile(candidate, report, options) {
|
|
86
|
+
const { budget, provider } = options;
|
|
87
|
+
const startedAt = Date.now();
|
|
88
|
+
const usageBefore = { ...budget.usage };
|
|
89
|
+
const absolute = path.resolve(options.root, candidate.path);
|
|
90
|
+
const previewPath = previewPathFor(absolute);
|
|
91
|
+
let wrotePreview = false;
|
|
92
|
+
let result = "ok";
|
|
93
|
+
const runTests = options.runTests ?? ((testPath, sourcePath) => runJest({ testPath, sourcePath, timeoutMs: options.timeoutMs }));
|
|
94
|
+
try {
|
|
95
|
+
const source = await validateSourceFile(candidate.path, options.root);
|
|
96
|
+
const analysis = analyzeSource(await readFile(absolute, "utf8"), source);
|
|
97
|
+
if (!analysis.functions.some((f) => f.exported)) {
|
|
98
|
+
Object.assign(report, { status: "skipped", skipReason: "no-exported-functions", detail: "no exported functions to test" });
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
// Never overwrite a file that is already there, generated or not.
|
|
102
|
+
if (await exists(previewPath)) {
|
|
103
|
+
Object.assign(report, { status: "skipped", skipReason: "preview-exists", detail: `${path.basename(previewPath)} already exists` });
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
let generation;
|
|
107
|
+
try {
|
|
108
|
+
generation = await generateTests({ source, provider, budget });
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
if (error instanceof BudgetExceededError) {
|
|
112
|
+
Object.assign(report, { status: "skipped", skipReason: "budget", detail: "the shared spending limit was reached" });
|
|
113
|
+
return (result = "budget");
|
|
114
|
+
}
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
report.accepted = generation.suites.reduce((n, s) => n + s.cases.length, 0);
|
|
118
|
+
report.rejected = generation.rejected.length;
|
|
119
|
+
report.generated = report.accepted + report.rejected;
|
|
120
|
+
await writeTestFile(previewPath, generation.content);
|
|
121
|
+
wrotePreview = true;
|
|
122
|
+
const firstRun = await runTests(previewPath, absolute);
|
|
123
|
+
report.initial = summarize(firstRun);
|
|
124
|
+
let finalRun = firstRun;
|
|
125
|
+
let content = generation.content;
|
|
126
|
+
let repair;
|
|
127
|
+
if (firstRun.status === "failed" && options.maxAttempts > 0) {
|
|
128
|
+
repair = await repairTests({
|
|
129
|
+
provider,
|
|
130
|
+
analysis: generation.analysis,
|
|
131
|
+
promptSource: generation.promptSource,
|
|
132
|
+
sourcePath: absolute,
|
|
133
|
+
testPath: previewPath,
|
|
134
|
+
extension: source.extension,
|
|
135
|
+
preamble: generation.preamble,
|
|
136
|
+
mockSummary: generation.mocks.summary,
|
|
137
|
+
suites: generation.suites,
|
|
138
|
+
initialRun: firstRun,
|
|
139
|
+
maxAttempts: options.maxAttempts,
|
|
140
|
+
budget,
|
|
141
|
+
runTests: (testPath) => runTests(testPath, absolute),
|
|
142
|
+
});
|
|
143
|
+
finalRun = repair.run;
|
|
144
|
+
content = repair.content;
|
|
145
|
+
const kept = repair.attempts.filter((a) => !a.regressed);
|
|
146
|
+
report.repairRounds = repair.attempts.length;
|
|
147
|
+
report.regressions = repair.attempts.length - kept.length;
|
|
148
|
+
report.flaggedAsSourceBugs = kept.flatMap((a) => a.changes).filter((c) => c.action === "skip").length;
|
|
149
|
+
report.removed = kept.flatMap((a) => a.changes).filter((c) => c.action === "remove").length;
|
|
150
|
+
report.gaveUp = repair.gaveUp.length;
|
|
151
|
+
report.stopReason = repair.stopReason;
|
|
152
|
+
if (repair.stopReason === "budget")
|
|
153
|
+
result = "budget";
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
report.stopReason = firstRun.status === "passed" ? "all-passing" : firstRun.status === "failed" ? "not-repaired" : "unrepairable";
|
|
157
|
+
}
|
|
158
|
+
report.final = summarize(finalRun);
|
|
159
|
+
report.repaired = countRepaired(firstRun, finalRun);
|
|
160
|
+
if (finalRun.infra) {
|
|
161
|
+
report.infra = { kind: finalRun.infra.kind, message: firstLine(finalRun.infra.message) };
|
|
162
|
+
Object.assign(report, { status: "failed", detail: `tests could not run (${finalRun.infra.kind})` });
|
|
163
|
+
}
|
|
164
|
+
const artifact = path.join(options.testsDir, path.dirname(candidate.path), path.basename(previewPath));
|
|
165
|
+
await mkdir(path.dirname(artifact), { recursive: true });
|
|
166
|
+
await writeFile(artifact, content);
|
|
167
|
+
report.artifact = path.relative(path.dirname(options.testsDir), artifact).split(path.sep).join("/");
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
// Keep whatever generation produced, then move on to the next file.
|
|
171
|
+
if (wrotePreview && !report.artifact)
|
|
172
|
+
await saveArtifact(previewPath, candidate.path, options.testsDir, report);
|
|
173
|
+
Object.assign(report, { status: "failed", detail: firstLine(error instanceof Error ? error.message : String(error)) });
|
|
174
|
+
}
|
|
175
|
+
finally {
|
|
176
|
+
if (wrotePreview)
|
|
177
|
+
await rm(previewPath, { force: true });
|
|
178
|
+
report.durationMs = Date.now() - startedAt;
|
|
179
|
+
report.usage = {
|
|
180
|
+
inputTokens: budget.usage.inputTokens - usageBefore.inputTokens,
|
|
181
|
+
outputTokens: budget.usage.outputTokens - usageBefore.outputTokens,
|
|
182
|
+
};
|
|
183
|
+
report.costUsd = estimateCost(budget.model, report.usage) ?? null;
|
|
184
|
+
}
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
async function saveArtifact(previewPath, relativeSource, testsDir, report) {
|
|
188
|
+
const artifact = path.join(testsDir, path.dirname(relativeSource), path.basename(previewPath));
|
|
189
|
+
try {
|
|
190
|
+
await mkdir(path.dirname(artifact), { recursive: true });
|
|
191
|
+
await copyFile(previewPath, artifact);
|
|
192
|
+
report.artifact = path.relative(path.dirname(testsDir), artifact).split(path.sep).join("/");
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
// The preview may not exist any more; the report still records the failure.
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/ci/run.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { UserError } from "../errors.js";
|
|
2
|
+
export declare class SelectionError extends UserError {
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Paths that are never worth generating tests for: dependencies, build output, tests themselves,
|
|
6
|
+
* type declarations, minified and generated code.
|
|
7
|
+
*/
|
|
8
|
+
export declare const DEFAULT_EXCLUDES: string[];
|
|
9
|
+
export type ChangeStatus = "added" | "modified" | "renamed" | "copied" | "deleted" | "explicit";
|
|
10
|
+
export interface ChangedFile {
|
|
11
|
+
/** Repository-relative path with forward slashes. */
|
|
12
|
+
path: string;
|
|
13
|
+
status: ChangeStatus;
|
|
14
|
+
/** Previous path of a renamed or copied file. */
|
|
15
|
+
from?: string;
|
|
16
|
+
}
|
|
17
|
+
export type SkipReason = "deleted" | "unsupported" | "excluded" | "not-included" | "generated" | "not-a-file" | "outside-workspace" | "too-large" | "file-limit" | "budget" | "no-exported-functions" | "preview-exists" | "dry-run" | "not-processed";
|
|
18
|
+
export interface Candidate extends ChangedFile {
|
|
19
|
+
/** Undefined when the file is eligible. */
|
|
20
|
+
skip?: {
|
|
21
|
+
reason: SkipReason;
|
|
22
|
+
detail: string;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Commit-ish values from event data or inputs. Rejects anything git could read as an option. */
|
|
26
|
+
export declare function checkRef(ref: string, name: string): string;
|
|
27
|
+
export declare function repositoryRoot(cwd: string): Promise<string>;
|
|
28
|
+
/** Parses `git diff --name-status -z` output. Paths may contain spaces, quotes, newlines, or any other byte. */
|
|
29
|
+
export declare function parseNameStatus(output: string): ChangedFile[];
|
|
30
|
+
/**
|
|
31
|
+
* Files added, modified, renamed, or deleted on `head` since it branched from `base`
|
|
32
|
+
* (`git diff base...head`), relative to the repository root.
|
|
33
|
+
*/
|
|
34
|
+
export declare function changedFiles(options: {
|
|
35
|
+
cwd: string;
|
|
36
|
+
base: string;
|
|
37
|
+
head?: string;
|
|
38
|
+
}): Promise<ChangedFile[]>;
|
|
39
|
+
/** Splits a multi-line or comma-separated input into trimmed, non-empty entries. */
|
|
40
|
+
export declare function splitList(value: string | undefined): string[];
|
|
41
|
+
/** Like `splitList`, but newline-only, so file names may contain commas. */
|
|
42
|
+
export declare function splitLines(value: string | undefined): string[];
|
|
43
|
+
/**
|
|
44
|
+
* Converts a glob to a regular expression: `**` matches any number of directories, `*` anything but `/`,
|
|
45
|
+
* `?` one character, and `{a,b}` alternatives. Patterns without a `/` match the file name at any depth.
|
|
46
|
+
*/
|
|
47
|
+
export declare function globToRegExp(glob: string): RegExp;
|
|
48
|
+
export declare function matchesAny(file: string, patterns: RegExp[]): boolean;
|
|
49
|
+
export interface ClassifyOptions {
|
|
50
|
+
root: string;
|
|
51
|
+
include: string[];
|
|
52
|
+
exclude: string[];
|
|
53
|
+
useDefaultExcludes: boolean;
|
|
54
|
+
}
|
|
55
|
+
/** Decides for each changed file whether Assertloom should generate tests for it, and why not. */
|
|
56
|
+
export declare function classify(files: ChangedFile[], options: ClassifyOptions): Promise<Candidate[]>;
|
|
57
|
+
/** Explicit file inputs, for manual runs outside a pull request. */
|
|
58
|
+
export declare function explicitFiles(paths: string[], root: string, cwd: string): ChangedFile[];
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { lstat, readFile, realpath } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { UserError } from "../errors.js";
|
|
5
|
+
import { SUPPORTED_EXTENSIONS } from "../types/index.js";
|
|
6
|
+
export class SelectionError extends UserError {
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Paths that are never worth generating tests for: dependencies, build output, tests themselves,
|
|
10
|
+
* type declarations, minified and generated code.
|
|
11
|
+
*/
|
|
12
|
+
export const DEFAULT_EXCLUDES = [
|
|
13
|
+
"**/node_modules/**",
|
|
14
|
+
"**/dist/**",
|
|
15
|
+
"**/build/**",
|
|
16
|
+
"**/out/**",
|
|
17
|
+
"**/coverage/**",
|
|
18
|
+
"**/vendor/**",
|
|
19
|
+
"**/.next/**",
|
|
20
|
+
"**/.nuxt/**",
|
|
21
|
+
"**/__tests__/**",
|
|
22
|
+
"**/__mocks__/**",
|
|
23
|
+
"**/__generated__/**",
|
|
24
|
+
"**/*.test.*",
|
|
25
|
+
"**/*.spec.*",
|
|
26
|
+
"**/*.d.ts",
|
|
27
|
+
"**/*.d.mts",
|
|
28
|
+
"**/*.d.cts",
|
|
29
|
+
"**/*.min.js",
|
|
30
|
+
"**/*.generated.*",
|
|
31
|
+
];
|
|
32
|
+
const MAX_FILE_SIZE_BYTES = 1024 * 1024;
|
|
33
|
+
const GIT_MAX_BUFFER = 64 * 1024 * 1024;
|
|
34
|
+
function git(args, cwd) {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
// Arguments are passed as an array with no shell, so file names and refs are never interpreted.
|
|
37
|
+
execFile("git", args, { cwd, encoding: "utf8", maxBuffer: GIT_MAX_BUFFER, windowsHide: true }, (error, stdout, stderr) => {
|
|
38
|
+
if (error)
|
|
39
|
+
reject(Object.assign(error, { stderr: String(stderr) }));
|
|
40
|
+
else
|
|
41
|
+
resolve(stdout);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/** Commit-ish values from event data or inputs. Rejects anything git could read as an option. */
|
|
46
|
+
export function checkRef(ref, name) {
|
|
47
|
+
if (!/^[0-9A-Za-z][0-9A-Za-z._/~^@{}-]*$/.test(ref) || ref.includes("..")) {
|
|
48
|
+
throw new SelectionError(`${name} "${ref.slice(0, 80)}" is not a valid commit SHA or ref.`);
|
|
49
|
+
}
|
|
50
|
+
return ref;
|
|
51
|
+
}
|
|
52
|
+
export async function repositoryRoot(cwd) {
|
|
53
|
+
try {
|
|
54
|
+
return path.resolve((await git(["rev-parse", "--show-toplevel"], cwd)).trim());
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
throw new SelectionError(`${cwd} is not inside a git repository. Check out the repository first (actions/checkout).`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function resolveCommit(ref, name, cwd) {
|
|
61
|
+
try {
|
|
62
|
+
return (await git(["rev-parse", "--verify", "--quiet", "--end-of-options", `${ref}^{commit}`], cwd)).trim();
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
const shallow = (await git(["rev-parse", "--is-shallow-repository"], cwd).catch(() => "")).trim() === "true";
|
|
66
|
+
throw new SelectionError(`The ${name} commit ${ref} is not available in this checkout${shallow ? " (it is a shallow clone)" : ""}. ` +
|
|
67
|
+
"Check out with full history (actions/checkout with `fetch-depth: 0`), or fetch the base commit before running Assertloom.");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** Parses `git diff --name-status -z` output. Paths may contain spaces, quotes, newlines, or any other byte. */
|
|
71
|
+
export function parseNameStatus(output) {
|
|
72
|
+
const fields = output.split("\0");
|
|
73
|
+
if (fields.at(-1) === "")
|
|
74
|
+
fields.pop();
|
|
75
|
+
const files = [];
|
|
76
|
+
for (let i = 0; i < fields.length;) {
|
|
77
|
+
const code = fields[i++];
|
|
78
|
+
const kind = code[0];
|
|
79
|
+
if (kind === "R" || kind === "C") {
|
|
80
|
+
const from = fields[i++];
|
|
81
|
+
const to = fields[i++];
|
|
82
|
+
files.push({ path: to, status: kind === "R" ? "renamed" : "copied", from });
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
const file = fields[i++];
|
|
86
|
+
const status = kind === "A" ? "added" : kind === "D" ? "deleted" : "modified";
|
|
87
|
+
files.push({ path: file, status });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return files;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Files added, modified, renamed, or deleted on `head` since it branched from `base`
|
|
94
|
+
* (`git diff base...head`), relative to the repository root.
|
|
95
|
+
*/
|
|
96
|
+
export async function changedFiles(options) {
|
|
97
|
+
const base = await resolveCommit(checkRef(options.base, "Base"), "base", options.cwd);
|
|
98
|
+
const head = await resolveCommit(checkRef(options.head ?? "HEAD", "Head"), "head", options.cwd);
|
|
99
|
+
try {
|
|
100
|
+
await git(["merge-base", base, head], options.cwd);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
throw new SelectionError(`No common ancestor of ${base.slice(0, 12)} and ${head.slice(0, 12)} is available. ` +
|
|
104
|
+
"The checkout is probably shallow: use actions/checkout with `fetch-depth: 0`.");
|
|
105
|
+
}
|
|
106
|
+
const output = await git(["-c", "core.quotepath=off", "diff", "--name-status", "-z", "--find-renames", "--no-ext-diff", "--no-textconv", `${base}...${head}`, "--"], options.cwd);
|
|
107
|
+
return parseNameStatus(output);
|
|
108
|
+
}
|
|
109
|
+
/** Splits a multi-line or comma-separated input into trimmed, non-empty entries. */
|
|
110
|
+
export function splitList(value) {
|
|
111
|
+
if (!value)
|
|
112
|
+
return [];
|
|
113
|
+
return value
|
|
114
|
+
.split(/\r?\n|,/)
|
|
115
|
+
.map((s) => s.trim())
|
|
116
|
+
.filter(Boolean);
|
|
117
|
+
}
|
|
118
|
+
/** Like `splitList`, but newline-only, so file names may contain commas. */
|
|
119
|
+
export function splitLines(value) {
|
|
120
|
+
if (!value)
|
|
121
|
+
return [];
|
|
122
|
+
return value
|
|
123
|
+
.split(/\r?\n/)
|
|
124
|
+
.map((s) => s.trim())
|
|
125
|
+
.filter(Boolean);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Converts a glob to a regular expression: `**` matches any number of directories, `*` anything but `/`,
|
|
129
|
+
* `?` one character, and `{a,b}` alternatives. Patterns without a `/` match the file name at any depth.
|
|
130
|
+
*/
|
|
131
|
+
export function globToRegExp(glob) {
|
|
132
|
+
const pattern = glob.includes("/") ? glob.replace(/^\.\//, "").replace(/^\//, "") : `**/${glob}`;
|
|
133
|
+
let re = "";
|
|
134
|
+
let braces = 0;
|
|
135
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
136
|
+
const c = pattern[i];
|
|
137
|
+
if (c === "*") {
|
|
138
|
+
if (pattern[i + 1] === "*") {
|
|
139
|
+
const slash = pattern[i + 2] === "/";
|
|
140
|
+
re += slash ? "(?:.*/)?" : ".*";
|
|
141
|
+
i += slash ? 2 : 1;
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
re += "[^/]*";
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else if (c === "?")
|
|
148
|
+
re += "[^/]";
|
|
149
|
+
else if (c === "{") {
|
|
150
|
+
braces++;
|
|
151
|
+
re += "(?:";
|
|
152
|
+
}
|
|
153
|
+
else if (c === "}" && braces > 0) {
|
|
154
|
+
braces--;
|
|
155
|
+
re += ")";
|
|
156
|
+
}
|
|
157
|
+
else if (c === "," && braces > 0)
|
|
158
|
+
re += "|";
|
|
159
|
+
else
|
|
160
|
+
re += c.replace(/[.+^$()|[\]\\]/g, "\\$&");
|
|
161
|
+
}
|
|
162
|
+
return new RegExp(`^${re}$`);
|
|
163
|
+
}
|
|
164
|
+
export function matchesAny(file, patterns) {
|
|
165
|
+
return patterns.some((p) => p.test(file));
|
|
166
|
+
}
|
|
167
|
+
const toPosix = (p) => p.split(path.sep).join("/");
|
|
168
|
+
/** `@generated` near the top of a file marks it as machine-written (a common convention). */
|
|
169
|
+
function looksGenerated(head) {
|
|
170
|
+
return /@generated\b|\bDO NOT EDIT\b|^\/\/ Generated by Assertloom\./m.test(head.slice(0, 600));
|
|
171
|
+
}
|
|
172
|
+
/** Decides for each changed file whether Assertloom should generate tests for it, and why not. */
|
|
173
|
+
export async function classify(files, options) {
|
|
174
|
+
const include = options.include.map(globToRegExp);
|
|
175
|
+
const exclude = [...(options.useDefaultExcludes ? DEFAULT_EXCLUDES : []), ...options.exclude].map(globToRegExp);
|
|
176
|
+
const realRoot = await realpath(options.root);
|
|
177
|
+
const seen = new Set();
|
|
178
|
+
const result = [];
|
|
179
|
+
for (const file of files) {
|
|
180
|
+
const rel = toPosix(file.path);
|
|
181
|
+
const key = `${file.status === "deleted" ? "D" : ""}${rel}`;
|
|
182
|
+
if (seen.has(key))
|
|
183
|
+
continue;
|
|
184
|
+
seen.add(key);
|
|
185
|
+
const candidate = { ...file, path: rel };
|
|
186
|
+
candidate.skip = await skipReason(candidate, { include, exclude, root: options.root, realRoot });
|
|
187
|
+
result.push(candidate);
|
|
188
|
+
}
|
|
189
|
+
return result.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
190
|
+
}
|
|
191
|
+
async function skipReason(file, ctx) {
|
|
192
|
+
if (file.status === "deleted")
|
|
193
|
+
return { reason: "deleted", detail: "deleted in this change" };
|
|
194
|
+
const absolute = path.resolve(ctx.root, file.path);
|
|
195
|
+
const inside = path.relative(ctx.root, absolute);
|
|
196
|
+
if (!inside || inside.startsWith("..") || path.isAbsolute(inside)) {
|
|
197
|
+
return { reason: "outside-workspace", detail: "path is outside the repository" };
|
|
198
|
+
}
|
|
199
|
+
const extension = path.extname(file.path).toLowerCase();
|
|
200
|
+
if (!SUPPORTED_EXTENSIONS.includes(extension)) {
|
|
201
|
+
return { reason: "unsupported", detail: `unsupported file type "${extension || "(none)"}"` };
|
|
202
|
+
}
|
|
203
|
+
if (matchesAny(file.path, ctx.exclude))
|
|
204
|
+
return { reason: "excluded", detail: "matches an exclude pattern" };
|
|
205
|
+
if (ctx.include.length && !matchesAny(file.path, ctx.include))
|
|
206
|
+
return { reason: "not-included", detail: "matches no include pattern" };
|
|
207
|
+
let stats;
|
|
208
|
+
try {
|
|
209
|
+
stats = await lstat(absolute);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return { reason: "not-a-file", detail: "not found in the checkout" };
|
|
213
|
+
}
|
|
214
|
+
// A symlink could point outside the repository; its target would be sent to the model.
|
|
215
|
+
if (stats.isSymbolicLink())
|
|
216
|
+
return { reason: "not-a-file", detail: "symbolic link" };
|
|
217
|
+
if (!stats.isFile())
|
|
218
|
+
return { reason: "not-a-file", detail: "not a regular file" };
|
|
219
|
+
const real = await realpath(absolute);
|
|
220
|
+
const realInside = path.relative(ctx.realRoot, real);
|
|
221
|
+
if (realInside.startsWith("..") || path.isAbsolute(realInside)) {
|
|
222
|
+
return { reason: "outside-workspace", detail: "resolves outside the repository" };
|
|
223
|
+
}
|
|
224
|
+
if (stats.size > MAX_FILE_SIZE_BYTES)
|
|
225
|
+
return { reason: "too-large", detail: `${Math.round(stats.size / 1024)} KB; the limit is 1 MB` };
|
|
226
|
+
const head = (await readFile(absolute, "utf8")).slice(0, 600);
|
|
227
|
+
if (looksGenerated(head))
|
|
228
|
+
return { reason: "generated", detail: "marked as generated" };
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
/** Explicit file inputs, for manual runs outside a pull request. */
|
|
232
|
+
export function explicitFiles(paths, root, cwd) {
|
|
233
|
+
return paths.map((p) => {
|
|
234
|
+
const relative = path.relative(root, path.resolve(cwd, p));
|
|
235
|
+
return { path: relative.startsWith("..") || path.isAbsolute(relative) ? p : toPosix(relative), status: "explicit" };
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=select.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select.js","sourceRoot":"","sources":["../../src/ci/select.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { formatSignature } from "../../analyzer/format.js";
|
|
2
|
+
import { analyzeFile } from "../../analyzer/index.js";
|
|
3
|
+
import { validateSourceFile } from "../validateFile.js";
|
|
4
|
+
function plural(count, word, pluralWord = `${word}s`) {
|
|
5
|
+
return `${count} ${count === 1 ? word : pluralWord}`;
|
|
6
|
+
}
|
|
7
|
+
function signature(fn) {
|
|
8
|
+
const alias = fn.exportedAs.filter((name) => name !== fn.name);
|
|
9
|
+
const notes = [
|
|
10
|
+
alias.length ? `exported as ${alias.join(", ")}` : "",
|
|
11
|
+
fn.throws ? "throws" : "",
|
|
12
|
+
fn.dependencies.length || fn.globals.length ? `uses ${[...fn.dependencies, ...fn.globals.map((g) => `global ${g}`)].join(", ")}` : "",
|
|
13
|
+
].filter(Boolean);
|
|
14
|
+
return `${formatSignature(fn)}${notes.length ? ` [${notes.join("; ")}]` : ""}`;
|
|
15
|
+
}
|
|
16
|
+
function printSummary(analysis) {
|
|
17
|
+
const exported = analysis.functions.filter((f) => f.exported);
|
|
18
|
+
const internal = analysis.functions.length - exported.length;
|
|
19
|
+
const { local, external, builtin } = analysis.dependencies;
|
|
20
|
+
const language = analysis.language === "typescript" ? "TypeScript" : "JavaScript";
|
|
21
|
+
console.log(`✓ Parsed ${analysis.file} (${language}, ${analysis.moduleSystem === "none" ? "no imports/exports" : analysis.moduleSystem.toUpperCase()})`);
|
|
22
|
+
console.log(`✓ Found ${plural(exported.length, "exported function")}${internal ? ` (+${internal} internal)` : ""}`);
|
|
23
|
+
console.log(`✓ Found ${plural(local.length + external.length + builtin.length, "dependency", "dependencies")} ` +
|
|
24
|
+
`(${external.length} external, ${local.length} local, ${builtin.length} built-in)`);
|
|
25
|
+
if (exported.length) {
|
|
26
|
+
console.log("");
|
|
27
|
+
for (const fn of exported)
|
|
28
|
+
console.log(` ${signature(fn)}`);
|
|
29
|
+
}
|
|
30
|
+
if (external.length)
|
|
31
|
+
console.log(`\nExternal packages: ${external.join(", ")}`);
|
|
32
|
+
}
|
|
33
|
+
export function registerAnalyzeCommand(program) {
|
|
34
|
+
program
|
|
35
|
+
.command("analyze")
|
|
36
|
+
.description("Statically analyze a JS/TS file: exported functions, parameters, imports, and dependencies")
|
|
37
|
+
.argument("<file>", "path to a JavaScript or TypeScript source file")
|
|
38
|
+
.option("--json", "print the full analysis as JSON")
|
|
39
|
+
.action(async (file, options) => {
|
|
40
|
+
const analysis = await analyzeFile(await validateSourceFile(file));
|
|
41
|
+
if (options.json) {
|
|
42
|
+
console.log(JSON.stringify(analysis, null, 2));
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
printSummary(analysis);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=analyze.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze.js","sourceRoot":"","sources":["../../../src/cli/commands/analyze.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type Command } from "commander";
|
|
2
|
+
import { type LLMProvider, type ProviderOptions } from "../../ai/index.js";
|
|
3
|
+
import { type CiReport } from "../../ci/report.js";
|
|
4
|
+
import type { RunResult } from "../../runner/index.js";
|
|
5
|
+
/** Environment variables that hold provider credentials. Their values are redacted from everything Assertloom writes. */
|
|
6
|
+
export declare const CREDENTIAL_ENV: string[];
|
|
7
|
+
export declare const DEFAULT_CI_MAX_FILES = 10;
|
|
8
|
+
export declare const DEFAULT_CI_MAX_COST_USD = 1;
|
|
9
|
+
export interface CiCommandOptions {
|
|
10
|
+
base?: string;
|
|
11
|
+
head?: string;
|
|
12
|
+
files?: string;
|
|
13
|
+
include?: string;
|
|
14
|
+
exclude?: string;
|
|
15
|
+
defaultExcludes: boolean;
|
|
16
|
+
maxFiles: string;
|
|
17
|
+
maxCost: string;
|
|
18
|
+
maxTokens?: string;
|
|
19
|
+
maxAttempts: string;
|
|
20
|
+
timeout: string;
|
|
21
|
+
out: string;
|
|
22
|
+
failOn: string;
|
|
23
|
+
provider: string;
|
|
24
|
+
model?: string;
|
|
25
|
+
baseUrl?: string;
|
|
26
|
+
workingDirectory?: string;
|
|
27
|
+
dryRun?: boolean;
|
|
28
|
+
githubSummary?: string;
|
|
29
|
+
githubOutput?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface CiDependencies {
|
|
32
|
+
createProvider: (name: string, options: ProviderOptions) => LLMProvider;
|
|
33
|
+
runTests?: (testPath: string, sourcePath: string) => Promise<RunResult>;
|
|
34
|
+
log: (line: string) => void;
|
|
35
|
+
}
|
|
36
|
+
export declare function runCi(options: CiCommandOptions, deps: CiDependencies): Promise<CiReport>;
|
|
37
|
+
export declare function registerCiCommand(program: Command, deps?: Partial<CiDependencies>): void;
|