@saasontools/strauss-kb 0.1.5 → 0.1.7

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
@@ -88,6 +88,24 @@ concepts. `type` is the only key OKF requires; `title`, `description`,
88
88
  OKF's. Unknown keys are preserved rather than stripped, as OKF requires of
89
89
  consumers.
90
90
 
91
+ `verified` is the record's append-only trail of checks. Each entry is OKF's
92
+ actor stamp — `{ by, at }` — and the entries this package's `verify` writes add
93
+ a `note`: what the check actually found, not just that one happened. The `note`
94
+ is a strauss extension key on the entries this tool writes, not an OKF
95
+ requirement on the array, so noteless entries a foreign producer wrote remain
96
+ readable, and prior entries are spread forward untouched rather than reshaped.
97
+
98
+ Who may append is the point. A verifier whose actor equals the record's
99
+ `generated.by` — compared case-insensitively over the whole actor, so case
100
+ drift cannot mint a distinct verifier identity — is refused unless the actor
101
+ is `human:`-prefixed: trust that can be self-granted is not trust, and a
102
+ generator re-reading its own output is not an independent check. The refusal
103
+ is recorded in the log as `verify:refused`, so an audit sees the attempt as
104
+ well as the rule. The `human:` prefix itself is an honor-system label — actor
105
+ identity is self-declared through `STRAUSS_KB_ACTOR`, not an authenticated
106
+ identity claim — which is worth knowing when deciding how much weight a
107
+ human-verified event carries.
108
+
91
109
  Anything prefixed `strauss_` is this package's extension, namespaced so a later
92
110
  OKF version defining the same name cannot collide:
93
111
 
@@ -196,7 +214,10 @@ strauss-kb [--bundle PATH] <command> [args]
196
214
  status <concept-id> <status> Move a record's status, compare-and-swap.
197
215
  supersede <concept-id> <replacement-id> Mark a record superseded, linking both directions.
198
216
  answer <concept-id> <answer...> Resolve an open question and append the answer.
217
+ verify <concept-id> --note <text> Append a verified[] event — who checked, when, and what the check found.
199
218
  load [type] [--budget N | --all] Hand over the whole base, each record with its standing.
219
+ pack <conceptId> [--hops N] [--max-nodes N] [--budget N]
220
+ The bounded neighbourhood around one record, every cut named.
200
221
  query <text...> Search; every match arrives flagged with its standing.
201
222
  trace <concept-id> [edges...] How a position was arrived at, as a timeline.
202
223
  list [type] Every record, optionally narrowed to one type.
@@ -215,7 +236,8 @@ strauss-kb [--bundle PATH] <command> [args]
215
236
  STRAUSS_KB_ACTOR names the writer in the log
216
237
  ```
217
238
 
218
- Results go to stdout as JSON — `index` is markdown, which is what it is. Errors
239
+ Results go to stdout as JSON — `index` and `pack` are markdown, which is what
240
+ they are. Errors
219
241
  go to stderr and exit 1. `validate` is the one command whose exit code is not
220
242
  just "did it run": a check that reports a problem succeeded as a command and
221
243
  failed as a check, so it exits 1 with its findings on stdout.
@@ -239,8 +261,8 @@ strauss-kb validate || echo "problems above"
239
261
 
240
262
  `strauss-kb-mcp` speaks stdio and takes no API key and no required environment.
241
263
  Every CLI verb is a tool: `kb_write`, `kb_write_decision`, `kb_no_decision`,
242
- `kb_status`, `kb_supersede`, `kb_answer`, `kb_load`, `kb_query`, `kb_trace`,
243
- `kb_list`, `kb_index`, `kb_log`, `kb_validate`, `kb_schema`, `kb_types`,
264
+ `kb_status`, `kb_supersede`, `kb_answer`, `kb_verify`, `kb_load`, `kb_pack`, `kb_query`,
265
+ `kb_trace`, `kb_list`, `kb_index`, `kb_log`, `kb_validate`, `kb_schema`, `kb_types`,
244
266
  `kb_pin`, `kb_unpin`, `kb_pins`, `kb_context`. Most tools take a `bundlePath`;
245
267
  `kb_schema` and `kb_types` describe the format rather than any one base, and
246
268
  `kb_pins` and `kb_context` read the workspace pin manifests instead. The one
@@ -299,6 +321,15 @@ problem:
299
321
  | Standing | `strauss_status`, the supersession chain | is this still what we hold? |
300
322
  | Freshness | `stale_after`, `verified[]` | has anyone confirmed it lately? |
301
323
 
324
+ Freshness is tiered by who did the confirming. OKF's spec (§5.3) defines the
325
+ trust tiers from the verifying actor's prefix: an empty `verified[]` is
326
+ unverified, an agent-prefixed verifier makes the record machine-confirmed, and
327
+ a `human:`-prefixed verifier makes it human-reviewed. Of that ladder, today's
328
+ adjudication reports only the first rung — the warning it attaches when
329
+ `verified[]` is empty; reporting the full tier is upcoming tooling. When it
330
+ lands, the tier will be derived from the events at read time, never stored, so
331
+ it cannot drift from the trail that justifies it.
332
+
302
333
  **Load before you search.** These bases run to a few thousand tokens — twenty
303
334
  records measured at about 3,000 — so the first thing to try is taking all of it.
304
335
  On nine questions whose wording appears in no record, a reader holding the whole
@@ -334,6 +365,22 @@ setting to reach for by default. A reader that does not actually need every
334
365
  record is better served by a narrower `type` filter or a `query` than by
335
366
  turning the guardrail off.
336
367
 
368
+ **Pack is the middle rung.** Under budget, load the base whole — perfect
369
+ recall beats any ranking. Over budget, when the work centres on a record you
370
+ can name, `pack` hands over that record's bounded neighbourhood instead:
371
+ everything within `--hops` of the root, walked over the base's edges — body
372
+ links (a `relatedConceptIds` entry is stored as one), supersession in both
373
+ directions, shared code anchors, and shared sources — ranked and cut to
374
+ `--max-nodes`. Standing travels with it: superseded neighbours arrive as the
375
+ same name, replacement and date stubs `load` emits. Every record the cut
376
+ dropped is named under Excluded, because a named gap is knowable and a silent
377
+ one is not, and past its own token budget `pack` refuses exactly as `load`
378
+ does — naming what was already cut, so the caller can narrow the walk or
379
+ raise the ceiling. Below the header, the only place a timestamp appears, the
380
+ output is byte-identical across runs over an unchanged base: two packs diff,
381
+ and a changed byte means changed knowledge. With neither a budget problem nor
382
+ a root record in hand, the question is a point lookup, and that is `query`.
383
+
337
384
  **Flag, never filter.** `query` returns every hit with its standing, because a
338
385
  filtered result set is invisible — the caller cannot tell it missed anything.
339
386
  The single exception is narrow: a superseded record is dropped only when its
@@ -2,8 +2,9 @@ import {
2
2
  KB_COMMANDS,
3
3
  KB_COMMANDS_BY_NAME,
4
4
  KB_DIR,
5
- KbStore
6
- } from "./chunk-FZIMFPGR.js";
5
+ KbStore,
6
+ VERSION
7
+ } from "./chunk-GKUQOJEK.js";
7
8
 
8
9
  // src/cli.ts
9
10
  import { join } from "path";
@@ -14,6 +15,11 @@ async function runKbCli(argv) {
14
15
  process.stdout.write(usage());
15
16
  return;
16
17
  }
18
+ if (name === "--version" || name === "-v") {
19
+ process.stdout.write(`${VERSION}
20
+ `);
21
+ return;
22
+ }
17
23
  const command = KB_COMMANDS_BY_NAME.get(name);
18
24
  if (!command) die(`unknown command ${name}`);
19
25
  const raw = await command.fromArgv(rest, bundle, readStdin);
@@ -83,6 +89,7 @@ function usage() {
83
89
  ),
84
90
  "",
85
91
  ` --bundle PATH defaults to ./${KB_DIR}`,
92
+ " --version the installed package version",
86
93
  " STRAUSS_KB_ACTOR names the writer in the log",
87
94
  ""
88
95
  ].join("\n");
@@ -91,4 +98,4 @@ function usage() {
91
98
  export {
92
99
  runKbCli
93
100
  };
94
- //# sourceMappingURL=chunk-KQMGKSPZ.js.map
101
+ //# sourceMappingURL=chunk-GKCG4P3L.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["/**\n * strauss-kb — a knowledge base's command line.\n *\n * A dispatcher over `KB_COMMANDS`, which the MCP server also projects. Nothing\n * command-specific lives here beyond turning argv into the object both\n * surfaces pass.\n */\nimport { join } from \"node:path\";\nimport { KB_COMMANDS, KB_COMMANDS_BY_NAME } from \"./commands/index.js\";\nimport { KB_DIR, KbStore } from \"./kb-store.js\";\nimport { VERSION } from \"./version.js\";\n\nexport async function runKbCli(argv: string[]): Promise<void> {\n const { bundle, rest } = takeBundle(argv);\n const name = rest[0] ?? \"\";\n\n if (!name || name === \"-h\" || name === \"--help\") {\n process.stdout.write(usage());\n return;\n }\n\n // The plugin in front of this CLI updates from a marketplace while the CLI\n // updates from npm, and neither prompts for the other. Answering \"which one\n // is installed\" is what makes that skew diagnosable instead of mysterious.\n if (name === \"--version\" || name === \"-v\") {\n process.stdout.write(`${VERSION}\\n`);\n return;\n }\n\n const command = KB_COMMANDS_BY_NAME.get(name);\n if (!command) die(`unknown command ${name}`);\n\n const raw = await command.fromArgv(rest, bundle, readStdin);\n const parsed = command.input.safeParse(raw);\n if (!parsed.success) {\n die(\n `${name}: ${parsed.error.issues\n .map((issue) => `${issue.path.join(\".\") || \"(root)\"}: ${issue.message}`)\n .join(\"; \")}`,\n );\n }\n\n const store = new KbStore({\n warn: (entry) => process.stderr.write(`${JSON.stringify(entry)}\\n`),\n });\n const result = await command.run(\n {\n store,\n actor: process.env.STRAUSS_KB_ACTOR ?? \"unknown\",\n now: () => new Date().toISOString(),\n },\n parsed.data,\n );\n\n // A check reporting a problem succeeded as a command and failed as a check;\n // the command says which, rather than the dispatcher knowing their names.\n if (command.failsWhen?.(result)) process.exitCode = 1;\n // An empty string is deliberate silence — `context` with nothing pinned\n // runs from hooks at every session start, and even a bare newline is noise\n // injected into a fresh context.\n if (result === \"\") return;\n process.stdout.write(\n typeof result === \"string\"\n ? result.endsWith(\"\\n\")\n ? result\n : `${result}\\n`\n : `${JSON.stringify(result, null, 2)}\\n`,\n );\n}\n\n/**\n * `--bundle` addresses a base directly; without it the command works on the\n * one under the current directory. A base belongs to whatever prompted it, so\n * the default cannot be the only option.\n */\nfunction takeBundle(argv: string[]): { bundle: string; rest: string[] } {\n const at = argv.indexOf(\"--bundle\");\n if (at === -1) {\n return { bundle: join(process.cwd(), KB_DIR), rest: argv };\n }\n const bundle = argv[at + 1];\n if (!bundle) die(\"--bundle requires a path\");\n return { bundle, rest: [...argv.slice(0, at), ...argv.slice(at + 2)] };\n}\n\nfunction readStdin(): Promise<string> {\n return new Promise((resolve, reject) => {\n let text = \"\";\n process.stdin.setEncoding(\"utf8\");\n process.stdin.on(\"data\", (chunk) => (text += chunk));\n process.stdin.on(\"end\", () => resolve(text));\n process.stdin.on(\"error\", reject);\n });\n}\n\nfunction die(message: string): never {\n process.stderr.write(`strauss-kb: error: ${message}\\n`);\n process.exit(1);\n}\n\n/** First sentence, capped — the full text is what an MCP client shows. */\nfunction summarise(description: string): string {\n const first = description.split(\"\\n\")[0] ?? \"\";\n const sentence = first.includes(\". \")\n ? `${first.slice(0, first.indexOf(\". \"))}.`\n : first;\n return sentence.length > 78 ? `${sentence.slice(0, 75)}…` : sentence;\n}\n\nfunction usage(): string {\n const width = Math.max(...KB_COMMANDS.map((command) => command.usage.length));\n return [\n \"strauss-kb — knowledge base commands\",\n \"\",\n \"Usage: strauss-kb [--bundle PATH] <command> [args]\",\n \"\",\n ...KB_COMMANDS.map(\n (command) =>\n ` ${command.usage.padEnd(width)} ${summarise(command.description)}`,\n ),\n \"\",\n ` --bundle PATH defaults to ./${KB_DIR}`,\n \" --version the installed package version\",\n \" STRAUSS_KB_ACTOR names the writer in the log\",\n \"\",\n ].join(\"\\n\");\n}\n"],"mappings":"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","names":[]}