@codacy/verity-cli 0.28.1-experimental.a57c8d9 → 0.28.1-experimental.dbd87b1
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 +6 -1
- package/bin/verity.js +1051 -736
- 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,37 @@ 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
|
+
}
|
|
10808
10710
|
function parseLocalCredentialFile(content) {
|
|
10809
10711
|
const tokenMatch = content.match(/token:\s*((?:gate_|verity_)[a-f0-9]+)/);
|
|
10810
10712
|
if (!tokenMatch) return null;
|
|
@@ -10857,457 +10759,623 @@ async function foldLegacyLocalCredential(remoteArg) {
|
|
|
10857
10759
|
return true;
|
|
10858
10760
|
}
|
|
10859
10761
|
|
|
10860
|
-
// src/lib/
|
|
10861
|
-
|
|
10862
|
-
|
|
10863
|
-
|
|
10762
|
+
// src/lib/git.ts
|
|
10763
|
+
var import_node_child_process3 = require("node:child_process");
|
|
10764
|
+
var import_node_fs3 = require("node:fs");
|
|
10765
|
+
var import_node_path3 = require("node:path");
|
|
10766
|
+
function resolveFile(relpath) {
|
|
10767
|
+
return (0, import_node_fs3.existsSync)(relpath) ? relpath : null;
|
|
10864
10768
|
}
|
|
10865
|
-
|
|
10769
|
+
function execGit(cmd) {
|
|
10866
10770
|
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
|
-
}
|
|
10771
|
+
return (0, import_node_child_process3.execSync)(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
10878
10772
|
} catch {
|
|
10773
|
+
return "";
|
|
10879
10774
|
}
|
|
10880
|
-
return null;
|
|
10881
10775
|
}
|
|
10882
|
-
|
|
10883
|
-
|
|
10884
|
-
|
|
10776
|
+
function splitLines(s) {
|
|
10777
|
+
return s.split("\n").filter((l) => l.length > 0);
|
|
10778
|
+
}
|
|
10779
|
+
var SHA_RE = /^[0-9a-f]{40}$/;
|
|
10780
|
+
function readBaselineSha() {
|
|
10781
|
+
if (!(0, import_node_fs3.existsSync)(BASELINE_SHA_FILE)) return null;
|
|
10782
|
+
let sha;
|
|
10783
|
+
try {
|
|
10784
|
+
sha = (0, import_node_fs3.readFileSync)(BASELINE_SHA_FILE, "utf-8").trim();
|
|
10785
|
+
} catch {
|
|
10786
|
+
return null;
|
|
10885
10787
|
}
|
|
10886
|
-
|
|
10887
|
-
|
|
10888
|
-
|
|
10788
|
+
if (!SHA_RE.test(sha)) return null;
|
|
10789
|
+
const reachable = execGit(`git cat-file -e ${sha}^{commit} 2>/dev/null && echo ok`) === "ok";
|
|
10790
|
+
if (!reachable) {
|
|
10791
|
+
try {
|
|
10792
|
+
(0, import_node_fs3.unlinkSync)(BASELINE_SHA_FILE);
|
|
10793
|
+
} catch {
|
|
10794
|
+
}
|
|
10795
|
+
return null;
|
|
10889
10796
|
}
|
|
10890
|
-
|
|
10891
|
-
|
|
10892
|
-
|
|
10797
|
+
return sha;
|
|
10798
|
+
}
|
|
10799
|
+
function writeBaselineSha(sha) {
|
|
10800
|
+
if (!SHA_RE.test(sha)) return;
|
|
10801
|
+
try {
|
|
10802
|
+
(0, import_node_fs3.mkdirSync)((0, import_node_path3.dirname)(BASELINE_SHA_FILE), { recursive: true });
|
|
10803
|
+
(0, import_node_fs3.writeFileSync)(BASELINE_SHA_FILE, sha);
|
|
10804
|
+
} catch {
|
|
10893
10805
|
}
|
|
10894
|
-
|
|
10895
|
-
|
|
10896
|
-
|
|
10806
|
+
}
|
|
10807
|
+
function getChangedFiles() {
|
|
10808
|
+
const sets = /* @__PURE__ */ new Set();
|
|
10809
|
+
let hasRecentCommitFiles = false;
|
|
10810
|
+
for (const f of splitLines(execGit("git diff --name-only HEAD"))) sets.add(f);
|
|
10811
|
+
for (const f of splitLines(execGit("git diff --name-only --cached"))) sets.add(f);
|
|
10812
|
+
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) sets.add(f);
|
|
10813
|
+
const baseline = readBaselineSha();
|
|
10814
|
+
if (baseline) {
|
|
10815
|
+
const committed = splitLines(execGit(`git diff --name-only ${baseline}..HEAD`));
|
|
10816
|
+
if (committed.length > 0) {
|
|
10817
|
+
hasRecentCommitFiles = true;
|
|
10818
|
+
for (const f of committed) sets.add(f);
|
|
10819
|
+
}
|
|
10820
|
+
} else {
|
|
10821
|
+
const headTimestamp = parseInt(execGit("git log -1 --format=%ct HEAD"), 10) || 0;
|
|
10822
|
+
const commitAge = Math.floor(Date.now() / 1e3) - headTimestamp;
|
|
10823
|
+
const hasUnstaged = splitLines(execGit("git diff --name-only HEAD")).length > 0;
|
|
10824
|
+
const hasStaged = splitLines(execGit("git diff --name-only --cached")).length > 0;
|
|
10825
|
+
if (commitAge < 120 && !hasUnstaged && !hasStaged) {
|
|
10826
|
+
const recentFiles = splitLines(execGit("git diff --name-only HEAD~1..HEAD"));
|
|
10827
|
+
if (recentFiles.length > 0) {
|
|
10828
|
+
hasRecentCommitFiles = true;
|
|
10829
|
+
for (const f of recentFiles) sets.add(f);
|
|
10830
|
+
}
|
|
10831
|
+
}
|
|
10897
10832
|
}
|
|
10898
|
-
|
|
10833
|
+
const filtered = Array.from(sets).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
10834
|
+
return { files: filtered, hasRecentCommitFiles };
|
|
10899
10835
|
}
|
|
10900
|
-
|
|
10901
|
-
|
|
10902
|
-
return result.ok ? { ok: true, data: result.data.url } : result;
|
|
10836
|
+
function getStagedFiles() {
|
|
10837
|
+
return splitLines(execGit("git diff --cached --name-only")).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
10903
10838
|
}
|
|
10904
|
-
function
|
|
10905
|
-
|
|
10839
|
+
function getDirtyFiles() {
|
|
10840
|
+
const set = /* @__PURE__ */ new Set();
|
|
10841
|
+
for (const f of splitLines(execGit("git diff --name-only HEAD"))) set.add(f);
|
|
10842
|
+
for (const f of splitLines(execGit("git diff --name-only --cached"))) set.add(f);
|
|
10843
|
+
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
|
|
10844
|
+
return Array.from(set).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
10906
10845
|
}
|
|
10907
|
-
|
|
10908
|
-
|
|
10909
|
-
|
|
10910
|
-
|
|
10911
|
-
return
|
|
10912
|
-
|
|
10913
|
-
|
|
10914
|
-
|
|
10915
|
-
|
|
10846
|
+
function showContentAtRef(ref, repoRelPath) {
|
|
10847
|
+
if (!ref || ref === "no-git") return null;
|
|
10848
|
+
const normalizedPath = repoRelPath.replace(/\\/g, "/");
|
|
10849
|
+
try {
|
|
10850
|
+
return (0, import_node_child_process3.execFileSync)("git", ["show", `${ref}:${normalizedPath}`], {
|
|
10851
|
+
encoding: "utf-8",
|
|
10852
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
10853
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
10854
|
+
});
|
|
10855
|
+
} catch {
|
|
10856
|
+
return null;
|
|
10916
10857
|
}
|
|
10917
|
-
|
|
10918
|
-
|
|
10919
|
-
|
|
10920
|
-
|
|
10921
|
-
|
|
10922
|
-
}
|
|
10858
|
+
}
|
|
10859
|
+
function getPushRangeFiles() {
|
|
10860
|
+
const diff = (range) => splitLines(execGit(`git diff --name-only ${range}`)).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
10861
|
+
const resolvers = [
|
|
10862
|
+
() => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{push}") ? "@{push}..HEAD" : null,
|
|
10863
|
+
() => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{upstream}") ? "@{upstream}..HEAD" : null,
|
|
10864
|
+
() => {
|
|
10865
|
+
const branch = execGit("git rev-parse --abbrev-ref HEAD");
|
|
10866
|
+
return branch && branch !== "HEAD" && execGit(`git rev-parse --verify -q origin/${branch}`) ? `origin/${branch}..HEAD` : null;
|
|
10867
|
+
}
|
|
10868
|
+
];
|
|
10869
|
+
for (const resolve2 of resolvers) {
|
|
10870
|
+
const range = resolve2();
|
|
10871
|
+
if (range) return { files: diff(range), range };
|
|
10923
10872
|
}
|
|
10924
|
-
const
|
|
10925
|
-
if (
|
|
10926
|
-
|
|
10927
|
-
|
|
10928
|
-
data: { token: local.token, source: "local", userId: local.userId, email: local.email }
|
|
10929
|
-
};
|
|
10873
|
+
const baseline = readBaselineSha();
|
|
10874
|
+
if (baseline) {
|
|
10875
|
+
const files = diff(`${baseline}..HEAD`);
|
|
10876
|
+
if (files.length > 0) return { files, range: `${baseline}..HEAD` };
|
|
10930
10877
|
}
|
|
10931
|
-
|
|
10878
|
+
const last = diff("HEAD~1..HEAD");
|
|
10879
|
+
return { files: last, range: last.length > 0 ? "HEAD~1..HEAD" : null };
|
|
10932
10880
|
}
|
|
10933
|
-
|
|
10934
|
-
|
|
10935
|
-
|
|
10936
|
-
|
|
10937
|
-
|
|
10938
|
-
|
|
10939
|
-
|
|
10940
|
-
|
|
10881
|
+
function getPushRangeMessages() {
|
|
10882
|
+
const { range } = getPushRangeFiles();
|
|
10883
|
+
if (!range) return "";
|
|
10884
|
+
return execGit(`git log ${range} --format=%B%x00`).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
|
|
10885
|
+
}
|
|
10886
|
+
function filterAnalyzable(files) {
|
|
10887
|
+
return files.filter((f) => {
|
|
10888
|
+
const ext = (0, import_node_path3.extname)(f).slice(1);
|
|
10889
|
+
return ANALYZABLE_EXTENSIONS.has(ext);
|
|
10941
10890
|
});
|
|
10942
10891
|
}
|
|
10943
|
-
|
|
10944
|
-
|
|
10945
|
-
|
|
10946
|
-
|
|
10947
|
-
|
|
10948
|
-
|
|
10949
|
-
|
|
10950
|
-
|
|
10892
|
+
function filterReviewable(files) {
|
|
10893
|
+
return files.filter((f) => {
|
|
10894
|
+
const ext = (0, import_node_path3.extname)(f).slice(1);
|
|
10895
|
+
if (ANALYZABLE_EXTENSIONS.has(ext)) return false;
|
|
10896
|
+
if (REVIEWABLE_EXTENSIONS.has(ext)) return true;
|
|
10897
|
+
const basename4 = f.split("/").pop() ?? "";
|
|
10898
|
+
if (REVIEWABLE_FILENAMES.has(basename4)) return true;
|
|
10899
|
+
if (REVIEWABLE_PATH_PATTERNS.some((p) => p.test(f))) return true;
|
|
10900
|
+
return false;
|
|
10951
10901
|
});
|
|
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
10902
|
}
|
|
10955
|
-
|
|
10956
|
-
|
|
10957
|
-
|
|
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 };
|
|
10903
|
+
function filterSecurity(files) {
|
|
10904
|
+
return files.filter(
|
|
10905
|
+
(f) => SECURITY_PATTERNS.some((p) => p.test(f))
|
|
10906
|
+
);
|
|
10977
10907
|
}
|
|
10978
|
-
|
|
10979
|
-
|
|
10980
|
-
|
|
10981
|
-
|
|
10982
|
-
|
|
10983
|
-
|
|
10984
|
-
|
|
10985
|
-
|
|
10908
|
+
function getCurrentCommit() {
|
|
10909
|
+
return execGit("git rev-parse HEAD") || "no-git";
|
|
10910
|
+
}
|
|
10911
|
+
function getCurrentBranch() {
|
|
10912
|
+
const b = execGit("git rev-parse --abbrev-ref HEAD");
|
|
10913
|
+
return !b || b === "HEAD" ? null : b;
|
|
10914
|
+
}
|
|
10915
|
+
function commitResolves(sha) {
|
|
10916
|
+
if (!sha) return false;
|
|
10986
10917
|
try {
|
|
10987
|
-
|
|
10988
|
-
|
|
10989
|
-
Accept: "application/vnd.github+json",
|
|
10990
|
-
"X-GitHub-Api-Version": "2022-11-28",
|
|
10991
|
-
"User-Agent": "verity-cli"
|
|
10992
|
-
}
|
|
10918
|
+
(0, import_node_child_process3.execSync)(`git merge-base --is-ancestor ${JSON.stringify(sha)} HEAD`, {
|
|
10919
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
10993
10920
|
});
|
|
10994
|
-
|
|
10995
|
-
const body = await res.json();
|
|
10996
|
-
return typeof body.id === "number" ? body.id : null;
|
|
10921
|
+
return true;
|
|
10997
10922
|
} catch {
|
|
10998
|
-
return
|
|
10923
|
+
return false;
|
|
10999
10924
|
}
|
|
11000
10925
|
}
|
|
11001
|
-
|
|
11002
|
-
|
|
10926
|
+
function commitsSincePaths(sha, paths) {
|
|
10927
|
+
if (!sha) return null;
|
|
11003
10928
|
try {
|
|
11004
|
-
|
|
11005
|
-
|
|
11006
|
-
|
|
11007
|
-
|
|
11008
|
-
|
|
11009
|
-
|
|
11010
|
-
|
|
11011
|
-
|
|
11012
|
-
|
|
11013
|
-
}
|
|
11014
|
-
);
|
|
11015
|
-
} catch (err) {
|
|
11016
|
-
return { ok: false, error: `Network error contacting GitHub: ${err.message}` };
|
|
10929
|
+
const scope = paths.length > 0 ? ` -- ${paths.slice(0, 50).map((p) => JSON.stringify(p)).join(" ")}` : "";
|
|
10930
|
+
const out = (0, import_node_child_process3.execSync)(`git rev-list --count ${JSON.stringify(sha)}..HEAD${scope}`, {
|
|
10931
|
+
encoding: "utf-8",
|
|
10932
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
10933
|
+
}).trim();
|
|
10934
|
+
const n = Number.parseInt(out, 10);
|
|
10935
|
+
return Number.isFinite(n) ? n : null;
|
|
10936
|
+
} catch {
|
|
10937
|
+
return null;
|
|
11017
10938
|
}
|
|
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
10939
|
}
|
|
11022
|
-
|
|
11023
|
-
const
|
|
11024
|
-
if (
|
|
11025
|
-
|
|
11026
|
-
|
|
11027
|
-
|
|
11028
|
-
|
|
11029
|
-
|
|
11030
|
-
|
|
11031
|
-
|
|
11032
|
-
|
|
11033
|
-
|
|
11034
|
-
|
|
11035
|
-
|
|
11036
|
-
|
|
11037
|
-
|
|
11038
|
-
|
|
11039
|
-
|
|
11040
|
-
|
|
11041
|
-
|
|
11042
|
-
|
|
11043
|
-
|
|
11044
|
-
|
|
11045
|
-
|
|
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
|
-
}
|
|
10940
|
+
function detectProvider(host) {
|
|
10941
|
+
const h = host.toLowerCase();
|
|
10942
|
+
if (h.includes("github")) return "github";
|
|
10943
|
+
if (h.includes("gitlab")) return "gitlab";
|
|
10944
|
+
if (h.includes("bitbucket")) return "bitbucket";
|
|
10945
|
+
return "unknown";
|
|
10946
|
+
}
|
|
10947
|
+
function parseRemote(raw) {
|
|
10948
|
+
if (!raw || typeof raw !== "string") return null;
|
|
10949
|
+
let s = raw.trim();
|
|
10950
|
+
if (!s) return null;
|
|
10951
|
+
let host = "";
|
|
10952
|
+
let path = "";
|
|
10953
|
+
const scp = s.match(/^[^/@]+@([^:/]+):(.+)$/);
|
|
10954
|
+
if (scp) {
|
|
10955
|
+
host = scp[1];
|
|
10956
|
+
path = scp[2];
|
|
10957
|
+
} else {
|
|
10958
|
+
s = s.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//, "");
|
|
10959
|
+
s = s.replace(/^[^/@]+@/, "");
|
|
10960
|
+
const slash = s.indexOf("/");
|
|
10961
|
+
if (slash === -1) return null;
|
|
10962
|
+
host = s.slice(0, slash);
|
|
10963
|
+
path = s.slice(slash + 1);
|
|
11082
10964
|
}
|
|
11083
|
-
|
|
10965
|
+
host = host.toLowerCase().trim();
|
|
10966
|
+
path = path.replace(/\/+$/, "").replace(/\.git$/, "");
|
|
10967
|
+
if (!host || !path) return null;
|
|
10968
|
+
const segments = path.split("/").filter(Boolean);
|
|
10969
|
+
if (segments.length < 2) return null;
|
|
10970
|
+
const owner = segments[0];
|
|
10971
|
+
const repo = segments[segments.length - 1];
|
|
10972
|
+
if (!owner || !repo) return null;
|
|
10973
|
+
return {
|
|
10974
|
+
host,
|
|
10975
|
+
owner,
|
|
10976
|
+
repo,
|
|
10977
|
+
provider: detectProvider(host),
|
|
10978
|
+
orgUrl: `https://${host}/${owner}`,
|
|
10979
|
+
orgName: owner
|
|
10980
|
+
};
|
|
11084
10981
|
}
|
|
11085
|
-
|
|
11086
|
-
|
|
11087
|
-
|
|
11088
|
-
|
|
11089
|
-
|
|
11090
|
-
function resolveFile(relpath) {
|
|
11091
|
-
return (0, import_node_fs3.existsSync)(relpath) ? relpath : null;
|
|
10982
|
+
function listTrackedFiles() {
|
|
10983
|
+
const set = /* @__PURE__ */ new Set();
|
|
10984
|
+
for (const f of splitLines(execGit("git ls-files"))) set.add(f);
|
|
10985
|
+
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
|
|
10986
|
+
return Array.from(set);
|
|
11092
10987
|
}
|
|
11093
|
-
function
|
|
11094
|
-
|
|
11095
|
-
return (0, import_node_child_process3.execSync)(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
11096
|
-
} catch {
|
|
11097
|
-
return "";
|
|
11098
|
-
}
|
|
10988
|
+
function sanitizeRemote(remote) {
|
|
10989
|
+
return remote.replace(/^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)[^/]*@/, "$1");
|
|
11099
10990
|
}
|
|
11100
|
-
|
|
11101
|
-
|
|
10991
|
+
|
|
10992
|
+
// src/lib/api-client.ts
|
|
10993
|
+
var cachedRemote = null;
|
|
10994
|
+
function requestRemote() {
|
|
10995
|
+
const override = process.env.VERITY_REMOTE_OVERRIDE;
|
|
10996
|
+
if (override) return sanitizeRemote(override.trim());
|
|
10997
|
+
if (cachedRemote === null) cachedRemote = sanitizeRemote(currentRemote());
|
|
10998
|
+
return cachedRemote;
|
|
11102
10999
|
}
|
|
11103
|
-
|
|
11104
|
-
|
|
11105
|
-
|
|
11106
|
-
|
|
11107
|
-
|
|
11108
|
-
|
|
11109
|
-
|
|
11110
|
-
|
|
11000
|
+
function describeFetchError(err, url) {
|
|
11001
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11002
|
+
const cause = err?.cause;
|
|
11003
|
+
const causeBits = [cause?.code, cause?.hostname].filter(Boolean).join(" ");
|
|
11004
|
+
const detail = causeBits || (cause?.message && cause.message !== message ? cause.message : "");
|
|
11005
|
+
return `${message}${detail ? ` (${detail})` : ""} \u2014 could not reach ${url}`;
|
|
11006
|
+
}
|
|
11007
|
+
async function apiRequest(options) {
|
|
11008
|
+
const {
|
|
11009
|
+
method,
|
|
11010
|
+
path,
|
|
11011
|
+
serviceUrl,
|
|
11012
|
+
token,
|
|
11013
|
+
body,
|
|
11014
|
+
verbose,
|
|
11015
|
+
timeout = 9e4,
|
|
11016
|
+
cmd = "unknown",
|
|
11017
|
+
retry = false,
|
|
11018
|
+
encodeBody = false,
|
|
11019
|
+
extraHeaders
|
|
11020
|
+
} = options;
|
|
11021
|
+
const url = `${serviceUrl}${path}`;
|
|
11022
|
+
const headers = {
|
|
11023
|
+
"Content-Type": "application/json"
|
|
11024
|
+
};
|
|
11025
|
+
if (token) {
|
|
11026
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
11111
11027
|
}
|
|
11112
|
-
|
|
11113
|
-
|
|
11114
|
-
|
|
11115
|
-
|
|
11116
|
-
|
|
11117
|
-
|
|
11028
|
+
const remote = requestRemote();
|
|
11029
|
+
if (remote) {
|
|
11030
|
+
headers["X-Verity-Remote"] = remote;
|
|
11031
|
+
}
|
|
11032
|
+
if (extraHeaders) {
|
|
11033
|
+
Object.assign(headers, extraHeaders);
|
|
11034
|
+
}
|
|
11035
|
+
const testMockScenario = process.env.VERITY_TEST_MOCK_SCENARIO;
|
|
11036
|
+
if (testMockScenario) {
|
|
11037
|
+
headers["X-Verity-Mock-Scenario"] = testMockScenario;
|
|
11038
|
+
}
|
|
11039
|
+
const testMockFailure = process.env.VERITY_TEST_MOCK_FAILURE;
|
|
11040
|
+
if (testMockFailure) {
|
|
11041
|
+
headers["X-Verity-Mock-Failure"] = testMockFailure;
|
|
11042
|
+
}
|
|
11043
|
+
printVerbose(`${method} ${url}`, verbose);
|
|
11044
|
+
let serializedBody;
|
|
11045
|
+
if (body !== void 0 && body !== null) {
|
|
11046
|
+
const innerJson = JSON.stringify(body);
|
|
11047
|
+
if (encodeBody) {
|
|
11048
|
+
const payload = Buffer.from(innerJson, "utf8").toString("base64");
|
|
11049
|
+
serializedBody = JSON.stringify({ encoding: "base64", payload });
|
|
11050
|
+
} else {
|
|
11051
|
+
serializedBody = innerJson;
|
|
11118
11052
|
}
|
|
11119
|
-
|
|
11053
|
+
printVerbose(`Body: ${serializedBody.slice(0, 500)}`, verbose);
|
|
11120
11054
|
}
|
|
11121
|
-
|
|
11122
|
-
|
|
11123
|
-
|
|
11124
|
-
|
|
11055
|
+
const startedAt = Date.now();
|
|
11056
|
+
const bodyBytes = serializedBody ? Buffer.byteLength(serializedBody) : 0;
|
|
11057
|
+
const logBase = { cmd, method, url, body_bytes: bodyBytes, retry, encoded: encodeBody };
|
|
11058
|
+
let response;
|
|
11125
11059
|
try {
|
|
11126
|
-
|
|
11127
|
-
|
|
11060
|
+
response = await fetch(url, {
|
|
11061
|
+
method,
|
|
11062
|
+
headers,
|
|
11063
|
+
body: serializedBody,
|
|
11064
|
+
signal: AbortSignal.timeout(timeout)
|
|
11065
|
+
});
|
|
11066
|
+
} catch (err) {
|
|
11067
|
+
const duration2 = Date.now() - startedAt;
|
|
11068
|
+
const isTimeout = err instanceof DOMException && err.name === "TimeoutError";
|
|
11069
|
+
const category = isTimeout ? "timeout" : "network";
|
|
11070
|
+
const error = isTimeout ? `Request timed out after ${timeout}ms (${url})` : `Network error: ${describeFetchError(err, url)}`;
|
|
11071
|
+
logHttpCall({ ...logBase, duration_ms: duration2, http_status: null, category, error });
|
|
11072
|
+
return { ok: false, error, category, http_status: null };
|
|
11073
|
+
}
|
|
11074
|
+
let data;
|
|
11075
|
+
try {
|
|
11076
|
+
data = response.status === 204 || response.headers.get("content-length") === "0" ? {} : await response.json();
|
|
11128
11077
|
} catch {
|
|
11078
|
+
const duration2 = Date.now() - startedAt;
|
|
11079
|
+
const error = `Invalid JSON response (HTTP ${response.status})`;
|
|
11080
|
+
logHttpCall({
|
|
11081
|
+
...logBase,
|
|
11082
|
+
duration_ms: duration2,
|
|
11083
|
+
http_status: response.status,
|
|
11084
|
+
category: "invalid_json",
|
|
11085
|
+
error
|
|
11086
|
+
});
|
|
11087
|
+
return { ok: false, error, category: "invalid_json", http_status: response.status };
|
|
11129
11088
|
}
|
|
11130
|
-
}
|
|
11131
|
-
|
|
11132
|
-
|
|
11133
|
-
|
|
11134
|
-
|
|
11135
|
-
|
|
11136
|
-
|
|
11137
|
-
|
|
11138
|
-
|
|
11139
|
-
|
|
11140
|
-
|
|
11141
|
-
|
|
11142
|
-
|
|
11143
|
-
|
|
11144
|
-
|
|
11145
|
-
|
|
11146
|
-
const commitAge = Math.floor(Date.now() / 1e3) - headTimestamp;
|
|
11147
|
-
const hasUnstaged = splitLines(execGit("git diff --name-only HEAD")).length > 0;
|
|
11148
|
-
const hasStaged = splitLines(execGit("git diff --name-only --cached")).length > 0;
|
|
11149
|
-
if (commitAge < 120 && !hasUnstaged && !hasStaged) {
|
|
11150
|
-
const recentFiles = splitLines(execGit("git diff --name-only HEAD~1..HEAD"));
|
|
11151
|
-
if (recentFiles.length > 0) {
|
|
11152
|
-
hasRecentCommitFiles = true;
|
|
11153
|
-
for (const f of recentFiles) sets.add(f);
|
|
11154
|
-
}
|
|
11155
|
-
}
|
|
11089
|
+
printVerbose(`Response ${response.status}: ${JSON.stringify(data).slice(0, 500)}`, verbose);
|
|
11090
|
+
const duration = Date.now() - startedAt;
|
|
11091
|
+
if (!response.ok) {
|
|
11092
|
+
const apiErr = data;
|
|
11093
|
+
const code = apiErr?.error?.code ?? "UNKNOWN";
|
|
11094
|
+
const message = apiErr?.error?.message ?? `HTTP ${response.status}`;
|
|
11095
|
+
const category = response.status >= 500 ? "http_5xx" : "http_4xx";
|
|
11096
|
+
const error = `${code}: ${message}`;
|
|
11097
|
+
logHttpCall({
|
|
11098
|
+
...logBase,
|
|
11099
|
+
duration_ms: duration,
|
|
11100
|
+
http_status: response.status,
|
|
11101
|
+
category,
|
|
11102
|
+
error
|
|
11103
|
+
});
|
|
11104
|
+
return { ok: false, error, category, http_status: response.status };
|
|
11156
11105
|
}
|
|
11157
|
-
|
|
11158
|
-
|
|
11106
|
+
logHttpCall({
|
|
11107
|
+
...logBase,
|
|
11108
|
+
duration_ms: duration,
|
|
11109
|
+
http_status: response.status,
|
|
11110
|
+
category: "ok"
|
|
11111
|
+
});
|
|
11112
|
+
return { ok: true, data };
|
|
11159
11113
|
}
|
|
11160
|
-
function
|
|
11161
|
-
return
|
|
11114
|
+
function analyzeRequest(options) {
|
|
11115
|
+
return apiRequest({
|
|
11116
|
+
method: "POST",
|
|
11117
|
+
path: "/analyze",
|
|
11118
|
+
serviceUrl: options.serviceUrl,
|
|
11119
|
+
token: options.token,
|
|
11120
|
+
body: options.body,
|
|
11121
|
+
timeout: options.timeout,
|
|
11122
|
+
cmd: options.cmd,
|
|
11123
|
+
verbose: options.verbose,
|
|
11124
|
+
retry: options.retry,
|
|
11125
|
+
encodeBody: true
|
|
11126
|
+
});
|
|
11162
11127
|
}
|
|
11163
|
-
|
|
11164
|
-
|
|
11165
|
-
|
|
11166
|
-
|
|
11167
|
-
|
|
11168
|
-
return
|
|
11128
|
+
|
|
11129
|
+
// src/lib/service-url.ts
|
|
11130
|
+
var import_promises2 = require("node:fs/promises");
|
|
11131
|
+
async function serviceUrlFromCredentials() {
|
|
11132
|
+
const rec = await readGlobalCredential(currentRemote());
|
|
11133
|
+
return rec?.serviceUrl ?? null;
|
|
11169
11134
|
}
|
|
11170
|
-
function
|
|
11171
|
-
if (!ref || ref === "no-git") return null;
|
|
11172
|
-
const normalizedPath = repoRelPath.replace(/\\/g, "/");
|
|
11135
|
+
async function serviceUrlFromVerityMd() {
|
|
11173
11136
|
try {
|
|
11174
|
-
|
|
11175
|
-
|
|
11176
|
-
|
|
11177
|
-
|
|
11178
|
-
|
|
11137
|
+
const content = await (0, import_promises2.readFile)(projectPath(VERITY_MD_FILE), "utf-8");
|
|
11138
|
+
const boldLine = content.split("\n").find((l) => /\*\*url\*\*/i.test(l));
|
|
11139
|
+
if (boldLine) {
|
|
11140
|
+
const urlMatch = boldLine.match(/https:\/\/[^\s]+/);
|
|
11141
|
+
if (urlMatch) return urlMatch[0];
|
|
11142
|
+
}
|
|
11143
|
+
const plainLine = content.split("\n").find((l) => /(?:url|service)\s*:/i.test(l));
|
|
11144
|
+
if (plainLine) {
|
|
11145
|
+
const urlMatch = plainLine.match(/https:\/\/[^\s]+/);
|
|
11146
|
+
if (urlMatch) return urlMatch[0];
|
|
11147
|
+
}
|
|
11179
11148
|
} catch {
|
|
11180
|
-
return null;
|
|
11181
11149
|
}
|
|
11150
|
+
return null;
|
|
11182
11151
|
}
|
|
11183
|
-
function
|
|
11184
|
-
|
|
11185
|
-
|
|
11186
|
-
() => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{push}") ? "@{push}..HEAD" : null,
|
|
11187
|
-
() => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{upstream}") ? "@{upstream}..HEAD" : null,
|
|
11188
|
-
() => {
|
|
11189
|
-
const branch = execGit("git rev-parse --abbrev-ref HEAD");
|
|
11190
|
-
return branch && branch !== "HEAD" && execGit(`git rev-parse --verify -q origin/${branch}`) ? `origin/${branch}..HEAD` : null;
|
|
11191
|
-
}
|
|
11192
|
-
];
|
|
11193
|
-
for (const resolve2 of resolvers) {
|
|
11194
|
-
const range = resolve2();
|
|
11195
|
-
if (range) return { files: diff(range), range };
|
|
11152
|
+
async function resolveServiceUrlDetailed(flagUrl) {
|
|
11153
|
+
if (flagUrl) {
|
|
11154
|
+
return { ok: true, data: { url: flagUrl, source: "flag" } };
|
|
11196
11155
|
}
|
|
11197
|
-
const
|
|
11198
|
-
if (
|
|
11199
|
-
|
|
11200
|
-
if (files.length > 0) return { files, range: `${baseline}..HEAD` };
|
|
11156
|
+
const envUrl = process.env.VERITY_SERVICE_URL;
|
|
11157
|
+
if (envUrl) {
|
|
11158
|
+
return { ok: true, data: { url: envUrl, source: "env" } };
|
|
11201
11159
|
}
|
|
11202
|
-
const
|
|
11203
|
-
|
|
11160
|
+
const credsUrl = await serviceUrlFromCredentials();
|
|
11161
|
+
if (credsUrl) {
|
|
11162
|
+
return { ok: true, data: { url: credsUrl, source: "credentials" } };
|
|
11163
|
+
}
|
|
11164
|
+
const mdUrl = await serviceUrlFromVerityMd();
|
|
11165
|
+
if (mdUrl) {
|
|
11166
|
+
return { ok: true, data: { url: mdUrl, source: "verity_md" } };
|
|
11167
|
+
}
|
|
11168
|
+
return { ok: false, error: "No Verity service URL found. Run /verity-setup to configure." };
|
|
11204
11169
|
}
|
|
11205
|
-
function
|
|
11206
|
-
const
|
|
11207
|
-
|
|
11208
|
-
return execGit(`git log ${range} --format=%B%x00`).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
|
|
11170
|
+
async function resolveServiceUrl(flagUrl) {
|
|
11171
|
+
const result = await resolveServiceUrlDetailed(flagUrl);
|
|
11172
|
+
return result.ok ? { ok: true, data: result.data.url } : result;
|
|
11209
11173
|
}
|
|
11210
|
-
function
|
|
11211
|
-
return
|
|
11212
|
-
const ext = (0, import_node_path3.extname)(f).slice(1);
|
|
11213
|
-
return ANALYZABLE_EXTENSIONS.has(ext);
|
|
11214
|
-
});
|
|
11174
|
+
function isHealCandidate(resolved) {
|
|
11175
|
+
return (resolved.source === "credentials" || resolved.source === "verity_md") && resolved.url !== DEFAULT_SERVICE_URL;
|
|
11215
11176
|
}
|
|
11216
|
-
|
|
11217
|
-
|
|
11218
|
-
|
|
11219
|
-
|
|
11220
|
-
|
|
11221
|
-
|
|
11222
|
-
|
|
11223
|
-
|
|
11224
|
-
return
|
|
11177
|
+
|
|
11178
|
+
// src/lib/auth.ts
|
|
11179
|
+
async function resolveToken(flagToken) {
|
|
11180
|
+
if (flagToken) {
|
|
11181
|
+
return { ok: true, data: { token: flagToken, source: "flag" } };
|
|
11182
|
+
}
|
|
11183
|
+
const envToken = process.env.VERITY_TOKEN;
|
|
11184
|
+
if (envToken) {
|
|
11185
|
+
return { ok: true, data: { token: envToken, source: "env" } };
|
|
11186
|
+
}
|
|
11187
|
+
const rec = await readGlobalCredential(currentRemote());
|
|
11188
|
+
if (rec) {
|
|
11189
|
+
return {
|
|
11190
|
+
ok: true,
|
|
11191
|
+
data: { token: rec.token, source: "global", userId: rec.userId, email: rec.email }
|
|
11192
|
+
};
|
|
11193
|
+
}
|
|
11194
|
+
const local = await readLegacyLocalCredential();
|
|
11195
|
+
if (local) {
|
|
11196
|
+
return {
|
|
11197
|
+
ok: true,
|
|
11198
|
+
data: { token: local.token, source: "local", userId: local.userId, email: local.email }
|
|
11199
|
+
};
|
|
11200
|
+
}
|
|
11201
|
+
return { ok: false, error: "No Verity token found. Run /verity-setup to configure." };
|
|
11202
|
+
}
|
|
11203
|
+
async function whoami(token, serviceUrl, verbose) {
|
|
11204
|
+
return apiRequest({
|
|
11205
|
+
method: "GET",
|
|
11206
|
+
path: "/auth/whoami",
|
|
11207
|
+
serviceUrl,
|
|
11208
|
+
token,
|
|
11209
|
+
verbose,
|
|
11210
|
+
cmd: "whoami"
|
|
11225
11211
|
});
|
|
11226
11212
|
}
|
|
11227
|
-
function
|
|
11228
|
-
|
|
11229
|
-
|
|
11230
|
-
|
|
11213
|
+
function reverifyNudge(who) {
|
|
11214
|
+
if (who.grant_status === "hard_stale") {
|
|
11215
|
+
return 'Your GitHub verification has expired \u2014 run "verity login" to restore saved runs and memory.';
|
|
11216
|
+
}
|
|
11217
|
+
if (who.grant_status === "soft_stale") {
|
|
11218
|
+
const by = who.reverify_by ? ` by ${who.reverify_by.slice(0, 10)}` : " soon";
|
|
11219
|
+
return `Your GitHub verification needs a refresh${by} \u2014 run "verity login" to re-verify.`;
|
|
11220
|
+
}
|
|
11221
|
+
return null;
|
|
11231
11222
|
}
|
|
11232
|
-
function
|
|
11233
|
-
|
|
11223
|
+
function authDenialRemedy(error) {
|
|
11224
|
+
if (error.startsWith("STALE_VERIFICATION")) {
|
|
11225
|
+
return {
|
|
11226
|
+
code: "STALE_VERIFICATION",
|
|
11227
|
+
remedy: 'Your GitHub verification has expired \u2014 run "verity login" to re-verify your repository access.'
|
|
11228
|
+
};
|
|
11229
|
+
}
|
|
11230
|
+
if (error.startsWith("FORBIDDEN")) {
|
|
11231
|
+
return {
|
|
11232
|
+
code: "FORBIDDEN",
|
|
11233
|
+
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.'
|
|
11234
|
+
};
|
|
11235
|
+
}
|
|
11236
|
+
return null;
|
|
11234
11237
|
}
|
|
11235
|
-
function
|
|
11236
|
-
const
|
|
11237
|
-
|
|
11238
|
+
async function probeService(serviceUrl, verbose) {
|
|
11239
|
+
const res = await apiRequest({
|
|
11240
|
+
method: "GET",
|
|
11241
|
+
path: "/auth/whoami",
|
|
11242
|
+
serviceUrl,
|
|
11243
|
+
verbose,
|
|
11244
|
+
timeout: 5e3,
|
|
11245
|
+
cmd: "probe"
|
|
11246
|
+
});
|
|
11247
|
+
if (res.ok || res.http_status != null) return { reachable: true };
|
|
11248
|
+
return { reachable: false, dnsDead: /\bENOTFOUND\b/.test(res.error), error: res.error };
|
|
11238
11249
|
}
|
|
11239
|
-
function
|
|
11240
|
-
if (!
|
|
11241
|
-
|
|
11242
|
-
|
|
11243
|
-
|
|
11244
|
-
|
|
11245
|
-
return
|
|
11246
|
-
}
|
|
11247
|
-
|
|
11250
|
+
async function maybeHealServiceUrl(resolution, verbose) {
|
|
11251
|
+
if (!isHealCandidate(resolution)) {
|
|
11252
|
+
return { serviceUrl: resolution.url, healed: false };
|
|
11253
|
+
}
|
|
11254
|
+
const probe = await probeService(resolution.url, verbose);
|
|
11255
|
+
if (probe.reachable) {
|
|
11256
|
+
return { serviceUrl: resolution.url, healed: false };
|
|
11257
|
+
}
|
|
11258
|
+
const from = resolution.source === "credentials" ? "~/.verity/credentials" : "VERITY.md";
|
|
11259
|
+
printWarn(`Your configured Verity service URL is unreachable: ${resolution.url}`);
|
|
11260
|
+
printInfo(` (${probe.error})`);
|
|
11261
|
+
if (probe.dnsDead && (await probeService(DEFAULT_SERVICE_URL, verbose)).reachable) {
|
|
11262
|
+
printInfo(` The hostname no longer exists \u2014 the URL in ${from} is stale (e.g. a retired preview backend).`);
|
|
11263
|
+
printInfo(` Falling back to the default Verity service: ${DEFAULT_SERVICE_URL}`);
|
|
11264
|
+
if (resolution.source === "verity_md") {
|
|
11265
|
+
printWarn(` Note: VERITY.md still contains the stale URL \u2014 update it to ${DEFAULT_SERVICE_URL} and commit.`);
|
|
11266
|
+
}
|
|
11267
|
+
return { serviceUrl: DEFAULT_SERVICE_URL, healed: true };
|
|
11248
11268
|
}
|
|
11269
|
+
printInfo(" Continuing against the configured URL. If it is stale, log in against the default with:");
|
|
11270
|
+
printInfo(` VERITY_SERVICE_URL=${DEFAULT_SERVICE_URL} verity login`);
|
|
11271
|
+
return { serviceUrl: resolution.url, healed: false };
|
|
11249
11272
|
}
|
|
11250
|
-
|
|
11251
|
-
|
|
11273
|
+
|
|
11274
|
+
// src/lib/register.ts
|
|
11275
|
+
var readline = __toESM(require("node:readline/promises"));
|
|
11276
|
+
|
|
11277
|
+
// src/lib/provider-auth.ts
|
|
11278
|
+
var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
11279
|
+
var form = (fields) => new URLSearchParams(fields).toString();
|
|
11280
|
+
async function githubAccountId(owner) {
|
|
11252
11281
|
try {
|
|
11253
|
-
const
|
|
11254
|
-
|
|
11255
|
-
|
|
11256
|
-
|
|
11257
|
-
|
|
11258
|
-
|
|
11259
|
-
|
|
11282
|
+
const res = await fetch(`https://api.github.com/users/${encodeURIComponent(owner)}`, {
|
|
11283
|
+
headers: {
|
|
11284
|
+
Accept: "application/vnd.github+json",
|
|
11285
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
11286
|
+
"User-Agent": "verity-cli"
|
|
11287
|
+
}
|
|
11288
|
+
});
|
|
11289
|
+
if (!res.ok) return null;
|
|
11290
|
+
const body = await res.json();
|
|
11291
|
+
return typeof body.id === "number" ? body.id : null;
|
|
11260
11292
|
} catch {
|
|
11261
11293
|
return null;
|
|
11262
11294
|
}
|
|
11263
11295
|
}
|
|
11264
|
-
function
|
|
11265
|
-
|
|
11266
|
-
|
|
11267
|
-
|
|
11268
|
-
|
|
11269
|
-
|
|
11270
|
-
|
|
11271
|
-
|
|
11272
|
-
|
|
11273
|
-
|
|
11274
|
-
|
|
11275
|
-
|
|
11276
|
-
|
|
11277
|
-
|
|
11278
|
-
|
|
11279
|
-
|
|
11280
|
-
|
|
11281
|
-
|
|
11282
|
-
|
|
11283
|
-
|
|
11284
|
-
const slash = s.indexOf("/");
|
|
11285
|
-
if (slash === -1) return null;
|
|
11286
|
-
host = s.slice(0, slash);
|
|
11287
|
-
path = s.slice(slash + 1);
|
|
11288
|
-
}
|
|
11289
|
-
host = host.toLowerCase().trim();
|
|
11290
|
-
path = path.replace(/\/+$/, "").replace(/\.git$/, "");
|
|
11291
|
-
if (!host || !path) return null;
|
|
11292
|
-
const segments = path.split("/").filter(Boolean);
|
|
11293
|
-
if (segments.length < 2) return null;
|
|
11294
|
-
const owner = segments[0];
|
|
11295
|
-
const repo = segments[segments.length - 1];
|
|
11296
|
-
if (!owner || !repo) return null;
|
|
11297
|
-
return {
|
|
11298
|
-
host,
|
|
11299
|
-
owner,
|
|
11300
|
-
repo,
|
|
11301
|
-
provider: detectProvider(host),
|
|
11302
|
-
orgUrl: `https://${host}/${owner}`,
|
|
11303
|
-
orgName: owner
|
|
11304
|
-
};
|
|
11296
|
+
async function githubCanSeeRepo(owner, repo, token) {
|
|
11297
|
+
let res;
|
|
11298
|
+
try {
|
|
11299
|
+
res = await fetch(
|
|
11300
|
+
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`,
|
|
11301
|
+
{
|
|
11302
|
+
headers: {
|
|
11303
|
+
Authorization: `Bearer ${token}`,
|
|
11304
|
+
Accept: "application/vnd.github+json",
|
|
11305
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
11306
|
+
"User-Agent": "verity-cli"
|
|
11307
|
+
}
|
|
11308
|
+
}
|
|
11309
|
+
);
|
|
11310
|
+
} catch (err) {
|
|
11311
|
+
return { ok: false, error: `Network error contacting GitHub: ${err.message}` };
|
|
11312
|
+
}
|
|
11313
|
+
if (res.status === 404) return { ok: true, data: false };
|
|
11314
|
+
if (res.ok) return { ok: true, data: true };
|
|
11315
|
+
return { ok: false, error: `GitHub repo lookup failed (HTTP ${res.status})` };
|
|
11305
11316
|
}
|
|
11306
|
-
function
|
|
11307
|
-
const
|
|
11308
|
-
|
|
11309
|
-
|
|
11310
|
-
|
|
11317
|
+
async function githubDeviceFlow() {
|
|
11318
|
+
const override = process.env.VERITY_PROVIDER_TOKEN;
|
|
11319
|
+
if (override) return { ok: true, data: override };
|
|
11320
|
+
let dc;
|
|
11321
|
+
try {
|
|
11322
|
+
const res = await fetch(GITHUB_DEVICE_CODE_URL, {
|
|
11323
|
+
method: "POST",
|
|
11324
|
+
headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
|
|
11325
|
+
body: form({ client_id: GITHUB_CLIENT_ID })
|
|
11326
|
+
});
|
|
11327
|
+
if (!res.ok) {
|
|
11328
|
+
return { ok: false, error: `GitHub device-code request failed (HTTP ${res.status})` };
|
|
11329
|
+
}
|
|
11330
|
+
dc = await res.json();
|
|
11331
|
+
} catch (err) {
|
|
11332
|
+
return { ok: false, error: `Network error contacting GitHub: ${err.message}` };
|
|
11333
|
+
}
|
|
11334
|
+
if (!dc.device_code || !dc.user_code) {
|
|
11335
|
+
return {
|
|
11336
|
+
ok: false,
|
|
11337
|
+
error: "GitHub did not return a device code (is Device Flow enabled on the OAuth app?)"
|
|
11338
|
+
};
|
|
11339
|
+
}
|
|
11340
|
+
printInfo("");
|
|
11341
|
+
printInfo(`To authorize Verity, open: ${dc.verification_uri}`);
|
|
11342
|
+
printInfo(`And enter the code: ${dc.user_code}`);
|
|
11343
|
+
printInfo("Waiting for authorization\u2026");
|
|
11344
|
+
const deadline = Date.now() + (dc.expires_in || 900) * 1e3;
|
|
11345
|
+
let interval = dc.interval || 5;
|
|
11346
|
+
while (Date.now() < deadline) {
|
|
11347
|
+
await sleep(interval * 1e3);
|
|
11348
|
+
let data;
|
|
11349
|
+
try {
|
|
11350
|
+
const res = await fetch(GITHUB_ACCESS_TOKEN_URL, {
|
|
11351
|
+
method: "POST",
|
|
11352
|
+
headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
|
|
11353
|
+
body: form({
|
|
11354
|
+
client_id: GITHUB_CLIENT_ID,
|
|
11355
|
+
device_code: dc.device_code,
|
|
11356
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
11357
|
+
})
|
|
11358
|
+
});
|
|
11359
|
+
data = await res.json().catch(() => ({}));
|
|
11360
|
+
} catch {
|
|
11361
|
+
continue;
|
|
11362
|
+
}
|
|
11363
|
+
if (data.access_token) return { ok: true, data: data.access_token };
|
|
11364
|
+
switch (data.error) {
|
|
11365
|
+
case "authorization_pending":
|
|
11366
|
+
break;
|
|
11367
|
+
case "slow_down":
|
|
11368
|
+
interval += 5;
|
|
11369
|
+
break;
|
|
11370
|
+
case "access_denied":
|
|
11371
|
+
return { ok: false, error: "Authorization was denied on GitHub." };
|
|
11372
|
+
case "expired_token":
|
|
11373
|
+
return { ok: false, error: "The authorization code expired. Re-run register." };
|
|
11374
|
+
default:
|
|
11375
|
+
if (data.error) return { ok: false, error: `GitHub auth error: ${data.error}` };
|
|
11376
|
+
}
|
|
11377
|
+
}
|
|
11378
|
+
return { ok: false, error: "Timed out waiting for GitHub authorization." };
|
|
11311
11379
|
}
|
|
11312
11380
|
|
|
11313
11381
|
// src/lib/register.ts
|
|
@@ -11392,6 +11460,60 @@ async function registerProject(opts) {
|
|
|
11392
11460
|
}
|
|
11393
11461
|
return { ok: true, data: { projectId: project_id, serviceUrl: service_url, email, userId } };
|
|
11394
11462
|
}
|
|
11463
|
+
async function loginOnce(opts) {
|
|
11464
|
+
const usingTokenOverride = Boolean(process.env.VERITY_PROVIDER_TOKEN);
|
|
11465
|
+
const providerAuth = await githubDeviceFlow();
|
|
11466
|
+
if (!providerAuth.ok) {
|
|
11467
|
+
return { ok: false, error: providerAuth.error };
|
|
11468
|
+
}
|
|
11469
|
+
const providerToken = providerAuth.data;
|
|
11470
|
+
const parsed = opts.remote ? parseRemote(opts.remote) : null;
|
|
11471
|
+
if (!usingTokenOverride && parsed && parsed.provider === "github") {
|
|
11472
|
+
const installed = await ensureAppInstalled(parsed.owner, parsed.repo, providerToken);
|
|
11473
|
+
if (!installed.ok) {
|
|
11474
|
+
printWarn(installed.error);
|
|
11475
|
+
printInfo("Continuing login \u2014 repositories on other accounts are still granted.");
|
|
11476
|
+
}
|
|
11477
|
+
}
|
|
11478
|
+
const result = await apiRequest({
|
|
11479
|
+
method: "POST",
|
|
11480
|
+
path: "/auth/login",
|
|
11481
|
+
serviceUrl: opts.serviceUrl,
|
|
11482
|
+
extraHeaders: { "X-Provider-Token": providerToken },
|
|
11483
|
+
verbose: opts.verbose,
|
|
11484
|
+
cmd: "login"
|
|
11485
|
+
});
|
|
11486
|
+
if (!result.ok) {
|
|
11487
|
+
return { ok: false, error: result.error };
|
|
11488
|
+
}
|
|
11489
|
+
const { token, service_url, user_id, user, repo_count } = result.data;
|
|
11490
|
+
const loginUserId = user_id ?? user?.id ?? void 0;
|
|
11491
|
+
try {
|
|
11492
|
+
await upsertGlobalCredential("", {
|
|
11493
|
+
token,
|
|
11494
|
+
serviceUrl: service_url,
|
|
11495
|
+
userId: loginUserId,
|
|
11496
|
+
email: user?.email
|
|
11497
|
+
});
|
|
11498
|
+
} catch (err) {
|
|
11499
|
+
return {
|
|
11500
|
+
ok: false,
|
|
11501
|
+
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".`
|
|
11502
|
+
};
|
|
11503
|
+
}
|
|
11504
|
+
const pruned = await removeSupersededUserCredentials(service_url, loginUserId);
|
|
11505
|
+
return {
|
|
11506
|
+
ok: true,
|
|
11507
|
+
data: {
|
|
11508
|
+
token,
|
|
11509
|
+
serviceUrl: service_url,
|
|
11510
|
+
email: user?.email,
|
|
11511
|
+
userId: loginUserId,
|
|
11512
|
+
repoCount: repo_count ?? 0,
|
|
11513
|
+
prunedCredentials: pruned
|
|
11514
|
+
}
|
|
11515
|
+
};
|
|
11516
|
+
}
|
|
11395
11517
|
|
|
11396
11518
|
// src/commands/auth.ts
|
|
11397
11519
|
function registerAuthCommands(program2) {
|
|
@@ -11481,10 +11603,8 @@ function registerAuthCommands(program2) {
|
|
|
11481
11603
|
}
|
|
11482
11604
|
|
|
11483
11605
|
// src/commands/login.ts
|
|
11484
|
-
var import_node_child_process5 = require("node:child_process");
|
|
11485
|
-
var import_node_path4 = require("node:path");
|
|
11486
11606
|
function registerLoginCommand(program2) {
|
|
11487
|
-
program2.command("login").description("Log in to Verity (
|
|
11607
|
+
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
11608
|
const globals = program2.opts();
|
|
11489
11609
|
const urlResult = await resolveServiceUrlDetailed(globals.serviceUrl);
|
|
11490
11610
|
if (!urlResult.ok) {
|
|
@@ -11494,60 +11614,208 @@ function registerLoginCommand(program2) {
|
|
|
11494
11614
|
const heal = await maybeHealServiceUrl(urlResult.data, globals.verbose);
|
|
11495
11615
|
const serviceUrl = heal.serviceUrl;
|
|
11496
11616
|
if (heal.healed) {
|
|
11497
|
-
printInfo(" Completing login
|
|
11617
|
+
printInfo(" Completing login updates ~/.verity/credentials against the live service.");
|
|
11618
|
+
}
|
|
11619
|
+
const remote = currentRemote();
|
|
11620
|
+
if (remote && !parseRemote(remote)) {
|
|
11621
|
+
printWarn(`This repository's origin remote is not a recognizable git URL: ${remote}`);
|
|
11622
|
+
printInfo(" Verity cannot identify the repository from it, so runs here will not be saved.");
|
|
11623
|
+
printInfo(" Point origin at the full URL (e.g. git@github.com:owner/repo.git) to fix it.");
|
|
11498
11624
|
}
|
|
11499
11625
|
const existing = await resolveToken(globals.token);
|
|
11500
11626
|
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) {
|
|
11627
|
+
const who = await whoami(existing.data.token, serviceUrl, globals.verbose);
|
|
11628
|
+
if (who.ok && who.data.logged_in) {
|
|
11629
|
+
const nudge = reverifyNudge(who.data);
|
|
11630
|
+
if (!nudge) {
|
|
11631
|
+
printInfo(`Already logged in as ${who.data.email ?? `user #${who.data.user_id}`}. \u2713`);
|
|
11632
|
+
printInfo(" Re-authenticate with: verity login --force");
|
|
11633
|
+
return;
|
|
11634
|
+
}
|
|
11635
|
+
printWarn(nudge);
|
|
11636
|
+
printInfo("Re-verifying your repository access\u2026");
|
|
11637
|
+
} else if (who.ok && who.data.anonymous) {
|
|
11513
11638
|
printInfo("You have an anonymous token (the gate runs, but nothing is saved). Logging you in\u2026");
|
|
11514
|
-
} else if (!
|
|
11639
|
+
} else if (!who.ok) {
|
|
11515
11640
|
printWarn("Could not confirm your current login state with the service \u2014 proceeding to log in.");
|
|
11516
11641
|
}
|
|
11517
11642
|
}
|
|
11518
|
-
|
|
11519
|
-
|
|
11520
|
-
|
|
11521
|
-
|
|
11643
|
+
printInfo("Authenticating with GitHub\u2026");
|
|
11644
|
+
const result = await loginOnce({ serviceUrl, remote: remote || void 0, verbose: globals.verbose });
|
|
11645
|
+
if (!result.ok) {
|
|
11646
|
+
printError(`Login failed: ${result.error}`);
|
|
11647
|
+
process.exit(1);
|
|
11522
11648
|
}
|
|
11523
|
-
|
|
11524
|
-
|
|
11525
|
-
|
|
11649
|
+
const out = result.data;
|
|
11650
|
+
const identity = out.email ?? (out.userId != null ? `user #${out.userId}` : "your account");
|
|
11651
|
+
printInfo(`Logged in as ${identity}. \u2713`);
|
|
11652
|
+
printInfo(` Access granted to ${out.repoCount} ${out.repoCount === 1 ? "repository" : "repositories"}.`);
|
|
11653
|
+
printInfo(" Runs, history, and cloud memory now sync to Verity everywhere you have write access.");
|
|
11654
|
+
if (out.prunedCredentials > 0) {
|
|
11655
|
+
printVerbose(`Pruned ${out.prunedCredentials} superseded per-repository credential line(s).`, globals.verbose);
|
|
11656
|
+
} else if (out.prunedCredentials < 0) {
|
|
11657
|
+
printWarn(" Could not rewrite ~/.verity/credentials: superseded per-repository tokens remain and");
|
|
11658
|
+
printWarn(" will keep taking precedence over this login in their own repositories.");
|
|
11659
|
+
printInfo(` Check the file's permissions; the next successful "verity login" retries the cleanup.`);
|
|
11660
|
+
}
|
|
11661
|
+
if (out.repoCount === 0) {
|
|
11662
|
+
printWarn("The Verity GitHub App is not installed on any account you can access.");
|
|
11663
|
+
printInfo(` Install it (and grant your repositories), then re-run verity login:`);
|
|
11664
|
+
printInfo(` ${githubAppInstallUrl(null)}`);
|
|
11665
|
+
return;
|
|
11666
|
+
}
|
|
11667
|
+
if (remote) {
|
|
11668
|
+
const who = await whoami(out.token, out.serviceUrl, globals.verbose);
|
|
11669
|
+
if (who.ok && who.data.grant_status != null) {
|
|
11670
|
+
printInfo(" \u2713 This repository is covered.");
|
|
11671
|
+
} else if (!who.ok) {
|
|
11672
|
+
printWarn(` Could not confirm this repository's coverage (${who.error}) \u2014 verity status will show it.`);
|
|
11673
|
+
} else {
|
|
11674
|
+
const parsed = parseRemote(remote);
|
|
11675
|
+
const installUrl = githubAppInstallUrl(parsed ? await githubAccountId(parsed.owner) : null);
|
|
11676
|
+
printWarn(` This repository (${parsed ? `${parsed.owner}/${parsed.repo}` : remote}) is NOT covered by your grants.`);
|
|
11677
|
+
printInfo(" Grant the Verity GitHub App access to it, then re-run verity login:");
|
|
11678
|
+
printInfo(` ${installUrl}`);
|
|
11679
|
+
}
|
|
11680
|
+
const rec = await readGlobalCredential(remote);
|
|
11681
|
+
if (rec && rec.token !== out.token) {
|
|
11682
|
+
const otherBackend = rec.serviceUrl != null && rec.serviceUrl !== out.serviceUrl;
|
|
11683
|
+
if (otherBackend) {
|
|
11684
|
+
printWarn(` Note: this repository is pinned to a different Verity service (${rec.serviceUrl})`);
|
|
11685
|
+
printWarn(" by its own credential line, which takes precedence here \u2014 this login does not");
|
|
11686
|
+
printWarn(" change that. To move the repository, remove its line from ~/.verity/credentials.");
|
|
11687
|
+
} else {
|
|
11688
|
+
const kind = rec.userId != null ? "superseded per-repository" : "anonymous project-specific";
|
|
11689
|
+
printWarn(` Note: this repository has a ${kind} credential that takes`);
|
|
11690
|
+
printWarn(" precedence here. Remove its line from ~/.verity/credentials to use your login.");
|
|
11691
|
+
}
|
|
11692
|
+
}
|
|
11693
|
+
}
|
|
11694
|
+
});
|
|
11695
|
+
}
|
|
11696
|
+
|
|
11697
|
+
// src/commands/token.ts
|
|
11698
|
+
function registerTokenCommand(program2) {
|
|
11699
|
+
const token = program2.command("token").description("Manage service (CI/machine) tokens for this repository");
|
|
11700
|
+
async function requireAuth(globals) {
|
|
11701
|
+
const tokenResult = await resolveToken(globals.token);
|
|
11702
|
+
if (!tokenResult.ok) {
|
|
11703
|
+
printError(tokenResult.error);
|
|
11526
11704
|
process.exit(1);
|
|
11527
11705
|
}
|
|
11528
|
-
const
|
|
11529
|
-
|
|
11530
|
-
|
|
11706
|
+
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
11707
|
+
if (!urlResult.ok) {
|
|
11708
|
+
printError(urlResult.error);
|
|
11709
|
+
process.exit(1);
|
|
11710
|
+
}
|
|
11711
|
+
return { bearer: tokenResult.data.token, serviceUrl: urlResult.data };
|
|
11712
|
+
}
|
|
11713
|
+
function explainDenial(error) {
|
|
11714
|
+
if (error.startsWith("STALE_VERIFICATION")) {
|
|
11715
|
+
printInfo(' Your GitHub verification has expired \u2014 run "verity login", then retry.');
|
|
11716
|
+
} else if (error.startsWith("FORBIDDEN")) {
|
|
11717
|
+
printInfo(" You need write access to this repository. If it was added recently,");
|
|
11718
|
+
printInfo(' run "verity login" to refresh your grants.');
|
|
11719
|
+
} else if (error.startsWith("INVALID_REQUEST")) {
|
|
11720
|
+
printInfo(" Tokens are managed per repository \u2014 run this inside a repository with a");
|
|
11721
|
+
printInfo(" git remote (origin), so Verity knows which project the token belongs to.");
|
|
11722
|
+
}
|
|
11723
|
+
}
|
|
11724
|
+
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) => {
|
|
11725
|
+
const globals = program2.opts();
|
|
11726
|
+
const { bearer, serviceUrl } = await requireAuth(globals);
|
|
11727
|
+
let expiresInDays;
|
|
11728
|
+
if (opts.expires != null) {
|
|
11729
|
+
expiresInDays = Number(opts.expires);
|
|
11730
|
+
if (!Number.isInteger(expiresInDays) || expiresInDays <= 0 || expiresInDays > 3650) {
|
|
11731
|
+
printError("--expires must be a whole number of days between 1 and 3650");
|
|
11732
|
+
process.exit(1);
|
|
11733
|
+
}
|
|
11734
|
+
}
|
|
11735
|
+
const result = await apiRequest({
|
|
11736
|
+
method: "POST",
|
|
11737
|
+
path: "/auth/tokens",
|
|
11738
|
+
serviceUrl,
|
|
11739
|
+
token: bearer,
|
|
11740
|
+
body: {
|
|
11741
|
+
agent_name: String(opts.name).trim(),
|
|
11742
|
+
token_type: "service",
|
|
11743
|
+
...expiresInDays != null ? { expires_in_days: expiresInDays } : {}
|
|
11744
|
+
},
|
|
11745
|
+
verbose: globals.verbose,
|
|
11746
|
+
cmd: "token-create"
|
|
11747
|
+
});
|
|
11531
11748
|
if (!result.ok) {
|
|
11532
|
-
printError(`
|
|
11749
|
+
printError(`Could not create the service token: ${result.error}`);
|
|
11750
|
+
explainDenial(result.error);
|
|
11533
11751
|
process.exit(1);
|
|
11534
11752
|
}
|
|
11535
|
-
|
|
11536
|
-
printInfo(
|
|
11537
|
-
printInfo(
|
|
11538
|
-
|
|
11539
|
-
|
|
11753
|
+
printInfo(`Service token "${result.data.agent_name}" created. \u2713`);
|
|
11754
|
+
printInfo("");
|
|
11755
|
+
printInfo(` ${result.data.token}`);
|
|
11756
|
+
printInfo("");
|
|
11757
|
+
printWarn("This token is shown ONCE \u2014 store it now (e.g. as a VERITY_TOKEN CI secret).");
|
|
11758
|
+
if (result.data.expires_at) {
|
|
11759
|
+
printInfo(` Expires: ${result.data.expires_at.slice(0, 10)}`);
|
|
11540
11760
|
}
|
|
11761
|
+
printInfo(" Any current writer of this repository can revoke it: verity token revoke <id>");
|
|
11762
|
+
printInfo(` Token id: ${result.data.token_id}`);
|
|
11763
|
+
});
|
|
11764
|
+
token.command("list").description("List this repository's tokens (ids and metadata only \u2014 never secrets)").action(async () => {
|
|
11765
|
+
const globals = program2.opts();
|
|
11766
|
+
const { bearer, serviceUrl } = await requireAuth(globals);
|
|
11767
|
+
const result = await apiRequest({
|
|
11768
|
+
method: "GET",
|
|
11769
|
+
path: "/auth/tokens",
|
|
11770
|
+
serviceUrl,
|
|
11771
|
+
token: bearer,
|
|
11772
|
+
verbose: globals.verbose,
|
|
11773
|
+
cmd: "token-list"
|
|
11774
|
+
});
|
|
11775
|
+
if (!result.ok) {
|
|
11776
|
+
printError(`Could not list tokens: ${result.error}`);
|
|
11777
|
+
explainDenial(result.error);
|
|
11778
|
+
process.exit(1);
|
|
11779
|
+
}
|
|
11780
|
+
const tokens = result.data.tokens ?? [];
|
|
11781
|
+
if (tokens.length === 0) {
|
|
11782
|
+
printInfo("No tokens found for this repository.");
|
|
11783
|
+
return;
|
|
11784
|
+
}
|
|
11785
|
+
for (const t of tokens) {
|
|
11786
|
+
const type = t.token_type ?? "user";
|
|
11787
|
+
const expires = t.expires_at ? `expires ${t.expires_at.slice(0, 10)}` : "no expiry";
|
|
11788
|
+
const lastUsed = t.last_used_at ? `last used ${t.last_used_at.slice(0, 10)}` : "never used";
|
|
11789
|
+
printInfo(`${t.id} [${type}] ${t.agent_name ?? "unnamed"} (${expires}, ${lastUsed})`);
|
|
11790
|
+
}
|
|
11791
|
+
});
|
|
11792
|
+
token.command("revoke <id>").description("Revoke a token by id (service tokens: any current writer may revoke)").action(async (id) => {
|
|
11793
|
+
const globals = program2.opts();
|
|
11794
|
+
const { bearer, serviceUrl } = await requireAuth(globals);
|
|
11795
|
+
const result = await apiRequest({
|
|
11796
|
+
method: "DELETE",
|
|
11797
|
+
path: `/auth/tokens/${encodeURIComponent(id)}`,
|
|
11798
|
+
serviceUrl,
|
|
11799
|
+
token: bearer,
|
|
11800
|
+
verbose: globals.verbose,
|
|
11801
|
+
cmd: "token-revoke"
|
|
11802
|
+
});
|
|
11803
|
+
if (!result.ok) {
|
|
11804
|
+
printError(`Could not revoke the token: ${result.error}`);
|
|
11805
|
+
explainDenial(result.error);
|
|
11806
|
+
process.exit(1);
|
|
11807
|
+
}
|
|
11808
|
+
printInfo(`Token ${result.data.token_id} revoked. \u2713`);
|
|
11541
11809
|
});
|
|
11542
11810
|
}
|
|
11543
11811
|
|
|
11544
11812
|
// src/lib/hooks.ts
|
|
11545
11813
|
var import_promises4 = require("node:fs/promises");
|
|
11546
|
-
var
|
|
11814
|
+
var import_node_path5 = require("node:path");
|
|
11547
11815
|
|
|
11548
11816
|
// src/lib/json-file.ts
|
|
11549
11817
|
var import_promises3 = require("node:fs/promises");
|
|
11550
|
-
var
|
|
11818
|
+
var import_node_path4 = require("node:path");
|
|
11551
11819
|
function jsonSemanticEqual(a, b) {
|
|
11552
11820
|
if (a === b) return true;
|
|
11553
11821
|
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) {
|
|
@@ -11593,7 +11861,7 @@ async function writeJsonFilePreservingStyle(file, value) {
|
|
|
11593
11861
|
const indent = currentRaw !== null ? detectJsonIndent(currentRaw) : 2;
|
|
11594
11862
|
const next = JSON.stringify(value, null, indent) + "\n";
|
|
11595
11863
|
if (next === currentRaw) return false;
|
|
11596
|
-
await (0, import_promises3.mkdir)((0,
|
|
11864
|
+
await (0, import_promises3.mkdir)((0, import_node_path4.dirname)(file), { recursive: true });
|
|
11597
11865
|
await (0, import_promises3.writeFile)(file, next);
|
|
11598
11866
|
return true;
|
|
11599
11867
|
}
|
|
@@ -11766,13 +12034,13 @@ async function writeSettings(settings) {
|
|
|
11766
12034
|
}
|
|
11767
12035
|
async function readSettingsAt(root) {
|
|
11768
12036
|
try {
|
|
11769
|
-
return JSON.parse(await (0, import_promises4.readFile)((0,
|
|
12037
|
+
return JSON.parse(await (0, import_promises4.readFile)((0, import_node_path5.join)(root, CLAUDE_SETTINGS_FILE), "utf-8"));
|
|
11770
12038
|
} catch {
|
|
11771
12039
|
return {};
|
|
11772
12040
|
}
|
|
11773
12041
|
}
|
|
11774
12042
|
async function writeSettingsAt(root, settings) {
|
|
11775
|
-
await writeJsonFilePreservingStyle((0,
|
|
12043
|
+
await writeJsonFilePreservingStyle((0, import_node_path5.join)(root, CLAUDE_SETTINGS_FILE), settings);
|
|
11776
12044
|
}
|
|
11777
12045
|
async function hasLegacyHooksAt(root) {
|
|
11778
12046
|
const settings = await readSettingsAt(root);
|
|
@@ -12045,7 +12313,7 @@ var import_node_crypto6 = require("node:crypto");
|
|
|
12045
12313
|
// src/lib/conversation-buffer.ts
|
|
12046
12314
|
var import_promises5 = require("node:fs/promises");
|
|
12047
12315
|
var import_node_fs4 = require("node:fs");
|
|
12048
|
-
var
|
|
12316
|
+
var import_node_child_process5 = require("node:child_process");
|
|
12049
12317
|
var import_node_crypto = require("node:crypto");
|
|
12050
12318
|
function stripImageReferences(text) {
|
|
12051
12319
|
return text.replace(/\[Image #\d+\]/g, "[screenshot \u2014 not available for review]");
|
|
@@ -12148,7 +12416,7 @@ async function readBufferEntries() {
|
|
|
12148
12416
|
}
|
|
12149
12417
|
function getRecentCommitMessages() {
|
|
12150
12418
|
try {
|
|
12151
|
-
const output = (0,
|
|
12419
|
+
const output = (0, import_node_child_process5.execSync)(
|
|
12152
12420
|
'git log --since="30 minutes ago" --format="%s" -5',
|
|
12153
12421
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
12154
12422
|
).trim();
|
|
@@ -12163,7 +12431,7 @@ function getRecentCommitMessages() {
|
|
|
12163
12431
|
var import_node_crypto2 = require("node:crypto");
|
|
12164
12432
|
var import_node_fs5 = require("node:fs");
|
|
12165
12433
|
var import_node_os = require("node:os");
|
|
12166
|
-
var
|
|
12434
|
+
var import_node_path6 = require("node:path");
|
|
12167
12435
|
var SHARED_SENTINELS = /* @__PURE__ */ new Set([
|
|
12168
12436
|
"",
|
|
12169
12437
|
"-",
|
|
@@ -12217,13 +12485,13 @@ function contextIdentity(input) {
|
|
|
12217
12485
|
}
|
|
12218
12486
|
function verityHome() {
|
|
12219
12487
|
const override = process.env.VERITY_HOME;
|
|
12220
|
-
return override && override.trim() ? (0,
|
|
12488
|
+
return override && override.trim() ? (0, import_node_path6.resolve)(override) : (0, import_node_path6.join)((0, import_node_os.homedir)(), ".verity");
|
|
12221
12489
|
}
|
|
12222
12490
|
function dossierDir(identity) {
|
|
12223
|
-
return (0,
|
|
12491
|
+
return (0, import_node_path6.join)(verityHome(), "sessions", identity.userKey, identity.treeKey, identity.sessionKey);
|
|
12224
12492
|
}
|
|
12225
12493
|
function treeDir(identity) {
|
|
12226
|
-
return (0,
|
|
12494
|
+
return (0, import_node_path6.join)(verityHome(), "sessions", identity.userKey, identity.treeKey);
|
|
12227
12495
|
}
|
|
12228
12496
|
function scopeIdentity(token, sessionId) {
|
|
12229
12497
|
const t = (token ?? "").trim();
|
|
@@ -12239,7 +12507,7 @@ function sessionScopeKey(token, sessionId) {
|
|
|
12239
12507
|
// src/lib/task-context-buffer.ts
|
|
12240
12508
|
var import_promises6 = require("node:fs/promises");
|
|
12241
12509
|
var import_node_fs6 = require("node:fs");
|
|
12242
|
-
var
|
|
12510
|
+
var import_node_path7 = require("node:path");
|
|
12243
12511
|
var TASK_CONTEXT_DIR = `${VERITY_DIR}/.task-context`;
|
|
12244
12512
|
var MAX_BUFFER_BYTES = 500 * 1024;
|
|
12245
12513
|
var MAX_PROMPT_CHARS = 2e3;
|
|
@@ -12317,7 +12585,7 @@ async function cleanupTaskContextBuffers() {
|
|
|
12317
12585
|
const cutoffMs = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
12318
12586
|
for (const file of files) {
|
|
12319
12587
|
if (!file.endsWith(".jsonl")) continue;
|
|
12320
|
-
const filePath = (0,
|
|
12588
|
+
const filePath = (0, import_node_path7.join)(TASK_CONTEXT_DIR, file);
|
|
12321
12589
|
try {
|
|
12322
12590
|
const stats = await (0, import_promises6.stat)(filePath);
|
|
12323
12591
|
if (stats.mtimeMs < cutoffMs) {
|
|
@@ -12331,7 +12599,7 @@ async function cleanupTaskContextBuffers() {
|
|
|
12331
12599
|
}
|
|
12332
12600
|
function bufferPath(taskId) {
|
|
12333
12601
|
const safe = taskId.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
12334
|
-
return (0,
|
|
12602
|
+
return (0, import_node_path7.join)(TASK_CONTEXT_DIR, `${safe}.jsonl`);
|
|
12335
12603
|
}
|
|
12336
12604
|
async function appendEntry(taskId, entry) {
|
|
12337
12605
|
try {
|
|
@@ -12357,7 +12625,7 @@ async function appendEntry(taskId, entry) {
|
|
|
12357
12625
|
// src/lib/memory-retrieval.ts
|
|
12358
12626
|
var import_promises7 = require("node:fs/promises");
|
|
12359
12627
|
var import_node_fs7 = require("node:fs");
|
|
12360
|
-
var
|
|
12628
|
+
var import_node_path8 = require("node:path");
|
|
12361
12629
|
var memoryDir = () => projectPath(`${VERITY_DIR}/memory`);
|
|
12362
12630
|
var DOMAINS = ["decisions", "quality", "security", "intent", "gotchas", "patterns", "domain", "integrations"];
|
|
12363
12631
|
var DEFAULT_BUDGET_TOKENS = 2e3;
|
|
@@ -12455,14 +12723,14 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
|
|
|
12455
12723
|
const promptTokens = tokenize(promptText);
|
|
12456
12724
|
const nodes = [];
|
|
12457
12725
|
for (const domain of DOMAINS) {
|
|
12458
|
-
const domainDir = (0,
|
|
12726
|
+
const domainDir = (0, import_node_path8.join)(memoryDir(), domain);
|
|
12459
12727
|
if (!(0, import_node_fs7.existsSync)(domainDir)) continue;
|
|
12460
12728
|
try {
|
|
12461
12729
|
const files = await (0, import_promises7.readdir)(domainDir);
|
|
12462
12730
|
for (const file of files) {
|
|
12463
12731
|
if (!file.endsWith(".md")) continue;
|
|
12464
12732
|
try {
|
|
12465
|
-
const content = await (0, import_promises7.readFile)((0,
|
|
12733
|
+
const content = await (0, import_promises7.readFile)((0, import_node_path8.join)(domainDir, file), "utf-8");
|
|
12466
12734
|
const { fm, body } = parseFrontmatter(content);
|
|
12467
12735
|
if (fm.status && fm.status !== "active") continue;
|
|
12468
12736
|
nodes.push({
|
|
@@ -12522,7 +12790,7 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
|
|
|
12522
12790
|
// src/lib/memory-sync.ts
|
|
12523
12791
|
var import_promises8 = require("node:fs/promises");
|
|
12524
12792
|
var import_node_fs8 = require("node:fs");
|
|
12525
|
-
var
|
|
12793
|
+
var import_node_path9 = require("node:path");
|
|
12526
12794
|
var import_node_crypto3 = require("node:crypto");
|
|
12527
12795
|
|
|
12528
12796
|
// src/lib/glob-match.ts
|
|
@@ -12593,16 +12861,16 @@ var syncStateFile = () => projectPath(`${VERITY_DIR}/.memory-sync-state.json`);
|
|
|
12593
12861
|
async function ensureMemoryDir() {
|
|
12594
12862
|
await (0, import_promises8.mkdir)(memoryDir2(), { recursive: true });
|
|
12595
12863
|
for (const domain of DOMAINS2) {
|
|
12596
|
-
await (0, import_promises8.mkdir)((0,
|
|
12864
|
+
await (0, import_promises8.mkdir)((0, import_node_path9.join)(memoryDir2(), domain), { recursive: true });
|
|
12597
12865
|
}
|
|
12598
|
-
if (!(0, import_node_fs8.existsSync)((0,
|
|
12599
|
-
await (0, import_promises8.writeFile)((0,
|
|
12866
|
+
if (!(0, import_node_fs8.existsSync)((0, import_node_path9.join)(memoryDir2(), "SCHEMA.md"))) {
|
|
12867
|
+
await (0, import_promises8.writeFile)((0, import_node_path9.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
|
|
12600
12868
|
}
|
|
12601
|
-
if (!(0, import_node_fs8.existsSync)((0,
|
|
12602
|
-
await (0, import_promises8.writeFile)((0,
|
|
12869
|
+
if (!(0, import_node_fs8.existsSync)((0, import_node_path9.join)(memoryDir2(), "index.md"))) {
|
|
12870
|
+
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
12871
|
}
|
|
12604
|
-
if (!(0, import_node_fs8.existsSync)((0,
|
|
12605
|
-
await (0, import_promises8.writeFile)((0,
|
|
12872
|
+
if (!(0, import_node_fs8.existsSync)((0, import_node_path9.join)(memoryDir2(), "log.md"))) {
|
|
12873
|
+
await (0, import_promises8.writeFile)((0, import_node_path9.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
|
|
12606
12874
|
}
|
|
12607
12875
|
}
|
|
12608
12876
|
async function buildManifest() {
|
|
@@ -12611,14 +12879,14 @@ async function buildManifest() {
|
|
|
12611
12879
|
}
|
|
12612
12880
|
const nodes = [];
|
|
12613
12881
|
for (const domain of DOMAINS2) {
|
|
12614
|
-
const domainDir = (0,
|
|
12882
|
+
const domainDir = (0, import_node_path9.join)(memoryDir2(), domain);
|
|
12615
12883
|
if (!(0, import_node_fs8.existsSync)(domainDir)) continue;
|
|
12616
12884
|
try {
|
|
12617
12885
|
const files = await (0, import_promises8.readdir)(domainDir);
|
|
12618
12886
|
for (const file of files) {
|
|
12619
12887
|
if (!file.endsWith(".md")) continue;
|
|
12620
12888
|
const filePath = `${domain}/${file}`;
|
|
12621
|
-
const fullPath = (0,
|
|
12889
|
+
const fullPath = (0, import_node_path9.join)(memoryDir2(), filePath);
|
|
12622
12890
|
try {
|
|
12623
12891
|
const content = await (0, import_promises8.readFile)(fullPath, "utf-8");
|
|
12624
12892
|
const hash = (0, import_node_crypto3.createHash)("sha256").update(content).digest("hex").slice(0, 16);
|
|
@@ -12631,13 +12899,13 @@ async function buildManifest() {
|
|
|
12631
12899
|
}
|
|
12632
12900
|
let indexHash = null;
|
|
12633
12901
|
try {
|
|
12634
|
-
const indexContent = await (0, import_promises8.readFile)((0,
|
|
12902
|
+
const indexContent = await (0, import_promises8.readFile)((0, import_node_path9.join)(memoryDir2(), "index.md"), "utf-8");
|
|
12635
12903
|
indexHash = `sha256:${(0, import_node_crypto3.createHash)("sha256").update(indexContent).digest("hex").slice(0, 16)}`;
|
|
12636
12904
|
} catch {
|
|
12637
12905
|
}
|
|
12638
12906
|
let logLength = 0;
|
|
12639
12907
|
try {
|
|
12640
|
-
const logContent = await (0, import_promises8.readFile)((0,
|
|
12908
|
+
const logContent = await (0, import_promises8.readFile)((0, import_node_path9.join)(memoryDir2(), "log.md"), "utf-8");
|
|
12641
12909
|
logLength = logContent.split("\n").length;
|
|
12642
12910
|
} catch {
|
|
12643
12911
|
}
|
|
@@ -12650,13 +12918,13 @@ async function readOnDiskNodes() {
|
|
|
12650
12918
|
const out = /* @__PURE__ */ new Map();
|
|
12651
12919
|
if (!(0, import_node_fs8.existsSync)(memoryDir2())) return out;
|
|
12652
12920
|
for (const domain of DOMAINS2) {
|
|
12653
|
-
const domainDir = (0,
|
|
12921
|
+
const domainDir = (0, import_node_path9.join)(memoryDir2(), domain);
|
|
12654
12922
|
if (!(0, import_node_fs8.existsSync)(domainDir)) continue;
|
|
12655
12923
|
try {
|
|
12656
12924
|
for (const file of await (0, import_promises8.readdir)(domainDir)) {
|
|
12657
12925
|
if (!file.endsWith(".md")) continue;
|
|
12658
12926
|
try {
|
|
12659
|
-
out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0,
|
|
12927
|
+
out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0, import_node_path9.join)(domainDir, file), "utf-8")));
|
|
12660
12928
|
} catch {
|
|
12661
12929
|
}
|
|
12662
12930
|
}
|
|
@@ -12702,7 +12970,7 @@ async function computeEditedNodeUploads() {
|
|
|
12702
12970
|
const uploads = [];
|
|
12703
12971
|
for (const [path, prevHash] of prev) {
|
|
12704
12972
|
if (prevHash == null) continue;
|
|
12705
|
-
const full = (0,
|
|
12973
|
+
const full = (0, import_node_path9.join)(memoryDir2(), path);
|
|
12706
12974
|
if (!(0, import_node_fs8.existsSync)(full)) continue;
|
|
12707
12975
|
let content;
|
|
12708
12976
|
try {
|
|
@@ -12739,15 +13007,15 @@ async function applyMemoryWrites(writes, opts = {}) {
|
|
|
12739
13007
|
const logLines = [`- ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)} \u2014 Applied ${count} write(s) from server`];
|
|
12740
13008
|
for (const n of notes) logLines.push(` - ${n}`);
|
|
12741
13009
|
try {
|
|
12742
|
-
const existing = (0, import_node_fs8.existsSync)((0,
|
|
12743
|
-
await (0, import_promises8.writeFile)((0,
|
|
13010
|
+
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";
|
|
13011
|
+
await (0, import_promises8.writeFile)((0, import_node_path9.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
|
|
12744
13012
|
} catch {
|
|
12745
13013
|
}
|
|
12746
13014
|
await recordSyncedNodePaths();
|
|
12747
13015
|
return count;
|
|
12748
13016
|
}
|
|
12749
13017
|
async function applyOneWrite(write, treePaths) {
|
|
12750
|
-
const fullPath = (0,
|
|
13018
|
+
const fullPath = (0, import_node_path9.join)(memoryDir2(), write.path);
|
|
12751
13019
|
const notes = [];
|
|
12752
13020
|
let content = write.content;
|
|
12753
13021
|
if (treePaths && treePaths.length > 0) {
|
|
@@ -12769,7 +13037,7 @@ async function applyOneWrite(write, treePaths) {
|
|
|
12769
13037
|
return { written: false, notes };
|
|
12770
13038
|
}
|
|
12771
13039
|
}
|
|
12772
|
-
await (0, import_promises8.mkdir)((0,
|
|
13040
|
+
await (0, import_promises8.mkdir)((0, import_node_path9.dirname)(fullPath), { recursive: true });
|
|
12773
13041
|
await (0, import_promises8.writeFile)(fullPath, content);
|
|
12774
13042
|
return { written: true, notes };
|
|
12775
13043
|
}
|
|
@@ -12810,7 +13078,7 @@ async function regenerateIndex() {
|
|
|
12810
13078
|
];
|
|
12811
13079
|
let totalNodes = 0;
|
|
12812
13080
|
for (const domain of DOMAINS2.filter((d) => d !== "_archive")) {
|
|
12813
|
-
const domainDir = (0,
|
|
13081
|
+
const domainDir = (0, import_node_path9.join)(memoryDir2(), domain);
|
|
12814
13082
|
if (!(0, import_node_fs8.existsSync)(domainDir)) continue;
|
|
12815
13083
|
try {
|
|
12816
13084
|
const files = await (0, import_promises8.readdir)(domainDir);
|
|
@@ -12821,7 +13089,7 @@ async function regenerateIndex() {
|
|
|
12821
13089
|
for (const file of mdFiles.sort()) {
|
|
12822
13090
|
const slug = file.replace(/\.md$/, "");
|
|
12823
13091
|
try {
|
|
12824
|
-
const content = await (0, import_promises8.readFile)((0,
|
|
13092
|
+
const content = await (0, import_promises8.readFile)((0, import_node_path9.join)(domainDir, file), "utf-8");
|
|
12825
13093
|
const title = pickFrontmatter(content, "title") ?? slug;
|
|
12826
13094
|
const kind = pickFrontmatter(content, "kind") ?? "-";
|
|
12827
13095
|
const confidence = pickFrontmatter(content, "confidence");
|
|
@@ -12845,7 +13113,7 @@ async function regenerateIndex() {
|
|
|
12845
13113
|
lines.push("No nodes yet. Run an analysis to start building the knowledge graph.");
|
|
12846
13114
|
}
|
|
12847
13115
|
const next = lines.join("\n") + "\n";
|
|
12848
|
-
const indexPath = (0,
|
|
13116
|
+
const indexPath = (0, import_node_path9.join)(memoryDir2(), "index.md");
|
|
12849
13117
|
let existing = null;
|
|
12850
13118
|
try {
|
|
12851
13119
|
existing = await (0, import_promises8.readFile)(indexPath, "utf-8");
|
|
@@ -12927,7 +13195,7 @@ function hasLegacyMemoryBlock(text) {
|
|
|
12927
13195
|
return findMarker(text, LEGACY_MD_START) !== -1;
|
|
12928
13196
|
}
|
|
12929
13197
|
async function ensureClaudeMdPointer(cwd = repoRoot()) {
|
|
12930
|
-
const claudeMdPath = (0,
|
|
13198
|
+
const claudeMdPath = (0, import_node_path9.join)(cwd, "CLAUDE.md");
|
|
12931
13199
|
let existing = "";
|
|
12932
13200
|
if ((0, import_node_fs8.existsSync)(claudeMdPath)) {
|
|
12933
13201
|
existing = await (0, import_promises8.readFile)(claudeMdPath, "utf-8");
|
|
@@ -13061,12 +13329,12 @@ Body content (\u22648KB). Use [[node-id]] wikilinks for cross-references.
|
|
|
13061
13329
|
// src/lib/dossier-session.ts
|
|
13062
13330
|
var import_node_fs10 = require("node:fs");
|
|
13063
13331
|
var import_node_crypto5 = require("node:crypto");
|
|
13064
|
-
var
|
|
13332
|
+
var import_node_path11 = require("node:path");
|
|
13065
13333
|
|
|
13066
13334
|
// src/lib/dossier.ts
|
|
13067
13335
|
var import_node_fs9 = require("node:fs");
|
|
13068
13336
|
var import_node_crypto4 = require("node:crypto");
|
|
13069
|
-
var
|
|
13337
|
+
var import_node_path10 = require("node:path");
|
|
13070
13338
|
var MAX_LINE_BYTES = 4096;
|
|
13071
13339
|
var MAX_GOAL_CHARS = 2e3;
|
|
13072
13340
|
var GOAL_KEEP = 8;
|
|
@@ -13109,9 +13377,9 @@ function openDossier(identity) {
|
|
|
13109
13377
|
return {
|
|
13110
13378
|
dir,
|
|
13111
13379
|
identity,
|
|
13112
|
-
eventsPath: (0,
|
|
13113
|
-
foldPath: (0,
|
|
13114
|
-
rotatedDir: (0,
|
|
13380
|
+
eventsPath: (0, import_node_path10.join)(dir, "events.jsonl"),
|
|
13381
|
+
foldPath: (0, import_node_path10.join)(dir, "fold.json"),
|
|
13382
|
+
rotatedDir: (0, import_node_path10.join)(dir, "rotated")
|
|
13115
13383
|
};
|
|
13116
13384
|
} catch {
|
|
13117
13385
|
return null;
|
|
@@ -13184,11 +13452,11 @@ function rotateIfNeeded2(d) {
|
|
|
13184
13452
|
if (!(0, import_node_fs9.existsSync)(d.eventsPath)) return;
|
|
13185
13453
|
if ((0, import_node_fs9.statSync)(d.eventsPath).size < ROTATE_BYTES) return;
|
|
13186
13454
|
(0, import_node_fs9.mkdirSync)(d.rotatedDir, { recursive: true, mode: 448 });
|
|
13187
|
-
(0, import_node_fs9.renameSync)(d.eventsPath, (0,
|
|
13455
|
+
(0, import_node_fs9.renameSync)(d.eventsPath, (0, import_node_path10.join)(d.rotatedDir, `events.${Date.now()}.jsonl`));
|
|
13188
13456
|
const kept = (0, import_node_fs9.readdirSync)(d.rotatedDir).filter((f) => f.endsWith(".jsonl")).sort();
|
|
13189
13457
|
for (const stale of kept.slice(0, Math.max(0, kept.length - ROTATE_KEEP))) {
|
|
13190
13458
|
try {
|
|
13191
|
-
(0, import_node_fs9.renameSync)((0,
|
|
13459
|
+
(0, import_node_fs9.renameSync)((0, import_node_path10.join)(d.rotatedDir, stale), (0, import_node_path10.join)(d.rotatedDir, `${stale}.pruned`));
|
|
13192
13460
|
} catch {
|
|
13193
13461
|
}
|
|
13194
13462
|
}
|
|
@@ -13250,7 +13518,7 @@ function foldDossier(d, opts = {}) {
|
|
|
13250
13518
|
state.meta.rotations = files.length;
|
|
13251
13519
|
for (const f of files) {
|
|
13252
13520
|
try {
|
|
13253
|
-
ingest((0, import_node_fs9.readFileSync)((0,
|
|
13521
|
+
ingest((0, import_node_fs9.readFileSync)((0, import_node_path10.join)(d.rotatedDir, f), "utf8"));
|
|
13254
13522
|
} catch {
|
|
13255
13523
|
state.meta.dropped_lines++;
|
|
13256
13524
|
}
|
|
@@ -14046,16 +14314,16 @@ function foreignAuthoredPaths(identity, opts = {}) {
|
|
|
14046
14314
|
for (const entry of (0, import_node_fs10.readdirSync)(dir, { withFileTypes: true })) {
|
|
14047
14315
|
if (!entry.isDirectory()) continue;
|
|
14048
14316
|
if (entry.name === identity.sessionKey) continue;
|
|
14049
|
-
const log = (0,
|
|
14317
|
+
const log = (0, import_node_path11.join)(dir, entry.name, "events.jsonl");
|
|
14050
14318
|
try {
|
|
14051
14319
|
if (!(0, import_node_fs10.existsSync)(log)) continue;
|
|
14052
14320
|
if (now - (0, import_node_fs10.statSync)(log).mtimeMs > windowMs) continue;
|
|
14053
14321
|
const sib = {
|
|
14054
|
-
dir: (0,
|
|
14322
|
+
dir: (0, import_node_path11.join)(dir, entry.name),
|
|
14055
14323
|
identity,
|
|
14056
14324
|
eventsPath: log,
|
|
14057
|
-
foldPath: (0,
|
|
14058
|
-
rotatedDir: (0,
|
|
14325
|
+
foldPath: (0, import_node_path11.join)(dir, entry.name, "fold.json"),
|
|
14326
|
+
rotatedDir: (0, import_node_path11.join)(dir, entry.name, "rotated")
|
|
14059
14327
|
};
|
|
14060
14328
|
const state = readFoldCache(sib) ?? foldDossier(sib);
|
|
14061
14329
|
sessions++;
|
|
@@ -14083,22 +14351,22 @@ function pruneOldDossiers(identity, maxAgeMs = 7 * 24 * 60 * 60 * 1e3) {
|
|
|
14083
14351
|
let removed = 0;
|
|
14084
14352
|
try {
|
|
14085
14353
|
const mine = dossierDir(identity);
|
|
14086
|
-
const userDir = (0,
|
|
14354
|
+
const userDir = (0, import_node_path11.dirname)((0, import_node_path11.dirname)(mine));
|
|
14087
14355
|
if (!(0, import_node_fs10.existsSync)(userDir)) return 0;
|
|
14088
14356
|
const cutoff = Date.now() - maxAgeMs;
|
|
14089
14357
|
for (const tree of (0, import_node_fs10.readdirSync)(userDir, { withFileTypes: true })) {
|
|
14090
14358
|
if (!tree.isDirectory()) continue;
|
|
14091
|
-
const treePath = (0,
|
|
14359
|
+
const treePath = (0, import_node_path11.join)(userDir, tree.name);
|
|
14092
14360
|
let live = 0;
|
|
14093
14361
|
for (const entry of (0, import_node_fs10.readdirSync)(treePath, { withFileTypes: true })) {
|
|
14094
14362
|
if (!entry.isDirectory()) continue;
|
|
14095
|
-
const dir = (0,
|
|
14363
|
+
const dir = (0, import_node_path11.join)(treePath, entry.name);
|
|
14096
14364
|
if (dir === mine) {
|
|
14097
14365
|
live++;
|
|
14098
14366
|
continue;
|
|
14099
14367
|
}
|
|
14100
14368
|
try {
|
|
14101
|
-
const log = (0,
|
|
14369
|
+
const log = (0, import_node_path11.join)(dir, "events.jsonl");
|
|
14102
14370
|
const at = (0, import_node_fs10.existsSync)(log) ? (0, import_node_fs10.statSync)(log).mtimeMs : (0, import_node_fs10.statSync)(dir).mtimeMs;
|
|
14103
14371
|
if (at < cutoff) {
|
|
14104
14372
|
(0, import_node_fs10.rmSync)(dir, { recursive: true, force: true });
|
|
@@ -14141,7 +14409,7 @@ function recordTurn(d, t) {
|
|
|
14141
14409
|
for (const a of t.authored) {
|
|
14142
14410
|
const origin = a.owner === "subagent" ? "subagent" : "edit_tool";
|
|
14143
14411
|
const prior = t.known?.authored?.get(a.p);
|
|
14144
|
-
const hash = fileHash((0,
|
|
14412
|
+
const hash = fileHash((0, import_node_path11.join)(root, a.p));
|
|
14145
14413
|
const hunks = Math.max(0, a.h - (prior?.hunks ?? 0));
|
|
14146
14414
|
const adds = Math.max(0, a.a - (prior?.adds ?? 0));
|
|
14147
14415
|
const dels = Math.max(0, a.d - (prior?.dels ?? 0));
|
|
@@ -14174,7 +14442,7 @@ function recordTurn(d, t) {
|
|
|
14174
14442
|
}
|
|
14175
14443
|
const seenDivergence = t.known?.divergence ?? /* @__PURE__ */ new Set();
|
|
14176
14444
|
for (const u of t.unobserved) {
|
|
14177
|
-
const hash = fileHash((0,
|
|
14445
|
+
const hash = fileHash((0, import_node_path11.join)(root, u.p));
|
|
14178
14446
|
if (seenDivergence.has(divergenceKey(u.p, hash))) continue;
|
|
14179
14447
|
appendEvent(d, { k: "divergence", kind: "external_mutation", path: u.p, hash });
|
|
14180
14448
|
}
|
|
@@ -14203,7 +14471,7 @@ function recordVerdict(d, v) {
|
|
|
14203
14471
|
if (!f.file || typeof f.line !== "number" || !f.pattern_id) continue;
|
|
14204
14472
|
if (!lines.has(f.file)) {
|
|
14205
14473
|
try {
|
|
14206
|
-
const abs = (0,
|
|
14474
|
+
const abs = (0, import_node_path11.join)(root, f.file);
|
|
14207
14475
|
lines.set(f.file, (0, import_node_fs10.existsSync)(abs) ? (0, import_node_fs10.readFileSync)(abs, "utf8").split("\n") : null);
|
|
14208
14476
|
} catch {
|
|
14209
14477
|
lines.set(f.file, null);
|
|
@@ -14278,7 +14546,7 @@ function recallMemory(d, identity, opts) {
|
|
|
14278
14546
|
budgetBytes: opts.budgetBytes,
|
|
14279
14547
|
readFileLines: (file) => {
|
|
14280
14548
|
try {
|
|
14281
|
-
const abs = (0,
|
|
14549
|
+
const abs = (0, import_node_path11.join)(root, file);
|
|
14282
14550
|
return (0, import_node_fs10.existsSync)(abs) ? (0, import_node_fs10.readFileSync)(abs, "utf8").split("\n") : null;
|
|
14283
14551
|
} catch {
|
|
14284
14552
|
return null;
|
|
@@ -14418,21 +14686,21 @@ async function fireClassify(prompt, sessionId, globals) {
|
|
|
14418
14686
|
|
|
14419
14687
|
// src/commands/lifecycle.ts
|
|
14420
14688
|
var import_node_fs14 = require("node:fs");
|
|
14421
|
-
var
|
|
14689
|
+
var import_node_path15 = require("node:path");
|
|
14422
14690
|
|
|
14423
14691
|
// src/lib/baseline.ts
|
|
14424
14692
|
var import_node_fs13 = require("node:fs");
|
|
14425
|
-
var
|
|
14693
|
+
var import_node_path14 = require("node:path");
|
|
14426
14694
|
var import_node_crypto7 = require("node:crypto");
|
|
14427
14695
|
|
|
14428
14696
|
// src/lib/snapshot.ts
|
|
14429
14697
|
var import_node_fs12 = require("node:fs");
|
|
14430
|
-
var
|
|
14431
|
-
var
|
|
14698
|
+
var import_node_path13 = require("node:path");
|
|
14699
|
+
var import_node_child_process6 = require("node:child_process");
|
|
14432
14700
|
|
|
14433
14701
|
// src/lib/files.ts
|
|
14434
14702
|
var import_node_fs11 = require("node:fs");
|
|
14435
|
-
var
|
|
14703
|
+
var import_node_path12 = require("node:path");
|
|
14436
14704
|
var LANG_MAP = {
|
|
14437
14705
|
// Analyzable (static analysis + Gemini)
|
|
14438
14706
|
ts: "typescript",
|
|
@@ -14500,7 +14768,7 @@ var LANG_MAP = {
|
|
|
14500
14768
|
mk: "make"
|
|
14501
14769
|
};
|
|
14502
14770
|
function detectLanguage(filepath) {
|
|
14503
|
-
const ext = (0,
|
|
14771
|
+
const ext = (0, import_node_path12.extname)(filepath).slice(1);
|
|
14504
14772
|
return LANG_MAP[ext] ?? ext;
|
|
14505
14773
|
}
|
|
14506
14774
|
function sortByMtime(files) {
|
|
@@ -14588,7 +14856,7 @@ function generateSnapshotDiffs(files) {
|
|
|
14588
14856
|
}
|
|
14589
14857
|
const diffs = [];
|
|
14590
14858
|
for (const file of files) {
|
|
14591
|
-
const snapshotPath = (0,
|
|
14859
|
+
const snapshotPath = (0, import_node_path13.join)(SNAPSHOT_DIR, file.path);
|
|
14592
14860
|
const language = file.language ?? detectLanguage(file.path);
|
|
14593
14861
|
if ((0, import_node_fs12.existsSync)(snapshotPath)) {
|
|
14594
14862
|
const oldContent = (0, import_node_fs12.readFileSync)(snapshotPath, "utf-8");
|
|
@@ -14615,21 +14883,21 @@ ${addedLines}`,
|
|
|
14615
14883
|
function saveSnapshots(files) {
|
|
14616
14884
|
const snapshotPaths = /* @__PURE__ */ new Set();
|
|
14617
14885
|
for (const file of files) {
|
|
14618
|
-
const snapshotPath = (0,
|
|
14886
|
+
const snapshotPath = (0, import_node_path13.join)(SNAPSHOT_DIR, file.path);
|
|
14619
14887
|
snapshotPaths.add(snapshotPath);
|
|
14620
|
-
(0, import_node_fs12.mkdirSync)((0,
|
|
14888
|
+
(0, import_node_fs12.mkdirSync)((0, import_node_path13.dirname)(snapshotPath), { recursive: true });
|
|
14621
14889
|
(0, import_node_fs12.writeFileSync)(snapshotPath, file.content);
|
|
14622
14890
|
}
|
|
14623
14891
|
cleanStaleSnapshots(SNAPSHOT_DIR, snapshotPaths);
|
|
14624
14892
|
}
|
|
14625
14893
|
function computeDiff(oldContent, newContent, filePath) {
|
|
14626
|
-
const tmpOld = (0,
|
|
14627
|
-
const tmpNew = (0,
|
|
14894
|
+
const tmpOld = (0, import_node_path13.join)(SNAPSHOT_DIR, ".diff-old.tmp");
|
|
14895
|
+
const tmpNew = (0, import_node_path13.join)(SNAPSHOT_DIR, ".diff-new.tmp");
|
|
14628
14896
|
try {
|
|
14629
14897
|
(0, import_node_fs12.mkdirSync)(SNAPSHOT_DIR, { recursive: true });
|
|
14630
14898
|
(0, import_node_fs12.writeFileSync)(tmpOld, oldContent);
|
|
14631
14899
|
(0, import_node_fs12.writeFileSync)(tmpNew, newContent);
|
|
14632
|
-
const result = (0,
|
|
14900
|
+
const result = (0, import_node_child_process6.execSync)(
|
|
14633
14901
|
`git diff --no-index --unified=10 -- "${tmpOld}" "${tmpNew}"`,
|
|
14634
14902
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
14635
14903
|
);
|
|
@@ -14657,7 +14925,7 @@ function cleanStaleSnapshots(dir, keepSet) {
|
|
|
14657
14925
|
const entries = (0, import_node_fs12.readdirSync)(dir, { withFileTypes: true });
|
|
14658
14926
|
for (const entry of entries) {
|
|
14659
14927
|
if (entry.name.startsWith(".")) continue;
|
|
14660
|
-
const fullPath = (0,
|
|
14928
|
+
const fullPath = (0, import_node_path13.join)(dir, entry.name);
|
|
14661
14929
|
if (entry.isDirectory()) {
|
|
14662
14930
|
cleanStaleSnapshots(fullPath, keepSet);
|
|
14663
14931
|
try {
|
|
@@ -14686,13 +14954,13 @@ function sessionKey(sessionId) {
|
|
|
14686
14954
|
return (0, import_node_crypto7.createHash)("sha256").update(sessionId).digest("hex").slice(0, 16);
|
|
14687
14955
|
}
|
|
14688
14956
|
function sessionDir(key) {
|
|
14689
|
-
return (0,
|
|
14957
|
+
return (0, import_node_path14.join)(projectPath(BASELINE_DIR), key);
|
|
14690
14958
|
}
|
|
14691
14959
|
function manifestPath(dir) {
|
|
14692
|
-
return (0,
|
|
14960
|
+
return (0, import_node_path14.join)(dir, "manifest.json");
|
|
14693
14961
|
}
|
|
14694
14962
|
function mirrorPath(dir, repoRelPath) {
|
|
14695
|
-
return (0,
|
|
14963
|
+
return (0, import_node_path14.join)(dir, "files", repoRelPath);
|
|
14696
14964
|
}
|
|
14697
14965
|
var CARRY_FILE = `${BASELINE_DIR}/.carry`;
|
|
14698
14966
|
var CARRY_WINDOW_MS = 12e4;
|
|
@@ -14754,7 +15022,7 @@ function captureBaseline(opts = {}) {
|
|
|
14754
15022
|
(0, import_node_fs13.rmSync)(dir, { recursive: true, force: true });
|
|
14755
15023
|
} catch {
|
|
14756
15024
|
}
|
|
14757
|
-
const filesDir = (0,
|
|
15025
|
+
const filesDir = (0, import_node_path14.join)(dir, "files");
|
|
14758
15026
|
const mirrored = [];
|
|
14759
15027
|
try {
|
|
14760
15028
|
(0, import_node_fs13.mkdirSync)(filesDir, { recursive: true });
|
|
@@ -14764,7 +15032,7 @@ function captureBaseline(opts = {}) {
|
|
|
14764
15032
|
if (content === null) continue;
|
|
14765
15033
|
const dest = mirrorPath(dir, p);
|
|
14766
15034
|
try {
|
|
14767
|
-
(0, import_node_fs13.mkdirSync)((0,
|
|
15035
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path14.dirname)(dest), { recursive: true });
|
|
14768
15036
|
(0, import_node_fs13.writeFileSync)(dest, content);
|
|
14769
15037
|
mirrored.push(p);
|
|
14770
15038
|
} catch {
|
|
@@ -14892,7 +15160,7 @@ function pruneOldBaselines() {
|
|
|
14892
15160
|
}
|
|
14893
15161
|
const now = Date.now();
|
|
14894
15162
|
for (const name of entries) {
|
|
14895
|
-
const dir = (0,
|
|
15163
|
+
const dir = (0, import_node_path14.join)(root, name);
|
|
14896
15164
|
const manifest = readManifest(dir);
|
|
14897
15165
|
if (!manifest) {
|
|
14898
15166
|
try {
|
|
@@ -15078,7 +15346,7 @@ function buildCompactionContext(session) {
|
|
|
15078
15346
|
commitsSince: commitsSincePaths(watermark, (state.authored ?? []).map((a) => a.path)),
|
|
15079
15347
|
readFileLines: (file) => {
|
|
15080
15348
|
try {
|
|
15081
|
-
const abs = (0,
|
|
15349
|
+
const abs = (0, import_node_path15.join)(root, file);
|
|
15082
15350
|
return (0, import_node_fs14.existsSync)(abs) ? (0, import_node_fs14.readFileSync)(abs, "utf8").split("\n") : null;
|
|
15083
15351
|
} catch {
|
|
15084
15352
|
return null;
|
|
@@ -15414,6 +15682,7 @@ function registerStatusCommand(program2) {
|
|
|
15414
15682
|
}
|
|
15415
15683
|
const token = tokenResult.data.token;
|
|
15416
15684
|
const serviceUrl = urlResult.data;
|
|
15685
|
+
const who = await whoami(token, serviceUrl, globals.verbose);
|
|
15417
15686
|
const memResult = await apiRequest({
|
|
15418
15687
|
method: "GET",
|
|
15419
15688
|
path: "/memory",
|
|
@@ -15421,14 +15690,21 @@ function registerStatusCommand(program2) {
|
|
|
15421
15690
|
token,
|
|
15422
15691
|
verbose: globals.verbose
|
|
15423
15692
|
});
|
|
15424
|
-
|
|
15693
|
+
const denial = memResult.ok ? null : authDenialRemedy(memResult.error);
|
|
15694
|
+
if (!memResult.ok && !denial) {
|
|
15425
15695
|
printError(memResult.error);
|
|
15426
15696
|
process.exit(1);
|
|
15427
15697
|
}
|
|
15428
|
-
const mem = memResult.data;
|
|
15698
|
+
const mem = memResult.ok ? memResult.data : null;
|
|
15429
15699
|
if (opts.json) {
|
|
15430
|
-
const output = {
|
|
15431
|
-
|
|
15700
|
+
const output = {
|
|
15701
|
+
auth: who.ok ? who.data : { error: who.error },
|
|
15702
|
+
memory: mem
|
|
15703
|
+
};
|
|
15704
|
+
if (denial && !memResult.ok) {
|
|
15705
|
+
output.error = { code: denial.code, message: memResult.error, remedy: denial.remedy };
|
|
15706
|
+
}
|
|
15707
|
+
if (opts.history && !denial) {
|
|
15432
15708
|
const runsResult = await apiRequest({
|
|
15433
15709
|
method: "GET",
|
|
15434
15710
|
path: `/runs?limit=${opts.limit}`,
|
|
@@ -15443,23 +15719,30 @@ function registerStatusCommand(program2) {
|
|
|
15443
15719
|
printJson(output);
|
|
15444
15720
|
return;
|
|
15445
15721
|
}
|
|
15446
|
-
if (mem
|
|
15722
|
+
if (mem?.configured === false) {
|
|
15447
15723
|
printInfo("Verity is not configured for this project. Run /verity-setup.");
|
|
15448
15724
|
return;
|
|
15449
15725
|
}
|
|
15450
15726
|
printInfo("=== Verity Status ===");
|
|
15451
|
-
if (
|
|
15452
|
-
printInfo(`Account: Logged in as ${
|
|
15453
|
-
} else {
|
|
15454
|
-
|
|
15455
|
-
|
|
15456
|
-
|
|
15457
|
-
|
|
15458
|
-
|
|
15459
|
-
|
|
15460
|
-
|
|
15461
|
-
|
|
15462
|
-
|
|
15727
|
+
if (who.ok && who.data.logged_in) {
|
|
15728
|
+
printInfo(`Account: Logged in as ${who.data.email ?? `user #${who.data.user_id}`} \u2713`);
|
|
15729
|
+
} else if (who.ok && who.data.anonymous) {
|
|
15730
|
+
printInfo('Account: Anonymous \u2014 runs not saved, no cloud memory. Run "verity login".');
|
|
15731
|
+
} else if (tokenResult.data.userId != null) {
|
|
15732
|
+
printInfo(`Account: Logged in as ${tokenResult.data.email ?? `user #${tokenResult.data.userId}`} (cached \u2014 could not reach the Verity service) \u2713`);
|
|
15733
|
+
} else if (!who.ok) {
|
|
15734
|
+
printInfo(`Account: Unknown \u2014 could not reach the Verity service (${who.error})`);
|
|
15735
|
+
}
|
|
15736
|
+
if (who.ok) {
|
|
15737
|
+
const nudge = reverifyNudge(who.data);
|
|
15738
|
+
if (nudge) printWarn(` ${nudge}`);
|
|
15739
|
+
}
|
|
15740
|
+
if (denial) {
|
|
15741
|
+
printWarn(`Access: ${denial.remedy}`);
|
|
15742
|
+
printInfo(" Project status, history, and cloud memory stay unavailable until then.");
|
|
15743
|
+
}
|
|
15744
|
+
if (mem?.project_name) printInfo(`Project: ${mem.project_name}`);
|
|
15745
|
+
if (mem?.standard) {
|
|
15463
15746
|
const s = mem.standard;
|
|
15464
15747
|
printInfo(`Standard: v${s.version} (${s.quality_dimensions} quality, ${s.security_patterns} security, ${s.custom_patterns} custom)`);
|
|
15465
15748
|
printInfo(`Languages: ${s.languages.join(", ")}`);
|
|
@@ -15470,6 +15753,7 @@ function registerStatusCommand(program2) {
|
|
|
15470
15753
|
if (hookStatus.guardOn.includes("commit")) moments.push("pre-commit");
|
|
15471
15754
|
if (hookStatus.guardOn.includes("push")) moments.push("pre-push/PR");
|
|
15472
15755
|
printInfo(`Moments: ${moments.length > 0 ? moments.join(", ") : "none (run /verity-setup)"}`);
|
|
15756
|
+
if (!mem) return;
|
|
15473
15757
|
if (mem.recent_runs) {
|
|
15474
15758
|
const r = mem.recent_runs;
|
|
15475
15759
|
printInfo("");
|
|
@@ -15621,7 +15905,7 @@ async function sendGeneralFeedback(message, opts, globals) {
|
|
|
15621
15905
|
|
|
15622
15906
|
// src/commands/analyze.ts
|
|
15623
15907
|
var import_node_fs24 = require("node:fs");
|
|
15624
|
-
var
|
|
15908
|
+
var import_node_path20 = require("node:path");
|
|
15625
15909
|
|
|
15626
15910
|
// src/lib/debounce.ts
|
|
15627
15911
|
var import_node_fs15 = require("node:fs");
|
|
@@ -15758,7 +16042,7 @@ function writeIteration(iteration, commit, _contentHash) {
|
|
|
15758
16042
|
}
|
|
15759
16043
|
|
|
15760
16044
|
// src/lib/static-analysis.ts
|
|
15761
|
-
var
|
|
16045
|
+
var import_node_child_process7 = require("node:child_process");
|
|
15762
16046
|
var import_node_fs16 = require("node:fs");
|
|
15763
16047
|
var SEVERITY_ORDER = {
|
|
15764
16048
|
Error: 0,
|
|
@@ -15771,7 +16055,7 @@ var SEVERITY_ORDER = {
|
|
|
15771
16055
|
};
|
|
15772
16056
|
function isCodacyAvailable() {
|
|
15773
16057
|
try {
|
|
15774
|
-
(0,
|
|
16058
|
+
(0, import_node_child_process7.execSync)("which codacy-analysis", { stdio: "pipe" });
|
|
15775
16059
|
return true;
|
|
15776
16060
|
} catch {
|
|
15777
16061
|
return false;
|
|
@@ -15795,7 +16079,7 @@ function runCodacyAnalysis(files) {
|
|
|
15795
16079
|
const fileArgs = existingFiles.join(" ");
|
|
15796
16080
|
let output;
|
|
15797
16081
|
try {
|
|
15798
|
-
output = (0,
|
|
16082
|
+
output = (0, import_node_child_process7.execSync)(
|
|
15799
16083
|
`codacy-analysis analyze --install-dependencies --files ${fileArgs} --output-format json --log-level error --parallel-tools 3`,
|
|
15800
16084
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], maxBuffer: 10 * 1024 * 1024 }
|
|
15801
16085
|
);
|
|
@@ -15849,7 +16133,7 @@ function runCodacyAnalysis(files) {
|
|
|
15849
16133
|
|
|
15850
16134
|
// src/lib/specs.ts
|
|
15851
16135
|
var import_node_fs17 = require("node:fs");
|
|
15852
|
-
var
|
|
16136
|
+
var import_node_path16 = require("node:path");
|
|
15853
16137
|
var SPEC_CANDIDATES = [
|
|
15854
16138
|
"CLAUDE.md",
|
|
15855
16139
|
"AGENTS.md",
|
|
@@ -15911,7 +16195,7 @@ function findMdFiles(dir, maxDepth, depth = 0) {
|
|
|
15911
16195
|
try {
|
|
15912
16196
|
const entries = (0, import_node_fs17.readdirSync)(dir, { withFileTypes: true });
|
|
15913
16197
|
for (const entry of entries) {
|
|
15914
|
-
const fullPath = (0,
|
|
16198
|
+
const fullPath = (0, import_node_path16.join)(dir, entry.name);
|
|
15915
16199
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
15916
16200
|
result.push(fullPath);
|
|
15917
16201
|
} else if (entry.isDirectory() && depth < maxDepth - 1) {
|
|
@@ -15923,7 +16207,7 @@ function findMdFiles(dir, maxDepth, depth = 0) {
|
|
|
15923
16207
|
return result;
|
|
15924
16208
|
}
|
|
15925
16209
|
function discoverPlans() {
|
|
15926
|
-
const homePlansDir = (0,
|
|
16210
|
+
const homePlansDir = (0, import_node_path16.join)(process.env.HOME ?? "", ".claude", "plans");
|
|
15927
16211
|
const localPlansDir = ".claude/plans";
|
|
15928
16212
|
const candidates = [];
|
|
15929
16213
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -15933,7 +16217,7 @@ function discoverPlans() {
|
|
|
15933
16217
|
for (const f of (0, import_node_fs17.readdirSync)(plansDir)) {
|
|
15934
16218
|
if (!f.endsWith(".md") || seen.has(f)) continue;
|
|
15935
16219
|
seen.add(f);
|
|
15936
|
-
const fullPath = (0,
|
|
16220
|
+
const fullPath = (0, import_node_path16.join)(plansDir, f);
|
|
15937
16221
|
try {
|
|
15938
16222
|
const stat3 = (0, import_node_fs17.statSync)(fullPath);
|
|
15939
16223
|
candidates.push({ name: f, path: fullPath, mtime: stat3.mtimeMs, size: stat3.size });
|
|
@@ -15957,7 +16241,7 @@ function discoverPlans() {
|
|
|
15957
16241
|
}
|
|
15958
16242
|
|
|
15959
16243
|
// src/lib/task-context.ts
|
|
15960
|
-
var
|
|
16244
|
+
var import_node_child_process8 = require("node:child_process");
|
|
15961
16245
|
var CLOSING_RE = /\b(close[sd]?|fix(?:e[sd])?|resolve[sd]?)\b[\s:]*#(\d+)/i;
|
|
15962
16246
|
var BRANCH_RE = /(?:^|[/_-])(?:issue|gh|fix)[-_/]?(\d+)\b/i;
|
|
15963
16247
|
function parseLinkedIssue(sources) {
|
|
@@ -15973,7 +16257,7 @@ function parseLinkedIssue(sources) {
|
|
|
15973
16257
|
}
|
|
15974
16258
|
function safeExec(cmd, timeout) {
|
|
15975
16259
|
try {
|
|
15976
|
-
return (0,
|
|
16260
|
+
return (0, import_node_child_process8.execSync)(cmd, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout }).trim();
|
|
15977
16261
|
} catch {
|
|
15978
16262
|
return "";
|
|
15979
16263
|
}
|
|
@@ -16003,7 +16287,7 @@ function resolveTaskContext(opts) {
|
|
|
16003
16287
|
// src/lib/cli-version.ts
|
|
16004
16288
|
function cliVersion() {
|
|
16005
16289
|
try {
|
|
16006
|
-
return true ? "0.28.1-experimental.
|
|
16290
|
+
return true ? "0.28.1-experimental.dbd87b1" : "dev";
|
|
16007
16291
|
} catch {
|
|
16008
16292
|
return "dev";
|
|
16009
16293
|
}
|
|
@@ -16207,7 +16491,7 @@ function truthy(v) {
|
|
|
16207
16491
|
|
|
16208
16492
|
// src/lib/fold.ts
|
|
16209
16493
|
var import_node_fs20 = require("node:fs");
|
|
16210
|
-
var
|
|
16494
|
+
var import_node_path17 = require("node:path");
|
|
16211
16495
|
var KNOWN_RECORD_TYPES = /* @__PURE__ */ new Set([
|
|
16212
16496
|
"user",
|
|
16213
16497
|
"assistant",
|
|
@@ -16391,7 +16675,7 @@ function fold(transcriptPath, opts = {}) {
|
|
|
16391
16675
|
return result;
|
|
16392
16676
|
}
|
|
16393
16677
|
try {
|
|
16394
|
-
const sidecarDir = (0,
|
|
16678
|
+
const sidecarDir = (0, import_node_path17.join)((0, import_node_path17.dirname)(transcriptPath), "subagents");
|
|
16395
16679
|
if ((0, import_node_fs20.existsSync)(sidecarDir)) {
|
|
16396
16680
|
const maxFiles = opts.maxSidecars ?? 200;
|
|
16397
16681
|
const maxBytes = opts.maxSidecarBytes ?? 16 * 1024 * 1024;
|
|
@@ -16399,7 +16683,7 @@ function fold(transcriptPath, opts = {}) {
|
|
|
16399
16683
|
const walk = (d, depth) => {
|
|
16400
16684
|
if (depth > 4) return;
|
|
16401
16685
|
for (const e of (0, import_node_fs20.readdirSync)(d, { withFileTypes: true })) {
|
|
16402
|
-
const p = (0,
|
|
16686
|
+
const p = (0, import_node_path17.join)(d, e.name);
|
|
16403
16687
|
if (e.isDirectory()) {
|
|
16404
16688
|
walk(p, depth + 1);
|
|
16405
16689
|
} else if (e.name.startsWith("agent-") && e.name.endsWith(".jsonl")) {
|
|
@@ -16606,7 +16890,7 @@ function buildHookOutput(gateDecision, systemMessage, agentContext) {
|
|
|
16606
16890
|
|
|
16607
16891
|
// src/lib/cache-cleanup.ts
|
|
16608
16892
|
var import_node_fs21 = require("node:fs");
|
|
16609
|
-
var
|
|
16893
|
+
var import_node_path18 = require("node:path");
|
|
16610
16894
|
var CACHE_TTL_DAYS = 7;
|
|
16611
16895
|
function pruneStaleCache() {
|
|
16612
16896
|
try {
|
|
@@ -16614,7 +16898,7 @@ function pruneStaleCache() {
|
|
|
16614
16898
|
const cutoff = Date.now() - CACHE_TTL_DAYS * 24 * 3600 * 1e3;
|
|
16615
16899
|
for (const entry of (0, import_node_fs21.readdirSync)(dir)) {
|
|
16616
16900
|
if (!entry.startsWith("pending-")) continue;
|
|
16617
|
-
const path = (0,
|
|
16901
|
+
const path = (0, import_node_path18.join)(dir, entry);
|
|
16618
16902
|
try {
|
|
16619
16903
|
const stat3 = (0, import_node_fs21.statSync)(path);
|
|
16620
16904
|
if (stat3.mtimeMs < cutoff) {
|
|
@@ -17061,7 +17345,7 @@ function isExplicitlyAutonomous(env = process.env) {
|
|
|
17061
17345
|
// src/lib/seed-runner.ts
|
|
17062
17346
|
var import_promises11 = require("node:fs/promises");
|
|
17063
17347
|
var import_node_fs23 = require("node:fs");
|
|
17064
|
-
var
|
|
17348
|
+
var import_node_path19 = require("node:path");
|
|
17065
17349
|
var import_yaml2 = __toESM(require_dist());
|
|
17066
17350
|
|
|
17067
17351
|
// src/lib/seed.ts
|
|
@@ -17343,7 +17627,7 @@ async function runSeed(opts) {
|
|
|
17343
17627
|
if (candidates.length === 0) {
|
|
17344
17628
|
return { created: 0, failed: 0, skipped: "no_candidates", candidates: [] };
|
|
17345
17629
|
}
|
|
17346
|
-
const overviewPath = (0,
|
|
17630
|
+
const overviewPath = (0, import_node_path19.join)(MEMORY_DIR, "domain", "project-overview.md");
|
|
17347
17631
|
if ((0, import_node_fs23.existsSync)(overviewPath) && !opts.force) {
|
|
17348
17632
|
return { created: 0, failed: 0, skipped: "already_seeded", candidates };
|
|
17349
17633
|
}
|
|
@@ -17379,9 +17663,9 @@ async function runSeed(opts) {
|
|
|
17379
17663
|
}
|
|
17380
17664
|
const nodeId = res.data.node_id;
|
|
17381
17665
|
const filePathRel = res.data.file_path;
|
|
17382
|
-
const targetPath = (0,
|
|
17666
|
+
const targetPath = (0, import_node_path19.join)(MEMORY_DIR, filePathRel);
|
|
17383
17667
|
try {
|
|
17384
|
-
await (0, import_promises11.mkdir)((0,
|
|
17668
|
+
await (0, import_promises11.mkdir)((0, import_node_path19.dirname)(targetPath), { recursive: true });
|
|
17385
17669
|
await (0, import_promises11.writeFile)(targetPath, renderNodeMarkdown(c, nodeId, createdAt));
|
|
17386
17670
|
created++;
|
|
17387
17671
|
opts.onCreated?.(nodeId, filePathRel, c);
|
|
@@ -17707,7 +17991,7 @@ async function runAnalyze(opts, globals) {
|
|
|
17707
17991
|
let autoSeedNotice = null;
|
|
17708
17992
|
try {
|
|
17709
17993
|
await ensureMemoryDir();
|
|
17710
|
-
const seedMarker = (0,
|
|
17994
|
+
const seedMarker = (0, import_node_path20.join)(VERITY_DIR, ".seeded");
|
|
17711
17995
|
const hasStandard = (0, import_node_fs24.existsSync)(STANDARD_FILE);
|
|
17712
17996
|
const alreadyTried = (0, import_node_fs24.existsSync)(seedMarker);
|
|
17713
17997
|
if (hasStandard && !alreadyTried) {
|
|
@@ -17775,7 +18059,7 @@ async function runAnalyze(opts, globals) {
|
|
|
17775
18059
|
const priorState = foldForMarks(memorySession.d);
|
|
17776
18060
|
incrementReport = computeIncrement(
|
|
17777
18061
|
allForReview,
|
|
17778
|
-
(p) => fileHash((0,
|
|
18062
|
+
(p) => fileHash((0, import_node_path20.join)(repoRoot(), p)),
|
|
17779
18063
|
priorState.authored_all.map((a) => ({
|
|
17780
18064
|
path: a.path,
|
|
17781
18065
|
hash_at_last_verdict: a.hash_at_last_verdict
|
|
@@ -18030,6 +18314,10 @@ async function runAnalyze(opts, globals) {
|
|
|
18030
18314
|
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
18315
|
} else if (result.category === "network" || result.category === "timeout") {
|
|
18032
18316
|
message = `Verity offline \u2014 ${result.error}`;
|
|
18317
|
+
} else if (result.error.startsWith("STALE_VERIFICATION")) {
|
|
18318
|
+
message = "Verity: your GitHub verification expired \u2014 run `verity login` to re-verify (local analysis this run)";
|
|
18319
|
+
} else if (result.error.startsWith("FORBIDDEN")) {
|
|
18320
|
+
message = "Verity: no access grant for this repository \u2014 run `verity login` to refresh your grants (local analysis this run)";
|
|
18033
18321
|
} else if (result.http_status && result.http_status >= 400 && result.http_status < 500) {
|
|
18034
18322
|
message = `Verity: request rejected (HTTP ${result.http_status}) \u2014 ${result.error}`;
|
|
18035
18323
|
} else if (result.http_status && result.http_status >= 500) {
|
|
@@ -18153,6 +18441,11 @@ async function runAnalyze(opts, globals) {
|
|
|
18153
18441
|
}
|
|
18154
18442
|
saveSnapshots(codeDelta.files.map((f) => ({ path: f.path, content: f.content })));
|
|
18155
18443
|
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." : "";
|
|
18444
|
+
const grantWarning = reverifyNudge({
|
|
18445
|
+
grant_status: response.grant_status,
|
|
18446
|
+
reverify_by: response.reverify_by
|
|
18447
|
+
});
|
|
18448
|
+
const grantNudge = grantWarning ? ` \u26A0\uFE0F ${grantWarning}` : "";
|
|
18156
18449
|
switch (decision) {
|
|
18157
18450
|
case "FAIL": {
|
|
18158
18451
|
writeIteration(iteration + 1, currentCommit, contentHash ?? void 0);
|
|
@@ -18228,6 +18521,9 @@ async function runAnalyze(opts, globals) {
|
|
|
18228
18521
|
`);
|
|
18229
18522
|
if (loginNudge) process.stderr.write(`
|
|
18230
18523
|
${YELLOW}${loginNudge.trim()}${NC}
|
|
18524
|
+
`);
|
|
18525
|
+
if (grantNudge) process.stderr.write(`
|
|
18526
|
+
${YELLOW}${grantNudge.trim()}${NC}
|
|
18231
18527
|
`);
|
|
18232
18528
|
process.exit(2);
|
|
18233
18529
|
break;
|
|
@@ -18240,7 +18536,7 @@ ${YELLOW}${loginNudge.trim()}${NC}
|
|
|
18240
18536
|
const viewUrl = response.view_url ?? "";
|
|
18241
18537
|
if (viewUrl) userSummary += ` Report: ${viewUrl}`;
|
|
18242
18538
|
if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
|
|
18243
|
-
userSummary += loginNudge;
|
|
18539
|
+
userSummary += loginNudge + grantNudge;
|
|
18244
18540
|
printJsonCompact(
|
|
18245
18541
|
buildHookOutput("PASS", userSummary, agentContextFor(response))
|
|
18246
18542
|
);
|
|
@@ -18254,7 +18550,7 @@ ${YELLOW}${loginNudge.trim()}${NC}
|
|
|
18254
18550
|
const viewUrl = response.view_url ?? "";
|
|
18255
18551
|
if (viewUrl) userSummary += ` Report: ${viewUrl}`;
|
|
18256
18552
|
if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
|
|
18257
|
-
userSummary += loginNudge;
|
|
18553
|
+
userSummary += loginNudge + grantNudge;
|
|
18258
18554
|
printJsonCompact(
|
|
18259
18555
|
buildHookOutput("WARN", userSummary, agentContextFor(response))
|
|
18260
18556
|
);
|
|
@@ -18263,7 +18559,7 @@ ${YELLOW}${loginNudge.trim()}${NC}
|
|
|
18263
18559
|
}
|
|
18264
18560
|
default: {
|
|
18265
18561
|
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;
|
|
18562
|
+
const msg = (autoSeedNotice ? `${autoSeedNotice} Verity: unrecognised verdict \u2014 treating as WARN` : "Verity: unrecognised verdict \u2014 treating as WARN") + loginNudge + grantNudge;
|
|
18267
18563
|
process.stderr.write(
|
|
18268
18564
|
`Verity: server returned an unrecognised gate_decision (${raw}). Rendering WARN rather than PASS. Update the CLI: npm i -g @codacy/verity-cli
|
|
18269
18565
|
`
|
|
@@ -18430,9 +18726,9 @@ async function runReview(opts, globals) {
|
|
|
18430
18726
|
|
|
18431
18727
|
// src/commands/guard.ts
|
|
18432
18728
|
var import_node_fs27 = require("node:fs");
|
|
18433
|
-
var
|
|
18729
|
+
var import_node_path21 = require("node:path");
|
|
18434
18730
|
var GUARD_BLOCK_CAP = 2;
|
|
18435
|
-
var GUARD_ITER_FILE = (0,
|
|
18731
|
+
var GUARD_ITER_FILE = (0, import_node_path21.join)(VERITY_DIR, ".guard-iteration");
|
|
18436
18732
|
function readPreToolUseStdin() {
|
|
18437
18733
|
const empty = { command: "", cwd: null, sessionId: null };
|
|
18438
18734
|
return new Promise((resolve2) => {
|
|
@@ -18674,9 +18970,10 @@ async function runGuard(opts, globals) {
|
|
|
18674
18970
|
cmd: "guard"
|
|
18675
18971
|
});
|
|
18676
18972
|
if (!result.ok) {
|
|
18973
|
+
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." : "";
|
|
18677
18974
|
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
|
|
18975
|
+
`\u26A0 Verity ${moment}: ${authRemedy ? "not authorized" : "service offline"} \u2014 ${verb}ed WITHOUT review${authRemedy}`,
|
|
18976
|
+
`Verity ${moment}: ${authRemedy ? "not authorized" : "service unavailable"} (${result.error}); the ${verb} was allowed WITHOUT a Verity review.${authRemedy}`
|
|
18680
18977
|
);
|
|
18681
18978
|
}
|
|
18682
18979
|
if (opts.json) process.stderr.write(JSON.stringify(result.data) + "\n");
|
|
@@ -18748,14 +19045,14 @@ function writeBlockMessage(moment, response) {
|
|
|
18748
19045
|
// src/commands/init.ts
|
|
18749
19046
|
var import_node_fs29 = require("node:fs");
|
|
18750
19047
|
var import_promises13 = require("node:fs/promises");
|
|
18751
|
-
var
|
|
18752
|
-
var
|
|
19048
|
+
var import_node_path23 = require("node:path");
|
|
19049
|
+
var import_node_child_process10 = require("node:child_process");
|
|
18753
19050
|
var readline2 = __toESM(require("node:readline/promises"));
|
|
18754
19051
|
|
|
18755
19052
|
// src/commands/migrate.ts
|
|
18756
19053
|
var import_node_fs28 = require("node:fs");
|
|
18757
|
-
var
|
|
18758
|
-
var
|
|
19054
|
+
var import_node_path22 = require("node:path");
|
|
19055
|
+
var import_node_child_process9 = require("node:child_process");
|
|
18759
19056
|
|
|
18760
19057
|
// src/lib/telemetry.ts
|
|
18761
19058
|
var import_promises12 = require("node:fs/promises");
|
|
@@ -18850,11 +19147,11 @@ async function uninstallTelemetry() {
|
|
|
18850
19147
|
// src/commands/migrate.ts
|
|
18851
19148
|
var LEGACY_NPM_PACKAGE = "@codacy/gate-cli";
|
|
18852
19149
|
function defaultNpmRemover(pkg) {
|
|
18853
|
-
(0,
|
|
19150
|
+
(0, import_node_child_process9.execSync)(`npm rm -g ${pkg}`, { stdio: "pipe", timeout: 12e4 });
|
|
18854
19151
|
}
|
|
18855
19152
|
function isGitTracked(cwd, relPath) {
|
|
18856
19153
|
try {
|
|
18857
|
-
(0,
|
|
19154
|
+
(0, import_node_child_process9.execSync)(`git ls-files --error-unmatch ${relPath}`, { cwd, stdio: "pipe" });
|
|
18858
19155
|
return true;
|
|
18859
19156
|
} catch {
|
|
18860
19157
|
return false;
|
|
@@ -18862,7 +19159,7 @@ function isGitTracked(cwd, relPath) {
|
|
|
18862
19159
|
}
|
|
18863
19160
|
function isGitRepo(cwd) {
|
|
18864
19161
|
try {
|
|
18865
|
-
(0,
|
|
19162
|
+
(0, import_node_child_process9.execSync)("git rev-parse --is-inside-work-tree", { cwd, stdio: "pipe" });
|
|
18866
19163
|
return true;
|
|
18867
19164
|
} catch {
|
|
18868
19165
|
return false;
|
|
@@ -18883,8 +19180,8 @@ async function runMigration(opts = {}) {
|
|
|
18883
19180
|
return { actions, migrated: actions.length > 0 };
|
|
18884
19181
|
}
|
|
18885
19182
|
function migrateProjectDir(root, actions) {
|
|
18886
|
-
const gateDir = (0,
|
|
18887
|
-
const verityDir = (0,
|
|
19183
|
+
const gateDir = (0, import_node_path22.join)(root, ".gate");
|
|
19184
|
+
const verityDir = (0, import_node_path22.join)(root, ".verity");
|
|
18888
19185
|
if ((0, import_node_fs28.existsSync)(gateDir) && !(0, import_node_fs28.existsSync)(verityDir)) {
|
|
18889
19186
|
return migrateProjectDirRename(root, gateDir, verityDir, actions);
|
|
18890
19187
|
}
|
|
@@ -18902,7 +19199,7 @@ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
|
|
|
18902
19199
|
);
|
|
18903
19200
|
}
|
|
18904
19201
|
try {
|
|
18905
|
-
(0,
|
|
19202
|
+
(0, import_node_child_process9.execSync)("git mv .gate .verity", { cwd: root, stdio: "pipe" });
|
|
18906
19203
|
actions.push("Moved .gate/ \u2192 .verity/ (git mv, staged)");
|
|
18907
19204
|
moved = true;
|
|
18908
19205
|
} catch {
|
|
@@ -18938,11 +19235,11 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
|
|
|
18938
19235
|
}
|
|
18939
19236
|
function migrateGlobalCredentials(home, actions) {
|
|
18940
19237
|
if (!home) return;
|
|
18941
|
-
const gateCreds = (0,
|
|
18942
|
-
const verityCreds = (0,
|
|
19238
|
+
const gateCreds = (0, import_node_path22.join)(home, ".gate", "credentials");
|
|
19239
|
+
const verityCreds = (0, import_node_path22.join)(home, ".verity", "credentials");
|
|
18943
19240
|
if (!(0, import_node_fs28.existsSync)(gateCreds)) return;
|
|
18944
19241
|
if (!(0, import_node_fs28.existsSync)(verityCreds)) {
|
|
18945
|
-
(0, import_node_fs28.mkdirSync)((0,
|
|
19242
|
+
(0, import_node_fs28.mkdirSync)((0, import_node_path22.join)(home, ".verity"), { recursive: true });
|
|
18946
19243
|
moveFile(gateCreds, verityCreds);
|
|
18947
19244
|
actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
|
|
18948
19245
|
return;
|
|
@@ -18964,7 +19261,7 @@ async function migrateLegacyHooks(root, actions) {
|
|
|
18964
19261
|
}
|
|
18965
19262
|
}
|
|
18966
19263
|
async function migrateClaudeMd(root, actions) {
|
|
18967
|
-
const claudeMd = (0,
|
|
19264
|
+
const claudeMd = (0, import_node_path22.join)(root, "CLAUDE.md");
|
|
18968
19265
|
const hadLegacyBlock = (0, import_node_fs28.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
|
|
18969
19266
|
if (!hadLegacyBlock) return;
|
|
18970
19267
|
try {
|
|
@@ -18975,13 +19272,13 @@ async function migrateClaudeMd(root, actions) {
|
|
|
18975
19272
|
}
|
|
18976
19273
|
}
|
|
18977
19274
|
function migrateStandardFile(root, actions) {
|
|
18978
|
-
const gateMd = (0,
|
|
18979
|
-
const verityMd = (0,
|
|
19275
|
+
const gateMd = (0, import_node_path22.join)(root, "GATE.md");
|
|
19276
|
+
const verityMd = (0, import_node_path22.join)(root, "VERITY.md");
|
|
18980
19277
|
if (!(0, import_node_fs28.existsSync)(gateMd) || (0, import_node_fs28.existsSync)(verityMd)) return;
|
|
18981
19278
|
let moved = false;
|
|
18982
19279
|
if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
|
|
18983
19280
|
try {
|
|
18984
|
-
(0,
|
|
19281
|
+
(0, import_node_child_process9.execSync)("git mv GATE.md VERITY.md", { cwd: root, stdio: "pipe" });
|
|
18985
19282
|
moved = true;
|
|
18986
19283
|
} catch {
|
|
18987
19284
|
}
|
|
@@ -18993,7 +19290,7 @@ function migrateStandardFile(root, actions) {
|
|
|
18993
19290
|
actions.push("Renamed GATE.md \u2192 VERITY.md");
|
|
18994
19291
|
}
|
|
18995
19292
|
async function migrateTelemetryHeaders(root, actions) {
|
|
18996
|
-
const file = (0,
|
|
19293
|
+
const file = (0, import_node_path22.join)(root, ".claude", "settings.local.json");
|
|
18997
19294
|
if (!(0, import_node_fs28.existsSync)(file)) return;
|
|
18998
19295
|
let settings;
|
|
18999
19296
|
try {
|
|
@@ -19056,7 +19353,7 @@ function readFileSyncSafe(path) {
|
|
|
19056
19353
|
}
|
|
19057
19354
|
function hasStagedChanges(root) {
|
|
19058
19355
|
try {
|
|
19059
|
-
(0,
|
|
19356
|
+
(0, import_node_child_process9.execSync)("git diff --cached --quiet", { cwd: root, stdio: "pipe" });
|
|
19060
19357
|
return false;
|
|
19061
19358
|
} catch {
|
|
19062
19359
|
return true;
|
|
@@ -19083,15 +19380,15 @@ function moveFile(from, to) {
|
|
|
19083
19380
|
function carryLegacyContents(gateDir, verityDir) {
|
|
19084
19381
|
let copied = 0;
|
|
19085
19382
|
const walk = (relDir) => {
|
|
19086
|
-
const srcDir = (0,
|
|
19383
|
+
const srcDir = (0, import_node_path22.join)(gateDir, relDir);
|
|
19087
19384
|
for (const entry of (0, import_node_fs28.readdirSync)(srcDir)) {
|
|
19088
|
-
const rel = relDir ? (0,
|
|
19089
|
-
const src = (0,
|
|
19090
|
-
const dest = (0,
|
|
19385
|
+
const rel = relDir ? (0, import_node_path22.join)(relDir, entry) : entry;
|
|
19386
|
+
const src = (0, import_node_path22.join)(gateDir, rel);
|
|
19387
|
+
const dest = (0, import_node_path22.join)(verityDir, rel);
|
|
19091
19388
|
if ((0, import_node_fs28.statSync)(src).isDirectory()) {
|
|
19092
19389
|
walk(rel);
|
|
19093
19390
|
} else if (!(0, import_node_fs28.existsSync)(dest)) {
|
|
19094
|
-
(0, import_node_fs28.mkdirSync)((0,
|
|
19391
|
+
(0, import_node_fs28.mkdirSync)((0, import_node_path22.dirname)(dest), { recursive: true });
|
|
19095
19392
|
(0, import_node_fs28.cpSync)(src, dest);
|
|
19096
19393
|
copied++;
|
|
19097
19394
|
}
|
|
@@ -19101,22 +19398,22 @@ function carryLegacyContents(gateDir, verityDir) {
|
|
|
19101
19398
|
return copied;
|
|
19102
19399
|
}
|
|
19103
19400
|
async function needsMigration(root = repoRoot()) {
|
|
19104
|
-
const gateDir = (0,
|
|
19105
|
-
const verityDir = (0,
|
|
19401
|
+
const gateDir = (0, import_node_path22.join)(root, ".gate");
|
|
19402
|
+
const verityDir = (0, import_node_path22.join)(root, ".verity");
|
|
19106
19403
|
if ((0, import_node_fs28.existsSync)(gateDir) && !(0, import_node_fs28.existsSync)(verityDir)) return true;
|
|
19107
19404
|
if ((0, import_node_fs28.existsSync)(gateDir) && (0, import_node_fs28.existsSync)(verityDir)) {
|
|
19108
|
-
if ((0, import_node_fs28.existsSync)((0,
|
|
19405
|
+
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
19406
|
return true;
|
|
19110
19407
|
}
|
|
19111
|
-
if ((0, import_node_fs28.existsSync)((0,
|
|
19408
|
+
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
19409
|
return true;
|
|
19113
19410
|
}
|
|
19114
19411
|
}
|
|
19115
|
-
const claudeMd = (0,
|
|
19412
|
+
const claudeMd = (0, import_node_path22.join)(root, "CLAUDE.md");
|
|
19116
19413
|
if ((0, import_node_fs28.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
|
|
19117
19414
|
return true;
|
|
19118
19415
|
}
|
|
19119
|
-
if ((0, import_node_fs28.existsSync)((0,
|
|
19416
|
+
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
19417
|
return true;
|
|
19121
19418
|
}
|
|
19122
19419
|
if (await hasLegacyHooksAt(root)) return true;
|
|
@@ -19152,12 +19449,25 @@ async function promptYes(question) {
|
|
|
19152
19449
|
rl.close();
|
|
19153
19450
|
}
|
|
19154
19451
|
}
|
|
19155
|
-
async function confirmExistingLogin(serviceUrl, opts) {
|
|
19452
|
+
async function confirmExistingLogin(serviceUrl, remote, opts) {
|
|
19156
19453
|
const existing = await resolveToken(opts.token);
|
|
19157
19454
|
if (!existing.ok) return "drive-login";
|
|
19158
19455
|
const who = await whoami(existing.data.token, serviceUrl, opts.verbose);
|
|
19159
19456
|
if (who.ok && who.data.logged_in) {
|
|
19160
|
-
|
|
19457
|
+
const identity = who.data.email ?? `user #${who.data.user_id}`;
|
|
19458
|
+
const covered = who.data.grant_status != null;
|
|
19459
|
+
if (!remote) {
|
|
19460
|
+
printInfo(`Logged in as ${identity} \u2713`);
|
|
19461
|
+
printInfo(" This directory has no git remote, so there is no project here to sync to.");
|
|
19462
|
+
} else if (covered) {
|
|
19463
|
+
printInfo(`Logged in as ${identity} \u2713 \u2014 runs & memory sync to Verity.`);
|
|
19464
|
+
} else {
|
|
19465
|
+
printInfo(`Logged in as ${identity} \u2713`);
|
|
19466
|
+
printWarn(" This repository is NOT covered by your Verity access grants \u2014 nothing will sync.");
|
|
19467
|
+
printInfo(' Grant the Verity GitHub App access to it, then run "verity login" to refresh your grants.');
|
|
19468
|
+
}
|
|
19469
|
+
const nudge = reverifyNudge(who.data);
|
|
19470
|
+
if (nudge) printWarn(` ${nudge}`);
|
|
19161
19471
|
return "handled";
|
|
19162
19472
|
}
|
|
19163
19473
|
if (!who.ok) {
|
|
@@ -19186,18 +19496,18 @@ async function runOptionalAuth(resolution, opts = {}) {
|
|
|
19186
19496
|
printInfo(" Log in below to re-register this project and repair ~/.verity/credentials.");
|
|
19187
19497
|
}
|
|
19188
19498
|
}
|
|
19189
|
-
if (!healed) {
|
|
19190
|
-
const state = await confirmExistingLogin(serviceUrl, opts);
|
|
19191
|
-
if (state === "handled") return;
|
|
19192
|
-
}
|
|
19193
19499
|
let remote = "";
|
|
19194
19500
|
try {
|
|
19195
|
-
remote = (0,
|
|
19501
|
+
remote = (0, import_node_child_process10.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
19196
19502
|
} catch {
|
|
19197
19503
|
}
|
|
19504
|
+
if (!healed) {
|
|
19505
|
+
const state = await confirmExistingLogin(serviceUrl, remote, opts);
|
|
19506
|
+
if (state === "handled") return;
|
|
19507
|
+
}
|
|
19198
19508
|
const localOnlyNote = () => {
|
|
19199
19509
|
printInfo("Verity runs in local-only mode: the gate still runs and shows static findings, but nothing uploads.");
|
|
19200
|
-
printInfo(' Authenticate anytime: run "verity
|
|
19510
|
+
printInfo(' Authenticate anytime: run "verity login" (one login covers every repo you can write to).');
|
|
19201
19511
|
};
|
|
19202
19512
|
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
19203
19513
|
console.log("");
|
|
@@ -19223,7 +19533,7 @@ async function runOptionalAuth(resolution, opts = {}) {
|
|
|
19223
19533
|
localOnlyNote();
|
|
19224
19534
|
return;
|
|
19225
19535
|
}
|
|
19226
|
-
const projectName = parseRemote(remote)?.repo ?? (0,
|
|
19536
|
+
const projectName = parseRemote(remote)?.repo ?? (0, import_node_path23.basename)(process.cwd());
|
|
19227
19537
|
printInfo("Authenticating with GitHub\u2026");
|
|
19228
19538
|
const result = await registerProject({ projectName, remote, serviceUrl, verbose: opts.verbose });
|
|
19229
19539
|
if (result.ok) {
|
|
@@ -19236,15 +19546,15 @@ async function runOptionalAuth(resolution, opts = {}) {
|
|
|
19236
19546
|
}
|
|
19237
19547
|
function resolveDataDir() {
|
|
19238
19548
|
const candidates = [
|
|
19239
|
-
(0,
|
|
19549
|
+
(0, import_node_path23.join)(__dirname, "..", "data"),
|
|
19240
19550
|
// installed: node_modules/@codacy/verity-cli/data
|
|
19241
|
-
(0,
|
|
19551
|
+
(0, import_node_path23.join)(__dirname, "..", "..", "data"),
|
|
19242
19552
|
// edge case: nested resolution
|
|
19243
|
-
(0,
|
|
19553
|
+
(0, import_node_path23.join)(process.cwd(), "cli", "data")
|
|
19244
19554
|
// local dev: running from repo root
|
|
19245
19555
|
];
|
|
19246
19556
|
for (const candidate of candidates) {
|
|
19247
|
-
if ((0, import_node_fs29.existsSync)((0,
|
|
19557
|
+
if ((0, import_node_fs29.existsSync)((0, import_node_path23.join)(candidate, "skills"))) {
|
|
19248
19558
|
return candidate;
|
|
19249
19559
|
}
|
|
19250
19560
|
}
|
|
@@ -19288,30 +19598,30 @@ function registerInitCommand(program2) {
|
|
|
19288
19598
|
}
|
|
19289
19599
|
printInfo(` Node.js ${nodeVersion} \u2713`);
|
|
19290
19600
|
try {
|
|
19291
|
-
const gitVersion = (0,
|
|
19601
|
+
const gitVersion = (0, import_node_child_process10.execSync)("git --version", { encoding: "utf-8" }).trim();
|
|
19292
19602
|
printInfo(` ${gitVersion} \u2713`);
|
|
19293
19603
|
} catch {
|
|
19294
19604
|
printError("git is required but not installed. Install from https://git-scm.com");
|
|
19295
19605
|
process.exit(1);
|
|
19296
19606
|
}
|
|
19297
19607
|
try {
|
|
19298
|
-
(0,
|
|
19608
|
+
(0, import_node_child_process10.execSync)("which claude", { encoding: "utf-8" });
|
|
19299
19609
|
printInfo(" Claude Code \u2713");
|
|
19300
19610
|
} catch {
|
|
19301
19611
|
printWarn(" Claude Code not found \u2014 hooks will be configured but need Claude Code to run.");
|
|
19302
19612
|
}
|
|
19303
19613
|
try {
|
|
19304
|
-
(0,
|
|
19614
|
+
(0, import_node_child_process10.execSync)("which codacy-analysis", { encoding: "utf-8", stdio: "pipe" });
|
|
19305
19615
|
printInfo(" @codacy/analysis-cli \u2713");
|
|
19306
19616
|
} catch {
|
|
19307
19617
|
printInfo(" Installing @codacy/analysis-cli...");
|
|
19308
19618
|
try {
|
|
19309
|
-
(0,
|
|
19619
|
+
(0, import_node_child_process10.execSync)("npm install -g @codacy/analysis-cli", { encoding: "utf-8", stdio: "pipe", timeout: 12e4 });
|
|
19310
19620
|
printInfo(" @codacy/analysis-cli installed \u2713");
|
|
19311
19621
|
} catch {
|
|
19312
19622
|
try {
|
|
19313
19623
|
printWarn(" Retrying with sudo...");
|
|
19314
|
-
(0,
|
|
19624
|
+
(0, import_node_child_process10.execSync)("sudo npm install -g @codacy/analysis-cli", { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
|
|
19315
19625
|
printInfo(" @codacy/analysis-cli installed \u2713");
|
|
19316
19626
|
} catch {
|
|
19317
19627
|
printWarn(" Could not install @codacy/analysis-cli automatically.");
|
|
@@ -19323,20 +19633,20 @@ function registerInitCommand(program2) {
|
|
|
19323
19633
|
console.log("");
|
|
19324
19634
|
printInfo("Installing skills...");
|
|
19325
19635
|
const dataDir = resolveDataDir();
|
|
19326
|
-
const skillsSource = (0,
|
|
19636
|
+
const skillsSource = (0, import_node_path23.join)(dataDir, "skills");
|
|
19327
19637
|
const skillsDest = ".claude/skills";
|
|
19328
19638
|
const skills = ["verity-setup", "verity-analyze", "verity-status", "verity-feedback", "verity-learn", "verity-memory", "verity-insights", "verity-reflect"];
|
|
19329
19639
|
let skillsInstalled = 0;
|
|
19330
19640
|
for (const skill of skills) {
|
|
19331
|
-
const src = (0,
|
|
19332
|
-
const dest = (0,
|
|
19641
|
+
const src = (0, import_node_path23.join)(skillsSource, skill);
|
|
19642
|
+
const dest = (0, import_node_path23.join)(skillsDest, skill);
|
|
19333
19643
|
if (!(0, import_node_fs29.existsSync)(src)) {
|
|
19334
19644
|
printWarn(` Skill data not found: ${skill}`);
|
|
19335
19645
|
continue;
|
|
19336
19646
|
}
|
|
19337
19647
|
if ((0, import_node_fs29.existsSync)(dest) && !force) {
|
|
19338
|
-
const srcSkill = (0,
|
|
19339
|
-
const destSkill = (0,
|
|
19648
|
+
const srcSkill = (0, import_node_path23.join)(src, "SKILL.md");
|
|
19649
|
+
const destSkill = (0, import_node_path23.join)(dest, "SKILL.md");
|
|
19340
19650
|
if ((0, import_node_fs29.existsSync)(destSkill)) {
|
|
19341
19651
|
try {
|
|
19342
19652
|
const srcContent = await (0, import_promises13.readFile)(srcSkill, "utf-8");
|
|
@@ -19374,7 +19684,7 @@ function registerInitCommand(program2) {
|
|
|
19374
19684
|
} catch (err) {
|
|
19375
19685
|
printWarn(` Could not update CLAUDE.md: ${err.message}`);
|
|
19376
19686
|
}
|
|
19377
|
-
const globalVerityDir = (0,
|
|
19687
|
+
const globalVerityDir = (0, import_node_path23.join)(process.env.HOME ?? "", ".verity");
|
|
19378
19688
|
await (0, import_promises13.mkdir)(globalVerityDir, { recursive: true });
|
|
19379
19689
|
console.log("");
|
|
19380
19690
|
try {
|
|
@@ -19403,14 +19713,14 @@ function registerInitCommand(program2) {
|
|
|
19403
19713
|
console.log(" .verity/memory/ \u2014 knowledge base (8 domains, commit to git)");
|
|
19404
19714
|
console.log("");
|
|
19405
19715
|
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
|
|
19716
|
+
console.log(' (Not authenticated? Verity runs in local-only mode until you run "verity login".)');
|
|
19407
19717
|
console.log("");
|
|
19408
19718
|
});
|
|
19409
19719
|
}
|
|
19410
19720
|
|
|
19411
19721
|
// src/commands/uninstall.ts
|
|
19412
19722
|
var import_node_fs30 = require("node:fs");
|
|
19413
|
-
var
|
|
19723
|
+
var import_node_path24 = require("node:path");
|
|
19414
19724
|
var SKILL_NAMES = [
|
|
19415
19725
|
"verity-setup",
|
|
19416
19726
|
"verity-analyze",
|
|
@@ -19429,7 +19739,7 @@ function registerUninstallCommand(program2) {
|
|
|
19429
19739
|
const actions = [];
|
|
19430
19740
|
const skillsRoot = projectPath(".claude/skills");
|
|
19431
19741
|
for (const name of SKILL_NAMES) {
|
|
19432
|
-
const dir = (0,
|
|
19742
|
+
const dir = (0, import_node_path24.join)(skillsRoot, name);
|
|
19433
19743
|
if ((0, import_node_fs30.existsSync)(dir)) {
|
|
19434
19744
|
actions.push({
|
|
19435
19745
|
label: `Remove .claude/skills/${name}/`,
|
|
@@ -19475,7 +19785,7 @@ function registerUninstallCommand(program2) {
|
|
|
19475
19785
|
}
|
|
19476
19786
|
});
|
|
19477
19787
|
const home = process.env.HOME ?? "";
|
|
19478
|
-
const globalVerityDir = (0,
|
|
19788
|
+
const globalVerityDir = (0, import_node_path24.join)(home, ".verity");
|
|
19479
19789
|
if (purgeGlobal && (0, import_node_fs30.existsSync)(globalVerityDir)) {
|
|
19480
19790
|
actions.push({
|
|
19481
19791
|
label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
|
|
@@ -19674,7 +19984,7 @@ function registerTaskCommands(program2) {
|
|
|
19674
19984
|
|
|
19675
19985
|
// src/commands/reset.ts
|
|
19676
19986
|
var import_node_fs31 = require("node:fs");
|
|
19677
|
-
var
|
|
19987
|
+
var import_node_path25 = require("node:path");
|
|
19678
19988
|
function registerResetCommand(program2) {
|
|
19679
19989
|
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
19990
|
const globals = program2.opts();
|
|
@@ -19715,7 +20025,7 @@ function registerResetCommand(program2) {
|
|
|
19715
20025
|
for (const entry of (0, import_node_fs31.readdirSync)(cacheDir)) {
|
|
19716
20026
|
if (entry.startsWith("pending-")) {
|
|
19717
20027
|
try {
|
|
19718
|
-
(0, import_node_fs31.unlinkSync)((0,
|
|
20028
|
+
(0, import_node_fs31.unlinkSync)((0, import_node_path25.join)(cacheDir, entry));
|
|
19719
20029
|
purged++;
|
|
19720
20030
|
} catch {
|
|
19721
20031
|
}
|
|
@@ -19742,7 +20052,7 @@ function registerResetCommand(program2) {
|
|
|
19742
20052
|
if ((0, import_node_fs31.existsSync)(logsDir)) {
|
|
19743
20053
|
for (const entry of (0, import_node_fs31.readdirSync)(logsDir)) {
|
|
19744
20054
|
try {
|
|
19745
|
-
(0, import_node_fs31.unlinkSync)((0,
|
|
20055
|
+
(0, import_node_fs31.unlinkSync)((0, import_node_path25.join)(logsDir, entry));
|
|
19746
20056
|
} catch {
|
|
19747
20057
|
}
|
|
19748
20058
|
}
|
|
@@ -20010,7 +20320,11 @@ function registerTelemetryCommands(program2) {
|
|
|
20010
20320
|
const globals = program2.opts();
|
|
20011
20321
|
const tokenResult = await resolveToken(globals.token);
|
|
20012
20322
|
if (tokenResult.ok) {
|
|
20013
|
-
|
|
20323
|
+
const remote = requestRemote();
|
|
20324
|
+
printJsonCompact({
|
|
20325
|
+
Authorization: `Bearer ${tokenResult.data.token}`,
|
|
20326
|
+
...remote ? { "X-Verity-Remote": remote } : {}
|
|
20327
|
+
});
|
|
20014
20328
|
} else {
|
|
20015
20329
|
printJsonCompact({});
|
|
20016
20330
|
}
|
|
@@ -20040,7 +20354,7 @@ function registerTelemetryCommands(program2) {
|
|
|
20040
20354
|
}
|
|
20041
20355
|
|
|
20042
20356
|
// src/cli.ts
|
|
20043
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.28.1-experimental.
|
|
20357
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.28.1-experimental.dbd87b1").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
20358
|
try {
|
|
20045
20359
|
await foldLegacyLocalCredential();
|
|
20046
20360
|
} catch {
|
|
@@ -20048,6 +20362,7 @@ program.name("verity").description("CLI for Verity quality gate service").versio
|
|
|
20048
20362
|
});
|
|
20049
20363
|
registerAuthCommands(program);
|
|
20050
20364
|
registerLoginCommand(program);
|
|
20365
|
+
registerTokenCommand(program);
|
|
20051
20366
|
registerHooksCommands(program);
|
|
20052
20367
|
registerIntentCommands(program);
|
|
20053
20368
|
registerLifecycleCommands(program);
|