@codacy/verity-cli 0.30.1 → 0.31.0-experimental.2221878
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 +1148 -420
- package/data/skills/verity-insights/SKILL.md +24 -0
- package/data/skills/verity-setup/SKILL.md +42 -0
- package/package.json +1 -1
package/bin/verity.js
CHANGED
|
@@ -10387,6 +10387,7 @@ var CLAUDE_SETTINGS_FILE = ".claude/settings.json";
|
|
|
10387
10387
|
var STANDARD_FILE = `${VERITY_DIR}/standard.yaml`;
|
|
10388
10388
|
var MEMORY_DIR = `${VERITY_DIR}/memory`;
|
|
10389
10389
|
var CODACY_CONFIG_FILE = ".codacy/codacy.config.json";
|
|
10390
|
+
var VERITYIGNORE_FILE = ".verityignore";
|
|
10390
10391
|
function projectPath(relativePath) {
|
|
10391
10392
|
return (0, import_node_path.join)(repoRoot(), relativePath);
|
|
10392
10393
|
}
|
|
@@ -10406,6 +10407,13 @@ var CONVERSATION_BUFFER_FILE = `${VERITY_DIR}/.conversation-buffer`;
|
|
|
10406
10407
|
var CONVERSATION_MAX_ENTRIES = 10;
|
|
10407
10408
|
var CONVERSATION_WINDOW_MINUTES = 15;
|
|
10408
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
|
+
};
|
|
10409
10417
|
var ANALYZABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
10410
10418
|
"ts",
|
|
10411
10419
|
"tsx",
|
|
@@ -10477,7 +10485,8 @@ var REVIEWABLE_FILENAMES = /* @__PURE__ */ new Set([
|
|
|
10477
10485
|
"Makefile",
|
|
10478
10486
|
"Dockerfile",
|
|
10479
10487
|
"Jenkinsfile",
|
|
10480
|
-
"Vagrantfile"
|
|
10488
|
+
"Vagrantfile",
|
|
10489
|
+
".verityignore"
|
|
10481
10490
|
]);
|
|
10482
10491
|
var REVIEWABLE_PATH_PATTERNS = [
|
|
10483
10492
|
/\.circleci\//,
|
|
@@ -10497,7 +10506,7 @@ var SECURITY_PATTERNS = [
|
|
|
10497
10506
|
/Dockerfile/
|
|
10498
10507
|
];
|
|
10499
10508
|
var PROD_SERVICE_URL = "https://ofcamwrjwrkazqvdchko.supabase.co/functions/v1";
|
|
10500
|
-
var DEFAULT_SERVICE_URL = "".length > 0 ? "" : PROD_SERVICE_URL;
|
|
10509
|
+
var DEFAULT_SERVICE_URL = "https://wukeddyzpijoegyajtnc.supabase.co/functions/v1".length > 0 ? "https://wukeddyzpijoegyajtnc.supabase.co/functions/v1" : PROD_SERVICE_URL;
|
|
10501
10510
|
var GITHUB_CLIENT_ID = "Iv23li88HxAi3ZrbYzWh";
|
|
10502
10511
|
var GITHUB_DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
10503
10512
|
var GITHUB_ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
@@ -10507,6 +10516,7 @@ function githubAppInstallUrl(accountId) {
|
|
|
10507
10516
|
return accountId != null ? `https://github.com/apps/${GITHUB_APP_SLUG}/installations/new/permissions?target_id=${accountId}` : GITHUB_APP_INSTALL_URL;
|
|
10508
10517
|
}
|
|
10509
10518
|
var ADVISORY_EPISODE_FILE = `${VERITY_DIR}/.advisory-episode`;
|
|
10519
|
+
var IGNORE_DECLARATION_FILE = `${VERITY_DIR}/.ignore-declaration`;
|
|
10510
10520
|
|
|
10511
10521
|
// src/lib/output.ts
|
|
10512
10522
|
var RED = "\x1B[0;31m";
|
|
@@ -13530,6 +13540,90 @@ var LEGACY_MD_END = "<!-- gate-memory:end -->";
|
|
|
13530
13540
|
var LEGACY_PRESERVE_START = "<!-- gate-memory:preserve -->";
|
|
13531
13541
|
var LEGACY_PRESERVE_END = "<!-- /gate-memory:preserve -->";
|
|
13532
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 = [
|
|
13591
|
+
"## Project Memory",
|
|
13592
|
+
"",
|
|
13593
|
+
"This project has a knowledge graph maintained at `.verity/memory/`. Before starting",
|
|
13594
|
+
"non-trivial work, scan `.verity/memory/index.md` for decisions, gotchas, and patterns",
|
|
13595
|
+
"that may apply to the change you are about to make. Open specific node files via",
|
|
13596
|
+
"the Read tool when the title or scope suggests relevance.",
|
|
13597
|
+
"",
|
|
13598
|
+
"The graph is auto-maintained by Verity. Files at `.verity/memory/_archive/` are",
|
|
13599
|
+
"superseded \u2014 ignore them unless investigating history.",
|
|
13600
|
+
"",
|
|
13601
|
+
"> Durable, hand-curated guidance goes in the preserve region below (it survives",
|
|
13602
|
+
"> regeneration) or anywhere OUTSIDE these markers. Everything else between the",
|
|
13603
|
+
"> markers is tool-owned and overwritten on each run.",
|
|
13604
|
+
"",
|
|
13605
|
+
"## Housekeeping Turns",
|
|
13606
|
+
"",
|
|
13607
|
+
"When a turn will be pure housekeeping \u2014 pulling, installing dependencies,",
|
|
13608
|
+
"rebasing, a formatting sweep you are not authoring \u2014 declare it BEFORE doing it:",
|
|
13609
|
+
"",
|
|
13610
|
+
"```bash",
|
|
13611
|
+
'verity ignore --turn --agent --reason "pulling latest before starting"',
|
|
13612
|
+
"```",
|
|
13613
|
+
"",
|
|
13614
|
+
"This skips the review for that turn, which saves the turn Verity would",
|
|
13615
|
+
"otherwise spend saying it had nothing to say. Use `--for 30m` instead of",
|
|
13616
|
+
"`--turn` when a single piece of housekeeping spans several turns.",
|
|
13617
|
+
"",
|
|
13618
|
+
"**It is a claim about the turn, not a way to silence review.** The declaration",
|
|
13619
|
+
"is checked against what the turn actually did: if anything is authored \u2014 by you,",
|
|
13620
|
+
"by a subagent, or by a shell command that can write files \u2014 it voids, the review",
|
|
13621
|
+
"runs anyway, and the broken declaration is reported. So declare housekeeping you",
|
|
13622
|
+
"are about to do, never work you have already done, and never as a way to get past",
|
|
13623
|
+
"a finding. Declarations are budgeted per session and every one is recorded with",
|
|
13624
|
+
"its reason."
|
|
13625
|
+
].join("\n");
|
|
13626
|
+
var CLAUDE_MD_PROSE_PRE_IGNORE = [
|
|
13533
13627
|
"## Project Memory",
|
|
13534
13628
|
"",
|
|
13535
13629
|
"This project has a knowledge graph maintained at `.verity/memory/`. Before starting",
|
|
@@ -13654,7 +13748,12 @@ function extractPreserveContent(interior) {
|
|
|
13654
13748
|
}
|
|
13655
13749
|
function stripKnownProse(interior) {
|
|
13656
13750
|
const trimmed = interior.replace(/^\n+/, "");
|
|
13657
|
-
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
|
+
]) {
|
|
13658
13757
|
if (trimmed.startsWith(prose)) return trimmed.slice(prose.length);
|
|
13659
13758
|
}
|
|
13660
13759
|
return trimmed;
|
|
@@ -14320,6 +14419,7 @@ function reduce(state, events, now) {
|
|
|
14320
14419
|
existing.line_sha = ev.line_sha ?? existing.line_sha;
|
|
14321
14420
|
existing.register = ev.register;
|
|
14322
14421
|
if (existing.outcome === "fixed") existing.outcome = "recurred";
|
|
14422
|
+
else if (existing.outcome !== "open") existing.outcome = "open";
|
|
14323
14423
|
break;
|
|
14324
14424
|
}
|
|
14325
14425
|
byAnchor.set(ev.anchor_key, {
|
|
@@ -16270,7 +16370,145 @@ async function readHookStdin() {
|
|
|
16270
16370
|
|
|
16271
16371
|
// src/commands/standard.ts
|
|
16272
16372
|
var import_promises9 = require("node:fs/promises");
|
|
16373
|
+
var import_node_fs20 = require("node:fs");
|
|
16273
16374
|
var import_yaml = __toESM(require_dist());
|
|
16375
|
+
|
|
16376
|
+
// src/lib/verityignore.ts
|
|
16377
|
+
var import_node_fs19 = require("node:fs");
|
|
16378
|
+
var EMPTY = { rules: [], securityOverlap: [], problems: [] };
|
|
16379
|
+
var SECURITY_PROBES = [
|
|
16380
|
+
".env",
|
|
16381
|
+
".env.local",
|
|
16382
|
+
".env.production",
|
|
16383
|
+
"config/.env",
|
|
16384
|
+
"services/api/.env",
|
|
16385
|
+
"package-lock.json",
|
|
16386
|
+
"yarn.lock",
|
|
16387
|
+
"pnpm-lock.yaml",
|
|
16388
|
+
"Cargo.lock",
|
|
16389
|
+
"go.sum",
|
|
16390
|
+
"Gemfile.lock",
|
|
16391
|
+
"Dockerfile",
|
|
16392
|
+
"docker/Dockerfile"
|
|
16393
|
+
];
|
|
16394
|
+
function isSecuritySensitive(path) {
|
|
16395
|
+
return SECURITY_PATTERNS.some((p) => p.test(path));
|
|
16396
|
+
}
|
|
16397
|
+
function compile(pattern) {
|
|
16398
|
+
let p = pattern;
|
|
16399
|
+
const dirOnly = p.endsWith("/");
|
|
16400
|
+
if (dirOnly) p = p.slice(0, -1);
|
|
16401
|
+
const anchored = p.includes("/");
|
|
16402
|
+
if (p.startsWith("/")) p = p.slice(1);
|
|
16403
|
+
const base = anchored ? p : `**/${p}`;
|
|
16404
|
+
const forms = dirOnly ? [`${base}/**/*`] : [base, `${base}/**`];
|
|
16405
|
+
const regexes = [];
|
|
16406
|
+
for (const f of forms) {
|
|
16407
|
+
try {
|
|
16408
|
+
regexes.push(globToRegex(f));
|
|
16409
|
+
} catch {
|
|
16410
|
+
}
|
|
16411
|
+
}
|
|
16412
|
+
if (regexes.length === 0) return () => false;
|
|
16413
|
+
return (path) => regexes.some((r) => r.test(path));
|
|
16414
|
+
}
|
|
16415
|
+
function parseVerityIgnore(content) {
|
|
16416
|
+
const rules = [];
|
|
16417
|
+
const problems = [];
|
|
16418
|
+
const lines = content.split("\n");
|
|
16419
|
+
for (let i = 0; i < lines.length; i++) {
|
|
16420
|
+
const lineNo = i + 1;
|
|
16421
|
+
let raw = lines[i];
|
|
16422
|
+
raw = raw.replace(/(?<!\\)\s+$/, "");
|
|
16423
|
+
if (raw.length === 0) continue;
|
|
16424
|
+
if (raw.startsWith("#")) continue;
|
|
16425
|
+
let negated = false;
|
|
16426
|
+
if (raw.startsWith("!")) {
|
|
16427
|
+
negated = true;
|
|
16428
|
+
raw = raw.slice(1);
|
|
16429
|
+
} else if (raw.startsWith("\\!") || raw.startsWith("\\#")) {
|
|
16430
|
+
raw = raw.slice(1);
|
|
16431
|
+
}
|
|
16432
|
+
if (raw.length === 0) {
|
|
16433
|
+
problems.push(`line ${lineNo}: "!" with no pattern after it`);
|
|
16434
|
+
continue;
|
|
16435
|
+
}
|
|
16436
|
+
if (raw === "**" || raw === "*" || raw === "/" || raw === "**/*") {
|
|
16437
|
+
problems.push(
|
|
16438
|
+
`line ${lineNo}: "${raw}" would exclude the whole repository from review \u2014 refused. List the directories you mean instead.`
|
|
16439
|
+
);
|
|
16440
|
+
continue;
|
|
16441
|
+
}
|
|
16442
|
+
rules.push({ raw, line: lineNo, negated, test: compile(raw) });
|
|
16443
|
+
}
|
|
16444
|
+
const byRule = /* @__PURE__ */ new Map();
|
|
16445
|
+
for (const probe of SECURITY_PROBES) {
|
|
16446
|
+
let responsible = null;
|
|
16447
|
+
for (const rule of rules) {
|
|
16448
|
+
if (rule.test(probe)) responsible = rule.negated ? null : rule;
|
|
16449
|
+
}
|
|
16450
|
+
if (!responsible) continue;
|
|
16451
|
+
const entry = byRule.get(responsible.line) ?? { raw: responsible.raw, line: responsible.line, hides: [] };
|
|
16452
|
+
entry.hides.push(probe);
|
|
16453
|
+
byRule.set(responsible.line, entry);
|
|
16454
|
+
}
|
|
16455
|
+
const securityOverlap = [...byRule.values()].sort((a, b) => a.line - b.line);
|
|
16456
|
+
return { rules, securityOverlap, problems };
|
|
16457
|
+
}
|
|
16458
|
+
function decide(rules, path) {
|
|
16459
|
+
let excluded = false;
|
|
16460
|
+
for (const rule of rules) {
|
|
16461
|
+
if (rule.test(path)) excluded = !rule.negated;
|
|
16462
|
+
}
|
|
16463
|
+
return excluded;
|
|
16464
|
+
}
|
|
16465
|
+
function isIgnored(ig, path) {
|
|
16466
|
+
return decide(ig.rules, path);
|
|
16467
|
+
}
|
|
16468
|
+
function loadVerityIgnore() {
|
|
16469
|
+
const file = projectPath(VERITYIGNORE_FILE);
|
|
16470
|
+
if (!(0, import_node_fs19.existsSync)(file)) return EMPTY;
|
|
16471
|
+
try {
|
|
16472
|
+
return parseVerityIgnore((0, import_node_fs19.readFileSync)(file, "utf-8"));
|
|
16473
|
+
} catch {
|
|
16474
|
+
return EMPTY;
|
|
16475
|
+
}
|
|
16476
|
+
}
|
|
16477
|
+
function partitionIgnored(paths, ig) {
|
|
16478
|
+
const suspended = paths.some((p) => p === VERITYIGNORE_FILE);
|
|
16479
|
+
if (suspended || ig.rules.length === 0) {
|
|
16480
|
+
return { rules: ig.rules.length, kept: [...paths], ignored: [], securityExcluded: [], suspended };
|
|
16481
|
+
}
|
|
16482
|
+
const kept = [];
|
|
16483
|
+
const ignored = [];
|
|
16484
|
+
for (const p of paths) {
|
|
16485
|
+
if (p !== VERITYIGNORE_FILE && isIgnored(ig, p)) ignored.push(p);
|
|
16486
|
+
else kept.push(p);
|
|
16487
|
+
}
|
|
16488
|
+
return {
|
|
16489
|
+
rules: ig.rules.length,
|
|
16490
|
+
kept,
|
|
16491
|
+
ignored,
|
|
16492
|
+
securityExcluded: ignored.filter(isSecuritySensitive),
|
|
16493
|
+
suspended: false
|
|
16494
|
+
};
|
|
16495
|
+
}
|
|
16496
|
+
function ignoreShare(kept, ignored) {
|
|
16497
|
+
const total = kept + ignored;
|
|
16498
|
+
if (total === 0) return 0;
|
|
16499
|
+
return Math.round(ignored / total * 1e3) / 1e3;
|
|
16500
|
+
}
|
|
16501
|
+
function describeSecurityOverlap(ig) {
|
|
16502
|
+
if (ig.securityOverlap.length === 0) return null;
|
|
16503
|
+
const lines = ig.securityOverlap.map(
|
|
16504
|
+
(o) => ` line ${o.line}: "${o.raw}" would hide ${o.hides.slice(0, 3).join(", ")}` + (o.hides.length > 3 ? ` (+${o.hides.length - 3} more)` : "")
|
|
16505
|
+
);
|
|
16506
|
+
return `.verityignore: ${ig.securityOverlap.length} pattern(s) can hide security-sensitive files from review:
|
|
16507
|
+
${lines.join("\n")}
|
|
16508
|
+
These are still excluded \u2014 this is a warning, not a refusal. Add a \`!\` rule to keep one in scope, e.g. \`!.env*\`.`;
|
|
16509
|
+
}
|
|
16510
|
+
|
|
16511
|
+
// src/commands/standard.ts
|
|
16274
16512
|
function registerStandardCommands(program2) {
|
|
16275
16513
|
const standard = program2.command("standard").description("Manage the project Standard");
|
|
16276
16514
|
standard.command("push").description("Upload the project Standard to the service").option("--file <path>", "Path to standard YAML file", STANDARD_FILE).option("--created-by <name>", "Who created this version", "claude-code").action(async (opts) => {
|
|
@@ -16299,12 +16537,25 @@ function registerStandardCommands(program2) {
|
|
|
16299
16537
|
printError(`Invalid YAML in ${opts.file}: ${err.message}`);
|
|
16300
16538
|
process.exit(1);
|
|
16301
16539
|
}
|
|
16540
|
+
let ignoreRaw = null;
|
|
16541
|
+
const ignorePath = projectPath(VERITYIGNORE_FILE);
|
|
16542
|
+
if ((0, import_node_fs20.existsSync)(ignorePath)) {
|
|
16543
|
+
try {
|
|
16544
|
+
ignoreRaw = (0, import_node_fs20.readFileSync)(ignorePath, "utf-8");
|
|
16545
|
+
const overlap = describeSecurityOverlap(parseVerityIgnore(ignoreRaw));
|
|
16546
|
+
if (overlap) printWarn(overlap);
|
|
16547
|
+
} catch {
|
|
16548
|
+
}
|
|
16549
|
+
}
|
|
16302
16550
|
const result = await apiRequest({
|
|
16303
16551
|
method: "POST",
|
|
16304
16552
|
path: "/standards",
|
|
16305
16553
|
serviceUrl: urlResult.data,
|
|
16306
16554
|
token: tokenResult.data.token,
|
|
16307
|
-
body: {
|
|
16555
|
+
body: {
|
|
16556
|
+
content: ignoreRaw === null ? content : { ...content, verityignore: ignoreRaw },
|
|
16557
|
+
created_by: opts.createdBy
|
|
16558
|
+
},
|
|
16308
16559
|
verbose: globals.verbose
|
|
16309
16560
|
});
|
|
16310
16561
|
if (!result.ok) {
|
|
@@ -16381,6 +16632,15 @@ function registerStandardCommands(program2) {
|
|
|
16381
16632
|
var import_promises10 = require("node:fs/promises");
|
|
16382
16633
|
function registerConfigCommands(program2) {
|
|
16383
16634
|
const config = program2.command("config").description("Manage analysis configuration");
|
|
16635
|
+
config.command("service-url").description("Print the resolved service URL (used by the shipped skills)").action(async () => {
|
|
16636
|
+
const globals = program2.opts();
|
|
16637
|
+
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
16638
|
+
if (!urlResult.ok) {
|
|
16639
|
+
printError(urlResult.error);
|
|
16640
|
+
process.exit(1);
|
|
16641
|
+
}
|
|
16642
|
+
process.stdout.write(urlResult.data + "\n");
|
|
16643
|
+
});
|
|
16384
16644
|
config.command("push").description("Upload the analysis config to the service").option("--file <path>", "Path to config file", CODACY_CONFIG_FILE).action(async (opts) => {
|
|
16385
16645
|
const globals = program2.opts();
|
|
16386
16646
|
const tokenResult = await resolveToken(globals.token);
|
|
@@ -16522,99 +16782,402 @@ function formatRunDetail(run) {
|
|
|
16522
16782
|
return lines;
|
|
16523
16783
|
}
|
|
16524
16784
|
|
|
16525
|
-
// src/
|
|
16526
|
-
|
|
16527
|
-
|
|
16528
|
-
|
|
16529
|
-
|
|
16530
|
-
|
|
16531
|
-
|
|
16532
|
-
if (
|
|
16533
|
-
|
|
16534
|
-
return `${days}d ago`;
|
|
16785
|
+
// src/lib/ignore-declaration.ts
|
|
16786
|
+
var import_node_fs22 = require("node:fs");
|
|
16787
|
+
|
|
16788
|
+
// src/lib/debounce.ts
|
|
16789
|
+
var import_node_fs21 = require("node:fs");
|
|
16790
|
+
var import_node_crypto10 = require("node:crypto");
|
|
16791
|
+
function scopedFile(base, sessionId) {
|
|
16792
|
+
if (!sessionId) return base;
|
|
16793
|
+
return `${base}.${(0, import_node_crypto10.createHash)("sha1").update(sessionId).digest("hex").slice(0, 12)}`;
|
|
16535
16794
|
}
|
|
16536
|
-
function
|
|
16537
|
-
|
|
16538
|
-
|
|
16539
|
-
|
|
16540
|
-
|
|
16541
|
-
|
|
16542
|
-
|
|
16543
|
-
|
|
16544
|
-
|
|
16545
|
-
if (!urlResult.ok) {
|
|
16546
|
-
printError(urlResult.error);
|
|
16547
|
-
process.exit(1);
|
|
16548
|
-
}
|
|
16549
|
-
const token = tokenResult.data.token;
|
|
16550
|
-
const serviceUrl = urlResult.data;
|
|
16551
|
-
const who = await whoami(token, serviceUrl, globals.verbose);
|
|
16552
|
-
const memResult = await apiRequest({
|
|
16553
|
-
method: "GET",
|
|
16554
|
-
path: "/memory",
|
|
16555
|
-
serviceUrl,
|
|
16556
|
-
token,
|
|
16557
|
-
verbose: globals.verbose
|
|
16558
|
-
});
|
|
16559
|
-
const denial = memResult.ok ? null : authDenialRemedy(memResult.error);
|
|
16560
|
-
if (!memResult.ok && !denial) {
|
|
16561
|
-
printError(memResult.error);
|
|
16562
|
-
process.exit(1);
|
|
16795
|
+
function checkDebounce(debounceSeconds = DEBOUNCE_SECONDS, sessionId) {
|
|
16796
|
+
const file = scopedFile(DEBOUNCE_FILE, sessionId);
|
|
16797
|
+
if (!(0, import_node_fs21.existsSync)(file)) return null;
|
|
16798
|
+
try {
|
|
16799
|
+
const lastTs = parseInt((0, import_node_fs21.readFileSync)(file, "utf-8").trim(), 10);
|
|
16800
|
+
const nowTs = Math.floor(Date.now() / 1e3);
|
|
16801
|
+
const elapsed = nowTs - lastTs;
|
|
16802
|
+
if (elapsed < debounceSeconds) {
|
|
16803
|
+
return `Debounced \u2014 last analysis was ${elapsed}s ago`;
|
|
16563
16804
|
}
|
|
16564
|
-
|
|
16565
|
-
|
|
16566
|
-
|
|
16567
|
-
|
|
16568
|
-
|
|
16569
|
-
|
|
16570
|
-
|
|
16571
|
-
|
|
16572
|
-
|
|
16573
|
-
|
|
16574
|
-
|
|
16575
|
-
|
|
16576
|
-
|
|
16577
|
-
|
|
16578
|
-
|
|
16579
|
-
|
|
16580
|
-
|
|
16581
|
-
|
|
16582
|
-
|
|
16583
|
-
|
|
16805
|
+
} catch {
|
|
16806
|
+
}
|
|
16807
|
+
return null;
|
|
16808
|
+
}
|
|
16809
|
+
function checkMtime(files, bypassForRecentCommits, sessionId) {
|
|
16810
|
+
if (bypassForRecentCommits) return null;
|
|
16811
|
+
const file = scopedFile(DEBOUNCE_FILE, sessionId);
|
|
16812
|
+
if (!(0, import_node_fs21.existsSync)(file)) return null;
|
|
16813
|
+
let debounceTime;
|
|
16814
|
+
try {
|
|
16815
|
+
debounceTime = (0, import_node_fs21.statSync)(file).mtimeMs;
|
|
16816
|
+
} catch {
|
|
16817
|
+
return null;
|
|
16818
|
+
}
|
|
16819
|
+
for (const f of files) {
|
|
16820
|
+
const resolved = resolveFile(f);
|
|
16821
|
+
if (!resolved) continue;
|
|
16822
|
+
try {
|
|
16823
|
+
const stat3 = (0, import_node_fs21.statSync)(resolved);
|
|
16824
|
+
if (stat3.mtimeMs > debounceTime) {
|
|
16825
|
+
return null;
|
|
16584
16826
|
}
|
|
16585
|
-
|
|
16586
|
-
|
|
16587
|
-
}
|
|
16588
|
-
if (mem?.configured === false) {
|
|
16589
|
-
printInfo("Verity is not configured for this project. Run /verity-setup.");
|
|
16590
|
-
return;
|
|
16591
|
-
}
|
|
16592
|
-
printInfo("=== Verity Status ===");
|
|
16593
|
-
if (who.ok && who.data.logged_in) {
|
|
16594
|
-
printInfo(`Account: Logged in as ${who.data.email ?? `user #${who.data.user_id}`} \u2713`);
|
|
16595
|
-
} else if (who.ok && who.data.anonymous) {
|
|
16596
|
-
printInfo('Account: Anonymous \u2014 runs not saved, no cloud memory. Run "verity login".');
|
|
16597
|
-
} else if (tokenResult.data.userId != null) {
|
|
16598
|
-
printInfo(`Account: Logged in as ${tokenResult.data.email ?? `user #${tokenResult.data.userId}`} (cached \u2014 could not reach the Verity service) \u2713`);
|
|
16599
|
-
} else if (!who.ok) {
|
|
16600
|
-
printInfo(`Account: Unknown \u2014 could not reach the Verity service (${who.error})`);
|
|
16827
|
+
} catch {
|
|
16828
|
+
continue;
|
|
16601
16829
|
}
|
|
16602
|
-
|
|
16603
|
-
|
|
16604
|
-
|
|
16830
|
+
}
|
|
16831
|
+
return "No files modified since last analysis";
|
|
16832
|
+
}
|
|
16833
|
+
function computeContentHash(files) {
|
|
16834
|
+
const hash = (0, import_node_crypto10.createHash)("sha1");
|
|
16835
|
+
const sorted = [...files].sort();
|
|
16836
|
+
for (const f of sorted) {
|
|
16837
|
+
const resolved = resolveFile(f) ?? f;
|
|
16838
|
+
try {
|
|
16839
|
+
if ((0, import_node_fs21.existsSync)(resolved)) {
|
|
16840
|
+
hash.update((0, import_node_fs21.readFileSync)(resolved));
|
|
16841
|
+
}
|
|
16842
|
+
} catch {
|
|
16605
16843
|
}
|
|
16606
|
-
|
|
16607
|
-
|
|
16608
|
-
|
|
16844
|
+
}
|
|
16845
|
+
return hash.digest("hex");
|
|
16846
|
+
}
|
|
16847
|
+
function checkContentHash(files, sessionId) {
|
|
16848
|
+
const hash = computeContentHash(files);
|
|
16849
|
+
const file = scopedFile(HASH_FILE, sessionId);
|
|
16850
|
+
if ((0, import_node_fs21.existsSync)(file)) {
|
|
16851
|
+
try {
|
|
16852
|
+
const storedHash = (0, import_node_fs21.readFileSync)(file, "utf-8").trim();
|
|
16853
|
+
if (hash === storedHash) {
|
|
16854
|
+
return { skip: "No source changes since last analysis", hash };
|
|
16855
|
+
}
|
|
16856
|
+
} catch {
|
|
16609
16857
|
}
|
|
16610
|
-
|
|
16611
|
-
|
|
16612
|
-
|
|
16613
|
-
|
|
16614
|
-
|
|
16858
|
+
}
|
|
16859
|
+
return { skip: null, hash };
|
|
16860
|
+
}
|
|
16861
|
+
function recordAnalysisStart(sessionId) {
|
|
16862
|
+
(0, import_node_fs21.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
16863
|
+
(0, import_node_fs21.writeFileSync)(scopedFile(DEBOUNCE_FILE, sessionId), String(Math.floor(Date.now() / 1e3)));
|
|
16864
|
+
}
|
|
16865
|
+
function recordPassHash(hash, sessionId) {
|
|
16866
|
+
(0, import_node_fs21.writeFileSync)(scopedFile(HASH_FILE, sessionId), hash);
|
|
16867
|
+
}
|
|
16868
|
+
function narrowToRecent(files, sessionId) {
|
|
16869
|
+
const file = scopedFile(DEBOUNCE_FILE, sessionId);
|
|
16870
|
+
if (!(0, import_node_fs21.existsSync)(file)) return files;
|
|
16871
|
+
let debounceTime;
|
|
16872
|
+
try {
|
|
16873
|
+
debounceTime = (0, import_node_fs21.statSync)(file).mtimeMs;
|
|
16874
|
+
} catch {
|
|
16875
|
+
return files;
|
|
16876
|
+
}
|
|
16877
|
+
const recent = files.filter((f) => {
|
|
16878
|
+
try {
|
|
16879
|
+
return (0, import_node_fs21.existsSync)(f) && (0, import_node_fs21.statSync)(f).mtimeMs > debounceTime;
|
|
16880
|
+
} catch {
|
|
16881
|
+
return false;
|
|
16615
16882
|
}
|
|
16616
|
-
|
|
16617
|
-
|
|
16883
|
+
});
|
|
16884
|
+
return recent.length > 0 ? recent : files;
|
|
16885
|
+
}
|
|
16886
|
+
function readIteration(currentCommit, _contentHash) {
|
|
16887
|
+
return Math.max(1, readBlockState(currentCommit).attempts);
|
|
16888
|
+
}
|
|
16889
|
+
var NO_BLOCKS = { attempts: 0, blocks: 0, fingerprint: null };
|
|
16890
|
+
function readBlockState(currentCommit, opts) {
|
|
16891
|
+
if (opts?.newUserPrompt) return NO_BLOCKS;
|
|
16892
|
+
if (!(0, import_node_fs21.existsSync)(ITERATION_FILE)) return NO_BLOCKS;
|
|
16893
|
+
try {
|
|
16894
|
+
const stored = (0, import_node_fs21.readFileSync)(ITERATION_FILE, "utf-8").trim();
|
|
16895
|
+
const parsed = stored.startsWith("{") ? parseJsonState(stored) : parseLegacyState(stored);
|
|
16896
|
+
if (!parsed) return NO_BLOCKS;
|
|
16897
|
+
if (parsed.commit !== currentCommit) return NO_BLOCKS;
|
|
16898
|
+
if (parsed.ts > 0 && Math.floor(Date.now() / 1e3) - parsed.ts > 600) return NO_BLOCKS;
|
|
16899
|
+
return { attempts: parsed.attempts, blocks: parsed.blocks, fingerprint: parsed.fingerprint };
|
|
16900
|
+
} catch {
|
|
16901
|
+
return NO_BLOCKS;
|
|
16902
|
+
}
|
|
16903
|
+
}
|
|
16904
|
+
function parseJsonState(raw) {
|
|
16905
|
+
const o = JSON.parse(raw);
|
|
16906
|
+
const attempts = typeof o.attempts === "number" ? o.attempts : NaN;
|
|
16907
|
+
if (isNaN(attempts)) return null;
|
|
16908
|
+
return {
|
|
16909
|
+
attempts,
|
|
16910
|
+
blocks: typeof o.blocks === "number" ? o.blocks : attempts,
|
|
16911
|
+
fingerprint: typeof o.fingerprint === "string" && o.fingerprint ? o.fingerprint : null,
|
|
16912
|
+
commit: typeof o.commit === "string" ? o.commit : "",
|
|
16913
|
+
ts: typeof o.ts === "number" ? o.ts : 0
|
|
16914
|
+
};
|
|
16915
|
+
}
|
|
16916
|
+
function parseLegacyState(raw) {
|
|
16917
|
+
const parts = raw.split(":");
|
|
16918
|
+
const n = parseInt(parts[0], 10);
|
|
16919
|
+
if (isNaN(n)) return null;
|
|
16920
|
+
return {
|
|
16921
|
+
attempts: n,
|
|
16922
|
+
// The old file has no separate block count; the old counter is the closest
|
|
16923
|
+
// honest answer, and it errs toward releasing sooner rather than later.
|
|
16924
|
+
blocks: n,
|
|
16925
|
+
fingerprint: parts.slice(3).join(":") || null,
|
|
16926
|
+
commit: parts[1] ?? "",
|
|
16927
|
+
ts: parseInt(parts[2] ?? "0", 10)
|
|
16928
|
+
};
|
|
16929
|
+
}
|
|
16930
|
+
function findingsFingerprint(findings) {
|
|
16931
|
+
const keys = findings.map((f) => `${String(f.pattern_id ?? "?")}|${String(f.file ?? "?")}`).filter((k) => k !== "?|?");
|
|
16932
|
+
return [...new Set(keys)].sort().join(",");
|
|
16933
|
+
}
|
|
16934
|
+
function isSameProblem(previous, current) {
|
|
16935
|
+
if (!previous || !current) return false;
|
|
16936
|
+
const prev = new Set(previous.split(","));
|
|
16937
|
+
return current.split(",").some((k) => prev.has(k));
|
|
16938
|
+
}
|
|
16939
|
+
function writeBlockState(commit, state) {
|
|
16940
|
+
(0, import_node_fs21.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
16941
|
+
(0, import_node_fs21.writeFileSync)(
|
|
16942
|
+
ITERATION_FILE,
|
|
16943
|
+
JSON.stringify({
|
|
16944
|
+
v: 2,
|
|
16945
|
+
attempts: state.attempts,
|
|
16946
|
+
blocks: state.blocks,
|
|
16947
|
+
commit,
|
|
16948
|
+
ts: Math.floor(Date.now() / 1e3),
|
|
16949
|
+
fingerprint: state.fingerprint ?? void 0
|
|
16950
|
+
})
|
|
16951
|
+
);
|
|
16952
|
+
}
|
|
16953
|
+
function resetBlockState(commit) {
|
|
16954
|
+
writeBlockState(commit, { attempts: 0, blocks: 0, fingerprint: null });
|
|
16955
|
+
}
|
|
16956
|
+
|
|
16957
|
+
// src/lib/ignore-declaration.ts
|
|
16958
|
+
var IGNORE_BUDGET = 3;
|
|
16959
|
+
var MAX_WINDOW_SECONDS = 60 * 60;
|
|
16960
|
+
var TURN_FUSE_SECONDS = 10 * 60;
|
|
16961
|
+
function parseDuration(input) {
|
|
16962
|
+
const trimmed = input.trim().toLowerCase();
|
|
16963
|
+
const m = /^(\d+)(s|m|h)?$/.exec(trimmed);
|
|
16964
|
+
if (!m) {
|
|
16965
|
+
return { ok: false, error: `Could not read "${input}" as a duration. Use 30m, 45s, or 1h.` };
|
|
16966
|
+
}
|
|
16967
|
+
const n = parseInt(m[1], 10);
|
|
16968
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
16969
|
+
return { ok: false, error: `A duration must be a positive number of seconds, minutes or hours \u2014 got "${input}".` };
|
|
16970
|
+
}
|
|
16971
|
+
const unit = m[2] ?? "m";
|
|
16972
|
+
const seconds = unit === "s" ? n : unit === "h" ? n * 3600 : n * 60;
|
|
16973
|
+
if (seconds > MAX_WINDOW_SECONDS) {
|
|
16974
|
+
return {
|
|
16975
|
+
ok: false,
|
|
16976
|
+
error: `${input} is longer than the ${MAX_WINDOW_SECONDS / 60}-minute maximum. An ignore is meant to cover one piece of housekeeping, not a sitting \u2014 declare it again when you need it.`
|
|
16977
|
+
};
|
|
16978
|
+
}
|
|
16979
|
+
return { ok: true, seconds };
|
|
16980
|
+
}
|
|
16981
|
+
function resolveActive(state, now) {
|
|
16982
|
+
if (!state?.active) return null;
|
|
16983
|
+
if (state.active.expires <= now) return null;
|
|
16984
|
+
return state.active;
|
|
16985
|
+
}
|
|
16986
|
+
function verifyDeclaration(input) {
|
|
16987
|
+
const d = input.declaration;
|
|
16988
|
+
if (!d) return { honoured: false, void: false };
|
|
16989
|
+
if (!input.authorshipIsObservable) {
|
|
16990
|
+
return {
|
|
16991
|
+
honoured: false,
|
|
16992
|
+
void: true,
|
|
16993
|
+
why: "the transcript window could not show what this turn did, so the declaration could not be checked"
|
|
16994
|
+
};
|
|
16995
|
+
}
|
|
16996
|
+
if (input.commandRecordTruncated) {
|
|
16997
|
+
return {
|
|
16998
|
+
honoured: false,
|
|
16999
|
+
void: true,
|
|
17000
|
+
why: "the record of commands run this turn was incomplete, so the declaration could not be checked"
|
|
17001
|
+
};
|
|
17002
|
+
}
|
|
17003
|
+
if (input.agentAuthoredFiles > 0) {
|
|
17004
|
+
return {
|
|
17005
|
+
honoured: false,
|
|
17006
|
+
void: true,
|
|
17007
|
+
why: `${input.agentAuthoredFiles} file${input.agentAuthoredFiles === 1 ? " was" : "s were"} authored during it`
|
|
17008
|
+
};
|
|
17009
|
+
}
|
|
17010
|
+
if (input.subagents > 0) {
|
|
17011
|
+
return {
|
|
17012
|
+
honoured: false,
|
|
17013
|
+
void: true,
|
|
17014
|
+
why: "work was delegated to a subagent during it, whose authorship this turn cannot account for"
|
|
17015
|
+
};
|
|
17016
|
+
}
|
|
17017
|
+
const authoring = [...input.agentCommands ?? [], ...input.userCommands ?? []].filter(
|
|
17018
|
+
(c) => !isNonAuthoringCommand(c)
|
|
17019
|
+
);
|
|
17020
|
+
if (authoring.length > 0) {
|
|
17021
|
+
return {
|
|
17022
|
+
honoured: false,
|
|
17023
|
+
void: true,
|
|
17024
|
+
why: `a command that can write files ran during it (${authoring[0]})`
|
|
17025
|
+
};
|
|
17026
|
+
}
|
|
17027
|
+
return { honoured: true };
|
|
17028
|
+
}
|
|
17029
|
+
function stateFile(sessionId) {
|
|
17030
|
+
return projectPath(scopedFile(IGNORE_DECLARATION_FILE, sessionId));
|
|
17031
|
+
}
|
|
17032
|
+
function ignoreStateKeys(token, sessionId) {
|
|
17033
|
+
const scoped = sessionScopeKey(token, sessionId);
|
|
17034
|
+
const userOnly = sessionScopeKey(token, void 0);
|
|
17035
|
+
return scoped === userOnly ? [scoped] : [scoped, userOnly];
|
|
17036
|
+
}
|
|
17037
|
+
function resolveIgnoreState(keys) {
|
|
17038
|
+
for (const key of keys) {
|
|
17039
|
+
const state = readIgnoreState(key);
|
|
17040
|
+
if (state) return { state, key };
|
|
17041
|
+
}
|
|
17042
|
+
return null;
|
|
17043
|
+
}
|
|
17044
|
+
function readIgnoreState(sessionId) {
|
|
17045
|
+
const file = stateFile(sessionId);
|
|
17046
|
+
if (!(0, import_node_fs22.existsSync)(file)) return null;
|
|
17047
|
+
try {
|
|
17048
|
+
const o = JSON.parse((0, import_node_fs22.readFileSync)(file, "utf-8")) ?? {};
|
|
17049
|
+
const spent = typeof o.spent === "number" ? o.spent : 0;
|
|
17050
|
+
const raw = o.active;
|
|
17051
|
+
let active = null;
|
|
17052
|
+
if (raw && typeof raw === "object") {
|
|
17053
|
+
const scope2 = raw.scope === "window" ? "window" : raw.scope === "turn" ? "turn" : null;
|
|
17054
|
+
const expires = typeof raw.expires === "number" ? raw.expires : null;
|
|
17055
|
+
if (scope2 && expires != null) {
|
|
17056
|
+
active = {
|
|
17057
|
+
scope: scope2,
|
|
17058
|
+
origin: raw.origin === "agent" ? "agent" : "user",
|
|
17059
|
+
reason: typeof raw.reason === "string" ? raw.reason : "",
|
|
17060
|
+
at: typeof raw.at === "number" ? raw.at : 0,
|
|
17061
|
+
expires
|
|
17062
|
+
};
|
|
17063
|
+
}
|
|
17064
|
+
}
|
|
17065
|
+
return { v: 1, active, spent };
|
|
17066
|
+
} catch {
|
|
17067
|
+
return null;
|
|
17068
|
+
}
|
|
17069
|
+
}
|
|
17070
|
+
function writeIgnoreState(state, sessionId) {
|
|
17071
|
+
try {
|
|
17072
|
+
(0, import_node_fs22.mkdirSync)(projectPath(VERITY_DIR), { recursive: true });
|
|
17073
|
+
(0, import_node_fs22.writeFileSync)(stateFile(sessionId), JSON.stringify({ v: 1, active: state.active, spent: state.spent }));
|
|
17074
|
+
} catch {
|
|
17075
|
+
}
|
|
17076
|
+
}
|
|
17077
|
+
function clearActiveDeclaration(sessionId) {
|
|
17078
|
+
const prev = readIgnoreState(sessionId);
|
|
17079
|
+
if (!prev) return;
|
|
17080
|
+
writeIgnoreState({ v: 1, active: null, spent: prev.spent }, sessionId);
|
|
17081
|
+
}
|
|
17082
|
+
function describeRemaining(d, now) {
|
|
17083
|
+
const left = Math.max(0, d.expires - now);
|
|
17084
|
+
if (left >= 90) return `${Math.round(left / 60)}m left`;
|
|
17085
|
+
return `${left}s left`;
|
|
17086
|
+
}
|
|
17087
|
+
|
|
17088
|
+
// src/commands/status.ts
|
|
17089
|
+
function timeAgo(isoDate) {
|
|
17090
|
+
const ms = Date.now() - new Date(isoDate).getTime();
|
|
17091
|
+
const mins = Math.floor(ms / 6e4);
|
|
17092
|
+
if (mins < 1) return "just now";
|
|
17093
|
+
if (mins < 60) return `${mins}m ago`;
|
|
17094
|
+
const hrs = Math.floor(mins / 60);
|
|
17095
|
+
if (hrs < 24) return `${hrs}h ago`;
|
|
17096
|
+
const days = Math.floor(hrs / 24);
|
|
17097
|
+
return `${days}d ago`;
|
|
17098
|
+
}
|
|
17099
|
+
function registerStatusCommand(program2) {
|
|
17100
|
+
program2.command("status").description("Show project quality status").option("--history", "Include recent run history").option("--limit <n>", "Number of history entries", "5").option("--json", "Output raw JSON").action(async (opts) => {
|
|
17101
|
+
const globals = program2.opts();
|
|
17102
|
+
const tokenResult = await resolveToken(globals.token);
|
|
17103
|
+
if (!tokenResult.ok) {
|
|
17104
|
+
printError(tokenResult.error);
|
|
17105
|
+
process.exit(1);
|
|
17106
|
+
}
|
|
17107
|
+
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
17108
|
+
if (!urlResult.ok) {
|
|
17109
|
+
printError(urlResult.error);
|
|
17110
|
+
process.exit(1);
|
|
17111
|
+
}
|
|
17112
|
+
const token = tokenResult.data.token;
|
|
17113
|
+
const serviceUrl = urlResult.data;
|
|
17114
|
+
const who = await whoami(token, serviceUrl, globals.verbose);
|
|
17115
|
+
const memResult = await apiRequest({
|
|
17116
|
+
method: "GET",
|
|
17117
|
+
path: "/memory",
|
|
17118
|
+
serviceUrl,
|
|
17119
|
+
token,
|
|
17120
|
+
verbose: globals.verbose
|
|
17121
|
+
});
|
|
17122
|
+
const denial = memResult.ok ? null : authDenialRemedy(memResult.error);
|
|
17123
|
+
if (!memResult.ok && !denial) {
|
|
17124
|
+
printError(memResult.error);
|
|
17125
|
+
process.exit(1);
|
|
17126
|
+
}
|
|
17127
|
+
const mem = memResult.ok ? memResult.data : null;
|
|
17128
|
+
if (opts.json) {
|
|
17129
|
+
const output = {
|
|
17130
|
+
auth: who.ok ? who.data : { error: who.error },
|
|
17131
|
+
memory: mem
|
|
17132
|
+
};
|
|
17133
|
+
if (denial && !memResult.ok) {
|
|
17134
|
+
output.error = { code: denial.code, message: memResult.error, remedy: denial.remedy };
|
|
17135
|
+
}
|
|
17136
|
+
if (opts.history && !denial) {
|
|
17137
|
+
const runsResult = await apiRequest({
|
|
17138
|
+
method: "GET",
|
|
17139
|
+
path: `/runs?limit=${opts.limit}`,
|
|
17140
|
+
serviceUrl,
|
|
17141
|
+
token,
|
|
17142
|
+
verbose: globals.verbose
|
|
17143
|
+
});
|
|
17144
|
+
if (runsResult.ok) {
|
|
17145
|
+
output.runs = runsResult.data.runs;
|
|
17146
|
+
}
|
|
17147
|
+
}
|
|
17148
|
+
printJson(output);
|
|
17149
|
+
return;
|
|
17150
|
+
}
|
|
17151
|
+
if (mem?.configured === false) {
|
|
17152
|
+
printInfo("Verity is not configured for this project. Run /verity-setup.");
|
|
17153
|
+
return;
|
|
17154
|
+
}
|
|
17155
|
+
printInfo("=== Verity Status ===");
|
|
17156
|
+
if (who.ok && who.data.logged_in) {
|
|
17157
|
+
printInfo(`Account: Logged in as ${who.data.email ?? `user #${who.data.user_id}`} \u2713`);
|
|
17158
|
+
} else if (who.ok && who.data.anonymous) {
|
|
17159
|
+
printInfo('Account: Anonymous \u2014 runs not saved, no cloud memory. Run "verity login".');
|
|
17160
|
+
} else if (tokenResult.data.userId != null) {
|
|
17161
|
+
printInfo(`Account: Logged in as ${tokenResult.data.email ?? `user #${tokenResult.data.userId}`} (cached \u2014 could not reach the Verity service) \u2713`);
|
|
17162
|
+
} else if (!who.ok) {
|
|
17163
|
+
printInfo(`Account: Unknown \u2014 could not reach the Verity service (${who.error})`);
|
|
17164
|
+
}
|
|
17165
|
+
if (who.ok) {
|
|
17166
|
+
const nudge = reverifyNudge(who.data);
|
|
17167
|
+
if (nudge) printWarn(` ${nudge}`);
|
|
17168
|
+
}
|
|
17169
|
+
if (denial) {
|
|
17170
|
+
printWarn(`Access: ${denial.remedy}`);
|
|
17171
|
+
printInfo(" Project status, history, and cloud memory stay unavailable until then.");
|
|
17172
|
+
}
|
|
17173
|
+
if (mem?.project_name) printInfo(`Project: ${mem.project_name}`);
|
|
17174
|
+
if (mem?.standard) {
|
|
17175
|
+
const s = mem.standard;
|
|
17176
|
+
printInfo(`Standard: v${s.version} (${s.quality_dimensions} quality, ${s.security_patterns} security, ${s.custom_patterns} custom)`);
|
|
17177
|
+
printInfo(`Languages: ${s.languages.join(", ")}`);
|
|
17178
|
+
}
|
|
17179
|
+
const hookStatus = await checkAllVerityHooks();
|
|
17180
|
+
const moments = [];
|
|
16618
17181
|
if (hookStatus.stop) moments.push("stop");
|
|
16619
17182
|
if (hookStatus.guardOn.includes("commit")) moments.push("pre-commit");
|
|
16620
17183
|
if (hookStatus.guardOn.includes("push")) moments.push("pre-push/PR");
|
|
@@ -16630,11 +17193,45 @@ function registerStatusCommand(program2) {
|
|
|
16630
17193
|
printInfo(`Trend: ${r.trend}`);
|
|
16631
17194
|
printInfo(`Runs: ${r.count} recorded`);
|
|
16632
17195
|
}
|
|
17196
|
+
{
|
|
17197
|
+
const tokenForScope = tokenResult.data.token;
|
|
17198
|
+
const found = resolveIgnoreState(
|
|
17199
|
+
ignoreStateKeys(tokenForScope, process.env.CLAUDE_SESSION_ID || void 0)
|
|
17200
|
+
);
|
|
17201
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
17202
|
+
const active = resolveActive(found?.state ?? null, now);
|
|
17203
|
+
const spent = found?.state.spent ?? 0;
|
|
17204
|
+
if (active) {
|
|
17205
|
+
printInfo("");
|
|
17206
|
+
printInfo("--- Ignore Declared ---");
|
|
17207
|
+
printInfo(`Scope: ${active.scope === "turn" ? "the next turn" : `a window, ${describeRemaining(active, now)}`}`);
|
|
17208
|
+
printInfo(`Reason: ${active.reason}`);
|
|
17209
|
+
printInfo(`Declared by: ${active.origin === "agent" ? "the agent" : "you"}`);
|
|
17210
|
+
printInfo(`Budget: ${spent}/${IGNORE_BUDGET} declarations this session`);
|
|
17211
|
+
printInfo('Turns that author anything are still reviewed \u2014 the declaration voids. "verity ignore clear" cancels it.');
|
|
17212
|
+
} else if (spent > 0) {
|
|
17213
|
+
printInfo("");
|
|
17214
|
+
printInfo(`Ignore budget: ${spent}/${IGNORE_BUDGET} declarations used this session.`);
|
|
17215
|
+
}
|
|
17216
|
+
}
|
|
17217
|
+
{
|
|
17218
|
+
const ig = loadVerityIgnore();
|
|
17219
|
+
if (ig.rules.length > 0 || ig.problems.length > 0) {
|
|
17220
|
+
printInfo("");
|
|
17221
|
+
printInfo("--- .verityignore ---");
|
|
17222
|
+
printInfo(`Rules: ${ig.rules.length}`);
|
|
17223
|
+
for (const problem of ig.problems) printWarn(` ${problem}`);
|
|
17224
|
+
const overlap = describeSecurityOverlap(ig);
|
|
17225
|
+
if (overlap) printWarn(overlap);
|
|
17226
|
+
printInfo("An edit to .verityignore suspends every rule for that turn \u2014 the edit is always reviewed.");
|
|
17227
|
+
}
|
|
17228
|
+
}
|
|
16633
17229
|
if (mem.pending_items && mem.pending_items.length > 0) {
|
|
16634
17230
|
printInfo("");
|
|
16635
17231
|
printInfo("--- Pending Items ---");
|
|
16636
17232
|
for (const item of mem.pending_items) {
|
|
16637
|
-
|
|
17233
|
+
const priority = typeof item.priority === "string" && item.priority.length > 0 ? item.priority.toUpperCase() : "UNSPECIFIED";
|
|
17234
|
+
printInfo(` [${priority}] ${item.description ?? "(no description)"}`);
|
|
16638
17235
|
}
|
|
16639
17236
|
}
|
|
16640
17237
|
const recentTasks = mem.recent_tasks;
|
|
@@ -16793,6 +17390,7 @@ function createRun(opts, globals) {
|
|
|
16793
17390
|
allChanged: [],
|
|
16794
17391
|
hasRecentCommitFiles: false,
|
|
16795
17392
|
changedUniverse: [],
|
|
17393
|
+
verityIgnored: { rules: 0, kept: [], ignored: [], securityExcluded: [], suspended: false },
|
|
16796
17394
|
analyzable: [],
|
|
16797
17395
|
reviewable: [],
|
|
16798
17396
|
securityFiles: [],
|
|
@@ -16826,6 +17424,7 @@ function createRun(opts, globals) {
|
|
|
16826
17424
|
deletedNodePaths: [],
|
|
16827
17425
|
editedUploads: [],
|
|
16828
17426
|
autoSeedNotice: null,
|
|
17427
|
+
voidedIgnoreNotice: null,
|
|
16829
17428
|
foldResult: null,
|
|
16830
17429
|
foldConservation: null,
|
|
16831
17430
|
memorySession: null,
|
|
@@ -16846,7 +17445,7 @@ function createRun(opts, globals) {
|
|
|
16846
17445
|
}
|
|
16847
17446
|
|
|
16848
17447
|
// src/lib/stderr-log.ts
|
|
16849
|
-
var
|
|
17448
|
+
var import_node_fs23 = require("node:fs");
|
|
16850
17449
|
var TOKEN_RE2 = /verity_[0-9a-f]{16,}/g;
|
|
16851
17450
|
var ANSI_RE = /\u001b\[[0-?]*[ -/]*[@-~]/g;
|
|
16852
17451
|
function scrub(s) {
|
|
@@ -16859,9 +17458,9 @@ function append(text) {
|
|
|
16859
17458
|
try {
|
|
16860
17459
|
const dir = projectPath(DEBUG_LOG_DIR);
|
|
16861
17460
|
const file = projectPath(STDERR_LOG_FILE);
|
|
16862
|
-
(0,
|
|
17461
|
+
(0, import_node_fs23.mkdirSync)(dir, { recursive: true });
|
|
16863
17462
|
rotateIfNeeded(file);
|
|
16864
|
-
(0,
|
|
17463
|
+
(0, import_node_fs23.appendFileSync)(file, text);
|
|
16865
17464
|
} catch {
|
|
16866
17465
|
}
|
|
16867
17466
|
}
|
|
@@ -17019,7 +17618,7 @@ function installRunEvidence(run) {
|
|
|
17019
17618
|
|
|
17020
17619
|
// src/lib/git-frame.ts
|
|
17021
17620
|
var import_node_child_process7 = require("node:child_process");
|
|
17022
|
-
var
|
|
17621
|
+
var import_node_fs24 = require("node:fs");
|
|
17023
17622
|
var import_node_os3 = require("node:os");
|
|
17024
17623
|
var import_node_path18 = require("node:path");
|
|
17025
17624
|
var import_node_path19 = require("node:path");
|
|
@@ -17158,14 +17757,14 @@ function gitAt(dir, args) {
|
|
|
17158
17757
|
}
|
|
17159
17758
|
function realpathOr(p) {
|
|
17160
17759
|
try {
|
|
17161
|
-
return
|
|
17760
|
+
return import_node_fs24.realpathSync.native(p);
|
|
17162
17761
|
} catch {
|
|
17163
17762
|
return (0, import_node_path18.resolve)(p);
|
|
17164
17763
|
}
|
|
17165
17764
|
}
|
|
17166
17765
|
function resolveFrame(input) {
|
|
17167
17766
|
const found = findMomentSegment(input.command, input.on);
|
|
17168
|
-
const hookDirUsable = !!input.hookCwd && (0,
|
|
17767
|
+
const hookDirUsable = !!input.hookCwd && (0, import_node_fs24.existsSync)(input.hookCwd);
|
|
17169
17768
|
const baseDir = hookDirUsable ? input.hookCwd : process.cwd();
|
|
17170
17769
|
let anchor = hookDirUsable ? "hook-cwd" : "process-cwd";
|
|
17171
17770
|
const refuse = (refusal) => ({
|
|
@@ -17188,7 +17787,7 @@ function resolveFrame(input) {
|
|
|
17188
17787
|
if (dirs.size > 1) return refuse(`target:multiple ${found.moment} targets in one command`);
|
|
17189
17788
|
const targetDir = dirs.size === 1 ? [...dirs][0] : baseDir;
|
|
17190
17789
|
if (targetDir !== baseDir) {
|
|
17191
|
-
if (!(0,
|
|
17790
|
+
if (!(0, import_node_fs24.existsSync)(targetDir)) return refuse(`target:directory does not exist: ${targetDir}`);
|
|
17192
17791
|
dir = targetDir;
|
|
17193
17792
|
}
|
|
17194
17793
|
}
|
|
@@ -17227,7 +17826,7 @@ var SHA_RE2 = /^[0-9a-f]{40}$/;
|
|
|
17227
17826
|
function baselineShaAt(frame) {
|
|
17228
17827
|
if (!frame.worktreeRoot) return null;
|
|
17229
17828
|
try {
|
|
17230
|
-
const sha = (0,
|
|
17829
|
+
const sha = (0, import_node_fs24.readFileSync)((0, import_node_path19.join)(frame.worktreeRoot, BASELINE_SHA_FILE), "utf-8").trim();
|
|
17231
17830
|
if (!SHA_RE2.test(sha)) return null;
|
|
17232
17831
|
return refResolves(frame, sha) ? sha : null;
|
|
17233
17832
|
} catch {
|
|
@@ -17340,7 +17939,7 @@ function truthy(v) {
|
|
|
17340
17939
|
}
|
|
17341
17940
|
|
|
17342
17941
|
// src/lib/transcript.ts
|
|
17343
|
-
var
|
|
17942
|
+
var import_node_fs25 = require("node:fs");
|
|
17344
17943
|
var MAX_READ_BYTES = 256 * 1024;
|
|
17345
17944
|
var SMALL_FILE_BYTES = 64 * 1024;
|
|
17346
17945
|
var MAX_FILES_LIST = 20;
|
|
@@ -17366,7 +17965,7 @@ async function extractActionSummary(transcriptPath) {
|
|
|
17366
17965
|
function readTurnLines(transcriptPath) {
|
|
17367
17966
|
let size;
|
|
17368
17967
|
try {
|
|
17369
|
-
size = (0,
|
|
17968
|
+
size = (0, import_node_fs25.statSync)(transcriptPath).size;
|
|
17370
17969
|
} catch {
|
|
17371
17970
|
return null;
|
|
17372
17971
|
}
|
|
@@ -17374,7 +17973,7 @@ function readTurnLines(transcriptPath) {
|
|
|
17374
17973
|
let raw;
|
|
17375
17974
|
let windowed = false;
|
|
17376
17975
|
if (size <= SMALL_FILE_BYTES) {
|
|
17377
|
-
raw = (0,
|
|
17976
|
+
raw = (0, import_node_fs25.readFileSync)(transcriptPath, "utf-8");
|
|
17378
17977
|
} else {
|
|
17379
17978
|
windowed = true;
|
|
17380
17979
|
const buf = Buffer.alloc(Math.min(MAX_READ_BYTES, size));
|
|
@@ -17432,6 +18031,7 @@ function buildSummary(lines) {
|
|
|
17432
18031
|
const commands = [];
|
|
17433
18032
|
const userCommands = [];
|
|
17434
18033
|
let userCommandsTruncated = false;
|
|
18034
|
+
let commandsTruncated = false;
|
|
17435
18035
|
let searches = 0;
|
|
17436
18036
|
let subagents = 0;
|
|
17437
18037
|
let webFetches = 0;
|
|
@@ -17472,7 +18072,10 @@ function buildSummary(lines) {
|
|
|
17472
18072
|
totalToolCalls++;
|
|
17473
18073
|
const toolName = block.name ?? "unknown";
|
|
17474
18074
|
toolCounts[toolName] = (toolCounts[toolName] ?? 0) + 1;
|
|
17475
|
-
if (totalToolCalls > MAX_TOOL_BLOCKS)
|
|
18075
|
+
if (totalToolCalls > MAX_TOOL_BLOCKS) {
|
|
18076
|
+
if (toolName === "Bash") commandsTruncated = true;
|
|
18077
|
+
continue;
|
|
18078
|
+
}
|
|
17476
18079
|
const input = block.input ?? {};
|
|
17477
18080
|
switch (toolName) {
|
|
17478
18081
|
case "Read":
|
|
@@ -17492,9 +18095,11 @@ function buildSummary(lines) {
|
|
|
17492
18095
|
addPath(filesEdited, input.file_path);
|
|
17493
18096
|
break;
|
|
17494
18097
|
case "Bash": {
|
|
17495
|
-
const cmd =
|
|
17496
|
-
if (
|
|
17497
|
-
|
|
18098
|
+
const { cmd, lost } = sanitizeCommandWithLoss(input.command);
|
|
18099
|
+
if (lost) commandsTruncated = true;
|
|
18100
|
+
if (cmd) {
|
|
18101
|
+
if (commands.length < MAX_COMMANDS) commands.push(cmd);
|
|
18102
|
+
else commandsTruncated = true;
|
|
17498
18103
|
}
|
|
17499
18104
|
break;
|
|
17500
18105
|
}
|
|
@@ -17537,6 +18142,7 @@ function buildSummary(lines) {
|
|
|
17537
18142
|
],
|
|
17538
18143
|
searches,
|
|
17539
18144
|
commands,
|
|
18145
|
+
...commandsTruncated ? { commands_truncated: true } : {},
|
|
17540
18146
|
user_commands: userCommands,
|
|
17541
18147
|
...userCommandsTruncated ? { user_commands_truncated: true } : {},
|
|
17542
18148
|
subagents,
|
|
@@ -17547,6 +18153,7 @@ function buildSummary(lines) {
|
|
|
17547
18153
|
};
|
|
17548
18154
|
if (JSON.stringify(summary).length > MAX_SUMMARY_BYTES) {
|
|
17549
18155
|
summary.commands = [];
|
|
18156
|
+
summary.commands_truncated = true;
|
|
17550
18157
|
summary.user_commands = [];
|
|
17551
18158
|
summary.user_commands_truncated = true;
|
|
17552
18159
|
if (JSON.stringify(summary).length > MAX_SUMMARY_BYTES) {
|
|
@@ -17582,12 +18189,22 @@ function userTypedCommands(entry) {
|
|
|
17582
18189
|
}
|
|
17583
18190
|
return { commands, truncated };
|
|
17584
18191
|
}
|
|
18192
|
+
function sanitizeCommandWithLoss(rawCmd) {
|
|
18193
|
+
if (typeof rawCmd !== "string" || !rawCmd) return { cmd: null, lost: false };
|
|
18194
|
+
const lines = rawCmd.split("\n");
|
|
18195
|
+
const lost = lines.slice(1).some((l) => l.trim().length > 0);
|
|
18196
|
+
const first = lines[0];
|
|
18197
|
+
const cmd = sanitizeCommand(first);
|
|
18198
|
+
const hadSeparator = SEPARATORS.some((sep2) => first.indexOf(sep2) > 0);
|
|
18199
|
+
return { cmd, lost: lost || !hadSeparator && first.length > MAX_COMMAND_CHARS };
|
|
18200
|
+
}
|
|
18201
|
+
var SEPARATORS = [" | ", " > ", " >> ", " 2>", " && ", " ; "];
|
|
17585
18202
|
function sanitizeCommand(rawCmd) {
|
|
17586
18203
|
if (typeof rawCmd !== "string" || !rawCmd) return null;
|
|
17587
18204
|
let cmd = rawCmd.split("\n")[0];
|
|
17588
18205
|
let cut = -1;
|
|
17589
18206
|
let marker = "";
|
|
17590
|
-
for (const sep2 of
|
|
18207
|
+
for (const sep2 of SEPARATORS) {
|
|
17591
18208
|
const idx = cmd.indexOf(sep2);
|
|
17592
18209
|
if (idx > 0 && (cut === -1 || idx < cut)) {
|
|
17593
18210
|
cut = idx;
|
|
@@ -17837,7 +18454,7 @@ function channelSilence(input) {
|
|
|
17837
18454
|
// src/lib/cli-version.ts
|
|
17838
18455
|
function cliVersion() {
|
|
17839
18456
|
try {
|
|
17840
|
-
return true ? "0.
|
|
18457
|
+
return true ? "0.31.0-experimental.2221878" : "dev";
|
|
17841
18458
|
} catch {
|
|
17842
18459
|
return "dev";
|
|
17843
18460
|
}
|
|
@@ -17878,7 +18495,7 @@ async function sendSkipBeacon(ctx, reason) {
|
|
|
17878
18495
|
|
|
17879
18496
|
// src/lib/static-analysis.ts
|
|
17880
18497
|
var import_node_child_process8 = require("node:child_process");
|
|
17881
|
-
var
|
|
18498
|
+
var import_node_fs26 = require("node:fs");
|
|
17882
18499
|
var SEVERITY_ORDER = {
|
|
17883
18500
|
Error: 0,
|
|
17884
18501
|
Critical: 0,
|
|
@@ -17926,7 +18543,7 @@ function runCodacyAnalysis(files) {
|
|
|
17926
18543
|
if (files.length === 0) return empty;
|
|
17927
18544
|
const existingFiles = files.filter((f) => {
|
|
17928
18545
|
try {
|
|
17929
|
-
return (0,
|
|
18546
|
+
return (0, import_node_fs26.existsSync)(f);
|
|
17930
18547
|
} catch {
|
|
17931
18548
|
return false;
|
|
17932
18549
|
}
|
|
@@ -18087,9 +18704,10 @@ function describeOpenElsewhere(open) {
|
|
|
18087
18704
|
const lines = open.slice(0, 5).map((o) => ` ${o.file}:${o.line} [${o.pattern_id}]`);
|
|
18088
18705
|
const more = open.length > 5 ? `
|
|
18089
18706
|
(+${open.length - 5} more)` : "";
|
|
18090
|
-
return `STILL OPEN ELSEWHERE. ${open.length}
|
|
18707
|
+
return `STILL OPEN ELSEWHERE. ${open.length} finding(s) Verity raised earlier are still on disk in files this run did not review:
|
|
18091
18708
|
${lines.join("\n")}${more}
|
|
18092
|
-
|
|
18709
|
+
They did not gate this run \u2014 this verdict covers the current change only. The tree is not clean.
|
|
18710
|
+
Fix them, or record a disposition: verity waive <pattern-id> --file <path> --reason "\u2026"`;
|
|
18093
18711
|
}
|
|
18094
18712
|
|
|
18095
18713
|
// src/commands/analyze/exit.ts
|
|
@@ -18124,6 +18742,7 @@ async function passAndExit(run, reason, skip, kindOverride) {
|
|
|
18124
18742
|
"bare-acknowledgment",
|
|
18125
18743
|
"reflection-prompt",
|
|
18126
18744
|
"command-only-turn",
|
|
18745
|
+
"declared-ignore",
|
|
18127
18746
|
"skip-mode",
|
|
18128
18747
|
"zero-increment",
|
|
18129
18748
|
"debounce",
|
|
@@ -18163,19 +18782,37 @@ async function scope(run) {
|
|
|
18163
18782
|
const { files: allChanged, hasRecentCommitFiles } = getChangedFiles();
|
|
18164
18783
|
run.changedUniverse = allChanged;
|
|
18165
18784
|
const { kept: external } = partitionVerityOwned(allChanged);
|
|
18166
|
-
const
|
|
18167
|
-
const
|
|
18168
|
-
const
|
|
18785
|
+
const verityIgnore = loadVerityIgnore();
|
|
18786
|
+
const ignored = partitionIgnored(external, verityIgnore);
|
|
18787
|
+
for (const problem of verityIgnore.problems) {
|
|
18788
|
+
process.stderr.write(`Verity: .verityignore ${problem}
|
|
18789
|
+
`);
|
|
18790
|
+
}
|
|
18791
|
+
if (ignored.securityExcluded.length > 0) {
|
|
18792
|
+
process.stderr.write(
|
|
18793
|
+
`Verity: .verityignore excluded ${ignored.securityExcluded.length} security-sensitive file(s) from review this run: ${ignored.securityExcluded.slice(0, 5).join(", ")}. Add a \`!\` rule to keep them in scope.
|
|
18794
|
+
`
|
|
18795
|
+
);
|
|
18796
|
+
}
|
|
18797
|
+
if (ignored.suspended) {
|
|
18798
|
+
process.stderr.write(
|
|
18799
|
+
"Verity: .verityignore changed this turn \u2014 its rules are suspended for this run, so nothing is excluded by them. They take effect once this turn has been reviewed.\n"
|
|
18800
|
+
);
|
|
18801
|
+
}
|
|
18802
|
+
const inScope = ignored.kept;
|
|
18803
|
+
const analyzable = filterAnalyzable(inScope);
|
|
18804
|
+
const reviewable = filterReviewable(inScope);
|
|
18805
|
+
const securityFiles = filterSecurity(inScope);
|
|
18169
18806
|
const noFilesChanged = analyzable.length === 0 && reviewable.length === 0 && securityFiles.length === 0;
|
|
18170
18807
|
if (noFilesChanged && !assistantResponse) {
|
|
18171
18808
|
await passAndExit(run, "No analyzable files changed", "no-analyzable-files");
|
|
18172
18809
|
}
|
|
18173
18810
|
const allForReview = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable]));
|
|
18174
|
-
Object.assign(run, { allChanged, allForReview, analyzable, hasRecentCommitFiles, noFilesChanged, reviewable, securityFiles });
|
|
18811
|
+
Object.assign(run, { allChanged, allForReview, analyzable, hasRecentCommitFiles, noFilesChanged, reviewable, securityFiles, verityIgnored: ignored });
|
|
18175
18812
|
}
|
|
18176
18813
|
|
|
18177
18814
|
// src/lib/specs.ts
|
|
18178
|
-
var
|
|
18815
|
+
var import_node_fs27 = require("node:fs");
|
|
18179
18816
|
var import_node_path20 = require("node:path");
|
|
18180
18817
|
var SPEC_CANDIDATES = [
|
|
18181
18818
|
"CLAUDE.md",
|
|
@@ -18207,16 +18844,16 @@ function discoverSpecs(consulted = []) {
|
|
|
18207
18844
|
const totalCap = relevant ? MAX_TOTAL_SPEC_BYTES : UNCONSULTED_TOTAL_BYTES;
|
|
18208
18845
|
if (totalBytes >= totalCap) return false;
|
|
18209
18846
|
if (seen.has(specPath)) return true;
|
|
18210
|
-
if (!(0,
|
|
18847
|
+
if (!(0, import_node_fs27.existsSync)(specPath)) return true;
|
|
18211
18848
|
seen.add(specPath);
|
|
18212
18849
|
const remaining = totalCap - totalBytes;
|
|
18213
18850
|
const fileCap = relevant ? MAX_SPEC_FILE_BYTES : UNCONSULTED_FILE_BYTES;
|
|
18214
18851
|
const readBytes = Math.min(fileCap, remaining);
|
|
18215
18852
|
try {
|
|
18216
18853
|
const buf = Buffer.alloc(readBytes);
|
|
18217
|
-
const fd = (0,
|
|
18218
|
-
const bytesRead = (0,
|
|
18219
|
-
(0,
|
|
18854
|
+
const fd = (0, import_node_fs27.openSync)(specPath, "r");
|
|
18855
|
+
const bytesRead = (0, import_node_fs27.readSync)(fd, buf, 0, readBytes, 0);
|
|
18856
|
+
(0, import_node_fs27.closeSync)(fd);
|
|
18220
18857
|
const content = buf.slice(0, bytesRead).toString("utf-8");
|
|
18221
18858
|
if (!content) return true;
|
|
18222
18859
|
result.push({ path: specPath, content });
|
|
@@ -18232,7 +18869,7 @@ function discoverSpecs(consulted = []) {
|
|
|
18232
18869
|
if (!addSpec(candidate)) break;
|
|
18233
18870
|
}
|
|
18234
18871
|
for (const dir of ["spec", "docs"]) {
|
|
18235
|
-
if (!(0,
|
|
18872
|
+
if (!(0, import_node_fs27.existsSync)(dir)) continue;
|
|
18236
18873
|
try {
|
|
18237
18874
|
const mdFiles = findMdFiles(dir, 2).sort();
|
|
18238
18875
|
for (const mdFile of mdFiles) {
|
|
@@ -18247,7 +18884,7 @@ function findMdFiles(dir, maxDepth, depth = 0) {
|
|
|
18247
18884
|
if (depth >= maxDepth) return [];
|
|
18248
18885
|
const result = [];
|
|
18249
18886
|
try {
|
|
18250
|
-
const entries = (0,
|
|
18887
|
+
const entries = (0, import_node_fs27.readdirSync)(dir, { withFileTypes: true });
|
|
18251
18888
|
for (const entry of entries) {
|
|
18252
18889
|
const fullPath = (0, import_node_path20.join)(dir, entry.name);
|
|
18253
18890
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -18266,14 +18903,14 @@ function discoverPlans() {
|
|
|
18266
18903
|
const candidates = [];
|
|
18267
18904
|
const seen = /* @__PURE__ */ new Set();
|
|
18268
18905
|
for (const plansDir of [localPlansDir, homePlansDir]) {
|
|
18269
|
-
if (!(0,
|
|
18906
|
+
if (!(0, import_node_fs27.existsSync)(plansDir)) continue;
|
|
18270
18907
|
try {
|
|
18271
|
-
for (const f of (0,
|
|
18908
|
+
for (const f of (0, import_node_fs27.readdirSync)(plansDir)) {
|
|
18272
18909
|
if (!f.endsWith(".md") || seen.has(f)) continue;
|
|
18273
18910
|
seen.add(f);
|
|
18274
18911
|
const fullPath = (0, import_node_path20.join)(plansDir, f);
|
|
18275
18912
|
try {
|
|
18276
|
-
const stat3 = (0,
|
|
18913
|
+
const stat3 = (0, import_node_fs27.statSync)(fullPath);
|
|
18277
18914
|
candidates.push({ name: f, path: fullPath, mtime: stat3.mtimeMs, size: stat3.size });
|
|
18278
18915
|
} catch {
|
|
18279
18916
|
}
|
|
@@ -18286,13 +18923,35 @@ function discoverPlans() {
|
|
|
18286
18923
|
for (const entry of candidates.slice(0, MAX_PLAN_FILES)) {
|
|
18287
18924
|
if (entry.size > MAX_PLAN_FILE_BYTES) continue;
|
|
18288
18925
|
try {
|
|
18289
|
-
const content = (0,
|
|
18926
|
+
const content = (0, import_node_fs27.readFileSync)(entry.path, "utf-8");
|
|
18290
18927
|
result.push({ name: entry.name, content });
|
|
18291
18928
|
} catch {
|
|
18292
18929
|
}
|
|
18293
18930
|
}
|
|
18294
18931
|
return result;
|
|
18295
18932
|
}
|
|
18933
|
+
var GUARD_DOC_EXT = /\.(md|mdx|txt|rst|adoc)$/i;
|
|
18934
|
+
function discoverGuardDocs(rangeFiles2) {
|
|
18935
|
+
const result = [];
|
|
18936
|
+
let totalBytes = 0;
|
|
18937
|
+
for (const path of rangeFiles2) {
|
|
18938
|
+
if (result.length >= MAX_SPEC_FILES) break;
|
|
18939
|
+
if (!GUARD_DOC_EXT.test(path)) continue;
|
|
18940
|
+
if (path.startsWith("/") || path.includes("..")) continue;
|
|
18941
|
+
if (!(0, import_node_fs27.existsSync)(path)) continue;
|
|
18942
|
+
try {
|
|
18943
|
+
const stat3 = (0, import_node_fs27.statSync)(path);
|
|
18944
|
+
if (stat3.size > MAX_PLAN_FILE_BYTES) continue;
|
|
18945
|
+
if (totalBytes + stat3.size > MAX_TOTAL_SPEC_BYTES) continue;
|
|
18946
|
+
const content = (0, import_node_fs27.readFileSync)(path, "utf-8");
|
|
18947
|
+
if (!content) continue;
|
|
18948
|
+
result.push({ name: path, content });
|
|
18949
|
+
totalBytes += content.length;
|
|
18950
|
+
} catch {
|
|
18951
|
+
}
|
|
18952
|
+
}
|
|
18953
|
+
return result;
|
|
18954
|
+
}
|
|
18296
18955
|
|
|
18297
18956
|
// src/commands/analyze/phases/03-intent-inputs.ts
|
|
18298
18957
|
async function intentInputs(run) {
|
|
@@ -18306,6 +18965,50 @@ async function intentInputs(run) {
|
|
|
18306
18965
|
})) {
|
|
18307
18966
|
await passAndExit(run, "User command only \u2014 skipping analysis", "command-only-turn");
|
|
18308
18967
|
}
|
|
18968
|
+
{
|
|
18969
|
+
const ignoreKeys = ignoreStateKeys(
|
|
18970
|
+
run.tokenResult.ok ? run.tokenResult.data.token : void 0,
|
|
18971
|
+
null
|
|
18972
|
+
);
|
|
18973
|
+
const found = resolveIgnoreState([baselineSessionId, ...ignoreKeys]);
|
|
18974
|
+
const declaration = resolveActive(found?.state ?? null, Math.floor(Date.now() / 1e3));
|
|
18975
|
+
if (declaration) {
|
|
18976
|
+
const commandRecordTruncated = actionSummary?.commands_truncated === true || actionSummary?.user_commands_truncated === true || // The derived signal, and the only one an older client's summary can
|
|
18977
|
+
// give: `tool_counts` keeps counting Bash calls past every cap that
|
|
18978
|
+
// stops the list growing, so a mismatch IS a loss. See
|
|
18979
|
+
// `ActionSummary.commands_truncated`.
|
|
18980
|
+
(actionSummary?.tool_counts?.Bash ?? 0) > (actionSummary?.commands?.length ?? 0);
|
|
18981
|
+
const outcome = verifyDeclaration({
|
|
18982
|
+
declaration,
|
|
18983
|
+
agentAuthoredFiles: (actionSummary?.files_edited.length ?? 0) + (actionSummary?.files_created.length ?? 0),
|
|
18984
|
+
subagents: actionSummary?.subagents ?? 0,
|
|
18985
|
+
agentCommands: actionSummary?.commands,
|
|
18986
|
+
userCommands: actionSummary?.user_commands,
|
|
18987
|
+
commandRecordTruncated,
|
|
18988
|
+
authorshipIsObservable: !!actionSummary && actionSummary.transcript_windowed !== "orphaned"
|
|
18989
|
+
});
|
|
18990
|
+
if (outcome.honoured) {
|
|
18991
|
+
if (declaration.scope === "turn" && found) clearActiveDeclaration(found.key);
|
|
18992
|
+
logEvent("ignore_honoured", { scope: declaration.scope, origin: declaration.origin });
|
|
18993
|
+
await passAndExit(
|
|
18994
|
+
run,
|
|
18995
|
+
`skipping this turn \u2014 declared housekeeping ("${declaration.reason}")`,
|
|
18996
|
+
"declared-ignore"
|
|
18997
|
+
);
|
|
18998
|
+
} else if (outcome.void) {
|
|
18999
|
+
if (found) clearActiveDeclaration(found.key);
|
|
19000
|
+
logEvent("ignore_voided", {
|
|
19001
|
+
scope: declaration.scope,
|
|
19002
|
+
origin: declaration.origin,
|
|
19003
|
+
why: outcome.why
|
|
19004
|
+
});
|
|
19005
|
+
const notice = `Verity: the ignore declared for this window ("${declaration.reason}") was voided \u2014 ${outcome.why}. Reviewing normally.`;
|
|
19006
|
+
process.stderr.write(`${notice}
|
|
19007
|
+
`);
|
|
19008
|
+
run.voidedIgnoreNotice = notice;
|
|
19009
|
+
}
|
|
19010
|
+
}
|
|
19011
|
+
}
|
|
18309
19012
|
const conversation = await readAndClearConversationBuffer(baselineSessionId);
|
|
18310
19013
|
const specs = discoverSpecs(actionSummary?.files_read ?? []);
|
|
18311
19014
|
const plans = discoverPlans();
|
|
@@ -18419,177 +19122,8 @@ async function mode(run) {
|
|
|
18419
19122
|
Object.assign(run, { analysisMode, contextFilePaths, sessionAuthoredCode, sessionIdForMemory });
|
|
18420
19123
|
}
|
|
18421
19124
|
|
|
18422
|
-
// src/lib/debounce.ts
|
|
18423
|
-
var import_node_fs24 = require("node:fs");
|
|
18424
|
-
var import_node_crypto10 = require("node:crypto");
|
|
18425
|
-
function scopedFile(base, sessionId) {
|
|
18426
|
-
if (!sessionId) return base;
|
|
18427
|
-
return `${base}.${(0, import_node_crypto10.createHash)("sha1").update(sessionId).digest("hex").slice(0, 12)}`;
|
|
18428
|
-
}
|
|
18429
|
-
function checkDebounce(debounceSeconds = DEBOUNCE_SECONDS, sessionId) {
|
|
18430
|
-
const file = scopedFile(DEBOUNCE_FILE, sessionId);
|
|
18431
|
-
if (!(0, import_node_fs24.existsSync)(file)) return null;
|
|
18432
|
-
try {
|
|
18433
|
-
const lastTs = parseInt((0, import_node_fs24.readFileSync)(file, "utf-8").trim(), 10);
|
|
18434
|
-
const nowTs = Math.floor(Date.now() / 1e3);
|
|
18435
|
-
const elapsed = nowTs - lastTs;
|
|
18436
|
-
if (elapsed < debounceSeconds) {
|
|
18437
|
-
return `Debounced \u2014 last analysis was ${elapsed}s ago`;
|
|
18438
|
-
}
|
|
18439
|
-
} catch {
|
|
18440
|
-
}
|
|
18441
|
-
return null;
|
|
18442
|
-
}
|
|
18443
|
-
function checkMtime(files, bypassForRecentCommits, sessionId) {
|
|
18444
|
-
if (bypassForRecentCommits) return null;
|
|
18445
|
-
const file = scopedFile(DEBOUNCE_FILE, sessionId);
|
|
18446
|
-
if (!(0, import_node_fs24.existsSync)(file)) return null;
|
|
18447
|
-
let debounceTime;
|
|
18448
|
-
try {
|
|
18449
|
-
debounceTime = (0, import_node_fs24.statSync)(file).mtimeMs;
|
|
18450
|
-
} catch {
|
|
18451
|
-
return null;
|
|
18452
|
-
}
|
|
18453
|
-
for (const f of files) {
|
|
18454
|
-
const resolved = resolveFile(f);
|
|
18455
|
-
if (!resolved) continue;
|
|
18456
|
-
try {
|
|
18457
|
-
const stat3 = (0, import_node_fs24.statSync)(resolved);
|
|
18458
|
-
if (stat3.mtimeMs > debounceTime) {
|
|
18459
|
-
return null;
|
|
18460
|
-
}
|
|
18461
|
-
} catch {
|
|
18462
|
-
continue;
|
|
18463
|
-
}
|
|
18464
|
-
}
|
|
18465
|
-
return "No files modified since last analysis";
|
|
18466
|
-
}
|
|
18467
|
-
function computeContentHash(files) {
|
|
18468
|
-
const hash = (0, import_node_crypto10.createHash)("sha1");
|
|
18469
|
-
const sorted = [...files].sort();
|
|
18470
|
-
for (const f of sorted) {
|
|
18471
|
-
const resolved = resolveFile(f) ?? f;
|
|
18472
|
-
try {
|
|
18473
|
-
if ((0, import_node_fs24.existsSync)(resolved)) {
|
|
18474
|
-
hash.update((0, import_node_fs24.readFileSync)(resolved));
|
|
18475
|
-
}
|
|
18476
|
-
} catch {
|
|
18477
|
-
}
|
|
18478
|
-
}
|
|
18479
|
-
return hash.digest("hex");
|
|
18480
|
-
}
|
|
18481
|
-
function checkContentHash(files, sessionId) {
|
|
18482
|
-
const hash = computeContentHash(files);
|
|
18483
|
-
const file = scopedFile(HASH_FILE, sessionId);
|
|
18484
|
-
if ((0, import_node_fs24.existsSync)(file)) {
|
|
18485
|
-
try {
|
|
18486
|
-
const storedHash = (0, import_node_fs24.readFileSync)(file, "utf-8").trim();
|
|
18487
|
-
if (hash === storedHash) {
|
|
18488
|
-
return { skip: "No source changes since last analysis", hash };
|
|
18489
|
-
}
|
|
18490
|
-
} catch {
|
|
18491
|
-
}
|
|
18492
|
-
}
|
|
18493
|
-
return { skip: null, hash };
|
|
18494
|
-
}
|
|
18495
|
-
function recordAnalysisStart(sessionId) {
|
|
18496
|
-
(0, import_node_fs24.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
18497
|
-
(0, import_node_fs24.writeFileSync)(scopedFile(DEBOUNCE_FILE, sessionId), String(Math.floor(Date.now() / 1e3)));
|
|
18498
|
-
}
|
|
18499
|
-
function recordPassHash(hash, sessionId) {
|
|
18500
|
-
(0, import_node_fs24.writeFileSync)(scopedFile(HASH_FILE, sessionId), hash);
|
|
18501
|
-
}
|
|
18502
|
-
function narrowToRecent(files, sessionId) {
|
|
18503
|
-
const file = scopedFile(DEBOUNCE_FILE, sessionId);
|
|
18504
|
-
if (!(0, import_node_fs24.existsSync)(file)) return files;
|
|
18505
|
-
let debounceTime;
|
|
18506
|
-
try {
|
|
18507
|
-
debounceTime = (0, import_node_fs24.statSync)(file).mtimeMs;
|
|
18508
|
-
} catch {
|
|
18509
|
-
return files;
|
|
18510
|
-
}
|
|
18511
|
-
const recent = files.filter((f) => {
|
|
18512
|
-
try {
|
|
18513
|
-
return (0, import_node_fs24.existsSync)(f) && (0, import_node_fs24.statSync)(f).mtimeMs > debounceTime;
|
|
18514
|
-
} catch {
|
|
18515
|
-
return false;
|
|
18516
|
-
}
|
|
18517
|
-
});
|
|
18518
|
-
return recent.length > 0 ? recent : files;
|
|
18519
|
-
}
|
|
18520
|
-
function readIteration(currentCommit, _contentHash) {
|
|
18521
|
-
return Math.max(1, readBlockState(currentCommit).attempts);
|
|
18522
|
-
}
|
|
18523
|
-
var NO_BLOCKS = { attempts: 0, blocks: 0, fingerprint: null };
|
|
18524
|
-
function readBlockState(currentCommit, opts) {
|
|
18525
|
-
if (opts?.newUserPrompt) return NO_BLOCKS;
|
|
18526
|
-
if (!(0, import_node_fs24.existsSync)(ITERATION_FILE)) return NO_BLOCKS;
|
|
18527
|
-
try {
|
|
18528
|
-
const stored = (0, import_node_fs24.readFileSync)(ITERATION_FILE, "utf-8").trim();
|
|
18529
|
-
const parsed = stored.startsWith("{") ? parseJsonState(stored) : parseLegacyState(stored);
|
|
18530
|
-
if (!parsed) return NO_BLOCKS;
|
|
18531
|
-
if (parsed.commit !== currentCommit) return NO_BLOCKS;
|
|
18532
|
-
if (parsed.ts > 0 && Math.floor(Date.now() / 1e3) - parsed.ts > 600) return NO_BLOCKS;
|
|
18533
|
-
return { attempts: parsed.attempts, blocks: parsed.blocks, fingerprint: parsed.fingerprint };
|
|
18534
|
-
} catch {
|
|
18535
|
-
return NO_BLOCKS;
|
|
18536
|
-
}
|
|
18537
|
-
}
|
|
18538
|
-
function parseJsonState(raw) {
|
|
18539
|
-
const o = JSON.parse(raw);
|
|
18540
|
-
const attempts = typeof o.attempts === "number" ? o.attempts : NaN;
|
|
18541
|
-
if (isNaN(attempts)) return null;
|
|
18542
|
-
return {
|
|
18543
|
-
attempts,
|
|
18544
|
-
blocks: typeof o.blocks === "number" ? o.blocks : attempts,
|
|
18545
|
-
fingerprint: typeof o.fingerprint === "string" && o.fingerprint ? o.fingerprint : null,
|
|
18546
|
-
commit: typeof o.commit === "string" ? o.commit : "",
|
|
18547
|
-
ts: typeof o.ts === "number" ? o.ts : 0
|
|
18548
|
-
};
|
|
18549
|
-
}
|
|
18550
|
-
function parseLegacyState(raw) {
|
|
18551
|
-
const parts = raw.split(":");
|
|
18552
|
-
const n = parseInt(parts[0], 10);
|
|
18553
|
-
if (isNaN(n)) return null;
|
|
18554
|
-
return {
|
|
18555
|
-
attempts: n,
|
|
18556
|
-
// The old file has no separate block count; the old counter is the closest
|
|
18557
|
-
// honest answer, and it errs toward releasing sooner rather than later.
|
|
18558
|
-
blocks: n,
|
|
18559
|
-
fingerprint: parts.slice(3).join(":") || null,
|
|
18560
|
-
commit: parts[1] ?? "",
|
|
18561
|
-
ts: parseInt(parts[2] ?? "0", 10)
|
|
18562
|
-
};
|
|
18563
|
-
}
|
|
18564
|
-
function findingsFingerprint(findings) {
|
|
18565
|
-
const keys = findings.map((f) => `${String(f.pattern_id ?? "?")}|${String(f.file ?? "?")}`).filter((k) => k !== "?|?");
|
|
18566
|
-
return [...new Set(keys)].sort().join(",");
|
|
18567
|
-
}
|
|
18568
|
-
function isSameProblem(previous, current) {
|
|
18569
|
-
if (!previous || !current) return false;
|
|
18570
|
-
const prev = new Set(previous.split(","));
|
|
18571
|
-
return current.split(",").some((k) => prev.has(k));
|
|
18572
|
-
}
|
|
18573
|
-
function writeBlockState(commit, state) {
|
|
18574
|
-
(0, import_node_fs24.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
18575
|
-
(0, import_node_fs24.writeFileSync)(
|
|
18576
|
-
ITERATION_FILE,
|
|
18577
|
-
JSON.stringify({
|
|
18578
|
-
v: 2,
|
|
18579
|
-
attempts: state.attempts,
|
|
18580
|
-
blocks: state.blocks,
|
|
18581
|
-
commit,
|
|
18582
|
-
ts: Math.floor(Date.now() / 1e3),
|
|
18583
|
-
fingerprint: state.fingerprint ?? void 0
|
|
18584
|
-
})
|
|
18585
|
-
);
|
|
18586
|
-
}
|
|
18587
|
-
function resetBlockState(commit) {
|
|
18588
|
-
writeBlockState(commit, { attempts: 0, blocks: 0, fingerprint: null });
|
|
18589
|
-
}
|
|
18590
|
-
|
|
18591
19125
|
// src/lib/fold.ts
|
|
18592
|
-
var
|
|
19126
|
+
var import_node_fs28 = require("node:fs");
|
|
18593
19127
|
var import_node_path21 = require("node:path");
|
|
18594
19128
|
var KNOWN_RECORD_TYPES = /* @__PURE__ */ new Set([
|
|
18595
19129
|
"user",
|
|
@@ -18727,7 +19261,7 @@ function candidateRoots(repoRoot2) {
|
|
|
18727
19261
|
const norm = repoRoot2.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
18728
19262
|
const out = [norm];
|
|
18729
19263
|
try {
|
|
18730
|
-
const real =
|
|
19264
|
+
const real = import_node_fs28.realpathSync.native(norm).replace(/\\/g, "/").replace(/\/+$/, "");
|
|
18731
19265
|
if (real !== norm) out.push(real);
|
|
18732
19266
|
} catch {
|
|
18733
19267
|
}
|
|
@@ -18815,8 +19349,8 @@ function fold(transcriptPath, opts = {}) {
|
|
|
18815
19349
|
}
|
|
18816
19350
|
};
|
|
18817
19351
|
try {
|
|
18818
|
-
if (!(0,
|
|
18819
|
-
ingest((0,
|
|
19352
|
+
if (!(0, import_node_fs28.existsSync)(transcriptPath)) return result;
|
|
19353
|
+
ingest((0, import_node_fs28.readFileSync)(transcriptPath, "utf8"), "agent");
|
|
18820
19354
|
result.coverage.complete = true;
|
|
18821
19355
|
} catch {
|
|
18822
19356
|
return result;
|
|
@@ -18827,19 +19361,19 @@ function fold(transcriptPath, opts = {}) {
|
|
|
18827
19361
|
(0, import_node_path21.basename)(transcriptPath).replace(/\.jsonl$/, ""),
|
|
18828
19362
|
"subagents"
|
|
18829
19363
|
);
|
|
18830
|
-
if ((0,
|
|
19364
|
+
if ((0, import_node_fs28.existsSync)(sidecarDir)) {
|
|
18831
19365
|
const maxFiles = opts.maxSidecars ?? 200;
|
|
18832
19366
|
const maxBytes = opts.maxSidecarBytes ?? 16 * 1024 * 1024;
|
|
18833
19367
|
const found = [];
|
|
18834
19368
|
const walk = (d, depth) => {
|
|
18835
19369
|
if (depth > 4) return;
|
|
18836
|
-
for (const e of (0,
|
|
19370
|
+
for (const e of (0, import_node_fs28.readdirSync)(d, { withFileTypes: true })) {
|
|
18837
19371
|
const p = (0, import_node_path21.join)(d, e.name);
|
|
18838
19372
|
if (e.isDirectory()) {
|
|
18839
19373
|
walk(p, depth + 1);
|
|
18840
19374
|
} else if (e.name.startsWith("agent-") && e.name.endsWith(".jsonl")) {
|
|
18841
19375
|
try {
|
|
18842
|
-
const st = (0,
|
|
19376
|
+
const st = (0, import_node_fs28.statSync)(p);
|
|
18843
19377
|
found.push({ path: p, size: st.size, mtimeMs: st.mtimeMs });
|
|
18844
19378
|
} catch {
|
|
18845
19379
|
result.coverage.malformed++;
|
|
@@ -18856,7 +19390,7 @@ function fold(transcriptPath, opts = {}) {
|
|
|
18856
19390
|
continue;
|
|
18857
19391
|
}
|
|
18858
19392
|
try {
|
|
18859
|
-
ingest((0,
|
|
19393
|
+
ingest((0, import_node_fs28.readFileSync)(f.path, "utf8"), "subagent");
|
|
18860
19394
|
bytes += f.size;
|
|
18861
19395
|
result.coverage.subagentFiles++;
|
|
18862
19396
|
} catch {
|
|
@@ -18891,7 +19425,7 @@ function fold(transcriptPath, opts = {}) {
|
|
|
18891
19425
|
}
|
|
18892
19426
|
function classifyUnobserved(path) {
|
|
18893
19427
|
try {
|
|
18894
|
-
const st = (0,
|
|
19428
|
+
const st = (0, import_node_fs28.statSync)(path);
|
|
18895
19429
|
if (!st.isFile()) return "unreadable";
|
|
18896
19430
|
} catch {
|
|
18897
19431
|
return "unreadable";
|
|
@@ -19195,20 +19729,20 @@ async function evidence(run) {
|
|
|
19195
19729
|
}
|
|
19196
19730
|
|
|
19197
19731
|
// src/lib/cache-cleanup.ts
|
|
19198
|
-
var
|
|
19732
|
+
var import_node_fs29 = require("node:fs");
|
|
19199
19733
|
var import_node_path22 = require("node:path");
|
|
19200
19734
|
var CACHE_TTL_DAYS = 7;
|
|
19201
19735
|
function pruneStaleCache() {
|
|
19202
19736
|
try {
|
|
19203
19737
|
const dir = projectPath(CACHE_DIR);
|
|
19204
19738
|
const cutoff = Date.now() - CACHE_TTL_DAYS * 24 * 3600 * 1e3;
|
|
19205
|
-
for (const entry of (0,
|
|
19739
|
+
for (const entry of (0, import_node_fs29.readdirSync)(dir)) {
|
|
19206
19740
|
if (!entry.startsWith("pending-")) continue;
|
|
19207
19741
|
const path = (0, import_node_path22.join)(dir, entry);
|
|
19208
19742
|
try {
|
|
19209
|
-
const stat3 = (0,
|
|
19743
|
+
const stat3 = (0, import_node_fs29.statSync)(path);
|
|
19210
19744
|
if (stat3.mtimeMs < cutoff) {
|
|
19211
|
-
(0,
|
|
19745
|
+
(0, import_node_fs29.unlinkSync)(path);
|
|
19212
19746
|
logEvent("cache_entry_pruned", {
|
|
19213
19747
|
path: entry,
|
|
19214
19748
|
age_days: Math.round((Date.now() - stat3.mtimeMs) / 864e5)
|
|
@@ -19222,7 +19756,7 @@ function pruneStaleCache() {
|
|
|
19222
19756
|
}
|
|
19223
19757
|
|
|
19224
19758
|
// src/lib/context-files.ts
|
|
19225
|
-
var
|
|
19759
|
+
var import_node_fs30 = require("node:fs");
|
|
19226
19760
|
var MAX_CONTEXT_FILES = 10;
|
|
19227
19761
|
var MAX_CONTEXT_FILE_BYTES = 10240;
|
|
19228
19762
|
var MAX_CONTEXT_TOTAL_BYTES = 51200;
|
|
@@ -19243,7 +19777,7 @@ function gatherContextFiles(contextPaths, deltaFiles) {
|
|
|
19243
19777
|
continue;
|
|
19244
19778
|
}
|
|
19245
19779
|
try {
|
|
19246
|
-
const content = (0,
|
|
19780
|
+
const content = (0, import_node_fs30.readFileSync)(safePath, "utf8");
|
|
19247
19781
|
const bytes = Buffer.byteLength(content);
|
|
19248
19782
|
if (bytes > MAX_CONTEXT_FILE_BYTES) {
|
|
19249
19783
|
logEvent("context_file_skipped", { path: filePath, reason: "too_large", bytes });
|
|
@@ -19305,7 +19839,7 @@ async function contextFiles(run) {
|
|
|
19305
19839
|
|
|
19306
19840
|
// src/lib/seed-runner.ts
|
|
19307
19841
|
var import_promises11 = require("node:fs/promises");
|
|
19308
|
-
var
|
|
19842
|
+
var import_node_fs31 = require("node:fs");
|
|
19309
19843
|
var import_node_path23 = require("node:path");
|
|
19310
19844
|
var import_yaml2 = __toESM(require_dist());
|
|
19311
19845
|
|
|
@@ -19545,7 +20079,7 @@ function renderNodeMarkdown(candidate, nodeId, createdAt) {
|
|
|
19545
20079
|
return fm;
|
|
19546
20080
|
}
|
|
19547
20081
|
async function runSeed(opts) {
|
|
19548
|
-
if (!(0,
|
|
20082
|
+
if (!(0, import_node_fs31.existsSync)(STANDARD_FILE)) {
|
|
19549
20083
|
return { created: 0, failed: 0, skipped: "no_standard", candidates: [] };
|
|
19550
20084
|
}
|
|
19551
20085
|
let standardDoc;
|
|
@@ -19557,7 +20091,7 @@ async function runSeed(opts) {
|
|
|
19557
20091
|
}
|
|
19558
20092
|
const knowledgeSpec = standardDoc.knowledge_spec ?? {};
|
|
19559
20093
|
let readmeContent;
|
|
19560
|
-
if ((0,
|
|
20094
|
+
if ((0, import_node_fs31.existsSync)("README.md")) {
|
|
19561
20095
|
try {
|
|
19562
20096
|
readmeContent = await (0, import_promises11.readFile)("README.md", "utf-8");
|
|
19563
20097
|
} catch {
|
|
@@ -19565,7 +20099,7 @@ async function runSeed(opts) {
|
|
|
19565
20099
|
}
|
|
19566
20100
|
let claudeMdContent;
|
|
19567
20101
|
for (const p of ["CLAUDE.md", ".claude/CLAUDE.md"]) {
|
|
19568
|
-
if ((0,
|
|
20102
|
+
if ((0, import_node_fs31.existsSync)(p)) {
|
|
19569
20103
|
try {
|
|
19570
20104
|
claudeMdContent = await (0, import_promises11.readFile)(p, "utf-8");
|
|
19571
20105
|
break;
|
|
@@ -19589,7 +20123,7 @@ async function runSeed(opts) {
|
|
|
19589
20123
|
return { created: 0, failed: 0, skipped: "no_candidates", candidates: [] };
|
|
19590
20124
|
}
|
|
19591
20125
|
const overviewPath = (0, import_node_path23.join)(MEMORY_DIR, "domain", "project-overview.md");
|
|
19592
|
-
if ((0,
|
|
20126
|
+
if ((0, import_node_fs31.existsSync)(overviewPath) && !opts.force) {
|
|
19593
20127
|
return { created: 0, failed: 0, skipped: "already_seeded", candidates };
|
|
19594
20128
|
}
|
|
19595
20129
|
if (opts.dryRun) {
|
|
@@ -19644,7 +20178,7 @@ async function runSeed(opts) {
|
|
|
19644
20178
|
}
|
|
19645
20179
|
|
|
19646
20180
|
// src/commands/analyze/phases/08-memory-manifest.ts
|
|
19647
|
-
var
|
|
20181
|
+
var import_node_fs32 = require("node:fs");
|
|
19648
20182
|
var import_node_path24 = require("node:path");
|
|
19649
20183
|
async function memoryManifest(run) {
|
|
19650
20184
|
const { globals } = run;
|
|
@@ -19656,8 +20190,8 @@ async function memoryManifest(run) {
|
|
|
19656
20190
|
try {
|
|
19657
20191
|
await ensureMemoryDir();
|
|
19658
20192
|
const seedMarker = (0, import_node_path24.join)(VERITY_DIR, ".seeded");
|
|
19659
|
-
const hasStandard = (0,
|
|
19660
|
-
const alreadyTried = (0,
|
|
20193
|
+
const hasStandard = (0, import_node_fs32.existsSync)(STANDARD_FILE);
|
|
20194
|
+
const alreadyTried = (0, import_node_fs32.existsSync)(seedMarker);
|
|
19661
20195
|
if (hasStandard && !alreadyTried) {
|
|
19662
20196
|
const preManifest = await buildManifest();
|
|
19663
20197
|
if (preManifest.nodes.length === 0) {
|
|
@@ -19670,7 +20204,7 @@ async function memoryManifest(run) {
|
|
|
19670
20204
|
dryRun: false
|
|
19671
20205
|
});
|
|
19672
20206
|
if (seedResult.created > 0) {
|
|
19673
|
-
(0,
|
|
20207
|
+
(0, import_node_fs32.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} created=${seedResult.created}
|
|
19674
20208
|
`);
|
|
19675
20209
|
autoSeedNotice = `Seeded ${seedResult.created} knowledge node(s) from your existing Standard (one-time).`;
|
|
19676
20210
|
logEvent("auto_seed_ran", {
|
|
@@ -19678,7 +20212,7 @@ async function memoryManifest(run) {
|
|
|
19678
20212
|
failed: seedResult.failed
|
|
19679
20213
|
});
|
|
19680
20214
|
} else if (seedResult.skipped === "already_seeded") {
|
|
19681
|
-
(0,
|
|
20215
|
+
(0, import_node_fs32.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} skipped=already_seeded
|
|
19682
20216
|
`);
|
|
19683
20217
|
} else {
|
|
19684
20218
|
logEvent("auto_seed_noop", {
|
|
@@ -19867,7 +20401,7 @@ async function workingMemory(run) {
|
|
|
19867
20401
|
}
|
|
19868
20402
|
|
|
19869
20403
|
// src/lib/note-budget.ts
|
|
19870
|
-
var
|
|
20404
|
+
var import_node_fs33 = require("node:fs");
|
|
19871
20405
|
var ADVISORY_BUDGET = { PASS: 1, WARN: 2 };
|
|
19872
20406
|
var EPISODE_STALE_SECONDS = 30 * 60;
|
|
19873
20407
|
var FRESH = { delivered: 0, tasksCompleted: 0, ts: 0 };
|
|
@@ -19889,9 +20423,9 @@ function advisoryBudgetSpent(episode, rawDecision) {
|
|
|
19889
20423
|
}
|
|
19890
20424
|
function readAdvisoryEpisode(sessionId) {
|
|
19891
20425
|
const file = scopedFile(ADVISORY_EPISODE_FILE, sessionId);
|
|
19892
|
-
if (!(0,
|
|
20426
|
+
if (!(0, import_node_fs33.existsSync)(file)) return null;
|
|
19893
20427
|
try {
|
|
19894
|
-
const o = JSON.parse((0,
|
|
20428
|
+
const o = JSON.parse((0, import_node_fs33.readFileSync)(file, "utf-8")) ?? {};
|
|
19895
20429
|
const delivered = typeof o.delivered === "number" ? o.delivered : NaN;
|
|
19896
20430
|
if (isNaN(delivered)) return null;
|
|
19897
20431
|
return {
|
|
@@ -19905,8 +20439,8 @@ function readAdvisoryEpisode(sessionId) {
|
|
|
19905
20439
|
}
|
|
19906
20440
|
function writeAdvisoryEpisode(episode, sessionId) {
|
|
19907
20441
|
try {
|
|
19908
|
-
(0,
|
|
19909
|
-
(0,
|
|
20442
|
+
(0, import_node_fs33.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
20443
|
+
(0, import_node_fs33.writeFileSync)(
|
|
19910
20444
|
scopedFile(ADVISORY_EPISODE_FILE, sessionId),
|
|
19911
20445
|
JSON.stringify({ v: 1, ...episode })
|
|
19912
20446
|
);
|
|
@@ -20013,7 +20547,16 @@ async function buildRequest(run) {
|
|
|
20013
20547
|
// the state, so the number is one turn lagged by construction. The
|
|
20014
20548
|
// degenerate win for the budget is a dead channel that looks like clean
|
|
20015
20549
|
// code; this is what makes "did delivery rate collapse" a query.
|
|
20016
|
-
advisory_delivered_prior: readAdvisoryEpisode(run.baselineSessionId)?.delivered ?? 0
|
|
20550
|
+
advisory_delivered_prior: readAdvisoryEpisode(run.baselineSessionId)?.delivered ?? 0,
|
|
20551
|
+
// `.verityignore` — see CoverageTelemetry.verityignore for why the SHARE is
|
|
20552
|
+
// the number that matters and why no paths travel with it.
|
|
20553
|
+
verityignore: {
|
|
20554
|
+
rules: run.verityIgnored.rules,
|
|
20555
|
+
excluded: run.verityIgnored.ignored.length,
|
|
20556
|
+
share: ignoreShare(run.verityIgnored.kept.length, run.verityIgnored.ignored.length),
|
|
20557
|
+
security_excluded: run.verityIgnored.securityExcluded.length,
|
|
20558
|
+
suspended: run.verityIgnored.suspended
|
|
20559
|
+
}
|
|
20017
20560
|
};
|
|
20018
20561
|
const requestBody = {
|
|
20019
20562
|
coverage_telemetry: coverageTelemetry,
|
|
@@ -20211,14 +20754,14 @@ async function buildRequest(run) {
|
|
|
20211
20754
|
}
|
|
20212
20755
|
|
|
20213
20756
|
// src/lib/offline.ts
|
|
20214
|
-
var
|
|
20757
|
+
var import_node_fs34 = require("node:fs");
|
|
20215
20758
|
var import_node_crypto11 = require("node:crypto");
|
|
20216
20759
|
function cacheRequest(body) {
|
|
20217
20760
|
try {
|
|
20218
|
-
(0,
|
|
20761
|
+
(0, import_node_fs34.mkdirSync)(CACHE_DIR, { recursive: true });
|
|
20219
20762
|
const suffix = (0, import_node_crypto11.randomBytes)(4).toString("hex");
|
|
20220
20763
|
const filename = `pending-${Math.floor(Date.now() / 1e3)}-${suffix}.json`;
|
|
20221
|
-
(0,
|
|
20764
|
+
(0, import_node_fs34.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(redactRequest(body)));
|
|
20222
20765
|
} catch {
|
|
20223
20766
|
}
|
|
20224
20767
|
}
|
|
@@ -20337,7 +20880,7 @@ async function transmit(run) {
|
|
|
20337
20880
|
}
|
|
20338
20881
|
|
|
20339
20882
|
// src/commands/analyze/phases/13-reconcile.ts
|
|
20340
|
-
var
|
|
20883
|
+
var import_node_fs35 = require("node:fs");
|
|
20341
20884
|
var import_node_path26 = require("node:path");
|
|
20342
20885
|
async function reconcile(run) {
|
|
20343
20886
|
const { actionSummary, allChanged, analyzable, baseline, baselineSessionId, codeDelta, contentHash, conversation, decision, foldResult, memory, memorySession, response, reviewable, securityFiles, turnId } = run;
|
|
@@ -20348,7 +20891,7 @@ async function reconcile(run) {
|
|
|
20348
20891
|
const st = foldDossier(memorySession.d);
|
|
20349
20892
|
openElsewhere = openBlockingElsewhere(st.statements, sentPaths, (file, line) => {
|
|
20350
20893
|
try {
|
|
20351
|
-
const src = (0,
|
|
20894
|
+
const src = (0, import_node_fs35.readFileSync)((0, import_node_path26.join)(repoRoot(), file), "utf8").split("\n");
|
|
20352
20895
|
const at = src[line - 1];
|
|
20353
20896
|
return at === void 0 ? null : lineSha(at);
|
|
20354
20897
|
} catch {
|
|
@@ -20409,6 +20952,25 @@ async function reconcile(run) {
|
|
|
20409
20952
|
stage: "self-scope",
|
|
20410
20953
|
kind: "policy"
|
|
20411
20954
|
})),
|
|
20955
|
+
// ⚠ WHAT THE TEAM'S OWN `.verityignore` REMOVED — every path, named
|
|
20956
|
+
// (VRT-135 · 3/3). The 0.30 principle: an exclusion the ledger cannot see
|
|
20957
|
+
// is a scoping decision nobody can audit, and this is the one exclusion
|
|
20958
|
+
// stage a user can edit, so it is the one that most needs to be visible.
|
|
20959
|
+
//
|
|
20960
|
+
// `policy`, by D2's bar — no version of this product would have reviewed a
|
|
20961
|
+
// file the project declared out of scope, so it must not downgrade PASS to
|
|
20962
|
+
// WARN. That is also why it is not narrated per-turn by `describeCoverage`
|
|
20963
|
+
// (policy exclusions are recorded, not announced): repeating "your dist/
|
|
20964
|
+
// went unreviewed" on every turn is how a channel gets muted. The SHARE
|
|
20965
|
+
// below is the signal that replaces the noise.
|
|
20966
|
+
//
|
|
20967
|
+
// Taken from the run, not recomputed — see context.ts `verityIgnored`.
|
|
20968
|
+
...run.verityIgnored.ignored.map((path) => ({
|
|
20969
|
+
path,
|
|
20970
|
+
reason: "verityignore",
|
|
20971
|
+
stage: "verityignore",
|
|
20972
|
+
kind: "policy"
|
|
20973
|
+
})),
|
|
20412
20974
|
// The extension allowlist. POLICY: a changed README was never going to be
|
|
20413
20975
|
// reviewed, and calling that a coverage gap would downgrade nearly every
|
|
20414
20976
|
// PASS to WARN until WARN meant nothing. Recorded so the ledger balances and
|
|
@@ -20483,6 +21045,14 @@ async function reconcile(run) {
|
|
|
20483
21045
|
})() : [];
|
|
20484
21046
|
if (memorySession) {
|
|
20485
21047
|
try {
|
|
21048
|
+
const settledAnchors = response.metadata?.settled_anchors;
|
|
21049
|
+
if (Array.isArray(settledAnchors)) {
|
|
21050
|
+
for (const anchorKey of settledAnchors) {
|
|
21051
|
+
if (typeof anchorKey === "string" && anchorKey.length > 0) {
|
|
21052
|
+
appendEvent(memorySession.d, { k: "outcome", anchor_key: anchorKey, outcome: "answered" });
|
|
21053
|
+
}
|
|
21054
|
+
}
|
|
21055
|
+
}
|
|
20486
21056
|
recordVerdict(memorySession.d, {
|
|
20487
21057
|
runId: response.run_id ?? turnId,
|
|
20488
21058
|
decision,
|
|
@@ -20638,7 +21208,7 @@ function agentContextFor(response, intentRepeat = 0, priorPendingFingerprints =
|
|
|
20638
21208
|
}
|
|
20639
21209
|
async function render(run) {
|
|
20640
21210
|
const { opts, globals } = run;
|
|
20641
|
-
const { actionSummary, assistantResponse, autoSeedNotice, baselineSessionId, codeDelta, contentHash, conversation, currentCommit, decision, intentRepeatCount, memory, openElsewhere, priorPendingFingerprints, response, reviewCoverage, serviceUrl, sessionIdForMemory, silenced, token, watermarkHash, watermarkIsPartial } = run;
|
|
21211
|
+
const { actionSummary, assistantResponse, autoSeedNotice, voidedIgnoreNotice, baselineSessionId, codeDelta, contentHash, conversation, currentCommit, decision, intentRepeatCount, memory, openElsewhere, priorPendingFingerprints, response, reviewCoverage, serviceUrl, sessionIdForMemory, silenced, token, watermarkHash, watermarkIsPartial } = run;
|
|
20642
21212
|
let { iteration } = run;
|
|
20643
21213
|
const metadata = response.metadata ?? {};
|
|
20644
21214
|
const intentAmbiguity = metadata.intent_ambiguity;
|
|
@@ -20890,6 +21460,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
20890
21460
|
const viewUrl = response.view_url ?? "";
|
|
20891
21461
|
if (viewUrl) userSummary += ` Report: ${viewUrl}`;
|
|
20892
21462
|
if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
|
|
21463
|
+
if (voidedIgnoreNotice) userSummary = `${voidedIgnoreNotice} ${userSummary}`;
|
|
20893
21464
|
userSummary += loginNudge + grantNudge;
|
|
20894
21465
|
emitVerdict({
|
|
20895
21466
|
proposed: "PASS",
|
|
@@ -20911,6 +21482,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
20911
21482
|
const viewUrl = response.view_url ?? "";
|
|
20912
21483
|
if (viewUrl) userSummary += ` Report: ${viewUrl}`;
|
|
20913
21484
|
if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
|
|
21485
|
+
if (voidedIgnoreNotice) userSummary = `${voidedIgnoreNotice} ${userSummary}`;
|
|
20914
21486
|
userSummary += loginNudge + grantNudge;
|
|
20915
21487
|
emitVerdict({
|
|
20916
21488
|
proposed: "WARN",
|
|
@@ -20926,7 +21498,11 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
20926
21498
|
}
|
|
20927
21499
|
default: {
|
|
20928
21500
|
const raw = String(decision ?? "(missing)");
|
|
20929
|
-
const msg =
|
|
21501
|
+
const msg = [
|
|
21502
|
+
voidedIgnoreNotice,
|
|
21503
|
+
autoSeedNotice,
|
|
21504
|
+
"Verity: unrecognised verdict \u2014 treating as WARN"
|
|
21505
|
+
].filter(Boolean).join(" ") + loginNudge + grantNudge;
|
|
20930
21506
|
process.stderr.write(
|
|
20931
21507
|
`Verity: server returned an unrecognised gate_decision (${raw}). Rendering WARN rather than PASS. Update the CLI: npm i -g @codacy/verity-cli
|
|
20932
21508
|
`
|
|
@@ -21003,7 +21579,7 @@ async function runAnalyze(opts, globals) {
|
|
|
21003
21579
|
}
|
|
21004
21580
|
|
|
21005
21581
|
// src/commands/baseline.ts
|
|
21006
|
-
var
|
|
21582
|
+
var import_node_fs36 = require("node:fs");
|
|
21007
21583
|
function registerBaselineCommands(program2) {
|
|
21008
21584
|
const baseline = program2.command("baseline").description("Manage the task-start working-tree baseline");
|
|
21009
21585
|
baseline.command("capture").description("Snapshot the working tree at task start (used by SessionStart hook)").option("--session-id <id>", "Session id (overrides any value from stdin)").option("--source <source>", "Lifecycle hint: startup|resume|clear|compact").action(async (opts) => {
|
|
@@ -21012,7 +21588,7 @@ function registerBaselineCommands(program2) {
|
|
|
21012
21588
|
process.chdir(repoRoot());
|
|
21013
21589
|
} catch {
|
|
21014
21590
|
}
|
|
21015
|
-
if (!(0,
|
|
21591
|
+
if (!(0, import_node_fs36.existsSync)(VERITY_DIR)) {
|
|
21016
21592
|
process.exit(0);
|
|
21017
21593
|
}
|
|
21018
21594
|
let sessionId = opts.sessionId;
|
|
@@ -21052,7 +21628,7 @@ async function readStdin() {
|
|
|
21052
21628
|
}
|
|
21053
21629
|
|
|
21054
21630
|
// src/commands/review.ts
|
|
21055
|
-
var
|
|
21631
|
+
var import_node_fs37 = require("node:fs");
|
|
21056
21632
|
function registerReviewCommand(program2) {
|
|
21057
21633
|
program2.command("review").description("Run on-demand Verity analysis (advisory, never blocks)").requiredOption("--files <paths>", "Comma-separated file list").option("--changed <paths>", "Subset of --files that were modified").option("--intent <text>", "User intent description (max 2000 chars)").option("--specs <paths>", "Comma-separated spec file paths").option("--json", "Output raw JSON response").action(async (opts) => {
|
|
21058
21634
|
const globals = program2.opts();
|
|
@@ -21071,7 +21647,7 @@ async function runReview(opts, globals) {
|
|
|
21071
21647
|
const securityFiles = filterSecurity(allFiles);
|
|
21072
21648
|
let staticResults;
|
|
21073
21649
|
if (isCodacyAvailable()) {
|
|
21074
|
-
const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0,
|
|
21650
|
+
const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs37.existsSync)(f) || resolveFile(f) !== null);
|
|
21075
21651
|
staticResults = runCodacyAnalysis(scannable);
|
|
21076
21652
|
} else {
|
|
21077
21653
|
staticResults = {
|
|
@@ -21097,10 +21673,10 @@ async function runReview(opts, globals) {
|
|
|
21097
21673
|
const specPaths = opts.specs.split(",").map((f) => f.trim()).filter(Boolean);
|
|
21098
21674
|
specs = [];
|
|
21099
21675
|
for (const p of specPaths) {
|
|
21100
|
-
if (!(0,
|
|
21676
|
+
if (!(0, import_node_fs37.existsSync)(p)) continue;
|
|
21101
21677
|
try {
|
|
21102
|
-
const { readFileSync:
|
|
21103
|
-
const content =
|
|
21678
|
+
const { readFileSync: readFileSync24 } = await import("node:fs");
|
|
21679
|
+
const content = readFileSync24(p, "utf-8");
|
|
21104
21680
|
specs.push({ path: p, content: content.slice(0, 10240) });
|
|
21105
21681
|
} catch {
|
|
21106
21682
|
}
|
|
@@ -21157,7 +21733,7 @@ async function runReview(opts, globals) {
|
|
|
21157
21733
|
}
|
|
21158
21734
|
|
|
21159
21735
|
// src/commands/guard.ts
|
|
21160
|
-
var
|
|
21736
|
+
var import_node_fs38 = require("node:fs");
|
|
21161
21737
|
var import_node_path27 = require("node:path");
|
|
21162
21738
|
var GUARD_BLOCK_CAP = 2;
|
|
21163
21739
|
var GUARD_ITER_FILE = (0, import_node_path27.join)(VERITY_DIR, ".guard-iteration");
|
|
@@ -21205,7 +21781,7 @@ function readPreToolUseStdin() {
|
|
|
21205
21781
|
}
|
|
21206
21782
|
function readIterMap() {
|
|
21207
21783
|
try {
|
|
21208
|
-
const raw = JSON.parse((0,
|
|
21784
|
+
const raw = JSON.parse((0, import_node_fs38.readFileSync)(GUARD_ITER_FILE, "utf-8"));
|
|
21209
21785
|
if (raw && typeof raw === "object") {
|
|
21210
21786
|
if (typeof raw.moment === "string" && typeof raw.count === "number") {
|
|
21211
21787
|
return { [raw.moment]: raw.count };
|
|
@@ -21225,10 +21801,10 @@ function readIter(moment) {
|
|
|
21225
21801
|
}
|
|
21226
21802
|
function writeIter(moment, count) {
|
|
21227
21803
|
try {
|
|
21228
|
-
(0,
|
|
21804
|
+
(0, import_node_fs38.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
21229
21805
|
const map = readIterMap();
|
|
21230
21806
|
map[moment] = count;
|
|
21231
|
-
(0,
|
|
21807
|
+
(0, import_node_fs38.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
|
|
21232
21808
|
} catch {
|
|
21233
21809
|
}
|
|
21234
21810
|
}
|
|
@@ -21238,10 +21814,10 @@ function resetIter(moment) {
|
|
|
21238
21814
|
if (!(moment in map)) return;
|
|
21239
21815
|
delete map[moment];
|
|
21240
21816
|
if (Object.keys(map).length === 0) {
|
|
21241
|
-
if ((0,
|
|
21817
|
+
if ((0, import_node_fs38.existsSync)(GUARD_ITER_FILE)) (0, import_node_fs38.unlinkSync)(GUARD_ITER_FILE);
|
|
21242
21818
|
} else {
|
|
21243
|
-
(0,
|
|
21244
|
-
(0,
|
|
21819
|
+
(0, import_node_fs38.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
21820
|
+
(0, import_node_fs38.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
|
|
21245
21821
|
}
|
|
21246
21822
|
} catch {
|
|
21247
21823
|
}
|
|
@@ -21302,16 +21878,17 @@ function extractStatedIntent(moment, command, pushedMessages = null) {
|
|
|
21302
21878
|
const text = moment === "pre-commit" ? parseCommitMessage(command) : parsePrIntent(command) ?? pushedMessages;
|
|
21303
21879
|
return isSubstantiveIntent(text) ? text : null;
|
|
21304
21880
|
}
|
|
21305
|
-
function hasBlockingFinding(response) {
|
|
21881
|
+
function hasBlockingFinding(response, sentFiles) {
|
|
21306
21882
|
const findings = response.findings ?? [];
|
|
21307
|
-
|
|
21883
|
+
const sent = sentFiles ? new Set(sentFiles.map((p) => p.replace(/\\/g, "/"))) : null;
|
|
21884
|
+
return findings.some((f) => f.scope !== "pre-existing" && ["critical", "high"].includes((f.severity ?? "").toLowerCase()) && (sent === null || typeof f.file === "string" && sent.has(f.file.replace(/\\/g, "/"))));
|
|
21308
21885
|
}
|
|
21309
21886
|
function buildGuardRequest(moment, files, codeDelta, iter, sessionId, statedIntent, coverageTelemetry) {
|
|
21310
21887
|
const analyzable = filterAnalyzable(files);
|
|
21311
21888
|
const securityFiles = filterSecurity(files);
|
|
21312
21889
|
let staticResults;
|
|
21313
21890
|
if (isCodacyAvailable()) {
|
|
21314
|
-
const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0,
|
|
21891
|
+
const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0, import_node_fs38.existsSync)(f) ? f : resolveFile(f)).filter((f) => f !== null);
|
|
21315
21892
|
staticResults = runCodacyAnalysis(scannable);
|
|
21316
21893
|
} else {
|
|
21317
21894
|
staticResults = { tool: "@codacy/analysis-cli", findings: [], summary: { total_findings: 0, by_severity: {}, tools_run: [] } };
|
|
@@ -21333,13 +21910,11 @@ function buildGuardRequest(moment, files, codeDelta, iter, sessionId, statedInte
|
|
|
21333
21910
|
}
|
|
21334
21911
|
};
|
|
21335
21912
|
if (coverageTelemetry) requestBody.coverage_telemetry = coverageTelemetry;
|
|
21336
|
-
const
|
|
21337
|
-
|
|
21338
|
-
if (specs.length > 0 || plans.length > 0 || statedIntent) {
|
|
21913
|
+
const docs = discoverGuardDocs(files);
|
|
21914
|
+
if (docs.length > 0 || statedIntent) {
|
|
21339
21915
|
const intentContext = {};
|
|
21340
21916
|
if (statedIntent) intentContext.user_prompt = statedIntent;
|
|
21341
|
-
if (
|
|
21342
|
-
if (plans.length > 0) intentContext.plans = plans;
|
|
21917
|
+
if (docs.length > 0) intentContext.plans = docs;
|
|
21343
21918
|
requestBody.intent_context = intentContext;
|
|
21344
21919
|
}
|
|
21345
21920
|
return requestBody;
|
|
@@ -21420,7 +21995,7 @@ async function runGuard(opts, globals) {
|
|
|
21420
21995
|
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
21421
21996
|
if (!tokenResult.ok || !urlResult.ok) process.exit(0);
|
|
21422
21997
|
logEvent("guard_frame", { moment, ...frameTelemetry(frame, range) });
|
|
21423
|
-
const codeDelta = collectCodeDelta(files);
|
|
21998
|
+
const codeDelta = collectCodeDelta(files, GIT_MOMENT_BUDGET);
|
|
21424
21999
|
if (codeDelta.total_files === 0) process.exit(0);
|
|
21425
22000
|
const statedIntent = extractStatedIntent(
|
|
21426
22001
|
moment,
|
|
@@ -21446,6 +22021,7 @@ async function runGuard(opts, globals) {
|
|
|
21446
22021
|
excluded: codeDelta.excluded.map((e) => ({ path: e.path, reason: e.reason }))
|
|
21447
22022
|
};
|
|
21448
22023
|
logToFileOnly(coverageBlock(coverage));
|
|
22024
|
+
const reviewStart = Date.now();
|
|
21449
22025
|
const result = await analyzeRequest({
|
|
21450
22026
|
serviceUrl: urlResult.data,
|
|
21451
22027
|
token: tokenResult.data.token,
|
|
@@ -21454,6 +22030,7 @@ async function runGuard(opts, globals) {
|
|
|
21454
22030
|
timeout: 29e4,
|
|
21455
22031
|
cmd: "guard"
|
|
21456
22032
|
});
|
|
22033
|
+
const reviewSecs = Math.max(1, Math.round((Date.now() - reviewStart) / 1e3));
|
|
21457
22034
|
if (!result.ok) {
|
|
21458
22035
|
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." : "";
|
|
21459
22036
|
emitAllowNotice(
|
|
@@ -21466,9 +22043,9 @@ async function runGuard(opts, globals) {
|
|
|
21466
22043
|
const decision = response.gate_decision ?? "(unrecognised)";
|
|
21467
22044
|
const viewUrl = response.view_url ?? "";
|
|
21468
22045
|
const link = viewUrl ? ` \u2014 ${viewUrl}` : "";
|
|
21469
|
-
const covLine = coverageSummary(coverage)
|
|
22046
|
+
const covLine = `${coverageSummary(coverage)} in ${reviewSecs}s`;
|
|
21470
22047
|
const covDetail = coverageBlock(coverage);
|
|
21471
|
-
if (decision === "FAIL" && hasBlockingFinding(response)) {
|
|
22048
|
+
if (decision === "FAIL" && hasBlockingFinding(response, codeDelta.files.map((f) => f.path))) {
|
|
21472
22049
|
writeIter(moment, iter + 1);
|
|
21473
22050
|
writeBlockMessage(moment, response, covDetail);
|
|
21474
22051
|
process.exit(2);
|
|
@@ -21535,18 +22112,167 @@ function writeBlockMessage(moment, response, covDetail) {
|
|
|
21535
22112
|
|
|
21536
22113
|
`);
|
|
21537
22114
|
process.stderr.write(`${BOLD}Fix these, then re-run the ${verb}.${NC}
|
|
22115
|
+
`);
|
|
22116
|
+
process.stderr.write(`${DIM}Accepted risk? Record it: verity waive <pattern-id> --file <path> --reason "\u2026" (voids automatically when the file changes).${NC}
|
|
21538
22117
|
`);
|
|
21539
22118
|
}
|
|
21540
22119
|
|
|
22120
|
+
// src/commands/ignore.ts
|
|
22121
|
+
function registerIgnoreCommand(program2) {
|
|
22122
|
+
const ignore = program2.command("ignore").description("Declare the next turn (or a short window) as housekeeping \u2014 no review needed").option("--turn", "Cover the next turn only (the default)").option("--for <duration>", "Cover a window: 30m, 45s, 1h (max 60m)").option("--reason <reason>", "Why this window needs no review \u2014 required, and recorded").option("--agent", "Mark the declaration as agent-invoked (a ledger label, not a permission)").option("--session-id <id>", "Session id (defaults to $CLAUDE_SESSION_ID)").option("--json", "Output raw JSON").action(async (opts) => {
|
|
22123
|
+
const globals = program2.opts();
|
|
22124
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
22125
|
+
const reason = opts.reason?.trim();
|
|
22126
|
+
if (!reason) {
|
|
22127
|
+
printError(
|
|
22128
|
+
'A reason is required: verity ignore --turn --reason "pulling latest before starting".\nIt is recorded with the declaration and is the only trace a skipped turn leaves.'
|
|
22129
|
+
);
|
|
22130
|
+
process.exit(1);
|
|
22131
|
+
}
|
|
22132
|
+
if (opts.for && opts.turn) {
|
|
22133
|
+
printError("Use either --turn or --for, not both \u2014 they are two different windows.");
|
|
22134
|
+
process.exit(1);
|
|
22135
|
+
}
|
|
22136
|
+
let scope2 = "turn";
|
|
22137
|
+
let ttl = TURN_FUSE_SECONDS;
|
|
22138
|
+
if (opts.for) {
|
|
22139
|
+
const parsed = parseDuration(opts.for);
|
|
22140
|
+
if (!parsed.ok) {
|
|
22141
|
+
printError(parsed.error);
|
|
22142
|
+
process.exit(1);
|
|
22143
|
+
}
|
|
22144
|
+
scope2 = "window";
|
|
22145
|
+
ttl = parsed.seconds;
|
|
22146
|
+
}
|
|
22147
|
+
const tokenResult = await resolveToken(globals.token);
|
|
22148
|
+
const token = tokenResult.ok ? tokenResult.data.token : void 0;
|
|
22149
|
+
const sessionId = opts.sessionId || process.env.CLAUDE_SESSION_ID || void 0;
|
|
22150
|
+
const keys = ignoreStateKeys(token, sessionId);
|
|
22151
|
+
const existing = resolveIgnoreState(keys);
|
|
22152
|
+
const spent = existing?.state.spent ?? 0;
|
|
22153
|
+
const writeKey = existing?.key ?? keys[0];
|
|
22154
|
+
if (spent >= IGNORE_BUDGET) {
|
|
22155
|
+
const msg = `Ignore budget spent for this session (${spent}/${IGNORE_BUDGET} declarations). The next turn will be reviewed normally. The budget is per session \u2014 it is what stops an ignore from becoming a standing mute.`;
|
|
22156
|
+
if (opts.json) {
|
|
22157
|
+
printJson({ declared: false, reason_refused: "budget-spent", spent, budget: IGNORE_BUDGET });
|
|
22158
|
+
} else {
|
|
22159
|
+
printWarn(msg);
|
|
22160
|
+
}
|
|
22161
|
+
logEvent("ignore_refused", { why: "budget-spent", spent, budget: IGNORE_BUDGET });
|
|
22162
|
+
process.exit(0);
|
|
22163
|
+
}
|
|
22164
|
+
const declaration = {
|
|
22165
|
+
scope: scope2,
|
|
22166
|
+
origin: opts.agent ? "agent" : "user",
|
|
22167
|
+
reason,
|
|
22168
|
+
at: now,
|
|
22169
|
+
expires: now + ttl
|
|
22170
|
+
};
|
|
22171
|
+
writeIgnoreState({ v: 1, active: declaration, spent: spent + 1 }, writeKey);
|
|
22172
|
+
logEvent("ignore_declared", {
|
|
22173
|
+
scope: scope2,
|
|
22174
|
+
origin: declaration.origin,
|
|
22175
|
+
ttl_seconds: ttl,
|
|
22176
|
+
spent: spent + 1,
|
|
22177
|
+
budget: IGNORE_BUDGET
|
|
22178
|
+
});
|
|
22179
|
+
if (opts.json) {
|
|
22180
|
+
printJson({
|
|
22181
|
+
declared: true,
|
|
22182
|
+
scope: scope2,
|
|
22183
|
+
origin: declaration.origin,
|
|
22184
|
+
reason,
|
|
22185
|
+
expires_at: new Date(declaration.expires * 1e3).toISOString(),
|
|
22186
|
+
spent: spent + 1,
|
|
22187
|
+
budget: IGNORE_BUDGET
|
|
22188
|
+
});
|
|
22189
|
+
return;
|
|
22190
|
+
}
|
|
22191
|
+
const window = scope2 === "turn" ? "the next turn" : `the next ${describeRemaining(declaration, now).replace(" left", "")}`;
|
|
22192
|
+
printInfo(`Verity will skip ${window} \u2014 "${reason}" (${spent + 1}/${IGNORE_BUDGET} this session).`);
|
|
22193
|
+
printInfo("It covers turns that author nothing. If anything is written, the declaration voids and the review runs.");
|
|
22194
|
+
});
|
|
22195
|
+
ignore.command("clear").description("Cancel the active declaration (the spent budget is not refunded)").option("--session-id <id>", "Session id (defaults to $CLAUDE_SESSION_ID)").action(async (opts) => {
|
|
22196
|
+
const globals = program2.opts();
|
|
22197
|
+
const tokenResult = await resolveToken(globals.token);
|
|
22198
|
+
const token = tokenResult.ok ? tokenResult.data.token : void 0;
|
|
22199
|
+
const sessionId = opts.sessionId || process.env.CLAUDE_SESSION_ID || void 0;
|
|
22200
|
+
const found = resolveIgnoreState(ignoreStateKeys(token, sessionId));
|
|
22201
|
+
const active = resolveActive(found?.state ?? null, Math.floor(Date.now() / 1e3));
|
|
22202
|
+
if (!found || !active) {
|
|
22203
|
+
printInfo("No active ignore declaration.");
|
|
22204
|
+
return;
|
|
22205
|
+
}
|
|
22206
|
+
clearActiveDeclaration(found.key);
|
|
22207
|
+
logEvent("ignore_cleared", { scope: active.scope, origin: active.origin });
|
|
22208
|
+
printInfo(`Cleared: "${active.reason}". The next turn will be reviewed normally.`);
|
|
22209
|
+
});
|
|
22210
|
+
}
|
|
22211
|
+
|
|
22212
|
+
// src/commands/waive.ts
|
|
22213
|
+
var import_node_crypto12 = require("node:crypto");
|
|
22214
|
+
var import_node_fs39 = require("node:fs");
|
|
22215
|
+
function registerWaiveCommand(program2) {
|
|
22216
|
+
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) => {
|
|
22217
|
+
const globals = program2.opts();
|
|
22218
|
+
try {
|
|
22219
|
+
process.chdir(repoRoot());
|
|
22220
|
+
} catch {
|
|
22221
|
+
}
|
|
22222
|
+
if (opts.reason.trim().length < 20) {
|
|
22223
|
+
printError("A waive needs a real reason (>= 20 chars) \u2014 name the human disposition it records.");
|
|
22224
|
+
process.exit(1);
|
|
22225
|
+
}
|
|
22226
|
+
const tokenResult = await resolveToken(globals.token);
|
|
22227
|
+
if (!tokenResult.ok) {
|
|
22228
|
+
printError(tokenResult.error);
|
|
22229
|
+
process.exit(1);
|
|
22230
|
+
}
|
|
22231
|
+
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
22232
|
+
if (!urlResult.ok) {
|
|
22233
|
+
printError(urlResult.error);
|
|
22234
|
+
process.exit(1);
|
|
22235
|
+
}
|
|
22236
|
+
const body = {
|
|
22237
|
+
pattern_id: patternId,
|
|
22238
|
+
reason: opts.reason.trim()
|
|
22239
|
+
};
|
|
22240
|
+
if (opts.file) {
|
|
22241
|
+
body.file = opts.file;
|
|
22242
|
+
try {
|
|
22243
|
+
body.file_sha256 = (0, import_node_crypto12.createHash)("sha256").update((0, import_node_fs39.readFileSync)(opts.file)).digest("hex");
|
|
22244
|
+
} catch {
|
|
22245
|
+
printError(`Cannot read ${opts.file} \u2014 run from the repo root, or omit --file to waive by pattern.`);
|
|
22246
|
+
process.exit(1);
|
|
22247
|
+
}
|
|
22248
|
+
}
|
|
22249
|
+
const result = await apiRequest({
|
|
22250
|
+
method: "POST",
|
|
22251
|
+
path: "/feedback/waive",
|
|
22252
|
+
serviceUrl: urlResult.data,
|
|
22253
|
+
token: tokenResult.data.token,
|
|
22254
|
+
body,
|
|
22255
|
+
verbose: globals.verbose
|
|
22256
|
+
});
|
|
22257
|
+
if (!result.ok) {
|
|
22258
|
+
printError(`Couldn't record the waive: ${result.error}`);
|
|
22259
|
+
process.exit(1);
|
|
22260
|
+
}
|
|
22261
|
+
logEvent("waive_recorded", { pattern: patternId, file: opts.file ?? null, waived: result.data.waived });
|
|
22262
|
+
printInfo(`Waived ${result.data.waived} open statement(s) for '${patternId}'${opts.file ? ` on ${opts.file}` : ""}.`);
|
|
22263
|
+
printInfo(result.data.note);
|
|
22264
|
+
});
|
|
22265
|
+
}
|
|
22266
|
+
|
|
21541
22267
|
// src/commands/init.ts
|
|
21542
|
-
var
|
|
22268
|
+
var import_node_fs41 = require("node:fs");
|
|
21543
22269
|
var import_promises13 = require("node:fs/promises");
|
|
21544
22270
|
var import_node_path29 = require("node:path");
|
|
21545
22271
|
var import_node_child_process11 = require("node:child_process");
|
|
21546
22272
|
var readline2 = __toESM(require("node:readline/promises"));
|
|
21547
22273
|
|
|
21548
22274
|
// src/commands/migrate.ts
|
|
21549
|
-
var
|
|
22275
|
+
var import_node_fs40 = require("node:fs");
|
|
21550
22276
|
var import_node_path28 = require("node:path");
|
|
21551
22277
|
var import_node_child_process10 = require("node:child_process");
|
|
21552
22278
|
|
|
@@ -21678,10 +22404,10 @@ async function runMigration(opts = {}) {
|
|
|
21678
22404
|
function migrateProjectDir(root, actions) {
|
|
21679
22405
|
const gateDir = (0, import_node_path28.join)(root, ".gate");
|
|
21680
22406
|
const verityDir = (0, import_node_path28.join)(root, ".verity");
|
|
21681
|
-
if ((0,
|
|
22407
|
+
if ((0, import_node_fs40.existsSync)(gateDir) && !(0, import_node_fs40.existsSync)(verityDir)) {
|
|
21682
22408
|
return migrateProjectDirRename(root, gateDir, verityDir, actions);
|
|
21683
22409
|
}
|
|
21684
|
-
if ((0,
|
|
22410
|
+
if ((0, import_node_fs40.existsSync)(gateDir) && (0, import_node_fs40.existsSync)(verityDir)) {
|
|
21685
22411
|
return migrateProjectDirCarry(gateDir, verityDir, actions);
|
|
21686
22412
|
}
|
|
21687
22413
|
return false;
|
|
@@ -21702,13 +22428,13 @@ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
|
|
|
21702
22428
|
}
|
|
21703
22429
|
}
|
|
21704
22430
|
if (moved) {
|
|
21705
|
-
if ((0,
|
|
22431
|
+
if ((0, import_node_fs40.existsSync)(gateDir)) {
|
|
21706
22432
|
const carried = carryLegacyContents(gateDir, verityDir);
|
|
21707
22433
|
if (carried > 0) {
|
|
21708
22434
|
actions.push(`Carried ${carried} untracked legacy file(s) from .gate/ into .verity/`);
|
|
21709
22435
|
}
|
|
21710
22436
|
try {
|
|
21711
|
-
(0,
|
|
22437
|
+
(0, import_node_fs40.rmSync)(gateDir, { recursive: true, force: true });
|
|
21712
22438
|
} catch {
|
|
21713
22439
|
}
|
|
21714
22440
|
}
|
|
@@ -21724,7 +22450,7 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
|
|
|
21724
22450
|
actions.push(`Carried ${carried} legacy file(s) from .gate/ into .verity/`);
|
|
21725
22451
|
}
|
|
21726
22452
|
try {
|
|
21727
|
-
(0,
|
|
22453
|
+
(0, import_node_fs40.rmSync)(gateDir, { recursive: true, force: true });
|
|
21728
22454
|
} catch {
|
|
21729
22455
|
}
|
|
21730
22456
|
return carried > 0;
|
|
@@ -21733,9 +22459,9 @@ function migrateGlobalCredentials(home, actions) {
|
|
|
21733
22459
|
if (!home) return;
|
|
21734
22460
|
const gateCreds = (0, import_node_path28.join)(home, ".gate", "credentials");
|
|
21735
22461
|
const verityCreds = (0, import_node_path28.join)(home, ".verity", "credentials");
|
|
21736
|
-
if (!(0,
|
|
21737
|
-
if (!(0,
|
|
21738
|
-
(0,
|
|
22462
|
+
if (!(0, import_node_fs40.existsSync)(gateCreds)) return;
|
|
22463
|
+
if (!(0, import_node_fs40.existsSync)(verityCreds)) {
|
|
22464
|
+
(0, import_node_fs40.mkdirSync)((0, import_node_path28.join)(home, ".verity"), { recursive: true });
|
|
21739
22465
|
moveFile(gateCreds, verityCreds);
|
|
21740
22466
|
actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
|
|
21741
22467
|
return;
|
|
@@ -21758,7 +22484,7 @@ async function migrateLegacyHooks(root, actions) {
|
|
|
21758
22484
|
}
|
|
21759
22485
|
async function migrateClaudeMd(root, actions) {
|
|
21760
22486
|
const claudeMd = (0, import_node_path28.join)(root, "CLAUDE.md");
|
|
21761
|
-
const hadLegacyBlock = (0,
|
|
22487
|
+
const hadLegacyBlock = (0, import_node_fs40.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
|
|
21762
22488
|
if (!hadLegacyBlock) return;
|
|
21763
22489
|
try {
|
|
21764
22490
|
await ensureClaudeMdPointer(root);
|
|
@@ -21770,7 +22496,7 @@ async function migrateClaudeMd(root, actions) {
|
|
|
21770
22496
|
function migrateStandardFile(root, actions) {
|
|
21771
22497
|
const gateMd = (0, import_node_path28.join)(root, "GATE.md");
|
|
21772
22498
|
const verityMd = (0, import_node_path28.join)(root, "VERITY.md");
|
|
21773
|
-
if (!(0,
|
|
22499
|
+
if (!(0, import_node_fs40.existsSync)(gateMd) || (0, import_node_fs40.existsSync)(verityMd)) return;
|
|
21774
22500
|
let moved = false;
|
|
21775
22501
|
if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
|
|
21776
22502
|
try {
|
|
@@ -21782,12 +22508,12 @@ function migrateStandardFile(root, actions) {
|
|
|
21782
22508
|
if (!moved) moveFile(gateMd, verityMd);
|
|
21783
22509
|
const content = readFileSyncSafe(verityMd);
|
|
21784
22510
|
const refreshed = content.split("GATE.md").join("VERITY.md");
|
|
21785
|
-
if (refreshed !== content) (0,
|
|
22511
|
+
if (refreshed !== content) (0, import_node_fs40.writeFileSync)(verityMd, refreshed);
|
|
21786
22512
|
actions.push("Renamed GATE.md \u2192 VERITY.md");
|
|
21787
22513
|
}
|
|
21788
22514
|
async function migrateTelemetryHeaders(root, actions) {
|
|
21789
22515
|
const file = (0, import_node_path28.join)(root, ".claude", "settings.local.json");
|
|
21790
|
-
if (!(0,
|
|
22516
|
+
if (!(0, import_node_fs40.existsSync)(file)) return;
|
|
21791
22517
|
let settings;
|
|
21792
22518
|
try {
|
|
21793
22519
|
settings = JSON.parse(readFileSyncSafe(file) || "{}");
|
|
@@ -21835,14 +22561,14 @@ function mergeGlobalCredentials(gateCreds, verityCreds) {
|
|
|
21835
22561
|
}
|
|
21836
22562
|
if (toAppend.length > 0) {
|
|
21837
22563
|
const sep2 = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
|
|
21838
|
-
(0,
|
|
22564
|
+
(0, import_node_fs40.writeFileSync)(verityCreds, verityContent + sep2 + toAppend.join("\n") + "\n");
|
|
21839
22565
|
}
|
|
21840
|
-
(0,
|
|
22566
|
+
(0, import_node_fs40.rmSync)(gateCreds, { force: true });
|
|
21841
22567
|
return toAppend.length;
|
|
21842
22568
|
}
|
|
21843
22569
|
function readFileSyncSafe(path) {
|
|
21844
22570
|
try {
|
|
21845
|
-
return (0,
|
|
22571
|
+
return (0, import_node_fs40.readFileSync)(path, "utf-8");
|
|
21846
22572
|
} catch {
|
|
21847
22573
|
return "";
|
|
21848
22574
|
}
|
|
@@ -21857,35 +22583,35 @@ function hasStagedChanges(root) {
|
|
|
21857
22583
|
}
|
|
21858
22584
|
function moveDir(from, to) {
|
|
21859
22585
|
try {
|
|
21860
|
-
(0,
|
|
22586
|
+
(0, import_node_fs40.renameSync)(from, to);
|
|
21861
22587
|
} catch (err) {
|
|
21862
22588
|
if (err.code !== "EXDEV") throw err;
|
|
21863
|
-
(0,
|
|
21864
|
-
(0,
|
|
22589
|
+
(0, import_node_fs40.cpSync)(from, to, { recursive: true });
|
|
22590
|
+
(0, import_node_fs40.rmSync)(from, { recursive: true, force: true });
|
|
21865
22591
|
}
|
|
21866
22592
|
}
|
|
21867
22593
|
function moveFile(from, to) {
|
|
21868
22594
|
try {
|
|
21869
|
-
(0,
|
|
22595
|
+
(0, import_node_fs40.renameSync)(from, to);
|
|
21870
22596
|
} catch (err) {
|
|
21871
22597
|
if (err.code !== "EXDEV") throw err;
|
|
21872
|
-
(0,
|
|
21873
|
-
(0,
|
|
22598
|
+
(0, import_node_fs40.cpSync)(from, to);
|
|
22599
|
+
(0, import_node_fs40.rmSync)(from, { force: true });
|
|
21874
22600
|
}
|
|
21875
22601
|
}
|
|
21876
22602
|
function carryLegacyContents(gateDir, verityDir) {
|
|
21877
22603
|
let copied = 0;
|
|
21878
22604
|
const walk = (relDir) => {
|
|
21879
22605
|
const srcDir = (0, import_node_path28.join)(gateDir, relDir);
|
|
21880
|
-
for (const entry of (0,
|
|
22606
|
+
for (const entry of (0, import_node_fs40.readdirSync)(srcDir)) {
|
|
21881
22607
|
const rel = relDir ? (0, import_node_path28.join)(relDir, entry) : entry;
|
|
21882
22608
|
const src = (0, import_node_path28.join)(gateDir, rel);
|
|
21883
22609
|
const dest = (0, import_node_path28.join)(verityDir, rel);
|
|
21884
|
-
if ((0,
|
|
22610
|
+
if ((0, import_node_fs40.statSync)(src).isDirectory()) {
|
|
21885
22611
|
walk(rel);
|
|
21886
|
-
} else if (!(0,
|
|
21887
|
-
(0,
|
|
21888
|
-
(0,
|
|
22612
|
+
} else if (!(0, import_node_fs40.existsSync)(dest)) {
|
|
22613
|
+
(0, import_node_fs40.mkdirSync)((0, import_node_path28.dirname)(dest), { recursive: true });
|
|
22614
|
+
(0, import_node_fs40.cpSync)(src, dest);
|
|
21889
22615
|
copied++;
|
|
21890
22616
|
}
|
|
21891
22617
|
}
|
|
@@ -21896,20 +22622,20 @@ function carryLegacyContents(gateDir, verityDir) {
|
|
|
21896
22622
|
async function needsMigration(root = repoRoot()) {
|
|
21897
22623
|
const gateDir = (0, import_node_path28.join)(root, ".gate");
|
|
21898
22624
|
const verityDir = (0, import_node_path28.join)(root, ".verity");
|
|
21899
|
-
if ((0,
|
|
21900
|
-
if ((0,
|
|
21901
|
-
if ((0,
|
|
22625
|
+
if ((0, import_node_fs40.existsSync)(gateDir) && !(0, import_node_fs40.existsSync)(verityDir)) return true;
|
|
22626
|
+
if ((0, import_node_fs40.existsSync)(gateDir) && (0, import_node_fs40.existsSync)(verityDir)) {
|
|
22627
|
+
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"))) {
|
|
21902
22628
|
return true;
|
|
21903
22629
|
}
|
|
21904
|
-
if ((0,
|
|
22630
|
+
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"))) {
|
|
21905
22631
|
return true;
|
|
21906
22632
|
}
|
|
21907
22633
|
}
|
|
21908
22634
|
const claudeMd = (0, import_node_path28.join)(root, "CLAUDE.md");
|
|
21909
|
-
if ((0,
|
|
22635
|
+
if ((0, import_node_fs40.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
|
|
21910
22636
|
return true;
|
|
21911
22637
|
}
|
|
21912
|
-
if ((0,
|
|
22638
|
+
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"))) {
|
|
21913
22639
|
return true;
|
|
21914
22640
|
}
|
|
21915
22641
|
if (await hasLegacyHooksAt(root)) return true;
|
|
@@ -22053,7 +22779,7 @@ function resolveDataDir() {
|
|
|
22053
22779
|
// local dev: running from repo root
|
|
22054
22780
|
];
|
|
22055
22781
|
for (const candidate of candidates) {
|
|
22056
|
-
if ((0,
|
|
22782
|
+
if ((0, import_node_fs41.existsSync)((0, import_node_path29.join)(candidate, "skills"))) {
|
|
22057
22783
|
return candidate;
|
|
22058
22784
|
}
|
|
22059
22785
|
}
|
|
@@ -22069,7 +22795,7 @@ function registerInitCommand(program2) {
|
|
|
22069
22795
|
program2.command("init").description("Initialize Verity in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
|
|
22070
22796
|
const force = opts.force ?? false;
|
|
22071
22797
|
const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
|
|
22072
|
-
const isProject = projectMarkers.some((m) => (0,
|
|
22798
|
+
const isProject = projectMarkers.some((m) => (0, import_node_fs41.existsSync)(m));
|
|
22073
22799
|
if (!isProject) {
|
|
22074
22800
|
printError("No project detected in the current directory.");
|
|
22075
22801
|
printInfo('Run "verity init" from your project root.');
|
|
@@ -22139,14 +22865,14 @@ function registerInitCommand(program2) {
|
|
|
22139
22865
|
for (const skill of skills) {
|
|
22140
22866
|
const src = (0, import_node_path29.join)(skillsSource, skill);
|
|
22141
22867
|
const dest = (0, import_node_path29.join)(skillsDest, skill);
|
|
22142
|
-
if (!(0,
|
|
22868
|
+
if (!(0, import_node_fs41.existsSync)(src)) {
|
|
22143
22869
|
printWarn(` Skill data not found: ${skill}`);
|
|
22144
22870
|
continue;
|
|
22145
22871
|
}
|
|
22146
|
-
if ((0,
|
|
22872
|
+
if ((0, import_node_fs41.existsSync)(dest) && !force) {
|
|
22147
22873
|
const srcSkill = (0, import_node_path29.join)(src, "SKILL.md");
|
|
22148
22874
|
const destSkill = (0, import_node_path29.join)(dest, "SKILL.md");
|
|
22149
|
-
if ((0,
|
|
22875
|
+
if ((0, import_node_fs41.existsSync)(destSkill)) {
|
|
22150
22876
|
try {
|
|
22151
22877
|
const srcContent = await (0, import_promises13.readFile)(srcSkill, "utf-8");
|
|
22152
22878
|
const destContent = await (0, import_promises13.readFile)(destSkill, "utf-8");
|
|
@@ -22224,7 +22950,7 @@ function registerInitCommand(program2) {
|
|
|
22224
22950
|
}
|
|
22225
22951
|
|
|
22226
22952
|
// src/commands/uninstall.ts
|
|
22227
|
-
var
|
|
22953
|
+
var import_node_fs42 = require("node:fs");
|
|
22228
22954
|
var import_node_path30 = require("node:path");
|
|
22229
22955
|
var SKILL_NAMES = [
|
|
22230
22956
|
"verity-setup",
|
|
@@ -22245,10 +22971,10 @@ function registerUninstallCommand(program2) {
|
|
|
22245
22971
|
const skillsRoot = projectPath(".claude/skills");
|
|
22246
22972
|
for (const name of SKILL_NAMES) {
|
|
22247
22973
|
const dir = (0, import_node_path30.join)(skillsRoot, name);
|
|
22248
|
-
if ((0,
|
|
22974
|
+
if ((0, import_node_fs42.existsSync)(dir)) {
|
|
22249
22975
|
actions.push({
|
|
22250
22976
|
label: `Remove .claude/skills/${name}/`,
|
|
22251
|
-
apply: () => (0,
|
|
22977
|
+
apply: () => (0, import_node_fs42.rmSync)(dir, { recursive: true, force: true })
|
|
22252
22978
|
});
|
|
22253
22979
|
}
|
|
22254
22980
|
}
|
|
@@ -22262,24 +22988,24 @@ function registerUninstallCommand(program2) {
|
|
|
22262
22988
|
});
|
|
22263
22989
|
}
|
|
22264
22990
|
const verityDir = projectPath(VERITY_DIR);
|
|
22265
|
-
if ((0,
|
|
22991
|
+
if ((0, import_node_fs42.existsSync)(verityDir)) {
|
|
22266
22992
|
actions.push({
|
|
22267
22993
|
label: `Remove ${VERITY_DIR}/`,
|
|
22268
|
-
apply: () => (0,
|
|
22994
|
+
apply: () => (0, import_node_fs42.rmSync)(verityDir, { recursive: true, force: true })
|
|
22269
22995
|
});
|
|
22270
22996
|
}
|
|
22271
22997
|
if (!keepVerityMd) {
|
|
22272
22998
|
const verityMd = projectPath(VERITY_MD_FILE);
|
|
22273
|
-
if ((0,
|
|
22999
|
+
if ((0, import_node_fs42.existsSync)(verityMd)) {
|
|
22274
23000
|
actions.push({
|
|
22275
23001
|
label: `Remove ${VERITY_MD_FILE}`,
|
|
22276
|
-
apply: () => (0,
|
|
23002
|
+
apply: () => (0, import_node_fs42.rmSync)(verityMd, { force: true })
|
|
22277
23003
|
});
|
|
22278
23004
|
}
|
|
22279
23005
|
}
|
|
22280
23006
|
const cleanupEmptyDir = (path) => {
|
|
22281
|
-
if ((0,
|
|
22282
|
-
(0,
|
|
23007
|
+
if ((0, import_node_fs42.existsSync)(path) && (0, import_node_fs42.statSync)(path).isDirectory() && (0, import_node_fs42.readdirSync)(path).length === 0) {
|
|
23008
|
+
(0, import_node_fs42.rmdirSync)(path);
|
|
22283
23009
|
}
|
|
22284
23010
|
};
|
|
22285
23011
|
actions.push({
|
|
@@ -22291,10 +23017,10 @@ function registerUninstallCommand(program2) {
|
|
|
22291
23017
|
});
|
|
22292
23018
|
const home = process.env.HOME ?? "";
|
|
22293
23019
|
const globalVerityDir = (0, import_node_path30.join)(home, ".verity");
|
|
22294
|
-
if (purgeGlobal && (0,
|
|
23020
|
+
if (purgeGlobal && (0, import_node_fs42.existsSync)(globalVerityDir)) {
|
|
22295
23021
|
actions.push({
|
|
22296
23022
|
label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
|
|
22297
|
-
apply: () => (0,
|
|
23023
|
+
apply: () => (0, import_node_fs42.rmSync)(globalVerityDir, { recursive: true, force: true })
|
|
22298
23024
|
});
|
|
22299
23025
|
}
|
|
22300
23026
|
if (actions.length === 0) {
|
|
@@ -22488,7 +23214,7 @@ function registerTaskCommands(program2) {
|
|
|
22488
23214
|
}
|
|
22489
23215
|
|
|
22490
23216
|
// src/commands/reset.ts
|
|
22491
|
-
var
|
|
23217
|
+
var import_node_fs43 = require("node:fs");
|
|
22492
23218
|
var import_node_path31 = require("node:path");
|
|
22493
23219
|
function registerResetCommand(program2) {
|
|
22494
23220
|
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) => {
|
|
@@ -22526,11 +23252,11 @@ function registerResetCommand(program2) {
|
|
|
22526
23252
|
}
|
|
22527
23253
|
const cacheDir = projectPath(CACHE_DIR);
|
|
22528
23254
|
let purged = 0;
|
|
22529
|
-
if ((0,
|
|
22530
|
-
for (const entry of (0,
|
|
23255
|
+
if ((0, import_node_fs43.existsSync)(cacheDir)) {
|
|
23256
|
+
for (const entry of (0, import_node_fs43.readdirSync)(cacheDir)) {
|
|
22531
23257
|
if (entry.startsWith("pending-")) {
|
|
22532
23258
|
try {
|
|
22533
|
-
(0,
|
|
23259
|
+
(0, import_node_fs43.unlinkSync)((0, import_node_path31.join)(cacheDir, entry));
|
|
22534
23260
|
purged++;
|
|
22535
23261
|
} catch {
|
|
22536
23262
|
}
|
|
@@ -22545,19 +23271,19 @@ function registerResetCommand(program2) {
|
|
|
22545
23271
|
projectPath(`${VERITY_DIR}/.last-analysis`)
|
|
22546
23272
|
];
|
|
22547
23273
|
for (const file of filesToClear) {
|
|
22548
|
-
if ((0,
|
|
23274
|
+
if ((0, import_node_fs43.existsSync)(file)) {
|
|
22549
23275
|
try {
|
|
22550
|
-
(0,
|
|
23276
|
+
(0, import_node_fs43.writeFileSync)(file, "");
|
|
22551
23277
|
} catch {
|
|
22552
23278
|
}
|
|
22553
23279
|
}
|
|
22554
23280
|
}
|
|
22555
23281
|
if (opts.all) {
|
|
22556
23282
|
const logsDir = projectPath(`${VERITY_DIR}/.logs`);
|
|
22557
|
-
if ((0,
|
|
22558
|
-
for (const entry of (0,
|
|
23283
|
+
if ((0, import_node_fs43.existsSync)(logsDir)) {
|
|
23284
|
+
for (const entry of (0, import_node_fs43.readdirSync)(logsDir)) {
|
|
22559
23285
|
try {
|
|
22560
|
-
(0,
|
|
23286
|
+
(0, import_node_fs43.unlinkSync)((0, import_node_path31.join)(logsDir, entry));
|
|
22561
23287
|
} catch {
|
|
22562
23288
|
}
|
|
22563
23289
|
}
|
|
@@ -22865,8 +23591,8 @@ function registerTelemetryCommands(program2) {
|
|
|
22865
23591
|
}
|
|
22866
23592
|
|
|
22867
23593
|
// src/cli.ts
|
|
22868
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.
|
|
22869
|
-
installStderrLog(actionCommand.name(), process.argv.slice(2), "0.
|
|
23594
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.31.0-experimental.2221878").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) => {
|
|
23595
|
+
installStderrLog(actionCommand.name(), process.argv.slice(2), "0.31.0-experimental.2221878");
|
|
22870
23596
|
setUserNamedServiceUrl(program.opts().serviceUrl);
|
|
22871
23597
|
try {
|
|
22872
23598
|
await foldLegacyLocalCredential();
|
|
@@ -22889,6 +23615,8 @@ registerAnalyzeCommand(program);
|
|
|
22889
23615
|
registerBaselineCommands(program);
|
|
22890
23616
|
registerReviewCommand(program);
|
|
22891
23617
|
registerGuardCommand(program);
|
|
23618
|
+
registerIgnoreCommand(program);
|
|
23619
|
+
registerWaiveCommand(program);
|
|
22892
23620
|
registerInitCommand(program);
|
|
22893
23621
|
registerUninstallCommand(program);
|
|
22894
23622
|
registerTaskCommands(program);
|