@codacy/verity-cli 0.29.4-experimental.e387745 → 0.29.4-experimental.e818a8a
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 +448 -149
- package/data/skills/verity-setup/SKILL.md +21 -8
- package/package.json +1 -1
package/bin/verity.js
CHANGED
|
@@ -10502,6 +10502,7 @@ var GITHUB_APP_INSTALL_URL = `https://github.com/apps/${GITHUB_APP_SLUG}/install
|
|
|
10502
10502
|
function githubAppInstallUrl(accountId) {
|
|
10503
10503
|
return accountId != null ? `https://github.com/apps/${GITHUB_APP_SLUG}/installations/new/permissions?target_id=${accountId}` : GITHUB_APP_INSTALL_URL;
|
|
10504
10504
|
}
|
|
10505
|
+
var ADVISORY_EPISODE_FILE = `${VERITY_DIR}/.advisory-episode`;
|
|
10505
10506
|
|
|
10506
10507
|
// src/lib/output.ts
|
|
10507
10508
|
var RED = "\x1B[0;31m";
|
|
@@ -15060,8 +15061,10 @@ function recordVerdict(d, v) {
|
|
|
15060
15061
|
if (summary) appendEvent(d, { k: "goal_delivered", summary });
|
|
15061
15062
|
}
|
|
15062
15063
|
const lines = /* @__PURE__ */ new Map();
|
|
15064
|
+
const sent = new Set(v.sentPaths);
|
|
15063
15065
|
for (const f of v.findings) {
|
|
15064
15066
|
if (!f.file || typeof f.line !== "number" || !f.pattern_id) continue;
|
|
15067
|
+
if (!sent.has(f.file)) continue;
|
|
15065
15068
|
if (!lines.has(f.file)) {
|
|
15066
15069
|
try {
|
|
15067
15070
|
const abs = (0, import_node_path12.join)(root, f.file);
|
|
@@ -15513,6 +15516,33 @@ ${addedLines}`,
|
|
|
15513
15516
|
}
|
|
15514
15517
|
return { diffs, has_snapshots: true };
|
|
15515
15518
|
}
|
|
15519
|
+
function ensureSnapshotGitignored() {
|
|
15520
|
+
let content = "";
|
|
15521
|
+
try {
|
|
15522
|
+
content = (0, import_node_fs15.readFileSync)(".gitignore", "utf-8");
|
|
15523
|
+
} catch {
|
|
15524
|
+
}
|
|
15525
|
+
let ignored = null;
|
|
15526
|
+
try {
|
|
15527
|
+
(0, import_node_child_process6.execSync)("git check-ignore -q -- .verity/.snapshot/__probe__", { stdio: "pipe" });
|
|
15528
|
+
ignored = true;
|
|
15529
|
+
} catch (err) {
|
|
15530
|
+
ignored = err.status === 1 ? false : null;
|
|
15531
|
+
}
|
|
15532
|
+
if (ignored === true) return "covered";
|
|
15533
|
+
if (ignored === null) {
|
|
15534
|
+
const lines = content.split("\n").map((l) => l.trim());
|
|
15535
|
+
const covering = [".verity/.snapshot/", ".verity/.snapshot", ".verity/", ".verity", ".verity/*"];
|
|
15536
|
+
if (lines.some((l) => covering.includes(l))) return "covered";
|
|
15537
|
+
}
|
|
15538
|
+
try {
|
|
15539
|
+
const block = "# Verity \u2014 snapshots of analyzed files (machine state, never commit)\n.verity/.snapshot/\n";
|
|
15540
|
+
(0, import_node_fs15.writeFileSync)(".gitignore", content ? content + (content.endsWith("\n") ? "" : "\n") + "\n" + block : block);
|
|
15541
|
+
return "added";
|
|
15542
|
+
} catch {
|
|
15543
|
+
return "failed";
|
|
15544
|
+
}
|
|
15545
|
+
}
|
|
15516
15546
|
function saveSnapshots(files) {
|
|
15517
15547
|
const snapshotPaths = /* @__PURE__ */ new Set();
|
|
15518
15548
|
for (const file of files) {
|
|
@@ -15557,7 +15587,7 @@ function cleanStaleSnapshots(dir, keepSet) {
|
|
|
15557
15587
|
try {
|
|
15558
15588
|
const entries = (0, import_node_fs15.readdirSync)(dir, { withFileTypes: true });
|
|
15559
15589
|
for (const entry of entries) {
|
|
15560
|
-
if (entry.name.
|
|
15590
|
+
if (dir === SNAPSHOT_DIR && (entry.name === ".diff-old.tmp" || entry.name === ".diff-new.tmp")) continue;
|
|
15561
15591
|
const fullPath = (0, import_node_path14.join)(dir, entry.name);
|
|
15562
15592
|
if (entry.isDirectory()) {
|
|
15563
15593
|
cleanStaleSnapshots(fullPath, keepSet);
|
|
@@ -16717,7 +16747,7 @@ function formatRunEvidence(run, startedAt) {
|
|
|
16717
16747
|
const md = run.modeDecision;
|
|
16718
16748
|
if (md) {
|
|
16719
16749
|
const how = md.forced ? "forced by --mode" : `predicted=${md.predicted ?? "none"} \u2192 ${md.resolved}`;
|
|
16720
|
-
out += row("mode", `${md.resolved} \xB7 ${how} \xB7 authored=${md.authored ? "yes" : "no"} \xB7 investigated=${md.investigated ? "yes" : "no"}`);
|
|
16750
|
+
out += row("mode", `${md.resolved} \xB7 ${how}` + (md.flip ? ` (flipped to plan: no delta at ${md.flip})` : "") + ` \xB7 authored=${md.authored ? "yes" : "no"} \xB7 investigated=${md.investigated ? "yes" : "no"}`);
|
|
16721
16751
|
} else {
|
|
16722
16752
|
out += row("mode", `? (this run stopped in ${run.phaseReached || "no phase"}, before the mode was decided)`);
|
|
16723
16753
|
}
|
|
@@ -17146,6 +17176,39 @@ async function bootstrap(run) {
|
|
|
17146
17176
|
Object.assign(run, { actionSummary, assistantResponse, baseline, baselineSessionId, reachability, sessionId, stopReason, tokenResult, transcriptPath, turnId });
|
|
17147
17177
|
}
|
|
17148
17178
|
|
|
17179
|
+
// src/lib/self-scope.ts
|
|
17180
|
+
var LEGACY_GATE_SKILLS = /* @__PURE__ */ new Set([
|
|
17181
|
+
"gate-setup",
|
|
17182
|
+
"gate-analyze",
|
|
17183
|
+
"gate-review",
|
|
17184
|
+
"gate-status",
|
|
17185
|
+
"gate-feedback",
|
|
17186
|
+
"gate-insights",
|
|
17187
|
+
"gate-learn",
|
|
17188
|
+
"gate-memory",
|
|
17189
|
+
"gate-reflect"
|
|
17190
|
+
]);
|
|
17191
|
+
function isVerityOwned(path) {
|
|
17192
|
+
const segments = path.replace(/\\/g, "/").split("/");
|
|
17193
|
+
for (let i = 0; i < segments.length; i++) {
|
|
17194
|
+
const seg = segments[i];
|
|
17195
|
+
if (seg === ".verity" || seg === ".codacy") return true;
|
|
17196
|
+
if (i === segments.length - 1 && (seg === "VERITY.md" || seg === "GATE.md")) return true;
|
|
17197
|
+
if (seg === ".claude" && segments[i + 1] === "skills" && typeof segments[i + 2] === "string") {
|
|
17198
|
+
const skill = segments[i + 2];
|
|
17199
|
+
if (skill.startsWith("verity-") || LEGACY_GATE_SKILLS.has(skill)) return true;
|
|
17200
|
+
}
|
|
17201
|
+
if (seg === ".claude" && segments[i + 1] === "settings.json") return true;
|
|
17202
|
+
}
|
|
17203
|
+
return false;
|
|
17204
|
+
}
|
|
17205
|
+
function partitionVerityOwned(paths) {
|
|
17206
|
+
const kept = [];
|
|
17207
|
+
const owned = [];
|
|
17208
|
+
for (const p of paths) (isVerityOwned(p) ? owned : kept).push(p);
|
|
17209
|
+
return { kept, owned };
|
|
17210
|
+
}
|
|
17211
|
+
|
|
17149
17212
|
// src/lib/channel.ts
|
|
17150
17213
|
var MAX_AGENT_CONTEXT_CHARS = 1500;
|
|
17151
17214
|
var MAX_AGENT_ITEMS = 5;
|
|
@@ -17155,6 +17218,29 @@ function renderItem(label2, text, patternId, file, line) {
|
|
|
17155
17218
|
const id = patternId ? ` [${patternId}]` : "";
|
|
17156
17219
|
return `- ${label2}${text}${where}${id}`;
|
|
17157
17220
|
}
|
|
17221
|
+
function channelInputFrom(response, intentRepeat = 0, priorPendingFingerprints = []) {
|
|
17222
|
+
const metadata = response.metadata ?? {};
|
|
17223
|
+
const intent = response.intent_alignment ?? {};
|
|
17224
|
+
return {
|
|
17225
|
+
intentRepeat,
|
|
17226
|
+
priorPendingFingerprints,
|
|
17227
|
+
gateDecision: String(response.gate_decision ?? ""),
|
|
17228
|
+
findings: response.findings ?? [],
|
|
17229
|
+
pendingItems: response.pending_items ?? [],
|
|
17230
|
+
reviewStatus: metadata.review_status,
|
|
17231
|
+
coverage: metadata.coverage,
|
|
17232
|
+
intentVerdict: intent.verdict,
|
|
17233
|
+
intentGaps: intent.gaps
|
|
17234
|
+
};
|
|
17235
|
+
}
|
|
17236
|
+
function classifyChannelContent(input) {
|
|
17237
|
+
const refusal = input.reviewStatus === "not_reviewed" || input.reviewStatus === "no_authorship_evidence";
|
|
17238
|
+
const intentFlag = input.intentVerdict === "misaligned" || input.intentVerdict === "partial";
|
|
17239
|
+
const advisory = (input.findings ?? []).some((f) => f.scope !== "pre-existing") || (input.pendingItems ?? []).some(
|
|
17240
|
+
(p) => p.pattern_id !== "intent-misalignment" && !!(p.description ?? p.title ?? p.reason)
|
|
17241
|
+
);
|
|
17242
|
+
return { refusal, intentFlag, advisory };
|
|
17243
|
+
}
|
|
17158
17244
|
function buildAgentContext(input) {
|
|
17159
17245
|
const lines = [];
|
|
17160
17246
|
if (input.reviewStatus === "not_reviewed") {
|
|
@@ -17240,7 +17326,7 @@ function channelSilence(input) {
|
|
|
17240
17326
|
// src/lib/cli-version.ts
|
|
17241
17327
|
function cliVersion() {
|
|
17242
17328
|
try {
|
|
17243
|
-
return true ? "0.29.4-experimental.
|
|
17329
|
+
return true ? "0.29.4-experimental.e818a8a" : "dev";
|
|
17244
17330
|
} catch {
|
|
17245
17331
|
return "dev";
|
|
17246
17332
|
}
|
|
@@ -17562,9 +17648,10 @@ async function scope(run) {
|
|
|
17562
17648
|
const { assistantResponse } = run;
|
|
17563
17649
|
const { files: allChanged, hasRecentCommitFiles } = getChangedFiles();
|
|
17564
17650
|
run.changedUniverse = allChanged;
|
|
17565
|
-
const
|
|
17566
|
-
const
|
|
17567
|
-
const
|
|
17651
|
+
const { kept: external } = partitionVerityOwned(allChanged);
|
|
17652
|
+
const analyzable = filterAnalyzable(external);
|
|
17653
|
+
const reviewable = filterReviewable(external);
|
|
17654
|
+
const securityFiles = filterSecurity(external);
|
|
17568
17655
|
const noFilesChanged = analyzable.length === 0 && reviewable.length === 0 && securityFiles.length === 0;
|
|
17569
17656
|
if (noFilesChanged && !assistantResponse) {
|
|
17570
17657
|
await passAndExit(run, "No analyzable files changed", "no-analyzable-files");
|
|
@@ -18057,26 +18144,50 @@ function narrowToRecent(files, sessionId) {
|
|
|
18057
18144
|
});
|
|
18058
18145
|
return recent.length > 0 ? recent : files;
|
|
18059
18146
|
}
|
|
18060
|
-
function
|
|
18061
|
-
|
|
18147
|
+
function readIteration(currentCommit, _contentHash) {
|
|
18148
|
+
return Math.max(1, readBlockState(currentCommit).attempts);
|
|
18149
|
+
}
|
|
18150
|
+
var NO_BLOCKS = { attempts: 0, blocks: 0, fingerprint: null };
|
|
18151
|
+
function readBlockState(currentCommit, opts) {
|
|
18152
|
+
if (opts?.newUserPrompt) return NO_BLOCKS;
|
|
18153
|
+
if (!(0, import_node_fs22.existsSync)(ITERATION_FILE)) return NO_BLOCKS;
|
|
18062
18154
|
try {
|
|
18063
18155
|
const stored = (0, import_node_fs22.readFileSync)(ITERATION_FILE, "utf-8").trim();
|
|
18064
|
-
const
|
|
18065
|
-
|
|
18066
|
-
|
|
18067
|
-
|
|
18068
|
-
|
|
18069
|
-
if (isNaN(iter)) return { iteration: 1, fingerprint: null };
|
|
18070
|
-
if (storedCommit !== currentCommit) return { iteration: 1, fingerprint: null };
|
|
18071
|
-
if (storedTimestamp > 0) {
|
|
18072
|
-
const elapsed = Math.floor(Date.now() / 1e3) - storedTimestamp;
|
|
18073
|
-
if (elapsed > 600) return { iteration: 1, fingerprint: null };
|
|
18074
|
-
}
|
|
18075
|
-
return { iteration: iter, fingerprint };
|
|
18156
|
+
const parsed = stored.startsWith("{") ? parseJsonState(stored) : parseLegacyState(stored);
|
|
18157
|
+
if (!parsed) return NO_BLOCKS;
|
|
18158
|
+
if (parsed.commit !== currentCommit) return NO_BLOCKS;
|
|
18159
|
+
if (parsed.ts > 0 && Math.floor(Date.now() / 1e3) - parsed.ts > 600) return NO_BLOCKS;
|
|
18160
|
+
return { attempts: parsed.attempts, blocks: parsed.blocks, fingerprint: parsed.fingerprint };
|
|
18076
18161
|
} catch {
|
|
18077
|
-
return
|
|
18162
|
+
return NO_BLOCKS;
|
|
18078
18163
|
}
|
|
18079
18164
|
}
|
|
18165
|
+
function parseJsonState(raw) {
|
|
18166
|
+
const o = JSON.parse(raw);
|
|
18167
|
+
const attempts = typeof o.attempts === "number" ? o.attempts : NaN;
|
|
18168
|
+
if (isNaN(attempts)) return null;
|
|
18169
|
+
return {
|
|
18170
|
+
attempts,
|
|
18171
|
+
blocks: typeof o.blocks === "number" ? o.blocks : attempts,
|
|
18172
|
+
fingerprint: typeof o.fingerprint === "string" && o.fingerprint ? o.fingerprint : null,
|
|
18173
|
+
commit: typeof o.commit === "string" ? o.commit : "",
|
|
18174
|
+
ts: typeof o.ts === "number" ? o.ts : 0
|
|
18175
|
+
};
|
|
18176
|
+
}
|
|
18177
|
+
function parseLegacyState(raw) {
|
|
18178
|
+
const parts = raw.split(":");
|
|
18179
|
+
const n = parseInt(parts[0], 10);
|
|
18180
|
+
if (isNaN(n)) return null;
|
|
18181
|
+
return {
|
|
18182
|
+
attempts: n,
|
|
18183
|
+
// The old file has no separate block count; the old counter is the closest
|
|
18184
|
+
// honest answer, and it errs toward releasing sooner rather than later.
|
|
18185
|
+
blocks: n,
|
|
18186
|
+
fingerprint: parts.slice(3).join(":") || null,
|
|
18187
|
+
commit: parts[1] ?? "",
|
|
18188
|
+
ts: parseInt(parts[2] ?? "0", 10)
|
|
18189
|
+
};
|
|
18190
|
+
}
|
|
18080
18191
|
function findingsFingerprint(findings) {
|
|
18081
18192
|
const keys = findings.map((f) => `${String(f.pattern_id ?? "?")}|${String(f.file ?? "?")}`).filter((k) => k !== "?|?");
|
|
18082
18193
|
return [...new Set(keys)].sort().join(",");
|
|
@@ -18086,11 +18197,22 @@ function isSameProblem(previous, current) {
|
|
|
18086
18197
|
const prev = new Set(previous.split(","));
|
|
18087
18198
|
return current.split(",").some((k) => prev.has(k));
|
|
18088
18199
|
}
|
|
18089
|
-
function
|
|
18200
|
+
function writeBlockState(commit, state) {
|
|
18090
18201
|
(0, import_node_fs22.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
18091
|
-
|
|
18092
|
-
|
|
18093
|
-
|
|
18202
|
+
(0, import_node_fs22.writeFileSync)(
|
|
18203
|
+
ITERATION_FILE,
|
|
18204
|
+
JSON.stringify({
|
|
18205
|
+
v: 2,
|
|
18206
|
+
attempts: state.attempts,
|
|
18207
|
+
blocks: state.blocks,
|
|
18208
|
+
commit,
|
|
18209
|
+
ts: Math.floor(Date.now() / 1e3),
|
|
18210
|
+
fingerprint: state.fingerprint ?? void 0
|
|
18211
|
+
})
|
|
18212
|
+
);
|
|
18213
|
+
}
|
|
18214
|
+
function resetBlockState(commit) {
|
|
18215
|
+
writeBlockState(commit, { attempts: 0, blocks: 0, fingerprint: null });
|
|
18094
18216
|
}
|
|
18095
18217
|
|
|
18096
18218
|
// src/lib/fold.ts
|
|
@@ -18282,8 +18404,10 @@ function fold(transcriptPath, opts = {}) {
|
|
|
18282
18404
|
subagentSkipped: 0,
|
|
18283
18405
|
compactions: 0,
|
|
18284
18406
|
complete: false
|
|
18285
|
-
}
|
|
18407
|
+
},
|
|
18408
|
+
planApproval: { approvals: 0, activeSinceLastPrompt: false }
|
|
18286
18409
|
};
|
|
18410
|
+
const flow = { seq: 0, lastPrompt: -1, lastApproval: -1, approvals: 0 };
|
|
18287
18411
|
const byPath = /* @__PURE__ */ new Map();
|
|
18288
18412
|
const commandStats = /* @__PURE__ */ new Map();
|
|
18289
18413
|
const pendingByToolUse = /* @__PURE__ */ new Map();
|
|
@@ -18309,8 +18433,12 @@ function fold(transcriptPath, opts = {}) {
|
|
|
18309
18433
|
if (type === "system" && record.subtype === "compact_boundary") {
|
|
18310
18434
|
result.coverage.compactions++;
|
|
18311
18435
|
}
|
|
18312
|
-
if (type === "user" && hasUserText(record))
|
|
18313
|
-
|
|
18436
|
+
if (type === "user" && hasUserText(record)) {
|
|
18437
|
+
result.coverage.userMessages++;
|
|
18438
|
+
if (owner === "agent") flow.lastPrompt = flow.seq;
|
|
18439
|
+
}
|
|
18440
|
+
if (owner === "agent") flow.seq++;
|
|
18441
|
+
collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, toolStats, pendingToolName, taskById, pendingTaskName, opts.repoRoot, result.coverage, flow);
|
|
18314
18442
|
}
|
|
18315
18443
|
};
|
|
18316
18444
|
try {
|
|
@@ -18382,6 +18510,10 @@ function fold(transcriptPath, opts = {}) {
|
|
|
18382
18510
|
if (!p || authoredPaths.has(p)) continue;
|
|
18383
18511
|
result.unobserved.push({ p, cause: classifyUnobserved(raw) });
|
|
18384
18512
|
}
|
|
18513
|
+
result.planApproval = {
|
|
18514
|
+
approvals: flow.approvals,
|
|
18515
|
+
activeSinceLastPrompt: flow.lastApproval >= 0 && flow.lastApproval > flow.lastPrompt
|
|
18516
|
+
};
|
|
18385
18517
|
return result;
|
|
18386
18518
|
}
|
|
18387
18519
|
function classifyUnobserved(path) {
|
|
@@ -18394,7 +18526,7 @@ function classifyUnobserved(path) {
|
|
|
18394
18526
|
if (/\.(png|jpg|jpeg|gif|pdf|zip|woff2?|ico|mp4)$/i.test(path)) return "binary";
|
|
18395
18527
|
return "no_edit_record";
|
|
18396
18528
|
}
|
|
18397
|
-
function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, toolStats, pendingToolName, taskById, pendingTaskName, repoRoot2, tally) {
|
|
18529
|
+
function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, toolStats, pendingToolName, taskById, pendingTaskName, repoRoot2, tally, flow) {
|
|
18398
18530
|
const message = record.message;
|
|
18399
18531
|
const content = message?.content ?? record.content;
|
|
18400
18532
|
const blocks = Array.isArray(content) ? content : content && typeof content === "object" ? [content] : [];
|
|
@@ -18478,6 +18610,13 @@ function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse
|
|
|
18478
18610
|
}
|
|
18479
18611
|
}
|
|
18480
18612
|
const toolName = id ? pendingToolName.get(id) : void 0;
|
|
18613
|
+
if (toolName === "ExitPlanMode" && flow && block.is_error !== true) {
|
|
18614
|
+
const body = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => typeof c.text === "string" ? c.text : "").join(" ") : "";
|
|
18615
|
+
if (/approved your plan/i.test(body)) {
|
|
18616
|
+
flow.lastApproval = flow.seq;
|
|
18617
|
+
flow.approvals += 1;
|
|
18618
|
+
}
|
|
18619
|
+
}
|
|
18481
18620
|
if (toolName) {
|
|
18482
18621
|
pendingToolName.delete(id);
|
|
18483
18622
|
const prevTool = toolStats.get(toolName);
|
|
@@ -18535,8 +18674,13 @@ function checkConservation(changedFiles, result, repoRoot2) {
|
|
|
18535
18674
|
// src/commands/analyze/phases/06-evidence.ts
|
|
18536
18675
|
async function evidence(run) {
|
|
18537
18676
|
const { opts } = run;
|
|
18538
|
-
const { actionSummary, allForReview, analyzable, assistantResponse, authorshipIsObservable, baseline, baselineSessionId, hasRecentCommitFiles, reviewable, securityFiles, sessionAuthoredCode, transcriptPath } = run;
|
|
18677
|
+
const { actionSummary, allForReview, analyzable, assistantResponse, authorshipIsObservable, baseline, baselineSessionId, hasRecentCommitFiles, reviewable, securityFiles, sessionAuthoredCode, transcriptPath, turnAuthoredCode } = run;
|
|
18539
18678
|
let { analysisMode, earlyFold } = run;
|
|
18679
|
+
const recordFlip = (stage) => {
|
|
18680
|
+
if (run.modeDecision) run.modeDecision = { ...run.modeDecision, resolved: "plan", flip: stage };
|
|
18681
|
+
logEvent("mode_flipped", { stage, to: "plan" });
|
|
18682
|
+
};
|
|
18683
|
+
const planWorthy = !!assistantResponse && !turnAuthoredCode;
|
|
18540
18684
|
let staticResults = {
|
|
18541
18685
|
tool: "@codacy/analysis-cli",
|
|
18542
18686
|
findings: [],
|
|
@@ -18556,8 +18700,9 @@ async function evidence(run) {
|
|
|
18556
18700
|
const debounceSeconds = parseInt(opts.debounce, 10);
|
|
18557
18701
|
const debounceSkip = checkDebounce(debounceSeconds, baselineSessionId);
|
|
18558
18702
|
if (debounceSkip) {
|
|
18559
|
-
if (
|
|
18703
|
+
if (planWorthy) {
|
|
18560
18704
|
analysisMode = "plan";
|
|
18705
|
+
recordFlip("debounce");
|
|
18561
18706
|
} else {
|
|
18562
18707
|
await passAndExit(run, debounceSkip, "debounce");
|
|
18563
18708
|
}
|
|
@@ -18566,8 +18711,9 @@ async function evidence(run) {
|
|
|
18566
18711
|
const allCheckable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable, ...securityFiles]));
|
|
18567
18712
|
const mtimeSkip = checkMtime(allCheckable, hasRecentCommitFiles, baselineSessionId);
|
|
18568
18713
|
if (mtimeSkip) {
|
|
18569
|
-
if (
|
|
18714
|
+
if (planWorthy) {
|
|
18570
18715
|
analysisMode = "plan";
|
|
18716
|
+
recordFlip("mtime");
|
|
18571
18717
|
} else {
|
|
18572
18718
|
await passAndExit(run, mtimeSkip, "no-delta-since-last-review");
|
|
18573
18719
|
}
|
|
@@ -18578,8 +18724,9 @@ async function evidence(run) {
|
|
|
18578
18724
|
const allCheckable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable, ...securityFiles]));
|
|
18579
18725
|
const hashResult = checkContentHash(allCheckable, baselineSessionId);
|
|
18580
18726
|
if (hashResult.skip) {
|
|
18581
|
-
if (
|
|
18727
|
+
if (planWorthy) {
|
|
18582
18728
|
analysisMode = "plan";
|
|
18729
|
+
recordFlip("content-hash");
|
|
18583
18730
|
} else {
|
|
18584
18731
|
await passAndExit(run, hashResult.skip, "no-delta-since-last-review");
|
|
18585
18732
|
}
|
|
@@ -18641,8 +18788,9 @@ async function evidence(run) {
|
|
|
18641
18788
|
maxTotalBytes: parseInt(opts.maxTotalSize, 10)
|
|
18642
18789
|
});
|
|
18643
18790
|
if (codeDelta.files.length === 0 && staticResults.findings.length === 0) {
|
|
18644
|
-
if (
|
|
18791
|
+
if (planWorthy) {
|
|
18645
18792
|
analysisMode = "plan";
|
|
18793
|
+
recordFlip("empty-after-scoping");
|
|
18646
18794
|
} else {
|
|
18647
18795
|
await passAndExit(
|
|
18648
18796
|
run,
|
|
@@ -18662,13 +18810,13 @@ async function evidence(run) {
|
|
|
18662
18810
|
snapshotResult = generateSnapshotDiffs(codeDelta.files);
|
|
18663
18811
|
}
|
|
18664
18812
|
currentCommit = getCurrentCommit();
|
|
18665
|
-
iteration =
|
|
18813
|
+
iteration = readIteration(currentCommit);
|
|
18666
18814
|
}
|
|
18667
18815
|
}
|
|
18668
18816
|
if (analysisMode === "plan") {
|
|
18669
18817
|
recordAnalysisStart();
|
|
18670
18818
|
currentCommit = getCurrentCommit();
|
|
18671
|
-
iteration =
|
|
18819
|
+
iteration = readIteration(currentCommit);
|
|
18672
18820
|
}
|
|
18673
18821
|
Object.assign(run, { analysisMode, codeDelta, contentHash, currentCommit, earlyFold, iteration, snapshotResult, staticResults });
|
|
18674
18822
|
}
|
|
@@ -18764,7 +18912,8 @@ function gatherContextFiles(contextPaths, deltaFiles) {
|
|
|
18764
18912
|
// src/commands/analyze/phases/07-context-files.ts
|
|
18765
18913
|
async function contextFiles(run) {
|
|
18766
18914
|
const { codeDelta, contextFilePaths } = run;
|
|
18767
|
-
const
|
|
18915
|
+
const { kept: externalContext } = partitionVerityOwned(contextFilePaths ?? []);
|
|
18916
|
+
const contextFiles2 = gatherContextFiles(externalContext, codeDelta.files);
|
|
18768
18917
|
for (const f of codeDelta.files) {
|
|
18769
18918
|
f.role = "delta";
|
|
18770
18919
|
}
|
|
@@ -19334,6 +19483,54 @@ async function workingMemory(run) {
|
|
|
19334
19483
|
Object.assign(run, { incrementReport, memory, memorySession, reachability });
|
|
19335
19484
|
}
|
|
19336
19485
|
|
|
19486
|
+
// src/lib/note-budget.ts
|
|
19487
|
+
var import_node_fs28 = require("node:fs");
|
|
19488
|
+
var ADVISORY_BUDGET = { PASS: 1, WARN: 2 };
|
|
19489
|
+
var EPISODE_STALE_SECONDS = 30 * 60;
|
|
19490
|
+
var FRESH = { delivered: 0, tasksCompleted: 0, ts: 0 };
|
|
19491
|
+
function resolveEpisode(prev, signals) {
|
|
19492
|
+
if (!prev) return { ...FRESH, tasksCompleted: signals.tasksCompleted, ts: signals.now };
|
|
19493
|
+
if (signals.humanSpoke) return { ...FRESH, tasksCompleted: signals.tasksCompleted, ts: signals.now };
|
|
19494
|
+
if (signals.rawFail) return { ...FRESH, tasksCompleted: signals.tasksCompleted, ts: signals.now };
|
|
19495
|
+
if (prev.tasksCompleted !== signals.tasksCompleted) {
|
|
19496
|
+
return { ...FRESH, tasksCompleted: signals.tasksCompleted, ts: signals.now };
|
|
19497
|
+
}
|
|
19498
|
+
if (prev.ts > 0 && signals.now - prev.ts > EPISODE_STALE_SECONDS) {
|
|
19499
|
+
return { ...FRESH, tasksCompleted: signals.tasksCompleted, ts: signals.now };
|
|
19500
|
+
}
|
|
19501
|
+
return prev;
|
|
19502
|
+
}
|
|
19503
|
+
function advisoryBudgetSpent(episode, rawDecision) {
|
|
19504
|
+
const budget = ADVISORY_BUDGET[rawDecision] ?? ADVISORY_BUDGET.WARN;
|
|
19505
|
+
return episode.delivered >= budget;
|
|
19506
|
+
}
|
|
19507
|
+
function readAdvisoryEpisode(sessionId) {
|
|
19508
|
+
const file = scopedFile(ADVISORY_EPISODE_FILE, sessionId);
|
|
19509
|
+
if (!(0, import_node_fs28.existsSync)(file)) return null;
|
|
19510
|
+
try {
|
|
19511
|
+
const o = JSON.parse((0, import_node_fs28.readFileSync)(file, "utf-8")) ?? {};
|
|
19512
|
+
const delivered = typeof o.delivered === "number" ? o.delivered : NaN;
|
|
19513
|
+
if (isNaN(delivered)) return null;
|
|
19514
|
+
return {
|
|
19515
|
+
delivered,
|
|
19516
|
+
tasksCompleted: typeof o.tasksCompleted === "number" ? o.tasksCompleted : 0,
|
|
19517
|
+
ts: typeof o.ts === "number" ? o.ts : 0
|
|
19518
|
+
};
|
|
19519
|
+
} catch {
|
|
19520
|
+
return null;
|
|
19521
|
+
}
|
|
19522
|
+
}
|
|
19523
|
+
function writeAdvisoryEpisode(episode, sessionId) {
|
|
19524
|
+
try {
|
|
19525
|
+
(0, import_node_fs28.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
19526
|
+
(0, import_node_fs28.writeFileSync)(
|
|
19527
|
+
scopedFile(ADVISORY_EPISODE_FILE, sessionId),
|
|
19528
|
+
JSON.stringify({ v: 1, ...episode })
|
|
19529
|
+
);
|
|
19530
|
+
} catch {
|
|
19531
|
+
}
|
|
19532
|
+
}
|
|
19533
|
+
|
|
19337
19534
|
// src/lib/run-mode.ts
|
|
19338
19535
|
function parseAutonomousEnv(raw) {
|
|
19339
19536
|
if (raw === void 0) return void 0;
|
|
@@ -19427,7 +19624,13 @@ async function buildRequest(run) {
|
|
|
19427
19624
|
excluded_by_reason: excludedByReason,
|
|
19428
19625
|
// was the transcript itself truncated? The 256 KB window means "this turn"
|
|
19429
19626
|
// can quietly mean "the last 256 KB of it".
|
|
19430
|
-
transcript_windowed: actionSummary?.transcript_windowed ?? null
|
|
19627
|
+
transcript_windowed: actionSummary?.transcript_windowed ?? null,
|
|
19628
|
+
// The advisory budget's fleet counter-metric (note-budget.ts): deliveries in
|
|
19629
|
+
// the episode as of the PREVIOUS turn — this runs before phase 13 updates
|
|
19630
|
+
// the state, so the number is one turn lagged by construction. The
|
|
19631
|
+
// degenerate win for the budget is a dead channel that looks like clean
|
|
19632
|
+
// code; this is what makes "did delivery rate collapse" a query.
|
|
19633
|
+
advisory_delivered_prior: readAdvisoryEpisode(run.baselineSessionId)?.delivered ?? 0
|
|
19431
19634
|
};
|
|
19432
19635
|
const requestBody = {
|
|
19433
19636
|
coverage_telemetry: coverageTelemetry,
|
|
@@ -19566,7 +19769,8 @@ async function buildRequest(run) {
|
|
|
19566
19769
|
}
|
|
19567
19770
|
const noHumanPrompt = (conversation?.prompts?.length ?? 0) === 0;
|
|
19568
19771
|
const w4Task = noHumanPrompt && isExplicitlyAutonomous() ? resolveTaskContext() : null;
|
|
19569
|
-
const
|
|
19772
|
+
const planApprovalActive = foldResult?.planApproval?.activeSinceLastPrompt === true;
|
|
19773
|
+
const hasIntent = (conversation?.prompts?.length ?? 0) > 0 || specs.length > 0 || plans.length > 0 || !!assistantResponse || !!w4Task || planApprovalActive;
|
|
19570
19774
|
if (hasIntent) {
|
|
19571
19775
|
const intentContext = {};
|
|
19572
19776
|
if (conversation && conversation.prompts.length > 0) {
|
|
@@ -19598,6 +19802,10 @@ async function buildRequest(run) {
|
|
|
19598
19802
|
intentContext.user_prompt = w4Task.goal;
|
|
19599
19803
|
logEvent("w4_issue_anchor", { issue: w4Task.number, via: w4Task.via });
|
|
19600
19804
|
}
|
|
19805
|
+
if (planApprovalActive) {
|
|
19806
|
+
intentContext.plan_approved = true;
|
|
19807
|
+
logEvent("plan_approval_carried", { approvals: foldResult.planApproval.approvals });
|
|
19808
|
+
}
|
|
19601
19809
|
if (assistantResponse) {
|
|
19602
19810
|
const cap = analysisMode === "plan" ? MAX_ASSISTANT_RESPONSE_CHARS_PLAN : MAX_ASSISTANT_RESPONSE_CHARS_DEFAULT;
|
|
19603
19811
|
intentContext.assistant_response = assistantResponse.length > cap ? assistantResponse.slice(0, cap) : assistantResponse;
|
|
@@ -19617,14 +19825,14 @@ async function buildRequest(run) {
|
|
|
19617
19825
|
}
|
|
19618
19826
|
|
|
19619
19827
|
// src/lib/offline.ts
|
|
19620
|
-
var
|
|
19828
|
+
var import_node_fs29 = require("node:fs");
|
|
19621
19829
|
var import_node_crypto11 = require("node:crypto");
|
|
19622
19830
|
function cacheRequest(body) {
|
|
19623
19831
|
try {
|
|
19624
|
-
(0,
|
|
19832
|
+
(0, import_node_fs29.mkdirSync)(CACHE_DIR, { recursive: true });
|
|
19625
19833
|
const suffix = (0, import_node_crypto11.randomBytes)(4).toString("hex");
|
|
19626
19834
|
const filename = `pending-${Math.floor(Date.now() / 1e3)}-${suffix}.json`;
|
|
19627
|
-
(0,
|
|
19835
|
+
(0, import_node_fs29.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(redactRequest(body)));
|
|
19628
19836
|
} catch {
|
|
19629
19837
|
}
|
|
19630
19838
|
}
|
|
@@ -19743,10 +19951,10 @@ async function transmit(run) {
|
|
|
19743
19951
|
}
|
|
19744
19952
|
|
|
19745
19953
|
// src/commands/analyze/phases/13-reconcile.ts
|
|
19746
|
-
var
|
|
19954
|
+
var import_node_fs30 = require("node:fs");
|
|
19747
19955
|
var import_node_path23 = require("node:path");
|
|
19748
19956
|
async function reconcile(run) {
|
|
19749
|
-
const { actionSummary, allChanged, analyzable, baseline, codeDelta, contentHash, conversation, decision, memory, memorySession, response, reviewable, securityFiles, turnId } = run;
|
|
19957
|
+
const { actionSummary, allChanged, analyzable, baseline, baselineSessionId, codeDelta, contentHash, conversation, decision, foldResult, memory, memorySession, response, reviewable, securityFiles, turnId } = run;
|
|
19750
19958
|
const sentPaths = codeDelta.files.map((f) => f.path);
|
|
19751
19959
|
let openElsewhere = [];
|
|
19752
19960
|
if (memorySession) {
|
|
@@ -19754,7 +19962,7 @@ async function reconcile(run) {
|
|
|
19754
19962
|
const st = foldDossier(memorySession.d);
|
|
19755
19963
|
openElsewhere = openBlockingElsewhere(st.statements, sentPaths, (file, line) => {
|
|
19756
19964
|
try {
|
|
19757
|
-
const src = (0,
|
|
19965
|
+
const src = (0, import_node_fs30.readFileSync)((0, import_node_path23.join)(repoRoot(), file), "utf8").split("\n");
|
|
19758
19966
|
const at = src[line - 1];
|
|
19759
19967
|
return at === void 0 ? null : lineSha(at);
|
|
19760
19968
|
} catch {
|
|
@@ -19764,6 +19972,7 @@ async function reconcile(run) {
|
|
|
19764
19972
|
} catch {
|
|
19765
19973
|
}
|
|
19766
19974
|
}
|
|
19975
|
+
const { kept: externalChanged, owned: verityOwned } = partitionVerityOwned(allChanged);
|
|
19767
19976
|
const reviewCoverage = {
|
|
19768
19977
|
reviewed: sentPaths,
|
|
19769
19978
|
// Declared drops from the stages that DO report themselves today. The other
|
|
@@ -19805,11 +20014,20 @@ async function reconcile(run) {
|
|
|
19805
20014
|
stage: "baseline-scoping",
|
|
19806
20015
|
kind: "policy"
|
|
19807
20016
|
})),
|
|
20017
|
+
// ⚠ VERITY'S OWN FILES, named as such — not laundered into the
|
|
20018
|
+
// extension bucket below, where "we do not review our own installer's
|
|
20019
|
+
// dirt" would read as "a changed README". See self-scope.ts.
|
|
20020
|
+
...verityOwned.map((path) => ({
|
|
20021
|
+
path,
|
|
20022
|
+
reason: "verity-owned",
|
|
20023
|
+
stage: "self-scope",
|
|
20024
|
+
kind: "policy"
|
|
20025
|
+
})),
|
|
19808
20026
|
// The extension allowlist. POLICY: a changed README was never going to be
|
|
19809
20027
|
// reviewed, and calling that a coverage gap would downgrade nearly every
|
|
19810
20028
|
// PASS to WARN until WARN meant nothing. Recorded so the ledger balances and
|
|
19811
20029
|
// so "what did Verity ignore entirely" is answerable.
|
|
19812
|
-
...
|
|
20030
|
+
...externalChanged.filter((p) => !analyzable.includes(p) && !reviewable.includes(p) && !securityFiles.includes(p)).map((path) => ({
|
|
19813
20031
|
path,
|
|
19814
20032
|
reason: "not-a-reviewed-file-type",
|
|
19815
20033
|
stage: "extension-allowlist",
|
|
@@ -19846,6 +20064,29 @@ async function reconcile(run) {
|
|
|
19846
20064
|
decision
|
|
19847
20065
|
});
|
|
19848
20066
|
}
|
|
20067
|
+
const episodeSignals = {
|
|
20068
|
+
humanSpoke: (conversation?.prompts?.length ?? 0) > 0,
|
|
20069
|
+
rawFail: decision === "FAIL",
|
|
20070
|
+
tasksCompleted: (foldResult?.tasks ?? []).filter((t) => t.status === "completed").length,
|
|
20071
|
+
now: Math.floor(Date.now() / 1e3)
|
|
20072
|
+
};
|
|
20073
|
+
let episode = resolveEpisode(readAdvisoryEpisode(baselineSessionId), episodeSignals);
|
|
20074
|
+
const contentClass = classifyChannelContent(channelInputFrom(response));
|
|
20075
|
+
const wouldCarryAdvisory = contentClass.advisory || openElsewhere.length > 0;
|
|
20076
|
+
if (decision !== "FAIL" && !silenced && wouldCarryAdvisory && !contentClass.refusal && !contentClass.intentFlag && advisoryBudgetSpent(episode, decision)) {
|
|
20077
|
+
silenced = "note-budget";
|
|
20078
|
+
logEvent("channel_silenced", {
|
|
20079
|
+
reason: silenced,
|
|
20080
|
+
run_id: response.run_id ?? turnId,
|
|
20081
|
+
decision,
|
|
20082
|
+
episode_delivered: episode.delivered
|
|
20083
|
+
});
|
|
20084
|
+
}
|
|
20085
|
+
const deliveringAdvisory = decision !== "FAIL" && !silenced && wouldCarryAdvisory;
|
|
20086
|
+
writeAdvisoryEpisode(
|
|
20087
|
+
{ ...episode, delivered: episode.delivered + (deliveringAdvisory ? 1 : 0), ts: episodeSignals.now },
|
|
20088
|
+
baselineSessionId
|
|
20089
|
+
);
|
|
19849
20090
|
let intentRepeatCount = 0;
|
|
19850
20091
|
const priorPendingFingerprints = memorySession ? (() => {
|
|
19851
20092
|
try {
|
|
@@ -19861,6 +20102,11 @@ async function reconcile(run) {
|
|
|
19861
20102
|
decision,
|
|
19862
20103
|
branch: getCurrentBranch(),
|
|
19863
20104
|
watermarkSha: watermarkIsPartial ? null : watermarkHash,
|
|
20105
|
+
// The byte witness — the same "only honest definition of reviewed" the
|
|
20106
|
+
// coverage column uses. A finding on a path outside this set records no
|
|
20107
|
+
// statement (plan-mode prose anchored to unsent files must not become
|
|
20108
|
+
// "STILL OPEN … the tree is not clean").
|
|
20109
|
+
sentPaths,
|
|
19864
20110
|
findings: response.findings?.map((f) => ({
|
|
19865
20111
|
file: f.file,
|
|
19866
20112
|
line: f.line,
|
|
@@ -19927,6 +20173,39 @@ ${YELLOW2}${note}${NC2}
|
|
|
19927
20173
|
return exit(0);
|
|
19928
20174
|
}
|
|
19929
20175
|
|
|
20176
|
+
// src/lib/may-block.ts
|
|
20177
|
+
var HARD_BLOCK_CEILING = 5;
|
|
20178
|
+
function mayBlock(input) {
|
|
20179
|
+
const ceiling = input.ceiling ?? HARD_BLOCK_CEILING;
|
|
20180
|
+
if (input.reviewedFileCount === 0 && input.staticFindingCount === 0) {
|
|
20181
|
+
return { block: false, release: "no-code-reviewed" };
|
|
20182
|
+
}
|
|
20183
|
+
if (input.cycleCutFired) {
|
|
20184
|
+
return { block: false, release: "nothing-moved" };
|
|
20185
|
+
}
|
|
20186
|
+
if (input.attempts > input.maxIterations) {
|
|
20187
|
+
return { block: false, release: "same-problem-cap" };
|
|
20188
|
+
}
|
|
20189
|
+
if (input.blocks > ceiling) {
|
|
20190
|
+
return { block: false, release: "block-ceiling" };
|
|
20191
|
+
}
|
|
20192
|
+
return { block: true, release: null };
|
|
20193
|
+
}
|
|
20194
|
+
function describeRelease(release, input) {
|
|
20195
|
+
const ceiling = input.ceiling ?? HARD_BLOCK_CEILING;
|
|
20196
|
+
const open = input.findingCount > 0 ? `${input.findingCount} finding(s) remain OPEN and were NOT fixed. Human review required before deploying.` : "Human review required before deploying.";
|
|
20197
|
+
switch (release) {
|
|
20198
|
+
case "no-code-reviewed":
|
|
20199
|
+
return `Verity: WARN \u2014 NOT BLOCKING: no code was reviewed on this turn, so there is nothing here to fix. Reported as advice instead. ${open}`;
|
|
20200
|
+
case "nothing-moved":
|
|
20201
|
+
return `Verity: WARN \u2014 NOT BLOCKING: nothing has changed since the last verdict, so re-raising it cannot move anything forward. ${open}`;
|
|
20202
|
+
case "same-problem-cap":
|
|
20203
|
+
return `Verity: WARN \u2014 self-healing limit (${input.maxIterations}) reached on the same finding. NO LONGER BLOCKING, but ${open}`;
|
|
20204
|
+
case "block-ceiling":
|
|
20205
|
+
return `Verity: WARN \u2014 ${ceiling} consecutive blocking verdicts reached; releasing the block so this cannot loop. ${open}`;
|
|
20206
|
+
}
|
|
20207
|
+
}
|
|
20208
|
+
|
|
19930
20209
|
// src/lib/remediation-guard.ts
|
|
19931
20210
|
var TOOL_CONFIG_PATTERNS = [
|
|
19932
20211
|
/(^|\/)\.codacy\//,
|
|
@@ -19969,19 +20248,7 @@ function screenRemediation(fix, findingFile) {
|
|
|
19969
20248
|
|
|
19970
20249
|
// src/commands/analyze/phases/14-render.ts
|
|
19971
20250
|
function agentContextFor(response, intentRepeat = 0, priorPendingFingerprints = []) {
|
|
19972
|
-
|
|
19973
|
-
const intent = response.intent_alignment ?? {};
|
|
19974
|
-
return buildAgentContext({
|
|
19975
|
-
intentRepeat,
|
|
19976
|
-
priorPendingFingerprints,
|
|
19977
|
-
gateDecision: String(response.gate_decision ?? ""),
|
|
19978
|
-
findings: response.findings ?? [],
|
|
19979
|
-
pendingItems: response.pending_items ?? [],
|
|
19980
|
-
reviewStatus: metadata.review_status,
|
|
19981
|
-
coverage: metadata.coverage,
|
|
19982
|
-
intentVerdict: intent.verdict,
|
|
19983
|
-
intentGaps: intent.gaps
|
|
19984
|
-
});
|
|
20251
|
+
return buildAgentContext(channelInputFrom(response, intentRepeat, priorPendingFingerprints));
|
|
19985
20252
|
}
|
|
19986
20253
|
async function render(run) {
|
|
19987
20254
|
const { opts, globals } = run;
|
|
@@ -20075,38 +20342,63 @@ async function render(run) {
|
|
|
20075
20342
|
reverify_by: response.reverify_by
|
|
20076
20343
|
});
|
|
20077
20344
|
const grantNudge = grantWarning ? ` \u26A0\uFE0F ${grantWarning}` : "";
|
|
20078
|
-
let
|
|
20345
|
+
let release = null;
|
|
20079
20346
|
let effectiveDecision = decision;
|
|
20080
20347
|
if (decision === "FAIL") {
|
|
20081
|
-
const
|
|
20348
|
+
const findings = response.findings ?? [];
|
|
20349
|
+
const blocking = findings.filter((f) => {
|
|
20082
20350
|
const sev = String(f.severity ?? "").toLowerCase();
|
|
20083
20351
|
return sev === "critical" || sev === "high";
|
|
20084
20352
|
});
|
|
20085
20353
|
const fingerprint = findingsFingerprint(blocking);
|
|
20086
|
-
const prior =
|
|
20354
|
+
const prior = readBlockState(currentCommit, {
|
|
20355
|
+
newUserPrompt: (conversation?.prompts?.length ?? 0) > 0
|
|
20356
|
+
});
|
|
20087
20357
|
const sameProblem = isSameProblem(prior.fingerprint, fingerprint);
|
|
20088
|
-
const nextIteration = sameProblem ? prior.iteration + 1 : 1;
|
|
20089
20358
|
const maxIterations = parseInt(opts.maxIterations, 10);
|
|
20090
|
-
|
|
20091
|
-
|
|
20092
|
-
|
|
20093
|
-
|
|
20359
|
+
const attempts = sameProblem ? prior.attempts + 1 : 1;
|
|
20360
|
+
const blocks = prior.blocks + 1;
|
|
20361
|
+
const decisionNow = mayBlock({
|
|
20362
|
+
reviewedFileCount: codeDelta.files.length,
|
|
20363
|
+
staticFindingCount: run.staticResults?.findings?.length ?? 0,
|
|
20364
|
+
cycleCutFired: silenced !== null,
|
|
20365
|
+
attempts,
|
|
20366
|
+
blocks,
|
|
20367
|
+
maxIterations
|
|
20368
|
+
});
|
|
20369
|
+
if (decisionNow.block) {
|
|
20370
|
+
writeBlockState(currentCommit, { attempts, blocks, fingerprint });
|
|
20371
|
+
iteration = attempts;
|
|
20372
|
+
} else {
|
|
20373
|
+
release = decisionNow.release;
|
|
20094
20374
|
effectiveDecision = "WARN";
|
|
20095
|
-
logEvent("
|
|
20375
|
+
logEvent("block_released", {
|
|
20376
|
+
reason: release,
|
|
20377
|
+
attempts,
|
|
20378
|
+
blocks,
|
|
20379
|
+
reviewed_files: codeDelta.files.length,
|
|
20380
|
+
cycle_cut: silenced,
|
|
20381
|
+
fingerprint
|
|
20382
|
+
});
|
|
20096
20383
|
}
|
|
20097
20384
|
}
|
|
20098
|
-
if (
|
|
20385
|
+
if (release) {
|
|
20099
20386
|
const findings = response.findings ?? [];
|
|
20100
20387
|
const lines = findings.slice(0, 5).map((f) => ` [${String(f.severity ?? "?").toUpperCase()}] ${String(f.title ?? f.message ?? "")} (${String(f.file ?? "?")}:${String(f.line ?? "?")})`);
|
|
20388
|
+
const summary = describeRelease(release, {
|
|
20389
|
+
findingCount: findings.length,
|
|
20390
|
+
maxIterations: parseInt(opts.maxIterations, 10)
|
|
20391
|
+
});
|
|
20101
20392
|
emitVerdict({
|
|
20102
20393
|
proposed: "WARN",
|
|
20103
20394
|
changed: run.changedUniverse,
|
|
20104
20395
|
coverage: reviewCoverage,
|
|
20105
|
-
userSummary:
|
|
20106
|
-
${lines.join("\n")}
|
|
20396
|
+
userSummary: lines.length > 0 ? `${summary}
|
|
20397
|
+
${lines.join("\n")}` : summary,
|
|
20107
20398
|
agentContext: null,
|
|
20108
20399
|
silenced: true
|
|
20109
20400
|
});
|
|
20401
|
+
return;
|
|
20110
20402
|
}
|
|
20111
20403
|
switch (effectiveDecision) {
|
|
20112
20404
|
case "FAIL": {
|
|
@@ -20205,7 +20497,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
20205
20497
|
break;
|
|
20206
20498
|
}
|
|
20207
20499
|
case "PASS": {
|
|
20208
|
-
|
|
20500
|
+
resetBlockState(currentCommit);
|
|
20209
20501
|
if (watermarkHash) recordPassHash(watermarkHash, baselineSessionId);
|
|
20210
20502
|
if (!watermarkIsPartial && currentCommit && currentCommit !== "no-git") writeBaselineSha(currentCommit);
|
|
20211
20503
|
let userSummary = response.user_summary ?? "Verity: PASS";
|
|
@@ -20226,6 +20518,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
20226
20518
|
break;
|
|
20227
20519
|
}
|
|
20228
20520
|
case "WARN": {
|
|
20521
|
+
if (decision !== "FAIL") resetBlockState(currentCommit);
|
|
20229
20522
|
if (watermarkHash) recordPassHash(watermarkHash, baselineSessionId);
|
|
20230
20523
|
if (!watermarkIsPartial && currentCommit && currentCommit !== "no-git") writeBaselineSha(currentCommit);
|
|
20231
20524
|
let userSummary = response.user_summary ?? "Verity: WARN";
|
|
@@ -20324,7 +20617,7 @@ async function runAnalyze(opts, globals) {
|
|
|
20324
20617
|
}
|
|
20325
20618
|
|
|
20326
20619
|
// src/commands/baseline.ts
|
|
20327
|
-
var
|
|
20620
|
+
var import_node_fs31 = require("node:fs");
|
|
20328
20621
|
function registerBaselineCommands(program2) {
|
|
20329
20622
|
const baseline = program2.command("baseline").description("Manage the task-start working-tree baseline");
|
|
20330
20623
|
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) => {
|
|
@@ -20333,7 +20626,7 @@ function registerBaselineCommands(program2) {
|
|
|
20333
20626
|
process.chdir(repoRoot());
|
|
20334
20627
|
} catch {
|
|
20335
20628
|
}
|
|
20336
|
-
if (!(0,
|
|
20629
|
+
if (!(0, import_node_fs31.existsSync)(VERITY_DIR)) {
|
|
20337
20630
|
process.exit(0);
|
|
20338
20631
|
}
|
|
20339
20632
|
let sessionId = opts.sessionId;
|
|
@@ -20373,7 +20666,7 @@ async function readStdin() {
|
|
|
20373
20666
|
}
|
|
20374
20667
|
|
|
20375
20668
|
// src/commands/review.ts
|
|
20376
|
-
var
|
|
20669
|
+
var import_node_fs32 = require("node:fs");
|
|
20377
20670
|
function registerReviewCommand(program2) {
|
|
20378
20671
|
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) => {
|
|
20379
20672
|
const globals = program2.opts();
|
|
@@ -20392,7 +20685,7 @@ async function runReview(opts, globals) {
|
|
|
20392
20685
|
const securityFiles = filterSecurity(allFiles);
|
|
20393
20686
|
let staticResults;
|
|
20394
20687
|
if (isCodacyAvailable()) {
|
|
20395
|
-
const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0,
|
|
20688
|
+
const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs32.existsSync)(f) || resolveFile(f) !== null);
|
|
20396
20689
|
staticResults = runCodacyAnalysis(scannable);
|
|
20397
20690
|
} else {
|
|
20398
20691
|
staticResults = {
|
|
@@ -20418,10 +20711,10 @@ async function runReview(opts, globals) {
|
|
|
20418
20711
|
const specPaths = opts.specs.split(",").map((f) => f.trim()).filter(Boolean);
|
|
20419
20712
|
specs = [];
|
|
20420
20713
|
for (const p of specPaths) {
|
|
20421
|
-
if (!(0,
|
|
20714
|
+
if (!(0, import_node_fs32.existsSync)(p)) continue;
|
|
20422
20715
|
try {
|
|
20423
|
-
const { readFileSync:
|
|
20424
|
-
const content =
|
|
20716
|
+
const { readFileSync: readFileSync19 } = await import("node:fs");
|
|
20717
|
+
const content = readFileSync19(p, "utf-8");
|
|
20425
20718
|
specs.push({ path: p, content: content.slice(0, 10240) });
|
|
20426
20719
|
} catch {
|
|
20427
20720
|
}
|
|
@@ -20478,7 +20771,7 @@ async function runReview(opts, globals) {
|
|
|
20478
20771
|
}
|
|
20479
20772
|
|
|
20480
20773
|
// src/commands/guard.ts
|
|
20481
|
-
var
|
|
20774
|
+
var import_node_fs33 = require("node:fs");
|
|
20482
20775
|
var import_node_path24 = require("node:path");
|
|
20483
20776
|
var GUARD_BLOCK_CAP = 2;
|
|
20484
20777
|
var GUARD_ITER_FILE = (0, import_node_path24.join)(VERITY_DIR, ".guard-iteration");
|
|
@@ -20545,7 +20838,7 @@ function classifyCommand2(command, on) {
|
|
|
20545
20838
|
}
|
|
20546
20839
|
function readIterMap() {
|
|
20547
20840
|
try {
|
|
20548
|
-
const raw = JSON.parse((0,
|
|
20841
|
+
const raw = JSON.parse((0, import_node_fs33.readFileSync)(GUARD_ITER_FILE, "utf-8"));
|
|
20549
20842
|
if (raw && typeof raw === "object") {
|
|
20550
20843
|
if (typeof raw.moment === "string" && typeof raw.count === "number") {
|
|
20551
20844
|
return { [raw.moment]: raw.count };
|
|
@@ -20565,10 +20858,10 @@ function readIter(moment) {
|
|
|
20565
20858
|
}
|
|
20566
20859
|
function writeIter(moment, count) {
|
|
20567
20860
|
try {
|
|
20568
|
-
(0,
|
|
20861
|
+
(0, import_node_fs33.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
20569
20862
|
const map = readIterMap();
|
|
20570
20863
|
map[moment] = count;
|
|
20571
|
-
(0,
|
|
20864
|
+
(0, import_node_fs33.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
|
|
20572
20865
|
} catch {
|
|
20573
20866
|
}
|
|
20574
20867
|
}
|
|
@@ -20578,10 +20871,10 @@ function resetIter(moment) {
|
|
|
20578
20871
|
if (!(moment in map)) return;
|
|
20579
20872
|
delete map[moment];
|
|
20580
20873
|
if (Object.keys(map).length === 0) {
|
|
20581
|
-
if ((0,
|
|
20874
|
+
if ((0, import_node_fs33.existsSync)(GUARD_ITER_FILE)) (0, import_node_fs33.unlinkSync)(GUARD_ITER_FILE);
|
|
20582
20875
|
} else {
|
|
20583
|
-
(0,
|
|
20584
|
-
(0,
|
|
20876
|
+
(0, import_node_fs33.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
20877
|
+
(0, import_node_fs33.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
|
|
20585
20878
|
}
|
|
20586
20879
|
} catch {
|
|
20587
20880
|
}
|
|
@@ -20645,7 +20938,7 @@ function buildGuardRequest(moment, files, iter, sessionId, command) {
|
|
|
20645
20938
|
const securityFiles = filterSecurity(files);
|
|
20646
20939
|
let staticResults;
|
|
20647
20940
|
if (isCodacyAvailable()) {
|
|
20648
|
-
const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0,
|
|
20941
|
+
const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0, import_node_fs33.existsSync)(f) ? f : resolveFile(f)).filter((f) => f !== null);
|
|
20649
20942
|
staticResults = runCodacyAnalysis(scannable);
|
|
20650
20943
|
} else {
|
|
20651
20944
|
staticResults = { tool: "@codacy/analysis-cli", findings: [], summary: { total_findings: 0, by_severity: {}, tools_run: [] } };
|
|
@@ -20690,7 +20983,7 @@ function emitAllowNotice(userMsg, agentMsg) {
|
|
|
20690
20983
|
async function runGuard(opts, globals) {
|
|
20691
20984
|
const on = opts.on.split(",").map((s) => s.trim()).filter((s) => s === "commit" || s === "push");
|
|
20692
20985
|
const { command, cwd, sessionId } = await readPreToolUseStdin();
|
|
20693
|
-
if (cwd && (0,
|
|
20986
|
+
if (cwd && (0, import_node_fs33.existsSync)(cwd)) {
|
|
20694
20987
|
try {
|
|
20695
20988
|
process.chdir(cwd);
|
|
20696
20989
|
} catch {
|
|
@@ -20796,14 +21089,14 @@ function writeBlockMessage(moment, response) {
|
|
|
20796
21089
|
}
|
|
20797
21090
|
|
|
20798
21091
|
// src/commands/init.ts
|
|
20799
|
-
var
|
|
21092
|
+
var import_node_fs35 = require("node:fs");
|
|
20800
21093
|
var import_promises13 = require("node:fs/promises");
|
|
20801
21094
|
var import_node_path26 = require("node:path");
|
|
20802
21095
|
var import_node_child_process10 = require("node:child_process");
|
|
20803
21096
|
var readline2 = __toESM(require("node:readline/promises"));
|
|
20804
21097
|
|
|
20805
21098
|
// src/commands/migrate.ts
|
|
20806
|
-
var
|
|
21099
|
+
var import_node_fs34 = require("node:fs");
|
|
20807
21100
|
var import_node_path25 = require("node:path");
|
|
20808
21101
|
var import_node_child_process9 = require("node:child_process");
|
|
20809
21102
|
|
|
@@ -20935,10 +21228,10 @@ async function runMigration(opts = {}) {
|
|
|
20935
21228
|
function migrateProjectDir(root, actions) {
|
|
20936
21229
|
const gateDir = (0, import_node_path25.join)(root, ".gate");
|
|
20937
21230
|
const verityDir = (0, import_node_path25.join)(root, ".verity");
|
|
20938
|
-
if ((0,
|
|
21231
|
+
if ((0, import_node_fs34.existsSync)(gateDir) && !(0, import_node_fs34.existsSync)(verityDir)) {
|
|
20939
21232
|
return migrateProjectDirRename(root, gateDir, verityDir, actions);
|
|
20940
21233
|
}
|
|
20941
|
-
if ((0,
|
|
21234
|
+
if ((0, import_node_fs34.existsSync)(gateDir) && (0, import_node_fs34.existsSync)(verityDir)) {
|
|
20942
21235
|
return migrateProjectDirCarry(gateDir, verityDir, actions);
|
|
20943
21236
|
}
|
|
20944
21237
|
return false;
|
|
@@ -20959,13 +21252,13 @@ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
|
|
|
20959
21252
|
}
|
|
20960
21253
|
}
|
|
20961
21254
|
if (moved) {
|
|
20962
|
-
if ((0,
|
|
21255
|
+
if ((0, import_node_fs34.existsSync)(gateDir)) {
|
|
20963
21256
|
const carried = carryLegacyContents(gateDir, verityDir);
|
|
20964
21257
|
if (carried > 0) {
|
|
20965
21258
|
actions.push(`Carried ${carried} untracked legacy file(s) from .gate/ into .verity/`);
|
|
20966
21259
|
}
|
|
20967
21260
|
try {
|
|
20968
|
-
(0,
|
|
21261
|
+
(0, import_node_fs34.rmSync)(gateDir, { recursive: true, force: true });
|
|
20969
21262
|
} catch {
|
|
20970
21263
|
}
|
|
20971
21264
|
}
|
|
@@ -20981,7 +21274,7 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
|
|
|
20981
21274
|
actions.push(`Carried ${carried} legacy file(s) from .gate/ into .verity/`);
|
|
20982
21275
|
}
|
|
20983
21276
|
try {
|
|
20984
|
-
(0,
|
|
21277
|
+
(0, import_node_fs34.rmSync)(gateDir, { recursive: true, force: true });
|
|
20985
21278
|
} catch {
|
|
20986
21279
|
}
|
|
20987
21280
|
return carried > 0;
|
|
@@ -20990,9 +21283,9 @@ function migrateGlobalCredentials(home, actions) {
|
|
|
20990
21283
|
if (!home) return;
|
|
20991
21284
|
const gateCreds = (0, import_node_path25.join)(home, ".gate", "credentials");
|
|
20992
21285
|
const verityCreds = (0, import_node_path25.join)(home, ".verity", "credentials");
|
|
20993
|
-
if (!(0,
|
|
20994
|
-
if (!(0,
|
|
20995
|
-
(0,
|
|
21286
|
+
if (!(0, import_node_fs34.existsSync)(gateCreds)) return;
|
|
21287
|
+
if (!(0, import_node_fs34.existsSync)(verityCreds)) {
|
|
21288
|
+
(0, import_node_fs34.mkdirSync)((0, import_node_path25.join)(home, ".verity"), { recursive: true });
|
|
20996
21289
|
moveFile(gateCreds, verityCreds);
|
|
20997
21290
|
actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
|
|
20998
21291
|
return;
|
|
@@ -21015,7 +21308,7 @@ async function migrateLegacyHooks(root, actions) {
|
|
|
21015
21308
|
}
|
|
21016
21309
|
async function migrateClaudeMd(root, actions) {
|
|
21017
21310
|
const claudeMd = (0, import_node_path25.join)(root, "CLAUDE.md");
|
|
21018
|
-
const hadLegacyBlock = (0,
|
|
21311
|
+
const hadLegacyBlock = (0, import_node_fs34.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
|
|
21019
21312
|
if (!hadLegacyBlock) return;
|
|
21020
21313
|
try {
|
|
21021
21314
|
await ensureClaudeMdPointer(root);
|
|
@@ -21027,7 +21320,7 @@ async function migrateClaudeMd(root, actions) {
|
|
|
21027
21320
|
function migrateStandardFile(root, actions) {
|
|
21028
21321
|
const gateMd = (0, import_node_path25.join)(root, "GATE.md");
|
|
21029
21322
|
const verityMd = (0, import_node_path25.join)(root, "VERITY.md");
|
|
21030
|
-
if (!(0,
|
|
21323
|
+
if (!(0, import_node_fs34.existsSync)(gateMd) || (0, import_node_fs34.existsSync)(verityMd)) return;
|
|
21031
21324
|
let moved = false;
|
|
21032
21325
|
if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
|
|
21033
21326
|
try {
|
|
@@ -21039,12 +21332,12 @@ function migrateStandardFile(root, actions) {
|
|
|
21039
21332
|
if (!moved) moveFile(gateMd, verityMd);
|
|
21040
21333
|
const content = readFileSyncSafe(verityMd);
|
|
21041
21334
|
const refreshed = content.split("GATE.md").join("VERITY.md");
|
|
21042
|
-
if (refreshed !== content) (0,
|
|
21335
|
+
if (refreshed !== content) (0, import_node_fs34.writeFileSync)(verityMd, refreshed);
|
|
21043
21336
|
actions.push("Renamed GATE.md \u2192 VERITY.md");
|
|
21044
21337
|
}
|
|
21045
21338
|
async function migrateTelemetryHeaders(root, actions) {
|
|
21046
21339
|
const file = (0, import_node_path25.join)(root, ".claude", "settings.local.json");
|
|
21047
|
-
if (!(0,
|
|
21340
|
+
if (!(0, import_node_fs34.existsSync)(file)) return;
|
|
21048
21341
|
let settings;
|
|
21049
21342
|
try {
|
|
21050
21343
|
settings = JSON.parse(readFileSyncSafe(file) || "{}");
|
|
@@ -21092,14 +21385,14 @@ function mergeGlobalCredentials(gateCreds, verityCreds) {
|
|
|
21092
21385
|
}
|
|
21093
21386
|
if (toAppend.length > 0) {
|
|
21094
21387
|
const sep = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
|
|
21095
|
-
(0,
|
|
21388
|
+
(0, import_node_fs34.writeFileSync)(verityCreds, verityContent + sep + toAppend.join("\n") + "\n");
|
|
21096
21389
|
}
|
|
21097
|
-
(0,
|
|
21390
|
+
(0, import_node_fs34.rmSync)(gateCreds, { force: true });
|
|
21098
21391
|
return toAppend.length;
|
|
21099
21392
|
}
|
|
21100
21393
|
function readFileSyncSafe(path) {
|
|
21101
21394
|
try {
|
|
21102
|
-
return (0,
|
|
21395
|
+
return (0, import_node_fs34.readFileSync)(path, "utf-8");
|
|
21103
21396
|
} catch {
|
|
21104
21397
|
return "";
|
|
21105
21398
|
}
|
|
@@ -21114,35 +21407,35 @@ function hasStagedChanges(root) {
|
|
|
21114
21407
|
}
|
|
21115
21408
|
function moveDir(from, to) {
|
|
21116
21409
|
try {
|
|
21117
|
-
(0,
|
|
21410
|
+
(0, import_node_fs34.renameSync)(from, to);
|
|
21118
21411
|
} catch (err) {
|
|
21119
21412
|
if (err.code !== "EXDEV") throw err;
|
|
21120
|
-
(0,
|
|
21121
|
-
(0,
|
|
21413
|
+
(0, import_node_fs34.cpSync)(from, to, { recursive: true });
|
|
21414
|
+
(0, import_node_fs34.rmSync)(from, { recursive: true, force: true });
|
|
21122
21415
|
}
|
|
21123
21416
|
}
|
|
21124
21417
|
function moveFile(from, to) {
|
|
21125
21418
|
try {
|
|
21126
|
-
(0,
|
|
21419
|
+
(0, import_node_fs34.renameSync)(from, to);
|
|
21127
21420
|
} catch (err) {
|
|
21128
21421
|
if (err.code !== "EXDEV") throw err;
|
|
21129
|
-
(0,
|
|
21130
|
-
(0,
|
|
21422
|
+
(0, import_node_fs34.cpSync)(from, to);
|
|
21423
|
+
(0, import_node_fs34.rmSync)(from, { force: true });
|
|
21131
21424
|
}
|
|
21132
21425
|
}
|
|
21133
21426
|
function carryLegacyContents(gateDir, verityDir) {
|
|
21134
21427
|
let copied = 0;
|
|
21135
21428
|
const walk = (relDir) => {
|
|
21136
21429
|
const srcDir = (0, import_node_path25.join)(gateDir, relDir);
|
|
21137
|
-
for (const entry of (0,
|
|
21430
|
+
for (const entry of (0, import_node_fs34.readdirSync)(srcDir)) {
|
|
21138
21431
|
const rel = relDir ? (0, import_node_path25.join)(relDir, entry) : entry;
|
|
21139
21432
|
const src = (0, import_node_path25.join)(gateDir, rel);
|
|
21140
21433
|
const dest = (0, import_node_path25.join)(verityDir, rel);
|
|
21141
|
-
if ((0,
|
|
21434
|
+
if ((0, import_node_fs34.statSync)(src).isDirectory()) {
|
|
21142
21435
|
walk(rel);
|
|
21143
|
-
} else if (!(0,
|
|
21144
|
-
(0,
|
|
21145
|
-
(0,
|
|
21436
|
+
} else if (!(0, import_node_fs34.existsSync)(dest)) {
|
|
21437
|
+
(0, import_node_fs34.mkdirSync)((0, import_node_path25.dirname)(dest), { recursive: true });
|
|
21438
|
+
(0, import_node_fs34.cpSync)(src, dest);
|
|
21146
21439
|
copied++;
|
|
21147
21440
|
}
|
|
21148
21441
|
}
|
|
@@ -21153,20 +21446,20 @@ function carryLegacyContents(gateDir, verityDir) {
|
|
|
21153
21446
|
async function needsMigration(root = repoRoot()) {
|
|
21154
21447
|
const gateDir = (0, import_node_path25.join)(root, ".gate");
|
|
21155
21448
|
const verityDir = (0, import_node_path25.join)(root, ".verity");
|
|
21156
|
-
if ((0,
|
|
21157
|
-
if ((0,
|
|
21158
|
-
if ((0,
|
|
21449
|
+
if ((0, import_node_fs34.existsSync)(gateDir) && !(0, import_node_fs34.existsSync)(verityDir)) return true;
|
|
21450
|
+
if ((0, import_node_fs34.existsSync)(gateDir) && (0, import_node_fs34.existsSync)(verityDir)) {
|
|
21451
|
+
if ((0, import_node_fs34.existsSync)((0, import_node_path25.join)(gateDir, "credentials")) && !(0, import_node_fs34.existsSync)((0, import_node_path25.join)(verityDir, "credentials"))) {
|
|
21159
21452
|
return true;
|
|
21160
21453
|
}
|
|
21161
|
-
if ((0,
|
|
21454
|
+
if ((0, import_node_fs34.existsSync)((0, import_node_path25.join)(gateDir, "memory")) && !(0, import_node_fs34.existsSync)((0, import_node_path25.join)(verityDir, "memory"))) {
|
|
21162
21455
|
return true;
|
|
21163
21456
|
}
|
|
21164
21457
|
}
|
|
21165
21458
|
const claudeMd = (0, import_node_path25.join)(root, "CLAUDE.md");
|
|
21166
|
-
if ((0,
|
|
21459
|
+
if ((0, import_node_fs34.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
|
|
21167
21460
|
return true;
|
|
21168
21461
|
}
|
|
21169
|
-
if ((0,
|
|
21462
|
+
if ((0, import_node_fs34.existsSync)((0, import_node_path25.join)(root, "GATE.md")) && !(0, import_node_fs34.existsSync)((0, import_node_path25.join)(root, "VERITY.md"))) {
|
|
21170
21463
|
return true;
|
|
21171
21464
|
}
|
|
21172
21465
|
if (await hasLegacyHooksAt(root)) return true;
|
|
@@ -21308,7 +21601,7 @@ function resolveDataDir() {
|
|
|
21308
21601
|
// local dev: running from repo root
|
|
21309
21602
|
];
|
|
21310
21603
|
for (const candidate of candidates) {
|
|
21311
|
-
if ((0,
|
|
21604
|
+
if ((0, import_node_fs35.existsSync)((0, import_node_path26.join)(candidate, "skills"))) {
|
|
21312
21605
|
return candidate;
|
|
21313
21606
|
}
|
|
21314
21607
|
}
|
|
@@ -21324,7 +21617,7 @@ function registerInitCommand(program2) {
|
|
|
21324
21617
|
program2.command("init").description("Initialize Verity in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
|
|
21325
21618
|
const force = opts.force ?? false;
|
|
21326
21619
|
const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
|
|
21327
|
-
const isProject = projectMarkers.some((m) => (0,
|
|
21620
|
+
const isProject = projectMarkers.some((m) => (0, import_node_fs35.existsSync)(m));
|
|
21328
21621
|
if (!isProject) {
|
|
21329
21622
|
printError("No project detected in the current directory.");
|
|
21330
21623
|
printInfo('Run "verity init" from your project root.');
|
|
@@ -21394,14 +21687,14 @@ function registerInitCommand(program2) {
|
|
|
21394
21687
|
for (const skill of skills) {
|
|
21395
21688
|
const src = (0, import_node_path26.join)(skillsSource, skill);
|
|
21396
21689
|
const dest = (0, import_node_path26.join)(skillsDest, skill);
|
|
21397
|
-
if (!(0,
|
|
21690
|
+
if (!(0, import_node_fs35.existsSync)(src)) {
|
|
21398
21691
|
printWarn(` Skill data not found: ${skill}`);
|
|
21399
21692
|
continue;
|
|
21400
21693
|
}
|
|
21401
|
-
if ((0,
|
|
21694
|
+
if ((0, import_node_fs35.existsSync)(dest) && !force) {
|
|
21402
21695
|
const srcSkill = (0, import_node_path26.join)(src, "SKILL.md");
|
|
21403
21696
|
const destSkill = (0, import_node_path26.join)(dest, "SKILL.md");
|
|
21404
|
-
if ((0,
|
|
21697
|
+
if ((0, import_node_fs35.existsSync)(destSkill)) {
|
|
21405
21698
|
try {
|
|
21406
21699
|
const srcContent = await (0, import_promises13.readFile)(srcSkill, "utf-8");
|
|
21407
21700
|
const destContent = await (0, import_promises13.readFile)(destSkill, "utf-8");
|
|
@@ -21432,6 +21725,12 @@ function registerInitCommand(program2) {
|
|
|
21432
21725
|
}
|
|
21433
21726
|
await (0, import_promises13.mkdir)(VERITY_DIR, { recursive: true });
|
|
21434
21727
|
await ensureMemoryDir();
|
|
21728
|
+
const ignoreResult = ensureSnapshotGitignored();
|
|
21729
|
+
if (ignoreResult === "failed") {
|
|
21730
|
+
printWarn(" .gitignore: could not add .verity/.snapshot/ \u2014 add it manually (it holds copies of analyzed files)");
|
|
21731
|
+
} else {
|
|
21732
|
+
printInfo(` .gitignore: .verity/.snapshot/ ${ignoreResult === "added" ? "added" : "already covered"} \u2713`);
|
|
21733
|
+
}
|
|
21435
21734
|
try {
|
|
21436
21735
|
await ensureClaudeMdPointer();
|
|
21437
21736
|
printInfo(" CLAUDE.md memory pointer \u2713");
|
|
@@ -21473,7 +21772,7 @@ function registerInitCommand(program2) {
|
|
|
21473
21772
|
}
|
|
21474
21773
|
|
|
21475
21774
|
// src/commands/uninstall.ts
|
|
21476
|
-
var
|
|
21775
|
+
var import_node_fs36 = require("node:fs");
|
|
21477
21776
|
var import_node_path27 = require("node:path");
|
|
21478
21777
|
var SKILL_NAMES = [
|
|
21479
21778
|
"verity-setup",
|
|
@@ -21494,10 +21793,10 @@ function registerUninstallCommand(program2) {
|
|
|
21494
21793
|
const skillsRoot = projectPath(".claude/skills");
|
|
21495
21794
|
for (const name of SKILL_NAMES) {
|
|
21496
21795
|
const dir = (0, import_node_path27.join)(skillsRoot, name);
|
|
21497
|
-
if ((0,
|
|
21796
|
+
if ((0, import_node_fs36.existsSync)(dir)) {
|
|
21498
21797
|
actions.push({
|
|
21499
21798
|
label: `Remove .claude/skills/${name}/`,
|
|
21500
|
-
apply: () => (0,
|
|
21799
|
+
apply: () => (0, import_node_fs36.rmSync)(dir, { recursive: true, force: true })
|
|
21501
21800
|
});
|
|
21502
21801
|
}
|
|
21503
21802
|
}
|
|
@@ -21511,24 +21810,24 @@ function registerUninstallCommand(program2) {
|
|
|
21511
21810
|
});
|
|
21512
21811
|
}
|
|
21513
21812
|
const verityDir = projectPath(VERITY_DIR);
|
|
21514
|
-
if ((0,
|
|
21813
|
+
if ((0, import_node_fs36.existsSync)(verityDir)) {
|
|
21515
21814
|
actions.push({
|
|
21516
21815
|
label: `Remove ${VERITY_DIR}/`,
|
|
21517
|
-
apply: () => (0,
|
|
21816
|
+
apply: () => (0, import_node_fs36.rmSync)(verityDir, { recursive: true, force: true })
|
|
21518
21817
|
});
|
|
21519
21818
|
}
|
|
21520
21819
|
if (!keepVerityMd) {
|
|
21521
21820
|
const verityMd = projectPath(VERITY_MD_FILE);
|
|
21522
|
-
if ((0,
|
|
21821
|
+
if ((0, import_node_fs36.existsSync)(verityMd)) {
|
|
21523
21822
|
actions.push({
|
|
21524
21823
|
label: `Remove ${VERITY_MD_FILE}`,
|
|
21525
|
-
apply: () => (0,
|
|
21824
|
+
apply: () => (0, import_node_fs36.rmSync)(verityMd, { force: true })
|
|
21526
21825
|
});
|
|
21527
21826
|
}
|
|
21528
21827
|
}
|
|
21529
21828
|
const cleanupEmptyDir = (path) => {
|
|
21530
|
-
if ((0,
|
|
21531
|
-
(0,
|
|
21829
|
+
if ((0, import_node_fs36.existsSync)(path) && (0, import_node_fs36.statSync)(path).isDirectory() && (0, import_node_fs36.readdirSync)(path).length === 0) {
|
|
21830
|
+
(0, import_node_fs36.rmdirSync)(path);
|
|
21532
21831
|
}
|
|
21533
21832
|
};
|
|
21534
21833
|
actions.push({
|
|
@@ -21540,10 +21839,10 @@ function registerUninstallCommand(program2) {
|
|
|
21540
21839
|
});
|
|
21541
21840
|
const home = process.env.HOME ?? "";
|
|
21542
21841
|
const globalVerityDir = (0, import_node_path27.join)(home, ".verity");
|
|
21543
|
-
if (purgeGlobal && (0,
|
|
21842
|
+
if (purgeGlobal && (0, import_node_fs36.existsSync)(globalVerityDir)) {
|
|
21544
21843
|
actions.push({
|
|
21545
21844
|
label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
|
|
21546
|
-
apply: () => (0,
|
|
21845
|
+
apply: () => (0, import_node_fs36.rmSync)(globalVerityDir, { recursive: true, force: true })
|
|
21547
21846
|
});
|
|
21548
21847
|
}
|
|
21549
21848
|
if (actions.length === 0) {
|
|
@@ -21737,7 +22036,7 @@ function registerTaskCommands(program2) {
|
|
|
21737
22036
|
}
|
|
21738
22037
|
|
|
21739
22038
|
// src/commands/reset.ts
|
|
21740
|
-
var
|
|
22039
|
+
var import_node_fs37 = require("node:fs");
|
|
21741
22040
|
var import_node_path28 = require("node:path");
|
|
21742
22041
|
function registerResetCommand(program2) {
|
|
21743
22042
|
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) => {
|
|
@@ -21775,11 +22074,11 @@ function registerResetCommand(program2) {
|
|
|
21775
22074
|
}
|
|
21776
22075
|
const cacheDir = projectPath(CACHE_DIR);
|
|
21777
22076
|
let purged = 0;
|
|
21778
|
-
if ((0,
|
|
21779
|
-
for (const entry of (0,
|
|
22077
|
+
if ((0, import_node_fs37.existsSync)(cacheDir)) {
|
|
22078
|
+
for (const entry of (0, import_node_fs37.readdirSync)(cacheDir)) {
|
|
21780
22079
|
if (entry.startsWith("pending-")) {
|
|
21781
22080
|
try {
|
|
21782
|
-
(0,
|
|
22081
|
+
(0, import_node_fs37.unlinkSync)((0, import_node_path28.join)(cacheDir, entry));
|
|
21783
22082
|
purged++;
|
|
21784
22083
|
} catch {
|
|
21785
22084
|
}
|
|
@@ -21794,19 +22093,19 @@ function registerResetCommand(program2) {
|
|
|
21794
22093
|
projectPath(`${VERITY_DIR}/.last-analysis`)
|
|
21795
22094
|
];
|
|
21796
22095
|
for (const file of filesToClear) {
|
|
21797
|
-
if ((0,
|
|
22096
|
+
if ((0, import_node_fs37.existsSync)(file)) {
|
|
21798
22097
|
try {
|
|
21799
|
-
(0,
|
|
22098
|
+
(0, import_node_fs37.writeFileSync)(file, "");
|
|
21800
22099
|
} catch {
|
|
21801
22100
|
}
|
|
21802
22101
|
}
|
|
21803
22102
|
}
|
|
21804
22103
|
if (opts.all) {
|
|
21805
22104
|
const logsDir = projectPath(`${VERITY_DIR}/.logs`);
|
|
21806
|
-
if ((0,
|
|
21807
|
-
for (const entry of (0,
|
|
22105
|
+
if ((0, import_node_fs37.existsSync)(logsDir)) {
|
|
22106
|
+
for (const entry of (0, import_node_fs37.readdirSync)(logsDir)) {
|
|
21808
22107
|
try {
|
|
21809
|
-
(0,
|
|
22108
|
+
(0, import_node_fs37.unlinkSync)((0, import_node_path28.join)(logsDir, entry));
|
|
21810
22109
|
} catch {
|
|
21811
22110
|
}
|
|
21812
22111
|
}
|
|
@@ -22114,8 +22413,8 @@ function registerTelemetryCommands(program2) {
|
|
|
22114
22413
|
}
|
|
22115
22414
|
|
|
22116
22415
|
// src/cli.ts
|
|
22117
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.29.4-experimental.
|
|
22118
|
-
installStderrLog(actionCommand.name(), process.argv.slice(2), "0.29.4-experimental.
|
|
22416
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.29.4-experimental.e818a8a").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) => {
|
|
22417
|
+
installStderrLog(actionCommand.name(), process.argv.slice(2), "0.29.4-experimental.e818a8a");
|
|
22119
22418
|
setUserNamedServiceUrl(program.opts().serviceUrl);
|
|
22120
22419
|
try {
|
|
22121
22420
|
await foldLegacyLocalCredential();
|
|
@@ -711,18 +711,31 @@ to-be-pushed diff before it lands.
|
|
|
711
711
|
Add these entries to `.gitignore` (create it if it doesn't exist, append if it does):
|
|
712
712
|
|
|
713
713
|
```
|
|
714
|
-
# Verity
|
|
715
|
-
.
|
|
716
|
-
.verity
|
|
717
|
-
.
|
|
718
|
-
|
|
719
|
-
.verity/.last-pass-hash
|
|
720
|
-
.verity/.last-intent
|
|
721
|
-
.verity/.memory-sync-state.json
|
|
714
|
+
# Verity — machine-local state. Everything in .verity/ is ignored EXCEPT the
|
|
715
|
+
# shared standard and the knowledge graph, which are meant to be committed.
|
|
716
|
+
.verity/*
|
|
717
|
+
!.verity/standard.yaml
|
|
718
|
+
!.verity/memory/
|
|
722
719
|
.verity/memory/log.md
|
|
723
720
|
.claude/settings.local.json
|
|
724
721
|
```
|
|
725
722
|
|
|
723
|
+
This is a whitelist on purpose: `.verity/` accumulates state files over time
|
|
724
|
+
(`.snapshot/` holds byte-for-byte copies of analyzed files — including any
|
|
725
|
+
secret the gate just flagged — plus `.cache/`, `.logs/`, `.baseline`,
|
|
726
|
+
`.task-context`, session-suffixed `.last-analysis.*` files, and whatever comes
|
|
727
|
+
next). Enumerating them one by one is how `.snapshot/` ended up committed in a
|
|
728
|
+
real repo; ignoring everything and re-including the two shared artifacts means
|
|
729
|
+
a future state file can never repeat that.
|
|
730
|
+
|
|
731
|
+
**If the repo previously committed Verity state** (check with
|
|
732
|
+
`git ls-files .verity`), untrack everything except the shared artifacts once:
|
|
733
|
+
|
|
734
|
+
```bash
|
|
735
|
+
git rm -r --cached .verity
|
|
736
|
+
git add .verity/standard.yaml .verity/memory
|
|
737
|
+
```
|
|
738
|
+
|
|
726
739
|
`.claude/settings.local.json` is machine-local Claude Code config (telemetry endpoint + an
|
|
727
740
|
`otelHeadersHelper` reference — no token). `verity telemetry install` adds this entry
|
|
728
741
|
automatically.
|
package/package.json
CHANGED