@codacy/verity-cli 0.25.0 → 0.26.0-experimental.a2dc844
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/bin/verity.js +883 -580
- package/data/skills/verity-analyze/SKILL.md +7 -1
- package/data/skills/verity-setup/SKILL.md +49 -10
- package/package.json +1 -1
package/bin/verity.js
CHANGED
|
@@ -10325,9 +10325,7 @@ var {
|
|
|
10325
10325
|
} = import_index.default;
|
|
10326
10326
|
|
|
10327
10327
|
// src/commands/auth.ts
|
|
10328
|
-
var
|
|
10329
|
-
var import_node_child_process3 = require("node:child_process");
|
|
10330
|
-
var import_node_path2 = require("node:path");
|
|
10328
|
+
var import_node_child_process4 = require("node:child_process");
|
|
10331
10329
|
|
|
10332
10330
|
// src/lib/auth.ts
|
|
10333
10331
|
var import_promises = require("node:fs/promises");
|
|
@@ -10475,7 +10473,11 @@ var SECURITY_PATTERNS = [
|
|
|
10475
10473
|
/Dockerfile/
|
|
10476
10474
|
];
|
|
10477
10475
|
var PROD_SERVICE_URL = "https://ofcamwrjwrkazqvdchko.supabase.co/functions/v1";
|
|
10478
|
-
var DEFAULT_SERVICE_URL = "".length > 0 ? "" : PROD_SERVICE_URL;
|
|
10476
|
+
var DEFAULT_SERVICE_URL = "https://yyfaqvcgslcrzvrbvqik.supabase.co/functions/v1".length > 0 ? "https://yyfaqvcgslcrzvrbvqik.supabase.co/functions/v1" : PROD_SERVICE_URL;
|
|
10477
|
+
var GITHUB_CLIENT_ID = "Ov23liBpj42KMTtUtN10";
|
|
10478
|
+
var GITHUB_DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
10479
|
+
var GITHUB_ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
10480
|
+
var GITHUB_OAUTH_SCOPES = "repo user:email";
|
|
10479
10481
|
|
|
10480
10482
|
// src/lib/auth.ts
|
|
10481
10483
|
async function resolveToken(flagToken) {
|
|
@@ -10641,7 +10643,8 @@ async function apiRequest(options) {
|
|
|
10641
10643
|
timeout = 9e4,
|
|
10642
10644
|
cmd = "unknown",
|
|
10643
10645
|
retry = false,
|
|
10644
|
-
encodeBody = false
|
|
10646
|
+
encodeBody = false,
|
|
10647
|
+
extraHeaders
|
|
10645
10648
|
} = options;
|
|
10646
10649
|
const url = `${serviceUrl}${path}`;
|
|
10647
10650
|
const headers = {
|
|
@@ -10650,6 +10653,9 @@ async function apiRequest(options) {
|
|
|
10650
10653
|
if (token) {
|
|
10651
10654
|
headers["Authorization"] = `Bearer ${token}`;
|
|
10652
10655
|
}
|
|
10656
|
+
if (extraHeaders) {
|
|
10657
|
+
Object.assign(headers, extraHeaders);
|
|
10658
|
+
}
|
|
10653
10659
|
const testMockScenario = process.env.VERITY_TEST_MOCK_SCENARIO;
|
|
10654
10660
|
if (testMockScenario) {
|
|
10655
10661
|
headers["X-Verity-Mock-Scenario"] = testMockScenario;
|
|
@@ -10740,6 +10746,386 @@ function analyzeRequest(options) {
|
|
|
10740
10746
|
});
|
|
10741
10747
|
}
|
|
10742
10748
|
|
|
10749
|
+
// src/lib/register.ts
|
|
10750
|
+
var import_promises3 = require("node:fs/promises");
|
|
10751
|
+
var import_node_path3 = require("node:path");
|
|
10752
|
+
|
|
10753
|
+
// src/lib/provider-auth.ts
|
|
10754
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
10755
|
+
var form = (fields) => new URLSearchParams(fields).toString();
|
|
10756
|
+
async function githubDeviceFlow() {
|
|
10757
|
+
const override = process.env.VERITY_PROVIDER_TOKEN;
|
|
10758
|
+
if (override) return { ok: true, data: override };
|
|
10759
|
+
let dc;
|
|
10760
|
+
try {
|
|
10761
|
+
const res = await fetch(GITHUB_DEVICE_CODE_URL, {
|
|
10762
|
+
method: "POST",
|
|
10763
|
+
headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
|
|
10764
|
+
body: form({ client_id: GITHUB_CLIENT_ID, scope: GITHUB_OAUTH_SCOPES })
|
|
10765
|
+
});
|
|
10766
|
+
if (!res.ok) {
|
|
10767
|
+
return { ok: false, error: `GitHub device-code request failed (HTTP ${res.status})` };
|
|
10768
|
+
}
|
|
10769
|
+
dc = await res.json();
|
|
10770
|
+
} catch (err) {
|
|
10771
|
+
return { ok: false, error: `Network error contacting GitHub: ${err.message}` };
|
|
10772
|
+
}
|
|
10773
|
+
if (!dc.device_code || !dc.user_code) {
|
|
10774
|
+
return {
|
|
10775
|
+
ok: false,
|
|
10776
|
+
error: "GitHub did not return a device code (is Device Flow enabled on the OAuth app?)"
|
|
10777
|
+
};
|
|
10778
|
+
}
|
|
10779
|
+
printInfo("");
|
|
10780
|
+
printInfo(`To authorize Verity, open: ${dc.verification_uri}`);
|
|
10781
|
+
printInfo(`And enter the code: ${dc.user_code}`);
|
|
10782
|
+
printInfo("Waiting for authorization\u2026");
|
|
10783
|
+
const deadline = Date.now() + (dc.expires_in || 900) * 1e3;
|
|
10784
|
+
let interval = dc.interval || 5;
|
|
10785
|
+
while (Date.now() < deadline) {
|
|
10786
|
+
await sleep(interval * 1e3);
|
|
10787
|
+
let data;
|
|
10788
|
+
try {
|
|
10789
|
+
const res = await fetch(GITHUB_ACCESS_TOKEN_URL, {
|
|
10790
|
+
method: "POST",
|
|
10791
|
+
headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
|
|
10792
|
+
body: form({
|
|
10793
|
+
client_id: GITHUB_CLIENT_ID,
|
|
10794
|
+
device_code: dc.device_code,
|
|
10795
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
10796
|
+
})
|
|
10797
|
+
});
|
|
10798
|
+
data = await res.json().catch(() => ({}));
|
|
10799
|
+
} catch {
|
|
10800
|
+
continue;
|
|
10801
|
+
}
|
|
10802
|
+
if (data.access_token) return { ok: true, data: data.access_token };
|
|
10803
|
+
switch (data.error) {
|
|
10804
|
+
case "authorization_pending":
|
|
10805
|
+
break;
|
|
10806
|
+
case "slow_down":
|
|
10807
|
+
interval += 5;
|
|
10808
|
+
break;
|
|
10809
|
+
case "access_denied":
|
|
10810
|
+
return { ok: false, error: "Authorization was denied on GitHub." };
|
|
10811
|
+
case "expired_token":
|
|
10812
|
+
return { ok: false, error: "The authorization code expired. Re-run register." };
|
|
10813
|
+
default:
|
|
10814
|
+
if (data.error) return { ok: false, error: `GitHub auth error: ${data.error}` };
|
|
10815
|
+
}
|
|
10816
|
+
}
|
|
10817
|
+
return { ok: false, error: "Timed out waiting for GitHub authorization." };
|
|
10818
|
+
}
|
|
10819
|
+
|
|
10820
|
+
// src/lib/git.ts
|
|
10821
|
+
var import_node_child_process3 = require("node:child_process");
|
|
10822
|
+
var import_node_fs2 = require("node:fs");
|
|
10823
|
+
var import_node_path2 = require("node:path");
|
|
10824
|
+
function resolveFile(relpath) {
|
|
10825
|
+
if ((0, import_node_fs2.existsSync)(relpath)) return relpath;
|
|
10826
|
+
if ((0, import_node_fs2.existsSync)(".claude/worktrees")) {
|
|
10827
|
+
try {
|
|
10828
|
+
const entries = (0, import_node_fs2.readdirSync)(".claude/worktrees", { withFileTypes: true });
|
|
10829
|
+
for (const entry of entries) {
|
|
10830
|
+
if (!entry.isDirectory()) continue;
|
|
10831
|
+
const candidate = (0, import_node_path2.join)(".claude/worktrees", entry.name, relpath);
|
|
10832
|
+
if ((0, import_node_fs2.existsSync)(candidate)) return candidate;
|
|
10833
|
+
}
|
|
10834
|
+
} catch {
|
|
10835
|
+
}
|
|
10836
|
+
}
|
|
10837
|
+
return null;
|
|
10838
|
+
}
|
|
10839
|
+
function execGit(cmd) {
|
|
10840
|
+
try {
|
|
10841
|
+
return (0, import_node_child_process3.execSync)(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
10842
|
+
} catch {
|
|
10843
|
+
return "";
|
|
10844
|
+
}
|
|
10845
|
+
}
|
|
10846
|
+
function splitLines(s) {
|
|
10847
|
+
return s.split("\n").filter((l) => l.length > 0);
|
|
10848
|
+
}
|
|
10849
|
+
var SHA_RE = /^[0-9a-f]{40}$/;
|
|
10850
|
+
function readBaselineSha() {
|
|
10851
|
+
if (!(0, import_node_fs2.existsSync)(BASELINE_SHA_FILE)) return null;
|
|
10852
|
+
let sha;
|
|
10853
|
+
try {
|
|
10854
|
+
sha = (0, import_node_fs2.readFileSync)(BASELINE_SHA_FILE, "utf-8").trim();
|
|
10855
|
+
} catch {
|
|
10856
|
+
return null;
|
|
10857
|
+
}
|
|
10858
|
+
if (!SHA_RE.test(sha)) return null;
|
|
10859
|
+
const reachable = execGit(`git cat-file -e ${sha}^{commit} 2>/dev/null && echo ok`) === "ok";
|
|
10860
|
+
if (!reachable) {
|
|
10861
|
+
try {
|
|
10862
|
+
(0, import_node_fs2.unlinkSync)(BASELINE_SHA_FILE);
|
|
10863
|
+
} catch {
|
|
10864
|
+
}
|
|
10865
|
+
return null;
|
|
10866
|
+
}
|
|
10867
|
+
return sha;
|
|
10868
|
+
}
|
|
10869
|
+
function writeBaselineSha(sha) {
|
|
10870
|
+
if (!SHA_RE.test(sha)) return;
|
|
10871
|
+
try {
|
|
10872
|
+
(0, import_node_fs2.mkdirSync)((0, import_node_path2.dirname)(BASELINE_SHA_FILE), { recursive: true });
|
|
10873
|
+
(0, import_node_fs2.writeFileSync)(BASELINE_SHA_FILE, sha);
|
|
10874
|
+
} catch {
|
|
10875
|
+
}
|
|
10876
|
+
}
|
|
10877
|
+
function getChangedFiles() {
|
|
10878
|
+
const sets = /* @__PURE__ */ new Set();
|
|
10879
|
+
let hasRecentCommitFiles = false;
|
|
10880
|
+
for (const f of splitLines(execGit("git diff --name-only HEAD"))) sets.add(f);
|
|
10881
|
+
for (const f of splitLines(execGit("git diff --name-only --cached"))) sets.add(f);
|
|
10882
|
+
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) sets.add(f);
|
|
10883
|
+
const baseline = readBaselineSha();
|
|
10884
|
+
if (baseline) {
|
|
10885
|
+
const committed = splitLines(execGit(`git diff --name-only ${baseline}..HEAD`));
|
|
10886
|
+
if (committed.length > 0) {
|
|
10887
|
+
hasRecentCommitFiles = true;
|
|
10888
|
+
for (const f of committed) sets.add(f);
|
|
10889
|
+
}
|
|
10890
|
+
} else {
|
|
10891
|
+
const headTimestamp = parseInt(execGit("git log -1 --format=%ct HEAD"), 10) || 0;
|
|
10892
|
+
const commitAge = Math.floor(Date.now() / 1e3) - headTimestamp;
|
|
10893
|
+
const hasUnstaged = splitLines(execGit("git diff --name-only HEAD")).length > 0;
|
|
10894
|
+
const hasStaged = splitLines(execGit("git diff --name-only --cached")).length > 0;
|
|
10895
|
+
if (commitAge < 120 && !hasUnstaged && !hasStaged) {
|
|
10896
|
+
const recentFiles = splitLines(execGit("git diff --name-only HEAD~1..HEAD"));
|
|
10897
|
+
if (recentFiles.length > 0) {
|
|
10898
|
+
hasRecentCommitFiles = true;
|
|
10899
|
+
for (const f of recentFiles) sets.add(f);
|
|
10900
|
+
}
|
|
10901
|
+
}
|
|
10902
|
+
}
|
|
10903
|
+
for (const f of getWorktreeFiles()) sets.add(f);
|
|
10904
|
+
const filtered = Array.from(sets).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
10905
|
+
return { files: filtered, hasRecentCommitFiles };
|
|
10906
|
+
}
|
|
10907
|
+
function getStagedFiles() {
|
|
10908
|
+
return splitLines(execGit("git diff --cached --name-only")).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
10909
|
+
}
|
|
10910
|
+
function getDirtyFiles() {
|
|
10911
|
+
const set = /* @__PURE__ */ new Set();
|
|
10912
|
+
for (const f of splitLines(execGit("git diff --name-only HEAD"))) set.add(f);
|
|
10913
|
+
for (const f of splitLines(execGit("git diff --name-only --cached"))) set.add(f);
|
|
10914
|
+
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
|
|
10915
|
+
return Array.from(set).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
10916
|
+
}
|
|
10917
|
+
function showContentAtRef(ref, repoRelPath) {
|
|
10918
|
+
if (!ref || ref === "no-git") return null;
|
|
10919
|
+
const normalizedPath = repoRelPath.replace(/\\/g, "/");
|
|
10920
|
+
try {
|
|
10921
|
+
return (0, import_node_child_process3.execFileSync)("git", ["show", `${ref}:${normalizedPath}`], {
|
|
10922
|
+
encoding: "utf-8",
|
|
10923
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
10924
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
10925
|
+
});
|
|
10926
|
+
} catch {
|
|
10927
|
+
return null;
|
|
10928
|
+
}
|
|
10929
|
+
}
|
|
10930
|
+
function getPushRangeFiles() {
|
|
10931
|
+
const diff = (range) => splitLines(execGit(`git diff --name-only ${range}`)).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
10932
|
+
const resolvers = [
|
|
10933
|
+
() => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{push}") ? "@{push}..HEAD" : null,
|
|
10934
|
+
() => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{upstream}") ? "@{upstream}..HEAD" : null,
|
|
10935
|
+
() => {
|
|
10936
|
+
const branch = execGit("git rev-parse --abbrev-ref HEAD");
|
|
10937
|
+
return branch && branch !== "HEAD" && execGit(`git rev-parse --verify -q origin/${branch}`) ? `origin/${branch}..HEAD` : null;
|
|
10938
|
+
}
|
|
10939
|
+
];
|
|
10940
|
+
for (const resolve of resolvers) {
|
|
10941
|
+
const range = resolve();
|
|
10942
|
+
if (range) return { files: diff(range), range };
|
|
10943
|
+
}
|
|
10944
|
+
const baseline = readBaselineSha();
|
|
10945
|
+
if (baseline) {
|
|
10946
|
+
const files = diff(`${baseline}..HEAD`);
|
|
10947
|
+
if (files.length > 0) return { files, range: `${baseline}..HEAD` };
|
|
10948
|
+
}
|
|
10949
|
+
const last = diff("HEAD~1..HEAD");
|
|
10950
|
+
return { files: last, range: last.length > 0 ? "HEAD~1..HEAD" : null };
|
|
10951
|
+
}
|
|
10952
|
+
function getPushRangeMessages() {
|
|
10953
|
+
const { range } = getPushRangeFiles();
|
|
10954
|
+
if (!range) return "";
|
|
10955
|
+
return execGit(`git log ${range} --format=%B%x00`).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
|
|
10956
|
+
}
|
|
10957
|
+
function getWorktreeFiles() {
|
|
10958
|
+
const result = [];
|
|
10959
|
+
const worktreeDir = ".claude/worktrees";
|
|
10960
|
+
if (!(0, import_node_fs2.existsSync)(worktreeDir)) return result;
|
|
10961
|
+
try {
|
|
10962
|
+
const fiveMinAgo = Date.now() - 5 * 60 * 1e3;
|
|
10963
|
+
const entries = (0, import_node_fs2.readdirSync)(worktreeDir, { withFileTypes: true });
|
|
10964
|
+
for (const entry of entries) {
|
|
10965
|
+
if (!entry.isDirectory()) continue;
|
|
10966
|
+
const wtDir = (0, import_node_path2.join)(worktreeDir, entry.name);
|
|
10967
|
+
scanDir(wtDir, wtDir, fiveMinAgo, result);
|
|
10968
|
+
}
|
|
10969
|
+
} catch {
|
|
10970
|
+
}
|
|
10971
|
+
return result;
|
|
10972
|
+
}
|
|
10973
|
+
function scanDir(baseDir, dir, minMtime, result) {
|
|
10974
|
+
try {
|
|
10975
|
+
const entries = (0, import_node_fs2.readdirSync)(dir, { withFileTypes: true });
|
|
10976
|
+
for (const entry of entries) {
|
|
10977
|
+
const fullPath = (0, import_node_path2.join)(dir, entry.name);
|
|
10978
|
+
if (entry.isDirectory()) {
|
|
10979
|
+
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
10980
|
+
scanDir(baseDir, fullPath, minMtime, result);
|
|
10981
|
+
} else if (entry.isFile()) {
|
|
10982
|
+
const ext = (0, import_node_path2.extname)(entry.name).slice(1);
|
|
10983
|
+
if (!ANALYZABLE_EXTENSIONS.has(ext)) continue;
|
|
10984
|
+
try {
|
|
10985
|
+
const stat3 = (0, import_node_fs2.statSync)(fullPath);
|
|
10986
|
+
if (stat3.mtimeMs >= minMtime) {
|
|
10987
|
+
const relPath = fullPath.slice(baseDir.length + 1);
|
|
10988
|
+
result.push(relPath);
|
|
10989
|
+
}
|
|
10990
|
+
} catch {
|
|
10991
|
+
}
|
|
10992
|
+
}
|
|
10993
|
+
}
|
|
10994
|
+
} catch {
|
|
10995
|
+
}
|
|
10996
|
+
}
|
|
10997
|
+
function filterAnalyzable(files) {
|
|
10998
|
+
return files.filter((f) => {
|
|
10999
|
+
const ext = (0, import_node_path2.extname)(f).slice(1);
|
|
11000
|
+
return ANALYZABLE_EXTENSIONS.has(ext);
|
|
11001
|
+
});
|
|
11002
|
+
}
|
|
11003
|
+
function filterReviewable(files) {
|
|
11004
|
+
return files.filter((f) => {
|
|
11005
|
+
const ext = (0, import_node_path2.extname)(f).slice(1);
|
|
11006
|
+
if (ANALYZABLE_EXTENSIONS.has(ext)) return false;
|
|
11007
|
+
if (REVIEWABLE_EXTENSIONS.has(ext)) return true;
|
|
11008
|
+
const basename3 = f.split("/").pop() ?? "";
|
|
11009
|
+
if (REVIEWABLE_FILENAMES.has(basename3)) return true;
|
|
11010
|
+
if (REVIEWABLE_PATH_PATTERNS.some((p) => p.test(f))) return true;
|
|
11011
|
+
return false;
|
|
11012
|
+
});
|
|
11013
|
+
}
|
|
11014
|
+
function filterSecurity(files) {
|
|
11015
|
+
return files.filter(
|
|
11016
|
+
(f) => SECURITY_PATTERNS.some((p) => p.test(f))
|
|
11017
|
+
);
|
|
11018
|
+
}
|
|
11019
|
+
function getCurrentCommit() {
|
|
11020
|
+
return execGit("git rev-parse HEAD") || "no-git";
|
|
11021
|
+
}
|
|
11022
|
+
function detectProvider(host) {
|
|
11023
|
+
const h = host.toLowerCase();
|
|
11024
|
+
if (h.includes("github")) return "github";
|
|
11025
|
+
if (h.includes("gitlab")) return "gitlab";
|
|
11026
|
+
if (h.includes("bitbucket")) return "bitbucket";
|
|
11027
|
+
return "unknown";
|
|
11028
|
+
}
|
|
11029
|
+
function parseRemote(raw) {
|
|
11030
|
+
if (!raw || typeof raw !== "string") return null;
|
|
11031
|
+
let s = raw.trim();
|
|
11032
|
+
if (!s) return null;
|
|
11033
|
+
let host = "";
|
|
11034
|
+
let path = "";
|
|
11035
|
+
const scp = s.match(/^[^/@]+@([^:/]+):(.+)$/);
|
|
11036
|
+
if (scp) {
|
|
11037
|
+
host = scp[1];
|
|
11038
|
+
path = scp[2];
|
|
11039
|
+
} else {
|
|
11040
|
+
s = s.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//, "");
|
|
11041
|
+
s = s.replace(/^[^/@]+@/, "");
|
|
11042
|
+
const slash = s.indexOf("/");
|
|
11043
|
+
if (slash === -1) return null;
|
|
11044
|
+
host = s.slice(0, slash);
|
|
11045
|
+
path = s.slice(slash + 1);
|
|
11046
|
+
}
|
|
11047
|
+
host = host.toLowerCase().trim();
|
|
11048
|
+
path = path.replace(/\/+$/, "").replace(/\.git$/, "");
|
|
11049
|
+
if (!host || !path) return null;
|
|
11050
|
+
const segments = path.split("/").filter(Boolean);
|
|
11051
|
+
if (segments.length < 2) return null;
|
|
11052
|
+
const owner = segments[0];
|
|
11053
|
+
const repo = segments[segments.length - 1];
|
|
11054
|
+
if (!owner || !repo) return null;
|
|
11055
|
+
return {
|
|
11056
|
+
host,
|
|
11057
|
+
owner,
|
|
11058
|
+
repo,
|
|
11059
|
+
provider: detectProvider(host),
|
|
11060
|
+
orgUrl: `https://${host}/${owner}`,
|
|
11061
|
+
orgName: owner
|
|
11062
|
+
};
|
|
11063
|
+
}
|
|
11064
|
+
function listTrackedFiles() {
|
|
11065
|
+
const set = /* @__PURE__ */ new Set();
|
|
11066
|
+
for (const f of splitLines(execGit("git ls-files"))) set.add(f);
|
|
11067
|
+
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
|
|
11068
|
+
return Array.from(set);
|
|
11069
|
+
}
|
|
11070
|
+
|
|
11071
|
+
// src/lib/register.ts
|
|
11072
|
+
async function registerProject(opts) {
|
|
11073
|
+
const parsed = parseRemote(opts.remote);
|
|
11074
|
+
if (!parsed) {
|
|
11075
|
+
return { ok: false, error: `Could not parse git remote: ${opts.remote}` };
|
|
11076
|
+
}
|
|
11077
|
+
if (parsed.provider !== "github") {
|
|
11078
|
+
return { ok: false, error: `Provider '${parsed.provider}' is not supported yet \u2014 GitHub only for now.` };
|
|
11079
|
+
}
|
|
11080
|
+
const providerAuth = await githubDeviceFlow();
|
|
11081
|
+
if (!providerAuth.ok) {
|
|
11082
|
+
return { ok: false, error: providerAuth.error };
|
|
11083
|
+
}
|
|
11084
|
+
const providerToken = providerAuth.data;
|
|
11085
|
+
const result = await apiRequest({
|
|
11086
|
+
method: "POST",
|
|
11087
|
+
path: "/auth/register",
|
|
11088
|
+
serviceUrl: opts.serviceUrl,
|
|
11089
|
+
body: { project_name: opts.projectName, git_remote_url: opts.remote },
|
|
11090
|
+
extraHeaders: { "X-Provider-Token": providerToken },
|
|
11091
|
+
verbose: opts.verbose
|
|
11092
|
+
});
|
|
11093
|
+
if (!result.ok) {
|
|
11094
|
+
return { ok: false, error: result.error };
|
|
11095
|
+
}
|
|
11096
|
+
const { project_id, token, service_url, user } = result.data;
|
|
11097
|
+
try {
|
|
11098
|
+
await (0, import_promises3.mkdir)(VERITY_DIR, { recursive: true });
|
|
11099
|
+
await (0, import_promises3.writeFile)(
|
|
11100
|
+
CREDENTIALS_FILE,
|
|
11101
|
+
`token: ${token}
|
|
11102
|
+
service_url: ${service_url}
|
|
11103
|
+
`,
|
|
11104
|
+
{ mode: 384 }
|
|
11105
|
+
);
|
|
11106
|
+
await (0, import_promises3.chmod)(CREDENTIALS_FILE, 384).catch(() => {
|
|
11107
|
+
});
|
|
11108
|
+
} catch (err) {
|
|
11109
|
+
return {
|
|
11110
|
+
ok: false,
|
|
11111
|
+
error: `Registered with the Verity service, but could not save credentials to ${CREDENTIALS_FILE}: ${err.message}. Check filesystem permissions and re-run "verity auth register".`
|
|
11112
|
+
};
|
|
11113
|
+
}
|
|
11114
|
+
try {
|
|
11115
|
+
await (0, import_promises3.mkdir)((0, import_node_path3.dirname)(GLOBAL_CREDENTIALS_FILE), { recursive: true });
|
|
11116
|
+
await (0, import_promises3.appendFile)(
|
|
11117
|
+
GLOBAL_CREDENTIALS_FILE,
|
|
11118
|
+
`${opts.remote} token: ${token}
|
|
11119
|
+
`,
|
|
11120
|
+
{ mode: 384 }
|
|
11121
|
+
);
|
|
11122
|
+
await (0, import_promises3.chmod)(GLOBAL_CREDENTIALS_FILE, 384).catch(() => {
|
|
11123
|
+
});
|
|
11124
|
+
} catch {
|
|
11125
|
+
}
|
|
11126
|
+
return { ok: true, data: { projectId: project_id, serviceUrl: service_url, email: user?.email } };
|
|
11127
|
+
}
|
|
11128
|
+
|
|
10743
11129
|
// src/commands/auth.ts
|
|
10744
11130
|
function registerAuthCommands(program2) {
|
|
10745
11131
|
const auth = program2.command("auth").description("Manage project authentication");
|
|
@@ -10749,36 +11135,26 @@ function registerAuthCommands(program2) {
|
|
|
10749
11135
|
let remote = opts.remote;
|
|
10750
11136
|
if (!remote) {
|
|
10751
11137
|
try {
|
|
10752
|
-
remote = (0,
|
|
11138
|
+
remote = (0, import_node_child_process4.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
|
|
10753
11139
|
} catch {
|
|
10754
11140
|
printError("No git remote found. Use --remote to specify one.");
|
|
10755
11141
|
process.exit(1);
|
|
10756
11142
|
}
|
|
10757
11143
|
}
|
|
10758
|
-
const result = await
|
|
10759
|
-
|
|
10760
|
-
|
|
11144
|
+
const result = await registerProject({
|
|
11145
|
+
projectName: opts.project,
|
|
11146
|
+
remote,
|
|
10761
11147
|
serviceUrl,
|
|
10762
|
-
body: { project_name: opts.project, git_remote_url: remote },
|
|
10763
11148
|
verbose: globals.verbose
|
|
10764
11149
|
});
|
|
10765
11150
|
if (!result.ok) {
|
|
10766
11151
|
printError(result.error);
|
|
10767
11152
|
process.exit(1);
|
|
10768
11153
|
}
|
|
10769
|
-
const {
|
|
10770
|
-
|
|
10771
|
-
|
|
10772
|
-
service_url:
|
|
10773
|
-
`);
|
|
10774
|
-
try {
|
|
10775
|
-
await (0, import_promises3.mkdir)((0, import_node_path2.dirname)(GLOBAL_CREDENTIALS_FILE), { recursive: true });
|
|
10776
|
-
await (0, import_promises3.appendFile)(GLOBAL_CREDENTIALS_FILE, `${remote} token: ${token}
|
|
10777
|
-
`);
|
|
10778
|
-
} catch {
|
|
10779
|
-
}
|
|
10780
|
-
printInfo(`Project registered: ${project_id}`);
|
|
10781
|
-
printJson({ project_id, service_url });
|
|
11154
|
+
const { projectId, serviceUrl: resolvedUrl, email } = result.data;
|
|
11155
|
+
printInfo(`Project registered: ${projectId}`);
|
|
11156
|
+
if (email) printInfo(`Authenticated as: ${email}`);
|
|
11157
|
+
printJson({ project_id: projectId, service_url: resolvedUrl });
|
|
10782
11158
|
});
|
|
10783
11159
|
auth.command("verify").description("Verify the current token is valid").action(async () => {
|
|
10784
11160
|
const globals = program2.opts();
|
|
@@ -10811,7 +11187,7 @@ service_url: ${service_url}
|
|
|
10811
11187
|
let remote = opts.remote;
|
|
10812
11188
|
if (!remote) {
|
|
10813
11189
|
try {
|
|
10814
|
-
remote = (0,
|
|
11190
|
+
remote = (0, import_node_child_process4.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
|
|
10815
11191
|
} catch {
|
|
10816
11192
|
printError("No git remote found. Use --remote to specify one.");
|
|
10817
11193
|
process.exit(1);
|
|
@@ -10838,8 +11214,63 @@ service_url: ${service_url}
|
|
|
10838
11214
|
}
|
|
10839
11215
|
|
|
10840
11216
|
// src/lib/hooks.ts
|
|
10841
|
-
var
|
|
10842
|
-
var
|
|
11217
|
+
var import_promises5 = require("node:fs/promises");
|
|
11218
|
+
var import_node_path5 = require("node:path");
|
|
11219
|
+
|
|
11220
|
+
// src/lib/json-file.ts
|
|
11221
|
+
var import_promises4 = require("node:fs/promises");
|
|
11222
|
+
var import_node_path4 = require("node:path");
|
|
11223
|
+
function jsonSemanticEqual(a, b) {
|
|
11224
|
+
if (a === b) return true;
|
|
11225
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) {
|
|
11226
|
+
return a === b;
|
|
11227
|
+
}
|
|
11228
|
+
const aIsArr = Array.isArray(a);
|
|
11229
|
+
const bIsArr = Array.isArray(b);
|
|
11230
|
+
if (aIsArr || bIsArr) {
|
|
11231
|
+
if (!aIsArr || !bIsArr || a.length !== b.length) return false;
|
|
11232
|
+
for (let i = 0; i < a.length; i++) {
|
|
11233
|
+
if (!jsonSemanticEqual(a[i], b[i])) return false;
|
|
11234
|
+
}
|
|
11235
|
+
return true;
|
|
11236
|
+
}
|
|
11237
|
+
const ao = a;
|
|
11238
|
+
const bo = b;
|
|
11239
|
+
const aKeys = Object.keys(ao).filter((k) => ao[k] !== void 0);
|
|
11240
|
+
const bKeys = Object.keys(bo).filter((k) => bo[k] !== void 0);
|
|
11241
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
11242
|
+
for (const k of aKeys) {
|
|
11243
|
+
if (bo[k] === void 0) return false;
|
|
11244
|
+
if (!jsonSemanticEqual(ao[k], bo[k])) return false;
|
|
11245
|
+
}
|
|
11246
|
+
return true;
|
|
11247
|
+
}
|
|
11248
|
+
function detectJsonIndent(raw) {
|
|
11249
|
+
const m = raw.match(/\n([ \t]+)\S/);
|
|
11250
|
+
return m ? m[1] : 2;
|
|
11251
|
+
}
|
|
11252
|
+
async function writeJsonFilePreservingStyle(file, value) {
|
|
11253
|
+
let currentRaw = null;
|
|
11254
|
+
try {
|
|
11255
|
+
currentRaw = await (0, import_promises4.readFile)(file, "utf-8");
|
|
11256
|
+
} catch {
|
|
11257
|
+
currentRaw = null;
|
|
11258
|
+
}
|
|
11259
|
+
if (currentRaw !== null) {
|
|
11260
|
+
try {
|
|
11261
|
+
if (jsonSemanticEqual(JSON.parse(currentRaw), value)) return false;
|
|
11262
|
+
} catch {
|
|
11263
|
+
}
|
|
11264
|
+
}
|
|
11265
|
+
const indent = currentRaw !== null ? detectJsonIndent(currentRaw) : 2;
|
|
11266
|
+
const next = JSON.stringify(value, null, indent) + "\n";
|
|
11267
|
+
if (next === currentRaw) return false;
|
|
11268
|
+
await (0, import_promises4.mkdir)((0, import_node_path4.dirname)(file), { recursive: true });
|
|
11269
|
+
await (0, import_promises4.writeFile)(file, next);
|
|
11270
|
+
return true;
|
|
11271
|
+
}
|
|
11272
|
+
|
|
11273
|
+
// src/lib/hooks.ts
|
|
10843
11274
|
var VERITY_STOP_HOOK = {
|
|
10844
11275
|
type: "command",
|
|
10845
11276
|
command: "verity analyze",
|
|
@@ -10917,7 +11348,7 @@ function globalSettingsFile() {
|
|
|
10917
11348
|
}
|
|
10918
11349
|
async function readSettings() {
|
|
10919
11350
|
try {
|
|
10920
|
-
const content = await (0,
|
|
11351
|
+
const content = await (0, import_promises5.readFile)(CLAUDE_SETTINGS_FILE, "utf-8");
|
|
10921
11352
|
return JSON.parse(content);
|
|
10922
11353
|
} catch {
|
|
10923
11354
|
return {};
|
|
@@ -10928,7 +11359,7 @@ async function readAllSettings() {
|
|
|
10928
11359
|
const out = [];
|
|
10929
11360
|
for (const f of files) {
|
|
10930
11361
|
try {
|
|
10931
|
-
out.push(JSON.parse(await (0,
|
|
11362
|
+
out.push(JSON.parse(await (0, import_promises5.readFile)(f, "utf-8")));
|
|
10932
11363
|
} catch {
|
|
10933
11364
|
}
|
|
10934
11365
|
}
|
|
@@ -10957,7 +11388,7 @@ async function checkExternalVerityHooks() {
|
|
|
10957
11388
|
for (const f of [SETTINGS_LOCAL_FILE, globalSettingsFile()]) {
|
|
10958
11389
|
let settings;
|
|
10959
11390
|
try {
|
|
10960
|
-
settings = JSON.parse(await (0,
|
|
11391
|
+
settings = JSON.parse(await (0, import_promises5.readFile)(f, "utf-8"));
|
|
10961
11392
|
} catch {
|
|
10962
11393
|
continue;
|
|
10963
11394
|
}
|
|
@@ -10991,20 +11422,17 @@ async function checkAllVerityHooksDetailed() {
|
|
|
10991
11422
|
return { stop, intent, baseline, current: hasCurrent, legacyOnly: hasLegacy && !hasCurrent };
|
|
10992
11423
|
}
|
|
10993
11424
|
async function writeSettings(settings) {
|
|
10994
|
-
await (
|
|
10995
|
-
await (0, import_promises4.writeFile)(CLAUDE_SETTINGS_FILE, JSON.stringify(settings, null, 2) + "\n");
|
|
11425
|
+
await writeJsonFilePreservingStyle(CLAUDE_SETTINGS_FILE, settings);
|
|
10996
11426
|
}
|
|
10997
11427
|
async function readSettingsAt(root) {
|
|
10998
11428
|
try {
|
|
10999
|
-
return JSON.parse(await (0,
|
|
11429
|
+
return JSON.parse(await (0, import_promises5.readFile)((0, import_node_path5.join)(root, CLAUDE_SETTINGS_FILE), "utf-8"));
|
|
11000
11430
|
} catch {
|
|
11001
11431
|
return {};
|
|
11002
11432
|
}
|
|
11003
11433
|
}
|
|
11004
11434
|
async function writeSettingsAt(root, settings) {
|
|
11005
|
-
|
|
11006
|
-
await (0, import_promises4.mkdir)((0, import_node_path3.dirname)(file), { recursive: true });
|
|
11007
|
-
await (0, import_promises4.writeFile)(file, JSON.stringify(settings, null, 2) + "\n");
|
|
11435
|
+
await writeJsonFilePreservingStyle((0, import_node_path5.join)(root, CLAUDE_SETTINGS_FILE), settings);
|
|
11008
11436
|
}
|
|
11009
11437
|
async function hasLegacyHooksAt(root) {
|
|
11010
11438
|
const settings = await readSettingsAt(root);
|
|
@@ -11243,9 +11671,9 @@ function registerHooksCommands(program2) {
|
|
|
11243
11671
|
var import_node_crypto3 = require("node:crypto");
|
|
11244
11672
|
|
|
11245
11673
|
// src/lib/conversation-buffer.ts
|
|
11246
|
-
var
|
|
11247
|
-
var
|
|
11248
|
-
var
|
|
11674
|
+
var import_promises6 = require("node:fs/promises");
|
|
11675
|
+
var import_node_fs3 = require("node:fs");
|
|
11676
|
+
var import_node_child_process5 = require("node:child_process");
|
|
11249
11677
|
var import_node_crypto = require("node:crypto");
|
|
11250
11678
|
function stripImageReferences(text) {
|
|
11251
11679
|
return text.replace(/\[Image #\d+\]/g, "[screenshot \u2014 not available for review]");
|
|
@@ -11255,7 +11683,7 @@ function bufferTmpPath() {
|
|
|
11255
11683
|
}
|
|
11256
11684
|
async function appendToConversationBuffer(prompt, sessionId) {
|
|
11257
11685
|
try {
|
|
11258
|
-
await (0,
|
|
11686
|
+
await (0, import_promises6.mkdir)(VERITY_DIR, { recursive: true });
|
|
11259
11687
|
let sanitized = prompt.length > MAX_INTENT_CHARS ? prompt.slice(0, MAX_INTENT_CHARS) : prompt;
|
|
11260
11688
|
sanitized = stripImageReferences(sanitized);
|
|
11261
11689
|
const entry = {
|
|
@@ -11273,14 +11701,14 @@ async function appendToConversationBuffer(prompt, sessionId) {
|
|
|
11273
11701
|
const capped = recent.slice(-CONVERSATION_MAX_ENTRIES);
|
|
11274
11702
|
const content = capped.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
11275
11703
|
const tmpFile = bufferTmpPath();
|
|
11276
|
-
await (0,
|
|
11277
|
-
await (0,
|
|
11704
|
+
await (0, import_promises6.writeFile)(tmpFile, content);
|
|
11705
|
+
await (0, import_promises6.rename)(tmpFile, CONVERSATION_BUFFER_FILE);
|
|
11278
11706
|
} catch {
|
|
11279
11707
|
}
|
|
11280
11708
|
}
|
|
11281
11709
|
async function readAndClearConversationBuffer(currentSessionId) {
|
|
11282
11710
|
try {
|
|
11283
|
-
if ((0,
|
|
11711
|
+
if ((0, import_node_fs3.existsSync)(CONVERSATION_BUFFER_FILE)) {
|
|
11284
11712
|
const entries = await readBufferEntries();
|
|
11285
11713
|
let mine = entries;
|
|
11286
11714
|
let others = [];
|
|
@@ -11291,10 +11719,10 @@ async function readAndClearConversationBuffer(currentSessionId) {
|
|
|
11291
11719
|
if (others.length > 0) {
|
|
11292
11720
|
const remaining = others.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
11293
11721
|
const tmpFile = bufferTmpPath();
|
|
11294
|
-
await (0,
|
|
11295
|
-
await (0,
|
|
11722
|
+
await (0, import_promises6.writeFile)(tmpFile, remaining);
|
|
11723
|
+
await (0, import_promises6.rename)(tmpFile, CONVERSATION_BUFFER_FILE);
|
|
11296
11724
|
} else {
|
|
11297
|
-
await (0,
|
|
11725
|
+
await (0, import_promises6.unlink)(CONVERSATION_BUFFER_FILE).catch(() => {
|
|
11298
11726
|
});
|
|
11299
11727
|
}
|
|
11300
11728
|
if (mine.length > 0) {
|
|
@@ -11304,10 +11732,10 @@ async function readAndClearConversationBuffer(currentSessionId) {
|
|
|
11304
11732
|
};
|
|
11305
11733
|
}
|
|
11306
11734
|
}
|
|
11307
|
-
if ((0,
|
|
11735
|
+
if ((0, import_node_fs3.existsSync)(INTENT_FILE)) {
|
|
11308
11736
|
try {
|
|
11309
|
-
const content = await (0,
|
|
11310
|
-
await (0,
|
|
11737
|
+
const content = await (0, import_promises6.readFile)(INTENT_FILE, "utf-8");
|
|
11738
|
+
await (0, import_promises6.unlink)(INTENT_FILE).catch(() => {
|
|
11311
11739
|
});
|
|
11312
11740
|
const data = JSON.parse(content);
|
|
11313
11741
|
if (data.prompt) {
|
|
@@ -11330,7 +11758,7 @@ async function readAndClearConversationBuffer(currentSessionId) {
|
|
|
11330
11758
|
}
|
|
11331
11759
|
async function readBufferEntries() {
|
|
11332
11760
|
try {
|
|
11333
|
-
const content = await (0,
|
|
11761
|
+
const content = await (0, import_promises6.readFile)(CONVERSATION_BUFFER_FILE, "utf-8");
|
|
11334
11762
|
const entries = [];
|
|
11335
11763
|
for (const line of content.split("\n")) {
|
|
11336
11764
|
const trimmed = line.trim();
|
|
@@ -11348,7 +11776,7 @@ async function readBufferEntries() {
|
|
|
11348
11776
|
}
|
|
11349
11777
|
function getRecentCommitMessages() {
|
|
11350
11778
|
try {
|
|
11351
|
-
const output = (0,
|
|
11779
|
+
const output = (0, import_node_child_process5.execSync)(
|
|
11352
11780
|
'git log --since="30 minutes ago" --format="%s" -5',
|
|
11353
11781
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
11354
11782
|
).trim();
|
|
@@ -11360,9 +11788,9 @@ function getRecentCommitMessages() {
|
|
|
11360
11788
|
}
|
|
11361
11789
|
|
|
11362
11790
|
// src/lib/task-context-buffer.ts
|
|
11363
|
-
var
|
|
11364
|
-
var
|
|
11365
|
-
var
|
|
11791
|
+
var import_promises7 = require("node:fs/promises");
|
|
11792
|
+
var import_node_fs4 = require("node:fs");
|
|
11793
|
+
var import_node_path6 = require("node:path");
|
|
11366
11794
|
var TASK_CONTEXT_DIR = `${VERITY_DIR}/.task-context`;
|
|
11367
11795
|
var MAX_BUFFER_BYTES = 500 * 1024;
|
|
11368
11796
|
var MAX_PROMPT_CHARS = 2e3;
|
|
@@ -11401,9 +11829,9 @@ async function appendResponseToTaskBuffer(taskId, assistantResponse, actionSumma
|
|
|
11401
11829
|
}
|
|
11402
11830
|
async function readTaskContextBuffer(taskId) {
|
|
11403
11831
|
const filePath = bufferPath(taskId);
|
|
11404
|
-
if (!(0,
|
|
11832
|
+
if (!(0, import_node_fs4.existsSync)(filePath)) return null;
|
|
11405
11833
|
try {
|
|
11406
|
-
const content = await (0,
|
|
11834
|
+
const content = await (0, import_promises7.readFile)(filePath, "utf-8");
|
|
11407
11835
|
if (!content.trim()) return null;
|
|
11408
11836
|
const lines = content.split("\n").filter((l) => l.trim());
|
|
11409
11837
|
const formatted = [];
|
|
@@ -11435,16 +11863,16 @@ async function readTaskContextBuffer(taskId) {
|
|
|
11435
11863
|
}
|
|
11436
11864
|
async function cleanupTaskContextBuffers() {
|
|
11437
11865
|
try {
|
|
11438
|
-
if (!(0,
|
|
11439
|
-
const files = await (0,
|
|
11866
|
+
if (!(0, import_node_fs4.existsSync)(TASK_CONTEXT_DIR)) return;
|
|
11867
|
+
const files = await (0, import_promises7.readdir)(TASK_CONTEXT_DIR);
|
|
11440
11868
|
const cutoffMs = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
11441
11869
|
for (const file of files) {
|
|
11442
11870
|
if (!file.endsWith(".jsonl")) continue;
|
|
11443
|
-
const filePath = (0,
|
|
11871
|
+
const filePath = (0, import_node_path6.join)(TASK_CONTEXT_DIR, file);
|
|
11444
11872
|
try {
|
|
11445
|
-
const stats = await (0,
|
|
11873
|
+
const stats = await (0, import_promises7.stat)(filePath);
|
|
11446
11874
|
if (stats.mtimeMs < cutoffMs) {
|
|
11447
|
-
await (0,
|
|
11875
|
+
await (0, import_promises7.unlink)(filePath);
|
|
11448
11876
|
}
|
|
11449
11877
|
} catch {
|
|
11450
11878
|
}
|
|
@@ -11454,33 +11882,33 @@ async function cleanupTaskContextBuffers() {
|
|
|
11454
11882
|
}
|
|
11455
11883
|
function bufferPath(taskId) {
|
|
11456
11884
|
const safe = taskId.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
11457
|
-
return (0,
|
|
11885
|
+
return (0, import_node_path6.join)(TASK_CONTEXT_DIR, `${safe}.jsonl`);
|
|
11458
11886
|
}
|
|
11459
11887
|
async function appendEntry(taskId, entry) {
|
|
11460
11888
|
try {
|
|
11461
|
-
await (0,
|
|
11889
|
+
await (0, import_promises7.mkdir)(TASK_CONTEXT_DIR, { recursive: true });
|
|
11462
11890
|
const filePath = bufferPath(taskId);
|
|
11463
|
-
if ((0,
|
|
11464
|
-
const stats = await (0,
|
|
11891
|
+
if ((0, import_node_fs4.existsSync)(filePath)) {
|
|
11892
|
+
const stats = await (0, import_promises7.stat)(filePath);
|
|
11465
11893
|
if (stats.size >= MAX_BUFFER_BYTES) {
|
|
11466
|
-
const content = await (0,
|
|
11894
|
+
const content = await (0, import_promises7.readFile)(filePath, "utf-8");
|
|
11467
11895
|
const lines = content.split("\n").filter((l) => l.trim());
|
|
11468
11896
|
const keepFrom = Math.floor(lines.length * 0.25);
|
|
11469
11897
|
const pruned = lines.slice(keepFrom).join("\n") + "\n";
|
|
11470
|
-
await (0,
|
|
11898
|
+
await (0, import_promises7.writeFile)(filePath, pruned);
|
|
11471
11899
|
}
|
|
11472
11900
|
}
|
|
11473
11901
|
const line = JSON.stringify(entry) + "\n";
|
|
11474
|
-
const existing = (0,
|
|
11475
|
-
await (0,
|
|
11902
|
+
const existing = (0, import_node_fs4.existsSync)(filePath) ? await (0, import_promises7.readFile)(filePath, "utf-8") : "";
|
|
11903
|
+
await (0, import_promises7.writeFile)(filePath, existing + line);
|
|
11476
11904
|
} catch {
|
|
11477
11905
|
}
|
|
11478
11906
|
}
|
|
11479
11907
|
|
|
11480
11908
|
// src/lib/memory-retrieval.ts
|
|
11481
|
-
var
|
|
11482
|
-
var
|
|
11483
|
-
var
|
|
11909
|
+
var import_promises8 = require("node:fs/promises");
|
|
11910
|
+
var import_node_fs5 = require("node:fs");
|
|
11911
|
+
var import_node_path7 = require("node:path");
|
|
11484
11912
|
var memoryDir = () => projectPath(`${VERITY_DIR}/memory`);
|
|
11485
11913
|
var DOMAINS = ["decisions", "quality", "security", "intent", "gotchas", "patterns", "domain", "integrations"];
|
|
11486
11914
|
var DEFAULT_BUDGET_TOKENS = 2e3;
|
|
@@ -11573,19 +12001,19 @@ function parseFrontmatter(content) {
|
|
|
11573
12001
|
return { fm, body: match[2].trim() };
|
|
11574
12002
|
}
|
|
11575
12003
|
async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = DEFAULT_BUDGET_TOKENS) {
|
|
11576
|
-
if (!(0,
|
|
12004
|
+
if (!(0, import_node_fs5.existsSync)(memoryDir())) return null;
|
|
11577
12005
|
const budget = Math.min(budgetTokens, MAX_BUDGET_TOKENS);
|
|
11578
12006
|
const promptTokens = tokenize(promptText);
|
|
11579
12007
|
const nodes = [];
|
|
11580
12008
|
for (const domain of DOMAINS) {
|
|
11581
|
-
const domainDir = (0,
|
|
11582
|
-
if (!(0,
|
|
12009
|
+
const domainDir = (0, import_node_path7.join)(memoryDir(), domain);
|
|
12010
|
+
if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
|
|
11583
12011
|
try {
|
|
11584
|
-
const files = await (0,
|
|
12012
|
+
const files = await (0, import_promises8.readdir)(domainDir);
|
|
11585
12013
|
for (const file of files) {
|
|
11586
12014
|
if (!file.endsWith(".md")) continue;
|
|
11587
12015
|
try {
|
|
11588
|
-
const content = await (0,
|
|
12016
|
+
const content = await (0, import_promises8.readFile)((0, import_node_path7.join)(domainDir, file), "utf-8");
|
|
11589
12017
|
const { fm, body } = parseFrontmatter(content);
|
|
11590
12018
|
if (fm.status && fm.status !== "active") continue;
|
|
11591
12019
|
nodes.push({
|
|
@@ -11643,9 +12071,9 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
|
|
|
11643
12071
|
}
|
|
11644
12072
|
|
|
11645
12073
|
// src/lib/memory-sync.ts
|
|
11646
|
-
var
|
|
11647
|
-
var
|
|
11648
|
-
var
|
|
12074
|
+
var import_promises9 = require("node:fs/promises");
|
|
12075
|
+
var import_node_fs6 = require("node:fs");
|
|
12076
|
+
var import_node_path8 = require("node:path");
|
|
11649
12077
|
var import_node_crypto2 = require("node:crypto");
|
|
11650
12078
|
|
|
11651
12079
|
// src/lib/glob-match.ts
|
|
@@ -11714,36 +12142,36 @@ var memoryDir2 = () => projectPath(`${VERITY_DIR}/memory`);
|
|
|
11714
12142
|
var DOMAINS2 = ["decisions", "quality", "security", "intent", "gotchas", "patterns", "domain", "integrations", "_archive"];
|
|
11715
12143
|
var syncStateFile = () => projectPath(`${VERITY_DIR}/.memory-sync-state.json`);
|
|
11716
12144
|
async function ensureMemoryDir() {
|
|
11717
|
-
await (0,
|
|
12145
|
+
await (0, import_promises9.mkdir)(memoryDir2(), { recursive: true });
|
|
11718
12146
|
for (const domain of DOMAINS2) {
|
|
11719
|
-
await (0,
|
|
12147
|
+
await (0, import_promises9.mkdir)((0, import_node_path8.join)(memoryDir2(), domain), { recursive: true });
|
|
11720
12148
|
}
|
|
11721
|
-
if (!(0,
|
|
11722
|
-
await (0,
|
|
12149
|
+
if (!(0, import_node_fs6.existsSync)((0, import_node_path8.join)(memoryDir2(), "SCHEMA.md"))) {
|
|
12150
|
+
await (0, import_promises9.writeFile)((0, import_node_path8.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
|
|
11723
12151
|
}
|
|
11724
|
-
if (!(0,
|
|
11725
|
-
await (0,
|
|
12152
|
+
if (!(0, import_node_fs6.existsSync)((0, import_node_path8.join)(memoryDir2(), "index.md"))) {
|
|
12153
|
+
await (0, import_promises9.writeFile)((0, import_node_path8.join)(memoryDir2(), "index.md"), "# Project Memory Index\n\nNo nodes yet. Run an analysis to start building the knowledge graph.\n");
|
|
11726
12154
|
}
|
|
11727
|
-
if (!(0,
|
|
11728
|
-
await (0,
|
|
12155
|
+
if (!(0, import_node_fs6.existsSync)((0, import_node_path8.join)(memoryDir2(), "log.md"))) {
|
|
12156
|
+
await (0, import_promises9.writeFile)((0, import_node_path8.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
|
|
11729
12157
|
}
|
|
11730
12158
|
}
|
|
11731
12159
|
async function buildManifest() {
|
|
11732
|
-
if (!(0,
|
|
12160
|
+
if (!(0, import_node_fs6.existsSync)(memoryDir2())) {
|
|
11733
12161
|
return { schema_version: 1, nodes: [], index_hash: null, log_length: 0 };
|
|
11734
12162
|
}
|
|
11735
12163
|
const nodes = [];
|
|
11736
12164
|
for (const domain of DOMAINS2) {
|
|
11737
|
-
const domainDir = (0,
|
|
11738
|
-
if (!(0,
|
|
12165
|
+
const domainDir = (0, import_node_path8.join)(memoryDir2(), domain);
|
|
12166
|
+
if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
|
|
11739
12167
|
try {
|
|
11740
|
-
const files = await (0,
|
|
12168
|
+
const files = await (0, import_promises9.readdir)(domainDir);
|
|
11741
12169
|
for (const file of files) {
|
|
11742
12170
|
if (!file.endsWith(".md")) continue;
|
|
11743
12171
|
const filePath = `${domain}/${file}`;
|
|
11744
|
-
const fullPath = (0,
|
|
12172
|
+
const fullPath = (0, import_node_path8.join)(memoryDir2(), filePath);
|
|
11745
12173
|
try {
|
|
11746
|
-
const content = await (0,
|
|
12174
|
+
const content = await (0, import_promises9.readFile)(fullPath, "utf-8");
|
|
11747
12175
|
const hash = (0, import_node_crypto2.createHash)("sha256").update(content).digest("hex").slice(0, 16);
|
|
11748
12176
|
nodes.push({ path: filePath, content_hash: `sha256:${hash}` });
|
|
11749
12177
|
} catch {
|
|
@@ -11754,13 +12182,13 @@ async function buildManifest() {
|
|
|
11754
12182
|
}
|
|
11755
12183
|
let indexHash = null;
|
|
11756
12184
|
try {
|
|
11757
|
-
const indexContent = await (0,
|
|
12185
|
+
const indexContent = await (0, import_promises9.readFile)((0, import_node_path8.join)(memoryDir2(), "index.md"), "utf-8");
|
|
11758
12186
|
indexHash = `sha256:${(0, import_node_crypto2.createHash)("sha256").update(indexContent).digest("hex").slice(0, 16)}`;
|
|
11759
12187
|
} catch {
|
|
11760
12188
|
}
|
|
11761
12189
|
let logLength = 0;
|
|
11762
12190
|
try {
|
|
11763
|
-
const logContent = await (0,
|
|
12191
|
+
const logContent = await (0, import_promises9.readFile)((0, import_node_path8.join)(memoryDir2(), "log.md"), "utf-8");
|
|
11764
12192
|
logLength = logContent.split("\n").length;
|
|
11765
12193
|
} catch {
|
|
11766
12194
|
}
|
|
@@ -11771,15 +12199,15 @@ function hashContent(content) {
|
|
|
11771
12199
|
}
|
|
11772
12200
|
async function readOnDiskNodes() {
|
|
11773
12201
|
const out = /* @__PURE__ */ new Map();
|
|
11774
|
-
if (!(0,
|
|
12202
|
+
if (!(0, import_node_fs6.existsSync)(memoryDir2())) return out;
|
|
11775
12203
|
for (const domain of DOMAINS2) {
|
|
11776
|
-
const domainDir = (0,
|
|
11777
|
-
if (!(0,
|
|
12204
|
+
const domainDir = (0, import_node_path8.join)(memoryDir2(), domain);
|
|
12205
|
+
if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
|
|
11778
12206
|
try {
|
|
11779
|
-
for (const file of await (0,
|
|
12207
|
+
for (const file of await (0, import_promises9.readdir)(domainDir)) {
|
|
11780
12208
|
if (!file.endsWith(".md")) continue;
|
|
11781
12209
|
try {
|
|
11782
|
-
out.set(`${domain}/${file}`, hashContent(await (0,
|
|
12210
|
+
out.set(`${domain}/${file}`, hashContent(await (0, import_promises9.readFile)((0, import_node_path8.join)(domainDir, file), "utf-8")));
|
|
11783
12211
|
} catch {
|
|
11784
12212
|
}
|
|
11785
12213
|
}
|
|
@@ -11791,7 +12219,7 @@ async function readOnDiskNodes() {
|
|
|
11791
12219
|
async function readSyncBaseline() {
|
|
11792
12220
|
const out = /* @__PURE__ */ new Map();
|
|
11793
12221
|
try {
|
|
11794
|
-
const parsed = JSON.parse(await (0,
|
|
12222
|
+
const parsed = JSON.parse(await (0, import_promises9.readFile)(syncStateFile(), "utf-8"));
|
|
11795
12223
|
if (Array.isArray(parsed?.nodes)) {
|
|
11796
12224
|
for (const n of parsed.nodes) if (n?.path) out.set(n.path, n.hash ?? null);
|
|
11797
12225
|
} else if (Array.isArray(parsed?.paths)) {
|
|
@@ -11807,12 +12235,12 @@ async function recordSyncedNodePaths() {
|
|
|
11807
12235
|
const next = JSON.stringify({ schema: 2, nodes }) + "\n";
|
|
11808
12236
|
let existing = "";
|
|
11809
12237
|
try {
|
|
11810
|
-
existing = await (0,
|
|
12238
|
+
existing = await (0, import_promises9.readFile)(syncStateFile(), "utf-8");
|
|
11811
12239
|
} catch {
|
|
11812
12240
|
}
|
|
11813
12241
|
if (existing === next) return;
|
|
11814
|
-
await (0,
|
|
11815
|
-
await (0,
|
|
12242
|
+
await (0, import_promises9.mkdir)(projectPath(VERITY_DIR), { recursive: true });
|
|
12243
|
+
await (0, import_promises9.writeFile)(syncStateFile(), next);
|
|
11816
12244
|
} catch {
|
|
11817
12245
|
}
|
|
11818
12246
|
}
|
|
@@ -11825,11 +12253,11 @@ async function computeEditedNodeUploads() {
|
|
|
11825
12253
|
const uploads = [];
|
|
11826
12254
|
for (const [path, prevHash] of prev) {
|
|
11827
12255
|
if (prevHash == null) continue;
|
|
11828
|
-
const full = (0,
|
|
11829
|
-
if (!(0,
|
|
12256
|
+
const full = (0, import_node_path8.join)(memoryDir2(), path);
|
|
12257
|
+
if (!(0, import_node_fs6.existsSync)(full)) continue;
|
|
11830
12258
|
let content;
|
|
11831
12259
|
try {
|
|
11832
|
-
content = await (0,
|
|
12260
|
+
content = await (0, import_promises9.readFile)(full, "utf-8");
|
|
11833
12261
|
} catch {
|
|
11834
12262
|
continue;
|
|
11835
12263
|
}
|
|
@@ -11862,15 +12290,15 @@ async function applyMemoryWrites(writes, opts = {}) {
|
|
|
11862
12290
|
const logLines = [`- ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)} \u2014 Applied ${count} write(s) from server`];
|
|
11863
12291
|
for (const n of notes) logLines.push(` - ${n}`);
|
|
11864
12292
|
try {
|
|
11865
|
-
const existing = (0,
|
|
11866
|
-
await (0,
|
|
12293
|
+
const existing = (0, import_node_fs6.existsSync)((0, import_node_path8.join)(memoryDir2(), "log.md")) ? await (0, import_promises9.readFile)((0, import_node_path8.join)(memoryDir2(), "log.md"), "utf-8") : "# Memory Log\n\n";
|
|
12294
|
+
await (0, import_promises9.writeFile)((0, import_node_path8.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
|
|
11867
12295
|
} catch {
|
|
11868
12296
|
}
|
|
11869
12297
|
await recordSyncedNodePaths();
|
|
11870
12298
|
return count;
|
|
11871
12299
|
}
|
|
11872
12300
|
async function applyOneWrite(write, treePaths) {
|
|
11873
|
-
const fullPath = (0,
|
|
12301
|
+
const fullPath = (0, import_node_path8.join)(memoryDir2(), write.path);
|
|
11874
12302
|
const notes = [];
|
|
11875
12303
|
let content = write.content;
|
|
11876
12304
|
if (treePaths && treePaths.length > 0) {
|
|
@@ -11880,10 +12308,10 @@ async function applyOneWrite(write, treePaths) {
|
|
|
11880
12308
|
notes.push(`${write.path}: dropped unmatched file_globs [${grounded.dropped.join(", ")}]`);
|
|
11881
12309
|
}
|
|
11882
12310
|
}
|
|
11883
|
-
if ((0,
|
|
12311
|
+
if ((0, import_node_fs6.existsSync)(fullPath)) {
|
|
11884
12312
|
let existing = "";
|
|
11885
12313
|
try {
|
|
11886
|
-
existing = await (0,
|
|
12314
|
+
existing = await (0, import_promises9.readFile)(fullPath, "utf-8");
|
|
11887
12315
|
} catch {
|
|
11888
12316
|
}
|
|
11889
12317
|
if (existing === content) return { written: false, notes };
|
|
@@ -11892,8 +12320,8 @@ async function applyOneWrite(write, treePaths) {
|
|
|
11892
12320
|
return { written: false, notes };
|
|
11893
12321
|
}
|
|
11894
12322
|
}
|
|
11895
|
-
await (0,
|
|
11896
|
-
await (0,
|
|
12323
|
+
await (0, import_promises9.mkdir)((0, import_node_path8.dirname)(fullPath), { recursive: true });
|
|
12324
|
+
await (0, import_promises9.writeFile)(fullPath, content);
|
|
11897
12325
|
return { written: true, notes };
|
|
11898
12326
|
}
|
|
11899
12327
|
function groundFileGlobs(content, treePaths) {
|
|
@@ -11933,10 +12361,10 @@ async function regenerateIndex() {
|
|
|
11933
12361
|
];
|
|
11934
12362
|
let totalNodes = 0;
|
|
11935
12363
|
for (const domain of DOMAINS2.filter((d) => d !== "_archive")) {
|
|
11936
|
-
const domainDir = (0,
|
|
11937
|
-
if (!(0,
|
|
12364
|
+
const domainDir = (0, import_node_path8.join)(memoryDir2(), domain);
|
|
12365
|
+
if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
|
|
11938
12366
|
try {
|
|
11939
|
-
const files = await (0,
|
|
12367
|
+
const files = await (0, import_promises9.readdir)(domainDir);
|
|
11940
12368
|
const mdFiles = files.filter((f) => f.endsWith(".md"));
|
|
11941
12369
|
if (mdFiles.length === 0) continue;
|
|
11942
12370
|
lines.push(`## ${domain}/ (${mdFiles.length})`);
|
|
@@ -11944,7 +12372,7 @@ async function regenerateIndex() {
|
|
|
11944
12372
|
for (const file of mdFiles.sort()) {
|
|
11945
12373
|
const slug = file.replace(/\.md$/, "");
|
|
11946
12374
|
try {
|
|
11947
|
-
const content = await (0,
|
|
12375
|
+
const content = await (0, import_promises9.readFile)((0, import_node_path8.join)(domainDir, file), "utf-8");
|
|
11948
12376
|
const title = pickFrontmatter(content, "title") ?? slug;
|
|
11949
12377
|
const kind = pickFrontmatter(content, "kind") ?? "-";
|
|
11950
12378
|
const confidence = pickFrontmatter(content, "confidence");
|
|
@@ -11968,14 +12396,14 @@ async function regenerateIndex() {
|
|
|
11968
12396
|
lines.push("No nodes yet. Run an analysis to start building the knowledge graph.");
|
|
11969
12397
|
}
|
|
11970
12398
|
const next = lines.join("\n") + "\n";
|
|
11971
|
-
const indexPath = (0,
|
|
12399
|
+
const indexPath = (0, import_node_path8.join)(memoryDir2(), "index.md");
|
|
11972
12400
|
let existing = null;
|
|
11973
12401
|
try {
|
|
11974
|
-
existing = await (0,
|
|
12402
|
+
existing = await (0, import_promises9.readFile)(indexPath, "utf-8");
|
|
11975
12403
|
} catch {
|
|
11976
12404
|
}
|
|
11977
12405
|
if (existing === next) return;
|
|
11978
|
-
await (0,
|
|
12406
|
+
await (0, import_promises9.writeFile)(indexPath, next);
|
|
11979
12407
|
}
|
|
11980
12408
|
function pickFrontmatter(content, key) {
|
|
11981
12409
|
const re = new RegExp(`^${key}:\\s*"?([^"\\n]+?)"?\\s*$`, "m");
|
|
@@ -12050,10 +12478,10 @@ function hasLegacyMemoryBlock(text) {
|
|
|
12050
12478
|
return findMarker(text, LEGACY_MD_START) !== -1;
|
|
12051
12479
|
}
|
|
12052
12480
|
async function ensureClaudeMdPointer(cwd = repoRoot()) {
|
|
12053
|
-
const claudeMdPath = (0,
|
|
12481
|
+
const claudeMdPath = (0, import_node_path8.join)(cwd, "CLAUDE.md");
|
|
12054
12482
|
let existing = "";
|
|
12055
|
-
if ((0,
|
|
12056
|
-
existing = await (0,
|
|
12483
|
+
if ((0, import_node_fs6.existsSync)(claudeMdPath)) {
|
|
12484
|
+
existing = await (0, import_promises9.readFile)(claudeMdPath, "utf-8");
|
|
12057
12485
|
}
|
|
12058
12486
|
let startTag = CLAUDE_MD_START;
|
|
12059
12487
|
let endTag = CLAUDE_MD_END;
|
|
@@ -12109,7 +12537,7 @@ async function ensureClaudeMdPointer(cwd = repoRoot()) {
|
|
|
12109
12537
|
next = existing.replace(/\n*$/, "") + "\n\n" + block + "\n";
|
|
12110
12538
|
}
|
|
12111
12539
|
if (next === existing) return;
|
|
12112
|
-
await (0,
|
|
12540
|
+
await (0, import_promises9.writeFile)(claudeMdPath, next);
|
|
12113
12541
|
}
|
|
12114
12542
|
function extractPreserveContent(interior) {
|
|
12115
12543
|
for (const [start, end] of [
|
|
@@ -12182,7 +12610,7 @@ Body content (\u22648KB). Use [[node-id]] wikilinks for cross-references.
|
|
|
12182
12610
|
`;
|
|
12183
12611
|
|
|
12184
12612
|
// src/commands/intent.ts
|
|
12185
|
-
var
|
|
12613
|
+
var import_node_fs7 = require("node:fs");
|
|
12186
12614
|
function registerIntentCommands(program2) {
|
|
12187
12615
|
const intent = program2.command("intent").description("Manage intent capture");
|
|
12188
12616
|
intent.command("capture").description("Capture user intent from stdin (used by UserPromptSubmit hook)").action(async () => {
|
|
@@ -12191,7 +12619,7 @@ function registerIntentCommands(program2) {
|
|
|
12191
12619
|
process.chdir(repoRoot());
|
|
12192
12620
|
} catch {
|
|
12193
12621
|
}
|
|
12194
|
-
if (!(0,
|
|
12622
|
+
if (!(0, import_node_fs7.existsSync)(VERITY_DIR)) {
|
|
12195
12623
|
process.exit(0);
|
|
12196
12624
|
}
|
|
12197
12625
|
const chunks = [];
|
|
@@ -12292,7 +12720,7 @@ async function fireClassify(prompt, sessionId) {
|
|
|
12292
12720
|
}
|
|
12293
12721
|
|
|
12294
12722
|
// src/commands/standard.ts
|
|
12295
|
-
var
|
|
12723
|
+
var import_promises10 = require("node:fs/promises");
|
|
12296
12724
|
var import_yaml = __toESM(require_dist());
|
|
12297
12725
|
function registerStandardCommands(program2) {
|
|
12298
12726
|
const standard = program2.command("standard").description("Manage the project Standard");
|
|
@@ -12310,7 +12738,7 @@ function registerStandardCommands(program2) {
|
|
|
12310
12738
|
}
|
|
12311
12739
|
let yamlContent;
|
|
12312
12740
|
try {
|
|
12313
|
-
yamlContent = await (0,
|
|
12741
|
+
yamlContent = await (0, import_promises10.readFile)(opts.file, "utf-8");
|
|
12314
12742
|
} catch {
|
|
12315
12743
|
printError(`Cannot read ${opts.file}`);
|
|
12316
12744
|
process.exit(1);
|
|
@@ -12401,7 +12829,7 @@ function registerStandardCommands(program2) {
|
|
|
12401
12829
|
}
|
|
12402
12830
|
|
|
12403
12831
|
// src/commands/config.ts
|
|
12404
|
-
var
|
|
12832
|
+
var import_promises11 = require("node:fs/promises");
|
|
12405
12833
|
function registerConfigCommands(program2) {
|
|
12406
12834
|
const config = program2.command("config").description("Manage analysis configuration");
|
|
12407
12835
|
config.command("push").description("Upload the analysis config to the service").option("--file <path>", "Path to config file", CODACY_CONFIG_FILE).action(async (opts) => {
|
|
@@ -12418,7 +12846,7 @@ function registerConfigCommands(program2) {
|
|
|
12418
12846
|
}
|
|
12419
12847
|
let content;
|
|
12420
12848
|
try {
|
|
12421
|
-
const raw = await (0,
|
|
12849
|
+
const raw = await (0, import_promises11.readFile)(opts.file, "utf-8");
|
|
12422
12850
|
content = JSON.parse(raw);
|
|
12423
12851
|
} catch {
|
|
12424
12852
|
printError(`Cannot read or parse ${opts.file}`);
|
|
@@ -12618,370 +13046,161 @@ function registerStatusCommand(program2) {
|
|
|
12618
13046
|
if (hookStatus.guardOn.includes("push")) moments.push("pre-push/PR");
|
|
12619
13047
|
printInfo(`Moments: ${moments.length > 0 ? moments.join(", ") : "none (run /verity-setup)"}`);
|
|
12620
13048
|
if (mem.recent_runs) {
|
|
12621
|
-
const r = mem.recent_runs;
|
|
12622
|
-
printInfo("");
|
|
12623
|
-
printInfo("--- Last Run ---");
|
|
12624
|
-
printInfo(`Decision: ${r.last_gate_decision ?? "none"}`);
|
|
12625
|
-
printInfo(`Quality: ${r.last_quality_score ?? "-"}/10`);
|
|
12626
|
-
printInfo(`Security: ${r.last_security_score ?? "-"}/10`);
|
|
12627
|
-
printInfo(`Trend: ${r.trend}`);
|
|
12628
|
-
printInfo(`Runs: ${r.count} recorded`);
|
|
12629
|
-
}
|
|
12630
|
-
if (mem.pending_items && mem.pending_items.length > 0) {
|
|
12631
|
-
printInfo("");
|
|
12632
|
-
printInfo("--- Pending Items ---");
|
|
12633
|
-
for (const item of mem.pending_items) {
|
|
12634
|
-
printInfo(` [${item.priority.toUpperCase()}] ${item.description}`);
|
|
12635
|
-
}
|
|
12636
|
-
}
|
|
12637
|
-
const recentTasks = mem.recent_tasks;
|
|
12638
|
-
const currentTask = mem.current_task;
|
|
12639
|
-
const contextFiles = mem.context_files;
|
|
12640
|
-
if (recentTasks && recentTasks.length > 0) {
|
|
12641
|
-
printInfo("");
|
|
12642
|
-
printInfo("--- Active Tasks ---");
|
|
12643
|
-
for (const task of recentTasks) {
|
|
12644
|
-
const isCurrent = currentTask && task.id === currentTask.id;
|
|
12645
|
-
const marker = isCurrent ? "\u25CF" : "\u25CB";
|
|
12646
|
-
const runCount = task.run_count ?? 0;
|
|
12647
|
-
const lastAt = task.last_run_at ? timeAgo(task.last_run_at) : "no runs";
|
|
12648
|
-
printInfo(` ${marker} ${task.label} ${runCount} run${runCount === 1 ? "" : "s"}, ${lastAt}`);
|
|
12649
|
-
}
|
|
12650
|
-
if (currentTask) {
|
|
12651
|
-
printInfo("");
|
|
12652
|
-
printInfo(`Current task: ${currentTask.label}`);
|
|
12653
|
-
if (contextFiles && contextFiles.length > 0) {
|
|
12654
|
-
printInfo(` Files in task: ${contextFiles.length} (${contextFiles.slice(0, 3).join(", ")}${contextFiles.length > 3 ? "..." : ""})`);
|
|
12655
|
-
}
|
|
12656
|
-
}
|
|
12657
|
-
}
|
|
12658
|
-
if (opts.history) {
|
|
12659
|
-
const runsResult = await apiRequest({
|
|
12660
|
-
method: "GET",
|
|
12661
|
-
path: `/runs?limit=${opts.limit}`,
|
|
12662
|
-
serviceUrl,
|
|
12663
|
-
token,
|
|
12664
|
-
verbose: globals.verbose
|
|
12665
|
-
});
|
|
12666
|
-
if (runsResult.ok && runsResult.data.runs.length > 0) {
|
|
12667
|
-
printInfo("");
|
|
12668
|
-
printInfo("--- Recent Runs ---");
|
|
12669
|
-
printInfo(`${"Run ID".padEnd(32)} ${"Decision".padEnd(10)}${"Q".padEnd(4)}${"S".padEnd(4)}${"Findings".padEnd(32)}Date`);
|
|
12670
|
-
for (const run of runsResult.data.runs) {
|
|
12671
|
-
const q = run.quality_score != null ? `${run.quality_score}` : "-";
|
|
12672
|
-
const s = run.security_score != null ? `${run.security_score}` : "-";
|
|
12673
|
-
const findings = formatFindingsSummary(run.findings_count);
|
|
12674
|
-
const date = run.created_at.slice(0, 19).replace("T", " ");
|
|
12675
|
-
printInfo(`${run.run_id.padEnd(32)} ${run.gate_decision.padEnd(10)}${q.padEnd(4)}${s.padEnd(4)}${findings.padEnd(32)}${date}`);
|
|
12676
|
-
}
|
|
12677
|
-
}
|
|
12678
|
-
}
|
|
12679
|
-
});
|
|
12680
|
-
}
|
|
12681
|
-
|
|
12682
|
-
// src/commands/feedback.ts
|
|
12683
|
-
function registerFeedbackCommand(program2) {
|
|
12684
|
-
const feedbackCmd = program2.command("feedback").description("Send feedback to the Verity team");
|
|
12685
|
-
feedbackCmd.command("message <text>").description("Send general feedback").option("--session-id <id>", "Session identifier").option("--model <name>", "Agent model name").action(async (message, opts) => {
|
|
12686
|
-
const globals = program2.opts();
|
|
12687
|
-
await sendGeneralFeedback(message, opts, globals);
|
|
12688
|
-
});
|
|
12689
|
-
feedbackCmd.command("finding <run-id> <pattern-id> <action> [note]").description("Submit per-finding feedback (false_positive, acknowledged, useful, etc.)").option("--file <path>", "File path the finding applies to").option("--line <n>", "Line number", parseInt).action(async (runId, patternId, action, note, opts) => {
|
|
12690
|
-
const globals = program2.opts();
|
|
12691
|
-
const validActions = ["false_positive", "acknowledged", "will_fix_later", "wrong_severity", "useful"];
|
|
12692
|
-
if (!validActions.includes(action)) {
|
|
12693
|
-
printError(`Invalid action "${action}". Must be one of: ${validActions.join(", ")}`);
|
|
12694
|
-
process.exit(1);
|
|
12695
|
-
}
|
|
12696
|
-
const tokenResult = await resolveToken(globals.token);
|
|
12697
|
-
if (!tokenResult.ok) {
|
|
12698
|
-
printError(tokenResult.error);
|
|
12699
|
-
process.exit(1);
|
|
12700
|
-
}
|
|
12701
|
-
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
12702
|
-
if (!urlResult.ok) {
|
|
12703
|
-
printError(urlResult.error);
|
|
12704
|
-
process.exit(1);
|
|
12705
|
-
}
|
|
12706
|
-
const body = {
|
|
12707
|
-
run_id: runId,
|
|
12708
|
-
pattern_id: patternId,
|
|
12709
|
-
action
|
|
12710
|
-
};
|
|
12711
|
-
if (note) body.note = note;
|
|
12712
|
-
if (opts.file) body.file_path = opts.file;
|
|
12713
|
-
if (opts.line != null) body.line = opts.line;
|
|
12714
|
-
const result = await apiRequest({
|
|
12715
|
-
method: "POST",
|
|
12716
|
-
path: "/feedback/findings",
|
|
12717
|
-
serviceUrl: urlResult.data,
|
|
12718
|
-
token: tokenResult.data.token,
|
|
12719
|
-
body,
|
|
12720
|
-
verbose: globals.verbose
|
|
12721
|
-
});
|
|
12722
|
-
if (!result.ok) {
|
|
12723
|
-
printError(`Couldn't submit finding feedback: ${result.error}`);
|
|
12724
|
-
process.exit(1);
|
|
12725
|
-
}
|
|
12726
|
-
const status = result.data.suppression_active ? "Suppression active \u2014 this pattern will be skipped in future runs for matching files." : "Feedback recorded.";
|
|
12727
|
-
printInfo(status);
|
|
12728
|
-
});
|
|
12729
|
-
feedbackCmd.argument("[message]", "Feedback message (for backwards compat)").option("--session-id <id>", "Session identifier").option("--model <name>", "Agent model name").action(async (message, opts) => {
|
|
12730
|
-
if (!message) return;
|
|
12731
|
-
const globals = program2.opts();
|
|
12732
|
-
await sendGeneralFeedback(message, opts, globals);
|
|
12733
|
-
});
|
|
12734
|
-
}
|
|
12735
|
-
async function sendGeneralFeedback(message, opts, globals) {
|
|
12736
|
-
const tokenResult = await resolveToken(globals.token);
|
|
12737
|
-
if (!tokenResult.ok) {
|
|
12738
|
-
printError(tokenResult.error);
|
|
12739
|
-
printInfo(`Your message: ${message}`);
|
|
12740
|
-
process.exit(1);
|
|
12741
|
-
}
|
|
12742
|
-
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
12743
|
-
if (!urlResult.ok) {
|
|
12744
|
-
printError(urlResult.error);
|
|
12745
|
-
printInfo(`Your message: ${message}`);
|
|
12746
|
-
process.exit(1);
|
|
12747
|
-
}
|
|
12748
|
-
const body = { message };
|
|
12749
|
-
const sessionId = opts.sessionId ?? process.env.CLAUDE_SESSION_ID;
|
|
12750
|
-
const model = opts.model ?? process.env.CLAUDE_MODEL ?? "unknown";
|
|
12751
|
-
if (sessionId) body.session_id = sessionId;
|
|
12752
|
-
if (model) body.agent_model = model;
|
|
12753
|
-
const result = await apiRequest({
|
|
12754
|
-
method: "POST",
|
|
12755
|
-
path: "/feedback",
|
|
12756
|
-
serviceUrl: urlResult.data,
|
|
12757
|
-
token: tokenResult.data.token,
|
|
12758
|
-
body,
|
|
12759
|
-
verbose: globals.verbose
|
|
12760
|
-
});
|
|
12761
|
-
if (!result.ok) {
|
|
12762
|
-
printError(`Couldn't send feedback: ${result.error}`);
|
|
12763
|
-
printInfo(`Your message: ${message}`);
|
|
12764
|
-
process.exit(1);
|
|
12765
|
-
}
|
|
12766
|
-
printInfo("Thanks, feedback sent!");
|
|
12767
|
-
}
|
|
12768
|
-
|
|
12769
|
-
// src/commands/analyze.ts
|
|
12770
|
-
var import_node_fs19 = require("node:fs");
|
|
12771
|
-
var import_node_path14 = require("node:path");
|
|
12772
|
-
|
|
12773
|
-
// src/lib/git.ts
|
|
12774
|
-
var import_node_child_process5 = require("node:child_process");
|
|
12775
|
-
var import_node_fs7 = require("node:fs");
|
|
12776
|
-
var import_node_path7 = require("node:path");
|
|
12777
|
-
function resolveFile(relpath) {
|
|
12778
|
-
if ((0, import_node_fs7.existsSync)(relpath)) return relpath;
|
|
12779
|
-
if ((0, import_node_fs7.existsSync)(".claude/worktrees")) {
|
|
12780
|
-
try {
|
|
12781
|
-
const entries = (0, import_node_fs7.readdirSync)(".claude/worktrees", { withFileTypes: true });
|
|
12782
|
-
for (const entry of entries) {
|
|
12783
|
-
if (!entry.isDirectory()) continue;
|
|
12784
|
-
const candidate = (0, import_node_path7.join)(".claude/worktrees", entry.name, relpath);
|
|
12785
|
-
if ((0, import_node_fs7.existsSync)(candidate)) return candidate;
|
|
12786
|
-
}
|
|
12787
|
-
} catch {
|
|
12788
|
-
}
|
|
12789
|
-
}
|
|
12790
|
-
return null;
|
|
12791
|
-
}
|
|
12792
|
-
function execGit(cmd) {
|
|
12793
|
-
try {
|
|
12794
|
-
return (0, import_node_child_process5.execSync)(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
12795
|
-
} catch {
|
|
12796
|
-
return "";
|
|
12797
|
-
}
|
|
12798
|
-
}
|
|
12799
|
-
function splitLines(s) {
|
|
12800
|
-
return s.split("\n").filter((l) => l.length > 0);
|
|
12801
|
-
}
|
|
12802
|
-
var SHA_RE = /^[0-9a-f]{40}$/;
|
|
12803
|
-
function readBaselineSha() {
|
|
12804
|
-
if (!(0, import_node_fs7.existsSync)(BASELINE_SHA_FILE)) return null;
|
|
12805
|
-
let sha;
|
|
12806
|
-
try {
|
|
12807
|
-
sha = (0, import_node_fs7.readFileSync)(BASELINE_SHA_FILE, "utf-8").trim();
|
|
12808
|
-
} catch {
|
|
12809
|
-
return null;
|
|
12810
|
-
}
|
|
12811
|
-
if (!SHA_RE.test(sha)) return null;
|
|
12812
|
-
const reachable = execGit(`git cat-file -e ${sha}^{commit} 2>/dev/null && echo ok`) === "ok";
|
|
12813
|
-
if (!reachable) {
|
|
12814
|
-
try {
|
|
12815
|
-
(0, import_node_fs7.unlinkSync)(BASELINE_SHA_FILE);
|
|
12816
|
-
} catch {
|
|
12817
|
-
}
|
|
12818
|
-
return null;
|
|
12819
|
-
}
|
|
12820
|
-
return sha;
|
|
12821
|
-
}
|
|
12822
|
-
function writeBaselineSha(sha) {
|
|
12823
|
-
if (!SHA_RE.test(sha)) return;
|
|
12824
|
-
try {
|
|
12825
|
-
(0, import_node_fs7.mkdirSync)((0, import_node_path7.dirname)(BASELINE_SHA_FILE), { recursive: true });
|
|
12826
|
-
(0, import_node_fs7.writeFileSync)(BASELINE_SHA_FILE, sha);
|
|
12827
|
-
} catch {
|
|
12828
|
-
}
|
|
12829
|
-
}
|
|
12830
|
-
function getChangedFiles() {
|
|
12831
|
-
const sets = /* @__PURE__ */ new Set();
|
|
12832
|
-
let hasRecentCommitFiles = false;
|
|
12833
|
-
for (const f of splitLines(execGit("git diff --name-only HEAD"))) sets.add(f);
|
|
12834
|
-
for (const f of splitLines(execGit("git diff --name-only --cached"))) sets.add(f);
|
|
12835
|
-
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) sets.add(f);
|
|
12836
|
-
const baseline = readBaselineSha();
|
|
12837
|
-
if (baseline) {
|
|
12838
|
-
const committed = splitLines(execGit(`git diff --name-only ${baseline}..HEAD`));
|
|
12839
|
-
if (committed.length > 0) {
|
|
12840
|
-
hasRecentCommitFiles = true;
|
|
12841
|
-
for (const f of committed) sets.add(f);
|
|
12842
|
-
}
|
|
12843
|
-
} else {
|
|
12844
|
-
const headTimestamp = parseInt(execGit("git log -1 --format=%ct HEAD"), 10) || 0;
|
|
12845
|
-
const commitAge = Math.floor(Date.now() / 1e3) - headTimestamp;
|
|
12846
|
-
const hasUnstaged = splitLines(execGit("git diff --name-only HEAD")).length > 0;
|
|
12847
|
-
const hasStaged = splitLines(execGit("git diff --name-only --cached")).length > 0;
|
|
12848
|
-
if (commitAge < 120 && !hasUnstaged && !hasStaged) {
|
|
12849
|
-
const recentFiles = splitLines(execGit("git diff --name-only HEAD~1..HEAD"));
|
|
12850
|
-
if (recentFiles.length > 0) {
|
|
12851
|
-
hasRecentCommitFiles = true;
|
|
12852
|
-
for (const f of recentFiles) sets.add(f);
|
|
12853
|
-
}
|
|
12854
|
-
}
|
|
12855
|
-
}
|
|
12856
|
-
for (const f of getWorktreeFiles()) sets.add(f);
|
|
12857
|
-
const filtered = Array.from(sets).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
12858
|
-
return { files: filtered, hasRecentCommitFiles };
|
|
12859
|
-
}
|
|
12860
|
-
function getStagedFiles() {
|
|
12861
|
-
return splitLines(execGit("git diff --cached --name-only")).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
12862
|
-
}
|
|
12863
|
-
function getDirtyFiles() {
|
|
12864
|
-
const set = /* @__PURE__ */ new Set();
|
|
12865
|
-
for (const f of splitLines(execGit("git diff --name-only HEAD"))) set.add(f);
|
|
12866
|
-
for (const f of splitLines(execGit("git diff --name-only --cached"))) set.add(f);
|
|
12867
|
-
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
|
|
12868
|
-
return Array.from(set).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
12869
|
-
}
|
|
12870
|
-
function showContentAtRef(ref, repoRelPath) {
|
|
12871
|
-
if (!ref || ref === "no-git") return null;
|
|
12872
|
-
const normalizedPath = repoRelPath.replace(/\\/g, "/");
|
|
12873
|
-
try {
|
|
12874
|
-
return (0, import_node_child_process5.execFileSync)("git", ["show", `${ref}:${normalizedPath}`], {
|
|
12875
|
-
encoding: "utf-8",
|
|
12876
|
-
maxBuffer: 64 * 1024 * 1024,
|
|
12877
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
12878
|
-
});
|
|
12879
|
-
} catch {
|
|
12880
|
-
return null;
|
|
12881
|
-
}
|
|
12882
|
-
}
|
|
12883
|
-
function getPushRangeFiles() {
|
|
12884
|
-
const diff = (range) => splitLines(execGit(`git diff --name-only ${range}`)).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
12885
|
-
const resolvers = [
|
|
12886
|
-
() => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{push}") ? "@{push}..HEAD" : null,
|
|
12887
|
-
() => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{upstream}") ? "@{upstream}..HEAD" : null,
|
|
12888
|
-
() => {
|
|
12889
|
-
const branch = execGit("git rev-parse --abbrev-ref HEAD");
|
|
12890
|
-
return branch && branch !== "HEAD" && execGit(`git rev-parse --verify -q origin/${branch}`) ? `origin/${branch}..HEAD` : null;
|
|
12891
|
-
}
|
|
12892
|
-
];
|
|
12893
|
-
for (const resolve of resolvers) {
|
|
12894
|
-
const range = resolve();
|
|
12895
|
-
if (range) return { files: diff(range), range };
|
|
12896
|
-
}
|
|
12897
|
-
const baseline = readBaselineSha();
|
|
12898
|
-
if (baseline) {
|
|
12899
|
-
const files = diff(`${baseline}..HEAD`);
|
|
12900
|
-
if (files.length > 0) return { files, range: `${baseline}..HEAD` };
|
|
12901
|
-
}
|
|
12902
|
-
const last = diff("HEAD~1..HEAD");
|
|
12903
|
-
return { files: last, range: last.length > 0 ? "HEAD~1..HEAD" : null };
|
|
12904
|
-
}
|
|
12905
|
-
function getPushRangeMessages() {
|
|
12906
|
-
const { range } = getPushRangeFiles();
|
|
12907
|
-
if (!range) return "";
|
|
12908
|
-
return execGit(`git log ${range} --format=%B%x00`).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
|
|
12909
|
-
}
|
|
12910
|
-
function getWorktreeFiles() {
|
|
12911
|
-
const result = [];
|
|
12912
|
-
const worktreeDir = ".claude/worktrees";
|
|
12913
|
-
if (!(0, import_node_fs7.existsSync)(worktreeDir)) return result;
|
|
12914
|
-
try {
|
|
12915
|
-
const fiveMinAgo = Date.now() - 5 * 60 * 1e3;
|
|
12916
|
-
const entries = (0, import_node_fs7.readdirSync)(worktreeDir, { withFileTypes: true });
|
|
12917
|
-
for (const entry of entries) {
|
|
12918
|
-
if (!entry.isDirectory()) continue;
|
|
12919
|
-
const wtDir = (0, import_node_path7.join)(worktreeDir, entry.name);
|
|
12920
|
-
scanDir(wtDir, wtDir, fiveMinAgo, result);
|
|
13049
|
+
const r = mem.recent_runs;
|
|
13050
|
+
printInfo("");
|
|
13051
|
+
printInfo("--- Last Run ---");
|
|
13052
|
+
printInfo(`Decision: ${r.last_gate_decision ?? "none"}`);
|
|
13053
|
+
printInfo(`Quality: ${r.last_quality_score ?? "-"}/10`);
|
|
13054
|
+
printInfo(`Security: ${r.last_security_score ?? "-"}/10`);
|
|
13055
|
+
printInfo(`Trend: ${r.trend}`);
|
|
13056
|
+
printInfo(`Runs: ${r.count} recorded`);
|
|
12921
13057
|
}
|
|
12922
|
-
|
|
12923
|
-
|
|
12924
|
-
|
|
12925
|
-
|
|
12926
|
-
|
|
12927
|
-
|
|
12928
|
-
|
|
12929
|
-
|
|
12930
|
-
|
|
12931
|
-
|
|
12932
|
-
|
|
12933
|
-
|
|
12934
|
-
|
|
12935
|
-
|
|
12936
|
-
|
|
12937
|
-
|
|
12938
|
-
|
|
12939
|
-
|
|
12940
|
-
|
|
12941
|
-
|
|
12942
|
-
|
|
12943
|
-
|
|
13058
|
+
if (mem.pending_items && mem.pending_items.length > 0) {
|
|
13059
|
+
printInfo("");
|
|
13060
|
+
printInfo("--- Pending Items ---");
|
|
13061
|
+
for (const item of mem.pending_items) {
|
|
13062
|
+
printInfo(` [${item.priority.toUpperCase()}] ${item.description}`);
|
|
13063
|
+
}
|
|
13064
|
+
}
|
|
13065
|
+
const recentTasks = mem.recent_tasks;
|
|
13066
|
+
const currentTask = mem.current_task;
|
|
13067
|
+
const contextFiles = mem.context_files;
|
|
13068
|
+
if (recentTasks && recentTasks.length > 0) {
|
|
13069
|
+
printInfo("");
|
|
13070
|
+
printInfo("--- Active Tasks ---");
|
|
13071
|
+
for (const task of recentTasks) {
|
|
13072
|
+
const isCurrent = currentTask && task.id === currentTask.id;
|
|
13073
|
+
const marker = isCurrent ? "\u25CF" : "\u25CB";
|
|
13074
|
+
const runCount = task.run_count ?? 0;
|
|
13075
|
+
const lastAt = task.last_run_at ? timeAgo(task.last_run_at) : "no runs";
|
|
13076
|
+
printInfo(` ${marker} ${task.label} ${runCount} run${runCount === 1 ? "" : "s"}, ${lastAt}`);
|
|
13077
|
+
}
|
|
13078
|
+
if (currentTask) {
|
|
13079
|
+
printInfo("");
|
|
13080
|
+
printInfo(`Current task: ${currentTask.label}`);
|
|
13081
|
+
if (contextFiles && contextFiles.length > 0) {
|
|
13082
|
+
printInfo(` Files in task: ${contextFiles.length} (${contextFiles.slice(0, 3).join(", ")}${contextFiles.length > 3 ? "..." : ""})`);
|
|
13083
|
+
}
|
|
13084
|
+
}
|
|
13085
|
+
}
|
|
13086
|
+
if (opts.history) {
|
|
13087
|
+
const runsResult = await apiRequest({
|
|
13088
|
+
method: "GET",
|
|
13089
|
+
path: `/runs?limit=${opts.limit}`,
|
|
13090
|
+
serviceUrl,
|
|
13091
|
+
token,
|
|
13092
|
+
verbose: globals.verbose
|
|
13093
|
+
});
|
|
13094
|
+
if (runsResult.ok && runsResult.data.runs.length > 0) {
|
|
13095
|
+
printInfo("");
|
|
13096
|
+
printInfo("--- Recent Runs ---");
|
|
13097
|
+
printInfo(`${"Run ID".padEnd(32)} ${"Decision".padEnd(10)}${"Q".padEnd(4)}${"S".padEnd(4)}${"Findings".padEnd(32)}Date`);
|
|
13098
|
+
for (const run of runsResult.data.runs) {
|
|
13099
|
+
const q = run.quality_score != null ? `${run.quality_score}` : "-";
|
|
13100
|
+
const s = run.security_score != null ? `${run.security_score}` : "-";
|
|
13101
|
+
const findings = formatFindingsSummary(run.findings_count);
|
|
13102
|
+
const date = run.created_at.slice(0, 19).replace("T", " ");
|
|
13103
|
+
printInfo(`${run.run_id.padEnd(32)} ${run.gate_decision.padEnd(10)}${q.padEnd(4)}${s.padEnd(4)}${findings.padEnd(32)}${date}`);
|
|
12944
13104
|
}
|
|
12945
13105
|
}
|
|
12946
13106
|
}
|
|
12947
|
-
} catch {
|
|
12948
|
-
}
|
|
12949
|
-
}
|
|
12950
|
-
function filterAnalyzable(files) {
|
|
12951
|
-
return files.filter((f) => {
|
|
12952
|
-
const ext = (0, import_node_path7.extname)(f).slice(1);
|
|
12953
|
-
return ANALYZABLE_EXTENSIONS.has(ext);
|
|
12954
13107
|
});
|
|
12955
13108
|
}
|
|
12956
|
-
|
|
12957
|
-
|
|
12958
|
-
|
|
12959
|
-
|
|
12960
|
-
|
|
12961
|
-
const
|
|
12962
|
-
|
|
12963
|
-
|
|
12964
|
-
|
|
13109
|
+
|
|
13110
|
+
// src/commands/feedback.ts
|
|
13111
|
+
function registerFeedbackCommand(program2) {
|
|
13112
|
+
const feedbackCmd = program2.command("feedback").description("Send feedback to the Verity team");
|
|
13113
|
+
feedbackCmd.command("message <text>").description("Send general feedback").option("--session-id <id>", "Session identifier").option("--model <name>", "Agent model name").action(async (message, opts) => {
|
|
13114
|
+
const globals = program2.opts();
|
|
13115
|
+
await sendGeneralFeedback(message, opts, globals);
|
|
13116
|
+
});
|
|
13117
|
+
feedbackCmd.command("finding <run-id> <pattern-id> <action> [note]").description("Submit per-finding feedback (false_positive, acknowledged, useful, etc.)").option("--file <path>", "File path the finding applies to").option("--line <n>", "Line number", parseInt).action(async (runId, patternId, action, note, opts) => {
|
|
13118
|
+
const globals = program2.opts();
|
|
13119
|
+
const validActions = ["false_positive", "acknowledged", "will_fix_later", "wrong_severity", "useful"];
|
|
13120
|
+
if (!validActions.includes(action)) {
|
|
13121
|
+
printError(`Invalid action "${action}". Must be one of: ${validActions.join(", ")}`);
|
|
13122
|
+
process.exit(1);
|
|
13123
|
+
}
|
|
13124
|
+
const tokenResult = await resolveToken(globals.token);
|
|
13125
|
+
if (!tokenResult.ok) {
|
|
13126
|
+
printError(tokenResult.error);
|
|
13127
|
+
process.exit(1);
|
|
13128
|
+
}
|
|
13129
|
+
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
13130
|
+
if (!urlResult.ok) {
|
|
13131
|
+
printError(urlResult.error);
|
|
13132
|
+
process.exit(1);
|
|
13133
|
+
}
|
|
13134
|
+
const body = {
|
|
13135
|
+
run_id: runId,
|
|
13136
|
+
pattern_id: patternId,
|
|
13137
|
+
action
|
|
13138
|
+
};
|
|
13139
|
+
if (note) body.note = note;
|
|
13140
|
+
if (opts.file) body.file_path = opts.file;
|
|
13141
|
+
if (opts.line != null) body.line = opts.line;
|
|
13142
|
+
const result = await apiRequest({
|
|
13143
|
+
method: "POST",
|
|
13144
|
+
path: "/feedback/findings",
|
|
13145
|
+
serviceUrl: urlResult.data,
|
|
13146
|
+
token: tokenResult.data.token,
|
|
13147
|
+
body,
|
|
13148
|
+
verbose: globals.verbose
|
|
13149
|
+
});
|
|
13150
|
+
if (!result.ok) {
|
|
13151
|
+
printError(`Couldn't submit finding feedback: ${result.error}`);
|
|
13152
|
+
process.exit(1);
|
|
13153
|
+
}
|
|
13154
|
+
const status = result.data.suppression_active ? "Suppression active \u2014 this pattern will be skipped in future runs for matching files." : "Feedback recorded.";
|
|
13155
|
+
printInfo(status);
|
|
13156
|
+
});
|
|
13157
|
+
feedbackCmd.argument("[message]", "Feedback message (for backwards compat)").option("--session-id <id>", "Session identifier").option("--model <name>", "Agent model name").action(async (message, opts) => {
|
|
13158
|
+
if (!message) return;
|
|
13159
|
+
const globals = program2.opts();
|
|
13160
|
+
await sendGeneralFeedback(message, opts, globals);
|
|
12965
13161
|
});
|
|
12966
13162
|
}
|
|
12967
|
-
function
|
|
12968
|
-
|
|
12969
|
-
|
|
12970
|
-
|
|
12971
|
-
}
|
|
12972
|
-
|
|
12973
|
-
|
|
12974
|
-
|
|
12975
|
-
|
|
12976
|
-
|
|
12977
|
-
|
|
12978
|
-
|
|
12979
|
-
|
|
13163
|
+
async function sendGeneralFeedback(message, opts, globals) {
|
|
13164
|
+
const tokenResult = await resolveToken(globals.token);
|
|
13165
|
+
if (!tokenResult.ok) {
|
|
13166
|
+
printError(tokenResult.error);
|
|
13167
|
+
printInfo(`Your message: ${message}`);
|
|
13168
|
+
process.exit(1);
|
|
13169
|
+
}
|
|
13170
|
+
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
13171
|
+
if (!urlResult.ok) {
|
|
13172
|
+
printError(urlResult.error);
|
|
13173
|
+
printInfo(`Your message: ${message}`);
|
|
13174
|
+
process.exit(1);
|
|
13175
|
+
}
|
|
13176
|
+
const body = { message };
|
|
13177
|
+
const sessionId = opts.sessionId ?? process.env.CLAUDE_SESSION_ID;
|
|
13178
|
+
const model = opts.model ?? process.env.CLAUDE_MODEL ?? "unknown";
|
|
13179
|
+
if (sessionId) body.session_id = sessionId;
|
|
13180
|
+
if (model) body.agent_model = model;
|
|
13181
|
+
const result = await apiRequest({
|
|
13182
|
+
method: "POST",
|
|
13183
|
+
path: "/feedback",
|
|
13184
|
+
serviceUrl: urlResult.data,
|
|
13185
|
+
token: tokenResult.data.token,
|
|
13186
|
+
body,
|
|
13187
|
+
verbose: globals.verbose
|
|
13188
|
+
});
|
|
13189
|
+
if (!result.ok) {
|
|
13190
|
+
printError(`Couldn't send feedback: ${result.error}`);
|
|
13191
|
+
printInfo(`Your message: ${message}`);
|
|
13192
|
+
process.exit(1);
|
|
13193
|
+
}
|
|
13194
|
+
printInfo("Thanks, feedback sent!");
|
|
12980
13195
|
}
|
|
12981
13196
|
|
|
13197
|
+
// src/commands/analyze.ts
|
|
13198
|
+
var import_node_fs19 = require("node:fs");
|
|
13199
|
+
var import_node_path15 = require("node:path");
|
|
13200
|
+
|
|
12982
13201
|
// src/lib/files.ts
|
|
12983
13202
|
var import_node_fs8 = require("node:fs");
|
|
12984
|
-
var
|
|
13203
|
+
var import_node_path9 = require("node:path");
|
|
12985
13204
|
var LANG_MAP = {
|
|
12986
13205
|
// Analyzable (static analysis + Gemini)
|
|
12987
13206
|
ts: "typescript",
|
|
@@ -13049,7 +13268,7 @@ var LANG_MAP = {
|
|
|
13049
13268
|
mk: "make"
|
|
13050
13269
|
};
|
|
13051
13270
|
function detectLanguage(filepath) {
|
|
13052
|
-
const ext = (0,
|
|
13271
|
+
const ext = (0, import_node_path9.extname)(filepath).slice(1);
|
|
13053
13272
|
return LANG_MAP[ext] ?? ext;
|
|
13054
13273
|
}
|
|
13055
13274
|
function sortByMtime(files) {
|
|
@@ -13335,7 +13554,7 @@ function runCodacyAnalysis(files) {
|
|
|
13335
13554
|
|
|
13336
13555
|
// src/lib/specs.ts
|
|
13337
13556
|
var import_node_fs11 = require("node:fs");
|
|
13338
|
-
var
|
|
13557
|
+
var import_node_path10 = require("node:path");
|
|
13339
13558
|
var SPEC_CANDIDATES = [
|
|
13340
13559
|
"CLAUDE.md",
|
|
13341
13560
|
"AGENTS.md",
|
|
@@ -13397,7 +13616,7 @@ function findMdFiles(dir, maxDepth, depth = 0) {
|
|
|
13397
13616
|
try {
|
|
13398
13617
|
const entries = (0, import_node_fs11.readdirSync)(dir, { withFileTypes: true });
|
|
13399
13618
|
for (const entry of entries) {
|
|
13400
|
-
const fullPath = (0,
|
|
13619
|
+
const fullPath = (0, import_node_path10.join)(dir, entry.name);
|
|
13401
13620
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
13402
13621
|
result.push(fullPath);
|
|
13403
13622
|
} else if (entry.isDirectory() && depth < maxDepth - 1) {
|
|
@@ -13409,7 +13628,7 @@ function findMdFiles(dir, maxDepth, depth = 0) {
|
|
|
13409
13628
|
return result;
|
|
13410
13629
|
}
|
|
13411
13630
|
function discoverPlans() {
|
|
13412
|
-
const homePlansDir = (0,
|
|
13631
|
+
const homePlansDir = (0, import_node_path10.join)(process.env.HOME ?? "", ".claude", "plans");
|
|
13413
13632
|
const localPlansDir = ".claude/plans";
|
|
13414
13633
|
const candidates = [];
|
|
13415
13634
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -13419,7 +13638,7 @@ function discoverPlans() {
|
|
|
13419
13638
|
for (const f of (0, import_node_fs11.readdirSync)(plansDir)) {
|
|
13420
13639
|
if (!f.endsWith(".md") || seen.has(f)) continue;
|
|
13421
13640
|
seen.add(f);
|
|
13422
|
-
const fullPath = (0,
|
|
13641
|
+
const fullPath = (0, import_node_path10.join)(plansDir, f);
|
|
13423
13642
|
try {
|
|
13424
13643
|
const stat3 = (0, import_node_fs11.statSync)(fullPath);
|
|
13425
13644
|
candidates.push({ name: f, path: fullPath, mtime: stat3.mtimeMs, size: stat3.size });
|
|
@@ -13444,7 +13663,7 @@ function discoverPlans() {
|
|
|
13444
13663
|
|
|
13445
13664
|
// src/lib/snapshot.ts
|
|
13446
13665
|
var import_node_fs12 = require("node:fs");
|
|
13447
|
-
var
|
|
13666
|
+
var import_node_path11 = require("node:path");
|
|
13448
13667
|
var import_node_child_process7 = require("node:child_process");
|
|
13449
13668
|
function generateSnapshotDiffs(files) {
|
|
13450
13669
|
if (!(0, import_node_fs12.existsSync)(SNAPSHOT_DIR)) {
|
|
@@ -13452,7 +13671,7 @@ function generateSnapshotDiffs(files) {
|
|
|
13452
13671
|
}
|
|
13453
13672
|
const diffs = [];
|
|
13454
13673
|
for (const file of files) {
|
|
13455
|
-
const snapshotPath = (0,
|
|
13674
|
+
const snapshotPath = (0, import_node_path11.join)(SNAPSHOT_DIR, file.path);
|
|
13456
13675
|
const language = file.language ?? detectLanguage(file.path);
|
|
13457
13676
|
if ((0, import_node_fs12.existsSync)(snapshotPath)) {
|
|
13458
13677
|
const oldContent = (0, import_node_fs12.readFileSync)(snapshotPath, "utf-8");
|
|
@@ -13479,16 +13698,16 @@ ${addedLines}`,
|
|
|
13479
13698
|
function saveSnapshots(files) {
|
|
13480
13699
|
const snapshotPaths = /* @__PURE__ */ new Set();
|
|
13481
13700
|
for (const file of files) {
|
|
13482
|
-
const snapshotPath = (0,
|
|
13701
|
+
const snapshotPath = (0, import_node_path11.join)(SNAPSHOT_DIR, file.path);
|
|
13483
13702
|
snapshotPaths.add(snapshotPath);
|
|
13484
|
-
(0, import_node_fs12.mkdirSync)((0,
|
|
13703
|
+
(0, import_node_fs12.mkdirSync)((0, import_node_path11.dirname)(snapshotPath), { recursive: true });
|
|
13485
13704
|
(0, import_node_fs12.writeFileSync)(snapshotPath, file.content);
|
|
13486
13705
|
}
|
|
13487
13706
|
cleanStaleSnapshots(SNAPSHOT_DIR, snapshotPaths);
|
|
13488
13707
|
}
|
|
13489
13708
|
function computeDiff(oldContent, newContent, filePath) {
|
|
13490
|
-
const tmpOld = (0,
|
|
13491
|
-
const tmpNew = (0,
|
|
13709
|
+
const tmpOld = (0, import_node_path11.join)(SNAPSHOT_DIR, ".diff-old.tmp");
|
|
13710
|
+
const tmpNew = (0, import_node_path11.join)(SNAPSHOT_DIR, ".diff-new.tmp");
|
|
13492
13711
|
try {
|
|
13493
13712
|
(0, import_node_fs12.mkdirSync)(SNAPSHOT_DIR, { recursive: true });
|
|
13494
13713
|
(0, import_node_fs12.writeFileSync)(tmpOld, oldContent);
|
|
@@ -13521,7 +13740,7 @@ function cleanStaleSnapshots(dir, keepSet) {
|
|
|
13521
13740
|
const entries = (0, import_node_fs12.readdirSync)(dir, { withFileTypes: true });
|
|
13522
13741
|
for (const entry of entries) {
|
|
13523
13742
|
if (entry.name.startsWith(".")) continue;
|
|
13524
|
-
const fullPath = (0,
|
|
13743
|
+
const fullPath = (0, import_node_path11.join)(dir, entry.name);
|
|
13525
13744
|
if (entry.isDirectory()) {
|
|
13526
13745
|
cleanStaleSnapshots(fullPath, keepSet);
|
|
13527
13746
|
try {
|
|
@@ -13542,7 +13761,7 @@ function cleanStaleSnapshots(dir, keepSet) {
|
|
|
13542
13761
|
|
|
13543
13762
|
// src/lib/baseline.ts
|
|
13544
13763
|
var import_node_fs13 = require("node:fs");
|
|
13545
|
-
var
|
|
13764
|
+
var import_node_path12 = require("node:path");
|
|
13546
13765
|
var import_node_crypto5 = require("node:crypto");
|
|
13547
13766
|
var BASELINE_VERSION = 1;
|
|
13548
13767
|
var BASELINE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
@@ -13553,13 +13772,13 @@ function sessionKey(sessionId) {
|
|
|
13553
13772
|
return (0, import_node_crypto5.createHash)("sha256").update(sessionId).digest("hex").slice(0, 16);
|
|
13554
13773
|
}
|
|
13555
13774
|
function sessionDir(key) {
|
|
13556
|
-
return (0,
|
|
13775
|
+
return (0, import_node_path12.join)(projectPath(BASELINE_DIR), key);
|
|
13557
13776
|
}
|
|
13558
13777
|
function manifestPath(dir) {
|
|
13559
|
-
return (0,
|
|
13778
|
+
return (0, import_node_path12.join)(dir, "manifest.json");
|
|
13560
13779
|
}
|
|
13561
13780
|
function mirrorPath(dir, repoRelPath) {
|
|
13562
|
-
return (0,
|
|
13781
|
+
return (0, import_node_path12.join)(dir, "files", repoRelPath);
|
|
13563
13782
|
}
|
|
13564
13783
|
function captureBaseline(opts = {}) {
|
|
13565
13784
|
const key = sessionKey(opts.sessionId);
|
|
@@ -13575,7 +13794,7 @@ function captureBaseline(opts = {}) {
|
|
|
13575
13794
|
(0, import_node_fs13.rmSync)(dir, { recursive: true, force: true });
|
|
13576
13795
|
} catch {
|
|
13577
13796
|
}
|
|
13578
|
-
const filesDir = (0,
|
|
13797
|
+
const filesDir = (0, import_node_path12.join)(dir, "files");
|
|
13579
13798
|
const mirrored = [];
|
|
13580
13799
|
try {
|
|
13581
13800
|
(0, import_node_fs13.mkdirSync)(filesDir, { recursive: true });
|
|
@@ -13585,7 +13804,7 @@ function captureBaseline(opts = {}) {
|
|
|
13585
13804
|
if (content === null) continue;
|
|
13586
13805
|
const dest = mirrorPath(dir, p);
|
|
13587
13806
|
try {
|
|
13588
|
-
(0, import_node_fs13.mkdirSync)((0,
|
|
13807
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path12.dirname)(dest), { recursive: true });
|
|
13589
13808
|
(0, import_node_fs13.writeFileSync)(dest, content);
|
|
13590
13809
|
mirrored.push(p);
|
|
13591
13810
|
} catch {
|
|
@@ -13713,7 +13932,7 @@ function pruneOldBaselines() {
|
|
|
13713
13932
|
}
|
|
13714
13933
|
const now = Date.now();
|
|
13715
13934
|
for (const name of entries) {
|
|
13716
|
-
const dir = (0,
|
|
13935
|
+
const dir = (0, import_node_path12.join)(root, name);
|
|
13717
13936
|
const manifest = readManifest(dir);
|
|
13718
13937
|
if (!manifest) {
|
|
13719
13938
|
try {
|
|
@@ -13812,7 +14031,7 @@ function gatherContextFiles(contextPaths, deltaFiles) {
|
|
|
13812
14031
|
|
|
13813
14032
|
// src/lib/cache-cleanup.ts
|
|
13814
14033
|
var import_node_fs16 = require("node:fs");
|
|
13815
|
-
var
|
|
14034
|
+
var import_node_path13 = require("node:path");
|
|
13816
14035
|
var CACHE_TTL_DAYS = 7;
|
|
13817
14036
|
function pruneStaleCache() {
|
|
13818
14037
|
try {
|
|
@@ -13820,7 +14039,7 @@ function pruneStaleCache() {
|
|
|
13820
14039
|
const cutoff = Date.now() - CACHE_TTL_DAYS * 24 * 3600 * 1e3;
|
|
13821
14040
|
for (const entry of (0, import_node_fs16.readdirSync)(dir)) {
|
|
13822
14041
|
if (!entry.startsWith("pending-")) continue;
|
|
13823
|
-
const path = (0,
|
|
14042
|
+
const path = (0, import_node_path13.join)(dir, entry);
|
|
13824
14043
|
try {
|
|
13825
14044
|
const stat3 = (0, import_node_fs16.statSync)(path);
|
|
13826
14045
|
if (stat3.mtimeMs < cutoff) {
|
|
@@ -14211,9 +14430,9 @@ function capArray(set, max) {
|
|
|
14211
14430
|
}
|
|
14212
14431
|
|
|
14213
14432
|
// src/lib/seed-runner.ts
|
|
14214
|
-
var
|
|
14433
|
+
var import_promises12 = require("node:fs/promises");
|
|
14215
14434
|
var import_node_fs18 = require("node:fs");
|
|
14216
|
-
var
|
|
14435
|
+
var import_node_path14 = require("node:path");
|
|
14217
14436
|
var import_yaml2 = __toESM(require_dist());
|
|
14218
14437
|
|
|
14219
14438
|
// src/lib/seed.ts
|
|
@@ -14457,7 +14676,7 @@ async function runSeed(opts) {
|
|
|
14457
14676
|
}
|
|
14458
14677
|
let standardDoc;
|
|
14459
14678
|
try {
|
|
14460
|
-
const raw = await (0,
|
|
14679
|
+
const raw = await (0, import_promises12.readFile)(STANDARD_FILE, "utf-8");
|
|
14461
14680
|
standardDoc = (0, import_yaml2.parse)(raw);
|
|
14462
14681
|
} catch {
|
|
14463
14682
|
return { created: 0, failed: 0, skipped: "no_standard", candidates: [] };
|
|
@@ -14466,7 +14685,7 @@ async function runSeed(opts) {
|
|
|
14466
14685
|
let readmeContent;
|
|
14467
14686
|
if ((0, import_node_fs18.existsSync)("README.md")) {
|
|
14468
14687
|
try {
|
|
14469
|
-
readmeContent = await (0,
|
|
14688
|
+
readmeContent = await (0, import_promises12.readFile)("README.md", "utf-8");
|
|
14470
14689
|
} catch {
|
|
14471
14690
|
}
|
|
14472
14691
|
}
|
|
@@ -14474,7 +14693,7 @@ async function runSeed(opts) {
|
|
|
14474
14693
|
for (const p of ["CLAUDE.md", ".claude/CLAUDE.md"]) {
|
|
14475
14694
|
if ((0, import_node_fs18.existsSync)(p)) {
|
|
14476
14695
|
try {
|
|
14477
|
-
claudeMdContent = await (0,
|
|
14696
|
+
claudeMdContent = await (0, import_promises12.readFile)(p, "utf-8");
|
|
14478
14697
|
break;
|
|
14479
14698
|
} catch {
|
|
14480
14699
|
}
|
|
@@ -14495,7 +14714,7 @@ async function runSeed(opts) {
|
|
|
14495
14714
|
if (candidates.length === 0) {
|
|
14496
14715
|
return { created: 0, failed: 0, skipped: "no_candidates", candidates: [] };
|
|
14497
14716
|
}
|
|
14498
|
-
const overviewPath = (0,
|
|
14717
|
+
const overviewPath = (0, import_node_path14.join)(MEMORY_DIR, "domain", "project-overview.md");
|
|
14499
14718
|
if ((0, import_node_fs18.existsSync)(overviewPath) && !opts.force) {
|
|
14500
14719
|
return { created: 0, failed: 0, skipped: "already_seeded", candidates };
|
|
14501
14720
|
}
|
|
@@ -14531,10 +14750,10 @@ async function runSeed(opts) {
|
|
|
14531
14750
|
}
|
|
14532
14751
|
const nodeId = res.data.node_id;
|
|
14533
14752
|
const filePathRel = res.data.file_path;
|
|
14534
|
-
const targetPath = (0,
|
|
14753
|
+
const targetPath = (0, import_node_path14.join)(MEMORY_DIR, filePathRel);
|
|
14535
14754
|
try {
|
|
14536
|
-
await (0,
|
|
14537
|
-
await (0,
|
|
14755
|
+
await (0, import_promises12.mkdir)((0, import_node_path14.dirname)(targetPath), { recursive: true });
|
|
14756
|
+
await (0, import_promises12.writeFile)(targetPath, renderNodeMarkdown(c, nodeId, createdAt));
|
|
14538
14757
|
created++;
|
|
14539
14758
|
opts.onCreated?.(nodeId, filePathRel, c);
|
|
14540
14759
|
} catch (err) {
|
|
@@ -14586,6 +14805,27 @@ function passAndExit(reason) {
|
|
|
14586
14805
|
printJsonCompact({ gate_decision: "PASS", systemMessage: `Verity: ${reason}` });
|
|
14587
14806
|
process.exit(0);
|
|
14588
14807
|
}
|
|
14808
|
+
var EMPTY_STATIC = {
|
|
14809
|
+
tool: "@codacy/analysis-cli",
|
|
14810
|
+
findings: [],
|
|
14811
|
+
summary: { total_findings: 0, by_severity: {}, tools_run: [] }
|
|
14812
|
+
};
|
|
14813
|
+
function runLocalStatic(analyzable, securityFiles, baseline, skipStatic) {
|
|
14814
|
+
if (skipStatic || !isCodacyAvailable()) return EMPTY_STATIC;
|
|
14815
|
+
let scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles]));
|
|
14816
|
+
if (baseline) scannable = scannable.filter((f) => changedSinceBaseline(f, baseline));
|
|
14817
|
+
if (scannable.length === 0) return EMPTY_STATIC;
|
|
14818
|
+
return runCodacyAnalysis(scannable);
|
|
14819
|
+
}
|
|
14820
|
+
function localOnlyAndExit(staticResults) {
|
|
14821
|
+
printJsonCompact({
|
|
14822
|
+
gate_decision: "PASS",
|
|
14823
|
+
systemMessage: "Verity: not authenticated \u2014 ran a local static-only check (no deep review, no upload). Run `verity init` to authenticate and enable the full quality gate.",
|
|
14824
|
+
unauthenticated: true,
|
|
14825
|
+
static_results: staticResults
|
|
14826
|
+
});
|
|
14827
|
+
process.exit(0);
|
|
14828
|
+
}
|
|
14589
14829
|
function registerAnalyzeCommand(program2) {
|
|
14590
14830
|
program2.command("analyze").description("Run Verity analysis on changed files (stop hook)").option("--debounce <seconds>", "Skip if last analysis was within N seconds", "30").option("--max-iterations <n>", "Force PASS after N FAIL cycles", "2").option("--max-files <n>", "Max files to send for review", "20").option("--max-file-size <bytes>", "Skip files larger than N bytes", "51200").option("--max-total-size <bytes>", "Stop collecting files at N total bytes", "194560").option("--skip-static", "Skip codacy-analysis").option("--mode <mode>", "Force analysis mode (standard|plan|debug|skip)").option("--json", "Output raw JSON response").action(async (opts) => {
|
|
14591
14831
|
const globals = program2.opts();
|
|
@@ -14636,12 +14876,9 @@ async function runAnalyze(opts, globals) {
|
|
|
14636
14876
|
passAndExit("Reflection-prompt turn \u2014 skipping analysis");
|
|
14637
14877
|
}
|
|
14638
14878
|
const tokenResult = await resolveToken(globals.token);
|
|
14639
|
-
if (!tokenResult.ok) {
|
|
14640
|
-
passAndExit("Not configured yet \u2014 run /verity-setup in Claude Code to enable quality gates.");
|
|
14641
|
-
}
|
|
14642
14879
|
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
14643
|
-
if (!urlResult.ok) {
|
|
14644
|
-
|
|
14880
|
+
if (!tokenResult.ok || !urlResult.ok) {
|
|
14881
|
+
localOnlyAndExit(runLocalStatic(analyzable, securityFiles, baseline, !!opts.skipStatic));
|
|
14645
14882
|
}
|
|
14646
14883
|
const sessionIdForMemory = sessionId || process.env.CLAUDE_SESSION_ID || "";
|
|
14647
14884
|
let contextFilePaths = [];
|
|
@@ -14797,7 +15034,7 @@ async function runAnalyze(opts, globals) {
|
|
|
14797
15034
|
let autoSeedNotice = null;
|
|
14798
15035
|
try {
|
|
14799
15036
|
await ensureMemoryDir();
|
|
14800
|
-
const seedMarker = (0,
|
|
15037
|
+
const seedMarker = (0, import_node_path15.join)(VERITY_DIR, ".seeded");
|
|
14801
15038
|
const hasStandard = (0, import_node_fs19.existsSync)(STANDARD_FILE);
|
|
14802
15039
|
const alreadyTried = (0, import_node_fs19.existsSync)(seedMarker);
|
|
14803
15040
|
if (hasStandard && !alreadyTried) {
|
|
@@ -15215,13 +15452,14 @@ async function runReview(opts, globals) {
|
|
|
15215
15452
|
}
|
|
15216
15453
|
const codeDelta = collectCodeDelta(allFiles);
|
|
15217
15454
|
const tokenResult = await resolveToken(globals.token);
|
|
15218
|
-
if (!tokenResult.ok) {
|
|
15219
|
-
printError("Not configured yet \u2014 run /verity-setup in Claude Code to enable quality gates.");
|
|
15220
|
-
process.exit(0);
|
|
15221
|
-
}
|
|
15222
15455
|
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
15223
|
-
if (!urlResult.ok) {
|
|
15224
|
-
|
|
15456
|
+
if (!tokenResult.ok || !urlResult.ok) {
|
|
15457
|
+
printJsonCompact({
|
|
15458
|
+
gate_decision: "PASS",
|
|
15459
|
+
systemMessage: "Verity: not authenticated \u2014 showing local static results only (no deep review, no upload). Run `verity init` to authenticate and unlock the full review.",
|
|
15460
|
+
unauthenticated: true,
|
|
15461
|
+
static_results: staticResults
|
|
15462
|
+
});
|
|
15225
15463
|
process.exit(0);
|
|
15226
15464
|
}
|
|
15227
15465
|
let specs;
|
|
@@ -15290,9 +15528,9 @@ async function runReview(opts, globals) {
|
|
|
15290
15528
|
|
|
15291
15529
|
// src/commands/guard.ts
|
|
15292
15530
|
var import_node_fs22 = require("node:fs");
|
|
15293
|
-
var
|
|
15531
|
+
var import_node_path16 = require("node:path");
|
|
15294
15532
|
var GUARD_BLOCK_CAP = 2;
|
|
15295
|
-
var GUARD_ITER_FILE = (0,
|
|
15533
|
+
var GUARD_ITER_FILE = (0, import_node_path16.join)(VERITY_DIR, ".guard-iteration");
|
|
15296
15534
|
function readPreToolUseStdin() {
|
|
15297
15535
|
const empty = { command: "", cwd: null, sessionId: null };
|
|
15298
15536
|
return new Promise((resolve) => {
|
|
@@ -15607,13 +15845,14 @@ function writeBlockMessage(moment, response) {
|
|
|
15607
15845
|
|
|
15608
15846
|
// src/commands/init.ts
|
|
15609
15847
|
var import_node_fs24 = require("node:fs");
|
|
15610
|
-
var
|
|
15611
|
-
var
|
|
15848
|
+
var import_promises13 = require("node:fs/promises");
|
|
15849
|
+
var import_node_path18 = require("node:path");
|
|
15612
15850
|
var import_node_child_process9 = require("node:child_process");
|
|
15851
|
+
var readline = __toESM(require("node:readline/promises"));
|
|
15613
15852
|
|
|
15614
15853
|
// src/commands/migrate.ts
|
|
15615
15854
|
var import_node_fs23 = require("node:fs");
|
|
15616
|
-
var
|
|
15855
|
+
var import_node_path17 = require("node:path");
|
|
15617
15856
|
var import_node_child_process8 = require("node:child_process");
|
|
15618
15857
|
var LEGACY_NPM_PACKAGE = "@codacy/gate-cli";
|
|
15619
15858
|
function defaultNpmRemover(pkg) {
|
|
@@ -15649,8 +15888,8 @@ async function runMigration(opts = {}) {
|
|
|
15649
15888
|
return { actions, migrated: actions.length > 0 };
|
|
15650
15889
|
}
|
|
15651
15890
|
function migrateProjectDir(root, actions) {
|
|
15652
|
-
const gateDir = (0,
|
|
15653
|
-
const verityDir = (0,
|
|
15891
|
+
const gateDir = (0, import_node_path17.join)(root, ".gate");
|
|
15892
|
+
const verityDir = (0, import_node_path17.join)(root, ".verity");
|
|
15654
15893
|
if ((0, import_node_fs23.existsSync)(gateDir) && !(0, import_node_fs23.existsSync)(verityDir)) {
|
|
15655
15894
|
return migrateProjectDirRename(root, gateDir, verityDir, actions);
|
|
15656
15895
|
}
|
|
@@ -15704,11 +15943,11 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
|
|
|
15704
15943
|
}
|
|
15705
15944
|
function migrateGlobalCredentials(home, actions) {
|
|
15706
15945
|
if (!home) return;
|
|
15707
|
-
const gateCreds = (0,
|
|
15708
|
-
const verityCreds = (0,
|
|
15946
|
+
const gateCreds = (0, import_node_path17.join)(home, ".gate", "credentials");
|
|
15947
|
+
const verityCreds = (0, import_node_path17.join)(home, ".verity", "credentials");
|
|
15709
15948
|
if (!(0, import_node_fs23.existsSync)(gateCreds)) return;
|
|
15710
15949
|
if (!(0, import_node_fs23.existsSync)(verityCreds)) {
|
|
15711
|
-
(0, import_node_fs23.mkdirSync)((0,
|
|
15950
|
+
(0, import_node_fs23.mkdirSync)((0, import_node_path17.join)(home, ".verity"), { recursive: true });
|
|
15712
15951
|
moveFile(gateCreds, verityCreds);
|
|
15713
15952
|
actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
|
|
15714
15953
|
return;
|
|
@@ -15730,7 +15969,7 @@ async function migrateLegacyHooks(root, actions) {
|
|
|
15730
15969
|
}
|
|
15731
15970
|
}
|
|
15732
15971
|
async function migrateClaudeMd(root, actions) {
|
|
15733
|
-
const claudeMd = (0,
|
|
15972
|
+
const claudeMd = (0, import_node_path17.join)(root, "CLAUDE.md");
|
|
15734
15973
|
const hadLegacyBlock = (0, import_node_fs23.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
|
|
15735
15974
|
if (!hadLegacyBlock) return;
|
|
15736
15975
|
try {
|
|
@@ -15741,8 +15980,8 @@ async function migrateClaudeMd(root, actions) {
|
|
|
15741
15980
|
}
|
|
15742
15981
|
}
|
|
15743
15982
|
function migrateStandardFile(root, actions) {
|
|
15744
|
-
const gateMd = (0,
|
|
15745
|
-
const verityMd = (0,
|
|
15983
|
+
const gateMd = (0, import_node_path17.join)(root, "GATE.md");
|
|
15984
|
+
const verityMd = (0, import_node_path17.join)(root, "VERITY.md");
|
|
15746
15985
|
if (!(0, import_node_fs23.existsSync)(gateMd) || (0, import_node_fs23.existsSync)(verityMd)) return;
|
|
15747
15986
|
let moved = false;
|
|
15748
15987
|
if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
|
|
@@ -15830,15 +16069,15 @@ function moveFile(from, to) {
|
|
|
15830
16069
|
function carryLegacyContents(gateDir, verityDir) {
|
|
15831
16070
|
let copied = 0;
|
|
15832
16071
|
const walk = (relDir) => {
|
|
15833
|
-
const srcDir = (0,
|
|
16072
|
+
const srcDir = (0, import_node_path17.join)(gateDir, relDir);
|
|
15834
16073
|
for (const entry of (0, import_node_fs23.readdirSync)(srcDir)) {
|
|
15835
|
-
const rel = relDir ? (0,
|
|
15836
|
-
const src = (0,
|
|
15837
|
-
const dest = (0,
|
|
16074
|
+
const rel = relDir ? (0, import_node_path17.join)(relDir, entry) : entry;
|
|
16075
|
+
const src = (0, import_node_path17.join)(gateDir, rel);
|
|
16076
|
+
const dest = (0, import_node_path17.join)(verityDir, rel);
|
|
15838
16077
|
if ((0, import_node_fs23.statSync)(src).isDirectory()) {
|
|
15839
16078
|
walk(rel);
|
|
15840
16079
|
} else if (!(0, import_node_fs23.existsSync)(dest)) {
|
|
15841
|
-
(0, import_node_fs23.mkdirSync)((0,
|
|
16080
|
+
(0, import_node_fs23.mkdirSync)((0, import_node_path17.dirname)(dest), { recursive: true });
|
|
15842
16081
|
(0, import_node_fs23.cpSync)(src, dest);
|
|
15843
16082
|
copied++;
|
|
15844
16083
|
}
|
|
@@ -15848,22 +16087,22 @@ function carryLegacyContents(gateDir, verityDir) {
|
|
|
15848
16087
|
return copied;
|
|
15849
16088
|
}
|
|
15850
16089
|
async function needsMigration(root = repoRoot()) {
|
|
15851
|
-
const gateDir = (0,
|
|
15852
|
-
const verityDir = (0,
|
|
16090
|
+
const gateDir = (0, import_node_path17.join)(root, ".gate");
|
|
16091
|
+
const verityDir = (0, import_node_path17.join)(root, ".verity");
|
|
15853
16092
|
if ((0, import_node_fs23.existsSync)(gateDir) && !(0, import_node_fs23.existsSync)(verityDir)) return true;
|
|
15854
16093
|
if ((0, import_node_fs23.existsSync)(gateDir) && (0, import_node_fs23.existsSync)(verityDir)) {
|
|
15855
|
-
if ((0, import_node_fs23.existsSync)((0,
|
|
16094
|
+
if ((0, import_node_fs23.existsSync)((0, import_node_path17.join)(gateDir, "credentials")) && !(0, import_node_fs23.existsSync)((0, import_node_path17.join)(verityDir, "credentials"))) {
|
|
15856
16095
|
return true;
|
|
15857
16096
|
}
|
|
15858
|
-
if ((0, import_node_fs23.existsSync)((0,
|
|
16097
|
+
if ((0, import_node_fs23.existsSync)((0, import_node_path17.join)(gateDir, "memory")) && !(0, import_node_fs23.existsSync)((0, import_node_path17.join)(verityDir, "memory"))) {
|
|
15859
16098
|
return true;
|
|
15860
16099
|
}
|
|
15861
16100
|
}
|
|
15862
|
-
const claudeMd = (0,
|
|
16101
|
+
const claudeMd = (0, import_node_path17.join)(root, "CLAUDE.md");
|
|
15863
16102
|
if ((0, import_node_fs23.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
|
|
15864
16103
|
return true;
|
|
15865
16104
|
}
|
|
15866
|
-
if ((0, import_node_fs23.existsSync)((0,
|
|
16105
|
+
if ((0, import_node_fs23.existsSync)((0, import_node_path17.join)(root, "GATE.md")) && !(0, import_node_fs23.existsSync)((0, import_node_path17.join)(root, "VERITY.md"))) {
|
|
15867
16106
|
return true;
|
|
15868
16107
|
}
|
|
15869
16108
|
if (await hasLegacyHooksAt(root)) return true;
|
|
@@ -15889,17 +16128,77 @@ function registerMigrateCommand(program2) {
|
|
|
15889
16128
|
}
|
|
15890
16129
|
|
|
15891
16130
|
// src/commands/init.ts
|
|
16131
|
+
async function promptYes(question) {
|
|
16132
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
|
|
16133
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
16134
|
+
try {
|
|
16135
|
+
const answer = (await rl.question(question)).trim().toLowerCase();
|
|
16136
|
+
return answer === "" || answer === "y" || answer === "yes";
|
|
16137
|
+
} finally {
|
|
16138
|
+
rl.close();
|
|
16139
|
+
}
|
|
16140
|
+
}
|
|
16141
|
+
async function runOptionalAuth() {
|
|
16142
|
+
const existing = await resolveToken();
|
|
16143
|
+
if (existing.ok) {
|
|
16144
|
+
printInfo("Already authenticated \u2014 results will upload to the Verity service. \u2713");
|
|
16145
|
+
return;
|
|
16146
|
+
}
|
|
16147
|
+
let remote = "";
|
|
16148
|
+
try {
|
|
16149
|
+
remote = (0, import_node_child_process9.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
16150
|
+
} catch {
|
|
16151
|
+
}
|
|
16152
|
+
const localOnlyNote = () => {
|
|
16153
|
+
printInfo("Verity runs in local-only mode: the gate still runs and shows static findings, but nothing uploads.");
|
|
16154
|
+
printInfo(' Authenticate anytime: run "verity init" again, or "verity auth register".');
|
|
16155
|
+
};
|
|
16156
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
16157
|
+
console.log("");
|
|
16158
|
+
console.log(" Signing in is optional. What it does:");
|
|
16159
|
+
console.log(" - Confirms you have write access to this repository. The GitHub token");
|
|
16160
|
+
console.log(" is used once to verify that, then discarded \u2014 Verity never stores it.");
|
|
16161
|
+
console.log(" - It does NOT give Verity access to your code. Code checked by the gate");
|
|
16162
|
+
console.log(" is analyzed in memory and discarded \u2014 we never store your code.");
|
|
16163
|
+
console.log(" - It is required to store and access run history for this repo");
|
|
16164
|
+
console.log(" (past results, trends, and shareable reports).");
|
|
16165
|
+
console.log(" - Skip and Verity still works fully locally: the gate runs and shows");
|
|
16166
|
+
console.log(" findings, but nothing is uploaded.");
|
|
16167
|
+
console.log("");
|
|
16168
|
+
}
|
|
16169
|
+
const wantsAuth = await promptYes("Authenticate with GitHub now to upload results to Verity? [Y/skip] ");
|
|
16170
|
+
if (!wantsAuth) {
|
|
16171
|
+
printInfo("Skipped authentication.");
|
|
16172
|
+
localOnlyNote();
|
|
16173
|
+
return;
|
|
16174
|
+
}
|
|
16175
|
+
if (!remote) {
|
|
16176
|
+
printWarn("No git remote found \u2014 cannot authenticate yet.");
|
|
16177
|
+
localOnlyNote();
|
|
16178
|
+
return;
|
|
16179
|
+
}
|
|
16180
|
+
const projectName = parseRemote(remote)?.repo ?? (0, import_node_path18.basename)(process.cwd());
|
|
16181
|
+
printInfo("Authenticating with GitHub\u2026");
|
|
16182
|
+
const result = await registerProject({ projectName, remote, serviceUrl: DEFAULT_SERVICE_URL });
|
|
16183
|
+
if (result.ok) {
|
|
16184
|
+
printInfo(`Project registered: ${result.data.projectId} \u2713`);
|
|
16185
|
+
if (result.data.email) printInfo(` Authenticated as: ${result.data.email}`);
|
|
16186
|
+
} else {
|
|
16187
|
+
printWarn(`Authentication did not complete: ${result.error}`);
|
|
16188
|
+
localOnlyNote();
|
|
16189
|
+
}
|
|
16190
|
+
}
|
|
15892
16191
|
function resolveDataDir() {
|
|
15893
16192
|
const candidates = [
|
|
15894
|
-
(0,
|
|
16193
|
+
(0, import_node_path18.join)(__dirname, "..", "data"),
|
|
15895
16194
|
// installed: node_modules/@codacy/verity-cli/data
|
|
15896
|
-
(0,
|
|
16195
|
+
(0, import_node_path18.join)(__dirname, "..", "..", "data"),
|
|
15897
16196
|
// edge case: nested resolution
|
|
15898
|
-
(0,
|
|
16197
|
+
(0, import_node_path18.join)(process.cwd(), "cli", "data")
|
|
15899
16198
|
// local dev: running from repo root
|
|
15900
16199
|
];
|
|
15901
16200
|
for (const candidate of candidates) {
|
|
15902
|
-
if ((0, import_node_fs24.existsSync)((0,
|
|
16201
|
+
if ((0, import_node_fs24.existsSync)((0, import_node_path18.join)(candidate, "skills"))) {
|
|
15903
16202
|
return candidate;
|
|
15904
16203
|
}
|
|
15905
16204
|
}
|
|
@@ -15908,8 +16207,8 @@ function resolveDataDir() {
|
|
|
15908
16207
|
);
|
|
15909
16208
|
}
|
|
15910
16209
|
async function copyDir(src, dest) {
|
|
15911
|
-
await (0,
|
|
15912
|
-
await (0,
|
|
16210
|
+
await (0, import_promises13.mkdir)(dest, { recursive: true });
|
|
16211
|
+
await (0, import_promises13.cp)(src, dest, { recursive: true, force: true });
|
|
15913
16212
|
}
|
|
15914
16213
|
function registerInitCommand(program2) {
|
|
15915
16214
|
program2.command("init").description("Initialize Verity in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
|
|
@@ -15978,24 +16277,24 @@ function registerInitCommand(program2) {
|
|
|
15978
16277
|
console.log("");
|
|
15979
16278
|
printInfo("Installing skills...");
|
|
15980
16279
|
const dataDir = resolveDataDir();
|
|
15981
|
-
const skillsSource = (0,
|
|
16280
|
+
const skillsSource = (0, import_node_path18.join)(dataDir, "skills");
|
|
15982
16281
|
const skillsDest = ".claude/skills";
|
|
15983
16282
|
const skills = ["verity-setup", "verity-analyze", "verity-status", "verity-feedback", "verity-learn", "verity-memory", "verity-insights", "verity-reflect"];
|
|
15984
16283
|
let skillsInstalled = 0;
|
|
15985
16284
|
for (const skill of skills) {
|
|
15986
|
-
const src = (0,
|
|
15987
|
-
const dest = (0,
|
|
16285
|
+
const src = (0, import_node_path18.join)(skillsSource, skill);
|
|
16286
|
+
const dest = (0, import_node_path18.join)(skillsDest, skill);
|
|
15988
16287
|
if (!(0, import_node_fs24.existsSync)(src)) {
|
|
15989
16288
|
printWarn(` Skill data not found: ${skill}`);
|
|
15990
16289
|
continue;
|
|
15991
16290
|
}
|
|
15992
16291
|
if ((0, import_node_fs24.existsSync)(dest) && !force) {
|
|
15993
|
-
const srcSkill = (0,
|
|
15994
|
-
const destSkill = (0,
|
|
16292
|
+
const srcSkill = (0, import_node_path18.join)(src, "SKILL.md");
|
|
16293
|
+
const destSkill = (0, import_node_path18.join)(dest, "SKILL.md");
|
|
15995
16294
|
if ((0, import_node_fs24.existsSync)(destSkill)) {
|
|
15996
16295
|
try {
|
|
15997
|
-
const srcContent = await (0,
|
|
15998
|
-
const destContent = await (0,
|
|
16296
|
+
const srcContent = await (0, import_promises13.readFile)(srcSkill, "utf-8");
|
|
16297
|
+
const destContent = await (0, import_promises13.readFile)(destSkill, "utf-8");
|
|
15999
16298
|
if (srcContent === destContent) {
|
|
16000
16299
|
skillsInstalled++;
|
|
16001
16300
|
continue;
|
|
@@ -16021,7 +16320,7 @@ function registerInitCommand(program2) {
|
|
|
16021
16320
|
printWarn(` ${hookResult.error}`);
|
|
16022
16321
|
printInfo(' Run "verity hooks install --force" to overwrite.');
|
|
16023
16322
|
}
|
|
16024
|
-
await (0,
|
|
16323
|
+
await (0, import_promises13.mkdir)(VERITY_DIR, { recursive: true });
|
|
16025
16324
|
await ensureMemoryDir();
|
|
16026
16325
|
try {
|
|
16027
16326
|
await ensureClaudeMdPointer();
|
|
@@ -16029,8 +16328,14 @@ function registerInitCommand(program2) {
|
|
|
16029
16328
|
} catch (err) {
|
|
16030
16329
|
printWarn(` Could not update CLAUDE.md: ${err.message}`);
|
|
16031
16330
|
}
|
|
16032
|
-
const globalVerityDir = (0,
|
|
16033
|
-
await (0,
|
|
16331
|
+
const globalVerityDir = (0, import_node_path18.join)(process.env.HOME ?? "", ".verity");
|
|
16332
|
+
await (0, import_promises13.mkdir)(globalVerityDir, { recursive: true });
|
|
16333
|
+
console.log("");
|
|
16334
|
+
try {
|
|
16335
|
+
await runOptionalAuth();
|
|
16336
|
+
} catch (err) {
|
|
16337
|
+
printWarn(`Authentication step skipped: ${err.message}`);
|
|
16338
|
+
}
|
|
16034
16339
|
console.log("");
|
|
16035
16340
|
printInfo("Verity initialized!");
|
|
16036
16341
|
console.log("");
|
|
@@ -16047,13 +16352,14 @@ function registerInitCommand(program2) {
|
|
|
16047
16352
|
console.log(" .verity/memory/ \u2014 knowledge base (8 domains, commit to git)");
|
|
16048
16353
|
console.log("");
|
|
16049
16354
|
console.log(" Next step: open this project in Claude Code and run /verity-setup");
|
|
16355
|
+
console.log(' (Not authenticated? Verity runs in local-only mode until you run "verity auth register".)');
|
|
16050
16356
|
console.log("");
|
|
16051
16357
|
});
|
|
16052
16358
|
}
|
|
16053
16359
|
|
|
16054
16360
|
// src/commands/uninstall.ts
|
|
16055
16361
|
var import_node_fs25 = require("node:fs");
|
|
16056
|
-
var
|
|
16362
|
+
var import_node_path19 = require("node:path");
|
|
16057
16363
|
var SKILL_NAMES = [
|
|
16058
16364
|
"verity-setup",
|
|
16059
16365
|
"verity-analyze",
|
|
@@ -16072,7 +16378,7 @@ function registerUninstallCommand(program2) {
|
|
|
16072
16378
|
const actions = [];
|
|
16073
16379
|
const skillsRoot = projectPath(".claude/skills");
|
|
16074
16380
|
for (const name of SKILL_NAMES) {
|
|
16075
|
-
const dir = (0,
|
|
16381
|
+
const dir = (0, import_node_path19.join)(skillsRoot, name);
|
|
16076
16382
|
if ((0, import_node_fs25.existsSync)(dir)) {
|
|
16077
16383
|
actions.push({
|
|
16078
16384
|
label: `Remove .claude/skills/${name}/`,
|
|
@@ -16118,7 +16424,7 @@ function registerUninstallCommand(program2) {
|
|
|
16118
16424
|
}
|
|
16119
16425
|
});
|
|
16120
16426
|
const home = process.env.HOME ?? "";
|
|
16121
|
-
const globalVerityDir = (0,
|
|
16427
|
+
const globalVerityDir = (0, import_node_path19.join)(home, ".verity");
|
|
16122
16428
|
if (purgeGlobal && (0, import_node_fs25.existsSync)(globalVerityDir)) {
|
|
16123
16429
|
actions.push({
|
|
16124
16430
|
label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
|
|
@@ -16317,7 +16623,7 @@ function registerTaskCommands(program2) {
|
|
|
16317
16623
|
|
|
16318
16624
|
// src/commands/reset.ts
|
|
16319
16625
|
var import_node_fs26 = require("node:fs");
|
|
16320
|
-
var
|
|
16626
|
+
var import_node_path20 = require("node:path");
|
|
16321
16627
|
function registerResetCommand(program2) {
|
|
16322
16628
|
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) => {
|
|
16323
16629
|
const globals = program2.opts();
|
|
@@ -16358,7 +16664,7 @@ function registerResetCommand(program2) {
|
|
|
16358
16664
|
for (const entry of (0, import_node_fs26.readdirSync)(cacheDir)) {
|
|
16359
16665
|
if (entry.startsWith("pending-")) {
|
|
16360
16666
|
try {
|
|
16361
|
-
(0, import_node_fs26.unlinkSync)((0,
|
|
16667
|
+
(0, import_node_fs26.unlinkSync)((0, import_node_path20.join)(cacheDir, entry));
|
|
16362
16668
|
purged++;
|
|
16363
16669
|
} catch {
|
|
16364
16670
|
}
|
|
@@ -16385,7 +16691,7 @@ function registerResetCommand(program2) {
|
|
|
16385
16691
|
if ((0, import_node_fs26.existsSync)(logsDir)) {
|
|
16386
16692
|
for (const entry of (0, import_node_fs26.readdirSync)(logsDir)) {
|
|
16387
16693
|
try {
|
|
16388
|
-
(0, import_node_fs26.unlinkSync)((0,
|
|
16694
|
+
(0, import_node_fs26.unlinkSync)((0, import_node_path20.join)(logsDir, entry));
|
|
16389
16695
|
} catch {
|
|
16390
16696
|
}
|
|
16391
16697
|
}
|
|
@@ -16643,8 +16949,7 @@ function registerRunCommand(program2) {
|
|
|
16643
16949
|
}
|
|
16644
16950
|
|
|
16645
16951
|
// src/lib/telemetry.ts
|
|
16646
|
-
var
|
|
16647
|
-
var import_node_path20 = require("node:path");
|
|
16952
|
+
var import_promises14 = require("node:fs/promises");
|
|
16648
16953
|
var SETTINGS_LOCAL_FILE2 = ".claude/settings.local.json";
|
|
16649
16954
|
var GITIGNORE_FILE = ".gitignore";
|
|
16650
16955
|
var GITIGNORE_ENTRY = ".claude/settings.local.json";
|
|
@@ -16675,21 +16980,19 @@ function buildTelemetryEnv(serviceUrl, token) {
|
|
|
16675
16980
|
var VERITY_TELEMETRY_KEYS = Object.keys(buildTelemetryEnv("", ""));
|
|
16676
16981
|
async function readSettingsLocal() {
|
|
16677
16982
|
try {
|
|
16678
|
-
return JSON.parse(await (0,
|
|
16983
|
+
return JSON.parse(await (0, import_promises14.readFile)(projectPath(SETTINGS_LOCAL_FILE2), "utf-8"));
|
|
16679
16984
|
} catch {
|
|
16680
16985
|
return {};
|
|
16681
16986
|
}
|
|
16682
16987
|
}
|
|
16683
16988
|
async function writeSettingsLocal(settings) {
|
|
16684
|
-
|
|
16685
|
-
await (0, import_promises13.mkdir)((0, import_node_path20.dirname)(file), { recursive: true });
|
|
16686
|
-
await (0, import_promises13.writeFile)(file, JSON.stringify(settings, null, 2) + "\n");
|
|
16989
|
+
await writeJsonFilePreservingStyle(projectPath(SETTINGS_LOCAL_FILE2), settings);
|
|
16687
16990
|
}
|
|
16688
16991
|
async function ensureGitignore() {
|
|
16689
16992
|
const file = projectPath(GITIGNORE_FILE);
|
|
16690
16993
|
let content = "";
|
|
16691
16994
|
try {
|
|
16692
|
-
content = await (0,
|
|
16995
|
+
content = await (0, import_promises14.readFile)(file, "utf-8");
|
|
16693
16996
|
} catch {
|
|
16694
16997
|
}
|
|
16695
16998
|
const lines = content.split("\n").map((l) => l.trim());
|
|
@@ -16698,7 +17001,7 @@ async function ensureGitignore() {
|
|
|
16698
17001
|
}
|
|
16699
17002
|
const block = "# Verity telemetry \u2014 holds your project token\n" + GITIGNORE_ENTRY + "\n";
|
|
16700
17003
|
const next = content ? content + (content.endsWith("\n") ? "" : "\n") + "\n" + block : block;
|
|
16701
|
-
await (0,
|
|
17004
|
+
await (0, import_promises14.writeFile)(file, next);
|
|
16702
17005
|
}
|
|
16703
17006
|
async function installTelemetry(serviceUrl, token) {
|
|
16704
17007
|
const env = buildTelemetryEnv(serviceUrl, token);
|
|
@@ -16778,7 +17081,7 @@ function registerTelemetryCommands(program2) {
|
|
|
16778
17081
|
}
|
|
16779
17082
|
|
|
16780
17083
|
// src/cli.ts
|
|
16781
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.
|
|
17084
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.26.0-experimental.a2dc844").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
|
|
16782
17085
|
registerAuthCommands(program);
|
|
16783
17086
|
registerHooksCommands(program);
|
|
16784
17087
|
registerIntentCommands(program);
|