@saasontools/strauss-kb 0.1.6 → 0.1.8

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
@@ -216,12 +216,16 @@ strauss-kb [--bundle PATH] <command> [args]
216
216
  answer <concept-id> <answer...> Resolve an open question and append the answer.
217
217
  verify <concept-id> --note <text> Append a verified[] event — who checked, when, and what the check found.
218
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.
219
221
  query <text...> Search; every match arrives flagged with its standing.
220
222
  trace <concept-id> [edges...] How a position was arrived at, as a timeline.
221
223
  list [type] Every record, optionally narrowed to one type.
222
224
  index The index, rebuilt if it disagrees with the records.
223
225
  log What touched what, and when.
224
226
  validate Cross-record checks. Exits 1 when it reports a problem.
227
+ doctor [--expiring-days N] [--unverified-days N] [--aging-days N] [--strict]
228
+ Health sweep: what expired, went unconfirmed, aged, or was orphaned.
225
229
  schema JSON Schema for the format.
226
230
  types The twelve types, their sections and initial status.
227
231
  pin [bundle-path] [flags] Pin a base. --mode, --profiles, --frozen; --local/--user pick the layer.
@@ -231,13 +235,20 @@ strauss-kb [--bundle PATH] <command> [args]
231
235
  sync-instructions <file> Plant the context block between sentinels in an instruction file.
232
236
 
233
237
  --bundle PATH defaults to ./.strauss/kb
238
+ --json the machine shape, where a command prints a table
239
+ -- everything after it is text, not flags
234
240
  STRAUSS_KB_ACTOR names the writer in the log
235
241
  ```
236
242
 
237
- Results go to stdout as JSON — `index` is markdown, which is what it is. Errors
238
- go to stderr and exit 1. `validate` is the one command whose exit code is not
239
- just "did it run": a check that reports a problem succeeded as a command and
240
- failed as a check, so it exits 1 with its findings on stdout.
243
+ Results go to stdout as JSON — `index` and `pack` are markdown, which is what
244
+ they are, and `doctor` prints a table unless `--json` asks for the object
245
+ behind it. `--json` is refused rather than ignored on the commands that have
246
+ only one form, since a flag that quietly does nothing reads as one that
247
+ worked; `--` ends flag parsing, for the verbs that end in free prose. Errors
248
+ go to stderr and exit 1. `validate` and `doctor --strict` are the commands
249
+ whose exit code is not just "did it run": a check that reports a problem
250
+ succeeded as a command and failed as a check, so it exits 1 with its findings
251
+ on stdout.
241
252
 
242
253
  ```bash
243
254
  strauss-kb --bundle .strauss/kb write fact <<'JSON'
@@ -258,8 +269,9 @@ strauss-kb validate || echo "problems above"
258
269
 
259
270
  `strauss-kb-mcp` speaks stdio and takes no API key and no required environment.
260
271
  Every CLI verb is a tool: `kb_write`, `kb_write_decision`, `kb_no_decision`,
261
- `kb_status`, `kb_supersede`, `kb_answer`, `kb_verify`, `kb_load`, `kb_query`,
262
- `kb_trace`, `kb_list`, `kb_index`, `kb_log`, `kb_validate`, `kb_schema`, `kb_types`,
272
+ `kb_status`, `kb_supersede`, `kb_answer`, `kb_verify`, `kb_load`, `kb_pack`, `kb_query`,
273
+ `kb_trace`, `kb_list`, `kb_index`, `kb_log`, `kb_validate`, `kb_doctor`,
274
+ `kb_schema`, `kb_types`,
263
275
  `kb_pin`, `kb_unpin`, `kb_pins`, `kb_context`. Most tools take a `bundlePath`;
264
276
  `kb_schema` and `kb_types` describe the format rather than any one base, and
265
277
  `kb_pins` and `kb_context` read the workspace pin manifests instead. The one
@@ -362,6 +374,22 @@ setting to reach for by default. A reader that does not actually need every
362
374
  record is better served by a narrower `type` filter or a `query` than by
363
375
  turning the guardrail off.
364
376
 
377
+ **Pack is the middle rung.** Under budget, load the base whole — perfect
378
+ recall beats any ranking. Over budget, when the work centres on a record you
379
+ can name, `pack` hands over that record's bounded neighbourhood instead:
380
+ everything within `--hops` of the root, walked over the base's edges — body
381
+ links (a `relatedConceptIds` entry is stored as one), supersession in both
382
+ directions, shared code anchors, and shared sources — ranked and cut to
383
+ `--max-nodes`. Standing travels with it: superseded neighbours arrive as the
384
+ same name, replacement and date stubs `load` emits. Every record the cut
385
+ dropped is named under Excluded, because a named gap is knowable and a silent
386
+ one is not, and past its own token budget `pack` refuses exactly as `load`
387
+ does — naming what was already cut, so the caller can narrow the walk or
388
+ raise the ceiling. Below the header, the only place a timestamp appears, the
389
+ output is byte-identical across runs over an unchanged base: two packs diff,
390
+ and a changed byte means changed knowledge. With neither a budget problem nor
391
+ a root record in hand, the question is a point lookup, and that is `query`.
392
+
365
393
  **Flag, never filter.** `query` returns every hit with its standing, because a
366
394
  filtered result set is invisible — the caller cannot tell it missed anything.
367
395
  The single exception is narrow: a superseded record is dropped only when its
@@ -382,6 +410,83 @@ fact; a missing replacement is `broken-chain` with no head — the case that nee
382
410
  the most care, because returning the stale record unmarked looks exactly like
383
411
  success.
384
412
 
413
+ ## Health
414
+
415
+ `doctor` sweeps a whole base and reports what has decayed. It is read-only —
416
+ nothing is re-dated, re-verified, superseded, or deleted — because every
417
+ finding is a judgment somebody has to make: whether a claim still holds, which
418
+ question is worth answering, which island to link or drop.
419
+
420
+ It exists because decay is invisible from inside a single record. A stale
421
+ record reads exactly like a live one, a question nobody answered reads exactly
422
+ like one nobody asked, and a record nothing links to is reachable only by
423
+ someone who already knows it is there. `validate` is the narrower neighbour:
424
+ it asks only whether pointers between records agree.
425
+
426
+ | Check | Reports |
427
+ | ---------------------- | ------------------------------------------------------------------------------ |
428
+ | `expired` | `stale_after` is in the past — or is not a readable date, which is no better. |
429
+ | `expiring` | `stale_after` falls inside the next `--expiring-days` (30). |
430
+ | `unverified` | `verified[]` is empty and the record is over `--unverified-days` (90) old. |
431
+ | `aging` | Still `open` or `proposed` after `--aging-days` (90). |
432
+ | `orphaned` | No other record links to it, by body link or supersession. |
433
+ | `broken-supersession` | A chain that does not resolve: no replacement, a missing one, a cycle, a fork. |
434
+ | `superseded-but-cited` | A record that still holds, whose body links to one that does not. |
435
+
436
+ The last check's name is for its common case: a rejected target counts too, and
437
+ is the worse half — a superseded record at least names its replacement, while a
438
+ rejected one is a well-formed assertion of what someone decided _not_ to do,
439
+ cited by a record the reader trusts.
440
+
441
+ ```bash
442
+ strauss-kb doctor # the table
443
+ strauss-kb doctor --json # the object behind it
444
+ strauss-kb doctor --strict # exit 1 if anything has expired
445
+ strauss-kb doctor --unverified-days 30 # a stricter confirmation window
446
+ ```
447
+
448
+ All seven groups are reported even when empty. A check that found nothing and
449
+ a check that never ran look identical in a report that only lists findings,
450
+ which is the whole value of a sweep.
451
+
452
+ Judgments the checks make, worth knowing before reading a report:
453
+
454
+ - **Superseded and rejected records sit out the freshness checks.** A replaced
455
+ record whose date has passed needs no repair, and reporting it would bury the
456
+ records that do. They stay in the graph checks, where standing is not the
457
+ question.
458
+ - **A date-only `stale_after` expires at UTC midnight.** `2026-09-01` parses as
459
+ `2026-09-01T00:00:00Z`, so a record goes stale at the start of its date: a
460
+ sweep run at exactly that instant still calls it expiring, and one a minute
461
+ later calls it expired. That is `adjudicate`'s comparison rather than a
462
+ second one — two readings of the same field disagreeing about the day would
463
+ be worse than either.
464
+ - **Age is read from `generated.at`, exclusively.** A record carrying no
465
+ timestamp is not reported as aging or unverified — without a start there is
466
+ no duration, and inventing one would flag every foreign record as overdue
467
+ (adjudication still warns `unverified` on it at read time). Exactly N days
468
+ old is not yet "older than N".
469
+ - **`orphaned` counts incoming links only, and reads supersession one way.** A
470
+ record that cites five others and is cited by none is precisely the island:
471
+ reachable if you already know it exists. The replacement references what it
472
+ replaced, never the reverse — taken symmetrically, a dead record would vouch
473
+ for its own replacement and an old→new pair nothing else touches would rescue
474
+ itself. Shared anchors and shared sources are co-location rather than
475
+ reference, so they do not rescue a record either.
476
+ - **A record citing the one it replaced is not superseded-but-cited.** That
477
+ link is the history working as designed, and reporting it would put a finding
478
+ on every correctly performed supersession.
479
+ - **A replacement pointer is checked whatever the status says.** The store
480
+ writes `strauss_status` and `strauss_superseded_by` in one mutation, so a
481
+ record left `accepted` while naming a replacement was hand-edited — and
482
+ adjudication reads it as current no matter what the pointer says, which is
483
+ what makes it worth naming.
484
+
485
+ `--strict` gates on expiry alone. The other six report debt a reader decides
486
+ about; an expired record is the base itself saying it would stop standing
487
+ behind something, which is the one finding a pipeline can act on without a
488
+ judgment call.
489
+
385
490
  ## Living in an agent session
386
491
 
387
492
  Long sessions lose a knowledge base twice over: attention decays, and
@@ -2,20 +2,35 @@ import {
2
2
  KB_COMMANDS,
3
3
  KB_COMMANDS_BY_NAME,
4
4
  KB_DIR,
5
- KbStore
6
- } from "./chunk-PNSRTKYN.js";
5
+ KbStore,
6
+ VERSION
7
+ } from "./chunk-YJK7KGHN.js";
7
8
 
8
9
  // src/cli.ts
9
10
  import { join } from "path";
10
11
  async function runKbCli(argv) {
11
- const { bundle, rest } = takeBundle(argv);
12
- const name = rest[0] ?? "";
12
+ const { flags, literal } = takeLiteral(argv);
13
+ const { bundle, rest: withFlags } = takeBundle(flags);
14
+ const name = withFlags[0] ?? "";
13
15
  if (!name || name === "-h" || name === "--help") {
14
16
  process.stdout.write(usage());
15
17
  return;
16
18
  }
19
+ if (name === "--version" || name === "-v") {
20
+ process.stdout.write(`${VERSION}
21
+ `);
22
+ return;
23
+ }
17
24
  const command = KB_COMMANDS_BY_NAME.get(name);
18
25
  if (!command) die(`unknown command ${name}`);
26
+ const json = withFlags.includes("--json");
27
+ if (json && !command.render) {
28
+ die(`${name} takes no --json: its result is already the machine shape`);
29
+ }
30
+ const rest = [
31
+ ...json ? withFlags.filter((argument) => argument !== "--json") : withFlags,
32
+ ...literal
33
+ ];
19
34
  const raw = await command.fromArgv(rest, bundle, readStdin);
20
35
  const parsed = command.input.safeParse(raw);
21
36
  if (!parsed.success) {
@@ -35,13 +50,16 @@ async function runKbCli(argv) {
35
50
  },
36
51
  parsed.data
37
52
  );
38
- if (command.failsWhen?.(result)) process.exitCode = 1;
53
+ if (command.failsWhen?.(result, parsed.data)) process.exitCode = 1;
39
54
  if (result === "") return;
40
- process.stdout.write(
41
- typeof result === "string" ? result.endsWith("\n") ? result : `${result}
42
- ` : `${JSON.stringify(result, null, 2)}
43
- `
44
- );
55
+ const text = command.render && !json ? command.render(result) : typeof result === "string" ? result : JSON.stringify(result, null, 2);
56
+ process.stdout.write(text.endsWith("\n") ? text : `${text}
57
+ `);
58
+ }
59
+ function takeLiteral(argv) {
60
+ const at = argv.indexOf("--");
61
+ if (at === -1) return { flags: argv, literal: [] };
62
+ return { flags: argv.slice(0, at), literal: argv.slice(at + 1) };
45
63
  }
46
64
  function takeBundle(argv) {
47
65
  const at = argv.indexOf("--bundle");
@@ -83,6 +101,9 @@ function usage() {
83
101
  ),
84
102
  "",
85
103
  ` --bundle PATH defaults to ./${KB_DIR}`,
104
+ " --json the machine shape, where a command prints a table",
105
+ " -- everything after it is text, not flags",
106
+ " --version the installed package version",
86
107
  " STRAUSS_KB_ACTOR names the writer in the log",
87
108
  ""
88
109
  ].join("\n");
@@ -91,4 +112,4 @@ function usage() {
91
112
  export {
92
113
  runKbCli
93
114
  };
94
- //# sourceMappingURL=chunk-V7TRZ2ER.js.map
115
+ //# sourceMappingURL=chunk-MGPYUZOM.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 { flags, literal } = takeLiteral(argv);\n const { bundle, rest: withFlags } = takeBundle(flags);\n const name = withFlags[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 // Output shape, not an argument: stripped before the command sees argv, so a\n // positional adapter never has to know the flag exists. Refused rather than\n // ignored where a command has only one form — a flag that silently does\n // nothing teaches a caller that it worked.\n const json = withFlags.includes(\"--json\");\n if (json && !command.render) {\n die(`${name} takes no --json: its result is already the machine shape`);\n }\n const rest = [\n ...(json\n ? withFlags.filter((argument) => argument !== \"--json\")\n : withFlags),\n ...literal,\n ];\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, parsed.data)) 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 const text =\n command.render && !json\n ? command.render(result)\n : typeof result === \"string\"\n ? result\n : JSON.stringify(result, null, 2);\n process.stdout.write(text.endsWith(\"\\n\") ? text : `${text}\\n`);\n}\n\n/**\n * Everything after a bare `--` is text, never flags.\n *\n * Several verbs end in free prose — `no-decision`, `answer`, `query` — and a\n * reason that happens to contain `--json` or `--bundle` would otherwise lose a\n * word to the flag scan, or two. The sentinel is the usual escape, and the\n * token itself is dropped while the order of everything else is kept, so the\n * positional adapters still index the same way.\n */\nfunction takeLiteral(argv: string[]): { flags: string[]; literal: string[] } {\n const at = argv.indexOf(\"--\");\n if (at === -1) return { flags: argv, literal: [] };\n return { flags: argv.slice(0, at), literal: argv.slice(at + 1) };\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 \" --json the machine shape, where a command prints a table\",\n \" -- everything after it is text, not flags\",\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":[]}