@codacy/verity-cli 0.26.0-experimental.d7dfc00 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/verity.js +303 -559
- package/data/skills/verity-analyze/SKILL.md +1 -7
- package/data/skills/verity-setup/SKILL.md +10 -49
- package/package.json +1 -1
package/bin/verity.js
CHANGED
|
@@ -10325,7 +10325,9 @@ var {
|
|
|
10325
10325
|
} = import_index.default;
|
|
10326
10326
|
|
|
10327
10327
|
// src/commands/auth.ts
|
|
10328
|
-
var
|
|
10328
|
+
var import_promises3 = require("node:fs/promises");
|
|
10329
|
+
var import_node_child_process3 = require("node:child_process");
|
|
10330
|
+
var import_node_path2 = require("node:path");
|
|
10329
10331
|
|
|
10330
10332
|
// src/lib/auth.ts
|
|
10331
10333
|
var import_promises = require("node:fs/promises");
|
|
@@ -10473,11 +10475,7 @@ var SECURITY_PATTERNS = [
|
|
|
10473
10475
|
/Dockerfile/
|
|
10474
10476
|
];
|
|
10475
10477
|
var PROD_SERVICE_URL = "https://ofcamwrjwrkazqvdchko.supabase.co/functions/v1";
|
|
10476
|
-
var DEFAULT_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";
|
|
10478
|
+
var DEFAULT_SERVICE_URL = "".length > 0 ? "" : PROD_SERVICE_URL;
|
|
10481
10479
|
|
|
10482
10480
|
// src/lib/auth.ts
|
|
10483
10481
|
async function resolveToken(flagToken) {
|
|
@@ -10643,8 +10641,7 @@ async function apiRequest(options) {
|
|
|
10643
10641
|
timeout = 9e4,
|
|
10644
10642
|
cmd = "unknown",
|
|
10645
10643
|
retry = false,
|
|
10646
|
-
encodeBody = false
|
|
10647
|
-
extraHeaders
|
|
10644
|
+
encodeBody = false
|
|
10648
10645
|
} = options;
|
|
10649
10646
|
const url = `${serviceUrl}${path}`;
|
|
10650
10647
|
const headers = {
|
|
@@ -10653,9 +10650,6 @@ async function apiRequest(options) {
|
|
|
10653
10650
|
if (token) {
|
|
10654
10651
|
headers["Authorization"] = `Bearer ${token}`;
|
|
10655
10652
|
}
|
|
10656
|
-
if (extraHeaders) {
|
|
10657
|
-
Object.assign(headers, extraHeaders);
|
|
10658
|
-
}
|
|
10659
10653
|
const testMockScenario = process.env.VERITY_TEST_MOCK_SCENARIO;
|
|
10660
10654
|
if (testMockScenario) {
|
|
10661
10655
|
headers["X-Verity-Mock-Scenario"] = testMockScenario;
|
|
@@ -10746,388 +10740,6 @@ function analyzeRequest(options) {
|
|
|
10746
10740
|
});
|
|
10747
10741
|
}
|
|
10748
10742
|
|
|
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
|
-
provider_token: ${providerToken}
|
|
11104
|
-
`,
|
|
11105
|
-
{ mode: 384 }
|
|
11106
|
-
);
|
|
11107
|
-
await (0, import_promises3.chmod)(CREDENTIALS_FILE, 384).catch(() => {
|
|
11108
|
-
});
|
|
11109
|
-
} catch (err) {
|
|
11110
|
-
return {
|
|
11111
|
-
ok: false,
|
|
11112
|
-
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".`
|
|
11113
|
-
};
|
|
11114
|
-
}
|
|
11115
|
-
try {
|
|
11116
|
-
await (0, import_promises3.mkdir)((0, import_node_path3.dirname)(GLOBAL_CREDENTIALS_FILE), { recursive: true });
|
|
11117
|
-
await (0, import_promises3.appendFile)(
|
|
11118
|
-
GLOBAL_CREDENTIALS_FILE,
|
|
11119
|
-
`${opts.remote} token: ${token}
|
|
11120
|
-
${opts.remote} provider_token: ${providerToken}
|
|
11121
|
-
`,
|
|
11122
|
-
{ mode: 384 }
|
|
11123
|
-
);
|
|
11124
|
-
await (0, import_promises3.chmod)(GLOBAL_CREDENTIALS_FILE, 384).catch(() => {
|
|
11125
|
-
});
|
|
11126
|
-
} catch {
|
|
11127
|
-
}
|
|
11128
|
-
return { ok: true, data: { projectId: project_id, serviceUrl: service_url, email: user?.email } };
|
|
11129
|
-
}
|
|
11130
|
-
|
|
11131
10743
|
// src/commands/auth.ts
|
|
11132
10744
|
function registerAuthCommands(program2) {
|
|
11133
10745
|
const auth = program2.command("auth").description("Manage project authentication");
|
|
@@ -11137,26 +10749,36 @@ function registerAuthCommands(program2) {
|
|
|
11137
10749
|
let remote = opts.remote;
|
|
11138
10750
|
if (!remote) {
|
|
11139
10751
|
try {
|
|
11140
|
-
remote = (0,
|
|
10752
|
+
remote = (0, import_node_child_process3.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
|
|
11141
10753
|
} catch {
|
|
11142
10754
|
printError("No git remote found. Use --remote to specify one.");
|
|
11143
10755
|
process.exit(1);
|
|
11144
10756
|
}
|
|
11145
10757
|
}
|
|
11146
|
-
const result = await
|
|
11147
|
-
|
|
11148
|
-
|
|
10758
|
+
const result = await apiRequest({
|
|
10759
|
+
method: "POST",
|
|
10760
|
+
path: "/auth/register",
|
|
11149
10761
|
serviceUrl,
|
|
10762
|
+
body: { project_name: opts.project, git_remote_url: remote },
|
|
11150
10763
|
verbose: globals.verbose
|
|
11151
10764
|
});
|
|
11152
10765
|
if (!result.ok) {
|
|
11153
10766
|
printError(result.error);
|
|
11154
10767
|
process.exit(1);
|
|
11155
10768
|
}
|
|
11156
|
-
const {
|
|
11157
|
-
|
|
11158
|
-
|
|
11159
|
-
|
|
10769
|
+
const { project_id, token, service_url } = result.data;
|
|
10770
|
+
await (0, import_promises3.mkdir)(VERITY_DIR, { recursive: true });
|
|
10771
|
+
await (0, import_promises3.writeFile)(CREDENTIALS_FILE, `token: ${token}
|
|
10772
|
+
service_url: ${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 });
|
|
11160
10782
|
});
|
|
11161
10783
|
auth.command("verify").description("Verify the current token is valid").action(async () => {
|
|
11162
10784
|
const globals = program2.opts();
|
|
@@ -11189,7 +10811,7 @@ function registerAuthCommands(program2) {
|
|
|
11189
10811
|
let remote = opts.remote;
|
|
11190
10812
|
if (!remote) {
|
|
11191
10813
|
try {
|
|
11192
|
-
remote = (0,
|
|
10814
|
+
remote = (0, import_node_child_process3.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
|
|
11193
10815
|
} catch {
|
|
11194
10816
|
printError("No git remote found. Use --remote to specify one.");
|
|
11195
10817
|
process.exit(1);
|
|
@@ -11217,11 +10839,11 @@ function registerAuthCommands(program2) {
|
|
|
11217
10839
|
|
|
11218
10840
|
// src/lib/hooks.ts
|
|
11219
10841
|
var import_promises5 = require("node:fs/promises");
|
|
11220
|
-
var
|
|
10842
|
+
var import_node_path4 = require("node:path");
|
|
11221
10843
|
|
|
11222
10844
|
// src/lib/json-file.ts
|
|
11223
10845
|
var import_promises4 = require("node:fs/promises");
|
|
11224
|
-
var
|
|
10846
|
+
var import_node_path3 = require("node:path");
|
|
11225
10847
|
function jsonSemanticEqual(a, b) {
|
|
11226
10848
|
if (a === b) return true;
|
|
11227
10849
|
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) {
|
|
@@ -11267,7 +10889,7 @@ async function writeJsonFilePreservingStyle(file, value) {
|
|
|
11267
10889
|
const indent = currentRaw !== null ? detectJsonIndent(currentRaw) : 2;
|
|
11268
10890
|
const next = JSON.stringify(value, null, indent) + "\n";
|
|
11269
10891
|
if (next === currentRaw) return false;
|
|
11270
|
-
await (0, import_promises4.mkdir)((0,
|
|
10892
|
+
await (0, import_promises4.mkdir)((0, import_node_path3.dirname)(file), { recursive: true });
|
|
11271
10893
|
await (0, import_promises4.writeFile)(file, next);
|
|
11272
10894
|
return true;
|
|
11273
10895
|
}
|
|
@@ -11428,13 +11050,13 @@ async function writeSettings(settings) {
|
|
|
11428
11050
|
}
|
|
11429
11051
|
async function readSettingsAt(root) {
|
|
11430
11052
|
try {
|
|
11431
|
-
return JSON.parse(await (0, import_promises5.readFile)((0,
|
|
11053
|
+
return JSON.parse(await (0, import_promises5.readFile)((0, import_node_path4.join)(root, CLAUDE_SETTINGS_FILE), "utf-8"));
|
|
11432
11054
|
} catch {
|
|
11433
11055
|
return {};
|
|
11434
11056
|
}
|
|
11435
11057
|
}
|
|
11436
11058
|
async function writeSettingsAt(root, settings) {
|
|
11437
|
-
await writeJsonFilePreservingStyle((0,
|
|
11059
|
+
await writeJsonFilePreservingStyle((0, import_node_path4.join)(root, CLAUDE_SETTINGS_FILE), settings);
|
|
11438
11060
|
}
|
|
11439
11061
|
async function hasLegacyHooksAt(root) {
|
|
11440
11062
|
const settings = await readSettingsAt(root);
|
|
@@ -11674,8 +11296,8 @@ var import_node_crypto3 = require("node:crypto");
|
|
|
11674
11296
|
|
|
11675
11297
|
// src/lib/conversation-buffer.ts
|
|
11676
11298
|
var import_promises6 = require("node:fs/promises");
|
|
11677
|
-
var
|
|
11678
|
-
var
|
|
11299
|
+
var import_node_fs2 = require("node:fs");
|
|
11300
|
+
var import_node_child_process4 = require("node:child_process");
|
|
11679
11301
|
var import_node_crypto = require("node:crypto");
|
|
11680
11302
|
function stripImageReferences(text) {
|
|
11681
11303
|
return text.replace(/\[Image #\d+\]/g, "[screenshot \u2014 not available for review]");
|
|
@@ -11710,7 +11332,7 @@ async function appendToConversationBuffer(prompt, sessionId) {
|
|
|
11710
11332
|
}
|
|
11711
11333
|
async function readAndClearConversationBuffer(currentSessionId) {
|
|
11712
11334
|
try {
|
|
11713
|
-
if ((0,
|
|
11335
|
+
if ((0, import_node_fs2.existsSync)(CONVERSATION_BUFFER_FILE)) {
|
|
11714
11336
|
const entries = await readBufferEntries();
|
|
11715
11337
|
let mine = entries;
|
|
11716
11338
|
let others = [];
|
|
@@ -11734,7 +11356,7 @@ async function readAndClearConversationBuffer(currentSessionId) {
|
|
|
11734
11356
|
};
|
|
11735
11357
|
}
|
|
11736
11358
|
}
|
|
11737
|
-
if ((0,
|
|
11359
|
+
if ((0, import_node_fs2.existsSync)(INTENT_FILE)) {
|
|
11738
11360
|
try {
|
|
11739
11361
|
const content = await (0, import_promises6.readFile)(INTENT_FILE, "utf-8");
|
|
11740
11362
|
await (0, import_promises6.unlink)(INTENT_FILE).catch(() => {
|
|
@@ -11778,7 +11400,7 @@ async function readBufferEntries() {
|
|
|
11778
11400
|
}
|
|
11779
11401
|
function getRecentCommitMessages() {
|
|
11780
11402
|
try {
|
|
11781
|
-
const output = (0,
|
|
11403
|
+
const output = (0, import_node_child_process4.execSync)(
|
|
11782
11404
|
'git log --since="30 minutes ago" --format="%s" -5',
|
|
11783
11405
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
11784
11406
|
).trim();
|
|
@@ -11791,8 +11413,8 @@ function getRecentCommitMessages() {
|
|
|
11791
11413
|
|
|
11792
11414
|
// src/lib/task-context-buffer.ts
|
|
11793
11415
|
var import_promises7 = require("node:fs/promises");
|
|
11794
|
-
var
|
|
11795
|
-
var
|
|
11416
|
+
var import_node_fs3 = require("node:fs");
|
|
11417
|
+
var import_node_path5 = require("node:path");
|
|
11796
11418
|
var TASK_CONTEXT_DIR = `${VERITY_DIR}/.task-context`;
|
|
11797
11419
|
var MAX_BUFFER_BYTES = 500 * 1024;
|
|
11798
11420
|
var MAX_PROMPT_CHARS = 2e3;
|
|
@@ -11831,7 +11453,7 @@ async function appendResponseToTaskBuffer(taskId, assistantResponse, actionSumma
|
|
|
11831
11453
|
}
|
|
11832
11454
|
async function readTaskContextBuffer(taskId) {
|
|
11833
11455
|
const filePath = bufferPath(taskId);
|
|
11834
|
-
if (!(0,
|
|
11456
|
+
if (!(0, import_node_fs3.existsSync)(filePath)) return null;
|
|
11835
11457
|
try {
|
|
11836
11458
|
const content = await (0, import_promises7.readFile)(filePath, "utf-8");
|
|
11837
11459
|
if (!content.trim()) return null;
|
|
@@ -11865,12 +11487,12 @@ async function readTaskContextBuffer(taskId) {
|
|
|
11865
11487
|
}
|
|
11866
11488
|
async function cleanupTaskContextBuffers() {
|
|
11867
11489
|
try {
|
|
11868
|
-
if (!(0,
|
|
11490
|
+
if (!(0, import_node_fs3.existsSync)(TASK_CONTEXT_DIR)) return;
|
|
11869
11491
|
const files = await (0, import_promises7.readdir)(TASK_CONTEXT_DIR);
|
|
11870
11492
|
const cutoffMs = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
11871
11493
|
for (const file of files) {
|
|
11872
11494
|
if (!file.endsWith(".jsonl")) continue;
|
|
11873
|
-
const filePath = (0,
|
|
11495
|
+
const filePath = (0, import_node_path5.join)(TASK_CONTEXT_DIR, file);
|
|
11874
11496
|
try {
|
|
11875
11497
|
const stats = await (0, import_promises7.stat)(filePath);
|
|
11876
11498
|
if (stats.mtimeMs < cutoffMs) {
|
|
@@ -11884,13 +11506,13 @@ async function cleanupTaskContextBuffers() {
|
|
|
11884
11506
|
}
|
|
11885
11507
|
function bufferPath(taskId) {
|
|
11886
11508
|
const safe = taskId.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
11887
|
-
return (0,
|
|
11509
|
+
return (0, import_node_path5.join)(TASK_CONTEXT_DIR, `${safe}.jsonl`);
|
|
11888
11510
|
}
|
|
11889
11511
|
async function appendEntry(taskId, entry) {
|
|
11890
11512
|
try {
|
|
11891
11513
|
await (0, import_promises7.mkdir)(TASK_CONTEXT_DIR, { recursive: true });
|
|
11892
11514
|
const filePath = bufferPath(taskId);
|
|
11893
|
-
if ((0,
|
|
11515
|
+
if ((0, import_node_fs3.existsSync)(filePath)) {
|
|
11894
11516
|
const stats = await (0, import_promises7.stat)(filePath);
|
|
11895
11517
|
if (stats.size >= MAX_BUFFER_BYTES) {
|
|
11896
11518
|
const content = await (0, import_promises7.readFile)(filePath, "utf-8");
|
|
@@ -11901,7 +11523,7 @@ async function appendEntry(taskId, entry) {
|
|
|
11901
11523
|
}
|
|
11902
11524
|
}
|
|
11903
11525
|
const line = JSON.stringify(entry) + "\n";
|
|
11904
|
-
const existing = (0,
|
|
11526
|
+
const existing = (0, import_node_fs3.existsSync)(filePath) ? await (0, import_promises7.readFile)(filePath, "utf-8") : "";
|
|
11905
11527
|
await (0, import_promises7.writeFile)(filePath, existing + line);
|
|
11906
11528
|
} catch {
|
|
11907
11529
|
}
|
|
@@ -11909,8 +11531,8 @@ async function appendEntry(taskId, entry) {
|
|
|
11909
11531
|
|
|
11910
11532
|
// src/lib/memory-retrieval.ts
|
|
11911
11533
|
var import_promises8 = require("node:fs/promises");
|
|
11912
|
-
var
|
|
11913
|
-
var
|
|
11534
|
+
var import_node_fs4 = require("node:fs");
|
|
11535
|
+
var import_node_path6 = require("node:path");
|
|
11914
11536
|
var memoryDir = () => projectPath(`${VERITY_DIR}/memory`);
|
|
11915
11537
|
var DOMAINS = ["decisions", "quality", "security", "intent", "gotchas", "patterns", "domain", "integrations"];
|
|
11916
11538
|
var DEFAULT_BUDGET_TOKENS = 2e3;
|
|
@@ -12003,19 +11625,19 @@ function parseFrontmatter(content) {
|
|
|
12003
11625
|
return { fm, body: match[2].trim() };
|
|
12004
11626
|
}
|
|
12005
11627
|
async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = DEFAULT_BUDGET_TOKENS) {
|
|
12006
|
-
if (!(0,
|
|
11628
|
+
if (!(0, import_node_fs4.existsSync)(memoryDir())) return null;
|
|
12007
11629
|
const budget = Math.min(budgetTokens, MAX_BUDGET_TOKENS);
|
|
12008
11630
|
const promptTokens = tokenize(promptText);
|
|
12009
11631
|
const nodes = [];
|
|
12010
11632
|
for (const domain of DOMAINS) {
|
|
12011
|
-
const domainDir = (0,
|
|
12012
|
-
if (!(0,
|
|
11633
|
+
const domainDir = (0, import_node_path6.join)(memoryDir(), domain);
|
|
11634
|
+
if (!(0, import_node_fs4.existsSync)(domainDir)) continue;
|
|
12013
11635
|
try {
|
|
12014
11636
|
const files = await (0, import_promises8.readdir)(domainDir);
|
|
12015
11637
|
for (const file of files) {
|
|
12016
11638
|
if (!file.endsWith(".md")) continue;
|
|
12017
11639
|
try {
|
|
12018
|
-
const content = await (0, import_promises8.readFile)((0,
|
|
11640
|
+
const content = await (0, import_promises8.readFile)((0, import_node_path6.join)(domainDir, file), "utf-8");
|
|
12019
11641
|
const { fm, body } = parseFrontmatter(content);
|
|
12020
11642
|
if (fm.status && fm.status !== "active") continue;
|
|
12021
11643
|
nodes.push({
|
|
@@ -12074,8 +11696,8 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
|
|
|
12074
11696
|
|
|
12075
11697
|
// src/lib/memory-sync.ts
|
|
12076
11698
|
var import_promises9 = require("node:fs/promises");
|
|
12077
|
-
var
|
|
12078
|
-
var
|
|
11699
|
+
var import_node_fs5 = require("node:fs");
|
|
11700
|
+
var import_node_path7 = require("node:path");
|
|
12079
11701
|
var import_node_crypto2 = require("node:crypto");
|
|
12080
11702
|
|
|
12081
11703
|
// src/lib/glob-match.ts
|
|
@@ -12146,32 +11768,32 @@ var syncStateFile = () => projectPath(`${VERITY_DIR}/.memory-sync-state.json`);
|
|
|
12146
11768
|
async function ensureMemoryDir() {
|
|
12147
11769
|
await (0, import_promises9.mkdir)(memoryDir2(), { recursive: true });
|
|
12148
11770
|
for (const domain of DOMAINS2) {
|
|
12149
|
-
await (0, import_promises9.mkdir)((0,
|
|
11771
|
+
await (0, import_promises9.mkdir)((0, import_node_path7.join)(memoryDir2(), domain), { recursive: true });
|
|
12150
11772
|
}
|
|
12151
|
-
if (!(0,
|
|
12152
|
-
await (0, import_promises9.writeFile)((0,
|
|
11773
|
+
if (!(0, import_node_fs5.existsSync)((0, import_node_path7.join)(memoryDir2(), "SCHEMA.md"))) {
|
|
11774
|
+
await (0, import_promises9.writeFile)((0, import_node_path7.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
|
|
12153
11775
|
}
|
|
12154
|
-
if (!(0,
|
|
12155
|
-
await (0, import_promises9.writeFile)((0,
|
|
11776
|
+
if (!(0, import_node_fs5.existsSync)((0, import_node_path7.join)(memoryDir2(), "index.md"))) {
|
|
11777
|
+
await (0, import_promises9.writeFile)((0, import_node_path7.join)(memoryDir2(), "index.md"), "# Project Memory Index\n\nNo nodes yet. Run an analysis to start building the knowledge graph.\n");
|
|
12156
11778
|
}
|
|
12157
|
-
if (!(0,
|
|
12158
|
-
await (0, import_promises9.writeFile)((0,
|
|
11779
|
+
if (!(0, import_node_fs5.existsSync)((0, import_node_path7.join)(memoryDir2(), "log.md"))) {
|
|
11780
|
+
await (0, import_promises9.writeFile)((0, import_node_path7.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
|
|
12159
11781
|
}
|
|
12160
11782
|
}
|
|
12161
11783
|
async function buildManifest() {
|
|
12162
|
-
if (!(0,
|
|
11784
|
+
if (!(0, import_node_fs5.existsSync)(memoryDir2())) {
|
|
12163
11785
|
return { schema_version: 1, nodes: [], index_hash: null, log_length: 0 };
|
|
12164
11786
|
}
|
|
12165
11787
|
const nodes = [];
|
|
12166
11788
|
for (const domain of DOMAINS2) {
|
|
12167
|
-
const domainDir = (0,
|
|
12168
|
-
if (!(0,
|
|
11789
|
+
const domainDir = (0, import_node_path7.join)(memoryDir2(), domain);
|
|
11790
|
+
if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
|
|
12169
11791
|
try {
|
|
12170
11792
|
const files = await (0, import_promises9.readdir)(domainDir);
|
|
12171
11793
|
for (const file of files) {
|
|
12172
11794
|
if (!file.endsWith(".md")) continue;
|
|
12173
11795
|
const filePath = `${domain}/${file}`;
|
|
12174
|
-
const fullPath = (0,
|
|
11796
|
+
const fullPath = (0, import_node_path7.join)(memoryDir2(), filePath);
|
|
12175
11797
|
try {
|
|
12176
11798
|
const content = await (0, import_promises9.readFile)(fullPath, "utf-8");
|
|
12177
11799
|
const hash = (0, import_node_crypto2.createHash)("sha256").update(content).digest("hex").slice(0, 16);
|
|
@@ -12184,13 +11806,13 @@ async function buildManifest() {
|
|
|
12184
11806
|
}
|
|
12185
11807
|
let indexHash = null;
|
|
12186
11808
|
try {
|
|
12187
|
-
const indexContent = await (0, import_promises9.readFile)((0,
|
|
11809
|
+
const indexContent = await (0, import_promises9.readFile)((0, import_node_path7.join)(memoryDir2(), "index.md"), "utf-8");
|
|
12188
11810
|
indexHash = `sha256:${(0, import_node_crypto2.createHash)("sha256").update(indexContent).digest("hex").slice(0, 16)}`;
|
|
12189
11811
|
} catch {
|
|
12190
11812
|
}
|
|
12191
11813
|
let logLength = 0;
|
|
12192
11814
|
try {
|
|
12193
|
-
const logContent = await (0, import_promises9.readFile)((0,
|
|
11815
|
+
const logContent = await (0, import_promises9.readFile)((0, import_node_path7.join)(memoryDir2(), "log.md"), "utf-8");
|
|
12194
11816
|
logLength = logContent.split("\n").length;
|
|
12195
11817
|
} catch {
|
|
12196
11818
|
}
|
|
@@ -12201,15 +11823,15 @@ function hashContent(content) {
|
|
|
12201
11823
|
}
|
|
12202
11824
|
async function readOnDiskNodes() {
|
|
12203
11825
|
const out = /* @__PURE__ */ new Map();
|
|
12204
|
-
if (!(0,
|
|
11826
|
+
if (!(0, import_node_fs5.existsSync)(memoryDir2())) return out;
|
|
12205
11827
|
for (const domain of DOMAINS2) {
|
|
12206
|
-
const domainDir = (0,
|
|
12207
|
-
if (!(0,
|
|
11828
|
+
const domainDir = (0, import_node_path7.join)(memoryDir2(), domain);
|
|
11829
|
+
if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
|
|
12208
11830
|
try {
|
|
12209
11831
|
for (const file of await (0, import_promises9.readdir)(domainDir)) {
|
|
12210
11832
|
if (!file.endsWith(".md")) continue;
|
|
12211
11833
|
try {
|
|
12212
|
-
out.set(`${domain}/${file}`, hashContent(await (0, import_promises9.readFile)((0,
|
|
11834
|
+
out.set(`${domain}/${file}`, hashContent(await (0, import_promises9.readFile)((0, import_node_path7.join)(domainDir, file), "utf-8")));
|
|
12213
11835
|
} catch {
|
|
12214
11836
|
}
|
|
12215
11837
|
}
|
|
@@ -12255,8 +11877,8 @@ async function computeEditedNodeUploads() {
|
|
|
12255
11877
|
const uploads = [];
|
|
12256
11878
|
for (const [path, prevHash] of prev) {
|
|
12257
11879
|
if (prevHash == null) continue;
|
|
12258
|
-
const full = (0,
|
|
12259
|
-
if (!(0,
|
|
11880
|
+
const full = (0, import_node_path7.join)(memoryDir2(), path);
|
|
11881
|
+
if (!(0, import_node_fs5.existsSync)(full)) continue;
|
|
12260
11882
|
let content;
|
|
12261
11883
|
try {
|
|
12262
11884
|
content = await (0, import_promises9.readFile)(full, "utf-8");
|
|
@@ -12292,15 +11914,15 @@ async function applyMemoryWrites(writes, opts = {}) {
|
|
|
12292
11914
|
const logLines = [`- ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)} \u2014 Applied ${count} write(s) from server`];
|
|
12293
11915
|
for (const n of notes) logLines.push(` - ${n}`);
|
|
12294
11916
|
try {
|
|
12295
|
-
const existing = (0,
|
|
12296
|
-
await (0, import_promises9.writeFile)((0,
|
|
11917
|
+
const existing = (0, import_node_fs5.existsSync)((0, import_node_path7.join)(memoryDir2(), "log.md")) ? await (0, import_promises9.readFile)((0, import_node_path7.join)(memoryDir2(), "log.md"), "utf-8") : "# Memory Log\n\n";
|
|
11918
|
+
await (0, import_promises9.writeFile)((0, import_node_path7.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
|
|
12297
11919
|
} catch {
|
|
12298
11920
|
}
|
|
12299
11921
|
await recordSyncedNodePaths();
|
|
12300
11922
|
return count;
|
|
12301
11923
|
}
|
|
12302
11924
|
async function applyOneWrite(write, treePaths) {
|
|
12303
|
-
const fullPath = (0,
|
|
11925
|
+
const fullPath = (0, import_node_path7.join)(memoryDir2(), write.path);
|
|
12304
11926
|
const notes = [];
|
|
12305
11927
|
let content = write.content;
|
|
12306
11928
|
if (treePaths && treePaths.length > 0) {
|
|
@@ -12310,7 +11932,7 @@ async function applyOneWrite(write, treePaths) {
|
|
|
12310
11932
|
notes.push(`${write.path}: dropped unmatched file_globs [${grounded.dropped.join(", ")}]`);
|
|
12311
11933
|
}
|
|
12312
11934
|
}
|
|
12313
|
-
if ((0,
|
|
11935
|
+
if ((0, import_node_fs5.existsSync)(fullPath)) {
|
|
12314
11936
|
let existing = "";
|
|
12315
11937
|
try {
|
|
12316
11938
|
existing = await (0, import_promises9.readFile)(fullPath, "utf-8");
|
|
@@ -12322,7 +11944,7 @@ async function applyOneWrite(write, treePaths) {
|
|
|
12322
11944
|
return { written: false, notes };
|
|
12323
11945
|
}
|
|
12324
11946
|
}
|
|
12325
|
-
await (0, import_promises9.mkdir)((0,
|
|
11947
|
+
await (0, import_promises9.mkdir)((0, import_node_path7.dirname)(fullPath), { recursive: true });
|
|
12326
11948
|
await (0, import_promises9.writeFile)(fullPath, content);
|
|
12327
11949
|
return { written: true, notes };
|
|
12328
11950
|
}
|
|
@@ -12363,8 +11985,8 @@ async function regenerateIndex() {
|
|
|
12363
11985
|
];
|
|
12364
11986
|
let totalNodes = 0;
|
|
12365
11987
|
for (const domain of DOMAINS2.filter((d) => d !== "_archive")) {
|
|
12366
|
-
const domainDir = (0,
|
|
12367
|
-
if (!(0,
|
|
11988
|
+
const domainDir = (0, import_node_path7.join)(memoryDir2(), domain);
|
|
11989
|
+
if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
|
|
12368
11990
|
try {
|
|
12369
11991
|
const files = await (0, import_promises9.readdir)(domainDir);
|
|
12370
11992
|
const mdFiles = files.filter((f) => f.endsWith(".md"));
|
|
@@ -12374,7 +11996,7 @@ async function regenerateIndex() {
|
|
|
12374
11996
|
for (const file of mdFiles.sort()) {
|
|
12375
11997
|
const slug = file.replace(/\.md$/, "");
|
|
12376
11998
|
try {
|
|
12377
|
-
const content = await (0, import_promises9.readFile)((0,
|
|
11999
|
+
const content = await (0, import_promises9.readFile)((0, import_node_path7.join)(domainDir, file), "utf-8");
|
|
12378
12000
|
const title = pickFrontmatter(content, "title") ?? slug;
|
|
12379
12001
|
const kind = pickFrontmatter(content, "kind") ?? "-";
|
|
12380
12002
|
const confidence = pickFrontmatter(content, "confidence");
|
|
@@ -12398,7 +12020,7 @@ async function regenerateIndex() {
|
|
|
12398
12020
|
lines.push("No nodes yet. Run an analysis to start building the knowledge graph.");
|
|
12399
12021
|
}
|
|
12400
12022
|
const next = lines.join("\n") + "\n";
|
|
12401
|
-
const indexPath = (0,
|
|
12023
|
+
const indexPath = (0, import_node_path7.join)(memoryDir2(), "index.md");
|
|
12402
12024
|
let existing = null;
|
|
12403
12025
|
try {
|
|
12404
12026
|
existing = await (0, import_promises9.readFile)(indexPath, "utf-8");
|
|
@@ -12480,9 +12102,9 @@ function hasLegacyMemoryBlock(text) {
|
|
|
12480
12102
|
return findMarker(text, LEGACY_MD_START) !== -1;
|
|
12481
12103
|
}
|
|
12482
12104
|
async function ensureClaudeMdPointer(cwd = repoRoot()) {
|
|
12483
|
-
const claudeMdPath = (0,
|
|
12105
|
+
const claudeMdPath = (0, import_node_path7.join)(cwd, "CLAUDE.md");
|
|
12484
12106
|
let existing = "";
|
|
12485
|
-
if ((0,
|
|
12107
|
+
if ((0, import_node_fs5.existsSync)(claudeMdPath)) {
|
|
12486
12108
|
existing = await (0, import_promises9.readFile)(claudeMdPath, "utf-8");
|
|
12487
12109
|
}
|
|
12488
12110
|
let startTag = CLAUDE_MD_START;
|
|
@@ -12612,7 +12234,7 @@ Body content (\u22648KB). Use [[node-id]] wikilinks for cross-references.
|
|
|
12612
12234
|
`;
|
|
12613
12235
|
|
|
12614
12236
|
// src/commands/intent.ts
|
|
12615
|
-
var
|
|
12237
|
+
var import_node_fs6 = require("node:fs");
|
|
12616
12238
|
function registerIntentCommands(program2) {
|
|
12617
12239
|
const intent = program2.command("intent").description("Manage intent capture");
|
|
12618
12240
|
intent.command("capture").description("Capture user intent from stdin (used by UserPromptSubmit hook)").action(async () => {
|
|
@@ -12621,7 +12243,7 @@ function registerIntentCommands(program2) {
|
|
|
12621
12243
|
process.chdir(repoRoot());
|
|
12622
12244
|
} catch {
|
|
12623
12245
|
}
|
|
12624
|
-
if (!(0,
|
|
12246
|
+
if (!(0, import_node_fs6.existsSync)(VERITY_DIR)) {
|
|
12625
12247
|
process.exit(0);
|
|
12626
12248
|
}
|
|
12627
12249
|
const chunks = [];
|
|
@@ -13200,6 +12822,215 @@ async function sendGeneralFeedback(message, opts, globals) {
|
|
|
13200
12822
|
var import_node_fs19 = require("node:fs");
|
|
13201
12823
|
var import_node_path15 = require("node:path");
|
|
13202
12824
|
|
|
12825
|
+
// src/lib/git.ts
|
|
12826
|
+
var import_node_child_process5 = require("node:child_process");
|
|
12827
|
+
var import_node_fs7 = require("node:fs");
|
|
12828
|
+
var import_node_path8 = require("node:path");
|
|
12829
|
+
function resolveFile(relpath) {
|
|
12830
|
+
if ((0, import_node_fs7.existsSync)(relpath)) return relpath;
|
|
12831
|
+
if ((0, import_node_fs7.existsSync)(".claude/worktrees")) {
|
|
12832
|
+
try {
|
|
12833
|
+
const entries = (0, import_node_fs7.readdirSync)(".claude/worktrees", { withFileTypes: true });
|
|
12834
|
+
for (const entry of entries) {
|
|
12835
|
+
if (!entry.isDirectory()) continue;
|
|
12836
|
+
const candidate = (0, import_node_path8.join)(".claude/worktrees", entry.name, relpath);
|
|
12837
|
+
if ((0, import_node_fs7.existsSync)(candidate)) return candidate;
|
|
12838
|
+
}
|
|
12839
|
+
} catch {
|
|
12840
|
+
}
|
|
12841
|
+
}
|
|
12842
|
+
return null;
|
|
12843
|
+
}
|
|
12844
|
+
function execGit(cmd) {
|
|
12845
|
+
try {
|
|
12846
|
+
return (0, import_node_child_process5.execSync)(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
12847
|
+
} catch {
|
|
12848
|
+
return "";
|
|
12849
|
+
}
|
|
12850
|
+
}
|
|
12851
|
+
function splitLines(s) {
|
|
12852
|
+
return s.split("\n").filter((l) => l.length > 0);
|
|
12853
|
+
}
|
|
12854
|
+
var SHA_RE = /^[0-9a-f]{40}$/;
|
|
12855
|
+
function readBaselineSha() {
|
|
12856
|
+
if (!(0, import_node_fs7.existsSync)(BASELINE_SHA_FILE)) return null;
|
|
12857
|
+
let sha;
|
|
12858
|
+
try {
|
|
12859
|
+
sha = (0, import_node_fs7.readFileSync)(BASELINE_SHA_FILE, "utf-8").trim();
|
|
12860
|
+
} catch {
|
|
12861
|
+
return null;
|
|
12862
|
+
}
|
|
12863
|
+
if (!SHA_RE.test(sha)) return null;
|
|
12864
|
+
const reachable = execGit(`git cat-file -e ${sha}^{commit} 2>/dev/null && echo ok`) === "ok";
|
|
12865
|
+
if (!reachable) {
|
|
12866
|
+
try {
|
|
12867
|
+
(0, import_node_fs7.unlinkSync)(BASELINE_SHA_FILE);
|
|
12868
|
+
} catch {
|
|
12869
|
+
}
|
|
12870
|
+
return null;
|
|
12871
|
+
}
|
|
12872
|
+
return sha;
|
|
12873
|
+
}
|
|
12874
|
+
function writeBaselineSha(sha) {
|
|
12875
|
+
if (!SHA_RE.test(sha)) return;
|
|
12876
|
+
try {
|
|
12877
|
+
(0, import_node_fs7.mkdirSync)((0, import_node_path8.dirname)(BASELINE_SHA_FILE), { recursive: true });
|
|
12878
|
+
(0, import_node_fs7.writeFileSync)(BASELINE_SHA_FILE, sha);
|
|
12879
|
+
} catch {
|
|
12880
|
+
}
|
|
12881
|
+
}
|
|
12882
|
+
function getChangedFiles() {
|
|
12883
|
+
const sets = /* @__PURE__ */ new Set();
|
|
12884
|
+
let hasRecentCommitFiles = false;
|
|
12885
|
+
for (const f of splitLines(execGit("git diff --name-only HEAD"))) sets.add(f);
|
|
12886
|
+
for (const f of splitLines(execGit("git diff --name-only --cached"))) sets.add(f);
|
|
12887
|
+
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) sets.add(f);
|
|
12888
|
+
const baseline = readBaselineSha();
|
|
12889
|
+
if (baseline) {
|
|
12890
|
+
const committed = splitLines(execGit(`git diff --name-only ${baseline}..HEAD`));
|
|
12891
|
+
if (committed.length > 0) {
|
|
12892
|
+
hasRecentCommitFiles = true;
|
|
12893
|
+
for (const f of committed) sets.add(f);
|
|
12894
|
+
}
|
|
12895
|
+
} else {
|
|
12896
|
+
const headTimestamp = parseInt(execGit("git log -1 --format=%ct HEAD"), 10) || 0;
|
|
12897
|
+
const commitAge = Math.floor(Date.now() / 1e3) - headTimestamp;
|
|
12898
|
+
const hasUnstaged = splitLines(execGit("git diff --name-only HEAD")).length > 0;
|
|
12899
|
+
const hasStaged = splitLines(execGit("git diff --name-only --cached")).length > 0;
|
|
12900
|
+
if (commitAge < 120 && !hasUnstaged && !hasStaged) {
|
|
12901
|
+
const recentFiles = splitLines(execGit("git diff --name-only HEAD~1..HEAD"));
|
|
12902
|
+
if (recentFiles.length > 0) {
|
|
12903
|
+
hasRecentCommitFiles = true;
|
|
12904
|
+
for (const f of recentFiles) sets.add(f);
|
|
12905
|
+
}
|
|
12906
|
+
}
|
|
12907
|
+
}
|
|
12908
|
+
for (const f of getWorktreeFiles()) sets.add(f);
|
|
12909
|
+
const filtered = Array.from(sets).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
12910
|
+
return { files: filtered, hasRecentCommitFiles };
|
|
12911
|
+
}
|
|
12912
|
+
function getStagedFiles() {
|
|
12913
|
+
return splitLines(execGit("git diff --cached --name-only")).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
12914
|
+
}
|
|
12915
|
+
function getDirtyFiles() {
|
|
12916
|
+
const set = /* @__PURE__ */ new Set();
|
|
12917
|
+
for (const f of splitLines(execGit("git diff --name-only HEAD"))) set.add(f);
|
|
12918
|
+
for (const f of splitLines(execGit("git diff --name-only --cached"))) set.add(f);
|
|
12919
|
+
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
|
|
12920
|
+
return Array.from(set).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
12921
|
+
}
|
|
12922
|
+
function showContentAtRef(ref, repoRelPath) {
|
|
12923
|
+
if (!ref || ref === "no-git") return null;
|
|
12924
|
+
const normalizedPath = repoRelPath.replace(/\\/g, "/");
|
|
12925
|
+
try {
|
|
12926
|
+
return (0, import_node_child_process5.execFileSync)("git", ["show", `${ref}:${normalizedPath}`], {
|
|
12927
|
+
encoding: "utf-8",
|
|
12928
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
12929
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
12930
|
+
});
|
|
12931
|
+
} catch {
|
|
12932
|
+
return null;
|
|
12933
|
+
}
|
|
12934
|
+
}
|
|
12935
|
+
function getPushRangeFiles() {
|
|
12936
|
+
const diff = (range) => splitLines(execGit(`git diff --name-only ${range}`)).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
12937
|
+
const resolvers = [
|
|
12938
|
+
() => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{push}") ? "@{push}..HEAD" : null,
|
|
12939
|
+
() => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{upstream}") ? "@{upstream}..HEAD" : null,
|
|
12940
|
+
() => {
|
|
12941
|
+
const branch = execGit("git rev-parse --abbrev-ref HEAD");
|
|
12942
|
+
return branch && branch !== "HEAD" && execGit(`git rev-parse --verify -q origin/${branch}`) ? `origin/${branch}..HEAD` : null;
|
|
12943
|
+
}
|
|
12944
|
+
];
|
|
12945
|
+
for (const resolve of resolvers) {
|
|
12946
|
+
const range = resolve();
|
|
12947
|
+
if (range) return { files: diff(range), range };
|
|
12948
|
+
}
|
|
12949
|
+
const baseline = readBaselineSha();
|
|
12950
|
+
if (baseline) {
|
|
12951
|
+
const files = diff(`${baseline}..HEAD`);
|
|
12952
|
+
if (files.length > 0) return { files, range: `${baseline}..HEAD` };
|
|
12953
|
+
}
|
|
12954
|
+
const last = diff("HEAD~1..HEAD");
|
|
12955
|
+
return { files: last, range: last.length > 0 ? "HEAD~1..HEAD" : null };
|
|
12956
|
+
}
|
|
12957
|
+
function getPushRangeMessages() {
|
|
12958
|
+
const { range } = getPushRangeFiles();
|
|
12959
|
+
if (!range) return "";
|
|
12960
|
+
return execGit(`git log ${range} --format=%B%x00`).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
|
|
12961
|
+
}
|
|
12962
|
+
function getWorktreeFiles() {
|
|
12963
|
+
const result = [];
|
|
12964
|
+
const worktreeDir = ".claude/worktrees";
|
|
12965
|
+
if (!(0, import_node_fs7.existsSync)(worktreeDir)) return result;
|
|
12966
|
+
try {
|
|
12967
|
+
const fiveMinAgo = Date.now() - 5 * 60 * 1e3;
|
|
12968
|
+
const entries = (0, import_node_fs7.readdirSync)(worktreeDir, { withFileTypes: true });
|
|
12969
|
+
for (const entry of entries) {
|
|
12970
|
+
if (!entry.isDirectory()) continue;
|
|
12971
|
+
const wtDir = (0, import_node_path8.join)(worktreeDir, entry.name);
|
|
12972
|
+
scanDir(wtDir, wtDir, fiveMinAgo, result);
|
|
12973
|
+
}
|
|
12974
|
+
} catch {
|
|
12975
|
+
}
|
|
12976
|
+
return result;
|
|
12977
|
+
}
|
|
12978
|
+
function scanDir(baseDir, dir, minMtime, result) {
|
|
12979
|
+
try {
|
|
12980
|
+
const entries = (0, import_node_fs7.readdirSync)(dir, { withFileTypes: true });
|
|
12981
|
+
for (const entry of entries) {
|
|
12982
|
+
const fullPath = (0, import_node_path8.join)(dir, entry.name);
|
|
12983
|
+
if (entry.isDirectory()) {
|
|
12984
|
+
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
12985
|
+
scanDir(baseDir, fullPath, minMtime, result);
|
|
12986
|
+
} else if (entry.isFile()) {
|
|
12987
|
+
const ext = (0, import_node_path8.extname)(entry.name).slice(1);
|
|
12988
|
+
if (!ANALYZABLE_EXTENSIONS.has(ext)) continue;
|
|
12989
|
+
try {
|
|
12990
|
+
const stat3 = (0, import_node_fs7.statSync)(fullPath);
|
|
12991
|
+
if (stat3.mtimeMs >= minMtime) {
|
|
12992
|
+
const relPath = fullPath.slice(baseDir.length + 1);
|
|
12993
|
+
result.push(relPath);
|
|
12994
|
+
}
|
|
12995
|
+
} catch {
|
|
12996
|
+
}
|
|
12997
|
+
}
|
|
12998
|
+
}
|
|
12999
|
+
} catch {
|
|
13000
|
+
}
|
|
13001
|
+
}
|
|
13002
|
+
function filterAnalyzable(files) {
|
|
13003
|
+
return files.filter((f) => {
|
|
13004
|
+
const ext = (0, import_node_path8.extname)(f).slice(1);
|
|
13005
|
+
return ANALYZABLE_EXTENSIONS.has(ext);
|
|
13006
|
+
});
|
|
13007
|
+
}
|
|
13008
|
+
function filterReviewable(files) {
|
|
13009
|
+
return files.filter((f) => {
|
|
13010
|
+
const ext = (0, import_node_path8.extname)(f).slice(1);
|
|
13011
|
+
if (ANALYZABLE_EXTENSIONS.has(ext)) return false;
|
|
13012
|
+
if (REVIEWABLE_EXTENSIONS.has(ext)) return true;
|
|
13013
|
+
const basename2 = f.split("/").pop() ?? "";
|
|
13014
|
+
if (REVIEWABLE_FILENAMES.has(basename2)) return true;
|
|
13015
|
+
if (REVIEWABLE_PATH_PATTERNS.some((p) => p.test(f))) return true;
|
|
13016
|
+
return false;
|
|
13017
|
+
});
|
|
13018
|
+
}
|
|
13019
|
+
function filterSecurity(files) {
|
|
13020
|
+
return files.filter(
|
|
13021
|
+
(f) => SECURITY_PATTERNS.some((p) => p.test(f))
|
|
13022
|
+
);
|
|
13023
|
+
}
|
|
13024
|
+
function getCurrentCommit() {
|
|
13025
|
+
return execGit("git rev-parse HEAD") || "no-git";
|
|
13026
|
+
}
|
|
13027
|
+
function listTrackedFiles() {
|
|
13028
|
+
const set = /* @__PURE__ */ new Set();
|
|
13029
|
+
for (const f of splitLines(execGit("git ls-files"))) set.add(f);
|
|
13030
|
+
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
|
|
13031
|
+
return Array.from(set);
|
|
13032
|
+
}
|
|
13033
|
+
|
|
13203
13034
|
// src/lib/files.ts
|
|
13204
13035
|
var import_node_fs8 = require("node:fs");
|
|
13205
13036
|
var import_node_path9 = require("node:path");
|
|
@@ -14807,27 +14638,6 @@ function passAndExit(reason) {
|
|
|
14807
14638
|
printJsonCompact({ gate_decision: "PASS", systemMessage: `Verity: ${reason}` });
|
|
14808
14639
|
process.exit(0);
|
|
14809
14640
|
}
|
|
14810
|
-
var EMPTY_STATIC = {
|
|
14811
|
-
tool: "@codacy/analysis-cli",
|
|
14812
|
-
findings: [],
|
|
14813
|
-
summary: { total_findings: 0, by_severity: {}, tools_run: [] }
|
|
14814
|
-
};
|
|
14815
|
-
function runLocalStatic(analyzable, securityFiles, baseline, skipStatic) {
|
|
14816
|
-
if (skipStatic || !isCodacyAvailable()) return EMPTY_STATIC;
|
|
14817
|
-
let scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles]));
|
|
14818
|
-
if (baseline) scannable = scannable.filter((f) => changedSinceBaseline(f, baseline));
|
|
14819
|
-
if (scannable.length === 0) return EMPTY_STATIC;
|
|
14820
|
-
return runCodacyAnalysis(scannable);
|
|
14821
|
-
}
|
|
14822
|
-
function localOnlyAndExit(staticResults) {
|
|
14823
|
-
printJsonCompact({
|
|
14824
|
-
gate_decision: "PASS",
|
|
14825
|
-
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.",
|
|
14826
|
-
unauthenticated: true,
|
|
14827
|
-
static_results: staticResults
|
|
14828
|
-
});
|
|
14829
|
-
process.exit(0);
|
|
14830
|
-
}
|
|
14831
14641
|
function registerAnalyzeCommand(program2) {
|
|
14832
14642
|
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) => {
|
|
14833
14643
|
const globals = program2.opts();
|
|
@@ -14878,9 +14688,12 @@ async function runAnalyze(opts, globals) {
|
|
|
14878
14688
|
passAndExit("Reflection-prompt turn \u2014 skipping analysis");
|
|
14879
14689
|
}
|
|
14880
14690
|
const tokenResult = await resolveToken(globals.token);
|
|
14691
|
+
if (!tokenResult.ok) {
|
|
14692
|
+
passAndExit("Not configured yet \u2014 run /verity-setup in Claude Code to enable quality gates.");
|
|
14693
|
+
}
|
|
14881
14694
|
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
14882
|
-
if (!
|
|
14883
|
-
|
|
14695
|
+
if (!urlResult.ok) {
|
|
14696
|
+
passAndExit("Not configured yet \u2014 run /verity-setup in Claude Code to enable quality gates.");
|
|
14884
14697
|
}
|
|
14885
14698
|
const sessionIdForMemory = sessionId || process.env.CLAUDE_SESSION_ID || "";
|
|
14886
14699
|
let contextFilePaths = [];
|
|
@@ -15454,14 +15267,13 @@ async function runReview(opts, globals) {
|
|
|
15454
15267
|
}
|
|
15455
15268
|
const codeDelta = collectCodeDelta(allFiles);
|
|
15456
15269
|
const tokenResult = await resolveToken(globals.token);
|
|
15270
|
+
if (!tokenResult.ok) {
|
|
15271
|
+
printError("Not configured yet \u2014 run /verity-setup in Claude Code to enable quality gates.");
|
|
15272
|
+
process.exit(0);
|
|
15273
|
+
}
|
|
15457
15274
|
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
15458
|
-
if (!
|
|
15459
|
-
|
|
15460
|
-
gate_decision: "PASS",
|
|
15461
|
-
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.",
|
|
15462
|
-
unauthenticated: true,
|
|
15463
|
-
static_results: staticResults
|
|
15464
|
-
});
|
|
15275
|
+
if (!urlResult.ok) {
|
|
15276
|
+
printError("Not configured yet \u2014 run /verity-setup in Claude Code to enable quality gates.");
|
|
15465
15277
|
process.exit(0);
|
|
15466
15278
|
}
|
|
15467
15279
|
let specs;
|
|
@@ -15850,7 +15662,6 @@ var import_node_fs24 = require("node:fs");
|
|
|
15850
15662
|
var import_promises13 = require("node:fs/promises");
|
|
15851
15663
|
var import_node_path18 = require("node:path");
|
|
15852
15664
|
var import_node_child_process9 = require("node:child_process");
|
|
15853
|
-
var readline = __toESM(require("node:readline/promises"));
|
|
15854
15665
|
|
|
15855
15666
|
// src/commands/migrate.ts
|
|
15856
15667
|
var import_node_fs23 = require("node:fs");
|
|
@@ -16130,66 +15941,6 @@ function registerMigrateCommand(program2) {
|
|
|
16130
15941
|
}
|
|
16131
15942
|
|
|
16132
15943
|
// src/commands/init.ts
|
|
16133
|
-
async function promptYes(question) {
|
|
16134
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
|
|
16135
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
16136
|
-
try {
|
|
16137
|
-
const answer = (await rl.question(question)).trim().toLowerCase();
|
|
16138
|
-
return answer === "" || answer === "y" || answer === "yes";
|
|
16139
|
-
} finally {
|
|
16140
|
-
rl.close();
|
|
16141
|
-
}
|
|
16142
|
-
}
|
|
16143
|
-
async function runOptionalAuth() {
|
|
16144
|
-
const existing = await resolveToken();
|
|
16145
|
-
if (existing.ok) {
|
|
16146
|
-
printInfo("Already authenticated \u2014 results will upload to the Verity service. \u2713");
|
|
16147
|
-
return;
|
|
16148
|
-
}
|
|
16149
|
-
let remote = "";
|
|
16150
|
-
try {
|
|
16151
|
-
remote = (0, import_node_child_process9.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
16152
|
-
} catch {
|
|
16153
|
-
}
|
|
16154
|
-
const localOnlyNote = () => {
|
|
16155
|
-
printInfo("Verity runs in local-only mode: the gate still runs and shows static findings, but nothing uploads.");
|
|
16156
|
-
printInfo(' Authenticate anytime: run "verity init" again, or "verity auth register".');
|
|
16157
|
-
};
|
|
16158
|
-
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
16159
|
-
console.log("");
|
|
16160
|
-
console.log(" Signing in is optional. What it does:");
|
|
16161
|
-
console.log(" - Confirms you have write access to this repository. The GitHub token");
|
|
16162
|
-
console.log(" is used once to verify that, then discarded \u2014 Verity never stores it.");
|
|
16163
|
-
console.log(" - It does NOT give Verity access to your code. Code checked by the gate");
|
|
16164
|
-
console.log(" is analyzed in memory and discarded \u2014 we never store your code.");
|
|
16165
|
-
console.log(" - It is required to store and access run history for this repo");
|
|
16166
|
-
console.log(" (past results, trends, and shareable reports).");
|
|
16167
|
-
console.log(" - Skip and Verity still works fully locally: the gate runs and shows");
|
|
16168
|
-
console.log(" findings, but nothing is uploaded.");
|
|
16169
|
-
console.log("");
|
|
16170
|
-
}
|
|
16171
|
-
const wantsAuth = await promptYes("Authenticate with GitHub now to upload results to Verity? [Y/skip] ");
|
|
16172
|
-
if (!wantsAuth) {
|
|
16173
|
-
printInfo("Skipped authentication.");
|
|
16174
|
-
localOnlyNote();
|
|
16175
|
-
return;
|
|
16176
|
-
}
|
|
16177
|
-
if (!remote) {
|
|
16178
|
-
printWarn("No git remote found \u2014 cannot authenticate yet.");
|
|
16179
|
-
localOnlyNote();
|
|
16180
|
-
return;
|
|
16181
|
-
}
|
|
16182
|
-
const projectName = parseRemote(remote)?.repo ?? (0, import_node_path18.basename)(process.cwd());
|
|
16183
|
-
printInfo("Authenticating with GitHub\u2026");
|
|
16184
|
-
const result = await registerProject({ projectName, remote, serviceUrl: DEFAULT_SERVICE_URL });
|
|
16185
|
-
if (result.ok) {
|
|
16186
|
-
printInfo(`Project registered: ${result.data.projectId} \u2713`);
|
|
16187
|
-
if (result.data.email) printInfo(` Authenticated as: ${result.data.email}`);
|
|
16188
|
-
} else {
|
|
16189
|
-
printWarn(`Authentication did not complete: ${result.error}`);
|
|
16190
|
-
localOnlyNote();
|
|
16191
|
-
}
|
|
16192
|
-
}
|
|
16193
15944
|
function resolveDataDir() {
|
|
16194
15945
|
const candidates = [
|
|
16195
15946
|
(0, import_node_path18.join)(__dirname, "..", "data"),
|
|
@@ -16333,12 +16084,6 @@ function registerInitCommand(program2) {
|
|
|
16333
16084
|
const globalVerityDir = (0, import_node_path18.join)(process.env.HOME ?? "", ".verity");
|
|
16334
16085
|
await (0, import_promises13.mkdir)(globalVerityDir, { recursive: true });
|
|
16335
16086
|
console.log("");
|
|
16336
|
-
try {
|
|
16337
|
-
await runOptionalAuth();
|
|
16338
|
-
} catch (err) {
|
|
16339
|
-
printWarn(`Authentication step skipped: ${err.message}`);
|
|
16340
|
-
}
|
|
16341
|
-
console.log("");
|
|
16342
16087
|
printInfo("Verity initialized!");
|
|
16343
16088
|
console.log("");
|
|
16344
16089
|
console.log(" Installed:");
|
|
@@ -16354,7 +16099,6 @@ function registerInitCommand(program2) {
|
|
|
16354
16099
|
console.log(" .verity/memory/ \u2014 knowledge base (8 domains, commit to git)");
|
|
16355
16100
|
console.log("");
|
|
16356
16101
|
console.log(" Next step: open this project in Claude Code and run /verity-setup");
|
|
16357
|
-
console.log(' (Not authenticated? Verity runs in local-only mode until you run "verity auth register".)');
|
|
16358
16102
|
console.log("");
|
|
16359
16103
|
});
|
|
16360
16104
|
}
|
|
@@ -17083,7 +16827,7 @@ function registerTelemetryCommands(program2) {
|
|
|
17083
16827
|
}
|
|
17084
16828
|
|
|
17085
16829
|
// src/cli.ts
|
|
17086
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.26.0
|
|
16830
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.26.0").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
|
|
17087
16831
|
registerAuthCommands(program);
|
|
17088
16832
|
registerHooksCommands(program);
|
|
17089
16833
|
registerIntentCommands(program);
|
|
@@ -15,13 +15,7 @@ You are running an on-demand Verity analysis. This is like a "second opinion"
|
|
|
15
15
|
|
|
16
16
|
1. Verify `.verity/standard.yaml` exists. If not: "Run `/verity-setup` first."
|
|
17
17
|
2. Verify `verity` CLI is available: `which verity`. If not: "Re-run the Verity installer: `curl -fsSL https://raw.githubusercontent.com/codacy/verity/main/install.sh | bash`"
|
|
18
|
-
3. Run `verity auth verify` to check
|
|
19
|
-
- **Valid**: proceed with the full deep review.
|
|
20
|
-
- **Fails / not authenticated**: don't stop. Tell the user Verity is in
|
|
21
|
-
**local-only mode**, so the review will run static analysis and show findings
|
|
22
|
-
but won't perform the deep (LLM) review or upload anything. To unlock the deep
|
|
23
|
-
review they can authenticate with `verity init` (or `verity auth register`).
|
|
24
|
-
Continue — `verity review` degrades to a local static-only report on its own.
|
|
18
|
+
3. Run `verity auth verify` to check token is valid. If it fails: "Verity not configured. Run `/verity-setup` first."
|
|
25
19
|
|
|
26
20
|
---
|
|
27
21
|
|
|
@@ -310,60 +310,25 @@ Expected: single-digit findings per file, not hundreds. If you see 50+ issues fr
|
|
|
310
310
|
|
|
311
311
|
---
|
|
312
312
|
|
|
313
|
-
## Step 6:
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
This step only checks whether the user authenticated during init, and branches
|
|
317
|
-
the rest of setup accordingly.
|
|
318
|
-
|
|
319
|
-
**If the user asks what signing in does or why it matters, tell them:**
|
|
320
|
-
- It confirms they have **write access to this repository** — the GitHub token is
|
|
321
|
-
used **once** to verify that, then discarded. Verity never stores it.
|
|
322
|
-
- It does **not** give Verity access to their code. Code checked by the gate is
|
|
323
|
-
analyzed **in memory and discarded** — Verity never stores their code.
|
|
324
|
-
- It is **required to store and access run history** for the repo (past results,
|
|
325
|
-
trends, and shareable reports).
|
|
326
|
-
- Skipping keeps Verity fully **local-only**: the gate still runs and shows
|
|
327
|
-
findings, but nothing is uploaded.
|
|
313
|
+
## Step 6: Register with Verity service
|
|
314
|
+
|
|
315
|
+
Use the `verity` CLI to register. It handles credential storage and service URL automatically.
|
|
328
316
|
|
|
329
317
|
```bash
|
|
330
|
-
verity auth
|
|
318
|
+
verity auth register --project "PROJECT_NAME" --remote "GIT_REMOTE_URL"
|
|
331
319
|
```
|
|
332
320
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
- **
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
history/org/repo data is stored. **Skip Steps 7 and 7b** (they require a token)
|
|
340
|
-
and continue to Step 8. Tell the user they can authenticate anytime to unlock
|
|
341
|
-
deep review, history, and shareable reports by running:
|
|
342
|
-
|
|
343
|
-
```bash
|
|
344
|
-
verity init # re-runs init; offers the auth prompt again
|
|
345
|
-
# or, directly:
|
|
346
|
-
verity auth register --project "PROJECT_NAME" --remote "GIT_REMOTE_URL"
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
Registration is **provider-gated** (GitHub today): the CLI runs a GitHub OAuth
|
|
350
|
-
**device flow** ("open https://github.com/login/device and enter code
|
|
351
|
-
WXYZ-1234"), proving the user has **write access** to the repo before the
|
|
352
|
-
service issues a token.
|
|
353
|
-
|
|
354
|
-
When authenticated, `.verity/credentials` contains `token`, `service_url`, and
|
|
355
|
-
`provider_token` — all subsequent `verity` upload commands work.
|
|
321
|
+
This command:
|
|
322
|
+
- **New project**: Registers, stores token + service URL in `.verity/credentials`, prints `project_id`. Continue.
|
|
323
|
+
- **Already registered**: Automatically discovers the project, ensures `.verity/credentials` has the service URL. If a token already exists in credentials, it updates the file and succeeds. If no token exists, it asks you to paste one.
|
|
324
|
+
- **Other errors**: Show the error and stop.
|
|
325
|
+
|
|
326
|
+
After this step, `.verity/credentials` will contain both `token` and `service_url` — all subsequent `verity` commands will work.
|
|
356
327
|
|
|
357
328
|
---
|
|
358
329
|
|
|
359
330
|
## Step 7: Upload Standard and config
|
|
360
331
|
|
|
361
|
-
> **Skip this entire step if the user is not authenticated** (Step 6 reported
|
|
362
|
-
> local-only mode). These commands require a token and will fail without one. The
|
|
363
|
-
> `.verity/standard.yaml` and `.codacy/codacy.config.json` you generated locally
|
|
364
|
-
> still drive the gate; they'll upload the next time the user authenticates and
|
|
365
|
-
> re-runs setup.
|
|
366
|
-
|
|
367
332
|
### Upload the Standard
|
|
368
333
|
|
|
369
334
|
The `verity` CLI handles YAML→JSON conversion automatically:
|
|
@@ -394,10 +359,6 @@ This derives a small set of descriptive memory nodes from what you already analy
|
|
|
394
359
|
|
|
395
360
|
## Step 7b: Enable telemetry (only if the user opted in at Step 3b)
|
|
396
361
|
|
|
397
|
-
> **Skip this step if the user is not authenticated** (local-only mode) — telemetry
|
|
398
|
-
> export requires the token. Note that `/usage` stays empty until they authenticate
|
|
399
|
-
> and run `verity telemetry install`.
|
|
400
|
-
|
|
401
362
|
If — and only if — the user said **Yes** in Step 3b, enable the Claude Code telemetry export
|
|
402
363
|
now (the token from Step 6 must already exist):
|
|
403
364
|
|