@codacy/verity-cli 0.28.1-experimental.a57c8d9 → 0.28.1-experimental.af3c52d
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 +1287 -668
- 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");
|
|
@@ -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;
|
|
@@ -10857,275 +10789,49 @@ async function foldLegacyLocalCredential(remoteArg) {
|
|
|
10857
10789
|
return true;
|
|
10858
10790
|
}
|
|
10859
10791
|
|
|
10860
|
-
// src/lib/
|
|
10861
|
-
|
|
10862
|
-
|
|
10863
|
-
|
|
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;
|
|
10864
10798
|
}
|
|
10865
|
-
|
|
10799
|
+
function execGit(cmd) {
|
|
10866
10800
|
try {
|
|
10867
|
-
|
|
10868
|
-
const boldLine = content.split("\n").find((l) => /\*\*url\*\*/i.test(l));
|
|
10869
|
-
if (boldLine) {
|
|
10870
|
-
const urlMatch = boldLine.match(/https:\/\/[^\s]+/);
|
|
10871
|
-
if (urlMatch) return urlMatch[0];
|
|
10872
|
-
}
|
|
10873
|
-
const plainLine = content.split("\n").find((l) => /(?:url|service)\s*:/i.test(l));
|
|
10874
|
-
if (plainLine) {
|
|
10875
|
-
const urlMatch = plainLine.match(/https:\/\/[^\s]+/);
|
|
10876
|
-
if (urlMatch) return urlMatch[0];
|
|
10877
|
-
}
|
|
10801
|
+
return (0, import_node_child_process3.execSync)(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
10878
10802
|
} catch {
|
|
10803
|
+
return "";
|
|
10879
10804
|
}
|
|
10880
|
-
return null;
|
|
10881
10805
|
}
|
|
10882
|
-
|
|
10883
|
-
|
|
10884
|
-
|
|
10885
|
-
|
|
10886
|
-
|
|
10887
|
-
if (
|
|
10888
|
-
|
|
10889
|
-
|
|
10890
|
-
|
|
10891
|
-
|
|
10892
|
-
return
|
|
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;
|
|
10893
10817
|
}
|
|
10894
|
-
|
|
10895
|
-
|
|
10896
|
-
|
|
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;
|
|
10897
10826
|
}
|
|
10898
|
-
return
|
|
10899
|
-
}
|
|
10900
|
-
async function resolveServiceUrl(flagUrl) {
|
|
10901
|
-
const result = await resolveServiceUrlDetailed(flagUrl);
|
|
10902
|
-
return result.ok ? { ok: true, data: result.data.url } : result;
|
|
10903
|
-
}
|
|
10904
|
-
function isHealCandidate(resolved) {
|
|
10905
|
-
return (resolved.source === "credentials" || resolved.source === "verity_md") && resolved.url !== DEFAULT_SERVICE_URL;
|
|
10827
|
+
return sha;
|
|
10906
10828
|
}
|
|
10907
|
-
|
|
10908
|
-
|
|
10909
|
-
|
|
10910
|
-
|
|
10911
|
-
|
|
10912
|
-
}
|
|
10913
|
-
const envToken = process.env.VERITY_TOKEN;
|
|
10914
|
-
if (envToken) {
|
|
10915
|
-
return { ok: true, data: { token: envToken, source: "env" } };
|
|
10916
|
-
}
|
|
10917
|
-
const rec = await readGlobalCredential(currentRemote());
|
|
10918
|
-
if (rec) {
|
|
10919
|
-
return {
|
|
10920
|
-
ok: true,
|
|
10921
|
-
data: { token: rec.token, source: "global", userId: rec.userId, email: rec.email }
|
|
10922
|
-
};
|
|
10923
|
-
}
|
|
10924
|
-
const local = await readLegacyLocalCredential();
|
|
10925
|
-
if (local) {
|
|
10926
|
-
return {
|
|
10927
|
-
ok: true,
|
|
10928
|
-
data: { token: local.token, source: "local", userId: local.userId, email: local.email }
|
|
10929
|
-
};
|
|
10930
|
-
}
|
|
10931
|
-
return { ok: false, error: "No Verity token found. Run /verity-setup to configure." };
|
|
10932
|
-
}
|
|
10933
|
-
async function whoami(token, serviceUrl, verbose) {
|
|
10934
|
-
return apiRequest({
|
|
10935
|
-
method: "GET",
|
|
10936
|
-
path: "/auth/whoami",
|
|
10937
|
-
serviceUrl,
|
|
10938
|
-
token,
|
|
10939
|
-
verbose,
|
|
10940
|
-
cmd: "whoami"
|
|
10941
|
-
});
|
|
10942
|
-
}
|
|
10943
|
-
async function probeService(serviceUrl, verbose) {
|
|
10944
|
-
const res = await apiRequest({
|
|
10945
|
-
method: "GET",
|
|
10946
|
-
path: "/auth/whoami",
|
|
10947
|
-
serviceUrl,
|
|
10948
|
-
verbose,
|
|
10949
|
-
timeout: 5e3,
|
|
10950
|
-
cmd: "probe"
|
|
10951
|
-
});
|
|
10952
|
-
if (res.ok || res.http_status != null) return { reachable: true };
|
|
10953
|
-
return { reachable: false, dnsDead: /\bENOTFOUND\b/.test(res.error), error: res.error };
|
|
10954
|
-
}
|
|
10955
|
-
async function maybeHealServiceUrl(resolution, verbose) {
|
|
10956
|
-
if (!isHealCandidate(resolution)) {
|
|
10957
|
-
return { serviceUrl: resolution.url, healed: false };
|
|
10958
|
-
}
|
|
10959
|
-
const probe = await probeService(resolution.url, verbose);
|
|
10960
|
-
if (probe.reachable) {
|
|
10961
|
-
return { serviceUrl: resolution.url, healed: false };
|
|
10962
|
-
}
|
|
10963
|
-
const from = resolution.source === "credentials" ? "~/.verity/credentials" : "VERITY.md";
|
|
10964
|
-
printWarn(`Your configured Verity service URL is unreachable: ${resolution.url}`);
|
|
10965
|
-
printInfo(` (${probe.error})`);
|
|
10966
|
-
if (probe.dnsDead && (await probeService(DEFAULT_SERVICE_URL, verbose)).reachable) {
|
|
10967
|
-
printInfo(` The hostname no longer exists \u2014 the URL in ${from} is stale (e.g. a retired preview backend).`);
|
|
10968
|
-
printInfo(` Falling back to the default Verity service: ${DEFAULT_SERVICE_URL}`);
|
|
10969
|
-
if (resolution.source === "verity_md") {
|
|
10970
|
-
printWarn(` Note: VERITY.md still contains the stale URL \u2014 update it to ${DEFAULT_SERVICE_URL} and commit.`);
|
|
10971
|
-
}
|
|
10972
|
-
return { serviceUrl: DEFAULT_SERVICE_URL, healed: true };
|
|
10973
|
-
}
|
|
10974
|
-
printInfo(" Continuing against the configured URL. If it is stale, log in against the default with:");
|
|
10975
|
-
printInfo(` VERITY_SERVICE_URL=${DEFAULT_SERVICE_URL} verity login`);
|
|
10976
|
-
return { serviceUrl: resolution.url, healed: false };
|
|
10977
|
-
}
|
|
10978
|
-
|
|
10979
|
-
// src/lib/register.ts
|
|
10980
|
-
var readline = __toESM(require("node:readline/promises"));
|
|
10981
|
-
|
|
10982
|
-
// src/lib/provider-auth.ts
|
|
10983
|
-
var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
10984
|
-
var form = (fields) => new URLSearchParams(fields).toString();
|
|
10985
|
-
async function githubAccountId(owner) {
|
|
10986
|
-
try {
|
|
10987
|
-
const res = await fetch(`https://api.github.com/users/${encodeURIComponent(owner)}`, {
|
|
10988
|
-
headers: {
|
|
10989
|
-
Accept: "application/vnd.github+json",
|
|
10990
|
-
"X-GitHub-Api-Version": "2022-11-28",
|
|
10991
|
-
"User-Agent": "verity-cli"
|
|
10992
|
-
}
|
|
10993
|
-
});
|
|
10994
|
-
if (!res.ok) return null;
|
|
10995
|
-
const body = await res.json();
|
|
10996
|
-
return typeof body.id === "number" ? body.id : null;
|
|
10997
|
-
} catch {
|
|
10998
|
-
return null;
|
|
10999
|
-
}
|
|
11000
|
-
}
|
|
11001
|
-
async function githubCanSeeRepo(owner, repo, token) {
|
|
11002
|
-
let res;
|
|
11003
|
-
try {
|
|
11004
|
-
res = await fetch(
|
|
11005
|
-
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`,
|
|
11006
|
-
{
|
|
11007
|
-
headers: {
|
|
11008
|
-
Authorization: `Bearer ${token}`,
|
|
11009
|
-
Accept: "application/vnd.github+json",
|
|
11010
|
-
"X-GitHub-Api-Version": "2022-11-28",
|
|
11011
|
-
"User-Agent": "verity-cli"
|
|
11012
|
-
}
|
|
11013
|
-
}
|
|
11014
|
-
);
|
|
11015
|
-
} catch (err) {
|
|
11016
|
-
return { ok: false, error: `Network error contacting GitHub: ${err.message}` };
|
|
11017
|
-
}
|
|
11018
|
-
if (res.status === 404) return { ok: true, data: false };
|
|
11019
|
-
if (res.ok) return { ok: true, data: true };
|
|
11020
|
-
return { ok: false, error: `GitHub repo lookup failed (HTTP ${res.status})` };
|
|
11021
|
-
}
|
|
11022
|
-
async function githubDeviceFlow() {
|
|
11023
|
-
const override = process.env.VERITY_PROVIDER_TOKEN;
|
|
11024
|
-
if (override) return { ok: true, data: override };
|
|
11025
|
-
let dc;
|
|
11026
|
-
try {
|
|
11027
|
-
const res = await fetch(GITHUB_DEVICE_CODE_URL, {
|
|
11028
|
-
method: "POST",
|
|
11029
|
-
headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
|
|
11030
|
-
body: form({ client_id: GITHUB_CLIENT_ID })
|
|
11031
|
-
});
|
|
11032
|
-
if (!res.ok) {
|
|
11033
|
-
return { ok: false, error: `GitHub device-code request failed (HTTP ${res.status})` };
|
|
11034
|
-
}
|
|
11035
|
-
dc = await res.json();
|
|
11036
|
-
} catch (err) {
|
|
11037
|
-
return { ok: false, error: `Network error contacting GitHub: ${err.message}` };
|
|
11038
|
-
}
|
|
11039
|
-
if (!dc.device_code || !dc.user_code) {
|
|
11040
|
-
return {
|
|
11041
|
-
ok: false,
|
|
11042
|
-
error: "GitHub did not return a device code (is Device Flow enabled on the OAuth app?)"
|
|
11043
|
-
};
|
|
11044
|
-
}
|
|
11045
|
-
printInfo("");
|
|
11046
|
-
printInfo(`To authorize Verity, open: ${dc.verification_uri}`);
|
|
11047
|
-
printInfo(`And enter the code: ${dc.user_code}`);
|
|
11048
|
-
printInfo("Waiting for authorization\u2026");
|
|
11049
|
-
const deadline = Date.now() + (dc.expires_in || 900) * 1e3;
|
|
11050
|
-
let interval = dc.interval || 5;
|
|
11051
|
-
while (Date.now() < deadline) {
|
|
11052
|
-
await sleep(interval * 1e3);
|
|
11053
|
-
let data;
|
|
11054
|
-
try {
|
|
11055
|
-
const res = await fetch(GITHUB_ACCESS_TOKEN_URL, {
|
|
11056
|
-
method: "POST",
|
|
11057
|
-
headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
|
|
11058
|
-
body: form({
|
|
11059
|
-
client_id: GITHUB_CLIENT_ID,
|
|
11060
|
-
device_code: dc.device_code,
|
|
11061
|
-
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
11062
|
-
})
|
|
11063
|
-
});
|
|
11064
|
-
data = await res.json().catch(() => ({}));
|
|
11065
|
-
} catch {
|
|
11066
|
-
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 {
|
|
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 {
|
|
11129
10835
|
}
|
|
11130
10836
|
}
|
|
11131
10837
|
function getChangedFiles() {
|
|
@@ -11218,8 +10924,8 @@ function filterReviewable(files) {
|
|
|
11218
10924
|
const ext = (0, import_node_path3.extname)(f).slice(1);
|
|
11219
10925
|
if (ANALYZABLE_EXTENSIONS.has(ext)) return false;
|
|
11220
10926
|
if (REVIEWABLE_EXTENSIONS.has(ext)) return true;
|
|
11221
|
-
const
|
|
11222
|
-
if (REVIEWABLE_FILENAMES.has(
|
|
10927
|
+
const basename4 = f.split("/").pop() ?? "";
|
|
10928
|
+
if (REVIEWABLE_FILENAMES.has(basename4)) return true;
|
|
11223
10929
|
if (REVIEWABLE_PATH_PATTERNS.some((p) => p.test(f))) return true;
|
|
11224
10930
|
return false;
|
|
11225
10931
|
});
|
|
@@ -11309,47 +11015,446 @@ function listTrackedFiles() {
|
|
|
11309
11015
|
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
|
|
11310
11016
|
return Array.from(set);
|
|
11311
11017
|
}
|
|
11018
|
+
function sanitizeRemote(remote) {
|
|
11019
|
+
return remote.replace(/^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)[^/]*@/, "$1");
|
|
11020
|
+
}
|
|
11312
11021
|
|
|
11313
|
-
// src/lib/
|
|
11314
|
-
var
|
|
11315
|
-
|
|
11316
|
-
|
|
11317
|
-
|
|
11318
|
-
|
|
11319
|
-
|
|
11320
|
-
} finally {
|
|
11321
|
-
rl.close();
|
|
11322
|
-
}
|
|
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;
|
|
11323
11029
|
}
|
|
11324
|
-
|
|
11325
|
-
|
|
11326
|
-
const
|
|
11327
|
-
|
|
11328
|
-
|
|
11329
|
-
|
|
11330
|
-
if (visible.data) return { ok: true, data: void 0 };
|
|
11331
|
-
if (installUrl === null) installUrl = githubAppInstallUrl(await githubAccountId(owner));
|
|
11332
|
-
if (!isInteractive() || attempt >= maxAttempts) {
|
|
11333
|
-
return {
|
|
11334
|
-
ok: false,
|
|
11335
|
-
error: `The Verity GitHub App can't see ${owner}/${repo}. Install it on "${owner}" and grant access to this repository, then re-run:
|
|
11336
|
-
${installUrl}`
|
|
11337
|
-
};
|
|
11338
|
-
}
|
|
11339
|
-
printWarn(`The Verity GitHub App can't see ${owner}/${repo} yet.`);
|
|
11340
|
-
printInfo(`Install it on "${owner}" (grant access to this repo):`);
|
|
11341
|
-
printInfo(` ${installUrl}`);
|
|
11342
|
-
await waitForEnter("Press Enter once installed to retry\u2026 ");
|
|
11343
|
-
}
|
|
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}`;
|
|
11344
11036
|
}
|
|
11345
|
-
async function
|
|
11346
|
-
const
|
|
11347
|
-
|
|
11348
|
-
|
|
11349
|
-
|
|
11350
|
-
|
|
11351
|
-
|
|
11352
|
-
|
|
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
|
+
});
|
|
11157
|
+
}
|
|
11158
|
+
|
|
11159
|
+
// src/lib/service-url.ts
|
|
11160
|
+
var import_promises2 = require("node:fs/promises");
|
|
11161
|
+
async function serviceUrlFromCredentials() {
|
|
11162
|
+
const rec = await readGlobalCredential(currentRemote());
|
|
11163
|
+
return rec?.serviceUrl ?? null;
|
|
11164
|
+
}
|
|
11165
|
+
async function serviceUrlFromVerityMd() {
|
|
11166
|
+
try {
|
|
11167
|
+
const content = await (0, import_promises2.readFile)(projectPath(VERITY_MD_FILE), "utf-8");
|
|
11168
|
+
const boldLine = content.split("\n").find((l) => /\*\*url\*\*/i.test(l));
|
|
11169
|
+
if (boldLine) {
|
|
11170
|
+
const urlMatch = boldLine.match(/https:\/\/[^\s]+/);
|
|
11171
|
+
if (urlMatch) return urlMatch[0];
|
|
11172
|
+
}
|
|
11173
|
+
const plainLine = content.split("\n").find((l) => /(?:url|service)\s*:/i.test(l));
|
|
11174
|
+
if (plainLine) {
|
|
11175
|
+
const urlMatch = plainLine.match(/https:\/\/[^\s]+/);
|
|
11176
|
+
if (urlMatch) return urlMatch[0];
|
|
11177
|
+
}
|
|
11178
|
+
} catch {
|
|
11179
|
+
}
|
|
11180
|
+
return null;
|
|
11181
|
+
}
|
|
11182
|
+
async function resolveServiceUrlDetailed(flagUrl) {
|
|
11183
|
+
if (flagUrl) {
|
|
11184
|
+
return { ok: true, data: { url: flagUrl, source: "flag" } };
|
|
11185
|
+
}
|
|
11186
|
+
const envUrl = process.env.VERITY_SERVICE_URL;
|
|
11187
|
+
if (envUrl) {
|
|
11188
|
+
return { ok: true, data: { url: envUrl, source: "env" } };
|
|
11189
|
+
}
|
|
11190
|
+
const credsUrl = await serviceUrlFromCredentials();
|
|
11191
|
+
if (credsUrl) {
|
|
11192
|
+
return { ok: true, data: { url: credsUrl, source: "credentials" } };
|
|
11193
|
+
}
|
|
11194
|
+
const mdUrl = await serviceUrlFromVerityMd();
|
|
11195
|
+
if (mdUrl) {
|
|
11196
|
+
return { ok: true, data: { url: mdUrl, source: "verity_md" } };
|
|
11197
|
+
}
|
|
11198
|
+
return { ok: false, error: "No Verity service URL found. Run /verity-setup to configure." };
|
|
11199
|
+
}
|
|
11200
|
+
async function resolveServiceUrl(flagUrl) {
|
|
11201
|
+
const result = await resolveServiceUrlDetailed(flagUrl);
|
|
11202
|
+
return result.ok ? { ok: true, data: result.data.url } : result;
|
|
11203
|
+
}
|
|
11204
|
+
function isHealCandidate(resolved) {
|
|
11205
|
+
return (resolved.source === "credentials" || resolved.source === "verity_md") && resolved.url !== DEFAULT_SERVICE_URL;
|
|
11206
|
+
}
|
|
11207
|
+
|
|
11208
|
+
// src/lib/auth.ts
|
|
11209
|
+
async function resolveToken(flagToken) {
|
|
11210
|
+
if (flagToken) {
|
|
11211
|
+
return { ok: true, data: { token: flagToken, source: "flag" } };
|
|
11212
|
+
}
|
|
11213
|
+
const envToken = process.env.VERITY_TOKEN;
|
|
11214
|
+
if (envToken) {
|
|
11215
|
+
return { ok: true, data: { token: envToken, source: "env" } };
|
|
11216
|
+
}
|
|
11217
|
+
const rec = await readGlobalCredential(currentRemote());
|
|
11218
|
+
if (rec) {
|
|
11219
|
+
return {
|
|
11220
|
+
ok: true,
|
|
11221
|
+
data: { token: rec.token, source: "global", userId: rec.userId, email: rec.email }
|
|
11222
|
+
};
|
|
11223
|
+
}
|
|
11224
|
+
const local = await readLegacyLocalCredential();
|
|
11225
|
+
if (local) {
|
|
11226
|
+
return {
|
|
11227
|
+
ok: true,
|
|
11228
|
+
data: { token: local.token, source: "local", userId: local.userId, email: local.email }
|
|
11229
|
+
};
|
|
11230
|
+
}
|
|
11231
|
+
return { ok: false, error: "No Verity token found. Run /verity-setup to configure." };
|
|
11232
|
+
}
|
|
11233
|
+
async function whoami(token, serviceUrl, verbose) {
|
|
11234
|
+
return apiRequest({
|
|
11235
|
+
method: "GET",
|
|
11236
|
+
path: "/auth/whoami",
|
|
11237
|
+
serviceUrl,
|
|
11238
|
+
token,
|
|
11239
|
+
verbose,
|
|
11240
|
+
cmd: "whoami"
|
|
11241
|
+
});
|
|
11242
|
+
}
|
|
11243
|
+
function reverifyNudge(who) {
|
|
11244
|
+
if (who.grant_status === "hard_stale") {
|
|
11245
|
+
return 'Your GitHub verification has expired \u2014 run "verity login" to restore saved runs and memory.';
|
|
11246
|
+
}
|
|
11247
|
+
if (who.grant_status === "soft_stale") {
|
|
11248
|
+
const by = who.reverify_by ? ` by ${who.reverify_by.slice(0, 10)}` : " soon";
|
|
11249
|
+
return `Your GitHub verification needs a refresh${by} \u2014 run "verity login" to re-verify.`;
|
|
11250
|
+
}
|
|
11251
|
+
return null;
|
|
11252
|
+
}
|
|
11253
|
+
function authDenialRemedy(error) {
|
|
11254
|
+
if (error.startsWith("STALE_VERIFICATION")) {
|
|
11255
|
+
return {
|
|
11256
|
+
code: "STALE_VERIFICATION",
|
|
11257
|
+
remedy: 'Your GitHub verification has expired \u2014 run "verity login" to re-verify your repository access.'
|
|
11258
|
+
};
|
|
11259
|
+
}
|
|
11260
|
+
if (error.startsWith("FORBIDDEN")) {
|
|
11261
|
+
return {
|
|
11262
|
+
code: "FORBIDDEN",
|
|
11263
|
+
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.'
|
|
11264
|
+
};
|
|
11265
|
+
}
|
|
11266
|
+
if (error.startsWith("INVALID_TOKEN")) {
|
|
11267
|
+
return {
|
|
11268
|
+
code: "INVALID_TOKEN",
|
|
11269
|
+
remedy: 'Your Verity login has expired or was revoked \u2014 run "verity login" to sign in again.'
|
|
11270
|
+
};
|
|
11271
|
+
}
|
|
11272
|
+
return null;
|
|
11273
|
+
}
|
|
11274
|
+
async function probeService(serviceUrl, verbose) {
|
|
11275
|
+
const res = await apiRequest({
|
|
11276
|
+
method: "GET",
|
|
11277
|
+
path: "/auth/whoami",
|
|
11278
|
+
serviceUrl,
|
|
11279
|
+
verbose,
|
|
11280
|
+
timeout: 5e3,
|
|
11281
|
+
cmd: "probe"
|
|
11282
|
+
});
|
|
11283
|
+
if (res.ok || res.http_status != null) return { reachable: true };
|
|
11284
|
+
return { reachable: false, dnsDead: /\bENOTFOUND\b/.test(res.error), error: res.error };
|
|
11285
|
+
}
|
|
11286
|
+
async function maybeHealServiceUrl(resolution, verbose) {
|
|
11287
|
+
if (!isHealCandidate(resolution)) {
|
|
11288
|
+
return { serviceUrl: resolution.url, healed: false };
|
|
11289
|
+
}
|
|
11290
|
+
const probe = await probeService(resolution.url, verbose);
|
|
11291
|
+
if (probe.reachable) {
|
|
11292
|
+
return { serviceUrl: resolution.url, healed: false };
|
|
11293
|
+
}
|
|
11294
|
+
const from = resolution.source === "credentials" ? "~/.verity/credentials" : "VERITY.md";
|
|
11295
|
+
printWarn(`Your configured Verity service URL is unreachable: ${resolution.url}`);
|
|
11296
|
+
printInfo(` (${probe.error})`);
|
|
11297
|
+
if (probe.dnsDead && (await probeService(DEFAULT_SERVICE_URL, verbose)).reachable) {
|
|
11298
|
+
printInfo(` The hostname no longer exists \u2014 the URL in ${from} is stale (e.g. a retired preview backend).`);
|
|
11299
|
+
printInfo(` Falling back to the default Verity service: ${DEFAULT_SERVICE_URL}`);
|
|
11300
|
+
if (resolution.source === "verity_md") {
|
|
11301
|
+
printWarn(` Note: VERITY.md still contains the stale URL \u2014 update it to ${DEFAULT_SERVICE_URL} and commit.`);
|
|
11302
|
+
}
|
|
11303
|
+
return { serviceUrl: DEFAULT_SERVICE_URL, healed: true };
|
|
11304
|
+
}
|
|
11305
|
+
printInfo(" Continuing against the configured URL. If it is stale, log in against the default with:");
|
|
11306
|
+
printInfo(` VERITY_SERVICE_URL=${DEFAULT_SERVICE_URL} verity login`);
|
|
11307
|
+
return { serviceUrl: resolution.url, healed: false };
|
|
11308
|
+
}
|
|
11309
|
+
|
|
11310
|
+
// src/lib/register.ts
|
|
11311
|
+
var readline = __toESM(require("node:readline/promises"));
|
|
11312
|
+
var import_node_os = require("node:os");
|
|
11313
|
+
|
|
11314
|
+
// src/lib/provider-auth.ts
|
|
11315
|
+
var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
11316
|
+
var form = (fields) => new URLSearchParams(fields).toString();
|
|
11317
|
+
async function githubAccountId(owner) {
|
|
11318
|
+
try {
|
|
11319
|
+
const res = await fetch(`https://api.github.com/users/${encodeURIComponent(owner)}`, {
|
|
11320
|
+
headers: {
|
|
11321
|
+
Accept: "application/vnd.github+json",
|
|
11322
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
11323
|
+
"User-Agent": "verity-cli"
|
|
11324
|
+
}
|
|
11325
|
+
});
|
|
11326
|
+
if (!res.ok) return null;
|
|
11327
|
+
const body = await res.json();
|
|
11328
|
+
return typeof body.id === "number" ? body.id : null;
|
|
11329
|
+
} catch {
|
|
11330
|
+
return null;
|
|
11331
|
+
}
|
|
11332
|
+
}
|
|
11333
|
+
async function githubCanSeeRepo(owner, repo, token) {
|
|
11334
|
+
let res;
|
|
11335
|
+
try {
|
|
11336
|
+
res = await fetch(
|
|
11337
|
+
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`,
|
|
11338
|
+
{
|
|
11339
|
+
headers: {
|
|
11340
|
+
Authorization: `Bearer ${token}`,
|
|
11341
|
+
Accept: "application/vnd.github+json",
|
|
11342
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
11343
|
+
"User-Agent": "verity-cli"
|
|
11344
|
+
}
|
|
11345
|
+
}
|
|
11346
|
+
);
|
|
11347
|
+
} catch (err) {
|
|
11348
|
+
return { ok: false, error: `Network error contacting GitHub: ${err.message}` };
|
|
11349
|
+
}
|
|
11350
|
+
if (res.status === 404) return { ok: true, data: false };
|
|
11351
|
+
if (res.ok) return { ok: true, data: true };
|
|
11352
|
+
return { ok: false, error: `GitHub repo lookup failed (HTTP ${res.status})` };
|
|
11353
|
+
}
|
|
11354
|
+
async function githubDeviceFlow() {
|
|
11355
|
+
const override = process.env.VERITY_PROVIDER_TOKEN;
|
|
11356
|
+
if (override) return { ok: true, data: override };
|
|
11357
|
+
let dc;
|
|
11358
|
+
try {
|
|
11359
|
+
const res = await fetch(GITHUB_DEVICE_CODE_URL, {
|
|
11360
|
+
method: "POST",
|
|
11361
|
+
headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
|
|
11362
|
+
body: form({ client_id: GITHUB_CLIENT_ID })
|
|
11363
|
+
});
|
|
11364
|
+
if (!res.ok) {
|
|
11365
|
+
return { ok: false, error: `GitHub device-code request failed (HTTP ${res.status})` };
|
|
11366
|
+
}
|
|
11367
|
+
dc = await res.json();
|
|
11368
|
+
} catch (err) {
|
|
11369
|
+
return { ok: false, error: `Network error contacting GitHub: ${err.message}` };
|
|
11370
|
+
}
|
|
11371
|
+
if (!dc.device_code || !dc.user_code) {
|
|
11372
|
+
return {
|
|
11373
|
+
ok: false,
|
|
11374
|
+
error: "GitHub did not return a device code (is Device Flow enabled on the OAuth app?)"
|
|
11375
|
+
};
|
|
11376
|
+
}
|
|
11377
|
+
printInfo("");
|
|
11378
|
+
printInfo(`To authorize Verity, open: ${dc.verification_uri}`);
|
|
11379
|
+
printInfo(`And enter the code: ${dc.user_code}`);
|
|
11380
|
+
printInfo("Waiting for authorization\u2026");
|
|
11381
|
+
const deadline = Date.now() + (dc.expires_in || 900) * 1e3;
|
|
11382
|
+
let interval = dc.interval || 5;
|
|
11383
|
+
while (Date.now() < deadline) {
|
|
11384
|
+
await sleep(interval * 1e3);
|
|
11385
|
+
let data;
|
|
11386
|
+
try {
|
|
11387
|
+
const res = await fetch(GITHUB_ACCESS_TOKEN_URL, {
|
|
11388
|
+
method: "POST",
|
|
11389
|
+
headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
|
|
11390
|
+
body: form({
|
|
11391
|
+
client_id: GITHUB_CLIENT_ID,
|
|
11392
|
+
device_code: dc.device_code,
|
|
11393
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
11394
|
+
})
|
|
11395
|
+
});
|
|
11396
|
+
data = await res.json().catch(() => ({}));
|
|
11397
|
+
} catch {
|
|
11398
|
+
continue;
|
|
11399
|
+
}
|
|
11400
|
+
if (data.access_token) return { ok: true, data: data.access_token };
|
|
11401
|
+
switch (data.error) {
|
|
11402
|
+
case "authorization_pending":
|
|
11403
|
+
break;
|
|
11404
|
+
case "slow_down":
|
|
11405
|
+
interval += 5;
|
|
11406
|
+
break;
|
|
11407
|
+
case "access_denied":
|
|
11408
|
+
return { ok: false, error: "Authorization was denied on GitHub." };
|
|
11409
|
+
case "expired_token":
|
|
11410
|
+
return { ok: false, error: "The authorization code expired. Re-run register." };
|
|
11411
|
+
default:
|
|
11412
|
+
if (data.error) return { ok: false, error: `GitHub auth error: ${data.error}` };
|
|
11413
|
+
}
|
|
11414
|
+
}
|
|
11415
|
+
return { ok: false, error: "Timed out waiting for GitHub authorization." };
|
|
11416
|
+
}
|
|
11417
|
+
|
|
11418
|
+
// src/lib/register.ts
|
|
11419
|
+
var isInteractive = () => Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
11420
|
+
async function waitForEnter(prompt) {
|
|
11421
|
+
if (!isInteractive()) return;
|
|
11422
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
11423
|
+
try {
|
|
11424
|
+
await rl.question(prompt);
|
|
11425
|
+
} finally {
|
|
11426
|
+
rl.close();
|
|
11427
|
+
}
|
|
11428
|
+
}
|
|
11429
|
+
async function ensureAppInstalled(owner, repo, token) {
|
|
11430
|
+
let installUrl = null;
|
|
11431
|
+
const maxAttempts = 3;
|
|
11432
|
+
for (let attempt = 1; ; attempt++) {
|
|
11433
|
+
const visible = await githubCanSeeRepo(owner, repo, token);
|
|
11434
|
+
if (!visible.ok) return { ok: true, data: void 0 };
|
|
11435
|
+
if (visible.data) return { ok: true, data: void 0 };
|
|
11436
|
+
if (installUrl === null) installUrl = githubAppInstallUrl(await githubAccountId(owner));
|
|
11437
|
+
if (!isInteractive() || attempt >= maxAttempts) {
|
|
11438
|
+
return {
|
|
11439
|
+
ok: false,
|
|
11440
|
+
error: `The Verity GitHub App can't see ${owner}/${repo}. Install it on "${owner}" and grant access to this repository, then re-run:
|
|
11441
|
+
${installUrl}`
|
|
11442
|
+
};
|
|
11443
|
+
}
|
|
11444
|
+
printWarn(`The Verity GitHub App can't see ${owner}/${repo} yet.`);
|
|
11445
|
+
printInfo(`Install it on "${owner}" (grant access to this repo):`);
|
|
11446
|
+
printInfo(` ${installUrl}`);
|
|
11447
|
+
await waitForEnter("Press Enter once installed to retry\u2026 ");
|
|
11448
|
+
}
|
|
11449
|
+
}
|
|
11450
|
+
async function registerProject(opts) {
|
|
11451
|
+
const parsed = parseRemote(opts.remote);
|
|
11452
|
+
if (!parsed) {
|
|
11453
|
+
return { ok: false, error: `Could not parse git remote: ${opts.remote}` };
|
|
11454
|
+
}
|
|
11455
|
+
if (parsed.provider !== "github") {
|
|
11456
|
+
return { ok: false, error: `Provider '${parsed.provider}' is not supported yet \u2014 GitHub only for now.` };
|
|
11457
|
+
}
|
|
11353
11458
|
const usingTokenOverride = Boolean(process.env.VERITY_PROVIDER_TOKEN);
|
|
11354
11459
|
const providerAuth = await githubDeviceFlow();
|
|
11355
11460
|
if (!providerAuth.ok) {
|
|
@@ -11392,11 +11497,81 @@ async function registerProject(opts) {
|
|
|
11392
11497
|
}
|
|
11393
11498
|
return { ok: true, data: { projectId: project_id, serviceUrl: service_url, email, userId } };
|
|
11394
11499
|
}
|
|
11500
|
+
function deviceLabel() {
|
|
11501
|
+
const override = process.env.VERITY_DEVICE_LABEL?.trim();
|
|
11502
|
+
if (override) return override;
|
|
11503
|
+
try {
|
|
11504
|
+
return (0, import_node_os.hostname)() || void 0;
|
|
11505
|
+
} catch {
|
|
11506
|
+
return void 0;
|
|
11507
|
+
}
|
|
11508
|
+
}
|
|
11509
|
+
async function loginOnce(opts) {
|
|
11510
|
+
const usingTokenOverride = Boolean(process.env.VERITY_PROVIDER_TOKEN);
|
|
11511
|
+
const providerAuth = await githubDeviceFlow();
|
|
11512
|
+
if (!providerAuth.ok) {
|
|
11513
|
+
return { ok: false, error: providerAuth.error };
|
|
11514
|
+
}
|
|
11515
|
+
const providerToken = providerAuth.data;
|
|
11516
|
+
const parsed = opts.remote ? parseRemote(opts.remote) : null;
|
|
11517
|
+
if (!usingTokenOverride && parsed && parsed.provider === "github") {
|
|
11518
|
+
const installed = await ensureAppInstalled(parsed.owner, parsed.repo, providerToken);
|
|
11519
|
+
if (!installed.ok) {
|
|
11520
|
+
printWarn(installed.error);
|
|
11521
|
+
printInfo("Continuing login \u2014 repositories on other accounts are still granted.");
|
|
11522
|
+
}
|
|
11523
|
+
}
|
|
11524
|
+
const result = await apiRequest({
|
|
11525
|
+
method: "POST",
|
|
11526
|
+
path: "/auth/login",
|
|
11527
|
+
serviceUrl: opts.serviceUrl,
|
|
11528
|
+
extraHeaders: { "X-Provider-Token": providerToken },
|
|
11529
|
+
// Label the session so its owner can tell their machines apart in
|
|
11530
|
+
// `verity sessions list` — a list of identical "login" rows is unusable when
|
|
11531
|
+
// the question is "which of these is the laptop I lost?". The hostname is the
|
|
11532
|
+
// useful default; VERITY_DEVICE_LABEL overrides it for anyone who would
|
|
11533
|
+
// rather not send it. Server-side it is sanitized and capped.
|
|
11534
|
+
body: { device: deviceLabel() },
|
|
11535
|
+
verbose: opts.verbose,
|
|
11536
|
+
cmd: "login"
|
|
11537
|
+
});
|
|
11538
|
+
if (!result.ok) {
|
|
11539
|
+
return { ok: false, error: result.error };
|
|
11540
|
+
}
|
|
11541
|
+
const { token, service_url, user_id, user, repo_count, expires_at } = result.data;
|
|
11542
|
+
const loginUserId = user_id ?? user?.id ?? void 0;
|
|
11543
|
+
try {
|
|
11544
|
+
await upsertGlobalCredential("", {
|
|
11545
|
+
token,
|
|
11546
|
+
serviceUrl: service_url,
|
|
11547
|
+
userId: loginUserId,
|
|
11548
|
+
email: user?.email
|
|
11549
|
+
});
|
|
11550
|
+
} catch (err) {
|
|
11551
|
+
return {
|
|
11552
|
+
ok: false,
|
|
11553
|
+
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".`
|
|
11554
|
+
};
|
|
11555
|
+
}
|
|
11556
|
+
const pruned = await removeSupersededUserCredentials(service_url, loginUserId);
|
|
11557
|
+
return {
|
|
11558
|
+
ok: true,
|
|
11559
|
+
data: {
|
|
11560
|
+
token,
|
|
11561
|
+
serviceUrl: service_url,
|
|
11562
|
+
email: user?.email,
|
|
11563
|
+
userId: loginUserId,
|
|
11564
|
+
repoCount: repo_count ?? 0,
|
|
11565
|
+
prunedCredentials: pruned,
|
|
11566
|
+
expiresAt: expires_at
|
|
11567
|
+
}
|
|
11568
|
+
};
|
|
11569
|
+
}
|
|
11395
11570
|
|
|
11396
11571
|
// src/commands/auth.ts
|
|
11397
11572
|
function registerAuthCommands(program2) {
|
|
11398
|
-
const
|
|
11399
|
-
|
|
11573
|
+
const auth2 = program2.command("auth").description("Manage project authentication");
|
|
11574
|
+
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
11575
|
const globals = program2.opts();
|
|
11401
11576
|
const serviceUrl = globals.serviceUrl ?? DEFAULT_SERVICE_URL;
|
|
11402
11577
|
let remote = opts.remote;
|
|
@@ -11423,7 +11598,7 @@ function registerAuthCommands(program2) {
|
|
|
11423
11598
|
if (email) printInfo(`Authenticated as: ${email}`);
|
|
11424
11599
|
printJson({ project_id: projectId, service_url: resolvedUrl });
|
|
11425
11600
|
});
|
|
11426
|
-
|
|
11601
|
+
auth2.command("verify").description("Verify the current token is valid").action(async () => {
|
|
11427
11602
|
const globals = program2.opts();
|
|
11428
11603
|
const tokenResult = await resolveToken(globals.token);
|
|
11429
11604
|
if (!tokenResult.ok) {
|
|
@@ -11449,7 +11624,7 @@ function registerAuthCommands(program2) {
|
|
|
11449
11624
|
printInfo(`Token valid. Project: ${result.data.project_name}`);
|
|
11450
11625
|
printJson(result.data);
|
|
11451
11626
|
});
|
|
11452
|
-
|
|
11627
|
+
auth2.command("discover").description("Check if a project is registered").option("--remote <url>", "Git remote URL (auto-detected if omitted)").action(async (opts) => {
|
|
11453
11628
|
const globals = program2.opts();
|
|
11454
11629
|
let remote = opts.remote;
|
|
11455
11630
|
if (!remote) {
|
|
@@ -11481,10 +11656,8 @@ function registerAuthCommands(program2) {
|
|
|
11481
11656
|
}
|
|
11482
11657
|
|
|
11483
11658
|
// src/commands/login.ts
|
|
11484
|
-
var import_node_child_process5 = require("node:child_process");
|
|
11485
|
-
var import_node_path4 = require("node:path");
|
|
11486
11659
|
function registerLoginCommand(program2) {
|
|
11487
|
-
program2.command("login").description("Log in to Verity (
|
|
11660
|
+
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
11661
|
const globals = program2.opts();
|
|
11489
11662
|
const urlResult = await resolveServiceUrlDetailed(globals.serviceUrl);
|
|
11490
11663
|
if (!urlResult.ok) {
|
|
@@ -11494,60 +11667,383 @@ function registerLoginCommand(program2) {
|
|
|
11494
11667
|
const heal = await maybeHealServiceUrl(urlResult.data, globals.verbose);
|
|
11495
11668
|
const serviceUrl = heal.serviceUrl;
|
|
11496
11669
|
if (heal.healed) {
|
|
11497
|
-
printInfo(" Completing login
|
|
11670
|
+
printInfo(" Completing login updates ~/.verity/credentials against the live service.");
|
|
11671
|
+
}
|
|
11672
|
+
const remote = currentRemote();
|
|
11673
|
+
if (remote && !parseRemote(remote)) {
|
|
11674
|
+
printWarn(`This repository's origin remote is not a recognizable git URL: ${remote}`);
|
|
11675
|
+
printInfo(" Verity cannot identify the repository from it, so runs here will not be saved.");
|
|
11676
|
+
printInfo(" Point origin at the full URL (e.g. git@github.com:owner/repo.git) to fix it.");
|
|
11498
11677
|
}
|
|
11499
11678
|
const existing = await resolveToken(globals.token);
|
|
11500
11679
|
if (existing.ok && !opts.force && !heal.healed) {
|
|
11501
|
-
|
|
11502
|
-
|
|
11503
|
-
|
|
11504
|
-
|
|
11505
|
-
|
|
11506
|
-
|
|
11507
|
-
|
|
11508
|
-
|
|
11509
|
-
|
|
11510
|
-
|
|
11511
|
-
}
|
|
11512
|
-
if (who2.ok && who2.data.anonymous) {
|
|
11680
|
+
const who = await whoami(existing.data.token, serviceUrl, globals.verbose);
|
|
11681
|
+
if (who.ok && who.data.logged_in) {
|
|
11682
|
+
const nudge = reverifyNudge(who.data);
|
|
11683
|
+
if (!nudge) {
|
|
11684
|
+
printInfo(`Already logged in as ${who.data.email ?? `user #${who.data.user_id}`}. \u2713`);
|
|
11685
|
+
printInfo(" Re-authenticate with: verity login --force");
|
|
11686
|
+
return;
|
|
11687
|
+
}
|
|
11688
|
+
printWarn(nudge);
|
|
11689
|
+
printInfo("Re-verifying your repository access\u2026");
|
|
11690
|
+
} else if (who.ok && who.data.anonymous) {
|
|
11513
11691
|
printInfo("You have an anonymous token (the gate runs, but nothing is saved). Logging you in\u2026");
|
|
11514
|
-
} else if (!
|
|
11692
|
+
} else if (!who.ok) {
|
|
11515
11693
|
printWarn("Could not confirm your current login state with the service \u2014 proceeding to log in.");
|
|
11516
11694
|
}
|
|
11517
11695
|
}
|
|
11518
|
-
|
|
11519
|
-
|
|
11520
|
-
|
|
11521
|
-
|
|
11696
|
+
printInfo("Authenticating with GitHub\u2026");
|
|
11697
|
+
const result = await loginOnce({ serviceUrl, remote: remote || void 0, verbose: globals.verbose });
|
|
11698
|
+
if (!result.ok) {
|
|
11699
|
+
printError(`Login failed: ${result.error}`);
|
|
11700
|
+
process.exit(1);
|
|
11522
11701
|
}
|
|
11523
|
-
|
|
11524
|
-
|
|
11525
|
-
|
|
11702
|
+
const out = result.data;
|
|
11703
|
+
const identity = out.email ?? (out.userId != null ? `user #${out.userId}` : "your account");
|
|
11704
|
+
printInfo(`Logged in as ${identity}. \u2713`);
|
|
11705
|
+
printInfo(` Access granted to ${out.repoCount} ${out.repoCount === 1 ? "repository" : "repositories"}.`);
|
|
11706
|
+
if (out.expiresAt) {
|
|
11707
|
+
printInfo(` This login expires on ${out.expiresAt.slice(0, 10)} \u2014 "verity login" again to renew.`);
|
|
11708
|
+
printInfo(' See your machines with "verity sessions list"; sign out with "verity logout".');
|
|
11709
|
+
}
|
|
11710
|
+
printInfo(" Runs, history, and cloud memory now sync to Verity everywhere you have write access.");
|
|
11711
|
+
if (out.prunedCredentials > 0) {
|
|
11712
|
+
printVerbose(`Pruned ${out.prunedCredentials} superseded per-repository credential line(s).`, globals.verbose);
|
|
11713
|
+
} else if (out.prunedCredentials < 0) {
|
|
11714
|
+
printWarn(" Could not rewrite ~/.verity/credentials: superseded per-repository tokens remain and");
|
|
11715
|
+
printWarn(" will keep taking precedence over this login in their own repositories.");
|
|
11716
|
+
printInfo(` Check the file's permissions; the next successful "verity login" retries the cleanup.`);
|
|
11717
|
+
}
|
|
11718
|
+
if (out.repoCount === 0) {
|
|
11719
|
+
printWarn("The Verity GitHub App is not installed on any account you can access.");
|
|
11720
|
+
printInfo(` Install it (and grant your repositories), then re-run verity login:`);
|
|
11721
|
+
printInfo(` ${githubAppInstallUrl(null)}`);
|
|
11722
|
+
return;
|
|
11723
|
+
}
|
|
11724
|
+
if (remote) {
|
|
11725
|
+
const who = await whoami(out.token, out.serviceUrl, globals.verbose);
|
|
11726
|
+
if (who.ok && who.data.grant_status != null) {
|
|
11727
|
+
printInfo(" \u2713 This repository is covered.");
|
|
11728
|
+
} else if (!who.ok) {
|
|
11729
|
+
printWarn(` Could not confirm this repository's coverage (${who.error}) \u2014 verity status will show it.`);
|
|
11730
|
+
} else {
|
|
11731
|
+
const parsed = parseRemote(remote);
|
|
11732
|
+
const installUrl = githubAppInstallUrl(parsed ? await githubAccountId(parsed.owner) : null);
|
|
11733
|
+
printWarn(` This repository (${parsed ? `${parsed.owner}/${parsed.repo}` : remote}) is NOT covered by your grants.`);
|
|
11734
|
+
printInfo(" Grant the Verity GitHub App access to it, then re-run verity login:");
|
|
11735
|
+
printInfo(` ${installUrl}`);
|
|
11736
|
+
}
|
|
11737
|
+
const rec = await readGlobalCredential(remote);
|
|
11738
|
+
if (rec && rec.token !== out.token) {
|
|
11739
|
+
const otherBackend = rec.serviceUrl != null && rec.serviceUrl !== out.serviceUrl;
|
|
11740
|
+
if (otherBackend) {
|
|
11741
|
+
printWarn(` Note: this repository is pinned to a different Verity service (${rec.serviceUrl})`);
|
|
11742
|
+
printWarn(" by its own credential line, which takes precedence here \u2014 this login does not");
|
|
11743
|
+
printWarn(" change that. To move the repository, remove its line from ~/.verity/credentials.");
|
|
11744
|
+
} else {
|
|
11745
|
+
const kind = rec.userId != null ? "superseded per-repository" : "anonymous project-specific";
|
|
11746
|
+
printWarn(` Note: this repository has a ${kind} credential that takes`);
|
|
11747
|
+
printWarn(" precedence here. Remove its line from ~/.verity/credentials to use your login.");
|
|
11748
|
+
}
|
|
11749
|
+
}
|
|
11750
|
+
}
|
|
11751
|
+
});
|
|
11752
|
+
}
|
|
11753
|
+
|
|
11754
|
+
// src/commands/token.ts
|
|
11755
|
+
function registerTokenCommand(program2) {
|
|
11756
|
+
const token = program2.command("token").description("Manage service (CI/machine) tokens for this repository");
|
|
11757
|
+
async function requireAuth(globals) {
|
|
11758
|
+
const tokenResult = await resolveToken(globals.token);
|
|
11759
|
+
if (!tokenResult.ok) {
|
|
11760
|
+
printError(tokenResult.error);
|
|
11526
11761
|
process.exit(1);
|
|
11527
11762
|
}
|
|
11528
|
-
const
|
|
11529
|
-
|
|
11530
|
-
|
|
11763
|
+
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
11764
|
+
if (!urlResult.ok) {
|
|
11765
|
+
printError(urlResult.error);
|
|
11766
|
+
process.exit(1);
|
|
11767
|
+
}
|
|
11768
|
+
return { bearer: tokenResult.data.token, serviceUrl: urlResult.data };
|
|
11769
|
+
}
|
|
11770
|
+
function explainDenial(error) {
|
|
11771
|
+
if (error.startsWith("STALE_VERIFICATION")) {
|
|
11772
|
+
printInfo(' Your GitHub verification has expired \u2014 run "verity login", then retry.');
|
|
11773
|
+
} else if (error.startsWith("FORBIDDEN")) {
|
|
11774
|
+
printInfo(" You need write access to this repository. If it was added recently,");
|
|
11775
|
+
printInfo(' run "verity login" to refresh your grants.');
|
|
11776
|
+
} else if (error.startsWith("INVALID_REQUEST")) {
|
|
11777
|
+
printInfo(" Tokens are managed per repository \u2014 run this inside a repository with a");
|
|
11778
|
+
printInfo(" git remote (origin), so Verity knows which project the token belongs to.");
|
|
11779
|
+
}
|
|
11780
|
+
}
|
|
11781
|
+
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) => {
|
|
11782
|
+
const globals = program2.opts();
|
|
11783
|
+
const { bearer, serviceUrl } = await requireAuth(globals);
|
|
11784
|
+
let expiresInDays;
|
|
11785
|
+
if (opts.expires != null) {
|
|
11786
|
+
expiresInDays = Number(opts.expires);
|
|
11787
|
+
if (!Number.isInteger(expiresInDays) || expiresInDays <= 0 || expiresInDays > 3650) {
|
|
11788
|
+
printError("--expires must be a whole number of days between 1 and 3650");
|
|
11789
|
+
process.exit(1);
|
|
11790
|
+
}
|
|
11791
|
+
}
|
|
11792
|
+
const result = await apiRequest({
|
|
11793
|
+
method: "POST",
|
|
11794
|
+
path: "/auth/tokens",
|
|
11795
|
+
serviceUrl,
|
|
11796
|
+
token: bearer,
|
|
11797
|
+
body: {
|
|
11798
|
+
agent_name: String(opts.name).trim(),
|
|
11799
|
+
token_type: "service",
|
|
11800
|
+
...expiresInDays != null ? { expires_in_days: expiresInDays } : {}
|
|
11801
|
+
},
|
|
11802
|
+
verbose: globals.verbose,
|
|
11803
|
+
cmd: "token-create"
|
|
11804
|
+
});
|
|
11531
11805
|
if (!result.ok) {
|
|
11532
|
-
printError(`
|
|
11806
|
+
printError(`Could not create the service token: ${result.error}`);
|
|
11807
|
+
explainDenial(result.error);
|
|
11808
|
+
process.exit(1);
|
|
11809
|
+
}
|
|
11810
|
+
printInfo(`Service token "${result.data.agent_name}" created. \u2713`);
|
|
11811
|
+
printInfo("");
|
|
11812
|
+
printInfo(` ${result.data.token}`);
|
|
11813
|
+
printInfo("");
|
|
11814
|
+
printWarn("This token is shown ONCE \u2014 store it now (e.g. as a VERITY_TOKEN CI secret).");
|
|
11815
|
+
if (result.data.expires_at) {
|
|
11816
|
+
printInfo(` Expires: ${result.data.expires_at.slice(0, 10)}`);
|
|
11817
|
+
}
|
|
11818
|
+
printInfo(" Any current writer of this repository can revoke it: verity token revoke <id>");
|
|
11819
|
+
printInfo(` Token id: ${result.data.token_id}`);
|
|
11820
|
+
});
|
|
11821
|
+
token.command("list").description("List this repository's tokens (ids and metadata only \u2014 never secrets)").action(async () => {
|
|
11822
|
+
const globals = program2.opts();
|
|
11823
|
+
const { bearer, serviceUrl } = await requireAuth(globals);
|
|
11824
|
+
const result = await apiRequest({
|
|
11825
|
+
method: "GET",
|
|
11826
|
+
path: "/auth/tokens",
|
|
11827
|
+
serviceUrl,
|
|
11828
|
+
token: bearer,
|
|
11829
|
+
verbose: globals.verbose,
|
|
11830
|
+
cmd: "token-list"
|
|
11831
|
+
});
|
|
11832
|
+
if (!result.ok) {
|
|
11833
|
+
printError(`Could not list tokens: ${result.error}`);
|
|
11834
|
+
explainDenial(result.error);
|
|
11835
|
+
process.exit(1);
|
|
11836
|
+
}
|
|
11837
|
+
const tokens = result.data.tokens ?? [];
|
|
11838
|
+
if (tokens.length === 0) {
|
|
11839
|
+
printInfo("No tokens found for this repository.");
|
|
11840
|
+
return;
|
|
11841
|
+
}
|
|
11842
|
+
for (const t of tokens) {
|
|
11843
|
+
const type = t.token_type ?? "user";
|
|
11844
|
+
const expires = t.expires_at ? `expires ${t.expires_at.slice(0, 10)}` : "no expiry";
|
|
11845
|
+
const lastUsed = t.last_used_at ? `last used ${t.last_used_at.slice(0, 10)}` : "never used";
|
|
11846
|
+
printInfo(`${t.id} [${type}] ${t.agent_name ?? "unnamed"} (${expires}, ${lastUsed})`);
|
|
11847
|
+
}
|
|
11848
|
+
});
|
|
11849
|
+
token.command("revoke <id>").description("Revoke a token by id (service tokens: any current writer may revoke)").action(async (id) => {
|
|
11850
|
+
const globals = program2.opts();
|
|
11851
|
+
const { bearer, serviceUrl } = await requireAuth(globals);
|
|
11852
|
+
const result = await apiRequest({
|
|
11853
|
+
method: "DELETE",
|
|
11854
|
+
path: `/auth/tokens/${encodeURIComponent(id)}`,
|
|
11855
|
+
serviceUrl,
|
|
11856
|
+
token: bearer,
|
|
11857
|
+
verbose: globals.verbose,
|
|
11858
|
+
cmd: "token-revoke"
|
|
11859
|
+
});
|
|
11860
|
+
if (!result.ok) {
|
|
11861
|
+
printError(`Could not revoke the token: ${result.error}`);
|
|
11862
|
+
explainDenial(result.error);
|
|
11863
|
+
process.exit(1);
|
|
11864
|
+
}
|
|
11865
|
+
printInfo(`Token ${result.data.token_id} revoked. \u2713`);
|
|
11866
|
+
});
|
|
11867
|
+
}
|
|
11868
|
+
|
|
11869
|
+
// src/commands/sessions.ts
|
|
11870
|
+
function shortDate(iso) {
|
|
11871
|
+
return iso ? iso.slice(0, 10) : "\u2014";
|
|
11872
|
+
}
|
|
11873
|
+
function daysUntil(iso) {
|
|
11874
|
+
if (!iso) return null;
|
|
11875
|
+
const ms = Date.parse(iso);
|
|
11876
|
+
if (Number.isNaN(ms)) return null;
|
|
11877
|
+
return Math.round((ms - Date.now()) / 864e5);
|
|
11878
|
+
}
|
|
11879
|
+
async function auth(globals) {
|
|
11880
|
+
const tokenResult = await resolveToken(globals.token);
|
|
11881
|
+
if (!tokenResult.ok) {
|
|
11882
|
+
printError(tokenResult.error);
|
|
11883
|
+
process.exit(1);
|
|
11884
|
+
}
|
|
11885
|
+
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
11886
|
+
if (!urlResult.ok) {
|
|
11887
|
+
printError(urlResult.error);
|
|
11888
|
+
process.exit(1);
|
|
11889
|
+
}
|
|
11890
|
+
return { token: tokenResult.data.token, serviceUrl: urlResult.data };
|
|
11891
|
+
}
|
|
11892
|
+
function explain(error) {
|
|
11893
|
+
if (error.startsWith("FORBIDDEN")) {
|
|
11894
|
+
printInfo(' Sessions belong to a logged-in account \u2014 run "verity login" first.');
|
|
11895
|
+
} else if (error.startsWith("INVALID_TOKEN")) {
|
|
11896
|
+
printInfo(' This login has expired or was revoked \u2014 run "verity login" to sign in again.');
|
|
11897
|
+
}
|
|
11898
|
+
}
|
|
11899
|
+
function registerSessionsCommands(program2) {
|
|
11900
|
+
const sessions = program2.command("sessions").description("List and revoke your Verity logins (one per machine)");
|
|
11901
|
+
sessions.command("list").description("List your active logins \u2014 device, last use, and expiry").option("--json", "Output raw JSON").action(async (opts) => {
|
|
11902
|
+
const globals = program2.opts();
|
|
11903
|
+
const { token, serviceUrl } = await auth(globals);
|
|
11904
|
+
const result = await apiRequest({
|
|
11905
|
+
method: "GET",
|
|
11906
|
+
path: "/auth/sessions",
|
|
11907
|
+
serviceUrl,
|
|
11908
|
+
token,
|
|
11909
|
+
verbose: globals.verbose,
|
|
11910
|
+
cmd: "sessions"
|
|
11911
|
+
});
|
|
11912
|
+
if (!result.ok) {
|
|
11913
|
+
printError(result.error);
|
|
11914
|
+
explain(result.error);
|
|
11915
|
+
process.exit(1);
|
|
11916
|
+
}
|
|
11917
|
+
if (opts.json) {
|
|
11918
|
+
printJson(result.data);
|
|
11919
|
+
return;
|
|
11920
|
+
}
|
|
11921
|
+
const list = result.data.sessions;
|
|
11922
|
+
if (list.length === 0) {
|
|
11923
|
+
printInfo('No active logins. (Run "verity login".)');
|
|
11924
|
+
return;
|
|
11925
|
+
}
|
|
11926
|
+
printInfo(`${list.length} active login${list.length === 1 ? "" : "s"}:`);
|
|
11927
|
+
printInfo("");
|
|
11928
|
+
printInfo(`${"SESSION ID".padEnd(38)}${"DEVICE".padEnd(24)}${"CREATED".padEnd(12)}${"LAST USED".padEnd(12)}EXPIRES`);
|
|
11929
|
+
for (const s of list) {
|
|
11930
|
+
const days = daysUntil(s.expires_at);
|
|
11931
|
+
const expiry = s.expires_at ? `${shortDate(s.expires_at)}${days != null ? ` (${days}d)` : ""}` : "never";
|
|
11932
|
+
const device = (s.device ?? "login").slice(0, 22);
|
|
11933
|
+
printInfo(
|
|
11934
|
+
`${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" : ""}`
|
|
11935
|
+
);
|
|
11936
|
+
}
|
|
11937
|
+
printInfo("");
|
|
11938
|
+
printInfo("Revoke one: verity sessions revoke <session-id>");
|
|
11939
|
+
printInfo("Sign out everywhere else: verity logout --others");
|
|
11940
|
+
});
|
|
11941
|
+
sessions.command("revoke <session-id>").description("Revoke one login. Revoking this machine's also clears the local credential").action(async (sessionId) => {
|
|
11942
|
+
const globals = program2.opts();
|
|
11943
|
+
const { token, serviceUrl } = await auth(globals);
|
|
11944
|
+
const result = await apiRequest({
|
|
11945
|
+
method: "DELETE",
|
|
11946
|
+
path: `/auth/sessions/${encodeURIComponent(sessionId)}`,
|
|
11947
|
+
serviceUrl,
|
|
11948
|
+
token,
|
|
11949
|
+
verbose: globals.verbose,
|
|
11950
|
+
cmd: "sessions-revoke"
|
|
11951
|
+
});
|
|
11952
|
+
if (!result.ok) {
|
|
11953
|
+
printError(result.error);
|
|
11954
|
+
if (result.http_status === 404) {
|
|
11955
|
+
printInfo(' No session with that id on your account \u2014 check "verity sessions list".');
|
|
11956
|
+
}
|
|
11957
|
+
explain(result.error);
|
|
11958
|
+
process.exit(1);
|
|
11959
|
+
}
|
|
11960
|
+
printInfo(`Session ${sessionId} revoked. \u2713`);
|
|
11961
|
+
if (result.data.was_current) {
|
|
11962
|
+
const cleared = await removeGlobalCredential("");
|
|
11963
|
+
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.');
|
|
11964
|
+
}
|
|
11965
|
+
});
|
|
11966
|
+
}
|
|
11967
|
+
function registerLogoutCommand(program2) {
|
|
11968
|
+
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) => {
|
|
11969
|
+
const globals = program2.opts();
|
|
11970
|
+
if (opts.all && opts.others) {
|
|
11971
|
+
printError("Use either --all or --others, not both.");
|
|
11972
|
+
process.exit(1);
|
|
11973
|
+
}
|
|
11974
|
+
const { token, serviceUrl } = await auth(globals);
|
|
11975
|
+
if (opts.all || opts.others) {
|
|
11976
|
+
const result = await apiRequest({
|
|
11977
|
+
method: "DELETE",
|
|
11978
|
+
path: opts.others ? "/auth/sessions?others=true" : "/auth/sessions",
|
|
11979
|
+
serviceUrl,
|
|
11980
|
+
token,
|
|
11981
|
+
verbose: globals.verbose,
|
|
11982
|
+
cmd: "logout"
|
|
11983
|
+
});
|
|
11984
|
+
if (!result.ok) {
|
|
11985
|
+
printError(result.error);
|
|
11986
|
+
explain(result.error);
|
|
11987
|
+
process.exit(1);
|
|
11988
|
+
}
|
|
11989
|
+
const n = result.data.revoked;
|
|
11990
|
+
printInfo(`Revoked ${n} login${n === 1 ? "" : "s"}. \u2713`);
|
|
11991
|
+
if (opts.others) {
|
|
11992
|
+
printInfo(" This machine is still signed in.");
|
|
11993
|
+
return;
|
|
11994
|
+
}
|
|
11995
|
+
const cleared2 = await removeGlobalCredential("");
|
|
11996
|
+
if (cleared2) printInfo(" Local credential cleared.");
|
|
11997
|
+
printInfo(' Run "verity login" to sign back in.');
|
|
11998
|
+
return;
|
|
11999
|
+
}
|
|
12000
|
+
const list = await apiRequest({
|
|
12001
|
+
method: "GET",
|
|
12002
|
+
path: "/auth/sessions",
|
|
12003
|
+
serviceUrl,
|
|
12004
|
+
token,
|
|
12005
|
+
verbose: globals.verbose,
|
|
12006
|
+
cmd: "logout"
|
|
12007
|
+
});
|
|
12008
|
+
if (!list.ok) {
|
|
12009
|
+
printError(list.error);
|
|
12010
|
+
explain(list.error);
|
|
11533
12011
|
process.exit(1);
|
|
11534
12012
|
}
|
|
11535
|
-
const
|
|
11536
|
-
|
|
11537
|
-
|
|
11538
|
-
|
|
11539
|
-
|
|
12013
|
+
const current = list.data.sessions.find((s) => s.current);
|
|
12014
|
+
if (!current) {
|
|
12015
|
+
printWarn("This machine is not signed in with a Verity login.");
|
|
12016
|
+
const cleared2 = await removeGlobalCredential("");
|
|
12017
|
+
if (cleared2) printInfo(" Cleared the local login credential anyway.");
|
|
12018
|
+
return;
|
|
12019
|
+
}
|
|
12020
|
+
const revoked = await apiRequest({
|
|
12021
|
+
method: "DELETE",
|
|
12022
|
+
path: `/auth/sessions/${current.id}`,
|
|
12023
|
+
serviceUrl,
|
|
12024
|
+
token,
|
|
12025
|
+
verbose: globals.verbose,
|
|
12026
|
+
cmd: "logout"
|
|
12027
|
+
});
|
|
12028
|
+
if (!revoked.ok) {
|
|
12029
|
+
printError(revoked.error);
|
|
12030
|
+
explain(revoked.error);
|
|
12031
|
+
process.exit(1);
|
|
11540
12032
|
}
|
|
12033
|
+
const cleared = await removeGlobalCredential("");
|
|
12034
|
+
printInfo("Signed out on this machine. \u2713");
|
|
12035
|
+
if (cleared) printInfo(" Local credential cleared.");
|
|
12036
|
+
printInfo(' Your other machines are unaffected \u2014 use "verity logout --all" for all of them.');
|
|
11541
12037
|
});
|
|
11542
12038
|
}
|
|
11543
12039
|
|
|
11544
12040
|
// src/lib/hooks.ts
|
|
11545
12041
|
var import_promises4 = require("node:fs/promises");
|
|
11546
|
-
var
|
|
12042
|
+
var import_node_path5 = require("node:path");
|
|
11547
12043
|
|
|
11548
12044
|
// src/lib/json-file.ts
|
|
11549
12045
|
var import_promises3 = require("node:fs/promises");
|
|
11550
|
-
var
|
|
12046
|
+
var import_node_path4 = require("node:path");
|
|
11551
12047
|
function jsonSemanticEqual(a, b) {
|
|
11552
12048
|
if (a === b) return true;
|
|
11553
12049
|
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) {
|
|
@@ -11593,7 +12089,7 @@ async function writeJsonFilePreservingStyle(file, value) {
|
|
|
11593
12089
|
const indent = currentRaw !== null ? detectJsonIndent(currentRaw) : 2;
|
|
11594
12090
|
const next = JSON.stringify(value, null, indent) + "\n";
|
|
11595
12091
|
if (next === currentRaw) return false;
|
|
11596
|
-
await (0, import_promises3.mkdir)((0,
|
|
12092
|
+
await (0, import_promises3.mkdir)((0, import_node_path4.dirname)(file), { recursive: true });
|
|
11597
12093
|
await (0, import_promises3.writeFile)(file, next);
|
|
11598
12094
|
return true;
|
|
11599
12095
|
}
|
|
@@ -11766,13 +12262,13 @@ async function writeSettings(settings) {
|
|
|
11766
12262
|
}
|
|
11767
12263
|
async function readSettingsAt(root) {
|
|
11768
12264
|
try {
|
|
11769
|
-
return JSON.parse(await (0, import_promises4.readFile)((0,
|
|
12265
|
+
return JSON.parse(await (0, import_promises4.readFile)((0, import_node_path5.join)(root, CLAUDE_SETTINGS_FILE), "utf-8"));
|
|
11770
12266
|
} catch {
|
|
11771
12267
|
return {};
|
|
11772
12268
|
}
|
|
11773
12269
|
}
|
|
11774
12270
|
async function writeSettingsAt(root, settings) {
|
|
11775
|
-
await writeJsonFilePreservingStyle((0,
|
|
12271
|
+
await writeJsonFilePreservingStyle((0, import_node_path5.join)(root, CLAUDE_SETTINGS_FILE), settings);
|
|
11776
12272
|
}
|
|
11777
12273
|
async function hasLegacyHooksAt(root) {
|
|
11778
12274
|
const settings = await readSettingsAt(root);
|
|
@@ -12045,7 +12541,7 @@ var import_node_crypto6 = require("node:crypto");
|
|
|
12045
12541
|
// src/lib/conversation-buffer.ts
|
|
12046
12542
|
var import_promises5 = require("node:fs/promises");
|
|
12047
12543
|
var import_node_fs4 = require("node:fs");
|
|
12048
|
-
var
|
|
12544
|
+
var import_node_child_process5 = require("node:child_process");
|
|
12049
12545
|
var import_node_crypto = require("node:crypto");
|
|
12050
12546
|
function stripImageReferences(text) {
|
|
12051
12547
|
return text.replace(/\[Image #\d+\]/g, "[screenshot \u2014 not available for review]");
|
|
@@ -12148,7 +12644,7 @@ async function readBufferEntries() {
|
|
|
12148
12644
|
}
|
|
12149
12645
|
function getRecentCommitMessages() {
|
|
12150
12646
|
try {
|
|
12151
|
-
const output = (0,
|
|
12647
|
+
const output = (0, import_node_child_process5.execSync)(
|
|
12152
12648
|
'git log --since="30 minutes ago" --format="%s" -5',
|
|
12153
12649
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
12154
12650
|
).trim();
|
|
@@ -12162,8 +12658,8 @@ function getRecentCommitMessages() {
|
|
|
12162
12658
|
// src/lib/context-identity.ts
|
|
12163
12659
|
var import_node_crypto2 = require("node:crypto");
|
|
12164
12660
|
var import_node_fs5 = require("node:fs");
|
|
12165
|
-
var
|
|
12166
|
-
var
|
|
12661
|
+
var import_node_os2 = require("node:os");
|
|
12662
|
+
var import_node_path6 = require("node:path");
|
|
12167
12663
|
var SHARED_SENTINELS = /* @__PURE__ */ new Set([
|
|
12168
12664
|
"",
|
|
12169
12665
|
"-",
|
|
@@ -12217,13 +12713,13 @@ function contextIdentity(input) {
|
|
|
12217
12713
|
}
|
|
12218
12714
|
function verityHome() {
|
|
12219
12715
|
const override = process.env.VERITY_HOME;
|
|
12220
|
-
return override && override.trim() ? (0,
|
|
12716
|
+
return override && override.trim() ? (0, import_node_path6.resolve)(override) : (0, import_node_path6.join)((0, import_node_os2.homedir)(), ".verity");
|
|
12221
12717
|
}
|
|
12222
12718
|
function dossierDir(identity) {
|
|
12223
|
-
return (0,
|
|
12719
|
+
return (0, import_node_path6.join)(verityHome(), "sessions", identity.userKey, identity.treeKey, identity.sessionKey);
|
|
12224
12720
|
}
|
|
12225
12721
|
function treeDir(identity) {
|
|
12226
|
-
return (0,
|
|
12722
|
+
return (0, import_node_path6.join)(verityHome(), "sessions", identity.userKey, identity.treeKey);
|
|
12227
12723
|
}
|
|
12228
12724
|
function scopeIdentity(token, sessionId) {
|
|
12229
12725
|
const t = (token ?? "").trim();
|
|
@@ -12239,7 +12735,7 @@ function sessionScopeKey(token, sessionId) {
|
|
|
12239
12735
|
// src/lib/task-context-buffer.ts
|
|
12240
12736
|
var import_promises6 = require("node:fs/promises");
|
|
12241
12737
|
var import_node_fs6 = require("node:fs");
|
|
12242
|
-
var
|
|
12738
|
+
var import_node_path7 = require("node:path");
|
|
12243
12739
|
var TASK_CONTEXT_DIR = `${VERITY_DIR}/.task-context`;
|
|
12244
12740
|
var MAX_BUFFER_BYTES = 500 * 1024;
|
|
12245
12741
|
var MAX_PROMPT_CHARS = 2e3;
|
|
@@ -12317,7 +12813,7 @@ async function cleanupTaskContextBuffers() {
|
|
|
12317
12813
|
const cutoffMs = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
12318
12814
|
for (const file of files) {
|
|
12319
12815
|
if (!file.endsWith(".jsonl")) continue;
|
|
12320
|
-
const filePath = (0,
|
|
12816
|
+
const filePath = (0, import_node_path7.join)(TASK_CONTEXT_DIR, file);
|
|
12321
12817
|
try {
|
|
12322
12818
|
const stats = await (0, import_promises6.stat)(filePath);
|
|
12323
12819
|
if (stats.mtimeMs < cutoffMs) {
|
|
@@ -12331,7 +12827,7 @@ async function cleanupTaskContextBuffers() {
|
|
|
12331
12827
|
}
|
|
12332
12828
|
function bufferPath(taskId) {
|
|
12333
12829
|
const safe = taskId.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
12334
|
-
return (0,
|
|
12830
|
+
return (0, import_node_path7.join)(TASK_CONTEXT_DIR, `${safe}.jsonl`);
|
|
12335
12831
|
}
|
|
12336
12832
|
async function appendEntry(taskId, entry) {
|
|
12337
12833
|
try {
|
|
@@ -12357,7 +12853,7 @@ async function appendEntry(taskId, entry) {
|
|
|
12357
12853
|
// src/lib/memory-retrieval.ts
|
|
12358
12854
|
var import_promises7 = require("node:fs/promises");
|
|
12359
12855
|
var import_node_fs7 = require("node:fs");
|
|
12360
|
-
var
|
|
12856
|
+
var import_node_path8 = require("node:path");
|
|
12361
12857
|
var memoryDir = () => projectPath(`${VERITY_DIR}/memory`);
|
|
12362
12858
|
var DOMAINS = ["decisions", "quality", "security", "intent", "gotchas", "patterns", "domain", "integrations"];
|
|
12363
12859
|
var DEFAULT_BUDGET_TOKENS = 2e3;
|
|
@@ -12455,14 +12951,14 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
|
|
|
12455
12951
|
const promptTokens = tokenize(promptText);
|
|
12456
12952
|
const nodes = [];
|
|
12457
12953
|
for (const domain of DOMAINS) {
|
|
12458
|
-
const domainDir = (0,
|
|
12954
|
+
const domainDir = (0, import_node_path8.join)(memoryDir(), domain);
|
|
12459
12955
|
if (!(0, import_node_fs7.existsSync)(domainDir)) continue;
|
|
12460
12956
|
try {
|
|
12461
12957
|
const files = await (0, import_promises7.readdir)(domainDir);
|
|
12462
12958
|
for (const file of files) {
|
|
12463
12959
|
if (!file.endsWith(".md")) continue;
|
|
12464
12960
|
try {
|
|
12465
|
-
const content = await (0, import_promises7.readFile)((0,
|
|
12961
|
+
const content = await (0, import_promises7.readFile)((0, import_node_path8.join)(domainDir, file), "utf-8");
|
|
12466
12962
|
const { fm, body } = parseFrontmatter(content);
|
|
12467
12963
|
if (fm.status && fm.status !== "active") continue;
|
|
12468
12964
|
nodes.push({
|
|
@@ -12522,7 +13018,7 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
|
|
|
12522
13018
|
// src/lib/memory-sync.ts
|
|
12523
13019
|
var import_promises8 = require("node:fs/promises");
|
|
12524
13020
|
var import_node_fs8 = require("node:fs");
|
|
12525
|
-
var
|
|
13021
|
+
var import_node_path9 = require("node:path");
|
|
12526
13022
|
var import_node_crypto3 = require("node:crypto");
|
|
12527
13023
|
|
|
12528
13024
|
// src/lib/glob-match.ts
|
|
@@ -12593,16 +13089,16 @@ var syncStateFile = () => projectPath(`${VERITY_DIR}/.memory-sync-state.json`);
|
|
|
12593
13089
|
async function ensureMemoryDir() {
|
|
12594
13090
|
await (0, import_promises8.mkdir)(memoryDir2(), { recursive: true });
|
|
12595
13091
|
for (const domain of DOMAINS2) {
|
|
12596
|
-
await (0, import_promises8.mkdir)((0,
|
|
13092
|
+
await (0, import_promises8.mkdir)((0, import_node_path9.join)(memoryDir2(), domain), { recursive: true });
|
|
12597
13093
|
}
|
|
12598
|
-
if (!(0, import_node_fs8.existsSync)((0,
|
|
12599
|
-
await (0, import_promises8.writeFile)((0,
|
|
13094
|
+
if (!(0, import_node_fs8.existsSync)((0, import_node_path9.join)(memoryDir2(), "SCHEMA.md"))) {
|
|
13095
|
+
await (0, import_promises8.writeFile)((0, import_node_path9.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
|
|
12600
13096
|
}
|
|
12601
|
-
if (!(0, import_node_fs8.existsSync)((0,
|
|
12602
|
-
await (0, import_promises8.writeFile)((0,
|
|
13097
|
+
if (!(0, import_node_fs8.existsSync)((0, import_node_path9.join)(memoryDir2(), "index.md"))) {
|
|
13098
|
+
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
13099
|
}
|
|
12604
|
-
if (!(0, import_node_fs8.existsSync)((0,
|
|
12605
|
-
await (0, import_promises8.writeFile)((0,
|
|
13100
|
+
if (!(0, import_node_fs8.existsSync)((0, import_node_path9.join)(memoryDir2(), "log.md"))) {
|
|
13101
|
+
await (0, import_promises8.writeFile)((0, import_node_path9.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
|
|
12606
13102
|
}
|
|
12607
13103
|
}
|
|
12608
13104
|
async function buildManifest() {
|
|
@@ -12611,14 +13107,14 @@ async function buildManifest() {
|
|
|
12611
13107
|
}
|
|
12612
13108
|
const nodes = [];
|
|
12613
13109
|
for (const domain of DOMAINS2) {
|
|
12614
|
-
const domainDir = (0,
|
|
13110
|
+
const domainDir = (0, import_node_path9.join)(memoryDir2(), domain);
|
|
12615
13111
|
if (!(0, import_node_fs8.existsSync)(domainDir)) continue;
|
|
12616
13112
|
try {
|
|
12617
13113
|
const files = await (0, import_promises8.readdir)(domainDir);
|
|
12618
13114
|
for (const file of files) {
|
|
12619
13115
|
if (!file.endsWith(".md")) continue;
|
|
12620
13116
|
const filePath = `${domain}/${file}`;
|
|
12621
|
-
const fullPath = (0,
|
|
13117
|
+
const fullPath = (0, import_node_path9.join)(memoryDir2(), filePath);
|
|
12622
13118
|
try {
|
|
12623
13119
|
const content = await (0, import_promises8.readFile)(fullPath, "utf-8");
|
|
12624
13120
|
const hash = (0, import_node_crypto3.createHash)("sha256").update(content).digest("hex").slice(0, 16);
|
|
@@ -12631,13 +13127,13 @@ async function buildManifest() {
|
|
|
12631
13127
|
}
|
|
12632
13128
|
let indexHash = null;
|
|
12633
13129
|
try {
|
|
12634
|
-
const indexContent = await (0, import_promises8.readFile)((0,
|
|
13130
|
+
const indexContent = await (0, import_promises8.readFile)((0, import_node_path9.join)(memoryDir2(), "index.md"), "utf-8");
|
|
12635
13131
|
indexHash = `sha256:${(0, import_node_crypto3.createHash)("sha256").update(indexContent).digest("hex").slice(0, 16)}`;
|
|
12636
13132
|
} catch {
|
|
12637
13133
|
}
|
|
12638
13134
|
let logLength = 0;
|
|
12639
13135
|
try {
|
|
12640
|
-
const logContent = await (0, import_promises8.readFile)((0,
|
|
13136
|
+
const logContent = await (0, import_promises8.readFile)((0, import_node_path9.join)(memoryDir2(), "log.md"), "utf-8");
|
|
12641
13137
|
logLength = logContent.split("\n").length;
|
|
12642
13138
|
} catch {
|
|
12643
13139
|
}
|
|
@@ -12650,13 +13146,13 @@ async function readOnDiskNodes() {
|
|
|
12650
13146
|
const out = /* @__PURE__ */ new Map();
|
|
12651
13147
|
if (!(0, import_node_fs8.existsSync)(memoryDir2())) return out;
|
|
12652
13148
|
for (const domain of DOMAINS2) {
|
|
12653
|
-
const domainDir = (0,
|
|
13149
|
+
const domainDir = (0, import_node_path9.join)(memoryDir2(), domain);
|
|
12654
13150
|
if (!(0, import_node_fs8.existsSync)(domainDir)) continue;
|
|
12655
13151
|
try {
|
|
12656
13152
|
for (const file of await (0, import_promises8.readdir)(domainDir)) {
|
|
12657
13153
|
if (!file.endsWith(".md")) continue;
|
|
12658
13154
|
try {
|
|
12659
|
-
out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0,
|
|
13155
|
+
out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0, import_node_path9.join)(domainDir, file), "utf-8")));
|
|
12660
13156
|
} catch {
|
|
12661
13157
|
}
|
|
12662
13158
|
}
|
|
@@ -12702,7 +13198,7 @@ async function computeEditedNodeUploads() {
|
|
|
12702
13198
|
const uploads = [];
|
|
12703
13199
|
for (const [path, prevHash] of prev) {
|
|
12704
13200
|
if (prevHash == null) continue;
|
|
12705
|
-
const full = (0,
|
|
13201
|
+
const full = (0, import_node_path9.join)(memoryDir2(), path);
|
|
12706
13202
|
if (!(0, import_node_fs8.existsSync)(full)) continue;
|
|
12707
13203
|
let content;
|
|
12708
13204
|
try {
|
|
@@ -12739,15 +13235,15 @@ async function applyMemoryWrites(writes, opts = {}) {
|
|
|
12739
13235
|
const logLines = [`- ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)} \u2014 Applied ${count} write(s) from server`];
|
|
12740
13236
|
for (const n of notes) logLines.push(` - ${n}`);
|
|
12741
13237
|
try {
|
|
12742
|
-
const existing = (0, import_node_fs8.existsSync)((0,
|
|
12743
|
-
await (0, import_promises8.writeFile)((0,
|
|
13238
|
+
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";
|
|
13239
|
+
await (0, import_promises8.writeFile)((0, import_node_path9.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
|
|
12744
13240
|
} catch {
|
|
12745
13241
|
}
|
|
12746
13242
|
await recordSyncedNodePaths();
|
|
12747
13243
|
return count;
|
|
12748
13244
|
}
|
|
12749
13245
|
async function applyOneWrite(write, treePaths) {
|
|
12750
|
-
const fullPath = (0,
|
|
13246
|
+
const fullPath = (0, import_node_path9.join)(memoryDir2(), write.path);
|
|
12751
13247
|
const notes = [];
|
|
12752
13248
|
let content = write.content;
|
|
12753
13249
|
if (treePaths && treePaths.length > 0) {
|
|
@@ -12769,7 +13265,7 @@ async function applyOneWrite(write, treePaths) {
|
|
|
12769
13265
|
return { written: false, notes };
|
|
12770
13266
|
}
|
|
12771
13267
|
}
|
|
12772
|
-
await (0, import_promises8.mkdir)((0,
|
|
13268
|
+
await (0, import_promises8.mkdir)((0, import_node_path9.dirname)(fullPath), { recursive: true });
|
|
12773
13269
|
await (0, import_promises8.writeFile)(fullPath, content);
|
|
12774
13270
|
return { written: true, notes };
|
|
12775
13271
|
}
|
|
@@ -12810,7 +13306,7 @@ async function regenerateIndex() {
|
|
|
12810
13306
|
];
|
|
12811
13307
|
let totalNodes = 0;
|
|
12812
13308
|
for (const domain of DOMAINS2.filter((d) => d !== "_archive")) {
|
|
12813
|
-
const domainDir = (0,
|
|
13309
|
+
const domainDir = (0, import_node_path9.join)(memoryDir2(), domain);
|
|
12814
13310
|
if (!(0, import_node_fs8.existsSync)(domainDir)) continue;
|
|
12815
13311
|
try {
|
|
12816
13312
|
const files = await (0, import_promises8.readdir)(domainDir);
|
|
@@ -12821,7 +13317,7 @@ async function regenerateIndex() {
|
|
|
12821
13317
|
for (const file of mdFiles.sort()) {
|
|
12822
13318
|
const slug = file.replace(/\.md$/, "");
|
|
12823
13319
|
try {
|
|
12824
|
-
const content = await (0, import_promises8.readFile)((0,
|
|
13320
|
+
const content = await (0, import_promises8.readFile)((0, import_node_path9.join)(domainDir, file), "utf-8");
|
|
12825
13321
|
const title = pickFrontmatter(content, "title") ?? slug;
|
|
12826
13322
|
const kind = pickFrontmatter(content, "kind") ?? "-";
|
|
12827
13323
|
const confidence = pickFrontmatter(content, "confidence");
|
|
@@ -12845,7 +13341,7 @@ async function regenerateIndex() {
|
|
|
12845
13341
|
lines.push("No nodes yet. Run an analysis to start building the knowledge graph.");
|
|
12846
13342
|
}
|
|
12847
13343
|
const next = lines.join("\n") + "\n";
|
|
12848
|
-
const indexPath = (0,
|
|
13344
|
+
const indexPath = (0, import_node_path9.join)(memoryDir2(), "index.md");
|
|
12849
13345
|
let existing = null;
|
|
12850
13346
|
try {
|
|
12851
13347
|
existing = await (0, import_promises8.readFile)(indexPath, "utf-8");
|
|
@@ -12927,7 +13423,7 @@ function hasLegacyMemoryBlock(text) {
|
|
|
12927
13423
|
return findMarker(text, LEGACY_MD_START) !== -1;
|
|
12928
13424
|
}
|
|
12929
13425
|
async function ensureClaudeMdPointer(cwd = repoRoot()) {
|
|
12930
|
-
const claudeMdPath = (0,
|
|
13426
|
+
const claudeMdPath = (0, import_node_path9.join)(cwd, "CLAUDE.md");
|
|
12931
13427
|
let existing = "";
|
|
12932
13428
|
if ((0, import_node_fs8.existsSync)(claudeMdPath)) {
|
|
12933
13429
|
existing = await (0, import_promises8.readFile)(claudeMdPath, "utf-8");
|
|
@@ -13061,12 +13557,12 @@ Body content (\u22648KB). Use [[node-id]] wikilinks for cross-references.
|
|
|
13061
13557
|
// src/lib/dossier-session.ts
|
|
13062
13558
|
var import_node_fs10 = require("node:fs");
|
|
13063
13559
|
var import_node_crypto5 = require("node:crypto");
|
|
13064
|
-
var
|
|
13560
|
+
var import_node_path11 = require("node:path");
|
|
13065
13561
|
|
|
13066
13562
|
// src/lib/dossier.ts
|
|
13067
13563
|
var import_node_fs9 = require("node:fs");
|
|
13068
13564
|
var import_node_crypto4 = require("node:crypto");
|
|
13069
|
-
var
|
|
13565
|
+
var import_node_path10 = require("node:path");
|
|
13070
13566
|
var MAX_LINE_BYTES = 4096;
|
|
13071
13567
|
var MAX_GOAL_CHARS = 2e3;
|
|
13072
13568
|
var GOAL_KEEP = 8;
|
|
@@ -13109,9 +13605,9 @@ function openDossier(identity) {
|
|
|
13109
13605
|
return {
|
|
13110
13606
|
dir,
|
|
13111
13607
|
identity,
|
|
13112
|
-
eventsPath: (0,
|
|
13113
|
-
foldPath: (0,
|
|
13114
|
-
rotatedDir: (0,
|
|
13608
|
+
eventsPath: (0, import_node_path10.join)(dir, "events.jsonl"),
|
|
13609
|
+
foldPath: (0, import_node_path10.join)(dir, "fold.json"),
|
|
13610
|
+
rotatedDir: (0, import_node_path10.join)(dir, "rotated")
|
|
13115
13611
|
};
|
|
13116
13612
|
} catch {
|
|
13117
13613
|
return null;
|
|
@@ -13184,11 +13680,11 @@ function rotateIfNeeded2(d) {
|
|
|
13184
13680
|
if (!(0, import_node_fs9.existsSync)(d.eventsPath)) return;
|
|
13185
13681
|
if ((0, import_node_fs9.statSync)(d.eventsPath).size < ROTATE_BYTES) return;
|
|
13186
13682
|
(0, import_node_fs9.mkdirSync)(d.rotatedDir, { recursive: true, mode: 448 });
|
|
13187
|
-
(0, import_node_fs9.renameSync)(d.eventsPath, (0,
|
|
13683
|
+
(0, import_node_fs9.renameSync)(d.eventsPath, (0, import_node_path10.join)(d.rotatedDir, `events.${Date.now()}.jsonl`));
|
|
13188
13684
|
const kept = (0, import_node_fs9.readdirSync)(d.rotatedDir).filter((f) => f.endsWith(".jsonl")).sort();
|
|
13189
13685
|
for (const stale of kept.slice(0, Math.max(0, kept.length - ROTATE_KEEP))) {
|
|
13190
13686
|
try {
|
|
13191
|
-
(0, import_node_fs9.renameSync)((0,
|
|
13687
|
+
(0, import_node_fs9.renameSync)((0, import_node_path10.join)(d.rotatedDir, stale), (0, import_node_path10.join)(d.rotatedDir, `${stale}.pruned`));
|
|
13192
13688
|
} catch {
|
|
13193
13689
|
}
|
|
13194
13690
|
}
|
|
@@ -13250,7 +13746,7 @@ function foldDossier(d, opts = {}) {
|
|
|
13250
13746
|
state.meta.rotations = files.length;
|
|
13251
13747
|
for (const f of files) {
|
|
13252
13748
|
try {
|
|
13253
|
-
ingest((0, import_node_fs9.readFileSync)((0,
|
|
13749
|
+
ingest((0, import_node_fs9.readFileSync)((0, import_node_path10.join)(d.rotatedDir, f), "utf8"));
|
|
13254
13750
|
} catch {
|
|
13255
13751
|
state.meta.dropped_lines++;
|
|
13256
13752
|
}
|
|
@@ -13311,6 +13807,13 @@ function reduce(state, events, now) {
|
|
|
13311
13807
|
});
|
|
13312
13808
|
break;
|
|
13313
13809
|
}
|
|
13810
|
+
case "goal_delivered": {
|
|
13811
|
+
const g = state.goal.find((x) => x.status === "active");
|
|
13812
|
+
if (!g) break;
|
|
13813
|
+
if (g.delivered) break;
|
|
13814
|
+
g.delivered = { at: ev.at, seq: ev.seq, summary: ev.summary };
|
|
13815
|
+
break;
|
|
13816
|
+
}
|
|
13314
13817
|
case "authored": {
|
|
13315
13818
|
authoredEvents++;
|
|
13316
13819
|
const e = byPath.get(ev.path) ?? {
|
|
@@ -13425,6 +13928,12 @@ function reduce(state, events, now) {
|
|
|
13425
13928
|
}
|
|
13426
13929
|
case "verdict": {
|
|
13427
13930
|
state.meta.last_verdict_seq = ev.seq;
|
|
13931
|
+
state.meta.last_adjudication = ev.intent_verdict ? { verdict: ev.intent_verdict, score: ev.intent_score ?? null, at: ev.at, decision: ev.decision } : void 0;
|
|
13932
|
+
if (ev.intent_sig) {
|
|
13933
|
+
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 };
|
|
13934
|
+
} else {
|
|
13935
|
+
state.meta.intent_repeat = void 0;
|
|
13936
|
+
}
|
|
13428
13937
|
state.meta.watermark = {
|
|
13429
13938
|
sha: ev.head_sha,
|
|
13430
13939
|
reviewed_hash: ev.watermark_sha,
|
|
@@ -13510,7 +14019,12 @@ function compactState(s) {
|
|
|
13510
14019
|
const ms = (iso) => Date.parse(iso) || 0;
|
|
13511
14020
|
return {
|
|
13512
14021
|
v: 1,
|
|
13513
|
-
|
|
14022
|
+
// ⚠ APPEND-ONLY POSITIONALLY. Indices 11-13 carry `delivered`; a cache row
|
|
14023
|
+
// written before it existed has length 11 and expands with `delivered`
|
|
14024
|
+
// absent, which is the correct reading of "nothing had been delivered yet".
|
|
14025
|
+
// Inserting rather than appending would silently re-interpret every existing
|
|
14026
|
+
// cached row.
|
|
14027
|
+
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
14028
|
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
14029
|
n: s.not_mine?.map((n) => [n.path, n.reason, ms(n.at), n.head_sha]) ?? null,
|
|
13516
14030
|
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 +14070,14 @@ function expandState(raw) {
|
|
|
13556
14070
|
...x[7] ? { status: x[7] } : {},
|
|
13557
14071
|
...x[8] ? { repeats: x[8] } : {},
|
|
13558
14072
|
...x[9] ? { truncated: true } : {},
|
|
13559
|
-
...x[10] ? { collapsed: true } : {}
|
|
14073
|
+
...x[10] ? { collapsed: true } : {},
|
|
14074
|
+
...x[11] ? {
|
|
14075
|
+
delivered: {
|
|
14076
|
+
at: iso(x[11]),
|
|
14077
|
+
seq: x[12] ?? 0,
|
|
14078
|
+
summary: x[13] || ""
|
|
14079
|
+
}
|
|
14080
|
+
} : {}
|
|
13560
14081
|
})),
|
|
13561
14082
|
authored: decodedAuthored,
|
|
13562
14083
|
// The cache stores the BOUNDED list, so this is the bounded list too. That
|
|
@@ -13770,9 +14291,14 @@ function projectMemory(state, opts) {
|
|
|
13770
14291
|
seq: active.seq,
|
|
13771
14292
|
superseded,
|
|
13772
14293
|
truncated: active.truncated === true,
|
|
13773
|
-
collapsed: state.meta.collapsed.goal ?? 0
|
|
14294
|
+
collapsed: state.meta.collapsed.goal ?? 0,
|
|
14295
|
+
...active.delivered && { delivered: active.delivered }
|
|
13774
14296
|
};
|
|
13775
14297
|
}
|
|
14298
|
+
if (state.meta.last_adjudication) {
|
|
14299
|
+
const a = state.meta.last_adjudication;
|
|
14300
|
+
p.last_adjudication = { verdict: a.verdict, score: a.score, at: a.at };
|
|
14301
|
+
}
|
|
13776
14302
|
const ageOf = (seq, carried) => carried ? "carried" : seq > lastVerdictSeq ? "this_turn" : "this_session";
|
|
13777
14303
|
if (opts.spoken.length > 0) {
|
|
13778
14304
|
p.statements = opts.spoken.map((s) => ({
|
|
@@ -14046,16 +14572,16 @@ function foreignAuthoredPaths(identity, opts = {}) {
|
|
|
14046
14572
|
for (const entry of (0, import_node_fs10.readdirSync)(dir, { withFileTypes: true })) {
|
|
14047
14573
|
if (!entry.isDirectory()) continue;
|
|
14048
14574
|
if (entry.name === identity.sessionKey) continue;
|
|
14049
|
-
const log = (0,
|
|
14575
|
+
const log = (0, import_node_path11.join)(dir, entry.name, "events.jsonl");
|
|
14050
14576
|
try {
|
|
14051
14577
|
if (!(0, import_node_fs10.existsSync)(log)) continue;
|
|
14052
14578
|
if (now - (0, import_node_fs10.statSync)(log).mtimeMs > windowMs) continue;
|
|
14053
14579
|
const sib = {
|
|
14054
|
-
dir: (0,
|
|
14580
|
+
dir: (0, import_node_path11.join)(dir, entry.name),
|
|
14055
14581
|
identity,
|
|
14056
14582
|
eventsPath: log,
|
|
14057
|
-
foldPath: (0,
|
|
14058
|
-
rotatedDir: (0,
|
|
14583
|
+
foldPath: (0, import_node_path11.join)(dir, entry.name, "fold.json"),
|
|
14584
|
+
rotatedDir: (0, import_node_path11.join)(dir, entry.name, "rotated")
|
|
14059
14585
|
};
|
|
14060
14586
|
const state = readFoldCache(sib) ?? foldDossier(sib);
|
|
14061
14587
|
sessions++;
|
|
@@ -14083,22 +14609,22 @@ function pruneOldDossiers(identity, maxAgeMs = 7 * 24 * 60 * 60 * 1e3) {
|
|
|
14083
14609
|
let removed = 0;
|
|
14084
14610
|
try {
|
|
14085
14611
|
const mine = dossierDir(identity);
|
|
14086
|
-
const userDir = (0,
|
|
14612
|
+
const userDir = (0, import_node_path11.dirname)((0, import_node_path11.dirname)(mine));
|
|
14087
14613
|
if (!(0, import_node_fs10.existsSync)(userDir)) return 0;
|
|
14088
14614
|
const cutoff = Date.now() - maxAgeMs;
|
|
14089
14615
|
for (const tree of (0, import_node_fs10.readdirSync)(userDir, { withFileTypes: true })) {
|
|
14090
14616
|
if (!tree.isDirectory()) continue;
|
|
14091
|
-
const treePath = (0,
|
|
14617
|
+
const treePath = (0, import_node_path11.join)(userDir, tree.name);
|
|
14092
14618
|
let live = 0;
|
|
14093
14619
|
for (const entry of (0, import_node_fs10.readdirSync)(treePath, { withFileTypes: true })) {
|
|
14094
14620
|
if (!entry.isDirectory()) continue;
|
|
14095
|
-
const dir = (0,
|
|
14621
|
+
const dir = (0, import_node_path11.join)(treePath, entry.name);
|
|
14096
14622
|
if (dir === mine) {
|
|
14097
14623
|
live++;
|
|
14098
14624
|
continue;
|
|
14099
14625
|
}
|
|
14100
14626
|
try {
|
|
14101
|
-
const log = (0,
|
|
14627
|
+
const log = (0, import_node_path11.join)(dir, "events.jsonl");
|
|
14102
14628
|
const at = (0, import_node_fs10.existsSync)(log) ? (0, import_node_fs10.statSync)(log).mtimeMs : (0, import_node_fs10.statSync)(dir).mtimeMs;
|
|
14103
14629
|
if (at < cutoff) {
|
|
14104
14630
|
(0, import_node_fs10.rmSync)(dir, { recursive: true, force: true });
|
|
@@ -14141,7 +14667,7 @@ function recordTurn(d, t) {
|
|
|
14141
14667
|
for (const a of t.authored) {
|
|
14142
14668
|
const origin = a.owner === "subagent" ? "subagent" : "edit_tool";
|
|
14143
14669
|
const prior = t.known?.authored?.get(a.p);
|
|
14144
|
-
const hash = fileHash((0,
|
|
14670
|
+
const hash = fileHash((0, import_node_path11.join)(root, a.p));
|
|
14145
14671
|
const hunks = Math.max(0, a.h - (prior?.hunks ?? 0));
|
|
14146
14672
|
const adds = Math.max(0, a.a - (prior?.adds ?? 0));
|
|
14147
14673
|
const dels = Math.max(0, a.d - (prior?.dels ?? 0));
|
|
@@ -14174,7 +14700,7 @@ function recordTurn(d, t) {
|
|
|
14174
14700
|
}
|
|
14175
14701
|
const seenDivergence = t.known?.divergence ?? /* @__PURE__ */ new Set();
|
|
14176
14702
|
for (const u of t.unobserved) {
|
|
14177
|
-
const hash = fileHash((0,
|
|
14703
|
+
const hash = fileHash((0, import_node_path11.join)(root, u.p));
|
|
14178
14704
|
if (seenDivergence.has(divergenceKey(u.p, hash))) continue;
|
|
14179
14705
|
appendEvent(d, { k: "divergence", kind: "external_mutation", path: u.p, hash });
|
|
14180
14706
|
}
|
|
@@ -14198,12 +14724,16 @@ function toStatus(n) {
|
|
|
14198
14724
|
}
|
|
14199
14725
|
function recordVerdict(d, v) {
|
|
14200
14726
|
const root = repoRoot();
|
|
14727
|
+
if (v.intent?.verdict === "aligned") {
|
|
14728
|
+
const summary = (v.intent.implemented ?? "").trim().slice(0, 400);
|
|
14729
|
+
if (summary) appendEvent(d, { k: "goal_delivered", summary });
|
|
14730
|
+
}
|
|
14201
14731
|
const lines = /* @__PURE__ */ new Map();
|
|
14202
14732
|
for (const f of v.findings) {
|
|
14203
14733
|
if (!f.file || typeof f.line !== "number" || !f.pattern_id) continue;
|
|
14204
14734
|
if (!lines.has(f.file)) {
|
|
14205
14735
|
try {
|
|
14206
|
-
const abs = (0,
|
|
14736
|
+
const abs = (0, import_node_path11.join)(root, f.file);
|
|
14207
14737
|
lines.set(f.file, (0, import_node_fs10.existsSync)(abs) ? (0, import_node_fs10.readFileSync)(abs, "utf8").split("\n") : null);
|
|
14208
14738
|
} catch {
|
|
14209
14739
|
lines.set(f.file, null);
|
|
@@ -14231,9 +14761,17 @@ function recordVerdict(d, v) {
|
|
|
14231
14761
|
head_sha: getCurrentCommit(),
|
|
14232
14762
|
watermark_sha: v.watermarkSha,
|
|
14233
14763
|
branch: v.branch,
|
|
14234
|
-
decision: v.decision
|
|
14764
|
+
decision: v.decision,
|
|
14765
|
+
...intentSignature(v.intent) && { intent_sig: intentSignature(v.intent) },
|
|
14766
|
+
...v.intent?.verdict && { intent_verdict: v.intent.verdict },
|
|
14767
|
+
...typeof v.intent?.score === "number" && { intent_score: v.intent.score }
|
|
14235
14768
|
});
|
|
14236
14769
|
}
|
|
14770
|
+
function intentSignature(intent) {
|
|
14771
|
+
if (!intent?.verdict) return null;
|
|
14772
|
+
if (intent.verdict !== "misaligned" && intent.verdict !== "partial") return null;
|
|
14773
|
+
return `${intent.verdict}:${lineSha(intent.gaps?.[0] ?? "")}`;
|
|
14774
|
+
}
|
|
14237
14775
|
function toRegister(severity) {
|
|
14238
14776
|
switch (severity) {
|
|
14239
14777
|
case "critical":
|
|
@@ -14278,7 +14816,7 @@ function recallMemory(d, identity, opts) {
|
|
|
14278
14816
|
budgetBytes: opts.budgetBytes,
|
|
14279
14817
|
readFileLines: (file) => {
|
|
14280
14818
|
try {
|
|
14281
|
-
const abs = (0,
|
|
14819
|
+
const abs = (0, import_node_path11.join)(root, file);
|
|
14282
14820
|
return (0, import_node_fs10.existsSync)(abs) ? (0, import_node_fs10.readFileSync)(abs, "utf8").split("\n") : null;
|
|
14283
14821
|
} catch {
|
|
14284
14822
|
return null;
|
|
@@ -14418,21 +14956,21 @@ async function fireClassify(prompt, sessionId, globals) {
|
|
|
14418
14956
|
|
|
14419
14957
|
// src/commands/lifecycle.ts
|
|
14420
14958
|
var import_node_fs14 = require("node:fs");
|
|
14421
|
-
var
|
|
14959
|
+
var import_node_path15 = require("node:path");
|
|
14422
14960
|
|
|
14423
14961
|
// src/lib/baseline.ts
|
|
14424
14962
|
var import_node_fs13 = require("node:fs");
|
|
14425
|
-
var
|
|
14963
|
+
var import_node_path14 = require("node:path");
|
|
14426
14964
|
var import_node_crypto7 = require("node:crypto");
|
|
14427
14965
|
|
|
14428
14966
|
// src/lib/snapshot.ts
|
|
14429
14967
|
var import_node_fs12 = require("node:fs");
|
|
14430
|
-
var
|
|
14431
|
-
var
|
|
14968
|
+
var import_node_path13 = require("node:path");
|
|
14969
|
+
var import_node_child_process6 = require("node:child_process");
|
|
14432
14970
|
|
|
14433
14971
|
// src/lib/files.ts
|
|
14434
14972
|
var import_node_fs11 = require("node:fs");
|
|
14435
|
-
var
|
|
14973
|
+
var import_node_path12 = require("node:path");
|
|
14436
14974
|
var LANG_MAP = {
|
|
14437
14975
|
// Analyzable (static analysis + Gemini)
|
|
14438
14976
|
ts: "typescript",
|
|
@@ -14500,7 +15038,7 @@ var LANG_MAP = {
|
|
|
14500
15038
|
mk: "make"
|
|
14501
15039
|
};
|
|
14502
15040
|
function detectLanguage(filepath) {
|
|
14503
|
-
const ext = (0,
|
|
15041
|
+
const ext = (0, import_node_path12.extname)(filepath).slice(1);
|
|
14504
15042
|
return LANG_MAP[ext] ?? ext;
|
|
14505
15043
|
}
|
|
14506
15044
|
function sortByMtime(files) {
|
|
@@ -14588,7 +15126,7 @@ function generateSnapshotDiffs(files) {
|
|
|
14588
15126
|
}
|
|
14589
15127
|
const diffs = [];
|
|
14590
15128
|
for (const file of files) {
|
|
14591
|
-
const snapshotPath = (0,
|
|
15129
|
+
const snapshotPath = (0, import_node_path13.join)(SNAPSHOT_DIR, file.path);
|
|
14592
15130
|
const language = file.language ?? detectLanguage(file.path);
|
|
14593
15131
|
if ((0, import_node_fs12.existsSync)(snapshotPath)) {
|
|
14594
15132
|
const oldContent = (0, import_node_fs12.readFileSync)(snapshotPath, "utf-8");
|
|
@@ -14615,21 +15153,21 @@ ${addedLines}`,
|
|
|
14615
15153
|
function saveSnapshots(files) {
|
|
14616
15154
|
const snapshotPaths = /* @__PURE__ */ new Set();
|
|
14617
15155
|
for (const file of files) {
|
|
14618
|
-
const snapshotPath = (0,
|
|
15156
|
+
const snapshotPath = (0, import_node_path13.join)(SNAPSHOT_DIR, file.path);
|
|
14619
15157
|
snapshotPaths.add(snapshotPath);
|
|
14620
|
-
(0, import_node_fs12.mkdirSync)((0,
|
|
15158
|
+
(0, import_node_fs12.mkdirSync)((0, import_node_path13.dirname)(snapshotPath), { recursive: true });
|
|
14621
15159
|
(0, import_node_fs12.writeFileSync)(snapshotPath, file.content);
|
|
14622
15160
|
}
|
|
14623
15161
|
cleanStaleSnapshots(SNAPSHOT_DIR, snapshotPaths);
|
|
14624
15162
|
}
|
|
14625
15163
|
function computeDiff(oldContent, newContent, filePath) {
|
|
14626
|
-
const tmpOld = (0,
|
|
14627
|
-
const tmpNew = (0,
|
|
15164
|
+
const tmpOld = (0, import_node_path13.join)(SNAPSHOT_DIR, ".diff-old.tmp");
|
|
15165
|
+
const tmpNew = (0, import_node_path13.join)(SNAPSHOT_DIR, ".diff-new.tmp");
|
|
14628
15166
|
try {
|
|
14629
15167
|
(0, import_node_fs12.mkdirSync)(SNAPSHOT_DIR, { recursive: true });
|
|
14630
15168
|
(0, import_node_fs12.writeFileSync)(tmpOld, oldContent);
|
|
14631
15169
|
(0, import_node_fs12.writeFileSync)(tmpNew, newContent);
|
|
14632
|
-
const result = (0,
|
|
15170
|
+
const result = (0, import_node_child_process6.execSync)(
|
|
14633
15171
|
`git diff --no-index --unified=10 -- "${tmpOld}" "${tmpNew}"`,
|
|
14634
15172
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
14635
15173
|
);
|
|
@@ -14657,7 +15195,7 @@ function cleanStaleSnapshots(dir, keepSet) {
|
|
|
14657
15195
|
const entries = (0, import_node_fs12.readdirSync)(dir, { withFileTypes: true });
|
|
14658
15196
|
for (const entry of entries) {
|
|
14659
15197
|
if (entry.name.startsWith(".")) continue;
|
|
14660
|
-
const fullPath = (0,
|
|
15198
|
+
const fullPath = (0, import_node_path13.join)(dir, entry.name);
|
|
14661
15199
|
if (entry.isDirectory()) {
|
|
14662
15200
|
cleanStaleSnapshots(fullPath, keepSet);
|
|
14663
15201
|
try {
|
|
@@ -14686,13 +15224,13 @@ function sessionKey(sessionId) {
|
|
|
14686
15224
|
return (0, import_node_crypto7.createHash)("sha256").update(sessionId).digest("hex").slice(0, 16);
|
|
14687
15225
|
}
|
|
14688
15226
|
function sessionDir(key) {
|
|
14689
|
-
return (0,
|
|
15227
|
+
return (0, import_node_path14.join)(projectPath(BASELINE_DIR), key);
|
|
14690
15228
|
}
|
|
14691
15229
|
function manifestPath(dir) {
|
|
14692
|
-
return (0,
|
|
15230
|
+
return (0, import_node_path14.join)(dir, "manifest.json");
|
|
14693
15231
|
}
|
|
14694
15232
|
function mirrorPath(dir, repoRelPath) {
|
|
14695
|
-
return (0,
|
|
15233
|
+
return (0, import_node_path14.join)(dir, "files", repoRelPath);
|
|
14696
15234
|
}
|
|
14697
15235
|
var CARRY_FILE = `${BASELINE_DIR}/.carry`;
|
|
14698
15236
|
var CARRY_WINDOW_MS = 12e4;
|
|
@@ -14754,7 +15292,7 @@ function captureBaseline(opts = {}) {
|
|
|
14754
15292
|
(0, import_node_fs13.rmSync)(dir, { recursive: true, force: true });
|
|
14755
15293
|
} catch {
|
|
14756
15294
|
}
|
|
14757
|
-
const filesDir = (0,
|
|
15295
|
+
const filesDir = (0, import_node_path14.join)(dir, "files");
|
|
14758
15296
|
const mirrored = [];
|
|
14759
15297
|
try {
|
|
14760
15298
|
(0, import_node_fs13.mkdirSync)(filesDir, { recursive: true });
|
|
@@ -14764,7 +15302,7 @@ function captureBaseline(opts = {}) {
|
|
|
14764
15302
|
if (content === null) continue;
|
|
14765
15303
|
const dest = mirrorPath(dir, p);
|
|
14766
15304
|
try {
|
|
14767
|
-
(0, import_node_fs13.mkdirSync)((0,
|
|
15305
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path14.dirname)(dest), { recursive: true });
|
|
14768
15306
|
(0, import_node_fs13.writeFileSync)(dest, content);
|
|
14769
15307
|
mirrored.push(p);
|
|
14770
15308
|
} catch {
|
|
@@ -14892,7 +15430,7 @@ function pruneOldBaselines() {
|
|
|
14892
15430
|
}
|
|
14893
15431
|
const now = Date.now();
|
|
14894
15432
|
for (const name of entries) {
|
|
14895
|
-
const dir = (0,
|
|
15433
|
+
const dir = (0, import_node_path14.join)(root, name);
|
|
14896
15434
|
const manifest = readManifest(dir);
|
|
14897
15435
|
if (!manifest) {
|
|
14898
15436
|
try {
|
|
@@ -15078,7 +15616,7 @@ function buildCompactionContext(session) {
|
|
|
15078
15616
|
commitsSince: commitsSincePaths(watermark, (state.authored ?? []).map((a) => a.path)),
|
|
15079
15617
|
readFileLines: (file) => {
|
|
15080
15618
|
try {
|
|
15081
|
-
const abs = (0,
|
|
15619
|
+
const abs = (0, import_node_path15.join)(root, file);
|
|
15082
15620
|
return (0, import_node_fs14.existsSync)(abs) ? (0, import_node_fs14.readFileSync)(abs, "utf8").split("\n") : null;
|
|
15083
15621
|
} catch {
|
|
15084
15622
|
return null;
|
|
@@ -15414,6 +15952,7 @@ function registerStatusCommand(program2) {
|
|
|
15414
15952
|
}
|
|
15415
15953
|
const token = tokenResult.data.token;
|
|
15416
15954
|
const serviceUrl = urlResult.data;
|
|
15955
|
+
const who = await whoami(token, serviceUrl, globals.verbose);
|
|
15417
15956
|
const memResult = await apiRequest({
|
|
15418
15957
|
method: "GET",
|
|
15419
15958
|
path: "/memory",
|
|
@@ -15421,14 +15960,21 @@ function registerStatusCommand(program2) {
|
|
|
15421
15960
|
token,
|
|
15422
15961
|
verbose: globals.verbose
|
|
15423
15962
|
});
|
|
15424
|
-
|
|
15963
|
+
const denial = memResult.ok ? null : authDenialRemedy(memResult.error);
|
|
15964
|
+
if (!memResult.ok && !denial) {
|
|
15425
15965
|
printError(memResult.error);
|
|
15426
15966
|
process.exit(1);
|
|
15427
15967
|
}
|
|
15428
|
-
const mem = memResult.data;
|
|
15968
|
+
const mem = memResult.ok ? memResult.data : null;
|
|
15429
15969
|
if (opts.json) {
|
|
15430
|
-
const output = {
|
|
15431
|
-
|
|
15970
|
+
const output = {
|
|
15971
|
+
auth: who.ok ? who.data : { error: who.error },
|
|
15972
|
+
memory: mem
|
|
15973
|
+
};
|
|
15974
|
+
if (denial && !memResult.ok) {
|
|
15975
|
+
output.error = { code: denial.code, message: memResult.error, remedy: denial.remedy };
|
|
15976
|
+
}
|
|
15977
|
+
if (opts.history && !denial) {
|
|
15432
15978
|
const runsResult = await apiRequest({
|
|
15433
15979
|
method: "GET",
|
|
15434
15980
|
path: `/runs?limit=${opts.limit}`,
|
|
@@ -15443,23 +15989,30 @@ function registerStatusCommand(program2) {
|
|
|
15443
15989
|
printJson(output);
|
|
15444
15990
|
return;
|
|
15445
15991
|
}
|
|
15446
|
-
if (mem
|
|
15992
|
+
if (mem?.configured === false) {
|
|
15447
15993
|
printInfo("Verity is not configured for this project. Run /verity-setup.");
|
|
15448
15994
|
return;
|
|
15449
15995
|
}
|
|
15450
15996
|
printInfo("=== Verity Status ===");
|
|
15451
|
-
if (
|
|
15452
|
-
printInfo(`Account: Logged in as ${
|
|
15453
|
-
} else {
|
|
15454
|
-
|
|
15455
|
-
|
|
15456
|
-
|
|
15457
|
-
|
|
15458
|
-
|
|
15459
|
-
|
|
15460
|
-
|
|
15461
|
-
|
|
15462
|
-
|
|
15997
|
+
if (who.ok && who.data.logged_in) {
|
|
15998
|
+
printInfo(`Account: Logged in as ${who.data.email ?? `user #${who.data.user_id}`} \u2713`);
|
|
15999
|
+
} else if (who.ok && who.data.anonymous) {
|
|
16000
|
+
printInfo('Account: Anonymous \u2014 runs not saved, no cloud memory. Run "verity login".');
|
|
16001
|
+
} else if (tokenResult.data.userId != null) {
|
|
16002
|
+
printInfo(`Account: Logged in as ${tokenResult.data.email ?? `user #${tokenResult.data.userId}`} (cached \u2014 could not reach the Verity service) \u2713`);
|
|
16003
|
+
} else if (!who.ok) {
|
|
16004
|
+
printInfo(`Account: Unknown \u2014 could not reach the Verity service (${who.error})`);
|
|
16005
|
+
}
|
|
16006
|
+
if (who.ok) {
|
|
16007
|
+
const nudge = reverifyNudge(who.data);
|
|
16008
|
+
if (nudge) printWarn(` ${nudge}`);
|
|
16009
|
+
}
|
|
16010
|
+
if (denial) {
|
|
16011
|
+
printWarn(`Access: ${denial.remedy}`);
|
|
16012
|
+
printInfo(" Project status, history, and cloud memory stay unavailable until then.");
|
|
16013
|
+
}
|
|
16014
|
+
if (mem?.project_name) printInfo(`Project: ${mem.project_name}`);
|
|
16015
|
+
if (mem?.standard) {
|
|
15463
16016
|
const s = mem.standard;
|
|
15464
16017
|
printInfo(`Standard: v${s.version} (${s.quality_dimensions} quality, ${s.security_patterns} security, ${s.custom_patterns} custom)`);
|
|
15465
16018
|
printInfo(`Languages: ${s.languages.join(", ")}`);
|
|
@@ -15470,6 +16023,7 @@ function registerStatusCommand(program2) {
|
|
|
15470
16023
|
if (hookStatus.guardOn.includes("commit")) moments.push("pre-commit");
|
|
15471
16024
|
if (hookStatus.guardOn.includes("push")) moments.push("pre-push/PR");
|
|
15472
16025
|
printInfo(`Moments: ${moments.length > 0 ? moments.join(", ") : "none (run /verity-setup)"}`);
|
|
16026
|
+
if (!mem) return;
|
|
15473
16027
|
if (mem.recent_runs) {
|
|
15474
16028
|
const r = mem.recent_runs;
|
|
15475
16029
|
printInfo("");
|
|
@@ -15621,7 +16175,7 @@ async function sendGeneralFeedback(message, opts, globals) {
|
|
|
15621
16175
|
|
|
15622
16176
|
// src/commands/analyze.ts
|
|
15623
16177
|
var import_node_fs24 = require("node:fs");
|
|
15624
|
-
var
|
|
16178
|
+
var import_node_path20 = require("node:path");
|
|
15625
16179
|
|
|
15626
16180
|
// src/lib/debounce.ts
|
|
15627
16181
|
var import_node_fs15 = require("node:fs");
|
|
@@ -15758,7 +16312,7 @@ function writeIteration(iteration, commit, _contentHash) {
|
|
|
15758
16312
|
}
|
|
15759
16313
|
|
|
15760
16314
|
// src/lib/static-analysis.ts
|
|
15761
|
-
var
|
|
16315
|
+
var import_node_child_process7 = require("node:child_process");
|
|
15762
16316
|
var import_node_fs16 = require("node:fs");
|
|
15763
16317
|
var SEVERITY_ORDER = {
|
|
15764
16318
|
Error: 0,
|
|
@@ -15771,7 +16325,7 @@ var SEVERITY_ORDER = {
|
|
|
15771
16325
|
};
|
|
15772
16326
|
function isCodacyAvailable() {
|
|
15773
16327
|
try {
|
|
15774
|
-
(0,
|
|
16328
|
+
(0, import_node_child_process7.execSync)("which codacy-analysis", { stdio: "pipe" });
|
|
15775
16329
|
return true;
|
|
15776
16330
|
} catch {
|
|
15777
16331
|
return false;
|
|
@@ -15795,7 +16349,7 @@ function runCodacyAnalysis(files) {
|
|
|
15795
16349
|
const fileArgs = existingFiles.join(" ");
|
|
15796
16350
|
let output;
|
|
15797
16351
|
try {
|
|
15798
|
-
output = (0,
|
|
16352
|
+
output = (0, import_node_child_process7.execSync)(
|
|
15799
16353
|
`codacy-analysis analyze --install-dependencies --files ${fileArgs} --output-format json --log-level error --parallel-tools 3`,
|
|
15800
16354
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], maxBuffer: 10 * 1024 * 1024 }
|
|
15801
16355
|
);
|
|
@@ -15849,7 +16403,7 @@ function runCodacyAnalysis(files) {
|
|
|
15849
16403
|
|
|
15850
16404
|
// src/lib/specs.ts
|
|
15851
16405
|
var import_node_fs17 = require("node:fs");
|
|
15852
|
-
var
|
|
16406
|
+
var import_node_path16 = require("node:path");
|
|
15853
16407
|
var SPEC_CANDIDATES = [
|
|
15854
16408
|
"CLAUDE.md",
|
|
15855
16409
|
"AGENTS.md",
|
|
@@ -15911,7 +16465,7 @@ function findMdFiles(dir, maxDepth, depth = 0) {
|
|
|
15911
16465
|
try {
|
|
15912
16466
|
const entries = (0, import_node_fs17.readdirSync)(dir, { withFileTypes: true });
|
|
15913
16467
|
for (const entry of entries) {
|
|
15914
|
-
const fullPath = (0,
|
|
16468
|
+
const fullPath = (0, import_node_path16.join)(dir, entry.name);
|
|
15915
16469
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
15916
16470
|
result.push(fullPath);
|
|
15917
16471
|
} else if (entry.isDirectory() && depth < maxDepth - 1) {
|
|
@@ -15923,7 +16477,7 @@ function findMdFiles(dir, maxDepth, depth = 0) {
|
|
|
15923
16477
|
return result;
|
|
15924
16478
|
}
|
|
15925
16479
|
function discoverPlans() {
|
|
15926
|
-
const homePlansDir = (0,
|
|
16480
|
+
const homePlansDir = (0, import_node_path16.join)(process.env.HOME ?? "", ".claude", "plans");
|
|
15927
16481
|
const localPlansDir = ".claude/plans";
|
|
15928
16482
|
const candidates = [];
|
|
15929
16483
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -15933,7 +16487,7 @@ function discoverPlans() {
|
|
|
15933
16487
|
for (const f of (0, import_node_fs17.readdirSync)(plansDir)) {
|
|
15934
16488
|
if (!f.endsWith(".md") || seen.has(f)) continue;
|
|
15935
16489
|
seen.add(f);
|
|
15936
|
-
const fullPath = (0,
|
|
16490
|
+
const fullPath = (0, import_node_path16.join)(plansDir, f);
|
|
15937
16491
|
try {
|
|
15938
16492
|
const stat3 = (0, import_node_fs17.statSync)(fullPath);
|
|
15939
16493
|
candidates.push({ name: f, path: fullPath, mtime: stat3.mtimeMs, size: stat3.size });
|
|
@@ -15957,7 +16511,7 @@ function discoverPlans() {
|
|
|
15957
16511
|
}
|
|
15958
16512
|
|
|
15959
16513
|
// src/lib/task-context.ts
|
|
15960
|
-
var
|
|
16514
|
+
var import_node_child_process8 = require("node:child_process");
|
|
15961
16515
|
var CLOSING_RE = /\b(close[sd]?|fix(?:e[sd])?|resolve[sd]?)\b[\s:]*#(\d+)/i;
|
|
15962
16516
|
var BRANCH_RE = /(?:^|[/_-])(?:issue|gh|fix)[-_/]?(\d+)\b/i;
|
|
15963
16517
|
function parseLinkedIssue(sources) {
|
|
@@ -15973,7 +16527,7 @@ function parseLinkedIssue(sources) {
|
|
|
15973
16527
|
}
|
|
15974
16528
|
function safeExec(cmd, timeout) {
|
|
15975
16529
|
try {
|
|
15976
|
-
return (0,
|
|
16530
|
+
return (0, import_node_child_process8.execSync)(cmd, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout }).trim();
|
|
15977
16531
|
} catch {
|
|
15978
16532
|
return "";
|
|
15979
16533
|
}
|
|
@@ -16003,7 +16557,7 @@ function resolveTaskContext(opts) {
|
|
|
16003
16557
|
// src/lib/cli-version.ts
|
|
16004
16558
|
function cliVersion() {
|
|
16005
16559
|
try {
|
|
16006
|
-
return true ? "0.28.1-experimental.
|
|
16560
|
+
return true ? "0.28.1-experimental.af3c52d" : "dev";
|
|
16007
16561
|
} catch {
|
|
16008
16562
|
return "dev";
|
|
16009
16563
|
}
|
|
@@ -16207,7 +16761,7 @@ function truthy(v) {
|
|
|
16207
16761
|
|
|
16208
16762
|
// src/lib/fold.ts
|
|
16209
16763
|
var import_node_fs20 = require("node:fs");
|
|
16210
|
-
var
|
|
16764
|
+
var import_node_path17 = require("node:path");
|
|
16211
16765
|
var KNOWN_RECORD_TYPES = /* @__PURE__ */ new Set([
|
|
16212
16766
|
"user",
|
|
16213
16767
|
"assistant",
|
|
@@ -16391,7 +16945,7 @@ function fold(transcriptPath, opts = {}) {
|
|
|
16391
16945
|
return result;
|
|
16392
16946
|
}
|
|
16393
16947
|
try {
|
|
16394
|
-
const sidecarDir = (0,
|
|
16948
|
+
const sidecarDir = (0, import_node_path17.join)((0, import_node_path17.dirname)(transcriptPath), "subagents");
|
|
16395
16949
|
if ((0, import_node_fs20.existsSync)(sidecarDir)) {
|
|
16396
16950
|
const maxFiles = opts.maxSidecars ?? 200;
|
|
16397
16951
|
const maxBytes = opts.maxSidecarBytes ?? 16 * 1024 * 1024;
|
|
@@ -16399,7 +16953,7 @@ function fold(transcriptPath, opts = {}) {
|
|
|
16399
16953
|
const walk = (d, depth) => {
|
|
16400
16954
|
if (depth > 4) return;
|
|
16401
16955
|
for (const e of (0, import_node_fs20.readdirSync)(d, { withFileTypes: true })) {
|
|
16402
|
-
const p = (0,
|
|
16956
|
+
const p = (0, import_node_path17.join)(d, e.name);
|
|
16403
16957
|
if (e.isDirectory()) {
|
|
16404
16958
|
walk(p, depth + 1);
|
|
16405
16959
|
} else if (e.name.startsWith("agent-") && e.name.endsWith(".jsonl")) {
|
|
@@ -16554,9 +17108,16 @@ function buildAgentContext(input) {
|
|
|
16554
17108
|
}
|
|
16555
17109
|
if (input.intentVerdict === "misaligned" || input.intentVerdict === "partial") {
|
|
16556
17110
|
const gap = input.intentGaps?.[0];
|
|
16557
|
-
|
|
16558
|
-
|
|
16559
|
-
|
|
17111
|
+
const repeat = input.intentRepeat ?? 0;
|
|
17112
|
+
if (repeat > 0) {
|
|
17113
|
+
lines.push(
|
|
17114
|
+
`- 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.`
|
|
17115
|
+
);
|
|
17116
|
+
} else {
|
|
17117
|
+
lines.push(
|
|
17118
|
+
`- 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."
|
|
17119
|
+
);
|
|
17120
|
+
}
|
|
16560
17121
|
}
|
|
16561
17122
|
const agentFindings = (input.findings ?? []).filter((f) => f.scope !== "pre-existing");
|
|
16562
17123
|
for (const f of agentFindings) {
|
|
@@ -16606,7 +17167,7 @@ function buildHookOutput(gateDecision, systemMessage, agentContext) {
|
|
|
16606
17167
|
|
|
16607
17168
|
// src/lib/cache-cleanup.ts
|
|
16608
17169
|
var import_node_fs21 = require("node:fs");
|
|
16609
|
-
var
|
|
17170
|
+
var import_node_path18 = require("node:path");
|
|
16610
17171
|
var CACHE_TTL_DAYS = 7;
|
|
16611
17172
|
function pruneStaleCache() {
|
|
16612
17173
|
try {
|
|
@@ -16614,7 +17175,7 @@ function pruneStaleCache() {
|
|
|
16614
17175
|
const cutoff = Date.now() - CACHE_TTL_DAYS * 24 * 3600 * 1e3;
|
|
16615
17176
|
for (const entry of (0, import_node_fs21.readdirSync)(dir)) {
|
|
16616
17177
|
if (!entry.startsWith("pending-")) continue;
|
|
16617
|
-
const path = (0,
|
|
17178
|
+
const path = (0, import_node_path18.join)(dir, entry);
|
|
16618
17179
|
try {
|
|
16619
17180
|
const stat3 = (0, import_node_fs21.statSync)(path);
|
|
16620
17181
|
if (stat3.mtimeMs < cutoff) {
|
|
@@ -17061,7 +17622,7 @@ function isExplicitlyAutonomous(env = process.env) {
|
|
|
17061
17622
|
// src/lib/seed-runner.ts
|
|
17062
17623
|
var import_promises11 = require("node:fs/promises");
|
|
17063
17624
|
var import_node_fs23 = require("node:fs");
|
|
17064
|
-
var
|
|
17625
|
+
var import_node_path19 = require("node:path");
|
|
17065
17626
|
var import_yaml2 = __toESM(require_dist());
|
|
17066
17627
|
|
|
17067
17628
|
// src/lib/seed.ts
|
|
@@ -17343,7 +17904,7 @@ async function runSeed(opts) {
|
|
|
17343
17904
|
if (candidates.length === 0) {
|
|
17344
17905
|
return { created: 0, failed: 0, skipped: "no_candidates", candidates: [] };
|
|
17345
17906
|
}
|
|
17346
|
-
const overviewPath = (0,
|
|
17907
|
+
const overviewPath = (0, import_node_path19.join)(MEMORY_DIR, "domain", "project-overview.md");
|
|
17347
17908
|
if ((0, import_node_fs23.existsSync)(overviewPath) && !opts.force) {
|
|
17348
17909
|
return { created: 0, failed: 0, skipped: "already_seeded", candidates };
|
|
17349
17910
|
}
|
|
@@ -17379,9 +17940,9 @@ async function runSeed(opts) {
|
|
|
17379
17940
|
}
|
|
17380
17941
|
const nodeId = res.data.node_id;
|
|
17381
17942
|
const filePathRel = res.data.file_path;
|
|
17382
|
-
const targetPath = (0,
|
|
17943
|
+
const targetPath = (0, import_node_path19.join)(MEMORY_DIR, filePathRel);
|
|
17383
17944
|
try {
|
|
17384
|
-
await (0, import_promises11.mkdir)((0,
|
|
17945
|
+
await (0, import_promises11.mkdir)((0, import_node_path19.dirname)(targetPath), { recursive: true });
|
|
17385
17946
|
await (0, import_promises11.writeFile)(targetPath, renderNodeMarkdown(c, nodeId, createdAt));
|
|
17386
17947
|
created++;
|
|
17387
17948
|
opts.onCreated?.(nodeId, filePathRel, c);
|
|
@@ -17430,10 +17991,11 @@ async function readStopHookStdin() {
|
|
|
17430
17991
|
return empty;
|
|
17431
17992
|
}
|
|
17432
17993
|
}
|
|
17433
|
-
function agentContextFor(response) {
|
|
17994
|
+
function agentContextFor(response, intentRepeat = 0) {
|
|
17434
17995
|
const metadata = response.metadata ?? {};
|
|
17435
17996
|
const intent = response.intent_alignment ?? {};
|
|
17436
17997
|
return buildAgentContext({
|
|
17998
|
+
intentRepeat,
|
|
17437
17999
|
gateDecision: String(response.gate_decision ?? ""),
|
|
17438
18000
|
findings: response.findings ?? [],
|
|
17439
18001
|
pendingItems: response.pending_items ?? [],
|
|
@@ -17707,7 +18269,7 @@ async function runAnalyze(opts, globals) {
|
|
|
17707
18269
|
let autoSeedNotice = null;
|
|
17708
18270
|
try {
|
|
17709
18271
|
await ensureMemoryDir();
|
|
17710
|
-
const seedMarker = (0,
|
|
18272
|
+
const seedMarker = (0, import_node_path20.join)(VERITY_DIR, ".seeded");
|
|
17711
18273
|
const hasStandard = (0, import_node_fs24.existsSync)(STANDARD_FILE);
|
|
17712
18274
|
const alreadyTried = (0, import_node_fs24.existsSync)(seedMarker);
|
|
17713
18275
|
if (hasStandard && !alreadyTried) {
|
|
@@ -17775,7 +18337,7 @@ async function runAnalyze(opts, globals) {
|
|
|
17775
18337
|
const priorState = foldForMarks(memorySession.d);
|
|
17776
18338
|
incrementReport = computeIncrement(
|
|
17777
18339
|
allForReview,
|
|
17778
|
-
(p) => fileHash((0,
|
|
18340
|
+
(p) => fileHash((0, import_node_path20.join)(repoRoot(), p)),
|
|
17779
18341
|
priorState.authored_all.map((a) => ({
|
|
17780
18342
|
path: a.path,
|
|
17781
18343
|
hash_at_last_verdict: a.hash_at_last_verdict
|
|
@@ -17917,6 +18479,25 @@ async function runAnalyze(opts, globals) {
|
|
|
17917
18479
|
projection: {
|
|
17918
18480
|
v: 1,
|
|
17919
18481
|
authored: foldResult?.authored ?? [],
|
|
18482
|
+
// ⚠ AUTHORED *SINCE THE LAST VERDICT* — a different question from `authored`.
|
|
18483
|
+
//
|
|
18484
|
+
// `authored` above is the fold of the session TRANSCRIPT, which is
|
|
18485
|
+
// cumulative: on turn 3 it still lists the files turn 2 edited. The
|
|
18486
|
+
// account's close-out reads it as "was this file edited since the
|
|
18487
|
+
// statement was raised", and those are not the same set.
|
|
18488
|
+
//
|
|
18489
|
+
// Measured 2026-08-03 (shirt-seller): four real vulnerabilities were
|
|
18490
|
+
// raised on the turn that wrote them, then marked `fixed` 36 seconds
|
|
18491
|
+
// later by a SUMMARY turn that edited nothing — `authored` still said 2,
|
|
18492
|
+
// the turn carried 0 findings, so "gone + file authored" resolved to
|
|
18493
|
+
// fixed. The vulnerabilities were still on disk. A silent false `fixed`
|
|
18494
|
+
// is worse than a false `open`: it retires the statement the Account
|
|
18495
|
+
// exists to keep.
|
|
18496
|
+
//
|
|
18497
|
+
// `hash_at_last_verdict` is frozen at each verdict and `hash_now` tracks
|
|
18498
|
+
// disk, so their inequality IS "changed since we last spoke" — already
|
|
18499
|
+
// computed, already maintained by the divergence machinery.
|
|
18500
|
+
authored_since_verdict: memorySession ? foldDossier(memorySession.d).authored_all.filter((a) => a.hash_now !== a.hash_at_last_verdict).map((a) => a.path) : [],
|
|
17920
18501
|
unobserved: foldResult?.unobserved ?? [],
|
|
17921
18502
|
commands: foldResult?.commands ?? [],
|
|
17922
18503
|
unknown_types: foldResult?.unknownTypes ?? [],
|
|
@@ -18030,6 +18611,12 @@ async function runAnalyze(opts, globals) {
|
|
|
18030
18611
|
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
18612
|
} else if (result.category === "network" || result.category === "timeout") {
|
|
18032
18613
|
message = `Verity offline \u2014 ${result.error}`;
|
|
18614
|
+
} else if (result.error.startsWith("STALE_VERIFICATION")) {
|
|
18615
|
+
message = "Verity: your GitHub verification expired \u2014 run `verity login` to re-verify (local analysis this run)";
|
|
18616
|
+
} else if (result.error.startsWith("FORBIDDEN")) {
|
|
18617
|
+
message = "Verity: no access grant for this repository \u2014 run `verity login` to refresh your grants (local analysis this run)";
|
|
18618
|
+
} else if (result.error.startsWith("INVALID_TOKEN")) {
|
|
18619
|
+
message = "Verity: your login expired or was revoked \u2014 run `verity login` to sign in again (local analysis this run)";
|
|
18033
18620
|
} else if (result.http_status && result.http_status >= 400 && result.http_status < 500) {
|
|
18034
18621
|
message = `Verity: request rejected (HTTP ${result.http_status}) \u2014 ${result.error}`;
|
|
18035
18622
|
} else if (result.http_status && result.http_status >= 500) {
|
|
@@ -18046,6 +18633,7 @@ async function runAnalyze(opts, globals) {
|
|
|
18046
18633
|
const sentPaths = codeDelta.files.map((f) => f.path);
|
|
18047
18634
|
const watermarkHash = sentPaths.length > 0 ? computeContentHash(sentPaths) : contentHash;
|
|
18048
18635
|
const watermarkIsPartial = !!codeDelta.truncated;
|
|
18636
|
+
let intentRepeatCount = 0;
|
|
18049
18637
|
if (memorySession) {
|
|
18050
18638
|
try {
|
|
18051
18639
|
recordVerdict(memorySession.d, {
|
|
@@ -18059,8 +18647,10 @@ async function runAnalyze(opts, globals) {
|
|
|
18059
18647
|
pattern_id: f.pattern_id ?? f.rule_id,
|
|
18060
18648
|
title: f.title,
|
|
18061
18649
|
severity: f.severity
|
|
18062
|
-
})) ?? []
|
|
18650
|
+
})) ?? [],
|
|
18651
|
+
intent: response.intent_alignment ?? null
|
|
18063
18652
|
});
|
|
18653
|
+
intentRepeatCount = Math.max(0, (foldDossier(memorySession.d).meta.intent_repeat?.consecutive ?? 1) - 1);
|
|
18064
18654
|
} catch {
|
|
18065
18655
|
}
|
|
18066
18656
|
}
|
|
@@ -18153,6 +18743,11 @@ async function runAnalyze(opts, globals) {
|
|
|
18153
18743
|
}
|
|
18154
18744
|
saveSnapshots(codeDelta.files.map((f) => ({ path: f.path, content: f.content })));
|
|
18155
18745
|
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." : "";
|
|
18746
|
+
const grantWarning = reverifyNudge({
|
|
18747
|
+
grant_status: response.grant_status,
|
|
18748
|
+
reverify_by: response.reverify_by
|
|
18749
|
+
});
|
|
18750
|
+
const grantNudge = grantWarning ? ` \u26A0\uFE0F ${grantWarning}` : "";
|
|
18156
18751
|
switch (decision) {
|
|
18157
18752
|
case "FAIL": {
|
|
18158
18753
|
writeIteration(iteration + 1, currentCommit, contentHash ?? void 0);
|
|
@@ -18228,6 +18823,9 @@ async function runAnalyze(opts, globals) {
|
|
|
18228
18823
|
`);
|
|
18229
18824
|
if (loginNudge) process.stderr.write(`
|
|
18230
18825
|
${YELLOW}${loginNudge.trim()}${NC}
|
|
18826
|
+
`);
|
|
18827
|
+
if (grantNudge) process.stderr.write(`
|
|
18828
|
+
${YELLOW}${grantNudge.trim()}${NC}
|
|
18231
18829
|
`);
|
|
18232
18830
|
process.exit(2);
|
|
18233
18831
|
break;
|
|
@@ -18240,9 +18838,9 @@ ${YELLOW}${loginNudge.trim()}${NC}
|
|
|
18240
18838
|
const viewUrl = response.view_url ?? "";
|
|
18241
18839
|
if (viewUrl) userSummary += ` Report: ${viewUrl}`;
|
|
18242
18840
|
if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
|
|
18243
|
-
userSummary += loginNudge;
|
|
18841
|
+
userSummary += loginNudge + grantNudge;
|
|
18244
18842
|
printJsonCompact(
|
|
18245
|
-
buildHookOutput("PASS", userSummary, agentContextFor(response))
|
|
18843
|
+
buildHookOutput("PASS", userSummary, agentContextFor(response, intentRepeatCount))
|
|
18246
18844
|
);
|
|
18247
18845
|
process.exit(0);
|
|
18248
18846
|
break;
|
|
@@ -18254,16 +18852,16 @@ ${YELLOW}${loginNudge.trim()}${NC}
|
|
|
18254
18852
|
const viewUrl = response.view_url ?? "";
|
|
18255
18853
|
if (viewUrl) userSummary += ` Report: ${viewUrl}`;
|
|
18256
18854
|
if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
|
|
18257
|
-
userSummary += loginNudge;
|
|
18855
|
+
userSummary += loginNudge + grantNudge;
|
|
18258
18856
|
printJsonCompact(
|
|
18259
|
-
buildHookOutput("WARN", userSummary, agentContextFor(response))
|
|
18857
|
+
buildHookOutput("WARN", userSummary, agentContextFor(response, intentRepeatCount))
|
|
18260
18858
|
);
|
|
18261
18859
|
process.exit(0);
|
|
18262
18860
|
break;
|
|
18263
18861
|
}
|
|
18264
18862
|
default: {
|
|
18265
18863
|
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;
|
|
18864
|
+
const msg = (autoSeedNotice ? `${autoSeedNotice} Verity: unrecognised verdict \u2014 treating as WARN` : "Verity: unrecognised verdict \u2014 treating as WARN") + loginNudge + grantNudge;
|
|
18267
18865
|
process.stderr.write(
|
|
18268
18866
|
`Verity: server returned an unrecognised gate_decision (${raw}). Rendering WARN rather than PASS. Update the CLI: npm i -g @codacy/verity-cli
|
|
18269
18867
|
`
|
|
@@ -18430,9 +19028,9 @@ async function runReview(opts, globals) {
|
|
|
18430
19028
|
|
|
18431
19029
|
// src/commands/guard.ts
|
|
18432
19030
|
var import_node_fs27 = require("node:fs");
|
|
18433
|
-
var
|
|
19031
|
+
var import_node_path21 = require("node:path");
|
|
18434
19032
|
var GUARD_BLOCK_CAP = 2;
|
|
18435
|
-
var GUARD_ITER_FILE = (0,
|
|
19033
|
+
var GUARD_ITER_FILE = (0, import_node_path21.join)(VERITY_DIR, ".guard-iteration");
|
|
18436
19034
|
function readPreToolUseStdin() {
|
|
18437
19035
|
const empty = { command: "", cwd: null, sessionId: null };
|
|
18438
19036
|
return new Promise((resolve2) => {
|
|
@@ -18674,9 +19272,10 @@ async function runGuard(opts, globals) {
|
|
|
18674
19272
|
cmd: "guard"
|
|
18675
19273
|
});
|
|
18676
19274
|
if (!result.ok) {
|
|
19275
|
+
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
19276
|
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
|
|
19277
|
+
`\u26A0 Verity ${moment}: ${authRemedy ? "not authorized" : "service offline"} \u2014 ${verb}ed WITHOUT review${authRemedy}`,
|
|
19278
|
+
`Verity ${moment}: ${authRemedy ? "not authorized" : "service unavailable"} (${result.error}); the ${verb} was allowed WITHOUT a Verity review.${authRemedy}`
|
|
18680
19279
|
);
|
|
18681
19280
|
}
|
|
18682
19281
|
if (opts.json) process.stderr.write(JSON.stringify(result.data) + "\n");
|
|
@@ -18748,14 +19347,14 @@ function writeBlockMessage(moment, response) {
|
|
|
18748
19347
|
// src/commands/init.ts
|
|
18749
19348
|
var import_node_fs29 = require("node:fs");
|
|
18750
19349
|
var import_promises13 = require("node:fs/promises");
|
|
18751
|
-
var
|
|
18752
|
-
var
|
|
19350
|
+
var import_node_path23 = require("node:path");
|
|
19351
|
+
var import_node_child_process10 = require("node:child_process");
|
|
18753
19352
|
var readline2 = __toESM(require("node:readline/promises"));
|
|
18754
19353
|
|
|
18755
19354
|
// src/commands/migrate.ts
|
|
18756
19355
|
var import_node_fs28 = require("node:fs");
|
|
18757
|
-
var
|
|
18758
|
-
var
|
|
19356
|
+
var import_node_path22 = require("node:path");
|
|
19357
|
+
var import_node_child_process9 = require("node:child_process");
|
|
18759
19358
|
|
|
18760
19359
|
// src/lib/telemetry.ts
|
|
18761
19360
|
var import_promises12 = require("node:fs/promises");
|
|
@@ -18850,11 +19449,11 @@ async function uninstallTelemetry() {
|
|
|
18850
19449
|
// src/commands/migrate.ts
|
|
18851
19450
|
var LEGACY_NPM_PACKAGE = "@codacy/gate-cli";
|
|
18852
19451
|
function defaultNpmRemover(pkg) {
|
|
18853
|
-
(0,
|
|
19452
|
+
(0, import_node_child_process9.execSync)(`npm rm -g ${pkg}`, { stdio: "pipe", timeout: 12e4 });
|
|
18854
19453
|
}
|
|
18855
19454
|
function isGitTracked(cwd, relPath) {
|
|
18856
19455
|
try {
|
|
18857
|
-
(0,
|
|
19456
|
+
(0, import_node_child_process9.execSync)(`git ls-files --error-unmatch ${relPath}`, { cwd, stdio: "pipe" });
|
|
18858
19457
|
return true;
|
|
18859
19458
|
} catch {
|
|
18860
19459
|
return false;
|
|
@@ -18862,7 +19461,7 @@ function isGitTracked(cwd, relPath) {
|
|
|
18862
19461
|
}
|
|
18863
19462
|
function isGitRepo(cwd) {
|
|
18864
19463
|
try {
|
|
18865
|
-
(0,
|
|
19464
|
+
(0, import_node_child_process9.execSync)("git rev-parse --is-inside-work-tree", { cwd, stdio: "pipe" });
|
|
18866
19465
|
return true;
|
|
18867
19466
|
} catch {
|
|
18868
19467
|
return false;
|
|
@@ -18883,8 +19482,8 @@ async function runMigration(opts = {}) {
|
|
|
18883
19482
|
return { actions, migrated: actions.length > 0 };
|
|
18884
19483
|
}
|
|
18885
19484
|
function migrateProjectDir(root, actions) {
|
|
18886
|
-
const gateDir = (0,
|
|
18887
|
-
const verityDir = (0,
|
|
19485
|
+
const gateDir = (0, import_node_path22.join)(root, ".gate");
|
|
19486
|
+
const verityDir = (0, import_node_path22.join)(root, ".verity");
|
|
18888
19487
|
if ((0, import_node_fs28.existsSync)(gateDir) && !(0, import_node_fs28.existsSync)(verityDir)) {
|
|
18889
19488
|
return migrateProjectDirRename(root, gateDir, verityDir, actions);
|
|
18890
19489
|
}
|
|
@@ -18902,7 +19501,7 @@ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
|
|
|
18902
19501
|
);
|
|
18903
19502
|
}
|
|
18904
19503
|
try {
|
|
18905
|
-
(0,
|
|
19504
|
+
(0, import_node_child_process9.execSync)("git mv .gate .verity", { cwd: root, stdio: "pipe" });
|
|
18906
19505
|
actions.push("Moved .gate/ \u2192 .verity/ (git mv, staged)");
|
|
18907
19506
|
moved = true;
|
|
18908
19507
|
} catch {
|
|
@@ -18938,11 +19537,11 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
|
|
|
18938
19537
|
}
|
|
18939
19538
|
function migrateGlobalCredentials(home, actions) {
|
|
18940
19539
|
if (!home) return;
|
|
18941
|
-
const gateCreds = (0,
|
|
18942
|
-
const verityCreds = (0,
|
|
19540
|
+
const gateCreds = (0, import_node_path22.join)(home, ".gate", "credentials");
|
|
19541
|
+
const verityCreds = (0, import_node_path22.join)(home, ".verity", "credentials");
|
|
18943
19542
|
if (!(0, import_node_fs28.existsSync)(gateCreds)) return;
|
|
18944
19543
|
if (!(0, import_node_fs28.existsSync)(verityCreds)) {
|
|
18945
|
-
(0, import_node_fs28.mkdirSync)((0,
|
|
19544
|
+
(0, import_node_fs28.mkdirSync)((0, import_node_path22.join)(home, ".verity"), { recursive: true });
|
|
18946
19545
|
moveFile(gateCreds, verityCreds);
|
|
18947
19546
|
actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
|
|
18948
19547
|
return;
|
|
@@ -18964,7 +19563,7 @@ async function migrateLegacyHooks(root, actions) {
|
|
|
18964
19563
|
}
|
|
18965
19564
|
}
|
|
18966
19565
|
async function migrateClaudeMd(root, actions) {
|
|
18967
|
-
const claudeMd = (0,
|
|
19566
|
+
const claudeMd = (0, import_node_path22.join)(root, "CLAUDE.md");
|
|
18968
19567
|
const hadLegacyBlock = (0, import_node_fs28.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
|
|
18969
19568
|
if (!hadLegacyBlock) return;
|
|
18970
19569
|
try {
|
|
@@ -18975,13 +19574,13 @@ async function migrateClaudeMd(root, actions) {
|
|
|
18975
19574
|
}
|
|
18976
19575
|
}
|
|
18977
19576
|
function migrateStandardFile(root, actions) {
|
|
18978
|
-
const gateMd = (0,
|
|
18979
|
-
const verityMd = (0,
|
|
19577
|
+
const gateMd = (0, import_node_path22.join)(root, "GATE.md");
|
|
19578
|
+
const verityMd = (0, import_node_path22.join)(root, "VERITY.md");
|
|
18980
19579
|
if (!(0, import_node_fs28.existsSync)(gateMd) || (0, import_node_fs28.existsSync)(verityMd)) return;
|
|
18981
19580
|
let moved = false;
|
|
18982
19581
|
if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
|
|
18983
19582
|
try {
|
|
18984
|
-
(0,
|
|
19583
|
+
(0, import_node_child_process9.execSync)("git mv GATE.md VERITY.md", { cwd: root, stdio: "pipe" });
|
|
18985
19584
|
moved = true;
|
|
18986
19585
|
} catch {
|
|
18987
19586
|
}
|
|
@@ -18993,7 +19592,7 @@ function migrateStandardFile(root, actions) {
|
|
|
18993
19592
|
actions.push("Renamed GATE.md \u2192 VERITY.md");
|
|
18994
19593
|
}
|
|
18995
19594
|
async function migrateTelemetryHeaders(root, actions) {
|
|
18996
|
-
const file = (0,
|
|
19595
|
+
const file = (0, import_node_path22.join)(root, ".claude", "settings.local.json");
|
|
18997
19596
|
if (!(0, import_node_fs28.existsSync)(file)) return;
|
|
18998
19597
|
let settings;
|
|
18999
19598
|
try {
|
|
@@ -19056,7 +19655,7 @@ function readFileSyncSafe(path) {
|
|
|
19056
19655
|
}
|
|
19057
19656
|
function hasStagedChanges(root) {
|
|
19058
19657
|
try {
|
|
19059
|
-
(0,
|
|
19658
|
+
(0, import_node_child_process9.execSync)("git diff --cached --quiet", { cwd: root, stdio: "pipe" });
|
|
19060
19659
|
return false;
|
|
19061
19660
|
} catch {
|
|
19062
19661
|
return true;
|
|
@@ -19083,15 +19682,15 @@ function moveFile(from, to) {
|
|
|
19083
19682
|
function carryLegacyContents(gateDir, verityDir) {
|
|
19084
19683
|
let copied = 0;
|
|
19085
19684
|
const walk = (relDir) => {
|
|
19086
|
-
const srcDir = (0,
|
|
19685
|
+
const srcDir = (0, import_node_path22.join)(gateDir, relDir);
|
|
19087
19686
|
for (const entry of (0, import_node_fs28.readdirSync)(srcDir)) {
|
|
19088
|
-
const rel = relDir ? (0,
|
|
19089
|
-
const src = (0,
|
|
19090
|
-
const dest = (0,
|
|
19687
|
+
const rel = relDir ? (0, import_node_path22.join)(relDir, entry) : entry;
|
|
19688
|
+
const src = (0, import_node_path22.join)(gateDir, rel);
|
|
19689
|
+
const dest = (0, import_node_path22.join)(verityDir, rel);
|
|
19091
19690
|
if ((0, import_node_fs28.statSync)(src).isDirectory()) {
|
|
19092
19691
|
walk(rel);
|
|
19093
19692
|
} else if (!(0, import_node_fs28.existsSync)(dest)) {
|
|
19094
|
-
(0, import_node_fs28.mkdirSync)((0,
|
|
19693
|
+
(0, import_node_fs28.mkdirSync)((0, import_node_path22.dirname)(dest), { recursive: true });
|
|
19095
19694
|
(0, import_node_fs28.cpSync)(src, dest);
|
|
19096
19695
|
copied++;
|
|
19097
19696
|
}
|
|
@@ -19101,22 +19700,22 @@ function carryLegacyContents(gateDir, verityDir) {
|
|
|
19101
19700
|
return copied;
|
|
19102
19701
|
}
|
|
19103
19702
|
async function needsMigration(root = repoRoot()) {
|
|
19104
|
-
const gateDir = (0,
|
|
19105
|
-
const verityDir = (0,
|
|
19703
|
+
const gateDir = (0, import_node_path22.join)(root, ".gate");
|
|
19704
|
+
const verityDir = (0, import_node_path22.join)(root, ".verity");
|
|
19106
19705
|
if ((0, import_node_fs28.existsSync)(gateDir) && !(0, import_node_fs28.existsSync)(verityDir)) return true;
|
|
19107
19706
|
if ((0, import_node_fs28.existsSync)(gateDir) && (0, import_node_fs28.existsSync)(verityDir)) {
|
|
19108
|
-
if ((0, import_node_fs28.existsSync)((0,
|
|
19707
|
+
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
19708
|
return true;
|
|
19110
19709
|
}
|
|
19111
|
-
if ((0, import_node_fs28.existsSync)((0,
|
|
19710
|
+
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
19711
|
return true;
|
|
19113
19712
|
}
|
|
19114
19713
|
}
|
|
19115
|
-
const claudeMd = (0,
|
|
19714
|
+
const claudeMd = (0, import_node_path22.join)(root, "CLAUDE.md");
|
|
19116
19715
|
if ((0, import_node_fs28.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
|
|
19117
19716
|
return true;
|
|
19118
19717
|
}
|
|
19119
|
-
if ((0, import_node_fs28.existsSync)((0,
|
|
19718
|
+
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
19719
|
return true;
|
|
19121
19720
|
}
|
|
19122
19721
|
if (await hasLegacyHooksAt(root)) return true;
|
|
@@ -19152,12 +19751,25 @@ async function promptYes(question) {
|
|
|
19152
19751
|
rl.close();
|
|
19153
19752
|
}
|
|
19154
19753
|
}
|
|
19155
|
-
async function confirmExistingLogin(serviceUrl, opts) {
|
|
19754
|
+
async function confirmExistingLogin(serviceUrl, remote, opts) {
|
|
19156
19755
|
const existing = await resolveToken(opts.token);
|
|
19157
19756
|
if (!existing.ok) return "drive-login";
|
|
19158
19757
|
const who = await whoami(existing.data.token, serviceUrl, opts.verbose);
|
|
19159
19758
|
if (who.ok && who.data.logged_in) {
|
|
19160
|
-
|
|
19759
|
+
const identity = who.data.email ?? `user #${who.data.user_id}`;
|
|
19760
|
+
const covered = who.data.grant_status != null;
|
|
19761
|
+
if (!remote) {
|
|
19762
|
+
printInfo(`Logged in as ${identity} \u2713`);
|
|
19763
|
+
printInfo(" This directory has no git remote, so there is no project here to sync to.");
|
|
19764
|
+
} else if (covered) {
|
|
19765
|
+
printInfo(`Logged in as ${identity} \u2713 \u2014 runs & memory sync to Verity.`);
|
|
19766
|
+
} else {
|
|
19767
|
+
printInfo(`Logged in as ${identity} \u2713`);
|
|
19768
|
+
printWarn(" This repository is NOT covered by your Verity access grants \u2014 nothing will sync.");
|
|
19769
|
+
printInfo(' Grant the Verity GitHub App access to it, then run "verity login" to refresh your grants.');
|
|
19770
|
+
}
|
|
19771
|
+
const nudge = reverifyNudge(who.data);
|
|
19772
|
+
if (nudge) printWarn(` ${nudge}`);
|
|
19161
19773
|
return "handled";
|
|
19162
19774
|
}
|
|
19163
19775
|
if (!who.ok) {
|
|
@@ -19186,18 +19798,18 @@ async function runOptionalAuth(resolution, opts = {}) {
|
|
|
19186
19798
|
printInfo(" Log in below to re-register this project and repair ~/.verity/credentials.");
|
|
19187
19799
|
}
|
|
19188
19800
|
}
|
|
19189
|
-
if (!healed) {
|
|
19190
|
-
const state = await confirmExistingLogin(serviceUrl, opts);
|
|
19191
|
-
if (state === "handled") return;
|
|
19192
|
-
}
|
|
19193
19801
|
let remote = "";
|
|
19194
19802
|
try {
|
|
19195
|
-
remote = (0,
|
|
19803
|
+
remote = (0, import_node_child_process10.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
19196
19804
|
} catch {
|
|
19197
19805
|
}
|
|
19806
|
+
if (!healed) {
|
|
19807
|
+
const state = await confirmExistingLogin(serviceUrl, remote, opts);
|
|
19808
|
+
if (state === "handled") return;
|
|
19809
|
+
}
|
|
19198
19810
|
const localOnlyNote = () => {
|
|
19199
19811
|
printInfo("Verity runs in local-only mode: the gate still runs and shows static findings, but nothing uploads.");
|
|
19200
|
-
printInfo(' Authenticate anytime: run "verity
|
|
19812
|
+
printInfo(' Authenticate anytime: run "verity login" (one login covers every repo you can write to).');
|
|
19201
19813
|
};
|
|
19202
19814
|
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
19203
19815
|
console.log("");
|
|
@@ -19223,7 +19835,7 @@ async function runOptionalAuth(resolution, opts = {}) {
|
|
|
19223
19835
|
localOnlyNote();
|
|
19224
19836
|
return;
|
|
19225
19837
|
}
|
|
19226
|
-
const projectName = parseRemote(remote)?.repo ?? (0,
|
|
19838
|
+
const projectName = parseRemote(remote)?.repo ?? (0, import_node_path23.basename)(process.cwd());
|
|
19227
19839
|
printInfo("Authenticating with GitHub\u2026");
|
|
19228
19840
|
const result = await registerProject({ projectName, remote, serviceUrl, verbose: opts.verbose });
|
|
19229
19841
|
if (result.ok) {
|
|
@@ -19236,15 +19848,15 @@ async function runOptionalAuth(resolution, opts = {}) {
|
|
|
19236
19848
|
}
|
|
19237
19849
|
function resolveDataDir() {
|
|
19238
19850
|
const candidates = [
|
|
19239
|
-
(0,
|
|
19851
|
+
(0, import_node_path23.join)(__dirname, "..", "data"),
|
|
19240
19852
|
// installed: node_modules/@codacy/verity-cli/data
|
|
19241
|
-
(0,
|
|
19853
|
+
(0, import_node_path23.join)(__dirname, "..", "..", "data"),
|
|
19242
19854
|
// edge case: nested resolution
|
|
19243
|
-
(0,
|
|
19855
|
+
(0, import_node_path23.join)(process.cwd(), "cli", "data")
|
|
19244
19856
|
// local dev: running from repo root
|
|
19245
19857
|
];
|
|
19246
19858
|
for (const candidate of candidates) {
|
|
19247
|
-
if ((0, import_node_fs29.existsSync)((0,
|
|
19859
|
+
if ((0, import_node_fs29.existsSync)((0, import_node_path23.join)(candidate, "skills"))) {
|
|
19248
19860
|
return candidate;
|
|
19249
19861
|
}
|
|
19250
19862
|
}
|
|
@@ -19288,30 +19900,30 @@ function registerInitCommand(program2) {
|
|
|
19288
19900
|
}
|
|
19289
19901
|
printInfo(` Node.js ${nodeVersion} \u2713`);
|
|
19290
19902
|
try {
|
|
19291
|
-
const gitVersion = (0,
|
|
19903
|
+
const gitVersion = (0, import_node_child_process10.execSync)("git --version", { encoding: "utf-8" }).trim();
|
|
19292
19904
|
printInfo(` ${gitVersion} \u2713`);
|
|
19293
19905
|
} catch {
|
|
19294
19906
|
printError("git is required but not installed. Install from https://git-scm.com");
|
|
19295
19907
|
process.exit(1);
|
|
19296
19908
|
}
|
|
19297
19909
|
try {
|
|
19298
|
-
(0,
|
|
19910
|
+
(0, import_node_child_process10.execSync)("which claude", { encoding: "utf-8" });
|
|
19299
19911
|
printInfo(" Claude Code \u2713");
|
|
19300
19912
|
} catch {
|
|
19301
19913
|
printWarn(" Claude Code not found \u2014 hooks will be configured but need Claude Code to run.");
|
|
19302
19914
|
}
|
|
19303
19915
|
try {
|
|
19304
|
-
(0,
|
|
19916
|
+
(0, import_node_child_process10.execSync)("which codacy-analysis", { encoding: "utf-8", stdio: "pipe" });
|
|
19305
19917
|
printInfo(" @codacy/analysis-cli \u2713");
|
|
19306
19918
|
} catch {
|
|
19307
19919
|
printInfo(" Installing @codacy/analysis-cli...");
|
|
19308
19920
|
try {
|
|
19309
|
-
(0,
|
|
19921
|
+
(0, import_node_child_process10.execSync)("npm install -g @codacy/analysis-cli", { encoding: "utf-8", stdio: "pipe", timeout: 12e4 });
|
|
19310
19922
|
printInfo(" @codacy/analysis-cli installed \u2713");
|
|
19311
19923
|
} catch {
|
|
19312
19924
|
try {
|
|
19313
19925
|
printWarn(" Retrying with sudo...");
|
|
19314
|
-
(0,
|
|
19926
|
+
(0, import_node_child_process10.execSync)("sudo npm install -g @codacy/analysis-cli", { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
|
|
19315
19927
|
printInfo(" @codacy/analysis-cli installed \u2713");
|
|
19316
19928
|
} catch {
|
|
19317
19929
|
printWarn(" Could not install @codacy/analysis-cli automatically.");
|
|
@@ -19323,20 +19935,20 @@ function registerInitCommand(program2) {
|
|
|
19323
19935
|
console.log("");
|
|
19324
19936
|
printInfo("Installing skills...");
|
|
19325
19937
|
const dataDir = resolveDataDir();
|
|
19326
|
-
const skillsSource = (0,
|
|
19938
|
+
const skillsSource = (0, import_node_path23.join)(dataDir, "skills");
|
|
19327
19939
|
const skillsDest = ".claude/skills";
|
|
19328
19940
|
const skills = ["verity-setup", "verity-analyze", "verity-status", "verity-feedback", "verity-learn", "verity-memory", "verity-insights", "verity-reflect"];
|
|
19329
19941
|
let skillsInstalled = 0;
|
|
19330
19942
|
for (const skill of skills) {
|
|
19331
|
-
const src = (0,
|
|
19332
|
-
const dest = (0,
|
|
19943
|
+
const src = (0, import_node_path23.join)(skillsSource, skill);
|
|
19944
|
+
const dest = (0, import_node_path23.join)(skillsDest, skill);
|
|
19333
19945
|
if (!(0, import_node_fs29.existsSync)(src)) {
|
|
19334
19946
|
printWarn(` Skill data not found: ${skill}`);
|
|
19335
19947
|
continue;
|
|
19336
19948
|
}
|
|
19337
19949
|
if ((0, import_node_fs29.existsSync)(dest) && !force) {
|
|
19338
|
-
const srcSkill = (0,
|
|
19339
|
-
const destSkill = (0,
|
|
19950
|
+
const srcSkill = (0, import_node_path23.join)(src, "SKILL.md");
|
|
19951
|
+
const destSkill = (0, import_node_path23.join)(dest, "SKILL.md");
|
|
19340
19952
|
if ((0, import_node_fs29.existsSync)(destSkill)) {
|
|
19341
19953
|
try {
|
|
19342
19954
|
const srcContent = await (0, import_promises13.readFile)(srcSkill, "utf-8");
|
|
@@ -19374,7 +19986,7 @@ function registerInitCommand(program2) {
|
|
|
19374
19986
|
} catch (err) {
|
|
19375
19987
|
printWarn(` Could not update CLAUDE.md: ${err.message}`);
|
|
19376
19988
|
}
|
|
19377
|
-
const globalVerityDir = (0,
|
|
19989
|
+
const globalVerityDir = (0, import_node_path23.join)(process.env.HOME ?? "", ".verity");
|
|
19378
19990
|
await (0, import_promises13.mkdir)(globalVerityDir, { recursive: true });
|
|
19379
19991
|
console.log("");
|
|
19380
19992
|
try {
|
|
@@ -19403,14 +20015,14 @@ function registerInitCommand(program2) {
|
|
|
19403
20015
|
console.log(" .verity/memory/ \u2014 knowledge base (8 domains, commit to git)");
|
|
19404
20016
|
console.log("");
|
|
19405
20017
|
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
|
|
20018
|
+
console.log(' (Not authenticated? Verity runs in local-only mode until you run "verity login".)');
|
|
19407
20019
|
console.log("");
|
|
19408
20020
|
});
|
|
19409
20021
|
}
|
|
19410
20022
|
|
|
19411
20023
|
// src/commands/uninstall.ts
|
|
19412
20024
|
var import_node_fs30 = require("node:fs");
|
|
19413
|
-
var
|
|
20025
|
+
var import_node_path24 = require("node:path");
|
|
19414
20026
|
var SKILL_NAMES = [
|
|
19415
20027
|
"verity-setup",
|
|
19416
20028
|
"verity-analyze",
|
|
@@ -19429,7 +20041,7 @@ function registerUninstallCommand(program2) {
|
|
|
19429
20041
|
const actions = [];
|
|
19430
20042
|
const skillsRoot = projectPath(".claude/skills");
|
|
19431
20043
|
for (const name of SKILL_NAMES) {
|
|
19432
|
-
const dir = (0,
|
|
20044
|
+
const dir = (0, import_node_path24.join)(skillsRoot, name);
|
|
19433
20045
|
if ((0, import_node_fs30.existsSync)(dir)) {
|
|
19434
20046
|
actions.push({
|
|
19435
20047
|
label: `Remove .claude/skills/${name}/`,
|
|
@@ -19475,7 +20087,7 @@ function registerUninstallCommand(program2) {
|
|
|
19475
20087
|
}
|
|
19476
20088
|
});
|
|
19477
20089
|
const home = process.env.HOME ?? "";
|
|
19478
|
-
const globalVerityDir = (0,
|
|
20090
|
+
const globalVerityDir = (0, import_node_path24.join)(home, ".verity");
|
|
19479
20091
|
if (purgeGlobal && (0, import_node_fs30.existsSync)(globalVerityDir)) {
|
|
19480
20092
|
actions.push({
|
|
19481
20093
|
label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
|
|
@@ -19674,7 +20286,7 @@ function registerTaskCommands(program2) {
|
|
|
19674
20286
|
|
|
19675
20287
|
// src/commands/reset.ts
|
|
19676
20288
|
var import_node_fs31 = require("node:fs");
|
|
19677
|
-
var
|
|
20289
|
+
var import_node_path25 = require("node:path");
|
|
19678
20290
|
function registerResetCommand(program2) {
|
|
19679
20291
|
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
20292
|
const globals = program2.opts();
|
|
@@ -19715,7 +20327,7 @@ function registerResetCommand(program2) {
|
|
|
19715
20327
|
for (const entry of (0, import_node_fs31.readdirSync)(cacheDir)) {
|
|
19716
20328
|
if (entry.startsWith("pending-")) {
|
|
19717
20329
|
try {
|
|
19718
|
-
(0, import_node_fs31.unlinkSync)((0,
|
|
20330
|
+
(0, import_node_fs31.unlinkSync)((0, import_node_path25.join)(cacheDir, entry));
|
|
19719
20331
|
purged++;
|
|
19720
20332
|
} catch {
|
|
19721
20333
|
}
|
|
@@ -19742,7 +20354,7 @@ function registerResetCommand(program2) {
|
|
|
19742
20354
|
if ((0, import_node_fs31.existsSync)(logsDir)) {
|
|
19743
20355
|
for (const entry of (0, import_node_fs31.readdirSync)(logsDir)) {
|
|
19744
20356
|
try {
|
|
19745
|
-
(0, import_node_fs31.unlinkSync)((0,
|
|
20357
|
+
(0, import_node_fs31.unlinkSync)((0, import_node_path25.join)(logsDir, entry));
|
|
19746
20358
|
} catch {
|
|
19747
20359
|
}
|
|
19748
20360
|
}
|
|
@@ -20010,7 +20622,11 @@ function registerTelemetryCommands(program2) {
|
|
|
20010
20622
|
const globals = program2.opts();
|
|
20011
20623
|
const tokenResult = await resolveToken(globals.token);
|
|
20012
20624
|
if (tokenResult.ok) {
|
|
20013
|
-
|
|
20625
|
+
const remote = requestRemote();
|
|
20626
|
+
printJsonCompact({
|
|
20627
|
+
Authorization: `Bearer ${tokenResult.data.token}`,
|
|
20628
|
+
...remote ? { "X-Verity-Remote": remote } : {}
|
|
20629
|
+
});
|
|
20014
20630
|
} else {
|
|
20015
20631
|
printJsonCompact({});
|
|
20016
20632
|
}
|
|
@@ -20040,7 +20656,7 @@ function registerTelemetryCommands(program2) {
|
|
|
20040
20656
|
}
|
|
20041
20657
|
|
|
20042
20658
|
// src/cli.ts
|
|
20043
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.28.1-experimental.
|
|
20659
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.28.1-experimental.af3c52d").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
20660
|
try {
|
|
20045
20661
|
await foldLegacyLocalCredential();
|
|
20046
20662
|
} catch {
|
|
@@ -20048,6 +20664,9 @@ program.name("verity").description("CLI for Verity quality gate service").versio
|
|
|
20048
20664
|
});
|
|
20049
20665
|
registerAuthCommands(program);
|
|
20050
20666
|
registerLoginCommand(program);
|
|
20667
|
+
registerTokenCommand(program);
|
|
20668
|
+
registerSessionsCommands(program);
|
|
20669
|
+
registerLogoutCommand(program);
|
|
20051
20670
|
registerHooksCommands(program);
|
|
20052
20671
|
registerIntentCommands(program);
|
|
20053
20672
|
registerLifecycleCommands(program);
|