@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 +18 -0
- package/dist/bundles/vfkb-mcp.mjs +44 -19
- package/dist/bundles/vfkb.mjs +17 -12
- package/dist/bundles.test.js +12 -0
- package/dist/cli.js +13 -3
- package/dist/mcp-server.js +35 -5
- package/package.json +5 -5
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
|
-
|
|
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.
|
|
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
|
-
|
|
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(
|
|
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
|
);
|
package/dist/bundles/vfkb.mjs
CHANGED
|
@@ -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.
|
|
16349
|
-
var ENGINE_COMMIT = true ? "
|
|
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
|
-
|
|
17071
|
-
|
|
17072
|
-
|
|
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) => {
|
package/dist/bundles.test.js
CHANGED
|
@@ -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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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) => {
|
package/dist/mcp-server.js
CHANGED
|
@@ -58,8 +58,35 @@ function renderNoMatch(d, q) {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
-
|
|
62
|
-
|
|
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.
|
|
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": "^
|
|
43
|
-
"esbuild": "^0.
|
|
44
|
-
"typescript": "^
|
|
45
|
-
"vitest": "^
|
|
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",
|