@codacy/verity-cli 0.33.2 → 0.33.3

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/bin/verity.js +116 -22
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -64,6 +64,32 @@ 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.33.3] — 2026-09-24
68
+
69
+ **The knowledge seed runs once per project, not once per checkout.**
70
+
71
+ - **`verity memory seed` and the analyze auto-seed no longer re-seed a project
72
+ that is already seeded.** The old "already seeded" check looked for a file the
73
+ server never writes (`domain/project-overview.md`; nodes are named
74
+ `n{seq}-{slug}.md`), so it never matched, and an empty, not-yet-pulled
75
+ `.verity/memory/` in a fresh clone or worktree read as "never seeded". Every
76
+ new checkout posted the whole batch again and the server recorded every copy.
77
+ One project reached 13,497 nodes, 11,214 of them copies of 38 seed titles, and
78
+ every analyze for it died on request size (HTTP 546, VRT-243). The CLI now
79
+ checks the frontmatter of what is on disk, then asks the server which seed
80
+ titles the project has, and posts only the titles that are missing — so a
81
+ seed that only half landed is completed, and a seeded project is never
82
+ re-posted.
83
+ - **The server refuses to record the same seed twice.** Posting a seed node whose
84
+ title and body the project already carries returns the existing node
85
+ (`200`, `deduplicated: true`) instead of minting another. This holds for old
86
+ CLIs, cleared markers and parallel worktrees alike. A seed whose body changed
87
+ (the README was edited) is still a new node, and an archived seed can be
88
+ recorded again.
89
+ - `verity memory seed` reports nodes that already existed apart from nodes it
90
+ created, and `--force` re-materializes the existing nodes locally rather than
91
+ duplicating them.
92
+
67
93
  ## [0.33.2] — 2026-09-23
68
94
 
69
95
  **Which build is gating, said out loud.**
package/bin/verity.js CHANGED
@@ -22159,7 +22159,7 @@ function channelSilence(input) {
22159
22159
  // src/lib/cli-version.ts
22160
22160
  function cliVersion() {
22161
22161
  try {
22162
- return true ? "0.33.2" : "dev";
22162
+ return true ? "0.33.3" : "dev";
22163
22163
  } catch {
22164
22164
  return "dev";
22165
22165
  }
@@ -23870,6 +23870,73 @@ _Seeded from CLAUDE.md. Edit or archive if outdated._`,
23870
23870
  }
23871
23871
 
23872
23872
  // src/lib/seed-runner.ts
23873
+ var NODE_DIRS = ["decisions", "quality", "security", "intent", "gotchas", "patterns", "domain", "integrations"];
23874
+ function normalizeTitle(title) {
23875
+ return title.trim().replace(/\s+/g, " ").toLowerCase();
23876
+ }
23877
+ async function localSeedTitles(memoryDir3 = MEMORY_DIR) {
23878
+ const titles = /* @__PURE__ */ new Set();
23879
+ for (const dir of NODE_DIRS) {
23880
+ const full = (0, import_node_path32.join)(memoryDir3, dir);
23881
+ if (!(0, import_node_fs42.existsSync)(full)) continue;
23882
+ let files;
23883
+ try {
23884
+ files = await (0, import_promises15.readdir)(full);
23885
+ } catch {
23886
+ continue;
23887
+ }
23888
+ for (const file of files) {
23889
+ if (!file.endsWith(".md")) continue;
23890
+ let content;
23891
+ try {
23892
+ content = await (0, import_promises15.readFile)((0, import_node_path32.join)(full, file), "utf-8");
23893
+ } catch {
23894
+ continue;
23895
+ }
23896
+ if (!isSeedNodeMarkdown(content)) continue;
23897
+ const title = pickFrontmatterTitle(content);
23898
+ if (title) titles.add(normalizeTitle(title));
23899
+ }
23900
+ }
23901
+ return titles;
23902
+ }
23903
+ function frontmatterOf(content) {
23904
+ if (!content.startsWith("---")) return null;
23905
+ const end = content.indexOf("\n---", 3);
23906
+ return end === -1 ? content : content.slice(0, end);
23907
+ }
23908
+ function isSeedNodeMarkdown(content) {
23909
+ const frontmatter = frontmatterOf(content);
23910
+ return frontmatter !== null && /^created_by:\s*["']?seed["']?\s*$/m.test(frontmatter);
23911
+ }
23912
+ function pickFrontmatterTitle(content) {
23913
+ const frontmatter = frontmatterOf(content);
23914
+ const match = frontmatter?.match(/^title:[ \t]*(.*)$/m);
23915
+ if (!match) return null;
23916
+ const raw = match[1].trim();
23917
+ if (raw.startsWith('"')) {
23918
+ try {
23919
+ return String(JSON.parse(raw));
23920
+ } catch {
23921
+ return raw.slice(1, raw.endsWith('"') ? -1 : void 0).replace(/\\"/g, '"');
23922
+ }
23923
+ }
23924
+ return raw;
23925
+ }
23926
+ async function serverSeedTitles(opts) {
23927
+ const res = await apiRequest({
23928
+ method: "GET",
23929
+ path: "/compound/memory/nodes?created_by=seed&status=active&limit=100",
23930
+ serviceUrl: opts.serviceUrl,
23931
+ token: opts.token,
23932
+ verbose: opts.verbose,
23933
+ cmd: "memory_seed"
23934
+ });
23935
+ if (!res.ok || !Array.isArray(res.data.nodes)) return null;
23936
+ const titles = /* @__PURE__ */ new Set();
23937
+ for (const n of res.data.nodes) if (typeof n?.title === "string") titles.add(normalizeTitle(n.title));
23938
+ return titles;
23939
+ }
23873
23940
  function renderNodeMarkdown(candidate, nodeId, createdAt) {
23874
23941
  const fm = [
23875
23942
  "---",
@@ -23894,14 +23961,14 @@ function renderNodeMarkdown(candidate, nodeId, createdAt) {
23894
23961
  }
23895
23962
  async function runSeed(opts) {
23896
23963
  if (!(0, import_node_fs42.existsSync)(STANDARD_FILE)) {
23897
- return { created: 0, failed: 0, skipped: "no_standard", candidates: [] };
23964
+ return { created: 0, existing: 0, failed: 0, skipped: "no_standard", candidates: [] };
23898
23965
  }
23899
23966
  let standardDoc;
23900
23967
  try {
23901
23968
  const raw = await (0, import_promises15.readFile)(STANDARD_FILE, "utf-8");
23902
23969
  standardDoc = (0, import_yaml4.parse)(raw);
23903
23970
  } catch {
23904
- return { created: 0, failed: 0, skipped: "no_standard", candidates: [] };
23971
+ return { created: 0, existing: 0, failed: 0, skipped: "no_standard", candidates: [] };
23905
23972
  }
23906
23973
  const knowledgeSpec = standardDoc.knowledge_spec ?? {};
23907
23974
  let readmeContent;
@@ -23934,19 +24001,33 @@ async function runSeed(opts) {
23934
24001
  claudeMdContent
23935
24002
  });
23936
24003
  if (candidates2.length === 0) {
23937
- return { created: 0, failed: 0, skipped: "no_candidates", candidates: [] };
24004
+ return { created: 0, existing: 0, failed: 0, skipped: "no_candidates", candidates: [] };
23938
24005
  }
23939
- const overviewPath = (0, import_node_path32.join)(MEMORY_DIR, "domain", "project-overview.md");
23940
- if ((0, import_node_fs42.existsSync)(overviewPath) && !opts.force) {
23941
- return { created: 0, failed: 0, skipped: "already_seeded", candidates: candidates2 };
24006
+ let toPost = candidates2;
24007
+ if (!opts.force) {
24008
+ const local = await localSeedTitles();
24009
+ toPost = toPost.filter((c) => !local.has(normalizeTitle(c.title)));
24010
+ if (toPost.length === 0) {
24011
+ return { created: 0, existing: 0, failed: 0, skipped: "already_seeded", candidates: candidates2 };
24012
+ }
24013
+ if (!opts.dryRun) {
24014
+ const remote = await serverSeedTitles(opts);
24015
+ if (remote) {
24016
+ toPost = toPost.filter((c) => !remote.has(normalizeTitle(c.title)));
24017
+ if (toPost.length === 0) {
24018
+ return { created: 0, existing: 0, failed: 0, skipped: "already_seeded", candidates: candidates2 };
24019
+ }
24020
+ }
24021
+ }
23942
24022
  }
23943
24023
  if (opts.dryRun) {
23944
- return { created: 0, failed: 0, skipped: null, candidates: candidates2 };
24024
+ return { created: 0, existing: 0, failed: 0, skipped: null, candidates: candidates2 };
23945
24025
  }
23946
24026
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
23947
24027
  let created = 0;
24028
+ let existing = 0;
23948
24029
  let failed = 0;
23949
- for (const c of candidates2) {
24030
+ for (const c of toPost) {
23950
24031
  const res = await apiRequest({
23951
24032
  method: "POST",
23952
24033
  path: "/compound/memory/nodes",
@@ -23978,17 +24059,26 @@ async function runSeed(opts) {
23978
24059
  failed++;
23979
24060
  continue;
23980
24061
  }
24062
+ const deduplicated = res.data.deduplicated === true;
24063
+ if (deduplicated && (0, import_node_fs42.existsSync)(targetPath)) {
24064
+ existing++;
24065
+ continue;
24066
+ }
24067
+ const content = typeof res.data.content === "string" && res.data.content.length > 0 ? res.data.content : renderNodeMarkdown(c, nodeId, createdAt);
23981
24068
  try {
23982
24069
  await (0, import_promises15.mkdir)((0, import_node_path32.dirname)(targetPath), { recursive: true });
23983
- await (0, import_promises15.writeFile)(targetPath, renderNodeMarkdown(c, nodeId, createdAt));
23984
- created++;
23985
- opts.onCreated?.(nodeId, filePathRel, c);
24070
+ await (0, import_promises15.writeFile)(targetPath, content);
24071
+ if (deduplicated) existing++;
24072
+ else {
24073
+ created++;
24074
+ opts.onCreated?.(nodeId, filePathRel, c);
24075
+ }
23986
24076
  } catch (err) {
23987
24077
  opts.onFailed?.(c, `Server accepted "${c.title}" but couldn't write local file: ${err.message}`);
23988
24078
  failed++;
23989
24079
  }
23990
24080
  }
23991
- return { created, failed, skipped: null, candidates: candidates2 };
24081
+ return { created, existing, failed, skipped: null, candidates: candidates2 };
23992
24082
  }
23993
24083
 
23994
24084
  // src/commands/analyze/phases/08-memory-manifest.ts
@@ -24017,12 +24107,15 @@ async function memoryManifest(run2) {
24017
24107
  force: false,
24018
24108
  dryRun: false
24019
24109
  });
24020
- if (seedResult.created > 0) {
24021
- (0, import_node_fs43.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} created=${seedResult.created}
24110
+ if (seedResult.created > 0 || seedResult.existing > 0) {
24111
+ (0, import_node_fs43.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} created=${seedResult.created} existing=${seedResult.existing}
24022
24112
  `);
24023
- autoSeedNotice = `Seeded ${seedResult.created} knowledge node(s) from your existing Standard (one-time).`;
24113
+ if (seedResult.created > 0) {
24114
+ autoSeedNotice = `Seeded ${seedResult.created} knowledge node(s) from your existing Standard (one-time).`;
24115
+ }
24024
24116
  logEvent("auto_seed_ran", {
24025
24117
  created: seedResult.created,
24118
+ existing: seedResult.existing,
24026
24119
  failed: seedResult.failed
24027
24120
  });
24028
24121
  } else if (seedResult.skipped === "already_seeded") {
@@ -28774,7 +28867,7 @@ function registerInitCommand(program2) {
28774
28867
  ...telemetryChoice ? { telemetry: telemetryChoice } : {},
28775
28868
  init: {
28776
28869
  completed_at: (/* @__PURE__ */ new Date()).toISOString(),
28777
- cli_version: true ? "0.33.2" : "dev"
28870
+ cli_version: true ? "0.33.3" : "dev"
28778
28871
  }
28779
28872
  });
28780
28873
  } catch (err) {
@@ -29400,7 +29493,7 @@ function registerMemoryCommand(program2) {
29400
29493
  process.exit(0);
29401
29494
  }
29402
29495
  if (result.skipped === "already_seeded") {
29403
- printInfo("Memory graph already seeded (.verity/memory/domain/project-overview.md exists). Use --force to re-seed.");
29496
+ printInfo("Memory graph already seeded \u2014 this project has seed nodes (locally or on the server). Use --force to re-materialize them.");
29404
29497
  process.exit(0);
29405
29498
  }
29406
29499
  if (opts.dryRun) {
@@ -29411,10 +29504,11 @@ function registerMemoryCommand(program2) {
29411
29504
  process.exit(0);
29412
29505
  }
29413
29506
  console.log("");
29507
+ const existingNote = result.existing > 0 ? ` ${result.existing} already existed on the server and were not duplicated.` : "";
29414
29508
  if (result.failed === 0) {
29415
- printInfo(`Seeded ${result.created} knowledge node(s) from the Standard into .verity/memory/.`);
29509
+ printInfo(`Seeded ${result.created} knowledge node(s) from the Standard into .verity/memory/.${existingNote}`);
29416
29510
  } else {
29417
- printWarn(`Seeded ${result.created} node(s); ${result.failed} failed. See messages above.`);
29511
+ printWarn(`Seeded ${result.created} node(s); ${result.failed} failed.${existingNote} See messages above.`);
29418
29512
  }
29419
29513
  process.exit(result.failed === 0 ? 0 : 1);
29420
29514
  });
@@ -29696,8 +29790,8 @@ function registerTelemetryCommands(program2) {
29696
29790
  }
29697
29791
 
29698
29792
  // src/cli.ts
29699
- program.name("verity").description("CLI for Verity quality gate service").version("0.33.2").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) => {
29700
- installStderrLog(actionCommand.name(), process.argv.slice(2), "0.33.2");
29793
+ program.name("verity").description("CLI for Verity quality gate service").version("0.33.3").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) => {
29794
+ installStderrLog(actionCommand.name(), process.argv.slice(2), "0.33.3");
29701
29795
  setUserNamedServiceUrl(program.opts().serviceUrl);
29702
29796
  try {
29703
29797
  await foldLegacyLocalCredential();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codacy/verity-cli",
3
- "version": "0.33.2",
3
+ "version": "0.33.3",
4
4
  "description": "CLI for Verity quality gate service",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://verity.md",