revyse 0.2.0 → 0.2.1
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/dist/agent.js +1 -1
- package/dist/{chunk-FYWZYXR4.js → chunk-FVZWKW32.js} +417 -184
- package/dist/headless.js +429 -196
- package/dist/index.js +1 -1
- package/dist/overlay.js +26 -26
- package/package.json +3 -3
|
@@ -6,8 +6,8 @@ var __export = (target, all) => {
|
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
// src/agent/local-agent.ts
|
|
9
|
-
import { existsSync as
|
|
10
|
-
import
|
|
9
|
+
import { existsSync as existsSync9, readFileSync as readFileSync12, watch } from "fs";
|
|
10
|
+
import path21 from "path";
|
|
11
11
|
import { fileURLToPath } from "url";
|
|
12
12
|
|
|
13
13
|
// ../agent-core/src/path-policy.ts
|
|
@@ -925,10 +925,10 @@ function mergeDefs(...defs) {
|
|
|
925
925
|
function cloneDef(schema) {
|
|
926
926
|
return mergeDefs(schema._zod.def);
|
|
927
927
|
}
|
|
928
|
-
function getElementAtPath(obj,
|
|
929
|
-
if (!
|
|
928
|
+
function getElementAtPath(obj, path22) {
|
|
929
|
+
if (!path22)
|
|
930
930
|
return obj;
|
|
931
|
-
return
|
|
931
|
+
return path22.reduce((acc, key) => acc?.[key], obj);
|
|
932
932
|
}
|
|
933
933
|
function promiseAllObject(promisesObj) {
|
|
934
934
|
const keys = Object.keys(promisesObj);
|
|
@@ -1268,11 +1268,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
1268
1268
|
}
|
|
1269
1269
|
return false;
|
|
1270
1270
|
}
|
|
1271
|
-
function prefixIssues(
|
|
1271
|
+
function prefixIssues(path22, issues) {
|
|
1272
1272
|
return issues.map((iss) => {
|
|
1273
1273
|
var _a3;
|
|
1274
1274
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
1275
|
-
iss.path.unshift(
|
|
1275
|
+
iss.path.unshift(path22);
|
|
1276
1276
|
return iss;
|
|
1277
1277
|
});
|
|
1278
1278
|
}
|
|
@@ -1734,16 +1734,16 @@ function flattenError(error62, mapper = (issue2) => issue2.message) {
|
|
|
1734
1734
|
}
|
|
1735
1735
|
function formatError(error62, mapper = (issue2) => issue2.message) {
|
|
1736
1736
|
const fieldErrors = { _errors: [] };
|
|
1737
|
-
const processError = (error63,
|
|
1737
|
+
const processError = (error63, path22 = []) => {
|
|
1738
1738
|
for (const issue2 of error63.issues) {
|
|
1739
1739
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
1740
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
1740
|
+
issue2.errors.map((issues) => processError({ issues }, [...path22, ...issue2.path]));
|
|
1741
1741
|
} else if (issue2.code === "invalid_key") {
|
|
1742
|
-
processError({ issues: issue2.issues }, [...
|
|
1742
|
+
processError({ issues: issue2.issues }, [...path22, ...issue2.path]);
|
|
1743
1743
|
} else if (issue2.code === "invalid_element") {
|
|
1744
|
-
processError({ issues: issue2.issues }, [...
|
|
1744
|
+
processError({ issues: issue2.issues }, [...path22, ...issue2.path]);
|
|
1745
1745
|
} else {
|
|
1746
|
-
const fullpath = [...
|
|
1746
|
+
const fullpath = [...path22, ...issue2.path];
|
|
1747
1747
|
if (fullpath.length === 0) {
|
|
1748
1748
|
fieldErrors._errors.push(mapper(issue2));
|
|
1749
1749
|
} else {
|
|
@@ -1782,17 +1782,17 @@ function formatError(error62, mapper = (issue2) => issue2.message) {
|
|
|
1782
1782
|
}
|
|
1783
1783
|
function treeifyError(error62, mapper = (issue2) => issue2.message) {
|
|
1784
1784
|
const result = { errors: [] };
|
|
1785
|
-
const processError = (error63,
|
|
1785
|
+
const processError = (error63, path22 = []) => {
|
|
1786
1786
|
var _a3;
|
|
1787
1787
|
for (const issue2 of error63.issues) {
|
|
1788
1788
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
1789
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
1789
|
+
issue2.errors.map((issues) => processError({ issues }, [...path22, ...issue2.path]));
|
|
1790
1790
|
} else if (issue2.code === "invalid_key") {
|
|
1791
|
-
processError({ issues: issue2.issues }, [...
|
|
1791
|
+
processError({ issues: issue2.issues }, [...path22, ...issue2.path]);
|
|
1792
1792
|
} else if (issue2.code === "invalid_element") {
|
|
1793
|
-
processError({ issues: issue2.issues }, [...
|
|
1793
|
+
processError({ issues: issue2.issues }, [...path22, ...issue2.path]);
|
|
1794
1794
|
} else {
|
|
1795
|
-
const fullpath = [...
|
|
1795
|
+
const fullpath = [...path22, ...issue2.path];
|
|
1796
1796
|
if (fullpath.length === 0) {
|
|
1797
1797
|
result.errors.push(mapper(issue2));
|
|
1798
1798
|
continue;
|
|
@@ -1831,8 +1831,8 @@ function treeifyError(error62, mapper = (issue2) => issue2.message) {
|
|
|
1831
1831
|
}
|
|
1832
1832
|
function toDotPath(_path) {
|
|
1833
1833
|
const segs = [];
|
|
1834
|
-
const
|
|
1835
|
-
for (const seg of
|
|
1834
|
+
const path22 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
1835
|
+
for (const seg of path22) {
|
|
1836
1836
|
if (typeof seg === "number")
|
|
1837
1837
|
segs.push(`[${seg}]`);
|
|
1838
1838
|
else if (typeof seg === "symbol")
|
|
@@ -18970,13 +18970,13 @@ function resolveRef(ref, ctx) {
|
|
|
18970
18970
|
if (!ref.startsWith("#")) {
|
|
18971
18971
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
18972
18972
|
}
|
|
18973
|
-
const
|
|
18974
|
-
if (
|
|
18973
|
+
const path22 = ref.slice(1).split("/").filter(Boolean);
|
|
18974
|
+
if (path22.length === 0) {
|
|
18975
18975
|
return ctx.rootSchema;
|
|
18976
18976
|
}
|
|
18977
18977
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
18978
|
-
if (
|
|
18979
|
-
const key =
|
|
18978
|
+
if (path22[0] === defsKey) {
|
|
18979
|
+
const key = path22[1] === void 0 ? void 0 : decodeJSONPointerSegment(path22[1]);
|
|
18980
18980
|
if (!key || !ctx.defs[key]) {
|
|
18981
18981
|
throw new Error(`Reference not found: ${ref}`);
|
|
18982
18982
|
}
|
|
@@ -20280,6 +20280,14 @@ var agentEventSchema = external_exports.object({
|
|
|
20280
20280
|
event: external_exports.string(),
|
|
20281
20281
|
data: external_exports.unknown().optional()
|
|
20282
20282
|
});
|
|
20283
|
+
var changedFileSchema = external_exports.object({
|
|
20284
|
+
path: external_exports.string(),
|
|
20285
|
+
additions: external_exports.number().int(),
|
|
20286
|
+
deletions: external_exports.number().int(),
|
|
20287
|
+
explanation: external_exports.string().optional(),
|
|
20288
|
+
/** Agent engine: whether the file was created, modified or deleted. */
|
|
20289
|
+
kind: external_exports.enum(["create", "modify", "delete"]).optional()
|
|
20290
|
+
});
|
|
20283
20291
|
var agentChangeEventSchema = external_exports.discriminatedUnion("type", [
|
|
20284
20292
|
external_exports.object({ type: external_exports.literal("planning"), changeId: external_exports.uuid() }),
|
|
20285
20293
|
external_exports.object({
|
|
@@ -20287,6 +20295,8 @@ var agentChangeEventSchema = external_exports.discriminatedUnion("type", [
|
|
|
20287
20295
|
changeId: external_exports.uuid(),
|
|
20288
20296
|
plan: changePlanSchema,
|
|
20289
20297
|
model: external_exports.string(),
|
|
20298
|
+
/** Models tried first that couldn't run (A7, A11): the plan says it ran on another. */
|
|
20299
|
+
fellBackFrom: external_exports.array(external_exports.string()).optional(),
|
|
20290
20300
|
/** Credits the change has cost so far (B1). */
|
|
20291
20301
|
cost: external_exports.number().nonnegative().optional()
|
|
20292
20302
|
}),
|
|
@@ -20306,22 +20316,23 @@ var agentChangeEventSchema = external_exports.discriminatedUnion("type", [
|
|
|
20306
20316
|
external_exports.object({
|
|
20307
20317
|
type: external_exports.literal("checking"),
|
|
20308
20318
|
changeId: external_exports.uuid(),
|
|
20309
|
-
checks: external_exports.array(
|
|
20319
|
+
checks: external_exports.array(
|
|
20320
|
+
external_exports.object({
|
|
20321
|
+
kind: checkKindSchema,
|
|
20322
|
+
status: checkStatusSchema,
|
|
20323
|
+
/** How long a finished check took (L16). */
|
|
20324
|
+
durationMs: external_exports.number().int().nonnegative().optional()
|
|
20325
|
+
})
|
|
20326
|
+
),
|
|
20327
|
+
/** The change as written, shown while its checks run (L17, ADR 0042). */
|
|
20328
|
+
files: external_exports.array(changedFileSchema).optional(),
|
|
20329
|
+
diff: external_exports.string().optional()
|
|
20310
20330
|
}),
|
|
20311
20331
|
external_exports.object({
|
|
20312
20332
|
type: external_exports.literal("result"),
|
|
20313
20333
|
changeId: external_exports.uuid(),
|
|
20314
20334
|
state: changeStateSchema,
|
|
20315
|
-
files: external_exports.array(
|
|
20316
|
-
external_exports.object({
|
|
20317
|
-
path: external_exports.string(),
|
|
20318
|
-
additions: external_exports.number().int(),
|
|
20319
|
-
deletions: external_exports.number().int(),
|
|
20320
|
-
explanation: external_exports.string().optional(),
|
|
20321
|
-
/** Agent engine: whether the file was created, modified or deleted. */
|
|
20322
|
-
kind: external_exports.enum(["create", "modify", "delete"]).optional()
|
|
20323
|
-
})
|
|
20324
|
-
),
|
|
20335
|
+
files: external_exports.array(changedFileSchema),
|
|
20325
20336
|
diff: external_exports.string(),
|
|
20326
20337
|
checks: external_exports.array(checkResultSchema),
|
|
20327
20338
|
/** Credits the change has cost so far (B1). */
|
|
@@ -21778,6 +21789,71 @@ ${summary}` : summary
|
|
|
21778
21789
|
// ../agent-core/src/checks/browser.ts
|
|
21779
21790
|
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
21780
21791
|
import path11 from "path";
|
|
21792
|
+
var LOC_ATTRIBUTE = "data-revyse-loc";
|
|
21793
|
+
var FRAME_PADDING = 24;
|
|
21794
|
+
var FRAME_MIN_WIDTH = 480;
|
|
21795
|
+
var FRAME_MAX_RATIO = 2;
|
|
21796
|
+
function frameAround(box, viewport) {
|
|
21797
|
+
const width = Math.min(Math.max(box.width + 2 * FRAME_PADDING, FRAME_MIN_WIDTH), viewport.width);
|
|
21798
|
+
const height = Math.min(
|
|
21799
|
+
Math.max(box.height + 2 * FRAME_PADDING, width / FRAME_MAX_RATIO),
|
|
21800
|
+
viewport.height
|
|
21801
|
+
);
|
|
21802
|
+
const place = (start, size, frame, limit) => Math.round(Math.min(Math.max(start + size / 2 - frame / 2, 0), limit - frame));
|
|
21803
|
+
return {
|
|
21804
|
+
x: place(box.x, box.width, width, viewport.width),
|
|
21805
|
+
y: place(box.y, box.height, height, viewport.height),
|
|
21806
|
+
width: Math.round(width),
|
|
21807
|
+
height: Math.round(height)
|
|
21808
|
+
};
|
|
21809
|
+
}
|
|
21810
|
+
function nearestLoc(locs, source) {
|
|
21811
|
+
const distance = (loc) => {
|
|
21812
|
+
const [line, column] = loc.split(":").slice(-2).map(Number);
|
|
21813
|
+
if (!Number.isFinite(line) || source.line === void 0) return Infinity;
|
|
21814
|
+
return Math.abs(line - source.line) * 1e4 + Math.abs((column ?? 0) - (source.column ?? 0));
|
|
21815
|
+
};
|
|
21816
|
+
let best = 0;
|
|
21817
|
+
for (let i = 1; i < locs.length; i++) if (distance(locs[i]) < distance(locs[best])) best = i;
|
|
21818
|
+
return best;
|
|
21819
|
+
}
|
|
21820
|
+
async function findSelected(page, selector, source) {
|
|
21821
|
+
const bySelector = page.locator(selector).first();
|
|
21822
|
+
const selectorFound = await page.locator(selector).count().catch(() => 0) > 0;
|
|
21823
|
+
if (source?.line) {
|
|
21824
|
+
const exact = source.column === void 0 ? `${source.file}:${source.line}:` : `${source.file}:${source.line}:${source.column}`;
|
|
21825
|
+
const isExact = (loc) => source.column === void 0 ? !!loc?.startsWith(exact) : loc === exact;
|
|
21826
|
+
if (selectorFound) {
|
|
21827
|
+
const loc = await bySelector.evaluate((el, attr) => el.closest(`[${attr}]`)?.getAttribute(attr) ?? null, LOC_ATTRIBUTE).catch(() => null);
|
|
21828
|
+
if (isExact(loc)) return bySelector;
|
|
21829
|
+
}
|
|
21830
|
+
const op = source.column === void 0 ? "^=" : "=";
|
|
21831
|
+
const byLoc = page.locator(`[${LOC_ATTRIBUTE}${op}${JSON.stringify(exact)}]`);
|
|
21832
|
+
if (await byLoc.count().catch(() => 0) > 0) return byLoc.first();
|
|
21833
|
+
}
|
|
21834
|
+
if (selectorFound) return bySelector;
|
|
21835
|
+
if (!source) return null;
|
|
21836
|
+
const fromFile = page.locator(`[${LOC_ATTRIBUTE}^=${JSON.stringify(`${source.file}:`)}]`);
|
|
21837
|
+
const locs = await fromFile.evaluateAll((els, attr) => els.map((el) => el.getAttribute(attr) ?? ""), LOC_ATTRIBUTE).catch(() => []);
|
|
21838
|
+
return locs.length > 0 ? fromFile.nth(nearestLoc(locs, source)) : null;
|
|
21839
|
+
}
|
|
21840
|
+
async function frameSelected(page, target) {
|
|
21841
|
+
await target.scrollIntoViewIfNeeded({ timeout: 2e3 }).catch(() => {
|
|
21842
|
+
});
|
|
21843
|
+
await page.evaluate(
|
|
21844
|
+
() => Promise.race([
|
|
21845
|
+
Promise.all(
|
|
21846
|
+
Array.from(document.images).filter((img) => !img.complete).map((img) => new Promise((done) => img.addEventListener("loadend", done)))
|
|
21847
|
+
),
|
|
21848
|
+
new Promise((done) => setTimeout(done, 2e3))
|
|
21849
|
+
])
|
|
21850
|
+
).catch(() => {
|
|
21851
|
+
});
|
|
21852
|
+
const box = await target.boundingBox().catch(() => null);
|
|
21853
|
+
const viewport = page.viewportSize();
|
|
21854
|
+
if (!box || !viewport || box.width <= 0 || box.height <= 0) return null;
|
|
21855
|
+
return frameAround(box, viewport);
|
|
21856
|
+
}
|
|
21781
21857
|
var BrowserUnavailableError = class extends Error {
|
|
21782
21858
|
};
|
|
21783
21859
|
var MAX_ERRORS = 10;
|
|
@@ -21805,7 +21881,7 @@ async function createPlaywrightDriver(env = process.env) {
|
|
|
21805
21881
|
if (!browser) throw new BrowserUnavailableError(`No Chromium could be started. ${NOT_RUN_HINT}`);
|
|
21806
21882
|
const b = browser;
|
|
21807
21883
|
return {
|
|
21808
|
-
async capture({ url: url2, selector,
|
|
21884
|
+
async capture({ url: url2, selector, source, timeoutMs }) {
|
|
21809
21885
|
const context = await b.newContext({ viewport: { width: 1280, height: 800 } });
|
|
21810
21886
|
const page = await context.newPage();
|
|
21811
21887
|
const errors = [];
|
|
@@ -21825,9 +21901,15 @@ async function createPlaywrightDriver(env = process.env) {
|
|
|
21825
21901
|
let found = null;
|
|
21826
21902
|
if (await page.locator(selector).count().catch(() => 0) > 0)
|
|
21827
21903
|
found = "selector";
|
|
21828
|
-
else if (
|
|
21904
|
+
else if (source && await page.locator(`[${LOC_ATTRIBUTE}^=${JSON.stringify(`${source.file}:`)}]`).count() > 0)
|
|
21829
21905
|
found = "source";
|
|
21830
|
-
const
|
|
21906
|
+
const target = await findSelected(page, selector, source);
|
|
21907
|
+
const clip = target ? await frameSelected(page, target) : null;
|
|
21908
|
+
const screenshot = await page.screenshot({
|
|
21909
|
+
type: "png",
|
|
21910
|
+
animations: "disabled",
|
|
21911
|
+
...clip ? { clip } : {}
|
|
21912
|
+
});
|
|
21831
21913
|
return { status: res?.status() ?? null, found, screenshot, errors };
|
|
21832
21914
|
} finally {
|
|
21833
21915
|
await context.close();
|
|
@@ -21942,7 +22024,7 @@ function createAgentApi(opts) {
|
|
|
21942
22024
|
const f = opts.fetch ?? fetch;
|
|
21943
22025
|
const base = opts.apiUrl.replace(/\/+$/, "");
|
|
21944
22026
|
const auth = { authorization: `Bearer ${opts.token}` };
|
|
21945
|
-
const call = (
|
|
22027
|
+
const call = (path22, body, signal) => f(`${base}${path22}`, {
|
|
21946
22028
|
method: "POST",
|
|
21947
22029
|
headers: { "content-type": "application/json", ...auth },
|
|
21948
22030
|
body: JSON.stringify(body),
|
|
@@ -21958,18 +22040,18 @@ function createAgentApi(opts) {
|
|
|
21958
22040
|
}
|
|
21959
22041
|
return {
|
|
21960
22042
|
baseUrl: base,
|
|
21961
|
-
async get(
|
|
21962
|
-
const res = await f(`${base}${
|
|
22043
|
+
async get(path22) {
|
|
22044
|
+
const res = await f(`${base}${path22}`, { headers: auth });
|
|
21963
22045
|
if (!res.ok) throw await failure2(res);
|
|
21964
22046
|
return await res.json();
|
|
21965
22047
|
},
|
|
21966
|
-
async post(
|
|
21967
|
-
const res = await call(
|
|
22048
|
+
async post(path22, body) {
|
|
22049
|
+
const res = await call(path22, body);
|
|
21968
22050
|
if (!res.ok) throw await failure2(res);
|
|
21969
22051
|
return await res.json();
|
|
21970
22052
|
},
|
|
21971
|
-
async *stream(
|
|
21972
|
-
const res = await call(
|
|
22053
|
+
async *stream(path22, body, signal) {
|
|
22054
|
+
const res = await call(path22, body, signal);
|
|
21973
22055
|
if (!res.ok) throw await failure2(res);
|
|
21974
22056
|
if (!res.body) return;
|
|
21975
22057
|
const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
|
|
@@ -22002,11 +22084,51 @@ function parseBlock(block) {
|
|
|
22002
22084
|
|
|
22003
22085
|
// ../agent-core/src/change/runner.ts
|
|
22004
22086
|
import { readFileSync as readFileSync6, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
22005
|
-
import
|
|
22087
|
+
import path15 from "path";
|
|
22088
|
+
|
|
22089
|
+
// ../agent-core/src/patch/originals.ts
|
|
22090
|
+
import { existsSync as existsSync4 } from "fs";
|
|
22091
|
+
import path12 from "path";
|
|
22092
|
+
async function withOriginals(opts) {
|
|
22093
|
+
const io = opts.io ?? nodeIO;
|
|
22094
|
+
const dir = changeDir(opts.projectRoot, opts.changeId);
|
|
22095
|
+
const manifest = JSON.parse(io.readFile(path12.join(dir, "manifest.json")));
|
|
22096
|
+
const files = manifest.files.map((f) => ({
|
|
22097
|
+
...f,
|
|
22098
|
+
real: resolveProjectPath(opts.projectRoot, f.path),
|
|
22099
|
+
pre: f.preHash === null ? null : io.readFile(path12.join(dir, "pre", f.path)),
|
|
22100
|
+
post: f.postHash === null ? null : io.readFile(path12.join(dir, "post", f.path))
|
|
22101
|
+
}));
|
|
22102
|
+
const hashOf = (real) => existsSync4(real) ? hashContent(io.readFile(real)) : null;
|
|
22103
|
+
const put = (real, content) => {
|
|
22104
|
+
if (content === null) io.unlink(real);
|
|
22105
|
+
else {
|
|
22106
|
+
io.mkdir(path12.dirname(real));
|
|
22107
|
+
io.writeFile(real, content);
|
|
22108
|
+
}
|
|
22109
|
+
};
|
|
22110
|
+
const changed = files.filter((f) => hashOf(f.real) !== f.postHash).map((f) => f.path);
|
|
22111
|
+
if (changed.length > 0) return { status: "changed", paths: changed };
|
|
22112
|
+
for (const f of files) put(f.real, f.pre);
|
|
22113
|
+
const putBack = () => {
|
|
22114
|
+
const kept2 = files.filter((f) => hashOf(f.real) !== f.preHash).map((f) => f.path);
|
|
22115
|
+
for (const f of files) if (!kept2.includes(f.path)) put(f.real, f.post);
|
|
22116
|
+
return kept2;
|
|
22117
|
+
};
|
|
22118
|
+
let value;
|
|
22119
|
+
try {
|
|
22120
|
+
value = await opts.work();
|
|
22121
|
+
} catch (err) {
|
|
22122
|
+
putBack();
|
|
22123
|
+
throw err;
|
|
22124
|
+
}
|
|
22125
|
+
const kept = putBack();
|
|
22126
|
+
return kept.length > 0 ? { status: "kept-edit", value, paths: kept } : { status: "done", value };
|
|
22127
|
+
}
|
|
22006
22128
|
|
|
22007
22129
|
// ../agent-core/src/tools/file-host.ts
|
|
22008
22130
|
import { readdirSync as readdirSync3, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
|
|
22009
|
-
import
|
|
22131
|
+
import path13 from "path";
|
|
22010
22132
|
var SKIPPED_DIRS = /* @__PURE__ */ new Set([
|
|
22011
22133
|
"node_modules",
|
|
22012
22134
|
".git",
|
|
@@ -22022,7 +22144,7 @@ var SKIPPED_DIRS = /* @__PURE__ */ new Set([
|
|
|
22022
22144
|
]);
|
|
22023
22145
|
var MAX_HOST_FILE_BYTES = 1e6;
|
|
22024
22146
|
var MAX_LISTED = 2e4;
|
|
22025
|
-
var toPosix2 = (p) => p.split(
|
|
22147
|
+
var toPosix2 = (p) => p.split(path13.sep).join("/");
|
|
22026
22148
|
function localFileHost(projectRoot) {
|
|
22027
22149
|
return {
|
|
22028
22150
|
async read(relativePath) {
|
|
@@ -22050,12 +22172,12 @@ function localFileHost(projectRoot) {
|
|
|
22050
22172
|
if (out.length >= MAX_LISTED) return;
|
|
22051
22173
|
let entries;
|
|
22052
22174
|
try {
|
|
22053
|
-
entries = readdirSync3(
|
|
22175
|
+
entries = readdirSync3(path13.join(projectRoot, dir), { withFileTypes: true });
|
|
22054
22176
|
} catch {
|
|
22055
22177
|
return;
|
|
22056
22178
|
}
|
|
22057
22179
|
for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
22058
|
-
const rel = toPosix2(
|
|
22180
|
+
const rel = toPosix2(path13.join(dir, e.name));
|
|
22059
22181
|
if (e.isDirectory()) {
|
|
22060
22182
|
if (!SKIPPED_DIRS.has(e.name) && !isSensitivePath(rel)) walk(rel);
|
|
22061
22183
|
} else if (e.isFile() && !isSensitivePath(rel)) {
|
|
@@ -22071,7 +22193,7 @@ function localFileHost(projectRoot) {
|
|
|
22071
22193
|
}
|
|
22072
22194
|
|
|
22073
22195
|
// ../agent-core/src/tools/integrations.ts
|
|
22074
|
-
import
|
|
22196
|
+
import path14 from "path";
|
|
22075
22197
|
var CODE = /\.(tsx?|jsx?|mjs|cjs)$/;
|
|
22076
22198
|
var ENV_TEMPLATE = /^\.env\.(example|sample|template)$/;
|
|
22077
22199
|
var ENV_IN_CODE = /process\.env\.([A-Z][A-Z0-9_]*)|process\.env\[\s*["']([A-Z][A-Z0-9_]*)["']\s*\]|import\.meta\.env\.([A-Z][A-Z0-9_]*)/g;
|
|
@@ -22094,7 +22216,7 @@ async function projectSignals(read, paths) {
|
|
|
22094
22216
|
for (const m of text.matchAll(ENV_IN_CODE)) env.add(m[1] ?? m[2] ?? m[3]);
|
|
22095
22217
|
for (const m of text.matchAll(IMPORT)) imports.add(packageOf(m[1] ?? m[2]));
|
|
22096
22218
|
}
|
|
22097
|
-
for (const p of paths.filter((p2) => ENV_TEMPLATE.test(
|
|
22219
|
+
for (const p of paths.filter((p2) => ENV_TEMPLATE.test(path14.posix.basename(p2)))) {
|
|
22098
22220
|
for (const line of (await read(p) ?? "").split("\n")) {
|
|
22099
22221
|
const name = line.match(/^\s*(?:export\s+)?([A-Z][A-Z0-9_]*)\s*=/)?.[1];
|
|
22100
22222
|
if (name) env.add(name);
|
|
@@ -22154,27 +22276,27 @@ var StagedFiles = class {
|
|
|
22154
22276
|
/** Paths written by the package manager (I7), not by the model: the write rules don't apply. */
|
|
22155
22277
|
fromPackageManager = /* @__PURE__ */ new Set();
|
|
22156
22278
|
/** Current content as the agent sees it: staged if written, else the project's. */
|
|
22157
|
-
async read(
|
|
22158
|
-
const s = this.staged.get(
|
|
22279
|
+
async read(path22) {
|
|
22280
|
+
const s = this.staged.get(path22);
|
|
22159
22281
|
if (s) return s.content;
|
|
22160
|
-
const content = await this.host.read(
|
|
22161
|
-
if (!this.seen.has(
|
|
22282
|
+
const content = await this.host.read(path22);
|
|
22283
|
+
if (!this.seen.has(path22)) this.seen.set(path22, content === null ? null : hashContent(content));
|
|
22162
22284
|
return content;
|
|
22163
22285
|
}
|
|
22164
22286
|
/** Like `read`, without recording a snapshot: for searching, which only looks. */
|
|
22165
|
-
async peek(
|
|
22166
|
-
const s = this.staged.get(
|
|
22167
|
-
return s ? s.content : this.host.read(
|
|
22287
|
+
async peek(path22) {
|
|
22288
|
+
const s = this.staged.get(path22);
|
|
22289
|
+
return s ? s.content : this.host.read(path22);
|
|
22168
22290
|
}
|
|
22169
22291
|
/** Writes `content` (or deletes, with null) in the staging layer. */
|
|
22170
|
-
async write(
|
|
22171
|
-
const existing = this.staged.get(
|
|
22292
|
+
async write(path22, content) {
|
|
22293
|
+
const existing = this.staged.get(path22);
|
|
22172
22294
|
if (existing) {
|
|
22173
22295
|
existing.content = content;
|
|
22174
22296
|
return;
|
|
22175
22297
|
}
|
|
22176
|
-
const before = await this.read(
|
|
22177
|
-
this.staged.set(
|
|
22298
|
+
const before = await this.read(path22);
|
|
22299
|
+
this.staged.set(path22, {
|
|
22178
22300
|
content,
|
|
22179
22301
|
existed: before !== null,
|
|
22180
22302
|
baseHash: before === null ? null : hashContent(before)
|
|
@@ -22185,9 +22307,9 @@ var StagedFiles = class {
|
|
|
22185
22307
|
* a supported SDK). The patch engine applies it with the rest, so it lands, is checked and is
|
|
22186
22308
|
* reverted together; the write rules that keep the model out of these files don't apply.
|
|
22187
22309
|
*/
|
|
22188
|
-
async writeFromPackageManager(
|
|
22189
|
-
await this.write(
|
|
22190
|
-
this.fromPackageManager.add(
|
|
22310
|
+
async writeFromPackageManager(path22, content) {
|
|
22311
|
+
await this.write(path22, content);
|
|
22312
|
+
this.fromPackageManager.add(path22);
|
|
22191
22313
|
}
|
|
22192
22314
|
/** The paths the package manager wrote. */
|
|
22193
22315
|
packageManagerPaths() {
|
|
@@ -22205,18 +22327,18 @@ var StagedFiles = class {
|
|
|
22205
22327
|
/** What the apply phase changed, net: a file created then deleted is no change at all. */
|
|
22206
22328
|
changes() {
|
|
22207
22329
|
const out = [];
|
|
22208
|
-
for (const [
|
|
22330
|
+
for (const [path22, s] of [...this.staged].sort(([a], [b]) => a.localeCompare(b))) {
|
|
22209
22331
|
if (!s.existed && s.content === null) continue;
|
|
22210
|
-
if (!s.existed) out.push({ path:
|
|
22211
|
-
else if (s.content === null) out.push({ path:
|
|
22332
|
+
if (!s.existed) out.push({ path: path22, kind: "create", content: s.content });
|
|
22333
|
+
else if (s.content === null) out.push({ path: path22, kind: "delete", baseHash: s.baseHash });
|
|
22212
22334
|
else if (hashContent(s.content) !== s.baseHash)
|
|
22213
|
-
out.push({ path:
|
|
22335
|
+
out.push({ path: path22, kind: "modify", content: s.content, baseHash: s.baseHash });
|
|
22214
22336
|
}
|
|
22215
22337
|
return out;
|
|
22216
22338
|
}
|
|
22217
22339
|
/** Every file the agent read or wrote, with its hash when first seen (null: didn't exist). */
|
|
22218
22340
|
snapshots() {
|
|
22219
|
-
return [...this.seen].map(([
|
|
22341
|
+
return [...this.seen].map(([path22, hash2]) => ({ path: path22, hash: hash2 })).sort((a, b) => a.path.localeCompare(b.path));
|
|
22220
22342
|
}
|
|
22221
22343
|
};
|
|
22222
22344
|
|
|
@@ -22253,7 +22375,7 @@ var ok = (callId, output2) => ({
|
|
|
22253
22375
|
output: output2,
|
|
22254
22376
|
isError: false
|
|
22255
22377
|
});
|
|
22256
|
-
var secretFile = (
|
|
22378
|
+
var secretFile = (path22) => `Can't change ${path22}: it looks like it contains a secret. Leave it, and name it in notDone so the developer moves the secret to an environment variable.`;
|
|
22257
22379
|
var fail = (callId, output2) => ({
|
|
22258
22380
|
callId,
|
|
22259
22381
|
output: output2,
|
|
@@ -22296,12 +22418,12 @@ function createToolRuntime(opts) {
|
|
|
22296
22418
|
const found = [];
|
|
22297
22419
|
let withheld = 0;
|
|
22298
22420
|
let stopped = false;
|
|
22299
|
-
for (const
|
|
22421
|
+
for (const path22 of await visible(input2.path, input2.glob)) {
|
|
22300
22422
|
if (found.length >= max || Date.now() - started > budget) {
|
|
22301
22423
|
stopped = true;
|
|
22302
22424
|
break;
|
|
22303
22425
|
}
|
|
22304
|
-
const content = await staging.peek(
|
|
22426
|
+
const content = await staging.peek(path22);
|
|
22305
22427
|
if (content === null) continue;
|
|
22306
22428
|
if (containsSecret(content)) {
|
|
22307
22429
|
withheld++;
|
|
@@ -22313,7 +22435,7 @@ function createToolRuntime(opts) {
|
|
|
22313
22435
|
if (line.length > MAX_SEARCH_LINE || !matches(line)) continue;
|
|
22314
22436
|
const text = line.trim();
|
|
22315
22437
|
found.push(
|
|
22316
|
-
`${
|
|
22438
|
+
`${path22}:${i + 1}: ${text.length > MAX_LINE ? `${text.slice(0, MAX_LINE)}\u2026` : text}`
|
|
22317
22439
|
);
|
|
22318
22440
|
}
|
|
22319
22441
|
}
|
|
@@ -22521,11 +22643,13 @@ function createChangeRunner(opts) {
|
|
|
22521
22643
|
function run(session, work) {
|
|
22522
22644
|
if (session.busy) throw new Busy("Revyse is still working on this change");
|
|
22523
22645
|
session.busy = true;
|
|
22524
|
-
|
|
22525
|
-
|
|
22646
|
+
const controller = new AbortController();
|
|
22647
|
+
session.abort = controller;
|
|
22648
|
+
const signal = controller.signal;
|
|
22526
22649
|
void work(signal).catch((err) => {
|
|
22527
22650
|
if (!signal.aborted) report(session.id, err);
|
|
22528
22651
|
}).finally(() => {
|
|
22652
|
+
if (session.abort !== controller) return;
|
|
22529
22653
|
session.busy = false;
|
|
22530
22654
|
session.abort = void 0;
|
|
22531
22655
|
});
|
|
@@ -22553,9 +22677,9 @@ function createChangeRunner(opts) {
|
|
|
22553
22677
|
const stored = [];
|
|
22554
22678
|
for (const key of localKeys) {
|
|
22555
22679
|
try {
|
|
22556
|
-
const bytes2 = readFileSync6(
|
|
22680
|
+
const bytes2 = readFileSync6(path15.join(opts.projectRoot, key));
|
|
22557
22681
|
const { artifact, upload } = await opts.api.post(`/api/changes/${changeId}/artifacts`, {
|
|
22558
|
-
name:
|
|
22682
|
+
name: path15.basename(key),
|
|
22559
22683
|
contentType: "image/png",
|
|
22560
22684
|
size: bytes2.length
|
|
22561
22685
|
});
|
|
@@ -22600,7 +22724,7 @@ function createChangeRunner(opts) {
|
|
|
22600
22724
|
const before = await driver.capture({
|
|
22601
22725
|
url: url2,
|
|
22602
22726
|
selector: session.element.selector,
|
|
22603
|
-
|
|
22727
|
+
source: session.element.source,
|
|
22604
22728
|
timeoutMs: 3e4
|
|
22605
22729
|
}).catch((err) => {
|
|
22606
22730
|
log(`browser check: before-capture failed: ${err.message}`);
|
|
@@ -22633,7 +22757,7 @@ function createChangeRunner(opts) {
|
|
|
22633
22757
|
`/api/changes/${session.id}/plan`,
|
|
22634
22758
|
{
|
|
22635
22759
|
element: session.element,
|
|
22636
|
-
files: gathered.files.map(({ path:
|
|
22760
|
+
files: gathered.files.map(({ path: path22, content, hash: hash2 }) => ({ path: path22, content, hash: hash2 })),
|
|
22637
22761
|
modelTier: session.tier,
|
|
22638
22762
|
model: session.model,
|
|
22639
22763
|
...extra
|
|
@@ -22645,16 +22769,24 @@ function createChangeRunner(opts) {
|
|
|
22645
22769
|
const {
|
|
22646
22770
|
plan: result,
|
|
22647
22771
|
model,
|
|
22648
|
-
cost
|
|
22772
|
+
cost,
|
|
22773
|
+
fellBackFrom
|
|
22649
22774
|
} = ev.data;
|
|
22650
|
-
onPlan(session, result, model, cost);
|
|
22775
|
+
onPlan(session, result, model, cost, void 0, fellBackFrom);
|
|
22651
22776
|
}
|
|
22652
22777
|
}
|
|
22653
|
-
function onPlan(session, result, model, cost, connectPath) {
|
|
22778
|
+
function onPlan(session, result, model, cost, connectPath, fellBackFrom) {
|
|
22654
22779
|
session.plan = result;
|
|
22655
22780
|
if (cost !== void 0) session.cost = cost;
|
|
22656
22781
|
if (result.decision === "proceed")
|
|
22657
|
-
opts.emit({
|
|
22782
|
+
opts.emit({
|
|
22783
|
+
type: "plan",
|
|
22784
|
+
changeId: session.id,
|
|
22785
|
+
plan: result,
|
|
22786
|
+
model,
|
|
22787
|
+
...fellBackFrom?.length ? { fellBackFrom } : {},
|
|
22788
|
+
cost: session.cost
|
|
22789
|
+
});
|
|
22658
22790
|
else if (result.decision === "clarify")
|
|
22659
22791
|
opts.emit({
|
|
22660
22792
|
type: "clarification",
|
|
@@ -22684,19 +22816,23 @@ function createChangeRunner(opts) {
|
|
|
22684
22816
|
}
|
|
22685
22817
|
async function apply(session, signal) {
|
|
22686
22818
|
const browser = prepareBrowser(session);
|
|
22687
|
-
const baseline = hasLocalChecks() ? runChecks(session.contextPaths).catch((err) => {
|
|
22688
|
-
log(`baseline checks failed to run: ${err.message}`);
|
|
22689
|
-
return [];
|
|
22690
|
-
}) : Promise.resolve([]);
|
|
22691
22819
|
try {
|
|
22692
|
-
await applyWithBrowser(session, signal, browser
|
|
22820
|
+
await applyWithBrowser(session, signal, browser);
|
|
22693
22821
|
} finally {
|
|
22694
22822
|
void browser.then((b) => b.driver?.close()).catch(() => {
|
|
22695
22823
|
});
|
|
22696
22824
|
}
|
|
22697
22825
|
}
|
|
22698
|
-
|
|
22699
|
-
|
|
22826
|
+
async function beforePage(prep) {
|
|
22827
|
+
const late = /* @__PURE__ */ Symbol("late");
|
|
22828
|
+
const ready = await Promise.race([
|
|
22829
|
+
prep,
|
|
22830
|
+
new Promise((r) => setTimeout(() => r(late), opts.beforeWaitMs ?? 3e3))
|
|
22831
|
+
]);
|
|
22832
|
+
if (ready !== late) return ready;
|
|
22833
|
+
return { before: null, reason: "The page took too long to load before the change." };
|
|
22834
|
+
}
|
|
22835
|
+
async function applyWithBrowser(session, signal, browserPrep) {
|
|
22700
22836
|
const { files, missing } = readFiles(session.contextPaths);
|
|
22701
22837
|
const stale = [
|
|
22702
22838
|
...missing,
|
|
@@ -22713,10 +22849,16 @@ function createChangeRunner(opts) {
|
|
|
22713
22849
|
return conflict(session.id, "changed_since_plan", stale);
|
|
22714
22850
|
}
|
|
22715
22851
|
session.stage = "patching";
|
|
22852
|
+
const planned = new Set(session.plan?.steps.flatMap((s) => s.files) ?? []);
|
|
22853
|
+
const toPatch = planned.size > 0 ? files.filter((f) => planned.has(f.path)) : files;
|
|
22716
22854
|
let patch;
|
|
22717
|
-
for await (const ev of opts.api.stream(
|
|
22855
|
+
for await (const ev of opts.api.stream(
|
|
22856
|
+
`/api/changes/${session.id}/patch`,
|
|
22857
|
+
{ files: toPatch },
|
|
22858
|
+
signal
|
|
22859
|
+
)) {
|
|
22718
22860
|
if (ev.event === "progress" && ev.data.stage === "patching")
|
|
22719
|
-
opts.emit({ type: "applying", changeId: session.id, files:
|
|
22861
|
+
opts.emit({ type: "applying", changeId: session.id, files: toPatch.length });
|
|
22720
22862
|
if (ev.event === "error") return emitStreamError(session, ev.data);
|
|
22721
22863
|
if (ev.event === "result") {
|
|
22722
22864
|
const data = ev.data;
|
|
@@ -22725,13 +22867,13 @@ function createChangeRunner(opts) {
|
|
|
22725
22867
|
}
|
|
22726
22868
|
}
|
|
22727
22869
|
if (!patch) return;
|
|
22728
|
-
const
|
|
22870
|
+
const browser = await beforePage(browserPrep);
|
|
22729
22871
|
session.stage = "applying";
|
|
22730
|
-
const snapshots = files.map(({ path:
|
|
22872
|
+
const snapshots = files.map(({ path: path22, hash: hash2 }) => ({ path: path22, hash: hash2 }));
|
|
22731
22873
|
const applied = applyPatch({
|
|
22732
22874
|
projectRoot: opts.projectRoot,
|
|
22733
22875
|
changeId: session.id,
|
|
22734
|
-
patch: { files: patch.files.map(({ path:
|
|
22876
|
+
patch: { files: patch.files.map(({ path: path22, edits }) => ({ path: path22, edits })) },
|
|
22735
22877
|
snapshots
|
|
22736
22878
|
});
|
|
22737
22879
|
if (applied.status === "conflict") {
|
|
@@ -22754,23 +22896,56 @@ function createChangeRunner(opts) {
|
|
|
22754
22896
|
}
|
|
22755
22897
|
session.applied = true;
|
|
22756
22898
|
const explanations = new Map(patch.files.map((f) => [f.path, f.explanation]));
|
|
22757
|
-
await checkAndReport(session, applied, browser,
|
|
22899
|
+
await checkAndReport(session, applied, browser, signal, { explanations });
|
|
22758
22900
|
}
|
|
22759
|
-
async function checkAndReport(session, applied, browser,
|
|
22901
|
+
async function checkAndReport(session, applied, browser, signal, extras = {}) {
|
|
22760
22902
|
session.stage = "checking";
|
|
22761
|
-
const
|
|
22762
|
-
|
|
22763
|
-
|
|
22764
|
-
|
|
22765
|
-
|
|
22766
|
-
|
|
22767
|
-
|
|
22768
|
-
|
|
22769
|
-
});
|
|
22903
|
+
const ALL = ["typecheck", "lint", "test", "build"];
|
|
22904
|
+
const kinds = ALL.filter((k) => opts.config.scripts[k]);
|
|
22905
|
+
const files = applied.files.map((f) => ({
|
|
22906
|
+
path: f.path,
|
|
22907
|
+
additions: f.additions,
|
|
22908
|
+
deletions: f.deletions,
|
|
22909
|
+
explanation: extras.explanations?.get(f.path),
|
|
22910
|
+
...f.kind ? { kind: f.kind } : {}
|
|
22911
|
+
}));
|
|
22912
|
+
const progress = new Map([
|
|
22913
|
+
...kinds.map((k) => [k, { status: "running" }]),
|
|
22914
|
+
...browser.driver ? [["browser", { status: "running" }]] : []
|
|
22915
|
+
]);
|
|
22916
|
+
const report2 = () => {
|
|
22917
|
+
if (signal.aborted) return;
|
|
22918
|
+
opts.emit({
|
|
22919
|
+
type: "checking",
|
|
22920
|
+
changeId: session.id,
|
|
22921
|
+
checks: [...progress].map(([kind, p]) => ({
|
|
22922
|
+
kind,
|
|
22923
|
+
status: p.status,
|
|
22924
|
+
...p.ms !== void 0 ? { durationMs: p.ms } : {}
|
|
22925
|
+
})),
|
|
22926
|
+
files,
|
|
22927
|
+
diff: applied.diff
|
|
22928
|
+
});
|
|
22929
|
+
};
|
|
22930
|
+
const finished = (c) => {
|
|
22931
|
+
progress.set(c.kind, { status: c.status, ms: c.durationMs });
|
|
22932
|
+
report2();
|
|
22933
|
+
return c;
|
|
22934
|
+
};
|
|
22935
|
+
report2();
|
|
22770
22936
|
const changed = applied.files.filter((f) => f.kind !== "delete").map((f) => f.path);
|
|
22771
22937
|
const startedBrowser = performance.now();
|
|
22772
22938
|
const [local, browserRaw] = await Promise.all([
|
|
22773
|
-
|
|
22939
|
+
Promise.all(
|
|
22940
|
+
ALL.map(async (kind) => {
|
|
22941
|
+
const [r] = await runChecks(changed, [kind]);
|
|
22942
|
+
if (!r) {
|
|
22943
|
+
if (progress.delete(kind)) report2();
|
|
22944
|
+
return null;
|
|
22945
|
+
}
|
|
22946
|
+
return progress.has(kind) ? finished(r) : r;
|
|
22947
|
+
})
|
|
22948
|
+
).then((results) => results.filter((r) => r !== null)),
|
|
22774
22949
|
browser.driver && browser.url ? runBrowserCheck({
|
|
22775
22950
|
projectRoot: opts.projectRoot,
|
|
22776
22951
|
changeId: session.id,
|
|
@@ -22782,26 +22957,45 @@ function createChangeRunner(opts) {
|
|
|
22782
22957
|
return browser.driver.capture({
|
|
22783
22958
|
url: browser.url,
|
|
22784
22959
|
selector: session.element.selector,
|
|
22785
|
-
|
|
22960
|
+
source: session.element.source,
|
|
22786
22961
|
timeoutMs: 6e4
|
|
22787
22962
|
});
|
|
22788
22963
|
},
|
|
22789
22964
|
durationMs: () => performance.now() - startedBrowser
|
|
22790
|
-
}) : Promise.resolve(browserNotRun(browser.reason ?? "The browser check didn't run."))
|
|
22965
|
+
}).then((r) => finished(redactCheck(r))) : Promise.resolve(browserNotRun(browser.reason ?? "The browser check didn't run."))
|
|
22791
22966
|
]);
|
|
22792
|
-
|
|
22967
|
+
if (signal.aborted) return;
|
|
22968
|
+
const browserResult = browserRaw;
|
|
22793
22969
|
const localKeys = browserResult.evidence?.artifactKeys ?? [];
|
|
22794
22970
|
if (localKeys.length > 0) {
|
|
22795
22971
|
const stored = await uploadArtifacts(session.id, localKeys);
|
|
22796
22972
|
browserResult.evidence.artifactKeys = [...localKeys, ...stored];
|
|
22797
22973
|
}
|
|
22798
|
-
const
|
|
22799
|
-
|
|
22800
|
-
|
|
22801
|
-
|
|
22802
|
-
|
|
22803
|
-
|
|
22804
|
-
|
|
22974
|
+
const failed = local.filter((c) => c.status === "failed").map((c) => c.kind);
|
|
22975
|
+
let baseline = [];
|
|
22976
|
+
if (failed.length > 0) {
|
|
22977
|
+
const before = await withOriginals({
|
|
22978
|
+
projectRoot: opts.projectRoot,
|
|
22979
|
+
changeId: session.id,
|
|
22980
|
+
work: () => runChecks(changed, failed)
|
|
22981
|
+
}).catch((err) => {
|
|
22982
|
+
log(`couldn't re-run the failed checks on the original files: ${err.message}`);
|
|
22983
|
+
return null;
|
|
22984
|
+
});
|
|
22985
|
+
if (signal.aborted) return;
|
|
22986
|
+
if (before?.status === "kept-edit") {
|
|
22987
|
+
await transition(
|
|
22988
|
+
session.id,
|
|
22989
|
+
"failed",
|
|
22990
|
+
`Files changed while Revyse was checking: ${before.paths.join(", ")}`,
|
|
22991
|
+
"conflict"
|
|
22992
|
+
).catch(() => {
|
|
22993
|
+
});
|
|
22994
|
+
return conflict(session.id, "changed_while_applying", before.paths);
|
|
22995
|
+
}
|
|
22996
|
+
if (before?.status === "done") baseline = before.value;
|
|
22997
|
+
}
|
|
22998
|
+
const checks = [...compareWithBaseline(local, baseline), browserResult];
|
|
22805
22999
|
session.stage = "reporting";
|
|
22806
23000
|
const { change } = await opts.api.post(
|
|
22807
23001
|
`/api/changes/${session.id}/result`,
|
|
@@ -22823,13 +23017,7 @@ function createChangeRunner(opts) {
|
|
|
22823
23017
|
type: "result",
|
|
22824
23018
|
changeId: session.id,
|
|
22825
23019
|
state: change.state,
|
|
22826
|
-
files
|
|
22827
|
-
path: f.path,
|
|
22828
|
-
additions: f.additions,
|
|
22829
|
-
deletions: f.deletions,
|
|
22830
|
-
explanation: extras.explanations?.get(f.path),
|
|
22831
|
-
...f.kind ? { kind: f.kind } : {}
|
|
22832
|
-
})),
|
|
23020
|
+
files,
|
|
22833
23021
|
diff: applied.diff,
|
|
22834
23022
|
checks,
|
|
22835
23023
|
cost: session.cost,
|
|
@@ -22955,21 +23143,19 @@ function createChangeRunner(opts) {
|
|
|
22955
23143
|
const planned = new Set(session.plan?.steps.flatMap((s) => s.files) ?? []);
|
|
22956
23144
|
opts.emit({ type: "applying", changeId: session.id, files: planned.size });
|
|
22957
23145
|
const browserPrep = prepareBrowser(session);
|
|
22958
|
-
const baselinePrep = hasLocalChecks() ? runChecks([]).catch((err) => {
|
|
22959
|
-
log(`baseline checks failed to run: ${err.message}`);
|
|
22960
|
-
return [];
|
|
22961
|
-
}) : Promise.resolve([]);
|
|
22962
23146
|
try {
|
|
22963
|
-
await agentApplyWith(session, signal, browserPrep
|
|
23147
|
+
await agentApplyWith(session, signal, browserPrep);
|
|
22964
23148
|
} finally {
|
|
22965
23149
|
void browserPrep.then((b) => b.driver?.close()).catch(() => {
|
|
22966
23150
|
});
|
|
22967
23151
|
}
|
|
22968
23152
|
}
|
|
22969
|
-
async function agentApplyWith(session, signal, browserPrep
|
|
23153
|
+
async function agentApplyWith(session, signal, browserPrep) {
|
|
23154
|
+
let ready;
|
|
23155
|
+
const browserReady = () => ready ??= beforePage(browserPrep);
|
|
22970
23156
|
const stop = {};
|
|
22971
23157
|
const flush = async () => {
|
|
22972
|
-
await
|
|
23158
|
+
await browserReady();
|
|
22973
23159
|
if (session.applied) {
|
|
22974
23160
|
const undone = revertChange({ projectRoot: opts.projectRoot, changeId: session.id });
|
|
22975
23161
|
if (undone.status === "conflict")
|
|
@@ -23033,21 +23219,20 @@ ${detail}` : "."}`,
|
|
|
23033
23219
|
});
|
|
23034
23220
|
}
|
|
23035
23221
|
if (final.status !== "applied") return landingFailed(session, final);
|
|
23036
|
-
|
|
23037
|
-
await checkAndReport(session, final, browser, baseline, { summary: done.summary });
|
|
23222
|
+
await checkAndReport(session, final, await browserReady(), signal, { summary: done.summary });
|
|
23038
23223
|
}
|
|
23039
23224
|
async function addDependency(rt, pkg, version2) {
|
|
23040
23225
|
const pm = opts.config.packageManager;
|
|
23041
23226
|
const files = ["package.json", LOCKFILE[pm]];
|
|
23042
23227
|
const disk = (rel) => {
|
|
23043
23228
|
try {
|
|
23044
|
-
return readFileSync6(
|
|
23229
|
+
return readFileSync6(path15.join(opts.projectRoot, rel), "utf8");
|
|
23045
23230
|
} catch {
|
|
23046
23231
|
return null;
|
|
23047
23232
|
}
|
|
23048
23233
|
};
|
|
23049
23234
|
const put = (rel, content) => {
|
|
23050
|
-
const file2 =
|
|
23235
|
+
const file2 = path15.join(opts.projectRoot, rel);
|
|
23051
23236
|
if (content === null) rmSync2(file2, { force: true });
|
|
23052
23237
|
else writeFileSync3(file2, content);
|
|
23053
23238
|
};
|
|
@@ -23259,6 +23444,11 @@ ${r.output.slice(-3e3)}`,
|
|
|
23259
23444
|
},
|
|
23260
23445
|
revert(changeId) {
|
|
23261
23446
|
const s = sessionOf(changeId);
|
|
23447
|
+
if (s.busy && s.stage === "checking") {
|
|
23448
|
+
s.abort?.abort();
|
|
23449
|
+
s.abort = void 0;
|
|
23450
|
+
s.busy = false;
|
|
23451
|
+
}
|
|
23262
23452
|
run(s, async () => {
|
|
23263
23453
|
s.stage = "reverting";
|
|
23264
23454
|
const files = restore(s);
|
|
@@ -23310,32 +23500,32 @@ ${r.output.slice(-3e3)}`,
|
|
|
23310
23500
|
}
|
|
23311
23501
|
|
|
23312
23502
|
// src/credentials.ts
|
|
23313
|
-
import { chmodSync, existsSync as
|
|
23503
|
+
import { chmodSync, existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
23314
23504
|
import { homedir } from "os";
|
|
23315
|
-
import
|
|
23505
|
+
import path16 from "path";
|
|
23316
23506
|
function credentialsPath(env = process.env) {
|
|
23317
|
-
const base = env.XDG_CONFIG_HOME ||
|
|
23318
|
-
return
|
|
23507
|
+
const base = env.XDG_CONFIG_HOME || path16.join(homedir(), ".config");
|
|
23508
|
+
return path16.join(base, "revyse", "credentials.json");
|
|
23319
23509
|
}
|
|
23320
23510
|
function readAll(file2) {
|
|
23321
|
-
if (!
|
|
23511
|
+
if (!existsSync5(file2)) return {};
|
|
23322
23512
|
return JSON.parse(readFileSync7(file2, "utf8"));
|
|
23323
23513
|
}
|
|
23324
23514
|
function writeAll(file2, data) {
|
|
23325
|
-
mkdirSync3(
|
|
23515
|
+
mkdirSync3(path16.dirname(file2), { recursive: true, mode: 448 });
|
|
23326
23516
|
writeFileSync4(file2, `${JSON.stringify(data, null, 2)}
|
|
23327
23517
|
`, { mode: 384 });
|
|
23328
23518
|
chmodSync(file2, 384);
|
|
23329
23519
|
}
|
|
23330
23520
|
function loadCredential(projectRoot, file2 = credentialsPath()) {
|
|
23331
|
-
return readAll(file2)[
|
|
23521
|
+
return readAll(file2)[path16.resolve(projectRoot)] ?? null;
|
|
23332
23522
|
}
|
|
23333
23523
|
function saveCredential(projectRoot, cred, file2 = credentialsPath()) {
|
|
23334
|
-
writeAll(file2, { ...readAll(file2), [
|
|
23524
|
+
writeAll(file2, { ...readAll(file2), [path16.resolve(projectRoot)]: cred });
|
|
23335
23525
|
}
|
|
23336
23526
|
function deleteCredential(projectRoot, file2 = credentialsPath()) {
|
|
23337
23527
|
const all = readAll(file2);
|
|
23338
|
-
delete all[
|
|
23528
|
+
delete all[path16.resolve(projectRoot)];
|
|
23339
23529
|
writeAll(file2, all);
|
|
23340
23530
|
}
|
|
23341
23531
|
|
|
@@ -23374,13 +23564,13 @@ Full policy: ${DATA_HANDLING_URL}
|
|
|
23374
23564
|
`;
|
|
23375
23565
|
|
|
23376
23566
|
// src/init-flow.ts
|
|
23377
|
-
import { existsSync as
|
|
23378
|
-
import
|
|
23567
|
+
import { existsSync as existsSync8, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
|
|
23568
|
+
import path19 from "path";
|
|
23379
23569
|
import { createTwoFilesPatch as createTwoFilesPatch4 } from "diff";
|
|
23380
23570
|
|
|
23381
23571
|
// src/detect.ts
|
|
23382
|
-
import { existsSync as
|
|
23383
|
-
import
|
|
23572
|
+
import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync8, statSync as statSync5 } from "fs";
|
|
23573
|
+
import path17 from "path";
|
|
23384
23574
|
var LOCKFILES2 = [
|
|
23385
23575
|
["pnpm-lock.yaml", "pnpm"],
|
|
23386
23576
|
["yarn.lock", "yarn"],
|
|
@@ -23390,8 +23580,8 @@ var LOCKFILES2 = [
|
|
|
23390
23580
|
];
|
|
23391
23581
|
var SOURCE_DIR_CANDIDATES = ["src", "app", "pages", "components", "lib"];
|
|
23392
23582
|
function detectProject(root) {
|
|
23393
|
-
const pkgPath =
|
|
23394
|
-
if (!
|
|
23583
|
+
const pkgPath = path17.join(root, "package.json");
|
|
23584
|
+
if (!existsSync6(pkgPath)) {
|
|
23395
23585
|
throw new Error(`No package.json found in ${root}. Run revyse init from your project root.`);
|
|
23396
23586
|
}
|
|
23397
23587
|
const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
|
|
@@ -23403,13 +23593,13 @@ function detectProject(root) {
|
|
|
23403
23593
|
} else if (framework !== "nextjs") {
|
|
23404
23594
|
warnings.push(`${framework} support is experimental; Next.js is the supported target.`);
|
|
23405
23595
|
}
|
|
23406
|
-
const managers = LOCKFILES2.filter(([file2]) =>
|
|
23596
|
+
const managers = LOCKFILES2.filter(([file2]) => existsSync6(path17.join(root, file2)));
|
|
23407
23597
|
const packageManager = managers[0]?.[1] ?? "npm";
|
|
23408
23598
|
if (managers.length === 0) warnings.push("No lockfile found; defaulting to npm.");
|
|
23409
23599
|
if (new Set(managers.map(([, m]) => m)).size > 1) {
|
|
23410
23600
|
warnings.push(`Multiple lockfiles found; using ${packageManager}.`);
|
|
23411
23601
|
}
|
|
23412
|
-
const sourceDirs = SOURCE_DIR_CANDIDATES.filter((d) =>
|
|
23602
|
+
const sourceDirs = SOURCE_DIR_CANDIDATES.filter((d) => existsSync6(path17.join(root, d)));
|
|
23413
23603
|
for (const d of aliasedDirs(root)) if (!sourceDirs.includes(d)) sourceDirs.push(d);
|
|
23414
23604
|
if (sourceDirs.length === 0) {
|
|
23415
23605
|
warnings.push("No source directories found; edit sourceDirs in revyse.config.json.");
|
|
@@ -23427,14 +23617,16 @@ function detectProject(root) {
|
|
|
23427
23617
|
dev: pick2("dev"),
|
|
23428
23618
|
typecheck: pick2("typecheck", "type-check", "tsc"),
|
|
23429
23619
|
lint: pick2("lint"),
|
|
23430
|
-
|
|
23620
|
+
// Tests are opt-in (L19, ADR 0042): a suite can take minutes, and a local change should
|
|
23621
|
+
// take seconds. The build stays: it only ever runs in cloud workspaces.
|
|
23431
23622
|
build: pick2("build")
|
|
23432
23623
|
},
|
|
23433
23624
|
exclude: [],
|
|
23434
23625
|
agentPort: 4545,
|
|
23435
23626
|
appOrigins: devOrigins(scripts.dev)
|
|
23436
23627
|
},
|
|
23437
|
-
warnings
|
|
23628
|
+
warnings,
|
|
23629
|
+
optional: { test: pick2("test") }
|
|
23438
23630
|
};
|
|
23439
23631
|
}
|
|
23440
23632
|
function detectFramework(deps) {
|
|
@@ -23482,13 +23674,13 @@ function hasSource(dir, depth = 3) {
|
|
|
23482
23674
|
for (const entry of readdirSync4(dir, { withFileTypes: true })) {
|
|
23483
23675
|
if (entry.isFile() && SOURCE_FILE.test(entry.name)) return true;
|
|
23484
23676
|
if (entry.isDirectory() && depth > 1 && !NOT_SOURCE.has(entry.name)) {
|
|
23485
|
-
if (hasSource(
|
|
23677
|
+
if (hasSource(path17.join(dir, entry.name), depth - 1)) return true;
|
|
23486
23678
|
}
|
|
23487
23679
|
}
|
|
23488
23680
|
return false;
|
|
23489
23681
|
}
|
|
23490
23682
|
function aliasedDirs(root) {
|
|
23491
|
-
const file2 = ["tsconfig.json", "jsconfig.json"].map((f) =>
|
|
23683
|
+
const file2 = ["tsconfig.json", "jsconfig.json"].map((f) => path17.join(root, f)).find((f) => existsSync6(f));
|
|
23492
23684
|
if (!file2) return [];
|
|
23493
23685
|
let compilerOptions;
|
|
23494
23686
|
try {
|
|
@@ -23500,15 +23692,15 @@ function aliasedDirs(root) {
|
|
|
23500
23692
|
const baseUrl = compilerOptions?.baseUrl ?? ".";
|
|
23501
23693
|
const dirs = /* @__PURE__ */ new Set();
|
|
23502
23694
|
for (const target of Object.values(paths).flat()) {
|
|
23503
|
-
const rel =
|
|
23695
|
+
const rel = path17.relative(root, path17.join(root, baseUrl, target)).split(path17.sep).join("/");
|
|
23504
23696
|
const top = rel.split("/")[0];
|
|
23505
23697
|
if (rel.startsWith("..") || NOT_SOURCE.has(top)) continue;
|
|
23506
23698
|
if (top === "*" || top === "") {
|
|
23507
23699
|
for (const entry of readdirSync4(root, { withFileTypes: true }))
|
|
23508
23700
|
if (entry.isDirectory() && !NOT_SOURCE.has(entry.name) && !entry.name.startsWith(".")) {
|
|
23509
|
-
if (hasSource(
|
|
23701
|
+
if (hasSource(path17.join(root, entry.name))) dirs.add(entry.name);
|
|
23510
23702
|
}
|
|
23511
|
-
} else if (
|
|
23703
|
+
} else if (existsSync6(path17.join(root, top)) && statSync5(path17.join(root, top)).isDirectory()) {
|
|
23512
23704
|
dirs.add(top);
|
|
23513
23705
|
}
|
|
23514
23706
|
}
|
|
@@ -23516,8 +23708,8 @@ function aliasedDirs(root) {
|
|
|
23516
23708
|
}
|
|
23517
23709
|
|
|
23518
23710
|
// src/next-config.ts
|
|
23519
|
-
import { existsSync as
|
|
23520
|
-
import
|
|
23711
|
+
import { existsSync as existsSync7, readFileSync as readFileSync9 } from "fs";
|
|
23712
|
+
import path18 from "path";
|
|
23521
23713
|
import { parse as parse3 } from "@babel/parser";
|
|
23522
23714
|
import MagicString from "magic-string";
|
|
23523
23715
|
var NEXT_CONFIG_FILES = [
|
|
@@ -23528,7 +23720,7 @@ var NEXT_CONFIG_FILES = [
|
|
|
23528
23720
|
];
|
|
23529
23721
|
function findNextConfig(root) {
|
|
23530
23722
|
for (const name of NEXT_CONFIG_FILES) {
|
|
23531
|
-
if (
|
|
23723
|
+
if (existsSync7(path18.join(root, name))) return name;
|
|
23532
23724
|
}
|
|
23533
23725
|
return null;
|
|
23534
23726
|
}
|
|
@@ -23583,7 +23775,7 @@ ${importLine}`);
|
|
|
23583
23775
|
return { status: "unsupported", reason: "it has no `export default` or `module.exports`" };
|
|
23584
23776
|
}
|
|
23585
23777
|
function readNextConfig(root, file2) {
|
|
23586
|
-
return readFileSync9(
|
|
23778
|
+
return readFileSync9(path18.join(root, file2), "utf8");
|
|
23587
23779
|
}
|
|
23588
23780
|
|
|
23589
23781
|
// src/version.ts
|
|
@@ -23610,9 +23802,9 @@ async function runInit(opts, io) {
|
|
|
23610
23802
|
const ask = (q) => opts.yes ? Promise.resolve(true) : io.confirm(q);
|
|
23611
23803
|
let configured;
|
|
23612
23804
|
let status;
|
|
23613
|
-
if (
|
|
23805
|
+
if (existsSync8(path19.join(root, CONFIG_FILE)) && !opts.force) {
|
|
23614
23806
|
const config3 = projectConfigSchema.parse(
|
|
23615
|
-
JSON.parse(readFileSync10(
|
|
23807
|
+
JSON.parse(readFileSync10(path19.join(root, CONFIG_FILE), "utf8"))
|
|
23616
23808
|
);
|
|
23617
23809
|
io.log(`${CONFIG_FILE} is already here; checking the rest of the setup.`);
|
|
23618
23810
|
if (!config3.dataDisclosure || config3.dataDisclosure.version < DISCLOSURE_VERSION)
|
|
@@ -23649,7 +23841,7 @@ Next: ${steps.join(", then ")}. Then start your app as usual.`
|
|
|
23649
23841
|
async function configure(opts, io, ask) {
|
|
23650
23842
|
const { root } = opts;
|
|
23651
23843
|
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
23652
|
-
const { config: config2, warnings } = detectProject(root);
|
|
23844
|
+
const { config: config2, warnings, optional: optional2 } = detectProject(root);
|
|
23653
23845
|
io.log(`Detected ${config2.framework} (${config2.packageManager})`);
|
|
23654
23846
|
io.log(`Source directories: ${config2.sourceDirs.join(", ")}`);
|
|
23655
23847
|
for (const w of warnings) io.warn(`warning: ${w}`);
|
|
@@ -23693,27 +23885,68 @@ Revyse needs this change to ${file2} (it only has an effect in \`next dev\`):
|
|
|
23693
23885
|
}
|
|
23694
23886
|
}
|
|
23695
23887
|
}
|
|
23696
|
-
writeFileSync5(
|
|
23888
|
+
writeFileSync5(path19.join(root, CONFIG_FILE), `${JSON.stringify(accepted, null, 2)}
|
|
23697
23889
|
`);
|
|
23698
|
-
if (nextConfigEdit) writeFileSync5(
|
|
23890
|
+
if (nextConfigEdit) writeFileSync5(path19.join(root, nextConfigEdit.file), nextConfigEdit.code);
|
|
23699
23891
|
if (ensureGitignored(root))
|
|
23700
23892
|
io.log(`Added ${GITIGNORE_ENTRY} to .gitignore (local change history).`);
|
|
23701
23893
|
io.log(`
|
|
23702
23894
|
Wrote ${CONFIG_FILE}.`);
|
|
23895
|
+
const checks = [
|
|
23896
|
+
...config2.scripts.typecheck ? ["typecheck"] : [],
|
|
23897
|
+
...config2.scripts.lint ? ["lint"] : [],
|
|
23898
|
+
...config2.framework === "nextjs" ? ["the page in a browser"] : []
|
|
23899
|
+
];
|
|
23900
|
+
if (checks.length > 0)
|
|
23901
|
+
io.log(
|
|
23902
|
+
`After each change, Revyse checks ${checks.join(", ").replace(/, ([^,]*)$/, " and $1")}.`
|
|
23903
|
+
);
|
|
23904
|
+
if (optional2.test)
|
|
23905
|
+
io.log(
|
|
23906
|
+
`Your tests (\`${optional2.test}\`) don't run on each change, so changes stay fast. To run them too, add "test": "${optional2.test}" to scripts in ${CONFIG_FILE}.`
|
|
23907
|
+
);
|
|
23703
23908
|
return { config: accepted, nextConfigEdited: Boolean(nextConfigEdit) };
|
|
23704
23909
|
}
|
|
23910
|
+
function olderThan(a, b) {
|
|
23911
|
+
const parse4 = (v) => /^(\d+)\.(\d+)\.(\d+)/.exec(v)?.slice(1).map(Number);
|
|
23912
|
+
const [x, y] = [parse4(a), parse4(b)];
|
|
23913
|
+
if (!x || !y) return false;
|
|
23914
|
+
for (let i = 0; i < 3; i++) if (x[i] !== y[i]) return x[i] < y[i];
|
|
23915
|
+
return false;
|
|
23916
|
+
}
|
|
23917
|
+
function installedVersion(root, name) {
|
|
23918
|
+
try {
|
|
23919
|
+
const pkg = JSON.parse(
|
|
23920
|
+
readFileSync10(path19.join(root, "node_modules", name, "package.json"), "utf8")
|
|
23921
|
+
);
|
|
23922
|
+
return pkg.version ?? null;
|
|
23923
|
+
} catch {
|
|
23924
|
+
return null;
|
|
23925
|
+
}
|
|
23926
|
+
}
|
|
23705
23927
|
async function installPackages(root, config2, setup, io, ask) {
|
|
23706
|
-
const
|
|
23707
|
-
|
|
23708
|
-
|
|
23928
|
+
const listed2 = devDependencies(root);
|
|
23929
|
+
const todo = PACKAGES.flatMap((name) => {
|
|
23930
|
+
const installed = installedVersion(root, name);
|
|
23931
|
+
if (!listed2[name] || !installed) return [{ name, from: null }];
|
|
23932
|
+
return olderThan(installed, VERSION) ? [{ name, from: installed }] : [];
|
|
23933
|
+
});
|
|
23934
|
+
if (todo.length === 0) return true;
|
|
23935
|
+
const specs = todo.map(({ name }) => `${name}@${VERSION}`);
|
|
23709
23936
|
const command = installCommand(config2.packageManager, specs).join(" ");
|
|
23710
|
-
|
|
23711
|
-
|
|
23937
|
+
const installs = todo.filter((t) => !t.from).map((t) => t.name);
|
|
23938
|
+
const updates = todo.filter((t) => t.from).map((t) => `${t.name} from ${t.from} to ${VERSION}`);
|
|
23939
|
+
const what = [
|
|
23940
|
+
...installs.length ? [`install ${installs.join(" and ")} as dev dependencies`] : [],
|
|
23941
|
+
...updates.length ? [`update ${updates.join(" and ")}`] : []
|
|
23942
|
+
].join(", and ");
|
|
23943
|
+
if (!await ask(`${what[0].toUpperCase()}${what.slice(1)} (${command})?`)) {
|
|
23944
|
+
io.log(`Skipped. Run it yourself: ${command}`);
|
|
23712
23945
|
return false;
|
|
23713
23946
|
}
|
|
23714
23947
|
try {
|
|
23715
23948
|
await setup.install(root, config2.packageManager, specs);
|
|
23716
|
-
io.log(`
|
|
23949
|
+
io.log(`Revyse's packages are at ${VERSION}.`);
|
|
23717
23950
|
return true;
|
|
23718
23951
|
} catch (err) {
|
|
23719
23952
|
io.warn(`Couldn't install them (${err.message}). Run: ${command}`);
|
|
@@ -23745,11 +23978,11 @@ async function connectProject(root, config2, setup, io, ask) {
|
|
|
23745
23978
|
}
|
|
23746
23979
|
function projectName(root) {
|
|
23747
23980
|
const name = readPackageJson(root)?.name?.replace(/^@[^/]+\//, "").trim();
|
|
23748
|
-
return (name ||
|
|
23981
|
+
return (name || path19.basename(root)).slice(0, 100);
|
|
23749
23982
|
}
|
|
23750
23983
|
function ensureGitignored(root) {
|
|
23751
|
-
const file2 =
|
|
23752
|
-
const current =
|
|
23984
|
+
const file2 = path19.join(root, ".gitignore");
|
|
23985
|
+
const current = existsSync8(file2) ? readFileSync10(file2, "utf8") : "";
|
|
23753
23986
|
const lines = current.split(/\r?\n/).map((l) => l.trim());
|
|
23754
23987
|
if (lines.includes(GITIGNORE_ENTRY) || lines.includes(".revyse")) return false;
|
|
23755
23988
|
const separator = current === "" ? "" : current.endsWith("\n") ? "\n" : "\n\n";
|
|
@@ -23760,7 +23993,7 @@ ${GITIGNORE_ENTRY}
|
|
|
23760
23993
|
}
|
|
23761
23994
|
function readPackageJson(root) {
|
|
23762
23995
|
try {
|
|
23763
|
-
return JSON.parse(readFileSync10(
|
|
23996
|
+
return JSON.parse(readFileSync10(path19.join(root, "package.json"), "utf8"));
|
|
23764
23997
|
} catch {
|
|
23765
23998
|
return null;
|
|
23766
23999
|
}
|
|
@@ -23805,7 +24038,7 @@ function createRequestHandler(currentRunner) {
|
|
|
23805
24038
|
import { randomBytes, timingSafeEqual } from "crypto";
|
|
23806
24039
|
import { readFileSync as readFileSync11 } from "fs";
|
|
23807
24040
|
import { createServer } from "http";
|
|
23808
|
-
import
|
|
24041
|
+
import path20 from "path";
|
|
23809
24042
|
import { WebSocketServer } from "ws";
|
|
23810
24043
|
var ARTIFACT_PATH = /^\/artifacts\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\/(before|after)\.png$/;
|
|
23811
24044
|
var safeEqual = (a, b) => {
|
|
@@ -23857,7 +24090,7 @@ ${opts.overlayBundle()}`
|
|
|
23857
24090
|
res.writeHead(403).end();
|
|
23858
24091
|
return;
|
|
23859
24092
|
}
|
|
23860
|
-
const file2 =
|
|
24093
|
+
const file2 = path20.join(
|
|
23861
24094
|
opts.project.root,
|
|
23862
24095
|
".revyse",
|
|
23863
24096
|
"changes",
|
|
@@ -23984,7 +24217,7 @@ ${opts.overlayBundle()}`
|
|
|
23984
24217
|
|
|
23985
24218
|
// src/agent/local-agent.ts
|
|
23986
24219
|
function loadConfig(file2) {
|
|
23987
|
-
if (!
|
|
24220
|
+
if (!existsSync9(file2)) throw new Error(`No ${CONFIG_FILE} found. Run \`revyse init\` first.`);
|
|
23988
24221
|
const config2 = projectConfigSchema.parse(JSON.parse(readFileSync12(file2, "utf8")));
|
|
23989
24222
|
if (!config2.dataDisclosure || config2.dataDisclosure.version < DISCLOSURE_VERSION) {
|
|
23990
24223
|
throw new Error("Review and accept the data disclosure first: run `revyse init --force`.");
|
|
@@ -23992,7 +24225,7 @@ function loadConfig(file2) {
|
|
|
23992
24225
|
return config2;
|
|
23993
24226
|
}
|
|
23994
24227
|
function readOverlayBundle() {
|
|
23995
|
-
const file2 =
|
|
24228
|
+
const file2 = path21.join(path21.dirname(fileURLToPath(import.meta.url)), "overlay.js");
|
|
23996
24229
|
return readFileSync12(file2, "utf8");
|
|
23997
24230
|
}
|
|
23998
24231
|
async function identifyAgent(port) {
|
|
@@ -24009,7 +24242,7 @@ async function identifyAgent(port) {
|
|
|
24009
24242
|
}
|
|
24010
24243
|
async function startLocalAgent(opts) {
|
|
24011
24244
|
const { root, log } = opts;
|
|
24012
|
-
const configFile =
|
|
24245
|
+
const configFile = path21.join(root, CONFIG_FILE);
|
|
24013
24246
|
let config2 = loadConfig(configFile);
|
|
24014
24247
|
const port = opts.port ?? config2.agentPort;
|
|
24015
24248
|
const sink = {};
|