@saasontools/strauss-kb 0.1.20 → 0.1.21

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
@@ -222,11 +222,12 @@ end of it. Six optional fields extend it:
222
222
  them; `repo` and `ref` are **author-owned identity** and a resolution pass
223
223
  never writes them.
224
224
 
225
- `anchor-resolve <concept-id>` (`kb_anchor_resolve`) is `verify`'s mechanical
226
- counterpart: it checks the anchored code against the working tree
227
- (`--repo-root` when the base is not inside it). Per anchor:
225
+ `anchor-resolve <concept-id>` (`kb_anchor_resolve`) checks the anchored code
226
+ against the working tree (`--repo-root` when the base is not inside it) and
227
+ never writes `verified[]`; `--check` writes nothing at all. Per anchor:
228
228
 
229
- - **stamped** — no hash yet; hash, line count, and timestamp are written.
229
+ - **stamped** — no hash yet; hash, line count, and timestamp are written
230
+ (`unstamped` under `--check`, which writes none).
230
231
  - **match** — unchanged; nothing written. `--restamp` re-dates on purpose.
231
232
  - **drifted** — hash changed. Baseline kept unless `--rebaseline`.
232
233
  - **unresolved** — not comparable, with a reason. A finding, not an error.
@@ -245,9 +246,6 @@ and again on the real path after symlinks.
245
246
  Exit code is non-zero on **drifted**, or on **unresolved** for an anchor that
246
247
  carries a hash; unstamped anchors and unreachable remotes never fail.
247
248
 
248
- A fully clean run — every anchor checked and `match`, none stamped this run —
249
- appends a `verified[]` event.
250
-
251
249
  Symbols resolve tree-sitter first — the 20 language packs that have both a
252
250
  grammar and a definitions query, pinned together by `pnpm grammars pin` from
253
251
  `grammars/packs.json` and proved at pin time — then the regex heuristic for
@@ -4,7 +4,7 @@ import {
4
4
  KB_DIR,
5
5
  KbStore,
6
6
  VERSION
7
- } from "./chunk-QQLPJO4R.js";
7
+ } from "./chunk-XBZ7G5R5.js";
8
8
 
9
9
  // src/cli.ts
10
10
  import { join } from "path";
@@ -121,4 +121,4 @@ function usage() {
121
121
  export {
122
122
  runKbCli
123
123
  };
124
- //# sourceMappingURL=chunk-XDH6J6CH.js.map
124
+ //# sourceMappingURL=chunk-VXWN62HT.js.map
@@ -2,7 +2,7 @@ import {
2
2
  KB_COMMANDS,
3
3
  KbStore,
4
4
  VERSION
5
- } from "./chunk-QQLPJO4R.js";
5
+ } from "./chunk-XBZ7G5R5.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-EXZQZJU2.js.map
49
+ //# sourceMappingURL=chunk-WVJULVGY.js.map
@@ -2235,6 +2235,8 @@ var Fault = /* @__PURE__ */ ((Fault2) => {
2235
2235
  var ErrorTypes = /* @__PURE__ */ ((ErrorTypes2) => {
2236
2236
  ErrorTypes2["KbRecordAlreadyExists"] = "KbRecordAlreadyExists";
2237
2237
  ErrorTypes2["KbClassifyInput"] = "KbClassifyInput";
2238
+ ErrorTypes2["KbFlagConflict"] = "KbFlagConflict";
2239
+ ErrorTypes2["KbInvalidActor"] = "KbInvalidActor";
2238
2240
  ErrorTypes2["KbInvalidConceptId"] = "KbInvalidConceptId";
2239
2241
  ErrorTypes2["KbMatchInput"] = "KbMatchInput";
2240
2242
  ErrorTypes2["KbMissingFlagValue"] = "KbMissingFlagValue";
@@ -2335,6 +2337,38 @@ var KbSelfVerificationError = class extends BaseError {
2335
2337
  actor;
2336
2338
  generatedBy;
2337
2339
  };
2340
+ var KbInvalidActorError = class extends BaseError {
2341
+ constructor(actor, reason) {
2342
+ super({
2343
+ message: `kb: actor ${JSON.stringify(actor)} ${reason} \u2014 set STRAUSS_KB_ACTOR, e.g. human:alice or agent:reviewer`,
2344
+ errorType: "KbInvalidActor" /* KbInvalidActor */,
2345
+ code: 400,
2346
+ fault: "User" /* User */,
2347
+ retriable: false,
2348
+ reportToUser: true,
2349
+ details: { actor, reason, action: "refused" }
2350
+ });
2351
+ this.actor = actor;
2352
+ this.reason = reason;
2353
+ }
2354
+ actor;
2355
+ reason;
2356
+ };
2357
+ var KbFlagConflictError = class extends BaseError {
2358
+ constructor(flags) {
2359
+ super({
2360
+ message: `kb: ${flags.join(" and ")} cannot be combined`,
2361
+ errorType: "KbFlagConflict" /* KbFlagConflict */,
2362
+ code: 400,
2363
+ fault: "User" /* User */,
2364
+ retriable: false,
2365
+ reportToUser: true,
2366
+ details: { flags }
2367
+ });
2368
+ this.flags = flags;
2369
+ }
2370
+ flags;
2371
+ };
2338
2372
  var KbPackBudgetExceededError = class extends BaseError {
2339
2373
  constructor(recordCount, approxTokens2, budgetTokens, excluded) {
2340
2374
  super({
@@ -4978,19 +5012,11 @@ function byGeneratedAt(left, right) {
4978
5012
 
4979
5013
  // src/commands/anchor-resolve.ts
4980
5014
  import { z as z10 } from "zod";
4981
- function resolverSummary(results) {
4982
- const names = [
4983
- ...new Set(
4984
- results.flatMap((entry) => entry.resolver ? [entry.resolver] : [])
4985
- )
4986
- ].sort();
4987
- return names.length ? `${names.join(" + ")} resolver` : "whole-file";
4988
- }
4989
5015
  var anchorResolveCommand = define({
4990
5016
  name: "anchor-resolve",
4991
5017
  tool: "kb_anchor_resolve",
4992
- usage: "anchor-resolve <concept-id> [--repo-root <path>] [--offline] [--rebaseline] [--restamp]",
4993
- description: "Resolve a record's anchors: stamp a hash onto anchors that lack one, report drift where the code moved. An anchor naming another repository is read from that remote through a bare cache; --offline uses the cache only. kb_verify's mechanical counterpart \u2014 reach for it when the question is whether the code still is what it was. Exits non-zero on drift.",
5018
+ usage: "anchor-resolve <concept-id> [--repo-root <path>] [--offline] [--rebaseline] [--restamp] [--check]",
5019
+ description: "Resolve a record's anchors: stamp a hash onto anchors that lack one, report drift where the code moved. An anchor naming another repository is read from that remote through a bare cache; --offline uses the cache only. Never writes verified[]; a judgment is kb_verify. Exits non-zero on drift.",
4994
5020
  input: z10.object({
4995
5021
  bundlePath,
4996
5022
  conceptId,
@@ -5003,6 +5029,9 @@ var anchorResolveCommand = define({
5003
5029
  ),
5004
5030
  restamp: z10.boolean().optional().describe(
5005
5031
  "Refresh `resolved_at` on anchors that already match. Off by default, so a green run writes nothing."
5032
+ ),
5033
+ check: z10.boolean().optional().describe(
5034
+ "Resolve and report only: no hash, no `resolved_at`, no log entry."
5006
5035
  )
5007
5036
  }),
5008
5037
  fromArgv: (argv, path) => ({
@@ -5011,9 +5040,24 @@ var anchorResolveCommand = define({
5011
5040
  repoRoot: argvFlag(argv, "--repo-root"),
5012
5041
  offline: argv.includes("--offline"),
5013
5042
  rebaseline: argv.includes("--rebaseline"),
5014
- restamp: argv.includes("--restamp")
5043
+ restamp: argv.includes("--restamp"),
5044
+ check: argv.includes("--check")
5015
5045
  }),
5016
- run: async ({ store, actor, now }, { bundlePath: path, conceptId: id, repoRoot, offline, rebaseline, restamp }) => {
5046
+ run: async ({ store, actor, now }, {
5047
+ bundlePath: path,
5048
+ conceptId: id,
5049
+ repoRoot,
5050
+ offline,
5051
+ rebaseline,
5052
+ restamp,
5053
+ check
5054
+ }) => {
5055
+ if (check && (rebaseline || restamp)) {
5056
+ throw new KbFlagConflictError([
5057
+ "check",
5058
+ rebaseline ? "rebaseline" : "restamp"
5059
+ ]);
5060
+ }
5017
5061
  const root = repoRoot ?? process.cwd();
5018
5062
  const record = await store.read(path, id);
5019
5063
  if (!record) throw new KbRecordNotFoundError(id);
@@ -5022,7 +5066,6 @@ var anchorResolveCommand = define({
5022
5066
  return {
5023
5067
  conceptId: id,
5024
5068
  results: [],
5025
- verified: false,
5026
5069
  note: "record has no anchors"
5027
5070
  };
5028
5071
  }
@@ -5080,7 +5123,7 @@ var anchorResolveCommand = define({
5080
5123
  if (!anchor.hash) {
5081
5124
  results.push({
5082
5125
  ...base2,
5083
- state: "stamped",
5126
+ state: check ? "unstamped" : "stamped",
5084
5127
  currentHash: stampedHash,
5085
5128
  hashKind: stampedKind,
5086
5129
  ...producedBy ? { resolver: producedBy } : {}
@@ -5132,7 +5175,7 @@ var anchorResolveCommand = define({
5132
5175
  if (refresh) dirty = true;
5133
5176
  }
5134
5177
  let frozen = false;
5135
- if (dirty) {
5178
+ if (dirty && !check) {
5136
5179
  try {
5137
5180
  await assertBaseNotFrozen(process.cwd(), path);
5138
5181
  } catch (error) {
@@ -5147,42 +5190,11 @@ var anchorResolveCommand = define({
5147
5190
  const unreachable = results.filter(
5148
5191
  (entry) => isUncheckedReason(entry.reason)
5149
5192
  ).length;
5150
- const checked = results.length - unreachable;
5151
5193
  const matches3 = results.filter((entry) => entry.state === "match").length;
5152
- const note = `${matches3}/${checked} anchors match${unreachable ? `, ${unreachable} unreachable` : ""}`;
5153
- const clean = checked > 0 && matches3 === checked && unreachable === 0;
5154
- if (clean) {
5155
- try {
5156
- await store.verify(
5157
- path,
5158
- id,
5159
- `anchor-resolve: ${note} (${resolverSummary(results)})`,
5160
- actor,
5161
- now()
5162
- );
5163
- } catch (error) {
5164
- if (!(error instanceof KbSelfVerificationError)) throw error;
5165
- return {
5166
- conceptId: id,
5167
- results,
5168
- verified: false,
5169
- verifyRefused: "self-verification",
5170
- ...frozenNote,
5171
- ...hintNote
5172
- };
5173
- }
5174
- return {
5175
- conceptId: id,
5176
- results,
5177
- verified: true,
5178
- ...frozenNote,
5179
- ...hintNote
5180
- };
5181
- }
5194
+ const note = `${matches3}/${results.length - unreachable} anchors match, ${unreachable} unreachable`;
5182
5195
  return {
5183
5196
  conceptId: id,
5184
5197
  results,
5185
- verified: false,
5186
5198
  ...unreachable ? { note } : {},
5187
5199
  ...frozenNote,
5188
5200
  ...hintNote
@@ -7581,6 +7593,7 @@ var KbStore = class {
7581
7593
  * its contents.
7582
7594
  */
7583
7595
  async write(bundlePath2, input, actor = "unknown") {
7596
+ assertActor(actor);
7584
7597
  if (!KB_SLUG_PATTERN.test(input.slug)) {
7585
7598
  throw new KbInvalidConceptIdError("slug must be kebab-case", {
7586
7599
  slug: input.slug
@@ -7682,6 +7695,7 @@ var KbStore = class {
7682
7695
  * timeouts.
7683
7696
  */
7684
7697
  async setStatus(bundlePath2, conceptId2, status, actor = "unknown") {
7698
+ assertActor(actor);
7685
7699
  return this.mutate(
7686
7700
  bundlePath2,
7687
7701
  conceptId2,
@@ -7700,6 +7714,7 @@ var KbStore = class {
7700
7714
  * the frontmatter must not be published back out under an actor stamp.
7701
7715
  */
7702
7716
  async updateAnchors(bundlePath2, conceptId2, anchors, actor = "unknown") {
7717
+ assertActor(actor);
7703
7718
  const checked = anchors.map((anchor) => kbAnchorWriteSchema.parse(anchor));
7704
7719
  return this.mutate(
7705
7720
  bundlePath2,
@@ -7720,6 +7735,7 @@ var KbStore = class {
7720
7735
  * logs what it publishes.
7721
7736
  */
7722
7737
  async verify(bundlePath2, conceptId2, note, actor = "unknown", at2 = (/* @__PURE__ */ new Date()).toISOString()) {
7738
+ assertActor(actor, { named: true });
7723
7739
  const event = kbVerifiedEventSchema.parse({ by: actor, at: at2, note });
7724
7740
  const existing = await this.read(bundlePath2, conceptId2);
7725
7741
  if (!existing) throw new KbRecordNotFoundError(conceptId2);
@@ -7750,6 +7766,7 @@ var KbStore = class {
7750
7766
  * in normal use, and validation drops to catching hand-edits.
7751
7767
  */
7752
7768
  async supersede(bundlePath2, conceptId2, replacementId, actor = "unknown") {
7769
+ assertActor(actor);
7753
7770
  const replacement = await this.read(bundlePath2, replacementId);
7754
7771
  if (!replacement) throw new KbRecordNotFoundError(replacementId);
7755
7772
  const superseded = await this.markSuperseded(
@@ -7773,6 +7790,7 @@ var KbStore = class {
7773
7790
  }
7774
7791
  /** Resolves an open question, stamping who answered and when. */
7775
7792
  async answer(bundlePath2, conceptId2, answer, actor = "unknown", at2 = (/* @__PURE__ */ new Date()).toISOString()) {
7793
+ assertActor(actor);
7776
7794
  return this.mutate(
7777
7795
  bundlePath2,
7778
7796
  conceptId2,
@@ -7799,6 +7817,7 @@ ${answer}
7799
7817
  * terminal status since the caller listed it is reported, not removed.
7800
7818
  */
7801
7819
  async deleteRecord(bundlePath2, conceptId2, expected, actor = "unknown") {
7820
+ assertActor(actor);
7802
7821
  const target = this.recordPath(bundlePath2, conceptId2);
7803
7822
  const witness = await this.read(bundlePath2, conceptId2);
7804
7823
  if (!witness) throw new KbRecordNotFoundError(conceptId2);
@@ -8102,6 +8121,7 @@ ${answer}
8102
8121
  * base too, where nothing was written.
8103
8122
  */
8104
8123
  async note(bundlePath2, entry) {
8124
+ assertActor(entry.by);
8105
8125
  await this.record(this.root(bundlePath2), entry);
8106
8126
  }
8107
8127
  /**
@@ -8359,6 +8379,15 @@ function normalizeActor(id) {
8359
8379
  if (colon === -1) return id.toLowerCase();
8360
8380
  return id.slice(0, colon + 1).toLowerCase() + id.slice(colon + 1);
8361
8381
  }
8382
+ var KB_ACTOR_PATTERN = /^[A-Za-z][\w-]*(?::[\p{L}\p{M}\p{N}_.@+/-]+)?$/u;
8383
+ function assertActor(actor, { named = false } = {}) {
8384
+ if (!KB_ACTOR_PATTERN.test(actor)) {
8385
+ throw new KbInvalidActorError(actor, "is not kind or kind:name");
8386
+ }
8387
+ if (named && normalizeActor(actor) === "unknown") {
8388
+ throw new KbInvalidActorError(actor, "cannot verify: name who checked");
8389
+ }
8390
+ }
8362
8391
 
8363
8392
  // src/pack.ts
8364
8393
  var DEFAULT_PACK_HOPS = 2;
@@ -8445,7 +8474,7 @@ function typeRank(record) {
8445
8474
  }
8446
8475
 
8447
8476
  // src/version.ts
8448
- var VERSION = true ? "0.1.20" : "0.0.0-dev";
8477
+ var VERSION = true ? "0.1.21" : "0.0.0-dev";
8449
8478
 
8450
8479
  export {
8451
8480
  kbSourceSchema,
@@ -8593,4 +8622,4 @@ export {
8593
8622
  KbStore,
8594
8623
  VERSION
8595
8624
  };
8596
- //# sourceMappingURL=chunk-QQLPJO4R.js.map
8625
+ //# sourceMappingURL=chunk-XBZ7G5R5.js.map