@codacy/verity-cli 0.24.0-experimental.ae62726 → 0.24.0-experimental.b35bbc7
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 +458 -303
- 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");
|
|
@@ -10475,7 +10475,11 @@ var SECURITY_PATTERNS = [
|
|
|
10475
10475
|
/Dockerfile/
|
|
10476
10476
|
];
|
|
10477
10477
|
var PROD_SERVICE_URL = "https://ofcamwrjwrkazqvdchko.supabase.co/functions/v1";
|
|
10478
|
-
var DEFAULT_SERVICE_URL = "https://
|
|
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) {
|
|
@@ -11244,8 +11600,8 @@ var import_node_crypto2 = 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");
|
|
11249
11605
|
function stripImageReferences(text) {
|
|
11250
11606
|
return text.replace(/\[Image #\d+\]/g, "[screenshot \u2014 not available for review]");
|
|
11251
11607
|
}
|
|
@@ -11276,7 +11632,7 @@ async function appendToConversationBuffer(prompt, sessionId) {
|
|
|
11276
11632
|
}
|
|
11277
11633
|
async function readAndClearConversationBuffer(currentSessionId) {
|
|
11278
11634
|
try {
|
|
11279
|
-
if ((0,
|
|
11635
|
+
if ((0, import_node_fs3.existsSync)(CONVERSATION_BUFFER_FILE)) {
|
|
11280
11636
|
const entries = await readBufferEntries();
|
|
11281
11637
|
let mine = entries;
|
|
11282
11638
|
let others = [];
|
|
@@ -11300,7 +11656,7 @@ async function readAndClearConversationBuffer(currentSessionId) {
|
|
|
11300
11656
|
};
|
|
11301
11657
|
}
|
|
11302
11658
|
}
|
|
11303
|
-
if ((0,
|
|
11659
|
+
if ((0, import_node_fs3.existsSync)(INTENT_FILE)) {
|
|
11304
11660
|
try {
|
|
11305
11661
|
const content = await (0, import_promises5.readFile)(INTENT_FILE, "utf-8");
|
|
11306
11662
|
await (0, import_promises5.unlink)(INTENT_FILE).catch(() => {
|
|
@@ -11344,7 +11700,7 @@ async function readBufferEntries() {
|
|
|
11344
11700
|
}
|
|
11345
11701
|
function getRecentCommitMessages() {
|
|
11346
11702
|
try {
|
|
11347
|
-
const output = (0,
|
|
11703
|
+
const output = (0, import_node_child_process5.execSync)(
|
|
11348
11704
|
'git log --since="30 minutes ago" --format="%s" -5',
|
|
11349
11705
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
11350
11706
|
).trim();
|
|
@@ -11357,8 +11713,8 @@ function getRecentCommitMessages() {
|
|
|
11357
11713
|
|
|
11358
11714
|
// src/lib/task-context-buffer.ts
|
|
11359
11715
|
var import_promises6 = require("node:fs/promises");
|
|
11360
|
-
var
|
|
11361
|
-
var
|
|
11716
|
+
var import_node_fs4 = require("node:fs");
|
|
11717
|
+
var import_node_path5 = require("node:path");
|
|
11362
11718
|
var TASK_CONTEXT_DIR = `${VERITY_DIR}/.task-context`;
|
|
11363
11719
|
var MAX_BUFFER_BYTES = 500 * 1024;
|
|
11364
11720
|
var MAX_PROMPT_CHARS = 2e3;
|
|
@@ -11397,7 +11753,7 @@ async function appendResponseToTaskBuffer(taskId, assistantResponse, actionSumma
|
|
|
11397
11753
|
}
|
|
11398
11754
|
async function readTaskContextBuffer(taskId) {
|
|
11399
11755
|
const filePath = bufferPath(taskId);
|
|
11400
|
-
if (!(0,
|
|
11756
|
+
if (!(0, import_node_fs4.existsSync)(filePath)) return null;
|
|
11401
11757
|
try {
|
|
11402
11758
|
const content = await (0, import_promises6.readFile)(filePath, "utf-8");
|
|
11403
11759
|
if (!content.trim()) return null;
|
|
@@ -11431,12 +11787,12 @@ async function readTaskContextBuffer(taskId) {
|
|
|
11431
11787
|
}
|
|
11432
11788
|
async function cleanupTaskContextBuffers() {
|
|
11433
11789
|
try {
|
|
11434
|
-
if (!(0,
|
|
11790
|
+
if (!(0, import_node_fs4.existsSync)(TASK_CONTEXT_DIR)) return;
|
|
11435
11791
|
const files = await (0, import_promises6.readdir)(TASK_CONTEXT_DIR);
|
|
11436
11792
|
const cutoffMs = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
11437
11793
|
for (const file of files) {
|
|
11438
11794
|
if (!file.endsWith(".jsonl")) continue;
|
|
11439
|
-
const filePath = (0,
|
|
11795
|
+
const filePath = (0, import_node_path5.join)(TASK_CONTEXT_DIR, file);
|
|
11440
11796
|
try {
|
|
11441
11797
|
const stats = await (0, import_promises6.stat)(filePath);
|
|
11442
11798
|
if (stats.mtimeMs < cutoffMs) {
|
|
@@ -11450,13 +11806,13 @@ async function cleanupTaskContextBuffers() {
|
|
|
11450
11806
|
}
|
|
11451
11807
|
function bufferPath(taskId) {
|
|
11452
11808
|
const safe = taskId.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
11453
|
-
return (0,
|
|
11809
|
+
return (0, import_node_path5.join)(TASK_CONTEXT_DIR, `${safe}.jsonl`);
|
|
11454
11810
|
}
|
|
11455
11811
|
async function appendEntry(taskId, entry) {
|
|
11456
11812
|
try {
|
|
11457
11813
|
await (0, import_promises6.mkdir)(TASK_CONTEXT_DIR, { recursive: true });
|
|
11458
11814
|
const filePath = bufferPath(taskId);
|
|
11459
|
-
if ((0,
|
|
11815
|
+
if ((0, import_node_fs4.existsSync)(filePath)) {
|
|
11460
11816
|
const stats = await (0, import_promises6.stat)(filePath);
|
|
11461
11817
|
if (stats.size >= MAX_BUFFER_BYTES) {
|
|
11462
11818
|
const content = await (0, import_promises6.readFile)(filePath, "utf-8");
|
|
@@ -11467,7 +11823,7 @@ async function appendEntry(taskId, entry) {
|
|
|
11467
11823
|
}
|
|
11468
11824
|
}
|
|
11469
11825
|
const line = JSON.stringify(entry) + "\n";
|
|
11470
|
-
const existing = (0,
|
|
11826
|
+
const existing = (0, import_node_fs4.existsSync)(filePath) ? await (0, import_promises6.readFile)(filePath, "utf-8") : "";
|
|
11471
11827
|
await (0, import_promises6.writeFile)(filePath, existing + line);
|
|
11472
11828
|
} catch {
|
|
11473
11829
|
}
|
|
@@ -11475,8 +11831,8 @@ async function appendEntry(taskId, entry) {
|
|
|
11475
11831
|
|
|
11476
11832
|
// src/lib/memory-retrieval.ts
|
|
11477
11833
|
var import_promises7 = require("node:fs/promises");
|
|
11478
|
-
var
|
|
11479
|
-
var
|
|
11834
|
+
var import_node_fs5 = require("node:fs");
|
|
11835
|
+
var import_node_path6 = require("node:path");
|
|
11480
11836
|
var memoryDir = () => projectPath(`${VERITY_DIR}/memory`);
|
|
11481
11837
|
var DOMAINS = ["decisions", "quality", "security", "intent", "gotchas", "patterns", "domain", "integrations"];
|
|
11482
11838
|
var DEFAULT_BUDGET_TOKENS = 2e3;
|
|
@@ -11569,19 +11925,19 @@ function parseFrontmatter(content) {
|
|
|
11569
11925
|
return { fm, body: match[2].trim() };
|
|
11570
11926
|
}
|
|
11571
11927
|
async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = DEFAULT_BUDGET_TOKENS) {
|
|
11572
|
-
if (!(0,
|
|
11928
|
+
if (!(0, import_node_fs5.existsSync)(memoryDir())) return null;
|
|
11573
11929
|
const budget = Math.min(budgetTokens, MAX_BUDGET_TOKENS);
|
|
11574
11930
|
const promptTokens = tokenize(promptText);
|
|
11575
11931
|
const nodes = [];
|
|
11576
11932
|
for (const domain of DOMAINS) {
|
|
11577
|
-
const domainDir = (0,
|
|
11578
|
-
if (!(0,
|
|
11933
|
+
const domainDir = (0, import_node_path6.join)(memoryDir(), domain);
|
|
11934
|
+
if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
|
|
11579
11935
|
try {
|
|
11580
11936
|
const files = await (0, import_promises7.readdir)(domainDir);
|
|
11581
11937
|
for (const file of files) {
|
|
11582
11938
|
if (!file.endsWith(".md")) continue;
|
|
11583
11939
|
try {
|
|
11584
|
-
const content = await (0, import_promises7.readFile)((0,
|
|
11940
|
+
const content = await (0, import_promises7.readFile)((0, import_node_path6.join)(domainDir, file), "utf-8");
|
|
11585
11941
|
const { fm, body } = parseFrontmatter(content);
|
|
11586
11942
|
if (fm.status && fm.status !== "active") continue;
|
|
11587
11943
|
nodes.push({
|
|
@@ -11640,8 +11996,8 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
|
|
|
11640
11996
|
|
|
11641
11997
|
// src/lib/memory-sync.ts
|
|
11642
11998
|
var import_promises8 = require("node:fs/promises");
|
|
11643
|
-
var
|
|
11644
|
-
var
|
|
11999
|
+
var import_node_fs6 = require("node:fs");
|
|
12000
|
+
var import_node_path7 = require("node:path");
|
|
11645
12001
|
var import_node_crypto = require("node:crypto");
|
|
11646
12002
|
|
|
11647
12003
|
// src/lib/glob-match.ts
|
|
@@ -11712,32 +12068,32 @@ var syncStateFile = () => projectPath(`${VERITY_DIR}/.memory-sync-state.json`);
|
|
|
11712
12068
|
async function ensureMemoryDir() {
|
|
11713
12069
|
await (0, import_promises8.mkdir)(memoryDir2(), { recursive: true });
|
|
11714
12070
|
for (const domain of DOMAINS2) {
|
|
11715
|
-
await (0, import_promises8.mkdir)((0,
|
|
12071
|
+
await (0, import_promises8.mkdir)((0, import_node_path7.join)(memoryDir2(), domain), { recursive: true });
|
|
11716
12072
|
}
|
|
11717
|
-
if (!(0,
|
|
11718
|
-
await (0, import_promises8.writeFile)((0,
|
|
12073
|
+
if (!(0, import_node_fs6.existsSync)((0, import_node_path7.join)(memoryDir2(), "SCHEMA.md"))) {
|
|
12074
|
+
await (0, import_promises8.writeFile)((0, import_node_path7.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
|
|
11719
12075
|
}
|
|
11720
|
-
if (!(0,
|
|
11721
|
-
await (0, import_promises8.writeFile)((0,
|
|
12076
|
+
if (!(0, import_node_fs6.existsSync)((0, import_node_path7.join)(memoryDir2(), "index.md"))) {
|
|
12077
|
+
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
12078
|
}
|
|
11723
|
-
if (!(0,
|
|
11724
|
-
await (0, import_promises8.writeFile)((0,
|
|
12079
|
+
if (!(0, import_node_fs6.existsSync)((0, import_node_path7.join)(memoryDir2(), "log.md"))) {
|
|
12080
|
+
await (0, import_promises8.writeFile)((0, import_node_path7.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
|
|
11725
12081
|
}
|
|
11726
12082
|
}
|
|
11727
12083
|
async function buildManifest() {
|
|
11728
|
-
if (!(0,
|
|
12084
|
+
if (!(0, import_node_fs6.existsSync)(memoryDir2())) {
|
|
11729
12085
|
return { schema_version: 1, nodes: [], index_hash: null, log_length: 0 };
|
|
11730
12086
|
}
|
|
11731
12087
|
const nodes = [];
|
|
11732
12088
|
for (const domain of DOMAINS2) {
|
|
11733
|
-
const domainDir = (0,
|
|
11734
|
-
if (!(0,
|
|
12089
|
+
const domainDir = (0, import_node_path7.join)(memoryDir2(), domain);
|
|
12090
|
+
if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
|
|
11735
12091
|
try {
|
|
11736
12092
|
const files = await (0, import_promises8.readdir)(domainDir);
|
|
11737
12093
|
for (const file of files) {
|
|
11738
12094
|
if (!file.endsWith(".md")) continue;
|
|
11739
12095
|
const filePath = `${domain}/${file}`;
|
|
11740
|
-
const fullPath = (0,
|
|
12096
|
+
const fullPath = (0, import_node_path7.join)(memoryDir2(), filePath);
|
|
11741
12097
|
try {
|
|
11742
12098
|
const content = await (0, import_promises8.readFile)(fullPath, "utf-8");
|
|
11743
12099
|
const hash = (0, import_node_crypto.createHash)("sha256").update(content).digest("hex").slice(0, 16);
|
|
@@ -11750,13 +12106,13 @@ async function buildManifest() {
|
|
|
11750
12106
|
}
|
|
11751
12107
|
let indexHash = null;
|
|
11752
12108
|
try {
|
|
11753
|
-
const indexContent = await (0, import_promises8.readFile)((0,
|
|
12109
|
+
const indexContent = await (0, import_promises8.readFile)((0, import_node_path7.join)(memoryDir2(), "index.md"), "utf-8");
|
|
11754
12110
|
indexHash = `sha256:${(0, import_node_crypto.createHash)("sha256").update(indexContent).digest("hex").slice(0, 16)}`;
|
|
11755
12111
|
} catch {
|
|
11756
12112
|
}
|
|
11757
12113
|
let logLength = 0;
|
|
11758
12114
|
try {
|
|
11759
|
-
const logContent = await (0, import_promises8.readFile)((0,
|
|
12115
|
+
const logContent = await (0, import_promises8.readFile)((0, import_node_path7.join)(memoryDir2(), "log.md"), "utf-8");
|
|
11760
12116
|
logLength = logContent.split("\n").length;
|
|
11761
12117
|
} catch {
|
|
11762
12118
|
}
|
|
@@ -11767,15 +12123,15 @@ function hashContent(content) {
|
|
|
11767
12123
|
}
|
|
11768
12124
|
async function readOnDiskNodes() {
|
|
11769
12125
|
const out = /* @__PURE__ */ new Map();
|
|
11770
|
-
if (!(0,
|
|
12126
|
+
if (!(0, import_node_fs6.existsSync)(memoryDir2())) return out;
|
|
11771
12127
|
for (const domain of DOMAINS2) {
|
|
11772
|
-
const domainDir = (0,
|
|
11773
|
-
if (!(0,
|
|
12128
|
+
const domainDir = (0, import_node_path7.join)(memoryDir2(), domain);
|
|
12129
|
+
if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
|
|
11774
12130
|
try {
|
|
11775
12131
|
for (const file of await (0, import_promises8.readdir)(domainDir)) {
|
|
11776
12132
|
if (!file.endsWith(".md")) continue;
|
|
11777
12133
|
try {
|
|
11778
|
-
out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0,
|
|
12134
|
+
out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0, import_node_path7.join)(domainDir, file), "utf-8")));
|
|
11779
12135
|
} catch {
|
|
11780
12136
|
}
|
|
11781
12137
|
}
|
|
@@ -11821,8 +12177,8 @@ async function computeEditedNodeUploads() {
|
|
|
11821
12177
|
const uploads = [];
|
|
11822
12178
|
for (const [path, prevHash] of prev) {
|
|
11823
12179
|
if (prevHash == null) continue;
|
|
11824
|
-
const full = (0,
|
|
11825
|
-
if (!(0,
|
|
12180
|
+
const full = (0, import_node_path7.join)(memoryDir2(), path);
|
|
12181
|
+
if (!(0, import_node_fs6.existsSync)(full)) continue;
|
|
11826
12182
|
let content;
|
|
11827
12183
|
try {
|
|
11828
12184
|
content = await (0, import_promises8.readFile)(full, "utf-8");
|
|
@@ -11858,15 +12214,15 @@ async function applyMemoryWrites(writes, opts = {}) {
|
|
|
11858
12214
|
const logLines = [`- ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)} \u2014 Applied ${count} write(s) from server`];
|
|
11859
12215
|
for (const n of notes) logLines.push(` - ${n}`);
|
|
11860
12216
|
try {
|
|
11861
|
-
const existing = (0,
|
|
11862
|
-
await (0, import_promises8.writeFile)((0,
|
|
12217
|
+
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";
|
|
12218
|
+
await (0, import_promises8.writeFile)((0, import_node_path7.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
|
|
11863
12219
|
} catch {
|
|
11864
12220
|
}
|
|
11865
12221
|
await recordSyncedNodePaths();
|
|
11866
12222
|
return count;
|
|
11867
12223
|
}
|
|
11868
12224
|
async function applyOneWrite(write, treePaths) {
|
|
11869
|
-
const fullPath = (0,
|
|
12225
|
+
const fullPath = (0, import_node_path7.join)(memoryDir2(), write.path);
|
|
11870
12226
|
const notes = [];
|
|
11871
12227
|
let content = write.content;
|
|
11872
12228
|
if (treePaths && treePaths.length > 0) {
|
|
@@ -11876,7 +12232,7 @@ async function applyOneWrite(write, treePaths) {
|
|
|
11876
12232
|
notes.push(`${write.path}: dropped unmatched file_globs [${grounded.dropped.join(", ")}]`);
|
|
11877
12233
|
}
|
|
11878
12234
|
}
|
|
11879
|
-
if ((0,
|
|
12235
|
+
if ((0, import_node_fs6.existsSync)(fullPath)) {
|
|
11880
12236
|
let existing = "";
|
|
11881
12237
|
try {
|
|
11882
12238
|
existing = await (0, import_promises8.readFile)(fullPath, "utf-8");
|
|
@@ -11888,7 +12244,7 @@ async function applyOneWrite(write, treePaths) {
|
|
|
11888
12244
|
return { written: false, notes };
|
|
11889
12245
|
}
|
|
11890
12246
|
}
|
|
11891
|
-
await (0, import_promises8.mkdir)((0,
|
|
12247
|
+
await (0, import_promises8.mkdir)((0, import_node_path7.dirname)(fullPath), { recursive: true });
|
|
11892
12248
|
await (0, import_promises8.writeFile)(fullPath, content);
|
|
11893
12249
|
return { written: true, notes };
|
|
11894
12250
|
}
|
|
@@ -11929,8 +12285,8 @@ async function regenerateIndex() {
|
|
|
11929
12285
|
];
|
|
11930
12286
|
let totalNodes = 0;
|
|
11931
12287
|
for (const domain of DOMAINS2.filter((d) => d !== "_archive")) {
|
|
11932
|
-
const domainDir = (0,
|
|
11933
|
-
if (!(0,
|
|
12288
|
+
const domainDir = (0, import_node_path7.join)(memoryDir2(), domain);
|
|
12289
|
+
if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
|
|
11934
12290
|
try {
|
|
11935
12291
|
const files = await (0, import_promises8.readdir)(domainDir);
|
|
11936
12292
|
const mdFiles = files.filter((f) => f.endsWith(".md"));
|
|
@@ -11940,7 +12296,7 @@ async function regenerateIndex() {
|
|
|
11940
12296
|
for (const file of mdFiles.sort()) {
|
|
11941
12297
|
const slug = file.replace(/\.md$/, "");
|
|
11942
12298
|
try {
|
|
11943
|
-
const content = await (0, import_promises8.readFile)((0,
|
|
12299
|
+
const content = await (0, import_promises8.readFile)((0, import_node_path7.join)(domainDir, file), "utf-8");
|
|
11944
12300
|
const title = pickFrontmatter(content, "title") ?? slug;
|
|
11945
12301
|
const kind = pickFrontmatter(content, "kind") ?? "-";
|
|
11946
12302
|
const confidence = pickFrontmatter(content, "confidence");
|
|
@@ -11964,7 +12320,7 @@ async function regenerateIndex() {
|
|
|
11964
12320
|
lines.push("No nodes yet. Run an analysis to start building the knowledge graph.");
|
|
11965
12321
|
}
|
|
11966
12322
|
const next = lines.join("\n") + "\n";
|
|
11967
|
-
const indexPath = (0,
|
|
12323
|
+
const indexPath = (0, import_node_path7.join)(memoryDir2(), "index.md");
|
|
11968
12324
|
let existing = null;
|
|
11969
12325
|
try {
|
|
11970
12326
|
existing = await (0, import_promises8.readFile)(indexPath, "utf-8");
|
|
@@ -12046,9 +12402,9 @@ function hasLegacyMemoryBlock(text) {
|
|
|
12046
12402
|
return findMarker(text, LEGACY_MD_START) !== -1;
|
|
12047
12403
|
}
|
|
12048
12404
|
async function ensureClaudeMdPointer(cwd = repoRoot()) {
|
|
12049
|
-
const claudeMdPath = (0,
|
|
12405
|
+
const claudeMdPath = (0, import_node_path7.join)(cwd, "CLAUDE.md");
|
|
12050
12406
|
let existing = "";
|
|
12051
|
-
if ((0,
|
|
12407
|
+
if ((0, import_node_fs6.existsSync)(claudeMdPath)) {
|
|
12052
12408
|
existing = await (0, import_promises8.readFile)(claudeMdPath, "utf-8");
|
|
12053
12409
|
}
|
|
12054
12410
|
let startTag = CLAUDE_MD_START;
|
|
@@ -12178,7 +12534,7 @@ Body content (\u22648KB). Use [[node-id]] wikilinks for cross-references.
|
|
|
12178
12534
|
`;
|
|
12179
12535
|
|
|
12180
12536
|
// src/commands/intent.ts
|
|
12181
|
-
var
|
|
12537
|
+
var import_node_fs7 = require("node:fs");
|
|
12182
12538
|
function registerIntentCommands(program2) {
|
|
12183
12539
|
const intent = program2.command("intent").description("Manage intent capture");
|
|
12184
12540
|
intent.command("capture").description("Capture user intent from stdin (used by UserPromptSubmit hook)").action(async () => {
|
|
@@ -12187,7 +12543,7 @@ function registerIntentCommands(program2) {
|
|
|
12187
12543
|
process.chdir(repoRoot());
|
|
12188
12544
|
} catch {
|
|
12189
12545
|
}
|
|
12190
|
-
if (!(0,
|
|
12546
|
+
if (!(0, import_node_fs7.existsSync)(VERITY_DIR)) {
|
|
12191
12547
|
process.exit(0);
|
|
12192
12548
|
}
|
|
12193
12549
|
const chunks = [];
|
|
@@ -12766,215 +13122,6 @@ async function sendGeneralFeedback(message, opts, globals) {
|
|
|
12766
13122
|
var import_node_fs19 = require("node:fs");
|
|
12767
13123
|
var import_node_path14 = require("node:path");
|
|
12768
13124
|
|
|
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
13125
|
// src/lib/files.ts
|
|
12979
13126
|
var import_node_fs8 = require("node:fs");
|
|
12980
13127
|
var import_node_path8 = require("node:path");
|
|
@@ -13108,10 +13255,15 @@ function collectCodeDelta(files, opts) {
|
|
|
13108
13255
|
// src/lib/debounce.ts
|
|
13109
13256
|
var import_node_fs9 = require("node:fs");
|
|
13110
13257
|
var import_node_crypto3 = require("node:crypto");
|
|
13111
|
-
function
|
|
13112
|
-
if (!
|
|
13258
|
+
function scopedFile(base, sessionId) {
|
|
13259
|
+
if (!sessionId) return base;
|
|
13260
|
+
return `${base}.${(0, import_node_crypto3.createHash)("sha1").update(sessionId).digest("hex").slice(0, 12)}`;
|
|
13261
|
+
}
|
|
13262
|
+
function checkDebounce(debounceSeconds = DEBOUNCE_SECONDS, sessionId) {
|
|
13263
|
+
const file = scopedFile(DEBOUNCE_FILE, sessionId);
|
|
13264
|
+
if (!(0, import_node_fs9.existsSync)(file)) return null;
|
|
13113
13265
|
try {
|
|
13114
|
-
const lastTs = parseInt((0, import_node_fs9.readFileSync)(
|
|
13266
|
+
const lastTs = parseInt((0, import_node_fs9.readFileSync)(file, "utf-8").trim(), 10);
|
|
13115
13267
|
const nowTs = Math.floor(Date.now() / 1e3);
|
|
13116
13268
|
const elapsed = nowTs - lastTs;
|
|
13117
13269
|
if (elapsed < debounceSeconds) {
|
|
@@ -13121,12 +13273,13 @@ function checkDebounce(debounceSeconds = DEBOUNCE_SECONDS) {
|
|
|
13121
13273
|
}
|
|
13122
13274
|
return null;
|
|
13123
13275
|
}
|
|
13124
|
-
function checkMtime(files, bypassForRecentCommits) {
|
|
13276
|
+
function checkMtime(files, bypassForRecentCommits, sessionId) {
|
|
13125
13277
|
if (bypassForRecentCommits) return null;
|
|
13126
|
-
|
|
13278
|
+
const file = scopedFile(DEBOUNCE_FILE, sessionId);
|
|
13279
|
+
if (!(0, import_node_fs9.existsSync)(file)) return null;
|
|
13127
13280
|
let debounceTime;
|
|
13128
13281
|
try {
|
|
13129
|
-
debounceTime = (0, import_node_fs9.statSync)(
|
|
13282
|
+
debounceTime = (0, import_node_fs9.statSync)(file).mtimeMs;
|
|
13130
13283
|
} catch {
|
|
13131
13284
|
return null;
|
|
13132
13285
|
}
|
|
@@ -13158,11 +13311,12 @@ function computeContentHash(files) {
|
|
|
13158
13311
|
}
|
|
13159
13312
|
return hash.digest("hex");
|
|
13160
13313
|
}
|
|
13161
|
-
function checkContentHash(files) {
|
|
13314
|
+
function checkContentHash(files, sessionId) {
|
|
13162
13315
|
const hash = computeContentHash(files);
|
|
13163
|
-
|
|
13316
|
+
const file = scopedFile(HASH_FILE, sessionId);
|
|
13317
|
+
if ((0, import_node_fs9.existsSync)(file)) {
|
|
13164
13318
|
try {
|
|
13165
|
-
const storedHash = (0, import_node_fs9.readFileSync)(
|
|
13319
|
+
const storedHash = (0, import_node_fs9.readFileSync)(file, "utf-8").trim();
|
|
13166
13320
|
if (hash === storedHash) {
|
|
13167
13321
|
return { skip: "No source changes since last analysis", hash };
|
|
13168
13322
|
}
|
|
@@ -13171,18 +13325,19 @@ function checkContentHash(files) {
|
|
|
13171
13325
|
}
|
|
13172
13326
|
return { skip: null, hash };
|
|
13173
13327
|
}
|
|
13174
|
-
function recordAnalysisStart() {
|
|
13328
|
+
function recordAnalysisStart(sessionId) {
|
|
13175
13329
|
(0, import_node_fs9.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
13176
|
-
(0, import_node_fs9.writeFileSync)(DEBOUNCE_FILE, String(Math.floor(Date.now() / 1e3)));
|
|
13330
|
+
(0, import_node_fs9.writeFileSync)(scopedFile(DEBOUNCE_FILE, sessionId), String(Math.floor(Date.now() / 1e3)));
|
|
13177
13331
|
}
|
|
13178
|
-
function recordPassHash(hash) {
|
|
13179
|
-
(0, import_node_fs9.writeFileSync)(HASH_FILE, hash);
|
|
13332
|
+
function recordPassHash(hash, sessionId) {
|
|
13333
|
+
(0, import_node_fs9.writeFileSync)(scopedFile(HASH_FILE, sessionId), hash);
|
|
13180
13334
|
}
|
|
13181
|
-
function narrowToRecent(files) {
|
|
13182
|
-
|
|
13335
|
+
function narrowToRecent(files, sessionId) {
|
|
13336
|
+
const file = scopedFile(DEBOUNCE_FILE, sessionId);
|
|
13337
|
+
if (!(0, import_node_fs9.existsSync)(file)) return files;
|
|
13183
13338
|
let debounceTime;
|
|
13184
13339
|
try {
|
|
13185
|
-
debounceTime = (0, import_node_fs9.statSync)(
|
|
13340
|
+
debounceTime = (0, import_node_fs9.statSync)(file).mtimeMs;
|
|
13186
13341
|
} catch {
|
|
13187
13342
|
return files;
|
|
13188
13343
|
}
|
|
@@ -14688,7 +14843,7 @@ async function runAnalyze(opts, globals) {
|
|
|
14688
14843
|
let currentCommit = "";
|
|
14689
14844
|
if (analysisMode !== "plan") {
|
|
14690
14845
|
const debounceSeconds = parseInt(opts.debounce, 10);
|
|
14691
|
-
const debounceSkip = checkDebounce(debounceSeconds);
|
|
14846
|
+
const debounceSkip = checkDebounce(debounceSeconds, baselineSessionId);
|
|
14692
14847
|
if (debounceSkip) {
|
|
14693
14848
|
if (assistantResponse) {
|
|
14694
14849
|
analysisMode = "plan";
|
|
@@ -14698,7 +14853,7 @@ async function runAnalyze(opts, globals) {
|
|
|
14698
14853
|
}
|
|
14699
14854
|
if (analysisMode !== "plan") {
|
|
14700
14855
|
const allCheckable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable, ...securityFiles]));
|
|
14701
|
-
const mtimeSkip = checkMtime(allCheckable, hasRecentCommitFiles);
|
|
14856
|
+
const mtimeSkip = checkMtime(allCheckable, hasRecentCommitFiles, baselineSessionId);
|
|
14702
14857
|
if (mtimeSkip) {
|
|
14703
14858
|
if (assistantResponse) {
|
|
14704
14859
|
analysisMode = "plan";
|
|
@@ -14708,9 +14863,9 @@ async function runAnalyze(opts, globals) {
|
|
|
14708
14863
|
}
|
|
14709
14864
|
}
|
|
14710
14865
|
if (analysisMode !== "plan") {
|
|
14711
|
-
recordAnalysisStart();
|
|
14866
|
+
recordAnalysisStart(baselineSessionId);
|
|
14712
14867
|
const allCheckable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable, ...securityFiles]));
|
|
14713
|
-
const hashResult = checkContentHash(allCheckable);
|
|
14868
|
+
const hashResult = checkContentHash(allCheckable, baselineSessionId);
|
|
14714
14869
|
if (hashResult.skip) {
|
|
14715
14870
|
if (assistantResponse) {
|
|
14716
14871
|
analysisMode = "plan";
|
|
@@ -14722,7 +14877,7 @@ async function runAnalyze(opts, globals) {
|
|
|
14722
14877
|
if (analysisMode !== "plan") {
|
|
14723
14878
|
const agentNarrowed = narrowToAgentAuthored(allForReview, actionSummary);
|
|
14724
14879
|
const baseForReview = agentNarrowed.length > 0 ? agentNarrowed : allForReview;
|
|
14725
|
-
const recentForReview = narrowToRecent(baseForReview);
|
|
14880
|
+
const recentForReview = narrowToRecent(baseForReview, baselineSessionId);
|
|
14726
14881
|
if (!opts.skipStatic && isCodacyAvailable()) {
|
|
14727
14882
|
let allScannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles]));
|
|
14728
14883
|
if (baseline) {
|
|
@@ -15097,7 +15252,7 @@ async function runAnalyze(opts, globals) {
|
|
|
15097
15252
|
}
|
|
15098
15253
|
case "PASS": {
|
|
15099
15254
|
writeIteration(1, currentCommit, contentHash ?? void 0);
|
|
15100
|
-
if (contentHash) recordPassHash(contentHash);
|
|
15255
|
+
if (contentHash) recordPassHash(contentHash, baselineSessionId);
|
|
15101
15256
|
if (currentCommit && currentCommit !== "no-git") writeBaselineSha(currentCommit);
|
|
15102
15257
|
let userSummary = response.user_summary ?? "Verity: PASS";
|
|
15103
15258
|
const viewUrl = response.view_url ?? "";
|
|
@@ -15108,7 +15263,7 @@ async function runAnalyze(opts, globals) {
|
|
|
15108
15263
|
break;
|
|
15109
15264
|
}
|
|
15110
15265
|
case "WARN": {
|
|
15111
|
-
if (contentHash) recordPassHash(contentHash);
|
|
15266
|
+
if (contentHash) recordPassHash(contentHash, baselineSessionId);
|
|
15112
15267
|
if (currentCommit && currentCommit !== "no-git") writeBaselineSha(currentCommit);
|
|
15113
15268
|
let userSummary = response.user_summary ?? "Verity: WARN";
|
|
15114
15269
|
const viewUrl = response.view_url ?? "";
|
|
@@ -16766,7 +16921,7 @@ function registerTelemetryCommands(program2) {
|
|
|
16766
16921
|
}
|
|
16767
16922
|
|
|
16768
16923
|
// src/cli.ts
|
|
16769
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.24.0-experimental.
|
|
16924
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.24.0-experimental.b35bbc7").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
|
|
16770
16925
|
registerAuthCommands(program);
|
|
16771
16926
|
registerHooksCommands(program);
|
|
16772
16927
|
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
|
|