@codacy/verity-cli 0.26.0 → 0.27.0-experimental.1106a16
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 +646 -306
- package/data/skills/verity-analyze/SKILL.md +7 -1
- package/data/skills/verity-setup/SKILL.md +51 -10
- package/package.json +1 -1
package/bin/verity.js
CHANGED
|
@@ -10325,9 +10325,7 @@ var {
|
|
|
10325
10325
|
} = import_index.default;
|
|
10326
10326
|
|
|
10327
10327
|
// src/commands/auth.ts
|
|
10328
|
-
var
|
|
10329
|
-
var import_node_child_process3 = require("node:child_process");
|
|
10330
|
-
var import_node_path2 = require("node:path");
|
|
10328
|
+
var import_node_child_process4 = require("node:child_process");
|
|
10331
10329
|
|
|
10332
10330
|
// src/lib/auth.ts
|
|
10333
10331
|
var import_promises = require("node:fs/promises");
|
|
@@ -10475,7 +10473,15 @@ var SECURITY_PATTERNS = [
|
|
|
10475
10473
|
/Dockerfile/
|
|
10476
10474
|
];
|
|
10477
10475
|
var PROD_SERVICE_URL = "https://ofcamwrjwrkazqvdchko.supabase.co/functions/v1";
|
|
10478
|
-
var DEFAULT_SERVICE_URL = "".length > 0 ? "" : PROD_SERVICE_URL;
|
|
10476
|
+
var DEFAULT_SERVICE_URL = "https://yyfaqvcgslcrzvrbvqik.supabase.co/functions/v1".length > 0 ? "https://yyfaqvcgslcrzvrbvqik.supabase.co/functions/v1" : PROD_SERVICE_URL;
|
|
10477
|
+
var GITHUB_CLIENT_ID = "Iv23li88HxAi3ZrbYzWh";
|
|
10478
|
+
var GITHUB_DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
10479
|
+
var GITHUB_ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
10480
|
+
var GITHUB_APP_SLUG = "verity-auth";
|
|
10481
|
+
var GITHUB_APP_INSTALL_URL = `https://github.com/apps/${GITHUB_APP_SLUG}/installations/new`;
|
|
10482
|
+
function githubAppInstallUrl(accountId) {
|
|
10483
|
+
return accountId != null ? `https://github.com/apps/${GITHUB_APP_SLUG}/installations/new/permissions?target_id=${accountId}` : GITHUB_APP_INSTALL_URL;
|
|
10484
|
+
}
|
|
10479
10485
|
|
|
10480
10486
|
// src/lib/auth.ts
|
|
10481
10487
|
async function resolveToken(flagToken) {
|
|
@@ -10641,7 +10647,8 @@ async function apiRequest(options) {
|
|
|
10641
10647
|
timeout = 9e4,
|
|
10642
10648
|
cmd = "unknown",
|
|
10643
10649
|
retry = false,
|
|
10644
|
-
encodeBody = false
|
|
10650
|
+
encodeBody = false,
|
|
10651
|
+
extraHeaders
|
|
10645
10652
|
} = options;
|
|
10646
10653
|
const url = `${serviceUrl}${path}`;
|
|
10647
10654
|
const headers = {
|
|
@@ -10650,6 +10657,9 @@ async function apiRequest(options) {
|
|
|
10650
10657
|
if (token) {
|
|
10651
10658
|
headers["Authorization"] = `Bearer ${token}`;
|
|
10652
10659
|
}
|
|
10660
|
+
if (extraHeaders) {
|
|
10661
|
+
Object.assign(headers, extraHeaders);
|
|
10662
|
+
}
|
|
10653
10663
|
const testMockScenario = process.env.VERITY_TEST_MOCK_SCENARIO;
|
|
10654
10664
|
if (testMockScenario) {
|
|
10655
10665
|
headers["X-Verity-Mock-Scenario"] = testMockScenario;
|
|
@@ -10740,6 +10750,468 @@ function analyzeRequest(options) {
|
|
|
10740
10750
|
});
|
|
10741
10751
|
}
|
|
10742
10752
|
|
|
10753
|
+
// src/lib/register.ts
|
|
10754
|
+
var import_promises3 = require("node:fs/promises");
|
|
10755
|
+
var import_node_path3 = require("node:path");
|
|
10756
|
+
var readline = __toESM(require("node:readline/promises"));
|
|
10757
|
+
|
|
10758
|
+
// src/lib/provider-auth.ts
|
|
10759
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
10760
|
+
var form = (fields) => new URLSearchParams(fields).toString();
|
|
10761
|
+
async function githubAccountId(owner) {
|
|
10762
|
+
try {
|
|
10763
|
+
const res = await fetch(`https://api.github.com/users/${encodeURIComponent(owner)}`, {
|
|
10764
|
+
headers: {
|
|
10765
|
+
Accept: "application/vnd.github+json",
|
|
10766
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
10767
|
+
"User-Agent": "verity-cli"
|
|
10768
|
+
}
|
|
10769
|
+
});
|
|
10770
|
+
if (!res.ok) return null;
|
|
10771
|
+
const body = await res.json();
|
|
10772
|
+
return typeof body.id === "number" ? body.id : null;
|
|
10773
|
+
} catch {
|
|
10774
|
+
return null;
|
|
10775
|
+
}
|
|
10776
|
+
}
|
|
10777
|
+
async function githubCanSeeRepo(owner, repo, token) {
|
|
10778
|
+
let res;
|
|
10779
|
+
try {
|
|
10780
|
+
res = await fetch(
|
|
10781
|
+
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`,
|
|
10782
|
+
{
|
|
10783
|
+
headers: {
|
|
10784
|
+
Authorization: `Bearer ${token}`,
|
|
10785
|
+
Accept: "application/vnd.github+json",
|
|
10786
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
10787
|
+
"User-Agent": "verity-cli"
|
|
10788
|
+
}
|
|
10789
|
+
}
|
|
10790
|
+
);
|
|
10791
|
+
} catch (err) {
|
|
10792
|
+
return { ok: false, error: `Network error contacting GitHub: ${err.message}` };
|
|
10793
|
+
}
|
|
10794
|
+
if (res.status === 404) return { ok: true, data: false };
|
|
10795
|
+
if (res.ok) return { ok: true, data: true };
|
|
10796
|
+
return { ok: false, error: `GitHub repo lookup failed (HTTP ${res.status})` };
|
|
10797
|
+
}
|
|
10798
|
+
async function githubDeviceFlow() {
|
|
10799
|
+
const override = process.env.VERITY_PROVIDER_TOKEN;
|
|
10800
|
+
if (override) return { ok: true, data: override };
|
|
10801
|
+
let dc;
|
|
10802
|
+
try {
|
|
10803
|
+
const res = await fetch(GITHUB_DEVICE_CODE_URL, {
|
|
10804
|
+
method: "POST",
|
|
10805
|
+
headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
|
|
10806
|
+
body: form({ client_id: GITHUB_CLIENT_ID })
|
|
10807
|
+
});
|
|
10808
|
+
if (!res.ok) {
|
|
10809
|
+
return { ok: false, error: `GitHub device-code request failed (HTTP ${res.status})` };
|
|
10810
|
+
}
|
|
10811
|
+
dc = await res.json();
|
|
10812
|
+
} catch (err) {
|
|
10813
|
+
return { ok: false, error: `Network error contacting GitHub: ${err.message}` };
|
|
10814
|
+
}
|
|
10815
|
+
if (!dc.device_code || !dc.user_code) {
|
|
10816
|
+
return {
|
|
10817
|
+
ok: false,
|
|
10818
|
+
error: "GitHub did not return a device code (is Device Flow enabled on the OAuth app?)"
|
|
10819
|
+
};
|
|
10820
|
+
}
|
|
10821
|
+
printInfo("");
|
|
10822
|
+
printInfo(`To authorize Verity, open: ${dc.verification_uri}`);
|
|
10823
|
+
printInfo(`And enter the code: ${dc.user_code}`);
|
|
10824
|
+
printInfo("Waiting for authorization\u2026");
|
|
10825
|
+
const deadline = Date.now() + (dc.expires_in || 900) * 1e3;
|
|
10826
|
+
let interval = dc.interval || 5;
|
|
10827
|
+
while (Date.now() < deadline) {
|
|
10828
|
+
await sleep(interval * 1e3);
|
|
10829
|
+
let data;
|
|
10830
|
+
try {
|
|
10831
|
+
const res = await fetch(GITHUB_ACCESS_TOKEN_URL, {
|
|
10832
|
+
method: "POST",
|
|
10833
|
+
headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
|
|
10834
|
+
body: form({
|
|
10835
|
+
client_id: GITHUB_CLIENT_ID,
|
|
10836
|
+
device_code: dc.device_code,
|
|
10837
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
10838
|
+
})
|
|
10839
|
+
});
|
|
10840
|
+
data = await res.json().catch(() => ({}));
|
|
10841
|
+
} catch {
|
|
10842
|
+
continue;
|
|
10843
|
+
}
|
|
10844
|
+
if (data.access_token) return { ok: true, data: data.access_token };
|
|
10845
|
+
switch (data.error) {
|
|
10846
|
+
case "authorization_pending":
|
|
10847
|
+
break;
|
|
10848
|
+
case "slow_down":
|
|
10849
|
+
interval += 5;
|
|
10850
|
+
break;
|
|
10851
|
+
case "access_denied":
|
|
10852
|
+
return { ok: false, error: "Authorization was denied on GitHub." };
|
|
10853
|
+
case "expired_token":
|
|
10854
|
+
return { ok: false, error: "The authorization code expired. Re-run register." };
|
|
10855
|
+
default:
|
|
10856
|
+
if (data.error) return { ok: false, error: `GitHub auth error: ${data.error}` };
|
|
10857
|
+
}
|
|
10858
|
+
}
|
|
10859
|
+
return { ok: false, error: "Timed out waiting for GitHub authorization." };
|
|
10860
|
+
}
|
|
10861
|
+
|
|
10862
|
+
// src/lib/git.ts
|
|
10863
|
+
var import_node_child_process3 = require("node:child_process");
|
|
10864
|
+
var import_node_fs2 = require("node:fs");
|
|
10865
|
+
var import_node_path2 = require("node:path");
|
|
10866
|
+
function resolveFile(relpath) {
|
|
10867
|
+
if ((0, import_node_fs2.existsSync)(relpath)) return relpath;
|
|
10868
|
+
if ((0, import_node_fs2.existsSync)(".claude/worktrees")) {
|
|
10869
|
+
try {
|
|
10870
|
+
const entries = (0, import_node_fs2.readdirSync)(".claude/worktrees", { withFileTypes: true });
|
|
10871
|
+
for (const entry of entries) {
|
|
10872
|
+
if (!entry.isDirectory()) continue;
|
|
10873
|
+
const candidate = (0, import_node_path2.join)(".claude/worktrees", entry.name, relpath);
|
|
10874
|
+
if ((0, import_node_fs2.existsSync)(candidate)) return candidate;
|
|
10875
|
+
}
|
|
10876
|
+
} catch {
|
|
10877
|
+
}
|
|
10878
|
+
}
|
|
10879
|
+
return null;
|
|
10880
|
+
}
|
|
10881
|
+
function execGit(cmd) {
|
|
10882
|
+
try {
|
|
10883
|
+
return (0, import_node_child_process3.execSync)(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
10884
|
+
} catch {
|
|
10885
|
+
return "";
|
|
10886
|
+
}
|
|
10887
|
+
}
|
|
10888
|
+
function splitLines(s) {
|
|
10889
|
+
return s.split("\n").filter((l) => l.length > 0);
|
|
10890
|
+
}
|
|
10891
|
+
var SHA_RE = /^[0-9a-f]{40}$/;
|
|
10892
|
+
function readBaselineSha() {
|
|
10893
|
+
if (!(0, import_node_fs2.existsSync)(BASELINE_SHA_FILE)) return null;
|
|
10894
|
+
let sha;
|
|
10895
|
+
try {
|
|
10896
|
+
sha = (0, import_node_fs2.readFileSync)(BASELINE_SHA_FILE, "utf-8").trim();
|
|
10897
|
+
} catch {
|
|
10898
|
+
return null;
|
|
10899
|
+
}
|
|
10900
|
+
if (!SHA_RE.test(sha)) return null;
|
|
10901
|
+
const reachable = execGit(`git cat-file -e ${sha}^{commit} 2>/dev/null && echo ok`) === "ok";
|
|
10902
|
+
if (!reachable) {
|
|
10903
|
+
try {
|
|
10904
|
+
(0, import_node_fs2.unlinkSync)(BASELINE_SHA_FILE);
|
|
10905
|
+
} catch {
|
|
10906
|
+
}
|
|
10907
|
+
return null;
|
|
10908
|
+
}
|
|
10909
|
+
return sha;
|
|
10910
|
+
}
|
|
10911
|
+
function writeBaselineSha(sha) {
|
|
10912
|
+
if (!SHA_RE.test(sha)) return;
|
|
10913
|
+
try {
|
|
10914
|
+
(0, import_node_fs2.mkdirSync)((0, import_node_path2.dirname)(BASELINE_SHA_FILE), { recursive: true });
|
|
10915
|
+
(0, import_node_fs2.writeFileSync)(BASELINE_SHA_FILE, sha);
|
|
10916
|
+
} catch {
|
|
10917
|
+
}
|
|
10918
|
+
}
|
|
10919
|
+
function getChangedFiles() {
|
|
10920
|
+
const sets = /* @__PURE__ */ new Set();
|
|
10921
|
+
let hasRecentCommitFiles = false;
|
|
10922
|
+
for (const f of splitLines(execGit("git diff --name-only HEAD"))) sets.add(f);
|
|
10923
|
+
for (const f of splitLines(execGit("git diff --name-only --cached"))) sets.add(f);
|
|
10924
|
+
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) sets.add(f);
|
|
10925
|
+
const baseline = readBaselineSha();
|
|
10926
|
+
if (baseline) {
|
|
10927
|
+
const committed = splitLines(execGit(`git diff --name-only ${baseline}..HEAD`));
|
|
10928
|
+
if (committed.length > 0) {
|
|
10929
|
+
hasRecentCommitFiles = true;
|
|
10930
|
+
for (const f of committed) sets.add(f);
|
|
10931
|
+
}
|
|
10932
|
+
} else {
|
|
10933
|
+
const headTimestamp = parseInt(execGit("git log -1 --format=%ct HEAD"), 10) || 0;
|
|
10934
|
+
const commitAge = Math.floor(Date.now() / 1e3) - headTimestamp;
|
|
10935
|
+
const hasUnstaged = splitLines(execGit("git diff --name-only HEAD")).length > 0;
|
|
10936
|
+
const hasStaged = splitLines(execGit("git diff --name-only --cached")).length > 0;
|
|
10937
|
+
if (commitAge < 120 && !hasUnstaged && !hasStaged) {
|
|
10938
|
+
const recentFiles = splitLines(execGit("git diff --name-only HEAD~1..HEAD"));
|
|
10939
|
+
if (recentFiles.length > 0) {
|
|
10940
|
+
hasRecentCommitFiles = true;
|
|
10941
|
+
for (const f of recentFiles) sets.add(f);
|
|
10942
|
+
}
|
|
10943
|
+
}
|
|
10944
|
+
}
|
|
10945
|
+
for (const f of getWorktreeFiles()) sets.add(f);
|
|
10946
|
+
const filtered = Array.from(sets).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
10947
|
+
return { files: filtered, hasRecentCommitFiles };
|
|
10948
|
+
}
|
|
10949
|
+
function getStagedFiles() {
|
|
10950
|
+
return splitLines(execGit("git diff --cached --name-only")).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
10951
|
+
}
|
|
10952
|
+
function getDirtyFiles() {
|
|
10953
|
+
const set = /* @__PURE__ */ new Set();
|
|
10954
|
+
for (const f of splitLines(execGit("git diff --name-only HEAD"))) set.add(f);
|
|
10955
|
+
for (const f of splitLines(execGit("git diff --name-only --cached"))) set.add(f);
|
|
10956
|
+
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
|
|
10957
|
+
return Array.from(set).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
10958
|
+
}
|
|
10959
|
+
function showContentAtRef(ref, repoRelPath) {
|
|
10960
|
+
if (!ref || ref === "no-git") return null;
|
|
10961
|
+
const normalizedPath = repoRelPath.replace(/\\/g, "/");
|
|
10962
|
+
try {
|
|
10963
|
+
return (0, import_node_child_process3.execFileSync)("git", ["show", `${ref}:${normalizedPath}`], {
|
|
10964
|
+
encoding: "utf-8",
|
|
10965
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
10966
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
10967
|
+
});
|
|
10968
|
+
} catch {
|
|
10969
|
+
return null;
|
|
10970
|
+
}
|
|
10971
|
+
}
|
|
10972
|
+
function getPushRangeFiles() {
|
|
10973
|
+
const diff = (range) => splitLines(execGit(`git diff --name-only ${range}`)).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
10974
|
+
const resolvers = [
|
|
10975
|
+
() => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{push}") ? "@{push}..HEAD" : null,
|
|
10976
|
+
() => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{upstream}") ? "@{upstream}..HEAD" : null,
|
|
10977
|
+
() => {
|
|
10978
|
+
const branch = execGit("git rev-parse --abbrev-ref HEAD");
|
|
10979
|
+
return branch && branch !== "HEAD" && execGit(`git rev-parse --verify -q origin/${branch}`) ? `origin/${branch}..HEAD` : null;
|
|
10980
|
+
}
|
|
10981
|
+
];
|
|
10982
|
+
for (const resolve of resolvers) {
|
|
10983
|
+
const range = resolve();
|
|
10984
|
+
if (range) return { files: diff(range), range };
|
|
10985
|
+
}
|
|
10986
|
+
const baseline = readBaselineSha();
|
|
10987
|
+
if (baseline) {
|
|
10988
|
+
const files = diff(`${baseline}..HEAD`);
|
|
10989
|
+
if (files.length > 0) return { files, range: `${baseline}..HEAD` };
|
|
10990
|
+
}
|
|
10991
|
+
const last = diff("HEAD~1..HEAD");
|
|
10992
|
+
return { files: last, range: last.length > 0 ? "HEAD~1..HEAD" : null };
|
|
10993
|
+
}
|
|
10994
|
+
function getPushRangeMessages() {
|
|
10995
|
+
const { range } = getPushRangeFiles();
|
|
10996
|
+
if (!range) return "";
|
|
10997
|
+
return execGit(`git log ${range} --format=%B%x00`).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
|
|
10998
|
+
}
|
|
10999
|
+
function getWorktreeFiles() {
|
|
11000
|
+
const result = [];
|
|
11001
|
+
const worktreeDir = ".claude/worktrees";
|
|
11002
|
+
if (!(0, import_node_fs2.existsSync)(worktreeDir)) return result;
|
|
11003
|
+
try {
|
|
11004
|
+
const fiveMinAgo = Date.now() - 5 * 60 * 1e3;
|
|
11005
|
+
const entries = (0, import_node_fs2.readdirSync)(worktreeDir, { withFileTypes: true });
|
|
11006
|
+
for (const entry of entries) {
|
|
11007
|
+
if (!entry.isDirectory()) continue;
|
|
11008
|
+
const wtDir = (0, import_node_path2.join)(worktreeDir, entry.name);
|
|
11009
|
+
scanDir(wtDir, wtDir, fiveMinAgo, result);
|
|
11010
|
+
}
|
|
11011
|
+
} catch {
|
|
11012
|
+
}
|
|
11013
|
+
return result;
|
|
11014
|
+
}
|
|
11015
|
+
function scanDir(baseDir, dir, minMtime, result) {
|
|
11016
|
+
try {
|
|
11017
|
+
const entries = (0, import_node_fs2.readdirSync)(dir, { withFileTypes: true });
|
|
11018
|
+
for (const entry of entries) {
|
|
11019
|
+
const fullPath = (0, import_node_path2.join)(dir, entry.name);
|
|
11020
|
+
if (entry.isDirectory()) {
|
|
11021
|
+
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
11022
|
+
scanDir(baseDir, fullPath, minMtime, result);
|
|
11023
|
+
} else if (entry.isFile()) {
|
|
11024
|
+
const ext = (0, import_node_path2.extname)(entry.name).slice(1);
|
|
11025
|
+
if (!ANALYZABLE_EXTENSIONS.has(ext)) continue;
|
|
11026
|
+
try {
|
|
11027
|
+
const stat3 = (0, import_node_fs2.statSync)(fullPath);
|
|
11028
|
+
if (stat3.mtimeMs >= minMtime) {
|
|
11029
|
+
const relPath = fullPath.slice(baseDir.length + 1);
|
|
11030
|
+
result.push(relPath);
|
|
11031
|
+
}
|
|
11032
|
+
} catch {
|
|
11033
|
+
}
|
|
11034
|
+
}
|
|
11035
|
+
}
|
|
11036
|
+
} catch {
|
|
11037
|
+
}
|
|
11038
|
+
}
|
|
11039
|
+
function filterAnalyzable(files) {
|
|
11040
|
+
return files.filter((f) => {
|
|
11041
|
+
const ext = (0, import_node_path2.extname)(f).slice(1);
|
|
11042
|
+
return ANALYZABLE_EXTENSIONS.has(ext);
|
|
11043
|
+
});
|
|
11044
|
+
}
|
|
11045
|
+
function filterReviewable(files) {
|
|
11046
|
+
return files.filter((f) => {
|
|
11047
|
+
const ext = (0, import_node_path2.extname)(f).slice(1);
|
|
11048
|
+
if (ANALYZABLE_EXTENSIONS.has(ext)) return false;
|
|
11049
|
+
if (REVIEWABLE_EXTENSIONS.has(ext)) return true;
|
|
11050
|
+
const basename3 = f.split("/").pop() ?? "";
|
|
11051
|
+
if (REVIEWABLE_FILENAMES.has(basename3)) return true;
|
|
11052
|
+
if (REVIEWABLE_PATH_PATTERNS.some((p) => p.test(f))) return true;
|
|
11053
|
+
return false;
|
|
11054
|
+
});
|
|
11055
|
+
}
|
|
11056
|
+
function filterSecurity(files) {
|
|
11057
|
+
return files.filter(
|
|
11058
|
+
(f) => SECURITY_PATTERNS.some((p) => p.test(f))
|
|
11059
|
+
);
|
|
11060
|
+
}
|
|
11061
|
+
function getCurrentCommit() {
|
|
11062
|
+
return execGit("git rev-parse HEAD") || "no-git";
|
|
11063
|
+
}
|
|
11064
|
+
function detectProvider(host) {
|
|
11065
|
+
const h = host.toLowerCase();
|
|
11066
|
+
if (h.includes("github")) return "github";
|
|
11067
|
+
if (h.includes("gitlab")) return "gitlab";
|
|
11068
|
+
if (h.includes("bitbucket")) return "bitbucket";
|
|
11069
|
+
return "unknown";
|
|
11070
|
+
}
|
|
11071
|
+
function parseRemote(raw) {
|
|
11072
|
+
if (!raw || typeof raw !== "string") return null;
|
|
11073
|
+
let s = raw.trim();
|
|
11074
|
+
if (!s) return null;
|
|
11075
|
+
let host = "";
|
|
11076
|
+
let path = "";
|
|
11077
|
+
const scp = s.match(/^[^/@]+@([^:/]+):(.+)$/);
|
|
11078
|
+
if (scp) {
|
|
11079
|
+
host = scp[1];
|
|
11080
|
+
path = scp[2];
|
|
11081
|
+
} else {
|
|
11082
|
+
s = s.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//, "");
|
|
11083
|
+
s = s.replace(/^[^/@]+@/, "");
|
|
11084
|
+
const slash = s.indexOf("/");
|
|
11085
|
+
if (slash === -1) return null;
|
|
11086
|
+
host = s.slice(0, slash);
|
|
11087
|
+
path = s.slice(slash + 1);
|
|
11088
|
+
}
|
|
11089
|
+
host = host.toLowerCase().trim();
|
|
11090
|
+
path = path.replace(/\/+$/, "").replace(/\.git$/, "");
|
|
11091
|
+
if (!host || !path) return null;
|
|
11092
|
+
const segments = path.split("/").filter(Boolean);
|
|
11093
|
+
if (segments.length < 2) return null;
|
|
11094
|
+
const owner = segments[0];
|
|
11095
|
+
const repo = segments[segments.length - 1];
|
|
11096
|
+
if (!owner || !repo) return null;
|
|
11097
|
+
return {
|
|
11098
|
+
host,
|
|
11099
|
+
owner,
|
|
11100
|
+
repo,
|
|
11101
|
+
provider: detectProvider(host),
|
|
11102
|
+
orgUrl: `https://${host}/${owner}`,
|
|
11103
|
+
orgName: owner
|
|
11104
|
+
};
|
|
11105
|
+
}
|
|
11106
|
+
function listTrackedFiles() {
|
|
11107
|
+
const set = /* @__PURE__ */ new Set();
|
|
11108
|
+
for (const f of splitLines(execGit("git ls-files"))) set.add(f);
|
|
11109
|
+
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
|
|
11110
|
+
return Array.from(set);
|
|
11111
|
+
}
|
|
11112
|
+
|
|
11113
|
+
// src/lib/register.ts
|
|
11114
|
+
var isInteractive = () => Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
11115
|
+
async function waitForEnter(prompt) {
|
|
11116
|
+
if (!isInteractive()) return;
|
|
11117
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
11118
|
+
try {
|
|
11119
|
+
await rl.question(prompt);
|
|
11120
|
+
} finally {
|
|
11121
|
+
rl.close();
|
|
11122
|
+
}
|
|
11123
|
+
}
|
|
11124
|
+
async function ensureAppInstalled(owner, repo, token, installUrl) {
|
|
11125
|
+
const notInstalledError = {
|
|
11126
|
+
ok: false,
|
|
11127
|
+
error: `The Verity GitHub App can't see ${owner}/${repo}. Install it on "${owner}" and grant access to this repository, then re-run:
|
|
11128
|
+
${installUrl}`
|
|
11129
|
+
};
|
|
11130
|
+
const maxAttempts = 3;
|
|
11131
|
+
for (let attempt = 1; ; attempt++) {
|
|
11132
|
+
const visible = await githubCanSeeRepo(owner, repo, token);
|
|
11133
|
+
if (!visible.ok) return { ok: true, data: void 0 };
|
|
11134
|
+
if (visible.data) return { ok: true, data: void 0 };
|
|
11135
|
+
if (!isInteractive() || attempt >= maxAttempts) return notInstalledError;
|
|
11136
|
+
printWarn(`The Verity GitHub App can't see ${owner}/${repo} yet.`);
|
|
11137
|
+
printInfo(`Install it on "${owner}" (grant access to this repo):`);
|
|
11138
|
+
printInfo(` ${installUrl}`);
|
|
11139
|
+
await waitForEnter("Press Enter once installed to retry\u2026 ");
|
|
11140
|
+
}
|
|
11141
|
+
}
|
|
11142
|
+
async function registerProject(opts) {
|
|
11143
|
+
const parsed = parseRemote(opts.remote);
|
|
11144
|
+
if (!parsed) {
|
|
11145
|
+
return { ok: false, error: `Could not parse git remote: ${opts.remote}` };
|
|
11146
|
+
}
|
|
11147
|
+
if (parsed.provider !== "github") {
|
|
11148
|
+
return { ok: false, error: `Provider '${parsed.provider}' is not supported yet \u2014 GitHub only for now.` };
|
|
11149
|
+
}
|
|
11150
|
+
const usingTokenOverride = Boolean(process.env.VERITY_PROVIDER_TOKEN);
|
|
11151
|
+
const ownerId = usingTokenOverride ? null : await githubAccountId(parsed.owner);
|
|
11152
|
+
const installUrl = githubAppInstallUrl(ownerId);
|
|
11153
|
+
if (!usingTokenOverride && isInteractive()) {
|
|
11154
|
+
printInfo("");
|
|
11155
|
+
printInfo(`Verity uses a read-only GitHub App to verify write access to ${parsed.owner}/${parsed.repo}.`);
|
|
11156
|
+
printInfo(`Install it on "${parsed.owner}" (grant access to this repo) if you haven't already:`);
|
|
11157
|
+
printInfo(` ${installUrl}`);
|
|
11158
|
+
await waitForEnter("Press Enter to continue to GitHub authorization\u2026 ");
|
|
11159
|
+
}
|
|
11160
|
+
const providerAuth = await githubDeviceFlow();
|
|
11161
|
+
if (!providerAuth.ok) {
|
|
11162
|
+
return { ok: false, error: providerAuth.error };
|
|
11163
|
+
}
|
|
11164
|
+
const providerToken = providerAuth.data;
|
|
11165
|
+
if (!usingTokenOverride) {
|
|
11166
|
+
const installed = await ensureAppInstalled(parsed.owner, parsed.repo, providerToken, installUrl);
|
|
11167
|
+
if (!installed.ok) {
|
|
11168
|
+
return { ok: false, error: installed.error };
|
|
11169
|
+
}
|
|
11170
|
+
}
|
|
11171
|
+
const result = await apiRequest({
|
|
11172
|
+
method: "POST",
|
|
11173
|
+
path: "/auth/register",
|
|
11174
|
+
serviceUrl: opts.serviceUrl,
|
|
11175
|
+
body: { project_name: opts.projectName, git_remote_url: opts.remote },
|
|
11176
|
+
extraHeaders: { "X-Provider-Token": providerToken },
|
|
11177
|
+
verbose: opts.verbose
|
|
11178
|
+
});
|
|
11179
|
+
if (!result.ok) {
|
|
11180
|
+
return { ok: false, error: result.error };
|
|
11181
|
+
}
|
|
11182
|
+
const { project_id, token, service_url, user } = result.data;
|
|
11183
|
+
try {
|
|
11184
|
+
await (0, import_promises3.mkdir)(VERITY_DIR, { recursive: true });
|
|
11185
|
+
await (0, import_promises3.writeFile)(
|
|
11186
|
+
CREDENTIALS_FILE,
|
|
11187
|
+
`token: ${token}
|
|
11188
|
+
service_url: ${service_url}
|
|
11189
|
+
`,
|
|
11190
|
+
{ mode: 384 }
|
|
11191
|
+
);
|
|
11192
|
+
await (0, import_promises3.chmod)(CREDENTIALS_FILE, 384).catch(() => {
|
|
11193
|
+
});
|
|
11194
|
+
} catch (err) {
|
|
11195
|
+
return {
|
|
11196
|
+
ok: false,
|
|
11197
|
+
error: `Registered with the Verity service, but could not save credentials to ${CREDENTIALS_FILE}: ${err.message}. Check filesystem permissions and re-run "verity auth register".`
|
|
11198
|
+
};
|
|
11199
|
+
}
|
|
11200
|
+
try {
|
|
11201
|
+
await (0, import_promises3.mkdir)((0, import_node_path3.dirname)(GLOBAL_CREDENTIALS_FILE), { recursive: true });
|
|
11202
|
+
await (0, import_promises3.appendFile)(
|
|
11203
|
+
GLOBAL_CREDENTIALS_FILE,
|
|
11204
|
+
`${opts.remote} token: ${token}
|
|
11205
|
+
`,
|
|
11206
|
+
{ mode: 384 }
|
|
11207
|
+
);
|
|
11208
|
+
await (0, import_promises3.chmod)(GLOBAL_CREDENTIALS_FILE, 384).catch(() => {
|
|
11209
|
+
});
|
|
11210
|
+
} catch {
|
|
11211
|
+
}
|
|
11212
|
+
return { ok: true, data: { projectId: project_id, serviceUrl: service_url, email: user?.email } };
|
|
11213
|
+
}
|
|
11214
|
+
|
|
10743
11215
|
// src/commands/auth.ts
|
|
10744
11216
|
function registerAuthCommands(program2) {
|
|
10745
11217
|
const auth = program2.command("auth").description("Manage project authentication");
|
|
@@ -10749,36 +11221,26 @@ function registerAuthCommands(program2) {
|
|
|
10749
11221
|
let remote = opts.remote;
|
|
10750
11222
|
if (!remote) {
|
|
10751
11223
|
try {
|
|
10752
|
-
remote = (0,
|
|
11224
|
+
remote = (0, import_node_child_process4.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
|
|
10753
11225
|
} catch {
|
|
10754
11226
|
printError("No git remote found. Use --remote to specify one.");
|
|
10755
11227
|
process.exit(1);
|
|
10756
11228
|
}
|
|
10757
11229
|
}
|
|
10758
|
-
const result = await
|
|
10759
|
-
|
|
10760
|
-
|
|
11230
|
+
const result = await registerProject({
|
|
11231
|
+
projectName: opts.project,
|
|
11232
|
+
remote,
|
|
10761
11233
|
serviceUrl,
|
|
10762
|
-
body: { project_name: opts.project, git_remote_url: remote },
|
|
10763
11234
|
verbose: globals.verbose
|
|
10764
11235
|
});
|
|
10765
|
-
if (!result.ok) {
|
|
10766
|
-
printError(result.error);
|
|
10767
|
-
process.exit(1);
|
|
10768
|
-
}
|
|
10769
|
-
const { project_id, token, service_url } = result.data;
|
|
10770
|
-
await (0, import_promises3.mkdir)(VERITY_DIR, { recursive: true });
|
|
10771
|
-
await (0, import_promises3.writeFile)(CREDENTIALS_FILE, `token: ${token}
|
|
10772
|
-
service_url: ${service_url}
|
|
10773
|
-
`);
|
|
10774
|
-
try {
|
|
10775
|
-
await (0, import_promises3.mkdir)((0, import_node_path2.dirname)(GLOBAL_CREDENTIALS_FILE), { recursive: true });
|
|
10776
|
-
await (0, import_promises3.appendFile)(GLOBAL_CREDENTIALS_FILE, `${remote} token: ${token}
|
|
10777
|
-
`);
|
|
10778
|
-
} catch {
|
|
11236
|
+
if (!result.ok) {
|
|
11237
|
+
printError(result.error);
|
|
11238
|
+
process.exit(1);
|
|
10779
11239
|
}
|
|
10780
|
-
|
|
10781
|
-
|
|
11240
|
+
const { projectId, serviceUrl: resolvedUrl, email } = result.data;
|
|
11241
|
+
printInfo(`Project registered: ${projectId}`);
|
|
11242
|
+
if (email) printInfo(`Authenticated as: ${email}`);
|
|
11243
|
+
printJson({ project_id: projectId, service_url: resolvedUrl });
|
|
10782
11244
|
});
|
|
10783
11245
|
auth.command("verify").description("Verify the current token is valid").action(async () => {
|
|
10784
11246
|
const globals = program2.opts();
|
|
@@ -10811,7 +11273,7 @@ service_url: ${service_url}
|
|
|
10811
11273
|
let remote = opts.remote;
|
|
10812
11274
|
if (!remote) {
|
|
10813
11275
|
try {
|
|
10814
|
-
remote = (0,
|
|
11276
|
+
remote = (0, import_node_child_process4.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
|
|
10815
11277
|
} catch {
|
|
10816
11278
|
printError("No git remote found. Use --remote to specify one.");
|
|
10817
11279
|
process.exit(1);
|
|
@@ -10839,11 +11301,11 @@ service_url: ${service_url}
|
|
|
10839
11301
|
|
|
10840
11302
|
// src/lib/hooks.ts
|
|
10841
11303
|
var import_promises5 = require("node:fs/promises");
|
|
10842
|
-
var
|
|
11304
|
+
var import_node_path5 = require("node:path");
|
|
10843
11305
|
|
|
10844
11306
|
// src/lib/json-file.ts
|
|
10845
11307
|
var import_promises4 = require("node:fs/promises");
|
|
10846
|
-
var
|
|
11308
|
+
var import_node_path4 = require("node:path");
|
|
10847
11309
|
function jsonSemanticEqual(a, b) {
|
|
10848
11310
|
if (a === b) return true;
|
|
10849
11311
|
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) {
|
|
@@ -10889,7 +11351,7 @@ async function writeJsonFilePreservingStyle(file, value) {
|
|
|
10889
11351
|
const indent = currentRaw !== null ? detectJsonIndent(currentRaw) : 2;
|
|
10890
11352
|
const next = JSON.stringify(value, null, indent) + "\n";
|
|
10891
11353
|
if (next === currentRaw) return false;
|
|
10892
|
-
await (0, import_promises4.mkdir)((0,
|
|
11354
|
+
await (0, import_promises4.mkdir)((0, import_node_path4.dirname)(file), { recursive: true });
|
|
10893
11355
|
await (0, import_promises4.writeFile)(file, next);
|
|
10894
11356
|
return true;
|
|
10895
11357
|
}
|
|
@@ -11050,13 +11512,13 @@ async function writeSettings(settings) {
|
|
|
11050
11512
|
}
|
|
11051
11513
|
async function readSettingsAt(root) {
|
|
11052
11514
|
try {
|
|
11053
|
-
return JSON.parse(await (0, import_promises5.readFile)((0,
|
|
11515
|
+
return JSON.parse(await (0, import_promises5.readFile)((0, import_node_path5.join)(root, CLAUDE_SETTINGS_FILE), "utf-8"));
|
|
11054
11516
|
} catch {
|
|
11055
11517
|
return {};
|
|
11056
11518
|
}
|
|
11057
11519
|
}
|
|
11058
11520
|
async function writeSettingsAt(root, settings) {
|
|
11059
|
-
await writeJsonFilePreservingStyle((0,
|
|
11521
|
+
await writeJsonFilePreservingStyle((0, import_node_path5.join)(root, CLAUDE_SETTINGS_FILE), settings);
|
|
11060
11522
|
}
|
|
11061
11523
|
async function hasLegacyHooksAt(root) {
|
|
11062
11524
|
const settings = await readSettingsAt(root);
|
|
@@ -11296,8 +11758,8 @@ var import_node_crypto3 = require("node:crypto");
|
|
|
11296
11758
|
|
|
11297
11759
|
// src/lib/conversation-buffer.ts
|
|
11298
11760
|
var import_promises6 = require("node:fs/promises");
|
|
11299
|
-
var
|
|
11300
|
-
var
|
|
11761
|
+
var import_node_fs3 = require("node:fs");
|
|
11762
|
+
var import_node_child_process5 = require("node:child_process");
|
|
11301
11763
|
var import_node_crypto = require("node:crypto");
|
|
11302
11764
|
function stripImageReferences(text) {
|
|
11303
11765
|
return text.replace(/\[Image #\d+\]/g, "[screenshot \u2014 not available for review]");
|
|
@@ -11332,7 +11794,7 @@ async function appendToConversationBuffer(prompt, sessionId) {
|
|
|
11332
11794
|
}
|
|
11333
11795
|
async function readAndClearConversationBuffer(currentSessionId) {
|
|
11334
11796
|
try {
|
|
11335
|
-
if ((0,
|
|
11797
|
+
if ((0, import_node_fs3.existsSync)(CONVERSATION_BUFFER_FILE)) {
|
|
11336
11798
|
const entries = await readBufferEntries();
|
|
11337
11799
|
let mine = entries;
|
|
11338
11800
|
let others = [];
|
|
@@ -11356,7 +11818,7 @@ async function readAndClearConversationBuffer(currentSessionId) {
|
|
|
11356
11818
|
};
|
|
11357
11819
|
}
|
|
11358
11820
|
}
|
|
11359
|
-
if ((0,
|
|
11821
|
+
if ((0, import_node_fs3.existsSync)(INTENT_FILE)) {
|
|
11360
11822
|
try {
|
|
11361
11823
|
const content = await (0, import_promises6.readFile)(INTENT_FILE, "utf-8");
|
|
11362
11824
|
await (0, import_promises6.unlink)(INTENT_FILE).catch(() => {
|
|
@@ -11400,7 +11862,7 @@ async function readBufferEntries() {
|
|
|
11400
11862
|
}
|
|
11401
11863
|
function getRecentCommitMessages() {
|
|
11402
11864
|
try {
|
|
11403
|
-
const output = (0,
|
|
11865
|
+
const output = (0, import_node_child_process5.execSync)(
|
|
11404
11866
|
'git log --since="30 minutes ago" --format="%s" -5',
|
|
11405
11867
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
11406
11868
|
).trim();
|
|
@@ -11413,8 +11875,8 @@ function getRecentCommitMessages() {
|
|
|
11413
11875
|
|
|
11414
11876
|
// src/lib/task-context-buffer.ts
|
|
11415
11877
|
var import_promises7 = require("node:fs/promises");
|
|
11416
|
-
var
|
|
11417
|
-
var
|
|
11878
|
+
var import_node_fs4 = require("node:fs");
|
|
11879
|
+
var import_node_path6 = require("node:path");
|
|
11418
11880
|
var TASK_CONTEXT_DIR = `${VERITY_DIR}/.task-context`;
|
|
11419
11881
|
var MAX_BUFFER_BYTES = 500 * 1024;
|
|
11420
11882
|
var MAX_PROMPT_CHARS = 2e3;
|
|
@@ -11453,7 +11915,7 @@ async function appendResponseToTaskBuffer(taskId, assistantResponse, actionSumma
|
|
|
11453
11915
|
}
|
|
11454
11916
|
async function readTaskContextBuffer(taskId) {
|
|
11455
11917
|
const filePath = bufferPath(taskId);
|
|
11456
|
-
if (!(0,
|
|
11918
|
+
if (!(0, import_node_fs4.existsSync)(filePath)) return null;
|
|
11457
11919
|
try {
|
|
11458
11920
|
const content = await (0, import_promises7.readFile)(filePath, "utf-8");
|
|
11459
11921
|
if (!content.trim()) return null;
|
|
@@ -11487,12 +11949,12 @@ async function readTaskContextBuffer(taskId) {
|
|
|
11487
11949
|
}
|
|
11488
11950
|
async function cleanupTaskContextBuffers() {
|
|
11489
11951
|
try {
|
|
11490
|
-
if (!(0,
|
|
11952
|
+
if (!(0, import_node_fs4.existsSync)(TASK_CONTEXT_DIR)) return;
|
|
11491
11953
|
const files = await (0, import_promises7.readdir)(TASK_CONTEXT_DIR);
|
|
11492
11954
|
const cutoffMs = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
11493
11955
|
for (const file of files) {
|
|
11494
11956
|
if (!file.endsWith(".jsonl")) continue;
|
|
11495
|
-
const filePath = (0,
|
|
11957
|
+
const filePath = (0, import_node_path6.join)(TASK_CONTEXT_DIR, file);
|
|
11496
11958
|
try {
|
|
11497
11959
|
const stats = await (0, import_promises7.stat)(filePath);
|
|
11498
11960
|
if (stats.mtimeMs < cutoffMs) {
|
|
@@ -11506,13 +11968,13 @@ async function cleanupTaskContextBuffers() {
|
|
|
11506
11968
|
}
|
|
11507
11969
|
function bufferPath(taskId) {
|
|
11508
11970
|
const safe = taskId.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
11509
|
-
return (0,
|
|
11971
|
+
return (0, import_node_path6.join)(TASK_CONTEXT_DIR, `${safe}.jsonl`);
|
|
11510
11972
|
}
|
|
11511
11973
|
async function appendEntry(taskId, entry) {
|
|
11512
11974
|
try {
|
|
11513
11975
|
await (0, import_promises7.mkdir)(TASK_CONTEXT_DIR, { recursive: true });
|
|
11514
11976
|
const filePath = bufferPath(taskId);
|
|
11515
|
-
if ((0,
|
|
11977
|
+
if ((0, import_node_fs4.existsSync)(filePath)) {
|
|
11516
11978
|
const stats = await (0, import_promises7.stat)(filePath);
|
|
11517
11979
|
if (stats.size >= MAX_BUFFER_BYTES) {
|
|
11518
11980
|
const content = await (0, import_promises7.readFile)(filePath, "utf-8");
|
|
@@ -11523,7 +11985,7 @@ async function appendEntry(taskId, entry) {
|
|
|
11523
11985
|
}
|
|
11524
11986
|
}
|
|
11525
11987
|
const line = JSON.stringify(entry) + "\n";
|
|
11526
|
-
const existing = (0,
|
|
11988
|
+
const existing = (0, import_node_fs4.existsSync)(filePath) ? await (0, import_promises7.readFile)(filePath, "utf-8") : "";
|
|
11527
11989
|
await (0, import_promises7.writeFile)(filePath, existing + line);
|
|
11528
11990
|
} catch {
|
|
11529
11991
|
}
|
|
@@ -11531,8 +11993,8 @@ async function appendEntry(taskId, entry) {
|
|
|
11531
11993
|
|
|
11532
11994
|
// src/lib/memory-retrieval.ts
|
|
11533
11995
|
var import_promises8 = require("node:fs/promises");
|
|
11534
|
-
var
|
|
11535
|
-
var
|
|
11996
|
+
var import_node_fs5 = require("node:fs");
|
|
11997
|
+
var import_node_path7 = require("node:path");
|
|
11536
11998
|
var memoryDir = () => projectPath(`${VERITY_DIR}/memory`);
|
|
11537
11999
|
var DOMAINS = ["decisions", "quality", "security", "intent", "gotchas", "patterns", "domain", "integrations"];
|
|
11538
12000
|
var DEFAULT_BUDGET_TOKENS = 2e3;
|
|
@@ -11625,19 +12087,19 @@ function parseFrontmatter(content) {
|
|
|
11625
12087
|
return { fm, body: match[2].trim() };
|
|
11626
12088
|
}
|
|
11627
12089
|
async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = DEFAULT_BUDGET_TOKENS) {
|
|
11628
|
-
if (!(0,
|
|
12090
|
+
if (!(0, import_node_fs5.existsSync)(memoryDir())) return null;
|
|
11629
12091
|
const budget = Math.min(budgetTokens, MAX_BUDGET_TOKENS);
|
|
11630
12092
|
const promptTokens = tokenize(promptText);
|
|
11631
12093
|
const nodes = [];
|
|
11632
12094
|
for (const domain of DOMAINS) {
|
|
11633
|
-
const domainDir = (0,
|
|
11634
|
-
if (!(0,
|
|
12095
|
+
const domainDir = (0, import_node_path7.join)(memoryDir(), domain);
|
|
12096
|
+
if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
|
|
11635
12097
|
try {
|
|
11636
12098
|
const files = await (0, import_promises8.readdir)(domainDir);
|
|
11637
12099
|
for (const file of files) {
|
|
11638
12100
|
if (!file.endsWith(".md")) continue;
|
|
11639
12101
|
try {
|
|
11640
|
-
const content = await (0, import_promises8.readFile)((0,
|
|
12102
|
+
const content = await (0, import_promises8.readFile)((0, import_node_path7.join)(domainDir, file), "utf-8");
|
|
11641
12103
|
const { fm, body } = parseFrontmatter(content);
|
|
11642
12104
|
if (fm.status && fm.status !== "active") continue;
|
|
11643
12105
|
nodes.push({
|
|
@@ -11696,8 +12158,8 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
|
|
|
11696
12158
|
|
|
11697
12159
|
// src/lib/memory-sync.ts
|
|
11698
12160
|
var import_promises9 = require("node:fs/promises");
|
|
11699
|
-
var
|
|
11700
|
-
var
|
|
12161
|
+
var import_node_fs6 = require("node:fs");
|
|
12162
|
+
var import_node_path8 = require("node:path");
|
|
11701
12163
|
var import_node_crypto2 = require("node:crypto");
|
|
11702
12164
|
|
|
11703
12165
|
// src/lib/glob-match.ts
|
|
@@ -11768,32 +12230,32 @@ var syncStateFile = () => projectPath(`${VERITY_DIR}/.memory-sync-state.json`);
|
|
|
11768
12230
|
async function ensureMemoryDir() {
|
|
11769
12231
|
await (0, import_promises9.mkdir)(memoryDir2(), { recursive: true });
|
|
11770
12232
|
for (const domain of DOMAINS2) {
|
|
11771
|
-
await (0, import_promises9.mkdir)((0,
|
|
12233
|
+
await (0, import_promises9.mkdir)((0, import_node_path8.join)(memoryDir2(), domain), { recursive: true });
|
|
11772
12234
|
}
|
|
11773
|
-
if (!(0,
|
|
11774
|
-
await (0, import_promises9.writeFile)((0,
|
|
12235
|
+
if (!(0, import_node_fs6.existsSync)((0, import_node_path8.join)(memoryDir2(), "SCHEMA.md"))) {
|
|
12236
|
+
await (0, import_promises9.writeFile)((0, import_node_path8.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
|
|
11775
12237
|
}
|
|
11776
|
-
if (!(0,
|
|
11777
|
-
await (0, import_promises9.writeFile)((0,
|
|
12238
|
+
if (!(0, import_node_fs6.existsSync)((0, import_node_path8.join)(memoryDir2(), "index.md"))) {
|
|
12239
|
+
await (0, import_promises9.writeFile)((0, import_node_path8.join)(memoryDir2(), "index.md"), "# Project Memory Index\n\nNo nodes yet. Run an analysis to start building the knowledge graph.\n");
|
|
11778
12240
|
}
|
|
11779
|
-
if (!(0,
|
|
11780
|
-
await (0, import_promises9.writeFile)((0,
|
|
12241
|
+
if (!(0, import_node_fs6.existsSync)((0, import_node_path8.join)(memoryDir2(), "log.md"))) {
|
|
12242
|
+
await (0, import_promises9.writeFile)((0, import_node_path8.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
|
|
11781
12243
|
}
|
|
11782
12244
|
}
|
|
11783
12245
|
async function buildManifest() {
|
|
11784
|
-
if (!(0,
|
|
12246
|
+
if (!(0, import_node_fs6.existsSync)(memoryDir2())) {
|
|
11785
12247
|
return { schema_version: 1, nodes: [], index_hash: null, log_length: 0 };
|
|
11786
12248
|
}
|
|
11787
12249
|
const nodes = [];
|
|
11788
12250
|
for (const domain of DOMAINS2) {
|
|
11789
|
-
const domainDir = (0,
|
|
11790
|
-
if (!(0,
|
|
12251
|
+
const domainDir = (0, import_node_path8.join)(memoryDir2(), domain);
|
|
12252
|
+
if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
|
|
11791
12253
|
try {
|
|
11792
12254
|
const files = await (0, import_promises9.readdir)(domainDir);
|
|
11793
12255
|
for (const file of files) {
|
|
11794
12256
|
if (!file.endsWith(".md")) continue;
|
|
11795
12257
|
const filePath = `${domain}/${file}`;
|
|
11796
|
-
const fullPath = (0,
|
|
12258
|
+
const fullPath = (0, import_node_path8.join)(memoryDir2(), filePath);
|
|
11797
12259
|
try {
|
|
11798
12260
|
const content = await (0, import_promises9.readFile)(fullPath, "utf-8");
|
|
11799
12261
|
const hash = (0, import_node_crypto2.createHash)("sha256").update(content).digest("hex").slice(0, 16);
|
|
@@ -11806,13 +12268,13 @@ async function buildManifest() {
|
|
|
11806
12268
|
}
|
|
11807
12269
|
let indexHash = null;
|
|
11808
12270
|
try {
|
|
11809
|
-
const indexContent = await (0, import_promises9.readFile)((0,
|
|
12271
|
+
const indexContent = await (0, import_promises9.readFile)((0, import_node_path8.join)(memoryDir2(), "index.md"), "utf-8");
|
|
11810
12272
|
indexHash = `sha256:${(0, import_node_crypto2.createHash)("sha256").update(indexContent).digest("hex").slice(0, 16)}`;
|
|
11811
12273
|
} catch {
|
|
11812
12274
|
}
|
|
11813
12275
|
let logLength = 0;
|
|
11814
12276
|
try {
|
|
11815
|
-
const logContent = await (0, import_promises9.readFile)((0,
|
|
12277
|
+
const logContent = await (0, import_promises9.readFile)((0, import_node_path8.join)(memoryDir2(), "log.md"), "utf-8");
|
|
11816
12278
|
logLength = logContent.split("\n").length;
|
|
11817
12279
|
} catch {
|
|
11818
12280
|
}
|
|
@@ -11823,15 +12285,15 @@ function hashContent(content) {
|
|
|
11823
12285
|
}
|
|
11824
12286
|
async function readOnDiskNodes() {
|
|
11825
12287
|
const out = /* @__PURE__ */ new Map();
|
|
11826
|
-
if (!(0,
|
|
12288
|
+
if (!(0, import_node_fs6.existsSync)(memoryDir2())) return out;
|
|
11827
12289
|
for (const domain of DOMAINS2) {
|
|
11828
|
-
const domainDir = (0,
|
|
11829
|
-
if (!(0,
|
|
12290
|
+
const domainDir = (0, import_node_path8.join)(memoryDir2(), domain);
|
|
12291
|
+
if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
|
|
11830
12292
|
try {
|
|
11831
12293
|
for (const file of await (0, import_promises9.readdir)(domainDir)) {
|
|
11832
12294
|
if (!file.endsWith(".md")) continue;
|
|
11833
12295
|
try {
|
|
11834
|
-
out.set(`${domain}/${file}`, hashContent(await (0, import_promises9.readFile)((0,
|
|
12296
|
+
out.set(`${domain}/${file}`, hashContent(await (0, import_promises9.readFile)((0, import_node_path8.join)(domainDir, file), "utf-8")));
|
|
11835
12297
|
} catch {
|
|
11836
12298
|
}
|
|
11837
12299
|
}
|
|
@@ -11877,8 +12339,8 @@ async function computeEditedNodeUploads() {
|
|
|
11877
12339
|
const uploads = [];
|
|
11878
12340
|
for (const [path, prevHash] of prev) {
|
|
11879
12341
|
if (prevHash == null) continue;
|
|
11880
|
-
const full = (0,
|
|
11881
|
-
if (!(0,
|
|
12342
|
+
const full = (0, import_node_path8.join)(memoryDir2(), path);
|
|
12343
|
+
if (!(0, import_node_fs6.existsSync)(full)) continue;
|
|
11882
12344
|
let content;
|
|
11883
12345
|
try {
|
|
11884
12346
|
content = await (0, import_promises9.readFile)(full, "utf-8");
|
|
@@ -11914,15 +12376,15 @@ async function applyMemoryWrites(writes, opts = {}) {
|
|
|
11914
12376
|
const logLines = [`- ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)} \u2014 Applied ${count} write(s) from server`];
|
|
11915
12377
|
for (const n of notes) logLines.push(` - ${n}`);
|
|
11916
12378
|
try {
|
|
11917
|
-
const existing = (0,
|
|
11918
|
-
await (0, import_promises9.writeFile)((0,
|
|
12379
|
+
const existing = (0, import_node_fs6.existsSync)((0, import_node_path8.join)(memoryDir2(), "log.md")) ? await (0, import_promises9.readFile)((0, import_node_path8.join)(memoryDir2(), "log.md"), "utf-8") : "# Memory Log\n\n";
|
|
12380
|
+
await (0, import_promises9.writeFile)((0, import_node_path8.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
|
|
11919
12381
|
} catch {
|
|
11920
12382
|
}
|
|
11921
12383
|
await recordSyncedNodePaths();
|
|
11922
12384
|
return count;
|
|
11923
12385
|
}
|
|
11924
12386
|
async function applyOneWrite(write, treePaths) {
|
|
11925
|
-
const fullPath = (0,
|
|
12387
|
+
const fullPath = (0, import_node_path8.join)(memoryDir2(), write.path);
|
|
11926
12388
|
const notes = [];
|
|
11927
12389
|
let content = write.content;
|
|
11928
12390
|
if (treePaths && treePaths.length > 0) {
|
|
@@ -11932,7 +12394,7 @@ async function applyOneWrite(write, treePaths) {
|
|
|
11932
12394
|
notes.push(`${write.path}: dropped unmatched file_globs [${grounded.dropped.join(", ")}]`);
|
|
11933
12395
|
}
|
|
11934
12396
|
}
|
|
11935
|
-
if ((0,
|
|
12397
|
+
if ((0, import_node_fs6.existsSync)(fullPath)) {
|
|
11936
12398
|
let existing = "";
|
|
11937
12399
|
try {
|
|
11938
12400
|
existing = await (0, import_promises9.readFile)(fullPath, "utf-8");
|
|
@@ -11944,7 +12406,7 @@ async function applyOneWrite(write, treePaths) {
|
|
|
11944
12406
|
return { written: false, notes };
|
|
11945
12407
|
}
|
|
11946
12408
|
}
|
|
11947
|
-
await (0, import_promises9.mkdir)((0,
|
|
12409
|
+
await (0, import_promises9.mkdir)((0, import_node_path8.dirname)(fullPath), { recursive: true });
|
|
11948
12410
|
await (0, import_promises9.writeFile)(fullPath, content);
|
|
11949
12411
|
return { written: true, notes };
|
|
11950
12412
|
}
|
|
@@ -11985,8 +12447,8 @@ async function regenerateIndex() {
|
|
|
11985
12447
|
];
|
|
11986
12448
|
let totalNodes = 0;
|
|
11987
12449
|
for (const domain of DOMAINS2.filter((d) => d !== "_archive")) {
|
|
11988
|
-
const domainDir = (0,
|
|
11989
|
-
if (!(0,
|
|
12450
|
+
const domainDir = (0, import_node_path8.join)(memoryDir2(), domain);
|
|
12451
|
+
if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
|
|
11990
12452
|
try {
|
|
11991
12453
|
const files = await (0, import_promises9.readdir)(domainDir);
|
|
11992
12454
|
const mdFiles = files.filter((f) => f.endsWith(".md"));
|
|
@@ -11996,7 +12458,7 @@ async function regenerateIndex() {
|
|
|
11996
12458
|
for (const file of mdFiles.sort()) {
|
|
11997
12459
|
const slug = file.replace(/\.md$/, "");
|
|
11998
12460
|
try {
|
|
11999
|
-
const content = await (0, import_promises9.readFile)((0,
|
|
12461
|
+
const content = await (0, import_promises9.readFile)((0, import_node_path8.join)(domainDir, file), "utf-8");
|
|
12000
12462
|
const title = pickFrontmatter(content, "title") ?? slug;
|
|
12001
12463
|
const kind = pickFrontmatter(content, "kind") ?? "-";
|
|
12002
12464
|
const confidence = pickFrontmatter(content, "confidence");
|
|
@@ -12020,7 +12482,7 @@ async function regenerateIndex() {
|
|
|
12020
12482
|
lines.push("No nodes yet. Run an analysis to start building the knowledge graph.");
|
|
12021
12483
|
}
|
|
12022
12484
|
const next = lines.join("\n") + "\n";
|
|
12023
|
-
const indexPath = (0,
|
|
12485
|
+
const indexPath = (0, import_node_path8.join)(memoryDir2(), "index.md");
|
|
12024
12486
|
let existing = null;
|
|
12025
12487
|
try {
|
|
12026
12488
|
existing = await (0, import_promises9.readFile)(indexPath, "utf-8");
|
|
@@ -12102,9 +12564,9 @@ function hasLegacyMemoryBlock(text) {
|
|
|
12102
12564
|
return findMarker(text, LEGACY_MD_START) !== -1;
|
|
12103
12565
|
}
|
|
12104
12566
|
async function ensureClaudeMdPointer(cwd = repoRoot()) {
|
|
12105
|
-
const claudeMdPath = (0,
|
|
12567
|
+
const claudeMdPath = (0, import_node_path8.join)(cwd, "CLAUDE.md");
|
|
12106
12568
|
let existing = "";
|
|
12107
|
-
if ((0,
|
|
12569
|
+
if ((0, import_node_fs6.existsSync)(claudeMdPath)) {
|
|
12108
12570
|
existing = await (0, import_promises9.readFile)(claudeMdPath, "utf-8");
|
|
12109
12571
|
}
|
|
12110
12572
|
let startTag = CLAUDE_MD_START;
|
|
@@ -12234,7 +12696,7 @@ Body content (\u22648KB). Use [[node-id]] wikilinks for cross-references.
|
|
|
12234
12696
|
`;
|
|
12235
12697
|
|
|
12236
12698
|
// src/commands/intent.ts
|
|
12237
|
-
var
|
|
12699
|
+
var import_node_fs7 = require("node:fs");
|
|
12238
12700
|
function registerIntentCommands(program2) {
|
|
12239
12701
|
const intent = program2.command("intent").description("Manage intent capture");
|
|
12240
12702
|
intent.command("capture").description("Capture user intent from stdin (used by UserPromptSubmit hook)").action(async () => {
|
|
@@ -12243,7 +12705,7 @@ function registerIntentCommands(program2) {
|
|
|
12243
12705
|
process.chdir(repoRoot());
|
|
12244
12706
|
} catch {
|
|
12245
12707
|
}
|
|
12246
|
-
if (!(0,
|
|
12708
|
+
if (!(0, import_node_fs7.existsSync)(VERITY_DIR)) {
|
|
12247
12709
|
process.exit(0);
|
|
12248
12710
|
}
|
|
12249
12711
|
const chunks = [];
|
|
@@ -12822,215 +13284,6 @@ async function sendGeneralFeedback(message, opts, globals) {
|
|
|
12822
13284
|
var import_node_fs19 = require("node:fs");
|
|
12823
13285
|
var import_node_path15 = require("node:path");
|
|
12824
13286
|
|
|
12825
|
-
// src/lib/git.ts
|
|
12826
|
-
var import_node_child_process5 = require("node:child_process");
|
|
12827
|
-
var import_node_fs7 = require("node:fs");
|
|
12828
|
-
var import_node_path8 = require("node:path");
|
|
12829
|
-
function resolveFile(relpath) {
|
|
12830
|
-
if ((0, import_node_fs7.existsSync)(relpath)) return relpath;
|
|
12831
|
-
if ((0, import_node_fs7.existsSync)(".claude/worktrees")) {
|
|
12832
|
-
try {
|
|
12833
|
-
const entries = (0, import_node_fs7.readdirSync)(".claude/worktrees", { withFileTypes: true });
|
|
12834
|
-
for (const entry of entries) {
|
|
12835
|
-
if (!entry.isDirectory()) continue;
|
|
12836
|
-
const candidate = (0, import_node_path8.join)(".claude/worktrees", entry.name, relpath);
|
|
12837
|
-
if ((0, import_node_fs7.existsSync)(candidate)) return candidate;
|
|
12838
|
-
}
|
|
12839
|
-
} catch {
|
|
12840
|
-
}
|
|
12841
|
-
}
|
|
12842
|
-
return null;
|
|
12843
|
-
}
|
|
12844
|
-
function execGit(cmd) {
|
|
12845
|
-
try {
|
|
12846
|
-
return (0, import_node_child_process5.execSync)(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
12847
|
-
} catch {
|
|
12848
|
-
return "";
|
|
12849
|
-
}
|
|
12850
|
-
}
|
|
12851
|
-
function splitLines(s) {
|
|
12852
|
-
return s.split("\n").filter((l) => l.length > 0);
|
|
12853
|
-
}
|
|
12854
|
-
var SHA_RE = /^[0-9a-f]{40}$/;
|
|
12855
|
-
function readBaselineSha() {
|
|
12856
|
-
if (!(0, import_node_fs7.existsSync)(BASELINE_SHA_FILE)) return null;
|
|
12857
|
-
let sha;
|
|
12858
|
-
try {
|
|
12859
|
-
sha = (0, import_node_fs7.readFileSync)(BASELINE_SHA_FILE, "utf-8").trim();
|
|
12860
|
-
} catch {
|
|
12861
|
-
return null;
|
|
12862
|
-
}
|
|
12863
|
-
if (!SHA_RE.test(sha)) return null;
|
|
12864
|
-
const reachable = execGit(`git cat-file -e ${sha}^{commit} 2>/dev/null && echo ok`) === "ok";
|
|
12865
|
-
if (!reachable) {
|
|
12866
|
-
try {
|
|
12867
|
-
(0, import_node_fs7.unlinkSync)(BASELINE_SHA_FILE);
|
|
12868
|
-
} catch {
|
|
12869
|
-
}
|
|
12870
|
-
return null;
|
|
12871
|
-
}
|
|
12872
|
-
return sha;
|
|
12873
|
-
}
|
|
12874
|
-
function writeBaselineSha(sha) {
|
|
12875
|
-
if (!SHA_RE.test(sha)) return;
|
|
12876
|
-
try {
|
|
12877
|
-
(0, import_node_fs7.mkdirSync)((0, import_node_path8.dirname)(BASELINE_SHA_FILE), { recursive: true });
|
|
12878
|
-
(0, import_node_fs7.writeFileSync)(BASELINE_SHA_FILE, sha);
|
|
12879
|
-
} catch {
|
|
12880
|
-
}
|
|
12881
|
-
}
|
|
12882
|
-
function getChangedFiles() {
|
|
12883
|
-
const sets = /* @__PURE__ */ new Set();
|
|
12884
|
-
let hasRecentCommitFiles = false;
|
|
12885
|
-
for (const f of splitLines(execGit("git diff --name-only HEAD"))) sets.add(f);
|
|
12886
|
-
for (const f of splitLines(execGit("git diff --name-only --cached"))) sets.add(f);
|
|
12887
|
-
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) sets.add(f);
|
|
12888
|
-
const baseline = readBaselineSha();
|
|
12889
|
-
if (baseline) {
|
|
12890
|
-
const committed = splitLines(execGit(`git diff --name-only ${baseline}..HEAD`));
|
|
12891
|
-
if (committed.length > 0) {
|
|
12892
|
-
hasRecentCommitFiles = true;
|
|
12893
|
-
for (const f of committed) sets.add(f);
|
|
12894
|
-
}
|
|
12895
|
-
} else {
|
|
12896
|
-
const headTimestamp = parseInt(execGit("git log -1 --format=%ct HEAD"), 10) || 0;
|
|
12897
|
-
const commitAge = Math.floor(Date.now() / 1e3) - headTimestamp;
|
|
12898
|
-
const hasUnstaged = splitLines(execGit("git diff --name-only HEAD")).length > 0;
|
|
12899
|
-
const hasStaged = splitLines(execGit("git diff --name-only --cached")).length > 0;
|
|
12900
|
-
if (commitAge < 120 && !hasUnstaged && !hasStaged) {
|
|
12901
|
-
const recentFiles = splitLines(execGit("git diff --name-only HEAD~1..HEAD"));
|
|
12902
|
-
if (recentFiles.length > 0) {
|
|
12903
|
-
hasRecentCommitFiles = true;
|
|
12904
|
-
for (const f of recentFiles) sets.add(f);
|
|
12905
|
-
}
|
|
12906
|
-
}
|
|
12907
|
-
}
|
|
12908
|
-
for (const f of getWorktreeFiles()) sets.add(f);
|
|
12909
|
-
const filtered = Array.from(sets).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
12910
|
-
return { files: filtered, hasRecentCommitFiles };
|
|
12911
|
-
}
|
|
12912
|
-
function getStagedFiles() {
|
|
12913
|
-
return splitLines(execGit("git diff --cached --name-only")).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
12914
|
-
}
|
|
12915
|
-
function getDirtyFiles() {
|
|
12916
|
-
const set = /* @__PURE__ */ new Set();
|
|
12917
|
-
for (const f of splitLines(execGit("git diff --name-only HEAD"))) set.add(f);
|
|
12918
|
-
for (const f of splitLines(execGit("git diff --name-only --cached"))) set.add(f);
|
|
12919
|
-
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
|
|
12920
|
-
return Array.from(set).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
12921
|
-
}
|
|
12922
|
-
function showContentAtRef(ref, repoRelPath) {
|
|
12923
|
-
if (!ref || ref === "no-git") return null;
|
|
12924
|
-
const normalizedPath = repoRelPath.replace(/\\/g, "/");
|
|
12925
|
-
try {
|
|
12926
|
-
return (0, import_node_child_process5.execFileSync)("git", ["show", `${ref}:${normalizedPath}`], {
|
|
12927
|
-
encoding: "utf-8",
|
|
12928
|
-
maxBuffer: 64 * 1024 * 1024,
|
|
12929
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
12930
|
-
});
|
|
12931
|
-
} catch {
|
|
12932
|
-
return null;
|
|
12933
|
-
}
|
|
12934
|
-
}
|
|
12935
|
-
function getPushRangeFiles() {
|
|
12936
|
-
const diff = (range) => splitLines(execGit(`git diff --name-only ${range}`)).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
12937
|
-
const resolvers = [
|
|
12938
|
-
() => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{push}") ? "@{push}..HEAD" : null,
|
|
12939
|
-
() => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{upstream}") ? "@{upstream}..HEAD" : null,
|
|
12940
|
-
() => {
|
|
12941
|
-
const branch = execGit("git rev-parse --abbrev-ref HEAD");
|
|
12942
|
-
return branch && branch !== "HEAD" && execGit(`git rev-parse --verify -q origin/${branch}`) ? `origin/${branch}..HEAD` : null;
|
|
12943
|
-
}
|
|
12944
|
-
];
|
|
12945
|
-
for (const resolve of resolvers) {
|
|
12946
|
-
const range = resolve();
|
|
12947
|
-
if (range) return { files: diff(range), range };
|
|
12948
|
-
}
|
|
12949
|
-
const baseline = readBaselineSha();
|
|
12950
|
-
if (baseline) {
|
|
12951
|
-
const files = diff(`${baseline}..HEAD`);
|
|
12952
|
-
if (files.length > 0) return { files, range: `${baseline}..HEAD` };
|
|
12953
|
-
}
|
|
12954
|
-
const last = diff("HEAD~1..HEAD");
|
|
12955
|
-
return { files: last, range: last.length > 0 ? "HEAD~1..HEAD" : null };
|
|
12956
|
-
}
|
|
12957
|
-
function getPushRangeMessages() {
|
|
12958
|
-
const { range } = getPushRangeFiles();
|
|
12959
|
-
if (!range) return "";
|
|
12960
|
-
return execGit(`git log ${range} --format=%B%x00`).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
|
|
12961
|
-
}
|
|
12962
|
-
function getWorktreeFiles() {
|
|
12963
|
-
const result = [];
|
|
12964
|
-
const worktreeDir = ".claude/worktrees";
|
|
12965
|
-
if (!(0, import_node_fs7.existsSync)(worktreeDir)) return result;
|
|
12966
|
-
try {
|
|
12967
|
-
const fiveMinAgo = Date.now() - 5 * 60 * 1e3;
|
|
12968
|
-
const entries = (0, import_node_fs7.readdirSync)(worktreeDir, { withFileTypes: true });
|
|
12969
|
-
for (const entry of entries) {
|
|
12970
|
-
if (!entry.isDirectory()) continue;
|
|
12971
|
-
const wtDir = (0, import_node_path8.join)(worktreeDir, entry.name);
|
|
12972
|
-
scanDir(wtDir, wtDir, fiveMinAgo, result);
|
|
12973
|
-
}
|
|
12974
|
-
} catch {
|
|
12975
|
-
}
|
|
12976
|
-
return result;
|
|
12977
|
-
}
|
|
12978
|
-
function scanDir(baseDir, dir, minMtime, result) {
|
|
12979
|
-
try {
|
|
12980
|
-
const entries = (0, import_node_fs7.readdirSync)(dir, { withFileTypes: true });
|
|
12981
|
-
for (const entry of entries) {
|
|
12982
|
-
const fullPath = (0, import_node_path8.join)(dir, entry.name);
|
|
12983
|
-
if (entry.isDirectory()) {
|
|
12984
|
-
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
12985
|
-
scanDir(baseDir, fullPath, minMtime, result);
|
|
12986
|
-
} else if (entry.isFile()) {
|
|
12987
|
-
const ext = (0, import_node_path8.extname)(entry.name).slice(1);
|
|
12988
|
-
if (!ANALYZABLE_EXTENSIONS.has(ext)) continue;
|
|
12989
|
-
try {
|
|
12990
|
-
const stat3 = (0, import_node_fs7.statSync)(fullPath);
|
|
12991
|
-
if (stat3.mtimeMs >= minMtime) {
|
|
12992
|
-
const relPath = fullPath.slice(baseDir.length + 1);
|
|
12993
|
-
result.push(relPath);
|
|
12994
|
-
}
|
|
12995
|
-
} catch {
|
|
12996
|
-
}
|
|
12997
|
-
}
|
|
12998
|
-
}
|
|
12999
|
-
} catch {
|
|
13000
|
-
}
|
|
13001
|
-
}
|
|
13002
|
-
function filterAnalyzable(files) {
|
|
13003
|
-
return files.filter((f) => {
|
|
13004
|
-
const ext = (0, import_node_path8.extname)(f).slice(1);
|
|
13005
|
-
return ANALYZABLE_EXTENSIONS.has(ext);
|
|
13006
|
-
});
|
|
13007
|
-
}
|
|
13008
|
-
function filterReviewable(files) {
|
|
13009
|
-
return files.filter((f) => {
|
|
13010
|
-
const ext = (0, import_node_path8.extname)(f).slice(1);
|
|
13011
|
-
if (ANALYZABLE_EXTENSIONS.has(ext)) return false;
|
|
13012
|
-
if (REVIEWABLE_EXTENSIONS.has(ext)) return true;
|
|
13013
|
-
const basename2 = f.split("/").pop() ?? "";
|
|
13014
|
-
if (REVIEWABLE_FILENAMES.has(basename2)) return true;
|
|
13015
|
-
if (REVIEWABLE_PATH_PATTERNS.some((p) => p.test(f))) return true;
|
|
13016
|
-
return false;
|
|
13017
|
-
});
|
|
13018
|
-
}
|
|
13019
|
-
function filterSecurity(files) {
|
|
13020
|
-
return files.filter(
|
|
13021
|
-
(f) => SECURITY_PATTERNS.some((p) => p.test(f))
|
|
13022
|
-
);
|
|
13023
|
-
}
|
|
13024
|
-
function getCurrentCommit() {
|
|
13025
|
-
return execGit("git rev-parse HEAD") || "no-git";
|
|
13026
|
-
}
|
|
13027
|
-
function listTrackedFiles() {
|
|
13028
|
-
const set = /* @__PURE__ */ new Set();
|
|
13029
|
-
for (const f of splitLines(execGit("git ls-files"))) set.add(f);
|
|
13030
|
-
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) set.add(f);
|
|
13031
|
-
return Array.from(set);
|
|
13032
|
-
}
|
|
13033
|
-
|
|
13034
13287
|
// src/lib/files.ts
|
|
13035
13288
|
var import_node_fs8 = require("node:fs");
|
|
13036
13289
|
var import_node_path9 = require("node:path");
|
|
@@ -14638,6 +14891,27 @@ function passAndExit(reason) {
|
|
|
14638
14891
|
printJsonCompact({ gate_decision: "PASS", systemMessage: `Verity: ${reason}` });
|
|
14639
14892
|
process.exit(0);
|
|
14640
14893
|
}
|
|
14894
|
+
var EMPTY_STATIC = {
|
|
14895
|
+
tool: "@codacy/analysis-cli",
|
|
14896
|
+
findings: [],
|
|
14897
|
+
summary: { total_findings: 0, by_severity: {}, tools_run: [] }
|
|
14898
|
+
};
|
|
14899
|
+
function runLocalStatic(analyzable, securityFiles, baseline, skipStatic) {
|
|
14900
|
+
if (skipStatic || !isCodacyAvailable()) return EMPTY_STATIC;
|
|
14901
|
+
let scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles]));
|
|
14902
|
+
if (baseline) scannable = scannable.filter((f) => changedSinceBaseline(f, baseline));
|
|
14903
|
+
if (scannable.length === 0) return EMPTY_STATIC;
|
|
14904
|
+
return runCodacyAnalysis(scannable);
|
|
14905
|
+
}
|
|
14906
|
+
function localOnlyAndExit(staticResults) {
|
|
14907
|
+
printJsonCompact({
|
|
14908
|
+
gate_decision: "PASS",
|
|
14909
|
+
systemMessage: "Verity: not authenticated \u2014 ran a local static-only check (no deep review, no upload). Run `verity init` to authenticate and enable the full quality gate.",
|
|
14910
|
+
unauthenticated: true,
|
|
14911
|
+
static_results: staticResults
|
|
14912
|
+
});
|
|
14913
|
+
process.exit(0);
|
|
14914
|
+
}
|
|
14641
14915
|
function registerAnalyzeCommand(program2) {
|
|
14642
14916
|
program2.command("analyze").description("Run Verity analysis on changed files (stop hook)").option("--debounce <seconds>", "Skip if last analysis was within N seconds", "30").option("--max-iterations <n>", "Force PASS after N FAIL cycles", "2").option("--max-files <n>", "Max files to send for review", "20").option("--max-file-size <bytes>", "Skip files larger than N bytes", "51200").option("--max-total-size <bytes>", "Stop collecting files at N total bytes", "194560").option("--skip-static", "Skip codacy-analysis").option("--mode <mode>", "Force analysis mode (standard|plan|debug|skip)").option("--json", "Output raw JSON response").action(async (opts) => {
|
|
14643
14917
|
const globals = program2.opts();
|
|
@@ -14688,12 +14962,9 @@ async function runAnalyze(opts, globals) {
|
|
|
14688
14962
|
passAndExit("Reflection-prompt turn \u2014 skipping analysis");
|
|
14689
14963
|
}
|
|
14690
14964
|
const tokenResult = await resolveToken(globals.token);
|
|
14691
|
-
if (!tokenResult.ok) {
|
|
14692
|
-
passAndExit("Not configured yet \u2014 run /verity-setup in Claude Code to enable quality gates.");
|
|
14693
|
-
}
|
|
14694
14965
|
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
14695
|
-
if (!urlResult.ok) {
|
|
14696
|
-
|
|
14966
|
+
if (!tokenResult.ok || !urlResult.ok) {
|
|
14967
|
+
localOnlyAndExit(runLocalStatic(analyzable, securityFiles, baseline, !!opts.skipStatic));
|
|
14697
14968
|
}
|
|
14698
14969
|
const sessionIdForMemory = sessionId || process.env.CLAUDE_SESSION_ID || "";
|
|
14699
14970
|
let contextFilePaths = [];
|
|
@@ -15267,13 +15538,14 @@ async function runReview(opts, globals) {
|
|
|
15267
15538
|
}
|
|
15268
15539
|
const codeDelta = collectCodeDelta(allFiles);
|
|
15269
15540
|
const tokenResult = await resolveToken(globals.token);
|
|
15270
|
-
if (!tokenResult.ok) {
|
|
15271
|
-
printError("Not configured yet \u2014 run /verity-setup in Claude Code to enable quality gates.");
|
|
15272
|
-
process.exit(0);
|
|
15273
|
-
}
|
|
15274
15541
|
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
15275
|
-
if (!urlResult.ok) {
|
|
15276
|
-
|
|
15542
|
+
if (!tokenResult.ok || !urlResult.ok) {
|
|
15543
|
+
printJsonCompact({
|
|
15544
|
+
gate_decision: "PASS",
|
|
15545
|
+
systemMessage: "Verity: not authenticated \u2014 showing local static results only (no deep review, no upload). Run `verity init` to authenticate and unlock the full review.",
|
|
15546
|
+
unauthenticated: true,
|
|
15547
|
+
static_results: staticResults
|
|
15548
|
+
});
|
|
15277
15549
|
process.exit(0);
|
|
15278
15550
|
}
|
|
15279
15551
|
let specs;
|
|
@@ -15662,6 +15934,7 @@ var import_node_fs24 = require("node:fs");
|
|
|
15662
15934
|
var import_promises13 = require("node:fs/promises");
|
|
15663
15935
|
var import_node_path18 = require("node:path");
|
|
15664
15936
|
var import_node_child_process9 = require("node:child_process");
|
|
15937
|
+
var readline2 = __toESM(require("node:readline/promises"));
|
|
15665
15938
|
|
|
15666
15939
|
// src/commands/migrate.ts
|
|
15667
15940
|
var import_node_fs23 = require("node:fs");
|
|
@@ -15941,6 +16214,66 @@ function registerMigrateCommand(program2) {
|
|
|
15941
16214
|
}
|
|
15942
16215
|
|
|
15943
16216
|
// src/commands/init.ts
|
|
16217
|
+
async function promptYes(question) {
|
|
16218
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
|
|
16219
|
+
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
16220
|
+
try {
|
|
16221
|
+
const answer = (await rl.question(question)).trim().toLowerCase();
|
|
16222
|
+
return answer === "" || answer === "y" || answer === "yes";
|
|
16223
|
+
} finally {
|
|
16224
|
+
rl.close();
|
|
16225
|
+
}
|
|
16226
|
+
}
|
|
16227
|
+
async function runOptionalAuth() {
|
|
16228
|
+
const existing = await resolveToken();
|
|
16229
|
+
if (existing.ok) {
|
|
16230
|
+
printInfo("Already authenticated \u2014 results will upload to the Verity service. \u2713");
|
|
16231
|
+
return;
|
|
16232
|
+
}
|
|
16233
|
+
let remote = "";
|
|
16234
|
+
try {
|
|
16235
|
+
remote = (0, import_node_child_process9.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
16236
|
+
} catch {
|
|
16237
|
+
}
|
|
16238
|
+
const localOnlyNote = () => {
|
|
16239
|
+
printInfo("Verity runs in local-only mode: the gate still runs and shows static findings, but nothing uploads.");
|
|
16240
|
+
printInfo(' Authenticate anytime: run "verity init" again, or "verity auth register".');
|
|
16241
|
+
};
|
|
16242
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
16243
|
+
console.log("");
|
|
16244
|
+
console.log(" Signing in is optional. What it does:");
|
|
16245
|
+
console.log(" - Confirms you have write access to this repository. The GitHub token");
|
|
16246
|
+
console.log(" is used once to verify that, then discarded \u2014 Verity never stores it.");
|
|
16247
|
+
console.log(" - It does NOT give Verity access to your code. Code checked by the gate");
|
|
16248
|
+
console.log(" is analyzed in memory and discarded \u2014 we never store your code.");
|
|
16249
|
+
console.log(" - It is required to store and access run history for this repo");
|
|
16250
|
+
console.log(" (past results, trends, and shareable reports).");
|
|
16251
|
+
console.log(" - Skip and Verity still works fully locally: the gate runs and shows");
|
|
16252
|
+
console.log(" findings, but nothing is uploaded.");
|
|
16253
|
+
console.log("");
|
|
16254
|
+
}
|
|
16255
|
+
const wantsAuth = await promptYes("Authenticate with GitHub now to upload results to Verity? [Y/skip] ");
|
|
16256
|
+
if (!wantsAuth) {
|
|
16257
|
+
printInfo("Skipped authentication.");
|
|
16258
|
+
localOnlyNote();
|
|
16259
|
+
return;
|
|
16260
|
+
}
|
|
16261
|
+
if (!remote) {
|
|
16262
|
+
printWarn("No git remote found \u2014 cannot authenticate yet.");
|
|
16263
|
+
localOnlyNote();
|
|
16264
|
+
return;
|
|
16265
|
+
}
|
|
16266
|
+
const projectName = parseRemote(remote)?.repo ?? (0, import_node_path18.basename)(process.cwd());
|
|
16267
|
+
printInfo("Authenticating with GitHub\u2026");
|
|
16268
|
+
const result = await registerProject({ projectName, remote, serviceUrl: DEFAULT_SERVICE_URL });
|
|
16269
|
+
if (result.ok) {
|
|
16270
|
+
printInfo(`Project registered: ${result.data.projectId} \u2713`);
|
|
16271
|
+
if (result.data.email) printInfo(` Authenticated as: ${result.data.email}`);
|
|
16272
|
+
} else {
|
|
16273
|
+
printWarn(`Authentication did not complete: ${result.error}`);
|
|
16274
|
+
localOnlyNote();
|
|
16275
|
+
}
|
|
16276
|
+
}
|
|
15944
16277
|
function resolveDataDir() {
|
|
15945
16278
|
const candidates = [
|
|
15946
16279
|
(0, import_node_path18.join)(__dirname, "..", "data"),
|
|
@@ -16084,6 +16417,12 @@ function registerInitCommand(program2) {
|
|
|
16084
16417
|
const globalVerityDir = (0, import_node_path18.join)(process.env.HOME ?? "", ".verity");
|
|
16085
16418
|
await (0, import_promises13.mkdir)(globalVerityDir, { recursive: true });
|
|
16086
16419
|
console.log("");
|
|
16420
|
+
try {
|
|
16421
|
+
await runOptionalAuth();
|
|
16422
|
+
} catch (err) {
|
|
16423
|
+
printWarn(`Authentication step skipped: ${err.message}`);
|
|
16424
|
+
}
|
|
16425
|
+
console.log("");
|
|
16087
16426
|
printInfo("Verity initialized!");
|
|
16088
16427
|
console.log("");
|
|
16089
16428
|
console.log(" Installed:");
|
|
@@ -16099,6 +16438,7 @@ function registerInitCommand(program2) {
|
|
|
16099
16438
|
console.log(" .verity/memory/ \u2014 knowledge base (8 domains, commit to git)");
|
|
16100
16439
|
console.log("");
|
|
16101
16440
|
console.log(" Next step: open this project in Claude Code and run /verity-setup");
|
|
16441
|
+
console.log(' (Not authenticated? Verity runs in local-only mode until you run "verity auth register".)');
|
|
16102
16442
|
console.log("");
|
|
16103
16443
|
});
|
|
16104
16444
|
}
|
|
@@ -16827,7 +17167,7 @@ function registerTelemetryCommands(program2) {
|
|
|
16827
17167
|
}
|
|
16828
17168
|
|
|
16829
17169
|
// src/cli.ts
|
|
16830
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.
|
|
17170
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.27.0-experimental.1106a16").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
|
|
16831
17171
|
registerAuthCommands(program);
|
|
16832
17172
|
registerHooksCommands(program);
|
|
16833
17173
|
registerIntentCommands(program);
|