@codacy/verity-cli 0.28.1-experimental.a57c8d9 → 0.28.1-experimental.a72d2d9
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 +11 -1
- package/bin/verity.js +1742 -677
- package/data/skills/verity-analyze/SKILL.md +2 -1
- package/data/skills/verity-setup/SKILL.md +18 -13
- package/package.json +1 -1
package/bin/verity.js
CHANGED
|
@@ -10570,135 +10570,6 @@ function rotateIfNeeded(file) {
|
|
|
10570
10570
|
}
|
|
10571
10571
|
}
|
|
10572
10572
|
|
|
10573
|
-
// src/lib/api-client.ts
|
|
10574
|
-
function describeFetchError(err, url) {
|
|
10575
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
10576
|
-
const cause = err?.cause;
|
|
10577
|
-
const causeBits = [cause?.code, cause?.hostname].filter(Boolean).join(" ");
|
|
10578
|
-
const detail = causeBits || (cause?.message && cause.message !== message ? cause.message : "");
|
|
10579
|
-
return `${message}${detail ? ` (${detail})` : ""} \u2014 could not reach ${url}`;
|
|
10580
|
-
}
|
|
10581
|
-
async function apiRequest(options) {
|
|
10582
|
-
const {
|
|
10583
|
-
method,
|
|
10584
|
-
path,
|
|
10585
|
-
serviceUrl,
|
|
10586
|
-
token,
|
|
10587
|
-
body,
|
|
10588
|
-
verbose,
|
|
10589
|
-
timeout = 9e4,
|
|
10590
|
-
cmd = "unknown",
|
|
10591
|
-
retry = false,
|
|
10592
|
-
encodeBody = false,
|
|
10593
|
-
extraHeaders
|
|
10594
|
-
} = options;
|
|
10595
|
-
const url = `${serviceUrl}${path}`;
|
|
10596
|
-
const headers = {
|
|
10597
|
-
"Content-Type": "application/json"
|
|
10598
|
-
};
|
|
10599
|
-
if (token) {
|
|
10600
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
10601
|
-
}
|
|
10602
|
-
if (extraHeaders) {
|
|
10603
|
-
Object.assign(headers, extraHeaders);
|
|
10604
|
-
}
|
|
10605
|
-
const testMockScenario = process.env.VERITY_TEST_MOCK_SCENARIO;
|
|
10606
|
-
if (testMockScenario) {
|
|
10607
|
-
headers["X-Verity-Mock-Scenario"] = testMockScenario;
|
|
10608
|
-
}
|
|
10609
|
-
const testMockFailure = process.env.VERITY_TEST_MOCK_FAILURE;
|
|
10610
|
-
if (testMockFailure) {
|
|
10611
|
-
headers["X-Verity-Mock-Failure"] = testMockFailure;
|
|
10612
|
-
}
|
|
10613
|
-
printVerbose(`${method} ${url}`, verbose);
|
|
10614
|
-
let serializedBody;
|
|
10615
|
-
if (body !== void 0 && body !== null) {
|
|
10616
|
-
const innerJson = JSON.stringify(body);
|
|
10617
|
-
if (encodeBody) {
|
|
10618
|
-
const payload = Buffer.from(innerJson, "utf8").toString("base64");
|
|
10619
|
-
serializedBody = JSON.stringify({ encoding: "base64", payload });
|
|
10620
|
-
} else {
|
|
10621
|
-
serializedBody = innerJson;
|
|
10622
|
-
}
|
|
10623
|
-
printVerbose(`Body: ${serializedBody.slice(0, 500)}`, verbose);
|
|
10624
|
-
}
|
|
10625
|
-
const startedAt = Date.now();
|
|
10626
|
-
const bodyBytes = serializedBody ? Buffer.byteLength(serializedBody) : 0;
|
|
10627
|
-
const logBase = { cmd, method, url, body_bytes: bodyBytes, retry, encoded: encodeBody };
|
|
10628
|
-
let response;
|
|
10629
|
-
try {
|
|
10630
|
-
response = await fetch(url, {
|
|
10631
|
-
method,
|
|
10632
|
-
headers,
|
|
10633
|
-
body: serializedBody,
|
|
10634
|
-
signal: AbortSignal.timeout(timeout)
|
|
10635
|
-
});
|
|
10636
|
-
} catch (err) {
|
|
10637
|
-
const duration2 = Date.now() - startedAt;
|
|
10638
|
-
const isTimeout = err instanceof DOMException && err.name === "TimeoutError";
|
|
10639
|
-
const category = isTimeout ? "timeout" : "network";
|
|
10640
|
-
const error = isTimeout ? `Request timed out after ${timeout}ms (${url})` : `Network error: ${describeFetchError(err, url)}`;
|
|
10641
|
-
logHttpCall({ ...logBase, duration_ms: duration2, http_status: null, category, error });
|
|
10642
|
-
return { ok: false, error, category, http_status: null };
|
|
10643
|
-
}
|
|
10644
|
-
let data;
|
|
10645
|
-
try {
|
|
10646
|
-
data = response.status === 204 || response.headers.get("content-length") === "0" ? {} : await response.json();
|
|
10647
|
-
} catch {
|
|
10648
|
-
const duration2 = Date.now() - startedAt;
|
|
10649
|
-
const error = `Invalid JSON response (HTTP ${response.status})`;
|
|
10650
|
-
logHttpCall({
|
|
10651
|
-
...logBase,
|
|
10652
|
-
duration_ms: duration2,
|
|
10653
|
-
http_status: response.status,
|
|
10654
|
-
category: "invalid_json",
|
|
10655
|
-
error
|
|
10656
|
-
});
|
|
10657
|
-
return { ok: false, error, category: "invalid_json", http_status: response.status };
|
|
10658
|
-
}
|
|
10659
|
-
printVerbose(`Response ${response.status}: ${JSON.stringify(data).slice(0, 500)}`, verbose);
|
|
10660
|
-
const duration = Date.now() - startedAt;
|
|
10661
|
-
if (!response.ok) {
|
|
10662
|
-
const apiErr = data;
|
|
10663
|
-
const code = apiErr?.error?.code ?? "UNKNOWN";
|
|
10664
|
-
const message = apiErr?.error?.message ?? `HTTP ${response.status}`;
|
|
10665
|
-
const category = response.status >= 500 ? "http_5xx" : "http_4xx";
|
|
10666
|
-
const error = `${code}: ${message}`;
|
|
10667
|
-
logHttpCall({
|
|
10668
|
-
...logBase,
|
|
10669
|
-
duration_ms: duration,
|
|
10670
|
-
http_status: response.status,
|
|
10671
|
-
category,
|
|
10672
|
-
error
|
|
10673
|
-
});
|
|
10674
|
-
return { ok: false, error, category, http_status: response.status };
|
|
10675
|
-
}
|
|
10676
|
-
logHttpCall({
|
|
10677
|
-
...logBase,
|
|
10678
|
-
duration_ms: duration,
|
|
10679
|
-
http_status: response.status,
|
|
10680
|
-
category: "ok"
|
|
10681
|
-
});
|
|
10682
|
-
return { ok: true, data };
|
|
10683
|
-
}
|
|
10684
|
-
function analyzeRequest(options) {
|
|
10685
|
-
return apiRequest({
|
|
10686
|
-
method: "POST",
|
|
10687
|
-
path: "/analyze",
|
|
10688
|
-
serviceUrl: options.serviceUrl,
|
|
10689
|
-
token: options.token,
|
|
10690
|
-
body: options.body,
|
|
10691
|
-
timeout: options.timeout,
|
|
10692
|
-
cmd: options.cmd,
|
|
10693
|
-
verbose: options.verbose,
|
|
10694
|
-
retry: options.retry,
|
|
10695
|
-
encodeBody: true
|
|
10696
|
-
});
|
|
10697
|
-
}
|
|
10698
|
-
|
|
10699
|
-
// src/lib/service-url.ts
|
|
10700
|
-
var import_promises2 = require("node:fs/promises");
|
|
10701
|
-
|
|
10702
10573
|
// src/lib/credentials.ts
|
|
10703
10574
|
var import_promises = require("node:fs/promises");
|
|
10704
10575
|
var import_node_path2 = require("node:path");
|
|
@@ -10765,14 +10636,14 @@ async function readGlobalCredential(remote) {
|
|
|
10765
10636
|
const parsed = parseCredentialLine(line);
|
|
10766
10637
|
if (parsed && parsed.remote === key) last = parsed.rec;
|
|
10767
10638
|
}
|
|
10768
|
-
if (last) return last;
|
|
10639
|
+
if (last) return { ...last, keyed: true };
|
|
10769
10640
|
}
|
|
10770
10641
|
let plain = null;
|
|
10771
10642
|
for (const line of lines) {
|
|
10772
10643
|
const parsed = parseCredentialLine(line);
|
|
10773
10644
|
if (parsed && parsed.remote === "") plain = parsed.rec;
|
|
10774
10645
|
}
|
|
10775
|
-
return plain;
|
|
10646
|
+
return plain ? { ...plain, keyed: false } : null;
|
|
10776
10647
|
}
|
|
10777
10648
|
async function upsertGlobalCredential(remote, rec) {
|
|
10778
10649
|
const path = globalCredentialsPath();
|
|
@@ -10805,6 +10676,67 @@ async function upsertGlobalCredential(remote, rec) {
|
|
|
10805
10676
|
await (0, import_promises.chmod)(path, 384).catch(() => {
|
|
10806
10677
|
});
|
|
10807
10678
|
}
|
|
10679
|
+
async function removeSupersededUserCredentials(loginServiceUrl, loginUserId) {
|
|
10680
|
+
if (loginUserId == null) return 0;
|
|
10681
|
+
const path = globalCredentialsPath();
|
|
10682
|
+
let content;
|
|
10683
|
+
try {
|
|
10684
|
+
content = await (0, import_promises.readFile)(path, "utf-8");
|
|
10685
|
+
} catch {
|
|
10686
|
+
return 0;
|
|
10687
|
+
}
|
|
10688
|
+
const kept = [];
|
|
10689
|
+
let removed = 0;
|
|
10690
|
+
for (const line of content.split("\n")) {
|
|
10691
|
+
const parsed = parseCredentialLine(line);
|
|
10692
|
+
const superseded = parsed !== null && parsed.remote !== "" && parsed.rec.userId === loginUserId && (!parsed.rec.serviceUrl || parsed.rec.serviceUrl === loginServiceUrl);
|
|
10693
|
+
if (superseded) {
|
|
10694
|
+
removed++;
|
|
10695
|
+
continue;
|
|
10696
|
+
}
|
|
10697
|
+
kept.push(line);
|
|
10698
|
+
}
|
|
10699
|
+
if (removed === 0) return 0;
|
|
10700
|
+
while (kept.length && kept[kept.length - 1].trim() === "") kept.pop();
|
|
10701
|
+
try {
|
|
10702
|
+
await (0, import_promises.writeFile)(path, kept.join("\n") + "\n", { mode: 384 });
|
|
10703
|
+
await (0, import_promises.chmod)(path, 384).catch(() => {
|
|
10704
|
+
});
|
|
10705
|
+
} catch {
|
|
10706
|
+
return -1;
|
|
10707
|
+
}
|
|
10708
|
+
return removed;
|
|
10709
|
+
}
|
|
10710
|
+
async function removeGlobalCredential(remote) {
|
|
10711
|
+
const path = globalCredentialsPath();
|
|
10712
|
+
let content;
|
|
10713
|
+
try {
|
|
10714
|
+
content = await (0, import_promises.readFile)(path, "utf-8");
|
|
10715
|
+
} catch {
|
|
10716
|
+
return false;
|
|
10717
|
+
}
|
|
10718
|
+
const key = encodeRemoteKey(remote);
|
|
10719
|
+
const kept = [];
|
|
10720
|
+
let removed = false;
|
|
10721
|
+
for (const line of content.split("\n")) {
|
|
10722
|
+
const parsed = parseCredentialLine(line);
|
|
10723
|
+
if (parsed && parsed.remote === key) {
|
|
10724
|
+
removed = true;
|
|
10725
|
+
continue;
|
|
10726
|
+
}
|
|
10727
|
+
kept.push(line);
|
|
10728
|
+
}
|
|
10729
|
+
if (!removed) return false;
|
|
10730
|
+
while (kept.length && kept[kept.length - 1].trim() === "") kept.pop();
|
|
10731
|
+
try {
|
|
10732
|
+
await (0, import_promises.writeFile)(path, kept.length ? kept.join("\n") + "\n" : "", { mode: 384 });
|
|
10733
|
+
await (0, import_promises.chmod)(path, 384).catch(() => {
|
|
10734
|
+
});
|
|
10735
|
+
} catch {
|
|
10736
|
+
return false;
|
|
10737
|
+
}
|
|
10738
|
+
return true;
|
|
10739
|
+
}
|
|
10808
10740
|
function parseLocalCredentialFile(content) {
|
|
10809
10741
|
const tokenMatch = content.match(/token:\s*((?:gate_|verity_)[a-f0-9]+)/);
|
|
10810
10742
|
if (!tokenMatch) return null;
|
|
@@ -10854,10 +10786,378 @@ async function foldLegacyLocalCredential(remoteArg) {
|
|
|
10854
10786
|
}
|
|
10855
10787
|
await (0, import_promises.unlink)(projectPath(CREDENTIALS_FILE)).catch(() => {
|
|
10856
10788
|
});
|
|
10857
|
-
return true;
|
|
10789
|
+
return true;
|
|
10790
|
+
}
|
|
10791
|
+
|
|
10792
|
+
// src/lib/git.ts
|
|
10793
|
+
var import_node_child_process3 = require("node:child_process");
|
|
10794
|
+
var import_node_fs3 = require("node:fs");
|
|
10795
|
+
var import_node_path3 = require("node:path");
|
|
10796
|
+
function resolveFile(relpath) {
|
|
10797
|
+
return (0, import_node_fs3.existsSync)(relpath) ? relpath : null;
|
|
10798
|
+
}
|
|
10799
|
+
function execGit(cmd) {
|
|
10800
|
+
try {
|
|
10801
|
+
return (0, import_node_child_process3.execSync)(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
10802
|
+
} catch {
|
|
10803
|
+
return "";
|
|
10804
|
+
}
|
|
10805
|
+
}
|
|
10806
|
+
function splitLines(s) {
|
|
10807
|
+
return s.split("\n").filter((l) => l.length > 0);
|
|
10808
|
+
}
|
|
10809
|
+
var SHA_RE = /^[0-9a-f]{40}$/;
|
|
10810
|
+
function readBaselineSha() {
|
|
10811
|
+
if (!(0, import_node_fs3.existsSync)(BASELINE_SHA_FILE)) return null;
|
|
10812
|
+
let sha;
|
|
10813
|
+
try {
|
|
10814
|
+
sha = (0, import_node_fs3.readFileSync)(BASELINE_SHA_FILE, "utf-8").trim();
|
|
10815
|
+
} catch {
|
|
10816
|
+
return null;
|
|
10817
|
+
}
|
|
10818
|
+
if (!SHA_RE.test(sha)) return null;
|
|
10819
|
+
const reachable = execGit(`git cat-file -e ${sha}^{commit} 2>/dev/null && echo ok`) === "ok";
|
|
10820
|
+
if (!reachable) {
|
|
10821
|
+
try {
|
|
10822
|
+
(0, import_node_fs3.unlinkSync)(BASELINE_SHA_FILE);
|
|
10823
|
+
} catch {
|
|
10824
|
+
}
|
|
10825
|
+
return null;
|
|
10826
|
+
}
|
|
10827
|
+
return sha;
|
|
10828
|
+
}
|
|
10829
|
+
function writeBaselineSha(sha) {
|
|
10830
|
+
if (!SHA_RE.test(sha)) return;
|
|
10831
|
+
try {
|
|
10832
|
+
(0, import_node_fs3.mkdirSync)((0, import_node_path3.dirname)(BASELINE_SHA_FILE), { recursive: true });
|
|
10833
|
+
(0, import_node_fs3.writeFileSync)(BASELINE_SHA_FILE, sha);
|
|
10834
|
+
} catch {
|
|
10835
|
+
}
|
|
10836
|
+
}
|
|
10837
|
+
function getChangedFiles() {
|
|
10838
|
+
const sets = /* @__PURE__ */ new Set();
|
|
10839
|
+
let hasRecentCommitFiles = false;
|
|
10840
|
+
for (const f of splitLines(execGit("git diff --name-only HEAD"))) sets.add(f);
|
|
10841
|
+
for (const f of splitLines(execGit("git diff --name-only --cached"))) sets.add(f);
|
|
10842
|
+
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) sets.add(f);
|
|
10843
|
+
const baseline = readBaselineSha();
|
|
10844
|
+
if (baseline) {
|
|
10845
|
+
const committed = splitLines(execGit(`git diff --name-only ${baseline}..HEAD`));
|
|
10846
|
+
if (committed.length > 0) {
|
|
10847
|
+
hasRecentCommitFiles = true;
|
|
10848
|
+
for (const f of committed) sets.add(f);
|
|
10849
|
+
}
|
|
10850
|
+
} else {
|
|
10851
|
+
const headTimestamp = parseInt(execGit("git log -1 --format=%ct HEAD"), 10) || 0;
|
|
10852
|
+
const commitAge = Math.floor(Date.now() / 1e3) - headTimestamp;
|
|
10853
|
+
const hasUnstaged = splitLines(execGit("git diff --name-only HEAD")).length > 0;
|
|
10854
|
+
const hasStaged = splitLines(execGit("git diff --name-only --cached")).length > 0;
|
|
10855
|
+
if (commitAge < 120 && !hasUnstaged && !hasStaged) {
|
|
10856
|
+
const recentFiles = splitLines(execGit("git diff --name-only HEAD~1..HEAD"));
|
|
10857
|
+
if (recentFiles.length > 0) {
|
|
10858
|
+
hasRecentCommitFiles = true;
|
|
10859
|
+
for (const f of recentFiles) sets.add(f);
|
|
10860
|
+
}
|
|
10861
|
+
}
|
|
10862
|
+
}
|
|
10863
|
+
const filtered = Array.from(sets).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
10864
|
+
return { files: filtered, hasRecentCommitFiles };
|
|
10865
|
+
}
|
|
10866
|
+
function getStagedFiles() {
|
|
10867
|
+
return splitLines(execGit("git diff --cached --name-only")).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
10868
|
+
}
|
|
10869
|
+
function getDirtyFiles() {
|
|
10870
|
+
const set = /* @__PURE__ */ new Set();
|
|
10871
|
+
for (const f of splitLines(execGit("git diff --name-only HEAD"))) set.add(f);
|
|
10872
|
+
for (const f of splitLines(execGit("git diff --name-only --cached"))) set.add(f);
|
|
10873
|
+
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
|
|
10874
|
+
return Array.from(set).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
10875
|
+
}
|
|
10876
|
+
function showContentAtRef(ref, repoRelPath) {
|
|
10877
|
+
if (!ref || ref === "no-git") return null;
|
|
10878
|
+
const normalizedPath = repoRelPath.replace(/\\/g, "/");
|
|
10879
|
+
try {
|
|
10880
|
+
return (0, import_node_child_process3.execFileSync)("git", ["show", `${ref}:${normalizedPath}`], {
|
|
10881
|
+
encoding: "utf-8",
|
|
10882
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
10883
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
10884
|
+
});
|
|
10885
|
+
} catch {
|
|
10886
|
+
return null;
|
|
10887
|
+
}
|
|
10888
|
+
}
|
|
10889
|
+
function getPushRangeFiles() {
|
|
10890
|
+
const diff = (range) => splitLines(execGit(`git diff --name-only ${range}`)).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
10891
|
+
const resolvers = [
|
|
10892
|
+
() => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{push}") ? "@{push}..HEAD" : null,
|
|
10893
|
+
() => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{upstream}") ? "@{upstream}..HEAD" : null,
|
|
10894
|
+
() => {
|
|
10895
|
+
const branch = execGit("git rev-parse --abbrev-ref HEAD");
|
|
10896
|
+
return branch && branch !== "HEAD" && execGit(`git rev-parse --verify -q origin/${branch}`) ? `origin/${branch}..HEAD` : null;
|
|
10897
|
+
}
|
|
10898
|
+
];
|
|
10899
|
+
for (const resolve2 of resolvers) {
|
|
10900
|
+
const range = resolve2();
|
|
10901
|
+
if (range) return { files: diff(range), range };
|
|
10902
|
+
}
|
|
10903
|
+
const baseline = readBaselineSha();
|
|
10904
|
+
if (baseline) {
|
|
10905
|
+
const files = diff(`${baseline}..HEAD`);
|
|
10906
|
+
if (files.length > 0) return { files, range: `${baseline}..HEAD` };
|
|
10907
|
+
}
|
|
10908
|
+
const last = diff("HEAD~1..HEAD");
|
|
10909
|
+
return { files: last, range: last.length > 0 ? "HEAD~1..HEAD" : null };
|
|
10910
|
+
}
|
|
10911
|
+
function getPushRangeMessages() {
|
|
10912
|
+
const { range } = getPushRangeFiles();
|
|
10913
|
+
if (!range) return "";
|
|
10914
|
+
return execGit(`git log ${range} --format=%B%x00`).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
|
|
10915
|
+
}
|
|
10916
|
+
function filterAnalyzable(files) {
|
|
10917
|
+
return files.filter((f) => {
|
|
10918
|
+
const ext = (0, import_node_path3.extname)(f).slice(1);
|
|
10919
|
+
return ANALYZABLE_EXTENSIONS.has(ext);
|
|
10920
|
+
});
|
|
10921
|
+
}
|
|
10922
|
+
function filterReviewable(files) {
|
|
10923
|
+
return files.filter((f) => {
|
|
10924
|
+
const ext = (0, import_node_path3.extname)(f).slice(1);
|
|
10925
|
+
if (ANALYZABLE_EXTENSIONS.has(ext)) return false;
|
|
10926
|
+
if (REVIEWABLE_EXTENSIONS.has(ext)) return true;
|
|
10927
|
+
const basename4 = f.split("/").pop() ?? "";
|
|
10928
|
+
if (REVIEWABLE_FILENAMES.has(basename4)) return true;
|
|
10929
|
+
if (REVIEWABLE_PATH_PATTERNS.some((p) => p.test(f))) return true;
|
|
10930
|
+
return false;
|
|
10931
|
+
});
|
|
10932
|
+
}
|
|
10933
|
+
function filterSecurity(files) {
|
|
10934
|
+
return files.filter(
|
|
10935
|
+
(f) => SECURITY_PATTERNS.some((p) => p.test(f))
|
|
10936
|
+
);
|
|
10937
|
+
}
|
|
10938
|
+
function getCurrentCommit() {
|
|
10939
|
+
return execGit("git rev-parse HEAD") || "no-git";
|
|
10940
|
+
}
|
|
10941
|
+
function getCurrentBranch() {
|
|
10942
|
+
const b = execGit("git rev-parse --abbrev-ref HEAD");
|
|
10943
|
+
return !b || b === "HEAD" ? null : b;
|
|
10944
|
+
}
|
|
10945
|
+
function commitResolves(sha) {
|
|
10946
|
+
if (!sha) return false;
|
|
10947
|
+
try {
|
|
10948
|
+
(0, import_node_child_process3.execSync)(`git merge-base --is-ancestor ${JSON.stringify(sha)} HEAD`, {
|
|
10949
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
10950
|
+
});
|
|
10951
|
+
return true;
|
|
10952
|
+
} catch {
|
|
10953
|
+
return false;
|
|
10954
|
+
}
|
|
10955
|
+
}
|
|
10956
|
+
function commitsSincePaths(sha, paths) {
|
|
10957
|
+
if (!sha) return null;
|
|
10958
|
+
try {
|
|
10959
|
+
const scope = paths.length > 0 ? ` -- ${paths.slice(0, 50).map((p) => JSON.stringify(p)).join(" ")}` : "";
|
|
10960
|
+
const out = (0, import_node_child_process3.execSync)(`git rev-list --count ${JSON.stringify(sha)}..HEAD${scope}`, {
|
|
10961
|
+
encoding: "utf-8",
|
|
10962
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
10963
|
+
}).trim();
|
|
10964
|
+
const n = Number.parseInt(out, 10);
|
|
10965
|
+
return Number.isFinite(n) ? n : null;
|
|
10966
|
+
} catch {
|
|
10967
|
+
return null;
|
|
10968
|
+
}
|
|
10969
|
+
}
|
|
10970
|
+
function detectProvider(host) {
|
|
10971
|
+
const h = host.toLowerCase();
|
|
10972
|
+
if (h.includes("github")) return "github";
|
|
10973
|
+
if (h.includes("gitlab")) return "gitlab";
|
|
10974
|
+
if (h.includes("bitbucket")) return "bitbucket";
|
|
10975
|
+
return "unknown";
|
|
10976
|
+
}
|
|
10977
|
+
function parseRemote(raw) {
|
|
10978
|
+
if (!raw || typeof raw !== "string") return null;
|
|
10979
|
+
let s = raw.trim();
|
|
10980
|
+
if (!s) return null;
|
|
10981
|
+
let host = "";
|
|
10982
|
+
let path = "";
|
|
10983
|
+
const scp = s.match(/^[^/@]+@([^:/]+):(.+)$/);
|
|
10984
|
+
if (scp) {
|
|
10985
|
+
host = scp[1];
|
|
10986
|
+
path = scp[2];
|
|
10987
|
+
} else {
|
|
10988
|
+
s = s.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//, "");
|
|
10989
|
+
s = s.replace(/^[^/@]+@/, "");
|
|
10990
|
+
const slash = s.indexOf("/");
|
|
10991
|
+
if (slash === -1) return null;
|
|
10992
|
+
host = s.slice(0, slash);
|
|
10993
|
+
path = s.slice(slash + 1);
|
|
10994
|
+
}
|
|
10995
|
+
host = host.toLowerCase().trim();
|
|
10996
|
+
path = path.replace(/\/+$/, "").replace(/\.git$/, "");
|
|
10997
|
+
if (!host || !path) return null;
|
|
10998
|
+
const segments = path.split("/").filter(Boolean);
|
|
10999
|
+
if (segments.length < 2) return null;
|
|
11000
|
+
const owner = segments[0];
|
|
11001
|
+
const repo = segments[segments.length - 1];
|
|
11002
|
+
if (!owner || !repo) return null;
|
|
11003
|
+
return {
|
|
11004
|
+
host,
|
|
11005
|
+
owner,
|
|
11006
|
+
repo,
|
|
11007
|
+
provider: detectProvider(host),
|
|
11008
|
+
orgUrl: `https://${host}/${owner}`,
|
|
11009
|
+
orgName: owner
|
|
11010
|
+
};
|
|
11011
|
+
}
|
|
11012
|
+
function listTrackedFiles() {
|
|
11013
|
+
const set = /* @__PURE__ */ new Set();
|
|
11014
|
+
for (const f of splitLines(execGit("git ls-files"))) set.add(f);
|
|
11015
|
+
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
|
|
11016
|
+
return Array.from(set);
|
|
11017
|
+
}
|
|
11018
|
+
function sanitizeRemote(remote) {
|
|
11019
|
+
return remote.replace(/^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)[^/]*@/, "$1");
|
|
11020
|
+
}
|
|
11021
|
+
|
|
11022
|
+
// src/lib/api-client.ts
|
|
11023
|
+
var cachedRemote = null;
|
|
11024
|
+
function requestRemote() {
|
|
11025
|
+
const override = process.env.VERITY_REMOTE_OVERRIDE;
|
|
11026
|
+
if (override) return sanitizeRemote(override.trim());
|
|
11027
|
+
if (cachedRemote === null) cachedRemote = sanitizeRemote(currentRemote());
|
|
11028
|
+
return cachedRemote;
|
|
11029
|
+
}
|
|
11030
|
+
function describeFetchError(err, url) {
|
|
11031
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11032
|
+
const cause = err?.cause;
|
|
11033
|
+
const causeBits = [cause?.code, cause?.hostname].filter(Boolean).join(" ");
|
|
11034
|
+
const detail = causeBits || (cause?.message && cause.message !== message ? cause.message : "");
|
|
11035
|
+
return `${message}${detail ? ` (${detail})` : ""} \u2014 could not reach ${url}`;
|
|
11036
|
+
}
|
|
11037
|
+
async function apiRequest(options) {
|
|
11038
|
+
const {
|
|
11039
|
+
method,
|
|
11040
|
+
path,
|
|
11041
|
+
serviceUrl,
|
|
11042
|
+
token,
|
|
11043
|
+
body,
|
|
11044
|
+
verbose,
|
|
11045
|
+
timeout = 9e4,
|
|
11046
|
+
cmd = "unknown",
|
|
11047
|
+
retry = false,
|
|
11048
|
+
encodeBody = false,
|
|
11049
|
+
extraHeaders
|
|
11050
|
+
} = options;
|
|
11051
|
+
const url = `${serviceUrl}${path}`;
|
|
11052
|
+
const headers = {
|
|
11053
|
+
"Content-Type": "application/json"
|
|
11054
|
+
};
|
|
11055
|
+
if (token) {
|
|
11056
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
11057
|
+
}
|
|
11058
|
+
const remote = requestRemote();
|
|
11059
|
+
if (remote) {
|
|
11060
|
+
headers["X-Verity-Remote"] = remote;
|
|
11061
|
+
}
|
|
11062
|
+
if (extraHeaders) {
|
|
11063
|
+
Object.assign(headers, extraHeaders);
|
|
11064
|
+
}
|
|
11065
|
+
const testMockScenario = process.env.VERITY_TEST_MOCK_SCENARIO;
|
|
11066
|
+
if (testMockScenario) {
|
|
11067
|
+
headers["X-Verity-Mock-Scenario"] = testMockScenario;
|
|
11068
|
+
}
|
|
11069
|
+
const testMockFailure = process.env.VERITY_TEST_MOCK_FAILURE;
|
|
11070
|
+
if (testMockFailure) {
|
|
11071
|
+
headers["X-Verity-Mock-Failure"] = testMockFailure;
|
|
11072
|
+
}
|
|
11073
|
+
printVerbose(`${method} ${url}`, verbose);
|
|
11074
|
+
let serializedBody;
|
|
11075
|
+
if (body !== void 0 && body !== null) {
|
|
11076
|
+
const innerJson = JSON.stringify(body);
|
|
11077
|
+
if (encodeBody) {
|
|
11078
|
+
const payload = Buffer.from(innerJson, "utf8").toString("base64");
|
|
11079
|
+
serializedBody = JSON.stringify({ encoding: "base64", payload });
|
|
11080
|
+
} else {
|
|
11081
|
+
serializedBody = innerJson;
|
|
11082
|
+
}
|
|
11083
|
+
printVerbose(`Body: ${serializedBody.slice(0, 500)}`, verbose);
|
|
11084
|
+
}
|
|
11085
|
+
const startedAt = Date.now();
|
|
11086
|
+
const bodyBytes = serializedBody ? Buffer.byteLength(serializedBody) : 0;
|
|
11087
|
+
const logBase = { cmd, method, url, body_bytes: bodyBytes, retry, encoded: encodeBody };
|
|
11088
|
+
let response;
|
|
11089
|
+
try {
|
|
11090
|
+
response = await fetch(url, {
|
|
11091
|
+
method,
|
|
11092
|
+
headers,
|
|
11093
|
+
body: serializedBody,
|
|
11094
|
+
signal: AbortSignal.timeout(timeout)
|
|
11095
|
+
});
|
|
11096
|
+
} catch (err) {
|
|
11097
|
+
const duration2 = Date.now() - startedAt;
|
|
11098
|
+
const isTimeout = err instanceof DOMException && err.name === "TimeoutError";
|
|
11099
|
+
const category = isTimeout ? "timeout" : "network";
|
|
11100
|
+
const error = isTimeout ? `Request timed out after ${timeout}ms (${url})` : `Network error: ${describeFetchError(err, url)}`;
|
|
11101
|
+
logHttpCall({ ...logBase, duration_ms: duration2, http_status: null, category, error });
|
|
11102
|
+
return { ok: false, error, category, http_status: null };
|
|
11103
|
+
}
|
|
11104
|
+
let data;
|
|
11105
|
+
try {
|
|
11106
|
+
data = response.status === 204 || response.headers.get("content-length") === "0" ? {} : await response.json();
|
|
11107
|
+
} catch {
|
|
11108
|
+
const duration2 = Date.now() - startedAt;
|
|
11109
|
+
const error = `Invalid JSON response (HTTP ${response.status})`;
|
|
11110
|
+
logHttpCall({
|
|
11111
|
+
...logBase,
|
|
11112
|
+
duration_ms: duration2,
|
|
11113
|
+
http_status: response.status,
|
|
11114
|
+
category: "invalid_json",
|
|
11115
|
+
error
|
|
11116
|
+
});
|
|
11117
|
+
return { ok: false, error, category: "invalid_json", http_status: response.status };
|
|
11118
|
+
}
|
|
11119
|
+
printVerbose(`Response ${response.status}: ${JSON.stringify(data).slice(0, 500)}`, verbose);
|
|
11120
|
+
const duration = Date.now() - startedAt;
|
|
11121
|
+
if (!response.ok) {
|
|
11122
|
+
const apiErr = data;
|
|
11123
|
+
const code = apiErr?.error?.code ?? "UNKNOWN";
|
|
11124
|
+
const message = apiErr?.error?.message ?? `HTTP ${response.status}`;
|
|
11125
|
+
const category = response.status >= 500 ? "http_5xx" : "http_4xx";
|
|
11126
|
+
const error = `${code}: ${message}`;
|
|
11127
|
+
logHttpCall({
|
|
11128
|
+
...logBase,
|
|
11129
|
+
duration_ms: duration,
|
|
11130
|
+
http_status: response.status,
|
|
11131
|
+
category,
|
|
11132
|
+
error
|
|
11133
|
+
});
|
|
11134
|
+
return { ok: false, error, category, http_status: response.status };
|
|
11135
|
+
}
|
|
11136
|
+
logHttpCall({
|
|
11137
|
+
...logBase,
|
|
11138
|
+
duration_ms: duration,
|
|
11139
|
+
http_status: response.status,
|
|
11140
|
+
category: "ok"
|
|
11141
|
+
});
|
|
11142
|
+
return { ok: true, data };
|
|
11143
|
+
}
|
|
11144
|
+
function analyzeRequest(options) {
|
|
11145
|
+
return apiRequest({
|
|
11146
|
+
method: "POST",
|
|
11147
|
+
path: "/analyze",
|
|
11148
|
+
serviceUrl: options.serviceUrl,
|
|
11149
|
+
token: options.token,
|
|
11150
|
+
body: options.body,
|
|
11151
|
+
timeout: options.timeout,
|
|
11152
|
+
cmd: options.cmd,
|
|
11153
|
+
verbose: options.verbose,
|
|
11154
|
+
retry: options.retry,
|
|
11155
|
+
encodeBody: true
|
|
11156
|
+
});
|
|
10858
11157
|
}
|
|
10859
11158
|
|
|
10860
11159
|
// src/lib/service-url.ts
|
|
11160
|
+
var import_promises2 = require("node:fs/promises");
|
|
10861
11161
|
async function serviceUrlFromCredentials() {
|
|
10862
11162
|
const rec = await readGlobalCredential(currentRemote());
|
|
10863
11163
|
return rec?.serviceUrl ?? null;
|
|
@@ -10918,7 +11218,13 @@ async function resolveToken(flagToken) {
|
|
|
10918
11218
|
if (rec) {
|
|
10919
11219
|
return {
|
|
10920
11220
|
ok: true,
|
|
10921
|
-
data: {
|
|
11221
|
+
data: {
|
|
11222
|
+
token: rec.token,
|
|
11223
|
+
source: "global",
|
|
11224
|
+
userId: rec.userId,
|
|
11225
|
+
email: rec.email,
|
|
11226
|
+
keyed: rec.keyed
|
|
11227
|
+
}
|
|
10922
11228
|
};
|
|
10923
11229
|
}
|
|
10924
11230
|
const local = await readLegacyLocalCredential();
|
|
@@ -10940,6 +11246,47 @@ async function whoami(token, serviceUrl, verbose) {
|
|
|
10940
11246
|
cmd: "whoami"
|
|
10941
11247
|
});
|
|
10942
11248
|
}
|
|
11249
|
+
function reverifyNudge(who) {
|
|
11250
|
+
if (who.grant_status === "hard_stale") {
|
|
11251
|
+
return 'Your GitHub verification has expired \u2014 run "verity login" to restore saved runs and memory.';
|
|
11252
|
+
}
|
|
11253
|
+
if (who.grant_status === "soft_stale") {
|
|
11254
|
+
const by = who.reverify_by ? ` by ${who.reverify_by.slice(0, 10)}` : " soon";
|
|
11255
|
+
return `Your GitHub verification needs a refresh${by} \u2014 run "verity login" to re-verify.`;
|
|
11256
|
+
}
|
|
11257
|
+
return null;
|
|
11258
|
+
}
|
|
11259
|
+
function isLegacyPerRepoCredential(auth2) {
|
|
11260
|
+
return auth2.source === "local" || auth2.source === "global" && auth2.keyed === true;
|
|
11261
|
+
}
|
|
11262
|
+
async function shouldUpgradeOnLogin(auth2) {
|
|
11263
|
+
if (!isLegacyPerRepoCredential(auth2)) return false;
|
|
11264
|
+
if (auth2.userId == null) return true;
|
|
11265
|
+
const bare = await readGlobalCredential("");
|
|
11266
|
+
if (bare?.userId == null) return true;
|
|
11267
|
+
return bare.userId === auth2.userId;
|
|
11268
|
+
}
|
|
11269
|
+
function authDenialRemedy(error) {
|
|
11270
|
+
if (error.startsWith("STALE_VERIFICATION")) {
|
|
11271
|
+
return {
|
|
11272
|
+
code: "STALE_VERIFICATION",
|
|
11273
|
+
remedy: 'Your GitHub verification has expired \u2014 run "verity login" to re-verify your repository access.'
|
|
11274
|
+
};
|
|
11275
|
+
}
|
|
11276
|
+
if (error.startsWith("FORBIDDEN")) {
|
|
11277
|
+
return {
|
|
11278
|
+
code: "FORBIDDEN",
|
|
11279
|
+
remedy: 'No access grant for this repository \u2014 run "verity login" to refresh your grants (a repo granted after your last login needs one), or get write access to it.'
|
|
11280
|
+
};
|
|
11281
|
+
}
|
|
11282
|
+
if (error.startsWith("INVALID_TOKEN")) {
|
|
11283
|
+
return {
|
|
11284
|
+
code: "INVALID_TOKEN",
|
|
11285
|
+
remedy: 'Your Verity login has expired or was revoked \u2014 run "verity login" to sign in again.'
|
|
11286
|
+
};
|
|
11287
|
+
}
|
|
11288
|
+
return null;
|
|
11289
|
+
}
|
|
10943
11290
|
async function probeService(serviceUrl, verbose) {
|
|
10944
11291
|
const res = await apiRequest({
|
|
10945
11292
|
method: "GET",
|
|
@@ -10978,6 +11325,7 @@ async function maybeHealServiceUrl(resolution, verbose) {
|
|
|
10978
11325
|
|
|
10979
11326
|
// src/lib/register.ts
|
|
10980
11327
|
var readline = __toESM(require("node:readline/promises"));
|
|
11328
|
+
var import_node_os = require("node:os");
|
|
10981
11329
|
|
|
10982
11330
|
// src/lib/provider-auth.ts
|
|
10983
11331
|
var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
@@ -11064,250 +11412,23 @@ async function githubDeviceFlow() {
|
|
|
11064
11412
|
data = await res.json().catch(() => ({}));
|
|
11065
11413
|
} catch {
|
|
11066
11414
|
continue;
|
|
11067
|
-
}
|
|
11068
|
-
if (data.access_token) return { ok: true, data: data.access_token };
|
|
11069
|
-
switch (data.error) {
|
|
11070
|
-
case "authorization_pending":
|
|
11071
|
-
break;
|
|
11072
|
-
case "slow_down":
|
|
11073
|
-
interval += 5;
|
|
11074
|
-
break;
|
|
11075
|
-
case "access_denied":
|
|
11076
|
-
return { ok: false, error: "Authorization was denied on GitHub." };
|
|
11077
|
-
case "expired_token":
|
|
11078
|
-
return { ok: false, error: "The authorization code expired. Re-run register." };
|
|
11079
|
-
default:
|
|
11080
|
-
if (data.error) return { ok: false, error: `GitHub auth error: ${data.error}` };
|
|
11081
|
-
}
|
|
11082
|
-
}
|
|
11083
|
-
return { ok: false, error: "Timed out waiting for GitHub authorization." };
|
|
11084
|
-
}
|
|
11085
|
-
|
|
11086
|
-
// src/lib/git.ts
|
|
11087
|
-
var import_node_child_process3 = require("node:child_process");
|
|
11088
|
-
var import_node_fs3 = require("node:fs");
|
|
11089
|
-
var import_node_path3 = require("node:path");
|
|
11090
|
-
function resolveFile(relpath) {
|
|
11091
|
-
return (0, import_node_fs3.existsSync)(relpath) ? relpath : null;
|
|
11092
|
-
}
|
|
11093
|
-
function execGit(cmd) {
|
|
11094
|
-
try {
|
|
11095
|
-
return (0, import_node_child_process3.execSync)(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
11096
|
-
} catch {
|
|
11097
|
-
return "";
|
|
11098
|
-
}
|
|
11099
|
-
}
|
|
11100
|
-
function splitLines(s) {
|
|
11101
|
-
return s.split("\n").filter((l) => l.length > 0);
|
|
11102
|
-
}
|
|
11103
|
-
var SHA_RE = /^[0-9a-f]{40}$/;
|
|
11104
|
-
function readBaselineSha() {
|
|
11105
|
-
if (!(0, import_node_fs3.existsSync)(BASELINE_SHA_FILE)) return null;
|
|
11106
|
-
let sha;
|
|
11107
|
-
try {
|
|
11108
|
-
sha = (0, import_node_fs3.readFileSync)(BASELINE_SHA_FILE, "utf-8").trim();
|
|
11109
|
-
} catch {
|
|
11110
|
-
return null;
|
|
11111
|
-
}
|
|
11112
|
-
if (!SHA_RE.test(sha)) return null;
|
|
11113
|
-
const reachable = execGit(`git cat-file -e ${sha}^{commit} 2>/dev/null && echo ok`) === "ok";
|
|
11114
|
-
if (!reachable) {
|
|
11115
|
-
try {
|
|
11116
|
-
(0, import_node_fs3.unlinkSync)(BASELINE_SHA_FILE);
|
|
11117
|
-
} catch {
|
|
11118
|
-
}
|
|
11119
|
-
return null;
|
|
11120
|
-
}
|
|
11121
|
-
return sha;
|
|
11122
|
-
}
|
|
11123
|
-
function writeBaselineSha(sha) {
|
|
11124
|
-
if (!SHA_RE.test(sha)) return;
|
|
11125
|
-
try {
|
|
11126
|
-
(0, import_node_fs3.mkdirSync)((0, import_node_path3.dirname)(BASELINE_SHA_FILE), { recursive: true });
|
|
11127
|
-
(0, import_node_fs3.writeFileSync)(BASELINE_SHA_FILE, sha);
|
|
11128
|
-
} catch {
|
|
11129
|
-
}
|
|
11130
|
-
}
|
|
11131
|
-
function getChangedFiles() {
|
|
11132
|
-
const sets = /* @__PURE__ */ new Set();
|
|
11133
|
-
let hasRecentCommitFiles = false;
|
|
11134
|
-
for (const f of splitLines(execGit("git diff --name-only HEAD"))) sets.add(f);
|
|
11135
|
-
for (const f of splitLines(execGit("git diff --name-only --cached"))) sets.add(f);
|
|
11136
|
-
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) sets.add(f);
|
|
11137
|
-
const baseline = readBaselineSha();
|
|
11138
|
-
if (baseline) {
|
|
11139
|
-
const committed = splitLines(execGit(`git diff --name-only ${baseline}..HEAD`));
|
|
11140
|
-
if (committed.length > 0) {
|
|
11141
|
-
hasRecentCommitFiles = true;
|
|
11142
|
-
for (const f of committed) sets.add(f);
|
|
11143
|
-
}
|
|
11144
|
-
} else {
|
|
11145
|
-
const headTimestamp = parseInt(execGit("git log -1 --format=%ct HEAD"), 10) || 0;
|
|
11146
|
-
const commitAge = Math.floor(Date.now() / 1e3) - headTimestamp;
|
|
11147
|
-
const hasUnstaged = splitLines(execGit("git diff --name-only HEAD")).length > 0;
|
|
11148
|
-
const hasStaged = splitLines(execGit("git diff --name-only --cached")).length > 0;
|
|
11149
|
-
if (commitAge < 120 && !hasUnstaged && !hasStaged) {
|
|
11150
|
-
const recentFiles = splitLines(execGit("git diff --name-only HEAD~1..HEAD"));
|
|
11151
|
-
if (recentFiles.length > 0) {
|
|
11152
|
-
hasRecentCommitFiles = true;
|
|
11153
|
-
for (const f of recentFiles) sets.add(f);
|
|
11154
|
-
}
|
|
11155
|
-
}
|
|
11156
|
-
}
|
|
11157
|
-
const filtered = Array.from(sets).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
11158
|
-
return { files: filtered, hasRecentCommitFiles };
|
|
11159
|
-
}
|
|
11160
|
-
function getStagedFiles() {
|
|
11161
|
-
return splitLines(execGit("git diff --cached --name-only")).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
11162
|
-
}
|
|
11163
|
-
function getDirtyFiles() {
|
|
11164
|
-
const set = /* @__PURE__ */ new Set();
|
|
11165
|
-
for (const f of splitLines(execGit("git diff --name-only HEAD"))) set.add(f);
|
|
11166
|
-
for (const f of splitLines(execGit("git diff --name-only --cached"))) set.add(f);
|
|
11167
|
-
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
|
|
11168
|
-
return Array.from(set).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
11169
|
-
}
|
|
11170
|
-
function showContentAtRef(ref, repoRelPath) {
|
|
11171
|
-
if (!ref || ref === "no-git") return null;
|
|
11172
|
-
const normalizedPath = repoRelPath.replace(/\\/g, "/");
|
|
11173
|
-
try {
|
|
11174
|
-
return (0, import_node_child_process3.execFileSync)("git", ["show", `${ref}:${normalizedPath}`], {
|
|
11175
|
-
encoding: "utf-8",
|
|
11176
|
-
maxBuffer: 64 * 1024 * 1024,
|
|
11177
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
11178
|
-
});
|
|
11179
|
-
} catch {
|
|
11180
|
-
return null;
|
|
11181
|
-
}
|
|
11182
|
-
}
|
|
11183
|
-
function getPushRangeFiles() {
|
|
11184
|
-
const diff = (range) => splitLines(execGit(`git diff --name-only ${range}`)).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
11185
|
-
const resolvers = [
|
|
11186
|
-
() => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{push}") ? "@{push}..HEAD" : null,
|
|
11187
|
-
() => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{upstream}") ? "@{upstream}..HEAD" : null,
|
|
11188
|
-
() => {
|
|
11189
|
-
const branch = execGit("git rev-parse --abbrev-ref HEAD");
|
|
11190
|
-
return branch && branch !== "HEAD" && execGit(`git rev-parse --verify -q origin/${branch}`) ? `origin/${branch}..HEAD` : null;
|
|
11191
|
-
}
|
|
11192
|
-
];
|
|
11193
|
-
for (const resolve2 of resolvers) {
|
|
11194
|
-
const range = resolve2();
|
|
11195
|
-
if (range) return { files: diff(range), range };
|
|
11196
|
-
}
|
|
11197
|
-
const baseline = readBaselineSha();
|
|
11198
|
-
if (baseline) {
|
|
11199
|
-
const files = diff(`${baseline}..HEAD`);
|
|
11200
|
-
if (files.length > 0) return { files, range: `${baseline}..HEAD` };
|
|
11201
|
-
}
|
|
11202
|
-
const last = diff("HEAD~1..HEAD");
|
|
11203
|
-
return { files: last, range: last.length > 0 ? "HEAD~1..HEAD" : null };
|
|
11204
|
-
}
|
|
11205
|
-
function getPushRangeMessages() {
|
|
11206
|
-
const { range } = getPushRangeFiles();
|
|
11207
|
-
if (!range) return "";
|
|
11208
|
-
return execGit(`git log ${range} --format=%B%x00`).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
|
|
11209
|
-
}
|
|
11210
|
-
function filterAnalyzable(files) {
|
|
11211
|
-
return files.filter((f) => {
|
|
11212
|
-
const ext = (0, import_node_path3.extname)(f).slice(1);
|
|
11213
|
-
return ANALYZABLE_EXTENSIONS.has(ext);
|
|
11214
|
-
});
|
|
11215
|
-
}
|
|
11216
|
-
function filterReviewable(files) {
|
|
11217
|
-
return files.filter((f) => {
|
|
11218
|
-
const ext = (0, import_node_path3.extname)(f).slice(1);
|
|
11219
|
-
if (ANALYZABLE_EXTENSIONS.has(ext)) return false;
|
|
11220
|
-
if (REVIEWABLE_EXTENSIONS.has(ext)) return true;
|
|
11221
|
-
const basename5 = f.split("/").pop() ?? "";
|
|
11222
|
-
if (REVIEWABLE_FILENAMES.has(basename5)) return true;
|
|
11223
|
-
if (REVIEWABLE_PATH_PATTERNS.some((p) => p.test(f))) return true;
|
|
11224
|
-
return false;
|
|
11225
|
-
});
|
|
11226
|
-
}
|
|
11227
|
-
function filterSecurity(files) {
|
|
11228
|
-
return files.filter(
|
|
11229
|
-
(f) => SECURITY_PATTERNS.some((p) => p.test(f))
|
|
11230
|
-
);
|
|
11231
|
-
}
|
|
11232
|
-
function getCurrentCommit() {
|
|
11233
|
-
return execGit("git rev-parse HEAD") || "no-git";
|
|
11234
|
-
}
|
|
11235
|
-
function getCurrentBranch() {
|
|
11236
|
-
const b = execGit("git rev-parse --abbrev-ref HEAD");
|
|
11237
|
-
return !b || b === "HEAD" ? null : b;
|
|
11238
|
-
}
|
|
11239
|
-
function commitResolves(sha) {
|
|
11240
|
-
if (!sha) return false;
|
|
11241
|
-
try {
|
|
11242
|
-
(0, import_node_child_process3.execSync)(`git merge-base --is-ancestor ${JSON.stringify(sha)} HEAD`, {
|
|
11243
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
11244
|
-
});
|
|
11245
|
-
return true;
|
|
11246
|
-
} catch {
|
|
11247
|
-
return false;
|
|
11248
|
-
}
|
|
11249
|
-
}
|
|
11250
|
-
function commitsSincePaths(sha, paths) {
|
|
11251
|
-
if (!sha) return null;
|
|
11252
|
-
try {
|
|
11253
|
-
const scope = paths.length > 0 ? ` -- ${paths.slice(0, 50).map((p) => JSON.stringify(p)).join(" ")}` : "";
|
|
11254
|
-
const out = (0, import_node_child_process3.execSync)(`git rev-list --count ${JSON.stringify(sha)}..HEAD${scope}`, {
|
|
11255
|
-
encoding: "utf-8",
|
|
11256
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
11257
|
-
}).trim();
|
|
11258
|
-
const n = Number.parseInt(out, 10);
|
|
11259
|
-
return Number.isFinite(n) ? n : null;
|
|
11260
|
-
} catch {
|
|
11261
|
-
return null;
|
|
11262
|
-
}
|
|
11263
|
-
}
|
|
11264
|
-
function detectProvider(host) {
|
|
11265
|
-
const h = host.toLowerCase();
|
|
11266
|
-
if (h.includes("github")) return "github";
|
|
11267
|
-
if (h.includes("gitlab")) return "gitlab";
|
|
11268
|
-
if (h.includes("bitbucket")) return "bitbucket";
|
|
11269
|
-
return "unknown";
|
|
11270
|
-
}
|
|
11271
|
-
function parseRemote(raw) {
|
|
11272
|
-
if (!raw || typeof raw !== "string") return null;
|
|
11273
|
-
let s = raw.trim();
|
|
11274
|
-
if (!s) return null;
|
|
11275
|
-
let host = "";
|
|
11276
|
-
let path = "";
|
|
11277
|
-
const scp = s.match(/^[^/@]+@([^:/]+):(.+)$/);
|
|
11278
|
-
if (scp) {
|
|
11279
|
-
host = scp[1];
|
|
11280
|
-
path = scp[2];
|
|
11281
|
-
} else {
|
|
11282
|
-
s = s.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//, "");
|
|
11283
|
-
s = s.replace(/^[^/@]+@/, "");
|
|
11284
|
-
const slash = s.indexOf("/");
|
|
11285
|
-
if (slash === -1) return null;
|
|
11286
|
-
host = s.slice(0, slash);
|
|
11287
|
-
path = s.slice(slash + 1);
|
|
11415
|
+
}
|
|
11416
|
+
if (data.access_token) return { ok: true, data: data.access_token };
|
|
11417
|
+
switch (data.error) {
|
|
11418
|
+
case "authorization_pending":
|
|
11419
|
+
break;
|
|
11420
|
+
case "slow_down":
|
|
11421
|
+
interval += 5;
|
|
11422
|
+
break;
|
|
11423
|
+
case "access_denied":
|
|
11424
|
+
return { ok: false, error: "Authorization was denied on GitHub." };
|
|
11425
|
+
case "expired_token":
|
|
11426
|
+
return { ok: false, error: "The authorization code expired. Re-run register." };
|
|
11427
|
+
default:
|
|
11428
|
+
if (data.error) return { ok: false, error: `GitHub auth error: ${data.error}` };
|
|
11429
|
+
}
|
|
11288
11430
|
}
|
|
11289
|
-
|
|
11290
|
-
path = path.replace(/\/+$/, "").replace(/\.git$/, "");
|
|
11291
|
-
if (!host || !path) return null;
|
|
11292
|
-
const segments = path.split("/").filter(Boolean);
|
|
11293
|
-
if (segments.length < 2) return null;
|
|
11294
|
-
const owner = segments[0];
|
|
11295
|
-
const repo = segments[segments.length - 1];
|
|
11296
|
-
if (!owner || !repo) return null;
|
|
11297
|
-
return {
|
|
11298
|
-
host,
|
|
11299
|
-
owner,
|
|
11300
|
-
repo,
|
|
11301
|
-
provider: detectProvider(host),
|
|
11302
|
-
orgUrl: `https://${host}/${owner}`,
|
|
11303
|
-
orgName: owner
|
|
11304
|
-
};
|
|
11305
|
-
}
|
|
11306
|
-
function listTrackedFiles() {
|
|
11307
|
-
const set = /* @__PURE__ */ new Set();
|
|
11308
|
-
for (const f of splitLines(execGit("git ls-files"))) set.add(f);
|
|
11309
|
-
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
|
|
11310
|
-
return Array.from(set);
|
|
11431
|
+
return { ok: false, error: "Timed out waiting for GitHub authorization." };
|
|
11311
11432
|
}
|
|
11312
11433
|
|
|
11313
11434
|
// src/lib/register.ts
|
|
@@ -11392,11 +11513,81 @@ async function registerProject(opts) {
|
|
|
11392
11513
|
}
|
|
11393
11514
|
return { ok: true, data: { projectId: project_id, serviceUrl: service_url, email, userId } };
|
|
11394
11515
|
}
|
|
11516
|
+
function deviceLabel() {
|
|
11517
|
+
const override = process.env.VERITY_DEVICE_LABEL?.trim();
|
|
11518
|
+
if (override) return override;
|
|
11519
|
+
try {
|
|
11520
|
+
return (0, import_node_os.hostname)() || void 0;
|
|
11521
|
+
} catch {
|
|
11522
|
+
return void 0;
|
|
11523
|
+
}
|
|
11524
|
+
}
|
|
11525
|
+
async function loginOnce(opts) {
|
|
11526
|
+
const usingTokenOverride = Boolean(process.env.VERITY_PROVIDER_TOKEN);
|
|
11527
|
+
const providerAuth = await githubDeviceFlow();
|
|
11528
|
+
if (!providerAuth.ok) {
|
|
11529
|
+
return { ok: false, error: providerAuth.error };
|
|
11530
|
+
}
|
|
11531
|
+
const providerToken = providerAuth.data;
|
|
11532
|
+
const parsed = opts.remote ? parseRemote(opts.remote) : null;
|
|
11533
|
+
if (!usingTokenOverride && parsed && parsed.provider === "github") {
|
|
11534
|
+
const installed = await ensureAppInstalled(parsed.owner, parsed.repo, providerToken);
|
|
11535
|
+
if (!installed.ok) {
|
|
11536
|
+
printWarn(installed.error);
|
|
11537
|
+
printInfo("Continuing login \u2014 repositories on other accounts are still granted.");
|
|
11538
|
+
}
|
|
11539
|
+
}
|
|
11540
|
+
const result = await apiRequest({
|
|
11541
|
+
method: "POST",
|
|
11542
|
+
path: "/auth/login",
|
|
11543
|
+
serviceUrl: opts.serviceUrl,
|
|
11544
|
+
extraHeaders: { "X-Provider-Token": providerToken },
|
|
11545
|
+
// Label the session so its owner can tell their machines apart in
|
|
11546
|
+
// `verity sessions list` — a list of identical "login" rows is unusable when
|
|
11547
|
+
// the question is "which of these is the laptop I lost?". The hostname is the
|
|
11548
|
+
// useful default; VERITY_DEVICE_LABEL overrides it for anyone who would
|
|
11549
|
+
// rather not send it. Server-side it is sanitized and capped.
|
|
11550
|
+
body: { device: deviceLabel() },
|
|
11551
|
+
verbose: opts.verbose,
|
|
11552
|
+
cmd: "login"
|
|
11553
|
+
});
|
|
11554
|
+
if (!result.ok) {
|
|
11555
|
+
return { ok: false, error: result.error };
|
|
11556
|
+
}
|
|
11557
|
+
const { token, service_url, user_id, user, repo_count, expires_at } = result.data;
|
|
11558
|
+
const loginUserId = user_id ?? user?.id ?? void 0;
|
|
11559
|
+
try {
|
|
11560
|
+
await upsertGlobalCredential("", {
|
|
11561
|
+
token,
|
|
11562
|
+
serviceUrl: service_url,
|
|
11563
|
+
userId: loginUserId,
|
|
11564
|
+
email: user?.email
|
|
11565
|
+
});
|
|
11566
|
+
} catch (err) {
|
|
11567
|
+
return {
|
|
11568
|
+
ok: false,
|
|
11569
|
+
error: `Logged in with the Verity service, but could not save credentials to ~/.verity/credentials: ${err.message}. Check filesystem permissions and re-run "verity login".`
|
|
11570
|
+
};
|
|
11571
|
+
}
|
|
11572
|
+
const pruned = await removeSupersededUserCredentials(service_url, loginUserId);
|
|
11573
|
+
return {
|
|
11574
|
+
ok: true,
|
|
11575
|
+
data: {
|
|
11576
|
+
token,
|
|
11577
|
+
serviceUrl: service_url,
|
|
11578
|
+
email: user?.email,
|
|
11579
|
+
userId: loginUserId,
|
|
11580
|
+
repoCount: repo_count ?? 0,
|
|
11581
|
+
prunedCredentials: pruned,
|
|
11582
|
+
expiresAt: expires_at
|
|
11583
|
+
}
|
|
11584
|
+
};
|
|
11585
|
+
}
|
|
11395
11586
|
|
|
11396
11587
|
// src/commands/auth.ts
|
|
11397
11588
|
function registerAuthCommands(program2) {
|
|
11398
|
-
const
|
|
11399
|
-
|
|
11589
|
+
const auth2 = program2.command("auth").description("Manage project authentication");
|
|
11590
|
+
auth2.command("register").description("Register a project with Verity").requiredOption("--project <name>", "Project name").option("--remote <url>", "Git remote URL (auto-detected if omitted)").action(async (opts) => {
|
|
11400
11591
|
const globals = program2.opts();
|
|
11401
11592
|
const serviceUrl = globals.serviceUrl ?? DEFAULT_SERVICE_URL;
|
|
11402
11593
|
let remote = opts.remote;
|
|
@@ -11423,7 +11614,7 @@ function registerAuthCommands(program2) {
|
|
|
11423
11614
|
if (email) printInfo(`Authenticated as: ${email}`);
|
|
11424
11615
|
printJson({ project_id: projectId, service_url: resolvedUrl });
|
|
11425
11616
|
});
|
|
11426
|
-
|
|
11617
|
+
auth2.command("verify").description("Verify the current token is valid").action(async () => {
|
|
11427
11618
|
const globals = program2.opts();
|
|
11428
11619
|
const tokenResult = await resolveToken(globals.token);
|
|
11429
11620
|
if (!tokenResult.ok) {
|
|
@@ -11449,7 +11640,7 @@ function registerAuthCommands(program2) {
|
|
|
11449
11640
|
printInfo(`Token valid. Project: ${result.data.project_name}`);
|
|
11450
11641
|
printJson(result.data);
|
|
11451
11642
|
});
|
|
11452
|
-
|
|
11643
|
+
auth2.command("discover").description("Check if a project is registered").option("--remote <url>", "Git remote URL (auto-detected if omitted)").action(async (opts) => {
|
|
11453
11644
|
const globals = program2.opts();
|
|
11454
11645
|
let remote = opts.remote;
|
|
11455
11646
|
if (!remote) {
|
|
@@ -11481,10 +11672,8 @@ function registerAuthCommands(program2) {
|
|
|
11481
11672
|
}
|
|
11482
11673
|
|
|
11483
11674
|
// src/commands/login.ts
|
|
11484
|
-
var import_node_child_process5 = require("node:child_process");
|
|
11485
|
-
var import_node_path4 = require("node:path");
|
|
11486
11675
|
function registerLoginCommand(program2) {
|
|
11487
|
-
program2.command("login").description("Log in to Verity (
|
|
11676
|
+
program2.command("login").description("Log in to Verity (one GitHub login grants access to all your repositories)").option("--force", "Re-authenticate even if already logged in").action(async (opts) => {
|
|
11488
11677
|
const globals = program2.opts();
|
|
11489
11678
|
const urlResult = await resolveServiceUrlDetailed(globals.serviceUrl);
|
|
11490
11679
|
if (!urlResult.ok) {
|
|
@@ -11494,60 +11683,399 @@ function registerLoginCommand(program2) {
|
|
|
11494
11683
|
const heal = await maybeHealServiceUrl(urlResult.data, globals.verbose);
|
|
11495
11684
|
const serviceUrl = heal.serviceUrl;
|
|
11496
11685
|
if (heal.healed) {
|
|
11497
|
-
printInfo(" Completing login
|
|
11686
|
+
printInfo(" Completing login updates ~/.verity/credentials against the live service.");
|
|
11687
|
+
}
|
|
11688
|
+
const remote = currentRemote();
|
|
11689
|
+
if (remote && !parseRemote(remote)) {
|
|
11690
|
+
printWarn(`This repository's origin remote is not a recognizable git URL: ${remote}`);
|
|
11691
|
+
printInfo(" Verity cannot identify the repository from it, so runs here will not be saved.");
|
|
11692
|
+
printInfo(" Point origin at the full URL (e.g. git@github.com:owner/repo.git) to fix it.");
|
|
11498
11693
|
}
|
|
11499
11694
|
const existing = await resolveToken(globals.token);
|
|
11500
11695
|
if (existing.ok && !opts.force && !heal.healed) {
|
|
11501
|
-
|
|
11502
|
-
|
|
11503
|
-
|
|
11504
|
-
|
|
11505
|
-
|
|
11506
|
-
|
|
11507
|
-
|
|
11508
|
-
|
|
11509
|
-
|
|
11510
|
-
|
|
11511
|
-
|
|
11512
|
-
|
|
11696
|
+
const who = await whoami(existing.data.token, serviceUrl, globals.verbose);
|
|
11697
|
+
if (who.ok && who.data.logged_in) {
|
|
11698
|
+
const nudge = reverifyNudge(who.data);
|
|
11699
|
+
const upgrade = await shouldUpgradeOnLogin(existing.data);
|
|
11700
|
+
if (!nudge && !upgrade) {
|
|
11701
|
+
printInfo(`Already logged in as ${who.data.email ?? `user #${who.data.user_id}`}. \u2713`);
|
|
11702
|
+
printInfo(" Re-authenticate with: verity login --force");
|
|
11703
|
+
return;
|
|
11704
|
+
}
|
|
11705
|
+
if (nudge) {
|
|
11706
|
+
printWarn(nudge);
|
|
11707
|
+
printInfo("Re-verifying your repository access\u2026");
|
|
11708
|
+
} else {
|
|
11709
|
+
printInfo("You are signed in with a per-repository token (the old format).");
|
|
11710
|
+
printInfo(" Upgrading to a single login that covers every repository you can write to\u2026");
|
|
11711
|
+
}
|
|
11712
|
+
} else if (who.ok && who.data.anonymous) {
|
|
11513
11713
|
printInfo("You have an anonymous token (the gate runs, but nothing is saved). Logging you in\u2026");
|
|
11514
|
-
} else if (!
|
|
11714
|
+
} else if (!who.ok) {
|
|
11515
11715
|
printWarn("Could not confirm your current login state with the service \u2014 proceeding to log in.");
|
|
11516
11716
|
}
|
|
11517
11717
|
}
|
|
11518
|
-
|
|
11519
|
-
|
|
11520
|
-
|
|
11521
|
-
|
|
11718
|
+
printInfo("Authenticating with GitHub\u2026");
|
|
11719
|
+
const result = await loginOnce({ serviceUrl, remote: remote || void 0, verbose: globals.verbose });
|
|
11720
|
+
if (!result.ok) {
|
|
11721
|
+
printError(`Login failed: ${result.error}`);
|
|
11722
|
+
process.exit(1);
|
|
11522
11723
|
}
|
|
11523
|
-
|
|
11524
|
-
|
|
11525
|
-
|
|
11724
|
+
const out = result.data;
|
|
11725
|
+
const identity = out.email ?? (out.userId != null ? `user #${out.userId}` : "your account");
|
|
11726
|
+
printInfo(`Logged in as ${identity}. \u2713`);
|
|
11727
|
+
printInfo(` Access granted to ${out.repoCount} ${out.repoCount === 1 ? "repository" : "repositories"}.`);
|
|
11728
|
+
if (out.expiresAt) {
|
|
11729
|
+
printInfo(` This login expires on ${out.expiresAt.slice(0, 10)} \u2014 "verity login" again to renew.`);
|
|
11730
|
+
printInfo(' See your machines with "verity sessions list"; sign out with "verity logout".');
|
|
11731
|
+
}
|
|
11732
|
+
printInfo(" Runs, history, and cloud memory now sync to Verity everywhere you have write access.");
|
|
11733
|
+
if (out.prunedCredentials > 0) {
|
|
11734
|
+
printVerbose(`Pruned ${out.prunedCredentials} superseded per-repository credential line(s).`, globals.verbose);
|
|
11735
|
+
} else if (out.prunedCredentials < 0) {
|
|
11736
|
+
printWarn(" Could not rewrite ~/.verity/credentials: superseded per-repository tokens remain and");
|
|
11737
|
+
printWarn(" will keep taking precedence over this login in their own repositories.");
|
|
11738
|
+
printInfo(` Check the file's permissions; the next successful "verity login" retries the cleanup.`);
|
|
11739
|
+
}
|
|
11740
|
+
if (out.repoCount === 0) {
|
|
11741
|
+
printWarn("The Verity GitHub App is not installed on any account you can access.");
|
|
11742
|
+
printInfo(` Install it (and grant your repositories), then re-run verity login:`);
|
|
11743
|
+
printInfo(` ${githubAppInstallUrl(null)}`);
|
|
11744
|
+
return;
|
|
11745
|
+
}
|
|
11746
|
+
if (remote) {
|
|
11747
|
+
const who = await whoami(out.token, out.serviceUrl, globals.verbose);
|
|
11748
|
+
if (who.ok && who.data.grant_status != null) {
|
|
11749
|
+
printInfo(" \u2713 This repository is covered.");
|
|
11750
|
+
} else if (!who.ok) {
|
|
11751
|
+
printWarn(` Could not confirm this repository's coverage (${who.error}) \u2014 verity status will show it.`);
|
|
11752
|
+
} else {
|
|
11753
|
+
const parsed = parseRemote(remote);
|
|
11754
|
+
const installUrl = githubAppInstallUrl(parsed ? await githubAccountId(parsed.owner) : null);
|
|
11755
|
+
printWarn(` This repository (${parsed ? `${parsed.owner}/${parsed.repo}` : remote}) is NOT covered by your grants.`);
|
|
11756
|
+
printInfo(" Grant the Verity GitHub App access to it, then re-run verity login:");
|
|
11757
|
+
printInfo(` ${installUrl}`);
|
|
11758
|
+
}
|
|
11759
|
+
const rec = await readGlobalCredential(remote);
|
|
11760
|
+
if (rec && rec.token !== out.token) {
|
|
11761
|
+
const otherBackend = rec.serviceUrl != null && rec.serviceUrl !== out.serviceUrl;
|
|
11762
|
+
const otherIdentity = rec.userId != null && out.userId != null && rec.userId !== out.userId;
|
|
11763
|
+
if (otherBackend) {
|
|
11764
|
+
printWarn(` Note: this repository is pinned to a different Verity service (${rec.serviceUrl})`);
|
|
11765
|
+
printWarn(" by its own credential line, which takes precedence here \u2014 this login does not");
|
|
11766
|
+
printWarn(" change that. To move the repository, remove its line from ~/.verity/credentials.");
|
|
11767
|
+
printWarn(' Until that line is removed, "verity login" here cannot fast-path and will run');
|
|
11768
|
+
printWarn(" the full GitHub flow every time.");
|
|
11769
|
+
} else if (otherIdentity) {
|
|
11770
|
+
printWarn(" Note: this repository uses a different account's credential, which takes");
|
|
11771
|
+
printWarn(' precedence here \u2014 this login leaves it in place, and "verity login" in this');
|
|
11772
|
+
printWarn(" repository will report that account. Remove its line from ~/.verity/credentials");
|
|
11773
|
+
printWarn(" only if you want this repository on the login you just completed.");
|
|
11774
|
+
} else {
|
|
11775
|
+
const kind = rec.userId != null ? "superseded per-repository" : "anonymous project-specific";
|
|
11776
|
+
printWarn(` Note: this repository has a ${kind} credential that takes`);
|
|
11777
|
+
printWarn(" precedence here. Remove its line from ~/.verity/credentials to use your login.");
|
|
11778
|
+
printWarn(' Until then, "verity login" in this repository re-runs the full GitHub flow');
|
|
11779
|
+
printWarn(" every time.");
|
|
11780
|
+
}
|
|
11781
|
+
}
|
|
11782
|
+
}
|
|
11783
|
+
});
|
|
11784
|
+
}
|
|
11785
|
+
|
|
11786
|
+
// src/commands/token.ts
|
|
11787
|
+
function registerTokenCommand(program2) {
|
|
11788
|
+
const token = program2.command("token").description("Manage service (CI/machine) tokens for this repository");
|
|
11789
|
+
async function requireAuth(globals) {
|
|
11790
|
+
const tokenResult = await resolveToken(globals.token);
|
|
11791
|
+
if (!tokenResult.ok) {
|
|
11792
|
+
printError(tokenResult.error);
|
|
11526
11793
|
process.exit(1);
|
|
11527
11794
|
}
|
|
11528
|
-
const
|
|
11529
|
-
|
|
11530
|
-
|
|
11795
|
+
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
11796
|
+
if (!urlResult.ok) {
|
|
11797
|
+
printError(urlResult.error);
|
|
11798
|
+
process.exit(1);
|
|
11799
|
+
}
|
|
11800
|
+
return { bearer: tokenResult.data.token, serviceUrl: urlResult.data };
|
|
11801
|
+
}
|
|
11802
|
+
function explainDenial(error) {
|
|
11803
|
+
if (error.startsWith("STALE_VERIFICATION")) {
|
|
11804
|
+
printInfo(' Your GitHub verification has expired \u2014 run "verity login", then retry.');
|
|
11805
|
+
} else if (error.startsWith("FORBIDDEN")) {
|
|
11806
|
+
printInfo(" You need write access to this repository. If it was added recently,");
|
|
11807
|
+
printInfo(' run "verity login" to refresh your grants.');
|
|
11808
|
+
} else if (error.startsWith("INVALID_REQUEST")) {
|
|
11809
|
+
printInfo(" Tokens are managed per repository \u2014 run this inside a repository with a");
|
|
11810
|
+
printInfo(" git remote (origin), so Verity knows which project the token belongs to.");
|
|
11811
|
+
}
|
|
11812
|
+
}
|
|
11813
|
+
token.command("create").description("Mint a service token for CI (store it in your CI secret store as VERITY_TOKEN)").requiredOption("--name <name>", "Label for the token (e.g. github-actions)").option("--expires <days>", "Optional expiry in days (default: never)").action(async (opts) => {
|
|
11814
|
+
const globals = program2.opts();
|
|
11815
|
+
const { bearer, serviceUrl } = await requireAuth(globals);
|
|
11816
|
+
let expiresInDays;
|
|
11817
|
+
if (opts.expires != null) {
|
|
11818
|
+
expiresInDays = Number(opts.expires);
|
|
11819
|
+
if (!Number.isInteger(expiresInDays) || expiresInDays <= 0 || expiresInDays > 3650) {
|
|
11820
|
+
printError("--expires must be a whole number of days between 1 and 3650");
|
|
11821
|
+
process.exit(1);
|
|
11822
|
+
}
|
|
11823
|
+
}
|
|
11824
|
+
const result = await apiRequest({
|
|
11825
|
+
method: "POST",
|
|
11826
|
+
path: "/auth/tokens",
|
|
11827
|
+
serviceUrl,
|
|
11828
|
+
token: bearer,
|
|
11829
|
+
body: {
|
|
11830
|
+
agent_name: String(opts.name).trim(),
|
|
11831
|
+
token_type: "service",
|
|
11832
|
+
...expiresInDays != null ? { expires_in_days: expiresInDays } : {}
|
|
11833
|
+
},
|
|
11834
|
+
verbose: globals.verbose,
|
|
11835
|
+
cmd: "token-create"
|
|
11836
|
+
});
|
|
11531
11837
|
if (!result.ok) {
|
|
11532
|
-
printError(`
|
|
11838
|
+
printError(`Could not create the service token: ${result.error}`);
|
|
11839
|
+
explainDenial(result.error);
|
|
11840
|
+
process.exit(1);
|
|
11841
|
+
}
|
|
11842
|
+
printInfo(`Service token "${result.data.agent_name}" created. \u2713`);
|
|
11843
|
+
printInfo("");
|
|
11844
|
+
printInfo(` ${result.data.token}`);
|
|
11845
|
+
printInfo("");
|
|
11846
|
+
printWarn("This token is shown ONCE \u2014 store it now (e.g. as a VERITY_TOKEN CI secret).");
|
|
11847
|
+
if (result.data.expires_at) {
|
|
11848
|
+
printInfo(` Expires: ${result.data.expires_at.slice(0, 10)}`);
|
|
11849
|
+
}
|
|
11850
|
+
printInfo(" Any current writer of this repository can revoke it: verity token revoke <id>");
|
|
11851
|
+
printInfo(` Token id: ${result.data.token_id}`);
|
|
11852
|
+
});
|
|
11853
|
+
token.command("list").description("List this repository's tokens (ids and metadata only \u2014 never secrets)").action(async () => {
|
|
11854
|
+
const globals = program2.opts();
|
|
11855
|
+
const { bearer, serviceUrl } = await requireAuth(globals);
|
|
11856
|
+
const result = await apiRequest({
|
|
11857
|
+
method: "GET",
|
|
11858
|
+
path: "/auth/tokens",
|
|
11859
|
+
serviceUrl,
|
|
11860
|
+
token: bearer,
|
|
11861
|
+
verbose: globals.verbose,
|
|
11862
|
+
cmd: "token-list"
|
|
11863
|
+
});
|
|
11864
|
+
if (!result.ok) {
|
|
11865
|
+
printError(`Could not list tokens: ${result.error}`);
|
|
11866
|
+
explainDenial(result.error);
|
|
11867
|
+
process.exit(1);
|
|
11868
|
+
}
|
|
11869
|
+
const tokens = result.data.tokens ?? [];
|
|
11870
|
+
if (tokens.length === 0) {
|
|
11871
|
+
printInfo("No tokens found for this repository.");
|
|
11872
|
+
return;
|
|
11873
|
+
}
|
|
11874
|
+
for (const t of tokens) {
|
|
11875
|
+
const type = t.token_type ?? "user";
|
|
11876
|
+
const expires = t.expires_at ? `expires ${t.expires_at.slice(0, 10)}` : "no expiry";
|
|
11877
|
+
const lastUsed = t.last_used_at ? `last used ${t.last_used_at.slice(0, 10)}` : "never used";
|
|
11878
|
+
printInfo(`${t.id} [${type}] ${t.agent_name ?? "unnamed"} (${expires}, ${lastUsed})`);
|
|
11879
|
+
}
|
|
11880
|
+
});
|
|
11881
|
+
token.command("revoke <id>").description("Revoke a token by id (service tokens: any current writer may revoke)").action(async (id) => {
|
|
11882
|
+
const globals = program2.opts();
|
|
11883
|
+
const { bearer, serviceUrl } = await requireAuth(globals);
|
|
11884
|
+
const result = await apiRequest({
|
|
11885
|
+
method: "DELETE",
|
|
11886
|
+
path: `/auth/tokens/${encodeURIComponent(id)}`,
|
|
11887
|
+
serviceUrl,
|
|
11888
|
+
token: bearer,
|
|
11889
|
+
verbose: globals.verbose,
|
|
11890
|
+
cmd: "token-revoke"
|
|
11891
|
+
});
|
|
11892
|
+
if (!result.ok) {
|
|
11893
|
+
printError(`Could not revoke the token: ${result.error}`);
|
|
11894
|
+
explainDenial(result.error);
|
|
11895
|
+
process.exit(1);
|
|
11896
|
+
}
|
|
11897
|
+
printInfo(`Token ${result.data.token_id} revoked. \u2713`);
|
|
11898
|
+
});
|
|
11899
|
+
}
|
|
11900
|
+
|
|
11901
|
+
// src/commands/sessions.ts
|
|
11902
|
+
function shortDate(iso) {
|
|
11903
|
+
return iso ? iso.slice(0, 10) : "\u2014";
|
|
11904
|
+
}
|
|
11905
|
+
function daysUntil(iso) {
|
|
11906
|
+
if (!iso) return null;
|
|
11907
|
+
const ms = Date.parse(iso);
|
|
11908
|
+
if (Number.isNaN(ms)) return null;
|
|
11909
|
+
return Math.round((ms - Date.now()) / 864e5);
|
|
11910
|
+
}
|
|
11911
|
+
async function auth(globals) {
|
|
11912
|
+
const tokenResult = await resolveToken(globals.token);
|
|
11913
|
+
if (!tokenResult.ok) {
|
|
11914
|
+
printError(tokenResult.error);
|
|
11915
|
+
process.exit(1);
|
|
11916
|
+
}
|
|
11917
|
+
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
11918
|
+
if (!urlResult.ok) {
|
|
11919
|
+
printError(urlResult.error);
|
|
11920
|
+
process.exit(1);
|
|
11921
|
+
}
|
|
11922
|
+
return { token: tokenResult.data.token, serviceUrl: urlResult.data };
|
|
11923
|
+
}
|
|
11924
|
+
function explain(error) {
|
|
11925
|
+
if (error.startsWith("FORBIDDEN")) {
|
|
11926
|
+
printInfo(' Sessions belong to a logged-in account \u2014 run "verity login" first.');
|
|
11927
|
+
} else if (error.startsWith("INVALID_TOKEN")) {
|
|
11928
|
+
printInfo(' This login has expired or was revoked \u2014 run "verity login" to sign in again.');
|
|
11929
|
+
}
|
|
11930
|
+
}
|
|
11931
|
+
function registerSessionsCommands(program2) {
|
|
11932
|
+
const sessions = program2.command("sessions").description("List and revoke your Verity logins (one per machine)");
|
|
11933
|
+
sessions.command("list").description("List your active logins \u2014 device, last use, and expiry").option("--json", "Output raw JSON").action(async (opts) => {
|
|
11934
|
+
const globals = program2.opts();
|
|
11935
|
+
const { token, serviceUrl } = await auth(globals);
|
|
11936
|
+
const result = await apiRequest({
|
|
11937
|
+
method: "GET",
|
|
11938
|
+
path: "/auth/sessions",
|
|
11939
|
+
serviceUrl,
|
|
11940
|
+
token,
|
|
11941
|
+
verbose: globals.verbose,
|
|
11942
|
+
cmd: "sessions"
|
|
11943
|
+
});
|
|
11944
|
+
if (!result.ok) {
|
|
11945
|
+
printError(result.error);
|
|
11946
|
+
explain(result.error);
|
|
11947
|
+
process.exit(1);
|
|
11948
|
+
}
|
|
11949
|
+
if (opts.json) {
|
|
11950
|
+
printJson(result.data);
|
|
11951
|
+
return;
|
|
11952
|
+
}
|
|
11953
|
+
const list = result.data.sessions;
|
|
11954
|
+
if (list.length === 0) {
|
|
11955
|
+
printInfo('No active logins. (Run "verity login".)');
|
|
11956
|
+
return;
|
|
11957
|
+
}
|
|
11958
|
+
printInfo(`${list.length} active login${list.length === 1 ? "" : "s"}:`);
|
|
11959
|
+
printInfo("");
|
|
11960
|
+
printInfo(`${"SESSION ID".padEnd(38)}${"DEVICE".padEnd(24)}${"CREATED".padEnd(12)}${"LAST USED".padEnd(12)}EXPIRES`);
|
|
11961
|
+
for (const s of list) {
|
|
11962
|
+
const days = daysUntil(s.expires_at);
|
|
11963
|
+
const expiry = s.expires_at ? `${shortDate(s.expires_at)}${days != null ? ` (${days}d)` : ""}` : "never";
|
|
11964
|
+
const device = (s.device ?? "login").slice(0, 22);
|
|
11965
|
+
printInfo(
|
|
11966
|
+
`${s.id.padEnd(38)}${device.padEnd(24)}${shortDate(s.created_at).padEnd(12)}${shortDate(s.last_used_at).padEnd(12)}${expiry}${s.current ? " \u2190 this machine" : ""}`
|
|
11967
|
+
);
|
|
11968
|
+
}
|
|
11969
|
+
printInfo("");
|
|
11970
|
+
printInfo("Revoke one: verity sessions revoke <session-id>");
|
|
11971
|
+
printInfo("Sign out everywhere else: verity logout --others");
|
|
11972
|
+
});
|
|
11973
|
+
sessions.command("revoke <session-id>").description("Revoke one login. Revoking this machine's also clears the local credential").action(async (sessionId) => {
|
|
11974
|
+
const globals = program2.opts();
|
|
11975
|
+
const { token, serviceUrl } = await auth(globals);
|
|
11976
|
+
const result = await apiRequest({
|
|
11977
|
+
method: "DELETE",
|
|
11978
|
+
path: `/auth/sessions/${encodeURIComponent(sessionId)}`,
|
|
11979
|
+
serviceUrl,
|
|
11980
|
+
token,
|
|
11981
|
+
verbose: globals.verbose,
|
|
11982
|
+
cmd: "sessions-revoke"
|
|
11983
|
+
});
|
|
11984
|
+
if (!result.ok) {
|
|
11985
|
+
printError(result.error);
|
|
11986
|
+
if (result.http_status === 404) {
|
|
11987
|
+
printInfo(' No session with that id on your account \u2014 check "verity sessions list".');
|
|
11988
|
+
}
|
|
11989
|
+
explain(result.error);
|
|
11990
|
+
process.exit(1);
|
|
11991
|
+
}
|
|
11992
|
+
printInfo(`Session ${sessionId} revoked. \u2713`);
|
|
11993
|
+
if (result.data.was_current) {
|
|
11994
|
+
const cleared = await removeGlobalCredential("");
|
|
11995
|
+
printInfo(cleared ? ' That was this machine \u2014 local credential cleared. Run "verity login" to sign back in.' : ' That was this machine. Run "verity login" to sign back in.');
|
|
11996
|
+
}
|
|
11997
|
+
});
|
|
11998
|
+
}
|
|
11999
|
+
function registerLogoutCommand(program2) {
|
|
12000
|
+
program2.command("logout").description("Sign out of Verity on this machine (--all / --others for every machine)").option("--all", "Revoke every login on every machine, including this one").option("--others", "Revoke every login EXCEPT this machine (e.g. a lost laptop)").action(async (opts) => {
|
|
12001
|
+
const globals = program2.opts();
|
|
12002
|
+
if (opts.all && opts.others) {
|
|
12003
|
+
printError("Use either --all or --others, not both.");
|
|
12004
|
+
process.exit(1);
|
|
12005
|
+
}
|
|
12006
|
+
const { token, serviceUrl } = await auth(globals);
|
|
12007
|
+
if (opts.all || opts.others) {
|
|
12008
|
+
const result = await apiRequest({
|
|
12009
|
+
method: "DELETE",
|
|
12010
|
+
path: opts.others ? "/auth/sessions?others=true" : "/auth/sessions",
|
|
12011
|
+
serviceUrl,
|
|
12012
|
+
token,
|
|
12013
|
+
verbose: globals.verbose,
|
|
12014
|
+
cmd: "logout"
|
|
12015
|
+
});
|
|
12016
|
+
if (!result.ok) {
|
|
12017
|
+
printError(result.error);
|
|
12018
|
+
explain(result.error);
|
|
12019
|
+
process.exit(1);
|
|
12020
|
+
}
|
|
12021
|
+
const n = result.data.revoked;
|
|
12022
|
+
printInfo(`Revoked ${n} login${n === 1 ? "" : "s"}. \u2713`);
|
|
12023
|
+
if (opts.others) {
|
|
12024
|
+
printInfo(" This machine is still signed in.");
|
|
12025
|
+
return;
|
|
12026
|
+
}
|
|
12027
|
+
const cleared2 = await removeGlobalCredential("");
|
|
12028
|
+
if (cleared2) printInfo(" Local credential cleared.");
|
|
12029
|
+
printInfo(' Run "verity login" to sign back in.');
|
|
12030
|
+
return;
|
|
12031
|
+
}
|
|
12032
|
+
const list = await apiRequest({
|
|
12033
|
+
method: "GET",
|
|
12034
|
+
path: "/auth/sessions",
|
|
12035
|
+
serviceUrl,
|
|
12036
|
+
token,
|
|
12037
|
+
verbose: globals.verbose,
|
|
12038
|
+
cmd: "logout"
|
|
12039
|
+
});
|
|
12040
|
+
if (!list.ok) {
|
|
12041
|
+
printError(list.error);
|
|
12042
|
+
explain(list.error);
|
|
11533
12043
|
process.exit(1);
|
|
11534
12044
|
}
|
|
11535
|
-
const
|
|
11536
|
-
|
|
11537
|
-
|
|
11538
|
-
|
|
11539
|
-
|
|
12045
|
+
const current = list.data.sessions.find((s) => s.current);
|
|
12046
|
+
if (!current) {
|
|
12047
|
+
printWarn("This machine is not signed in with a Verity login.");
|
|
12048
|
+
const cleared2 = await removeGlobalCredential("");
|
|
12049
|
+
if (cleared2) printInfo(" Cleared the local login credential anyway.");
|
|
12050
|
+
return;
|
|
12051
|
+
}
|
|
12052
|
+
const revoked = await apiRequest({
|
|
12053
|
+
method: "DELETE",
|
|
12054
|
+
path: `/auth/sessions/${current.id}`,
|
|
12055
|
+
serviceUrl,
|
|
12056
|
+
token,
|
|
12057
|
+
verbose: globals.verbose,
|
|
12058
|
+
cmd: "logout"
|
|
12059
|
+
});
|
|
12060
|
+
if (!revoked.ok) {
|
|
12061
|
+
printError(revoked.error);
|
|
12062
|
+
explain(revoked.error);
|
|
12063
|
+
process.exit(1);
|
|
11540
12064
|
}
|
|
12065
|
+
const cleared = await removeGlobalCredential("");
|
|
12066
|
+
printInfo("Signed out on this machine. \u2713");
|
|
12067
|
+
if (cleared) printInfo(" Local credential cleared.");
|
|
12068
|
+
printInfo(' Your other machines are unaffected \u2014 use "verity logout --all" for all of them.');
|
|
11541
12069
|
});
|
|
11542
12070
|
}
|
|
11543
12071
|
|
|
11544
12072
|
// src/lib/hooks.ts
|
|
11545
12073
|
var import_promises4 = require("node:fs/promises");
|
|
11546
|
-
var
|
|
12074
|
+
var import_node_path5 = require("node:path");
|
|
11547
12075
|
|
|
11548
12076
|
// src/lib/json-file.ts
|
|
11549
12077
|
var import_promises3 = require("node:fs/promises");
|
|
11550
|
-
var
|
|
12078
|
+
var import_node_path4 = require("node:path");
|
|
11551
12079
|
function jsonSemanticEqual(a, b) {
|
|
11552
12080
|
if (a === b) return true;
|
|
11553
12081
|
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) {
|
|
@@ -11593,7 +12121,7 @@ async function writeJsonFilePreservingStyle(file, value) {
|
|
|
11593
12121
|
const indent = currentRaw !== null ? detectJsonIndent(currentRaw) : 2;
|
|
11594
12122
|
const next = JSON.stringify(value, null, indent) + "\n";
|
|
11595
12123
|
if (next === currentRaw) return false;
|
|
11596
|
-
await (0, import_promises3.mkdir)((0,
|
|
12124
|
+
await (0, import_promises3.mkdir)((0, import_node_path4.dirname)(file), { recursive: true });
|
|
11597
12125
|
await (0, import_promises3.writeFile)(file, next);
|
|
11598
12126
|
return true;
|
|
11599
12127
|
}
|
|
@@ -11766,13 +12294,13 @@ async function writeSettings(settings) {
|
|
|
11766
12294
|
}
|
|
11767
12295
|
async function readSettingsAt(root) {
|
|
11768
12296
|
try {
|
|
11769
|
-
return JSON.parse(await (0, import_promises4.readFile)((0,
|
|
12297
|
+
return JSON.parse(await (0, import_promises4.readFile)((0, import_node_path5.join)(root, CLAUDE_SETTINGS_FILE), "utf-8"));
|
|
11770
12298
|
} catch {
|
|
11771
12299
|
return {};
|
|
11772
12300
|
}
|
|
11773
12301
|
}
|
|
11774
12302
|
async function writeSettingsAt(root, settings) {
|
|
11775
|
-
await writeJsonFilePreservingStyle((0,
|
|
12303
|
+
await writeJsonFilePreservingStyle((0, import_node_path5.join)(root, CLAUDE_SETTINGS_FILE), settings);
|
|
11776
12304
|
}
|
|
11777
12305
|
async function hasLegacyHooksAt(root) {
|
|
11778
12306
|
const settings = await readSettingsAt(root);
|
|
@@ -12045,7 +12573,7 @@ var import_node_crypto6 = require("node:crypto");
|
|
|
12045
12573
|
// src/lib/conversation-buffer.ts
|
|
12046
12574
|
var import_promises5 = require("node:fs/promises");
|
|
12047
12575
|
var import_node_fs4 = require("node:fs");
|
|
12048
|
-
var
|
|
12576
|
+
var import_node_child_process5 = require("node:child_process");
|
|
12049
12577
|
var import_node_crypto = require("node:crypto");
|
|
12050
12578
|
function stripImageReferences(text) {
|
|
12051
12579
|
return text.replace(/\[Image #\d+\]/g, "[screenshot \u2014 not available for review]");
|
|
@@ -12148,7 +12676,7 @@ async function readBufferEntries() {
|
|
|
12148
12676
|
}
|
|
12149
12677
|
function getRecentCommitMessages() {
|
|
12150
12678
|
try {
|
|
12151
|
-
const output = (0,
|
|
12679
|
+
const output = (0, import_node_child_process5.execSync)(
|
|
12152
12680
|
'git log --since="30 minutes ago" --format="%s" -5',
|
|
12153
12681
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
12154
12682
|
).trim();
|
|
@@ -12162,8 +12690,8 @@ function getRecentCommitMessages() {
|
|
|
12162
12690
|
// src/lib/context-identity.ts
|
|
12163
12691
|
var import_node_crypto2 = require("node:crypto");
|
|
12164
12692
|
var import_node_fs5 = require("node:fs");
|
|
12165
|
-
var
|
|
12166
|
-
var
|
|
12693
|
+
var import_node_os2 = require("node:os");
|
|
12694
|
+
var import_node_path6 = require("node:path");
|
|
12167
12695
|
var SHARED_SENTINELS = /* @__PURE__ */ new Set([
|
|
12168
12696
|
"",
|
|
12169
12697
|
"-",
|
|
@@ -12217,13 +12745,13 @@ function contextIdentity(input) {
|
|
|
12217
12745
|
}
|
|
12218
12746
|
function verityHome() {
|
|
12219
12747
|
const override = process.env.VERITY_HOME;
|
|
12220
|
-
return override && override.trim() ? (0,
|
|
12748
|
+
return override && override.trim() ? (0, import_node_path6.resolve)(override) : (0, import_node_path6.join)((0, import_node_os2.homedir)(), ".verity");
|
|
12221
12749
|
}
|
|
12222
12750
|
function dossierDir(identity) {
|
|
12223
|
-
return (0,
|
|
12751
|
+
return (0, import_node_path6.join)(verityHome(), "sessions", identity.userKey, identity.treeKey, identity.sessionKey);
|
|
12224
12752
|
}
|
|
12225
12753
|
function treeDir(identity) {
|
|
12226
|
-
return (0,
|
|
12754
|
+
return (0, import_node_path6.join)(verityHome(), "sessions", identity.userKey, identity.treeKey);
|
|
12227
12755
|
}
|
|
12228
12756
|
function scopeIdentity(token, sessionId) {
|
|
12229
12757
|
const t = (token ?? "").trim();
|
|
@@ -12239,7 +12767,7 @@ function sessionScopeKey(token, sessionId) {
|
|
|
12239
12767
|
// src/lib/task-context-buffer.ts
|
|
12240
12768
|
var import_promises6 = require("node:fs/promises");
|
|
12241
12769
|
var import_node_fs6 = require("node:fs");
|
|
12242
|
-
var
|
|
12770
|
+
var import_node_path7 = require("node:path");
|
|
12243
12771
|
var TASK_CONTEXT_DIR = `${VERITY_DIR}/.task-context`;
|
|
12244
12772
|
var MAX_BUFFER_BYTES = 500 * 1024;
|
|
12245
12773
|
var MAX_PROMPT_CHARS = 2e3;
|
|
@@ -12317,7 +12845,7 @@ async function cleanupTaskContextBuffers() {
|
|
|
12317
12845
|
const cutoffMs = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
12318
12846
|
for (const file of files) {
|
|
12319
12847
|
if (!file.endsWith(".jsonl")) continue;
|
|
12320
|
-
const filePath = (0,
|
|
12848
|
+
const filePath = (0, import_node_path7.join)(TASK_CONTEXT_DIR, file);
|
|
12321
12849
|
try {
|
|
12322
12850
|
const stats = await (0, import_promises6.stat)(filePath);
|
|
12323
12851
|
if (stats.mtimeMs < cutoffMs) {
|
|
@@ -12331,7 +12859,7 @@ async function cleanupTaskContextBuffers() {
|
|
|
12331
12859
|
}
|
|
12332
12860
|
function bufferPath(taskId) {
|
|
12333
12861
|
const safe = taskId.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
12334
|
-
return (0,
|
|
12862
|
+
return (0, import_node_path7.join)(TASK_CONTEXT_DIR, `${safe}.jsonl`);
|
|
12335
12863
|
}
|
|
12336
12864
|
async function appendEntry(taskId, entry) {
|
|
12337
12865
|
try {
|
|
@@ -12357,7 +12885,7 @@ async function appendEntry(taskId, entry) {
|
|
|
12357
12885
|
// src/lib/memory-retrieval.ts
|
|
12358
12886
|
var import_promises7 = require("node:fs/promises");
|
|
12359
12887
|
var import_node_fs7 = require("node:fs");
|
|
12360
|
-
var
|
|
12888
|
+
var import_node_path8 = require("node:path");
|
|
12361
12889
|
var memoryDir = () => projectPath(`${VERITY_DIR}/memory`);
|
|
12362
12890
|
var DOMAINS = ["decisions", "quality", "security", "intent", "gotchas", "patterns", "domain", "integrations"];
|
|
12363
12891
|
var DEFAULT_BUDGET_TOKENS = 2e3;
|
|
@@ -12455,14 +12983,14 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
|
|
|
12455
12983
|
const promptTokens = tokenize(promptText);
|
|
12456
12984
|
const nodes = [];
|
|
12457
12985
|
for (const domain of DOMAINS) {
|
|
12458
|
-
const domainDir = (0,
|
|
12986
|
+
const domainDir = (0, import_node_path8.join)(memoryDir(), domain);
|
|
12459
12987
|
if (!(0, import_node_fs7.existsSync)(domainDir)) continue;
|
|
12460
12988
|
try {
|
|
12461
12989
|
const files = await (0, import_promises7.readdir)(domainDir);
|
|
12462
12990
|
for (const file of files) {
|
|
12463
12991
|
if (!file.endsWith(".md")) continue;
|
|
12464
12992
|
try {
|
|
12465
|
-
const content = await (0, import_promises7.readFile)((0,
|
|
12993
|
+
const content = await (0, import_promises7.readFile)((0, import_node_path8.join)(domainDir, file), "utf-8");
|
|
12466
12994
|
const { fm, body } = parseFrontmatter(content);
|
|
12467
12995
|
if (fm.status && fm.status !== "active") continue;
|
|
12468
12996
|
nodes.push({
|
|
@@ -12522,7 +13050,7 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
|
|
|
12522
13050
|
// src/lib/memory-sync.ts
|
|
12523
13051
|
var import_promises8 = require("node:fs/promises");
|
|
12524
13052
|
var import_node_fs8 = require("node:fs");
|
|
12525
|
-
var
|
|
13053
|
+
var import_node_path9 = require("node:path");
|
|
12526
13054
|
var import_node_crypto3 = require("node:crypto");
|
|
12527
13055
|
|
|
12528
13056
|
// src/lib/glob-match.ts
|
|
@@ -12593,16 +13121,16 @@ var syncStateFile = () => projectPath(`${VERITY_DIR}/.memory-sync-state.json`);
|
|
|
12593
13121
|
async function ensureMemoryDir() {
|
|
12594
13122
|
await (0, import_promises8.mkdir)(memoryDir2(), { recursive: true });
|
|
12595
13123
|
for (const domain of DOMAINS2) {
|
|
12596
|
-
await (0, import_promises8.mkdir)((0,
|
|
13124
|
+
await (0, import_promises8.mkdir)((0, import_node_path9.join)(memoryDir2(), domain), { recursive: true });
|
|
12597
13125
|
}
|
|
12598
|
-
if (!(0, import_node_fs8.existsSync)((0,
|
|
12599
|
-
await (0, import_promises8.writeFile)((0,
|
|
13126
|
+
if (!(0, import_node_fs8.existsSync)((0, import_node_path9.join)(memoryDir2(), "SCHEMA.md"))) {
|
|
13127
|
+
await (0, import_promises8.writeFile)((0, import_node_path9.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
|
|
12600
13128
|
}
|
|
12601
|
-
if (!(0, import_node_fs8.existsSync)((0,
|
|
12602
|
-
await (0, import_promises8.writeFile)((0,
|
|
13129
|
+
if (!(0, import_node_fs8.existsSync)((0, import_node_path9.join)(memoryDir2(), "index.md"))) {
|
|
13130
|
+
await (0, import_promises8.writeFile)((0, import_node_path9.join)(memoryDir2(), "index.md"), "# Project Memory Index\n\nNo nodes yet. Run an analysis to start building the knowledge graph.\n");
|
|
12603
13131
|
}
|
|
12604
|
-
if (!(0, import_node_fs8.existsSync)((0,
|
|
12605
|
-
await (0, import_promises8.writeFile)((0,
|
|
13132
|
+
if (!(0, import_node_fs8.existsSync)((0, import_node_path9.join)(memoryDir2(), "log.md"))) {
|
|
13133
|
+
await (0, import_promises8.writeFile)((0, import_node_path9.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
|
|
12606
13134
|
}
|
|
12607
13135
|
}
|
|
12608
13136
|
async function buildManifest() {
|
|
@@ -12611,14 +13139,14 @@ async function buildManifest() {
|
|
|
12611
13139
|
}
|
|
12612
13140
|
const nodes = [];
|
|
12613
13141
|
for (const domain of DOMAINS2) {
|
|
12614
|
-
const domainDir = (0,
|
|
13142
|
+
const domainDir = (0, import_node_path9.join)(memoryDir2(), domain);
|
|
12615
13143
|
if (!(0, import_node_fs8.existsSync)(domainDir)) continue;
|
|
12616
13144
|
try {
|
|
12617
13145
|
const files = await (0, import_promises8.readdir)(domainDir);
|
|
12618
13146
|
for (const file of files) {
|
|
12619
13147
|
if (!file.endsWith(".md")) continue;
|
|
12620
13148
|
const filePath = `${domain}/${file}`;
|
|
12621
|
-
const fullPath = (0,
|
|
13149
|
+
const fullPath = (0, import_node_path9.join)(memoryDir2(), filePath);
|
|
12622
13150
|
try {
|
|
12623
13151
|
const content = await (0, import_promises8.readFile)(fullPath, "utf-8");
|
|
12624
13152
|
const hash = (0, import_node_crypto3.createHash)("sha256").update(content).digest("hex").slice(0, 16);
|
|
@@ -12631,13 +13159,13 @@ async function buildManifest() {
|
|
|
12631
13159
|
}
|
|
12632
13160
|
let indexHash = null;
|
|
12633
13161
|
try {
|
|
12634
|
-
const indexContent = await (0, import_promises8.readFile)((0,
|
|
13162
|
+
const indexContent = await (0, import_promises8.readFile)((0, import_node_path9.join)(memoryDir2(), "index.md"), "utf-8");
|
|
12635
13163
|
indexHash = `sha256:${(0, import_node_crypto3.createHash)("sha256").update(indexContent).digest("hex").slice(0, 16)}`;
|
|
12636
13164
|
} catch {
|
|
12637
13165
|
}
|
|
12638
13166
|
let logLength = 0;
|
|
12639
13167
|
try {
|
|
12640
|
-
const logContent = await (0, import_promises8.readFile)((0,
|
|
13168
|
+
const logContent = await (0, import_promises8.readFile)((0, import_node_path9.join)(memoryDir2(), "log.md"), "utf-8");
|
|
12641
13169
|
logLength = logContent.split("\n").length;
|
|
12642
13170
|
} catch {
|
|
12643
13171
|
}
|
|
@@ -12650,13 +13178,13 @@ async function readOnDiskNodes() {
|
|
|
12650
13178
|
const out = /* @__PURE__ */ new Map();
|
|
12651
13179
|
if (!(0, import_node_fs8.existsSync)(memoryDir2())) return out;
|
|
12652
13180
|
for (const domain of DOMAINS2) {
|
|
12653
|
-
const domainDir = (0,
|
|
13181
|
+
const domainDir = (0, import_node_path9.join)(memoryDir2(), domain);
|
|
12654
13182
|
if (!(0, import_node_fs8.existsSync)(domainDir)) continue;
|
|
12655
13183
|
try {
|
|
12656
13184
|
for (const file of await (0, import_promises8.readdir)(domainDir)) {
|
|
12657
13185
|
if (!file.endsWith(".md")) continue;
|
|
12658
13186
|
try {
|
|
12659
|
-
out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0,
|
|
13187
|
+
out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0, import_node_path9.join)(domainDir, file), "utf-8")));
|
|
12660
13188
|
} catch {
|
|
12661
13189
|
}
|
|
12662
13190
|
}
|
|
@@ -12702,7 +13230,7 @@ async function computeEditedNodeUploads() {
|
|
|
12702
13230
|
const uploads = [];
|
|
12703
13231
|
for (const [path, prevHash] of prev) {
|
|
12704
13232
|
if (prevHash == null) continue;
|
|
12705
|
-
const full = (0,
|
|
13233
|
+
const full = (0, import_node_path9.join)(memoryDir2(), path);
|
|
12706
13234
|
if (!(0, import_node_fs8.existsSync)(full)) continue;
|
|
12707
13235
|
let content;
|
|
12708
13236
|
try {
|
|
@@ -12739,15 +13267,15 @@ async function applyMemoryWrites(writes, opts = {}) {
|
|
|
12739
13267
|
const logLines = [`- ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)} \u2014 Applied ${count} write(s) from server`];
|
|
12740
13268
|
for (const n of notes) logLines.push(` - ${n}`);
|
|
12741
13269
|
try {
|
|
12742
|
-
const existing = (0, import_node_fs8.existsSync)((0,
|
|
12743
|
-
await (0, import_promises8.writeFile)((0,
|
|
13270
|
+
const existing = (0, import_node_fs8.existsSync)((0, import_node_path9.join)(memoryDir2(), "log.md")) ? await (0, import_promises8.readFile)((0, import_node_path9.join)(memoryDir2(), "log.md"), "utf-8") : "# Memory Log\n\n";
|
|
13271
|
+
await (0, import_promises8.writeFile)((0, import_node_path9.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
|
|
12744
13272
|
} catch {
|
|
12745
13273
|
}
|
|
12746
13274
|
await recordSyncedNodePaths();
|
|
12747
13275
|
return count;
|
|
12748
13276
|
}
|
|
12749
13277
|
async function applyOneWrite(write, treePaths) {
|
|
12750
|
-
const fullPath = (0,
|
|
13278
|
+
const fullPath = (0, import_node_path9.join)(memoryDir2(), write.path);
|
|
12751
13279
|
const notes = [];
|
|
12752
13280
|
let content = write.content;
|
|
12753
13281
|
if (treePaths && treePaths.length > 0) {
|
|
@@ -12769,7 +13297,7 @@ async function applyOneWrite(write, treePaths) {
|
|
|
12769
13297
|
return { written: false, notes };
|
|
12770
13298
|
}
|
|
12771
13299
|
}
|
|
12772
|
-
await (0, import_promises8.mkdir)((0,
|
|
13300
|
+
await (0, import_promises8.mkdir)((0, import_node_path9.dirname)(fullPath), { recursive: true });
|
|
12773
13301
|
await (0, import_promises8.writeFile)(fullPath, content);
|
|
12774
13302
|
return { written: true, notes };
|
|
12775
13303
|
}
|
|
@@ -12810,7 +13338,7 @@ async function regenerateIndex() {
|
|
|
12810
13338
|
];
|
|
12811
13339
|
let totalNodes = 0;
|
|
12812
13340
|
for (const domain of DOMAINS2.filter((d) => d !== "_archive")) {
|
|
12813
|
-
const domainDir = (0,
|
|
13341
|
+
const domainDir = (0, import_node_path9.join)(memoryDir2(), domain);
|
|
12814
13342
|
if (!(0, import_node_fs8.existsSync)(domainDir)) continue;
|
|
12815
13343
|
try {
|
|
12816
13344
|
const files = await (0, import_promises8.readdir)(domainDir);
|
|
@@ -12821,7 +13349,7 @@ async function regenerateIndex() {
|
|
|
12821
13349
|
for (const file of mdFiles.sort()) {
|
|
12822
13350
|
const slug = file.replace(/\.md$/, "");
|
|
12823
13351
|
try {
|
|
12824
|
-
const content = await (0, import_promises8.readFile)((0,
|
|
13352
|
+
const content = await (0, import_promises8.readFile)((0, import_node_path9.join)(domainDir, file), "utf-8");
|
|
12825
13353
|
const title = pickFrontmatter(content, "title") ?? slug;
|
|
12826
13354
|
const kind = pickFrontmatter(content, "kind") ?? "-";
|
|
12827
13355
|
const confidence = pickFrontmatter(content, "confidence");
|
|
@@ -12845,7 +13373,7 @@ async function regenerateIndex() {
|
|
|
12845
13373
|
lines.push("No nodes yet. Run an analysis to start building the knowledge graph.");
|
|
12846
13374
|
}
|
|
12847
13375
|
const next = lines.join("\n") + "\n";
|
|
12848
|
-
const indexPath = (0,
|
|
13376
|
+
const indexPath = (0, import_node_path9.join)(memoryDir2(), "index.md");
|
|
12849
13377
|
let existing = null;
|
|
12850
13378
|
try {
|
|
12851
13379
|
existing = await (0, import_promises8.readFile)(indexPath, "utf-8");
|
|
@@ -12927,7 +13455,7 @@ function hasLegacyMemoryBlock(text) {
|
|
|
12927
13455
|
return findMarker(text, LEGACY_MD_START) !== -1;
|
|
12928
13456
|
}
|
|
12929
13457
|
async function ensureClaudeMdPointer(cwd = repoRoot()) {
|
|
12930
|
-
const claudeMdPath = (0,
|
|
13458
|
+
const claudeMdPath = (0, import_node_path9.join)(cwd, "CLAUDE.md");
|
|
12931
13459
|
let existing = "";
|
|
12932
13460
|
if ((0, import_node_fs8.existsSync)(claudeMdPath)) {
|
|
12933
13461
|
existing = await (0, import_promises8.readFile)(claudeMdPath, "utf-8");
|
|
@@ -13061,12 +13589,76 @@ Body content (\u22648KB). Use [[node-id]] wikilinks for cross-references.
|
|
|
13061
13589
|
// src/lib/dossier-session.ts
|
|
13062
13590
|
var import_node_fs10 = require("node:fs");
|
|
13063
13591
|
var import_node_crypto5 = require("node:crypto");
|
|
13064
|
-
var
|
|
13592
|
+
var import_node_path11 = require("node:path");
|
|
13593
|
+
|
|
13594
|
+
// src/lib/skip-detection.ts
|
|
13595
|
+
function isBareAckPrompt(prompt) {
|
|
13596
|
+
if (typeof prompt !== "string") return false;
|
|
13597
|
+
const trimmed = prompt.trim();
|
|
13598
|
+
if (trimmed.length === 0) return false;
|
|
13599
|
+
if (trimmed.length > 20) return false;
|
|
13600
|
+
const bareAckPattern = /^(\d{1,2}|y|n|yes|no|yep|nope|ok(ay)?|sure|skip|cancel|stop|done|noted|got\s+it|sounds\s+good|thanks|thank\s+you|thx)[.!?]*$/i;
|
|
13601
|
+
return bareAckPattern.test(trimmed);
|
|
13602
|
+
}
|
|
13603
|
+
function isContinuationPrompt(prompt) {
|
|
13604
|
+
if (typeof prompt !== "string") return false;
|
|
13605
|
+
const trimmed = prompt.trim();
|
|
13606
|
+
if (trimmed.length === 0) return false;
|
|
13607
|
+
if (trimmed.length > 24) return false;
|
|
13608
|
+
const continuation = /^(let['’]?s\s+(go|do\s+it|start|continue)|go|go\s+ahead|go\s+on|proceed|continue|carry\s+on|keep\s+going|do\s+it|make\s+it\s+so|next|start|begin|ship\s+it|yes\s+please|please\s+continue|perfect|great|nice|excellent|agreed)[.!]*$/i;
|
|
13609
|
+
return continuation.test(trimmed) || isBareAckPrompt(trimmed);
|
|
13610
|
+
}
|
|
13611
|
+
function resolveGoalPrompt(prompts) {
|
|
13612
|
+
if (prompts.length === 0) return null;
|
|
13613
|
+
const latest = prompts[prompts.length - 1];
|
|
13614
|
+
if (!isContinuationPrompt(latest.prompt)) return { entry: latest, turnsBack: 0 };
|
|
13615
|
+
for (let i = prompts.length - 2; i >= 0; i--) {
|
|
13616
|
+
if (!isContinuationPrompt(prompts[i].prompt)) {
|
|
13617
|
+
return { entry: prompts[i], turnsBack: prompts.length - 1 - i };
|
|
13618
|
+
}
|
|
13619
|
+
}
|
|
13620
|
+
return { entry: latest, turnsBack: 0 };
|
|
13621
|
+
}
|
|
13622
|
+
function isReflectionQuestion(response) {
|
|
13623
|
+
if (!response || typeof response !== "string") return false;
|
|
13624
|
+
const markers = [
|
|
13625
|
+
/reflection\s+for\s+future\s+agents/i,
|
|
13626
|
+
/what(?:'s|\s+is)\s+one\s+thing\s+you\s+learned/i,
|
|
13627
|
+
/say\s+['"]?skip['"]?\s+to\s+skip/i,
|
|
13628
|
+
/quick\s+reflection\s+question/i,
|
|
13629
|
+
// Post-flip (VRT-21): the agent drafts the reflection itself and, when
|
|
13630
|
+
// interactive, asks the user to confirm/correct before recording. That
|
|
13631
|
+
// turn authors no code either, so it's still a reflection turn.
|
|
13632
|
+
/reflection\s+draft/i,
|
|
13633
|
+
/confirm,?\s+correct,?\s+or\s+add/i
|
|
13634
|
+
];
|
|
13635
|
+
return markers.some((m) => m.test(response));
|
|
13636
|
+
}
|
|
13637
|
+
function isMetaTaskLabel(label2) {
|
|
13638
|
+
if (label2 === null || label2 === void 0) return false;
|
|
13639
|
+
if (typeof label2 !== "string") return false;
|
|
13640
|
+
const trimmed = label2.trim();
|
|
13641
|
+
if (trimmed.length === 0) return true;
|
|
13642
|
+
const metaPatterns = [
|
|
13643
|
+
/^verity\s+[\w-]+\s+response$/i,
|
|
13644
|
+
// "Verity reflect response"
|
|
13645
|
+
/^simple user response$/i,
|
|
13646
|
+
/^verity\s+command$/i,
|
|
13647
|
+
// "Verity command"
|
|
13648
|
+
/^user\s+(question|reply|response|ack)$/i
|
|
13649
|
+
];
|
|
13650
|
+
return metaPatterns.some((p) => p.test(trimmed));
|
|
13651
|
+
}
|
|
13652
|
+
function shouldSkipForBareAck(input) {
|
|
13653
|
+
if (!isBareAckPrompt(input.prompt)) return false;
|
|
13654
|
+
if (input.turnAuthoredCode) return false;
|
|
13655
|
+
return input.canSeeTurnAuthorship;
|
|
13656
|
+
}
|
|
13065
13657
|
|
|
13066
13658
|
// src/lib/dossier.ts
|
|
13067
13659
|
var import_node_fs9 = require("node:fs");
|
|
13068
13660
|
var import_node_crypto4 = require("node:crypto");
|
|
13069
|
-
var
|
|
13661
|
+
var import_node_path10 = require("node:path");
|
|
13070
13662
|
var MAX_LINE_BYTES = 4096;
|
|
13071
13663
|
var MAX_GOAL_CHARS = 2e3;
|
|
13072
13664
|
var GOAL_KEEP = 8;
|
|
@@ -13109,9 +13701,9 @@ function openDossier(identity) {
|
|
|
13109
13701
|
return {
|
|
13110
13702
|
dir,
|
|
13111
13703
|
identity,
|
|
13112
|
-
eventsPath: (0,
|
|
13113
|
-
foldPath: (0,
|
|
13114
|
-
rotatedDir: (0,
|
|
13704
|
+
eventsPath: (0, import_node_path10.join)(dir, "events.jsonl"),
|
|
13705
|
+
foldPath: (0, import_node_path10.join)(dir, "fold.json"),
|
|
13706
|
+
rotatedDir: (0, import_node_path10.join)(dir, "rotated")
|
|
13115
13707
|
};
|
|
13116
13708
|
} catch {
|
|
13117
13709
|
return null;
|
|
@@ -13184,11 +13776,11 @@ function rotateIfNeeded2(d) {
|
|
|
13184
13776
|
if (!(0, import_node_fs9.existsSync)(d.eventsPath)) return;
|
|
13185
13777
|
if ((0, import_node_fs9.statSync)(d.eventsPath).size < ROTATE_BYTES) return;
|
|
13186
13778
|
(0, import_node_fs9.mkdirSync)(d.rotatedDir, { recursive: true, mode: 448 });
|
|
13187
|
-
(0, import_node_fs9.renameSync)(d.eventsPath, (0,
|
|
13779
|
+
(0, import_node_fs9.renameSync)(d.eventsPath, (0, import_node_path10.join)(d.rotatedDir, `events.${Date.now()}.jsonl`));
|
|
13188
13780
|
const kept = (0, import_node_fs9.readdirSync)(d.rotatedDir).filter((f) => f.endsWith(".jsonl")).sort();
|
|
13189
13781
|
for (const stale of kept.slice(0, Math.max(0, kept.length - ROTATE_KEEP))) {
|
|
13190
13782
|
try {
|
|
13191
|
-
(0, import_node_fs9.renameSync)((0,
|
|
13783
|
+
(0, import_node_fs9.renameSync)((0, import_node_path10.join)(d.rotatedDir, stale), (0, import_node_path10.join)(d.rotatedDir, `${stale}.pruned`));
|
|
13192
13784
|
} catch {
|
|
13193
13785
|
}
|
|
13194
13786
|
}
|
|
@@ -13250,7 +13842,7 @@ function foldDossier(d, opts = {}) {
|
|
|
13250
13842
|
state.meta.rotations = files.length;
|
|
13251
13843
|
for (const f of files) {
|
|
13252
13844
|
try {
|
|
13253
|
-
ingest((0, import_node_fs9.readFileSync)((0,
|
|
13845
|
+
ingest((0, import_node_fs9.readFileSync)((0, import_node_path10.join)(d.rotatedDir, f), "utf8"));
|
|
13254
13846
|
} catch {
|
|
13255
13847
|
state.meta.dropped_lines++;
|
|
13256
13848
|
}
|
|
@@ -13311,6 +13903,13 @@ function reduce(state, events, now) {
|
|
|
13311
13903
|
});
|
|
13312
13904
|
break;
|
|
13313
13905
|
}
|
|
13906
|
+
case "goal_delivered": {
|
|
13907
|
+
const g = state.goal.find((x) => x.status === "active");
|
|
13908
|
+
if (!g) break;
|
|
13909
|
+
if (g.delivered) break;
|
|
13910
|
+
g.delivered = { at: ev.at, seq: ev.seq, summary: ev.summary };
|
|
13911
|
+
break;
|
|
13912
|
+
}
|
|
13314
13913
|
case "authored": {
|
|
13315
13914
|
authoredEvents++;
|
|
13316
13915
|
const e = byPath.get(ev.path) ?? {
|
|
@@ -13425,6 +14024,18 @@ function reduce(state, events, now) {
|
|
|
13425
14024
|
}
|
|
13426
14025
|
case "verdict": {
|
|
13427
14026
|
state.meta.last_verdict_seq = ev.seq;
|
|
14027
|
+
state.meta.channel = {
|
|
14028
|
+
emittedLast: ev.emitted === true,
|
|
14029
|
+
// Reset by ANY movement, so the counter measures a standstill rather
|
|
14030
|
+
// than session length.
|
|
14031
|
+
consecutiveIdle: ev.idle === false ? 0 : (state.meta.channel?.consecutiveIdle ?? 0) + 1
|
|
14032
|
+
};
|
|
14033
|
+
state.meta.last_adjudication = ev.intent_verdict ? { verdict: ev.intent_verdict, score: ev.intent_score ?? null, at: ev.at, decision: ev.decision } : void 0;
|
|
14034
|
+
if (ev.intent_sig) {
|
|
14035
|
+
state.meta.intent_repeat = state.meta.intent_repeat && state.meta.intent_repeat.sig === ev.intent_sig ? { sig: ev.intent_sig, consecutive: state.meta.intent_repeat.consecutive + 1 } : { sig: ev.intent_sig, consecutive: 1 };
|
|
14036
|
+
} else {
|
|
14037
|
+
state.meta.intent_repeat = void 0;
|
|
14038
|
+
}
|
|
13428
14039
|
state.meta.watermark = {
|
|
13429
14040
|
sha: ev.head_sha,
|
|
13430
14041
|
reviewed_hash: ev.watermark_sha,
|
|
@@ -13510,7 +14121,12 @@ function compactState(s) {
|
|
|
13510
14121
|
const ms = (iso) => Date.parse(iso) || 0;
|
|
13511
14122
|
return {
|
|
13512
14123
|
v: 1,
|
|
13513
|
-
|
|
14124
|
+
// ⚠ APPEND-ONLY POSITIONALLY. Indices 11-13 carry `delivered`; a cache row
|
|
14125
|
+
// written before it existed has length 11 and expands with `delivered`
|
|
14126
|
+
// absent, which is the correct reading of "nothing had been delivered yet".
|
|
14127
|
+
// Inserting rather than appending would silently re-interpret every existing
|
|
14128
|
+
// cached row.
|
|
14129
|
+
g: s.goal.map((g) => [g.seq, ms(g.at), g.hash, g.superseded_by, g.text ?? 0, g.text_len ?? 0, g.source ?? 0, g.status ?? 0, g.repeats ?? 0, g.truncated ? 1 : 0, g.collapsed ? 1 : 0, g.delivered ? ms(g.delivered.at) : 0, g.delivered?.seq ?? 0, g.delivered?.summary ?? 0]),
|
|
13514
14130
|
a: s.authored?.map((a) => [a.path, a.origin, a.edits, a.hunks, a.adds, a.dels, a.hash_now, a.hash_at_last_verdict, a.first_seq, a.last_seq]) ?? null,
|
|
13515
14131
|
n: s.not_mine?.map((n) => [n.path, n.reason, ms(n.at), n.head_sha]) ?? null,
|
|
13516
14132
|
t: s.statements.map((x) => [x.anchor_key, x.file, x.line, x.pattern_id, x.title_hash, x.register, x.line_sha, x.said_at_seq, ms(x.said_at), x.outcome, x.outcome_at ? ms(x.outcome_at) : 0, x.repeats, x.carried ? 1 : 0]),
|
|
@@ -13556,7 +14172,14 @@ function expandState(raw) {
|
|
|
13556
14172
|
...x[7] ? { status: x[7] } : {},
|
|
13557
14173
|
...x[8] ? { repeats: x[8] } : {},
|
|
13558
14174
|
...x[9] ? { truncated: true } : {},
|
|
13559
|
-
...x[10] ? { collapsed: true } : {}
|
|
14175
|
+
...x[10] ? { collapsed: true } : {},
|
|
14176
|
+
...x[11] ? {
|
|
14177
|
+
delivered: {
|
|
14178
|
+
at: iso(x[11]),
|
|
14179
|
+
seq: x[12] ?? 0,
|
|
14180
|
+
summary: x[13] || ""
|
|
14181
|
+
}
|
|
14182
|
+
} : {}
|
|
13560
14183
|
})),
|
|
13561
14184
|
authored: decodedAuthored,
|
|
13562
14185
|
// The cache stores the BOUNDED list, so this is the bounded list too. That
|
|
@@ -13770,9 +14393,18 @@ function projectMemory(state, opts) {
|
|
|
13770
14393
|
seq: active.seq,
|
|
13771
14394
|
superseded,
|
|
13772
14395
|
truncated: active.truncated === true,
|
|
13773
|
-
collapsed: state.meta.collapsed.goal ?? 0
|
|
14396
|
+
collapsed: state.meta.collapsed.goal ?? 0,
|
|
14397
|
+
...active.delivered && { delivered: active.delivered }
|
|
13774
14398
|
};
|
|
13775
14399
|
}
|
|
14400
|
+
if (opts.capture) {
|
|
14401
|
+
const missed = Math.max(0, opts.capture.seen - opts.capture.captured);
|
|
14402
|
+
p.capture = { seen: opts.capture.seen, captured: opts.capture.captured, missed };
|
|
14403
|
+
}
|
|
14404
|
+
if (state.meta.last_adjudication) {
|
|
14405
|
+
const a = state.meta.last_adjudication;
|
|
14406
|
+
p.last_adjudication = { verdict: a.verdict, score: a.score, at: a.at };
|
|
14407
|
+
}
|
|
13776
14408
|
const ageOf = (seq, carried) => carried ? "carried" : seq > lastVerdictSeq ? "this_turn" : "this_session";
|
|
13777
14409
|
if (opts.spoken.length > 0) {
|
|
13778
14410
|
p.statements = opts.spoken.map((s) => ({
|
|
@@ -14015,7 +14647,8 @@ function recall(d, input) {
|
|
|
14015
14647
|
continuity,
|
|
14016
14648
|
spoken: reanchored.spoken,
|
|
14017
14649
|
refused: reanchored.dropped.length,
|
|
14018
|
-
lastVerdictSeq
|
|
14650
|
+
lastVerdictSeq,
|
|
14651
|
+
...input.capture && { capture: input.capture }
|
|
14019
14652
|
});
|
|
14020
14653
|
return {
|
|
14021
14654
|
state: effective,
|
|
@@ -14046,16 +14679,16 @@ function foreignAuthoredPaths(identity, opts = {}) {
|
|
|
14046
14679
|
for (const entry of (0, import_node_fs10.readdirSync)(dir, { withFileTypes: true })) {
|
|
14047
14680
|
if (!entry.isDirectory()) continue;
|
|
14048
14681
|
if (entry.name === identity.sessionKey) continue;
|
|
14049
|
-
const log = (0,
|
|
14682
|
+
const log = (0, import_node_path11.join)(dir, entry.name, "events.jsonl");
|
|
14050
14683
|
try {
|
|
14051
14684
|
if (!(0, import_node_fs10.existsSync)(log)) continue;
|
|
14052
14685
|
if (now - (0, import_node_fs10.statSync)(log).mtimeMs > windowMs) continue;
|
|
14053
14686
|
const sib = {
|
|
14054
|
-
dir: (0,
|
|
14687
|
+
dir: (0, import_node_path11.join)(dir, entry.name),
|
|
14055
14688
|
identity,
|
|
14056
14689
|
eventsPath: log,
|
|
14057
|
-
foldPath: (0,
|
|
14058
|
-
rotatedDir: (0,
|
|
14690
|
+
foldPath: (0, import_node_path11.join)(dir, entry.name, "fold.json"),
|
|
14691
|
+
rotatedDir: (0, import_node_path11.join)(dir, entry.name, "rotated")
|
|
14059
14692
|
};
|
|
14060
14693
|
const state = readFoldCache(sib) ?? foldDossier(sib);
|
|
14061
14694
|
sessions++;
|
|
@@ -14083,22 +14716,22 @@ function pruneOldDossiers(identity, maxAgeMs = 7 * 24 * 60 * 60 * 1e3) {
|
|
|
14083
14716
|
let removed = 0;
|
|
14084
14717
|
try {
|
|
14085
14718
|
const mine = dossierDir(identity);
|
|
14086
|
-
const userDir = (0,
|
|
14719
|
+
const userDir = (0, import_node_path11.dirname)((0, import_node_path11.dirname)(mine));
|
|
14087
14720
|
if (!(0, import_node_fs10.existsSync)(userDir)) return 0;
|
|
14088
14721
|
const cutoff = Date.now() - maxAgeMs;
|
|
14089
14722
|
for (const tree of (0, import_node_fs10.readdirSync)(userDir, { withFileTypes: true })) {
|
|
14090
14723
|
if (!tree.isDirectory()) continue;
|
|
14091
|
-
const treePath = (0,
|
|
14724
|
+
const treePath = (0, import_node_path11.join)(userDir, tree.name);
|
|
14092
14725
|
let live = 0;
|
|
14093
14726
|
for (const entry of (0, import_node_fs10.readdirSync)(treePath, { withFileTypes: true })) {
|
|
14094
14727
|
if (!entry.isDirectory()) continue;
|
|
14095
|
-
const dir = (0,
|
|
14728
|
+
const dir = (0, import_node_path11.join)(treePath, entry.name);
|
|
14096
14729
|
if (dir === mine) {
|
|
14097
14730
|
live++;
|
|
14098
14731
|
continue;
|
|
14099
14732
|
}
|
|
14100
14733
|
try {
|
|
14101
|
-
const log = (0,
|
|
14734
|
+
const log = (0, import_node_path11.join)(dir, "events.jsonl");
|
|
14102
14735
|
const at = (0, import_node_fs10.existsSync)(log) ? (0, import_node_fs10.statSync)(log).mtimeMs : (0, import_node_fs10.statSync)(dir).mtimeMs;
|
|
14103
14736
|
if (at < cutoff) {
|
|
14104
14737
|
(0, import_node_fs10.rmSync)(dir, { recursive: true, force: true });
|
|
@@ -14126,7 +14759,19 @@ function sessionDossier(token, sessionId) {
|
|
|
14126
14759
|
const d = openDossier(identity);
|
|
14127
14760
|
return d ? { d, identity } : null;
|
|
14128
14761
|
}
|
|
14762
|
+
function hasActiveGoal(d) {
|
|
14763
|
+
try {
|
|
14764
|
+
if (!(0, import_node_fs10.existsSync)(d.eventsPath)) return false;
|
|
14765
|
+
return (0, import_node_fs10.readFileSync)(d.eventsPath, "utf8").includes('"k":"goal"');
|
|
14766
|
+
} catch {
|
|
14767
|
+
return false;
|
|
14768
|
+
}
|
|
14769
|
+
}
|
|
14129
14770
|
function recordGoal(d, prompt, source = "prompt") {
|
|
14771
|
+
if (source === "prompt" && isContinuationPrompt(prompt) && hasActiveGoal(d)) {
|
|
14772
|
+
appendEvent(d, { k: "goal_continue", text: prompt.slice(0, 64) });
|
|
14773
|
+
return;
|
|
14774
|
+
}
|
|
14130
14775
|
const text = prompt.slice(0, MAX_GOAL_CHARS);
|
|
14131
14776
|
appendEvent(d, {
|
|
14132
14777
|
k: "goal",
|
|
@@ -14141,7 +14786,7 @@ function recordTurn(d, t) {
|
|
|
14141
14786
|
for (const a of t.authored) {
|
|
14142
14787
|
const origin = a.owner === "subagent" ? "subagent" : "edit_tool";
|
|
14143
14788
|
const prior = t.known?.authored?.get(a.p);
|
|
14144
|
-
const hash = fileHash((0,
|
|
14789
|
+
const hash = fileHash((0, import_node_path11.join)(root, a.p));
|
|
14145
14790
|
const hunks = Math.max(0, a.h - (prior?.hunks ?? 0));
|
|
14146
14791
|
const adds = Math.max(0, a.a - (prior?.adds ?? 0));
|
|
14147
14792
|
const dels = Math.max(0, a.d - (prior?.dels ?? 0));
|
|
@@ -14174,7 +14819,7 @@ function recordTurn(d, t) {
|
|
|
14174
14819
|
}
|
|
14175
14820
|
const seenDivergence = t.known?.divergence ?? /* @__PURE__ */ new Set();
|
|
14176
14821
|
for (const u of t.unobserved) {
|
|
14177
|
-
const hash = fileHash((0,
|
|
14822
|
+
const hash = fileHash((0, import_node_path11.join)(root, u.p));
|
|
14178
14823
|
if (seenDivergence.has(divergenceKey(u.p, hash))) continue;
|
|
14179
14824
|
appendEvent(d, { k: "divergence", kind: "external_mutation", path: u.p, hash });
|
|
14180
14825
|
}
|
|
@@ -14198,12 +14843,16 @@ function toStatus(n) {
|
|
|
14198
14843
|
}
|
|
14199
14844
|
function recordVerdict(d, v) {
|
|
14200
14845
|
const root = repoRoot();
|
|
14846
|
+
if (v.intent?.verdict === "aligned") {
|
|
14847
|
+
const summary = (v.intent.implemented ?? "").trim().slice(0, 400);
|
|
14848
|
+
if (summary) appendEvent(d, { k: "goal_delivered", summary });
|
|
14849
|
+
}
|
|
14201
14850
|
const lines = /* @__PURE__ */ new Map();
|
|
14202
14851
|
for (const f of v.findings) {
|
|
14203
14852
|
if (!f.file || typeof f.line !== "number" || !f.pattern_id) continue;
|
|
14204
14853
|
if (!lines.has(f.file)) {
|
|
14205
14854
|
try {
|
|
14206
|
-
const abs = (0,
|
|
14855
|
+
const abs = (0, import_node_path11.join)(root, f.file);
|
|
14207
14856
|
lines.set(f.file, (0, import_node_fs10.existsSync)(abs) ? (0, import_node_fs10.readFileSync)(abs, "utf8").split("\n") : null);
|
|
14208
14857
|
} catch {
|
|
14209
14858
|
lines.set(f.file, null);
|
|
@@ -14225,15 +14874,32 @@ function recordVerdict(d, v) {
|
|
|
14225
14874
|
line_sha: at !== void 0 ? lineSha(at) : null
|
|
14226
14875
|
});
|
|
14227
14876
|
}
|
|
14877
|
+
const foldedNow = foldDossier(d);
|
|
14878
|
+
const sig = intentSignature(v.intent, {
|
|
14879
|
+
goalSeq: foldedNow.goal.find((g) => g.status === "active")?.seq ?? 0,
|
|
14880
|
+
idle: v.idle !== false
|
|
14881
|
+
});
|
|
14228
14882
|
appendEvent(d, {
|
|
14229
14883
|
k: "verdict",
|
|
14230
14884
|
run_id: v.runId,
|
|
14231
14885
|
head_sha: getCurrentCommit(),
|
|
14232
14886
|
watermark_sha: v.watermarkSha,
|
|
14233
14887
|
branch: v.branch,
|
|
14234
|
-
decision: v.decision
|
|
14888
|
+
decision: v.decision,
|
|
14889
|
+
...sig && { intent_sig: sig },
|
|
14890
|
+
emitted: v.emitted === true,
|
|
14891
|
+
idle: v.idle !== false,
|
|
14892
|
+
...v.intent?.verdict && { intent_verdict: v.intent.verdict },
|
|
14893
|
+
...typeof v.intent?.score === "number" && { intent_score: v.intent.score }
|
|
14235
14894
|
});
|
|
14236
14895
|
}
|
|
14896
|
+
function intentSignature(intent, ctx) {
|
|
14897
|
+
if (!intent?.verdict) return null;
|
|
14898
|
+
if (intent.verdict !== "misaligned" && intent.verdict !== "partial") return null;
|
|
14899
|
+
const goal = ctx ? `g${ctx.goalSeq}` : "g?";
|
|
14900
|
+
const moved = ctx?.idle === false ? "active" : "idle";
|
|
14901
|
+
return `${intent.verdict}:${goal}:${moved}`;
|
|
14902
|
+
}
|
|
14237
14903
|
function toRegister(severity) {
|
|
14238
14904
|
switch (severity) {
|
|
14239
14905
|
case "critical":
|
|
@@ -14269,7 +14935,9 @@ function recallMemory(d, identity, opts) {
|
|
|
14269
14935
|
const state = foldDossier(d);
|
|
14270
14936
|
const watermark = state.meta.watermark?.sha ?? null;
|
|
14271
14937
|
const watermarkPaths = (state.authored ?? []).map((a) => a.path);
|
|
14938
|
+
const captureCmp = typeof opts.userMessagesSeen === "number" ? { seen: opts.userMessagesSeen, captured: state.meta.goal_chain } : void 0;
|
|
14272
14939
|
const r = recall(d, {
|
|
14940
|
+
...captureCmp && { capture: captureCmp },
|
|
14273
14941
|
identity,
|
|
14274
14942
|
currentSessionKey: opts.currentSessionKey,
|
|
14275
14943
|
branchNow: getCurrentBranch(),
|
|
@@ -14278,7 +14946,7 @@ function recallMemory(d, identity, opts) {
|
|
|
14278
14946
|
budgetBytes: opts.budgetBytes,
|
|
14279
14947
|
readFileLines: (file) => {
|
|
14280
14948
|
try {
|
|
14281
|
-
const abs = (0,
|
|
14949
|
+
const abs = (0, import_node_path11.join)(root, file);
|
|
14282
14950
|
return (0, import_node_fs10.existsSync)(abs) ? (0, import_node_fs10.readFileSync)(abs, "utf8").split("\n") : null;
|
|
14283
14951
|
} catch {
|
|
14284
14952
|
return null;
|
|
@@ -14418,21 +15086,21 @@ async function fireClassify(prompt, sessionId, globals) {
|
|
|
14418
15086
|
|
|
14419
15087
|
// src/commands/lifecycle.ts
|
|
14420
15088
|
var import_node_fs14 = require("node:fs");
|
|
14421
|
-
var
|
|
15089
|
+
var import_node_path15 = require("node:path");
|
|
14422
15090
|
|
|
14423
15091
|
// src/lib/baseline.ts
|
|
14424
15092
|
var import_node_fs13 = require("node:fs");
|
|
14425
|
-
var
|
|
15093
|
+
var import_node_path14 = require("node:path");
|
|
14426
15094
|
var import_node_crypto7 = require("node:crypto");
|
|
14427
15095
|
|
|
14428
15096
|
// src/lib/snapshot.ts
|
|
14429
15097
|
var import_node_fs12 = require("node:fs");
|
|
14430
|
-
var
|
|
14431
|
-
var
|
|
15098
|
+
var import_node_path13 = require("node:path");
|
|
15099
|
+
var import_node_child_process6 = require("node:child_process");
|
|
14432
15100
|
|
|
14433
15101
|
// src/lib/files.ts
|
|
14434
15102
|
var import_node_fs11 = require("node:fs");
|
|
14435
|
-
var
|
|
15103
|
+
var import_node_path12 = require("node:path");
|
|
14436
15104
|
var LANG_MAP = {
|
|
14437
15105
|
// Analyzable (static analysis + Gemini)
|
|
14438
15106
|
ts: "typescript",
|
|
@@ -14500,7 +15168,7 @@ var LANG_MAP = {
|
|
|
14500
15168
|
mk: "make"
|
|
14501
15169
|
};
|
|
14502
15170
|
function detectLanguage(filepath) {
|
|
14503
|
-
const ext = (0,
|
|
15171
|
+
const ext = (0, import_node_path12.extname)(filepath).slice(1);
|
|
14504
15172
|
return LANG_MAP[ext] ?? ext;
|
|
14505
15173
|
}
|
|
14506
15174
|
function sortByMtime(files) {
|
|
@@ -14526,6 +15194,8 @@ function collectCodeDelta(files, opts) {
|
|
|
14526
15194
|
let totalSize = 0;
|
|
14527
15195
|
let truncationReason = null;
|
|
14528
15196
|
const droppedPaths = [];
|
|
15197
|
+
const excluded = [];
|
|
15198
|
+
const exclude = (path, reason) => excluded.push({ path, reason, stage: "collectCodeDelta", kind: "capacity" });
|
|
14529
15199
|
for (const filepath of sorted) {
|
|
14530
15200
|
if (result.length >= maxFiles) {
|
|
14531
15201
|
truncationReason ??= "max_files";
|
|
@@ -14533,14 +15203,21 @@ function collectCodeDelta(files, opts) {
|
|
|
14533
15203
|
continue;
|
|
14534
15204
|
}
|
|
14535
15205
|
const resolved = resolveFile(filepath);
|
|
14536
|
-
if (!resolved)
|
|
15206
|
+
if (!resolved) {
|
|
15207
|
+
exclude(filepath, "path-not-resolvable");
|
|
15208
|
+
continue;
|
|
15209
|
+
}
|
|
14537
15210
|
let size;
|
|
14538
15211
|
try {
|
|
14539
15212
|
size = (0, import_node_fs11.statSync)(resolved).size;
|
|
14540
15213
|
} catch {
|
|
15214
|
+
exclude(filepath, "not-stattable");
|
|
15215
|
+
continue;
|
|
15216
|
+
}
|
|
15217
|
+
if (size > maxFileBytes) {
|
|
15218
|
+
exclude(filepath, `over-file-size-limit-${maxFileBytes}b`);
|
|
14541
15219
|
continue;
|
|
14542
15220
|
}
|
|
14543
|
-
if (size > maxFileBytes) continue;
|
|
14544
15221
|
if (totalSize + size > maxTotalBytes) {
|
|
14545
15222
|
truncationReason ??= "max_total_bytes";
|
|
14546
15223
|
const idx = sorted.indexOf(filepath);
|
|
@@ -14551,6 +15228,7 @@ function collectCodeDelta(files, opts) {
|
|
|
14551
15228
|
try {
|
|
14552
15229
|
content = (0, import_node_fs11.readFileSync)(resolved, "utf-8");
|
|
14553
15230
|
} catch {
|
|
15231
|
+
exclude(filepath, "not-readable");
|
|
14554
15232
|
continue;
|
|
14555
15233
|
}
|
|
14556
15234
|
totalSize += size;
|
|
@@ -14564,10 +15242,14 @@ function collectCodeDelta(files, opts) {
|
|
|
14564
15242
|
(sum, f) => sum + f.content.split("\n").length,
|
|
14565
15243
|
0
|
|
14566
15244
|
);
|
|
15245
|
+
for (const path of droppedPaths) {
|
|
15246
|
+
exclude(path, truncationReason === "max_files" ? "max-files-cap" : "max-total-bytes-cap");
|
|
15247
|
+
}
|
|
14567
15248
|
return {
|
|
14568
15249
|
files: result,
|
|
14569
15250
|
total_lines: totalLines,
|
|
14570
15251
|
total_files: result.length,
|
|
15252
|
+
excluded,
|
|
14571
15253
|
...truncationReason && {
|
|
14572
15254
|
truncated: {
|
|
14573
15255
|
reason: truncationReason,
|
|
@@ -14588,7 +15270,7 @@ function generateSnapshotDiffs(files) {
|
|
|
14588
15270
|
}
|
|
14589
15271
|
const diffs = [];
|
|
14590
15272
|
for (const file of files) {
|
|
14591
|
-
const snapshotPath = (0,
|
|
15273
|
+
const snapshotPath = (0, import_node_path13.join)(SNAPSHOT_DIR, file.path);
|
|
14592
15274
|
const language = file.language ?? detectLanguage(file.path);
|
|
14593
15275
|
if ((0, import_node_fs12.existsSync)(snapshotPath)) {
|
|
14594
15276
|
const oldContent = (0, import_node_fs12.readFileSync)(snapshotPath, "utf-8");
|
|
@@ -14615,21 +15297,21 @@ ${addedLines}`,
|
|
|
14615
15297
|
function saveSnapshots(files) {
|
|
14616
15298
|
const snapshotPaths = /* @__PURE__ */ new Set();
|
|
14617
15299
|
for (const file of files) {
|
|
14618
|
-
const snapshotPath = (0,
|
|
15300
|
+
const snapshotPath = (0, import_node_path13.join)(SNAPSHOT_DIR, file.path);
|
|
14619
15301
|
snapshotPaths.add(snapshotPath);
|
|
14620
|
-
(0, import_node_fs12.mkdirSync)((0,
|
|
15302
|
+
(0, import_node_fs12.mkdirSync)((0, import_node_path13.dirname)(snapshotPath), { recursive: true });
|
|
14621
15303
|
(0, import_node_fs12.writeFileSync)(snapshotPath, file.content);
|
|
14622
15304
|
}
|
|
14623
15305
|
cleanStaleSnapshots(SNAPSHOT_DIR, snapshotPaths);
|
|
14624
15306
|
}
|
|
14625
15307
|
function computeDiff(oldContent, newContent, filePath) {
|
|
14626
|
-
const tmpOld = (0,
|
|
14627
|
-
const tmpNew = (0,
|
|
15308
|
+
const tmpOld = (0, import_node_path13.join)(SNAPSHOT_DIR, ".diff-old.tmp");
|
|
15309
|
+
const tmpNew = (0, import_node_path13.join)(SNAPSHOT_DIR, ".diff-new.tmp");
|
|
14628
15310
|
try {
|
|
14629
15311
|
(0, import_node_fs12.mkdirSync)(SNAPSHOT_DIR, { recursive: true });
|
|
14630
15312
|
(0, import_node_fs12.writeFileSync)(tmpOld, oldContent);
|
|
14631
15313
|
(0, import_node_fs12.writeFileSync)(tmpNew, newContent);
|
|
14632
|
-
const result = (0,
|
|
15314
|
+
const result = (0, import_node_child_process6.execSync)(
|
|
14633
15315
|
`git diff --no-index --unified=10 -- "${tmpOld}" "${tmpNew}"`,
|
|
14634
15316
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
14635
15317
|
);
|
|
@@ -14657,7 +15339,7 @@ function cleanStaleSnapshots(dir, keepSet) {
|
|
|
14657
15339
|
const entries = (0, import_node_fs12.readdirSync)(dir, { withFileTypes: true });
|
|
14658
15340
|
for (const entry of entries) {
|
|
14659
15341
|
if (entry.name.startsWith(".")) continue;
|
|
14660
|
-
const fullPath = (0,
|
|
15342
|
+
const fullPath = (0, import_node_path13.join)(dir, entry.name);
|
|
14661
15343
|
if (entry.isDirectory()) {
|
|
14662
15344
|
cleanStaleSnapshots(fullPath, keepSet);
|
|
14663
15345
|
try {
|
|
@@ -14686,13 +15368,13 @@ function sessionKey(sessionId) {
|
|
|
14686
15368
|
return (0, import_node_crypto7.createHash)("sha256").update(sessionId).digest("hex").slice(0, 16);
|
|
14687
15369
|
}
|
|
14688
15370
|
function sessionDir(key) {
|
|
14689
|
-
return (0,
|
|
15371
|
+
return (0, import_node_path14.join)(projectPath(BASELINE_DIR), key);
|
|
14690
15372
|
}
|
|
14691
15373
|
function manifestPath(dir) {
|
|
14692
|
-
return (0,
|
|
15374
|
+
return (0, import_node_path14.join)(dir, "manifest.json");
|
|
14693
15375
|
}
|
|
14694
15376
|
function mirrorPath(dir, repoRelPath) {
|
|
14695
|
-
return (0,
|
|
15377
|
+
return (0, import_node_path14.join)(dir, "files", repoRelPath);
|
|
14696
15378
|
}
|
|
14697
15379
|
var CARRY_FILE = `${BASELINE_DIR}/.carry`;
|
|
14698
15380
|
var CARRY_WINDOW_MS = 12e4;
|
|
@@ -14754,7 +15436,7 @@ function captureBaseline(opts = {}) {
|
|
|
14754
15436
|
(0, import_node_fs13.rmSync)(dir, { recursive: true, force: true });
|
|
14755
15437
|
} catch {
|
|
14756
15438
|
}
|
|
14757
|
-
const filesDir = (0,
|
|
15439
|
+
const filesDir = (0, import_node_path14.join)(dir, "files");
|
|
14758
15440
|
const mirrored = [];
|
|
14759
15441
|
try {
|
|
14760
15442
|
(0, import_node_fs13.mkdirSync)(filesDir, { recursive: true });
|
|
@@ -14764,7 +15446,7 @@ function captureBaseline(opts = {}) {
|
|
|
14764
15446
|
if (content === null) continue;
|
|
14765
15447
|
const dest = mirrorPath(dir, p);
|
|
14766
15448
|
try {
|
|
14767
|
-
(0, import_node_fs13.mkdirSync)((0,
|
|
15449
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path14.dirname)(dest), { recursive: true });
|
|
14768
15450
|
(0, import_node_fs13.writeFileSync)(dest, content);
|
|
14769
15451
|
mirrored.push(p);
|
|
14770
15452
|
} catch {
|
|
@@ -14892,7 +15574,7 @@ function pruneOldBaselines() {
|
|
|
14892
15574
|
}
|
|
14893
15575
|
const now = Date.now();
|
|
14894
15576
|
for (const name of entries) {
|
|
14895
|
-
const dir = (0,
|
|
15577
|
+
const dir = (0, import_node_path14.join)(root, name);
|
|
14896
15578
|
const manifest = readManifest(dir);
|
|
14897
15579
|
if (!manifest) {
|
|
14898
15580
|
try {
|
|
@@ -15078,7 +15760,7 @@ function buildCompactionContext(session) {
|
|
|
15078
15760
|
commitsSince: commitsSincePaths(watermark, (state.authored ?? []).map((a) => a.path)),
|
|
15079
15761
|
readFileLines: (file) => {
|
|
15080
15762
|
try {
|
|
15081
|
-
const abs = (0,
|
|
15763
|
+
const abs = (0, import_node_path15.join)(root, file);
|
|
15082
15764
|
return (0, import_node_fs14.existsSync)(abs) ? (0, import_node_fs14.readFileSync)(abs, "utf8").split("\n") : null;
|
|
15083
15765
|
} catch {
|
|
15084
15766
|
return null;
|
|
@@ -15414,6 +16096,7 @@ function registerStatusCommand(program2) {
|
|
|
15414
16096
|
}
|
|
15415
16097
|
const token = tokenResult.data.token;
|
|
15416
16098
|
const serviceUrl = urlResult.data;
|
|
16099
|
+
const who = await whoami(token, serviceUrl, globals.verbose);
|
|
15417
16100
|
const memResult = await apiRequest({
|
|
15418
16101
|
method: "GET",
|
|
15419
16102
|
path: "/memory",
|
|
@@ -15421,14 +16104,21 @@ function registerStatusCommand(program2) {
|
|
|
15421
16104
|
token,
|
|
15422
16105
|
verbose: globals.verbose
|
|
15423
16106
|
});
|
|
15424
|
-
|
|
16107
|
+
const denial = memResult.ok ? null : authDenialRemedy(memResult.error);
|
|
16108
|
+
if (!memResult.ok && !denial) {
|
|
15425
16109
|
printError(memResult.error);
|
|
15426
16110
|
process.exit(1);
|
|
15427
16111
|
}
|
|
15428
|
-
const mem = memResult.data;
|
|
16112
|
+
const mem = memResult.ok ? memResult.data : null;
|
|
15429
16113
|
if (opts.json) {
|
|
15430
|
-
const output = {
|
|
15431
|
-
|
|
16114
|
+
const output = {
|
|
16115
|
+
auth: who.ok ? who.data : { error: who.error },
|
|
16116
|
+
memory: mem
|
|
16117
|
+
};
|
|
16118
|
+
if (denial && !memResult.ok) {
|
|
16119
|
+
output.error = { code: denial.code, message: memResult.error, remedy: denial.remedy };
|
|
16120
|
+
}
|
|
16121
|
+
if (opts.history && !denial) {
|
|
15432
16122
|
const runsResult = await apiRequest({
|
|
15433
16123
|
method: "GET",
|
|
15434
16124
|
path: `/runs?limit=${opts.limit}`,
|
|
@@ -15443,23 +16133,30 @@ function registerStatusCommand(program2) {
|
|
|
15443
16133
|
printJson(output);
|
|
15444
16134
|
return;
|
|
15445
16135
|
}
|
|
15446
|
-
if (mem
|
|
16136
|
+
if (mem?.configured === false) {
|
|
15447
16137
|
printInfo("Verity is not configured for this project. Run /verity-setup.");
|
|
15448
16138
|
return;
|
|
15449
16139
|
}
|
|
15450
16140
|
printInfo("=== Verity Status ===");
|
|
15451
|
-
if (
|
|
15452
|
-
printInfo(`Account: Logged in as ${
|
|
15453
|
-
} else {
|
|
15454
|
-
|
|
15455
|
-
|
|
15456
|
-
|
|
15457
|
-
|
|
15458
|
-
|
|
15459
|
-
|
|
15460
|
-
|
|
15461
|
-
|
|
15462
|
-
|
|
16141
|
+
if (who.ok && who.data.logged_in) {
|
|
16142
|
+
printInfo(`Account: Logged in as ${who.data.email ?? `user #${who.data.user_id}`} \u2713`);
|
|
16143
|
+
} else if (who.ok && who.data.anonymous) {
|
|
16144
|
+
printInfo('Account: Anonymous \u2014 runs not saved, no cloud memory. Run "verity login".');
|
|
16145
|
+
} else if (tokenResult.data.userId != null) {
|
|
16146
|
+
printInfo(`Account: Logged in as ${tokenResult.data.email ?? `user #${tokenResult.data.userId}`} (cached \u2014 could not reach the Verity service) \u2713`);
|
|
16147
|
+
} else if (!who.ok) {
|
|
16148
|
+
printInfo(`Account: Unknown \u2014 could not reach the Verity service (${who.error})`);
|
|
16149
|
+
}
|
|
16150
|
+
if (who.ok) {
|
|
16151
|
+
const nudge = reverifyNudge(who.data);
|
|
16152
|
+
if (nudge) printWarn(` ${nudge}`);
|
|
16153
|
+
}
|
|
16154
|
+
if (denial) {
|
|
16155
|
+
printWarn(`Access: ${denial.remedy}`);
|
|
16156
|
+
printInfo(" Project status, history, and cloud memory stay unavailable until then.");
|
|
16157
|
+
}
|
|
16158
|
+
if (mem?.project_name) printInfo(`Project: ${mem.project_name}`);
|
|
16159
|
+
if (mem?.standard) {
|
|
15463
16160
|
const s = mem.standard;
|
|
15464
16161
|
printInfo(`Standard: v${s.version} (${s.quality_dimensions} quality, ${s.security_patterns} security, ${s.custom_patterns} custom)`);
|
|
15465
16162
|
printInfo(`Languages: ${s.languages.join(", ")}`);
|
|
@@ -15470,6 +16167,7 @@ function registerStatusCommand(program2) {
|
|
|
15470
16167
|
if (hookStatus.guardOn.includes("commit")) moments.push("pre-commit");
|
|
15471
16168
|
if (hookStatus.guardOn.includes("push")) moments.push("pre-push/PR");
|
|
15472
16169
|
printInfo(`Moments: ${moments.length > 0 ? moments.join(", ") : "none (run /verity-setup)"}`);
|
|
16170
|
+
if (!mem) return;
|
|
15473
16171
|
if (mem.recent_runs) {
|
|
15474
16172
|
const r = mem.recent_runs;
|
|
15475
16173
|
printInfo("");
|
|
@@ -15621,7 +16319,7 @@ async function sendGeneralFeedback(message, opts, globals) {
|
|
|
15621
16319
|
|
|
15622
16320
|
// src/commands/analyze.ts
|
|
15623
16321
|
var import_node_fs24 = require("node:fs");
|
|
15624
|
-
var
|
|
16322
|
+
var import_node_path20 = require("node:path");
|
|
15625
16323
|
|
|
15626
16324
|
// src/lib/debounce.ts
|
|
15627
16325
|
var import_node_fs15 = require("node:fs");
|
|
@@ -15758,7 +16456,7 @@ function writeIteration(iteration, commit, _contentHash) {
|
|
|
15758
16456
|
}
|
|
15759
16457
|
|
|
15760
16458
|
// src/lib/static-analysis.ts
|
|
15761
|
-
var
|
|
16459
|
+
var import_node_child_process7 = require("node:child_process");
|
|
15762
16460
|
var import_node_fs16 = require("node:fs");
|
|
15763
16461
|
var SEVERITY_ORDER = {
|
|
15764
16462
|
Error: 0,
|
|
@@ -15771,7 +16469,7 @@ var SEVERITY_ORDER = {
|
|
|
15771
16469
|
};
|
|
15772
16470
|
function isCodacyAvailable() {
|
|
15773
16471
|
try {
|
|
15774
|
-
(0,
|
|
16472
|
+
(0, import_node_child_process7.execSync)("which codacy-analysis", { stdio: "pipe" });
|
|
15775
16473
|
return true;
|
|
15776
16474
|
} catch {
|
|
15777
16475
|
return false;
|
|
@@ -15795,7 +16493,7 @@ function runCodacyAnalysis(files) {
|
|
|
15795
16493
|
const fileArgs = existingFiles.join(" ");
|
|
15796
16494
|
let output;
|
|
15797
16495
|
try {
|
|
15798
|
-
output = (0,
|
|
16496
|
+
output = (0, import_node_child_process7.execSync)(
|
|
15799
16497
|
`codacy-analysis analyze --install-dependencies --files ${fileArgs} --output-format json --log-level error --parallel-tools 3`,
|
|
15800
16498
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], maxBuffer: 10 * 1024 * 1024 }
|
|
15801
16499
|
);
|
|
@@ -15849,7 +16547,7 @@ function runCodacyAnalysis(files) {
|
|
|
15849
16547
|
|
|
15850
16548
|
// src/lib/specs.ts
|
|
15851
16549
|
var import_node_fs17 = require("node:fs");
|
|
15852
|
-
var
|
|
16550
|
+
var import_node_path16 = require("node:path");
|
|
15853
16551
|
var SPEC_CANDIDATES = [
|
|
15854
16552
|
"CLAUDE.md",
|
|
15855
16553
|
"AGENTS.md",
|
|
@@ -15911,7 +16609,7 @@ function findMdFiles(dir, maxDepth, depth = 0) {
|
|
|
15911
16609
|
try {
|
|
15912
16610
|
const entries = (0, import_node_fs17.readdirSync)(dir, { withFileTypes: true });
|
|
15913
16611
|
for (const entry of entries) {
|
|
15914
|
-
const fullPath = (0,
|
|
16612
|
+
const fullPath = (0, import_node_path16.join)(dir, entry.name);
|
|
15915
16613
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
15916
16614
|
result.push(fullPath);
|
|
15917
16615
|
} else if (entry.isDirectory() && depth < maxDepth - 1) {
|
|
@@ -15923,7 +16621,7 @@ function findMdFiles(dir, maxDepth, depth = 0) {
|
|
|
15923
16621
|
return result;
|
|
15924
16622
|
}
|
|
15925
16623
|
function discoverPlans() {
|
|
15926
|
-
const homePlansDir = (0,
|
|
16624
|
+
const homePlansDir = (0, import_node_path16.join)(process.env.HOME ?? "", ".claude", "plans");
|
|
15927
16625
|
const localPlansDir = ".claude/plans";
|
|
15928
16626
|
const candidates = [];
|
|
15929
16627
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -15933,7 +16631,7 @@ function discoverPlans() {
|
|
|
15933
16631
|
for (const f of (0, import_node_fs17.readdirSync)(plansDir)) {
|
|
15934
16632
|
if (!f.endsWith(".md") || seen.has(f)) continue;
|
|
15935
16633
|
seen.add(f);
|
|
15936
|
-
const fullPath = (0,
|
|
16634
|
+
const fullPath = (0, import_node_path16.join)(plansDir, f);
|
|
15937
16635
|
try {
|
|
15938
16636
|
const stat3 = (0, import_node_fs17.statSync)(fullPath);
|
|
15939
16637
|
candidates.push({ name: f, path: fullPath, mtime: stat3.mtimeMs, size: stat3.size });
|
|
@@ -15957,7 +16655,7 @@ function discoverPlans() {
|
|
|
15957
16655
|
}
|
|
15958
16656
|
|
|
15959
16657
|
// src/lib/task-context.ts
|
|
15960
|
-
var
|
|
16658
|
+
var import_node_child_process8 = require("node:child_process");
|
|
15961
16659
|
var CLOSING_RE = /\b(close[sd]?|fix(?:e[sd])?|resolve[sd]?)\b[\s:]*#(\d+)/i;
|
|
15962
16660
|
var BRANCH_RE = /(?:^|[/_-])(?:issue|gh|fix)[-_/]?(\d+)\b/i;
|
|
15963
16661
|
function parseLinkedIssue(sources) {
|
|
@@ -15973,7 +16671,7 @@ function parseLinkedIssue(sources) {
|
|
|
15973
16671
|
}
|
|
15974
16672
|
function safeExec(cmd, timeout) {
|
|
15975
16673
|
try {
|
|
15976
|
-
return (0,
|
|
16674
|
+
return (0, import_node_child_process8.execSync)(cmd, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout }).trim();
|
|
15977
16675
|
} catch {
|
|
15978
16676
|
return "";
|
|
15979
16677
|
}
|
|
@@ -16003,7 +16701,7 @@ function resolveTaskContext(opts) {
|
|
|
16003
16701
|
// src/lib/cli-version.ts
|
|
16004
16702
|
function cliVersion() {
|
|
16005
16703
|
try {
|
|
16006
|
-
return true ? "0.28.1-experimental.
|
|
16704
|
+
return true ? "0.28.1-experimental.a72d2d9" : "dev";
|
|
16007
16705
|
} catch {
|
|
16008
16706
|
return "dev";
|
|
16009
16707
|
}
|
|
@@ -16207,7 +16905,7 @@ function truthy(v) {
|
|
|
16207
16905
|
|
|
16208
16906
|
// src/lib/fold.ts
|
|
16209
16907
|
var import_node_fs20 = require("node:fs");
|
|
16210
|
-
var
|
|
16908
|
+
var import_node_path17 = require("node:path");
|
|
16211
16909
|
var KNOWN_RECORD_TYPES = /* @__PURE__ */ new Set([
|
|
16212
16910
|
"user",
|
|
16213
16911
|
"assistant",
|
|
@@ -16228,6 +16926,16 @@ var KNOWN_RECORD_TYPES = /* @__PURE__ */ new Set([
|
|
|
16228
16926
|
"subagent"
|
|
16229
16927
|
]);
|
|
16230
16928
|
var EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "NotebookEdit", "MultiEdit"]);
|
|
16929
|
+
var DISPATCH_TOOLS = /* @__PURE__ */ new Set(["Task", "Workflow"]);
|
|
16930
|
+
function hasUserText(record) {
|
|
16931
|
+
const message = record.message;
|
|
16932
|
+
const content = message?.content ?? record.content;
|
|
16933
|
+
if (typeof content === "string") return content.trim().length > 0;
|
|
16934
|
+
if (!Array.isArray(content)) return false;
|
|
16935
|
+
return content.some(
|
|
16936
|
+
(b) => b?.type === "text" && typeof b.text === "string" && b.text.trim().length > 0
|
|
16937
|
+
);
|
|
16938
|
+
}
|
|
16231
16939
|
var COMMAND_CLASSES = [
|
|
16232
16940
|
[/\b(npm|yarn|pnpm|bun)\s+(run\s+)?test\b|\bvitest\b|\bjest\b|\bpytest\b|\bgo test\b/, "test"],
|
|
16233
16941
|
[/\b(npm|yarn|pnpm|bun)\s+(run\s+)?build\b|\btsc\b|\bwebpack\b|\bcargo build\b/, "build"],
|
|
@@ -16354,6 +17062,8 @@ function fold(transcriptPath, opts = {}) {
|
|
|
16354
17062
|
totalRecords: 0,
|
|
16355
17063
|
malformed: 0,
|
|
16356
17064
|
subagentFiles: 0,
|
|
17065
|
+
dispatched: 0,
|
|
17066
|
+
userMessages: 0,
|
|
16357
17067
|
subagentSkipped: 0,
|
|
16358
17068
|
compactions: 0,
|
|
16359
17069
|
complete: false
|
|
@@ -16380,7 +17090,8 @@ function fold(transcriptPath, opts = {}) {
|
|
|
16380
17090
|
if (type === "system" && record.subtype === "compact_boundary") {
|
|
16381
17091
|
result.coverage.compactions++;
|
|
16382
17092
|
}
|
|
16383
|
-
|
|
17093
|
+
if (type === "user" && hasUserText(record)) result.coverage.userMessages++;
|
|
17094
|
+
collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, opts.repoRoot, result.coverage);
|
|
16384
17095
|
}
|
|
16385
17096
|
};
|
|
16386
17097
|
try {
|
|
@@ -16391,7 +17102,7 @@ function fold(transcriptPath, opts = {}) {
|
|
|
16391
17102
|
return result;
|
|
16392
17103
|
}
|
|
16393
17104
|
try {
|
|
16394
|
-
const sidecarDir = (0,
|
|
17105
|
+
const sidecarDir = (0, import_node_path17.join)((0, import_node_path17.dirname)(transcriptPath), "subagents");
|
|
16395
17106
|
if ((0, import_node_fs20.existsSync)(sidecarDir)) {
|
|
16396
17107
|
const maxFiles = opts.maxSidecars ?? 200;
|
|
16397
17108
|
const maxBytes = opts.maxSidecarBytes ?? 16 * 1024 * 1024;
|
|
@@ -16399,7 +17110,7 @@ function fold(transcriptPath, opts = {}) {
|
|
|
16399
17110
|
const walk = (d, depth) => {
|
|
16400
17111
|
if (depth > 4) return;
|
|
16401
17112
|
for (const e of (0, import_node_fs20.readdirSync)(d, { withFileTypes: true })) {
|
|
16402
|
-
const p = (0,
|
|
17113
|
+
const p = (0, import_node_path17.join)(d, e.name);
|
|
16403
17114
|
if (e.isDirectory()) {
|
|
16404
17115
|
walk(p, depth + 1);
|
|
16405
17116
|
} else if (e.name.startsWith("agent-") && e.name.endsWith(".jsonl")) {
|
|
@@ -16452,7 +17163,7 @@ function classifyUnobserved(path) {
|
|
|
16452
17163
|
if (/\.(png|jpg|jpeg|gif|pdf|zip|woff2?|ico|mp4)$/i.test(path)) return "binary";
|
|
16453
17164
|
return "no_edit_record";
|
|
16454
17165
|
}
|
|
16455
|
-
function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, repoRoot2) {
|
|
17166
|
+
function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, repoRoot2, tally) {
|
|
16456
17167
|
const message = record.message;
|
|
16457
17168
|
const content = message?.content ?? record.content;
|
|
16458
17169
|
const blocks = Array.isArray(content) ? content : content && typeof content === "object" ? [content] : [];
|
|
@@ -16474,6 +17185,9 @@ function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse
|
|
|
16474
17185
|
byPath.set(path, entry);
|
|
16475
17186
|
}
|
|
16476
17187
|
}
|
|
17188
|
+
if (DISPATCH_TOOLS.has(name) && tally) {
|
|
17189
|
+
tally.dispatched += 1;
|
|
17190
|
+
}
|
|
16477
17191
|
if (name === "Bash") {
|
|
16478
17192
|
const cmd = typeof input.command === "string" ? input.command : "";
|
|
16479
17193
|
if (cmd) {
|
|
@@ -16532,6 +17246,86 @@ function checkConservation(changedFiles, result, repoRoot2) {
|
|
|
16532
17246
|
};
|
|
16533
17247
|
}
|
|
16534
17248
|
|
|
17249
|
+
// src/lib/verdict.ts
|
|
17250
|
+
function reconcileCoverage(changed, coverage) {
|
|
17251
|
+
const changedSet = new Set(changed);
|
|
17252
|
+
const reviewed = coverage.reviewed.filter((p) => changedSet.has(p));
|
|
17253
|
+
const claimed = /* @__PURE__ */ new Set([...reviewed, ...coverage.notReviewed.map((n) => n.path)]);
|
|
17254
|
+
const unaccounted = [...changedSet].filter((p) => !claimed.has(p)).sort();
|
|
17255
|
+
const notReviewed = [
|
|
17256
|
+
...coverage.notReviewed.filter((n) => changedSet.has(n.path)),
|
|
17257
|
+
...unaccounted.map((path) => ({
|
|
17258
|
+
path,
|
|
17259
|
+
reason: "unaccounted",
|
|
17260
|
+
// Named so the eventual bug report writes itself: some stage removed this
|
|
17261
|
+
// path and did not say so.
|
|
17262
|
+
stage: "unknown-stage",
|
|
17263
|
+
// An undeclared drop is CAPACITY by default. A stage that cannot be
|
|
17264
|
+
// bothered to say why it dropped a file does not get the benefit of the
|
|
17265
|
+
// doubt — that default is what makes forgetting expensive.
|
|
17266
|
+
kind: "capacity"
|
|
17267
|
+
}))
|
|
17268
|
+
];
|
|
17269
|
+
return {
|
|
17270
|
+
coverage: { reviewed: [...new Set(reviewed)].sort(), notReviewed },
|
|
17271
|
+
unaccounted,
|
|
17272
|
+
balances: unaccounted.length === 0
|
|
17273
|
+
};
|
|
17274
|
+
}
|
|
17275
|
+
function resolveVerdict(proposed, coverage) {
|
|
17276
|
+
if (proposed === "FAIL") return "FAIL";
|
|
17277
|
+
const blocking = coverage.notReviewed.filter((n) => (n.kind ?? "capacity") !== "policy");
|
|
17278
|
+
if (blocking.length === 0) return proposed;
|
|
17279
|
+
return "WARN";
|
|
17280
|
+
}
|
|
17281
|
+
function describeCoverage(coverage, maxPaths = 5) {
|
|
17282
|
+
const relevant = coverage.notReviewed.filter((n) => (n.kind ?? "capacity") !== "policy");
|
|
17283
|
+
if (relevant.length === 0) return null;
|
|
17284
|
+
const byReason = /* @__PURE__ */ new Map();
|
|
17285
|
+
for (const n of relevant) {
|
|
17286
|
+
const key = `${n.reason}`;
|
|
17287
|
+
const list = byReason.get(key) ?? [];
|
|
17288
|
+
list.push(n.path);
|
|
17289
|
+
byReason.set(key, list);
|
|
17290
|
+
}
|
|
17291
|
+
const lines = [];
|
|
17292
|
+
for (const [reason, paths] of [...byReason.entries()].sort()) {
|
|
17293
|
+
const shown = paths.slice(0, maxPaths).join(", ");
|
|
17294
|
+
const more = paths.length > maxPaths ? ` (+${paths.length - maxPaths} more)` : "";
|
|
17295
|
+
lines.push(` ${paths.length} not reviewed \u2014 ${reason}: ${shown}${more}`);
|
|
17296
|
+
}
|
|
17297
|
+
return `NOT A CLEAN REVIEW. ${relevant.length} changed file(s) never reached the reviewer, so this verdict does not cover them:
|
|
17298
|
+
${lines.join("\n")}
|
|
17299
|
+
Treat those files as UNCHECKED, not as approved.`;
|
|
17300
|
+
}
|
|
17301
|
+
function openBlockingElsewhere(statements, reviewedNow, lineShaAt) {
|
|
17302
|
+
const reviewed = new Set(reviewedNow);
|
|
17303
|
+
const out = [];
|
|
17304
|
+
const seen = /* @__PURE__ */ new Set();
|
|
17305
|
+
for (const s of statements) {
|
|
17306
|
+
if (s.outcome !== "open") continue;
|
|
17307
|
+
if (s.register !== "BLOCK") continue;
|
|
17308
|
+
if (s.carried) continue;
|
|
17309
|
+
if (reviewed.has(s.file)) continue;
|
|
17310
|
+
if (!s.line_sha) continue;
|
|
17311
|
+
if (lineShaAt(s.file, s.line) !== s.line_sha) continue;
|
|
17312
|
+
const key = `${s.file}::${s.pattern_id}`;
|
|
17313
|
+
if (seen.has(key)) continue;
|
|
17314
|
+
seen.add(key);
|
|
17315
|
+
out.push({ file: s.file, line: s.line, pattern_id: s.pattern_id });
|
|
17316
|
+
}
|
|
17317
|
+
return out;
|
|
17318
|
+
}
|
|
17319
|
+
function describeOpenElsewhere(open) {
|
|
17320
|
+
if (open.length === 0) return null;
|
|
17321
|
+
const lines = open.slice(0, 5).map((o) => ` ${o.file}:${o.line} [${o.pattern_id}]`);
|
|
17322
|
+
const more = open.length > 5 ? `
|
|
17323
|
+
(+${open.length - 5} more)` : "";
|
|
17324
|
+
return `STILL OPEN ELSEWHERE. ${open.length} blocking finding(s) Verity raised earlier are still present in files this run did not review:
|
|
17325
|
+
${lines.join("\n")}${more}
|
|
17326
|
+
This verdict covers the current change only. The tree is not clean.`;
|
|
17327
|
+
}
|
|
17328
|
+
|
|
16535
17329
|
// src/lib/channel.ts
|
|
16536
17330
|
var MAX_AGENT_CONTEXT_CHARS = 1500;
|
|
16537
17331
|
var MAX_AGENT_ITEMS = 5;
|
|
@@ -16554,9 +17348,16 @@ function buildAgentContext(input) {
|
|
|
16554
17348
|
}
|
|
16555
17349
|
if (input.intentVerdict === "misaligned" || input.intentVerdict === "partial") {
|
|
16556
17350
|
const gap = input.intentGaps?.[0];
|
|
16557
|
-
|
|
16558
|
-
|
|
16559
|
-
|
|
17351
|
+
const repeat = input.intentRepeat ?? 0;
|
|
17352
|
+
if (repeat > 0) {
|
|
17353
|
+
lines.push(
|
|
17354
|
+
`- Same intent flag as the last ${repeat === 1 ? "turn" : `${repeat} turns`}, on a goal that has not changed. Nothing new here \u2014 do not relay or re-explain it again; either act on it or carry on.`
|
|
17355
|
+
);
|
|
17356
|
+
} else {
|
|
17357
|
+
lines.push(
|
|
17358
|
+
`- This does not look like the change that was asked for` + (gap ? `: ${gap}` : ".") + " Not blocking \u2014 but check it against what you were asked to do."
|
|
17359
|
+
);
|
|
17360
|
+
}
|
|
16560
17361
|
}
|
|
16561
17362
|
const agentFindings = (input.findings ?? []).filter((f) => f.scope !== "pre-existing");
|
|
16562
17363
|
for (const f of agentFindings) {
|
|
@@ -16603,10 +17404,51 @@ function buildHookOutput(gateDecision, systemMessage, agentContext) {
|
|
|
16603
17404
|
} : {}
|
|
16604
17405
|
};
|
|
16605
17406
|
}
|
|
17407
|
+
var IDLE_EPISODE_CAP = 3;
|
|
17408
|
+
function channelSilence(input) {
|
|
17409
|
+
const movedSomething = input.newUserPrompt || input.newAuthorship;
|
|
17410
|
+
if (movedSomething) return null;
|
|
17411
|
+
if (input.consecutiveIdle >= IDLE_EPISODE_CAP) return "idle-episode-cap";
|
|
17412
|
+
if (input.emittedLast) return "caused-by-our-own-emission";
|
|
17413
|
+
return null;
|
|
17414
|
+
}
|
|
17415
|
+
|
|
17416
|
+
// src/lib/emit.ts
|
|
17417
|
+
var YELLOW2 = "\x1B[33m";
|
|
17418
|
+
var NC2 = "\x1B[0m";
|
|
17419
|
+
function emitVerdict(input) {
|
|
17420
|
+
const exit = input.exit ?? ((code) => process.exit(code));
|
|
17421
|
+
const { coverage, unaccounted } = reconcileCoverage(input.changed, input.coverage);
|
|
17422
|
+
let verdict = resolveVerdict(input.proposed, coverage);
|
|
17423
|
+
const openElsewhere = input.openElsewhere ?? [];
|
|
17424
|
+
if (verdict === "PASS" && openElsewhere.length > 0) verdict = "WARN";
|
|
17425
|
+
const note = [describeCoverage(coverage), describeOpenElsewhere(openElsewhere)].filter(Boolean).join("\n\n") || null;
|
|
17426
|
+
if (unaccounted.length > 0) {
|
|
17427
|
+
process.stderr.write(
|
|
17428
|
+
`${YELLOW2}Verity: ${unaccounted.length} changed file(s) could not be attributed to any review stage \u2014 counted as unreviewed.${NC2}
|
|
17429
|
+
`
|
|
17430
|
+
);
|
|
17431
|
+
}
|
|
17432
|
+
if (verdict === "FAIL") {
|
|
17433
|
+
input.renderBlocking?.();
|
|
17434
|
+
if (input.agentContext) {
|
|
17435
|
+
process.stderr.write(`
|
|
17436
|
+
${input.agentContext}
|
|
17437
|
+
`);
|
|
17438
|
+
}
|
|
17439
|
+
if (note && !input.silenced) process.stderr.write(`
|
|
17440
|
+
${YELLOW2}${note}${NC2}
|
|
17441
|
+
`);
|
|
17442
|
+
return exit(2);
|
|
17443
|
+
}
|
|
17444
|
+
const agentBlock = input.silenced ? null : [input.agentContext, note].filter(Boolean).join("\n\n") || null;
|
|
17445
|
+
printJsonCompact(buildHookOutput(verdict, input.userSummary, agentBlock));
|
|
17446
|
+
return exit(0);
|
|
17447
|
+
}
|
|
16606
17448
|
|
|
16607
17449
|
// src/lib/cache-cleanup.ts
|
|
16608
17450
|
var import_node_fs21 = require("node:fs");
|
|
16609
|
-
var
|
|
17451
|
+
var import_node_path18 = require("node:path");
|
|
16610
17452
|
var CACHE_TTL_DAYS = 7;
|
|
16611
17453
|
function pruneStaleCache() {
|
|
16612
17454
|
try {
|
|
@@ -16614,7 +17456,7 @@ function pruneStaleCache() {
|
|
|
16614
17456
|
const cutoff = Date.now() - CACHE_TTL_DAYS * 24 * 3600 * 1e3;
|
|
16615
17457
|
for (const entry of (0, import_node_fs21.readdirSync)(dir)) {
|
|
16616
17458
|
if (!entry.startsWith("pending-")) continue;
|
|
16617
|
-
const path = (0,
|
|
17459
|
+
const path = (0, import_node_path18.join)(dir, entry);
|
|
16618
17460
|
try {
|
|
16619
17461
|
const stat3 = (0, import_node_fs21.statSync)(path);
|
|
16620
17462
|
if (stat3.mtimeMs < cutoff) {
|
|
@@ -16783,46 +17625,6 @@ function shouldWarmRetryAnalyze(result) {
|
|
|
16783
17625
|
return false;
|
|
16784
17626
|
}
|
|
16785
17627
|
|
|
16786
|
-
// src/lib/skip-detection.ts
|
|
16787
|
-
function isBareAckPrompt(prompt) {
|
|
16788
|
-
if (typeof prompt !== "string") return false;
|
|
16789
|
-
const trimmed = prompt.trim();
|
|
16790
|
-
if (trimmed.length === 0) return false;
|
|
16791
|
-
if (trimmed.length > 20) return false;
|
|
16792
|
-
const bareAckPattern = /^(\d{1,2}|y|n|yes|no|yep|nope|ok(ay)?|sure|skip|cancel|stop|done|noted|got\s+it|sounds\s+good|thanks|thank\s+you|thx)[.!?]*$/i;
|
|
16793
|
-
return bareAckPattern.test(trimmed);
|
|
16794
|
-
}
|
|
16795
|
-
function isReflectionQuestion(response) {
|
|
16796
|
-
if (!response || typeof response !== "string") return false;
|
|
16797
|
-
const markers = [
|
|
16798
|
-
/reflection\s+for\s+future\s+agents/i,
|
|
16799
|
-
/what(?:'s|\s+is)\s+one\s+thing\s+you\s+learned/i,
|
|
16800
|
-
/say\s+['"]?skip['"]?\s+to\s+skip/i,
|
|
16801
|
-
/quick\s+reflection\s+question/i,
|
|
16802
|
-
// Post-flip (VRT-21): the agent drafts the reflection itself and, when
|
|
16803
|
-
// interactive, asks the user to confirm/correct before recording. That
|
|
16804
|
-
// turn authors no code either, so it's still a reflection turn.
|
|
16805
|
-
/reflection\s+draft/i,
|
|
16806
|
-
/confirm,?\s+correct,?\s+or\s+add/i
|
|
16807
|
-
];
|
|
16808
|
-
return markers.some((m) => m.test(response));
|
|
16809
|
-
}
|
|
16810
|
-
function isMetaTaskLabel(label2) {
|
|
16811
|
-
if (label2 === null || label2 === void 0) return false;
|
|
16812
|
-
if (typeof label2 !== "string") return false;
|
|
16813
|
-
const trimmed = label2.trim();
|
|
16814
|
-
if (trimmed.length === 0) return true;
|
|
16815
|
-
const metaPatterns = [
|
|
16816
|
-
/^verity\s+[\w-]+\s+response$/i,
|
|
16817
|
-
// "Verity reflect response"
|
|
16818
|
-
/^simple user response$/i,
|
|
16819
|
-
/^verity\s+command$/i,
|
|
16820
|
-
// "Verity command"
|
|
16821
|
-
/^user\s+(question|reply|response|ack)$/i
|
|
16822
|
-
];
|
|
16823
|
-
return metaPatterns.some((p) => p.test(trimmed));
|
|
16824
|
-
}
|
|
16825
|
-
|
|
16826
17628
|
// src/lib/transcript.ts
|
|
16827
17629
|
var import_node_fs22 = require("node:fs");
|
|
16828
17630
|
var MAX_READ_BYTES = 256 * 1024;
|
|
@@ -16986,6 +17788,13 @@ function buildSummary(lines) {
|
|
|
16986
17788
|
files_read: capArray(filesRead, MAX_FILES_LIST),
|
|
16987
17789
|
files_edited: capArray(filesEdited, MAX_FILES_LIST),
|
|
16988
17790
|
files_created: capArray(filesCreated, MAX_CREATED_LIST),
|
|
17791
|
+
// The complement of the two caps that affect SCOPE. `files_read` is excluded
|
|
17792
|
+
// deliberately: reading a file is not authoring it, so a capped read list
|
|
17793
|
+
// narrows nothing.
|
|
17794
|
+
capped_out: [
|
|
17795
|
+
...cappedOut(filesEdited, MAX_FILES_LIST),
|
|
17796
|
+
...cappedOut(filesCreated, MAX_CREATED_LIST)
|
|
17797
|
+
],
|
|
16989
17798
|
searches,
|
|
16990
17799
|
commands,
|
|
16991
17800
|
subagents,
|
|
@@ -17036,6 +17845,9 @@ function sanitizeCommand(rawCmd) {
|
|
|
17036
17845
|
function capArray(set, max) {
|
|
17037
17846
|
return Array.from(set).slice(0, max);
|
|
17038
17847
|
}
|
|
17848
|
+
function cappedOut(set, max) {
|
|
17849
|
+
return Array.from(set).slice(max);
|
|
17850
|
+
}
|
|
17039
17851
|
|
|
17040
17852
|
// src/lib/run-mode.ts
|
|
17041
17853
|
function parseAutonomousEnv(raw) {
|
|
@@ -17061,7 +17873,7 @@ function isExplicitlyAutonomous(env = process.env) {
|
|
|
17061
17873
|
// src/lib/seed-runner.ts
|
|
17062
17874
|
var import_promises11 = require("node:fs/promises");
|
|
17063
17875
|
var import_node_fs23 = require("node:fs");
|
|
17064
|
-
var
|
|
17876
|
+
var import_node_path19 = require("node:path");
|
|
17065
17877
|
var import_yaml2 = __toESM(require_dist());
|
|
17066
17878
|
|
|
17067
17879
|
// src/lib/seed.ts
|
|
@@ -17343,7 +18155,7 @@ async function runSeed(opts) {
|
|
|
17343
18155
|
if (candidates.length === 0) {
|
|
17344
18156
|
return { created: 0, failed: 0, skipped: "no_candidates", candidates: [] };
|
|
17345
18157
|
}
|
|
17346
|
-
const overviewPath = (0,
|
|
18158
|
+
const overviewPath = (0, import_node_path19.join)(MEMORY_DIR, "domain", "project-overview.md");
|
|
17347
18159
|
if ((0, import_node_fs23.existsSync)(overviewPath) && !opts.force) {
|
|
17348
18160
|
return { created: 0, failed: 0, skipped: "already_seeded", candidates };
|
|
17349
18161
|
}
|
|
@@ -17379,9 +18191,9 @@ async function runSeed(opts) {
|
|
|
17379
18191
|
}
|
|
17380
18192
|
const nodeId = res.data.node_id;
|
|
17381
18193
|
const filePathRel = res.data.file_path;
|
|
17382
|
-
const targetPath = (0,
|
|
18194
|
+
const targetPath = (0, import_node_path19.join)(MEMORY_DIR, filePathRel);
|
|
17383
18195
|
try {
|
|
17384
|
-
await (0, import_promises11.mkdir)((0,
|
|
18196
|
+
await (0, import_promises11.mkdir)((0, import_node_path19.dirname)(targetPath), { recursive: true });
|
|
17385
18197
|
await (0, import_promises11.writeFile)(targetPath, renderNodeMarkdown(c, nodeId, createdAt));
|
|
17386
18198
|
created++;
|
|
17387
18199
|
opts.onCreated?.(nodeId, filePathRel, c);
|
|
@@ -17430,10 +18242,11 @@ async function readStopHookStdin() {
|
|
|
17430
18242
|
return empty;
|
|
17431
18243
|
}
|
|
17432
18244
|
}
|
|
17433
|
-
function agentContextFor(response) {
|
|
18245
|
+
function agentContextFor(response, intentRepeat = 0) {
|
|
17434
18246
|
const metadata = response.metadata ?? {};
|
|
17435
18247
|
const intent = response.intent_alignment ?? {};
|
|
17436
18248
|
return buildAgentContext({
|
|
18249
|
+
intentRepeat,
|
|
17437
18250
|
gateDecision: String(response.gate_decision ?? ""),
|
|
17438
18251
|
findings: response.findings ?? [],
|
|
17439
18252
|
pendingItems: response.pending_items ?? [],
|
|
@@ -17444,12 +18257,45 @@ function agentContextFor(response) {
|
|
|
17444
18257
|
});
|
|
17445
18258
|
}
|
|
17446
18259
|
var beaconCtx = null;
|
|
17447
|
-
async function passAndExit(reason, skip) {
|
|
18260
|
+
async function passAndExit(reason, skip, kindOverride) {
|
|
17448
18261
|
const sent = await sendSkipBeacon(beaconCtx, skip);
|
|
17449
18262
|
logEvent("skip", { reason: skip, beacon: sent });
|
|
17450
|
-
|
|
18263
|
+
const POLICY_SKIPS = /* @__PURE__ */ new Set([
|
|
18264
|
+
"no-analyzable-files",
|
|
18265
|
+
"verity-command",
|
|
18266
|
+
"bare-acknowledgment",
|
|
18267
|
+
"reflection-prompt",
|
|
18268
|
+
"skip-mode",
|
|
18269
|
+
"zero-increment",
|
|
18270
|
+
"debounce",
|
|
18271
|
+
"no-delta-since-last-review"
|
|
18272
|
+
]);
|
|
18273
|
+
const skipKind = kindOverride ?? (POLICY_SKIPS.has(skip) ? "policy" : "capacity");
|
|
18274
|
+
const changed = skipCoverageChanged;
|
|
18275
|
+
const { coverage, unaccounted } = reconcileCoverage(changed, {
|
|
18276
|
+
reviewed: [],
|
|
18277
|
+
notReviewed: changed.map((path) => ({ path, reason: skip, stage: "pre-flight", kind: skipKind }))
|
|
18278
|
+
});
|
|
18279
|
+
const verdict = resolveVerdict("PASS", coverage);
|
|
18280
|
+
const note = describeCoverage(coverage);
|
|
18281
|
+
if (unaccounted.length > 0) {
|
|
18282
|
+
logEvent("coverage_unaccounted", { where: "passAndExit", skip, count: unaccounted.length });
|
|
18283
|
+
}
|
|
18284
|
+
const AGENT_SILENT_SKIPS = /* @__PURE__ */ new Set(["iteration-cap"]);
|
|
18285
|
+
const agentNote = AGENT_SILENT_SKIPS.has(skip) ? null : note;
|
|
18286
|
+
printJsonCompact(
|
|
18287
|
+
buildHookOutput(
|
|
18288
|
+
verdict,
|
|
18289
|
+
`Verity: ${reason}`,
|
|
18290
|
+
// The agent's ONLY input is additionalContext. Sixteen of the nineteen
|
|
18291
|
+
// terminating paths wrote `systemMessage` — the human's field — and told
|
|
18292
|
+
// the agent nothing at all.
|
|
18293
|
+
agentNote
|
|
18294
|
+
)
|
|
18295
|
+
);
|
|
17451
18296
|
process.exit(0);
|
|
17452
18297
|
}
|
|
18298
|
+
var skipCoverageChanged = [];
|
|
17453
18299
|
var EMPTY_STATIC = {
|
|
17454
18300
|
tool: "@codacy/analysis-cli",
|
|
17455
18301
|
findings: [],
|
|
@@ -17464,7 +18310,7 @@ function runLocalStatic(analyzable, securityFiles, baseline, skipStatic) {
|
|
|
17464
18310
|
}
|
|
17465
18311
|
function localOnlyAndExit(staticResults) {
|
|
17466
18312
|
printJsonCompact({
|
|
17467
|
-
gate_decision: "
|
|
18313
|
+
gate_decision: "WARN",
|
|
17468
18314
|
systemMessage: "Verity: not authenticated \u2014 ran a local static-only check (no deep review, no upload). Run `verity init` to authenticate and enable the full quality gate.",
|
|
17469
18315
|
unauthenticated: true,
|
|
17470
18316
|
static_results: staticResults
|
|
@@ -17524,6 +18370,7 @@ async function runAnalyze(opts, globals) {
|
|
|
17524
18370
|
});
|
|
17525
18371
|
}
|
|
17526
18372
|
const { files: allChanged, hasRecentCommitFiles } = getChangedFiles();
|
|
18373
|
+
skipCoverageChanged = allChanged;
|
|
17527
18374
|
const analyzable = filterAnalyzable(allChanged);
|
|
17528
18375
|
const reviewable = filterReviewable(allChanged);
|
|
17529
18376
|
const securityFiles = filterSecurity(allChanged);
|
|
@@ -17539,10 +18386,12 @@ async function runAnalyze(opts, globals) {
|
|
|
17539
18386
|
if (/^\s*\/verity-/i.test(latestPrompt)) {
|
|
17540
18387
|
await passAndExit("Verity command \u2014 skipping analysis", "verity-command");
|
|
17541
18388
|
}
|
|
17542
|
-
|
|
18389
|
+
const agentAuthoredCodeThisTurn = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0));
|
|
18390
|
+
const turnAuthoredCode = agentAuthoredCodeThisTurn || !!baseline && allForReview.some((f) => changedSinceBaseline(f, baseline));
|
|
18391
|
+
const canSeeTurnAuthorship = !!actionSummary || !!baseline;
|
|
18392
|
+
if (shouldSkipForBareAck({ prompt: latestPrompt, turnAuthoredCode, canSeeTurnAuthorship })) {
|
|
17543
18393
|
await passAndExit("Bare acknowledgment \u2014 skipping analysis", "bare-acknowledgment");
|
|
17544
18394
|
}
|
|
17545
|
-
const agentAuthoredCodeThisTurn = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0));
|
|
17546
18395
|
if (isReflectionQuestion(assistantResponse) && !agentAuthoredCodeThisTurn) {
|
|
17547
18396
|
await passAndExit("Reflection-prompt turn \u2014 skipping analysis", "reflection-prompt");
|
|
17548
18397
|
}
|
|
@@ -17599,7 +18448,8 @@ async function runAnalyze(opts, globals) {
|
|
|
17599
18448
|
let codeDelta = {
|
|
17600
18449
|
files: [],
|
|
17601
18450
|
total_lines: 0,
|
|
17602
|
-
total_files: 0
|
|
18451
|
+
total_files: 0,
|
|
18452
|
+
excluded: []
|
|
17603
18453
|
};
|
|
17604
18454
|
let snapshotResult = { has_snapshots: false, diffs: [] };
|
|
17605
18455
|
let contentHash = null;
|
|
@@ -17664,7 +18514,11 @@ async function runAnalyze(opts, globals) {
|
|
|
17664
18514
|
if (assistantResponse) {
|
|
17665
18515
|
analysisMode = "plan";
|
|
17666
18516
|
} else {
|
|
17667
|
-
await passAndExit(
|
|
18517
|
+
await passAndExit(
|
|
18518
|
+
"No files within size limits to analyze",
|
|
18519
|
+
"size-limit",
|
|
18520
|
+
codeDelta.excluded.length > 0 ? "capacity" : "policy"
|
|
18521
|
+
);
|
|
17668
18522
|
}
|
|
17669
18523
|
}
|
|
17670
18524
|
}
|
|
@@ -17707,7 +18561,7 @@ async function runAnalyze(opts, globals) {
|
|
|
17707
18561
|
let autoSeedNotice = null;
|
|
17708
18562
|
try {
|
|
17709
18563
|
await ensureMemoryDir();
|
|
17710
|
-
const seedMarker = (0,
|
|
18564
|
+
const seedMarker = (0, import_node_path20.join)(VERITY_DIR, ".seeded");
|
|
17711
18565
|
const hasStandard = (0, import_node_fs24.existsSync)(STANDARD_FILE);
|
|
17712
18566
|
const alreadyTried = (0, import_node_fs24.existsSync)(seedMarker);
|
|
17713
18567
|
if (hasStandard && !alreadyTried) {
|
|
@@ -17775,7 +18629,7 @@ async function runAnalyze(opts, globals) {
|
|
|
17775
18629
|
const priorState = foldForMarks(memorySession.d);
|
|
17776
18630
|
incrementReport = computeIncrement(
|
|
17777
18631
|
allForReview,
|
|
17778
|
-
(p) => fileHash((0,
|
|
18632
|
+
(p) => fileHash((0, import_node_path20.join)(repoRoot(), p)),
|
|
17779
18633
|
priorState.authored_all.map((a) => ({
|
|
17780
18634
|
path: a.path,
|
|
17781
18635
|
hash_at_last_verdict: a.hash_at_last_verdict
|
|
@@ -17838,7 +18692,10 @@ async function runAnalyze(opts, globals) {
|
|
|
17838
18692
|
priorState.capabilities
|
|
17839
18693
|
);
|
|
17840
18694
|
memory = recallMemory(memorySession.d, memorySession.identity, {
|
|
17841
|
-
currentSessionKey: memorySession.identity.sessionKey
|
|
18695
|
+
currentSessionKey: memorySession.identity.sessionKey,
|
|
18696
|
+
// The independent witness. Only meaningful when a transcript was folded —
|
|
18697
|
+
// otherwise it stays undefined and capture coverage reads as UNKNOWN.
|
|
18698
|
+
...foldResult && { userMessagesSeen: foldResult.coverage.userMessages }
|
|
17842
18699
|
});
|
|
17843
18700
|
if (memory && !memory.provenanceHolds) {
|
|
17844
18701
|
process.stderr.write("Verity: working-memory provenance check failed \u2014 recall suppressed.\n");
|
|
@@ -17917,6 +18774,25 @@ async function runAnalyze(opts, globals) {
|
|
|
17917
18774
|
projection: {
|
|
17918
18775
|
v: 1,
|
|
17919
18776
|
authored: foldResult?.authored ?? [],
|
|
18777
|
+
// ⚠ AUTHORED *SINCE THE LAST VERDICT* — a different question from `authored`.
|
|
18778
|
+
//
|
|
18779
|
+
// `authored` above is the fold of the session TRANSCRIPT, which is
|
|
18780
|
+
// cumulative: on turn 3 it still lists the files turn 2 edited. The
|
|
18781
|
+
// account's close-out reads it as "was this file edited since the
|
|
18782
|
+
// statement was raised", and those are not the same set.
|
|
18783
|
+
//
|
|
18784
|
+
// Measured 2026-08-03 (shirt-seller): four real vulnerabilities were
|
|
18785
|
+
// raised on the turn that wrote them, then marked `fixed` 36 seconds
|
|
18786
|
+
// later by a SUMMARY turn that edited nothing — `authored` still said 2,
|
|
18787
|
+
// the turn carried 0 findings, so "gone + file authored" resolved to
|
|
18788
|
+
// fixed. The vulnerabilities were still on disk. A silent false `fixed`
|
|
18789
|
+
// is worse than a false `open`: it retires the statement the Account
|
|
18790
|
+
// exists to keep.
|
|
18791
|
+
//
|
|
18792
|
+
// `hash_at_last_verdict` is frozen at each verdict and `hash_now` tracks
|
|
18793
|
+
// disk, so their inequality IS "changed since we last spoke" — already
|
|
18794
|
+
// computed, already maintained by the divergence machinery.
|
|
18795
|
+
authored_since_verdict: memorySession ? foldDossier(memorySession.d).authored_all.filter((a) => a.hash_now !== a.hash_at_last_verdict).map((a) => a.path) : [],
|
|
17920
18796
|
unobserved: foldResult?.unobserved ?? [],
|
|
17921
18797
|
commands: foldResult?.commands ?? [],
|
|
17922
18798
|
unknown_types: foldResult?.unknownTypes ?? [],
|
|
@@ -17958,7 +18834,20 @@ async function runAnalyze(opts, globals) {
|
|
|
17958
18834
|
const intentContext = {};
|
|
17959
18835
|
if (conversation && conversation.prompts.length > 0) {
|
|
17960
18836
|
const latest = conversation.prompts[conversation.prompts.length - 1];
|
|
17961
|
-
|
|
18837
|
+
const goalPrompt = resolveGoalPrompt(conversation.prompts) ?? { entry: latest, turnsBack: 0 };
|
|
18838
|
+
intentContext.user_prompt = goalPrompt.entry.prompt;
|
|
18839
|
+
if (isContinuationPrompt(intentContext.user_prompt)) {
|
|
18840
|
+
const carried = memory?.projection.goal?.text;
|
|
18841
|
+
if (carried && !isContinuationPrompt(carried)) {
|
|
18842
|
+
intentContext.continuation_prompt = latest.prompt;
|
|
18843
|
+
intentContext.user_prompt = carried;
|
|
18844
|
+
logEvent("goal_from_dossier", { chars: carried.length });
|
|
18845
|
+
}
|
|
18846
|
+
}
|
|
18847
|
+
if (goalPrompt.turnsBack > 0) {
|
|
18848
|
+
intentContext.continuation_prompt = latest.prompt;
|
|
18849
|
+
logEvent("goal_walked_back", { turns_back: goalPrompt.turnsBack });
|
|
18850
|
+
}
|
|
17962
18851
|
intentContext.session_id = latest.session_id || void 0;
|
|
17963
18852
|
intentContext.prompt_captured_at = latest.captured_at || void 0;
|
|
17964
18853
|
if (conversation.prompts.length > 1) {
|
|
@@ -18030,6 +18919,12 @@ async function runAnalyze(opts, globals) {
|
|
|
18030
18919
|
message = `Verity: request rejected \u2014 payload too large (${kb}KB over the 190KB cap). Commit or stash pre-existing changes, or run with --mode skip if you didn't intend to submit code this turn.`;
|
|
18031
18920
|
} else if (result.category === "network" || result.category === "timeout") {
|
|
18032
18921
|
message = `Verity offline \u2014 ${result.error}`;
|
|
18922
|
+
} else if (result.error.startsWith("STALE_VERIFICATION")) {
|
|
18923
|
+
message = "Verity: your GitHub verification expired \u2014 run `verity login` to re-verify (local analysis this run)";
|
|
18924
|
+
} else if (result.error.startsWith("FORBIDDEN")) {
|
|
18925
|
+
message = "Verity: no access grant for this repository \u2014 run `verity login` to refresh your grants (local analysis this run)";
|
|
18926
|
+
} else if (result.error.startsWith("INVALID_TOKEN")) {
|
|
18927
|
+
message = "Verity: your login expired or was revoked \u2014 run `verity login` to sign in again (local analysis this run)";
|
|
18033
18928
|
} else if (result.http_status && result.http_status >= 400 && result.http_status < 500) {
|
|
18034
18929
|
message = `Verity: request rejected (HTTP ${result.http_status}) \u2014 ${result.error}`;
|
|
18035
18930
|
} else if (result.http_status && result.http_status >= 500) {
|
|
@@ -18044,8 +18939,115 @@ async function runAnalyze(opts, globals) {
|
|
|
18044
18939
|
const response = result.data;
|
|
18045
18940
|
const decision = response.gate_decision ?? "(unrecognised)";
|
|
18046
18941
|
const sentPaths = codeDelta.files.map((f) => f.path);
|
|
18942
|
+
let openElsewhere = [];
|
|
18943
|
+
if (memorySession) {
|
|
18944
|
+
try {
|
|
18945
|
+
const st = foldDossier(memorySession.d);
|
|
18946
|
+
openElsewhere = openBlockingElsewhere(st.statements, sentPaths, (file, line) => {
|
|
18947
|
+
try {
|
|
18948
|
+
const src = (0, import_node_fs24.readFileSync)((0, import_node_path20.join)(repoRoot(), file), "utf8").split("\n");
|
|
18949
|
+
const at = src[line - 1];
|
|
18950
|
+
return at === void 0 ? null : lineSha(at);
|
|
18951
|
+
} catch {
|
|
18952
|
+
return null;
|
|
18953
|
+
}
|
|
18954
|
+
});
|
|
18955
|
+
} catch {
|
|
18956
|
+
}
|
|
18957
|
+
}
|
|
18958
|
+
const reviewCoverage = {
|
|
18959
|
+
reviewed: sentPaths,
|
|
18960
|
+
// Declared drops from the stages that DO report themselves today. The other
|
|
18961
|
+
// stages surface via `unaccounted`, which is the tripwire, not the design.
|
|
18962
|
+
notReviewed: [
|
|
18963
|
+
// Every exit from the collection loop, each named. Six reasons where there
|
|
18964
|
+
// used to be two recorded and four silent — the silent ones including the
|
|
18965
|
+
// per-file size cap, which could drop a whole source file without leaving a
|
|
18966
|
+
// trace anywhere in the payload or the run row.
|
|
18967
|
+
...codeDelta.excluded,
|
|
18968
|
+
// The server-side 300-line middle-out truncation. It only bites on the
|
|
18969
|
+
// full-file branch (a first analysis, before snapshots exist) because
|
|
18970
|
+
// analyze normally sends diffs — but on that branch the reviewer sees the
|
|
18971
|
+
// first and last 100 lines and nothing between, and until now said so to
|
|
18972
|
+
// nobody. CAPACITY: a partial look is not a look.
|
|
18973
|
+
...(response.metadata?.truncated_files ?? []).map((path) => ({
|
|
18974
|
+
path,
|
|
18975
|
+
reason: "file-middle-truncated-300-lines",
|
|
18976
|
+
stage: "prompt-builder",
|
|
18977
|
+
kind: "capacity"
|
|
18978
|
+
})),
|
|
18979
|
+
// The 20-entry edit cap. CAPACITY, and the sharpest of the lot: it narrows
|
|
18980
|
+
// what is REVIEWED, not merely what is summarised — a session editing 25
|
|
18981
|
+
// files had five silently excluded from the reviewed set.
|
|
18982
|
+
...(actionSummary?.capped_out ?? []).map((path) => ({
|
|
18983
|
+
path,
|
|
18984
|
+
reason: "edit-list-cap-20",
|
|
18985
|
+
stage: "extractActionSummary",
|
|
18986
|
+
kind: "capacity"
|
|
18987
|
+
})),
|
|
18988
|
+
// ⚠ BASELINE SCOPING — the biggest source of false NOT A CLEAN REVIEW.
|
|
18989
|
+
//
|
|
18990
|
+
// The universe is `allChanged`, git's whole dirty tree. The reviewed set is
|
|
18991
|
+
// scoped to what THIS SESSION authored (the VRT-26 contamination cure), so
|
|
18992
|
+
// every pre-existing dirty file is in the universe, absent from `reviewed`,
|
|
18993
|
+
// and — until now — declared by nobody. It fell through to `unaccounted`,
|
|
18994
|
+
// became capacity, and produced "NOT A CLEAN REVIEW: admin.js" over a file
|
|
18995
|
+
// that was never this session's to review.
|
|
18996
|
+
//
|
|
18997
|
+
// Measured 2026-08-04: three consecutive runs over an untouched tree gave
|
|
18998
|
+
// three different answers — .claude/settings.json, then admin.js, then six
|
|
18999
|
+
// files — because each run took a different path and each path had a
|
|
19000
|
+
// different idea of the universe. POLICY: not this session's work is not a
|
|
19001
|
+
// coverage gap, it is the cure working.
|
|
19002
|
+
...allChanged.filter((p) => !sentPaths.includes(p) && !codeDelta.excluded.some((e) => e.path === p)).filter((p) => analyzable.includes(p) || reviewable.includes(p) || securityFiles.includes(p)).map((path) => ({
|
|
19003
|
+
path,
|
|
19004
|
+
reason: "not-authored-this-session",
|
|
19005
|
+
stage: "baseline-scoping",
|
|
19006
|
+
kind: "policy"
|
|
19007
|
+
})),
|
|
19008
|
+
// The extension allowlist, and it is POLICY rather than capacity: a changed
|
|
19009
|
+
// README was never going to be reviewed, and treating that as a coverage
|
|
19010
|
+
// gap would downgrade nearly every PASS to WARN until WARN meant nothing.
|
|
19011
|
+
// Recorded so the ledger balances and so "what did Verity ignore entirely"
|
|
19012
|
+
// is answerable — but it never touches the verdict.
|
|
19013
|
+
...allChanged.filter((p) => !analyzable.includes(p) && !reviewable.includes(p) && !securityFiles.includes(p)).map((path) => ({
|
|
19014
|
+
path,
|
|
19015
|
+
reason: "not-a-reviewed-file-type",
|
|
19016
|
+
stage: "extension-allowlist",
|
|
19017
|
+
kind: "policy"
|
|
19018
|
+
}))
|
|
19019
|
+
]
|
|
19020
|
+
};
|
|
18047
19021
|
const watermarkHash = sentPaths.length > 0 ? computeContentHash(sentPaths) : contentHash;
|
|
18048
19022
|
const watermarkIsPartial = !!codeDelta.truncated;
|
|
19023
|
+
let silenced = null;
|
|
19024
|
+
let turnIsIdleForChannel = true;
|
|
19025
|
+
if (memorySession) {
|
|
19026
|
+
try {
|
|
19027
|
+
const st = foldDossier(memorySession.d);
|
|
19028
|
+
turnIsIdleForChannel = st.authored_all.every((a) => a.hash_now === a.hash_at_last_verdict);
|
|
19029
|
+
silenced = channelSilence({
|
|
19030
|
+
// The BUFFER, not intentContext.user_prompt: the latter falls back to a
|
|
19031
|
+
// linked issue (VRT-53 W4) when no human spoke, and a fallback goal is
|
|
19032
|
+
// not a user utterance. Treating it as one would keep the loop alive on
|
|
19033
|
+
// exactly the autonomous cohort.
|
|
19034
|
+
newUserPrompt: (conversation?.prompts?.length ?? 0) > 0,
|
|
19035
|
+
newAuthorship: !turnIsIdleForChannel,
|
|
19036
|
+
emittedLast: st.meta.channel?.emittedLast === true,
|
|
19037
|
+
consecutiveIdle: st.meta.channel?.consecutiveIdle ?? 0
|
|
19038
|
+
});
|
|
19039
|
+
} catch {
|
|
19040
|
+
silenced = null;
|
|
19041
|
+
}
|
|
19042
|
+
}
|
|
19043
|
+
if (silenced) {
|
|
19044
|
+
logEvent("channel_silenced", {
|
|
19045
|
+
reason: silenced,
|
|
19046
|
+
run_id: response.run_id ?? turnId,
|
|
19047
|
+
decision
|
|
19048
|
+
});
|
|
19049
|
+
}
|
|
19050
|
+
let intentRepeatCount = 0;
|
|
18049
19051
|
if (memorySession) {
|
|
18050
19052
|
try {
|
|
18051
19053
|
recordVerdict(memorySession.d, {
|
|
@@ -18059,8 +19061,18 @@ async function runAnalyze(opts, globals) {
|
|
|
18059
19061
|
pattern_id: f.pattern_id ?? f.rule_id,
|
|
18060
19062
|
title: f.title,
|
|
18061
19063
|
severity: f.severity
|
|
18062
|
-
})) ?? []
|
|
19064
|
+
})) ?? [],
|
|
19065
|
+
intent: response.intent_alignment ?? null,
|
|
19066
|
+
// The same signal F1 introduced: bytes differing from the hash frozen at
|
|
19067
|
+
// the last verdict. A turn that moved nothing is the only kind that can
|
|
19068
|
+
// accumulate a repeat.
|
|
19069
|
+
idle: turnIsIdleForChannel,
|
|
19070
|
+
// What next turn reads as `emittedLast`. A suppressed turn did not
|
|
19071
|
+
// speak, so it cannot be the cause of the turn after it — which is what
|
|
19072
|
+
// keeps this from becoming a permanent gag.
|
|
19073
|
+
emitted: !silenced
|
|
18063
19074
|
});
|
|
19075
|
+
intentRepeatCount = Math.max(0, (foldDossier(memorySession.d).meta.intent_repeat?.consecutive ?? 1) - 1);
|
|
18064
19076
|
} catch {
|
|
18065
19077
|
}
|
|
18066
19078
|
}
|
|
@@ -18153,6 +19165,11 @@ async function runAnalyze(opts, globals) {
|
|
|
18153
19165
|
}
|
|
18154
19166
|
saveSnapshots(codeDelta.files.map((f) => ({ path: f.path, content: f.content })));
|
|
18155
19167
|
const loginNudge = response.persisted === false ? " \u26A0\uFE0F Not logged in \u2014 this run was NOT saved and Verity has no memory of your project. Run `verity login` to unlock run history, trends, and cloud memory." : "";
|
|
19168
|
+
const grantWarning = reverifyNudge({
|
|
19169
|
+
grant_status: response.grant_status,
|
|
19170
|
+
reverify_by: response.reverify_by
|
|
19171
|
+
});
|
|
19172
|
+
const grantNudge = grantWarning ? ` \u26A0\uFE0F ${grantWarning}` : "";
|
|
18156
19173
|
switch (decision) {
|
|
18157
19174
|
case "FAIL": {
|
|
18158
19175
|
writeIteration(iteration + 1, currentCommit, contentHash ?? void 0);
|
|
@@ -18229,7 +19246,22 @@ async function runAnalyze(opts, globals) {
|
|
|
18229
19246
|
if (loginNudge) process.stderr.write(`
|
|
18230
19247
|
${YELLOW}${loginNudge.trim()}${NC}
|
|
18231
19248
|
`);
|
|
18232
|
-
process.
|
|
19249
|
+
if (grantNudge) process.stderr.write(`
|
|
19250
|
+
${YELLOW}${grantNudge.trim()}${NC}
|
|
19251
|
+
`);
|
|
19252
|
+
emitVerdict({
|
|
19253
|
+
proposed: "FAIL",
|
|
19254
|
+
changed: skipCoverageChanged,
|
|
19255
|
+
coverage: reviewCoverage,
|
|
19256
|
+
userSummary: "",
|
|
19257
|
+
// Subject to the SAME cycle cut as PASS/WARN. Suppressing here is safe:
|
|
19258
|
+
// the findings themselves are rendered above by the blocking renderer,
|
|
19259
|
+
// so what the cut removes is the repeated commentary, never the defect.
|
|
19260
|
+
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount),
|
|
19261
|
+
// The coverage note is silenced with it — half a channel is still a channel.
|
|
19262
|
+
silenced: !!silenced,
|
|
19263
|
+
openElsewhere
|
|
19264
|
+
});
|
|
18233
19265
|
break;
|
|
18234
19266
|
}
|
|
18235
19267
|
case "PASS": {
|
|
@@ -18240,11 +19272,17 @@ ${YELLOW}${loginNudge.trim()}${NC}
|
|
|
18240
19272
|
const viewUrl = response.view_url ?? "";
|
|
18241
19273
|
if (viewUrl) userSummary += ` Report: ${viewUrl}`;
|
|
18242
19274
|
if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
|
|
18243
|
-
userSummary += loginNudge;
|
|
18244
|
-
|
|
18245
|
-
|
|
18246
|
-
|
|
18247
|
-
|
|
19275
|
+
userSummary += loginNudge + grantNudge;
|
|
19276
|
+
emitVerdict({
|
|
19277
|
+
proposed: "PASS",
|
|
19278
|
+
changed: skipCoverageChanged,
|
|
19279
|
+
coverage: reviewCoverage,
|
|
19280
|
+
userSummary,
|
|
19281
|
+
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount),
|
|
19282
|
+
// The coverage note is silenced with it — half a channel is still a channel.
|
|
19283
|
+
silenced: !!silenced,
|
|
19284
|
+
openElsewhere
|
|
19285
|
+
});
|
|
18248
19286
|
break;
|
|
18249
19287
|
}
|
|
18250
19288
|
case "WARN": {
|
|
@@ -18254,16 +19292,22 @@ ${YELLOW}${loginNudge.trim()}${NC}
|
|
|
18254
19292
|
const viewUrl = response.view_url ?? "";
|
|
18255
19293
|
if (viewUrl) userSummary += ` Report: ${viewUrl}`;
|
|
18256
19294
|
if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
|
|
18257
|
-
userSummary += loginNudge;
|
|
18258
|
-
|
|
18259
|
-
|
|
18260
|
-
|
|
18261
|
-
|
|
19295
|
+
userSummary += loginNudge + grantNudge;
|
|
19296
|
+
emitVerdict({
|
|
19297
|
+
proposed: "WARN",
|
|
19298
|
+
changed: skipCoverageChanged,
|
|
19299
|
+
coverage: reviewCoverage,
|
|
19300
|
+
userSummary,
|
|
19301
|
+
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount),
|
|
19302
|
+
// The coverage note is silenced with it — half a channel is still a channel.
|
|
19303
|
+
silenced: !!silenced,
|
|
19304
|
+
openElsewhere
|
|
19305
|
+
});
|
|
18262
19306
|
break;
|
|
18263
19307
|
}
|
|
18264
19308
|
default: {
|
|
18265
19309
|
const raw = String(decision ?? "(missing)");
|
|
18266
|
-
const msg = (autoSeedNotice ? `${autoSeedNotice} Verity: unrecognised verdict \u2014 treating as WARN` : "Verity: unrecognised verdict \u2014 treating as WARN") + loginNudge;
|
|
19310
|
+
const msg = (autoSeedNotice ? `${autoSeedNotice} Verity: unrecognised verdict \u2014 treating as WARN` : "Verity: unrecognised verdict \u2014 treating as WARN") + loginNudge + grantNudge;
|
|
18267
19311
|
process.stderr.write(
|
|
18268
19312
|
`Verity: server returned an unrecognised gate_decision (${raw}). Rendering WARN rather than PASS. Update the CLI: npm i -g @codacy/verity-cli
|
|
18269
19313
|
`
|
|
@@ -18371,8 +19415,8 @@ async function runReview(opts, globals) {
|
|
|
18371
19415
|
for (const p of specPaths) {
|
|
18372
19416
|
if (!(0, import_node_fs26.existsSync)(p)) continue;
|
|
18373
19417
|
try {
|
|
18374
|
-
const { readFileSync:
|
|
18375
|
-
const content =
|
|
19418
|
+
const { readFileSync: readFileSync16 } = await import("node:fs");
|
|
19419
|
+
const content = readFileSync16(p, "utf-8");
|
|
18376
19420
|
specs.push({ path: p, content: content.slice(0, 10240) });
|
|
18377
19421
|
} catch {
|
|
18378
19422
|
}
|
|
@@ -18430,9 +19474,9 @@ async function runReview(opts, globals) {
|
|
|
18430
19474
|
|
|
18431
19475
|
// src/commands/guard.ts
|
|
18432
19476
|
var import_node_fs27 = require("node:fs");
|
|
18433
|
-
var
|
|
19477
|
+
var import_node_path21 = require("node:path");
|
|
18434
19478
|
var GUARD_BLOCK_CAP = 2;
|
|
18435
|
-
var GUARD_ITER_FILE = (0,
|
|
19479
|
+
var GUARD_ITER_FILE = (0, import_node_path21.join)(VERITY_DIR, ".guard-iteration");
|
|
18436
19480
|
function readPreToolUseStdin() {
|
|
18437
19481
|
const empty = { command: "", cwd: null, sessionId: null };
|
|
18438
19482
|
return new Promise((resolve2) => {
|
|
@@ -18674,9 +19718,10 @@ async function runGuard(opts, globals) {
|
|
|
18674
19718
|
cmd: "guard"
|
|
18675
19719
|
});
|
|
18676
19720
|
if (!result.ok) {
|
|
19721
|
+
const authRemedy = result.error.startsWith("STALE_VERIFICATION") ? " Your GitHub verification expired \u2014 run `verity login` to re-verify." : result.error.startsWith("FORBIDDEN") ? " No access grant for this repository \u2014 run `verity login` to refresh your grants." : result.error.startsWith("INVALID_TOKEN") ? " Your Verity login expired or was revoked \u2014 run `verity login` to sign in again." : "";
|
|
18677
19722
|
emitAllowNotice(
|
|
18678
|
-
`\u26A0 Verity ${moment}: service offline \u2014 ${verb}ed WITHOUT review`,
|
|
18679
|
-
`Verity ${moment}: service unavailable (${result.error}); the ${verb} was allowed WITHOUT a Verity review
|
|
19723
|
+
`\u26A0 Verity ${moment}: ${authRemedy ? "not authorized" : "service offline"} \u2014 ${verb}ed WITHOUT review${authRemedy}`,
|
|
19724
|
+
`Verity ${moment}: ${authRemedy ? "not authorized" : "service unavailable"} (${result.error}); the ${verb} was allowed WITHOUT a Verity review.${authRemedy}`
|
|
18680
19725
|
);
|
|
18681
19726
|
}
|
|
18682
19727
|
if (opts.json) process.stderr.write(JSON.stringify(result.data) + "\n");
|
|
@@ -18748,14 +19793,14 @@ function writeBlockMessage(moment, response) {
|
|
|
18748
19793
|
// src/commands/init.ts
|
|
18749
19794
|
var import_node_fs29 = require("node:fs");
|
|
18750
19795
|
var import_promises13 = require("node:fs/promises");
|
|
18751
|
-
var
|
|
18752
|
-
var
|
|
19796
|
+
var import_node_path23 = require("node:path");
|
|
19797
|
+
var import_node_child_process10 = require("node:child_process");
|
|
18753
19798
|
var readline2 = __toESM(require("node:readline/promises"));
|
|
18754
19799
|
|
|
18755
19800
|
// src/commands/migrate.ts
|
|
18756
19801
|
var import_node_fs28 = require("node:fs");
|
|
18757
|
-
var
|
|
18758
|
-
var
|
|
19802
|
+
var import_node_path22 = require("node:path");
|
|
19803
|
+
var import_node_child_process9 = require("node:child_process");
|
|
18759
19804
|
|
|
18760
19805
|
// src/lib/telemetry.ts
|
|
18761
19806
|
var import_promises12 = require("node:fs/promises");
|
|
@@ -18850,11 +19895,11 @@ async function uninstallTelemetry() {
|
|
|
18850
19895
|
// src/commands/migrate.ts
|
|
18851
19896
|
var LEGACY_NPM_PACKAGE = "@codacy/gate-cli";
|
|
18852
19897
|
function defaultNpmRemover(pkg) {
|
|
18853
|
-
(0,
|
|
19898
|
+
(0, import_node_child_process9.execSync)(`npm rm -g ${pkg}`, { stdio: "pipe", timeout: 12e4 });
|
|
18854
19899
|
}
|
|
18855
19900
|
function isGitTracked(cwd, relPath) {
|
|
18856
19901
|
try {
|
|
18857
|
-
(0,
|
|
19902
|
+
(0, import_node_child_process9.execSync)(`git ls-files --error-unmatch ${relPath}`, { cwd, stdio: "pipe" });
|
|
18858
19903
|
return true;
|
|
18859
19904
|
} catch {
|
|
18860
19905
|
return false;
|
|
@@ -18862,7 +19907,7 @@ function isGitTracked(cwd, relPath) {
|
|
|
18862
19907
|
}
|
|
18863
19908
|
function isGitRepo(cwd) {
|
|
18864
19909
|
try {
|
|
18865
|
-
(0,
|
|
19910
|
+
(0, import_node_child_process9.execSync)("git rev-parse --is-inside-work-tree", { cwd, stdio: "pipe" });
|
|
18866
19911
|
return true;
|
|
18867
19912
|
} catch {
|
|
18868
19913
|
return false;
|
|
@@ -18883,8 +19928,8 @@ async function runMigration(opts = {}) {
|
|
|
18883
19928
|
return { actions, migrated: actions.length > 0 };
|
|
18884
19929
|
}
|
|
18885
19930
|
function migrateProjectDir(root, actions) {
|
|
18886
|
-
const gateDir = (0,
|
|
18887
|
-
const verityDir = (0,
|
|
19931
|
+
const gateDir = (0, import_node_path22.join)(root, ".gate");
|
|
19932
|
+
const verityDir = (0, import_node_path22.join)(root, ".verity");
|
|
18888
19933
|
if ((0, import_node_fs28.existsSync)(gateDir) && !(0, import_node_fs28.existsSync)(verityDir)) {
|
|
18889
19934
|
return migrateProjectDirRename(root, gateDir, verityDir, actions);
|
|
18890
19935
|
}
|
|
@@ -18902,7 +19947,7 @@ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
|
|
|
18902
19947
|
);
|
|
18903
19948
|
}
|
|
18904
19949
|
try {
|
|
18905
|
-
(0,
|
|
19950
|
+
(0, import_node_child_process9.execSync)("git mv .gate .verity", { cwd: root, stdio: "pipe" });
|
|
18906
19951
|
actions.push("Moved .gate/ \u2192 .verity/ (git mv, staged)");
|
|
18907
19952
|
moved = true;
|
|
18908
19953
|
} catch {
|
|
@@ -18938,11 +19983,11 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
|
|
|
18938
19983
|
}
|
|
18939
19984
|
function migrateGlobalCredentials(home, actions) {
|
|
18940
19985
|
if (!home) return;
|
|
18941
|
-
const gateCreds = (0,
|
|
18942
|
-
const verityCreds = (0,
|
|
19986
|
+
const gateCreds = (0, import_node_path22.join)(home, ".gate", "credentials");
|
|
19987
|
+
const verityCreds = (0, import_node_path22.join)(home, ".verity", "credentials");
|
|
18943
19988
|
if (!(0, import_node_fs28.existsSync)(gateCreds)) return;
|
|
18944
19989
|
if (!(0, import_node_fs28.existsSync)(verityCreds)) {
|
|
18945
|
-
(0, import_node_fs28.mkdirSync)((0,
|
|
19990
|
+
(0, import_node_fs28.mkdirSync)((0, import_node_path22.join)(home, ".verity"), { recursive: true });
|
|
18946
19991
|
moveFile(gateCreds, verityCreds);
|
|
18947
19992
|
actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
|
|
18948
19993
|
return;
|
|
@@ -18964,7 +20009,7 @@ async function migrateLegacyHooks(root, actions) {
|
|
|
18964
20009
|
}
|
|
18965
20010
|
}
|
|
18966
20011
|
async function migrateClaudeMd(root, actions) {
|
|
18967
|
-
const claudeMd = (0,
|
|
20012
|
+
const claudeMd = (0, import_node_path22.join)(root, "CLAUDE.md");
|
|
18968
20013
|
const hadLegacyBlock = (0, import_node_fs28.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
|
|
18969
20014
|
if (!hadLegacyBlock) return;
|
|
18970
20015
|
try {
|
|
@@ -18975,13 +20020,13 @@ async function migrateClaudeMd(root, actions) {
|
|
|
18975
20020
|
}
|
|
18976
20021
|
}
|
|
18977
20022
|
function migrateStandardFile(root, actions) {
|
|
18978
|
-
const gateMd = (0,
|
|
18979
|
-
const verityMd = (0,
|
|
20023
|
+
const gateMd = (0, import_node_path22.join)(root, "GATE.md");
|
|
20024
|
+
const verityMd = (0, import_node_path22.join)(root, "VERITY.md");
|
|
18980
20025
|
if (!(0, import_node_fs28.existsSync)(gateMd) || (0, import_node_fs28.existsSync)(verityMd)) return;
|
|
18981
20026
|
let moved = false;
|
|
18982
20027
|
if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
|
|
18983
20028
|
try {
|
|
18984
|
-
(0,
|
|
20029
|
+
(0, import_node_child_process9.execSync)("git mv GATE.md VERITY.md", { cwd: root, stdio: "pipe" });
|
|
18985
20030
|
moved = true;
|
|
18986
20031
|
} catch {
|
|
18987
20032
|
}
|
|
@@ -18993,7 +20038,7 @@ function migrateStandardFile(root, actions) {
|
|
|
18993
20038
|
actions.push("Renamed GATE.md \u2192 VERITY.md");
|
|
18994
20039
|
}
|
|
18995
20040
|
async function migrateTelemetryHeaders(root, actions) {
|
|
18996
|
-
const file = (0,
|
|
20041
|
+
const file = (0, import_node_path22.join)(root, ".claude", "settings.local.json");
|
|
18997
20042
|
if (!(0, import_node_fs28.existsSync)(file)) return;
|
|
18998
20043
|
let settings;
|
|
18999
20044
|
try {
|
|
@@ -19056,7 +20101,7 @@ function readFileSyncSafe(path) {
|
|
|
19056
20101
|
}
|
|
19057
20102
|
function hasStagedChanges(root) {
|
|
19058
20103
|
try {
|
|
19059
|
-
(0,
|
|
20104
|
+
(0, import_node_child_process9.execSync)("git diff --cached --quiet", { cwd: root, stdio: "pipe" });
|
|
19060
20105
|
return false;
|
|
19061
20106
|
} catch {
|
|
19062
20107
|
return true;
|
|
@@ -19083,15 +20128,15 @@ function moveFile(from, to) {
|
|
|
19083
20128
|
function carryLegacyContents(gateDir, verityDir) {
|
|
19084
20129
|
let copied = 0;
|
|
19085
20130
|
const walk = (relDir) => {
|
|
19086
|
-
const srcDir = (0,
|
|
20131
|
+
const srcDir = (0, import_node_path22.join)(gateDir, relDir);
|
|
19087
20132
|
for (const entry of (0, import_node_fs28.readdirSync)(srcDir)) {
|
|
19088
|
-
const rel = relDir ? (0,
|
|
19089
|
-
const src = (0,
|
|
19090
|
-
const dest = (0,
|
|
20133
|
+
const rel = relDir ? (0, import_node_path22.join)(relDir, entry) : entry;
|
|
20134
|
+
const src = (0, import_node_path22.join)(gateDir, rel);
|
|
20135
|
+
const dest = (0, import_node_path22.join)(verityDir, rel);
|
|
19091
20136
|
if ((0, import_node_fs28.statSync)(src).isDirectory()) {
|
|
19092
20137
|
walk(rel);
|
|
19093
20138
|
} else if (!(0, import_node_fs28.existsSync)(dest)) {
|
|
19094
|
-
(0, import_node_fs28.mkdirSync)((0,
|
|
20139
|
+
(0, import_node_fs28.mkdirSync)((0, import_node_path22.dirname)(dest), { recursive: true });
|
|
19095
20140
|
(0, import_node_fs28.cpSync)(src, dest);
|
|
19096
20141
|
copied++;
|
|
19097
20142
|
}
|
|
@@ -19101,22 +20146,22 @@ function carryLegacyContents(gateDir, verityDir) {
|
|
|
19101
20146
|
return copied;
|
|
19102
20147
|
}
|
|
19103
20148
|
async function needsMigration(root = repoRoot()) {
|
|
19104
|
-
const gateDir = (0,
|
|
19105
|
-
const verityDir = (0,
|
|
20149
|
+
const gateDir = (0, import_node_path22.join)(root, ".gate");
|
|
20150
|
+
const verityDir = (0, import_node_path22.join)(root, ".verity");
|
|
19106
20151
|
if ((0, import_node_fs28.existsSync)(gateDir) && !(0, import_node_fs28.existsSync)(verityDir)) return true;
|
|
19107
20152
|
if ((0, import_node_fs28.existsSync)(gateDir) && (0, import_node_fs28.existsSync)(verityDir)) {
|
|
19108
|
-
if ((0, import_node_fs28.existsSync)((0,
|
|
20153
|
+
if ((0, import_node_fs28.existsSync)((0, import_node_path22.join)(gateDir, "credentials")) && !(0, import_node_fs28.existsSync)((0, import_node_path22.join)(verityDir, "credentials"))) {
|
|
19109
20154
|
return true;
|
|
19110
20155
|
}
|
|
19111
|
-
if ((0, import_node_fs28.existsSync)((0,
|
|
20156
|
+
if ((0, import_node_fs28.existsSync)((0, import_node_path22.join)(gateDir, "memory")) && !(0, import_node_fs28.existsSync)((0, import_node_path22.join)(verityDir, "memory"))) {
|
|
19112
20157
|
return true;
|
|
19113
20158
|
}
|
|
19114
20159
|
}
|
|
19115
|
-
const claudeMd = (0,
|
|
20160
|
+
const claudeMd = (0, import_node_path22.join)(root, "CLAUDE.md");
|
|
19116
20161
|
if ((0, import_node_fs28.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
|
|
19117
20162
|
return true;
|
|
19118
20163
|
}
|
|
19119
|
-
if ((0, import_node_fs28.existsSync)((0,
|
|
20164
|
+
if ((0, import_node_fs28.existsSync)((0, import_node_path22.join)(root, "GATE.md")) && !(0, import_node_fs28.existsSync)((0, import_node_path22.join)(root, "VERITY.md"))) {
|
|
19120
20165
|
return true;
|
|
19121
20166
|
}
|
|
19122
20167
|
if (await hasLegacyHooksAt(root)) return true;
|
|
@@ -19152,12 +20197,25 @@ async function promptYes(question) {
|
|
|
19152
20197
|
rl.close();
|
|
19153
20198
|
}
|
|
19154
20199
|
}
|
|
19155
|
-
async function confirmExistingLogin(serviceUrl, opts) {
|
|
20200
|
+
async function confirmExistingLogin(serviceUrl, remote, opts) {
|
|
19156
20201
|
const existing = await resolveToken(opts.token);
|
|
19157
20202
|
if (!existing.ok) return "drive-login";
|
|
19158
20203
|
const who = await whoami(existing.data.token, serviceUrl, opts.verbose);
|
|
19159
20204
|
if (who.ok && who.data.logged_in) {
|
|
19160
|
-
|
|
20205
|
+
const identity = who.data.email ?? `user #${who.data.user_id}`;
|
|
20206
|
+
const covered = who.data.grant_status != null;
|
|
20207
|
+
if (!remote) {
|
|
20208
|
+
printInfo(`Logged in as ${identity} \u2713`);
|
|
20209
|
+
printInfo(" This directory has no git remote, so there is no project here to sync to.");
|
|
20210
|
+
} else if (covered) {
|
|
20211
|
+
printInfo(`Logged in as ${identity} \u2713 \u2014 runs & memory sync to Verity.`);
|
|
20212
|
+
} else {
|
|
20213
|
+
printInfo(`Logged in as ${identity} \u2713`);
|
|
20214
|
+
printWarn(" This repository is NOT covered by your Verity access grants \u2014 nothing will sync.");
|
|
20215
|
+
printInfo(' Grant the Verity GitHub App access to it, then run "verity login" to refresh your grants.');
|
|
20216
|
+
}
|
|
20217
|
+
const nudge = reverifyNudge(who.data);
|
|
20218
|
+
if (nudge) printWarn(` ${nudge}`);
|
|
19161
20219
|
return "handled";
|
|
19162
20220
|
}
|
|
19163
20221
|
if (!who.ok) {
|
|
@@ -19186,18 +20244,18 @@ async function runOptionalAuth(resolution, opts = {}) {
|
|
|
19186
20244
|
printInfo(" Log in below to re-register this project and repair ~/.verity/credentials.");
|
|
19187
20245
|
}
|
|
19188
20246
|
}
|
|
19189
|
-
if (!healed) {
|
|
19190
|
-
const state = await confirmExistingLogin(serviceUrl, opts);
|
|
19191
|
-
if (state === "handled") return;
|
|
19192
|
-
}
|
|
19193
20247
|
let remote = "";
|
|
19194
20248
|
try {
|
|
19195
|
-
remote = (0,
|
|
20249
|
+
remote = (0, import_node_child_process10.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
19196
20250
|
} catch {
|
|
19197
20251
|
}
|
|
20252
|
+
if (!healed) {
|
|
20253
|
+
const state = await confirmExistingLogin(serviceUrl, remote, opts);
|
|
20254
|
+
if (state === "handled") return;
|
|
20255
|
+
}
|
|
19198
20256
|
const localOnlyNote = () => {
|
|
19199
20257
|
printInfo("Verity runs in local-only mode: the gate still runs and shows static findings, but nothing uploads.");
|
|
19200
|
-
printInfo(' Authenticate anytime: run "verity
|
|
20258
|
+
printInfo(' Authenticate anytime: run "verity login" (one login covers every repo you can write to).');
|
|
19201
20259
|
};
|
|
19202
20260
|
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
19203
20261
|
console.log("");
|
|
@@ -19223,7 +20281,7 @@ async function runOptionalAuth(resolution, opts = {}) {
|
|
|
19223
20281
|
localOnlyNote();
|
|
19224
20282
|
return;
|
|
19225
20283
|
}
|
|
19226
|
-
const projectName = parseRemote(remote)?.repo ?? (0,
|
|
20284
|
+
const projectName = parseRemote(remote)?.repo ?? (0, import_node_path23.basename)(process.cwd());
|
|
19227
20285
|
printInfo("Authenticating with GitHub\u2026");
|
|
19228
20286
|
const result = await registerProject({ projectName, remote, serviceUrl, verbose: opts.verbose });
|
|
19229
20287
|
if (result.ok) {
|
|
@@ -19236,15 +20294,15 @@ async function runOptionalAuth(resolution, opts = {}) {
|
|
|
19236
20294
|
}
|
|
19237
20295
|
function resolveDataDir() {
|
|
19238
20296
|
const candidates = [
|
|
19239
|
-
(0,
|
|
20297
|
+
(0, import_node_path23.join)(__dirname, "..", "data"),
|
|
19240
20298
|
// installed: node_modules/@codacy/verity-cli/data
|
|
19241
|
-
(0,
|
|
20299
|
+
(0, import_node_path23.join)(__dirname, "..", "..", "data"),
|
|
19242
20300
|
// edge case: nested resolution
|
|
19243
|
-
(0,
|
|
20301
|
+
(0, import_node_path23.join)(process.cwd(), "cli", "data")
|
|
19244
20302
|
// local dev: running from repo root
|
|
19245
20303
|
];
|
|
19246
20304
|
for (const candidate of candidates) {
|
|
19247
|
-
if ((0, import_node_fs29.existsSync)((0,
|
|
20305
|
+
if ((0, import_node_fs29.existsSync)((0, import_node_path23.join)(candidate, "skills"))) {
|
|
19248
20306
|
return candidate;
|
|
19249
20307
|
}
|
|
19250
20308
|
}
|
|
@@ -19288,30 +20346,30 @@ function registerInitCommand(program2) {
|
|
|
19288
20346
|
}
|
|
19289
20347
|
printInfo(` Node.js ${nodeVersion} \u2713`);
|
|
19290
20348
|
try {
|
|
19291
|
-
const gitVersion = (0,
|
|
20349
|
+
const gitVersion = (0, import_node_child_process10.execSync)("git --version", { encoding: "utf-8" }).trim();
|
|
19292
20350
|
printInfo(` ${gitVersion} \u2713`);
|
|
19293
20351
|
} catch {
|
|
19294
20352
|
printError("git is required but not installed. Install from https://git-scm.com");
|
|
19295
20353
|
process.exit(1);
|
|
19296
20354
|
}
|
|
19297
20355
|
try {
|
|
19298
|
-
(0,
|
|
20356
|
+
(0, import_node_child_process10.execSync)("which claude", { encoding: "utf-8" });
|
|
19299
20357
|
printInfo(" Claude Code \u2713");
|
|
19300
20358
|
} catch {
|
|
19301
20359
|
printWarn(" Claude Code not found \u2014 hooks will be configured but need Claude Code to run.");
|
|
19302
20360
|
}
|
|
19303
20361
|
try {
|
|
19304
|
-
(0,
|
|
20362
|
+
(0, import_node_child_process10.execSync)("which codacy-analysis", { encoding: "utf-8", stdio: "pipe" });
|
|
19305
20363
|
printInfo(" @codacy/analysis-cli \u2713");
|
|
19306
20364
|
} catch {
|
|
19307
20365
|
printInfo(" Installing @codacy/analysis-cli...");
|
|
19308
20366
|
try {
|
|
19309
|
-
(0,
|
|
20367
|
+
(0, import_node_child_process10.execSync)("npm install -g @codacy/analysis-cli", { encoding: "utf-8", stdio: "pipe", timeout: 12e4 });
|
|
19310
20368
|
printInfo(" @codacy/analysis-cli installed \u2713");
|
|
19311
20369
|
} catch {
|
|
19312
20370
|
try {
|
|
19313
20371
|
printWarn(" Retrying with sudo...");
|
|
19314
|
-
(0,
|
|
20372
|
+
(0, import_node_child_process10.execSync)("sudo npm install -g @codacy/analysis-cli", { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
|
|
19315
20373
|
printInfo(" @codacy/analysis-cli installed \u2713");
|
|
19316
20374
|
} catch {
|
|
19317
20375
|
printWarn(" Could not install @codacy/analysis-cli automatically.");
|
|
@@ -19323,20 +20381,20 @@ function registerInitCommand(program2) {
|
|
|
19323
20381
|
console.log("");
|
|
19324
20382
|
printInfo("Installing skills...");
|
|
19325
20383
|
const dataDir = resolveDataDir();
|
|
19326
|
-
const skillsSource = (0,
|
|
20384
|
+
const skillsSource = (0, import_node_path23.join)(dataDir, "skills");
|
|
19327
20385
|
const skillsDest = ".claude/skills";
|
|
19328
20386
|
const skills = ["verity-setup", "verity-analyze", "verity-status", "verity-feedback", "verity-learn", "verity-memory", "verity-insights", "verity-reflect"];
|
|
19329
20387
|
let skillsInstalled = 0;
|
|
19330
20388
|
for (const skill of skills) {
|
|
19331
|
-
const src = (0,
|
|
19332
|
-
const dest = (0,
|
|
20389
|
+
const src = (0, import_node_path23.join)(skillsSource, skill);
|
|
20390
|
+
const dest = (0, import_node_path23.join)(skillsDest, skill);
|
|
19333
20391
|
if (!(0, import_node_fs29.existsSync)(src)) {
|
|
19334
20392
|
printWarn(` Skill data not found: ${skill}`);
|
|
19335
20393
|
continue;
|
|
19336
20394
|
}
|
|
19337
20395
|
if ((0, import_node_fs29.existsSync)(dest) && !force) {
|
|
19338
|
-
const srcSkill = (0,
|
|
19339
|
-
const destSkill = (0,
|
|
20396
|
+
const srcSkill = (0, import_node_path23.join)(src, "SKILL.md");
|
|
20397
|
+
const destSkill = (0, import_node_path23.join)(dest, "SKILL.md");
|
|
19340
20398
|
if ((0, import_node_fs29.existsSync)(destSkill)) {
|
|
19341
20399
|
try {
|
|
19342
20400
|
const srcContent = await (0, import_promises13.readFile)(srcSkill, "utf-8");
|
|
@@ -19374,7 +20432,7 @@ function registerInitCommand(program2) {
|
|
|
19374
20432
|
} catch (err) {
|
|
19375
20433
|
printWarn(` Could not update CLAUDE.md: ${err.message}`);
|
|
19376
20434
|
}
|
|
19377
|
-
const globalVerityDir = (0,
|
|
20435
|
+
const globalVerityDir = (0, import_node_path23.join)(process.env.HOME ?? "", ".verity");
|
|
19378
20436
|
await (0, import_promises13.mkdir)(globalVerityDir, { recursive: true });
|
|
19379
20437
|
console.log("");
|
|
19380
20438
|
try {
|
|
@@ -19403,14 +20461,14 @@ function registerInitCommand(program2) {
|
|
|
19403
20461
|
console.log(" .verity/memory/ \u2014 knowledge base (8 domains, commit to git)");
|
|
19404
20462
|
console.log("");
|
|
19405
20463
|
console.log(" Next step: open this project in Claude Code and run /verity-setup");
|
|
19406
|
-
console.log(' (Not authenticated? Verity runs in local-only mode until you run "verity
|
|
20464
|
+
console.log(' (Not authenticated? Verity runs in local-only mode until you run "verity login".)');
|
|
19407
20465
|
console.log("");
|
|
19408
20466
|
});
|
|
19409
20467
|
}
|
|
19410
20468
|
|
|
19411
20469
|
// src/commands/uninstall.ts
|
|
19412
20470
|
var import_node_fs30 = require("node:fs");
|
|
19413
|
-
var
|
|
20471
|
+
var import_node_path24 = require("node:path");
|
|
19414
20472
|
var SKILL_NAMES = [
|
|
19415
20473
|
"verity-setup",
|
|
19416
20474
|
"verity-analyze",
|
|
@@ -19429,7 +20487,7 @@ function registerUninstallCommand(program2) {
|
|
|
19429
20487
|
const actions = [];
|
|
19430
20488
|
const skillsRoot = projectPath(".claude/skills");
|
|
19431
20489
|
for (const name of SKILL_NAMES) {
|
|
19432
|
-
const dir = (0,
|
|
20490
|
+
const dir = (0, import_node_path24.join)(skillsRoot, name);
|
|
19433
20491
|
if ((0, import_node_fs30.existsSync)(dir)) {
|
|
19434
20492
|
actions.push({
|
|
19435
20493
|
label: `Remove .claude/skills/${name}/`,
|
|
@@ -19475,7 +20533,7 @@ function registerUninstallCommand(program2) {
|
|
|
19475
20533
|
}
|
|
19476
20534
|
});
|
|
19477
20535
|
const home = process.env.HOME ?? "";
|
|
19478
|
-
const globalVerityDir = (0,
|
|
20536
|
+
const globalVerityDir = (0, import_node_path24.join)(home, ".verity");
|
|
19479
20537
|
if (purgeGlobal && (0, import_node_fs30.existsSync)(globalVerityDir)) {
|
|
19480
20538
|
actions.push({
|
|
19481
20539
|
label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
|
|
@@ -19674,7 +20732,7 @@ function registerTaskCommands(program2) {
|
|
|
19674
20732
|
|
|
19675
20733
|
// src/commands/reset.ts
|
|
19676
20734
|
var import_node_fs31 = require("node:fs");
|
|
19677
|
-
var
|
|
20735
|
+
var import_node_path25 = require("node:path");
|
|
19678
20736
|
function registerResetCommand(program2) {
|
|
19679
20737
|
program2.command("reset").description("Close the current task and clear transient state").option("--keep-task", "Only purge caches; leave the current task open").option("--all", "Also purge diagnostic logs (.verity/.logs/)").action(async (opts) => {
|
|
19680
20738
|
const globals = program2.opts();
|
|
@@ -19715,7 +20773,7 @@ function registerResetCommand(program2) {
|
|
|
19715
20773
|
for (const entry of (0, import_node_fs31.readdirSync)(cacheDir)) {
|
|
19716
20774
|
if (entry.startsWith("pending-")) {
|
|
19717
20775
|
try {
|
|
19718
|
-
(0, import_node_fs31.unlinkSync)((0,
|
|
20776
|
+
(0, import_node_fs31.unlinkSync)((0, import_node_path25.join)(cacheDir, entry));
|
|
19719
20777
|
purged++;
|
|
19720
20778
|
} catch {
|
|
19721
20779
|
}
|
|
@@ -19742,7 +20800,7 @@ function registerResetCommand(program2) {
|
|
|
19742
20800
|
if ((0, import_node_fs31.existsSync)(logsDir)) {
|
|
19743
20801
|
for (const entry of (0, import_node_fs31.readdirSync)(logsDir)) {
|
|
19744
20802
|
try {
|
|
19745
|
-
(0, import_node_fs31.unlinkSync)((0,
|
|
20803
|
+
(0, import_node_fs31.unlinkSync)((0, import_node_path25.join)(logsDir, entry));
|
|
19746
20804
|
} catch {
|
|
19747
20805
|
}
|
|
19748
20806
|
}
|
|
@@ -20010,7 +21068,11 @@ function registerTelemetryCommands(program2) {
|
|
|
20010
21068
|
const globals = program2.opts();
|
|
20011
21069
|
const tokenResult = await resolveToken(globals.token);
|
|
20012
21070
|
if (tokenResult.ok) {
|
|
20013
|
-
|
|
21071
|
+
const remote = requestRemote();
|
|
21072
|
+
printJsonCompact({
|
|
21073
|
+
Authorization: `Bearer ${tokenResult.data.token}`,
|
|
21074
|
+
...remote ? { "X-Verity-Remote": remote } : {}
|
|
21075
|
+
});
|
|
20014
21076
|
} else {
|
|
20015
21077
|
printJsonCompact({});
|
|
20016
21078
|
}
|
|
@@ -20040,7 +21102,7 @@ function registerTelemetryCommands(program2) {
|
|
|
20040
21102
|
}
|
|
20041
21103
|
|
|
20042
21104
|
// src/cli.ts
|
|
20043
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.28.1-experimental.
|
|
21105
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.28.1-experimental.a72d2d9").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async () => {
|
|
20044
21106
|
try {
|
|
20045
21107
|
await foldLegacyLocalCredential();
|
|
20046
21108
|
} catch {
|
|
@@ -20048,6 +21110,9 @@ program.name("verity").description("CLI for Verity quality gate service").versio
|
|
|
20048
21110
|
});
|
|
20049
21111
|
registerAuthCommands(program);
|
|
20050
21112
|
registerLoginCommand(program);
|
|
21113
|
+
registerTokenCommand(program);
|
|
21114
|
+
registerSessionsCommands(program);
|
|
21115
|
+
registerLogoutCommand(program);
|
|
20051
21116
|
registerHooksCommands(program);
|
|
20052
21117
|
registerIntentCommands(program);
|
|
20053
21118
|
registerLifecycleCommands(program);
|