@codacy/verity-cli 0.31.0-experimental.48d33ea → 0.31.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 +222 -73
- package/data/skills/verity-insights/SKILL.md +24 -0
- package/package.json +1 -1
package/bin/verity.js
CHANGED
|
@@ -10407,6 +10407,13 @@ var CONVERSATION_BUFFER_FILE = `${VERITY_DIR}/.conversation-buffer`;
|
|
|
10407
10407
|
var CONVERSATION_MAX_ENTRIES = 10;
|
|
10408
10408
|
var CONVERSATION_WINDOW_MINUTES = 15;
|
|
10409
10409
|
var MAX_FINDINGS = 25;
|
|
10410
|
+
var GIT_MOMENT_BUDGET = {
|
|
10411
|
+
maxFiles: 120,
|
|
10412
|
+
maxFileBytes: 98304,
|
|
10413
|
+
// 96KB, under the server's 100k chars
|
|
10414
|
+
maxTotalBytes: 583680
|
|
10415
|
+
// 570KB, under the server's 600k chars
|
|
10416
|
+
};
|
|
10410
10417
|
var ANALYZABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
10411
10418
|
"ts",
|
|
10412
10419
|
"tsx",
|
|
@@ -10499,7 +10506,7 @@ var SECURITY_PATTERNS = [
|
|
|
10499
10506
|
/Dockerfile/
|
|
10500
10507
|
];
|
|
10501
10508
|
var PROD_SERVICE_URL = "https://ofcamwrjwrkazqvdchko.supabase.co/functions/v1";
|
|
10502
|
-
var DEFAULT_SERVICE_URL = "
|
|
10509
|
+
var DEFAULT_SERVICE_URL = "".length > 0 ? "" : PROD_SERVICE_URL;
|
|
10503
10510
|
var GITHUB_CLIENT_ID = "Iv23li88HxAi3ZrbYzWh";
|
|
10504
10511
|
var GITHUB_DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
10505
10512
|
var GITHUB_ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
@@ -13533,6 +13540,54 @@ var LEGACY_MD_END = "<!-- gate-memory:end -->";
|
|
|
13533
13540
|
var LEGACY_PRESERVE_START = "<!-- gate-memory:preserve -->";
|
|
13534
13541
|
var LEGACY_PRESERVE_END = "<!-- /gate-memory:preserve -->";
|
|
13535
13542
|
var CLAUDE_MD_PROSE = [
|
|
13543
|
+
"## Project Memory",
|
|
13544
|
+
"",
|
|
13545
|
+
"This project has a knowledge graph maintained at `.verity/memory/`. Before starting",
|
|
13546
|
+
"non-trivial work, scan `.verity/memory/index.md` for decisions, gotchas, and patterns",
|
|
13547
|
+
"that may apply to the change you are about to make. Open specific node files via",
|
|
13548
|
+
"the Read tool when the title or scope suggests relevance.",
|
|
13549
|
+
"",
|
|
13550
|
+
"The graph is auto-maintained by Verity. Files at `.verity/memory/_archive/` are",
|
|
13551
|
+
"superseded \u2014 ignore them unless investigating history.",
|
|
13552
|
+
"",
|
|
13553
|
+
"## Quality gate: accepted risks",
|
|
13554
|
+
"",
|
|
13555
|
+
"When the Verity pre-commit/pre-push gate FAILs, fix the findings \u2014 that is the",
|
|
13556
|
+
'default. Use `verity waive <pattern-id> --file <path> --reason "\u2026"` ONLY to relay',
|
|
13557
|
+
"a risk a human has explicitly accepted: a named code-review finding, an ADR, or",
|
|
13558
|
+
"the user saying so in this conversation. The --reason must cite that source.",
|
|
13559
|
+
"",
|
|
13560
|
+
"Never waive on your own judgment, to get past a block, or pre-emptively. A waive",
|
|
13561
|
+
"binds to the file's current bytes and voids automatically when the file changes,",
|
|
13562
|
+
"and every waive is recorded in the run ledger. For a pattern-level false positive",
|
|
13563
|
+
"use `verity feedback finding <run-id> <pattern-id> false_positive` instead.",
|
|
13564
|
+
"",
|
|
13565
|
+
"> Durable, hand-curated guidance goes in the preserve region below (it survives",
|
|
13566
|
+
"> regeneration) or anywhere OUTSIDE these markers. Everything else between the",
|
|
13567
|
+
"> markers is tool-owned and overwritten on each run.",
|
|
13568
|
+
"",
|
|
13569
|
+
"## Housekeeping Turns",
|
|
13570
|
+
"",
|
|
13571
|
+
"When a turn will be pure housekeeping \u2014 pulling, installing dependencies,",
|
|
13572
|
+
"rebasing, a formatting sweep you are not authoring \u2014 declare it BEFORE doing it:",
|
|
13573
|
+
"",
|
|
13574
|
+
"```bash",
|
|
13575
|
+
'verity ignore --turn --agent --reason "pulling latest before starting"',
|
|
13576
|
+
"```",
|
|
13577
|
+
"",
|
|
13578
|
+
"This skips the review for that turn, which saves the turn Verity would",
|
|
13579
|
+
"otherwise spend saying it had nothing to say. Use `--for 30m` instead of",
|
|
13580
|
+
"`--turn` when a single piece of housekeeping spans several turns.",
|
|
13581
|
+
"",
|
|
13582
|
+
"**It is a claim about the turn, not a way to silence review.** The declaration",
|
|
13583
|
+
"is checked against what the turn actually did: if anything is authored \u2014 by you,",
|
|
13584
|
+
"by a subagent, or by a shell command that can write files \u2014 it voids, the review",
|
|
13585
|
+
"runs anyway, and the broken declaration is reported. So declare housekeeping you",
|
|
13586
|
+
"are about to do, never work you have already done, and never as a way to get past",
|
|
13587
|
+
"a finding. Declarations are budgeted per session and every one is recorded with",
|
|
13588
|
+
"its reason."
|
|
13589
|
+
].join("\n");
|
|
13590
|
+
var CLAUDE_MD_PROSE_PRE_WAIVE = [
|
|
13536
13591
|
"## Project Memory",
|
|
13537
13592
|
"",
|
|
13538
13593
|
"This project has a knowledge graph maintained at `.verity/memory/`. Before starting",
|
|
@@ -13693,7 +13748,12 @@ function extractPreserveContent(interior) {
|
|
|
13693
13748
|
}
|
|
13694
13749
|
function stripKnownProse(interior) {
|
|
13695
13750
|
const trimmed = interior.replace(/^\n+/, "");
|
|
13696
|
-
for (const prose of [
|
|
13751
|
+
for (const prose of [
|
|
13752
|
+
CLAUDE_MD_PROSE,
|
|
13753
|
+
CLAUDE_MD_PROSE_PRE_WAIVE,
|
|
13754
|
+
CLAUDE_MD_PROSE_PRE_IGNORE,
|
|
13755
|
+
CLAUDE_MD_PROSE_LEGACY
|
|
13756
|
+
]) {
|
|
13697
13757
|
if (trimmed.startsWith(prose)) return trimmed.slice(prose.length);
|
|
13698
13758
|
}
|
|
13699
13759
|
return trimmed;
|
|
@@ -16571,6 +16631,15 @@ function registerStandardCommands(program2) {
|
|
|
16571
16631
|
var import_promises10 = require("node:fs/promises");
|
|
16572
16632
|
function registerConfigCommands(program2) {
|
|
16573
16633
|
const config = program2.command("config").description("Manage analysis configuration");
|
|
16634
|
+
config.command("service-url").description("Print the resolved service URL (used by the shipped skills)").action(async () => {
|
|
16635
|
+
const globals = program2.opts();
|
|
16636
|
+
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
16637
|
+
if (!urlResult.ok) {
|
|
16638
|
+
printError(urlResult.error);
|
|
16639
|
+
process.exit(1);
|
|
16640
|
+
}
|
|
16641
|
+
process.stdout.write(urlResult.data + "\n");
|
|
16642
|
+
});
|
|
16574
16643
|
config.command("push").description("Upload the analysis config to the service").option("--file <path>", "Path to config file", CODACY_CONFIG_FILE).action(async (opts) => {
|
|
16575
16644
|
const globals = program2.opts();
|
|
16576
16645
|
const tokenResult = await resolveToken(globals.token);
|
|
@@ -18383,7 +18452,7 @@ function channelSilence(input) {
|
|
|
18383
18452
|
// src/lib/cli-version.ts
|
|
18384
18453
|
function cliVersion() {
|
|
18385
18454
|
try {
|
|
18386
|
-
return true ? "0.31.0
|
|
18455
|
+
return true ? "0.31.0" : "dev";
|
|
18387
18456
|
} catch {
|
|
18388
18457
|
return "dev";
|
|
18389
18458
|
}
|
|
@@ -18858,6 +18927,28 @@ function discoverPlans() {
|
|
|
18858
18927
|
}
|
|
18859
18928
|
return result;
|
|
18860
18929
|
}
|
|
18930
|
+
var GUARD_DOC_EXT = /\.(md|mdx|txt|rst|adoc)$/i;
|
|
18931
|
+
function discoverGuardDocs(rangeFiles2) {
|
|
18932
|
+
const result = [];
|
|
18933
|
+
let totalBytes = 0;
|
|
18934
|
+
for (const path of rangeFiles2) {
|
|
18935
|
+
if (result.length >= MAX_SPEC_FILES) break;
|
|
18936
|
+
if (!GUARD_DOC_EXT.test(path)) continue;
|
|
18937
|
+
if (path.startsWith("/") || path.includes("..")) continue;
|
|
18938
|
+
if (!(0, import_node_fs27.existsSync)(path)) continue;
|
|
18939
|
+
try {
|
|
18940
|
+
const stat3 = (0, import_node_fs27.statSync)(path);
|
|
18941
|
+
if (stat3.size > MAX_PLAN_FILE_BYTES) continue;
|
|
18942
|
+
if (totalBytes + stat3.size > MAX_TOTAL_SPEC_BYTES) continue;
|
|
18943
|
+
const content = (0, import_node_fs27.readFileSync)(path, "utf-8");
|
|
18944
|
+
if (!content) continue;
|
|
18945
|
+
result.push({ name: path, content });
|
|
18946
|
+
totalBytes += content.length;
|
|
18947
|
+
} catch {
|
|
18948
|
+
}
|
|
18949
|
+
}
|
|
18950
|
+
return result;
|
|
18951
|
+
}
|
|
18861
18952
|
|
|
18862
18953
|
// src/commands/analyze/phases/03-intent-inputs.ts
|
|
18863
18954
|
async function intentInputs(run) {
|
|
@@ -21573,8 +21664,8 @@ async function runReview(opts, globals) {
|
|
|
21573
21664
|
for (const p of specPaths) {
|
|
21574
21665
|
if (!(0, import_node_fs37.existsSync)(p)) continue;
|
|
21575
21666
|
try {
|
|
21576
|
-
const { readFileSync:
|
|
21577
|
-
const content =
|
|
21667
|
+
const { readFileSync: readFileSync24 } = await import("node:fs");
|
|
21668
|
+
const content = readFileSync24(p, "utf-8");
|
|
21578
21669
|
specs.push({ path: p, content: content.slice(0, 10240) });
|
|
21579
21670
|
} catch {
|
|
21580
21671
|
}
|
|
@@ -21808,13 +21899,11 @@ function buildGuardRequest(moment, files, codeDelta, iter, sessionId, statedInte
|
|
|
21808
21899
|
}
|
|
21809
21900
|
};
|
|
21810
21901
|
if (coverageTelemetry) requestBody.coverage_telemetry = coverageTelemetry;
|
|
21811
|
-
const
|
|
21812
|
-
|
|
21813
|
-
if (specs.length > 0 || plans.length > 0 || statedIntent) {
|
|
21902
|
+
const docs = discoverGuardDocs(files);
|
|
21903
|
+
if (docs.length > 0 || statedIntent) {
|
|
21814
21904
|
const intentContext = {};
|
|
21815
21905
|
if (statedIntent) intentContext.user_prompt = statedIntent;
|
|
21816
|
-
if (
|
|
21817
|
-
if (plans.length > 0) intentContext.plans = plans;
|
|
21906
|
+
if (docs.length > 0) intentContext.plans = docs;
|
|
21818
21907
|
requestBody.intent_context = intentContext;
|
|
21819
21908
|
}
|
|
21820
21909
|
return requestBody;
|
|
@@ -21895,7 +21984,7 @@ async function runGuard(opts, globals) {
|
|
|
21895
21984
|
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
21896
21985
|
if (!tokenResult.ok || !urlResult.ok) process.exit(0);
|
|
21897
21986
|
logEvent("guard_frame", { moment, ...frameTelemetry(frame, range) });
|
|
21898
|
-
const codeDelta = collectCodeDelta(files);
|
|
21987
|
+
const codeDelta = collectCodeDelta(files, GIT_MOMENT_BUDGET);
|
|
21899
21988
|
if (codeDelta.total_files === 0) process.exit(0);
|
|
21900
21989
|
const statedIntent = extractStatedIntent(
|
|
21901
21990
|
moment,
|
|
@@ -21921,6 +22010,7 @@ async function runGuard(opts, globals) {
|
|
|
21921
22010
|
excluded: codeDelta.excluded.map((e) => ({ path: e.path, reason: e.reason }))
|
|
21922
22011
|
};
|
|
21923
22012
|
logToFileOnly(coverageBlock(coverage));
|
|
22013
|
+
const reviewStart = Date.now();
|
|
21924
22014
|
const result = await analyzeRequest({
|
|
21925
22015
|
serviceUrl: urlResult.data,
|
|
21926
22016
|
token: tokenResult.data.token,
|
|
@@ -21929,6 +22019,7 @@ async function runGuard(opts, globals) {
|
|
|
21929
22019
|
timeout: 29e4,
|
|
21930
22020
|
cmd: "guard"
|
|
21931
22021
|
});
|
|
22022
|
+
const reviewSecs = Math.max(1, Math.round((Date.now() - reviewStart) / 1e3));
|
|
21932
22023
|
if (!result.ok) {
|
|
21933
22024
|
const authRemedy = result.error.startsWith("STALE_VERIFICATION") ? " Your GitHub verification expired \u2014 run `verity login` to re-verify." : result.error.startsWith("FORBIDDEN") ? " No access grant for this repository \u2014 run `verity login` to refresh your grants." : result.error.startsWith("INVALID_TOKEN") ? " Your Verity login expired or was revoked \u2014 run `verity login` to sign in again." : "";
|
|
21934
22025
|
emitAllowNotice(
|
|
@@ -21941,7 +22032,7 @@ async function runGuard(opts, globals) {
|
|
|
21941
22032
|
const decision = response.gate_decision ?? "(unrecognised)";
|
|
21942
22033
|
const viewUrl = response.view_url ?? "";
|
|
21943
22034
|
const link = viewUrl ? ` \u2014 ${viewUrl}` : "";
|
|
21944
|
-
const covLine = coverageSummary(coverage)
|
|
22035
|
+
const covLine = `${coverageSummary(coverage)} in ${reviewSecs}s`;
|
|
21945
22036
|
const covDetail = coverageBlock(coverage);
|
|
21946
22037
|
if (decision === "FAIL" && hasBlockingFinding(response, codeDelta.files.map((f) => f.path))) {
|
|
21947
22038
|
writeIter(moment, iter + 1);
|
|
@@ -22010,6 +22101,8 @@ function writeBlockMessage(moment, response, covDetail) {
|
|
|
22010
22101
|
|
|
22011
22102
|
`);
|
|
22012
22103
|
process.stderr.write(`${BOLD}Fix these, then re-run the ${verb}.${NC}
|
|
22104
|
+
`);
|
|
22105
|
+
process.stderr.write(`${DIM}Accepted risk? Record it: verity waive <pattern-id> --file <path> --reason "\u2026" (voids automatically when the file changes).${NC}
|
|
22013
22106
|
`);
|
|
22014
22107
|
}
|
|
22015
22108
|
|
|
@@ -22105,15 +22198,70 @@ function registerIgnoreCommand(program2) {
|
|
|
22105
22198
|
});
|
|
22106
22199
|
}
|
|
22107
22200
|
|
|
22201
|
+
// src/commands/waive.ts
|
|
22202
|
+
var import_node_crypto12 = require("node:crypto");
|
|
22203
|
+
var import_node_fs39 = require("node:fs");
|
|
22204
|
+
function registerWaiveCommand(program2) {
|
|
22205
|
+
program2.command("waive <pattern-id>").description("Record an accepted-risk disposition for an open finding (voids when the file changes)").option("--file <path>", "File the finding is anchored to, REPO-RELATIVE (recommended \u2014 narrows the waive)").requiredOption("--reason <text>", "The human disposition this records (reviewer finding, ADR, \u2026)").action(async (patternId, opts) => {
|
|
22206
|
+
const globals = program2.opts();
|
|
22207
|
+
try {
|
|
22208
|
+
process.chdir(repoRoot());
|
|
22209
|
+
} catch {
|
|
22210
|
+
}
|
|
22211
|
+
if (opts.reason.trim().length < 20) {
|
|
22212
|
+
printError("A waive needs a real reason (>= 20 chars) \u2014 name the human disposition it records.");
|
|
22213
|
+
process.exit(1);
|
|
22214
|
+
}
|
|
22215
|
+
const tokenResult = await resolveToken(globals.token);
|
|
22216
|
+
if (!tokenResult.ok) {
|
|
22217
|
+
printError(tokenResult.error);
|
|
22218
|
+
process.exit(1);
|
|
22219
|
+
}
|
|
22220
|
+
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
22221
|
+
if (!urlResult.ok) {
|
|
22222
|
+
printError(urlResult.error);
|
|
22223
|
+
process.exit(1);
|
|
22224
|
+
}
|
|
22225
|
+
const body = {
|
|
22226
|
+
pattern_id: patternId,
|
|
22227
|
+
reason: opts.reason.trim()
|
|
22228
|
+
};
|
|
22229
|
+
if (opts.file) {
|
|
22230
|
+
body.file = opts.file;
|
|
22231
|
+
try {
|
|
22232
|
+
body.file_sha256 = (0, import_node_crypto12.createHash)("sha256").update((0, import_node_fs39.readFileSync)(opts.file)).digest("hex");
|
|
22233
|
+
} catch {
|
|
22234
|
+
printError(`Cannot read ${opts.file} \u2014 run from the repo root, or omit --file to waive by pattern.`);
|
|
22235
|
+
process.exit(1);
|
|
22236
|
+
}
|
|
22237
|
+
}
|
|
22238
|
+
const result = await apiRequest({
|
|
22239
|
+
method: "POST",
|
|
22240
|
+
path: "/feedback/waive",
|
|
22241
|
+
serviceUrl: urlResult.data,
|
|
22242
|
+
token: tokenResult.data.token,
|
|
22243
|
+
body,
|
|
22244
|
+
verbose: globals.verbose
|
|
22245
|
+
});
|
|
22246
|
+
if (!result.ok) {
|
|
22247
|
+
printError(`Couldn't record the waive: ${result.error}`);
|
|
22248
|
+
process.exit(1);
|
|
22249
|
+
}
|
|
22250
|
+
logEvent("waive_recorded", { pattern: patternId, file: opts.file ?? null, waived: result.data.waived });
|
|
22251
|
+
printInfo(`Waived ${result.data.waived} open statement(s) for '${patternId}'${opts.file ? ` on ${opts.file}` : ""}.`);
|
|
22252
|
+
printInfo(result.data.note);
|
|
22253
|
+
});
|
|
22254
|
+
}
|
|
22255
|
+
|
|
22108
22256
|
// src/commands/init.ts
|
|
22109
|
-
var
|
|
22257
|
+
var import_node_fs41 = require("node:fs");
|
|
22110
22258
|
var import_promises13 = require("node:fs/promises");
|
|
22111
22259
|
var import_node_path29 = require("node:path");
|
|
22112
22260
|
var import_node_child_process11 = require("node:child_process");
|
|
22113
22261
|
var readline2 = __toESM(require("node:readline/promises"));
|
|
22114
22262
|
|
|
22115
22263
|
// src/commands/migrate.ts
|
|
22116
|
-
var
|
|
22264
|
+
var import_node_fs40 = require("node:fs");
|
|
22117
22265
|
var import_node_path28 = require("node:path");
|
|
22118
22266
|
var import_node_child_process10 = require("node:child_process");
|
|
22119
22267
|
|
|
@@ -22245,10 +22393,10 @@ async function runMigration(opts = {}) {
|
|
|
22245
22393
|
function migrateProjectDir(root, actions) {
|
|
22246
22394
|
const gateDir = (0, import_node_path28.join)(root, ".gate");
|
|
22247
22395
|
const verityDir = (0, import_node_path28.join)(root, ".verity");
|
|
22248
|
-
if ((0,
|
|
22396
|
+
if ((0, import_node_fs40.existsSync)(gateDir) && !(0, import_node_fs40.existsSync)(verityDir)) {
|
|
22249
22397
|
return migrateProjectDirRename(root, gateDir, verityDir, actions);
|
|
22250
22398
|
}
|
|
22251
|
-
if ((0,
|
|
22399
|
+
if ((0, import_node_fs40.existsSync)(gateDir) && (0, import_node_fs40.existsSync)(verityDir)) {
|
|
22252
22400
|
return migrateProjectDirCarry(gateDir, verityDir, actions);
|
|
22253
22401
|
}
|
|
22254
22402
|
return false;
|
|
@@ -22269,13 +22417,13 @@ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
|
|
|
22269
22417
|
}
|
|
22270
22418
|
}
|
|
22271
22419
|
if (moved) {
|
|
22272
|
-
if ((0,
|
|
22420
|
+
if ((0, import_node_fs40.existsSync)(gateDir)) {
|
|
22273
22421
|
const carried = carryLegacyContents(gateDir, verityDir);
|
|
22274
22422
|
if (carried > 0) {
|
|
22275
22423
|
actions.push(`Carried ${carried} untracked legacy file(s) from .gate/ into .verity/`);
|
|
22276
22424
|
}
|
|
22277
22425
|
try {
|
|
22278
|
-
(0,
|
|
22426
|
+
(0, import_node_fs40.rmSync)(gateDir, { recursive: true, force: true });
|
|
22279
22427
|
} catch {
|
|
22280
22428
|
}
|
|
22281
22429
|
}
|
|
@@ -22291,7 +22439,7 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
|
|
|
22291
22439
|
actions.push(`Carried ${carried} legacy file(s) from .gate/ into .verity/`);
|
|
22292
22440
|
}
|
|
22293
22441
|
try {
|
|
22294
|
-
(0,
|
|
22442
|
+
(0, import_node_fs40.rmSync)(gateDir, { recursive: true, force: true });
|
|
22295
22443
|
} catch {
|
|
22296
22444
|
}
|
|
22297
22445
|
return carried > 0;
|
|
@@ -22300,9 +22448,9 @@ function migrateGlobalCredentials(home, actions) {
|
|
|
22300
22448
|
if (!home) return;
|
|
22301
22449
|
const gateCreds = (0, import_node_path28.join)(home, ".gate", "credentials");
|
|
22302
22450
|
const verityCreds = (0, import_node_path28.join)(home, ".verity", "credentials");
|
|
22303
|
-
if (!(0,
|
|
22304
|
-
if (!(0,
|
|
22305
|
-
(0,
|
|
22451
|
+
if (!(0, import_node_fs40.existsSync)(gateCreds)) return;
|
|
22452
|
+
if (!(0, import_node_fs40.existsSync)(verityCreds)) {
|
|
22453
|
+
(0, import_node_fs40.mkdirSync)((0, import_node_path28.join)(home, ".verity"), { recursive: true });
|
|
22306
22454
|
moveFile(gateCreds, verityCreds);
|
|
22307
22455
|
actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
|
|
22308
22456
|
return;
|
|
@@ -22325,7 +22473,7 @@ async function migrateLegacyHooks(root, actions) {
|
|
|
22325
22473
|
}
|
|
22326
22474
|
async function migrateClaudeMd(root, actions) {
|
|
22327
22475
|
const claudeMd = (0, import_node_path28.join)(root, "CLAUDE.md");
|
|
22328
|
-
const hadLegacyBlock = (0,
|
|
22476
|
+
const hadLegacyBlock = (0, import_node_fs40.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
|
|
22329
22477
|
if (!hadLegacyBlock) return;
|
|
22330
22478
|
try {
|
|
22331
22479
|
await ensureClaudeMdPointer(root);
|
|
@@ -22337,7 +22485,7 @@ async function migrateClaudeMd(root, actions) {
|
|
|
22337
22485
|
function migrateStandardFile(root, actions) {
|
|
22338
22486
|
const gateMd = (0, import_node_path28.join)(root, "GATE.md");
|
|
22339
22487
|
const verityMd = (0, import_node_path28.join)(root, "VERITY.md");
|
|
22340
|
-
if (!(0,
|
|
22488
|
+
if (!(0, import_node_fs40.existsSync)(gateMd) || (0, import_node_fs40.existsSync)(verityMd)) return;
|
|
22341
22489
|
let moved = false;
|
|
22342
22490
|
if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
|
|
22343
22491
|
try {
|
|
@@ -22349,12 +22497,12 @@ function migrateStandardFile(root, actions) {
|
|
|
22349
22497
|
if (!moved) moveFile(gateMd, verityMd);
|
|
22350
22498
|
const content = readFileSyncSafe(verityMd);
|
|
22351
22499
|
const refreshed = content.split("GATE.md").join("VERITY.md");
|
|
22352
|
-
if (refreshed !== content) (0,
|
|
22500
|
+
if (refreshed !== content) (0, import_node_fs40.writeFileSync)(verityMd, refreshed);
|
|
22353
22501
|
actions.push("Renamed GATE.md \u2192 VERITY.md");
|
|
22354
22502
|
}
|
|
22355
22503
|
async function migrateTelemetryHeaders(root, actions) {
|
|
22356
22504
|
const file = (0, import_node_path28.join)(root, ".claude", "settings.local.json");
|
|
22357
|
-
if (!(0,
|
|
22505
|
+
if (!(0, import_node_fs40.existsSync)(file)) return;
|
|
22358
22506
|
let settings;
|
|
22359
22507
|
try {
|
|
22360
22508
|
settings = JSON.parse(readFileSyncSafe(file) || "{}");
|
|
@@ -22402,14 +22550,14 @@ function mergeGlobalCredentials(gateCreds, verityCreds) {
|
|
|
22402
22550
|
}
|
|
22403
22551
|
if (toAppend.length > 0) {
|
|
22404
22552
|
const sep2 = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
|
|
22405
|
-
(0,
|
|
22553
|
+
(0, import_node_fs40.writeFileSync)(verityCreds, verityContent + sep2 + toAppend.join("\n") + "\n");
|
|
22406
22554
|
}
|
|
22407
|
-
(0,
|
|
22555
|
+
(0, import_node_fs40.rmSync)(gateCreds, { force: true });
|
|
22408
22556
|
return toAppend.length;
|
|
22409
22557
|
}
|
|
22410
22558
|
function readFileSyncSafe(path) {
|
|
22411
22559
|
try {
|
|
22412
|
-
return (0,
|
|
22560
|
+
return (0, import_node_fs40.readFileSync)(path, "utf-8");
|
|
22413
22561
|
} catch {
|
|
22414
22562
|
return "";
|
|
22415
22563
|
}
|
|
@@ -22424,35 +22572,35 @@ function hasStagedChanges(root) {
|
|
|
22424
22572
|
}
|
|
22425
22573
|
function moveDir(from, to) {
|
|
22426
22574
|
try {
|
|
22427
|
-
(0,
|
|
22575
|
+
(0, import_node_fs40.renameSync)(from, to);
|
|
22428
22576
|
} catch (err) {
|
|
22429
22577
|
if (err.code !== "EXDEV") throw err;
|
|
22430
|
-
(0,
|
|
22431
|
-
(0,
|
|
22578
|
+
(0, import_node_fs40.cpSync)(from, to, { recursive: true });
|
|
22579
|
+
(0, import_node_fs40.rmSync)(from, { recursive: true, force: true });
|
|
22432
22580
|
}
|
|
22433
22581
|
}
|
|
22434
22582
|
function moveFile(from, to) {
|
|
22435
22583
|
try {
|
|
22436
|
-
(0,
|
|
22584
|
+
(0, import_node_fs40.renameSync)(from, to);
|
|
22437
22585
|
} catch (err) {
|
|
22438
22586
|
if (err.code !== "EXDEV") throw err;
|
|
22439
|
-
(0,
|
|
22440
|
-
(0,
|
|
22587
|
+
(0, import_node_fs40.cpSync)(from, to);
|
|
22588
|
+
(0, import_node_fs40.rmSync)(from, { force: true });
|
|
22441
22589
|
}
|
|
22442
22590
|
}
|
|
22443
22591
|
function carryLegacyContents(gateDir, verityDir) {
|
|
22444
22592
|
let copied = 0;
|
|
22445
22593
|
const walk = (relDir) => {
|
|
22446
22594
|
const srcDir = (0, import_node_path28.join)(gateDir, relDir);
|
|
22447
|
-
for (const entry of (0,
|
|
22595
|
+
for (const entry of (0, import_node_fs40.readdirSync)(srcDir)) {
|
|
22448
22596
|
const rel = relDir ? (0, import_node_path28.join)(relDir, entry) : entry;
|
|
22449
22597
|
const src = (0, import_node_path28.join)(gateDir, rel);
|
|
22450
22598
|
const dest = (0, import_node_path28.join)(verityDir, rel);
|
|
22451
|
-
if ((0,
|
|
22599
|
+
if ((0, import_node_fs40.statSync)(src).isDirectory()) {
|
|
22452
22600
|
walk(rel);
|
|
22453
|
-
} else if (!(0,
|
|
22454
|
-
(0,
|
|
22455
|
-
(0,
|
|
22601
|
+
} else if (!(0, import_node_fs40.existsSync)(dest)) {
|
|
22602
|
+
(0, import_node_fs40.mkdirSync)((0, import_node_path28.dirname)(dest), { recursive: true });
|
|
22603
|
+
(0, import_node_fs40.cpSync)(src, dest);
|
|
22456
22604
|
copied++;
|
|
22457
22605
|
}
|
|
22458
22606
|
}
|
|
@@ -22463,20 +22611,20 @@ function carryLegacyContents(gateDir, verityDir) {
|
|
|
22463
22611
|
async function needsMigration(root = repoRoot()) {
|
|
22464
22612
|
const gateDir = (0, import_node_path28.join)(root, ".gate");
|
|
22465
22613
|
const verityDir = (0, import_node_path28.join)(root, ".verity");
|
|
22466
|
-
if ((0,
|
|
22467
|
-
if ((0,
|
|
22468
|
-
if ((0,
|
|
22614
|
+
if ((0, import_node_fs40.existsSync)(gateDir) && !(0, import_node_fs40.existsSync)(verityDir)) return true;
|
|
22615
|
+
if ((0, import_node_fs40.existsSync)(gateDir) && (0, import_node_fs40.existsSync)(verityDir)) {
|
|
22616
|
+
if ((0, import_node_fs40.existsSync)((0, import_node_path28.join)(gateDir, "credentials")) && !(0, import_node_fs40.existsSync)((0, import_node_path28.join)(verityDir, "credentials"))) {
|
|
22469
22617
|
return true;
|
|
22470
22618
|
}
|
|
22471
|
-
if ((0,
|
|
22619
|
+
if ((0, import_node_fs40.existsSync)((0, import_node_path28.join)(gateDir, "memory")) && !(0, import_node_fs40.existsSync)((0, import_node_path28.join)(verityDir, "memory"))) {
|
|
22472
22620
|
return true;
|
|
22473
22621
|
}
|
|
22474
22622
|
}
|
|
22475
22623
|
const claudeMd = (0, import_node_path28.join)(root, "CLAUDE.md");
|
|
22476
|
-
if ((0,
|
|
22624
|
+
if ((0, import_node_fs40.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
|
|
22477
22625
|
return true;
|
|
22478
22626
|
}
|
|
22479
|
-
if ((0,
|
|
22627
|
+
if ((0, import_node_fs40.existsSync)((0, import_node_path28.join)(root, "GATE.md")) && !(0, import_node_fs40.existsSync)((0, import_node_path28.join)(root, "VERITY.md"))) {
|
|
22480
22628
|
return true;
|
|
22481
22629
|
}
|
|
22482
22630
|
if (await hasLegacyHooksAt(root)) return true;
|
|
@@ -22620,7 +22768,7 @@ function resolveDataDir() {
|
|
|
22620
22768
|
// local dev: running from repo root
|
|
22621
22769
|
];
|
|
22622
22770
|
for (const candidate of candidates) {
|
|
22623
|
-
if ((0,
|
|
22771
|
+
if ((0, import_node_fs41.existsSync)((0, import_node_path29.join)(candidate, "skills"))) {
|
|
22624
22772
|
return candidate;
|
|
22625
22773
|
}
|
|
22626
22774
|
}
|
|
@@ -22636,7 +22784,7 @@ function registerInitCommand(program2) {
|
|
|
22636
22784
|
program2.command("init").description("Initialize Verity in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
|
|
22637
22785
|
const force = opts.force ?? false;
|
|
22638
22786
|
const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
|
|
22639
|
-
const isProject = projectMarkers.some((m) => (0,
|
|
22787
|
+
const isProject = projectMarkers.some((m) => (0, import_node_fs41.existsSync)(m));
|
|
22640
22788
|
if (!isProject) {
|
|
22641
22789
|
printError("No project detected in the current directory.");
|
|
22642
22790
|
printInfo('Run "verity init" from your project root.');
|
|
@@ -22706,14 +22854,14 @@ function registerInitCommand(program2) {
|
|
|
22706
22854
|
for (const skill of skills) {
|
|
22707
22855
|
const src = (0, import_node_path29.join)(skillsSource, skill);
|
|
22708
22856
|
const dest = (0, import_node_path29.join)(skillsDest, skill);
|
|
22709
|
-
if (!(0,
|
|
22857
|
+
if (!(0, import_node_fs41.existsSync)(src)) {
|
|
22710
22858
|
printWarn(` Skill data not found: ${skill}`);
|
|
22711
22859
|
continue;
|
|
22712
22860
|
}
|
|
22713
|
-
if ((0,
|
|
22861
|
+
if ((0, import_node_fs41.existsSync)(dest) && !force) {
|
|
22714
22862
|
const srcSkill = (0, import_node_path29.join)(src, "SKILL.md");
|
|
22715
22863
|
const destSkill = (0, import_node_path29.join)(dest, "SKILL.md");
|
|
22716
|
-
if ((0,
|
|
22864
|
+
if ((0, import_node_fs41.existsSync)(destSkill)) {
|
|
22717
22865
|
try {
|
|
22718
22866
|
const srcContent = await (0, import_promises13.readFile)(srcSkill, "utf-8");
|
|
22719
22867
|
const destContent = await (0, import_promises13.readFile)(destSkill, "utf-8");
|
|
@@ -22791,7 +22939,7 @@ function registerInitCommand(program2) {
|
|
|
22791
22939
|
}
|
|
22792
22940
|
|
|
22793
22941
|
// src/commands/uninstall.ts
|
|
22794
|
-
var
|
|
22942
|
+
var import_node_fs42 = require("node:fs");
|
|
22795
22943
|
var import_node_path30 = require("node:path");
|
|
22796
22944
|
var SKILL_NAMES = [
|
|
22797
22945
|
"verity-setup",
|
|
@@ -22812,10 +22960,10 @@ function registerUninstallCommand(program2) {
|
|
|
22812
22960
|
const skillsRoot = projectPath(".claude/skills");
|
|
22813
22961
|
for (const name of SKILL_NAMES) {
|
|
22814
22962
|
const dir = (0, import_node_path30.join)(skillsRoot, name);
|
|
22815
|
-
if ((0,
|
|
22963
|
+
if ((0, import_node_fs42.existsSync)(dir)) {
|
|
22816
22964
|
actions.push({
|
|
22817
22965
|
label: `Remove .claude/skills/${name}/`,
|
|
22818
|
-
apply: () => (0,
|
|
22966
|
+
apply: () => (0, import_node_fs42.rmSync)(dir, { recursive: true, force: true })
|
|
22819
22967
|
});
|
|
22820
22968
|
}
|
|
22821
22969
|
}
|
|
@@ -22829,24 +22977,24 @@ function registerUninstallCommand(program2) {
|
|
|
22829
22977
|
});
|
|
22830
22978
|
}
|
|
22831
22979
|
const verityDir = projectPath(VERITY_DIR);
|
|
22832
|
-
if ((0,
|
|
22980
|
+
if ((0, import_node_fs42.existsSync)(verityDir)) {
|
|
22833
22981
|
actions.push({
|
|
22834
22982
|
label: `Remove ${VERITY_DIR}/`,
|
|
22835
|
-
apply: () => (0,
|
|
22983
|
+
apply: () => (0, import_node_fs42.rmSync)(verityDir, { recursive: true, force: true })
|
|
22836
22984
|
});
|
|
22837
22985
|
}
|
|
22838
22986
|
if (!keepVerityMd) {
|
|
22839
22987
|
const verityMd = projectPath(VERITY_MD_FILE);
|
|
22840
|
-
if ((0,
|
|
22988
|
+
if ((0, import_node_fs42.existsSync)(verityMd)) {
|
|
22841
22989
|
actions.push({
|
|
22842
22990
|
label: `Remove ${VERITY_MD_FILE}`,
|
|
22843
|
-
apply: () => (0,
|
|
22991
|
+
apply: () => (0, import_node_fs42.rmSync)(verityMd, { force: true })
|
|
22844
22992
|
});
|
|
22845
22993
|
}
|
|
22846
22994
|
}
|
|
22847
22995
|
const cleanupEmptyDir = (path) => {
|
|
22848
|
-
if ((0,
|
|
22849
|
-
(0,
|
|
22996
|
+
if ((0, import_node_fs42.existsSync)(path) && (0, import_node_fs42.statSync)(path).isDirectory() && (0, import_node_fs42.readdirSync)(path).length === 0) {
|
|
22997
|
+
(0, import_node_fs42.rmdirSync)(path);
|
|
22850
22998
|
}
|
|
22851
22999
|
};
|
|
22852
23000
|
actions.push({
|
|
@@ -22858,10 +23006,10 @@ function registerUninstallCommand(program2) {
|
|
|
22858
23006
|
});
|
|
22859
23007
|
const home = process.env.HOME ?? "";
|
|
22860
23008
|
const globalVerityDir = (0, import_node_path30.join)(home, ".verity");
|
|
22861
|
-
if (purgeGlobal && (0,
|
|
23009
|
+
if (purgeGlobal && (0, import_node_fs42.existsSync)(globalVerityDir)) {
|
|
22862
23010
|
actions.push({
|
|
22863
23011
|
label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
|
|
22864
|
-
apply: () => (0,
|
|
23012
|
+
apply: () => (0, import_node_fs42.rmSync)(globalVerityDir, { recursive: true, force: true })
|
|
22865
23013
|
});
|
|
22866
23014
|
}
|
|
22867
23015
|
if (actions.length === 0) {
|
|
@@ -23055,7 +23203,7 @@ function registerTaskCommands(program2) {
|
|
|
23055
23203
|
}
|
|
23056
23204
|
|
|
23057
23205
|
// src/commands/reset.ts
|
|
23058
|
-
var
|
|
23206
|
+
var import_node_fs43 = require("node:fs");
|
|
23059
23207
|
var import_node_path31 = require("node:path");
|
|
23060
23208
|
function registerResetCommand(program2) {
|
|
23061
23209
|
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) => {
|
|
@@ -23093,11 +23241,11 @@ function registerResetCommand(program2) {
|
|
|
23093
23241
|
}
|
|
23094
23242
|
const cacheDir = projectPath(CACHE_DIR);
|
|
23095
23243
|
let purged = 0;
|
|
23096
|
-
if ((0,
|
|
23097
|
-
for (const entry of (0,
|
|
23244
|
+
if ((0, import_node_fs43.existsSync)(cacheDir)) {
|
|
23245
|
+
for (const entry of (0, import_node_fs43.readdirSync)(cacheDir)) {
|
|
23098
23246
|
if (entry.startsWith("pending-")) {
|
|
23099
23247
|
try {
|
|
23100
|
-
(0,
|
|
23248
|
+
(0, import_node_fs43.unlinkSync)((0, import_node_path31.join)(cacheDir, entry));
|
|
23101
23249
|
purged++;
|
|
23102
23250
|
} catch {
|
|
23103
23251
|
}
|
|
@@ -23112,19 +23260,19 @@ function registerResetCommand(program2) {
|
|
|
23112
23260
|
projectPath(`${VERITY_DIR}/.last-analysis`)
|
|
23113
23261
|
];
|
|
23114
23262
|
for (const file of filesToClear) {
|
|
23115
|
-
if ((0,
|
|
23263
|
+
if ((0, import_node_fs43.existsSync)(file)) {
|
|
23116
23264
|
try {
|
|
23117
|
-
(0,
|
|
23265
|
+
(0, import_node_fs43.writeFileSync)(file, "");
|
|
23118
23266
|
} catch {
|
|
23119
23267
|
}
|
|
23120
23268
|
}
|
|
23121
23269
|
}
|
|
23122
23270
|
if (opts.all) {
|
|
23123
23271
|
const logsDir = projectPath(`${VERITY_DIR}/.logs`);
|
|
23124
|
-
if ((0,
|
|
23125
|
-
for (const entry of (0,
|
|
23272
|
+
if ((0, import_node_fs43.existsSync)(logsDir)) {
|
|
23273
|
+
for (const entry of (0, import_node_fs43.readdirSync)(logsDir)) {
|
|
23126
23274
|
try {
|
|
23127
|
-
(0,
|
|
23275
|
+
(0, import_node_fs43.unlinkSync)((0, import_node_path31.join)(logsDir, entry));
|
|
23128
23276
|
} catch {
|
|
23129
23277
|
}
|
|
23130
23278
|
}
|
|
@@ -23432,8 +23580,8 @@ function registerTelemetryCommands(program2) {
|
|
|
23432
23580
|
}
|
|
23433
23581
|
|
|
23434
23582
|
// src/cli.ts
|
|
23435
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.31.0
|
|
23436
|
-
installStderrLog(actionCommand.name(), process.argv.slice(2), "0.31.0
|
|
23583
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.31.0").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async (_thisCommand, actionCommand) => {
|
|
23584
|
+
installStderrLog(actionCommand.name(), process.argv.slice(2), "0.31.0");
|
|
23437
23585
|
setUserNamedServiceUrl(program.opts().serviceUrl);
|
|
23438
23586
|
try {
|
|
23439
23587
|
await foldLegacyLocalCredential();
|
|
@@ -23457,6 +23605,7 @@ registerBaselineCommands(program);
|
|
|
23457
23605
|
registerReviewCommand(program);
|
|
23458
23606
|
registerGuardCommand(program);
|
|
23459
23607
|
registerIgnoreCommand(program);
|
|
23608
|
+
registerWaiveCommand(program);
|
|
23460
23609
|
registerInitCommand(program);
|
|
23461
23610
|
registerUninstallCommand(program);
|
|
23462
23611
|
registerTaskCommands(program);
|
|
@@ -85,3 +85,27 @@ curl -s -X POST -H "Authorization: Bearer $VERITY_TOKEN" \
|
|
|
85
85
|
3. **Review**: User sees suggestions with evidence (run counts, FP rates, citation counts)
|
|
86
86
|
4. **Apply**: Approved suggestions become a new Standard version (immutable, rollback-safe)
|
|
87
87
|
5. **No autonomous changes** in v1 — every suggestion requires human approval
|
|
88
|
+
|
|
89
|
+
## Recording an accepted-risk disposition (unblocking a guard deadlock)
|
|
90
|
+
|
|
91
|
+
When the pre-commit/pre-push guard blocks on a finding that humans have already
|
|
92
|
+
adjudicated the other way (a reviewer finding, an ADR, phase exit criteria),
|
|
93
|
+
record the disposition with:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
verity waive <pattern-id> --file <path> --reason "per reviewer finding 5 — no code change needed"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
What this does — and deliberately does not do:
|
|
100
|
+
|
|
101
|
+
- Marks the open statement `waived` with your reason; the next guard/analyze run
|
|
102
|
+
on the **same file bytes** no longer blocks on it, and the disposition is
|
|
103
|
+
visible in the run report.
|
|
104
|
+
- **It voids automatically when the file changes** — a waive is an assertion
|
|
105
|
+
about specific bytes, never a standing mute. Edit the file and the finding is
|
|
106
|
+
reconsidered fresh.
|
|
107
|
+
- The reason is mandatory (≥ 20 chars) and should name the human decision it
|
|
108
|
+
traces to. At most 3 statements are waived per invocation.
|
|
109
|
+
- `verity feedback finding <run-id> <pattern-id> false_positive` remains the
|
|
110
|
+
right tool for *pattern-level* feedback (it feeds suppression/evolution);
|
|
111
|
+
`verity waive` is the *per-site, accepted-risk* path that unblocks a push.
|