@codacy/verity-cli 0.22.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 +493 -112
- 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";
|
|
@@ -14431,16 +14563,13 @@ async function runAnalyze(opts, globals) {
|
|
|
14431
14563
|
requestBody.intent_context = intentContext;
|
|
14432
14564
|
}
|
|
14433
14565
|
const ANALYZE_TIMEOUT_MS = 1e5;
|
|
14434
|
-
let result = await
|
|
14435
|
-
method: "POST",
|
|
14436
|
-
path: "/analyze",
|
|
14566
|
+
let result = await analyzeRequest({
|
|
14437
14567
|
serviceUrl: urlResult.data,
|
|
14438
14568
|
token: tokenResult.data.token,
|
|
14439
14569
|
body: requestBody,
|
|
14440
14570
|
verbose: globals.verbose,
|
|
14441
14571
|
timeout: ANALYZE_TIMEOUT_MS,
|
|
14442
|
-
cmd: "analyze"
|
|
14443
|
-
encodeBody: true
|
|
14572
|
+
cmd: "analyze"
|
|
14444
14573
|
});
|
|
14445
14574
|
if (!result.ok && shouldWarmRetryAnalyze(result)) {
|
|
14446
14575
|
logEvent("analyze_warm_retry", {
|
|
@@ -14457,17 +14586,14 @@ async function runAnalyze(opts, globals) {
|
|
|
14457
14586
|
timeout: 15e3,
|
|
14458
14587
|
cmd: "analyze_warmup"
|
|
14459
14588
|
});
|
|
14460
|
-
result = await
|
|
14461
|
-
method: "POST",
|
|
14462
|
-
path: "/analyze",
|
|
14589
|
+
result = await analyzeRequest({
|
|
14463
14590
|
serviceUrl: urlResult.data,
|
|
14464
14591
|
token: tokenResult.data.token,
|
|
14465
14592
|
body: requestBody,
|
|
14466
14593
|
verbose: globals.verbose,
|
|
14467
14594
|
timeout: ANALYZE_TIMEOUT_MS,
|
|
14468
14595
|
cmd: "analyze",
|
|
14469
|
-
retry: true
|
|
14470
|
-
encodeBody: true
|
|
14596
|
+
retry: true
|
|
14471
14597
|
});
|
|
14472
14598
|
}
|
|
14473
14599
|
if (!result.ok) {
|
|
@@ -14728,8 +14854,8 @@ async function runReview(opts, globals) {
|
|
|
14728
14854
|
for (const p of specPaths) {
|
|
14729
14855
|
if (!(0, import_node_fs19.existsSync)(p)) continue;
|
|
14730
14856
|
try {
|
|
14731
|
-
const { readFileSync:
|
|
14732
|
-
const content =
|
|
14857
|
+
const { readFileSync: readFileSync11 } = await import("node:fs");
|
|
14858
|
+
const content = readFileSync11(p, "utf-8");
|
|
14733
14859
|
specs.push({ path: p, content: content.slice(0, 10240) });
|
|
14734
14860
|
} catch {
|
|
14735
14861
|
}
|
|
@@ -14759,14 +14885,13 @@ async function runReview(opts, globals) {
|
|
|
14759
14885
|
if (plans.length > 0) intentContext.plans = plans;
|
|
14760
14886
|
requestBody.intent_context = intentContext;
|
|
14761
14887
|
}
|
|
14762
|
-
const result = await
|
|
14763
|
-
method: "POST",
|
|
14764
|
-
path: "/analyze",
|
|
14888
|
+
const result = await analyzeRequest({
|
|
14765
14889
|
serviceUrl: urlResult.data,
|
|
14766
14890
|
token: tokenResult.data.token,
|
|
14767
14891
|
body: requestBody,
|
|
14768
14892
|
verbose: globals.verbose,
|
|
14769
|
-
timeout: 12e4
|
|
14893
|
+
timeout: 12e4,
|
|
14894
|
+
cmd: "review"
|
|
14770
14895
|
});
|
|
14771
14896
|
if (!result.ok) {
|
|
14772
14897
|
printError(`Service error: ${result.error}`);
|
|
@@ -14786,15 +14911,270 @@ async function runReview(opts, globals) {
|
|
|
14786
14911
|
process.exit(0);
|
|
14787
14912
|
}
|
|
14788
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
|
+
|
|
14789
15169
|
// src/commands/init.ts
|
|
14790
|
-
var
|
|
15170
|
+
var import_node_fs22 = require("node:fs");
|
|
14791
15171
|
var import_promises12 = require("node:fs/promises");
|
|
14792
|
-
var
|
|
15172
|
+
var import_node_path16 = require("node:path");
|
|
14793
15173
|
var import_node_child_process9 = require("node:child_process");
|
|
14794
15174
|
|
|
14795
15175
|
// src/commands/migrate.ts
|
|
14796
|
-
var
|
|
14797
|
-
var
|
|
15176
|
+
var import_node_fs21 = require("node:fs");
|
|
15177
|
+
var import_node_path15 = require("node:path");
|
|
14798
15178
|
var import_node_child_process8 = require("node:child_process");
|
|
14799
15179
|
var LEGACY_NPM_PACKAGE = "@codacy/gate-cli";
|
|
14800
15180
|
function defaultNpmRemover(pkg) {
|
|
@@ -14830,12 +15210,12 @@ async function runMigration(opts = {}) {
|
|
|
14830
15210
|
return { actions, migrated: actions.length > 0 };
|
|
14831
15211
|
}
|
|
14832
15212
|
function migrateProjectDir(root, actions) {
|
|
14833
|
-
const gateDir = (0,
|
|
14834
|
-
const verityDir = (0,
|
|
14835
|
-
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)) {
|
|
14836
15216
|
return migrateProjectDirRename(root, gateDir, verityDir, actions);
|
|
14837
15217
|
}
|
|
14838
|
-
if ((0,
|
|
15218
|
+
if ((0, import_node_fs21.existsSync)(gateDir) && (0, import_node_fs21.existsSync)(verityDir)) {
|
|
14839
15219
|
return migrateProjectDirCarry(gateDir, verityDir, actions);
|
|
14840
15220
|
}
|
|
14841
15221
|
return false;
|
|
@@ -14856,13 +15236,13 @@ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
|
|
|
14856
15236
|
}
|
|
14857
15237
|
}
|
|
14858
15238
|
if (moved) {
|
|
14859
|
-
if ((0,
|
|
15239
|
+
if ((0, import_node_fs21.existsSync)(gateDir)) {
|
|
14860
15240
|
const carried = carryLegacyContents(gateDir, verityDir);
|
|
14861
15241
|
if (carried > 0) {
|
|
14862
15242
|
actions.push(`Carried ${carried} untracked legacy file(s) from .gate/ into .verity/`);
|
|
14863
15243
|
}
|
|
14864
15244
|
try {
|
|
14865
|
-
(0,
|
|
15245
|
+
(0, import_node_fs21.rmSync)(gateDir, { recursive: true, force: true });
|
|
14866
15246
|
} catch {
|
|
14867
15247
|
}
|
|
14868
15248
|
}
|
|
@@ -14878,18 +15258,18 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
|
|
|
14878
15258
|
actions.push(`Carried ${carried} legacy file(s) from .gate/ into .verity/`);
|
|
14879
15259
|
}
|
|
14880
15260
|
try {
|
|
14881
|
-
(0,
|
|
15261
|
+
(0, import_node_fs21.rmSync)(gateDir, { recursive: true, force: true });
|
|
14882
15262
|
} catch {
|
|
14883
15263
|
}
|
|
14884
15264
|
return carried > 0;
|
|
14885
15265
|
}
|
|
14886
15266
|
function migrateGlobalCredentials(home, actions) {
|
|
14887
15267
|
if (!home) return;
|
|
14888
|
-
const gateCreds = (0,
|
|
14889
|
-
const verityCreds = (0,
|
|
14890
|
-
if (!(0,
|
|
14891
|
-
if (!(0,
|
|
14892
|
-
(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 });
|
|
14893
15273
|
moveFile(gateCreds, verityCreds);
|
|
14894
15274
|
actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
|
|
14895
15275
|
return;
|
|
@@ -14911,8 +15291,8 @@ async function migrateLegacyHooks(root, actions) {
|
|
|
14911
15291
|
}
|
|
14912
15292
|
}
|
|
14913
15293
|
async function migrateClaudeMd(root, actions) {
|
|
14914
|
-
const claudeMd = (0,
|
|
14915
|
-
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));
|
|
14916
15296
|
if (!hadLegacyBlock) return;
|
|
14917
15297
|
try {
|
|
14918
15298
|
await ensureClaudeMdPointer(root);
|
|
@@ -14922,9 +15302,9 @@ async function migrateClaudeMd(root, actions) {
|
|
|
14922
15302
|
}
|
|
14923
15303
|
}
|
|
14924
15304
|
function migrateStandardFile(root, actions) {
|
|
14925
|
-
const gateMd = (0,
|
|
14926
|
-
const verityMd = (0,
|
|
14927
|
-
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;
|
|
14928
15308
|
let moved = false;
|
|
14929
15309
|
if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
|
|
14930
15310
|
try {
|
|
@@ -14936,7 +15316,7 @@ function migrateStandardFile(root, actions) {
|
|
|
14936
15316
|
if (!moved) moveFile(gateMd, verityMd);
|
|
14937
15317
|
const content = readFileSyncSafe(verityMd);
|
|
14938
15318
|
const refreshed = content.split("GATE.md").join("VERITY.md");
|
|
14939
|
-
if (refreshed !== content) (0,
|
|
15319
|
+
if (refreshed !== content) (0, import_node_fs21.writeFileSync)(verityMd, refreshed);
|
|
14940
15320
|
actions.push("Renamed GATE.md \u2192 VERITY.md");
|
|
14941
15321
|
}
|
|
14942
15322
|
function removeLegacyPackage(movedProjectDir, npmRemover, actions) {
|
|
@@ -14970,14 +15350,14 @@ function mergeGlobalCredentials(gateCreds, verityCreds) {
|
|
|
14970
15350
|
}
|
|
14971
15351
|
if (toAppend.length > 0) {
|
|
14972
15352
|
const sep = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
|
|
14973
|
-
(0,
|
|
15353
|
+
(0, import_node_fs21.writeFileSync)(verityCreds, verityContent + sep + toAppend.join("\n") + "\n");
|
|
14974
15354
|
}
|
|
14975
|
-
(0,
|
|
15355
|
+
(0, import_node_fs21.rmSync)(gateCreds, { force: true });
|
|
14976
15356
|
return toAppend.length;
|
|
14977
15357
|
}
|
|
14978
15358
|
function readFileSyncSafe(path) {
|
|
14979
15359
|
try {
|
|
14980
|
-
return (0,
|
|
15360
|
+
return (0, import_node_fs21.readFileSync)(path, "utf-8");
|
|
14981
15361
|
} catch {
|
|
14982
15362
|
return "";
|
|
14983
15363
|
}
|
|
@@ -14992,35 +15372,35 @@ function hasStagedChanges(root) {
|
|
|
14992
15372
|
}
|
|
14993
15373
|
function moveDir(from, to) {
|
|
14994
15374
|
try {
|
|
14995
|
-
(0,
|
|
15375
|
+
(0, import_node_fs21.renameSync)(from, to);
|
|
14996
15376
|
} catch (err) {
|
|
14997
15377
|
if (err.code !== "EXDEV") throw err;
|
|
14998
|
-
(0,
|
|
14999
|
-
(0,
|
|
15378
|
+
(0, import_node_fs21.cpSync)(from, to, { recursive: true });
|
|
15379
|
+
(0, import_node_fs21.rmSync)(from, { recursive: true, force: true });
|
|
15000
15380
|
}
|
|
15001
15381
|
}
|
|
15002
15382
|
function moveFile(from, to) {
|
|
15003
15383
|
try {
|
|
15004
|
-
(0,
|
|
15384
|
+
(0, import_node_fs21.renameSync)(from, to);
|
|
15005
15385
|
} catch (err) {
|
|
15006
15386
|
if (err.code !== "EXDEV") throw err;
|
|
15007
|
-
(0,
|
|
15008
|
-
(0,
|
|
15387
|
+
(0, import_node_fs21.cpSync)(from, to);
|
|
15388
|
+
(0, import_node_fs21.rmSync)(from, { force: true });
|
|
15009
15389
|
}
|
|
15010
15390
|
}
|
|
15011
15391
|
function carryLegacyContents(gateDir, verityDir) {
|
|
15012
15392
|
let copied = 0;
|
|
15013
15393
|
const walk = (relDir) => {
|
|
15014
|
-
const srcDir = (0,
|
|
15015
|
-
for (const entry of (0,
|
|
15016
|
-
const rel = relDir ? (0,
|
|
15017
|
-
const src = (0,
|
|
15018
|
-
const dest = (0,
|
|
15019
|
-
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()) {
|
|
15020
15400
|
walk(rel);
|
|
15021
|
-
} else if (!(0,
|
|
15022
|
-
(0,
|
|
15023
|
-
(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);
|
|
15024
15404
|
copied++;
|
|
15025
15405
|
}
|
|
15026
15406
|
}
|
|
@@ -15029,22 +15409,22 @@ function carryLegacyContents(gateDir, verityDir) {
|
|
|
15029
15409
|
return copied;
|
|
15030
15410
|
}
|
|
15031
15411
|
async function needsMigration(root = repoRoot()) {
|
|
15032
|
-
const gateDir = (0,
|
|
15033
|
-
const verityDir = (0,
|
|
15034
|
-
if ((0,
|
|
15035
|
-
if ((0,
|
|
15036
|
-
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"))) {
|
|
15037
15417
|
return true;
|
|
15038
15418
|
}
|
|
15039
|
-
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"))) {
|
|
15040
15420
|
return true;
|
|
15041
15421
|
}
|
|
15042
15422
|
}
|
|
15043
|
-
const claudeMd = (0,
|
|
15044
|
-
if ((0,
|
|
15423
|
+
const claudeMd = (0, import_node_path15.join)(root, "CLAUDE.md");
|
|
15424
|
+
if ((0, import_node_fs21.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
|
|
15045
15425
|
return true;
|
|
15046
15426
|
}
|
|
15047
|
-
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"))) {
|
|
15048
15428
|
return true;
|
|
15049
15429
|
}
|
|
15050
15430
|
if (await hasLegacyHooksAt(root)) return true;
|
|
@@ -15072,15 +15452,15 @@ function registerMigrateCommand(program2) {
|
|
|
15072
15452
|
// src/commands/init.ts
|
|
15073
15453
|
function resolveDataDir() {
|
|
15074
15454
|
const candidates = [
|
|
15075
|
-
(0,
|
|
15455
|
+
(0, import_node_path16.join)(__dirname, "..", "data"),
|
|
15076
15456
|
// installed: node_modules/@codacy/verity-cli/data
|
|
15077
|
-
(0,
|
|
15457
|
+
(0, import_node_path16.join)(__dirname, "..", "..", "data"),
|
|
15078
15458
|
// edge case: nested resolution
|
|
15079
|
-
(0,
|
|
15459
|
+
(0, import_node_path16.join)(process.cwd(), "cli", "data")
|
|
15080
15460
|
// local dev: running from repo root
|
|
15081
15461
|
];
|
|
15082
15462
|
for (const candidate of candidates) {
|
|
15083
|
-
if ((0,
|
|
15463
|
+
if ((0, import_node_fs22.existsSync)((0, import_node_path16.join)(candidate, "skills"))) {
|
|
15084
15464
|
return candidate;
|
|
15085
15465
|
}
|
|
15086
15466
|
}
|
|
@@ -15096,7 +15476,7 @@ function registerInitCommand(program2) {
|
|
|
15096
15476
|
program2.command("init").description("Initialize Verity in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
|
|
15097
15477
|
const force = opts.force ?? false;
|
|
15098
15478
|
const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
|
|
15099
|
-
const isProject = projectMarkers.some((m) => (0,
|
|
15479
|
+
const isProject = projectMarkers.some((m) => (0, import_node_fs22.existsSync)(m));
|
|
15100
15480
|
if (!isProject) {
|
|
15101
15481
|
printError("No project detected in the current directory.");
|
|
15102
15482
|
printInfo('Run "verity init" from your project root.');
|
|
@@ -15159,21 +15539,21 @@ function registerInitCommand(program2) {
|
|
|
15159
15539
|
console.log("");
|
|
15160
15540
|
printInfo("Installing skills...");
|
|
15161
15541
|
const dataDir = resolveDataDir();
|
|
15162
|
-
const skillsSource = (0,
|
|
15542
|
+
const skillsSource = (0, import_node_path16.join)(dataDir, "skills");
|
|
15163
15543
|
const skillsDest = ".claude/skills";
|
|
15164
15544
|
const skills = ["verity-setup", "verity-analyze", "verity-status", "verity-feedback", "verity-learn", "verity-memory", "verity-insights", "verity-reflect"];
|
|
15165
15545
|
let skillsInstalled = 0;
|
|
15166
15546
|
for (const skill of skills) {
|
|
15167
|
-
const src = (0,
|
|
15168
|
-
const dest = (0,
|
|
15169
|
-
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)) {
|
|
15170
15550
|
printWarn(` Skill data not found: ${skill}`);
|
|
15171
15551
|
continue;
|
|
15172
15552
|
}
|
|
15173
|
-
if ((0,
|
|
15174
|
-
const srcSkill = (0,
|
|
15175
|
-
const destSkill = (0,
|
|
15176
|
-
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)) {
|
|
15177
15557
|
try {
|
|
15178
15558
|
const srcContent = await (0, import_promises12.readFile)(srcSkill, "utf-8");
|
|
15179
15559
|
const destContent = await (0, import_promises12.readFile)(destSkill, "utf-8");
|
|
@@ -15209,7 +15589,7 @@ function registerInitCommand(program2) {
|
|
|
15209
15589
|
} catch (err) {
|
|
15210
15590
|
printWarn(` Could not update CLAUDE.md: ${err.message}`);
|
|
15211
15591
|
}
|
|
15212
|
-
const globalVerityDir = (0,
|
|
15592
|
+
const globalVerityDir = (0, import_node_path16.join)(process.env.HOME ?? "", ".verity");
|
|
15213
15593
|
await (0, import_promises12.mkdir)(globalVerityDir, { recursive: true });
|
|
15214
15594
|
console.log("");
|
|
15215
15595
|
printInfo("Verity initialized!");
|
|
@@ -15232,8 +15612,8 @@ function registerInitCommand(program2) {
|
|
|
15232
15612
|
}
|
|
15233
15613
|
|
|
15234
15614
|
// src/commands/uninstall.ts
|
|
15235
|
-
var
|
|
15236
|
-
var
|
|
15615
|
+
var import_node_fs23 = require("node:fs");
|
|
15616
|
+
var import_node_path17 = require("node:path");
|
|
15237
15617
|
var SKILL_NAMES = [
|
|
15238
15618
|
"verity-setup",
|
|
15239
15619
|
"verity-analyze",
|
|
@@ -15252,11 +15632,11 @@ function registerUninstallCommand(program2) {
|
|
|
15252
15632
|
const actions = [];
|
|
15253
15633
|
const skillsRoot = projectPath(".claude/skills");
|
|
15254
15634
|
for (const name of SKILL_NAMES) {
|
|
15255
|
-
const dir = (0,
|
|
15256
|
-
if ((0,
|
|
15635
|
+
const dir = (0, import_node_path17.join)(skillsRoot, name);
|
|
15636
|
+
if ((0, import_node_fs23.existsSync)(dir)) {
|
|
15257
15637
|
actions.push({
|
|
15258
15638
|
label: `Remove .claude/skills/${name}/`,
|
|
15259
|
-
apply: () => (0,
|
|
15639
|
+
apply: () => (0, import_node_fs23.rmSync)(dir, { recursive: true, force: true })
|
|
15260
15640
|
});
|
|
15261
15641
|
}
|
|
15262
15642
|
}
|
|
@@ -15270,24 +15650,24 @@ function registerUninstallCommand(program2) {
|
|
|
15270
15650
|
});
|
|
15271
15651
|
}
|
|
15272
15652
|
const verityDir = projectPath(VERITY_DIR);
|
|
15273
|
-
if ((0,
|
|
15653
|
+
if ((0, import_node_fs23.existsSync)(verityDir)) {
|
|
15274
15654
|
actions.push({
|
|
15275
15655
|
label: `Remove ${VERITY_DIR}/`,
|
|
15276
|
-
apply: () => (0,
|
|
15656
|
+
apply: () => (0, import_node_fs23.rmSync)(verityDir, { recursive: true, force: true })
|
|
15277
15657
|
});
|
|
15278
15658
|
}
|
|
15279
15659
|
if (!keepVerityMd) {
|
|
15280
15660
|
const verityMd = projectPath(VERITY_MD_FILE);
|
|
15281
|
-
if ((0,
|
|
15661
|
+
if ((0, import_node_fs23.existsSync)(verityMd)) {
|
|
15282
15662
|
actions.push({
|
|
15283
15663
|
label: `Remove ${VERITY_MD_FILE}`,
|
|
15284
|
-
apply: () => (0,
|
|
15664
|
+
apply: () => (0, import_node_fs23.rmSync)(verityMd, { force: true })
|
|
15285
15665
|
});
|
|
15286
15666
|
}
|
|
15287
15667
|
}
|
|
15288
15668
|
const cleanupEmptyDir = (path) => {
|
|
15289
|
-
if ((0,
|
|
15290
|
-
(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);
|
|
15291
15671
|
}
|
|
15292
15672
|
};
|
|
15293
15673
|
actions.push({
|
|
@@ -15298,11 +15678,11 @@ function registerUninstallCommand(program2) {
|
|
|
15298
15678
|
}
|
|
15299
15679
|
});
|
|
15300
15680
|
const home = process.env.HOME ?? "";
|
|
15301
|
-
const globalVerityDir = (0,
|
|
15302
|
-
if (purgeGlobal && (0,
|
|
15681
|
+
const globalVerityDir = (0, import_node_path17.join)(home, ".verity");
|
|
15682
|
+
if (purgeGlobal && (0, import_node_fs23.existsSync)(globalVerityDir)) {
|
|
15303
15683
|
actions.push({
|
|
15304
15684
|
label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
|
|
15305
|
-
apply: () => (0,
|
|
15685
|
+
apply: () => (0, import_node_fs23.rmSync)(globalVerityDir, { recursive: true, force: true })
|
|
15306
15686
|
});
|
|
15307
15687
|
}
|
|
15308
15688
|
if (actions.length === 0) {
|
|
@@ -15496,8 +15876,8 @@ function registerTaskCommands(program2) {
|
|
|
15496
15876
|
}
|
|
15497
15877
|
|
|
15498
15878
|
// src/commands/reset.ts
|
|
15499
|
-
var
|
|
15500
|
-
var
|
|
15879
|
+
var import_node_fs24 = require("node:fs");
|
|
15880
|
+
var import_node_path18 = require("node:path");
|
|
15501
15881
|
function registerResetCommand(program2) {
|
|
15502
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) => {
|
|
15503
15883
|
const globals = program2.opts();
|
|
@@ -15534,11 +15914,11 @@ function registerResetCommand(program2) {
|
|
|
15534
15914
|
}
|
|
15535
15915
|
const cacheDir = projectPath(CACHE_DIR);
|
|
15536
15916
|
let purged = 0;
|
|
15537
|
-
if ((0,
|
|
15538
|
-
for (const entry of (0,
|
|
15917
|
+
if ((0, import_node_fs24.existsSync)(cacheDir)) {
|
|
15918
|
+
for (const entry of (0, import_node_fs24.readdirSync)(cacheDir)) {
|
|
15539
15919
|
if (entry.startsWith("pending-")) {
|
|
15540
15920
|
try {
|
|
15541
|
-
(0,
|
|
15921
|
+
(0, import_node_fs24.unlinkSync)((0, import_node_path18.join)(cacheDir, entry));
|
|
15542
15922
|
purged++;
|
|
15543
15923
|
} catch {
|
|
15544
15924
|
}
|
|
@@ -15553,19 +15933,19 @@ function registerResetCommand(program2) {
|
|
|
15553
15933
|
projectPath(`${VERITY_DIR}/.last-analysis`)
|
|
15554
15934
|
];
|
|
15555
15935
|
for (const file of filesToClear) {
|
|
15556
|
-
if ((0,
|
|
15936
|
+
if ((0, import_node_fs24.existsSync)(file)) {
|
|
15557
15937
|
try {
|
|
15558
|
-
(0,
|
|
15938
|
+
(0, import_node_fs24.writeFileSync)(file, "");
|
|
15559
15939
|
} catch {
|
|
15560
15940
|
}
|
|
15561
15941
|
}
|
|
15562
15942
|
}
|
|
15563
15943
|
if (opts.all) {
|
|
15564
15944
|
const logsDir = projectPath(`${VERITY_DIR}/.logs`);
|
|
15565
|
-
if ((0,
|
|
15566
|
-
for (const entry of (0,
|
|
15945
|
+
if ((0, import_node_fs24.existsSync)(logsDir)) {
|
|
15946
|
+
for (const entry of (0, import_node_fs24.readdirSync)(logsDir)) {
|
|
15567
15947
|
try {
|
|
15568
|
-
(0,
|
|
15948
|
+
(0, import_node_fs24.unlinkSync)((0, import_node_path18.join)(logsDir, entry));
|
|
15569
15949
|
} catch {
|
|
15570
15950
|
}
|
|
15571
15951
|
}
|
|
@@ -15803,7 +16183,7 @@ function registerRunCommand(program2) {
|
|
|
15803
16183
|
|
|
15804
16184
|
// src/lib/telemetry.ts
|
|
15805
16185
|
var import_promises13 = require("node:fs/promises");
|
|
15806
|
-
var
|
|
16186
|
+
var import_node_path19 = require("node:path");
|
|
15807
16187
|
var SETTINGS_LOCAL_FILE2 = ".claude/settings.local.json";
|
|
15808
16188
|
var GITIGNORE_FILE = ".gitignore";
|
|
15809
16189
|
var GITIGNORE_ENTRY = ".claude/settings.local.json";
|
|
@@ -15841,7 +16221,7 @@ async function readSettingsLocal() {
|
|
|
15841
16221
|
}
|
|
15842
16222
|
async function writeSettingsLocal(settings) {
|
|
15843
16223
|
const file = projectPath(SETTINGS_LOCAL_FILE2);
|
|
15844
|
-
await (0, import_promises13.mkdir)((0,
|
|
16224
|
+
await (0, import_promises13.mkdir)((0, import_node_path19.dirname)(file), { recursive: true });
|
|
15845
16225
|
await (0, import_promises13.writeFile)(file, JSON.stringify(settings, null, 2) + "\n");
|
|
15846
16226
|
}
|
|
15847
16227
|
async function ensureGitignore() {
|
|
@@ -15937,7 +16317,7 @@ function registerTelemetryCommands(program2) {
|
|
|
15937
16317
|
}
|
|
15938
16318
|
|
|
15939
16319
|
// src/cli.ts
|
|
15940
|
-
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");
|
|
15941
16321
|
registerAuthCommands(program);
|
|
15942
16322
|
registerHooksCommands(program);
|
|
15943
16323
|
registerIntentCommands(program);
|
|
@@ -15947,6 +16327,7 @@ registerStatusCommand(program);
|
|
|
15947
16327
|
registerFeedbackCommand(program);
|
|
15948
16328
|
registerAnalyzeCommand(program);
|
|
15949
16329
|
registerReviewCommand(program);
|
|
16330
|
+
registerGuardCommand(program);
|
|
15950
16331
|
registerInitCommand(program);
|
|
15951
16332
|
registerUninstallCommand(program);
|
|
15952
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
|
},
|