@codacy/verity-cli 0.23.2 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/verity.js CHANGED
@@ -12833,6 +12833,11 @@ function getPushRangeFiles() {
12833
12833
  const last = diff("HEAD~1..HEAD");
12834
12834
  return { files: last, range: last.length > 0 ? "HEAD~1..HEAD" : null };
12835
12835
  }
12836
+ function getPushRangeMessages() {
12837
+ const { range } = getPushRangeFiles();
12838
+ if (!range) return "";
12839
+ return execGit(`git log ${range} --format=%B%x00`).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
12840
+ }
12836
12841
  function getWorktreeFiles() {
12837
12842
  const result = [];
12838
12843
  const worktreeDir = ".claude/worktrees";
@@ -13705,7 +13710,12 @@ function isReflectionQuestion(response) {
13705
13710
  /reflection\s+for\s+future\s+agents/i,
13706
13711
  /what(?:'s|\s+is)\s+one\s+thing\s+you\s+learned/i,
13707
13712
  /say\s+['"]?skip['"]?\s+to\s+skip/i,
13708
- /quick\s+reflection\s+question/i
13713
+ /quick\s+reflection\s+question/i,
13714
+ // Post-flip (VRT-21): the agent drafts the reflection itself and, when
13715
+ // interactive, asks the user to confirm/correct before recording. That
13716
+ // turn authors no code either, so it's still a reflection turn.
13717
+ /reflection\s+draft/i,
13718
+ /confirm,?\s+correct,?\s+or\s+add/i
13709
13719
  ];
13710
13720
  return markers.some((m) => m.test(response));
13711
13721
  }
@@ -15057,7 +15067,48 @@ function registerGuardCommand(program2) {
15057
15067
  function getMomentFiles(moment) {
15058
15068
  return moment === "pre-commit" ? getStagedFiles() : getPushRangeFiles().files;
15059
15069
  }
15060
- function buildGuardRequest(moment, files, iter, sessionId) {
15070
+ function matchFlagValue(command, flags) {
15071
+ const re = new RegExp(`(?<![\\w-])(?:${flags})(?:=|\\s+)('((?:[^'\\\\]|\\\\.)*)'|"((?:[^"\\\\]|\\\\.)*)"|([^\\s'"-][^\\s]*))`);
15072
+ const m = re.exec(command);
15073
+ if (!m) return null;
15074
+ const v = m[2] ?? m[3] ?? m[4];
15075
+ return v != null ? v.replace(/\\(["'])/g, "$1") : null;
15076
+ }
15077
+ function parseCommitMessage(command) {
15078
+ const parts = [];
15079
+ const re = /(?<![\w-])(?:--message|-am|-m)(?:=|\s+)('((?:[^'\\]|\\.)*)'|"((?:[^"\\]|\\.)*)"|([^\s'"-][^\s]*))/g;
15080
+ let m;
15081
+ while ((m = re.exec(command)) !== null) {
15082
+ const v = m[2] ?? m[3] ?? m[4];
15083
+ if (v != null && v.length) parts.push(v.replace(/\\(["'])/g, "$1"));
15084
+ }
15085
+ return parts.length ? parts.join("\n\n") : null;
15086
+ }
15087
+ function parsePrIntent(command) {
15088
+ const title = matchFlagValue(command, "--title|-t");
15089
+ const body = matchFlagValue(command, "--body|-b");
15090
+ const parts = [title, body].filter((x) => !!x);
15091
+ return parts.length ? parts.join("\n\n") : null;
15092
+ }
15093
+ var TRIVIAL_INTENT = /^(wip|tmp|temp|test|fix|chore|amend|\.+|fixup!.*|squash!.*|merge\b.*)$/i;
15094
+ var SHELL_PLUMBING = /\$\(|`|<<-?\s*['"]?[A-Za-z_]/;
15095
+ function isSubstantiveIntent(text) {
15096
+ if (!text) return false;
15097
+ const t = text.trim();
15098
+ if (t.length < 12) return false;
15099
+ if (TRIVIAL_INTENT.test(t)) return false;
15100
+ if (SHELL_PLUMBING.test(t)) return false;
15101
+ return true;
15102
+ }
15103
+ function extractStatedIntent(moment, command) {
15104
+ const text = moment === "pre-commit" ? parseCommitMessage(command) : parsePrIntent(command) ?? (getPushRangeMessages() || null);
15105
+ return isSubstantiveIntent(text) ? text : null;
15106
+ }
15107
+ function hasBlockingFinding(response) {
15108
+ const findings = response.findings ?? [];
15109
+ return findings.some((f) => f.scope !== "pre-existing" && ["critical", "high"].includes((f.severity ?? "").toLowerCase()));
15110
+ }
15111
+ function buildGuardRequest(moment, files, iter, sessionId, command) {
15061
15112
  const analyzable = filterAnalyzable(files);
15062
15113
  const securityFiles = filterSecurity(files);
15063
15114
  let staticResults;
@@ -15087,20 +15138,21 @@ function buildGuardRequest(moment, files, iter, sessionId) {
15087
15138
  };
15088
15139
  const specs = discoverSpecs();
15089
15140
  const plans = discoverPlans();
15090
- if (specs.length > 0 || plans.length > 0) {
15141
+ const statedIntent = extractStatedIntent(moment, command);
15142
+ if (specs.length > 0 || plans.length > 0 || statedIntent) {
15091
15143
  const intentContext = {};
15144
+ if (statedIntent) intentContext.user_prompt = statedIntent;
15092
15145
  if (specs.length > 0) intentContext.specs = specs;
15093
15146
  if (plans.length > 0) intentContext.plans = plans;
15094
15147
  requestBody.intent_context = intentContext;
15095
15148
  }
15096
15149
  return requestBody;
15097
15150
  }
15098
- function allowWithNotice(moment, decision) {
15099
- resetIter(moment);
15100
- if (decision === "WARN") {
15101
- process.stderr.write(`${YELLOW}Verity ${moment}: WARN \u2014 proceeding. See findings in the report.${NC}
15102
- `);
15103
- }
15151
+ function emitAllowNotice(userMsg, agentMsg) {
15152
+ process.stdout.write(JSON.stringify({
15153
+ systemMessage: userMsg,
15154
+ hookSpecificOutput: { hookEventName: "PreToolUse", additionalContext: agentMsg }
15155
+ }) + "\n");
15104
15156
  process.exit(0);
15105
15157
  }
15106
15158
  async function runGuard(opts, globals) {
@@ -15114,19 +15166,21 @@ async function runGuard(opts, globals) {
15114
15166
  }
15115
15167
  const moment = classifyCommand(command, on);
15116
15168
  if (!moment) process.exit(0);
15169
+ const verb = moment === "pre-commit" ? "commit" : "push";
15117
15170
  const iter = readIter(moment);
15118
15171
  if (iter >= GUARD_BLOCK_CAP) {
15119
- process.stderr.write(`${DIM}Verity ${moment}: ${GUARD_BLOCK_CAP} review cycles reached \u2014 allowing through.${NC}
15120
- `);
15121
15172
  resetIter(moment);
15122
- process.exit(0);
15173
+ emitAllowNotice(
15174
+ `Verity ${moment}: ${GUARD_BLOCK_CAP} review cycles reached \u2014 allowing the ${verb} through`,
15175
+ `Verity ${moment}: review-cycle cap (${GUARD_BLOCK_CAP}) reached; the ${verb} was allowed without a further block.`
15176
+ );
15123
15177
  }
15124
15178
  const files = getMomentFiles(moment);
15125
15179
  if (files.length === 0) process.exit(0);
15126
15180
  const tokenResult = await resolveToken(globals.token);
15127
15181
  const urlResult = await resolveServiceUrl(globals.serviceUrl);
15128
15182
  if (!tokenResult.ok || !urlResult.ok) process.exit(0);
15129
- const requestBody = buildGuardRequest(moment, files, iter, sessionId);
15183
+ const requestBody = buildGuardRequest(moment, files, iter, sessionId, command);
15130
15184
  if (!requestBody) process.exit(0);
15131
15185
  const result = await analyzeRequest({
15132
15186
  serviceUrl: urlResult.data,
@@ -15137,21 +15191,39 @@ async function runGuard(opts, globals) {
15137
15191
  cmd: "guard"
15138
15192
  });
15139
15193
  if (!result.ok) {
15140
- process.stderr.write(`${DIM}Verity ${moment}: service unavailable \u2014 allowing commit/push (${result.error}).${NC}
15141
- `);
15142
- process.exit(0);
15143
- }
15144
- if (opts.json) {
15145
- process.stdout.write(JSON.stringify(result.data) + "\n");
15194
+ emitAllowNotice(
15195
+ `\u26A0 Verity ${moment}: service offline \u2014 ${verb}ed WITHOUT review`,
15196
+ `Verity ${moment}: service unavailable (${result.error}); the ${verb} was allowed WITHOUT a Verity review.`
15197
+ );
15146
15198
  }
15199
+ if (opts.json) process.stderr.write(JSON.stringify(result.data) + "\n");
15147
15200
  const response = result.data;
15148
15201
  const decision = response.gate_decision ?? "PASS";
15149
- if (decision === "FAIL") {
15202
+ const viewUrl = response.view_url ?? "";
15203
+ const link = viewUrl ? ` \u2014 ${viewUrl}` : "";
15204
+ if (decision === "FAIL" && hasBlockingFinding(response)) {
15150
15205
  writeIter(moment, iter + 1);
15151
15206
  writeBlockMessage(moment, response);
15152
15207
  process.exit(2);
15153
15208
  }
15154
- allowWithNotice(moment, decision);
15209
+ resetIter(moment);
15210
+ if (decision === "FAIL") {
15211
+ const narrative = response.assessment?.narrative ?? "";
15212
+ emitAllowNotice(
15213
+ `\u26A0 Verity ${moment}: the ${verb} may not match its stated purpose \u2014 proceeding${link}`,
15214
+ `Verity ${moment}: intent-alignment WARNING (not blocked).${narrative ? " " + narrative : ""}${viewUrl ? ` Report: ${viewUrl}` : ""}`
15215
+ );
15216
+ }
15217
+ if (decision === "WARN") {
15218
+ emitAllowNotice(
15219
+ `\u26A0 Verity ${moment}: WARN \u2014 proceeding${link}`,
15220
+ `Verity ${moment} review: WARN (proceeding).${viewUrl ? ` Report: ${viewUrl}` : ""}`
15221
+ );
15222
+ }
15223
+ emitAllowNotice(
15224
+ `\u2713 Verity ${moment}: PASS${link}`,
15225
+ `Verity ${moment} review: PASS.${viewUrl ? ` Report: ${viewUrl}` : ""}`
15226
+ );
15155
15227
  }
15156
15228
  function writeBlockMessage(moment, response) {
15157
15229
  const label2 = moment === "pre-commit" ? "pre-commit" : "pre-push";
@@ -15980,10 +16052,29 @@ function registerResetCommand(program2) {
15980
16052
  });
15981
16053
  }
15982
16054
 
16055
+ // src/lib/run-mode.ts
16056
+ function parseAutonomousEnv(raw) {
16057
+ if (raw === void 0) return void 0;
16058
+ const v = raw.trim().toLowerCase();
16059
+ if (v === "") return void 0;
16060
+ if (v === "0" || v === "false" || v === "off" || v === "no") return false;
16061
+ return true;
16062
+ }
16063
+ function resolveRunMode(inputs = {}) {
16064
+ if (inputs.autonomousFlag === true) return "autonomous";
16065
+ if (inputs.autonomousFlag === false) return "interactive";
16066
+ const env = inputs.env ?? process.env;
16067
+ const envDecision = parseAutonomousEnv(env.VERITY_AUTONOMOUS);
16068
+ if (envDecision !== void 0) return envDecision ? "autonomous" : "interactive";
16069
+ const isTTY = inputs.isTTY ?? Boolean(process.stdin?.isTTY);
16070
+ return isTTY ? "interactive" : "autonomous";
16071
+ }
16072
+
15983
16073
  // src/commands/reflect.ts
15984
16074
  function registerReflectCommand(program2) {
15985
- program2.command("reflect").description("Capture learnings \u2014 auto-extract or submit a human reflection").option("--user-input <text>", "Your reflection (becomes a high-confidence knowledge node)").option("--kind <kind>", "Node kind (decision, gotcha, pattern, security, quality, intent, domain, integration)", "gotcha").option("--task-id <id>", "Task to reflect on (defaults to current task)").action(async (opts) => {
16075
+ program2.command("reflect").description("Capture learnings \u2014 auto-extract or submit a human reflection").option("--user-input <text>", "The reflection to record (the agent-drafted or user-confirmed text)").option("--kind <kind>", "Node kind (decision, gotcha, pattern, security, quality, intent, domain, integration)", "gotcha").option("--task-id <id>", "Task to reflect on (defaults to current task)").option("--autonomous", "Record the drafted reflection without a confirm step (auto-detected from TTY / VERITY_AUTONOMOUS when omitted)").action(async (opts) => {
15986
16076
  const globals = program2.opts();
16077
+ const mode = resolveRunMode({ autonomousFlag: opts.autonomous });
15987
16078
  await ensureMemoryDir();
15988
16079
  const tokenResult = await resolveToken(globals.token);
15989
16080
  if (!tokenResult.ok) {
@@ -16074,7 +16165,9 @@ function registerReflectCommand(program2) {
16074
16165
  const nodeId = result.data.node_id;
16075
16166
  const filePath = result.data.file_path;
16076
16167
  printInfo(`Queued: ${nodeId} (will sync to .verity/memory/${filePath} on next analysis).`);
16077
- printInfo("Your reflection is now part of the project knowledge base (confidence: 100%).");
16168
+ printInfo(
16169
+ mode === "autonomous" ? "Recorded autonomously (no confirm step) into the project knowledge base." : "Recorded your confirmed reflection into the project knowledge base."
16170
+ );
16078
16171
  if (taskResolution === "none") {
16079
16172
  printInfo("(No task context \u2014 reflection lives at the project level.)");
16080
16173
  }
@@ -16341,7 +16434,7 @@ function registerTelemetryCommands(program2) {
16341
16434
  }
16342
16435
 
16343
16436
  // src/cli.ts
16344
- program.name("verity").description("CLI for Verity quality gate service").version("0.23.2").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
16437
+ program.name("verity").description("CLI for Verity quality gate service").version("0.24.0").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
16345
16438
  registerAuthCommands(program);
16346
16439
  registerHooksCommands(program);
16347
16440
  registerIntentCommands(program);
@@ -1,47 +1,83 @@
1
1
  # /verity-reflect — Capture learnings after a task
2
2
 
3
- Trigger knowledge extraction for the current task, or submit a human reflection that becomes a high-confidence knowledge node.
3
+ At a natural task-completion moment, **the agent reflects first** draft a
4
+ concrete, cited observation from what actually happened — then either confirm it
5
+ with the user (interactive) or record it directly (autonomous). This is the
6
+ "reflection flip": Verity stops asking the user to do the reflecting and instead
7
+ does the work, with the human as editor rather than author.
4
8
 
5
- ## Auto-reflection (extract from task history)
9
+ ## The flip draft first, then confirm or auto-record
10
+
11
+ ### 1. Self-draft (always)
12
+
13
+ When a task is complete, draft **1–3** things genuinely worth remembering — a
14
+ **decision**, a **gotcha**, or a **pattern** — from what happened this task. Each
15
+ item must be **concrete and cited**: name the files, PR, commands, or
16
+ error-signatures it came from. **Skip entirely if nothing non-obvious happened**
17
+ — an empty reflection is better than a filler one.
18
+
19
+ Pick the right `--kind` per item: `decision`, `gotcha`, `pattern`, `security`,
20
+ `quality`, `intent`, `domain`, `integration`.
21
+
22
+ ### 2. Branch on run mode
6
23
 
7
- When called without `--user-input`, triggers the LLM extractor to review the current task's run history and produce 0-3 knowledge nodes:
24
+ **Interactive** (a person is at the keyboard a TTY): show your draft and ask
25
+ one question before recording:
26
+
27
+ > "Here's my reflection draft for the project memory: _\<draft\>_. Anything to
28
+ > **confirm, correct, or add** before I record it? (Say 'skip' to drop it.)"
29
+
30
+ Then record the **final** text — with their corrections folded in — via:
8
31
 
9
32
  ```bash
10
- verity reflect
33
+ verity reflect --user-input "<final reflection>" --kind <kind>
34
+ ```
35
+
36
+ If they say "skip", do **not** record.
37
+
38
+ **Autonomous** (no person to confirm — headless / CI / cron, or
39
+ `VERITY_AUTONOMOUS=1`, or a non-TTY session): record your draft **directly**,
40
+ never blocking:
41
+
42
+ ```bash
43
+ verity reflect --user-input "<your draft>" --kind <kind> --autonomous
11
44
  ```
12
45
 
13
- This is the same extraction that runs automatically on task close and every 5 runs. Use this to manually trigger it mid-task if you've learned something significant.
46
+ Mode is auto-detected when you omit the flag (TTY interactive, non-TTY
47
+ autonomous); pass `--autonomous` to be explicit, or set `VERITY_AUTONOMOUS=0` to
48
+ force a confirm step even in a headless run.
49
+
50
+ ### 3. Where it lands
51
+
52
+ The reflection is recorded **only to Verity's own memory namespace** —
53
+ `.verity/memory/` (synced to the service). Verity never writes a repo's own
54
+ `agents/memory` or any other store. One node per item; `source: 'user'`,
55
+ `confidence: 1.0`, never auto-archived.
14
56
 
15
- ## Human reflection (the compound moment)
57
+ ## Auto-reflection (extract from task history)
16
58
 
17
- When the user provides their own insight, it becomes a high-confidence node (`source: user`, `confidence: 1.0`) that is never auto-archived:
59
+ Complementary to the flip: trigger the server-side LLM extractor to mine the
60
+ current task's run history and produce 0–3 nodes. This is the same extraction
61
+ that runs automatically on task close and every 5 runs — use it to trigger
62
+ mid-task:
18
63
 
19
64
  ```bash
20
- verity reflect --user-input "The Stripe retry logic needs idempotency keys or we double-charge"
65
+ verity reflect
21
66
  ```
22
67
 
23
- The CLI will:
24
- 1. Call the extractor LLM to classify the reflection into a domain (decisions/, patterns/, gotchas/, etc.)
25
- 2. Set `source: 'user'` and `confidence: 1.0`
26
- 3. Opportunistically link to existing nodes
27
- 4. Write the node locally and sync to cloud
68
+ Use the self-draft flow above for the **compound moment** (a specific insight
69
+ worth a confirmed node); use auto-extract to harvest what the run history shows.
28
70
 
29
71
  ## When to reflect
30
72
 
31
- The agent should ask for a reflection at natural task-completion moments:
73
+ At natural task-completion moments:
32
74
 
33
75
  - After creating a PR
34
76
  - When the user says "done", "ship it", or "that's it"
35
77
  - When a task is explicitly closed
36
78
 
37
- The reflection prompt (installed in CLAUDE.md during setup):
38
-
39
- > "Quick reflection for future agents: what's one thing you learned during this task that would help next time? A decision, a gotcha, a pattern — anything worth remembering. (Say 'skip' to skip.)"
40
-
41
- If the user says "skip", do NOT call `verity reflect`. The reflection is optional.
42
-
43
- ## Examples of good reflections
79
+ ## Examples of good drafts
44
80
 
45
- - "The Stripe retry logic needs to use idempotency keys or we double-charge. Learned this the hard way." → `gotchas/stripe-idempotency-keys.md`
46
- - "We decided to use advisory locks instead of optimistic locking because Supabase supports it natively." → `decisions/advisory-locks-for-versioning.md`
47
- - "Don't touch the RLS policies without updating the cleanup cron job — they're coupled." → `gotchas/rls-cleanup-coupling.md`
81
+ - "The Stripe retry logic needs idempotency keys or we double-charge hit it in `payments/retry.ts:88`." → `gotchas/stripe-idempotency-keys.md`
82
+ - "We chose advisory locks over optimistic locking because Supabase supports them natively (see PR #214)." → `decisions/advisory-locks-for-versioning.md`
83
+ - "Don't touch the RLS policies without updating the cleanup cron — they're coupled (`migrations/0190`, `cron/cleanup.sql`)." → `gotchas/rls-cleanup-coupling.md`
@@ -425,18 +425,31 @@ If `CLAUDE.md` exists (or `.claude/CLAUDE.md`), append these two blocks. If it d
425
425
 
426
426
  ## Post-task reflection
427
427
  When a task is complete (you've created a PR, the user says "done" or "ship it",
428
- or the work is clearly finished), ask the user one question before moving on:
428
+ or the work is clearly finished), **draft the reflection yourself first** 1–3
429
+ concrete things worth remembering (a decision, a gotcha, or a pattern), each
430
+ cited with the files / PR / commands / error-signatures it came from. Skip
431
+ entirely if nothing non-obvious happened.
429
432
 
430
- > "Quick reflection for future agents: what's one thing you learned during this
431
- > task that would help next time? A decision, a gotcha, a pattern — anything
432
- > worth remembering. (Say 'skip' to skip.)"
433
+ Then, depending on how the session is running:
433
434
 
434
- If the user responds (not "skip"), run `verity reflect --user-input "<their response>"`.
435
+ - **Interactive** (a person is at the keyboard): show your draft and ask one
436
+ question before recording —
437
+
438
+ > "Here's my reflection draft for the project memory: \<draft\>. Anything to
439
+ > confirm, correct, or add before I record it? (Say 'skip' to drop it.)"
440
+
441
+ Record the final text (with their corrections) via
442
+ `verity reflect --user-input "<final reflection>" --kind <kind>`. If they say
443
+ "skip", do not record.
444
+
445
+ - **Autonomous** (headless / CI / cron, or `VERITY_AUTONOMOUS=1`): record your
446
+ draft directly, never blocking —
447
+ `verity reflect --user-input "<your draft>" --kind <kind> --autonomous`.
435
448
  ```
436
449
 
437
450
  These blocks enable two capabilities:
438
451
  1. **Knowledge Base pointer** — agents can browse `.verity/memory/` for project context on demand (KNOWLEDGE-SYSTEM §8.2 Path B)
439
- 2. **Post-task reflection** — the compound moment where the user's judgment enters the knowledge base (KNOWLEDGE-SYSTEM §9.3)
452
+ 2. **Post-task reflection** — the compound moment where the agent drafts a cited reflection and the user confirms/corrects it (interactive) or it's auto-recorded (autonomous), entering the knowledge base (KNOWLEDGE-SYSTEM §9.3)
440
453
 
441
454
  ---
442
455
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/verity-cli",
3
- "version": "0.23.2",
3
+ "version": "0.24.0",
4
4
  "description": "CLI for Verity quality gate service",
5
5
  "homepage": "https://verity.md",
6
6
  "repository": {