@codacy/verity-cli 0.24.0-experimental.fe39839 → 0.25.0-experimental.15d1b58
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 +475 -316
- package/data/skills/verity-setup/SKILL.md +11 -4
- package/package.json +1 -1
package/bin/verity.js
CHANGED
|
@@ -10326,8 +10326,8 @@ var {
|
|
|
10326
10326
|
|
|
10327
10327
|
// src/commands/auth.ts
|
|
10328
10328
|
var import_promises3 = require("node:fs/promises");
|
|
10329
|
-
var
|
|
10330
|
-
var
|
|
10329
|
+
var import_node_child_process4 = require("node:child_process");
|
|
10330
|
+
var import_node_path3 = require("node:path");
|
|
10331
10331
|
|
|
10332
10332
|
// src/lib/auth.ts
|
|
10333
10333
|
var import_promises = require("node:fs/promises");
|
|
@@ -10476,6 +10476,10 @@ var SECURITY_PATTERNS = [
|
|
|
10476
10476
|
];
|
|
10477
10477
|
var PROD_SERVICE_URL = "https://ofcamwrjwrkazqvdchko.supabase.co/functions/v1";
|
|
10478
10478
|
var DEFAULT_SERVICE_URL = "https://yyfaqvcgslcrzvrbvqik.supabase.co/functions/v1".length > 0 ? "https://yyfaqvcgslcrzvrbvqik.supabase.co/functions/v1" : PROD_SERVICE_URL;
|
|
10479
|
+
var GITHUB_CLIENT_ID = "Ov23liBpj42KMTtUtN10";
|
|
10480
|
+
var GITHUB_DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
10481
|
+
var GITHUB_ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
10482
|
+
var GITHUB_OAUTH_SCOPES = "repo user:email";
|
|
10479
10483
|
|
|
10480
10484
|
// src/lib/auth.ts
|
|
10481
10485
|
async function resolveToken(flagToken) {
|
|
@@ -10641,7 +10645,8 @@ async function apiRequest(options) {
|
|
|
10641
10645
|
timeout = 9e4,
|
|
10642
10646
|
cmd = "unknown",
|
|
10643
10647
|
retry = false,
|
|
10644
|
-
encodeBody = false
|
|
10648
|
+
encodeBody = false,
|
|
10649
|
+
extraHeaders
|
|
10645
10650
|
} = options;
|
|
10646
10651
|
const url = `${serviceUrl}${path}`;
|
|
10647
10652
|
const headers = {
|
|
@@ -10650,6 +10655,9 @@ async function apiRequest(options) {
|
|
|
10650
10655
|
if (token) {
|
|
10651
10656
|
headers["Authorization"] = `Bearer ${token}`;
|
|
10652
10657
|
}
|
|
10658
|
+
if (extraHeaders) {
|
|
10659
|
+
Object.assign(headers, extraHeaders);
|
|
10660
|
+
}
|
|
10653
10661
|
const testMockScenario = process.env.VERITY_TEST_MOCK_SCENARIO;
|
|
10654
10662
|
if (testMockScenario) {
|
|
10655
10663
|
headers["X-Verity-Mock-Scenario"] = testMockScenario;
|
|
@@ -10740,6 +10748,324 @@ function analyzeRequest(options) {
|
|
|
10740
10748
|
});
|
|
10741
10749
|
}
|
|
10742
10750
|
|
|
10751
|
+
// src/lib/provider-auth.ts
|
|
10752
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
10753
|
+
var form = (fields) => new URLSearchParams(fields).toString();
|
|
10754
|
+
async function githubDeviceFlow() {
|
|
10755
|
+
const override = process.env.VERITY_PROVIDER_TOKEN;
|
|
10756
|
+
if (override) return { ok: true, data: override };
|
|
10757
|
+
let dc;
|
|
10758
|
+
try {
|
|
10759
|
+
const res = await fetch(GITHUB_DEVICE_CODE_URL, {
|
|
10760
|
+
method: "POST",
|
|
10761
|
+
headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
|
|
10762
|
+
body: form({ client_id: GITHUB_CLIENT_ID, scope: GITHUB_OAUTH_SCOPES })
|
|
10763
|
+
});
|
|
10764
|
+
if (!res.ok) {
|
|
10765
|
+
return { ok: false, error: `GitHub device-code request failed (HTTP ${res.status})` };
|
|
10766
|
+
}
|
|
10767
|
+
dc = await res.json();
|
|
10768
|
+
} catch (err) {
|
|
10769
|
+
return { ok: false, error: `Network error contacting GitHub: ${err.message}` };
|
|
10770
|
+
}
|
|
10771
|
+
if (!dc.device_code || !dc.user_code) {
|
|
10772
|
+
return {
|
|
10773
|
+
ok: false,
|
|
10774
|
+
error: "GitHub did not return a device code (is Device Flow enabled on the OAuth app?)"
|
|
10775
|
+
};
|
|
10776
|
+
}
|
|
10777
|
+
printInfo("");
|
|
10778
|
+
printInfo(`To authorize Verity, open: ${dc.verification_uri}`);
|
|
10779
|
+
printInfo(`And enter the code: ${dc.user_code}`);
|
|
10780
|
+
printInfo("Waiting for authorization\u2026");
|
|
10781
|
+
const deadline = Date.now() + (dc.expires_in || 900) * 1e3;
|
|
10782
|
+
let interval = dc.interval || 5;
|
|
10783
|
+
while (Date.now() < deadline) {
|
|
10784
|
+
await sleep(interval * 1e3);
|
|
10785
|
+
let data;
|
|
10786
|
+
try {
|
|
10787
|
+
const res = await fetch(GITHUB_ACCESS_TOKEN_URL, {
|
|
10788
|
+
method: "POST",
|
|
10789
|
+
headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
|
|
10790
|
+
body: form({
|
|
10791
|
+
client_id: GITHUB_CLIENT_ID,
|
|
10792
|
+
device_code: dc.device_code,
|
|
10793
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
10794
|
+
})
|
|
10795
|
+
});
|
|
10796
|
+
data = await res.json().catch(() => ({}));
|
|
10797
|
+
} catch {
|
|
10798
|
+
continue;
|
|
10799
|
+
}
|
|
10800
|
+
if (data.access_token) return { ok: true, data: data.access_token };
|
|
10801
|
+
switch (data.error) {
|
|
10802
|
+
case "authorization_pending":
|
|
10803
|
+
break;
|
|
10804
|
+
case "slow_down":
|
|
10805
|
+
interval += 5;
|
|
10806
|
+
break;
|
|
10807
|
+
case "access_denied":
|
|
10808
|
+
return { ok: false, error: "Authorization was denied on GitHub." };
|
|
10809
|
+
case "expired_token":
|
|
10810
|
+
return { ok: false, error: "The authorization code expired. Re-run register." };
|
|
10811
|
+
default:
|
|
10812
|
+
if (data.error) return { ok: false, error: `GitHub auth error: ${data.error}` };
|
|
10813
|
+
}
|
|
10814
|
+
}
|
|
10815
|
+
return { ok: false, error: "Timed out waiting for GitHub authorization." };
|
|
10816
|
+
}
|
|
10817
|
+
|
|
10818
|
+
// src/lib/git.ts
|
|
10819
|
+
var import_node_child_process3 = require("node:child_process");
|
|
10820
|
+
var import_node_fs2 = require("node:fs");
|
|
10821
|
+
var import_node_path2 = require("node:path");
|
|
10822
|
+
function resolveFile(relpath) {
|
|
10823
|
+
if ((0, import_node_fs2.existsSync)(relpath)) return relpath;
|
|
10824
|
+
if ((0, import_node_fs2.existsSync)(".claude/worktrees")) {
|
|
10825
|
+
try {
|
|
10826
|
+
const entries = (0, import_node_fs2.readdirSync)(".claude/worktrees", { withFileTypes: true });
|
|
10827
|
+
for (const entry of entries) {
|
|
10828
|
+
if (!entry.isDirectory()) continue;
|
|
10829
|
+
const candidate = (0, import_node_path2.join)(".claude/worktrees", entry.name, relpath);
|
|
10830
|
+
if ((0, import_node_fs2.existsSync)(candidate)) return candidate;
|
|
10831
|
+
}
|
|
10832
|
+
} catch {
|
|
10833
|
+
}
|
|
10834
|
+
}
|
|
10835
|
+
return null;
|
|
10836
|
+
}
|
|
10837
|
+
function execGit(cmd) {
|
|
10838
|
+
try {
|
|
10839
|
+
return (0, import_node_child_process3.execSync)(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
10840
|
+
} catch {
|
|
10841
|
+
return "";
|
|
10842
|
+
}
|
|
10843
|
+
}
|
|
10844
|
+
function splitLines(s) {
|
|
10845
|
+
return s.split("\n").filter((l) => l.length > 0);
|
|
10846
|
+
}
|
|
10847
|
+
var SHA_RE = /^[0-9a-f]{40}$/;
|
|
10848
|
+
function readBaselineSha() {
|
|
10849
|
+
if (!(0, import_node_fs2.existsSync)(BASELINE_SHA_FILE)) return null;
|
|
10850
|
+
let sha;
|
|
10851
|
+
try {
|
|
10852
|
+
sha = (0, import_node_fs2.readFileSync)(BASELINE_SHA_FILE, "utf-8").trim();
|
|
10853
|
+
} catch {
|
|
10854
|
+
return null;
|
|
10855
|
+
}
|
|
10856
|
+
if (!SHA_RE.test(sha)) return null;
|
|
10857
|
+
const reachable = execGit(`git cat-file -e ${sha}^{commit} 2>/dev/null && echo ok`) === "ok";
|
|
10858
|
+
if (!reachable) {
|
|
10859
|
+
try {
|
|
10860
|
+
(0, import_node_fs2.unlinkSync)(BASELINE_SHA_FILE);
|
|
10861
|
+
} catch {
|
|
10862
|
+
}
|
|
10863
|
+
return null;
|
|
10864
|
+
}
|
|
10865
|
+
return sha;
|
|
10866
|
+
}
|
|
10867
|
+
function writeBaselineSha(sha) {
|
|
10868
|
+
if (!SHA_RE.test(sha)) return;
|
|
10869
|
+
try {
|
|
10870
|
+
(0, import_node_fs2.mkdirSync)((0, import_node_path2.dirname)(BASELINE_SHA_FILE), { recursive: true });
|
|
10871
|
+
(0, import_node_fs2.writeFileSync)(BASELINE_SHA_FILE, sha);
|
|
10872
|
+
} catch {
|
|
10873
|
+
}
|
|
10874
|
+
}
|
|
10875
|
+
function getChangedFiles() {
|
|
10876
|
+
const sets = /* @__PURE__ */ new Set();
|
|
10877
|
+
let hasRecentCommitFiles = false;
|
|
10878
|
+
for (const f of splitLines(execGit("git diff --name-only HEAD"))) sets.add(f);
|
|
10879
|
+
for (const f of splitLines(execGit("git diff --name-only --cached"))) sets.add(f);
|
|
10880
|
+
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) sets.add(f);
|
|
10881
|
+
const baseline = readBaselineSha();
|
|
10882
|
+
if (baseline) {
|
|
10883
|
+
const committed = splitLines(execGit(`git diff --name-only ${baseline}..HEAD`));
|
|
10884
|
+
if (committed.length > 0) {
|
|
10885
|
+
hasRecentCommitFiles = true;
|
|
10886
|
+
for (const f of committed) sets.add(f);
|
|
10887
|
+
}
|
|
10888
|
+
} else {
|
|
10889
|
+
const headTimestamp = parseInt(execGit("git log -1 --format=%ct HEAD"), 10) || 0;
|
|
10890
|
+
const commitAge = Math.floor(Date.now() / 1e3) - headTimestamp;
|
|
10891
|
+
const hasUnstaged = splitLines(execGit("git diff --name-only HEAD")).length > 0;
|
|
10892
|
+
const hasStaged = splitLines(execGit("git diff --name-only --cached")).length > 0;
|
|
10893
|
+
if (commitAge < 120 && !hasUnstaged && !hasStaged) {
|
|
10894
|
+
const recentFiles = splitLines(execGit("git diff --name-only HEAD~1..HEAD"));
|
|
10895
|
+
if (recentFiles.length > 0) {
|
|
10896
|
+
hasRecentCommitFiles = true;
|
|
10897
|
+
for (const f of recentFiles) sets.add(f);
|
|
10898
|
+
}
|
|
10899
|
+
}
|
|
10900
|
+
}
|
|
10901
|
+
for (const f of getWorktreeFiles()) sets.add(f);
|
|
10902
|
+
const filtered = Array.from(sets).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
10903
|
+
return { files: filtered, hasRecentCommitFiles };
|
|
10904
|
+
}
|
|
10905
|
+
function getStagedFiles() {
|
|
10906
|
+
return splitLines(execGit("git diff --cached --name-only")).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
10907
|
+
}
|
|
10908
|
+
function getDirtyFiles() {
|
|
10909
|
+
const set = /* @__PURE__ */ new Set();
|
|
10910
|
+
for (const f of splitLines(execGit("git diff --name-only HEAD"))) set.add(f);
|
|
10911
|
+
for (const f of splitLines(execGit("git diff --name-only --cached"))) set.add(f);
|
|
10912
|
+
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
|
|
10913
|
+
return Array.from(set).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
10914
|
+
}
|
|
10915
|
+
function showContentAtRef(ref, repoRelPath) {
|
|
10916
|
+
if (!ref || ref === "no-git") return null;
|
|
10917
|
+
const normalizedPath = repoRelPath.replace(/\\/g, "/");
|
|
10918
|
+
try {
|
|
10919
|
+
return (0, import_node_child_process3.execFileSync)("git", ["show", `${ref}:${normalizedPath}`], {
|
|
10920
|
+
encoding: "utf-8",
|
|
10921
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
10922
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
10923
|
+
});
|
|
10924
|
+
} catch {
|
|
10925
|
+
return null;
|
|
10926
|
+
}
|
|
10927
|
+
}
|
|
10928
|
+
function getPushRangeFiles() {
|
|
10929
|
+
const diff = (range) => splitLines(execGit(`git diff --name-only ${range}`)).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
10930
|
+
const resolvers = [
|
|
10931
|
+
() => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{push}") ? "@{push}..HEAD" : null,
|
|
10932
|
+
() => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{upstream}") ? "@{upstream}..HEAD" : null,
|
|
10933
|
+
() => {
|
|
10934
|
+
const branch = execGit("git rev-parse --abbrev-ref HEAD");
|
|
10935
|
+
return branch && branch !== "HEAD" && execGit(`git rev-parse --verify -q origin/${branch}`) ? `origin/${branch}..HEAD` : null;
|
|
10936
|
+
}
|
|
10937
|
+
];
|
|
10938
|
+
for (const resolve of resolvers) {
|
|
10939
|
+
const range = resolve();
|
|
10940
|
+
if (range) return { files: diff(range), range };
|
|
10941
|
+
}
|
|
10942
|
+
const baseline = readBaselineSha();
|
|
10943
|
+
if (baseline) {
|
|
10944
|
+
const files = diff(`${baseline}..HEAD`);
|
|
10945
|
+
if (files.length > 0) return { files, range: `${baseline}..HEAD` };
|
|
10946
|
+
}
|
|
10947
|
+
const last = diff("HEAD~1..HEAD");
|
|
10948
|
+
return { files: last, range: last.length > 0 ? "HEAD~1..HEAD" : null };
|
|
10949
|
+
}
|
|
10950
|
+
function getPushRangeMessages() {
|
|
10951
|
+
const { range } = getPushRangeFiles();
|
|
10952
|
+
if (!range) return "";
|
|
10953
|
+
return execGit(`git log ${range} --format=%B%x00`).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
|
|
10954
|
+
}
|
|
10955
|
+
function getWorktreeFiles() {
|
|
10956
|
+
const result = [];
|
|
10957
|
+
const worktreeDir = ".claude/worktrees";
|
|
10958
|
+
if (!(0, import_node_fs2.existsSync)(worktreeDir)) return result;
|
|
10959
|
+
try {
|
|
10960
|
+
const fiveMinAgo = Date.now() - 5 * 60 * 1e3;
|
|
10961
|
+
const entries = (0, import_node_fs2.readdirSync)(worktreeDir, { withFileTypes: true });
|
|
10962
|
+
for (const entry of entries) {
|
|
10963
|
+
if (!entry.isDirectory()) continue;
|
|
10964
|
+
const wtDir = (0, import_node_path2.join)(worktreeDir, entry.name);
|
|
10965
|
+
scanDir(wtDir, wtDir, fiveMinAgo, result);
|
|
10966
|
+
}
|
|
10967
|
+
} catch {
|
|
10968
|
+
}
|
|
10969
|
+
return result;
|
|
10970
|
+
}
|
|
10971
|
+
function scanDir(baseDir, dir, minMtime, result) {
|
|
10972
|
+
try {
|
|
10973
|
+
const entries = (0, import_node_fs2.readdirSync)(dir, { withFileTypes: true });
|
|
10974
|
+
for (const entry of entries) {
|
|
10975
|
+
const fullPath = (0, import_node_path2.join)(dir, entry.name);
|
|
10976
|
+
if (entry.isDirectory()) {
|
|
10977
|
+
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
10978
|
+
scanDir(baseDir, fullPath, minMtime, result);
|
|
10979
|
+
} else if (entry.isFile()) {
|
|
10980
|
+
const ext = (0, import_node_path2.extname)(entry.name).slice(1);
|
|
10981
|
+
if (!ANALYZABLE_EXTENSIONS.has(ext)) continue;
|
|
10982
|
+
try {
|
|
10983
|
+
const stat3 = (0, import_node_fs2.statSync)(fullPath);
|
|
10984
|
+
if (stat3.mtimeMs >= minMtime) {
|
|
10985
|
+
const relPath = fullPath.slice(baseDir.length + 1);
|
|
10986
|
+
result.push(relPath);
|
|
10987
|
+
}
|
|
10988
|
+
} catch {
|
|
10989
|
+
}
|
|
10990
|
+
}
|
|
10991
|
+
}
|
|
10992
|
+
} catch {
|
|
10993
|
+
}
|
|
10994
|
+
}
|
|
10995
|
+
function filterAnalyzable(files) {
|
|
10996
|
+
return files.filter((f) => {
|
|
10997
|
+
const ext = (0, import_node_path2.extname)(f).slice(1);
|
|
10998
|
+
return ANALYZABLE_EXTENSIONS.has(ext);
|
|
10999
|
+
});
|
|
11000
|
+
}
|
|
11001
|
+
function filterReviewable(files) {
|
|
11002
|
+
return files.filter((f) => {
|
|
11003
|
+
const ext = (0, import_node_path2.extname)(f).slice(1);
|
|
11004
|
+
if (ANALYZABLE_EXTENSIONS.has(ext)) return false;
|
|
11005
|
+
if (REVIEWABLE_EXTENSIONS.has(ext)) return true;
|
|
11006
|
+
const basename2 = f.split("/").pop() ?? "";
|
|
11007
|
+
if (REVIEWABLE_FILENAMES.has(basename2)) return true;
|
|
11008
|
+
if (REVIEWABLE_PATH_PATTERNS.some((p) => p.test(f))) return true;
|
|
11009
|
+
return false;
|
|
11010
|
+
});
|
|
11011
|
+
}
|
|
11012
|
+
function filterSecurity(files) {
|
|
11013
|
+
return files.filter(
|
|
11014
|
+
(f) => SECURITY_PATTERNS.some((p) => p.test(f))
|
|
11015
|
+
);
|
|
11016
|
+
}
|
|
11017
|
+
function getCurrentCommit() {
|
|
11018
|
+
return execGit("git rev-parse HEAD") || "no-git";
|
|
11019
|
+
}
|
|
11020
|
+
function detectProvider(host) {
|
|
11021
|
+
const h = host.toLowerCase();
|
|
11022
|
+
if (h.includes("github")) return "github";
|
|
11023
|
+
if (h.includes("gitlab")) return "gitlab";
|
|
11024
|
+
if (h.includes("bitbucket")) return "bitbucket";
|
|
11025
|
+
return "unknown";
|
|
11026
|
+
}
|
|
11027
|
+
function parseRemote(raw) {
|
|
11028
|
+
if (!raw || typeof raw !== "string") return null;
|
|
11029
|
+
let s = raw.trim();
|
|
11030
|
+
if (!s) return null;
|
|
11031
|
+
let host = "";
|
|
11032
|
+
let path = "";
|
|
11033
|
+
const scp = s.match(/^[^/@]+@([^:/]+):(.+)$/);
|
|
11034
|
+
if (scp) {
|
|
11035
|
+
host = scp[1];
|
|
11036
|
+
path = scp[2];
|
|
11037
|
+
} else {
|
|
11038
|
+
s = s.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//, "");
|
|
11039
|
+
s = s.replace(/^[^/@]+@/, "");
|
|
11040
|
+
const slash = s.indexOf("/");
|
|
11041
|
+
if (slash === -1) return null;
|
|
11042
|
+
host = s.slice(0, slash);
|
|
11043
|
+
path = s.slice(slash + 1);
|
|
11044
|
+
}
|
|
11045
|
+
host = host.toLowerCase().trim();
|
|
11046
|
+
path = path.replace(/\/+$/, "").replace(/\.git$/, "");
|
|
11047
|
+
if (!host || !path) return null;
|
|
11048
|
+
const segments = path.split("/").filter(Boolean);
|
|
11049
|
+
if (segments.length < 2) return null;
|
|
11050
|
+
const owner = segments[0];
|
|
11051
|
+
const repo = segments[segments.length - 1];
|
|
11052
|
+
if (!owner || !repo) return null;
|
|
11053
|
+
return {
|
|
11054
|
+
host,
|
|
11055
|
+
owner,
|
|
11056
|
+
repo,
|
|
11057
|
+
provider: detectProvider(host),
|
|
11058
|
+
orgUrl: `https://${host}/${owner}`,
|
|
11059
|
+
orgName: owner
|
|
11060
|
+
};
|
|
11061
|
+
}
|
|
11062
|
+
function listTrackedFiles() {
|
|
11063
|
+
const set = /* @__PURE__ */ new Set();
|
|
11064
|
+
for (const f of splitLines(execGit("git ls-files"))) set.add(f);
|
|
11065
|
+
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
|
|
11066
|
+
return Array.from(set);
|
|
11067
|
+
}
|
|
11068
|
+
|
|
10743
11069
|
// src/commands/auth.ts
|
|
10744
11070
|
function registerAuthCommands(program2) {
|
|
10745
11071
|
const auth = program2.command("auth").description("Manage project authentication");
|
|
@@ -10749,35 +11075,65 @@ function registerAuthCommands(program2) {
|
|
|
10749
11075
|
let remote = opts.remote;
|
|
10750
11076
|
if (!remote) {
|
|
10751
11077
|
try {
|
|
10752
|
-
remote = (0,
|
|
11078
|
+
remote = (0, import_node_child_process4.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
|
|
10753
11079
|
} catch {
|
|
10754
11080
|
printError("No git remote found. Use --remote to specify one.");
|
|
10755
11081
|
process.exit(1);
|
|
10756
11082
|
}
|
|
10757
11083
|
}
|
|
11084
|
+
const parsed = parseRemote(remote);
|
|
11085
|
+
if (!parsed) {
|
|
11086
|
+
printError(`Could not parse git remote: ${remote}`);
|
|
11087
|
+
process.exit(1);
|
|
11088
|
+
}
|
|
11089
|
+
if (parsed.provider !== "github") {
|
|
11090
|
+
printError(`Provider '${parsed.provider}' is not supported yet \u2014 GitHub only for now.`);
|
|
11091
|
+
process.exit(1);
|
|
11092
|
+
}
|
|
11093
|
+
const providerAuth = await githubDeviceFlow();
|
|
11094
|
+
if (!providerAuth.ok) {
|
|
11095
|
+
printError(providerAuth.error);
|
|
11096
|
+
process.exit(1);
|
|
11097
|
+
}
|
|
11098
|
+
const providerToken = providerAuth.data;
|
|
10758
11099
|
const result = await apiRequest({
|
|
10759
11100
|
method: "POST",
|
|
10760
11101
|
path: "/auth/register",
|
|
10761
11102
|
serviceUrl,
|
|
10762
11103
|
body: { project_name: opts.project, git_remote_url: remote },
|
|
11104
|
+
extraHeaders: { "X-Provider-Token": providerToken },
|
|
10763
11105
|
verbose: globals.verbose
|
|
10764
11106
|
});
|
|
10765
11107
|
if (!result.ok) {
|
|
10766
11108
|
printError(result.error);
|
|
10767
11109
|
process.exit(1);
|
|
10768
11110
|
}
|
|
10769
|
-
const { project_id, token, service_url } = result.data;
|
|
11111
|
+
const { project_id, token, service_url, user } = result.data;
|
|
10770
11112
|
await (0, import_promises3.mkdir)(VERITY_DIR, { recursive: true });
|
|
10771
|
-
await (0, import_promises3.writeFile)(
|
|
11113
|
+
await (0, import_promises3.writeFile)(
|
|
11114
|
+
CREDENTIALS_FILE,
|
|
11115
|
+
`token: ${token}
|
|
10772
11116
|
service_url: ${service_url}
|
|
10773
|
-
|
|
11117
|
+
provider_token: ${providerToken}
|
|
11118
|
+
`,
|
|
11119
|
+
{ mode: 384 }
|
|
11120
|
+
);
|
|
11121
|
+
await (0, import_promises3.chmod)(CREDENTIALS_FILE, 384).catch(() => {
|
|
11122
|
+
});
|
|
10774
11123
|
try {
|
|
10775
|
-
await (0, import_promises3.mkdir)((0,
|
|
10776
|
-
await (0, import_promises3.appendFile)(
|
|
10777
|
-
|
|
11124
|
+
await (0, import_promises3.mkdir)((0, import_node_path3.dirname)(GLOBAL_CREDENTIALS_FILE), { recursive: true });
|
|
11125
|
+
await (0, import_promises3.appendFile)(
|
|
11126
|
+
GLOBAL_CREDENTIALS_FILE,
|
|
11127
|
+
`${remote} token: ${token}
|
|
11128
|
+
${remote} provider_token: ${providerToken}
|
|
11129
|
+
`
|
|
11130
|
+
);
|
|
11131
|
+
await (0, import_promises3.chmod)(GLOBAL_CREDENTIALS_FILE, 384).catch(() => {
|
|
11132
|
+
});
|
|
10778
11133
|
} catch {
|
|
10779
11134
|
}
|
|
10780
11135
|
printInfo(`Project registered: ${project_id}`);
|
|
11136
|
+
if (user?.email) printInfo(`Authenticated as: ${user.email}`);
|
|
10781
11137
|
printJson({ project_id, service_url });
|
|
10782
11138
|
});
|
|
10783
11139
|
auth.command("verify").description("Verify the current token is valid").action(async () => {
|
|
@@ -10811,7 +11167,7 @@ service_url: ${service_url}
|
|
|
10811
11167
|
let remote = opts.remote;
|
|
10812
11168
|
if (!remote) {
|
|
10813
11169
|
try {
|
|
10814
|
-
remote = (0,
|
|
11170
|
+
remote = (0, import_node_child_process4.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
|
|
10815
11171
|
} catch {
|
|
10816
11172
|
printError("No git remote found. Use --remote to specify one.");
|
|
10817
11173
|
process.exit(1);
|
|
@@ -10839,7 +11195,7 @@ service_url: ${service_url}
|
|
|
10839
11195
|
|
|
10840
11196
|
// src/lib/hooks.ts
|
|
10841
11197
|
var import_promises4 = require("node:fs/promises");
|
|
10842
|
-
var
|
|
11198
|
+
var import_node_path4 = require("node:path");
|
|
10843
11199
|
var VERITY_STOP_HOOK = {
|
|
10844
11200
|
type: "command",
|
|
10845
11201
|
command: "verity analyze",
|
|
@@ -10991,19 +11347,19 @@ async function checkAllVerityHooksDetailed() {
|
|
|
10991
11347
|
return { stop, intent, baseline, current: hasCurrent, legacyOnly: hasLegacy && !hasCurrent };
|
|
10992
11348
|
}
|
|
10993
11349
|
async function writeSettings(settings) {
|
|
10994
|
-
await (0, import_promises4.mkdir)((0,
|
|
11350
|
+
await (0, import_promises4.mkdir)((0, import_node_path4.dirname)(CLAUDE_SETTINGS_FILE), { recursive: true });
|
|
10995
11351
|
await (0, import_promises4.writeFile)(CLAUDE_SETTINGS_FILE, JSON.stringify(settings, null, 2) + "\n");
|
|
10996
11352
|
}
|
|
10997
11353
|
async function readSettingsAt(root) {
|
|
10998
11354
|
try {
|
|
10999
|
-
return JSON.parse(await (0, import_promises4.readFile)((0,
|
|
11355
|
+
return JSON.parse(await (0, import_promises4.readFile)((0, import_node_path4.join)(root, CLAUDE_SETTINGS_FILE), "utf-8"));
|
|
11000
11356
|
} catch {
|
|
11001
11357
|
return {};
|
|
11002
11358
|
}
|
|
11003
11359
|
}
|
|
11004
11360
|
async function writeSettingsAt(root, settings) {
|
|
11005
|
-
const file = (0,
|
|
11006
|
-
await (0, import_promises4.mkdir)((0,
|
|
11361
|
+
const file = (0, import_node_path4.join)(root, CLAUDE_SETTINGS_FILE);
|
|
11362
|
+
await (0, import_promises4.mkdir)((0, import_node_path4.dirname)(file), { recursive: true });
|
|
11007
11363
|
await (0, import_promises4.writeFile)(file, JSON.stringify(settings, null, 2) + "\n");
|
|
11008
11364
|
}
|
|
11009
11365
|
async function hasLegacyHooksAt(root) {
|
|
@@ -11240,15 +11596,19 @@ function registerHooksCommands(program2) {
|
|
|
11240
11596
|
}
|
|
11241
11597
|
|
|
11242
11598
|
// src/commands/intent.ts
|
|
11243
|
-
var
|
|
11599
|
+
var import_node_crypto3 = require("node:crypto");
|
|
11244
11600
|
|
|
11245
11601
|
// src/lib/conversation-buffer.ts
|
|
11246
11602
|
var import_promises5 = require("node:fs/promises");
|
|
11247
|
-
var
|
|
11248
|
-
var
|
|
11603
|
+
var import_node_fs3 = require("node:fs");
|
|
11604
|
+
var import_node_child_process5 = require("node:child_process");
|
|
11605
|
+
var import_node_crypto = require("node:crypto");
|
|
11249
11606
|
function stripImageReferences(text) {
|
|
11250
11607
|
return text.replace(/\[Image #\d+\]/g, "[screenshot \u2014 not available for review]");
|
|
11251
11608
|
}
|
|
11609
|
+
function bufferTmpPath() {
|
|
11610
|
+
return `${CONVERSATION_BUFFER_FILE}.${process.pid}.${(0, import_node_crypto.randomBytes)(4).toString("hex")}.tmp`;
|
|
11611
|
+
}
|
|
11252
11612
|
async function appendToConversationBuffer(prompt, sessionId) {
|
|
11253
11613
|
try {
|
|
11254
11614
|
await (0, import_promises5.mkdir)(VERITY_DIR, { recursive: true });
|
|
@@ -11268,7 +11628,7 @@ async function appendToConversationBuffer(prompt, sessionId) {
|
|
|
11268
11628
|
recent.push(entry);
|
|
11269
11629
|
const capped = recent.slice(-CONVERSATION_MAX_ENTRIES);
|
|
11270
11630
|
const content = capped.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
11271
|
-
const tmpFile =
|
|
11631
|
+
const tmpFile = bufferTmpPath();
|
|
11272
11632
|
await (0, import_promises5.writeFile)(tmpFile, content);
|
|
11273
11633
|
await (0, import_promises5.rename)(tmpFile, CONVERSATION_BUFFER_FILE);
|
|
11274
11634
|
} catch {
|
|
@@ -11276,7 +11636,7 @@ async function appendToConversationBuffer(prompt, sessionId) {
|
|
|
11276
11636
|
}
|
|
11277
11637
|
async function readAndClearConversationBuffer(currentSessionId) {
|
|
11278
11638
|
try {
|
|
11279
|
-
if ((0,
|
|
11639
|
+
if ((0, import_node_fs3.existsSync)(CONVERSATION_BUFFER_FILE)) {
|
|
11280
11640
|
const entries = await readBufferEntries();
|
|
11281
11641
|
let mine = entries;
|
|
11282
11642
|
let others = [];
|
|
@@ -11286,7 +11646,7 @@ async function readAndClearConversationBuffer(currentSessionId) {
|
|
|
11286
11646
|
}
|
|
11287
11647
|
if (others.length > 0) {
|
|
11288
11648
|
const remaining = others.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
11289
|
-
const tmpFile =
|
|
11649
|
+
const tmpFile = bufferTmpPath();
|
|
11290
11650
|
await (0, import_promises5.writeFile)(tmpFile, remaining);
|
|
11291
11651
|
await (0, import_promises5.rename)(tmpFile, CONVERSATION_BUFFER_FILE);
|
|
11292
11652
|
} else {
|
|
@@ -11300,7 +11660,7 @@ async function readAndClearConversationBuffer(currentSessionId) {
|
|
|
11300
11660
|
};
|
|
11301
11661
|
}
|
|
11302
11662
|
}
|
|
11303
|
-
if ((0,
|
|
11663
|
+
if ((0, import_node_fs3.existsSync)(INTENT_FILE)) {
|
|
11304
11664
|
try {
|
|
11305
11665
|
const content = await (0, import_promises5.readFile)(INTENT_FILE, "utf-8");
|
|
11306
11666
|
await (0, import_promises5.unlink)(INTENT_FILE).catch(() => {
|
|
@@ -11344,7 +11704,7 @@ async function readBufferEntries() {
|
|
|
11344
11704
|
}
|
|
11345
11705
|
function getRecentCommitMessages() {
|
|
11346
11706
|
try {
|
|
11347
|
-
const output = (0,
|
|
11707
|
+
const output = (0, import_node_child_process5.execSync)(
|
|
11348
11708
|
'git log --since="30 minutes ago" --format="%s" -5',
|
|
11349
11709
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
11350
11710
|
).trim();
|
|
@@ -11357,8 +11717,8 @@ function getRecentCommitMessages() {
|
|
|
11357
11717
|
|
|
11358
11718
|
// src/lib/task-context-buffer.ts
|
|
11359
11719
|
var import_promises6 = require("node:fs/promises");
|
|
11360
|
-
var
|
|
11361
|
-
var
|
|
11720
|
+
var import_node_fs4 = require("node:fs");
|
|
11721
|
+
var import_node_path5 = require("node:path");
|
|
11362
11722
|
var TASK_CONTEXT_DIR = `${VERITY_DIR}/.task-context`;
|
|
11363
11723
|
var MAX_BUFFER_BYTES = 500 * 1024;
|
|
11364
11724
|
var MAX_PROMPT_CHARS = 2e3;
|
|
@@ -11397,7 +11757,7 @@ async function appendResponseToTaskBuffer(taskId, assistantResponse, actionSumma
|
|
|
11397
11757
|
}
|
|
11398
11758
|
async function readTaskContextBuffer(taskId) {
|
|
11399
11759
|
const filePath = bufferPath(taskId);
|
|
11400
|
-
if (!(0,
|
|
11760
|
+
if (!(0, import_node_fs4.existsSync)(filePath)) return null;
|
|
11401
11761
|
try {
|
|
11402
11762
|
const content = await (0, import_promises6.readFile)(filePath, "utf-8");
|
|
11403
11763
|
if (!content.trim()) return null;
|
|
@@ -11431,12 +11791,12 @@ async function readTaskContextBuffer(taskId) {
|
|
|
11431
11791
|
}
|
|
11432
11792
|
async function cleanupTaskContextBuffers() {
|
|
11433
11793
|
try {
|
|
11434
|
-
if (!(0,
|
|
11794
|
+
if (!(0, import_node_fs4.existsSync)(TASK_CONTEXT_DIR)) return;
|
|
11435
11795
|
const files = await (0, import_promises6.readdir)(TASK_CONTEXT_DIR);
|
|
11436
11796
|
const cutoffMs = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
11437
11797
|
for (const file of files) {
|
|
11438
11798
|
if (!file.endsWith(".jsonl")) continue;
|
|
11439
|
-
const filePath = (0,
|
|
11799
|
+
const filePath = (0, import_node_path5.join)(TASK_CONTEXT_DIR, file);
|
|
11440
11800
|
try {
|
|
11441
11801
|
const stats = await (0, import_promises6.stat)(filePath);
|
|
11442
11802
|
if (stats.mtimeMs < cutoffMs) {
|
|
@@ -11450,13 +11810,13 @@ async function cleanupTaskContextBuffers() {
|
|
|
11450
11810
|
}
|
|
11451
11811
|
function bufferPath(taskId) {
|
|
11452
11812
|
const safe = taskId.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
11453
|
-
return (0,
|
|
11813
|
+
return (0, import_node_path5.join)(TASK_CONTEXT_DIR, `${safe}.jsonl`);
|
|
11454
11814
|
}
|
|
11455
11815
|
async function appendEntry(taskId, entry) {
|
|
11456
11816
|
try {
|
|
11457
11817
|
await (0, import_promises6.mkdir)(TASK_CONTEXT_DIR, { recursive: true });
|
|
11458
11818
|
const filePath = bufferPath(taskId);
|
|
11459
|
-
if ((0,
|
|
11819
|
+
if ((0, import_node_fs4.existsSync)(filePath)) {
|
|
11460
11820
|
const stats = await (0, import_promises6.stat)(filePath);
|
|
11461
11821
|
if (stats.size >= MAX_BUFFER_BYTES) {
|
|
11462
11822
|
const content = await (0, import_promises6.readFile)(filePath, "utf-8");
|
|
@@ -11467,7 +11827,7 @@ async function appendEntry(taskId, entry) {
|
|
|
11467
11827
|
}
|
|
11468
11828
|
}
|
|
11469
11829
|
const line = JSON.stringify(entry) + "\n";
|
|
11470
|
-
const existing = (0,
|
|
11830
|
+
const existing = (0, import_node_fs4.existsSync)(filePath) ? await (0, import_promises6.readFile)(filePath, "utf-8") : "";
|
|
11471
11831
|
await (0, import_promises6.writeFile)(filePath, existing + line);
|
|
11472
11832
|
} catch {
|
|
11473
11833
|
}
|
|
@@ -11475,8 +11835,8 @@ async function appendEntry(taskId, entry) {
|
|
|
11475
11835
|
|
|
11476
11836
|
// src/lib/memory-retrieval.ts
|
|
11477
11837
|
var import_promises7 = require("node:fs/promises");
|
|
11478
|
-
var
|
|
11479
|
-
var
|
|
11838
|
+
var import_node_fs5 = require("node:fs");
|
|
11839
|
+
var import_node_path6 = require("node:path");
|
|
11480
11840
|
var memoryDir = () => projectPath(`${VERITY_DIR}/memory`);
|
|
11481
11841
|
var DOMAINS = ["decisions", "quality", "security", "intent", "gotchas", "patterns", "domain", "integrations"];
|
|
11482
11842
|
var DEFAULT_BUDGET_TOKENS = 2e3;
|
|
@@ -11569,19 +11929,19 @@ function parseFrontmatter(content) {
|
|
|
11569
11929
|
return { fm, body: match[2].trim() };
|
|
11570
11930
|
}
|
|
11571
11931
|
async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = DEFAULT_BUDGET_TOKENS) {
|
|
11572
|
-
if (!(0,
|
|
11932
|
+
if (!(0, import_node_fs5.existsSync)(memoryDir())) return null;
|
|
11573
11933
|
const budget = Math.min(budgetTokens, MAX_BUDGET_TOKENS);
|
|
11574
11934
|
const promptTokens = tokenize(promptText);
|
|
11575
11935
|
const nodes = [];
|
|
11576
11936
|
for (const domain of DOMAINS) {
|
|
11577
|
-
const domainDir = (0,
|
|
11578
|
-
if (!(0,
|
|
11937
|
+
const domainDir = (0, import_node_path6.join)(memoryDir(), domain);
|
|
11938
|
+
if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
|
|
11579
11939
|
try {
|
|
11580
11940
|
const files = await (0, import_promises7.readdir)(domainDir);
|
|
11581
11941
|
for (const file of files) {
|
|
11582
11942
|
if (!file.endsWith(".md")) continue;
|
|
11583
11943
|
try {
|
|
11584
|
-
const content = await (0, import_promises7.readFile)((0,
|
|
11944
|
+
const content = await (0, import_promises7.readFile)((0, import_node_path6.join)(domainDir, file), "utf-8");
|
|
11585
11945
|
const { fm, body } = parseFrontmatter(content);
|
|
11586
11946
|
if (fm.status && fm.status !== "active") continue;
|
|
11587
11947
|
nodes.push({
|
|
@@ -11640,9 +12000,9 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
|
|
|
11640
12000
|
|
|
11641
12001
|
// src/lib/memory-sync.ts
|
|
11642
12002
|
var import_promises8 = require("node:fs/promises");
|
|
11643
|
-
var
|
|
11644
|
-
var
|
|
11645
|
-
var
|
|
12003
|
+
var import_node_fs6 = require("node:fs");
|
|
12004
|
+
var import_node_path7 = require("node:path");
|
|
12005
|
+
var import_node_crypto2 = require("node:crypto");
|
|
11646
12006
|
|
|
11647
12007
|
// src/lib/glob-match.ts
|
|
11648
12008
|
function globToRegex(glob) {
|
|
@@ -11712,35 +12072,35 @@ var syncStateFile = () => projectPath(`${VERITY_DIR}/.memory-sync-state.json`);
|
|
|
11712
12072
|
async function ensureMemoryDir() {
|
|
11713
12073
|
await (0, import_promises8.mkdir)(memoryDir2(), { recursive: true });
|
|
11714
12074
|
for (const domain of DOMAINS2) {
|
|
11715
|
-
await (0, import_promises8.mkdir)((0,
|
|
12075
|
+
await (0, import_promises8.mkdir)((0, import_node_path7.join)(memoryDir2(), domain), { recursive: true });
|
|
11716
12076
|
}
|
|
11717
|
-
if (!(0,
|
|
11718
|
-
await (0, import_promises8.writeFile)((0,
|
|
12077
|
+
if (!(0, import_node_fs6.existsSync)((0, import_node_path7.join)(memoryDir2(), "SCHEMA.md"))) {
|
|
12078
|
+
await (0, import_promises8.writeFile)((0, import_node_path7.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
|
|
11719
12079
|
}
|
|
11720
|
-
if (!(0,
|
|
11721
|
-
await (0, import_promises8.writeFile)((0,
|
|
12080
|
+
if (!(0, import_node_fs6.existsSync)((0, import_node_path7.join)(memoryDir2(), "index.md"))) {
|
|
12081
|
+
await (0, import_promises8.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");
|
|
11722
12082
|
}
|
|
11723
|
-
if (!(0,
|
|
11724
|
-
await (0, import_promises8.writeFile)((0,
|
|
12083
|
+
if (!(0, import_node_fs6.existsSync)((0, import_node_path7.join)(memoryDir2(), "log.md"))) {
|
|
12084
|
+
await (0, import_promises8.writeFile)((0, import_node_path7.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
|
|
11725
12085
|
}
|
|
11726
12086
|
}
|
|
11727
12087
|
async function buildManifest() {
|
|
11728
|
-
if (!(0,
|
|
12088
|
+
if (!(0, import_node_fs6.existsSync)(memoryDir2())) {
|
|
11729
12089
|
return { schema_version: 1, nodes: [], index_hash: null, log_length: 0 };
|
|
11730
12090
|
}
|
|
11731
12091
|
const nodes = [];
|
|
11732
12092
|
for (const domain of DOMAINS2) {
|
|
11733
|
-
const domainDir = (0,
|
|
11734
|
-
if (!(0,
|
|
12093
|
+
const domainDir = (0, import_node_path7.join)(memoryDir2(), domain);
|
|
12094
|
+
if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
|
|
11735
12095
|
try {
|
|
11736
12096
|
const files = await (0, import_promises8.readdir)(domainDir);
|
|
11737
12097
|
for (const file of files) {
|
|
11738
12098
|
if (!file.endsWith(".md")) continue;
|
|
11739
12099
|
const filePath = `${domain}/${file}`;
|
|
11740
|
-
const fullPath = (0,
|
|
12100
|
+
const fullPath = (0, import_node_path7.join)(memoryDir2(), filePath);
|
|
11741
12101
|
try {
|
|
11742
12102
|
const content = await (0, import_promises8.readFile)(fullPath, "utf-8");
|
|
11743
|
-
const hash = (0,
|
|
12103
|
+
const hash = (0, import_node_crypto2.createHash)("sha256").update(content).digest("hex").slice(0, 16);
|
|
11744
12104
|
nodes.push({ path: filePath, content_hash: `sha256:${hash}` });
|
|
11745
12105
|
} catch {
|
|
11746
12106
|
}
|
|
@@ -11750,32 +12110,32 @@ async function buildManifest() {
|
|
|
11750
12110
|
}
|
|
11751
12111
|
let indexHash = null;
|
|
11752
12112
|
try {
|
|
11753
|
-
const indexContent = await (0, import_promises8.readFile)((0,
|
|
11754
|
-
indexHash = `sha256:${(0,
|
|
12113
|
+
const indexContent = await (0, import_promises8.readFile)((0, import_node_path7.join)(memoryDir2(), "index.md"), "utf-8");
|
|
12114
|
+
indexHash = `sha256:${(0, import_node_crypto2.createHash)("sha256").update(indexContent).digest("hex").slice(0, 16)}`;
|
|
11755
12115
|
} catch {
|
|
11756
12116
|
}
|
|
11757
12117
|
let logLength = 0;
|
|
11758
12118
|
try {
|
|
11759
|
-
const logContent = await (0, import_promises8.readFile)((0,
|
|
12119
|
+
const logContent = await (0, import_promises8.readFile)((0, import_node_path7.join)(memoryDir2(), "log.md"), "utf-8");
|
|
11760
12120
|
logLength = logContent.split("\n").length;
|
|
11761
12121
|
} catch {
|
|
11762
12122
|
}
|
|
11763
12123
|
return { schema_version: 1, nodes, index_hash: indexHash, log_length: logLength };
|
|
11764
12124
|
}
|
|
11765
12125
|
function hashContent(content) {
|
|
11766
|
-
return `sha256:${(0,
|
|
12126
|
+
return `sha256:${(0, import_node_crypto2.createHash)("sha256").update(content).digest("hex").slice(0, 16)}`;
|
|
11767
12127
|
}
|
|
11768
12128
|
async function readOnDiskNodes() {
|
|
11769
12129
|
const out = /* @__PURE__ */ new Map();
|
|
11770
|
-
if (!(0,
|
|
12130
|
+
if (!(0, import_node_fs6.existsSync)(memoryDir2())) return out;
|
|
11771
12131
|
for (const domain of DOMAINS2) {
|
|
11772
|
-
const domainDir = (0,
|
|
11773
|
-
if (!(0,
|
|
12132
|
+
const domainDir = (0, import_node_path7.join)(memoryDir2(), domain);
|
|
12133
|
+
if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
|
|
11774
12134
|
try {
|
|
11775
12135
|
for (const file of await (0, import_promises8.readdir)(domainDir)) {
|
|
11776
12136
|
if (!file.endsWith(".md")) continue;
|
|
11777
12137
|
try {
|
|
11778
|
-
out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0,
|
|
12138
|
+
out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0, import_node_path7.join)(domainDir, file), "utf-8")));
|
|
11779
12139
|
} catch {
|
|
11780
12140
|
}
|
|
11781
12141
|
}
|
|
@@ -11821,8 +12181,8 @@ async function computeEditedNodeUploads() {
|
|
|
11821
12181
|
const uploads = [];
|
|
11822
12182
|
for (const [path, prevHash] of prev) {
|
|
11823
12183
|
if (prevHash == null) continue;
|
|
11824
|
-
const full = (0,
|
|
11825
|
-
if (!(0,
|
|
12184
|
+
const full = (0, import_node_path7.join)(memoryDir2(), path);
|
|
12185
|
+
if (!(0, import_node_fs6.existsSync)(full)) continue;
|
|
11826
12186
|
let content;
|
|
11827
12187
|
try {
|
|
11828
12188
|
content = await (0, import_promises8.readFile)(full, "utf-8");
|
|
@@ -11858,15 +12218,15 @@ async function applyMemoryWrites(writes, opts = {}) {
|
|
|
11858
12218
|
const logLines = [`- ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)} \u2014 Applied ${count} write(s) from server`];
|
|
11859
12219
|
for (const n of notes) logLines.push(` - ${n}`);
|
|
11860
12220
|
try {
|
|
11861
|
-
const existing = (0,
|
|
11862
|
-
await (0, import_promises8.writeFile)((0,
|
|
12221
|
+
const existing = (0, import_node_fs6.existsSync)((0, import_node_path7.join)(memoryDir2(), "log.md")) ? await (0, import_promises8.readFile)((0, import_node_path7.join)(memoryDir2(), "log.md"), "utf-8") : "# Memory Log\n\n";
|
|
12222
|
+
await (0, import_promises8.writeFile)((0, import_node_path7.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
|
|
11863
12223
|
} catch {
|
|
11864
12224
|
}
|
|
11865
12225
|
await recordSyncedNodePaths();
|
|
11866
12226
|
return count;
|
|
11867
12227
|
}
|
|
11868
12228
|
async function applyOneWrite(write, treePaths) {
|
|
11869
|
-
const fullPath = (0,
|
|
12229
|
+
const fullPath = (0, import_node_path7.join)(memoryDir2(), write.path);
|
|
11870
12230
|
const notes = [];
|
|
11871
12231
|
let content = write.content;
|
|
11872
12232
|
if (treePaths && treePaths.length > 0) {
|
|
@@ -11876,7 +12236,7 @@ async function applyOneWrite(write, treePaths) {
|
|
|
11876
12236
|
notes.push(`${write.path}: dropped unmatched file_globs [${grounded.dropped.join(", ")}]`);
|
|
11877
12237
|
}
|
|
11878
12238
|
}
|
|
11879
|
-
if ((0,
|
|
12239
|
+
if ((0, import_node_fs6.existsSync)(fullPath)) {
|
|
11880
12240
|
let existing = "";
|
|
11881
12241
|
try {
|
|
11882
12242
|
existing = await (0, import_promises8.readFile)(fullPath, "utf-8");
|
|
@@ -11888,7 +12248,7 @@ async function applyOneWrite(write, treePaths) {
|
|
|
11888
12248
|
return { written: false, notes };
|
|
11889
12249
|
}
|
|
11890
12250
|
}
|
|
11891
|
-
await (0, import_promises8.mkdir)((0,
|
|
12251
|
+
await (0, import_promises8.mkdir)((0, import_node_path7.dirname)(fullPath), { recursive: true });
|
|
11892
12252
|
await (0, import_promises8.writeFile)(fullPath, content);
|
|
11893
12253
|
return { written: true, notes };
|
|
11894
12254
|
}
|
|
@@ -11929,8 +12289,8 @@ async function regenerateIndex() {
|
|
|
11929
12289
|
];
|
|
11930
12290
|
let totalNodes = 0;
|
|
11931
12291
|
for (const domain of DOMAINS2.filter((d) => d !== "_archive")) {
|
|
11932
|
-
const domainDir = (0,
|
|
11933
|
-
if (!(0,
|
|
12292
|
+
const domainDir = (0, import_node_path7.join)(memoryDir2(), domain);
|
|
12293
|
+
if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
|
|
11934
12294
|
try {
|
|
11935
12295
|
const files = await (0, import_promises8.readdir)(domainDir);
|
|
11936
12296
|
const mdFiles = files.filter((f) => f.endsWith(".md"));
|
|
@@ -11940,7 +12300,7 @@ async function regenerateIndex() {
|
|
|
11940
12300
|
for (const file of mdFiles.sort()) {
|
|
11941
12301
|
const slug = file.replace(/\.md$/, "");
|
|
11942
12302
|
try {
|
|
11943
|
-
const content = await (0, import_promises8.readFile)((0,
|
|
12303
|
+
const content = await (0, import_promises8.readFile)((0, import_node_path7.join)(domainDir, file), "utf-8");
|
|
11944
12304
|
const title = pickFrontmatter(content, "title") ?? slug;
|
|
11945
12305
|
const kind = pickFrontmatter(content, "kind") ?? "-";
|
|
11946
12306
|
const confidence = pickFrontmatter(content, "confidence");
|
|
@@ -11964,7 +12324,7 @@ async function regenerateIndex() {
|
|
|
11964
12324
|
lines.push("No nodes yet. Run an analysis to start building the knowledge graph.");
|
|
11965
12325
|
}
|
|
11966
12326
|
const next = lines.join("\n") + "\n";
|
|
11967
|
-
const indexPath = (0,
|
|
12327
|
+
const indexPath = (0, import_node_path7.join)(memoryDir2(), "index.md");
|
|
11968
12328
|
let existing = null;
|
|
11969
12329
|
try {
|
|
11970
12330
|
existing = await (0, import_promises8.readFile)(indexPath, "utf-8");
|
|
@@ -12046,9 +12406,9 @@ function hasLegacyMemoryBlock(text) {
|
|
|
12046
12406
|
return findMarker(text, LEGACY_MD_START) !== -1;
|
|
12047
12407
|
}
|
|
12048
12408
|
async function ensureClaudeMdPointer(cwd = repoRoot()) {
|
|
12049
|
-
const claudeMdPath = (0,
|
|
12409
|
+
const claudeMdPath = (0, import_node_path7.join)(cwd, "CLAUDE.md");
|
|
12050
12410
|
let existing = "";
|
|
12051
|
-
if ((0,
|
|
12411
|
+
if ((0, import_node_fs6.existsSync)(claudeMdPath)) {
|
|
12052
12412
|
existing = await (0, import_promises8.readFile)(claudeMdPath, "utf-8");
|
|
12053
12413
|
}
|
|
12054
12414
|
let startTag = CLAUDE_MD_START;
|
|
@@ -12178,7 +12538,7 @@ Body content (\u22648KB). Use [[node-id]] wikilinks for cross-references.
|
|
|
12178
12538
|
`;
|
|
12179
12539
|
|
|
12180
12540
|
// src/commands/intent.ts
|
|
12181
|
-
var
|
|
12541
|
+
var import_node_fs7 = require("node:fs");
|
|
12182
12542
|
function registerIntentCommands(program2) {
|
|
12183
12543
|
const intent = program2.command("intent").description("Manage intent capture");
|
|
12184
12544
|
intent.command("capture").description("Capture user intent from stdin (used by UserPromptSubmit hook)").action(async () => {
|
|
@@ -12187,7 +12547,7 @@ function registerIntentCommands(program2) {
|
|
|
12187
12547
|
process.chdir(repoRoot());
|
|
12188
12548
|
} catch {
|
|
12189
12549
|
}
|
|
12190
|
-
if (!(0,
|
|
12550
|
+
if (!(0, import_node_fs7.existsSync)(VERITY_DIR)) {
|
|
12191
12551
|
process.exit(0);
|
|
12192
12552
|
}
|
|
12193
12553
|
const chunks = [];
|
|
@@ -12250,7 +12610,7 @@ async function fireClassify(prompt, sessionId) {
|
|
|
12250
12610
|
logEvent("classify_skipped", { reason: "no_service_url", detail: urlResult.error });
|
|
12251
12611
|
return;
|
|
12252
12612
|
}
|
|
12253
|
-
const promptHash = (0,
|
|
12613
|
+
const promptHash = (0, import_node_crypto3.createHash)("sha256").update(prompt).digest("hex");
|
|
12254
12614
|
const result = await apiRequest({
|
|
12255
12615
|
method: "POST",
|
|
12256
12616
|
path: "/classify-task",
|
|
@@ -12766,215 +13126,6 @@ async function sendGeneralFeedback(message, opts, globals) {
|
|
|
12766
13126
|
var import_node_fs19 = require("node:fs");
|
|
12767
13127
|
var import_node_path14 = require("node:path");
|
|
12768
13128
|
|
|
12769
|
-
// src/lib/git.ts
|
|
12770
|
-
var import_node_child_process5 = require("node:child_process");
|
|
12771
|
-
var import_node_fs7 = require("node:fs");
|
|
12772
|
-
var import_node_path7 = require("node:path");
|
|
12773
|
-
function resolveFile(relpath) {
|
|
12774
|
-
if ((0, import_node_fs7.existsSync)(relpath)) return relpath;
|
|
12775
|
-
if ((0, import_node_fs7.existsSync)(".claude/worktrees")) {
|
|
12776
|
-
try {
|
|
12777
|
-
const entries = (0, import_node_fs7.readdirSync)(".claude/worktrees", { withFileTypes: true });
|
|
12778
|
-
for (const entry of entries) {
|
|
12779
|
-
if (!entry.isDirectory()) continue;
|
|
12780
|
-
const candidate = (0, import_node_path7.join)(".claude/worktrees", entry.name, relpath);
|
|
12781
|
-
if ((0, import_node_fs7.existsSync)(candidate)) return candidate;
|
|
12782
|
-
}
|
|
12783
|
-
} catch {
|
|
12784
|
-
}
|
|
12785
|
-
}
|
|
12786
|
-
return null;
|
|
12787
|
-
}
|
|
12788
|
-
function execGit(cmd) {
|
|
12789
|
-
try {
|
|
12790
|
-
return (0, import_node_child_process5.execSync)(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
12791
|
-
} catch {
|
|
12792
|
-
return "";
|
|
12793
|
-
}
|
|
12794
|
-
}
|
|
12795
|
-
function splitLines(s) {
|
|
12796
|
-
return s.split("\n").filter((l) => l.length > 0);
|
|
12797
|
-
}
|
|
12798
|
-
var SHA_RE = /^[0-9a-f]{40}$/;
|
|
12799
|
-
function readBaselineSha() {
|
|
12800
|
-
if (!(0, import_node_fs7.existsSync)(BASELINE_SHA_FILE)) return null;
|
|
12801
|
-
let sha;
|
|
12802
|
-
try {
|
|
12803
|
-
sha = (0, import_node_fs7.readFileSync)(BASELINE_SHA_FILE, "utf-8").trim();
|
|
12804
|
-
} catch {
|
|
12805
|
-
return null;
|
|
12806
|
-
}
|
|
12807
|
-
if (!SHA_RE.test(sha)) return null;
|
|
12808
|
-
const reachable = execGit(`git cat-file -e ${sha}^{commit} 2>/dev/null && echo ok`) === "ok";
|
|
12809
|
-
if (!reachable) {
|
|
12810
|
-
try {
|
|
12811
|
-
(0, import_node_fs7.unlinkSync)(BASELINE_SHA_FILE);
|
|
12812
|
-
} catch {
|
|
12813
|
-
}
|
|
12814
|
-
return null;
|
|
12815
|
-
}
|
|
12816
|
-
return sha;
|
|
12817
|
-
}
|
|
12818
|
-
function writeBaselineSha(sha) {
|
|
12819
|
-
if (!SHA_RE.test(sha)) return;
|
|
12820
|
-
try {
|
|
12821
|
-
(0, import_node_fs7.mkdirSync)((0, import_node_path7.dirname)(BASELINE_SHA_FILE), { recursive: true });
|
|
12822
|
-
(0, import_node_fs7.writeFileSync)(BASELINE_SHA_FILE, sha);
|
|
12823
|
-
} catch {
|
|
12824
|
-
}
|
|
12825
|
-
}
|
|
12826
|
-
function getChangedFiles() {
|
|
12827
|
-
const sets = /* @__PURE__ */ new Set();
|
|
12828
|
-
let hasRecentCommitFiles = false;
|
|
12829
|
-
for (const f of splitLines(execGit("git diff --name-only HEAD"))) sets.add(f);
|
|
12830
|
-
for (const f of splitLines(execGit("git diff --name-only --cached"))) sets.add(f);
|
|
12831
|
-
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) sets.add(f);
|
|
12832
|
-
const baseline = readBaselineSha();
|
|
12833
|
-
if (baseline) {
|
|
12834
|
-
const committed = splitLines(execGit(`git diff --name-only ${baseline}..HEAD`));
|
|
12835
|
-
if (committed.length > 0) {
|
|
12836
|
-
hasRecentCommitFiles = true;
|
|
12837
|
-
for (const f of committed) sets.add(f);
|
|
12838
|
-
}
|
|
12839
|
-
} else {
|
|
12840
|
-
const headTimestamp = parseInt(execGit("git log -1 --format=%ct HEAD"), 10) || 0;
|
|
12841
|
-
const commitAge = Math.floor(Date.now() / 1e3) - headTimestamp;
|
|
12842
|
-
const hasUnstaged = splitLines(execGit("git diff --name-only HEAD")).length > 0;
|
|
12843
|
-
const hasStaged = splitLines(execGit("git diff --name-only --cached")).length > 0;
|
|
12844
|
-
if (commitAge < 120 && !hasUnstaged && !hasStaged) {
|
|
12845
|
-
const recentFiles = splitLines(execGit("git diff --name-only HEAD~1..HEAD"));
|
|
12846
|
-
if (recentFiles.length > 0) {
|
|
12847
|
-
hasRecentCommitFiles = true;
|
|
12848
|
-
for (const f of recentFiles) sets.add(f);
|
|
12849
|
-
}
|
|
12850
|
-
}
|
|
12851
|
-
}
|
|
12852
|
-
for (const f of getWorktreeFiles()) sets.add(f);
|
|
12853
|
-
const filtered = Array.from(sets).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
12854
|
-
return { files: filtered, hasRecentCommitFiles };
|
|
12855
|
-
}
|
|
12856
|
-
function getStagedFiles() {
|
|
12857
|
-
return splitLines(execGit("git diff --cached --name-only")).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
12858
|
-
}
|
|
12859
|
-
function getDirtyFiles() {
|
|
12860
|
-
const set = /* @__PURE__ */ new Set();
|
|
12861
|
-
for (const f of splitLines(execGit("git diff --name-only HEAD"))) set.add(f);
|
|
12862
|
-
for (const f of splitLines(execGit("git diff --name-only --cached"))) set.add(f);
|
|
12863
|
-
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
|
|
12864
|
-
return Array.from(set).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
12865
|
-
}
|
|
12866
|
-
function showContentAtRef(ref, repoRelPath) {
|
|
12867
|
-
if (!ref || ref === "no-git") return null;
|
|
12868
|
-
const normalizedPath = repoRelPath.replace(/\\/g, "/");
|
|
12869
|
-
try {
|
|
12870
|
-
return (0, import_node_child_process5.execFileSync)("git", ["show", `${ref}:${normalizedPath}`], {
|
|
12871
|
-
encoding: "utf-8",
|
|
12872
|
-
maxBuffer: 64 * 1024 * 1024,
|
|
12873
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
12874
|
-
});
|
|
12875
|
-
} catch {
|
|
12876
|
-
return null;
|
|
12877
|
-
}
|
|
12878
|
-
}
|
|
12879
|
-
function getPushRangeFiles() {
|
|
12880
|
-
const diff = (range) => splitLines(execGit(`git diff --name-only ${range}`)).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
12881
|
-
const resolvers = [
|
|
12882
|
-
() => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{push}") ? "@{push}..HEAD" : null,
|
|
12883
|
-
() => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{upstream}") ? "@{upstream}..HEAD" : null,
|
|
12884
|
-
() => {
|
|
12885
|
-
const branch = execGit("git rev-parse --abbrev-ref HEAD");
|
|
12886
|
-
return branch && branch !== "HEAD" && execGit(`git rev-parse --verify -q origin/${branch}`) ? `origin/${branch}..HEAD` : null;
|
|
12887
|
-
}
|
|
12888
|
-
];
|
|
12889
|
-
for (const resolve of resolvers) {
|
|
12890
|
-
const range = resolve();
|
|
12891
|
-
if (range) return { files: diff(range), range };
|
|
12892
|
-
}
|
|
12893
|
-
const baseline = readBaselineSha();
|
|
12894
|
-
if (baseline) {
|
|
12895
|
-
const files = diff(`${baseline}..HEAD`);
|
|
12896
|
-
if (files.length > 0) return { files, range: `${baseline}..HEAD` };
|
|
12897
|
-
}
|
|
12898
|
-
const last = diff("HEAD~1..HEAD");
|
|
12899
|
-
return { files: last, range: last.length > 0 ? "HEAD~1..HEAD" : null };
|
|
12900
|
-
}
|
|
12901
|
-
function getPushRangeMessages() {
|
|
12902
|
-
const { range } = getPushRangeFiles();
|
|
12903
|
-
if (!range) return "";
|
|
12904
|
-
return execGit(`git log ${range} --format=%B%x00`).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
|
|
12905
|
-
}
|
|
12906
|
-
function getWorktreeFiles() {
|
|
12907
|
-
const result = [];
|
|
12908
|
-
const worktreeDir = ".claude/worktrees";
|
|
12909
|
-
if (!(0, import_node_fs7.existsSync)(worktreeDir)) return result;
|
|
12910
|
-
try {
|
|
12911
|
-
const fiveMinAgo = Date.now() - 5 * 60 * 1e3;
|
|
12912
|
-
const entries = (0, import_node_fs7.readdirSync)(worktreeDir, { withFileTypes: true });
|
|
12913
|
-
for (const entry of entries) {
|
|
12914
|
-
if (!entry.isDirectory()) continue;
|
|
12915
|
-
const wtDir = (0, import_node_path7.join)(worktreeDir, entry.name);
|
|
12916
|
-
scanDir(wtDir, wtDir, fiveMinAgo, result);
|
|
12917
|
-
}
|
|
12918
|
-
} catch {
|
|
12919
|
-
}
|
|
12920
|
-
return result;
|
|
12921
|
-
}
|
|
12922
|
-
function scanDir(baseDir, dir, minMtime, result) {
|
|
12923
|
-
try {
|
|
12924
|
-
const entries = (0, import_node_fs7.readdirSync)(dir, { withFileTypes: true });
|
|
12925
|
-
for (const entry of entries) {
|
|
12926
|
-
const fullPath = (0, import_node_path7.join)(dir, entry.name);
|
|
12927
|
-
if (entry.isDirectory()) {
|
|
12928
|
-
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
12929
|
-
scanDir(baseDir, fullPath, minMtime, result);
|
|
12930
|
-
} else if (entry.isFile()) {
|
|
12931
|
-
const ext = (0, import_node_path7.extname)(entry.name).slice(1);
|
|
12932
|
-
if (!ANALYZABLE_EXTENSIONS.has(ext)) continue;
|
|
12933
|
-
try {
|
|
12934
|
-
const stat3 = (0, import_node_fs7.statSync)(fullPath);
|
|
12935
|
-
if (stat3.mtimeMs >= minMtime) {
|
|
12936
|
-
const relPath = fullPath.slice(baseDir.length + 1);
|
|
12937
|
-
result.push(relPath);
|
|
12938
|
-
}
|
|
12939
|
-
} catch {
|
|
12940
|
-
}
|
|
12941
|
-
}
|
|
12942
|
-
}
|
|
12943
|
-
} catch {
|
|
12944
|
-
}
|
|
12945
|
-
}
|
|
12946
|
-
function filterAnalyzable(files) {
|
|
12947
|
-
return files.filter((f) => {
|
|
12948
|
-
const ext = (0, import_node_path7.extname)(f).slice(1);
|
|
12949
|
-
return ANALYZABLE_EXTENSIONS.has(ext);
|
|
12950
|
-
});
|
|
12951
|
-
}
|
|
12952
|
-
function filterReviewable(files) {
|
|
12953
|
-
return files.filter((f) => {
|
|
12954
|
-
const ext = (0, import_node_path7.extname)(f).slice(1);
|
|
12955
|
-
if (ANALYZABLE_EXTENSIONS.has(ext)) return false;
|
|
12956
|
-
if (REVIEWABLE_EXTENSIONS.has(ext)) return true;
|
|
12957
|
-
const basename2 = f.split("/").pop() ?? "";
|
|
12958
|
-
if (REVIEWABLE_FILENAMES.has(basename2)) return true;
|
|
12959
|
-
if (REVIEWABLE_PATH_PATTERNS.some((p) => p.test(f))) return true;
|
|
12960
|
-
return false;
|
|
12961
|
-
});
|
|
12962
|
-
}
|
|
12963
|
-
function filterSecurity(files) {
|
|
12964
|
-
return files.filter(
|
|
12965
|
-
(f) => SECURITY_PATTERNS.some((p) => p.test(f))
|
|
12966
|
-
);
|
|
12967
|
-
}
|
|
12968
|
-
function getCurrentCommit() {
|
|
12969
|
-
return execGit("git rev-parse HEAD") || "no-git";
|
|
12970
|
-
}
|
|
12971
|
-
function listTrackedFiles() {
|
|
12972
|
-
const set = /* @__PURE__ */ new Set();
|
|
12973
|
-
for (const f of splitLines(execGit("git ls-files"))) set.add(f);
|
|
12974
|
-
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
|
|
12975
|
-
return Array.from(set);
|
|
12976
|
-
}
|
|
12977
|
-
|
|
12978
13129
|
// src/lib/files.ts
|
|
12979
13130
|
var import_node_fs8 = require("node:fs");
|
|
12980
13131
|
var import_node_path8 = require("node:path");
|
|
@@ -13107,11 +13258,16 @@ function collectCodeDelta(files, opts) {
|
|
|
13107
13258
|
|
|
13108
13259
|
// src/lib/debounce.ts
|
|
13109
13260
|
var import_node_fs9 = require("node:fs");
|
|
13110
|
-
var
|
|
13111
|
-
function
|
|
13112
|
-
if (!
|
|
13261
|
+
var import_node_crypto4 = require("node:crypto");
|
|
13262
|
+
function scopedFile(base, sessionId) {
|
|
13263
|
+
if (!sessionId) return base;
|
|
13264
|
+
return `${base}.${(0, import_node_crypto4.createHash)("sha1").update(sessionId).digest("hex").slice(0, 12)}`;
|
|
13265
|
+
}
|
|
13266
|
+
function checkDebounce(debounceSeconds = DEBOUNCE_SECONDS, sessionId) {
|
|
13267
|
+
const file = scopedFile(DEBOUNCE_FILE, sessionId);
|
|
13268
|
+
if (!(0, import_node_fs9.existsSync)(file)) return null;
|
|
13113
13269
|
try {
|
|
13114
|
-
const lastTs = parseInt((0, import_node_fs9.readFileSync)(
|
|
13270
|
+
const lastTs = parseInt((0, import_node_fs9.readFileSync)(file, "utf-8").trim(), 10);
|
|
13115
13271
|
const nowTs = Math.floor(Date.now() / 1e3);
|
|
13116
13272
|
const elapsed = nowTs - lastTs;
|
|
13117
13273
|
if (elapsed < debounceSeconds) {
|
|
@@ -13121,12 +13277,13 @@ function checkDebounce(debounceSeconds = DEBOUNCE_SECONDS) {
|
|
|
13121
13277
|
}
|
|
13122
13278
|
return null;
|
|
13123
13279
|
}
|
|
13124
|
-
function checkMtime(files, bypassForRecentCommits) {
|
|
13280
|
+
function checkMtime(files, bypassForRecentCommits, sessionId) {
|
|
13125
13281
|
if (bypassForRecentCommits) return null;
|
|
13126
|
-
|
|
13282
|
+
const file = scopedFile(DEBOUNCE_FILE, sessionId);
|
|
13283
|
+
if (!(0, import_node_fs9.existsSync)(file)) return null;
|
|
13127
13284
|
let debounceTime;
|
|
13128
13285
|
try {
|
|
13129
|
-
debounceTime = (0, import_node_fs9.statSync)(
|
|
13286
|
+
debounceTime = (0, import_node_fs9.statSync)(file).mtimeMs;
|
|
13130
13287
|
} catch {
|
|
13131
13288
|
return null;
|
|
13132
13289
|
}
|
|
@@ -13145,7 +13302,7 @@ function checkMtime(files, bypassForRecentCommits) {
|
|
|
13145
13302
|
return "No files modified since last analysis";
|
|
13146
13303
|
}
|
|
13147
13304
|
function computeContentHash(files) {
|
|
13148
|
-
const hash = (0,
|
|
13305
|
+
const hash = (0, import_node_crypto4.createHash)("sha1");
|
|
13149
13306
|
const sorted = [...files].sort();
|
|
13150
13307
|
for (const f of sorted) {
|
|
13151
13308
|
const resolved = resolveFile(f) ?? f;
|
|
@@ -13158,11 +13315,12 @@ function computeContentHash(files) {
|
|
|
13158
13315
|
}
|
|
13159
13316
|
return hash.digest("hex");
|
|
13160
13317
|
}
|
|
13161
|
-
function checkContentHash(files) {
|
|
13318
|
+
function checkContentHash(files, sessionId) {
|
|
13162
13319
|
const hash = computeContentHash(files);
|
|
13163
|
-
|
|
13320
|
+
const file = scopedFile(HASH_FILE, sessionId);
|
|
13321
|
+
if ((0, import_node_fs9.existsSync)(file)) {
|
|
13164
13322
|
try {
|
|
13165
|
-
const storedHash = (0, import_node_fs9.readFileSync)(
|
|
13323
|
+
const storedHash = (0, import_node_fs9.readFileSync)(file, "utf-8").trim();
|
|
13166
13324
|
if (hash === storedHash) {
|
|
13167
13325
|
return { skip: "No source changes since last analysis", hash };
|
|
13168
13326
|
}
|
|
@@ -13171,18 +13329,19 @@ function checkContentHash(files) {
|
|
|
13171
13329
|
}
|
|
13172
13330
|
return { skip: null, hash };
|
|
13173
13331
|
}
|
|
13174
|
-
function recordAnalysisStart() {
|
|
13332
|
+
function recordAnalysisStart(sessionId) {
|
|
13175
13333
|
(0, import_node_fs9.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
13176
|
-
(0, import_node_fs9.writeFileSync)(DEBOUNCE_FILE, String(Math.floor(Date.now() / 1e3)));
|
|
13334
|
+
(0, import_node_fs9.writeFileSync)(scopedFile(DEBOUNCE_FILE, sessionId), String(Math.floor(Date.now() / 1e3)));
|
|
13177
13335
|
}
|
|
13178
|
-
function recordPassHash(hash) {
|
|
13179
|
-
(0, import_node_fs9.writeFileSync)(HASH_FILE, hash);
|
|
13336
|
+
function recordPassHash(hash, sessionId) {
|
|
13337
|
+
(0, import_node_fs9.writeFileSync)(scopedFile(HASH_FILE, sessionId), hash);
|
|
13180
13338
|
}
|
|
13181
|
-
function narrowToRecent(files) {
|
|
13182
|
-
|
|
13339
|
+
function narrowToRecent(files, sessionId) {
|
|
13340
|
+
const file = scopedFile(DEBOUNCE_FILE, sessionId);
|
|
13341
|
+
if (!(0, import_node_fs9.existsSync)(file)) return files;
|
|
13183
13342
|
let debounceTime;
|
|
13184
13343
|
try {
|
|
13185
|
-
debounceTime = (0, import_node_fs9.statSync)(
|
|
13344
|
+
debounceTime = (0, import_node_fs9.statSync)(file).mtimeMs;
|
|
13186
13345
|
} catch {
|
|
13187
13346
|
return files;
|
|
13188
13347
|
}
|
|
@@ -13531,14 +13690,14 @@ function cleanStaleSnapshots(dir, keepSet) {
|
|
|
13531
13690
|
// src/lib/baseline.ts
|
|
13532
13691
|
var import_node_fs13 = require("node:fs");
|
|
13533
13692
|
var import_node_path11 = require("node:path");
|
|
13534
|
-
var
|
|
13693
|
+
var import_node_crypto5 = require("node:crypto");
|
|
13535
13694
|
var BASELINE_VERSION = 1;
|
|
13536
13695
|
var BASELINE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
13537
13696
|
var MIRROR_MAX_BYTES = 2 * 1024 * 1024;
|
|
13538
13697
|
var DEFAULT_SESSION_KEY = "_default";
|
|
13539
13698
|
function sessionKey(sessionId) {
|
|
13540
13699
|
if (!sessionId) return DEFAULT_SESSION_KEY;
|
|
13541
|
-
return (0,
|
|
13700
|
+
return (0, import_node_crypto5.createHash)("sha256").update(sessionId).digest("hex").slice(0, 16);
|
|
13542
13701
|
}
|
|
13543
13702
|
function sessionDir(key) {
|
|
13544
13703
|
return (0, import_node_path11.join)(projectPath(BASELINE_DIR), key);
|
|
@@ -13722,11 +13881,11 @@ function pruneOldBaselines() {
|
|
|
13722
13881
|
|
|
13723
13882
|
// src/lib/offline.ts
|
|
13724
13883
|
var import_node_fs14 = require("node:fs");
|
|
13725
|
-
var
|
|
13884
|
+
var import_node_crypto6 = require("node:crypto");
|
|
13726
13885
|
function cacheRequest(body) {
|
|
13727
13886
|
try {
|
|
13728
13887
|
(0, import_node_fs14.mkdirSync)(CACHE_DIR, { recursive: true });
|
|
13729
|
-
const suffix = (0,
|
|
13888
|
+
const suffix = (0, import_node_crypto6.randomBytes)(4).toString("hex");
|
|
13730
13889
|
const filename = `pending-${Math.floor(Date.now() / 1e3)}-${suffix}.json`;
|
|
13731
13890
|
(0, import_node_fs14.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(body));
|
|
13732
13891
|
} catch {
|
|
@@ -14688,7 +14847,7 @@ async function runAnalyze(opts, globals) {
|
|
|
14688
14847
|
let currentCommit = "";
|
|
14689
14848
|
if (analysisMode !== "plan") {
|
|
14690
14849
|
const debounceSeconds = parseInt(opts.debounce, 10);
|
|
14691
|
-
const debounceSkip = checkDebounce(debounceSeconds);
|
|
14850
|
+
const debounceSkip = checkDebounce(debounceSeconds, baselineSessionId);
|
|
14692
14851
|
if (debounceSkip) {
|
|
14693
14852
|
if (assistantResponse) {
|
|
14694
14853
|
analysisMode = "plan";
|
|
@@ -14698,7 +14857,7 @@ async function runAnalyze(opts, globals) {
|
|
|
14698
14857
|
}
|
|
14699
14858
|
if (analysisMode !== "plan") {
|
|
14700
14859
|
const allCheckable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable, ...securityFiles]));
|
|
14701
|
-
const mtimeSkip = checkMtime(allCheckable, hasRecentCommitFiles);
|
|
14860
|
+
const mtimeSkip = checkMtime(allCheckable, hasRecentCommitFiles, baselineSessionId);
|
|
14702
14861
|
if (mtimeSkip) {
|
|
14703
14862
|
if (assistantResponse) {
|
|
14704
14863
|
analysisMode = "plan";
|
|
@@ -14708,9 +14867,9 @@ async function runAnalyze(opts, globals) {
|
|
|
14708
14867
|
}
|
|
14709
14868
|
}
|
|
14710
14869
|
if (analysisMode !== "plan") {
|
|
14711
|
-
recordAnalysisStart();
|
|
14870
|
+
recordAnalysisStart(baselineSessionId);
|
|
14712
14871
|
const allCheckable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable, ...securityFiles]));
|
|
14713
|
-
const hashResult = checkContentHash(allCheckable);
|
|
14872
|
+
const hashResult = checkContentHash(allCheckable, baselineSessionId);
|
|
14714
14873
|
if (hashResult.skip) {
|
|
14715
14874
|
if (assistantResponse) {
|
|
14716
14875
|
analysisMode = "plan";
|
|
@@ -14722,7 +14881,7 @@ async function runAnalyze(opts, globals) {
|
|
|
14722
14881
|
if (analysisMode !== "plan") {
|
|
14723
14882
|
const agentNarrowed = narrowToAgentAuthored(allForReview, actionSummary);
|
|
14724
14883
|
const baseForReview = agentNarrowed.length > 0 ? agentNarrowed : allForReview;
|
|
14725
|
-
const recentForReview = narrowToRecent(baseForReview);
|
|
14884
|
+
const recentForReview = narrowToRecent(baseForReview, baselineSessionId);
|
|
14726
14885
|
if (!opts.skipStatic && isCodacyAvailable()) {
|
|
14727
14886
|
let allScannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles]));
|
|
14728
14887
|
if (baseline) {
|
|
@@ -15097,7 +15256,7 @@ async function runAnalyze(opts, globals) {
|
|
|
15097
15256
|
}
|
|
15098
15257
|
case "PASS": {
|
|
15099
15258
|
writeIteration(1, currentCommit, contentHash ?? void 0);
|
|
15100
|
-
if (contentHash) recordPassHash(contentHash);
|
|
15259
|
+
if (contentHash) recordPassHash(contentHash, baselineSessionId);
|
|
15101
15260
|
if (currentCommit && currentCommit !== "no-git") writeBaselineSha(currentCommit);
|
|
15102
15261
|
let userSummary = response.user_summary ?? "Verity: PASS";
|
|
15103
15262
|
const viewUrl = response.view_url ?? "";
|
|
@@ -15108,7 +15267,7 @@ async function runAnalyze(opts, globals) {
|
|
|
15108
15267
|
break;
|
|
15109
15268
|
}
|
|
15110
15269
|
case "WARN": {
|
|
15111
|
-
if (contentHash) recordPassHash(contentHash);
|
|
15270
|
+
if (contentHash) recordPassHash(contentHash, baselineSessionId);
|
|
15112
15271
|
if (currentCommit && currentCommit !== "no-git") writeBaselineSha(currentCommit);
|
|
15113
15272
|
let userSummary = response.user_summary ?? "Verity: WARN";
|
|
15114
15273
|
const viewUrl = response.view_url ?? "";
|
|
@@ -16766,7 +16925,7 @@ function registerTelemetryCommands(program2) {
|
|
|
16766
16925
|
}
|
|
16767
16926
|
|
|
16768
16927
|
// src/cli.ts
|
|
16769
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.
|
|
16928
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.25.0-experimental.15d1b58").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
|
|
16770
16929
|
registerAuthCommands(program);
|
|
16771
16930
|
registerHooksCommands(program);
|
|
16772
16931
|
registerIntentCommands(program);
|
|
@@ -312,18 +312,25 @@ Expected: single-digit findings per file, not hundreds. If you see 50+ issues fr
|
|
|
312
312
|
|
|
313
313
|
## Step 6: Register with Verity service
|
|
314
314
|
|
|
315
|
-
Use the `verity` CLI to register. It handles credential storage and service URL automatically.
|
|
315
|
+
Use the `verity` CLI to register. It handles provider auth, credential storage, and the service URL automatically.
|
|
316
316
|
|
|
317
317
|
```bash
|
|
318
318
|
verity auth register --project "PROJECT_NAME" --remote "GIT_REMOTE_URL"
|
|
319
319
|
```
|
|
320
320
|
|
|
321
|
+
Registration is **provider-gated** (GitHub today): the CLI runs a GitHub OAuth
|
|
322
|
+
**device flow** and prints something like *"open https://github.com/login/device
|
|
323
|
+
and enter code WXYZ-1234"*. The user approves in the browser; the CLI then proves
|
|
324
|
+
the user has **write access** to the repo before the service issues a token. Tell
|
|
325
|
+
the user to expect this prompt and to complete it in their browser.
|
|
326
|
+
|
|
321
327
|
This command:
|
|
322
|
-
- **
|
|
323
|
-
- **
|
|
328
|
+
- **Write access confirmed**: Registers, stores the verity token + service URL + provider token in `.verity/credentials` (perms 600), prints `project_id` and the authenticated email. Continue.
|
|
329
|
+
- **No write access** (`403 NO_WRITE_ACCESS`): The user lacks push rights on the repo — they can't register it. Verity still runs locally as an anonymous gate, but no history/org/repo data is stored. Stop.
|
|
330
|
+
- **Non-GitHub remote**: Only GitHub is supported for now; show the error and stop.
|
|
324
331
|
- **Other errors**: Show the error and stop.
|
|
325
332
|
|
|
326
|
-
After this step, `.verity/credentials` will contain
|
|
333
|
+
After this step, `.verity/credentials` will contain `token`, `service_url`, and `provider_token` — all subsequent `verity` commands will work.
|
|
327
334
|
|
|
328
335
|
---
|
|
329
336
|
|