@codacy/verity-cli 0.28.1-experimental.dbd87b1 → 0.28.1-experimental.dfb3ce6
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/README.md +5 -0
- package/bin/verity.js +429 -30
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,6 +63,11 @@ No re-setup needed: your token, Standard, and run history all carry over.
|
|
|
63
63
|
| `verity token create --name <name>` | Mint a service token for CI (`--expires <days>` optional) |
|
|
64
64
|
| `verity token list` | List this repository's tokens (ids and metadata only) |
|
|
65
65
|
| `verity token revoke <id>` | Revoke a token by id |
|
|
66
|
+
| `verity sessions list` | List your logins — device, last use, expiry (logins expire after 90 days) |
|
|
67
|
+
| `verity sessions revoke <id>` | Revoke one login by session id |
|
|
68
|
+
| `verity logout` | Sign out on this machine |
|
|
69
|
+
| `verity logout --others` | Sign out every OTHER machine (e.g. a lost laptop) |
|
|
70
|
+
| `verity logout --all` | Sign out everywhere, including here |
|
|
66
71
|
| `verity hooks install` | Wire Claude Code hooks |
|
|
67
72
|
| `verity standard push` | Upload project Standard |
|
|
68
73
|
| **Knowledge** | |
|
package/bin/verity.js
CHANGED
|
@@ -10636,14 +10636,14 @@ async function readGlobalCredential(remote) {
|
|
|
10636
10636
|
const parsed = parseCredentialLine(line);
|
|
10637
10637
|
if (parsed && parsed.remote === key) last = parsed.rec;
|
|
10638
10638
|
}
|
|
10639
|
-
if (last) return last;
|
|
10639
|
+
if (last) return { ...last, keyed: true };
|
|
10640
10640
|
}
|
|
10641
10641
|
let plain = null;
|
|
10642
10642
|
for (const line of lines) {
|
|
10643
10643
|
const parsed = parseCredentialLine(line);
|
|
10644
10644
|
if (parsed && parsed.remote === "") plain = parsed.rec;
|
|
10645
10645
|
}
|
|
10646
|
-
return plain;
|
|
10646
|
+
return plain ? { ...plain, keyed: false } : null;
|
|
10647
10647
|
}
|
|
10648
10648
|
async function upsertGlobalCredential(remote, rec) {
|
|
10649
10649
|
const path = globalCredentialsPath();
|
|
@@ -10707,6 +10707,36 @@ async function removeSupersededUserCredentials(loginServiceUrl, loginUserId) {
|
|
|
10707
10707
|
}
|
|
10708
10708
|
return removed;
|
|
10709
10709
|
}
|
|
10710
|
+
async function removeGlobalCredential(remote) {
|
|
10711
|
+
const path = globalCredentialsPath();
|
|
10712
|
+
let content;
|
|
10713
|
+
try {
|
|
10714
|
+
content = await (0, import_promises.readFile)(path, "utf-8");
|
|
10715
|
+
} catch {
|
|
10716
|
+
return false;
|
|
10717
|
+
}
|
|
10718
|
+
const key = encodeRemoteKey(remote);
|
|
10719
|
+
const kept = [];
|
|
10720
|
+
let removed = false;
|
|
10721
|
+
for (const line of content.split("\n")) {
|
|
10722
|
+
const parsed = parseCredentialLine(line);
|
|
10723
|
+
if (parsed && parsed.remote === key) {
|
|
10724
|
+
removed = true;
|
|
10725
|
+
continue;
|
|
10726
|
+
}
|
|
10727
|
+
kept.push(line);
|
|
10728
|
+
}
|
|
10729
|
+
if (!removed) return false;
|
|
10730
|
+
while (kept.length && kept[kept.length - 1].trim() === "") kept.pop();
|
|
10731
|
+
try {
|
|
10732
|
+
await (0, import_promises.writeFile)(path, kept.length ? kept.join("\n") + "\n" : "", { mode: 384 });
|
|
10733
|
+
await (0, import_promises.chmod)(path, 384).catch(() => {
|
|
10734
|
+
});
|
|
10735
|
+
} catch {
|
|
10736
|
+
return false;
|
|
10737
|
+
}
|
|
10738
|
+
return true;
|
|
10739
|
+
}
|
|
10710
10740
|
function parseLocalCredentialFile(content) {
|
|
10711
10741
|
const tokenMatch = content.match(/token:\s*((?:gate_|verity_)[a-f0-9]+)/);
|
|
10712
10742
|
if (!tokenMatch) return null;
|
|
@@ -11188,7 +11218,13 @@ async function resolveToken(flagToken) {
|
|
|
11188
11218
|
if (rec) {
|
|
11189
11219
|
return {
|
|
11190
11220
|
ok: true,
|
|
11191
|
-
data: {
|
|
11221
|
+
data: {
|
|
11222
|
+
token: rec.token,
|
|
11223
|
+
source: "global",
|
|
11224
|
+
userId: rec.userId,
|
|
11225
|
+
email: rec.email,
|
|
11226
|
+
keyed: rec.keyed
|
|
11227
|
+
}
|
|
11192
11228
|
};
|
|
11193
11229
|
}
|
|
11194
11230
|
const local = await readLegacyLocalCredential();
|
|
@@ -11220,6 +11256,16 @@ function reverifyNudge(who) {
|
|
|
11220
11256
|
}
|
|
11221
11257
|
return null;
|
|
11222
11258
|
}
|
|
11259
|
+
function isLegacyPerRepoCredential(auth2) {
|
|
11260
|
+
return auth2.source === "local" || auth2.source === "global" && auth2.keyed === true;
|
|
11261
|
+
}
|
|
11262
|
+
async function shouldUpgradeOnLogin(auth2) {
|
|
11263
|
+
if (!isLegacyPerRepoCredential(auth2)) return false;
|
|
11264
|
+
if (auth2.userId == null) return true;
|
|
11265
|
+
const bare = await readGlobalCredential("");
|
|
11266
|
+
if (bare?.userId == null) return true;
|
|
11267
|
+
return bare.userId === auth2.userId;
|
|
11268
|
+
}
|
|
11223
11269
|
function authDenialRemedy(error) {
|
|
11224
11270
|
if (error.startsWith("STALE_VERIFICATION")) {
|
|
11225
11271
|
return {
|
|
@@ -11233,6 +11279,12 @@ function authDenialRemedy(error) {
|
|
|
11233
11279
|
remedy: 'No access grant for this repository \u2014 run "verity login" to refresh your grants (a repo granted after your last login needs one), or get write access to it.'
|
|
11234
11280
|
};
|
|
11235
11281
|
}
|
|
11282
|
+
if (error.startsWith("INVALID_TOKEN")) {
|
|
11283
|
+
return {
|
|
11284
|
+
code: "INVALID_TOKEN",
|
|
11285
|
+
remedy: 'Your Verity login has expired or was revoked \u2014 run "verity login" to sign in again.'
|
|
11286
|
+
};
|
|
11287
|
+
}
|
|
11236
11288
|
return null;
|
|
11237
11289
|
}
|
|
11238
11290
|
async function probeService(serviceUrl, verbose) {
|
|
@@ -11273,6 +11325,7 @@ async function maybeHealServiceUrl(resolution, verbose) {
|
|
|
11273
11325
|
|
|
11274
11326
|
// src/lib/register.ts
|
|
11275
11327
|
var readline = __toESM(require("node:readline/promises"));
|
|
11328
|
+
var import_node_os = require("node:os");
|
|
11276
11329
|
|
|
11277
11330
|
// src/lib/provider-auth.ts
|
|
11278
11331
|
var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
@@ -11460,6 +11513,15 @@ async function registerProject(opts) {
|
|
|
11460
11513
|
}
|
|
11461
11514
|
return { ok: true, data: { projectId: project_id, serviceUrl: service_url, email, userId } };
|
|
11462
11515
|
}
|
|
11516
|
+
function deviceLabel() {
|
|
11517
|
+
const override = process.env.VERITY_DEVICE_LABEL?.trim();
|
|
11518
|
+
if (override) return override;
|
|
11519
|
+
try {
|
|
11520
|
+
return (0, import_node_os.hostname)() || void 0;
|
|
11521
|
+
} catch {
|
|
11522
|
+
return void 0;
|
|
11523
|
+
}
|
|
11524
|
+
}
|
|
11463
11525
|
async function loginOnce(opts) {
|
|
11464
11526
|
const usingTokenOverride = Boolean(process.env.VERITY_PROVIDER_TOKEN);
|
|
11465
11527
|
const providerAuth = await githubDeviceFlow();
|
|
@@ -11480,13 +11542,19 @@ async function loginOnce(opts) {
|
|
|
11480
11542
|
path: "/auth/login",
|
|
11481
11543
|
serviceUrl: opts.serviceUrl,
|
|
11482
11544
|
extraHeaders: { "X-Provider-Token": providerToken },
|
|
11545
|
+
// Label the session so its owner can tell their machines apart in
|
|
11546
|
+
// `verity sessions list` — a list of identical "login" rows is unusable when
|
|
11547
|
+
// the question is "which of these is the laptop I lost?". The hostname is the
|
|
11548
|
+
// useful default; VERITY_DEVICE_LABEL overrides it for anyone who would
|
|
11549
|
+
// rather not send it. Server-side it is sanitized and capped.
|
|
11550
|
+
body: { device: deviceLabel() },
|
|
11483
11551
|
verbose: opts.verbose,
|
|
11484
11552
|
cmd: "login"
|
|
11485
11553
|
});
|
|
11486
11554
|
if (!result.ok) {
|
|
11487
11555
|
return { ok: false, error: result.error };
|
|
11488
11556
|
}
|
|
11489
|
-
const { token, service_url, user_id, user, repo_count } = result.data;
|
|
11557
|
+
const { token, service_url, user_id, user, repo_count, expires_at } = result.data;
|
|
11490
11558
|
const loginUserId = user_id ?? user?.id ?? void 0;
|
|
11491
11559
|
try {
|
|
11492
11560
|
await upsertGlobalCredential("", {
|
|
@@ -11510,15 +11578,16 @@ async function loginOnce(opts) {
|
|
|
11510
11578
|
email: user?.email,
|
|
11511
11579
|
userId: loginUserId,
|
|
11512
11580
|
repoCount: repo_count ?? 0,
|
|
11513
|
-
prunedCredentials: pruned
|
|
11581
|
+
prunedCredentials: pruned,
|
|
11582
|
+
expiresAt: expires_at
|
|
11514
11583
|
}
|
|
11515
11584
|
};
|
|
11516
11585
|
}
|
|
11517
11586
|
|
|
11518
11587
|
// src/commands/auth.ts
|
|
11519
11588
|
function registerAuthCommands(program2) {
|
|
11520
|
-
const
|
|
11521
|
-
|
|
11589
|
+
const auth2 = program2.command("auth").description("Manage project authentication");
|
|
11590
|
+
auth2.command("register").description("Register a project with Verity").requiredOption("--project <name>", "Project name").option("--remote <url>", "Git remote URL (auto-detected if omitted)").action(async (opts) => {
|
|
11522
11591
|
const globals = program2.opts();
|
|
11523
11592
|
const serviceUrl = globals.serviceUrl ?? DEFAULT_SERVICE_URL;
|
|
11524
11593
|
let remote = opts.remote;
|
|
@@ -11545,7 +11614,7 @@ function registerAuthCommands(program2) {
|
|
|
11545
11614
|
if (email) printInfo(`Authenticated as: ${email}`);
|
|
11546
11615
|
printJson({ project_id: projectId, service_url: resolvedUrl });
|
|
11547
11616
|
});
|
|
11548
|
-
|
|
11617
|
+
auth2.command("verify").description("Verify the current token is valid").action(async () => {
|
|
11549
11618
|
const globals = program2.opts();
|
|
11550
11619
|
const tokenResult = await resolveToken(globals.token);
|
|
11551
11620
|
if (!tokenResult.ok) {
|
|
@@ -11571,7 +11640,7 @@ function registerAuthCommands(program2) {
|
|
|
11571
11640
|
printInfo(`Token valid. Project: ${result.data.project_name}`);
|
|
11572
11641
|
printJson(result.data);
|
|
11573
11642
|
});
|
|
11574
|
-
|
|
11643
|
+
auth2.command("discover").description("Check if a project is registered").option("--remote <url>", "Git remote URL (auto-detected if omitted)").action(async (opts) => {
|
|
11575
11644
|
const globals = program2.opts();
|
|
11576
11645
|
let remote = opts.remote;
|
|
11577
11646
|
if (!remote) {
|
|
@@ -11627,13 +11696,19 @@ function registerLoginCommand(program2) {
|
|
|
11627
11696
|
const who = await whoami(existing.data.token, serviceUrl, globals.verbose);
|
|
11628
11697
|
if (who.ok && who.data.logged_in) {
|
|
11629
11698
|
const nudge = reverifyNudge(who.data);
|
|
11630
|
-
|
|
11699
|
+
const upgrade = await shouldUpgradeOnLogin(existing.data);
|
|
11700
|
+
if (!nudge && !upgrade) {
|
|
11631
11701
|
printInfo(`Already logged in as ${who.data.email ?? `user #${who.data.user_id}`}. \u2713`);
|
|
11632
11702
|
printInfo(" Re-authenticate with: verity login --force");
|
|
11633
11703
|
return;
|
|
11634
11704
|
}
|
|
11635
|
-
|
|
11636
|
-
|
|
11705
|
+
if (nudge) {
|
|
11706
|
+
printWarn(nudge);
|
|
11707
|
+
printInfo("Re-verifying your repository access\u2026");
|
|
11708
|
+
} else {
|
|
11709
|
+
printInfo("You are signed in with a per-repository token (the old format).");
|
|
11710
|
+
printInfo(" Upgrading to a single login that covers every repository you can write to\u2026");
|
|
11711
|
+
}
|
|
11637
11712
|
} else if (who.ok && who.data.anonymous) {
|
|
11638
11713
|
printInfo("You have an anonymous token (the gate runs, but nothing is saved). Logging you in\u2026");
|
|
11639
11714
|
} else if (!who.ok) {
|
|
@@ -11650,6 +11725,10 @@ function registerLoginCommand(program2) {
|
|
|
11650
11725
|
const identity = out.email ?? (out.userId != null ? `user #${out.userId}` : "your account");
|
|
11651
11726
|
printInfo(`Logged in as ${identity}. \u2713`);
|
|
11652
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
|
+
}
|
|
11653
11732
|
printInfo(" Runs, history, and cloud memory now sync to Verity everywhere you have write access.");
|
|
11654
11733
|
if (out.prunedCredentials > 0) {
|
|
11655
11734
|
printVerbose(`Pruned ${out.prunedCredentials} superseded per-repository credential line(s).`, globals.verbose);
|
|
@@ -11680,14 +11759,24 @@ function registerLoginCommand(program2) {
|
|
|
11680
11759
|
const rec = await readGlobalCredential(remote);
|
|
11681
11760
|
if (rec && rec.token !== out.token) {
|
|
11682
11761
|
const otherBackend = rec.serviceUrl != null && rec.serviceUrl !== out.serviceUrl;
|
|
11762
|
+
const otherIdentity = rec.userId != null && out.userId != null && rec.userId !== out.userId;
|
|
11683
11763
|
if (otherBackend) {
|
|
11684
11764
|
printWarn(` Note: this repository is pinned to a different Verity service (${rec.serviceUrl})`);
|
|
11685
11765
|
printWarn(" by its own credential line, which takes precedence here \u2014 this login does not");
|
|
11686
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.");
|
|
11687
11774
|
} else {
|
|
11688
11775
|
const kind = rec.userId != null ? "superseded per-repository" : "anonymous project-specific";
|
|
11689
11776
|
printWarn(` Note: this repository has a ${kind} credential that takes`);
|
|
11690
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.");
|
|
11691
11780
|
}
|
|
11692
11781
|
}
|
|
11693
11782
|
}
|
|
@@ -11809,6 +11898,177 @@ function registerTokenCommand(program2) {
|
|
|
11809
11898
|
});
|
|
11810
11899
|
}
|
|
11811
11900
|
|
|
11901
|
+
// src/commands/sessions.ts
|
|
11902
|
+
function shortDate(iso) {
|
|
11903
|
+
return iso ? iso.slice(0, 10) : "\u2014";
|
|
11904
|
+
}
|
|
11905
|
+
function daysUntil(iso) {
|
|
11906
|
+
if (!iso) return null;
|
|
11907
|
+
const ms = Date.parse(iso);
|
|
11908
|
+
if (Number.isNaN(ms)) return null;
|
|
11909
|
+
return Math.round((ms - Date.now()) / 864e5);
|
|
11910
|
+
}
|
|
11911
|
+
async function auth(globals) {
|
|
11912
|
+
const tokenResult = await resolveToken(globals.token);
|
|
11913
|
+
if (!tokenResult.ok) {
|
|
11914
|
+
printError(tokenResult.error);
|
|
11915
|
+
process.exit(1);
|
|
11916
|
+
}
|
|
11917
|
+
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
11918
|
+
if (!urlResult.ok) {
|
|
11919
|
+
printError(urlResult.error);
|
|
11920
|
+
process.exit(1);
|
|
11921
|
+
}
|
|
11922
|
+
return { token: tokenResult.data.token, serviceUrl: urlResult.data };
|
|
11923
|
+
}
|
|
11924
|
+
function explain(error) {
|
|
11925
|
+
if (error.startsWith("FORBIDDEN")) {
|
|
11926
|
+
printInfo(' Sessions belong to a logged-in account \u2014 run "verity login" first.');
|
|
11927
|
+
} else if (error.startsWith("INVALID_TOKEN")) {
|
|
11928
|
+
printInfo(' This login has expired or was revoked \u2014 run "verity login" to sign in again.');
|
|
11929
|
+
}
|
|
11930
|
+
}
|
|
11931
|
+
function registerSessionsCommands(program2) {
|
|
11932
|
+
const sessions = program2.command("sessions").description("List and revoke your Verity logins (one per machine)");
|
|
11933
|
+
sessions.command("list").description("List your active logins \u2014 device, last use, and expiry").option("--json", "Output raw JSON").action(async (opts) => {
|
|
11934
|
+
const globals = program2.opts();
|
|
11935
|
+
const { token, serviceUrl } = await auth(globals);
|
|
11936
|
+
const result = await apiRequest({
|
|
11937
|
+
method: "GET",
|
|
11938
|
+
path: "/auth/sessions",
|
|
11939
|
+
serviceUrl,
|
|
11940
|
+
token,
|
|
11941
|
+
verbose: globals.verbose,
|
|
11942
|
+
cmd: "sessions"
|
|
11943
|
+
});
|
|
11944
|
+
if (!result.ok) {
|
|
11945
|
+
printError(result.error);
|
|
11946
|
+
explain(result.error);
|
|
11947
|
+
process.exit(1);
|
|
11948
|
+
}
|
|
11949
|
+
if (opts.json) {
|
|
11950
|
+
printJson(result.data);
|
|
11951
|
+
return;
|
|
11952
|
+
}
|
|
11953
|
+
const list = result.data.sessions;
|
|
11954
|
+
if (list.length === 0) {
|
|
11955
|
+
printInfo('No active logins. (Run "verity login".)');
|
|
11956
|
+
return;
|
|
11957
|
+
}
|
|
11958
|
+
printInfo(`${list.length} active login${list.length === 1 ? "" : "s"}:`);
|
|
11959
|
+
printInfo("");
|
|
11960
|
+
printInfo(`${"SESSION ID".padEnd(38)}${"DEVICE".padEnd(24)}${"CREATED".padEnd(12)}${"LAST USED".padEnd(12)}EXPIRES`);
|
|
11961
|
+
for (const s of list) {
|
|
11962
|
+
const days = daysUntil(s.expires_at);
|
|
11963
|
+
const expiry = s.expires_at ? `${shortDate(s.expires_at)}${days != null ? ` (${days}d)` : ""}` : "never";
|
|
11964
|
+
const device = (s.device ?? "login").slice(0, 22);
|
|
11965
|
+
printInfo(
|
|
11966
|
+
`${s.id.padEnd(38)}${device.padEnd(24)}${shortDate(s.created_at).padEnd(12)}${shortDate(s.last_used_at).padEnd(12)}${expiry}${s.current ? " \u2190 this machine" : ""}`
|
|
11967
|
+
);
|
|
11968
|
+
}
|
|
11969
|
+
printInfo("");
|
|
11970
|
+
printInfo("Revoke one: verity sessions revoke <session-id>");
|
|
11971
|
+
printInfo("Sign out everywhere else: verity logout --others");
|
|
11972
|
+
});
|
|
11973
|
+
sessions.command("revoke <session-id>").description("Revoke one login. Revoking this machine's also clears the local credential").action(async (sessionId) => {
|
|
11974
|
+
const globals = program2.opts();
|
|
11975
|
+
const { token, serviceUrl } = await auth(globals);
|
|
11976
|
+
const result = await apiRequest({
|
|
11977
|
+
method: "DELETE",
|
|
11978
|
+
path: `/auth/sessions/${encodeURIComponent(sessionId)}`,
|
|
11979
|
+
serviceUrl,
|
|
11980
|
+
token,
|
|
11981
|
+
verbose: globals.verbose,
|
|
11982
|
+
cmd: "sessions-revoke"
|
|
11983
|
+
});
|
|
11984
|
+
if (!result.ok) {
|
|
11985
|
+
printError(result.error);
|
|
11986
|
+
if (result.http_status === 404) {
|
|
11987
|
+
printInfo(' No session with that id on your account \u2014 check "verity sessions list".');
|
|
11988
|
+
}
|
|
11989
|
+
explain(result.error);
|
|
11990
|
+
process.exit(1);
|
|
11991
|
+
}
|
|
11992
|
+
printInfo(`Session ${sessionId} revoked. \u2713`);
|
|
11993
|
+
if (result.data.was_current) {
|
|
11994
|
+
const cleared = await removeGlobalCredential("");
|
|
11995
|
+
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
|
+
}
|
|
11997
|
+
});
|
|
11998
|
+
}
|
|
11999
|
+
function registerLogoutCommand(program2) {
|
|
12000
|
+
program2.command("logout").description("Sign out of Verity on this machine (--all / --others for every machine)").option("--all", "Revoke every login on every machine, including this one").option("--others", "Revoke every login EXCEPT this machine (e.g. a lost laptop)").action(async (opts) => {
|
|
12001
|
+
const globals = program2.opts();
|
|
12002
|
+
if (opts.all && opts.others) {
|
|
12003
|
+
printError("Use either --all or --others, not both.");
|
|
12004
|
+
process.exit(1);
|
|
12005
|
+
}
|
|
12006
|
+
const { token, serviceUrl } = await auth(globals);
|
|
12007
|
+
if (opts.all || opts.others) {
|
|
12008
|
+
const result = await apiRequest({
|
|
12009
|
+
method: "DELETE",
|
|
12010
|
+
path: opts.others ? "/auth/sessions?others=true" : "/auth/sessions",
|
|
12011
|
+
serviceUrl,
|
|
12012
|
+
token,
|
|
12013
|
+
verbose: globals.verbose,
|
|
12014
|
+
cmd: "logout"
|
|
12015
|
+
});
|
|
12016
|
+
if (!result.ok) {
|
|
12017
|
+
printError(result.error);
|
|
12018
|
+
explain(result.error);
|
|
12019
|
+
process.exit(1);
|
|
12020
|
+
}
|
|
12021
|
+
const n = result.data.revoked;
|
|
12022
|
+
printInfo(`Revoked ${n} login${n === 1 ? "" : "s"}. \u2713`);
|
|
12023
|
+
if (opts.others) {
|
|
12024
|
+
printInfo(" This machine is still signed in.");
|
|
12025
|
+
return;
|
|
12026
|
+
}
|
|
12027
|
+
const cleared2 = await removeGlobalCredential("");
|
|
12028
|
+
if (cleared2) printInfo(" Local credential cleared.");
|
|
12029
|
+
printInfo(' Run "verity login" to sign back in.');
|
|
12030
|
+
return;
|
|
12031
|
+
}
|
|
12032
|
+
const list = await apiRequest({
|
|
12033
|
+
method: "GET",
|
|
12034
|
+
path: "/auth/sessions",
|
|
12035
|
+
serviceUrl,
|
|
12036
|
+
token,
|
|
12037
|
+
verbose: globals.verbose,
|
|
12038
|
+
cmd: "logout"
|
|
12039
|
+
});
|
|
12040
|
+
if (!list.ok) {
|
|
12041
|
+
printError(list.error);
|
|
12042
|
+
explain(list.error);
|
|
12043
|
+
process.exit(1);
|
|
12044
|
+
}
|
|
12045
|
+
const current = list.data.sessions.find((s) => s.current);
|
|
12046
|
+
if (!current) {
|
|
12047
|
+
printWarn("This machine is not signed in with a Verity login.");
|
|
12048
|
+
const cleared2 = await removeGlobalCredential("");
|
|
12049
|
+
if (cleared2) printInfo(" Cleared the local login credential anyway.");
|
|
12050
|
+
return;
|
|
12051
|
+
}
|
|
12052
|
+
const revoked = await apiRequest({
|
|
12053
|
+
method: "DELETE",
|
|
12054
|
+
path: `/auth/sessions/${current.id}`,
|
|
12055
|
+
serviceUrl,
|
|
12056
|
+
token,
|
|
12057
|
+
verbose: globals.verbose,
|
|
12058
|
+
cmd: "logout"
|
|
12059
|
+
});
|
|
12060
|
+
if (!revoked.ok) {
|
|
12061
|
+
printError(revoked.error);
|
|
12062
|
+
explain(revoked.error);
|
|
12063
|
+
process.exit(1);
|
|
12064
|
+
}
|
|
12065
|
+
const cleared = await removeGlobalCredential("");
|
|
12066
|
+
printInfo("Signed out on this machine. \u2713");
|
|
12067
|
+
if (cleared) printInfo(" Local credential cleared.");
|
|
12068
|
+
printInfo(' Your other machines are unaffected \u2014 use "verity logout --all" for all of them.');
|
|
12069
|
+
});
|
|
12070
|
+
}
|
|
12071
|
+
|
|
11812
12072
|
// src/lib/hooks.ts
|
|
11813
12073
|
var import_promises4 = require("node:fs/promises");
|
|
11814
12074
|
var import_node_path5 = require("node:path");
|
|
@@ -12430,7 +12690,7 @@ function getRecentCommitMessages() {
|
|
|
12430
12690
|
// src/lib/context-identity.ts
|
|
12431
12691
|
var import_node_crypto2 = require("node:crypto");
|
|
12432
12692
|
var import_node_fs5 = require("node:fs");
|
|
12433
|
-
var
|
|
12693
|
+
var import_node_os2 = require("node:os");
|
|
12434
12694
|
var import_node_path6 = require("node:path");
|
|
12435
12695
|
var SHARED_SENTINELS = /* @__PURE__ */ new Set([
|
|
12436
12696
|
"",
|
|
@@ -12485,7 +12745,7 @@ function contextIdentity(input) {
|
|
|
12485
12745
|
}
|
|
12486
12746
|
function verityHome() {
|
|
12487
12747
|
const override = process.env.VERITY_HOME;
|
|
12488
|
-
return override && override.trim() ? (0, import_node_path6.resolve)(override) : (0, import_node_path6.join)((0,
|
|
12748
|
+
return override && override.trim() ? (0, import_node_path6.resolve)(override) : (0, import_node_path6.join)((0, import_node_os2.homedir)(), ".verity");
|
|
12489
12749
|
}
|
|
12490
12750
|
function dossierDir(identity) {
|
|
12491
12751
|
return (0, import_node_path6.join)(verityHome(), "sessions", identity.userKey, identity.treeKey, identity.sessionKey);
|
|
@@ -13579,6 +13839,13 @@ function reduce(state, events, now) {
|
|
|
13579
13839
|
});
|
|
13580
13840
|
break;
|
|
13581
13841
|
}
|
|
13842
|
+
case "goal_delivered": {
|
|
13843
|
+
const g = state.goal.find((x) => x.status === "active");
|
|
13844
|
+
if (!g) break;
|
|
13845
|
+
if (g.delivered) break;
|
|
13846
|
+
g.delivered = { at: ev.at, seq: ev.seq, summary: ev.summary };
|
|
13847
|
+
break;
|
|
13848
|
+
}
|
|
13582
13849
|
case "authored": {
|
|
13583
13850
|
authoredEvents++;
|
|
13584
13851
|
const e = byPath.get(ev.path) ?? {
|
|
@@ -13693,6 +13960,18 @@ function reduce(state, events, now) {
|
|
|
13693
13960
|
}
|
|
13694
13961
|
case "verdict": {
|
|
13695
13962
|
state.meta.last_verdict_seq = ev.seq;
|
|
13963
|
+
state.meta.channel = {
|
|
13964
|
+
emittedLast: ev.emitted === true,
|
|
13965
|
+
// Reset by ANY movement, so the counter measures a standstill rather
|
|
13966
|
+
// than session length.
|
|
13967
|
+
consecutiveIdle: ev.idle === false ? 0 : (state.meta.channel?.consecutiveIdle ?? 0) + 1
|
|
13968
|
+
};
|
|
13969
|
+
state.meta.last_adjudication = ev.intent_verdict ? { verdict: ev.intent_verdict, score: ev.intent_score ?? null, at: ev.at, decision: ev.decision } : void 0;
|
|
13970
|
+
if (ev.intent_sig) {
|
|
13971
|
+
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
|
+
} else {
|
|
13973
|
+
state.meta.intent_repeat = void 0;
|
|
13974
|
+
}
|
|
13696
13975
|
state.meta.watermark = {
|
|
13697
13976
|
sha: ev.head_sha,
|
|
13698
13977
|
reviewed_hash: ev.watermark_sha,
|
|
@@ -13778,7 +14057,12 @@ function compactState(s) {
|
|
|
13778
14057
|
const ms = (iso) => Date.parse(iso) || 0;
|
|
13779
14058
|
return {
|
|
13780
14059
|
v: 1,
|
|
13781
|
-
|
|
14060
|
+
// ⚠ APPEND-ONLY POSITIONALLY. Indices 11-13 carry `delivered`; a cache row
|
|
14061
|
+
// written before it existed has length 11 and expands with `delivered`
|
|
14062
|
+
// absent, which is the correct reading of "nothing had been delivered yet".
|
|
14063
|
+
// Inserting rather than appending would silently re-interpret every existing
|
|
14064
|
+
// cached row.
|
|
14065
|
+
g: s.goal.map((g) => [g.seq, ms(g.at), g.hash, g.superseded_by, g.text ?? 0, g.text_len ?? 0, g.source ?? 0, g.status ?? 0, g.repeats ?? 0, g.truncated ? 1 : 0, g.collapsed ? 1 : 0, g.delivered ? ms(g.delivered.at) : 0, g.delivered?.seq ?? 0, g.delivered?.summary ?? 0]),
|
|
13782
14066
|
a: s.authored?.map((a) => [a.path, a.origin, a.edits, a.hunks, a.adds, a.dels, a.hash_now, a.hash_at_last_verdict, a.first_seq, a.last_seq]) ?? null,
|
|
13783
14067
|
n: s.not_mine?.map((n) => [n.path, n.reason, ms(n.at), n.head_sha]) ?? null,
|
|
13784
14068
|
t: s.statements.map((x) => [x.anchor_key, x.file, x.line, x.pattern_id, x.title_hash, x.register, x.line_sha, x.said_at_seq, ms(x.said_at), x.outcome, x.outcome_at ? ms(x.outcome_at) : 0, x.repeats, x.carried ? 1 : 0]),
|
|
@@ -13824,7 +14108,14 @@ function expandState(raw) {
|
|
|
13824
14108
|
...x[7] ? { status: x[7] } : {},
|
|
13825
14109
|
...x[8] ? { repeats: x[8] } : {},
|
|
13826
14110
|
...x[9] ? { truncated: true } : {},
|
|
13827
|
-
...x[10] ? { collapsed: true } : {}
|
|
14111
|
+
...x[10] ? { collapsed: true } : {},
|
|
14112
|
+
...x[11] ? {
|
|
14113
|
+
delivered: {
|
|
14114
|
+
at: iso(x[11]),
|
|
14115
|
+
seq: x[12] ?? 0,
|
|
14116
|
+
summary: x[13] || ""
|
|
14117
|
+
}
|
|
14118
|
+
} : {}
|
|
13828
14119
|
})),
|
|
13829
14120
|
authored: decodedAuthored,
|
|
13830
14121
|
// The cache stores the BOUNDED list, so this is the bounded list too. That
|
|
@@ -14038,9 +14329,14 @@ function projectMemory(state, opts) {
|
|
|
14038
14329
|
seq: active.seq,
|
|
14039
14330
|
superseded,
|
|
14040
14331
|
truncated: active.truncated === true,
|
|
14041
|
-
collapsed: state.meta.collapsed.goal ?? 0
|
|
14332
|
+
collapsed: state.meta.collapsed.goal ?? 0,
|
|
14333
|
+
...active.delivered && { delivered: active.delivered }
|
|
14042
14334
|
};
|
|
14043
14335
|
}
|
|
14336
|
+
if (state.meta.last_adjudication) {
|
|
14337
|
+
const a = state.meta.last_adjudication;
|
|
14338
|
+
p.last_adjudication = { verdict: a.verdict, score: a.score, at: a.at };
|
|
14339
|
+
}
|
|
14044
14340
|
const ageOf = (seq, carried) => carried ? "carried" : seq > lastVerdictSeq ? "this_turn" : "this_session";
|
|
14045
14341
|
if (opts.spoken.length > 0) {
|
|
14046
14342
|
p.statements = opts.spoken.map((s) => ({
|
|
@@ -14466,6 +14762,10 @@ function toStatus(n) {
|
|
|
14466
14762
|
}
|
|
14467
14763
|
function recordVerdict(d, v) {
|
|
14468
14764
|
const root = repoRoot();
|
|
14765
|
+
if (v.intent?.verdict === "aligned") {
|
|
14766
|
+
const summary = (v.intent.implemented ?? "").trim().slice(0, 400);
|
|
14767
|
+
if (summary) appendEvent(d, { k: "goal_delivered", summary });
|
|
14768
|
+
}
|
|
14469
14769
|
const lines = /* @__PURE__ */ new Map();
|
|
14470
14770
|
for (const f of v.findings) {
|
|
14471
14771
|
if (!f.file || typeof f.line !== "number" || !f.pattern_id) continue;
|
|
@@ -14493,15 +14793,32 @@ function recordVerdict(d, v) {
|
|
|
14493
14793
|
line_sha: at !== void 0 ? lineSha(at) : null
|
|
14494
14794
|
});
|
|
14495
14795
|
}
|
|
14796
|
+
const foldedNow = foldDossier(d);
|
|
14797
|
+
const sig = intentSignature(v.intent, {
|
|
14798
|
+
goalSeq: foldedNow.goal.find((g) => g.status === "active")?.seq ?? 0,
|
|
14799
|
+
idle: v.idle !== false
|
|
14800
|
+
});
|
|
14496
14801
|
appendEvent(d, {
|
|
14497
14802
|
k: "verdict",
|
|
14498
14803
|
run_id: v.runId,
|
|
14499
14804
|
head_sha: getCurrentCommit(),
|
|
14500
14805
|
watermark_sha: v.watermarkSha,
|
|
14501
14806
|
branch: v.branch,
|
|
14502
|
-
decision: v.decision
|
|
14807
|
+
decision: v.decision,
|
|
14808
|
+
...sig && { intent_sig: sig },
|
|
14809
|
+
emitted: v.emitted === true,
|
|
14810
|
+
idle: v.idle !== false,
|
|
14811
|
+
...v.intent?.verdict && { intent_verdict: v.intent.verdict },
|
|
14812
|
+
...typeof v.intent?.score === "number" && { intent_score: v.intent.score }
|
|
14503
14813
|
});
|
|
14504
14814
|
}
|
|
14815
|
+
function intentSignature(intent, ctx) {
|
|
14816
|
+
if (!intent?.verdict) return null;
|
|
14817
|
+
if (intent.verdict !== "misaligned" && intent.verdict !== "partial") return null;
|
|
14818
|
+
const goal = ctx ? `g${ctx.goalSeq}` : "g?";
|
|
14819
|
+
const moved = ctx?.idle === false ? "active" : "idle";
|
|
14820
|
+
return `${intent.verdict}:${goal}:${moved}`;
|
|
14821
|
+
}
|
|
14505
14822
|
function toRegister(severity) {
|
|
14506
14823
|
switch (severity) {
|
|
14507
14824
|
case "critical":
|
|
@@ -16287,7 +16604,7 @@ function resolveTaskContext(opts) {
|
|
|
16287
16604
|
// src/lib/cli-version.ts
|
|
16288
16605
|
function cliVersion() {
|
|
16289
16606
|
try {
|
|
16290
|
-
return true ? "0.28.1-experimental.
|
|
16607
|
+
return true ? "0.28.1-experimental.dfb3ce6" : "dev";
|
|
16291
16608
|
} catch {
|
|
16292
16609
|
return "dev";
|
|
16293
16610
|
}
|
|
@@ -16512,6 +16829,7 @@ var KNOWN_RECORD_TYPES = /* @__PURE__ */ new Set([
|
|
|
16512
16829
|
"subagent"
|
|
16513
16830
|
]);
|
|
16514
16831
|
var EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "NotebookEdit", "MultiEdit"]);
|
|
16832
|
+
var DISPATCH_TOOLS = /* @__PURE__ */ new Set(["Task", "Workflow"]);
|
|
16515
16833
|
var COMMAND_CLASSES = [
|
|
16516
16834
|
[/\b(npm|yarn|pnpm|bun)\s+(run\s+)?test\b|\bvitest\b|\bjest\b|\bpytest\b|\bgo test\b/, "test"],
|
|
16517
16835
|
[/\b(npm|yarn|pnpm|bun)\s+(run\s+)?build\b|\btsc\b|\bwebpack\b|\bcargo build\b/, "build"],
|
|
@@ -16638,6 +16956,7 @@ function fold(transcriptPath, opts = {}) {
|
|
|
16638
16956
|
totalRecords: 0,
|
|
16639
16957
|
malformed: 0,
|
|
16640
16958
|
subagentFiles: 0,
|
|
16959
|
+
dispatched: 0,
|
|
16641
16960
|
subagentSkipped: 0,
|
|
16642
16961
|
compactions: 0,
|
|
16643
16962
|
complete: false
|
|
@@ -16664,7 +16983,7 @@ function fold(transcriptPath, opts = {}) {
|
|
|
16664
16983
|
if (type === "system" && record.subtype === "compact_boundary") {
|
|
16665
16984
|
result.coverage.compactions++;
|
|
16666
16985
|
}
|
|
16667
|
-
collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, opts.repoRoot);
|
|
16986
|
+
collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, opts.repoRoot, result.coverage);
|
|
16668
16987
|
}
|
|
16669
16988
|
};
|
|
16670
16989
|
try {
|
|
@@ -16736,7 +17055,7 @@ function classifyUnobserved(path) {
|
|
|
16736
17055
|
if (/\.(png|jpg|jpeg|gif|pdf|zip|woff2?|ico|mp4)$/i.test(path)) return "binary";
|
|
16737
17056
|
return "no_edit_record";
|
|
16738
17057
|
}
|
|
16739
|
-
function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, repoRoot2) {
|
|
17058
|
+
function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, repoRoot2, tally) {
|
|
16740
17059
|
const message = record.message;
|
|
16741
17060
|
const content = message?.content ?? record.content;
|
|
16742
17061
|
const blocks = Array.isArray(content) ? content : content && typeof content === "object" ? [content] : [];
|
|
@@ -16758,6 +17077,9 @@ function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse
|
|
|
16758
17077
|
byPath.set(path, entry);
|
|
16759
17078
|
}
|
|
16760
17079
|
}
|
|
17080
|
+
if (DISPATCH_TOOLS.has(name) && tally) {
|
|
17081
|
+
tally.dispatched += 1;
|
|
17082
|
+
}
|
|
16761
17083
|
if (name === "Bash") {
|
|
16762
17084
|
const cmd = typeof input.command === "string" ? input.command : "";
|
|
16763
17085
|
if (cmd) {
|
|
@@ -16838,9 +17160,16 @@ function buildAgentContext(input) {
|
|
|
16838
17160
|
}
|
|
16839
17161
|
if (input.intentVerdict === "misaligned" || input.intentVerdict === "partial") {
|
|
16840
17162
|
const gap = input.intentGaps?.[0];
|
|
16841
|
-
|
|
16842
|
-
|
|
16843
|
-
|
|
17163
|
+
const repeat = input.intentRepeat ?? 0;
|
|
17164
|
+
if (repeat > 0) {
|
|
17165
|
+
lines.push(
|
|
17166
|
+
`- Same intent flag as the last ${repeat === 1 ? "turn" : `${repeat} turns`}, on a goal that has not changed. Nothing new here \u2014 do not relay or re-explain it again; either act on it or carry on.`
|
|
17167
|
+
);
|
|
17168
|
+
} else {
|
|
17169
|
+
lines.push(
|
|
17170
|
+
`- This does not look like the change that was asked for` + (gap ? `: ${gap}` : ".") + " Not blocking \u2014 but check it against what you were asked to do."
|
|
17171
|
+
);
|
|
17172
|
+
}
|
|
16844
17173
|
}
|
|
16845
17174
|
const agentFindings = (input.findings ?? []).filter((f) => f.scope !== "pre-existing");
|
|
16846
17175
|
for (const f of agentFindings) {
|
|
@@ -16887,6 +17216,14 @@ function buildHookOutput(gateDecision, systemMessage, agentContext) {
|
|
|
16887
17216
|
} : {}
|
|
16888
17217
|
};
|
|
16889
17218
|
}
|
|
17219
|
+
var IDLE_EPISODE_CAP = 3;
|
|
17220
|
+
function channelSilence(input) {
|
|
17221
|
+
const movedSomething = input.newUserPrompt || input.newAuthorship;
|
|
17222
|
+
if (movedSomething) return null;
|
|
17223
|
+
if (input.consecutiveIdle >= IDLE_EPISODE_CAP) return "idle-episode-cap";
|
|
17224
|
+
if (input.emittedLast) return "caused-by-our-own-emission";
|
|
17225
|
+
return null;
|
|
17226
|
+
}
|
|
16890
17227
|
|
|
16891
17228
|
// src/lib/cache-cleanup.ts
|
|
16892
17229
|
var import_node_fs21 = require("node:fs");
|
|
@@ -17714,10 +18051,11 @@ async function readStopHookStdin() {
|
|
|
17714
18051
|
return empty;
|
|
17715
18052
|
}
|
|
17716
18053
|
}
|
|
17717
|
-
function agentContextFor(response) {
|
|
18054
|
+
function agentContextFor(response, intentRepeat = 0) {
|
|
17718
18055
|
const metadata = response.metadata ?? {};
|
|
17719
18056
|
const intent = response.intent_alignment ?? {};
|
|
17720
18057
|
return buildAgentContext({
|
|
18058
|
+
intentRepeat,
|
|
17721
18059
|
gateDecision: String(response.gate_decision ?? ""),
|
|
17722
18060
|
findings: response.findings ?? [],
|
|
17723
18061
|
pendingItems: response.pending_items ?? [],
|
|
@@ -18201,6 +18539,25 @@ async function runAnalyze(opts, globals) {
|
|
|
18201
18539
|
projection: {
|
|
18202
18540
|
v: 1,
|
|
18203
18541
|
authored: foldResult?.authored ?? [],
|
|
18542
|
+
// ⚠ AUTHORED *SINCE THE LAST VERDICT* — a different question from `authored`.
|
|
18543
|
+
//
|
|
18544
|
+
// `authored` above is the fold of the session TRANSCRIPT, which is
|
|
18545
|
+
// cumulative: on turn 3 it still lists the files turn 2 edited. The
|
|
18546
|
+
// account's close-out reads it as "was this file edited since the
|
|
18547
|
+
// statement was raised", and those are not the same set.
|
|
18548
|
+
//
|
|
18549
|
+
// Measured 2026-08-03 (shirt-seller): four real vulnerabilities were
|
|
18550
|
+
// raised on the turn that wrote them, then marked `fixed` 36 seconds
|
|
18551
|
+
// later by a SUMMARY turn that edited nothing — `authored` still said 2,
|
|
18552
|
+
// the turn carried 0 findings, so "gone + file authored" resolved to
|
|
18553
|
+
// fixed. The vulnerabilities were still on disk. A silent false `fixed`
|
|
18554
|
+
// is worse than a false `open`: it retires the statement the Account
|
|
18555
|
+
// exists to keep.
|
|
18556
|
+
//
|
|
18557
|
+
// `hash_at_last_verdict` is frozen at each verdict and `hash_now` tracks
|
|
18558
|
+
// disk, so their inequality IS "changed since we last spoke" — already
|
|
18559
|
+
// computed, already maintained by the divergence machinery.
|
|
18560
|
+
authored_since_verdict: memorySession ? foldDossier(memorySession.d).authored_all.filter((a) => a.hash_now !== a.hash_at_last_verdict).map((a) => a.path) : [],
|
|
18204
18561
|
unobserved: foldResult?.unobserved ?? [],
|
|
18205
18562
|
commands: foldResult?.commands ?? [],
|
|
18206
18563
|
unknown_types: foldResult?.unknownTypes ?? [],
|
|
@@ -18318,6 +18675,8 @@ async function runAnalyze(opts, globals) {
|
|
|
18318
18675
|
message = "Verity: your GitHub verification expired \u2014 run `verity login` to re-verify (local analysis this run)";
|
|
18319
18676
|
} else if (result.error.startsWith("FORBIDDEN")) {
|
|
18320
18677
|
message = "Verity: no access grant for this repository \u2014 run `verity login` to refresh your grants (local analysis this run)";
|
|
18678
|
+
} else if (result.error.startsWith("INVALID_TOKEN")) {
|
|
18679
|
+
message = "Verity: your login expired or was revoked \u2014 run `verity login` to sign in again (local analysis this run)";
|
|
18321
18680
|
} else if (result.http_status && result.http_status >= 400 && result.http_status < 500) {
|
|
18322
18681
|
message = `Verity: request rejected (HTTP ${result.http_status}) \u2014 ${result.error}`;
|
|
18323
18682
|
} else if (result.http_status && result.http_status >= 500) {
|
|
@@ -18334,6 +18693,34 @@ async function runAnalyze(opts, globals) {
|
|
|
18334
18693
|
const sentPaths = codeDelta.files.map((f) => f.path);
|
|
18335
18694
|
const watermarkHash = sentPaths.length > 0 ? computeContentHash(sentPaths) : contentHash;
|
|
18336
18695
|
const watermarkIsPartial = !!codeDelta.truncated;
|
|
18696
|
+
let silenced = null;
|
|
18697
|
+
let turnIsIdleForChannel = true;
|
|
18698
|
+
if (memorySession) {
|
|
18699
|
+
try {
|
|
18700
|
+
const st = foldDossier(memorySession.d);
|
|
18701
|
+
turnIsIdleForChannel = st.authored_all.every((a) => a.hash_now === a.hash_at_last_verdict);
|
|
18702
|
+
silenced = channelSilence({
|
|
18703
|
+
// The BUFFER, not intentContext.user_prompt: the latter falls back to a
|
|
18704
|
+
// linked issue (VRT-53 W4) when no human spoke, and a fallback goal is
|
|
18705
|
+
// not a user utterance. Treating it as one would keep the loop alive on
|
|
18706
|
+
// exactly the autonomous cohort.
|
|
18707
|
+
newUserPrompt: (conversation?.prompts?.length ?? 0) > 0,
|
|
18708
|
+
newAuthorship: !turnIsIdleForChannel,
|
|
18709
|
+
emittedLast: st.meta.channel?.emittedLast === true,
|
|
18710
|
+
consecutiveIdle: st.meta.channel?.consecutiveIdle ?? 0
|
|
18711
|
+
});
|
|
18712
|
+
} catch {
|
|
18713
|
+
silenced = null;
|
|
18714
|
+
}
|
|
18715
|
+
}
|
|
18716
|
+
if (silenced) {
|
|
18717
|
+
logEvent("channel_silenced", {
|
|
18718
|
+
reason: silenced,
|
|
18719
|
+
run_id: response.run_id ?? turnId,
|
|
18720
|
+
decision
|
|
18721
|
+
});
|
|
18722
|
+
}
|
|
18723
|
+
let intentRepeatCount = 0;
|
|
18337
18724
|
if (memorySession) {
|
|
18338
18725
|
try {
|
|
18339
18726
|
recordVerdict(memorySession.d, {
|
|
@@ -18347,8 +18734,18 @@ async function runAnalyze(opts, globals) {
|
|
|
18347
18734
|
pattern_id: f.pattern_id ?? f.rule_id,
|
|
18348
18735
|
title: f.title,
|
|
18349
18736
|
severity: f.severity
|
|
18350
|
-
})) ?? []
|
|
18737
|
+
})) ?? [],
|
|
18738
|
+
intent: response.intent_alignment ?? null,
|
|
18739
|
+
// The same signal F1 introduced: bytes differing from the hash frozen at
|
|
18740
|
+
// the last verdict. A turn that moved nothing is the only kind that can
|
|
18741
|
+
// accumulate a repeat.
|
|
18742
|
+
idle: turnIsIdleForChannel,
|
|
18743
|
+
// What next turn reads as `emittedLast`. A suppressed turn did not
|
|
18744
|
+
// speak, so it cannot be the cause of the turn after it — which is what
|
|
18745
|
+
// keeps this from becoming a permanent gag.
|
|
18746
|
+
emitted: !silenced
|
|
18351
18747
|
});
|
|
18748
|
+
intentRepeatCount = Math.max(0, (foldDossier(memorySession.d).meta.intent_repeat?.consecutive ?? 1) - 1);
|
|
18352
18749
|
} catch {
|
|
18353
18750
|
}
|
|
18354
18751
|
}
|
|
@@ -18538,7 +18935,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
18538
18935
|
if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
|
|
18539
18936
|
userSummary += loginNudge + grantNudge;
|
|
18540
18937
|
printJsonCompact(
|
|
18541
|
-
buildHookOutput("PASS", userSummary, agentContextFor(response))
|
|
18938
|
+
buildHookOutput("PASS", userSummary, silenced ? null : agentContextFor(response, intentRepeatCount))
|
|
18542
18939
|
);
|
|
18543
18940
|
process.exit(0);
|
|
18544
18941
|
break;
|
|
@@ -18552,7 +18949,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
18552
18949
|
if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
|
|
18553
18950
|
userSummary += loginNudge + grantNudge;
|
|
18554
18951
|
printJsonCompact(
|
|
18555
|
-
buildHookOutput("WARN", userSummary, agentContextFor(response))
|
|
18952
|
+
buildHookOutput("WARN", userSummary, silenced ? null : agentContextFor(response, intentRepeatCount))
|
|
18556
18953
|
);
|
|
18557
18954
|
process.exit(0);
|
|
18558
18955
|
break;
|
|
@@ -18970,7 +19367,7 @@ async function runGuard(opts, globals) {
|
|
|
18970
19367
|
cmd: "guard"
|
|
18971
19368
|
});
|
|
18972
19369
|
if (!result.ok) {
|
|
18973
|
-
const authRemedy = result.error.startsWith("STALE_VERIFICATION") ? " Your GitHub verification expired \u2014 run `verity login` to re-verify." : result.error.startsWith("FORBIDDEN") ? " No access grant for this repository \u2014 run `verity login` to refresh your grants." : "";
|
|
19370
|
+
const authRemedy = result.error.startsWith("STALE_VERIFICATION") ? " Your GitHub verification expired \u2014 run `verity login` to re-verify." : result.error.startsWith("FORBIDDEN") ? " No access grant for this repository \u2014 run `verity login` to refresh your grants." : result.error.startsWith("INVALID_TOKEN") ? " Your Verity login expired or was revoked \u2014 run `verity login` to sign in again." : "";
|
|
18974
19371
|
emitAllowNotice(
|
|
18975
19372
|
`\u26A0 Verity ${moment}: ${authRemedy ? "not authorized" : "service offline"} \u2014 ${verb}ed WITHOUT review${authRemedy}`,
|
|
18976
19373
|
`Verity ${moment}: ${authRemedy ? "not authorized" : "service unavailable"} (${result.error}); the ${verb} was allowed WITHOUT a Verity review.${authRemedy}`
|
|
@@ -20354,7 +20751,7 @@ function registerTelemetryCommands(program2) {
|
|
|
20354
20751
|
}
|
|
20355
20752
|
|
|
20356
20753
|
// src/cli.ts
|
|
20357
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.28.1-experimental.
|
|
20754
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.28.1-experimental.dfb3ce6").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async () => {
|
|
20358
20755
|
try {
|
|
20359
20756
|
await foldLegacyLocalCredential();
|
|
20360
20757
|
} catch {
|
|
@@ -20363,6 +20760,8 @@ program.name("verity").description("CLI for Verity quality gate service").versio
|
|
|
20363
20760
|
registerAuthCommands(program);
|
|
20364
20761
|
registerLoginCommand(program);
|
|
20365
20762
|
registerTokenCommand(program);
|
|
20763
|
+
registerSessionsCommands(program);
|
|
20764
|
+
registerLogoutCommand(program);
|
|
20366
20765
|
registerHooksCommands(program);
|
|
20367
20766
|
registerIntentCommands(program);
|
|
20368
20767
|
registerLifecycleCommands(program);
|