@saasontools/strauss-kb 0.1.20 → 0.1.22

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/dist/index.cjs CHANGED
@@ -60,6 +60,7 @@ __export(index_exports, {
60
60
  KB_RECORD_STATUSES: () => KB_RECORD_STATUSES,
61
61
  KB_RECORD_TYPES: () => KB_RECORD_TYPES,
62
62
  KB_SLUG_PATTERN: () => KB_SLUG_PATTERN,
63
+ KbAnchorSetDuplicateError: () => KbAnchorSetDuplicateError,
63
64
  KbBaseFrozenError: () => KbBaseFrozenError,
64
65
  KbClassifyInputError: () => KbClassifyInputError,
65
66
  KbInvalidConceptIdError: () => KbInvalidConceptIdError,
@@ -90,6 +91,8 @@ __export(index_exports, {
90
91
  adjudicate: () => adjudicate,
91
92
  anchorFilePath: () => anchorFilePath,
92
93
  anchorOnHunk: () => anchorOnHunk,
94
+ anchorSetInputSchema: () => anchorSetInputSchema,
95
+ applyAnchorSet: () => applyAnchorSet,
93
96
  assertBaseNotFrozen: () => assertBaseNotFrozen,
94
97
  backlinks: () => backlinks,
95
98
  buildContext: () => buildContext,
@@ -123,19 +126,23 @@ __export(index_exports, {
123
126
  isNoDecisionRecord: () => isNoDecisionRecord,
124
127
  isReviewTag: () => isReviewTag,
125
128
  kbActorStampSchema: () => kbActorStampSchema,
129
+ kbAnchorLocatorSchema: () => kbAnchorLocatorSchema,
126
130
  kbAnchorSchema: () => kbAnchorSchema,
127
131
  kbAnchorSpanSchema: () => kbAnchorSpanSchema,
128
132
  kbAnchorWriteSchema: () => kbAnchorWriteSchema,
129
133
  kbConceptIdSchema: () => kbConceptIdSchema,
130
134
  kbJsonSchemas: () => kbJsonSchemas,
131
135
  kbLinkSchema: () => kbLinkSchema,
136
+ kbLogAnchorChangeSchema: () => kbLogAnchorChangeSchema,
132
137
  kbLogEntrySchema: () => kbLogEntrySchema,
138
+ kbLogEntryWriteSchema: () => kbLogEntryWriteSchema,
133
139
  kbRecordFrontmatterSchema: () => kbRecordFrontmatterSchema,
134
140
  kbSourceSchema: () => kbSourceSchema,
135
141
  kbVerifiedEventSchema: () => kbVerifiedEventSchema,
136
142
  languageForFile: () => languageForFile,
137
143
  listPins: () => listPins,
138
144
  loadQmd: () => loadQmd,
145
+ locatorOf: () => locatorOf,
139
146
  matchToDiff: () => matchToDiff,
140
147
  matchesTags: () => matchesTags,
141
148
  mergedContextBudgets: () => mergedContextBudgets,
@@ -347,6 +354,14 @@ var kbAnchorWriteSchema = kbAnchorSchema.superRefine((anchor, ctx) => {
347
354
  });
348
355
  }
349
356
  });
357
+ var kbAnchorLocatorSchema = kbAnchorSchema.pick({
358
+ file: true,
359
+ symbol: true,
360
+ span: true,
361
+ side: true,
362
+ repo: true,
363
+ ref: true
364
+ });
350
365
  var kbLinkSchema = import_zod.z.object({
351
366
  target: import_zod.z.string().min(1),
352
367
  rel: import_zod.z.string().min(1)
@@ -433,7 +448,10 @@ var Fault = /* @__PURE__ */ ((Fault2) => {
433
448
  })(Fault || {});
434
449
  var ErrorTypes = /* @__PURE__ */ ((ErrorTypes2) => {
435
450
  ErrorTypes2["KbRecordAlreadyExists"] = "KbRecordAlreadyExists";
451
+ ErrorTypes2["KbAnchorSetDuplicate"] = "KbAnchorSetDuplicate";
436
452
  ErrorTypes2["KbClassifyInput"] = "KbClassifyInput";
453
+ ErrorTypes2["KbFlagConflict"] = "KbFlagConflict";
454
+ ErrorTypes2["KbInvalidActor"] = "KbInvalidActor";
437
455
  ErrorTypes2["KbInvalidConceptId"] = "KbInvalidConceptId";
438
456
  ErrorTypes2["KbMatchInput"] = "KbMatchInput";
439
457
  ErrorTypes2["KbMissingFlagValue"] = "KbMissingFlagValue";
@@ -534,6 +552,38 @@ var KbSelfVerificationError = class extends BaseError {
534
552
  actor;
535
553
  generatedBy;
536
554
  };
555
+ var KbInvalidActorError = class extends BaseError {
556
+ constructor(actor, reason) {
557
+ super({
558
+ message: `kb: actor ${JSON.stringify(actor)} ${reason} \u2014 set STRAUSS_KB_ACTOR, e.g. human:alice or agent:reviewer`,
559
+ errorType: "KbInvalidActor" /* KbInvalidActor */,
560
+ code: 400,
561
+ fault: "User" /* User */,
562
+ retriable: false,
563
+ reportToUser: true,
564
+ details: { actor, reason, action: "refused" }
565
+ });
566
+ this.actor = actor;
567
+ this.reason = reason;
568
+ }
569
+ actor;
570
+ reason;
571
+ };
572
+ var KbFlagConflictError = class extends BaseError {
573
+ constructor(flags) {
574
+ super({
575
+ message: `kb: ${flags.join(" and ")} cannot be combined`,
576
+ errorType: "KbFlagConflict" /* KbFlagConflict */,
577
+ code: 400,
578
+ fault: "User" /* User */,
579
+ retriable: false,
580
+ reportToUser: true,
581
+ details: { flags }
582
+ });
583
+ this.flags = flags;
584
+ }
585
+ flags;
586
+ };
537
587
  var KbPackBudgetExceededError = class extends BaseError {
538
588
  constructor(recordCount, approxTokens2, budgetTokens, excluded) {
539
589
  super({
@@ -2619,7 +2669,12 @@ var import_node_path6 = require("path");
2619
2669
  // src/kb-log.ts
2620
2670
  var import_zod3 = require("zod");
2621
2671
  var LOG_FILE = "log.jsonl";
2622
- var kbLogEntrySchema = import_zod3.z.object({
2672
+ var kbLogAnchorChangeSchema = import_zod3.z.object({
2673
+ op: import_zod3.z.enum(["move", "add", "drop"]),
2674
+ from: kbAnchorLocatorSchema.optional(),
2675
+ to: kbAnchorLocatorSchema.optional()
2676
+ }).strict();
2677
+ var kbLogEntryFields = import_zod3.z.object({
2623
2678
  // Validated, not just `min(1)`: `at` is a sort key (see `parseLog`
2624
2679
  // below), and a value that isn't actually chronological — a Unix
2625
2680
  // timestamp, a human-typed date, garbage — would sort wrong without
@@ -2636,10 +2691,20 @@ var kbLogEntrySchema = import_zod3.z.object({
2636
2691
  * The operation's other end, where it has one: a second concept id for
2637
2692
  * supersession, the other base's path for promotion.
2638
2693
  */
2639
- target: import_zod3.z.string().min(1).optional()
2640
- }).strict();
2694
+ target: import_zod3.z.string().min(1).optional(),
2695
+ /**
2696
+ * Why the operation was performed, where the operation demands one.
2697
+ * `anchor-set` does: a pointer moved by a reader is only auditable if
2698
+ * the reading is recorded beside it.
2699
+ */
2700
+ reason: import_zod3.z.string().min(1).optional(),
2701
+ /** What `anchor-set` changed, derived from the record before and after. */
2702
+ anchors: import_zod3.z.array(kbLogAnchorChangeSchema).optional()
2703
+ });
2704
+ var kbLogEntrySchema = kbLogEntryFields.passthrough();
2705
+ var kbLogEntryWriteSchema = kbLogEntryFields.strict();
2641
2706
  function renderLogEntry(entry) {
2642
- return `${JSON.stringify(kbLogEntrySchema.parse(entry))}
2707
+ return `${JSON.stringify(kbLogEntryWriteSchema.parse(entry))}
2643
2708
  `;
2644
2709
  }
2645
2710
  var CONFLICT_MARKER = /^(<{7}|\|{7}|={7}|>{7})/;
@@ -3361,6 +3426,7 @@ var KbStore = class {
3361
3426
  * its contents.
3362
3427
  */
3363
3428
  async write(bundlePath2, input, actor = "unknown") {
3429
+ assertActor(actor);
3364
3430
  if (!KB_SLUG_PATTERN.test(input.slug)) {
3365
3431
  throw new KbInvalidConceptIdError("slug must be kebab-case", {
3366
3432
  slug: input.slug
@@ -3462,6 +3528,7 @@ var KbStore = class {
3462
3528
  * timeouts.
3463
3529
  */
3464
3530
  async setStatus(bundlePath2, conceptId2, status, actor = "unknown") {
3531
+ assertActor(actor);
3465
3532
  return this.mutate(
3466
3533
  bundlePath2,
3467
3534
  conceptId2,
@@ -3470,22 +3537,31 @@ var KbStore = class {
3470
3537
  );
3471
3538
  }
3472
3539
  /**
3473
- * Replaces a record's anchors wholesale, preserving everything else.
3474
- *
3475
- * Wholesale rather than merged: the caller just resolved the anchors it is
3476
- * writing, so it holds the complete current set, and a merge would keep
3477
- * stale entries the resolution pass deliberately dropped.
3478
- *
3479
- * Through the write schema: this is a write, and a defect a hand-edit put in
3480
- * the frontmatter must not be published back out under an actor stamp.
3540
+ * Replaces a record's anchors, preserving everything else. An array is the
3541
+ * whole set; a function is a patch and runs inside the mutation, against
3542
+ * the anchors the record holds then see
3543
+ * `decision.anchor-update-patch-inside-mutation`.
3481
3544
  */
3482
3545
  async updateAnchors(bundlePath2, conceptId2, anchors, actor = "unknown") {
3483
- const checked = anchors.map((anchor) => kbAnchorWriteSchema.parse(anchor));
3546
+ assertActor(actor);
3547
+ let entry = {
3548
+ operation: "anchor-resolve",
3549
+ by: actor
3550
+ };
3484
3551
  return this.mutate(
3485
3552
  bundlePath2,
3486
3553
  conceptId2,
3487
- (frontmatter) => ({ ...frontmatter, strauss_anchors: checked }),
3488
- { operation: "anchor-resolve", by: actor }
3554
+ (frontmatter) => {
3555
+ const write = typeof anchors === "function" ? anchors(frontmatter.strauss_anchors ?? []) : { anchors };
3556
+ if (write.log) entry = { ...write.log, by: actor };
3557
+ return {
3558
+ ...frontmatter,
3559
+ strauss_anchors: write.anchors.map(
3560
+ (anchor) => kbAnchorWriteSchema.parse(anchor)
3561
+ )
3562
+ };
3563
+ },
3564
+ () => entry
3489
3565
  );
3490
3566
  }
3491
3567
  /**
@@ -3500,6 +3576,7 @@ var KbStore = class {
3500
3576
  * logs what it publishes.
3501
3577
  */
3502
3578
  async verify(bundlePath2, conceptId2, note, actor = "unknown", at2 = (/* @__PURE__ */ new Date()).toISOString()) {
3579
+ assertActor(actor, { named: true });
3503
3580
  const event = kbVerifiedEventSchema.parse({ by: actor, at: at2, note });
3504
3581
  const existing = await this.read(bundlePath2, conceptId2);
3505
3582
  if (!existing) throw new KbRecordNotFoundError(conceptId2);
@@ -3530,6 +3607,7 @@ var KbStore = class {
3530
3607
  * in normal use, and validation drops to catching hand-edits.
3531
3608
  */
3532
3609
  async supersede(bundlePath2, conceptId2, replacementId, actor = "unknown") {
3610
+ assertActor(actor);
3533
3611
  const replacement = await this.read(bundlePath2, replacementId);
3534
3612
  if (!replacement) throw new KbRecordNotFoundError(replacementId);
3535
3613
  const superseded = await this.markSuperseded(
@@ -3553,6 +3631,7 @@ var KbStore = class {
3553
3631
  }
3554
3632
  /** Resolves an open question, stamping who answered and when. */
3555
3633
  async answer(bundlePath2, conceptId2, answer, actor = "unknown", at2 = (/* @__PURE__ */ new Date()).toISOString()) {
3634
+ assertActor(actor);
3556
3635
  return this.mutate(
3557
3636
  bundlePath2,
3558
3637
  conceptId2,
@@ -3579,6 +3658,7 @@ ${answer}
3579
3658
  * terminal status since the caller listed it is reported, not removed.
3580
3659
  */
3581
3660
  async deleteRecord(bundlePath2, conceptId2, expected, actor = "unknown") {
3661
+ assertActor(actor);
3582
3662
  const target = this.recordPath(bundlePath2, conceptId2);
3583
3663
  const witness = await this.read(bundlePath2, conceptId2);
3584
3664
  if (!witness) throw new KbRecordNotFoundError(conceptId2);
@@ -3882,6 +3962,7 @@ ${answer}
3882
3962
  * base too, where nothing was written.
3883
3963
  */
3884
3964
  async note(bundlePath2, entry) {
3965
+ assertActor(entry.by);
3885
3966
  await this.record(this.root(bundlePath2), entry);
3886
3967
  }
3887
3968
  /**
@@ -3934,7 +4015,10 @@ ${answer}
3934
4015
  throw new KbWriteConflictError(conceptId2);
3935
4016
  }
3936
4017
  await this.publish(target, contents, true, conceptId2);
3937
- await this.record(this.root(bundlePath2), { ...entry, conceptId: conceptId2 });
4018
+ await this.record(this.root(bundlePath2), {
4019
+ ...typeof entry === "function" ? entry() : entry,
4020
+ conceptId: conceptId2
4021
+ });
3938
4022
  return { conceptId: conceptId2, frontmatter, body };
3939
4023
  }
3940
4024
  /**
@@ -4139,9 +4223,119 @@ function normalizeActor(id) {
4139
4223
  if (colon === -1) return id.toLowerCase();
4140
4224
  return id.slice(0, colon + 1).toLowerCase() + id.slice(colon + 1);
4141
4225
  }
4226
+ var KB_ACTOR_PATTERN = /^[A-Za-z][\w-]*(?::[\p{L}\p{M}\p{N}_.@+/-]+)?$/u;
4227
+ function assertActor(actor, { named = false } = {}) {
4228
+ if (!KB_ACTOR_PATTERN.test(actor)) {
4229
+ throw new KbInvalidActorError(actor, "is not kind or kind:name");
4230
+ }
4231
+ if (named && normalizeActor(actor) === "unknown") {
4232
+ throw new KbInvalidActorError(actor, "cannot verify: name who checked");
4233
+ }
4234
+ }
4142
4235
 
4143
4236
  // src/compose.ts
4144
4237
  var import_zod4 = require("zod");
4238
+
4239
+ // src/anchors/errors.ts
4240
+ function locatorText(locator) {
4241
+ const span2 = locator.span ? `:${locator.span.start}-${locator.span.end}` : "";
4242
+ const symbol = locator.symbol ? `:${cap(locator.symbol)}` : "";
4243
+ const repo = locator.repo ? `${cap(locator.repo)}@` : "";
4244
+ const ref = locator.ref ? `@${cap(locator.ref)}` : "";
4245
+ return `${repo}${cap(locator.file)}${symbol}${span2}${ref}`;
4246
+ }
4247
+ var FIELD_CAP = 120;
4248
+ function cap(value) {
4249
+ return value.length > FIELD_CAP ? `${value.slice(0, FIELD_CAP - 1)}\u2026` : value;
4250
+ }
4251
+ var KbAnchorSetDuplicateError = class extends BaseError {
4252
+ constructor(locator) {
4253
+ super({
4254
+ message: `kb: ${locator} appears twice in this set \u2014 a record holds each pointer once`,
4255
+ errorType: "KbAnchorSetDuplicate" /* KbAnchorSetDuplicate */,
4256
+ code: 400,
4257
+ fault: "User" /* User */,
4258
+ retriable: false,
4259
+ reportToUser: true,
4260
+ details: { locator, action: "refused" }
4261
+ });
4262
+ this.locator = locator;
4263
+ }
4264
+ locator;
4265
+ };
4266
+
4267
+ // src/anchors/apply.ts
4268
+ var LOCATOR_FIELDS = [
4269
+ "file",
4270
+ "symbol",
4271
+ "span",
4272
+ "side",
4273
+ "repo",
4274
+ "ref"
4275
+ ];
4276
+ function applyAnchorSet(current, incoming) {
4277
+ const anchors = incoming.map((anchor) => ({ ...anchor }));
4278
+ const seen = /* @__PURE__ */ new Set();
4279
+ for (const anchor of anchors) {
4280
+ const key2 = locatorKey(anchor);
4281
+ if (seen.has(key2)) {
4282
+ throw new KbAnchorSetDuplicateError(locatorText(locatorOf(anchor)));
4283
+ }
4284
+ seen.add(key2);
4285
+ }
4286
+ return { anchors, changes: diff(current, anchors) };
4287
+ }
4288
+ function diff(current, next) {
4289
+ const before = new Map(
4290
+ current.filter((anchor) => anchor.hash).map((a) => [a.hash, a])
4291
+ );
4292
+ const beforeLocators = new Map(current.map((a) => [locatorKey(a), a]));
4293
+ const afterLocators = new Set(next.map((anchor) => locatorKey(anchor)));
4294
+ const moved = /* @__PURE__ */ new Set();
4295
+ const changes = [];
4296
+ for (const anchor of next) {
4297
+ const source = anchor.hash ? before.get(anchor.hash) : void 0;
4298
+ if (source) {
4299
+ if (locatorKey(source) === locatorKey(anchor)) continue;
4300
+ moved.add(locatorKey(source));
4301
+ changes.push({
4302
+ op: "move",
4303
+ from: locatorOf(source),
4304
+ to: locatorOf(anchor)
4305
+ });
4306
+ continue;
4307
+ }
4308
+ if (beforeLocators.has(locatorKey(anchor))) continue;
4309
+ changes.push({ op: "add", to: locatorOf(anchor) });
4310
+ }
4311
+ for (const anchor of current) {
4312
+ const key2 = locatorKey(anchor);
4313
+ if (afterLocators.has(key2) || moved.has(key2)) continue;
4314
+ changes.push({ op: "drop", from: locatorOf(anchor) });
4315
+ }
4316
+ return changes;
4317
+ }
4318
+ function locatorOf(anchor) {
4319
+ return kbAnchorLocatorSchema.parse(
4320
+ Object.fromEntries(
4321
+ LOCATOR_FIELDS.flatMap(
4322
+ (field) => anchor[field] === void 0 ? [] : [[field, anchor[field]]]
4323
+ )
4324
+ )
4325
+ );
4326
+ }
4327
+ function locatorKey(anchor) {
4328
+ return JSON.stringify([
4329
+ anchor.file,
4330
+ anchor.symbol ?? "",
4331
+ anchor.span ? `${anchor.span.start}-${anchor.span.end}` : "",
4332
+ anchor.side ?? "new",
4333
+ anchor.repo === void 0 ? "" : normalizeRepoUrl(anchor.repo),
4334
+ anchor.ref ?? ""
4335
+ ]);
4336
+ }
4337
+
4338
+ // src/compose.ts
4145
4339
  var composeLinkSchema = import_zod4.z.object({
4146
4340
  target: kbConceptIdSchema,
4147
4341
  rel: import_zod4.z.enum(KB_LINK_RELS)
@@ -4213,7 +4407,9 @@ function composeRecord(type, input, writtenBy, writtenAt) {
4213
4407
  strauss_status: spec.initialStatus
4214
4408
  };
4215
4409
  if (parsed.stale_after) frontmatter.stale_after = parsed.stale_after;
4216
- if (parsed.anchors?.length) frontmatter.strauss_anchors = parsed.anchors;
4410
+ if (parsed.anchors?.length) {
4411
+ frontmatter.strauss_anchors = applyAnchorSet([], parsed.anchors).anchors;
4412
+ }
4217
4413
  if (parsed.verify?.length) frontmatter.strauss_verify = parsed.verify;
4218
4414
  if (parsed.tags?.length) frontmatter.tags = parsed.tags;
4219
4415
  if (parsed.sources?.length) frontmatter.sources = parsed.sources;
@@ -5639,19 +5835,11 @@ function argvPositional(argv, ...names) {
5639
5835
  }
5640
5836
 
5641
5837
  // src/commands/anchor-resolve.ts
5642
- function resolverSummary(results) {
5643
- const names = [
5644
- ...new Set(
5645
- results.flatMap((entry) => entry.resolver ? [entry.resolver] : [])
5646
- )
5647
- ].sort();
5648
- return names.length ? `${names.join(" + ")} resolver` : "whole-file";
5649
- }
5650
5838
  var anchorResolveCommand = define({
5651
5839
  name: "anchor-resolve",
5652
5840
  tool: "kb_anchor_resolve",
5653
- usage: "anchor-resolve <concept-id> [--repo-root <path>] [--offline] [--rebaseline] [--restamp]",
5654
- 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.",
5841
+ usage: "anchor-resolve <concept-id> [--repo-root <path>] [--offline] [--rebaseline] [--restamp] [--check]",
5842
+ 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.",
5655
5843
  input: import_zod9.z.object({
5656
5844
  bundlePath,
5657
5845
  conceptId,
@@ -5664,6 +5852,9 @@ var anchorResolveCommand = define({
5664
5852
  ),
5665
5853
  restamp: import_zod9.z.boolean().optional().describe(
5666
5854
  "Refresh `resolved_at` on anchors that already match. Off by default, so a green run writes nothing."
5855
+ ),
5856
+ check: import_zod9.z.boolean().optional().describe(
5857
+ "Resolve and report only: no hash, no `resolved_at`, no log entry."
5667
5858
  )
5668
5859
  }),
5669
5860
  fromArgv: (argv, path) => ({
@@ -5672,9 +5863,24 @@ var anchorResolveCommand = define({
5672
5863
  repoRoot: argvFlag(argv, "--repo-root"),
5673
5864
  offline: argv.includes("--offline"),
5674
5865
  rebaseline: argv.includes("--rebaseline"),
5675
- restamp: argv.includes("--restamp")
5866
+ restamp: argv.includes("--restamp"),
5867
+ check: argv.includes("--check")
5676
5868
  }),
5677
- run: async ({ store, actor, now }, { bundlePath: path, conceptId: id, repoRoot, offline, rebaseline, restamp }) => {
5869
+ run: async ({ store, actor, now }, {
5870
+ bundlePath: path,
5871
+ conceptId: id,
5872
+ repoRoot,
5873
+ offline,
5874
+ rebaseline,
5875
+ restamp,
5876
+ check
5877
+ }) => {
5878
+ if (check && (rebaseline || restamp)) {
5879
+ throw new KbFlagConflictError([
5880
+ "check",
5881
+ rebaseline ? "rebaseline" : "restamp"
5882
+ ]);
5883
+ }
5678
5884
  const root = repoRoot ?? process.cwd();
5679
5885
  const record = await store.read(path, id);
5680
5886
  if (!record) throw new KbRecordNotFoundError(id);
@@ -5683,7 +5889,6 @@ var anchorResolveCommand = define({
5683
5889
  return {
5684
5890
  conceptId: id,
5685
5891
  results: [],
5686
- verified: false,
5687
5892
  note: "record has no anchors"
5688
5893
  };
5689
5894
  }
@@ -5741,7 +5946,7 @@ var anchorResolveCommand = define({
5741
5946
  if (!anchor.hash) {
5742
5947
  results.push({
5743
5948
  ...base2,
5744
- state: "stamped",
5949
+ state: check ? "unstamped" : "stamped",
5745
5950
  currentHash: stampedHash,
5746
5951
  hashKind: stampedKind,
5747
5952
  ...producedBy ? { resolver: producedBy } : {}
@@ -5793,7 +5998,7 @@ var anchorResolveCommand = define({
5793
5998
  if (refresh) dirty = true;
5794
5999
  }
5795
6000
  let frozen = false;
5796
- if (dirty) {
6001
+ if (dirty && !check) {
5797
6002
  try {
5798
6003
  await assertBaseNotFrozen(process.cwd(), path);
5799
6004
  } catch (error) {
@@ -5808,42 +6013,11 @@ var anchorResolveCommand = define({
5808
6013
  const unreachable = results.filter(
5809
6014
  (entry) => isUncheckedReason(entry.reason)
5810
6015
  ).length;
5811
- const checked = results.length - unreachable;
5812
6016
  const matches3 = results.filter((entry) => entry.state === "match").length;
5813
- const note = `${matches3}/${checked} anchors match${unreachable ? `, ${unreachable} unreachable` : ""}`;
5814
- const clean = checked > 0 && matches3 === checked && unreachable === 0;
5815
- if (clean) {
5816
- try {
5817
- await store.verify(
5818
- path,
5819
- id,
5820
- `anchor-resolve: ${note} (${resolverSummary(results)})`,
5821
- actor,
5822
- now()
5823
- );
5824
- } catch (error) {
5825
- if (!(error instanceof KbSelfVerificationError)) throw error;
5826
- return {
5827
- conceptId: id,
5828
- results,
5829
- verified: false,
5830
- verifyRefused: "self-verification",
5831
- ...frozenNote,
5832
- ...hintNote
5833
- };
5834
- }
5835
- return {
5836
- conceptId: id,
5837
- results,
5838
- verified: true,
5839
- ...frozenNote,
5840
- ...hintNote
5841
- };
5842
- }
6017
+ const note = `${matches3}/${results.length - unreachable} anchors match, ${unreachable} unreachable`;
5843
6018
  return {
5844
6019
  conceptId: id,
5845
6020
  results,
5846
- verified: false,
5847
6021
  ...unreachable ? { note } : {},
5848
6022
  ...frozenNote,
5849
6023
  ...hintNote
@@ -5930,14 +6104,117 @@ async function readSources(anchors, root, offline) {
5930
6104
  return sources;
5931
6105
  }
5932
6106
 
5933
- // src/commands/answer.ts
6107
+ // src/commands/anchor-set/model.ts
5934
6108
  var import_zod10 = require("zod");
6109
+ var anchorSetInputSchema = import_zod10.z.object({
6110
+ reason: import_zod10.z.string().refine((text) => text.trim().length > 0, {
6111
+ message: "reason must say what was reviewed"
6112
+ }).describe(
6113
+ "What the reviewer read that makes these the right pointers. Recorded in the log."
6114
+ ),
6115
+ anchors: import_zod10.z.array(kbAnchorWriteSchema).min(1).describe(
6116
+ "The complete new anchor set. Carry an anchor's hash forward to keep drift visible until the new code is read."
6117
+ )
6118
+ }).strict();
6119
+ var anchorSetCommandInput = import_zod10.z.object({
6120
+ bundlePath,
6121
+ conceptId,
6122
+ input: anchorSetInputSchema,
6123
+ resolve: import_zod10.z.boolean().optional().describe(
6124
+ "Also resolve and stamp every anchor against the current code, as anchor-resolve --rebaseline does."
6125
+ ),
6126
+ repoRoot: import_zod10.z.string().min(1).optional().describe(
6127
+ "Where the anchored source lives, for resolve. Defaults to the working directory."
6128
+ ),
6129
+ offline: import_zod10.z.boolean().optional().describe("With resolve, read foreign anchors from the repo cache only.")
6130
+ });
6131
+
6132
+ // src/commands/anchor-set/command.ts
6133
+ var NOTE = "pointers only: nothing was resolved or verified. Run anchor-resolve to check the new pointers, --rebaseline to accept the code, or pass resolve to do both here.";
6134
+ var STAMPED_NOTE = "pointers set and stamped against the current code. Not verification: run verify separately if someone reviewed it.";
6135
+ var anchorSetCommand = define({
6136
+ name: "anchor-set",
6137
+ tool: "kb_anchor_set",
6138
+ usage: "anchor-set <concept-id> [--resolve] [--repo-root <path>] [--offline] < anchors.json",
6139
+ description: "Set a record's code anchors after a reviewed refactor, with a reason. The array is the whole set. With resolve, every anchor is stamped against the current code in the same call; without it, run kb_anchor_resolve next. Recorded in the log, never verification.",
6140
+ input: anchorSetCommandInput,
6141
+ fromArgv: async (argv, path, stdin) => ({
6142
+ bundlePath: path,
6143
+ conceptId: argv[1],
6144
+ input: JSON.parse(await stdin()),
6145
+ resolve: argv.includes("--resolve"),
6146
+ repoRoot: argvFlag(argv, "--repo-root"),
6147
+ offline: argv.includes("--offline")
6148
+ }),
6149
+ run: async (ctx, { bundlePath: path, conceptId: id, input, resolve: resolve7, repoRoot, offline }) => {
6150
+ const { store, actor } = ctx;
6151
+ await assertBaseNotFrozen(process.cwd(), path);
6152
+ let applied;
6153
+ const record = await store.updateAnchors(
6154
+ path,
6155
+ id,
6156
+ (current) => {
6157
+ applied = applyAnchorSet(current, input.anchors);
6158
+ return {
6159
+ anchors: applied.anchors,
6160
+ log: {
6161
+ operation: "anchor-set",
6162
+ reason: input.reason,
6163
+ anchors: applied.changes
6164
+ }
6165
+ };
6166
+ },
6167
+ actor
6168
+ );
6169
+ const changes = applied?.changes ?? [];
6170
+ if (!resolve7) {
6171
+ return {
6172
+ conceptId: id,
6173
+ reason: input.reason,
6174
+ changes,
6175
+ anchors: record.frontmatter.strauss_anchors ?? [],
6176
+ baseline: "unchanged",
6177
+ note: NOTE
6178
+ };
6179
+ }
6180
+ const resolved = await anchorResolveCommand.run(
6181
+ ctx,
6182
+ anchorResolveCommand.input.parse({
6183
+ bundlePath: path,
6184
+ conceptId: id,
6185
+ rebaseline: true,
6186
+ ...repoRoot ? { repoRoot } : {},
6187
+ ...offline ? { offline } : {}
6188
+ })
6189
+ );
6190
+ const after = await store.read(path, id);
6191
+ return {
6192
+ conceptId: id,
6193
+ reason: input.reason,
6194
+ changes,
6195
+ anchors: after?.frontmatter.strauss_anchors ?? [],
6196
+ baseline: "stamped",
6197
+ resolved: resolved.results,
6198
+ note: STAMPED_NOTE
6199
+ };
6200
+ },
6201
+ // With `resolve`, a pointer that names nothing is a failed set, not a
6202
+ // finding to read later. A remote nothing could reach was never checked, so
6203
+ // it does not fail — the same line anchor-resolve draws.
6204
+ failsWhen: (result) => (result.resolved ?? []).some((entry) => {
6205
+ const { state, reason } = entry;
6206
+ return state === "unresolved" && !isUncheckedReason(reason);
6207
+ })
6208
+ });
6209
+
6210
+ // src/commands/answer.ts
6211
+ var import_zod11 = require("zod");
5935
6212
  var answerCommand = define({
5936
6213
  name: "answer",
5937
6214
  tool: "kb_answer",
5938
6215
  usage: "answer <concept-id> <answer...>",
5939
6216
  description: "Resolve an open question: set status, stamp who and when, append an Answer section. If the answer overturns a decision or assumption, supersede that record explicitly.",
5940
- input: import_zod10.z.object({ bundlePath, conceptId, answer: import_zod10.z.string().min(1) }),
6217
+ input: import_zod11.z.object({ bundlePath, conceptId, answer: import_zod11.z.string().min(1) }),
5941
6218
  fromArgv: (argv, path) => ({
5942
6219
  bundlePath: path,
5943
6220
  conceptId: argv[1],
@@ -5951,27 +6228,27 @@ var answerCommand = define({
5951
6228
  });
5952
6229
 
5953
6230
  // src/commands/backlinks.ts
5954
- var import_zod11 = require("zod");
6231
+ var import_zod12 = require("zod");
5955
6232
  var backlinksCommand = define({
5956
6233
  name: "backlinks",
5957
6234
  tool: "kb_backlinks",
5958
6235
  usage: "backlinks <concept-id>",
5959
6236
  description: "Who points at this record: every inbound typed causal link (`strauss_links`), one hop, every rel including `related_to`, each with its rel and the standing of the record that made it. Use it when you need the exact edges \u2014 reviewing or renaming a record.",
5960
- input: import_zod11.z.object({ bundlePath, conceptId }),
6237
+ input: import_zod12.z.object({ bundlePath, conceptId }),
5961
6238
  fromArgv: (argv, path) => ({ bundlePath: path, conceptId: argv[1] }),
5962
6239
  run: async ({ store }, { bundlePath: path, conceptId: id }) => store.backlinks(path, id)
5963
6240
  });
5964
6241
 
5965
6242
  // src/commands/catalog.ts
5966
- var import_zod12 = require("zod");
6243
+ var import_zod13 = require("zod");
5967
6244
  var catalogCommand = define({
5968
6245
  name: "catalog",
5969
6246
  tool: "kb_catalog",
5970
6247
  usage: "catalog [type] [--tag T]...",
5971
6248
  description: "Lists every record as one line \u2014 concept id, type, title, standing, and a stale flag \u2014 at roughly thirty tokens each. Pick this over kb_load once kb_load refuses: kb_catalog never refuses. Superseded records show only their replacement; fetch bodies with kb_load, kb_pack, kb_query, or kb_trace.",
5972
- input: import_zod12.z.object({
6249
+ input: import_zod13.z.object({
5973
6250
  bundlePath,
5974
- type: import_zod12.z.enum(KB_RECORD_TYPES).optional(),
6251
+ type: import_zod13.z.enum(KB_RECORD_TYPES).optional(),
5975
6252
  tags: TAGS
5976
6253
  }),
5977
6254
  fromArgv: (argv, path) => {
@@ -6043,7 +6320,7 @@ function count(value, noun) {
6043
6320
  var import_node_buffer = require("buffer");
6044
6321
  var import_promises10 = require("fs/promises");
6045
6322
  var import_node_path12 = require("path");
6046
- var import_zod15 = require("zod");
6323
+ var import_zod16 = require("zod");
6047
6324
 
6048
6325
  // src/drift/moved.ts
6049
6326
  var import_promises9 = require("fs/promises");
@@ -6380,7 +6657,7 @@ function claimOf(record) {
6380
6657
  }
6381
6658
 
6382
6659
  // src/commands/match/command.ts
6383
- var import_zod14 = require("zod");
6660
+ var import_zod15 = require("zod");
6384
6661
 
6385
6662
  // src/commands/match/errors.ts
6386
6663
  var KbMatchInputError = class extends BaseError {
@@ -6400,21 +6677,21 @@ var KbMatchInputError = class extends BaseError {
6400
6677
  };
6401
6678
 
6402
6679
  // src/commands/match/model.ts
6403
- var import_zod13 = require("zod");
6404
- var diffHunkSchema = import_zod13.z.object({
6405
- startLine: import_zod13.z.number().int().positive(),
6406
- endLine: import_zod13.z.number().int().positive(),
6407
- side: import_zod13.z.enum(["old", "new"]).optional()
6680
+ var import_zod14 = require("zod");
6681
+ var diffHunkSchema = import_zod14.z.object({
6682
+ startLine: import_zod14.z.number().int().positive(),
6683
+ endLine: import_zod14.z.number().int().positive(),
6684
+ side: import_zod14.z.enum(["old", "new"]).optional()
6408
6685
  }).passthrough();
6409
- var diffFileSchema = import_zod13.z.object({
6410
- filePath: import_zod13.z.string().min(1).describe("Repo-relative, spelled the way anchors are."),
6411
- hunks: import_zod13.z.array(diffHunkSchema)
6686
+ var diffFileSchema = import_zod14.z.object({
6687
+ filePath: import_zod14.z.string().min(1).describe("Repo-relative, spelled the way anchors are."),
6688
+ hunks: import_zod14.z.array(diffHunkSchema)
6412
6689
  });
6413
- var symbolRangeSchema = import_zod13.z.object({
6414
- file: import_zod13.z.string().min(1),
6415
- symbol: import_zod13.z.string().min(1),
6416
- startLine: import_zod13.z.number().int().positive(),
6417
- endLine: import_zod13.z.number().int().positive()
6690
+ var symbolRangeSchema = import_zod14.z.object({
6691
+ file: import_zod14.z.string().min(1),
6692
+ symbol: import_zod14.z.string().min(1),
6693
+ startLine: import_zod14.z.number().int().positive(),
6694
+ endLine: import_zod14.z.number().int().positive()
6418
6695
  });
6419
6696
 
6420
6697
  // src/commands/match/parse-unified-diff.ts
@@ -6663,17 +6940,17 @@ var matchCommand = define({
6663
6940
  tool: "kb_match",
6664
6941
  usage: "match --git <base>..<head> | --stdin [--repo-root <path>] [--offline] [--include-non-current]",
6665
6942
  description: "Which records sit on each changed hunk: the anchored records per file range, current first, each with its standing and the anchor that matched. kb_load hands over a whole base; this narrows a diff. Symbol ranges resolve from repoRoot when omitted; non-current records need includeNonCurrent.",
6666
- input: import_zod14.z.object({
6943
+ input: import_zod15.z.object({
6667
6944
  bundlePath,
6668
- files: import_zod14.z.array(diffFileSchema).describe("The changed files, each with its post-change line ranges."),
6669
- symbolRanges: import_zod14.z.array(symbolRangeSchema).optional().describe(
6945
+ files: import_zod15.z.array(diffFileSchema).describe("The changed files, each with its post-change line ranges."),
6946
+ symbolRanges: import_zod15.z.array(symbolRangeSchema).optional().describe(
6670
6947
  "Symbol spans the caller already has. Resolved from repoRoot when omitted."
6671
6948
  ),
6672
6949
  repoRoot: REPO_ROOT,
6673
- offline: import_zod14.z.boolean().optional().describe(
6950
+ offline: import_zod15.z.boolean().optional().describe(
6674
6951
  "Resolve symbol ranges from what is already on disk, never fetching a grammar."
6675
6952
  ),
6676
- includeNonCurrent: import_zod14.z.boolean().optional().describe(
6953
+ includeNonCurrent: import_zod15.z.boolean().optional().describe(
6677
6954
  "Return superseded, rejected and unsettled records too, each carrying its standing."
6678
6955
  )
6679
6956
  }),
@@ -6687,11 +6964,11 @@ var matchCommand = define({
6687
6964
  ...argv.includes("--include-non-current") ? { includeNonCurrent: true } : {}
6688
6965
  };
6689
6966
  if (range !== void 0) {
6690
- const diff = await readRangeDiff(repoRoot ?? process.cwd(), range);
6691
- if (!diff.ok) {
6692
- throw new KbMatchInputError(`--git ${range} ${REFUSED[diff.reason]}`);
6967
+ const diff2 = await readRangeDiff(repoRoot ?? process.cwd(), range);
6968
+ if (!diff2.ok) {
6969
+ throw new KbMatchInputError(`--git ${range} ${REFUSED[diff2.reason]}`);
6693
6970
  }
6694
- return { ...base2, files: parseUnifiedDiff(diff.text) };
6971
+ return { ...base2, files: parseUnifiedDiff(diff2.text) };
6695
6972
  }
6696
6973
  if (!argv.includes("--stdin")) {
6697
6974
  throw new KbMatchInputError(
@@ -6777,22 +7054,22 @@ function project(match, ranges, all) {
6777
7054
 
6778
7055
  // src/commands/classify.ts
6779
7056
  var classifyFileSchema = diffFileSchema.extend({
6780
- hunks: import_zod15.z.array(
6781
- diffHunkSchema.extend({ lines: import_zod15.z.array(import_zod15.z.string()).optional() })
7057
+ hunks: import_zod16.z.array(
7058
+ diffHunkSchema.extend({ lines: import_zod16.z.array(import_zod16.z.string()).optional() })
6782
7059
  ),
6783
- renamedFrom: import_zod15.z.string().min(1).optional().describe("Where `git diff -M` says the path came from."),
6784
- similarity: import_zod15.z.number().min(0).max(100).optional()
7060
+ renamedFrom: import_zod16.z.string().min(1).optional().describe("Where `git diff -M` says the path came from."),
7061
+ similarity: import_zod16.z.number().min(0).max(100).optional()
6785
7062
  });
6786
7063
  var classifyCommand = define({
6787
7064
  name: "classify",
6788
7065
  tool: "kb_classify",
6789
7066
  usage: "classify --git <base>..<head> | --stdin [--repo-root <path>] [--offline]",
6790
7067
  description: "What kind of change each file carries: test, config, ci, docs, lockfile, generated, boilerplate, rename or source, with the rule that decided it. Derived from the diff and never stored; a `review:generated`, `review:boilerplate` or `review:move` fact anchored on a file overrides the heuristic. kb_match says what sits on a hunk; this says whether to read it.",
6791
- input: import_zod15.z.object({
7068
+ input: import_zod16.z.object({
6792
7069
  bundlePath,
6793
- files: import_zod15.z.array(classifyFileSchema).describe("The changed files, each with its line ranges."),
7070
+ files: import_zod16.z.array(classifyFileSchema).describe("The changed files, each with its line ranges."),
6794
7071
  repoRoot: REPO_ROOT,
6795
- offline: import_zod15.z.boolean().optional().describe(
7072
+ offline: import_zod16.z.boolean().optional().describe(
6796
7073
  "Resolve symbol ranges from what is already on disk, never fetching a grammar."
6797
7074
  )
6798
7075
  }),
@@ -6805,15 +7082,15 @@ var classifyCommand = define({
6805
7082
  ...argv.includes("--offline") ? { offline: true } : {}
6806
7083
  };
6807
7084
  if (range !== void 0) {
6808
- const diff = await readRangeDiff(repoRoot ?? process.cwd(), range);
6809
- if (!diff.ok) {
7085
+ const diff2 = await readRangeDiff(repoRoot ?? process.cwd(), range);
7086
+ if (!diff2.ok) {
6810
7087
  throw new KbClassifyInputError(
6811
- `--git ${range} ${REFUSED2[diff.reason]}`
7088
+ `--git ${range} ${REFUSED2[diff2.reason]}`
6812
7089
  );
6813
7090
  }
6814
7091
  return {
6815
7092
  ...base2,
6816
- files: parseUnifiedDiff(diff.text, {
7093
+ files: parseUnifiedDiff(diff2.text, {
6817
7094
  keepEmpty: true,
6818
7095
  withLines: true
6819
7096
  })
@@ -6903,29 +7180,29 @@ function renderClassify(result) {
6903
7180
  }
6904
7181
 
6905
7182
  // src/commands/context.ts
6906
- var import_zod16 = require("zod");
7183
+ var import_zod17 = require("zod");
6907
7184
  var contextCommand = define({
6908
7185
  name: "context",
6909
7186
  tool: "kb_context",
6910
7187
  usage: "context [--profile NAME] [--budget N] [--full-under N] [--exclude-tag T]... [--format json] [--event NAME]",
6911
7188
  description: "Index block of pinned bases (ids, titles, standing) for injection at context birth. Takes no bundlePath \u2014 reads the workspace pin manifests. Empty when nothing is pinned; refuses over budget rather than truncating. Budget precedence: flags, then the manifest `context[profile]` over `context.default`, then the built-in profile, then package defaults.",
6912
- input: import_zod16.z.object({
6913
- budgetTokens: import_zod16.z.number().int().positive().optional().describe(
7189
+ input: import_zod17.z.object({
7190
+ budgetTokens: import_zod17.z.number().int().positive().optional().describe(
6914
7191
  "Ceiling on the whole emitted block; past it the command refuses with a list of bases rather than truncating. Defaults to 4000."
6915
7192
  ),
6916
- fullUnderTokens: import_zod16.z.number().int().positive().optional().describe(
7193
+ fullUnderTokens: import_zod17.z.number().int().positive().optional().describe(
6917
7194
  "Per-base rendering threshold, applied before the budget: a base whose complete load fits under this arrives as full records instead of index lines, and the whole block still answers to budgetTokens. Off by default \u2014 index-only is the safe default at a context birth, because injected bodies outlive the qualifiers on them; the session-start profile opts tiny bases in at 1500."
6918
7195
  ),
6919
- profile: import_zod16.z.string().optional().describe(
7196
+ profile: import_zod17.z.string().optional().describe(
6920
7197
  "Named budget set: built-ins are session-start (full-under 1500), compact and turn (budget 2500); the manifests' `context` tables override per repo. Unknown names fall through to defaults rather than failing."
6921
7198
  ),
6922
- excludeTags: import_zod16.z.array(import_zod16.z.string().min(1)).optional().describe(
7199
+ excludeTags: import_zod17.z.array(import_zod17.z.string().min(1)).optional().describe(
6923
7200
  "Frontmatter tags whose records stay out of the block. The base stays pinned and stays readable by tool; resolved like the budgets."
6924
7201
  ),
6925
- format: import_zod16.z.enum(["markdown", "json"]).optional().describe(
7202
+ format: import_zod17.z.enum(["markdown", "json"]).optional().describe(
6926
7203
  "CLI envelope for hook protocols that require strict JSON on stdout. MCP callers omit this \u2014 the block itself is identical."
6927
7204
  ),
6928
- event: import_zod16.z.string().optional().describe(
7205
+ event: import_zod17.z.string().optional().describe(
6929
7206
  "hookEventName stamped into the JSON envelope. Only meaningful with format=json."
6930
7207
  )
6931
7208
  }),
@@ -6964,20 +7241,20 @@ var contextCommand = define({
6964
7241
  });
6965
7242
 
6966
7243
  // src/commands/doctor.ts
6967
- var import_zod18 = require("zod");
7244
+ var import_zod19 = require("zod");
6968
7245
 
6969
7246
  // src/commands/reassess.ts
6970
- var import_zod17 = require("zod");
7247
+ var import_zod18 = require("zod");
6971
7248
  var reassessCommand = define({
6972
7249
  name: "reassess",
6973
7250
  tool: "kb_reassess",
6974
7251
  usage: "reassess <concept-id> [--repo-root <path>] [--with-diff]",
6975
7252
  description: "One drifted record, as something to judge: its claim, each anchor's drift class, the old-vs-new span diff, and the records that depend on it. Formatting-only drift is dropped. Empty when there is nothing to reassess. Writes: relocates moved anchors, keeping their hash; never verifies, supersedes, or changes standing.",
6976
- input: import_zod17.z.object({
7253
+ input: import_zod18.z.object({
6977
7254
  bundlePath,
6978
7255
  conceptId,
6979
7256
  repoRoot: REPO_ROOT,
6980
- withDiff: import_zod17.z.boolean().optional().describe(
7257
+ withDiff: import_zod18.z.boolean().optional().describe(
6981
7258
  "Recover each anchor's committed span and render the diff. Reads git history."
6982
7259
  )
6983
7260
  }),
@@ -7122,13 +7399,13 @@ function at(file, symbol) {
7122
7399
  }
7123
7400
 
7124
7401
  // src/commands/doctor.ts
7125
- var days = (what, fallback) => import_zod18.z.number().int().positive().optional().describe(`${what} Defaults to ${fallback}.`);
7402
+ var days = (what, fallback) => import_zod19.z.number().int().positive().optional().describe(`${what} Defaults to ${fallback}.`);
7126
7403
  var doctorCommand = define({
7127
7404
  name: "doctor",
7128
7405
  tool: "kb_doctor",
7129
7406
  usage: "doctor [--expiring-days N] [--unverified-days N] [--aging-days N] [--repo-root PATH] [--offline] [--strict] [--drifted [--with-diff]]",
7130
7407
  description: "Read-only health sweep: expired, expiring, unverified, aging, orphaned, broken-supersession, superseded-but-cited, drifted and unchecked anchors. Every group is reported even when empty; nothing is written or re-stamped. `drifted` narrows it to a reassessment packet per drifted record, `with_diff` adding each anchor's old-vs-new span.",
7131
- input: import_zod18.z.object({
7408
+ input: import_zod19.z.object({
7132
7409
  bundlePath,
7133
7410
  repoRoot: REPO_ROOT,
7134
7411
  expiringDays: days(
@@ -7143,16 +7420,16 @@ var doctorCommand = define({
7143
7420
  "How long a record may stay `open` or `proposed` before `aging` reports it, in days.",
7144
7421
  DEFAULT_AGING_DAYS
7145
7422
  ),
7146
- offline: import_zod18.z.boolean().optional().describe(
7423
+ offline: import_zod19.z.boolean().optional().describe(
7147
7424
  "Read foreign anchors from the local repo cache only, never fetching."
7148
7425
  ),
7149
- strict: import_zod18.z.boolean().optional().describe(
7426
+ strict: import_zod19.z.boolean().optional().describe(
7150
7427
  "Turn an expired record into a non-zero exit for the CLI. No effect on the report itself."
7151
7428
  ),
7152
- drifted: import_zod18.z.boolean().optional().describe(
7429
+ drifted: import_zod19.z.boolean().optional().describe(
7153
7430
  "Report only drift, as a reassessment packet per record: claim, per-anchor class, and what depends on it."
7154
7431
  ),
7155
- withDiff: import_zod18.z.boolean().optional().describe(
7432
+ withDiff: import_zod19.z.boolean().optional().describe(
7156
7433
  "With `drifted`: recover each anchor's committed span and render the old-vs-new diff. Reads git history."
7157
7434
  )
7158
7435
  }),
@@ -7329,7 +7606,7 @@ function renderPackets(result) {
7329
7606
  // src/commands/export.ts
7330
7607
  var import_promises11 = require("fs/promises");
7331
7608
  var import_node_path13 = require("path");
7332
- var import_zod19 = require("zod");
7609
+ var import_zod20 = require("zod");
7333
7610
  var NUMBERED = /^(\d{4})-(.+)\.md$/;
7334
7611
  var MARKER = "<!-- strauss-kb export: ";
7335
7612
  var exportCommand = define({
@@ -7337,10 +7614,10 @@ var exportCommand = define({
7337
7614
  tool: "kb_export",
7338
7615
  usage: "export --format madr --to <dir>",
7339
7616
  description: "Write the base's decisions out as numbered MADR files, one per decision, for a repository that keeps ADRs of its own. Numbering is by slug, so a re-run rewrites its own files in place. A superseded decision is exported with what replaced it.",
7340
- input: import_zod19.z.object({
7617
+ input: import_zod20.z.object({
7341
7618
  bundlePath,
7342
- format: import_zod19.z.enum(["madr"]).describe("Output layout. `madr` is the only one so far."),
7343
- to: import_zod19.z.string().min(1).describe("Directory the ADR files are written into.")
7619
+ format: import_zod20.z.enum(["madr"]).describe("Output layout. `madr` is the only one so far."),
7620
+ to: import_zod20.z.string().min(1).describe("Directory the ADR files are written into.")
7344
7621
  }),
7345
7622
  fromArgv: (argv, path) => ({
7346
7623
  bundlePath: path,
@@ -7462,19 +7739,19 @@ function bodySections(body) {
7462
7739
  }
7463
7740
 
7464
7741
  // src/commands/impact.ts
7465
- var import_zod20 = require("zod");
7742
+ var import_zod21 = require("zod");
7466
7743
  var impactCommand = define({
7467
7744
  name: "impact",
7468
7745
  tool: "kb_impact",
7469
7746
  usage: "impact <concept-id> [--depth N] [--rels a,b]",
7470
7747
  description: "What breaks if this record changes: its transitive set of dependants, each with its standing. Each rel declares which of its ends depends on the other, and the walk follows each rel in its own direction. Naming `related_to` or an unknown rel in `rels` is an error. kb_backlinks gives one flat hop.",
7471
- input: import_zod20.z.object({
7748
+ input: import_zod21.z.object({
7472
7749
  bundlePath,
7473
7750
  conceptId,
7474
- depth: import_zod20.z.number().int().positive().optional().describe(
7751
+ depth: import_zod21.z.number().int().positive().optional().describe(
7475
7752
  "Hops out from the record. Unbounded when omitted; a walk this cuts reports truncated: true."
7476
7753
  ),
7477
- rels: import_zod20.z.array(import_zod20.z.enum(KB_CAUSAL_LINK_RELS)).optional().describe(
7754
+ rels: import_zod21.z.array(import_zod21.z.enum(KB_CAUSAL_LINK_RELS)).optional().describe(
7478
7755
  "Narrow which rels the walk follows. Defaults to every rel that carries a dependence \u2014 all but related_to."
7479
7756
  )
7480
7757
  }),
@@ -7495,15 +7772,15 @@ var impactCommand = define({
7495
7772
  });
7496
7773
 
7497
7774
  // src/commands/list.ts
7498
- var import_zod21 = require("zod");
7775
+ var import_zod22 = require("zod");
7499
7776
  var listCommand = define({
7500
7777
  name: "list",
7501
7778
  tool: "kb_list",
7502
7779
  usage: "list [type] [--tag T]...",
7503
7780
  description: "Every record, optionally one type or tag. For enumerating; use kb_query for a question.",
7504
- input: import_zod21.z.object({
7781
+ input: import_zod22.z.object({
7505
7782
  bundlePath,
7506
- type: import_zod21.z.enum(KB_RECORD_TYPES).optional(),
7783
+ type: import_zod22.z.enum(KB_RECORD_TYPES).optional(),
7507
7784
  tags: TAGS
7508
7785
  }),
7509
7786
  fromArgv: (argv, path) => {
@@ -7527,17 +7804,17 @@ var listCommand = define({
7527
7804
  });
7528
7805
 
7529
7806
  // src/commands/load.ts
7530
- var import_zod22 = require("zod");
7807
+ var import_zod23 = require("zod");
7531
7808
  var loadCommand = define({
7532
7809
  name: "load",
7533
7810
  tool: "kb_load",
7534
7811
  usage: "load [type] [--budget N | --all] [--repo-root PATH]",
7535
7812
  description: "Load the whole base, each record with its standing \u2014 call it first, at the point of use, since compaction drops it. Superseded records arrive as stubs; kb_trace has the history. Over budget it refuses: kb_catalog, then kb_pack, or narrow with `type`; `all` bypasses. Never read record files directly \u2014 only kb_* tools resolve supersession. `digest` stamps the base's content, so hooks know when to reload.",
7536
- input: import_zod22.z.object({
7813
+ input: import_zod23.z.object({
7537
7814
  bundlePath,
7538
- type: import_zod22.z.enum(KB_RECORD_TYPES).optional(),
7539
- budgetTokens: import_zod22.z.number().int().positive().optional().describe("Approximate token ceiling. Defaults to 25000."),
7540
- all: import_zod22.z.boolean().optional().describe(
7815
+ type: import_zod23.z.enum(KB_RECORD_TYPES).optional(),
7816
+ budgetTokens: import_zod23.z.number().int().positive().optional().describe("Approximate token ceiling. Defaults to 25000."),
7817
+ all: import_zod23.z.boolean().optional().describe(
7541
7818
  "Loads the entire base regardless of size, bypassing the token budget; mutually exclusive with budgetTokens."
7542
7819
  ),
7543
7820
  repoRoot: REPO_ROOT
@@ -7579,25 +7856,25 @@ var loadCommand = define({
7579
7856
  });
7580
7857
 
7581
7858
  // src/commands/log.ts
7582
- var import_zod23 = require("zod");
7859
+ var import_zod24 = require("zod");
7583
7860
  var logCommand = define({
7584
7861
  name: "log",
7585
7862
  tool: "kb_log",
7586
7863
  usage: "log",
7587
7864
  description: "Who touched what, and when. Append-only; malformed lines are reported, never repaired.",
7588
- input: import_zod23.z.object({ bundlePath }),
7865
+ input: import_zod24.z.object({ bundlePath }),
7589
7866
  fromArgv: (_argv, path) => ({ bundlePath: path }),
7590
7867
  run: ({ store }, { bundlePath: path }) => store.readLog(path)
7591
7868
  });
7592
7869
 
7593
7870
  // src/commands/no-decision.ts
7594
- var import_zod24 = require("zod");
7871
+ var import_zod25 = require("zod");
7595
7872
  var noDecisionCommand = define({
7596
7873
  name: "no-decision",
7597
7874
  tool: "kb_no_decision",
7598
7875
  usage: "no-decision <reason...>",
7599
7876
  description: "Record in one sentence that a piece of work had nothing to decide. Idempotent.",
7600
- input: import_zod24.z.object({ bundlePath, reason: import_zod24.z.string().min(1) }),
7877
+ input: import_zod25.z.object({ bundlePath, reason: import_zod25.z.string().min(1) }),
7601
7878
  fromArgv: (argv, path) => ({
7602
7879
  bundlePath: path,
7603
7880
  reason: argv.slice(1).join(" ").trim()
@@ -7614,20 +7891,20 @@ var noDecisionCommand = define({
7614
7891
  });
7615
7892
 
7616
7893
  // src/commands/pack.ts
7617
- var import_zod25 = require("zod");
7894
+ var import_zod26 = require("zod");
7618
7895
  var packCommand = define({
7619
7896
  name: "pack",
7620
7897
  tool: "kb_pack",
7621
7898
  usage: "pack <conceptId> [--hops N] [--max-nodes N] [--budget N]",
7622
7899
  description: "Bounded neighbourhood around one record: within `hops`, ranked, cut to `maxNodes`, with every cut record named under Excluded. Use when the base is over kb_load's budget and the work centres on a record you can name. Refuses over budget rather than truncating. Everything below the header is byte-stable across runs. Resolves supersession like kb_load.",
7623
- input: import_zod25.z.object({
7900
+ input: import_zod26.z.object({
7624
7901
  bundlePath,
7625
7902
  conceptId,
7626
- hops: import_zod25.z.number().int().positive().optional().describe("How far from the root the walk may reach. Defaults to 2."),
7627
- maxNodes: import_zod25.z.number().int().positive().optional().describe(
7903
+ hops: import_zod26.z.number().int().positive().optional().describe("How far from the root the walk may reach. Defaults to 2."),
7904
+ maxNodes: import_zod26.z.number().int().positive().optional().describe(
7628
7905
  "How many records the pack may hold, root included. Defaults to 20."
7629
7906
  ),
7630
- budgetTokens: import_zod25.z.number().int().positive().optional().describe(
7907
+ budgetTokens: import_zod26.z.number().int().positive().optional().describe(
7631
7908
  "Approximate token ceiling over what is actually emitted. Defaults to 25000."
7632
7909
  )
7633
7910
  }),
@@ -7714,22 +7991,22 @@ function warningLabel(warning) {
7714
7991
  }
7715
7992
 
7716
7993
  // src/commands/pin.ts
7717
- var import_zod26 = require("zod");
7994
+ var import_zod27 = require("zod");
7718
7995
  var pinCommand = define({
7719
7996
  name: "pin",
7720
7997
  tool: "kb_pin",
7721
7998
  usage: "pin [bundle-path] [--mode full|index] [--profiles a,b] [--local|--user] [--frozen|--unfreeze]",
7722
7999
  description: "Pin a base into a workspace manifest so kb_context surfaces it. Layers, nearest wins: project `.strauss/kb-pins.json` (default), `--local` (personal, gitignored), `--user` (`~/.strauss`). Idempotent; `--mode full|index`, `--profiles`, `--frozen`/`--unfreeze` update only those fields. A path with no records pins with a warning. Never touches the base itself.",
7723
- input: import_zod26.z.object({
8000
+ input: import_zod27.z.object({
7724
8001
  bundlePath,
7725
- mode: import_zod26.z.enum(["full", "index"]).optional().describe(
8002
+ mode: import_zod27.z.enum(["full", "index"]).optional().describe(
7726
8003
  "full: always emit this base's records whole (still under the block budget); index: never upgrade. Absent: the profile's full-under threshold decides."
7727
8004
  ),
7728
- profiles: import_zod26.z.array(import_zod26.z.string()).optional().describe("Context profiles this pin surfaces in. Absent: all of them."),
7729
- layer: import_zod26.z.enum(["project", "local", "user"]).optional().describe(
8005
+ profiles: import_zod27.z.array(import_zod27.z.string()).optional().describe("Context profiles this pin surfaces in. Absent: all of them."),
8006
+ layer: import_zod27.z.enum(["project", "local", "user"]).optional().describe(
7730
8007
  "Which manifest to write: project (committed, default), local (personal, gitignored), user (~/.strauss, every workspace)."
7731
8008
  ),
7732
- frozen: import_zod26.z.boolean().optional().describe(
8009
+ frozen: import_zod27.z.boolean().optional().describe(
7733
8010
  "true: the base is concluded \u2014 writes against it refuse while pinned. false: lift a freeze."
7734
8011
  )
7735
8012
  }),
@@ -7758,13 +8035,13 @@ var pinCommand = define({
7758
8035
  });
7759
8036
 
7760
8037
  // src/commands/pins.ts
7761
- var import_zod27 = require("zod");
8038
+ var import_zod28 = require("zod");
7762
8039
  var pinsCommand = define({
7763
8040
  name: "pins",
7764
8041
  tool: "kb_pins",
7765
8042
  usage: "pins",
7766
8043
  description: "Every pinned base across the manifest layers, with its layer and whether it resolves to records. Takes no bundlePath.",
7767
- input: import_zod27.z.object({}),
8044
+ input: import_zod28.z.object({}),
7768
8045
  fromArgv: () => ({}),
7769
8046
  run: ({ store }) => listPins(store, process.cwd())
7770
8047
  });
@@ -7900,16 +8177,16 @@ function recordType(conceptId2) {
7900
8177
  }
7901
8178
 
7902
8179
  // src/commands/promote/model.ts
7903
- var import_zod28 = require("zod");
7904
- var promoteInputSchema = import_zod28.z.object({
8180
+ var import_zod29 = require("zod");
8181
+ var promoteInputSchema = import_zod29.z.object({
7905
8182
  bundlePath,
7906
- conceptIds: import_zod28.z.array(conceptId).max(64).optional().describe("Records to copy into the target base. Omit with `list`."),
7907
- to: import_zod28.z.string().min(1).optional().describe("Absolute path to the base being promoted into."),
7908
- source: import_zod28.z.string().min(1).optional().describe(
8183
+ conceptIds: import_zod29.z.array(conceptId).max(64).optional().describe("Records to copy into the target base. Omit with `list`."),
8184
+ to: import_zod29.z.string().min(1).optional().describe("Absolute path to the base being promoted into."),
8185
+ source: import_zod29.z.string().min(1).optional().describe(
7909
8186
  "Where the promotion came from, usually the pull request URL. Recorded on each copy as a source."
7910
8187
  ),
7911
- force: import_zod28.z.boolean().optional().describe("Overwrite a record the target base already holds."),
7912
- list: import_zod28.z.boolean().optional().describe("List the source base's candidates instead of promoting.")
8188
+ force: import_zod29.z.boolean().optional().describe("Overwrite a record the target base already holds."),
8189
+ list: import_zod29.z.boolean().optional().describe("List the source base's candidates instead of promoting.")
7913
8190
  }).refine((input) => input.list === true || input.to !== void 0, {
7914
8191
  message: "promote needs a target base \u2014 pass --to <bundle>, or --list",
7915
8192
  path: ["to"]
@@ -8046,17 +8323,17 @@ function renderPromote(result) {
8046
8323
  }
8047
8324
 
8048
8325
  // src/commands/query.ts
8049
- var import_zod29 = require("zod");
8326
+ var import_zod30 = require("zod");
8050
8327
  var queryCommand = define({
8051
8328
  name: "query",
8052
8329
  tool: "kb_query",
8053
8330
  usage: "query <text...> [--tag T]... [--repo-root PATH]",
8054
8331
  description: "Search; every hit carries its standing. Flagged, never filtered: a superseded hit returns with its replacement, a rejected one is marked. Prefer kb_load when the base fits its budget \u2014 a full read beats search. Results are volatile: place them at the tail, not the cached prefix. Never read record files directly.",
8055
- input: import_zod29.z.object({
8332
+ input: import_zod30.z.object({
8056
8333
  bundlePath,
8057
- text: import_zod29.z.string().optional(),
8058
- type: import_zod29.z.enum(KB_RECORD_TYPES).optional(),
8059
- includeNonCurrent: import_zod29.z.boolean().optional(),
8334
+ text: import_zod30.z.string().optional(),
8335
+ type: import_zod30.z.enum(KB_RECORD_TYPES).optional(),
8336
+ includeNonCurrent: import_zod30.z.boolean().optional(),
8060
8337
  tags: TAGS,
8061
8338
  repoRoot: REPO_ROOT
8062
8339
  }),
@@ -8090,43 +8367,43 @@ var queryCommand = define({
8090
8367
  });
8091
8368
 
8092
8369
  // src/commands/read-index.ts
8093
- var import_zod30 = require("zod");
8370
+ var import_zod31 = require("zod");
8094
8371
  var readIndexCommand = define({
8095
8372
  name: "index",
8096
8373
  tool: "kb_index",
8097
8374
  usage: "index",
8098
8375
  description: "The index \u2014 title, type, status, description per record \u2014 rebuilt if stale. Cheapest re-orientation after compaction: call it (or kb_context) first, then kb_load or fetch by id.",
8099
- input: import_zod30.z.object({ bundlePath }),
8376
+ input: import_zod31.z.object({ bundlePath }),
8100
8377
  fromArgv: (_argv, path) => ({ bundlePath: path }),
8101
8378
  run: ({ store }, { bundlePath: path }) => store.readIndex(path)
8102
8379
  });
8103
8380
 
8104
8381
  // src/commands/schema.ts
8105
- var import_zod31 = require("zod");
8382
+ var import_zod32 = require("zod");
8106
8383
  var schemaCommand = define({
8107
8384
  name: "schema",
8108
8385
  tool: "kb_schema",
8109
8386
  usage: "schema",
8110
8387
  description: "JSON Schema for frontmatter, write input, and log entries, generated from the enforcing code.",
8111
- input: import_zod31.z.object({}),
8388
+ input: import_zod32.z.object({}),
8112
8389
  fromArgv: () => ({}),
8113
8390
  run: () => Promise.resolve(kbJsonSchemas())
8114
8391
  });
8115
8392
 
8116
8393
  // src/commands/stamp.ts
8117
8394
  var import_promises12 = require("fs/promises");
8118
- var import_zod32 = require("zod");
8395
+ var import_zod33 = require("zod");
8119
8396
  var DIGEST = /^[0-9a-f]{64}$/;
8120
8397
  var stampCommand = define({
8121
8398
  name: "stamp",
8122
8399
  tool: "kb_stamp",
8123
8400
  usage: "stamp [--bundle PATH] [--since DIGEST|FILE]",
8124
8401
  description: "Content stamp of a base \u2014 `load`'s digest, record counts, per-record digests, how many records have drifted anchors \u2014 without any bodies. Takes no bundlePath to stamp every pinned base. With `since`, reports only the bases that moved, naming the changed ids. Reads, never writes.",
8125
- input: import_zod32.z.object({
8126
- bundlePath: import_zod32.z.string().min(1).optional().describe(
8402
+ input: import_zod33.z.object({
8403
+ bundlePath: import_zod33.z.string().min(1).optional().describe(
8127
8404
  "Absolute path to one knowledge base. Omit to stamp every pinned base."
8128
8405
  ),
8129
- since: import_zod32.z.string().min(1).optional().describe(
8406
+ since: import_zod33.z.string().min(1).optional().describe(
8130
8407
  "Prior digest, or path to a prior `stamp --json`; only moved bases return, with changed ids when the baseline is a file."
8131
8408
  )
8132
8409
  }),
@@ -8212,16 +8489,16 @@ async function readBaseline(since) {
8212
8489
  }
8213
8490
 
8214
8491
  // src/commands/status.ts
8215
- var import_zod33 = require("zod");
8492
+ var import_zod34 = require("zod");
8216
8493
  var statusCommand = define({
8217
8494
  name: "status",
8218
8495
  tool: "kb_status",
8219
8496
  usage: "status <concept-id> <status>",
8220
8497
  description: "Move a record's status. Compare-and-swap: a concurrent change fails instead of being overwritten.",
8221
- input: import_zod33.z.object({
8498
+ input: import_zod34.z.object({
8222
8499
  bundlePath,
8223
8500
  conceptId,
8224
- status: import_zod33.z.enum(KB_RECORD_STATUSES)
8501
+ status: import_zod34.z.enum(KB_RECORD_STATUSES)
8225
8502
  }),
8226
8503
  fromArgv: (argv, path) => ({
8227
8504
  bundlePath: path,
@@ -8236,13 +8513,13 @@ var statusCommand = define({
8236
8513
  });
8237
8514
 
8238
8515
  // src/commands/supersede.ts
8239
- var import_zod34 = require("zod");
8516
+ var import_zod35 = require("zod");
8240
8517
  var supersedeCommand = define({
8241
8518
  name: "supersede",
8242
8519
  tool: "kb_supersede",
8243
8520
  usage: "supersede <concept-id> <replacement-id>",
8244
8521
  description: "Mark a record superseded by another, linked in both directions. Use instead of editing a record whose meaning changed.",
8245
- input: import_zod34.z.object({ bundlePath, conceptId, replacementId: conceptId }),
8522
+ input: import_zod35.z.object({ bundlePath, conceptId, replacementId: conceptId }),
8246
8523
  fromArgv: (argv, path) => ({
8247
8524
  bundlePath: path,
8248
8525
  conceptId: argv[1],
@@ -8256,7 +8533,7 @@ var supersedeCommand = define({
8256
8533
  });
8257
8534
 
8258
8535
  // src/commands/sweep.ts
8259
- var import_zod35 = require("zod");
8536
+ var import_zod36 = require("zod");
8260
8537
  var TERMINAL = [
8261
8538
  "resolved",
8262
8539
  "rejected",
@@ -8267,15 +8544,15 @@ var sweepCommand = define({
8267
8544
  tool: "kb_sweep",
8268
8545
  usage: "sweep --tag <tag> --terminal [--dry-run]",
8269
8546
  description: "Delete tagged records that are resolved, rejected or superseded. Refuses without --tag, keeps any record a surviving record still points at, and logs each deletion.",
8270
- input: import_zod35.z.object({
8547
+ input: import_zod36.z.object({
8271
8548
  bundlePath,
8272
- tag: import_zod35.z.string({ error: "sweep needs --tag: it never sweeps a whole base" }).min(1).describe("Only records carrying this tag are considered."),
8273
- terminal: import_zod35.z.literal(true, {
8549
+ tag: import_zod36.z.string({ error: "sweep needs --tag: it never sweeps a whole base" }).min(1).describe("Only records carrying this tag are considered."),
8550
+ terminal: import_zod36.z.literal(true, {
8274
8551
  error: "sweep needs --terminal: it deletes only settled records"
8275
8552
  }).describe(
8276
8553
  "Required. Names the only scope sweep deletes: resolved, rejected and superseded records."
8277
8554
  ),
8278
- dryRun: import_zod35.z.boolean().optional().describe("Report what would go, and delete nothing.")
8555
+ dryRun: import_zod36.z.boolean().optional().describe("Report what would go, and delete nothing.")
8279
8556
  }),
8280
8557
  fromArgv: (argv, path) => ({
8281
8558
  bundlePath: path,
@@ -8392,16 +8669,16 @@ function renderSweep(result) {
8392
8669
  }
8393
8670
 
8394
8671
  // src/commands/sync-instructions.ts
8395
- var import_zod36 = require("zod");
8672
+ var import_zod37 = require("zod");
8396
8673
  var syncInstructionsCommand = define({
8397
8674
  name: "sync-instructions",
8398
8675
  usage: "sync-instructions <file> [--profile NAME] [--budget N] [--full-under N]",
8399
8676
  description: "CLI-only: plant the kb_context block between sentinel comments in AGENTS.md or CLAUDE.md, idempotently.",
8400
- input: import_zod36.z.object({
8401
- file: import_zod36.z.string().min(1).describe("The instruction file to edit in place."),
8402
- budgetTokens: import_zod36.z.number().int().positive().optional(),
8403
- fullUnderTokens: import_zod36.z.number().int().positive().optional(),
8404
- profile: import_zod36.z.string().optional()
8677
+ input: import_zod37.z.object({
8678
+ file: import_zod37.z.string().min(1).describe("The instruction file to edit in place."),
8679
+ budgetTokens: import_zod37.z.number().int().positive().optional(),
8680
+ fullUnderTokens: import_zod37.z.number().int().positive().optional(),
8681
+ profile: import_zod37.z.string().optional()
8405
8682
  }),
8406
8683
  fromArgv: (argv) => {
8407
8684
  const budget = argvFlag(argv, "--budget");
@@ -8427,17 +8704,17 @@ var syncInstructionsCommand = define({
8427
8704
  });
8428
8705
 
8429
8706
  // src/commands/trace.ts
8430
- var import_zod37 = require("zod");
8707
+ var import_zod38 = require("zod");
8431
8708
  var traceCommand = define({
8432
8709
  name: "trace",
8433
8710
  tool: "kb_trace",
8434
8711
  usage: "trace <concept-id> [edges...]",
8435
8712
  description: 'Timeline of how a position was reached, ordered by write time, following supersession, shared anchors and shared sources. Includes rejected, draft and superseded records \u2014 in a history they are the content. For "why is it like this"; kb_load answers "what holds now".',
8436
- input: import_zod37.z.object({
8713
+ input: import_zod38.z.object({
8437
8714
  bundlePath,
8438
8715
  conceptId,
8439
- edges: import_zod37.z.array(import_zod37.z.enum(TRACE_EDGES)).optional(),
8440
- depth: import_zod37.z.number().int().positive().optional()
8716
+ edges: import_zod38.z.array(import_zod38.z.enum(TRACE_EDGES)).optional(),
8717
+ depth: import_zod38.z.number().int().positive().optional()
8441
8718
  }),
8442
8719
  fromArgv: (argv, path) => ({
8443
8720
  bundlePath: path,
@@ -8459,37 +8736,37 @@ var traceCommand = define({
8459
8736
  });
8460
8737
 
8461
8738
  // src/commands/types.ts
8462
- var import_zod38 = require("zod");
8739
+ var import_zod39 = require("zod");
8463
8740
  var typesCommand = define({
8464
8741
  name: "types",
8465
8742
  tool: "kb_types",
8466
8743
  usage: "types",
8467
8744
  description: "The twelve record types with their purpose, body sections, and starting status. Read this before writing rather than guessing headings \u2014 a section the type does not define is rejected.",
8468
- input: import_zod38.z.object({}),
8745
+ input: import_zod39.z.object({}),
8469
8746
  fromArgv: () => ({}),
8470
8747
  run: () => Promise.resolve(RECORD_TYPES)
8471
8748
  });
8472
8749
 
8473
8750
  // src/commands/unpin.ts
8474
- var import_zod39 = require("zod");
8751
+ var import_zod40 = require("zod");
8475
8752
  var unpinCommand = define({
8476
8753
  name: "unpin",
8477
8754
  tool: "kb_unpin",
8478
8755
  usage: "unpin [bundle-path]",
8479
8756
  description: "Remove a base from every manifest layer that holds it. Reports the layers touched.",
8480
- input: import_zod39.z.object({ bundlePath }),
8757
+ input: import_zod40.z.object({ bundlePath }),
8481
8758
  fromArgv: (argv, path) => ({ bundlePath: argv[1] ?? path }),
8482
8759
  run: (_ctx, { bundlePath: path }) => unpinBase(process.cwd(), path)
8483
8760
  });
8484
8761
 
8485
8762
  // src/commands/validate.ts
8486
- var import_zod40 = require("zod");
8763
+ var import_zod41 = require("zod");
8487
8764
  var validateCommand = define({
8488
8765
  name: "validate",
8489
8766
  tool: "kb_validate",
8490
8767
  usage: "validate",
8491
8768
  description: "Check pointers no single record can see: supersession links that disagree between the two records, typed causal links, and assumptions that cite sources. Each finding carries a severity: errors fail the exit code, warnings do not.",
8492
- input: import_zod40.z.object({ bundlePath }),
8769
+ input: import_zod41.z.object({ bundlePath }),
8493
8770
  fromArgv: (_argv, path) => ({ bundlePath: path }),
8494
8771
  run: async ({ store }, { bundlePath: path }) => validateBundle(await store.list(path)),
8495
8772
  // Warnings never fail the exit code; every other severity does.
@@ -8499,16 +8776,16 @@ var validateCommand = define({
8499
8776
  });
8500
8777
 
8501
8778
  // src/commands/verify.ts
8502
- var import_zod41 = require("zod");
8779
+ var import_zod42 = require("zod");
8503
8780
  var verifyCommand = define({
8504
8781
  name: "verify",
8505
8782
  tool: "kb_verify",
8506
8783
  usage: "verify <concept-id> --note <text>",
8507
8784
  description: "Append a verified[] event: who checked, when, and what was found. Append-only. A record's own generator is refused unless the actor is `human:`-prefixed.",
8508
- input: import_zod41.z.object({
8785
+ input: import_zod42.z.object({
8509
8786
  bundlePath,
8510
8787
  conceptId,
8511
- note: import_zod41.z.string().refine((s) => s.trim().length > 0, {
8788
+ note: import_zod42.z.string().refine((s) => s.trim().length > 0, {
8512
8789
  message: "note must say what the check found"
8513
8790
  })
8514
8791
  }),
@@ -8528,15 +8805,15 @@ var verifyCommand = define({
8528
8805
  });
8529
8806
 
8530
8807
  // src/commands/write.ts
8531
- var import_zod42 = require("zod");
8808
+ var import_zod43 = require("zod");
8532
8809
  var writeCommand = define({
8533
8810
  name: "write",
8534
8811
  tool: "kb_write",
8535
8812
  usage: "write <type> < record.json",
8536
8813
  description: "Write one record. Search first \u2014 a duplicate concept id is rejected, not overwritten; kb_types lists each type's sections. An unsourced claim is an `assumption` with assumption: true, never a vague `fact`. Conflicting records get a `risk`, `open-question`, or superseding `decision`. Prefer a new short record over overloading one. Never delete; supersede.",
8537
- input: import_zod42.z.object({
8814
+ input: import_zod43.z.object({
8538
8815
  bundlePath,
8539
- type: import_zod42.z.enum(KB_RECORD_TYPES),
8816
+ type: import_zod43.z.enum(KB_RECORD_TYPES),
8540
8817
  input: composeInputSchema
8541
8818
  }),
8542
8819
  fromArgv: async (argv, path, stdin) => ({
@@ -8560,13 +8837,13 @@ var writeCommand = define({
8560
8837
  });
8561
8838
 
8562
8839
  // src/commands/write-decision.ts
8563
- var import_zod43 = require("zod");
8840
+ var import_zod44 = require("zod");
8564
8841
  var writeDecisionCommand = define({
8565
8842
  name: "write-decision",
8566
8843
  tool: "kb_write_decision",
8567
8844
  usage: "write-decision < decision.json",
8568
8845
  description: "Write a decision, with `alternative` (what was rejected and why) and `impact` as fields. Record one when a later reader would otherwise simplify the constraint away; skip when the diff already answers it. `sources` for material read, `anchors` for code, `relatedConceptIds` for records.",
8569
- input: import_zod43.z.object({ bundlePath, input: decisionInputSchema }),
8846
+ input: import_zod44.z.object({ bundlePath, input: decisionInputSchema }),
8570
8847
  fromArgv: async (_argv, path, stdin) => ({
8571
8848
  bundlePath: path,
8572
8849
  input: JSON.parse(await stdin())
@@ -8596,6 +8873,7 @@ var KB_COMMANDS = [
8596
8873
  answerCommand,
8597
8874
  verifyCommand,
8598
8875
  anchorResolveCommand,
8876
+ anchorSetCommand,
8599
8877
  reassessCommand,
8600
8878
  promoteCommand,
8601
8879
  loadCommand,
@@ -8632,7 +8910,7 @@ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
8632
8910
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
8633
8911
 
8634
8912
  // src/version.ts
8635
- var VERSION = true ? "0.1.20" : "0.0.0-dev";
8913
+ var VERSION = true ? "0.1.22" : "0.0.0-dev";
8636
8914
 
8637
8915
  // src/mcp.ts
8638
8916
  function createKbMcpServer() {
@@ -8813,6 +9091,7 @@ function usage() {
8813
9091
  KB_RECORD_STATUSES,
8814
9092
  KB_RECORD_TYPES,
8815
9093
  KB_SLUG_PATTERN,
9094
+ KbAnchorSetDuplicateError,
8816
9095
  KbBaseFrozenError,
8817
9096
  KbClassifyInputError,
8818
9097
  KbInvalidConceptIdError,
@@ -8843,6 +9122,8 @@ function usage() {
8843
9122
  adjudicate,
8844
9123
  anchorFilePath,
8845
9124
  anchorOnHunk,
9125
+ anchorSetInputSchema,
9126
+ applyAnchorSet,
8846
9127
  assertBaseNotFrozen,
8847
9128
  backlinks,
8848
9129
  buildContext,
@@ -8876,19 +9157,23 @@ function usage() {
8876
9157
  isNoDecisionRecord,
8877
9158
  isReviewTag,
8878
9159
  kbActorStampSchema,
9160
+ kbAnchorLocatorSchema,
8879
9161
  kbAnchorSchema,
8880
9162
  kbAnchorSpanSchema,
8881
9163
  kbAnchorWriteSchema,
8882
9164
  kbConceptIdSchema,
8883
9165
  kbJsonSchemas,
8884
9166
  kbLinkSchema,
9167
+ kbLogAnchorChangeSchema,
8885
9168
  kbLogEntrySchema,
9169
+ kbLogEntryWriteSchema,
8886
9170
  kbRecordFrontmatterSchema,
8887
9171
  kbSourceSchema,
8888
9172
  kbVerifiedEventSchema,
8889
9173
  languageForFile,
8890
9174
  listPins,
8891
9175
  loadQmd,
9176
+ locatorOf,
8892
9177
  matchToDiff,
8893
9178
  matchesTags,
8894
9179
  mergedContextBudgets,