@codacy/verity-cli 0.28.1-experimental.c2dc717 → 0.28.1-experimental.db64e52
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/LICENSE +156 -0
- package/bin/verity.js +603 -171
- package/package.json +4 -2
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
|
});
|
|
@@ -11016,7 +11015,54 @@ function listTrackedFiles() {
|
|
|
11016
11015
|
return Array.from(set);
|
|
11017
11016
|
}
|
|
11018
11017
|
function sanitizeRemote(remote) {
|
|
11019
|
-
|
|
11018
|
+
const withoutUserinfo = remote.replace(/^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)[^/]*@/, "$1");
|
|
11019
|
+
return /[\u0000-\u001f\u007f]/.test(withoutUserinfo) ? "" : withoutUserinfo;
|
|
11020
|
+
}
|
|
11021
|
+
|
|
11022
|
+
// src/lib/token-pin.ts
|
|
11023
|
+
var cached;
|
|
11024
|
+
var warned = false;
|
|
11025
|
+
function warnOnce(message, warn) {
|
|
11026
|
+
if (!warned) {
|
|
11027
|
+
warned = true;
|
|
11028
|
+
warn(message);
|
|
11029
|
+
}
|
|
11030
|
+
return message;
|
|
11031
|
+
}
|
|
11032
|
+
async function storedCredential() {
|
|
11033
|
+
if (cached !== void 0) return cached;
|
|
11034
|
+
const rec = await readGlobalCredential(currentRemote());
|
|
11035
|
+
cached = rec ? { token: rec.token, serviceUrl: rec.serviceUrl } : null;
|
|
11036
|
+
return cached;
|
|
11037
|
+
}
|
|
11038
|
+
var userNamedUrl = null;
|
|
11039
|
+
function setUserNamedServiceUrl(url) {
|
|
11040
|
+
userNamedUrl = url?.trim() || null;
|
|
11041
|
+
}
|
|
11042
|
+
async function checkTokenPin(token, targetUrl) {
|
|
11043
|
+
if (process.env.VERITY_TOKEN && token === process.env.VERITY_TOKEN) return { attach: true };
|
|
11044
|
+
const envUrl = process.env.VERITY_SERVICE_URL?.trim();
|
|
11045
|
+
if (envUrl && normalizeUrl(envUrl) === normalizeUrl(targetUrl)) return { attach: true };
|
|
11046
|
+
if (userNamedUrl && normalizeUrl(userNamedUrl) === normalizeUrl(targetUrl)) return { attach: true };
|
|
11047
|
+
const stored = await storedCredential();
|
|
11048
|
+
if (!stored || stored.token !== token) return { attach: true };
|
|
11049
|
+
if (!stored.serviceUrl) return { attach: false, reason: "unpinnable" };
|
|
11050
|
+
return normalizeUrl(stored.serviceUrl) === normalizeUrl(targetUrl) ? { attach: true } : { attach: false, reason: "mismatch", mintedFor: stored.serviceUrl };
|
|
11051
|
+
}
|
|
11052
|
+
function normalizeUrl(raw) {
|
|
11053
|
+
const trimmed = raw.trim().replace(/\/+$/, "");
|
|
11054
|
+
try {
|
|
11055
|
+
const u = new URL(trimmed);
|
|
11056
|
+
return `${u.protocol}//${u.host.toLowerCase()}${u.pathname.replace(/\/+$/, "")}`;
|
|
11057
|
+
} catch {
|
|
11058
|
+
return trimmed.toLowerCase();
|
|
11059
|
+
}
|
|
11060
|
+
}
|
|
11061
|
+
function pinRefusalMessage(verdict, targetUrl) {
|
|
11062
|
+
if (verdict.reason === "unpinnable") {
|
|
11063
|
+
return `Refusing to send your Verity login to ${targetUrl}: this machine's credential does not record which service issued it, so it cannot be verified. Run "verity login" to re-issue it.`;
|
|
11064
|
+
}
|
|
11065
|
+
return `Refusing to send your Verity login to ${targetUrl}: it was issued by ${verdict.mintedFor}. A repository cannot redirect your credential to another service. If this service is genuinely yours, log in against it explicitly: VERITY_SERVICE_URL=${targetUrl} verity login`;
|
|
11020
11066
|
}
|
|
11021
11067
|
|
|
11022
11068
|
// src/lib/api-client.ts
|
|
@@ -11053,6 +11099,24 @@ async function apiRequest(options) {
|
|
|
11053
11099
|
"Content-Type": "application/json"
|
|
11054
11100
|
};
|
|
11055
11101
|
if (token) {
|
|
11102
|
+
const pin = await checkTokenPin(token, serviceUrl);
|
|
11103
|
+
if (!pin.attach) {
|
|
11104
|
+
const message = warnOnce(pinRefusalMessage(pin, serviceUrl), printWarn);
|
|
11105
|
+
logHttpCall({
|
|
11106
|
+
cmd,
|
|
11107
|
+
method,
|
|
11108
|
+
url,
|
|
11109
|
+
duration_ms: 0,
|
|
11110
|
+
http_status: null,
|
|
11111
|
+
// 'network' rather than a new category on purpose: no request left the
|
|
11112
|
+
// machine, and every caller's offline path is exactly the handling a
|
|
11113
|
+
// refusal wants (fall back to local, never fabricate a verdict).
|
|
11114
|
+
category: "network",
|
|
11115
|
+
error: "token_pin_refused",
|
|
11116
|
+
retry
|
|
11117
|
+
});
|
|
11118
|
+
return { ok: false, error: `TOKEN_PIN_REFUSED: ${message}`, category: "network" };
|
|
11119
|
+
}
|
|
11056
11120
|
headers["Authorization"] = `Bearer ${token}`;
|
|
11057
11121
|
}
|
|
11058
11122
|
const remote = requestRemote();
|
|
@@ -11165,14 +11229,9 @@ async function serviceUrlFromCredentials() {
|
|
|
11165
11229
|
async function serviceUrlFromVerityMd() {
|
|
11166
11230
|
try {
|
|
11167
11231
|
const content = await (0, import_promises2.readFile)(projectPath(VERITY_MD_FILE), "utf-8");
|
|
11168
|
-
const
|
|
11169
|
-
if (
|
|
11170
|
-
const urlMatch =
|
|
11171
|
-
if (urlMatch) return urlMatch[0];
|
|
11172
|
-
}
|
|
11173
|
-
const plainLine = content.split("\n").find((l) => /(?:url|service)\s*:/i.test(l));
|
|
11174
|
-
if (plainLine) {
|
|
11175
|
-
const urlMatch = plainLine.match(/https:\/\/[^\s]+/);
|
|
11232
|
+
const line = content.split("\n").find((l) => /\*{0,2}(?:url|service)\*{0,2}\s*:/i.test(l));
|
|
11233
|
+
if (line) {
|
|
11234
|
+
const urlMatch = line.match(/https:\/\/[^\s]+/);
|
|
11176
11235
|
if (urlMatch) return urlMatch[0];
|
|
11177
11236
|
}
|
|
11178
11237
|
} catch {
|
|
@@ -11195,7 +11254,15 @@ async function resolveServiceUrlDetailed(flagUrl) {
|
|
|
11195
11254
|
if (mdUrl) {
|
|
11196
11255
|
return { ok: true, data: { url: mdUrl, source: "verity_md" } };
|
|
11197
11256
|
}
|
|
11198
|
-
return {
|
|
11257
|
+
return {
|
|
11258
|
+
ok: false,
|
|
11259
|
+
error: 'No Verity service URL found. Run "verity login" to get started, or /verity-setup to configure this project.'
|
|
11260
|
+
};
|
|
11261
|
+
}
|
|
11262
|
+
async function resolveServiceUrlForAuth(flagUrl) {
|
|
11263
|
+
const strict = await resolveServiceUrlDetailed(flagUrl);
|
|
11264
|
+
if (strict.ok) return strict.data;
|
|
11265
|
+
return { url: DEFAULT_SERVICE_URL, source: "default" };
|
|
11199
11266
|
}
|
|
11200
11267
|
async function resolveServiceUrl(flagUrl) {
|
|
11201
11268
|
const result = await resolveServiceUrlDetailed(flagUrl);
|
|
@@ -11234,7 +11301,10 @@ async function resolveToken(flagToken) {
|
|
|
11234
11301
|
data: { token: local.token, source: "local", userId: local.userId, email: local.email }
|
|
11235
11302
|
};
|
|
11236
11303
|
}
|
|
11237
|
-
return {
|
|
11304
|
+
return {
|
|
11305
|
+
ok: false,
|
|
11306
|
+
error: 'No Verity token found. Run "verity login" to sign in, or /verity-setup to set up this project.'
|
|
11307
|
+
};
|
|
11238
11308
|
}
|
|
11239
11309
|
async function whoami(token, serviceUrl, verbose) {
|
|
11240
11310
|
return apiRequest({
|
|
@@ -11671,16 +11741,81 @@ function registerAuthCommands(program2) {
|
|
|
11671
11741
|
});
|
|
11672
11742
|
}
|
|
11673
11743
|
|
|
11744
|
+
// src/lib/login-report.ts
|
|
11745
|
+
async function reportLoginOutcome(out, opts = {}) {
|
|
11746
|
+
const identity = out.email ?? (out.userId != null ? `user #${out.userId}` : "your account");
|
|
11747
|
+
printInfo(`Logged in as ${identity}. \u2713`);
|
|
11748
|
+
printInfo(` Access granted to ${out.repoCount} ${out.repoCount === 1 ? "repository" : "repositories"}.`);
|
|
11749
|
+
if (out.expiresAt) {
|
|
11750
|
+
printInfo(` This login expires on ${out.expiresAt.slice(0, 10)} \u2014 "verity login" again to renew.`);
|
|
11751
|
+
printInfo(' See your machines with "verity sessions list"; sign out with "verity logout".');
|
|
11752
|
+
}
|
|
11753
|
+
if (out.repoCount > 0) {
|
|
11754
|
+
printInfo(" Runs, history, and cloud memory now sync to Verity everywhere you have write access.");
|
|
11755
|
+
}
|
|
11756
|
+
if (out.prunedCredentials > 0) {
|
|
11757
|
+
printVerbose(`Pruned ${out.prunedCredentials} superseded per-repository credential line(s).`, opts.verbose);
|
|
11758
|
+
} else if (out.prunedCredentials < 0) {
|
|
11759
|
+
printWarn(" Could not rewrite ~/.verity/credentials: superseded per-repository tokens remain and");
|
|
11760
|
+
printWarn(" will keep taking precedence over this login in their own repositories.");
|
|
11761
|
+
printInfo(` Check the file's permissions; the next successful "verity login" retries the cleanup.`);
|
|
11762
|
+
}
|
|
11763
|
+
if (out.repoCount === 0) {
|
|
11764
|
+
printWarn("The Verity GitHub App is not installed on any account you can access.");
|
|
11765
|
+
printInfo(" Install it (and grant your repositories), then re-run verity login:");
|
|
11766
|
+
printInfo(` ${githubAppInstallUrl(null)}`);
|
|
11767
|
+
return;
|
|
11768
|
+
}
|
|
11769
|
+
const remote = opts.remote;
|
|
11770
|
+
if (!remote) return;
|
|
11771
|
+
const who = await whoami(out.token, out.serviceUrl, opts.verbose);
|
|
11772
|
+
if (who.ok && who.data.grant_status != null) {
|
|
11773
|
+
printInfo(" \u2713 This repository is covered.");
|
|
11774
|
+
} else if (!who.ok) {
|
|
11775
|
+
printWarn(` Could not confirm this repository's coverage (${who.error}) \u2014 verity status will show it.`);
|
|
11776
|
+
} else {
|
|
11777
|
+
const parsed = parseRemote(remote);
|
|
11778
|
+
const installUrl = githubAppInstallUrl(parsed ? await githubAccountId(parsed.owner) : null);
|
|
11779
|
+
printWarn(` This repository (${parsed ? `${parsed.owner}/${parsed.repo}` : remote}) is NOT covered by your grants.`);
|
|
11780
|
+
printInfo(" Grant the Verity GitHub App access to it, then re-run verity login:");
|
|
11781
|
+
printInfo(` ${installUrl}`);
|
|
11782
|
+
}
|
|
11783
|
+
const rec = await readGlobalCredential(remote);
|
|
11784
|
+
if (rec && rec.token !== out.token) {
|
|
11785
|
+
const otherBackend = rec.serviceUrl != null && rec.serviceUrl !== out.serviceUrl;
|
|
11786
|
+
const otherIdentity = rec.userId != null && out.userId != null && rec.userId !== out.userId;
|
|
11787
|
+
if (otherBackend) {
|
|
11788
|
+
printWarn(` Note: this repository is pinned to a different Verity service (${rec.serviceUrl})`);
|
|
11789
|
+
printWarn(" by its own credential line, which takes precedence here \u2014 this login does not");
|
|
11790
|
+
printWarn(" change that. To move the repository, remove its line from ~/.verity/credentials.");
|
|
11791
|
+
printWarn(' Until that line is removed, "verity login" here cannot fast-path and will run');
|
|
11792
|
+
printWarn(" the full GitHub flow every time.");
|
|
11793
|
+
} else if (otherIdentity) {
|
|
11794
|
+
printWarn(" Note: this repository uses a different account's credential, which takes");
|
|
11795
|
+
printWarn(' precedence here \u2014 this login leaves it in place, and "verity login" in this');
|
|
11796
|
+
printWarn(" repository will report that account. Remove its line from ~/.verity/credentials");
|
|
11797
|
+
printWarn(" only if you want this repository on the login you just completed.");
|
|
11798
|
+
} else {
|
|
11799
|
+
const kind = rec.userId != null ? "superseded per-repository" : "anonymous project-specific";
|
|
11800
|
+
printWarn(` Note: this repository has a ${kind} credential that takes`);
|
|
11801
|
+
printWarn(" precedence here. Remove its line from ~/.verity/credentials to use your login.");
|
|
11802
|
+
printWarn(' Until then, "verity login" in this repository re-runs the full GitHub flow');
|
|
11803
|
+
printWarn(" every time.");
|
|
11804
|
+
}
|
|
11805
|
+
}
|
|
11806
|
+
}
|
|
11807
|
+
|
|
11674
11808
|
// src/commands/login.ts
|
|
11675
11809
|
function registerLoginCommand(program2) {
|
|
11676
11810
|
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
11811
|
const globals = program2.opts();
|
|
11678
|
-
const
|
|
11679
|
-
if (
|
|
11680
|
-
|
|
11681
|
-
|
|
11812
|
+
const resolution = await resolveServiceUrlForAuth(globals.serviceUrl);
|
|
11813
|
+
if (resolution.source === "default") {
|
|
11814
|
+
printInfo(`No Verity service configured on this machine \u2014 using the default: ${resolution.url}`);
|
|
11815
|
+
} else {
|
|
11816
|
+
printVerbose(`Service URL from ${resolution.source}: ${resolution.url}`, globals.verbose);
|
|
11682
11817
|
}
|
|
11683
|
-
const heal = await maybeHealServiceUrl(
|
|
11818
|
+
const heal = await maybeHealServiceUrl(resolution, globals.verbose);
|
|
11684
11819
|
const serviceUrl = heal.serviceUrl;
|
|
11685
11820
|
if (heal.healed) {
|
|
11686
11821
|
printInfo(" Completing login updates ~/.verity/credentials against the live service.");
|
|
@@ -11721,65 +11856,7 @@ function registerLoginCommand(program2) {
|
|
|
11721
11856
|
printError(`Login failed: ${result.error}`);
|
|
11722
11857
|
process.exit(1);
|
|
11723
11858
|
}
|
|
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
|
-
}
|
|
11859
|
+
await reportLoginOutcome(result.data, { remote: remote || void 0, verbose: globals.verbose });
|
|
11783
11860
|
});
|
|
11784
11861
|
}
|
|
11785
11862
|
|
|
@@ -11914,12 +11991,12 @@ async function auth(globals) {
|
|
|
11914
11991
|
printError(tokenResult.error);
|
|
11915
11992
|
process.exit(1);
|
|
11916
11993
|
}
|
|
11917
|
-
const
|
|
11918
|
-
|
|
11919
|
-
|
|
11920
|
-
|
|
11921
|
-
|
|
11922
|
-
|
|
11994
|
+
const resolution = await resolveServiceUrlForAuth(globals.serviceUrl);
|
|
11995
|
+
return {
|
|
11996
|
+
token: tokenResult.data.token,
|
|
11997
|
+
serviceUrl: resolution.url,
|
|
11998
|
+
keyed: tokenResult.data.keyed === true
|
|
11999
|
+
};
|
|
11923
12000
|
}
|
|
11924
12001
|
function explain(error) {
|
|
11925
12002
|
if (error.startsWith("FORBIDDEN")) {
|
|
@@ -11928,6 +12005,20 @@ function explain(error) {
|
|
|
11928
12005
|
printInfo(' This login has expired or was revoked \u2014 run "verity login" to sign in again.');
|
|
11929
12006
|
}
|
|
11930
12007
|
}
|
|
12008
|
+
async function logoutLocallyOnly(keyed) {
|
|
12009
|
+
printWarn("Could not reach the Verity service to revoke this login (see above).");
|
|
12010
|
+
const cleared = await removeGlobalCredential(keyed ? currentRemote() : "");
|
|
12011
|
+
if (cleared) {
|
|
12012
|
+
printInfo(keyed ? "This repository\u2019s Verity credential cleared \u2014 it is signed out. \u2713" : "Local login credential cleared \u2014 this machine is signed out. \u2713");
|
|
12013
|
+
printInfo(" The session may still exist server-side; revoke it from another machine with");
|
|
12014
|
+
printInfo(' "verity sessions revoke <session-id>" if that matters.');
|
|
12015
|
+
} else {
|
|
12016
|
+
printInfo("No local credential to clear \u2014 already signed out here.");
|
|
12017
|
+
}
|
|
12018
|
+
}
|
|
12019
|
+
function isPinRefusal(error) {
|
|
12020
|
+
return error.startsWith("TOKEN_PIN_REFUSED");
|
|
12021
|
+
}
|
|
11931
12022
|
function registerSessionsCommands(program2) {
|
|
11932
12023
|
const sessions = program2.command("sessions").description("List and revoke your Verity logins (one per machine)");
|
|
11933
12024
|
sessions.command("list").description("List your active logins \u2014 device, last use, and expiry").option("--json", "Output raw JSON").action(async (opts) => {
|
|
@@ -11972,7 +12063,7 @@ function registerSessionsCommands(program2) {
|
|
|
11972
12063
|
});
|
|
11973
12064
|
sessions.command("revoke <session-id>").description("Revoke one login. Revoking this machine's also clears the local credential").action(async (sessionId) => {
|
|
11974
12065
|
const globals = program2.opts();
|
|
11975
|
-
const { token, serviceUrl } = await auth(globals);
|
|
12066
|
+
const { token, serviceUrl, keyed } = await auth(globals);
|
|
11976
12067
|
const result = await apiRequest({
|
|
11977
12068
|
method: "DELETE",
|
|
11978
12069
|
path: `/auth/sessions/${encodeURIComponent(sessionId)}`,
|
|
@@ -11991,7 +12082,7 @@ function registerSessionsCommands(program2) {
|
|
|
11991
12082
|
}
|
|
11992
12083
|
printInfo(`Session ${sessionId} revoked. \u2713`);
|
|
11993
12084
|
if (result.data.was_current) {
|
|
11994
|
-
const cleared = await removeGlobalCredential("");
|
|
12085
|
+
const cleared = await removeGlobalCredential(keyed ? currentRemote() : "");
|
|
11995
12086
|
printInfo(cleared ? ' That was this machine \u2014 local credential cleared. Run "verity login" to sign back in.' : ' That was this machine. Run "verity login" to sign back in.');
|
|
11996
12087
|
}
|
|
11997
12088
|
});
|
|
@@ -12003,7 +12094,7 @@ function registerLogoutCommand(program2) {
|
|
|
12003
12094
|
printError("Use either --all or --others, not both.");
|
|
12004
12095
|
process.exit(1);
|
|
12005
12096
|
}
|
|
12006
|
-
const { token, serviceUrl } = await auth(globals);
|
|
12097
|
+
const { token, serviceUrl, keyed } = await auth(globals);
|
|
12007
12098
|
if (opts.all || opts.others) {
|
|
12008
12099
|
const result = await apiRequest({
|
|
12009
12100
|
method: "DELETE",
|
|
@@ -12014,6 +12105,10 @@ function registerLogoutCommand(program2) {
|
|
|
12014
12105
|
cmd: "logout"
|
|
12015
12106
|
});
|
|
12016
12107
|
if (!result.ok) {
|
|
12108
|
+
if (isPinRefusal(result.error) && !opts.others) {
|
|
12109
|
+
await logoutLocallyOnly(keyed);
|
|
12110
|
+
return;
|
|
12111
|
+
}
|
|
12017
12112
|
printError(result.error);
|
|
12018
12113
|
explain(result.error);
|
|
12019
12114
|
process.exit(1);
|
|
@@ -12038,15 +12133,21 @@ function registerLogoutCommand(program2) {
|
|
|
12038
12133
|
cmd: "logout"
|
|
12039
12134
|
});
|
|
12040
12135
|
if (!list.ok) {
|
|
12136
|
+
if (isPinRefusal(list.error)) {
|
|
12137
|
+
await logoutLocallyOnly(keyed);
|
|
12138
|
+
return;
|
|
12139
|
+
}
|
|
12041
12140
|
printError(list.error);
|
|
12042
12141
|
explain(list.error);
|
|
12043
12142
|
process.exit(1);
|
|
12044
12143
|
}
|
|
12045
12144
|
const current = list.data.sessions.find((s) => s.current);
|
|
12046
12145
|
if (!current) {
|
|
12047
|
-
printWarn("This machine is not signed in with a Verity login.");
|
|
12048
|
-
const cleared2 = await removeGlobalCredential("");
|
|
12049
|
-
if (cleared2)
|
|
12146
|
+
printWarn(keyed ? "This repository uses its own Verity credential, not a machine login." : "This machine is not signed in with a Verity login.");
|
|
12147
|
+
const cleared2 = await removeGlobalCredential(keyed ? currentRemote() : "");
|
|
12148
|
+
if (cleared2) {
|
|
12149
|
+
printInfo(keyed ? " Cleared this repository\u2019s credential; your machine login is untouched." : " Cleared the local login credential anyway.");
|
|
12150
|
+
}
|
|
12050
12151
|
return;
|
|
12051
12152
|
}
|
|
12052
12153
|
const revoked = await apiRequest({
|
|
@@ -12058,6 +12159,10 @@ function registerLogoutCommand(program2) {
|
|
|
12058
12159
|
cmd: "logout"
|
|
12059
12160
|
});
|
|
12060
12161
|
if (!revoked.ok) {
|
|
12162
|
+
if (isPinRefusal(revoked.error)) {
|
|
12163
|
+
await logoutLocallyOnly(keyed);
|
|
12164
|
+
return;
|
|
12165
|
+
}
|
|
12061
12166
|
printError(revoked.error);
|
|
12062
12167
|
explain(revoked.error);
|
|
12063
12168
|
process.exit(1);
|
|
@@ -13649,6 +13754,84 @@ function isMetaTaskLabel(label2) {
|
|
|
13649
13754
|
];
|
|
13650
13755
|
return metaPatterns.some((p) => p.test(trimmed));
|
|
13651
13756
|
}
|
|
13757
|
+
function shouldSkipForBareAck(input) {
|
|
13758
|
+
if (!isBareAckPrompt(input.prompt)) return false;
|
|
13759
|
+
if (input.turnAuthoredCode) return false;
|
|
13760
|
+
return input.canSeeTurnAuthorship;
|
|
13761
|
+
}
|
|
13762
|
+
|
|
13763
|
+
// src/lib/pending-repeat.ts
|
|
13764
|
+
var STOP = /* @__PURE__ */ new Set([
|
|
13765
|
+
"the",
|
|
13766
|
+
"and",
|
|
13767
|
+
"that",
|
|
13768
|
+
"this",
|
|
13769
|
+
"with",
|
|
13770
|
+
"from",
|
|
13771
|
+
"have",
|
|
13772
|
+
"been",
|
|
13773
|
+
"were",
|
|
13774
|
+
"what",
|
|
13775
|
+
"when",
|
|
13776
|
+
"which",
|
|
13777
|
+
"their",
|
|
13778
|
+
"there",
|
|
13779
|
+
"these",
|
|
13780
|
+
"those",
|
|
13781
|
+
"would",
|
|
13782
|
+
"could",
|
|
13783
|
+
"should",
|
|
13784
|
+
"must",
|
|
13785
|
+
"will",
|
|
13786
|
+
"also",
|
|
13787
|
+
"just",
|
|
13788
|
+
"only",
|
|
13789
|
+
"into",
|
|
13790
|
+
"over",
|
|
13791
|
+
"than",
|
|
13792
|
+
"then",
|
|
13793
|
+
"them",
|
|
13794
|
+
"some",
|
|
13795
|
+
"such",
|
|
13796
|
+
"more",
|
|
13797
|
+
"most",
|
|
13798
|
+
"other",
|
|
13799
|
+
"about",
|
|
13800
|
+
"after",
|
|
13801
|
+
"before",
|
|
13802
|
+
"since",
|
|
13803
|
+
"because",
|
|
13804
|
+
"while",
|
|
13805
|
+
"where",
|
|
13806
|
+
"whether",
|
|
13807
|
+
"ensure",
|
|
13808
|
+
"confirm",
|
|
13809
|
+
"verify",
|
|
13810
|
+
"check"
|
|
13811
|
+
]);
|
|
13812
|
+
function pendingTokens(text) {
|
|
13813
|
+
if (!text || typeof text !== "string") return [];
|
|
13814
|
+
const out = /* @__PURE__ */ new Set();
|
|
13815
|
+
for (const raw of text.toLowerCase().split(/[^a-z0-9]+/)) {
|
|
13816
|
+
if (raw.length <= 3) continue;
|
|
13817
|
+
if (STOP.has(raw)) continue;
|
|
13818
|
+
out.add(raw);
|
|
13819
|
+
}
|
|
13820
|
+
return [...out].sort();
|
|
13821
|
+
}
|
|
13822
|
+
var REPEAT_THRESHOLD = 0.3;
|
|
13823
|
+
function overlapCoefficient(a, b) {
|
|
13824
|
+
if (a.length === 0 || b.length === 0) return 0;
|
|
13825
|
+
const setB = new Set(b);
|
|
13826
|
+
let shared = 0;
|
|
13827
|
+
for (const t of a) if (setB.has(t)) shared++;
|
|
13828
|
+
return shared / Math.min(a.length, b.length);
|
|
13829
|
+
}
|
|
13830
|
+
function isRepeatOfAny(text, priorFingerprints, threshold = REPEAT_THRESHOLD) {
|
|
13831
|
+
const tokens = pendingTokens(text);
|
|
13832
|
+
if (tokens.length === 0) return false;
|
|
13833
|
+
return priorFingerprints.some((prior) => overlapCoefficient(tokens, prior) >= threshold);
|
|
13834
|
+
}
|
|
13652
13835
|
|
|
13653
13836
|
// src/lib/dossier.ts
|
|
13654
13837
|
var import_node_fs9 = require("node:fs");
|
|
@@ -13658,6 +13841,7 @@ var MAX_LINE_BYTES = 4096;
|
|
|
13658
13841
|
var MAX_GOAL_CHARS = 2e3;
|
|
13659
13842
|
var GOAL_KEEP = 8;
|
|
13660
13843
|
var GOAL_TOTAL_CAP = 32;
|
|
13844
|
+
var RECENT_PENDING_CAP = 20;
|
|
13661
13845
|
var HASH_WIDTH = 16;
|
|
13662
13846
|
var AUTHORED_CAP = 300;
|
|
13663
13847
|
var NOT_MINE_CAP = 300;
|
|
@@ -14026,6 +14210,10 @@ function reduce(state, events, now) {
|
|
|
14026
14210
|
consecutiveIdle: ev.idle === false ? 0 : (state.meta.channel?.consecutiveIdle ?? 0) + 1
|
|
14027
14211
|
};
|
|
14028
14212
|
state.meta.last_adjudication = ev.intent_verdict ? { verdict: ev.intent_verdict, score: ev.intent_score ?? null, at: ev.at, decision: ev.decision } : void 0;
|
|
14213
|
+
if (Array.isArray(ev.pending_sigs) && ev.pending_sigs.length > 0) {
|
|
14214
|
+
const prior = state.meta.recent_pending_sigs ?? [];
|
|
14215
|
+
state.meta.recent_pending_sigs = [...prior, ...ev.pending_sigs].slice(-RECENT_PENDING_CAP);
|
|
14216
|
+
}
|
|
14029
14217
|
if (ev.intent_sig) {
|
|
14030
14218
|
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 };
|
|
14031
14219
|
} else {
|
|
@@ -14250,12 +14438,12 @@ function readFoldCache(d) {
|
|
|
14250
14438
|
if (!(0, import_node_fs9.existsSync)(d.foldPath)) return null;
|
|
14251
14439
|
const raw = JSON.parse((0, import_node_fs9.readFileSync)(d.foldPath, "utf8"));
|
|
14252
14440
|
if (raw?.v !== 1) return null;
|
|
14253
|
-
const
|
|
14254
|
-
if (!
|
|
14441
|
+
const cached2 = expandState(raw);
|
|
14442
|
+
if (!cached2?.meta) return null;
|
|
14255
14443
|
const size = (0, import_node_fs9.existsSync)(d.eventsPath) ? (0, import_node_fs9.statSync)(d.eventsPath).size : 0;
|
|
14256
14444
|
const rotations = (0, import_node_fs9.existsSync)(d.rotatedDir) ? (0, import_node_fs9.readdirSync)(d.rotatedDir).filter((f) => f.endsWith(".jsonl")).length : 0;
|
|
14257
|
-
if (
|
|
14258
|
-
return
|
|
14445
|
+
if (cached2.meta.upto_offset !== size || cached2.meta.rotations !== rotations) return null;
|
|
14446
|
+
return cached2;
|
|
14259
14447
|
} catch {
|
|
14260
14448
|
return null;
|
|
14261
14449
|
}
|
|
@@ -14882,6 +15070,13 @@ function recordVerdict(d, v) {
|
|
|
14882
15070
|
branch: v.branch,
|
|
14883
15071
|
decision: v.decision,
|
|
14884
15072
|
...sig && { intent_sig: sig },
|
|
15073
|
+
// Fingerprints of the pending items this verdict delivered, so the NEXT turn
|
|
15074
|
+
// can tell a repeat from a new requirement. Only recorded when the channel
|
|
15075
|
+
// actually spoke — a silenced turn delivered nothing, so nothing was "said
|
|
15076
|
+
// before" and labelling the next turn's items as repeats would be a lie.
|
|
15077
|
+
...v.emitted === true && v.pendingTexts && v.pendingTexts.length > 0 && {
|
|
15078
|
+
pending_sigs: v.pendingTexts.slice(0, 8).map((t) => pendingTokens(t).slice(0, 16))
|
|
15079
|
+
},
|
|
14885
15080
|
emitted: v.emitted === true,
|
|
14886
15081
|
idle: v.idle !== false,
|
|
14887
15082
|
...v.intent?.verdict && { intent_verdict: v.intent.verdict },
|
|
@@ -15493,8 +15688,8 @@ function preImage(repoRelPath, baseline) {
|
|
|
15493
15688
|
perBaseline = /* @__PURE__ */ new Map();
|
|
15494
15689
|
preImageCache.set(baseline, perBaseline);
|
|
15495
15690
|
}
|
|
15496
|
-
const
|
|
15497
|
-
if (
|
|
15691
|
+
const cached2 = perBaseline.get(repoRelPath);
|
|
15692
|
+
if (cached2) return cached2;
|
|
15498
15693
|
const resolved = resolvePreImage(repoRelPath, baseline);
|
|
15499
15694
|
perBaseline.set(repoRelPath, resolved);
|
|
15500
15695
|
return resolved;
|
|
@@ -15538,6 +15733,34 @@ ${addedLines}`,
|
|
|
15538
15733
|
}
|
|
15539
15734
|
return { diffs, has_baseline: true };
|
|
15540
15735
|
}
|
|
15736
|
+
function absorbIntoBaseline(paths, sessionId) {
|
|
15737
|
+
const baseline = readBaseline(sessionId);
|
|
15738
|
+
if (!baseline || paths.length === 0) return 0;
|
|
15739
|
+
const dir = sessionDir(sessionKey(baseline.session_id));
|
|
15740
|
+
let adopted = 0;
|
|
15741
|
+
const dirty = new Set(baseline.dirty_paths);
|
|
15742
|
+
for (const p of paths) {
|
|
15743
|
+
try {
|
|
15744
|
+
const content = safeReadForMirror(projectPath(p));
|
|
15745
|
+
if (content === null) continue;
|
|
15746
|
+
const dest = mirrorPath(dir, p);
|
|
15747
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path14.dirname)(dest), { recursive: true });
|
|
15748
|
+
(0, import_node_fs13.writeFileSync)(dest, content);
|
|
15749
|
+
dirty.add(p);
|
|
15750
|
+
adopted++;
|
|
15751
|
+
} catch {
|
|
15752
|
+
}
|
|
15753
|
+
}
|
|
15754
|
+
if (adopted === 0) return 0;
|
|
15755
|
+
try {
|
|
15756
|
+
const updated = { ...baseline, dirty_paths: [...dirty] };
|
|
15757
|
+
(0, import_node_fs13.writeFileSync)(manifestPath(dir), JSON.stringify(updated));
|
|
15758
|
+
preImageCache.delete(baseline);
|
|
15759
|
+
} catch {
|
|
15760
|
+
return 0;
|
|
15761
|
+
}
|
|
15762
|
+
return adopted;
|
|
15763
|
+
}
|
|
15541
15764
|
function changedSinceBaseline(repoRelPath, baseline) {
|
|
15542
15765
|
const pre = preImage(repoRelPath, baseline);
|
|
15543
15766
|
let current;
|
|
@@ -16414,40 +16637,40 @@ function narrowToRecent(files, sessionId) {
|
|
|
16414
16637
|
});
|
|
16415
16638
|
return recent.length > 0 ? recent : files;
|
|
16416
16639
|
}
|
|
16417
|
-
function
|
|
16418
|
-
if (!(0, import_node_fs15.existsSync)(ITERATION_FILE)) return 1;
|
|
16640
|
+
function readIterationState(currentCommit) {
|
|
16641
|
+
if (!(0, import_node_fs15.existsSync)(ITERATION_FILE)) return { iteration: 1, fingerprint: null };
|
|
16419
16642
|
try {
|
|
16420
16643
|
const stored = (0, import_node_fs15.readFileSync)(ITERATION_FILE, "utf-8").trim();
|
|
16421
16644
|
const parts = stored.split(":");
|
|
16422
16645
|
const iter = parseInt(parts[0], 10);
|
|
16423
16646
|
const storedCommit = parts[1] ?? "";
|
|
16424
16647
|
const storedTimestamp = parseInt(parts[2] ?? "0", 10);
|
|
16425
|
-
|
|
16426
|
-
if (
|
|
16648
|
+
const fingerprint = parts.slice(3).join(":") || null;
|
|
16649
|
+
if (isNaN(iter)) return { iteration: 1, fingerprint: null };
|
|
16650
|
+
if (storedCommit !== currentCommit) return { iteration: 1, fingerprint: null };
|
|
16427
16651
|
if (storedTimestamp > 0) {
|
|
16428
16652
|
const elapsed = Math.floor(Date.now() / 1e3) - storedTimestamp;
|
|
16429
|
-
if (elapsed > 600) return 1;
|
|
16653
|
+
if (elapsed > 600) return { iteration: 1, fingerprint: null };
|
|
16430
16654
|
}
|
|
16431
|
-
return iter;
|
|
16655
|
+
return { iteration: iter, fingerprint };
|
|
16432
16656
|
} catch {
|
|
16433
|
-
return 1;
|
|
16657
|
+
return { iteration: 1, fingerprint: null };
|
|
16434
16658
|
}
|
|
16435
16659
|
}
|
|
16436
|
-
function
|
|
16437
|
-
const
|
|
16438
|
-
|
|
16439
|
-
writeIteration(1, currentCommit, contentHash);
|
|
16440
|
-
return {
|
|
16441
|
-
skip: `Max Verity iterations (${maxIterations}) reached \u2014 accepting to prevent infinite loop. Human review required before deploying.`,
|
|
16442
|
-
iteration
|
|
16443
|
-
};
|
|
16444
|
-
}
|
|
16445
|
-
return { skip: null, iteration };
|
|
16660
|
+
function findingsFingerprint(findings) {
|
|
16661
|
+
const keys = findings.map((f) => `${String(f.pattern_id ?? "?")}|${String(f.file ?? "?")}`).filter((k) => k !== "?|?");
|
|
16662
|
+
return [...new Set(keys)].sort().join(",");
|
|
16446
16663
|
}
|
|
16447
|
-
function
|
|
16664
|
+
function isSameProblem(previous, current) {
|
|
16665
|
+
if (!previous || !current) return false;
|
|
16666
|
+
const prev = new Set(previous.split(","));
|
|
16667
|
+
return current.split(",").some((k) => prev.has(k));
|
|
16668
|
+
}
|
|
16669
|
+
function writeIteration(iteration, commit, _contentHash, fingerprint) {
|
|
16448
16670
|
(0, import_node_fs15.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
16449
16671
|
const ts = Math.floor(Date.now() / 1e3);
|
|
16450
|
-
|
|
16672
|
+
const fp = fingerprint ? `:${fingerprint}` : "";
|
|
16673
|
+
(0, import_node_fs15.writeFileSync)(ITERATION_FILE, `${iteration}:${commit}:${ts}${fp}`);
|
|
16451
16674
|
}
|
|
16452
16675
|
|
|
16453
16676
|
// src/lib/static-analysis.ts
|
|
@@ -16696,7 +16919,7 @@ function resolveTaskContext(opts) {
|
|
|
16696
16919
|
// src/lib/cli-version.ts
|
|
16697
16920
|
function cliVersion() {
|
|
16698
16921
|
try {
|
|
16699
|
-
return true ? "0.28.1-experimental.
|
|
16922
|
+
return true ? "0.28.1-experimental.db64e52" : "dev";
|
|
16700
16923
|
} catch {
|
|
16701
16924
|
return "dev";
|
|
16702
16925
|
}
|
|
@@ -16790,10 +17013,26 @@ function cacheRequest(body) {
|
|
|
16790
17013
|
(0, import_node_fs18.mkdirSync)(CACHE_DIR, { recursive: true });
|
|
16791
17014
|
const suffix = (0, import_node_crypto9.randomBytes)(4).toString("hex");
|
|
16792
17015
|
const filename = `pending-${Math.floor(Date.now() / 1e3)}-${suffix}.json`;
|
|
16793
|
-
(0, import_node_fs18.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(body));
|
|
17016
|
+
(0, import_node_fs18.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(redactRequest(body)));
|
|
16794
17017
|
} catch {
|
|
16795
17018
|
}
|
|
16796
17019
|
}
|
|
17020
|
+
function redactRequest(body) {
|
|
17021
|
+
const b = body ?? {};
|
|
17022
|
+
const delta = b.code_delta ?? {};
|
|
17023
|
+
const files = Array.isArray(delta.files) ? delta.files : [];
|
|
17024
|
+
return {
|
|
17025
|
+
captured_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17026
|
+
undelivered: true,
|
|
17027
|
+
total_files: files.length,
|
|
17028
|
+
total_bytes: files.reduce(
|
|
17029
|
+
(n, f) => n + (typeof f.content === "string" ? f.content.length : 0),
|
|
17030
|
+
0
|
|
17031
|
+
),
|
|
17032
|
+
// Paths only. A path is already visible in the repository; the content is not.
|
|
17033
|
+
files: files.map((f) => typeof f.path === "string" ? f.path : "<unknown>").slice(0, 100)
|
|
17034
|
+
};
|
|
17035
|
+
}
|
|
16797
17036
|
function buildOfflineFallback(reason, staticResults) {
|
|
16798
17037
|
return {
|
|
16799
17038
|
// G12 / INV-18 — WARN, not PASS.
|
|
@@ -17020,8 +17259,8 @@ function commandShape(cmd) {
|
|
|
17020
17259
|
var COMMAND_HEAD_CHARS = 80;
|
|
17021
17260
|
var rootCandidateCache = /* @__PURE__ */ new Map();
|
|
17022
17261
|
function candidateRoots(repoRoot2) {
|
|
17023
|
-
const
|
|
17024
|
-
if (
|
|
17262
|
+
const cached2 = rootCandidateCache.get(repoRoot2);
|
|
17263
|
+
if (cached2) return cached2;
|
|
17025
17264
|
const norm = repoRoot2.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
17026
17265
|
const out = [norm];
|
|
17027
17266
|
try {
|
|
@@ -17370,9 +17609,13 @@ function buildAgentContext(input) {
|
|
|
17370
17609
|
}
|
|
17371
17610
|
for (const p of input.pendingItems ?? []) {
|
|
17372
17611
|
if (lines.length >= MAX_AGENT_ITEMS) break;
|
|
17612
|
+
if (p.pattern_id === "intent-misalignment") continue;
|
|
17373
17613
|
const text = p.description ?? p.title ?? p.reason;
|
|
17374
17614
|
if (!text) continue;
|
|
17375
|
-
|
|
17615
|
+
const seenBefore = isRepeatOfAny(text, input.priorPendingFingerprints ?? []);
|
|
17616
|
+
lines.push(
|
|
17617
|
+
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)" : "")
|
|
17618
|
+
);
|
|
17376
17619
|
}
|
|
17377
17620
|
if (lines.length === 0) return null;
|
|
17378
17621
|
const body = `${REPORT_PREFIX}
|
|
@@ -17522,6 +17765,13 @@ function isGitOnlyPrompt(prompt) {
|
|
|
17522
17765
|
return true;
|
|
17523
17766
|
}
|
|
17524
17767
|
function reconcileAnalysisMode(predictedMode, signals) {
|
|
17768
|
+
const mode = resolveAnalysisMode(predictedMode, signals);
|
|
17769
|
+
if (mode !== "skip") return mode;
|
|
17770
|
+
const windowIsOrphaned = signals.actionSummary?.transcript_windowed === "orphaned";
|
|
17771
|
+
if (windowIsOrphaned && !signals.sessionAuthoredCode) return "standard";
|
|
17772
|
+
return mode;
|
|
17773
|
+
}
|
|
17774
|
+
function resolveAnalysisMode(predictedMode, signals) {
|
|
17525
17775
|
if (!predictedMode || !isValidMode(predictedMode)) {
|
|
17526
17776
|
return detectAnalysisMode(
|
|
17527
17777
|
signals.noFilesChanged,
|
|
@@ -17633,9 +17883,11 @@ var MAX_SUMMARY_BYTES = 4096;
|
|
|
17633
17883
|
var HOME = process.env.HOME ?? "";
|
|
17634
17884
|
async function extractActionSummary(transcriptPath) {
|
|
17635
17885
|
try {
|
|
17636
|
-
const
|
|
17637
|
-
if (!
|
|
17638
|
-
|
|
17886
|
+
const read = readTurnLines(transcriptPath);
|
|
17887
|
+
if (!read || read.lines.length === 0) return null;
|
|
17888
|
+
const summary = buildSummary(read.lines);
|
|
17889
|
+
if (summary) summary.transcript_windowed = read.window;
|
|
17890
|
+
return summary;
|
|
17639
17891
|
} catch {
|
|
17640
17892
|
return null;
|
|
17641
17893
|
}
|
|
@@ -17649,9 +17901,11 @@ function readTurnLines(transcriptPath) {
|
|
|
17649
17901
|
}
|
|
17650
17902
|
if (size === 0) return null;
|
|
17651
17903
|
let raw;
|
|
17904
|
+
let windowed = false;
|
|
17652
17905
|
if (size <= SMALL_FILE_BYTES) {
|
|
17653
17906
|
raw = (0, import_node_fs22.readFileSync)(transcriptPath, "utf-8");
|
|
17654
17907
|
} else {
|
|
17908
|
+
windowed = true;
|
|
17655
17909
|
const buf = Buffer.alloc(Math.min(MAX_READ_BYTES, size));
|
|
17656
17910
|
const fd = require("node:fs").openSync(transcriptPath, "r");
|
|
17657
17911
|
try {
|
|
@@ -17669,17 +17923,22 @@ function readTurnLines(transcriptPath) {
|
|
|
17669
17923
|
const allLines = raw.split("\n").filter((l) => l.trim().length > 0);
|
|
17670
17924
|
if (allLines.length === 0) return null;
|
|
17671
17925
|
let turnStart = 0;
|
|
17926
|
+
let boundaryFound = false;
|
|
17672
17927
|
for (let i = allLines.length - 1; i >= 0; i--) {
|
|
17673
17928
|
try {
|
|
17674
17929
|
const parsed = JSON.parse(allLines[i]);
|
|
17675
17930
|
if (parsed.type === "user" && isRealUserMessage(parsed)) {
|
|
17676
17931
|
turnStart = i;
|
|
17932
|
+
boundaryFound = true;
|
|
17677
17933
|
break;
|
|
17678
17934
|
}
|
|
17679
17935
|
} catch {
|
|
17680
17936
|
}
|
|
17681
17937
|
}
|
|
17682
|
-
return
|
|
17938
|
+
return {
|
|
17939
|
+
lines: allLines.slice(turnStart),
|
|
17940
|
+
window: !windowed ? "whole" : boundaryFound ? "windowed" : "orphaned"
|
|
17941
|
+
};
|
|
17683
17942
|
}
|
|
17684
17943
|
function isRealUserMessage(parsed) {
|
|
17685
17944
|
const message = parsed.message;
|
|
@@ -18237,11 +18496,12 @@ async function readStopHookStdin() {
|
|
|
18237
18496
|
return empty;
|
|
18238
18497
|
}
|
|
18239
18498
|
}
|
|
18240
|
-
function agentContextFor(response, intentRepeat = 0) {
|
|
18499
|
+
function agentContextFor(response, intentRepeat = 0, priorPendingFingerprints = []) {
|
|
18241
18500
|
const metadata = response.metadata ?? {};
|
|
18242
18501
|
const intent = response.intent_alignment ?? {};
|
|
18243
18502
|
return buildAgentContext({
|
|
18244
18503
|
intentRepeat,
|
|
18504
|
+
priorPendingFingerprints,
|
|
18245
18505
|
gateDecision: String(response.gate_decision ?? ""),
|
|
18246
18506
|
findings: response.findings ?? [],
|
|
18247
18507
|
pendingItems: response.pending_items ?? [],
|
|
@@ -18276,7 +18536,7 @@ async function passAndExit(reason, skip, kindOverride) {
|
|
|
18276
18536
|
if (unaccounted.length > 0) {
|
|
18277
18537
|
logEvent("coverage_unaccounted", { where: "passAndExit", skip, count: unaccounted.length });
|
|
18278
18538
|
}
|
|
18279
|
-
const AGENT_SILENT_SKIPS = /* @__PURE__ */ new Set([
|
|
18539
|
+
const AGENT_SILENT_SKIPS = /* @__PURE__ */ new Set([]);
|
|
18280
18540
|
const agentNote = AGENT_SILENT_SKIPS.has(skip) ? null : note;
|
|
18281
18541
|
printJsonCompact(
|
|
18282
18542
|
buildHookOutput(
|
|
@@ -18377,15 +18637,27 @@ async function runAnalyze(opts, globals) {
|
|
|
18377
18637
|
const conversation = await readAndClearConversationBuffer(baselineSessionId);
|
|
18378
18638
|
const specs = discoverSpecs();
|
|
18379
18639
|
const plans = discoverPlans();
|
|
18640
|
+
const agentAuthoredCodeThisTurn = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0));
|
|
18641
|
+
const turnAuthoredCode = agentAuthoredCodeThisTurn || !!baseline && allForReview.some((f) => changedSinceBaseline(f, baseline));
|
|
18642
|
+
const authorshipIsObservable = !!actionSummary && actionSummary.transcript_windowed !== "orphaned" || !!baseline;
|
|
18643
|
+
const canSeeTurnAuthorship = authorshipIsObservable;
|
|
18644
|
+
let earlyFold = null;
|
|
18380
18645
|
const latestPrompt = conversation?.prompts?.[conversation.prompts.length - 1]?.prompt ?? "";
|
|
18381
18646
|
if (/^\s*\/verity-/i.test(latestPrompt)) {
|
|
18647
|
+
const setupAuthored = [
|
|
18648
|
+
...actionSummary?.files_edited ?? [],
|
|
18649
|
+
...actionSummary?.files_created ?? []
|
|
18650
|
+
];
|
|
18651
|
+
if (setupAuthored.length > 0) {
|
|
18652
|
+
const adopted = absorbIntoBaseline(setupAuthored, baselineSessionId);
|
|
18653
|
+
logEvent("baseline_absorbed", { skip: "verity-command", offered: setupAuthored.length, adopted });
|
|
18654
|
+
}
|
|
18382
18655
|
await passAndExit("Verity command \u2014 skipping analysis", "verity-command");
|
|
18383
18656
|
}
|
|
18384
|
-
if (
|
|
18657
|
+
if (shouldSkipForBareAck({ prompt: latestPrompt, turnAuthoredCode, canSeeTurnAuthorship })) {
|
|
18385
18658
|
await passAndExit("Bare acknowledgment \u2014 skipping analysis", "bare-acknowledgment");
|
|
18386
18659
|
}
|
|
18387
|
-
|
|
18388
|
-
if (isReflectionQuestion(assistantResponse) && !agentAuthoredCodeThisTurn) {
|
|
18660
|
+
if (isReflectionQuestion(assistantResponse) && !turnAuthoredCode && canSeeTurnAuthorship) {
|
|
18389
18661
|
await passAndExit("Reflection-prompt turn \u2014 skipping analysis", "reflection-prompt");
|
|
18390
18662
|
}
|
|
18391
18663
|
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
@@ -18431,7 +18703,11 @@ async function runAnalyze(opts, globals) {
|
|
|
18431
18703
|
);
|
|
18432
18704
|
}
|
|
18433
18705
|
if (analysisMode === "skip") {
|
|
18434
|
-
await passAndExit(
|
|
18706
|
+
await passAndExit(
|
|
18707
|
+
"Skip mode \u2014 no code work to analyze",
|
|
18708
|
+
"skip-mode",
|
|
18709
|
+
turnAuthoredCode ? "capacity" : void 0
|
|
18710
|
+
);
|
|
18435
18711
|
}
|
|
18436
18712
|
let staticResults = {
|
|
18437
18713
|
tool: "@codacy/analysis-cli",
|
|
@@ -18483,10 +18759,43 @@ async function runAnalyze(opts, globals) {
|
|
|
18483
18759
|
contentHash = hashResult.hash;
|
|
18484
18760
|
if (analysisMode !== "plan") {
|
|
18485
18761
|
const scoped = scopeToAuthored(allForReview, actionSummary);
|
|
18486
|
-
|
|
18762
|
+
const canTrustNoneAuthored = scoped.signal === "none-authored" && authorshipIsObservable;
|
|
18763
|
+
if (canTrustNoneAuthored && !hasNonEditAuthorship(actionSummary, sessionAuthoredCode)) {
|
|
18487
18764
|
await passAndExit("No agent-authored code this turn \u2014 working-tree changes were not authored by this session", "zero-increment");
|
|
18488
18765
|
}
|
|
18489
|
-
|
|
18766
|
+
if (scoped.signal === "none-authored" && !authorshipIsObservable) {
|
|
18767
|
+
logEvent("none_authored_unverifiable", {
|
|
18768
|
+
reason: "orphaned_window_no_baseline",
|
|
18769
|
+
would_have_skipped: allForReview.length
|
|
18770
|
+
});
|
|
18771
|
+
}
|
|
18772
|
+
const narrowingIsTrustworthy = scoped.signal === "authored" && scoped.files.length > 0 && actionSummary?.transcript_windowed !== "orphaned";
|
|
18773
|
+
let recoveredScope = [];
|
|
18774
|
+
if (!narrowingIsTrustworthy && transcriptPath) {
|
|
18775
|
+
try {
|
|
18776
|
+
earlyFold = fold(transcriptPath, { changedFiles: allForReview, repoRoot: repoRoot() });
|
|
18777
|
+
const authoredWhole = new Set(earlyFold.authored.map((a) => a.p));
|
|
18778
|
+
if (authoredWhole.size > 0) {
|
|
18779
|
+
const root = repoRoot();
|
|
18780
|
+
recoveredScope = allForReview.filter((f) => authoredWhole.has(toRepoRelative(f, root)));
|
|
18781
|
+
}
|
|
18782
|
+
logEvent("scope_recovered_from_fold", {
|
|
18783
|
+
window_saw: scoped.files.length,
|
|
18784
|
+
fold_saw: authoredWhole.size,
|
|
18785
|
+
recovered: recoveredScope.length,
|
|
18786
|
+
would_have_widened_to: allForReview.length
|
|
18787
|
+
});
|
|
18788
|
+
} catch {
|
|
18789
|
+
earlyFold = null;
|
|
18790
|
+
}
|
|
18791
|
+
}
|
|
18792
|
+
if (!narrowingIsTrustworthy && scoped.signal === "authored" && recoveredScope.length === 0) {
|
|
18793
|
+
logEvent("scope_widened_orphaned_window", {
|
|
18794
|
+
would_have_sent: scoped.files.length,
|
|
18795
|
+
widened_to: allForReview.length
|
|
18796
|
+
});
|
|
18797
|
+
}
|
|
18798
|
+
const baseForReview = narrowingIsTrustworthy ? scoped.files : recoveredScope.length > 0 ? recoveredScope : allForReview;
|
|
18490
18799
|
const recentForReview = narrowToRecent(baseForReview, baselineSessionId);
|
|
18491
18800
|
if (!opts.skipStatic && isCodacyAvailable()) {
|
|
18492
18801
|
let allScannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles]));
|
|
@@ -18524,19 +18833,13 @@ async function runAnalyze(opts, globals) {
|
|
|
18524
18833
|
snapshotResult = generateSnapshotDiffs(codeDelta.files);
|
|
18525
18834
|
}
|
|
18526
18835
|
currentCommit = getCurrentCommit();
|
|
18527
|
-
|
|
18528
|
-
const iterResult = checkMaxIterations(currentCommit, maxIterations, contentHash ?? void 0);
|
|
18529
|
-
if (iterResult.skip) await passAndExit(iterResult.skip, "iteration-cap");
|
|
18530
|
-
iteration = iterResult.iteration;
|
|
18836
|
+
iteration = readIterationState(currentCommit).iteration;
|
|
18531
18837
|
}
|
|
18532
18838
|
}
|
|
18533
18839
|
if (analysisMode === "plan") {
|
|
18534
18840
|
recordAnalysisStart();
|
|
18535
18841
|
currentCommit = getCurrentCommit();
|
|
18536
|
-
|
|
18537
|
-
const iterResult = checkMaxIterations(currentCommit, maxIterations);
|
|
18538
|
-
if (iterResult.skip) await passAndExit(iterResult.skip, "iteration-cap");
|
|
18539
|
-
iteration = iterResult.iteration;
|
|
18842
|
+
iteration = readIterationState(currentCommit).iteration;
|
|
18540
18843
|
}
|
|
18541
18844
|
const contextFiles = gatherContextFiles(contextFilePaths, codeDelta.files);
|
|
18542
18845
|
for (const f of codeDelta.files) {
|
|
@@ -18603,7 +18906,7 @@ async function runAnalyze(opts, globals) {
|
|
|
18603
18906
|
let foldConservation = null;
|
|
18604
18907
|
if (transcriptPath) {
|
|
18605
18908
|
try {
|
|
18606
|
-
foldResult = fold(transcriptPath, { changedFiles: allForReview, repoRoot: repoRoot() });
|
|
18909
|
+
foldResult = earlyFold ?? fold(transcriptPath, { changedFiles: allForReview, repoRoot: repoRoot() });
|
|
18607
18910
|
foldConservation = checkConservation(allForReview, foldResult, repoRoot());
|
|
18608
18911
|
if (!foldConservation.holds) {
|
|
18609
18912
|
process.stderr.write(
|
|
@@ -18709,7 +19012,30 @@ async function runAnalyze(opts, globals) {
|
|
|
18709
19012
|
hasUserPrompt: (conversation?.prompts?.length ?? 0) > 0,
|
|
18710
19013
|
isTTY: process.stdout.isTTY === true
|
|
18711
19014
|
});
|
|
19015
|
+
const excludedByReason = {};
|
|
19016
|
+
for (const e of codeDelta.excluded ?? []) {
|
|
19017
|
+
excludedByReason[e.reason] = (excludedByReason[e.reason] ?? 0) + 1;
|
|
19018
|
+
}
|
|
19019
|
+
const coverageTelemetry = {
|
|
19020
|
+
// git's whole answer, before ANY narrowing. The number that has never been sent.
|
|
19021
|
+
changed_all: allChanged.length,
|
|
19022
|
+
analyzable: analyzable.length,
|
|
19023
|
+
reviewable: reviewable.length,
|
|
19024
|
+
security: securityFiles.length,
|
|
19025
|
+
// after the allowlist, before authorship scoping and the caps
|
|
19026
|
+
for_review: allForReview.length,
|
|
19027
|
+
// what actually reaches the reviewer
|
|
19028
|
+
sent: codeDelta.files.length,
|
|
19029
|
+
// the two silent narrowings, counted separately so they can be told apart
|
|
19030
|
+
capped_out: actionSummary?.capped_out?.length ?? 0,
|
|
19031
|
+
excluded: (codeDelta.excluded ?? []).length,
|
|
19032
|
+
excluded_by_reason: excludedByReason,
|
|
19033
|
+
// was the transcript itself truncated? The 256 KB window means "this turn"
|
|
19034
|
+
// can quietly mean "the last 256 KB of it".
|
|
19035
|
+
transcript_windowed: actionSummary?.transcript_windowed ?? null
|
|
19036
|
+
};
|
|
18712
19037
|
const requestBody = {
|
|
19038
|
+
coverage_telemetry: coverageTelemetry,
|
|
18713
19039
|
static_results: staticResults,
|
|
18714
19040
|
code_delta: codeDelta,
|
|
18715
19041
|
changed_files: allForReview,
|
|
@@ -18795,6 +19121,54 @@ async function runAnalyze(opts, globals) {
|
|
|
18795
19121
|
// replaced a population floor with ≈35% power that was sub-integer for
|
|
18796
19122
|
// three-quarters of the fleet.
|
|
18797
19123
|
conservation: foldConservation,
|
|
19124
|
+
// ⚠ VRT-52 — RECORDED, NOT APPLIED. The number nobody has.
|
|
19125
|
+
//
|
|
19126
|
+
// The whole "task-scoped delta" design space rests on an assumption that
|
|
19127
|
+
// has been observed exactly ONCE: that delta files routinely belong to
|
|
19128
|
+
// earlier work. Three designs were built on it and all three were killed
|
|
19129
|
+
// adversarially — two by measurement — so before another is attempted,
|
|
19130
|
+
// measure the base rate.
|
|
19131
|
+
//
|
|
19132
|
+
// `authored_under_earlier_goal` counts delta paths whose LAST authorship
|
|
19133
|
+
// event precedes the seq of the goal now in force. Both numbers come from
|
|
19134
|
+
// the same append-only counter (`nextSeq`), so the comparison is exact.
|
|
19135
|
+
//
|
|
19136
|
+
// Keyed on the GOAL, deliberately, not on the task id. The task classifier
|
|
19137
|
+
// reported `is_new_task` on two consecutive turns of one task 25 seconds
|
|
19138
|
+
// apart, so a task-keyed number would measure its unreliability rather
|
|
19139
|
+
// than the phenomenon. And this only became meaningful once `recordGoal`
|
|
19140
|
+
// stopped letting a bare "ok" supersede the goal — before that the seq
|
|
19141
|
+
// advanced every turn and this would have degenerated to "not edited this
|
|
19142
|
+
// turn", which is the exact mistake that sank one of the three designs.
|
|
19143
|
+
//
|
|
19144
|
+
// Changes no payload the reviewer sees, no narrowing, no verdict.
|
|
19145
|
+
vrt52: (() => {
|
|
19146
|
+
const goalSeq = memory?.projection.goal?.seq;
|
|
19147
|
+
if (goalSeq === void 0 || !memorySession) return { known: false };
|
|
19148
|
+
const lastSeq2 = new Map(
|
|
19149
|
+
foldDossier(memorySession.d).authored_all.map((a) => [a.path, a.last_seq])
|
|
19150
|
+
);
|
|
19151
|
+
let earlier = 0;
|
|
19152
|
+
let unknown = 0;
|
|
19153
|
+
for (const f of codeDelta.files) {
|
|
19154
|
+
const seen = lastSeq2.get(f.path);
|
|
19155
|
+
if (seen === void 0) unknown++;
|
|
19156
|
+
else if (seen < goalSeq) earlier++;
|
|
19157
|
+
}
|
|
19158
|
+
return {
|
|
19159
|
+
known: true,
|
|
19160
|
+
goal_seq: goalSeq,
|
|
19161
|
+
delta: codeDelta.files.length,
|
|
19162
|
+
// Files this delta carries that were last written under an EARLIER
|
|
19163
|
+
// instruction. If this stays near zero, VRT-52's code half is
|
|
19164
|
+
// unnecessary and should be closed saying so.
|
|
19165
|
+
authored_under_earlier_goal: earlier,
|
|
19166
|
+
// Delta files the dossier has no authorship record for at all —
|
|
19167
|
+
// pre-existing tree state, or an authorship channel the fold cannot
|
|
19168
|
+
// see. Reported separately so a blind spot is never counted as a zero.
|
|
19169
|
+
no_authorship_record: unknown
|
|
19170
|
+
};
|
|
19171
|
+
})(),
|
|
18798
19172
|
// P2 — RECORDED, NOT APPLIED. What this run WOULD have reviewed if it
|
|
18799
19173
|
// narrowed to the within-session increment: what changed since the last
|
|
18800
19174
|
// VERDICT rather than since task start.
|
|
@@ -18829,6 +19203,14 @@ async function runAnalyze(opts, globals) {
|
|
|
18829
19203
|
const latest = conversation.prompts[conversation.prompts.length - 1];
|
|
18830
19204
|
const goalPrompt = resolveGoalPrompt(conversation.prompts) ?? { entry: latest, turnsBack: 0 };
|
|
18831
19205
|
intentContext.user_prompt = goalPrompt.entry.prompt;
|
|
19206
|
+
if (isContinuationPrompt(intentContext.user_prompt)) {
|
|
19207
|
+
const carried = memory?.projection.goal?.text;
|
|
19208
|
+
if (carried && !isContinuationPrompt(carried)) {
|
|
19209
|
+
intentContext.continuation_prompt = latest.prompt;
|
|
19210
|
+
intentContext.user_prompt = carried;
|
|
19211
|
+
logEvent("goal_from_dossier", { chars: carried.length });
|
|
19212
|
+
}
|
|
19213
|
+
}
|
|
18832
19214
|
if (goalPrompt.turnsBack > 0) {
|
|
18833
19215
|
intentContext.continuation_prompt = latest.prompt;
|
|
18834
19216
|
logEvent("goal_walked_back", { turns_back: goalPrompt.turnsBack });
|
|
@@ -19033,6 +19415,13 @@ async function runAnalyze(opts, globals) {
|
|
|
19033
19415
|
});
|
|
19034
19416
|
}
|
|
19035
19417
|
let intentRepeatCount = 0;
|
|
19418
|
+
const priorPendingFingerprints = memorySession ? (() => {
|
|
19419
|
+
try {
|
|
19420
|
+
return foldDossier(memorySession.d).meta.recent_pending_sigs ?? [];
|
|
19421
|
+
} catch {
|
|
19422
|
+
return [];
|
|
19423
|
+
}
|
|
19424
|
+
})() : [];
|
|
19036
19425
|
if (memorySession) {
|
|
19037
19426
|
try {
|
|
19038
19427
|
recordVerdict(memorySession.d, {
|
|
@@ -19055,7 +19444,10 @@ async function runAnalyze(opts, globals) {
|
|
|
19055
19444
|
// What next turn reads as `emittedLast`. A suppressed turn did not
|
|
19056
19445
|
// speak, so it cannot be the cause of the turn after it — which is what
|
|
19057
19446
|
// keeps this from becoming a permanent gag.
|
|
19058
|
-
emitted: !silenced
|
|
19447
|
+
emitted: !silenced,
|
|
19448
|
+
// Fingerprinted for the NEXT turn's repeat check. Reviewer pending items
|
|
19449
|
+
// carry no `pattern_id`, so their content is the only available key.
|
|
19450
|
+
pendingTexts: (response.pending_items ?? []).map((p) => String(p.description ?? p.title ?? p.reason ?? "")).filter(Boolean)
|
|
19059
19451
|
});
|
|
19060
19452
|
intentRepeatCount = Math.max(0, (foldDossier(memorySession.d).meta.intent_repeat?.consecutive ?? 1) - 1);
|
|
19061
19453
|
} catch {
|
|
@@ -19155,9 +19547,41 @@ async function runAnalyze(opts, globals) {
|
|
|
19155
19547
|
reverify_by: response.reverify_by
|
|
19156
19548
|
});
|
|
19157
19549
|
const grantNudge = grantWarning ? ` \u26A0\uFE0F ${grantWarning}` : "";
|
|
19158
|
-
|
|
19550
|
+
let capReleased = false;
|
|
19551
|
+
let effectiveDecision = decision;
|
|
19552
|
+
if (decision === "FAIL") {
|
|
19553
|
+
const blocking = (response.findings ?? []).filter((f) => {
|
|
19554
|
+
const sev = String(f.severity ?? "").toLowerCase();
|
|
19555
|
+
return sev === "critical" || sev === "high";
|
|
19556
|
+
});
|
|
19557
|
+
const fingerprint = findingsFingerprint(blocking);
|
|
19558
|
+
const prior = readIterationState(currentCommit);
|
|
19559
|
+
const sameProblem = isSameProblem(prior.fingerprint, fingerprint);
|
|
19560
|
+
const nextIteration = sameProblem ? prior.iteration + 1 : 1;
|
|
19561
|
+
const maxIterations = parseInt(opts.maxIterations, 10);
|
|
19562
|
+
writeIteration(nextIteration, currentCommit, contentHash ?? void 0, fingerprint);
|
|
19563
|
+
iteration = nextIteration;
|
|
19564
|
+
if (nextIteration > maxIterations) {
|
|
19565
|
+
capReleased = true;
|
|
19566
|
+
effectiveDecision = "WARN";
|
|
19567
|
+
logEvent("iteration_cap_released", { iteration: nextIteration, fingerprint });
|
|
19568
|
+
}
|
|
19569
|
+
}
|
|
19570
|
+
if (capReleased) {
|
|
19571
|
+
const findings = response.findings ?? [];
|
|
19572
|
+
const lines = findings.slice(0, 5).map((f) => ` [${String(f.severity ?? "?").toUpperCase()}] ${String(f.title ?? f.message ?? "")} (${String(f.file ?? "?")}:${String(f.line ?? "?")})`);
|
|
19573
|
+
emitVerdict({
|
|
19574
|
+
proposed: "WARN",
|
|
19575
|
+
changed: skipCoverageChanged,
|
|
19576
|
+
coverage: reviewCoverage,
|
|
19577
|
+
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.
|
|
19578
|
+
${lines.join("\n")}`,
|
|
19579
|
+
agentContext: null,
|
|
19580
|
+
silenced: true
|
|
19581
|
+
});
|
|
19582
|
+
}
|
|
19583
|
+
switch (effectiveDecision) {
|
|
19159
19584
|
case "FAIL": {
|
|
19160
|
-
writeIteration(iteration + 1, currentCommit, contentHash ?? void 0);
|
|
19161
19585
|
const assessment = response.assessment;
|
|
19162
19586
|
const narrative = assessment?.narrative ?? "";
|
|
19163
19587
|
const findings = response.findings ?? [];
|
|
@@ -19242,7 +19666,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
19242
19666
|
// Subject to the SAME cycle cut as PASS/WARN. Suppressing here is safe:
|
|
19243
19667
|
// the findings themselves are rendered above by the blocking renderer,
|
|
19244
19668
|
// so what the cut removes is the repeated commentary, never the defect.
|
|
19245
|
-
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount),
|
|
19669
|
+
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount, priorPendingFingerprints),
|
|
19246
19670
|
// The coverage note is silenced with it — half a channel is still a channel.
|
|
19247
19671
|
silenced: !!silenced,
|
|
19248
19672
|
openElsewhere
|
|
@@ -19263,7 +19687,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
19263
19687
|
changed: skipCoverageChanged,
|
|
19264
19688
|
coverage: reviewCoverage,
|
|
19265
19689
|
userSummary,
|
|
19266
|
-
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount),
|
|
19690
|
+
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount, priorPendingFingerprints),
|
|
19267
19691
|
// The coverage note is silenced with it — half a channel is still a channel.
|
|
19268
19692
|
silenced: !!silenced,
|
|
19269
19693
|
openElsewhere
|
|
@@ -19283,7 +19707,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
19283
19707
|
changed: skipCoverageChanged,
|
|
19284
19708
|
coverage: reviewCoverage,
|
|
19285
19709
|
userSummary,
|
|
19286
|
-
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount),
|
|
19710
|
+
agentContext: silenced ? null : agentContextFor(response, intentRepeatCount, priorPendingFingerprints),
|
|
19287
19711
|
// The coverage note is silenced with it — half a channel is still a channel.
|
|
19288
19712
|
silenced: !!silenced,
|
|
19289
19713
|
openElsewhere
|
|
@@ -20204,6 +20628,13 @@ async function confirmExistingLogin(serviceUrl, remote, opts) {
|
|
|
20204
20628
|
return "handled";
|
|
20205
20629
|
}
|
|
20206
20630
|
if (!who.ok) {
|
|
20631
|
+
const denial = authDenialRemedy(who.error);
|
|
20632
|
+
if (denial) {
|
|
20633
|
+
console.log("");
|
|
20634
|
+
printWarn(`Your existing Verity credential was rejected (${denial.code}).`);
|
|
20635
|
+
printInfo(` ${denial.remedy}`);
|
|
20636
|
+
return "drive-login";
|
|
20637
|
+
}
|
|
20207
20638
|
if (existing.data.userId != null) {
|
|
20208
20639
|
printInfo(`Logged in as ${existing.data.email ?? `user #${existing.data.userId}`} (cached \u2014 could not reach the Verity service). \u2713`);
|
|
20209
20640
|
} else {
|
|
@@ -20219,15 +20650,14 @@ async function confirmExistingLogin(serviceUrl, remote, opts) {
|
|
|
20219
20650
|
return "drive-login";
|
|
20220
20651
|
}
|
|
20221
20652
|
async function runOptionalAuth(resolution, opts = {}) {
|
|
20222
|
-
|
|
20223
|
-
|
|
20224
|
-
|
|
20225
|
-
|
|
20226
|
-
|
|
20227
|
-
|
|
20228
|
-
|
|
20229
|
-
|
|
20230
|
-
}
|
|
20653
|
+
if (resolution.source === "default") {
|
|
20654
|
+
printInfo(`No Verity service configured on this machine \u2014 using the default: ${resolution.url}`);
|
|
20655
|
+
}
|
|
20656
|
+
const heal = await maybeHealServiceUrl(resolution, opts.verbose);
|
|
20657
|
+
const serviceUrl = heal.serviceUrl;
|
|
20658
|
+
const healed = heal.healed;
|
|
20659
|
+
if (healed) {
|
|
20660
|
+
printInfo(" Log in below to re-register this project and repair ~/.verity/credentials.");
|
|
20231
20661
|
}
|
|
20232
20662
|
let remote = "";
|
|
20233
20663
|
try {
|
|
@@ -20245,8 +20675,10 @@ async function runOptionalAuth(resolution, opts = {}) {
|
|
|
20245
20675
|
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
20246
20676
|
console.log("");
|
|
20247
20677
|
console.log(" Signing in is optional. What it does:");
|
|
20248
|
-
console.log(" - Confirms you
|
|
20249
|
-
console.log("
|
|
20678
|
+
console.log(" - Confirms which repositories you can write to. The GitHub token is");
|
|
20679
|
+
console.log(" used once for that check, then discarded \u2014 Verity never stores it.");
|
|
20680
|
+
console.log(" - One login covers every repository you can write to \u2014 other repos");
|
|
20681
|
+
console.log(" need no further sign-in on this machine.");
|
|
20250
20682
|
console.log(" - It does NOT give Verity access to your code. Code checked by the gate");
|
|
20251
20683
|
console.log(" is analyzed in memory and discarded \u2014 we never store your code.");
|
|
20252
20684
|
console.log(" - It is required to store and access run history for this repo");
|
|
@@ -20261,17 +20693,10 @@ async function runOptionalAuth(resolution, opts = {}) {
|
|
|
20261
20693
|
localOnlyNote();
|
|
20262
20694
|
return;
|
|
20263
20695
|
}
|
|
20264
|
-
if (!remote) {
|
|
20265
|
-
printWarn("No git remote found \u2014 cannot authenticate yet.");
|
|
20266
|
-
localOnlyNote();
|
|
20267
|
-
return;
|
|
20268
|
-
}
|
|
20269
|
-
const projectName = parseRemote(remote)?.repo ?? (0, import_node_path23.basename)(process.cwd());
|
|
20270
20696
|
printInfo("Authenticating with GitHub\u2026");
|
|
20271
|
-
const result = await
|
|
20697
|
+
const result = await loginOnce({ serviceUrl, remote: remote || void 0, verbose: opts.verbose });
|
|
20272
20698
|
if (result.ok) {
|
|
20273
|
-
|
|
20274
|
-
printInfo(`Logged in${who ? ` as ${who}` : ""} \u2713 \u2014 runs, history, and cloud memory now sync to Verity.`);
|
|
20699
|
+
await reportLoginOutcome(result.data, { remote: remote || void 0, verbose: opts.verbose });
|
|
20275
20700
|
} else {
|
|
20276
20701
|
printWarn(`Authentication did not complete: ${result.error}`);
|
|
20277
20702
|
localOnlyNote();
|
|
@@ -20422,8 +20847,8 @@ function registerInitCommand(program2) {
|
|
|
20422
20847
|
console.log("");
|
|
20423
20848
|
try {
|
|
20424
20849
|
const globals = program2.opts();
|
|
20425
|
-
const
|
|
20426
|
-
await runOptionalAuth(
|
|
20850
|
+
const resolution = await resolveServiceUrlForAuth(globals.serviceUrl);
|
|
20851
|
+
await runOptionalAuth(resolution, {
|
|
20427
20852
|
token: globals.token,
|
|
20428
20853
|
verbose: globals.verbose
|
|
20429
20854
|
});
|
|
@@ -21039,6 +21464,12 @@ function registerTelemetryCommands(program2) {
|
|
|
21039
21464
|
printError(urlResult.error);
|
|
21040
21465
|
process.exit(1);
|
|
21041
21466
|
}
|
|
21467
|
+
const pin = await checkTokenPin(tokenResult.data.token, urlResult.data);
|
|
21468
|
+
if (!pin.attach && pin.reason === "unpinnable") {
|
|
21469
|
+
printWarn("Your Verity credential does not record which service issued it, so this endpoint");
|
|
21470
|
+
printWarn(" cannot be verified \u2014 a repository could point your telemetry elsewhere.");
|
|
21471
|
+
printWarn(' Run "verity login" to re-issue the credential; Verity can check it after that.');
|
|
21472
|
+
}
|
|
21042
21473
|
const result = await installTelemetry(urlResult.data);
|
|
21043
21474
|
if (!result.ok) {
|
|
21044
21475
|
printError(result.error);
|
|
@@ -21087,7 +21518,8 @@ function registerTelemetryCommands(program2) {
|
|
|
21087
21518
|
}
|
|
21088
21519
|
|
|
21089
21520
|
// src/cli.ts
|
|
21090
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.28.1-experimental.
|
|
21521
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.28.1-experimental.db64e52").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async () => {
|
|
21522
|
+
setUserNamedServiceUrl(program.opts().serviceUrl);
|
|
21091
21523
|
try {
|
|
21092
21524
|
await foldLegacyLocalCredential();
|
|
21093
21525
|
} catch {
|