@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/dist/cli-main.cjs CHANGED
@@ -2348,6 +2348,38 @@ var KbSelfVerificationError = class extends BaseError {
2348
2348
  actor;
2349
2349
  generatedBy;
2350
2350
  };
2351
+ var KbInvalidActorError = class extends BaseError {
2352
+ constructor(actor, reason) {
2353
+ super({
2354
+ message: `kb: actor ${JSON.stringify(actor)} ${reason} \u2014 set STRAUSS_KB_ACTOR, e.g. human:alice or agent:reviewer`,
2355
+ errorType: "KbInvalidActor" /* KbInvalidActor */,
2356
+ code: 400,
2357
+ fault: "User" /* User */,
2358
+ retriable: false,
2359
+ reportToUser: true,
2360
+ details: { actor, reason, action: "refused" }
2361
+ });
2362
+ this.actor = actor;
2363
+ this.reason = reason;
2364
+ }
2365
+ actor;
2366
+ reason;
2367
+ };
2368
+ var KbFlagConflictError = class extends BaseError {
2369
+ constructor(flags) {
2370
+ super({
2371
+ message: `kb: ${flags.join(" and ")} cannot be combined`,
2372
+ errorType: "KbFlagConflict" /* KbFlagConflict */,
2373
+ code: 400,
2374
+ fault: "User" /* User */,
2375
+ retriable: false,
2376
+ reportToUser: true,
2377
+ details: { flags }
2378
+ });
2379
+ this.flags = flags;
2380
+ }
2381
+ flags;
2382
+ };
2351
2383
  var KbPackBudgetExceededError = class extends BaseError {
2352
2384
  constructor(recordCount, approxTokens2, budgetTokens, excluded) {
2353
2385
  super({
@@ -2891,19 +2923,11 @@ function argvPositional(argv, ...names) {
2891
2923
  }
2892
2924
 
2893
2925
  // src/commands/anchor-resolve.ts
2894
- function resolverSummary(results) {
2895
- const names = [
2896
- ...new Set(
2897
- results.flatMap((entry) => entry.resolver ? [entry.resolver] : [])
2898
- )
2899
- ].sort();
2900
- return names.length ? `${names.join(" + ")} resolver` : "whole-file";
2901
- }
2902
2926
  var anchorResolveCommand = define({
2903
2927
  name: "anchor-resolve",
2904
2928
  tool: "kb_anchor_resolve",
2905
- usage: "anchor-resolve <concept-id> [--repo-root <path>] [--offline] [--rebaseline] [--restamp]",
2906
- 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.",
2929
+ usage: "anchor-resolve <concept-id> [--repo-root <path>] [--offline] [--rebaseline] [--restamp] [--check]",
2930
+ 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.",
2907
2931
  input: import_zod7.z.object({
2908
2932
  bundlePath,
2909
2933
  conceptId,
@@ -2916,6 +2940,9 @@ var anchorResolveCommand = define({
2916
2940
  ),
2917
2941
  restamp: import_zod7.z.boolean().optional().describe(
2918
2942
  "Refresh `resolved_at` on anchors that already match. Off by default, so a green run writes nothing."
2943
+ ),
2944
+ check: import_zod7.z.boolean().optional().describe(
2945
+ "Resolve and report only: no hash, no `resolved_at`, no log entry."
2919
2946
  )
2920
2947
  }),
2921
2948
  fromArgv: (argv, path) => ({
@@ -2924,9 +2951,24 @@ var anchorResolveCommand = define({
2924
2951
  repoRoot: argvFlag(argv, "--repo-root"),
2925
2952
  offline: argv.includes("--offline"),
2926
2953
  rebaseline: argv.includes("--rebaseline"),
2927
- restamp: argv.includes("--restamp")
2954
+ restamp: argv.includes("--restamp"),
2955
+ check: argv.includes("--check")
2928
2956
  }),
2929
- run: async ({ store, actor, now }, { bundlePath: path, conceptId: id, repoRoot, offline, rebaseline, restamp }) => {
2957
+ run: async ({ store, actor, now }, {
2958
+ bundlePath: path,
2959
+ conceptId: id,
2960
+ repoRoot,
2961
+ offline,
2962
+ rebaseline,
2963
+ restamp,
2964
+ check
2965
+ }) => {
2966
+ if (check && (rebaseline || restamp)) {
2967
+ throw new KbFlagConflictError([
2968
+ "check",
2969
+ rebaseline ? "rebaseline" : "restamp"
2970
+ ]);
2971
+ }
2930
2972
  const root = repoRoot ?? process.cwd();
2931
2973
  const record = await store.read(path, id);
2932
2974
  if (!record) throw new KbRecordNotFoundError(id);
@@ -2935,7 +2977,6 @@ var anchorResolveCommand = define({
2935
2977
  return {
2936
2978
  conceptId: id,
2937
2979
  results: [],
2938
- verified: false,
2939
2980
  note: "record has no anchors"
2940
2981
  };
2941
2982
  }
@@ -2993,7 +3034,7 @@ var anchorResolveCommand = define({
2993
3034
  if (!anchor.hash) {
2994
3035
  results.push({
2995
3036
  ...base2,
2996
- state: "stamped",
3037
+ state: check ? "unstamped" : "stamped",
2997
3038
  currentHash: stampedHash,
2998
3039
  hashKind: stampedKind,
2999
3040
  ...producedBy ? { resolver: producedBy } : {}
@@ -3045,7 +3086,7 @@ var anchorResolveCommand = define({
3045
3086
  if (refresh) dirty = true;
3046
3087
  }
3047
3088
  let frozen = false;
3048
- if (dirty) {
3089
+ if (dirty && !check) {
3049
3090
  try {
3050
3091
  await assertBaseNotFrozen(process.cwd(), path);
3051
3092
  } catch (error) {
@@ -3060,42 +3101,11 @@ var anchorResolveCommand = define({
3060
3101
  const unreachable = results.filter(
3061
3102
  (entry) => isUncheckedReason(entry.reason)
3062
3103
  ).length;
3063
- const checked = results.length - unreachable;
3064
3104
  const matches3 = results.filter((entry) => entry.state === "match").length;
3065
- const note = `${matches3}/${checked} anchors match${unreachable ? `, ${unreachable} unreachable` : ""}`;
3066
- const clean = checked > 0 && matches3 === checked && unreachable === 0;
3067
- if (clean) {
3068
- try {
3069
- await store.verify(
3070
- path,
3071
- id,
3072
- `anchor-resolve: ${note} (${resolverSummary(results)})`,
3073
- actor,
3074
- now()
3075
- );
3076
- } catch (error) {
3077
- if (!(error instanceof KbSelfVerificationError)) throw error;
3078
- return {
3079
- conceptId: id,
3080
- results,
3081
- verified: false,
3082
- verifyRefused: "self-verification",
3083
- ...frozenNote,
3084
- ...hintNote
3085
- };
3086
- }
3087
- return {
3088
- conceptId: id,
3089
- results,
3090
- verified: true,
3091
- ...frozenNote,
3092
- ...hintNote
3093
- };
3094
- }
3105
+ const note = `${matches3}/${results.length - unreachable} anchors match, ${unreachable} unreachable`;
3095
3106
  return {
3096
3107
  conceptId: id,
3097
3108
  results,
3098
- verified: false,
3099
3109
  ...unreachable ? { note } : {},
3100
3110
  ...frozenNote,
3101
3111
  ...hintNote
@@ -7652,6 +7662,7 @@ var KbStore = class {
7652
7662
  * its contents.
7653
7663
  */
7654
7664
  async write(bundlePath2, input, actor = "unknown") {
7665
+ assertActor(actor);
7655
7666
  if (!KB_SLUG_PATTERN.test(input.slug)) {
7656
7667
  throw new KbInvalidConceptIdError("slug must be kebab-case", {
7657
7668
  slug: input.slug
@@ -7753,6 +7764,7 @@ var KbStore = class {
7753
7764
  * timeouts.
7754
7765
  */
7755
7766
  async setStatus(bundlePath2, conceptId2, status, actor = "unknown") {
7767
+ assertActor(actor);
7756
7768
  return this.mutate(
7757
7769
  bundlePath2,
7758
7770
  conceptId2,
@@ -7771,6 +7783,7 @@ var KbStore = class {
7771
7783
  * the frontmatter must not be published back out under an actor stamp.
7772
7784
  */
7773
7785
  async updateAnchors(bundlePath2, conceptId2, anchors, actor = "unknown") {
7786
+ assertActor(actor);
7774
7787
  const checked = anchors.map((anchor) => kbAnchorWriteSchema.parse(anchor));
7775
7788
  return this.mutate(
7776
7789
  bundlePath2,
@@ -7791,6 +7804,7 @@ var KbStore = class {
7791
7804
  * logs what it publishes.
7792
7805
  */
7793
7806
  async verify(bundlePath2, conceptId2, note, actor = "unknown", at2 = (/* @__PURE__ */ new Date()).toISOString()) {
7807
+ assertActor(actor, { named: true });
7794
7808
  const event = kbVerifiedEventSchema.parse({ by: actor, at: at2, note });
7795
7809
  const existing = await this.read(bundlePath2, conceptId2);
7796
7810
  if (!existing) throw new KbRecordNotFoundError(conceptId2);
@@ -7821,6 +7835,7 @@ var KbStore = class {
7821
7835
  * in normal use, and validation drops to catching hand-edits.
7822
7836
  */
7823
7837
  async supersede(bundlePath2, conceptId2, replacementId, actor = "unknown") {
7838
+ assertActor(actor);
7824
7839
  const replacement = await this.read(bundlePath2, replacementId);
7825
7840
  if (!replacement) throw new KbRecordNotFoundError(replacementId);
7826
7841
  const superseded = await this.markSuperseded(
@@ -7844,6 +7859,7 @@ var KbStore = class {
7844
7859
  }
7845
7860
  /** Resolves an open question, stamping who answered and when. */
7846
7861
  async answer(bundlePath2, conceptId2, answer, actor = "unknown", at2 = (/* @__PURE__ */ new Date()).toISOString()) {
7862
+ assertActor(actor);
7847
7863
  return this.mutate(
7848
7864
  bundlePath2,
7849
7865
  conceptId2,
@@ -7870,6 +7886,7 @@ ${answer}
7870
7886
  * terminal status since the caller listed it is reported, not removed.
7871
7887
  */
7872
7888
  async deleteRecord(bundlePath2, conceptId2, expected, actor = "unknown") {
7889
+ assertActor(actor);
7873
7890
  const target = this.recordPath(bundlePath2, conceptId2);
7874
7891
  const witness = await this.read(bundlePath2, conceptId2);
7875
7892
  if (!witness) throw new KbRecordNotFoundError(conceptId2);
@@ -8173,6 +8190,7 @@ ${answer}
8173
8190
  * base too, where nothing was written.
8174
8191
  */
8175
8192
  async note(bundlePath2, entry) {
8193
+ assertActor(entry.by);
8176
8194
  await this.record(this.root(bundlePath2), entry);
8177
8195
  }
8178
8196
  /**
@@ -8430,9 +8448,18 @@ function normalizeActor(id) {
8430
8448
  if (colon === -1) return id.toLowerCase();
8431
8449
  return id.slice(0, colon + 1).toLowerCase() + id.slice(colon + 1);
8432
8450
  }
8451
+ var KB_ACTOR_PATTERN = /^[A-Za-z][\w-]*(?::[\p{L}\p{M}\p{N}_.@+/-]+)?$/u;
8452
+ function assertActor(actor, { named = false } = {}) {
8453
+ if (!KB_ACTOR_PATTERN.test(actor)) {
8454
+ throw new KbInvalidActorError(actor, "is not kind or kind:name");
8455
+ }
8456
+ if (named && normalizeActor(actor) === "unknown") {
8457
+ throw new KbInvalidActorError(actor, "cannot verify: name who checked");
8458
+ }
8459
+ }
8433
8460
 
8434
8461
  // src/version.ts
8435
- var VERSION = true ? "0.1.20" : "0.0.0-dev";
8462
+ var VERSION = true ? "0.1.21" : "0.0.0-dev";
8436
8463
 
8437
8464
  // src/cli.ts
8438
8465
  async function runKbCli(argv) {