truecourse 0.6.1 → 0.6.6-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/cli.mjs +1599 -920
- package/package.json +1 -1
- package/server.mjs +3088 -2507
package/cli.mjs
CHANGED
|
@@ -973,7 +973,7 @@ var require_command = __commonJS({
|
|
|
973
973
|
var EventEmitter2 = __require("node:events").EventEmitter;
|
|
974
974
|
var childProcess = __require("node:child_process");
|
|
975
975
|
var path60 = __require("node:path");
|
|
976
|
-
var
|
|
976
|
+
var fs51 = __require("node:fs");
|
|
977
977
|
var process2 = __require("node:process");
|
|
978
978
|
var { Argument: Argument2, humanReadableArgName } = require_argument();
|
|
979
979
|
var { CommanderError: CommanderError2 } = require_error();
|
|
@@ -1906,10 +1906,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1906
1906
|
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
1907
1907
|
function findFile(baseDir, baseName) {
|
|
1908
1908
|
const localBin = path60.resolve(baseDir, baseName);
|
|
1909
|
-
if (
|
|
1909
|
+
if (fs51.existsSync(localBin)) return localBin;
|
|
1910
1910
|
if (sourceExt.includes(path60.extname(baseName))) return void 0;
|
|
1911
1911
|
const foundExt = sourceExt.find(
|
|
1912
|
-
(ext2) =>
|
|
1912
|
+
(ext2) => fs51.existsSync(`${localBin}${ext2}`)
|
|
1913
1913
|
);
|
|
1914
1914
|
if (foundExt) return `${localBin}${foundExt}`;
|
|
1915
1915
|
return void 0;
|
|
@@ -1921,7 +1921,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1921
1921
|
if (this._scriptPath) {
|
|
1922
1922
|
let resolvedScriptPath;
|
|
1923
1923
|
try {
|
|
1924
|
-
resolvedScriptPath =
|
|
1924
|
+
resolvedScriptPath = fs51.realpathSync(this._scriptPath);
|
|
1925
1925
|
} catch (err) {
|
|
1926
1926
|
resolvedScriptPath = this._scriptPath;
|
|
1927
1927
|
}
|
|
@@ -3492,7 +3492,9 @@ var require_src = __commonJS({
|
|
|
3492
3492
|
|
|
3493
3493
|
// node_modules/.pnpm/@clack+core@1.2.0/node_modules/@clack/core/dist/index.mjs
|
|
3494
3494
|
import { stdout as S, stdin as $ } from "node:process";
|
|
3495
|
+
import * as _ from "node:readline";
|
|
3495
3496
|
import P from "node:readline";
|
|
3497
|
+
import { ReadStream as D } from "node:tty";
|
|
3496
3498
|
function d(r, t2, e) {
|
|
3497
3499
|
if (!e.some((o) => !o.disabled)) return r;
|
|
3498
3500
|
const s = r + t2, i = Math.max(e.length - 1, 0), n = s < 0 ? i : s > i ? 0 : s;
|
|
@@ -3518,6 +3520,27 @@ function w(r, t2) {
|
|
|
3518
3520
|
const e = r;
|
|
3519
3521
|
e.isTTY && e.setRawMode(t2);
|
|
3520
3522
|
}
|
|
3523
|
+
function z({ input: r = $, output: t2 = S, overwrite: e = true, hideCursor: s = true } = {}) {
|
|
3524
|
+
const i = _.createInterface({ input: r, output: t2, prompt: "", tabSize: 1 });
|
|
3525
|
+
_.emitKeypressEvents(r, i), r instanceof D && r.isTTY && r.setRawMode(true);
|
|
3526
|
+
const n = (o, { name: a, sequence: h }) => {
|
|
3527
|
+
const l = String(o);
|
|
3528
|
+
if (V([l, a, h], "cancel")) {
|
|
3529
|
+
s && t2.write(import_sisteransi.cursor.show), process.exit(0);
|
|
3530
|
+
return;
|
|
3531
|
+
}
|
|
3532
|
+
if (!e) return;
|
|
3533
|
+
const f2 = a === "return" ? 0 : -1, v = a === "return" ? -1 : 0;
|
|
3534
|
+
_.moveCursor(t2, f2, v, () => {
|
|
3535
|
+
_.clearLine(t2, 1, () => {
|
|
3536
|
+
r.once("keypress", n);
|
|
3537
|
+
});
|
|
3538
|
+
});
|
|
3539
|
+
};
|
|
3540
|
+
return s && t2.write(import_sisteransi.cursor.hide), r.once("keypress", n), () => {
|
|
3541
|
+
r.off("keypress", n), s && t2.write(import_sisteransi.cursor.show), r instanceof D && r.isTTY && !Y && r.setRawMode(false), i.terminal = false, i.close();
|
|
3542
|
+
};
|
|
3543
|
+
}
|
|
3521
3544
|
function R(r, t2, e, s = e) {
|
|
3522
3545
|
const i = O(r ?? S);
|
|
3523
3546
|
return wrapAnsi(t2, i - e.length, { hard: true, trim: false }).split(`
|
|
@@ -3711,7 +3734,7 @@ import P2 from "node:process";
|
|
|
3711
3734
|
function Ze() {
|
|
3712
3735
|
return P2.platform !== "win32" ? P2.env.TERM !== "linux" : !!P2.env.CI || !!P2.env.WT_SESSION || !!P2.env.TERMINUS_SUBLIME || P2.env.ConEmuTask === "{cmd::Cmder}" || P2.env.TERM_PROGRAM === "Terminus-Sublime" || P2.env.TERM_PROGRAM === "vscode" || P2.env.TERM === "xterm-256color" || P2.env.TERM === "alacritty" || P2.env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
|
|
3713
3736
|
}
|
|
3714
|
-
var import_sisteransi2, ee, w2, _e, oe, ue, F, le, d2, E2, Ie, Ee, z2, H2, te, U, J, xe, se, ce, Ge, $e, de, Oe, he, pe, me, ge, V2, ye, et2, Y2, ot2, O2, pt, mt, gt, Ve, re, _t, je;
|
|
3737
|
+
var import_sisteransi2, ee, ae, w2, _e, oe, ue, F, le, d2, E2, Ie, Ee, z2, H2, te, U, J, xe, se, ce, Ge, $e, de, Oe, he, pe, me, ge, V2, ye, et2, Y2, ot2, O2, pt, mt, gt, Ct, fe, Ve, re, _t, je;
|
|
3715
3738
|
var init_dist4 = __esm({
|
|
3716
3739
|
"node_modules/.pnpm/@clack+prompts@1.2.0/node_modules/@clack/prompts/dist/index.mjs"() {
|
|
3717
3740
|
init_dist3();
|
|
@@ -3720,6 +3743,7 @@ var init_dist4 = __esm({
|
|
|
3720
3743
|
init_dist2();
|
|
3721
3744
|
import_sisteransi2 = __toESM(require_src(), 1);
|
|
3722
3745
|
ee = Ze();
|
|
3746
|
+
ae = () => process.env.CI === "true";
|
|
3723
3747
|
w2 = (e, i) => ee ? e : i;
|
|
3724
3748
|
_e = w2("\u25C6", "*");
|
|
3725
3749
|
oe = w2("\u25A0", "x");
|
|
@@ -3796,7 +3820,7 @@ var init_dist4 = __esm({
|
|
|
3796
3820
|
}
|
|
3797
3821
|
if (f2 > $2) {
|
|
3798
3822
|
let b = 0, x = 0, G2 = f2;
|
|
3799
|
-
const M2 = e - v, R2 = (j2,
|
|
3823
|
+
const M2 = e - v, R2 = (j2, D2) => et2(h, G2, j2, D2, $2);
|
|
3800
3824
|
m ? ({ lineCount: G2, removals: b } = R2(0, M2), G2 > $2 && ({ lineCount: G2, removals: x } = R2(M2 + 1, h.length))) : ({ lineCount: G2, removals: x } = R2(M2 + 1, h.length), G2 > $2 && ({ lineCount: G2, removals: b } = R2(0, M2))), b > 0 && (m = true, h.splice(0, b)), x > 0 && (g = true, h.splice(h.length - x, x));
|
|
3801
3825
|
}
|
|
3802
3826
|
const C3 = [];
|
|
@@ -3875,6 +3899,59 @@ ${t("gray", E2)} ` : "";
|
|
|
3875
3899
|
|
|
3876
3900
|
`);
|
|
3877
3901
|
};
|
|
3902
|
+
Ct = (e) => t("magenta", e);
|
|
3903
|
+
fe = ({ indicator: e = "dots", onCancel: i, output: s = process.stdout, cancelMessage: r, errorMessage: u2, frames: n = ee ? ["\u25D2", "\u25D0", "\u25D3", "\u25D1"] : ["\u2022", "o", "O", "0"], delay: o = ee ? 80 : 120, signal: c2, ...a } = {}) => {
|
|
3904
|
+
const l = ae();
|
|
3905
|
+
let $2, y, p2 = false, m = false, g = "", S2, h = performance.now();
|
|
3906
|
+
const f2 = O(s), v = a?.styleFrame ?? Ct, T = (_2) => {
|
|
3907
|
+
const A2 = _2 > 1 ? u2 ?? u.messages.error : r ?? u.messages.cancel;
|
|
3908
|
+
m = _2 === 1, p2 && (W(A2, _2), m && typeof i == "function" && i());
|
|
3909
|
+
}, C3 = () => T(2), b = () => T(1), x = () => {
|
|
3910
|
+
process.on("uncaughtExceptionMonitor", C3), process.on("unhandledRejection", C3), process.on("SIGINT", b), process.on("SIGTERM", b), process.on("exit", T), c2 && c2.addEventListener("abort", b);
|
|
3911
|
+
}, G2 = () => {
|
|
3912
|
+
process.removeListener("uncaughtExceptionMonitor", C3), process.removeListener("unhandledRejection", C3), process.removeListener("SIGINT", b), process.removeListener("SIGTERM", b), process.removeListener("exit", T), c2 && c2.removeEventListener("abort", b);
|
|
3913
|
+
}, M2 = () => {
|
|
3914
|
+
if (S2 === void 0) return;
|
|
3915
|
+
l && s.write(`
|
|
3916
|
+
`);
|
|
3917
|
+
const _2 = wrapAnsi(S2, f2, { hard: true, trim: false }).split(`
|
|
3918
|
+
`);
|
|
3919
|
+
_2.length > 1 && s.write(import_sisteransi2.cursor.up(_2.length - 1)), s.write(import_sisteransi2.cursor.to(0)), s.write(import_sisteransi2.erase.down());
|
|
3920
|
+
}, R2 = (_2) => _2.replace(/\.+$/, ""), j2 = (_2) => {
|
|
3921
|
+
const A2 = (performance.now() - _2) / 1e3, k = Math.floor(A2 / 60), L = Math.floor(A2 % 60);
|
|
3922
|
+
return k > 0 ? `[${k}m ${L}s]` : `[${L}s]`;
|
|
3923
|
+
}, D2 = a.withGuide ?? u.withGuide, ie = (_2 = "") => {
|
|
3924
|
+
p2 = true, $2 = z({ output: s }), g = R2(_2), h = performance.now(), D2 && s.write(`${t("gray", d2)}
|
|
3925
|
+
`);
|
|
3926
|
+
let A2 = 0, k = 0;
|
|
3927
|
+
x(), y = setInterval(() => {
|
|
3928
|
+
if (l && g === S2) return;
|
|
3929
|
+
M2(), S2 = g;
|
|
3930
|
+
const L = v(n[A2]);
|
|
3931
|
+
let Z;
|
|
3932
|
+
if (l) Z = `${L} ${g}...`;
|
|
3933
|
+
else if (e === "timer") Z = `${L} ${g} ${j2(h)}`;
|
|
3934
|
+
else {
|
|
3935
|
+
const Be = ".".repeat(Math.floor(k)).slice(0, 3);
|
|
3936
|
+
Z = `${L} ${g}${Be}`;
|
|
3937
|
+
}
|
|
3938
|
+
const Ne = wrapAnsi(Z, f2, { hard: true, trim: false });
|
|
3939
|
+
s.write(Ne), A2 = A2 + 1 < n.length ? A2 + 1 : 0, k = k < 4 ? k + 0.125 : 0;
|
|
3940
|
+
}, o);
|
|
3941
|
+
}, W = (_2 = "", A2 = 0, k = false) => {
|
|
3942
|
+
if (!p2) return;
|
|
3943
|
+
p2 = false, clearInterval(y), M2();
|
|
3944
|
+
const L = A2 === 0 ? t("green", F) : A2 === 1 ? t("red", oe) : t("red", ue);
|
|
3945
|
+
g = _2 ?? g, k || (e === "timer" ? s.write(`${L} ${g} ${j2(h)}
|
|
3946
|
+
`) : s.write(`${L} ${g}
|
|
3947
|
+
`)), G2(), $2();
|
|
3948
|
+
};
|
|
3949
|
+
return { start: ie, stop: (_2 = "") => W(_2, 0), message: (_2 = "") => {
|
|
3950
|
+
g = R2(_2 ?? g);
|
|
3951
|
+
}, cancel: (_2 = "") => W(_2, 1), error: (_2 = "") => W(_2, 2), clear: () => W("", 0, true), get isCancelled() {
|
|
3952
|
+
return m;
|
|
3953
|
+
} };
|
|
3954
|
+
};
|
|
3878
3955
|
Ve = { light: w2("\u2500", "-"), heavy: w2("\u2501", "="), block: w2("\u2588", "#") };
|
|
3879
3956
|
re = (e, i) => e.includes(`
|
|
3880
3957
|
`) ? e.split(`
|
|
@@ -3976,7 +4053,20 @@ var init_paths = __esm({
|
|
|
3976
4053
|
"packages/core/dist/config/paths.js"() {
|
|
3977
4054
|
"use strict";
|
|
3978
4055
|
TRUECOURSE_DIR = ".truecourse";
|
|
3979
|
-
GITIGNORE_CONTENTS =
|
|
4056
|
+
GITIGNORE_CONTENTS = [
|
|
4057
|
+
"analyses/",
|
|
4058
|
+
"history.json",
|
|
4059
|
+
"diff.json",
|
|
4060
|
+
"ui-state.json",
|
|
4061
|
+
"logs/",
|
|
4062
|
+
".analyze.lock",
|
|
4063
|
+
"contracts/",
|
|
4064
|
+
"verifier/runs/",
|
|
4065
|
+
"verifier/history.json",
|
|
4066
|
+
"verifier/diff.json",
|
|
4067
|
+
".cache/",
|
|
4068
|
+
""
|
|
4069
|
+
].join("\n");
|
|
3980
4070
|
}
|
|
3981
4071
|
});
|
|
3982
4072
|
|
|
@@ -4566,6 +4656,16 @@ var init_helpers = __esm({
|
|
|
4566
4656
|
}
|
|
4567
4657
|
});
|
|
4568
4658
|
|
|
4659
|
+
// packages/shared/dist/claude-binary.js
|
|
4660
|
+
function resolveClaudeBinary() {
|
|
4661
|
+
return process.env.CLAUDE_CODE_BINARY || process.env.CLAUDE_CODE_BIN || "claude";
|
|
4662
|
+
}
|
|
4663
|
+
var init_claude_binary = __esm({
|
|
4664
|
+
"packages/shared/dist/claude-binary.js"() {
|
|
4665
|
+
"use strict";
|
|
4666
|
+
}
|
|
4667
|
+
});
|
|
4668
|
+
|
|
4569
4669
|
// packages/core/dist/lib/atomic-write.js
|
|
4570
4670
|
import fs5 from "node:fs";
|
|
4571
4671
|
import path5 from "node:path";
|
|
@@ -5411,7 +5511,7 @@ var require_node = __commonJS({
|
|
|
5411
5511
|
exports.inspectOpts = Object.keys(process.env).filter((key2) => {
|
|
5412
5512
|
return /^debug_/i.test(key2);
|
|
5413
5513
|
}).reduce((obj, key2) => {
|
|
5414
|
-
const prop = key2.substring(6).toLowerCase().replace(/_([a-z])/g, (
|
|
5514
|
+
const prop = key2.substring(6).toLowerCase().replace(/_([a-z])/g, (_2, k) => {
|
|
5415
5515
|
return k.toUpperCase();
|
|
5416
5516
|
});
|
|
5417
5517
|
let val = process.env[key2];
|
|
@@ -6887,7 +6987,7 @@ function deleteBranchTask(branch, forceDelete = false) {
|
|
|
6887
6987
|
parser(stdOut, stdErr) {
|
|
6888
6988
|
return parseBranchDeletions(stdOut, stdErr).branches[branch];
|
|
6889
6989
|
},
|
|
6890
|
-
onError({ exitCode, stdErr, stdOut }, error,
|
|
6990
|
+
onError({ exitCode, stdErr, stdOut }, error, _2, fail4) {
|
|
6891
6991
|
if (!hasBranchDeletionError(String(error), exitCode)) {
|
|
6892
6992
|
return fail4(error);
|
|
6893
6993
|
}
|
|
@@ -10066,6 +10166,7 @@ var init_errors = __esm({
|
|
|
10066
10166
|
});
|
|
10067
10167
|
|
|
10068
10168
|
// packages/core/dist/lib/git.js
|
|
10169
|
+
import fs8 from "node:fs";
|
|
10069
10170
|
async function isGitRepo(repoPath) {
|
|
10070
10171
|
try {
|
|
10071
10172
|
return await simpleGit(repoPath).checkIsRepo();
|
|
@@ -10081,31 +10182,74 @@ async function getGit(repoPath) {
|
|
|
10081
10182
|
}
|
|
10082
10183
|
return git;
|
|
10083
10184
|
}
|
|
10185
|
+
async function runWithStash(repoRoot6, options, fn) {
|
|
10186
|
+
let didStash = false;
|
|
10187
|
+
let stashGit;
|
|
10188
|
+
if (!options.skipStash) {
|
|
10189
|
+
try {
|
|
10190
|
+
stashGit = await getGit(repoRoot6);
|
|
10191
|
+
const status = await stashGit.status();
|
|
10192
|
+
if (!status.isClean()) {
|
|
10193
|
+
const gitRoot = (await stashGit.revparse(["--show-toplevel"])).trim();
|
|
10194
|
+
if (fs8.realpathSync(repoRoot6) === fs8.realpathSync(gitRoot)) {
|
|
10195
|
+
options.onStashStart?.();
|
|
10196
|
+
const res = await stashGit.stash([
|
|
10197
|
+
"push",
|
|
10198
|
+
"--include-untracked",
|
|
10199
|
+
"-m",
|
|
10200
|
+
options.message,
|
|
10201
|
+
// Stash everything under the repo root EXCEPT `.truecourse/`, so the
|
|
10202
|
+
// tool's own working dir (contracts, specs, baselines) survives.
|
|
10203
|
+
"--",
|
|
10204
|
+
".",
|
|
10205
|
+
`:(exclude,top)${TRUECOURSE_DIR}`
|
|
10206
|
+
]);
|
|
10207
|
+
didStash = !res.includes("No local changes");
|
|
10208
|
+
}
|
|
10209
|
+
}
|
|
10210
|
+
} catch (error) {
|
|
10211
|
+
options.onStashError?.(error instanceof Error ? error : new Error(String(error)));
|
|
10212
|
+
}
|
|
10213
|
+
}
|
|
10214
|
+
try {
|
|
10215
|
+
return await fn();
|
|
10216
|
+
} finally {
|
|
10217
|
+
if (didStash && stashGit) {
|
|
10218
|
+
options.onRestoreStart?.();
|
|
10219
|
+
try {
|
|
10220
|
+
await stashGit.stash(["pop"]);
|
|
10221
|
+
} catch (error) {
|
|
10222
|
+
options.onRestoreError?.(error instanceof Error ? error : new Error(String(error)));
|
|
10223
|
+
}
|
|
10224
|
+
}
|
|
10225
|
+
}
|
|
10226
|
+
}
|
|
10084
10227
|
var NOT_A_GIT_REPO_MESSAGE;
|
|
10085
10228
|
var init_git = __esm({
|
|
10086
10229
|
"packages/core/dist/lib/git.js"() {
|
|
10087
10230
|
"use strict";
|
|
10088
10231
|
init_esm();
|
|
10089
10232
|
init_errors();
|
|
10233
|
+
init_paths();
|
|
10090
10234
|
NOT_A_GIT_REPO_MESSAGE = "The selected folder is not a git repository. Please select a folder that has been initialized with git.";
|
|
10091
10235
|
}
|
|
10092
10236
|
});
|
|
10093
10237
|
|
|
10094
10238
|
// packages/core/dist/config/project-config.js
|
|
10095
|
-
import
|
|
10239
|
+
import fs9 from "node:fs";
|
|
10096
10240
|
function readProjectConfig(repoDir) {
|
|
10097
10241
|
const file = getRepoConfigPath(repoDir);
|
|
10098
|
-
if (!
|
|
10242
|
+
if (!fs9.existsSync(file))
|
|
10099
10243
|
return { ...EMPTY };
|
|
10100
10244
|
try {
|
|
10101
|
-
return JSON.parse(
|
|
10245
|
+
return JSON.parse(fs9.readFileSync(file, "utf-8"));
|
|
10102
10246
|
} catch {
|
|
10103
10247
|
return { ...EMPTY };
|
|
10104
10248
|
}
|
|
10105
10249
|
}
|
|
10106
10250
|
function writeProjectConfig(repoDir, config2) {
|
|
10107
10251
|
ensureRepoTruecourseDir(repoDir);
|
|
10108
|
-
|
|
10252
|
+
fs9.writeFileSync(getRepoConfigPath(repoDir), JSON.stringify(config2, null, 2), "utf-8");
|
|
10109
10253
|
}
|
|
10110
10254
|
function updateProjectConfig(repoDir, patch) {
|
|
10111
10255
|
const current = readProjectConfig(repoDir);
|
|
@@ -10174,7 +10318,7 @@ var require_ignore = __commonJS({
|
|
|
10174
10318
|
// (a ) -> (a)
|
|
10175
10319
|
// (a \ ) -> (a )
|
|
10176
10320
|
/((?:\\\\)*?)(\\?\s+)$/,
|
|
10177
|
-
(
|
|
10321
|
+
(_2, m1, m2) => m1 + (m2.indexOf("\\") === 0 ? SPACE : EMPTY2)
|
|
10178
10322
|
],
|
|
10179
10323
|
// Replace (\ ) with ' '
|
|
10180
10324
|
// (\ ) -> ' '
|
|
@@ -10182,7 +10326,7 @@ var require_ignore = __commonJS({
|
|
|
10182
10326
|
// (\\\ ) -> '\\ '
|
|
10183
10327
|
[
|
|
10184
10328
|
/(\\+?)\s/g,
|
|
10185
|
-
(
|
|
10329
|
+
(_2, m1) => {
|
|
10186
10330
|
const { length } = m1;
|
|
10187
10331
|
return m1.slice(0, length - length % 2) + SPACE;
|
|
10188
10332
|
}
|
|
@@ -10253,7 +10397,7 @@ var require_ignore = __commonJS({
|
|
|
10253
10397
|
// Zero, one or several directories
|
|
10254
10398
|
// should not use '*', or it will be replaced by the next replacer
|
|
10255
10399
|
// Check if it is not the last `'/**'`
|
|
10256
|
-
(
|
|
10400
|
+
(_2, index, str2) => index + 6 < str2.length ? "(?:\\/[^\\/]+)*" : "\\/.+"
|
|
10257
10401
|
],
|
|
10258
10402
|
// normal intermediate wildcards
|
|
10259
10403
|
[
|
|
@@ -10265,7 +10409,7 @@ var require_ignore = __commonJS({
|
|
|
10265
10409
|
/(^|[^\\]+)(\\\*)+(?=.+)/g,
|
|
10266
10410
|
// '*.js' matches '.js'
|
|
10267
10411
|
// '*.js' doesn't match 'abc'
|
|
10268
|
-
(
|
|
10412
|
+
(_2, p1, p2) => {
|
|
10269
10413
|
const unescaped = p2.replace(/\\\*/g, "[^\\/]*");
|
|
10270
10414
|
return p1 + unescaped;
|
|
10271
10415
|
}
|
|
@@ -10312,11 +10456,11 @@ var require_ignore = __commonJS({
|
|
|
10312
10456
|
var MODE_CHECK_IGNORE = "checkRegex";
|
|
10313
10457
|
var UNDERSCORE = "_";
|
|
10314
10458
|
var TRAILING_WILD_CARD_REPLACERS = {
|
|
10315
|
-
[MODE_IGNORE](
|
|
10459
|
+
[MODE_IGNORE](_2, p1) {
|
|
10316
10460
|
const prefix = p1 ? `${p1}[^/]+` : "[^/]*";
|
|
10317
10461
|
return `${prefix}(?=$|\\/$)`;
|
|
10318
10462
|
},
|
|
10319
|
-
[MODE_CHECK_IGNORE](
|
|
10463
|
+
[MODE_CHECK_IGNORE](_2, p1) {
|
|
10320
10464
|
const prefix = p1 ? `${p1}[^/]*` : "[^/]*";
|
|
10321
10465
|
return `${prefix}(?=$|\\/$)`;
|
|
10322
10466
|
}
|
|
@@ -10999,10 +11143,10 @@ import { Parser, Language } from "web-tree-sitter";
|
|
|
10999
11143
|
import { createRequire as _createRequire } from "node:module";
|
|
11000
11144
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
11001
11145
|
import path8 from "node:path";
|
|
11002
|
-
import
|
|
11146
|
+
import fs10 from "node:fs";
|
|
11003
11147
|
function resolveWasmPath(subpath) {
|
|
11004
11148
|
const bundled = path8.join(BUNDLED_WASM_DIR, path8.basename(subpath));
|
|
11005
|
-
if (
|
|
11149
|
+
if (fs10.existsSync(bundled))
|
|
11006
11150
|
return bundled;
|
|
11007
11151
|
return _require.resolve(subpath);
|
|
11008
11152
|
}
|
|
@@ -15507,7 +15651,7 @@ var init_ast = __esm({
|
|
|
15507
15651
|
if (!isExtglobAST(this)) {
|
|
15508
15652
|
const noEmpty = this.isStart() && this.isEnd() && !this.#parts.some((s) => typeof s !== "string");
|
|
15509
15653
|
const src = this.#parts.map((p2) => {
|
|
15510
|
-
const [re2,
|
|
15654
|
+
const [re2, _2, hasMagic, uflag] = typeof p2 === "string" ? _a2.#parseGlob(p2, this.#hasMagic, noEmpty) : p2.toRegExpSource(allowDot);
|
|
15511
15655
|
this.#hasMagic = this.#hasMagic || hasMagic;
|
|
15512
15656
|
this.#uflag = this.#uflag || uflag;
|
|
15513
15657
|
return re2;
|
|
@@ -15613,7 +15757,7 @@ var init_ast = __esm({
|
|
|
15613
15757
|
if (typeof p2 === "string") {
|
|
15614
15758
|
throw new Error("string type in extglob ast??");
|
|
15615
15759
|
}
|
|
15616
|
-
const [re2,
|
|
15760
|
+
const [re2, _2, _hasMagic, uflag] = p2.toRegExpSource(dot);
|
|
15617
15761
|
this.#uflag = this.#uflag || uflag;
|
|
15618
15762
|
return re2;
|
|
15619
15763
|
}).filter((p2) => !(this.isStart() && this.isEnd()) || !!p2).join("|");
|
|
@@ -15886,7 +16030,7 @@ var init_esm4 = __esm({
|
|
|
15886
16030
|
}
|
|
15887
16031
|
return false;
|
|
15888
16032
|
}
|
|
15889
|
-
debug(...
|
|
16033
|
+
debug(..._2) {
|
|
15890
16034
|
}
|
|
15891
16035
|
make() {
|
|
15892
16036
|
const pattern = this.pattern;
|
|
@@ -15908,7 +16052,7 @@ var init_esm4 = __esm({
|
|
|
15908
16052
|
const rawGlobParts = this.globSet.map((s) => this.slashSplit(s));
|
|
15909
16053
|
this.globParts = this.preprocess(rawGlobParts);
|
|
15910
16054
|
this.debug(this.pattern, this.globParts);
|
|
15911
|
-
let set2 = this.globParts.map((s,
|
|
16055
|
+
let set2 = this.globParts.map((s, _2, __) => {
|
|
15912
16056
|
if (this.isWindows && this.windowsNoMagicRoot) {
|
|
15913
16057
|
const isUNC = s[0] === "" && s[1] === "" && (s[2] === "?" || !globMagic.test(s[2])) && !globMagic.test(s[3]);
|
|
15914
16058
|
const isDrive = /^[a-z]:/i.test(s[0]);
|
|
@@ -61925,7 +62069,7 @@ function pythonRegexToJs(pattern) {
|
|
|
61925
62069
|
collectFlag(f2);
|
|
61926
62070
|
pattern = pattern.slice(prefix[0].length);
|
|
61927
62071
|
}
|
|
61928
|
-
pattern = pattern.replace(/\(\?([aiLmsux]+):/g, (
|
|
62072
|
+
pattern = pattern.replace(/\(\?([aiLmsux]+):/g, (_2, flagSet) => {
|
|
61929
62073
|
for (const f2 of flagSet)
|
|
61930
62074
|
collectFlag(f2);
|
|
61931
62075
|
return "(?:";
|
|
@@ -74612,8 +74756,8 @@ var init_test_empty_file = __esm({
|
|
|
74612
74756
|
nodeTypes: ["program"],
|
|
74613
74757
|
visit(node2, filePath, sourceCode) {
|
|
74614
74758
|
const lowerPath = filePath.toLowerCase();
|
|
74615
|
-
const
|
|
74616
|
-
if (!
|
|
74759
|
+
const isTestFile3 = lowerPath.includes(".test.") || lowerPath.includes(".spec.") || lowerPath.includes("__tests__");
|
|
74760
|
+
if (!isTestFile3)
|
|
74617
74761
|
return null;
|
|
74618
74762
|
if (hasTestFunction(node2))
|
|
74619
74763
|
return null;
|
|
@@ -88426,8 +88570,8 @@ var init_test_not_discoverable = __esm({
|
|
|
88426
88570
|
return null;
|
|
88427
88571
|
const className = classNameNode.text;
|
|
88428
88572
|
const isTestClass = className.startsWith("Test") || className.endsWith("Test") || className.endsWith("Tests");
|
|
88429
|
-
const
|
|
88430
|
-
if (!isTestClass && !
|
|
88573
|
+
const isTestFile3 = isPythonTestFile(filePath);
|
|
88574
|
+
if (!isTestClass && !isTestFile3)
|
|
88431
88575
|
return null;
|
|
88432
88576
|
const body = node2.childForFieldName("body");
|
|
88433
88577
|
if (!body)
|
|
@@ -90915,7 +91059,7 @@ var init_js_naming_convention = __esm({
|
|
|
90915
91059
|
return null;
|
|
90916
91060
|
}
|
|
90917
91061
|
if (funcName.includes("_") && !funcName.startsWith("_")) {
|
|
90918
|
-
return makeViolation(this.ruleKey, node2, filePath, "low", "Function uses snake_case naming", `Function '${funcName}' uses snake_case. JavaScript convention is camelCase.`, sourceCode, `Rename to camelCase: ${funcName.replace(/_([a-z])/g, (
|
|
91062
|
+
return makeViolation(this.ruleKey, node2, filePath, "low", "Function uses snake_case naming", `Function '${funcName}' uses snake_case. JavaScript convention is camelCase.`, sourceCode, `Rename to camelCase: ${funcName.replace(/_([a-z])/g, (_2, c2) => c2.toUpperCase())}.`);
|
|
90919
91063
|
}
|
|
90920
91064
|
return null;
|
|
90921
91065
|
}
|
|
@@ -99324,7 +99468,7 @@ var util, objectUtil, ZodParsedType, getParsedType;
|
|
|
99324
99468
|
var init_util3 = __esm({
|
|
99325
99469
|
"node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/util.js"() {
|
|
99326
99470
|
(function(util2) {
|
|
99327
|
-
util2.assertEqual = (
|
|
99471
|
+
util2.assertEqual = (_2) => {
|
|
99328
99472
|
};
|
|
99329
99473
|
function assertIs(_arg) {
|
|
99330
99474
|
}
|
|
@@ -99374,7 +99518,7 @@ var init_util3 = __esm({
|
|
|
99374
99518
|
return array.map((val) => typeof val === "string" ? `'${val}'` : val).join(separator);
|
|
99375
99519
|
}
|
|
99376
99520
|
util2.joinValues = joinValues;
|
|
99377
|
-
util2.jsonStringifyReplacer = (
|
|
99521
|
+
util2.jsonStringifyReplacer = (_2, value) => {
|
|
99378
99522
|
if (typeof value === "bigint") {
|
|
99379
99523
|
return value.toString();
|
|
99380
99524
|
}
|
|
@@ -104025,13 +104169,13 @@ var init_schemas = __esm({
|
|
|
104025
104169
|
});
|
|
104026
104170
|
|
|
104027
104171
|
// packages/shared/dist/fs/tcignore.js
|
|
104028
|
-
import
|
|
104172
|
+
import fs11 from "node:fs";
|
|
104029
104173
|
import path12 from "node:path";
|
|
104030
104174
|
function findRepoRoot(startDir) {
|
|
104031
104175
|
const start = path12.resolve(startDir);
|
|
104032
104176
|
let dir = start;
|
|
104033
104177
|
for (; ; ) {
|
|
104034
|
-
if (ROOT_MARKERS.some((m) =>
|
|
104178
|
+
if (ROOT_MARKERS.some((m) => fs11.existsSync(path12.join(dir, m))))
|
|
104035
104179
|
return dir;
|
|
104036
104180
|
const parent = path12.dirname(dir);
|
|
104037
104181
|
if (parent === dir)
|
|
@@ -104043,7 +104187,7 @@ function loadTcIgnore(startDir) {
|
|
|
104043
104187
|
const root = findRepoRoot(startDir);
|
|
104044
104188
|
const ig = (0, import_ignore2.default)();
|
|
104045
104189
|
try {
|
|
104046
|
-
ig.add(
|
|
104190
|
+
ig.add(fs11.readFileSync(path12.join(root, ".truecourseignore"), "utf8"));
|
|
104047
104191
|
} catch {
|
|
104048
104192
|
}
|
|
104049
104193
|
return {
|
|
@@ -104072,6 +104216,7 @@ var init_dist7 = __esm({
|
|
|
104072
104216
|
init_types3();
|
|
104073
104217
|
init_schemas();
|
|
104074
104218
|
init_tcignore();
|
|
104219
|
+
init_claude_binary();
|
|
104075
104220
|
}
|
|
104076
104221
|
});
|
|
104077
104222
|
|
|
@@ -104200,7 +104345,7 @@ var require_windows = __commonJS({
|
|
|
104200
104345
|
"node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/windows.js"(exports, module) {
|
|
104201
104346
|
module.exports = isexe;
|
|
104202
104347
|
isexe.sync = sync;
|
|
104203
|
-
var
|
|
104348
|
+
var fs51 = __require("fs");
|
|
104204
104349
|
function checkPathExt(path60, options) {
|
|
104205
104350
|
var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
|
|
104206
104351
|
if (!pathext) {
|
|
@@ -104225,12 +104370,12 @@ var require_windows = __commonJS({
|
|
|
104225
104370
|
return checkPathExt(path60, options);
|
|
104226
104371
|
}
|
|
104227
104372
|
function isexe(path60, options, cb) {
|
|
104228
|
-
|
|
104373
|
+
fs51.stat(path60, function(er, stat) {
|
|
104229
104374
|
cb(er, er ? false : checkStat(stat, path60, options));
|
|
104230
104375
|
});
|
|
104231
104376
|
}
|
|
104232
104377
|
function sync(path60, options) {
|
|
104233
|
-
return checkStat(
|
|
104378
|
+
return checkStat(fs51.statSync(path60), path60, options);
|
|
104234
104379
|
}
|
|
104235
104380
|
}
|
|
104236
104381
|
});
|
|
@@ -104240,14 +104385,14 @@ var require_mode = __commonJS({
|
|
|
104240
104385
|
"node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/mode.js"(exports, module) {
|
|
104241
104386
|
module.exports = isexe;
|
|
104242
104387
|
isexe.sync = sync;
|
|
104243
|
-
var
|
|
104388
|
+
var fs51 = __require("fs");
|
|
104244
104389
|
function isexe(path60, options, cb) {
|
|
104245
|
-
|
|
104390
|
+
fs51.stat(path60, function(er, stat) {
|
|
104246
104391
|
cb(er, er ? false : checkStat(stat, options));
|
|
104247
104392
|
});
|
|
104248
104393
|
}
|
|
104249
104394
|
function sync(path60, options) {
|
|
104250
|
-
return checkStat(
|
|
104395
|
+
return checkStat(fs51.statSync(path60), options);
|
|
104251
104396
|
}
|
|
104252
104397
|
function checkStat(stat, options) {
|
|
104253
104398
|
return stat.isFile() && checkMode(stat, options);
|
|
@@ -104271,7 +104416,7 @@ var require_mode = __commonJS({
|
|
|
104271
104416
|
// node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/index.js
|
|
104272
104417
|
var require_isexe = __commonJS({
|
|
104273
104418
|
"node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/index.js"(exports, module) {
|
|
104274
|
-
var
|
|
104419
|
+
var fs51 = __require("fs");
|
|
104275
104420
|
var core2;
|
|
104276
104421
|
if (process.platform === "win32" || global.TESTING_WINDOWS) {
|
|
104277
104422
|
core2 = require_windows();
|
|
@@ -104535,16 +104680,16 @@ var require_shebang_command = __commonJS({
|
|
|
104535
104680
|
var require_readShebang = __commonJS({
|
|
104536
104681
|
"node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/readShebang.js"(exports, module) {
|
|
104537
104682
|
"use strict";
|
|
104538
|
-
var
|
|
104683
|
+
var fs51 = __require("fs");
|
|
104539
104684
|
var shebangCommand = require_shebang_command();
|
|
104540
104685
|
function readShebang(command) {
|
|
104541
104686
|
const size = 150;
|
|
104542
104687
|
const buffer = Buffer.alloc(size);
|
|
104543
104688
|
let fd;
|
|
104544
104689
|
try {
|
|
104545
|
-
fd =
|
|
104546
|
-
|
|
104547
|
-
|
|
104690
|
+
fd = fs51.openSync(command, "r");
|
|
104691
|
+
fs51.readSync(fd, buffer, 0, size, 0);
|
|
104692
|
+
fs51.closeSync(fd);
|
|
104548
104693
|
} catch (e) {
|
|
104549
104694
|
}
|
|
104550
104695
|
return shebangCommand(buffer.toString());
|
|
@@ -104672,7 +104817,7 @@ var require_cross_spawn = __commonJS({
|
|
|
104672
104817
|
var cp = __require("child_process");
|
|
104673
104818
|
var parse2 = require_parse();
|
|
104674
104819
|
var enoent = require_enoent();
|
|
104675
|
-
function
|
|
104820
|
+
function spawn7(command, args, options) {
|
|
104676
104821
|
const parsed = parse2(command, args, options);
|
|
104677
104822
|
const spawned = cp.spawn(parsed.command, parsed.args, parsed.options);
|
|
104678
104823
|
enoent.hookChildProcess(spawned, parsed);
|
|
@@ -104684,8 +104829,8 @@ var require_cross_spawn = __commonJS({
|
|
|
104684
104829
|
result.error = result.error || enoent.verifyENOENTSync(result.status, parsed);
|
|
104685
104830
|
return result;
|
|
104686
104831
|
}
|
|
104687
|
-
module.exports =
|
|
104688
|
-
module.exports.spawn =
|
|
104832
|
+
module.exports = spawn7;
|
|
104833
|
+
module.exports.spawn = spawn7;
|
|
104689
104834
|
module.exports.sync = spawnSync2;
|
|
104690
104835
|
module.exports._parse = parse2;
|
|
104691
104836
|
module.exports._enoent = enoent;
|
|
@@ -105417,7 +105562,7 @@ function parseStringDef(def, refs) {
|
|
|
105417
105562
|
case "trim":
|
|
105418
105563
|
break;
|
|
105419
105564
|
default:
|
|
105420
|
-
/* @__PURE__ */ ((
|
|
105565
|
+
/* @__PURE__ */ ((_2) => {
|
|
105421
105566
|
})(check);
|
|
105422
105567
|
}
|
|
105423
105568
|
}
|
|
@@ -106265,7 +106410,7 @@ var init_selectParser = __esm({
|
|
|
106265
106410
|
case ZodFirstPartyTypeKind.ZodSymbol:
|
|
106266
106411
|
return void 0;
|
|
106267
106412
|
default:
|
|
106268
|
-
return /* @__PURE__ */ ((
|
|
106413
|
+
return /* @__PURE__ */ ((_2) => void 0)(typeName);
|
|
106269
106414
|
}
|
|
106270
106415
|
};
|
|
106271
106416
|
}
|
|
@@ -106528,7 +106673,7 @@ var require_package = __commonJS({
|
|
|
106528
106673
|
// node_modules/.pnpm/dotenv@16.6.1/node_modules/dotenv/lib/main.js
|
|
106529
106674
|
var require_main = __commonJS({
|
|
106530
106675
|
"node_modules/.pnpm/dotenv@16.6.1/node_modules/dotenv/lib/main.js"(exports, module) {
|
|
106531
|
-
var
|
|
106676
|
+
var fs51 = __require("fs");
|
|
106532
106677
|
var path60 = __require("path");
|
|
106533
106678
|
var os11 = __require("os");
|
|
106534
106679
|
var crypto4 = __require("crypto");
|
|
@@ -106637,7 +106782,7 @@ var require_main = __commonJS({
|
|
|
106637
106782
|
if (options && options.path && options.path.length > 0) {
|
|
106638
106783
|
if (Array.isArray(options.path)) {
|
|
106639
106784
|
for (const filepath of options.path) {
|
|
106640
|
-
if (
|
|
106785
|
+
if (fs51.existsSync(filepath)) {
|
|
106641
106786
|
possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
|
|
106642
106787
|
}
|
|
106643
106788
|
}
|
|
@@ -106647,7 +106792,7 @@ var require_main = __commonJS({
|
|
|
106647
106792
|
} else {
|
|
106648
106793
|
possibleVaultPath = path60.resolve(process.cwd(), ".env.vault");
|
|
106649
106794
|
}
|
|
106650
|
-
if (
|
|
106795
|
+
if (fs51.existsSync(possibleVaultPath)) {
|
|
106651
106796
|
return possibleVaultPath;
|
|
106652
106797
|
}
|
|
106653
106798
|
return null;
|
|
@@ -106696,7 +106841,7 @@ var require_main = __commonJS({
|
|
|
106696
106841
|
const parsedAll = {};
|
|
106697
106842
|
for (const path61 of optionPaths) {
|
|
106698
106843
|
try {
|
|
106699
|
-
const parsed = DotenvModule.parse(
|
|
106844
|
+
const parsed = DotenvModule.parse(fs51.readFileSync(path61, { encoding }));
|
|
106700
106845
|
DotenvModule.populate(parsedAll, parsed, options);
|
|
106701
106846
|
} catch (e) {
|
|
106702
106847
|
if (debug2) {
|
|
@@ -106818,11 +106963,11 @@ var require_main = __commonJS({
|
|
|
106818
106963
|
// packages/core/dist/config/env.js
|
|
106819
106964
|
import path13 from "node:path";
|
|
106820
106965
|
import os3 from "node:os";
|
|
106821
|
-
import
|
|
106966
|
+
import fs12 from "node:fs";
|
|
106822
106967
|
function findRepoRootEnv() {
|
|
106823
106968
|
let dir = process.cwd();
|
|
106824
106969
|
for (let i = 0; i < 10; i++) {
|
|
106825
|
-
if (
|
|
106970
|
+
if (fs12.existsSync(path13.join(dir, "pnpm-workspace.yaml"))) {
|
|
106826
106971
|
return path13.join(dir, ".env");
|
|
106827
106972
|
}
|
|
106828
106973
|
const parent = path13.dirname(dir);
|
|
@@ -106859,10 +107004,13 @@ var init_config2 = __esm({
|
|
|
106859
107004
|
"packages/core/dist/config/index.js"() {
|
|
106860
107005
|
"use strict";
|
|
106861
107006
|
init_env();
|
|
107007
|
+
init_dist7();
|
|
106862
107008
|
config = {
|
|
106863
107009
|
llmProvider: "claude-code",
|
|
106864
|
-
// Claude Code CLI provider settings
|
|
106865
|
-
|
|
107010
|
+
// Claude Code CLI provider settings. Binary resolution is centralized in
|
|
107011
|
+
// `resolveClaudeBinary` (@truecourse/shared) so every command, the LLM
|
|
107012
|
+
// provider, and the extraction runners agree on the same target.
|
|
107013
|
+
claudeCodeBinary: resolveClaudeBinary(),
|
|
106866
107014
|
claudeCodeModel: process.env.CLAUDE_CODE_MODEL || "",
|
|
106867
107015
|
claudeCodeTimeoutMs: parseInt(process.env.CLAUDE_CODE_TIMEOUT_MS || "120000", 10),
|
|
106868
107016
|
claudeCodeMaxRetries: parseInt(process.env.CLAUDE_CODE_MAX_RETRIES || "2", 10),
|
|
@@ -109100,7 +109248,7 @@ var init_violation_lifecycle_service = __esm({
|
|
|
109100
109248
|
|
|
109101
109249
|
// packages/core/dist/services/violation-pipeline.service.js
|
|
109102
109250
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
109103
|
-
import
|
|
109251
|
+
import fs13 from "node:fs";
|
|
109104
109252
|
import path14 from "node:path";
|
|
109105
109253
|
function throwIfAborted(signal) {
|
|
109106
109254
|
if (signal?.aborted)
|
|
@@ -109218,9 +109366,9 @@ async function runViolationPipeline(input) {
|
|
|
109218
109366
|
if (!lang)
|
|
109219
109367
|
continue;
|
|
109220
109368
|
const absPath = resolve9 ? path14.resolve(repoPath, filePath) : path14.isAbsolute(filePath) ? filePath : path14.join(repoPath, filePath);
|
|
109221
|
-
if (!
|
|
109369
|
+
if (!fs13.existsSync(absPath))
|
|
109222
109370
|
continue;
|
|
109223
|
-
const content =
|
|
109371
|
+
const content = fs13.readFileSync(absPath, "utf-8");
|
|
109224
109372
|
const lineCount = content.split("\n").length;
|
|
109225
109373
|
fileContents.set(changedFileSet ? absPath : filePath, { content, lineCount });
|
|
109226
109374
|
} catch {
|
|
@@ -109359,10 +109507,10 @@ async function runViolationPipeline(input) {
|
|
|
109359
109507
|
log.info(`[Pipeline] Code scan: ${allCodeViolations.length} violations from ${filesToScan.length} files (${enabledCodeRules.length} det rules, ${enabledLlmCodeRules.length} LLM rules)`);
|
|
109360
109508
|
if (enabledCodeRules.some((r) => r.key === "bugs/deterministic/invalid-pyproject-toml")) {
|
|
109361
109509
|
const pyprojectPath = path14.join(repoPath, "pyproject.toml");
|
|
109362
|
-
if (
|
|
109510
|
+
if (fs13.existsSync(pyprojectPath)) {
|
|
109363
109511
|
try {
|
|
109364
109512
|
const { checkPyprojectToml: checkPyprojectToml2 } = await Promise.resolve().then(() => (init_dist6(), dist_exports2));
|
|
109365
|
-
const content =
|
|
109513
|
+
const content = fs13.readFileSync(pyprojectPath, "utf-8");
|
|
109366
109514
|
const tomlViolations = checkPyprojectToml2(pyprojectPath, content);
|
|
109367
109515
|
allCodeViolations.push(...tomlViolations);
|
|
109368
109516
|
} catch {
|
|
@@ -110269,7 +110417,6 @@ var init_usage_service = __esm({
|
|
|
110269
110417
|
|
|
110270
110418
|
// packages/core/dist/commands/analyze-core.js
|
|
110271
110419
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
110272
|
-
import path15 from "node:path";
|
|
110273
110420
|
async function analyzeCore(project, options) {
|
|
110274
110421
|
try {
|
|
110275
110422
|
acquireAnalyzeLock(project.path);
|
|
@@ -110311,32 +110458,20 @@ async function analyzeCore(project, options) {
|
|
|
110311
110458
|
const start = Date.now();
|
|
110312
110459
|
const effectiveCategories = options.enabledCategoriesOverride?.length ? options.enabledCategoriesOverride : projectConfig.enabledCategories ?? void 0;
|
|
110313
110460
|
const effectiveLlmRules = projectConfig.enableLlmRules ?? options.enableLlmRulesOverride ?? true;
|
|
110314
|
-
|
|
110315
|
-
|
|
110316
|
-
|
|
110317
|
-
|
|
110318
|
-
|
|
110319
|
-
|
|
110320
|
-
|
|
110321
|
-
|
|
110322
|
-
|
|
110323
|
-
|
|
110324
|
-
|
|
110325
|
-
|
|
110326
|
-
|
|
110327
|
-
|
|
110328
|
-
"--include-untracked",
|
|
110329
|
-
"-m",
|
|
110330
|
-
"truecourse-analysis-stash"
|
|
110331
|
-
]);
|
|
110332
|
-
didStash = !stashResult.includes("No local changes");
|
|
110333
|
-
}
|
|
110334
|
-
}
|
|
110335
|
-
} catch (error) {
|
|
110336
|
-
log.warn(`[Analyzer] Failed to stash changes, analyzing current state: ${error instanceof Error ? error.message : String(error)}`);
|
|
110337
|
-
}
|
|
110338
|
-
}
|
|
110339
|
-
try {
|
|
110461
|
+
return await runWithStash(project.path, {
|
|
110462
|
+
skipStash: isDiff || skipGit || (options.skipStash ?? false),
|
|
110463
|
+
message: "truecourse-analysis-stash",
|
|
110464
|
+
onStashStart: () => {
|
|
110465
|
+
options.tracker?.detail("parse", "Stashing pending changes...");
|
|
110466
|
+
options.onProgress?.({ detail: "Stashing pending changes to analyze committed state..." });
|
|
110467
|
+
},
|
|
110468
|
+
onRestoreStart: () => {
|
|
110469
|
+
options.tracker?.detail("parse", "Restoring pending changes...");
|
|
110470
|
+
options.onProgress?.({ detail: "Restoring pending changes..." });
|
|
110471
|
+
},
|
|
110472
|
+
onStashError: (error) => log.warn(`[Analyzer] Failed to stash changes, analyzing current state: ${error.message}`),
|
|
110473
|
+
onRestoreError: (error) => log.error(`[Analyzer] Failed to restore stashed changes. Run "git stash pop" manually. ${error.message}`)
|
|
110474
|
+
}, async () => {
|
|
110340
110475
|
options.tracker?.start("parse", isDiff ? "Analyzing working tree..." : "Starting analysis...");
|
|
110341
110476
|
const result = await runAnalysis(project.path, branch ?? void 0, (progress) => {
|
|
110342
110477
|
options.tracker?.detail("parse", progress.detail ?? "Analyzing...");
|
|
@@ -110448,17 +110583,7 @@ async function analyzeCore(project, options) {
|
|
|
110448
110583
|
previousAnalysisId,
|
|
110449
110584
|
analysisResult: result
|
|
110450
110585
|
};
|
|
110451
|
-
}
|
|
110452
|
-
if (didStash && stashGit) {
|
|
110453
|
-
options.tracker?.detail("parse", "Restoring pending changes...");
|
|
110454
|
-
options.onProgress?.({ detail: "Restoring pending changes..." });
|
|
110455
|
-
try {
|
|
110456
|
-
await stashGit.stash(["pop"]);
|
|
110457
|
-
} catch (error) {
|
|
110458
|
-
log.error(`[Analyzer] Failed to restore stashed changes. Run "git stash pop" manually. ${error instanceof Error ? error.message : String(error)}`);
|
|
110459
|
-
}
|
|
110460
|
-
}
|
|
110461
|
-
}
|
|
110586
|
+
});
|
|
110462
110587
|
} finally {
|
|
110463
110588
|
releaseAnalyzeLock(project.path);
|
|
110464
110589
|
}
|
|
@@ -110494,7 +110619,7 @@ var init_analyze_core = __esm({
|
|
|
110494
110619
|
});
|
|
110495
110620
|
|
|
110496
110621
|
// packages/core/dist/commands/analyze-persist.js
|
|
110497
|
-
import
|
|
110622
|
+
import path15 from "node:path";
|
|
110498
110623
|
function persistFullAnalysis(project, core2, startedAt) {
|
|
110499
110624
|
const filename = buildAnalysisFilename(core2.analysisId, core2.now);
|
|
110500
110625
|
const snapshot = {
|
|
@@ -110622,8 +110747,8 @@ function buildDiffSnapshot(repoPath, core2, baseline) {
|
|
|
110622
110747
|
const newViolations = pipelineResult.added.map(denormalize);
|
|
110623
110748
|
const latestById = new Map(baseline.violations.map((v) => [v.id, v]));
|
|
110624
110749
|
const resolvedViolations = pipelineResult.resolvedRefs.map((r) => latestById.get(r.id)).filter((v) => !!v);
|
|
110625
|
-
const changedAbs = new Set(changedFiles.map((c2) =>
|
|
110626
|
-
const matchesChanged = (p2) => !!p2 && (changedAbs.has(p2) || changedAbs.has(
|
|
110750
|
+
const changedAbs = new Set(changedFiles.map((c2) => path15.resolve(repoPath, c2.path)));
|
|
110751
|
+
const matchesChanged = (p2) => !!p2 && (changedAbs.has(p2) || changedAbs.has(path15.resolve(repoPath, p2)));
|
|
110627
110752
|
const affectedModules = graph.modules.filter((m) => matchesChanged(m.filePath));
|
|
110628
110753
|
const affectedModuleIdSet = new Set(affectedModules.map((m) => m.id));
|
|
110629
110754
|
const serviceNameById = new Map(graph.services.map((s) => [s.id, s.name]));
|
|
@@ -119990,7 +120115,7 @@ var require_shams = __commonJS({
|
|
|
119990
120115
|
}
|
|
119991
120116
|
var symVal = 42;
|
|
119992
120117
|
obj[sym] = symVal;
|
|
119993
|
-
for (var
|
|
120118
|
+
for (var _2 in obj) {
|
|
119994
120119
|
return false;
|
|
119995
120120
|
}
|
|
119996
120121
|
if (typeof Object.keys === "function" && Object.keys(obj).length !== 0) {
|
|
@@ -120658,7 +120783,7 @@ var require_form_data = __commonJS({
|
|
|
120658
120783
|
var http = __require("http");
|
|
120659
120784
|
var https = __require("https");
|
|
120660
120785
|
var parseUrl = __require("url").parse;
|
|
120661
|
-
var
|
|
120786
|
+
var fs51 = __require("fs");
|
|
120662
120787
|
var Stream = __require("stream").Stream;
|
|
120663
120788
|
var crypto4 = __require("crypto");
|
|
120664
120789
|
var mime = require_mime_types();
|
|
@@ -120725,7 +120850,7 @@ var require_form_data = __commonJS({
|
|
|
120725
120850
|
if (value.end != void 0 && value.end != Infinity && value.start != void 0) {
|
|
120726
120851
|
callback(null, value.end + 1 - (value.start ? value.start : 0));
|
|
120727
120852
|
} else {
|
|
120728
|
-
|
|
120853
|
+
fs51.stat(value.path, function(err, stat) {
|
|
120729
120854
|
if (err) {
|
|
120730
120855
|
callback(err);
|
|
120731
120856
|
return;
|
|
@@ -121276,7 +121401,7 @@ var require_node2 = __commonJS({
|
|
|
121276
121401
|
exports.inspectOpts = Object.keys(process.env).filter(function(key2) {
|
|
121277
121402
|
return /^debug_/i.test(key2);
|
|
121278
121403
|
}).reduce(function(obj, key2) {
|
|
121279
|
-
var prop = key2.substring(6).toLowerCase().replace(/_([a-z])/g, function(
|
|
121404
|
+
var prop = key2.substring(6).toLowerCase().replace(/_([a-z])/g, function(_2, k) {
|
|
121280
121405
|
return k.toUpperCase();
|
|
121281
121406
|
});
|
|
121282
121407
|
var val = process.env[key2];
|
|
@@ -121343,8 +121468,8 @@ var require_node2 = __commonJS({
|
|
|
121343
121468
|
}
|
|
121344
121469
|
break;
|
|
121345
121470
|
case "FILE":
|
|
121346
|
-
var
|
|
121347
|
-
stream2 = new
|
|
121471
|
+
var fs51 = __require("fs");
|
|
121472
|
+
stream2 = new fs51.SyncWriteStream(fd2, { autoClose: false });
|
|
121348
121473
|
stream2._type = "fs";
|
|
121349
121474
|
break;
|
|
121350
121475
|
case "PIPE":
|
|
@@ -122915,11 +123040,11 @@ var require_axios = __commonJS({
|
|
|
122915
123040
|
function normalizeHeader(header) {
|
|
122916
123041
|
return header && String(header).trim().toLowerCase();
|
|
122917
123042
|
}
|
|
122918
|
-
function
|
|
123043
|
+
function normalizeValue2(value) {
|
|
122919
123044
|
if (value === false || value == null) {
|
|
122920
123045
|
return value;
|
|
122921
123046
|
}
|
|
122922
|
-
return utils$1.isArray(value) ? value.map(
|
|
123047
|
+
return utils$1.isArray(value) ? value.map(normalizeValue2) : String(value).replace(/[\r\n]+$/, "");
|
|
122923
123048
|
}
|
|
122924
123049
|
function parseTokens(str2) {
|
|
122925
123050
|
const tokens = /* @__PURE__ */ Object.create(null);
|
|
@@ -122975,7 +123100,7 @@ var require_axios = __commonJS({
|
|
|
122975
123100
|
}
|
|
122976
123101
|
const key2 = utils$1.findKey(self2, lHeader);
|
|
122977
123102
|
if (!key2 || self2[key2] === void 0 || _rewrite === true || _rewrite === void 0 && self2[key2] !== false) {
|
|
122978
|
-
self2[key2 || _header] =
|
|
123103
|
+
self2[key2 || _header] = normalizeValue2(_value);
|
|
122979
123104
|
}
|
|
122980
123105
|
}
|
|
122981
123106
|
const setHeaders = (headers, _rewrite) => utils$1.forEach(headers, (_value, _header) => setHeader(_value, _header, _rewrite));
|
|
@@ -123066,7 +123191,7 @@ var require_axios = __commonJS({
|
|
|
123066
123191
|
utils$1.forEach(this, (value, header) => {
|
|
123067
123192
|
const key2 = utils$1.findKey(headers, header);
|
|
123068
123193
|
if (key2) {
|
|
123069
|
-
self2[key2] =
|
|
123194
|
+
self2[key2] = normalizeValue2(value);
|
|
123070
123195
|
delete self2[header];
|
|
123071
123196
|
return;
|
|
123072
123197
|
}
|
|
@@ -123074,7 +123199,7 @@ var require_axios = __commonJS({
|
|
|
123074
123199
|
if (normalized !== header) {
|
|
123075
123200
|
delete self2[header];
|
|
123076
123201
|
}
|
|
123077
|
-
self2[normalized] =
|
|
123202
|
+
self2[normalized] = normalizeValue2(value);
|
|
123078
123203
|
headers[normalized] = true;
|
|
123079
123204
|
});
|
|
123080
123205
|
return this;
|
|
@@ -125460,7 +125585,7 @@ var require_axios = __commonJS({
|
|
|
125460
125585
|
// node_modules/.pnpm/posthog-node@4.18.0/node_modules/posthog-node/lib/node/index.mjs
|
|
125461
125586
|
import { posix, dirname as dirname8, sep as sep2 } from "path";
|
|
125462
125587
|
import { createReadStream } from "node:fs";
|
|
125463
|
-
import { createInterface } from "node:readline";
|
|
125588
|
+
import { createInterface as createInterface2 } from "node:readline";
|
|
125464
125589
|
function createEventProcessor(_posthog, {
|
|
125465
125590
|
organization,
|
|
125466
125591
|
projectId,
|
|
@@ -125880,7 +126005,7 @@ async function addSourceContext(frames) {
|
|
|
125880
126005
|
function getContextLinesFromFile(path60, ranges, output) {
|
|
125881
126006
|
return new Promise((resolve9) => {
|
|
125882
126007
|
const stream = createReadStream(path60);
|
|
125883
|
-
const lineReaded =
|
|
126008
|
+
const lineReaded = createInterface2({
|
|
125884
126009
|
input: stream
|
|
125885
126010
|
});
|
|
125886
126011
|
function destroyStreamAndResolve() {
|
|
@@ -128247,7 +128372,7 @@ var init_node = __esm({
|
|
|
128247
128372
|
}
|
|
128248
128373
|
};
|
|
128249
128374
|
return Promise.race([
|
|
128250
|
-
new Promise((
|
|
128375
|
+
new Promise((_2, reject) => {
|
|
128251
128376
|
safeSetTimeout(() => {
|
|
128252
128377
|
this.logMsgIfDebug(() => console.error("Timed out while shutting down PostHog"));
|
|
128253
128378
|
hasTimedOut = true;
|
|
@@ -129225,18 +129350,18 @@ var init_node = __esm({
|
|
|
129225
129350
|
});
|
|
129226
129351
|
|
|
129227
129352
|
// packages/core/dist/services/telemetry.service.js
|
|
129228
|
-
import
|
|
129229
|
-
import
|
|
129353
|
+
import fs14 from "node:fs";
|
|
129354
|
+
import path16 from "node:path";
|
|
129230
129355
|
import os4 from "node:os";
|
|
129231
129356
|
import crypto from "node:crypto";
|
|
129232
129357
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
129233
129358
|
function getTelemetryConfigPath() {
|
|
129234
|
-
return
|
|
129359
|
+
return path16.join(os4.homedir(), ".truecourse", "telemetry.json");
|
|
129235
129360
|
}
|
|
129236
129361
|
function readTelemetryConfig() {
|
|
129237
129362
|
const configPath = getTelemetryConfigPath();
|
|
129238
129363
|
try {
|
|
129239
|
-
const raw =
|
|
129364
|
+
const raw = fs14.readFileSync(configPath, "utf-8");
|
|
129240
129365
|
const parsed = JSON.parse(raw);
|
|
129241
129366
|
const config2 = { ...DEFAULT_CONFIG2, ...parsed };
|
|
129242
129367
|
if (!config2.anonymousId) {
|
|
@@ -129256,17 +129381,17 @@ function readTelemetryConfig() {
|
|
|
129256
129381
|
}
|
|
129257
129382
|
function writeTelemetryConfig(partial) {
|
|
129258
129383
|
const configPath = getTelemetryConfigPath();
|
|
129259
|
-
const dir =
|
|
129260
|
-
|
|
129384
|
+
const dir = path16.dirname(configPath);
|
|
129385
|
+
fs14.mkdirSync(dir, { recursive: true });
|
|
129261
129386
|
let current;
|
|
129262
129387
|
try {
|
|
129263
|
-
const raw =
|
|
129388
|
+
const raw = fs14.readFileSync(configPath, "utf-8");
|
|
129264
129389
|
current = { ...DEFAULT_CONFIG2, ...JSON.parse(raw) };
|
|
129265
129390
|
} catch {
|
|
129266
129391
|
current = { ...DEFAULT_CONFIG2 };
|
|
129267
129392
|
}
|
|
129268
129393
|
const merged = { ...current, ...partial };
|
|
129269
|
-
|
|
129394
|
+
fs14.writeFileSync(configPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
129270
129395
|
}
|
|
129271
129396
|
function isTelemetryEnabled() {
|
|
129272
129397
|
if (!POSTHOG_API_KEY)
|
|
@@ -129312,7 +129437,7 @@ function detectLanguages(result) {
|
|
|
129312
129437
|
const languages = /* @__PURE__ */ new Set();
|
|
129313
129438
|
for (const service of result.services) {
|
|
129314
129439
|
for (const filePath of service.files) {
|
|
129315
|
-
const ext2 =
|
|
129440
|
+
const ext2 = path16.extname(filePath).toLowerCase();
|
|
129316
129441
|
const lang = EXTENSION_TO_LANGUAGE[ext2];
|
|
129317
129442
|
if (lang)
|
|
129318
129443
|
languages.add(lang);
|
|
@@ -129324,13 +129449,13 @@ function readToolVersion() {
|
|
|
129324
129449
|
if (cachedVersion)
|
|
129325
129450
|
return cachedVersion;
|
|
129326
129451
|
if (true) {
|
|
129327
|
-
cachedVersion = "0.6.
|
|
129452
|
+
cachedVersion = "0.6.6-next.0";
|
|
129328
129453
|
return cachedVersion;
|
|
129329
129454
|
}
|
|
129330
129455
|
try {
|
|
129331
129456
|
const here = fileURLToPath5(import.meta.url);
|
|
129332
|
-
const pkgPath =
|
|
129333
|
-
const pkg = JSON.parse(
|
|
129457
|
+
const pkgPath = path16.resolve(path16.dirname(here), "..", "..", "package.json");
|
|
129458
|
+
const pkg = JSON.parse(fs14.readFileSync(pkgPath, "utf-8"));
|
|
129334
129459
|
cachedVersion = String(pkg.version ?? "0.0.0");
|
|
129335
129460
|
} catch {
|
|
129336
129461
|
cachedVersion = "0.0.0";
|
|
@@ -131224,7 +131349,7 @@ var init_forbidden_artifact = __esm({
|
|
|
131224
131349
|
// packages/contract-verifier/dist/resolver/lifters/named-constant.js
|
|
131225
131350
|
function liftNamedConstant(body) {
|
|
131226
131351
|
let type2 = "string";
|
|
131227
|
-
let expectedValue =
|
|
131352
|
+
let expectedValue = void 0;
|
|
131228
131353
|
for (const stmt of body) {
|
|
131229
131354
|
const h = stmt.head;
|
|
131230
131355
|
if (h.length === 0 || h[0].kind !== "ident")
|
|
@@ -131705,6 +131830,351 @@ var init_resolver = __esm({
|
|
|
131705
131830
|
}
|
|
131706
131831
|
});
|
|
131707
131832
|
|
|
131833
|
+
// packages/contract-verifier/dist/extractor/handler-facts.js
|
|
131834
|
+
function extractHandlerFacts(body, source) {
|
|
131835
|
+
return {
|
|
131836
|
+
emission: extractEmission(body, source),
|
|
131837
|
+
ownershipCheckCandidates: extractOwnershipCandidates(body, source)
|
|
131838
|
+
};
|
|
131839
|
+
}
|
|
131840
|
+
function emptyHandlerFacts() {
|
|
131841
|
+
return {
|
|
131842
|
+
emission: {
|
|
131843
|
+
staticEvents: /* @__PURE__ */ new Set(),
|
|
131844
|
+
hasDynamicEmit: false,
|
|
131845
|
+
failureEmitSites: [],
|
|
131846
|
+
branchEmits: /* @__PURE__ */ new Map()
|
|
131847
|
+
},
|
|
131848
|
+
ownershipCheckCandidates: []
|
|
131849
|
+
};
|
|
131850
|
+
}
|
|
131851
|
+
function extractEmission(body, source) {
|
|
131852
|
+
const callMap = collectEmitCalls(body, source);
|
|
131853
|
+
const failureEmitSites = [];
|
|
131854
|
+
for (const [event, calls] of callMap) {
|
|
131855
|
+
for (const call of calls) {
|
|
131856
|
+
if (emitIsInFailureBlock(call, source)) {
|
|
131857
|
+
failureEmitSites.push({
|
|
131858
|
+
event,
|
|
131859
|
+
lineStart: call.startPosition.row + 1,
|
|
131860
|
+
lineEnd: call.endPosition.row + 1
|
|
131861
|
+
});
|
|
131862
|
+
}
|
|
131863
|
+
}
|
|
131864
|
+
}
|
|
131865
|
+
return {
|
|
131866
|
+
staticEvents: new Set(callMap.keys()),
|
|
131867
|
+
hasDynamicEmit: handlerHasDynamicEmit(body, source),
|
|
131868
|
+
failureEmitSites,
|
|
131869
|
+
branchEmits: collectBranchEmits(body, source)
|
|
131870
|
+
};
|
|
131871
|
+
}
|
|
131872
|
+
function isCall(n) {
|
|
131873
|
+
return n.type === "call_expression" || n.type === "call";
|
|
131874
|
+
}
|
|
131875
|
+
function isMember(n) {
|
|
131876
|
+
return n?.type === "member_expression" || n?.type === "attribute";
|
|
131877
|
+
}
|
|
131878
|
+
function memberProp(n, source) {
|
|
131879
|
+
const p2 = n.childForFieldName("property") ?? n.childForFieldName("attribute");
|
|
131880
|
+
return p2 ? source.slice(p2.startIndex, p2.endIndex) : "";
|
|
131881
|
+
}
|
|
131882
|
+
function isBlock(n) {
|
|
131883
|
+
return n.type === "statement_block" || n.type === "block";
|
|
131884
|
+
}
|
|
131885
|
+
function isFnBoundary(n) {
|
|
131886
|
+
return n.type === "function_declaration" || n.type === "arrow_function" || n.type === "function_expression" || n.type === "function_definition";
|
|
131887
|
+
}
|
|
131888
|
+
function isControlFlow(n) {
|
|
131889
|
+
return /^(if_statement|switch_statement|try_statement|for_statement|for_in_statement|while_statement|do_statement)$/.test(n.type);
|
|
131890
|
+
}
|
|
131891
|
+
function strVal(n, source) {
|
|
131892
|
+
if (n.type !== "string")
|
|
131893
|
+
return null;
|
|
131894
|
+
const frag = n.namedChildren.find((c2) => c2.type === "string_fragment" || c2.type === "string_content");
|
|
131895
|
+
return frag ? source.slice(frag.startIndex, frag.endIndex) : null;
|
|
131896
|
+
}
|
|
131897
|
+
function isEmitFn(fn, source) {
|
|
131898
|
+
if (isMember(fn))
|
|
131899
|
+
return memberProp(fn, source) === "emit";
|
|
131900
|
+
if (fn.type === "identifier")
|
|
131901
|
+
return /^emit/i.test(source.slice(fn.startIndex, fn.endIndex));
|
|
131902
|
+
return false;
|
|
131903
|
+
}
|
|
131904
|
+
function collectEmitCalls(body, source) {
|
|
131905
|
+
const out = /* @__PURE__ */ new Map();
|
|
131906
|
+
const visit = (node2) => {
|
|
131907
|
+
if (isCall(node2)) {
|
|
131908
|
+
const fn = node2.childForFieldName("function");
|
|
131909
|
+
const args = node2.childForFieldName("arguments");
|
|
131910
|
+
if (fn && args && isEmitFn(fn, source)) {
|
|
131911
|
+
const first2 = args.namedChild(0);
|
|
131912
|
+
const eventName = first2 ? strVal(first2, source) : null;
|
|
131913
|
+
if (eventName !== null) {
|
|
131914
|
+
const arr = out.get(eventName) ?? [];
|
|
131915
|
+
arr.push(node2);
|
|
131916
|
+
out.set(eventName, arr);
|
|
131917
|
+
}
|
|
131918
|
+
}
|
|
131919
|
+
}
|
|
131920
|
+
for (const child of node2.namedChildren)
|
|
131921
|
+
visit(child);
|
|
131922
|
+
};
|
|
131923
|
+
visit(body);
|
|
131924
|
+
return out;
|
|
131925
|
+
}
|
|
131926
|
+
function handlerHasDynamicEmit(body, source) {
|
|
131927
|
+
let dynamic = false;
|
|
131928
|
+
const visit = (node2) => {
|
|
131929
|
+
if (dynamic)
|
|
131930
|
+
return;
|
|
131931
|
+
if (isCall(node2)) {
|
|
131932
|
+
const fn = node2.childForFieldName("function");
|
|
131933
|
+
const args = node2.childForFieldName("arguments");
|
|
131934
|
+
if (fn && args && isEmitFn(fn, source)) {
|
|
131935
|
+
const first2 = args.namedChild(0);
|
|
131936
|
+
if (first2 && first2.type !== "string") {
|
|
131937
|
+
dynamic = true;
|
|
131938
|
+
return;
|
|
131939
|
+
}
|
|
131940
|
+
}
|
|
131941
|
+
}
|
|
131942
|
+
for (const child of node2.namedChildren) {
|
|
131943
|
+
visit(child);
|
|
131944
|
+
if (dynamic)
|
|
131945
|
+
return;
|
|
131946
|
+
}
|
|
131947
|
+
};
|
|
131948
|
+
visit(body);
|
|
131949
|
+
return dynamic;
|
|
131950
|
+
}
|
|
131951
|
+
function emitIsInFailureBlock(emitCall, source) {
|
|
131952
|
+
let cur = emitCall.parent;
|
|
131953
|
+
while (cur) {
|
|
131954
|
+
if (isBlock(cur))
|
|
131955
|
+
return blockContainsFailureStatusShallow(cur, source);
|
|
131956
|
+
if (isFnBoundary(cur))
|
|
131957
|
+
break;
|
|
131958
|
+
cur = cur.parent;
|
|
131959
|
+
}
|
|
131960
|
+
return false;
|
|
131961
|
+
}
|
|
131962
|
+
function blockContainsFailureStatusShallow(block, source) {
|
|
131963
|
+
for (const stmt of block.namedChildren) {
|
|
131964
|
+
if (containsTopLevelFailureStatus(stmt, source))
|
|
131965
|
+
return true;
|
|
131966
|
+
}
|
|
131967
|
+
return false;
|
|
131968
|
+
}
|
|
131969
|
+
function containsTopLevelFailureStatus(stmt, source) {
|
|
131970
|
+
if (isControlFlow(stmt))
|
|
131971
|
+
return false;
|
|
131972
|
+
let found = false;
|
|
131973
|
+
const visit = (node2) => {
|
|
131974
|
+
if (found)
|
|
131975
|
+
return;
|
|
131976
|
+
if (isControlFlow(node2))
|
|
131977
|
+
return;
|
|
131978
|
+
if (isCall(node2) && callEmitsFailureStatus(node2, source)) {
|
|
131979
|
+
found = true;
|
|
131980
|
+
return;
|
|
131981
|
+
}
|
|
131982
|
+
for (const child of node2.namedChildren) {
|
|
131983
|
+
visit(child);
|
|
131984
|
+
if (found)
|
|
131985
|
+
return;
|
|
131986
|
+
}
|
|
131987
|
+
};
|
|
131988
|
+
visit(stmt);
|
|
131989
|
+
return found;
|
|
131990
|
+
}
|
|
131991
|
+
function callEmitsFailureStatus(call, source) {
|
|
131992
|
+
const fn = call.childForFieldName("function");
|
|
131993
|
+
if (!fn)
|
|
131994
|
+
return false;
|
|
131995
|
+
const args = call.childForFieldName("arguments");
|
|
131996
|
+
if (!args)
|
|
131997
|
+
return false;
|
|
131998
|
+
if (isMember(fn) && memberProp(fn, source) === "status") {
|
|
131999
|
+
const arg = args.namedChild(0);
|
|
132000
|
+
return !!arg && arg.type === "number" && /^[45]\d{2}$/.test(source.slice(arg.startIndex, arg.endIndex));
|
|
132001
|
+
}
|
|
132002
|
+
const fnName = fn.type === "identifier" ? source.slice(fn.startIndex, fn.endIndex) : isMember(fn) ? memberProp(fn, source) : "";
|
|
132003
|
+
if (fnName === "JSONResponse" || fnName === "HTTPException" || fnName === "Response") {
|
|
132004
|
+
for (let i = 0; i < args.namedChildCount; i++) {
|
|
132005
|
+
const a = args.namedChild(i);
|
|
132006
|
+
if (a?.type === "keyword_argument") {
|
|
132007
|
+
const name = a.childForFieldName("name");
|
|
132008
|
+
const value = a.childForFieldName("value");
|
|
132009
|
+
if (name && value && source.slice(name.startIndex, name.endIndex) === "status_code" && value.type === "integer" && /^[45]\d{2}$/.test(source.slice(value.startIndex, value.endIndex))) {
|
|
132010
|
+
return true;
|
|
132011
|
+
}
|
|
132012
|
+
}
|
|
132013
|
+
}
|
|
132014
|
+
const first2 = args.namedChild(0);
|
|
132015
|
+
if (first2?.type === "integer" && /^[45]\d{2}$/.test(source.slice(first2.startIndex, first2.endIndex)))
|
|
132016
|
+
return true;
|
|
132017
|
+
}
|
|
132018
|
+
return false;
|
|
132019
|
+
}
|
|
132020
|
+
function collectBranchEmits(body, source) {
|
|
132021
|
+
const out = /* @__PURE__ */ new Map();
|
|
132022
|
+
const visit = (node2) => {
|
|
132023
|
+
if (node2.type === "if_statement") {
|
|
132024
|
+
const cond = node2.childForFieldName("condition");
|
|
132025
|
+
const consequent = node2.childForFieldName("consequence");
|
|
132026
|
+
const alternative = node2.childForFieldName("alternative");
|
|
132027
|
+
const literal = cond ? conditionLiteral(cond, source) : null;
|
|
132028
|
+
if (literal !== null && consequent && !out.has(literal)) {
|
|
132029
|
+
out.set(literal, {
|
|
132030
|
+
consequentEmits: blockHasAnyEmit(consequent, source),
|
|
132031
|
+
alternativeEmits: alternative ? blockHasAnyEmit(alternative, source) : false
|
|
132032
|
+
});
|
|
132033
|
+
}
|
|
132034
|
+
}
|
|
132035
|
+
for (const child of node2.namedChildren)
|
|
132036
|
+
visit(child);
|
|
132037
|
+
};
|
|
132038
|
+
visit(body);
|
|
132039
|
+
return out;
|
|
132040
|
+
}
|
|
132041
|
+
function conditionLiteral(node2, source) {
|
|
132042
|
+
const u2 = unwrapParens(node2);
|
|
132043
|
+
if (u2.type === "binary_expression") {
|
|
132044
|
+
const opNode = u2.childForFieldName("operator");
|
|
132045
|
+
const op = opNode ? source.slice(opNode.startIndex, opNode.endIndex) : "";
|
|
132046
|
+
if (op !== "===" && op !== "==")
|
|
132047
|
+
return null;
|
|
132048
|
+
return litText(u2.childForFieldName("left"), source) ?? litText(u2.childForFieldName("right"), source);
|
|
132049
|
+
}
|
|
132050
|
+
if (u2.type === "comparison_operator") {
|
|
132051
|
+
const a = u2.namedChild(0);
|
|
132052
|
+
const b = u2.namedChild(1);
|
|
132053
|
+
if (!a || !b || source.slice(a.endIndex, b.startIndex).trim() !== "==")
|
|
132054
|
+
return null;
|
|
132055
|
+
return litText(a, source) ?? litText(b, source);
|
|
132056
|
+
}
|
|
132057
|
+
return null;
|
|
132058
|
+
}
|
|
132059
|
+
function litText(node2, source) {
|
|
132060
|
+
return node2 && node2.type === "string" ? strVal(node2, source) : null;
|
|
132061
|
+
}
|
|
132062
|
+
function unwrapParens(node2) {
|
|
132063
|
+
let cur = node2;
|
|
132064
|
+
while (cur.type === "parenthesized_expression") {
|
|
132065
|
+
const child = cur.namedChildren[0];
|
|
132066
|
+
if (!child)
|
|
132067
|
+
break;
|
|
132068
|
+
cur = child;
|
|
132069
|
+
}
|
|
132070
|
+
return cur;
|
|
132071
|
+
}
|
|
132072
|
+
function blockHasAnyEmit(block, source) {
|
|
132073
|
+
let found = false;
|
|
132074
|
+
const visit = (node2) => {
|
|
132075
|
+
if (found)
|
|
132076
|
+
return;
|
|
132077
|
+
if (isCall(node2)) {
|
|
132078
|
+
const fn = node2.childForFieldName("function");
|
|
132079
|
+
if (fn && isEmitFn(fn, source)) {
|
|
132080
|
+
found = true;
|
|
132081
|
+
return;
|
|
132082
|
+
}
|
|
132083
|
+
}
|
|
132084
|
+
for (const child of node2.namedChildren) {
|
|
132085
|
+
visit(child);
|
|
132086
|
+
if (found)
|
|
132087
|
+
return;
|
|
132088
|
+
}
|
|
132089
|
+
};
|
|
132090
|
+
visit(block);
|
|
132091
|
+
return found;
|
|
132092
|
+
}
|
|
132093
|
+
function extractOwnershipCandidates(body, source) {
|
|
132094
|
+
const out = [];
|
|
132095
|
+
const tryEq = (left, right, line) => {
|
|
132096
|
+
const leftAuth = matchesAuthSide(left, source);
|
|
132097
|
+
const rightAuth = matchesAuthSide(right, source);
|
|
132098
|
+
if (!leftAuth && !rightAuth)
|
|
132099
|
+
return;
|
|
132100
|
+
if (rightAuth) {
|
|
132101
|
+
const field = terminalProperty(left, source);
|
|
132102
|
+
if (field)
|
|
132103
|
+
out.push({ resourceField: field, line });
|
|
132104
|
+
}
|
|
132105
|
+
if (leftAuth) {
|
|
132106
|
+
const field = terminalProperty(right, source);
|
|
132107
|
+
if (field)
|
|
132108
|
+
out.push({ resourceField: field, line });
|
|
132109
|
+
}
|
|
132110
|
+
};
|
|
132111
|
+
const visit = (node2) => {
|
|
132112
|
+
if (node2.type === "binary_expression") {
|
|
132113
|
+
const opNode = node2.childForFieldName("operator");
|
|
132114
|
+
const op = opNode ? source.slice(opNode.startIndex, opNode.endIndex) : "";
|
|
132115
|
+
if (op === "===" || op === "==" || op === "!==" || op === "!=") {
|
|
132116
|
+
const left = node2.childForFieldName("left");
|
|
132117
|
+
const right = node2.childForFieldName("right");
|
|
132118
|
+
if (left && right)
|
|
132119
|
+
tryEq(left, right, node2.startPosition.row + 1);
|
|
132120
|
+
}
|
|
132121
|
+
}
|
|
132122
|
+
if (node2.type === "comparison_operator") {
|
|
132123
|
+
const a = node2.namedChild(0);
|
|
132124
|
+
const b = node2.namedChild(1);
|
|
132125
|
+
if (a && b) {
|
|
132126
|
+
const op = source.slice(a.endIndex, b.startIndex).trim();
|
|
132127
|
+
if (op === "==" || op === "!=")
|
|
132128
|
+
tryEq(a, b, node2.startPosition.row + 1);
|
|
132129
|
+
}
|
|
132130
|
+
}
|
|
132131
|
+
for (const child of node2.namedChildren)
|
|
132132
|
+
visit(child);
|
|
132133
|
+
};
|
|
132134
|
+
visit(body);
|
|
132135
|
+
return out;
|
|
132136
|
+
}
|
|
132137
|
+
function terminalProperty(node2, source) {
|
|
132138
|
+
if (node2.type !== "member_expression" && node2.type !== "attribute")
|
|
132139
|
+
return null;
|
|
132140
|
+
const prop = node2.childForFieldName("property") ?? node2.childForFieldName("attribute");
|
|
132141
|
+
if (!prop)
|
|
132142
|
+
return null;
|
|
132143
|
+
return source.slice(prop.startIndex, prop.endIndex);
|
|
132144
|
+
}
|
|
132145
|
+
function matchesAuthSide(node2, source) {
|
|
132146
|
+
if (node2.type === "attribute") {
|
|
132147
|
+
return /\b(req|request)\.(auth|user)\b|\bcurrent_user\b/.test(source.slice(node2.startIndex, node2.endIndex));
|
|
132148
|
+
}
|
|
132149
|
+
let cur = node2;
|
|
132150
|
+
while (cur) {
|
|
132151
|
+
if (cur.type === "identifier") {
|
|
132152
|
+
const text = source.slice(cur.startIndex, cur.endIndex);
|
|
132153
|
+
if (text === "req" || text === "request")
|
|
132154
|
+
return false;
|
|
132155
|
+
return false;
|
|
132156
|
+
}
|
|
132157
|
+
if (cur.type === "member_expression") {
|
|
132158
|
+
const text = source.slice(cur.startIndex, cur.endIndex);
|
|
132159
|
+
if (/\b(req|request)\.auth\b|\b(req|request)\?\.auth\b/.test(text))
|
|
132160
|
+
return true;
|
|
132161
|
+
cur = cur.childForFieldName("object");
|
|
132162
|
+
continue;
|
|
132163
|
+
}
|
|
132164
|
+
if (cur.type === "optional_chain_expression" || cur.type === "subscript_expression") {
|
|
132165
|
+
cur = cur.childForFieldName("object");
|
|
132166
|
+
continue;
|
|
132167
|
+
}
|
|
132168
|
+
return false;
|
|
132169
|
+
}
|
|
132170
|
+
return false;
|
|
132171
|
+
}
|
|
132172
|
+
var init_handler_facts = __esm({
|
|
132173
|
+
"packages/contract-verifier/dist/extractor/handler-facts.js"() {
|
|
132174
|
+
"use strict";
|
|
132175
|
+
}
|
|
132176
|
+
});
|
|
132177
|
+
|
|
131708
132178
|
// packages/contract-verifier/dist/extractor/operation.js
|
|
131709
132179
|
function extractOperationsFromFile(filePath, source, tree) {
|
|
131710
132180
|
const fnIndex = buildFunctionIndex(tree.rootNode, source);
|
|
@@ -131788,6 +132258,7 @@ function tryExtractRouteCall(call, source, filePath, fnIndex) {
|
|
|
131788
132258
|
const bodyToWalk = resolveDelegationTarget(handlerBody, source, fnIndex) ?? handlerBody;
|
|
131789
132259
|
const responses = extractResponses(bodyToWalk, source);
|
|
131790
132260
|
const observed = collectHandlerObservations(bodyToWalk, source);
|
|
132261
|
+
const facts = extractHandlerFacts(bodyToWalk, source);
|
|
131791
132262
|
return {
|
|
131792
132263
|
identity: `${method.toUpperCase()} ${pathLit}`,
|
|
131793
132264
|
contract: {
|
|
@@ -131801,8 +132272,8 @@ function tryExtractRouteCall(call, source, filePath, fnIndex) {
|
|
|
131801
132272
|
declarationLine: call.startPosition.row + 1,
|
|
131802
132273
|
routerName,
|
|
131803
132274
|
observed,
|
|
131804
|
-
|
|
131805
|
-
|
|
132275
|
+
emission: facts.emission,
|
|
132276
|
+
ownershipCheckCandidates: facts.ownershipCheckCandidates
|
|
131806
132277
|
};
|
|
131807
132278
|
}
|
|
131808
132279
|
function collectHandlerObservationsFromBody(body, source) {
|
|
@@ -131980,7 +132451,7 @@ function describeResCall(call, source) {
|
|
|
131980
132451
|
}
|
|
131981
132452
|
function describeWebResponseNew(node2, source) {
|
|
131982
132453
|
const ctor = node2.childForFieldName("constructor");
|
|
131983
|
-
if (!ctor || ctor.type !== "identifier" || sliceText(ctor, source)
|
|
132454
|
+
if (!ctor || ctor.type !== "identifier" || !WEB_RESPONSE_CTORS.has(sliceText(ctor, source)))
|
|
131984
132455
|
return null;
|
|
131985
132456
|
const args = node2.childForFieldName("arguments");
|
|
131986
132457
|
const status = readStatusFromInit(args?.namedChild(1) ?? null, source) ?? "200";
|
|
@@ -132006,7 +132477,7 @@ function describeWebResponseCall(call, source) {
|
|
|
132006
132477
|
if (obj.type !== "identifier")
|
|
132007
132478
|
return null;
|
|
132008
132479
|
const objName = sliceText(obj, source);
|
|
132009
|
-
if (objName !== "Response" && objName !== "NextResponse")
|
|
132480
|
+
if (objName !== "Response" && objName !== "NextResponse" && objName !== "HttpResponse")
|
|
132010
132481
|
return null;
|
|
132011
132482
|
const args = call.childForFieldName("arguments");
|
|
132012
132483
|
const status = readStatusFromInit(args?.namedChild(1) ?? null, source) ?? "200";
|
|
@@ -132163,10 +132634,135 @@ function readStringLiteral(node2, source) {
|
|
|
132163
132634
|
}
|
|
132164
132635
|
return null;
|
|
132165
132636
|
}
|
|
132166
|
-
|
|
132637
|
+
function inferPluginMountPrefix(filePath) {
|
|
132638
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
132639
|
+
const m = normalized.match(/\/([^/]+)\/server(?:\/src)?\/routes\//);
|
|
132640
|
+
if (!m)
|
|
132641
|
+
return null;
|
|
132642
|
+
return "/" + m[1];
|
|
132643
|
+
}
|
|
132644
|
+
function joinPluginPath(prefix, routePath) {
|
|
132645
|
+
if (!routePath)
|
|
132646
|
+
return prefix || "/";
|
|
132647
|
+
const a = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
132648
|
+
const b = routePath.startsWith("/") ? routePath : "/" + routePath;
|
|
132649
|
+
return a + b;
|
|
132650
|
+
}
|
|
132651
|
+
function tryReadPluginRouteDescriptor(obj, source) {
|
|
132652
|
+
let method = null;
|
|
132653
|
+
let routePath = null;
|
|
132654
|
+
let authExempt = false;
|
|
132655
|
+
for (const child of obj.namedChildren) {
|
|
132656
|
+
if (child.type !== "pair")
|
|
132657
|
+
continue;
|
|
132658
|
+
const key2 = child.childForFieldName("key");
|
|
132659
|
+
const val = child.childForFieldName("value");
|
|
132660
|
+
if (!key2 || !val)
|
|
132661
|
+
continue;
|
|
132662
|
+
const keyName = key2.text.replace(/^['"]|['"]$/g, "");
|
|
132663
|
+
if (keyName === "method") {
|
|
132664
|
+
const v = readStringLiteral(val, source);
|
|
132665
|
+
if (v && PLUGIN_ROUTE_METHODS.has(v.toUpperCase()))
|
|
132666
|
+
method = v.toUpperCase();
|
|
132667
|
+
} else if (keyName === "path") {
|
|
132668
|
+
const v = readStringLiteral(val, source);
|
|
132669
|
+
if (v !== null && v.startsWith("/"))
|
|
132670
|
+
routePath = v;
|
|
132671
|
+
} else if (keyName === "config" && val.type === "object") {
|
|
132672
|
+
for (const cfgChild of val.namedChildren) {
|
|
132673
|
+
if (cfgChild.type !== "pair")
|
|
132674
|
+
continue;
|
|
132675
|
+
const cfgKey = cfgChild.childForFieldName("key");
|
|
132676
|
+
const cfgVal = cfgChild.childForFieldName("value");
|
|
132677
|
+
if (!cfgKey || !cfgVal)
|
|
132678
|
+
continue;
|
|
132679
|
+
const cfgKeyName = cfgKey.text.replace(/^['"]|['"]$/g, "");
|
|
132680
|
+
if (cfgKeyName === "auth" && cfgVal.type === "false") {
|
|
132681
|
+
authExempt = true;
|
|
132682
|
+
} else if (cfgKeyName === "policies" && cfgVal.type === "array" && cfgVal.namedChildCount > 0) {
|
|
132683
|
+
authExempt = true;
|
|
132684
|
+
}
|
|
132685
|
+
}
|
|
132686
|
+
}
|
|
132687
|
+
}
|
|
132688
|
+
if (!method || routePath === null)
|
|
132689
|
+
return null;
|
|
132690
|
+
return { method, path: routePath, line: obj.startPosition.row + 1, authExempt };
|
|
132691
|
+
}
|
|
132692
|
+
function collectPluginRouteDescriptors(node2, source) {
|
|
132693
|
+
const out = [];
|
|
132694
|
+
if (node2.type === "array") {
|
|
132695
|
+
for (const el of node2.namedChildren) {
|
|
132696
|
+
if (el.type === "object") {
|
|
132697
|
+
const desc = tryReadPluginRouteDescriptor(el, source);
|
|
132698
|
+
if (desc)
|
|
132699
|
+
out.push(desc);
|
|
132700
|
+
}
|
|
132701
|
+
}
|
|
132702
|
+
return out;
|
|
132703
|
+
}
|
|
132704
|
+
if (node2.type === "object") {
|
|
132705
|
+
for (const child of node2.namedChildren) {
|
|
132706
|
+
if (child.type !== "pair")
|
|
132707
|
+
continue;
|
|
132708
|
+
const key2 = child.childForFieldName("key");
|
|
132709
|
+
const val = child.childForFieldName("value");
|
|
132710
|
+
if (!key2 || !val)
|
|
132711
|
+
continue;
|
|
132712
|
+
const keyName = key2.text.replace(/^['"]|['"]$/g, "");
|
|
132713
|
+
if (keyName === "routes" && val.type === "array") {
|
|
132714
|
+
for (const el of val.namedChildren) {
|
|
132715
|
+
if (el.type === "object") {
|
|
132716
|
+
const desc = tryReadPluginRouteDescriptor(el, source);
|
|
132717
|
+
if (desc)
|
|
132718
|
+
out.push(desc);
|
|
132719
|
+
}
|
|
132720
|
+
}
|
|
132721
|
+
}
|
|
132722
|
+
}
|
|
132723
|
+
}
|
|
132724
|
+
return out;
|
|
132725
|
+
}
|
|
132726
|
+
function extractPluginStyleRoutesFromFile(filePath, source, tree) {
|
|
132727
|
+
const prefix = inferPluginMountPrefix(filePath);
|
|
132728
|
+
if (!prefix)
|
|
132729
|
+
return [];
|
|
132730
|
+
const out = [];
|
|
132731
|
+
const visit = (node2) => {
|
|
132732
|
+
if (node2.type === "export_statement") {
|
|
132733
|
+
const value = node2.childForFieldName("value");
|
|
132734
|
+
if (value) {
|
|
132735
|
+
const descriptors = collectPluginRouteDescriptors(value, source);
|
|
132736
|
+
for (const { method, path: routePath, line, authExempt } of descriptors) {
|
|
132737
|
+
const fullPath = joinPluginPath(prefix, routePath);
|
|
132738
|
+
out.push({
|
|
132739
|
+
identity: `${method} ${fullPath}`,
|
|
132740
|
+
contract: {
|
|
132741
|
+
protocol: "http",
|
|
132742
|
+
method,
|
|
132743
|
+
path: fullPath,
|
|
132744
|
+
tags: [],
|
|
132745
|
+
responses: []
|
|
132746
|
+
},
|
|
132747
|
+
filePath,
|
|
132748
|
+
declarationLine: line,
|
|
132749
|
+
observed: { queryParams: [], numericClamps: [], hasClampCall: false },
|
|
132750
|
+
...authExempt ? { authExempt: true } : {}
|
|
132751
|
+
});
|
|
132752
|
+
}
|
|
132753
|
+
}
|
|
132754
|
+
}
|
|
132755
|
+
for (const child of node2.namedChildren)
|
|
132756
|
+
visit(child);
|
|
132757
|
+
};
|
|
132758
|
+
visit(tree.rootNode);
|
|
132759
|
+
return out;
|
|
132760
|
+
}
|
|
132761
|
+
var HTTP_METHODS3, RES_SEND_METHODS, WEB_RESPONSE_CTORS, PLUGIN_ROUTE_METHODS;
|
|
132167
132762
|
var init_operation2 = __esm({
|
|
132168
132763
|
"packages/contract-verifier/dist/extractor/operation.js"() {
|
|
132169
132764
|
"use strict";
|
|
132765
|
+
init_handler_facts();
|
|
132170
132766
|
HTTP_METHODS3 = /* @__PURE__ */ new Set(["get", "post", "put", "delete", "patch"]);
|
|
132171
132767
|
RES_SEND_METHODS = /* @__PURE__ */ new Set([
|
|
132172
132768
|
"json",
|
|
@@ -132176,12 +132772,15 @@ var init_operation2 = __esm({
|
|
|
132176
132772
|
"sendFile",
|
|
132177
132773
|
"render"
|
|
132178
132774
|
]);
|
|
132775
|
+
WEB_RESPONSE_CTORS = /* @__PURE__ */ new Set(["Response", "HttpResponse"]);
|
|
132776
|
+
PLUGIN_ROUTE_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"]);
|
|
132179
132777
|
}
|
|
132180
132778
|
});
|
|
132181
132779
|
|
|
132182
132780
|
// packages/contract-verifier/dist/extractor/operation-fastapi.js
|
|
132183
132781
|
function extractFastApiOperationsFromFile(filePath, source, tree) {
|
|
132184
132782
|
const routers = collectRouters(tree.rootNode, source);
|
|
132783
|
+
const stringVars = collectStringVars(tree.rootNode, source);
|
|
132185
132784
|
const out = [];
|
|
132186
132785
|
walk(tree.rootNode, (node2) => {
|
|
132187
132786
|
if (node2.type !== "decorated_definition")
|
|
@@ -132193,7 +132792,7 @@ function extractFastApiOperationsFromFile(filePath, source, tree) {
|
|
|
132193
132792
|
const dec = node2.namedChild(i);
|
|
132194
132793
|
if (dec?.type !== "decorator")
|
|
132195
132794
|
continue;
|
|
132196
|
-
const route = parseRouteDecorator(dec, source);
|
|
132795
|
+
const route = parseRouteDecorator(dec, source, stringVars);
|
|
132197
132796
|
if (!route)
|
|
132198
132797
|
continue;
|
|
132199
132798
|
const router = routers.get(route.routerVar) ?? { prefix: "", hasAuthDep: false };
|
|
@@ -132202,6 +132801,7 @@ function extractFastApiOperationsFromFile(filePath, source, tree) {
|
|
|
132202
132801
|
const paramsNode = def.childForFieldName("parameters");
|
|
132203
132802
|
const responses = extractResponses2(body, source, route.successStatus);
|
|
132204
132803
|
const observed = collectObservations(paramsNode, body, source, fullPath);
|
|
132804
|
+
const facts = body ? extractHandlerFacts(body, source) : emptyHandlerFacts();
|
|
132205
132805
|
out.push({
|
|
132206
132806
|
identity: `${route.method.toUpperCase()} ${fullPath}`,
|
|
132207
132807
|
contract: {
|
|
@@ -132215,8 +132815,8 @@ function extractFastApiOperationsFromFile(filePath, source, tree) {
|
|
|
132215
132815
|
declarationLine: node2.startPosition.row + 1,
|
|
132216
132816
|
routerName: route.routerVar,
|
|
132217
132817
|
observed,
|
|
132218
|
-
|
|
132219
|
-
|
|
132818
|
+
emission: facts.emission,
|
|
132819
|
+
ownershipCheckCandidates: facts.ownershipCheckCandidates
|
|
132220
132820
|
});
|
|
132221
132821
|
break;
|
|
132222
132822
|
}
|
|
@@ -132234,7 +132834,7 @@ function collectRouters(root, source) {
|
|
|
132234
132834
|
return;
|
|
132235
132835
|
const fn = right.childForFieldName("function");
|
|
132236
132836
|
const fnName = fn ? source.slice(fn.startIndex, fn.endIndex) : "";
|
|
132237
|
-
if (fnName !== "
|
|
132837
|
+
if (fnName !== "FastAPI" && !fnName.endsWith("Router"))
|
|
132238
132838
|
return;
|
|
132239
132839
|
const args = right.childForFieldName("arguments");
|
|
132240
132840
|
let prefix = "";
|
|
@@ -132267,7 +132867,37 @@ function fastApiFileHasAuthRouter(source, tree) {
|
|
|
132267
132867
|
}
|
|
132268
132868
|
return false;
|
|
132269
132869
|
}
|
|
132270
|
-
function
|
|
132870
|
+
function collectStringVars(root, source) {
|
|
132871
|
+
const vars = /* @__PURE__ */ new Map();
|
|
132872
|
+
walk(root, (node2) => {
|
|
132873
|
+
if (node2.type === "assignment") {
|
|
132874
|
+
const left = node2.childForFieldName("left");
|
|
132875
|
+
const right = node2.childForFieldName("right");
|
|
132876
|
+
if (left?.type === "identifier" && right?.type === "string") {
|
|
132877
|
+
vars.set(source.slice(left.startIndex, left.endIndex), pyStr(right, source));
|
|
132878
|
+
}
|
|
132879
|
+
}
|
|
132880
|
+
if (node2.type === "function_definition") {
|
|
132881
|
+
const params = node2.childForFieldName("parameters");
|
|
132882
|
+
if (params) {
|
|
132883
|
+
for (let i = 0; i < params.namedChildCount; i++) {
|
|
132884
|
+
const p2 = params.namedChild(i);
|
|
132885
|
+
if (!p2)
|
|
132886
|
+
continue;
|
|
132887
|
+
if (p2.type === "typed_default_parameter" || p2.type === "default_parameter") {
|
|
132888
|
+
const name = p2.childForFieldName("name");
|
|
132889
|
+
const value = p2.childForFieldName("value");
|
|
132890
|
+
if (name?.type === "identifier" && value?.type === "string") {
|
|
132891
|
+
vars.set(source.slice(name.startIndex, name.endIndex), pyStr(value, source));
|
|
132892
|
+
}
|
|
132893
|
+
}
|
|
132894
|
+
}
|
|
132895
|
+
}
|
|
132896
|
+
}
|
|
132897
|
+
});
|
|
132898
|
+
return vars;
|
|
132899
|
+
}
|
|
132900
|
+
function parseRouteDecorator(dec, source, stringVars) {
|
|
132271
132901
|
const call = dec.namedChild(0);
|
|
132272
132902
|
if (call?.type !== "call")
|
|
132273
132903
|
return null;
|
|
@@ -132300,6 +132930,14 @@ function parseRouteDecorator(dec, source) {
|
|
|
132300
132930
|
}
|
|
132301
132931
|
}
|
|
132302
132932
|
}
|
|
132933
|
+
if (path60 === null) {
|
|
132934
|
+
const firstArg = args.namedChild(0);
|
|
132935
|
+
if (firstArg?.type === "identifier") {
|
|
132936
|
+
const resolved = stringVars.get(source.slice(firstArg.startIndex, firstArg.endIndex));
|
|
132937
|
+
if (resolved !== void 0)
|
|
132938
|
+
path60 = resolved;
|
|
132939
|
+
}
|
|
132940
|
+
}
|
|
132303
132941
|
if (path60 === null)
|
|
132304
132942
|
return null;
|
|
132305
132943
|
return { method, path: path60, routerVar, successStatus };
|
|
@@ -132510,28 +133148,29 @@ var HTTP_METHODS4, AUTH_DEP_NAMES;
|
|
|
132510
133148
|
var init_operation_fastapi = __esm({
|
|
132511
133149
|
"packages/contract-verifier/dist/extractor/operation-fastapi.js"() {
|
|
132512
133150
|
"use strict";
|
|
133151
|
+
init_handler_facts();
|
|
132513
133152
|
HTTP_METHODS4 = /* @__PURE__ */ new Set(["get", "post", "put", "delete", "patch"]);
|
|
132514
133153
|
AUTH_DEP_NAMES = /require_bearer|require_auth|authenticate|get_current_user|bearer|require_role|require_admin/i;
|
|
132515
133154
|
}
|
|
132516
133155
|
});
|
|
132517
133156
|
|
|
132518
133157
|
// packages/contract-verifier/dist/extractor/source-walker.js
|
|
132519
|
-
import
|
|
132520
|
-
import
|
|
133158
|
+
import fs33 from "node:fs";
|
|
133159
|
+
import path36 from "node:path";
|
|
132521
133160
|
async function eachParsedSource(rootDir, visit) {
|
|
132522
133161
|
await initParsers();
|
|
132523
133162
|
const tcIgnore = loadTcIgnore(rootDir);
|
|
132524
133163
|
const walk11 = (dir) => {
|
|
132525
133164
|
let entries;
|
|
132526
133165
|
try {
|
|
132527
|
-
entries =
|
|
133166
|
+
entries = fs33.readdirSync(dir, { withFileTypes: true });
|
|
132528
133167
|
} catch {
|
|
132529
133168
|
return;
|
|
132530
133169
|
}
|
|
132531
133170
|
for (const entry of entries) {
|
|
132532
133171
|
if (SKIP_DIRS2.has(entry.name))
|
|
132533
133172
|
continue;
|
|
132534
|
-
const full =
|
|
133173
|
+
const full = path36.join(dir, entry.name);
|
|
132535
133174
|
if (tcIgnore.ignores(full))
|
|
132536
133175
|
continue;
|
|
132537
133176
|
if (entry.isDirectory()) {
|
|
@@ -132540,19 +133179,22 @@ async function eachParsedSource(rootDir, visit) {
|
|
|
132540
133179
|
}
|
|
132541
133180
|
if (!entry.isFile())
|
|
132542
133181
|
continue;
|
|
132543
|
-
const lang = EXT_TO_LANG[
|
|
133182
|
+
const lang = EXT_TO_LANG[path36.extname(entry.name)];
|
|
132544
133183
|
if (!lang)
|
|
132545
133184
|
continue;
|
|
132546
133185
|
let source;
|
|
132547
133186
|
try {
|
|
132548
|
-
source =
|
|
133187
|
+
source = fs33.readFileSync(full, "utf-8");
|
|
132549
133188
|
} catch {
|
|
132550
133189
|
continue;
|
|
132551
133190
|
}
|
|
133191
|
+
let tree;
|
|
132552
133192
|
try {
|
|
132553
|
-
|
|
133193
|
+
tree = parseFile(full, source, lang);
|
|
132554
133194
|
visit({ filePath: full, source, tree, lang });
|
|
132555
133195
|
} catch {
|
|
133196
|
+
} finally {
|
|
133197
|
+
tree?.delete();
|
|
132556
133198
|
}
|
|
132557
133199
|
}
|
|
132558
133200
|
};
|
|
@@ -132608,8 +133250,8 @@ var init_source_walker = __esm({
|
|
|
132608
133250
|
});
|
|
132609
133251
|
|
|
132610
133252
|
// packages/contract-verifier/dist/extractor/file-based-routes.js
|
|
132611
|
-
import
|
|
132612
|
-
import
|
|
133253
|
+
import fs34 from "node:fs";
|
|
133254
|
+
import path37 from "node:path";
|
|
132613
133255
|
async function extractFileBasedRoutesFromDir(rootDir) {
|
|
132614
133256
|
await initParsers();
|
|
132615
133257
|
const out = [];
|
|
@@ -132627,7 +133269,7 @@ function findDirs(rootDir, wantedRel) {
|
|
|
132627
133269
|
const visit = (dir) => {
|
|
132628
133270
|
let entries;
|
|
132629
133271
|
try {
|
|
132630
|
-
entries =
|
|
133272
|
+
entries = fs34.readdirSync(dir, { withFileTypes: true });
|
|
132631
133273
|
} catch {
|
|
132632
133274
|
return;
|
|
132633
133275
|
}
|
|
@@ -132636,11 +133278,11 @@ function findDirs(rootDir, wantedRel) {
|
|
|
132636
133278
|
continue;
|
|
132637
133279
|
if (!entry.isDirectory())
|
|
132638
133280
|
continue;
|
|
132639
|
-
const full =
|
|
133281
|
+
const full = path37.join(dir, entry.name);
|
|
132640
133282
|
if (tcIgnore.ignores(full))
|
|
132641
133283
|
continue;
|
|
132642
|
-
const match4 =
|
|
132643
|
-
if (full.endsWith(
|
|
133284
|
+
const match4 = path37.join(...wantedSegments);
|
|
133285
|
+
if (full.endsWith(path37.sep + match4) || full === match4) {
|
|
132644
133286
|
results.push(full);
|
|
132645
133287
|
continue;
|
|
132646
133288
|
}
|
|
@@ -132653,10 +133295,10 @@ function findDirs(rootDir, wantedRel) {
|
|
|
132653
133295
|
function walkRoot(rootAbs, root, out) {
|
|
132654
133296
|
const tcIgnore = loadTcIgnore(rootAbs);
|
|
132655
133297
|
const visit = (dir) => {
|
|
132656
|
-
for (const entry of
|
|
133298
|
+
for (const entry of fs34.readdirSync(dir, { withFileTypes: true })) {
|
|
132657
133299
|
if (entry.name === "node_modules" || entry.name === ".git")
|
|
132658
133300
|
continue;
|
|
132659
|
-
const full =
|
|
133301
|
+
const full = path37.join(dir, entry.name);
|
|
132660
133302
|
if (tcIgnore.ignores(full))
|
|
132661
133303
|
continue;
|
|
132662
133304
|
if (entry.isDirectory()) {
|
|
@@ -132665,7 +133307,7 @@ function walkRoot(rootAbs, root, out) {
|
|
|
132665
133307
|
}
|
|
132666
133308
|
if (!entry.isFile())
|
|
132667
133309
|
continue;
|
|
132668
|
-
const ext2 =
|
|
133310
|
+
const ext2 = path37.extname(entry.name);
|
|
132669
133311
|
if (!TS_EXT.has(ext2))
|
|
132670
133312
|
continue;
|
|
132671
133313
|
if (root.shape === "next-app" && entry.name !== "route.ts" && entry.name !== "route.js" && entry.name !== "route.tsx" && entry.name !== "route.jsx") {
|
|
@@ -132674,11 +133316,11 @@ function walkRoot(rootAbs, root, out) {
|
|
|
132674
133316
|
if (root.shape === "sveltekit" && !/^\+server\.(t|j)sx?$/.test(entry.name)) {
|
|
132675
133317
|
continue;
|
|
132676
133318
|
}
|
|
132677
|
-
const relUnderRoot =
|
|
133319
|
+
const relUnderRoot = path37.relative(rootAbs, full);
|
|
132678
133320
|
const url = deriveUrl(relUnderRoot, root);
|
|
132679
133321
|
if (url === null)
|
|
132680
133322
|
continue;
|
|
132681
|
-
const source =
|
|
133323
|
+
const source = fs34.readFileSync(full, "utf-8");
|
|
132682
133324
|
const lang = ext2 === ".tsx" ? "tsx" : ext2 === ".ts" ? "typescript" : "javascript";
|
|
132683
133325
|
let tree;
|
|
132684
133326
|
try {
|
|
@@ -132686,24 +133328,29 @@ function walkRoot(rootAbs, root, out) {
|
|
|
132686
133328
|
} catch {
|
|
132687
133329
|
continue;
|
|
132688
133330
|
}
|
|
132689
|
-
|
|
132690
|
-
|
|
132691
|
-
const
|
|
132692
|
-
|
|
132693
|
-
|
|
132694
|
-
|
|
132695
|
-
|
|
132696
|
-
|
|
132697
|
-
|
|
132698
|
-
|
|
132699
|
-
|
|
132700
|
-
|
|
132701
|
-
|
|
132702
|
-
|
|
132703
|
-
|
|
132704
|
-
|
|
132705
|
-
|
|
132706
|
-
|
|
133331
|
+
try {
|
|
133332
|
+
const exports = collectHttpMethodExports(tree.rootNode, source);
|
|
133333
|
+
for (const exp of exports) {
|
|
133334
|
+
const contract = {
|
|
133335
|
+
protocol: "http",
|
|
133336
|
+
method: exp.method.toUpperCase(),
|
|
133337
|
+
path: url,
|
|
133338
|
+
tags: [],
|
|
133339
|
+
responses: extractResponsesFromBody(exp.body, source)
|
|
133340
|
+
};
|
|
133341
|
+
const facts = extractHandlerFacts(exp.body, source);
|
|
133342
|
+
out.push({
|
|
133343
|
+
identity: `${exp.method.toUpperCase()} ${url}`,
|
|
133344
|
+
contract,
|
|
133345
|
+
filePath: full,
|
|
133346
|
+
declarationLine: exp.line,
|
|
133347
|
+
observed: collectHandlerObservationsFromBody(exp.body, source),
|
|
133348
|
+
emission: facts.emission,
|
|
133349
|
+
ownershipCheckCandidates: facts.ownershipCheckCandidates
|
|
133350
|
+
});
|
|
133351
|
+
}
|
|
133352
|
+
} finally {
|
|
133353
|
+
tree.delete();
|
|
132707
133354
|
}
|
|
132708
133355
|
}
|
|
132709
133356
|
};
|
|
@@ -132711,7 +133358,7 @@ function walkRoot(rootAbs, root, out) {
|
|
|
132711
133358
|
}
|
|
132712
133359
|
function deriveUrl(relPath, root) {
|
|
132713
133360
|
const noExt = relPath.replace(/\.(t|j)sx?$/, "");
|
|
132714
|
-
const segments = noExt.split(
|
|
133361
|
+
const segments = noExt.split(path37.sep);
|
|
132715
133362
|
switch (root.shape) {
|
|
132716
133363
|
case "next-pages":
|
|
132717
133364
|
return "/api/" + segmentsToUrl(segments, { stripIndex: true });
|
|
@@ -132795,6 +133442,7 @@ var init_file_based_routes = __esm({
|
|
|
132795
133442
|
init_dist6();
|
|
132796
133443
|
init_dist7();
|
|
132797
133444
|
init_operation2();
|
|
133445
|
+
init_handler_facts();
|
|
132798
133446
|
TS_EXT = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
|
|
132799
133447
|
HTTP_METHODS5 = /* @__PURE__ */ new Set(["get", "post", "put", "delete", "patch", "options", "head"]);
|
|
132800
133448
|
ROUTE_ROOTS = [
|
|
@@ -132812,8 +133460,8 @@ var init_file_based_routes = __esm({
|
|
|
132812
133460
|
});
|
|
132813
133461
|
|
|
132814
133462
|
// packages/contract-verifier/dist/extractor/mount-graph.js
|
|
132815
|
-
import
|
|
132816
|
-
import
|
|
133463
|
+
import fs35 from "node:fs";
|
|
133464
|
+
import path38 from "node:path";
|
|
132817
133465
|
function analyzeRouterFile(filePath, source, tree) {
|
|
132818
133466
|
const declarations = [];
|
|
132819
133467
|
const mounts = [];
|
|
@@ -132949,21 +133597,21 @@ function collectImports(node2, source, filePath, out) {
|
|
|
132949
133597
|
function resolveImportPath(fromFile, spec) {
|
|
132950
133598
|
if (!spec.startsWith("."))
|
|
132951
133599
|
return null;
|
|
132952
|
-
const baseDir =
|
|
133600
|
+
const baseDir = path38.dirname(fromFile);
|
|
132953
133601
|
const noExt = spec.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, "");
|
|
132954
133602
|
const candidates = [
|
|
132955
133603
|
noExt + ".ts",
|
|
132956
133604
|
noExt + ".tsx",
|
|
132957
133605
|
noExt + ".js",
|
|
132958
133606
|
noExt + ".jsx",
|
|
132959
|
-
|
|
132960
|
-
|
|
132961
|
-
|
|
132962
|
-
|
|
133607
|
+
path38.join(noExt, "index.ts"),
|
|
133608
|
+
path38.join(noExt, "index.tsx"),
|
|
133609
|
+
path38.join(noExt, "index.js"),
|
|
133610
|
+
path38.join(noExt, "index.jsx")
|
|
132963
133611
|
];
|
|
132964
133612
|
for (const c2 of candidates) {
|
|
132965
|
-
const abs =
|
|
132966
|
-
if (
|
|
133613
|
+
const abs = path38.resolve(baseDir, c2);
|
|
133614
|
+
if (fs35.existsSync(abs) && fs35.statSync(abs).isFile())
|
|
132967
133615
|
return abs;
|
|
132968
133616
|
}
|
|
132969
133617
|
return null;
|
|
@@ -133148,8 +133796,8 @@ var init_mount_graph = __esm({
|
|
|
133148
133796
|
});
|
|
133149
133797
|
|
|
133150
133798
|
// packages/contract-verifier/dist/extractor/auth-presence.js
|
|
133151
|
-
import
|
|
133152
|
-
import
|
|
133799
|
+
import fs36 from "node:fs";
|
|
133800
|
+
import path39 from "node:path";
|
|
133153
133801
|
async function detectAuthPresence(rootDir) {
|
|
133154
133802
|
await initParsers();
|
|
133155
133803
|
const edges = [];
|
|
@@ -133159,10 +133807,10 @@ async function detectAuthPresence(rootDir) {
|
|
|
133159
133807
|
const scanned = [];
|
|
133160
133808
|
const tcIgnore = loadTcIgnore(rootDir);
|
|
133161
133809
|
const visit = (dir) => {
|
|
133162
|
-
for (const entry of
|
|
133810
|
+
for (const entry of fs36.readdirSync(dir, { withFileTypes: true })) {
|
|
133163
133811
|
if (entry.name === "node_modules" || entry.name === ".git")
|
|
133164
133812
|
continue;
|
|
133165
|
-
const full =
|
|
133813
|
+
const full = path39.join(dir, entry.name);
|
|
133166
133814
|
if (tcIgnore.ignores(full))
|
|
133167
133815
|
continue;
|
|
133168
133816
|
if (entry.isDirectory()) {
|
|
@@ -133171,10 +133819,10 @@ async function detectAuthPresence(rootDir) {
|
|
|
133171
133819
|
}
|
|
133172
133820
|
if (!entry.isFile())
|
|
133173
133821
|
continue;
|
|
133174
|
-
const ext2 =
|
|
133822
|
+
const ext2 = path39.extname(entry.name);
|
|
133175
133823
|
if (!TS_EXT2.has(ext2))
|
|
133176
133824
|
continue;
|
|
133177
|
-
const source =
|
|
133825
|
+
const source = fs36.readFileSync(full, "utf-8");
|
|
133178
133826
|
const lang = ext2 === ".tsx" ? "tsx" : ext2 === ".ts" ? "typescript" : "javascript";
|
|
133179
133827
|
let tree;
|
|
133180
133828
|
try {
|
|
@@ -133182,8 +133830,12 @@ async function detectAuthPresence(rootDir) {
|
|
|
133182
133830
|
} catch {
|
|
133183
133831
|
continue;
|
|
133184
133832
|
}
|
|
133185
|
-
|
|
133186
|
-
|
|
133833
|
+
try {
|
|
133834
|
+
scanned.push(full);
|
|
133835
|
+
collectFromTree(tree, source, full, edges, fileDefaultExports, fileImports, routerVarsByFile);
|
|
133836
|
+
} finally {
|
|
133837
|
+
tree.delete();
|
|
133838
|
+
}
|
|
133187
133839
|
}
|
|
133188
133840
|
};
|
|
133189
133841
|
visit(rootDir);
|
|
@@ -133238,7 +133890,7 @@ async function detectAuthPresence(rootDir) {
|
|
|
133238
133890
|
function resolveImportPath2(importingFile, sourceStr) {
|
|
133239
133891
|
if (!sourceStr.startsWith("."))
|
|
133240
133892
|
return null;
|
|
133241
|
-
const baseRaw =
|
|
133893
|
+
const baseRaw = path39.resolve(path39.dirname(importingFile), sourceStr);
|
|
133242
133894
|
const candidates = [];
|
|
133243
133895
|
candidates.push(baseRaw);
|
|
133244
133896
|
if (baseRaw.endsWith(".js")) {
|
|
@@ -133249,14 +133901,14 @@ function resolveImportPath2(importingFile, sourceStr) {
|
|
|
133249
133901
|
const stem = baseRaw.slice(0, -4);
|
|
133250
133902
|
candidates.push(stem + ".tsx", stem + ".ts");
|
|
133251
133903
|
}
|
|
133252
|
-
if (!
|
|
133904
|
+
if (!path39.extname(baseRaw)) {
|
|
133253
133905
|
for (const ext2 of [".ts", ".tsx", ".js", ".jsx"])
|
|
133254
133906
|
candidates.push(baseRaw + ext2);
|
|
133255
133907
|
for (const ext2 of [".ts", ".tsx", ".js", ".jsx"])
|
|
133256
|
-
candidates.push(
|
|
133908
|
+
candidates.push(path39.join(baseRaw, `index${ext2}`));
|
|
133257
133909
|
}
|
|
133258
133910
|
for (const c2 of candidates) {
|
|
133259
|
-
if (
|
|
133911
|
+
if (fs36.existsSync(c2) && fs36.statSync(c2).isFile())
|
|
133260
133912
|
return c2;
|
|
133261
133913
|
}
|
|
133262
133914
|
return null;
|
|
@@ -133347,8 +133999,8 @@ var init_auth_presence = __esm({
|
|
|
133347
133999
|
});
|
|
133348
134000
|
|
|
133349
134001
|
// packages/contract-verifier/dist/extractor/idempotency-presence.js
|
|
133350
|
-
import
|
|
133351
|
-
import
|
|
134002
|
+
import fs37 from "node:fs";
|
|
134003
|
+
import path40 from "node:path";
|
|
133352
134004
|
function routeKey(filePath, declarationLine) {
|
|
133353
134005
|
return `${filePath}::${declarationLine}`;
|
|
133354
134006
|
}
|
|
@@ -133356,14 +134008,14 @@ async function detectIdempotencyPresence(rootDir, requestHeader) {
|
|
|
133356
134008
|
await initParsers();
|
|
133357
134009
|
const headerLower = requestHeader.toLowerCase();
|
|
133358
134010
|
const fileBindings = /* @__PURE__ */ new Map();
|
|
133359
|
-
const
|
|
134011
|
+
const pendingByFile = /* @__PURE__ */ new Map();
|
|
133360
134012
|
const scanned = [];
|
|
133361
134013
|
const tcIgnore = loadTcIgnore(rootDir);
|
|
133362
134014
|
const visit = (dir) => {
|
|
133363
|
-
for (const entry of
|
|
134015
|
+
for (const entry of fs37.readdirSync(dir, { withFileTypes: true })) {
|
|
133364
134016
|
if (entry.name === "node_modules" || entry.name === ".git")
|
|
133365
134017
|
continue;
|
|
133366
|
-
const full =
|
|
134018
|
+
const full = path40.join(dir, entry.name);
|
|
133367
134019
|
if (tcIgnore.ignores(full))
|
|
133368
134020
|
continue;
|
|
133369
134021
|
if (entry.isDirectory()) {
|
|
@@ -133372,10 +134024,10 @@ async function detectIdempotencyPresence(rootDir, requestHeader) {
|
|
|
133372
134024
|
}
|
|
133373
134025
|
if (!entry.isFile())
|
|
133374
134026
|
continue;
|
|
133375
|
-
const ext2 =
|
|
134027
|
+
const ext2 = path40.extname(entry.name);
|
|
133376
134028
|
if (!TS_EXT3.has(ext2))
|
|
133377
134029
|
continue;
|
|
133378
|
-
const source =
|
|
134030
|
+
const source = fs37.readFileSync(full, "utf-8");
|
|
133379
134031
|
const lang = ext2 === ".tsx" ? "tsx" : ext2 === ".ts" ? "typescript" : "javascript";
|
|
133380
134032
|
let tree;
|
|
133381
134033
|
try {
|
|
@@ -133383,19 +134035,23 @@ async function detectIdempotencyPresence(rootDir, requestHeader) {
|
|
|
133383
134035
|
} catch {
|
|
133384
134036
|
continue;
|
|
133385
134037
|
}
|
|
133386
|
-
|
|
133387
|
-
|
|
133388
|
-
|
|
134038
|
+
try {
|
|
134039
|
+
scanned.push(full);
|
|
134040
|
+
fileBindings.set(full, collectBindings(tree, source, full, headerLower));
|
|
134041
|
+
pendingByFile.set(full, collectPendingRoutes(tree, source, headerLower));
|
|
134042
|
+
} finally {
|
|
134043
|
+
tree.delete();
|
|
134044
|
+
}
|
|
133389
134045
|
}
|
|
133390
134046
|
};
|
|
133391
134047
|
visit(rootDir);
|
|
133392
134048
|
const protectedRoutes = /* @__PURE__ */ new Set();
|
|
133393
|
-
for (const [filePath,
|
|
133394
|
-
|
|
133395
|
-
if (anyIdentIsAware(middlewareIdents, filePath, fileBindings)
|
|
133396
|
-
protectedRoutes.add(routeKey(filePath, declarationLine));
|
|
134049
|
+
for (const [filePath, routes] of pendingByFile) {
|
|
134050
|
+
for (const r of routes) {
|
|
134051
|
+
if (r.handlerReadsHeader || anyIdentIsAware(r.middlewareIdents, filePath, fileBindings)) {
|
|
134052
|
+
protectedRoutes.add(routeKey(filePath, r.declarationLine));
|
|
133397
134053
|
}
|
|
133398
|
-
}
|
|
134054
|
+
}
|
|
133399
134055
|
}
|
|
133400
134056
|
return { protectedRoutes, scannedFiles: scanned };
|
|
133401
134057
|
}
|
|
@@ -133465,6 +134121,17 @@ function collectBindings(tree, source, filePath, headerLower) {
|
|
|
133465
134121
|
visit(tree.rootNode);
|
|
133466
134122
|
return { imports, awareLocally, declaredFunctions };
|
|
133467
134123
|
}
|
|
134124
|
+
function collectPendingRoutes(tree, source, headerLower) {
|
|
134125
|
+
const out = [];
|
|
134126
|
+
walkRoutes(tree.rootNode, source, (declarationLine, middlewareIdents, handlerBody) => {
|
|
134127
|
+
out.push({
|
|
134128
|
+
declarationLine,
|
|
134129
|
+
middlewareIdents,
|
|
134130
|
+
handlerReadsHeader: !!handlerBody && nodeReadsHeader(handlerBody, source, headerLower)
|
|
134131
|
+
});
|
|
134132
|
+
});
|
|
134133
|
+
return out;
|
|
134134
|
+
}
|
|
133468
134135
|
function nodeReadsHeader(node2, source, headerLower) {
|
|
133469
134136
|
const target = headerLower;
|
|
133470
134137
|
let found = false;
|
|
@@ -133573,7 +134240,7 @@ function anyIdentIsAware(idents, filePath, fileBindings) {
|
|
|
133573
134240
|
function resolveImportPath3(importingFile, sourceStr) {
|
|
133574
134241
|
if (!sourceStr.startsWith("."))
|
|
133575
134242
|
return null;
|
|
133576
|
-
const baseRaw =
|
|
134243
|
+
const baseRaw = path40.resolve(path40.dirname(importingFile), sourceStr);
|
|
133577
134244
|
const candidates = [];
|
|
133578
134245
|
candidates.push(baseRaw);
|
|
133579
134246
|
if (baseRaw.endsWith(".js")) {
|
|
@@ -133584,14 +134251,14 @@ function resolveImportPath3(importingFile, sourceStr) {
|
|
|
133584
134251
|
const stem = baseRaw.slice(0, -4);
|
|
133585
134252
|
candidates.push(stem + ".tsx", stem + ".ts");
|
|
133586
134253
|
}
|
|
133587
|
-
if (!
|
|
134254
|
+
if (!path40.extname(baseRaw)) {
|
|
133588
134255
|
for (const ext2 of [".ts", ".tsx", ".js", ".jsx"])
|
|
133589
134256
|
candidates.push(baseRaw + ext2);
|
|
133590
134257
|
for (const ext2 of [".ts", ".tsx", ".js", ".jsx"])
|
|
133591
|
-
candidates.push(
|
|
134258
|
+
candidates.push(path40.join(baseRaw, `index${ext2}`));
|
|
133592
134259
|
}
|
|
133593
134260
|
for (const c2 of candidates) {
|
|
133594
|
-
if (
|
|
134261
|
+
if (fs37.existsSync(c2) && fs37.statSync(c2).isFile())
|
|
133595
134262
|
return c2;
|
|
133596
134263
|
}
|
|
133597
134264
|
return null;
|
|
@@ -135661,6 +136328,51 @@ function walk6(node2, visit) {
|
|
|
135661
136328
|
walk6(c2, visit);
|
|
135662
136329
|
}
|
|
135663
136330
|
}
|
|
136331
|
+
function extractIdLiteralFromExport(_filePath, _source, tree) {
|
|
136332
|
+
for (let i = 0; i < tree.rootNode.namedChildCount; i++) {
|
|
136333
|
+
const node2 = tree.rootNode.namedChild(i);
|
|
136334
|
+
if (!node2 || node2.type !== "export_statement")
|
|
136335
|
+
continue;
|
|
136336
|
+
for (let j2 = 0; j2 < node2.namedChildCount; j2++) {
|
|
136337
|
+
const child = node2.namedChild(j2);
|
|
136338
|
+
if (!child)
|
|
136339
|
+
continue;
|
|
136340
|
+
const id = extractIdFromObjectLike(child);
|
|
136341
|
+
if (id !== null)
|
|
136342
|
+
return id;
|
|
136343
|
+
}
|
|
136344
|
+
}
|
|
136345
|
+
return null;
|
|
136346
|
+
}
|
|
136347
|
+
function extractIdFromObjectLike(node2) {
|
|
136348
|
+
if (node2.type === "object") {
|
|
136349
|
+
return extractIdProperty(node2);
|
|
136350
|
+
}
|
|
136351
|
+
if (node2.type === "call_expression") {
|
|
136352
|
+
for (const arg of collectArgs4(node2)) {
|
|
136353
|
+
if (arg.type === "object") {
|
|
136354
|
+
const id = extractIdProperty(arg);
|
|
136355
|
+
if (id !== null)
|
|
136356
|
+
return id;
|
|
136357
|
+
}
|
|
136358
|
+
}
|
|
136359
|
+
}
|
|
136360
|
+
return null;
|
|
136361
|
+
}
|
|
136362
|
+
function extractIdProperty(obj) {
|
|
136363
|
+
for (let i = 0; i < obj.namedChildCount; i++) {
|
|
136364
|
+
const pair = obj.namedChild(i);
|
|
136365
|
+
if (pair?.type !== "pair")
|
|
136366
|
+
continue;
|
|
136367
|
+
const key2 = pair.childForFieldName("key");
|
|
136368
|
+
if (key2?.text !== "id")
|
|
136369
|
+
continue;
|
|
136370
|
+
const value = pair.childForFieldName("value");
|
|
136371
|
+
if (value?.type === "string")
|
|
136372
|
+
return value.text.slice(1, -1);
|
|
136373
|
+
}
|
|
136374
|
+
return null;
|
|
136375
|
+
}
|
|
135664
136376
|
var ENUM_CONVENTION_NAME, ENUM_CONVENTION_SUFFIX;
|
|
135665
136377
|
var init_ts_enums = __esm({
|
|
135666
136378
|
"packages/contract-verifier/dist/extractor/enum/ts-enums.js"() {
|
|
@@ -135695,7 +136407,10 @@ function extractEnumClass(node2, filePath, source) {
|
|
|
135695
136407
|
if (!name)
|
|
135696
136408
|
return null;
|
|
135697
136409
|
const supers = node2.childForFieldName("superclasses");
|
|
135698
|
-
if (!supers
|
|
136410
|
+
if (!supers)
|
|
136411
|
+
return null;
|
|
136412
|
+
const isAutoEnum = superclassesHaveAutoEnum(supers, source);
|
|
136413
|
+
if (!superclassesLookEnum(supers, source) && !isAutoEnum)
|
|
135699
136414
|
return null;
|
|
135700
136415
|
const body = node2.childForFieldName("body");
|
|
135701
136416
|
if (!body)
|
|
@@ -135708,17 +136423,31 @@ function extractEnumClass(node2, filePath, source) {
|
|
|
135708
136423
|
const assign = stmt.namedChild(0);
|
|
135709
136424
|
if (assign?.type !== "assignment")
|
|
135710
136425
|
continue;
|
|
136426
|
+
const left = assign.childForFieldName("left");
|
|
135711
136427
|
const right = assign.childForFieldName("right");
|
|
135712
136428
|
if (right?.type === "string") {
|
|
135713
136429
|
const v = stringValue(right, source);
|
|
135714
136430
|
if (v !== null)
|
|
135715
136431
|
values.push(v);
|
|
136432
|
+
} else if (isAutoEnum && right?.type === "call" && left?.type === "identifier") {
|
|
136433
|
+
values.push(source.slice(left.startIndex, left.endIndex));
|
|
135716
136434
|
}
|
|
135717
136435
|
}
|
|
135718
136436
|
if (values.length === 0)
|
|
135719
136437
|
return null;
|
|
135720
136438
|
return mkEnum2(name, values, "py-enum", node2, filePath);
|
|
135721
136439
|
}
|
|
136440
|
+
function superclassesHaveAutoEnum(supers, source) {
|
|
136441
|
+
for (let i = 0; i < supers.namedChildCount; i++) {
|
|
136442
|
+
const c2 = supers.namedChild(i);
|
|
136443
|
+
if (!c2)
|
|
136444
|
+
continue;
|
|
136445
|
+
const text = source.slice(c2.startIndex, c2.endIndex);
|
|
136446
|
+
if (/(^|\.)AutoEnum$/.test(text))
|
|
136447
|
+
return true;
|
|
136448
|
+
}
|
|
136449
|
+
return false;
|
|
136450
|
+
}
|
|
135722
136451
|
function superclassesLookEnum(supers, source) {
|
|
135723
136452
|
for (let i = 0; i < supers.namedChildCount; i++) {
|
|
135724
136453
|
const c2 = supers.namedChild(i);
|
|
@@ -135835,13 +136564,35 @@ var init_py_enums = __esm({
|
|
|
135835
136564
|
});
|
|
135836
136565
|
|
|
135837
136566
|
// packages/contract-verifier/dist/extractor/enum/index.js
|
|
136567
|
+
import path41 from "node:path";
|
|
135838
136568
|
async function extractEnumsFromDir(rootDir) {
|
|
135839
136569
|
const raw = [];
|
|
136570
|
+
const siblingIds = /* @__PURE__ */ new Map();
|
|
135840
136571
|
await eachParsedSource(rootDir, (s) => {
|
|
135841
136572
|
const matcher = MATCHERS2[s.lang];
|
|
135842
136573
|
if (matcher)
|
|
135843
136574
|
raw.push(...matcher(s));
|
|
136575
|
+
const base = path41.basename(s.filePath);
|
|
136576
|
+
if ((base === "index.ts" || base === "index.js") && s.lang === "typescript") {
|
|
136577
|
+
const idVal = extractIdLiteralFromExport(s.filePath, s.source, s.tree);
|
|
136578
|
+
if (idVal !== null) {
|
|
136579
|
+
const groupDir = path41.dirname(path41.dirname(s.filePath));
|
|
136580
|
+
const list = siblingIds.get(groupDir) ?? [];
|
|
136581
|
+
list.push({ value: idVal, filePath: s.filePath });
|
|
136582
|
+
siblingIds.set(groupDir, list);
|
|
136583
|
+
}
|
|
136584
|
+
}
|
|
135844
136585
|
});
|
|
136586
|
+
for (const [groupDir, entries] of siblingIds) {
|
|
136587
|
+
if (entries.length < 3)
|
|
136588
|
+
continue;
|
|
136589
|
+
raw.push({
|
|
136590
|
+
name: path41.basename(groupDir),
|
|
136591
|
+
values: [...new Set(entries.map((e) => e.value))].sort(),
|
|
136592
|
+
shape: "sibling-id-literal",
|
|
136593
|
+
source: { filePath: entries[0].filePath, lineStart: 1, lineEnd: 1 }
|
|
136594
|
+
});
|
|
136595
|
+
}
|
|
135845
136596
|
const seen = /* @__PURE__ */ new Map();
|
|
135846
136597
|
for (const e of raw) {
|
|
135847
136598
|
const key2 = `${e.name}|${e.values.join(",")}`;
|
|
@@ -135890,7 +136641,7 @@ var init_minimatch = __esm({
|
|
|
135890
136641
|
});
|
|
135891
136642
|
|
|
135892
136643
|
// packages/contract-verifier/dist/extractor/manifests.js
|
|
135893
|
-
import
|
|
136644
|
+
import fs38 from "node:fs";
|
|
135894
136645
|
import path42 from "node:path";
|
|
135895
136646
|
function collectDependencies(rootDir) {
|
|
135896
136647
|
const out = [];
|
|
@@ -135898,7 +136649,7 @@ function collectDependencies(rootDir) {
|
|
|
135898
136649
|
const visit = (dir) => {
|
|
135899
136650
|
let entries;
|
|
135900
136651
|
try {
|
|
135901
|
-
entries =
|
|
136652
|
+
entries = fs38.readdirSync(dir, { withFileTypes: true });
|
|
135902
136653
|
} catch {
|
|
135903
136654
|
return;
|
|
135904
136655
|
}
|
|
@@ -135930,7 +136681,7 @@ function collectDependencies(rootDir) {
|
|
|
135930
136681
|
function readPackageJson(filePath) {
|
|
135931
136682
|
let pkg;
|
|
135932
136683
|
try {
|
|
135933
|
-
pkg = JSON.parse(
|
|
136684
|
+
pkg = JSON.parse(fs38.readFileSync(filePath, "utf-8"));
|
|
135934
136685
|
} catch {
|
|
135935
136686
|
return [];
|
|
135936
136687
|
}
|
|
@@ -135955,7 +136706,7 @@ function pyDistName(spec) {
|
|
|
135955
136706
|
function readRequirements(filePath) {
|
|
135956
136707
|
let text;
|
|
135957
136708
|
try {
|
|
135958
|
-
text =
|
|
136709
|
+
text = fs38.readFileSync(filePath, "utf-8");
|
|
135959
136710
|
} catch {
|
|
135960
136711
|
return [];
|
|
135961
136712
|
}
|
|
@@ -135970,7 +136721,7 @@ function readRequirements(filePath) {
|
|
|
135970
136721
|
function readPyproject(filePath) {
|
|
135971
136722
|
let text;
|
|
135972
136723
|
try {
|
|
135973
|
-
text =
|
|
136724
|
+
text = fs38.readFileSync(filePath, "utf-8");
|
|
135974
136725
|
} catch {
|
|
135975
136726
|
return [];
|
|
135976
136727
|
}
|
|
@@ -135995,7 +136746,7 @@ function readPyproject(filePath) {
|
|
|
135995
136746
|
function readSetupPy(filePath) {
|
|
135996
136747
|
let text;
|
|
135997
136748
|
try {
|
|
135998
|
-
text =
|
|
136749
|
+
text = fs38.readFileSync(filePath, "utf-8");
|
|
135999
136750
|
} catch {
|
|
136000
136751
|
return [];
|
|
136001
136752
|
}
|
|
@@ -136034,7 +136785,7 @@ var init_manifests = __esm({
|
|
|
136034
136785
|
});
|
|
136035
136786
|
|
|
136036
136787
|
// packages/contract-verifier/dist/extractor/forbidden/index.js
|
|
136037
|
-
import
|
|
136788
|
+
import fs39 from "node:fs";
|
|
136038
136789
|
import path43 from "node:path";
|
|
136039
136790
|
function detectForbiddenFiles(rootDir, pattern) {
|
|
136040
136791
|
const out = [];
|
|
@@ -136042,7 +136793,7 @@ function detectForbiddenFiles(rootDir, pattern) {
|
|
|
136042
136793
|
const visit = (dir) => {
|
|
136043
136794
|
let entries;
|
|
136044
136795
|
try {
|
|
136045
|
-
entries =
|
|
136796
|
+
entries = fs39.readdirSync(dir, { withFileTypes: true });
|
|
136046
136797
|
} catch {
|
|
136047
136798
|
return;
|
|
136048
136799
|
}
|
|
@@ -136220,7 +136971,7 @@ function detectFlagInConfigFiles(rootDir, name) {
|
|
|
136220
136971
|
const visit = (dir) => {
|
|
136221
136972
|
let entries;
|
|
136222
136973
|
try {
|
|
136223
|
-
entries =
|
|
136974
|
+
entries = fs39.readdirSync(dir, { withFileTypes: true });
|
|
136224
136975
|
} catch {
|
|
136225
136976
|
return;
|
|
136226
136977
|
}
|
|
@@ -136239,7 +136990,7 @@ function detectFlagInConfigFiles(rootDir, name) {
|
|
|
136239
136990
|
continue;
|
|
136240
136991
|
let source;
|
|
136241
136992
|
try {
|
|
136242
|
-
source =
|
|
136993
|
+
source = fs39.readFileSync(full, "utf-8");
|
|
136243
136994
|
} catch {
|
|
136244
136995
|
continue;
|
|
136245
136996
|
}
|
|
@@ -136279,6 +137030,7 @@ var init_forbidden = __esm({
|
|
|
136279
137030
|
// packages/contract-verifier/dist/extractor/constant/ts-constants.js
|
|
136280
137031
|
function extractConstantsFromFile(filePath, source, tree) {
|
|
136281
137032
|
const out = [];
|
|
137033
|
+
const seenWindowGlobals = /* @__PURE__ */ new Set();
|
|
136282
137034
|
walk9(tree.rootNode, (node2) => {
|
|
136283
137035
|
if (node2.type === "variable_declarator") {
|
|
136284
137036
|
const result = extractDeclarator(node2, filePath, source);
|
|
@@ -136291,6 +137043,22 @@ function extractConstantsFromFile(filePath, source, tree) {
|
|
|
136291
137043
|
out.push(result);
|
|
136292
137044
|
return true;
|
|
136293
137045
|
}
|
|
137046
|
+
if (node2.type === "member_expression") {
|
|
137047
|
+
const objNode = node2.childForFieldName("object");
|
|
137048
|
+
const propNode = node2.childForFieldName("property");
|
|
137049
|
+
if (objNode?.text === "window" && propNode?.type === "property_identifier") {
|
|
137050
|
+
const propName = propNode.text;
|
|
137051
|
+
if (!seenWindowGlobals.has(propName)) {
|
|
137052
|
+
seenWindowGlobals.add(propName);
|
|
137053
|
+
out.push({
|
|
137054
|
+
name: propName,
|
|
137055
|
+
value: void 0,
|
|
137056
|
+
shape: "window-global",
|
|
137057
|
+
source: mkLoc(node2, filePath)
|
|
137058
|
+
});
|
|
137059
|
+
}
|
|
137060
|
+
}
|
|
137061
|
+
}
|
|
136294
137062
|
return true;
|
|
136295
137063
|
});
|
|
136296
137064
|
return out;
|
|
@@ -136330,6 +137098,12 @@ function extractDeclarator(node2, filePath, source) {
|
|
|
136330
137098
|
shape: "object-property",
|
|
136331
137099
|
source: mkLoc(kNode, filePath)
|
|
136332
137100
|
});
|
|
137101
|
+
out.push({
|
|
137102
|
+
name: `${name}.${k}`,
|
|
137103
|
+
value: v,
|
|
137104
|
+
shape: "object-property",
|
|
137105
|
+
source: mkLoc(kNode, filePath)
|
|
137106
|
+
});
|
|
136333
137107
|
}
|
|
136334
137108
|
return out;
|
|
136335
137109
|
}
|
|
@@ -136665,13 +137439,13 @@ var init_constant = __esm({
|
|
|
136665
137439
|
});
|
|
136666
137440
|
|
|
136667
137441
|
// packages/contract-verifier/dist/extractor/effect/index.js
|
|
136668
|
-
function
|
|
137442
|
+
function isCall2(n) {
|
|
136669
137443
|
return n.type === "call_expression" || n.type === "call";
|
|
136670
137444
|
}
|
|
136671
|
-
function
|
|
137445
|
+
function isMember2(n) {
|
|
136672
137446
|
return n?.type === "member_expression" || n?.type === "attribute";
|
|
136673
137447
|
}
|
|
136674
|
-
function
|
|
137448
|
+
function memberProp2(n, source) {
|
|
136675
137449
|
const p2 = n.childForFieldName("property") ?? n.childForFieldName("attribute");
|
|
136676
137450
|
return p2 ? source.slice(p2.startIndex, p2.endIndex) : "";
|
|
136677
137451
|
}
|
|
@@ -136679,7 +137453,7 @@ function memberObject(n, source) {
|
|
|
136679
137453
|
const o = n.childForFieldName("object");
|
|
136680
137454
|
return o ? source.slice(o.startIndex, o.endIndex) : "";
|
|
136681
137455
|
}
|
|
136682
|
-
function
|
|
137456
|
+
function strVal2(n, source) {
|
|
136683
137457
|
if (n.type !== "string")
|
|
136684
137458
|
return null;
|
|
136685
137459
|
const frag = n.namedChildren.find((c2) => c2.type === "string_fragment" || c2.type === "string_content");
|
|
@@ -136688,19 +137462,19 @@ function strVal(n, source) {
|
|
|
136688
137462
|
function matchEffects(s) {
|
|
136689
137463
|
const out = [];
|
|
136690
137464
|
const visit = (node2) => {
|
|
136691
|
-
if (
|
|
137465
|
+
if (isCall2(node2)) {
|
|
136692
137466
|
const fn = node2.childForFieldName("function");
|
|
136693
137467
|
const args = node2.childForFieldName("arguments");
|
|
136694
137468
|
if (fn && args) {
|
|
136695
137469
|
let channel = null;
|
|
136696
|
-
if (
|
|
137470
|
+
if (isMember2(fn) && memberProp2(fn, s.source) === "emit") {
|
|
136697
137471
|
channel = memberObject(fn, s.source) || "event-bus";
|
|
136698
137472
|
} else if (fn.type === "identifier" && /^emit/i.test(s.source.slice(fn.startIndex, fn.endIndex))) {
|
|
136699
137473
|
channel = "event-bus";
|
|
136700
137474
|
}
|
|
136701
137475
|
if (channel !== null) {
|
|
136702
137476
|
const first2 = args.namedChild(0);
|
|
136703
|
-
const event = first2 ?
|
|
137477
|
+
const event = first2 ? strVal2(first2, s.source) : null;
|
|
136704
137478
|
if (event) {
|
|
136705
137479
|
out.push({
|
|
136706
137480
|
event,
|
|
@@ -136747,255 +137521,16 @@ var init_effect = __esm({
|
|
|
136747
137521
|
function extractEmissionFacts(ops) {
|
|
136748
137522
|
const facts = /* @__PURE__ */ new Map();
|
|
136749
137523
|
for (const op of ops) {
|
|
136750
|
-
if (!op.
|
|
137524
|
+
if (!op.emission)
|
|
136751
137525
|
continue;
|
|
136752
|
-
const body = op.handlerBody;
|
|
136753
|
-
const source = op.handlerSource;
|
|
136754
|
-
const callMap = collectEmitCalls(body, source);
|
|
136755
|
-
const failureEmitSites = [];
|
|
136756
|
-
for (const [event, calls] of callMap) {
|
|
136757
|
-
for (const call of calls) {
|
|
136758
|
-
if (emitIsInFailureBlock(call, source)) {
|
|
136759
|
-
failureEmitSites.push({
|
|
136760
|
-
event,
|
|
136761
|
-
lineStart: call.startPosition.row + 1,
|
|
136762
|
-
lineEnd: call.endPosition.row + 1
|
|
136763
|
-
});
|
|
136764
|
-
}
|
|
136765
|
-
}
|
|
136766
|
-
}
|
|
136767
137526
|
facts.set(op.identity, {
|
|
136768
137527
|
filePath: op.filePath,
|
|
136769
137528
|
declarationLine: op.declarationLine,
|
|
136770
|
-
|
|
136771
|
-
hasDynamicEmit: handlerHasDynamicEmit(body, source),
|
|
136772
|
-
failureEmitSites,
|
|
136773
|
-
branchEmits: collectBranchEmits(body, source)
|
|
137529
|
+
...op.emission
|
|
136774
137530
|
});
|
|
136775
137531
|
}
|
|
136776
137532
|
return facts;
|
|
136777
137533
|
}
|
|
136778
|
-
function isCall2(n) {
|
|
136779
|
-
return n.type === "call_expression" || n.type === "call";
|
|
136780
|
-
}
|
|
136781
|
-
function isMember2(n) {
|
|
136782
|
-
return n?.type === "member_expression" || n?.type === "attribute";
|
|
136783
|
-
}
|
|
136784
|
-
function memberProp2(n, source) {
|
|
136785
|
-
const p2 = n.childForFieldName("property") ?? n.childForFieldName("attribute");
|
|
136786
|
-
return p2 ? source.slice(p2.startIndex, p2.endIndex) : "";
|
|
136787
|
-
}
|
|
136788
|
-
function isBlock(n) {
|
|
136789
|
-
return n.type === "statement_block" || n.type === "block";
|
|
136790
|
-
}
|
|
136791
|
-
function isFnBoundary(n) {
|
|
136792
|
-
return n.type === "function_declaration" || n.type === "arrow_function" || n.type === "function_expression" || n.type === "function_definition";
|
|
136793
|
-
}
|
|
136794
|
-
function isControlFlow(n) {
|
|
136795
|
-
return /^(if_statement|switch_statement|try_statement|for_statement|for_in_statement|while_statement|do_statement)$/.test(n.type);
|
|
136796
|
-
}
|
|
136797
|
-
function strVal2(n, source) {
|
|
136798
|
-
if (n.type !== "string")
|
|
136799
|
-
return null;
|
|
136800
|
-
const frag = n.namedChildren.find((c2) => c2.type === "string_fragment" || c2.type === "string_content");
|
|
136801
|
-
return frag ? source.slice(frag.startIndex, frag.endIndex) : null;
|
|
136802
|
-
}
|
|
136803
|
-
function isEmitFn(fn, source) {
|
|
136804
|
-
if (isMember2(fn))
|
|
136805
|
-
return memberProp2(fn, source) === "emit";
|
|
136806
|
-
if (fn.type === "identifier")
|
|
136807
|
-
return /^emit/i.test(source.slice(fn.startIndex, fn.endIndex));
|
|
136808
|
-
return false;
|
|
136809
|
-
}
|
|
136810
|
-
function collectEmitCalls(body, source) {
|
|
136811
|
-
const out = /* @__PURE__ */ new Map();
|
|
136812
|
-
const visit = (node2) => {
|
|
136813
|
-
if (isCall2(node2)) {
|
|
136814
|
-
const fn = node2.childForFieldName("function");
|
|
136815
|
-
const args = node2.childForFieldName("arguments");
|
|
136816
|
-
if (fn && args && isEmitFn(fn, source)) {
|
|
136817
|
-
const first2 = args.namedChild(0);
|
|
136818
|
-
const eventName = first2 ? strVal2(first2, source) : null;
|
|
136819
|
-
if (eventName !== null) {
|
|
136820
|
-
const arr = out.get(eventName) ?? [];
|
|
136821
|
-
arr.push(node2);
|
|
136822
|
-
out.set(eventName, arr);
|
|
136823
|
-
}
|
|
136824
|
-
}
|
|
136825
|
-
}
|
|
136826
|
-
for (const child of node2.namedChildren)
|
|
136827
|
-
visit(child);
|
|
136828
|
-
};
|
|
136829
|
-
visit(body);
|
|
136830
|
-
return out;
|
|
136831
|
-
}
|
|
136832
|
-
function handlerHasDynamicEmit(body, source) {
|
|
136833
|
-
let dynamic = false;
|
|
136834
|
-
const visit = (node2) => {
|
|
136835
|
-
if (dynamic)
|
|
136836
|
-
return;
|
|
136837
|
-
if (isCall2(node2)) {
|
|
136838
|
-
const fn = node2.childForFieldName("function");
|
|
136839
|
-
const args = node2.childForFieldName("arguments");
|
|
136840
|
-
if (fn && args && isEmitFn(fn, source)) {
|
|
136841
|
-
const first2 = args.namedChild(0);
|
|
136842
|
-
if (first2 && first2.type !== "string") {
|
|
136843
|
-
dynamic = true;
|
|
136844
|
-
return;
|
|
136845
|
-
}
|
|
136846
|
-
}
|
|
136847
|
-
}
|
|
136848
|
-
for (const child of node2.namedChildren) {
|
|
136849
|
-
visit(child);
|
|
136850
|
-
if (dynamic)
|
|
136851
|
-
return;
|
|
136852
|
-
}
|
|
136853
|
-
};
|
|
136854
|
-
visit(body);
|
|
136855
|
-
return dynamic;
|
|
136856
|
-
}
|
|
136857
|
-
function emitIsInFailureBlock(emitCall, source) {
|
|
136858
|
-
let cur = emitCall.parent;
|
|
136859
|
-
while (cur) {
|
|
136860
|
-
if (isBlock(cur))
|
|
136861
|
-
return blockContainsFailureStatusShallow(cur, source);
|
|
136862
|
-
if (isFnBoundary(cur))
|
|
136863
|
-
break;
|
|
136864
|
-
cur = cur.parent;
|
|
136865
|
-
}
|
|
136866
|
-
return false;
|
|
136867
|
-
}
|
|
136868
|
-
function blockContainsFailureStatusShallow(block, source) {
|
|
136869
|
-
for (const stmt of block.namedChildren) {
|
|
136870
|
-
if (containsTopLevelFailureStatus(stmt, source))
|
|
136871
|
-
return true;
|
|
136872
|
-
}
|
|
136873
|
-
return false;
|
|
136874
|
-
}
|
|
136875
|
-
function containsTopLevelFailureStatus(stmt, source) {
|
|
136876
|
-
if (isControlFlow(stmt))
|
|
136877
|
-
return false;
|
|
136878
|
-
let found = false;
|
|
136879
|
-
const visit = (node2) => {
|
|
136880
|
-
if (found)
|
|
136881
|
-
return;
|
|
136882
|
-
if (isControlFlow(node2))
|
|
136883
|
-
return;
|
|
136884
|
-
if (isCall2(node2) && callEmitsFailureStatus(node2, source)) {
|
|
136885
|
-
found = true;
|
|
136886
|
-
return;
|
|
136887
|
-
}
|
|
136888
|
-
for (const child of node2.namedChildren) {
|
|
136889
|
-
visit(child);
|
|
136890
|
-
if (found)
|
|
136891
|
-
return;
|
|
136892
|
-
}
|
|
136893
|
-
};
|
|
136894
|
-
visit(stmt);
|
|
136895
|
-
return found;
|
|
136896
|
-
}
|
|
136897
|
-
function callEmitsFailureStatus(call, source) {
|
|
136898
|
-
const fn = call.childForFieldName("function");
|
|
136899
|
-
if (!fn)
|
|
136900
|
-
return false;
|
|
136901
|
-
const args = call.childForFieldName("arguments");
|
|
136902
|
-
if (!args)
|
|
136903
|
-
return false;
|
|
136904
|
-
if (isMember2(fn) && memberProp2(fn, source) === "status") {
|
|
136905
|
-
const arg = args.namedChild(0);
|
|
136906
|
-
return !!arg && arg.type === "number" && /^[45]\d{2}$/.test(source.slice(arg.startIndex, arg.endIndex));
|
|
136907
|
-
}
|
|
136908
|
-
const fnName = fn.type === "identifier" ? source.slice(fn.startIndex, fn.endIndex) : isMember2(fn) ? memberProp2(fn, source) : "";
|
|
136909
|
-
if (fnName === "JSONResponse" || fnName === "HTTPException" || fnName === "Response") {
|
|
136910
|
-
for (let i = 0; i < args.namedChildCount; i++) {
|
|
136911
|
-
const a = args.namedChild(i);
|
|
136912
|
-
if (a?.type === "keyword_argument") {
|
|
136913
|
-
const name = a.childForFieldName("name");
|
|
136914
|
-
const value = a.childForFieldName("value");
|
|
136915
|
-
if (name && value && source.slice(name.startIndex, name.endIndex) === "status_code" && value.type === "integer" && /^[45]\d{2}$/.test(source.slice(value.startIndex, value.endIndex))) {
|
|
136916
|
-
return true;
|
|
136917
|
-
}
|
|
136918
|
-
}
|
|
136919
|
-
}
|
|
136920
|
-
const first2 = args.namedChild(0);
|
|
136921
|
-
if (first2?.type === "integer" && /^[45]\d{2}$/.test(source.slice(first2.startIndex, first2.endIndex)))
|
|
136922
|
-
return true;
|
|
136923
|
-
}
|
|
136924
|
-
return false;
|
|
136925
|
-
}
|
|
136926
|
-
function collectBranchEmits(body, source) {
|
|
136927
|
-
const out = /* @__PURE__ */ new Map();
|
|
136928
|
-
const visit = (node2) => {
|
|
136929
|
-
if (node2.type === "if_statement") {
|
|
136930
|
-
const cond = node2.childForFieldName("condition");
|
|
136931
|
-
const consequent = node2.childForFieldName("consequence");
|
|
136932
|
-
const alternative = node2.childForFieldName("alternative");
|
|
136933
|
-
const literal = cond ? conditionLiteral(cond, source) : null;
|
|
136934
|
-
if (literal !== null && consequent && !out.has(literal)) {
|
|
136935
|
-
out.set(literal, {
|
|
136936
|
-
consequentEmits: blockHasAnyEmit(consequent, source),
|
|
136937
|
-
alternativeEmits: alternative ? blockHasAnyEmit(alternative, source) : false
|
|
136938
|
-
});
|
|
136939
|
-
}
|
|
136940
|
-
}
|
|
136941
|
-
for (const child of node2.namedChildren)
|
|
136942
|
-
visit(child);
|
|
136943
|
-
};
|
|
136944
|
-
visit(body);
|
|
136945
|
-
return out;
|
|
136946
|
-
}
|
|
136947
|
-
function conditionLiteral(node2, source) {
|
|
136948
|
-
const u2 = unwrapParens(node2);
|
|
136949
|
-
if (u2.type === "binary_expression") {
|
|
136950
|
-
const opNode = u2.childForFieldName("operator");
|
|
136951
|
-
const op = opNode ? source.slice(opNode.startIndex, opNode.endIndex) : "";
|
|
136952
|
-
if (op !== "===" && op !== "==")
|
|
136953
|
-
return null;
|
|
136954
|
-
return litText(u2.childForFieldName("left"), source) ?? litText(u2.childForFieldName("right"), source);
|
|
136955
|
-
}
|
|
136956
|
-
if (u2.type === "comparison_operator") {
|
|
136957
|
-
const a = u2.namedChild(0);
|
|
136958
|
-
const b = u2.namedChild(1);
|
|
136959
|
-
if (!a || !b || source.slice(a.endIndex, b.startIndex).trim() !== "==")
|
|
136960
|
-
return null;
|
|
136961
|
-
return litText(a, source) ?? litText(b, source);
|
|
136962
|
-
}
|
|
136963
|
-
return null;
|
|
136964
|
-
}
|
|
136965
|
-
function litText(node2, source) {
|
|
136966
|
-
return node2 && node2.type === "string" ? strVal2(node2, source) : null;
|
|
136967
|
-
}
|
|
136968
|
-
function unwrapParens(node2) {
|
|
136969
|
-
let cur = node2;
|
|
136970
|
-
while (cur.type === "parenthesized_expression") {
|
|
136971
|
-
const child = cur.namedChildren[0];
|
|
136972
|
-
if (!child)
|
|
136973
|
-
break;
|
|
136974
|
-
cur = child;
|
|
136975
|
-
}
|
|
136976
|
-
return cur;
|
|
136977
|
-
}
|
|
136978
|
-
function blockHasAnyEmit(block, source) {
|
|
136979
|
-
let found = false;
|
|
136980
|
-
const visit = (node2) => {
|
|
136981
|
-
if (found)
|
|
136982
|
-
return;
|
|
136983
|
-
if (isCall2(node2)) {
|
|
136984
|
-
const fn = node2.childForFieldName("function");
|
|
136985
|
-
if (fn && isEmitFn(fn, source)) {
|
|
136986
|
-
found = true;
|
|
136987
|
-
return;
|
|
136988
|
-
}
|
|
136989
|
-
}
|
|
136990
|
-
for (const child of node2.namedChildren) {
|
|
136991
|
-
visit(child);
|
|
136992
|
-
if (found)
|
|
136993
|
-
return;
|
|
136994
|
-
}
|
|
136995
|
-
};
|
|
136996
|
-
visit(block);
|
|
136997
|
-
return found;
|
|
136998
|
-
}
|
|
136999
137534
|
var init_emission_facts = __esm({
|
|
137000
137535
|
"packages/contract-verifier/dist/extractor/effect/emission-facts.js"() {
|
|
137001
137536
|
"use strict";
|
|
@@ -137003,7 +137538,7 @@ var init_emission_facts = __esm({
|
|
|
137003
137538
|
});
|
|
137004
137539
|
|
|
137005
137540
|
// packages/contract-verifier/dist/extractor/entity-schema/index.js
|
|
137006
|
-
import
|
|
137541
|
+
import fs40 from "node:fs";
|
|
137007
137542
|
import path44 from "node:path";
|
|
137008
137543
|
async function extractEntitiesFromDir(rootDir) {
|
|
137009
137544
|
const tcIgnore = loadTcIgnore(rootDir);
|
|
@@ -137011,7 +137546,7 @@ async function extractEntitiesFromDir(rootDir) {
|
|
|
137011
137546
|
const walk11 = (dir) => {
|
|
137012
137547
|
let entries;
|
|
137013
137548
|
try {
|
|
137014
|
-
entries =
|
|
137549
|
+
entries = fs40.readdirSync(dir, { withFileTypes: true });
|
|
137015
137550
|
} catch {
|
|
137016
137551
|
return;
|
|
137017
137552
|
}
|
|
@@ -137024,7 +137559,7 @@ async function extractEntitiesFromDir(rootDir) {
|
|
|
137024
137559
|
if (e.isDirectory())
|
|
137025
137560
|
walk11(full);
|
|
137026
137561
|
else if (e.isFile() && e.name.endsWith(".prisma")) {
|
|
137027
|
-
out.push(...parsePrismaModels(full,
|
|
137562
|
+
out.push(...parsePrismaModels(full, fs40.readFileSync(full, "utf-8")));
|
|
137028
137563
|
}
|
|
137029
137564
|
}
|
|
137030
137565
|
};
|
|
@@ -137568,7 +138103,7 @@ async function extractFormulaFacts(rootDir, targetField) {
|
|
|
137568
138103
|
return result;
|
|
137569
138104
|
}
|
|
137570
138105
|
function candidateNames(fieldName) {
|
|
137571
|
-
const camel = fieldName.replace(/_([a-z])/g, (
|
|
138106
|
+
const camel = fieldName.replace(/_([a-z])/g, (_2, c2) => c2.toUpperCase());
|
|
137572
138107
|
const snake = fieldName.replace(/[A-Z]/g, (c2) => `_${c2.toLowerCase()}`);
|
|
137573
138108
|
const names = /* @__PURE__ */ new Set();
|
|
137574
138109
|
for (const base of /* @__PURE__ */ new Set([fieldName, camel, snake])) {
|
|
@@ -138119,7 +138654,7 @@ var init_characteristic_imports = __esm({
|
|
|
138119
138654
|
});
|
|
138120
138655
|
|
|
138121
138656
|
// packages/contract-verifier/dist/extractor/architecture/shared/config-files.js
|
|
138122
|
-
import
|
|
138657
|
+
import fs41 from "node:fs";
|
|
138123
138658
|
import path45 from "node:path";
|
|
138124
138659
|
function collectFileIndex(rootDir) {
|
|
138125
138660
|
const files = [];
|
|
@@ -138128,7 +138663,7 @@ function collectFileIndex(rootDir) {
|
|
|
138128
138663
|
const visit = (dir) => {
|
|
138129
138664
|
let entries;
|
|
138130
138665
|
try {
|
|
138131
|
-
entries =
|
|
138666
|
+
entries = fs41.readdirSync(dir, { withFileTypes: true });
|
|
138132
138667
|
} catch {
|
|
138133
138668
|
return;
|
|
138134
138669
|
}
|
|
@@ -138154,7 +138689,7 @@ function collectFileIndex(rootDir) {
|
|
|
138154
138689
|
return cache2.get(relPath);
|
|
138155
138690
|
let text;
|
|
138156
138691
|
try {
|
|
138157
|
-
text =
|
|
138692
|
+
text = fs41.readFileSync(path45.join(rootDir, relPath), "utf-8");
|
|
138158
138693
|
} catch {
|
|
138159
138694
|
text = null;
|
|
138160
138695
|
}
|
|
@@ -138607,15 +139142,24 @@ __export(extractor_exports, {
|
|
|
138607
139142
|
extractStateMachineFacts: () => extractStateMachineFacts,
|
|
138608
139143
|
getArchitectureDetector: () => getArchitectureDetector
|
|
138609
139144
|
});
|
|
138610
|
-
import
|
|
139145
|
+
import fs42 from "node:fs";
|
|
138611
139146
|
import path47 from "node:path";
|
|
139147
|
+
function isTestFile2(filePath) {
|
|
139148
|
+
const parts = filePath.replace(/\\/g, "/").split("/");
|
|
139149
|
+
const fileName = parts[parts.length - 1];
|
|
139150
|
+
if (/\.(test|spec)\.(t|j)sx?$/.test(fileName))
|
|
139151
|
+
return true;
|
|
139152
|
+
if (parts.includes("__tests__"))
|
|
139153
|
+
return true;
|
|
139154
|
+
return false;
|
|
139155
|
+
}
|
|
138612
139156
|
async function extractOperationsFromDir(rootDir) {
|
|
138613
139157
|
await initParsers();
|
|
138614
139158
|
const rawOps = [];
|
|
138615
139159
|
const fileAnalyses = [];
|
|
138616
139160
|
const tcIgnore = loadTcIgnore(rootDir);
|
|
138617
139161
|
const visit = (dir) => {
|
|
138618
|
-
for (const entry of
|
|
139162
|
+
for (const entry of fs42.readdirSync(dir, { withFileTypes: true })) {
|
|
138619
139163
|
if (entry.name === "node_modules" || entry.name === ".git")
|
|
138620
139164
|
continue;
|
|
138621
139165
|
const full = path47.join(dir, entry.name);
|
|
@@ -138630,13 +139174,19 @@ async function extractOperationsFromDir(rootDir) {
|
|
|
138630
139174
|
const ext2 = path47.extname(entry.name);
|
|
138631
139175
|
if (!TS_EXT4.has(ext2))
|
|
138632
139176
|
continue;
|
|
138633
|
-
|
|
139177
|
+
if (isTestFile2(full))
|
|
139178
|
+
continue;
|
|
139179
|
+
const source = fs42.readFileSync(full, "utf-8");
|
|
138634
139180
|
const lang = ext2 === ".ts" || ext2 === ".tsx" ? ext2 === ".tsx" ? "tsx" : "typescript" : "javascript";
|
|
139181
|
+
let tree;
|
|
138635
139182
|
try {
|
|
138636
|
-
|
|
139183
|
+
tree = parseFile(full, source, lang);
|
|
138637
139184
|
rawOps.push(...extractOperationsFromFile(full, source, tree));
|
|
139185
|
+
rawOps.push(...extractPluginStyleRoutesFromFile(full, source, tree));
|
|
138638
139186
|
fileAnalyses.push(analyzeRouterFile(full, source, tree));
|
|
138639
139187
|
} catch {
|
|
139188
|
+
} finally {
|
|
139189
|
+
tree?.delete();
|
|
138640
139190
|
}
|
|
138641
139191
|
}
|
|
138642
139192
|
};
|
|
@@ -138702,6 +139252,8 @@ function compareOperation(input) {
|
|
|
138702
139252
|
const codeByStatus = /* @__PURE__ */ new Map();
|
|
138703
139253
|
for (const r of input.code.responses)
|
|
138704
139254
|
codeByStatus.set(r.status, r);
|
|
139255
|
+
if (input.code.responses.length === 0)
|
|
139256
|
+
return out;
|
|
138705
139257
|
for (const specResp of input.spec.responses) {
|
|
138706
139258
|
if (specResp.inheritedFrom)
|
|
138707
139259
|
continue;
|
|
@@ -139090,6 +139642,8 @@ function compareAuthRequirement(input) {
|
|
|
139090
139642
|
continue;
|
|
139091
139643
|
if (!matchesOperation2(input.contract.selector, op, specOp))
|
|
139092
139644
|
continue;
|
|
139645
|
+
if (op.authExempt)
|
|
139646
|
+
continue;
|
|
139093
139647
|
if (input.protectedFiles.has(op.filePath))
|
|
139094
139648
|
continue;
|
|
139095
139649
|
if (input.contract.except?.some((ex) => matchesOperation2(ex, op, specOp)))
|
|
@@ -139186,9 +139740,9 @@ function compareAuthorizationRule(input) {
|
|
|
139186
139740
|
for (const op of input.recognizedOps) {
|
|
139187
139741
|
if (!targets.has(op.identity))
|
|
139188
139742
|
continue;
|
|
139189
|
-
if (!op.
|
|
139743
|
+
if (!op.ownershipCheckCandidates)
|
|
139190
139744
|
continue;
|
|
139191
|
-
if (
|
|
139745
|
+
if (op.ownershipCheckCandidates.some((c2) => c2.resourceField === fieldName))
|
|
139192
139746
|
continue;
|
|
139193
139747
|
out.push({
|
|
139194
139748
|
id: randomUUID13(),
|
|
@@ -139212,79 +139766,6 @@ function parseResourceField(predicate) {
|
|
|
139212
139766
|
return m[1];
|
|
139213
139767
|
return null;
|
|
139214
139768
|
}
|
|
139215
|
-
function handlerHasOwnershipCheck(body, source, fieldName) {
|
|
139216
|
-
let found = false;
|
|
139217
|
-
const check = (left, right) => (matchesAuthSide(left, source) || matchesAuthSide(right, source)) && (matchesResourceField(left, source, fieldName) || matchesResourceField(right, source, fieldName));
|
|
139218
|
-
const visit = (node2) => {
|
|
139219
|
-
if (found)
|
|
139220
|
-
return;
|
|
139221
|
-
if (node2.type === "binary_expression") {
|
|
139222
|
-
const opNode = node2.childForFieldName("operator");
|
|
139223
|
-
const op = opNode ? source.slice(opNode.startIndex, opNode.endIndex) : "";
|
|
139224
|
-
if (op === "===" || op === "==" || op === "!==" || op === "!=") {
|
|
139225
|
-
const left = node2.childForFieldName("left");
|
|
139226
|
-
const right = node2.childForFieldName("right");
|
|
139227
|
-
if (left && right && check(left, right)) {
|
|
139228
|
-
found = true;
|
|
139229
|
-
return;
|
|
139230
|
-
}
|
|
139231
|
-
}
|
|
139232
|
-
}
|
|
139233
|
-
if (node2.type === "comparison_operator") {
|
|
139234
|
-
const a = node2.namedChild(0);
|
|
139235
|
-
const b = node2.namedChild(1);
|
|
139236
|
-
if (a && b) {
|
|
139237
|
-
const op = source.slice(a.endIndex, b.startIndex).trim();
|
|
139238
|
-
if ((op === "==" || op === "!=") && check(a, b)) {
|
|
139239
|
-
found = true;
|
|
139240
|
-
return;
|
|
139241
|
-
}
|
|
139242
|
-
}
|
|
139243
|
-
}
|
|
139244
|
-
for (const child of node2.namedChildren) {
|
|
139245
|
-
visit(child);
|
|
139246
|
-
if (found)
|
|
139247
|
-
return;
|
|
139248
|
-
}
|
|
139249
|
-
};
|
|
139250
|
-
visit(body);
|
|
139251
|
-
return found;
|
|
139252
|
-
}
|
|
139253
|
-
function matchesAuthSide(node2, source) {
|
|
139254
|
-
if (node2.type === "attribute") {
|
|
139255
|
-
return /\b(req|request)\.(auth|user)\b|\bcurrent_user\b/.test(source.slice(node2.startIndex, node2.endIndex));
|
|
139256
|
-
}
|
|
139257
|
-
let cur = node2;
|
|
139258
|
-
while (cur) {
|
|
139259
|
-
if (cur.type === "identifier") {
|
|
139260
|
-
const text = source.slice(cur.startIndex, cur.endIndex);
|
|
139261
|
-
if (text === "req" || text === "request")
|
|
139262
|
-
return false;
|
|
139263
|
-
return false;
|
|
139264
|
-
}
|
|
139265
|
-
if (cur.type === "member_expression") {
|
|
139266
|
-
const text = source.slice(cur.startIndex, cur.endIndex);
|
|
139267
|
-
if (/\b(req|request)\.auth\b|\b(req|request)\?\.auth\b/.test(text))
|
|
139268
|
-
return true;
|
|
139269
|
-
cur = cur.childForFieldName("object");
|
|
139270
|
-
continue;
|
|
139271
|
-
}
|
|
139272
|
-
if (cur.type === "optional_chain_expression" || cur.type === "subscript_expression") {
|
|
139273
|
-
cur = cur.childForFieldName("object");
|
|
139274
|
-
continue;
|
|
139275
|
-
}
|
|
139276
|
-
return false;
|
|
139277
|
-
}
|
|
139278
|
-
return false;
|
|
139279
|
-
}
|
|
139280
|
-
function matchesResourceField(node2, source, fieldName) {
|
|
139281
|
-
if (node2.type !== "member_expression" && node2.type !== "attribute")
|
|
139282
|
-
return false;
|
|
139283
|
-
const prop = node2.childForFieldName("property") ?? node2.childForFieldName("attribute");
|
|
139284
|
-
if (!prop)
|
|
139285
|
-
return false;
|
|
139286
|
-
return source.slice(prop.startIndex, prop.endIndex) === fieldName;
|
|
139287
|
-
}
|
|
139288
139769
|
var init_authorization_rule2 = __esm({
|
|
139289
139770
|
"packages/contract-verifier/dist/comparator/authorization-rule.js"() {
|
|
139290
139771
|
"use strict";
|
|
@@ -139972,10 +140453,10 @@ function compareEnum(input) {
|
|
|
139972
140453
|
});
|
|
139973
140454
|
} else {
|
|
139974
140455
|
for (const m of nameMatches) {
|
|
139975
|
-
const
|
|
139976
|
-
const
|
|
139977
|
-
const missing = contract.values.filter((v) => !
|
|
139978
|
-
const extra = m.values.filter((v) => !
|
|
140456
|
+
const specNorm = new Map(contract.values.map((v) => [normalizeValue(v), v]));
|
|
140457
|
+
const codeNorm = new Map(m.values.map((v) => [normalizeValue(v), v]));
|
|
140458
|
+
const missing = contract.values.filter((v) => !codeNorm.has(normalizeValue(v)));
|
|
140459
|
+
const extra = m.values.filter((v) => !specNorm.has(normalizeValue(v)));
|
|
139979
140460
|
for (const v of missing) {
|
|
139980
140461
|
drifts.push(mkValueDrift(ref, "missing-value", v, m, contract.values));
|
|
139981
140462
|
}
|
|
@@ -140003,10 +140484,10 @@ function compareEnum(input) {
|
|
|
140003
140484
|
continue;
|
|
140004
140485
|
}
|
|
140005
140486
|
for (const m of subsetMatches) {
|
|
140006
|
-
const
|
|
140007
|
-
const
|
|
140008
|
-
const missing = subset.values.filter((v) => !
|
|
140009
|
-
const extra = m.values.filter((v) => !
|
|
140487
|
+
const specNorm = new Map(subset.values.map((v) => [normalizeValue(v), v]));
|
|
140488
|
+
const codeNorm = new Map(m.values.map((v) => [normalizeValue(v), v]));
|
|
140489
|
+
const missing = subset.values.filter((v) => !codeNorm.has(normalizeValue(v)));
|
|
140490
|
+
const extra = m.values.filter((v) => !specNorm.has(normalizeValue(v)));
|
|
140010
140491
|
for (const v of missing) {
|
|
140011
140492
|
drifts.push(mkSubsetDrift(ref, subset.name, "missing-value", v, m, subset.values));
|
|
140012
140493
|
}
|
|
@@ -140036,6 +140517,12 @@ function matchByName(contract, codeEnums, specName) {
|
|
|
140036
140517
|
continue;
|
|
140037
140518
|
}
|
|
140038
140519
|
if (codeName.includes(target) || target.includes(codeName)) {
|
|
140520
|
+
if (!codeName.includes(target)) {
|
|
140521
|
+
const qualifyingPrefix = target.slice(0, target.indexOf(codeName));
|
|
140522
|
+
if (qualifyingPrefix.length > 5) {
|
|
140523
|
+
continue;
|
|
140524
|
+
}
|
|
140525
|
+
}
|
|
140039
140526
|
if (valueSetOverlap(contract.values, e.values) >= 0.5) {
|
|
140040
140527
|
out.push(e);
|
|
140041
140528
|
continue;
|
|
@@ -140048,13 +140535,22 @@ function matchByName(contract, codeEnums, specName) {
|
|
|
140048
140535
|
if (overlap >= 0.6 && sizeDiff <= 2) {
|
|
140049
140536
|
out.push(e);
|
|
140050
140537
|
}
|
|
140538
|
+
} else if (minLen >= 2) {
|
|
140539
|
+
const overlap = valueSetOverlap(contract.values, e.values);
|
|
140540
|
+
const sizeDiff = Math.abs(contract.values.length - e.values.length);
|
|
140541
|
+
if (overlap === 1 && sizeDiff === 0) {
|
|
140542
|
+
out.push(e);
|
|
140543
|
+
}
|
|
140051
140544
|
}
|
|
140052
140545
|
}
|
|
140053
140546
|
return out;
|
|
140054
140547
|
}
|
|
140548
|
+
function normalizeValue(v) {
|
|
140549
|
+
return v.replace(/[^A-Za-z0-9]/g, "").toLowerCase();
|
|
140550
|
+
}
|
|
140055
140551
|
function valueSetOverlap(a, b) {
|
|
140056
|
-
const aSet = new Set(a);
|
|
140057
|
-
const bSet = new Set(b);
|
|
140552
|
+
const aSet = new Set(a.map(normalizeValue));
|
|
140553
|
+
const bSet = new Set(b.map(normalizeValue));
|
|
140058
140554
|
const intersection = [...aSet].filter((v) => bSet.has(v)).length;
|
|
140059
140555
|
const union = (/* @__PURE__ */ new Set([...aSet, ...bSet])).size;
|
|
140060
140556
|
return union === 0 ? 0 : intersection / union;
|
|
@@ -140166,7 +140662,26 @@ import { randomUUID as randomUUID21 } from "node:crypto";
|
|
|
140166
140662
|
function compareNamedConstant(input) {
|
|
140167
140663
|
const { ref, contract, codeConstants } = input;
|
|
140168
140664
|
const target = normalizeName2(ref.identity);
|
|
140169
|
-
|
|
140665
|
+
let matches = codeConstants.filter((c2) => normalizeName2(c2.name) === target);
|
|
140666
|
+
if (matches.length === 0 && ref.identity.includes(".")) {
|
|
140667
|
+
const lastPart = ref.identity.split(".").pop();
|
|
140668
|
+
const lastTarget = normalizeName2(lastPart);
|
|
140669
|
+
matches = codeConstants.filter((c2) => {
|
|
140670
|
+
if (normalizeName2(c2.name) !== lastTarget)
|
|
140671
|
+
return false;
|
|
140672
|
+
if (c2.shape === "window-global")
|
|
140673
|
+
return true;
|
|
140674
|
+
if (c2.shape === "const-literal") {
|
|
140675
|
+
return contract.expectedValue === void 0 || deepEqual(
|
|
140676
|
+
contract.expectedValue,
|
|
140677
|
+
c2.value,
|
|
140678
|
+
/*allowExtraCodeKeys*/
|
|
140679
|
+
true
|
|
140680
|
+
);
|
|
140681
|
+
}
|
|
140682
|
+
return false;
|
|
140683
|
+
});
|
|
140684
|
+
}
|
|
140170
140685
|
if (matches.length === 0) {
|
|
140171
140686
|
return [{
|
|
140172
140687
|
id: randomUUID21(),
|
|
@@ -140177,11 +140692,14 @@ function compareNamedConstant(input) {
|
|
|
140177
140692
|
filePath: ref.identity,
|
|
140178
140693
|
lineStart: 0,
|
|
140179
140694
|
lineEnd: 0,
|
|
140180
|
-
message: `Spec declares constant \`${ref.identity}\`
|
|
140695
|
+
message: `Spec declares constant \`${ref.identity}\` but no code-side constant matches by name.`,
|
|
140181
140696
|
specSide: `expected: ${formatValue2(contract.expectedValue)}`,
|
|
140182
140697
|
codeSide: "<no match>"
|
|
140183
140698
|
}];
|
|
140184
140699
|
}
|
|
140700
|
+
if (contract.expectedValue === void 0) {
|
|
140701
|
+
return [];
|
|
140702
|
+
}
|
|
140185
140703
|
const drifts = [];
|
|
140186
140704
|
for (const m of matches) {
|
|
140187
140705
|
if (deepEqual(
|
|
@@ -140272,6 +140790,8 @@ function compareArchitectureDecision(input) {
|
|
|
140272
140790
|
const { ref, contract, detected, codeDir } = input;
|
|
140273
140791
|
const { category, chosen, reason } = contract;
|
|
140274
140792
|
const why = reason ? ` Rationale: ${reason}` : "";
|
|
140793
|
+
if (!chosen)
|
|
140794
|
+
return [];
|
|
140275
140795
|
if (detected.confidence === "inconclusive") {
|
|
140276
140796
|
return [
|
|
140277
140797
|
mk(ref, `architecture.${category}.inconclusive`, "info", codeDir, 0, `Spec asserts ${category} = \`${chosen}\`, but no ${category} signals were found in the code \u2014 the claim is not testable from current signals.${why}`, `${category}: ${chosen}`, "<no signals>")
|
|
@@ -140375,12 +140895,12 @@ var init_types4 = __esm({
|
|
|
140375
140895
|
});
|
|
140376
140896
|
|
|
140377
140897
|
// packages/contract-verifier/dist/verify.js
|
|
140378
|
-
import
|
|
140898
|
+
import fs43 from "node:fs";
|
|
140379
140899
|
import path49 from "node:path";
|
|
140380
140900
|
async function verify(opts) {
|
|
140381
140901
|
const specFiles = [];
|
|
140382
140902
|
walkTcFiles(opts.contractsDir, (filePath) => {
|
|
140383
|
-
const source =
|
|
140903
|
+
const source = fs43.readFileSync(filePath, "utf-8");
|
|
140384
140904
|
specFiles.push(parseFile2(filePath, source));
|
|
140385
140905
|
}, opts.includeInferred ?? false);
|
|
140386
140906
|
const resolution = resolve8(specFiles);
|
|
@@ -140684,7 +141204,7 @@ async function verify(opts) {
|
|
|
140684
141204
|
};
|
|
140685
141205
|
}
|
|
140686
141206
|
function walkTcFiles(rootDir, visit, includeInferred) {
|
|
140687
|
-
for (const entry of
|
|
141207
|
+
for (const entry of fs43.readdirSync(rootDir, { withFileTypes: true })) {
|
|
140688
141208
|
const full = path49.join(rootDir, entry.name);
|
|
140689
141209
|
if (entry.isDirectory()) {
|
|
140690
141210
|
if (!includeInferred && entry.name === "_inferred")
|
|
@@ -140930,7 +141450,7 @@ var init_serialize = __esm({
|
|
|
140930
141450
|
});
|
|
140931
141451
|
|
|
140932
141452
|
// packages/contract-verifier/dist/infer/index.js
|
|
140933
|
-
import
|
|
141453
|
+
import fs44 from "node:fs";
|
|
140934
141454
|
import path51 from "node:path";
|
|
140935
141455
|
async function infer(opts) {
|
|
140936
141456
|
const authored = loadAuthored(opts.contractsDir);
|
|
@@ -140954,7 +141474,7 @@ async function infer(opts) {
|
|
|
140954
141474
|
function loadAuthored(contractsDir) {
|
|
140955
141475
|
const files = [];
|
|
140956
141476
|
walkAuthoredTcFiles(contractsDir, (filePath) => {
|
|
140957
|
-
files.push(parseFile2(filePath,
|
|
141477
|
+
files.push(parseFile2(filePath, fs44.readFileSync(filePath, "utf-8")));
|
|
140958
141478
|
});
|
|
140959
141479
|
return resolve8(files).index;
|
|
140960
141480
|
}
|
|
@@ -141494,21 +142014,21 @@ function writeInferred(contractsDir, decisions, options = {}) {
|
|
|
141494
142014
|
proposed.push(filePath);
|
|
141495
142015
|
continue;
|
|
141496
142016
|
}
|
|
141497
|
-
|
|
141498
|
-
const existing =
|
|
142017
|
+
fs44.mkdirSync(path51.dirname(filePath), { recursive: true });
|
|
142018
|
+
const existing = fs44.existsSync(filePath) ? fs44.readFileSync(filePath, "utf-8") : null;
|
|
141499
142019
|
if (existing === r.tcSource)
|
|
141500
142020
|
continue;
|
|
141501
|
-
|
|
142021
|
+
fs44.writeFileSync(filePath, r.tcSource);
|
|
141502
142022
|
written.push(filePath);
|
|
141503
142023
|
}
|
|
141504
|
-
if (!options.dryRun &&
|
|
142024
|
+
if (!options.dryRun && fs44.existsSync(root))
|
|
141505
142025
|
pruneStale(root, live);
|
|
141506
142026
|
return { written, proposed };
|
|
141507
142027
|
}
|
|
141508
142028
|
function walkAuthoredTcFiles(rootDir, visit) {
|
|
141509
|
-
if (!
|
|
142029
|
+
if (!fs44.existsSync(rootDir))
|
|
141510
142030
|
return;
|
|
141511
|
-
for (const entry of
|
|
142031
|
+
for (const entry of fs44.readdirSync(rootDir, { withFileTypes: true })) {
|
|
141512
142032
|
if (entry.isDirectory()) {
|
|
141513
142033
|
if (entry.name === INFERRED_DIR)
|
|
141514
142034
|
continue;
|
|
@@ -141524,22 +142044,22 @@ function toRelCode(codeDir, fp) {
|
|
|
141524
142044
|
}
|
|
141525
142045
|
function pruneStale(root, live) {
|
|
141526
142046
|
const visit = (dir) => {
|
|
141527
|
-
if (!
|
|
142047
|
+
if (!fs44.existsSync(dir))
|
|
141528
142048
|
return false;
|
|
141529
142049
|
let dirEmpty = true;
|
|
141530
|
-
for (const entry of
|
|
142050
|
+
for (const entry of fs44.readdirSync(dir, { withFileTypes: true })) {
|
|
141531
142051
|
const full = path51.join(dir, entry.name);
|
|
141532
142052
|
if (entry.isDirectory()) {
|
|
141533
142053
|
if (visit(full)) {
|
|
141534
142054
|
try {
|
|
141535
|
-
|
|
142055
|
+
fs44.rmdirSync(full);
|
|
141536
142056
|
} catch {
|
|
141537
142057
|
}
|
|
141538
142058
|
} else {
|
|
141539
142059
|
dirEmpty = false;
|
|
141540
142060
|
}
|
|
141541
142061
|
} else if (entry.isFile() && entry.name.endsWith(".tc") && !live.has(full)) {
|
|
141542
|
-
|
|
142062
|
+
fs44.unlinkSync(full);
|
|
141543
142063
|
} else {
|
|
141544
142064
|
dirEmpty = false;
|
|
141545
142065
|
}
|
|
@@ -141698,9 +142218,10 @@ async function runAdd(options = {}) {
|
|
|
141698
142218
|
|
|
141699
142219
|
// tools/cli/src/commands/analyze.ts
|
|
141700
142220
|
init_dist4();
|
|
141701
|
-
import
|
|
142221
|
+
import path18 from "node:path";
|
|
141702
142222
|
|
|
141703
142223
|
// packages/shared/dist/llm/transport.js
|
|
142224
|
+
init_claude_binary();
|
|
141704
142225
|
import { spawn } from "node:child_process";
|
|
141705
142226
|
import { createHash as createHash2, randomUUID } from "node:crypto";
|
|
141706
142227
|
import fs4 from "node:fs";
|
|
@@ -141711,7 +142232,7 @@ function stripCodeFences(text) {
|
|
|
141711
142232
|
return fence ? fence[1] : trimmed2;
|
|
141712
142233
|
}
|
|
141713
142234
|
function cliTransport(opts = {}) {
|
|
141714
|
-
const bin = opts.bin ??
|
|
142235
|
+
const bin = opts.bin ?? resolveClaudeBinary();
|
|
141715
142236
|
return (req) => new Promise((resolve9, reject) => {
|
|
141716
142237
|
const modelArgs = [];
|
|
141717
142238
|
if (req.model)
|
|
@@ -141859,8 +142380,12 @@ init_project_config();
|
|
|
141859
142380
|
init_git();
|
|
141860
142381
|
init_logger();
|
|
141861
142382
|
|
|
142383
|
+
// tools/cli/src/lib/claude-preflight.ts
|
|
142384
|
+
init_dist4();
|
|
142385
|
+
|
|
141862
142386
|
// packages/core/dist/lib/cli-binary.js
|
|
141863
142387
|
var import_cross_spawn2 = __toESM(require_cross_spawn(), 1);
|
|
142388
|
+
init_dist7();
|
|
141864
142389
|
function isCliBinaryAvailable(binary2) {
|
|
141865
142390
|
const result = (0, import_cross_spawn2.sync)(binary2, ["--version"], {
|
|
141866
142391
|
stdio: "ignore",
|
|
@@ -141868,9 +142393,80 @@ function isCliBinaryAvailable(binary2) {
|
|
|
141868
142393
|
});
|
|
141869
142394
|
return result.status === 0;
|
|
141870
142395
|
}
|
|
142396
|
+
function cleanClaudeEnv() {
|
|
142397
|
+
const env = { ...process.env };
|
|
142398
|
+
for (const key2 of Object.keys(env)) {
|
|
142399
|
+
if (key2.startsWith("CLAUDE_CODE") || key2.startsWith("CLAUDE_INTERNAL")) {
|
|
142400
|
+
delete env[key2];
|
|
142401
|
+
}
|
|
142402
|
+
}
|
|
142403
|
+
return env;
|
|
142404
|
+
}
|
|
142405
|
+
function classifyClaudeProbe(code, output) {
|
|
142406
|
+
if (code === 0)
|
|
142407
|
+
return { ok: true };
|
|
142408
|
+
return { ok: false, reason: "failed", code, output: output.trim() };
|
|
142409
|
+
}
|
|
142410
|
+
function checkClaudeAuth(binary2 = resolveClaudeBinary(), options = {}) {
|
|
142411
|
+
if (!isCliBinaryAvailable(binary2)) {
|
|
142412
|
+
return Promise.resolve({ ok: false, reason: "not-found" });
|
|
142413
|
+
}
|
|
142414
|
+
const timeoutMs = options.timeoutMs ?? 6e4;
|
|
142415
|
+
return new Promise((resolve9) => {
|
|
142416
|
+
const proc = (0, import_cross_spawn2.spawn)(binary2, ["-p", "Reply with the single word: ok"], {
|
|
142417
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
142418
|
+
env: cleanClaudeEnv()
|
|
142419
|
+
});
|
|
142420
|
+
const output = [];
|
|
142421
|
+
let settled = false;
|
|
142422
|
+
const finish = (r) => {
|
|
142423
|
+
if (settled)
|
|
142424
|
+
return;
|
|
142425
|
+
settled = true;
|
|
142426
|
+
clearTimeout(timer);
|
|
142427
|
+
resolve9(r);
|
|
142428
|
+
};
|
|
142429
|
+
const timer = setTimeout(() => {
|
|
142430
|
+
proc.kill("SIGKILL");
|
|
142431
|
+
finish({ ok: true });
|
|
142432
|
+
}, timeoutMs);
|
|
142433
|
+
proc.stdout?.on("data", (b) => output.push(b));
|
|
142434
|
+
proc.stderr?.on("data", (b) => output.push(b));
|
|
142435
|
+
proc.on("error", () => finish({ ok: true }));
|
|
142436
|
+
proc.on("close", (code) => finish(classifyClaudeProbe(code, Buffer.concat(output).toString("utf-8"))));
|
|
142437
|
+
});
|
|
142438
|
+
}
|
|
142439
|
+
|
|
142440
|
+
// tools/cli/src/lib/claude-preflight.ts
|
|
142441
|
+
async function preflightClaudeOrExit() {
|
|
142442
|
+
const s = fe();
|
|
142443
|
+
s.start("Checking the `claude` CLI is logged in");
|
|
142444
|
+
const result = await checkClaudeAuth();
|
|
142445
|
+
if (result.ok) {
|
|
142446
|
+
s.stop("`claude` CLI ready");
|
|
142447
|
+
return;
|
|
142448
|
+
}
|
|
142449
|
+
s.stop("`claude` CLI not ready");
|
|
142450
|
+
const { title, hint } = describeClaudePreflightFailure(result);
|
|
142451
|
+
O2.error(title);
|
|
142452
|
+
O2.message(hint);
|
|
142453
|
+
pt("Aborted \u2014 fix the `claude` CLI and retry.");
|
|
142454
|
+
process.exit(1);
|
|
142455
|
+
}
|
|
142456
|
+
function describeClaudePreflightFailure(result) {
|
|
142457
|
+
if (result.reason === "not-found") {
|
|
142458
|
+
return {
|
|
142459
|
+
title: "The `claude` CLI isn't installed or isn't on your PATH.",
|
|
142460
|
+
hint: "Install Claude Code (https://docs.anthropic.com/en/docs/claude-code), or set CLAUDE_CODE_BINARY to its name/path, then retry."
|
|
142461
|
+
};
|
|
142462
|
+
}
|
|
142463
|
+
return {
|
|
142464
|
+
title: `The \`claude\` CLI failed (exit ${result.code ?? "null"}). Its output:`,
|
|
142465
|
+
hint: result.output || "(no output)"
|
|
142466
|
+
};
|
|
142467
|
+
}
|
|
141871
142468
|
|
|
141872
142469
|
// tools/cli/src/commands/analyze.ts
|
|
141873
|
-
init_config2();
|
|
141874
142470
|
init_helpers();
|
|
141875
142471
|
|
|
141876
142472
|
// tools/cli/src/commands/llm-prompt.ts
|
|
@@ -141913,8 +142509,8 @@ function showFirstRunNotice() {
|
|
|
141913
142509
|
// tools/cli/src/community-prompts.ts
|
|
141914
142510
|
init_paths();
|
|
141915
142511
|
init_helpers();
|
|
141916
|
-
import
|
|
141917
|
-
import
|
|
142512
|
+
import fs15 from "node:fs";
|
|
142513
|
+
import path17 from "node:path";
|
|
141918
142514
|
var DISCORD_URL = "https://discord.gg/TanxB63arz";
|
|
141919
142515
|
var GITHUB_URL = "https://github.com/truecourse-ai/truecourse";
|
|
141920
142516
|
function link(url) {
|
|
@@ -141962,11 +142558,11 @@ var DEFAULT_CONFIG3 = {
|
|
|
141962
142558
|
starShown: false
|
|
141963
142559
|
};
|
|
141964
142560
|
function getConfigPath2() {
|
|
141965
|
-
return
|
|
142561
|
+
return path17.join(getGlobalDir(), "community-prompts.json");
|
|
141966
142562
|
}
|
|
141967
142563
|
function readConfig2() {
|
|
141968
142564
|
try {
|
|
141969
|
-
const raw =
|
|
142565
|
+
const raw = fs15.readFileSync(getConfigPath2(), "utf-8");
|
|
141970
142566
|
return { ...DEFAULT_CONFIG3, ...JSON.parse(raw) };
|
|
141971
142567
|
} catch {
|
|
141972
142568
|
return { ...DEFAULT_CONFIG3 };
|
|
@@ -141974,8 +142570,8 @@ function readConfig2() {
|
|
|
141974
142570
|
}
|
|
141975
142571
|
function writeConfig2(config2) {
|
|
141976
142572
|
const configPath = getConfigPath2();
|
|
141977
|
-
|
|
141978
|
-
|
|
142573
|
+
fs15.mkdirSync(path17.dirname(configPath), { recursive: true });
|
|
142574
|
+
fs15.writeFileSync(configPath, JSON.stringify(config2, null, 2) + "\n", "utf-8");
|
|
141979
142575
|
}
|
|
141980
142576
|
function recordAnalyzeAndMaybePrompt() {
|
|
141981
142577
|
if (!isInteractive()) return;
|
|
@@ -142004,15 +142600,6 @@ ${link(GITHUB_URL)}`
|
|
|
142004
142600
|
}
|
|
142005
142601
|
|
|
142006
142602
|
// tools/cli/src/commands/analyze.ts
|
|
142007
|
-
function ensureClaudeCli() {
|
|
142008
|
-
const binary2 = config.claudeCodeBinary;
|
|
142009
|
-
if (isCliBinaryAvailable(binary2)) return;
|
|
142010
|
-
O2.error(
|
|
142011
|
-
`Claude Code CLI not found (tried \`${binary2}\`). TrueCourse requires the Claude Code binary to run analysis.
|
|
142012
|
-
Install it from https://docs.anthropic.com/en/docs/claude-code, or set CLAUDE_CODE_BINARY to its name or absolute path if it's installed elsewhere.`
|
|
142013
|
-
);
|
|
142014
|
-
process.exit(1);
|
|
142015
|
-
}
|
|
142016
142603
|
function resolveOrInitProject() {
|
|
142017
142604
|
const repoDir = resolveRepoDir(process.cwd()) ?? process.cwd();
|
|
142018
142605
|
ensureRepoTruecourseDir(repoDir);
|
|
@@ -142139,18 +142726,18 @@ async function resolveStashDecision(options, repoPath) {
|
|
|
142139
142726
|
}
|
|
142140
142727
|
async function runAnalyze(options = {}) {
|
|
142141
142728
|
mt("Analyzing repository");
|
|
142142
|
-
ensureClaudeCli();
|
|
142143
142729
|
showFirstRunNotice();
|
|
142144
142730
|
const project = resolveOrInitProject();
|
|
142145
142731
|
O2.step(`Repository: ${project.name}`);
|
|
142146
142732
|
await promptInstallSkills(project.path, { install: options.installSkills });
|
|
142147
142733
|
configureLogger({
|
|
142148
|
-
filePath:
|
|
142734
|
+
filePath: path18.join(project.path, ".truecourse/logs/analyze.log")
|
|
142149
142735
|
});
|
|
142150
142736
|
const config2 = readProjectConfig(project.path);
|
|
142151
142737
|
const enabledCategories = config2.enabledCategories ?? void 0;
|
|
142152
142738
|
const llmDecision = resolveLlmDecision(options, config2.enableLlmRules ?? true);
|
|
142153
142739
|
const enableLlmRules = llmDecision.enabled;
|
|
142740
|
+
if (enableLlmRules) await preflightClaudeOrExit();
|
|
142154
142741
|
const stashDecision = await resolveStashDecision(options, project.path);
|
|
142155
142742
|
renderPhase = enableLlmRules ? "pre-llm" : "all";
|
|
142156
142743
|
if (wipeLegacyPostgresData()) {
|
|
@@ -142217,18 +142804,18 @@ async function runAnalyzeDiff(options = {}) {
|
|
|
142217
142804
|
const { diffInProcess: diffInProcess2 } = await Promise.resolve().then(() => (init_diff_in_process(), diff_in_process_exports));
|
|
142218
142805
|
const { renderDiffResultsSummary: renderDiffResultsSummary2 } = await Promise.resolve().then(() => (init_helpers(), helpers_exports));
|
|
142219
142806
|
mt("Running diff check");
|
|
142220
|
-
ensureClaudeCli();
|
|
142221
142807
|
showFirstRunNotice();
|
|
142222
142808
|
const project = resolveOrInitProject();
|
|
142223
142809
|
O2.step(`Repository: ${project.name}`);
|
|
142224
142810
|
await promptInstallSkills(project.path, { install: options.installSkills });
|
|
142225
142811
|
configureLogger({
|
|
142226
|
-
filePath:
|
|
142812
|
+
filePath: path18.join(project.path, ".truecourse/logs/analyze.log")
|
|
142227
142813
|
});
|
|
142228
142814
|
const config2 = readProjectConfig(project.path);
|
|
142229
142815
|
const enabledCategories = config2.enabledCategories ?? void 0;
|
|
142230
142816
|
const llmDecision = resolveLlmDecision(options, config2.enableLlmRules ?? true);
|
|
142231
142817
|
const enableLlmRules = llmDecision.enabled;
|
|
142818
|
+
if (enableLlmRules) await preflightClaudeOrExit();
|
|
142232
142819
|
renderPhase = enableLlmRules ? "pre-llm" : "all";
|
|
142233
142820
|
const stepDefs = buildAnalysisSteps(enabledCategories, enableLlmRules);
|
|
142234
142821
|
const tracker = new StepTracker((payload) => {
|
|
@@ -142292,25 +142879,25 @@ init_dist4();
|
|
|
142292
142879
|
init_paths();
|
|
142293
142880
|
init_registry();
|
|
142294
142881
|
init_helpers();
|
|
142295
|
-
import { spawn as
|
|
142296
|
-
import
|
|
142297
|
-
import
|
|
142882
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
142883
|
+
import fs20 from "node:fs";
|
|
142884
|
+
import path23 from "node:path";
|
|
142298
142885
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
142299
142886
|
|
|
142300
142887
|
// tools/cli/src/commands/service/macos.ts
|
|
142301
|
-
import
|
|
142302
|
-
import
|
|
142888
|
+
import fs17 from "node:fs";
|
|
142889
|
+
import path19 from "node:path";
|
|
142303
142890
|
import os5 from "node:os";
|
|
142304
142891
|
import { execSync } from "node:child_process";
|
|
142305
142892
|
|
|
142306
142893
|
// tools/cli/src/commands/service/env.ts
|
|
142307
|
-
import
|
|
142894
|
+
import fs16 from "node:fs";
|
|
142308
142895
|
function parseEnvFile(filePath) {
|
|
142309
142896
|
const vars = {};
|
|
142310
|
-
if (!
|
|
142897
|
+
if (!fs16.existsSync(filePath)) {
|
|
142311
142898
|
return vars;
|
|
142312
142899
|
}
|
|
142313
|
-
const content =
|
|
142900
|
+
const content = fs16.readFileSync(filePath, "utf-8");
|
|
142314
142901
|
for (const line of content.split("\n")) {
|
|
142315
142902
|
const trimmed2 = line.trim();
|
|
142316
142903
|
if (!trimmed2 || trimmed2.startsWith("#")) continue;
|
|
@@ -142330,15 +142917,15 @@ function parseEnvFile(filePath) {
|
|
|
142330
142917
|
|
|
142331
142918
|
// tools/cli/src/commands/service/macos.ts
|
|
142332
142919
|
var SERVICE_LABEL = "com.truecourse.server";
|
|
142333
|
-
var PLIST_DIR =
|
|
142334
|
-
var PLIST_PATH =
|
|
142920
|
+
var PLIST_DIR = path19.join(os5.homedir(), "Library", "LaunchAgents");
|
|
142921
|
+
var PLIST_PATH = path19.join(PLIST_DIR, `${SERVICE_LABEL}.plist`);
|
|
142335
142922
|
function escapeXml(s) {
|
|
142336
142923
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
142337
142924
|
}
|
|
142338
142925
|
function buildPlist(serverPath, logPath, envVars) {
|
|
142339
|
-
const logDir =
|
|
142340
|
-
const stdoutPath =
|
|
142341
|
-
const stderrPath =
|
|
142926
|
+
const logDir = path19.dirname(logPath);
|
|
142927
|
+
const stdoutPath = path19.join(logDir, "dashboard.out.log");
|
|
142928
|
+
const stderrPath = path19.join(logDir, "dashboard.err.log");
|
|
142342
142929
|
let envSection = "";
|
|
142343
142930
|
if (Object.keys(envVars).length > 0) {
|
|
142344
142931
|
const entries = Object.entries(envVars).map(([k, v]) => ` <key>${escapeXml(k)}</key>
|
|
@@ -142374,17 +142961,17 @@ ${entries}
|
|
|
142374
142961
|
}
|
|
142375
142962
|
var MacOSService = class {
|
|
142376
142963
|
async install(serverPath, logPath) {
|
|
142377
|
-
const envFile =
|
|
142964
|
+
const envFile = path19.join(os5.homedir(), ".truecourse", ".env");
|
|
142378
142965
|
const envVars = parseEnvFile(envFile);
|
|
142379
142966
|
if (process.env.PATH && !envVars.PATH) {
|
|
142380
142967
|
envVars.PATH = process.env.PATH;
|
|
142381
142968
|
}
|
|
142382
|
-
envVars.TRUECOURSE_LOG_DIR =
|
|
142383
|
-
envVars.TRUECOURSE_HOME =
|
|
142384
|
-
|
|
142385
|
-
|
|
142969
|
+
envVars.TRUECOURSE_LOG_DIR = path19.dirname(logPath);
|
|
142970
|
+
envVars.TRUECOURSE_HOME = path19.join(os5.homedir(), ".truecourse");
|
|
142971
|
+
fs17.mkdirSync(PLIST_DIR, { recursive: true });
|
|
142972
|
+
fs17.mkdirSync(path19.dirname(logPath), { recursive: true });
|
|
142386
142973
|
const plist = buildPlist(serverPath, logPath, envVars);
|
|
142387
|
-
|
|
142974
|
+
fs17.writeFileSync(PLIST_PATH, plist, "utf-8");
|
|
142388
142975
|
execSync(`launchctl load -w "${PLIST_PATH}"`, { stdio: "pipe" });
|
|
142389
142976
|
}
|
|
142390
142977
|
async uninstall() {
|
|
@@ -142392,12 +142979,12 @@ var MacOSService = class {
|
|
|
142392
142979
|
execSync(`launchctl unload "${PLIST_PATH}"`, { stdio: "pipe" });
|
|
142393
142980
|
} catch {
|
|
142394
142981
|
}
|
|
142395
|
-
if (
|
|
142396
|
-
|
|
142982
|
+
if (fs17.existsSync(PLIST_PATH)) {
|
|
142983
|
+
fs17.unlinkSync(PLIST_PATH);
|
|
142397
142984
|
}
|
|
142398
142985
|
}
|
|
142399
142986
|
async start() {
|
|
142400
|
-
if (!
|
|
142987
|
+
if (!fs17.existsSync(PLIST_PATH)) {
|
|
142401
142988
|
throw new Error("Service is not installed. Run 'truecourse service install' first.");
|
|
142402
142989
|
}
|
|
142403
142990
|
execSync(`launchctl load -w "${PLIST_PATH}"`, { stdio: "pipe" });
|
|
@@ -142427,22 +143014,22 @@ var MacOSService = class {
|
|
|
142427
143014
|
}
|
|
142428
143015
|
}
|
|
142429
143016
|
async isInstalled() {
|
|
142430
|
-
return
|
|
143017
|
+
return fs17.existsSync(PLIST_PATH);
|
|
142431
143018
|
}
|
|
142432
143019
|
};
|
|
142433
143020
|
|
|
142434
143021
|
// tools/cli/src/commands/service/linux.ts
|
|
142435
|
-
import
|
|
142436
|
-
import
|
|
143022
|
+
import fs18 from "node:fs";
|
|
143023
|
+
import path20 from "node:path";
|
|
142437
143024
|
import os6 from "node:os";
|
|
142438
143025
|
import { execSync as execSync2 } from "node:child_process";
|
|
142439
143026
|
var SERVICE_NAME = "truecourse";
|
|
142440
|
-
var UNIT_DIR =
|
|
142441
|
-
var UNIT_PATH =
|
|
143027
|
+
var UNIT_DIR = path20.join(os6.homedir(), ".config", "systemd", "user");
|
|
143028
|
+
var UNIT_PATH = path20.join(UNIT_DIR, `${SERVICE_NAME}.service`);
|
|
142442
143029
|
function buildUnitFile(serverPath, logPath) {
|
|
142443
|
-
const truecourseHome =
|
|
142444
|
-
const envFile =
|
|
142445
|
-
const logDir =
|
|
143030
|
+
const truecourseHome = path20.join(os6.homedir(), ".truecourse");
|
|
143031
|
+
const envFile = path20.join(truecourseHome, ".env");
|
|
143032
|
+
const logDir = path20.dirname(logPath);
|
|
142446
143033
|
return `[Unit]
|
|
142447
143034
|
Description=TrueCourse Server
|
|
142448
143035
|
After=network.target
|
|
@@ -142455,8 +143042,8 @@ RestartSec=5
|
|
|
142455
143042
|
EnvironmentFile=${envFile}
|
|
142456
143043
|
Environment=TRUECOURSE_HOME=${truecourseHome}
|
|
142457
143044
|
Environment=TRUECOURSE_LOG_DIR=${logDir}
|
|
142458
|
-
StandardOutput=append:${
|
|
142459
|
-
StandardError=append:${
|
|
143045
|
+
StandardOutput=append:${path20.join(logDir, "dashboard.out.log")}
|
|
143046
|
+
StandardError=append:${path20.join(logDir, "dashboard.err.log")}
|
|
142460
143047
|
|
|
142461
143048
|
[Install]
|
|
142462
143049
|
WantedBy=default.target
|
|
@@ -142464,10 +143051,10 @@ WantedBy=default.target
|
|
|
142464
143051
|
}
|
|
142465
143052
|
var LinuxService = class {
|
|
142466
143053
|
async install(serverPath, logPath) {
|
|
142467
|
-
|
|
142468
|
-
|
|
143054
|
+
fs18.mkdirSync(UNIT_DIR, { recursive: true });
|
|
143055
|
+
fs18.mkdirSync(path20.dirname(logPath), { recursive: true });
|
|
142469
143056
|
const unit = buildUnitFile(serverPath, logPath);
|
|
142470
|
-
|
|
143057
|
+
fs18.writeFileSync(UNIT_PATH, unit, "utf-8");
|
|
142471
143058
|
execSync2("systemctl --user daemon-reload", { stdio: "pipe" });
|
|
142472
143059
|
execSync2(`systemctl --user enable ${SERVICE_NAME}`, { stdio: "pipe" });
|
|
142473
143060
|
}
|
|
@@ -142480,8 +143067,8 @@ var LinuxService = class {
|
|
|
142480
143067
|
execSync2(`systemctl --user disable ${SERVICE_NAME}`, { stdio: "pipe" });
|
|
142481
143068
|
} catch {
|
|
142482
143069
|
}
|
|
142483
|
-
if (
|
|
142484
|
-
|
|
143070
|
+
if (fs18.existsSync(UNIT_PATH)) {
|
|
143071
|
+
fs18.unlinkSync(UNIT_PATH);
|
|
142485
143072
|
}
|
|
142486
143073
|
try {
|
|
142487
143074
|
execSync2("systemctl --user daemon-reload", { stdio: "pipe" });
|
|
@@ -142510,14 +143097,14 @@ var LinuxService = class {
|
|
|
142510
143097
|
}
|
|
142511
143098
|
}
|
|
142512
143099
|
async isInstalled() {
|
|
142513
|
-
return
|
|
143100
|
+
return fs18.existsSync(UNIT_PATH);
|
|
142514
143101
|
}
|
|
142515
143102
|
};
|
|
142516
143103
|
|
|
142517
143104
|
// tools/cli/src/commands/service/windows.ts
|
|
142518
143105
|
import { execSync as execSync3 } from "node:child_process";
|
|
142519
143106
|
import os7 from "node:os";
|
|
142520
|
-
import
|
|
143107
|
+
import path21 from "node:path";
|
|
142521
143108
|
var SERVICE_DISPLAY_NAME = "TrueCourse";
|
|
142522
143109
|
var SERVICE_SCM_NAME = "truecourse.exe";
|
|
142523
143110
|
var WindowsService = class {
|
|
@@ -142533,8 +143120,8 @@ var WindowsService = class {
|
|
|
142533
143120
|
async install(serverPath, logPath) {
|
|
142534
143121
|
const nw = await this.getNodeWindows();
|
|
142535
143122
|
const { Service } = nw;
|
|
142536
|
-
const logDir =
|
|
142537
|
-
const truecourseHome =
|
|
143123
|
+
const logDir = path21.dirname(logPath);
|
|
143124
|
+
const truecourseHome = path21.join(os7.homedir(), ".truecourse");
|
|
142538
143125
|
return new Promise((resolve9, reject) => {
|
|
142539
143126
|
const svc = new Service({
|
|
142540
143127
|
name: SERVICE_DISPLAY_NAME,
|
|
@@ -142620,8 +143207,8 @@ function getPlatform() {
|
|
|
142620
143207
|
}
|
|
142621
143208
|
|
|
142622
143209
|
// tools/cli/src/commands/service/logs.ts
|
|
142623
|
-
import
|
|
142624
|
-
import
|
|
143210
|
+
import fs19 from "node:fs";
|
|
143211
|
+
import path22 from "node:path";
|
|
142625
143212
|
import os8 from "node:os";
|
|
142626
143213
|
var MAX_LOG_SIZE2 = 10 * 1024 * 1024;
|
|
142627
143214
|
var MAX_LOG_FILES2 = 5;
|
|
@@ -142632,38 +143219,38 @@ var ROTATABLE_LOG_NAMES = [
|
|
|
142632
143219
|
"dashboard.log"
|
|
142633
143220
|
];
|
|
142634
143221
|
function getLogDir() {
|
|
142635
|
-
return
|
|
143222
|
+
return path22.join(os8.homedir(), ".truecourse", "logs");
|
|
142636
143223
|
}
|
|
142637
143224
|
function getLogPath() {
|
|
142638
|
-
return
|
|
143225
|
+
return path22.join(getLogDir(), "dashboard.log");
|
|
142639
143226
|
}
|
|
142640
143227
|
function existingLogFiles(logDir) {
|
|
142641
|
-
if (!
|
|
142642
|
-
return
|
|
143228
|
+
if (!fs19.existsSync(logDir)) return [];
|
|
143229
|
+
return fs19.readdirSync(logDir).filter((name) => /\.log$/.test(name)).sort().map((name) => path22.join(logDir, name));
|
|
142643
143230
|
}
|
|
142644
143231
|
function rotateOne(logFile) {
|
|
142645
|
-
if (!
|
|
142646
|
-
if (
|
|
143232
|
+
if (!fs19.existsSync(logFile)) return;
|
|
143233
|
+
if (fs19.statSync(logFile).size < MAX_LOG_SIZE2) return;
|
|
142647
143234
|
for (let i = MAX_LOG_FILES2; i >= 1; i--) {
|
|
142648
143235
|
const older = `${logFile}.${i}`;
|
|
142649
143236
|
if (i === MAX_LOG_FILES2) {
|
|
142650
|
-
if (
|
|
143237
|
+
if (fs19.existsSync(older)) fs19.unlinkSync(older);
|
|
142651
143238
|
} else {
|
|
142652
143239
|
const newer = `${logFile}.${i + 1}`;
|
|
142653
|
-
if (
|
|
143240
|
+
if (fs19.existsSync(older)) fs19.renameSync(older, newer);
|
|
142654
143241
|
}
|
|
142655
143242
|
}
|
|
142656
|
-
|
|
143243
|
+
fs19.renameSync(logFile, `${logFile}.1`);
|
|
142657
143244
|
}
|
|
142658
143245
|
function rotateLogs(logDir) {
|
|
142659
143246
|
for (const name of ROTATABLE_LOG_NAMES) {
|
|
142660
|
-
rotateOne(
|
|
143247
|
+
rotateOne(path22.join(logDir, name));
|
|
142661
143248
|
}
|
|
142662
|
-
if (!
|
|
143249
|
+
if (!fs19.existsSync(logDir)) return;
|
|
142663
143250
|
for (const file of existingLogFiles(logDir)) rotateOne(file);
|
|
142664
143251
|
}
|
|
142665
143252
|
function readLastLines(filePath, maxLines) {
|
|
142666
|
-
const content =
|
|
143253
|
+
const content = fs19.readFileSync(filePath, "utf-8");
|
|
142667
143254
|
const lines = content.split("\n");
|
|
142668
143255
|
if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
142669
143256
|
return lines.slice(-maxLines).join("\n");
|
|
@@ -142674,7 +143261,7 @@ function dumpLogTails(logDir, linesPerFile = 50) {
|
|
|
142674
143261
|
for (const file of files) {
|
|
142675
143262
|
const tail = readLastLines(file, linesPerFile);
|
|
142676
143263
|
process.stdout.write(`
|
|
142677
|
-
----- ${
|
|
143264
|
+
----- ${path22.basename(file)} (last ${linesPerFile} lines) -----
|
|
142678
143265
|
`);
|
|
142679
143266
|
process.stdout.write(tail);
|
|
142680
143267
|
if (!tail.endsWith("\n")) process.stdout.write("\n");
|
|
@@ -142691,18 +143278,18 @@ function tailLogs() {
|
|
|
142691
143278
|
}
|
|
142692
143279
|
dumpLogTails(logDir);
|
|
142693
143280
|
const sizes = /* @__PURE__ */ new Map();
|
|
142694
|
-
for (const file of files) sizes.set(file,
|
|
143281
|
+
for (const file of files) sizes.set(file, fs19.statSync(file).size);
|
|
142695
143282
|
const watch = (file) => {
|
|
142696
|
-
|
|
143283
|
+
fs19.watchFile(file, { interval: POLL_INTERVAL_MS }, () => {
|
|
142697
143284
|
try {
|
|
142698
|
-
const newSize =
|
|
143285
|
+
const newSize = fs19.statSync(file).size;
|
|
142699
143286
|
const lastSize = sizes.get(file) ?? 0;
|
|
142700
143287
|
if (newSize > lastSize) {
|
|
142701
|
-
const fd =
|
|
143288
|
+
const fd = fs19.openSync(file, "r");
|
|
142702
143289
|
const buf = Buffer.alloc(newSize - lastSize);
|
|
142703
|
-
|
|
142704
|
-
|
|
142705
|
-
const tag = `[${
|
|
143290
|
+
fs19.readSync(fd, buf, 0, buf.length, lastSize);
|
|
143291
|
+
fs19.closeSync(fd);
|
|
143292
|
+
const tag = `[${path22.basename(file)}] `;
|
|
142706
143293
|
const text = buf.toString("utf-8");
|
|
142707
143294
|
const tagged = text.split("\n").map((line, i, arr) => i === arr.length - 1 && line === "" ? "" : tag + line).join("\n");
|
|
142708
143295
|
process.stdout.write(tagged);
|
|
@@ -142716,7 +143303,7 @@ function tailLogs() {
|
|
|
142716
143303
|
};
|
|
142717
143304
|
for (const file of files) watch(file);
|
|
142718
143305
|
const cleanup = () => {
|
|
142719
|
-
for (const file of files)
|
|
143306
|
+
for (const file of files) fs19.unwatchFile(file);
|
|
142720
143307
|
process.exit(0);
|
|
142721
143308
|
};
|
|
142722
143309
|
process.on("SIGINT", cleanup);
|
|
@@ -142724,15 +143311,15 @@ function tailLogs() {
|
|
|
142724
143311
|
}
|
|
142725
143312
|
|
|
142726
143313
|
// tools/cli/src/commands/dashboard.ts
|
|
142727
|
-
var __dirname2 =
|
|
143314
|
+
var __dirname2 = path23.dirname(fileURLToPath6(import.meta.url));
|
|
142728
143315
|
function resolveServerEntry() {
|
|
142729
143316
|
const candidates = [
|
|
142730
143317
|
// Packaged CLI: dist/cli.mjs next to dist/server.mjs
|
|
142731
|
-
|
|
143318
|
+
path23.join(__dirname2, "server.mjs"),
|
|
142732
143319
|
// Source build output: tools/cli/dist → ../../../../dist/server.mjs
|
|
142733
|
-
|
|
143320
|
+
path23.resolve(__dirname2, "..", "..", "..", "..", "dist", "server.mjs")
|
|
142734
143321
|
];
|
|
142735
|
-
return candidates.find((p2) =>
|
|
143322
|
+
return candidates.find((p2) => fs20.existsSync(p2)) ?? null;
|
|
142736
143323
|
}
|
|
142737
143324
|
async function waitForHealth(url, timeoutMs = 3e4) {
|
|
142738
143325
|
const start = Date.now();
|
|
@@ -142769,7 +143356,7 @@ async function promptRunMode() {
|
|
|
142769
143356
|
async function runConsoleMode(serverEntry) {
|
|
142770
143357
|
const url = getServerUrl();
|
|
142771
143358
|
O2.step("Starting dashboard server...");
|
|
142772
|
-
const serverProcess =
|
|
143359
|
+
const serverProcess = spawn6(process.execPath, [serverEntry], {
|
|
142773
143360
|
stdio: "inherit",
|
|
142774
143361
|
env: { ...process.env }
|
|
142775
143362
|
});
|
|
@@ -142860,7 +143447,7 @@ async function runDashboard(options = {}) {
|
|
|
142860
143447
|
);
|
|
142861
143448
|
process.exit(1);
|
|
142862
143449
|
}
|
|
142863
|
-
const configured =
|
|
143450
|
+
const configured = fs20.existsSync(getConfigPath());
|
|
142864
143451
|
const needsDecision = !configured || options.reconfigure;
|
|
142865
143452
|
let runMode;
|
|
142866
143453
|
if (options.mode) {
|
|
@@ -143312,13 +143899,13 @@ async function runRulesReset({ ruleKey }) {
|
|
|
143312
143899
|
|
|
143313
143900
|
// tools/cli/src/commands/contracts.ts
|
|
143314
143901
|
init_dist4();
|
|
143315
|
-
import
|
|
143902
|
+
import fs49 from "node:fs";
|
|
143316
143903
|
import path55 from "node:path";
|
|
143317
143904
|
|
|
143318
143905
|
// packages/contract-extractor/dist/cache.js
|
|
143319
143906
|
import { createHash as createHash3 } from "node:crypto";
|
|
143320
|
-
import
|
|
143321
|
-
import
|
|
143907
|
+
import fs21 from "node:fs";
|
|
143908
|
+
import path24 from "node:path";
|
|
143322
143909
|
|
|
143323
143910
|
// packages/contract-extractor/dist/types.js
|
|
143324
143911
|
init_zod();
|
|
@@ -143896,14 +144483,20 @@ Use sensible defaults when the spec doesn't spell them out:
|
|
|
143896
144483
|
on-violation {
|
|
143897
144484
|
status 401
|
|
143898
144485
|
error-code unauthenticated
|
|
143899
|
-
body ErrorEnvelope:error.envelope.standard
|
|
143900
144486
|
}
|
|
143901
144487
|
}
|
|
143902
144488
|
|
|
143903
|
-
Default \`on-violation\` is
|
|
143904
|
-
body
|
|
143905
|
-
|
|
143906
|
-
|
|
144489
|
+
Default \`on-violation\` is status 401, error-code \`unauthenticated\`. Add a
|
|
144490
|
+
\`body ErrorEnvelope:error.envelope.standard\` line ONLY when the spec/corpus
|
|
144491
|
+
actually establishes a standard error envelope \u2014 a dedicated errors /
|
|
144492
|
+
error-response section, an error-code catalog, or another slice that describes
|
|
144493
|
+
the error body shape. When the spec is SILENT about the error response body
|
|
144494
|
+
(common for terse auth docs that only state the scheme), OMIT the \`body\` line:
|
|
144495
|
+
a \`body\` reference to an envelope nothing defines is a dangling cross-reference
|
|
144496
|
+
that fails the validation gate, while an omitted body is faithful. See
|
|
144497
|
+
"Error-envelope references must be grounded" below. Default selector is
|
|
144498
|
+
\`path-glob "/api/**"\` when the spec says "all /api/* endpoints" without naming a
|
|
144499
|
+
more specific pattern.
|
|
143907
144500
|
|
|
143908
144501
|
When a spec describes role-based auth ("admin only", "moderators can \u2026"), produce
|
|
143909
144502
|
a SEPARATE \`auth-requirement\` with \`required-role <role>\`, identity
|
|
@@ -143938,7 +144531,8 @@ is the correct fallback when the slice lacks operation context.
|
|
|
143938
144531
|
on-violation {
|
|
143939
144532
|
status 403
|
|
143940
144533
|
error-code forbidden
|
|
143941
|
-
body ErrorEnvelope:error.envelope.standard
|
|
144534
|
+
// body ErrorEnvelope:error.envelope.standard only when that envelope is
|
|
144535
|
+
// defined in the corpus \u2014 see "Error-envelope references must be grounded".
|
|
143942
144536
|
}
|
|
143943
144537
|
}
|
|
143944
144538
|
|
|
@@ -144485,6 +145079,37 @@ response as a literal:
|
|
|
144485
145079
|
- Only declare a literal \`response 401/403 on \u2026\` when the spec describes the
|
|
144486
145080
|
response as standalone, NOT delegated to a rule.
|
|
144487
145081
|
|
|
145082
|
+
# Error-envelope references must be grounded
|
|
145083
|
+
|
|
145084
|
+
\`ErrorEnvelope:error.envelope.standard\` is a CROSS-REFERENCE, not a literal \u2014 it
|
|
145085
|
+
only resolves if a matching \`error-envelope error.envelope.standard { \u2026 }\`
|
|
145086
|
+
artifact exists somewhere in the corpus. Emitting the reference with nothing
|
|
145087
|
+
defining it produces a dangling cross-reference that fails the validation gate,
|
|
145088
|
+
exactly like referencing an undefined \`Enum:\`. This is the same prime-directive
|
|
145089
|
+
rule as enums ("never reference an enum without defining it"), applied to error
|
|
145090
|
+
bodies.
|
|
145091
|
+
|
|
145092
|
+
The rule applies EVERYWHERE an envelope can appear:
|
|
145093
|
+
\`on-violation { \u2026 body ErrorEnvelope:\u2026 }\` on auth-requirements and
|
|
145094
|
+
authorization-rules, and \`response \u2026 { body envelope ErrorEnvelope:\u2026 { \u2026 } }\`
|
|
145095
|
+
on operations.
|
|
145096
|
+
|
|
145097
|
+
- Reference \`ErrorEnvelope:error.envelope.standard\` ONLY when the spec actually
|
|
145098
|
+
establishes a standard error envelope: a dedicated errors / error-response
|
|
145099
|
+
section, an error-code catalog, or prose that names "the standard error
|
|
145100
|
+
envelope". In a multi-doc corpus the defining section may live in ANOTHER
|
|
145101
|
+
slice \u2014 that's fine, just like an \`Enum:\` defined in a sibling slice.
|
|
145102
|
+
- When you reference it AND the slice in front of you is the one that describes
|
|
145103
|
+
the envelope, ALSO emit the \`error-envelope error.envelope.standard { \u2026 }\`
|
|
145104
|
+
artifact \u2014 mirroring the described shape; never invent fields the spec does
|
|
145105
|
+
not list.
|
|
145106
|
+
- When the spec is SILENT about the error response body \u2014 no error section, no
|
|
145107
|
+
envelope, no error codes \u2014 do NOT emit the reference at all. Omit the \`body\`
|
|
145108
|
+
line from \`on-violation\`; on an operation response use a plain \`body \u2026\` only
|
|
145109
|
+
if the spec gives a shape, otherwise omit it. Faithful under-specification
|
|
145110
|
+
beats a dangling reference: never conjure a standard envelope just to fill the
|
|
145111
|
+
slot.
|
|
145112
|
+
|
|
144488
145113
|
# Naming conventions for artifact identities
|
|
144489
145114
|
|
|
144490
145115
|
Use these canonical identity formats:
|
|
@@ -144529,29 +145154,29 @@ function buildUserPrompt(slice3) {
|
|
|
144529
145154
|
}
|
|
144530
145155
|
|
|
144531
145156
|
// packages/contract-extractor/dist/cache.js
|
|
144532
|
-
var CACHE_DIR =
|
|
145157
|
+
var CACHE_DIR = path24.join(".truecourse", ".cache", "extractor");
|
|
144533
145158
|
var SLICES_SUBDIR = "slices";
|
|
144534
145159
|
var MANIFEST_FILE = "manifest.json";
|
|
144535
145160
|
var EXTRACTION_PROMPT_FINGERPRINT = createHash3("sha256").update(SYSTEM_PROMPT).digest("hex").slice(0, 16);
|
|
144536
145161
|
function cachePaths(repoRoot6) {
|
|
144537
|
-
const cacheDir =
|
|
145162
|
+
const cacheDir = path24.join(repoRoot6, CACHE_DIR);
|
|
144538
145163
|
return {
|
|
144539
145164
|
cacheDir,
|
|
144540
|
-
slicesDir:
|
|
144541
|
-
manifestPath:
|
|
145165
|
+
slicesDir: path24.join(cacheDir, SLICES_SUBDIR),
|
|
145166
|
+
manifestPath: path24.join(cacheDir, MANIFEST_FILE)
|
|
144542
145167
|
};
|
|
144543
145168
|
}
|
|
144544
145169
|
function ensureCacheDirs(repoRoot6) {
|
|
144545
145170
|
const paths = cachePaths(repoRoot6);
|
|
144546
|
-
|
|
145171
|
+
fs21.mkdirSync(paths.slicesDir, { recursive: true });
|
|
144547
145172
|
return paths;
|
|
144548
145173
|
}
|
|
144549
145174
|
function readSliceEntry(repoRoot6, sliceId) {
|
|
144550
|
-
const file =
|
|
144551
|
-
if (!
|
|
145175
|
+
const file = path24.join(cachePaths(repoRoot6).slicesDir, `${sliceId}.json`);
|
|
145176
|
+
if (!fs21.existsSync(file))
|
|
144552
145177
|
return null;
|
|
144553
145178
|
try {
|
|
144554
|
-
const raw = JSON.parse(
|
|
145179
|
+
const raw = JSON.parse(fs21.readFileSync(file, "utf-8"));
|
|
144555
145180
|
const entry = SliceCacheEntrySchema.parse(raw);
|
|
144556
145181
|
if (entry.promptFingerprint !== EXTRACTION_PROMPT_FINGERPRINT)
|
|
144557
145182
|
return null;
|
|
@@ -144571,16 +145196,16 @@ function writeSliceEntry(repoRoot6, slice3, result) {
|
|
|
144571
145196
|
cachedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
144572
145197
|
promptFingerprint: EXTRACTION_PROMPT_FINGERPRINT
|
|
144573
145198
|
};
|
|
144574
|
-
const file =
|
|
144575
|
-
|
|
145199
|
+
const file = path24.join(cachePaths(repoRoot6).slicesDir, `${slice3.id}.json`);
|
|
145200
|
+
fs21.writeFileSync(file, JSON.stringify(entry, null, 2));
|
|
144576
145201
|
return entry;
|
|
144577
145202
|
}
|
|
144578
145203
|
function readManifest(repoRoot6) {
|
|
144579
145204
|
const file = cachePaths(repoRoot6).manifestPath;
|
|
144580
|
-
if (!
|
|
145205
|
+
if (!fs21.existsSync(file))
|
|
144581
145206
|
return null;
|
|
144582
145207
|
try {
|
|
144583
|
-
const raw = JSON.parse(
|
|
145208
|
+
const raw = JSON.parse(fs21.readFileSync(file, "utf-8"));
|
|
144584
145209
|
return ManifestSchema.parse(raw);
|
|
144585
145210
|
} catch {
|
|
144586
145211
|
return null;
|
|
@@ -144589,11 +145214,11 @@ function readManifest(repoRoot6) {
|
|
|
144589
145214
|
function writeManifest(repoRoot6, manifest) {
|
|
144590
145215
|
ensureCacheDirs(repoRoot6);
|
|
144591
145216
|
const file = cachePaths(repoRoot6).manifestPath;
|
|
144592
|
-
|
|
145217
|
+
fs21.writeFileSync(file, JSON.stringify(manifest, null, 2));
|
|
144593
145218
|
}
|
|
144594
145219
|
function gcOrphanedSlices(repoRoot6, manifest) {
|
|
144595
145220
|
const paths = cachePaths(repoRoot6);
|
|
144596
|
-
if (!
|
|
145221
|
+
if (!fs21.existsSync(paths.slicesDir))
|
|
144597
145222
|
return 0;
|
|
144598
145223
|
const live = /* @__PURE__ */ new Set();
|
|
144599
145224
|
for (const spec of Object.values(manifest.specs)) {
|
|
@@ -144601,12 +145226,12 @@ function gcOrphanedSlices(repoRoot6, manifest) {
|
|
|
144601
145226
|
live.add(s.sliceId);
|
|
144602
145227
|
}
|
|
144603
145228
|
let removed = 0;
|
|
144604
|
-
for (const name of
|
|
145229
|
+
for (const name of fs21.readdirSync(paths.slicesDir)) {
|
|
144605
145230
|
if (!name.endsWith(".json"))
|
|
144606
145231
|
continue;
|
|
144607
145232
|
const id = name.slice(0, -".json".length);
|
|
144608
145233
|
if (!live.has(id)) {
|
|
144609
|
-
|
|
145234
|
+
fs21.unlinkSync(path24.join(paths.slicesDir, name));
|
|
144610
145235
|
removed++;
|
|
144611
145236
|
}
|
|
144612
145237
|
}
|
|
@@ -144615,8 +145240,8 @@ function gcOrphanedSlices(repoRoot6, manifest) {
|
|
|
144615
145240
|
|
|
144616
145241
|
// packages/contract-extractor/dist/claims-reader.js
|
|
144617
145242
|
import crypto2 from "node:crypto";
|
|
144618
|
-
import
|
|
144619
|
-
import
|
|
145243
|
+
import fs32 from "node:fs";
|
|
145244
|
+
import path35 from "node:path";
|
|
144620
145245
|
|
|
144621
145246
|
// packages/spec-consolidator/dist/types.js
|
|
144622
145247
|
init_zod();
|
|
@@ -144826,8 +145451,8 @@ var ModuleManifestSchema = external_exports.object({
|
|
|
144826
145451
|
init_dist7();
|
|
144827
145452
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
144828
145453
|
import { createHash as createHash4 } from "node:crypto";
|
|
144829
|
-
import
|
|
144830
|
-
import
|
|
145454
|
+
import fs22 from "node:fs";
|
|
145455
|
+
import path25 from "node:path";
|
|
144831
145456
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
144832
145457
|
"node_modules",
|
|
144833
145458
|
".git",
|
|
@@ -144848,7 +145473,7 @@ function discoverDocs(rootDir, opts = {}) {
|
|
|
144848
145473
|
const visit = (dir) => {
|
|
144849
145474
|
let entries;
|
|
144850
145475
|
try {
|
|
144851
|
-
entries =
|
|
145476
|
+
entries = fs22.readdirSync(dir, { withFileTypes: true });
|
|
144852
145477
|
} catch {
|
|
144853
145478
|
return;
|
|
144854
145479
|
}
|
|
@@ -144858,7 +145483,7 @@ function discoverDocs(rootDir, opts = {}) {
|
|
|
144858
145483
|
continue;
|
|
144859
145484
|
if (entry.name.startsWith(".") && entry.name !== ".")
|
|
144860
145485
|
continue;
|
|
144861
|
-
const full =
|
|
145486
|
+
const full = path25.join(dir, entry.name);
|
|
144862
145487
|
if (tcIgnore.ignores(full))
|
|
144863
145488
|
continue;
|
|
144864
145489
|
if (entry.isDirectory()) {
|
|
@@ -144867,7 +145492,7 @@ function discoverDocs(rootDir, opts = {}) {
|
|
|
144867
145492
|
}
|
|
144868
145493
|
if (!entry.isFile())
|
|
144869
145494
|
continue;
|
|
144870
|
-
if (
|
|
145495
|
+
if (path25.extname(entry.name).toLowerCase() !== ".md")
|
|
144871
145496
|
continue;
|
|
144872
145497
|
const candidate = makeCandidate(full, rootDir, previewLines, opts);
|
|
144873
145498
|
if (candidate)
|
|
@@ -144881,12 +145506,12 @@ function makeCandidate(absPath, rootDir, previewLines, opts) {
|
|
|
144881
145506
|
let content;
|
|
144882
145507
|
let stat;
|
|
144883
145508
|
try {
|
|
144884
|
-
content =
|
|
144885
|
-
stat =
|
|
145509
|
+
content = fs22.readFileSync(absPath, "utf-8");
|
|
145510
|
+
stat = fs22.statSync(absPath);
|
|
144886
145511
|
} catch {
|
|
144887
145512
|
return null;
|
|
144888
145513
|
}
|
|
144889
|
-
const rel =
|
|
145514
|
+
const rel = path25.relative(rootDir, absPath).split(path25.sep).join("/");
|
|
144890
145515
|
const preview = content.split(/\r?\n/).slice(0, previewLines).join("\n");
|
|
144891
145516
|
const contentHash = createHash4("sha256").update(content).digest("hex");
|
|
144892
145517
|
const lastTouched = opts.skipGit ? stat.mtime.toISOString() : gitLastTouched(rootDir, rel) ?? stat.mtime.toISOString();
|
|
@@ -144914,8 +145539,8 @@ function gitLastTouched(rootDir, relPath) {
|
|
|
144914
145539
|
}
|
|
144915
145540
|
}
|
|
144916
145541
|
function classifyDoc(relPath, content) {
|
|
144917
|
-
const base =
|
|
144918
|
-
const dirParts =
|
|
145542
|
+
const base = path25.basename(relPath).toLowerCase();
|
|
145543
|
+
const dirParts = path25.dirname(relPath).split("/").map((p2) => p2.toLowerCase());
|
|
144919
145544
|
if (/^(specs?|specification|specs?-.*)\.md$/i.test(base))
|
|
144920
145545
|
return "spec";
|
|
144921
145546
|
if (/^adr[-_]?\d+/i.test(base) || dirParts.some((p2) => p2 === "adr" || p2 === "adrs")) {
|
|
@@ -145147,6 +145772,14 @@ You are given ONE block of markdown from a documentation file (a PRD, ADR, RFC,
|
|
|
145147
145772
|
|
|
145148
145773
|
6. The faithfulness rule. Encode ONLY what the spec STATES. Never guess. Never default to common patterns. If the spec doesn't say what status code is returned, don't assume 200. If the spec doesn't say auth is required, don't add an auth claim.
|
|
145149
145774
|
|
|
145775
|
+
7. The completeness rule (enumerations). When the spec presents an enumeration \u2014 a bulleted list, a numbered list, a markdown table column of values, or an inline comma-separated list \u2014 capture EVERY entry. Never summarize, never include a "representative subset", never drop entries because they look similar to ones you already captured. This applies regardless of how you structure the claim:
|
|
145776
|
+
|
|
145777
|
+
- If you emit ONE claim whose \`content.fields\` lists the entries (e.g. an entity's columns, an enum's value-to-description map), every enumerated value must be a key in \`fields\`. A 15-item bulleted list must produce 15 keys, not 8.
|
|
145778
|
+
- If you emit ONE claim per entry (the forbidden-artifact pattern), emit exactly N claims for N enumerated items.
|
|
145779
|
+
- If the spec lists values across multiple sections of the same doc, each section contributes its own claim; the merger handles the union downstream \u2014 don't pre-dedupe across sections.
|
|
145780
|
+
|
|
145781
|
+
Counting check before you return: if the source block contains a bulleted or numbered list under a heading like "Available <X>", "Supported <X> types", "Operation types", "Roles", "Statuses", or similar, count the bullets in that list, then count the entries you've emitted. The counts MUST match. If they don't, you've summarized \u2014 re-extract the missed entries before returning.
|
|
145782
|
+
|
|
145150
145783
|
# Content shapes per topic
|
|
145151
145784
|
|
|
145152
145785
|
## endpoints
|
|
@@ -145347,7 +145980,7 @@ async function runOne(transport, block, timeoutMs, model, fallbackModel) {
|
|
|
145347
145980
|
|
|
145348
145981
|
// packages/spec-consolidator/dist/extractor.js
|
|
145349
145982
|
import { createHash as createHash6 } from "node:crypto";
|
|
145350
|
-
import
|
|
145983
|
+
import fs23 from "node:fs";
|
|
145351
145984
|
async function extractClaims(rootDir, opts = {}) {
|
|
145352
145985
|
const docs = opts.docs ?? discoverDocs(rootDir, opts);
|
|
145353
145986
|
const allBlocks = [];
|
|
@@ -145424,7 +146057,7 @@ function shortQuote(text) {
|
|
|
145424
146057
|
return [...lines.slice(0, 40), `\u2026 (+${lines.length - 40} more lines)`].join("\n");
|
|
145425
146058
|
}
|
|
145426
146059
|
function readFileSync10(absPath) {
|
|
145427
|
-
return
|
|
146060
|
+
return fs23.readFileSync(absPath, "utf-8");
|
|
145428
146061
|
}
|
|
145429
146062
|
|
|
145430
146063
|
// packages/spec-consolidator/dist/merger.js
|
|
@@ -145614,7 +146247,7 @@ function pickRichestSameFile(list) {
|
|
|
145614
146247
|
}
|
|
145615
146248
|
}
|
|
145616
146249
|
const winner = list[winnerIdx];
|
|
145617
|
-
const others = list.filter((
|
|
146250
|
+
const others = list.filter((_2, i) => i !== winnerIdx);
|
|
145618
146251
|
return {
|
|
145619
146252
|
...winner,
|
|
145620
146253
|
provenance: {
|
|
@@ -146271,21 +146904,21 @@ function uniqueSorted(arr) {
|
|
|
146271
146904
|
// packages/spec-consolidator/dist/cache.js
|
|
146272
146905
|
init_zod();
|
|
146273
146906
|
import { createHash as createHash8 } from "node:crypto";
|
|
146274
|
-
import
|
|
146275
|
-
import
|
|
146907
|
+
import fs24 from "node:fs";
|
|
146908
|
+
import path26 from "node:path";
|
|
146276
146909
|
var EXTRACTION_PROMPT_FINGERPRINT2 = createHash8("sha256").update(SYSTEM_PROMPT2).digest("hex").slice(0, 16);
|
|
146277
|
-
var CACHE_RELATIVE =
|
|
146910
|
+
var CACHE_RELATIVE = path26.join(".truecourse", ".cache", "consolidator");
|
|
146278
146911
|
var BLOCKS_SUBDIR = "blocks";
|
|
146279
146912
|
function cachePaths2(repoRoot6) {
|
|
146280
|
-
const cacheDir =
|
|
146913
|
+
const cacheDir = path26.join(repoRoot6, CACHE_RELATIVE);
|
|
146281
146914
|
return {
|
|
146282
146915
|
cacheDir,
|
|
146283
|
-
blocksDir:
|
|
146916
|
+
blocksDir: path26.join(cacheDir, BLOCKS_SUBDIR)
|
|
146284
146917
|
};
|
|
146285
146918
|
}
|
|
146286
146919
|
function ensureCacheDirs2(repoRoot6) {
|
|
146287
146920
|
const paths = cachePaths2(repoRoot6);
|
|
146288
|
-
|
|
146921
|
+
fs24.mkdirSync(paths.blocksDir, { recursive: true });
|
|
146289
146922
|
return paths;
|
|
146290
146923
|
}
|
|
146291
146924
|
var BlockCacheEntrySchema = external_exports.object({
|
|
@@ -146302,11 +146935,11 @@ var BlockCacheEntrySchema = external_exports.object({
|
|
|
146302
146935
|
promptFingerprint: external_exports.string().optional()
|
|
146303
146936
|
});
|
|
146304
146937
|
function readBlockCache(repoRoot6, blockId) {
|
|
146305
|
-
const file =
|
|
146306
|
-
if (!
|
|
146938
|
+
const file = path26.join(cachePaths2(repoRoot6).blocksDir, `${blockId}.json`);
|
|
146939
|
+
if (!fs24.existsSync(file))
|
|
146307
146940
|
return null;
|
|
146308
146941
|
try {
|
|
146309
|
-
const raw = JSON.parse(
|
|
146942
|
+
const raw = JSON.parse(fs24.readFileSync(file, "utf-8"));
|
|
146310
146943
|
const entry = BlockCacheEntrySchema.parse(raw);
|
|
146311
146944
|
if (entry.promptFingerprint !== EXTRACTION_PROMPT_FINGERPRINT2)
|
|
146312
146945
|
return null;
|
|
@@ -146323,19 +146956,19 @@ function writeBlockCache(repoRoot6, blockId, extraction) {
|
|
|
146323
146956
|
cachedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
146324
146957
|
promptFingerprint: EXTRACTION_PROMPT_FINGERPRINT2
|
|
146325
146958
|
};
|
|
146326
|
-
const file =
|
|
146327
|
-
|
|
146959
|
+
const file = path26.join(cachePaths2(repoRoot6).blocksDir, `${blockId}.json`);
|
|
146960
|
+
fs24.writeFileSync(file, JSON.stringify(entry, null, 2));
|
|
146328
146961
|
}
|
|
146329
146962
|
var SCAN_STATE_FILE = "scan-state.json";
|
|
146330
146963
|
function scanStatePath(repoRoot6) {
|
|
146331
|
-
return
|
|
146964
|
+
return path26.join(cachePaths2(repoRoot6).cacheDir, SCAN_STATE_FILE);
|
|
146332
146965
|
}
|
|
146333
146966
|
function readScanState(repoRoot6) {
|
|
146334
146967
|
const file = scanStatePath(repoRoot6);
|
|
146335
|
-
if (!
|
|
146968
|
+
if (!fs24.existsSync(file))
|
|
146336
146969
|
return null;
|
|
146337
146970
|
try {
|
|
146338
|
-
const raw = JSON.parse(
|
|
146971
|
+
const raw = JSON.parse(fs24.readFileSync(file, "utf-8"));
|
|
146339
146972
|
if (typeof raw.scannedAt !== "string")
|
|
146340
146973
|
return null;
|
|
146341
146974
|
if (!Array.isArray(raw.openConflicts) || !Array.isArray(raw.decidedConflicts))
|
|
@@ -146348,16 +146981,16 @@ function readScanState(repoRoot6) {
|
|
|
146348
146981
|
function writeScanState(repoRoot6, state) {
|
|
146349
146982
|
ensureCacheDirs2(repoRoot6);
|
|
146350
146983
|
const file = scanStatePath(repoRoot6);
|
|
146351
|
-
|
|
146984
|
+
fs24.writeFileSync(file, JSON.stringify(state, null, 2) + "\n");
|
|
146352
146985
|
}
|
|
146353
146986
|
|
|
146354
146987
|
// packages/spec-consolidator/dist/claims-store.js
|
|
146355
146988
|
init_zod();
|
|
146356
|
-
import
|
|
146357
|
-
import
|
|
146989
|
+
import fs25 from "node:fs";
|
|
146990
|
+
import path27 from "node:path";
|
|
146358
146991
|
var CLAIMS_FILE = "claims.json";
|
|
146359
146992
|
function claimsFilePath(repoRoot6) {
|
|
146360
|
-
return
|
|
146993
|
+
return path27.join(repoRoot6, ".truecourse", "specs", CLAIMS_FILE);
|
|
146361
146994
|
}
|
|
146362
146995
|
var ClaimsFileModuleSchema = ModuleManifestSchema;
|
|
146363
146996
|
var ClaimsFileEntrySchema = ClaimSchema.extend({
|
|
@@ -146378,10 +147011,10 @@ var ClaimsFileSchema = external_exports.object({
|
|
|
146378
147011
|
});
|
|
146379
147012
|
function readClaims(repoRoot6) {
|
|
146380
147013
|
const file = claimsFilePath(repoRoot6);
|
|
146381
|
-
if (!
|
|
147014
|
+
if (!fs25.existsSync(file))
|
|
146382
147015
|
return null;
|
|
146383
147016
|
try {
|
|
146384
|
-
const raw = JSON.parse(
|
|
147017
|
+
const raw = JSON.parse(fs25.readFileSync(file, "utf-8"));
|
|
146385
147018
|
return ClaimsFileSchema.parse(raw);
|
|
146386
147019
|
} catch {
|
|
146387
147020
|
return null;
|
|
@@ -146389,26 +147022,26 @@ function readClaims(repoRoot6) {
|
|
|
146389
147022
|
}
|
|
146390
147023
|
function writeClaims(repoRoot6, input) {
|
|
146391
147024
|
const file = claimsFilePath(repoRoot6);
|
|
146392
|
-
|
|
147025
|
+
fs25.mkdirSync(path27.dirname(file), { recursive: true });
|
|
146393
147026
|
const payload = {
|
|
146394
147027
|
version: 1,
|
|
146395
147028
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
146396
147029
|
modules: input.modules,
|
|
146397
147030
|
claims: input.claims
|
|
146398
147031
|
};
|
|
146399
|
-
|
|
147032
|
+
fs25.writeFileSync(file, JSON.stringify(payload, null, 2) + "\n");
|
|
146400
147033
|
}
|
|
146401
147034
|
function entryFromClaim(claim, module, source = "extracted") {
|
|
146402
147035
|
return { ...claim, module, source };
|
|
146403
147036
|
}
|
|
146404
147037
|
|
|
146405
147038
|
// packages/spec-consolidator/dist/orchestrator.js
|
|
146406
|
-
import
|
|
146407
|
-
import
|
|
147039
|
+
import fs31 from "node:fs";
|
|
147040
|
+
import path34 from "node:path";
|
|
146408
147041
|
|
|
146409
147042
|
// packages/spec-consolidator/dist/version-chain.js
|
|
146410
147043
|
import { createHash as createHash9 } from "node:crypto";
|
|
146411
|
-
import
|
|
147044
|
+
import path28 from "node:path";
|
|
146412
147045
|
function materializeManualChains(manualChains, docs) {
|
|
146413
147046
|
const byPath = new Map(docs.map((d3) => [d3.path, d3]));
|
|
146414
147047
|
const out = [];
|
|
@@ -146444,10 +147077,10 @@ function detectVersionChains(docs) {
|
|
|
146444
147077
|
}
|
|
146445
147078
|
var VERSION_SUFFIX_RE = /(.*?)(v\d+)(\.[^./]+)?$/i;
|
|
146446
147079
|
function filenameVersionPair(a, b) {
|
|
146447
|
-
if (
|
|
147080
|
+
if (path28.dirname(a.path) !== path28.dirname(b.path))
|
|
146448
147081
|
return null;
|
|
146449
|
-
const aMatch = VERSION_SUFFIX_RE.exec(stripExt(
|
|
146450
|
-
const bMatch = VERSION_SUFFIX_RE.exec(stripExt(
|
|
147082
|
+
const aMatch = VERSION_SUFFIX_RE.exec(stripExt(path28.basename(a.path)));
|
|
147083
|
+
const bMatch = VERSION_SUFFIX_RE.exec(stripExt(path28.basename(b.path)));
|
|
146451
147084
|
if (!aMatch || !bMatch)
|
|
146452
147085
|
return null;
|
|
146453
147086
|
if (aMatch[1].toLowerCase() !== bMatch[1].toLowerCase())
|
|
@@ -146470,8 +147103,8 @@ function makeChain(docs, detectedFrom) {
|
|
|
146470
147103
|
// packages/spec-consolidator/dist/chain-recheck.js
|
|
146471
147104
|
init_zod();
|
|
146472
147105
|
import { createHash as createHash10 } from "node:crypto";
|
|
146473
|
-
import
|
|
146474
|
-
import
|
|
147106
|
+
import fs26 from "node:fs";
|
|
147107
|
+
import path29 from "node:path";
|
|
146475
147108
|
var CACHE_FILE = "chain-recheck.json";
|
|
146476
147109
|
var FUNDAMENTAL_SUBJECTS = [
|
|
146477
147110
|
/\bauth\b/i,
|
|
@@ -146564,7 +147197,7 @@ async function runChainRecheck(repoRoot6, pairs2, opts = {}) {
|
|
|
146564
147197
|
}
|
|
146565
147198
|
function readSafe(absPath) {
|
|
146566
147199
|
try {
|
|
146567
|
-
return
|
|
147200
|
+
return fs26.readFileSync(absPath, "utf-8");
|
|
146568
147201
|
} catch {
|
|
146569
147202
|
return null;
|
|
146570
147203
|
}
|
|
@@ -146658,14 +147291,14 @@ function computePairCacheKey(pair, olderContent, newerContent) {
|
|
|
146658
147291
|
return createHash10("sha256").update(`${PROMPT_FINGERPRINT}::${pair.older.path}|${olderHash}::${pair.newer.path}|${newerHash}`).digest("hex");
|
|
146659
147292
|
}
|
|
146660
147293
|
function pairCacheFile(repoRoot6) {
|
|
146661
|
-
return
|
|
147294
|
+
return path29.join(cachePaths2(repoRoot6).cacheDir, CACHE_FILE);
|
|
146662
147295
|
}
|
|
146663
147296
|
function readPairCache(repoRoot6, cacheKey) {
|
|
146664
147297
|
const file = pairCacheFile(repoRoot6);
|
|
146665
|
-
if (!
|
|
147298
|
+
if (!fs26.existsSync(file))
|
|
146666
147299
|
return null;
|
|
146667
147300
|
try {
|
|
146668
|
-
const raw = JSON.parse(
|
|
147301
|
+
const raw = JSON.parse(fs26.readFileSync(file, "utf-8"));
|
|
146669
147302
|
const entry = (raw.entries ?? []).find((e) => e.cacheKey === cacheKey);
|
|
146670
147303
|
return entry ? ChainRecheckResultSchema.parse(entry.result) : null;
|
|
146671
147304
|
} catch {
|
|
@@ -146676,9 +147309,9 @@ function writePairCache(repoRoot6, cacheKey, result) {
|
|
|
146676
147309
|
ensureCacheDirs2(repoRoot6);
|
|
146677
147310
|
const file = pairCacheFile(repoRoot6);
|
|
146678
147311
|
let entries = [];
|
|
146679
|
-
if (
|
|
147312
|
+
if (fs26.existsSync(file)) {
|
|
146680
147313
|
try {
|
|
146681
|
-
const raw = JSON.parse(
|
|
147314
|
+
const raw = JSON.parse(fs26.readFileSync(file, "utf-8"));
|
|
146682
147315
|
entries = raw.entries ?? [];
|
|
146683
147316
|
} catch {
|
|
146684
147317
|
entries = [];
|
|
@@ -146686,14 +147319,14 @@ function writePairCache(repoRoot6, cacheKey, result) {
|
|
|
146686
147319
|
}
|
|
146687
147320
|
const filtered = entries.filter((e) => e.cacheKey !== cacheKey);
|
|
146688
147321
|
filtered.push({ cacheKey, result, cachedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
146689
|
-
|
|
147322
|
+
fs26.writeFileSync(file, JSON.stringify({ entries: filtered }, null, 2) + "\n");
|
|
146690
147323
|
}
|
|
146691
147324
|
|
|
146692
147325
|
// packages/spec-consolidator/dist/conflict-explainer.js
|
|
146693
147326
|
init_zod();
|
|
146694
147327
|
import { createHash as createHash11 } from "node:crypto";
|
|
146695
|
-
import
|
|
146696
|
-
import
|
|
147328
|
+
import fs27 from "node:fs";
|
|
147329
|
+
import path30 from "node:path";
|
|
146697
147330
|
var CACHE_FILE2 = "conflict-explanations.json";
|
|
146698
147331
|
async function explainConflicts(repoRoot6, conflicts2, opts = {}) {
|
|
146699
147332
|
if (opts.enabled === false)
|
|
@@ -146912,14 +147545,14 @@ function computeCacheKey(conflict) {
|
|
|
146912
147545
|
return createHash11("sha256").update(`${fp}::${conflict.id}::${ids}`).digest("hex");
|
|
146913
147546
|
}
|
|
146914
147547
|
function cacheFile(repoRoot6) {
|
|
146915
|
-
return
|
|
147548
|
+
return path30.join(cachePaths2(repoRoot6).cacheDir, CACHE_FILE2);
|
|
146916
147549
|
}
|
|
146917
147550
|
function readCache(repoRoot6, cacheKey) {
|
|
146918
147551
|
const file = cacheFile(repoRoot6);
|
|
146919
|
-
if (!
|
|
147552
|
+
if (!fs27.existsSync(file))
|
|
146920
147553
|
return null;
|
|
146921
147554
|
try {
|
|
146922
|
-
const raw = ExplanationCacheFileSchema.parse(JSON.parse(
|
|
147555
|
+
const raw = ExplanationCacheFileSchema.parse(JSON.parse(fs27.readFileSync(file, "utf-8")));
|
|
146923
147556
|
const entry = raw.entries.find((e) => e.cacheKey === cacheKey);
|
|
146924
147557
|
return entry ? entry.explanation : null;
|
|
146925
147558
|
} catch {
|
|
@@ -146930,23 +147563,23 @@ function writeCache(repoRoot6, cacheKey, explanation) {
|
|
|
146930
147563
|
ensureCacheDirs2(repoRoot6);
|
|
146931
147564
|
const file = cacheFile(repoRoot6);
|
|
146932
147565
|
let entries = [];
|
|
146933
|
-
if (
|
|
147566
|
+
if (fs27.existsSync(file)) {
|
|
146934
147567
|
try {
|
|
146935
|
-
entries = ExplanationCacheFileSchema.parse(JSON.parse(
|
|
147568
|
+
entries = ExplanationCacheFileSchema.parse(JSON.parse(fs27.readFileSync(file, "utf-8"))).entries;
|
|
146936
147569
|
} catch {
|
|
146937
147570
|
entries = [];
|
|
146938
147571
|
}
|
|
146939
147572
|
}
|
|
146940
147573
|
const filtered = entries.filter((e) => e.cacheKey !== cacheKey);
|
|
146941
147574
|
filtered.push({ cacheKey, explanation, cachedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
146942
|
-
|
|
147575
|
+
fs27.writeFileSync(file, JSON.stringify({ entries: filtered }, null, 2) + "\n");
|
|
146943
147576
|
}
|
|
146944
147577
|
|
|
146945
147578
|
// packages/spec-consolidator/dist/conflict-resolver.js
|
|
146946
147579
|
init_zod();
|
|
146947
147580
|
import { createHash as createHash12 } from "node:crypto";
|
|
146948
|
-
import
|
|
146949
|
-
import
|
|
147581
|
+
import fs28 from "node:fs";
|
|
147582
|
+
import path31 from "node:path";
|
|
146950
147583
|
var CACHE_FILE3 = "conflict-resolutions.json";
|
|
146951
147584
|
async function resolveConflicts(repoRoot6, conflicts2, opts = {}) {
|
|
146952
147585
|
if (opts.enabled === false || conflicts2.length === 0)
|
|
@@ -147158,14 +147791,14 @@ function computeCacheKey2(conflict) {
|
|
|
147158
147791
|
return createHash12("sha256").update(`${PROMPT_FINGERPRINT2}::${conflict.id}::${ids}`).digest("hex");
|
|
147159
147792
|
}
|
|
147160
147793
|
function cacheFile2(repoRoot6) {
|
|
147161
|
-
return
|
|
147794
|
+
return path31.join(cachePaths2(repoRoot6).cacheDir, CACHE_FILE3);
|
|
147162
147795
|
}
|
|
147163
147796
|
function readCache2(repoRoot6, cacheKey) {
|
|
147164
147797
|
const file = cacheFile2(repoRoot6);
|
|
147165
|
-
if (!
|
|
147798
|
+
if (!fs28.existsSync(file))
|
|
147166
147799
|
return null;
|
|
147167
147800
|
try {
|
|
147168
|
-
const raw = ResolutionCacheFileSchema.parse(JSON.parse(
|
|
147801
|
+
const raw = ResolutionCacheFileSchema.parse(JSON.parse(fs28.readFileSync(file, "utf-8")));
|
|
147169
147802
|
const entry = raw.entries.find((e) => e.cacheKey === cacheKey);
|
|
147170
147803
|
return entry ? entry.resolution : null;
|
|
147171
147804
|
} catch {
|
|
@@ -147176,23 +147809,23 @@ function writeCache2(repoRoot6, cacheKey, resolution) {
|
|
|
147176
147809
|
ensureCacheDirs2(repoRoot6);
|
|
147177
147810
|
const file = cacheFile2(repoRoot6);
|
|
147178
147811
|
let entries = [];
|
|
147179
|
-
if (
|
|
147812
|
+
if (fs28.existsSync(file)) {
|
|
147180
147813
|
try {
|
|
147181
|
-
entries = ResolutionCacheFileSchema.parse(JSON.parse(
|
|
147814
|
+
entries = ResolutionCacheFileSchema.parse(JSON.parse(fs28.readFileSync(file, "utf-8"))).entries;
|
|
147182
147815
|
} catch {
|
|
147183
147816
|
entries = [];
|
|
147184
147817
|
}
|
|
147185
147818
|
}
|
|
147186
147819
|
const filtered = entries.filter((e) => e.cacheKey !== cacheKey);
|
|
147187
147820
|
filtered.push({ cacheKey, resolution, cachedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
147188
|
-
|
|
147821
|
+
fs28.writeFileSync(file, JSON.stringify({ entries: filtered }, null, 2) + "\n");
|
|
147189
147822
|
}
|
|
147190
147823
|
|
|
147191
147824
|
// packages/spec-consolidator/dist/relevance-filter.js
|
|
147192
147825
|
init_zod();
|
|
147193
147826
|
import { createHash as createHash13 } from "node:crypto";
|
|
147194
|
-
import
|
|
147195
|
-
import
|
|
147827
|
+
import fs29 from "node:fs";
|
|
147828
|
+
import path32 from "node:path";
|
|
147196
147829
|
var CACHE_FILE4 = "relevance.json";
|
|
147197
147830
|
async function filterByRelevance(repoRoot6, docs, opts = {}) {
|
|
147198
147831
|
if (opts.enabled === false || docs.length === 0) {
|
|
@@ -147344,14 +147977,14 @@ function computeCacheKey3(doc) {
|
|
|
147344
147977
|
return createHash13("sha256").update(`${PROMPT_FINGERPRINT3}::${doc.path}::${doc.contentHash}`).digest("hex");
|
|
147345
147978
|
}
|
|
147346
147979
|
function cacheFile3(repoRoot6) {
|
|
147347
|
-
return
|
|
147980
|
+
return path32.join(cachePaths2(repoRoot6).cacheDir, CACHE_FILE4);
|
|
147348
147981
|
}
|
|
147349
147982
|
function readCache3(repoRoot6, cacheKey) {
|
|
147350
147983
|
const file = cacheFile3(repoRoot6);
|
|
147351
|
-
if (!
|
|
147984
|
+
if (!fs29.existsSync(file))
|
|
147352
147985
|
return null;
|
|
147353
147986
|
try {
|
|
147354
|
-
const raw = RelevanceCacheFileSchema.parse(JSON.parse(
|
|
147987
|
+
const raw = RelevanceCacheFileSchema.parse(JSON.parse(fs29.readFileSync(file, "utf-8")));
|
|
147355
147988
|
const entry = raw.entries.find((e) => e.cacheKey === cacheKey);
|
|
147356
147989
|
return entry ? entry.verdict : null;
|
|
147357
147990
|
} catch {
|
|
@@ -147362,23 +147995,23 @@ function writeCache3(repoRoot6, cacheKey, verdict) {
|
|
|
147362
147995
|
ensureCacheDirs2(repoRoot6);
|
|
147363
147996
|
const file = cacheFile3(repoRoot6);
|
|
147364
147997
|
let entries = [];
|
|
147365
|
-
if (
|
|
147998
|
+
if (fs29.existsSync(file)) {
|
|
147366
147999
|
try {
|
|
147367
|
-
entries = RelevanceCacheFileSchema.parse(JSON.parse(
|
|
148000
|
+
entries = RelevanceCacheFileSchema.parse(JSON.parse(fs29.readFileSync(file, "utf-8"))).entries;
|
|
147368
148001
|
} catch {
|
|
147369
148002
|
entries = [];
|
|
147370
148003
|
}
|
|
147371
148004
|
}
|
|
147372
148005
|
const filtered = entries.filter((e) => e.cacheKey !== cacheKey);
|
|
147373
148006
|
filtered.push({ cacheKey, verdict, cachedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
147374
|
-
|
|
148007
|
+
fs29.writeFileSync(file, JSON.stringify({ entries: filtered }, null, 2) + "\n");
|
|
147375
148008
|
}
|
|
147376
148009
|
|
|
147377
148010
|
// packages/spec-consolidator/dist/version-chain-llm.js
|
|
147378
148011
|
init_zod();
|
|
147379
148012
|
import { createHash as createHash14 } from "node:crypto";
|
|
147380
|
-
import
|
|
147381
|
-
import
|
|
148013
|
+
import fs30 from "node:fs";
|
|
148014
|
+
import path33 from "node:path";
|
|
147382
148015
|
var CACHE_FILE5 = "chain-detection.json";
|
|
147383
148016
|
var DetectedChainOutputSchema = external_exports.object({
|
|
147384
148017
|
chains: external_exports.array(external_exports.object({
|
|
@@ -147481,11 +148114,11 @@ function computeCacheKey4(inputs) {
|
|
|
147481
148114
|
return createHash14("sha256").update(`${PROMPT_FINGERPRINT4}::${material}`).digest("hex");
|
|
147482
148115
|
}
|
|
147483
148116
|
function readChainDetectionCache(repoRoot6, cacheKey) {
|
|
147484
|
-
const file =
|
|
147485
|
-
if (!
|
|
148117
|
+
const file = path33.join(cachePaths2(repoRoot6).cacheDir, CACHE_FILE5);
|
|
148118
|
+
if (!fs30.existsSync(file))
|
|
147486
148119
|
return null;
|
|
147487
148120
|
try {
|
|
147488
|
-
const raw = JSON.parse(
|
|
148121
|
+
const raw = JSON.parse(fs30.readFileSync(file, "utf-8"));
|
|
147489
148122
|
if (raw.cacheKey !== cacheKey)
|
|
147490
148123
|
return null;
|
|
147491
148124
|
return DetectedChainOutputSchema.parse(raw.result);
|
|
@@ -147495,13 +148128,13 @@ function readChainDetectionCache(repoRoot6, cacheKey) {
|
|
|
147495
148128
|
}
|
|
147496
148129
|
function writeChainDetectionCache(repoRoot6, cacheKey, result) {
|
|
147497
148130
|
ensureCacheDirs2(repoRoot6);
|
|
147498
|
-
const file =
|
|
148131
|
+
const file = path33.join(cachePaths2(repoRoot6).cacheDir, CACHE_FILE5);
|
|
147499
148132
|
const entry = {
|
|
147500
148133
|
cacheKey,
|
|
147501
148134
|
result,
|
|
147502
148135
|
cachedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
147503
148136
|
};
|
|
147504
|
-
|
|
148137
|
+
fs30.writeFileSync(file, JSON.stringify(entry, null, 2) + "\n");
|
|
147505
148138
|
}
|
|
147506
148139
|
function materializeChains(result, docs) {
|
|
147507
148140
|
const byPath = new Map(docs.map((d3) => [d3.path, d3]));
|
|
@@ -147658,7 +148291,7 @@ function synthesizeChainConflict(chain) {
|
|
|
147658
148291
|
return {
|
|
147659
148292
|
id: chain.id,
|
|
147660
148293
|
topic: "overview",
|
|
147661
|
-
subject: `version chain: ${chain.docs.map((d3) =>
|
|
148294
|
+
subject: `version chain: ${chain.docs.map((d3) => path34.basename(d3.path)).join(" \u2192 ")}`,
|
|
147662
148295
|
candidates,
|
|
147663
148296
|
defaultPick: candidates.length - 1
|
|
147664
148297
|
};
|
|
@@ -147670,7 +148303,7 @@ function docToSyntheticClaim(doc, chain) {
|
|
|
147670
148303
|
return {
|
|
147671
148304
|
id: `version-chain:${chain.id}:${doc.path}`,
|
|
147672
148305
|
topic: "overview",
|
|
147673
|
-
subject: `version chain: ${chain.docs.map((d3) =>
|
|
148306
|
+
subject: `version chain: ${chain.docs.map((d3) => path34.basename(d3.path)).join(" \u2192 ")}`,
|
|
147674
148307
|
content: {
|
|
147675
148308
|
file: doc.path,
|
|
147676
148309
|
detectedFrom: chain.detectedFrom,
|
|
@@ -147845,14 +148478,14 @@ function stitchChainConflicts(merge2, chainConflicts, chains, decisions) {
|
|
|
147845
148478
|
}
|
|
147846
148479
|
var EMPTY_DECISIONS = { version: 1, decisions: [], manualChains: [], manualIncludes: [] };
|
|
147847
148480
|
function decisionsPath(repoRoot6) {
|
|
147848
|
-
return
|
|
148481
|
+
return path34.join(repoRoot6, ".truecourse", "specs", "decisions.json");
|
|
147849
148482
|
}
|
|
147850
148483
|
function readDecisions(repoRoot6) {
|
|
147851
148484
|
const file = decisionsPath(repoRoot6);
|
|
147852
|
-
if (!
|
|
148485
|
+
if (!fs31.existsSync(file))
|
|
147853
148486
|
return EMPTY_DECISIONS;
|
|
147854
148487
|
try {
|
|
147855
|
-
const raw = JSON.parse(
|
|
148488
|
+
const raw = JSON.parse(fs31.readFileSync(file, "utf-8"));
|
|
147856
148489
|
return DecisionsFileSchema.parse(raw);
|
|
147857
148490
|
} catch {
|
|
147858
148491
|
return EMPTY_DECISIONS;
|
|
@@ -147860,8 +148493,8 @@ function readDecisions(repoRoot6) {
|
|
|
147860
148493
|
}
|
|
147861
148494
|
function writeDecisions(repoRoot6, decisions) {
|
|
147862
148495
|
const file = decisionsPath(repoRoot6);
|
|
147863
|
-
|
|
147864
|
-
|
|
148496
|
+
fs31.mkdirSync(path34.dirname(file), { recursive: true });
|
|
148497
|
+
fs31.writeFileSync(file, JSON.stringify(decisions, null, 2) + "\n");
|
|
147865
148498
|
}
|
|
147866
148499
|
function wrapBlockRunner(repoRoot6, inner, onBlockDone) {
|
|
147867
148500
|
return async (blocks) => {
|
|
@@ -147973,11 +148606,11 @@ function synthesizeCustomClaim(conflict, content, resolvedAt) {
|
|
|
147973
148606
|
|
|
147974
148607
|
// packages/contract-extractor/dist/claims-reader.js
|
|
147975
148608
|
function canonicalSpecPath(repoRoot6) {
|
|
147976
|
-
return
|
|
148609
|
+
return path35.join(repoRoot6, ".truecourse", "specs", "claims.json");
|
|
147977
148610
|
}
|
|
147978
148611
|
function hasCanonicalSpec(repoRoot6) {
|
|
147979
148612
|
const file = canonicalSpecPath(repoRoot6);
|
|
147980
|
-
if (!
|
|
148613
|
+
if (!fs32.existsSync(file))
|
|
147981
148614
|
return false;
|
|
147982
148615
|
const parsed = readClaims(repoRoot6);
|
|
147983
148616
|
return parsed !== null;
|
|
@@ -148578,7 +149211,7 @@ function rulesFor(a) {
|
|
|
148578
149211
|
}
|
|
148579
149212
|
if (a.kind === "auth-requirement") {
|
|
148580
149213
|
if (!/\bon-violation\s*\{/.test(src)) {
|
|
148581
|
-
out.push("missing `on-violation { status ... error-code ... body ErrorEnvelope
|
|
149214
|
+
out.push("missing `on-violation { status ... error-code ... }` (add `body ErrorEnvelope:error.envelope.standard` only when that envelope is defined in the corpus).");
|
|
148582
149215
|
}
|
|
148583
149216
|
if (/\brequired-role\b/.test(src)) {
|
|
148584
149217
|
const hasBroadGlob = /selector\s+path-glob\s+"\/api\/(\*\*|\*)"/.test(src);
|
|
@@ -148851,7 +149484,7 @@ function validateMerged(artifacts) {
|
|
|
148851
149484
|
}
|
|
148852
149485
|
|
|
148853
149486
|
// packages/contract-extractor/dist/writer.js
|
|
148854
|
-
import
|
|
149487
|
+
import fs45 from "node:fs";
|
|
148855
149488
|
import path52 from "node:path";
|
|
148856
149489
|
var CONTRACTS_DIR = path52.join(".truecourse", "contracts");
|
|
148857
149490
|
var SHARED_KINDS2 = /* @__PURE__ */ new Set([
|
|
@@ -148871,14 +149504,14 @@ function writeContracts(repoRoot6, artifacts, options = {}) {
|
|
|
148871
149504
|
proposed.push(req.filePath);
|
|
148872
149505
|
continue;
|
|
148873
149506
|
}
|
|
148874
|
-
|
|
148875
|
-
const existing =
|
|
149507
|
+
fs45.mkdirSync(path52.dirname(req.filePath), { recursive: true });
|
|
149508
|
+
const existing = fs45.existsSync(req.filePath) ? fs45.readFileSync(req.filePath, "utf-8") : null;
|
|
148876
149509
|
if (existing === req.tcSource)
|
|
148877
149510
|
continue;
|
|
148878
|
-
|
|
149511
|
+
fs45.writeFileSync(req.filePath, req.tcSource);
|
|
148879
149512
|
written.push(req.filePath);
|
|
148880
149513
|
}
|
|
148881
|
-
if (options.prune && !options.dryRun &&
|
|
149514
|
+
if (options.prune && !options.dryRun && fs45.existsSync(root)) {
|
|
148882
149515
|
const live = new Set(requests.map((r) => r.filePath));
|
|
148883
149516
|
pruneStale2(root, live);
|
|
148884
149517
|
}
|
|
@@ -148947,16 +149580,16 @@ function slugifyIdentity2(identity) {
|
|
|
148947
149580
|
}
|
|
148948
149581
|
function pruneStale2(root, live) {
|
|
148949
149582
|
const visit = (dir) => {
|
|
148950
|
-
if (!
|
|
149583
|
+
if (!fs45.existsSync(dir))
|
|
148951
149584
|
return false;
|
|
148952
149585
|
let dirEmpty = true;
|
|
148953
|
-
for (const entry of
|
|
149586
|
+
for (const entry of fs45.readdirSync(dir, { withFileTypes: true })) {
|
|
148954
149587
|
const full = path52.join(dir, entry.name);
|
|
148955
149588
|
if (entry.isDirectory()) {
|
|
148956
149589
|
const childEmpty = visit(full);
|
|
148957
149590
|
if (childEmpty) {
|
|
148958
149591
|
try {
|
|
148959
|
-
|
|
149592
|
+
fs45.rmdirSync(full);
|
|
148960
149593
|
} catch {
|
|
148961
149594
|
}
|
|
148962
149595
|
} else {
|
|
@@ -148964,7 +149597,7 @@ function pruneStale2(root, live) {
|
|
|
148964
149597
|
}
|
|
148965
149598
|
} else if (entry.isFile()) {
|
|
148966
149599
|
if (entry.name.endsWith(".tc") && !live.has(full)) {
|
|
148967
|
-
|
|
149600
|
+
fs45.unlinkSync(full);
|
|
148968
149601
|
} else {
|
|
148969
149602
|
dirEmpty = false;
|
|
148970
149603
|
}
|
|
@@ -149178,7 +149811,7 @@ function hashString(s) {
|
|
|
149178
149811
|
|
|
149179
149812
|
// packages/core/dist/config/llm-models.js
|
|
149180
149813
|
init_paths();
|
|
149181
|
-
import
|
|
149814
|
+
import fs46 from "node:fs";
|
|
149182
149815
|
var STAGE_DEFAULTS = {
|
|
149183
149816
|
"spec.chainDetect": "haiku",
|
|
149184
149817
|
"spec.claimExtract": "sonnet",
|
|
@@ -149207,10 +149840,10 @@ function stageEnvVar(stageId) {
|
|
|
149207
149840
|
}
|
|
149208
149841
|
function readConfigSync(repoDir) {
|
|
149209
149842
|
const file = getRepoConfigPath(repoDir);
|
|
149210
|
-
if (!
|
|
149843
|
+
if (!fs46.existsSync(file))
|
|
149211
149844
|
return {};
|
|
149212
149845
|
try {
|
|
149213
|
-
return JSON.parse(
|
|
149846
|
+
return JSON.parse(fs46.readFileSync(file, "utf-8"));
|
|
149214
149847
|
} catch {
|
|
149215
149848
|
return {};
|
|
149216
149849
|
}
|
|
@@ -149282,14 +149915,14 @@ function describeStageResolutions(repoDir = resolveRepoDir(process.cwd())) {
|
|
|
149282
149915
|
// packages/core/dist/commands/spec-in-process.js
|
|
149283
149916
|
init_dist8();
|
|
149284
149917
|
init_git();
|
|
149285
|
-
import
|
|
149918
|
+
import fs48 from "node:fs";
|
|
149286
149919
|
import path54 from "node:path";
|
|
149287
149920
|
import { randomUUID as randomUUID23 } from "node:crypto";
|
|
149288
149921
|
|
|
149289
149922
|
// packages/core/dist/lib/verify-store.js
|
|
149290
149923
|
init_atomic_write();
|
|
149291
149924
|
init_analysis_store();
|
|
149292
|
-
import
|
|
149925
|
+
import fs47 from "node:fs";
|
|
149293
149926
|
import path53 from "node:path";
|
|
149294
149927
|
|
|
149295
149928
|
// packages/core/dist/types/verify-snapshot.js
|
|
@@ -149363,7 +149996,7 @@ function readVerifyLatest(repoPath) {
|
|
|
149363
149996
|
const file = verifyLatestPath(repoPath);
|
|
149364
149997
|
let mtime;
|
|
149365
149998
|
try {
|
|
149366
|
-
mtime =
|
|
149999
|
+
mtime = fs47.statSync(file).mtimeMs;
|
|
149367
150000
|
} catch (err) {
|
|
149368
150001
|
if (err.code === "ENOENT") {
|
|
149369
150002
|
latestCache2.delete(repoPath);
|
|
@@ -149374,7 +150007,7 @@ function readVerifyLatest(repoPath) {
|
|
|
149374
150007
|
const cached = latestCache2.get(repoPath);
|
|
149375
150008
|
if (cached && cached.mtime === mtime)
|
|
149376
150009
|
return cached.data;
|
|
149377
|
-
const data = JSON.parse(
|
|
150010
|
+
const data = JSON.parse(fs47.readFileSync(file, "utf-8"));
|
|
149378
150011
|
latestCache2.set(repoPath, { mtime, data });
|
|
149379
150012
|
return data;
|
|
149380
150013
|
}
|
|
@@ -149389,9 +150022,9 @@ function writeVerifyRun(repoPath, snapshot) {
|
|
|
149389
150022
|
}
|
|
149390
150023
|
function readVerifyHistory(repoPath) {
|
|
149391
150024
|
const file = verifyHistoryPath(repoPath);
|
|
149392
|
-
if (!
|
|
150025
|
+
if (!fs47.existsSync(file))
|
|
149393
150026
|
return { runs: [] };
|
|
149394
|
-
return JSON.parse(
|
|
150027
|
+
return JSON.parse(fs47.readFileSync(file, "utf-8"));
|
|
149395
150028
|
}
|
|
149396
150029
|
function appendVerifyHistory(repoPath, entry) {
|
|
149397
150030
|
const history = readVerifyHistory(repoPath);
|
|
@@ -149403,7 +150036,7 @@ function writeVerifyDiff(repoPath, diff) {
|
|
|
149403
150036
|
}
|
|
149404
150037
|
function deleteVerifyDiff(repoPath) {
|
|
149405
150038
|
try {
|
|
149406
|
-
|
|
150039
|
+
fs47.unlinkSync(verifyDiffPath(repoPath));
|
|
149407
150040
|
} catch (err) {
|
|
149408
150041
|
if (err.code !== "ENOENT")
|
|
149409
150042
|
throw err;
|
|
@@ -149458,8 +150091,8 @@ function generatedMarkerPath(repoRoot6) {
|
|
|
149458
150091
|
}
|
|
149459
150092
|
function stampGeneratedMarker(repoRoot6) {
|
|
149460
150093
|
const file = generatedMarkerPath(repoRoot6);
|
|
149461
|
-
|
|
149462
|
-
|
|
150094
|
+
fs48.mkdirSync(path54.dirname(file), { recursive: true });
|
|
150095
|
+
fs48.writeFileSync(file, JSON.stringify({ generatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n");
|
|
149463
150096
|
}
|
|
149464
150097
|
function resolveConsolidateModels(repoRoot6) {
|
|
149465
150098
|
return {
|
|
@@ -149728,7 +150361,7 @@ async function verifyInProcess(repoRoot6, options = {}) {
|
|
|
149728
150361
|
const startedAt = Date.now();
|
|
149729
150362
|
const contractsDir = options.contractsDir ?? path54.join(repoRoot6, ".truecourse", "contracts");
|
|
149730
150363
|
const codeDir = options.codeDir ?? autodetectCodeDir(repoRoot6);
|
|
149731
|
-
if (!
|
|
150364
|
+
if (!fs48.existsSync(contractsDir)) {
|
|
149732
150365
|
const err = new Error(`Contracts directory not found at ${contractsDir}. Run \`truecourse contracts generate\` first.`);
|
|
149733
150366
|
tracker?.error("load", err.message);
|
|
149734
150367
|
throw err;
|
|
@@ -149736,7 +150369,16 @@ async function verifyInProcess(repoRoot6, options = {}) {
|
|
|
149736
150369
|
tracker?.start("load");
|
|
149737
150370
|
let result;
|
|
149738
150371
|
try {
|
|
149739
|
-
result = await runWithStash(repoRoot6,
|
|
150372
|
+
result = await runWithStash(repoRoot6, {
|
|
150373
|
+
skipStash: options.skipStash ?? false,
|
|
150374
|
+
message: "truecourse-verify-stash",
|
|
150375
|
+
onStashStart: () => tracker?.detail?.("load", "Stashing pending changes..."),
|
|
150376
|
+
onRestoreStart: () => tracker?.detail?.("load", "Restoring pending changes..."),
|
|
150377
|
+
onRestoreError: (e) => (
|
|
150378
|
+
// eslint-disable-next-line no-console
|
|
150379
|
+
console.error(`[Verify] Failed to restore stashed changes. Run "git stash pop" manually. ${e.message}`)
|
|
150380
|
+
)
|
|
150381
|
+
}, () => verify({ contractsDir, codeDir }));
|
|
149740
150382
|
} catch (e) {
|
|
149741
150383
|
tracker?.error("load", e.message);
|
|
149742
150384
|
throw e;
|
|
@@ -149786,7 +150428,7 @@ async function verifyInProcess(repoRoot6, options = {}) {
|
|
|
149786
150428
|
bySeverity: summary.bySeverity
|
|
149787
150429
|
});
|
|
149788
150430
|
deleteVerifyDiff(repoRoot6);
|
|
149789
|
-
|
|
150431
|
+
fs48.rmSync(legacyVerifyStatePath(repoRoot6), { force: true });
|
|
149790
150432
|
const state = {
|
|
149791
150433
|
verifiedAt,
|
|
149792
150434
|
contractsDir,
|
|
@@ -149819,37 +150461,6 @@ async function gitMeta(repoRoot6) {
|
|
|
149819
150461
|
return { branch: null, commitHash: null };
|
|
149820
150462
|
}
|
|
149821
150463
|
}
|
|
149822
|
-
async function runWithStash(repoRoot6, skipStash, tracker, fn) {
|
|
149823
|
-
let didStash = false;
|
|
149824
|
-
let stashGit;
|
|
149825
|
-
if (!skipStash) {
|
|
149826
|
-
try {
|
|
149827
|
-
stashGit = await getGit(repoRoot6);
|
|
149828
|
-
const status = await stashGit.status();
|
|
149829
|
-
if (!status.isClean()) {
|
|
149830
|
-
const gitRoot = (await stashGit.revparse(["--show-toplevel"])).trim();
|
|
149831
|
-
if (path54.resolve(repoRoot6) === path54.resolve(gitRoot)) {
|
|
149832
|
-
tracker?.detail?.("load", "Stashing pending changes...");
|
|
149833
|
-
const res = await stashGit.stash(["push", "--include-untracked", "-m", "truecourse-verify-stash"]);
|
|
149834
|
-
didStash = !res.includes("No local changes");
|
|
149835
|
-
}
|
|
149836
|
-
}
|
|
149837
|
-
} catch {
|
|
149838
|
-
}
|
|
149839
|
-
}
|
|
149840
|
-
try {
|
|
149841
|
-
return await fn();
|
|
149842
|
-
} finally {
|
|
149843
|
-
if (didStash && stashGit) {
|
|
149844
|
-
tracker?.detail?.("load", "Restoring pending changes...");
|
|
149845
|
-
try {
|
|
149846
|
-
await stashGit.stash(["pop"]);
|
|
149847
|
-
} catch (e) {
|
|
149848
|
-
console.error(`[Verify] Failed to restore stashed changes. Run "git stash pop" manually. ${e.message}`);
|
|
149849
|
-
}
|
|
149850
|
-
}
|
|
149851
|
-
}
|
|
149852
|
-
}
|
|
149853
150464
|
async function gitChangedFiles(repoRoot6) {
|
|
149854
150465
|
const out = [];
|
|
149855
150466
|
try {
|
|
@@ -149886,7 +150497,7 @@ async function verifyDiffInProcess(repoRoot6, options = {}) {
|
|
|
149886
150497
|
tracker?.error("load", err.message);
|
|
149887
150498
|
throw err;
|
|
149888
150499
|
}
|
|
149889
|
-
if (!
|
|
150500
|
+
if (!fs48.existsSync(contractsDir)) {
|
|
149890
150501
|
const err = new Error(`Contracts directory not found at ${contractsDir}. Run \`truecourse contracts generate\` first.`);
|
|
149891
150502
|
tracker?.error("load", err.message);
|
|
149892
150503
|
throw err;
|
|
@@ -149965,7 +150576,7 @@ async function inferInProcess(repoRoot6, options = {}) {
|
|
|
149965
150576
|
}
|
|
149966
150577
|
function autodetectCodeDir(repoRoot6) {
|
|
149967
150578
|
const codeSubdir = path54.join(repoRoot6, "code");
|
|
149968
|
-
if (
|
|
150579
|
+
if (fs48.existsSync(codeSubdir) && fs48.statSync(codeSubdir).isDirectory()) {
|
|
149969
150580
|
return codeSubdir;
|
|
149970
150581
|
}
|
|
149971
150582
|
return repoRoot6;
|
|
@@ -150089,6 +150700,7 @@ async function runContractsGenerate(options = {}) {
|
|
|
150089
150700
|
gt("Aborted.");
|
|
150090
150701
|
process.exit(1);
|
|
150091
150702
|
}
|
|
150703
|
+
await preflightClaudeOrExit();
|
|
150092
150704
|
const concurrency = defaultConcurrency2();
|
|
150093
150705
|
const extractModel = resolveModel("contract.extract", void 0, repoRoot6);
|
|
150094
150706
|
const repairModel = resolveModel("contract.repair", void 0, repoRoot6);
|
|
@@ -150151,11 +150763,27 @@ async function runContractsGenerate(options = {}) {
|
|
|
150151
150763
|
if (f2.run?.error) console.log(` \u2192 ${f2.run.error}`);
|
|
150152
150764
|
}
|
|
150153
150765
|
}
|
|
150154
|
-
|
|
150155
|
-
|
|
150156
|
-
|
|
150157
|
-
|
|
150766
|
+
const hardIssues = result.validationIssues.filter((i) => i.severity === "hard");
|
|
150767
|
+
const softIssues = result.validationIssues.filter((i) => i.severity === "soft");
|
|
150768
|
+
if (softIssues.length > 0) {
|
|
150769
|
+
const counts = /* @__PURE__ */ new Map();
|
|
150770
|
+
for (const issue of softIssues) {
|
|
150771
|
+
const line = `${issue.artifactKey}: ${issue.message}`;
|
|
150772
|
+
counts.set(line, (counts.get(line) ?? 0) + 1);
|
|
150158
150773
|
}
|
|
150774
|
+
O2.warn(
|
|
150775
|
+
`${counts.size} unresolved cross-reference${counts.size === 1 ? "" : "s"} (non-blocking \u2014 the referenced artifact wasn't generated; \`truecourse verify\` will flag any real drift):`
|
|
150776
|
+
);
|
|
150777
|
+
for (const [line, n] of counts) console.log(` ${line}${n > 1 ? ` (\xD7${n})` : ""}`);
|
|
150778
|
+
}
|
|
150779
|
+
if (hardIssues.length > 0) {
|
|
150780
|
+
O2.error(
|
|
150781
|
+
`${hardIssues.length} artifact${hardIssues.length === 1 ? " was" : "s were"} dropped (invalid \`.tc\` \u2014 parse error or duplicate identity):`
|
|
150782
|
+
);
|
|
150783
|
+
for (const issue of hardIssues) console.log(` ${issue.artifactKey}: ${issue.message}`);
|
|
150784
|
+
}
|
|
150785
|
+
const produced = options.diff ? result.write.proposed.length : result.write.written.length;
|
|
150786
|
+
if (produced === 0 && result.validationIssues.length > 0) {
|
|
150159
150787
|
gt("No contracts were written. Edit the spec or re-run after fixing.");
|
|
150160
150788
|
process.exit(1);
|
|
150161
150789
|
}
|
|
@@ -150197,7 +150825,7 @@ async function runContractsGenerate(options = {}) {
|
|
|
150197
150825
|
async function runContractsList(options = {}) {
|
|
150198
150826
|
const repoRoot6 = options.cwd ?? process.cwd();
|
|
150199
150827
|
const contractsDir = path55.join(repoRoot6, ".truecourse", "contracts");
|
|
150200
|
-
if (!
|
|
150828
|
+
if (!fs49.existsSync(contractsDir)) {
|
|
150201
150829
|
O2.info("No contracts found. Run `truecourse contracts generate` first.");
|
|
150202
150830
|
return;
|
|
150203
150831
|
}
|
|
@@ -150205,12 +150833,12 @@ async function runContractsList(options = {}) {
|
|
|
150205
150833
|
const fileNodes = [];
|
|
150206
150834
|
let parseErrors = 0;
|
|
150207
150835
|
const visit = (dir) => {
|
|
150208
|
-
for (const entry of
|
|
150836
|
+
for (const entry of fs49.readdirSync(dir, { withFileTypes: true })) {
|
|
150209
150837
|
const full = path55.join(dir, entry.name);
|
|
150210
150838
|
if (entry.isDirectory()) visit(full);
|
|
150211
150839
|
else if (entry.isFile() && entry.name.endsWith(".tc")) {
|
|
150212
150840
|
try {
|
|
150213
|
-
fileNodes.push(parser4.parseFile(full,
|
|
150841
|
+
fileNodes.push(parser4.parseFile(full, fs49.readFileSync(full, "utf-8")));
|
|
150214
150842
|
} catch {
|
|
150215
150843
|
parseErrors += 1;
|
|
150216
150844
|
}
|
|
@@ -150250,7 +150878,7 @@ async function runContractsList(options = {}) {
|
|
|
150250
150878
|
async function runContractsValidate(options = {}) {
|
|
150251
150879
|
const repoRoot6 = options.cwd ?? process.cwd();
|
|
150252
150880
|
const contractsDir = path55.join(repoRoot6, ".truecourse", "contracts");
|
|
150253
|
-
if (!
|
|
150881
|
+
if (!fs49.existsSync(contractsDir)) {
|
|
150254
150882
|
O2.error("No .truecourse/contracts/ directory found.");
|
|
150255
150883
|
process.exit(1);
|
|
150256
150884
|
}
|
|
@@ -150258,12 +150886,12 @@ async function runContractsValidate(options = {}) {
|
|
|
150258
150886
|
const fileNodes = [];
|
|
150259
150887
|
const issues = [];
|
|
150260
150888
|
const visit = (dir) => {
|
|
150261
|
-
for (const entry of
|
|
150889
|
+
for (const entry of fs49.readdirSync(dir, { withFileTypes: true })) {
|
|
150262
150890
|
const full = path55.join(dir, entry.name);
|
|
150263
150891
|
if (entry.isDirectory()) visit(full);
|
|
150264
150892
|
else if (entry.isFile() && entry.name.endsWith(".tc")) {
|
|
150265
150893
|
try {
|
|
150266
|
-
fileNodes.push(parser4.parseFile(full,
|
|
150894
|
+
fileNodes.push(parser4.parseFile(full, fs49.readFileSync(full, "utf-8")));
|
|
150267
150895
|
} catch (e) {
|
|
150268
150896
|
issues.push(`${path55.relative(repoRoot6, full)}: parse error: ${e instanceof Error ? e.message : e}`);
|
|
150269
150897
|
}
|
|
@@ -150375,42 +151003,88 @@ async function runSpecScan(opts = {}) {
|
|
|
150375
151003
|
const root = repoRoot(opts);
|
|
150376
151004
|
mt("Spec scan");
|
|
150377
151005
|
await requireGitRepo(root);
|
|
151006
|
+
await preflightClaudeOrExit();
|
|
150378
151007
|
const { renderer, tracker } = withTracker(SCAN_STEPS);
|
|
150379
|
-
|
|
150380
|
-
|
|
150381
|
-
|
|
150382
|
-
|
|
150383
|
-
|
|
150384
|
-
|
|
150385
|
-
O2.step(`claims ${extract4.claims.length}`);
|
|
150386
|
-
O2.step(`resolved ${merge2.resolvedClaims.length}`);
|
|
150387
|
-
O2.step(`decided ${merge2.decidedConflicts.length}`);
|
|
150388
|
-
O2.step(`open ${merge2.openConflicts.length}`);
|
|
150389
|
-
if (merge2.openConflicts.length > 0) {
|
|
150390
|
-
O2.message("");
|
|
150391
|
-
O2.message("Open conflicts:");
|
|
150392
|
-
for (const c2 of merge2.openConflicts.slice(0, 10)) {
|
|
150393
|
-
O2.message(` \u2022 ${c2.subject} (${c2.candidates.length} candidates, default: ${c2.candidates[c2.defaultPick].claim.provenance.file})`);
|
|
150394
|
-
O2.message(` id: ${c2.id}`);
|
|
150395
|
-
}
|
|
150396
|
-
if (merge2.openConflicts.length > 10) {
|
|
150397
|
-
O2.message(` \u2026 (+${merge2.openConflicts.length - 10} more \u2014 run \`truecourse spec conflicts list\`)`);
|
|
150398
|
-
}
|
|
150399
|
-
O2.message("");
|
|
150400
|
-
O2.message("Resolve them:");
|
|
150401
|
-
O2.message(" \u2022 dashboard: truecourse dashboard (Spec tab)");
|
|
150402
|
-
O2.message(" \u2022 per conflict: truecourse spec conflicts show <id>");
|
|
150403
|
-
O2.message(" truecourse spec conflicts pick <id> <candidateIndex>");
|
|
150404
|
-
O2.message(' truecourse spec conflicts custom <id> --text "\u2026"');
|
|
150405
|
-
O2.message(" \u2022 accept defaults: truecourse spec resolve --all-defaults");
|
|
150406
|
-
}
|
|
150407
|
-
gt(
|
|
150408
|
-
merge2.openConflicts.length === 0 ? "No open conflicts \u2014 run `truecourse contracts generate`." : `${merge2.openConflicts.length} open.`
|
|
150409
|
-
);
|
|
150410
|
-
} catch (e) {
|
|
151008
|
+
const { consolidate: consolidate2 } = await scanInProcess(root, {
|
|
151009
|
+
tracker,
|
|
151010
|
+
source: "cli",
|
|
151011
|
+
llm: opts.llm,
|
|
151012
|
+
io: opts.io
|
|
151013
|
+
}).catch((e) => {
|
|
150411
151014
|
renderer.dispose();
|
|
150412
151015
|
pt(`Failed: ${e.message}`);
|
|
151016
|
+
process.exit(1);
|
|
151017
|
+
});
|
|
151018
|
+
renderer.dispose();
|
|
151019
|
+
const { extract: extract4, merge: merge2 } = consolidate2;
|
|
151020
|
+
O2.step(`docs ${extract4.docsScanned}`);
|
|
151021
|
+
O2.step(`blocks ${extract4.blocksAttempted} (${extract4.failures.length} failures)`);
|
|
151022
|
+
O2.step(`claims ${extract4.claims.length}`);
|
|
151023
|
+
O2.step(`resolved ${merge2.resolvedClaims.length}`);
|
|
151024
|
+
O2.step(`decided ${merge2.decidedConflicts.length}`);
|
|
151025
|
+
O2.step(`open ${merge2.openConflicts.length}`);
|
|
151026
|
+
const failures = summarizeExtractionFailures(extract4);
|
|
151027
|
+
const outcome = decideScanOutcome({
|
|
151028
|
+
blocksAttempted: extract4.blocksAttempted,
|
|
151029
|
+
claims: extract4.claims.length,
|
|
151030
|
+
openConflicts: merge2.openConflicts.length,
|
|
151031
|
+
failures
|
|
151032
|
+
});
|
|
151033
|
+
if (failures.total > 0) {
|
|
151034
|
+
O2.message("");
|
|
151035
|
+
O2.warn(`${failures.total} block${failures.total === 1 ? "" : "s"} failed to extract:`);
|
|
151036
|
+
for (const s of failures.samples) {
|
|
151037
|
+
O2.message(` \u2022 ${oneLine(s.message)}${s.count > 1 ? ` (\xD7${s.count})` : ""}`);
|
|
151038
|
+
}
|
|
151039
|
+
}
|
|
151040
|
+
if (outcome.exitCode !== 0) {
|
|
151041
|
+
gt(outcome.outro);
|
|
151042
|
+
process.exit(outcome.exitCode);
|
|
151043
|
+
}
|
|
151044
|
+
if (merge2.openConflicts.length > 0) {
|
|
151045
|
+
O2.message("");
|
|
151046
|
+
O2.message("Open conflicts:");
|
|
151047
|
+
for (const c2 of merge2.openConflicts.slice(0, 10)) {
|
|
151048
|
+
O2.message(` \u2022 ${c2.subject} (${c2.candidates.length} candidates, default: ${c2.candidates[c2.defaultPick].claim.provenance.file})`);
|
|
151049
|
+
O2.message(` id: ${c2.id}`);
|
|
151050
|
+
}
|
|
151051
|
+
if (merge2.openConflicts.length > 10) {
|
|
151052
|
+
O2.message(` \u2026 (+${merge2.openConflicts.length - 10} more \u2014 run \`truecourse spec conflicts list\`)`);
|
|
151053
|
+
}
|
|
151054
|
+
O2.message("");
|
|
151055
|
+
O2.message("Resolve them:");
|
|
151056
|
+
O2.message(" \u2022 dashboard: truecourse dashboard (Spec tab)");
|
|
151057
|
+
O2.message(" \u2022 per conflict: truecourse spec conflicts show <id>");
|
|
151058
|
+
O2.message(" truecourse spec conflicts pick <id> <candidateIndex>");
|
|
151059
|
+
O2.message(' truecourse spec conflicts custom <id> --text "\u2026"');
|
|
151060
|
+
O2.message(" \u2022 accept defaults: truecourse spec resolve --all-defaults");
|
|
151061
|
+
}
|
|
151062
|
+
gt(outcome.outro);
|
|
151063
|
+
}
|
|
151064
|
+
function decideScanOutcome(input) {
|
|
151065
|
+
const { failures } = input;
|
|
151066
|
+
if (failures.allFailed) {
|
|
151067
|
+
return {
|
|
151068
|
+
exitCode: 1,
|
|
151069
|
+
outro: `Aborted \u2014 all ${input.blocksAttempted} blocks failed, no claims extracted.`
|
|
151070
|
+
};
|
|
151071
|
+
}
|
|
151072
|
+
const outro = input.openConflicts > 0 ? `${input.openConflicts} open.` : input.claims === 0 ? "No claims extracted \u2014 nothing to generate yet." : "No open conflicts \u2014 run `truecourse contracts generate`.";
|
|
151073
|
+
return { exitCode: 0, outro };
|
|
151074
|
+
}
|
|
151075
|
+
function summarizeExtractionFailures(result, opts = {}) {
|
|
151076
|
+
const { failures, blocksAttempted } = result;
|
|
151077
|
+
const sampleLimit = opts.sampleLimit ?? 3;
|
|
151078
|
+
const counts = /* @__PURE__ */ new Map();
|
|
151079
|
+
for (const f2 of failures) {
|
|
151080
|
+
counts.set(f2.error, (counts.get(f2.error) ?? 0) + 1);
|
|
150413
151081
|
}
|
|
151082
|
+
const samples = [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, sampleLimit).map(([message, count]) => ({ message, count }));
|
|
151083
|
+
return {
|
|
151084
|
+
total: failures.length,
|
|
151085
|
+
allFailed: blocksAttempted > 0 && failures.length === blocksAttempted,
|
|
151086
|
+
samples
|
|
151087
|
+
};
|
|
150414
151088
|
}
|
|
150415
151089
|
async function runSpecResolve(opts = {}) {
|
|
150416
151090
|
const root = repoRoot(opts);
|
|
@@ -150423,6 +151097,7 @@ async function runSpecResolve(opts = {}) {
|
|
|
150423
151097
|
}
|
|
150424
151098
|
mt("Spec resolve \u2014 accepting all defaults");
|
|
150425
151099
|
await requireGitRepo(root);
|
|
151100
|
+
await preflightClaudeOrExit();
|
|
150426
151101
|
const { renderer, tracker } = withTracker(RESOLVE_STEPS);
|
|
150427
151102
|
try {
|
|
150428
151103
|
const { additions } = await resolveAllDefaultsInProcess(root, { tracker, llm: opts.llm, io: opts.io });
|
|
@@ -150587,6 +151262,10 @@ function summarizeConflicts(label, conflicts2) {
|
|
|
150587
151262
|
function decisionsRelPath(root) {
|
|
150588
151263
|
return path56.join(root, ".truecourse", "specs", "decisions.json");
|
|
150589
151264
|
}
|
|
151265
|
+
function oneLine(s, max = 200) {
|
|
151266
|
+
const collapsed = s.replace(/\s+/g, " ").trim();
|
|
151267
|
+
return collapsed.length <= max ? collapsed : `${collapsed.slice(0, max - 1)}\u2026`;
|
|
151268
|
+
}
|
|
150590
151269
|
|
|
150591
151270
|
// tools/cli/src/commands/spec-conflicts.ts
|
|
150592
151271
|
init_dist4();
|
|
@@ -151003,7 +151682,7 @@ async function runConfigLlmShow(options = {}) {
|
|
|
151003
151682
|
|
|
151004
151683
|
// tools/cli/src/commands/hooks.ts
|
|
151005
151684
|
import { execSync as execSync4 } from "node:child_process";
|
|
151006
|
-
import
|
|
151685
|
+
import fs50 from "node:fs";
|
|
151007
151686
|
import path59 from "node:path";
|
|
151008
151687
|
|
|
151009
151688
|
// node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/dist/js-yaml.mjs
|
|
@@ -153665,11 +154344,11 @@ function findGitDir(from) {
|
|
|
153665
154344
|
let dir = from;
|
|
153666
154345
|
while (true) {
|
|
153667
154346
|
const gitPath = path59.join(dir, ".git");
|
|
153668
|
-
if (
|
|
153669
|
-
const stat =
|
|
154347
|
+
if (fs50.existsSync(gitPath)) {
|
|
154348
|
+
const stat = fs50.statSync(gitPath);
|
|
153670
154349
|
if (stat.isDirectory()) return gitPath;
|
|
153671
154350
|
if (stat.isFile()) {
|
|
153672
|
-
const content =
|
|
154351
|
+
const content = fs50.readFileSync(gitPath, "utf-8").trim();
|
|
153673
154352
|
const match4 = content.match(/^gitdir:\s*(.+)$/);
|
|
153674
154353
|
if (match4) return path59.resolve(dir, match4[1]);
|
|
153675
154354
|
}
|
|
@@ -153682,7 +154361,7 @@ function findGitDir(from) {
|
|
|
153682
154361
|
function findProjectRoot(from) {
|
|
153683
154362
|
let dir = from;
|
|
153684
154363
|
while (true) {
|
|
153685
|
-
if (
|
|
154364
|
+
if (fs50.existsSync(path59.join(dir, ".git"))) return dir;
|
|
153686
154365
|
const parent = path59.dirname(dir);
|
|
153687
154366
|
if (parent === dir) return null;
|
|
153688
154367
|
dir = parent;
|
|
@@ -153690,10 +154369,10 @@ function findProjectRoot(from) {
|
|
|
153690
154369
|
}
|
|
153691
154370
|
function loadConfig(projectRoot) {
|
|
153692
154371
|
const configPath = path59.join(projectRoot, ".truecourse", "hooks.yaml");
|
|
153693
|
-
if (!
|
|
154372
|
+
if (!fs50.existsSync(configPath)) return null;
|
|
153694
154373
|
let parsed;
|
|
153695
154374
|
try {
|
|
153696
|
-
const raw =
|
|
154375
|
+
const raw = fs50.readFileSync(configPath, "utf-8");
|
|
153697
154376
|
parsed = jsYaml.load(raw) || {};
|
|
153698
154377
|
} catch (err) {
|
|
153699
154378
|
console.error(`Error parsing ${configPath}: ${err.message}`);
|
|
@@ -153745,10 +154424,10 @@ async function runHooksInstall() {
|
|
|
153745
154424
|
console.log(INSTALL_WARNING);
|
|
153746
154425
|
}
|
|
153747
154426
|
const hooksDir = path59.join(gitDir, "hooks");
|
|
153748
|
-
|
|
154427
|
+
fs50.mkdirSync(hooksDir, { recursive: true });
|
|
153749
154428
|
const hookPath = path59.join(hooksDir, "pre-commit");
|
|
153750
|
-
if (
|
|
153751
|
-
const existing =
|
|
154429
|
+
if (fs50.existsSync(hookPath)) {
|
|
154430
|
+
const existing = fs50.readFileSync(hookPath, "utf-8");
|
|
153752
154431
|
if (!existing.includes(HOOK_IDENTIFIER)) {
|
|
153753
154432
|
console.error(
|
|
153754
154433
|
"Error: A pre-commit hook already exists and was not installed by TrueCourse."
|
|
@@ -153758,16 +154437,16 @@ async function runHooksInstall() {
|
|
|
153758
154437
|
process.exit(1);
|
|
153759
154438
|
}
|
|
153760
154439
|
}
|
|
153761
|
-
|
|
154440
|
+
fs50.writeFileSync(hookPath, HOOK_SCRIPT, { mode: 493 });
|
|
153762
154441
|
console.log("TrueCourse pre-commit hook installed.");
|
|
153763
154442
|
console.log(` ${hookPath}`);
|
|
153764
154443
|
const projectRoot = findProjectRoot(process.cwd());
|
|
153765
154444
|
if (projectRoot) {
|
|
153766
154445
|
const cfgDir = path59.join(projectRoot, ".truecourse");
|
|
153767
154446
|
const cfgPath = path59.join(cfgDir, "hooks.yaml");
|
|
153768
|
-
if (!
|
|
153769
|
-
|
|
153770
|
-
|
|
154447
|
+
if (!fs50.existsSync(cfgPath)) {
|
|
154448
|
+
fs50.mkdirSync(cfgDir, { recursive: true });
|
|
154449
|
+
fs50.writeFileSync(cfgPath, HOOKS_YAML_TEMPLATE);
|
|
153771
154450
|
console.log(` ${cfgPath} (starter config \u2014 edit to customize, commit to share with the team)`);
|
|
153772
154451
|
}
|
|
153773
154452
|
}
|
|
@@ -153779,16 +154458,16 @@ function runHooksUninstall() {
|
|
|
153779
154458
|
process.exit(1);
|
|
153780
154459
|
}
|
|
153781
154460
|
const hookPath = path59.join(gitDir, "hooks", "pre-commit");
|
|
153782
|
-
if (!
|
|
154461
|
+
if (!fs50.existsSync(hookPath)) {
|
|
153783
154462
|
console.log("No pre-commit hook installed.");
|
|
153784
154463
|
return;
|
|
153785
154464
|
}
|
|
153786
|
-
const content =
|
|
154465
|
+
const content = fs50.readFileSync(hookPath, "utf-8");
|
|
153787
154466
|
if (!content.includes(HOOK_IDENTIFIER)) {
|
|
153788
154467
|
console.error("Error: The pre-commit hook was not installed by TrueCourse. Leaving it in place.");
|
|
153789
154468
|
process.exit(1);
|
|
153790
154469
|
}
|
|
153791
|
-
|
|
154470
|
+
fs50.unlinkSync(hookPath);
|
|
153792
154471
|
console.log("TrueCourse pre-commit hook removed.");
|
|
153793
154472
|
}
|
|
153794
154473
|
function runHooksStatus() {
|
|
@@ -153798,7 +154477,7 @@ function runHooksStatus() {
|
|
|
153798
154477
|
process.exit(1);
|
|
153799
154478
|
}
|
|
153800
154479
|
const hookPath = path59.join(gitDir, "hooks", "pre-commit");
|
|
153801
|
-
const installed =
|
|
154480
|
+
const installed = fs50.existsSync(hookPath) && fs50.readFileSync(hookPath, "utf-8").includes(HOOK_IDENTIFIER);
|
|
153802
154481
|
if (installed) {
|
|
153803
154482
|
console.log("TrueCourse pre-commit hook: installed");
|
|
153804
154483
|
console.log(` ${hookPath}`);
|
|
@@ -153907,7 +154586,7 @@ async function runHooksRun() {
|
|
|
153907
154586
|
|
|
153908
154587
|
// tools/cli/src/index.ts
|
|
153909
154588
|
var program2 = new Command();
|
|
153910
|
-
program2.name("truecourse").version("0.6.
|
|
154589
|
+
program2.name("truecourse").version("0.6.6-next.0").description("TrueCourse CLI \u2014 analyze your repository and open the dashboard");
|
|
153911
154590
|
var dashboardCmd = program2.command("dashboard").description("Start the TrueCourse dashboard and open it in your browser").option("--reconfigure", "Re-prompt for console vs background service mode").option("--service", "Run as a background service (skips mode prompt)").option("--console", "Run in this terminal (skips mode prompt)").action(async (options) => {
|
|
153912
154591
|
if (options.service && options.console) {
|
|
153913
154592
|
console.error("error: --service and --console are mutually exclusive");
|