@codacy/verity-cli 0.32.3 → 0.32.4-experimental.e769665
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/CHANGELOG.md +67 -0
- package/bin/verity.js +126 -43
- package/data/skills/verity-init/SKILL.md +113 -0
- package/data/skills/verity-setup/SKILL.md +20 -11
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -64,6 +64,73 @@ installs are untouched.
|
|
|
64
64
|
which is the ordinary case for someone whose first Verity install *is* the
|
|
65
65
|
plugin.
|
|
66
66
|
|
|
67
|
+
## [0.32.4] — 2026-09-11
|
|
68
|
+
|
|
69
|
+
**Setup asks you how you want Verity to work.** Until now, installing the Claude
|
|
70
|
+
Code plugin and running setup silently chose **stop-only** review for you — no
|
|
71
|
+
commit gate, no push gate — because the CLI asks its questions on a terminal and
|
|
72
|
+
a skill has none. You were never asked, and nothing said so.
|
|
73
|
+
|
|
74
|
+
### 🎯 `/verity:init` — the one command after installing
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
/plugin install verity@verity
|
|
78
|
+
/verity:init
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
It asks two questions in a single prompt — **when** Verity should review (after
|
|
82
|
+
every turn, before commit, before push) and **how deeply** — then does the whole
|
|
83
|
+
install with your answers. It says plainly that choosing stop-only means your
|
|
84
|
+
commits and pushes are not gated.
|
|
85
|
+
|
|
86
|
+
It also repairs what an install cannot: the plugin has no post-install hook, so
|
|
87
|
+
only a command you run can remove a project's superseded `.claude/skills/verity-*`
|
|
88
|
+
copies, stand down duplicate hooks, and derive the Standard.
|
|
89
|
+
|
|
90
|
+
**`verity init` takes the answers directly too**, for a terminal or CI:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
verity init --moments stop,pre-commit --intensity balanced
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
An unrecognised moment is refused rather than dropped — a typo cannot quietly
|
|
97
|
+
remove a gate.
|
|
98
|
+
|
|
99
|
+
**Signing in stays yours.** `verity login` is a device flow that waits on your
|
|
100
|
+
browser, so the skill asks you to run it in your own terminal instead of hanging
|
|
101
|
+
on a tool call.
|
|
102
|
+
|
|
103
|
+
### 📋 `/verity:setup` keeps the job only a model can do
|
|
104
|
+
|
|
105
|
+
Project-specific rules — *"every route under `api/` uses the auth middleware"* —
|
|
106
|
+
which the CLI deliberately leaves empty. It no longer runs the install, because a
|
|
107
|
+
skill that quietly answers the setup questions is the thing this release fixes.
|
|
108
|
+
|
|
109
|
+
### 🔍 Small projects get static analysis again
|
|
110
|
+
|
|
111
|
+
A service whose whole source was one file derived **no languages at all**, so no
|
|
112
|
+
`.codacy/` config was written and static analysis ran nothing — reported as "No
|
|
113
|
+
static-analysis tool matched this codebase", as though it were a fact about the
|
|
114
|
+
project. The file-count floor exists to stop one stray `.py` turning on Ruff for
|
|
115
|
+
a TypeScript repo; it now applies to a *minority* language rather than to whether
|
|
116
|
+
the project has one.
|
|
117
|
+
|
|
118
|
+
### 👀 Verity says what it is doing, where you can see it
|
|
119
|
+
|
|
120
|
+
The notices added in the previous release went to the agent's channel rather than
|
|
121
|
+
yours, so Verity said "this project is not set up" to Claude, silently. They now
|
|
122
|
+
reach your screen. Verity also asks for `/verity:init` once per plugin version —
|
|
123
|
+
returning after an update, quiet in between.
|
|
124
|
+
|
|
125
|
+
### 🔧 Fixed
|
|
126
|
+
|
|
127
|
+
- `git add … && git commit` was never reviewed: the hook runs before the command,
|
|
128
|
+
so the index it read had no staged files. It now works out what the command
|
|
129
|
+
will stage.
|
|
130
|
+
- On a plugin-only machine, every skill's `verity …` command was `command not
|
|
131
|
+
found`; Verity now tells the agent how to reach the CLI it ships.
|
|
132
|
+
- A verdict the CLI does not understand is reported as unreviewed, not as a pass.
|
|
133
|
+
|
|
67
134
|
## [0.32.3] — 2026-09-10
|
|
68
135
|
|
|
69
136
|
**Three things the first five minutes got wrong.** All reported from the field
|
package/bin/verity.js
CHANGED
|
@@ -10509,7 +10509,7 @@ var SECURITY_PATTERNS = [
|
|
|
10509
10509
|
/Dockerfile/
|
|
10510
10510
|
];
|
|
10511
10511
|
var PROD_SERVICE_URL = "https://ofcamwrjwrkazqvdchko.supabase.co/functions/v1";
|
|
10512
|
-
var DEFAULT_SERVICE_URL = "".length > 0 ? "" : PROD_SERVICE_URL;
|
|
10512
|
+
var DEFAULT_SERVICE_URL = "https://wukeddyzpijoegyajtnc.supabase.co/functions/v1".length > 0 ? "https://wukeddyzpijoegyajtnc.supabase.co/functions/v1" : PROD_SERVICE_URL;
|
|
10513
10513
|
var GITHUB_CLIENT_ID = "Iv23li88HxAi3ZrbYzWh";
|
|
10514
10514
|
var GITHUB_DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
10515
10515
|
var GITHUB_ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
@@ -12936,6 +12936,11 @@ function pluginCliInvocation() {
|
|
|
12936
12936
|
if (verityOnPath()) return null;
|
|
12937
12937
|
return `node ${JSON.stringify((0, import_node_path7.join)(root, "scripts", "verity.mjs"))}`;
|
|
12938
12938
|
}
|
|
12939
|
+
function activePluginVersion() {
|
|
12940
|
+
const fromEnv = process.env.VERITY_PLUGIN_VERSION;
|
|
12941
|
+
if (fromEnv) return fromEnv;
|
|
12942
|
+
return activePluginInstall()?.version ?? null;
|
|
12943
|
+
}
|
|
12939
12944
|
var VERITY_MARKETPLACE = "verity";
|
|
12940
12945
|
var VERITY_MARKETPLACE_REPO = "codacy/verity";
|
|
12941
12946
|
function marketplaceConflict() {
|
|
@@ -13125,8 +13130,8 @@ async function resolveHookWiring() {
|
|
|
13125
13130
|
// src/commands/hooks.ts
|
|
13126
13131
|
var ALL_MOMENTS = ["stop", "pre-commit", "pre-push"];
|
|
13127
13132
|
function parseMomentSelection(raw) {
|
|
13128
|
-
const parts = raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
13129
|
-
if (parts.length === 1 && parts[0]
|
|
13133
|
+
const parts = raw.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
13134
|
+
if (parts.length === 1 && parts[0] === "none") return { moments: [] };
|
|
13130
13135
|
if (parts.length === 0) {
|
|
13131
13136
|
return { error: `--moments was empty. Pass one or more of ${ALL_MOMENTS.join(", ")} \u2014 or "none" to gate nothing.` };
|
|
13132
13137
|
}
|
|
@@ -17080,6 +17085,7 @@ var TOOLED_LANGUAGES = /* @__PURE__ */ new Set([
|
|
|
17080
17085
|
"shell",
|
|
17081
17086
|
"dockerfile"
|
|
17082
17087
|
]);
|
|
17088
|
+
var ACCESSORY_LANGUAGES = /* @__PURE__ */ new Set(["shell", "dockerfile"]);
|
|
17083
17089
|
var MIN_FILES_FOR_LANGUAGE = 3;
|
|
17084
17090
|
var IGNORED_SEGMENTS = [
|
|
17085
17091
|
"node_modules",
|
|
@@ -17329,7 +17335,12 @@ function detectProject(root = repoRoot()) {
|
|
|
17329
17335
|
if (!lang) continue;
|
|
17330
17336
|
languageCounts[lang] = (languageCounts[lang] ?? 0) + 1;
|
|
17331
17337
|
}
|
|
17332
|
-
const
|
|
17338
|
+
const tooled = Object.entries(languageCounts).filter(([lang]) => TOOLED_LANGUAGES.has(lang)).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
|
|
17339
|
+
let languages = tooled.filter(([, count]) => count >= MIN_FILES_FOR_LANGUAGE).map(([lang]) => lang);
|
|
17340
|
+
if (languages.length === 0) {
|
|
17341
|
+
const primary = tooled.filter(([lang]) => !ACCESSORY_LANGUAGES.has(lang));
|
|
17342
|
+
if (primary.length > 0) languages = [primary[0][0]];
|
|
17343
|
+
}
|
|
17333
17344
|
const dependencies = declaredDependencies(root, files);
|
|
17334
17345
|
const existingToolConfigs = [];
|
|
17335
17346
|
for (const [tool, markers] of TOOL_CONFIG_MARKERS) {
|
|
@@ -20937,7 +20948,7 @@ function channelSilence(input) {
|
|
|
20937
20948
|
// src/lib/cli-version.ts
|
|
20938
20949
|
function cliVersion() {
|
|
20939
20950
|
try {
|
|
20940
|
-
return true ? "0.32.
|
|
20951
|
+
return true ? "0.32.4-experimental.e769665" : "dev";
|
|
20941
20952
|
} catch {
|
|
20942
20953
|
return "dev";
|
|
20943
20954
|
}
|
|
@@ -24167,7 +24178,8 @@ var PROJECT_SKILL_NAMES = [
|
|
|
24167
24178
|
"verity-learn",
|
|
24168
24179
|
"verity-memory",
|
|
24169
24180
|
"verity-insights",
|
|
24170
|
-
"verity-reflect"
|
|
24181
|
+
"verity-reflect",
|
|
24182
|
+
"verity-init"
|
|
24171
24183
|
];
|
|
24172
24184
|
var LEGACY_SKILL_NAMES = [
|
|
24173
24185
|
"gate-setup",
|
|
@@ -24204,16 +24216,37 @@ function registerBaselineCommands(program2) {
|
|
|
24204
24216
|
process.chdir(repoRoot());
|
|
24205
24217
|
} catch {
|
|
24206
24218
|
}
|
|
24219
|
+
let sessionId = opts.sessionId;
|
|
24220
|
+
let source = opts.source;
|
|
24221
|
+
if (!process.stdin.isTTY) {
|
|
24222
|
+
const input = (await readStdin()).trim();
|
|
24223
|
+
if (input) {
|
|
24224
|
+
try {
|
|
24225
|
+
const event = JSON.parse(input);
|
|
24226
|
+
sessionId = sessionId ?? event.session_id;
|
|
24227
|
+
source = source ?? event.source;
|
|
24228
|
+
} catch {
|
|
24229
|
+
}
|
|
24230
|
+
}
|
|
24231
|
+
}
|
|
24207
24232
|
const cli = pluginCliInvocation();
|
|
24208
24233
|
const cliLine = cli ? `Verity CLI: this machine has no \`verity\` on PATH \u2014 the plugin carries its own copy. When a Verity skill tells you to run \`verity <args>\`, run \`${cli} <args>\` instead. Everything else about the command is identical.` : null;
|
|
24209
24234
|
if (!verityConfigured()) {
|
|
24210
|
-
const how = isPluginInvocation() ? "
|
|
24235
|
+
const how = isPluginInvocation() ? "Tell the user to run /verity:init \u2014 they type it; you cannot invoke it." : "Tell the user to run `verity init` in their terminal.";
|
|
24211
24236
|
const lines = [
|
|
24212
24237
|
`Verity is installed but this project is not set up yet, so the quality gate will not review anything here. ${how}`
|
|
24213
24238
|
];
|
|
24214
24239
|
if (cliLine) lines.push(cliLine);
|
|
24215
24240
|
process.stdout.write(
|
|
24216
24241
|
JSON.stringify({
|
|
24242
|
+
// ⚠ BOTH FIELDS, AND THEY ARE NOT THE SAME AUDIENCE.
|
|
24243
|
+
// `additionalContext` reaches the AGENT; `systemMessage` reaches
|
|
24244
|
+
// the USER. Emitting only the first is what made this notice
|
|
24245
|
+
// invisible: the agent was told, said nothing unprompted, and a
|
|
24246
|
+
// user who restarted Claude Code specifically to see it saw an
|
|
24247
|
+
// empty screen. guard.ts has carried that distinction in a comment
|
|
24248
|
+
// since 0.30 — the same lesson, unapplied here until someone hit it.
|
|
24249
|
+
systemMessage: isPluginInvocation() ? "Verity: this project is not set up yet, so nothing is being reviewed here. Run /verity:init to configure it." : "Verity: this project is not set up yet, so nothing is being reviewed here. Run `verity init` to configure it.",
|
|
24217
24250
|
hookSpecificOutput: {
|
|
24218
24251
|
hookEventName: "SessionStart",
|
|
24219
24252
|
additionalContext: lines.join("\n")
|
|
@@ -24222,21 +24255,32 @@ function registerBaselineCommands(program2) {
|
|
|
24222
24255
|
);
|
|
24223
24256
|
process.exit(0);
|
|
24224
24257
|
}
|
|
24225
|
-
|
|
24258
|
+
const realStart = source === void 0 || source === "startup" || source === "clear";
|
|
24259
|
+
if (isPluginInvocation() && realStart) {
|
|
24260
|
+
const installed2 = activePluginVersion();
|
|
24261
|
+
const state = await readSetupState().catch(() => null);
|
|
24262
|
+
const stale = staleProjectSkills();
|
|
24226
24263
|
const notices = [];
|
|
24227
24264
|
if (cliLine) notices.push(cliLine);
|
|
24228
|
-
const
|
|
24229
|
-
|
|
24230
|
-
|
|
24231
|
-
|
|
24232
|
-
|
|
24265
|
+
const neverRan = !state?.pluginSetup;
|
|
24266
|
+
const ownsItsWiring = state?.wiring === "project";
|
|
24267
|
+
const needsSetup = !ownsItsWiring && (neverRan || stale.length > 0) && !!installed2;
|
|
24268
|
+
let userMsg = null;
|
|
24269
|
+
if (needsSetup) {
|
|
24270
|
+
userMsg = stale.length > 0 ? `Verity: the plugin is installed \u2014 run /verity:init to finish setting it up here (it will also clear ${stale.length} duplicate skills).` : "Verity: the plugin is installed \u2014 run /verity:init to finish setting it up here.";
|
|
24233
24271
|
notices.push(
|
|
24234
|
-
|
|
24272
|
+
"The Verity plugin is installed here but /verity:init has not been run for this project. Installing the plugin cannot repair a project on its own \u2014 Claude Code has no post-install hook \u2014 so tell the user to run /verity:init (they type it; it is not a skill you can invoke). It asks how they want reviews to run, then does the repair."
|
|
24235
24273
|
);
|
|
24274
|
+
if (stale.length > 0) {
|
|
24275
|
+
notices.push(
|
|
24276
|
+
`Specifically: this project still carries ${stale.length} of its own Verity skills (${stale.slice(0, 3).join(", ")}${stale.length > 3 ? ", \u2026" : ""}) from an npm install, which is why the slash-command list shows each one twice. /verity:init removes them.`
|
|
24277
|
+
);
|
|
24278
|
+
}
|
|
24236
24279
|
}
|
|
24237
24280
|
if (notices.length > 0) {
|
|
24238
24281
|
process.stdout.write(
|
|
24239
24282
|
JSON.stringify({
|
|
24283
|
+
...userMsg ? { systemMessage: userMsg } : {},
|
|
24240
24284
|
hookSpecificOutput: {
|
|
24241
24285
|
hookEventName: "SessionStart",
|
|
24242
24286
|
additionalContext: notices.join("\n")
|
|
@@ -24245,19 +24289,6 @@ function registerBaselineCommands(program2) {
|
|
|
24245
24289
|
);
|
|
24246
24290
|
}
|
|
24247
24291
|
}
|
|
24248
|
-
let sessionId = opts.sessionId;
|
|
24249
|
-
let source = opts.source;
|
|
24250
|
-
if (!process.stdin.isTTY) {
|
|
24251
|
-
const input = (await readStdin()).trim();
|
|
24252
|
-
if (input) {
|
|
24253
|
-
try {
|
|
24254
|
-
const event = JSON.parse(input);
|
|
24255
|
-
sessionId = sessionId ?? event.session_id;
|
|
24256
|
-
source = source ?? event.source;
|
|
24257
|
-
} catch {
|
|
24258
|
-
}
|
|
24259
|
-
}
|
|
24260
|
-
}
|
|
24261
24292
|
if (deferredToPlugin("baseline capture", sessionId ?? process.env.CLAUDE_SESSION_ID ?? null)) {
|
|
24262
24293
|
process.exit(0);
|
|
24263
24294
|
}
|
|
@@ -26296,7 +26327,8 @@ var SKILLS = [
|
|
|
26296
26327
|
"verity-learn",
|
|
26297
26328
|
"verity-memory",
|
|
26298
26329
|
"verity-insights",
|
|
26299
|
-
"verity-reflect"
|
|
26330
|
+
"verity-reflect",
|
|
26331
|
+
"verity-init"
|
|
26300
26332
|
];
|
|
26301
26333
|
var INTENSITY_CHOICES = [
|
|
26302
26334
|
{ id: "lightweight", label: "lightweight", hint: "critical security only, fastest (~3s)" },
|
|
@@ -26317,21 +26349,23 @@ async function momentsFromInstalledHooks() {
|
|
|
26317
26349
|
if (status.guardOn.includes("push")) wired.push("pre-push");
|
|
26318
26350
|
return wired.length > 0 ? wired : DEFAULT_MOMENTS;
|
|
26319
26351
|
}
|
|
26320
|
-
async function askSetupQuestions(defaultsOnly, previous) {
|
|
26321
|
-
const intensityDefault = previous?.intensity ?? "balanced";
|
|
26322
|
-
const momentsDefault = previous?.moments?.length ? previous.moments : await momentsFromInstalledHooks();
|
|
26323
|
-
|
|
26352
|
+
async function askSetupQuestions(defaultsOnly, previous, given = {}) {
|
|
26353
|
+
const intensityDefault = given.intensity ?? previous?.intensity ?? "balanced";
|
|
26354
|
+
const momentsDefault = given.moments ?? (previous?.moments?.length ? previous.moments : await momentsFromInstalledHooks());
|
|
26355
|
+
const askIntensity = !given.intensity;
|
|
26356
|
+
const askMoments = !given.moments;
|
|
26357
|
+
if (defaultsOnly || !askIntensity && !askMoments) {
|
|
26324
26358
|
return { intensity: intensityDefault, moments: momentsDefault, telemetry: "not-asked" };
|
|
26325
26359
|
}
|
|
26326
26360
|
if (previous?.intensity || previous?.moments) {
|
|
26327
26361
|
printInfo(`Current: ${intensityDefault} \xB7 ${momentsDefault.join(", ")} \u2014 press Enter to keep either.`);
|
|
26328
26362
|
}
|
|
26329
|
-
const intensity = await promptChoice(
|
|
26363
|
+
const intensity = given.intensity ?? await promptChoice(
|
|
26330
26364
|
"Analysis intensity \u2014 how deeply should Verity review?",
|
|
26331
26365
|
INTENSITY_CHOICES,
|
|
26332
26366
|
intensityDefault
|
|
26333
26367
|
);
|
|
26334
|
-
const moments = await promptMultiSelect(
|
|
26368
|
+
const moments = given.moments ?? await promptMultiSelect(
|
|
26335
26369
|
"When should Verity review your code?",
|
|
26336
26370
|
MOMENT_CHOICES,
|
|
26337
26371
|
momentsDefault
|
|
@@ -26671,7 +26705,13 @@ function registerInitCommand(program2) {
|
|
|
26671
26705
|
).option(
|
|
26672
26706
|
"--no-plugin",
|
|
26673
26707
|
"Ignore any Claude Code plugin here and wire this project's own skills and hooks"
|
|
26674
|
-
).option("--no-adopt", "Don't offer this repository's existing Standard from the service; synthesize a new one").
|
|
26708
|
+
).option("--no-adopt", "Don't offer this repository's existing Standard from the service; synthesize a new one").option(
|
|
26709
|
+
"--moments <list>",
|
|
26710
|
+
'Answer the review-moments question without asking: stop,pre-commit,pre-push \u2014 or "none"'
|
|
26711
|
+
).option(
|
|
26712
|
+
"--intensity <level>",
|
|
26713
|
+
"Answer the analysis-intensity question without asking: lightweight | balanced | thorough"
|
|
26714
|
+
).action(async (opts) => {
|
|
26675
26715
|
const force = opts.force ?? false;
|
|
26676
26716
|
const wantsHandoff = opts.setup !== false;
|
|
26677
26717
|
const wantsAdopt = opts.adopt !== false;
|
|
@@ -26739,7 +26779,31 @@ function registerInitCommand(program2) {
|
|
|
26739
26779
|
}
|
|
26740
26780
|
step(defaultsOnly ? "Setup answers (defaults)" : "Your setup answers");
|
|
26741
26781
|
const previous = await readSetupState();
|
|
26742
|
-
|
|
26782
|
+
let givenMoments;
|
|
26783
|
+
if (opts.moments != null) {
|
|
26784
|
+
const parsed = parseMomentSelection(String(opts.moments));
|
|
26785
|
+
if ("error" in parsed) {
|
|
26786
|
+
printError(parsed.error);
|
|
26787
|
+
process.exit(1);
|
|
26788
|
+
}
|
|
26789
|
+
givenMoments = parsed.moments;
|
|
26790
|
+
}
|
|
26791
|
+
let givenIntensity;
|
|
26792
|
+
if (opts.intensity != null) {
|
|
26793
|
+
const want = String(opts.intensity).trim().toLowerCase();
|
|
26794
|
+
const match = INTENSITY_CHOICES.find((c) => c.id === want);
|
|
26795
|
+
if (!match) {
|
|
26796
|
+
printError(
|
|
26797
|
+
`--intensity: unrecognised "${opts.intensity}". Valid: ${INTENSITY_CHOICES.map((c) => c.id).join(", ")}.`
|
|
26798
|
+
);
|
|
26799
|
+
process.exit(1);
|
|
26800
|
+
}
|
|
26801
|
+
givenIntensity = match.id;
|
|
26802
|
+
}
|
|
26803
|
+
const answers = await askSetupQuestions(defaultsOnly, previous, {
|
|
26804
|
+
intensity: givenIntensity,
|
|
26805
|
+
moments: givenMoments
|
|
26806
|
+
});
|
|
26743
26807
|
const { intensity, moments } = answers;
|
|
26744
26808
|
if (defaultsOnly) {
|
|
26745
26809
|
printInfo(` intensity: ${intensity} \xB7 moments: ${moments.join(", ") || "none"} (no questions asked)`);
|
|
@@ -26754,7 +26818,7 @@ function registerInitCommand(program2) {
|
|
|
26754
26818
|
...moments.includes("pre-push") ? ["push"] : []
|
|
26755
26819
|
];
|
|
26756
26820
|
const existingMoments = readProjectConfig();
|
|
26757
|
-
const keepExisting = defaultsOnly && existingMoments.git_moments_source === "user";
|
|
26821
|
+
const keepExisting = !givenMoments && defaultsOnly && existingMoments.git_moments_source === "user";
|
|
26758
26822
|
if (keepExisting) {
|
|
26759
26823
|
printInfo(
|
|
26760
26824
|
` Keeping the git moments you set: ${existingMoments.git_moments.join(", ") || "none"} (this run asked no questions, so it does not overrule them).`
|
|
@@ -26763,10 +26827,15 @@ function registerInitCommand(program2) {
|
|
|
26763
26827
|
writeProjectConfig({ git_moments: gitMoments, git_moments_source: "init" });
|
|
26764
26828
|
}
|
|
26765
26829
|
const effectiveMoments = keepExisting ? existingMoments.git_moments : gitMoments;
|
|
26830
|
+
const effectiveMomentIds = [
|
|
26831
|
+
...moments.includes("stop") ? ["stop"] : [],
|
|
26832
|
+
...effectiveMoments.includes("commit") ? ["pre-commit"] : [],
|
|
26833
|
+
...effectiveMoments.includes("push") ? ["pre-push"] : []
|
|
26834
|
+
];
|
|
26766
26835
|
if (pluginMode) {
|
|
26767
|
-
await adoptPluginWiring(effectiveMoments,
|
|
26836
|
+
await adoptPluginWiring(effectiveMoments, effectiveMomentIds);
|
|
26768
26837
|
} else {
|
|
26769
|
-
await reconcileOwnWiring(
|
|
26838
|
+
await reconcileOwnWiring(effectiveMomentIds);
|
|
26770
26839
|
}
|
|
26771
26840
|
console.log("");
|
|
26772
26841
|
step("Sign in to Verity (optional)");
|
|
@@ -26832,11 +26901,14 @@ function registerInitCommand(program2) {
|
|
|
26832
26901
|
try {
|
|
26833
26902
|
await writeSetupState({
|
|
26834
26903
|
intensity,
|
|
26835
|
-
|
|
26904
|
+
// The effective answer, not this run's: the next interactive init
|
|
26905
|
+
// pre-fills from this record, so a stale "stop" here offers "press
|
|
26906
|
+
// Enter to keep" over a commit gate that Enter would remove.
|
|
26907
|
+
moments: effectiveMomentIds,
|
|
26836
26908
|
...telemetryChoice ? { telemetry: telemetryChoice } : {},
|
|
26837
26909
|
init: {
|
|
26838
26910
|
completed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26839
|
-
cli_version: true ? "0.32.
|
|
26911
|
+
cli_version: true ? "0.32.4-experimental.e769665" : "dev"
|
|
26840
26912
|
}
|
|
26841
26913
|
});
|
|
26842
26914
|
} catch (err) {
|
|
@@ -26863,6 +26935,17 @@ function registerInitCommand(program2) {
|
|
|
26863
26935
|
console.log(` Intensity: ${intensity} Moments: ${moments.join(", ") || "none"}`);
|
|
26864
26936
|
console.log("");
|
|
26865
26937
|
if (haveStandard) {
|
|
26938
|
+
if (pluginMode) {
|
|
26939
|
+
const pluginVersion = activePluginVersion();
|
|
26940
|
+
if (pluginVersion) {
|
|
26941
|
+
await writeSetupState({
|
|
26942
|
+
pluginSetup: { version: pluginVersion, completed_at: (/* @__PURE__ */ new Date()).toISOString() },
|
|
26943
|
+
wiring: void 0
|
|
26944
|
+
});
|
|
26945
|
+
}
|
|
26946
|
+
} else if (opts.plugin === false) {
|
|
26947
|
+
await writeSetupState({ wiring: "project" });
|
|
26948
|
+
}
|
|
26866
26949
|
printInfo("Setup complete \u2014 the gate is live on your next Claude Code session.");
|
|
26867
26950
|
printInfo(' Add project-specific rules any time: edit .verity/standard.yaml, then "verity standard push".');
|
|
26868
26951
|
printInfo(" Or have a model propose them: /verity-setup --force in Claude Code.");
|
|
@@ -27506,8 +27589,8 @@ function registerTelemetryCommands(program2) {
|
|
|
27506
27589
|
}
|
|
27507
27590
|
|
|
27508
27591
|
// src/cli.ts
|
|
27509
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.32.
|
|
27510
|
-
installStderrLog(actionCommand.name(), process.argv.slice(2), "0.32.
|
|
27592
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.32.4-experimental.e769665").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async (_thisCommand, actionCommand) => {
|
|
27593
|
+
installStderrLog(actionCommand.name(), process.argv.slice(2), "0.32.4-experimental.e769665");
|
|
27511
27594
|
setUserNamedServiceUrl(program.opts().serviceUrl);
|
|
27512
27595
|
try {
|
|
27513
27596
|
await foldLegacyLocalCredential();
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: >-
|
|
3
|
+
Set Verity up in this project: ask the two setup questions, run the deterministic
|
|
4
|
+
install, and repair a project the Claude Code plugin has just been added to. Use
|
|
5
|
+
right after installing the Verity plugin, or when Verity says this project is not
|
|
6
|
+
set up.
|
|
7
|
+
disable-model-invocation: true
|
|
8
|
+
---
|
|
9
|
+
# /verity-init — set Verity up in this project
|
|
10
|
+
|
|
11
|
+
You are running the setup a plugin install cannot do for itself.
|
|
12
|
+
|
|
13
|
+
**Why this skill exists.** Claude Code has no post-install hook and a plugin
|
|
14
|
+
cannot invoke a skill, so installing Verity repairs nothing: it cannot remove a
|
|
15
|
+
project's superseded skills, stand down duplicate hooks, or derive a Standard.
|
|
16
|
+
All of that needs a command someone runs — this one.
|
|
17
|
+
|
|
18
|
+
**`verity init` prompts on a terminal, and you are not one.** That is the whole
|
|
19
|
+
problem this skill solves. Left to itself the CLI would take `--yes` and silently
|
|
20
|
+
choose **stop-only** review — no pre-commit gate, no pre-push gate — for every
|
|
21
|
+
plugin user, which is exactly the outcome we treat as a serious bug when it
|
|
22
|
+
happens by accident. So **you** ask, in the session, where the user actually is.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Step 1: Ask both questions in ONE prompt
|
|
27
|
+
|
|
28
|
+
Use `AskUserQuestion` with **both** questions in a single call. Do not ask them
|
|
29
|
+
one at a time, and do not ask them in prose — this is the only interruption the
|
|
30
|
+
setup gets, so spend it once.
|
|
31
|
+
|
|
32
|
+
**Question 1 — "When should Verity review your code?"** (multi-select)
|
|
33
|
+
|
|
34
|
+
| Option | Description |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `After every turn (recommended)` | Reviews what the agent just wrote, as you work. Fast feedback, never blocks a commit. |
|
|
37
|
+
| `Before commit` | Reviews the staged diff and blocks the commit on a failing review. |
|
|
38
|
+
| `Before push / PR` | Reviews the commits about to be pushed and blocks on a failing review. |
|
|
39
|
+
|
|
40
|
+
Pre-select **After every turn**. Say plainly in the question text that choosing
|
|
41
|
+
only the first means **git commits and pushes are not gated**.
|
|
42
|
+
|
|
43
|
+
**Question 2 — "How deeply should Verity review?"** (single-select)
|
|
44
|
+
|
|
45
|
+
| Option | Description |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `Balanced (recommended)` | Security + quality, about 8s per review. |
|
|
48
|
+
| `Lightweight` | Critical security only, about 3s. |
|
|
49
|
+
| `Thorough` | All tools, all rules, about 15s. |
|
|
50
|
+
|
|
51
|
+
## Step 2: Run the install with those answers
|
|
52
|
+
|
|
53
|
+
Map the answers to flags and run it — one command, no prompts, nothing silently
|
|
54
|
+
defaulted:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
verity init --yes --no-setup --moments <stop[,pre-commit][,pre-push]> --intensity <balanced|lightweight|thorough>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`--moments none` is the deliberate way to gate nothing; an unrecognised value is
|
|
61
|
+
refused rather than dropped, so a typo cannot quietly remove a gate.
|
|
62
|
+
|
|
63
|
+
This is idempotent and does the whole job: removes the project's own
|
|
64
|
+
`.claude/skills/verity-*` copies that the plugin supersedes (they are why the
|
|
65
|
+
slash-command list shows every skill twice), stands down duplicate
|
|
66
|
+
`.claude/settings.json` hooks so a turn is reviewed once, derives the Standard
|
|
67
|
+
and the analysis config if there is none, heals a stale one, and records the
|
|
68
|
+
plugin version — which is what stops Verity asking at every session start.
|
|
69
|
+
|
|
70
|
+
Report what it removed. Those skill directories are usually committed, so the
|
|
71
|
+
deletions will show in `git status` and are the user's to commit.
|
|
72
|
+
|
|
73
|
+
## Step 3: Tell the user to sign in — in their own terminal
|
|
74
|
+
|
|
75
|
+
**Do not run `verity login` yourself.** It is a GitHub device flow: it prints a
|
|
76
|
+
code, waits for the user to approve it in a browser, and blocks until they do.
|
|
77
|
+
Started from a tool call it just hangs, and the user never sees the code.
|
|
78
|
+
|
|
79
|
+
Say this, and stop:
|
|
80
|
+
|
|
81
|
+
> Verity is set up. One more step, in your own terminal — not here:
|
|
82
|
+
>
|
|
83
|
+
> ```
|
|
84
|
+
> verity login
|
|
85
|
+
> ```
|
|
86
|
+
>
|
|
87
|
+
> It prints a code and a link; approve it in your browser. One login covers every
|
|
88
|
+
> repository you can write to, for 90 days. Without it the gate still reviews
|
|
89
|
+
> your code — it just records nothing, so there is no history, no cloud memory
|
|
90
|
+
> and no dashboard.
|
|
91
|
+
|
|
92
|
+
If `verity status` already reports an account, skip this step and say so.
|
|
93
|
+
|
|
94
|
+
## Step 4: Offer the part a lookup cannot produce
|
|
95
|
+
|
|
96
|
+
The Standard `init` derives covers the research-backed patterns for the
|
|
97
|
+
languages and tools it found. What it deliberately leaves empty is
|
|
98
|
+
`custom_patterns` — rules like *"every route under `api/` uses the auth
|
|
99
|
+
middleware"*, which need something to read the code for intent.
|
|
100
|
+
|
|
101
|
+
Offer `/verity-setup` for that, and say it is optional: the gate is already
|
|
102
|
+
running.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## What NOT to do here
|
|
107
|
+
|
|
108
|
+
- **Do not wire hooks or edit `.gitignore` yourself.** `verity init` owns that.
|
|
109
|
+
Doing it here is how the two flows drifted apart before — the skill reconciled
|
|
110
|
+
hooks init had just installed and silently removed them.
|
|
111
|
+
- **Do not synthesize a Standard by hand.** The CLI derives it in about two
|
|
112
|
+
seconds from the same catalogue you would be reading.
|
|
113
|
+
- **Do not run `verity login`.** Step 3.
|
|
@@ -48,20 +48,29 @@ verity doctor --json
|
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
One read gives you prerequisites, which phase is done, the user's recorded
|
|
51
|
-
answers, the hook wiring, and a `next` list.
|
|
51
|
+
answers, the hook wiring, and a `next` list.
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
Run it (it is idempotent, and it is what installed this skill):
|
|
53
|
+
### 1a. If the project is not set up at all, that is init's job, not yours
|
|
55
54
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
⚠ **If `phases.init.done` is false, stop and say so — naming the command that
|
|
56
|
+
exists on THIS install:**
|
|
57
|
+
|
|
58
|
+
- **`hooks.source` is `"plugin"`** → `/verity-init`, which the plugin provides.
|
|
59
|
+
- **otherwise (npm install)** → `verity init`, run in a terminal. Do **not** send
|
|
60
|
+
an npm user to `/verity-init`: it is a plugin skill, and on that path it does
|
|
61
|
+
not exist. A remedy that names a command the user does not have is worse than
|
|
62
|
+
no remedy — they cannot tell whether they are stuck or broken.
|
|
63
|
+
|
|
64
|
+
**Do not run `verity init` from this skill.** That flow asks the user two
|
|
65
|
+
questions before it installs anything, and running it here takes the answers
|
|
66
|
+
away from them — silently choosing stop-only review, which means no commit or
|
|
67
|
+
push gate. One skill owns setup, and it is not this one.
|
|
68
|
+
|
|
69
|
+
Branch on the same `doctor` output:
|
|
59
70
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
terminal) is where those choices are made. `--no-setup` stops init from trying
|
|
64
|
-
to launch a second Claude Code session on top of this one.
|
|
71
|
+
- **`phases.init.done` is false** → setup has not run here. Point the user at the
|
|
72
|
+
command their install actually has (see 1a) and stop. That is the one place the
|
|
73
|
+
setup questions get asked.
|
|
65
74
|
- **`blocked` is true** → a required prerequisite is missing. Show the
|
|
66
75
|
`prerequisites[].remedy` lines and stop; nothing below can work.
|
|
67
76
|
- **`artifacts.standard` is true and `phases.setup.done` is true** → the
|