@codacy/verity-cli 0.30.1-experimental.e2137b9 → 0.31.0-experimental.48d33ea
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 +891 -323
- 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
|
}
|
|
@@ -10477,7 +10478,8 @@ var REVIEWABLE_FILENAMES = /* @__PURE__ */ new Set([
|
|
|
10477
10478
|
"Makefile",
|
|
10478
10479
|
"Dockerfile",
|
|
10479
10480
|
"Jenkinsfile",
|
|
10480
|
-
"Vagrantfile"
|
|
10481
|
+
"Vagrantfile",
|
|
10482
|
+
".verityignore"
|
|
10481
10483
|
]);
|
|
10482
10484
|
var REVIEWABLE_PATH_PATTERNS = [
|
|
10483
10485
|
/\.circleci\//,
|
|
@@ -10507,6 +10509,7 @@ function githubAppInstallUrl(accountId) {
|
|
|
10507
10509
|
return accountId != null ? `https://github.com/apps/${GITHUB_APP_SLUG}/installations/new/permissions?target_id=${accountId}` : GITHUB_APP_INSTALL_URL;
|
|
10508
10510
|
}
|
|
10509
10511
|
var ADVISORY_EPISODE_FILE = `${VERITY_DIR}/.advisory-episode`;
|
|
10512
|
+
var IGNORE_DECLARATION_FILE = `${VERITY_DIR}/.ignore-declaration`;
|
|
10510
10513
|
|
|
10511
10514
|
// src/lib/output.ts
|
|
10512
10515
|
var RED = "\x1B[0;31m";
|
|
@@ -13530,6 +13533,42 @@ var LEGACY_MD_END = "<!-- gate-memory:end -->";
|
|
|
13530
13533
|
var LEGACY_PRESERVE_START = "<!-- gate-memory:preserve -->";
|
|
13531
13534
|
var LEGACY_PRESERVE_END = "<!-- /gate-memory:preserve -->";
|
|
13532
13535
|
var CLAUDE_MD_PROSE = [
|
|
13536
|
+
"## Project Memory",
|
|
13537
|
+
"",
|
|
13538
|
+
"This project has a knowledge graph maintained at `.verity/memory/`. Before starting",
|
|
13539
|
+
"non-trivial work, scan `.verity/memory/index.md` for decisions, gotchas, and patterns",
|
|
13540
|
+
"that may apply to the change you are about to make. Open specific node files via",
|
|
13541
|
+
"the Read tool when the title or scope suggests relevance.",
|
|
13542
|
+
"",
|
|
13543
|
+
"The graph is auto-maintained by Verity. Files at `.verity/memory/_archive/` are",
|
|
13544
|
+
"superseded \u2014 ignore them unless investigating history.",
|
|
13545
|
+
"",
|
|
13546
|
+
"> Durable, hand-curated guidance goes in the preserve region below (it survives",
|
|
13547
|
+
"> regeneration) or anywhere OUTSIDE these markers. Everything else between the",
|
|
13548
|
+
"> markers is tool-owned and overwritten on each run.",
|
|
13549
|
+
"",
|
|
13550
|
+
"## Housekeeping Turns",
|
|
13551
|
+
"",
|
|
13552
|
+
"When a turn will be pure housekeeping \u2014 pulling, installing dependencies,",
|
|
13553
|
+
"rebasing, a formatting sweep you are not authoring \u2014 declare it BEFORE doing it:",
|
|
13554
|
+
"",
|
|
13555
|
+
"```bash",
|
|
13556
|
+
'verity ignore --turn --agent --reason "pulling latest before starting"',
|
|
13557
|
+
"```",
|
|
13558
|
+
"",
|
|
13559
|
+
"This skips the review for that turn, which saves the turn Verity would",
|
|
13560
|
+
"otherwise spend saying it had nothing to say. Use `--for 30m` instead of",
|
|
13561
|
+
"`--turn` when a single piece of housekeeping spans several turns.",
|
|
13562
|
+
"",
|
|
13563
|
+
"**It is a claim about the turn, not a way to silence review.** The declaration",
|
|
13564
|
+
"is checked against what the turn actually did: if anything is authored \u2014 by you,",
|
|
13565
|
+
"by a subagent, or by a shell command that can write files \u2014 it voids, the review",
|
|
13566
|
+
"runs anyway, and the broken declaration is reported. So declare housekeeping you",
|
|
13567
|
+
"are about to do, never work you have already done, and never as a way to get past",
|
|
13568
|
+
"a finding. Declarations are budgeted per session and every one is recorded with",
|
|
13569
|
+
"its reason."
|
|
13570
|
+
].join("\n");
|
|
13571
|
+
var CLAUDE_MD_PROSE_PRE_IGNORE = [
|
|
13533
13572
|
"## Project Memory",
|
|
13534
13573
|
"",
|
|
13535
13574
|
"This project has a knowledge graph maintained at `.verity/memory/`. Before starting",
|
|
@@ -13654,7 +13693,7 @@ function extractPreserveContent(interior) {
|
|
|
13654
13693
|
}
|
|
13655
13694
|
function stripKnownProse(interior) {
|
|
13656
13695
|
const trimmed = interior.replace(/^\n+/, "");
|
|
13657
|
-
for (const prose of [CLAUDE_MD_PROSE, CLAUDE_MD_PROSE_LEGACY]) {
|
|
13696
|
+
for (const prose of [CLAUDE_MD_PROSE, CLAUDE_MD_PROSE_PRE_IGNORE, CLAUDE_MD_PROSE_LEGACY]) {
|
|
13658
13697
|
if (trimmed.startsWith(prose)) return trimmed.slice(prose.length);
|
|
13659
13698
|
}
|
|
13660
13699
|
return trimmed;
|
|
@@ -16270,7 +16309,145 @@ async function readHookStdin() {
|
|
|
16270
16309
|
|
|
16271
16310
|
// src/commands/standard.ts
|
|
16272
16311
|
var import_promises9 = require("node:fs/promises");
|
|
16312
|
+
var import_node_fs20 = require("node:fs");
|
|
16273
16313
|
var import_yaml = __toESM(require_dist());
|
|
16314
|
+
|
|
16315
|
+
// src/lib/verityignore.ts
|
|
16316
|
+
var import_node_fs19 = require("node:fs");
|
|
16317
|
+
var EMPTY = { rules: [], securityOverlap: [], problems: [] };
|
|
16318
|
+
var SECURITY_PROBES = [
|
|
16319
|
+
".env",
|
|
16320
|
+
".env.local",
|
|
16321
|
+
".env.production",
|
|
16322
|
+
"config/.env",
|
|
16323
|
+
"services/api/.env",
|
|
16324
|
+
"package-lock.json",
|
|
16325
|
+
"yarn.lock",
|
|
16326
|
+
"pnpm-lock.yaml",
|
|
16327
|
+
"Cargo.lock",
|
|
16328
|
+
"go.sum",
|
|
16329
|
+
"Gemfile.lock",
|
|
16330
|
+
"Dockerfile",
|
|
16331
|
+
"docker/Dockerfile"
|
|
16332
|
+
];
|
|
16333
|
+
function isSecuritySensitive(path) {
|
|
16334
|
+
return SECURITY_PATTERNS.some((p) => p.test(path));
|
|
16335
|
+
}
|
|
16336
|
+
function compile(pattern) {
|
|
16337
|
+
let p = pattern;
|
|
16338
|
+
const dirOnly = p.endsWith("/");
|
|
16339
|
+
if (dirOnly) p = p.slice(0, -1);
|
|
16340
|
+
const anchored = p.includes("/");
|
|
16341
|
+
if (p.startsWith("/")) p = p.slice(1);
|
|
16342
|
+
const base = anchored ? p : `**/${p}`;
|
|
16343
|
+
const forms = dirOnly ? [`${base}/**/*`] : [base, `${base}/**`];
|
|
16344
|
+
const regexes = [];
|
|
16345
|
+
for (const f of forms) {
|
|
16346
|
+
try {
|
|
16347
|
+
regexes.push(globToRegex(f));
|
|
16348
|
+
} catch {
|
|
16349
|
+
}
|
|
16350
|
+
}
|
|
16351
|
+
if (regexes.length === 0) return () => false;
|
|
16352
|
+
return (path) => regexes.some((r) => r.test(path));
|
|
16353
|
+
}
|
|
16354
|
+
function parseVerityIgnore(content) {
|
|
16355
|
+
const rules = [];
|
|
16356
|
+
const problems = [];
|
|
16357
|
+
const lines = content.split("\n");
|
|
16358
|
+
for (let i = 0; i < lines.length; i++) {
|
|
16359
|
+
const lineNo = i + 1;
|
|
16360
|
+
let raw = lines[i];
|
|
16361
|
+
raw = raw.replace(/(?<!\\)\s+$/, "");
|
|
16362
|
+
if (raw.length === 0) continue;
|
|
16363
|
+
if (raw.startsWith("#")) continue;
|
|
16364
|
+
let negated = false;
|
|
16365
|
+
if (raw.startsWith("!")) {
|
|
16366
|
+
negated = true;
|
|
16367
|
+
raw = raw.slice(1);
|
|
16368
|
+
} else if (raw.startsWith("\\!") || raw.startsWith("\\#")) {
|
|
16369
|
+
raw = raw.slice(1);
|
|
16370
|
+
}
|
|
16371
|
+
if (raw.length === 0) {
|
|
16372
|
+
problems.push(`line ${lineNo}: "!" with no pattern after it`);
|
|
16373
|
+
continue;
|
|
16374
|
+
}
|
|
16375
|
+
if (raw === "**" || raw === "*" || raw === "/" || raw === "**/*") {
|
|
16376
|
+
problems.push(
|
|
16377
|
+
`line ${lineNo}: "${raw}" would exclude the whole repository from review \u2014 refused. List the directories you mean instead.`
|
|
16378
|
+
);
|
|
16379
|
+
continue;
|
|
16380
|
+
}
|
|
16381
|
+
rules.push({ raw, line: lineNo, negated, test: compile(raw) });
|
|
16382
|
+
}
|
|
16383
|
+
const byRule = /* @__PURE__ */ new Map();
|
|
16384
|
+
for (const probe of SECURITY_PROBES) {
|
|
16385
|
+
let responsible = null;
|
|
16386
|
+
for (const rule of rules) {
|
|
16387
|
+
if (rule.test(probe)) responsible = rule.negated ? null : rule;
|
|
16388
|
+
}
|
|
16389
|
+
if (!responsible) continue;
|
|
16390
|
+
const entry = byRule.get(responsible.line) ?? { raw: responsible.raw, line: responsible.line, hides: [] };
|
|
16391
|
+
entry.hides.push(probe);
|
|
16392
|
+
byRule.set(responsible.line, entry);
|
|
16393
|
+
}
|
|
16394
|
+
const securityOverlap = [...byRule.values()].sort((a, b) => a.line - b.line);
|
|
16395
|
+
return { rules, securityOverlap, problems };
|
|
16396
|
+
}
|
|
16397
|
+
function decide(rules, path) {
|
|
16398
|
+
let excluded = false;
|
|
16399
|
+
for (const rule of rules) {
|
|
16400
|
+
if (rule.test(path)) excluded = !rule.negated;
|
|
16401
|
+
}
|
|
16402
|
+
return excluded;
|
|
16403
|
+
}
|
|
16404
|
+
function isIgnored(ig, path) {
|
|
16405
|
+
return decide(ig.rules, path);
|
|
16406
|
+
}
|
|
16407
|
+
function loadVerityIgnore() {
|
|
16408
|
+
const file = projectPath(VERITYIGNORE_FILE);
|
|
16409
|
+
if (!(0, import_node_fs19.existsSync)(file)) return EMPTY;
|
|
16410
|
+
try {
|
|
16411
|
+
return parseVerityIgnore((0, import_node_fs19.readFileSync)(file, "utf-8"));
|
|
16412
|
+
} catch {
|
|
16413
|
+
return EMPTY;
|
|
16414
|
+
}
|
|
16415
|
+
}
|
|
16416
|
+
function partitionIgnored(paths, ig) {
|
|
16417
|
+
const suspended = paths.some((p) => p === VERITYIGNORE_FILE);
|
|
16418
|
+
if (suspended || ig.rules.length === 0) {
|
|
16419
|
+
return { rules: ig.rules.length, kept: [...paths], ignored: [], securityExcluded: [], suspended };
|
|
16420
|
+
}
|
|
16421
|
+
const kept = [];
|
|
16422
|
+
const ignored = [];
|
|
16423
|
+
for (const p of paths) {
|
|
16424
|
+
if (p !== VERITYIGNORE_FILE && isIgnored(ig, p)) ignored.push(p);
|
|
16425
|
+
else kept.push(p);
|
|
16426
|
+
}
|
|
16427
|
+
return {
|
|
16428
|
+
rules: ig.rules.length,
|
|
16429
|
+
kept,
|
|
16430
|
+
ignored,
|
|
16431
|
+
securityExcluded: ignored.filter(isSecuritySensitive),
|
|
16432
|
+
suspended: false
|
|
16433
|
+
};
|
|
16434
|
+
}
|
|
16435
|
+
function ignoreShare(kept, ignored) {
|
|
16436
|
+
const total = kept + ignored;
|
|
16437
|
+
if (total === 0) return 0;
|
|
16438
|
+
return Math.round(ignored / total * 1e3) / 1e3;
|
|
16439
|
+
}
|
|
16440
|
+
function describeSecurityOverlap(ig) {
|
|
16441
|
+
if (ig.securityOverlap.length === 0) return null;
|
|
16442
|
+
const lines = ig.securityOverlap.map(
|
|
16443
|
+
(o) => ` line ${o.line}: "${o.raw}" would hide ${o.hides.slice(0, 3).join(", ")}` + (o.hides.length > 3 ? ` (+${o.hides.length - 3} more)` : "")
|
|
16444
|
+
);
|
|
16445
|
+
return `.verityignore: ${ig.securityOverlap.length} pattern(s) can hide security-sensitive files from review:
|
|
16446
|
+
${lines.join("\n")}
|
|
16447
|
+
These are still excluded \u2014 this is a warning, not a refusal. Add a \`!\` rule to keep one in scope, e.g. \`!.env*\`.`;
|
|
16448
|
+
}
|
|
16449
|
+
|
|
16450
|
+
// src/commands/standard.ts
|
|
16274
16451
|
function registerStandardCommands(program2) {
|
|
16275
16452
|
const standard = program2.command("standard").description("Manage the project Standard");
|
|
16276
16453
|
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 +16476,25 @@ function registerStandardCommands(program2) {
|
|
|
16299
16476
|
printError(`Invalid YAML in ${opts.file}: ${err.message}`);
|
|
16300
16477
|
process.exit(1);
|
|
16301
16478
|
}
|
|
16479
|
+
let ignoreRaw = null;
|
|
16480
|
+
const ignorePath = projectPath(VERITYIGNORE_FILE);
|
|
16481
|
+
if ((0, import_node_fs20.existsSync)(ignorePath)) {
|
|
16482
|
+
try {
|
|
16483
|
+
ignoreRaw = (0, import_node_fs20.readFileSync)(ignorePath, "utf-8");
|
|
16484
|
+
const overlap = describeSecurityOverlap(parseVerityIgnore(ignoreRaw));
|
|
16485
|
+
if (overlap) printWarn(overlap);
|
|
16486
|
+
} catch {
|
|
16487
|
+
}
|
|
16488
|
+
}
|
|
16302
16489
|
const result = await apiRequest({
|
|
16303
16490
|
method: "POST",
|
|
16304
16491
|
path: "/standards",
|
|
16305
16492
|
serviceUrl: urlResult.data,
|
|
16306
16493
|
token: tokenResult.data.token,
|
|
16307
|
-
body: {
|
|
16494
|
+
body: {
|
|
16495
|
+
content: ignoreRaw === null ? content : { ...content, verityignore: ignoreRaw },
|
|
16496
|
+
created_by: opts.createdBy
|
|
16497
|
+
},
|
|
16308
16498
|
verbose: globals.verbose
|
|
16309
16499
|
});
|
|
16310
16500
|
if (!result.ok) {
|
|
@@ -16522,6 +16712,309 @@ function formatRunDetail(run) {
|
|
|
16522
16712
|
return lines;
|
|
16523
16713
|
}
|
|
16524
16714
|
|
|
16715
|
+
// src/lib/ignore-declaration.ts
|
|
16716
|
+
var import_node_fs22 = require("node:fs");
|
|
16717
|
+
|
|
16718
|
+
// src/lib/debounce.ts
|
|
16719
|
+
var import_node_fs21 = require("node:fs");
|
|
16720
|
+
var import_node_crypto10 = require("node:crypto");
|
|
16721
|
+
function scopedFile(base, sessionId) {
|
|
16722
|
+
if (!sessionId) return base;
|
|
16723
|
+
return `${base}.${(0, import_node_crypto10.createHash)("sha1").update(sessionId).digest("hex").slice(0, 12)}`;
|
|
16724
|
+
}
|
|
16725
|
+
function checkDebounce(debounceSeconds = DEBOUNCE_SECONDS, sessionId) {
|
|
16726
|
+
const file = scopedFile(DEBOUNCE_FILE, sessionId);
|
|
16727
|
+
if (!(0, import_node_fs21.existsSync)(file)) return null;
|
|
16728
|
+
try {
|
|
16729
|
+
const lastTs = parseInt((0, import_node_fs21.readFileSync)(file, "utf-8").trim(), 10);
|
|
16730
|
+
const nowTs = Math.floor(Date.now() / 1e3);
|
|
16731
|
+
const elapsed = nowTs - lastTs;
|
|
16732
|
+
if (elapsed < debounceSeconds) {
|
|
16733
|
+
return `Debounced \u2014 last analysis was ${elapsed}s ago`;
|
|
16734
|
+
}
|
|
16735
|
+
} catch {
|
|
16736
|
+
}
|
|
16737
|
+
return null;
|
|
16738
|
+
}
|
|
16739
|
+
function checkMtime(files, bypassForRecentCommits, sessionId) {
|
|
16740
|
+
if (bypassForRecentCommits) return null;
|
|
16741
|
+
const file = scopedFile(DEBOUNCE_FILE, sessionId);
|
|
16742
|
+
if (!(0, import_node_fs21.existsSync)(file)) return null;
|
|
16743
|
+
let debounceTime;
|
|
16744
|
+
try {
|
|
16745
|
+
debounceTime = (0, import_node_fs21.statSync)(file).mtimeMs;
|
|
16746
|
+
} catch {
|
|
16747
|
+
return null;
|
|
16748
|
+
}
|
|
16749
|
+
for (const f of files) {
|
|
16750
|
+
const resolved = resolveFile(f);
|
|
16751
|
+
if (!resolved) continue;
|
|
16752
|
+
try {
|
|
16753
|
+
const stat3 = (0, import_node_fs21.statSync)(resolved);
|
|
16754
|
+
if (stat3.mtimeMs > debounceTime) {
|
|
16755
|
+
return null;
|
|
16756
|
+
}
|
|
16757
|
+
} catch {
|
|
16758
|
+
continue;
|
|
16759
|
+
}
|
|
16760
|
+
}
|
|
16761
|
+
return "No files modified since last analysis";
|
|
16762
|
+
}
|
|
16763
|
+
function computeContentHash(files) {
|
|
16764
|
+
const hash = (0, import_node_crypto10.createHash)("sha1");
|
|
16765
|
+
const sorted = [...files].sort();
|
|
16766
|
+
for (const f of sorted) {
|
|
16767
|
+
const resolved = resolveFile(f) ?? f;
|
|
16768
|
+
try {
|
|
16769
|
+
if ((0, import_node_fs21.existsSync)(resolved)) {
|
|
16770
|
+
hash.update((0, import_node_fs21.readFileSync)(resolved));
|
|
16771
|
+
}
|
|
16772
|
+
} catch {
|
|
16773
|
+
}
|
|
16774
|
+
}
|
|
16775
|
+
return hash.digest("hex");
|
|
16776
|
+
}
|
|
16777
|
+
function checkContentHash(files, sessionId) {
|
|
16778
|
+
const hash = computeContentHash(files);
|
|
16779
|
+
const file = scopedFile(HASH_FILE, sessionId);
|
|
16780
|
+
if ((0, import_node_fs21.existsSync)(file)) {
|
|
16781
|
+
try {
|
|
16782
|
+
const storedHash = (0, import_node_fs21.readFileSync)(file, "utf-8").trim();
|
|
16783
|
+
if (hash === storedHash) {
|
|
16784
|
+
return { skip: "No source changes since last analysis", hash };
|
|
16785
|
+
}
|
|
16786
|
+
} catch {
|
|
16787
|
+
}
|
|
16788
|
+
}
|
|
16789
|
+
return { skip: null, hash };
|
|
16790
|
+
}
|
|
16791
|
+
function recordAnalysisStart(sessionId) {
|
|
16792
|
+
(0, import_node_fs21.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
16793
|
+
(0, import_node_fs21.writeFileSync)(scopedFile(DEBOUNCE_FILE, sessionId), String(Math.floor(Date.now() / 1e3)));
|
|
16794
|
+
}
|
|
16795
|
+
function recordPassHash(hash, sessionId) {
|
|
16796
|
+
(0, import_node_fs21.writeFileSync)(scopedFile(HASH_FILE, sessionId), hash);
|
|
16797
|
+
}
|
|
16798
|
+
function narrowToRecent(files, sessionId) {
|
|
16799
|
+
const file = scopedFile(DEBOUNCE_FILE, sessionId);
|
|
16800
|
+
if (!(0, import_node_fs21.existsSync)(file)) return files;
|
|
16801
|
+
let debounceTime;
|
|
16802
|
+
try {
|
|
16803
|
+
debounceTime = (0, import_node_fs21.statSync)(file).mtimeMs;
|
|
16804
|
+
} catch {
|
|
16805
|
+
return files;
|
|
16806
|
+
}
|
|
16807
|
+
const recent = files.filter((f) => {
|
|
16808
|
+
try {
|
|
16809
|
+
return (0, import_node_fs21.existsSync)(f) && (0, import_node_fs21.statSync)(f).mtimeMs > debounceTime;
|
|
16810
|
+
} catch {
|
|
16811
|
+
return false;
|
|
16812
|
+
}
|
|
16813
|
+
});
|
|
16814
|
+
return recent.length > 0 ? recent : files;
|
|
16815
|
+
}
|
|
16816
|
+
function readIteration(currentCommit, _contentHash) {
|
|
16817
|
+
return Math.max(1, readBlockState(currentCommit).attempts);
|
|
16818
|
+
}
|
|
16819
|
+
var NO_BLOCKS = { attempts: 0, blocks: 0, fingerprint: null };
|
|
16820
|
+
function readBlockState(currentCommit, opts) {
|
|
16821
|
+
if (opts?.newUserPrompt) return NO_BLOCKS;
|
|
16822
|
+
if (!(0, import_node_fs21.existsSync)(ITERATION_FILE)) return NO_BLOCKS;
|
|
16823
|
+
try {
|
|
16824
|
+
const stored = (0, import_node_fs21.readFileSync)(ITERATION_FILE, "utf-8").trim();
|
|
16825
|
+
const parsed = stored.startsWith("{") ? parseJsonState(stored) : parseLegacyState(stored);
|
|
16826
|
+
if (!parsed) return NO_BLOCKS;
|
|
16827
|
+
if (parsed.commit !== currentCommit) return NO_BLOCKS;
|
|
16828
|
+
if (parsed.ts > 0 && Math.floor(Date.now() / 1e3) - parsed.ts > 600) return NO_BLOCKS;
|
|
16829
|
+
return { attempts: parsed.attempts, blocks: parsed.blocks, fingerprint: parsed.fingerprint };
|
|
16830
|
+
} catch {
|
|
16831
|
+
return NO_BLOCKS;
|
|
16832
|
+
}
|
|
16833
|
+
}
|
|
16834
|
+
function parseJsonState(raw) {
|
|
16835
|
+
const o = JSON.parse(raw);
|
|
16836
|
+
const attempts = typeof o.attempts === "number" ? o.attempts : NaN;
|
|
16837
|
+
if (isNaN(attempts)) return null;
|
|
16838
|
+
return {
|
|
16839
|
+
attempts,
|
|
16840
|
+
blocks: typeof o.blocks === "number" ? o.blocks : attempts,
|
|
16841
|
+
fingerprint: typeof o.fingerprint === "string" && o.fingerprint ? o.fingerprint : null,
|
|
16842
|
+
commit: typeof o.commit === "string" ? o.commit : "",
|
|
16843
|
+
ts: typeof o.ts === "number" ? o.ts : 0
|
|
16844
|
+
};
|
|
16845
|
+
}
|
|
16846
|
+
function parseLegacyState(raw) {
|
|
16847
|
+
const parts = raw.split(":");
|
|
16848
|
+
const n = parseInt(parts[0], 10);
|
|
16849
|
+
if (isNaN(n)) return null;
|
|
16850
|
+
return {
|
|
16851
|
+
attempts: n,
|
|
16852
|
+
// The old file has no separate block count; the old counter is the closest
|
|
16853
|
+
// honest answer, and it errs toward releasing sooner rather than later.
|
|
16854
|
+
blocks: n,
|
|
16855
|
+
fingerprint: parts.slice(3).join(":") || null,
|
|
16856
|
+
commit: parts[1] ?? "",
|
|
16857
|
+
ts: parseInt(parts[2] ?? "0", 10)
|
|
16858
|
+
};
|
|
16859
|
+
}
|
|
16860
|
+
function findingsFingerprint(findings) {
|
|
16861
|
+
const keys = findings.map((f) => `${String(f.pattern_id ?? "?")}|${String(f.file ?? "?")}`).filter((k) => k !== "?|?");
|
|
16862
|
+
return [...new Set(keys)].sort().join(",");
|
|
16863
|
+
}
|
|
16864
|
+
function isSameProblem(previous, current) {
|
|
16865
|
+
if (!previous || !current) return false;
|
|
16866
|
+
const prev = new Set(previous.split(","));
|
|
16867
|
+
return current.split(",").some((k) => prev.has(k));
|
|
16868
|
+
}
|
|
16869
|
+
function writeBlockState(commit, state) {
|
|
16870
|
+
(0, import_node_fs21.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
16871
|
+
(0, import_node_fs21.writeFileSync)(
|
|
16872
|
+
ITERATION_FILE,
|
|
16873
|
+
JSON.stringify({
|
|
16874
|
+
v: 2,
|
|
16875
|
+
attempts: state.attempts,
|
|
16876
|
+
blocks: state.blocks,
|
|
16877
|
+
commit,
|
|
16878
|
+
ts: Math.floor(Date.now() / 1e3),
|
|
16879
|
+
fingerprint: state.fingerprint ?? void 0
|
|
16880
|
+
})
|
|
16881
|
+
);
|
|
16882
|
+
}
|
|
16883
|
+
function resetBlockState(commit) {
|
|
16884
|
+
writeBlockState(commit, { attempts: 0, blocks: 0, fingerprint: null });
|
|
16885
|
+
}
|
|
16886
|
+
|
|
16887
|
+
// src/lib/ignore-declaration.ts
|
|
16888
|
+
var IGNORE_BUDGET = 3;
|
|
16889
|
+
var MAX_WINDOW_SECONDS = 60 * 60;
|
|
16890
|
+
var TURN_FUSE_SECONDS = 10 * 60;
|
|
16891
|
+
function parseDuration(input) {
|
|
16892
|
+
const trimmed = input.trim().toLowerCase();
|
|
16893
|
+
const m = /^(\d+)(s|m|h)?$/.exec(trimmed);
|
|
16894
|
+
if (!m) {
|
|
16895
|
+
return { ok: false, error: `Could not read "${input}" as a duration. Use 30m, 45s, or 1h.` };
|
|
16896
|
+
}
|
|
16897
|
+
const n = parseInt(m[1], 10);
|
|
16898
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
16899
|
+
return { ok: false, error: `A duration must be a positive number of seconds, minutes or hours \u2014 got "${input}".` };
|
|
16900
|
+
}
|
|
16901
|
+
const unit = m[2] ?? "m";
|
|
16902
|
+
const seconds = unit === "s" ? n : unit === "h" ? n * 3600 : n * 60;
|
|
16903
|
+
if (seconds > MAX_WINDOW_SECONDS) {
|
|
16904
|
+
return {
|
|
16905
|
+
ok: false,
|
|
16906
|
+
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.`
|
|
16907
|
+
};
|
|
16908
|
+
}
|
|
16909
|
+
return { ok: true, seconds };
|
|
16910
|
+
}
|
|
16911
|
+
function resolveActive(state, now) {
|
|
16912
|
+
if (!state?.active) return null;
|
|
16913
|
+
if (state.active.expires <= now) return null;
|
|
16914
|
+
return state.active;
|
|
16915
|
+
}
|
|
16916
|
+
function verifyDeclaration(input) {
|
|
16917
|
+
const d = input.declaration;
|
|
16918
|
+
if (!d) return { honoured: false, void: false };
|
|
16919
|
+
if (!input.authorshipIsObservable) {
|
|
16920
|
+
return {
|
|
16921
|
+
honoured: false,
|
|
16922
|
+
void: true,
|
|
16923
|
+
why: "the transcript window could not show what this turn did, so the declaration could not be checked"
|
|
16924
|
+
};
|
|
16925
|
+
}
|
|
16926
|
+
if (input.commandRecordTruncated) {
|
|
16927
|
+
return {
|
|
16928
|
+
honoured: false,
|
|
16929
|
+
void: true,
|
|
16930
|
+
why: "the record of commands run this turn was incomplete, so the declaration could not be checked"
|
|
16931
|
+
};
|
|
16932
|
+
}
|
|
16933
|
+
if (input.agentAuthoredFiles > 0) {
|
|
16934
|
+
return {
|
|
16935
|
+
honoured: false,
|
|
16936
|
+
void: true,
|
|
16937
|
+
why: `${input.agentAuthoredFiles} file${input.agentAuthoredFiles === 1 ? " was" : "s were"} authored during it`
|
|
16938
|
+
};
|
|
16939
|
+
}
|
|
16940
|
+
if (input.subagents > 0) {
|
|
16941
|
+
return {
|
|
16942
|
+
honoured: false,
|
|
16943
|
+
void: true,
|
|
16944
|
+
why: "work was delegated to a subagent during it, whose authorship this turn cannot account for"
|
|
16945
|
+
};
|
|
16946
|
+
}
|
|
16947
|
+
const authoring = [...input.agentCommands ?? [], ...input.userCommands ?? []].filter(
|
|
16948
|
+
(c) => !isNonAuthoringCommand(c)
|
|
16949
|
+
);
|
|
16950
|
+
if (authoring.length > 0) {
|
|
16951
|
+
return {
|
|
16952
|
+
honoured: false,
|
|
16953
|
+
void: true,
|
|
16954
|
+
why: `a command that can write files ran during it (${authoring[0]})`
|
|
16955
|
+
};
|
|
16956
|
+
}
|
|
16957
|
+
return { honoured: true };
|
|
16958
|
+
}
|
|
16959
|
+
function stateFile(sessionId) {
|
|
16960
|
+
return projectPath(scopedFile(IGNORE_DECLARATION_FILE, sessionId));
|
|
16961
|
+
}
|
|
16962
|
+
function ignoreStateKeys(token, sessionId) {
|
|
16963
|
+
const scoped = sessionScopeKey(token, sessionId);
|
|
16964
|
+
const userOnly = sessionScopeKey(token, void 0);
|
|
16965
|
+
return scoped === userOnly ? [scoped] : [scoped, userOnly];
|
|
16966
|
+
}
|
|
16967
|
+
function resolveIgnoreState(keys) {
|
|
16968
|
+
for (const key of keys) {
|
|
16969
|
+
const state = readIgnoreState(key);
|
|
16970
|
+
if (state) return { state, key };
|
|
16971
|
+
}
|
|
16972
|
+
return null;
|
|
16973
|
+
}
|
|
16974
|
+
function readIgnoreState(sessionId) {
|
|
16975
|
+
const file = stateFile(sessionId);
|
|
16976
|
+
if (!(0, import_node_fs22.existsSync)(file)) return null;
|
|
16977
|
+
try {
|
|
16978
|
+
const o = JSON.parse((0, import_node_fs22.readFileSync)(file, "utf-8")) ?? {};
|
|
16979
|
+
const spent = typeof o.spent === "number" ? o.spent : 0;
|
|
16980
|
+
const raw = o.active;
|
|
16981
|
+
let active = null;
|
|
16982
|
+
if (raw && typeof raw === "object") {
|
|
16983
|
+
const scope2 = raw.scope === "window" ? "window" : raw.scope === "turn" ? "turn" : null;
|
|
16984
|
+
const expires = typeof raw.expires === "number" ? raw.expires : null;
|
|
16985
|
+
if (scope2 && expires != null) {
|
|
16986
|
+
active = {
|
|
16987
|
+
scope: scope2,
|
|
16988
|
+
origin: raw.origin === "agent" ? "agent" : "user",
|
|
16989
|
+
reason: typeof raw.reason === "string" ? raw.reason : "",
|
|
16990
|
+
at: typeof raw.at === "number" ? raw.at : 0,
|
|
16991
|
+
expires
|
|
16992
|
+
};
|
|
16993
|
+
}
|
|
16994
|
+
}
|
|
16995
|
+
return { v: 1, active, spent };
|
|
16996
|
+
} catch {
|
|
16997
|
+
return null;
|
|
16998
|
+
}
|
|
16999
|
+
}
|
|
17000
|
+
function writeIgnoreState(state, sessionId) {
|
|
17001
|
+
try {
|
|
17002
|
+
(0, import_node_fs22.mkdirSync)(projectPath(VERITY_DIR), { recursive: true });
|
|
17003
|
+
(0, import_node_fs22.writeFileSync)(stateFile(sessionId), JSON.stringify({ v: 1, active: state.active, spent: state.spent }));
|
|
17004
|
+
} catch {
|
|
17005
|
+
}
|
|
17006
|
+
}
|
|
17007
|
+
function clearActiveDeclaration(sessionId) {
|
|
17008
|
+
const prev = readIgnoreState(sessionId);
|
|
17009
|
+
if (!prev) return;
|
|
17010
|
+
writeIgnoreState({ v: 1, active: null, spent: prev.spent }, sessionId);
|
|
17011
|
+
}
|
|
17012
|
+
function describeRemaining(d, now) {
|
|
17013
|
+
const left = Math.max(0, d.expires - now);
|
|
17014
|
+
if (left >= 90) return `${Math.round(left / 60)}m left`;
|
|
17015
|
+
return `${left}s left`;
|
|
17016
|
+
}
|
|
17017
|
+
|
|
16525
17018
|
// src/commands/status.ts
|
|
16526
17019
|
function timeAgo(isoDate) {
|
|
16527
17020
|
const ms = Date.now() - new Date(isoDate).getTime();
|
|
@@ -16630,6 +17123,39 @@ function registerStatusCommand(program2) {
|
|
|
16630
17123
|
printInfo(`Trend: ${r.trend}`);
|
|
16631
17124
|
printInfo(`Runs: ${r.count} recorded`);
|
|
16632
17125
|
}
|
|
17126
|
+
{
|
|
17127
|
+
const tokenForScope = tokenResult.data.token;
|
|
17128
|
+
const found = resolveIgnoreState(
|
|
17129
|
+
ignoreStateKeys(tokenForScope, process.env.CLAUDE_SESSION_ID || void 0)
|
|
17130
|
+
);
|
|
17131
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
17132
|
+
const active = resolveActive(found?.state ?? null, now);
|
|
17133
|
+
const spent = found?.state.spent ?? 0;
|
|
17134
|
+
if (active) {
|
|
17135
|
+
printInfo("");
|
|
17136
|
+
printInfo("--- Ignore Declared ---");
|
|
17137
|
+
printInfo(`Scope: ${active.scope === "turn" ? "the next turn" : `a window, ${describeRemaining(active, now)}`}`);
|
|
17138
|
+
printInfo(`Reason: ${active.reason}`);
|
|
17139
|
+
printInfo(`Declared by: ${active.origin === "agent" ? "the agent" : "you"}`);
|
|
17140
|
+
printInfo(`Budget: ${spent}/${IGNORE_BUDGET} declarations this session`);
|
|
17141
|
+
printInfo('Turns that author anything are still reviewed \u2014 the declaration voids. "verity ignore clear" cancels it.');
|
|
17142
|
+
} else if (spent > 0) {
|
|
17143
|
+
printInfo("");
|
|
17144
|
+
printInfo(`Ignore budget: ${spent}/${IGNORE_BUDGET} declarations used this session.`);
|
|
17145
|
+
}
|
|
17146
|
+
}
|
|
17147
|
+
{
|
|
17148
|
+
const ig = loadVerityIgnore();
|
|
17149
|
+
if (ig.rules.length > 0 || ig.problems.length > 0) {
|
|
17150
|
+
printInfo("");
|
|
17151
|
+
printInfo("--- .verityignore ---");
|
|
17152
|
+
printInfo(`Rules: ${ig.rules.length}`);
|
|
17153
|
+
for (const problem of ig.problems) printWarn(` ${problem}`);
|
|
17154
|
+
const overlap = describeSecurityOverlap(ig);
|
|
17155
|
+
if (overlap) printWarn(overlap);
|
|
17156
|
+
printInfo("An edit to .verityignore suspends every rule for that turn \u2014 the edit is always reviewed.");
|
|
17157
|
+
}
|
|
17158
|
+
}
|
|
16633
17159
|
if (mem.pending_items && mem.pending_items.length > 0) {
|
|
16634
17160
|
printInfo("");
|
|
16635
17161
|
printInfo("--- Pending Items ---");
|
|
@@ -16793,6 +17319,7 @@ function createRun(opts, globals) {
|
|
|
16793
17319
|
allChanged: [],
|
|
16794
17320
|
hasRecentCommitFiles: false,
|
|
16795
17321
|
changedUniverse: [],
|
|
17322
|
+
verityIgnored: { rules: 0, kept: [], ignored: [], securityExcluded: [], suspended: false },
|
|
16796
17323
|
analyzable: [],
|
|
16797
17324
|
reviewable: [],
|
|
16798
17325
|
securityFiles: [],
|
|
@@ -16826,6 +17353,7 @@ function createRun(opts, globals) {
|
|
|
16826
17353
|
deletedNodePaths: [],
|
|
16827
17354
|
editedUploads: [],
|
|
16828
17355
|
autoSeedNotice: null,
|
|
17356
|
+
voidedIgnoreNotice: null,
|
|
16829
17357
|
foldResult: null,
|
|
16830
17358
|
foldConservation: null,
|
|
16831
17359
|
memorySession: null,
|
|
@@ -16846,7 +17374,7 @@ function createRun(opts, globals) {
|
|
|
16846
17374
|
}
|
|
16847
17375
|
|
|
16848
17376
|
// src/lib/stderr-log.ts
|
|
16849
|
-
var
|
|
17377
|
+
var import_node_fs23 = require("node:fs");
|
|
16850
17378
|
var TOKEN_RE2 = /verity_[0-9a-f]{16,}/g;
|
|
16851
17379
|
var ANSI_RE = /\u001b\[[0-?]*[ -/]*[@-~]/g;
|
|
16852
17380
|
function scrub(s) {
|
|
@@ -16859,9 +17387,9 @@ function append(text) {
|
|
|
16859
17387
|
try {
|
|
16860
17388
|
const dir = projectPath(DEBUG_LOG_DIR);
|
|
16861
17389
|
const file = projectPath(STDERR_LOG_FILE);
|
|
16862
|
-
(0,
|
|
17390
|
+
(0, import_node_fs23.mkdirSync)(dir, { recursive: true });
|
|
16863
17391
|
rotateIfNeeded(file);
|
|
16864
|
-
(0,
|
|
17392
|
+
(0, import_node_fs23.appendFileSync)(file, text);
|
|
16865
17393
|
} catch {
|
|
16866
17394
|
}
|
|
16867
17395
|
}
|
|
@@ -17019,7 +17547,7 @@ function installRunEvidence(run) {
|
|
|
17019
17547
|
|
|
17020
17548
|
// src/lib/git-frame.ts
|
|
17021
17549
|
var import_node_child_process7 = require("node:child_process");
|
|
17022
|
-
var
|
|
17550
|
+
var import_node_fs24 = require("node:fs");
|
|
17023
17551
|
var import_node_os3 = require("node:os");
|
|
17024
17552
|
var import_node_path18 = require("node:path");
|
|
17025
17553
|
var import_node_path19 = require("node:path");
|
|
@@ -17158,14 +17686,14 @@ function gitAt(dir, args) {
|
|
|
17158
17686
|
}
|
|
17159
17687
|
function realpathOr(p) {
|
|
17160
17688
|
try {
|
|
17161
|
-
return
|
|
17689
|
+
return import_node_fs24.realpathSync.native(p);
|
|
17162
17690
|
} catch {
|
|
17163
17691
|
return (0, import_node_path18.resolve)(p);
|
|
17164
17692
|
}
|
|
17165
17693
|
}
|
|
17166
17694
|
function resolveFrame(input) {
|
|
17167
17695
|
const found = findMomentSegment(input.command, input.on);
|
|
17168
|
-
const hookDirUsable = !!input.hookCwd && (0,
|
|
17696
|
+
const hookDirUsable = !!input.hookCwd && (0, import_node_fs24.existsSync)(input.hookCwd);
|
|
17169
17697
|
const baseDir = hookDirUsable ? input.hookCwd : process.cwd();
|
|
17170
17698
|
let anchor = hookDirUsable ? "hook-cwd" : "process-cwd";
|
|
17171
17699
|
const refuse = (refusal) => ({
|
|
@@ -17188,7 +17716,7 @@ function resolveFrame(input) {
|
|
|
17188
17716
|
if (dirs.size > 1) return refuse(`target:multiple ${found.moment} targets in one command`);
|
|
17189
17717
|
const targetDir = dirs.size === 1 ? [...dirs][0] : baseDir;
|
|
17190
17718
|
if (targetDir !== baseDir) {
|
|
17191
|
-
if (!(0,
|
|
17719
|
+
if (!(0, import_node_fs24.existsSync)(targetDir)) return refuse(`target:directory does not exist: ${targetDir}`);
|
|
17192
17720
|
dir = targetDir;
|
|
17193
17721
|
}
|
|
17194
17722
|
}
|
|
@@ -17227,7 +17755,7 @@ var SHA_RE2 = /^[0-9a-f]{40}$/;
|
|
|
17227
17755
|
function baselineShaAt(frame) {
|
|
17228
17756
|
if (!frame.worktreeRoot) return null;
|
|
17229
17757
|
try {
|
|
17230
|
-
const sha = (0,
|
|
17758
|
+
const sha = (0, import_node_fs24.readFileSync)((0, import_node_path19.join)(frame.worktreeRoot, BASELINE_SHA_FILE), "utf-8").trim();
|
|
17231
17759
|
if (!SHA_RE2.test(sha)) return null;
|
|
17232
17760
|
return refResolves(frame, sha) ? sha : null;
|
|
17233
17761
|
} catch {
|
|
@@ -17340,7 +17868,7 @@ function truthy(v) {
|
|
|
17340
17868
|
}
|
|
17341
17869
|
|
|
17342
17870
|
// src/lib/transcript.ts
|
|
17343
|
-
var
|
|
17871
|
+
var import_node_fs25 = require("node:fs");
|
|
17344
17872
|
var MAX_READ_BYTES = 256 * 1024;
|
|
17345
17873
|
var SMALL_FILE_BYTES = 64 * 1024;
|
|
17346
17874
|
var MAX_FILES_LIST = 20;
|
|
@@ -17366,7 +17894,7 @@ async function extractActionSummary(transcriptPath) {
|
|
|
17366
17894
|
function readTurnLines(transcriptPath) {
|
|
17367
17895
|
let size;
|
|
17368
17896
|
try {
|
|
17369
|
-
size = (0,
|
|
17897
|
+
size = (0, import_node_fs25.statSync)(transcriptPath).size;
|
|
17370
17898
|
} catch {
|
|
17371
17899
|
return null;
|
|
17372
17900
|
}
|
|
@@ -17374,7 +17902,7 @@ function readTurnLines(transcriptPath) {
|
|
|
17374
17902
|
let raw;
|
|
17375
17903
|
let windowed = false;
|
|
17376
17904
|
if (size <= SMALL_FILE_BYTES) {
|
|
17377
|
-
raw = (0,
|
|
17905
|
+
raw = (0, import_node_fs25.readFileSync)(transcriptPath, "utf-8");
|
|
17378
17906
|
} else {
|
|
17379
17907
|
windowed = true;
|
|
17380
17908
|
const buf = Buffer.alloc(Math.min(MAX_READ_BYTES, size));
|
|
@@ -17432,6 +17960,7 @@ function buildSummary(lines) {
|
|
|
17432
17960
|
const commands = [];
|
|
17433
17961
|
const userCommands = [];
|
|
17434
17962
|
let userCommandsTruncated = false;
|
|
17963
|
+
let commandsTruncated = false;
|
|
17435
17964
|
let searches = 0;
|
|
17436
17965
|
let subagents = 0;
|
|
17437
17966
|
let webFetches = 0;
|
|
@@ -17472,7 +18001,10 @@ function buildSummary(lines) {
|
|
|
17472
18001
|
totalToolCalls++;
|
|
17473
18002
|
const toolName = block.name ?? "unknown";
|
|
17474
18003
|
toolCounts[toolName] = (toolCounts[toolName] ?? 0) + 1;
|
|
17475
|
-
if (totalToolCalls > MAX_TOOL_BLOCKS)
|
|
18004
|
+
if (totalToolCalls > MAX_TOOL_BLOCKS) {
|
|
18005
|
+
if (toolName === "Bash") commandsTruncated = true;
|
|
18006
|
+
continue;
|
|
18007
|
+
}
|
|
17476
18008
|
const input = block.input ?? {};
|
|
17477
18009
|
switch (toolName) {
|
|
17478
18010
|
case "Read":
|
|
@@ -17492,9 +18024,11 @@ function buildSummary(lines) {
|
|
|
17492
18024
|
addPath(filesEdited, input.file_path);
|
|
17493
18025
|
break;
|
|
17494
18026
|
case "Bash": {
|
|
17495
|
-
const cmd =
|
|
17496
|
-
if (
|
|
17497
|
-
|
|
18027
|
+
const { cmd, lost } = sanitizeCommandWithLoss(input.command);
|
|
18028
|
+
if (lost) commandsTruncated = true;
|
|
18029
|
+
if (cmd) {
|
|
18030
|
+
if (commands.length < MAX_COMMANDS) commands.push(cmd);
|
|
18031
|
+
else commandsTruncated = true;
|
|
17498
18032
|
}
|
|
17499
18033
|
break;
|
|
17500
18034
|
}
|
|
@@ -17537,6 +18071,7 @@ function buildSummary(lines) {
|
|
|
17537
18071
|
],
|
|
17538
18072
|
searches,
|
|
17539
18073
|
commands,
|
|
18074
|
+
...commandsTruncated ? { commands_truncated: true } : {},
|
|
17540
18075
|
user_commands: userCommands,
|
|
17541
18076
|
...userCommandsTruncated ? { user_commands_truncated: true } : {},
|
|
17542
18077
|
subagents,
|
|
@@ -17547,6 +18082,7 @@ function buildSummary(lines) {
|
|
|
17547
18082
|
};
|
|
17548
18083
|
if (JSON.stringify(summary).length > MAX_SUMMARY_BYTES) {
|
|
17549
18084
|
summary.commands = [];
|
|
18085
|
+
summary.commands_truncated = true;
|
|
17550
18086
|
summary.user_commands = [];
|
|
17551
18087
|
summary.user_commands_truncated = true;
|
|
17552
18088
|
if (JSON.stringify(summary).length > MAX_SUMMARY_BYTES) {
|
|
@@ -17582,12 +18118,22 @@ function userTypedCommands(entry) {
|
|
|
17582
18118
|
}
|
|
17583
18119
|
return { commands, truncated };
|
|
17584
18120
|
}
|
|
18121
|
+
function sanitizeCommandWithLoss(rawCmd) {
|
|
18122
|
+
if (typeof rawCmd !== "string" || !rawCmd) return { cmd: null, lost: false };
|
|
18123
|
+
const lines = rawCmd.split("\n");
|
|
18124
|
+
const lost = lines.slice(1).some((l) => l.trim().length > 0);
|
|
18125
|
+
const first = lines[0];
|
|
18126
|
+
const cmd = sanitizeCommand(first);
|
|
18127
|
+
const hadSeparator = SEPARATORS.some((sep2) => first.indexOf(sep2) > 0);
|
|
18128
|
+
return { cmd, lost: lost || !hadSeparator && first.length > MAX_COMMAND_CHARS };
|
|
18129
|
+
}
|
|
18130
|
+
var SEPARATORS = [" | ", " > ", " >> ", " 2>", " && ", " ; "];
|
|
17585
18131
|
function sanitizeCommand(rawCmd) {
|
|
17586
18132
|
if (typeof rawCmd !== "string" || !rawCmd) return null;
|
|
17587
18133
|
let cmd = rawCmd.split("\n")[0];
|
|
17588
18134
|
let cut = -1;
|
|
17589
18135
|
let marker = "";
|
|
17590
|
-
for (const sep2 of
|
|
18136
|
+
for (const sep2 of SEPARATORS) {
|
|
17591
18137
|
const idx = cmd.indexOf(sep2);
|
|
17592
18138
|
if (idx > 0 && (cut === -1 || idx < cut)) {
|
|
17593
18139
|
cut = idx;
|
|
@@ -17837,7 +18383,7 @@ function channelSilence(input) {
|
|
|
17837
18383
|
// src/lib/cli-version.ts
|
|
17838
18384
|
function cliVersion() {
|
|
17839
18385
|
try {
|
|
17840
|
-
return true ? "0.
|
|
18386
|
+
return true ? "0.31.0-experimental.48d33ea" : "dev";
|
|
17841
18387
|
} catch {
|
|
17842
18388
|
return "dev";
|
|
17843
18389
|
}
|
|
@@ -17878,7 +18424,7 @@ async function sendSkipBeacon(ctx, reason) {
|
|
|
17878
18424
|
|
|
17879
18425
|
// src/lib/static-analysis.ts
|
|
17880
18426
|
var import_node_child_process8 = require("node:child_process");
|
|
17881
|
-
var
|
|
18427
|
+
var import_node_fs26 = require("node:fs");
|
|
17882
18428
|
var SEVERITY_ORDER = {
|
|
17883
18429
|
Error: 0,
|
|
17884
18430
|
Critical: 0,
|
|
@@ -17926,7 +18472,7 @@ function runCodacyAnalysis(files) {
|
|
|
17926
18472
|
if (files.length === 0) return empty;
|
|
17927
18473
|
const existingFiles = files.filter((f) => {
|
|
17928
18474
|
try {
|
|
17929
|
-
return (0,
|
|
18475
|
+
return (0, import_node_fs26.existsSync)(f);
|
|
17930
18476
|
} catch {
|
|
17931
18477
|
return false;
|
|
17932
18478
|
}
|
|
@@ -18124,6 +18670,7 @@ async function passAndExit(run, reason, skip, kindOverride) {
|
|
|
18124
18670
|
"bare-acknowledgment",
|
|
18125
18671
|
"reflection-prompt",
|
|
18126
18672
|
"command-only-turn",
|
|
18673
|
+
"declared-ignore",
|
|
18127
18674
|
"skip-mode",
|
|
18128
18675
|
"zero-increment",
|
|
18129
18676
|
"debounce",
|
|
@@ -18163,19 +18710,37 @@ async function scope(run) {
|
|
|
18163
18710
|
const { files: allChanged, hasRecentCommitFiles } = getChangedFiles();
|
|
18164
18711
|
run.changedUniverse = allChanged;
|
|
18165
18712
|
const { kept: external } = partitionVerityOwned(allChanged);
|
|
18166
|
-
const
|
|
18167
|
-
const
|
|
18168
|
-
const
|
|
18713
|
+
const verityIgnore = loadVerityIgnore();
|
|
18714
|
+
const ignored = partitionIgnored(external, verityIgnore);
|
|
18715
|
+
for (const problem of verityIgnore.problems) {
|
|
18716
|
+
process.stderr.write(`Verity: .verityignore ${problem}
|
|
18717
|
+
`);
|
|
18718
|
+
}
|
|
18719
|
+
if (ignored.securityExcluded.length > 0) {
|
|
18720
|
+
process.stderr.write(
|
|
18721
|
+
`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.
|
|
18722
|
+
`
|
|
18723
|
+
);
|
|
18724
|
+
}
|
|
18725
|
+
if (ignored.suspended) {
|
|
18726
|
+
process.stderr.write(
|
|
18727
|
+
"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"
|
|
18728
|
+
);
|
|
18729
|
+
}
|
|
18730
|
+
const inScope = ignored.kept;
|
|
18731
|
+
const analyzable = filterAnalyzable(inScope);
|
|
18732
|
+
const reviewable = filterReviewable(inScope);
|
|
18733
|
+
const securityFiles = filterSecurity(inScope);
|
|
18169
18734
|
const noFilesChanged = analyzable.length === 0 && reviewable.length === 0 && securityFiles.length === 0;
|
|
18170
18735
|
if (noFilesChanged && !assistantResponse) {
|
|
18171
18736
|
await passAndExit(run, "No analyzable files changed", "no-analyzable-files");
|
|
18172
18737
|
}
|
|
18173
18738
|
const allForReview = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable]));
|
|
18174
|
-
Object.assign(run, { allChanged, allForReview, analyzable, hasRecentCommitFiles, noFilesChanged, reviewable, securityFiles });
|
|
18739
|
+
Object.assign(run, { allChanged, allForReview, analyzable, hasRecentCommitFiles, noFilesChanged, reviewable, securityFiles, verityIgnored: ignored });
|
|
18175
18740
|
}
|
|
18176
18741
|
|
|
18177
18742
|
// src/lib/specs.ts
|
|
18178
|
-
var
|
|
18743
|
+
var import_node_fs27 = require("node:fs");
|
|
18179
18744
|
var import_node_path20 = require("node:path");
|
|
18180
18745
|
var SPEC_CANDIDATES = [
|
|
18181
18746
|
"CLAUDE.md",
|
|
@@ -18207,16 +18772,16 @@ function discoverSpecs(consulted = []) {
|
|
|
18207
18772
|
const totalCap = relevant ? MAX_TOTAL_SPEC_BYTES : UNCONSULTED_TOTAL_BYTES;
|
|
18208
18773
|
if (totalBytes >= totalCap) return false;
|
|
18209
18774
|
if (seen.has(specPath)) return true;
|
|
18210
|
-
if (!(0,
|
|
18775
|
+
if (!(0, import_node_fs27.existsSync)(specPath)) return true;
|
|
18211
18776
|
seen.add(specPath);
|
|
18212
18777
|
const remaining = totalCap - totalBytes;
|
|
18213
18778
|
const fileCap = relevant ? MAX_SPEC_FILE_BYTES : UNCONSULTED_FILE_BYTES;
|
|
18214
18779
|
const readBytes = Math.min(fileCap, remaining);
|
|
18215
18780
|
try {
|
|
18216
18781
|
const buf = Buffer.alloc(readBytes);
|
|
18217
|
-
const fd = (0,
|
|
18218
|
-
const bytesRead = (0,
|
|
18219
|
-
(0,
|
|
18782
|
+
const fd = (0, import_node_fs27.openSync)(specPath, "r");
|
|
18783
|
+
const bytesRead = (0, import_node_fs27.readSync)(fd, buf, 0, readBytes, 0);
|
|
18784
|
+
(0, import_node_fs27.closeSync)(fd);
|
|
18220
18785
|
const content = buf.slice(0, bytesRead).toString("utf-8");
|
|
18221
18786
|
if (!content) return true;
|
|
18222
18787
|
result.push({ path: specPath, content });
|
|
@@ -18232,7 +18797,7 @@ function discoverSpecs(consulted = []) {
|
|
|
18232
18797
|
if (!addSpec(candidate)) break;
|
|
18233
18798
|
}
|
|
18234
18799
|
for (const dir of ["spec", "docs"]) {
|
|
18235
|
-
if (!(0,
|
|
18800
|
+
if (!(0, import_node_fs27.existsSync)(dir)) continue;
|
|
18236
18801
|
try {
|
|
18237
18802
|
const mdFiles = findMdFiles(dir, 2).sort();
|
|
18238
18803
|
for (const mdFile of mdFiles) {
|
|
@@ -18247,7 +18812,7 @@ function findMdFiles(dir, maxDepth, depth = 0) {
|
|
|
18247
18812
|
if (depth >= maxDepth) return [];
|
|
18248
18813
|
const result = [];
|
|
18249
18814
|
try {
|
|
18250
|
-
const entries = (0,
|
|
18815
|
+
const entries = (0, import_node_fs27.readdirSync)(dir, { withFileTypes: true });
|
|
18251
18816
|
for (const entry of entries) {
|
|
18252
18817
|
const fullPath = (0, import_node_path20.join)(dir, entry.name);
|
|
18253
18818
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -18266,14 +18831,14 @@ function discoverPlans() {
|
|
|
18266
18831
|
const candidates = [];
|
|
18267
18832
|
const seen = /* @__PURE__ */ new Set();
|
|
18268
18833
|
for (const plansDir of [localPlansDir, homePlansDir]) {
|
|
18269
|
-
if (!(0,
|
|
18834
|
+
if (!(0, import_node_fs27.existsSync)(plansDir)) continue;
|
|
18270
18835
|
try {
|
|
18271
|
-
for (const f of (0,
|
|
18836
|
+
for (const f of (0, import_node_fs27.readdirSync)(plansDir)) {
|
|
18272
18837
|
if (!f.endsWith(".md") || seen.has(f)) continue;
|
|
18273
18838
|
seen.add(f);
|
|
18274
18839
|
const fullPath = (0, import_node_path20.join)(plansDir, f);
|
|
18275
18840
|
try {
|
|
18276
|
-
const stat3 = (0,
|
|
18841
|
+
const stat3 = (0, import_node_fs27.statSync)(fullPath);
|
|
18277
18842
|
candidates.push({ name: f, path: fullPath, mtime: stat3.mtimeMs, size: stat3.size });
|
|
18278
18843
|
} catch {
|
|
18279
18844
|
}
|
|
@@ -18286,7 +18851,7 @@ function discoverPlans() {
|
|
|
18286
18851
|
for (const entry of candidates.slice(0, MAX_PLAN_FILES)) {
|
|
18287
18852
|
if (entry.size > MAX_PLAN_FILE_BYTES) continue;
|
|
18288
18853
|
try {
|
|
18289
|
-
const content = (0,
|
|
18854
|
+
const content = (0, import_node_fs27.readFileSync)(entry.path, "utf-8");
|
|
18290
18855
|
result.push({ name: entry.name, content });
|
|
18291
18856
|
} catch {
|
|
18292
18857
|
}
|
|
@@ -18306,6 +18871,50 @@ async function intentInputs(run) {
|
|
|
18306
18871
|
})) {
|
|
18307
18872
|
await passAndExit(run, "User command only \u2014 skipping analysis", "command-only-turn");
|
|
18308
18873
|
}
|
|
18874
|
+
{
|
|
18875
|
+
const ignoreKeys = ignoreStateKeys(
|
|
18876
|
+
run.tokenResult.ok ? run.tokenResult.data.token : void 0,
|
|
18877
|
+
null
|
|
18878
|
+
);
|
|
18879
|
+
const found = resolveIgnoreState([baselineSessionId, ...ignoreKeys]);
|
|
18880
|
+
const declaration = resolveActive(found?.state ?? null, Math.floor(Date.now() / 1e3));
|
|
18881
|
+
if (declaration) {
|
|
18882
|
+
const commandRecordTruncated = actionSummary?.commands_truncated === true || actionSummary?.user_commands_truncated === true || // The derived signal, and the only one an older client's summary can
|
|
18883
|
+
// give: `tool_counts` keeps counting Bash calls past every cap that
|
|
18884
|
+
// stops the list growing, so a mismatch IS a loss. See
|
|
18885
|
+
// `ActionSummary.commands_truncated`.
|
|
18886
|
+
(actionSummary?.tool_counts?.Bash ?? 0) > (actionSummary?.commands?.length ?? 0);
|
|
18887
|
+
const outcome = verifyDeclaration({
|
|
18888
|
+
declaration,
|
|
18889
|
+
agentAuthoredFiles: (actionSummary?.files_edited.length ?? 0) + (actionSummary?.files_created.length ?? 0),
|
|
18890
|
+
subagents: actionSummary?.subagents ?? 0,
|
|
18891
|
+
agentCommands: actionSummary?.commands,
|
|
18892
|
+
userCommands: actionSummary?.user_commands,
|
|
18893
|
+
commandRecordTruncated,
|
|
18894
|
+
authorshipIsObservable: !!actionSummary && actionSummary.transcript_windowed !== "orphaned"
|
|
18895
|
+
});
|
|
18896
|
+
if (outcome.honoured) {
|
|
18897
|
+
if (declaration.scope === "turn" && found) clearActiveDeclaration(found.key);
|
|
18898
|
+
logEvent("ignore_honoured", { scope: declaration.scope, origin: declaration.origin });
|
|
18899
|
+
await passAndExit(
|
|
18900
|
+
run,
|
|
18901
|
+
`skipping this turn \u2014 declared housekeeping ("${declaration.reason}")`,
|
|
18902
|
+
"declared-ignore"
|
|
18903
|
+
);
|
|
18904
|
+
} else if (outcome.void) {
|
|
18905
|
+
if (found) clearActiveDeclaration(found.key);
|
|
18906
|
+
logEvent("ignore_voided", {
|
|
18907
|
+
scope: declaration.scope,
|
|
18908
|
+
origin: declaration.origin,
|
|
18909
|
+
why: outcome.why
|
|
18910
|
+
});
|
|
18911
|
+
const notice = `Verity: the ignore declared for this window ("${declaration.reason}") was voided \u2014 ${outcome.why}. Reviewing normally.`;
|
|
18912
|
+
process.stderr.write(`${notice}
|
|
18913
|
+
`);
|
|
18914
|
+
run.voidedIgnoreNotice = notice;
|
|
18915
|
+
}
|
|
18916
|
+
}
|
|
18917
|
+
}
|
|
18309
18918
|
const conversation = await readAndClearConversationBuffer(baselineSessionId);
|
|
18310
18919
|
const specs = discoverSpecs(actionSummary?.files_read ?? []);
|
|
18311
18920
|
const plans = discoverPlans();
|
|
@@ -18419,177 +19028,8 @@ async function mode(run) {
|
|
|
18419
19028
|
Object.assign(run, { analysisMode, contextFilePaths, sessionAuthoredCode, sessionIdForMemory });
|
|
18420
19029
|
}
|
|
18421
19030
|
|
|
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
19031
|
// src/lib/fold.ts
|
|
18592
|
-
var
|
|
19032
|
+
var import_node_fs28 = require("node:fs");
|
|
18593
19033
|
var import_node_path21 = require("node:path");
|
|
18594
19034
|
var KNOWN_RECORD_TYPES = /* @__PURE__ */ new Set([
|
|
18595
19035
|
"user",
|
|
@@ -18727,7 +19167,7 @@ function candidateRoots(repoRoot2) {
|
|
|
18727
19167
|
const norm = repoRoot2.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
18728
19168
|
const out = [norm];
|
|
18729
19169
|
try {
|
|
18730
|
-
const real =
|
|
19170
|
+
const real = import_node_fs28.realpathSync.native(norm).replace(/\\/g, "/").replace(/\/+$/, "");
|
|
18731
19171
|
if (real !== norm) out.push(real);
|
|
18732
19172
|
} catch {
|
|
18733
19173
|
}
|
|
@@ -18815,8 +19255,8 @@ function fold(transcriptPath, opts = {}) {
|
|
|
18815
19255
|
}
|
|
18816
19256
|
};
|
|
18817
19257
|
try {
|
|
18818
|
-
if (!(0,
|
|
18819
|
-
ingest((0,
|
|
19258
|
+
if (!(0, import_node_fs28.existsSync)(transcriptPath)) return result;
|
|
19259
|
+
ingest((0, import_node_fs28.readFileSync)(transcriptPath, "utf8"), "agent");
|
|
18820
19260
|
result.coverage.complete = true;
|
|
18821
19261
|
} catch {
|
|
18822
19262
|
return result;
|
|
@@ -18827,19 +19267,19 @@ function fold(transcriptPath, opts = {}) {
|
|
|
18827
19267
|
(0, import_node_path21.basename)(transcriptPath).replace(/\.jsonl$/, ""),
|
|
18828
19268
|
"subagents"
|
|
18829
19269
|
);
|
|
18830
|
-
if ((0,
|
|
19270
|
+
if ((0, import_node_fs28.existsSync)(sidecarDir)) {
|
|
18831
19271
|
const maxFiles = opts.maxSidecars ?? 200;
|
|
18832
19272
|
const maxBytes = opts.maxSidecarBytes ?? 16 * 1024 * 1024;
|
|
18833
19273
|
const found = [];
|
|
18834
19274
|
const walk = (d, depth) => {
|
|
18835
19275
|
if (depth > 4) return;
|
|
18836
|
-
for (const e of (0,
|
|
19276
|
+
for (const e of (0, import_node_fs28.readdirSync)(d, { withFileTypes: true })) {
|
|
18837
19277
|
const p = (0, import_node_path21.join)(d, e.name);
|
|
18838
19278
|
if (e.isDirectory()) {
|
|
18839
19279
|
walk(p, depth + 1);
|
|
18840
19280
|
} else if (e.name.startsWith("agent-") && e.name.endsWith(".jsonl")) {
|
|
18841
19281
|
try {
|
|
18842
|
-
const st = (0,
|
|
19282
|
+
const st = (0, import_node_fs28.statSync)(p);
|
|
18843
19283
|
found.push({ path: p, size: st.size, mtimeMs: st.mtimeMs });
|
|
18844
19284
|
} catch {
|
|
18845
19285
|
result.coverage.malformed++;
|
|
@@ -18856,7 +19296,7 @@ function fold(transcriptPath, opts = {}) {
|
|
|
18856
19296
|
continue;
|
|
18857
19297
|
}
|
|
18858
19298
|
try {
|
|
18859
|
-
ingest((0,
|
|
19299
|
+
ingest((0, import_node_fs28.readFileSync)(f.path, "utf8"), "subagent");
|
|
18860
19300
|
bytes += f.size;
|
|
18861
19301
|
result.coverage.subagentFiles++;
|
|
18862
19302
|
} catch {
|
|
@@ -18891,7 +19331,7 @@ function fold(transcriptPath, opts = {}) {
|
|
|
18891
19331
|
}
|
|
18892
19332
|
function classifyUnobserved(path) {
|
|
18893
19333
|
try {
|
|
18894
|
-
const st = (0,
|
|
19334
|
+
const st = (0, import_node_fs28.statSync)(path);
|
|
18895
19335
|
if (!st.isFile()) return "unreadable";
|
|
18896
19336
|
} catch {
|
|
18897
19337
|
return "unreadable";
|
|
@@ -19195,20 +19635,20 @@ async function evidence(run) {
|
|
|
19195
19635
|
}
|
|
19196
19636
|
|
|
19197
19637
|
// src/lib/cache-cleanup.ts
|
|
19198
|
-
var
|
|
19638
|
+
var import_node_fs29 = require("node:fs");
|
|
19199
19639
|
var import_node_path22 = require("node:path");
|
|
19200
19640
|
var CACHE_TTL_DAYS = 7;
|
|
19201
19641
|
function pruneStaleCache() {
|
|
19202
19642
|
try {
|
|
19203
19643
|
const dir = projectPath(CACHE_DIR);
|
|
19204
19644
|
const cutoff = Date.now() - CACHE_TTL_DAYS * 24 * 3600 * 1e3;
|
|
19205
|
-
for (const entry of (0,
|
|
19645
|
+
for (const entry of (0, import_node_fs29.readdirSync)(dir)) {
|
|
19206
19646
|
if (!entry.startsWith("pending-")) continue;
|
|
19207
19647
|
const path = (0, import_node_path22.join)(dir, entry);
|
|
19208
19648
|
try {
|
|
19209
|
-
const stat3 = (0,
|
|
19649
|
+
const stat3 = (0, import_node_fs29.statSync)(path);
|
|
19210
19650
|
if (stat3.mtimeMs < cutoff) {
|
|
19211
|
-
(0,
|
|
19651
|
+
(0, import_node_fs29.unlinkSync)(path);
|
|
19212
19652
|
logEvent("cache_entry_pruned", {
|
|
19213
19653
|
path: entry,
|
|
19214
19654
|
age_days: Math.round((Date.now() - stat3.mtimeMs) / 864e5)
|
|
@@ -19222,7 +19662,7 @@ function pruneStaleCache() {
|
|
|
19222
19662
|
}
|
|
19223
19663
|
|
|
19224
19664
|
// src/lib/context-files.ts
|
|
19225
|
-
var
|
|
19665
|
+
var import_node_fs30 = require("node:fs");
|
|
19226
19666
|
var MAX_CONTEXT_FILES = 10;
|
|
19227
19667
|
var MAX_CONTEXT_FILE_BYTES = 10240;
|
|
19228
19668
|
var MAX_CONTEXT_TOTAL_BYTES = 51200;
|
|
@@ -19243,7 +19683,7 @@ function gatherContextFiles(contextPaths, deltaFiles) {
|
|
|
19243
19683
|
continue;
|
|
19244
19684
|
}
|
|
19245
19685
|
try {
|
|
19246
|
-
const content = (0,
|
|
19686
|
+
const content = (0, import_node_fs30.readFileSync)(safePath, "utf8");
|
|
19247
19687
|
const bytes = Buffer.byteLength(content);
|
|
19248
19688
|
if (bytes > MAX_CONTEXT_FILE_BYTES) {
|
|
19249
19689
|
logEvent("context_file_skipped", { path: filePath, reason: "too_large", bytes });
|
|
@@ -19305,7 +19745,7 @@ async function contextFiles(run) {
|
|
|
19305
19745
|
|
|
19306
19746
|
// src/lib/seed-runner.ts
|
|
19307
19747
|
var import_promises11 = require("node:fs/promises");
|
|
19308
|
-
var
|
|
19748
|
+
var import_node_fs31 = require("node:fs");
|
|
19309
19749
|
var import_node_path23 = require("node:path");
|
|
19310
19750
|
var import_yaml2 = __toESM(require_dist());
|
|
19311
19751
|
|
|
@@ -19545,7 +19985,7 @@ function renderNodeMarkdown(candidate, nodeId, createdAt) {
|
|
|
19545
19985
|
return fm;
|
|
19546
19986
|
}
|
|
19547
19987
|
async function runSeed(opts) {
|
|
19548
|
-
if (!(0,
|
|
19988
|
+
if (!(0, import_node_fs31.existsSync)(STANDARD_FILE)) {
|
|
19549
19989
|
return { created: 0, failed: 0, skipped: "no_standard", candidates: [] };
|
|
19550
19990
|
}
|
|
19551
19991
|
let standardDoc;
|
|
@@ -19557,7 +19997,7 @@ async function runSeed(opts) {
|
|
|
19557
19997
|
}
|
|
19558
19998
|
const knowledgeSpec = standardDoc.knowledge_spec ?? {};
|
|
19559
19999
|
let readmeContent;
|
|
19560
|
-
if ((0,
|
|
20000
|
+
if ((0, import_node_fs31.existsSync)("README.md")) {
|
|
19561
20001
|
try {
|
|
19562
20002
|
readmeContent = await (0, import_promises11.readFile)("README.md", "utf-8");
|
|
19563
20003
|
} catch {
|
|
@@ -19565,7 +20005,7 @@ async function runSeed(opts) {
|
|
|
19565
20005
|
}
|
|
19566
20006
|
let claudeMdContent;
|
|
19567
20007
|
for (const p of ["CLAUDE.md", ".claude/CLAUDE.md"]) {
|
|
19568
|
-
if ((0,
|
|
20008
|
+
if ((0, import_node_fs31.existsSync)(p)) {
|
|
19569
20009
|
try {
|
|
19570
20010
|
claudeMdContent = await (0, import_promises11.readFile)(p, "utf-8");
|
|
19571
20011
|
break;
|
|
@@ -19589,7 +20029,7 @@ async function runSeed(opts) {
|
|
|
19589
20029
|
return { created: 0, failed: 0, skipped: "no_candidates", candidates: [] };
|
|
19590
20030
|
}
|
|
19591
20031
|
const overviewPath = (0, import_node_path23.join)(MEMORY_DIR, "domain", "project-overview.md");
|
|
19592
|
-
if ((0,
|
|
20032
|
+
if ((0, import_node_fs31.existsSync)(overviewPath) && !opts.force) {
|
|
19593
20033
|
return { created: 0, failed: 0, skipped: "already_seeded", candidates };
|
|
19594
20034
|
}
|
|
19595
20035
|
if (opts.dryRun) {
|
|
@@ -19644,7 +20084,7 @@ async function runSeed(opts) {
|
|
|
19644
20084
|
}
|
|
19645
20085
|
|
|
19646
20086
|
// src/commands/analyze/phases/08-memory-manifest.ts
|
|
19647
|
-
var
|
|
20087
|
+
var import_node_fs32 = require("node:fs");
|
|
19648
20088
|
var import_node_path24 = require("node:path");
|
|
19649
20089
|
async function memoryManifest(run) {
|
|
19650
20090
|
const { globals } = run;
|
|
@@ -19656,8 +20096,8 @@ async function memoryManifest(run) {
|
|
|
19656
20096
|
try {
|
|
19657
20097
|
await ensureMemoryDir();
|
|
19658
20098
|
const seedMarker = (0, import_node_path24.join)(VERITY_DIR, ".seeded");
|
|
19659
|
-
const hasStandard = (0,
|
|
19660
|
-
const alreadyTried = (0,
|
|
20099
|
+
const hasStandard = (0, import_node_fs32.existsSync)(STANDARD_FILE);
|
|
20100
|
+
const alreadyTried = (0, import_node_fs32.existsSync)(seedMarker);
|
|
19661
20101
|
if (hasStandard && !alreadyTried) {
|
|
19662
20102
|
const preManifest = await buildManifest();
|
|
19663
20103
|
if (preManifest.nodes.length === 0) {
|
|
@@ -19670,7 +20110,7 @@ async function memoryManifest(run) {
|
|
|
19670
20110
|
dryRun: false
|
|
19671
20111
|
});
|
|
19672
20112
|
if (seedResult.created > 0) {
|
|
19673
|
-
(0,
|
|
20113
|
+
(0, import_node_fs32.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} created=${seedResult.created}
|
|
19674
20114
|
`);
|
|
19675
20115
|
autoSeedNotice = `Seeded ${seedResult.created} knowledge node(s) from your existing Standard (one-time).`;
|
|
19676
20116
|
logEvent("auto_seed_ran", {
|
|
@@ -19678,7 +20118,7 @@ async function memoryManifest(run) {
|
|
|
19678
20118
|
failed: seedResult.failed
|
|
19679
20119
|
});
|
|
19680
20120
|
} else if (seedResult.skipped === "already_seeded") {
|
|
19681
|
-
(0,
|
|
20121
|
+
(0, import_node_fs32.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} skipped=already_seeded
|
|
19682
20122
|
`);
|
|
19683
20123
|
} else {
|
|
19684
20124
|
logEvent("auto_seed_noop", {
|
|
@@ -19867,7 +20307,7 @@ async function workingMemory(run) {
|
|
|
19867
20307
|
}
|
|
19868
20308
|
|
|
19869
20309
|
// src/lib/note-budget.ts
|
|
19870
|
-
var
|
|
20310
|
+
var import_node_fs33 = require("node:fs");
|
|
19871
20311
|
var ADVISORY_BUDGET = { PASS: 1, WARN: 2 };
|
|
19872
20312
|
var EPISODE_STALE_SECONDS = 30 * 60;
|
|
19873
20313
|
var FRESH = { delivered: 0, tasksCompleted: 0, ts: 0 };
|
|
@@ -19889,9 +20329,9 @@ function advisoryBudgetSpent(episode, rawDecision) {
|
|
|
19889
20329
|
}
|
|
19890
20330
|
function readAdvisoryEpisode(sessionId) {
|
|
19891
20331
|
const file = scopedFile(ADVISORY_EPISODE_FILE, sessionId);
|
|
19892
|
-
if (!(0,
|
|
20332
|
+
if (!(0, import_node_fs33.existsSync)(file)) return null;
|
|
19893
20333
|
try {
|
|
19894
|
-
const o = JSON.parse((0,
|
|
20334
|
+
const o = JSON.parse((0, import_node_fs33.readFileSync)(file, "utf-8")) ?? {};
|
|
19895
20335
|
const delivered = typeof o.delivered === "number" ? o.delivered : NaN;
|
|
19896
20336
|
if (isNaN(delivered)) return null;
|
|
19897
20337
|
return {
|
|
@@ -19905,8 +20345,8 @@ function readAdvisoryEpisode(sessionId) {
|
|
|
19905
20345
|
}
|
|
19906
20346
|
function writeAdvisoryEpisode(episode, sessionId) {
|
|
19907
20347
|
try {
|
|
19908
|
-
(0,
|
|
19909
|
-
(0,
|
|
20348
|
+
(0, import_node_fs33.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
20349
|
+
(0, import_node_fs33.writeFileSync)(
|
|
19910
20350
|
scopedFile(ADVISORY_EPISODE_FILE, sessionId),
|
|
19911
20351
|
JSON.stringify({ v: 1, ...episode })
|
|
19912
20352
|
);
|
|
@@ -20013,7 +20453,16 @@ async function buildRequest(run) {
|
|
|
20013
20453
|
// the state, so the number is one turn lagged by construction. The
|
|
20014
20454
|
// degenerate win for the budget is a dead channel that looks like clean
|
|
20015
20455
|
// code; this is what makes "did delivery rate collapse" a query.
|
|
20016
|
-
advisory_delivered_prior: readAdvisoryEpisode(run.baselineSessionId)?.delivered ?? 0
|
|
20456
|
+
advisory_delivered_prior: readAdvisoryEpisode(run.baselineSessionId)?.delivered ?? 0,
|
|
20457
|
+
// `.verityignore` — see CoverageTelemetry.verityignore for why the SHARE is
|
|
20458
|
+
// the number that matters and why no paths travel with it.
|
|
20459
|
+
verityignore: {
|
|
20460
|
+
rules: run.verityIgnored.rules,
|
|
20461
|
+
excluded: run.verityIgnored.ignored.length,
|
|
20462
|
+
share: ignoreShare(run.verityIgnored.kept.length, run.verityIgnored.ignored.length),
|
|
20463
|
+
security_excluded: run.verityIgnored.securityExcluded.length,
|
|
20464
|
+
suspended: run.verityIgnored.suspended
|
|
20465
|
+
}
|
|
20017
20466
|
};
|
|
20018
20467
|
const requestBody = {
|
|
20019
20468
|
coverage_telemetry: coverageTelemetry,
|
|
@@ -20211,14 +20660,14 @@ async function buildRequest(run) {
|
|
|
20211
20660
|
}
|
|
20212
20661
|
|
|
20213
20662
|
// src/lib/offline.ts
|
|
20214
|
-
var
|
|
20663
|
+
var import_node_fs34 = require("node:fs");
|
|
20215
20664
|
var import_node_crypto11 = require("node:crypto");
|
|
20216
20665
|
function cacheRequest(body) {
|
|
20217
20666
|
try {
|
|
20218
|
-
(0,
|
|
20667
|
+
(0, import_node_fs34.mkdirSync)(CACHE_DIR, { recursive: true });
|
|
20219
20668
|
const suffix = (0, import_node_crypto11.randomBytes)(4).toString("hex");
|
|
20220
20669
|
const filename = `pending-${Math.floor(Date.now() / 1e3)}-${suffix}.json`;
|
|
20221
|
-
(0,
|
|
20670
|
+
(0, import_node_fs34.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(redactRequest(body)));
|
|
20222
20671
|
} catch {
|
|
20223
20672
|
}
|
|
20224
20673
|
}
|
|
@@ -20337,7 +20786,7 @@ async function transmit(run) {
|
|
|
20337
20786
|
}
|
|
20338
20787
|
|
|
20339
20788
|
// src/commands/analyze/phases/13-reconcile.ts
|
|
20340
|
-
var
|
|
20789
|
+
var import_node_fs35 = require("node:fs");
|
|
20341
20790
|
var import_node_path26 = require("node:path");
|
|
20342
20791
|
async function reconcile(run) {
|
|
20343
20792
|
const { actionSummary, allChanged, analyzable, baseline, baselineSessionId, codeDelta, contentHash, conversation, decision, foldResult, memory, memorySession, response, reviewable, securityFiles, turnId } = run;
|
|
@@ -20348,7 +20797,7 @@ async function reconcile(run) {
|
|
|
20348
20797
|
const st = foldDossier(memorySession.d);
|
|
20349
20798
|
openElsewhere = openBlockingElsewhere(st.statements, sentPaths, (file, line) => {
|
|
20350
20799
|
try {
|
|
20351
|
-
const src = (0,
|
|
20800
|
+
const src = (0, import_node_fs35.readFileSync)((0, import_node_path26.join)(repoRoot(), file), "utf8").split("\n");
|
|
20352
20801
|
const at = src[line - 1];
|
|
20353
20802
|
return at === void 0 ? null : lineSha(at);
|
|
20354
20803
|
} catch {
|
|
@@ -20409,6 +20858,25 @@ async function reconcile(run) {
|
|
|
20409
20858
|
stage: "self-scope",
|
|
20410
20859
|
kind: "policy"
|
|
20411
20860
|
})),
|
|
20861
|
+
// ⚠ WHAT THE TEAM'S OWN `.verityignore` REMOVED — every path, named
|
|
20862
|
+
// (VRT-135 · 3/3). The 0.30 principle: an exclusion the ledger cannot see
|
|
20863
|
+
// is a scoping decision nobody can audit, and this is the one exclusion
|
|
20864
|
+
// stage a user can edit, so it is the one that most needs to be visible.
|
|
20865
|
+
//
|
|
20866
|
+
// `policy`, by D2's bar — no version of this product would have reviewed a
|
|
20867
|
+
// file the project declared out of scope, so it must not downgrade PASS to
|
|
20868
|
+
// WARN. That is also why it is not narrated per-turn by `describeCoverage`
|
|
20869
|
+
// (policy exclusions are recorded, not announced): repeating "your dist/
|
|
20870
|
+
// went unreviewed" on every turn is how a channel gets muted. The SHARE
|
|
20871
|
+
// below is the signal that replaces the noise.
|
|
20872
|
+
//
|
|
20873
|
+
// Taken from the run, not recomputed — see context.ts `verityIgnored`.
|
|
20874
|
+
...run.verityIgnored.ignored.map((path) => ({
|
|
20875
|
+
path,
|
|
20876
|
+
reason: "verityignore",
|
|
20877
|
+
stage: "verityignore",
|
|
20878
|
+
kind: "policy"
|
|
20879
|
+
})),
|
|
20412
20880
|
// The extension allowlist. POLICY: a changed README was never going to be
|
|
20413
20881
|
// reviewed, and calling that a coverage gap would downgrade nearly every
|
|
20414
20882
|
// PASS to WARN until WARN meant nothing. Recorded so the ledger balances and
|
|
@@ -20638,7 +21106,7 @@ function agentContextFor(response, intentRepeat = 0, priorPendingFingerprints =
|
|
|
20638
21106
|
}
|
|
20639
21107
|
async function render(run) {
|
|
20640
21108
|
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;
|
|
21109
|
+
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
21110
|
let { iteration } = run;
|
|
20643
21111
|
const metadata = response.metadata ?? {};
|
|
20644
21112
|
const intentAmbiguity = metadata.intent_ambiguity;
|
|
@@ -20890,6 +21358,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
20890
21358
|
const viewUrl = response.view_url ?? "";
|
|
20891
21359
|
if (viewUrl) userSummary += ` Report: ${viewUrl}`;
|
|
20892
21360
|
if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
|
|
21361
|
+
if (voidedIgnoreNotice) userSummary = `${voidedIgnoreNotice} ${userSummary}`;
|
|
20893
21362
|
userSummary += loginNudge + grantNudge;
|
|
20894
21363
|
emitVerdict({
|
|
20895
21364
|
proposed: "PASS",
|
|
@@ -20911,6 +21380,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
20911
21380
|
const viewUrl = response.view_url ?? "";
|
|
20912
21381
|
if (viewUrl) userSummary += ` Report: ${viewUrl}`;
|
|
20913
21382
|
if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
|
|
21383
|
+
if (voidedIgnoreNotice) userSummary = `${voidedIgnoreNotice} ${userSummary}`;
|
|
20914
21384
|
userSummary += loginNudge + grantNudge;
|
|
20915
21385
|
emitVerdict({
|
|
20916
21386
|
proposed: "WARN",
|
|
@@ -20926,7 +21396,11 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
20926
21396
|
}
|
|
20927
21397
|
default: {
|
|
20928
21398
|
const raw = String(decision ?? "(missing)");
|
|
20929
|
-
const msg =
|
|
21399
|
+
const msg = [
|
|
21400
|
+
voidedIgnoreNotice,
|
|
21401
|
+
autoSeedNotice,
|
|
21402
|
+
"Verity: unrecognised verdict \u2014 treating as WARN"
|
|
21403
|
+
].filter(Boolean).join(" ") + loginNudge + grantNudge;
|
|
20930
21404
|
process.stderr.write(
|
|
20931
21405
|
`Verity: server returned an unrecognised gate_decision (${raw}). Rendering WARN rather than PASS. Update the CLI: npm i -g @codacy/verity-cli
|
|
20932
21406
|
`
|
|
@@ -21003,7 +21477,7 @@ async function runAnalyze(opts, globals) {
|
|
|
21003
21477
|
}
|
|
21004
21478
|
|
|
21005
21479
|
// src/commands/baseline.ts
|
|
21006
|
-
var
|
|
21480
|
+
var import_node_fs36 = require("node:fs");
|
|
21007
21481
|
function registerBaselineCommands(program2) {
|
|
21008
21482
|
const baseline = program2.command("baseline").description("Manage the task-start working-tree baseline");
|
|
21009
21483
|
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 +21486,7 @@ function registerBaselineCommands(program2) {
|
|
|
21012
21486
|
process.chdir(repoRoot());
|
|
21013
21487
|
} catch {
|
|
21014
21488
|
}
|
|
21015
|
-
if (!(0,
|
|
21489
|
+
if (!(0, import_node_fs36.existsSync)(VERITY_DIR)) {
|
|
21016
21490
|
process.exit(0);
|
|
21017
21491
|
}
|
|
21018
21492
|
let sessionId = opts.sessionId;
|
|
@@ -21052,7 +21526,7 @@ async function readStdin() {
|
|
|
21052
21526
|
}
|
|
21053
21527
|
|
|
21054
21528
|
// src/commands/review.ts
|
|
21055
|
-
var
|
|
21529
|
+
var import_node_fs37 = require("node:fs");
|
|
21056
21530
|
function registerReviewCommand(program2) {
|
|
21057
21531
|
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
21532
|
const globals = program2.opts();
|
|
@@ -21071,7 +21545,7 @@ async function runReview(opts, globals) {
|
|
|
21071
21545
|
const securityFiles = filterSecurity(allFiles);
|
|
21072
21546
|
let staticResults;
|
|
21073
21547
|
if (isCodacyAvailable()) {
|
|
21074
|
-
const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0,
|
|
21548
|
+
const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs37.existsSync)(f) || resolveFile(f) !== null);
|
|
21075
21549
|
staticResults = runCodacyAnalysis(scannable);
|
|
21076
21550
|
} else {
|
|
21077
21551
|
staticResults = {
|
|
@@ -21097,10 +21571,10 @@ async function runReview(opts, globals) {
|
|
|
21097
21571
|
const specPaths = opts.specs.split(",").map((f) => f.trim()).filter(Boolean);
|
|
21098
21572
|
specs = [];
|
|
21099
21573
|
for (const p of specPaths) {
|
|
21100
|
-
if (!(0,
|
|
21574
|
+
if (!(0, import_node_fs37.existsSync)(p)) continue;
|
|
21101
21575
|
try {
|
|
21102
|
-
const { readFileSync:
|
|
21103
|
-
const content =
|
|
21576
|
+
const { readFileSync: readFileSync23 } = await import("node:fs");
|
|
21577
|
+
const content = readFileSync23(p, "utf-8");
|
|
21104
21578
|
specs.push({ path: p, content: content.slice(0, 10240) });
|
|
21105
21579
|
} catch {
|
|
21106
21580
|
}
|
|
@@ -21157,7 +21631,7 @@ async function runReview(opts, globals) {
|
|
|
21157
21631
|
}
|
|
21158
21632
|
|
|
21159
21633
|
// src/commands/guard.ts
|
|
21160
|
-
var
|
|
21634
|
+
var import_node_fs38 = require("node:fs");
|
|
21161
21635
|
var import_node_path27 = require("node:path");
|
|
21162
21636
|
var GUARD_BLOCK_CAP = 2;
|
|
21163
21637
|
var GUARD_ITER_FILE = (0, import_node_path27.join)(VERITY_DIR, ".guard-iteration");
|
|
@@ -21205,7 +21679,7 @@ function readPreToolUseStdin() {
|
|
|
21205
21679
|
}
|
|
21206
21680
|
function readIterMap() {
|
|
21207
21681
|
try {
|
|
21208
|
-
const raw = JSON.parse((0,
|
|
21682
|
+
const raw = JSON.parse((0, import_node_fs38.readFileSync)(GUARD_ITER_FILE, "utf-8"));
|
|
21209
21683
|
if (raw && typeof raw === "object") {
|
|
21210
21684
|
if (typeof raw.moment === "string" && typeof raw.count === "number") {
|
|
21211
21685
|
return { [raw.moment]: raw.count };
|
|
@@ -21225,10 +21699,10 @@ function readIter(moment) {
|
|
|
21225
21699
|
}
|
|
21226
21700
|
function writeIter(moment, count) {
|
|
21227
21701
|
try {
|
|
21228
|
-
(0,
|
|
21702
|
+
(0, import_node_fs38.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
21229
21703
|
const map = readIterMap();
|
|
21230
21704
|
map[moment] = count;
|
|
21231
|
-
(0,
|
|
21705
|
+
(0, import_node_fs38.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
|
|
21232
21706
|
} catch {
|
|
21233
21707
|
}
|
|
21234
21708
|
}
|
|
@@ -21238,10 +21712,10 @@ function resetIter(moment) {
|
|
|
21238
21712
|
if (!(moment in map)) return;
|
|
21239
21713
|
delete map[moment];
|
|
21240
21714
|
if (Object.keys(map).length === 0) {
|
|
21241
|
-
if ((0,
|
|
21715
|
+
if ((0, import_node_fs38.existsSync)(GUARD_ITER_FILE)) (0, import_node_fs38.unlinkSync)(GUARD_ITER_FILE);
|
|
21242
21716
|
} else {
|
|
21243
|
-
(0,
|
|
21244
|
-
(0,
|
|
21717
|
+
(0, import_node_fs38.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
21718
|
+
(0, import_node_fs38.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
|
|
21245
21719
|
}
|
|
21246
21720
|
} catch {
|
|
21247
21721
|
}
|
|
@@ -21302,16 +21776,17 @@ function extractStatedIntent(moment, command, pushedMessages = null) {
|
|
|
21302
21776
|
const text = moment === "pre-commit" ? parseCommitMessage(command) : parsePrIntent(command) ?? pushedMessages;
|
|
21303
21777
|
return isSubstantiveIntent(text) ? text : null;
|
|
21304
21778
|
}
|
|
21305
|
-
function hasBlockingFinding(response) {
|
|
21779
|
+
function hasBlockingFinding(response, sentFiles) {
|
|
21306
21780
|
const findings = response.findings ?? [];
|
|
21307
|
-
|
|
21781
|
+
const sent = sentFiles ? new Set(sentFiles.map((p) => p.replace(/\\/g, "/"))) : null;
|
|
21782
|
+
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
21783
|
}
|
|
21309
21784
|
function buildGuardRequest(moment, files, codeDelta, iter, sessionId, statedIntent, coverageTelemetry) {
|
|
21310
21785
|
const analyzable = filterAnalyzable(files);
|
|
21311
21786
|
const securityFiles = filterSecurity(files);
|
|
21312
21787
|
let staticResults;
|
|
21313
21788
|
if (isCodacyAvailable()) {
|
|
21314
|
-
const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0,
|
|
21789
|
+
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
21790
|
staticResults = runCodacyAnalysis(scannable);
|
|
21316
21791
|
} else {
|
|
21317
21792
|
staticResults = { tool: "@codacy/analysis-cli", findings: [], summary: { total_findings: 0, by_severity: {}, tools_run: [] } };
|
|
@@ -21468,7 +21943,7 @@ async function runGuard(opts, globals) {
|
|
|
21468
21943
|
const link = viewUrl ? ` \u2014 ${viewUrl}` : "";
|
|
21469
21944
|
const covLine = coverageSummary(coverage);
|
|
21470
21945
|
const covDetail = coverageBlock(coverage);
|
|
21471
|
-
if (decision === "FAIL" && hasBlockingFinding(response)) {
|
|
21946
|
+
if (decision === "FAIL" && hasBlockingFinding(response, codeDelta.files.map((f) => f.path))) {
|
|
21472
21947
|
writeIter(moment, iter + 1);
|
|
21473
21948
|
writeBlockMessage(moment, response, covDetail);
|
|
21474
21949
|
process.exit(2);
|
|
@@ -21538,15 +22013,107 @@ function writeBlockMessage(moment, response, covDetail) {
|
|
|
21538
22013
|
`);
|
|
21539
22014
|
}
|
|
21540
22015
|
|
|
22016
|
+
// src/commands/ignore.ts
|
|
22017
|
+
function registerIgnoreCommand(program2) {
|
|
22018
|
+
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) => {
|
|
22019
|
+
const globals = program2.opts();
|
|
22020
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
22021
|
+
const reason = opts.reason?.trim();
|
|
22022
|
+
if (!reason) {
|
|
22023
|
+
printError(
|
|
22024
|
+
'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.'
|
|
22025
|
+
);
|
|
22026
|
+
process.exit(1);
|
|
22027
|
+
}
|
|
22028
|
+
if (opts.for && opts.turn) {
|
|
22029
|
+
printError("Use either --turn or --for, not both \u2014 they are two different windows.");
|
|
22030
|
+
process.exit(1);
|
|
22031
|
+
}
|
|
22032
|
+
let scope2 = "turn";
|
|
22033
|
+
let ttl = TURN_FUSE_SECONDS;
|
|
22034
|
+
if (opts.for) {
|
|
22035
|
+
const parsed = parseDuration(opts.for);
|
|
22036
|
+
if (!parsed.ok) {
|
|
22037
|
+
printError(parsed.error);
|
|
22038
|
+
process.exit(1);
|
|
22039
|
+
}
|
|
22040
|
+
scope2 = "window";
|
|
22041
|
+
ttl = parsed.seconds;
|
|
22042
|
+
}
|
|
22043
|
+
const tokenResult = await resolveToken(globals.token);
|
|
22044
|
+
const token = tokenResult.ok ? tokenResult.data.token : void 0;
|
|
22045
|
+
const sessionId = opts.sessionId || process.env.CLAUDE_SESSION_ID || void 0;
|
|
22046
|
+
const keys = ignoreStateKeys(token, sessionId);
|
|
22047
|
+
const existing = resolveIgnoreState(keys);
|
|
22048
|
+
const spent = existing?.state.spent ?? 0;
|
|
22049
|
+
const writeKey = existing?.key ?? keys[0];
|
|
22050
|
+
if (spent >= IGNORE_BUDGET) {
|
|
22051
|
+
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.`;
|
|
22052
|
+
if (opts.json) {
|
|
22053
|
+
printJson({ declared: false, reason_refused: "budget-spent", spent, budget: IGNORE_BUDGET });
|
|
22054
|
+
} else {
|
|
22055
|
+
printWarn(msg);
|
|
22056
|
+
}
|
|
22057
|
+
logEvent("ignore_refused", { why: "budget-spent", spent, budget: IGNORE_BUDGET });
|
|
22058
|
+
process.exit(0);
|
|
22059
|
+
}
|
|
22060
|
+
const declaration = {
|
|
22061
|
+
scope: scope2,
|
|
22062
|
+
origin: opts.agent ? "agent" : "user",
|
|
22063
|
+
reason,
|
|
22064
|
+
at: now,
|
|
22065
|
+
expires: now + ttl
|
|
22066
|
+
};
|
|
22067
|
+
writeIgnoreState({ v: 1, active: declaration, spent: spent + 1 }, writeKey);
|
|
22068
|
+
logEvent("ignore_declared", {
|
|
22069
|
+
scope: scope2,
|
|
22070
|
+
origin: declaration.origin,
|
|
22071
|
+
ttl_seconds: ttl,
|
|
22072
|
+
spent: spent + 1,
|
|
22073
|
+
budget: IGNORE_BUDGET
|
|
22074
|
+
});
|
|
22075
|
+
if (opts.json) {
|
|
22076
|
+
printJson({
|
|
22077
|
+
declared: true,
|
|
22078
|
+
scope: scope2,
|
|
22079
|
+
origin: declaration.origin,
|
|
22080
|
+
reason,
|
|
22081
|
+
expires_at: new Date(declaration.expires * 1e3).toISOString(),
|
|
22082
|
+
spent: spent + 1,
|
|
22083
|
+
budget: IGNORE_BUDGET
|
|
22084
|
+
});
|
|
22085
|
+
return;
|
|
22086
|
+
}
|
|
22087
|
+
const window = scope2 === "turn" ? "the next turn" : `the next ${describeRemaining(declaration, now).replace(" left", "")}`;
|
|
22088
|
+
printInfo(`Verity will skip ${window} \u2014 "${reason}" (${spent + 1}/${IGNORE_BUDGET} this session).`);
|
|
22089
|
+
printInfo("It covers turns that author nothing. If anything is written, the declaration voids and the review runs.");
|
|
22090
|
+
});
|
|
22091
|
+
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) => {
|
|
22092
|
+
const globals = program2.opts();
|
|
22093
|
+
const tokenResult = await resolveToken(globals.token);
|
|
22094
|
+
const token = tokenResult.ok ? tokenResult.data.token : void 0;
|
|
22095
|
+
const sessionId = opts.sessionId || process.env.CLAUDE_SESSION_ID || void 0;
|
|
22096
|
+
const found = resolveIgnoreState(ignoreStateKeys(token, sessionId));
|
|
22097
|
+
const active = resolveActive(found?.state ?? null, Math.floor(Date.now() / 1e3));
|
|
22098
|
+
if (!found || !active) {
|
|
22099
|
+
printInfo("No active ignore declaration.");
|
|
22100
|
+
return;
|
|
22101
|
+
}
|
|
22102
|
+
clearActiveDeclaration(found.key);
|
|
22103
|
+
logEvent("ignore_cleared", { scope: active.scope, origin: active.origin });
|
|
22104
|
+
printInfo(`Cleared: "${active.reason}". The next turn will be reviewed normally.`);
|
|
22105
|
+
});
|
|
22106
|
+
}
|
|
22107
|
+
|
|
21541
22108
|
// src/commands/init.ts
|
|
21542
|
-
var
|
|
22109
|
+
var import_node_fs40 = require("node:fs");
|
|
21543
22110
|
var import_promises13 = require("node:fs/promises");
|
|
21544
22111
|
var import_node_path29 = require("node:path");
|
|
21545
22112
|
var import_node_child_process11 = require("node:child_process");
|
|
21546
22113
|
var readline2 = __toESM(require("node:readline/promises"));
|
|
21547
22114
|
|
|
21548
22115
|
// src/commands/migrate.ts
|
|
21549
|
-
var
|
|
22116
|
+
var import_node_fs39 = require("node:fs");
|
|
21550
22117
|
var import_node_path28 = require("node:path");
|
|
21551
22118
|
var import_node_child_process10 = require("node:child_process");
|
|
21552
22119
|
|
|
@@ -21678,10 +22245,10 @@ async function runMigration(opts = {}) {
|
|
|
21678
22245
|
function migrateProjectDir(root, actions) {
|
|
21679
22246
|
const gateDir = (0, import_node_path28.join)(root, ".gate");
|
|
21680
22247
|
const verityDir = (0, import_node_path28.join)(root, ".verity");
|
|
21681
|
-
if ((0,
|
|
22248
|
+
if ((0, import_node_fs39.existsSync)(gateDir) && !(0, import_node_fs39.existsSync)(verityDir)) {
|
|
21682
22249
|
return migrateProjectDirRename(root, gateDir, verityDir, actions);
|
|
21683
22250
|
}
|
|
21684
|
-
if ((0,
|
|
22251
|
+
if ((0, import_node_fs39.existsSync)(gateDir) && (0, import_node_fs39.existsSync)(verityDir)) {
|
|
21685
22252
|
return migrateProjectDirCarry(gateDir, verityDir, actions);
|
|
21686
22253
|
}
|
|
21687
22254
|
return false;
|
|
@@ -21702,13 +22269,13 @@ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
|
|
|
21702
22269
|
}
|
|
21703
22270
|
}
|
|
21704
22271
|
if (moved) {
|
|
21705
|
-
if ((0,
|
|
22272
|
+
if ((0, import_node_fs39.existsSync)(gateDir)) {
|
|
21706
22273
|
const carried = carryLegacyContents(gateDir, verityDir);
|
|
21707
22274
|
if (carried > 0) {
|
|
21708
22275
|
actions.push(`Carried ${carried} untracked legacy file(s) from .gate/ into .verity/`);
|
|
21709
22276
|
}
|
|
21710
22277
|
try {
|
|
21711
|
-
(0,
|
|
22278
|
+
(0, import_node_fs39.rmSync)(gateDir, { recursive: true, force: true });
|
|
21712
22279
|
} catch {
|
|
21713
22280
|
}
|
|
21714
22281
|
}
|
|
@@ -21724,7 +22291,7 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
|
|
|
21724
22291
|
actions.push(`Carried ${carried} legacy file(s) from .gate/ into .verity/`);
|
|
21725
22292
|
}
|
|
21726
22293
|
try {
|
|
21727
|
-
(0,
|
|
22294
|
+
(0, import_node_fs39.rmSync)(gateDir, { recursive: true, force: true });
|
|
21728
22295
|
} catch {
|
|
21729
22296
|
}
|
|
21730
22297
|
return carried > 0;
|
|
@@ -21733,9 +22300,9 @@ function migrateGlobalCredentials(home, actions) {
|
|
|
21733
22300
|
if (!home) return;
|
|
21734
22301
|
const gateCreds = (0, import_node_path28.join)(home, ".gate", "credentials");
|
|
21735
22302
|
const verityCreds = (0, import_node_path28.join)(home, ".verity", "credentials");
|
|
21736
|
-
if (!(0,
|
|
21737
|
-
if (!(0,
|
|
21738
|
-
(0,
|
|
22303
|
+
if (!(0, import_node_fs39.existsSync)(gateCreds)) return;
|
|
22304
|
+
if (!(0, import_node_fs39.existsSync)(verityCreds)) {
|
|
22305
|
+
(0, import_node_fs39.mkdirSync)((0, import_node_path28.join)(home, ".verity"), { recursive: true });
|
|
21739
22306
|
moveFile(gateCreds, verityCreds);
|
|
21740
22307
|
actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
|
|
21741
22308
|
return;
|
|
@@ -21758,7 +22325,7 @@ async function migrateLegacyHooks(root, actions) {
|
|
|
21758
22325
|
}
|
|
21759
22326
|
async function migrateClaudeMd(root, actions) {
|
|
21760
22327
|
const claudeMd = (0, import_node_path28.join)(root, "CLAUDE.md");
|
|
21761
|
-
const hadLegacyBlock = (0,
|
|
22328
|
+
const hadLegacyBlock = (0, import_node_fs39.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
|
|
21762
22329
|
if (!hadLegacyBlock) return;
|
|
21763
22330
|
try {
|
|
21764
22331
|
await ensureClaudeMdPointer(root);
|
|
@@ -21770,7 +22337,7 @@ async function migrateClaudeMd(root, actions) {
|
|
|
21770
22337
|
function migrateStandardFile(root, actions) {
|
|
21771
22338
|
const gateMd = (0, import_node_path28.join)(root, "GATE.md");
|
|
21772
22339
|
const verityMd = (0, import_node_path28.join)(root, "VERITY.md");
|
|
21773
|
-
if (!(0,
|
|
22340
|
+
if (!(0, import_node_fs39.existsSync)(gateMd) || (0, import_node_fs39.existsSync)(verityMd)) return;
|
|
21774
22341
|
let moved = false;
|
|
21775
22342
|
if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
|
|
21776
22343
|
try {
|
|
@@ -21782,12 +22349,12 @@ function migrateStandardFile(root, actions) {
|
|
|
21782
22349
|
if (!moved) moveFile(gateMd, verityMd);
|
|
21783
22350
|
const content = readFileSyncSafe(verityMd);
|
|
21784
22351
|
const refreshed = content.split("GATE.md").join("VERITY.md");
|
|
21785
|
-
if (refreshed !== content) (0,
|
|
22352
|
+
if (refreshed !== content) (0, import_node_fs39.writeFileSync)(verityMd, refreshed);
|
|
21786
22353
|
actions.push("Renamed GATE.md \u2192 VERITY.md");
|
|
21787
22354
|
}
|
|
21788
22355
|
async function migrateTelemetryHeaders(root, actions) {
|
|
21789
22356
|
const file = (0, import_node_path28.join)(root, ".claude", "settings.local.json");
|
|
21790
|
-
if (!(0,
|
|
22357
|
+
if (!(0, import_node_fs39.existsSync)(file)) return;
|
|
21791
22358
|
let settings;
|
|
21792
22359
|
try {
|
|
21793
22360
|
settings = JSON.parse(readFileSyncSafe(file) || "{}");
|
|
@@ -21835,14 +22402,14 @@ function mergeGlobalCredentials(gateCreds, verityCreds) {
|
|
|
21835
22402
|
}
|
|
21836
22403
|
if (toAppend.length > 0) {
|
|
21837
22404
|
const sep2 = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
|
|
21838
|
-
(0,
|
|
22405
|
+
(0, import_node_fs39.writeFileSync)(verityCreds, verityContent + sep2 + toAppend.join("\n") + "\n");
|
|
21839
22406
|
}
|
|
21840
|
-
(0,
|
|
22407
|
+
(0, import_node_fs39.rmSync)(gateCreds, { force: true });
|
|
21841
22408
|
return toAppend.length;
|
|
21842
22409
|
}
|
|
21843
22410
|
function readFileSyncSafe(path) {
|
|
21844
22411
|
try {
|
|
21845
|
-
return (0,
|
|
22412
|
+
return (0, import_node_fs39.readFileSync)(path, "utf-8");
|
|
21846
22413
|
} catch {
|
|
21847
22414
|
return "";
|
|
21848
22415
|
}
|
|
@@ -21857,35 +22424,35 @@ function hasStagedChanges(root) {
|
|
|
21857
22424
|
}
|
|
21858
22425
|
function moveDir(from, to) {
|
|
21859
22426
|
try {
|
|
21860
|
-
(0,
|
|
22427
|
+
(0, import_node_fs39.renameSync)(from, to);
|
|
21861
22428
|
} catch (err) {
|
|
21862
22429
|
if (err.code !== "EXDEV") throw err;
|
|
21863
|
-
(0,
|
|
21864
|
-
(0,
|
|
22430
|
+
(0, import_node_fs39.cpSync)(from, to, { recursive: true });
|
|
22431
|
+
(0, import_node_fs39.rmSync)(from, { recursive: true, force: true });
|
|
21865
22432
|
}
|
|
21866
22433
|
}
|
|
21867
22434
|
function moveFile(from, to) {
|
|
21868
22435
|
try {
|
|
21869
|
-
(0,
|
|
22436
|
+
(0, import_node_fs39.renameSync)(from, to);
|
|
21870
22437
|
} catch (err) {
|
|
21871
22438
|
if (err.code !== "EXDEV") throw err;
|
|
21872
|
-
(0,
|
|
21873
|
-
(0,
|
|
22439
|
+
(0, import_node_fs39.cpSync)(from, to);
|
|
22440
|
+
(0, import_node_fs39.rmSync)(from, { force: true });
|
|
21874
22441
|
}
|
|
21875
22442
|
}
|
|
21876
22443
|
function carryLegacyContents(gateDir, verityDir) {
|
|
21877
22444
|
let copied = 0;
|
|
21878
22445
|
const walk = (relDir) => {
|
|
21879
22446
|
const srcDir = (0, import_node_path28.join)(gateDir, relDir);
|
|
21880
|
-
for (const entry of (0,
|
|
22447
|
+
for (const entry of (0, import_node_fs39.readdirSync)(srcDir)) {
|
|
21881
22448
|
const rel = relDir ? (0, import_node_path28.join)(relDir, entry) : entry;
|
|
21882
22449
|
const src = (0, import_node_path28.join)(gateDir, rel);
|
|
21883
22450
|
const dest = (0, import_node_path28.join)(verityDir, rel);
|
|
21884
|
-
if ((0,
|
|
22451
|
+
if ((0, import_node_fs39.statSync)(src).isDirectory()) {
|
|
21885
22452
|
walk(rel);
|
|
21886
|
-
} else if (!(0,
|
|
21887
|
-
(0,
|
|
21888
|
-
(0,
|
|
22453
|
+
} else if (!(0, import_node_fs39.existsSync)(dest)) {
|
|
22454
|
+
(0, import_node_fs39.mkdirSync)((0, import_node_path28.dirname)(dest), { recursive: true });
|
|
22455
|
+
(0, import_node_fs39.cpSync)(src, dest);
|
|
21889
22456
|
copied++;
|
|
21890
22457
|
}
|
|
21891
22458
|
}
|
|
@@ -21896,20 +22463,20 @@ function carryLegacyContents(gateDir, verityDir) {
|
|
|
21896
22463
|
async function needsMigration(root = repoRoot()) {
|
|
21897
22464
|
const gateDir = (0, import_node_path28.join)(root, ".gate");
|
|
21898
22465
|
const verityDir = (0, import_node_path28.join)(root, ".verity");
|
|
21899
|
-
if ((0,
|
|
21900
|
-
if ((0,
|
|
21901
|
-
if ((0,
|
|
22466
|
+
if ((0, import_node_fs39.existsSync)(gateDir) && !(0, import_node_fs39.existsSync)(verityDir)) return true;
|
|
22467
|
+
if ((0, import_node_fs39.existsSync)(gateDir) && (0, import_node_fs39.existsSync)(verityDir)) {
|
|
22468
|
+
if ((0, import_node_fs39.existsSync)((0, import_node_path28.join)(gateDir, "credentials")) && !(0, import_node_fs39.existsSync)((0, import_node_path28.join)(verityDir, "credentials"))) {
|
|
21902
22469
|
return true;
|
|
21903
22470
|
}
|
|
21904
|
-
if ((0,
|
|
22471
|
+
if ((0, import_node_fs39.existsSync)((0, import_node_path28.join)(gateDir, "memory")) && !(0, import_node_fs39.existsSync)((0, import_node_path28.join)(verityDir, "memory"))) {
|
|
21905
22472
|
return true;
|
|
21906
22473
|
}
|
|
21907
22474
|
}
|
|
21908
22475
|
const claudeMd = (0, import_node_path28.join)(root, "CLAUDE.md");
|
|
21909
|
-
if ((0,
|
|
22476
|
+
if ((0, import_node_fs39.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
|
|
21910
22477
|
return true;
|
|
21911
22478
|
}
|
|
21912
|
-
if ((0,
|
|
22479
|
+
if ((0, import_node_fs39.existsSync)((0, import_node_path28.join)(root, "GATE.md")) && !(0, import_node_fs39.existsSync)((0, import_node_path28.join)(root, "VERITY.md"))) {
|
|
21913
22480
|
return true;
|
|
21914
22481
|
}
|
|
21915
22482
|
if (await hasLegacyHooksAt(root)) return true;
|
|
@@ -22053,7 +22620,7 @@ function resolveDataDir() {
|
|
|
22053
22620
|
// local dev: running from repo root
|
|
22054
22621
|
];
|
|
22055
22622
|
for (const candidate of candidates) {
|
|
22056
|
-
if ((0,
|
|
22623
|
+
if ((0, import_node_fs40.existsSync)((0, import_node_path29.join)(candidate, "skills"))) {
|
|
22057
22624
|
return candidate;
|
|
22058
22625
|
}
|
|
22059
22626
|
}
|
|
@@ -22069,7 +22636,7 @@ function registerInitCommand(program2) {
|
|
|
22069
22636
|
program2.command("init").description("Initialize Verity in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
|
|
22070
22637
|
const force = opts.force ?? false;
|
|
22071
22638
|
const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
|
|
22072
|
-
const isProject = projectMarkers.some((m) => (0,
|
|
22639
|
+
const isProject = projectMarkers.some((m) => (0, import_node_fs40.existsSync)(m));
|
|
22073
22640
|
if (!isProject) {
|
|
22074
22641
|
printError("No project detected in the current directory.");
|
|
22075
22642
|
printInfo('Run "verity init" from your project root.');
|
|
@@ -22139,14 +22706,14 @@ function registerInitCommand(program2) {
|
|
|
22139
22706
|
for (const skill of skills) {
|
|
22140
22707
|
const src = (0, import_node_path29.join)(skillsSource, skill);
|
|
22141
22708
|
const dest = (0, import_node_path29.join)(skillsDest, skill);
|
|
22142
|
-
if (!(0,
|
|
22709
|
+
if (!(0, import_node_fs40.existsSync)(src)) {
|
|
22143
22710
|
printWarn(` Skill data not found: ${skill}`);
|
|
22144
22711
|
continue;
|
|
22145
22712
|
}
|
|
22146
|
-
if ((0,
|
|
22713
|
+
if ((0, import_node_fs40.existsSync)(dest) && !force) {
|
|
22147
22714
|
const srcSkill = (0, import_node_path29.join)(src, "SKILL.md");
|
|
22148
22715
|
const destSkill = (0, import_node_path29.join)(dest, "SKILL.md");
|
|
22149
|
-
if ((0,
|
|
22716
|
+
if ((0, import_node_fs40.existsSync)(destSkill)) {
|
|
22150
22717
|
try {
|
|
22151
22718
|
const srcContent = await (0, import_promises13.readFile)(srcSkill, "utf-8");
|
|
22152
22719
|
const destContent = await (0, import_promises13.readFile)(destSkill, "utf-8");
|
|
@@ -22224,7 +22791,7 @@ function registerInitCommand(program2) {
|
|
|
22224
22791
|
}
|
|
22225
22792
|
|
|
22226
22793
|
// src/commands/uninstall.ts
|
|
22227
|
-
var
|
|
22794
|
+
var import_node_fs41 = require("node:fs");
|
|
22228
22795
|
var import_node_path30 = require("node:path");
|
|
22229
22796
|
var SKILL_NAMES = [
|
|
22230
22797
|
"verity-setup",
|
|
@@ -22245,10 +22812,10 @@ function registerUninstallCommand(program2) {
|
|
|
22245
22812
|
const skillsRoot = projectPath(".claude/skills");
|
|
22246
22813
|
for (const name of SKILL_NAMES) {
|
|
22247
22814
|
const dir = (0, import_node_path30.join)(skillsRoot, name);
|
|
22248
|
-
if ((0,
|
|
22815
|
+
if ((0, import_node_fs41.existsSync)(dir)) {
|
|
22249
22816
|
actions.push({
|
|
22250
22817
|
label: `Remove .claude/skills/${name}/`,
|
|
22251
|
-
apply: () => (0,
|
|
22818
|
+
apply: () => (0, import_node_fs41.rmSync)(dir, { recursive: true, force: true })
|
|
22252
22819
|
});
|
|
22253
22820
|
}
|
|
22254
22821
|
}
|
|
@@ -22262,24 +22829,24 @@ function registerUninstallCommand(program2) {
|
|
|
22262
22829
|
});
|
|
22263
22830
|
}
|
|
22264
22831
|
const verityDir = projectPath(VERITY_DIR);
|
|
22265
|
-
if ((0,
|
|
22832
|
+
if ((0, import_node_fs41.existsSync)(verityDir)) {
|
|
22266
22833
|
actions.push({
|
|
22267
22834
|
label: `Remove ${VERITY_DIR}/`,
|
|
22268
|
-
apply: () => (0,
|
|
22835
|
+
apply: () => (0, import_node_fs41.rmSync)(verityDir, { recursive: true, force: true })
|
|
22269
22836
|
});
|
|
22270
22837
|
}
|
|
22271
22838
|
if (!keepVerityMd) {
|
|
22272
22839
|
const verityMd = projectPath(VERITY_MD_FILE);
|
|
22273
|
-
if ((0,
|
|
22840
|
+
if ((0, import_node_fs41.existsSync)(verityMd)) {
|
|
22274
22841
|
actions.push({
|
|
22275
22842
|
label: `Remove ${VERITY_MD_FILE}`,
|
|
22276
|
-
apply: () => (0,
|
|
22843
|
+
apply: () => (0, import_node_fs41.rmSync)(verityMd, { force: true })
|
|
22277
22844
|
});
|
|
22278
22845
|
}
|
|
22279
22846
|
}
|
|
22280
22847
|
const cleanupEmptyDir = (path) => {
|
|
22281
|
-
if ((0,
|
|
22282
|
-
(0,
|
|
22848
|
+
if ((0, import_node_fs41.existsSync)(path) && (0, import_node_fs41.statSync)(path).isDirectory() && (0, import_node_fs41.readdirSync)(path).length === 0) {
|
|
22849
|
+
(0, import_node_fs41.rmdirSync)(path);
|
|
22283
22850
|
}
|
|
22284
22851
|
};
|
|
22285
22852
|
actions.push({
|
|
@@ -22291,10 +22858,10 @@ function registerUninstallCommand(program2) {
|
|
|
22291
22858
|
});
|
|
22292
22859
|
const home = process.env.HOME ?? "";
|
|
22293
22860
|
const globalVerityDir = (0, import_node_path30.join)(home, ".verity");
|
|
22294
|
-
if (purgeGlobal && (0,
|
|
22861
|
+
if (purgeGlobal && (0, import_node_fs41.existsSync)(globalVerityDir)) {
|
|
22295
22862
|
actions.push({
|
|
22296
22863
|
label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
|
|
22297
|
-
apply: () => (0,
|
|
22864
|
+
apply: () => (0, import_node_fs41.rmSync)(globalVerityDir, { recursive: true, force: true })
|
|
22298
22865
|
});
|
|
22299
22866
|
}
|
|
22300
22867
|
if (actions.length === 0) {
|
|
@@ -22488,7 +23055,7 @@ function registerTaskCommands(program2) {
|
|
|
22488
23055
|
}
|
|
22489
23056
|
|
|
22490
23057
|
// src/commands/reset.ts
|
|
22491
|
-
var
|
|
23058
|
+
var import_node_fs42 = require("node:fs");
|
|
22492
23059
|
var import_node_path31 = require("node:path");
|
|
22493
23060
|
function registerResetCommand(program2) {
|
|
22494
23061
|
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 +23093,11 @@ function registerResetCommand(program2) {
|
|
|
22526
23093
|
}
|
|
22527
23094
|
const cacheDir = projectPath(CACHE_DIR);
|
|
22528
23095
|
let purged = 0;
|
|
22529
|
-
if ((0,
|
|
22530
|
-
for (const entry of (0,
|
|
23096
|
+
if ((0, import_node_fs42.existsSync)(cacheDir)) {
|
|
23097
|
+
for (const entry of (0, import_node_fs42.readdirSync)(cacheDir)) {
|
|
22531
23098
|
if (entry.startsWith("pending-")) {
|
|
22532
23099
|
try {
|
|
22533
|
-
(0,
|
|
23100
|
+
(0, import_node_fs42.unlinkSync)((0, import_node_path31.join)(cacheDir, entry));
|
|
22534
23101
|
purged++;
|
|
22535
23102
|
} catch {
|
|
22536
23103
|
}
|
|
@@ -22545,19 +23112,19 @@ function registerResetCommand(program2) {
|
|
|
22545
23112
|
projectPath(`${VERITY_DIR}/.last-analysis`)
|
|
22546
23113
|
];
|
|
22547
23114
|
for (const file of filesToClear) {
|
|
22548
|
-
if ((0,
|
|
23115
|
+
if ((0, import_node_fs42.existsSync)(file)) {
|
|
22549
23116
|
try {
|
|
22550
|
-
(0,
|
|
23117
|
+
(0, import_node_fs42.writeFileSync)(file, "");
|
|
22551
23118
|
} catch {
|
|
22552
23119
|
}
|
|
22553
23120
|
}
|
|
22554
23121
|
}
|
|
22555
23122
|
if (opts.all) {
|
|
22556
23123
|
const logsDir = projectPath(`${VERITY_DIR}/.logs`);
|
|
22557
|
-
if ((0,
|
|
22558
|
-
for (const entry of (0,
|
|
23124
|
+
if ((0, import_node_fs42.existsSync)(logsDir)) {
|
|
23125
|
+
for (const entry of (0, import_node_fs42.readdirSync)(logsDir)) {
|
|
22559
23126
|
try {
|
|
22560
|
-
(0,
|
|
23127
|
+
(0, import_node_fs42.unlinkSync)((0, import_node_path31.join)(logsDir, entry));
|
|
22561
23128
|
} catch {
|
|
22562
23129
|
}
|
|
22563
23130
|
}
|
|
@@ -22865,8 +23432,8 @@ function registerTelemetryCommands(program2) {
|
|
|
22865
23432
|
}
|
|
22866
23433
|
|
|
22867
23434
|
// 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.
|
|
23435
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.31.0-experimental.48d33ea").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) => {
|
|
23436
|
+
installStderrLog(actionCommand.name(), process.argv.slice(2), "0.31.0-experimental.48d33ea");
|
|
22870
23437
|
setUserNamedServiceUrl(program.opts().serviceUrl);
|
|
22871
23438
|
try {
|
|
22872
23439
|
await foldLegacyLocalCredential();
|
|
@@ -22889,6 +23456,7 @@ registerAnalyzeCommand(program);
|
|
|
22889
23456
|
registerBaselineCommands(program);
|
|
22890
23457
|
registerReviewCommand(program);
|
|
22891
23458
|
registerGuardCommand(program);
|
|
23459
|
+
registerIgnoreCommand(program);
|
|
22892
23460
|
registerInitCommand(program);
|
|
22893
23461
|
registerUninstallCommand(program);
|
|
22894
23462
|
registerTaskCommands(program);
|