@codacy/verity-cli 0.28.1-experimental.055cd97 → 0.28.1-experimental.10cb8ce
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 +834 -202
- package/package.json +1 -1
package/bin/verity.js
CHANGED
|
@@ -10386,10 +10386,9 @@ function projectPath(relativePath) {
|
|
|
10386
10386
|
return (0, import_node_path.join)(repoRoot(), relativePath);
|
|
10387
10387
|
}
|
|
10388
10388
|
var MAX_DELTA_BYTES = 194560;
|
|
10389
|
-
var MAX_FILES =
|
|
10389
|
+
var MAX_FILES = 40;
|
|
10390
10390
|
var MAX_FILE_BYTES = 51200;
|
|
10391
10391
|
var DEBOUNCE_SECONDS = 30;
|
|
10392
|
-
var MAX_ITERATIONS = 2;
|
|
10393
10392
|
var MAX_SPEC_FILES = 10;
|
|
10394
10393
|
var MAX_SPEC_FILE_BYTES = 10240;
|
|
10395
10394
|
var MAX_TOTAL_SPEC_BYTES = 30720;
|
|
@@ -10924,8 +10923,8 @@ function filterReviewable(files) {
|
|
|
10924
10923
|
const ext = (0, import_node_path3.extname)(f).slice(1);
|
|
10925
10924
|
if (ANALYZABLE_EXTENSIONS.has(ext)) return false;
|
|
10926
10925
|
if (REVIEWABLE_EXTENSIONS.has(ext)) return true;
|
|
10927
|
-
const
|
|
10928
|
-
if (REVIEWABLE_FILENAMES.has(
|
|
10926
|
+
const basename3 = f.split("/").pop() ?? "";
|
|
10927
|
+
if (REVIEWABLE_FILENAMES.has(basename3)) return true;
|
|
10929
10928
|
if (REVIEWABLE_PATH_PATTERNS.some((p) => p.test(f))) return true;
|
|
10930
10929
|
return false;
|
|
10931
10930
|
});
|
|
@@ -11195,7 +11194,15 @@ async function resolveServiceUrlDetailed(flagUrl) {
|
|
|
11195
11194
|
if (mdUrl) {
|
|
11196
11195
|
return { ok: true, data: { url: mdUrl, source: "verity_md" } };
|
|
11197
11196
|
}
|
|
11198
|
-
return {
|
|
11197
|
+
return {
|
|
11198
|
+
ok: false,
|
|
11199
|
+
error: 'No Verity service URL found. Run "verity login" to get started, or /verity-setup to configure this project.'
|
|
11200
|
+
};
|
|
11201
|
+
}
|
|
11202
|
+
async function resolveServiceUrlForAuth(flagUrl) {
|
|
11203
|
+
const strict = await resolveServiceUrlDetailed(flagUrl);
|
|
11204
|
+
if (strict.ok) return strict.data;
|
|
11205
|
+
return { url: DEFAULT_SERVICE_URL, source: "default" };
|
|
11199
11206
|
}
|
|
11200
11207
|
async function resolveServiceUrl(flagUrl) {
|
|
11201
11208
|
const result = await resolveServiceUrlDetailed(flagUrl);
|
|
@@ -11234,7 +11241,10 @@ async function resolveToken(flagToken) {
|
|
|
11234
11241
|
data: { token: local.token, source: "local", userId: local.userId, email: local.email }
|
|
11235
11242
|
};
|
|
11236
11243
|
}
|
|
11237
|
-
return {
|
|
11244
|
+
return {
|
|
11245
|
+
ok: false,
|
|
11246
|
+
error: 'No Verity token found. Run "verity login" to sign in, or /verity-setup to set up this project.'
|
|
11247
|
+
};
|
|
11238
11248
|
}
|
|
11239
11249
|
async function whoami(token, serviceUrl, verbose) {
|
|
11240
11250
|
return apiRequest({
|
|
@@ -11671,16 +11681,81 @@ function registerAuthCommands(program2) {
|
|
|
11671
11681
|
});
|
|
11672
11682
|
}
|
|
11673
11683
|
|
|
11684
|
+
// src/lib/login-report.ts
|
|
11685
|
+
async function reportLoginOutcome(out, opts = {}) {
|
|
11686
|
+
const identity = out.email ?? (out.userId != null ? `user #${out.userId}` : "your account");
|
|
11687
|
+
printInfo(`Logged in as ${identity}. \u2713`);
|
|
11688
|
+
printInfo(` Access granted to ${out.repoCount} ${out.repoCount === 1 ? "repository" : "repositories"}.`);
|
|
11689
|
+
if (out.expiresAt) {
|
|
11690
|
+
printInfo(` This login expires on ${out.expiresAt.slice(0, 10)} \u2014 "verity login" again to renew.`);
|
|
11691
|
+
printInfo(' See your machines with "verity sessions list"; sign out with "verity logout".');
|
|
11692
|
+
}
|
|
11693
|
+
if (out.repoCount > 0) {
|
|
11694
|
+
printInfo(" Runs, history, and cloud memory now sync to Verity everywhere you have write access.");
|
|
11695
|
+
}
|
|
11696
|
+
if (out.prunedCredentials > 0) {
|
|
11697
|
+
printVerbose(`Pruned ${out.prunedCredentials} superseded per-repository credential line(s).`, opts.verbose);
|
|
11698
|
+
} else if (out.prunedCredentials < 0) {
|
|
11699
|
+
printWarn(" Could not rewrite ~/.verity/credentials: superseded per-repository tokens remain and");
|
|
11700
|
+
printWarn(" will keep taking precedence over this login in their own repositories.");
|
|
11701
|
+
printInfo(` Check the file's permissions; the next successful "verity login" retries the cleanup.`);
|
|
11702
|
+
}
|
|
11703
|
+
if (out.repoCount === 0) {
|
|
11704
|
+
printWarn("The Verity GitHub App is not installed on any account you can access.");
|
|
11705
|
+
printInfo(" Install it (and grant your repositories), then re-run verity login:");
|
|
11706
|
+
printInfo(` ${githubAppInstallUrl(null)}`);
|
|
11707
|
+
return;
|
|
11708
|
+
}
|
|
11709
|
+
const remote = opts.remote;
|
|
11710
|
+
if (!remote) return;
|
|
11711
|
+
const who = await whoami(out.token, out.serviceUrl, opts.verbose);
|
|
11712
|
+
if (who.ok && who.data.grant_status != null) {
|
|
11713
|
+
printInfo(" \u2713 This repository is covered.");
|
|
11714
|
+
} else if (!who.ok) {
|
|
11715
|
+
printWarn(` Could not confirm this repository's coverage (${who.error}) \u2014 verity status will show it.`);
|
|
11716
|
+
} else {
|
|
11717
|
+
const parsed = parseRemote(remote);
|
|
11718
|
+
const installUrl = githubAppInstallUrl(parsed ? await githubAccountId(parsed.owner) : null);
|
|
11719
|
+
printWarn(` This repository (${parsed ? `${parsed.owner}/${parsed.repo}` : remote}) is NOT covered by your grants.`);
|
|
11720
|
+
printInfo(" Grant the Verity GitHub App access to it, then re-run verity login:");
|
|
11721
|
+
printInfo(` ${installUrl}`);
|
|
11722
|
+
}
|
|
11723
|
+
const rec = await readGlobalCredential(remote);
|
|
11724
|
+
if (rec && rec.token !== out.token) {
|
|
11725
|
+
const otherBackend = rec.serviceUrl != null && rec.serviceUrl !== out.serviceUrl;
|
|
11726
|
+
const otherIdentity = rec.userId != null && out.userId != null && rec.userId !== out.userId;
|
|
11727
|
+
if (otherBackend) {
|
|
11728
|
+
printWarn(` Note: this repository is pinned to a different Verity service (${rec.serviceUrl})`);
|
|
11729
|
+
printWarn(" by its own credential line, which takes precedence here \u2014 this login does not");
|
|
11730
|
+
printWarn(" change that. To move the repository, remove its line from ~/.verity/credentials.");
|
|
11731
|
+
printWarn(' Until that line is removed, "verity login" here cannot fast-path and will run');
|
|
11732
|
+
printWarn(" the full GitHub flow every time.");
|
|
11733
|
+
} else if (otherIdentity) {
|
|
11734
|
+
printWarn(" Note: this repository uses a different account's credential, which takes");
|
|
11735
|
+
printWarn(' precedence here \u2014 this login leaves it in place, and "verity login" in this');
|
|
11736
|
+
printWarn(" repository will report that account. Remove its line from ~/.verity/credentials");
|
|
11737
|
+
printWarn(" only if you want this repository on the login you just completed.");
|
|
11738
|
+
} else {
|
|
11739
|
+
const kind = rec.userId != null ? "superseded per-repository" : "anonymous project-specific";
|
|
11740
|
+
printWarn(` Note: this repository has a ${kind} credential that takes`);
|
|
11741
|
+
printWarn(" precedence here. Remove its line from ~/.verity/credentials to use your login.");
|
|
11742
|
+
printWarn(' Until then, "verity login" in this repository re-runs the full GitHub flow');
|
|
11743
|
+
printWarn(" every time.");
|
|
11744
|
+
}
|
|
11745
|
+
}
|
|
11746
|
+
}
|
|
11747
|
+
|
|
11674
11748
|
// src/commands/login.ts
|
|
11675
11749
|
function registerLoginCommand(program2) {
|
|
11676
11750
|
program2.command("login").description("Log in to Verity (one GitHub login grants access to all your repositories)").option("--force", "Re-authenticate even if already logged in").action(async (opts) => {
|
|
11677
11751
|
const globals = program2.opts();
|
|
11678
|
-
const
|
|
11679
|
-
if (
|
|
11680
|
-
|
|
11681
|
-
|
|
11752
|
+
const resolution = await resolveServiceUrlForAuth(globals.serviceUrl);
|
|
11753
|
+
if (resolution.source === "default") {
|
|
11754
|
+
printInfo(`No Verity service configured on this machine \u2014 using the default: ${resolution.url}`);
|
|
11755
|
+
} else {
|
|
11756
|
+
printVerbose(`Service URL from ${resolution.source}: ${resolution.url}`, globals.verbose);
|
|
11682
11757
|
}
|
|
11683
|
-
const heal = await maybeHealServiceUrl(
|
|
11758
|
+
const heal = await maybeHealServiceUrl(resolution, globals.verbose);
|
|
11684
11759
|
const serviceUrl = heal.serviceUrl;
|
|
11685
11760
|
if (heal.healed) {
|
|
11686
11761
|
printInfo(" Completing login updates ~/.verity/credentials against the live service.");
|
|
@@ -11721,65 +11796,7 @@ function registerLoginCommand(program2) {
|
|
|
11721
11796
|
printError(`Login failed: ${result.error}`);
|
|
11722
11797
|
process.exit(1);
|
|
11723
11798
|
}
|
|
11724
|
-
|
|
11725
|
-
const identity = out.email ?? (out.userId != null ? `user #${out.userId}` : "your account");
|
|
11726
|
-
printInfo(`Logged in as ${identity}. \u2713`);
|
|
11727
|
-
printInfo(` Access granted to ${out.repoCount} ${out.repoCount === 1 ? "repository" : "repositories"}.`);
|
|
11728
|
-
if (out.expiresAt) {
|
|
11729
|
-
printInfo(` This login expires on ${out.expiresAt.slice(0, 10)} \u2014 "verity login" again to renew.`);
|
|
11730
|
-
printInfo(' See your machines with "verity sessions list"; sign out with "verity logout".');
|
|
11731
|
-
}
|
|
11732
|
-
printInfo(" Runs, history, and cloud memory now sync to Verity everywhere you have write access.");
|
|
11733
|
-
if (out.prunedCredentials > 0) {
|
|
11734
|
-
printVerbose(`Pruned ${out.prunedCredentials} superseded per-repository credential line(s).`, globals.verbose);
|
|
11735
|
-
} else if (out.prunedCredentials < 0) {
|
|
11736
|
-
printWarn(" Could not rewrite ~/.verity/credentials: superseded per-repository tokens remain and");
|
|
11737
|
-
printWarn(" will keep taking precedence over this login in their own repositories.");
|
|
11738
|
-
printInfo(` Check the file's permissions; the next successful "verity login" retries the cleanup.`);
|
|
11739
|
-
}
|
|
11740
|
-
if (out.repoCount === 0) {
|
|
11741
|
-
printWarn("The Verity GitHub App is not installed on any account you can access.");
|
|
11742
|
-
printInfo(` Install it (and grant your repositories), then re-run verity login:`);
|
|
11743
|
-
printInfo(` ${githubAppInstallUrl(null)}`);
|
|
11744
|
-
return;
|
|
11745
|
-
}
|
|
11746
|
-
if (remote) {
|
|
11747
|
-
const who = await whoami(out.token, out.serviceUrl, globals.verbose);
|
|
11748
|
-
if (who.ok && who.data.grant_status != null) {
|
|
11749
|
-
printInfo(" \u2713 This repository is covered.");
|
|
11750
|
-
} else if (!who.ok) {
|
|
11751
|
-
printWarn(` Could not confirm this repository's coverage (${who.error}) \u2014 verity status will show it.`);
|
|
11752
|
-
} else {
|
|
11753
|
-
const parsed = parseRemote(remote);
|
|
11754
|
-
const installUrl = githubAppInstallUrl(parsed ? await githubAccountId(parsed.owner) : null);
|
|
11755
|
-
printWarn(` This repository (${parsed ? `${parsed.owner}/${parsed.repo}` : remote}) is NOT covered by your grants.`);
|
|
11756
|
-
printInfo(" Grant the Verity GitHub App access to it, then re-run verity login:");
|
|
11757
|
-
printInfo(` ${installUrl}`);
|
|
11758
|
-
}
|
|
11759
|
-
const rec = await readGlobalCredential(remote);
|
|
11760
|
-
if (rec && rec.token !== out.token) {
|
|
11761
|
-
const otherBackend = rec.serviceUrl != null && rec.serviceUrl !== out.serviceUrl;
|
|
11762
|
-
const otherIdentity = rec.userId != null && out.userId != null && rec.userId !== out.userId;
|
|
11763
|
-
if (otherBackend) {
|
|
11764
|
-
printWarn(` Note: this repository is pinned to a different Verity service (${rec.serviceUrl})`);
|
|
11765
|
-
printWarn(" by its own credential line, which takes precedence here \u2014 this login does not");
|
|
11766
|
-
printWarn(" change that. To move the repository, remove its line from ~/.verity/credentials.");
|
|
11767
|
-
printWarn(' Until that line is removed, "verity login" here cannot fast-path and will run');
|
|
11768
|
-
printWarn(" the full GitHub flow every time.");
|
|
11769
|
-
} else if (otherIdentity) {
|
|
11770
|
-
printWarn(" Note: this repository uses a different account's credential, which takes");
|
|
11771
|
-
printWarn(' precedence here \u2014 this login leaves it in place, and "verity login" in this');
|
|
11772
|
-
printWarn(" repository will report that account. Remove its line from ~/.verity/credentials");
|
|
11773
|
-
printWarn(" only if you want this repository on the login you just completed.");
|
|
11774
|
-
} else {
|
|
11775
|
-
const kind = rec.userId != null ? "superseded per-repository" : "anonymous project-specific";
|
|
11776
|
-
printWarn(` Note: this repository has a ${kind} credential that takes`);
|
|
11777
|
-
printWarn(" precedence here. Remove its line from ~/.verity/credentials to use your login.");
|
|
11778
|
-
printWarn(' Until then, "verity login" in this repository re-runs the full GitHub flow');
|
|
11779
|
-
printWarn(" every time.");
|
|
11780
|
-
}
|
|
11781
|
-
}
|
|
11782
|
-
}
|
|
11799
|
+
await reportLoginOutcome(result.data, { remote: remote || void 0, verbose: globals.verbose });
|
|
11783
11800
|
});
|
|
11784
11801
|
}
|
|
11785
11802
|
|
|
@@ -11914,12 +11931,8 @@ async function auth(globals) {
|
|
|
11914
11931
|
printError(tokenResult.error);
|
|
11915
11932
|
process.exit(1);
|
|
11916
11933
|
}
|
|
11917
|
-
const
|
|
11918
|
-
|
|
11919
|
-
printError(urlResult.error);
|
|
11920
|
-
process.exit(1);
|
|
11921
|
-
}
|
|
11922
|
-
return { token: tokenResult.data.token, serviceUrl: urlResult.data };
|
|
11934
|
+
const resolution = await resolveServiceUrlForAuth(globals.serviceUrl);
|
|
11935
|
+
return { token: tokenResult.data.token, serviceUrl: resolution.url };
|
|
11923
11936
|
}
|
|
11924
11937
|
function explain(error) {
|
|
11925
11938
|
if (error.startsWith("FORBIDDEN")) {
|
|
@@ -13591,6 +13604,143 @@ var import_node_fs10 = require("node:fs");
|
|
|
13591
13604
|
var import_node_crypto5 = require("node:crypto");
|
|
13592
13605
|
var import_node_path11 = require("node:path");
|
|
13593
13606
|
|
|
13607
|
+
// src/lib/skip-detection.ts
|
|
13608
|
+
function isBareAckPrompt(prompt) {
|
|
13609
|
+
if (typeof prompt !== "string") return false;
|
|
13610
|
+
const trimmed = prompt.trim();
|
|
13611
|
+
if (trimmed.length === 0) return false;
|
|
13612
|
+
if (trimmed.length > 20) return false;
|
|
13613
|
+
const bareAckPattern = /^(\d{1,2}|y|n|yes|no|yep|nope|ok(ay)?|sure|skip|cancel|stop|done|noted|got\s+it|sounds\s+good|thanks|thank\s+you|thx)[.!?]*$/i;
|
|
13614
|
+
return bareAckPattern.test(trimmed);
|
|
13615
|
+
}
|
|
13616
|
+
function isContinuationPrompt(prompt) {
|
|
13617
|
+
if (typeof prompt !== "string") return false;
|
|
13618
|
+
const trimmed = prompt.trim();
|
|
13619
|
+
if (trimmed.length === 0) return false;
|
|
13620
|
+
if (trimmed.length > 24) return false;
|
|
13621
|
+
const continuation = /^(let['’]?s\s+(go|do\s+it|start|continue)|go|go\s+ahead|go\s+on|proceed|continue|carry\s+on|keep\s+going|do\s+it|make\s+it\s+so|next|start|begin|ship\s+it|yes\s+please|please\s+continue|perfect|great|nice|excellent|agreed)[.!]*$/i;
|
|
13622
|
+
return continuation.test(trimmed) || isBareAckPrompt(trimmed);
|
|
13623
|
+
}
|
|
13624
|
+
function resolveGoalPrompt(prompts) {
|
|
13625
|
+
if (prompts.length === 0) return null;
|
|
13626
|
+
const latest = prompts[prompts.length - 1];
|
|
13627
|
+
if (!isContinuationPrompt(latest.prompt)) return { entry: latest, turnsBack: 0 };
|
|
13628
|
+
for (let i = prompts.length - 2; i >= 0; i--) {
|
|
13629
|
+
if (!isContinuationPrompt(prompts[i].prompt)) {
|
|
13630
|
+
return { entry: prompts[i], turnsBack: prompts.length - 1 - i };
|
|
13631
|
+
}
|
|
13632
|
+
}
|
|
13633
|
+
return { entry: latest, turnsBack: 0 };
|
|
13634
|
+
}
|
|
13635
|
+
function isReflectionQuestion(response) {
|
|
13636
|
+
if (!response || typeof response !== "string") return false;
|
|
13637
|
+
const markers = [
|
|
13638
|
+
/reflection\s+for\s+future\s+agents/i,
|
|
13639
|
+
/what(?:'s|\s+is)\s+one\s+thing\s+you\s+learned/i,
|
|
13640
|
+
/say\s+['"]?skip['"]?\s+to\s+skip/i,
|
|
13641
|
+
/quick\s+reflection\s+question/i,
|
|
13642
|
+
// Post-flip (VRT-21): the agent drafts the reflection itself and, when
|
|
13643
|
+
// interactive, asks the user to confirm/correct before recording. That
|
|
13644
|
+
// turn authors no code either, so it's still a reflection turn.
|
|
13645
|
+
/reflection\s+draft/i,
|
|
13646
|
+
/confirm,?\s+correct,?\s+or\s+add/i
|
|
13647
|
+
];
|
|
13648
|
+
return markers.some((m) => m.test(response));
|
|
13649
|
+
}
|
|
13650
|
+
function isMetaTaskLabel(label2) {
|
|
13651
|
+
if (label2 === null || label2 === void 0) return false;
|
|
13652
|
+
if (typeof label2 !== "string") return false;
|
|
13653
|
+
const trimmed = label2.trim();
|
|
13654
|
+
if (trimmed.length === 0) return true;
|
|
13655
|
+
const metaPatterns = [
|
|
13656
|
+
/^verity\s+[\w-]+\s+response$/i,
|
|
13657
|
+
// "Verity reflect response"
|
|
13658
|
+
/^simple user response$/i,
|
|
13659
|
+
/^verity\s+command$/i,
|
|
13660
|
+
// "Verity command"
|
|
13661
|
+
/^user\s+(question|reply|response|ack)$/i
|
|
13662
|
+
];
|
|
13663
|
+
return metaPatterns.some((p) => p.test(trimmed));
|
|
13664
|
+
}
|
|
13665
|
+
function shouldSkipForBareAck(input) {
|
|
13666
|
+
if (!isBareAckPrompt(input.prompt)) return false;
|
|
13667
|
+
if (input.turnAuthoredCode) return false;
|
|
13668
|
+
return input.canSeeTurnAuthorship;
|
|
13669
|
+
}
|
|
13670
|
+
|
|
13671
|
+
// src/lib/pending-repeat.ts
|
|
13672
|
+
var STOP = /* @__PURE__ */ new Set([
|
|
13673
|
+
"the",
|
|
13674
|
+
"and",
|
|
13675
|
+
"that",
|
|
13676
|
+
"this",
|
|
13677
|
+
"with",
|
|
13678
|
+
"from",
|
|
13679
|
+
"have",
|
|
13680
|
+
"been",
|
|
13681
|
+
"were",
|
|
13682
|
+
"what",
|
|
13683
|
+
"when",
|
|
13684
|
+
"which",
|
|
13685
|
+
"their",
|
|
13686
|
+
"there",
|
|
13687
|
+
"these",
|
|
13688
|
+
"those",
|
|
13689
|
+
"would",
|
|
13690
|
+
"could",
|
|
13691
|
+
"should",
|
|
13692
|
+
"must",
|
|
13693
|
+
"will",
|
|
13694
|
+
"also",
|
|
13695
|
+
"just",
|
|
13696
|
+
"only",
|
|
13697
|
+
"into",
|
|
13698
|
+
"over",
|
|
13699
|
+
"than",
|
|
13700
|
+
"then",
|
|
13701
|
+
"them",
|
|
13702
|
+
"some",
|
|
13703
|
+
"such",
|
|
13704
|
+
"more",
|
|
13705
|
+
"most",
|
|
13706
|
+
"other",
|
|
13707
|
+
"about",
|
|
13708
|
+
"after",
|
|
13709
|
+
"before",
|
|
13710
|
+
"since",
|
|
13711
|
+
"because",
|
|
13712
|
+
"while",
|
|
13713
|
+
"where",
|
|
13714
|
+
"whether",
|
|
13715
|
+
"ensure",
|
|
13716
|
+
"confirm",
|
|
13717
|
+
"verify",
|
|
13718
|
+
"check"
|
|
13719
|
+
]);
|
|
13720
|
+
function pendingTokens(text) {
|
|
13721
|
+
if (!text || typeof text !== "string") return [];
|
|
13722
|
+
const out = /* @__PURE__ */ new Set();
|
|
13723
|
+
for (const raw of text.toLowerCase().split(/[^a-z0-9]+/)) {
|
|
13724
|
+
if (raw.length <= 3) continue;
|
|
13725
|
+
if (STOP.has(raw)) continue;
|
|
13726
|
+
out.add(raw);
|
|
13727
|
+
}
|
|
13728
|
+
return [...out].sort();
|
|
13729
|
+
}
|
|
13730
|
+
var REPEAT_THRESHOLD = 0.3;
|
|
13731
|
+
function overlapCoefficient(a, b) {
|
|
13732
|
+
if (a.length === 0 || b.length === 0) return 0;
|
|
13733
|
+
const setB = new Set(b);
|
|
13734
|
+
let shared = 0;
|
|
13735
|
+
for (const t of a) if (setB.has(t)) shared++;
|
|
13736
|
+
return shared / Math.min(a.length, b.length);
|
|
13737
|
+
}
|
|
13738
|
+
function isRepeatOfAny(text, priorFingerprints, threshold = REPEAT_THRESHOLD) {
|
|
13739
|
+
const tokens = pendingTokens(text);
|
|
13740
|
+
if (tokens.length === 0) return false;
|
|
13741
|
+
return priorFingerprints.some((prior) => overlapCoefficient(tokens, prior) >= threshold);
|
|
13742
|
+
}
|
|
13743
|
+
|
|
13594
13744
|
// src/lib/dossier.ts
|
|
13595
13745
|
var import_node_fs9 = require("node:fs");
|
|
13596
13746
|
var import_node_crypto4 = require("node:crypto");
|
|
@@ -13599,6 +13749,7 @@ var MAX_LINE_BYTES = 4096;
|
|
|
13599
13749
|
var MAX_GOAL_CHARS = 2e3;
|
|
13600
13750
|
var GOAL_KEEP = 8;
|
|
13601
13751
|
var GOAL_TOTAL_CAP = 32;
|
|
13752
|
+
var RECENT_PENDING_CAP = 20;
|
|
13602
13753
|
var HASH_WIDTH = 16;
|
|
13603
13754
|
var AUTHORED_CAP = 300;
|
|
13604
13755
|
var NOT_MINE_CAP = 300;
|
|
@@ -13967,6 +14118,10 @@ function reduce(state, events, now) {
|
|
|
13967
14118
|
consecutiveIdle: ev.idle === false ? 0 : (state.meta.channel?.consecutiveIdle ?? 0) + 1
|
|
13968
14119
|
};
|
|
13969
14120
|
state.meta.last_adjudication = ev.intent_verdict ? { verdict: ev.intent_verdict, score: ev.intent_score ?? null, at: ev.at, decision: ev.decision } : void 0;
|
|
14121
|
+
if (Array.isArray(ev.pending_sigs) && ev.pending_sigs.length > 0) {
|
|
14122
|
+
const prior = state.meta.recent_pending_sigs ?? [];
|
|
14123
|
+
state.meta.recent_pending_sigs = [...prior, ...ev.pending_sigs].slice(-RECENT_PENDING_CAP);
|
|
14124
|
+
}
|
|
13970
14125
|
if (ev.intent_sig) {
|
|
13971
14126
|
state.meta.intent_repeat = state.meta.intent_repeat && state.meta.intent_repeat.sig === ev.intent_sig ? { sig: ev.intent_sig, consecutive: state.meta.intent_repeat.consecutive + 1 } : { sig: ev.intent_sig, consecutive: 1 };
|
|
13972
14127
|
} else {
|
|
@@ -14695,7 +14850,19 @@ function sessionDossier(token, sessionId) {
|
|
|
14695
14850
|
const d = openDossier(identity);
|
|
14696
14851
|
return d ? { d, identity } : null;
|
|
14697
14852
|
}
|
|
14853
|
+
function hasActiveGoal(d) {
|
|
14854
|
+
try {
|
|
14855
|
+
if (!(0, import_node_fs10.existsSync)(d.eventsPath)) return false;
|
|
14856
|
+
return (0, import_node_fs10.readFileSync)(d.eventsPath, "utf8").includes('"k":"goal"');
|
|
14857
|
+
} catch {
|
|
14858
|
+
return false;
|
|
14859
|
+
}
|
|
14860
|
+
}
|
|
14698
14861
|
function recordGoal(d, prompt, source = "prompt") {
|
|
14862
|
+
if (source === "prompt" && isContinuationPrompt(prompt) && hasActiveGoal(d)) {
|
|
14863
|
+
appendEvent(d, { k: "goal_continue", text: prompt.slice(0, 64) });
|
|
14864
|
+
return;
|
|
14865
|
+
}
|
|
14699
14866
|
const text = prompt.slice(0, MAX_GOAL_CHARS);
|
|
14700
14867
|
appendEvent(d, {
|
|
14701
14868
|
k: "goal",
|
|
@@ -14811,6 +14978,13 @@ function recordVerdict(d, v) {
|
|
|
14811
14978
|
branch: v.branch,
|
|
14812
14979
|
decision: v.decision,
|
|
14813
14980
|
...sig && { intent_sig: sig },
|
|
14981
|
+
// Fingerprints of the pending items this verdict delivered, so the NEXT turn
|
|
14982
|
+
// can tell a repeat from a new requirement. Only recorded when the channel
|
|
14983
|
+
// actually spoke — a silenced turn delivered nothing, so nothing was "said
|
|
14984
|
+
// before" and labelling the next turn's items as repeats would be a lie.
|
|
14985
|
+
...v.emitted === true && v.pendingTexts && v.pendingTexts.length > 0 && {
|
|
14986
|
+
pending_sigs: v.pendingTexts.slice(0, 8).map((t) => pendingTokens(t).slice(0, 16))
|
|
14987
|
+
},
|
|
14814
14988
|
emitted: v.emitted === true,
|
|
14815
14989
|
idle: v.idle !== false,
|
|
14816
14990
|
...v.intent?.verdict && { intent_verdict: v.intent.verdict },
|
|
@@ -15118,6 +15292,8 @@ function collectCodeDelta(files, opts) {
|
|
|
15118
15292
|
let totalSize = 0;
|
|
15119
15293
|
let truncationReason = null;
|
|
15120
15294
|
const droppedPaths = [];
|
|
15295
|
+
const excluded = [];
|
|
15296
|
+
const exclude = (path, reason) => excluded.push({ path, reason, stage: "collectCodeDelta", kind: "capacity" });
|
|
15121
15297
|
for (const filepath of sorted) {
|
|
15122
15298
|
if (result.length >= maxFiles) {
|
|
15123
15299
|
truncationReason ??= "max_files";
|
|
@@ -15125,14 +15301,21 @@ function collectCodeDelta(files, opts) {
|
|
|
15125
15301
|
continue;
|
|
15126
15302
|
}
|
|
15127
15303
|
const resolved = resolveFile(filepath);
|
|
15128
|
-
if (!resolved)
|
|
15304
|
+
if (!resolved) {
|
|
15305
|
+
exclude(filepath, "path-not-resolvable");
|
|
15306
|
+
continue;
|
|
15307
|
+
}
|
|
15129
15308
|
let size;
|
|
15130
15309
|
try {
|
|
15131
15310
|
size = (0, import_node_fs11.statSync)(resolved).size;
|
|
15132
15311
|
} catch {
|
|
15312
|
+
exclude(filepath, "not-stattable");
|
|
15313
|
+
continue;
|
|
15314
|
+
}
|
|
15315
|
+
if (size > maxFileBytes) {
|
|
15316
|
+
exclude(filepath, `over-file-size-limit-${maxFileBytes}b`);
|
|
15133
15317
|
continue;
|
|
15134
15318
|
}
|
|
15135
|
-
if (size > maxFileBytes) continue;
|
|
15136
15319
|
if (totalSize + size > maxTotalBytes) {
|
|
15137
15320
|
truncationReason ??= "max_total_bytes";
|
|
15138
15321
|
const idx = sorted.indexOf(filepath);
|
|
@@ -15143,6 +15326,7 @@ function collectCodeDelta(files, opts) {
|
|
|
15143
15326
|
try {
|
|
15144
15327
|
content = (0, import_node_fs11.readFileSync)(resolved, "utf-8");
|
|
15145
15328
|
} catch {
|
|
15329
|
+
exclude(filepath, "not-readable");
|
|
15146
15330
|
continue;
|
|
15147
15331
|
}
|
|
15148
15332
|
totalSize += size;
|
|
@@ -15156,10 +15340,14 @@ function collectCodeDelta(files, opts) {
|
|
|
15156
15340
|
(sum, f) => sum + f.content.split("\n").length,
|
|
15157
15341
|
0
|
|
15158
15342
|
);
|
|
15343
|
+
for (const path of droppedPaths) {
|
|
15344
|
+
exclude(path, truncationReason === "max_files" ? "max-files-cap" : "max-total-bytes-cap");
|
|
15345
|
+
}
|
|
15159
15346
|
return {
|
|
15160
15347
|
files: result,
|
|
15161
15348
|
total_lines: totalLines,
|
|
15162
15349
|
total_files: result.length,
|
|
15350
|
+
excluded,
|
|
15163
15351
|
...truncationReason && {
|
|
15164
15352
|
truncated: {
|
|
15165
15353
|
reason: truncationReason,
|
|
@@ -15453,6 +15641,34 @@ ${addedLines}`,
|
|
|
15453
15641
|
}
|
|
15454
15642
|
return { diffs, has_baseline: true };
|
|
15455
15643
|
}
|
|
15644
|
+
function absorbIntoBaseline(paths, sessionId) {
|
|
15645
|
+
const baseline = readBaseline(sessionId);
|
|
15646
|
+
if (!baseline || paths.length === 0) return 0;
|
|
15647
|
+
const dir = sessionDir(sessionKey(baseline.session_id));
|
|
15648
|
+
let adopted = 0;
|
|
15649
|
+
const dirty = new Set(baseline.dirty_paths);
|
|
15650
|
+
for (const p of paths) {
|
|
15651
|
+
try {
|
|
15652
|
+
const content = safeReadForMirror(projectPath(p));
|
|
15653
|
+
if (content === null) continue;
|
|
15654
|
+
const dest = mirrorPath(dir, p);
|
|
15655
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path14.dirname)(dest), { recursive: true });
|
|
15656
|
+
(0, import_node_fs13.writeFileSync)(dest, content);
|
|
15657
|
+
dirty.add(p);
|
|
15658
|
+
adopted++;
|
|
15659
|
+
} catch {
|
|
15660
|
+
}
|
|
15661
|
+
}
|
|
15662
|
+
if (adopted === 0) return 0;
|
|
15663
|
+
try {
|
|
15664
|
+
const updated = { ...baseline, dirty_paths: [...dirty] };
|
|
15665
|
+
(0, import_node_fs13.writeFileSync)(manifestPath(dir), JSON.stringify(updated));
|
|
15666
|
+
preImageCache.delete(baseline);
|
|
15667
|
+
} catch {
|
|
15668
|
+
return 0;
|
|
15669
|
+
}
|
|
15670
|
+
return adopted;
|
|
15671
|
+
}
|
|
15456
15672
|
function changedSinceBaseline(repoRelPath, baseline) {
|
|
15457
15673
|
const pre = preImage(repoRelPath, baseline);
|
|
15458
15674
|
let current;
|
|
@@ -16329,40 +16545,40 @@ function narrowToRecent(files, sessionId) {
|
|
|
16329
16545
|
});
|
|
16330
16546
|
return recent.length > 0 ? recent : files;
|
|
16331
16547
|
}
|
|
16332
|
-
function
|
|
16333
|
-
if (!(0, import_node_fs15.existsSync)(ITERATION_FILE)) return 1;
|
|
16548
|
+
function readIterationState(currentCommit) {
|
|
16549
|
+
if (!(0, import_node_fs15.existsSync)(ITERATION_FILE)) return { iteration: 1, fingerprint: null };
|
|
16334
16550
|
try {
|
|
16335
16551
|
const stored = (0, import_node_fs15.readFileSync)(ITERATION_FILE, "utf-8").trim();
|
|
16336
16552
|
const parts = stored.split(":");
|
|
16337
16553
|
const iter = parseInt(parts[0], 10);
|
|
16338
16554
|
const storedCommit = parts[1] ?? "";
|
|
16339
16555
|
const storedTimestamp = parseInt(parts[2] ?? "0", 10);
|
|
16340
|
-
|
|
16341
|
-
if (
|
|
16556
|
+
const fingerprint = parts.slice(3).join(":") || null;
|
|
16557
|
+
if (isNaN(iter)) return { iteration: 1, fingerprint: null };
|
|
16558
|
+
if (storedCommit !== currentCommit) return { iteration: 1, fingerprint: null };
|
|
16342
16559
|
if (storedTimestamp > 0) {
|
|
16343
16560
|
const elapsed = Math.floor(Date.now() / 1e3) - storedTimestamp;
|
|
16344
|
-
if (elapsed > 600) return 1;
|
|
16561
|
+
if (elapsed > 600) return { iteration: 1, fingerprint: null };
|
|
16345
16562
|
}
|
|
16346
|
-
return iter;
|
|
16563
|
+
return { iteration: iter, fingerprint };
|
|
16347
16564
|
} catch {
|
|
16348
|
-
return 1;
|
|
16565
|
+
return { iteration: 1, fingerprint: null };
|
|
16349
16566
|
}
|
|
16350
16567
|
}
|
|
16351
|
-
function
|
|
16352
|
-
const
|
|
16353
|
-
|
|
16354
|
-
writeIteration(1, currentCommit, contentHash);
|
|
16355
|
-
return {
|
|
16356
|
-
skip: `Max Verity iterations (${maxIterations}) reached \u2014 accepting to prevent infinite loop. Human review required before deploying.`,
|
|
16357
|
-
iteration
|
|
16358
|
-
};
|
|
16359
|
-
}
|
|
16360
|
-
return { skip: null, iteration };
|
|
16568
|
+
function findingsFingerprint(findings) {
|
|
16569
|
+
const keys = findings.map((f) => `${String(f.pattern_id ?? "?")}|${String(f.file ?? "?")}`).filter((k) => k !== "?|?");
|
|
16570
|
+
return [...new Set(keys)].sort().join(",");
|
|
16361
16571
|
}
|
|
16362
|
-
function
|
|
16572
|
+
function isSameProblem(previous, current) {
|
|
16573
|
+
if (!previous || !current) return false;
|
|
16574
|
+
const prev = new Set(previous.split(","));
|
|
16575
|
+
return current.split(",").some((k) => prev.has(k));
|
|
16576
|
+
}
|
|
16577
|
+
function writeIteration(iteration, commit, _contentHash, fingerprint) {
|
|
16363
16578
|
(0, import_node_fs15.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
16364
16579
|
const ts = Math.floor(Date.now() / 1e3);
|
|
16365
|
-
|
|
16580
|
+
const fp = fingerprint ? `:${fingerprint}` : "";
|
|
16581
|
+
(0, import_node_fs15.writeFileSync)(ITERATION_FILE, `${iteration}:${commit}:${ts}${fp}`);
|
|
16366
16582
|
}
|
|
16367
16583
|
|
|
16368
16584
|
// src/lib/static-analysis.ts
|
|
@@ -16611,7 +16827,7 @@ function resolveTaskContext(opts) {
|
|
|
16611
16827
|
// src/lib/cli-version.ts
|
|
16612
16828
|
function cliVersion() {
|
|
16613
16829
|
try {
|
|
16614
|
-
return true ? "0.28.1-experimental.
|
|
16830
|
+
return true ? "0.28.1-experimental.10cb8ce" : "dev";
|
|
16615
16831
|
} catch {
|
|
16616
16832
|
return "dev";
|
|
16617
16833
|
}
|
|
@@ -17156,6 +17372,86 @@ function checkConservation(changedFiles, result, repoRoot2) {
|
|
|
17156
17372
|
};
|
|
17157
17373
|
}
|
|
17158
17374
|
|
|
17375
|
+
// src/lib/verdict.ts
|
|
17376
|
+
function reconcileCoverage(changed, coverage) {
|
|
17377
|
+
const changedSet = new Set(changed);
|
|
17378
|
+
const reviewed = coverage.reviewed.filter((p) => changedSet.has(p));
|
|
17379
|
+
const claimed = /* @__PURE__ */ new Set([...reviewed, ...coverage.notReviewed.map((n) => n.path)]);
|
|
17380
|
+
const unaccounted = [...changedSet].filter((p) => !claimed.has(p)).sort();
|
|
17381
|
+
const notReviewed = [
|
|
17382
|
+
...coverage.notReviewed.filter((n) => changedSet.has(n.path)),
|
|
17383
|
+
...unaccounted.map((path) => ({
|
|
17384
|
+
path,
|
|
17385
|
+
reason: "unaccounted",
|
|
17386
|
+
// Named so the eventual bug report writes itself: some stage removed this
|
|
17387
|
+
// path and did not say so.
|
|
17388
|
+
stage: "unknown-stage",
|
|
17389
|
+
// An undeclared drop is CAPACITY by default. A stage that cannot be
|
|
17390
|
+
// bothered to say why it dropped a file does not get the benefit of the
|
|
17391
|
+
// doubt — that default is what makes forgetting expensive.
|
|
17392
|
+
kind: "capacity"
|
|
17393
|
+
}))
|
|
17394
|
+
];
|
|
17395
|
+
return {
|
|
17396
|
+
coverage: { reviewed: [...new Set(reviewed)].sort(), notReviewed },
|
|
17397
|
+
unaccounted,
|
|
17398
|
+
balances: unaccounted.length === 0
|
|
17399
|
+
};
|
|
17400
|
+
}
|
|
17401
|
+
function resolveVerdict(proposed, coverage) {
|
|
17402
|
+
if (proposed === "FAIL") return "FAIL";
|
|
17403
|
+
const blocking = coverage.notReviewed.filter((n) => (n.kind ?? "capacity") !== "policy");
|
|
17404
|
+
if (blocking.length === 0) return proposed;
|
|
17405
|
+
return "WARN";
|
|
17406
|
+
}
|
|
17407
|
+
function describeCoverage(coverage, maxPaths = 5) {
|
|
17408
|
+
const relevant = coverage.notReviewed.filter((n) => (n.kind ?? "capacity") !== "policy");
|
|
17409
|
+
if (relevant.length === 0) return null;
|
|
17410
|
+
const byReason = /* @__PURE__ */ new Map();
|
|
17411
|
+
for (const n of relevant) {
|
|
17412
|
+
const key = `${n.reason}`;
|
|
17413
|
+
const list = byReason.get(key) ?? [];
|
|
17414
|
+
list.push(n.path);
|
|
17415
|
+
byReason.set(key, list);
|
|
17416
|
+
}
|
|
17417
|
+
const lines = [];
|
|
17418
|
+
for (const [reason, paths] of [...byReason.entries()].sort()) {
|
|
17419
|
+
const shown = paths.slice(0, maxPaths).join(", ");
|
|
17420
|
+
const more = paths.length > maxPaths ? ` (+${paths.length - maxPaths} more)` : "";
|
|
17421
|
+
lines.push(` ${paths.length} not reviewed \u2014 ${reason}: ${shown}${more}`);
|
|
17422
|
+
}
|
|
17423
|
+
return `NOT A CLEAN REVIEW. ${relevant.length} changed file(s) never reached the reviewer, so this verdict does not cover them:
|
|
17424
|
+
${lines.join("\n")}
|
|
17425
|
+
Treat those files as UNCHECKED, not as approved.`;
|
|
17426
|
+
}
|
|
17427
|
+
function openBlockingElsewhere(statements, reviewedNow, lineShaAt) {
|
|
17428
|
+
const reviewed = new Set(reviewedNow);
|
|
17429
|
+
const out = [];
|
|
17430
|
+
const seen = /* @__PURE__ */ new Set();
|
|
17431
|
+
for (const s of statements) {
|
|
17432
|
+
if (s.outcome !== "open") continue;
|
|
17433
|
+
if (s.register !== "BLOCK") continue;
|
|
17434
|
+
if (s.carried) continue;
|
|
17435
|
+
if (reviewed.has(s.file)) continue;
|
|
17436
|
+
if (!s.line_sha) continue;
|
|
17437
|
+
if (lineShaAt(s.file, s.line) !== s.line_sha) continue;
|
|
17438
|
+
const key = `${s.file}::${s.pattern_id}`;
|
|
17439
|
+
if (seen.has(key)) continue;
|
|
17440
|
+
seen.add(key);
|
|
17441
|
+
out.push({ file: s.file, line: s.line, pattern_id: s.pattern_id });
|
|
17442
|
+
}
|
|
17443
|
+
return out;
|
|
17444
|
+
}
|
|
17445
|
+
function describeOpenElsewhere(open) {
|
|
17446
|
+
if (open.length === 0) return null;
|
|
17447
|
+
const lines = open.slice(0, 5).map((o) => ` ${o.file}:${o.line} [${o.pattern_id}]`);
|
|
17448
|
+
const more = open.length > 5 ? `
|
|
17449
|
+
(+${open.length - 5} more)` : "";
|
|
17450
|
+
return `STILL OPEN ELSEWHERE. ${open.length} blocking finding(s) Verity raised earlier are still present in files this run did not review:
|
|
17451
|
+
${lines.join("\n")}${more}
|
|
17452
|
+
This verdict covers the current change only. The tree is not clean.`;
|
|
17453
|
+
}
|
|
17454
|
+
|
|
17159
17455
|
// src/lib/channel.ts
|
|
17160
17456
|
var MAX_AGENT_CONTEXT_CHARS = 1500;
|
|
17161
17457
|
var MAX_AGENT_ITEMS = 5;
|
|
@@ -17205,9 +17501,13 @@ function buildAgentContext(input) {
|
|
|
17205
17501
|
}
|
|
17206
17502
|
for (const p of input.pendingItems ?? []) {
|
|
17207
17503
|
if (lines.length >= MAX_AGENT_ITEMS) break;
|
|
17504
|
+
if (p.pattern_id === "intent-misalignment") continue;
|
|
17208
17505
|
const text = p.description ?? p.title ?? p.reason;
|
|
17209
17506
|
if (!text) continue;
|
|
17210
|
-
|
|
17507
|
+
const seenBefore = isRepeatOfAny(text, input.priorPendingFingerprints ?? []);
|
|
17508
|
+
lines.push(
|
|
17509
|
+
renderItem("", text, p.pattern_id, p.file, p.line) + (seenBefore ? "\n (raised earlier this session and still open \u2014 do not re-explain it; act on it or carry on)" : "")
|
|
17510
|
+
);
|
|
17211
17511
|
}
|
|
17212
17512
|
if (lines.length === 0) return null;
|
|
17213
17513
|
const body = `${REPORT_PREFIX}
|
|
@@ -17243,6 +17543,39 @@ function channelSilence(input) {
|
|
|
17243
17543
|
return null;
|
|
17244
17544
|
}
|
|
17245
17545
|
|
|
17546
|
+
// src/lib/emit.ts
|
|
17547
|
+
var YELLOW2 = "\x1B[33m";
|
|
17548
|
+
var NC2 = "\x1B[0m";
|
|
17549
|
+
function emitVerdict(input) {
|
|
17550
|
+
const exit = input.exit ?? ((code) => process.exit(code));
|
|
17551
|
+
const { coverage, unaccounted } = reconcileCoverage(input.changed, input.coverage);
|
|
17552
|
+
let verdict = resolveVerdict(input.proposed, coverage);
|
|
17553
|
+
const openElsewhere = input.openElsewhere ?? [];
|
|
17554
|
+
if (verdict === "PASS" && openElsewhere.length > 0) verdict = "WARN";
|
|
17555
|
+
const note = [describeCoverage(coverage), describeOpenElsewhere(openElsewhere)].filter(Boolean).join("\n\n") || null;
|
|
17556
|
+
if (unaccounted.length > 0) {
|
|
17557
|
+
process.stderr.write(
|
|
17558
|
+
`${YELLOW2}Verity: ${unaccounted.length} changed file(s) could not be attributed to any review stage \u2014 counted as unreviewed.${NC2}
|
|
17559
|
+
`
|
|
17560
|
+
);
|
|
17561
|
+
}
|
|
17562
|
+
if (verdict === "FAIL") {
|
|
17563
|
+
input.renderBlocking?.();
|
|
17564
|
+
if (input.agentContext) {
|
|
17565
|
+
process.stderr.write(`
|
|
17566
|
+
${input.agentContext}
|
|
17567
|
+
`);
|
|
17568
|
+
}
|
|
17569
|
+
if (note && !input.silenced) process.stderr.write(`
|
|
17570
|
+
${YELLOW2}${note}${NC2}
|
|
17571
|
+
`);
|
|
17572
|
+
return exit(2);
|
|
17573
|
+
}
|
|
17574
|
+
const agentBlock = input.silenced ? null : [input.agentContext, note].filter(Boolean).join("\n\n") || null;
|
|
17575
|
+
printJsonCompact(buildHookOutput(verdict, input.userSummary, agentBlock));
|
|
17576
|
+
return exit(0);
|
|
17577
|
+
}
|
|
17578
|
+
|
|
17246
17579
|
// src/lib/cache-cleanup.ts
|
|
17247
17580
|
var import_node_fs21 = require("node:fs");
|
|
17248
17581
|
var import_node_path18 = require("node:path");
|
|
@@ -17324,6 +17657,13 @@ function isGitOnlyPrompt(prompt) {
|
|
|
17324
17657
|
return true;
|
|
17325
17658
|
}
|
|
17326
17659
|
function reconcileAnalysisMode(predictedMode, signals) {
|
|
17660
|
+
const mode = resolveAnalysisMode(predictedMode, signals);
|
|
17661
|
+
if (mode !== "skip") return mode;
|
|
17662
|
+
const windowIsOrphaned = signals.actionSummary?.transcript_windowed === "orphaned";
|
|
17663
|
+
if (windowIsOrphaned && !signals.sessionAuthoredCode) return "standard";
|
|
17664
|
+
return mode;
|
|
17665
|
+
}
|
|
17666
|
+
function resolveAnalysisMode(predictedMode, signals) {
|
|
17327
17667
|
if (!predictedMode || !isValidMode(predictedMode)) {
|
|
17328
17668
|
return detectAnalysisMode(
|
|
17329
17669
|
signals.noFilesChanged,
|
|
@@ -17422,46 +17762,6 @@ function shouldWarmRetryAnalyze(result) {
|
|
|
17422
17762
|
return false;
|
|
17423
17763
|
}
|
|
17424
17764
|
|
|
17425
|
-
// src/lib/skip-detection.ts
|
|
17426
|
-
function isBareAckPrompt(prompt) {
|
|
17427
|
-
if (typeof prompt !== "string") return false;
|
|
17428
|
-
const trimmed = prompt.trim();
|
|
17429
|
-
if (trimmed.length === 0) return false;
|
|
17430
|
-
if (trimmed.length > 20) return false;
|
|
17431
|
-
const bareAckPattern = /^(\d{1,2}|y|n|yes|no|yep|nope|ok(ay)?|sure|skip|cancel|stop|done|noted|got\s+it|sounds\s+good|thanks|thank\s+you|thx)[.!?]*$/i;
|
|
17432
|
-
return bareAckPattern.test(trimmed);
|
|
17433
|
-
}
|
|
17434
|
-
function isReflectionQuestion(response) {
|
|
17435
|
-
if (!response || typeof response !== "string") return false;
|
|
17436
|
-
const markers = [
|
|
17437
|
-
/reflection\s+for\s+future\s+agents/i,
|
|
17438
|
-
/what(?:'s|\s+is)\s+one\s+thing\s+you\s+learned/i,
|
|
17439
|
-
/say\s+['"]?skip['"]?\s+to\s+skip/i,
|
|
17440
|
-
/quick\s+reflection\s+question/i,
|
|
17441
|
-
// Post-flip (VRT-21): the agent drafts the reflection itself and, when
|
|
17442
|
-
// interactive, asks the user to confirm/correct before recording. That
|
|
17443
|
-
// turn authors no code either, so it's still a reflection turn.
|
|
17444
|
-
/reflection\s+draft/i,
|
|
17445
|
-
/confirm,?\s+correct,?\s+or\s+add/i
|
|
17446
|
-
];
|
|
17447
|
-
return markers.some((m) => m.test(response));
|
|
17448
|
-
}
|
|
17449
|
-
function isMetaTaskLabel(label2) {
|
|
17450
|
-
if (label2 === null || label2 === void 0) return false;
|
|
17451
|
-
if (typeof label2 !== "string") return false;
|
|
17452
|
-
const trimmed = label2.trim();
|
|
17453
|
-
if (trimmed.length === 0) return true;
|
|
17454
|
-
const metaPatterns = [
|
|
17455
|
-
/^verity\s+[\w-]+\s+response$/i,
|
|
17456
|
-
// "Verity reflect response"
|
|
17457
|
-
/^simple user response$/i,
|
|
17458
|
-
/^verity\s+command$/i,
|
|
17459
|
-
// "Verity command"
|
|
17460
|
-
/^user\s+(question|reply|response|ack)$/i
|
|
17461
|
-
];
|
|
17462
|
-
return metaPatterns.some((p) => p.test(trimmed));
|
|
17463
|
-
}
|
|
17464
|
-
|
|
17465
17765
|
// src/lib/transcript.ts
|
|
17466
17766
|
var import_node_fs22 = require("node:fs");
|
|
17467
17767
|
var MAX_READ_BYTES = 256 * 1024;
|
|
@@ -17475,9 +17775,11 @@ var MAX_SUMMARY_BYTES = 4096;
|
|
|
17475
17775
|
var HOME = process.env.HOME ?? "";
|
|
17476
17776
|
async function extractActionSummary(transcriptPath) {
|
|
17477
17777
|
try {
|
|
17478
|
-
const
|
|
17479
|
-
if (!
|
|
17480
|
-
|
|
17778
|
+
const read = readTurnLines(transcriptPath);
|
|
17779
|
+
if (!read || read.lines.length === 0) return null;
|
|
17780
|
+
const summary = buildSummary(read.lines);
|
|
17781
|
+
if (summary) summary.transcript_windowed = read.window;
|
|
17782
|
+
return summary;
|
|
17481
17783
|
} catch {
|
|
17482
17784
|
return null;
|
|
17483
17785
|
}
|
|
@@ -17491,9 +17793,11 @@ function readTurnLines(transcriptPath) {
|
|
|
17491
17793
|
}
|
|
17492
17794
|
if (size === 0) return null;
|
|
17493
17795
|
let raw;
|
|
17796
|
+
let windowed = false;
|
|
17494
17797
|
if (size <= SMALL_FILE_BYTES) {
|
|
17495
17798
|
raw = (0, import_node_fs22.readFileSync)(transcriptPath, "utf-8");
|
|
17496
17799
|
} else {
|
|
17800
|
+
windowed = true;
|
|
17497
17801
|
const buf = Buffer.alloc(Math.min(MAX_READ_BYTES, size));
|
|
17498
17802
|
const fd = require("node:fs").openSync(transcriptPath, "r");
|
|
17499
17803
|
try {
|
|
@@ -17511,17 +17815,22 @@ function readTurnLines(transcriptPath) {
|
|
|
17511
17815
|
const allLines = raw.split("\n").filter((l) => l.trim().length > 0);
|
|
17512
17816
|
if (allLines.length === 0) return null;
|
|
17513
17817
|
let turnStart = 0;
|
|
17818
|
+
let boundaryFound = false;
|
|
17514
17819
|
for (let i = allLines.length - 1; i >= 0; i--) {
|
|
17515
17820
|
try {
|
|
17516
17821
|
const parsed = JSON.parse(allLines[i]);
|
|
17517
17822
|
if (parsed.type === "user" && isRealUserMessage(parsed)) {
|
|
17518
17823
|
turnStart = i;
|
|
17824
|
+
boundaryFound = true;
|
|
17519
17825
|
break;
|
|
17520
17826
|
}
|
|
17521
17827
|
} catch {
|
|
17522
17828
|
}
|
|
17523
17829
|
}
|
|
17524
|
-
return
|
|
17830
|
+
return {
|
|
17831
|
+
lines: allLines.slice(turnStart),
|
|
17832
|
+
window: !windowed ? "whole" : boundaryFound ? "windowed" : "orphaned"
|
|
17833
|
+
};
|
|
17525
17834
|
}
|
|
17526
17835
|
function isRealUserMessage(parsed) {
|
|
17527
17836
|
const message = parsed.message;
|
|
@@ -17625,6 +17934,13 @@ function buildSummary(lines) {
|
|
|
17625
17934
|
files_read: capArray(filesRead, MAX_FILES_LIST),
|
|
17626
17935
|
files_edited: capArray(filesEdited, MAX_FILES_LIST),
|
|
17627
17936
|
files_created: capArray(filesCreated, MAX_CREATED_LIST),
|
|
17937
|
+
// The complement of the two caps that affect SCOPE. `files_read` is excluded
|
|
17938
|
+
// deliberately: reading a file is not authoring it, so a capped read list
|
|
17939
|
+
// narrows nothing.
|
|
17940
|
+
capped_out: [
|
|
17941
|
+
...cappedOut(filesEdited, MAX_FILES_LIST),
|
|
17942
|
+
...cappedOut(filesCreated, MAX_CREATED_LIST)
|
|
17943
|
+
],
|
|
17628
17944
|
searches,
|
|
17629
17945
|
commands,
|
|
17630
17946
|
subagents,
|
|
@@ -17675,6 +17991,9 @@ function sanitizeCommand(rawCmd) {
|
|
|
17675
17991
|
function capArray(set, max) {
|
|
17676
17992
|
return Array.from(set).slice(0, max);
|
|
17677
17993
|
}
|
|
17994
|
+
function cappedOut(set, max) {
|
|
17995
|
+
return Array.from(set).slice(max);
|
|
17996
|
+
}
|
|
17678
17997
|
|
|
17679
17998
|
// src/lib/run-mode.ts
|
|
17680
17999
|
function parseAutonomousEnv(raw) {
|
|
@@ -18069,11 +18388,12 @@ async function readStopHookStdin() {
|
|
|
18069
18388
|
return empty;
|
|
18070
18389
|
}
|
|
18071
18390
|
}
|
|
18072
|
-
function agentContextFor(response, intentRepeat = 0) {
|
|
18391
|
+
function agentContextFor(response, intentRepeat = 0, priorPendingFingerprints = []) {
|
|
18073
18392
|
const metadata = response.metadata ?? {};
|
|
18074
18393
|
const intent = response.intent_alignment ?? {};
|
|
18075
18394
|
return buildAgentContext({
|
|
18076
18395
|
intentRepeat,
|
|
18396
|
+
priorPendingFingerprints,
|
|
18077
18397
|
gateDecision: String(response.gate_decision ?? ""),
|
|
18078
18398
|
findings: response.findings ?? [],
|
|
18079
18399
|
pendingItems: response.pending_items ?? [],
|
|
@@ -18084,12 +18404,45 @@ function agentContextFor(response, intentRepeat = 0) {
|
|
|
18084
18404
|
});
|
|
18085
18405
|
}
|
|
18086
18406
|
var beaconCtx = null;
|
|
18087
|
-
async function passAndExit(reason, skip) {
|
|
18407
|
+
async function passAndExit(reason, skip, kindOverride) {
|
|
18088
18408
|
const sent = await sendSkipBeacon(beaconCtx, skip);
|
|
18089
18409
|
logEvent("skip", { reason: skip, beacon: sent });
|
|
18090
|
-
|
|
18410
|
+
const POLICY_SKIPS = /* @__PURE__ */ new Set([
|
|
18411
|
+
"no-analyzable-files",
|
|
18412
|
+
"verity-command",
|
|
18413
|
+
"bare-acknowledgment",
|
|
18414
|
+
"reflection-prompt",
|
|
18415
|
+
"skip-mode",
|
|
18416
|
+
"zero-increment",
|
|
18417
|
+
"debounce",
|
|
18418
|
+
"no-delta-since-last-review"
|
|
18419
|
+
]);
|
|
18420
|
+
const skipKind = kindOverride ?? (POLICY_SKIPS.has(skip) ? "policy" : "capacity");
|
|
18421
|
+
const changed = skipCoverageChanged;
|
|
18422
|
+
const { coverage, unaccounted } = reconcileCoverage(changed, {
|
|
18423
|
+
reviewed: [],
|
|
18424
|
+
notReviewed: changed.map((path) => ({ path, reason: skip, stage: "pre-flight", kind: skipKind }))
|
|
18425
|
+
});
|
|
18426
|
+
const verdict = resolveVerdict("PASS", coverage);
|
|
18427
|
+
const note = describeCoverage(coverage);
|
|
18428
|
+
if (unaccounted.length > 0) {
|
|
18429
|
+
logEvent("coverage_unaccounted", { where: "passAndExit", skip, count: unaccounted.length });
|
|
18430
|
+
}
|
|
18431
|
+
const AGENT_SILENT_SKIPS = /* @__PURE__ */ new Set([]);
|
|
18432
|
+
const agentNote = AGENT_SILENT_SKIPS.has(skip) ? null : note;
|
|
18433
|
+
printJsonCompact(
|
|
18434
|
+
buildHookOutput(
|
|
18435
|
+
verdict,
|
|
18436
|
+
`Verity: ${reason}`,
|
|
18437
|
+
// The agent's ONLY input is additionalContext. Sixteen of the nineteen
|
|
18438
|
+
// terminating paths wrote `systemMessage` — the human's field — and told
|
|
18439
|
+
// the agent nothing at all.
|
|
18440
|
+
agentNote
|
|
18441
|
+
)
|
|
18442
|
+
);
|
|
18091
18443
|
process.exit(0);
|
|
18092
18444
|
}
|
|
18445
|
+
var skipCoverageChanged = [];
|
|
18093
18446
|
var EMPTY_STATIC = {
|
|
18094
18447
|
tool: "@codacy/analysis-cli",
|
|
18095
18448
|
findings: [],
|
|
@@ -18104,7 +18457,7 @@ function runLocalStatic(analyzable, securityFiles, baseline, skipStatic) {
|
|
|
18104
18457
|
}
|
|
18105
18458
|
function localOnlyAndExit(staticResults) {
|
|
18106
18459
|
printJsonCompact({
|
|
18107
|
-
gate_decision: "
|
|
18460
|
+
gate_decision: "WARN",
|
|
18108
18461
|
systemMessage: "Verity: not authenticated \u2014 ran a local static-only check (no deep review, no upload). Run `verity init` to authenticate and enable the full quality gate.",
|
|
18109
18462
|
unauthenticated: true,
|
|
18110
18463
|
static_results: staticResults
|
|
@@ -18164,6 +18517,7 @@ async function runAnalyze(opts, globals) {
|
|
|
18164
18517
|
});
|
|
18165
18518
|
}
|
|
18166
18519
|
const { files: allChanged, hasRecentCommitFiles } = getChangedFiles();
|
|
18520
|
+
skipCoverageChanged = allChanged;
|
|
18167
18521
|
const analyzable = filterAnalyzable(allChanged);
|
|
18168
18522
|
const reviewable = filterReviewable(allChanged);
|
|
18169
18523
|
const securityFiles = filterSecurity(allChanged);
|
|
@@ -18175,15 +18529,27 @@ async function runAnalyze(opts, globals) {
|
|
|
18175
18529
|
const conversation = await readAndClearConversationBuffer(baselineSessionId);
|
|
18176
18530
|
const specs = discoverSpecs();
|
|
18177
18531
|
const plans = discoverPlans();
|
|
18532
|
+
const agentAuthoredCodeThisTurn = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0));
|
|
18533
|
+
const turnAuthoredCode = agentAuthoredCodeThisTurn || !!baseline && allForReview.some((f) => changedSinceBaseline(f, baseline));
|
|
18534
|
+
const authorshipIsObservable = !!actionSummary && actionSummary.transcript_windowed !== "orphaned" || !!baseline;
|
|
18535
|
+
const canSeeTurnAuthorship = authorshipIsObservable;
|
|
18536
|
+
let earlyFold = null;
|
|
18178
18537
|
const latestPrompt = conversation?.prompts?.[conversation.prompts.length - 1]?.prompt ?? "";
|
|
18179
18538
|
if (/^\s*\/verity-/i.test(latestPrompt)) {
|
|
18539
|
+
const setupAuthored = [
|
|
18540
|
+
...actionSummary?.files_edited ?? [],
|
|
18541
|
+
...actionSummary?.files_created ?? []
|
|
18542
|
+
];
|
|
18543
|
+
if (setupAuthored.length > 0) {
|
|
18544
|
+
const adopted = absorbIntoBaseline(setupAuthored, baselineSessionId);
|
|
18545
|
+
logEvent("baseline_absorbed", { skip: "verity-command", offered: setupAuthored.length, adopted });
|
|
18546
|
+
}
|
|
18180
18547
|
await passAndExit("Verity command \u2014 skipping analysis", "verity-command");
|
|
18181
18548
|
}
|
|
18182
|
-
if (
|
|
18549
|
+
if (shouldSkipForBareAck({ prompt: latestPrompt, turnAuthoredCode, canSeeTurnAuthorship })) {
|
|
18183
18550
|
await passAndExit("Bare acknowledgment \u2014 skipping analysis", "bare-acknowledgment");
|
|
18184
18551
|
}
|
|
18185
|
-
|
|
18186
|
-
if (isReflectionQuestion(assistantResponse) && !agentAuthoredCodeThisTurn) {
|
|
18552
|
+
if (isReflectionQuestion(assistantResponse) && !turnAuthoredCode && canSeeTurnAuthorship) {
|
|
18187
18553
|
await passAndExit("Reflection-prompt turn \u2014 skipping analysis", "reflection-prompt");
|
|
18188
18554
|
}
|
|
18189
18555
|
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
@@ -18229,7 +18595,11 @@ async function runAnalyze(opts, globals) {
|
|
|
18229
18595
|
);
|
|
18230
18596
|
}
|
|
18231
18597
|
if (analysisMode === "skip") {
|
|
18232
|
-
await passAndExit(
|
|
18598
|
+
await passAndExit(
|
|
18599
|
+
"Skip mode \u2014 no code work to analyze",
|
|
18600
|
+
"skip-mode",
|
|
18601
|
+
turnAuthoredCode ? "capacity" : void 0
|
|
18602
|
+
);
|
|
18233
18603
|
}
|
|
18234
18604
|
let staticResults = {
|
|
18235
18605
|
tool: "@codacy/analysis-cli",
|
|
@@ -18239,7 +18609,8 @@ async function runAnalyze(opts, globals) {
|
|
|
18239
18609
|
let codeDelta = {
|
|
18240
18610
|
files: [],
|
|
18241
18611
|
total_lines: 0,
|
|
18242
|
-
total_files: 0
|
|
18612
|
+
total_files: 0,
|
|
18613
|
+
excluded: []
|
|
18243
18614
|
};
|
|
18244
18615
|
let snapshotResult = { has_snapshots: false, diffs: [] };
|
|
18245
18616
|
let contentHash = null;
|
|
@@ -18280,10 +18651,43 @@ async function runAnalyze(opts, globals) {
|
|
|
18280
18651
|
contentHash = hashResult.hash;
|
|
18281
18652
|
if (analysisMode !== "plan") {
|
|
18282
18653
|
const scoped = scopeToAuthored(allForReview, actionSummary);
|
|
18283
|
-
|
|
18654
|
+
const canTrustNoneAuthored = scoped.signal === "none-authored" && authorshipIsObservable;
|
|
18655
|
+
if (canTrustNoneAuthored && !hasNonEditAuthorship(actionSummary, sessionAuthoredCode)) {
|
|
18284
18656
|
await passAndExit("No agent-authored code this turn \u2014 working-tree changes were not authored by this session", "zero-increment");
|
|
18285
18657
|
}
|
|
18286
|
-
|
|
18658
|
+
if (scoped.signal === "none-authored" && !authorshipIsObservable) {
|
|
18659
|
+
logEvent("none_authored_unverifiable", {
|
|
18660
|
+
reason: "orphaned_window_no_baseline",
|
|
18661
|
+
would_have_skipped: allForReview.length
|
|
18662
|
+
});
|
|
18663
|
+
}
|
|
18664
|
+
const narrowingIsTrustworthy = scoped.signal === "authored" && scoped.files.length > 0 && actionSummary?.transcript_windowed !== "orphaned";
|
|
18665
|
+
let recoveredScope = [];
|
|
18666
|
+
if (!narrowingIsTrustworthy && transcriptPath) {
|
|
18667
|
+
try {
|
|
18668
|
+
earlyFold = fold(transcriptPath, { changedFiles: allForReview, repoRoot: repoRoot() });
|
|
18669
|
+
const authoredWhole = new Set(earlyFold.authored.map((a) => a.p));
|
|
18670
|
+
if (authoredWhole.size > 0) {
|
|
18671
|
+
const root = repoRoot();
|
|
18672
|
+
recoveredScope = allForReview.filter((f) => authoredWhole.has(toRepoRelative(f, root)));
|
|
18673
|
+
}
|
|
18674
|
+
logEvent("scope_recovered_from_fold", {
|
|
18675
|
+
window_saw: scoped.files.length,
|
|
18676
|
+
fold_saw: authoredWhole.size,
|
|
18677
|
+
recovered: recoveredScope.length,
|
|
18678
|
+
would_have_widened_to: allForReview.length
|
|
18679
|
+
});
|
|
18680
|
+
} catch {
|
|
18681
|
+
earlyFold = null;
|
|
18682
|
+
}
|
|
18683
|
+
}
|
|
18684
|
+
if (!narrowingIsTrustworthy && scoped.signal === "authored" && recoveredScope.length === 0) {
|
|
18685
|
+
logEvent("scope_widened_orphaned_window", {
|
|
18686
|
+
would_have_sent: scoped.files.length,
|
|
18687
|
+
widened_to: allForReview.length
|
|
18688
|
+
});
|
|
18689
|
+
}
|
|
18690
|
+
const baseForReview = narrowingIsTrustworthy ? scoped.files : recoveredScope.length > 0 ? recoveredScope : allForReview;
|
|
18287
18691
|
const recentForReview = narrowToRecent(baseForReview, baselineSessionId);
|
|
18288
18692
|
if (!opts.skipStatic && isCodacyAvailable()) {
|
|
18289
18693
|
let allScannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles]));
|
|
@@ -18304,7 +18708,11 @@ async function runAnalyze(opts, globals) {
|
|
|
18304
18708
|
if (assistantResponse) {
|
|
18305
18709
|
analysisMode = "plan";
|
|
18306
18710
|
} else {
|
|
18307
|
-
await passAndExit(
|
|
18711
|
+
await passAndExit(
|
|
18712
|
+
"No files within size limits to analyze",
|
|
18713
|
+
"size-limit",
|
|
18714
|
+
codeDelta.excluded.length > 0 ? "capacity" : "policy"
|
|
18715
|
+
);
|
|
18308
18716
|
}
|
|
18309
18717
|
}
|
|
18310
18718
|
}
|
|
@@ -18317,19 +18725,13 @@ async function runAnalyze(opts, globals) {
|
|
|
18317
18725
|
snapshotResult = generateSnapshotDiffs(codeDelta.files);
|
|
18318
18726
|
}
|
|
18319
18727
|
currentCommit = getCurrentCommit();
|
|
18320
|
-
|
|
18321
|
-
const iterResult = checkMaxIterations(currentCommit, maxIterations, contentHash ?? void 0);
|
|
18322
|
-
if (iterResult.skip) await passAndExit(iterResult.skip, "iteration-cap");
|
|
18323
|
-
iteration = iterResult.iteration;
|
|
18728
|
+
iteration = readIterationState(currentCommit).iteration;
|
|
18324
18729
|
}
|
|
18325
18730
|
}
|
|
18326
18731
|
if (analysisMode === "plan") {
|
|
18327
18732
|
recordAnalysisStart();
|
|
18328
18733
|
currentCommit = getCurrentCommit();
|
|
18329
|
-
|
|
18330
|
-
const iterResult = checkMaxIterations(currentCommit, maxIterations);
|
|
18331
|
-
if (iterResult.skip) await passAndExit(iterResult.skip, "iteration-cap");
|
|
18332
|
-
iteration = iterResult.iteration;
|
|
18734
|
+
iteration = readIterationState(currentCommit).iteration;
|
|
18333
18735
|
}
|
|
18334
18736
|
const contextFiles = gatherContextFiles(contextFilePaths, codeDelta.files);
|
|
18335
18737
|
for (const f of codeDelta.files) {
|
|
@@ -18396,7 +18798,7 @@ async function runAnalyze(opts, globals) {
|
|
|
18396
18798
|
let foldConservation = null;
|
|
18397
18799
|
if (transcriptPath) {
|
|
18398
18800
|
try {
|
|
18399
|
-
foldResult = fold(transcriptPath, { changedFiles: allForReview, repoRoot: repoRoot() });
|
|
18801
|
+
foldResult = earlyFold ?? fold(transcriptPath, { changedFiles: allForReview, repoRoot: repoRoot() });
|
|
18400
18802
|
foldConservation = checkConservation(allForReview, foldResult, repoRoot());
|
|
18401
18803
|
if (!foldConservation.holds) {
|
|
18402
18804
|
process.stderr.write(
|
|
@@ -18502,7 +18904,30 @@ async function runAnalyze(opts, globals) {
|
|
|
18502
18904
|
hasUserPrompt: (conversation?.prompts?.length ?? 0) > 0,
|
|
18503
18905
|
isTTY: process.stdout.isTTY === true
|
|
18504
18906
|
});
|
|
18907
|
+
const excludedByReason = {};
|
|
18908
|
+
for (const e of codeDelta.excluded ?? []) {
|
|
18909
|
+
excludedByReason[e.reason] = (excludedByReason[e.reason] ?? 0) + 1;
|
|
18910
|
+
}
|
|
18911
|
+
const coverageTelemetry = {
|
|
18912
|
+
// git's whole answer, before ANY narrowing. The number that has never been sent.
|
|
18913
|
+
changed_all: allChanged.length,
|
|
18914
|
+
analyzable: analyzable.length,
|
|
18915
|
+
reviewable: reviewable.length,
|
|
18916
|
+
security: securityFiles.length,
|
|
18917
|
+
// after the allowlist, before authorship scoping and the caps
|
|
18918
|
+
for_review: allForReview.length,
|
|
18919
|
+
// what actually reaches the reviewer
|
|
18920
|
+
sent: codeDelta.files.length,
|
|
18921
|
+
// the two silent narrowings, counted separately so they can be told apart
|
|
18922
|
+
capped_out: actionSummary?.capped_out?.length ?? 0,
|
|
18923
|
+
excluded: (codeDelta.excluded ?? []).length,
|
|
18924
|
+
excluded_by_reason: excludedByReason,
|
|
18925
|
+
// was the transcript itself truncated? The 256 KB window means "this turn"
|
|
18926
|
+
// can quietly mean "the last 256 KB of it".
|
|
18927
|
+
transcript_windowed: actionSummary?.transcript_windowed ?? null
|
|
18928
|
+
};
|
|
18505
18929
|
const requestBody = {
|
|
18930
|
+
coverage_telemetry: coverageTelemetry,
|
|
18506
18931
|
static_results: staticResults,
|
|
18507
18932
|
code_delta: codeDelta,
|
|
18508
18933
|
changed_files: allForReview,
|
|
@@ -18588,6 +19013,54 @@ async function runAnalyze(opts, globals) {
|
|
|
18588
19013
|
// replaced a population floor with ≈35% power that was sub-integer for
|
|
18589
19014
|
// three-quarters of the fleet.
|
|
18590
19015
|
conservation: foldConservation,
|
|
19016
|
+
// ⚠ VRT-52 — RECORDED, NOT APPLIED. The number nobody has.
|
|
19017
|
+
//
|
|
19018
|
+
// The whole "task-scoped delta" design space rests on an assumption that
|
|
19019
|
+
// has been observed exactly ONCE: that delta files routinely belong to
|
|
19020
|
+
// earlier work. Three designs were built on it and all three were killed
|
|
19021
|
+
// adversarially — two by measurement — so before another is attempted,
|
|
19022
|
+
// measure the base rate.
|
|
19023
|
+
//
|
|
19024
|
+
// `authored_under_earlier_goal` counts delta paths whose LAST authorship
|
|
19025
|
+
// event precedes the seq of the goal now in force. Both numbers come from
|
|
19026
|
+
// the same append-only counter (`nextSeq`), so the comparison is exact.
|
|
19027
|
+
//
|
|
19028
|
+
// Keyed on the GOAL, deliberately, not on the task id. The task classifier
|
|
19029
|
+
// reported `is_new_task` on two consecutive turns of one task 25 seconds
|
|
19030
|
+
// apart, so a task-keyed number would measure its unreliability rather
|
|
19031
|
+
// than the phenomenon. And this only became meaningful once `recordGoal`
|
|
19032
|
+
// stopped letting a bare "ok" supersede the goal — before that the seq
|
|
19033
|
+
// advanced every turn and this would have degenerated to "not edited this
|
|
19034
|
+
// turn", which is the exact mistake that sank one of the three designs.
|
|
19035
|
+
//
|
|
19036
|
+
// Changes no payload the reviewer sees, no narrowing, no verdict.
|
|
19037
|
+
vrt52: (() => {
|
|
19038
|
+
const goalSeq = memory?.projection.goal?.seq;
|
|
19039
|
+
if (goalSeq === void 0 || !memorySession) return { known: false };
|
|
19040
|
+
const lastSeq2 = new Map(
|
|
19041
|
+
foldDossier(memorySession.d).authored_all.map((a) => [a.path, a.last_seq])
|
|
19042
|
+
);
|
|
19043
|
+
let earlier = 0;
|
|
19044
|
+
let unknown = 0;
|
|
19045
|
+
for (const f of codeDelta.files) {
|
|
19046
|
+
const seen = lastSeq2.get(f.path);
|
|
19047
|
+
if (seen === void 0) unknown++;
|
|
19048
|
+
else if (seen < goalSeq) earlier++;
|
|
19049
|
+
}
|
|
19050
|
+
return {
|
|
19051
|
+
known: true,
|
|
19052
|
+
goal_seq: goalSeq,
|
|
19053
|
+
delta: codeDelta.files.length,
|
|
19054
|
+
// Files this delta carries that were last written under an EARLIER
|
|
19055
|
+
// instruction. If this stays near zero, VRT-52's code half is
|
|
19056
|
+
// unnecessary and should be closed saying so.
|
|
19057
|
+
authored_under_earlier_goal: earlier,
|
|
19058
|
+
// Delta files the dossier has no authorship record for at all —
|
|
19059
|
+
// pre-existing tree state, or an authorship channel the fold cannot
|
|
19060
|
+
// see. Reported separately so a blind spot is never counted as a zero.
|
|
19061
|
+
no_authorship_record: unknown
|
|
19062
|
+
};
|
|
19063
|
+
})(),
|
|
18591
19064
|
// P2 — RECORDED, NOT APPLIED. What this run WOULD have reviewed if it
|
|
18592
19065
|
// narrowed to the within-session increment: what changed since the last
|
|
18593
19066
|
// VERDICT rather than since task start.
|
|
@@ -18620,7 +19093,20 @@ async function runAnalyze(opts, globals) {
|
|
|
18620
19093
|
const intentContext = {};
|
|
18621
19094
|
if (conversation && conversation.prompts.length > 0) {
|
|
18622
19095
|
const latest = conversation.prompts[conversation.prompts.length - 1];
|
|
18623
|
-
|
|
19096
|
+
const goalPrompt = resolveGoalPrompt(conversation.prompts) ?? { entry: latest, turnsBack: 0 };
|
|
19097
|
+
intentContext.user_prompt = goalPrompt.entry.prompt;
|
|
19098
|
+
if (isContinuationPrompt(intentContext.user_prompt)) {
|
|
19099
|
+
const carried = memory?.projection.goal?.text;
|
|
19100
|
+
if (carried && !isContinuationPrompt(carried)) {
|
|
19101
|
+
intentContext.continuation_prompt = latest.prompt;
|
|
19102
|
+
intentContext.user_prompt = carried;
|
|
19103
|
+
logEvent("goal_from_dossier", { chars: carried.length });
|
|
19104
|
+
}
|
|
19105
|
+
}
|
|
19106
|
+
if (goalPrompt.turnsBack > 0) {
|
|
19107
|
+
intentContext.continuation_prompt = latest.prompt;
|
|
19108
|
+
logEvent("goal_walked_back", { turns_back: goalPrompt.turnsBack });
|
|
19109
|
+
}
|
|
18624
19110
|
intentContext.session_id = latest.session_id || void 0;
|
|
18625
19111
|
intentContext.prompt_captured_at = latest.captured_at || void 0;
|
|
18626
19112
|
if (conversation.prompts.length > 1) {
|
|
@@ -18712,6 +19198,85 @@ async function runAnalyze(opts, globals) {
|
|
|
18712
19198
|
const response = result.data;
|
|
18713
19199
|
const decision = response.gate_decision ?? "(unrecognised)";
|
|
18714
19200
|
const sentPaths = codeDelta.files.map((f) => f.path);
|
|
19201
|
+
let openElsewhere = [];
|
|
19202
|
+
if (memorySession) {
|
|
19203
|
+
try {
|
|
19204
|
+
const st = foldDossier(memorySession.d);
|
|
19205
|
+
openElsewhere = openBlockingElsewhere(st.statements, sentPaths, (file, line) => {
|
|
19206
|
+
try {
|
|
19207
|
+
const src = (0, import_node_fs24.readFileSync)((0, import_node_path20.join)(repoRoot(), file), "utf8").split("\n");
|
|
19208
|
+
const at = src[line - 1];
|
|
19209
|
+
return at === void 0 ? null : lineSha(at);
|
|
19210
|
+
} catch {
|
|
19211
|
+
return null;
|
|
19212
|
+
}
|
|
19213
|
+
});
|
|
19214
|
+
} catch {
|
|
19215
|
+
}
|
|
19216
|
+
}
|
|
19217
|
+
const reviewCoverage = {
|
|
19218
|
+
reviewed: sentPaths,
|
|
19219
|
+
// Declared drops from the stages that DO report themselves today. The other
|
|
19220
|
+
// stages surface via `unaccounted`, which is the tripwire, not the design.
|
|
19221
|
+
notReviewed: [
|
|
19222
|
+
// Every exit from the collection loop, each named. Six reasons where there
|
|
19223
|
+
// used to be two recorded and four silent — the silent ones including the
|
|
19224
|
+
// per-file size cap, which could drop a whole source file without leaving a
|
|
19225
|
+
// trace anywhere in the payload or the run row.
|
|
19226
|
+
...codeDelta.excluded,
|
|
19227
|
+
// The server-side 300-line middle-out truncation. It only bites on the
|
|
19228
|
+
// full-file branch (a first analysis, before snapshots exist) because
|
|
19229
|
+
// analyze normally sends diffs — but on that branch the reviewer sees the
|
|
19230
|
+
// first and last 100 lines and nothing between, and until now said so to
|
|
19231
|
+
// nobody. CAPACITY: a partial look is not a look.
|
|
19232
|
+
...(response.metadata?.truncated_files ?? []).map((path) => ({
|
|
19233
|
+
path,
|
|
19234
|
+
reason: "file-middle-truncated-300-lines",
|
|
19235
|
+
stage: "prompt-builder",
|
|
19236
|
+
kind: "capacity"
|
|
19237
|
+
})),
|
|
19238
|
+
// The 20-entry edit cap. CAPACITY, and the sharpest of the lot: it narrows
|
|
19239
|
+
// what is REVIEWED, not merely what is summarised — a session editing 25
|
|
19240
|
+
// files had five silently excluded from the reviewed set.
|
|
19241
|
+
...(actionSummary?.capped_out ?? []).map((path) => ({
|
|
19242
|
+
path,
|
|
19243
|
+
reason: "edit-list-cap-20",
|
|
19244
|
+
stage: "extractActionSummary",
|
|
19245
|
+
kind: "capacity"
|
|
19246
|
+
})),
|
|
19247
|
+
// ⚠ BASELINE SCOPING — the biggest source of false NOT A CLEAN REVIEW.
|
|
19248
|
+
//
|
|
19249
|
+
// The universe is `allChanged`, git's whole dirty tree. The reviewed set is
|
|
19250
|
+
// scoped to what THIS SESSION authored (the VRT-26 contamination cure), so
|
|
19251
|
+
// every pre-existing dirty file is in the universe, absent from `reviewed`,
|
|
19252
|
+
// and — until now — declared by nobody. It fell through to `unaccounted`,
|
|
19253
|
+
// became capacity, and produced "NOT A CLEAN REVIEW: admin.js" over a file
|
|
19254
|
+
// that was never this session's to review.
|
|
19255
|
+
//
|
|
19256
|
+
// Measured 2026-08-04: three consecutive runs over an untouched tree gave
|
|
19257
|
+
// three different answers — .claude/settings.json, then admin.js, then six
|
|
19258
|
+
// files — because each run took a different path and each path had a
|
|
19259
|
+
// different idea of the universe. POLICY: not this session's work is not a
|
|
19260
|
+
// coverage gap, it is the cure working.
|
|
19261
|
+
...allChanged.filter((p) => !sentPaths.includes(p) && !codeDelta.excluded.some((e) => e.path === p)).filter((p) => analyzable.includes(p) || reviewable.includes(p) || securityFiles.includes(p)).map((path) => ({
|
|
19262
|
+
path,
|
|
19263
|
+
reason: "not-authored-this-session",
|
|
19264
|
+
stage: "baseline-scoping",
|
|
19265
|
+
kind: "policy"
|
|
19266
|
+
})),
|
|
19267
|
+
// The extension allowlist, and it is POLICY rather than capacity: a changed
|
|
19268
|
+
// README was never going to be reviewed, and treating that as a coverage
|
|
19269
|
+
// gap would downgrade nearly every PASS to WARN until WARN meant nothing.
|
|
19270
|
+
// Recorded so the ledger balances and so "what did Verity ignore entirely"
|
|
19271
|
+
// is answerable — but it never touches the verdict.
|
|
19272
|
+
...allChanged.filter((p) => !analyzable.includes(p) && !reviewable.includes(p) && !securityFiles.includes(p)).map((path) => ({
|
|
19273
|
+
path,
|
|
19274
|
+
reason: "not-a-reviewed-file-type",
|
|
19275
|
+
stage: "extension-allowlist",
|
|
19276
|
+
kind: "policy"
|
|
19277
|
+
}))
|
|
19278
|
+
]
|
|
19279
|
+
};
|
|
18715
19280
|
const watermarkHash = sentPaths.length > 0 ? computeContentHash(sentPaths) : contentHash;
|
|
18716
19281
|
const watermarkIsPartial = !!codeDelta.truncated;
|
|
18717
19282
|
let silenced = null;
|
|
@@ -18742,6 +19307,13 @@ async function runAnalyze(opts, globals) {
|
|
|
18742
19307
|
});
|
|
18743
19308
|
}
|
|
18744
19309
|
let intentRepeatCount = 0;
|
|
19310
|
+
const priorPendingFingerprints = memorySession ? (() => {
|
|
19311
|
+
try {
|
|
19312
|
+
return foldDossier(memorySession.d).meta.recent_pending_sigs ?? [];
|
|
19313
|
+
} catch {
|
|
19314
|
+
return [];
|
|
19315
|
+
}
|
|
19316
|
+
})() : [];
|
|
18745
19317
|
if (memorySession) {
|
|
18746
19318
|
try {
|
|
18747
19319
|
recordVerdict(memorySession.d, {
|
|
@@ -18764,7 +19336,10 @@ async function runAnalyze(opts, globals) {
|
|
|
18764
19336
|
// What next turn reads as `emittedLast`. A suppressed turn did not
|
|
18765
19337
|
// speak, so it cannot be the cause of the turn after it — which is what
|
|
18766
19338
|
// keeps this from becoming a permanent gag.
|
|
18767
|
-
emitted: !silenced
|
|
19339
|
+
emitted: !silenced,
|
|
19340
|
+
// Fingerprinted for the NEXT turn's repeat check. Reviewer pending items
|
|
19341
|
+
// carry no `pattern_id`, so their content is the only available key.
|
|
19342
|
+
pendingTexts: (response.pending_items ?? []).map((p) => String(p.description ?? p.title ?? p.reason ?? "")).filter(Boolean)
|
|
18768
19343
|
});
|
|
18769
19344
|
intentRepeatCount = Math.max(0, (foldDossier(memorySession.d).meta.intent_repeat?.consecutive ?? 1) - 1);
|
|
18770
19345
|
} catch {
|
|
@@ -18864,9 +19439,41 @@ async function runAnalyze(opts, globals) {
|
|
|
18864
19439
|
reverify_by: response.reverify_by
|
|
18865
19440
|
});
|
|
18866
19441
|
const grantNudge = grantWarning ? ` \u26A0\uFE0F ${grantWarning}` : "";
|
|
18867
|
-
|
|
19442
|
+
let capReleased = false;
|
|
19443
|
+
let effectiveDecision = decision;
|
|
19444
|
+
if (decision === "FAIL") {
|
|
19445
|
+
const blocking = (response.findings ?? []).filter((f) => {
|
|
19446
|
+
const sev = String(f.severity ?? "").toLowerCase();
|
|
19447
|
+
return sev === "critical" || sev === "high";
|
|
19448
|
+
});
|
|
19449
|
+
const fingerprint = findingsFingerprint(blocking);
|
|
19450
|
+
const prior = readIterationState(currentCommit);
|
|
19451
|
+
const sameProblem = isSameProblem(prior.fingerprint, fingerprint);
|
|
19452
|
+
const nextIteration = sameProblem ? prior.iteration + 1 : 1;
|
|
19453
|
+
const maxIterations = parseInt(opts.maxIterations, 10);
|
|
19454
|
+
writeIteration(nextIteration, currentCommit, contentHash ?? void 0, fingerprint);
|
|
19455
|
+
iteration = nextIteration;
|
|
19456
|
+
if (nextIteration > maxIterations) {
|
|
19457
|
+
capReleased = true;
|
|
19458
|
+
effectiveDecision = "WARN";
|
|
19459
|
+
logEvent("iteration_cap_released", { iteration: nextIteration, fingerprint });
|
|
19460
|
+
}
|
|
19461
|
+
}
|
|
19462
|
+
if (capReleased) {
|
|
19463
|
+
const findings = response.findings ?? [];
|
|
19464
|
+
const lines = findings.slice(0, 5).map((f) => ` [${String(f.severity ?? "?").toUpperCase()}] ${String(f.title ?? f.message ?? "")} (${String(f.file ?? "?")}:${String(f.line ?? "?")})`);
|
|
19465
|
+
emitVerdict({
|
|
19466
|
+
proposed: "WARN",
|
|
19467
|
+
changed: skipCoverageChanged,
|
|
19468
|
+
coverage: reviewCoverage,
|
|
19469
|
+
userSummary: `Verity: WARN \u2014 self-healing limit (${opts.maxIterations}) reached on the same finding. NO LONGER BLOCKING, but ${findings.length} finding(s) remain OPEN and were NOT fixed. Human review required before deploying.
|
|
19470
|
+
${lines.join("\n")}`,
|
|
19471
|
+
agentContext: null,
|
|
19472
|
+
silenced: true
|
|
19473
|
+
});
|
|
19474
|
+
}
|
|
19475
|
+
switch (effectiveDecision) {
|
|
18868
19476
|
case "FAIL": {
|
|
18869
|
-
writeIteration(iteration + 1, currentCommit, contentHash ?? void 0);
|
|
18870
19477
|
const assessment = response.assessment;
|
|
18871
19478
|
const narrative = assessment?.narrative ?? "";
|
|
18872
19479
|
const findings = response.findings ?? [];
|
|
@@ -18943,7 +19550,19 @@ ${YELLOW}${loginNudge.trim()}${NC}
|
|
|
18943
19550
|
if (grantNudge) process.stderr.write(`
|
|
18944
19551
|
${YELLOW}${grantNudge.trim()}${NC}
|
|
18945
19552
|
`);
|
|
18946
|
-
|
|
19553
|
+
emitVerdict({
|
|
19554
|
+
proposed: "FAIL",
|
|
19555
|
+
changed: skipCoverageChanged,
|
|
19556
|
+
coverage: reviewCoverage,
|
|
19557
|
+
userSummary: "",
|
|
19558
|
+
// Subject to the SAME cycle cut as PASS/WARN. Suppressing here is safe:
|
|
19559
|
+
// the findings themselves are rendered above by the blocking renderer,
|
|
19560
|
+
// so what the cut removes is the repeated commentary, never the defect.
|
|
19561
|
+
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount, priorPendingFingerprints),
|
|
19562
|
+
// The coverage note is silenced with it — half a channel is still a channel.
|
|
19563
|
+
silenced: !!silenced,
|
|
19564
|
+
openElsewhere
|
|
19565
|
+
});
|
|
18947
19566
|
break;
|
|
18948
19567
|
}
|
|
18949
19568
|
case "PASS": {
|
|
@@ -18955,10 +19574,16 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
18955
19574
|
if (viewUrl) userSummary += ` Report: ${viewUrl}`;
|
|
18956
19575
|
if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
|
|
18957
19576
|
userSummary += loginNudge + grantNudge;
|
|
18958
|
-
|
|
18959
|
-
|
|
18960
|
-
|
|
18961
|
-
|
|
19577
|
+
emitVerdict({
|
|
19578
|
+
proposed: "PASS",
|
|
19579
|
+
changed: skipCoverageChanged,
|
|
19580
|
+
coverage: reviewCoverage,
|
|
19581
|
+
userSummary,
|
|
19582
|
+
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount, priorPendingFingerprints),
|
|
19583
|
+
// The coverage note is silenced with it — half a channel is still a channel.
|
|
19584
|
+
silenced: !!silenced,
|
|
19585
|
+
openElsewhere
|
|
19586
|
+
});
|
|
18962
19587
|
break;
|
|
18963
19588
|
}
|
|
18964
19589
|
case "WARN": {
|
|
@@ -18969,10 +19594,16 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
18969
19594
|
if (viewUrl) userSummary += ` Report: ${viewUrl}`;
|
|
18970
19595
|
if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
|
|
18971
19596
|
userSummary += loginNudge + grantNudge;
|
|
18972
|
-
|
|
18973
|
-
|
|
18974
|
-
|
|
18975
|
-
|
|
19597
|
+
emitVerdict({
|
|
19598
|
+
proposed: "WARN",
|
|
19599
|
+
changed: skipCoverageChanged,
|
|
19600
|
+
coverage: reviewCoverage,
|
|
19601
|
+
userSummary,
|
|
19602
|
+
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount, priorPendingFingerprints),
|
|
19603
|
+
// The coverage note is silenced with it — half a channel is still a channel.
|
|
19604
|
+
silenced: !!silenced,
|
|
19605
|
+
openElsewhere
|
|
19606
|
+
});
|
|
18976
19607
|
break;
|
|
18977
19608
|
}
|
|
18978
19609
|
default: {
|
|
@@ -19085,8 +19716,8 @@ async function runReview(opts, globals) {
|
|
|
19085
19716
|
for (const p of specPaths) {
|
|
19086
19717
|
if (!(0, import_node_fs26.existsSync)(p)) continue;
|
|
19087
19718
|
try {
|
|
19088
|
-
const { readFileSync:
|
|
19089
|
-
const content =
|
|
19719
|
+
const { readFileSync: readFileSync16 } = await import("node:fs");
|
|
19720
|
+
const content = readFileSync16(p, "utf-8");
|
|
19090
19721
|
specs.push({ path: p, content: content.slice(0, 10240) });
|
|
19091
19722
|
} catch {
|
|
19092
19723
|
}
|
|
@@ -19889,6 +20520,13 @@ async function confirmExistingLogin(serviceUrl, remote, opts) {
|
|
|
19889
20520
|
return "handled";
|
|
19890
20521
|
}
|
|
19891
20522
|
if (!who.ok) {
|
|
20523
|
+
const denial = authDenialRemedy(who.error);
|
|
20524
|
+
if (denial) {
|
|
20525
|
+
console.log("");
|
|
20526
|
+
printWarn(`Your existing Verity credential was rejected (${denial.code}).`);
|
|
20527
|
+
printInfo(` ${denial.remedy}`);
|
|
20528
|
+
return "drive-login";
|
|
20529
|
+
}
|
|
19892
20530
|
if (existing.data.userId != null) {
|
|
19893
20531
|
printInfo(`Logged in as ${existing.data.email ?? `user #${existing.data.userId}`} (cached \u2014 could not reach the Verity service). \u2713`);
|
|
19894
20532
|
} else {
|
|
@@ -19904,15 +20542,14 @@ async function confirmExistingLogin(serviceUrl, remote, opts) {
|
|
|
19904
20542
|
return "drive-login";
|
|
19905
20543
|
}
|
|
19906
20544
|
async function runOptionalAuth(resolution, opts = {}) {
|
|
19907
|
-
|
|
19908
|
-
|
|
19909
|
-
|
|
19910
|
-
|
|
19911
|
-
|
|
19912
|
-
|
|
19913
|
-
|
|
19914
|
-
|
|
19915
|
-
}
|
|
20545
|
+
if (resolution.source === "default") {
|
|
20546
|
+
printInfo(`No Verity service configured on this machine \u2014 using the default: ${resolution.url}`);
|
|
20547
|
+
}
|
|
20548
|
+
const heal = await maybeHealServiceUrl(resolution, opts.verbose);
|
|
20549
|
+
const serviceUrl = heal.serviceUrl;
|
|
20550
|
+
const healed = heal.healed;
|
|
20551
|
+
if (healed) {
|
|
20552
|
+
printInfo(" Log in below to re-register this project and repair ~/.verity/credentials.");
|
|
19916
20553
|
}
|
|
19917
20554
|
let remote = "";
|
|
19918
20555
|
try {
|
|
@@ -19930,8 +20567,10 @@ async function runOptionalAuth(resolution, opts = {}) {
|
|
|
19930
20567
|
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
19931
20568
|
console.log("");
|
|
19932
20569
|
console.log(" Signing in is optional. What it does:");
|
|
19933
|
-
console.log(" - Confirms you
|
|
19934
|
-
console.log("
|
|
20570
|
+
console.log(" - Confirms which repositories you can write to. The GitHub token is");
|
|
20571
|
+
console.log(" used once for that check, then discarded \u2014 Verity never stores it.");
|
|
20572
|
+
console.log(" - One login covers every repository you can write to \u2014 other repos");
|
|
20573
|
+
console.log(" need no further sign-in on this machine.");
|
|
19935
20574
|
console.log(" - It does NOT give Verity access to your code. Code checked by the gate");
|
|
19936
20575
|
console.log(" is analyzed in memory and discarded \u2014 we never store your code.");
|
|
19937
20576
|
console.log(" - It is required to store and access run history for this repo");
|
|
@@ -19946,17 +20585,10 @@ async function runOptionalAuth(resolution, opts = {}) {
|
|
|
19946
20585
|
localOnlyNote();
|
|
19947
20586
|
return;
|
|
19948
20587
|
}
|
|
19949
|
-
if (!remote) {
|
|
19950
|
-
printWarn("No git remote found \u2014 cannot authenticate yet.");
|
|
19951
|
-
localOnlyNote();
|
|
19952
|
-
return;
|
|
19953
|
-
}
|
|
19954
|
-
const projectName = parseRemote(remote)?.repo ?? (0, import_node_path23.basename)(process.cwd());
|
|
19955
20588
|
printInfo("Authenticating with GitHub\u2026");
|
|
19956
|
-
const result = await
|
|
20589
|
+
const result = await loginOnce({ serviceUrl, remote: remote || void 0, verbose: opts.verbose });
|
|
19957
20590
|
if (result.ok) {
|
|
19958
|
-
|
|
19959
|
-
printInfo(`Logged in${who ? ` as ${who}` : ""} \u2713 \u2014 runs, history, and cloud memory now sync to Verity.`);
|
|
20591
|
+
await reportLoginOutcome(result.data, { remote: remote || void 0, verbose: opts.verbose });
|
|
19960
20592
|
} else {
|
|
19961
20593
|
printWarn(`Authentication did not complete: ${result.error}`);
|
|
19962
20594
|
localOnlyNote();
|
|
@@ -20107,8 +20739,8 @@ function registerInitCommand(program2) {
|
|
|
20107
20739
|
console.log("");
|
|
20108
20740
|
try {
|
|
20109
20741
|
const globals = program2.opts();
|
|
20110
|
-
const
|
|
20111
|
-
await runOptionalAuth(
|
|
20742
|
+
const resolution = await resolveServiceUrlForAuth(globals.serviceUrl);
|
|
20743
|
+
await runOptionalAuth(resolution, {
|
|
20112
20744
|
token: globals.token,
|
|
20113
20745
|
verbose: globals.verbose
|
|
20114
20746
|
});
|
|
@@ -20772,7 +21404,7 @@ function registerTelemetryCommands(program2) {
|
|
|
20772
21404
|
}
|
|
20773
21405
|
|
|
20774
21406
|
// src/cli.ts
|
|
20775
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.28.1-experimental.
|
|
21407
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.28.1-experimental.10cb8ce").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async () => {
|
|
20776
21408
|
try {
|
|
20777
21409
|
await foldLegacyLocalCredential();
|
|
20778
21410
|
} catch {
|