@viloforge/vfkb 0.2.1 → 0.2.2

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 CHANGED
@@ -97,6 +97,24 @@ these are good tools solving a *different* problem:
97
97
 
98
98
  ## Install
99
99
 
100
+ ### From npm
101
+
102
+ ```bash
103
+ npm install -g @viloforge/vfkb
104
+
105
+ vfkb --version # prints the installed version
106
+ vfkb init # wire a repo: .vfkb/ brain + harness hooks
107
+ vfkb-mcp # the MCP server (stdio)
108
+ ```
109
+
110
+ Published as [`@viloforge/vfkb`](https://www.npmjs.com/package/@viloforge/vfkb) with
111
+ [provenance attestation](https://docs.npmjs.com/generating-provenance-statements) (SLSA v1,
112
+ built on GitHub Actions from this repo). The install path is delivery-proven, not assumed:
113
+ every release's [publish workflow](.github/workflows/publish.yml) ends in a canary job on a
114
+ fresh runner — no checkout, no cache — that installs the just-published version from the real
115
+ registry and content-asserts the CLI, `init` scaffolding, an add/list round-trip, and an MCP
116
+ initialize handshake ([v0.2.1 canary run](https://github.com/vilosource/vfkb/actions/runs/29197631539)).
117
+
100
118
  ### From source
101
119
 
102
120
  ```bash
@@ -6,7 +6,11 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
8
  var __commonJS = (cb, mod) => function __require() {
9
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
9
+ try {
10
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
+ } catch (e) {
12
+ throw mod = 0, e;
13
+ }
10
14
  };
11
15
  var __export = (target, all) => {
12
16
  for (var name in all)
@@ -10593,7 +10597,6 @@ ZodNaN.create = (params) => {
10593
10597
  ...processCreateParams(params)
10594
10598
  });
10595
10599
  };
10596
- var BRAND = Symbol("zod_brand");
10597
10600
  var ZodBranded = class extends ZodType {
10598
10601
  _parse(input) {
10599
10602
  const { ctx } = this._processInputParams(input);
@@ -11098,7 +11101,7 @@ function $constructor(name, initializer3, params) {
11098
11101
  Object.defineProperty(_, "name", { value: name });
11099
11102
  return _;
11100
11103
  }
11101
- var $brand = Symbol("zod_brand");
11104
+ var $brand = /* @__PURE__ */ Symbol("zod_brand");
11102
11105
  var $ZodAsyncError = class extends Error {
11103
11106
  constructor() {
11104
11107
  super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`);
@@ -15527,6 +15530,7 @@ var error5 = () => {
15527
15530
  return `Clau inv\xE0lida a ${issue2.origin}`;
15528
15531
  case "invalid_union":
15529
15532
  return "Entrada inv\xE0lida";
15533
+ // Could also be "Tipus d'unió invàlid" but "Entrada invàlida" is more general
15530
15534
  case "invalid_element":
15531
15535
  return `Element inv\xE0lid a ${issue2.origin}`;
15532
15536
  default:
@@ -20840,8 +20844,8 @@ function yo_default() {
20840
20844
 
20841
20845
  // node_modules/zod/v4/core/registries.js
20842
20846
  var _a2;
20843
- var $output = Symbol("ZodOutput");
20844
- var $input = Symbol("ZodInput");
20847
+ var $output = /* @__PURE__ */ Symbol("ZodOutput");
20848
+ var $input = /* @__PURE__ */ Symbol("ZodInput");
20845
20849
  var $ZodRegistry = class {
20846
20850
  constructor() {
20847
20851
  this._map = /* @__PURE__ */ new WeakMap();
@@ -21878,7 +21882,7 @@ function _stringbool(Classes, _params) {
21878
21882
  type: "pipe",
21879
21883
  in: stringSchema,
21880
21884
  out: booleanSchema,
21881
- transform: (input, payload) => {
21885
+ transform: ((input, payload) => {
21882
21886
  let data = input;
21883
21887
  if (params.case !== "sensitive")
21884
21888
  data = data.toLowerCase();
@@ -21897,14 +21901,14 @@ function _stringbool(Classes, _params) {
21897
21901
  });
21898
21902
  return {};
21899
21903
  }
21900
- },
21901
- reverseTransform: (input, _payload) => {
21904
+ }),
21905
+ reverseTransform: ((input, _payload) => {
21902
21906
  if (input === true) {
21903
21907
  return truthyArray[0] || "true";
21904
21908
  } else {
21905
21909
  return falsyArray[0] || "false";
21906
21910
  }
21907
- },
21911
+ }),
21908
21912
  error: params.error
21909
21913
  });
21910
21914
  return codec2;
@@ -22933,10 +22937,10 @@ var ZodMiniType = /* @__PURE__ */ $constructor("ZodMiniType", (inst, def) => {
22933
22937
  inst.with = inst.check;
22934
22938
  inst.clone = (_def, params) => clone(inst, _def, params);
22935
22939
  inst.brand = () => inst;
22936
- inst.register = (reg, meta3) => {
22940
+ inst.register = ((reg, meta3) => {
22937
22941
  reg.add(inst, meta3);
22938
22942
  return inst;
22939
- };
22943
+ });
22940
22944
  inst.apply = (fn) => fn(inst);
22941
22945
  });
22942
22946
  var ZodMiniObject = /* @__PURE__ */ $constructor("ZodMiniObject", (inst, def) => {
@@ -26843,11 +26847,13 @@ function assertCompleteRequestPrompt(request) {
26843
26847
  if (request.params.ref.type !== "ref/prompt") {
26844
26848
  throw new TypeError(`Expected CompleteRequestPrompt, but got ${request.params.ref.type}`);
26845
26849
  }
26850
+ void request;
26846
26851
  }
26847
26852
  function assertCompleteRequestResourceTemplate(request) {
26848
26853
  if (request.params.ref.type !== "ref/resource") {
26849
26854
  throw new TypeError(`Expected CompleteRequestResourceTemplate, but got ${request.params.ref.type}`);
26850
26855
  }
26856
+ void request;
26851
26857
  }
26852
26858
  var CompleteResultSchema = ResultSchema.extend({
26853
26859
  completion: looseObject({
@@ -27000,7 +27006,7 @@ function isTerminal(status) {
27000
27006
  }
27001
27007
 
27002
27008
  // node_modules/zod-to-json-schema/dist/esm/Options.js
27003
- var ignoreOverride = Symbol("Let zodToJsonSchema decide on which parser to use");
27009
+ var ignoreOverride = /* @__PURE__ */ Symbol("Let zodToJsonSchema decide on which parser to use");
27004
27010
  var defaultOptions = {
27005
27011
  name: void 0,
27006
27012
  $refStrategy: "root",
@@ -29976,7 +29982,7 @@ var Server = class extends Protocol {
29976
29982
  };
29977
29983
 
29978
29984
  // node_modules/@modelcontextprotocol/sdk/dist/esm/server/completable.js
29979
- var COMPLETABLE_SYMBOL = Symbol.for("mcp.completable");
29985
+ var COMPLETABLE_SYMBOL = /* @__PURE__ */ Symbol.for("mcp.completable");
29980
29986
  function isCompletable(schema) {
29981
29987
  return !!schema && typeof schema === "object" && COMPLETABLE_SYMBOL in schema;
29982
29988
  }
@@ -31934,7 +31940,7 @@ function queryExplained(opts = {}) {
31934
31940
  }
31935
31941
 
31936
31942
  // src/version.ts
31937
- var ENGINE_VERSION = true ? "0.2.1" : ownPackageVersion();
31943
+ var ENGINE_VERSION = true ? "0.2.2" : ownPackageVersion();
31938
31944
 
31939
31945
  // src/mcp-server.ts
31940
31946
  var SEARCH_DEFAULT_LIMIT = 25;
@@ -31970,8 +31976,24 @@ function renderNoMatch(d, q) {
31970
31976
  }
31971
31977
  }
31972
31978
  }
31973
- function tags(csv) {
31974
- return csv ? csv.split(",").map((t) => t.trim()).filter(Boolean) : void 0;
31979
+ function tags(csv, label = "tags") {
31980
+ if (csv === void 0) return void 0;
31981
+ const s = csv.trim();
31982
+ if (!s) return void 0;
31983
+ if (s.startsWith("[")) {
31984
+ let parsed;
31985
+ try {
31986
+ parsed = JSON.parse(s);
31987
+ } catch {
31988
+ throw new Error(`${label} received JSON-array-shaped input that does not parse: ${s} \u2014 pass a comma-separated string like "a,b"`);
31989
+ }
31990
+ if (!Array.isArray(parsed)) throw new Error(`${label} received JSON that is not an array: ${s}`);
31991
+ if (!parsed.every((t) => typeof t === "string")) {
31992
+ throw new Error(`${label} JSON array must contain only strings: ${s}`);
31993
+ }
31994
+ return parsed.map((t) => t.trim()).filter(Boolean);
31995
+ }
31996
+ return s.split(",").map((t) => t.trim()).filter(Boolean);
31975
31997
  }
31976
31998
  function envRole() {
31977
31999
  const p = ROLE2.safeParse(process.env.VFKB_ROLE);
@@ -32062,7 +32084,7 @@ server.registerTool(
32062
32084
  type: ENTRY_TYPE,
32063
32085
  text: external_exports.string(),
32064
32086
  why: external_exports.string().optional().describe('rationale; stored structurally AND folded into the text as a "Why: \u2026" line (esp. for decisions)'),
32065
- tags: external_exports.string().optional().describe("comma-separated"),
32087
+ tags: external_exports.string().optional().describe('comma-separated (a tag itself cannot start with "[")'),
32066
32088
  path: external_exports.string().optional().describe(
32067
32089
  'link target (path or URL) for type=link \u2014 folded into the text as "<text> \u2192 <path>" (link entries have no structural target field); rejected for other types'
32068
32090
  ),
@@ -32084,7 +32106,7 @@ server.registerTool(
32084
32106
  role: envRole() ?? a.role ?? "executor",
32085
32107
  why: a.why,
32086
32108
  tags: tags(a.tags),
32087
- contradicts: tags(a.contradicts),
32109
+ contradicts: tags(a.contradicts, "contradicts"),
32088
32110
  status: a.status,
32089
32111
  constitutional: a.constitutional
32090
32112
  });
@@ -32099,11 +32121,14 @@ server.registerTool(
32099
32121
  old_id: external_exports.string(),
32100
32122
  text: external_exports.string(),
32101
32123
  why: external_exports.string().optional().describe('rationale for the new decision; stored structurally AND folded into its text as a "Why: \u2026" line'),
32124
+ // Issue #127: without this in the schema the SDK strips a model-supplied
32125
+ // `tags` silently and every successor lands untagged.
32126
+ tags: external_exports.string().optional().describe('comma-separated; tags for the NEW decision (a tag itself cannot start with "[")'),
32102
32127
  role: ROLE2.optional()
32103
32128
  }
32104
32129
  },
32105
32130
  async (a) => {
32106
- const e = supersede(a.old_id, a.text, { role: envRole() ?? a.role ?? "architect", why: a.why });
32131
+ const e = supersede(a.old_id, a.text, { role: envRole() ?? a.role ?? "architect", why: a.why, tags: tags(a.tags) });
32107
32132
  return text(`superseded ${a.old_id} -> ${line(e)}`);
32108
32133
  }
32109
32134
  );
@@ -786,7 +786,7 @@ function $constructor(name, initializer3, params) {
786
786
  Object.defineProperty(_, "name", { value: name });
787
787
  return _;
788
788
  }
789
- var $brand = Symbol("zod_brand");
789
+ var $brand = /* @__PURE__ */ Symbol("zod_brand");
790
790
  var $ZodAsyncError = class extends Error {
791
791
  constructor() {
792
792
  super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`);
@@ -5215,6 +5215,7 @@ var error5 = () => {
5215
5215
  return `Clau inv\xE0lida a ${issue2.origin}`;
5216
5216
  case "invalid_union":
5217
5217
  return "Entrada inv\xE0lida";
5218
+ // Could also be "Tipus d'unió invàlid" but "Entrada invàlida" is more general
5218
5219
  case "invalid_element":
5219
5220
  return `Element inv\xE0lid a ${issue2.origin}`;
5220
5221
  default:
@@ -10528,8 +10529,8 @@ function yo_default() {
10528
10529
 
10529
10530
  // node_modules/zod/v4/core/registries.js
10530
10531
  var _a2;
10531
- var $output = Symbol("ZodOutput");
10532
- var $input = Symbol("ZodInput");
10532
+ var $output = /* @__PURE__ */ Symbol("ZodOutput");
10533
+ var $input = /* @__PURE__ */ Symbol("ZodInput");
10533
10534
  var $ZodRegistry = class {
10534
10535
  constructor() {
10535
10536
  this._map = /* @__PURE__ */ new WeakMap();
@@ -11566,7 +11567,7 @@ function _stringbool(Classes, _params) {
11566
11567
  type: "pipe",
11567
11568
  in: stringSchema,
11568
11569
  out: booleanSchema,
11569
- transform: (input, payload) => {
11570
+ transform: ((input, payload) => {
11570
11571
  let data = input;
11571
11572
  if (params.case !== "sensitive")
11572
11573
  data = data.toLowerCase();
@@ -11585,14 +11586,14 @@ function _stringbool(Classes, _params) {
11585
11586
  });
11586
11587
  return {};
11587
11588
  }
11588
- },
11589
- reverseTransform: (input, _payload) => {
11589
+ }),
11590
+ reverseTransform: ((input, _payload) => {
11590
11591
  if (input === true) {
11591
11592
  return truthyArray[0] || "true";
11592
11593
  } else {
11593
11594
  return falsyArray[0] || "false";
11594
11595
  }
11595
- },
11596
+ }),
11596
11597
  error: params.error
11597
11598
  });
11598
11599
  return codec2;
@@ -16345,8 +16346,8 @@ import { dirname as dirname3, join as join9 } from "node:path";
16345
16346
 
16346
16347
  // src/version.ts
16347
16348
  var SCHEMA_VERSION = 1;
16348
- var ENGINE_VERSION = true ? "0.2.1" : ownPackageVersion();
16349
- var ENGINE_COMMIT = true ? "38bbdb9" : "dev";
16349
+ var ENGINE_VERSION = true ? "0.2.2" : ownPackageVersion();
16350
+ var ENGINE_COMMIT = true ? "046120c" : "dev";
16350
16351
 
16351
16352
  // src/manifest.ts
16352
16353
  function manifestPath(brainDir2) {
@@ -17067,9 +17068,13 @@ import { readFileSync as readFileSync11 } from "node:fs";
17067
17068
  import { fileURLToPath } from "node:url";
17068
17069
  import { dirname as dirname5, join as join13 } from "node:path";
17069
17070
  function packageVersion() {
17070
- const here = dirname5(fileURLToPath(import.meta.url));
17071
- const pkg = JSON.parse(readFileSync11(join13(here, "..", "package.json"), "utf8"));
17072
- return pkg.version || "0.0.0";
17071
+ try {
17072
+ const here = dirname5(fileURLToPath(import.meta.url));
17073
+ const pkg = JSON.parse(readFileSync11(join13(here, "..", "package.json"), "utf8"));
17074
+ return pkg.version || ENGINE_VERSION;
17075
+ } catch {
17076
+ return ENGINE_VERSION;
17077
+ }
17073
17078
  }
17074
17079
  function readStdin() {
17075
17080
  return new Promise((resolve3) => {
@@ -174,4 +174,16 @@ describe('FR-2 portable single-file engine bundles (ADR-0030)', () => {
174
174
  const entries = readFileSync(join(brain, 'entries.jsonl'), 'utf8');
175
175
  expect(entries).toContain('bundle-portability-smoke');
176
176
  }, 30000);
177
+ // The bundle layout has no ../package.json (that resolves to dist/package.json),
178
+ // so a --version that insists on the manifest crashes every bundle deployment —
179
+ // observed 2026-07-12 vendoring into the Claude Code plugin. The bundle's
180
+ // ENGINE_VERSION is define-injected from the same manifest at build time, so
181
+ // falling back to it keeps ADR-0057's "never hardcoded" intent.
182
+ it('vfkb.mjs --version answers from a non-repo cwd (no ../package.json to read)', async () => {
183
+ const cwd = mkdtempSync(join(tmpdir(), 'vfkb-consumer-'));
184
+ const r = await run('node', [bundles.cli, '--version'], { cwd });
185
+ expect(r.code, `--version crashed:\n${r.stderr}`).toBe(0);
186
+ const pkg = JSON.parse(readFileSync(join(repoRoot, 'package.json'), 'utf8'));
187
+ expect(r.stdout.trim()).toBe(pkg.version);
188
+ }, 30000);
177
189
  });
package/dist/cli.js CHANGED
@@ -26,10 +26,20 @@ import { dirname, join } from 'node:path';
26
26
  // hardcoded (ADR-0057 step 1). Resolved relative to this module file so it works
27
27
  // from dist/cli.js inside an `npm i -g` install: bin -> dist/cli.js, package.json
28
28
  // is one level up from dist/, in both the tsc dev build and the packed tarball.
29
+ // The single-file bundles sit in dist/bundles/ where ../package.json does NOT
30
+ // exist — an unguarded read crashed --version in every bundle deployment
31
+ // (observed 2026-07-12 vendoring into the plugin). A bundle's ENGINE_VERSION is
32
+ // define-injected from this same manifest at build time, so falling back to it
33
+ // is the manifest's version by another route, not a hardcoded literal.
29
34
  function packageVersion() {
30
- const here = dirname(fileURLToPath(import.meta.url));
31
- const pkg = JSON.parse(readFileSync(join(here, '..', 'package.json'), 'utf8'));
32
- return pkg.version || '0.0.0';
35
+ try {
36
+ const here = dirname(fileURLToPath(import.meta.url));
37
+ const pkg = JSON.parse(readFileSync(join(here, '..', 'package.json'), 'utf8'));
38
+ return pkg.version || ENGINE_VERSION;
39
+ }
40
+ catch {
41
+ return ENGINE_VERSION;
42
+ }
33
43
  }
34
44
  function readStdin() {
35
45
  return new Promise((resolve) => {
@@ -58,8 +58,35 @@ function renderNoMatch(d, q) {
58
58
  }
59
59
  }
60
60
  }
61
- function tags(csv) {
62
- return csv ? csv.split(',').map((t) => t.trim()).filter(Boolean) : undefined;
61
+ // Issue #127: some harnesses serialize array-valued params to strings, so the
62
+ // JSON text of an array can arrive here (`'["a","b"]'`). A naive CSV split
63
+ // stores a mangled fragment per element, and a silent fallback is only ever
64
+ // discovered by a later reviewer — so JSON-array-shaped input is parsed
65
+ // honestly, and garbage errors loudly back to the agent instead of degrading.
66
+ function tags(csv, label = 'tags') {
67
+ if (csv === undefined)
68
+ return undefined;
69
+ const s = csv.trim();
70
+ if (!s)
71
+ return undefined;
72
+ if (s.startsWith('[')) {
73
+ let parsed;
74
+ try {
75
+ parsed = JSON.parse(s);
76
+ }
77
+ catch {
78
+ throw new Error(`${label} received JSON-array-shaped input that does not parse: ${s} — pass a comma-separated string like "a,b"`);
79
+ }
80
+ if (!Array.isArray(parsed))
81
+ throw new Error(`${label} received JSON that is not an array: ${s}`);
82
+ // String()-coercing a non-string element would store mangled values like
83
+ // '[object Object]' silently — the exact class this parser exists to stop.
84
+ if (!parsed.every((t) => typeof t === 'string')) {
85
+ throw new Error(`${label} JSON array must contain only strings: ${s}`);
86
+ }
87
+ return parsed.map((t) => t.trim()).filter(Boolean);
88
+ }
89
+ return s.split(',').map((t) => t.trim()).filter(Boolean);
63
90
  }
64
91
  // In the fleet, who wrote an entry must be stamped by the HARNESS, not self-reported
65
92
  // by the model (VERIFIED = observed, not asserted — applied to provenance). When the
@@ -143,7 +170,7 @@ server.registerTool('kb_add', {
143
170
  type: ENTRY_TYPE,
144
171
  text: z.string(),
145
172
  why: z.string().optional().describe('rationale; stored structurally AND folded into the text as a "Why: …" line (esp. for decisions)'),
146
- tags: z.string().optional().describe('comma-separated'),
173
+ tags: z.string().optional().describe('comma-separated (a tag itself cannot start with "[")'),
147
174
  path: z
148
175
  .string()
149
176
  .optional()
@@ -168,7 +195,7 @@ server.registerTool('kb_add', {
168
195
  role: envRole() ?? a.role ?? 'executor',
169
196
  why: a.why,
170
197
  tags: tags(a.tags),
171
- contradicts: tags(a.contradicts),
198
+ contradicts: tags(a.contradicts, 'contradicts'),
172
199
  status: a.status,
173
200
  constitutional: a.constitutional,
174
201
  });
@@ -183,10 +210,13 @@ server.registerTool('kb_supersede', {
183
210
  .string()
184
211
  .optional()
185
212
  .describe('rationale for the new decision; stored structurally AND folded into its text as a "Why: …" line'),
213
+ // Issue #127: without this in the schema the SDK strips a model-supplied
214
+ // `tags` silently and every successor lands untagged.
215
+ tags: z.string().optional().describe('comma-separated; tags for the NEW decision (a tag itself cannot start with "[")'),
186
216
  role: ROLE.optional(),
187
217
  },
188
218
  }, async (a) => {
189
- const e = supersede(a.old_id, a.text, { role: envRole() ?? a.role ?? 'architect', why: a.why });
219
+ const e = supersede(a.old_id, a.text, { role: envRole() ?? a.role ?? 'architect', why: a.why, tags: tags(a.tags) });
190
220
  return text(`superseded ${a.old_id} -> ${line(e)}`);
191
221
  });
192
222
  server.registerTool('kb_transition', {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viloforge/vfkb",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "ViloForge KnowledgeBase (vfkb) — per-project knowledge substrate for the ViloForge software factory. Greenfield TypeScript.",
5
5
  "type": "module",
6
6
  "main": "dist/engine.js",
@@ -39,10 +39,10 @@
39
39
  },
40
40
  "devDependencies": {
41
41
  "@tsconfig/node20": "^20.1.9",
42
- "@types/node": "^25.9.1",
43
- "esbuild": "^0.21.5",
44
- "typescript": "^5.5.0",
45
- "vitest": "^2.0.0"
42
+ "@types/node": "^26.1.1",
43
+ "esbuild": "^0.28.1",
44
+ "typescript": "^7.0.2",
45
+ "vitest": "^4.1.10"
46
46
  },
47
47
  "dependencies": {
48
48
  "@modelcontextprotocol/sdk": "^1.29.0",