@codacy/verity-cli 0.25.0-experimental.e3da48a → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/verity.js +280 -427
- 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");
|
|
@@ -10475,11 +10475,7 @@ 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 = "
|
|
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";
|
|
10478
|
+
var DEFAULT_SERVICE_URL = "".length > 0 ? "" : PROD_SERVICE_URL;
|
|
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_crypto3 = 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
|
var import_node_crypto = require("node:crypto");
|
|
11606
11250
|
function stripImageReferences(text) {
|
|
11607
11251
|
return text.replace(/\[Image #\d+\]/g, "[screenshot \u2014 not available for review]");
|
|
@@ -11636,7 +11280,7 @@ async function appendToConversationBuffer(prompt, sessionId) {
|
|
|
11636
11280
|
}
|
|
11637
11281
|
async function readAndClearConversationBuffer(currentSessionId) {
|
|
11638
11282
|
try {
|
|
11639
|
-
if ((0,
|
|
11283
|
+
if ((0, import_node_fs2.existsSync)(CONVERSATION_BUFFER_FILE)) {
|
|
11640
11284
|
const entries = await readBufferEntries();
|
|
11641
11285
|
let mine = entries;
|
|
11642
11286
|
let others = [];
|
|
@@ -11660,7 +11304,7 @@ async function readAndClearConversationBuffer(currentSessionId) {
|
|
|
11660
11304
|
};
|
|
11661
11305
|
}
|
|
11662
11306
|
}
|
|
11663
|
-
if ((0,
|
|
11307
|
+
if ((0, import_node_fs2.existsSync)(INTENT_FILE)) {
|
|
11664
11308
|
try {
|
|
11665
11309
|
const content = await (0, import_promises5.readFile)(INTENT_FILE, "utf-8");
|
|
11666
11310
|
await (0, import_promises5.unlink)(INTENT_FILE).catch(() => {
|
|
@@ -11704,7 +11348,7 @@ async function readBufferEntries() {
|
|
|
11704
11348
|
}
|
|
11705
11349
|
function getRecentCommitMessages() {
|
|
11706
11350
|
try {
|
|
11707
|
-
const output = (0,
|
|
11351
|
+
const output = (0, import_node_child_process4.execSync)(
|
|
11708
11352
|
'git log --since="30 minutes ago" --format="%s" -5',
|
|
11709
11353
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
11710
11354
|
).trim();
|
|
@@ -11717,8 +11361,8 @@ function getRecentCommitMessages() {
|
|
|
11717
11361
|
|
|
11718
11362
|
// src/lib/task-context-buffer.ts
|
|
11719
11363
|
var import_promises6 = require("node:fs/promises");
|
|
11720
|
-
var
|
|
11721
|
-
var
|
|
11364
|
+
var import_node_fs3 = require("node:fs");
|
|
11365
|
+
var import_node_path4 = require("node:path");
|
|
11722
11366
|
var TASK_CONTEXT_DIR = `${VERITY_DIR}/.task-context`;
|
|
11723
11367
|
var MAX_BUFFER_BYTES = 500 * 1024;
|
|
11724
11368
|
var MAX_PROMPT_CHARS = 2e3;
|
|
@@ -11757,7 +11401,7 @@ async function appendResponseToTaskBuffer(taskId, assistantResponse, actionSumma
|
|
|
11757
11401
|
}
|
|
11758
11402
|
async function readTaskContextBuffer(taskId) {
|
|
11759
11403
|
const filePath = bufferPath(taskId);
|
|
11760
|
-
if (!(0,
|
|
11404
|
+
if (!(0, import_node_fs3.existsSync)(filePath)) return null;
|
|
11761
11405
|
try {
|
|
11762
11406
|
const content = await (0, import_promises6.readFile)(filePath, "utf-8");
|
|
11763
11407
|
if (!content.trim()) return null;
|
|
@@ -11791,12 +11435,12 @@ async function readTaskContextBuffer(taskId) {
|
|
|
11791
11435
|
}
|
|
11792
11436
|
async function cleanupTaskContextBuffers() {
|
|
11793
11437
|
try {
|
|
11794
|
-
if (!(0,
|
|
11438
|
+
if (!(0, import_node_fs3.existsSync)(TASK_CONTEXT_DIR)) return;
|
|
11795
11439
|
const files = await (0, import_promises6.readdir)(TASK_CONTEXT_DIR);
|
|
11796
11440
|
const cutoffMs = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
11797
11441
|
for (const file of files) {
|
|
11798
11442
|
if (!file.endsWith(".jsonl")) continue;
|
|
11799
|
-
const filePath = (0,
|
|
11443
|
+
const filePath = (0, import_node_path4.join)(TASK_CONTEXT_DIR, file);
|
|
11800
11444
|
try {
|
|
11801
11445
|
const stats = await (0, import_promises6.stat)(filePath);
|
|
11802
11446
|
if (stats.mtimeMs < cutoffMs) {
|
|
@@ -11810,13 +11454,13 @@ async function cleanupTaskContextBuffers() {
|
|
|
11810
11454
|
}
|
|
11811
11455
|
function bufferPath(taskId) {
|
|
11812
11456
|
const safe = taskId.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
11813
|
-
return (0,
|
|
11457
|
+
return (0, import_node_path4.join)(TASK_CONTEXT_DIR, `${safe}.jsonl`);
|
|
11814
11458
|
}
|
|
11815
11459
|
async function appendEntry(taskId, entry) {
|
|
11816
11460
|
try {
|
|
11817
11461
|
await (0, import_promises6.mkdir)(TASK_CONTEXT_DIR, { recursive: true });
|
|
11818
11462
|
const filePath = bufferPath(taskId);
|
|
11819
|
-
if ((0,
|
|
11463
|
+
if ((0, import_node_fs3.existsSync)(filePath)) {
|
|
11820
11464
|
const stats = await (0, import_promises6.stat)(filePath);
|
|
11821
11465
|
if (stats.size >= MAX_BUFFER_BYTES) {
|
|
11822
11466
|
const content = await (0, import_promises6.readFile)(filePath, "utf-8");
|
|
@@ -11827,7 +11471,7 @@ async function appendEntry(taskId, entry) {
|
|
|
11827
11471
|
}
|
|
11828
11472
|
}
|
|
11829
11473
|
const line = JSON.stringify(entry) + "\n";
|
|
11830
|
-
const existing = (0,
|
|
11474
|
+
const existing = (0, import_node_fs3.existsSync)(filePath) ? await (0, import_promises6.readFile)(filePath, "utf-8") : "";
|
|
11831
11475
|
await (0, import_promises6.writeFile)(filePath, existing + line);
|
|
11832
11476
|
} catch {
|
|
11833
11477
|
}
|
|
@@ -11835,8 +11479,8 @@ async function appendEntry(taskId, entry) {
|
|
|
11835
11479
|
|
|
11836
11480
|
// src/lib/memory-retrieval.ts
|
|
11837
11481
|
var import_promises7 = require("node:fs/promises");
|
|
11838
|
-
var
|
|
11839
|
-
var
|
|
11482
|
+
var import_node_fs4 = require("node:fs");
|
|
11483
|
+
var import_node_path5 = require("node:path");
|
|
11840
11484
|
var memoryDir = () => projectPath(`${VERITY_DIR}/memory`);
|
|
11841
11485
|
var DOMAINS = ["decisions", "quality", "security", "intent", "gotchas", "patterns", "domain", "integrations"];
|
|
11842
11486
|
var DEFAULT_BUDGET_TOKENS = 2e3;
|
|
@@ -11929,19 +11573,19 @@ function parseFrontmatter(content) {
|
|
|
11929
11573
|
return { fm, body: match[2].trim() };
|
|
11930
11574
|
}
|
|
11931
11575
|
async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = DEFAULT_BUDGET_TOKENS) {
|
|
11932
|
-
if (!(0,
|
|
11576
|
+
if (!(0, import_node_fs4.existsSync)(memoryDir())) return null;
|
|
11933
11577
|
const budget = Math.min(budgetTokens, MAX_BUDGET_TOKENS);
|
|
11934
11578
|
const promptTokens = tokenize(promptText);
|
|
11935
11579
|
const nodes = [];
|
|
11936
11580
|
for (const domain of DOMAINS) {
|
|
11937
|
-
const domainDir = (0,
|
|
11938
|
-
if (!(0,
|
|
11581
|
+
const domainDir = (0, import_node_path5.join)(memoryDir(), domain);
|
|
11582
|
+
if (!(0, import_node_fs4.existsSync)(domainDir)) continue;
|
|
11939
11583
|
try {
|
|
11940
11584
|
const files = await (0, import_promises7.readdir)(domainDir);
|
|
11941
11585
|
for (const file of files) {
|
|
11942
11586
|
if (!file.endsWith(".md")) continue;
|
|
11943
11587
|
try {
|
|
11944
|
-
const content = await (0, import_promises7.readFile)((0,
|
|
11588
|
+
const content = await (0, import_promises7.readFile)((0, import_node_path5.join)(domainDir, file), "utf-8");
|
|
11945
11589
|
const { fm, body } = parseFrontmatter(content);
|
|
11946
11590
|
if (fm.status && fm.status !== "active") continue;
|
|
11947
11591
|
nodes.push({
|
|
@@ -12000,8 +11644,8 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
|
|
|
12000
11644
|
|
|
12001
11645
|
// src/lib/memory-sync.ts
|
|
12002
11646
|
var import_promises8 = require("node:fs/promises");
|
|
12003
|
-
var
|
|
12004
|
-
var
|
|
11647
|
+
var import_node_fs5 = require("node:fs");
|
|
11648
|
+
var import_node_path6 = require("node:path");
|
|
12005
11649
|
var import_node_crypto2 = require("node:crypto");
|
|
12006
11650
|
|
|
12007
11651
|
// src/lib/glob-match.ts
|
|
@@ -12072,32 +11716,32 @@ var syncStateFile = () => projectPath(`${VERITY_DIR}/.memory-sync-state.json`);
|
|
|
12072
11716
|
async function ensureMemoryDir() {
|
|
12073
11717
|
await (0, import_promises8.mkdir)(memoryDir2(), { recursive: true });
|
|
12074
11718
|
for (const domain of DOMAINS2) {
|
|
12075
|
-
await (0, import_promises8.mkdir)((0,
|
|
11719
|
+
await (0, import_promises8.mkdir)((0, import_node_path6.join)(memoryDir2(), domain), { recursive: true });
|
|
12076
11720
|
}
|
|
12077
|
-
if (!(0,
|
|
12078
|
-
await (0, import_promises8.writeFile)((0,
|
|
11721
|
+
if (!(0, import_node_fs5.existsSync)((0, import_node_path6.join)(memoryDir2(), "SCHEMA.md"))) {
|
|
11722
|
+
await (0, import_promises8.writeFile)((0, import_node_path6.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
|
|
12079
11723
|
}
|
|
12080
|
-
if (!(0,
|
|
12081
|
-
await (0, import_promises8.writeFile)((0,
|
|
11724
|
+
if (!(0, import_node_fs5.existsSync)((0, import_node_path6.join)(memoryDir2(), "index.md"))) {
|
|
11725
|
+
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");
|
|
12082
11726
|
}
|
|
12083
|
-
if (!(0,
|
|
12084
|
-
await (0, import_promises8.writeFile)((0,
|
|
11727
|
+
if (!(0, import_node_fs5.existsSync)((0, import_node_path6.join)(memoryDir2(), "log.md"))) {
|
|
11728
|
+
await (0, import_promises8.writeFile)((0, import_node_path6.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
|
|
12085
11729
|
}
|
|
12086
11730
|
}
|
|
12087
11731
|
async function buildManifest() {
|
|
12088
|
-
if (!(0,
|
|
11732
|
+
if (!(0, import_node_fs5.existsSync)(memoryDir2())) {
|
|
12089
11733
|
return { schema_version: 1, nodes: [], index_hash: null, log_length: 0 };
|
|
12090
11734
|
}
|
|
12091
11735
|
const nodes = [];
|
|
12092
11736
|
for (const domain of DOMAINS2) {
|
|
12093
|
-
const domainDir = (0,
|
|
12094
|
-
if (!(0,
|
|
11737
|
+
const domainDir = (0, import_node_path6.join)(memoryDir2(), domain);
|
|
11738
|
+
if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
|
|
12095
11739
|
try {
|
|
12096
11740
|
const files = await (0, import_promises8.readdir)(domainDir);
|
|
12097
11741
|
for (const file of files) {
|
|
12098
11742
|
if (!file.endsWith(".md")) continue;
|
|
12099
11743
|
const filePath = `${domain}/${file}`;
|
|
12100
|
-
const fullPath = (0,
|
|
11744
|
+
const fullPath = (0, import_node_path6.join)(memoryDir2(), filePath);
|
|
12101
11745
|
try {
|
|
12102
11746
|
const content = await (0, import_promises8.readFile)(fullPath, "utf-8");
|
|
12103
11747
|
const hash = (0, import_node_crypto2.createHash)("sha256").update(content).digest("hex").slice(0, 16);
|
|
@@ -12110,13 +11754,13 @@ async function buildManifest() {
|
|
|
12110
11754
|
}
|
|
12111
11755
|
let indexHash = null;
|
|
12112
11756
|
try {
|
|
12113
|
-
const indexContent = await (0, import_promises8.readFile)((0,
|
|
11757
|
+
const indexContent = await (0, import_promises8.readFile)((0, import_node_path6.join)(memoryDir2(), "index.md"), "utf-8");
|
|
12114
11758
|
indexHash = `sha256:${(0, import_node_crypto2.createHash)("sha256").update(indexContent).digest("hex").slice(0, 16)}`;
|
|
12115
11759
|
} catch {
|
|
12116
11760
|
}
|
|
12117
11761
|
let logLength = 0;
|
|
12118
11762
|
try {
|
|
12119
|
-
const logContent = await (0, import_promises8.readFile)((0,
|
|
11763
|
+
const logContent = await (0, import_promises8.readFile)((0, import_node_path6.join)(memoryDir2(), "log.md"), "utf-8");
|
|
12120
11764
|
logLength = logContent.split("\n").length;
|
|
12121
11765
|
} catch {
|
|
12122
11766
|
}
|
|
@@ -12127,15 +11771,15 @@ function hashContent(content) {
|
|
|
12127
11771
|
}
|
|
12128
11772
|
async function readOnDiskNodes() {
|
|
12129
11773
|
const out = /* @__PURE__ */ new Map();
|
|
12130
|
-
if (!(0,
|
|
11774
|
+
if (!(0, import_node_fs5.existsSync)(memoryDir2())) return out;
|
|
12131
11775
|
for (const domain of DOMAINS2) {
|
|
12132
|
-
const domainDir = (0,
|
|
12133
|
-
if (!(0,
|
|
11776
|
+
const domainDir = (0, import_node_path6.join)(memoryDir2(), domain);
|
|
11777
|
+
if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
|
|
12134
11778
|
try {
|
|
12135
11779
|
for (const file of await (0, import_promises8.readdir)(domainDir)) {
|
|
12136
11780
|
if (!file.endsWith(".md")) continue;
|
|
12137
11781
|
try {
|
|
12138
|
-
out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0,
|
|
11782
|
+
out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0, import_node_path6.join)(domainDir, file), "utf-8")));
|
|
12139
11783
|
} catch {
|
|
12140
11784
|
}
|
|
12141
11785
|
}
|
|
@@ -12181,8 +11825,8 @@ async function computeEditedNodeUploads() {
|
|
|
12181
11825
|
const uploads = [];
|
|
12182
11826
|
for (const [path, prevHash] of prev) {
|
|
12183
11827
|
if (prevHash == null) continue;
|
|
12184
|
-
const full = (0,
|
|
12185
|
-
if (!(0,
|
|
11828
|
+
const full = (0, import_node_path6.join)(memoryDir2(), path);
|
|
11829
|
+
if (!(0, import_node_fs5.existsSync)(full)) continue;
|
|
12186
11830
|
let content;
|
|
12187
11831
|
try {
|
|
12188
11832
|
content = await (0, import_promises8.readFile)(full, "utf-8");
|
|
@@ -12218,15 +11862,15 @@ async function applyMemoryWrites(writes, opts = {}) {
|
|
|
12218
11862
|
const logLines = [`- ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)} \u2014 Applied ${count} write(s) from server`];
|
|
12219
11863
|
for (const n of notes) logLines.push(` - ${n}`);
|
|
12220
11864
|
try {
|
|
12221
|
-
const existing = (0,
|
|
12222
|
-
await (0, import_promises8.writeFile)((0,
|
|
11865
|
+
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";
|
|
11866
|
+
await (0, import_promises8.writeFile)((0, import_node_path6.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
|
|
12223
11867
|
} catch {
|
|
12224
11868
|
}
|
|
12225
11869
|
await recordSyncedNodePaths();
|
|
12226
11870
|
return count;
|
|
12227
11871
|
}
|
|
12228
11872
|
async function applyOneWrite(write, treePaths) {
|
|
12229
|
-
const fullPath = (0,
|
|
11873
|
+
const fullPath = (0, import_node_path6.join)(memoryDir2(), write.path);
|
|
12230
11874
|
const notes = [];
|
|
12231
11875
|
let content = write.content;
|
|
12232
11876
|
if (treePaths && treePaths.length > 0) {
|
|
@@ -12236,7 +11880,7 @@ async function applyOneWrite(write, treePaths) {
|
|
|
12236
11880
|
notes.push(`${write.path}: dropped unmatched file_globs [${grounded.dropped.join(", ")}]`);
|
|
12237
11881
|
}
|
|
12238
11882
|
}
|
|
12239
|
-
if ((0,
|
|
11883
|
+
if ((0, import_node_fs5.existsSync)(fullPath)) {
|
|
12240
11884
|
let existing = "";
|
|
12241
11885
|
try {
|
|
12242
11886
|
existing = await (0, import_promises8.readFile)(fullPath, "utf-8");
|
|
@@ -12248,7 +11892,7 @@ async function applyOneWrite(write, treePaths) {
|
|
|
12248
11892
|
return { written: false, notes };
|
|
12249
11893
|
}
|
|
12250
11894
|
}
|
|
12251
|
-
await (0, import_promises8.mkdir)((0,
|
|
11895
|
+
await (0, import_promises8.mkdir)((0, import_node_path6.dirname)(fullPath), { recursive: true });
|
|
12252
11896
|
await (0, import_promises8.writeFile)(fullPath, content);
|
|
12253
11897
|
return { written: true, notes };
|
|
12254
11898
|
}
|
|
@@ -12289,8 +11933,8 @@ async function regenerateIndex() {
|
|
|
12289
11933
|
];
|
|
12290
11934
|
let totalNodes = 0;
|
|
12291
11935
|
for (const domain of DOMAINS2.filter((d) => d !== "_archive")) {
|
|
12292
|
-
const domainDir = (0,
|
|
12293
|
-
if (!(0,
|
|
11936
|
+
const domainDir = (0, import_node_path6.join)(memoryDir2(), domain);
|
|
11937
|
+
if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
|
|
12294
11938
|
try {
|
|
12295
11939
|
const files = await (0, import_promises8.readdir)(domainDir);
|
|
12296
11940
|
const mdFiles = files.filter((f) => f.endsWith(".md"));
|
|
@@ -12300,7 +11944,7 @@ async function regenerateIndex() {
|
|
|
12300
11944
|
for (const file of mdFiles.sort()) {
|
|
12301
11945
|
const slug = file.replace(/\.md$/, "");
|
|
12302
11946
|
try {
|
|
12303
|
-
const content = await (0, import_promises8.readFile)((0,
|
|
11947
|
+
const content = await (0, import_promises8.readFile)((0, import_node_path6.join)(domainDir, file), "utf-8");
|
|
12304
11948
|
const title = pickFrontmatter(content, "title") ?? slug;
|
|
12305
11949
|
const kind = pickFrontmatter(content, "kind") ?? "-";
|
|
12306
11950
|
const confidence = pickFrontmatter(content, "confidence");
|
|
@@ -12324,7 +11968,7 @@ async function regenerateIndex() {
|
|
|
12324
11968
|
lines.push("No nodes yet. Run an analysis to start building the knowledge graph.");
|
|
12325
11969
|
}
|
|
12326
11970
|
const next = lines.join("\n") + "\n";
|
|
12327
|
-
const indexPath = (0,
|
|
11971
|
+
const indexPath = (0, import_node_path6.join)(memoryDir2(), "index.md");
|
|
12328
11972
|
let existing = null;
|
|
12329
11973
|
try {
|
|
12330
11974
|
existing = await (0, import_promises8.readFile)(indexPath, "utf-8");
|
|
@@ -12406,9 +12050,9 @@ function hasLegacyMemoryBlock(text) {
|
|
|
12406
12050
|
return findMarker(text, LEGACY_MD_START) !== -1;
|
|
12407
12051
|
}
|
|
12408
12052
|
async function ensureClaudeMdPointer(cwd = repoRoot()) {
|
|
12409
|
-
const claudeMdPath = (0,
|
|
12053
|
+
const claudeMdPath = (0, import_node_path6.join)(cwd, "CLAUDE.md");
|
|
12410
12054
|
let existing = "";
|
|
12411
|
-
if ((0,
|
|
12055
|
+
if ((0, import_node_fs5.existsSync)(claudeMdPath)) {
|
|
12412
12056
|
existing = await (0, import_promises8.readFile)(claudeMdPath, "utf-8");
|
|
12413
12057
|
}
|
|
12414
12058
|
let startTag = CLAUDE_MD_START;
|
|
@@ -12538,7 +12182,7 @@ Body content (\u22648KB). Use [[node-id]] wikilinks for cross-references.
|
|
|
12538
12182
|
`;
|
|
12539
12183
|
|
|
12540
12184
|
// src/commands/intent.ts
|
|
12541
|
-
var
|
|
12185
|
+
var import_node_fs6 = require("node:fs");
|
|
12542
12186
|
function registerIntentCommands(program2) {
|
|
12543
12187
|
const intent = program2.command("intent").description("Manage intent capture");
|
|
12544
12188
|
intent.command("capture").description("Capture user intent from stdin (used by UserPromptSubmit hook)").action(async () => {
|
|
@@ -12547,7 +12191,7 @@ function registerIntentCommands(program2) {
|
|
|
12547
12191
|
process.chdir(repoRoot());
|
|
12548
12192
|
} catch {
|
|
12549
12193
|
}
|
|
12550
|
-
if (!(0,
|
|
12194
|
+
if (!(0, import_node_fs6.existsSync)(VERITY_DIR)) {
|
|
12551
12195
|
process.exit(0);
|
|
12552
12196
|
}
|
|
12553
12197
|
const chunks = [];
|
|
@@ -13126,6 +12770,215 @@ async function sendGeneralFeedback(message, opts, globals) {
|
|
|
13126
12770
|
var import_node_fs19 = require("node:fs");
|
|
13127
12771
|
var import_node_path14 = require("node:path");
|
|
13128
12772
|
|
|
12773
|
+
// src/lib/git.ts
|
|
12774
|
+
var import_node_child_process5 = require("node:child_process");
|
|
12775
|
+
var import_node_fs7 = require("node:fs");
|
|
12776
|
+
var import_node_path7 = require("node:path");
|
|
12777
|
+
function resolveFile(relpath) {
|
|
12778
|
+
if ((0, import_node_fs7.existsSync)(relpath)) return relpath;
|
|
12779
|
+
if ((0, import_node_fs7.existsSync)(".claude/worktrees")) {
|
|
12780
|
+
try {
|
|
12781
|
+
const entries = (0, import_node_fs7.readdirSync)(".claude/worktrees", { withFileTypes: true });
|
|
12782
|
+
for (const entry of entries) {
|
|
12783
|
+
if (!entry.isDirectory()) continue;
|
|
12784
|
+
const candidate = (0, import_node_path7.join)(".claude/worktrees", entry.name, relpath);
|
|
12785
|
+
if ((0, import_node_fs7.existsSync)(candidate)) return candidate;
|
|
12786
|
+
}
|
|
12787
|
+
} catch {
|
|
12788
|
+
}
|
|
12789
|
+
}
|
|
12790
|
+
return null;
|
|
12791
|
+
}
|
|
12792
|
+
function execGit(cmd) {
|
|
12793
|
+
try {
|
|
12794
|
+
return (0, import_node_child_process5.execSync)(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
12795
|
+
} catch {
|
|
12796
|
+
return "";
|
|
12797
|
+
}
|
|
12798
|
+
}
|
|
12799
|
+
function splitLines(s) {
|
|
12800
|
+
return s.split("\n").filter((l) => l.length > 0);
|
|
12801
|
+
}
|
|
12802
|
+
var SHA_RE = /^[0-9a-f]{40}$/;
|
|
12803
|
+
function readBaselineSha() {
|
|
12804
|
+
if (!(0, import_node_fs7.existsSync)(BASELINE_SHA_FILE)) return null;
|
|
12805
|
+
let sha;
|
|
12806
|
+
try {
|
|
12807
|
+
sha = (0, import_node_fs7.readFileSync)(BASELINE_SHA_FILE, "utf-8").trim();
|
|
12808
|
+
} catch {
|
|
12809
|
+
return null;
|
|
12810
|
+
}
|
|
12811
|
+
if (!SHA_RE.test(sha)) return null;
|
|
12812
|
+
const reachable = execGit(`git cat-file -e ${sha}^{commit} 2>/dev/null && echo ok`) === "ok";
|
|
12813
|
+
if (!reachable) {
|
|
12814
|
+
try {
|
|
12815
|
+
(0, import_node_fs7.unlinkSync)(BASELINE_SHA_FILE);
|
|
12816
|
+
} catch {
|
|
12817
|
+
}
|
|
12818
|
+
return null;
|
|
12819
|
+
}
|
|
12820
|
+
return sha;
|
|
12821
|
+
}
|
|
12822
|
+
function writeBaselineSha(sha) {
|
|
12823
|
+
if (!SHA_RE.test(sha)) return;
|
|
12824
|
+
try {
|
|
12825
|
+
(0, import_node_fs7.mkdirSync)((0, import_node_path7.dirname)(BASELINE_SHA_FILE), { recursive: true });
|
|
12826
|
+
(0, import_node_fs7.writeFileSync)(BASELINE_SHA_FILE, sha);
|
|
12827
|
+
} catch {
|
|
12828
|
+
}
|
|
12829
|
+
}
|
|
12830
|
+
function getChangedFiles() {
|
|
12831
|
+
const sets = /* @__PURE__ */ new Set();
|
|
12832
|
+
let hasRecentCommitFiles = false;
|
|
12833
|
+
for (const f of splitLines(execGit("git diff --name-only HEAD"))) sets.add(f);
|
|
12834
|
+
for (const f of splitLines(execGit("git diff --name-only --cached"))) sets.add(f);
|
|
12835
|
+
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) sets.add(f);
|
|
12836
|
+
const baseline = readBaselineSha();
|
|
12837
|
+
if (baseline) {
|
|
12838
|
+
const committed = splitLines(execGit(`git diff --name-only ${baseline}..HEAD`));
|
|
12839
|
+
if (committed.length > 0) {
|
|
12840
|
+
hasRecentCommitFiles = true;
|
|
12841
|
+
for (const f of committed) sets.add(f);
|
|
12842
|
+
}
|
|
12843
|
+
} else {
|
|
12844
|
+
const headTimestamp = parseInt(execGit("git log -1 --format=%ct HEAD"), 10) || 0;
|
|
12845
|
+
const commitAge = Math.floor(Date.now() / 1e3) - headTimestamp;
|
|
12846
|
+
const hasUnstaged = splitLines(execGit("git diff --name-only HEAD")).length > 0;
|
|
12847
|
+
const hasStaged = splitLines(execGit("git diff --name-only --cached")).length > 0;
|
|
12848
|
+
if (commitAge < 120 && !hasUnstaged && !hasStaged) {
|
|
12849
|
+
const recentFiles = splitLines(execGit("git diff --name-only HEAD~1..HEAD"));
|
|
12850
|
+
if (recentFiles.length > 0) {
|
|
12851
|
+
hasRecentCommitFiles = true;
|
|
12852
|
+
for (const f of recentFiles) sets.add(f);
|
|
12853
|
+
}
|
|
12854
|
+
}
|
|
12855
|
+
}
|
|
12856
|
+
for (const f of getWorktreeFiles()) sets.add(f);
|
|
12857
|
+
const filtered = Array.from(sets).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
12858
|
+
return { files: filtered, hasRecentCommitFiles };
|
|
12859
|
+
}
|
|
12860
|
+
function getStagedFiles() {
|
|
12861
|
+
return splitLines(execGit("git diff --cached --name-only")).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
12862
|
+
}
|
|
12863
|
+
function getDirtyFiles() {
|
|
12864
|
+
const set = /* @__PURE__ */ new Set();
|
|
12865
|
+
for (const f of splitLines(execGit("git diff --name-only HEAD"))) set.add(f);
|
|
12866
|
+
for (const f of splitLines(execGit("git diff --name-only --cached"))) set.add(f);
|
|
12867
|
+
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
|
|
12868
|
+
return Array.from(set).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
12869
|
+
}
|
|
12870
|
+
function showContentAtRef(ref, repoRelPath) {
|
|
12871
|
+
if (!ref || ref === "no-git") return null;
|
|
12872
|
+
const normalizedPath = repoRelPath.replace(/\\/g, "/");
|
|
12873
|
+
try {
|
|
12874
|
+
return (0, import_node_child_process5.execFileSync)("git", ["show", `${ref}:${normalizedPath}`], {
|
|
12875
|
+
encoding: "utf-8",
|
|
12876
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
12877
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
12878
|
+
});
|
|
12879
|
+
} catch {
|
|
12880
|
+
return null;
|
|
12881
|
+
}
|
|
12882
|
+
}
|
|
12883
|
+
function getPushRangeFiles() {
|
|
12884
|
+
const diff = (range) => splitLines(execGit(`git diff --name-only ${range}`)).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
12885
|
+
const resolvers = [
|
|
12886
|
+
() => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{push}") ? "@{push}..HEAD" : null,
|
|
12887
|
+
() => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{upstream}") ? "@{upstream}..HEAD" : null,
|
|
12888
|
+
() => {
|
|
12889
|
+
const branch = execGit("git rev-parse --abbrev-ref HEAD");
|
|
12890
|
+
return branch && branch !== "HEAD" && execGit(`git rev-parse --verify -q origin/${branch}`) ? `origin/${branch}..HEAD` : null;
|
|
12891
|
+
}
|
|
12892
|
+
];
|
|
12893
|
+
for (const resolve of resolvers) {
|
|
12894
|
+
const range = resolve();
|
|
12895
|
+
if (range) return { files: diff(range), range };
|
|
12896
|
+
}
|
|
12897
|
+
const baseline = readBaselineSha();
|
|
12898
|
+
if (baseline) {
|
|
12899
|
+
const files = diff(`${baseline}..HEAD`);
|
|
12900
|
+
if (files.length > 0) return { files, range: `${baseline}..HEAD` };
|
|
12901
|
+
}
|
|
12902
|
+
const last = diff("HEAD~1..HEAD");
|
|
12903
|
+
return { files: last, range: last.length > 0 ? "HEAD~1..HEAD" : null };
|
|
12904
|
+
}
|
|
12905
|
+
function getPushRangeMessages() {
|
|
12906
|
+
const { range } = getPushRangeFiles();
|
|
12907
|
+
if (!range) return "";
|
|
12908
|
+
return execGit(`git log ${range} --format=%B%x00`).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
|
|
12909
|
+
}
|
|
12910
|
+
function getWorktreeFiles() {
|
|
12911
|
+
const result = [];
|
|
12912
|
+
const worktreeDir = ".claude/worktrees";
|
|
12913
|
+
if (!(0, import_node_fs7.existsSync)(worktreeDir)) return result;
|
|
12914
|
+
try {
|
|
12915
|
+
const fiveMinAgo = Date.now() - 5 * 60 * 1e3;
|
|
12916
|
+
const entries = (0, import_node_fs7.readdirSync)(worktreeDir, { withFileTypes: true });
|
|
12917
|
+
for (const entry of entries) {
|
|
12918
|
+
if (!entry.isDirectory()) continue;
|
|
12919
|
+
const wtDir = (0, import_node_path7.join)(worktreeDir, entry.name);
|
|
12920
|
+
scanDir(wtDir, wtDir, fiveMinAgo, result);
|
|
12921
|
+
}
|
|
12922
|
+
} catch {
|
|
12923
|
+
}
|
|
12924
|
+
return result;
|
|
12925
|
+
}
|
|
12926
|
+
function scanDir(baseDir, dir, minMtime, result) {
|
|
12927
|
+
try {
|
|
12928
|
+
const entries = (0, import_node_fs7.readdirSync)(dir, { withFileTypes: true });
|
|
12929
|
+
for (const entry of entries) {
|
|
12930
|
+
const fullPath = (0, import_node_path7.join)(dir, entry.name);
|
|
12931
|
+
if (entry.isDirectory()) {
|
|
12932
|
+
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
12933
|
+
scanDir(baseDir, fullPath, minMtime, result);
|
|
12934
|
+
} else if (entry.isFile()) {
|
|
12935
|
+
const ext = (0, import_node_path7.extname)(entry.name).slice(1);
|
|
12936
|
+
if (!ANALYZABLE_EXTENSIONS.has(ext)) continue;
|
|
12937
|
+
try {
|
|
12938
|
+
const stat3 = (0, import_node_fs7.statSync)(fullPath);
|
|
12939
|
+
if (stat3.mtimeMs >= minMtime) {
|
|
12940
|
+
const relPath = fullPath.slice(baseDir.length + 1);
|
|
12941
|
+
result.push(relPath);
|
|
12942
|
+
}
|
|
12943
|
+
} catch {
|
|
12944
|
+
}
|
|
12945
|
+
}
|
|
12946
|
+
}
|
|
12947
|
+
} catch {
|
|
12948
|
+
}
|
|
12949
|
+
}
|
|
12950
|
+
function filterAnalyzable(files) {
|
|
12951
|
+
return files.filter((f) => {
|
|
12952
|
+
const ext = (0, import_node_path7.extname)(f).slice(1);
|
|
12953
|
+
return ANALYZABLE_EXTENSIONS.has(ext);
|
|
12954
|
+
});
|
|
12955
|
+
}
|
|
12956
|
+
function filterReviewable(files) {
|
|
12957
|
+
return files.filter((f) => {
|
|
12958
|
+
const ext = (0, import_node_path7.extname)(f).slice(1);
|
|
12959
|
+
if (ANALYZABLE_EXTENSIONS.has(ext)) return false;
|
|
12960
|
+
if (REVIEWABLE_EXTENSIONS.has(ext)) return true;
|
|
12961
|
+
const basename2 = f.split("/").pop() ?? "";
|
|
12962
|
+
if (REVIEWABLE_FILENAMES.has(basename2)) return true;
|
|
12963
|
+
if (REVIEWABLE_PATH_PATTERNS.some((p) => p.test(f))) return true;
|
|
12964
|
+
return false;
|
|
12965
|
+
});
|
|
12966
|
+
}
|
|
12967
|
+
function filterSecurity(files) {
|
|
12968
|
+
return files.filter(
|
|
12969
|
+
(f) => SECURITY_PATTERNS.some((p) => p.test(f))
|
|
12970
|
+
);
|
|
12971
|
+
}
|
|
12972
|
+
function getCurrentCommit() {
|
|
12973
|
+
return execGit("git rev-parse HEAD") || "no-git";
|
|
12974
|
+
}
|
|
12975
|
+
function listTrackedFiles() {
|
|
12976
|
+
const set = /* @__PURE__ */ new Set();
|
|
12977
|
+
for (const f of splitLines(execGit("git ls-files"))) set.add(f);
|
|
12978
|
+
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
|
|
12979
|
+
return Array.from(set);
|
|
12980
|
+
}
|
|
12981
|
+
|
|
13129
12982
|
// src/lib/files.ts
|
|
13130
12983
|
var import_node_fs8 = require("node:fs");
|
|
13131
12984
|
var import_node_path8 = require("node:path");
|
|
@@ -16925,7 +16778,7 @@ function registerTelemetryCommands(program2) {
|
|
|
16925
16778
|
}
|
|
16926
16779
|
|
|
16927
16780
|
// src/cli.ts
|
|
16928
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.25.0
|
|
16781
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.25.0").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
|
|
16929
16782
|
registerAuthCommands(program);
|
|
16930
16783
|
registerHooksCommands(program);
|
|
16931
16784
|
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
|
|