@codacy/verity-cli 0.30.1 → 0.31.0-experimental.6bc3b1b
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 +1134 -417
- 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;
|
|
@@ -16270,7 +16369,145 @@ async function readHookStdin() {
|
|
|
16270
16369
|
|
|
16271
16370
|
// src/commands/standard.ts
|
|
16272
16371
|
var import_promises9 = require("node:fs/promises");
|
|
16372
|
+
var import_node_fs20 = require("node:fs");
|
|
16273
16373
|
var import_yaml = __toESM(require_dist());
|
|
16374
|
+
|
|
16375
|
+
// src/lib/verityignore.ts
|
|
16376
|
+
var import_node_fs19 = require("node:fs");
|
|
16377
|
+
var EMPTY = { rules: [], securityOverlap: [], problems: [] };
|
|
16378
|
+
var SECURITY_PROBES = [
|
|
16379
|
+
".env",
|
|
16380
|
+
".env.local",
|
|
16381
|
+
".env.production",
|
|
16382
|
+
"config/.env",
|
|
16383
|
+
"services/api/.env",
|
|
16384
|
+
"package-lock.json",
|
|
16385
|
+
"yarn.lock",
|
|
16386
|
+
"pnpm-lock.yaml",
|
|
16387
|
+
"Cargo.lock",
|
|
16388
|
+
"go.sum",
|
|
16389
|
+
"Gemfile.lock",
|
|
16390
|
+
"Dockerfile",
|
|
16391
|
+
"docker/Dockerfile"
|
|
16392
|
+
];
|
|
16393
|
+
function isSecuritySensitive(path) {
|
|
16394
|
+
return SECURITY_PATTERNS.some((p) => p.test(path));
|
|
16395
|
+
}
|
|
16396
|
+
function compile(pattern) {
|
|
16397
|
+
let p = pattern;
|
|
16398
|
+
const dirOnly = p.endsWith("/");
|
|
16399
|
+
if (dirOnly) p = p.slice(0, -1);
|
|
16400
|
+
const anchored = p.includes("/");
|
|
16401
|
+
if (p.startsWith("/")) p = p.slice(1);
|
|
16402
|
+
const base = anchored ? p : `**/${p}`;
|
|
16403
|
+
const forms = dirOnly ? [`${base}/**/*`] : [base, `${base}/**`];
|
|
16404
|
+
const regexes = [];
|
|
16405
|
+
for (const f of forms) {
|
|
16406
|
+
try {
|
|
16407
|
+
regexes.push(globToRegex(f));
|
|
16408
|
+
} catch {
|
|
16409
|
+
}
|
|
16410
|
+
}
|
|
16411
|
+
if (regexes.length === 0) return () => false;
|
|
16412
|
+
return (path) => regexes.some((r) => r.test(path));
|
|
16413
|
+
}
|
|
16414
|
+
function parseVerityIgnore(content) {
|
|
16415
|
+
const rules = [];
|
|
16416
|
+
const problems = [];
|
|
16417
|
+
const lines = content.split("\n");
|
|
16418
|
+
for (let i = 0; i < lines.length; i++) {
|
|
16419
|
+
const lineNo = i + 1;
|
|
16420
|
+
let raw = lines[i];
|
|
16421
|
+
raw = raw.replace(/(?<!\\)\s+$/, "");
|
|
16422
|
+
if (raw.length === 0) continue;
|
|
16423
|
+
if (raw.startsWith("#")) continue;
|
|
16424
|
+
let negated = false;
|
|
16425
|
+
if (raw.startsWith("!")) {
|
|
16426
|
+
negated = true;
|
|
16427
|
+
raw = raw.slice(1);
|
|
16428
|
+
} else if (raw.startsWith("\\!") || raw.startsWith("\\#")) {
|
|
16429
|
+
raw = raw.slice(1);
|
|
16430
|
+
}
|
|
16431
|
+
if (raw.length === 0) {
|
|
16432
|
+
problems.push(`line ${lineNo}: "!" with no pattern after it`);
|
|
16433
|
+
continue;
|
|
16434
|
+
}
|
|
16435
|
+
if (raw === "**" || raw === "*" || raw === "/" || raw === "**/*") {
|
|
16436
|
+
problems.push(
|
|
16437
|
+
`line ${lineNo}: "${raw}" would exclude the whole repository from review \u2014 refused. List the directories you mean instead.`
|
|
16438
|
+
);
|
|
16439
|
+
continue;
|
|
16440
|
+
}
|
|
16441
|
+
rules.push({ raw, line: lineNo, negated, test: compile(raw) });
|
|
16442
|
+
}
|
|
16443
|
+
const byRule = /* @__PURE__ */ new Map();
|
|
16444
|
+
for (const probe of SECURITY_PROBES) {
|
|
16445
|
+
let responsible = null;
|
|
16446
|
+
for (const rule of rules) {
|
|
16447
|
+
if (rule.test(probe)) responsible = rule.negated ? null : rule;
|
|
16448
|
+
}
|
|
16449
|
+
if (!responsible) continue;
|
|
16450
|
+
const entry = byRule.get(responsible.line) ?? { raw: responsible.raw, line: responsible.line, hides: [] };
|
|
16451
|
+
entry.hides.push(probe);
|
|
16452
|
+
byRule.set(responsible.line, entry);
|
|
16453
|
+
}
|
|
16454
|
+
const securityOverlap = [...byRule.values()].sort((a, b) => a.line - b.line);
|
|
16455
|
+
return { rules, securityOverlap, problems };
|
|
16456
|
+
}
|
|
16457
|
+
function decide(rules, path) {
|
|
16458
|
+
let excluded = false;
|
|
16459
|
+
for (const rule of rules) {
|
|
16460
|
+
if (rule.test(path)) excluded = !rule.negated;
|
|
16461
|
+
}
|
|
16462
|
+
return excluded;
|
|
16463
|
+
}
|
|
16464
|
+
function isIgnored(ig, path) {
|
|
16465
|
+
return decide(ig.rules, path);
|
|
16466
|
+
}
|
|
16467
|
+
function loadVerityIgnore() {
|
|
16468
|
+
const file = projectPath(VERITYIGNORE_FILE);
|
|
16469
|
+
if (!(0, import_node_fs19.existsSync)(file)) return EMPTY;
|
|
16470
|
+
try {
|
|
16471
|
+
return parseVerityIgnore((0, import_node_fs19.readFileSync)(file, "utf-8"));
|
|
16472
|
+
} catch {
|
|
16473
|
+
return EMPTY;
|
|
16474
|
+
}
|
|
16475
|
+
}
|
|
16476
|
+
function partitionIgnored(paths, ig) {
|
|
16477
|
+
const suspended = paths.some((p) => p === VERITYIGNORE_FILE);
|
|
16478
|
+
if (suspended || ig.rules.length === 0) {
|
|
16479
|
+
return { rules: ig.rules.length, kept: [...paths], ignored: [], securityExcluded: [], suspended };
|
|
16480
|
+
}
|
|
16481
|
+
const kept = [];
|
|
16482
|
+
const ignored = [];
|
|
16483
|
+
for (const p of paths) {
|
|
16484
|
+
if (p !== VERITYIGNORE_FILE && isIgnored(ig, p)) ignored.push(p);
|
|
16485
|
+
else kept.push(p);
|
|
16486
|
+
}
|
|
16487
|
+
return {
|
|
16488
|
+
rules: ig.rules.length,
|
|
16489
|
+
kept,
|
|
16490
|
+
ignored,
|
|
16491
|
+
securityExcluded: ignored.filter(isSecuritySensitive),
|
|
16492
|
+
suspended: false
|
|
16493
|
+
};
|
|
16494
|
+
}
|
|
16495
|
+
function ignoreShare(kept, ignored) {
|
|
16496
|
+
const total = kept + ignored;
|
|
16497
|
+
if (total === 0) return 0;
|
|
16498
|
+
return Math.round(ignored / total * 1e3) / 1e3;
|
|
16499
|
+
}
|
|
16500
|
+
function describeSecurityOverlap(ig) {
|
|
16501
|
+
if (ig.securityOverlap.length === 0) return null;
|
|
16502
|
+
const lines = ig.securityOverlap.map(
|
|
16503
|
+
(o) => ` line ${o.line}: "${o.raw}" would hide ${o.hides.slice(0, 3).join(", ")}` + (o.hides.length > 3 ? ` (+${o.hides.length - 3} more)` : "")
|
|
16504
|
+
);
|
|
16505
|
+
return `.verityignore: ${ig.securityOverlap.length} pattern(s) can hide security-sensitive files from review:
|
|
16506
|
+
${lines.join("\n")}
|
|
16507
|
+
These are still excluded \u2014 this is a warning, not a refusal. Add a \`!\` rule to keep one in scope, e.g. \`!.env*\`.`;
|
|
16508
|
+
}
|
|
16509
|
+
|
|
16510
|
+
// src/commands/standard.ts
|
|
16274
16511
|
function registerStandardCommands(program2) {
|
|
16275
16512
|
const standard = program2.command("standard").description("Manage the project Standard");
|
|
16276
16513
|
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 +16536,25 @@ function registerStandardCommands(program2) {
|
|
|
16299
16536
|
printError(`Invalid YAML in ${opts.file}: ${err.message}`);
|
|
16300
16537
|
process.exit(1);
|
|
16301
16538
|
}
|
|
16539
|
+
let ignoreRaw = null;
|
|
16540
|
+
const ignorePath = projectPath(VERITYIGNORE_FILE);
|
|
16541
|
+
if ((0, import_node_fs20.existsSync)(ignorePath)) {
|
|
16542
|
+
try {
|
|
16543
|
+
ignoreRaw = (0, import_node_fs20.readFileSync)(ignorePath, "utf-8");
|
|
16544
|
+
const overlap = describeSecurityOverlap(parseVerityIgnore(ignoreRaw));
|
|
16545
|
+
if (overlap) printWarn(overlap);
|
|
16546
|
+
} catch {
|
|
16547
|
+
}
|
|
16548
|
+
}
|
|
16302
16549
|
const result = await apiRequest({
|
|
16303
16550
|
method: "POST",
|
|
16304
16551
|
path: "/standards",
|
|
16305
16552
|
serviceUrl: urlResult.data,
|
|
16306
16553
|
token: tokenResult.data.token,
|
|
16307
|
-
body: {
|
|
16554
|
+
body: {
|
|
16555
|
+
content: ignoreRaw === null ? content : { ...content, verityignore: ignoreRaw },
|
|
16556
|
+
created_by: opts.createdBy
|
|
16557
|
+
},
|
|
16308
16558
|
verbose: globals.verbose
|
|
16309
16559
|
});
|
|
16310
16560
|
if (!result.ok) {
|
|
@@ -16381,6 +16631,15 @@ function registerStandardCommands(program2) {
|
|
|
16381
16631
|
var import_promises10 = require("node:fs/promises");
|
|
16382
16632
|
function registerConfigCommands(program2) {
|
|
16383
16633
|
const config = program2.command("config").description("Manage analysis configuration");
|
|
16634
|
+
config.command("service-url").description("Print the resolved service URL (used by the shipped skills)").action(async () => {
|
|
16635
|
+
const globals = program2.opts();
|
|
16636
|
+
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
16637
|
+
if (!urlResult.ok) {
|
|
16638
|
+
printError(urlResult.error);
|
|
16639
|
+
process.exit(1);
|
|
16640
|
+
}
|
|
16641
|
+
process.stdout.write(urlResult.data + "\n");
|
|
16642
|
+
});
|
|
16384
16643
|
config.command("push").description("Upload the analysis config to the service").option("--file <path>", "Path to config file", CODACY_CONFIG_FILE).action(async (opts) => {
|
|
16385
16644
|
const globals = program2.opts();
|
|
16386
16645
|
const tokenResult = await resolveToken(globals.token);
|
|
@@ -16522,99 +16781,402 @@ function formatRunDetail(run) {
|
|
|
16522
16781
|
return lines;
|
|
16523
16782
|
}
|
|
16524
16783
|
|
|
16525
|
-
// src/
|
|
16526
|
-
|
|
16527
|
-
|
|
16528
|
-
|
|
16529
|
-
|
|
16530
|
-
|
|
16531
|
-
|
|
16532
|
-
if (
|
|
16533
|
-
|
|
16534
|
-
return `${days}d ago`;
|
|
16784
|
+
// src/lib/ignore-declaration.ts
|
|
16785
|
+
var import_node_fs22 = require("node:fs");
|
|
16786
|
+
|
|
16787
|
+
// src/lib/debounce.ts
|
|
16788
|
+
var import_node_fs21 = require("node:fs");
|
|
16789
|
+
var import_node_crypto10 = require("node:crypto");
|
|
16790
|
+
function scopedFile(base, sessionId) {
|
|
16791
|
+
if (!sessionId) return base;
|
|
16792
|
+
return `${base}.${(0, import_node_crypto10.createHash)("sha1").update(sessionId).digest("hex").slice(0, 12)}`;
|
|
16535
16793
|
}
|
|
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);
|
|
16794
|
+
function checkDebounce(debounceSeconds = DEBOUNCE_SECONDS, sessionId) {
|
|
16795
|
+
const file = scopedFile(DEBOUNCE_FILE, sessionId);
|
|
16796
|
+
if (!(0, import_node_fs21.existsSync)(file)) return null;
|
|
16797
|
+
try {
|
|
16798
|
+
const lastTs = parseInt((0, import_node_fs21.readFileSync)(file, "utf-8").trim(), 10);
|
|
16799
|
+
const nowTs = Math.floor(Date.now() / 1e3);
|
|
16800
|
+
const elapsed = nowTs - lastTs;
|
|
16801
|
+
if (elapsed < debounceSeconds) {
|
|
16802
|
+
return `Debounced \u2014 last analysis was ${elapsed}s ago`;
|
|
16563
16803
|
}
|
|
16564
|
-
|
|
16565
|
-
|
|
16566
|
-
|
|
16567
|
-
|
|
16568
|
-
|
|
16569
|
-
|
|
16570
|
-
|
|
16571
|
-
|
|
16572
|
-
|
|
16573
|
-
|
|
16574
|
-
|
|
16575
|
-
|
|
16576
|
-
|
|
16577
|
-
|
|
16578
|
-
|
|
16579
|
-
|
|
16580
|
-
|
|
16581
|
-
|
|
16582
|
-
|
|
16583
|
-
|
|
16804
|
+
} catch {
|
|
16805
|
+
}
|
|
16806
|
+
return null;
|
|
16807
|
+
}
|
|
16808
|
+
function checkMtime(files, bypassForRecentCommits, sessionId) {
|
|
16809
|
+
if (bypassForRecentCommits) return null;
|
|
16810
|
+
const file = scopedFile(DEBOUNCE_FILE, sessionId);
|
|
16811
|
+
if (!(0, import_node_fs21.existsSync)(file)) return null;
|
|
16812
|
+
let debounceTime;
|
|
16813
|
+
try {
|
|
16814
|
+
debounceTime = (0, import_node_fs21.statSync)(file).mtimeMs;
|
|
16815
|
+
} catch {
|
|
16816
|
+
return null;
|
|
16817
|
+
}
|
|
16818
|
+
for (const f of files) {
|
|
16819
|
+
const resolved = resolveFile(f);
|
|
16820
|
+
if (!resolved) continue;
|
|
16821
|
+
try {
|
|
16822
|
+
const stat3 = (0, import_node_fs21.statSync)(resolved);
|
|
16823
|
+
if (stat3.mtimeMs > debounceTime) {
|
|
16824
|
+
return null;
|
|
16584
16825
|
}
|
|
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})`);
|
|
16826
|
+
} catch {
|
|
16827
|
+
continue;
|
|
16601
16828
|
}
|
|
16602
|
-
|
|
16603
|
-
|
|
16604
|
-
|
|
16829
|
+
}
|
|
16830
|
+
return "No files modified since last analysis";
|
|
16831
|
+
}
|
|
16832
|
+
function computeContentHash(files) {
|
|
16833
|
+
const hash = (0, import_node_crypto10.createHash)("sha1");
|
|
16834
|
+
const sorted = [...files].sort();
|
|
16835
|
+
for (const f of sorted) {
|
|
16836
|
+
const resolved = resolveFile(f) ?? f;
|
|
16837
|
+
try {
|
|
16838
|
+
if ((0, import_node_fs21.existsSync)(resolved)) {
|
|
16839
|
+
hash.update((0, import_node_fs21.readFileSync)(resolved));
|
|
16840
|
+
}
|
|
16841
|
+
} catch {
|
|
16605
16842
|
}
|
|
16606
|
-
|
|
16607
|
-
|
|
16608
|
-
|
|
16843
|
+
}
|
|
16844
|
+
return hash.digest("hex");
|
|
16845
|
+
}
|
|
16846
|
+
function checkContentHash(files, sessionId) {
|
|
16847
|
+
const hash = computeContentHash(files);
|
|
16848
|
+
const file = scopedFile(HASH_FILE, sessionId);
|
|
16849
|
+
if ((0, import_node_fs21.existsSync)(file)) {
|
|
16850
|
+
try {
|
|
16851
|
+
const storedHash = (0, import_node_fs21.readFileSync)(file, "utf-8").trim();
|
|
16852
|
+
if (hash === storedHash) {
|
|
16853
|
+
return { skip: "No source changes since last analysis", hash };
|
|
16854
|
+
}
|
|
16855
|
+
} catch {
|
|
16609
16856
|
}
|
|
16610
|
-
|
|
16611
|
-
|
|
16612
|
-
|
|
16613
|
-
|
|
16614
|
-
|
|
16857
|
+
}
|
|
16858
|
+
return { skip: null, hash };
|
|
16859
|
+
}
|
|
16860
|
+
function recordAnalysisStart(sessionId) {
|
|
16861
|
+
(0, import_node_fs21.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
16862
|
+
(0, import_node_fs21.writeFileSync)(scopedFile(DEBOUNCE_FILE, sessionId), String(Math.floor(Date.now() / 1e3)));
|
|
16863
|
+
}
|
|
16864
|
+
function recordPassHash(hash, sessionId) {
|
|
16865
|
+
(0, import_node_fs21.writeFileSync)(scopedFile(HASH_FILE, sessionId), hash);
|
|
16866
|
+
}
|
|
16867
|
+
function narrowToRecent(files, sessionId) {
|
|
16868
|
+
const file = scopedFile(DEBOUNCE_FILE, sessionId);
|
|
16869
|
+
if (!(0, import_node_fs21.existsSync)(file)) return files;
|
|
16870
|
+
let debounceTime;
|
|
16871
|
+
try {
|
|
16872
|
+
debounceTime = (0, import_node_fs21.statSync)(file).mtimeMs;
|
|
16873
|
+
} catch {
|
|
16874
|
+
return files;
|
|
16875
|
+
}
|
|
16876
|
+
const recent = files.filter((f) => {
|
|
16877
|
+
try {
|
|
16878
|
+
return (0, import_node_fs21.existsSync)(f) && (0, import_node_fs21.statSync)(f).mtimeMs > debounceTime;
|
|
16879
|
+
} catch {
|
|
16880
|
+
return false;
|
|
16615
16881
|
}
|
|
16616
|
-
|
|
16617
|
-
|
|
16882
|
+
});
|
|
16883
|
+
return recent.length > 0 ? recent : files;
|
|
16884
|
+
}
|
|
16885
|
+
function readIteration(currentCommit, _contentHash) {
|
|
16886
|
+
return Math.max(1, readBlockState(currentCommit).attempts);
|
|
16887
|
+
}
|
|
16888
|
+
var NO_BLOCKS = { attempts: 0, blocks: 0, fingerprint: null };
|
|
16889
|
+
function readBlockState(currentCommit, opts) {
|
|
16890
|
+
if (opts?.newUserPrompt) return NO_BLOCKS;
|
|
16891
|
+
if (!(0, import_node_fs21.existsSync)(ITERATION_FILE)) return NO_BLOCKS;
|
|
16892
|
+
try {
|
|
16893
|
+
const stored = (0, import_node_fs21.readFileSync)(ITERATION_FILE, "utf-8").trim();
|
|
16894
|
+
const parsed = stored.startsWith("{") ? parseJsonState(stored) : parseLegacyState(stored);
|
|
16895
|
+
if (!parsed) return NO_BLOCKS;
|
|
16896
|
+
if (parsed.commit !== currentCommit) return NO_BLOCKS;
|
|
16897
|
+
if (parsed.ts > 0 && Math.floor(Date.now() / 1e3) - parsed.ts > 600) return NO_BLOCKS;
|
|
16898
|
+
return { attempts: parsed.attempts, blocks: parsed.blocks, fingerprint: parsed.fingerprint };
|
|
16899
|
+
} catch {
|
|
16900
|
+
return NO_BLOCKS;
|
|
16901
|
+
}
|
|
16902
|
+
}
|
|
16903
|
+
function parseJsonState(raw) {
|
|
16904
|
+
const o = JSON.parse(raw);
|
|
16905
|
+
const attempts = typeof o.attempts === "number" ? o.attempts : NaN;
|
|
16906
|
+
if (isNaN(attempts)) return null;
|
|
16907
|
+
return {
|
|
16908
|
+
attempts,
|
|
16909
|
+
blocks: typeof o.blocks === "number" ? o.blocks : attempts,
|
|
16910
|
+
fingerprint: typeof o.fingerprint === "string" && o.fingerprint ? o.fingerprint : null,
|
|
16911
|
+
commit: typeof o.commit === "string" ? o.commit : "",
|
|
16912
|
+
ts: typeof o.ts === "number" ? o.ts : 0
|
|
16913
|
+
};
|
|
16914
|
+
}
|
|
16915
|
+
function parseLegacyState(raw) {
|
|
16916
|
+
const parts = raw.split(":");
|
|
16917
|
+
const n = parseInt(parts[0], 10);
|
|
16918
|
+
if (isNaN(n)) return null;
|
|
16919
|
+
return {
|
|
16920
|
+
attempts: n,
|
|
16921
|
+
// The old file has no separate block count; the old counter is the closest
|
|
16922
|
+
// honest answer, and it errs toward releasing sooner rather than later.
|
|
16923
|
+
blocks: n,
|
|
16924
|
+
fingerprint: parts.slice(3).join(":") || null,
|
|
16925
|
+
commit: parts[1] ?? "",
|
|
16926
|
+
ts: parseInt(parts[2] ?? "0", 10)
|
|
16927
|
+
};
|
|
16928
|
+
}
|
|
16929
|
+
function findingsFingerprint(findings) {
|
|
16930
|
+
const keys = findings.map((f) => `${String(f.pattern_id ?? "?")}|${String(f.file ?? "?")}`).filter((k) => k !== "?|?");
|
|
16931
|
+
return [...new Set(keys)].sort().join(",");
|
|
16932
|
+
}
|
|
16933
|
+
function isSameProblem(previous, current) {
|
|
16934
|
+
if (!previous || !current) return false;
|
|
16935
|
+
const prev = new Set(previous.split(","));
|
|
16936
|
+
return current.split(",").some((k) => prev.has(k));
|
|
16937
|
+
}
|
|
16938
|
+
function writeBlockState(commit, state) {
|
|
16939
|
+
(0, import_node_fs21.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
16940
|
+
(0, import_node_fs21.writeFileSync)(
|
|
16941
|
+
ITERATION_FILE,
|
|
16942
|
+
JSON.stringify({
|
|
16943
|
+
v: 2,
|
|
16944
|
+
attempts: state.attempts,
|
|
16945
|
+
blocks: state.blocks,
|
|
16946
|
+
commit,
|
|
16947
|
+
ts: Math.floor(Date.now() / 1e3),
|
|
16948
|
+
fingerprint: state.fingerprint ?? void 0
|
|
16949
|
+
})
|
|
16950
|
+
);
|
|
16951
|
+
}
|
|
16952
|
+
function resetBlockState(commit) {
|
|
16953
|
+
writeBlockState(commit, { attempts: 0, blocks: 0, fingerprint: null });
|
|
16954
|
+
}
|
|
16955
|
+
|
|
16956
|
+
// src/lib/ignore-declaration.ts
|
|
16957
|
+
var IGNORE_BUDGET = 3;
|
|
16958
|
+
var MAX_WINDOW_SECONDS = 60 * 60;
|
|
16959
|
+
var TURN_FUSE_SECONDS = 10 * 60;
|
|
16960
|
+
function parseDuration(input) {
|
|
16961
|
+
const trimmed = input.trim().toLowerCase();
|
|
16962
|
+
const m = /^(\d+)(s|m|h)?$/.exec(trimmed);
|
|
16963
|
+
if (!m) {
|
|
16964
|
+
return { ok: false, error: `Could not read "${input}" as a duration. Use 30m, 45s, or 1h.` };
|
|
16965
|
+
}
|
|
16966
|
+
const n = parseInt(m[1], 10);
|
|
16967
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
16968
|
+
return { ok: false, error: `A duration must be a positive number of seconds, minutes or hours \u2014 got "${input}".` };
|
|
16969
|
+
}
|
|
16970
|
+
const unit = m[2] ?? "m";
|
|
16971
|
+
const seconds = unit === "s" ? n : unit === "h" ? n * 3600 : n * 60;
|
|
16972
|
+
if (seconds > MAX_WINDOW_SECONDS) {
|
|
16973
|
+
return {
|
|
16974
|
+
ok: false,
|
|
16975
|
+
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.`
|
|
16976
|
+
};
|
|
16977
|
+
}
|
|
16978
|
+
return { ok: true, seconds };
|
|
16979
|
+
}
|
|
16980
|
+
function resolveActive(state, now) {
|
|
16981
|
+
if (!state?.active) return null;
|
|
16982
|
+
if (state.active.expires <= now) return null;
|
|
16983
|
+
return state.active;
|
|
16984
|
+
}
|
|
16985
|
+
function verifyDeclaration(input) {
|
|
16986
|
+
const d = input.declaration;
|
|
16987
|
+
if (!d) return { honoured: false, void: false };
|
|
16988
|
+
if (!input.authorshipIsObservable) {
|
|
16989
|
+
return {
|
|
16990
|
+
honoured: false,
|
|
16991
|
+
void: true,
|
|
16992
|
+
why: "the transcript window could not show what this turn did, so the declaration could not be checked"
|
|
16993
|
+
};
|
|
16994
|
+
}
|
|
16995
|
+
if (input.commandRecordTruncated) {
|
|
16996
|
+
return {
|
|
16997
|
+
honoured: false,
|
|
16998
|
+
void: true,
|
|
16999
|
+
why: "the record of commands run this turn was incomplete, so the declaration could not be checked"
|
|
17000
|
+
};
|
|
17001
|
+
}
|
|
17002
|
+
if (input.agentAuthoredFiles > 0) {
|
|
17003
|
+
return {
|
|
17004
|
+
honoured: false,
|
|
17005
|
+
void: true,
|
|
17006
|
+
why: `${input.agentAuthoredFiles} file${input.agentAuthoredFiles === 1 ? " was" : "s were"} authored during it`
|
|
17007
|
+
};
|
|
17008
|
+
}
|
|
17009
|
+
if (input.subagents > 0) {
|
|
17010
|
+
return {
|
|
17011
|
+
honoured: false,
|
|
17012
|
+
void: true,
|
|
17013
|
+
why: "work was delegated to a subagent during it, whose authorship this turn cannot account for"
|
|
17014
|
+
};
|
|
17015
|
+
}
|
|
17016
|
+
const authoring = [...input.agentCommands ?? [], ...input.userCommands ?? []].filter(
|
|
17017
|
+
(c) => !isNonAuthoringCommand(c)
|
|
17018
|
+
);
|
|
17019
|
+
if (authoring.length > 0) {
|
|
17020
|
+
return {
|
|
17021
|
+
honoured: false,
|
|
17022
|
+
void: true,
|
|
17023
|
+
why: `a command that can write files ran during it (${authoring[0]})`
|
|
17024
|
+
};
|
|
17025
|
+
}
|
|
17026
|
+
return { honoured: true };
|
|
17027
|
+
}
|
|
17028
|
+
function stateFile(sessionId) {
|
|
17029
|
+
return projectPath(scopedFile(IGNORE_DECLARATION_FILE, sessionId));
|
|
17030
|
+
}
|
|
17031
|
+
function ignoreStateKeys(token, sessionId) {
|
|
17032
|
+
const scoped = sessionScopeKey(token, sessionId);
|
|
17033
|
+
const userOnly = sessionScopeKey(token, void 0);
|
|
17034
|
+
return scoped === userOnly ? [scoped] : [scoped, userOnly];
|
|
17035
|
+
}
|
|
17036
|
+
function resolveIgnoreState(keys) {
|
|
17037
|
+
for (const key of keys) {
|
|
17038
|
+
const state = readIgnoreState(key);
|
|
17039
|
+
if (state) return { state, key };
|
|
17040
|
+
}
|
|
17041
|
+
return null;
|
|
17042
|
+
}
|
|
17043
|
+
function readIgnoreState(sessionId) {
|
|
17044
|
+
const file = stateFile(sessionId);
|
|
17045
|
+
if (!(0, import_node_fs22.existsSync)(file)) return null;
|
|
17046
|
+
try {
|
|
17047
|
+
const o = JSON.parse((0, import_node_fs22.readFileSync)(file, "utf-8")) ?? {};
|
|
17048
|
+
const spent = typeof o.spent === "number" ? o.spent : 0;
|
|
17049
|
+
const raw = o.active;
|
|
17050
|
+
let active = null;
|
|
17051
|
+
if (raw && typeof raw === "object") {
|
|
17052
|
+
const scope2 = raw.scope === "window" ? "window" : raw.scope === "turn" ? "turn" : null;
|
|
17053
|
+
const expires = typeof raw.expires === "number" ? raw.expires : null;
|
|
17054
|
+
if (scope2 && expires != null) {
|
|
17055
|
+
active = {
|
|
17056
|
+
scope: scope2,
|
|
17057
|
+
origin: raw.origin === "agent" ? "agent" : "user",
|
|
17058
|
+
reason: typeof raw.reason === "string" ? raw.reason : "",
|
|
17059
|
+
at: typeof raw.at === "number" ? raw.at : 0,
|
|
17060
|
+
expires
|
|
17061
|
+
};
|
|
17062
|
+
}
|
|
17063
|
+
}
|
|
17064
|
+
return { v: 1, active, spent };
|
|
17065
|
+
} catch {
|
|
17066
|
+
return null;
|
|
17067
|
+
}
|
|
17068
|
+
}
|
|
17069
|
+
function writeIgnoreState(state, sessionId) {
|
|
17070
|
+
try {
|
|
17071
|
+
(0, import_node_fs22.mkdirSync)(projectPath(VERITY_DIR), { recursive: true });
|
|
17072
|
+
(0, import_node_fs22.writeFileSync)(stateFile(sessionId), JSON.stringify({ v: 1, active: state.active, spent: state.spent }));
|
|
17073
|
+
} catch {
|
|
17074
|
+
}
|
|
17075
|
+
}
|
|
17076
|
+
function clearActiveDeclaration(sessionId) {
|
|
17077
|
+
const prev = readIgnoreState(sessionId);
|
|
17078
|
+
if (!prev) return;
|
|
17079
|
+
writeIgnoreState({ v: 1, active: null, spent: prev.spent }, sessionId);
|
|
17080
|
+
}
|
|
17081
|
+
function describeRemaining(d, now) {
|
|
17082
|
+
const left = Math.max(0, d.expires - now);
|
|
17083
|
+
if (left >= 90) return `${Math.round(left / 60)}m left`;
|
|
17084
|
+
return `${left}s left`;
|
|
17085
|
+
}
|
|
17086
|
+
|
|
17087
|
+
// src/commands/status.ts
|
|
17088
|
+
function timeAgo(isoDate) {
|
|
17089
|
+
const ms = Date.now() - new Date(isoDate).getTime();
|
|
17090
|
+
const mins = Math.floor(ms / 6e4);
|
|
17091
|
+
if (mins < 1) return "just now";
|
|
17092
|
+
if (mins < 60) return `${mins}m ago`;
|
|
17093
|
+
const hrs = Math.floor(mins / 60);
|
|
17094
|
+
if (hrs < 24) return `${hrs}h ago`;
|
|
17095
|
+
const days = Math.floor(hrs / 24);
|
|
17096
|
+
return `${days}d ago`;
|
|
17097
|
+
}
|
|
17098
|
+
function registerStatusCommand(program2) {
|
|
17099
|
+
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) => {
|
|
17100
|
+
const globals = program2.opts();
|
|
17101
|
+
const tokenResult = await resolveToken(globals.token);
|
|
17102
|
+
if (!tokenResult.ok) {
|
|
17103
|
+
printError(tokenResult.error);
|
|
17104
|
+
process.exit(1);
|
|
17105
|
+
}
|
|
17106
|
+
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
17107
|
+
if (!urlResult.ok) {
|
|
17108
|
+
printError(urlResult.error);
|
|
17109
|
+
process.exit(1);
|
|
17110
|
+
}
|
|
17111
|
+
const token = tokenResult.data.token;
|
|
17112
|
+
const serviceUrl = urlResult.data;
|
|
17113
|
+
const who = await whoami(token, serviceUrl, globals.verbose);
|
|
17114
|
+
const memResult = await apiRequest({
|
|
17115
|
+
method: "GET",
|
|
17116
|
+
path: "/memory",
|
|
17117
|
+
serviceUrl,
|
|
17118
|
+
token,
|
|
17119
|
+
verbose: globals.verbose
|
|
17120
|
+
});
|
|
17121
|
+
const denial = memResult.ok ? null : authDenialRemedy(memResult.error);
|
|
17122
|
+
if (!memResult.ok && !denial) {
|
|
17123
|
+
printError(memResult.error);
|
|
17124
|
+
process.exit(1);
|
|
17125
|
+
}
|
|
17126
|
+
const mem = memResult.ok ? memResult.data : null;
|
|
17127
|
+
if (opts.json) {
|
|
17128
|
+
const output = {
|
|
17129
|
+
auth: who.ok ? who.data : { error: who.error },
|
|
17130
|
+
memory: mem
|
|
17131
|
+
};
|
|
17132
|
+
if (denial && !memResult.ok) {
|
|
17133
|
+
output.error = { code: denial.code, message: memResult.error, remedy: denial.remedy };
|
|
17134
|
+
}
|
|
17135
|
+
if (opts.history && !denial) {
|
|
17136
|
+
const runsResult = await apiRequest({
|
|
17137
|
+
method: "GET",
|
|
17138
|
+
path: `/runs?limit=${opts.limit}`,
|
|
17139
|
+
serviceUrl,
|
|
17140
|
+
token,
|
|
17141
|
+
verbose: globals.verbose
|
|
17142
|
+
});
|
|
17143
|
+
if (runsResult.ok) {
|
|
17144
|
+
output.runs = runsResult.data.runs;
|
|
17145
|
+
}
|
|
17146
|
+
}
|
|
17147
|
+
printJson(output);
|
|
17148
|
+
return;
|
|
17149
|
+
}
|
|
17150
|
+
if (mem?.configured === false) {
|
|
17151
|
+
printInfo("Verity is not configured for this project. Run /verity-setup.");
|
|
17152
|
+
return;
|
|
17153
|
+
}
|
|
17154
|
+
printInfo("=== Verity Status ===");
|
|
17155
|
+
if (who.ok && who.data.logged_in) {
|
|
17156
|
+
printInfo(`Account: Logged in as ${who.data.email ?? `user #${who.data.user_id}`} \u2713`);
|
|
17157
|
+
} else if (who.ok && who.data.anonymous) {
|
|
17158
|
+
printInfo('Account: Anonymous \u2014 runs not saved, no cloud memory. Run "verity login".');
|
|
17159
|
+
} else if (tokenResult.data.userId != null) {
|
|
17160
|
+
printInfo(`Account: Logged in as ${tokenResult.data.email ?? `user #${tokenResult.data.userId}`} (cached \u2014 could not reach the Verity service) \u2713`);
|
|
17161
|
+
} else if (!who.ok) {
|
|
17162
|
+
printInfo(`Account: Unknown \u2014 could not reach the Verity service (${who.error})`);
|
|
17163
|
+
}
|
|
17164
|
+
if (who.ok) {
|
|
17165
|
+
const nudge = reverifyNudge(who.data);
|
|
17166
|
+
if (nudge) printWarn(` ${nudge}`);
|
|
17167
|
+
}
|
|
17168
|
+
if (denial) {
|
|
17169
|
+
printWarn(`Access: ${denial.remedy}`);
|
|
17170
|
+
printInfo(" Project status, history, and cloud memory stay unavailable until then.");
|
|
17171
|
+
}
|
|
17172
|
+
if (mem?.project_name) printInfo(`Project: ${mem.project_name}`);
|
|
17173
|
+
if (mem?.standard) {
|
|
17174
|
+
const s = mem.standard;
|
|
17175
|
+
printInfo(`Standard: v${s.version} (${s.quality_dimensions} quality, ${s.security_patterns} security, ${s.custom_patterns} custom)`);
|
|
17176
|
+
printInfo(`Languages: ${s.languages.join(", ")}`);
|
|
17177
|
+
}
|
|
17178
|
+
const hookStatus = await checkAllVerityHooks();
|
|
17179
|
+
const moments = [];
|
|
16618
17180
|
if (hookStatus.stop) moments.push("stop");
|
|
16619
17181
|
if (hookStatus.guardOn.includes("commit")) moments.push("pre-commit");
|
|
16620
17182
|
if (hookStatus.guardOn.includes("push")) moments.push("pre-push/PR");
|
|
@@ -16630,6 +17192,39 @@ function registerStatusCommand(program2) {
|
|
|
16630
17192
|
printInfo(`Trend: ${r.trend}`);
|
|
16631
17193
|
printInfo(`Runs: ${r.count} recorded`);
|
|
16632
17194
|
}
|
|
17195
|
+
{
|
|
17196
|
+
const tokenForScope = tokenResult.data.token;
|
|
17197
|
+
const found = resolveIgnoreState(
|
|
17198
|
+
ignoreStateKeys(tokenForScope, process.env.CLAUDE_SESSION_ID || void 0)
|
|
17199
|
+
);
|
|
17200
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
17201
|
+
const active = resolveActive(found?.state ?? null, now);
|
|
17202
|
+
const spent = found?.state.spent ?? 0;
|
|
17203
|
+
if (active) {
|
|
17204
|
+
printInfo("");
|
|
17205
|
+
printInfo("--- Ignore Declared ---");
|
|
17206
|
+
printInfo(`Scope: ${active.scope === "turn" ? "the next turn" : `a window, ${describeRemaining(active, now)}`}`);
|
|
17207
|
+
printInfo(`Reason: ${active.reason}`);
|
|
17208
|
+
printInfo(`Declared by: ${active.origin === "agent" ? "the agent" : "you"}`);
|
|
17209
|
+
printInfo(`Budget: ${spent}/${IGNORE_BUDGET} declarations this session`);
|
|
17210
|
+
printInfo('Turns that author anything are still reviewed \u2014 the declaration voids. "verity ignore clear" cancels it.');
|
|
17211
|
+
} else if (spent > 0) {
|
|
17212
|
+
printInfo("");
|
|
17213
|
+
printInfo(`Ignore budget: ${spent}/${IGNORE_BUDGET} declarations used this session.`);
|
|
17214
|
+
}
|
|
17215
|
+
}
|
|
17216
|
+
{
|
|
17217
|
+
const ig = loadVerityIgnore();
|
|
17218
|
+
if (ig.rules.length > 0 || ig.problems.length > 0) {
|
|
17219
|
+
printInfo("");
|
|
17220
|
+
printInfo("--- .verityignore ---");
|
|
17221
|
+
printInfo(`Rules: ${ig.rules.length}`);
|
|
17222
|
+
for (const problem of ig.problems) printWarn(` ${problem}`);
|
|
17223
|
+
const overlap = describeSecurityOverlap(ig);
|
|
17224
|
+
if (overlap) printWarn(overlap);
|
|
17225
|
+
printInfo("An edit to .verityignore suspends every rule for that turn \u2014 the edit is always reviewed.");
|
|
17226
|
+
}
|
|
17227
|
+
}
|
|
16633
17228
|
if (mem.pending_items && mem.pending_items.length > 0) {
|
|
16634
17229
|
printInfo("");
|
|
16635
17230
|
printInfo("--- Pending Items ---");
|
|
@@ -16793,6 +17388,7 @@ function createRun(opts, globals) {
|
|
|
16793
17388
|
allChanged: [],
|
|
16794
17389
|
hasRecentCommitFiles: false,
|
|
16795
17390
|
changedUniverse: [],
|
|
17391
|
+
verityIgnored: { rules: 0, kept: [], ignored: [], securityExcluded: [], suspended: false },
|
|
16796
17392
|
analyzable: [],
|
|
16797
17393
|
reviewable: [],
|
|
16798
17394
|
securityFiles: [],
|
|
@@ -16826,6 +17422,7 @@ function createRun(opts, globals) {
|
|
|
16826
17422
|
deletedNodePaths: [],
|
|
16827
17423
|
editedUploads: [],
|
|
16828
17424
|
autoSeedNotice: null,
|
|
17425
|
+
voidedIgnoreNotice: null,
|
|
16829
17426
|
foldResult: null,
|
|
16830
17427
|
foldConservation: null,
|
|
16831
17428
|
memorySession: null,
|
|
@@ -16846,7 +17443,7 @@ function createRun(opts, globals) {
|
|
|
16846
17443
|
}
|
|
16847
17444
|
|
|
16848
17445
|
// src/lib/stderr-log.ts
|
|
16849
|
-
var
|
|
17446
|
+
var import_node_fs23 = require("node:fs");
|
|
16850
17447
|
var TOKEN_RE2 = /verity_[0-9a-f]{16,}/g;
|
|
16851
17448
|
var ANSI_RE = /\u001b\[[0-?]*[ -/]*[@-~]/g;
|
|
16852
17449
|
function scrub(s) {
|
|
@@ -16859,9 +17456,9 @@ function append(text) {
|
|
|
16859
17456
|
try {
|
|
16860
17457
|
const dir = projectPath(DEBUG_LOG_DIR);
|
|
16861
17458
|
const file = projectPath(STDERR_LOG_FILE);
|
|
16862
|
-
(0,
|
|
17459
|
+
(0, import_node_fs23.mkdirSync)(dir, { recursive: true });
|
|
16863
17460
|
rotateIfNeeded(file);
|
|
16864
|
-
(0,
|
|
17461
|
+
(0, import_node_fs23.appendFileSync)(file, text);
|
|
16865
17462
|
} catch {
|
|
16866
17463
|
}
|
|
16867
17464
|
}
|
|
@@ -17019,7 +17616,7 @@ function installRunEvidence(run) {
|
|
|
17019
17616
|
|
|
17020
17617
|
// src/lib/git-frame.ts
|
|
17021
17618
|
var import_node_child_process7 = require("node:child_process");
|
|
17022
|
-
var
|
|
17619
|
+
var import_node_fs24 = require("node:fs");
|
|
17023
17620
|
var import_node_os3 = require("node:os");
|
|
17024
17621
|
var import_node_path18 = require("node:path");
|
|
17025
17622
|
var import_node_path19 = require("node:path");
|
|
@@ -17158,14 +17755,14 @@ function gitAt(dir, args) {
|
|
|
17158
17755
|
}
|
|
17159
17756
|
function realpathOr(p) {
|
|
17160
17757
|
try {
|
|
17161
|
-
return
|
|
17758
|
+
return import_node_fs24.realpathSync.native(p);
|
|
17162
17759
|
} catch {
|
|
17163
17760
|
return (0, import_node_path18.resolve)(p);
|
|
17164
17761
|
}
|
|
17165
17762
|
}
|
|
17166
17763
|
function resolveFrame(input) {
|
|
17167
17764
|
const found = findMomentSegment(input.command, input.on);
|
|
17168
|
-
const hookDirUsable = !!input.hookCwd && (0,
|
|
17765
|
+
const hookDirUsable = !!input.hookCwd && (0, import_node_fs24.existsSync)(input.hookCwd);
|
|
17169
17766
|
const baseDir = hookDirUsable ? input.hookCwd : process.cwd();
|
|
17170
17767
|
let anchor = hookDirUsable ? "hook-cwd" : "process-cwd";
|
|
17171
17768
|
const refuse = (refusal) => ({
|
|
@@ -17188,7 +17785,7 @@ function resolveFrame(input) {
|
|
|
17188
17785
|
if (dirs.size > 1) return refuse(`target:multiple ${found.moment} targets in one command`);
|
|
17189
17786
|
const targetDir = dirs.size === 1 ? [...dirs][0] : baseDir;
|
|
17190
17787
|
if (targetDir !== baseDir) {
|
|
17191
|
-
if (!(0,
|
|
17788
|
+
if (!(0, import_node_fs24.existsSync)(targetDir)) return refuse(`target:directory does not exist: ${targetDir}`);
|
|
17192
17789
|
dir = targetDir;
|
|
17193
17790
|
}
|
|
17194
17791
|
}
|
|
@@ -17227,7 +17824,7 @@ var SHA_RE2 = /^[0-9a-f]{40}$/;
|
|
|
17227
17824
|
function baselineShaAt(frame) {
|
|
17228
17825
|
if (!frame.worktreeRoot) return null;
|
|
17229
17826
|
try {
|
|
17230
|
-
const sha = (0,
|
|
17827
|
+
const sha = (0, import_node_fs24.readFileSync)((0, import_node_path19.join)(frame.worktreeRoot, BASELINE_SHA_FILE), "utf-8").trim();
|
|
17231
17828
|
if (!SHA_RE2.test(sha)) return null;
|
|
17232
17829
|
return refResolves(frame, sha) ? sha : null;
|
|
17233
17830
|
} catch {
|
|
@@ -17340,7 +17937,7 @@ function truthy(v) {
|
|
|
17340
17937
|
}
|
|
17341
17938
|
|
|
17342
17939
|
// src/lib/transcript.ts
|
|
17343
|
-
var
|
|
17940
|
+
var import_node_fs25 = require("node:fs");
|
|
17344
17941
|
var MAX_READ_BYTES = 256 * 1024;
|
|
17345
17942
|
var SMALL_FILE_BYTES = 64 * 1024;
|
|
17346
17943
|
var MAX_FILES_LIST = 20;
|
|
@@ -17366,7 +17963,7 @@ async function extractActionSummary(transcriptPath) {
|
|
|
17366
17963
|
function readTurnLines(transcriptPath) {
|
|
17367
17964
|
let size;
|
|
17368
17965
|
try {
|
|
17369
|
-
size = (0,
|
|
17966
|
+
size = (0, import_node_fs25.statSync)(transcriptPath).size;
|
|
17370
17967
|
} catch {
|
|
17371
17968
|
return null;
|
|
17372
17969
|
}
|
|
@@ -17374,7 +17971,7 @@ function readTurnLines(transcriptPath) {
|
|
|
17374
17971
|
let raw;
|
|
17375
17972
|
let windowed = false;
|
|
17376
17973
|
if (size <= SMALL_FILE_BYTES) {
|
|
17377
|
-
raw = (0,
|
|
17974
|
+
raw = (0, import_node_fs25.readFileSync)(transcriptPath, "utf-8");
|
|
17378
17975
|
} else {
|
|
17379
17976
|
windowed = true;
|
|
17380
17977
|
const buf = Buffer.alloc(Math.min(MAX_READ_BYTES, size));
|
|
@@ -17432,6 +18029,7 @@ function buildSummary(lines) {
|
|
|
17432
18029
|
const commands = [];
|
|
17433
18030
|
const userCommands = [];
|
|
17434
18031
|
let userCommandsTruncated = false;
|
|
18032
|
+
let commandsTruncated = false;
|
|
17435
18033
|
let searches = 0;
|
|
17436
18034
|
let subagents = 0;
|
|
17437
18035
|
let webFetches = 0;
|
|
@@ -17472,7 +18070,10 @@ function buildSummary(lines) {
|
|
|
17472
18070
|
totalToolCalls++;
|
|
17473
18071
|
const toolName = block.name ?? "unknown";
|
|
17474
18072
|
toolCounts[toolName] = (toolCounts[toolName] ?? 0) + 1;
|
|
17475
|
-
if (totalToolCalls > MAX_TOOL_BLOCKS)
|
|
18073
|
+
if (totalToolCalls > MAX_TOOL_BLOCKS) {
|
|
18074
|
+
if (toolName === "Bash") commandsTruncated = true;
|
|
18075
|
+
continue;
|
|
18076
|
+
}
|
|
17476
18077
|
const input = block.input ?? {};
|
|
17477
18078
|
switch (toolName) {
|
|
17478
18079
|
case "Read":
|
|
@@ -17492,9 +18093,11 @@ function buildSummary(lines) {
|
|
|
17492
18093
|
addPath(filesEdited, input.file_path);
|
|
17493
18094
|
break;
|
|
17494
18095
|
case "Bash": {
|
|
17495
|
-
const cmd =
|
|
17496
|
-
if (
|
|
17497
|
-
|
|
18096
|
+
const { cmd, lost } = sanitizeCommandWithLoss(input.command);
|
|
18097
|
+
if (lost) commandsTruncated = true;
|
|
18098
|
+
if (cmd) {
|
|
18099
|
+
if (commands.length < MAX_COMMANDS) commands.push(cmd);
|
|
18100
|
+
else commandsTruncated = true;
|
|
17498
18101
|
}
|
|
17499
18102
|
break;
|
|
17500
18103
|
}
|
|
@@ -17537,6 +18140,7 @@ function buildSummary(lines) {
|
|
|
17537
18140
|
],
|
|
17538
18141
|
searches,
|
|
17539
18142
|
commands,
|
|
18143
|
+
...commandsTruncated ? { commands_truncated: true } : {},
|
|
17540
18144
|
user_commands: userCommands,
|
|
17541
18145
|
...userCommandsTruncated ? { user_commands_truncated: true } : {},
|
|
17542
18146
|
subagents,
|
|
@@ -17547,6 +18151,7 @@ function buildSummary(lines) {
|
|
|
17547
18151
|
};
|
|
17548
18152
|
if (JSON.stringify(summary).length > MAX_SUMMARY_BYTES) {
|
|
17549
18153
|
summary.commands = [];
|
|
18154
|
+
summary.commands_truncated = true;
|
|
17550
18155
|
summary.user_commands = [];
|
|
17551
18156
|
summary.user_commands_truncated = true;
|
|
17552
18157
|
if (JSON.stringify(summary).length > MAX_SUMMARY_BYTES) {
|
|
@@ -17582,12 +18187,22 @@ function userTypedCommands(entry) {
|
|
|
17582
18187
|
}
|
|
17583
18188
|
return { commands, truncated };
|
|
17584
18189
|
}
|
|
18190
|
+
function sanitizeCommandWithLoss(rawCmd) {
|
|
18191
|
+
if (typeof rawCmd !== "string" || !rawCmd) return { cmd: null, lost: false };
|
|
18192
|
+
const lines = rawCmd.split("\n");
|
|
18193
|
+
const lost = lines.slice(1).some((l) => l.trim().length > 0);
|
|
18194
|
+
const first = lines[0];
|
|
18195
|
+
const cmd = sanitizeCommand(first);
|
|
18196
|
+
const hadSeparator = SEPARATORS.some((sep2) => first.indexOf(sep2) > 0);
|
|
18197
|
+
return { cmd, lost: lost || !hadSeparator && first.length > MAX_COMMAND_CHARS };
|
|
18198
|
+
}
|
|
18199
|
+
var SEPARATORS = [" | ", " > ", " >> ", " 2>", " && ", " ; "];
|
|
17585
18200
|
function sanitizeCommand(rawCmd) {
|
|
17586
18201
|
if (typeof rawCmd !== "string" || !rawCmd) return null;
|
|
17587
18202
|
let cmd = rawCmd.split("\n")[0];
|
|
17588
18203
|
let cut = -1;
|
|
17589
18204
|
let marker = "";
|
|
17590
|
-
for (const sep2 of
|
|
18205
|
+
for (const sep2 of SEPARATORS) {
|
|
17591
18206
|
const idx = cmd.indexOf(sep2);
|
|
17592
18207
|
if (idx > 0 && (cut === -1 || idx < cut)) {
|
|
17593
18208
|
cut = idx;
|
|
@@ -17837,7 +18452,7 @@ function channelSilence(input) {
|
|
|
17837
18452
|
// src/lib/cli-version.ts
|
|
17838
18453
|
function cliVersion() {
|
|
17839
18454
|
try {
|
|
17840
|
-
return true ? "0.
|
|
18455
|
+
return true ? "0.31.0-experimental.6bc3b1b" : "dev";
|
|
17841
18456
|
} catch {
|
|
17842
18457
|
return "dev";
|
|
17843
18458
|
}
|
|
@@ -17878,7 +18493,7 @@ async function sendSkipBeacon(ctx, reason) {
|
|
|
17878
18493
|
|
|
17879
18494
|
// src/lib/static-analysis.ts
|
|
17880
18495
|
var import_node_child_process8 = require("node:child_process");
|
|
17881
|
-
var
|
|
18496
|
+
var import_node_fs26 = require("node:fs");
|
|
17882
18497
|
var SEVERITY_ORDER = {
|
|
17883
18498
|
Error: 0,
|
|
17884
18499
|
Critical: 0,
|
|
@@ -17926,7 +18541,7 @@ function runCodacyAnalysis(files) {
|
|
|
17926
18541
|
if (files.length === 0) return empty;
|
|
17927
18542
|
const existingFiles = files.filter((f) => {
|
|
17928
18543
|
try {
|
|
17929
|
-
return (0,
|
|
18544
|
+
return (0, import_node_fs26.existsSync)(f);
|
|
17930
18545
|
} catch {
|
|
17931
18546
|
return false;
|
|
17932
18547
|
}
|
|
@@ -18124,6 +18739,7 @@ async function passAndExit(run, reason, skip, kindOverride) {
|
|
|
18124
18739
|
"bare-acknowledgment",
|
|
18125
18740
|
"reflection-prompt",
|
|
18126
18741
|
"command-only-turn",
|
|
18742
|
+
"declared-ignore",
|
|
18127
18743
|
"skip-mode",
|
|
18128
18744
|
"zero-increment",
|
|
18129
18745
|
"debounce",
|
|
@@ -18163,19 +18779,37 @@ async function scope(run) {
|
|
|
18163
18779
|
const { files: allChanged, hasRecentCommitFiles } = getChangedFiles();
|
|
18164
18780
|
run.changedUniverse = allChanged;
|
|
18165
18781
|
const { kept: external } = partitionVerityOwned(allChanged);
|
|
18166
|
-
const
|
|
18167
|
-
const
|
|
18168
|
-
const
|
|
18782
|
+
const verityIgnore = loadVerityIgnore();
|
|
18783
|
+
const ignored = partitionIgnored(external, verityIgnore);
|
|
18784
|
+
for (const problem of verityIgnore.problems) {
|
|
18785
|
+
process.stderr.write(`Verity: .verityignore ${problem}
|
|
18786
|
+
`);
|
|
18787
|
+
}
|
|
18788
|
+
if (ignored.securityExcluded.length > 0) {
|
|
18789
|
+
process.stderr.write(
|
|
18790
|
+
`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.
|
|
18791
|
+
`
|
|
18792
|
+
);
|
|
18793
|
+
}
|
|
18794
|
+
if (ignored.suspended) {
|
|
18795
|
+
process.stderr.write(
|
|
18796
|
+
"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"
|
|
18797
|
+
);
|
|
18798
|
+
}
|
|
18799
|
+
const inScope = ignored.kept;
|
|
18800
|
+
const analyzable = filterAnalyzable(inScope);
|
|
18801
|
+
const reviewable = filterReviewable(inScope);
|
|
18802
|
+
const securityFiles = filterSecurity(inScope);
|
|
18169
18803
|
const noFilesChanged = analyzable.length === 0 && reviewable.length === 0 && securityFiles.length === 0;
|
|
18170
18804
|
if (noFilesChanged && !assistantResponse) {
|
|
18171
18805
|
await passAndExit(run, "No analyzable files changed", "no-analyzable-files");
|
|
18172
18806
|
}
|
|
18173
18807
|
const allForReview = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable]));
|
|
18174
|
-
Object.assign(run, { allChanged, allForReview, analyzable, hasRecentCommitFiles, noFilesChanged, reviewable, securityFiles });
|
|
18808
|
+
Object.assign(run, { allChanged, allForReview, analyzable, hasRecentCommitFiles, noFilesChanged, reviewable, securityFiles, verityIgnored: ignored });
|
|
18175
18809
|
}
|
|
18176
18810
|
|
|
18177
18811
|
// src/lib/specs.ts
|
|
18178
|
-
var
|
|
18812
|
+
var import_node_fs27 = require("node:fs");
|
|
18179
18813
|
var import_node_path20 = require("node:path");
|
|
18180
18814
|
var SPEC_CANDIDATES = [
|
|
18181
18815
|
"CLAUDE.md",
|
|
@@ -18207,16 +18841,16 @@ function discoverSpecs(consulted = []) {
|
|
|
18207
18841
|
const totalCap = relevant ? MAX_TOTAL_SPEC_BYTES : UNCONSULTED_TOTAL_BYTES;
|
|
18208
18842
|
if (totalBytes >= totalCap) return false;
|
|
18209
18843
|
if (seen.has(specPath)) return true;
|
|
18210
|
-
if (!(0,
|
|
18844
|
+
if (!(0, import_node_fs27.existsSync)(specPath)) return true;
|
|
18211
18845
|
seen.add(specPath);
|
|
18212
18846
|
const remaining = totalCap - totalBytes;
|
|
18213
18847
|
const fileCap = relevant ? MAX_SPEC_FILE_BYTES : UNCONSULTED_FILE_BYTES;
|
|
18214
18848
|
const readBytes = Math.min(fileCap, remaining);
|
|
18215
18849
|
try {
|
|
18216
18850
|
const buf = Buffer.alloc(readBytes);
|
|
18217
|
-
const fd = (0,
|
|
18218
|
-
const bytesRead = (0,
|
|
18219
|
-
(0,
|
|
18851
|
+
const fd = (0, import_node_fs27.openSync)(specPath, "r");
|
|
18852
|
+
const bytesRead = (0, import_node_fs27.readSync)(fd, buf, 0, readBytes, 0);
|
|
18853
|
+
(0, import_node_fs27.closeSync)(fd);
|
|
18220
18854
|
const content = buf.slice(0, bytesRead).toString("utf-8");
|
|
18221
18855
|
if (!content) return true;
|
|
18222
18856
|
result.push({ path: specPath, content });
|
|
@@ -18232,7 +18866,7 @@ function discoverSpecs(consulted = []) {
|
|
|
18232
18866
|
if (!addSpec(candidate)) break;
|
|
18233
18867
|
}
|
|
18234
18868
|
for (const dir of ["spec", "docs"]) {
|
|
18235
|
-
if (!(0,
|
|
18869
|
+
if (!(0, import_node_fs27.existsSync)(dir)) continue;
|
|
18236
18870
|
try {
|
|
18237
18871
|
const mdFiles = findMdFiles(dir, 2).sort();
|
|
18238
18872
|
for (const mdFile of mdFiles) {
|
|
@@ -18247,7 +18881,7 @@ function findMdFiles(dir, maxDepth, depth = 0) {
|
|
|
18247
18881
|
if (depth >= maxDepth) return [];
|
|
18248
18882
|
const result = [];
|
|
18249
18883
|
try {
|
|
18250
|
-
const entries = (0,
|
|
18884
|
+
const entries = (0, import_node_fs27.readdirSync)(dir, { withFileTypes: true });
|
|
18251
18885
|
for (const entry of entries) {
|
|
18252
18886
|
const fullPath = (0, import_node_path20.join)(dir, entry.name);
|
|
18253
18887
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -18266,14 +18900,14 @@ function discoverPlans() {
|
|
|
18266
18900
|
const candidates = [];
|
|
18267
18901
|
const seen = /* @__PURE__ */ new Set();
|
|
18268
18902
|
for (const plansDir of [localPlansDir, homePlansDir]) {
|
|
18269
|
-
if (!(0,
|
|
18903
|
+
if (!(0, import_node_fs27.existsSync)(plansDir)) continue;
|
|
18270
18904
|
try {
|
|
18271
|
-
for (const f of (0,
|
|
18905
|
+
for (const f of (0, import_node_fs27.readdirSync)(plansDir)) {
|
|
18272
18906
|
if (!f.endsWith(".md") || seen.has(f)) continue;
|
|
18273
18907
|
seen.add(f);
|
|
18274
18908
|
const fullPath = (0, import_node_path20.join)(plansDir, f);
|
|
18275
18909
|
try {
|
|
18276
|
-
const stat3 = (0,
|
|
18910
|
+
const stat3 = (0, import_node_fs27.statSync)(fullPath);
|
|
18277
18911
|
candidates.push({ name: f, path: fullPath, mtime: stat3.mtimeMs, size: stat3.size });
|
|
18278
18912
|
} catch {
|
|
18279
18913
|
}
|
|
@@ -18286,13 +18920,35 @@ function discoverPlans() {
|
|
|
18286
18920
|
for (const entry of candidates.slice(0, MAX_PLAN_FILES)) {
|
|
18287
18921
|
if (entry.size > MAX_PLAN_FILE_BYTES) continue;
|
|
18288
18922
|
try {
|
|
18289
|
-
const content = (0,
|
|
18923
|
+
const content = (0, import_node_fs27.readFileSync)(entry.path, "utf-8");
|
|
18290
18924
|
result.push({ name: entry.name, content });
|
|
18291
18925
|
} catch {
|
|
18292
18926
|
}
|
|
18293
18927
|
}
|
|
18294
18928
|
return result;
|
|
18295
18929
|
}
|
|
18930
|
+
var GUARD_DOC_EXT = /\.(md|mdx|txt|rst|adoc)$/i;
|
|
18931
|
+
function discoverGuardDocs(rangeFiles2) {
|
|
18932
|
+
const result = [];
|
|
18933
|
+
let totalBytes = 0;
|
|
18934
|
+
for (const path of rangeFiles2) {
|
|
18935
|
+
if (result.length >= MAX_SPEC_FILES) break;
|
|
18936
|
+
if (!GUARD_DOC_EXT.test(path)) continue;
|
|
18937
|
+
if (path.startsWith("/") || path.includes("..")) continue;
|
|
18938
|
+
if (!(0, import_node_fs27.existsSync)(path)) continue;
|
|
18939
|
+
try {
|
|
18940
|
+
const stat3 = (0, import_node_fs27.statSync)(path);
|
|
18941
|
+
if (stat3.size > MAX_PLAN_FILE_BYTES) continue;
|
|
18942
|
+
if (totalBytes + stat3.size > MAX_TOTAL_SPEC_BYTES) continue;
|
|
18943
|
+
const content = (0, import_node_fs27.readFileSync)(path, "utf-8");
|
|
18944
|
+
if (!content) continue;
|
|
18945
|
+
result.push({ name: path, content });
|
|
18946
|
+
totalBytes += content.length;
|
|
18947
|
+
} catch {
|
|
18948
|
+
}
|
|
18949
|
+
}
|
|
18950
|
+
return result;
|
|
18951
|
+
}
|
|
18296
18952
|
|
|
18297
18953
|
// src/commands/analyze/phases/03-intent-inputs.ts
|
|
18298
18954
|
async function intentInputs(run) {
|
|
@@ -18306,6 +18962,50 @@ async function intentInputs(run) {
|
|
|
18306
18962
|
})) {
|
|
18307
18963
|
await passAndExit(run, "User command only \u2014 skipping analysis", "command-only-turn");
|
|
18308
18964
|
}
|
|
18965
|
+
{
|
|
18966
|
+
const ignoreKeys = ignoreStateKeys(
|
|
18967
|
+
run.tokenResult.ok ? run.tokenResult.data.token : void 0,
|
|
18968
|
+
null
|
|
18969
|
+
);
|
|
18970
|
+
const found = resolveIgnoreState([baselineSessionId, ...ignoreKeys]);
|
|
18971
|
+
const declaration = resolveActive(found?.state ?? null, Math.floor(Date.now() / 1e3));
|
|
18972
|
+
if (declaration) {
|
|
18973
|
+
const commandRecordTruncated = actionSummary?.commands_truncated === true || actionSummary?.user_commands_truncated === true || // The derived signal, and the only one an older client's summary can
|
|
18974
|
+
// give: `tool_counts` keeps counting Bash calls past every cap that
|
|
18975
|
+
// stops the list growing, so a mismatch IS a loss. See
|
|
18976
|
+
// `ActionSummary.commands_truncated`.
|
|
18977
|
+
(actionSummary?.tool_counts?.Bash ?? 0) > (actionSummary?.commands?.length ?? 0);
|
|
18978
|
+
const outcome = verifyDeclaration({
|
|
18979
|
+
declaration,
|
|
18980
|
+
agentAuthoredFiles: (actionSummary?.files_edited.length ?? 0) + (actionSummary?.files_created.length ?? 0),
|
|
18981
|
+
subagents: actionSummary?.subagents ?? 0,
|
|
18982
|
+
agentCommands: actionSummary?.commands,
|
|
18983
|
+
userCommands: actionSummary?.user_commands,
|
|
18984
|
+
commandRecordTruncated,
|
|
18985
|
+
authorshipIsObservable: !!actionSummary && actionSummary.transcript_windowed !== "orphaned"
|
|
18986
|
+
});
|
|
18987
|
+
if (outcome.honoured) {
|
|
18988
|
+
if (declaration.scope === "turn" && found) clearActiveDeclaration(found.key);
|
|
18989
|
+
logEvent("ignore_honoured", { scope: declaration.scope, origin: declaration.origin });
|
|
18990
|
+
await passAndExit(
|
|
18991
|
+
run,
|
|
18992
|
+
`skipping this turn \u2014 declared housekeeping ("${declaration.reason}")`,
|
|
18993
|
+
"declared-ignore"
|
|
18994
|
+
);
|
|
18995
|
+
} else if (outcome.void) {
|
|
18996
|
+
if (found) clearActiveDeclaration(found.key);
|
|
18997
|
+
logEvent("ignore_voided", {
|
|
18998
|
+
scope: declaration.scope,
|
|
18999
|
+
origin: declaration.origin,
|
|
19000
|
+
why: outcome.why
|
|
19001
|
+
});
|
|
19002
|
+
const notice = `Verity: the ignore declared for this window ("${declaration.reason}") was voided \u2014 ${outcome.why}. Reviewing normally.`;
|
|
19003
|
+
process.stderr.write(`${notice}
|
|
19004
|
+
`);
|
|
19005
|
+
run.voidedIgnoreNotice = notice;
|
|
19006
|
+
}
|
|
19007
|
+
}
|
|
19008
|
+
}
|
|
18309
19009
|
const conversation = await readAndClearConversationBuffer(baselineSessionId);
|
|
18310
19010
|
const specs = discoverSpecs(actionSummary?.files_read ?? []);
|
|
18311
19011
|
const plans = discoverPlans();
|
|
@@ -18419,177 +19119,8 @@ async function mode(run) {
|
|
|
18419
19119
|
Object.assign(run, { analysisMode, contextFilePaths, sessionAuthoredCode, sessionIdForMemory });
|
|
18420
19120
|
}
|
|
18421
19121
|
|
|
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
19122
|
// src/lib/fold.ts
|
|
18592
|
-
var
|
|
19123
|
+
var import_node_fs28 = require("node:fs");
|
|
18593
19124
|
var import_node_path21 = require("node:path");
|
|
18594
19125
|
var KNOWN_RECORD_TYPES = /* @__PURE__ */ new Set([
|
|
18595
19126
|
"user",
|
|
@@ -18727,7 +19258,7 @@ function candidateRoots(repoRoot2) {
|
|
|
18727
19258
|
const norm = repoRoot2.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
18728
19259
|
const out = [norm];
|
|
18729
19260
|
try {
|
|
18730
|
-
const real =
|
|
19261
|
+
const real = import_node_fs28.realpathSync.native(norm).replace(/\\/g, "/").replace(/\/+$/, "");
|
|
18731
19262
|
if (real !== norm) out.push(real);
|
|
18732
19263
|
} catch {
|
|
18733
19264
|
}
|
|
@@ -18815,8 +19346,8 @@ function fold(transcriptPath, opts = {}) {
|
|
|
18815
19346
|
}
|
|
18816
19347
|
};
|
|
18817
19348
|
try {
|
|
18818
|
-
if (!(0,
|
|
18819
|
-
ingest((0,
|
|
19349
|
+
if (!(0, import_node_fs28.existsSync)(transcriptPath)) return result;
|
|
19350
|
+
ingest((0, import_node_fs28.readFileSync)(transcriptPath, "utf8"), "agent");
|
|
18820
19351
|
result.coverage.complete = true;
|
|
18821
19352
|
} catch {
|
|
18822
19353
|
return result;
|
|
@@ -18827,19 +19358,19 @@ function fold(transcriptPath, opts = {}) {
|
|
|
18827
19358
|
(0, import_node_path21.basename)(transcriptPath).replace(/\.jsonl$/, ""),
|
|
18828
19359
|
"subagents"
|
|
18829
19360
|
);
|
|
18830
|
-
if ((0,
|
|
19361
|
+
if ((0, import_node_fs28.existsSync)(sidecarDir)) {
|
|
18831
19362
|
const maxFiles = opts.maxSidecars ?? 200;
|
|
18832
19363
|
const maxBytes = opts.maxSidecarBytes ?? 16 * 1024 * 1024;
|
|
18833
19364
|
const found = [];
|
|
18834
19365
|
const walk = (d, depth) => {
|
|
18835
19366
|
if (depth > 4) return;
|
|
18836
|
-
for (const e of (0,
|
|
19367
|
+
for (const e of (0, import_node_fs28.readdirSync)(d, { withFileTypes: true })) {
|
|
18837
19368
|
const p = (0, import_node_path21.join)(d, e.name);
|
|
18838
19369
|
if (e.isDirectory()) {
|
|
18839
19370
|
walk(p, depth + 1);
|
|
18840
19371
|
} else if (e.name.startsWith("agent-") && e.name.endsWith(".jsonl")) {
|
|
18841
19372
|
try {
|
|
18842
|
-
const st = (0,
|
|
19373
|
+
const st = (0, import_node_fs28.statSync)(p);
|
|
18843
19374
|
found.push({ path: p, size: st.size, mtimeMs: st.mtimeMs });
|
|
18844
19375
|
} catch {
|
|
18845
19376
|
result.coverage.malformed++;
|
|
@@ -18856,7 +19387,7 @@ function fold(transcriptPath, opts = {}) {
|
|
|
18856
19387
|
continue;
|
|
18857
19388
|
}
|
|
18858
19389
|
try {
|
|
18859
|
-
ingest((0,
|
|
19390
|
+
ingest((0, import_node_fs28.readFileSync)(f.path, "utf8"), "subagent");
|
|
18860
19391
|
bytes += f.size;
|
|
18861
19392
|
result.coverage.subagentFiles++;
|
|
18862
19393
|
} catch {
|
|
@@ -18891,7 +19422,7 @@ function fold(transcriptPath, opts = {}) {
|
|
|
18891
19422
|
}
|
|
18892
19423
|
function classifyUnobserved(path) {
|
|
18893
19424
|
try {
|
|
18894
|
-
const st = (0,
|
|
19425
|
+
const st = (0, import_node_fs28.statSync)(path);
|
|
18895
19426
|
if (!st.isFile()) return "unreadable";
|
|
18896
19427
|
} catch {
|
|
18897
19428
|
return "unreadable";
|
|
@@ -19195,20 +19726,20 @@ async function evidence(run) {
|
|
|
19195
19726
|
}
|
|
19196
19727
|
|
|
19197
19728
|
// src/lib/cache-cleanup.ts
|
|
19198
|
-
var
|
|
19729
|
+
var import_node_fs29 = require("node:fs");
|
|
19199
19730
|
var import_node_path22 = require("node:path");
|
|
19200
19731
|
var CACHE_TTL_DAYS = 7;
|
|
19201
19732
|
function pruneStaleCache() {
|
|
19202
19733
|
try {
|
|
19203
19734
|
const dir = projectPath(CACHE_DIR);
|
|
19204
19735
|
const cutoff = Date.now() - CACHE_TTL_DAYS * 24 * 3600 * 1e3;
|
|
19205
|
-
for (const entry of (0,
|
|
19736
|
+
for (const entry of (0, import_node_fs29.readdirSync)(dir)) {
|
|
19206
19737
|
if (!entry.startsWith("pending-")) continue;
|
|
19207
19738
|
const path = (0, import_node_path22.join)(dir, entry);
|
|
19208
19739
|
try {
|
|
19209
|
-
const stat3 = (0,
|
|
19740
|
+
const stat3 = (0, import_node_fs29.statSync)(path);
|
|
19210
19741
|
if (stat3.mtimeMs < cutoff) {
|
|
19211
|
-
(0,
|
|
19742
|
+
(0, import_node_fs29.unlinkSync)(path);
|
|
19212
19743
|
logEvent("cache_entry_pruned", {
|
|
19213
19744
|
path: entry,
|
|
19214
19745
|
age_days: Math.round((Date.now() - stat3.mtimeMs) / 864e5)
|
|
@@ -19222,7 +19753,7 @@ function pruneStaleCache() {
|
|
|
19222
19753
|
}
|
|
19223
19754
|
|
|
19224
19755
|
// src/lib/context-files.ts
|
|
19225
|
-
var
|
|
19756
|
+
var import_node_fs30 = require("node:fs");
|
|
19226
19757
|
var MAX_CONTEXT_FILES = 10;
|
|
19227
19758
|
var MAX_CONTEXT_FILE_BYTES = 10240;
|
|
19228
19759
|
var MAX_CONTEXT_TOTAL_BYTES = 51200;
|
|
@@ -19243,7 +19774,7 @@ function gatherContextFiles(contextPaths, deltaFiles) {
|
|
|
19243
19774
|
continue;
|
|
19244
19775
|
}
|
|
19245
19776
|
try {
|
|
19246
|
-
const content = (0,
|
|
19777
|
+
const content = (0, import_node_fs30.readFileSync)(safePath, "utf8");
|
|
19247
19778
|
const bytes = Buffer.byteLength(content);
|
|
19248
19779
|
if (bytes > MAX_CONTEXT_FILE_BYTES) {
|
|
19249
19780
|
logEvent("context_file_skipped", { path: filePath, reason: "too_large", bytes });
|
|
@@ -19305,7 +19836,7 @@ async function contextFiles(run) {
|
|
|
19305
19836
|
|
|
19306
19837
|
// src/lib/seed-runner.ts
|
|
19307
19838
|
var import_promises11 = require("node:fs/promises");
|
|
19308
|
-
var
|
|
19839
|
+
var import_node_fs31 = require("node:fs");
|
|
19309
19840
|
var import_node_path23 = require("node:path");
|
|
19310
19841
|
var import_yaml2 = __toESM(require_dist());
|
|
19311
19842
|
|
|
@@ -19545,7 +20076,7 @@ function renderNodeMarkdown(candidate, nodeId, createdAt) {
|
|
|
19545
20076
|
return fm;
|
|
19546
20077
|
}
|
|
19547
20078
|
async function runSeed(opts) {
|
|
19548
|
-
if (!(0,
|
|
20079
|
+
if (!(0, import_node_fs31.existsSync)(STANDARD_FILE)) {
|
|
19549
20080
|
return { created: 0, failed: 0, skipped: "no_standard", candidates: [] };
|
|
19550
20081
|
}
|
|
19551
20082
|
let standardDoc;
|
|
@@ -19557,7 +20088,7 @@ async function runSeed(opts) {
|
|
|
19557
20088
|
}
|
|
19558
20089
|
const knowledgeSpec = standardDoc.knowledge_spec ?? {};
|
|
19559
20090
|
let readmeContent;
|
|
19560
|
-
if ((0,
|
|
20091
|
+
if ((0, import_node_fs31.existsSync)("README.md")) {
|
|
19561
20092
|
try {
|
|
19562
20093
|
readmeContent = await (0, import_promises11.readFile)("README.md", "utf-8");
|
|
19563
20094
|
} catch {
|
|
@@ -19565,7 +20096,7 @@ async function runSeed(opts) {
|
|
|
19565
20096
|
}
|
|
19566
20097
|
let claudeMdContent;
|
|
19567
20098
|
for (const p of ["CLAUDE.md", ".claude/CLAUDE.md"]) {
|
|
19568
|
-
if ((0,
|
|
20099
|
+
if ((0, import_node_fs31.existsSync)(p)) {
|
|
19569
20100
|
try {
|
|
19570
20101
|
claudeMdContent = await (0, import_promises11.readFile)(p, "utf-8");
|
|
19571
20102
|
break;
|
|
@@ -19589,7 +20120,7 @@ async function runSeed(opts) {
|
|
|
19589
20120
|
return { created: 0, failed: 0, skipped: "no_candidates", candidates: [] };
|
|
19590
20121
|
}
|
|
19591
20122
|
const overviewPath = (0, import_node_path23.join)(MEMORY_DIR, "domain", "project-overview.md");
|
|
19592
|
-
if ((0,
|
|
20123
|
+
if ((0, import_node_fs31.existsSync)(overviewPath) && !opts.force) {
|
|
19593
20124
|
return { created: 0, failed: 0, skipped: "already_seeded", candidates };
|
|
19594
20125
|
}
|
|
19595
20126
|
if (opts.dryRun) {
|
|
@@ -19644,7 +20175,7 @@ async function runSeed(opts) {
|
|
|
19644
20175
|
}
|
|
19645
20176
|
|
|
19646
20177
|
// src/commands/analyze/phases/08-memory-manifest.ts
|
|
19647
|
-
var
|
|
20178
|
+
var import_node_fs32 = require("node:fs");
|
|
19648
20179
|
var import_node_path24 = require("node:path");
|
|
19649
20180
|
async function memoryManifest(run) {
|
|
19650
20181
|
const { globals } = run;
|
|
@@ -19656,8 +20187,8 @@ async function memoryManifest(run) {
|
|
|
19656
20187
|
try {
|
|
19657
20188
|
await ensureMemoryDir();
|
|
19658
20189
|
const seedMarker = (0, import_node_path24.join)(VERITY_DIR, ".seeded");
|
|
19659
|
-
const hasStandard = (0,
|
|
19660
|
-
const alreadyTried = (0,
|
|
20190
|
+
const hasStandard = (0, import_node_fs32.existsSync)(STANDARD_FILE);
|
|
20191
|
+
const alreadyTried = (0, import_node_fs32.existsSync)(seedMarker);
|
|
19661
20192
|
if (hasStandard && !alreadyTried) {
|
|
19662
20193
|
const preManifest = await buildManifest();
|
|
19663
20194
|
if (preManifest.nodes.length === 0) {
|
|
@@ -19670,7 +20201,7 @@ async function memoryManifest(run) {
|
|
|
19670
20201
|
dryRun: false
|
|
19671
20202
|
});
|
|
19672
20203
|
if (seedResult.created > 0) {
|
|
19673
|
-
(0,
|
|
20204
|
+
(0, import_node_fs32.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} created=${seedResult.created}
|
|
19674
20205
|
`);
|
|
19675
20206
|
autoSeedNotice = `Seeded ${seedResult.created} knowledge node(s) from your existing Standard (one-time).`;
|
|
19676
20207
|
logEvent("auto_seed_ran", {
|
|
@@ -19678,7 +20209,7 @@ async function memoryManifest(run) {
|
|
|
19678
20209
|
failed: seedResult.failed
|
|
19679
20210
|
});
|
|
19680
20211
|
} else if (seedResult.skipped === "already_seeded") {
|
|
19681
|
-
(0,
|
|
20212
|
+
(0, import_node_fs32.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} skipped=already_seeded
|
|
19682
20213
|
`);
|
|
19683
20214
|
} else {
|
|
19684
20215
|
logEvent("auto_seed_noop", {
|
|
@@ -19867,7 +20398,7 @@ async function workingMemory(run) {
|
|
|
19867
20398
|
}
|
|
19868
20399
|
|
|
19869
20400
|
// src/lib/note-budget.ts
|
|
19870
|
-
var
|
|
20401
|
+
var import_node_fs33 = require("node:fs");
|
|
19871
20402
|
var ADVISORY_BUDGET = { PASS: 1, WARN: 2 };
|
|
19872
20403
|
var EPISODE_STALE_SECONDS = 30 * 60;
|
|
19873
20404
|
var FRESH = { delivered: 0, tasksCompleted: 0, ts: 0 };
|
|
@@ -19889,9 +20420,9 @@ function advisoryBudgetSpent(episode, rawDecision) {
|
|
|
19889
20420
|
}
|
|
19890
20421
|
function readAdvisoryEpisode(sessionId) {
|
|
19891
20422
|
const file = scopedFile(ADVISORY_EPISODE_FILE, sessionId);
|
|
19892
|
-
if (!(0,
|
|
20423
|
+
if (!(0, import_node_fs33.existsSync)(file)) return null;
|
|
19893
20424
|
try {
|
|
19894
|
-
const o = JSON.parse((0,
|
|
20425
|
+
const o = JSON.parse((0, import_node_fs33.readFileSync)(file, "utf-8")) ?? {};
|
|
19895
20426
|
const delivered = typeof o.delivered === "number" ? o.delivered : NaN;
|
|
19896
20427
|
if (isNaN(delivered)) return null;
|
|
19897
20428
|
return {
|
|
@@ -19905,8 +20436,8 @@ function readAdvisoryEpisode(sessionId) {
|
|
|
19905
20436
|
}
|
|
19906
20437
|
function writeAdvisoryEpisode(episode, sessionId) {
|
|
19907
20438
|
try {
|
|
19908
|
-
(0,
|
|
19909
|
-
(0,
|
|
20439
|
+
(0, import_node_fs33.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
20440
|
+
(0, import_node_fs33.writeFileSync)(
|
|
19910
20441
|
scopedFile(ADVISORY_EPISODE_FILE, sessionId),
|
|
19911
20442
|
JSON.stringify({ v: 1, ...episode })
|
|
19912
20443
|
);
|
|
@@ -20013,7 +20544,16 @@ async function buildRequest(run) {
|
|
|
20013
20544
|
// the state, so the number is one turn lagged by construction. The
|
|
20014
20545
|
// degenerate win for the budget is a dead channel that looks like clean
|
|
20015
20546
|
// code; this is what makes "did delivery rate collapse" a query.
|
|
20016
|
-
advisory_delivered_prior: readAdvisoryEpisode(run.baselineSessionId)?.delivered ?? 0
|
|
20547
|
+
advisory_delivered_prior: readAdvisoryEpisode(run.baselineSessionId)?.delivered ?? 0,
|
|
20548
|
+
// `.verityignore` — see CoverageTelemetry.verityignore for why the SHARE is
|
|
20549
|
+
// the number that matters and why no paths travel with it.
|
|
20550
|
+
verityignore: {
|
|
20551
|
+
rules: run.verityIgnored.rules,
|
|
20552
|
+
excluded: run.verityIgnored.ignored.length,
|
|
20553
|
+
share: ignoreShare(run.verityIgnored.kept.length, run.verityIgnored.ignored.length),
|
|
20554
|
+
security_excluded: run.verityIgnored.securityExcluded.length,
|
|
20555
|
+
suspended: run.verityIgnored.suspended
|
|
20556
|
+
}
|
|
20017
20557
|
};
|
|
20018
20558
|
const requestBody = {
|
|
20019
20559
|
coverage_telemetry: coverageTelemetry,
|
|
@@ -20211,14 +20751,14 @@ async function buildRequest(run) {
|
|
|
20211
20751
|
}
|
|
20212
20752
|
|
|
20213
20753
|
// src/lib/offline.ts
|
|
20214
|
-
var
|
|
20754
|
+
var import_node_fs34 = require("node:fs");
|
|
20215
20755
|
var import_node_crypto11 = require("node:crypto");
|
|
20216
20756
|
function cacheRequest(body) {
|
|
20217
20757
|
try {
|
|
20218
|
-
(0,
|
|
20758
|
+
(0, import_node_fs34.mkdirSync)(CACHE_DIR, { recursive: true });
|
|
20219
20759
|
const suffix = (0, import_node_crypto11.randomBytes)(4).toString("hex");
|
|
20220
20760
|
const filename = `pending-${Math.floor(Date.now() / 1e3)}-${suffix}.json`;
|
|
20221
|
-
(0,
|
|
20761
|
+
(0, import_node_fs34.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(redactRequest(body)));
|
|
20222
20762
|
} catch {
|
|
20223
20763
|
}
|
|
20224
20764
|
}
|
|
@@ -20337,7 +20877,7 @@ async function transmit(run) {
|
|
|
20337
20877
|
}
|
|
20338
20878
|
|
|
20339
20879
|
// src/commands/analyze/phases/13-reconcile.ts
|
|
20340
|
-
var
|
|
20880
|
+
var import_node_fs35 = require("node:fs");
|
|
20341
20881
|
var import_node_path26 = require("node:path");
|
|
20342
20882
|
async function reconcile(run) {
|
|
20343
20883
|
const { actionSummary, allChanged, analyzable, baseline, baselineSessionId, codeDelta, contentHash, conversation, decision, foldResult, memory, memorySession, response, reviewable, securityFiles, turnId } = run;
|
|
@@ -20348,7 +20888,7 @@ async function reconcile(run) {
|
|
|
20348
20888
|
const st = foldDossier(memorySession.d);
|
|
20349
20889
|
openElsewhere = openBlockingElsewhere(st.statements, sentPaths, (file, line) => {
|
|
20350
20890
|
try {
|
|
20351
|
-
const src = (0,
|
|
20891
|
+
const src = (0, import_node_fs35.readFileSync)((0, import_node_path26.join)(repoRoot(), file), "utf8").split("\n");
|
|
20352
20892
|
const at = src[line - 1];
|
|
20353
20893
|
return at === void 0 ? null : lineSha(at);
|
|
20354
20894
|
} catch {
|
|
@@ -20409,6 +20949,25 @@ async function reconcile(run) {
|
|
|
20409
20949
|
stage: "self-scope",
|
|
20410
20950
|
kind: "policy"
|
|
20411
20951
|
})),
|
|
20952
|
+
// ⚠ WHAT THE TEAM'S OWN `.verityignore` REMOVED — every path, named
|
|
20953
|
+
// (VRT-135 · 3/3). The 0.30 principle: an exclusion the ledger cannot see
|
|
20954
|
+
// is a scoping decision nobody can audit, and this is the one exclusion
|
|
20955
|
+
// stage a user can edit, so it is the one that most needs to be visible.
|
|
20956
|
+
//
|
|
20957
|
+
// `policy`, by D2's bar — no version of this product would have reviewed a
|
|
20958
|
+
// file the project declared out of scope, so it must not downgrade PASS to
|
|
20959
|
+
// WARN. That is also why it is not narrated per-turn by `describeCoverage`
|
|
20960
|
+
// (policy exclusions are recorded, not announced): repeating "your dist/
|
|
20961
|
+
// went unreviewed" on every turn is how a channel gets muted. The SHARE
|
|
20962
|
+
// below is the signal that replaces the noise.
|
|
20963
|
+
//
|
|
20964
|
+
// Taken from the run, not recomputed — see context.ts `verityIgnored`.
|
|
20965
|
+
...run.verityIgnored.ignored.map((path) => ({
|
|
20966
|
+
path,
|
|
20967
|
+
reason: "verityignore",
|
|
20968
|
+
stage: "verityignore",
|
|
20969
|
+
kind: "policy"
|
|
20970
|
+
})),
|
|
20412
20971
|
// The extension allowlist. POLICY: a changed README was never going to be
|
|
20413
20972
|
// reviewed, and calling that a coverage gap would downgrade nearly every
|
|
20414
20973
|
// PASS to WARN until WARN meant nothing. Recorded so the ledger balances and
|
|
@@ -20638,7 +21197,7 @@ function agentContextFor(response, intentRepeat = 0, priorPendingFingerprints =
|
|
|
20638
21197
|
}
|
|
20639
21198
|
async function render(run) {
|
|
20640
21199
|
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;
|
|
21200
|
+
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
21201
|
let { iteration } = run;
|
|
20643
21202
|
const metadata = response.metadata ?? {};
|
|
20644
21203
|
const intentAmbiguity = metadata.intent_ambiguity;
|
|
@@ -20890,6 +21449,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
20890
21449
|
const viewUrl = response.view_url ?? "";
|
|
20891
21450
|
if (viewUrl) userSummary += ` Report: ${viewUrl}`;
|
|
20892
21451
|
if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
|
|
21452
|
+
if (voidedIgnoreNotice) userSummary = `${voidedIgnoreNotice} ${userSummary}`;
|
|
20893
21453
|
userSummary += loginNudge + grantNudge;
|
|
20894
21454
|
emitVerdict({
|
|
20895
21455
|
proposed: "PASS",
|
|
@@ -20911,6 +21471,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
20911
21471
|
const viewUrl = response.view_url ?? "";
|
|
20912
21472
|
if (viewUrl) userSummary += ` Report: ${viewUrl}`;
|
|
20913
21473
|
if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
|
|
21474
|
+
if (voidedIgnoreNotice) userSummary = `${voidedIgnoreNotice} ${userSummary}`;
|
|
20914
21475
|
userSummary += loginNudge + grantNudge;
|
|
20915
21476
|
emitVerdict({
|
|
20916
21477
|
proposed: "WARN",
|
|
@@ -20926,7 +21487,11 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
20926
21487
|
}
|
|
20927
21488
|
default: {
|
|
20928
21489
|
const raw = String(decision ?? "(missing)");
|
|
20929
|
-
const msg =
|
|
21490
|
+
const msg = [
|
|
21491
|
+
voidedIgnoreNotice,
|
|
21492
|
+
autoSeedNotice,
|
|
21493
|
+
"Verity: unrecognised verdict \u2014 treating as WARN"
|
|
21494
|
+
].filter(Boolean).join(" ") + loginNudge + grantNudge;
|
|
20930
21495
|
process.stderr.write(
|
|
20931
21496
|
`Verity: server returned an unrecognised gate_decision (${raw}). Rendering WARN rather than PASS. Update the CLI: npm i -g @codacy/verity-cli
|
|
20932
21497
|
`
|
|
@@ -21003,7 +21568,7 @@ async function runAnalyze(opts, globals) {
|
|
|
21003
21568
|
}
|
|
21004
21569
|
|
|
21005
21570
|
// src/commands/baseline.ts
|
|
21006
|
-
var
|
|
21571
|
+
var import_node_fs36 = require("node:fs");
|
|
21007
21572
|
function registerBaselineCommands(program2) {
|
|
21008
21573
|
const baseline = program2.command("baseline").description("Manage the task-start working-tree baseline");
|
|
21009
21574
|
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 +21577,7 @@ function registerBaselineCommands(program2) {
|
|
|
21012
21577
|
process.chdir(repoRoot());
|
|
21013
21578
|
} catch {
|
|
21014
21579
|
}
|
|
21015
|
-
if (!(0,
|
|
21580
|
+
if (!(0, import_node_fs36.existsSync)(VERITY_DIR)) {
|
|
21016
21581
|
process.exit(0);
|
|
21017
21582
|
}
|
|
21018
21583
|
let sessionId = opts.sessionId;
|
|
@@ -21052,7 +21617,7 @@ async function readStdin() {
|
|
|
21052
21617
|
}
|
|
21053
21618
|
|
|
21054
21619
|
// src/commands/review.ts
|
|
21055
|
-
var
|
|
21620
|
+
var import_node_fs37 = require("node:fs");
|
|
21056
21621
|
function registerReviewCommand(program2) {
|
|
21057
21622
|
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
21623
|
const globals = program2.opts();
|
|
@@ -21071,7 +21636,7 @@ async function runReview(opts, globals) {
|
|
|
21071
21636
|
const securityFiles = filterSecurity(allFiles);
|
|
21072
21637
|
let staticResults;
|
|
21073
21638
|
if (isCodacyAvailable()) {
|
|
21074
|
-
const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0,
|
|
21639
|
+
const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs37.existsSync)(f) || resolveFile(f) !== null);
|
|
21075
21640
|
staticResults = runCodacyAnalysis(scannable);
|
|
21076
21641
|
} else {
|
|
21077
21642
|
staticResults = {
|
|
@@ -21097,10 +21662,10 @@ async function runReview(opts, globals) {
|
|
|
21097
21662
|
const specPaths = opts.specs.split(",").map((f) => f.trim()).filter(Boolean);
|
|
21098
21663
|
specs = [];
|
|
21099
21664
|
for (const p of specPaths) {
|
|
21100
|
-
if (!(0,
|
|
21665
|
+
if (!(0, import_node_fs37.existsSync)(p)) continue;
|
|
21101
21666
|
try {
|
|
21102
|
-
const { readFileSync:
|
|
21103
|
-
const content =
|
|
21667
|
+
const { readFileSync: readFileSync24 } = await import("node:fs");
|
|
21668
|
+
const content = readFileSync24(p, "utf-8");
|
|
21104
21669
|
specs.push({ path: p, content: content.slice(0, 10240) });
|
|
21105
21670
|
} catch {
|
|
21106
21671
|
}
|
|
@@ -21157,7 +21722,7 @@ async function runReview(opts, globals) {
|
|
|
21157
21722
|
}
|
|
21158
21723
|
|
|
21159
21724
|
// src/commands/guard.ts
|
|
21160
|
-
var
|
|
21725
|
+
var import_node_fs38 = require("node:fs");
|
|
21161
21726
|
var import_node_path27 = require("node:path");
|
|
21162
21727
|
var GUARD_BLOCK_CAP = 2;
|
|
21163
21728
|
var GUARD_ITER_FILE = (0, import_node_path27.join)(VERITY_DIR, ".guard-iteration");
|
|
@@ -21205,7 +21770,7 @@ function readPreToolUseStdin() {
|
|
|
21205
21770
|
}
|
|
21206
21771
|
function readIterMap() {
|
|
21207
21772
|
try {
|
|
21208
|
-
const raw = JSON.parse((0,
|
|
21773
|
+
const raw = JSON.parse((0, import_node_fs38.readFileSync)(GUARD_ITER_FILE, "utf-8"));
|
|
21209
21774
|
if (raw && typeof raw === "object") {
|
|
21210
21775
|
if (typeof raw.moment === "string" && typeof raw.count === "number") {
|
|
21211
21776
|
return { [raw.moment]: raw.count };
|
|
@@ -21225,10 +21790,10 @@ function readIter(moment) {
|
|
|
21225
21790
|
}
|
|
21226
21791
|
function writeIter(moment, count) {
|
|
21227
21792
|
try {
|
|
21228
|
-
(0,
|
|
21793
|
+
(0, import_node_fs38.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
21229
21794
|
const map = readIterMap();
|
|
21230
21795
|
map[moment] = count;
|
|
21231
|
-
(0,
|
|
21796
|
+
(0, import_node_fs38.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
|
|
21232
21797
|
} catch {
|
|
21233
21798
|
}
|
|
21234
21799
|
}
|
|
@@ -21238,10 +21803,10 @@ function resetIter(moment) {
|
|
|
21238
21803
|
if (!(moment in map)) return;
|
|
21239
21804
|
delete map[moment];
|
|
21240
21805
|
if (Object.keys(map).length === 0) {
|
|
21241
|
-
if ((0,
|
|
21806
|
+
if ((0, import_node_fs38.existsSync)(GUARD_ITER_FILE)) (0, import_node_fs38.unlinkSync)(GUARD_ITER_FILE);
|
|
21242
21807
|
} else {
|
|
21243
|
-
(0,
|
|
21244
|
-
(0,
|
|
21808
|
+
(0, import_node_fs38.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
21809
|
+
(0, import_node_fs38.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
|
|
21245
21810
|
}
|
|
21246
21811
|
} catch {
|
|
21247
21812
|
}
|
|
@@ -21302,16 +21867,17 @@ function extractStatedIntent(moment, command, pushedMessages = null) {
|
|
|
21302
21867
|
const text = moment === "pre-commit" ? parseCommitMessage(command) : parsePrIntent(command) ?? pushedMessages;
|
|
21303
21868
|
return isSubstantiveIntent(text) ? text : null;
|
|
21304
21869
|
}
|
|
21305
|
-
function hasBlockingFinding(response) {
|
|
21870
|
+
function hasBlockingFinding(response, sentFiles) {
|
|
21306
21871
|
const findings = response.findings ?? [];
|
|
21307
|
-
|
|
21872
|
+
const sent = sentFiles ? new Set(sentFiles.map((p) => p.replace(/\\/g, "/"))) : null;
|
|
21873
|
+
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
21874
|
}
|
|
21309
21875
|
function buildGuardRequest(moment, files, codeDelta, iter, sessionId, statedIntent, coverageTelemetry) {
|
|
21310
21876
|
const analyzable = filterAnalyzable(files);
|
|
21311
21877
|
const securityFiles = filterSecurity(files);
|
|
21312
21878
|
let staticResults;
|
|
21313
21879
|
if (isCodacyAvailable()) {
|
|
21314
|
-
const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0,
|
|
21880
|
+
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
21881
|
staticResults = runCodacyAnalysis(scannable);
|
|
21316
21882
|
} else {
|
|
21317
21883
|
staticResults = { tool: "@codacy/analysis-cli", findings: [], summary: { total_findings: 0, by_severity: {}, tools_run: [] } };
|
|
@@ -21333,13 +21899,11 @@ function buildGuardRequest(moment, files, codeDelta, iter, sessionId, statedInte
|
|
|
21333
21899
|
}
|
|
21334
21900
|
};
|
|
21335
21901
|
if (coverageTelemetry) requestBody.coverage_telemetry = coverageTelemetry;
|
|
21336
|
-
const
|
|
21337
|
-
|
|
21338
|
-
if (specs.length > 0 || plans.length > 0 || statedIntent) {
|
|
21902
|
+
const docs = discoverGuardDocs(files);
|
|
21903
|
+
if (docs.length > 0 || statedIntent) {
|
|
21339
21904
|
const intentContext = {};
|
|
21340
21905
|
if (statedIntent) intentContext.user_prompt = statedIntent;
|
|
21341
|
-
if (
|
|
21342
|
-
if (plans.length > 0) intentContext.plans = plans;
|
|
21906
|
+
if (docs.length > 0) intentContext.plans = docs;
|
|
21343
21907
|
requestBody.intent_context = intentContext;
|
|
21344
21908
|
}
|
|
21345
21909
|
return requestBody;
|
|
@@ -21420,7 +21984,7 @@ async function runGuard(opts, globals) {
|
|
|
21420
21984
|
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
21421
21985
|
if (!tokenResult.ok || !urlResult.ok) process.exit(0);
|
|
21422
21986
|
logEvent("guard_frame", { moment, ...frameTelemetry(frame, range) });
|
|
21423
|
-
const codeDelta = collectCodeDelta(files);
|
|
21987
|
+
const codeDelta = collectCodeDelta(files, GIT_MOMENT_BUDGET);
|
|
21424
21988
|
if (codeDelta.total_files === 0) process.exit(0);
|
|
21425
21989
|
const statedIntent = extractStatedIntent(
|
|
21426
21990
|
moment,
|
|
@@ -21446,6 +22010,7 @@ async function runGuard(opts, globals) {
|
|
|
21446
22010
|
excluded: codeDelta.excluded.map((e) => ({ path: e.path, reason: e.reason }))
|
|
21447
22011
|
};
|
|
21448
22012
|
logToFileOnly(coverageBlock(coverage));
|
|
22013
|
+
const reviewStart = Date.now();
|
|
21449
22014
|
const result = await analyzeRequest({
|
|
21450
22015
|
serviceUrl: urlResult.data,
|
|
21451
22016
|
token: tokenResult.data.token,
|
|
@@ -21454,6 +22019,7 @@ async function runGuard(opts, globals) {
|
|
|
21454
22019
|
timeout: 29e4,
|
|
21455
22020
|
cmd: "guard"
|
|
21456
22021
|
});
|
|
22022
|
+
const reviewSecs = Math.max(1, Math.round((Date.now() - reviewStart) / 1e3));
|
|
21457
22023
|
if (!result.ok) {
|
|
21458
22024
|
const authRemedy = result.error.startsWith("STALE_VERIFICATION") ? " Your GitHub verification expired \u2014 run `verity login` to re-verify." : result.error.startsWith("FORBIDDEN") ? " No access grant for this repository \u2014 run `verity login` to refresh your grants." : result.error.startsWith("INVALID_TOKEN") ? " Your Verity login expired or was revoked \u2014 run `verity login` to sign in again." : "";
|
|
21459
22025
|
emitAllowNotice(
|
|
@@ -21466,9 +22032,9 @@ async function runGuard(opts, globals) {
|
|
|
21466
22032
|
const decision = response.gate_decision ?? "(unrecognised)";
|
|
21467
22033
|
const viewUrl = response.view_url ?? "";
|
|
21468
22034
|
const link = viewUrl ? ` \u2014 ${viewUrl}` : "";
|
|
21469
|
-
const covLine = coverageSummary(coverage)
|
|
22035
|
+
const covLine = `${coverageSummary(coverage)} in ${reviewSecs}s`;
|
|
21470
22036
|
const covDetail = coverageBlock(coverage);
|
|
21471
|
-
if (decision === "FAIL" && hasBlockingFinding(response)) {
|
|
22037
|
+
if (decision === "FAIL" && hasBlockingFinding(response, codeDelta.files.map((f) => f.path))) {
|
|
21472
22038
|
writeIter(moment, iter + 1);
|
|
21473
22039
|
writeBlockMessage(moment, response, covDetail);
|
|
21474
22040
|
process.exit(2);
|
|
@@ -21536,17 +22102,166 @@ function writeBlockMessage(moment, response, covDetail) {
|
|
|
21536
22102
|
`);
|
|
21537
22103
|
process.stderr.write(`${BOLD}Fix these, then re-run the ${verb}.${NC}
|
|
21538
22104
|
`);
|
|
22105
|
+
process.stderr.write(`${DIM}Accepted risk? Record it: verity waive <pattern-id> --file <path> --reason "\u2026" (voids automatically when the file changes).${NC}
|
|
22106
|
+
`);
|
|
22107
|
+
}
|
|
22108
|
+
|
|
22109
|
+
// src/commands/ignore.ts
|
|
22110
|
+
function registerIgnoreCommand(program2) {
|
|
22111
|
+
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) => {
|
|
22112
|
+
const globals = program2.opts();
|
|
22113
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
22114
|
+
const reason = opts.reason?.trim();
|
|
22115
|
+
if (!reason) {
|
|
22116
|
+
printError(
|
|
22117
|
+
'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.'
|
|
22118
|
+
);
|
|
22119
|
+
process.exit(1);
|
|
22120
|
+
}
|
|
22121
|
+
if (opts.for && opts.turn) {
|
|
22122
|
+
printError("Use either --turn or --for, not both \u2014 they are two different windows.");
|
|
22123
|
+
process.exit(1);
|
|
22124
|
+
}
|
|
22125
|
+
let scope2 = "turn";
|
|
22126
|
+
let ttl = TURN_FUSE_SECONDS;
|
|
22127
|
+
if (opts.for) {
|
|
22128
|
+
const parsed = parseDuration(opts.for);
|
|
22129
|
+
if (!parsed.ok) {
|
|
22130
|
+
printError(parsed.error);
|
|
22131
|
+
process.exit(1);
|
|
22132
|
+
}
|
|
22133
|
+
scope2 = "window";
|
|
22134
|
+
ttl = parsed.seconds;
|
|
22135
|
+
}
|
|
22136
|
+
const tokenResult = await resolveToken(globals.token);
|
|
22137
|
+
const token = tokenResult.ok ? tokenResult.data.token : void 0;
|
|
22138
|
+
const sessionId = opts.sessionId || process.env.CLAUDE_SESSION_ID || void 0;
|
|
22139
|
+
const keys = ignoreStateKeys(token, sessionId);
|
|
22140
|
+
const existing = resolveIgnoreState(keys);
|
|
22141
|
+
const spent = existing?.state.spent ?? 0;
|
|
22142
|
+
const writeKey = existing?.key ?? keys[0];
|
|
22143
|
+
if (spent >= IGNORE_BUDGET) {
|
|
22144
|
+
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.`;
|
|
22145
|
+
if (opts.json) {
|
|
22146
|
+
printJson({ declared: false, reason_refused: "budget-spent", spent, budget: IGNORE_BUDGET });
|
|
22147
|
+
} else {
|
|
22148
|
+
printWarn(msg);
|
|
22149
|
+
}
|
|
22150
|
+
logEvent("ignore_refused", { why: "budget-spent", spent, budget: IGNORE_BUDGET });
|
|
22151
|
+
process.exit(0);
|
|
22152
|
+
}
|
|
22153
|
+
const declaration = {
|
|
22154
|
+
scope: scope2,
|
|
22155
|
+
origin: opts.agent ? "agent" : "user",
|
|
22156
|
+
reason,
|
|
22157
|
+
at: now,
|
|
22158
|
+
expires: now + ttl
|
|
22159
|
+
};
|
|
22160
|
+
writeIgnoreState({ v: 1, active: declaration, spent: spent + 1 }, writeKey);
|
|
22161
|
+
logEvent("ignore_declared", {
|
|
22162
|
+
scope: scope2,
|
|
22163
|
+
origin: declaration.origin,
|
|
22164
|
+
ttl_seconds: ttl,
|
|
22165
|
+
spent: spent + 1,
|
|
22166
|
+
budget: IGNORE_BUDGET
|
|
22167
|
+
});
|
|
22168
|
+
if (opts.json) {
|
|
22169
|
+
printJson({
|
|
22170
|
+
declared: true,
|
|
22171
|
+
scope: scope2,
|
|
22172
|
+
origin: declaration.origin,
|
|
22173
|
+
reason,
|
|
22174
|
+
expires_at: new Date(declaration.expires * 1e3).toISOString(),
|
|
22175
|
+
spent: spent + 1,
|
|
22176
|
+
budget: IGNORE_BUDGET
|
|
22177
|
+
});
|
|
22178
|
+
return;
|
|
22179
|
+
}
|
|
22180
|
+
const window = scope2 === "turn" ? "the next turn" : `the next ${describeRemaining(declaration, now).replace(" left", "")}`;
|
|
22181
|
+
printInfo(`Verity will skip ${window} \u2014 "${reason}" (${spent + 1}/${IGNORE_BUDGET} this session).`);
|
|
22182
|
+
printInfo("It covers turns that author nothing. If anything is written, the declaration voids and the review runs.");
|
|
22183
|
+
});
|
|
22184
|
+
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) => {
|
|
22185
|
+
const globals = program2.opts();
|
|
22186
|
+
const tokenResult = await resolveToken(globals.token);
|
|
22187
|
+
const token = tokenResult.ok ? tokenResult.data.token : void 0;
|
|
22188
|
+
const sessionId = opts.sessionId || process.env.CLAUDE_SESSION_ID || void 0;
|
|
22189
|
+
const found = resolveIgnoreState(ignoreStateKeys(token, sessionId));
|
|
22190
|
+
const active = resolveActive(found?.state ?? null, Math.floor(Date.now() / 1e3));
|
|
22191
|
+
if (!found || !active) {
|
|
22192
|
+
printInfo("No active ignore declaration.");
|
|
22193
|
+
return;
|
|
22194
|
+
}
|
|
22195
|
+
clearActiveDeclaration(found.key);
|
|
22196
|
+
logEvent("ignore_cleared", { scope: active.scope, origin: active.origin });
|
|
22197
|
+
printInfo(`Cleared: "${active.reason}". The next turn will be reviewed normally.`);
|
|
22198
|
+
});
|
|
22199
|
+
}
|
|
22200
|
+
|
|
22201
|
+
// src/commands/waive.ts
|
|
22202
|
+
var import_node_crypto12 = require("node:crypto");
|
|
22203
|
+
var import_node_fs39 = require("node:fs");
|
|
22204
|
+
function registerWaiveCommand(program2) {
|
|
22205
|
+
program2.command("waive <pattern-id>").description("Record an accepted-risk disposition for an open finding (voids when the file changes)").option("--file <path>", "File the finding is anchored to, REPO-RELATIVE (recommended \u2014 narrows the waive)").requiredOption("--reason <text>", "The human disposition this records (reviewer finding, ADR, \u2026)").action(async (patternId, opts) => {
|
|
22206
|
+
const globals = program2.opts();
|
|
22207
|
+
try {
|
|
22208
|
+
process.chdir(repoRoot());
|
|
22209
|
+
} catch {
|
|
22210
|
+
}
|
|
22211
|
+
if (opts.reason.trim().length < 20) {
|
|
22212
|
+
printError("A waive needs a real reason (>= 20 chars) \u2014 name the human disposition it records.");
|
|
22213
|
+
process.exit(1);
|
|
22214
|
+
}
|
|
22215
|
+
const tokenResult = await resolveToken(globals.token);
|
|
22216
|
+
if (!tokenResult.ok) {
|
|
22217
|
+
printError(tokenResult.error);
|
|
22218
|
+
process.exit(1);
|
|
22219
|
+
}
|
|
22220
|
+
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
22221
|
+
if (!urlResult.ok) {
|
|
22222
|
+
printError(urlResult.error);
|
|
22223
|
+
process.exit(1);
|
|
22224
|
+
}
|
|
22225
|
+
const body = {
|
|
22226
|
+
pattern_id: patternId,
|
|
22227
|
+
reason: opts.reason.trim()
|
|
22228
|
+
};
|
|
22229
|
+
if (opts.file) {
|
|
22230
|
+
body.file = opts.file;
|
|
22231
|
+
try {
|
|
22232
|
+
body.file_sha256 = (0, import_node_crypto12.createHash)("sha256").update((0, import_node_fs39.readFileSync)(opts.file)).digest("hex");
|
|
22233
|
+
} catch {
|
|
22234
|
+
printError(`Cannot read ${opts.file} \u2014 run from the repo root, or omit --file to waive by pattern.`);
|
|
22235
|
+
process.exit(1);
|
|
22236
|
+
}
|
|
22237
|
+
}
|
|
22238
|
+
const result = await apiRequest({
|
|
22239
|
+
method: "POST",
|
|
22240
|
+
path: "/feedback/waive",
|
|
22241
|
+
serviceUrl: urlResult.data,
|
|
22242
|
+
token: tokenResult.data.token,
|
|
22243
|
+
body,
|
|
22244
|
+
verbose: globals.verbose
|
|
22245
|
+
});
|
|
22246
|
+
if (!result.ok) {
|
|
22247
|
+
printError(`Couldn't record the waive: ${result.error}`);
|
|
22248
|
+
process.exit(1);
|
|
22249
|
+
}
|
|
22250
|
+
logEvent("waive_recorded", { pattern: patternId, file: opts.file ?? null, waived: result.data.waived });
|
|
22251
|
+
printInfo(`Waived ${result.data.waived} open statement(s) for '${patternId}'${opts.file ? ` on ${opts.file}` : ""}.`);
|
|
22252
|
+
printInfo(result.data.note);
|
|
22253
|
+
});
|
|
21539
22254
|
}
|
|
21540
22255
|
|
|
21541
22256
|
// src/commands/init.ts
|
|
21542
|
-
var
|
|
22257
|
+
var import_node_fs41 = require("node:fs");
|
|
21543
22258
|
var import_promises13 = require("node:fs/promises");
|
|
21544
22259
|
var import_node_path29 = require("node:path");
|
|
21545
22260
|
var import_node_child_process11 = require("node:child_process");
|
|
21546
22261
|
var readline2 = __toESM(require("node:readline/promises"));
|
|
21547
22262
|
|
|
21548
22263
|
// src/commands/migrate.ts
|
|
21549
|
-
var
|
|
22264
|
+
var import_node_fs40 = require("node:fs");
|
|
21550
22265
|
var import_node_path28 = require("node:path");
|
|
21551
22266
|
var import_node_child_process10 = require("node:child_process");
|
|
21552
22267
|
|
|
@@ -21678,10 +22393,10 @@ async function runMigration(opts = {}) {
|
|
|
21678
22393
|
function migrateProjectDir(root, actions) {
|
|
21679
22394
|
const gateDir = (0, import_node_path28.join)(root, ".gate");
|
|
21680
22395
|
const verityDir = (0, import_node_path28.join)(root, ".verity");
|
|
21681
|
-
if ((0,
|
|
22396
|
+
if ((0, import_node_fs40.existsSync)(gateDir) && !(0, import_node_fs40.existsSync)(verityDir)) {
|
|
21682
22397
|
return migrateProjectDirRename(root, gateDir, verityDir, actions);
|
|
21683
22398
|
}
|
|
21684
|
-
if ((0,
|
|
22399
|
+
if ((0, import_node_fs40.existsSync)(gateDir) && (0, import_node_fs40.existsSync)(verityDir)) {
|
|
21685
22400
|
return migrateProjectDirCarry(gateDir, verityDir, actions);
|
|
21686
22401
|
}
|
|
21687
22402
|
return false;
|
|
@@ -21702,13 +22417,13 @@ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
|
|
|
21702
22417
|
}
|
|
21703
22418
|
}
|
|
21704
22419
|
if (moved) {
|
|
21705
|
-
if ((0,
|
|
22420
|
+
if ((0, import_node_fs40.existsSync)(gateDir)) {
|
|
21706
22421
|
const carried = carryLegacyContents(gateDir, verityDir);
|
|
21707
22422
|
if (carried > 0) {
|
|
21708
22423
|
actions.push(`Carried ${carried} untracked legacy file(s) from .gate/ into .verity/`);
|
|
21709
22424
|
}
|
|
21710
22425
|
try {
|
|
21711
|
-
(0,
|
|
22426
|
+
(0, import_node_fs40.rmSync)(gateDir, { recursive: true, force: true });
|
|
21712
22427
|
} catch {
|
|
21713
22428
|
}
|
|
21714
22429
|
}
|
|
@@ -21724,7 +22439,7 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
|
|
|
21724
22439
|
actions.push(`Carried ${carried} legacy file(s) from .gate/ into .verity/`);
|
|
21725
22440
|
}
|
|
21726
22441
|
try {
|
|
21727
|
-
(0,
|
|
22442
|
+
(0, import_node_fs40.rmSync)(gateDir, { recursive: true, force: true });
|
|
21728
22443
|
} catch {
|
|
21729
22444
|
}
|
|
21730
22445
|
return carried > 0;
|
|
@@ -21733,9 +22448,9 @@ function migrateGlobalCredentials(home, actions) {
|
|
|
21733
22448
|
if (!home) return;
|
|
21734
22449
|
const gateCreds = (0, import_node_path28.join)(home, ".gate", "credentials");
|
|
21735
22450
|
const verityCreds = (0, import_node_path28.join)(home, ".verity", "credentials");
|
|
21736
|
-
if (!(0,
|
|
21737
|
-
if (!(0,
|
|
21738
|
-
(0,
|
|
22451
|
+
if (!(0, import_node_fs40.existsSync)(gateCreds)) return;
|
|
22452
|
+
if (!(0, import_node_fs40.existsSync)(verityCreds)) {
|
|
22453
|
+
(0, import_node_fs40.mkdirSync)((0, import_node_path28.join)(home, ".verity"), { recursive: true });
|
|
21739
22454
|
moveFile(gateCreds, verityCreds);
|
|
21740
22455
|
actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
|
|
21741
22456
|
return;
|
|
@@ -21758,7 +22473,7 @@ async function migrateLegacyHooks(root, actions) {
|
|
|
21758
22473
|
}
|
|
21759
22474
|
async function migrateClaudeMd(root, actions) {
|
|
21760
22475
|
const claudeMd = (0, import_node_path28.join)(root, "CLAUDE.md");
|
|
21761
|
-
const hadLegacyBlock = (0,
|
|
22476
|
+
const hadLegacyBlock = (0, import_node_fs40.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
|
|
21762
22477
|
if (!hadLegacyBlock) return;
|
|
21763
22478
|
try {
|
|
21764
22479
|
await ensureClaudeMdPointer(root);
|
|
@@ -21770,7 +22485,7 @@ async function migrateClaudeMd(root, actions) {
|
|
|
21770
22485
|
function migrateStandardFile(root, actions) {
|
|
21771
22486
|
const gateMd = (0, import_node_path28.join)(root, "GATE.md");
|
|
21772
22487
|
const verityMd = (0, import_node_path28.join)(root, "VERITY.md");
|
|
21773
|
-
if (!(0,
|
|
22488
|
+
if (!(0, import_node_fs40.existsSync)(gateMd) || (0, import_node_fs40.existsSync)(verityMd)) return;
|
|
21774
22489
|
let moved = false;
|
|
21775
22490
|
if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
|
|
21776
22491
|
try {
|
|
@@ -21782,12 +22497,12 @@ function migrateStandardFile(root, actions) {
|
|
|
21782
22497
|
if (!moved) moveFile(gateMd, verityMd);
|
|
21783
22498
|
const content = readFileSyncSafe(verityMd);
|
|
21784
22499
|
const refreshed = content.split("GATE.md").join("VERITY.md");
|
|
21785
|
-
if (refreshed !== content) (0,
|
|
22500
|
+
if (refreshed !== content) (0, import_node_fs40.writeFileSync)(verityMd, refreshed);
|
|
21786
22501
|
actions.push("Renamed GATE.md \u2192 VERITY.md");
|
|
21787
22502
|
}
|
|
21788
22503
|
async function migrateTelemetryHeaders(root, actions) {
|
|
21789
22504
|
const file = (0, import_node_path28.join)(root, ".claude", "settings.local.json");
|
|
21790
|
-
if (!(0,
|
|
22505
|
+
if (!(0, import_node_fs40.existsSync)(file)) return;
|
|
21791
22506
|
let settings;
|
|
21792
22507
|
try {
|
|
21793
22508
|
settings = JSON.parse(readFileSyncSafe(file) || "{}");
|
|
@@ -21835,14 +22550,14 @@ function mergeGlobalCredentials(gateCreds, verityCreds) {
|
|
|
21835
22550
|
}
|
|
21836
22551
|
if (toAppend.length > 0) {
|
|
21837
22552
|
const sep2 = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
|
|
21838
|
-
(0,
|
|
22553
|
+
(0, import_node_fs40.writeFileSync)(verityCreds, verityContent + sep2 + toAppend.join("\n") + "\n");
|
|
21839
22554
|
}
|
|
21840
|
-
(0,
|
|
22555
|
+
(0, import_node_fs40.rmSync)(gateCreds, { force: true });
|
|
21841
22556
|
return toAppend.length;
|
|
21842
22557
|
}
|
|
21843
22558
|
function readFileSyncSafe(path) {
|
|
21844
22559
|
try {
|
|
21845
|
-
return (0,
|
|
22560
|
+
return (0, import_node_fs40.readFileSync)(path, "utf-8");
|
|
21846
22561
|
} catch {
|
|
21847
22562
|
return "";
|
|
21848
22563
|
}
|
|
@@ -21857,35 +22572,35 @@ function hasStagedChanges(root) {
|
|
|
21857
22572
|
}
|
|
21858
22573
|
function moveDir(from, to) {
|
|
21859
22574
|
try {
|
|
21860
|
-
(0,
|
|
22575
|
+
(0, import_node_fs40.renameSync)(from, to);
|
|
21861
22576
|
} catch (err) {
|
|
21862
22577
|
if (err.code !== "EXDEV") throw err;
|
|
21863
|
-
(0,
|
|
21864
|
-
(0,
|
|
22578
|
+
(0, import_node_fs40.cpSync)(from, to, { recursive: true });
|
|
22579
|
+
(0, import_node_fs40.rmSync)(from, { recursive: true, force: true });
|
|
21865
22580
|
}
|
|
21866
22581
|
}
|
|
21867
22582
|
function moveFile(from, to) {
|
|
21868
22583
|
try {
|
|
21869
|
-
(0,
|
|
22584
|
+
(0, import_node_fs40.renameSync)(from, to);
|
|
21870
22585
|
} catch (err) {
|
|
21871
22586
|
if (err.code !== "EXDEV") throw err;
|
|
21872
|
-
(0,
|
|
21873
|
-
(0,
|
|
22587
|
+
(0, import_node_fs40.cpSync)(from, to);
|
|
22588
|
+
(0, import_node_fs40.rmSync)(from, { force: true });
|
|
21874
22589
|
}
|
|
21875
22590
|
}
|
|
21876
22591
|
function carryLegacyContents(gateDir, verityDir) {
|
|
21877
22592
|
let copied = 0;
|
|
21878
22593
|
const walk = (relDir) => {
|
|
21879
22594
|
const srcDir = (0, import_node_path28.join)(gateDir, relDir);
|
|
21880
|
-
for (const entry of (0,
|
|
22595
|
+
for (const entry of (0, import_node_fs40.readdirSync)(srcDir)) {
|
|
21881
22596
|
const rel = relDir ? (0, import_node_path28.join)(relDir, entry) : entry;
|
|
21882
22597
|
const src = (0, import_node_path28.join)(gateDir, rel);
|
|
21883
22598
|
const dest = (0, import_node_path28.join)(verityDir, rel);
|
|
21884
|
-
if ((0,
|
|
22599
|
+
if ((0, import_node_fs40.statSync)(src).isDirectory()) {
|
|
21885
22600
|
walk(rel);
|
|
21886
|
-
} else if (!(0,
|
|
21887
|
-
(0,
|
|
21888
|
-
(0,
|
|
22601
|
+
} else if (!(0, import_node_fs40.existsSync)(dest)) {
|
|
22602
|
+
(0, import_node_fs40.mkdirSync)((0, import_node_path28.dirname)(dest), { recursive: true });
|
|
22603
|
+
(0, import_node_fs40.cpSync)(src, dest);
|
|
21889
22604
|
copied++;
|
|
21890
22605
|
}
|
|
21891
22606
|
}
|
|
@@ -21896,20 +22611,20 @@ function carryLegacyContents(gateDir, verityDir) {
|
|
|
21896
22611
|
async function needsMigration(root = repoRoot()) {
|
|
21897
22612
|
const gateDir = (0, import_node_path28.join)(root, ".gate");
|
|
21898
22613
|
const verityDir = (0, import_node_path28.join)(root, ".verity");
|
|
21899
|
-
if ((0,
|
|
21900
|
-
if ((0,
|
|
21901
|
-
if ((0,
|
|
22614
|
+
if ((0, import_node_fs40.existsSync)(gateDir) && !(0, import_node_fs40.existsSync)(verityDir)) return true;
|
|
22615
|
+
if ((0, import_node_fs40.existsSync)(gateDir) && (0, import_node_fs40.existsSync)(verityDir)) {
|
|
22616
|
+
if ((0, import_node_fs40.existsSync)((0, import_node_path28.join)(gateDir, "credentials")) && !(0, import_node_fs40.existsSync)((0, import_node_path28.join)(verityDir, "credentials"))) {
|
|
21902
22617
|
return true;
|
|
21903
22618
|
}
|
|
21904
|
-
if ((0,
|
|
22619
|
+
if ((0, import_node_fs40.existsSync)((0, import_node_path28.join)(gateDir, "memory")) && !(0, import_node_fs40.existsSync)((0, import_node_path28.join)(verityDir, "memory"))) {
|
|
21905
22620
|
return true;
|
|
21906
22621
|
}
|
|
21907
22622
|
}
|
|
21908
22623
|
const claudeMd = (0, import_node_path28.join)(root, "CLAUDE.md");
|
|
21909
|
-
if ((0,
|
|
22624
|
+
if ((0, import_node_fs40.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
|
|
21910
22625
|
return true;
|
|
21911
22626
|
}
|
|
21912
|
-
if ((0,
|
|
22627
|
+
if ((0, import_node_fs40.existsSync)((0, import_node_path28.join)(root, "GATE.md")) && !(0, import_node_fs40.existsSync)((0, import_node_path28.join)(root, "VERITY.md"))) {
|
|
21913
22628
|
return true;
|
|
21914
22629
|
}
|
|
21915
22630
|
if (await hasLegacyHooksAt(root)) return true;
|
|
@@ -22053,7 +22768,7 @@ function resolveDataDir() {
|
|
|
22053
22768
|
// local dev: running from repo root
|
|
22054
22769
|
];
|
|
22055
22770
|
for (const candidate of candidates) {
|
|
22056
|
-
if ((0,
|
|
22771
|
+
if ((0, import_node_fs41.existsSync)((0, import_node_path29.join)(candidate, "skills"))) {
|
|
22057
22772
|
return candidate;
|
|
22058
22773
|
}
|
|
22059
22774
|
}
|
|
@@ -22069,7 +22784,7 @@ function registerInitCommand(program2) {
|
|
|
22069
22784
|
program2.command("init").description("Initialize Verity in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
|
|
22070
22785
|
const force = opts.force ?? false;
|
|
22071
22786
|
const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
|
|
22072
|
-
const isProject = projectMarkers.some((m) => (0,
|
|
22787
|
+
const isProject = projectMarkers.some((m) => (0, import_node_fs41.existsSync)(m));
|
|
22073
22788
|
if (!isProject) {
|
|
22074
22789
|
printError("No project detected in the current directory.");
|
|
22075
22790
|
printInfo('Run "verity init" from your project root.');
|
|
@@ -22139,14 +22854,14 @@ function registerInitCommand(program2) {
|
|
|
22139
22854
|
for (const skill of skills) {
|
|
22140
22855
|
const src = (0, import_node_path29.join)(skillsSource, skill);
|
|
22141
22856
|
const dest = (0, import_node_path29.join)(skillsDest, skill);
|
|
22142
|
-
if (!(0,
|
|
22857
|
+
if (!(0, import_node_fs41.existsSync)(src)) {
|
|
22143
22858
|
printWarn(` Skill data not found: ${skill}`);
|
|
22144
22859
|
continue;
|
|
22145
22860
|
}
|
|
22146
|
-
if ((0,
|
|
22861
|
+
if ((0, import_node_fs41.existsSync)(dest) && !force) {
|
|
22147
22862
|
const srcSkill = (0, import_node_path29.join)(src, "SKILL.md");
|
|
22148
22863
|
const destSkill = (0, import_node_path29.join)(dest, "SKILL.md");
|
|
22149
|
-
if ((0,
|
|
22864
|
+
if ((0, import_node_fs41.existsSync)(destSkill)) {
|
|
22150
22865
|
try {
|
|
22151
22866
|
const srcContent = await (0, import_promises13.readFile)(srcSkill, "utf-8");
|
|
22152
22867
|
const destContent = await (0, import_promises13.readFile)(destSkill, "utf-8");
|
|
@@ -22224,7 +22939,7 @@ function registerInitCommand(program2) {
|
|
|
22224
22939
|
}
|
|
22225
22940
|
|
|
22226
22941
|
// src/commands/uninstall.ts
|
|
22227
|
-
var
|
|
22942
|
+
var import_node_fs42 = require("node:fs");
|
|
22228
22943
|
var import_node_path30 = require("node:path");
|
|
22229
22944
|
var SKILL_NAMES = [
|
|
22230
22945
|
"verity-setup",
|
|
@@ -22245,10 +22960,10 @@ function registerUninstallCommand(program2) {
|
|
|
22245
22960
|
const skillsRoot = projectPath(".claude/skills");
|
|
22246
22961
|
for (const name of SKILL_NAMES) {
|
|
22247
22962
|
const dir = (0, import_node_path30.join)(skillsRoot, name);
|
|
22248
|
-
if ((0,
|
|
22963
|
+
if ((0, import_node_fs42.existsSync)(dir)) {
|
|
22249
22964
|
actions.push({
|
|
22250
22965
|
label: `Remove .claude/skills/${name}/`,
|
|
22251
|
-
apply: () => (0,
|
|
22966
|
+
apply: () => (0, import_node_fs42.rmSync)(dir, { recursive: true, force: true })
|
|
22252
22967
|
});
|
|
22253
22968
|
}
|
|
22254
22969
|
}
|
|
@@ -22262,24 +22977,24 @@ function registerUninstallCommand(program2) {
|
|
|
22262
22977
|
});
|
|
22263
22978
|
}
|
|
22264
22979
|
const verityDir = projectPath(VERITY_DIR);
|
|
22265
|
-
if ((0,
|
|
22980
|
+
if ((0, import_node_fs42.existsSync)(verityDir)) {
|
|
22266
22981
|
actions.push({
|
|
22267
22982
|
label: `Remove ${VERITY_DIR}/`,
|
|
22268
|
-
apply: () => (0,
|
|
22983
|
+
apply: () => (0, import_node_fs42.rmSync)(verityDir, { recursive: true, force: true })
|
|
22269
22984
|
});
|
|
22270
22985
|
}
|
|
22271
22986
|
if (!keepVerityMd) {
|
|
22272
22987
|
const verityMd = projectPath(VERITY_MD_FILE);
|
|
22273
|
-
if ((0,
|
|
22988
|
+
if ((0, import_node_fs42.existsSync)(verityMd)) {
|
|
22274
22989
|
actions.push({
|
|
22275
22990
|
label: `Remove ${VERITY_MD_FILE}`,
|
|
22276
|
-
apply: () => (0,
|
|
22991
|
+
apply: () => (0, import_node_fs42.rmSync)(verityMd, { force: true })
|
|
22277
22992
|
});
|
|
22278
22993
|
}
|
|
22279
22994
|
}
|
|
22280
22995
|
const cleanupEmptyDir = (path) => {
|
|
22281
|
-
if ((0,
|
|
22282
|
-
(0,
|
|
22996
|
+
if ((0, import_node_fs42.existsSync)(path) && (0, import_node_fs42.statSync)(path).isDirectory() && (0, import_node_fs42.readdirSync)(path).length === 0) {
|
|
22997
|
+
(0, import_node_fs42.rmdirSync)(path);
|
|
22283
22998
|
}
|
|
22284
22999
|
};
|
|
22285
23000
|
actions.push({
|
|
@@ -22291,10 +23006,10 @@ function registerUninstallCommand(program2) {
|
|
|
22291
23006
|
});
|
|
22292
23007
|
const home = process.env.HOME ?? "";
|
|
22293
23008
|
const globalVerityDir = (0, import_node_path30.join)(home, ".verity");
|
|
22294
|
-
if (purgeGlobal && (0,
|
|
23009
|
+
if (purgeGlobal && (0, import_node_fs42.existsSync)(globalVerityDir)) {
|
|
22295
23010
|
actions.push({
|
|
22296
23011
|
label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
|
|
22297
|
-
apply: () => (0,
|
|
23012
|
+
apply: () => (0, import_node_fs42.rmSync)(globalVerityDir, { recursive: true, force: true })
|
|
22298
23013
|
});
|
|
22299
23014
|
}
|
|
22300
23015
|
if (actions.length === 0) {
|
|
@@ -22488,7 +23203,7 @@ function registerTaskCommands(program2) {
|
|
|
22488
23203
|
}
|
|
22489
23204
|
|
|
22490
23205
|
// src/commands/reset.ts
|
|
22491
|
-
var
|
|
23206
|
+
var import_node_fs43 = require("node:fs");
|
|
22492
23207
|
var import_node_path31 = require("node:path");
|
|
22493
23208
|
function registerResetCommand(program2) {
|
|
22494
23209
|
program2.command("reset").description("Close the current task and clear transient state").option("--keep-task", "Only purge caches; leave the current task open").option("--all", "Also purge diagnostic logs (.verity/.logs/)").action(async (opts) => {
|
|
@@ -22526,11 +23241,11 @@ function registerResetCommand(program2) {
|
|
|
22526
23241
|
}
|
|
22527
23242
|
const cacheDir = projectPath(CACHE_DIR);
|
|
22528
23243
|
let purged = 0;
|
|
22529
|
-
if ((0,
|
|
22530
|
-
for (const entry of (0,
|
|
23244
|
+
if ((0, import_node_fs43.existsSync)(cacheDir)) {
|
|
23245
|
+
for (const entry of (0, import_node_fs43.readdirSync)(cacheDir)) {
|
|
22531
23246
|
if (entry.startsWith("pending-")) {
|
|
22532
23247
|
try {
|
|
22533
|
-
(0,
|
|
23248
|
+
(0, import_node_fs43.unlinkSync)((0, import_node_path31.join)(cacheDir, entry));
|
|
22534
23249
|
purged++;
|
|
22535
23250
|
} catch {
|
|
22536
23251
|
}
|
|
@@ -22545,19 +23260,19 @@ function registerResetCommand(program2) {
|
|
|
22545
23260
|
projectPath(`${VERITY_DIR}/.last-analysis`)
|
|
22546
23261
|
];
|
|
22547
23262
|
for (const file of filesToClear) {
|
|
22548
|
-
if ((0,
|
|
23263
|
+
if ((0, import_node_fs43.existsSync)(file)) {
|
|
22549
23264
|
try {
|
|
22550
|
-
(0,
|
|
23265
|
+
(0, import_node_fs43.writeFileSync)(file, "");
|
|
22551
23266
|
} catch {
|
|
22552
23267
|
}
|
|
22553
23268
|
}
|
|
22554
23269
|
}
|
|
22555
23270
|
if (opts.all) {
|
|
22556
23271
|
const logsDir = projectPath(`${VERITY_DIR}/.logs`);
|
|
22557
|
-
if ((0,
|
|
22558
|
-
for (const entry of (0,
|
|
23272
|
+
if ((0, import_node_fs43.existsSync)(logsDir)) {
|
|
23273
|
+
for (const entry of (0, import_node_fs43.readdirSync)(logsDir)) {
|
|
22559
23274
|
try {
|
|
22560
|
-
(0,
|
|
23275
|
+
(0, import_node_fs43.unlinkSync)((0, import_node_path31.join)(logsDir, entry));
|
|
22561
23276
|
} catch {
|
|
22562
23277
|
}
|
|
22563
23278
|
}
|
|
@@ -22865,8 +23580,8 @@ function registerTelemetryCommands(program2) {
|
|
|
22865
23580
|
}
|
|
22866
23581
|
|
|
22867
23582
|
// 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.
|
|
23583
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.31.0-experimental.6bc3b1b").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async (_thisCommand, actionCommand) => {
|
|
23584
|
+
installStderrLog(actionCommand.name(), process.argv.slice(2), "0.31.0-experimental.6bc3b1b");
|
|
22870
23585
|
setUserNamedServiceUrl(program.opts().serviceUrl);
|
|
22871
23586
|
try {
|
|
22872
23587
|
await foldLegacyLocalCredential();
|
|
@@ -22889,6 +23604,8 @@ registerAnalyzeCommand(program);
|
|
|
22889
23604
|
registerBaselineCommands(program);
|
|
22890
23605
|
registerReviewCommand(program);
|
|
22891
23606
|
registerGuardCommand(program);
|
|
23607
|
+
registerIgnoreCommand(program);
|
|
23608
|
+
registerWaiveCommand(program);
|
|
22892
23609
|
registerInitCommand(program);
|
|
22893
23610
|
registerUninstallCommand(program);
|
|
22894
23611
|
registerTaskCommands(program);
|