@panaversity/ksor 0.0.35 → 0.0.37

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,37 @@
1
1
  # @panaversity/ksor
2
2
 
3
+ ## 0.0.37
4
+
5
+ ### Patch Changes
6
+
7
+ - 3eafb25: The package README states the KSoR architecture: one governed record — Markdown in the KSoR Profile of the Open Knowledge Format (OKF) — behind one governance boundary, projected through open standards.
8
+
9
+ ## 0.0.36
10
+
11
+ ### Patch Changes
12
+
13
+ - 617dc46: The scaffold meets your package manager
14
+
15
+ `ksor init` now emits the scaffold for the manager that ran it: `npx
16
+ @panaversity/ksor init` produces an npm project, `bunx` a bun one, `pnpm dlx`
17
+ (or anything unrecognized) the pnpm shape every scaffold got before. Node stays
18
+ the one prerequisite — nobody installs a second package manager to open their
19
+ own knowledge base (issue #28).
20
+
21
+ The whole scaffold speaks the detected manager: scripts, README, AGENTS.md, the
22
+ agent kit, the CLI's own handoff text. npm and bun scaffolds declare
23
+ `workspaces` in the manifest and ship no lockfile — the pinned CLI version
24
+ cannot be pre-resolved into one, so the first install writes it and the README
25
+ says to commit it. The install-script denial carries over (`.npmrc` with
26
+ `ignore-scripts=true` for npm; bun refuses dependency lifecycle scripts by
27
+ default). What npm and bun cannot offer is pnpm's 48-hour quarantine on newly
28
+ published dependency versions — the emitted scaffold discloses that instead of
29
+ staying silent about it.
30
+
31
+ Each manager's shape was proven end to end before shipping — install, `ksor`
32
+ bin resolution, format checker, full static site build — and CI now walks npm
33
+ and bun scaffolds on every change.
34
+
3
35
  ## 0.0.35
4
36
 
5
37
  ### Patch Changes
package/README.md CHANGED
@@ -56,6 +56,12 @@ A quiz whose answers are guessable is refused by the build, and because ingest
56
56
  creates no node for an attachment, a quiz's answer key can never reach the
57
57
  agent surface at all.
58
58
 
59
+ The architecture: **one governed record** — Markdown in the KSoR Profile of
60
+ the Open Knowledge Format (OKF) — behind **one governance boundary**,
61
+ projected through open standards: MCP for agents, `llms.txt` for AI
62
+ discovery, OAuth/OIDC for identity, SLSA/Sigstore for publication integrity,
63
+ OpenTelemetry for observability.
64
+
59
65
  Full concept, design goals, and project status:
60
66
  **<https://github.com/panaversity/ksor>**
61
67
 
package/dist/cli.mjs CHANGED
@@ -6723,6 +6723,152 @@ function isEnvironmentError(value) {
6723
6723
  return code !== null && ENVIRONMENT_CODES.has(code);
6724
6724
  }
6725
6725
  //#endregion
6726
+ //#region src/init/manager.ts
6727
+ /**
6728
+ * Which manager spawned this process, from `npm_config_user_agent`
6729
+ * (e.g. "pnpm/11.22.0 npm/? node/v24.5.0 darwin arm64"). The first token
6730
+ * names the manager; only managers we emit a scaffold for are recognized.
6731
+ */
6732
+ function detectManager(userAgent) {
6733
+ const head = (userAgent ?? "").split("/")[0]?.trim();
6734
+ if (head === "npm") return "npm";
6735
+ if (head === "bun") return "bun";
6736
+ return "pnpm";
6737
+ }
6738
+ /** Template files that belong to exactly one manager's scaffold. */
6739
+ function isSkippedFor(templateName, manager) {
6740
+ if (manager === "pnpm") return false;
6741
+ return templateName === "pnpm-workspace.yaml" || templateName === "pnpm-lock.yaml";
6742
+ }
6743
+ /**
6744
+ * The workspace globs, shared by every manager. pnpm reads them from
6745
+ * pnpm-workspace.yaml; npm and bun read a `workspaces` field. One constant so
6746
+ * the two spellings cannot drift.
6747
+ */
6748
+ const WORKSPACE_GLOBS = [
6749
+ "system/site",
6750
+ "system/gateways/*",
6751
+ "system/packages/*"
6752
+ ];
6753
+ /**
6754
+ * The root scripts, per manager. pnpm's are the template's own bytes; npm and
6755
+ * bun REPLACE the manager-specific bodies and inherit everything else.
6756
+ * npm: `--prefix` is npm's spelling of "run it over there".
6757
+ * bun: cd-chains — see the module comment for why not `--cwd`.
6758
+ */
6759
+ const SCRIPT_BODIES = {
6760
+ npm: {
6761
+ dev: "npm --prefix system/site run dev",
6762
+ build: "npm run export-denylist && npm --prefix system/site run build",
6763
+ provision: "npm run schema && npm run grant",
6764
+ refresh: "npm run ingest && npm run gc"
6765
+ },
6766
+ bun: {
6767
+ dev: "cd system/site && bun run dev",
6768
+ build: "bun run export-denylist && cd system/site && bun run build",
6769
+ provision: "bun run schema && bun run grant",
6770
+ refresh: "bun run ingest && bun run gc"
6771
+ }
6772
+ };
6773
+ /**
6774
+ * Rewrite the scaffold's root package.json for the manager. Structured — a
6775
+ * JSON transform, never string surgery — because the manifest is the one
6776
+ * file where a half-applied spelling map would still parse and then lie.
6777
+ */
6778
+ function transformManifest(source, manager) {
6779
+ if (manager === "pnpm") return source;
6780
+ const parsed = JSON.parse(source);
6781
+ const { packageManager: _dropped, ...rest } = parsed;
6782
+ const out = {
6783
+ ...rest,
6784
+ scripts: {
6785
+ ...parsed.scripts,
6786
+ ...SCRIPT_BODIES[manager]
6787
+ },
6788
+ workspaces: [...WORKSPACE_GLOBS]
6789
+ };
6790
+ return `${JSON.stringify(out, null, 2)}\n`;
6791
+ }
6792
+ /**
6793
+ * Ordered prose translation, longest spelling first so `pnpm install` is
6794
+ * never half-eaten by a shorter rule. Applied to every emitted text file
6795
+ * except package.json (structured above). The conformance test asserts ZERO
6796
+ * surviving "pnpm" tokens outside the quarantine disclosure, so a template
6797
+ * edit that adds a spelling this map misses goes red instead of shipping an
6798
+ * instruction the adopter cannot run.
6799
+ */
6800
+ const SCRIPT_NAMES = [
6801
+ "dev",
6802
+ "build",
6803
+ "check",
6804
+ "serve",
6805
+ "provision",
6806
+ "refresh",
6807
+ "schema",
6808
+ "grant",
6809
+ "ingest",
6810
+ "gc",
6811
+ "export-denylist"
6812
+ ];
6813
+ function spellings(manager) {
6814
+ const run = (script) => manager === "npm" ? `npm run ${script}` : `bun run ${script}`;
6815
+ const pairs = [
6816
+ ["pnpm install --no-frozen-lockfile", manager === "npm" ? "npm install" : "bun install"],
6817
+ ["pnpm install", manager === "npm" ? "npm install" : "bun install"],
6818
+ ["pnpm exec ksor", manager === "npm" ? "npx ksor" : "bunx ksor"],
6819
+ ["pnpm dlx", manager === "npm" ? "npx" : "bunx"],
6820
+ ["pnpm add -D", manager === "npm" ? "npm i -D" : "bun add -d"],
6821
+ ["pnpm -C system/site", manager === "npm" ? "npm --prefix system/site run" : "cd system/site && bun run"]
6822
+ ];
6823
+ for (const script of SCRIPT_NAMES) pairs.push([`pnpm ${script}`, run(script)]);
6824
+ return pairs;
6825
+ }
6826
+ /**
6827
+ * Manager-conditional blocks in markdown templates:
6828
+ *
6829
+ * <!-- ksor:pm pnpm npm -->
6830
+ * ...lines kept only for those managers...
6831
+ * <!-- /ksor:pm -->
6832
+ *
6833
+ * The marker lines themselves never survive into any scaffold, so the pnpm
6834
+ * output stays exactly what an adopter always got.
6835
+ */
6836
+ const BLOCK_OPEN = /^[ \t]*<!-- ksor:pm ([a-z ]+?) -->[ \t]*$/;
6837
+ const BLOCK_CLOSE = /^[ \t]*<!-- \/ksor:pm -->[ \t]*$/;
6838
+ function applyProse(text, manager) {
6839
+ const lines = text.split("\n");
6840
+ const kept = [];
6841
+ let keeping = true;
6842
+ let inBlock = false;
6843
+ for (const line of lines) {
6844
+ const open = BLOCK_OPEN.exec(line);
6845
+ if (open !== null) {
6846
+ inBlock = true;
6847
+ keeping = open[1].split(/\s+/).includes(manager);
6848
+ continue;
6849
+ }
6850
+ if (BLOCK_CLOSE.test(line)) {
6851
+ inBlock = false;
6852
+ keeping = true;
6853
+ continue;
6854
+ }
6855
+ if (!inBlock || keeping) kept.push(line);
6856
+ }
6857
+ let out = kept.join("\n");
6858
+ if (manager !== "pnpm") for (const [from, to] of spellings(manager)) out = out.replaceAll(from, to);
6859
+ return out;
6860
+ }
6861
+ /**
6862
+ * Files a manager's scaffold gains beyond the template tree. npm's `.npmrc`
6863
+ * carries the denial half of the posture and DISCLOSES the missing half; bun
6864
+ * needs no file — denial is bun's own default — so its disclosure lives in
6865
+ * the README's lockfile note.
6866
+ */
6867
+ function extraFiles(manager) {
6868
+ if (manager !== "npm") return [];
6869
+ return [[".npmrc", "# Dependency install scripts are denied — the same posture the pnpm\n# scaffold enforces per-package. Flip to false only with a comment naming\n# what breaks without it.\n#\n# What npm cannot give you is pnpm's 48-hour quarantine on newly\n# published dependency versions (minimumReleaseAge): under npm a routine\n# install can pick up a day-zero compromised release the day it ships.\n# That protection exists only under pnpm.\nignore-scripts=true\n"]];
6870
+ }
6871
+ //#endregion
6726
6872
  //#region src/init/materialize.ts
6727
6873
  const EMITTED_NAMES = /* @__PURE__ */ new Map([
6728
6874
  ["gitignore", ".gitignore"],
@@ -6755,9 +6901,14 @@ function isTextFile(file) {
6755
6901
  * children, so a caller that cannot rename-over (the `init .` form) can undo
6756
6902
  * a half-written tree in reverse order.
6757
6903
  */
6758
- function materialize(templateDir, targetDir, stamps, created = []) {
6904
+ function materialize(templateDir, targetDir, stamps, manager = "pnpm", created = []) {
6905
+ materializeTree(templateDir, targetDir, stamps, manager, created, true);
6906
+ return created;
6907
+ }
6908
+ function materializeTree(templateDir, targetDir, stamps, manager, created, isRoot) {
6759
6909
  for (const entry of readdirSync(templateDir, { withFileTypes: true })) {
6760
6910
  if (entry.name === "node_modules") continue;
6911
+ if (isRoot && isSkippedFor(entry.name, manager)) continue;
6761
6912
  const from = path.join(templateDir, entry.name);
6762
6913
  const to = path.join(targetDir, EMITTED_NAMES.get(entry.name) ?? entry.name);
6763
6914
  if (entry.isDirectory()) {
@@ -6765,9 +6916,10 @@ function materialize(templateDir, targetDir, stamps, created = []) {
6765
6916
  mkdirSync(to, { recursive: true });
6766
6917
  created.push(to);
6767
6918
  }
6768
- materialize(from, to, stamps, created);
6919
+ materializeTree(from, to, stamps, manager, created, false);
6769
6920
  } else if (isTextFile(from)) {
6770
- const text = readFileSync(from, "utf8").replaceAll("KSOR-STAMP-NAME", stamps.name).replaceAll("KSOR-STAMP-VERSION", stamps.version);
6921
+ const stamped = readFileSync(from, "utf8").replaceAll("KSOR-STAMP-NAME", stamps.name).replaceAll("KSOR-STAMP-VERSION", stamps.version);
6922
+ const text = isRoot && entry.name === "package.json" ? transformManifest(stamped, manager) : applyProse(stamped, manager);
6771
6923
  created.push(to);
6772
6924
  writeFileSync(to, text);
6773
6925
  } else {
@@ -6775,6 +6927,14 @@ function materialize(templateDir, targetDir, stamps, created = []) {
6775
6927
  copyFileSync(from, to);
6776
6928
  }
6777
6929
  }
6930
+ }
6931
+ /** Emit the files a manager's scaffold gains beyond the template tree. */
6932
+ function materializeExtras(targetDir, manager, created = []) {
6933
+ for (const [name, content] of extraFiles(manager)) {
6934
+ const to = path.join(targetDir, name);
6935
+ created.push(to);
6936
+ writeFileSync(to, content);
6937
+ }
6778
6938
  return created;
6779
6939
  }
6780
6940
  //#endregion
@@ -6906,14 +7066,22 @@ function gitInit(dir, io) {
6906
7066
  io.err(`note: git init failed: ${detail}\n`);
6907
7067
  }
6908
7068
  }
6909
- function handoff(io, name, targetWasDot) {
7069
+ function handoff(io, name, targetWasDot, manager) {
6910
7070
  const enter = targetWasDot ? "" : ` cd ${name}\n`;
7071
+ const run = (script) => manager === "pnpm" ? `pnpm ${script}` : manager === "npm" ? `npm run ${script}` : `bun run ${script}`;
7072
+ const install = manager === "pnpm" ? "pnpm install" : `${manager} install`;
7073
+ const pnpmHint = manager === "pnpm" ? "no pnpm? run: npm install -g pnpm — or `corepack enable pnpm` on Nodes that bundle corepack\n\n" : "";
6911
7074
  io.out(`${name} is ready — your knowledge, your repo, yours outright.\n
6912
7075
  Next (or just tell your coding agent to take it from here):
6913
- ` + enter + " pnpm install\n pnpm dev # the site, live at http://localhost:3000\n\nThen, for the agent surface (needs Postgres and a provider key):\n pnpm provision # once: uncomment `database:` in instance.md, copy\n # .env.example to .env, then apply the schema\n pnpm refresh # PUBLISH the record — ingest knowledge/ into a generation\n pnpm serve # the MCP server, over what you just published\n\nno pnpm? run: npm install -g pnpm — or `corepack enable pnpm` on Nodes that bundle corepack\n\nStart in knowledge/ — AGENTS.md carries the working rules.\n");
7076
+ ` + enter + ` ${install}\n ${run("dev").padEnd(15)} # the site, live at http://localhost:3000\n
7077
+ Then, for the agent surface (needs Postgres and a provider key):
7078
+ ${run("provision").padEnd(15)} # once: uncomment \`database:\` in instance.md, copy\n # .env.example to .env, then apply the schema
7079
+ ${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
7080
+ ` + pnpmHint + "Start in knowledge/ — AGENTS.md carries the working rules.\n");
6914
7081
  }
6915
7082
  function init(args, cwd, io, env) {
6916
7083
  const { version, templatesDir } = env;
7084
+ const manager = detectManager(env.userAgent);
6917
7085
  if (!existsSync(templatesDir)) return fail(io, "broken-install", [`the ksor package is missing its templates: ${templatesDir}`, "reinstall it — `pnpm add -D @panaversity/ksor`, or `npm i -g @panaversity/ksor`."], exitCodes.environment);
6918
7086
  const word = args[0] ?? null;
6919
7087
  if (word === null) return usage$1(io);
@@ -6946,7 +7114,8 @@ function init(args, cwd, io, env) {
6946
7114
  materialize(templatesDir, targetDir, {
6947
7115
  name,
6948
7116
  version
6949
- }, created);
7117
+ }, manager, created);
7118
+ materializeExtras(targetDir, manager, created);
6950
7119
  } catch (error) {
6951
7120
  rollback(created);
6952
7121
  throw error;
@@ -6957,7 +7126,8 @@ function init(args, cwd, io, env) {
6957
7126
  materialize(templatesDir, stage, {
6958
7127
  name,
6959
7128
  version
6960
- });
7129
+ }, manager);
7130
+ materializeExtras(stage, manager);
6961
7131
  } catch (error) {
6962
7132
  rmSync(stage, {
6963
7133
  recursive: true,
@@ -6990,7 +7160,7 @@ function init(args, cwd, io, env) {
6990
7160
  const detail = error instanceof Error ? error.message : String(error);
6991
7161
  io.err(`note: the project was created, but a follow-up step failed: ${detail}\n`);
6992
7162
  }
6993
- handoff(io, name, isDot);
7163
+ handoff(io, name, isDot, manager);
6994
7164
  return 0;
6995
7165
  }
6996
7166
  function runInit(args, cwd, io, env) {
@@ -7080,7 +7250,8 @@ async function main(args) {
7080
7250
  err: (text) => process.stderr.write(text)
7081
7251
  }, {
7082
7252
  version: pkg.version,
7083
- templatesDir: fileURLToPath(new URL("../templates/scaffold", import.meta.url))
7253
+ templatesDir: fileURLToPath(new URL("../templates/scaffold", import.meta.url)),
7254
+ userAgent: process.env.npm_config_user_agent
7084
7255
  });
7085
7256
  }
7086
7257
  if (verb === "serve") {
package/docs/deploying.md CHANGED
@@ -7,6 +7,11 @@ status: draft
7
7
 
8
8
  ## Before you start
9
9
 
10
+ Commands on this page use the pnpm spelling (`pnpm build`, `pnpm serve`).
11
+ Since 0.0.36, `ksor init` emits the scaffold for the manager that ran it —
12
+ npm and bun included — and your scaffold's own README speaks that manager;
13
+ translate accordingly (`npm run build`, `bun run build`).
14
+
10
15
  Four things must exist, and the order matters. Nothing below works without them,
11
16
  and three of the four are outside this page.
12
17
 
package/docs/index.md CHANGED
@@ -19,7 +19,10 @@ instead of their training memory. The corpus grows with each implemented verb.
19
19
  `.claude/skills/` copies), adopter CI, and a dependency-free format
20
20
  checker (`pnpm check`). `ksor init .` scaffolds into an empty directory
21
21
  whose name passes the project-name grammar. Everything emitted belongs to
22
- the adopter (templates are MIT-0).
22
+ the adopter (templates are MIT-0). The scaffold is emitted for the package
23
+ manager that ran init — `npx …` yields an npm project, `bunx …` a bun one,
24
+ `pnpm dlx …` (or a bare `ksor`) the pnpm shape — so the commands below use
25
+ the pnpm spelling and your own scaffold's README speaks your manager.
23
26
  - Inside a scaffolded project, `pnpm install && pnpm dev` serves the record
24
27
  at `http://localhost:3000`; `pnpm build` writes a fully static export to
25
28
  `system/site/out/`. `KSOR_BASE_PATH=/repo pnpm build` targets sub-path
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@panaversity/ksor",
3
- "version": "0.0.35",
3
+ "version": "0.0.37",
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",
@@ -112,7 +112,8 @@ Stand it up in this order (each step's errors explain how to fix themselves):
112
112
 
113
113
  `provision` is separate on purpose: applying DDL and granting ingest are acts
114
114
  an operator performs, not side effects of starting a server. (It is not
115
- called `setup` because `pnpm setup` is pnpm's own command and would shadow
115
+ called `setup` because package managers claim that word for commands of
116
+ their own, which would shadow
116
117
  it — the step would print "No changes to the environment were made" and do
117
118
  nothing.)
118
119
 
@@ -18,8 +18,11 @@ pnpm install
18
18
  pnpm dev # browse the knowledge at http://localhost:3000
19
19
  ```
20
20
 
21
+ <!-- ksor:pm pnpm -->
21
22
  No pnpm? Run `npm install -g pnpm` — or `corepack enable pnpm` on Node
22
- versions that bundle corepack. The first `pnpm install` also fetches the
23
+ versions that bundle corepack.
24
+ <!-- /ksor:pm -->
25
+ The first `pnpm install` also fetches the
23
26
  `ksor` tool (pinned in `package.json`) and writes it into your lockfile —
24
27
  commit the updated lockfile.
25
28
 
@@ -128,6 +131,7 @@ the record well-formed (`format-checker`, also `pnpm check`).
128
131
 
129
132
  ### A note on the lockfile
130
133
 
134
+ <!-- ksor:pm pnpm -->
131
135
  The committed `pnpm-lock.yaml` covers the site. It cannot cover
132
136
  `@panaversity/ksor` itself, because the version pinned in `package.json` is
133
137
  stamped by the CLI that scaffolded this project and could not be resolved before
@@ -138,6 +142,34 @@ The deploy config already accounts for this (`vercel.json` installs with
138
142
  `--no-frozen-lockfile`), and the shipped `validate.yml` runs no install. If you
139
143
  add CI of your own, note that pnpm turns on `--frozen-lockfile` automatically
140
144
  whenever `CI` is set.
145
+ <!-- /ksor:pm -->
146
+ <!-- ksor:pm npm -->
147
+ No lockfile ships with this scaffold: npm keeps ONE lock for the whole
148
+ workspace, and the `@panaversity/ksor` version pinned in `package.json` was
149
+ stamped by the CLI that scaffolded this project — it could not be resolved
150
+ into a lock before it existed. Your FIRST `npm install` writes
151
+ `package-lock.json`; run it before you push, and COMMIT the result — that
152
+ lock is why two machines build the same site.
153
+
154
+ One honest difference from the pnpm scaffold: pnpm quarantines newly
155
+ published dependency versions for 48 hours (`minimumReleaseAge`), so a
156
+ routine install never picks up a day-zero compromised release. npm has no
157
+ equivalent — `.npmrc` here carries the install-script denial half of that
158
+ posture, and this sentence is the disclosure of the half it cannot.
159
+ <!-- /ksor:pm -->
160
+ <!-- ksor:pm bun -->
161
+ No lockfile ships with this scaffold: the `@panaversity/ksor` version pinned
162
+ in `package.json` was stamped by the CLI that scaffolded this project — it
163
+ could not be resolved into a lock before it existed. Your FIRST
164
+ `bun install` writes `bun.lock`; run it before you push, and COMMIT the
165
+ result — that lock is why two machines build the same site.
166
+
167
+ One honest difference from the pnpm scaffold: pnpm quarantines newly
168
+ published dependency versions for 48 hours (`minimumReleaseAge`), so a
169
+ routine install never picks up a day-zero compromised release. bun has no
170
+ equivalent (its default refusal of dependency install scripts covers the
171
+ OTHER half of that posture), and this sentence is the disclosure.
172
+ <!-- /ksor:pm -->
141
173
 
142
174
  ## The files, explained
143
175
 
@@ -158,9 +190,18 @@ different coding agent's way of finding the same working contract.
158
190
  | `.gitattributes` | markdown is checked out byte-stable on every platform, so the same commit hashes the same everywhere. |
159
191
  | `.env.example` | the variables the served rung needs; copy to `.env` (gitignored) and fill in. |
160
192
  | `.gitignore` | keeps build output, `node_modules/`, and `.env` out of the record's history. |
161
- | `package.json` | the surface commands — `pnpm dev` (the site) and `pnpm provision` / `pnpm refresh` / `pnpm serve` (the agent surface: set up once, publish, then serve) — plus `pnpm build` / `pnpm check`, the pinned `@panaversity/ksor` tool, and the pnpm version this project pins. |
193
+ | `package.json` | the surface commands — `pnpm dev` (the site) and `pnpm provision` / `pnpm refresh` / `pnpm serve` (the agent surface: set up once, publish, then serve) — plus `pnpm build` / `pnpm check`, the pinned `@panaversity/ksor` tool and the workspace layout the manifest declares. |
194
+ <!-- ksor:pm pnpm -->
162
195
  | `pnpm-workspace.yaml` | where the workspace looks for code (`system/site`, plus reserved `system/gateways/*` and `system/packages/*`), and the supply-chain policy for installs. |
163
196
  | `pnpm-lock.yaml` | the exact dependency versions — the reason two machines build the same site. |
197
+ <!-- /ksor:pm -->
198
+ <!-- ksor:pm npm -->
199
+ | `.npmrc` | dependency install scripts are denied; the comment inside discloses the one protection this scaffold lacks (a 48-hour quarantine on new releases). |
200
+ | `package-lock.json` | the exact dependency versions — written by your FIRST install; commit it, it is the reason two machines build the same site. |
201
+ <!-- /ksor:pm -->
202
+ <!-- ksor:pm bun -->
203
+ | `bun.lock` | the exact dependency versions — written by your FIRST install; commit it, it is the reason two machines build the same site. |
204
+ <!-- /ksor:pm -->
164
205
 
165
206
  `format-checker` deliberately contains a program, `check.mjs`, and not only
166
207
  prose: rules that are only written down cannot refuse anything. `pnpm check`
@@ -181,9 +222,11 @@ and anything that can serve files can serve it.
181
222
  (never pin `system/site` as the root directory — the record lives
182
223
  outside it), build with `pnpm build`, serve `system/site/out/`. It also
183
224
  declares the MCP **door** as a second service built from the shipped
184
- `Dockerfile`, so `/mcp` and the site share one domain. If the
185
- build image's pnpm predates the `packageManager` pin, set the
225
+ `Dockerfile`, so `/mcp` and the site share one domain.
226
+ <!-- ksor:pm pnpm -->
227
+ If the build image's pnpm predates the `packageManager` pin, set the
186
228
  `ENABLE_EXPERIMENTAL_COREPACK=1` build environment variable.
229
+ <!-- /ksor:pm -->
187
230
  **Once `instance.md` declares a `database:`, the BUILD needs the DSN too.**
188
231
  `pnpm build` first runs `pnpm export-denylist`, which asks the record's
189
232
  database what has been withdrawn (`ksor takedown --export`) and writes