@codacy/verity-cli 0.24.0-experimental.bdf0db7 → 0.24.0-experimental.fe39839
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 +279 -426
- package/data/skills/verity-setup/SKILL.md +4 -11
- 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_process3 = require("node:child_process");
|
|
10330
|
+
var import_node_path2 = require("node:path");
|
|
10331
10331
|
|
|
10332
10332
|
// src/lib/auth.ts
|
|
10333
10333
|
var import_promises = require("node:fs/promises");
|
|
@@ -10476,10 +10476,6 @@ 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";
|
|
10483
10479
|
|
|
10484
10480
|
// src/lib/auth.ts
|
|
10485
10481
|
async function resolveToken(flagToken) {
|
|
@@ -10645,8 +10641,7 @@ async function apiRequest(options) {
|
|
|
10645
10641
|
timeout = 9e4,
|
|
10646
10642
|
cmd = "unknown",
|
|
10647
10643
|
retry = false,
|
|
10648
|
-
encodeBody = false
|
|
10649
|
-
extraHeaders
|
|
10644
|
+
encodeBody = false
|
|
10650
10645
|
} = options;
|
|
10651
10646
|
const url = `${serviceUrl}${path}`;
|
|
10652
10647
|
const headers = {
|
|
@@ -10655,9 +10650,6 @@ async function apiRequest(options) {
|
|
|
10655
10650
|
if (token) {
|
|
10656
10651
|
headers["Authorization"] = `Bearer ${token}`;
|
|
10657
10652
|
}
|
|
10658
|
-
if (extraHeaders) {
|
|
10659
|
-
Object.assign(headers, extraHeaders);
|
|
10660
|
-
}
|
|
10661
10653
|
const testMockScenario = process.env.VERITY_TEST_MOCK_SCENARIO;
|
|
10662
10654
|
if (testMockScenario) {
|
|
10663
10655
|
headers["X-Verity-Mock-Scenario"] = testMockScenario;
|
|
@@ -10748,324 +10740,6 @@ function analyzeRequest(options) {
|
|
|
10748
10740
|
});
|
|
10749
10741
|
}
|
|
10750
10742
|
|
|
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
|
-
|
|
11069
10743
|
// src/commands/auth.ts
|
|
11070
10744
|
function registerAuthCommands(program2) {
|
|
11071
10745
|
const auth = program2.command("auth").description("Manage project authentication");
|
|
@@ -11075,65 +10749,35 @@ function registerAuthCommands(program2) {
|
|
|
11075
10749
|
let remote = opts.remote;
|
|
11076
10750
|
if (!remote) {
|
|
11077
10751
|
try {
|
|
11078
|
-
remote = (0,
|
|
10752
|
+
remote = (0, import_node_child_process3.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
|
|
11079
10753
|
} catch {
|
|
11080
10754
|
printError("No git remote found. Use --remote to specify one.");
|
|
11081
10755
|
process.exit(1);
|
|
11082
10756
|
}
|
|
11083
10757
|
}
|
|
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;
|
|
11099
10758
|
const result = await apiRequest({
|
|
11100
10759
|
method: "POST",
|
|
11101
10760
|
path: "/auth/register",
|
|
11102
10761
|
serviceUrl,
|
|
11103
10762
|
body: { project_name: opts.project, git_remote_url: remote },
|
|
11104
|
-
extraHeaders: { "X-Provider-Token": providerToken },
|
|
11105
10763
|
verbose: globals.verbose
|
|
11106
10764
|
});
|
|
11107
10765
|
if (!result.ok) {
|
|
11108
10766
|
printError(result.error);
|
|
11109
10767
|
process.exit(1);
|
|
11110
10768
|
}
|
|
11111
|
-
const { project_id, token, service_url
|
|
10769
|
+
const { project_id, token, service_url } = result.data;
|
|
11112
10770
|
await (0, import_promises3.mkdir)(VERITY_DIR, { recursive: true });
|
|
11113
|
-
await (0, import_promises3.writeFile)(
|
|
11114
|
-
CREDENTIALS_FILE,
|
|
11115
|
-
`token: ${token}
|
|
10771
|
+
await (0, import_promises3.writeFile)(CREDENTIALS_FILE, `token: ${token}
|
|
11116
10772
|
service_url: ${service_url}
|
|
11117
|
-
|
|
11118
|
-
`,
|
|
11119
|
-
{ mode: 384 }
|
|
11120
|
-
);
|
|
11121
|
-
await (0, import_promises3.chmod)(CREDENTIALS_FILE, 384).catch(() => {
|
|
11122
|
-
});
|
|
10773
|
+
`);
|
|
11123
10774
|
try {
|
|
11124
|
-
await (0, import_promises3.mkdir)((0,
|
|
11125
|
-
await (0, import_promises3.appendFile)(
|
|
11126
|
-
|
|
11127
|
-
`${remote} token: ${token}
|
|
11128
|
-
${remote} provider_token: ${providerToken}
|
|
11129
|
-
`
|
|
11130
|
-
);
|
|
11131
|
-
await (0, import_promises3.chmod)(GLOBAL_CREDENTIALS_FILE, 384).catch(() => {
|
|
11132
|
-
});
|
|
10775
|
+
await (0, import_promises3.mkdir)((0, import_node_path2.dirname)(GLOBAL_CREDENTIALS_FILE), { recursive: true });
|
|
10776
|
+
await (0, import_promises3.appendFile)(GLOBAL_CREDENTIALS_FILE, `${remote} token: ${token}
|
|
10777
|
+
`);
|
|
11133
10778
|
} catch {
|
|
11134
10779
|
}
|
|
11135
10780
|
printInfo(`Project registered: ${project_id}`);
|
|
11136
|
-
if (user?.email) printInfo(`Authenticated as: ${user.email}`);
|
|
11137
10781
|
printJson({ project_id, service_url });
|
|
11138
10782
|
});
|
|
11139
10783
|
auth.command("verify").description("Verify the current token is valid").action(async () => {
|
|
@@ -11167,7 +10811,7 @@ ${remote} provider_token: ${providerToken}
|
|
|
11167
10811
|
let remote = opts.remote;
|
|
11168
10812
|
if (!remote) {
|
|
11169
10813
|
try {
|
|
11170
|
-
remote = (0,
|
|
10814
|
+
remote = (0, import_node_child_process3.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
|
|
11171
10815
|
} catch {
|
|
11172
10816
|
printError("No git remote found. Use --remote to specify one.");
|
|
11173
10817
|
process.exit(1);
|
|
@@ -11195,7 +10839,7 @@ ${remote} provider_token: ${providerToken}
|
|
|
11195
10839
|
|
|
11196
10840
|
// src/lib/hooks.ts
|
|
11197
10841
|
var import_promises4 = require("node:fs/promises");
|
|
11198
|
-
var
|
|
10842
|
+
var import_node_path3 = require("node:path");
|
|
11199
10843
|
var VERITY_STOP_HOOK = {
|
|
11200
10844
|
type: "command",
|
|
11201
10845
|
command: "verity analyze",
|
|
@@ -11347,19 +10991,19 @@ async function checkAllVerityHooksDetailed() {
|
|
|
11347
10991
|
return { stop, intent, baseline, current: hasCurrent, legacyOnly: hasLegacy && !hasCurrent };
|
|
11348
10992
|
}
|
|
11349
10993
|
async function writeSettings(settings) {
|
|
11350
|
-
await (0, import_promises4.mkdir)((0,
|
|
10994
|
+
await (0, import_promises4.mkdir)((0, import_node_path3.dirname)(CLAUDE_SETTINGS_FILE), { recursive: true });
|
|
11351
10995
|
await (0, import_promises4.writeFile)(CLAUDE_SETTINGS_FILE, JSON.stringify(settings, null, 2) + "\n");
|
|
11352
10996
|
}
|
|
11353
10997
|
async function readSettingsAt(root) {
|
|
11354
10998
|
try {
|
|
11355
|
-
return JSON.parse(await (0, import_promises4.readFile)((0,
|
|
10999
|
+
return JSON.parse(await (0, import_promises4.readFile)((0, import_node_path3.join)(root, CLAUDE_SETTINGS_FILE), "utf-8"));
|
|
11356
11000
|
} catch {
|
|
11357
11001
|
return {};
|
|
11358
11002
|
}
|
|
11359
11003
|
}
|
|
11360
11004
|
async function writeSettingsAt(root, settings) {
|
|
11361
|
-
const file = (0,
|
|
11362
|
-
await (0, import_promises4.mkdir)((0,
|
|
11005
|
+
const file = (0, import_node_path3.join)(root, CLAUDE_SETTINGS_FILE);
|
|
11006
|
+
await (0, import_promises4.mkdir)((0, import_node_path3.dirname)(file), { recursive: true });
|
|
11363
11007
|
await (0, import_promises4.writeFile)(file, JSON.stringify(settings, null, 2) + "\n");
|
|
11364
11008
|
}
|
|
11365
11009
|
async function hasLegacyHooksAt(root) {
|
|
@@ -11600,8 +11244,8 @@ var import_node_crypto2 = require("node:crypto");
|
|
|
11600
11244
|
|
|
11601
11245
|
// src/lib/conversation-buffer.ts
|
|
11602
11246
|
var import_promises5 = require("node:fs/promises");
|
|
11603
|
-
var
|
|
11604
|
-
var
|
|
11247
|
+
var import_node_fs2 = require("node:fs");
|
|
11248
|
+
var import_node_child_process4 = require("node:child_process");
|
|
11605
11249
|
function stripImageReferences(text) {
|
|
11606
11250
|
return text.replace(/\[Image #\d+\]/g, "[screenshot \u2014 not available for review]");
|
|
11607
11251
|
}
|
|
@@ -11632,7 +11276,7 @@ async function appendToConversationBuffer(prompt, sessionId) {
|
|
|
11632
11276
|
}
|
|
11633
11277
|
async function readAndClearConversationBuffer(currentSessionId) {
|
|
11634
11278
|
try {
|
|
11635
|
-
if ((0,
|
|
11279
|
+
if ((0, import_node_fs2.existsSync)(CONVERSATION_BUFFER_FILE)) {
|
|
11636
11280
|
const entries = await readBufferEntries();
|
|
11637
11281
|
let mine = entries;
|
|
11638
11282
|
let others = [];
|
|
@@ -11656,7 +11300,7 @@ async function readAndClearConversationBuffer(currentSessionId) {
|
|
|
11656
11300
|
};
|
|
11657
11301
|
}
|
|
11658
11302
|
}
|
|
11659
|
-
if ((0,
|
|
11303
|
+
if ((0, import_node_fs2.existsSync)(INTENT_FILE)) {
|
|
11660
11304
|
try {
|
|
11661
11305
|
const content = await (0, import_promises5.readFile)(INTENT_FILE, "utf-8");
|
|
11662
11306
|
await (0, import_promises5.unlink)(INTENT_FILE).catch(() => {
|
|
@@ -11700,7 +11344,7 @@ async function readBufferEntries() {
|
|
|
11700
11344
|
}
|
|
11701
11345
|
function getRecentCommitMessages() {
|
|
11702
11346
|
try {
|
|
11703
|
-
const output = (0,
|
|
11347
|
+
const output = (0, import_node_child_process4.execSync)(
|
|
11704
11348
|
'git log --since="30 minutes ago" --format="%s" -5',
|
|
11705
11349
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
11706
11350
|
).trim();
|
|
@@ -11713,8 +11357,8 @@ function getRecentCommitMessages() {
|
|
|
11713
11357
|
|
|
11714
11358
|
// src/lib/task-context-buffer.ts
|
|
11715
11359
|
var import_promises6 = require("node:fs/promises");
|
|
11716
|
-
var
|
|
11717
|
-
var
|
|
11360
|
+
var import_node_fs3 = require("node:fs");
|
|
11361
|
+
var import_node_path4 = require("node:path");
|
|
11718
11362
|
var TASK_CONTEXT_DIR = `${VERITY_DIR}/.task-context`;
|
|
11719
11363
|
var MAX_BUFFER_BYTES = 500 * 1024;
|
|
11720
11364
|
var MAX_PROMPT_CHARS = 2e3;
|
|
@@ -11753,7 +11397,7 @@ async function appendResponseToTaskBuffer(taskId, assistantResponse, actionSumma
|
|
|
11753
11397
|
}
|
|
11754
11398
|
async function readTaskContextBuffer(taskId) {
|
|
11755
11399
|
const filePath = bufferPath(taskId);
|
|
11756
|
-
if (!(0,
|
|
11400
|
+
if (!(0, import_node_fs3.existsSync)(filePath)) return null;
|
|
11757
11401
|
try {
|
|
11758
11402
|
const content = await (0, import_promises6.readFile)(filePath, "utf-8");
|
|
11759
11403
|
if (!content.trim()) return null;
|
|
@@ -11787,12 +11431,12 @@ async function readTaskContextBuffer(taskId) {
|
|
|
11787
11431
|
}
|
|
11788
11432
|
async function cleanupTaskContextBuffers() {
|
|
11789
11433
|
try {
|
|
11790
|
-
if (!(0,
|
|
11434
|
+
if (!(0, import_node_fs3.existsSync)(TASK_CONTEXT_DIR)) return;
|
|
11791
11435
|
const files = await (0, import_promises6.readdir)(TASK_CONTEXT_DIR);
|
|
11792
11436
|
const cutoffMs = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
11793
11437
|
for (const file of files) {
|
|
11794
11438
|
if (!file.endsWith(".jsonl")) continue;
|
|
11795
|
-
const filePath = (0,
|
|
11439
|
+
const filePath = (0, import_node_path4.join)(TASK_CONTEXT_DIR, file);
|
|
11796
11440
|
try {
|
|
11797
11441
|
const stats = await (0, import_promises6.stat)(filePath);
|
|
11798
11442
|
if (stats.mtimeMs < cutoffMs) {
|
|
@@ -11806,13 +11450,13 @@ async function cleanupTaskContextBuffers() {
|
|
|
11806
11450
|
}
|
|
11807
11451
|
function bufferPath(taskId) {
|
|
11808
11452
|
const safe = taskId.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
11809
|
-
return (0,
|
|
11453
|
+
return (0, import_node_path4.join)(TASK_CONTEXT_DIR, `${safe}.jsonl`);
|
|
11810
11454
|
}
|
|
11811
11455
|
async function appendEntry(taskId, entry) {
|
|
11812
11456
|
try {
|
|
11813
11457
|
await (0, import_promises6.mkdir)(TASK_CONTEXT_DIR, { recursive: true });
|
|
11814
11458
|
const filePath = bufferPath(taskId);
|
|
11815
|
-
if ((0,
|
|
11459
|
+
if ((0, import_node_fs3.existsSync)(filePath)) {
|
|
11816
11460
|
const stats = await (0, import_promises6.stat)(filePath);
|
|
11817
11461
|
if (stats.size >= MAX_BUFFER_BYTES) {
|
|
11818
11462
|
const content = await (0, import_promises6.readFile)(filePath, "utf-8");
|
|
@@ -11823,7 +11467,7 @@ async function appendEntry(taskId, entry) {
|
|
|
11823
11467
|
}
|
|
11824
11468
|
}
|
|
11825
11469
|
const line = JSON.stringify(entry) + "\n";
|
|
11826
|
-
const existing = (0,
|
|
11470
|
+
const existing = (0, import_node_fs3.existsSync)(filePath) ? await (0, import_promises6.readFile)(filePath, "utf-8") : "";
|
|
11827
11471
|
await (0, import_promises6.writeFile)(filePath, existing + line);
|
|
11828
11472
|
} catch {
|
|
11829
11473
|
}
|
|
@@ -11831,8 +11475,8 @@ async function appendEntry(taskId, entry) {
|
|
|
11831
11475
|
|
|
11832
11476
|
// src/lib/memory-retrieval.ts
|
|
11833
11477
|
var import_promises7 = require("node:fs/promises");
|
|
11834
|
-
var
|
|
11835
|
-
var
|
|
11478
|
+
var import_node_fs4 = require("node:fs");
|
|
11479
|
+
var import_node_path5 = require("node:path");
|
|
11836
11480
|
var memoryDir = () => projectPath(`${VERITY_DIR}/memory`);
|
|
11837
11481
|
var DOMAINS = ["decisions", "quality", "security", "intent", "gotchas", "patterns", "domain", "integrations"];
|
|
11838
11482
|
var DEFAULT_BUDGET_TOKENS = 2e3;
|
|
@@ -11925,19 +11569,19 @@ function parseFrontmatter(content) {
|
|
|
11925
11569
|
return { fm, body: match[2].trim() };
|
|
11926
11570
|
}
|
|
11927
11571
|
async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = DEFAULT_BUDGET_TOKENS) {
|
|
11928
|
-
if (!(0,
|
|
11572
|
+
if (!(0, import_node_fs4.existsSync)(memoryDir())) return null;
|
|
11929
11573
|
const budget = Math.min(budgetTokens, MAX_BUDGET_TOKENS);
|
|
11930
11574
|
const promptTokens = tokenize(promptText);
|
|
11931
11575
|
const nodes = [];
|
|
11932
11576
|
for (const domain of DOMAINS) {
|
|
11933
|
-
const domainDir = (0,
|
|
11934
|
-
if (!(0,
|
|
11577
|
+
const domainDir = (0, import_node_path5.join)(memoryDir(), domain);
|
|
11578
|
+
if (!(0, import_node_fs4.existsSync)(domainDir)) continue;
|
|
11935
11579
|
try {
|
|
11936
11580
|
const files = await (0, import_promises7.readdir)(domainDir);
|
|
11937
11581
|
for (const file of files) {
|
|
11938
11582
|
if (!file.endsWith(".md")) continue;
|
|
11939
11583
|
try {
|
|
11940
|
-
const content = await (0, import_promises7.readFile)((0,
|
|
11584
|
+
const content = await (0, import_promises7.readFile)((0, import_node_path5.join)(domainDir, file), "utf-8");
|
|
11941
11585
|
const { fm, body } = parseFrontmatter(content);
|
|
11942
11586
|
if (fm.status && fm.status !== "active") continue;
|
|
11943
11587
|
nodes.push({
|
|
@@ -11996,8 +11640,8 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
|
|
|
11996
11640
|
|
|
11997
11641
|
// src/lib/memory-sync.ts
|
|
11998
11642
|
var import_promises8 = require("node:fs/promises");
|
|
11999
|
-
var
|
|
12000
|
-
var
|
|
11643
|
+
var import_node_fs5 = require("node:fs");
|
|
11644
|
+
var import_node_path6 = require("node:path");
|
|
12001
11645
|
var import_node_crypto = require("node:crypto");
|
|
12002
11646
|
|
|
12003
11647
|
// src/lib/glob-match.ts
|
|
@@ -12068,32 +11712,32 @@ var syncStateFile = () => projectPath(`${VERITY_DIR}/.memory-sync-state.json`);
|
|
|
12068
11712
|
async function ensureMemoryDir() {
|
|
12069
11713
|
await (0, import_promises8.mkdir)(memoryDir2(), { recursive: true });
|
|
12070
11714
|
for (const domain of DOMAINS2) {
|
|
12071
|
-
await (0, import_promises8.mkdir)((0,
|
|
11715
|
+
await (0, import_promises8.mkdir)((0, import_node_path6.join)(memoryDir2(), domain), { recursive: true });
|
|
12072
11716
|
}
|
|
12073
|
-
if (!(0,
|
|
12074
|
-
await (0, import_promises8.writeFile)((0,
|
|
11717
|
+
if (!(0, import_node_fs5.existsSync)((0, import_node_path6.join)(memoryDir2(), "SCHEMA.md"))) {
|
|
11718
|
+
await (0, import_promises8.writeFile)((0, import_node_path6.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
|
|
12075
11719
|
}
|
|
12076
|
-
if (!(0,
|
|
12077
|
-
await (0, import_promises8.writeFile)((0,
|
|
11720
|
+
if (!(0, import_node_fs5.existsSync)((0, import_node_path6.join)(memoryDir2(), "index.md"))) {
|
|
11721
|
+
await (0, import_promises8.writeFile)((0, import_node_path6.join)(memoryDir2(), "index.md"), "# Project Memory Index\n\nNo nodes yet. Run an analysis to start building the knowledge graph.\n");
|
|
12078
11722
|
}
|
|
12079
|
-
if (!(0,
|
|
12080
|
-
await (0, import_promises8.writeFile)((0,
|
|
11723
|
+
if (!(0, import_node_fs5.existsSync)((0, import_node_path6.join)(memoryDir2(), "log.md"))) {
|
|
11724
|
+
await (0, import_promises8.writeFile)((0, import_node_path6.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
|
|
12081
11725
|
}
|
|
12082
11726
|
}
|
|
12083
11727
|
async function buildManifest() {
|
|
12084
|
-
if (!(0,
|
|
11728
|
+
if (!(0, import_node_fs5.existsSync)(memoryDir2())) {
|
|
12085
11729
|
return { schema_version: 1, nodes: [], index_hash: null, log_length: 0 };
|
|
12086
11730
|
}
|
|
12087
11731
|
const nodes = [];
|
|
12088
11732
|
for (const domain of DOMAINS2) {
|
|
12089
|
-
const domainDir = (0,
|
|
12090
|
-
if (!(0,
|
|
11733
|
+
const domainDir = (0, import_node_path6.join)(memoryDir2(), domain);
|
|
11734
|
+
if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
|
|
12091
11735
|
try {
|
|
12092
11736
|
const files = await (0, import_promises8.readdir)(domainDir);
|
|
12093
11737
|
for (const file of files) {
|
|
12094
11738
|
if (!file.endsWith(".md")) continue;
|
|
12095
11739
|
const filePath = `${domain}/${file}`;
|
|
12096
|
-
const fullPath = (0,
|
|
11740
|
+
const fullPath = (0, import_node_path6.join)(memoryDir2(), filePath);
|
|
12097
11741
|
try {
|
|
12098
11742
|
const content = await (0, import_promises8.readFile)(fullPath, "utf-8");
|
|
12099
11743
|
const hash = (0, import_node_crypto.createHash)("sha256").update(content).digest("hex").slice(0, 16);
|
|
@@ -12106,13 +11750,13 @@ async function buildManifest() {
|
|
|
12106
11750
|
}
|
|
12107
11751
|
let indexHash = null;
|
|
12108
11752
|
try {
|
|
12109
|
-
const indexContent = await (0, import_promises8.readFile)((0,
|
|
11753
|
+
const indexContent = await (0, import_promises8.readFile)((0, import_node_path6.join)(memoryDir2(), "index.md"), "utf-8");
|
|
12110
11754
|
indexHash = `sha256:${(0, import_node_crypto.createHash)("sha256").update(indexContent).digest("hex").slice(0, 16)}`;
|
|
12111
11755
|
} catch {
|
|
12112
11756
|
}
|
|
12113
11757
|
let logLength = 0;
|
|
12114
11758
|
try {
|
|
12115
|
-
const logContent = await (0, import_promises8.readFile)((0,
|
|
11759
|
+
const logContent = await (0, import_promises8.readFile)((0, import_node_path6.join)(memoryDir2(), "log.md"), "utf-8");
|
|
12116
11760
|
logLength = logContent.split("\n").length;
|
|
12117
11761
|
} catch {
|
|
12118
11762
|
}
|
|
@@ -12123,15 +11767,15 @@ function hashContent(content) {
|
|
|
12123
11767
|
}
|
|
12124
11768
|
async function readOnDiskNodes() {
|
|
12125
11769
|
const out = /* @__PURE__ */ new Map();
|
|
12126
|
-
if (!(0,
|
|
11770
|
+
if (!(0, import_node_fs5.existsSync)(memoryDir2())) return out;
|
|
12127
11771
|
for (const domain of DOMAINS2) {
|
|
12128
|
-
const domainDir = (0,
|
|
12129
|
-
if (!(0,
|
|
11772
|
+
const domainDir = (0, import_node_path6.join)(memoryDir2(), domain);
|
|
11773
|
+
if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
|
|
12130
11774
|
try {
|
|
12131
11775
|
for (const file of await (0, import_promises8.readdir)(domainDir)) {
|
|
12132
11776
|
if (!file.endsWith(".md")) continue;
|
|
12133
11777
|
try {
|
|
12134
|
-
out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0,
|
|
11778
|
+
out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0, import_node_path6.join)(domainDir, file), "utf-8")));
|
|
12135
11779
|
} catch {
|
|
12136
11780
|
}
|
|
12137
11781
|
}
|
|
@@ -12177,8 +11821,8 @@ async function computeEditedNodeUploads() {
|
|
|
12177
11821
|
const uploads = [];
|
|
12178
11822
|
for (const [path, prevHash] of prev) {
|
|
12179
11823
|
if (prevHash == null) continue;
|
|
12180
|
-
const full = (0,
|
|
12181
|
-
if (!(0,
|
|
11824
|
+
const full = (0, import_node_path6.join)(memoryDir2(), path);
|
|
11825
|
+
if (!(0, import_node_fs5.existsSync)(full)) continue;
|
|
12182
11826
|
let content;
|
|
12183
11827
|
try {
|
|
12184
11828
|
content = await (0, import_promises8.readFile)(full, "utf-8");
|
|
@@ -12214,15 +11858,15 @@ async function applyMemoryWrites(writes, opts = {}) {
|
|
|
12214
11858
|
const logLines = [`- ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)} \u2014 Applied ${count} write(s) from server`];
|
|
12215
11859
|
for (const n of notes) logLines.push(` - ${n}`);
|
|
12216
11860
|
try {
|
|
12217
|
-
const existing = (0,
|
|
12218
|
-
await (0, import_promises8.writeFile)((0,
|
|
11861
|
+
const existing = (0, import_node_fs5.existsSync)((0, import_node_path6.join)(memoryDir2(), "log.md")) ? await (0, import_promises8.readFile)((0, import_node_path6.join)(memoryDir2(), "log.md"), "utf-8") : "# Memory Log\n\n";
|
|
11862
|
+
await (0, import_promises8.writeFile)((0, import_node_path6.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
|
|
12219
11863
|
} catch {
|
|
12220
11864
|
}
|
|
12221
11865
|
await recordSyncedNodePaths();
|
|
12222
11866
|
return count;
|
|
12223
11867
|
}
|
|
12224
11868
|
async function applyOneWrite(write, treePaths) {
|
|
12225
|
-
const fullPath = (0,
|
|
11869
|
+
const fullPath = (0, import_node_path6.join)(memoryDir2(), write.path);
|
|
12226
11870
|
const notes = [];
|
|
12227
11871
|
let content = write.content;
|
|
12228
11872
|
if (treePaths && treePaths.length > 0) {
|
|
@@ -12232,7 +11876,7 @@ async function applyOneWrite(write, treePaths) {
|
|
|
12232
11876
|
notes.push(`${write.path}: dropped unmatched file_globs [${grounded.dropped.join(", ")}]`);
|
|
12233
11877
|
}
|
|
12234
11878
|
}
|
|
12235
|
-
if ((0,
|
|
11879
|
+
if ((0, import_node_fs5.existsSync)(fullPath)) {
|
|
12236
11880
|
let existing = "";
|
|
12237
11881
|
try {
|
|
12238
11882
|
existing = await (0, import_promises8.readFile)(fullPath, "utf-8");
|
|
@@ -12244,7 +11888,7 @@ async function applyOneWrite(write, treePaths) {
|
|
|
12244
11888
|
return { written: false, notes };
|
|
12245
11889
|
}
|
|
12246
11890
|
}
|
|
12247
|
-
await (0, import_promises8.mkdir)((0,
|
|
11891
|
+
await (0, import_promises8.mkdir)((0, import_node_path6.dirname)(fullPath), { recursive: true });
|
|
12248
11892
|
await (0, import_promises8.writeFile)(fullPath, content);
|
|
12249
11893
|
return { written: true, notes };
|
|
12250
11894
|
}
|
|
@@ -12285,8 +11929,8 @@ async function regenerateIndex() {
|
|
|
12285
11929
|
];
|
|
12286
11930
|
let totalNodes = 0;
|
|
12287
11931
|
for (const domain of DOMAINS2.filter((d) => d !== "_archive")) {
|
|
12288
|
-
const domainDir = (0,
|
|
12289
|
-
if (!(0,
|
|
11932
|
+
const domainDir = (0, import_node_path6.join)(memoryDir2(), domain);
|
|
11933
|
+
if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
|
|
12290
11934
|
try {
|
|
12291
11935
|
const files = await (0, import_promises8.readdir)(domainDir);
|
|
12292
11936
|
const mdFiles = files.filter((f) => f.endsWith(".md"));
|
|
@@ -12296,7 +11940,7 @@ async function regenerateIndex() {
|
|
|
12296
11940
|
for (const file of mdFiles.sort()) {
|
|
12297
11941
|
const slug = file.replace(/\.md$/, "");
|
|
12298
11942
|
try {
|
|
12299
|
-
const content = await (0, import_promises8.readFile)((0,
|
|
11943
|
+
const content = await (0, import_promises8.readFile)((0, import_node_path6.join)(domainDir, file), "utf-8");
|
|
12300
11944
|
const title = pickFrontmatter(content, "title") ?? slug;
|
|
12301
11945
|
const kind = pickFrontmatter(content, "kind") ?? "-";
|
|
12302
11946
|
const confidence = pickFrontmatter(content, "confidence");
|
|
@@ -12320,7 +11964,7 @@ async function regenerateIndex() {
|
|
|
12320
11964
|
lines.push("No nodes yet. Run an analysis to start building the knowledge graph.");
|
|
12321
11965
|
}
|
|
12322
11966
|
const next = lines.join("\n") + "\n";
|
|
12323
|
-
const indexPath = (0,
|
|
11967
|
+
const indexPath = (0, import_node_path6.join)(memoryDir2(), "index.md");
|
|
12324
11968
|
let existing = null;
|
|
12325
11969
|
try {
|
|
12326
11970
|
existing = await (0, import_promises8.readFile)(indexPath, "utf-8");
|
|
@@ -12402,9 +12046,9 @@ function hasLegacyMemoryBlock(text) {
|
|
|
12402
12046
|
return findMarker(text, LEGACY_MD_START) !== -1;
|
|
12403
12047
|
}
|
|
12404
12048
|
async function ensureClaudeMdPointer(cwd = repoRoot()) {
|
|
12405
|
-
const claudeMdPath = (0,
|
|
12049
|
+
const claudeMdPath = (0, import_node_path6.join)(cwd, "CLAUDE.md");
|
|
12406
12050
|
let existing = "";
|
|
12407
|
-
if ((0,
|
|
12051
|
+
if ((0, import_node_fs5.existsSync)(claudeMdPath)) {
|
|
12408
12052
|
existing = await (0, import_promises8.readFile)(claudeMdPath, "utf-8");
|
|
12409
12053
|
}
|
|
12410
12054
|
let startTag = CLAUDE_MD_START;
|
|
@@ -12534,7 +12178,7 @@ Body content (\u22648KB). Use [[node-id]] wikilinks for cross-references.
|
|
|
12534
12178
|
`;
|
|
12535
12179
|
|
|
12536
12180
|
// src/commands/intent.ts
|
|
12537
|
-
var
|
|
12181
|
+
var import_node_fs6 = require("node:fs");
|
|
12538
12182
|
function registerIntentCommands(program2) {
|
|
12539
12183
|
const intent = program2.command("intent").description("Manage intent capture");
|
|
12540
12184
|
intent.command("capture").description("Capture user intent from stdin (used by UserPromptSubmit hook)").action(async () => {
|
|
@@ -12543,7 +12187,7 @@ function registerIntentCommands(program2) {
|
|
|
12543
12187
|
process.chdir(repoRoot());
|
|
12544
12188
|
} catch {
|
|
12545
12189
|
}
|
|
12546
|
-
if (!(0,
|
|
12190
|
+
if (!(0, import_node_fs6.existsSync)(VERITY_DIR)) {
|
|
12547
12191
|
process.exit(0);
|
|
12548
12192
|
}
|
|
12549
12193
|
const chunks = [];
|
|
@@ -13122,6 +12766,215 @@ async function sendGeneralFeedback(message, opts, globals) {
|
|
|
13122
12766
|
var import_node_fs19 = require("node:fs");
|
|
13123
12767
|
var import_node_path14 = require("node:path");
|
|
13124
12768
|
|
|
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
|
+
|
|
13125
12978
|
// src/lib/files.ts
|
|
13126
12979
|
var import_node_fs8 = require("node:fs");
|
|
13127
12980
|
var import_node_path8 = require("node:path");
|
|
@@ -16913,7 +16766,7 @@ function registerTelemetryCommands(program2) {
|
|
|
16913
16766
|
}
|
|
16914
16767
|
|
|
16915
16768
|
// src/cli.ts
|
|
16916
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.24.0-experimental.
|
|
16769
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.24.0-experimental.fe39839").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
|
|
16917
16770
|
registerAuthCommands(program);
|
|
16918
16771
|
registerHooksCommands(program);
|
|
16919
16772
|
registerIntentCommands(program);
|
|
@@ -312,25 +312,18 @@ 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
|
|
315
|
+
Use the `verity` CLI to register. It handles credential storage and 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
|
-
|
|
327
321
|
This command:
|
|
328
|
-
- **
|
|
329
|
-
- **
|
|
330
|
-
- **Non-GitHub remote**: Only GitHub is supported for now; show the error and stop.
|
|
322
|
+
- **New project**: Registers, stores token + service URL in `.verity/credentials`, prints `project_id`. Continue.
|
|
323
|
+
- **Already registered**: Automatically discovers the project, ensures `.verity/credentials` has the service URL. If a token already exists in credentials, it updates the file and succeeds. If no token exists, it asks you to paste one.
|
|
331
324
|
- **Other errors**: Show the error and stop.
|
|
332
325
|
|
|
333
|
-
After this step, `.verity/credentials` will contain `token
|
|
326
|
+
After this step, `.verity/credentials` will contain both `token` and `service_url` — all subsequent `verity` commands will work.
|
|
334
327
|
|
|
335
328
|
---
|
|
336
329
|
|