@saasontools/strauss-kb 0.1.10 → 0.1.11

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/ARCHITECTURE.md CHANGED
@@ -72,6 +72,23 @@ replacement's first log format was `·`-delimited, with a splitter to read it
72
72
  back. Both are gone: the log is JSONL and the schema is emitted from Zod, so
73
73
  `strauss-kb schema` is the contract rather than a description of one.
74
74
 
75
+ ## Anchor resolution
76
+
77
+ A fresh stamp is a baseline nobody has checked, so only a run where every
78
+ checkable anchor already matched appends `verified[]`.
79
+
80
+ The v1 resolver is regex-based, biased so a wrong answer loses to no answer: a
81
+ span short of the code it claims hashes as stable while that code moves. It
82
+ ranks by shape, scopes a dotted symbol to its parent, captures by brace depth or
83
+ Python indentation, and returns `null` otherwise. A pure `AnchorResolver`
84
+ interface admits a tree-sitter replacement.
85
+
86
+ Repository identity is normalised and compared, not parsed: an invented format
87
+ would reject correct values from unseen hosts.
88
+
89
+ Not one anchored file found under a default root is a wrong root, so its
90
+ findings are dropped.
91
+
75
92
  ## Cross-worktree log safety
76
93
 
77
94
  `log.jsonl` is append-only and the one artifact nothing can rebuild, so how it
package/README.md CHANGED
@@ -150,15 +150,15 @@ human-verified event carries.
150
150
  Anything prefixed `strauss_` is this package's extension, namespaced so a later
151
151
  OKF version defining the same name cannot collide:
152
152
 
153
- | Key | Meaning |
154
- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
155
- | `strauss_status` | `draft`, `proposed`, `accepted`, `open`, `resolved`, `rejected`, `superseded`. Parses with a default of `draft`. |
156
- | `strauss_supersedes` / `strauss_superseded_by` | Both directions of a supersession, written together. |
157
- | `strauss_anchors` | `{ file, symbol? }` — where the record attaches in the code. Symbolic, because a line number written mid-change is wrong by the end of it. |
158
- | `strauss_assumption` | The claim has no source, said as a field rather than as a fake entry in `sources`. |
159
- | `strauss_answered` | Who resolved an open question, and when. |
160
- | `strauss_verify` | Checks that would confirm the record still holds. |
161
- | `strauss_materiality` / `strauss_confidence` / `strauss_owner` | `blocking`/`important`/`non-blocking`, `low`/`medium`/`high`, and a name. |
153
+ | Key | Meaning |
154
+ | -------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
155
+ | `strauss_status` | `draft`, `proposed`, `accepted`, `open`, `resolved`, `rejected`, `superseded`. Parses with a default of `draft`. |
156
+ | `strauss_supersedes` / `strauss_superseded_by` | Both directions of a supersession, written together. |
157
+ | `strauss_anchors` | `{ file, symbol?, hash?, lines?, resolved_at? }` — where the record attaches in the code, and optionally what that code looked like when it did. See [Anchors and drift](#anchors-and-drift). |
158
+ | `strauss_assumption` | The claim has no source, said as a field rather than as a fake entry in `sources`. |
159
+ | `strauss_answered` | Who resolved an open question, and when. |
160
+ | `strauss_verify` | Checks that would confirm the record still holds. |
161
+ | `strauss_materiality` / `strauss_confidence` / `strauss_owner` | `blocking`/`important`/`non-blocking`, `low`/`medium`/`high`, and a name. |
162
162
 
163
163
  Edges are markdown links in the body, as OKF specifies — untyped, with the kind
164
164
  conveyed by the surrounding prose. Broken links are legal: records are routinely
@@ -244,6 +244,55 @@ decide (an idempotent `decision.none` record). It exists for workflow gates:
244
244
  "did you write a decision?" rewards writing a junk one, "did you answer?"
245
245
  does not — so silence has to be expressible.
246
246
 
247
+ ## Anchors and drift
248
+
249
+ An anchor names where a record attaches in the code: a `file`, optionally a
250
+ `symbol` — symbolic, because a line number written mid-change is wrong by the
251
+ end of it. Five optional fields extend it:
252
+
253
+ | Field | Meaning |
254
+ | ------------- | ---------------------------------------------------------------------------------------------------------------- |
255
+ | `hash` | `sha256:<hex>` over the anchored text, algorithm-prefixed. |
256
+ | `lines` | Line count the hash covered. |
257
+ | `resolved_at` | When the anchor last resolved. |
258
+ | `repo` | Which repository — remote URL or short name. Absent means this base's own repository. |
259
+ | `ref` | Git rev the evidence was taken at. Recorded but unused until [SAA-709](https://linear.app/saason/issue/SAA-709). |
260
+
261
+ `hash`, `lines`, and `resolved_at` are **measured** — a resolution pass stamps
262
+ them; `repo` and `ref` are **author-owned identity** and a resolution pass
263
+ never writes them.
264
+
265
+ `anchor-resolve <concept-id>` (`kb_anchor_resolve`) is `verify`'s mechanical
266
+ counterpart: it checks the anchored code against the working tree
267
+ (`--repo-root` when the base is not inside it). Per anchor:
268
+
269
+ - **stamped** — no hash yet; hash, line count, and timestamp are written.
270
+ - **match** — unchanged; nothing written. `--restamp` re-dates on purpose.
271
+ - **drifted** — hash changed. Baseline kept unless `--rebaseline`.
272
+ - **unresolved** — not comparable, with a reason. A finding, not an error.
273
+
274
+ An anchor naming another `repo` is skipped: never read, stamped, or counted.
275
+
276
+ An anchor must not read outside the repository it describes, checked lexically
277
+ and again on the real path after symlinks.
278
+
279
+ Exit code is non-zero on **drifted**, or on **unresolved** for an anchor that
280
+ carries a hash; unstamped and foreign anchors never fail.
281
+
282
+ A fully clean run — every checkable anchor `match`, none stamped this run —
283
+ appends a `verified[]` event.
284
+
285
+ Symbol resolution is a v1 heuristic; ties and unclosed blocks return
286
+ `unresolved`.
287
+
288
+ Drift also surfaces on read: `kb_load` and `kb_query` attach a
289
+ `{ kind: "drifted" }` warning, and `kb_doctor` lists every drifted anchor
290
+ base-wide — all three take `--repo-root`. With no `--repo-root` given, a run
291
+ that finds not one anchored file drops the finding as a wrong root; an explicit
292
+ `--repo-root` is taken at its word.
293
+
294
+ Details: [specification](https://saasontools.github.io/strauss-agent-tools/specification).
295
+
247
296
  ## CLI
248
297
 
249
298
  ```
@@ -256,19 +305,21 @@ strauss-kb [--bundle PATH] <command> [args]
256
305
  supersede <concept-id> <replacement-id> Mark a record superseded, linking both directions.
257
306
  answer <concept-id> <answer...> Resolve an open question and append the answer.
258
307
  verify <concept-id> --note <text> Append a verified[] event — who checked, when, and what the check found.
259
- load [type] [--budget N] [--all]
308
+ anchor-resolve <concept-id> [--repo-root <path>] [--rebaseline] [--restamp]
309
+ Resolve anchors against the working tree: stamp, or report drift.
310
+ load [type] [--budget N | --all] [--repo-root PATH]
260
311
  Hand over the whole base, each record with its standing.
261
312
  catalog [type] Every record in one line — id, type, title, standing, stale flag.
262
313
  pack <conceptId> [--hops N] [--max-nodes N] [--budget N]
263
314
  The bounded neighbourhood around one record, every cut named.
264
- query <text...> Search; every match arrives flagged with its standing.
315
+ query <text...> [--repo-root PATH] Search; every match arrives flagged with its standing.
265
316
  trace <concept-id> [edges...] How a position was arrived at, as a timeline.
266
317
  list [type] Every record, optionally narrowed to one type.
267
318
  index The index, rebuilt if it disagrees with the records.
268
319
  log What touched what, and when.
269
320
  validate Cross-record checks. Exits 1 when it reports a problem.
270
- doctor [--expiring-days N] [--unverified-days N] [--aging-days N] [--strict]
271
- Health sweep: what expired, went unconfirmed, aged, or was orphaned.
321
+ doctor [--expiring-days N] [--unverified-days N] [--aging-days N] [--repo-root PATH] [--strict]
322
+ Health sweep: what expired, went unconfirmed, aged, was orphaned, or drifted.
272
323
  schema JSON Schema for the format.
273
324
  types The twelve types, their sections and initial status.
274
325
  pin [bundle-path] [flags] Pin a base. --mode, --profiles, --frozen; --local/--user pick the layer.
@@ -288,10 +339,10 @@ they are, and `doctor` prints a table unless `--json` asks for the object
288
339
  behind it. `--json` is refused rather than ignored on the commands that have
289
340
  only one form, since a flag that quietly does nothing reads as one that
290
341
  worked; `--` ends flag parsing, for the verbs that end in free prose. Errors
291
- go to stderr and exit 1. `validate` and `doctor --strict` are the commands
292
- whose exit code is not just "did it run": a check that reports a problem
293
- succeeded as a command and failed as a check, so it exits 1 with its findings
294
- on stdout.
342
+ go to stderr and exit 1. `validate`, `anchor-resolve`, and `doctor --strict`
343
+ are the commands whose exit code is not just "did it run": a check that
344
+ reports a problem succeeded as a command and failed as a check, so each exits
345
+ 1 with its findings on stdout.
295
346
 
296
347
  A flag accepts either spelling — `--budget 4000` or `--budget=4000` — and a
297
348
  flag given no value is an error rather than a silent fallback to the default,
@@ -316,9 +367,9 @@ strauss-kb validate || echo "problems above"
316
367
 
317
368
  `strauss-kb-mcp` speaks stdio and takes no API key and no required environment.
318
369
  Every CLI verb is a tool: `kb_write`, `kb_write_decision`, `kb_no_decision`,
319
- `kb_status`, `kb_supersede`, `kb_answer`, `kb_verify`, `kb_load`, `kb_catalog`,
320
- `kb_pack`, `kb_query`, `kb_trace`, `kb_list`, `kb_index`, `kb_log`, `kb_validate`,
321
- `kb_doctor`, `kb_schema`, `kb_types`,
370
+ `kb_status`, `kb_supersede`, `kb_answer`, `kb_verify`, `kb_anchor_resolve`,
371
+ `kb_load`, `kb_catalog`, `kb_pack`, `kb_query`, `kb_trace`, `kb_list`,
372
+ `kb_index`, `kb_log`, `kb_validate`, `kb_doctor`, `kb_schema`, `kb_types`,
322
373
  `kb_pin`, `kb_unpin`, `kb_pins`, `kb_context`. Most tools take a `bundlePath`;
323
374
  `kb_schema` and `kb_types` describe the format rather than any one base, and
324
375
  `kb_pins` and `kb_context` read the workspace pin manifests instead. The one
@@ -356,7 +407,7 @@ const hits = await store.query(".strauss/kb", "cache key");
356
407
  for (const hit of hits) {
357
408
  hit.standing; // current | superseded | rejected | unsettled | open
358
409
  hit.heads; // where the supersession chain ends
359
- hit.warnings; // rejected, broken-chain, forked-chain, stale, unverified…
410
+ hit.warnings; // rejected, broken-chain, forked-chain, stale, unverified, drifted
360
411
  }
361
412
  ```
362
413
 
@@ -505,20 +556,25 @@ it asks only whether pointers between records agree.
505
556
  | `orphaned` | No other record links to it, by body link or supersession. |
506
557
  | `broken-supersession` | A chain that does not resolve: no replacement, a missing one, a cycle, a fork. |
507
558
  | `superseded-but-cited` | A record that still holds, whose body links to one that does not. |
559
+ | `drifted` | A hash-carrying anchor whose code moved, or whose file or symbol is gone. |
560
+
561
+ Pass `doctor --repo-root PATH` when the base does not sit inside the tree it
562
+ describes; without it the sweep looks under the working directory.
508
563
 
509
- The last check's name is for its common case: a rejected target counts too, and
510
- is the worse half — a superseded record at least names its replacement, while a
511
- rejected one is a well-formed assertion of what someone decided _not_ to do,
512
- cited by a record the reader trusts.
564
+ `superseded-but-cited`'s name is for its common case: a rejected target counts
565
+ too, and is the worse half — a superseded record at least names its
566
+ replacement, while a rejected one is a well-formed assertion of what someone
567
+ decided _not_ to do, cited by a record the reader trusts.
513
568
 
514
569
  ```bash
515
570
  strauss-kb doctor # the table
516
571
  strauss-kb doctor --json # the object behind it
517
572
  strauss-kb doctor --strict # exit 1 if anything has expired
518
573
  strauss-kb doctor --unverified-days 30 # a stricter confirmation window
574
+ strauss-kb doctor --repo-root ../app # where the anchored source lives
519
575
  ```
520
576
 
521
- All seven groups are reported even when empty. A check that found nothing and
577
+ All eight groups are reported even when empty. A check that found nothing and
522
578
  a check that never ran look identical in a report that only lists findings,
523
579
  which is the whole value of a sweep.
524
580
 
@@ -554,8 +610,13 @@ Judgments the checks make, worth knowing before reading a report:
554
610
  record left `accepted` while naming a replacement was hand-edited — and
555
611
  adjudication reads it as current no matter what the pointer says, which is
556
612
  what makes it worth naming.
613
+ - **`drifted` reports and never repairs.** It reads the same comparison
614
+ `kb_load` and `kb_query` do, so the sweep and the read paths cannot disagree
615
+ about what drift is. Unresolvable anchors ride in this group rather than a
616
+ ninth check, anchors carrying no hash are never read, and `--strict` does not
617
+ gate on drift.
557
618
 
558
- `--strict` gates on expiry alone. The other six report debt a reader decides
619
+ `--strict` gates on expiry alone. The other seven report debt a reader decides
559
620
  about; an expired record is the base itself saying it would stop standing
560
621
  behind something, which is the one finding a pipeline can act on without a
561
622
  judgment call.
@@ -2,7 +2,7 @@ import {
2
2
  KB_COMMANDS,
3
3
  KbStore,
4
4
  VERSION
5
- } from "./chunk-EJQPZWN5.js";
5
+ } from "./chunk-OVRQCQ6P.js";
6
6
 
7
7
  // src/mcp.ts
8
8
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -46,4 +46,4 @@ export {
46
46
  createKbMcpServer,
47
47
  runKbMcpServer
48
48
  };
49
- //# sourceMappingURL=chunk-NMTP7V7E.js.map
49
+ //# sourceMappingURL=chunk-CWWXMD35.js.map
@@ -4,7 +4,7 @@ import {
4
4
  KB_DIR,
5
5
  KbStore,
6
6
  VERSION
7
- } from "./chunk-EJQPZWN5.js";
7
+ } from "./chunk-OVRQCQ6P.js";
8
8
 
9
9
  // src/cli.ts
10
10
  import { join } from "path";
@@ -112,4 +112,4 @@ function usage() {
112
112
  export {
113
113
  runKbCli
114
114
  };
115
- //# sourceMappingURL=chunk-RGK3K6LN.js.map
115
+ //# sourceMappingURL=chunk-I3WW4F6X.js.map