@saasontools/strauss-kb 0.1.2 → 0.1.4
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 +121 -6
- package/dist/chunk-EDH43Z7J.js +2377 -0
- package/dist/chunk-EDH43Z7J.js.map +1 -0
- package/dist/{chunk-WFHYWZX5.js → chunk-TS26G7TL.js} +3 -2
- package/dist/chunk-TS26G7TL.js.map +1 -0
- package/dist/{chunk-KGM34MYU.js → chunk-Y5C7Z2HG.js} +3 -2
- package/dist/chunk-Y5C7Z2HG.js.map +1 -0
- package/dist/cli-main.cjs +1303 -459
- package/dist/cli-main.cjs.map +1 -1
- package/dist/cli-main.js +2 -2
- package/dist/index.cjs +1185 -308
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +298 -13
- package/dist/index.d.ts +298 -13
- package/dist/index.js +45 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp-main.cjs +1299 -455
- package/dist/mcp-main.cjs.map +1 -1
- package/dist/mcp-main.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-KGM34MYU.js.map +0 -1
- package/dist/chunk-WFHYWZX5.js.map +0 -1
- package/dist/chunk-ZSYSHJVZ.js +0 -1527
- package/dist/chunk-ZSYSHJVZ.js.map +0 -1
package/README.md
CHANGED
|
@@ -151,11 +151,40 @@ before publishing, which narrows the lost-update window rather than closing it.
|
|
|
151
151
|
[ARCHITECTURE.md](./ARCHITECTURE.md) says why a lock was rejected.
|
|
152
152
|
|
|
153
153
|
`supersede` writes both directions, so a backlink cannot drift in normal use and
|
|
154
|
-
`validate` drops to catching hand-edits.
|
|
154
|
+
`validate` drops to catching hand-edits. A `write` (or `write-decision`) that
|
|
155
|
+
carries `supersedes` does the same: the new record publishes first, then each
|
|
156
|
+
prior record it names is marked superseded in turn — a crash between the two
|
|
157
|
+
leaves an old record with no backlink, which `validate` already reports as
|
|
158
|
+
"is not marked superseded", never a silent drift. A `supersedes` id naming a
|
|
159
|
+
record that does not exist yet is legal and does not fail the write; `validate`
|
|
160
|
+
is what reports a target that never resolves. A `supersedes` id naming the
|
|
161
|
+
record's own concept id is a no-op rather than an error, duplicate ids mark
|
|
162
|
+
once, and the array is capped at 32 entries.
|
|
163
|
+
|
|
164
|
+
A concurrent writer marking the same target races the compare-and-swap check;
|
|
165
|
+
that's retried a few times before giving up, and giving up is reported the
|
|
166
|
+
same way as a target that doesn't exist yet — left out of `supersededIds` for
|
|
167
|
+
`validate` to catch, not thrown, since the calling record is already
|
|
168
|
+
published by that point. If two different records both name the same target
|
|
169
|
+
in `supersedes`, the target's backlink points at whichever wrote last;
|
|
170
|
+
`validate` doesn't see this as a problem because the target genuinely is
|
|
171
|
+
superseded, but `kb_query`/`kb_load`'s adjudication surfaces the resulting
|
|
172
|
+
fork as a warning at read time.
|
|
173
|
+
|
|
174
|
+
`kb_write` and `kb_write_decision` return
|
|
175
|
+
`{ conceptId, action: "created" | "superseded-prior", supersededIds }` —
|
|
176
|
+
`supersededIds` is only the ids actually marked, not every id the input named.
|
|
177
|
+
A 409 from a concept-id collision carries `action: "refused"` in its `details`,
|
|
178
|
+
alongside the `conceptId`.
|
|
155
179
|
|
|
156
180
|
Records are never deleted. Superseding keeps the earlier reasoning inspectable,
|
|
157
181
|
which is what a later `trace` reads.
|
|
158
182
|
|
|
183
|
+
`no-decision` records the explicit claim that a piece of work had nothing to
|
|
184
|
+
decide (an idempotent `decision.none` record). It exists for workflow gates:
|
|
185
|
+
"did you write a decision?" rewards writing a junk one, "did you answer?"
|
|
186
|
+
does not — so silence has to be expressible.
|
|
187
|
+
|
|
159
188
|
## CLI
|
|
160
189
|
|
|
161
190
|
```
|
|
@@ -176,6 +205,11 @@ strauss-kb [--bundle PATH] <command> [args]
|
|
|
176
205
|
validate Cross-record checks. Exits 1 when it reports a problem.
|
|
177
206
|
schema JSON Schema for the format.
|
|
178
207
|
types The twelve types, their sections and initial status.
|
|
208
|
+
pin [bundle-path] [flags] Pin a base. --mode, --profiles, --frozen; --local/--user pick the layer.
|
|
209
|
+
unpin [bundle-path] Remove a base from every manifest layer that holds it.
|
|
210
|
+
pins Every pinned base, with whether it resolves to records.
|
|
211
|
+
context [--profile NAME] [--budget N] The pinned-base index block, for injection at context birth.
|
|
212
|
+
sync-instructions <file> Plant the context block between sentinels in an instruction file.
|
|
179
213
|
|
|
180
214
|
--bundle PATH defaults to ./.strauss/kb
|
|
181
215
|
STRAUSS_KB_ACTOR names the writer in the log
|
|
@@ -206,9 +240,12 @@ strauss-kb validate || echo "problems above"
|
|
|
206
240
|
`strauss-kb-mcp` speaks stdio and takes no API key and no required environment.
|
|
207
241
|
Every CLI verb is a tool: `kb_write`, `kb_write_decision`, `kb_no_decision`,
|
|
208
242
|
`kb_status`, `kb_supersede`, `kb_answer`, `kb_load`, `kb_query`, `kb_trace`,
|
|
209
|
-
`kb_list`, `kb_index`, `kb_log`, `kb_validate`, `kb_schema`, `kb_types
|
|
210
|
-
|
|
211
|
-
format rather than any one base
|
|
243
|
+
`kb_list`, `kb_index`, `kb_log`, `kb_validate`, `kb_schema`, `kb_types`,
|
|
244
|
+
`kb_pin`, `kb_unpin`, `kb_pins`, `kb_context`. Most tools take a `bundlePath`;
|
|
245
|
+
`kb_schema` and `kb_types` describe the format rather than any one base, and
|
|
246
|
+
`kb_pins` and `kb_context` read the workspace pin manifests instead. The one
|
|
247
|
+
CLI verb with no tool is `sync-instructions` — file plumbing for hooks, not an
|
|
248
|
+
agent capability; the capability is `kb_context`.
|
|
212
249
|
|
|
213
250
|
```json
|
|
214
251
|
{
|
|
@@ -269,7 +306,10 @@ base answered eight; embedding search over the same records answered four. Two
|
|
|
269
306
|
of those differences are structural rather than matters of degree: a reader can
|
|
270
307
|
say no record answers the question, where vector search returns its nearest
|
|
271
308
|
neighbour whatever the distance; and a reader picks the record that answers the
|
|
272
|
-
question rather than the one nearest the topic.
|
|
309
|
+
question rather than the one nearest the topic. The mechanism is simple:
|
|
310
|
+
retrieval makes similarity the gatekeeper, and a match the ranker misses never
|
|
311
|
+
reaches the model. A full read lets the model do the matching itself —
|
|
312
|
+
synonymy, implication across records, aggregation — which no ranker does.
|
|
273
313
|
|
|
274
314
|
Read for a question, not for a session: a base loaded at the start of a long
|
|
275
315
|
conversation is summarised away by the end of it, and reloading costs about
|
|
@@ -278,7 +318,8 @@ three thousand tokens. Read it again at the point of use.
|
|
|
278
318
|
`load` refuses rather than truncating when a base exceeds its budget (25,000
|
|
279
319
|
tokens by default). A truncated base is indistinguishable from a complete one,
|
|
280
320
|
so a caller would answer "that was never decided" from a slice it did not know
|
|
281
|
-
was a slice.
|
|
321
|
+
was a slice. `context` refuses the same way at its own, tighter budget (4,000
|
|
322
|
+
by default). Superseded records come back as name, replacement and date only —
|
|
282
323
|
their bodies no longer hold, and a body read later in a long session outlives
|
|
283
324
|
the qualifier that said so. `trace` still reaches them by id.
|
|
284
325
|
|
|
@@ -302,6 +343,80 @@ fact; a missing replacement is `broken-chain` with no head — the case that nee
|
|
|
302
343
|
the most care, because returning the stale record unmarked looks exactly like
|
|
303
344
|
success.
|
|
304
345
|
|
|
346
|
+
## Living in an agent session
|
|
347
|
+
|
|
348
|
+
Long sessions lose a knowledge base twice over: attention decays, and
|
|
349
|
+
compaction summarises away both the records loaded early and the instruction
|
|
350
|
+
that said to consult them. The fix is two-tier: a small index is re-injected
|
|
351
|
+
at every context birth, and record bodies are fetched by tool when a question
|
|
352
|
+
actually needs them.
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
strauss-kb pin docs/kb # mark a base every session should see
|
|
356
|
+
strauss-kb context # emit the pinned index block
|
|
357
|
+
strauss-kb sync-instructions AGENTS.md # or keep it in an instruction file
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Pins live in `.strauss/kb-pins.json`, committed with the repo. Two more
|
|
361
|
+
layers exist: `.strauss/kb-pins.local.json` (personal, gitignore it) and
|
|
362
|
+
`~/.strauss/kb-pins.json` (every workspace). Nearest layer wins per base,
|
|
363
|
+
`--local`/`--user` write the other layers, and `unpin` removes from all
|
|
364
|
+
three. A malformed layer is skipped on read and refused on write.
|
|
365
|
+
|
|
366
|
+
Per pin:
|
|
367
|
+
|
|
368
|
+
- `--mode full` — inject the records themselves, not just the index. For
|
|
369
|
+
small or critical bases (ADRs). Falls back to a labelled index when it
|
|
370
|
+
cannot fit the block budget.
|
|
371
|
+
- `--mode index` — never inject bodies.
|
|
372
|
+
- `--profiles a,b` — only inject in the named profiles.
|
|
373
|
+
- `--frozen` — the base is concluded; write commands refuse until `--unfreeze`.
|
|
374
|
+
|
|
375
|
+
Budgets are named profiles — `session-start`, `compact`, `turn` — with
|
|
376
|
+
per-repo overrides in the manifest, so hook commands never carry numbers:
|
|
377
|
+
|
|
378
|
+
```json
|
|
379
|
+
{
|
|
380
|
+
"pins": [{ "path": "docs/adr", "mode": "full" }],
|
|
381
|
+
"context": { "compact": { "budgetTokens": 1500 } }
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Flags beat the manifest, the manifest beats the built-ins, and invalid values
|
|
386
|
+
fall back to defaults instead of silencing the index. Past its budget,
|
|
387
|
+
`context` refuses like `load` does — never truncates — and its refusal says
|
|
388
|
+
what to load directly and how to shrink the block.
|
|
389
|
+
|
|
390
|
+
`sync-instructions <file>` keeps the same block between
|
|
391
|
+
`<!-- strauss-kb:begin/end -->` sentinels in AGENTS.md or CLAUDE.md, touching
|
|
392
|
+
nothing outside them. Re-run it when pins change; it is idempotent. This is
|
|
393
|
+
the mechanism for runtimes without a reliable post-compaction hook, since
|
|
394
|
+
instruction files are re-read where conversation history is not.
|
|
395
|
+
|
|
396
|
+
What each runtime gets (configs in the
|
|
397
|
+
[plugin's adapters](../../plugins/strauss-kb/adapters/)):
|
|
398
|
+
|
|
399
|
+
| Layer | Claude Code | Codex CLI | Antigravity CLI |
|
|
400
|
+
| ------------------------- | ------------------ | ------------------------------------------- | -------------------------- |
|
|
401
|
+
| MCP tool descriptions | ✓ | ✓ | ✓ |
|
|
402
|
+
| Session-start injection | SessionStart hook | SessionStart hook | PreInvocation, per turn |
|
|
403
|
+
| Post-compact re-injection | ✓ `compact` source | ✓ client-side; instruction-only when hosted | moot — injected every turn |
|
|
404
|
+
| File-read blocking | opt-in PreToolUse | ✗ (shell is the side door) | opt-in PreToolUse, JSON |
|
|
405
|
+
| Instruction file | CLAUDE.md | AGENTS.md | AGENTS.md + rules/ |
|
|
406
|
+
|
|
407
|
+
One more thing agents add: file tools. A raw read of a record file bypasses
|
|
408
|
+
standing entirely — a superseded record reads exactly like a current one — so
|
|
409
|
+
bases are read through the tools, and a workspace can enforce that with deny
|
|
410
|
+
rules or the plugin's opt-in PreToolUse script:
|
|
411
|
+
|
|
412
|
+
```json
|
|
413
|
+
{
|
|
414
|
+
"permissions": {
|
|
415
|
+
"deny": ["Read(.strauss/kb/**)", "Read(**/.strauss/kb/**)"]
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
305
420
|
## Optional search tier
|
|
306
421
|
|
|
307
422
|
`@tobilu/qmd` is an **optional peer dependency** providing BM25 (`searchLex`,
|