@codacy/verity-cli 0.26.0 → 0.27.0-experimental.8f12b5a
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 +640 -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,462 @@ 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) {
|
|
11125
|
+
let installUrl = null;
|
|
11126
|
+
const maxAttempts = 3;
|
|
11127
|
+
for (let attempt = 1; ; attempt++) {
|
|
11128
|
+
const visible = await githubCanSeeRepo(owner, repo, token);
|
|
11129
|
+
if (!visible.ok) return { ok: true, data: void 0 };
|
|
11130
|
+
if (visible.data) return { ok: true, data: void 0 };
|
|
11131
|
+
if (installUrl === null) installUrl = githubAppInstallUrl(await githubAccountId(owner));
|
|
11132
|
+
if (!isInteractive() || attempt >= maxAttempts) {
|
|
11133
|
+
return {
|
|
11134
|
+
ok: false,
|
|
11135
|
+
error: `The Verity GitHub App can't see ${owner}/${repo}. Install it on "${owner}" and grant access to this repository, then re-run:
|
|
11136
|
+
${installUrl}`
|
|
11137
|
+
};
|
|
11138
|
+
}
|
|
11139
|
+
printWarn(`The Verity GitHub App can't see ${owner}/${repo} yet.`);
|
|
11140
|
+
printInfo(`Install it on "${owner}" (grant access to this repo):`);
|
|
11141
|
+
printInfo(` ${installUrl}`);
|
|
11142
|
+
await waitForEnter("Press Enter once installed to retry\u2026 ");
|
|
11143
|
+
}
|
|
11144
|
+
}
|
|
11145
|
+
async function registerProject(opts) {
|
|
11146
|
+
const parsed = parseRemote(opts.remote);
|
|
11147
|
+
if (!parsed) {
|
|
11148
|
+
return { ok: false, error: `Could not parse git remote: ${opts.remote}` };
|
|
11149
|
+
}
|
|
11150
|
+
if (parsed.provider !== "github") {
|
|
11151
|
+
return { ok: false, error: `Provider '${parsed.provider}' is not supported yet \u2014 GitHub only for now.` };
|
|
11152
|
+
}
|
|
11153
|
+
const usingTokenOverride = Boolean(process.env.VERITY_PROVIDER_TOKEN);
|
|
11154
|
+
const providerAuth = await githubDeviceFlow();
|
|
11155
|
+
if (!providerAuth.ok) {
|
|
11156
|
+
return { ok: false, error: providerAuth.error };
|
|
11157
|
+
}
|
|
11158
|
+
const providerToken = providerAuth.data;
|
|
11159
|
+
if (!usingTokenOverride) {
|
|
11160
|
+
const installed = await ensureAppInstalled(parsed.owner, parsed.repo, providerToken);
|
|
11161
|
+
if (!installed.ok) {
|
|
11162
|
+
return { ok: false, error: installed.error };
|
|
11163
|
+
}
|
|
11164
|
+
}
|
|
11165
|
+
const result = await apiRequest({
|
|
11166
|
+
method: "POST",
|
|
11167
|
+
path: "/auth/register",
|
|
11168
|
+
serviceUrl: opts.serviceUrl,
|
|
11169
|
+
body: { project_name: opts.projectName, git_remote_url: opts.remote },
|
|
11170
|
+
extraHeaders: { "X-Provider-Token": providerToken },
|
|
11171
|
+
verbose: opts.verbose
|
|
11172
|
+
});
|
|
11173
|
+
if (!result.ok) {
|
|
11174
|
+
return { ok: false, error: result.error };
|
|
11175
|
+
}
|
|
11176
|
+
const { project_id, token, service_url, user } = result.data;
|
|
11177
|
+
try {
|
|
11178
|
+
await (0, import_promises3.mkdir)(VERITY_DIR, { recursive: true });
|
|
11179
|
+
await (0, import_promises3.writeFile)(
|
|
11180
|
+
CREDENTIALS_FILE,
|
|
11181
|
+
`token: ${token}
|
|
11182
|
+
service_url: ${service_url}
|
|
11183
|
+
`,
|
|
11184
|
+
{ mode: 384 }
|
|
11185
|
+
);
|
|
11186
|
+
await (0, import_promises3.chmod)(CREDENTIALS_FILE, 384).catch(() => {
|
|
11187
|
+
});
|
|
11188
|
+
} catch (err) {
|
|
11189
|
+
return {
|
|
11190
|
+
ok: false,
|
|
11191
|
+
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".`
|
|
11192
|
+
};
|
|
11193
|
+
}
|
|
11194
|
+
try {
|
|
11195
|
+
await (0, import_promises3.mkdir)((0, import_node_path3.dirname)(GLOBAL_CREDENTIALS_FILE), { recursive: true });
|
|
11196
|
+
await (0, import_promises3.appendFile)(
|
|
11197
|
+
GLOBAL_CREDENTIALS_FILE,
|
|
11198
|
+
`${opts.remote} token: ${token}
|
|
11199
|
+
`,
|
|
11200
|
+
{ mode: 384 }
|
|
11201
|
+
);
|
|
11202
|
+
await (0, import_promises3.chmod)(GLOBAL_CREDENTIALS_FILE, 384).catch(() => {
|
|
11203
|
+
});
|
|
11204
|
+
} catch {
|
|
11205
|
+
}
|
|
11206
|
+
return { ok: true, data: { projectId: project_id, serviceUrl: service_url, email: user?.email } };
|
|
11207
|
+
}
|
|
11208
|
+
|
|
10743
11209
|
// src/commands/auth.ts
|
|
10744
11210
|
function registerAuthCommands(program2) {
|
|
10745
11211
|
const auth = program2.command("auth").description("Manage project authentication");
|
|
@@ -10749,36 +11215,26 @@ function registerAuthCommands(program2) {
|
|
|
10749
11215
|
let remote = opts.remote;
|
|
10750
11216
|
if (!remote) {
|
|
10751
11217
|
try {
|
|
10752
|
-
remote = (0,
|
|
11218
|
+
remote = (0, import_node_child_process4.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
|
|
10753
11219
|
} catch {
|
|
10754
11220
|
printError("No git remote found. Use --remote to specify one.");
|
|
10755
11221
|
process.exit(1);
|
|
10756
11222
|
}
|
|
10757
11223
|
}
|
|
10758
|
-
const result = await
|
|
10759
|
-
|
|
10760
|
-
|
|
11224
|
+
const result = await registerProject({
|
|
11225
|
+
projectName: opts.project,
|
|
11226
|
+
remote,
|
|
10761
11227
|
serviceUrl,
|
|
10762
|
-
body: { project_name: opts.project, git_remote_url: remote },
|
|
10763
11228
|
verbose: globals.verbose
|
|
10764
11229
|
});
|
|
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 {
|
|
11230
|
+
if (!result.ok) {
|
|
11231
|
+
printError(result.error);
|
|
11232
|
+
process.exit(1);
|
|
10779
11233
|
}
|
|
10780
|
-
|
|
10781
|
-
|
|
11234
|
+
const { projectId, serviceUrl: resolvedUrl, email } = result.data;
|
|
11235
|
+
printInfo(`Project registered: ${projectId}`);
|
|
11236
|
+
if (email) printInfo(`Authenticated as: ${email}`);
|
|
11237
|
+
printJson({ project_id: projectId, service_url: resolvedUrl });
|
|
10782
11238
|
});
|
|
10783
11239
|
auth.command("verify").description("Verify the current token is valid").action(async () => {
|
|
10784
11240
|
const globals = program2.opts();
|
|
@@ -10811,7 +11267,7 @@ service_url: ${service_url}
|
|
|
10811
11267
|
let remote = opts.remote;
|
|
10812
11268
|
if (!remote) {
|
|
10813
11269
|
try {
|
|
10814
|
-
remote = (0,
|
|
11270
|
+
remote = (0, import_node_child_process4.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
|
|
10815
11271
|
} catch {
|
|
10816
11272
|
printError("No git remote found. Use --remote to specify one.");
|
|
10817
11273
|
process.exit(1);
|
|
@@ -10839,11 +11295,11 @@ service_url: ${service_url}
|
|
|
10839
11295
|
|
|
10840
11296
|
// src/lib/hooks.ts
|
|
10841
11297
|
var import_promises5 = require("node:fs/promises");
|
|
10842
|
-
var
|
|
11298
|
+
var import_node_path5 = require("node:path");
|
|
10843
11299
|
|
|
10844
11300
|
// src/lib/json-file.ts
|
|
10845
11301
|
var import_promises4 = require("node:fs/promises");
|
|
10846
|
-
var
|
|
11302
|
+
var import_node_path4 = require("node:path");
|
|
10847
11303
|
function jsonSemanticEqual(a, b) {
|
|
10848
11304
|
if (a === b) return true;
|
|
10849
11305
|
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) {
|
|
@@ -10889,7 +11345,7 @@ async function writeJsonFilePreservingStyle(file, value) {
|
|
|
10889
11345
|
const indent = currentRaw !== null ? detectJsonIndent(currentRaw) : 2;
|
|
10890
11346
|
const next = JSON.stringify(value, null, indent) + "\n";
|
|
10891
11347
|
if (next === currentRaw) return false;
|
|
10892
|
-
await (0, import_promises4.mkdir)((0,
|
|
11348
|
+
await (0, import_promises4.mkdir)((0, import_node_path4.dirname)(file), { recursive: true });
|
|
10893
11349
|
await (0, import_promises4.writeFile)(file, next);
|
|
10894
11350
|
return true;
|
|
10895
11351
|
}
|
|
@@ -11050,13 +11506,13 @@ async function writeSettings(settings) {
|
|
|
11050
11506
|
}
|
|
11051
11507
|
async function readSettingsAt(root) {
|
|
11052
11508
|
try {
|
|
11053
|
-
return JSON.parse(await (0, import_promises5.readFile)((0,
|
|
11509
|
+
return JSON.parse(await (0, import_promises5.readFile)((0, import_node_path5.join)(root, CLAUDE_SETTINGS_FILE), "utf-8"));
|
|
11054
11510
|
} catch {
|
|
11055
11511
|
return {};
|
|
11056
11512
|
}
|
|
11057
11513
|
}
|
|
11058
11514
|
async function writeSettingsAt(root, settings) {
|
|
11059
|
-
await writeJsonFilePreservingStyle((0,
|
|
11515
|
+
await writeJsonFilePreservingStyle((0, import_node_path5.join)(root, CLAUDE_SETTINGS_FILE), settings);
|
|
11060
11516
|
}
|
|
11061
11517
|
async function hasLegacyHooksAt(root) {
|
|
11062
11518
|
const settings = await readSettingsAt(root);
|
|
@@ -11296,8 +11752,8 @@ var import_node_crypto3 = require("node:crypto");
|
|
|
11296
11752
|
|
|
11297
11753
|
// src/lib/conversation-buffer.ts
|
|
11298
11754
|
var import_promises6 = require("node:fs/promises");
|
|
11299
|
-
var
|
|
11300
|
-
var
|
|
11755
|
+
var import_node_fs3 = require("node:fs");
|
|
11756
|
+
var import_node_child_process5 = require("node:child_process");
|
|
11301
11757
|
var import_node_crypto = require("node:crypto");
|
|
11302
11758
|
function stripImageReferences(text) {
|
|
11303
11759
|
return text.replace(/\[Image #\d+\]/g, "[screenshot \u2014 not available for review]");
|
|
@@ -11332,7 +11788,7 @@ async function appendToConversationBuffer(prompt, sessionId) {
|
|
|
11332
11788
|
}
|
|
11333
11789
|
async function readAndClearConversationBuffer(currentSessionId) {
|
|
11334
11790
|
try {
|
|
11335
|
-
if ((0,
|
|
11791
|
+
if ((0, import_node_fs3.existsSync)(CONVERSATION_BUFFER_FILE)) {
|
|
11336
11792
|
const entries = await readBufferEntries();
|
|
11337
11793
|
let mine = entries;
|
|
11338
11794
|
let others = [];
|
|
@@ -11356,7 +11812,7 @@ async function readAndClearConversationBuffer(currentSessionId) {
|
|
|
11356
11812
|
};
|
|
11357
11813
|
}
|
|
11358
11814
|
}
|
|
11359
|
-
if ((0,
|
|
11815
|
+
if ((0, import_node_fs3.existsSync)(INTENT_FILE)) {
|
|
11360
11816
|
try {
|
|
11361
11817
|
const content = await (0, import_promises6.readFile)(INTENT_FILE, "utf-8");
|
|
11362
11818
|
await (0, import_promises6.unlink)(INTENT_FILE).catch(() => {
|
|
@@ -11400,7 +11856,7 @@ async function readBufferEntries() {
|
|
|
11400
11856
|
}
|
|
11401
11857
|
function getRecentCommitMessages() {
|
|
11402
11858
|
try {
|
|
11403
|
-
const output = (0,
|
|
11859
|
+
const output = (0, import_node_child_process5.execSync)(
|
|
11404
11860
|
'git log --since="30 minutes ago" --format="%s" -5',
|
|
11405
11861
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
11406
11862
|
).trim();
|
|
@@ -11413,8 +11869,8 @@ function getRecentCommitMessages() {
|
|
|
11413
11869
|
|
|
11414
11870
|
// src/lib/task-context-buffer.ts
|
|
11415
11871
|
var import_promises7 = require("node:fs/promises");
|
|
11416
|
-
var
|
|
11417
|
-
var
|
|
11872
|
+
var import_node_fs4 = require("node:fs");
|
|
11873
|
+
var import_node_path6 = require("node:path");
|
|
11418
11874
|
var TASK_CONTEXT_DIR = `${VERITY_DIR}/.task-context`;
|
|
11419
11875
|
var MAX_BUFFER_BYTES = 500 * 1024;
|
|
11420
11876
|
var MAX_PROMPT_CHARS = 2e3;
|
|
@@ -11453,7 +11909,7 @@ async function appendResponseToTaskBuffer(taskId, assistantResponse, actionSumma
|
|
|
11453
11909
|
}
|
|
11454
11910
|
async function readTaskContextBuffer(taskId) {
|
|
11455
11911
|
const filePath = bufferPath(taskId);
|
|
11456
|
-
if (!(0,
|
|
11912
|
+
if (!(0, import_node_fs4.existsSync)(filePath)) return null;
|
|
11457
11913
|
try {
|
|
11458
11914
|
const content = await (0, import_promises7.readFile)(filePath, "utf-8");
|
|
11459
11915
|
if (!content.trim()) return null;
|
|
@@ -11487,12 +11943,12 @@ async function readTaskContextBuffer(taskId) {
|
|
|
11487
11943
|
}
|
|
11488
11944
|
async function cleanupTaskContextBuffers() {
|
|
11489
11945
|
try {
|
|
11490
|
-
if (!(0,
|
|
11946
|
+
if (!(0, import_node_fs4.existsSync)(TASK_CONTEXT_DIR)) return;
|
|
11491
11947
|
const files = await (0, import_promises7.readdir)(TASK_CONTEXT_DIR);
|
|
11492
11948
|
const cutoffMs = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
11493
11949
|
for (const file of files) {
|
|
11494
11950
|
if (!file.endsWith(".jsonl")) continue;
|
|
11495
|
-
const filePath = (0,
|
|
11951
|
+
const filePath = (0, import_node_path6.join)(TASK_CONTEXT_DIR, file);
|
|
11496
11952
|
try {
|
|
11497
11953
|
const stats = await (0, import_promises7.stat)(filePath);
|
|
11498
11954
|
if (stats.mtimeMs < cutoffMs) {
|
|
@@ -11506,13 +11962,13 @@ async function cleanupTaskContextBuffers() {
|
|
|
11506
11962
|
}
|
|
11507
11963
|
function bufferPath(taskId) {
|
|
11508
11964
|
const safe = taskId.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
11509
|
-
return (0,
|
|
11965
|
+
return (0, import_node_path6.join)(TASK_CONTEXT_DIR, `${safe}.jsonl`);
|
|
11510
11966
|
}
|
|
11511
11967
|
async function appendEntry(taskId, entry) {
|
|
11512
11968
|
try {
|
|
11513
11969
|
await (0, import_promises7.mkdir)(TASK_CONTEXT_DIR, { recursive: true });
|
|
11514
11970
|
const filePath = bufferPath(taskId);
|
|
11515
|
-
if ((0,
|
|
11971
|
+
if ((0, import_node_fs4.existsSync)(filePath)) {
|
|
11516
11972
|
const stats = await (0, import_promises7.stat)(filePath);
|
|
11517
11973
|
if (stats.size >= MAX_BUFFER_BYTES) {
|
|
11518
11974
|
const content = await (0, import_promises7.readFile)(filePath, "utf-8");
|
|
@@ -11523,7 +11979,7 @@ async function appendEntry(taskId, entry) {
|
|
|
11523
11979
|
}
|
|
11524
11980
|
}
|
|
11525
11981
|
const line = JSON.stringify(entry) + "\n";
|
|
11526
|
-
const existing = (0,
|
|
11982
|
+
const existing = (0, import_node_fs4.existsSync)(filePath) ? await (0, import_promises7.readFile)(filePath, "utf-8") : "";
|
|
11527
11983
|
await (0, import_promises7.writeFile)(filePath, existing + line);
|
|
11528
11984
|
} catch {
|
|
11529
11985
|
}
|
|
@@ -11531,8 +11987,8 @@ async function appendEntry(taskId, entry) {
|
|
|
11531
11987
|
|
|
11532
11988
|
// src/lib/memory-retrieval.ts
|
|
11533
11989
|
var import_promises8 = require("node:fs/promises");
|
|
11534
|
-
var
|
|
11535
|
-
var
|
|
11990
|
+
var import_node_fs5 = require("node:fs");
|
|
11991
|
+
var import_node_path7 = require("node:path");
|
|
11536
11992
|
var memoryDir = () => projectPath(`${VERITY_DIR}/memory`);
|
|
11537
11993
|
var DOMAINS = ["decisions", "quality", "security", "intent", "gotchas", "patterns", "domain", "integrations"];
|
|
11538
11994
|
var DEFAULT_BUDGET_TOKENS = 2e3;
|
|
@@ -11625,19 +12081,19 @@ function parseFrontmatter(content) {
|
|
|
11625
12081
|
return { fm, body: match[2].trim() };
|
|
11626
12082
|
}
|
|
11627
12083
|
async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = DEFAULT_BUDGET_TOKENS) {
|
|
11628
|
-
if (!(0,
|
|
12084
|
+
if (!(0, import_node_fs5.existsSync)(memoryDir())) return null;
|
|
11629
12085
|
const budget = Math.min(budgetTokens, MAX_BUDGET_TOKENS);
|
|
11630
12086
|
const promptTokens = tokenize(promptText);
|
|
11631
12087
|
const nodes = [];
|
|
11632
12088
|
for (const domain of DOMAINS) {
|
|
11633
|
-
const domainDir = (0,
|
|
11634
|
-
if (!(0,
|
|
12089
|
+
const domainDir = (0, import_node_path7.join)(memoryDir(), domain);
|
|
12090
|
+
if (!(0, import_node_fs5.existsSync)(domainDir)) continue;
|
|
11635
12091
|
try {
|
|
11636
12092
|
const files = await (0, import_promises8.readdir)(domainDir);
|
|
11637
12093
|
for (const file of files) {
|
|
11638
12094
|
if (!file.endsWith(".md")) continue;
|
|
11639
12095
|
try {
|
|
11640
|
-
const content = await (0, import_promises8.readFile)((0,
|
|
12096
|
+
const content = await (0, import_promises8.readFile)((0, import_node_path7.join)(domainDir, file), "utf-8");
|
|
11641
12097
|
const { fm, body } = parseFrontmatter(content);
|
|
11642
12098
|
if (fm.status && fm.status !== "active") continue;
|
|
11643
12099
|
nodes.push({
|
|
@@ -11696,8 +12152,8 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
|
|
|
11696
12152
|
|
|
11697
12153
|
// src/lib/memory-sync.ts
|
|
11698
12154
|
var import_promises9 = require("node:fs/promises");
|
|
11699
|
-
var
|
|
11700
|
-
var
|
|
12155
|
+
var import_node_fs6 = require("node:fs");
|
|
12156
|
+
var import_node_path8 = require("node:path");
|
|
11701
12157
|
var import_node_crypto2 = require("node:crypto");
|
|
11702
12158
|
|
|
11703
12159
|
// src/lib/glob-match.ts
|
|
@@ -11768,32 +12224,32 @@ var syncStateFile = () => projectPath(`${VERITY_DIR}/.memory-sync-state.json`);
|
|
|
11768
12224
|
async function ensureMemoryDir() {
|
|
11769
12225
|
await (0, import_promises9.mkdir)(memoryDir2(), { recursive: true });
|
|
11770
12226
|
for (const domain of DOMAINS2) {
|
|
11771
|
-
await (0, import_promises9.mkdir)((0,
|
|
12227
|
+
await (0, import_promises9.mkdir)((0, import_node_path8.join)(memoryDir2(), domain), { recursive: true });
|
|
11772
12228
|
}
|
|
11773
|
-
if (!(0,
|
|
11774
|
-
await (0, import_promises9.writeFile)((0,
|
|
12229
|
+
if (!(0, import_node_fs6.existsSync)((0, import_node_path8.join)(memoryDir2(), "SCHEMA.md"))) {
|
|
12230
|
+
await (0, import_promises9.writeFile)((0, import_node_path8.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
|
|
11775
12231
|
}
|
|
11776
|
-
if (!(0,
|
|
11777
|
-
await (0, import_promises9.writeFile)((0,
|
|
12232
|
+
if (!(0, import_node_fs6.existsSync)((0, import_node_path8.join)(memoryDir2(), "index.md"))) {
|
|
12233
|
+
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
12234
|
}
|
|
11779
|
-
if (!(0,
|
|
11780
|
-
await (0, import_promises9.writeFile)((0,
|
|
12235
|
+
if (!(0, import_node_fs6.existsSync)((0, import_node_path8.join)(memoryDir2(), "log.md"))) {
|
|
12236
|
+
await (0, import_promises9.writeFile)((0, import_node_path8.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
|
|
11781
12237
|
}
|
|
11782
12238
|
}
|
|
11783
12239
|
async function buildManifest() {
|
|
11784
|
-
if (!(0,
|
|
12240
|
+
if (!(0, import_node_fs6.existsSync)(memoryDir2())) {
|
|
11785
12241
|
return { schema_version: 1, nodes: [], index_hash: null, log_length: 0 };
|
|
11786
12242
|
}
|
|
11787
12243
|
const nodes = [];
|
|
11788
12244
|
for (const domain of DOMAINS2) {
|
|
11789
|
-
const domainDir = (0,
|
|
11790
|
-
if (!(0,
|
|
12245
|
+
const domainDir = (0, import_node_path8.join)(memoryDir2(), domain);
|
|
12246
|
+
if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
|
|
11791
12247
|
try {
|
|
11792
12248
|
const files = await (0, import_promises9.readdir)(domainDir);
|
|
11793
12249
|
for (const file of files) {
|
|
11794
12250
|
if (!file.endsWith(".md")) continue;
|
|
11795
12251
|
const filePath = `${domain}/${file}`;
|
|
11796
|
-
const fullPath = (0,
|
|
12252
|
+
const fullPath = (0, import_node_path8.join)(memoryDir2(), filePath);
|
|
11797
12253
|
try {
|
|
11798
12254
|
const content = await (0, import_promises9.readFile)(fullPath, "utf-8");
|
|
11799
12255
|
const hash = (0, import_node_crypto2.createHash)("sha256").update(content).digest("hex").slice(0, 16);
|
|
@@ -11806,13 +12262,13 @@ async function buildManifest() {
|
|
|
11806
12262
|
}
|
|
11807
12263
|
let indexHash = null;
|
|
11808
12264
|
try {
|
|
11809
|
-
const indexContent = await (0, import_promises9.readFile)((0,
|
|
12265
|
+
const indexContent = await (0, import_promises9.readFile)((0, import_node_path8.join)(memoryDir2(), "index.md"), "utf-8");
|
|
11810
12266
|
indexHash = `sha256:${(0, import_node_crypto2.createHash)("sha256").update(indexContent).digest("hex").slice(0, 16)}`;
|
|
11811
12267
|
} catch {
|
|
11812
12268
|
}
|
|
11813
12269
|
let logLength = 0;
|
|
11814
12270
|
try {
|
|
11815
|
-
const logContent = await (0, import_promises9.readFile)((0,
|
|
12271
|
+
const logContent = await (0, import_promises9.readFile)((0, import_node_path8.join)(memoryDir2(), "log.md"), "utf-8");
|
|
11816
12272
|
logLength = logContent.split("\n").length;
|
|
11817
12273
|
} catch {
|
|
11818
12274
|
}
|
|
@@ -11823,15 +12279,15 @@ function hashContent(content) {
|
|
|
11823
12279
|
}
|
|
11824
12280
|
async function readOnDiskNodes() {
|
|
11825
12281
|
const out = /* @__PURE__ */ new Map();
|
|
11826
|
-
if (!(0,
|
|
12282
|
+
if (!(0, import_node_fs6.existsSync)(memoryDir2())) return out;
|
|
11827
12283
|
for (const domain of DOMAINS2) {
|
|
11828
|
-
const domainDir = (0,
|
|
11829
|
-
if (!(0,
|
|
12284
|
+
const domainDir = (0, import_node_path8.join)(memoryDir2(), domain);
|
|
12285
|
+
if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
|
|
11830
12286
|
try {
|
|
11831
12287
|
for (const file of await (0, import_promises9.readdir)(domainDir)) {
|
|
11832
12288
|
if (!file.endsWith(".md")) continue;
|
|
11833
12289
|
try {
|
|
11834
|
-
out.set(`${domain}/${file}`, hashContent(await (0, import_promises9.readFile)((0,
|
|
12290
|
+
out.set(`${domain}/${file}`, hashContent(await (0, import_promises9.readFile)((0, import_node_path8.join)(domainDir, file), "utf-8")));
|
|
11835
12291
|
} catch {
|
|
11836
12292
|
}
|
|
11837
12293
|
}
|
|
@@ -11877,8 +12333,8 @@ async function computeEditedNodeUploads() {
|
|
|
11877
12333
|
const uploads = [];
|
|
11878
12334
|
for (const [path, prevHash] of prev) {
|
|
11879
12335
|
if (prevHash == null) continue;
|
|
11880
|
-
const full = (0,
|
|
11881
|
-
if (!(0,
|
|
12336
|
+
const full = (0, import_node_path8.join)(memoryDir2(), path);
|
|
12337
|
+
if (!(0, import_node_fs6.existsSync)(full)) continue;
|
|
11882
12338
|
let content;
|
|
11883
12339
|
try {
|
|
11884
12340
|
content = await (0, import_promises9.readFile)(full, "utf-8");
|
|
@@ -11914,15 +12370,15 @@ async function applyMemoryWrites(writes, opts = {}) {
|
|
|
11914
12370
|
const logLines = [`- ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)} \u2014 Applied ${count} write(s) from server`];
|
|
11915
12371
|
for (const n of notes) logLines.push(` - ${n}`);
|
|
11916
12372
|
try {
|
|
11917
|
-
const existing = (0,
|
|
11918
|
-
await (0, import_promises9.writeFile)((0,
|
|
12373
|
+
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";
|
|
12374
|
+
await (0, import_promises9.writeFile)((0, import_node_path8.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
|
|
11919
12375
|
} catch {
|
|
11920
12376
|
}
|
|
11921
12377
|
await recordSyncedNodePaths();
|
|
11922
12378
|
return count;
|
|
11923
12379
|
}
|
|
11924
12380
|
async function applyOneWrite(write, treePaths) {
|
|
11925
|
-
const fullPath = (0,
|
|
12381
|
+
const fullPath = (0, import_node_path8.join)(memoryDir2(), write.path);
|
|
11926
12382
|
const notes = [];
|
|
11927
12383
|
let content = write.content;
|
|
11928
12384
|
if (treePaths && treePaths.length > 0) {
|
|
@@ -11932,7 +12388,7 @@ async function applyOneWrite(write, treePaths) {
|
|
|
11932
12388
|
notes.push(`${write.path}: dropped unmatched file_globs [${grounded.dropped.join(", ")}]`);
|
|
11933
12389
|
}
|
|
11934
12390
|
}
|
|
11935
|
-
if ((0,
|
|
12391
|
+
if ((0, import_node_fs6.existsSync)(fullPath)) {
|
|
11936
12392
|
let existing = "";
|
|
11937
12393
|
try {
|
|
11938
12394
|
existing = await (0, import_promises9.readFile)(fullPath, "utf-8");
|
|
@@ -11944,7 +12400,7 @@ async function applyOneWrite(write, treePaths) {
|
|
|
11944
12400
|
return { written: false, notes };
|
|
11945
12401
|
}
|
|
11946
12402
|
}
|
|
11947
|
-
await (0, import_promises9.mkdir)((0,
|
|
12403
|
+
await (0, import_promises9.mkdir)((0, import_node_path8.dirname)(fullPath), { recursive: true });
|
|
11948
12404
|
await (0, import_promises9.writeFile)(fullPath, content);
|
|
11949
12405
|
return { written: true, notes };
|
|
11950
12406
|
}
|
|
@@ -11985,8 +12441,8 @@ async function regenerateIndex() {
|
|
|
11985
12441
|
];
|
|
11986
12442
|
let totalNodes = 0;
|
|
11987
12443
|
for (const domain of DOMAINS2.filter((d) => d !== "_archive")) {
|
|
11988
|
-
const domainDir = (0,
|
|
11989
|
-
if (!(0,
|
|
12444
|
+
const domainDir = (0, import_node_path8.join)(memoryDir2(), domain);
|
|
12445
|
+
if (!(0, import_node_fs6.existsSync)(domainDir)) continue;
|
|
11990
12446
|
try {
|
|
11991
12447
|
const files = await (0, import_promises9.readdir)(domainDir);
|
|
11992
12448
|
const mdFiles = files.filter((f) => f.endsWith(".md"));
|
|
@@ -11996,7 +12452,7 @@ async function regenerateIndex() {
|
|
|
11996
12452
|
for (const file of mdFiles.sort()) {
|
|
11997
12453
|
const slug = file.replace(/\.md$/, "");
|
|
11998
12454
|
try {
|
|
11999
|
-
const content = await (0, import_promises9.readFile)((0,
|
|
12455
|
+
const content = await (0, import_promises9.readFile)((0, import_node_path8.join)(domainDir, file), "utf-8");
|
|
12000
12456
|
const title = pickFrontmatter(content, "title") ?? slug;
|
|
12001
12457
|
const kind = pickFrontmatter(content, "kind") ?? "-";
|
|
12002
12458
|
const confidence = pickFrontmatter(content, "confidence");
|
|
@@ -12020,7 +12476,7 @@ async function regenerateIndex() {
|
|
|
12020
12476
|
lines.push("No nodes yet. Run an analysis to start building the knowledge graph.");
|
|
12021
12477
|
}
|
|
12022
12478
|
const next = lines.join("\n") + "\n";
|
|
12023
|
-
const indexPath = (0,
|
|
12479
|
+
const indexPath = (0, import_node_path8.join)(memoryDir2(), "index.md");
|
|
12024
12480
|
let existing = null;
|
|
12025
12481
|
try {
|
|
12026
12482
|
existing = await (0, import_promises9.readFile)(indexPath, "utf-8");
|
|
@@ -12102,9 +12558,9 @@ function hasLegacyMemoryBlock(text) {
|
|
|
12102
12558
|
return findMarker(text, LEGACY_MD_START) !== -1;
|
|
12103
12559
|
}
|
|
12104
12560
|
async function ensureClaudeMdPointer(cwd = repoRoot()) {
|
|
12105
|
-
const claudeMdPath = (0,
|
|
12561
|
+
const claudeMdPath = (0, import_node_path8.join)(cwd, "CLAUDE.md");
|
|
12106
12562
|
let existing = "";
|
|
12107
|
-
if ((0,
|
|
12563
|
+
if ((0, import_node_fs6.existsSync)(claudeMdPath)) {
|
|
12108
12564
|
existing = await (0, import_promises9.readFile)(claudeMdPath, "utf-8");
|
|
12109
12565
|
}
|
|
12110
12566
|
let startTag = CLAUDE_MD_START;
|
|
@@ -12234,7 +12690,7 @@ Body content (\u22648KB). Use [[node-id]] wikilinks for cross-references.
|
|
|
12234
12690
|
`;
|
|
12235
12691
|
|
|
12236
12692
|
// src/commands/intent.ts
|
|
12237
|
-
var
|
|
12693
|
+
var import_node_fs7 = require("node:fs");
|
|
12238
12694
|
function registerIntentCommands(program2) {
|
|
12239
12695
|
const intent = program2.command("intent").description("Manage intent capture");
|
|
12240
12696
|
intent.command("capture").description("Capture user intent from stdin (used by UserPromptSubmit hook)").action(async () => {
|
|
@@ -12243,7 +12699,7 @@ function registerIntentCommands(program2) {
|
|
|
12243
12699
|
process.chdir(repoRoot());
|
|
12244
12700
|
} catch {
|
|
12245
12701
|
}
|
|
12246
|
-
if (!(0,
|
|
12702
|
+
if (!(0, import_node_fs7.existsSync)(VERITY_DIR)) {
|
|
12247
12703
|
process.exit(0);
|
|
12248
12704
|
}
|
|
12249
12705
|
const chunks = [];
|
|
@@ -12822,215 +13278,6 @@ async function sendGeneralFeedback(message, opts, globals) {
|
|
|
12822
13278
|
var import_node_fs19 = require("node:fs");
|
|
12823
13279
|
var import_node_path15 = require("node:path");
|
|
12824
13280
|
|
|
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
13281
|
// src/lib/files.ts
|
|
13035
13282
|
var import_node_fs8 = require("node:fs");
|
|
13036
13283
|
var import_node_path9 = require("node:path");
|
|
@@ -14638,6 +14885,27 @@ function passAndExit(reason) {
|
|
|
14638
14885
|
printJsonCompact({ gate_decision: "PASS", systemMessage: `Verity: ${reason}` });
|
|
14639
14886
|
process.exit(0);
|
|
14640
14887
|
}
|
|
14888
|
+
var EMPTY_STATIC = {
|
|
14889
|
+
tool: "@codacy/analysis-cli",
|
|
14890
|
+
findings: [],
|
|
14891
|
+
summary: { total_findings: 0, by_severity: {}, tools_run: [] }
|
|
14892
|
+
};
|
|
14893
|
+
function runLocalStatic(analyzable, securityFiles, baseline, skipStatic) {
|
|
14894
|
+
if (skipStatic || !isCodacyAvailable()) return EMPTY_STATIC;
|
|
14895
|
+
let scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles]));
|
|
14896
|
+
if (baseline) scannable = scannable.filter((f) => changedSinceBaseline(f, baseline));
|
|
14897
|
+
if (scannable.length === 0) return EMPTY_STATIC;
|
|
14898
|
+
return runCodacyAnalysis(scannable);
|
|
14899
|
+
}
|
|
14900
|
+
function localOnlyAndExit(staticResults) {
|
|
14901
|
+
printJsonCompact({
|
|
14902
|
+
gate_decision: "PASS",
|
|
14903
|
+
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.",
|
|
14904
|
+
unauthenticated: true,
|
|
14905
|
+
static_results: staticResults
|
|
14906
|
+
});
|
|
14907
|
+
process.exit(0);
|
|
14908
|
+
}
|
|
14641
14909
|
function registerAnalyzeCommand(program2) {
|
|
14642
14910
|
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
14911
|
const globals = program2.opts();
|
|
@@ -14688,12 +14956,9 @@ async function runAnalyze(opts, globals) {
|
|
|
14688
14956
|
passAndExit("Reflection-prompt turn \u2014 skipping analysis");
|
|
14689
14957
|
}
|
|
14690
14958
|
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
14959
|
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
14695
|
-
if (!urlResult.ok) {
|
|
14696
|
-
|
|
14960
|
+
if (!tokenResult.ok || !urlResult.ok) {
|
|
14961
|
+
localOnlyAndExit(runLocalStatic(analyzable, securityFiles, baseline, !!opts.skipStatic));
|
|
14697
14962
|
}
|
|
14698
14963
|
const sessionIdForMemory = sessionId || process.env.CLAUDE_SESSION_ID || "";
|
|
14699
14964
|
let contextFilePaths = [];
|
|
@@ -15267,13 +15532,14 @@ async function runReview(opts, globals) {
|
|
|
15267
15532
|
}
|
|
15268
15533
|
const codeDelta = collectCodeDelta(allFiles);
|
|
15269
15534
|
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
15535
|
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
15275
|
-
if (!urlResult.ok) {
|
|
15276
|
-
|
|
15536
|
+
if (!tokenResult.ok || !urlResult.ok) {
|
|
15537
|
+
printJsonCompact({
|
|
15538
|
+
gate_decision: "PASS",
|
|
15539
|
+
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.",
|
|
15540
|
+
unauthenticated: true,
|
|
15541
|
+
static_results: staticResults
|
|
15542
|
+
});
|
|
15277
15543
|
process.exit(0);
|
|
15278
15544
|
}
|
|
15279
15545
|
let specs;
|
|
@@ -15662,6 +15928,7 @@ var import_node_fs24 = require("node:fs");
|
|
|
15662
15928
|
var import_promises13 = require("node:fs/promises");
|
|
15663
15929
|
var import_node_path18 = require("node:path");
|
|
15664
15930
|
var import_node_child_process9 = require("node:child_process");
|
|
15931
|
+
var readline2 = __toESM(require("node:readline/promises"));
|
|
15665
15932
|
|
|
15666
15933
|
// src/commands/migrate.ts
|
|
15667
15934
|
var import_node_fs23 = require("node:fs");
|
|
@@ -15941,6 +16208,66 @@ function registerMigrateCommand(program2) {
|
|
|
15941
16208
|
}
|
|
15942
16209
|
|
|
15943
16210
|
// src/commands/init.ts
|
|
16211
|
+
async function promptYes(question) {
|
|
16212
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
|
|
16213
|
+
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
16214
|
+
try {
|
|
16215
|
+
const answer = (await rl.question(question)).trim().toLowerCase();
|
|
16216
|
+
return answer === "" || answer === "y" || answer === "yes";
|
|
16217
|
+
} finally {
|
|
16218
|
+
rl.close();
|
|
16219
|
+
}
|
|
16220
|
+
}
|
|
16221
|
+
async function runOptionalAuth() {
|
|
16222
|
+
const existing = await resolveToken();
|
|
16223
|
+
if (existing.ok) {
|
|
16224
|
+
printInfo("Already authenticated \u2014 results will upload to the Verity service. \u2713");
|
|
16225
|
+
return;
|
|
16226
|
+
}
|
|
16227
|
+
let remote = "";
|
|
16228
|
+
try {
|
|
16229
|
+
remote = (0, import_node_child_process9.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
16230
|
+
} catch {
|
|
16231
|
+
}
|
|
16232
|
+
const localOnlyNote = () => {
|
|
16233
|
+
printInfo("Verity runs in local-only mode: the gate still runs and shows static findings, but nothing uploads.");
|
|
16234
|
+
printInfo(' Authenticate anytime: run "verity init" again, or "verity auth register".');
|
|
16235
|
+
};
|
|
16236
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
16237
|
+
console.log("");
|
|
16238
|
+
console.log(" Signing in is optional. What it does:");
|
|
16239
|
+
console.log(" - Confirms you have write access to this repository. The GitHub token");
|
|
16240
|
+
console.log(" is used once to verify that, then discarded \u2014 Verity never stores it.");
|
|
16241
|
+
console.log(" - It does NOT give Verity access to your code. Code checked by the gate");
|
|
16242
|
+
console.log(" is analyzed in memory and discarded \u2014 we never store your code.");
|
|
16243
|
+
console.log(" - It is required to store and access run history for this repo");
|
|
16244
|
+
console.log(" (past results, trends, and shareable reports).");
|
|
16245
|
+
console.log(" - Skip and Verity still works fully locally: the gate runs and shows");
|
|
16246
|
+
console.log(" findings, but nothing is uploaded.");
|
|
16247
|
+
console.log("");
|
|
16248
|
+
}
|
|
16249
|
+
const wantsAuth = await promptYes("Authenticate with GitHub now to upload results to Verity? [Y/skip] ");
|
|
16250
|
+
if (!wantsAuth) {
|
|
16251
|
+
printInfo("Skipped authentication.");
|
|
16252
|
+
localOnlyNote();
|
|
16253
|
+
return;
|
|
16254
|
+
}
|
|
16255
|
+
if (!remote) {
|
|
16256
|
+
printWarn("No git remote found \u2014 cannot authenticate yet.");
|
|
16257
|
+
localOnlyNote();
|
|
16258
|
+
return;
|
|
16259
|
+
}
|
|
16260
|
+
const projectName = parseRemote(remote)?.repo ?? (0, import_node_path18.basename)(process.cwd());
|
|
16261
|
+
printInfo("Authenticating with GitHub\u2026");
|
|
16262
|
+
const result = await registerProject({ projectName, remote, serviceUrl: DEFAULT_SERVICE_URL });
|
|
16263
|
+
if (result.ok) {
|
|
16264
|
+
printInfo(`Project registered: ${result.data.projectId} \u2713`);
|
|
16265
|
+
if (result.data.email) printInfo(` Authenticated as: ${result.data.email}`);
|
|
16266
|
+
} else {
|
|
16267
|
+
printWarn(`Authentication did not complete: ${result.error}`);
|
|
16268
|
+
localOnlyNote();
|
|
16269
|
+
}
|
|
16270
|
+
}
|
|
15944
16271
|
function resolveDataDir() {
|
|
15945
16272
|
const candidates = [
|
|
15946
16273
|
(0, import_node_path18.join)(__dirname, "..", "data"),
|
|
@@ -16084,6 +16411,12 @@ function registerInitCommand(program2) {
|
|
|
16084
16411
|
const globalVerityDir = (0, import_node_path18.join)(process.env.HOME ?? "", ".verity");
|
|
16085
16412
|
await (0, import_promises13.mkdir)(globalVerityDir, { recursive: true });
|
|
16086
16413
|
console.log("");
|
|
16414
|
+
try {
|
|
16415
|
+
await runOptionalAuth();
|
|
16416
|
+
} catch (err) {
|
|
16417
|
+
printWarn(`Authentication step skipped: ${err.message}`);
|
|
16418
|
+
}
|
|
16419
|
+
console.log("");
|
|
16087
16420
|
printInfo("Verity initialized!");
|
|
16088
16421
|
console.log("");
|
|
16089
16422
|
console.log(" Installed:");
|
|
@@ -16099,6 +16432,7 @@ function registerInitCommand(program2) {
|
|
|
16099
16432
|
console.log(" .verity/memory/ \u2014 knowledge base (8 domains, commit to git)");
|
|
16100
16433
|
console.log("");
|
|
16101
16434
|
console.log(" Next step: open this project in Claude Code and run /verity-setup");
|
|
16435
|
+
console.log(' (Not authenticated? Verity runs in local-only mode until you run "verity auth register".)');
|
|
16102
16436
|
console.log("");
|
|
16103
16437
|
});
|
|
16104
16438
|
}
|
|
@@ -16827,7 +17161,7 @@ function registerTelemetryCommands(program2) {
|
|
|
16827
17161
|
}
|
|
16828
17162
|
|
|
16829
17163
|
// src/cli.ts
|
|
16830
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.
|
|
17164
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.27.0-experimental.8f12b5a").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
|
|
16831
17165
|
registerAuthCommands(program);
|
|
16832
17166
|
registerHooksCommands(program);
|
|
16833
17167
|
registerIntentCommands(program);
|