@codacy/verity-cli 0.21.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/verity.js +499 -117
- package/package.json +3 -2
package/bin/verity.js
CHANGED
|
@@ -10648,6 +10648,10 @@ async function apiRequest(options) {
|
|
|
10648
10648
|
if (token) {
|
|
10649
10649
|
headers["Authorization"] = `Bearer ${token}`;
|
|
10650
10650
|
}
|
|
10651
|
+
const testMockScenario = process.env.VERITY_TEST_MOCK_SCENARIO;
|
|
10652
|
+
if (testMockScenario) {
|
|
10653
|
+
headers["X-Verity-Mock-Scenario"] = testMockScenario;
|
|
10654
|
+
}
|
|
10651
10655
|
printVerbose(`${method} ${url}`, verbose);
|
|
10652
10656
|
let serializedBody;
|
|
10653
10657
|
if (body !== void 0 && body !== null) {
|
|
@@ -10719,6 +10723,20 @@ async function apiRequest(options) {
|
|
|
10719
10723
|
});
|
|
10720
10724
|
return { ok: true, data };
|
|
10721
10725
|
}
|
|
10726
|
+
function analyzeRequest(options) {
|
|
10727
|
+
return apiRequest({
|
|
10728
|
+
method: "POST",
|
|
10729
|
+
path: "/analyze",
|
|
10730
|
+
serviceUrl: options.serviceUrl,
|
|
10731
|
+
token: options.token,
|
|
10732
|
+
body: options.body,
|
|
10733
|
+
timeout: options.timeout,
|
|
10734
|
+
cmd: options.cmd,
|
|
10735
|
+
verbose: options.verbose,
|
|
10736
|
+
retry: options.retry,
|
|
10737
|
+
encodeBody: true
|
|
10738
|
+
});
|
|
10739
|
+
}
|
|
10722
10740
|
|
|
10723
10741
|
// src/commands/auth.ts
|
|
10724
10742
|
function registerAuthCommands(program2) {
|
|
@@ -10830,8 +10848,21 @@ var VERITY_INTENT_HOOK = {
|
|
|
10830
10848
|
type: "command",
|
|
10831
10849
|
command: "verity intent capture"
|
|
10832
10850
|
};
|
|
10851
|
+
var GUARD_TIMEOUT = 300;
|
|
10852
|
+
function buildGuardHook(on) {
|
|
10853
|
+
return {
|
|
10854
|
+
type: "command",
|
|
10855
|
+
command: `verity guard --on ${on.join(",")}`,
|
|
10856
|
+
timeout: GUARD_TIMEOUT,
|
|
10857
|
+
statusMessage: "Running Verity git-moment review"
|
|
10858
|
+
};
|
|
10859
|
+
}
|
|
10833
10860
|
var VERITY_STOP_RE = /(?:^|[\/\s"'])verity\s+analyze\b/;
|
|
10834
10861
|
var VERITY_INTENT_RE = /(?:^|[\/\s"'])verity\s+intent\s+capture\b/;
|
|
10862
|
+
var VERITY_GUARD_RE = /(?:^|[\/\s"'])verity\s+guard\b/;
|
|
10863
|
+
function isVerityGuardHook(entry) {
|
|
10864
|
+
return VERITY_GUARD_RE.test(entry.command ?? "");
|
|
10865
|
+
}
|
|
10835
10866
|
var LEGACY_STOP_RE = /(?:^|[\/\s"'])gate\s+analyze\b/;
|
|
10836
10867
|
var LEGACY_INTENT_RE = /(?:^|[\/\s"'])gate\s+intent\s+capture\b/;
|
|
10837
10868
|
function isVerityStopHook(entry) {
|
|
@@ -10843,7 +10874,7 @@ function isVerityIntentHook(entry) {
|
|
|
10843
10874
|
return VERITY_INTENT_RE.test(c) || LEGACY_INTENT_RE.test(c) || c.includes(".verity/hooks/capture-intent.sh") || c.includes(".gate/hooks/capture-intent.sh");
|
|
10844
10875
|
}
|
|
10845
10876
|
function isVerityHook(entry) {
|
|
10846
|
-
return isVerityStopHook(entry) || isVerityIntentHook(entry);
|
|
10877
|
+
return isVerityStopHook(entry) || isVerityIntentHook(entry) || isVerityGuardHook(entry);
|
|
10847
10878
|
}
|
|
10848
10879
|
function isCurrentVerityStopHook(entry) {
|
|
10849
10880
|
const c = entry.command ?? "";
|
|
@@ -10854,7 +10885,7 @@ function isCurrentVerityIntentHook(entry) {
|
|
|
10854
10885
|
return VERITY_INTENT_RE.test(c) || c.includes(".verity/hooks/capture-intent.sh");
|
|
10855
10886
|
}
|
|
10856
10887
|
function isCurrentVerityHook(entry) {
|
|
10857
|
-
return isCurrentVerityStopHook(entry) || isCurrentVerityIntentHook(entry);
|
|
10888
|
+
return isCurrentVerityStopHook(entry) || isCurrentVerityIntentHook(entry) || isVerityGuardHook(entry);
|
|
10858
10889
|
}
|
|
10859
10890
|
function settingsHasLegacyHook(settings) {
|
|
10860
10891
|
for (const groups of Object.values(settings.hooks ?? {})) {
|
|
@@ -10894,12 +10925,16 @@ async function readAllSettings() {
|
|
|
10894
10925
|
async function checkAllVerityHooks() {
|
|
10895
10926
|
let stop = false;
|
|
10896
10927
|
let intent = false;
|
|
10928
|
+
let guard = false;
|
|
10929
|
+
let guardOn = [];
|
|
10897
10930
|
for (const settings of await readAllSettings()) {
|
|
10898
10931
|
const r = checkVerityHooks(settings);
|
|
10899
10932
|
stop = stop || r.stop;
|
|
10900
10933
|
intent = intent || r.intent;
|
|
10934
|
+
guard = guard || r.guard;
|
|
10935
|
+
if (r.guardOn.length > guardOn.length) guardOn = r.guardOn;
|
|
10901
10936
|
}
|
|
10902
|
-
return { stop, intent };
|
|
10937
|
+
return { stop, intent, guard, guardOn };
|
|
10903
10938
|
}
|
|
10904
10939
|
async function checkAllVerityHooksDetailed() {
|
|
10905
10940
|
let stop = false;
|
|
@@ -10962,7 +10997,19 @@ function checkVerityHooks(settings) {
|
|
|
10962
10997
|
const hasIntent = intentGroups.some(
|
|
10963
10998
|
(g) => g.hooks?.some((h) => isCurrentVerityIntentHook(h))
|
|
10964
10999
|
);
|
|
10965
|
-
|
|
11000
|
+
let guard = false;
|
|
11001
|
+
let guardOn = [];
|
|
11002
|
+
for (const g of hooks["PreToolUse"] ?? []) {
|
|
11003
|
+
for (const h of g.hooks ?? []) {
|
|
11004
|
+
if (!isVerityGuardHook(h)) continue;
|
|
11005
|
+
guard = true;
|
|
11006
|
+
const m = (h.command ?? "").match(/--on[=\s]+([a-z,]+)/);
|
|
11007
|
+
if (m) {
|
|
11008
|
+
guardOn = m[1].split(",").filter((x) => x === "commit" || x === "push");
|
|
11009
|
+
}
|
|
11010
|
+
}
|
|
11011
|
+
}
|
|
11012
|
+
return { stop: hasStop, intent: hasIntent, guard, guardOn };
|
|
10966
11013
|
}
|
|
10967
11014
|
function installVerityHooks(settings, force, externalPresent = { stop: false, intent: false }) {
|
|
10968
11015
|
const local = checkVerityHooks(settings);
|
|
@@ -11011,6 +11058,7 @@ function installVerityHooks(settings, force, externalPresent = { stop: false, in
|
|
|
11011
11058
|
function removeVerityHooks(settings) {
|
|
11012
11059
|
const newSettings = { ...settings };
|
|
11013
11060
|
if (!newSettings.hooks) return newSettings;
|
|
11061
|
+
newSettings.hooks = { ...newSettings.hooks };
|
|
11014
11062
|
for (const key of Object.keys(newSettings.hooks)) {
|
|
11015
11063
|
const groups = newSettings.hooks[key];
|
|
11016
11064
|
if (!groups) continue;
|
|
@@ -11027,12 +11075,60 @@ function removeVerityHooks(settings) {
|
|
|
11027
11075
|
}
|
|
11028
11076
|
return newSettings;
|
|
11029
11077
|
}
|
|
11078
|
+
function reconcileMomentHooks(settings, moments, externalPresent = {
|
|
11079
|
+
stop: false,
|
|
11080
|
+
intent: false,
|
|
11081
|
+
guardOn: []
|
|
11082
|
+
}) {
|
|
11083
|
+
const s = removeVerityHooks(settings);
|
|
11084
|
+
if (!s.hooks) s.hooks = {};
|
|
11085
|
+
const push = (event, group) => {
|
|
11086
|
+
(s.hooks[event] ??= []).push(group);
|
|
11087
|
+
};
|
|
11088
|
+
if (!externalPresent.intent) push("UserPromptSubmit", { hooks: [VERITY_INTENT_HOOK] });
|
|
11089
|
+
if (moments.includes("stop") && !externalPresent.stop) {
|
|
11090
|
+
push("Stop", { hooks: [VERITY_STOP_HOOK] });
|
|
11091
|
+
}
|
|
11092
|
+
const on = [];
|
|
11093
|
+
if (moments.includes("pre-commit")) on.push("commit");
|
|
11094
|
+
if (moments.includes("pre-push")) on.push("push");
|
|
11095
|
+
const remaining = on.filter((m) => !externalPresent.guardOn.includes(m));
|
|
11096
|
+
if (remaining.length > 0) {
|
|
11097
|
+
push("PreToolUse", { matcher: "Bash", hooks: [buildGuardHook(remaining)] });
|
|
11098
|
+
}
|
|
11099
|
+
for (const k of Object.keys(s.hooks)) {
|
|
11100
|
+
if (s.hooks[k].length === 0) delete s.hooks[k];
|
|
11101
|
+
}
|
|
11102
|
+
if (Object.keys(s.hooks).length === 0) delete s.hooks;
|
|
11103
|
+
return s;
|
|
11104
|
+
}
|
|
11030
11105
|
|
|
11031
11106
|
// src/commands/hooks.ts
|
|
11107
|
+
var ALL_MOMENTS = ["stop", "pre-commit", "pre-push"];
|
|
11108
|
+
function parseMoments(raw) {
|
|
11109
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11110
|
+
for (const part of raw.split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
11111
|
+
if (ALL_MOMENTS.includes(part)) seen.add(part);
|
|
11112
|
+
}
|
|
11113
|
+
return ALL_MOMENTS.filter((m) => seen.has(m));
|
|
11114
|
+
}
|
|
11032
11115
|
function registerHooksCommands(program2) {
|
|
11033
11116
|
const hooks = program2.command("hooks").description("Manage Claude Code hook wiring");
|
|
11034
|
-
hooks.command("install").description("Install Verity hooks into Claude Code settings").option("--force", "Overwrite existing Verity hooks").action(async (opts) => {
|
|
11117
|
+
hooks.command("install").description("Install Verity hooks into Claude Code settings").option("--force", "Overwrite existing Verity hooks").option("--moments <list>", "Reconcile to exactly these moments: stop,pre-commit,pre-push").action(async (opts) => {
|
|
11035
11118
|
const force = opts.force ?? false;
|
|
11119
|
+
if (opts.moments != null) {
|
|
11120
|
+
const moments = parseMoments(opts.moments);
|
|
11121
|
+
const external = await checkAllVerityHooks();
|
|
11122
|
+
const settings2 = reconcileMomentHooks(await readSettings(), moments, external);
|
|
11123
|
+
await writeSettings(settings2);
|
|
11124
|
+
const status = await checkAllVerityHooks();
|
|
11125
|
+
printInfo("Verity hooks reconciled in .claude/settings.json:");
|
|
11126
|
+
printInfo(` Stop (verity analyze): ${moments.includes("stop") ? "on" : "off"}`);
|
|
11127
|
+
printInfo(` Pre-commit gate: ${status.guardOn.includes("commit") ? "on" : "off"}`);
|
|
11128
|
+
printInfo(` Pre-push/PR gate: ${status.guardOn.includes("push") ? "on" : "off"}`);
|
|
11129
|
+
printInfo(" UserPromptSubmit (verity intent capture): on");
|
|
11130
|
+
return;
|
|
11131
|
+
}
|
|
11036
11132
|
const detail = await checkAllVerityHooksDetailed();
|
|
11037
11133
|
const present = { stop: detail.stop, intent: detail.intent };
|
|
11038
11134
|
if (detail.legacyOnly) {
|
|
@@ -11070,7 +11166,12 @@ function registerHooksCommands(program2) {
|
|
|
11070
11166
|
const status = await checkAllVerityHooks();
|
|
11071
11167
|
printInfo(`Stop hook (verity analyze): ${status.stop ? "installed" : "not installed"}`);
|
|
11072
11168
|
printInfo(`Intent hook (verity intent capture): ${status.intent ? "installed" : "not installed"}`);
|
|
11073
|
-
|
|
11169
|
+
const gates = status.guard ? status.guardOn.join(", ") : "none";
|
|
11170
|
+
printInfo(`Git-moment gate (verity guard): ${status.guard ? `installed [${gates}]` : "not installed"}`);
|
|
11171
|
+
if (!status.stop && !status.guard) {
|
|
11172
|
+
printWarn('No analysis moment is active (neither Stop nor a git-moment gate) \u2014 code changes will not be reviewed. Run "verity hooks install --moments stop" (or /verity-setup) to enable one.');
|
|
11173
|
+
}
|
|
11174
|
+
if (!status.intent) {
|
|
11074
11175
|
printWarn('Run "verity hooks install" to install missing hooks.');
|
|
11075
11176
|
}
|
|
11076
11177
|
});
|
|
@@ -12437,6 +12538,12 @@ function registerStatusCommand(program2) {
|
|
|
12437
12538
|
printInfo(`Standard: v${s.version} (${s.quality_dimensions} quality, ${s.security_patterns} security, ${s.custom_patterns} custom)`);
|
|
12438
12539
|
printInfo(`Languages: ${s.languages.join(", ")}`);
|
|
12439
12540
|
}
|
|
12541
|
+
const hookStatus = await checkAllVerityHooks();
|
|
12542
|
+
const moments = [];
|
|
12543
|
+
if (hookStatus.stop) moments.push("stop");
|
|
12544
|
+
if (hookStatus.guardOn.includes("commit")) moments.push("pre-commit");
|
|
12545
|
+
if (hookStatus.guardOn.includes("push")) moments.push("pre-push/PR");
|
|
12546
|
+
printInfo(`Moments: ${moments.length > 0 ? moments.join(", ") : "none (run /verity-setup)"}`);
|
|
12440
12547
|
if (mem.recent_runs) {
|
|
12441
12548
|
const r = mem.recent_runs;
|
|
12442
12549
|
printInfo("");
|
|
@@ -12677,6 +12784,31 @@ function getChangedFiles() {
|
|
|
12677
12784
|
const filtered = Array.from(sets).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
12678
12785
|
return { files: filtered, hasRecentCommitFiles };
|
|
12679
12786
|
}
|
|
12787
|
+
function getStagedFiles() {
|
|
12788
|
+
return splitLines(execGit("git diff --cached --name-only")).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
12789
|
+
}
|
|
12790
|
+
function getPushRangeFiles() {
|
|
12791
|
+
const diff = (range) => splitLines(execGit(`git diff --name-only ${range}`)).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
12792
|
+
const resolvers = [
|
|
12793
|
+
() => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{push}") ? "@{push}..HEAD" : null,
|
|
12794
|
+
() => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{upstream}") ? "@{upstream}..HEAD" : null,
|
|
12795
|
+
() => {
|
|
12796
|
+
const branch = execGit("git rev-parse --abbrev-ref HEAD");
|
|
12797
|
+
return branch && branch !== "HEAD" && execGit(`git rev-parse --verify -q origin/${branch}`) ? `origin/${branch}..HEAD` : null;
|
|
12798
|
+
}
|
|
12799
|
+
];
|
|
12800
|
+
for (const resolve of resolvers) {
|
|
12801
|
+
const range = resolve();
|
|
12802
|
+
if (range) return { files: diff(range), range };
|
|
12803
|
+
}
|
|
12804
|
+
const baseline = readBaselineSha();
|
|
12805
|
+
if (baseline) {
|
|
12806
|
+
const files = diff(`${baseline}..HEAD`);
|
|
12807
|
+
if (files.length > 0) return { files, range: `${baseline}..HEAD` };
|
|
12808
|
+
}
|
|
12809
|
+
const last = diff("HEAD~1..HEAD");
|
|
12810
|
+
return { files: last, range: last.length > 0 ? "HEAD~1..HEAD" : null };
|
|
12811
|
+
}
|
|
12680
12812
|
function getWorktreeFiles() {
|
|
12681
12813
|
const result = [];
|
|
12682
12814
|
const worktreeDir = ".claude/worktrees";
|
|
@@ -14107,7 +14239,7 @@ async function runSeed(opts) {
|
|
|
14107
14239
|
var MAX_ASSISTANT_RESPONSE_CHARS_PLAN = 32768;
|
|
14108
14240
|
var MAX_ASSISTANT_RESPONSE_CHARS_DEFAULT = 8e3;
|
|
14109
14241
|
async function readStopHookStdin() {
|
|
14110
|
-
const empty = { assistantMessage: null, stopReason: null, transcriptPath: null };
|
|
14242
|
+
const empty = { assistantMessage: null, stopReason: null, transcriptPath: null, sessionId: null };
|
|
14111
14243
|
try {
|
|
14112
14244
|
if (process.stdin.isTTY) return empty;
|
|
14113
14245
|
const chunks = [];
|
|
@@ -14125,7 +14257,8 @@ async function readStopHookStdin() {
|
|
|
14125
14257
|
resolve({
|
|
14126
14258
|
assistantMessage: typeof data.last_assistant_message === "string" ? data.last_assistant_message : null,
|
|
14127
14259
|
stopReason: typeof data.stop_reason === "string" ? data.stop_reason : null,
|
|
14128
|
-
transcriptPath: typeof data.transcript_path === "string" ? data.transcript_path : null
|
|
14260
|
+
transcriptPath: typeof data.transcript_path === "string" ? data.transcript_path : null,
|
|
14261
|
+
sessionId: typeof data.session_id === "string" ? data.session_id : null
|
|
14129
14262
|
});
|
|
14130
14263
|
} catch {
|
|
14131
14264
|
resolve(empty);
|
|
@@ -14158,7 +14291,7 @@ async function runAnalyze(opts, globals) {
|
|
|
14158
14291
|
process.chdir(repoRoot());
|
|
14159
14292
|
} catch {
|
|
14160
14293
|
}
|
|
14161
|
-
const { assistantMessage: assistantResponse, stopReason, transcriptPath } = await readStopHookStdin();
|
|
14294
|
+
const { assistantMessage: assistantResponse, stopReason, transcriptPath, sessionId } = await readStopHookStdin();
|
|
14162
14295
|
const actionSummary = transcriptPath ? await extractActionSummary(transcriptPath) : null;
|
|
14163
14296
|
const { files: allChanged, hasRecentCommitFiles } = getChangedFiles();
|
|
14164
14297
|
const analyzable = filterAnalyzable(allChanged);
|
|
@@ -14191,7 +14324,7 @@ async function runAnalyze(opts, globals) {
|
|
|
14191
14324
|
if (!urlResult.ok) {
|
|
14192
14325
|
passAndExit("Not configured yet \u2014 run /verity-setup in Claude Code to enable quality gates.");
|
|
14193
14326
|
}
|
|
14194
|
-
const sessionIdForMemory = process.env.CLAUDE_SESSION_ID
|
|
14327
|
+
const sessionIdForMemory = sessionId || process.env.CLAUDE_SESSION_ID || "";
|
|
14195
14328
|
let contextFilePaths = [];
|
|
14196
14329
|
let predictedMode;
|
|
14197
14330
|
try {
|
|
@@ -14389,7 +14522,7 @@ async function runAnalyze(opts, globals) {
|
|
|
14389
14522
|
context: {
|
|
14390
14523
|
agent_model: process.env.CLAUDE_MODEL ?? "unknown",
|
|
14391
14524
|
agent_harness: "claude-code",
|
|
14392
|
-
session_id: process.env.CLAUDE_SESSION_ID
|
|
14525
|
+
session_id: sessionId || process.env.CLAUDE_SESSION_ID || "unknown",
|
|
14393
14526
|
iteration
|
|
14394
14527
|
},
|
|
14395
14528
|
...memoryManifest && { memory_manifest: memoryManifest },
|
|
@@ -14430,16 +14563,13 @@ async function runAnalyze(opts, globals) {
|
|
|
14430
14563
|
requestBody.intent_context = intentContext;
|
|
14431
14564
|
}
|
|
14432
14565
|
const ANALYZE_TIMEOUT_MS = 1e5;
|
|
14433
|
-
let result = await
|
|
14434
|
-
method: "POST",
|
|
14435
|
-
path: "/analyze",
|
|
14566
|
+
let result = await analyzeRequest({
|
|
14436
14567
|
serviceUrl: urlResult.data,
|
|
14437
14568
|
token: tokenResult.data.token,
|
|
14438
14569
|
body: requestBody,
|
|
14439
14570
|
verbose: globals.verbose,
|
|
14440
14571
|
timeout: ANALYZE_TIMEOUT_MS,
|
|
14441
|
-
cmd: "analyze"
|
|
14442
|
-
encodeBody: true
|
|
14572
|
+
cmd: "analyze"
|
|
14443
14573
|
});
|
|
14444
14574
|
if (!result.ok && shouldWarmRetryAnalyze(result)) {
|
|
14445
14575
|
logEvent("analyze_warm_retry", {
|
|
@@ -14456,17 +14586,14 @@ async function runAnalyze(opts, globals) {
|
|
|
14456
14586
|
timeout: 15e3,
|
|
14457
14587
|
cmd: "analyze_warmup"
|
|
14458
14588
|
});
|
|
14459
|
-
result = await
|
|
14460
|
-
method: "POST",
|
|
14461
|
-
path: "/analyze",
|
|
14589
|
+
result = await analyzeRequest({
|
|
14462
14590
|
serviceUrl: urlResult.data,
|
|
14463
14591
|
token: tokenResult.data.token,
|
|
14464
14592
|
body: requestBody,
|
|
14465
14593
|
verbose: globals.verbose,
|
|
14466
14594
|
timeout: ANALYZE_TIMEOUT_MS,
|
|
14467
14595
|
cmd: "analyze",
|
|
14468
|
-
retry: true
|
|
14469
|
-
encodeBody: true
|
|
14596
|
+
retry: true
|
|
14470
14597
|
});
|
|
14471
14598
|
}
|
|
14472
14599
|
if (!result.ok) {
|
|
@@ -14727,8 +14854,8 @@ async function runReview(opts, globals) {
|
|
|
14727
14854
|
for (const p of specPaths) {
|
|
14728
14855
|
if (!(0, import_node_fs19.existsSync)(p)) continue;
|
|
14729
14856
|
try {
|
|
14730
|
-
const { readFileSync:
|
|
14731
|
-
const content =
|
|
14857
|
+
const { readFileSync: readFileSync11 } = await import("node:fs");
|
|
14858
|
+
const content = readFileSync11(p, "utf-8");
|
|
14732
14859
|
specs.push({ path: p, content: content.slice(0, 10240) });
|
|
14733
14860
|
} catch {
|
|
14734
14861
|
}
|
|
@@ -14758,14 +14885,13 @@ async function runReview(opts, globals) {
|
|
|
14758
14885
|
if (plans.length > 0) intentContext.plans = plans;
|
|
14759
14886
|
requestBody.intent_context = intentContext;
|
|
14760
14887
|
}
|
|
14761
|
-
const result = await
|
|
14762
|
-
method: "POST",
|
|
14763
|
-
path: "/analyze",
|
|
14888
|
+
const result = await analyzeRequest({
|
|
14764
14889
|
serviceUrl: urlResult.data,
|
|
14765
14890
|
token: tokenResult.data.token,
|
|
14766
14891
|
body: requestBody,
|
|
14767
14892
|
verbose: globals.verbose,
|
|
14768
|
-
timeout: 12e4
|
|
14893
|
+
timeout: 12e4,
|
|
14894
|
+
cmd: "review"
|
|
14769
14895
|
});
|
|
14770
14896
|
if (!result.ok) {
|
|
14771
14897
|
printError(`Service error: ${result.error}`);
|
|
@@ -14785,15 +14911,270 @@ async function runReview(opts, globals) {
|
|
|
14785
14911
|
process.exit(0);
|
|
14786
14912
|
}
|
|
14787
14913
|
|
|
14914
|
+
// src/commands/guard.ts
|
|
14915
|
+
var import_node_fs20 = require("node:fs");
|
|
14916
|
+
var import_node_path14 = require("node:path");
|
|
14917
|
+
var GUARD_BLOCK_CAP = 2;
|
|
14918
|
+
var GUARD_ITER_FILE = (0, import_node_path14.join)(VERITY_DIR, ".guard-iteration");
|
|
14919
|
+
function readPreToolUseStdin() {
|
|
14920
|
+
const empty = { command: "", cwd: null, sessionId: null };
|
|
14921
|
+
return new Promise((resolve) => {
|
|
14922
|
+
try {
|
|
14923
|
+
if (process.stdin.isTTY) return resolve(empty);
|
|
14924
|
+
const chunks = [];
|
|
14925
|
+
let timer;
|
|
14926
|
+
let settled = false;
|
|
14927
|
+
const onData = (c) => chunks.push(c);
|
|
14928
|
+
const settle = (value) => {
|
|
14929
|
+
if (settled) return;
|
|
14930
|
+
settled = true;
|
|
14931
|
+
if (timer) clearTimeout(timer);
|
|
14932
|
+
process.stdin.removeListener("data", onData);
|
|
14933
|
+
process.stdin.removeListener("end", onEnd);
|
|
14934
|
+
process.stdin.removeListener("error", onError);
|
|
14935
|
+
process.stdin.pause();
|
|
14936
|
+
resolve(value);
|
|
14937
|
+
};
|
|
14938
|
+
const onEnd = () => {
|
|
14939
|
+
try {
|
|
14940
|
+
const data = JSON.parse(Buffer.concat(chunks).toString("utf-8"));
|
|
14941
|
+
settle({
|
|
14942
|
+
command: typeof data?.tool_input?.command === "string" ? data.tool_input.command : "",
|
|
14943
|
+
cwd: typeof data?.cwd === "string" ? data.cwd : null,
|
|
14944
|
+
sessionId: typeof data?.session_id === "string" ? data.session_id : null
|
|
14945
|
+
});
|
|
14946
|
+
} catch {
|
|
14947
|
+
settle(empty);
|
|
14948
|
+
}
|
|
14949
|
+
};
|
|
14950
|
+
const onError = () => settle(empty);
|
|
14951
|
+
timer = setTimeout(() => settle(empty), 2e3);
|
|
14952
|
+
process.stdin.on("data", onData);
|
|
14953
|
+
process.stdin.on("end", onEnd);
|
|
14954
|
+
process.stdin.on("error", onError);
|
|
14955
|
+
process.stdin.resume();
|
|
14956
|
+
} catch {
|
|
14957
|
+
resolve(empty);
|
|
14958
|
+
}
|
|
14959
|
+
});
|
|
14960
|
+
}
|
|
14961
|
+
function buildCommandRe(head) {
|
|
14962
|
+
return new RegExp(`(?:^|[\\s;&|(])${head}|(?:^|[;&|(])\\s*[^\\s;&|()'"]*\\/${head}`);
|
|
14963
|
+
}
|
|
14964
|
+
var GIT_GLOBAL_OPTS = "(?:\\s+(?:-[Cc]\\s+\\S+|--?[\\w-]+(?:=\\S+)?))*";
|
|
14965
|
+
var COMMIT_RE = buildCommandRe(`git${GIT_GLOBAL_OPTS}\\s+commit(?![\\w-])`);
|
|
14966
|
+
var PUSH_RE = buildCommandRe(`git${GIT_GLOBAL_OPTS}\\s+push\\b`);
|
|
14967
|
+
var GH_PR_RE = buildCommandRe(`gh${GIT_GLOBAL_OPTS}\\s+pr\\s+create\\b`);
|
|
14968
|
+
function classifyCommand(command, on) {
|
|
14969
|
+
let commit = false;
|
|
14970
|
+
let push = false;
|
|
14971
|
+
for (const seg of (command ?? "").split(/&&|\|\||;|\n/)) {
|
|
14972
|
+
if (/--dry-run\b/.test(seg)) continue;
|
|
14973
|
+
if (COMMIT_RE.test(seg)) commit = true;
|
|
14974
|
+
if (PUSH_RE.test(seg) || GH_PR_RE.test(seg)) push = true;
|
|
14975
|
+
}
|
|
14976
|
+
if (commit && on.includes("commit")) return "pre-commit";
|
|
14977
|
+
if (push && on.includes("push")) return "pre-push";
|
|
14978
|
+
return null;
|
|
14979
|
+
}
|
|
14980
|
+
function readIterMap() {
|
|
14981
|
+
try {
|
|
14982
|
+
const raw = JSON.parse((0, import_node_fs20.readFileSync)(GUARD_ITER_FILE, "utf-8"));
|
|
14983
|
+
if (raw && typeof raw === "object") {
|
|
14984
|
+
if (typeof raw.moment === "string" && typeof raw.count === "number") {
|
|
14985
|
+
return { [raw.moment]: raw.count };
|
|
14986
|
+
}
|
|
14987
|
+
const out = {};
|
|
14988
|
+
for (const m of ["pre-commit", "pre-push"]) {
|
|
14989
|
+
if (typeof raw[m] === "number") out[m] = raw[m];
|
|
14990
|
+
}
|
|
14991
|
+
return out;
|
|
14992
|
+
}
|
|
14993
|
+
} catch {
|
|
14994
|
+
}
|
|
14995
|
+
return {};
|
|
14996
|
+
}
|
|
14997
|
+
function readIter(moment) {
|
|
14998
|
+
return readIterMap()[moment] ?? 0;
|
|
14999
|
+
}
|
|
15000
|
+
function writeIter(moment, count) {
|
|
15001
|
+
try {
|
|
15002
|
+
(0, import_node_fs20.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
15003
|
+
const map = readIterMap();
|
|
15004
|
+
map[moment] = count;
|
|
15005
|
+
(0, import_node_fs20.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
|
|
15006
|
+
} catch {
|
|
15007
|
+
}
|
|
15008
|
+
}
|
|
15009
|
+
function resetIter(moment) {
|
|
15010
|
+
try {
|
|
15011
|
+
const map = readIterMap();
|
|
15012
|
+
if (!(moment in map)) return;
|
|
15013
|
+
delete map[moment];
|
|
15014
|
+
if (Object.keys(map).length === 0) {
|
|
15015
|
+
if ((0, import_node_fs20.existsSync)(GUARD_ITER_FILE)) (0, import_node_fs20.unlinkSync)(GUARD_ITER_FILE);
|
|
15016
|
+
} else {
|
|
15017
|
+
(0, import_node_fs20.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
15018
|
+
(0, import_node_fs20.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
|
|
15019
|
+
}
|
|
15020
|
+
} catch {
|
|
15021
|
+
}
|
|
15022
|
+
}
|
|
15023
|
+
function registerGuardCommand(program2) {
|
|
15024
|
+
program2.command("guard").description("Git-moment gate: review staged/to-push changes before a commit/push (PreToolUse hook)").option("--on <moments>", "Which git moments to gate: commit,push", "commit,push").option("--json", "Output raw JSON response (debug)").action(async (opts) => {
|
|
15025
|
+
const globals = program2.opts();
|
|
15026
|
+
try {
|
|
15027
|
+
await runGuard(opts, globals);
|
|
15028
|
+
} catch {
|
|
15029
|
+
process.exit(0);
|
|
15030
|
+
}
|
|
15031
|
+
});
|
|
15032
|
+
}
|
|
15033
|
+
function getMomentFiles(moment) {
|
|
15034
|
+
return moment === "pre-commit" ? getStagedFiles() : getPushRangeFiles().files;
|
|
15035
|
+
}
|
|
15036
|
+
function buildGuardRequest(moment, files, iter, sessionId) {
|
|
15037
|
+
const analyzable = filterAnalyzable(files);
|
|
15038
|
+
const securityFiles = filterSecurity(files);
|
|
15039
|
+
let staticResults;
|
|
15040
|
+
if (isCodacyAvailable()) {
|
|
15041
|
+
const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0, import_node_fs20.existsSync)(f) ? f : resolveFile(f)).filter((f) => f !== null);
|
|
15042
|
+
staticResults = runCodacyAnalysis(scannable);
|
|
15043
|
+
} else {
|
|
15044
|
+
staticResults = { tool: "@codacy/analysis-cli", findings: [], summary: { total_findings: 0, by_severity: {}, tools_run: [] } };
|
|
15045
|
+
}
|
|
15046
|
+
const codeDelta = collectCodeDelta(files);
|
|
15047
|
+
if (codeDelta.total_files === 0) return null;
|
|
15048
|
+
const trigger = moment === "pre-commit" ? "hook:pre-commit" : "hook:pre-push";
|
|
15049
|
+
const requestBody = {
|
|
15050
|
+
static_results: staticResults,
|
|
15051
|
+
code_delta: codeDelta,
|
|
15052
|
+
changed_files: files,
|
|
15053
|
+
standard_version: "latest",
|
|
15054
|
+
context: {
|
|
15055
|
+
agent_model: process.env.CLAUDE_MODEL ?? "unknown",
|
|
15056
|
+
agent_harness: "claude-code",
|
|
15057
|
+
// Prefer the session id Claude Code passes on the hook payload; fall back
|
|
15058
|
+
// to the env var, then 'unknown'.
|
|
15059
|
+
session_id: sessionId ?? process.env.CLAUDE_SESSION_ID ?? "unknown",
|
|
15060
|
+
trigger,
|
|
15061
|
+
iteration: iter + 1
|
|
15062
|
+
}
|
|
15063
|
+
};
|
|
15064
|
+
const specs = discoverSpecs();
|
|
15065
|
+
const plans = discoverPlans();
|
|
15066
|
+
if (specs.length > 0 || plans.length > 0) {
|
|
15067
|
+
const intentContext = {};
|
|
15068
|
+
if (specs.length > 0) intentContext.specs = specs;
|
|
15069
|
+
if (plans.length > 0) intentContext.plans = plans;
|
|
15070
|
+
requestBody.intent_context = intentContext;
|
|
15071
|
+
}
|
|
15072
|
+
return requestBody;
|
|
15073
|
+
}
|
|
15074
|
+
function allowWithNotice(moment, decision) {
|
|
15075
|
+
resetIter(moment);
|
|
15076
|
+
if (decision === "WARN") {
|
|
15077
|
+
process.stderr.write(`${YELLOW}Verity ${moment}: WARN \u2014 proceeding. See findings in the report.${NC}
|
|
15078
|
+
`);
|
|
15079
|
+
}
|
|
15080
|
+
process.exit(0);
|
|
15081
|
+
}
|
|
15082
|
+
async function runGuard(opts, globals) {
|
|
15083
|
+
const on = opts.on.split(",").map((s) => s.trim()).filter((s) => s === "commit" || s === "push");
|
|
15084
|
+
const { command, cwd, sessionId } = await readPreToolUseStdin();
|
|
15085
|
+
if (cwd && (0, import_node_fs20.existsSync)(cwd)) {
|
|
15086
|
+
try {
|
|
15087
|
+
process.chdir(cwd);
|
|
15088
|
+
} catch {
|
|
15089
|
+
}
|
|
15090
|
+
}
|
|
15091
|
+
const moment = classifyCommand(command, on);
|
|
15092
|
+
if (!moment) process.exit(0);
|
|
15093
|
+
const iter = readIter(moment);
|
|
15094
|
+
if (iter >= GUARD_BLOCK_CAP) {
|
|
15095
|
+
process.stderr.write(`${DIM}Verity ${moment}: ${GUARD_BLOCK_CAP} review cycles reached \u2014 allowing through.${NC}
|
|
15096
|
+
`);
|
|
15097
|
+
resetIter(moment);
|
|
15098
|
+
process.exit(0);
|
|
15099
|
+
}
|
|
15100
|
+
const files = getMomentFiles(moment);
|
|
15101
|
+
if (files.length === 0) process.exit(0);
|
|
15102
|
+
const tokenResult = await resolveToken(globals.token);
|
|
15103
|
+
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
15104
|
+
if (!tokenResult.ok || !urlResult.ok) process.exit(0);
|
|
15105
|
+
const requestBody = buildGuardRequest(moment, files, iter, sessionId);
|
|
15106
|
+
if (!requestBody) process.exit(0);
|
|
15107
|
+
const result = await analyzeRequest({
|
|
15108
|
+
serviceUrl: urlResult.data,
|
|
15109
|
+
token: tokenResult.data.token,
|
|
15110
|
+
body: requestBody,
|
|
15111
|
+
verbose: globals.verbose,
|
|
15112
|
+
timeout: 29e4,
|
|
15113
|
+
cmd: "guard"
|
|
15114
|
+
});
|
|
15115
|
+
if (!result.ok) {
|
|
15116
|
+
process.stderr.write(`${DIM}Verity ${moment}: service unavailable \u2014 allowing commit/push (${result.error}).${NC}
|
|
15117
|
+
`);
|
|
15118
|
+
process.exit(0);
|
|
15119
|
+
}
|
|
15120
|
+
if (opts.json) {
|
|
15121
|
+
process.stdout.write(JSON.stringify(result.data) + "\n");
|
|
15122
|
+
}
|
|
15123
|
+
const response = result.data;
|
|
15124
|
+
const decision = response.gate_decision ?? "PASS";
|
|
15125
|
+
if (decision === "FAIL") {
|
|
15126
|
+
writeIter(moment, iter + 1);
|
|
15127
|
+
writeBlockMessage(moment, response);
|
|
15128
|
+
process.exit(2);
|
|
15129
|
+
}
|
|
15130
|
+
allowWithNotice(moment, decision);
|
|
15131
|
+
}
|
|
15132
|
+
function writeBlockMessage(moment, response) {
|
|
15133
|
+
const label2 = moment === "pre-commit" ? "pre-commit" : "pre-push";
|
|
15134
|
+
const verb = moment === "pre-commit" ? "commit" : "push";
|
|
15135
|
+
const assessment = response.assessment;
|
|
15136
|
+
const narrative = assessment?.narrative ?? "";
|
|
15137
|
+
const findings = response.findings ?? [];
|
|
15138
|
+
const viewUrl = response.view_url ?? "";
|
|
15139
|
+
process.stderr.write(`${RED}${BOLD}\u2501\u2501\u2501 Verity ${label2} gate: FAIL \u2501\u2501\u2501${NC}
|
|
15140
|
+
|
|
15141
|
+
`);
|
|
15142
|
+
if (narrative) process.stderr.write(`${DIM}${narrative}${NC}
|
|
15143
|
+
|
|
15144
|
+
`);
|
|
15145
|
+
const agentFindings = findings.filter((f) => f.scope !== "pre-existing");
|
|
15146
|
+
if (agentFindings.length > 0) {
|
|
15147
|
+
process.stderr.write(`${BOLD}${agentFindings.length} issue(s) in the ${verb} \u2014 fix critical/high severity before ${verb === "commit" ? "committing" : "pushing"}:${NC}
|
|
15148
|
+
|
|
15149
|
+
`);
|
|
15150
|
+
for (const f of agentFindings) {
|
|
15151
|
+
const severity = (f.severity ?? "").toUpperCase();
|
|
15152
|
+
const fix = f.fix;
|
|
15153
|
+
process.stderr.write(`[${severity}] ${f.title ?? ""}
|
|
15154
|
+
`);
|
|
15155
|
+
process.stderr.write(` File: ${f.file ?? ""}:${f.line ?? ""}
|
|
15156
|
+
`);
|
|
15157
|
+
process.stderr.write(` Fix: ${fix?.description ?? "See description"}
|
|
15158
|
+
|
|
15159
|
+
`);
|
|
15160
|
+
}
|
|
15161
|
+
}
|
|
15162
|
+
if (viewUrl) process.stderr.write(`${CYAN}Full report: ${viewUrl}${NC}
|
|
15163
|
+
|
|
15164
|
+
`);
|
|
15165
|
+
process.stderr.write(`${BOLD}Fix these, then re-run the ${verb}.${NC}
|
|
15166
|
+
`);
|
|
15167
|
+
}
|
|
15168
|
+
|
|
14788
15169
|
// src/commands/init.ts
|
|
14789
|
-
var
|
|
15170
|
+
var import_node_fs22 = require("node:fs");
|
|
14790
15171
|
var import_promises12 = require("node:fs/promises");
|
|
14791
|
-
var
|
|
15172
|
+
var import_node_path16 = require("node:path");
|
|
14792
15173
|
var import_node_child_process9 = require("node:child_process");
|
|
14793
15174
|
|
|
14794
15175
|
// src/commands/migrate.ts
|
|
14795
|
-
var
|
|
14796
|
-
var
|
|
15176
|
+
var import_node_fs21 = require("node:fs");
|
|
15177
|
+
var import_node_path15 = require("node:path");
|
|
14797
15178
|
var import_node_child_process8 = require("node:child_process");
|
|
14798
15179
|
var LEGACY_NPM_PACKAGE = "@codacy/gate-cli";
|
|
14799
15180
|
function defaultNpmRemover(pkg) {
|
|
@@ -14829,12 +15210,12 @@ async function runMigration(opts = {}) {
|
|
|
14829
15210
|
return { actions, migrated: actions.length > 0 };
|
|
14830
15211
|
}
|
|
14831
15212
|
function migrateProjectDir(root, actions) {
|
|
14832
|
-
const gateDir = (0,
|
|
14833
|
-
const verityDir = (0,
|
|
14834
|
-
if ((0,
|
|
15213
|
+
const gateDir = (0, import_node_path15.join)(root, ".gate");
|
|
15214
|
+
const verityDir = (0, import_node_path15.join)(root, ".verity");
|
|
15215
|
+
if ((0, import_node_fs21.existsSync)(gateDir) && !(0, import_node_fs21.existsSync)(verityDir)) {
|
|
14835
15216
|
return migrateProjectDirRename(root, gateDir, verityDir, actions);
|
|
14836
15217
|
}
|
|
14837
|
-
if ((0,
|
|
15218
|
+
if ((0, import_node_fs21.existsSync)(gateDir) && (0, import_node_fs21.existsSync)(verityDir)) {
|
|
14838
15219
|
return migrateProjectDirCarry(gateDir, verityDir, actions);
|
|
14839
15220
|
}
|
|
14840
15221
|
return false;
|
|
@@ -14855,13 +15236,13 @@ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
|
|
|
14855
15236
|
}
|
|
14856
15237
|
}
|
|
14857
15238
|
if (moved) {
|
|
14858
|
-
if ((0,
|
|
15239
|
+
if ((0, import_node_fs21.existsSync)(gateDir)) {
|
|
14859
15240
|
const carried = carryLegacyContents(gateDir, verityDir);
|
|
14860
15241
|
if (carried > 0) {
|
|
14861
15242
|
actions.push(`Carried ${carried} untracked legacy file(s) from .gate/ into .verity/`);
|
|
14862
15243
|
}
|
|
14863
15244
|
try {
|
|
14864
|
-
(0,
|
|
15245
|
+
(0, import_node_fs21.rmSync)(gateDir, { recursive: true, force: true });
|
|
14865
15246
|
} catch {
|
|
14866
15247
|
}
|
|
14867
15248
|
}
|
|
@@ -14877,18 +15258,18 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
|
|
|
14877
15258
|
actions.push(`Carried ${carried} legacy file(s) from .gate/ into .verity/`);
|
|
14878
15259
|
}
|
|
14879
15260
|
try {
|
|
14880
|
-
(0,
|
|
15261
|
+
(0, import_node_fs21.rmSync)(gateDir, { recursive: true, force: true });
|
|
14881
15262
|
} catch {
|
|
14882
15263
|
}
|
|
14883
15264
|
return carried > 0;
|
|
14884
15265
|
}
|
|
14885
15266
|
function migrateGlobalCredentials(home, actions) {
|
|
14886
15267
|
if (!home) return;
|
|
14887
|
-
const gateCreds = (0,
|
|
14888
|
-
const verityCreds = (0,
|
|
14889
|
-
if (!(0,
|
|
14890
|
-
if (!(0,
|
|
14891
|
-
(0,
|
|
15268
|
+
const gateCreds = (0, import_node_path15.join)(home, ".gate", "credentials");
|
|
15269
|
+
const verityCreds = (0, import_node_path15.join)(home, ".verity", "credentials");
|
|
15270
|
+
if (!(0, import_node_fs21.existsSync)(gateCreds)) return;
|
|
15271
|
+
if (!(0, import_node_fs21.existsSync)(verityCreds)) {
|
|
15272
|
+
(0, import_node_fs21.mkdirSync)((0, import_node_path15.join)(home, ".verity"), { recursive: true });
|
|
14892
15273
|
moveFile(gateCreds, verityCreds);
|
|
14893
15274
|
actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
|
|
14894
15275
|
return;
|
|
@@ -14910,8 +15291,8 @@ async function migrateLegacyHooks(root, actions) {
|
|
|
14910
15291
|
}
|
|
14911
15292
|
}
|
|
14912
15293
|
async function migrateClaudeMd(root, actions) {
|
|
14913
|
-
const claudeMd = (0,
|
|
14914
|
-
const hadLegacyBlock = (0,
|
|
15294
|
+
const claudeMd = (0, import_node_path15.join)(root, "CLAUDE.md");
|
|
15295
|
+
const hadLegacyBlock = (0, import_node_fs21.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
|
|
14915
15296
|
if (!hadLegacyBlock) return;
|
|
14916
15297
|
try {
|
|
14917
15298
|
await ensureClaudeMdPointer(root);
|
|
@@ -14921,9 +15302,9 @@ async function migrateClaudeMd(root, actions) {
|
|
|
14921
15302
|
}
|
|
14922
15303
|
}
|
|
14923
15304
|
function migrateStandardFile(root, actions) {
|
|
14924
|
-
const gateMd = (0,
|
|
14925
|
-
const verityMd = (0,
|
|
14926
|
-
if (!(0,
|
|
15305
|
+
const gateMd = (0, import_node_path15.join)(root, "GATE.md");
|
|
15306
|
+
const verityMd = (0, import_node_path15.join)(root, "VERITY.md");
|
|
15307
|
+
if (!(0, import_node_fs21.existsSync)(gateMd) || (0, import_node_fs21.existsSync)(verityMd)) return;
|
|
14927
15308
|
let moved = false;
|
|
14928
15309
|
if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
|
|
14929
15310
|
try {
|
|
@@ -14935,7 +15316,7 @@ function migrateStandardFile(root, actions) {
|
|
|
14935
15316
|
if (!moved) moveFile(gateMd, verityMd);
|
|
14936
15317
|
const content = readFileSyncSafe(verityMd);
|
|
14937
15318
|
const refreshed = content.split("GATE.md").join("VERITY.md");
|
|
14938
|
-
if (refreshed !== content) (0,
|
|
15319
|
+
if (refreshed !== content) (0, import_node_fs21.writeFileSync)(verityMd, refreshed);
|
|
14939
15320
|
actions.push("Renamed GATE.md \u2192 VERITY.md");
|
|
14940
15321
|
}
|
|
14941
15322
|
function removeLegacyPackage(movedProjectDir, npmRemover, actions) {
|
|
@@ -14969,14 +15350,14 @@ function mergeGlobalCredentials(gateCreds, verityCreds) {
|
|
|
14969
15350
|
}
|
|
14970
15351
|
if (toAppend.length > 0) {
|
|
14971
15352
|
const sep = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
|
|
14972
|
-
(0,
|
|
15353
|
+
(0, import_node_fs21.writeFileSync)(verityCreds, verityContent + sep + toAppend.join("\n") + "\n");
|
|
14973
15354
|
}
|
|
14974
|
-
(0,
|
|
15355
|
+
(0, import_node_fs21.rmSync)(gateCreds, { force: true });
|
|
14975
15356
|
return toAppend.length;
|
|
14976
15357
|
}
|
|
14977
15358
|
function readFileSyncSafe(path) {
|
|
14978
15359
|
try {
|
|
14979
|
-
return (0,
|
|
15360
|
+
return (0, import_node_fs21.readFileSync)(path, "utf-8");
|
|
14980
15361
|
} catch {
|
|
14981
15362
|
return "";
|
|
14982
15363
|
}
|
|
@@ -14991,35 +15372,35 @@ function hasStagedChanges(root) {
|
|
|
14991
15372
|
}
|
|
14992
15373
|
function moveDir(from, to) {
|
|
14993
15374
|
try {
|
|
14994
|
-
(0,
|
|
15375
|
+
(0, import_node_fs21.renameSync)(from, to);
|
|
14995
15376
|
} catch (err) {
|
|
14996
15377
|
if (err.code !== "EXDEV") throw err;
|
|
14997
|
-
(0,
|
|
14998
|
-
(0,
|
|
15378
|
+
(0, import_node_fs21.cpSync)(from, to, { recursive: true });
|
|
15379
|
+
(0, import_node_fs21.rmSync)(from, { recursive: true, force: true });
|
|
14999
15380
|
}
|
|
15000
15381
|
}
|
|
15001
15382
|
function moveFile(from, to) {
|
|
15002
15383
|
try {
|
|
15003
|
-
(0,
|
|
15384
|
+
(0, import_node_fs21.renameSync)(from, to);
|
|
15004
15385
|
} catch (err) {
|
|
15005
15386
|
if (err.code !== "EXDEV") throw err;
|
|
15006
|
-
(0,
|
|
15007
|
-
(0,
|
|
15387
|
+
(0, import_node_fs21.cpSync)(from, to);
|
|
15388
|
+
(0, import_node_fs21.rmSync)(from, { force: true });
|
|
15008
15389
|
}
|
|
15009
15390
|
}
|
|
15010
15391
|
function carryLegacyContents(gateDir, verityDir) {
|
|
15011
15392
|
let copied = 0;
|
|
15012
15393
|
const walk = (relDir) => {
|
|
15013
|
-
const srcDir = (0,
|
|
15014
|
-
for (const entry of (0,
|
|
15015
|
-
const rel = relDir ? (0,
|
|
15016
|
-
const src = (0,
|
|
15017
|
-
const dest = (0,
|
|
15018
|
-
if ((0,
|
|
15394
|
+
const srcDir = (0, import_node_path15.join)(gateDir, relDir);
|
|
15395
|
+
for (const entry of (0, import_node_fs21.readdirSync)(srcDir)) {
|
|
15396
|
+
const rel = relDir ? (0, import_node_path15.join)(relDir, entry) : entry;
|
|
15397
|
+
const src = (0, import_node_path15.join)(gateDir, rel);
|
|
15398
|
+
const dest = (0, import_node_path15.join)(verityDir, rel);
|
|
15399
|
+
if ((0, import_node_fs21.statSync)(src).isDirectory()) {
|
|
15019
15400
|
walk(rel);
|
|
15020
|
-
} else if (!(0,
|
|
15021
|
-
(0,
|
|
15022
|
-
(0,
|
|
15401
|
+
} else if (!(0, import_node_fs21.existsSync)(dest)) {
|
|
15402
|
+
(0, import_node_fs21.mkdirSync)((0, import_node_path15.dirname)(dest), { recursive: true });
|
|
15403
|
+
(0, import_node_fs21.cpSync)(src, dest);
|
|
15023
15404
|
copied++;
|
|
15024
15405
|
}
|
|
15025
15406
|
}
|
|
@@ -15028,22 +15409,22 @@ function carryLegacyContents(gateDir, verityDir) {
|
|
|
15028
15409
|
return copied;
|
|
15029
15410
|
}
|
|
15030
15411
|
async function needsMigration(root = repoRoot()) {
|
|
15031
|
-
const gateDir = (0,
|
|
15032
|
-
const verityDir = (0,
|
|
15033
|
-
if ((0,
|
|
15034
|
-
if ((0,
|
|
15035
|
-
if ((0,
|
|
15412
|
+
const gateDir = (0, import_node_path15.join)(root, ".gate");
|
|
15413
|
+
const verityDir = (0, import_node_path15.join)(root, ".verity");
|
|
15414
|
+
if ((0, import_node_fs21.existsSync)(gateDir) && !(0, import_node_fs21.existsSync)(verityDir)) return true;
|
|
15415
|
+
if ((0, import_node_fs21.existsSync)(gateDir) && (0, import_node_fs21.existsSync)(verityDir)) {
|
|
15416
|
+
if ((0, import_node_fs21.existsSync)((0, import_node_path15.join)(gateDir, "credentials")) && !(0, import_node_fs21.existsSync)((0, import_node_path15.join)(verityDir, "credentials"))) {
|
|
15036
15417
|
return true;
|
|
15037
15418
|
}
|
|
15038
|
-
if ((0,
|
|
15419
|
+
if ((0, import_node_fs21.existsSync)((0, import_node_path15.join)(gateDir, "memory")) && !(0, import_node_fs21.existsSync)((0, import_node_path15.join)(verityDir, "memory"))) {
|
|
15039
15420
|
return true;
|
|
15040
15421
|
}
|
|
15041
15422
|
}
|
|
15042
|
-
const claudeMd = (0,
|
|
15043
|
-
if ((0,
|
|
15423
|
+
const claudeMd = (0, import_node_path15.join)(root, "CLAUDE.md");
|
|
15424
|
+
if ((0, import_node_fs21.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
|
|
15044
15425
|
return true;
|
|
15045
15426
|
}
|
|
15046
|
-
if ((0,
|
|
15427
|
+
if ((0, import_node_fs21.existsSync)((0, import_node_path15.join)(root, "GATE.md")) && !(0, import_node_fs21.existsSync)((0, import_node_path15.join)(root, "VERITY.md"))) {
|
|
15047
15428
|
return true;
|
|
15048
15429
|
}
|
|
15049
15430
|
if (await hasLegacyHooksAt(root)) return true;
|
|
@@ -15071,15 +15452,15 @@ function registerMigrateCommand(program2) {
|
|
|
15071
15452
|
// src/commands/init.ts
|
|
15072
15453
|
function resolveDataDir() {
|
|
15073
15454
|
const candidates = [
|
|
15074
|
-
(0,
|
|
15455
|
+
(0, import_node_path16.join)(__dirname, "..", "data"),
|
|
15075
15456
|
// installed: node_modules/@codacy/verity-cli/data
|
|
15076
|
-
(0,
|
|
15457
|
+
(0, import_node_path16.join)(__dirname, "..", "..", "data"),
|
|
15077
15458
|
// edge case: nested resolution
|
|
15078
|
-
(0,
|
|
15459
|
+
(0, import_node_path16.join)(process.cwd(), "cli", "data")
|
|
15079
15460
|
// local dev: running from repo root
|
|
15080
15461
|
];
|
|
15081
15462
|
for (const candidate of candidates) {
|
|
15082
|
-
if ((0,
|
|
15463
|
+
if ((0, import_node_fs22.existsSync)((0, import_node_path16.join)(candidate, "skills"))) {
|
|
15083
15464
|
return candidate;
|
|
15084
15465
|
}
|
|
15085
15466
|
}
|
|
@@ -15095,7 +15476,7 @@ function registerInitCommand(program2) {
|
|
|
15095
15476
|
program2.command("init").description("Initialize Verity in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
|
|
15096
15477
|
const force = opts.force ?? false;
|
|
15097
15478
|
const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
|
|
15098
|
-
const isProject = projectMarkers.some((m) => (0,
|
|
15479
|
+
const isProject = projectMarkers.some((m) => (0, import_node_fs22.existsSync)(m));
|
|
15099
15480
|
if (!isProject) {
|
|
15100
15481
|
printError("No project detected in the current directory.");
|
|
15101
15482
|
printInfo('Run "verity init" from your project root.');
|
|
@@ -15158,21 +15539,21 @@ function registerInitCommand(program2) {
|
|
|
15158
15539
|
console.log("");
|
|
15159
15540
|
printInfo("Installing skills...");
|
|
15160
15541
|
const dataDir = resolveDataDir();
|
|
15161
|
-
const skillsSource = (0,
|
|
15542
|
+
const skillsSource = (0, import_node_path16.join)(dataDir, "skills");
|
|
15162
15543
|
const skillsDest = ".claude/skills";
|
|
15163
15544
|
const skills = ["verity-setup", "verity-analyze", "verity-status", "verity-feedback", "verity-learn", "verity-memory", "verity-insights", "verity-reflect"];
|
|
15164
15545
|
let skillsInstalled = 0;
|
|
15165
15546
|
for (const skill of skills) {
|
|
15166
|
-
const src = (0,
|
|
15167
|
-
const dest = (0,
|
|
15168
|
-
if (!(0,
|
|
15547
|
+
const src = (0, import_node_path16.join)(skillsSource, skill);
|
|
15548
|
+
const dest = (0, import_node_path16.join)(skillsDest, skill);
|
|
15549
|
+
if (!(0, import_node_fs22.existsSync)(src)) {
|
|
15169
15550
|
printWarn(` Skill data not found: ${skill}`);
|
|
15170
15551
|
continue;
|
|
15171
15552
|
}
|
|
15172
|
-
if ((0,
|
|
15173
|
-
const srcSkill = (0,
|
|
15174
|
-
const destSkill = (0,
|
|
15175
|
-
if ((0,
|
|
15553
|
+
if ((0, import_node_fs22.existsSync)(dest) && !force) {
|
|
15554
|
+
const srcSkill = (0, import_node_path16.join)(src, "SKILL.md");
|
|
15555
|
+
const destSkill = (0, import_node_path16.join)(dest, "SKILL.md");
|
|
15556
|
+
if ((0, import_node_fs22.existsSync)(destSkill)) {
|
|
15176
15557
|
try {
|
|
15177
15558
|
const srcContent = await (0, import_promises12.readFile)(srcSkill, "utf-8");
|
|
15178
15559
|
const destContent = await (0, import_promises12.readFile)(destSkill, "utf-8");
|
|
@@ -15208,7 +15589,7 @@ function registerInitCommand(program2) {
|
|
|
15208
15589
|
} catch (err) {
|
|
15209
15590
|
printWarn(` Could not update CLAUDE.md: ${err.message}`);
|
|
15210
15591
|
}
|
|
15211
|
-
const globalVerityDir = (0,
|
|
15592
|
+
const globalVerityDir = (0, import_node_path16.join)(process.env.HOME ?? "", ".verity");
|
|
15212
15593
|
await (0, import_promises12.mkdir)(globalVerityDir, { recursive: true });
|
|
15213
15594
|
console.log("");
|
|
15214
15595
|
printInfo("Verity initialized!");
|
|
@@ -15231,8 +15612,8 @@ function registerInitCommand(program2) {
|
|
|
15231
15612
|
}
|
|
15232
15613
|
|
|
15233
15614
|
// src/commands/uninstall.ts
|
|
15234
|
-
var
|
|
15235
|
-
var
|
|
15615
|
+
var import_node_fs23 = require("node:fs");
|
|
15616
|
+
var import_node_path17 = require("node:path");
|
|
15236
15617
|
var SKILL_NAMES = [
|
|
15237
15618
|
"verity-setup",
|
|
15238
15619
|
"verity-analyze",
|
|
@@ -15251,11 +15632,11 @@ function registerUninstallCommand(program2) {
|
|
|
15251
15632
|
const actions = [];
|
|
15252
15633
|
const skillsRoot = projectPath(".claude/skills");
|
|
15253
15634
|
for (const name of SKILL_NAMES) {
|
|
15254
|
-
const dir = (0,
|
|
15255
|
-
if ((0,
|
|
15635
|
+
const dir = (0, import_node_path17.join)(skillsRoot, name);
|
|
15636
|
+
if ((0, import_node_fs23.existsSync)(dir)) {
|
|
15256
15637
|
actions.push({
|
|
15257
15638
|
label: `Remove .claude/skills/${name}/`,
|
|
15258
|
-
apply: () => (0,
|
|
15639
|
+
apply: () => (0, import_node_fs23.rmSync)(dir, { recursive: true, force: true })
|
|
15259
15640
|
});
|
|
15260
15641
|
}
|
|
15261
15642
|
}
|
|
@@ -15269,24 +15650,24 @@ function registerUninstallCommand(program2) {
|
|
|
15269
15650
|
});
|
|
15270
15651
|
}
|
|
15271
15652
|
const verityDir = projectPath(VERITY_DIR);
|
|
15272
|
-
if ((0,
|
|
15653
|
+
if ((0, import_node_fs23.existsSync)(verityDir)) {
|
|
15273
15654
|
actions.push({
|
|
15274
15655
|
label: `Remove ${VERITY_DIR}/`,
|
|
15275
|
-
apply: () => (0,
|
|
15656
|
+
apply: () => (0, import_node_fs23.rmSync)(verityDir, { recursive: true, force: true })
|
|
15276
15657
|
});
|
|
15277
15658
|
}
|
|
15278
15659
|
if (!keepVerityMd) {
|
|
15279
15660
|
const verityMd = projectPath(VERITY_MD_FILE);
|
|
15280
|
-
if ((0,
|
|
15661
|
+
if ((0, import_node_fs23.existsSync)(verityMd)) {
|
|
15281
15662
|
actions.push({
|
|
15282
15663
|
label: `Remove ${VERITY_MD_FILE}`,
|
|
15283
|
-
apply: () => (0,
|
|
15664
|
+
apply: () => (0, import_node_fs23.rmSync)(verityMd, { force: true })
|
|
15284
15665
|
});
|
|
15285
15666
|
}
|
|
15286
15667
|
}
|
|
15287
15668
|
const cleanupEmptyDir = (path) => {
|
|
15288
|
-
if ((0,
|
|
15289
|
-
(0,
|
|
15669
|
+
if ((0, import_node_fs23.existsSync)(path) && (0, import_node_fs23.statSync)(path).isDirectory() && (0, import_node_fs23.readdirSync)(path).length === 0) {
|
|
15670
|
+
(0, import_node_fs23.rmdirSync)(path);
|
|
15290
15671
|
}
|
|
15291
15672
|
};
|
|
15292
15673
|
actions.push({
|
|
@@ -15297,11 +15678,11 @@ function registerUninstallCommand(program2) {
|
|
|
15297
15678
|
}
|
|
15298
15679
|
});
|
|
15299
15680
|
const home = process.env.HOME ?? "";
|
|
15300
|
-
const globalVerityDir = (0,
|
|
15301
|
-
if (purgeGlobal && (0,
|
|
15681
|
+
const globalVerityDir = (0, import_node_path17.join)(home, ".verity");
|
|
15682
|
+
if (purgeGlobal && (0, import_node_fs23.existsSync)(globalVerityDir)) {
|
|
15302
15683
|
actions.push({
|
|
15303
15684
|
label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
|
|
15304
|
-
apply: () => (0,
|
|
15685
|
+
apply: () => (0, import_node_fs23.rmSync)(globalVerityDir, { recursive: true, force: true })
|
|
15305
15686
|
});
|
|
15306
15687
|
}
|
|
15307
15688
|
if (actions.length === 0) {
|
|
@@ -15495,8 +15876,8 @@ function registerTaskCommands(program2) {
|
|
|
15495
15876
|
}
|
|
15496
15877
|
|
|
15497
15878
|
// src/commands/reset.ts
|
|
15498
|
-
var
|
|
15499
|
-
var
|
|
15879
|
+
var import_node_fs24 = require("node:fs");
|
|
15880
|
+
var import_node_path18 = require("node:path");
|
|
15500
15881
|
function registerResetCommand(program2) {
|
|
15501
15882
|
program2.command("reset").description("Close the current task and clear transient state").option("--keep-task", "Only purge caches; leave the current task open").option("--all", "Also purge diagnostic logs (.verity/.logs/)").action(async (opts) => {
|
|
15502
15883
|
const globals = program2.opts();
|
|
@@ -15533,11 +15914,11 @@ function registerResetCommand(program2) {
|
|
|
15533
15914
|
}
|
|
15534
15915
|
const cacheDir = projectPath(CACHE_DIR);
|
|
15535
15916
|
let purged = 0;
|
|
15536
|
-
if ((0,
|
|
15537
|
-
for (const entry of (0,
|
|
15917
|
+
if ((0, import_node_fs24.existsSync)(cacheDir)) {
|
|
15918
|
+
for (const entry of (0, import_node_fs24.readdirSync)(cacheDir)) {
|
|
15538
15919
|
if (entry.startsWith("pending-")) {
|
|
15539
15920
|
try {
|
|
15540
|
-
(0,
|
|
15921
|
+
(0, import_node_fs24.unlinkSync)((0, import_node_path18.join)(cacheDir, entry));
|
|
15541
15922
|
purged++;
|
|
15542
15923
|
} catch {
|
|
15543
15924
|
}
|
|
@@ -15552,19 +15933,19 @@ function registerResetCommand(program2) {
|
|
|
15552
15933
|
projectPath(`${VERITY_DIR}/.last-analysis`)
|
|
15553
15934
|
];
|
|
15554
15935
|
for (const file of filesToClear) {
|
|
15555
|
-
if ((0,
|
|
15936
|
+
if ((0, import_node_fs24.existsSync)(file)) {
|
|
15556
15937
|
try {
|
|
15557
|
-
(0,
|
|
15938
|
+
(0, import_node_fs24.writeFileSync)(file, "");
|
|
15558
15939
|
} catch {
|
|
15559
15940
|
}
|
|
15560
15941
|
}
|
|
15561
15942
|
}
|
|
15562
15943
|
if (opts.all) {
|
|
15563
15944
|
const logsDir = projectPath(`${VERITY_DIR}/.logs`);
|
|
15564
|
-
if ((0,
|
|
15565
|
-
for (const entry of (0,
|
|
15945
|
+
if ((0, import_node_fs24.existsSync)(logsDir)) {
|
|
15946
|
+
for (const entry of (0, import_node_fs24.readdirSync)(logsDir)) {
|
|
15566
15947
|
try {
|
|
15567
|
-
(0,
|
|
15948
|
+
(0, import_node_fs24.unlinkSync)((0, import_node_path18.join)(logsDir, entry));
|
|
15568
15949
|
} catch {
|
|
15569
15950
|
}
|
|
15570
15951
|
}
|
|
@@ -15802,7 +16183,7 @@ function registerRunCommand(program2) {
|
|
|
15802
16183
|
|
|
15803
16184
|
// src/lib/telemetry.ts
|
|
15804
16185
|
var import_promises13 = require("node:fs/promises");
|
|
15805
|
-
var
|
|
16186
|
+
var import_node_path19 = require("node:path");
|
|
15806
16187
|
var SETTINGS_LOCAL_FILE2 = ".claude/settings.local.json";
|
|
15807
16188
|
var GITIGNORE_FILE = ".gitignore";
|
|
15808
16189
|
var GITIGNORE_ENTRY = ".claude/settings.local.json";
|
|
@@ -15840,7 +16221,7 @@ async function readSettingsLocal() {
|
|
|
15840
16221
|
}
|
|
15841
16222
|
async function writeSettingsLocal(settings) {
|
|
15842
16223
|
const file = projectPath(SETTINGS_LOCAL_FILE2);
|
|
15843
|
-
await (0, import_promises13.mkdir)((0,
|
|
16224
|
+
await (0, import_promises13.mkdir)((0, import_node_path19.dirname)(file), { recursive: true });
|
|
15844
16225
|
await (0, import_promises13.writeFile)(file, JSON.stringify(settings, null, 2) + "\n");
|
|
15845
16226
|
}
|
|
15846
16227
|
async function ensureGitignore() {
|
|
@@ -15936,7 +16317,7 @@ function registerTelemetryCommands(program2) {
|
|
|
15936
16317
|
}
|
|
15937
16318
|
|
|
15938
16319
|
// src/cli.ts
|
|
15939
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.
|
|
16320
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.23.0").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
|
|
15940
16321
|
registerAuthCommands(program);
|
|
15941
16322
|
registerHooksCommands(program);
|
|
15942
16323
|
registerIntentCommands(program);
|
|
@@ -15946,6 +16327,7 @@ registerStatusCommand(program);
|
|
|
15946
16327
|
registerFeedbackCommand(program);
|
|
15947
16328
|
registerAnalyzeCommand(program);
|
|
15948
16329
|
registerReviewCommand(program);
|
|
16330
|
+
registerGuardCommand(program);
|
|
15949
16331
|
registerInitCommand(program);
|
|
15950
16332
|
registerUninstallCommand(program);
|
|
15951
16333
|
registerTaskCommands(program);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codacy/verity-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "CLI for Verity quality gate service",
|
|
5
5
|
"homepage": "https://verity.md",
|
|
6
6
|
"repository": {
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
},
|
|
25
25
|
"scripts": {
|
|
26
26
|
"build": "node scripts/build.js",
|
|
27
|
-
"test": "node --import tsx --test $(find tests -name '*.test.ts' | sort)",
|
|
27
|
+
"test": "node --import tsx --test $(find tests -name '*.test.ts' -not -path 'tests/e2e/*' | sort)",
|
|
28
|
+
"test:e2e": "node --import tsx --test --test-concurrency=1 tests/e2e/*.test.ts",
|
|
28
29
|
"typecheck": "tsc --noEmit",
|
|
29
30
|
"prepublishOnly": "npm run typecheck && npm run build"
|
|
30
31
|
},
|