@panaversity/ksor 0.0.59 → 0.0.60

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/CHANGELOG.md CHANGED
@@ -1,5 +1,80 @@
1
1
  # @panaversity/ksor
2
2
 
3
+ ## 0.0.60
4
+
5
+ ### Patch Changes
6
+
7
+ - 2a7ef92: Five things a live walk of the published 0.0.59 found, fixed.
8
+
9
+ **`verify.mjs` reported ordinary markdown as an invented name.** Its name regex
10
+ let `\s+` cross a blank line, so a `## Meals` heading followed by a paragraph
11
+ opening `On travel…` was extracted as the name "Meals On" and reported as
12
+ changed-or-introduced. That fires on the first document an agent converts —
13
+ the check meant to make conversion trustworthy was crying wolf. Names are now
14
+ capitalised words on ONE line; a name the source never mentions is still caught.
15
+
16
+ **A scaffold followed verbatim published its first generation untraceably.**
17
+ `ksor init` runs `git init` and leaves zero commits, and its own epilogue went
18
+ install → dev → provision → refresh with no commit in between — so every
19
+ adopter's first publish said `source: unspecified` and skipped the R23
20
+ change-control check, which had no history to compare against. The epilogue now
21
+ says to commit before publishing, which is the whole fix.
22
+
23
+ **A plain build left a stale bundle tree unmentioned.** `ksor build` recomputes
24
+ every `bundles[].sha256` in the lock but only `--bundles` writes the directory,
25
+ so after one `--bundles` run and any ordinary build the lock claimed a digest
26
+ nothing on disk produced, while the tree that exists to be SENT somewhere aged
27
+ silently. A plain build now says which build the directory came from and how to
28
+ refresh it. Reported, never deleted: it is the adopter's output and may be
29
+ mid-handover.
30
+
31
+ **Tutorial 01's first build block was one line short.** R23 landed the day after
32
+ that walk, so the shipped block omitted `change-control: not checked` on a page
33
+ whose headline claim is that every output was pasted as it appeared. Re-captured
34
+ on 0.0.59, with a paragraph on what both honesty lines mean and when they go.
35
+
36
+ **The dev server's `/llms.txt` does not change after an approval.** Not a bug and
37
+ not fixable in the route: `output: "export"` requires a static route handler, so
38
+ `pnpm dev` computes it once per process while the document's page beside it
39
+ updates. Stated in the emitted README's troubleshooting table and in the tutorial
40
+ step whose own prompt is "Why isn't my refund policy in llms.txt?" — verified by
41
+ testing both alternatives, each of which breaks the export.
42
+
43
+ - 6c47618: Measure WHICH skill a real agent reaches for, and record what the first sweeps
44
+ found.
45
+
46
+ A live walk of the published 0.0.59 reported that `add-sources` did not fire on
47
+ its headline prompt. The tempting repair is to reword the description until it
48
+ does, which is a guess. This is the instrument that replaces the guess: N runs
49
+ per phrase, in a fresh scaffold with all three skills present, graded on which
50
+ skill the agent actually invoked, across more than one model. Reported, never
51
+ gating — a model is stochastic and a threshold over a handful of runs flakes.
52
+
53
+ **The model is a column, because the answer depends on it.** Same phrase, same
54
+ scaffold, same harness: `claude-sonnet-5` fired `add-sources` 3/3 where
55
+ `claude-opus-5` fired nothing 0/2. The walk used the CLI default and the first
56
+ probe pinned Sonnet, which is why they disagreed — neither was wrong, and
57
+ neither alone measured the trigger.
58
+
59
+ **The finding is narrower than the walk suggested.** `add-sources` fires on four
60
+ of five phrases on both models, and both controls behave: a different skill wins
61
+ the intake phrase, and nothing fires on a question about the repo itself. It
62
+ misses exactly one shape on Opus — the owner pointing at a file already in the
63
+ repo and naming a destination.
64
+
65
+ **And the obvious repair does not work.** Naming that shape in the description
66
+ was tried and measured: unchanged at 0/3. So the cause is not the wording — an
67
+ instruction concrete enough to act on gets acted on, and no skill is consulted.
68
+ The clause was reverted rather than kept, because it is resident context in
69
+ every session and bought nothing a measurement can see. The negative result is
70
+ recorded beside the rows it explains.
71
+
72
+ Also fixed while building it: the CLI can emit raw control characters inside its
73
+ JSON, which `JSON.parse` rejects outright — the harness lost the whole transcript
74
+ to a stray byte. It now falls back to a scrubbed parse.
75
+
76
+ Test infrastructure only; nothing an adopter installs behaves differently.
77
+
3
78
  ## 0.0.59
4
79
 
5
80
  ### Patch Changes
package/dist/cli.mjs CHANGED
@@ -12216,7 +12216,7 @@ function runBuild(args, cwd, io, options) {
12216
12216
  io.out(`ksor build: ${lock.documents.length} document(s), ${admitted} admitted to a machine surface at ${lock.as_of}\n${provenanceLine({
12217
12217
  ...facts,
12218
12218
  dirty: lock.dirty
12219
- }, root)}\n` + (change.notice === null ? "" : ` ${change.notice}\n`) + notice + `${pendingIndexes.map((w) => ` wrote ${w}\n`).join("")}${staleIndexes.map((r) => ` removed ${r} (its directory earns no index)\n`).join("")}` + (parsed.bundles ? bundlesReport(bundles, viewers) : "") + ` wrote build.lock.json — build_id ${lock.build_id}\n`);
12219
+ }, root)}\n` + (change.notice === null ? "" : ` ${change.notice}\n`) + notice + `${pendingIndexes.map((w) => ` wrote ${w}\n`).join("")}${staleIndexes.map((r) => ` removed ${r} (its directory earns no index)\n`).join("")}` + (parsed.bundles ? bundlesReport(bundles, viewers) : staleBundlesNotice(root, lock.build_id)) + ` wrote build.lock.json — build_id ${lock.build_id}\n`);
12220
12220
  return 0;
12221
12221
  }
12222
12222
  /**
@@ -12241,6 +12241,35 @@ function writeBundles(root, bundles, lockText) {
12241
12241
  }
12242
12242
  writeFileSync(path.join(out, LOCK_NAME), lockText);
12243
12243
  }
12244
+ /**
12245
+ * `.ksor/out/bundles/` from an EARLIER build, when this run did not write it.
12246
+ *
12247
+ * A plain `ksor build` recomputes every `bundles[].sha256` in the root lock —
12248
+ * they are a function of what `build_id` already hashes — but it does not
12249
+ * touch the directory, which only `--bundles` writes. So after one
12250
+ * `--bundles` run and any ordinary build, the root lock claims a digest that
12251
+ * nothing on disk produces, and the tree that exists to be SENT somewhere goes
12252
+ * on aging with nothing said (found by walking the published 0.0.59).
12253
+ *
12254
+ * Reported, never deleted and never refused: the directory is the adopter's
12255
+ * output, its co-located lock still describes its own bytes coherently, and a
12256
+ * build that removed it would destroy something an adopter may be mid-way
12257
+ * through handing over. Honest absence, never silent weakness.
12258
+ */
12259
+ function staleBundlesNotice(root, buildId) {
12260
+ const beside = path.join(root, BUNDLES_DIR, LOCK_NAME);
12261
+ if (!existsSync(beside)) return "";
12262
+ let wrote;
12263
+ try {
12264
+ wrote = JSON.parse(readFileSync(beside, "utf8")).build_id;
12265
+ } catch {
12266
+ wrote = "unreadable";
12267
+ }
12268
+ if (wrote === buildId) return "";
12269
+ return ` bundles: ${BUNDLES_DIR}/ is from build ${wrote}, not this one — the lock records this build's digests, so what is on disk no longer matches them
12270
+ fix: re-run with \`--bundles\` before sending them, or delete the directory
12271
+ `;
12272
+ }
12244
12273
  /** One line per bundle written, and a line per link it carries to a concept it excludes. */
12245
12274
  function bundlesReport(bundles, viewers) {
12246
12275
  let text = "";
@@ -14420,6 +14449,9 @@ function handoff(io, name, targetWasDot, manager) {
14420
14449
  io.out(`${name} is ready — your knowledge, your repo, yours outright.\n
14421
14450
  Next (or just tell your coding agent to take it from here):
14422
14451
  ` + enter + ` ${install}\n ${run("dev").padEnd(15)} # the site, live at http://localhost:3000\n
14452
+ Commit before you publish — a build traces to a reviewed commit:
14453
+ git add -A && git commit -m 'Scaffold the record'
14454
+
14423
14455
  Then, for the agent surface (needs Postgres and a provider key):
14424
14456
  ${run("provision").padEnd(15)} # once: copy .env.example to .env and set KSOR_DB_URL,\n # then apply the schema
14425
14457
  ${run("refresh").padEnd(15)} # PUBLISH the record — ingest knowledge/ into a generation\n ${run("serve").padEnd(15)} # the MCP server, over what you just published\n
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@panaversity/ksor",
3
- "version": "0.0.59",
3
+ "version": "0.0.60",
4
4
  "description": "Knowledge System of Record — compile governed markdown into a static site for people and an MCP server for AI agents, with citations and measured abstention.",
5
5
  "keywords": [
6
6
  "abstention",
@@ -38,7 +38,12 @@ const body = raw.replace(/^---\n[\s\S]*?\n---\n?/, "").replace(/\[\^[^\]]+\]:?/g
38
38
 
39
39
  const tokens = new Set();
40
40
  for (const m of body.matchAll(/\d[\d,.:/-]*\d|\d/g)) tokens.add(m[0]);
41
- for (const m of body.matchAll(/\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+)+\b/g)) tokens.add(m[0]);
41
+ // A name is capitalised words on ONE line. `\s+` here would cross a blank
42
+ // line, so a `## Meals` heading followed by a paragraph opening `On travel…`
43
+ // was extracted as the name "Meals On" and reported as invented — a false
44
+ // positive on ordinary markdown, hit on the first document an agent writes
45
+ // (found by walking the published 0.0.59).
46
+ for (const m of body.matchAll(/\b[A-Z][a-z]+(?:[^\S\r\n]+[A-Z][a-z]+)+\b/g)) tokens.add(m[0]);
42
47
 
43
48
  const missing = [...tokens].filter((t) => !extraction.includes(fold(t))).sort();
44
49
  for (const t of missing) console.log(t);
@@ -38,7 +38,12 @@ const body = raw.replace(/^---\n[\s\S]*?\n---\n?/, "").replace(/\[\^[^\]]+\]:?/g
38
38
 
39
39
  const tokens = new Set();
40
40
  for (const m of body.matchAll(/\d[\d,.:/-]*\d|\d/g)) tokens.add(m[0]);
41
- for (const m of body.matchAll(/\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+)+\b/g)) tokens.add(m[0]);
41
+ // A name is capitalised words on ONE line. `\s+` here would cross a blank
42
+ // line, so a `## Meals` heading followed by a paragraph opening `On travel…`
43
+ // was extracted as the name "Meals On" and reported as invented — a false
44
+ // positive on ordinary markdown, hit on the first document an agent writes
45
+ // (found by walking the published 0.0.59).
46
+ for (const m of body.matchAll(/\b[A-Z][a-z]+(?:[^\S\r\n]+[A-Z][a-z]+)+\b/g)) tokens.add(m[0]);
42
47
 
43
48
  const missing = [...tokens].filter((t) => !extraction.includes(fold(t))).sort();
44
49
  for (const t of missing) console.log(t);
@@ -724,6 +724,7 @@ map rather than a substitute.
724
724
  | the agent answers questions 2 and 3 instead of declining | no floor is measured, so the gate is off (`abstain OFF`, `gate: "off"`) — step 3's `calibrate` was skipped | `pnpm exec ksor calibrate --instance instance.md`, paste the block, restart |
725
725
  | a deployed door serves an empty record | deploying does not publish — and a laptop DSN is unreachable from the host | point both at one hosted Postgres, then `pnpm refresh` |
726
726
  | the home page and `/llms.txt` are empty | every document is still a draft — correct, not broken | approve one and rebuild |
727
+ | `/llms.txt` on the DEV server does not change after you approve | `output: "export"` requires its route handler to be static, so `pnpm dev` computes it once per process — the document page beside it updates, that file does not | restart `pnpm dev`; the built site is always current, so this is a dev-server artefact only |
727
728
  | `ksor-record-empty` | every document was deleted — a record is never empty, so nothing was written | add one document of your own (or restore one from git) before deleting the last starter |
728
729
  | `ksor-approver-unauthorised` | a document is approved by an actor `.ksor/governance.yaml` no longer names — usually `human:you` after the interview | re-attribute the approval to your handle, or restore the actor to `approval_authorities` |
729
730
  | `ksor-generated-stale` at `pnpm build` or `pnpm refresh` | a `stable` document's body changed since a commit where it was stable, and `generated.at` was not advanced past that commit's (left alone, or moved backward) — the stamp dates the text, and `pnpm check` reads no document history, so it passed | set `generated.at` to an instant after the edit and re-approve (`ksor.approval.at` may not precede it) |